diff --git a/DOCUMENTATION.md b/DOCUMENTATION.md index 43271d6..cc0bf3d 100644 --- a/DOCUMENTATION.md +++ b/DOCUMENTATION.md @@ -77,7 +77,26 @@ your profiles by running the ***content-cli profile list*** command. | Note: Please do not use blanks in profile names | |-------------------------------------------------| -#### API Token +#### Profile Types +You can create profiles of two types: using OAuth (Device Code +or Client Credentials) or using API Tokens (Application Key / API Key): + +##### OAuth + +OAuth supports with two grant types: Device Code & Client Credentials. + +With Device Code, creating the profile will trigger an authorization flow +(using the OAuth 2.0 Device code). You will be prompted to follow an authorization +link where you must authorize the **Content CLI** to be able to access the EMS environment +on your behalf. + +With Client Credentials, you need to provide the credentials (Client ID, Client Secret) configured for your OAuth client. +You can create and configure an OAuth clients in the `Admin & Settings` section of your EMS account, under `Applications`. +The OAuth client needs to have the following scopes configured: studio, integration.data-pools, action-engine.projects. +After creating an OAuth client, you should assign it the permissions necessary for the respective commands. More +information on registering OAuth clients can be found [here](https://docs.celonis.com/en/registering-oauth-client.html). + +##### API Token You can choose between two different options when asked for an API token. The first option is to use an API key, which identifies the user that created diff --git a/package.json b/package.json index bc1f199..61dbfcd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@celonis/content-cli", - "version": "0.8.5", + "version": "0.9.0", "description": "CLI Tool to help manage content in Celonis EMS", "main": "content-cli.js", "bin": { @@ -20,6 +20,7 @@ "axios": "1.6.2", "commander": "^6.0.0", "form-data": "4.0.0", + "openid-client": "^5.6.1", "hpagent": "^1.2.0", "semver": "^7.3.2", "valid-url": "^1.0.9", diff --git a/src/commands/objective.command.ts b/src/commands/objective.command.ts deleted file mode 100644 index d1ecce5..0000000 --- a/src/commands/objective.command.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { ContentService } from "../services/content.service"; -import { ObjectiveManagerFactory } from "../content/factory/objective-manager.factory"; - -export class ObjectiveCommand { - private contentService = new ContentService(); - private objectiveManagerFactory = new ObjectiveManagerFactory(); - - public async pullObjective(profile: string, id: string): Promise { - await this.contentService.pull(profile, this.objectiveManagerFactory.createManager(id, null)); - } - - public async pushObjective(profile: string, filename: string): Promise { - await this.contentService.push(profile, this.objectiveManagerFactory.createManager(null, filename)); - } -} diff --git a/src/commands/profile.command.ts b/src/commands/profile.command.ts index 87856b6..5a51ff8 100644 --- a/src/commands/profile.command.ts +++ b/src/commands/profile.command.ts @@ -1,8 +1,8 @@ import { QuestionService } from "../services/question.service"; -import { Profile } from "../interfaces/profile.interface"; +import {Profile, ProfileType} from "../interfaces/profile.interface"; import { ProfileService } from "../services/profile.service"; import { ProfileValidator } from "../validators/profile.validator"; -import { logger } from "../util/logger"; +import { FatalError, logger } from "../util/logger"; export class ProfileCommand { private profileService = new ProfileService(); @@ -11,8 +11,27 @@ export class ProfileCommand { const profile: Profile = {} as Profile; profile.name = await QuestionService.ask("Name of the profile: "); profile.team = await QuestionService.ask("Your team (please provide the full url): "); - profile.apiToken = await QuestionService.ask("Your api token: "); + const type = await QuestionService.ask("Profile type: OAuth Device Code (1), OAuth Client Credentials (2) or Application Key / API Key (3): " ); + switch (type) { + case "1": + profile.type = ProfileType.DEVICE_CODE; + break; + case "2": + profile.type = ProfileType.CLIENT_CREDENTIALS; + profile.clientId = await QuestionService.ask("Your client id: "); + profile.clientSecret = await QuestionService.ask("Your client secret: "); + break; + case "3": + profile.type = ProfileType.KEY; + profile.apiToken = await QuestionService.ask("Your api token: "); + break; + default: + logger.error(new FatalError("Invalid type")); + break; + } profile.authenticationType = await ProfileValidator.validateProfile(profile); + await this.profileService.authorizeProfile(profile); + this.profileService.storeProfile(profile); if (setAsDefault) { await this.makeDefaultProfile(profile.name); diff --git a/src/commands/widget-sourcemaps.command.ts b/src/commands/widget-sourcemaps.command.ts deleted file mode 100644 index 893559e..0000000 --- a/src/commands/widget-sourcemaps.command.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { WidgetSourcemapsManagerFactory } from "../content/factory/widget-sourcemaps-manager.factory"; - -export class WidgetSourcemapsCommand { - private widgetSourcemapsFactory = new WidgetSourcemapsManagerFactory(); - - public async pushSourceMaps(): Promise { - await this.widgetSourcemapsFactory.createManager().push(); - } -} diff --git a/src/content-cli-pull.ts b/src/content-cli-pull.ts index 6c24289..41a081e 100644 --- a/src/content-cli-pull.ts +++ b/src/content-cli-pull.ts @@ -1,6 +1,5 @@ import { AnalysisCommand } from "./commands/analysis.command"; import { SkillCommand } from "./commands/skill.command"; -import { ObjectiveCommand } from "./commands/objective.command"; import { DataPoolCommand } from "./commands/data-pool.command"; import { AssetCommand } from "./commands/asset.command"; import { PackageCommand } from "./commands/package.command"; @@ -55,20 +54,6 @@ class Pull { return program; } - public static objective(program: CommanderStatic): CommanderStatic { - program - .command("objective") - .description("Command to pull an objective") - .option("-p, --profile ", "Profile which you want to use to pull the objective") - .requiredOption("--id ", "Id of the objective you want to pull") - .action(async cmd => { - await new ObjectiveCommand().pullObjective(cmd.profile, cmd.id); - process.exit(); - }); - - return program; - } - public static dataPool(program: CommanderStatic): CommanderStatic { program .command("data-pool") @@ -118,7 +103,6 @@ class Pull { Pull.analysis(commander); Pull.analysisBookmarks(commander); Pull.skill(commander); -Pull.objective(commander); Pull.dataPool(commander); Pull.asset(commander); Pull.package(commander); diff --git a/src/content-cli-push.ts b/src/content-cli-push.ts index 80a0b6c..0f2690b 100644 --- a/src/content-cli-push.ts +++ b/src/content-cli-push.ts @@ -9,7 +9,6 @@ import { DataPoolCommand } from "./commands/data-pool.command"; import { AssetCommand } from "./commands/asset.command"; import { PackageCommand } from "./commands/package.command"; import { CTPCommand } from "./commands/ctp.command"; -import { WidgetSourcemapsCommand } from "./commands/widget-sourcemaps.command"; import { AnalysisBookmarksCommand } from "./commands/analysis-bookmarks.command"; import { execSync } from "child_process"; import { GracefulError, logger } from "./util/logger"; @@ -134,18 +133,6 @@ class Push { return program; } - public static widgetSourcemaps(program: CommanderStatic): CommanderStatic { - program - .command("widget-sourcemaps") - .description("Command to upload sourcemaps to Datadog RUM") - .action(async () => { - await new WidgetSourcemapsCommand().pushSourceMaps(); - process.exit(); - }); - - return program; - } - public static dataPool(program: CommanderStatic): CommanderStatic { program .command("data-pool") @@ -244,7 +231,6 @@ Push.asset(commander); Push.assets(commander); Push.package(commander); Push.packages(commander); -Push.widgetSourcemaps(commander); commander.parse(process.argv); diff --git a/src/content/factory/objective-manager.factory.ts b/src/content/factory/objective-manager.factory.ts deleted file mode 100644 index 7dbb2c5..0000000 --- a/src/content/factory/objective-manager.factory.ts +++ /dev/null @@ -1,22 +0,0 @@ -import * as fs from "fs"; -import * as path from "path"; -import { FatalError, logger } from "../../util/logger"; -import { ObjectiveManager } from "../manager/objective.manager"; - -export class ObjectiveManagerFactory { - public createManager(id: string, filename: string): ObjectiveManager { - const objectiveManager = new ObjectiveManager(); - objectiveManager.id = id; - if (filename !== null) { - objectiveManager.content = this.readFile(filename); - } - return objectiveManager; - } - - private readFile(filename: string): string { - if (!fs.existsSync(path.resolve(process.cwd(), filename))) { - logger.error(new FatalError("The provided file does not exit")); - } - return fs.readFileSync(path.resolve(process.cwd(), filename), { encoding: "utf-8" }); - } -} diff --git a/src/content/factory/widget-sourcemaps-manager.factory.ts b/src/content/factory/widget-sourcemaps-manager.factory.ts deleted file mode 100644 index b09728c..0000000 --- a/src/content/factory/widget-sourcemaps-manager.factory.ts +++ /dev/null @@ -1,24 +0,0 @@ -import * as path from "path"; -import { FatalError, logger } from "../../util/logger"; -import { WidgetSourcemapsManager } from "../manager/widget-sourcemaps.manager"; -import { WidgetManagerFactory } from "./widget-manager.factory"; - -export class WidgetSourcemapsManagerFactory { - private widgetManagerFactory = new WidgetManagerFactory(); - - public createManager(): WidgetSourcemapsManager { - const widgetSourcemapsManager = new WidgetSourcemapsManager(); - const manifest = this.widgetManagerFactory.fetchManifest(); - - if (!manifest) { - logger.error(new FatalError("Missing manifest file.")); - } - - this.widgetManagerFactory.validateManifest(manifest); - - widgetSourcemapsManager.distPath = path.resolve(process.cwd()); - widgetSourcemapsManager.service = manifest.key; - widgetSourcemapsManager.releaseVersion = manifest.version; - return widgetSourcemapsManager; - } -} diff --git a/src/content/manager/analysis-bookmarks.manager.ts b/src/content/manager/analysis-bookmarks.manager.ts index 93cea29..81a820a 100644 --- a/src/content/manager/analysis-bookmarks.manager.ts +++ b/src/content/manager/analysis-bookmarks.manager.ts @@ -4,7 +4,7 @@ import * as fs from "fs"; import * as FormData from "form-data"; export class AnalysisBookmarksManager extends BaseManager { - private static BASE_URL = "/process-analytics/api/bookmarks/"; + private static BASE_URL = "/process-analytics/api/bookmarks"; private static ANALYSIS_BOOKMARKS_FILE_PREFIX = "studio_analysis_bookmarks_"; private _analysisId: string; diff --git a/src/content/manager/objective.manager.ts b/src/content/manager/objective.manager.ts deleted file mode 100644 index eb4324d..0000000 --- a/src/content/manager/objective.manager.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { BaseManager } from "./base.manager"; -import { ManagerConfig } from "../../interfaces/manager-config.interface"; - -export class ObjectiveManager extends BaseManager { - private static BASE_URL = "/transformation-center/api"; - private _id: string; - private _content: string; - - public get content(): string { - return this._content; - } - - public set content(value: string) { - this._content = value; - } - - public get id(): string { - return this._id; - } - - public set id(value: string) { - this._id = value; - } - - public getConfig(): ManagerConfig { - return { - pushUrl: this.profile.team.replace(/\/?$/, `${ObjectiveManager.BASE_URL}/objective-kpis/import`), - pullUrl: this.profile.team.replace(/\/?$/, `${ObjectiveManager.BASE_URL}/objectives/export?id=${this.id}`), - exportFileName: "objective_" + this.id + ".json", - onPushSuccessMessage: (data: any): string => { - return "Objective was pushed successfully. New ID: " + data.analysis.id; - }, - }; - } - - public getBody(): any { - return { - useDataModelId: "dummy", - serializedObjectiveExports: this.content, - }; - } - - protected getSerializedFileContent(data: any): string { - return JSON.stringify(data); - } -} diff --git a/src/content/manager/skill.manager.ts b/src/content/manager/skill.manager.ts index 555e341..fd6ae23 100644 --- a/src/content/manager/skill.manager.ts +++ b/src/content/manager/skill.manager.ts @@ -3,7 +3,7 @@ import { ManagerConfig } from "../../interfaces/manager-config.interface"; import * as FormData from "form-data"; export class SkillManager extends BaseManager { - private static BASE_URL = "/action-engine/api/projects/"; + private static BASE_URL = "/action-engine/api/projects"; private _skillId: string; private _projectId: string; private _content: any; diff --git a/src/content/manager/widget-sourcemaps.manager.ts b/src/content/manager/widget-sourcemaps.manager.ts deleted file mode 100644 index 22173e9..0000000 --- a/src/content/manager/widget-sourcemaps.manager.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { exec, execSync } from "child_process"; -import { GracefulError, logger } from "../../util/logger"; -import { ManagerConfig } from "../../interfaces/manager-config.interface"; -import { BaseManager } from "./base.manager"; - -export class WidgetSourcemapsManager extends BaseManager { - public service: string; - public releaseVersion: string; - public distPath: string; - - public async push(): Promise { - return this.pushWidgetSourcemaps(`assets/widgets/${this.service}`, `assets/widgets/${this.service}`); - } - - private async pushWidgetSourcemaps(sourcemapsPath: string, distPathPostfix: string): Promise { - return new Promise((resolve, reject) => { - if (!process.env.DATADOG_SITE) { - logger.error(new GracefulError("Missing DATADOG_SITE")); - } else if (!process.env.DATADOG_API_KEY) { - logger.error(new GracefulError("Missing DATADOG_API_KEY")); - } - - const datadogCiPath = require.resolve("@datadog/datadog-ci/dist/cli.js"); - const commandLines = [ - `node ${datadogCiPath} sourcemaps upload .`, - `--service=package-manager`, - `--release-version=1.0.0`, - "--disable-git", - ]; - - const datadogCommandLine = [...commandLines, "--minified-path-prefix=/package-manager/"].join(" "); - exec(datadogCommandLine, (error, stdout) => { - if (error) { - logger.error(new GracefulError(error.message)); - } else { - logger.info(new GracefulError(stdout)); - } - - if (process.env.AWS_ACCESS_KEY_ID_CDN && process.env.AWS_SECRET_ACCESS_KEY_CDN) { - try { - const uploadSourcemapsStdout = execSync( - [ - ...commandLines, - `--minified-path-prefix=https://static.celonis.cloud/static/package-manager/`, - ].join(" ") - ); - logger.info(uploadSourcemapsStdout); - } catch (error) { - logger.error(new GracefulError(error.stderr?.toString() || error.message)); - } - } - - return resolve(error || stdout); - }); - }); - } - - protected getConfig(): ManagerConfig { - return {}; - } - - protected getBody(): object { - return {}; - } - - protected getSerializedFileContent(data: any): string { - return ""; - } -} diff --git a/src/interfaces/profile.interface.ts b/src/interfaces/profile.interface.ts index 59f0392..ae21f54 100644 --- a/src/interfaces/profile.interface.ts +++ b/src/interfaces/profile.interface.ts @@ -1,13 +1,34 @@ export interface Profile { name: string; team: string; + type: ProfileType; apiToken: string; authenticationType: AuthenticationType; + clientId?: string; + clientSecret?: string; + scopes?: string[]; + clientAuthenticationMethod?: ClientAuthenticationMethod; + refreshToken?: string; + expiresAt?: number; } export type AuthenticationType = "Bearer" | "AppKey"; +export type ProfileType = "Device Code" | "Client Credentials" | "Key"; + +export type ClientAuthenticationMethod = "client_secret_basic" | "client_secret_post"; // tslint:disable-next-line:variable-name export const AuthenticationType: { [key: string]: AuthenticationType } = { BEARER: "Bearer", APPKEY: "AppKey", }; +// tslint:disable-next-line:variable-name +export const ProfileType: { [key: string]: ProfileType } = { + DEVICE_CODE: "Device Code", + CLIENT_CREDENTIALS: "Client Credentials", + KEY: "Key" +}; +// tslint:disable-next-line:variable-name +export const ClientAuthenticationMethod: { [key: string]: ClientAuthenticationMethod } = { + CLIENT_SECRET_BASIC: "client_secret_basic", + CLIENT_SECRET_POST: "client_secret_post", +}; diff --git a/src/services/profile.service.ts b/src/services/profile.service.ts index 2d163f7..a9f6249 100644 --- a/src/services/profile.service.ts +++ b/src/services/profile.service.ts @@ -1,11 +1,20 @@ -import { AuthenticationType, Profile } from "../interfaces/profile.interface"; +import { + AuthenticationType, ClientAuthenticationMethod, + Profile, ProfileType +} from "../interfaces/profile.interface"; import { ProfileValidator } from "../validators/profile.validator"; import * as path from "path"; import * as fs from "fs"; import { FatalError, logger } from "../util/logger"; - +import { Issuer } from "openid-client"; +import axios from "axios"; import os = require("os"); + const homedir = os.homedir(); +// use 5 seconds buffer to avoid rare cases when accessToken is just about to expire before the command is sent +const expiryBuffer = 5000; +const deviceCodeScopes = ["studio", "package-manager", "integration.data-pools", "action-engine.projects"]; +const clientCredentialsScopes = ["studio", "integration.data-pools", "action-engine.projects"]; export interface Config { defaultProfile: string; @@ -25,7 +34,9 @@ export class ProfileService { path.resolve(this.profileContainerPath, this.constructProfileFileName(profileName)), { encoding: "utf-8" } ); - resolve(JSON.parse(file)); + const profile : Profile = JSON.parse(file); + this.refreshProfile(profile) + .then(() => resolve(profile)); } } catch (e) { reject( @@ -75,6 +86,7 @@ export class ProfileService { team: profileVariables.teamUrl, apiToken: profileVariables.apiToken, authenticationType: AuthenticationType.BEARER, + type: ProfileType.KEY }; profile.authenticationType = await ProfileValidator.validateProfile(profile); return profile; @@ -120,6 +132,126 @@ export class ProfileService { return fileNames; } + public async authorizeProfile(profile: Profile) : Promise { + switch (profile.type) { + case ProfileType.KEY: + const url = profile.team.replace(/\/?$/, "/api/cloud/team"); + try { + await this.tryKeyAuthentication(url, AuthenticationType.BEARER, profile.apiToken); + profile.authenticationType = AuthenticationType.BEARER; + } catch (e) { + try { + await this.tryKeyAuthentication(url, AuthenticationType.APPKEY, profile.apiToken); + profile.authenticationType = AuthenticationType.APPKEY; + } catch (err) { + logger.error(new FatalError("The provided team or api key is wrong.")); + } + } + break; + case ProfileType.DEVICE_CODE: + try { + const deviceCodeIssuer = await Issuer.discover(profile.team); + const deviceCodeOAuthClient = new deviceCodeIssuer.Client({ + client_id: "content-cli", + token_endpoint_auth_method: "none", + }); + const deviceCodeHandle = await deviceCodeOAuthClient.deviceAuthorization({ + scope: deviceCodeScopes.join(" ") + }); + logger.info(`Continue authorization here: ${deviceCodeHandle.verification_uri_complete}`); + const deviceCodeTokenSet = await deviceCodeHandle.poll(); + profile.apiToken = deviceCodeTokenSet.access_token; + profile.refreshToken = deviceCodeTokenSet.refresh_token; + profile.expiresAt = deviceCodeTokenSet.expires_at; + } catch (err) { + logger.error(new FatalError("The provided team is wrong.")); + } + break; + case ProfileType.CLIENT_CREDENTIALS: + const clientCredentialsIssuer = await Issuer.discover(profile.team); + try { + // try with client secret basic + const clientCredentialsOAuthClient = new clientCredentialsIssuer.Client({ + client_id: profile.clientId, + client_secret: profile.clientSecret, + token_endpoint_auth_method: ClientAuthenticationMethod.CLIENT_SECRET_BASIC, + }); + const clientCredentialsTokenSet = await clientCredentialsOAuthClient.grant({ + grant_type: "client_credentials", + scope: clientCredentialsScopes.join(" ") + }); + profile.clientAuthenticationMethod = ClientAuthenticationMethod.CLIENT_SECRET_BASIC; + profile.apiToken = clientCredentialsTokenSet.access_token; + profile.expiresAt = clientCredentialsTokenSet.expires_at; + } catch (e) { + try { + // try with client secret post + const clientCredentialsOAuthClient = new clientCredentialsIssuer.Client({ + client_id: profile.clientId, + client_secret: profile.clientSecret, + token_endpoint_auth_method: ClientAuthenticationMethod.CLIENT_SECRET_POST, + }); + const clientCredentialsTokenSet = await clientCredentialsOAuthClient.grant({ + grant_type: "client_credentials", + scope: clientCredentialsScopes.join(" ") + }); + profile.clientAuthenticationMethod = ClientAuthenticationMethod.CLIENT_SECRET_POST; + profile.apiToken = clientCredentialsTokenSet.access_token; + profile.expiresAt = clientCredentialsTokenSet.expires_at; + } catch (err) { + logger.error(new FatalError("The OAuth client configuration is incorrect. " + + "Check the id, secret and scopes for correctness.")); + } + } + profile.scopes = [...clientCredentialsScopes]; + + break; + default: + logger.error(new FatalError("Unsupported profile type")); + break; + } + } + + public async refreshProfile(profile: Profile) : Promise { + if (!this.isProfileExpired(profile, expiryBuffer)) { + return; + } + const issuer = await Issuer.discover(profile.team); + if (profile.type === ProfileType.DEVICE_CODE) { + try { + const oauthClient = new issuer.Client({ + client_id: "content-cli", + token_endpoint_auth_method: "none", + }); + const tokenSet = await oauthClient.refresh(profile.refreshToken); + profile.apiToken = tokenSet.access_token; + profile.expiresAt = tokenSet.expires_at; + profile.refreshToken = tokenSet.refresh_token; + } catch (err) { + logger.error(new FatalError("The profile cannot be refreshed. Please retry or recreate profile.")); + } + } + else { + try { + const oauthClient = new issuer.Client({ + client_id: profile.clientId, + client_secret: profile.clientSecret, + token_endpoint_auth_method: profile.clientAuthenticationMethod, + }); + const tokenSet = await oauthClient.grant({ + grant_type: "client_credentials", + scope: profile.scopes.join(" ") + }); + profile.apiToken = tokenSet.access_token; + profile.expiresAt = tokenSet.expires_at; + } catch (err) { + logger.error(new FatalError("The profile cannot be refreshed. Please retry or recreate profile.")); + } + } + + this.storeProfile(profile); + } + private getProfileEnvVariables(): any { return { teamUrl: this.getBaseTeamUrl(process.env.TEAM_URL), @@ -135,6 +267,34 @@ export class ProfileService { const url = new URL(teamUrl); return url.origin; } + + private isProfileExpired(profile: Profile, buffer: number = 0): boolean { + if (profile.type === null || profile.type === undefined || profile.type === ProfileType.KEY) { + return false; + } + const now = new Date(); + const expirationTime = new Date(profile.expiresAt * 1000 - buffer); + + return now > expirationTime; + } + + private tryKeyAuthentication(url: string, authType: AuthenticationType, apiToken: string): Promise { + return new Promise((resolve, reject) => { + axios.get(url, { + headers: { + Authorization: `${authType} ${apiToken}` + } + }).then(response => { + if (response.status === 200 && response.data.domain) { + resolve(); + } else { + reject(); + } + }).catch(() => { + reject(); + }) + }) + } } export const profileService = new ProfileService(); diff --git a/src/validators/profile.validator.ts b/src/validators/profile.validator.ts index b77ebd6..b311ae9 100644 --- a/src/validators/profile.validator.ts +++ b/src/validators/profile.validator.ts @@ -1,53 +1,23 @@ -import { AuthenticationType, Profile } from "../interfaces/profile.interface"; +import {Profile, ProfileType} from "../interfaces/profile.interface"; import { FatalError, logger } from "../util/logger"; import validUrl = require("valid-url"); -import axios from "axios"; export class ProfileValidator { public static async validateProfile(profile: Profile): Promise { - return new Promise(async (resolve, reject) => { - if (profile.name == null) { - logger.error(new FatalError("The name can not be empty")); - } - if (profile.team == null) { - logger.error(new FatalError("The team can not be empty")); - } - if (profile.apiToken == null) { - logger.error(new FatalError("The api token can not be empty")); - } - if (!validUrl.isUri(profile.team)) { - logger.error(new FatalError("The provided url is not a valid url.")); - } - const url = profile.team.replace(/\/?$/, "/api/cloud/team"); - - this.tryAuthenticationType(url, AuthenticationType.BEARER, profile.apiToken).then(() => { - resolve(AuthenticationType.BEARER); - }).catch(() => { - this.tryAuthenticationType(url, AuthenticationType.APPKEY, profile.apiToken).then(() => { - resolve(AuthenticationType.APPKEY); - }).catch(() => { - logger.error(new FatalError("The provided team or api key is wrong.")); - reject(); - }) - }); - }); - } - - private static tryAuthenticationType(url: string, authType: AuthenticationType, apiToken: string): Promise { - return new Promise((resolve, reject) => { - axios.get(url, { - headers: { - Authorization: `${authType} ${apiToken}` - } - }).then(response => { - if (response.status === 200 && response.data.domain) { - resolve(); - } else { - reject(); - } - }).catch(() => { - reject(); - }) - }) + if (profile.name == null) { + logger.error(new FatalError("The name can not be empty")); + } + if (profile.team == null) { + logger.error(new FatalError("The team can not be empty")); + } + if (profile.type === ProfileType.KEY && profile.apiToken == null) { + logger.error(new FatalError("The api token can not be empty for this profile type")); + } + if (profile.type === ProfileType.CLIENT_CREDENTIALS && (profile.clientId == null || profile.clientSecret == null)) { + logger.error(new FatalError("The client id and secret can not be empty for this profile type")); + } + if (!validUrl.isUri(profile.team)) { + logger.error(new FatalError("The provided url is not a valid url.")); + } } } diff --git a/tests/utls/context-mock.ts b/tests/utls/context-mock.ts index f1e239e..378d327 100644 --- a/tests/utls/context-mock.ts +++ b/tests/utls/context-mock.ts @@ -4,9 +4,10 @@ export function setDefaultProfile(): void { contextService.setContext({ profile: { name: "test", + type: "Key", team: "https://myTeam.celonis.cloud/", apiToken: "YnQ3N2M0M2ItYzQ3OS00YzgyLTg0ODgtOWNkNzhiNzYwOTU2OlFkNnBpVCs0M0JBYm1ZWGlCZ2hPd245aldwWTNubFQyYVFOTFBUeHEwdUxM", authenticationType: "Bearer" } }); -} \ No newline at end of file +} diff --git a/yarn.lock b/yarn.lock index 321ade0..b1f93b2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2816,6 +2816,11 @@ jmespath@0.15.0: resolved "https://registry.yarnpkg.com/jmespath/-/jmespath-0.15.0.tgz#a3f222a9aae9f966f5d27c796510e28091764217" integrity sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc= +jose@^4.15.1: + version "4.15.4" + resolved "https://registry.yarnpkg.com/jose/-/jose-4.15.4.tgz#02a9a763803e3872cf55f29ecef0dfdcc218cc03" + integrity sha512-W+oqK4H+r5sITxfxpSU+MMdr/YSWGvgZMQDIsNoBDGGy4i7GBPTtvFKibQzW06n3U3TqHjhvBJsirShsEJ6eeQ== + js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" @@ -3190,6 +3195,16 @@ object-assign@^4.1.0: resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= +object-hash@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-2.2.0.tgz#5ad518581eefc443bd763472b8ff2e9c2c0d54a5" + integrity sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw== + +oidc-token-hash@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/oidc-token-hash/-/oidc-token-hash-5.0.3.tgz#9a229f0a1ce9d4fc89bcaee5478c97a889e7b7b6" + integrity sha512-IF4PcGgzAr6XXSff26Sk/+P4KZFJVuHAJZj3wgO3vX2bMdNVp/QXTP3P7CEm9V1IdG8lDLY3HhiqpsE/nOwpPw== + once@^1.3.0, once@^1.3.1, once@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" @@ -3223,6 +3238,16 @@ onetime@^5.1.2: dependencies: mimic-fn "^2.1.0" +openid-client@^5.6.1: + version "5.6.1" + resolved "https://registry.yarnpkg.com/openid-client/-/openid-client-5.6.1.tgz#8f7526a50c290a5e28a7fe21b3ece3107511bc73" + integrity sha512-PtrWsY+dXg6y8mtMPyL/namZSYVz8pjXz3yJiBNZsEdCnu9miHLB4ELVC85WvneMKo2Rg62Ay7NkuCpM0bgiLQ== + dependencies: + jose "^4.15.1" + lru-cache "^6.0.0" + object-hash "^2.2.0" + oidc-token-hash "^5.0.3" + optionator@^0.8.1: version "0.8.3" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495"