Skip to content
Merged
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
45 changes: 43 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,40 @@ Alternatively, if you have selected an organization using `aio console:org:selec

To use a service account authentication, an integration (aka project) must be created in the [Adobe I/O Console](https://console.adobe.io) which has the Cloud Manager service.

***The required type of server-to-server authentication should be [Service Account (JWT)](https://developer.adobe.com/developer-console/docs/guides/authentication/ServerToServerAuthentication/#service-account-jwt-credential-deprecated).***
***The required type of server-to-server authentication should be [Service Account (JWT/OAuth)](https://developer.adobe.com/developer-console/docs/guides/authentication/ServerToServerAuthentication).***
***NOTE:*** The JWT mode of authentication is deprecated and will be completely removed by Jan,2025. So if you are using JWT integration, it is recommended to migrate to OAuth

#### Setup for OAuth integration

After you've created the integration, create a `config.json` file on your computer and navigate to the integration Overview page. From this page, copy the values into the file as described below.
```
//config.json
{
"client_id": "value from your CLI integration (String)",
"client_secret": "value from your CLI integration (String)",
"technical_account_id": "value from your CLI integration (String)",
"technical_account_email": "value from your CLI integration (String)",
"ims_org_id": "value from your CLI integration (String)",
"scopes": [
'openid',
'AdobeID',
'read_organizations',
'additional_info.projectedProductContext',
'read_pc.dma_aem_ams'
],
"oauth_enabled": true
}
```

Configure the credentials:

```
aio config:set ims.contexts.aio-cli-plugin-cloudmanager PATH_TO_CONFIG_JSON_FILE --file --json
```

#### Setup for JWT integration

After you've created the integration, create a `config.json` file on your computer and navigate to the integration Overview page. From this page, copy the values into the file as described below.
```
//config.json
{
Expand All @@ -83,7 +113,8 @@ After you've created the integration, create a `config.json` file on your comput
"ims_org_id": "value from your CLI integration (String)",
"meta_scopes": [
"ent_cloudmgr_sdk"
]
],
"oauth_enabled": false
}
```

Expand Down Expand Up @@ -1371,6 +1402,16 @@ Note that the private key **must** be base64 encoded, e.g. by running
$ base64 -i private.key
```

To run tests with OAuth credentials, add the following to `.env`:

```
OAUTH_E2E_CLIENT_ID=<CLIENT ID>
OAUTH_E2E_CLIENT_SECRET=<CLIENT SECRET>
OAUTH_E2E_TA_ID=<TECHNICAL ACCOUNT ID>
OAUTH_E2E_TA_EMAIL=<TECHNICAL ACCOUNT EMAIL>
OAUTH_E2E_IMS_ORG_ID=<ORG ID>
```

With this in place the end-to-end tests can be run with

