Skip to content

Commit

Permalink
Merge pull request #7340 from magento-cia/2.4-bugfixes-121621
Browse files Browse the repository at this point in the history
Bugfixes
  • Loading branch information
dvoskoboinikov authored Dec 22, 2021
2 parents 112c038 + 74b5b18 commit f69a8ee
Show file tree
Hide file tree
Showing 50 changed files with 1,588 additions and 305 deletions.
49 changes: 49 additions & 0 deletions app/code/Magento/Analytics/Plugin/BearerTokenValidatorPlugin.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/

declare(strict_types=1);

namespace Magento\Analytics\Plugin;

use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Integration\Model\Integration;
use Magento\Integration\Model\Validator\BearerTokenValidator;

/**
* Overrides authorization config to always allow analytics token to be used as bearer
*/
class BearerTokenValidatorPlugin
{
/**
* @var ScopeConfigInterface
*/
private ScopeConfigInterface $config;

/**
* @param ScopeConfigInterface $config
*/
public function __construct(ScopeConfigInterface $config)
{
$this->config = $config;
}

/***
* Always allow access token for analytics to be used as bearer
*
* @param BearerTokenValidator $subject
* @param bool $result
* @param Integration $integration
* @return bool
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function afterIsIntegrationAllowedAsBearerToken(
BearerTokenValidator $subject,
bool $result,
Integration $integration
): bool {
return $result || $integration->getName() === $this->config->getValue('analytics/integration_name');
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/

declare(strict_types=1);

namespace Magento\Analytics\Test\Unit\Plugin;

use Magento\Analytics\Plugin\BearerTokenValidatorPlugin;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Integration\Model\Integration;
use Magento\Integration\Model\Validator\BearerTokenValidator;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;

class BearerTokenValidatorPluginTest extends TestCase
{
/**
* @var BearerTokenValidatorPlugin
*/
private BearerTokenValidatorPlugin $plugin;

/**
* @var BearerTokenValidator|MockObject
*/
private $validator;

public function setUp(): void
{
$config = $this->createMock(ScopeConfigInterface::class);
$config->method('getValue')
->with('analytics/integration_name')
->willReturn('abc');
$this->plugin = new BearerTokenValidatorPlugin($config);
$this->validator = $this->createMock(BearerTokenValidator::class);
}

public function testTrueIsPassedThrough()
{
$integration = $this->createMock(Integration::class);
$integration->method('__call')
->with('getName')
->willReturn('invalid');

$result = $this->plugin->afterIsIntegrationAllowedAsBearerToken($this->validator, true, $integration);
self::assertTrue($result);
}

public function testFalseWhenIntegrationDoesntMatch()
{
$integration = $this->createMock(Integration::class);
$integration->method('__call')
->with('getName')
->willReturn('invalid');

$result = $this->plugin->afterIsIntegrationAllowedAsBearerToken($this->validator, false, $integration);
self::assertFalse($result);
}

public function testTrueWhenIntegrationMatches()
{
$integration = $this->createMock(Integration::class);
$integration->method('__call')
->with('getName')
->willReturn('abc');

$result = $this->plugin->afterIsIntegrationAllowedAsBearerToken($this->validator, true, $integration);
self::assertTrue($result);
}
}
3 changes: 3 additions & 0 deletions app/code/Magento/Analytics/etc/di.xml
Original file line number Diff line number Diff line change
Expand Up @@ -271,4 +271,7 @@
<argument name="connectionFactory" xsi:type="object">Magento\Framework\Model\ResourceModel\Type\Db\ConnectionFactory</argument>
</arguments>
</type>
<type name="Magento\Integration\Model\Validator\BearerTokenValidator">
<plugin name="allow_bearer_token" type="Magento\Analytics\Plugin\BearerTokenValidatorPlugin"/>
</type>
</config>
48 changes: 48 additions & 0 deletions app/code/Magento/Integration/Model/CompositeTokenReader.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/

declare(strict_types=1);

namespace Magento\Integration\Model;

use Magento\Integration\Api\Data\UserToken;
use Magento\Integration\Api\Exception\UserTokenException;
use Magento\Integration\Api\UserTokenReaderInterface;

/**
* Checks multiple sources for reading a token
*/
class CompositeTokenReader implements UserTokenReaderInterface
{
/**
* @var UserTokenReaderInterface[]
*/
private $readers;

/**
* @param UserTokenReaderInterface[] $readers
*/
public function __construct(array $readers)
{
$this->readers = $readers;
}

/**
* @inheritDoc
*/
public function read(string $token): UserToken
{
foreach ($this->readers as $reader) {
try {
return $reader->read($token);
} catch (UserTokenException $exception) {
continue;
}
}

throw new UserTokenException('Composite reader could not read a token');
}
}
49 changes: 49 additions & 0 deletions app/code/Magento/Integration/Model/Config/AuthorizationConfig.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/

declare(strict_types=1);

namespace Magento\Integration\Model\Config;

use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Store\Model\ScopeInterface;

/**
* Represents configuration related to WebAPI Authorization
*/
class AuthorizationConfig
{
/**
* XML Path for Enable Integration as Bearer
*/
private const CONFIG_PATH_INTEGRATION_BEARER = 'oauth/consumer/enable_integration_as_bearer';

/**
* @var ScopeConfigInterface
*/
private ScopeConfigInterface $scopeConfig;

/**
* @param ScopeConfigInterface $scopeConfig
*/
public function __construct(ScopeConfigInterface $scopeConfig)
{
$this->scopeConfig = $scopeConfig;
}

/**
* Return if integration access tokens can be used as bearer tokens
*
* @return bool
*/
public function isIntegrationAsBearerEnabled(): bool
{
return $this->scopeConfig->isSetFlag(
self::CONFIG_PATH_INTEGRATION_BEARER,
ScopeInterface::SCOPE_STORE
);
}
}
107 changes: 93 additions & 14 deletions app/code/Magento/Integration/Model/OpaqueToken/Reader.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,24 @@

