Skip to content

Commit ae8fafc

Browse files
committed
AC-1619: Integration access tokens do not work as Bearer tokens
1 parent 32b33fc commit ae8fafc

File tree

7 files changed

+200
-30
lines changed

7 files changed

+200
-30
lines changed
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
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\Analytics\Plugin;
10+
11+
use Magento\Framework\App\Config\ScopeConfigInterface;
12+
use Magento\Integration\Model\Integration;
13+
use Magento\Integration\Model\Validator\BearerTokenValidator;
14+
15+
/**
16+
* Overrides authorization config to always allow analytics token to be used as bearer
17+
*/
18+
class BearerTokenValidatorPlugin
19+
{
20+
/**
21+
* @var ScopeConfigInterface
22+
*/
23+
private ScopeConfigInterface $config;
24+
25+
/**
26+
* @param ScopeConfigInterface $config
27+
*/
28+
public function __construct(ScopeConfigInterface $config)
29+
{
30+
$this->config = $config;
31+
}
32+
33+
/***
34+
* Always allow access token for analytics to be used as bearer
35+
*
36+
* @param BearerTokenValidator $subject
37+
* @param bool $result
38+
* @param Integration $integration
39+
* @return bool
40+
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
41+
*/
42+
public function afterIsIntegrationAllowedToAsBearerToken(
43+
BearerTokenValidator $subject,
44+
bool $result,
45+
Integration $integration
46+
): bool {
47+
return $result || $integration->getName() === $this->config->getValue('analytics/integration_name');
48+
}
49+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
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\Analytics\Test\Unit\Plugin;
10+
11+
use Magento\Analytics\Plugin\BearerTokenValidatorPlugin;
12+
use Magento\Framework\App\Config\ScopeConfigInterface;
13+
use Magento\Integration\Model\Integration;
14+
use Magento\Integration\Model\Validator\BearerTokenValidator;
15+
use PHPUnit\Framework\MockObject\MockObject;
16+
use PHPUnit\Framework\TestCase;
17+
18+
class BearerTokenValidatorPluginTest extends TestCase
19+
{
20+
/**
21+
* @var BearerTokenValidatorPlugin
22+
*/
23+
private BearerTokenValidatorPlugin $plugin;
24+
25+
/**
26+
* @var BearerTokenValidator|MockObject
27+
*/
28+
private $validator;
29+
30+
public function setUp(): void
31+
{
32+
$config = $this->createMock(ScopeConfigInterface::class);
33+
$config->method('getValue')
34+
->with('analytics/integration_name')
35+
->willReturn('abc');
36+
$this->plugin = new BearerTokenValidatorPlugin($config);
37+
$this->validator = $this->createMock(BearerTokenValidator::class);
38+
}
39+
40+
public function testTrueIsPassedThrough()
41+
{
42+
$integration = $this->createMock(Integration::class);
43+
$integration->method('__call')
44+
->with('getName')
45+
->willReturn('invalid');
46+
47+
$result = $this->plugin->afterIsIntegrationAllowedToAsBearerToken($this->validator, true, $integration);
48+
self::assertTrue($result);
49+
}
50+
51+
public function testFalseWhenIntegrationDoesntMatch()
52+
{
53+
$integration = $this->createMock(Integration::class);
54+
$integration->method('__call')
55+
->with('getName')
56+
->willReturn('invalid');
57+
58+
$result = $this->plugin->afterIsIntegrationAllowedToAsBearerToken($this->validator, false, $integration);
59+
self::assertFalse($result);
60+
}
61+
62+
public function testTrueWhenIntegrationMatches()
63+
{
64+
$integration = $this->createMock(Integration::class);
65+
$integration->method('__call')
66+
->with('getName')
67+
->willReturn('abc');
68+
69+
$result = $this->plugin->afterIsIntegrationAllowedToAsBearerToken($this->validator, true, $integration);
70+
self::assertTrue($result);
71+
}
72+
}

app/code/Magento/Analytics/etc/di.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,4 +271,7 @@
271271
<argument name="connectionFactory" xsi:type="object">Magento\Framework\Model\ResourceModel\Type\Db\ConnectionFactory</argument>
272272
</arguments>
273273
</type>
274+
<type name="Magento\Integration\Model\Validator\BearerTokenValidator">
275+
<plugin name="allow_bearer_token" type="Magento\Analytics\Plugin\BearerTokenValidatorPlugin"/>
276+
</type>
274277
</config>

app/code/Magento/Integration/Model/Config/AuthorizationConfig.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ class AuthorizationConfig
2424
/**
2525
* @var ScopeConfigInterface
2626
*/
27-
private $scopeConfig;
27+
private ScopeConfigInterface $scopeConfig;
2828

2929
/**
3030
* @param ScopeConfigInterface $scopeConfig

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

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
namespace Magento\Integration\Model\OpaqueToken;
1010

11+
use Magento\Authorization\Model\UserContextInterface;
1112
use Magento\Framework\App\ObjectManager;
1213
use Magento\Integration\Api\Data\UserToken;
1314
use Magento\Integration\Api\Exception\UserTokenException;
@@ -18,6 +19,7 @@
1819
use Magento\Integration\Model\Oauth\Token;
1920
use Magento\Integration\Model\Oauth\TokenFactory;
2021
use Magento\Integration\Helper\Oauth\Data as OauthHelper;
22+
use Magento\Integration\Model\Validator\BearerTokenValidator;
2123

2224
/**
2325
* Reads user token data
@@ -37,33 +39,33 @@ class Reader implements UserTokenReaderInterface
3739
private $helper;
3840

3941
/**
40-
* @var AuthorizationConfig
42+
* @var IntegrationServiceInterface
4143
*/
42-
private $authorizationConfig;
44+
private IntegrationServiceInterface $integrationService;
4345

4446
/**
45-
* @var IntegrationServiceInterface
47+
* @var BearerTokenValidator
4648
*/
47-
private IntegrationServiceInterface $integrationService;
49+
private BearerTokenValidator $bearerTokenValidator;
4850

4951
/**
5052
* @param TokenFactory $tokenFactory
5153
* @param OauthHelper $helper
52-
* @param AuthorizationConfig|null $authorizationConfig
5354
* @param IntegrationServiceInterface|null $integrationService
55+
* @param BearerTokenValidator|null $bearerTokenValidator
5456
*/
5557
public function __construct(
5658
TokenFactory $tokenFactory,
5759
OauthHelper $helper,
58-
?AuthorizationConfig $authorizationConfig = null,
59-
?IntegrationServiceInterface $integrationService = null
60+
?IntegrationServiceInterface $integrationService = null,
61+
?BearerTokenValidator $bearerTokenValidator = null
6062
) {
6163
$this->tokenFactory = $tokenFactory;
6264
$this->helper = $helper;
63-
$this->authorizationConfig = $authorizationConfig ?? ObjectManager::getInstance()
64-
->get(AuthorizationConfig::class);
6565
$this->integrationService = $integrationService ?? ObjectManager::getInstance()
6666
->get(IntegrationServiceInterface::class);
67+
$this->bearerTokenValidator = $bearerTokenValidator ?? ObjectManager::getInstance()
68+
->get(BearerTokenValidator::class);
6769
}
6870

6971
/**
@@ -118,12 +120,9 @@ private function getTokenModel(string $token): Token
118120
*/
119121
private function validateUserType(int $userType): void
120122
{
121-
if ($userType === CustomUserContext::USER_TYPE_INTEGRATION) {
122-
if (!$this->authorizationConfig->isIntegrationAsBearerEnabled()) {
123-
throw new UserTokenException('Invalid token found');
124-
}
125-
} elseif ($userType !== CustomUserContext::USER_TYPE_ADMIN
123+
if ($userType !== CustomUserContext::USER_TYPE_ADMIN
126124
&& $userType !== CustomUserContext::USER_TYPE_CUSTOMER
125+
&& $userType !== CustomUserContext::USER_TYPE_INTEGRATION
127126
) {
128127
throw new UserTokenException('Invalid token found');
129128
}
@@ -138,11 +137,15 @@ private function validateUserType(int $userType): void
138137
private function getUserId(Token $tokenModel): int
139138
{
140139
$userType = (int)$tokenModel->getUserType();
140+
$userId = null;
141141

142142
if ($userType === CustomUserContext::USER_TYPE_ADMIN) {
143143
$userId = $tokenModel->getAdminId();
144144
} elseif ($userType === CustomUserContext::USER_TYPE_INTEGRATION) {
145-
$userId = $this->integrationService->findByConsumerId($tokenModel->getConsumerId())->getId();
145+
$integration = $this->integrationService->findByConsumerId($tokenModel->getConsumerId());
146+
if ($this->bearerTokenValidator->isIntegrationAllowedToAsBearerToken($integration)) {
147+
$userId = $integration->getId();
148+
}
146149
} else {
147150
$userId = $tokenModel->getCustomerId();
148151
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
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\Validator;
10+
11+
use Magento\Integration\Model\Config\AuthorizationConfig;
12+
use Magento\Integration\Model\Integration;
13+
14+
/**
15+
* Validate if an integration use the access token as a bearer token
16+
*/
17+
class BearerTokenValidator
18+
{
19+
/**
20+
* @var AuthorizationConfig
21+
*/
22+
private AuthorizationConfig $authorizationConfig;
23+
24+
/**
25+
* @param AuthorizationConfig $authorizationConfig
26+
*/
27+
public function __construct(AuthorizationConfig $authorizationConfig)
28+
{
29+
$this->authorizationConfig = $authorizationConfig;
30+
}
31+
32+
/**
33+
* Validate an integration's access token can be used as a standalone bearer token
34+
*
35+
* @param Integration $integration
36+
* @return bool
37+
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
38+
*/
39+
public function isIntegrationAllowedToAsBearerToken(Integration $integration): bool
40+
{
41+
return $this->authorizationConfig->isIntegrationAsBearerEnabled();
42+
}
43+
}

app/code/Magento/Webapi/Model/Authorization/SoapUserContext.php

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,11 @@
88

99
use Magento\Authorization\Model\UserContextInterface;
1010
use Magento\Framework\App\ObjectManager;
11-
use Magento\Integration\Model\Config\AuthorizationConfig;
1211
use Magento\Integration\Model\Oauth\Token;
1312
use Magento\Integration\Model\Oauth\TokenFactory;
1413
use Magento\Integration\Api\IntegrationServiceInterface;
1514
use Magento\Framework\Webapi\Request;
16-
use Magento\Framework\Stdlib\DateTime\DateTime as Date;
17-
use Magento\Framework\Stdlib\DateTime;
18-
use Magento\Integration\Helper\Oauth\Data as OauthHelper;
15+
use Magento\Integration\Model\Validator\BearerTokenValidator;
1916

2017
/**
2118
* SOAP specific user context based on opaque tokens.
@@ -50,32 +47,32 @@ class SoapUserContext implements UserContextInterface
5047
/**
5148
* @var IntegrationServiceInterface
5249
*/
53-
private $integrationService;
50+
private IntegrationServiceInterface $integrationService;
5451

5552
/**
56-
* @var AuthorizationConfig
53+
* @var BearerTokenValidator
5754
*/
58-
private $authorizationConfig;
55+
private BearerTokenValidator $bearerTokenValidator;
5956

6057
/**
6158
* Initialize dependencies.
6259
*
6360
* @param Request $request
6461
* @param TokenFactory $tokenFactory
6562
* @param IntegrationServiceInterface $integrationService
66-
* @param AuthorizationConfig|null $authorizationConfig
63+
* @param BearerTokenValidator|null $bearerTokenValidator
6764
*/
6865
public function __construct(
6966
Request $request,
7067
TokenFactory $tokenFactory,
7168
IntegrationServiceInterface $integrationService,
72-
?AuthorizationConfig $authorizationConfig = null
69+
?BearerTokenValidator $bearerTokenValidator = null
7370
) {
7471
$this->request = $request;
7572
$this->tokenFactory = $tokenFactory;
7673
$this->integrationService = $integrationService;
77-
$this->authorizationConfig = $authorizationConfig ?? ObjectManager::getInstance()
78-
->get(AuthorizationConfig::class);
74+
$this->bearerTokenValidator = $bearerTokenValidator ?? ObjectManager::getInstance()
75+
->get(BearerTokenValidator::class);
7976
}
8077

8178
/**
@@ -117,7 +114,7 @@ private function processRequest() //phpcs:ignore CopyPaste
117114
return;
118115
}
119116
$tokenType = strtolower($headerPieces[0]);
120-
if ($tokenType !== 'bearer' || !$this->authorizationConfig->isIntegrationAsBearerEnabled()) {
117+
if ($tokenType !== 'bearer') {
121118
$this->isRequestProcessed = true;
122119
return;
123120
}
@@ -131,8 +128,11 @@ private function processRequest() //phpcs:ignore CopyPaste
131128
return;
132129
}
133130
if (((int) $token->getUserType()) === UserContextInterface::USER_TYPE_INTEGRATION) {
134-
$this->userId = $this->integrationService->findByConsumerId($token->getConsumerId())->getId();
135-
$this->userType = UserContextInterface::USER_TYPE_INTEGRATION;
131+
$integration = $this->integrationService->findByConsumerId($token->getConsumerId());
132+
if ($this->bearerTokenValidator->isIntegrationAllowedToAsBearerToken($integration)) {
133+
$this->userId = $integration->getId();
134+
$this->userType = UserContextInterface::USER_TYPE_INTEGRATION;
135+
}
136136
}
137137
$this->isRequestProcessed = true;
138138
}

0 commit comments

Comments
 (0)