Skip to content

Commit 220e251

Browse files
Alexander PaliarushOleksii Korshenko
authored andcommitted
MAGETWO-50608: [Github][Security] Able to brute force API token access
1 parent bcf64cc commit 220e251

File tree

10 files changed

+437
-2
lines changed

10 files changed

+437
-2
lines changed

app/code/Magento/Integration/Model/AdminTokenService.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
use Magento\Integration\Model\Oauth\TokenFactory as TokenModelFactory;
1414
use Magento\Integration\Model\ResourceModel\Oauth\Token\CollectionFactory as TokenCollectionFactory;
1515
use Magento\User\Model\User as UserModel;
16+
use Magento\Integration\Model\Oauth\Token\RequestThrottler;
1617

1718
/**
1819
* Class to handle token generation for Admins
@@ -46,6 +47,11 @@ class AdminTokenService implements \Magento\Integration\Api\AdminTokenServiceInt
4647
*/
4748
private $tokenModelCollectionFactory;
4849

50+
/**
51+
* @var RequestThrottler
52+
*/
53+
private $requestThrottler;
54+
4955
/**
5056
* Initialize service
5157
*
@@ -72,8 +78,10 @@ public function __construct(
7278
public function createAdminAccessToken($username, $password)
7379
{
7480
$this->validatorHelper->validate($username, $password);
81+
$this->getRequestThrottler()->throttle($username, RequestThrottler::USER_TYPE_ADMIN);
7582
$this->userModel->login($username, $password);
7683
if (!$this->userModel->getId()) {
84+
$this->getRequestThrottler()->logAuthenticationFailure($username, RequestThrottler::USER_TYPE_ADMIN);
7785
/*
7886
* This message is same as one thrown in \Magento\Backend\Model\Auth to keep the behavior consistent.
7987
* Constant cannot be created in Auth Model since it uses legacy translation that doesn't support it.
@@ -83,6 +91,7 @@ public function createAdminAccessToken($username, $password)
8391
__('You did not sign in correctly or your account is temporarily disabled.')
8492
);
8593
}
94+
$this->getRequestThrottler()->resetAuthenticationFailuresCount($username, RequestThrottler::USER_TYPE_ADMIN);
8695
return $this->tokenModelFactory->create()->createAdminToken($this->userModel->getId())->getToken();
8796
}
8897

@@ -104,4 +113,18 @@ public function revokeAdminAccessToken($adminId)
104113
}
105114
return true;
106115
}
116+
117+
/**
118+
* Get request throttler instance
119+
*
120+
* @return RequestThrottler
121+
* @deprecated
122+
*/
123+
private function getRequestThrottler()
124+
{
125+
if (!$this->requestThrottler instanceof RequestThrottler) {
126+
return \Magento\Framework\App\ObjectManager::getInstance()->get(RequestThrottler::class);
127+
}
128+
return $this->requestThrottler;
129+
}
107130
}

app/code/Magento/Integration/Model/CustomerTokenService.php

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
use Magento\Integration\Model\Oauth\Token as Token;
1313
use Magento\Integration\Model\Oauth\TokenFactory as TokenModelFactory;
1414
use Magento\Integration\Model\ResourceModel\Oauth\Token\CollectionFactory as TokenCollectionFactory;
15+
use Magento\Integration\Model\Oauth\Token\RequestThrottler;
1516

