This commit is contained in:
Ramya Rao 2021-03-26 12:21:05 -07:00 коммит произвёл GitHub
Родитель 4c177518c3
Коммит 957a3ee390
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
23 изменённых файлов: 719 добавлений и 327 удалений

2
.prettierignore Normal file
Просмотреть файл

@ -0,0 +1,2 @@
typings/
**/*.d.ts

10
.prettierrc.json Normal file
Просмотреть файл

@ -0,0 +1,10 @@
{
"arrowParens": "always",
"bracketSpacing": true,
"endOfLine": "lf",
"printWidth": 100,
"semi": true,
"singleQuote": false,
"tabWidth": 2,
"trailingComma": "none"
}

Просмотреть файл

@ -1,3 +1,3 @@
import { checkPackageJsonVersion } from "@ts-common/azure-js-dev-tools";
process.exitCode = checkPackageJsonVersion({ startPath: __dirname }).check() as number;
process.exitCode = checkPackageJsonVersion({ startPath: __dirname }).check() as number;

Просмотреть файл

@ -101,19 +101,11 @@ export class ApplicationTokenCertificateCredentials extends ApplicationTokenCred
domain: string,
options: AzureTokenCredentialsOptions
): ApplicationTokenCertificateCredentials {
if (
!certificateStringOrFilePath ||
typeof certificateStringOrFilePath.valueOf() !== "string"
) {
throw new Error(
"'certificateStringOrFilePath' must be a non empty string."
);
if (!certificateStringOrFilePath || typeof certificateStringOrFilePath.valueOf() !== "string") {
throw new Error("'certificateStringOrFilePath' must be a non empty string.");
}
if (!certificateStringOrFilePath.startsWith("-----BEGIN")) {
certificateStringOrFilePath = readFileSync(
certificateStringOrFilePath,
"utf8"
);
certificateStringOrFilePath = readFileSync(certificateStringOrFilePath, "utf8");
}
const certificatePattern = /(-+BEGIN CERTIFICATE-+)(\n\r?|\r\n?)([A-Za-z0-9\+\/\n\r]+\=*)(\n\r?|\r\n?)(-+END CERTIFICATE-+)/;
const matchCert = certificateStringOrFilePath.match(certificatePattern);

Просмотреть файл

@ -46,10 +46,7 @@ export class ApplicationTokenCredentials extends ApplicationTokenCredentialsBase
try {
return await this.getTokenFromCache();
} catch (error) {
if (
error.message &&
error.message.startsWith(AuthConstants.SDK_INTERNAL_ERROR)
) {
if (error.message && error.message.startsWith(AuthConstants.SDK_INTERNAL_ERROR)) {
throw error;
}
const resource = this.getActiveDirectoryResourceId();

Просмотреть файл

@ -52,9 +52,9 @@ export abstract class ApplicationTokenCredentialsBase extends TokenCredentialsBa
throw new Error(
AuthConstants.SDK_INTERNAL_ERROR +
" : " +
"critical failure while removing expired token for service principal from token cache. " +
message
" : " +
"critical failure while removing expired token for service principal from token cache. " +
message
);
}
}
@ -71,7 +71,7 @@ export abstract class ApplicationTokenCredentialsBase extends TokenCredentialsBa
query: object
): Promise<{ result: boolean; details?: Error }> {
const self = this;
return new Promise<{ result: boolean; details?: Error }>(resolve => {
return new Promise<{ result: boolean; details?: Error }>((resolve) => {
self.tokenCache.find(query, (error: Error, entries: any[]) => {
if (error) {
return resolve({ result: false, details: error });

Просмотреть файл

@ -109,7 +109,8 @@ export class AzureCliCredentials implements TokenClientCredentials {
subscriptionInfo: LinkedSubscription,
tokenInfo: CliAccessToken,
// tslint:disable-next-line: no-inferrable-types
resource: string = "https://management.azure.com") {
resource: string = "https://management.azure.com"
) {
this.subscriptionInfo = subscriptionInfo;
this.tokenInfo = tokenInfo;
this.resource = resource;
@ -124,16 +125,14 @@ export class AzureCliCredentials implements TokenClientCredentials {
if (this._hasTokenExpired() || this._hasSubscriptionChanged() || this._hasResourceChanged()) {
try {
// refresh the access token
this.tokenInfo = await AzureCliCredentials.getAccessToken(
{
subscriptionIdOrName: this.subscriptionInfo.id,
resource: this.resource
}
);
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}`
`token:${err.stderr ? err.stderr : err.message}`
);
}
}
@ -162,9 +161,12 @@ export class AzureCliCredentials implements TokenClientCredentials {
private _hasTokenExpired(): boolean {
let result = true;
const now = Math.floor(Date.now() / 1000);
if (this.tokenInfo.expiresOn &&
if (
this.tokenInfo.expiresOn &&
this.tokenInfo.expiresOn instanceof Date &&
Math.floor(this.tokenInfo.expiresOn.getTime() / 1000) - now > this._tokenRenewalMarginInSeconds) {
Math.floor(this.tokenInfo.expiresOn.getTime() / 1000) - now >
this._tokenRenewalMarginInSeconds
) {
result = false;
}
return result;
@ -178,9 +180,14 @@ export class AzureCliCredentials implements TokenClientCredentials {
try {
const base64Url: string = this.tokenInfo.accessToken.split(".")[1];
const base64: string = decodeURIComponent(
Buffer.from(base64Url, "base64").toString("binary").split("").map((c) => {
return "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2);
}).join(""));
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) {
@ -192,22 +199,23 @@ export class AzureCliCredentials implements TokenClientCredentials {
private _isAzureResourceManagerEndpoint(newResource: string, currentResource: string): boolean {
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") ||
return (
(newResource === "https://management.core.windows.net" &&
currentResource === "https://management.azure.com") ||
(newResource === "https://management.azure.com" &&
currentResource === "https://management.core.windows.net");
currentResource === "https://management.core.windows.net")
);
}
private _hasResourceChanged(): boolean {
const parsedToken: ParsedToken = this._parseToken();
// normalize the resource string, since it is possible to
// provide a resource without a trailing slash
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 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;
@ -232,8 +240,7 @@ export class AzureCliCredentials implements TokenClientCredentials {
return result as CliAccessToken;
} catch (err) {
const message =
`An error occurred while getting credentials from ` +
`Azure CLI: ${err.stack}`;
`An error occurred while getting credentials from ` + `Azure CLI: ${err.stack}`;
throw new Error(message);
}
}
@ -244,7 +251,10 @@ export class AzureCliCredentials implements TokenClientCredentials {
* required.
*/
static async getSubscription(subscriptionIdOrName?: string): Promise<LinkedSubscription> {
if (subscriptionIdOrName && (typeof subscriptionIdOrName !== "string" || !subscriptionIdOrName.length)) {
if (
subscriptionIdOrName &&
(typeof subscriptionIdOrName !== "string" || !subscriptionIdOrName.length)
) {
throw new Error("'subscriptionIdOrName' must be a non-empty string.");
}
try {
@ -282,7 +292,9 @@ export class AzureCliCredentials implements TokenClientCredentials {
* Returns a list of all the subscriptions from Azure CLI.
* @param options Optional parameters that can be provided while listing all the subcriptions.
*/
static async listAllSubscriptions(options: ListAllSubscriptionOptions = {}): Promise<LinkedSubscription[]> {
static async listAllSubscriptions(
options: ListAllSubscriptionOptions = {}
): Promise<LinkedSubscription[]> {
let subscriptionList: any[] = [];
try {
let cmd = "account list";

Просмотреть файл

@ -7,7 +7,6 @@ import { AuthConstants, TokenAudience } from "../util/authConstants";
import { TokenResponse, TokenCache } from "adal-node";
export class DeviceTokenCredentials extends TokenCredentialsBase {
readonly username: string;
/**
@ -34,8 +33,8 @@ export class DeviceTokenCredentials extends TokenCredentialsBase {
username?: string,
tokenAudience?: TokenAudience,
environment?: Environment,
tokenCache?: TokenCache) {
tokenCache?: TokenCache
) {
if (!username) {
username = "user@example.com";
}
@ -57,4 +56,4 @@ export class DeviceTokenCredentials extends TokenCredentialsBase {
// For device auth, this is just getTokenFromCache.
return this.getTokenFromCache(this.username);
}
}
}

Просмотреть файл

@ -33,51 +33,83 @@ function _convert(credentials: MSITokenCredentials): MSITokenCredentials {
msiEndpoint: credentials.msiEndpoint
});
} else if (credentials instanceof MSITokenCredentials) {
throw new Error("MSI-credentials not one of: MSIVmTokenCredentials, MSIAppServiceTokenCredentials");
throw new Error(
"MSI-credentials not one of: MSIVmTokenCredentials, MSIAppServiceTokenCredentials"
);
} else {
return credentials;
}
}
function _createAuthenticatorMapper(credentials: MSITokenCredentials): Authenticator {
return (challenge: any) => new Promise((resolve, reject) => {
// Function to take token Response and format a authorization value
const _formAuthorizationValue = (err: Error, tokenResponse: TokenResponse | ErrorResponse) => {
if (err) {
return reject(err);
}
return (challenge: any) =>
new Promise((resolve, reject) => {
// Function to take token Response and format a authorization value
const _formAuthorizationValue = (
err: Error,
tokenResponse: TokenResponse | ErrorResponse
) => {
if (err) {
return reject(err);
}
if (tokenResponse.error) {
return reject(tokenResponse.error);
}
if (tokenResponse.error) {
return reject(tokenResponse.error);
}
tokenResponse = tokenResponse as TokenResponse;
// Calculate the value to be set in the request's Authorization header and resume the call.
const authorizationValue = tokenResponse.tokenType + " " + tokenResponse.accessToken;
return resolve(authorizationValue);
};
tokenResponse = tokenResponse as TokenResponse;
// Calculate the value to be set in the request's Authorization header and resume the call.
const authorizationValue = tokenResponse.tokenType + " " + tokenResponse.accessToken;
return resolve(authorizationValue);
};
// Create a new authentication context.
if (credentials instanceof TokenCredentialsBase) {
const context = new AuthenticationContext(challenge.authorization, true, credentials.authContext && credentials.authContext.cache);
if (credentials instanceof ApplicationTokenCredentials) {
return context.acquireTokenWithClientCredentials(
challenge.resource, credentials.clientId, credentials.secret, _formAuthorizationValue);
} else if (credentials instanceof ApplicationTokenCertificateCredentials) {
return context.acquireTokenWithClientCertificate(
challenge.resource, credentials.clientId, credentials.certificate, credentials.thumbprint, _formAuthorizationValue);
} else if (credentials instanceof UserTokenCredentials) {
return context.acquireTokenWithUsernamePassword(
challenge.resource, credentials.username, credentials.password, credentials.clientId, _formAuthorizationValue);
} else if (credentials instanceof DeviceTokenCredentials) {
return context.acquireToken(
challenge.resource, credentials.username, credentials.clientId, _formAuthorizationValue);
// Create a new authentication context.
if (credentials instanceof TokenCredentialsBase) {
const context = new AuthenticationContext(
challenge.authorization,
true,
credentials.authContext && credentials.authContext.cache
);
if (credentials instanceof ApplicationTokenCredentials) {
return context.acquireTokenWithClientCredentials(
challenge.resource,
credentials.clientId,
credentials.secret,
_formAuthorizationValue
);
} else if (credentials instanceof ApplicationTokenCertificateCredentials) {
return context.acquireTokenWithClientCertificate(
challenge.resource,
credentials.clientId,
credentials.certificate,
credentials.thumbprint,
_formAuthorizationValue
);
} else if (credentials instanceof UserTokenCredentials) {
return context.acquireTokenWithUsernamePassword(
challenge.resource,
credentials.username,
credentials.password,
credentials.clientId,
_formAuthorizationValue
);
} else if (credentials instanceof DeviceTokenCredentials) {
return context.acquireToken(
challenge.resource,
credentials.username,
credentials.clientId,
_formAuthorizationValue
);
}
} else if (credentials instanceof MSITokenCredentials) {
return credentials.getToken();
} else {
return reject(
new Error(
"credentials must be one of: ApplicationTokenCredentials, UserTokenCredentials, " +
"DeviceTokenCredentials, MSITokenCredentials"
)
);
}
} else if (credentials instanceof MSITokenCredentials) {
return credentials.getToken();
} else {
return reject(new Error("credentials must be one of: ApplicationTokenCredentials, UserTokenCredentials, " +
"DeviceTokenCredentials, MSITokenCredentials"));
}
});
});
}

Просмотреть файл

@ -70,16 +70,28 @@ export class MSIAppServiceTokenCredentials extends MSITokenCredentials {
constructor(options?: MSIAppServiceOptions) {
if (!options) options = {};
super(options);
options.msiEndpoint = options.msiEndpoint || process.env["IDENTITY_ENDPOINT"] || process.env["MSI_ENDPOINT"];
options.msiSecret = options.msiSecret || process.env["IDENTITY_SECRET"] || process.env["MSI_SECRET"];
if (!options.msiEndpoint || (options.msiEndpoint && typeof options.msiEndpoint.valueOf() !== "string")) {
throw new Error('Either provide "msiEndpoint" as a property of the "options" object ' +
'or set the environment variable "IDENTITY_ENDPOINT" or "MSI_ENDPOINT" and it must be of type "string".');
options.msiEndpoint =
options.msiEndpoint || process.env["IDENTITY_ENDPOINT"] || process.env["MSI_ENDPOINT"];
options.msiSecret =
options.msiSecret || process.env["IDENTITY_SECRET"] || process.env["MSI_SECRET"];
if (
!options.msiEndpoint ||
(options.msiEndpoint && typeof options.msiEndpoint.valueOf() !== "string")
) {
throw new Error(
'Either provide "msiEndpoint" as a property of the "options" object ' +
'or set the environment variable "IDENTITY_ENDPOINT" or "MSI_ENDPOINT" and it must be of type "string".'
);
}
if (!options.msiSecret || (options.msiSecret && typeof options.msiSecret.valueOf() !== "string")) {
throw new Error('Either provide "msiSecret" as a property of the "options" object ' +
'or set the environment variable "IDENTITY_SECRET" or "MSI_SECRET" and it must be of type "string".');
if (
!options.msiSecret ||
(options.msiSecret && typeof options.msiSecret.valueOf() !== "string")
) {
throw new Error(
'Either provide "msiSecret" as a property of the "options" object ' +
'or set the environment variable "IDENTITY_SECRET" or "MSI_SECRET" and it must be of type "string".'
);
}
if (!options.msiApiVersion) {
@ -103,14 +115,20 @@ export class MSIAppServiceTokenCredentials extends MSITokenCredentials {
const opRes = await this._httpClient.sendRequest(reqOptions);
if (opRes.bodyAsText === undefined || opRes.bodyAsText!.indexOf("ExceptionMessage") !== -1) {
throw new Error(`MSI: Failed to retrieve a token from "${reqOptions.url}" with an error: ${opRes.bodyAsText}`);
throw new Error(
`MSI: Failed to retrieve a token from "${reqOptions.url}" with an error: ${opRes.bodyAsText}`
);
}
const result = this.parseTokenResponse(opRes.bodyAsText!) as MSITokenResponse;
if (!result.tokenType) {
throw new Error(`Invalid token response, did not find tokenType. Response body is: ${opRes.bodyAsText}`);
throw new Error(
`Invalid token response, did not find tokenType. Response body is: ${opRes.bodyAsText}`
);
} else if (!result.accessToken) {
throw new Error(`Invalid token response, did not find accessToken. Response body is: ${opRes.bodyAsText}`);
throw new Error(
`Invalid token response, did not find accessToken. Response body is: ${opRes.bodyAsText}`
);
}
return result;
@ -121,14 +139,14 @@ export class MSIAppServiceTokenCredentials extends MSITokenCredentials {
const reqOptions: RequestPrepareOptions = {
url: endpoint,
headers: {
secret: this.msiSecret,
secret: this.msiSecret
},
queryParameters: {
"resource": this.resource,
resource: this.resource,
"api-version": this.msiApiVersion,
"clientid": this.clientId,
clientid: this.clientId
},
method: "GET",
method: "GET"
};
const webResource = new WebResource();

Просмотреть файл

@ -144,7 +144,10 @@ export abstract class MSITokenCredentials implements TokenClientCredentials {
*/
public async signRequest(webResource: WebResource): Promise<WebResource> {
const tokenResponse = await this.getToken();
webResource.headers.set(Constants.HeaderConstants.AUTHORIZATION, `${tokenResponse.tokenType} ${tokenResponse.accessToken}`);
webResource.headers.set(
Constants.HeaderConstants.AUTHORIZATION,
`${tokenResponse.tokenType} ${tokenResponse.accessToken}`
);
return webResource;
}
}

Просмотреть файл

@ -97,12 +97,15 @@ export class MSIVmTokenCredentials extends MSITokenCredentials {
const opRes = await this._httpClient.sendRequest(reqOptions);
const result = this.parseTokenResponse(opRes.bodyAsText!) as MSITokenResponse;
if (!result.tokenType) {
throw new Error(`Invalid token response, did not find tokenType. Response body is: ${opRes.bodyAsText}`);
throw new Error(
`Invalid token response, did not find tokenType. Response body is: ${opRes.bodyAsText}`
);
} else if (!result.accessToken) {
throw new Error(`Invalid token response, did not find accessToken. Response body is: ${opRes.bodyAsText}`);
throw new Error(
`Invalid token response, did not find accessToken. Response body is: ${opRes.bodyAsText}`
);
}
return result;
}
@ -111,15 +114,15 @@ export class MSIVmTokenCredentials extends MSITokenCredentials {
url: this.msiEndpoint,
headers: {
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
"Metadata": "true"
Metadata: "true"
},
method: this.httpMethod,
queryParameters: {
"api-version": this.apiVersion,
"resource": this.resource,
"object_id": this.objectId,
"client_id": this.clientId,
"mi_res_id": this.identityId
resource: this.resource,
object_id: this.objectId,
client_id: this.clientId,
mi_res_id: this.identityId
}
};

Просмотреть файл

@ -10,7 +10,7 @@ import {
AuthenticationContext,
MemoryCache,
ErrorResponse,
TokenCache,
TokenCache
} from "adal-node";
export abstract class TokenCredentialsBase implements TokenClientCredentials {
@ -31,18 +31,14 @@ export abstract class TokenCredentialsBase implements TokenClientCredentials {
throw new Error("domain must be a non empty string.");
}
if (
this.tokenAudience === "graph" &&
this.domain.toLowerCase() === "common"
) {
if (this.tokenAudience === "graph" && this.domain.toLowerCase() === "common") {
throw new Error(
`${'If the tokenAudience is specified as "graph" then "domain" cannot be defaulted to "common" tenant.\
It must be the actual tenant (preferably a string in a guid format).'}`
);
}
const authorityUrl =
this.environment.activeDirectoryEndpointUrl + this.domain;
const authorityUrl = this.environment.activeDirectoryEndpointUrl + this.domain;
this.authContext = new AuthenticationContext(
authorityUrl,
this.environment.validateAuthority,
@ -52,8 +48,7 @@ export abstract class TokenCredentialsBase implements TokenClientCredentials {
public setDomain(domain: string): void {
this.domain = domain;
const authorityUrl =
this.environment.activeDirectoryEndpointUrl + this.domain;
const authorityUrl = this.environment.activeDirectoryEndpointUrl + this.domain;
this.authContext = new AuthenticationContext(
authorityUrl,
this.environment.validateAuthority,

Просмотреть файл

@ -7,7 +7,6 @@ import { TokenAudience } from "../util/authConstants";
import { TokenResponse, ErrorResponse, TokenCache } from "adal-node";
export class UserTokenCredentials extends TokenCredentialsBase {
readonly username: string;
readonly password: string;
@ -33,8 +32,8 @@ export class UserTokenCredentials extends TokenCredentialsBase {
password: string,
tokenAudience?: TokenAudience,
environment?: Environment,
tokenCache?: TokenCache) {
tokenCache?: TokenCache
) {
if (!clientId || typeof clientId.valueOf() !== "string") {
throw new Error("clientId must be a non empty string.");
}
@ -60,7 +59,7 @@ export class UserTokenCredentials extends TokenCredentialsBase {
private crossCheckUserNameWithToken(username: string, userIdFromToken: string): boolean {
// to maintain the casing consistency between "azureprofile.json" and token cache. (RD 1996587)
// use the "userId" here, which should be the same with "username" except the casing.
return (username.toLowerCase() === userIdFromToken.toLowerCase());
return username.toLowerCase() === userIdFromToken.toLowerCase();
}
/**
@ -76,7 +75,11 @@ export class UserTokenCredentials extends TokenCredentialsBase {
const resource = this.getActiveDirectoryResourceId();
return new Promise<TokenResponse>((resolve, reject) => {
self.authContext.acquireTokenWithUsernamePassword(resource, self.username, self.password, self.clientId,
self.authContext.acquireTokenWithUsernamePassword(
resource,
self.username,
self.password,
self.clientId,
(error: Error, tokenResponse: TokenResponse | ErrorResponse) => {
if (error) {
return reject(error);
@ -88,12 +91,15 @@ export class UserTokenCredentials extends TokenCredentialsBase {
tokenResponse = tokenResponse as TokenResponse;
if (self.crossCheckUserNameWithToken(self.username, tokenResponse.userId!)) {
return resolve((tokenResponse as TokenResponse));
return resolve(tokenResponse as TokenResponse);
} else {
return reject(`The userId "${tokenResponse.userId}" in access token doesn't match the username "${self.username}" provided during authentication.`);
return reject(
`The userId "${tokenResponse.userId}" in access token doesn't match the username "${self.username}" provided during authentication.`
);
}
});
}
);
});
}
}
}
}

Просмотреть файл

@ -12,9 +12,16 @@ import { ApplicationTokenCertificateCredentials } from "./credentials/applicatio
import { DeviceTokenCredentials } from "./credentials/deviceTokenCredentials";
import { UserTokenCredentials } from "./credentials/userTokenCredentials";
import { AuthConstants, TokenAudience } from "./util/authConstants";
import { buildTenantList, getSubscriptionsFromTenants, LinkedSubscription } from "./subscriptionManagement/subscriptionUtils";
import {
buildTenantList,
getSubscriptionsFromTenants,
LinkedSubscription
} from "./subscriptionManagement/subscriptionUtils";
import { MSIVmTokenCredentials, MSIVmOptions } from "./credentials/msiVmTokenCredentials";
import { MSIAppServiceTokenCredentials, MSIAppServiceOptions } from "./credentials/msiAppServiceTokenCredentials";
import {
MSIAppServiceTokenCredentials,
MSIAppServiceOptions
} from "./credentials/msiAppServiceTokenCredentials";
import { MSITokenResponse } from "./credentials/msiTokenCredentials";
/**
@ -36,17 +43,16 @@ const managementPlaneTokenAudiences = [
function turnOnLogging() {
const log = adal.Logging;
log.setLoggingOptions(
{
level: 3, // Please use log.LOGGING_LEVEL.VERBOSE once AD TypeScript mappings are updated,
log: function (level: any, message: any, error: any) {
level;
console.info(message);
if (error) {
console.error(error);
}
log.setLoggingOptions({
level: 3, // Please use log.LOGGING_LEVEL.VERBOSE once AD TypeScript mappings are updated,
log: function (level: any, message: any, error: any) {
level;
console.info(message);
if (error) {
console.error(error);
}
});
}
});
}
if (process.env["AZURE_ADAL_LOGGING_ENABLED"]) {
@ -160,7 +166,11 @@ export type Callback<TResult> = (error?: Error, result?: TResult) => void;
*
* @returns A Promise that resolves to AuthResponse, which contains `credentials` and an optional `subscriptions` array, and rejects with an Error.
*/
export async function withUsernamePasswordWithAuthResponse(username: string, password: string, options?: LoginWithUsernamePasswordOptions): Promise<AuthResponse> {
export async function withUsernamePasswordWithAuthResponse(
username: string,
password: string,
options?: LoginWithUsernamePasswordOptions
): Promise<AuthResponse> {
if (!options) {
options = {};
}
@ -174,7 +184,15 @@ export async function withUsernamePasswordWithAuthResponse(username: string, pas
options.environment = Environment.AzureCloud;
}
const creds = new UserTokenCredentials(options.clientId, options.domain, username, password, options.tokenAudience, options.environment, options.tokenCache);
const creds = new UserTokenCredentials(
options.clientId,
options.domain,
username,
password,
options.tokenAudience,
options.environment,
options.tokenCache
);
const tokenResponse = await creds.getToken();
// The token cache gets propulated for all the tenants as a part of building the tenantList.
@ -183,7 +201,11 @@ export async function withUsernamePasswordWithAuthResponse(username: string, pas
tenantList = [tokenResponse.tenantId];
}
const subscriptionList: LinkedSubscription[] = await _getSubscriptions(creds, tenantList, options.tokenAudience);
const subscriptionList: LinkedSubscription[] = await _getSubscriptions(
creds,
tenantList,
options.tokenAudience
);
return { credentials: creds, subscriptions: subscriptionList };
}
@ -206,7 +228,12 @@ export async function withUsernamePasswordWithAuthResponse(username: string, pas
*
* @returns A Promise that resolves to AuthResponse, which contains "credentials" and optional "subscriptions" array and rejects with an Error.
*/
export async function withServicePrincipalSecretWithAuthResponse(clientId: string, secret: string, domain: string, options?: AzureTokenCredentialsOptions): Promise<AuthResponse> {
export async function withServicePrincipalSecretWithAuthResponse(
clientId: string,
secret: string,
domain: string,
options?: AzureTokenCredentialsOptions
): Promise<AuthResponse> {
if (!options) {
options = {};
}
@ -214,7 +241,14 @@ export async function withServicePrincipalSecretWithAuthResponse(clientId: strin
options.environment = Environment.AzureCloud;
}
const creds = new ApplicationTokenCredentials(clientId, domain, secret, options.tokenAudience, options.environment, options.tokenCache);
const creds = new ApplicationTokenCredentials(
clientId,
domain,
secret,
options.tokenAudience,
options.environment,
options.tokenCache
);
await creds.getToken();
const subscriptionList = await _getSubscriptions(creds, [domain], options.tokenAudience);
@ -242,7 +276,12 @@ export async function withServicePrincipalSecretWithAuthResponse(clientId: strin
*
* @returns A Promise that resolves to AuthResponse, which contains "credentials" and optional "subscriptions" array and rejects with an Error.
*/
export async function withServicePrincipalCertificateWithAuthResponse(clientId: string, certificateStringOrFilePath: string, domain: string, options?: AzureTokenCredentialsOptions): Promise<AuthResponse> {
export async function withServicePrincipalCertificateWithAuthResponse(
clientId: string,
certificateStringOrFilePath: string,
domain: string,
options?: AzureTokenCredentialsOptions
): Promise<AuthResponse> {
if (!options) {
options = {};
}
@ -250,7 +289,12 @@ export async function withServicePrincipalCertificateWithAuthResponse(clientId:
options.environment = Environment.AzureCloud;
}
const creds = ApplicationTokenCertificateCredentials.create(clientId, certificateStringOrFilePath, domain, options);
const creds = ApplicationTokenCertificateCredentials.create(
clientId,
certificateStringOrFilePath,
domain,
options
);
await creds.getToken();
const subscriptionList = await _getSubscriptions(creds, [domain], options.tokenAudience);
@ -269,7 +313,9 @@ function validateAuthFileContent(credsObj: any, filePath: string) {
throw new Error(`"clientId" is missing from the auth file: ${filePath}.`);
}
if (!credsObj.clientSecret && !credsObj.clientCertificate) {
throw new Error(`Either "clientSecret" or "clientCertificate" must be present in the auth file: ${filePath}.`);
throw new Error(
`Either "clientSecret" or "clientCertificate" must be present in the auth file: ${filePath}.`
);
}
if (!credsObj.subscriptionId) {
throw new Error(`"subscriptionId" is missing from the auth file: ${filePath}.`);
@ -302,7 +348,7 @@ function foundManagementEndpointUrl(authFileUrl: string, envUrl: string): boolea
authFileUrl = authFileUrl.endsWith("/") ? authFileUrl.slice(0, -1) : authFileUrl;
envUrl = envUrl.endsWith("/") ? envUrl.slice(0, -1) : envUrl;
return (authFileUrl.toLowerCase() === envUrl.toLowerCase());
return authFileUrl.toLowerCase() === envUrl.toLowerCase();
}
/**
@ -330,15 +376,19 @@ function foundManagementEndpointUrl(authFileUrl: string, envUrl: string): boolea
*
* @returns A Promise that resolves to AuthResponse, which contains "credentials" and optional "subscriptions" array and rejects with an Error.
*/
export async function withAuthFileWithAuthResponse(options?: LoginWithAuthFileOptions): Promise<AuthResponse> {
export async function withAuthFileWithAuthResponse(
options?: LoginWithAuthFileOptions
): Promise<AuthResponse> {
if (!options) options = { filePath: "" };
const filePath = options.filePath || process.env[AuthConstants.AZURE_AUTH_LOCATION];
const subscriptionEnvVariableName = options.subscriptionEnvVariableName || "AZURE_SUBSCRIPTION_ID";
const subscriptionEnvVariableName =
options.subscriptionEnvVariableName || "AZURE_SUBSCRIPTION_ID";
if (!filePath) {
const msg = `Either provide an absolute file path to the auth file or set/export the environment variable - ${AuthConstants.AZURE_AUTH_LOCATION}.`;
throw new Error(msg);
}
let content: string, credsObj: any = {};
let content: string,
credsObj: any = {};
const optionsForSp: any = {};
content = readFileSync(filePath, { encoding: "utf8" });
@ -358,9 +408,14 @@ export async function withAuthFileWithAuthResponse(options?: LoginWithAuthFileOp
for (let i = 0; i < envNames.length; i++) {
const env = envNames[i];
const environmentObj = (Environment as any)[env];
if (environmentObj &&
if (
environmentObj &&
environmentObj.managementEndpointUrl &&
foundManagementEndpointUrl(credsObj.managementEndpointUrl, environmentObj.managementEndpointUrl)) {
foundManagementEndpointUrl(
credsObj.managementEndpointUrl,
environmentObj.managementEndpointUrl
)
) {
envFound.name = environmentObj.name;
break;
}
@ -376,7 +431,9 @@ export async function withAuthFileWithAuthResponse(options?: LoginWithAuthFileOp
const keys = Object.keys(credsObj);
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
if (key.match(/^(clientId|clientSecret|clientCertificate|subscriptionId|tenantId)$/ig) === null) {
if (
key.match(/^(clientId|clientSecret|clientCertificate|subscriptionId|tenantId)$/gi) === null
) {
if (key === "activeDirectoryEndpointUrl" && !key.endsWith("/")) {
envParams[key] = credsObj[key] + "/";
} else {
@ -393,13 +450,22 @@ export async function withAuthFileWithAuthResponse(options?: LoginWithAuthFileOp
optionsForSp.environment = Environment.add(envParams);
}
if (credsObj.clientSecret) {
return withServicePrincipalSecretWithAuthResponse(credsObj.clientId, credsObj.clientSecret, credsObj.tenantId, optionsForSp);
return withServicePrincipalSecretWithAuthResponse(
credsObj.clientId,
credsObj.clientSecret,
credsObj.tenantId,
optionsForSp
);
}
return withServicePrincipalCertificateWithAuthResponse(credsObj.clientId, credsObj.clientCertificate, credsObj.tenantId, optionsForSp);
return withServicePrincipalCertificateWithAuthResponse(
credsObj.clientId,
credsObj.clientCertificate,
credsObj.tenantId,
optionsForSp
);
}
/**
* Provides a url and code that needs to be copy and pasted in a browser and authenticated over there. If successful, the user will get a DeviceTokenCredentials object and the list of subscriptions associated with that userId across all the applicable tenants.
*
@ -429,7 +495,9 @@ export async function withAuthFileWithAuthResponse(options?: LoginWithAuthFileOp
*
* @returns A Promise that resolves to AuthResponse, which contains "credentials" and optional "subscriptions" array and rejects with an Error.
*/
export async function withInteractiveWithAuthResponse(options?: InteractiveLoginOptions): Promise<AuthResponse> {
export async function withInteractiveWithAuthResponse(
options?: InteractiveLoginOptions
): Promise<AuthResponse> {
if (!options) {
options = {};
}
@ -465,32 +533,42 @@ export async function withInteractiveWithAuthResponse(options?: InteractiveLogin
interactiveOptions.tokenCache = options.tokenCache;
interactiveOptions.language = options.language;
interactiveOptions.userCodeResponseLogger = options.userCodeResponseLogger;
const authorityUrl: string = interactiveOptions.environment.activeDirectoryEndpointUrl + interactiveOptions.domain;
const authContext = new adal.AuthenticationContext(authorityUrl, interactiveOptions.environment.validateAuthority, interactiveOptions.tokenCache);
const authorityUrl: string =
interactiveOptions.environment.activeDirectoryEndpointUrl + interactiveOptions.domain;
const authContext = new adal.AuthenticationContext(
authorityUrl,
interactiveOptions.environment.validateAuthority,
interactiveOptions.tokenCache
);
interactiveOptions.context = authContext;
function tryAcquireToken(interactiveOptions: InteractiveLoginOptions, resolve: any, reject: any) {
authContext.acquireUserCode(interactiveOptions.tokenAudience!, interactiveOptions.clientId!, interactiveOptions.language!, (err: any, userCodeRes: adal.UserCodeInfo) => {
if (err) {
if (err.error === "authorization_pending") {
setTimeout(() => {
tryAcquireToken(interactiveOptions, resolve, reject);
}, 1000);
} else {
reject(err);
authContext.acquireUserCode(
interactiveOptions.tokenAudience!,
interactiveOptions.clientId!,
interactiveOptions.language!,
(err: any, userCodeRes: adal.UserCodeInfo) => {
if (err) {
if (err.error === "authorization_pending") {
setTimeout(() => {
tryAcquireToken(interactiveOptions, resolve, reject);
}, 1000);
} else {
reject(err);
}
return;
}
return;
}
if (interactiveOptions.userCodeResponseLogger) {
interactiveOptions.userCodeResponseLogger(userCodeRes.message);
} else {
console.log(userCodeRes.message);
}
if (interactiveOptions.userCodeResponseLogger) {
interactiveOptions.userCodeResponseLogger(userCodeRes.message);
} else {
console.log(userCodeRes.message);
return resolve(userCodeRes);
}
return resolve(userCodeRes);
});
);
}
const getUserCode = new Promise<adal.UserCodeInfo>((resolve, reject) => {
@ -499,24 +577,35 @@ export async function withInteractiveWithAuthResponse(options?: InteractiveLogin
const userCodeResponse = await getUserCode;
const creds = await new Promise<DeviceTokenCredentials>((resolve, reject) => {
return authContext.acquireTokenWithDeviceCode(interactiveOptions.tokenAudience, interactiveOptions.clientId, userCodeResponse, (error, tokenResponse) => {
if (error) {
return reject(error);
}
return authContext.acquireTokenWithDeviceCode(
interactiveOptions.tokenAudience,
interactiveOptions.clientId,
userCodeResponse,
(error, tokenResponse) => {
if (error) {
return reject(error);
}
const response = tokenResponse as adal.TokenResponse;
interactiveOptions.userName = response.userId;
interactiveOptions.authorizationScheme = response.tokenType;
const response = tokenResponse as adal.TokenResponse;
interactiveOptions.userName = response.userId;
interactiveOptions.authorizationScheme = response.tokenType;
let creds;
try {
creds = new DeviceTokenCredentials(interactiveOptions.clientId, interactiveOptions.domain, interactiveOptions.userName,
interactiveOptions.tokenAudience, interactiveOptions.environment, interactiveOptions.tokenCache);
} catch (err) {
return reject(err);
let creds;
try {
creds = new DeviceTokenCredentials(
interactiveOptions.clientId,
interactiveOptions.domain,
interactiveOptions.userName,
interactiveOptions.tokenAudience,
interactiveOptions.environment,
interactiveOptions.tokenCache
);
} catch (err) {
return reject(err);
}
return resolve(creds);
}
return resolve(creds);
});
);
});
const tenants = await buildTenantList(creds);
@ -560,9 +649,27 @@ export async function withInteractiveWithAuthResponse(options?: InteractiveLogin
*/
export function withAuthFile(): Promise<TokenCredentialsBase>;
export function withAuthFile(options: LoginWithAuthFileOptions): Promise<TokenCredentialsBase>;
export function withAuthFile(options: LoginWithAuthFileOptions, callback: { (err: Error, credentials: ApplicationTokenCredentials, subscriptions: Array<LinkedSubscription>): void }): void;
export function withAuthFile(
options: LoginWithAuthFileOptions,
callback: {
(
err: Error,
credentials: ApplicationTokenCredentials,
subscriptions: Array<LinkedSubscription>
): void;
}
): void;
export function withAuthFile(callback: any): void;
export function withAuthFile(options?: LoginWithAuthFileOptions, callback?: { (err: Error, credentials: ApplicationTokenCredentials, subscriptions: Array<LinkedSubscription>): void }): any {
export function withAuthFile(
options?: LoginWithAuthFileOptions,
callback?: {
(
err: Error,
credentials: ApplicationTokenCredentials,
subscriptions: Array<LinkedSubscription>
): void;
}
): any {
if (!callback && typeof options === "function") {
callback = options;
options = undefined;
@ -573,12 +680,14 @@ export function withAuthFile(options?: LoginWithAuthFileOptions, callback?: { (e
return authRes.credentials;
});
} else {
msRest.promiseToCallback(withAuthFileWithAuthResponse(options))((err: Error, authRes: AuthResponse) => {
if (err) {
return cb(err);
msRest.promiseToCallback(withAuthFileWithAuthResponse(options))(
(err: Error, authRes: AuthResponse) => {
if (err) {
return cb(err);
}
return cb(undefined, authRes.credentials, authRes.subscriptions);
}
return cb(undefined, authRes.credentials, authRes.subscriptions);
});
);
}
}
@ -613,9 +722,27 @@ export function withAuthFile(options?: LoginWithAuthFileOptions, callback?: { (e
*/
export function interactive(): Promise<DeviceTokenCredentials>;
export function interactive(options: InteractiveLoginOptions): Promise<DeviceTokenCredentials>;
export function interactive(options: InteractiveLoginOptions, callback: { (err: Error, credentials: DeviceTokenCredentials, subscriptions: Array<LinkedSubscription>): void }): void;
export function interactive(
options: InteractiveLoginOptions,
callback: {
(
err: Error,
credentials: DeviceTokenCredentials,
subscriptions: Array<LinkedSubscription>
): void;
}
): void;
export function interactive(callback: any): void;
export function interactive(options?: InteractiveLoginOptions, callback?: { (err: Error, credentials: DeviceTokenCredentials, subscriptions: Array<LinkedSubscription>): void }): any {
export function interactive(
options?: InteractiveLoginOptions,
callback?: {
(
err: Error,
credentials: DeviceTokenCredentials,
subscriptions: Array<LinkedSubscription>
): void;
}
): any {
if (!callback && typeof options === "function") {
callback = options;
options = undefined;
@ -626,12 +753,14 @@ export function interactive(options?: InteractiveLoginOptions, callback?: { (err
return authRes.credentials;
});
} else {
msRest.promiseToCallback(withInteractiveWithAuthResponse(options))((err: Error, authRes: AuthResponse) => {
if (err) {
return cb(err);
msRest.promiseToCallback(withInteractiveWithAuthResponse(options))(
(err: Error, authRes: AuthResponse) => {
if (err) {
return cb(err);
}
return cb(undefined, authRes.credentials, authRes.subscriptions);
}
return cb(undefined, authRes.credentials, authRes.subscriptions);
});
);
}
}
@ -662,22 +791,64 @@ export function interactive(options?: InteractiveLoginOptions, callback?: { (err
* @resolve {ApplicationTokenCredentials} The ApplicationTokenCredentials object.
* @reject {Error} - The error object.
*/
export function withServicePrincipalSecret(clientId: string, secret: string, domain: string): Promise<ApplicationTokenCredentials>;
export function withServicePrincipalSecret(clientId: string, secret: string, domain: string, options: AzureTokenCredentialsOptions): Promise<ApplicationTokenCredentials>;
export function withServicePrincipalSecret(clientId: string, secret: string, domain: string, options: AzureTokenCredentialsOptions, callback: { (err: Error, credentials: ApplicationTokenCredentials, subscriptions: Array<LinkedSubscription>): void }): void;
export function withServicePrincipalSecret(clientId: string, secret: string, domain: string, callback: any): void;
export function withServicePrincipalSecret(clientId: string, secret: string, domain: string, options?: AzureTokenCredentialsOptions, callback?: { (err: Error, credentials: ApplicationTokenCredentials, subscriptions: Array<LinkedSubscription>): void }): any {
export function withServicePrincipalSecret(
clientId: string,
secret: string,
domain: string
): Promise<ApplicationTokenCredentials>;
export function withServicePrincipalSecret(
clientId: string,
secret: string,
domain: string,
options: AzureTokenCredentialsOptions
): Promise<ApplicationTokenCredentials>;
export function withServicePrincipalSecret(
clientId: string,
secret: string,
domain: string,
options: AzureTokenCredentialsOptions,
callback: {
(
err: Error,
credentials: ApplicationTokenCredentials,
subscriptions: Array<LinkedSubscription>
): void;
}
): void;
export function withServicePrincipalSecret(
clientId: string,
secret: string,
domain: string,
callback: any
): void;
export function withServicePrincipalSecret(
clientId: string,
secret: string,
domain: string,
options?: AzureTokenCredentialsOptions,
callback?: {
(
err: Error,
credentials: ApplicationTokenCredentials,
subscriptions: Array<LinkedSubscription>
): void;
}
): any {
if (!callback && typeof options === "function") {
callback = options;
options = undefined;
}
const cb = callback as Function;
if (!callback) {
return withServicePrincipalSecretWithAuthResponse(clientId, secret, domain, options).then((authRes) => {
return authRes.credentials;
});
return withServicePrincipalSecretWithAuthResponse(clientId, secret, domain, options).then(
(authRes) => {
return authRes.credentials;
}
);
} else {
msRest.promiseToCallback(withServicePrincipalSecretWithAuthResponse(clientId, secret, domain, options))((err: Error, authRes: AuthResponse) => {
msRest.promiseToCallback(
withServicePrincipalSecretWithAuthResponse(clientId, secret, domain, options)
)((err: Error, authRes: AuthResponse) => {
if (err) {
return cb(err);
}
@ -715,22 +886,72 @@ export function withServicePrincipalSecret(clientId: string, secret: string, dom
* @resolve {ApplicationTokenCertificateCredentials} The ApplicationTokenCertificateCredentials object.
* @reject {Error} - The error object.
*/
export function withServicePrincipalCertificate(clientId: string, certificateStringOrFilePath: string, domain: string): Promise<ApplicationTokenCertificateCredentials>;
export function withServicePrincipalCertificate(clientId: string, certificateStringOrFilePath: string, domain: string, options: AzureTokenCredentialsOptions): Promise<ApplicationTokenCredentials>;
export function withServicePrincipalCertificate(clientId: string, certificateStringOrFilePath: string, domain: string, options: AzureTokenCredentialsOptions, callback: { (err: Error, credentials: ApplicationTokenCertificateCredentials, subscriptions: Array<LinkedSubscription>): void }): void;
export function withServicePrincipalCertificate(clientId: string, certificateStringOrFilePath: string, domain: string, callback: any): void;
export function withServicePrincipalCertificate(clientId: string, certificateStringOrFilePath: string, domain: string, options?: AzureTokenCredentialsOptions, callback?: { (err: Error, credentials: ApplicationTokenCertificateCredentials, subscriptions: Array<LinkedSubscription>): void }): any {
export function withServicePrincipalCertificate(
clientId: string,
certificateStringOrFilePath: string,
domain: string
): Promise<ApplicationTokenCertificateCredentials>;
export function withServicePrincipalCertificate(
clientId: string,
certificateStringOrFilePath: string,
domain: string,
options: AzureTokenCredentialsOptions
): Promise<ApplicationTokenCredentials>;
export function withServicePrincipalCertificate(
clientId: string,
certificateStringOrFilePath: string,
domain: string,
options: AzureTokenCredentialsOptions,
callback: {
(
err: Error,
credentials: ApplicationTokenCertificateCredentials,
subscriptions: Array<LinkedSubscription>
): void;
}
): void;
export function withServicePrincipalCertificate(
clientId: string,
certificateStringOrFilePath: string,
domain: string,
callback: any
): void;
export function withServicePrincipalCertificate(
clientId: string,
certificateStringOrFilePath: string,
domain: string,
options?: AzureTokenCredentialsOptions,
callback?: {
(
err: Error,
credentials: ApplicationTokenCertificateCredentials,
subscriptions: Array<LinkedSubscription>
): void;
}
): any {
if (!callback && typeof options === "function") {
callback = options;
options = undefined;
}
const cb = callback as Function;
if (!callback) {
return withServicePrincipalCertificateWithAuthResponse(clientId, certificateStringOrFilePath, domain, options).then((authRes) => {
return withServicePrincipalCertificateWithAuthResponse(
clientId,
certificateStringOrFilePath,
domain,
options
).then((authRes) => {
return authRes.credentials;
});
} else {
msRest.promiseToCallback(withServicePrincipalCertificateWithAuthResponse(clientId, certificateStringOrFilePath, domain, options))((err: Error, authRes: AuthResponse) => {
msRest.promiseToCallback(
withServicePrincipalCertificateWithAuthResponse(
clientId,
certificateStringOrFilePath,
domain,
options
)
)((err: Error, authRes: AuthResponse) => {
if (err) {
return cb(err);
}
@ -769,11 +990,32 @@ export function withServicePrincipalCertificate(clientId: string, certificateStr
* @resolve {UserTokenCredentials} The UserTokenCredentials object.
* @reject {Error} - The error object.
*/
export function withUsernamePassword(username: string, password: string): Promise<UserTokenCredentials>;
export function withUsernamePassword(username: string, password: string, options: LoginWithUsernamePasswordOptions): Promise<UserTokenCredentials>;
export function withUsernamePassword(
username: string,
password: string
): Promise<UserTokenCredentials>;
export function withUsernamePassword(
username: string,
password: string,
options: LoginWithUsernamePasswordOptions
): Promise<UserTokenCredentials>;
export function withUsernamePassword(username: string, password: string, callback: any): void;
export function withUsernamePassword(username: string, password: string, options: LoginWithUsernamePasswordOptions, callback: { (err: Error, credentials: UserTokenCredentials, subscriptions: Array<LinkedSubscription>): void }): void;
export function withUsernamePassword(username: string, password: string, options?: LoginWithUsernamePasswordOptions, callback?: { (err: Error, credentials: UserTokenCredentials, subscriptions: Array<LinkedSubscription>): void }): any {
export function withUsernamePassword(
username: string,
password: string,
options: LoginWithUsernamePasswordOptions,
callback: {
(err: Error, credentials: UserTokenCredentials, subscriptions: Array<LinkedSubscription>): void;
}
): void;
export function withUsernamePassword(
username: string,
password: string,
options?: LoginWithUsernamePasswordOptions,
callback?: {
(err: Error, credentials: UserTokenCredentials, subscriptions: Array<LinkedSubscription>): void;
}
): any {
if (!callback && typeof options === "function") {
callback = options;
options = undefined;
@ -784,12 +1026,14 @@ export function withUsernamePassword(username: string, password: string, options
return authRes.credentials;
});
} else {
msRest.promiseToCallback(withUsernamePasswordWithAuthResponse(username, password, options))((err: Error, authRes: AuthResponse) => {
if (err) {
return cb(err);
msRest.promiseToCallback(withUsernamePasswordWithAuthResponse(username, password, options))(
(err: Error, authRes: AuthResponse) => {
if (err) {
return cb(err);
}
return cb(undefined, authRes.credentials, authRes.subscriptions);
}
return cb(undefined, authRes.credentials, authRes.subscriptions);
});
);
}
}
@ -799,9 +1043,14 @@ export function withUsernamePassword(username: string, password: string, options
function _getSubscriptions(
creds: TokenCredentialsBase,
tenants: string[],
tokenAudience?: string): Promise<LinkedSubscription[]> {
if (tokenAudience &&
!managementPlaneTokenAudiences.some((item) => { return item === tokenAudience!.toLowerCase(); })) {
tokenAudience?: string
): Promise<LinkedSubscription[]> {
if (
tokenAudience &&
!managementPlaneTokenAudiences.some((item) => {
return item === tokenAudience!.toLowerCase();
})
) {
return Promise.resolve([]);
}
return getSubscriptionsFromTenants(creds, tenants);
@ -865,9 +1114,15 @@ async function _withMSI(options?: MSIVmOptions): Promise<MSIVmTokenCredentials>
*/
export function loginWithVmMSI(): Promise<MSIVmTokenCredentials>;
export function loginWithVmMSI(options: MSIVmOptions): Promise<MSIVmTokenCredentials>;
export function loginWithVmMSI(options: MSIVmOptions, callback: Callback<MSIVmTokenCredentials>): void;
export function loginWithVmMSI(
options: MSIVmOptions,
callback: Callback<MSIVmTokenCredentials>
): void;
export function loginWithVmMSI(callback: Callback<MSIVmTokenCredentials>): void;
export function loginWithVmMSI(options?: MSIVmOptions | Callback<MSIVmTokenCredentials>, callback?: Callback<MSIVmTokenCredentials>): void | Promise<MSIVmTokenCredentials> {
export function loginWithVmMSI(
options?: MSIVmOptions | Callback<MSIVmTokenCredentials>,
callback?: Callback<MSIVmTokenCredentials>
): void | Promise<MSIVmTokenCredentials> {
if (!callback && typeof options === "function") {
callback = options;
options = {};
@ -876,19 +1131,23 @@ export function loginWithVmMSI(options?: MSIVmOptions | Callback<MSIVmTokenCrede
if (!callback) {
return _withMSI(options as MSIVmOptions);
} else {
msRest.promiseToCallback(_withMSI(options as MSIVmOptions))((err: Error, tokenRes: MSITokenResponse) => {
if (err) {
return cb(err);
msRest.promiseToCallback(_withMSI(options as MSIVmOptions))(
(err: Error, tokenRes: MSITokenResponse) => {
if (err) {
return cb(err);
}
return cb(undefined, tokenRes);
}
return cb(undefined, tokenRes);
});
);
}
}
/**
* Private method
*/
async function _withAppServiceMSI(options: MSIAppServiceOptions): Promise<MSIAppServiceTokenCredentials> {
async function _withAppServiceMSI(
options: MSIAppServiceOptions
): Promise<MSIAppServiceTokenCredentials> {
if (!options) {
options = {};
}
@ -923,10 +1182,18 @@ async function _withAppServiceMSI(options: MSIAppServiceOptions): Promise<MSIApp
* @reject {Error} - error object.
*/
export function loginWithAppServiceMSI(): Promise<MSIAppServiceTokenCredentials>;
export function loginWithAppServiceMSI(options: MSIAppServiceOptions): Promise<MSIAppServiceTokenCredentials>;
export function loginWithAppServiceMSI(options: MSIAppServiceOptions, callback: Callback<MSIAppServiceTokenCredentials>): void;
export function loginWithAppServiceMSI(
options: MSIAppServiceOptions
): Promise<MSIAppServiceTokenCredentials>;
export function loginWithAppServiceMSI(
options: MSIAppServiceOptions,
callback: Callback<MSIAppServiceTokenCredentials>
): void;
export function loginWithAppServiceMSI(callback: Callback<MSIAppServiceTokenCredentials>): void;
export function loginWithAppServiceMSI(options?: MSIAppServiceOptions | Callback<MSIAppServiceTokenCredentials>, callback?: Callback<MSIAppServiceTokenCredentials>): void | Promise<MSIAppServiceTokenCredentials> {
export function loginWithAppServiceMSI(
options?: MSIAppServiceOptions | Callback<MSIAppServiceTokenCredentials>,
callback?: Callback<MSIAppServiceTokenCredentials>
): void | Promise<MSIAppServiceTokenCredentials> {
if (!callback && typeof options === "function") {
callback = options;
options = {};
@ -935,12 +1202,14 @@ export function loginWithAppServiceMSI(options?: MSIAppServiceOptions | Callback
if (!callback) {
return _withAppServiceMSI(options as MSIAppServiceOptions);
} else {
msRest.promiseToCallback(_withAppServiceMSI(options as MSIAppServiceOptions))((err: Error, tokenRes: MSITokenResponse) => {
if (err) {
return cb(err);
msRest.promiseToCallback(_withAppServiceMSI(options as MSIAppServiceOptions))(
(err: Error, tokenRes: MSITokenResponse) => {
if (err) {
return cb(err);
}
return cb(undefined, tokenRes);
}
return cb(undefined, tokenRes);
});
);
}
}
@ -959,7 +1228,8 @@ export async function execAz(cmd: string): Promise<any> {
try {
return resolve(JSON.parse(stdout));
} catch (err) {
const msg = `An error occurred while parsing the output "${stdout}", of ` +
const msg =
`An error occurred while parsing the output "${stdout}", of ` +
`the cmd "${cmd}": ${err.stack}.`;
return reject(new Error(msg));
}
@ -967,5 +1237,4 @@ export async function execAz(cmd: string): Promise<any> {
return resolve();
});
});
}

Просмотреть файл

@ -5,21 +5,36 @@ export { ApplicationTokenCredentials } from "./credentials/applicationTokenCrede
export { ApplicationTokenCertificateCredentials } from "./credentials/applicationTokenCertificateCredentials";
export { DeviceTokenCredentials } from "./credentials/deviceTokenCredentials";
export { createAuthenticator } from "./credentials/keyVaultFactory";
export { MSIAppServiceOptions, MSIAppServiceTokenCredentials } from "./credentials/msiAppServiceTokenCredentials";
export { MSIOptions, MSITokenCredentials, MSITokenResponse } from "./credentials/msiTokenCredentials";
export {
MSIAppServiceOptions,
MSIAppServiceTokenCredentials
} from "./credentials/msiAppServiceTokenCredentials";
export {
MSIOptions,
MSITokenCredentials,
MSITokenResponse
} from "./credentials/msiTokenCredentials";
export { MSIVmOptions, MSIVmTokenCredentials } from "./credentials/msiVmTokenCredentials";
export { TokenCredentialsBase } from "./credentials/tokenCredentialsBase";
export { UserTokenCredentials } from "./credentials/userTokenCredentials";
export { AuthConstants, TokenAudience } from "./util/authConstants";
export { LinkedSubscription, LinkedUser, UserType, buildTenantList } from "./subscriptionManagement/subscriptionUtils";
export {
LinkedSubscription,
LinkedUser,
UserType,
buildTenantList
} from "./subscriptionManagement/subscriptionUtils";
export {
AzureCliCredentials,
CliAccessToken,
ListAllSubscriptionOptions
} from "./credentials/azureCliCredentials";
export {
AuthResponse, LoginWithAuthFileOptions, InteractiveLoginOptions,
AzureTokenCredentialsOptions, LoginWithUsernamePasswordOptions,
AuthResponse,
LoginWithAuthFileOptions,
InteractiveLoginOptions,
AzureTokenCredentialsOptions,
LoginWithUsernamePasswordOptions,
interactive as interactiveLogin,
withInteractiveWithAuthResponse as interactiveLoginWithAuthResponse,
withUsernamePassword as loginWithUsernamePassword,

Просмотреть файл

@ -76,7 +76,10 @@ export interface LinkedSubscription {
* @param apiVersion - default value 2016-06-01
* @returns A promise that resolves to an array of tenantIds and rejects with an error.
*/
export async function buildTenantList(credentials: TokenCredentialsBase, apiVersion = "2016-06-01"): Promise<string[]> {
export async function buildTenantList(
credentials: TokenCredentialsBase,
apiVersion = "2016-06-01"
): Promise<string[]> {
if (credentials.domain && credentials.domain !== AuthConstants.AAD_COMMON_TENANT) {
return [credentials.domain];
}
@ -86,7 +89,7 @@ export async function buildTenantList(credentials: TokenCredentialsBase, apiVers
const reqUrl = `${baseUrl}${baseUrl.endsWith("/") ? "" : "/"}tenants?api-version=${apiVersion}`;
const req: msRest.RequestPrepareOptions = {
url: reqUrl,
method: "GET",
method: "GET"
};
const res = await client.sendRequest(req);
const result: string[] = [];
@ -101,7 +104,11 @@ export async function buildTenantList(credentials: TokenCredentialsBase, apiVers
return result;
}
export async function getSubscriptionsFromTenants(credentials: TokenCredentialsBase, tenantList: string[], apiVersion = "2016-06-01"): Promise<LinkedSubscription[]> {
export async function getSubscriptionsFromTenants(
credentials: TokenCredentialsBase,
tenantList: string[],
apiVersion = "2016-06-01"
): Promise<LinkedSubscription[]> {
let subscriptions: LinkedSubscription[] = [];
let userType = "user";
let username: string;
@ -116,25 +123,29 @@ export async function getSubscriptionsFromTenants(credentials: TokenCredentialsB
credentials.domain = tenant;
const client = new msRest.ServiceClient(credentials);
const baseUrl = credentials.environment.resourceManagerEndpointUrl;
const reqUrl = `${baseUrl}${baseUrl.endsWith("/") ? "" : "/"}subscriptions?api-version=${apiVersion}`;
const reqUrl = `${baseUrl}${
baseUrl.endsWith("/") ? "" : "/"
}subscriptions?api-version=${apiVersion}`;
const req: msRest.RequestPrepareOptions = {
url: reqUrl,
method: "GET",
method: "GET"
};
const res = await client.sendRequest(req);
const subscriptionList: any[] = (<any>res.parsedBody).value;
subscriptions = subscriptions.concat(subscriptionList.map((s: any) => {
s.tenantId = tenant;
s.user = { name: username, type: userType };
s.environmentName = credentials.environment.name;
s.name = s.displayName;
s.id = s.subscriptionId;
delete s.displayName;
delete s.subscriptionId;
delete s.subscriptionPolicies;
return s;
}));
subscriptions = subscriptions.concat(
subscriptionList.map((s: any) => {
s.tenantId = tenant;
s.user = { name: username, type: userType };
s.environmentName = credentials.environment.name;
s.name = s.displayName;
s.id = s.subscriptionId;
delete s.displayName;
delete s.subscriptionId;
delete s.subscriptionPolicies;
return s;
})
);
}
// Reset the original domain.
credentials.domain = originalDomain;

Просмотреть файл

@ -2,12 +2,12 @@
// Licensed under the MIT License. See License.txt in the project root for license information.
export const AuthConstants = {
"AAD_COMMON_TENANT": "common",
"DEFAULT_ADAL_CLIENT_ID": "04b07795-8ddb-461a-bbee-02f9e1bf7b46",
"SDK_INTERNAL_ERROR": "SDK_INTERNAL_ERROR",
"DEFAULT_LANGUAGE": "en-us",
"AZURE_AUTH_LOCATION": "AZURE_AUTH_LOCATION",
"RESOURCE_MANAGER_ENDPOINT": "https://management.azure.com/"
AAD_COMMON_TENANT: "common",
DEFAULT_ADAL_CLIENT_ID: "04b07795-8ddb-461a-bbee-02f9e1bf7b46",
SDK_INTERNAL_ERROR: "SDK_INTERNAL_ERROR",
DEFAULT_LANGUAGE: "en-us",
AZURE_AUTH_LOCATION: "AZURE_AUTH_LOCATION",
RESOURCE_MANAGER_ENDPOINT: "https://management.azure.com/"
};
export type TokenAudience = "graph" | "batch" | string | undefined;

Просмотреть файл

@ -46,6 +46,7 @@
"nock": "^10.0.6",
"npm-run-all": "^4.1.5",
"nyc": "^14.1.1",
"prettier": "2.2.1",
"rollup": "^1.18.0",
"rollup-plugin-sourcemaps": "^0.4.2",
"ts-node": "^8.3.0",
@ -64,10 +65,11 @@
"build": "run-s build:tsc build:rollup",
"build:tsc": "tsc -p tsconfig.json",
"build:rollup": "rollup -c rollup.config.js",
"format": "prettier --write \"./**/*.ts\"",
"prepack": "npm install && npm run build",
"test": "npm run build && run-p test:tslint test:unit",
"test:tslint": "tslint -p . -c tslint.json",
"test:unit": "mocha",
"check:packagejsonversion": "ts-node ./.scripts/checkPackageJsonVersion.ts"
}
}
}

Просмотреть файл

@ -16,16 +16,21 @@ async function listKeyVaultSecrets(creds: AzureCliCredentials): Promise<void> {
try {
console.log(">>>>>>> KeyVault <<<<<<<<<<<");
const client = new ServiceClient(creds);
console.log(">>> Subscription associated with the access token: '%s'.",
creds.tokenInfo.subscription);
console.log(
">>> Subscription associated with the access token: '%s'.",
creds.tokenInfo.subscription
);
const request: RequestPrepareOptions = {
url: getKVUrl(keyvaultAccountName),
method: "GET"
};
console.log(">>> Request url: '%s'.", request.url);
const res = await client.sendRequest(request);
console.log("List of secrets from keyvault account '%s': \n%O",
keyvaultAccountName, res.parsedBody);
console.log(
"List of secrets from keyvault account '%s': \n%O",
keyvaultAccountName,
res.parsedBody
);
} catch (err) {
console.log(err);
}
@ -40,29 +45,41 @@ async function listResourceGroups(creds: AzureCliCredentials): Promise<void> {
// Setting the resource to ARM endpoint.
creds.resource = "https://management.azure.com";
const client = new ServiceClient(creds);
console.log(">>> Subscription associated with the access token: '%s'.",
creds.tokenInfo.subscription);
console.log(
">>> Subscription associated with the access token: '%s'.",
creds.tokenInfo.subscription
);
const request: RequestPrepareOptions = {
url: getUrl(creds.subscriptionInfo.id),
method: "GET"
};
console.log(">>> Request url: '%s'.", request.url);
const res = await client.sendRequest(request);
console.log("List of resource groups from subscriptionId '%s': \n%O",
creds.subscriptionInfo.id, res.parsedBody);
console.log(
"List of resource groups from subscriptionId '%s': \n%O",
creds.subscriptionInfo.id,
res.parsedBody
);
// Let us change the subscriptionId, which should trigger refreshing the access token.
const subscriptions = await AzureCliCredentials.listAllSubscriptions();
creds.subscriptionInfo = subscriptions[1];
console.log(">>> The new subscription id associated with the credential object is: '%s'.",
creds.subscriptionInfo.id);
console.log(
">>> The new subscription id associated with the credential object is: '%s'.",
creds.subscriptionInfo.id
);
request.url = getUrl(creds.subscriptionInfo.id);
console.log(">>> Request url: '%s'.", request.url);
const res2 = await client.sendRequest(request);
console.log("List of resource groups from subscriptionId '%s': \n%O",
creds.subscriptionInfo.id, res2.parsedBody);
console.log(">>> Subscription associated with the access token: '%s'.",
creds.tokenInfo.subscription);
console.log(
"List of resource groups from subscriptionId '%s': \n%O",
creds.subscriptionInfo.id,
res2.parsedBody
);
console.log(
">>> Subscription associated with the access token: '%s'.",
creds.tokenInfo.subscription
);
} catch (err) {
console.log(err);
}
@ -74,4 +91,4 @@ async function main() {
await listResourceGroups(creds);
}
main();
main();

Просмотреть файл

@ -33,9 +33,7 @@ ${e}
}
// To get the tenants for your account, you can use the buildTenantList method:
const tenants = await msRestNodeAuth.buildTenantList(
authResponse.credentials
);
const tenants = await msRestNodeAuth.buildTenantList(authResponse.credentials);
// Update the domain used by the credentials so that it can generate tokens against a specific tenant.
authResponse.credentials.setDomain(tenants[0]);

Просмотреть файл

@ -7,7 +7,6 @@ import { expect, assert } from "chai";
import { WebResource, HttpHeaders, HttpClient, HttpOperationResponse } from "@azure/ms-rest-js";
describe("MSI App Service Authentication", function () {
function getMockHttpClient(response?: any, error?: any): HttpClient {
const httpClient = {
sendRequest: async (request: WebResource): Promise<HttpOperationResponse> => {
@ -60,7 +59,6 @@ describe("MSI App Service Authentication", function () {
token_type: "Bearer"
};
const httpClient = getMockHttpClient(mockResponse);
process.env["MSI_ENDPOINT"] = "http://127.0.0.1:41741/MSI/token/";
process.env["MSI_SECRET"] = "69418689F1E342DD946CB82994CDA3CB";
@ -73,8 +71,9 @@ describe("MSI App Service Authentication", function () {
it('should throw if the response contains "ExceptionMessage"', async function () {
const errorResponse = {
"error": "unknown",
"error_description": "ExceptionMessage: Failed to retrieve token from the Active directory. For details see logs in C:\\User1\\Logs\\Plugins\\Microsoft.Identity.MSI\\1.0\\service_identity_0.log"
error: "unknown",
error_description:
"ExceptionMessage: Failed to retrieve token from the Active directory. For details see logs in C:\\User1\\Logs\\Plugins\\Microsoft.Identity.MSI\\1.0\\service_identity_0.log"
};
const httpClient = getMockHttpClient(undefined, errorResponse);
@ -84,15 +83,13 @@ describe("MSI App Service Authentication", function () {
try {
await msiCredsObj.getToken();
assert.fail(undefined, undefined, "getToken should throw an exception");
}
catch (err) {
} catch (err) {
expect(err);
}
});
});
describe("loginWithAppServiceMSI (callback)", () => {
it("should successfully provide MSIAppServiceTokenCredentials object by providing optional properties", (done) => {
const mockResponse = {
access_token: "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1d",
@ -139,8 +136,9 @@ describe("MSI App Service Authentication", function () {
it('should throw if the response contains "ExceptionMessage"', async () => {
const errorResponse = {
"error": "unknown",
"error_description": "ExceptionMessage: Failed to retrieve token from the Active directory. For details see logs in C:\\User1\\Logs\\Plugins\\Microsoft.Identity.MSI\\1.0\\service_identity_0.log"
error: "unknown",
error_description:
"ExceptionMessage: Failed to retrieve token from the Active directory. For details see logs in C:\\User1\\Logs\\Plugins\\Microsoft.Identity.MSI\\1.0\\service_identity_0.log"
};
const httpClient = getMockHttpClient(undefined, errorResponse);
@ -155,4 +153,4 @@ describe("MSI App Service Authentication", function () {
}
});
});
});
});

Просмотреть файл

@ -6,14 +6,23 @@ import { expect, assert } from "chai";
import { HttpClient, HttpOperationResponse, WebResource, HttpHeaders } from "@azure/ms-rest-js";
describe("MSI Vm Authentication", () => {
function setupNockResponse(msiEndpoint?: string, expectedRequestHeaders?: any, response?: any, error?: any): HttpClient {
function setupNockResponse(
msiEndpoint?: string,
expectedRequestHeaders?: any,
response?: any,
error?: any
): HttpClient {
if (!msiEndpoint) {
msiEndpoint = "http://169.254.169.254/metadata/identity/oauth2/token";
}
const isMatch = (actualRequest: WebResource, expectedRequestHeaders: any) => {
return actualRequest.url === `${msiEndpoint}?api-version=${expectedRequestHeaders.apiVersion}&resource=${encodeURIComponent(expectedRequestHeaders.resource)}`;
return (
actualRequest.url ===
`${msiEndpoint}?api-version=${
expectedRequestHeaders.apiVersion
}&resource=${encodeURIComponent(expectedRequestHeaders.resource)}`
);
};
const httpClient = {
@ -47,8 +56,8 @@ describe("MSI Vm Authentication", () => {
};
const expectedQuery = {
"apiVersion": "2018-02-01",
"resource": "https://management.azure.com/"
apiVersion: "2018-02-01",
resource: "https://management.azure.com/"
};
const httpClient = setupNockResponse(undefined, expectedQuery, mockResponse);
@ -72,13 +81,16 @@ describe("MSI Vm Authentication", () => {
};
const expectedQuery = {
"apiVersion": "2018-02-01",
"resource": "https://management.azure.com/"
apiVersion: "2018-02-01",
resource: "https://management.azure.com/"
};
const customMsiEndpoint = "http://localhost:50342/oauth2/token";
const httpClient = setupNockResponse(customMsiEndpoint, expectedQuery, mockResponse);
const msiCredsObj = new MSIVmTokenCredentials({ msiEndpoint: customMsiEndpoint, httpClient: httpClient });
const msiCredsObj = new MSIVmTokenCredentials({
msiEndpoint: customMsiEndpoint,
httpClient: httpClient
});
const response = await msiCredsObj.getToken();
expect(response).to.exist;
expect(response!.accessToken).to.exist;
@ -87,18 +99,19 @@ describe("MSI Vm Authentication", () => {
it("should throw on requests with bad resource", async () => {
const errorMessage = "unknown";
const errorDescription = "Failed to retrieve token from the Active directory. For details see logs in C:\\User1\\Logs\\Plugins\\Microsoft.Identity.MSI\\1.0\\service_identity_0.log";
const errorDescription =
"Failed to retrieve token from the Active directory. For details see logs in C:\\User1\\Logs\\Plugins\\Microsoft.Identity.MSI\\1.0\\service_identity_0.log";
const errorResponse = {
"error": errorMessage,
"error_description": errorDescription
error: errorMessage,
error_description: errorDescription
};
const requestBodyToMatch = {
"resource": "badvalue"
resource: "badvalue"
};
const httpClient = setupNockResponse(undefined, requestBodyToMatch, undefined, errorResponse);
const msiCredsObj = new MSIVmTokenCredentials({ "resource": "badvalue", httpClient: httpClient });
const msiCredsObj = new MSIVmTokenCredentials({ resource: "badvalue", httpClient: httpClient });
try {
await msiCredsObj.getToken();
@ -114,16 +127,16 @@ describe("MSI Vm Authentication", () => {
const errorMessage = "bad_resource_200";
const errorDescription = "Invalid Resource";
const errorResponse = {
"error": errorMessage,
"error_description": errorDescription
error: errorMessage,
error_description: errorDescription
};
const requestBodyToMatch = {
"resource": " "
resource: " "
};
const httpClient = setupNockResponse(undefined, requestBodyToMatch, undefined, errorResponse);
const msiCredsObj = new MSIVmTokenCredentials({ "resource": " ", httpClient: httpClient });
const msiCredsObj = new MSIVmTokenCredentials({ resource: " ", httpClient: httpClient });
try {
await msiCredsObj.getToken();