Skip to content

Commit 9eb751e

Browse files
committed
Merge remote-tracking branch 'origin/MC-21666' into 2.3.5-develop-pr109
2 parents 33f5227 + f4793cc commit 9eb751e

File tree

8 files changed

+200
-100
lines changed

8 files changed

+200
-100
lines changed
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\Customer\Api;
9+
10+
/**
11+
* Interface for cleaning customer session data.
12+
*/
13+
interface SessionCleanerInterface
14+
{
15+
/**
16+
* Destroy all active customer sessions related to given customer id, including current session.
17+
*
18+
* @param int $customerId
19+
* @return void
20+
*/
21+
public function clearFor(int $customerId): void;
22+
}

app/code/Magento/Customer/Model/AccountManagement.php

Lines changed: 16 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
use Magento\Customer\Api\Data\AddressInterface;
1414
use Magento\Customer\Api\Data\CustomerInterface;
1515
use Magento\Customer\Api\Data\ValidationResultsInterfaceFactory;
16+
use Magento\Customer\Api\SessionCleanerInterface;
1617
use Magento\Customer\Helper\View as CustomerViewHelper;
1718
use Magento\Customer\Model\Config\Share as ConfigShare;
1819
use Magento\Customer\Model\Customer as CustomerModel;
@@ -200,6 +201,7 @@ class AccountManagement implements AccountManagementInterface
200201
* Minimum password length
201202
*
202203
* @deprecated Get rid of Helpers in Password Security Management
204+
* @see \Magento\Customer\Model\AccountManagement::XML_PATH_MINIMUM_PASSWORD_LENGTH
203205
*/
204206
const MIN_PASSWORD_LENGTH = 6;
205207

@@ -283,21 +285,6 @@ class AccountManagement implements AccountManagementInterface
283285
*/
284286
private $transportBuilder;
285287

286-
/**
287-
* @var SessionManagerInterface
288-
*/
289-
private $sessionManager;
290-
291-
/**
292-
* @var SaveHandlerInterface
293-
*/
294-
private $saveHandler;
295-
296-
/**
297-
* @var CollectionFactory
298-
*/
299-
private $visitorCollectionFactory;
300-
301288
/**
302289
* @var DataObjectProcessor
303290
*/
@@ -383,6 +370,11 @@ class AccountManagement implements AccountManagementInterface
383370
*/
384371
private $getByToken;
385372

