From a483033700f2e9d48690325ac1c9c256021a9e56 Mon Sep 17 00:00:00 2001 From: John Gresty Date: Fri, 3 Mar 2023 12:54:26 +0000 Subject: [PATCH] feat: add context flag to Snyk App create command The context flag is just passed to the create app endpoint, at this time we accept 'tenant' (the old option) and 'user'. This is to support Snyk Apps that act on behalf of users rather than utilising a bot user. --- src/cli/commands/apps/create-app.ts | 2 ++ src/lib/apps/constants.ts | 12 +++++++++--- src/lib/apps/create-app/index.ts | 10 ++++++++++ src/lib/apps/prompts.ts | 7 +++++++ src/lib/apps/types.ts | 4 ++++ test/jest/acceptance/snyk-apps/create-app.spec.ts | 14 ++++++++++++++ 6 files changed, 46 insertions(+), 3 deletions(-) diff --git a/src/cli/commands/apps/create-app.ts b/src/cli/commands/apps/create-app.ts index 1d8ed6784b..00263f9d7c 100644 --- a/src/cli/commands/apps/create-app.ts +++ b/src/cli/commands/apps/create-app.ts @@ -30,6 +30,7 @@ export async function createApp( snykAppName: name, snykAppRedirectUris: redirectUris, snykAppScopes: scopes, + context, } = data; const payload = { method: 'POST', @@ -38,6 +39,7 @@ export async function createApp( name, redirect_uris: redirectUris, scopes, + context, }, qs: { version: '2022-03-11~experimental', diff --git a/src/lib/apps/constants.ts b/src/lib/apps/constants.ts index 977dde9780..b169df9115 100644 --- a/src/lib/apps/constants.ts +++ b/src/lib/apps/constants.ts @@ -5,6 +5,7 @@ export const SNYK_APP_REDIRECT_URIS = 'snykAppRedirectUris'; export const SNYK_APP_SCOPES = 'snykAppScopes'; export const SNYK_APP_CLIENT_ID = 'snykAppClientId'; export const SNYK_APP_ORG_ID = 'snykAppOrgId'; +export const SNYK_APP_CONTEXT = 'context'; export const SNYK_APP_DEBUG = 'snyk:apps'; export enum EValidSubCommands { @@ -22,12 +23,13 @@ export const AppsErrorMessages = { nameRequired: `Option '--name' is required! For interactive mode, please use '--interactive' or '-i' flag. For more information please run the help command 'snyk apps --help' or 'snyk apps -h'.`, redirectUrisRequired: `Option '--redirect-uris' is required! For interactive mode, please use '--interactive' or '-i' flag. For more information please run the help command 'snyk apps --help' or 'snyk apps -h'.`, scopesRequired: `Option '--scopes' is required! For interactive mode, please use '--interactive' or '-i' flag. For more information please run the help command 'snyk apps --help' or 'snyk apps -h'.`, + invalidContext: `Option '--context' must be either 'tenant' or 'user'! For interactive mode, please use '--interactive' or '-i' flag. For more information please run the help command 'snyk apps --help' or 'snyk apps -h'.`, useExperimental: `\n${chalk.redBright( "All 'apps' commands are only accessible behind the '--experimental' flag.", - )}\n -The behaviour can change at any time, without prior notice. + )}\n +The behaviour can change at any time, without prior notice. You are kindly advised to use all the commands with caution. - + ${chalk.bold('Usage')} ${chalk.italic('snyk apps --experimental')}\n`, }; @@ -54,4 +56,8 @@ export const CreateAppPromptData = { message: 'Please provide the org id under which you want to create your Snyk App: ', }, + SNYK_APP_CONTEXT: { + name: SNYK_APP_CONTEXT, + message: 'Which context will your app operate under: ', + }, }; diff --git a/src/lib/apps/create-app/index.ts b/src/lib/apps/create-app/index.ts index 1ebc5894b7..fefb19b405 100644 --- a/src/lib/apps/create-app/index.ts +++ b/src/lib/apps/create-app/index.ts @@ -1,4 +1,5 @@ import { + AppContext, AppsErrorMessages, createAppPrompts, ICreateAppRequest, @@ -7,6 +8,7 @@ import { SNYK_APP_REDIRECT_URIS, SNYK_APP_SCOPES, SNYK_APP_ORG_ID, + SNYK_APP_CONTEXT, validateUUID, validateAllURL, } from '..'; @@ -37,6 +39,11 @@ export function createAppDataScriptable( ); } else if (!options.scopes) { throw new ValidationError(AppsErrorMessages.scopesRequired); + } else if ( + options.context != null && + !(options.context == 'user' || options.context == 'tenant') + ) { + throw new ValidationError(AppsErrorMessages.invalidContext); } else { return { orgId: options.org, @@ -45,6 +52,7 @@ export function createAppDataScriptable( .replace(/\s+/g, '') .split(','), snykAppScopes: options.scopes.replace(/\s+/g, '').split(','), + context: options.context, }; } } @@ -63,11 +71,13 @@ export async function createAppDataInteractive(): Promise { ',', ) as string[]; const orgId = answers[SNYK_APP_ORG_ID].trim() as string; + const context = answers[SNYK_APP_CONTEXT].trim() as AppContext; // POST: to create an app return { orgId, snykAppName, snykAppRedirectUris, snykAppScopes, + context, }; } diff --git a/src/lib/apps/prompts.ts b/src/lib/apps/prompts.ts index 18ab9e2067..fb3c30ec17 100644 --- a/src/lib/apps/prompts.ts +++ b/src/lib/apps/prompts.ts @@ -29,4 +29,11 @@ export const createAppPrompts = [ message: CreateAppPromptData.SNYK_APP_ORG_ID.message, validate: validateUUID, }, + { + name: CreateAppPromptData.SNYK_APP_CONTEXT.name, + type: 'select', + message: CreateAppPromptData.SNYK_APP_CONTEXT.message, + choices: ['tenant', 'user'], + initial: 'tenant', + }, ]; diff --git a/src/lib/apps/types.ts b/src/lib/apps/types.ts index 604235f83d..c22de496c7 100644 --- a/src/lib/apps/types.ts +++ b/src/lib/apps/types.ts @@ -1,3 +1,5 @@ +export type AppContext = 'tenant' | 'user'; + export interface IGetAppsURLOpts { orgId?: string; clientId?: string; @@ -48,6 +50,7 @@ export interface ICreateAppOptions extends IGenerateAppsOptions { name?: string; redirectUris?: string; scopes?: string; + context?: AppContext; } export interface ICreateAppRequest { @@ -55,4 +58,5 @@ export interface ICreateAppRequest { snykAppName: string; snykAppRedirectUris: string[]; snykAppScopes: string[]; + context?: AppContext; } diff --git a/test/jest/acceptance/snyk-apps/create-app.spec.ts b/test/jest/acceptance/snyk-apps/create-app.spec.ts index 08638c0163..d9fc90d76f 100644 --- a/test/jest/acceptance/snyk-apps/create-app.spec.ts +++ b/test/jest/acceptance/snyk-apps/create-app.spec.ts @@ -176,6 +176,9 @@ describe('snyk-apps: create app', () => { await expect(cli).toDisplay('Please provide the org id under which'); await cli.answer(testData.orgId); + await expect(cli).toDisplay('Which context will your app operate under'); + await cli.answer(''); + // Assert await expect(cli).toDisplay('Snyk App created successfully!'); await expect(cli).toDisplay(testData.appName); @@ -247,6 +250,17 @@ describe('snyk-apps: create app', () => { await expect(cli).toExitWith(2); }); + it('throws an error when an invalid context is provided', async () => { + cli = await startSnykCLI( + `apps create --org=${testData.orgId} --name=${testData.appName} --redirect-uris=${testData.redirectURIs} --scopes=${testData.scopes} --context=foobar --experimental`, + { env }, + ); + await expect(cli).toDisplay( + "Option '--context' must be either 'tenant' or 'user'! For interactive mode, please use '--interactive' or '-i' flag. For more information please run the help command 'snyk apps --help' or 'snyk apps -h'.", + ); + await expect(cli).toExitWith(2); + }); + it('should create app with user provided data', async () => { cli = await startSnykCLI( `apps create --org=${testData.orgId} --name=${testData.appName} --redirect-uris=${testData.redirectURIs} --scopes=${testData.scopes} --experimental`,