Skip to content

Commit 1017b2c

Browse files
author
Joan He
committed
MAGETWO-52441: Customer account lockout works incorrectly if this account is unlocked previously
1 parent ff74cb8 commit 1017b2c

File tree

2 files changed

+57
-86
lines changed

2 files changed

+57
-86
lines changed

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ public function processAuthenticationFailure($customerId)
8282
$customerSecure = $this->customerRegistry->retrieveSecureData($customerId);
8383

8484
if (!($lockThreshold && $maxFailures)) {
85-
return false;
85+
return;
8686
}
8787
$failuresNum = (int)$customerSecure->getFailuresNum() + 1;
8888

@@ -92,9 +92,10 @@ public function processAuthenticationFailure($customerId)
9292
}
9393

9494
$lockThreshInterval = new \DateInterval('PT' . $lockThreshold . 'S');
95-
$lockExpires = new \DateTime($customerSecure->getLockExpires());
95+
$lockExpires = $customerSecure->getLockExpires();
96+
$lockExpired = ($lockExpires !== null) && ($now > new \DateTime($lockExpires));
9697
// set first failure date when this is the first failure or the lock is expired
97-
if (1 === $failuresNum || !$firstFailureDate || $now > $lockExpires) {
98+
if (1 === $failuresNum || !$firstFailureDate || $lockExpired) {
9899
$customerSecure->setFirstFailure($this->dateTime->formatDate($now));
99100
$failuresNum = 1;
100101
$customerSecure->setLockExpires(null);

app/code/Magento/Customer/Test/Unit/Model/AuthenticationTest.php

Lines changed: 53 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
use Magento\Customer\Model\AccountManagement;
1313
use Magento\Customer\Model\CustomerRegistry;
1414
use Magento\Customer\Model\Data\CustomerSecure;
15+
use Magento\Framework\Stdlib\DateTime;
1516
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper;
1617

1718
class AuthenticationTest extends \PHPUnit_Framework_TestCase
@@ -46,6 +47,11 @@ class AuthenticationTest extends \PHPUnit_Framework_TestCase
4647
*/
4748
private $authentication;
4849

50+
/**
51+
* @var DateTime
52+
*/
53+
private $dateTimeMock;
54+
4955
protected function setUp()
5056
{
5157
$this->backendConfigMock = $this->getMockBuilder(ConfigInterface::class)
@@ -65,6 +71,12 @@ protected function setUp()
6571
$this->encryptorMock = $this->getMockBuilder(\Magento\Framework\Encryption\EncryptorInterface::class)
6672
->disableOriginalConstructor()
6773
->getMock();
74+
$this->dateTimeMock = $this->getMockBuilder(DateTime::class)
75+
->disableOriginalConstructor()
76+
->getMock();
77+
$this->dateTimeMock->expects($this->any())
78+
->method('formatDate')
79+
->willReturn('formattedDate');
6880
$this->customerSecureMock = $this->getMock(
6981
CustomerSecure::class,
7082
[
@@ -82,6 +94,7 @@ protected function setUp()
8294
'',
8395
false
8496
);
97+
8598
$objectManagerHelper = new ObjectManagerHelper($this);
8699

87100
$this->authentication = $objectManagerHelper->getObject(
@@ -91,6 +104,7 @@ protected function setUp()
91104
'backendConfig' => $this->backendConfigMock,
92105
'customerRepository' => $this->customerRepositoryMock,
93106
'encryptor' => $this->encryptorMock,
107+
'dateTime' => $this->dateTimeMock,
94108
]
95109
);
96110
}
@@ -112,42 +126,28 @@ public function testProcessAuthenticationFailureLockingIsDisabled()
112126
$this->authentication->processAuthenticationFailure($customerId);
113127
}
114128

115-
public function testProcessAuthenticationFailureFirstAttempt()
116-
{
117-
$customerId = 1;
118-
$this->backendConfigMock->expects($this->exactly(2))
119-
->method('getValue')
120-
->withConsecutive(
121-
[\Magento\Customer\Model\Authentication::LOCKOUT_THRESHOLD_PATH],
122-
[\Magento\Customer\Model\Authentication::MAX_FAILURES_PATH]
123-
)
124-
->willReturnOnConsecutiveCalls(10, 5);
125-
126-
$this->customerRegistryMock->expects($this->once())
127-
->method('retrieveSecureData')
128-
->with($customerId)
129-
->willReturn($this->customerSecureMock);
130-
$customerMock = $this->getMockBuilder(CustomerInterface::class)
131-
->disableOriginalConstructor()
132-
->getMock();
133-
$this->customerRepositoryMock->expects($this->once())
134-
->method('getById')
135-
->with($customerId)
136-
->willReturn($customerMock);
137-
$this->customerRepositoryMock->expects($this->once())
138-
->method('save')
139-
->with($customerMock);
140-
141-
$this->customerSecureMock->expects($this->once())->method('getFailuresNum')->willReturn(0);
142-
$this->customerSecureMock->expects($this->once())->method('getFirstFailure')->willReturn(0);
143-
$this->customerSecureMock->expects($this->once())->method('setFirstFailure');
144-
$this->customerSecureMock->expects($this->once())->method('setFailuresNum');
145-
146-
$this->authentication->processAuthenticationFailure($customerId);
147-
}
148-
149-
public function testProcessAuthenticationFailureLockExpired()
150-
{
129+
/**
130+
* @param int $failureNum
131+
* @param string $firstFailure
132+
* @param string $lockExpires
133+
* @param int $setFailureNumCallCtr
134+
* @param int $setFailureNumValue
135+
* @param int $setFirstFailureCallCtr
136+
* @param int $setFirstFailureValue
137+
* @param int $setLockExpiresCallCtr
138+
* @param int $setLockExpiresValue
139+
* @dataProvider processAuthenticationFailureDataProvider
140+
*/
141+
public function testProcessAuthenticationFailureFirstAttempt(
142+
$failureNum,
143+
$firstFailure,
144+
$lockExpires,
145+
$setFailureNumCallCtr,
146+
$setFailureNumValue,
147+
$setFirstFailureCallCtr,
148+
$setLockExpiresCallCtr,
149+
$setLockExpiresValue
150+
) {
151151
$customerId = 1;
152152
$this->backendConfigMock->expects($this->exactly(2))
153153
->method('getValue')
@@ -172,62 +172,32 @@ public function testProcessAuthenticationFailureLockExpired()
172172
->method('save')
173173
->with($customerMock);
174174

175-
$date = new \DateTime();
176-
$date->modify('-100 second');
177-
$lockExpiresDate = $date->format('Y-m-d H:i:s');
178-
$date->modify('-400 second');
179-
$firstFailureDate = $date->format('Y-m-d H:i:s');
180-
$this->customerSecureMock->expects($this->once())->method('getFailuresNum')->willReturn(5);
175+
$this->customerSecureMock->expects($this->once())->method('getFailuresNum')->willReturn($failureNum);
181176
$this->customerSecureMock->expects($this->once())
182177
->method('getFirstFailure')
183-
->willReturn($firstFailureDate);
178+
->willReturn($firstFailure ? (new \DateTime())->modify($firstFailure)->format('Y-m-d H:i:s') : null);
184179
$this->customerSecureMock->expects($this->once())
185180
->method('getLockExpires')
186-
->willReturn($lockExpiresDate);
187-
$this->customerSecureMock->expects($this->once())->method('setFirstFailure');
188-
$this->customerSecureMock->expects($this->once())->method('setLockExpires')->with(null);
189-
$this->customerSecureMock->expects($this->once())->method('setFailuresNum')->with(1);
181+
->willReturn($lockExpires ? (new \DateTime())->modify($lockExpires)->format('Y-m-d H:i:s') : null);
182+
$this->customerSecureMock->expects($this->exactly($setFirstFailureCallCtr))->method('setFirstFailure');
183+
$this->customerSecureMock->expects($this->exactly($setFailureNumCallCtr))
184+
->method('setFailuresNum')
185+
->with($setFailureNumValue);
186+
$this->customerSecureMock->expects($this->exactly($setLockExpiresCallCtr))
187+
->method('setLockExpires')
188+
->with($setLockExpiresValue);
190189

191190
$this->authentication->processAuthenticationFailure($customerId);
192191
}
193192

194-
public function testProcessAuthenticationFailureMaxNumberOfAttempts()
193+
public function processAuthenticationFailureDataProvider()
195194
{
196-
$customerId = 1;
197-
$date = new \DateTime();
198-
$date->modify('-500 second');
199-
$formattedDate = $date->format('Y-m-d H:i:s');
200-
$this->backendConfigMock->expects($this->exactly(2))
201-
->method('getValue')
202-
->withConsecutive(
203-
[\Magento\Customer\Model\Authentication::LOCKOUT_THRESHOLD_PATH],
204-
[\Magento\Customer\Model\Authentication::MAX_FAILURES_PATH]
205-
)
206-
->willReturnOnConsecutiveCalls(10, 5);
207-
208-
$this->customerRegistryMock->expects($this->once())
209-
->method('retrieveSecureData')
210-
->with($customerId)
211-
->willReturn($this->customerSecureMock);
212-
$customerMock = $this->getMockBuilder(CustomerInterface::class)
213-
->disableOriginalConstructor()
214-
->getMock();
215-
$this->customerRepositoryMock->expects($this->once())
216-
->method('getById')
217-
->with($customerId)
218-
->willReturn($customerMock);
219-
$this->customerRepositoryMock->expects($this->once())
220-
->method('save')
221-
->with($customerMock);
222-
223-
$this->customerSecureMock->expects($this->once())->method('getFailuresNum')->willReturn(4);
224-
$this->customerSecureMock->expects($this->once())
225-
->method('getFirstFailure')
226-
->willReturn($formattedDate);
227-
$this->customerSecureMock->expects($this->once())->method('setLockExpires');
228-
$this->customerSecureMock->expects($this->once())->method('setFailuresNum')->with(5);
229-
230-
$this->authentication->processAuthenticationFailure($customerId);
195+
return [
196+
'first attempt' => [0, null, null, 1, 1, 1, 1, null],
197+
'not locked' => [3, '-400 second', null, 1, 4, 0, 0, null],
198+
'lock expired' => [5, '-400 second', '-100 second', 1, 1, 1, 1, null],
199+
'max attempt' => [4, '-400 second', null, 1, 5, 0, 1, 'formattedDate'],
200+
];
231201
}
232202

233203
public function testUnlock()

0 commit comments

Comments
 (0)