diff --git a/src/Controller/ResetPasswordController.php b/src/Controller/ResetPasswordController.php index 6c8bed09..fbb25b20 100644 --- a/src/Controller/ResetPasswordController.php +++ b/src/Controller/ResetPasswordController.php @@ -12,6 +12,7 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Attribute\Route; use Symfony\Component\Security\Http\Attribute\IsGranted; +use Symfony\Component\Validator\Constraints\Length; use Symfony\Component\Validator\Constraints\Email; use Symfony\Component\Validator\Constraints\NotBlank; @@ -82,16 +83,17 @@ class ResetPasswordController extends AbstractController 'type' => PasswordType::class, 'first_options' => [ 'label' => 'Neues Passwort', + 'constraints' => [ + new Length( + min: 8, + minMessage: 'Das Passwort muss mindestens {{ limit }} Zeichen lang sein.', + ), + ], ], 'second_options' => [ 'label' => 'Neues Passwort wiederholen', ], 'invalid_message' => 'Die beiden Passwortangaben müssen identisch sein.', - 'constraints' => [ - new NotBlank([ - 'message' => 'Bitte angeben', - ]), - ], ]) ->getForm() ; diff --git a/src/Form/RegistrationType.php b/src/Form/RegistrationType.php index 352f8288..6878b37a 100644 --- a/src/Form/RegistrationType.php +++ b/src/Form/RegistrationType.php @@ -11,6 +11,9 @@ use Symfony\Component\Form\Extension\Core\Type\RepeatedType; use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Component\Validator\Constraints\Length; +use Symfony\Component\Validator\Constraints\PasswordStrength; +use Symfony\Component\Validator\Constraints\Sequentially; /** @extends AbstractType */ class RegistrationType extends AbstractType @@ -30,6 +33,12 @@ class RegistrationType extends AbstractType 'type' => PasswordType::class, 'first_options' => [ 'label' => 'Passwort', + 'constraints' => [ + new Length( + min: 8, + minMessage: 'Das Passwort muss mindestens {{ limit }} Zeichen lang sein.', + ), + ], 'attr' => [ 'autocomplete' => 'new-password', ], diff --git a/tests/Controller/RegistrationControllerTest.php b/tests/Controller/RegistrationControllerTest.php index 5ee52800..4560870d 100644 --- a/tests/Controller/RegistrationControllerTest.php +++ b/tests/Controller/RegistrationControllerTest.php @@ -23,6 +23,7 @@ class RegistrationControllerTest extends WebTestCase { $client = self::createClient([], [ 'HTTPS' => 'on', + 'HTTP_HX_REQUEST' => 'true', ]); $client->disableReboot(); @@ -54,8 +55,8 @@ class RegistrationControllerTest extends WebTestCase $form = $crawler->selectButton('Jetzt registrieren')->form([ 'registration[firstName]' => 'Customer', 'registration[name]' => 'Example', - 'registration[plainPassword][first]' => 'secret-password', - 'registration[plainPassword][second]' => 'secret-password', + 'registration[plainPassword][first]' => 'Str0ng-Passw0rd!', + 'registration[plainPassword][second]' => 'Str0ng-Passw0rd!', 'registration[personalData][gender]' => 'M', 'registration[personalData][dateOfBirth][day]' => '1', 'registration[personalData][dateOfBirth][month]' => '1', @@ -107,8 +108,8 @@ class RegistrationControllerTest extends WebTestCase $form = $crawler->selectButton('Jetzt registrieren')->form([ 'registration[firstName]' => 'Customer', 'registration[name]' => 'Example', - 'registration[plainPassword][first]' => 'secret-password', - 'registration[plainPassword][second]' => 'secret-password', + 'registration[plainPassword][first]' => 'Str0ng-Passw0rd!', + 'registration[plainPassword][second]' => 'Str0ng-Passw0rd!', 'registration[personalData][gender]' => 'M', 'registration[personalData][dateOfBirth][day]' => '1', 'registration[personalData][dateOfBirth][month]' => '1', @@ -153,8 +154,8 @@ class RegistrationControllerTest extends WebTestCase $form = $crawler->selectButton('Jetzt registrieren')->form([ 'registration[firstName]' => 'Customer', 'registration[name]' => 'Example', - 'registration[plainPassword][first]' => 'secret-password', - 'registration[plainPassword][second]' => 'secret-password', + 'registration[plainPassword][first]' => 'Str0ng-Passw0rd!', + 'registration[plainPassword][second]' => 'Str0ng-Passw0rd!', 'registration[personalData][gender]' => 'M', 'registration[personalData][dateOfBirth][day]' => '1', 'registration[personalData][dateOfBirth][month]' => '1', @@ -171,6 +172,52 @@ class RegistrationControllerTest extends WebTestCase self::assertResponseStatusCodeSame(422); } + public function testMismatchedPasswordRegistrationSubmissionIsRejectedBeforeApiCalls(): void + { + $client = self::createClient([], [ + 'HTTPS' => 'on', + ]); + $client->disableReboot(); + + $userRepository = $this->createMock(UserRepository::class); + $userRepository->expects(self::never())->method('findOneByNormalizedEmail'); + + $rateLimiter = $this->createMock(PublicFormRateLimiter::class); + $rateLimiter->expects(self::never())->method('isRegistrationAllowed'); + + $apiClient = $this->createMock(ApiClient::class); + $apiClient->expects(self::never())->method('register'); + $apiClient->expects(self::never())->method('getCrmAttributesWithPassword'); + $apiClient->expects(self::never())->method('getPersonalDataWithPassword'); + + self::getContainer()->set(UserRepository::class, $userRepository); + self::getContainer()->set(PublicFormRateLimiter::class, $rateLimiter); + self::getContainer()->set(ApiClient::class, $apiClient); + + $crawler = $client->request('GET', '/registration'); + $form = $crawler->selectButton('Jetzt registrieren')->form([ + 'registration[firstName]' => 'Customer', + 'registration[name]' => 'Example', + 'registration[plainPassword][first]' => 'Str0ng-Passw0rd!', + 'registration[plainPassword][second]' => 'Str0ng-Passw0rd!!', + 'registration[personalData][gender]' => 'M', + 'registration[personalData][dateOfBirth][day]' => '1', + 'registration[personalData][dateOfBirth][month]' => '1', + 'registration[personalData][dateOfBirth][year]' => '1990', + 'registration[personalData][street]' => 'Main Street 1', + 'registration[personalData][postCode]' => '12345', + 'registration[personalData][city]' => 'Berlin', + 'registration[personalData][country]' => 'DE', + 'registration[personalData][nationality]' => 'DE', + 'registration[personalData][email]' => 'Customer@Example.COM', + 'registration[personalData][mobile]' => '0123456789', + ]); + $client->submit($form); + + self::assertResponseStatusCodeSame(422); + self::assertStringContainsString('Die beiden Passwortangaben müssen identisch sein.', $client->getResponse()->getContent()); + } + public function testSuccessfulRegistrationDoesNotRefetchPersonalData(): void { $client = self::createClient([], [ @@ -214,7 +261,7 @@ class RegistrationControllerTest extends WebTestCase $passwordHasher ->expects(self::once()) ->method('hashPassword') - ->with(self::isInstanceOf(User::class), 'secret-password') + ->with(self::isInstanceOf(User::class), 'Str0ng-Passw0rd!') ->willReturn('hashed-secret'); $persistedUser = null; @@ -241,8 +288,8 @@ class RegistrationControllerTest extends WebTestCase $form = $crawler->selectButton('Jetzt registrieren')->form([ 'registration[firstName]' => 'Customer', 'registration[name]' => 'Example', - 'registration[plainPassword][first]' => 'secret-password', - 'registration[plainPassword][second]' => 'secret-password', + 'registration[plainPassword][first]' => 'Str0ng-Passw0rd!', + 'registration[plainPassword][second]' => 'Str0ng-Passw0rd!', 'registration[personalData][gender]' => 'M', 'registration[personalData][dateOfBirth][day]' => '1', 'registration[personalData][dateOfBirth][month]' => '1', @@ -319,8 +366,8 @@ class RegistrationControllerTest extends WebTestCase $form = $crawler->selectButton('Jetzt registrieren')->form([ 'registration[firstName]' => 'Customer', 'registration[name]' => 'Example', - 'registration[plainPassword][first]' => 'secret-password', - 'registration[plainPassword][second]' => 'secret-password', + 'registration[plainPassword][first]' => 'Str0ng-Passw0rd!', + 'registration[plainPassword][second]' => 'Str0ng-Passw0rd!', 'registration[personalData][gender]' => 'M', 'registration[personalData][dateOfBirth][day]' => '1', 'registration[personalData][dateOfBirth][month]' => '1', diff --git a/tests/Controller/ResetPasswordControllerTest.php b/tests/Controller/ResetPasswordControllerTest.php index 1f03fa68..09421352 100644 --- a/tests/Controller/ResetPasswordControllerTest.php +++ b/tests/Controller/ResetPasswordControllerTest.php @@ -16,6 +16,7 @@ class ResetPasswordControllerTest extends WebTestCase { $client = self::createClient([], [ 'HTTPS' => 'on', + 'HTTP_HX_REQUEST' => 'true', ]); $client->disableReboot(); @@ -121,7 +122,7 @@ class ResetPasswordControllerTest extends WebTestCase $manager ->expects(self::once()) ->method('resetPassword') - ->with($user, 'new-password'); + ->with($user, 'Str0ng-Passw0rd!'); self::getContainer()->set(PasswordResetManager::class, $manager); $crawler = $client->request('GET', '/reset-password/confirm?email=customer@example.com&nonce=token&expires=9999999999&_hash=signature'); @@ -129,8 +130,8 @@ class ResetPasswordControllerTest extends WebTestCase self::assertSelectorTextContains('h1', 'Neues Passwort'); $form = $crawler->selectButton('Passwort speichern')->form([ - 'form[password][first]' => 'new-password', - 'form[password][second]' => 'new-password', + 'form[password][first]' => 'Str0ng-Passw0rd!', + 'form[password][second]' => 'Str0ng-Passw0rd!', ]); $client->submit($form); @@ -139,4 +140,35 @@ class ResetPasswordControllerTest extends WebTestCase self::assertStringContainsString('Dein Passwort wurde aktualisiert', $client->getResponse()->getContent()); } + + public function testPasswordResetConfirmationRejectsTooShortPasswordBeforeSaving(): void + { + $client = self::createClient([], [ + 'HTTPS' => 'on', + ]); + $client->disableReboot(); + + $user = new User('customer@example.com'); + $manager = $this->createMock(PasswordResetManager::class); + $manager + ->expects(self::exactly(2)) + ->method('validateResetRequest') + ->with(self::isInstanceOf(Request::class)) + ->willReturn($user); + $manager->expects(self::never())->method('resetPassword'); + self::getContainer()->set(PasswordResetManager::class, $manager); + + $crawler = $client->request('GET', '/reset-password/confirm?email=customer@example.com&nonce=token&expires=9999999999&_hash=signature'); + self::assertResponseIsSuccessful(); + self::assertSelectorTextContains('h1', 'Neues Passwort'); + + $form = $crawler->selectButton('Passwort speichern')->form([ + 'form[password][first]' => 'short7', + 'form[password][second]' => 'short7', + ]); + $client->submit($form); + + self::assertResponseStatusCodeSame(422); + self::assertStringContainsString('Das Passwort muss mindestens 8 Zeichen lang sein.', $client->getResponse()->getContent()); + } }