diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index d1ab071..e5540d0 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -12,7 +12,6 @@ namespace Mosparo\MosparoBundle\DependencyInjection; -use Ramsey\Uuid\Uuid; use Symfony\Component\Config\Definition\Builder\TreeBuilder; use Symfony\Component\Config\Definition\ConfigurationInterface; @@ -36,7 +35,6 @@ public function getConfigTreeBuilder(): TreeBuilder if (\array_key_exists('projects', $v) || \array_key_exists('project', $v)) { return false; } - // Is there actually anything to use once excluded keys are considered? return (bool) array_diff_key($v, $excludedKeys); }) @@ -46,7 +44,6 @@ public function getConfigTreeBuilder(): TreeBuilder $project[$key] = $v[$key]; unset($v[$key]); } - $v['projects'] = [($v['default_project'] ?? 'default') => $project]; return $v; @@ -56,27 +53,13 @@ public function getConfigTreeBuilder(): TreeBuilder ->booleanNode('enabled')->defaultTrue()->end() ->scalarNode('default_project')->defaultValue('default')->end() ->arrayNode('projects') - ->isRequired() - ->requiresAtLeastOneElement() ->useAttributeAsKey('name') ->arrayPrototype() ->children() - ->scalarNode('instance_url') - ->isRequired() - ->validate() - ->ifTrue(static fn (string $value) => false === filter_var($value, \FILTER_VALIDATE_URL)) - ->thenInvalid('"instance_url" is not a valid URL') - ->end() - ->end() - ->scalarNode('uuid') - ->isRequired() - ->validate() - ->ifTrue(static fn (string $value) => true !== Uuid::isValid($value)) - ->thenInvalid('"uuid" is not a valid UUID') - ->end() - ->end() - ->scalarNode('public_key')->isRequired()->cannotBeEmpty()->end() - ->scalarNode('private_key')->isRequired()->cannotBeEmpty()->end() + ->scalarNode('instance_url')->isRequired()->end() + ->scalarNode('uuid')->isRequired()->end() + ->scalarNode('public_key')->isRequired()->end() + ->scalarNode('private_key')->isRequired()->end() ->booleanNode('verify_ssl')->defaultTrue()->end() ->end() ->end() diff --git a/src/Form/Type/MosparoType.php b/src/Form/Type/MosparoType.php index 253f2ba..9a7e4de 100644 --- a/src/Form/Type/MosparoType.php +++ b/src/Form/Type/MosparoType.php @@ -12,7 +12,9 @@ namespace Mosparo\MosparoBundle\Form\Type; +use Mosparo\ApiClient\Exception; use Mosparo\MosparoBundle\Validator\IsValidMosparo; +use Ramsey\Uuid\Uuid; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormInterface; @@ -58,12 +60,27 @@ public function configureOptions(OptionsResolver $resolver): void $resolver->setAllowedTypes('cssResourceUrl', 'string'); } + /** + * @throws Exception + */ public function buildView(FormView $view, FormInterface $form, array $options): void { + $hostKey = sprintf('mosparo.%s.%s', $options['project'], 'instance_url'); + $host = $this->parameters->get($hostKey); + if (false === filter_var($host, \FILTER_VALIDATE_URL)) { + throw new Exception(sprintf('Please check your "%s". "%s" is not a valid URL', $hostKey, $host)); + } + + $uuidKey = sprintf('mosparo.%s.%s', $options['project'], 'uuid'); + $uuid = $this->parameters->get($uuidKey); + if (false === Uuid::isValid($uuid)) { + throw new Exception(sprintf('Please check your "%s". "%s" is not a valid UUID', $uuidKey, $uuid)); + } + $view->vars['mosparo'] = [ 'enabled' => $this->enabled, - 'instance_url' => $this->parameters->get(sprintf('mosparo.%s.%s', $options['project'], 'instance_url')), - 'uuid' => $this->parameters->get(sprintf('mosparo.%s.%s', $options['project'], 'uuid')), + 'instance_url' => $host, + 'uuid' => $uuid, 'public_key' => $this->parameters->get(sprintf('mosparo.%s.%s', $options['project'], 'public_key')), 'private_key' => $this->parameters->get(sprintf('mosparo.%s.%s', $options['project'], 'private_key')), 'options' => [ diff --git a/src/Resources/config/services.yaml b/src/Resources/config/services.yaml index 411c8d3..d63ad5a 100755 --- a/src/Resources/config/services.yaml +++ b/src/Resources/config/services.yaml @@ -23,8 +23,7 @@ services: tags: - { name: serializer.normalizer } - Mosparo\MosparoBundle\Serializer\FormNormalizer: - alias: mosparo.form.normalizer + Mosparo\MosparoBundle\Serializer\FormNormalizer: '@mosparo.form.normalizer' Mosparo\MosparoBundle\Validator\IsValidMosparoValidator: arguments: diff --git a/src/Services/MosparoClient.php b/src/Services/MosparoClient.php index a6638b1..43aa4cd 100644 --- a/src/Services/MosparoClient.php +++ b/src/Services/MosparoClient.php @@ -13,13 +13,21 @@ namespace Mosparo\MosparoBundle\Services; use Mosparo\ApiClient\Client; +use Mosparo\ApiClient\Exception; class MosparoClient extends Client { private static MosparoClient $instance; + /** + * @throws Exception + */ public static function make(string $host, string $publicKey, string $privateKey, bool $verifySsl = true): self { + if (false === filter_var($host, \FILTER_VALIDATE_URL)) { + throw new Exception(sprintf('Please check yours "instance_url". "%s" is not a valid URL', $host)); + } + if (empty(self::$instance)) { self::$instance = new self( $host, diff --git a/src/Validator/IsValidMosparoValidator.php b/src/Validator/IsValidMosparoValidator.php index 5ea0577..9e6ac9f 100644 --- a/src/Validator/IsValidMosparoValidator.php +++ b/src/Validator/IsValidMosparoValidator.php @@ -12,12 +12,12 @@ namespace Mosparo\MosparoBundle\Validator; -use Mosparo\MosparoBundle\Serializer\FormNormalizer; use Mosparo\MosparoBundle\Services\MosparoClient; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; use Symfony\Component\Form\Form; use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\Serializer\Exception\ExceptionInterface; +use Symfony\Component\Serializer\Normalizer\NormalizerInterface; use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\ConstraintValidator; use Symfony\Component\Validator\Exception\UnexpectedTypeException; @@ -27,7 +27,7 @@ class IsValidMosparoValidator extends ConstraintValidator public function __construct( private RequestStack $requestStack, private ParameterBagInterface $parameters, - private FormNormalizer $normalizer, + private NormalizerInterface $normalizer, private bool $enabled = true, ) { } @@ -69,15 +69,16 @@ public function validate($value, Constraint $constraint): void if (!$form instanceof Form) { throw new UnexpectedTypeException($form, Form::class); } - $field = $form->get($this->context->getPropertyPath()); + + $field = $this->context->getObject(); $project = $field->getConfig() ->getOption('project', $this->parameters->get('mosparo.default_project')) ; - [ - 'formData' => $formData, - 'requiredFields' => $requiredFields, - 'verifiableFields' => $verifiableFields - ] = $this->normalizer->normalize($form); + $normalizedDatas = $this->normalizer->normalize($form); + + $formData = $normalizedDatas['formData'] ?? []; + $requiredFields = $normalizedDatas['requiredFields'] ?? []; + $verifiableFields = $normalizedDatas['verifiableFields'] ?? []; $result = $this ->getClient($project) @@ -108,7 +109,7 @@ public function validate($value, Constraint $constraint): void $this->context->buildViolation($constraint::VERIFICATION_FAILED) ->addViolation() ; - } catch (\Exception|ExceptionInterface $e) { + } catch (\Exception|ExceptionInterface) { $this->context->buildViolation($constraint::ERROR) ->addViolation() ; diff --git a/tests/Form/MosparoTypeTest.php b/tests/Form/MosparoTypeTest.php index 2690eb9..73a3250 100644 --- a/tests/Form/MosparoTypeTest.php +++ b/tests/Form/MosparoTypeTest.php @@ -12,6 +12,7 @@ namespace Mosparo\MosparoBundle\Tests\Form; +use Mosparo\ApiClient\Exception; use Mosparo\MosparoBundle\DependencyInjection\MosparoExtension; use Mosparo\MosparoBundle\Form\Type\MosparoType; use Symfony\Component\DependencyInjection\ContainerBuilder; @@ -57,6 +58,16 @@ protected function getSampleConfig() uuid: df056fb7-04a1-4d12-abde-70b75ece3847 public_key: xo0EZEo5eAEEAMVGSnNwqDdaMTZLxY private_key: xcFGBGRKOXgBBADFRkpzcKg3WjE2S8WPpXAVNdU + bad_instance: + instance_url: lorem-ipsum + uuid: df056fb7-04a1-4d12-abde-70b75ece3847 + public_key: xo0EZEo5eAEEAMVGSnNwqDdaMTZLxY + private_key: xcFGBGRKOXgBBADFRkpzcKg3WjE2S8WPpXAVNdU + bad_uuid: + instance_url: https://example.com + uuid: azerty-04a1-4d12-abde-70b75ece3847 + public_key: xo0EZEo5eAEEAMVGSnNwqDdaMTZLxY + private_key: xcFGBGRKOXgBBADFRkpzcKg3WjE2S8WPpXAVNdU EOF; return (new Parser())->parse($yaml); @@ -89,6 +100,26 @@ public function testDefaultOptions(): void self::assertTrue($view->vars['mosparo']['enabled']); } + public function testInstanceIsNotURLOptions(): void + { + $this->expectException(Exception::class); + $this->expectExceptionMessage('Please check your "mosparo.bad_instance.instance_url". "lorem-ipsum" is not a valid URL'); + + $this->factory->create(MosparoType::class, [], [ + 'project' => 'bad_instance', + ])->createView(); + } + + public function testUuidIsNotValidOptions(): void + { + $this->expectException(Exception::class); + $this->expectExceptionMessage('Please check your "mosparo.bad_uuid.uuid". "azerty-04a1-4d12-abde-70b75ece3847" is not a valid UUID'); + + $this->factory->create(MosparoType::class, [], [ + 'project' => 'bad_uuid', + ])->createView(); + } + public function testProjectOptions(): void { $form = $this->factory->create(MosparoType::class, [], [ diff --git a/tests/Serializer/FormNormalizerTest.php b/tests/Serializer/FormNormalizerTest.php index e088acd..ce5a7eb 100644 --- a/tests/Serializer/FormNormalizerTest.php +++ b/tests/Serializer/FormNormalizerTest.php @@ -96,13 +96,13 @@ public function testNormalize(): void { $expected = [ 'formData' => [ - 'name[name]' => 'John Example', + 'form[name]' => 'John Example', ], 'requiredFields' => [ - 'name[name]', + 'form[name]', ], 'verifiableFields' => [ - 'name[name]', + 'form[name]', ], ]; @@ -119,18 +119,18 @@ public function testNormalizeWithTypeOverride(): void { $expected = [ 'formData' => [ - 'name[name]' => 'John Example', - 'name[password]' => 'password', - 'name[optional]' => null, + 'form[name]' => 'John Example', + 'form[password]' => 'password', + 'form[optional]' => null, ], 'requiredFields' => [ - 'name[name]', - 'name[password]', + 'form[name]', + 'form[password]', ], 'verifiableFields' => [ - 'name[name]', - 'name[password]', - 'name[optional]', + 'form[name]', + 'form[password]', + 'form[optional]', ], ]; @@ -175,19 +175,19 @@ public function testNormalizeWithChildren(): void { $expected = [ 'formData' => [ - 'name[name]' => 'John Example', - 'name[collection][0]' => 'Entry 1', - 'name[collection][1]' => 'Entry 2', + 'form[name]' => 'John Example', + 'form[collection][0]' => 'Entry 1', + 'form[collection][1]' => 'Entry 2', ], 'requiredFields' => [ - 'name[name]', - 'name[collection][0]', - 'name[collection][1]', + 'form[name]', + 'form[collection][0]', + 'form[collection][1]', ], 'verifiableFields' => [ - 'name[name]', - 'name[collection][0]', - 'name[collection][1]', + 'form[name]', + 'form[collection][0]', + 'form[collection][1]', ], ]; diff --git a/tests/Services/MosparoClientTest.php b/tests/Services/MosparoClientTest.php index 4f8efe7..0f9a986 100644 --- a/tests/Services/MosparoClientTest.php +++ b/tests/Services/MosparoClientTest.php @@ -57,6 +57,14 @@ public function testSingletonInstance(): void self::assertInstanceOf(MosparoClient::class, $apiClient); } + public function testSingletonBadHostInstance(): void + { + $this->expectException(Exception::class); + $this->expectExceptionMessage('Please check yours "instance_url". "example" is not a valid URL'); + + MosparoClient::make('example', self::PUBLIC_KEY, self::PRIVATE_KEY); + } + public function testVerifySubmissionWithoutTokens(): void { $this->expectException(Exception::class); diff --git a/tests/Traits/FormTrait.php b/tests/Traits/FormTrait.php index e6189c7..799b7ad 100644 --- a/tests/Traits/FormTrait.php +++ b/tests/Traits/FormTrait.php @@ -50,7 +50,7 @@ private function getBuilder(string $name = 'name', string $dataClass = null, arr private function getCompoundForm($data, array $options = []): \Symfony\Component\Form\FormInterface { - return $this->getBuilder('name', \is_object($data) ? $data::class : null, $options) + return $this->getBuilder('form', \is_object($data) ? $data::class : null, $options) ->setData($data) ->setCompound(true) ->setDataMapper(new DataMapper()) diff --git a/tests/Validator/IsValidMosparoValidatorTest.php b/tests/Validator/IsValidMosparoValidatorTest.php index 505c39f..f955d3c 100644 --- a/tests/Validator/IsValidMosparoValidatorTest.php +++ b/tests/Validator/IsValidMosparoValidatorTest.php @@ -154,6 +154,7 @@ public function testIsValid(): void ->add('mosparo', MosparoType::class) ; + $this->setObject($form->get('mosparo')); $this->setPropertyPath('mosparo'); $form->submit(['name' => 'John Example', 'submit' => '']); @@ -162,7 +163,7 @@ public function testIsValid(): void true, true, [ - 'name[name]' => VerificationResult::FIELD_VALID, + 'form[name]' => VerificationResult::FIELD_VALID, ] ); @@ -177,6 +178,7 @@ public function testValidIfNotEnabled(): void ->add('mosparo', MosparoType::class) ; + $this->setObject($form->get('mosparo')); $this->setPropertyPath('mosparo'); $form->submit(['name' => 'John Example', 'submit' => '']); @@ -185,7 +187,7 @@ public function testValidIfNotEnabled(): void false, false, [ - 'name[name]' => VerificationResult::FIELD_INVALID, + 'form[name]' => VerificationResult::FIELD_INVALID, ], [], self::SUBMIT_TOKEN, @@ -204,6 +206,7 @@ public function testIsInvalid(): void ->add('mosparo', MosparoType::class) ; + $this->setObject($form->get('mosparo')); $this->setPropertyPath('mosparo'); $form->submit(['submit' => '']); @@ -212,10 +215,10 @@ public function testIsInvalid(): void false, false, [ - 'name[name]' => VerificationResult::FIELD_INVALID, + 'form[name]' => VerificationResult::FIELD_INVALID, ], [ - ['name' => 'name[name]', 'message' => 'Missing in form data, verification not possible.'], + ['name' => 'form[name]', 'message' => 'Missing in form data, verification not possible.'], ] ); @@ -238,6 +241,7 @@ public function testIsNested(): void ->add('mosparo', MosparoType::class) ; + $this->setObject($form->get('mosparo')); $this->setPropertyPath('mosparo'); $form->submit( @@ -255,9 +259,9 @@ public function testIsNested(): void true, true, [ - 'name[name]' => VerificationResult::FIELD_VALID, - 'name[collection][0]' => VerificationResult::FIELD_VALID, - 'name[collection][1]' => VerificationResult::FIELD_VALID, + 'form[name]' => VerificationResult::FIELD_VALID, + 'form[collection][0]' => VerificationResult::FIELD_VALID, + 'form[collection][1]' => VerificationResult::FIELD_VALID, ] ); $this->validator->validate(null, new IsValidMosparo()); @@ -284,6 +288,7 @@ public function testWithoutTokens(): void ->add('mosparo', MosparoType::class) ; + $this->setObject($form->get('mosparo')); $this->setPropertyPath('mosparo'); $form->submit(['name' => 'John Example', 'submit' => '']); @@ -292,7 +297,7 @@ public function testWithoutTokens(): void true, true, [ - 'name[name]' => VerificationResult::FIELD_VALID, + 'form[name]' => VerificationResult::FIELD_VALID, ], [], null, @@ -310,6 +315,7 @@ public function testVerificationFailed(): void ->add('mosparo', MosparoType::class) ; + $this->setObject($form->get('mosparo')); $this->setPropertyPath('mosparo'); $form->submit(['name' => 'John Example', 'submit' => '']); @@ -318,7 +324,7 @@ public function testVerificationFailed(): void false, false, [ - 'name[name]' => VerificationResult::FIELD_INVALID, + 'form[name]' => VerificationResult::FIELD_INVALID, ] );