diff --git a/api/src/State/ResetPasswordUpdateProcessor.php b/api/src/State/ResetPasswordUpdateProcessor.php index 0ba0497c3e..147148a70f 100644 --- a/api/src/State/ResetPasswordUpdateProcessor.php +++ b/api/src/State/ResetPasswordUpdateProcessor.php @@ -5,6 +5,7 @@ use ApiPlatform\Metadata\Operation; use ApiPlatform\State\ProcessorInterface; use App\DTO\ResetPassword; +use App\Entity\User; use App\Repository\UserRepository; use App\Security\ReCaptcha\ReCaptchaWrapper; use Doctrine\ORM\EntityManagerInterface; @@ -46,6 +47,9 @@ public function process($data, Operation $operation, array $uriVariables = [], a $passwordHasher = $this->pwHasherFactory->getPasswordHasher($user); $user->password = $passwordHasher->hash($data->password); $user->passwordResetKeyHash = null; + if (User::STATE_REGISTERED === $user->state) { + $user->state = User::STATE_ACTIVATED; + } $this->em->flush(); $data->password = ''; diff --git a/api/tests/Api/ResetPassword/UpdatePasswordTest.php b/api/tests/Api/ResetPassword/UpdatePasswordTest.php index 435219fbec..b06c6efc94 100644 --- a/api/tests/Api/ResetPassword/UpdatePasswordTest.php +++ b/api/tests/Api/ResetPassword/UpdatePasswordTest.php @@ -5,7 +5,10 @@ use App\Entity\User; use App\Security\ReCaptcha\ReCaptchaWrapper; use App\Tests\Api\ECampApiTestCase; +use Doctrine\ORM\NonUniqueResultException; +use Doctrine\ORM\NoResultException; use ReCaptcha\Response; +use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; /** * @internal @@ -96,6 +99,66 @@ public function testPatchResetPasswordValidatesUnreasonablyLongPassword() { ]); } + /** + * @throws NonUniqueResultException + * @throws TransportExceptionInterface + * @throws NoResultException + */ + public function testPatchResetPasswordActivatesUser() { + $this->mockRecaptcha(); + $this->getEntityManager()->createQueryBuilder() + ->update(User::class, 'u') + ->set('u.state', ':state') + ->where('u.id = :id') + ->setParameter('state', User::STATE_REGISTERED) + ->setParameter('id', $this->user->getId()) + ->getQuery() + ->execute() + ; + + $this->client->request( + 'POST', + '/authentication_token', + [ + 'json' => [ + 'identifier' => $this->user->getEmail(), + 'password' => 'test', + ], + ] + ); + + $this->assertResponseStatusCodeSame(401); + + $newPassword = 'new_password'; + $this->client->request( + 'PATCH', + '/auth/reset_password/'.$this->passwordResetKey, + [ + 'json' => [ + 'password' => $newPassword, + ], + 'headers' => [ + 'Content-Type' => 'application/merge-patch+json', + ], + ] + ); + + $this->assertResponseStatusCodeSame(200); + + $this->client->request( + 'POST', + '/authentication_token', + [ + 'json' => [ + 'identifier' => $this->user->getEmail(), + 'password' => $newPassword, + ], + ] + ); + + $this->assertResponseStatusCodeSame(204); + } + protected function mockRecaptcha($shouldReturnSuccess = true) { $container = static::getContainer(); $recaptcha = $this->createMock(ReCaptchaWrapper::class); diff --git a/api/tests/State/ResetPasswordUpdateProcessorTest.php b/api/tests/State/ResetPasswordUpdateProcessorTest.php index c36eb76b0f..d171061f3b 100644 --- a/api/tests/State/ResetPasswordUpdateProcessorTest.php +++ b/api/tests/State/ResetPasswordUpdateProcessorTest.php @@ -9,6 +9,7 @@ use App\Security\ReCaptcha\ReCaptchaWrapper; use App\State\ResetPasswordUpdateProcessor; use Doctrine\ORM\EntityManagerInterface; +use PHPUnit\Framework\Attributes\TestWith; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use ReCaptcha\Response; @@ -144,6 +145,7 @@ public function testUpdateWithCorrectResetKey() { ; $user = new User(); $user->passwordResetKeyHash = 'resetKey'; + $user->state = User::STATE_REGISTERED; $this->userRepository->expects(self::once()) ->method('loadUserByIdentifier') @@ -167,5 +169,42 @@ public function testUpdateWithCorrectResetKey() { self::assertThat($user->password, self::equalTo(md5('newPassword'))); self::assertThat($user->passwordResetKeyHash, self::isEmpty()); + self::assertThat($user->state, self::equalTo(User::STATE_ACTIVATED)); + } + + #[TestWith([User::STATE_DELETED])] + #[TestWith([User::STATE_ACTIVATED])] + public function testUpdateWithCorrectResetKeyDoesNotChangeActivatedOrDeletedUsers(string $state) { + $this->recaptchaResponse->expects(self::once()) + ->method('isSuccess') + ->willReturn(true) + ; + $user = new User(); + $user->passwordResetKeyHash = 'resetKey'; + $user->state = $state; + + $this->userRepository->expects(self::once()) + ->method('loadUserByIdentifier') + ->with(self::EMAIL) + ->willReturn($user) + ; + $this->pwHasher->expects(self::once()) + ->method('verify') + ->willReturn(true) + ; + $this->pwHasher->expects(self::once()) + ->method('hash') + ->willReturnCallback(fn ($raw) => md5($raw)) + ; + + $this->resetPassword->id = base64_encode(self::EMAIL.'#myKey'); + $this->resetPassword->recaptchaToken = 'token'; + $this->resetPassword->password = 'newPassword'; + + $this->processor->process($this->resetPassword, new Patch()); + + self::assertThat($user->password, self::equalTo(md5('newPassword'))); + self::assertThat($user->passwordResetKeyHash, self::isEmpty()); + self::assertThat($user->state, self::equalTo($state)); } }