Skip to content

Commit 3d0a2b8

Browse files
ENGCOM-8461: Extend password reset token validity on password change page load #25279
2 parents 0b2b1bb + b550107 commit 3d0a2b8

File tree

4 files changed

+157
-18
lines changed

4 files changed

+157
-18
lines changed

app/code/Magento/Customer/Controller/Account/CreatePassword.php

Lines changed: 31 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,21 +9,22 @@
99

1010
use Magento\Customer\Api\AccountManagementInterface;
1111
use Magento\Customer\Model\ForgotPasswordToken\ConfirmCustomerByToken;
12+
use Magento\Customer\Model\ForgotPasswordToken\GetCustomerByToken;
1213
use Magento\Customer\Model\Session;
13-
use Magento\Framework\App\Action\HttpGetActionInterface;
14-
use Magento\Framework\View\Result\PageFactory;
1514
use Magento\Framework\App\Action\Context;
15+
use Magento\Framework\App\Action\HttpGetActionInterface;
1616
use Magento\Framework\App\ObjectManager;
17+
use Magento\Framework\Controller\Result\Redirect;
18+
use Magento\Framework\View\Result\Page;
19+
use Magento\Framework\View\Result\PageFactory;
1720

1821
/**
19-
* Class CreatePassword
20-
*
21-
* @package Magento\Customer\Controller\Account
22+
* Controller for front-end customer password reset form
2223
*/
2324
class CreatePassword extends \Magento\Customer\Controller\AbstractAccount implements HttpGetActionInterface
2425
{
2526
/**
26-
* @var \Magento\Customer\Api\AccountManagementInterface
27+
* @var AccountManagementInterface
2728
*/
2829
protected $accountManagement;
2930

@@ -38,37 +39,46 @@ class CreatePassword extends \Magento\Customer\Controller\AbstractAccount implem
3839
protected $resultPageFactory;
3940

4041
/**
41-
* @var \Magento\Customer\Model\ForgotPasswordToken\ConfirmCustomerByToken
42+
* @var ConfirmCustomerByToken
4243
*/
4344
private $confirmByToken;
4445

4546
/**
46-
* @param \Magento\Framework\App\Action\Context $context
47-
* @param \Magento\Customer\Model\Session $customerSession
48-
* @param \Magento\Framework\View\Result\PageFactory $resultPageFactory
49-
* @param \Magento\Customer\Api\AccountManagementInterface $accountManagement
50-
* @param \Magento\Customer\Model\ForgotPasswordToken\ConfirmCustomerByToken $confirmByToken
47+
* @var GetCustomerByToken
48+
*/
49+
private $getByToken;
50+
51+
/**
52+
* @param Context $context
53+
* @param Session $customerSession
54+
* @param PageFactory $resultPageFactory
55+
* @param AccountManagementInterface $accountManagement
56+
* @param ConfirmCustomerByToken|null $confirmByToken
57+
* @param GetCustomerByToken|null $getByToken
5158
*/
5259
public function __construct(
5360
Context $context,
5461
Session $customerSession,
5562
PageFactory $resultPageFactory,
5663
AccountManagementInterface $accountManagement,
57-
ConfirmCustomerByToken $confirmByToken = null
64+
ConfirmCustomerByToken $confirmByToken = null,
65+
GetCustomerByToken $getByToken = null
5866
) {
5967
$this->session = $customerSession;
6068
$this->resultPageFactory = $resultPageFactory;
6169
$this->accountManagement = $accountManagement;
6270
$this->confirmByToken = $confirmByToken
6371
?? ObjectManager::getInstance()->get(ConfirmCustomerByToken::class);
72+
$this->getByToken = $getByToken
73+
?? ObjectManager::getInstance()->get(GetCustomerByToken::class);
6474

6575
parent::__construct($context);
6676
}
6777

6878
/**
6979
* Resetting password handler
7080
*
71-
* @return \Magento\Framework\Controller\Result\Redirect|\Magento\Framework\View\Result\Page
81+
* @return Redirect|Page
7282
*/
7383
public function execute()
7484
{
@@ -83,14 +93,19 @@ public function execute()
8393

8494
$this->confirmByToken->execute($resetPasswordToken);
8595

96+
// Extend token validity to avoid expiration while this form is
97+
// being completed by the user.
98+
$customer = $this->getByToken->execute($resetPasswordToken);
99+
$this->accountManagement->changeResetPasswordLinkToken($customer, $resetPasswordToken);
100+
86101
if ($isDirectLink) {
87102
$this->session->setRpToken($resetPasswordToken);
88103
$resultRedirect = $this->resultRedirectFactory->create();
89104
$resultRedirect->setPath('*/*/createpassword');
90105

91106
return $resultRedirect;
92107
} else {
93-
/** @var \Magento\Framework\View\Result\Page $resultPage */
108+
/** @var Page $resultPage */
94109
$resultPage = $this->resultPageFactory->create();
95110
$resultPage->getLayout()
96111
->getBlock('resetPassword')
@@ -100,7 +115,7 @@ public function execute()
100115
}
101116
} catch (\Exception $exception) {
102117
$this->messageManager->addErrorMessage(__('Your password reset link has expired.'));
103-
/** @var \Magento\Framework\Controller\Result\Redirect $resultRedirect */
118+
/** @var Redirect $resultRedirect */
104119
$resultRedirect = $this->resultRedirectFactory->create();
105120
$resultRedirect->setPath('*/*/forgotpassword');
106121

app/code/Magento/User/Controller/Adminhtml/Auth/ResetPassword.php

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
<?php
22
/**
3-
*
43
* Copyright © Magento, Inc. All rights reserved.
54
* See COPYING.txt for license details.
65
*/
76
namespace Magento\User\Controller\Adminhtml\Auth;
87

9-
class ResetPassword extends \Magento\User\Controller\Adminhtml\Auth
8+
use Magento\Framework\App\Action\HttpGetActionInterface;
9+
10+
/**
11+
* Controller for admin user password reset form
12+
*/
13+
class ResetPassword extends \Magento\User\Controller\Adminhtml\Auth implements HttpGetActionInterface
1014
{
1115
/**
1216
* Display reset forgotten password form
@@ -22,6 +26,12 @@ public function execute()
2226
try {
2327
$this->_validateResetPasswordLinkToken($userId, $passwordResetToken);
2428

29+
// Extend token validity to avoid expiration while this form is
30+
// being completed by the user.
31+
$user = $this->_userFactory->create()->load($userId);
32+
$user->changeResetPasswordLinkToken($passwordResetToken);
33+
$user->save();
34+
2535
$this->_view->loadLayout();
2636

2737
$content = $this->_view->getLayout()->getBlock('content');

dev/tests/integration/testsuite/Magento/Customer/Controller/CreatePasswordTest.php

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,11 @@
1010
use Magento\Customer\Model\CustomerRegistry;
1111
use Magento\Customer\Model\ResourceModel\Customer as CustomerResource;
1212
use Magento\Customer\Model\Session;
13+
use Magento\Framework\Intl\DateTimeFactory;
1314
use Magento\Framework\Math\Random;
15+
use Magento\Framework\Message\MessageInterface;
1416
use Magento\Framework\ObjectManagerInterface;
17+
use Magento\Framework\Stdlib\DateTime;
1518
use Magento\Framework\View\LayoutInterface;
1619
use Magento\Store\Api\WebsiteRepositoryInterface;
1720
use Magento\TestFramework\Helper\Bootstrap;
@@ -42,6 +45,9 @@ class CreatePasswordTest extends AbstractController
4245
/** @var CustomerRegistry */
4346
private $customerRegistry;
4447

48+
/** @var DateTimeFactory */
49+
private $dateTimeFactory;
50+
4551
/** @var WebsiteRepositoryInterface */
4652
private $websiteRepository;
4753

@@ -61,6 +67,7 @@ protected function setUp(): void
6167
$this->random = $this->objectManager->get(Random::class);
6268
$this->customerResource = $this->objectManager->get(CustomerResource::class);
6369
$this->customerRegistry = $this->objectManager->get(CustomerRegistry::class);
70+
$this->dateTimeFactory = $this->objectManager->get(DateTimeFactory::class);
6471
$this->websiteRepository = $this->objectManager->get(WebsiteRepositoryInterface::class);
6572
}
6673

@@ -94,4 +101,69 @@ public function testCreatePassword(): void
94101
$block = $this->layout->getBlock('resetPassword');
95102
$this->assertEquals($token, $block->getResetPasswordLinkToken());
96103
}
104+
105+
/**
106+
* @magentoDataFixture Magento/Customer/_files/customer_with_website.php
107+
*
108+
* @return void
109+
*/
110+
public function testTokenHasExpired(): void
111+
{
112+
$defaultWebsite = $this->websiteRepository->get('base')->getId();
113+
$customer = $this->customerRegistry->retrieveByEmail('john.doe@magento.com', $defaultWebsite);
114+
$this->customerId = $customer->getId();
115+
$token = $this->random->getUniqueHash();
116+
$tooLongAgo = $this->dateTimeFactory->create()
117+
->sub(\DateInterval::createFromDateString('1 month'))
118+
->format(DateTime::DATETIME_PHP_FORMAT);
119+
120+
$customer->changeResetPasswordLinkToken($token);
121+
$customer->setData('confirmation', 'confirmation');
122+
$customerSecure = $this->customerRegistry->retrieveSecureData($this->customerId);
123+
$customerSecure->setRpTokenCreatedAt($tooLongAgo);
124+
$this->customerResource->save($customer);
125+
126+
$this->session->setRpToken($token);
127+
$this->session->setRpCustomerId($this->customerId);
128+
129+
$this->dispatch('customer/account/createPassword');
130+
131+
$this->assertRedirect($this->stringContains('customer/account/forgotpassword'));
132+
$this->assertSessionMessages(
133+
$this->equalTo(['Your password reset link has expired.']),
134+
MessageInterface::TYPE_ERROR
135+
);
136+
}
137+
138+
/**
139+
* @magentoDataFixture Magento/Customer/_files/customer_with_website.php
140+
*
141+
* @return void
142+
*/
143+
public function testTokenExtendedOnPageLoad(): void
144+
{
145+
$defaultWebsite = $this->websiteRepository->get('base')->getId();
146+
$customer = $this->customerRegistry->retrieveByEmail('john.doe@magento.com', $defaultWebsite);
147+
$this->customerId = $customer->getId();
148+
$token = $this->random->getUniqueHash();
149+
$anHourAgo = $this->dateTimeFactory->create()
150+
->sub(\DateInterval::createFromDateString('1 hour'))
151+
->format(DateTime::DATETIME_PHP_FORMAT);
152+
153+
$customer->changeResetPasswordLinkToken($token);
154+
$customer->setData('confirmation', 'confirmation');
155+
$customerSecure = $this->customerRegistry->retrieveSecureData($this->customerId);
156+
$customerSecure->setRpTokenCreatedAt($anHourAgo);
157+
$this->customerResource->save($customer);
158+
159+
$this->session->setRpToken($token);
160+
$this->session->setRpCustomerId($this->customerId);
161+
162+
$this->dispatch('customer/account/createPassword');
163+
$block = $this->layout->getBlock('resetPassword');
164+
$this->assertEquals($token, $block->getResetPasswordLinkToken());
165+
166+
$customerSecure = $this->customerRegistry->retrieveSecureData($this->customerId);
167+
$this->assertNotEquals($anHourAgo, $customerSecure->getRpTokenCreatedAt());
168+
}
97169
}