1617
class CustomerTokenService implements \Magento\Integration\Api\CustomerTokenServiceInterface
1718
{
@@ -41,6 +42,11 @@ class CustomerTokenService implements \Magento\Integration\Api\CustomerTokenServ
4142
*/
4243
private $tokenModelCollectionFactory;
4344

45+
/**
46+
* @var RequestThrottler
47+
*/
48+
private $requestThrottler;
49+
4450
/**
4551
* Initialize service
4652
*
@@ -67,7 +73,14 @@ public function __construct(
6773
public function createCustomerAccessToken($username, $password)
6874
{
6975
$this->validatorHelper->validate($username, $password);
70-
$customerDataObject = $this->accountManagement->authenticate($username, $password);
76+
$this->getRequestThrottler()->throttle($username, RequestThrottler::USER_TYPE_CUSTOMER);
77+
try {
78+
$customerDataObject = $this->accountManagement->authenticate($username, $password);
79+
} catch (\Exception $e) {
80+
$this->getRequestThrottler()->logAuthenticationFailure($username, RequestThrottler::USER_TYPE_CUSTOMER);
81+
throw $e;
82+
}
83+
$this->getRequestThrottler()->resetAuthenticationFailuresCount($username, RequestThrottler::USER_TYPE_CUSTOMER);
7184
return $this->tokenModelFactory->create()->createCustomerToken($customerDataObject->getId())->getToken();
7285
}
7386

@@ -89,4 +102,18 @@ public function revokeCustomerAccessToken($customerId)
89102
}
90103
return true;
91104
}
105+
106+
/**
107+
* Get request throttler instance
108+
*
109+
* @return RequestThrottler
110+
* @deprecated
111+
*/
112+
private function getRequestThrottler()
113+
{
114+
if (!$this->requestThrottler instanceof RequestThrottler) {
115+
return \Magento\Framework\App\ObjectManager::getInstance()->get(RequestThrottler::class);
116+
}
117+
return $this->requestThrottler;
118+
}
92119
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?php
2+
/**
3+
* Copyright © 2016 Magento. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
namespace Magento\Integration\Model\Oauth\Token\RequestLog;
7+
8+
use Magento\Framework\App\Config\ReinitableConfigInterface;
9+
10+
/**
11+
* Token request log config.
12+
*/
13+
class Config
14+
{
15+
/**
16+
* @var ReinitableConfigInterface
17+
*/
18+
private $storeConfig;
19+
20+
/**
21+
* Initialize dependencies.
22+
*
23+
* @param ReinitableConfigInterface $storeConfig
24+
*/
25+
public function __construct(ReinitableConfigInterface $storeConfig)
26+
{
27+
$this->storeConfig = $storeConfig;
28+
}
29+
30+
/**
31+
* Get maximum allowed authentication failures count before account is locked.
32+
*
33+
* @return int
34+
*/
35+
public function getMaxFailuresCount()
36+
{
37+
return (int)$this->storeConfig->getValue('oauth/authentication_lock/max_failures_count');
38+
}
39+
40+
/**
41+
* Get period of time in seconds after which account will be unlocked.
42+
*
43+
* @return int
44+
*/
45+
public function getLockTimeout()
46+
{
47+
return (int)$this->storeConfig->getValue('oauth/authentication_lock/timeout');
48+
}
49+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
/**
3+
*
4+
* Copyright © 2016 Magento. All rights reserved.
5+
* See COPYING.txt for license details.
6+
*/
7+
namespace Magento\Integration\Model\Oauth\Token\RequestLog;
8+
9+
/**
10+
* OAuth token request log reader interface.
11+
*/
12+
interface ReaderInterface
13+
{
14+
/**
15+
* Get number of authentication failures for the specified user account.
16+
*
17+
* @param string $userName
18+
* @param int $userType
19+
* @return int
20+
*/
21+
public function getFailuresCount($userName, $userType);
22+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php
2+
/**
3+
*
4+
* Copyright © 2016 Magento. All rights reserved.
5+
* See COPYING.txt for license details.
6+
*/
7+
namespace Magento\Integration\Model\Oauth\Token\RequestLog;
8+
9+
/**
10+
* OAuth token request log writer interface.
11+
*/
12+
interface WriterInterface
13+
{
14+
/**
15+
* Reset number of authentication failures for the specified user account.
16+
*
17+
* @param string $userName
18+
* @param int $userType
19+
* @param return void
20+
*/
21+
public function resetFailuresCount($userName, $userType);
22+
23+
/**
24+
* Increment number of authentication failures for the specified user account.
25+
*
26+
* @param string $userName
27+
* @param int $userType
28+
* @param return void
29+
*/
30+
public function incrementFailuresCount($userName, $userType);
31+
32+
/**
33+
* Clear expired authentication failure logs.
34+
*
35+
* @return void
36+
*/
37+
public function clearExpiredFailures();
38+
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
<?php
2+
/**
3+
* Copyright © 2016 Magento. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
namespace Magento\Integration\Model\Oauth\Token;
7+
8+
use Magento\Integration\Model\Oauth\Token\RequestLog\ReaderInterface as RequestLogReader;
9+
use Magento\Integration\Model\Oauth\Token\RequestLog\WriterInterface as RequestLogWriter;
10+
use Magento\Integration\Model\Oauth\Token\RequestLog\Config as RequestLogConfig;
11+
use Magento\Framework\Exception\AuthenticationException;
12+
13+
/**
14+
* Model for OAuth admin/customer token requests throttling.
15+
*/
16+
class RequestThrottler
17+
{
18+
/**#@+
19+
* Web API user type
20+
*/
21+
const USER_TYPE_CUSTOMER = 2;
22+
const USER_TYPE_ADMIN = 3;
23+
/**#@-*/
24+
25+
/**
26+
* @var RequestLogReader
27+
*/
28+
private $requestLogReader;
29+
30+
/**
31+
* @var RequestLogWriter
32+
*/
33+
private $requestLogWriter;
34+
35+
/**
36+
* @var RequestLogConfig
37+
*/
38+
private $requestLogConfig;
39+
40+
/**
41+
* Initialize dependencies.
42+
*
43+
* @param RequestLogReader $requestLogReader
44+
* @param RequestLogWriter $requestLogWriter
45+
* @param RequestLogConfig $requestLogConfig
46+
*/
47+
public function __construct(
48+
RequestLogReader $requestLogReader,
49+
RequestLogWriter $requestLogWriter,
50+
RequestLogConfig $requestLogConfig
51+
) {
52+
$this->requestLogReader = $requestLogReader;
53+
$this->requestLogWriter = $requestLogWriter;
54+
$this->requestLogConfig = $requestLogConfig;
55+
}
56+
57+
/**
58+
* Throw exception if user account is currently locked because of too many failed authentication attempts.
59+
*
60+
* @param string $userName
61+
* @param int $userType
62+
* @return void
63+
* @throws AuthenticationException
64+
*/
65+
public function throttle($userName, $userType)
66+
{
67+
$count = $this->requestLogReader->getFailuresCount($userName, $userType);
68+
if ($count >= $this->requestLogConfig->getMaxFailuresCount()) {
69+
throw new AuthenticationException(__('Provided credentials are invalid or user account is locked.'));
70+
}
71+
}
72+
73+
/**
74+
* Reset count of failed authentication attempts.
75+
*
76+
* Unlock user account and make generation of OAuth tokens possible for this account again.
77+
*
78+
* @param string $userName
79+
* @param int $userType
80+
* @return void
81+
*/
82+
public function resetAuthenticationFailuresCount($userName, $userType)
83+
{
84+
$this->requestLogWriter->resetFailuresCount($userName, $userType);
85+
}
86+
87+
/**
88+
* Increment authentication failures count and lock user account if the limit is reached.
89+
*
90+
* Account will be locked until lock expires.
91+
*
92+
* @param string $userName
93+
* @param int $userType
94+
* @return void
95+
*/
96+
public function logAuthenticationFailure($userName, $userType)
97+
{
98+
$this->requestLogWriter->incrementFailuresCount($userName, $userType);
99+
}
100+
}

0 commit comments

Comments
 (0)