Skip to content

Commit 0ac4a27

Browse files
authored
Merge pull request #691 from adobe/issues/oauth-authentication
fix(auth): add support for OAuth authentication in parallel to JWT
2 parents 30b3d18 + 3a401af commit 0ac4a27

File tree

6 files changed

+157
-16
lines changed

6 files changed

+157
-16
lines changed

README.md

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,10 +70,40 @@ Alternatively, if you have selected an organization using `aio console:org:selec
7070

7171
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.
7272

73-
***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).***
73+
***The required type of server-to-server authentication should be [Service Account (JWT/OAuth)](https://developer.adobe.com/developer-console/docs/guides/authentication/ServerToServerAuthentication).***
74+
***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
75+
76+
#### Setup for OAuth integration
7477

7578
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.
79+
```
80+
//config.json
81+
{
82+
"client_id": "value from your CLI integration (String)",
83+
"client_secret": "value from your CLI integration (String)",
84+
"technical_account_id": "value from your CLI integration (String)",
85+
"technical_account_email": "value from your CLI integration (String)",
86+
"ims_org_id": "value from your CLI integration (String)",
87+
"scopes": [
88+
'openid',
89+
'AdobeID',
90+
'read_organizations',
91+
'additional_info.projectedProductContext',
92+
'read_pc.dma_aem_ams'
93+
],
94+
"oauth_enabled": true
95+
}
96+
```
97+
98+
Configure the credentials:
7699

100+
```
101+
aio config:set ims.contexts.aio-cli-plugin-cloudmanager PATH_TO_CONFIG_JSON_FILE --file --json
102+
```
103+
104+
#### Setup for JWT integration
105+
106+
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.
77107
```
78108
//config.json
79109
{
@@ -83,7 +113,8 @@ After you've created the integration, create a `config.json` file on your comput
83113
"ims_org_id": "value from your CLI integration (String)",
84114
"meta_scopes": [
85115
"ent_cloudmgr_sdk"
86-
]
116+
],
117+
"oauth_enabled": false
87118
}
88119
```
89120

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

1405+
To run tests with OAuth credentials, add the following to `.env`:
1406+
1407+
```
1408+
OAUTH_E2E_CLIENT_ID=<CLIENT ID>
1409+
OAUTH_E2E_CLIENT_SECRET=<CLIENT SECRET>
1410+
OAUTH_E2E_TA_ID=<TECHNICAL ACCOUNT ID>
1411+
OAUTH_E2E_TA_EMAIL=<TECHNICAL ACCOUNT EMAIL>
1412+
OAUTH_E2E_IMS_ORG_ID=<ORG ID>
1413+
```
1414+
13741415
With this in place the end-to-end tests can be run with
13751416

13761417
```

e2e/e2e.js

