Skip to content
Draft
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
74 changes: 71 additions & 3 deletions src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const archiver = require('archiver')
const SupportedRuntimes = ['sequence', 'blackbox', 'nodejs:10', 'nodejs:12', 'nodejs:14', 'nodejs:16', 'nodejs:18', 'nodejs:20', 'nodejs:22', 'nodejs:24']
const { HttpProxyAgent } = require('http-proxy-agent')
const PatchedHttpsProxyAgent = require('./PatchedHttpsProxyAgent.js')
const { getCliEnv, DEFAULT_ENV } = require('@adobe/aio-lib-env')

// must cover 'deploy-service[-region][.env].app-builder[.int|.corp].adp.adobe.io/runtime
const SUPPORTED_ADOBE_ANNOTATION_ENDPOINT_REGEXES = [
Expand All @@ -41,6 +42,7 @@ const ANNOTATION_WEB_EXPORT = 'web-export'
const ANNOTATION_RAW_HTTP = 'raw-http'
const ANNOTATION_REQUIRE_ADOBE_AUTH = 'require-adobe-auth'
const ANNOTATION_REQUIRE_WHISK_AUTH = 'require-whisk-auth'
const ANNOTATION_INCLUDE_IMS_CREDENTIALS = 'include-ims-credentials'
const VALUE_YES = 'yes'
const VALUE_RAW = 'raw'

Expand Down Expand Up @@ -1186,6 +1188,68 @@ function rewriteActionsWithAdobeAuthAnnotation (packages, deploymentPackages) {
}
}

/**
* This function implements the support for the `include-ims-credentials` annotation.
* It will expand the IMS_OAUTH_S2S environment variable into an input object stored under params.__ims_oauth_s2s and params.__ims_env
*
* @access private
* @param {ManifestPackages} packages the manifest packages
* @returns {ManifestPackages} newPackages, rewritten package with added inputs
*/
function rewriteActionsWithAdobeIncludeIMSCredentialsAnnotation (packages) {
// avoid side effects, do not modify input packages
const newPackages = cloneDeep(packages)

// constants
const IMS_OAUTH_S2S_ENV_KEY = 'IMS_OAUTH_S2S'

let imsAuthObject = null
try {
imsAuthObject = JSON.parse(process.env[IMS_OAUTH_S2S_ENV_KEY])
} catch (e) {}

// traverse all actions in all packages
Object.keys(newPackages).forEach((key) => {
if (newPackages[key].actions) {
Object.keys(newPackages[key].actions).forEach((actionName) => {
const thisAction = newPackages[key].actions[actionName]
const newInputs = getIncludeIMSCredentialsAnnotationInputs(thisAction, imsAuthObject)
if (newInputs) {
Object.entries(newInputs).forEach(([k, v]) => { thisAction.inputs[k] = v })
aioLogger.debug(`processed annotation '${ANNOTATION_INCLUDE_IMS_CREDENTIALS}' for action '${key}/${actionName}'.`)
}
})
}
})
return newPackages
}

/**
* Get the inputs for the include-ims-credentials annotation.
*
* @param {object} thisAction the action to process
* @param {object} imsAuthObject the IMS auth object
* @returns {object|undefined} the inputs
*/
function getIncludeIMSCredentialsAnnotationInputs (thisAction, imsAuthObject) {
const env = getCliEnv() || DEFAULT_ENV

const IMS_OAUTH_S2S_INPUT = '__ims_oauth_s2s'
const IMS_ENV_INPUT = '__ims_env'
const IMS_OAUTH_S2S_ENV_KEY = 'IMS_OAUTH_S2S'

// check if the annotation is defined
if (thisAction.annotations?.[ANNOTATION_INCLUDE_IMS_CREDENTIALS]) {
// check if the action is a web action
if (!imsAuthObject) {
aioLogger.warn(`The project has no credentials attached (missing the '${IMS_OAUTH_S2S_ENV_KEY}' environment variable). The annotation '${ANNOTATION_INCLUDE_IMS_CREDENTIALS}' will be ignored.`)
return
}

return { [IMS_OAUTH_S2S_INPUT]: { ...imsAuthObject }, [IMS_ENV_INPUT]: env }
}
}

