diff --git a/src/lib/defaults.js b/src/lib/defaults.js index c24d5440..7f0c5b81 100644 --- a/src/lib/defaults.js +++ b/src/lib/defaults.js @@ -22,6 +22,7 @@ module.exports = { defaultImageCacheDuration: '604800', AIO_CONFIG_IMS_ORG_ID: 'project.org.ims_org_id', SERVICE_API_KEY_ENV: 'SERVICE_API_KEY', + IMS_OAUTH_S2S_ENV: 'IMS_OAUTH_S2S', ENTP_INT_CERTS_FOLDER: 'entp-int-certs', CONSOLE_API_KEYS: { prod: 'aio-cli-console-auth', diff --git a/src/lib/import-helper.js b/src/lib/import-helper.js index 78922063..422b1a48 100644 --- a/src/lib/import-helper.js +++ b/src/lib/import-helper.js @@ -734,8 +734,31 @@ const getProjectCredentialType = (projectConfig, flags) => { return LibConsoleCLI.OAUTH_SERVER_TO_SERVER_CREDENTIAL } +/** + * Get the OAuth server_to_server credential in IMS API format, from the console config. + * + * @param {object} config Console config object + * @returns {{ client_id, client_secret, org_id, scopes } | undefined} OAuthS2S credential or undefined + */ +const getOAuthS2SCredential = (config) => { + const credential = config?.project?.workspace?.details?.credentials + ?.find(c => c.integration_type === 'oauth_server_to_server') + ?.oauth_server_to_server + const imsOrgId = config?.project?.org?.ims_org_id + + if (credential) { + return { + client_id: credential.client_id, + client_secret: credential.client_secrets[0], // take the first secret + org_id: imsOrgId, + scopes: credential.scopes + } + } +} + module.exports = { getServiceApiKey, + getOAuthS2SCredential, writeFile, loadConfigFile, loadAndValidateConfigFile, diff --git a/src/lib/import.js b/src/lib/import.js index ce2dcc4b..7a6f91ce 100644 --- a/src/lib/import.js +++ b/src/lib/import.js @@ -1,5 +1,5 @@ -const { loadAndValidateConfigFile, importConfigJson, loadConfigFile, getServiceApiKey } = require('./import-helper') -const { SERVICE_API_KEY_ENV } = require('./defaults') +const { loadAndValidateConfigFile, importConfigJson, loadConfigFile, getServiceApiKey, getOAuthS2SCredential } = require('./import-helper') +const { SERVICE_API_KEY_ENV, IMS_OAUTH_S2S_ENV } = require('./defaults') /** * Imports the project's console config to the local environment. @@ -23,7 +23,14 @@ async function importConsoleConfig (consoleConfigFileOrBuffer, flags) { const config = loadFunc(consoleConfigFileOrBuffer).values const serviceClientId = getServiceApiKey(config, useJwt) - const extraEnvVars = { [SERVICE_API_KEY_ENV]: serviceClientId } + const oauthS2SCredential = getOAuthS2SCredential(config) + + let extraEnvVars + if (typeof oauthS2SCredential === 'object') { + extraEnvVars = { [SERVICE_API_KEY_ENV]: serviceClientId, [IMS_OAUTH_S2S_ENV]: JSON.stringify(oauthS2SCredential) } + } else { + extraEnvVars = { [SERVICE_API_KEY_ENV]: serviceClientId } + } await importConfigJson(consoleConfigFileOrBuffer, process.cwd(), { interactive, overwrite, merge, useJwt }, extraEnvVars) return config diff --git a/test/commands/lib/import-helper.test.js b/test/commands/lib/import-helper.test.js index 130fb70f..c91855df 100644 --- a/test/commands/lib/import-helper.test.js +++ b/test/commands/lib/import-helper.test.js @@ -20,6 +20,7 @@ inquirer.createPromptModule.mockReturnValue(mockPrompt) const { getServiceApiKey, + getOAuthS2SCredential, loadAndValidateConfigFile, importConfigJson, writeAio, @@ -41,6 +42,9 @@ test('exports', () => { expect(getServiceApiKey).toBeDefined() expect(getServiceApiKey).toBeInstanceOf(Function) + expect(getOAuthS2SCredential).toBeDefined() + expect(getOAuthS2SCredential).toBeInstanceOf(Function) + expect(loadAndValidateConfigFile).toBeDefined() expect(loadAndValidateConfigFile).toBeInstanceOf(Function) @@ -446,3 +450,40 @@ describe('getServiceApiKey', () => { expect(getServiceApiKey(config, true)).toEqual('XUXUXUXUXUXUXUX') }) }) + +describe('getOAuthS2SCredential', () => { + test('bad config (undefined)', () => { + expect(getOAuthS2SCredential(undefined)).toBeUndefined() + }) + + test('bad config (empty object)', () => { + expect(getOAuthS2SCredential({})).toBeUndefined() + }) + + test('config file only has jwt (no OAuth S2S)', () => { + const config = fixtureHjson('valid.config.json') + expect(getOAuthS2SCredential(config)).toBeUndefined() + }) + + test('config file has no OAuth S2S credentials', () => { + const config = fixtureHjson('oauths2s/valid.config.no.creds.json') + expect(getOAuthS2SCredential(config)).toBeUndefined() + }) + + test('config file has OAuth S2S', () => { + const config = fixtureHjson('oauths2s/valid.config.json') + expect(getOAuthS2SCredential(config)).toEqual({ + client_id: 'CXCXCXCXCXCXCXCXC', + client_secret: 'SFSFSFSFSFSFSFSFSFSFSFSFSFS', + org_id: 'XOXOXOXOXOXOX@AdobeOrg', + scopes: ['openid', 'AdobeID'] + }) + }) + + test('config file has OAuth S2S (migration, contains jwt)', () => { + // Note: migration configs have integration_type 'oauth_server_to_server_migrate', not 'oauth_server_to_server' + // so getOAuthS2SCredential should return undefined for migration configs + const config = fixtureHjson('oauths2s/valid.config.migrate.json') + expect(getOAuthS2SCredential(config)).toBeUndefined() + }) +}) diff --git a/test/commands/lib/import.test.js b/test/commands/lib/import.test.js index c935a37c..4d9ca884 100644 --- a/test/commands/lib/import.test.js +++ b/test/commands/lib/import.test.js @@ -10,12 +10,16 @@ OF ANY KIND, either express or implied. See the License for the specific languag governing permissions and limitations under the License. */ const inquirer = require('inquirer') +const fs = require('fs-extra') // mock prompt before import const mockPrompt = jest.fn() inquirer.createPromptModule.mockReturnValue(mockPrompt) const { importConsoleConfig, downloadConsoleConfigToBuffer } = require('../../../src/lib/import') +const { SERVICE_API_KEY_ENV, IMS_OAUTH_S2S_ENV } = require('../../../src/lib/defaults') + +jest.mock('fs-extra') beforeEach(() => { jest.clearAllMocks() @@ -29,4 +33,46 @@ test('exports', () => { expect(downloadConsoleConfigToBuffer).toBeInstanceOf(Function) }) +describe('importConsoleConfig', () => { + test('with oauth_server_to_server credentials, adds IMS_OAUTH_S2S to env vars', async () => { + const configContent = fixtureFile('oauths2s/valid.config.json') + // The file is read twice: once by importConsoleConfig (loadFunc) and once by importConfigJson + fs.readFileSync.mockReturnValue(configContent) + + const config = await importConsoleConfig('/some/config/path', { overwrite: true }) + + expect(config).toBeDefined() + expect(config.project.name).toEqual('TestProject123') + + // Check that writeFile was called with the IMS_OAUTH_S2S_ENV variable + const envWriteCall = fs.writeFile.mock.calls.find(call => call[0].endsWith('.env')) + expect(envWriteCall).toBeDefined() + expect(envWriteCall[1]).toContain(SERVICE_API_KEY_ENV) + expect(envWriteCall[1]).toContain(IMS_OAUTH_S2S_ENV) + + // Verify the IMS_OAUTH_S2S value contains expected credential data + const envContent = envWriteCall[1] + expect(envContent).toContain('"client_id":"CXCXCXCXCXCXCXCXC"') + expect(envContent).toContain('"client_secret":"SFSFSFSFSFSFSFSFSFSFSFSFSFS"') + expect(envContent).toContain('"org_id":"XOXOXOXOXOXOX@AdobeOrg"') + }) + + test('with jwt credentials only, does not add IMS_OAUTH_S2S to env vars', async () => { + const configContent = fixtureFile('valid.config.json') + // The file is read twice: once by importConsoleConfig (loadFunc) and once by importConfigJson + fs.readFileSync.mockReturnValue(configContent) + + const config = await importConsoleConfig('/some/config/path', { overwrite: true }) + + expect(config).toBeDefined() + expect(config.project.name).toEqual('TestProject123') + + // Check that writeFile was called without the IMS_OAUTH_S2S_ENV variable + const envWriteCall = fs.writeFile.mock.calls.find(call => call[0].endsWith('.env')) + expect(envWriteCall).toBeDefined() + expect(envWriteCall[1]).toContain(SERVICE_API_KEY_ENV) + expect(envWriteCall[1]).not.toContain(IMS_OAUTH_S2S_ENV) + }) +}) + // The functions in this module are largely tested by use.test.js