Skip to content

Commit 04225f2

Browse files
author
ogorkun
committed
MC-32830: Do not store admin and customer tokens in DB
1 parent 5b33a52 commit 04225f2

File tree

9 files changed

+436
-6
lines changed

9 files changed

+436
-6
lines changed

app/code/Magento/Integration/Api/Exception/UserTokenException.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ public function __construct(
2626
?UserContextInterface $forContext = null
2727
) {
2828
parent::__construct($message, 0, $previous);
29+
$this->context = $forContext;
2930
}
3031

3132
public function getUserContext(): ?UserContextInterface

app/code/Magento/Integration/Model/OpaqueToken/Issuer.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,12 @@ public function __construct(TokenModelFactory $tokenFactory)
3939
public function create(UserContextInterface $userContext, UserTokenParametersInterface $params): string
4040
{
4141
/** @var Token $token */
42-
$token = $this->tokenModelFactory->create();
42+
$token = $this->tokenFactory->create();
4343

4444
if ($userContext->getUserType() === UserContextInterface::USER_TYPE_CUSTOMER) {
45-
$token = $token->createCustomerToken($userContext->getUserId())->getToken();
45+
$token = $token->createCustomerToken($userContext->getUserId());
4646
} elseif ($userContext->getUserType() === UserContextInterface::USER_TYPE_ADMIN) {
47-
$token = $token->createAdminToken($userContext->getUserId())->getToken();
47+
$token = $token->createAdminToken($userContext->getUserId());
4848
} else {
4949
throw new UserTokenException('Can only create tokens for customers and admin users');
5050
}

app/code/Magento/Integration/Model/OpaqueToken/Reader.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ public function read(string $token): UserToken
5353
if ($tokenModel->getRevoked()) {
5454
throw new UserTokenException('Token was revoked');
5555
}
56-
$userType = $tokenModel->getUserType();
56+
$userType = (int) $tokenModel->getUserType();
5757
if ($userType !== CustomUserContext::USER_TYPE_ADMIN && $userType !== CustomUserContext::USER_TYPE_CUSTOMER) {
5858
throw new UserTokenException('Invalid token found');
5959
}
@@ -72,7 +72,7 @@ public function read(string $token): UserToken
7272
);
7373
$lifetimeHours = $userType === CustomUserContext::USER_TYPE_ADMIN
7474
? $this->helper->getAdminTokenLifetime() : $this->helper->getCustomerTokenLifetime();
75-
$expires = $issued->add(new \DateInterval("P{$lifetimeHours}H"));
75+
$expires = $issued->add(new \DateInterval("PT{$lifetimeHours}H"));
7676

7777
return new UserToken(new CustomUserContext((int) $userId, (int) $userType), new Data($issued, $expires));
7878
}

app/code/Magento/Integration/Model/UserToken/ExpirationValidator.php

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,29 @@
1111
use Magento\Framework\Exception\AuthorizationException;
1212
use Magento\Integration\Api\Data\UserToken;
1313
use Magento\Integration\Api\UserTokenValidatorInterface;
14+
use Magento\Framework\Stdlib\DateTime\DateTime as DtUtil;
1415