/**
*
* Process the manifest and deployment content and returns deployment entities.
Expand All @@ -1210,11 +1274,14 @@ function processPackage (packages,

const isAdobeEndpoint = SUPPORTED_ADOBE_ANNOTATION_ENDPOINT_REGEXES.some(regex => regex.test(owOptions.apihost))
if (isAdobeEndpoint) {
// rewrite packages in case there are any `include-ims-credentials` annotations
const newPackages = rewriteActionsWithAdobeIncludeIMSCredentialsAnnotation(pkgs)

// rewrite packages in case there are any `require-adobe-auth` annotations
// this is a temporary feature and will be replaced by a native support in Adobe I/O Runtime
const { newPackages, newDeploymentPackages } = rewriteActionsWithAdobeAuthAnnotation(pkgs, deploymentPkgs)
pkgs = newPackages
deploymentPkgs = newDeploymentPackages
const ret = rewriteActionsWithAdobeAuthAnnotation(newPackages, deploymentPkgs)
pkgs = ret.newPackages
deploymentPkgs = ret.newDeploymentPackages
}

const pkgAndDeps = []
Expand Down Expand Up @@ -2185,5 +2252,6 @@ module.exports = {
dumpActionsBuiltInfo,
safeParse,
isSupportedActionKind,
getIncludeIMSCredentialsAnnotationInputs,
DEFAULT_PACKAGE_RESERVED_NAME
}
135 changes: 135 additions & 0 deletions test/utils.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2710,3 +2710,138 @@ describe('getProxyAgent', () => {
expect(result).toBeInstanceOf(PatchedHttpsProxyAgent)
})
})

describe('include-ims-credentials annotation', () => {
const fakeCode = 'fake action code'
let spy
let originalEnv

beforeEach(() => {
spy = jest.spyOn(fs, 'readFileSync')
spy.mockImplementation(() => fakeCode)
originalEnv = process.env.IMS_OAUTH_S2S
})

afterEach(() => {
spy.mockRestore()
libEnv.getCliEnv.mockReturnValue(PROD_ENV)
if (originalEnv !== undefined) {
process.env.IMS_OAUTH_S2S = originalEnv
} else {
delete process.env.IMS_OAUTH_S2S
}
})

test('action with include-ims-credentials annotation gets IMS inputs added', () => {
const imsCredentials = { client_id: 'test-client', client_secret: 'test-secret' }
process.env.IMS_OAUTH_S2S = JSON.stringify(imsCredentials)
libEnv.getCliEnv.mockReturnValue(PROD_ENV)

const packages = {
pkg1: {
actions: {
theaction: {
function: 'fake.js',
web: 'yes',
inputs: { existingInput: 'value' },
annotations: {
'include-ims-credentials': true
}
}
}
}
}

const res = utils.processPackage(packages, {}, {}, {}, false, { apihost: 'https://adobeioruntime.net' })
expect(res.actions[0].params).toEqual({
existingInput: 'value',
__ims_oauth_s2s: imsCredentials,
__ims_env: PROD_ENV
})
})

test('action with include-ims-credentials annotation and no credentials logs warning', () => {
delete process.env.IMS_OAUTH_S2S
const loggerSpy = jest.spyOn(aioLogger, 'warn')

const packages = {
pkg1: {
actions: {
theaction: {
function: 'fake.js',
web: 'yes',
annotations: {
'include-ims-credentials': true
}
}
}
}
}

utils.processPackage(packages, {}, {}, {}, false, { apihost: 'https://adobeioruntime.net' })
expect(loggerSpy).toHaveBeenCalledWith("The project has no credentials attached (missing the 'IMS_OAUTH_S2S' environment variable). The annotation 'include-ims-credentials' will be ignored.")
})
})

describe('getIncludeIMSCredentialsAnnotationInputs', () => {
afterEach(() => {
libEnv.getCliEnv.mockReturnValue(PROD_ENV)
})

test('returns undefined if annotation is not set', () => {
const action = {
annotations: {}
}
const result = utils.getIncludeIMSCredentialsAnnotationInputs(action, { client_id: 'test' })
expect(result).toBeUndefined()
})

test('returns undefined and warns if annotation is set but imsAuthObject is null', () => {
const action = {
annotations: { 'include-ims-credentials': true }
}
const loggerSpy = jest.spyOn(aioLogger, 'warn')
const result = utils.getIncludeIMSCredentialsAnnotationInputs(action, null)
expect(result).toBeUndefined()
expect(loggerSpy).toHaveBeenCalledWith("The project has no credentials attached (missing the 'IMS_OAUTH_S2S' environment variable). The annotation 'include-ims-credentials' will be ignored.")
})

test('returns inputs with ims credentials and prod env', () => {
libEnv.getCliEnv.mockReturnValue(PROD_ENV)
const action = {
annotations: { 'include-ims-credentials': true }
}
const imsAuthObject = { client_id: 'test-client', client_secret: 'test-secret' }
const result = utils.getIncludeIMSCredentialsAnnotationInputs(action, imsAuthObject)
expect(result).toEqual({
__ims_oauth_s2s: { client_id: 'test-client', client_secret: 'test-secret' },
__ims_env: PROD_ENV
})
})

test('returns inputs with ims credentials and stage env', () => {
libEnv.getCliEnv.mockReturnValue(STAGE_ENV)
const action = {
annotations: { 'include-ims-credentials': true }
}
const imsAuthObject = { client_id: 'test-client' }
const result = utils.getIncludeIMSCredentialsAnnotationInputs(action, imsAuthObject)
expect(result).toEqual({
__ims_oauth_s2s: { client_id: 'test-client' },
__ims_env: STAGE_ENV
})
})

test('returns inputs with default env when getCliEnv returns null', () => {
libEnv.getCliEnv.mockReturnValue(null)
const action = {
annotations: { 'include-ims-credentials': true }
}
const imsAuthObject = { client_id: 'test-client' }
const result = utils.getIncludeIMSCredentialsAnnotationInputs(action, imsAuthObject)
expect(result).toEqual({
__ims_oauth_s2s: { client_id: 'test-client' },
__ims_env: PROD_ENV
})
})
})