import { Constants as MSRestConstants , WebResource } from "@azure/ms-rest-js" ; import { TokenClientCredentials , TokenResponse } from "./tokenClientCredentials" ; import { LinkedSubscription } from "../subscriptionManagement/subscriptionUtils" ; import { execAz } from "../login" ; interface ParsedToken { /**
     * The token audience or the resource.
     */ aud : string ; [ prop ] : any ; } /**
 * Describes the access token retrieved from Azure CLI.
 */ export interface CliAccessToken { /**
     * The access token for the resource
     */ accessToken : string ; /**
     * Time when the access token expires.
     */ expiresOn : Date ; /**
     * SubscriptionId associated with the token.
     */ subscription : string ; /**
     * tenantId associated with the token.
     */ tenant : string ; /**
     * The token type. example: "Bearer".
     */ tokenType : string ; } /**
 * Describes the options that can be provided while listing all the subscriptions/accounts via
 * Azure CLI.
 */ export interface ListAllSubscriptionOptions { /**
     * List all subscriptions, rather just 'Enabled' ones.
     */ all ? : boolean ; /**
     * Retrieve up-to-date subscriptions from server.
     */ refresh ? : boolean ; } export interface AccessTokenOptions { /**
     * The subscription id or name for which the access token is required.
     */ subscriptionIdOrName ? : string ; /**
     * Azure resource endpoints.
     * - Defaults to Azure Resource Manager from environment: AzureCloud. "https://management.azure.com"
     * - For Azure KeyVault: "https://vault.azure.net"
     * - For Azure Batch: "https://batch.core.windows.net"
     * - For Azure Active Directory Graph: "https://graph.windows.net"
     *
     * To get the resource for other clouds:
     * - `az cloud list`
     */ resource ? : string ; } /**
 * Describes the credentials by retrieving token via Azure CLI.
 */ export class AzureCliCredentials implements TokenClientCredentials { /**
     * Provides information about the default/current subscription for Azure CLI.
     */ subscriptionInfo : LinkedSubscription ; /**
     * Provides information about the access token for the corresponding subscription for Azure CLI.
     */ tokenInfo : CliAccessToken ; /**
     * Azure resource endpoints.
     * - Defaults to Azure Resource Manager from environment: AzureCloud. "https://management.azure.com"
     * - For Azure KeyVault: "https://vault.azure.net"
     * - For Azure Batch: "https://batch.core.windows.net"
     * - For Azure Active Directory Graph: "https://graph.windows.net"
     *
     * To get the resource for other clouds:
     * - `az cloud list`
     */ resource : string = "https://management.azure.com" ; /**
     * The number of seconds within which it is good to renew the token.
     *  A constant set to 270 seconds (4.5 minutes).
     */ private readonly _tokenRenewalMarginInSeconds : number = 270 ; constructor ( subscriptionInfo , tokenInfo , resource = "https://management.azure.com" ) { this . subscriptionInfo = subscriptionInfo ; this . tokenInfo = tokenInfo ; this . resource = resource ; } returns public async getToken ( ) { if ( this . _hasTokenExpired ( ) || this . _hasSubscriptionChanged ( ) || this . _hasResourceChanged ( ) ) { try { this . tokenInfo = await AzureCliCredentials . getAccessToken ( { subscriptionIdOrName : this . subscriptionInfo . id , resource : this . resource } ) ; } catch ( err ) { throw new Error ( `An error occurred while refreshing the new access ` + `token:${ err . stderr ? err . stderr : err . message }` ) ; } } const result = { accessToken : this . tokenInfo . accessToken , tokenType : this . tokenInfo . tokenType , expiresOn : this . tokenInfo . expiresOn , tenantId : this . tokenInfo . tenant } ; return result ; } param The public async signRequest ( webResource ) { const tokenResponse = await this . getToken ( ) ; webResource . headers . set ( MSRestConstants . HeaderConstants . AUTHORIZATION , `${ tokenResponse . tokenType } ${ tokenResponse . accessToken }` ) ; return webResource ; } private _hasTokenExpired ( ) { let result = true ; const now = Math . floor ( Date . now ( ) / 1000 ) ; if ( this . tokenInfo . expiresOn && this . tokenInfo . expiresOn instanceof Date && Math . floor ( this . tokenInfo . expiresOn . getTime ( ) / 1000 ) - now > this . _tokenRenewalMarginInSeconds ) { result = false ; } return result ; } private _hasSubscriptionChanged ( ) { return this . subscriptionInfo . id !== this . tokenInfo . subscription ; } private _parseToken ( ) { try { const base64Url = this . tokenInfo . accessToken . split ( "." ) [ 1 ] ; const base64 = decodeURIComponent ( Buffer . from ( base64Url , "base64" ) . toString ( "binary" ) . split ( "" ) . map ( ( c ) => { return "%" + ( "00" + c . charCodeAt ( 0 ) . toString ( 16 ) ) . slice ( - 2 ) ; } ) . join ( "" ) ) ; return JSON . parse ( base64 ) ; } catch ( err ) { const msg = `An error occurred while parsing the access token: ${ err . stack }` ; throw new Error ( msg ) ; } } private _isAzureResourceManagerEndpoint ( newResource , currentResource ) { if ( newResource . endsWith ( "/" ) ) newResource = newResource . slice ( 0 , - 1 ) ; if ( currentResource . endsWith ( "/" ) ) currentResource = currentResource . slice ( 0 , - 1 ) ; return ( ( newResource === "https://management.core.windows.net" && currentResource === "https://management.azure.com" ) || ( newResource === "https://management.azure.com" && currentResource === "https://management.core.windows.net" ) ) ; } private _hasResourceChanged ( ) { const parsedToken = this . _parseToken ( ) ; const currentResource = parsedToken . aud && parsedToken . aud . endsWith ( "/" ) ? parsedToken . aud . slice ( 0 , - 1 ) : parsedToken . aud ; const newResource = this . resource . endsWith ( "/" ) ? this . resource . slice ( 0 , - 1 ) : this . resource ; const result = this . _isAzureResourceManagerEndpoint ( newResource , currentResource ) ? false : currentResource !== newResource ; return result ; } param options static async getAccessToken ( options = { } ) { try { const cmdArguments = [ "account" , "get-access-token" ] ; if ( options . subscriptionIdOrName ) { cmdArguments . push ( "-s" ) ; cmdArguments . push ( options . subscriptionIdOrName ) ; } if ( options . resource ) { cmdArguments . push ( "--resource" ) ; cmdArguments . push ( options . resource ) ; } const result = await execAz ( cmdArguments ) ; result . expiresOn = new Date ( result . expiresOn ) ; return result as CliAccessToken ; } catch ( err ) { const message = `An error occurred while getting credentials from ` + `Azure CLI: ${ err . stack }` ; throw new Error ( message ) ; } } param subscriptionIdOrName static async getSubscription ( subscriptionIdOrName ? ) { if ( subscriptionIdOrName && ( typeof subscriptionIdOrName !== "string" || ! subscriptionIdOrName . length ) ) { throw new Error ( "'subscriptionIdOrName' must be a non-empty string." ) ; } try { const cmdArguments = [ "account" , "show" ] ; if ( subscriptionIdOrName ) { cmdArguments . push ( "-s" ) ; cmdArguments . push ( subscriptionIdOrName ) ; } const result = await execAz ( cmdArguments ) ; return result ; } catch ( err ) { const message = `An error occurred while getting information about the current subscription from ` + `Azure CLI: ${ err . stack }` ; throw new Error ( message ) ; } } param subscriptionIdOrName static async setDefaultSubscription ( subscriptionIdOrName ) { try { await execAz ( [ "account" , "set" , "-s" , subscriptionIdOrName ] ) ; } catch ( err ) { const message = `An error occurred while setting the current subscription from ` + `Azure CLI: ${ err . stack }` ; throw new Error ( message ) ; } } param options static async listAllSubscriptions ( options = { } ) { let subscriptionList = [ ] ; try { const cmdArguments = [ "account" , "list" ] ; if ( options . all ) { cmdArguments . push ( " --all" ) ; } if ( options . refresh ) { cmdArguments . push ( "--refresh" ) ; } subscriptionList = await execAz ( cmdArguments ) ; if ( subscriptionList && subscriptionList . length ) { for ( const sub of subscriptionList ) { if ( sub . cloudName ) { sub . environmentName = sub . cloudName ; delete sub . cloudName ; } } } return subscriptionList ; } catch ( err ) { const message = `An error occurred while getting a list of all the subscription from ` + `Azure CLI: ${ err . stack }` ; throw new Error ( message ) ; } } /**
     * Provides credentials that can be used by the JS SDK to interact with Azure via azure cli.
     * **Pre-requisite**
     * - **install azure-cli** . For more information see
     *  https * - **login via `az login`**
     *  param options static async create ( options = { } ) { const [ subscriptinInfo , accessToken ] = await Promise . all ( [ AzureCliCredentials . getSubscription ( options . subscriptionIdOrName ) , AzureCliCredentials . getAccessToken ( options ) ] ) ; return new AzureCliCredentials ( subscriptinInfo , accessToken , options . resource ) ; } }