diff --git a/packages/core/README.md b/packages/core/README.md index 9f48af25..08ec495f 100644 --- a/packages/core/README.md +++ b/packages/core/README.md @@ -233,7 +233,8 @@ async function getIDTokenAction(): Promise { aud = `${audience}` const id_token = await core.getIDToken(aud) const val = `ID token is ${id_token}` - core.setOutput('id_token', id_token); + core.setSecret(id_token) + core.setOutput('id_token', id_token) } getIDTokenAction() diff --git a/packages/core/package-lock.json b/packages/core/package-lock.json index 00ee1a2a..4fa508f7 100644 --- a/packages/core/package-lock.json +++ b/packages/core/package-lock.json @@ -1,12 +1,12 @@ { "name": "@actions/core", - "version": "1.4.1", + "version": "1.5.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@actions/core", - "version": "1.4.1", + "version": "1.5.0", "license": "MIT", "devDependencies": { "@actions/http-client": "^1.0.11", diff --git a/packages/core/package.json b/packages/core/package.json index 0a6c32a8..78b29316 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@actions/core", - "version": "1.4.1", + "version": "1.5.0", "description": "Actions core lib", "keywords": [ "github", diff --git a/packages/core/src/core.ts b/packages/core/src/core.ts index 05ffd25f..861b624f 100644 --- a/packages/core/src/core.ts +++ b/packages/core/src/core.ts @@ -5,7 +5,7 @@ import {toCommandValue} from './utils' import * as os from 'os' import * as path from 'path' -import {getIDTokenUrl, parseJson, postCall} from './oidc-utils' +import {OidcClient} from './oidc-utils' /** * Interface for getInput options @@ -287,19 +287,7 @@ export function getState(name: string): string { return process.env[`STATE_${name}`] || '' } -export async function getIDToken(audience: string): Promise { - try { - // New ID Token is requested from action service - let id_token_url: string = getIDTokenUrl() - - debug(`ID token url is ${id_token_url}`) - - let body: string = await postCall(id_token_url, audience) - let id_token = parseJson(body) - return id_token - - } catch (error) { - setFailed(error.message) - return error.message - } +export function getIDToken(aud: string): Promise { + let oidcClient = new OidcClient() + return oidcClient.getIDToken(aud) } \ No newline at end of file diff --git a/packages/core/src/oidc-utils.ts b/packages/core/src/oidc-utils.ts index 8398a850..f9b95e6c 100644 --- a/packages/core/src/oidc-utils.ts +++ b/packages/core/src/oidc-utils.ts @@ -1,79 +1,119 @@ import * as actions_http_client from '@actions/http-client' -import {IHeaders} from '@actions/http-client/interfaces' +import {IHeaders,IRequestOptions} from '@actions/http-client/interfaces' import {HttpClient} from '@actions/http-client' import {BearerCredentialHandler} from '@actions/http-client/auth' import {debug} from './core' +interface IOidcClient { -export function createHttpClient() { - return new HttpClient('actions/oidc-client', [ - new BearerCredentialHandler(getRuntimeToken()) - ]) + createHttpClient(): actions_http_client.HttpClient + + getApiVersion(): string + + getRuntimeToken(): string + + getIDTokenUrl(): string + + isSuccessStatusCode(statusCode?: number): boolean + + postCall(id_token_url: string, audience: string): Promise + + parseJson(body: string): string + + getIDToken(audience: string): Promise } -export function getApiVersion(): string { - return '2.0' -} +export class OidcClient implements IOidcClient { -export function getRuntimeToken(){ - const token = process.env['ACTIONS_RUNTIME_TOKEN'] - if (!token) { - throw new Error('Unable to get ACTIONS_RUNTIME_TOKEN env variable') - } - return token -} - -export function getIDTokenUrl(){ - let runtimeUrl = process.env['ACTIONS_ID_TOKEN_REQUEST_URL'] - if (!runtimeUrl) { - throw new Error('Unable to get ACTIONS_ID_TOKEN_REQUEST_URL env variable') - } - return runtimeUrl + '?api-version=' + getApiVersion() -} - -export function isSuccessStatusCode(statusCode?: number): boolean { - if (!statusCode) { - return false - } - return statusCode >= 200 && statusCode < 300 -} - -export async function postCall(id_token_url: string, audience: string): Promise { - - const httpclient = createHttpClient() - if (httpclient === undefined) { - throw new Error(`Failed to get Httpclient `) + createHttpClient(allowRetry = true, maxRetry = 10) { + let requestOptions : IRequestOptions = {} + requestOptions.allowRetries = allowRetry + requestOptions.maxRetries = maxRetry + return new HttpClient('actions/oidc-client', [ + new BearerCredentialHandler(this.getRuntimeToken())], + requestOptions) } - debug(`Httpclient created ${httpclient} `) // debug is only output if you set the secret `ACTIONS_RUNNER_DEBUG` to true - - const additionalHeaders: IHeaders = {} - additionalHeaders[actions_http_client.Headers.ContentType] = actions_http_client.MediaTypes.ApplicationJson - additionalHeaders[actions_http_client.Headers.Accept] = actions_http_client.MediaTypes.ApplicationJson - - debug(`audience is ${audience !== null ? audience : 'null'}`) - - const data: string = audience !== null ? JSON.stringify({aud: audience}) : '' - const response = await httpclient.post(id_token_url, data, additionalHeaders) - - if (!isSuccessStatusCode(response.message.statusCode)) { - throw new Error( - `Failed to get ID Token. Error Code : ${response.message.statusCode} Error message : ${response.message.statusMessage}` - ) + getApiVersion(): string { + return '2.0' } - let body: string = await response.readBody() - return body -} - -export function parseJson(body: string): string { - const val = JSON.parse(body) - let id_token = '' - if ('value' in val) { - id_token = val['value'] - } else { - throw new Error('Response json body do not have ID Token field') + getRuntimeToken(){ + const token = process.env['ACTIONS_RUNTIME_TOKEN'] + if (!token) { + throw new Error('Unable to get ACTIONS_RUNTIME_TOKEN env variable') + } + return token } - debug(`id_token : ${id_token}`) - return id_token + + getIDTokenUrl(){ + let runtimeUrl = process.env['ACTIONS_ID_TOKEN_REQUEST_URL'] + if (!runtimeUrl) { + throw new Error('Unable to get ACTIONS_ID_TOKEN_REQUEST_URL env variable') + } + return runtimeUrl + '?api-version=' + this.getApiVersion() + } + + isSuccessStatusCode(statusCode?: number): boolean { + if (!statusCode) { + return false + } + return statusCode >= 200 && statusCode < 300 + } + + async postCall(id_token_url: string, audience: string): Promise { + + const httpclient = this.createHttpClient() + if (httpclient === undefined) { + throw new Error(`Failed to get Httpclient `) + } + + debug(`Httpclient created ${httpclient} `) // debug is only output if you set the secret `ACTIONS_RUNNER_DEBUG` to true + + let additionalHeaders: IHeaders = {} + additionalHeaders[actions_http_client.Headers.ContentType] = actions_http_client.MediaTypes.ApplicationJson + additionalHeaders[actions_http_client.Headers.Accept] = actions_http_client.MediaTypes.ApplicationJson + + debug(`audience is ${audience !== null ? audience : 'null'}`) + + const data: string = audience !== null ? JSON.stringify({aud: audience}) : '' + const response = await httpclient.post(id_token_url, data, additionalHeaders) + + if (!this.isSuccessStatusCode(response.message.statusCode)) { + throw new Error( + `Failed to get ID Token. Error Code : ${response.message.statusCode} Error message : ${response.message.statusMessage}` + ) + } + let body: string = await response.readBody() + + return body + } + + parseJson(body: string): string { + const val = JSON.parse(body) + let id_token = '' + if ('value' in val) { + id_token = val['value'] + } else { + throw new Error('Response json body do not have ID Token field') + } + return id_token + } + + async getIDToken(audience: string): Promise { + try { + // New ID Token is requested from action service + let id_token_url: string = this.getIDTokenUrl() + + debug(`ID token url is ${id_token_url}`) + + let body: string = await this.postCall(id_token_url, audience) + let id_token = this.parseJson(body) + return id_token + + } catch (error) { + throw new Error(`Error message: ${error.message}`) + } + } + } \ No newline at end of file