diff --git a/AdobeIms/Model/FlushUserTokens.php b/AdobeIms/Model/FlushUserTokens.php new file mode 100644 index 000000000000..21f74cdb61a8 --- /dev/null +++ b/AdobeIms/Model/FlushUserTokens.php @@ -0,0 +1,60 @@ +userContext = $userContext; + $this->userProfileRepository = $userProfileRepository; + } + + /** + * @inheritdoc + */ + public function execute(int $adminUserId = null): void + { + try { + if ($adminUserId === null) { + $adminUserId = (int) $this->userContext->getUserId(); + } + + $userProfile = $this->userProfileRepository->getByUserId($adminUserId); + $userProfile->setAccessToken(''); + $userProfile->setRefreshToken(''); + $this->userProfileRepository->save($userProfile); + } catch (\Exception $e) { + // User profile and tokens are not present in the system + } + } +} diff --git a/AdobeIms/Model/GetAccessToken.php b/AdobeIms/Model/GetAccessToken.php new file mode 100644 index 000000000000..271535dc9603 --- /dev/null +++ b/AdobeIms/Model/GetAccessToken.php @@ -0,0 +1,57 @@ +userContext = $userContext; + $this->userProfileRepository = $userProfileRepository; + } + + /** + * @inheritdoc + */ + public function execute(int $adminUserId = null): ?string + { + try { + if ($adminUserId === null) { + $adminUserId = (int) $this->userContext->getUserId(); + } + return $this->userProfileRepository->getByUserId($adminUserId)->getAccessToken(); + } catch (NoSuchEntityException $exception) { + return null; + } + } +} diff --git a/AdobeIms/Model/LogOut.php b/AdobeIms/Model/LogOut.php index 486ce5c83163..35411cb32c56 100644 --- a/AdobeIms/Model/LogOut.php +++ b/AdobeIms/Model/LogOut.php @@ -8,6 +8,7 @@ use Magento\AdobeImsApi\Api\LogOutInterface; use Magento\AdobeImsApi\Api\Data\ConfigInterface; use Magento\Authorization\Model\UserContextInterface; +use Magento\Framework\Exception\NoSuchEntityException; use Magento\Framework\HTTP\Client\CurlFactory; use Psr\Log\LoggerInterface; @@ -75,12 +76,22 @@ public function __construct( public function execute() : bool { try { - $userProfile = $this->userProfileRepository->getByUserId((int)$this->userContext->getUserId()); + try { + $userProfile = $this->userProfileRepository->getByUserId((int)$this->userContext->getUserId()); + } catch (NoSuchEntityException $exception) { + return true; + } + + $accessToken = $userProfile->getAccessToken(); + + if (empty($accessToken)) { + return true; + } $curl = $this->curlFactory->create(); $curl->addHeader('Content-Type', 'application/x-www-form-urlencoded'); $curl->addHeader('cache-control', 'no-cache'); - $curl->get($this->config->getLogoutUrl($userProfile->getAccessToken())); + $curl->get($this->config->getLogoutUrl($accessToken)); if ($curl->getStatus() === self::HTTP_FOUND) { $userProfile->setAccessToken(''); diff --git a/AdobeIms/etc/di.xml b/AdobeIms/etc/di.xml index da69fa18f1b7..5639601d57c5 100644 --- a/AdobeIms/etc/di.xml +++ b/AdobeIms/etc/di.xml @@ -14,4 +14,6 @@ + + diff --git a/AdobeImsApi/Api/FlushUserTokensInterface.php b/AdobeImsApi/Api/FlushUserTokensInterface.php new file mode 100644 index 000000000000..778f44e33d7d --- /dev/null +++ b/AdobeImsApi/Api/FlushUserTokensInterface.php @@ -0,0 +1,24 @@ +clientConfig = $clientConfig; - $this->imsConfig = $imsConfig; + $this->config = $config; + $this->connection = $connection; $this->searchResultFactory = $searchResultFactory; $this->searchParametersProvider = $searchParametersProvider; $this->localeResolver = $localeResolver; - $this->connectionFactory = $connectionFactory; $this->licenseRequestFactory = $licenseRequestFactory; $this->logger = $logger; - $this->userProfileRepository = $userProfileRepository; - $this->userContext = $userContext; $this->userQuotaFactory = $userQuotaFactory; $this->stockFileToDocument = $stockFileToDocument; $this->licenseConfirmationFactory = $licenseConfirmationFactory; @@ -164,20 +137,14 @@ public function search(SearchCriteriaInterface $searchCriteria): SearchResultInt $connection = $this->getConnection(); try { - $connection->searchFilesInitialize( - $this->getSearchRequest($searchCriteria), - $this->getAccessToken() - ); + $connection->searchFilesInitialize($this->getSearchRequest($searchCriteria)); $response = $connection->getNextResponse(); /** @var StockFile $file */ foreach ($response->getFiles() as $file) { $items[] = $this->stockFileToDocument->convert($file); } $totalCount = $response->getNbResults(); - } catch (Exception $exception) { - if (strpos($exception->getMessage(), 'Api Key is invalid') !== false) { - throw new AuthenticationException(__($exception->getMessage()), $exception, $exception->getCode()); - } + } catch (IntegrationException $exception) { $this->logger->critical($exception->getMessage()); } @@ -201,7 +168,7 @@ private function getLicenseRequest(int $contentId): LicenseRequest /** @var LicenseRequest $licenseRequest */ $licenseRequest = $this->licenseRequestFactory->create(); $licenseRequest->setContentId($contentId) - ->setLocale($this->clientConfig->getLocale()) + ->setLocale($this->config->getLocale()) ->setLicenseState('STANDARD'); return $licenseRequest; @@ -217,7 +184,7 @@ private function getLicenseRequest(int $contentId): LicenseRequest */ private function getLicenseInfo(int $contentId): License { - return $this->getConnection()->getMemberProfile($this->getLicenseRequest($contentId), $this->getAccessToken()); + return $this->getConnection()->getMemberProfile($this->getLicenseRequest($contentId)); } /** @@ -250,31 +217,19 @@ public function getLicenseConfirmation(int $contentId): LicenseConfirmationInter } /** - * Performs image license request to Adobe Stock APi - * - * @param int $contentId - * @throws IntegrationException - * @throws StockApi + * @inheritdoc */ public function licenseImage(int $contentId): void { - $licenseRequest = $this->getLicenseRequest($contentId); - $this->getConnection()->getContentLicense($licenseRequest, $this->getAccessToken()); + $this->getConnection()->getContentLicense($this->getLicenseRequest($contentId)); } /** - * Returns download URL for a licensed image - * - * @param int $contentId - * @return string - * @throws IntegrationException - * @throws \AdobeStock\Api\Exception\StockApi + * @inheritdoc */ public function getImageDownloadUrl(int $contentId): string { - $licenseRequest = $this->getLicenseRequest($contentId); - - return $this->getConnection()->downloadAssetUrl($licenseRequest, $this->getAccessToken()); + return $this->getConnection()->downloadAssetUrl($this->getLicenseRequest($contentId)); } /** @@ -305,7 +260,7 @@ private function getResultColumns(): array { $resultsColumns = Constants::getResultColumns(); $resultColumnArray = []; - foreach ($this->clientConfig->getSearchResultFields() as $field) { + foreach ($this->config->getSearchResultFields() as $field) { if (!isset($resultsColumns[$field])) { $message = __('Cannot retrieve the field %1. It\'s not available in Adobe Stock SDK', $field); $this->logger->critical($message); @@ -318,90 +273,17 @@ private function getResultColumns(): array /** * Initialize connection to the Adobe Stock service. - * - * @param string $key - * - * @return AdobeStock - * @throws IntegrationException - */ - private function getConnection(string $key = null): AdobeStock - { - try { - $apiKey = !empty($key) ? $key : (string) $this->imsConfig->getApiKey(); - return $this->connectionFactory->create( - $apiKey, - (string) $this->clientConfig->getProductName(), - (string) $this->clientConfig->getTargetEnvironment() - ); - } catch (Exception $exception) { - $message = __( - 'An error occurred during Adobe Stock connection initialization: %error_message', - ['error_message' => $exception->getMessage()] - ); - $this->processException($message, $exception); - } - } - - /** - * Retrieve an access token for current user - * - * @return string|null - */ - private function getAccessToken() - { - try { - return $this->userProfileRepository->getByUserId( - (int)$this->userContext->getUserId() - )->getAccessToken(); - } catch (NoSuchEntityException $exception) { - return null; - } - } - - /** - * Test connection to Adobe Stock API - * - * @param string $apiKey - * - * @return bool */ - public function testConnection(string $apiKey = null): bool + private function getConnection(): Connection { - try { - $searchParams = new SearchParameters(); - $searchRequest = new SearchFilesRequest(); - $resultColumnArray = []; - - $resultColumnArray[] = 'nb_results'; - - $searchRequest->setLocale('en_GB'); - $searchRequest->setSearchParams($searchParams); - $searchRequest->setResultColumns($resultColumnArray); - - $client = $this->getConnection($apiKey); - $client->searchFilesInitialize($searchRequest, $this->getAccessToken()); - - return (bool)$client->getNextResponse()->nb_results; - } catch (Exception $exception) { - $message = __( - 'An error occurred during Adobe Stock API connection test: %error_message', - ['error_message' => $exception->getMessage()] - ); - $this->logger->notice($message->render()); - return false; - } + return $this->connection; } /** - * Handle SDK Exception and throw Magento exception instead - * - * @param Phrase $message - * @param Exception $exception - * @throws IntegrationException + * @inheritdoc */ - private function processException(Phrase $message, Exception $exception) + public function testConnection(string $apiKey): bool { - $this->logger->critical($message->render()); - throw new IntegrationException($message, $exception, $exception->getCode()); + return $this->getConnection()->testApiKey($apiKey); } } diff --git a/AdobeStockClient/Model/Connection.php b/AdobeStockClient/Model/Connection.php new file mode 100644 index 000000000000..5f132b76e25f --- /dev/null +++ b/AdobeStockClient/Model/Connection.php @@ -0,0 +1,270 @@ +clientConfig = $clientConfig; + $this->connectionFactory = $connectionFactory; + $this->imsConfig = $imsConfig; + $this->log = $logger; + $this->getAccessToken = $getAccessToken; + $this->flushUserTokens = $flushUserTokens; + $this->httpClient = $httpClient; + } + + /** + * Create new SDK connection instance + * + * @param string|null $apiKey + * @return AdobeStock + */ + private function getConnection(string $apiKey = null): AdobeStock + { + if (!$this->connection) { + $this->connection = $this->connectionFactory->create( + $apiKey ?? $this->imsConfig->getApiKey(), + $this->clientConfig->getProductName(), + $this->clientConfig->getTargetEnvironment(), + $this->httpClient + ); + } + return $this->connection; + } + + /** + * Checks if Access token valid and returns result. + * + * @return string|null + */ + private function getAccessToken(): string + { + return $this->getAccessToken->execute(); + } + + /** + * Handle Adobe Stock SDK exception + * + * @param \Exception $exception + * @param string $message + * @throws AuthenticationException + * @throws AuthorizationException + * @throws IntegrationException + */ + private function handleException(\Exception $exception, string $message): void + { + if (strpos($exception->getMessage(), 'Api Key is invalid') !== false) { + throw new AuthenticationException(__('Adobe API Key is invalid!')); + } + if (strpos($exception->getMessage(), 'Oauth token is not valid') !== false) { + $this->flushUserTokens->execute(); + throw new AuthorizationException(__('Adobe API login has expired!')); + } + $phrase = __( + $message . ': %error_message', + ['error_message' => $exception->getMessage()] + ); + throw new IntegrationException($phrase, $exception, $exception->getCode()); + } + + /** + * Test if the connection to Adobe Stock API can be established with the given API key + * + * @param string $apiKey + * @return bool + */ + public function testApiKey(string $apiKey): bool + { + try { + $searchParams = new SearchParameters(); + $searchRequest = new SearchFilesRequest(); + $resultColumnArray = []; + $resultColumnArray[] = 'nb_results'; + + $searchRequest->setLocale('en_GB'); + $searchRequest->setSearchParams($searchParams); + $searchRequest->setResultColumns($resultColumnArray); + + $client = $this->getConnection($apiKey); + $client->searchFilesInitialize($searchRequest); + + return (bool)$client->getNextResponse()->nb_results; + } catch (Exception $exception) { + return false; + } + } + + /** + * Method to initialize search files. + * + * @param SearchFilesRequest $request + * @return $this + * @throws AuthenticationException + * @throws AuthorizationException + * @throws IntegrationException + */ + public function searchFilesInitialize(SearchFilesRequest $request): self + { + try { + $this->getConnection()->searchFilesInitialize($request, $this->getAccessToken()); + return $this; + } catch (\Exception $exception) { + $this->handleException($exception, 'Failed to initialize Adobe Stock search files request'); + } + } + + /** + * Get the next search files response page. + * + * @return SearchFilesResponse + * @throws AuthenticationException + * @throws AuthorizationException + * @throws IntegrationException + */ + public function getNextResponse(): SearchFilesResponse + { + try { + return $this->getConnection()->getNextResponse(); + } catch (\Exception $exception) { + $this->handleException($exception, 'Failed to retrieve Adobe Stock search files results'); + } + } + + /** + * Get the licensing capabilities for a user. + * + * @param LicenseRequest $request + * @return LicenseResponse + * @throws AuthenticationException + * @throws AuthorizationException + * @throws IntegrationException + */ + public function getMemberProfile(LicenseRequest $request): LicenseResponse + { + try { + return $this->getConnection()->getMemberProfile($request, $this->getAccessToken()); + } catch (\Exception $exception) { + $this->handleException($exception, 'Failed to retrieve Adobe Stock member profile'); + } + } + + /** + * Requests a license for an asset for a specific user. + * + * @param LicenseRequest $request + * @return LicenseResponse + * @throws AuthenticationException + * @throws AuthorizationException + * @throws IntegrationException + */ + public function getContentLicense(LicenseRequest $request): LicenseResponse + { + try { + return $this->getConnection()->getContentLicense($request, $this->getAccessToken()); + } catch (\Exception $exception) { + $this->handleException($exception, 'Failed to retrieve Adobe Stock content license'); + } + } + + /** + * Provide the url of the asset if it is already licensed. + * + * @param LicenseRequest $request + * @return string + * @throws AuthenticationException + * @throws AuthorizationException + * @throws IntegrationException + */ + public function downloadAssetUrl(LicenseRequest $request): string + { + try { + return $this->getConnection()->downloadAssetUrl($request, $this->getAccessToken()); + } catch (\Exception $exception) { + $this->handleException($exception, 'Failed to retrieve Adobe Stock asset download URL'); + } + } +} diff --git a/AdobeStockClient/Model/ConnectionFactory.php b/AdobeStockClient/Model/ConnectionFactory.php index c91fc2de27e9..9dbcf47a7315 100644 --- a/AdobeStockClient/Model/ConnectionFactory.php +++ b/AdobeStockClient/Model/ConnectionFactory.php @@ -9,6 +9,7 @@ namespace Magento\AdobeStockClient\Model; use AdobeStock\Api\Client\AdobeStock; +use AdobeStock\Api\Client\Http\HttpInterface; /** * Class ConnectionFactory @@ -21,10 +22,15 @@ class ConnectionFactory * @param string $apiKey * @param string $productName * @param string $targetEnvironment + * @param HttpInterface|null $httpClient * @return AdobeStock */ - public function create(string $apiKey, string $productName, string $targetEnvironment): AdobeStock - { - return new AdobeStock($apiKey, $productName, $targetEnvironment); + public function create( + string $apiKey, + string $productName, + string $targetEnvironment, + HttpInterface $httpClient = null + ): AdobeStock { + return new AdobeStock($apiKey, $productName, $targetEnvironment, $httpClient); } } diff --git a/AdobeStockClient/Test/Integration/Model/ClientTest.php b/AdobeStockClient/Test/Integration/Model/ClientTest.php index cfa2aa4acbdc..53412639d8e8 100644 --- a/AdobeStockClient/Test/Integration/Model/ClientTest.php +++ b/AdobeStockClient/Test/Integration/Model/ClientTest.php @@ -8,12 +8,11 @@ namespace Magento\AdobeStockClient\Test\Integration\Model; -use AdobeStock\Api\Client\AdobeStock; use AdobeStock\Api\Models\StockFile; use AdobeStock\Api\Response\SearchFiles as SearchFilesResponse; use AdobeStock\Api\Request\SearchFiles as SearchFilesRequest; use Magento\AdobeStockClient\Model\Client; -use Magento\AdobeStockClient\Model\ConnectionFactory; +use Magento\AdobeStockClient\Model\Connection; use Magento\Framework\Api\FilterBuilder; use Magento\Framework\Api\Search\SearchCriteriaBuilder; use Magento\Framework\Api\Search\SearchResultInterface; @@ -34,7 +33,7 @@ class ClientTest extends TestCase private $client; /** - * @var AdobeStock|MockObject + * @var Connection|MockObject */ private $connection; @@ -43,21 +42,14 @@ class ClientTest extends TestCase */ protected function setUp(): void { - $this->connection = $this->getMockBuilder(AdobeStock::class) + $this->connection = $this->getMockBuilder(Connection::class) ->setMethods(['searchFilesInitialize', 'getNextResponse']) ->disableOriginalConstructor() ->getMock(); - $connectionFactory = $this->getMockBuilder(ConnectionFactory::class) - ->setMethods(['create']) - ->disableOriginalConstructor() - ->getMock(); - $connectionFactory->expects($this->once()) - ->method('create') - ->willReturn($this->connection); $this->client = Bootstrap::getObjectManager()->create( Client::class, [ - 'connectionFactory' => $connectionFactory + 'connection' => $this->connection ] ); } @@ -81,7 +73,6 @@ public function testSearch(): void $response->expects($this->once()) ->method('getNbResults') ->willReturn(3); - $this->connection->expects($this->once()) ->method('searchFilesInitialize') ->with( @@ -92,8 +83,7 @@ function (SearchFilesRequest $searchFiles) use ($words) { && in_array('nb_results', $searchFiles->getResultColumns()) && $searchFiles->getSearchParams()->getWords() == $words; } - ), - null + ) ); $this->connection->expects($this->once()) ->method('getNextResponse') diff --git a/AdobeStockClientApi/Api/ClientInterface.php b/AdobeStockClientApi/Api/ClientInterface.php index 9dba2a39c401..05d0f0a2bfdb 100644 --- a/AdobeStockClientApi/Api/ClientInterface.php +++ b/AdobeStockClientApi/Api/ClientInterface.php @@ -44,11 +44,10 @@ public function getLicenseConfirmation(int $contentId): LicenseConfirmationInter /** * Perform a basic request to Adobe Stock API to check network connection, API key, etc. * - * @param string|null $apiKey - * + * @param string $apiKey * @return bool */ - public function testConnection(string $apiKey = null): bool; + public function testConnection(string $apiKey): bool; /** * Invokes licensing image operation via Adobe Stock API @@ -62,7 +61,7 @@ public function licenseImage(int $contentId): void; * Returns download URL for a licensed image * * @param int $contentId - * @return mixed + * @return string */ public function getImageDownloadUrl(int $contentId): string; } diff --git a/AdobeStockImageAdminUi/Model/SignInConfigProvider.php b/AdobeStockImageAdminUi/Model/SignInConfigProvider.php index 9c0ee4e11c05..9637925e5cf1 100644 --- a/AdobeStockImageAdminUi/Model/SignInConfigProvider.php +++ b/AdobeStockImageAdminUi/Model/SignInConfigProvider.php @@ -10,6 +10,8 @@ use Magento\AdobeImsApi\Api\ConfigProviderInterface; use Magento\AdobeImsApi\Api\UserAuthorizedInterface; use Magento\AdobeStockClientApi\Api\ClientInterface; +use Magento\Framework\Exception\AuthenticationException; +use Magento\Framework\Exception\AuthorizationException; use Magento\Framework\UrlInterface; /** @@ -68,16 +70,23 @@ public function get(): array */ private function getUserQuota(): array { + $defaultQuota = [ + 'images' => 0, + 'credits' => 0 + ]; if (!$this->userAuthorized->execute()) { + return $defaultQuota; + } + try { + $quota = $this->client->getQuota(); return [ - 'images' => 0, - 'credits' => 0 + 'images' => $quota->getImages(), + 'credits' => $quota->getCredits() ]; + } catch (AuthenticationException $exception) { + return $defaultQuota; + } catch (AuthorizationException $exception) { + return $defaultQuota; } - $quota = $this->client->getQuota(); - return [ - 'images' => $quota->getImages(), - 'credits' => $quota->getCredits() - ]; } }