Skip to content

Commit f69a8ee

Browse files
Merge pull request #7340 from magento-cia/2.4-bugfixes-121621
Bugfixes
2 parents 112c038 + 74b5b18 commit f69a8ee

File tree

50 files changed

+1588
-305
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+1588
-305
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 afterIsIntegrationAllowedAsBearerToken(
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->afterIsIntegrationAllowedAsBearerToken($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->afterIsIntegrationAllowedAsBearerToken($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->afterIsIntegrationAllowedAsBearerToken($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>
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
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;
10+
11+
use Magento\Integration\Api\Data\UserToken;
12+
use Magento\Integration\Api\Exception\UserTokenException;
13+
use Magento\Integration\Api\UserTokenReaderInterface;
14+
15+
/**
16+
* Checks multiple sources for reading a token
17+
*/
18+
class CompositeTokenReader implements UserTokenReaderInterface
19+
{
20+
/**
21+
* @var UserTokenReaderInterface[]
22+
*/
23+
private $readers;
24+
25+
/**
26+
* @param UserTokenReaderInterface[] $readers
27+
*/
28+
public function __construct(array $readers)
29+
{
30+
$this->readers = $readers;
31+
}
32+
33+
/**
34+
* @inheritDoc
35+
*/
36+
public function read(string $token): UserToken
37+
{
38+
foreach ($this->readers as $reader) {
39+
try {
40+
return $reader->read($token);
41+
} catch (UserTokenException $exception) {
42+
continue;
43+
}
44+
}
45+
46+
throw new UserTokenException('Composite reader could not read a token');
47+
}
48+
}
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\Integration\Model\Config;
10+
11+
use Magento\Framework\App\Config\ScopeConfigInterface;
12+
use Magento\Store\Model\ScopeInterface;
13+
14+
/**
15+
* Represents configuration related to WebAPI Authorization
16+
*/
17+
class AuthorizationConfig
18+
{
19+
/**
20+
* XML Path for Enable Integration as Bearer
21+
*/
22+
private const CONFIG_PATH_INTEGRATION_BEARER = 'oauth/consumer/enable_integration_as_bearer';
23+
24+
/**
25+
* @var ScopeConfigInterface
26+
*/
27+
private ScopeConfigInterface $scopeConfig;
28+
29+
/**
30+
* @param ScopeConfigInterface $scopeConfig
31+
*/
32+
public function __construct(ScopeConfigInterface $scopeConfig)
33+
{
34+
$this->scopeConfig = $scopeConfig;
35+
}
36+
37+
/**
38+
* Return if integration access tokens can be used as bearer tokens
39+
*
40+
* @return bool
41+
*/
42+
public function isIntegrationAsBearerEnabled(): bool
43+
{
44+
return $this->scopeConfig->isSetFlag(
45+
self::CONFIG_PATH_INTEGRATION_BEARER,
46+
ScopeInterface::SCOPE_STORE
47+
);
48+
}
49+
}

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

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

99
namespace Magento\Integration\Model\OpaqueToken;
1010

11+
use Magento\Authorization\Model\UserContextInterface;
12+
use Magento\Framework\App\ObjectManager;
1113
use Magento\Integration\Api\Data\UserToken;
1214
use Magento\Integration\Api\Exception\UserTokenException;
15+
use Magento\Integration\Api\IntegrationServiceInterface;
1316
use Magento\Integration\Api\UserTokenReaderInterface;
17+
use Magento\Integration\Model\Config\AuthorizationConfig;
1418
use Magento\Integration\Model\CustomUserContext;
1519
use Magento\Integration\Model\Oauth\Token;
1620
use Magento\Integration\Model\Oauth\TokenFactory;
1721
use Magento\Integration\Helper\Oauth\Data as OauthHelper;
22+
use Magento\Integration\Model\Validator\BearerTokenValidator;
1823

24+
/**
25+
* Reads user token data
26+
*
27+
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
28+
*/
1929
class Reader implements UserTokenReaderInterface
2030
{
2131
/**
@@ -28,22 +38,67 @@ class Reader implements UserTokenReaderInterface
2838
*/
2939
private $helper;
3040

41+
/**
42+
* @var IntegrationServiceInterface
43+
*/
44+
private IntegrationServiceInterface $integrationService;
45+
46+
/**
47+
* @var BearerTokenValidator
48+
*/
49+
private BearerTokenValidator $bearerTokenValidator;
50+
3151
/**
3252
* @param TokenFactory $tokenFactory
3353
* @param OauthHelper $helper
54+
* @param IntegrationServiceInterface|null $integrationService
55+
* @param BearerTokenValidator|null $bearerTokenValidator
3456
*/
35-
public function __construct(TokenFactory $tokenFactory, OauthHelper $helper)
36-
{
57+
public function __construct(
58+
TokenFactory $tokenFactory,
59+
OauthHelper $helper,
60+
?IntegrationServiceInterface $integrationService = null,
61+
?BearerTokenValidator $bearerTokenValidator = null
62+
) {
3763
$this->tokenFactory = $tokenFactory;
3864
$this->helper = $helper;
65+
$this->integrationService = $integrationService ?? ObjectManager::getInstance()
66+
->get(IntegrationServiceInterface::class);
67+
$this->bearerTokenValidator = $bearerTokenValidator ?? ObjectManager::getInstance()
68+
->get(BearerTokenValidator::class);
3969
}
4070

4171
/**
4272
* @inheritDoc
4373
*/
4474
public function read(string $token): UserToken
4575
{
46-
/** @var Token $tokenModel */
76+
77+
$tokenModel = $this->getTokenModel($token);
78+
$userType = (int) $tokenModel->getUserType();
79+
$this->validateUserType($userType);
80+
$userId = $this->getUserId($tokenModel);
81+
82+
$issued = \DateTimeImmutable::createFromFormat(
83+
'Y-m-d H:i:s',
84+
$tokenModel->getCreatedAt(),
85+
new \DateTimeZone('UTC')
86+
);
87+
$lifetimeHours = $userType === CustomUserContext::USER_TYPE_ADMIN
88+
? $this->helper->getAdminTokenLifetime() : $this->helper->getCustomerTokenLifetime();
89+
$expires = $issued->add(new \DateInterval("PT{$lifetimeHours}H"));
90+
91+
return new UserToken(new CustomUserContext((int) $userId, (int) $userType), new Data($issued, $expires));
92+
}
93+
94+
/**
95+
* Create the token model from the input
96+
*
97+
* @param string $token
98+
* @return Token
99+
*/
100+
private function getTokenModel(string $token): Token
101+
{
47102
$tokenModel = $this->tokenFactory->create();
48103
$tokenModel = $tokenModel->load($token, 'token');
49104

@@ -53,27 +108,51 @@ public function read(string $token): UserToken
53108
if ($tokenModel->getRevoked()) {
54109
throw new UserTokenException('Token was revoked');
55110
}
56-
$userType = (int) $tokenModel->getUserType();
57-
if ($userType !== CustomUserContext::USER_TYPE_ADMIN && $userType !== CustomUserContext::USER_TYPE_CUSTOMER) {
111+
112+
return $tokenModel;
113+
}
114+
115+
/**
116+
* Validate the given user type
117+
*
118+
* @param int $userType
119+
* @throws UserTokenException
120+
*/
121+
private function validateUserType(int $userType): void
122+
{
123+
if ($userType !== CustomUserContext::USER_TYPE_ADMIN
124+
&& $userType !== CustomUserContext::USER_TYPE_CUSTOMER
125+
&& $userType !== CustomUserContext::USER_TYPE_INTEGRATION
126+
) {
58127
throw new UserTokenException('Invalid token found');
59128
}
129+
}
130+
131+
/**
132+
* Determine the user id for a given token
133+
*
134+
* @param Token $tokenModel
135+
* @return int
136+
*/
137+
private function getUserId(Token $tokenModel): int
138+
{
139+
$userType = (int)$tokenModel->getUserType();
140+
$userId = null;
141+
60142
if ($userType === CustomUserContext::USER_TYPE_ADMIN) {
61143
$userId = $tokenModel->getAdminId();
144+
} elseif ($userType === CustomUserContext::USER_TYPE_INTEGRATION) {
145+
$integration = $this->integrationService->findByConsumerId($tokenModel->getConsumerId());
146+
if ($this->bearerTokenValidator->isIntegrationAllowedAsBearerToken($integration)) {
147+
$userId = $integration->getId();
148+
}
62149
} else {
63150
$userId = $tokenModel->getCustomerId();
64151
}
65152
if (!$userId) {
66153
throw new UserTokenException('Invalid token found');
67154
}
68-
$issued = \DateTimeImmutable::createFromFormat(
69-
'Y-m-d H:i:s',
70-
$tokenModel->getCreatedAt(),
71-
new \DateTimeZone('UTC')
72-
);
73-
$lifetimeHours = $userType === CustomUserContext::USER_TYPE_ADMIN
74-
? $this->helper->getAdminTokenLifetime() : $this->helper->getCustomerTokenLifetime();
75-
$expires = $issued->add(new \DateInterval("PT{$lifetimeHours}H"));
76155

77-
return new UserToken(new CustomUserContext((int) $userId, (int) $userType), new Data($issued, $expires));
156+
return (int)$userId;
78157
}
79158
}

0 commit comments

Comments
 (0)