dev/tests/integration/testsuite/Magento/User/Controller/Adminhtml/AuthTest.php

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,16 @@
55
*/
66
namespace Magento\User\Controller\Adminhtml;
77

8+
use Magento\Framework\Intl\DateTimeFactory;
9+
use Magento\Framework\Stdlib\DateTime;
810
use Magento\TestFramework\Mail\Template\TransportBuilderMock;
911
use Magento\TestFramework\Helper\Bootstrap;
1012

1113
/**
1214
* Test class for \Magento\User\Controller\Adminhtml\Auth
1315
*
1416
* @magentoAppArea adminhtml
17+
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
1518
*/
1619
class AuthTest extends \Magento\TestFramework\TestCase\AbstractBackendController
1720
{
@@ -106,6 +109,45 @@ public function testResetPasswordAction()
106109
$this->assertTrue((bool)strpos($this->getResponse()->getBody(), $resetPasswordToken));
107110
}
108111

112+
/**
113+
* Test reset password action extends expiry of token
114+
*
115+
* @covers \Magento\User\Controller\Adminhtml\Auth\ResetPassword::execute
116+
* @covers \Magento\User\Controller\Adminhtml\Auth\ResetPassword::_validateResetPasswordLinkToken
117+
* @magentoDataFixture Magento/User/_files/dummy_user.php
118+
*/
119+
public function testResetPasswordActionWithTokenNearExpiry()
120+
{
121+
/** @var $user \Magento\User\Model\User */
122+
$user = Bootstrap::getObjectManager()->create(
123+
\Magento\User\Model\User::class
124+
)->loadByUsername(
125+
'dummy_username'
126+
);
127+
$this->assertNotEmpty($user->getId(), 'Broken fixture');
128+
$resetPasswordToken = Bootstrap::getObjectManager()->get(
129+
\Magento\User\Helper\Data::class
130+
)->generateResetPasswordLinkToken();
131+
$user->changeResetPasswordLinkToken($resetPasswordToken);
132+
133+
$anHourAgo = Bootstrap::getObjectManager()->create(DateTimeFactory::class)
134+
->create()
135+
->sub(\DateInterval::createFromDateString('1 hour'))
136+
->format(DateTime::DATETIME_PHP_FORMAT);
137+
$user->setRpTokenCreatedAt($anHourAgo);
138+
$user->save();
139+
140+
$this->getRequest()->setQueryValue('token', $resetPasswordToken)->setQueryValue('id', $user->getId());
141+
$this->dispatch('backend/admin/auth/resetpassword');
142+
143+
$this->assertEquals('adminhtml', $this->getRequest()->getRouteName());
144+
$this->assertEquals('auth', $this->getRequest()->getControllerName());
145+
$this->assertEquals('resetpassword', $this->getRequest()->getActionName());
146+
$this->assertTrue((bool)strpos($this->getResponse()->getBody(), $resetPasswordToken));
147+
148+
$this->assertNotEquals($anHourAgo, $user->reload()->getRpTokenCreatedAt());
149+
}
150+
109151
/**
110152
* @covers \Magento\User\Controller\Adminhtml\Auth\ResetPassword::execute
111153
* @covers \Magento\User\Controller\Adminhtml\Auth\ResetPassword::_validateResetPasswordLinkToken

0 commit comments

Comments
 (0)