373+
/**
374+
* @var SessionCleanerInterface
375+
*/
376+
private $sessionCleaner;
377+
386378
/**
387379
* @param CustomerFactory $customerFactory
388380
* @param ManagerInterface $eventManager
@@ -417,10 +409,12 @@ class AccountManagement implements AccountManagementInterface
417409
* @param AddressRegistry|null $addressRegistry
418410
* @param GetCustomerByToken|null $getByToken
419411
* @param AllowedCountries|null $allowedCountriesReader
412+
* @param SessionCleanerInterface|null $sessionCleaner
420413
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
421414
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
422415
* @SuppressWarnings(PHPMD.NPathComplexity)
423416
* @SuppressWarnings(PHPMD.LongVariable)
417+
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
424418
*/
425419
public function __construct(
426420
CustomerFactory $customerFactory,
@@ -455,7 +449,8 @@ public function __construct(
455449
SearchCriteriaBuilder $searchCriteriaBuilder = null,
456450
AddressRegistry $addressRegistry = null,
457451
GetCustomerByToken $getByToken = null,
458-
AllowedCountries $allowedCountriesReader = null
452+
AllowedCountries $allowedCountriesReader = null,
453+
SessionCleanerInterface $sessionCleaner = null
459454
) {
460455
$this->customerFactory = $customerFactory;
461456
$this->eventManager = $eventManager;
@@ -486,12 +481,6 @@ public function __construct(
486481
$this->dateTimeFactory = $dateTimeFactory ?: $objectManager->get(DateTimeFactory::class);
487482
$this->accountConfirmation = $accountConfirmation ?: $objectManager
488483
->get(AccountConfirmation::class);
489-
$this->sessionManager = $sessionManager
490-
?: $objectManager->get(SessionManagerInterface::class);
491-
$this->saveHandler = $saveHandler
492-
?: $objectManager->get(SaveHandlerInterface::class);
493-
$this->visitorCollectionFactory = $visitorCollectionFactory
494-
?: $objectManager->get(CollectionFactory::class);
495484
$this->searchCriteriaBuilder = $searchCriteriaBuilder
496485
?: $objectManager->get(SearchCriteriaBuilder::class);
497486
$this->addressRegistry = $addressRegistry
@@ -500,6 +489,7 @@ public function __construct(
500489
?: $objectManager->get(GetCustomerByToken::class);
501490
$this->allowedCountriesReader = $allowedCountriesReader
502491
?: $objectManager->get(AllowedCountries::class);
492+
$this->sessionCleaner = $sessionCleaner ?? $objectManager->get(SessionCleanerInterface::class);
503493
}
504494

505495
/**
@@ -538,6 +528,8 @@ public function resendConfirmation($email, $websiteId = null, $redirectUrl = '')
538528
} catch (MailException $e) {
539529
// If we are not able to send a new account email, this should be ignored
540530
$this->logger->critical($e);
531+
532+
return false;
541533
}
542534
return true;
543535
}
@@ -725,7 +717,7 @@ public function resetPassword($email, $resetToken, $newPassword)
725717
$customerSecure->setRpToken(null);
726718
$customerSecure->setRpTokenCreatedAt(null);
727719
$customerSecure->setPasswordHash($this->createPasswordHash($newPassword));
728-
$this->destroyCustomerSessions($customer->getId());
720+
$this->sessionCleaner->clearFor((int)$customer->getId());
729721
$this->customerRepository->save($customer);
730722

731723
return true;
@@ -1054,7 +1046,7 @@ private function changePasswordForCustomer($customer, $currentPassword, $newPass
10541046
$customerSecure->setRpTokenCreatedAt(null);
10551047
$this->checkPasswordStrength($newPassword);
10561048
$customerSecure->setPasswordHash($this->createPasswordHash($newPassword));
1057-
$this->destroyCustomerSessions($customer->getId());
1049+
$this->sessionCleaner->clearFor((int)$customer->getId());
10581050
$this->disableAddressValidation($customer);
10591051
$this->customerRepository->save($customer);
10601052

@@ -1607,36 +1599,6 @@ private function getEmailNotification()
16071599
}
16081600
}
16091601

1610-
/**
1611-
* Destroy all active customer sessions by customer id (current session will not be destroyed).
1612-
*
1613-
* Customer sessions which should be deleted are collecting from the "customer_visitor" table considering
1614-
* configured session lifetime.
1615-
*
1616-
* @param string|int $customerId
1617-
* @return void
1618-
*/
1619-
private function destroyCustomerSessions($customerId)
1620-
{
1621-
$sessionLifetime = $this->scopeConfig->getValue(
1622-
\Magento\Framework\Session\Config::XML_PATH_COOKIE_LIFETIME,
1623-
\Magento\Store\Model\ScopeInterface::SCOPE_STORE
1624-
);
1625-
$dateTime = $this->dateTimeFactory->create();
1626-
$activeSessionsTime = $dateTime->setTimestamp($dateTime->getTimestamp() - $sessionLifetime)
1627-
->format(DateTime::DATETIME_PHP_FORMAT);
1628-
/** @var \Magento\Customer\Model\ResourceModel\Visitor\Collection $visitorCollection */
1629-
$visitorCollection = $this->visitorCollectionFactory->create();
1630-
$visitorCollection->addFieldToFilter('customer_id', $customerId);
1631-
$visitorCollection->addFieldToFilter('last_visit_at', ['from' => $activeSessionsTime]);
1632-
$visitorCollection->addFieldToFilter('session_id', ['neq' => $this->sessionManager->getSessionId()]);
1633-
/** @var \Magento\Customer\Model\Visitor $visitor */
1634-
foreach ($visitorCollection->getItems() as $visitor) {
1635-
$sessionId = $visitor->getSessionId();
1636-
$this->saveHandler->destroy($sessionId);
1637-
}
1638-
}
1639-
16401602
/**
16411603
* Set ignore_validation_flag for reset password flow to skip unnecessary address and customer validation
16421604
*
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\Customer\Model\Session;
9+
10+
use Magento\Customer\Api\SessionCleanerInterface;
11+
use Magento\Customer\Model\ResourceModel\Visitor\CollectionFactory as VisitorCollectionFactory;
12+
use Magento\Framework\App\Config\ScopeConfigInterface;
13+
use Magento\Framework\Intl\DateTimeFactory;
14+
use Magento\Framework\Session\Config;
15+
use Magento\Framework\Session\SaveHandlerInterface;
16+
use Magento\Framework\Session\SessionManagerInterface;
17+
use Magento\Framework\Stdlib\DateTime;
18+
use Magento\Store\Model\ScopeInterface;
19+
20+
/**
21+
* Deletes all session data which relates to customer, including current session data.
22+
*
23+
* @SuppressWarnings(PHPMD.CookieAndSessionMisuse)
24+
*/
25+
class SessionCleaner implements SessionCleanerInterface
26+
{
27+
/**
28+
* @var ScopeConfigInterface
29+
*/
30+
private $scopeConfig;
31+
32+
/**
33+
* @var DateTimeFactory
34+
*/
35+
private $dateTimeFactory;
36+
37+
/**
38+
* @var VisitorCollectionFactory
39+
*/
40+
private $visitorCollectionFactory;
41+
42+
/**
43+
* @var SessionManagerInterface
44+
*/
45+
private $sessionManager;
46+
47+
/**
48+
* @var SaveHandlerInterface
49+
*/
50+
private $saveHandler;
51+
52+
/**
53+
* @inheritdoc
54+
*/
55+
public function __construct(
56+
ScopeConfigInterface $scopeConfig,
57+
DateTimeFactory $dateTimeFactory,
58+
VisitorCollectionFactory $visitorCollectionFactory,
59+
SessionManagerInterface $sessionManager,
60+
SaveHandlerInterface $saveHandler
61+
) {
62+
$this->scopeConfig = $scopeConfig;
63+
$this->dateTimeFactory = $dateTimeFactory;
64+
$this->visitorCollectionFactory = $visitorCollectionFactory;
65+
$this->sessionManager = $sessionManager;
66+
$this->saveHandler = $saveHandler;
67+
}
68+
69+
/**
70+
* @inheritdoc
71+
*/
72+
public function clearFor(int $customerId): void
73+
{
74+
if ($this->sessionManager->isSessionExists()) {
75+
//delete old session and move data to the new session
76+
//use this instead of $this->sessionManager->regenerateId because last one doesn't delete old session
77+
// phpcs:ignore Magento2.Functions.DiscouragedFunction
78+
session_regenerate_id(true);
79+
}
80+
81+
$sessionLifetime = $this->scopeConfig->getValue(
82+
Config::XML_PATH_COOKIE_LIFETIME,
83+
ScopeInterface::SCOPE_STORE
84+
);
85+
$dateTime = $this->dateTimeFactory->create();
86+
$activeSessionsTime = $dateTime->setTimestamp($dateTime->getTimestamp() - $sessionLifetime)
87+
->format(DateTime::DATETIME_PHP_FORMAT);
88+
/** @var \Magento\Customer\Model\ResourceModel\Visitor\Collection $visitorCollection */
89+
$visitorCollection = $this->visitorCollectionFactory->create();
90+
$visitorCollection->addFieldToFilter('customer_id', $customerId);
91+
$visitorCollection->addFieldToFilter('last_visit_at', ['from' => $activeSessionsTime]);
92+
/** @var \Magento\Customer\Model\Visitor $visitor */
93+
foreach ($visitorCollection->getItems() as $visitor) {
94+
$sessionId = $visitor->getSessionId();
95+
$this->sessionManager->start();
96+
$this->saveHandler->destroy($sessionId);
97+
$this->sessionManager->writeClose();
98+
}
99+
}
100+
}

app/code/Magento/Customer/Model/Visitor.php

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
use Magento\Framework\App\RequestSafetyInterface;
1111

1212
/**
13-
* Class Visitor
13+
* Class Visitor responsible for initializing visitor's.
1414
*
1515
* Used to track sessions of the logged in customers
1616
*
@@ -195,7 +195,9 @@ public function initByRequest($observer)
195195
public function saveByRequest($observer)
196196
{
197197
// prevent saving Visitor for safe methods, e.g. GET request
198-
if ($this->skipRequestLogging || $this->requestSafety->isSafeMethod() || $this->isModuleIgnored($observer)) {
198+
if (($this->skipRequestLogging || $this->requestSafety->isSafeMethod() || $this->isModuleIgnored($observer))
199+
&& !$this->sessionIdHasChanged()
200+
) {
199201
return $this;
200202
}
201203

@@ -212,6 +214,23 @@ public function saveByRequest($observer)
212214
return $this;
213215
}
214216

217+
/**
218+
* Check if visitor session id was changed.
219+
*
220+
* @return bool
221+
*/
222+
private function sessionIdHasChanged(): bool
223+
{
224+
$visitorData = $this->session->getVisitorData();
225+
$hasChanged = false;
226+
227+
if (isset($visitorData['session_id'])) {
228+
$hasChanged = $this->session->getSessionId() !== $visitorData['session_id'];
229+
}
230+
231+
return $hasChanged;
232+
}
233+
215234
/**
216235
* Returns true if the module is required
217236
*

app/code/Magento/Customer/Test/Mftf/Test/StorefrontUpdateCustomerPasswordTest.xml

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,4 +78,17 @@
7878
<remove keyForRemoval="loginWithNewPassword"/>
7979
<remove keyForRemoval="seeMyEmail"/>
8080
</test>
81-
</tests>
81+
<test name="StorefrontUpdateCustomerPasswordRedirectOnLoginPage" extends="StorefrontUpdateCustomerPasswordValidCurrentPasswordTest">
82+
<annotations>
83+
<title value="Update Customer Password on Storefront Redirect on Login Page"/>
84+
<description value="Update Customer Password on Storefront Redirect on Login Page"/>
85+
<testCaseId value="MC-22957"/>
86+
<useCaseId value="MC-21666"/>
87+
</annotations>
88+
<remove keyForRemoval="logout"/>
89+
<remove keyForRemoval="loginWithNewPassword"/>
90+
<remove keyForRemoval="seeMyEmail"/>
91+
92+
<seeInCurrentUrl url="{{StorefrontCustomerSignInPage.url}}" stepKey="assertStorefrontCustomerLoginPage"/>
93+
</test>
94+
</tests>

0 commit comments

Comments
 (0)