```
Expand Down
62 changes: 58 additions & 4 deletions e2e/e2e.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,9 @@ const exec = (cmd, args) => {
/*
Used in list-programs, which is stubbed out.
Env vars need to be defined and code enabled
Test using JWT integrations; will be removed when JWT is discontinued
*/
const bootstrapAuthContext = async () => {
const bootstrapAuthContextWithJWTIntegration = async () => {
const contextObj = {
client_id: process.env.E2E_CLIENT_ID,
client_secret: process.env.E2E_CLIENT_SECRET,
Expand All @@ -54,6 +55,32 @@ const bootstrapAuthContext = async () => {
'ent_cloudmgr_sdk',
],
// private_key: Buffer.from(process.env.E2E_PRIVATE_KEY_B64, 'base64').toString(),
oauth_enabled: false,
}

await context.set(CONTEXT_NAME, contextObj)
}

/*
Used in list-programs, which is stubbed out.
Env vars need to be defined and code enabled
Test using OAuth integrations
*/
const bootstrapAuthContextWithOAuthIntegration = async () => {
const contextObj = {
client_id: process.env.OAUTH_E2E_CLIENT_ID,
client_secrets: [process.env.OAUTH_E2E_CLIENT_SECRET],
technical_account_id: process.env.OAUTH_E2E_TA_ID,
technical_account_email: process.env.OAUTH_E2E_TA_EMAIL,
ims_org_id: process.env.OAUTH_E2E_IMS_ORG_ID,
scopes: [
'openid',
'AdobeID',
'read_organizations',
'additional_info.projectedProductContext',
'read_pc.dma_aem_ams',
],
oauth_enabled: true,
}

await context.set(CONTEXT_NAME, contextObj)
Expand All @@ -76,11 +103,38 @@ test('plugin-cloudmanager help test', async () => {
*/
/*
* Note: this test cannot be run by the bot, since it requires setup which the bot can't provide
* If wanting to rn the test, the evironment variables have to be set with the required authentication information
* If wanting to run the test, the environment variables have to be set with the required authentication information
* Uses JWT integration which is deprecated; will be removed when JWT is discontinued
*/

test('plugin-cloudmanager list-programs', async () => {
await bootstrapAuthContext()
test('plugin-cloudmanager list-programs using JWT integration', async () => {
await bootstrapAuthContextWithJWTIntegration()
const packagejson = JSON.parse(fs.readFileSync('package.json').toString())
const name = `${packagejson.name}`
console.log(chalk.blue(`> e2e tests for ${chalk.bold(name)}`))

console.log(chalk.dim(' - plugin-cloudmanager list-programs ..'))

// let result
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this commented code needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so the way i've seen it work and based on some past pull requests; these tests are disabled when pushing code to PR. If someone needs to test e2e, they need to uncomment all commented part and run e2e. If you see current test, it does nothing until the main code is uncommented

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

check PR: #669

// expect(() => { result = exec('./bin/run', ['cloudmanager:list-programs', ...CONTEXT_ARGS, '--json']) }).not.toThrow()
// const parsed = JSON.parse(result.stdout)
const parsed = '{}'
expect(parsed).toSatisfy(arr => arr.length > 0)

console.log(chalk.green(` - done for ${chalk.bold(name)}`))
})

/*
Side condition: debug log output must not be enabled (DEBUG=* or LOG_LEVEL=debug),
or else the result in result.stdout is not valid JSON and cannot be parsed (line: JSON.parse...)
*/
/*
* Note: this test cannot be run by the bot, since it requires setup which the bot can't provide
* If wanting to run the test, the environment variables have to be set with the required authentication information
* Uses OAuth integrations
*/
test('plugin-cloudmanager list-programs using OAuth integration', async () => {
await bootstrapAuthContextWithOAuthIntegration()
const packagejson = JSON.parse(fs.readFileSync('package.json').toString())
const name = `${packagejson.name}`
console.log(chalk.blue(`> e2e tests for ${chalk.bold(name)}`))
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"@adobe/aio-lib-core-errors": "^3.1.1",
"@adobe/aio-lib-core-logging": "^2.0.0",
"@adobe/aio-lib-core-networking": "^3.0.0",
"@adobe/aio-lib-ims": "^6.0.1",
"@adobe/aio-lib-ims": "^6.5.0",
"@oclif/command": "^1.6.1",
"@oclif/config": "^1.15.1",
"@oclif/parser": "^3.8.5",
Expand Down Expand Up @@ -203,4 +203,4 @@
"@semantic-release/github"
]
}
}
}
1 change: 1 addition & 0 deletions src/ConfigurationErrors.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ E('CLI_AUTH_NO_ORG', 'The CLI has been authenticated, but no organization has be
E('NO_DEFAULT_IMS_CONTEXT', 'There is no IMS context configuration defined for %s. Either define this context configuration or authenticate using "aio auth:login" and select an organization using "aio cloudmanager:org:select".')
E('IMS_CONTEXT_MISSING_FIELDS', 'One or more of the required fields in %s were not set. Missing keys were %s.')
E('IMS_CONTEXT_MISSING_METASCOPE', 'The configuration %s is missing the required metascope %s.')
E('IMS_CONTEXT_MISSING_OAUTH_SCOPES', 'The configuration %s is missing the required OAuth scopes %s.')
E('CLI_AUTH_EXPLICIT_NO_AUTH', 'cli context explicitly enabled, but not authenticated. You must run "aio auth:login" first.')
E('CLI_AUTH_EXPLICIT_NO_ORG', 'cli context explicitly enabled but no org id specified. Configure using either "cloudmanager_orgid" or by running "aio cloudmanager:org:select"')
E('CLI_AUTH_CONTEXT_CANNOT_DECODE', 'The access token configured for cli authentication cannot be decoded.')
Expand Down
21 changes: 15 additions & 6 deletions src/hooks/prerun/check-ims-context-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@ const { isThisPlugin } = require('../../cloudmanager-hook-helpers')
const { defaultImsContextName: defaultContextName } = require('../../constants')
const { codes: configurationCodes } = require('../../ConfigurationErrors')

const requiredKeys = ['client_id', 'client_secret', 'technical_account_id', 'meta_scopes', 'ims_org_id', 'private_key']

const requiredMetaScope = 'ent_cloudmgr_sdk'
const requiredKeysForJWTIntegration = ['client_id', 'client_secret', 'technical_account_id', 'meta_scopes', 'ims_org_id', 'private_key']
const requiredKeysForOAuthIntegration = ['client_id', 'client_secrets', 'technical_account_email', 'technical_account_id', 'scopes', 'ims_org_id']
const requiredMetaScopeForJWTIntegration = 'ent_cloudmgr_sdk'
const requiredScopesForOAuthIntegration = ['openid', 'AdobeID', 'read_organizations', 'additional_info.projectedProductContext', 'read_pc.dma_aem_ams']

function getContextName (options) {
if (options.Command.flags && options.Command.flags.imsContextName) {
Expand Down Expand Up @@ -46,6 +47,7 @@ module.exports = function (hookOptions) {
}

const missingKeys = []
const requiredKeys = config.oauth_enabled ? requiredKeysForOAuthIntegration : requiredKeysForJWTIntegration

requiredKeys.forEach(key => {
if (!config[key]) {
Expand All @@ -57,9 +59,16 @@ module.exports = function (hookOptions) {
throw new configurationCodes.IMS_CONTEXT_MISSING_FIELDS({ messageValues: [configKey, missingKeys.join(', ')] })
}

const metaScopes = config.meta_scopes
if (!metaScopes.includes || !metaScopes.includes(requiredMetaScope)) {
throw new configurationCodes.IMS_CONTEXT_MISSING_METASCOPE({ messageValues: [configKey, requiredMetaScope] })
if (config.oauth_enabled) {
const oauthScopes = config.scopes
if (!oauthScopes.includes || !requiredScopesForOAuthIntegration.every(scope => oauthScopes.includes(scope))) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same question: why do we need the check !oauthScopes.includes?

throw new configurationCodes.IMS_CONTEXT_MISSING_OAUTH_SCOPES({ messageValues: [configKey, requiredScopesForOAuthIntegration.join(', ')] })
}
} else {
const metaScopes = config.meta_scopes
if (!metaScopes.includes || !metaScopes.includes(requiredMetaScopeForJWTIntegration)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we need to do !metaScopes.includes?

won't metaScopes.includes always be true?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, I think so. It was already there, so I didn't remove it in case there was a situation which needed this

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

probably, in case someone defines metascopes as some other data type that doesn't support .includes then it would return false

throw new configurationCodes.IMS_CONTEXT_MISSING_METASCOPE({ messageValues: [configKey, requiredMetaScopeForJWTIntegration] })
}
}
}

Expand Down
40 changes: 38 additions & 2 deletions test/hooks/prerun/check-ims-context-config.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ test('hook -- command from other plugin', async () => {
})).not.toThrowError()
})

test('hook -- ok', async () => {
test('hook -- ok with JWT', async () => {
setStore({
'ims.contexts.aio-cli-plugin-cloudmanager': {
client_id: 'test-client-id',
Expand All @@ -67,6 +67,27 @@ test('hook -- ok', async () => {
expect(invoke()).not.toThrowError()
})

test('hook -- ok with OAuth', async () => {
setStore({
'ims.contexts.aio-cli-plugin-cloudmanager': {
client_id: 'test-client-id',
client_secrets: ['5678'],
ims_org_id: 'someorg@AdobeOrg',
technical_account_id: '4321@techacct.adobe.com',
technical_account_email: 'unused',
scopes: [
'openid',
'AdobeID',
'read_organizations',
'additional_info.projectedProductContext',
'read_pc.dma_aem_ams',
],
oauth_enabled: true,
},
})
expect(invoke()).not.toThrowError()
})

test('hook -- fully configured cli auth enables cli auth mode', async () => {
setStore({
'ims.contexts.cli': {
Expand Down Expand Up @@ -272,7 +293,7 @@ test('hook -- missing some fields', async () => {
expect(invoke()).toThrowError('One or more of the required fields in ims.contexts.aio-cli-plugin-cloudmanager were not set. Missing keys were technical_account_id, meta_scopes, private_key.')
})

test('hook -- missing scope', async () => {
test('hook -- missing metascope for JWT', async () => {
setStore({
'ims.contexts.aio-cli-plugin-cloudmanager': {
client_id: 'test-client-id',
Expand All @@ -289,6 +310,21 @@ test('hook -- missing scope', async () => {
expect(invoke()).toThrowError('The configuration ims.contexts.aio-cli-plugin-cloudmanager is missing the required metascope ent_cloudmgr_sdk.')
})

test('hook -- missing scope for OAuth', async () => {
setStore({
'ims.contexts.aio-cli-plugin-cloudmanager': {
client_id: 'test-client-id',
client_secrets: ['5678'],
ims_org_id: 'someorg@AdobeOrg',
technical_account_id: '4321@techacct.adobe.com',
technical_account_email: 'unused',
scopes: [],
oauth_enabled: true,
},
})
expect(invoke()).toThrowError('[CloudManagerCLI:IMS_CONTEXT_MISSING_OAUTH_SCOPES] The configuration ims.contexts.aio-cli-plugin-cloudmanager is missing the required OAuth scopes openid, AdobeID, read_organizations, additional_info.projectedProductContext, read_pc.dma_aem_ams')
})

test('hook -- scope is a number', async () => {
setStore({
'ims.contexts.aio-cli-plugin-cloudmanager': {
Expand Down