From 65338d6a796117d5e75995a6fd4cb450cedd6a1c Mon Sep 17 00:00:00 2001 From: Rodrigue Villetard Date: Thu, 5 Jul 2018 09:59:24 +0200 Subject: [PATCH] fix #37, allow resending an unexpired token (#41) --- Exception/UnexpiredTokenHttpException.php | 25 ------------ Manager/ForgotPasswordManager.php | 8 ++-- features/forgotPassword.feature | 4 +- tests/Manager/ForgotPasswordManagerTest.php | 42 +++++++++++++++++---- 4 files changed, 40 insertions(+), 39 deletions(-) delete mode 100644 Exception/UnexpiredTokenHttpException.php diff --git a/Exception/UnexpiredTokenHttpException.php b/Exception/UnexpiredTokenHttpException.php deleted file mode 100644 index 0f56f60..0000000 --- a/Exception/UnexpiredTokenHttpException.php +++ /dev/null @@ -1,25 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace CoopTilleuls\ForgotPasswordBundle\Exception; - -use Symfony\Component\HttpKernel\Exception\HttpException; - -/** - * @author Vincent Chalamon - */ -final class UnexpiredTokenHttpException extends HttpException implements JsonHttpExceptionInterface -{ - public function __construct() - { - parent::__construct(400, 'An unexpired token already exists for this user.'); - } -} diff --git a/Manager/ForgotPasswordManager.php b/Manager/ForgotPasswordManager.php index 55bdcbf..fd90324 100644 --- a/Manager/ForgotPasswordManager.php +++ b/Manager/ForgotPasswordManager.php @@ -13,7 +13,6 @@ use CoopTilleuls\ForgotPasswordBundle\Entity\AbstractPasswordToken; use CoopTilleuls\ForgotPasswordBundle\Event\ForgotPasswordEvent; -use CoopTilleuls\ForgotPasswordBundle\Exception\UnexpiredTokenHttpException; use CoopTilleuls\ForgotPasswordBundle\Exception\UserNotFoundHttpException; use CoopTilleuls\ForgotPasswordBundle\Manager\Bridge\ManagerInterface; use Symfony\Component\EventDispatcher\EventDispatcherInterface; @@ -61,15 +60,16 @@ public function resetPassword($username) } $token = $this->passwordTokenManager->findOneByUser($user); + // A token already exists and has not expired - if (null !== $token && !$token->isExpired()) { - throw new UnexpiredTokenHttpException(); + if (null === $token || $token->isExpired()) { + $token = $this->passwordTokenManager->createPasswordToken($user); } // Generate password token $this->dispatcher->dispatch( ForgotPasswordEvent::CREATE_TOKEN, - new ForgotPasswordEvent($this->passwordTokenManager->createPasswordToken($user)) + new ForgotPasswordEvent($token) ); } diff --git a/features/forgotPassword.feature b/features/forgotPassword.feature index 324eb01..7f0679b 100644 --- a/features/forgotPassword.feature +++ b/features/forgotPassword.feature @@ -4,10 +4,10 @@ Feature: I need to be able to reset my password When I reset my password Then I should receive an email - Scenario: I can't reset my password if I already request a token + Scenario: I can reset my password even if I have already requested a token and this token has not expired yet Given I have a valid token When I reset my password - Then the request should be invalid with message 'An unexpired token already exists for this user.' + Then I should receive an email Scenario: I can reset my password if I already request a token but it has expired Given I have an expired token diff --git a/tests/Manager/ForgotPasswordManagerTest.php b/tests/Manager/ForgotPasswordManagerTest.php index 7cfd7cd..72d6ebb 100644 --- a/tests/Manager/ForgotPasswordManagerTest.php +++ b/tests/Manager/ForgotPasswordManagerTest.php @@ -65,20 +65,21 @@ public function testResetPasswordUserNotFoundHttpException() $this->manager->resetPassword('foo@example.com'); } - /** - * @expectedException \CoopTilleuls\ForgotPasswordBundle\Exception\UnexpiredTokenHttpException - * @expectedExceptionMessage An unexpired token already exists for this user. - */ - public function testResetPasswordUnexpiredTokenHttpException() + public function testResetPasswordWithNoPreviousToken() { + $tokenMock = $this->prophesize(AbstractPasswordToken::class); + $this->managerMock->findOneBy('App\Entity\User', ['email' => 'foo@example.com'])->willReturn($this->userMock->reveal())->shouldBeCalledTimes(1); - $this->passwordManagerMock->findOneByUser($this->userMock->reveal())->willReturn($this->tokenMock->reveal())->shouldBeCalledTimes(1); - $this->tokenMock->isExpired()->willReturn(false)->shouldBeCalledTimes(1); + $this->passwordManagerMock->findOneByUser($this->userMock->reveal())->willReturn(null)->shouldBeCalledTimes(1); + $this->passwordManagerMock->createPasswordToken($this->userMock->reveal())->willReturn($tokenMock->reveal())->shouldBeCalledTimes(1); + $this->eventDispatcherMock->dispatch(ForgotPasswordEvent::CREATE_TOKEN, Argument::that(function ($event) use ($tokenMock) { + return $event instanceof ForgotPasswordEvent && is_null($event->getPassword()) && $tokenMock->reveal() === $event->getPasswordToken(); + }))->shouldBeCalledTimes(1); $this->manager->resetPassword('foo@example.com'); } - public function testResetPassword() + public function testResetPasswordWithExpiredPreviousToken() { $tokenMock = $this->prophesize(AbstractPasswordToken::class); @@ -93,6 +94,31 @@ public function testResetPassword() $this->manager->resetPassword('foo@example.com'); } + /** + * @see https://github.com/coopTilleuls/CoopTilleulsForgotPasswordBundle/issues/37 + */ + public function testResetPasswordWithUnexpiredTokenHttp() + { + $tokenMock = $this->prophesize(AbstractPasswordToken::class); + + $tokenMock + ->isExpired() + ->willReturn(false) + ->shouldBeCalledTimes(1) + ; + + $token = $tokenMock->reveal(); + + $this->managerMock->findOneBy('App\Entity\User', ['email' => 'foo@example.com'])->willReturn($this->userMock->reveal())->shouldBeCalledTimes(1); + $this->passwordManagerMock->findOneByUser($this->userMock->reveal())->willReturn($token)->shouldBeCalledTimes(1); + + $this->eventDispatcherMock->dispatch(ForgotPasswordEvent::CREATE_TOKEN, Argument::that(function ($event) use ($token) { + return $event instanceof ForgotPasswordEvent && is_null($event->getPassword()) && $token === $event->getPasswordToken(); + }))->shouldBeCalledTimes(1); + + $this->manager->resetPassword('foo@example.com'); + } + public function testUpdatePassword() { $token = $this->tokenMock->reveal();