namespace Magento\Integration\Model\OpaqueToken;

use Magento\Authorization\Model\UserContextInterface;
use Magento\Framework\App\ObjectManager;
use Magento\Integration\Api\Data\UserToken;
use Magento\Integration\Api\Exception\UserTokenException;
use Magento\Integration\Api\IntegrationServiceInterface;
use Magento\Integration\Api\UserTokenReaderInterface;
use Magento\Integration\Model\Config\AuthorizationConfig;
use Magento\Integration\Model\CustomUserContext;
use Magento\Integration\Model\Oauth\Token;
use Magento\Integration\Model\Oauth\TokenFactory;
use Magento\Integration\Helper\Oauth\Data as OauthHelper;
use Magento\Integration\Model\Validator\BearerTokenValidator;

/**
* Reads user token data
*
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class Reader implements UserTokenReaderInterface
{
/**
Expand All @@ -28,22 +38,67 @@ class Reader implements UserTokenReaderInterface
*/
private $helper;

/**
* @var IntegrationServiceInterface
*/
private IntegrationServiceInterface $integrationService;

/**
* @var BearerTokenValidator
*/
private BearerTokenValidator $bearerTokenValidator;

/**
* @param TokenFactory $tokenFactory
* @param OauthHelper $helper
* @param IntegrationServiceInterface|null $integrationService
* @param BearerTokenValidator|null $bearerTokenValidator
*/
public function __construct(TokenFactory $tokenFactory, OauthHelper $helper)
{
public function __construct(
TokenFactory $tokenFactory,
OauthHelper $helper,
?IntegrationServiceInterface $integrationService = null,
?BearerTokenValidator $bearerTokenValidator = null
) {
$this->tokenFactory = $tokenFactory;
$this->helper = $helper;
$this->integrationService = $integrationService ?? ObjectManager::getInstance()
->get(IntegrationServiceInterface::class);
$this->bearerTokenValidator = $bearerTokenValidator ?? ObjectManager::getInstance()
->get(BearerTokenValidator::class);
}

/**
* @inheritDoc
*/
public function read(string $token): UserToken
{
/** @var Token $tokenModel */

$tokenModel = $this->getTokenModel($token);
$userType = (int) $tokenModel->getUserType();
$this->validateUserType($userType);
$userId = $this->getUserId($tokenModel);

$issued = \DateTimeImmutable::createFromFormat(
'Y-m-d H:i:s',
$tokenModel->getCreatedAt(),
new \DateTimeZone('UTC')
);
$lifetimeHours = $userType === CustomUserContext::USER_TYPE_ADMIN
? $this->helper->getAdminTokenLifetime() : $this->helper->getCustomerTokenLifetime();
$expires = $issued->add(new \DateInterval("PT{$lifetimeHours}H"));

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

/**
* Create the token model from the input
*
* @param string $token
* @return Token
*/
private function getTokenModel(string $token): Token
{
$tokenModel = $this->tokenFactory->create();
$tokenModel = $tokenModel->load($token, 'token');

Expand All @@ -53,27 +108,51 @@ public function read(string $token): UserToken
if ($tokenModel->getRevoked()) {
throw new UserTokenException('Token was revoked');
}
$userType = (int) $tokenModel->getUserType();
if ($userType !== CustomUserContext::USER_TYPE_ADMIN && $userType !== CustomUserContext::USER_TYPE_CUSTOMER) {

return $tokenModel;
}

/**
* Validate the given user type
*
* @param int $userType
* @throws UserTokenException
*/
private function validateUserType(int $userType): void
{
if ($userType !== CustomUserContext::USER_TYPE_ADMIN
&& $userType !== CustomUserContext::USER_TYPE_CUSTOMER
&& $userType !== CustomUserContext::USER_TYPE_INTEGRATION
) {
throw new UserTokenException('Invalid token found');
}
}

/**
* Determine the user id for a given token
*
* @param Token $tokenModel
* @return int
*/
private function getUserId(Token $tokenModel): int
{
$userType = (int)$tokenModel->getUserType();
$userId = null;

if ($userType === CustomUserContext::USER_TYPE_ADMIN) {
$userId = $tokenModel->getAdminId();
} elseif ($userType === CustomUserContext::USER_TYPE_INTEGRATION) {
$integration = $this->integrationService->findByConsumerId($tokenModel->getConsumerId());
if ($this->bearerTokenValidator->isIntegrationAllowedAsBearerToken($integration)) {
$userId = $integration->getId();
}
} else {
$userId = $tokenModel->getCustomerId();
}
if (!$userId) {
throw new UserTokenException('Invalid token found');
}
$issued = \DateTimeImmutable::createFromFormat(
'Y-m-d H:i:s',
$tokenModel->getCreatedAt(),
new \DateTimeZone('UTC')
);
$lifetimeHours = $userType === CustomUserContext::USER_TYPE_ADMIN
? $this->helper->getAdminTokenLifetime() : $this->helper->getCustomerTokenLifetime();
$expires = $issued->add(new \DateInterval("PT{$lifetimeHours}H"));

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

0 comments on commit f69a8ee

Please sign in to comment.