From e9500635dafbb4c19ee96361a862434fc842ff40 Mon Sep 17 00:00:00 2001 From: Constantine Nathanson <35217733+const-cloudinary@users.noreply.github.com> Date: Sun, 19 Nov 2023 20:42:11 +0200 Subject: [PATCH] Add support for access keys management in Account Provisioning API --- src/Api/Provisioning/AccountApi.php | 61 ++++++++++++- src/Api/Provisioning/AccountApiClient.php | 38 +++++++-- src/Api/Provisioning/AccountEndPoint.php | 1 + tests/Helpers/MockAccountApi.php | 43 ++++++++++ tests/Helpers/MockAccountApiClient.php | 21 +++++ tests/Helpers/RequestAssertionsTrait.php | 21 +++++ tests/Unit/Provisioning/AccessKeysTest.php | 85 +++++++++++++++++++ .../Provisioning/ProvisioningUnitTestCase.php | 13 ++- 8 files changed, 272 insertions(+), 11 deletions(-) create mode 100644 tests/Helpers/MockAccountApi.php create mode 100644 tests/Helpers/MockAccountApiClient.php create mode 100644 tests/Unit/Provisioning/AccessKeysTest.php diff --git a/src/Api/Provisioning/AccountApi.php b/src/Api/Provisioning/AccountApi.php index d70923d5..513ef1bd 100644 --- a/src/Api/Provisioning/AccountApi.php +++ b/src/Api/Provisioning/AccountApi.php @@ -13,6 +13,7 @@ use Cloudinary\Api\ApiResponse; use Cloudinary\Api\ApiUtils; use Cloudinary\Api\Exception\ApiError; +use Cloudinary\ArrayUtils; use Cloudinary\Configuration\Provisioning\ProvisioningConfiguration; /** @@ -27,7 +28,7 @@ class AccountApi /** * @var AccountApiClient $accountApiClient */ - private $accountApiClient; + protected $accountApiClient; /** * AccountApi constructor. @@ -410,4 +411,62 @@ public function userGroupUsers($groupId) return $this->accountApiClient->get($uri); } + + /** + * Gets sub account access keys. + * + * @param string $subAccountId The id of the sub account. + * @param array $options Additional options. + * + * @return ApiResponse A list of access keys. + * + * @api + */ + public function accessKeys($subAccountId, $options = []) + { + $uri = [AccountEndPoint::SUB_ACCOUNTS, $subAccountId, AccountEndPoint::ACCESS_KEYS]; + + $params = ArrayUtils::whitelist($options, ['page_size', 'page', 'sort_by', 'sort_order']); + + return $this->accountApiClient->get($uri, $params); + } + + /** + * Generates a new access key. + * + * @param string $subAccountId The id of the sub account. + * @param array $options Additional options. + * + * @return ApiResponse Generated access key. + * + * @api + */ + public function generateAccessKey($subAccountId, $options = []) + { + $uri = [AccountEndPoint::SUB_ACCOUNTS, $subAccountId, AccountEndPoint::ACCESS_KEYS]; + + $params = ArrayUtils::whitelist($options, ['name', 'enabled']); + + return $this->accountApiClient->postJson($uri, $params); + } + + /** + * Updates the access key. + * + * @param string $subAccountId The id of the sub account. + * @param string $apiKey The Api Key. + * @param array $options Additional options. + * + * @return ApiResponse Updated access key. + * + * @api + */ + public function updateAccessKey($subAccountId, $apiKey, $options = []) + { + $uri = [AccountEndPoint::SUB_ACCOUNTS, $subAccountId, AccountEndPoint::ACCESS_KEYS, $apiKey]; + + $params = ArrayUtils::whitelist($options, ['name', 'enabled']); + + return $this->accountApiClient->putJson($uri, $params); + } } diff --git a/src/Api/Provisioning/AccountApiClient.php b/src/Api/Provisioning/AccountApiClient.php index a6ac778e..b8d157f0 100644 --- a/src/Api/Provisioning/AccountApiClient.php +++ b/src/Api/Provisioning/AccountApiClient.php @@ -11,6 +11,8 @@ namespace Cloudinary\Api\Provisioning; use Cloudinary\Api\BaseApiClient; +use Cloudinary\Configuration\ApiConfig; +use Cloudinary\Configuration\Provisioning\ProvisioningAccountConfig; use Cloudinary\Configuration\Provisioning\ProvisioningConfiguration; use Cloudinary\Exception\ConfigurationException; use GuzzleHttp\Client; @@ -27,6 +29,11 @@ class AccountApiClient extends BaseApiClient const PROVISIONING = 'provisioning'; const ACCOUNTS = 'accounts'; + /** + * @var ProvisioningAccountConfig $provisioningAccount The Account API configuration. + */ + protected $provisioningAccount; + /** * AccountApiClient constructor * @@ -48,14 +55,16 @@ public function init(ProvisioningConfiguration $configuration = null) if (empty($configuration->provisioningAccount->accountId) || empty($configuration->provisioningAccount->provisioningApiKey) - || empty($configuration->provisioningAccount->provisioningApiSecret)) { + || empty($configuration->provisioningAccount->provisioningApiSecret) + ) { throw new ConfigurationException( 'When providing account id, key or secret, all must be provided' ); } - $this->api = $configuration->api; - $this->logging = $configuration->logging; + $this->api = $configuration->api; + $this->provisioningAccount = $configuration->provisioningAccount; + $this->logging = $configuration->logging; $this->baseUri = sprintf( '%s/%s/%s/%s/%s/', @@ -66,10 +75,23 @@ public function init(ProvisioningConfiguration $configuration = null) $configuration->provisioningAccount->accountId ); - $clientConfig = [ + $this->createHttpClient(); + } + + protected function createHttpClient() + { + $this->httpClient = new Client($this->buildHttpClientConfig()); + } + + /** + * @return array + */ + protected function buildHttpClientConfig() + { + return [ 'auth' => [ - $configuration->provisioningAccount->provisioningApiKey, - $configuration->provisioningAccount->provisioningApiSecret, + $this->provisioningAccount->provisioningApiKey, + $this->provisioningAccount->provisioningApiSecret, ], 'base_uri' => $this->baseUri, 'connect_timeout' => $this->api->connectionTimeout, @@ -78,7 +100,7 @@ public function init(ProvisioningConfiguration $configuration = null) 'headers' => ['User-Agent' => self::userAgent()], 'http_errors' => false, // We handle HTTP errors by ourselves and throw corresponding exceptions ]; - - $this->httpClient = new Client($clientConfig); } + + } diff --git a/src/Api/Provisioning/AccountEndPoint.php b/src/Api/Provisioning/AccountEndPoint.php index ebedf665..93014683 100644 --- a/src/Api/Provisioning/AccountEndPoint.php +++ b/src/Api/Provisioning/AccountEndPoint.php @@ -20,4 +20,5 @@ class AccountEndPoint const USERS = 'users'; const USER_GROUPS = 'user_groups'; const SUB_ACCOUNTS = 'sub_accounts'; + const ACCESS_KEYS = 'access_keys'; } diff --git a/tests/Helpers/MockAccountApi.php b/tests/Helpers/MockAccountApi.php new file mode 100644 index 00000000..afbb13b9 --- /dev/null +++ b/tests/Helpers/MockAccountApi.php @@ -0,0 +1,43 @@ +accountApiClient = new MockAccountApiClient($configuration); + } + + /** + * Returns a mock api client. + * + * @return MockAccountApiClient + */ + public function getApiClient() + { + return $this->accountApiClient; + } +} diff --git a/tests/Helpers/MockAccountApiClient.php b/tests/Helpers/MockAccountApiClient.php new file mode 100644 index 00000000..52d4b6ed --- /dev/null +++ b/tests/Helpers/MockAccountApiClient.php @@ -0,0 +1,21 @@ +provisioningAccount->accountId + . $path, + $request->getUri()->getPath(), + $message + ); + } + /** * Asserts that a request's query string contains the expected fields and values. * diff --git a/tests/Unit/Provisioning/AccessKeysTest.php b/tests/Unit/Provisioning/AccessKeysTest.php new file mode 100644 index 00000000..83e4b222 --- /dev/null +++ b/tests/Unit/Provisioning/AccessKeysTest.php @@ -0,0 +1,85 @@ +accessKeys( + self::SUB_ACCOUNT_ID, + ['page_size' => 2, 'page' => 1, 'sort_by' => 'name', 'sort_order' => 'asc'] + ); + + $lastRequest = $mockAccApi->getMockHandler()->getLastRequest(); + + self::assertAccountRequestUrl($lastRequest, '/sub_accounts/' . self::SUB_ACCOUNT_ID . '/access_keys'); + + self::assertRequestGet($lastRequest); + + self::assertRequestQueryStringSubset($lastRequest, ['page_size' => '2']); + self::assertRequestQueryStringSubset($lastRequest, ['page' => '1']); + self::assertRequestQueryStringSubset($lastRequest, ['sort_by' => 'name']); + self::assertRequestQueryStringSubset($lastRequest, ['sort_order' => 'asc']); + } + + /** + * Should allow generating access keys. + */ + public function testGenerateAccessKey() + { + $mockAccApi = new MockAccountApi(); + + $mockAccApi->generateAccessKey( + self::SUB_ACCOUNT_ID, + ['enabled' => true, 'name' => 'test_key'] + ); + + $lastRequest = $mockAccApi->getMockHandler()->getLastRequest(); + + self::assertAccountRequestUrl($lastRequest, '/sub_accounts/' . self::SUB_ACCOUNT_ID . '/access_keys'); + self::assertRequestPost($lastRequest); + + self::assertRequestJsonBodySubset($lastRequest, ['enabled' => true, 'name' => 'test_key']); + } + + /** + * Should allow updating access keys. + */ + public function testUpdateAccessKey() + { + $mockAccApi = new MockAccountApi(); + + $mockAccApi->updateAccessKey( + self::SUB_ACCOUNT_ID, + self::API_KEY, + ['enabled' => false, 'name' => 'updated_key'] + ); + + $lastRequest = $mockAccApi->getMockHandler()->getLastRequest(); + + self::assertAccountRequestUrl( + $lastRequest, + '/sub_accounts/' . self::SUB_ACCOUNT_ID . '/access_keys/' . self::API_KEY + ); + self::assertRequestPut($lastRequest); + + self::assertRequestJsonBodySubset($lastRequest, ['enabled' => false, 'name' => 'updated_key']); + } +} diff --git a/tests/Unit/Provisioning/ProvisioningUnitTestCase.php b/tests/Unit/Provisioning/ProvisioningUnitTestCase.php index f926db5e..f8db22e8 100644 --- a/tests/Unit/Provisioning/ProvisioningUnitTestCase.php +++ b/tests/Unit/Provisioning/ProvisioningUnitTestCase.php @@ -11,6 +11,7 @@ namespace Cloudinary\Test\Unit\Provisioning; use Cloudinary\Configuration\Provisioning\ProvisioningConfiguration; +use Cloudinary\Exception\ConfigurationException; use Cloudinary\Test\CloudinaryTestCase; /** @@ -18,7 +19,6 @@ */ abstract class ProvisioningUnitTestCase extends CloudinaryTestCase { - const ACCOUNT_ID = 'account123'; const ACCOUNT_API_KEY = 'accountKey'; const ACCOUNT_API_SECRET = 'accountSecret'; @@ -37,12 +37,21 @@ public function setUp() . $this::ACCOUNT_ID; putenv(ProvisioningConfiguration::CLOUDINARY_ACCOUNT_URL_ENV_VAR . '=' . $this->accountUrl); + + ProvisioningConfiguration::instance()->init(); } public function tearDown() { parent::tearDown(); - putenv(ProvisioningConfiguration::CLOUDINARY_ACCOUNT_URL_ENV_VAR . '=' . $this->accountUrlEnvBackup); + putenv( + ProvisioningConfiguration::CLOUDINARY_ACCOUNT_URL_ENV_VAR . + (! empty($this->accountUrlEnvBackup) ? '=' . $this->accountUrlEnvBackup : "") + ); + try { + ProvisioningConfiguration::instance()->init(); + } catch (ConfigurationException $ce) { + } } }