diff --git a/lib/provisioning/account.js b/lib/provisioning/account.js index c887d2d9..f2c8566c 100644 --- a/lib/provisioning/account.js +++ b/lib/provisioning/account.js @@ -291,6 +291,77 @@ function user_group_users(group_id, options = {}, callback) { return call_account_api('GET', uri, {}, callback, options); } +/** + * @desc Lists access keys in the given subaccount. + * @param sub_account_id {string} - The ID of the subaccount. + * @param [options] {object} - See {@link https://cloudinary.com/documentation/provisioning_api#tag/access-keys/GET/sub_accounts/{{sub_account_id}}/access_keys|get access keys optional parameters} in the SDK documentation. + * @param [callback] {function} + */ +function access_keys(sub_account_id, options = {}, callback) { + const params = pickOnlyExistingValues({ + page_size: options.page_size, + page: options.page, + sort_by: options.sort_by, + sort_order: options.sort_order + }, 'page_size', 'page', 'sort_by', 'sort_order'); + const uri = ['sub_accounts', sub_account_id, 'access_keys']; + return call_account_api('GET', uri, params, callback, options); +} + +/** + * @desc Generate a new access key pair in the given subaccount. + * @param sub_account_id {string} - The ID of the subaccount. + * @param [options] {object} - See {@link https://cloudinary.com/documentation/provisioning_api#tag/access-keys/POST/sub_accounts/{{sub_account_id}}/access_keys|generate access key optional parameters} in the SDK documentation. + * @param [callback] {function} + */ +function generate_access_key(sub_account_id, options = {}, callback) { + const params = pickOnlyExistingValues({ + name: options.name, + enabled: options.enabled + }, 'name', 'enabled'); + options.content_type = "json"; + const uri = ['sub_accounts', sub_account_id, 'access_keys']; + return call_account_api('POST', uri, params, callback, options); +} + +/** + * @desc Update an existing access key pair in the given subaccount. + * @param sub_account_id {string} - The ID of the subaccount. + * @param [options] {object} - See {@link https://cloudinary.com/documentation/provisioning_api#tag/access-keys/PUT/sub_accounts/{sub_account_id}/access_keys/{key}|update access key optional parameters} in the SDK documentation. + * @param [callback] {function} + */ +function update_access_key(sub_account_id, api_key, options = {}, callback) { + const params = pickOnlyExistingValues({ + name: options.name, + enabled: options.enabled + }, 'name', 'enabled'); + options.content_type = "json"; + const uri = ['sub_accounts', sub_account_id, 'access_keys', api_key]; + return call_account_api('PUT', uri, params, callback, options); +} + +/** + * @desc Delete an existing access key pair in the given subaccount. + * @param sub_account_id {string} - The ID of the subaccount. + * @param [options] {object} - See {@link https://cloudinary.com/documentation/provisioning_api#tag/access-keys/DELETE/sub_accounts/{sub_account_id}/access_keys|delete access key optional parameters} in the SDK documentation. + * @param [callback] {function} + */ +function delete_access_key(sub_account_id, api_key, options = {}, callback) { + const uri = ['sub_accounts', sub_account_id, 'access_keys', api_key]; + return call_account_api('DELETE', uri, {}, callback, options); +} + +/** + * @desc Delete an existing access key pair in the given subaccount by its name. + * @param sub_account_id {string} - The ID of the subaccount. + * @param [options] {object} - See {@link https://cloudinary.com/documentation/provisioning_api#tag/access-keys/DELETE/sub_accounts/{sub_account_id}/access_keys|delete access key optional parameters} in the SDK documentation. + * @param [callback] {function} + */ +function delete_access_key_by_name(sub_account_id, options = {}, callback) { + const params = { name: options.name }; + const uri = ['sub_accounts', sub_account_id, 'access_keys']; + return call_account_api('DELETE', uri, params, callback, options); +} module.exports = { sub_accounts, @@ -310,5 +381,10 @@ module.exports = { create_user, create_user_group, add_user_to_group, - delete_user_group + delete_user_group, + access_keys, + generate_access_key, + update_access_key, + delete_access_key, + delete_access_key_by_name }; diff --git a/test/integration/api/provisioning/access_keys_spec.js b/test/integration/api/provisioning/access_keys_spec.js new file mode 100644 index 00000000..cf525e90 --- /dev/null +++ b/test/integration/api/provisioning/access_keys_spec.js @@ -0,0 +1,122 @@ +const cloudinary = require("../../../../cloudinary"); +const TIMEOUT = require('../../../testUtils/testConstants').TIMEOUT; +let runOnlyForInternalPRs = process.env.TRAVIS_SECURE_ENV_VARS ? describe : describe.skip; + + +describe.skip('Provisioning API - Access Keys Management', function () { + let CLOUD_SECRET; + let CLOUD_API; + let CLOUD_NAME; + let CLOUD_ID; + let CLOUD_NAME_PREFIX = `justaname${process.hrtime()[1] % 10000}`; + this.timeout(TIMEOUT.LONG); + + before("Setup the required test", async () => { + let config = cloudinary.config(true); + if (!(config.provisioning_api_key && config.provisioning_api_secret && config.account_id)) { + // For external PRs the env variables are not availble, so we skip the provisioning API + this.skip(); + } + + let CLOUD_TO_CREATE = CLOUD_NAME_PREFIX + Date.now(); + // Create a sub account(sub cloud) + let res = await cloudinary.provisioning.account.create_sub_account(CLOUD_TO_CREATE, CLOUD_TO_CREATE, {}, true).catch((err) => { + throw err; + }); + + CLOUD_API = res.api_access_keys[0].key; + CLOUD_SECRET = res.api_access_keys[0].secret; + CLOUD_NAME = res.cloud_name; + CLOUD_ID = res.id; + + return true; + }); + + after('Destroy the sub_account and users that were created', async () => { + // Skip 'after' in case we don't have account configuration available + // This means that the beforeHook also didn't run + let config = cloudinary.config(true); + if (!(config.provisioning_api_key && config.provisioning_api_secret && config.account_id)) { + return; + } + + const delRes = await cloudinary.provisioning.account.delete_sub_account(CLOUD_ID); + expect(delRes.message).to.eql('ok'); + }); + + it('List access keys', async () => { + const accessKeys = await cloudinary.provisioning.account.access_keys(CLOUD_ID); + expect(Object.keys(accessKeys)).to.eql(['access_keys', 'total']); + expect(accessKeys.access_keys.length).to.eql(1); + expect(Object.keys(accessKeys.access_keys[0])).to.eql(['name', 'api_key', 'api_secret', 'created_at', 'updated_at', 'enabled']); + }); + + it('Generate new access key', async () => { + const keyName = `test-access-key-${Date.now()}` + const newAccessKey = await cloudinary.provisioning.account.generate_access_key(CLOUD_ID, { name: keyName }); + expect(Object.keys(newAccessKey)).to.eql(['name', 'api_key', 'api_secret', 'created_at', 'updated_at', 'enabled']); + expect(newAccessKey.name).to.eql(keyName); + }); + + it('List access keys with optional query params', async () => { + const keyName1 = `A-test-access-key-${Date.now()}` + const newAccessKey1 = await cloudinary.provisioning.account.generate_access_key(CLOUD_ID, { name: keyName1 }); + expect(Object.keys(newAccessKey1)).to.eql(['name', 'api_key', 'api_secret', 'created_at', 'updated_at', 'enabled']); + expect(newAccessKey1.name).to.eql(keyName1); + + const keyName2 = `B-test-access-key-${Date.now()}` + const newAccessKey2 = await cloudinary.provisioning.account.generate_access_key(CLOUD_ID, { name: keyName2 }); + expect(Object.keys(newAccessKey2)).to.eql(['name', 'api_key', 'api_secret', 'created_at', 'updated_at', 'enabled']); + expect(newAccessKey2.name).to.eql(keyName2); + + const keyName3 = `C-test-access-key-${Date.now()}` + const newAccessKey3 = await cloudinary.provisioning.account.generate_access_key(CLOUD_ID, { name: keyName3 }); + expect(Object.keys(newAccessKey3)).to.eql(['name', 'api_key', 'api_secret', 'created_at', 'updated_at', 'enabled']); + expect(newAccessKey3.name).to.eql(keyName3); + + const pageSize = 2; + const accessKeys = await cloudinary.provisioning.account.access_keys(CLOUD_ID, { + page_size: pageSize, + page: 1, + sort_by: 'name', + sort_order: 'desc' + }); + expect(Object.keys(accessKeys)).to.eql(['access_keys', 'total']); + expect(accessKeys.access_keys.length).to.eql(pageSize); + expect(Object.keys(accessKeys.access_keys[0])).to.eql(['name', 'api_key', 'api_secret', 'created_at', 'updated_at', 'enabled']); + }); + + it('Update access key', async () => { + const keyName = `test-access-key-${Date.now()}` + const newAccessKey = await cloudinary.provisioning.account.generate_access_key(CLOUD_ID, { name: keyName }); + expect(Object.keys(newAccessKey)).to.eql(['name', 'api_key', 'api_secret', 'created_at', 'updated_at', 'enabled']); + expect(newAccessKey.name).to.eql(keyName); + + const newName = `${keyName}-updated`; + const updatedAccessKey = await cloudinary.provisioning.account.update_access_key(CLOUD_ID, newAccessKey.api_key, { name: newName }); + expect(Object.keys(newAccessKey)).to.eql(['name', 'api_key', 'api_secret', 'created_at', 'updated_at', 'enabled']); + expect(updatedAccessKey.name).to.eql(newName); + }); + + it('Delete access keys', async () => { + const keyName = `test-access-key-${Date.now()}` + const newAccessKey = await cloudinary.provisioning.account.generate_access_key(CLOUD_ID, { name: keyName }); + expect(Object.keys(newAccessKey)).to.eql(['name', 'api_key', 'api_secret', 'created_at', 'updated_at', 'enabled']); + expect(newAccessKey.name).to.eql(keyName); + + const deleteAccessKey = await cloudinary.provisioning.account.delete_access_key(CLOUD_ID, newAccessKey.api_key); + expect(Object.keys(deleteAccessKey)).to.eql(['message']); + expect(deleteAccessKey.message).to.eql('ok'); + }); + + it('Delete access keys by name', async () => { + const keyName = `test-access-key-${Date.now()}` + const newAccessKey = await cloudinary.provisioning.account.generate_access_key(CLOUD_ID, { name: keyName }); + expect(Object.keys(newAccessKey)).to.eql(['name', 'api_key', 'api_secret', 'created_at', 'updated_at', 'enabled']); + expect(newAccessKey.name).to.eql(keyName); + + const deleteAccessKey = await cloudinary.provisioning.account.delete_access_key_by_name(CLOUD_ID, { name: keyName }); + expect(Object.keys(deleteAccessKey)).to.eql(['message']); + expect(deleteAccessKey.message).to.eql('ok'); + }); +}); diff --git a/types/index.d.ts b/types/index.d.ts index 5cc15669..908ef607 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -557,6 +557,24 @@ declare module 'cloudinary' { [futureKey: string]: any; } + export interface AccessKeyDetails { + name: string, + api_key: string, + api_secret: string, + created_at: string, + updated_at: string, + enabled: boolean + } + + export interface AccessKeysListResponse { + access_keys: Array, + total: number + } + + export interface DeleteAccessKeyResponse { + message: 'ok' | 'not_found' + } + export interface AuthTokenApiOptions { key: string; acl: string; @@ -1485,6 +1503,29 @@ declare module 'cloudinary' { function user_groups(options?: ProvisioningApiOptions, callback?: ResponseCallback): Promise; function user_group_users(groupId: string, options?: ProvisioningApiOptions, callback?: ResponseCallback): Promise; + + function access_keys(subAccountId: string, options?: ProvisioningApiOptions | { + page_size?: number, + page?: number, + sort_by?: string, + sort_order?: 'desc' | 'asc' + }, callback?: ResponseCallback): Promise; + + function generate_access_key(subAccountId: string, options?: ProvisioningApiOptions | { + name?: string, + enabled?: boolean + }, callback?: ResponseCallback): Promise; + + function update_access_key(subAccountId: string, apiKey: string, options?: ProvisioningApiOptions | { + name?: string, + enabled?: boolean + }, callback?: ResponseCallback): Promise; + + function delete_access_key(subAccountId: string, apiKey: string, options?: ProvisioningApiOptions, callback?: ResponseCallback): Promise; + + function delete_access_key_by_name(subAccountId: string, options: ProvisioningApiOptions | { + name: string, + }, callback?: ResponseCallback): Promise; } }