diff --git a/src/bundle/Controller/PasswordChangeController.php b/src/bundle/Controller/PasswordChangeController.php index 54161a6..174b7f1 100644 --- a/src/bundle/Controller/PasswordChangeController.php +++ b/src/bundle/Controller/PasswordChangeController.php @@ -66,7 +66,9 @@ public function __construct( */ public function userPasswordChangeAction(Request $request) { - $form = $this->formFactory->changeUserPassword(); + /** @var \eZ\Publish\API\Repository\Values\User\User $user */ + $user = $this->tokenStorage->getToken()->getUser()->getAPIUser(); + $form = $this->formFactory->changeUserPassword($user->getContentType()); $form->handleRequest($request); if ($form->isSubmitted() && $form->isValid()) { @@ -76,7 +78,6 @@ public function userPasswordChangeAction(Request $request) $newPassword = $data->getNewPassword(); $userUpdateStruct = $this->userService->newUserUpdateStruct(); $userUpdateStruct->password = $newPassword; - $user = $this->tokenStorage->getToken()->getUser()->getAPIUser(); $this->userService->updateUser($user, $userUpdateStruct); if ((new IsAdmin($this->siteAccessGroups))->isSatisfiedBy($request->attributes->get('siteaccess'))) { diff --git a/src/bundle/Controller/PasswordResetController.php b/src/bundle/Controller/PasswordResetController.php index 66d6858..31b3d73 100644 --- a/src/bundle/Controller/PasswordResetController.php +++ b/src/bundle/Controller/PasswordResetController.php @@ -166,8 +166,8 @@ public function userResetPasswordAction(Request $request, string $hashKey) return $view; } - $userPasswordResetData = new UserPasswordResetData(null, $user->getContentType()); - $form = $this->formFactory->resetUserPassword($userPasswordResetData); + $userPasswordResetData = new UserPasswordResetData(); + $form = $this->formFactory->resetUserPassword($userPasswordResetData, null, $user->getContentType()); $form->handleRequest($request); if ($form->isSubmitted() && $form->isValid()) { diff --git a/src/bundle/Resources/config/routing.yaml b/src/bundle/Resources/config/routing.yaml index 8000c50..66da23e 100644 --- a/src/bundle/Resources/config/routing.yaml +++ b/src/bundle/Resources/config/routing.yaml @@ -22,18 +22,6 @@ ezplatform.user.reset_password: defaults: _controller: 'EzSystems\EzPlatformUserBundle\Controller\PasswordResetController::userResetPasswordAction' -# Deprecated in v2.5 and will be removed in v3.0. Use ezplatform.user.register instead. -ez_user_register: - path: /register - defaults: - _controller: 'EzSystems\EzPlatformUserBundle\Controller\UserRegisterController::registerAction' - -# Deprecated in v2.5 and will be removed in v3.0. Use ezplatform.user.register_confirmation instead. -ez_user_register_confirmation: - path: /register-confirm - defaults: - _controller: 'EzSystems\EzPlatformUserBundle\Controller\UserRegisterController::registerConfirmAction' - ezplatform.user.register: &user_register path: /register defaults: diff --git a/src/bundle/Resources/config/services/validators.yaml b/src/bundle/Resources/config/services/validators.yaml index cc164a8..bfa4022 100644 --- a/src/bundle/Resources/config/services/validators.yaml +++ b/src/bundle/Resources/config/services/validators.yaml @@ -10,3 +10,9 @@ services: EzSystems\EzPlatformUser\Validator\Constraints\UserPasswordValidator: tags: - { name: validator.constraint_validator } + + EzSystems\EzPlatformUser\Validator\Constraints\PasswordValidator: + arguments: + $userService: '@ezpublish.api.service.user' + tags: + - { name: validator.constraint_validator } diff --git a/src/lib/Form/Data/UserPasswordResetData.php b/src/lib/Form/Data/UserPasswordResetData.php index f36fd43..1875a81 100644 --- a/src/lib/Form/Data/UserPasswordResetData.php +++ b/src/lib/Form/Data/UserPasswordResetData.php @@ -21,6 +21,8 @@ class UserPasswordResetData private $newPassword; /** + * @deprecated ContentType should be passed as option to FormType. + * * @var \eZ\Publish\API\Repository\Values\ContentType\ContentType */ private $contentType; diff --git a/src/lib/Form/Data/UserRegisterData.php b/src/lib/Form/Data/UserRegisterData.php index 58ad62e..9c3fc0f 100644 --- a/src/lib/Form/Data/UserRegisterData.php +++ b/src/lib/Form/Data/UserRegisterData.php @@ -8,8 +8,8 @@ namespace EzSystems\EzPlatformUser\Form\Data; -use EzSystems\EzPlatformContentForms\Data as RepositoryFormsData; +use EzSystems\EzPlatformContentForms\Data\User\UserCreateData; -class UserRegisterData extends RepositoryFormsData\User\UserRegisterData +class UserRegisterData extends UserCreateData { } diff --git a/src/lib/Form/Factory/FormFactory.php b/src/lib/Form/Factory/FormFactory.php index cc416d4..d49b1f4 100644 --- a/src/lib/Form/Factory/FormFactory.php +++ b/src/lib/Form/Factory/FormFactory.php @@ -8,6 +8,7 @@ namespace EzSystems\EzPlatformUser\Form\Factory; +use eZ\Publish\API\Repository\Values\ContentType\ContentType; use EzSystems\EzPlatformUser\Form\Data\UserPasswordForgotData; use EzSystems\EzPlatformUser\Form\Data\UserPasswordChangeData; use EzSystems\EzPlatformUser\Form\Data\UserSettingUpdateData; @@ -41,19 +42,19 @@ public function __construct(FormFactoryInterface $formFactory, UrlGeneratorInter $this->urlGenerator = $urlGenerator; } - /** - * @param \EzSystems\EzPlatformUser\Form\Data\UserPasswordChangeData|null $data - * @param string|null $name - * - * @return \Symfony\Component\Form\FormInterface - */ public function changeUserPassword( + ContentType $contentType, UserPasswordChangeData $data = null, ?string $name = null ): FormInterface { $name = $name ?: StringUtil::fqcnToBlockPrefix(UserPasswordChangeType::class); - return $this->formFactory->createNamed($name, UserPasswordChangeType::class, $data); + return $this->formFactory->createNamed( + $name, + UserPasswordChangeType::class, + $data, + ['content_type' => $contentType] + ); } /** @@ -100,11 +101,14 @@ public function forgotUserPasswordWithLogin( */ public function resetUserPassword( UserPasswordResetData $data = null, - ?string $name = null + ?string $name = null, + ContentType $contentType = null ): FormInterface { $name = $name ?: StringUtil::fqcnToBlockPrefix(UserPasswordResetType::class); - return $this->formFactory->createNamed($name, UserPasswordResetType::class, $data, ['content_type' => $data->getContentType()]); + $userContentType = $contentType ?? $data->getContentType(); + + return $this->formFactory->createNamed($name, UserPasswordResetType::class, $data, ['content_type' => $userContentType]); } /** diff --git a/src/lib/Form/Type/UserPasswordChangeType.php b/src/lib/Form/Type/UserPasswordChangeType.php index 8bf604d..e86642b 100644 --- a/src/lib/Form/Type/UserPasswordChangeType.php +++ b/src/lib/Form/Type/UserPasswordChangeType.php @@ -8,7 +8,9 @@ namespace EzSystems\EzPlatformUser\Form\Type; +use eZ\Publish\API\Repository\Values\ContentType\ContentType; use EzSystems\EzPlatformUser\Form\Data\UserPasswordChangeData; +use EzSystems\EzPlatformUser\Validator\Constraints\Password; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\PasswordType; use Symfony\Component\Form\Extension\Core\Type\RepeatedType; @@ -31,6 +33,11 @@ public function buildForm(FormBuilderInterface $builder, array $options) 'required' => true, 'first_options' => ['label' => /** @Desc("New password") */ 'ezplatform.change_user_password.new_password'], 'second_options' => ['label' => /** @Desc("Confirm password") */ 'ezplatform.change_user_password.confirm_new_password'], + 'constraints' => [ + new Password([ + 'contentType' => $options['content_type'], + ]), + ], ]) ->add( 'change', @@ -41,6 +48,8 @@ public function buildForm(FormBuilderInterface $builder, array $options) public function configureOptions(OptionsResolver $resolver) { + $resolver->setRequired('content_type'); + $resolver->setAllowedTypes('content_type', ContentType::class); $resolver->setDefaults([ 'data_class' => UserPasswordChangeData::class, 'translation_domain' => 'forms', diff --git a/src/lib/Form/Type/UserPasswordResetType.php b/src/lib/Form/Type/UserPasswordResetType.php index 2df9f4f..c34bc4a 100644 --- a/src/lib/Form/Type/UserPasswordResetType.php +++ b/src/lib/Form/Type/UserPasswordResetType.php @@ -8,8 +8,9 @@ namespace EzSystems\EzPlatformUser\Form\Type; +use eZ\Publish\API\Repository\Values\ContentType\ContentType; use EzSystems\EzPlatformUser\Form\Data\UserPasswordResetData; -use EzSystems\EzPlatformContentForms\Validator\Constraints\Password; +use EzSystems\EzPlatformUser\Validator\Constraints\Password; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\PasswordType; use Symfony\Component\Form\Extension\Core\Type\RepeatedType; @@ -41,10 +42,11 @@ public function buildForm(FormBuilderInterface $builder, array $options) public function configureOptions(OptionsResolver $resolver) { + $resolver->setRequired('content_type'); + $resolver->setAllowedTypes('content_type', ContentType::class); $resolver->setDefaults([ 'data_class' => UserPasswordResetData::class, 'translation_domain' => 'forms', - 'content_type' => null, ]); } } diff --git a/src/lib/Validator/Constraints/Password.php b/src/lib/Validator/Constraints/Password.php new file mode 100644 index 0000000..b547ec2 --- /dev/null +++ b/src/lib/Validator/Constraints/Password.php @@ -0,0 +1,31 @@ +userService = $userService; + } + + public function validate($value, Constraint $constraint): void + { + if (!\is_string($value) || empty($value)) { + return; + } + + $passwordValidationContext = new PasswordValidationContext([ + 'contentType' => $constraint->contentType, + ]); + + $validationErrors = $this->userService->validatePassword($value, $passwordValidationContext); + if (!empty($validationErrors)) { + $validationErrorsProcessor = $this->createValidationErrorsProcessor(); + $validationErrorsProcessor->processValidationErrors($validationErrors); + } + } + + protected function createValidationErrorsProcessor(): ValidationErrorsProcessor + { + return new ValidationErrorsProcessor($this->context); + } +} diff --git a/tests/lib/Validator/Constraint/PasswordTest.php b/tests/lib/Validator/Constraint/PasswordTest.php new file mode 100644 index 0000000..a003f28 --- /dev/null +++ b/tests/lib/Validator/Constraint/PasswordTest.php @@ -0,0 +1,39 @@ +constraint = new Password(); + } + + public function testConstruct(): void + { + $this->assertSame('ez.user.password.invalid', $this->constraint->message); + } + + public function testValidatedBy(): void + { + $this->assertSame(PasswordValidator::class, $this->constraint->validatedBy()); + } + + public function testGetTargets(): void + { + $this->assertSame([Password::CLASS_CONSTRAINT, Password::PROPERTY_CONSTRAINT], $this->constraint->getTargets()); + } +} diff --git a/tests/lib/Validator/Constraint/PasswordValidatorTest.php b/tests/lib/Validator/Constraint/PasswordValidatorTest.php new file mode 100644 index 0000000..adeea7d --- /dev/null +++ b/tests/lib/Validator/Constraint/PasswordValidatorTest.php @@ -0,0 +1,142 @@ +userService = $this->createMock(UserService::class); + $this->executionContext = $this->createMock(ExecutionContextInterface::class); + $this->validator = new PasswordValidator($this->userService); + $this->validator->initialize($this->executionContext); + } + + /** + * @dataProvider dataProviderForValidateNotSupportedValueType + */ + public function testValidateShouldBeSkipped($value): void + { + $this->userService + ->expects($this->never()) + ->method('validatePassword'); + + $this->executionContext + ->expects($this->never()) + ->method('buildViolation'); + + $this->validator->validate($value, new Password()); + } + + public function testValid(): void + { + $password = 'pass'; + $contentType = $this->createMock(ContentType::class); + + $this->userService + ->expects($this->once()) + ->method('validatePassword') + ->willReturnCallback(function (string $actualPassword, PasswordValidationContext $actualContext) use ( + $password, + $contentType + ): array { + $this->assertEquals($password, $actualPassword); + $this->assertInstanceOf(PasswordValidationContext::class, $actualContext); + $this->assertSame($contentType, $actualContext->contentType); + + return []; + }); + + $this->executionContext + ->expects($this->never()) + ->method('buildViolation'); + + $this->validator->validate($password, new Password([ + 'contentType' => $contentType, + ])); + } + + public function testInvalid(): void + { + $contentType = $this->createMock(ContentType::class); + $password = 'pass'; + $errorParameter = 'foo'; + $errorMessage = 'error'; + + $this->userService + ->expects($this->once()) + ->method('validatePassword') + ->willReturnCallback(function (string $actualPassword, PasswordValidationContext $actualContext) use ( + $password, + $contentType, + $errorMessage, + $errorParameter + ): array { + $this->assertEquals($password, $actualPassword); + $this->assertInstanceOf(PasswordValidationContext::class, $actualContext); + $this->assertSame($contentType, $actualContext->contentType); + + return [ + new ValidationError($errorMessage, null, ['%foo%' => $errorParameter]), + ]; + }); + + $constraintViolationBuilder = $this->createMock(ConstraintViolationBuilderInterface::class); + + $this->executionContext + ->expects($this->once()) + ->method('buildViolation') + ->willReturn($constraintViolationBuilder); + $this->executionContext + ->expects($this->once()) + ->method('buildViolation') + ->with($errorMessage) + ->willReturn($constraintViolationBuilder); + $constraintViolationBuilder + ->expects($this->once()) + ->method('setParameters') + ->with(['%foo%' => $errorParameter]) + ->willReturn($constraintViolationBuilder); + $constraintViolationBuilder + ->expects($this->once()) + ->method('addViolation'); + + $this->validator->validate('pass', new Password([ + 'contentType' => $contentType, + ])); + } + + public function dataProviderForValidateNotSupportedValueType(): array + { + return [ + [new \stdClass()], + [null], + [''], + ]; + } +}