Lines changed: 58 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,9 @@ const exec = (cmd, args) => {
4343
/*
4444
Used in list-programs, which is stubbed out.
4545
Env vars need to be defined and code enabled
46+
Test using JWT integrations; will be removed when JWT is discontinued
4647
*/
47-
const bootstrapAuthContext = async () => {
48+
const bootstrapAuthContextWithJWTIntegration = async () => {
4849
const contextObj = {
4950
client_id: process.env.E2E_CLIENT_ID,
5051
client_secret: process.env.E2E_CLIENT_SECRET,
@@ -54,6 +55,32 @@ const bootstrapAuthContext = async () => {
5455
'ent_cloudmgr_sdk',
5556
],
5657
// private_key: Buffer.from(process.env.E2E_PRIVATE_KEY_B64, 'base64').toString(),
58+
oauth_enabled: false,
59+
}
60+
61+
await context.set(CONTEXT_NAME, contextObj)
62+
}
63+
64+
/*
65+
Used in list-programs, which is stubbed out.
66+
Env vars need to be defined and code enabled
67+
Test using OAuth integrations
68+
*/
69+
const bootstrapAuthContextWithOAuthIntegration = async () => {
70+
const contextObj = {
71+
client_id: process.env.OAUTH_E2E_CLIENT_ID,
72+
client_secrets: [process.env.OAUTH_E2E_CLIENT_SECRET],
73+
technical_account_id: process.env.OAUTH_E2E_TA_ID,
74+
technical_account_email: process.env.OAUTH_E2E_TA_EMAIL,
75+
ims_org_id: process.env.OAUTH_E2E_IMS_ORG_ID,
76+
scopes: [
77+
'openid',
78+
'AdobeID',
79+
'read_organizations',
80+
'additional_info.projectedProductContext',
81+
'read_pc.dma_aem_ams',
82+
],
83+
oauth_enabled: true,
5784
}
5885

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

82-
test('plugin-cloudmanager list-programs', async () => {
83-
await bootstrapAuthContext()
110+
test('plugin-cloudmanager list-programs using JWT integration', async () => {
111+
await bootstrapAuthContextWithJWTIntegration()
112+
const packagejson = JSON.parse(fs.readFileSync('package.json').toString())
113+
const name = `${packagejson.name}`
114+
console.log(chalk.blue(`> e2e tests for ${chalk.bold(name)}`))
115+
116+
console.log(chalk.dim(' - plugin-cloudmanager list-programs ..'))
117+
118+
// let result
119+
// expect(() => { result = exec('./bin/run', ['cloudmanager:list-programs', ...CONTEXT_ARGS, '--json']) }).not.toThrow()
120+
// const parsed = JSON.parse(result.stdout)
121+
const parsed = '{}'
122+
expect(parsed).toSatisfy(arr => arr.length > 0)
123+
124+
console.log(chalk.green(` - done for ${chalk.bold(name)}`))
125+
})
126+
127+
/*
128+
Side condition: debug log output must not be enabled (DEBUG=* or LOG_LEVEL=debug),
129+
or else the result in result.stdout is not valid JSON and cannot be parsed (line: JSON.parse...)
130+
*/
131+
/*
132+
* Note: this test cannot be run by the bot, since it requires setup which the bot can't provide
133+
* If wanting to run the test, the environment variables have to be set with the required authentication information
134+
* Uses OAuth integrations
135+
*/
136+
test('plugin-cloudmanager list-programs using OAuth integration', async () => {
137+
await bootstrapAuthContextWithOAuthIntegration()
84138
const packagejson = JSON.parse(fs.readFileSync('package.json').toString())
85139
const name = `${packagejson.name}`
86140
console.log(chalk.blue(`> e2e tests for ${chalk.bold(name)}`))

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
"@adobe/aio-lib-core-errors": "^3.1.1",
1111
"@adobe/aio-lib-core-logging": "^2.0.0",
1212
"@adobe/aio-lib-core-networking": "^3.0.0",
13-
"@adobe/aio-lib-ims": "^6.0.1",
13+
"@adobe/aio-lib-ims": "^6.5.0",
1414
"@oclif/command": "^1.6.1",
1515
"@oclif/config": "^1.15.1",
1616
"@oclif/parser": "^3.8.5",
@@ -203,4 +203,4 @@
203203
"@semantic-release/github"
204204
]
205205
}
206-
}
206+
}

src/ConfigurationErrors.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ E('CLI_AUTH_NO_ORG', 'The CLI has been authenticated, but no organization has be
5555
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".')
5656
E('IMS_CONTEXT_MISSING_FIELDS', 'One or more of the required fields in %s were not set. Missing keys were %s.')
5757
E('IMS_CONTEXT_MISSING_METASCOPE', 'The configuration %s is missing the required metascope %s.')
58+
E('IMS_CONTEXT_MISSING_OAUTH_SCOPES', 'The configuration %s is missing the required OAuth scopes %s.')
5859
E('CLI_AUTH_EXPLICIT_NO_AUTH', 'cli context explicitly enabled, but not authenticated. You must run "aio auth:login" first.')
5960
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"')
6061
E('CLI_AUTH_CONTEXT_CANNOT_DECODE', 'The access token configured for cli authentication cannot be decoded.')

src/hooks/prerun/check-ims-context-config.js

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,10 @@ const { isThisPlugin } = require('../../cloudmanager-hook-helpers')
1616
const { defaultImsContextName: defaultContextName } = require('../../constants')
1717
const { codes: configurationCodes } = require('../../ConfigurationErrors')
1818

19-
const requiredKeys = ['client_id', 'client_secret', 'technical_account_id', 'meta_scopes', 'ims_org_id', 'private_key']
20-
21-
const requiredMetaScope = 'ent_cloudmgr_sdk'
19+
const requiredKeysForJWTIntegration = ['client_id', 'client_secret', 'technical_account_id', 'meta_scopes', 'ims_org_id', 'private_key']
20+
const requiredKeysForOAuthIntegration = ['client_id', 'client_secrets', 'technical_account_email', 'technical_account_id', 'scopes', 'ims_org_id']
21+
const requiredMetaScopeForJWTIntegration = 'ent_cloudmgr_sdk'
22+
const requiredScopesForOAuthIntegration = ['openid', 'AdobeID', 'read_organizations', 'additional_info.projectedProductContext', 'read_pc.dma_aem_ams']
2223

2324
function getContextName (options) {
2425
if (options.Command.flags && options.Command.flags.imsContextName) {
@@ -46,6 +47,7 @@ module.exports = function (hookOptions) {
4647
}
4748

4849
const missingKeys = []
50+
const requiredKeys = config.oauth_enabled ? requiredKeysForOAuthIntegration : requiredKeysForJWTIntegration
4951

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

60-
const metaScopes = config.meta_scopes
61-
if (!metaScopes.includes || !metaScopes.includes(requiredMetaScope)) {
62-
throw new configurationCodes.IMS_CONTEXT_MISSING_METASCOPE({ messageValues: [configKey, requiredMetaScope] })
62+
if (config.oauth_enabled) {
63+
const oauthScopes = config.scopes
64+
if (!oauthScopes.includes || !requiredScopesForOAuthIntegration.every(scope => oauthScopes.includes(scope))) {
65+
throw new configurationCodes.IMS_CONTEXT_MISSING_OAUTH_SCOPES({ messageValues: [configKey, requiredScopesForOAuthIntegration.join(', ')] })
66+
}
67+
} else {
68+
const metaScopes = config.meta_scopes
69+
if (!metaScopes.includes || !metaScopes.includes(requiredMetaScopeForJWTIntegration)) {
70+
throw new configurationCodes.IMS_CONTEXT_MISSING_METASCOPE({ messageValues: [configKey, requiredMetaScopeForJWTIntegration] })
71+
}
6372
}
6473
}
6574

test/hooks/prerun/check-ims-context-config.test.js

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ test('hook -- command from other plugin', async () => {
4949
})).not.toThrowError()
5050
})
5151

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

70+
test('hook -- ok with OAuth', async () => {
71+
setStore({
72+
'ims.contexts.aio-cli-plugin-cloudmanager': {
73+
client_id: 'test-client-id',
74+
client_secrets: ['5678'],
75+
ims_org_id: 'someorg@AdobeOrg',
76+
technical_account_id: '[email protected]',
77+
technical_account_email: 'unused',
78+
scopes: [
79+
'openid',
80+
'AdobeID',
81+
'read_organizations',
82+
'additional_info.projectedProductContext',
83+
'read_pc.dma_aem_ams',
84+
],
85+
oauth_enabled: true,
86+
},
87+
})
88+
expect(invoke()).not.toThrowError()
89+
})
90+
7091
test('hook -- fully configured cli auth enables cli auth mode', async () => {
7192
setStore({
7293
'ims.contexts.cli': {
@@ -272,7 +293,7 @@ test('hook -- missing some fields', async () => {
272293
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.')
273294
})
274295

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

313+
test('hook -- missing scope for OAuth', async () => {
314+
setStore({
315+
'ims.contexts.aio-cli-plugin-cloudmanager': {
316+
client_id: 'test-client-id',
317+
client_secrets: ['5678'],
318+
ims_org_id: 'someorg@AdobeOrg',
319+
technical_account_id: '[email protected]',
320+
technical_account_email: 'unused',
321+
scopes: [],
322+
oauth_enabled: true,
323+
},
324+
})
325+
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')
326+
})
327+
292328
test('hook -- scope is a number', async () => {
293329
setStore({
294330
'ims.contexts.aio-cli-plugin-cloudmanager': {

0 commit comments

Comments
 (0)