1516
class ExpirationValidator implements UserTokenValidatorInterface
1617
{
18+
/**
19+
* @var DtUtil
20+
*/
21+
private $datetimeUtil;
22+
23+
/**
24+
* @param DtUtil $datetimeUtil
25+
*/
26+
public function __construct(DtUtil $datetimeUtil)
27+
{
28+
$this->datetimeUtil = $datetimeUtil;
29+
}
30+
1731
/**
1832
* @inheritDoc
1933
*/
2034
public function validate(UserToken $token): void
2135
{
22-
if ($token->getData()->getExpires()->getTimestamp() <= time()) {
36+
if ($token->getData()->getExpires()->getTimestamp() <= $this->datetimeUtil->gmtTimestamp()) {
2337
throw new AuthorizationException(__('Consumer key has expired'));
2438
}
2539
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
7+
declare(strict_types=1);
8+
9+
namespace Magento\Integration\Test\Unit\Model;
10+
11+
use Magento\Integration\Api\Data\UserToken;
12+
use Magento\Integration\Api\UserTokenValidatorInterface;
13+
use Magento\Integration\Model\CompositeUserTokenValidator;
14+
use PHPUnit\Framework\TestCase;
15+
16+
class CompositeUserTokenValidatorTest extends TestCase
17+
{
18+
public function testValidate(): void
19+
{
20+
$userToken = $this->createMock(UserToken::class);
21+
22+
$validator1 = $this->createMock(UserTokenValidatorInterface::class);
23+
$validator1->expects($this->once())->method('validate')->with($userToken);
24+
25+
$validator2 = $this->createMock(UserTokenValidatorInterface::class);
26+
$validator2->expects($this->once())->method('validate')->with($userToken);
27+
28+
$model = new CompositeUserTokenValidator([$validator1, $validator2]);
29+
$model->validate($userToken);
30+
}
31+
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
7+
declare(strict_types=1);
8+
9+
namespace Magento\Integration\Test\Unit\Model\UserToken;
10+
11+
use Magento\Framework\Exception\AuthorizationException;
12+
use Magento\Integration\Api\Data\UserToken;
13+
use Magento\Integration\Api\Data\UserTokenDataInterface;
14+
use Magento\Integration\Model\UserToken\ExpirationValidator;
15+
use PHPUnit\Framework\MockObject\MockObject;
16+
use PHPUnit\Framework\TestCase;
17+
use Magento\Framework\Stdlib\DateTime\DateTime as DtUtil;
18+
19+
class ExpirationValidatorTest extends TestCase
20+
{
21+
/**
22+
* @var ExpirationValidator
23+
*/
24+
private $model;
25+
26+
/**
27+
* @var DtUtil|MockObject
28+
*/
29+
private $datetimeUtilMock;
30+
31+
/**
32+
* @inheritDoc
33+
*/
34+
protected function setUp(): void
35+
{
36+
parent::setUp();
37+
38+
$this->datetimeUtilMock = $this->createMock(DtUtil::class);
39+
$this->model = new ExpirationValidator($this->datetimeUtilMock);
40+
}
41+
42+
public function getUserTokens(): array
43+
{
44+
$currentTs = \DateTimeImmutable::createFromFormat('Y-m-d H:i:s', '2021-04-07 14:00:00')
45+
->getTimestamp();
46+
47+
$pastToken = $this->createMock(UserToken::class);
48+
$pastData = $this->createMock(UserTokenDataInterface::class);
49+
$pastData->method('getExpires')
50+
->willReturn(\DateTimeImmutable::createFromFormat('Y-m-d H:i:s', '2021-04-07 12:00:00'));
51+
$pastToken->method('getData')->willReturn($pastData);
52+
53+
$exactToken = $this->createMock(UserToken::class);
54+
$exactData = $this->createMock(UserTokenDataInterface::class);
55+
$exactData->method('getExpires')
56+
->willReturn(\DateTimeImmutable::createFromFormat('Y-m-d H:i:s', '2021-04-07 14:00:00'));
57+
$exactToken->method('getData')->willReturn($exactData);
58+
59+
$futureToken = $this->createMock(UserToken::class);
60+
$futureData = $this->createMock(UserTokenDataInterface::class);
61+
$futureData->method('getExpires')
62+
->willReturn(\DateTimeImmutable::createFromFormat('Y-m-d H:i:s', '2021-04-07 16:00:00'));
63+
$futureToken->method('getData')->willReturn($futureData);
64+
65+
return [
66+
'past' => [$pastToken, false, $currentTs],
67+
'exact' => [$exactToken, false, $currentTs],
68+
'future' => [$futureToken, true, $currentTs]
69+
];
70+
}
71+
72+
/**
73+
* Test "validate" method.
74+
*
75+
* @param UserToken $userToken
76+
* @param bool $isValid
77+
* @param int $currentTimestamp
78+
* @throws AuthorizationException
79+
* @dataProvider getUserTokens
80+
*/
81+
public function testValidate(UserToken $userToken, bool $isValid, int $currentTimestamp): void
82+
{
83+
if (!$isValid) {
84+
$this->expectException(AuthorizationException::class);
85+
}
86+
$this->datetimeUtilMock->method('gmtTimestamp')->willReturn($currentTimestamp);
87+
88+
$this->model->validate($userToken);
89+
}
90+
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
7+
declare(strict_types=1);
8+
9+
namespace Magento\Integration\Model\OpaqueToken;
10+
11+
use Magento\Authorization\Model\UserContextInterface;
12+
use Magento\Customer\Api\CustomerRepositoryInterface;
13+
use Magento\Integration\Model\CustomUserContext;
14+
use Magento\Integration\Model\UserToken\UserTokenParameters;
15+
use Magento\Integration\Model\UserToken\UserTokenParametersFactory;
16+
use Magento\TestFramework\Helper\Bootstrap;
17+
use PHPUnit\Framework\TestCase;
18+
use Magento\User\Model\User as UserModel;
19+
20+
class IssuerTest extends TestCase
21+
{
22+
/**
23+
* @var Issuer
24+
*/
25+
private $model;
26+
27+
/**
28+
* @var CustomerRepositoryInterface
29+
*/
30+
private $customerRepo;
31+
32+
/**
33+
* @var UserModel
34+
*/
35+
private $userModel;
36+
37+
/**
38+
* @var UserTokenParametersFactory
39+
*/
40+
private $paramsFactory;
41+
42+
protected function setUp(): void
43+
{
44+
$objectManager = Bootstrap::getObjectManager();
45+
46+
$this->model = $objectManager->get(Issuer::class);
47+
$this->customerRepo = $objectManager->get(CustomerRepositoryInterface::class);
48+
$this->userModel = $objectManager->create(UserModel::class);
49+
$this->paramsFactory = $objectManager->get(UserTokenParametersFactory::class);
50+
}
51+
52+
/**
53+
* Verify that a token can be issued for a customer.
54+
*
55+
* @return void
56+
* @throws \Throwable
57+
* @magentoDataFixture Magento/Customer/_files/customer.php
58+
*/
59+
public function testIssueForCustomer(): void
60+
{
61+
$customer = $this->customerRepo->get('[email protected]');
62+
/** @var UserTokenParameters $params */
63+
$params = $this->paramsFactory->create();
64+
$token = $this->model->create(
65+
new CustomUserContext((int) $customer->getId(), UserContextInterface::USER_TYPE_CUSTOMER),
66+
$params
67+
);
68+
69+
$this->assertNotEmpty($token);
70+
}
71+
72+
/**
73+
* Verify that a token can be issued for an admin user.
74+
*
75+
* @return void
76+
* @throws \Throwable
77+
* @magentoDataFixture Magento/User/_files/user_with_role.php
78+
*/
79+
public function testIssueForAdmin(): void
80+
{
81+
$admin = $this->userModel->loadByUsername('adminUser');
82+
/** @var UserTokenParameters $params */
83+
$params = $this->paramsFactory->create();
84+
$token = $this->model->create(
85+
new CustomUserContext((int) $admin->getId(), UserContextInterface::USER_TYPE_ADMIN),
86+
$params
87+
);
88+
89+
$this->assertNotEmpty($token);
90+
}
91+
}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
7+
declare(strict_types=1);
8+
9+
namespace Magento\Integration\Model\OpaqueToken;
10+
11+
use Magento\Authorization\Model\UserContextInterface;
12+
use Magento\Customer\Api\CustomerRepositoryInterface;
13+
use Magento\Integration\Model\CustomUserContext;
14+
use Magento\Integration\Model\UserToken\UserTokenParameters;
15+
use Magento\TestFramework\Helper\Bootstrap;
16+
use Magento\User\Model\User as UserModel;
17+
use PHPUnit\Framework\TestCase;
18+
use Magento\Integration\Model\UserToken\UserTokenParametersFactory;
19+
20+
class ReaderTest extends TestCase
21+
{
22+
/**
23+
* @var Reader
24+
*/
25+
private $model;
26+
27+
/**
28+
* @var CustomerRepositoryInterface
29+
*/
30+
private $customerRepo;
31+
32+
/**
33+
* @var UserModel
34+
*/
35+
private $userModel;
36+
37+
/**
38+
* @var UserTokenParametersFactory
39+
*/
40+
private $paramsFactory;
41+
42+
/**
43+
* @var Issuer
44+
*/
45+
private $issuer;
46+
47+
/**
48+
* @inheritDoc
49+
*/
50+
protected function setUp(): void
51+
{
52+
$objectManager = Bootstrap::getObjectManager();
53+
54+
$this->model = $objectManager->get(Reader::class);
55+
$this->customerRepo = $objectManager->get(CustomerRepositoryInterface::class);
56+
$this->userModel = $objectManager->create(UserModel::class);
57+
$this->paramsFactory = $objectManager->get(UserTokenParametersFactory::class);
58+
$this->issuer = $objectManager->get(Issuer::class);
59+
}
60+
61+
/**
62+
* Verify that a token can be accepted for a customer.
63+
*
64+
* @return void
65+
* @throws \Throwable
66+
* @magentoDataFixture Magento/Customer/_files/customer.php
67+
*/
68+
public function testReadingCustomer(): void
69+
{
70+
//Preparing the token
71+
$customer = $this->customerRepo->get('[email protected]');
72+
/** @var UserTokenParameters $params */
73+
$params = $this->paramsFactory->create(
74+
['issued' => $issued = (new \DateTimeImmutable())->sub(new \DateInterval('PT1H'))]
75+
);
76+
$token = $this->issuer->create(
77+
new CustomUserContext((int) $customer->getId(), UserContextInterface::USER_TYPE_CUSTOMER),
78+
$params
79+
);
80+
81+
$data = $this->model->read($token);
82+
$this->assertEquals(UserContextInterface::USER_TYPE_CUSTOMER, $data->getUserContext()->getUserType());
83+
$this->assertEquals((int) $customer->getId(), $data->getUserContext()->getUserId());
84+
$this->assertEquals($issued->format('Y-m-d H:i:s'), $data->getData()->getIssued()->format('Y-m-d H:i:s'));
85+
$this->assertGreaterThan($issued, $data->getData()->getExpires());
86+
}
87+
88+
/**
89+
* Verify that a token can be accepted for an admin user.
90+
*
91+
* @return void
92+
* @throws \Throwable
93+
* @magentoDataFixture Magento/User/_files/user_with_role.php
94+
*/
95+
public function testRadingAdmin(): void
96+
{
97+
//Preparing the token
98+
$admin = $this->userModel->loadByUsername('adminUser');
99+
/** @var UserTokenParameters $params */
100+
$params = $this->paramsFactory->create();
101+
$token = $this->issuer->create(
102+
new CustomUserContext((int) $admin->getId(), UserContextInterface::USER_TYPE_ADMIN),
103+
$params
104+
);
105+
106+
$data = $this->model->read($token);
107+
$this->assertEquals(UserContextInterface::USER_TYPE_ADMIN, $data->getUserContext()->getUserType());
108+
$this->assertEquals((int) $admin->getId(), $data->getUserContext()->getUserId());
109+
$this->assertGreaterThan($data->getData()->getIssued(), $data->getData()->getExpires());
110+
}
111+
}

0 commit comments

Comments
 (0)