Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions e2e/register.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Copyright 2023 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/

/* eslint-env mocha */
/* eslint mocha/no-mocha-arrows: "off" */

'use strict';

const { AssetComputeClient, getIntegrationConfiguration } = require("../index.js");
require("dotenv").config();

describe('e2e', () => {
it('register', async function () {
const integration = await getIntegrationConfiguration();
const assetCompute = new AssetComputeClient(integration);
await assetCompute.register();
await assetCompute.close();
}).timeout(60000);
});
49 changes: 36 additions & 13 deletions lib/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ const uuid = require("uuid");
const { AdobeAuth, AdobeIOEvents } = require("@adobe/asset-compute-events-client");
const { AssetCompute } = require("./assetcompute");
const { AssetComputeEventEmitter } = require("./eventemitter");
const { isOAuthServerToServerIntegration,
validateOAuthServerToServerIntegration,
createOAuthServerToServerAccessToken } = require("./oauthservertoserver");

function getAssetComputeClientId(event) {
return event.userData &&
Expand Down Expand Up @@ -95,8 +98,16 @@ function completeClientEvent(requestId, event, context) {
class AssetComputeClient extends EventEmitter {

/**
* Construct Asset Compute client
*
* @typedef {Object} OAuthServerToServerIntegration
* @property {String} TYPE Type of integration: "oauthservertoserver"
* @property {String} ORG_ID Organization id, such as "8765432DEAB65@AdobeOrg"
* @property {String[]} CLIENT_SECRETS Client secrets of the technical account
* @property {String} CLIENT_ID Client id (API key) of the technical account, such as "1234-5678-9876-5433"
* @property {String[]} SCOPES Scopes associated with integration
* @property {String} TECHNICAL_ACCOUNT_ID Id of the technical account, such as "12345667EDBA435@techacct.adobe.com"
* @property {String} TECHNICAL_ACCOUNT_EMAIL Email of the technical account, such as "00000000-0000-0000-0000-000000000000@techacct.adobe.com
*/
/**
* @typedef {Object} AdobeIdTechnicalAccount
* @property {String} id Id of the technical account, such as "12345667EDBA435@techacct.adobe.com"
* @property {String} org Organization id, such as "8765432DEAB65@AdobeOrg"
Expand All @@ -122,20 +133,25 @@ class AssetComputeClient extends EventEmitter {
/**
* Create a high-level asset compute client.
*
* @param {AssetComputeIntegration} integration Asset Compute Integration
* @param {AssetComputeIntegration|OAuthServerToServerIntegration} integration Asset Compute Integration
* @param {AssetComputeClientOptions} [options=] Options provided to the client
*/
constructor(integration, options={}) {
super();

// validate integration
if (!integration || !integration.metascopes || !integration.technicalAccount) {
if (isOAuthServerToServerIntegration(integration)) {
if (!validateOAuthServerToServerIntegration(integration)) {
throw Error(`Asset Compute OAuth Server-to-server integration details are required`);
}
} else if (!integration || !integration.metascopes || !integration.technicalAccount) {
throw Error(`Asset Compute integration details are required`);
}

// exchange JWT access token
this.adobeLoginHost = (options && options.imsEndpoint) || (integration && integration.imsEndpoint);
this.auth = new AdobeAuth({
adobeLoginHost: (options && options.imsEndpoint) || (integration && integration.imsEndpoint)
adobeLoginHost: this.adobeLoginHost
});

this.integration = integration;
Expand All @@ -152,7 +168,9 @@ class AssetComputeClient extends EventEmitter {
*/
static async create(integration, options) {
// validate integration
if (!integration || !integration.metascopes || !integration.technicalAccount) {
if (isOAuthServerToServerIntegration(integration) && !validateOAuthServerToServerIntegration(integration)) {
throw Error(`Asset Compute OAuth Server-to-server integration details are required`);
} else if (!integration || !integration.metascopes || !integration.technicalAccount) {
throw Error(`Asset Compute integration details are required`);
}
const assetComputeClient = new AssetComputeClient(integration, options);
Expand All @@ -165,17 +183,22 @@ class AssetComputeClient extends EventEmitter {
* Set up Asset Compute
*/
async initialize() {
const accessToken = await this.auth.createAccessToken(
this.integration.technicalAccount,
this.integration.metascopes
);
let accessToken;
if (isOAuthServerToServerIntegration(this.integration)) {
accessToken = await createOAuthServerToServerAccessToken(this.integration, this.adobeLoginHost);
} else {
accessToken = await this.auth.createAccessToken(
this.integration.technicalAccount,
this.integration.metascopes
);
}

// Set-up asset compute
const assetCompute = new AssetCompute({
...this.options,
accessToken,
org: this.integration.technicalAccount.org,
apiKey: (this.options && this.options.apiKey) || this.integration.technicalAccount.clientId
org: this.integration.ORG_ID || this.integration.technicalAccount.org,
apiKey: (this.options && this.options.apiKey) || this.integration.CLIENT_ID || this.integration.technicalAccount.clientId
});
this.assetCompute = assetCompute;
this.accessToken = accessToken;
Expand Down Expand Up @@ -266,7 +289,7 @@ class AssetComputeClient extends EventEmitter {
const eventEmitter = new AssetComputeEventEmitter({
...this.options,
accessToken: this.accessToken,
org: this.integration.technicalAccount.org,
org: this.integration.ORG_ID || this.integration.technicalAccount.org,
journal: this.journal
});

Expand Down
27 changes: 25 additions & 2 deletions lib/integrationConfiguration.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

const yaml = require('js-yaml');
const fs = require('fs-extra');
const { isOAuthServerToServerIntegration, validateOAuthServerToServerIntegration } = require('./oauthservertoserver');

let integrationPath;
let privateKeyPath;
Expand All @@ -40,13 +41,17 @@ async function validate() {
throw new Error('Missing required files');
}

const data = await validateJson() || await validateYaml();
const data = await validateOAuthServerToServerJson() || await validateJson() || await validateYaml();
validateData(data);
return data;
}

function validateData(data) {
if ((!data.metascopes || data.metascopes.length === 0)
if (isOAuthServerToServerIntegration(data)) {
if (!validateOAuthServerToServerIntegration(data)) {
throw new Error("Incomplete OAuth Server to Server configuration");
}
} else if ((!data.metascopes || data.metascopes.length === 0)
|| (!data.technicalAccount || data.technicalAccount.length === 0)
|| !data.technicalAccount.id
|| !data.technicalAccount.org
Expand All @@ -58,6 +63,24 @@ function validateData(data) {
}
}

async function validateOAuthServerToServerJson() {
let json;

try {
json = await fs.readJson(integrationPath);
} catch (error) { // eslint-disable-line no-unused-vars
return;
}

if (!json.ORG_ID) {
return;
}

return Object.assign({}, json, {
TYPE: "oauthservertoserver"
});
}

async function validateJson() {
let json;

Expand Down
92 changes: 92 additions & 0 deletions lib/oauthservertoserver.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*
* Copyright 2023 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/

"use strict";

const fetch = require("@adobe/node-fetch-retry");
const FormData = require('form-data');

/**
* Check if an integration configuration is an OAuth Server to Server integration
*
* @param {*} integration Integration configuration
* @returns {boolean} True if the integration is an OAuth Server to Server integration
*/
function isOAuthServerToServerIntegration(integration) {
return integration && integration.TYPE === "oauthservertoserver";
}

/**
* Validate an OAuth Server to Server integration configuration
*
* @param {*} integration Integration configuration
* @returns {boolean} True if the integration is a valid OAuth Server to Server integration
*/
function validateOAuthServerToServerIntegration(integration) {
return integration && (typeof integration.ORG_ID === "string") &&
Array.isArray(integration.CLIENT_SECRETS) && (integration.CLIENT_SECRETS.length > 0) ||
(typeof integration.CLIENT_ID === "string") ||
Array.isArray(integration.SCOPES) && (integration.SCOPES.length > 0) ||
(typeof integration.TECHNICAL_ACCOUNT_ID === "string") ||
(typeof integration.TECHNICAL_ACCOUNT_EMAIL === "string");
}

/**
* Create an access token for an OAuth Server to Server integration
*
* @param {*} integration Integration configuration
* @param {string} [adobeLoginHost] IMS host to use, defaults to https://ims-na1.adobelogin.com
* @returns {string} access token
*/
async function createOAuthServerToServerAccessToken(integration, adobeLoginHost) {
// API: https://wiki.corp.adobe.com/display/ims/IMS+API+-+Client+Credentials+Token

const host = adobeLoginHost || "https://ims-na1.adobelogin.com";
const clientId = integration.CLIENT_ID;
const scopes = integration.SCOPES.join(',');

for (const clientSecret of integration.CLIENT_SECRETS) {
const formData = new FormData();
formData.append('client_secret', clientSecret);
formData.append('grant_type', 'client_credentials');
formData.append('scope', scopes);

const response = await fetch(`${host}/ims/token/v3?client_id=${clientId}`, {
method: "POST",
body: formData
});

if (response.ok) {
const json = await response.json();
if (json && json.access_token) {
return json.access_token;
} else {
throw Error("Unexpected response from IMS");
}
} else {
const json = await response.json();
if (response.status === 400 && json.error === "invalid_client" && json.error_description === "invalid client_secret parameter") {
console.warn("Invalid client_secret, trying next one");
} else {
throw Error(`Unable to create access token: ${response.status} ${response.statusText} ${JSON.stringify(json)}`);
}
}
}

throw Error("Unable to create access token, all client_secret tokens failed");
}

module.exports = {
isOAuthServerToServerIntegration,
validateOAuthServerToServerIntegration,
createOAuthServerToServerAccessToken
};
Loading