Add extracted source directory and README navigation

This commit is contained in:
Shawn Bot
2026-03-31 14:56:06 +00:00
parent 6252bb6eb5
commit 91e01d755b
4757 changed files with 984951 additions and 0 deletions

View File

@@ -0,0 +1,60 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import { processMultiTenantRequest, resolveAdditionallyAllowedTenantIds, } from "../util/tenantIdUtils.js";
import { checkTenantId } from "../util/tenantIdUtils.js";
import { credentialLogger } from "../util/logging.js";
import { ensureScopes } from "../util/scopeUtils.js";
import { tracingClient } from "../util/tracing.js";
import { createMsalClient } from "../msal/nodeFlows/msalClient.js";
const logger = credentialLogger("AuthorizationCodeCredential");
/**
* Enables authentication to Microsoft Entra ID using an authorization code
* that was obtained through the authorization code flow, described in more detail
* in the Microsoft Entra ID documentation:
*
* https://learn.microsoft.com/entra/identity-platform/v2-oauth2-auth-code-flow
*/
export class AuthorizationCodeCredential {
/**
* @hidden
* @internal
*/
constructor(tenantId, clientId, clientSecretOrAuthorizationCode, authorizationCodeOrRedirectUri, redirectUriOrOptions, options) {
checkTenantId(logger, tenantId);
this.clientSecret = clientSecretOrAuthorizationCode;
if (typeof redirectUriOrOptions === "string") {
// the clientId+clientSecret constructor
this.authorizationCode = authorizationCodeOrRedirectUri;
this.redirectUri = redirectUriOrOptions;
// in this case, options are good as they come
}
else {
// clientId only
this.authorizationCode = clientSecretOrAuthorizationCode;
this.redirectUri = authorizationCodeOrRedirectUri;
this.clientSecret = undefined;
options = redirectUriOrOptions;
}
// TODO: Validate tenant if provided
this.tenantId = tenantId;
this.additionallyAllowedTenantIds = resolveAdditionallyAllowedTenantIds(options === null || options === void 0 ? void 0 : options.additionallyAllowedTenants);
this.msalClient = createMsalClient(clientId, tenantId, Object.assign(Object.assign({}, options), { logger, tokenCredentialOptions: options !== null && options !== void 0 ? options : {} }));
}
/**
* Authenticates with Microsoft Entra ID and returns an access token if successful.
* If authentication fails, a {@link CredentialUnavailableError} will be thrown with the details of the failure.
*
* @param scopes - The list of scopes for which the token will have access.
* @param options - The options used to configure any requests this
* TokenCredential implementation might make.
*/
async getToken(scopes, options = {}) {
return tracingClient.withSpan(`${this.constructor.name}.getToken`, options, async (newOptions) => {
const tenantId = processMultiTenantRequest(this.tenantId, newOptions, this.additionallyAllowedTenantIds);
newOptions.tenantId = tenantId;
const arrayScopes = ensureScopes(scopes);
return this.msalClient.getTokenByAuthorizationCode(arrayScopes, this.redirectUri, this.authorizationCode, this.clientSecret, Object.assign(Object.assign({}, newOptions), { disableAutomaticAuthentication: this.disableAutomaticAuthentication }));
});
}
}
//# sourceMappingURL=authorizationCodeCredential.js.map

View File

@@ -0,0 +1,191 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import { checkTenantId, processMultiTenantRequest, resolveAdditionallyAllowedTenantIds, } from "../util/tenantIdUtils.js";
import { credentialLogger, formatError, formatSuccess } from "../util/logging.js";
import { ensureValidScopeForDevTimeCreds, getScopeResource } from "../util/scopeUtils.js";
import { CredentialUnavailableError } from "../errors.js";
import child_process from "child_process";
import { tracingClient } from "../util/tracing.js";
import { checkSubscription } from "../util/subscriptionUtils.js";
const logger = credentialLogger("AzureCliCredential");
/**
* Mockable reference to the CLI credential cliCredentialFunctions
* @internal
*/
export const cliCredentialInternals = {
/**
* @internal
*/
getSafeWorkingDir() {
if (process.platform === "win32") {
let systemRoot = process.env.SystemRoot || process.env["SYSTEMROOT"];
if (!systemRoot) {
logger.getToken.warning("The SystemRoot environment variable is not set. This may cause issues when using the Azure CLI credential.");
systemRoot = "C:\\Windows";
}
return systemRoot;
}
else {
return "/bin";
}
},
/**
* Gets the access token from Azure CLI
* @param resource - The resource to use when getting the token
* @internal
*/
async getAzureCliAccessToken(resource, tenantId, subscription, timeout) {
let tenantSection = [];
let subscriptionSection = [];
if (tenantId) {
tenantSection = ["--tenant", tenantId];
}
if (subscription) {
// Add quotes around the subscription to handle subscriptions with spaces
subscriptionSection = ["--subscription", `"${subscription}"`];
}
return new Promise((resolve, reject) => {
try {
child_process.execFile("az", [
"account",
"get-access-token",
"--output",
"json",
"--resource",
resource,
...tenantSection,
...subscriptionSection,
], { cwd: cliCredentialInternals.getSafeWorkingDir(), shell: true, timeout }, (error, stdout, stderr) => {
resolve({ stdout: stdout, stderr: stderr, error });
});
}
catch (err) {
reject(err);
}
});
},
};
/**
* This credential will use the currently logged-in user login information
* via the Azure CLI ('az') commandline tool.
* To do so, it will read the user access token and expire time
* with Azure CLI command "az account get-access-token".
*/
export class AzureCliCredential {
/**
* Creates an instance of the {@link AzureCliCredential}.
*
* To use this credential, ensure that you have already logged
* in via the 'az' tool using the command "az login" from the commandline.
*
* @param options - Options, to optionally allow multi-tenant requests.
*/
constructor(options) {
if (options === null || options === void 0 ? void 0 : options.tenantId) {
checkTenantId(logger, options === null || options === void 0 ? void 0 : options.tenantId);
this.tenantId = options === null || options === void 0 ? void 0 : options.tenantId;
}
if (options === null || options === void 0 ? void 0 : options.subscription) {
checkSubscription(logger, options === null || options === void 0 ? void 0 : options.subscription);
this.subscription = options === null || options === void 0 ? void 0 : options.subscription;
}
this.additionallyAllowedTenantIds = resolveAdditionallyAllowedTenantIds(options === null || options === void 0 ? void 0 : options.additionallyAllowedTenants);
this.timeout = options === null || options === void 0 ? void 0 : options.processTimeoutInMs;
}
/**
* Authenticates with Microsoft Entra ID and returns an access token if successful.
* If authentication fails, a {@link CredentialUnavailableError} will be thrown with the details of the failure.
*
* @param scopes - The list of scopes for which the token will have access.
* @param options - The options used to configure any requests this
* TokenCredential implementation might make.
*/
async getToken(scopes, options = {}) {
const tenantId = processMultiTenantRequest(this.tenantId, options, this.additionallyAllowedTenantIds);
if (tenantId) {
checkTenantId(logger, tenantId);
}
if (this.subscription) {
checkSubscription(logger, this.subscription);
}
const scope = typeof scopes === "string" ? scopes : scopes[0];
logger.getToken.info(`Using the scope ${scope}`);
return tracingClient.withSpan(`${this.constructor.name}.getToken`, options, async () => {
var _a, _b, _c, _d;
try {
ensureValidScopeForDevTimeCreds(scope, logger);
const resource = getScopeResource(scope);
const obj = await cliCredentialInternals.getAzureCliAccessToken(resource, tenantId, this.subscription, this.timeout);
const specificScope = (_a = obj.stderr) === null || _a === void 0 ? void 0 : _a.match("(.*)az login --scope(.*)");
const isLoginError = ((_b = obj.stderr) === null || _b === void 0 ? void 0 : _b.match("(.*)az login(.*)")) && !specificScope;
const isNotInstallError = ((_c = obj.stderr) === null || _c === void 0 ? void 0 : _c.match("az:(.*)not found")) || ((_d = obj.stderr) === null || _d === void 0 ? void 0 : _d.startsWith("'az' is not recognized"));
if (isNotInstallError) {
const error = new CredentialUnavailableError("Azure CLI could not be found. Please visit https://aka.ms/azure-cli for installation instructions and then, once installed, authenticate to your Azure account using 'az login'.");
logger.getToken.info(formatError(scopes, error));
throw error;
}
if (isLoginError) {
const error = new CredentialUnavailableError("Please run 'az login' from a command prompt to authenticate before using this credential.");
logger.getToken.info(formatError(scopes, error));
throw error;
}
try {
const responseData = obj.stdout;
const response = this.parseRawResponse(responseData);
logger.getToken.info(formatSuccess(scopes));
return response;
}
catch (e) {
if (obj.stderr) {
throw new CredentialUnavailableError(obj.stderr);
}
throw e;
}
}
catch (err) {
const error = err.name === "CredentialUnavailableError"
? err
: new CredentialUnavailableError(err.message || "Unknown error while trying to retrieve the access token");
logger.getToken.info(formatError(scopes, error));
throw error;
}
});
}
/**
* Parses the raw JSON response from the Azure CLI into a usable AccessToken object
*
* @param rawResponse - The raw JSON response from the Azure CLI
* @returns An access token with the expiry time parsed from the raw response
*
* The expiryTime of the credential's access token, in milliseconds, is calculated as follows:
*
* When available, expires_on (introduced in Azure CLI v2.54.0) will be preferred. Otherwise falls back to expiresOn.
*/
parseRawResponse(rawResponse) {
const response = JSON.parse(rawResponse);
const token = response.accessToken;
// if available, expires_on will be a number representing seconds since epoch.
// ensure it's a number or NaN
let expiresOnTimestamp = Number.parseInt(response.expires_on, 10) * 1000;
if (!isNaN(expiresOnTimestamp)) {
logger.getToken.info("expires_on is available and is valid, using it");
return {
token,
expiresOnTimestamp,
tokenType: "Bearer",
};
}
// fallback to the older expiresOn - an RFC3339 date string
expiresOnTimestamp = new Date(response.expiresOn).getTime();
// ensure expiresOn is well-formatted
if (isNaN(expiresOnTimestamp)) {
throw new CredentialUnavailableError(`Unexpected response from Azure CLI when getting token. Expected "expiresOn" to be a RFC3339 date string. Got: "${response.expiresOn}"`);
}
return {
token,
expiresOnTimestamp,
tokenType: "Bearer",
};
}
}
//# sourceMappingURL=azureCliCredential.js.map

View File

@@ -0,0 +1,173 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import { credentialLogger, formatError, formatSuccess } from "../util/logging.js";
import { CredentialUnavailableError } from "../errors.js";
import child_process from "child_process";
import { checkTenantId, processMultiTenantRequest, resolveAdditionallyAllowedTenantIds, } from "../util/tenantIdUtils.js";
import { tracingClient } from "../util/tracing.js";
import { ensureValidScopeForDevTimeCreds } from "../util/scopeUtils.js";
const logger = credentialLogger("AzureDeveloperCliCredential");
/**
* Mockable reference to the Developer CLI credential cliCredentialFunctions
* @internal
*/
export const developerCliCredentialInternals = {
/**
* @internal
*/
getSafeWorkingDir() {
if (process.platform === "win32") {
let systemRoot = process.env.SystemRoot || process.env["SYSTEMROOT"];
if (!systemRoot) {
logger.getToken.warning("The SystemRoot environment variable is not set. This may cause issues when using the Azure Developer CLI credential.");
systemRoot = "C:\\Windows";
}
return systemRoot;
}
else {
return "/bin";
}
},
/**
* Gets the access token from Azure Developer CLI
* @param scopes - The scopes to use when getting the token
* @internal
*/
async getAzdAccessToken(scopes, tenantId, timeout) {
let tenantSection = [];
if (tenantId) {
tenantSection = ["--tenant-id", tenantId];
}
return new Promise((resolve, reject) => {
try {
child_process.execFile("azd", [
"auth",
"token",
"--output",
"json",
...scopes.reduce((previous, current) => previous.concat("--scope", current), []),
...tenantSection,
], {
cwd: developerCliCredentialInternals.getSafeWorkingDir(),
timeout,
}, (error, stdout, stderr) => {
resolve({ stdout, stderr, error });
});
}
catch (err) {
reject(err);
}
});
},
};
/**
* Azure Developer CLI is a command-line interface tool that allows developers to create, manage, and deploy
* resources in Azure. It's built on top of the Azure CLI and provides additional functionality specific
* to Azure developers. It allows users to authenticate as a user and/or a service principal against
* <a href="https://learn.microsoft.com/entra/fundamentals/">Microsoft Entra ID</a>. The
* AzureDeveloperCliCredential authenticates in a development environment and acquires a token on behalf of
* the logged-in user or service principal in the Azure Developer CLI. It acts as the Azure Developer CLI logged in user or
* service principal and executes an Azure CLI command underneath to authenticate the application against
* Microsoft Entra ID.
*
* <h2> Configure AzureDeveloperCliCredential </h2>
*
* To use this credential, the developer needs to authenticate locally in Azure Developer CLI using one of the
* commands below:
*
* <ol>
* <li>Run "azd auth login" in Azure Developer CLI to authenticate interactively as a user.</li>
* <li>Run "azd auth login --client-id clientID --client-secret clientSecret
* --tenant-id tenantID" to authenticate as a service principal.</li>
* </ol>
*
* You may need to repeat this process after a certain time period, depending on the refresh token validity in your
* organization. Generally, the refresh token validity period is a few weeks to a few months.
* AzureDeveloperCliCredential will prompt you to sign in again.
*/
export class AzureDeveloperCliCredential {
/**
* Creates an instance of the {@link AzureDeveloperCliCredential}.
*
* To use this credential, ensure that you have already logged
* in via the 'azd' tool using the command "azd auth login" from the commandline.
*
* @param options - Options, to optionally allow multi-tenant requests.
*/
constructor(options) {
if (options === null || options === void 0 ? void 0 : options.tenantId) {
checkTenantId(logger, options === null || options === void 0 ? void 0 : options.tenantId);
this.tenantId = options === null || options === void 0 ? void 0 : options.tenantId;
}
this.additionallyAllowedTenantIds = resolveAdditionallyAllowedTenantIds(options === null || options === void 0 ? void 0 : options.additionallyAllowedTenants);
this.timeout = options === null || options === void 0 ? void 0 : options.processTimeoutInMs;
}
/**
* Authenticates with Microsoft Entra ID and returns an access token if successful.
* If authentication fails, a {@link CredentialUnavailableError} will be thrown with the details of the failure.
*
* @param scopes - The list of scopes for which the token will have access.
* @param options - The options used to configure any requests this
* TokenCredential implementation might make.
*/
async getToken(scopes, options = {}) {
const tenantId = processMultiTenantRequest(this.tenantId, options, this.additionallyAllowedTenantIds);
if (tenantId) {
checkTenantId(logger, tenantId);
}
let scopeList;
if (typeof scopes === "string") {
scopeList = [scopes];
}
else {
scopeList = scopes;
}
logger.getToken.info(`Using the scopes ${scopes}`);
return tracingClient.withSpan(`${this.constructor.name}.getToken`, options, async () => {
var _a, _b, _c, _d;
try {
scopeList.forEach((scope) => {
ensureValidScopeForDevTimeCreds(scope, logger);
});
const obj = await developerCliCredentialInternals.getAzdAccessToken(scopeList, tenantId, this.timeout);
const isNotLoggedInError = ((_a = obj.stderr) === null || _a === void 0 ? void 0 : _a.match("not logged in, run `azd login` to login")) ||
((_b = obj.stderr) === null || _b === void 0 ? void 0 : _b.match("not logged in, run `azd auth login` to login"));
const isNotInstallError = ((_c = obj.stderr) === null || _c === void 0 ? void 0 : _c.match("azd:(.*)not found")) ||
((_d = obj.stderr) === null || _d === void 0 ? void 0 : _d.startsWith("'azd' is not recognized"));
if (isNotInstallError || (obj.error && obj.error.code === "ENOENT")) {
const error = new CredentialUnavailableError("Azure Developer CLI couldn't be found. To mitigate this issue, see the troubleshooting guidelines at https://aka.ms/azsdk/js/identity/azdevclicredential/troubleshoot.");
logger.getToken.info(formatError(scopes, error));
throw error;
}
if (isNotLoggedInError) {
const error = new CredentialUnavailableError("Please run 'azd auth login' from a command prompt to authenticate before using this credential. For more information, see the troubleshooting guidelines at https://aka.ms/azsdk/js/identity/azdevclicredential/troubleshoot.");
logger.getToken.info(formatError(scopes, error));
throw error;
}
try {
const resp = JSON.parse(obj.stdout);
logger.getToken.info(formatSuccess(scopes));
return {
token: resp.token,
expiresOnTimestamp: new Date(resp.expiresOn).getTime(),
tokenType: "Bearer",
};
}
catch (e) {
if (obj.stderr) {
throw new CredentialUnavailableError(obj.stderr);
}
throw e;
}
}
catch (err) {
const error = err.name === "CredentialUnavailableError"
? err
: new CredentialUnavailableError(err.message || "Unknown error while trying to retrieve the access token");
logger.getToken.info(formatError(scopes, error));
throw error;
}
});
}
}
//# sourceMappingURL=azureDeveloperCliCredential.js.map

View File

@@ -0,0 +1,141 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import { AuthenticationError, CredentialUnavailableError } from "../errors.js";
import { createHttpHeaders, createPipelineRequest } from "@azure/core-rest-pipeline";
import { ClientAssertionCredential } from "./clientAssertionCredential.js";
import { IdentityClient } from "../client/identityClient.js";
import { checkTenantId } from "../util/tenantIdUtils.js";
import { credentialLogger } from "../util/logging.js";
const credentialName = "AzurePipelinesCredential";
const logger = credentialLogger(credentialName);
const OIDC_API_VERSION = "7.1";
/**
* This credential is designed to be used in Azure Pipelines with service connections
* as a setup for workload identity federation.
*/
export class AzurePipelinesCredential {
/**
* AzurePipelinesCredential supports Federated Identity on Azure Pipelines through Service Connections.
* @param tenantId - tenantId associated with the service connection
* @param clientId - clientId associated with the service connection
* @param serviceConnectionId - Unique ID for the service connection, as found in the querystring's resourceId key
* @param systemAccessToken - The pipeline's <see href="https://learn.microsoft.com/azure/devops/pipelines/build/variables?view=azure-devops%26tabs=yaml#systemaccesstoken">System.AccessToken</see> value.
* @param options - The identity client options to use for authentication.
*/
constructor(tenantId, clientId, serviceConnectionId, systemAccessToken, options = {}) {
var _a, _b;
if (!clientId) {
throw new CredentialUnavailableError(`${credentialName}: is unavailable. clientId is a required parameter.`);
}
if (!tenantId) {
throw new CredentialUnavailableError(`${credentialName}: is unavailable. tenantId is a required parameter.`);
}
if (!serviceConnectionId) {
throw new CredentialUnavailableError(`${credentialName}: is unavailable. serviceConnectionId is a required parameter.`);
}
if (!systemAccessToken) {
throw new CredentialUnavailableError(`${credentialName}: is unavailable. systemAccessToken is a required parameter.`);
}
// Allow these headers to be logged for troubleshooting by AzurePipelines.
options.loggingOptions = Object.assign(Object.assign({}, options === null || options === void 0 ? void 0 : options.loggingOptions), { additionalAllowedHeaderNames: [
...((_b = (_a = options.loggingOptions) === null || _a === void 0 ? void 0 : _a.additionalAllowedHeaderNames) !== null && _b !== void 0 ? _b : []),
"x-vss-e2eid",
"x-msedge-ref",
] });
this.identityClient = new IdentityClient(options);
checkTenantId(logger, tenantId);
logger.info(`Invoking AzurePipelinesCredential with tenant ID: ${tenantId}, client ID: ${clientId}, and service connection ID: ${serviceConnectionId}`);
if (!process.env.SYSTEM_OIDCREQUESTURI) {
throw new CredentialUnavailableError(`${credentialName}: is unavailable. Ensure that you're running this task in an Azure Pipeline, so that following missing system variable(s) can be defined- "SYSTEM_OIDCREQUESTURI"`);
}
const oidcRequestUrl = `${process.env.SYSTEM_OIDCREQUESTURI}?api-version=${OIDC_API_VERSION}&serviceConnectionId=${serviceConnectionId}`;
logger.info(`Invoking ClientAssertionCredential with tenant ID: ${tenantId}, client ID: ${clientId} and service connection ID: ${serviceConnectionId}`);
this.clientAssertionCredential = new ClientAssertionCredential(tenantId, clientId, this.requestOidcToken.bind(this, oidcRequestUrl, systemAccessToken), options);
}
/**
* Authenticates with Microsoft Entra ID and returns an access token if successful.
* If authentication fails, a {@link CredentialUnavailableError} or {@link AuthenticationError} will be thrown with the details of the failure.
*
* @param scopes - The list of scopes for which the token will have access.
* @param options - The options used to configure any requests this
* TokenCredential implementation might make.
*/
async getToken(scopes, options) {
if (!this.clientAssertionCredential) {
const errorMessage = `${credentialName}: is unavailable. To use Federation Identity in Azure Pipelines, the following parameters are required -
tenantId,
clientId,
serviceConnectionId,
systemAccessToken,
"SYSTEM_OIDCREQUESTURI".
See the troubleshooting guide for more information: https://aka.ms/azsdk/js/identity/azurepipelinescredential/troubleshoot`;
logger.error(errorMessage);
throw new CredentialUnavailableError(errorMessage);
}
logger.info("Invoking getToken() of Client Assertion Credential");
return this.clientAssertionCredential.getToken(scopes, options);
}
/**
*
* @param oidcRequestUrl - oidc request url
* @param systemAccessToken - system access token
* @returns OIDC token from Azure Pipelines
*/
async requestOidcToken(oidcRequestUrl, systemAccessToken) {
logger.info("Requesting OIDC token from Azure Pipelines...");
logger.info(oidcRequestUrl);
const request = createPipelineRequest({
url: oidcRequestUrl,
method: "POST",
headers: createHttpHeaders({
"Content-Type": "application/json",
Authorization: `Bearer ${systemAccessToken}`,
// Prevents the service from responding with a redirect HTTP status code (useful for automation).
"X-TFS-FedAuthRedirect": "Suppress",
}),
});
const response = await this.identityClient.sendRequest(request);
return handleOidcResponse(response);
}
}
export function handleOidcResponse(response) {
// OIDC token is present in `bodyAsText` field
const text = response.bodyAsText;
if (!text) {
logger.error(`${credentialName}: Authentication Failed. Received null token from OIDC request. Response status- ${response.status}. Complete response - ${JSON.stringify(response)}`);
throw new AuthenticationError(response.status, {
error: `${credentialName}: Authentication Failed. Received null token from OIDC request.`,
error_description: `${JSON.stringify(response)}. See the troubleshooting guide for more information: https://aka.ms/azsdk/js/identity/azurepipelinescredential/troubleshoot`,
});
}
try {
const result = JSON.parse(text);
if (result === null || result === void 0 ? void 0 : result.oidcToken) {
return result.oidcToken;
}
else {
const errorMessage = `${credentialName}: Authentication Failed. oidcToken field not detected in the response.`;
let errorDescription = ``;
if (response.status !== 200) {
errorDescription = `Response body = ${text}. Response Headers ["x-vss-e2eid"] = ${response.headers.get("x-vss-e2eid")} and ["x-msedge-ref"] = ${response.headers.get("x-msedge-ref")}. See the troubleshooting guide for more information: https://aka.ms/azsdk/js/identity/azurepipelinescredential/troubleshoot`;
}
logger.error(errorMessage);
logger.error(errorDescription);
throw new AuthenticationError(response.status, {
error: errorMessage,
error_description: errorDescription,
});
}
}
catch (e) {
const errorDetails = `${credentialName}: Authentication Failed. oidcToken field not detected in the response.`;
logger.error(`Response from service = ${text}, Response Headers ["x-vss-e2eid"] = ${response.headers.get("x-vss-e2eid")}
and ["x-msedge-ref"] = ${response.headers.get("x-msedge-ref")}, error message = ${e.message}`);
logger.error(errorDetails);
throw new AuthenticationError(response.status, {
error: errorDetails,
error_description: `Response = ${text}. Response headers ["x-vss-e2eid"] = ${response.headers.get("x-vss-e2eid")} and ["x-msedge-ref"] = ${response.headers.get("x-msedge-ref")}. See the troubleshooting guide for more information: https://aka.ms/azsdk/js/identity/azurepipelinescredential/troubleshoot`,
});
}
}
//# sourceMappingURL=azurePipelinesCredential.js.map

View File

@@ -0,0 +1,229 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import { checkTenantId, processMultiTenantRequest, resolveAdditionallyAllowedTenantIds, } from "../util/tenantIdUtils.js";
import { credentialLogger, formatError, formatSuccess } from "../util/logging.js";
import { ensureValidScopeForDevTimeCreds, getScopeResource } from "../util/scopeUtils.js";
import { CredentialUnavailableError } from "../errors.js";
import { processUtils } from "../util/processUtils.js";
import { tracingClient } from "../util/tracing.js";
const logger = credentialLogger("AzurePowerShellCredential");
const isWindows = process.platform === "win32";
/**
* Returns a platform-appropriate command name by appending ".exe" on Windows.
*
* @internal
*/
export function formatCommand(commandName) {
if (isWindows) {
return `${commandName}.exe`;
}
else {
return commandName;
}
}
/**
* Receives a list of commands to run, executes them, then returns the outputs.
* If anything fails, an error is thrown.
* @internal
*/
async function runCommands(commands, timeout) {
const results = [];
for (const command of commands) {
const [file, ...parameters] = command;
const result = (await processUtils.execFile(file, parameters, {
encoding: "utf8",
timeout,
}));
results.push(result);
}
return results;
}
/**
* Known PowerShell errors
* @internal
*/
export const powerShellErrors = {
login: "Run Connect-AzAccount to login",
installed: "The specified module 'Az.Accounts' with version '2.2.0' was not loaded because no valid module file was found in any module directory",
};
/**
* Messages to use when throwing in this credential.
* @internal
*/
export const powerShellPublicErrorMessages = {
login: "Please run 'Connect-AzAccount' from PowerShell to authenticate before using this credential.",
installed: `The 'Az.Account' module >= 2.2.0 is not installed. Install the Azure Az PowerShell module with: "Install-Module -Name Az -Scope CurrentUser -Repository PSGallery -Force".`,
troubleshoot: `To troubleshoot, visit https://aka.ms/azsdk/js/identity/powershellcredential/troubleshoot.`,
};
// PowerShell Azure User not logged in error check.
const isLoginError = (err) => err.message.match(`(.*)${powerShellErrors.login}(.*)`);
// Az Module not Installed in Azure PowerShell check.
const isNotInstalledError = (err) => err.message.match(powerShellErrors.installed);
/**
* The PowerShell commands to be tried, in order.
*
* @internal
*/
export const commandStack = [formatCommand("pwsh")];
if (isWindows) {
commandStack.push(formatCommand("powershell"));
}
/**
* This credential will use the currently logged-in user information from the
* Azure PowerShell module. To do so, it will read the user access token and
* expire time with Azure PowerShell command `Get-AzAccessToken -ResourceUrl {ResourceScope}`
*/
export class AzurePowerShellCredential {
/**
* Creates an instance of the {@link AzurePowerShellCredential}.
*
* To use this credential:
* - Install the Azure Az PowerShell module with:
* `Install-Module -Name Az -Scope CurrentUser -Repository PSGallery -Force`.
* - You have already logged in to Azure PowerShell using the command
* `Connect-AzAccount` from the command line.
*
* @param options - Options, to optionally allow multi-tenant requests.
*/
constructor(options) {
if (options === null || options === void 0 ? void 0 : options.tenantId) {
checkTenantId(logger, options === null || options === void 0 ? void 0 : options.tenantId);
this.tenantId = options === null || options === void 0 ? void 0 : options.tenantId;
}
this.additionallyAllowedTenantIds = resolveAdditionallyAllowedTenantIds(options === null || options === void 0 ? void 0 : options.additionallyAllowedTenants);
this.timeout = options === null || options === void 0 ? void 0 : options.processTimeoutInMs;
}
/**
* Gets the access token from Azure PowerShell
* @param resource - The resource to use when getting the token
*/
async getAzurePowerShellAccessToken(resource, tenantId, timeout) {
// Clone the stack to avoid mutating it while iterating
for (const powerShellCommand of [...commandStack]) {
try {
await runCommands([[powerShellCommand, "/?"]], timeout);
}
catch (e) {
// Remove this credential from the original stack so that we don't try it again.
commandStack.shift();
continue;
}
const results = await runCommands([
[
powerShellCommand,
"-NoProfile",
"-NonInteractive",
"-Command",
`
$tenantId = "${tenantId !== null && tenantId !== void 0 ? tenantId : ""}"
$m = Import-Module Az.Accounts -MinimumVersion 2.2.0 -PassThru
$useSecureString = $m.Version -ge [version]'2.17.0'
$params = @{
ResourceUrl = "${resource}"
}
if ($tenantId.Length -gt 0) {
$params["TenantId"] = $tenantId
}
if ($useSecureString) {
$params["AsSecureString"] = $true
}
$token = Get-AzAccessToken @params
$result = New-Object -TypeName PSObject
$result | Add-Member -MemberType NoteProperty -Name ExpiresOn -Value $token.ExpiresOn
if ($useSecureString) {
$result | Add-Member -MemberType NoteProperty -Name Token -Value (ConvertFrom-SecureString -AsPlainText $token.Token)
} else {
$result | Add-Member -MemberType NoteProperty -Name Token -Value $token.Token
}
Write-Output (ConvertTo-Json $result)
`,
],
]);
const result = results[0];
return parseJsonToken(result);
}
throw new Error(`Unable to execute PowerShell. Ensure that it is installed in your system`);
}
/**
* Authenticates with Microsoft Entra ID and returns an access token if successful.
* If the authentication cannot be performed through PowerShell, a {@link CredentialUnavailableError} will be thrown.
*
* @param scopes - The list of scopes for which the token will have access.
* @param options - The options used to configure any requests this TokenCredential implementation might make.
*/
async getToken(scopes, options = {}) {
return tracingClient.withSpan(`${this.constructor.name}.getToken`, options, async () => {
const tenantId = processMultiTenantRequest(this.tenantId, options, this.additionallyAllowedTenantIds);
const scope = typeof scopes === "string" ? scopes : scopes[0];
if (tenantId) {
checkTenantId(logger, tenantId);
}
try {
ensureValidScopeForDevTimeCreds(scope, logger);
logger.getToken.info(`Using the scope ${scope}`);
const resource = getScopeResource(scope);
const response = await this.getAzurePowerShellAccessToken(resource, tenantId, this.timeout);
logger.getToken.info(formatSuccess(scopes));
return {
token: response.Token,
expiresOnTimestamp: new Date(response.ExpiresOn).getTime(),
tokenType: "Bearer",
};
}
catch (err) {
if (isNotInstalledError(err)) {
const error = new CredentialUnavailableError(powerShellPublicErrorMessages.installed);
logger.getToken.info(formatError(scope, error));
throw error;
}
else if (isLoginError(err)) {
const error = new CredentialUnavailableError(powerShellPublicErrorMessages.login);
logger.getToken.info(formatError(scope, error));
throw error;
}
const error = new CredentialUnavailableError(`${err}. ${powerShellPublicErrorMessages.troubleshoot}`);
logger.getToken.info(formatError(scope, error));
throw error;
}
});
}
}
/**
*
* @internal
*/
export async function parseJsonToken(result) {
const jsonRegex = /{[^{}]*}/g;
const matches = result.match(jsonRegex);
let resultWithoutToken = result;
if (matches) {
try {
for (const item of matches) {
try {
const jsonContent = JSON.parse(item);
if (jsonContent === null || jsonContent === void 0 ? void 0 : jsonContent.Token) {
resultWithoutToken = resultWithoutToken.replace(item, "");
if (resultWithoutToken) {
logger.getToken.warning(resultWithoutToken);
}
return jsonContent;
}
}
catch (e) {
continue;
}
}
}
catch (e) {
throw new Error(`Unable to parse the output of PowerShell. Received output: ${result}`);
}
}
throw new Error(`No access token found in the output. Received output: ${result}`);
}
//# sourceMappingURL=azurePowerShellCredential.js.map

View File

@@ -0,0 +1,92 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import { AggregateAuthenticationError, CredentialUnavailableError } from "../errors.js";
import { credentialLogger, formatError, formatSuccess } from "../util/logging.js";
import { tracingClient } from "../util/tracing.js";
/**
* @internal
*/
export const logger = credentialLogger("ChainedTokenCredential");
/**
* Enables multiple `TokenCredential` implementations to be tried in order until
* one of the getToken methods returns an access token. For more information, see
* [ChainedTokenCredential overview](https://aka.ms/azsdk/js/identity/credential-chains#use-chainedtokencredential-for-granularity).
*/
export class ChainedTokenCredential {
/**
* Creates an instance of ChainedTokenCredential using the given credentials.
*
* @param sources - `TokenCredential` implementations to be tried in order.
*
* Example usage:
* ```ts snippet:chained_token_credential_example
* import { ClientSecretCredential, ChainedTokenCredential } from "@azure/identity";
*
* const tenantId = "<tenant-id>";
* const clientId = "<client-id>";
* const clientSecret = "<client-secret>";
* const anotherClientId = "<another-client-id>";
* const anotherSecret = "<another-client-secret>";
*
* const firstCredential = new ClientSecretCredential(tenantId, clientId, clientSecret);
* const secondCredential = new ClientSecretCredential(tenantId, anotherClientId, anotherSecret);
*
* const credentialChain = new ChainedTokenCredential(firstCredential, secondCredential);
* ```
*/
constructor(...sources) {
this._sources = [];
this._sources = sources;
}
/**
* Returns the first access token returned by one of the chained
* `TokenCredential` implementations. Throws an {@link AggregateAuthenticationError}
* when one or more credentials throws an {@link AuthenticationError} and
* no credentials have returned an access token.
*
* This method is called automatically by Azure SDK client libraries. You may call this method
* directly, but you must also handle token caching and token refreshing.
*
* @param scopes - The list of scopes for which the token will have access.
* @param options - The options used to configure any requests this
* `TokenCredential` implementation might make.
*/
async getToken(scopes, options = {}) {
const { token } = await this.getTokenInternal(scopes, options);
return token;
}
async getTokenInternal(scopes, options = {}) {
let token = null;
let successfulCredential;
const errors = [];
return tracingClient.withSpan("ChainedTokenCredential.getToken", options, async (updatedOptions) => {
for (let i = 0; i < this._sources.length && token === null; i++) {
try {
token = await this._sources[i].getToken(scopes, updatedOptions);
successfulCredential = this._sources[i];
}
catch (err) {
if (err.name === "CredentialUnavailableError" ||
err.name === "AuthenticationRequiredError") {
errors.push(err);
}
else {
logger.getToken.info(formatError(scopes, err));
throw err;
}
}
}
if (!token && errors.length > 0) {
const err = new AggregateAuthenticationError(errors, "ChainedTokenCredential authentication failed.");
logger.getToken.info(formatError(scopes, err));
throw err;
}
logger.getToken.info(`Result for ${successfulCredential.constructor.name}: ${formatSuccess(scopes)}`);
if (token === null) {
throw new CredentialUnavailableError("Failed to retrieve a valid token");
}
return { token, successfulCredential };
});
}
}
//# sourceMappingURL=chainedTokenCredential.js.map

View File

@@ -0,0 +1,55 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import { createMsalClient } from "../msal/nodeFlows/msalClient.js";
import { processMultiTenantRequest, resolveAdditionallyAllowedTenantIds, } from "../util/tenantIdUtils.js";
import { CredentialUnavailableError } from "../errors.js";
import { credentialLogger } from "../util/logging.js";
import { tracingClient } from "../util/tracing.js";
const logger = credentialLogger("ClientAssertionCredential");
/**
* Authenticates a service principal with a JWT assertion.
*/
export class ClientAssertionCredential {
/**
* Creates an instance of the ClientAssertionCredential with the details
* needed to authenticate against Microsoft Entra ID with a client
* assertion provided by the developer through the `getAssertion` function parameter.
*
* @param tenantId - The Microsoft Entra tenant (directory) ID.
* @param clientId - The client (application) ID of an App Registration in the tenant.
* @param getAssertion - A function that retrieves the assertion for the credential to use.
* @param options - Options for configuring the client which makes the authentication request.
*/
constructor(tenantId, clientId, getAssertion, options = {}) {
if (!tenantId) {
throw new CredentialUnavailableError("ClientAssertionCredential: tenantId is a required parameter.");
}
if (!clientId) {
throw new CredentialUnavailableError("ClientAssertionCredential: clientId is a required parameter.");
}
if (!getAssertion) {
throw new CredentialUnavailableError("ClientAssertionCredential: clientAssertion is a required parameter.");
}
this.tenantId = tenantId;
this.additionallyAllowedTenantIds = resolveAdditionallyAllowedTenantIds(options === null || options === void 0 ? void 0 : options.additionallyAllowedTenants);
this.options = options;
this.getAssertion = getAssertion;
this.msalClient = createMsalClient(clientId, tenantId, Object.assign(Object.assign({}, options), { logger, tokenCredentialOptions: this.options }));
}
/**
* Authenticates with Microsoft Entra ID and returns an access token if successful.
* If authentication fails, a {@link CredentialUnavailableError} will be thrown with the details of the failure.
*
* @param scopes - The list of scopes for which the token will have access.
* @param options - The options used to configure any requests this
* TokenCredential implementation might make.
*/
async getToken(scopes, options = {}) {
return tracingClient.withSpan(`${this.constructor.name}.getToken`, options, async (newOptions) => {
newOptions.tenantId = processMultiTenantRequest(this.tenantId, newOptions, this.additionallyAllowedTenantIds, logger);
const arrayScopes = Array.isArray(scopes) ? scopes : [scopes];
return this.msalClient.getTokenByClientAssertion(arrayScopes, this.getAssertion, newOptions);
});
}
}
//# sourceMappingURL=clientAssertionCredential.js.map

View File

@@ -0,0 +1,128 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import { createMsalClient } from "../msal/nodeFlows/msalClient.js";
import { createHash, createPrivateKey } from "node:crypto";
import { processMultiTenantRequest, resolveAdditionallyAllowedTenantIds, } from "../util/tenantIdUtils.js";
import { credentialLogger } from "../util/logging.js";
import { readFile } from "node:fs/promises";
import { tracingClient } from "../util/tracing.js";
const credentialName = "ClientCertificateCredential";
const logger = credentialLogger(credentialName);
/**
* Enables authentication to Microsoft Entra ID using a PEM-encoded
* certificate that is assigned to an App Registration. More information
* on how to configure certificate authentication can be found here:
*
* https://learn.microsoft.com/azure/active-directory/develop/active-directory-certificate-credentials#register-your-certificate-with-azure-ad
*
*/
export class ClientCertificateCredential {
constructor(tenantId, clientId, certificatePathOrConfiguration, options = {}) {
if (!tenantId || !clientId) {
throw new Error(`${credentialName}: tenantId and clientId are required parameters.`);
}
this.tenantId = tenantId;
this.additionallyAllowedTenantIds = resolveAdditionallyAllowedTenantIds(options === null || options === void 0 ? void 0 : options.additionallyAllowedTenants);
this.sendCertificateChain = options.sendCertificateChain;
this.certificateConfiguration = Object.assign({}, (typeof certificatePathOrConfiguration === "string"
? {
certificatePath: certificatePathOrConfiguration,
}
: certificatePathOrConfiguration));
const certificate = this.certificateConfiguration
.certificate;
const certificatePath = this.certificateConfiguration
.certificatePath;
if (!this.certificateConfiguration || !(certificate || certificatePath)) {
throw new Error(`${credentialName}: Provide either a PEM certificate in string form, or the path to that certificate in the filesystem. To troubleshoot, visit https://aka.ms/azsdk/js/identity/serviceprincipalauthentication/troubleshoot.`);
}
if (certificate && certificatePath) {
throw new Error(`${credentialName}: To avoid unexpected behaviors, providing both the contents of a PEM certificate and the path to a PEM certificate is forbidden. To troubleshoot, visit https://aka.ms/azsdk/js/identity/serviceprincipalauthentication/troubleshoot.`);
}
this.msalClient = createMsalClient(clientId, tenantId, Object.assign(Object.assign({}, options), { logger, tokenCredentialOptions: options }));
}
/**
* Authenticates with Microsoft Entra ID and returns an access token if successful.
* If authentication fails, a {@link CredentialUnavailableError} will be thrown with the details of the failure.
*
* @param scopes - The list of scopes for which the token will have access.
* @param options - The options used to configure any requests this
* TokenCredential implementation might make.
*/
async getToken(scopes, options = {}) {
return tracingClient.withSpan(`${credentialName}.getToken`, options, async (newOptions) => {
newOptions.tenantId = processMultiTenantRequest(this.tenantId, newOptions, this.additionallyAllowedTenantIds, logger);
const arrayScopes = Array.isArray(scopes) ? scopes : [scopes];
const certificate = await this.buildClientCertificate();
return this.msalClient.getTokenByClientCertificate(arrayScopes, certificate, newOptions);
});
}
async buildClientCertificate() {
var _a;
const parts = await parseCertificate(this.certificateConfiguration, (_a = this.sendCertificateChain) !== null && _a !== void 0 ? _a : false);
let privateKey;
if (this.certificateConfiguration.certificatePassword !== undefined) {
privateKey = createPrivateKey({
key: parts.certificateContents,
passphrase: this.certificateConfiguration.certificatePassword,
format: "pem",
})
.export({
format: "pem",
type: "pkcs8",
})
.toString();
}
else {
privateKey = parts.certificateContents;
}
return {
thumbprint: parts.thumbprint,
thumbprintSha256: parts.thumbprintSha256,
privateKey,
x5c: parts.x5c,
};
}
}
/**
* Parses a certificate into its relevant parts
*
* @param certificateConfiguration - The certificate contents or path to the certificate
* @param sendCertificateChain - true if the entire certificate chain should be sent for SNI, false otherwise
* @returns The parsed certificate parts and the certificate contents
*/
export async function parseCertificate(certificateConfiguration, sendCertificateChain) {
const certificate = certificateConfiguration.certificate;
const certificatePath = certificateConfiguration
.certificatePath;
const certificateContents = certificate || (await readFile(certificatePath, "utf8"));
const x5c = sendCertificateChain ? certificateContents : undefined;
const certificatePattern = /(-+BEGIN CERTIFICATE-+)(\n\r?|\r\n?)([A-Za-z0-9+/\n\r]+=*)(\n\r?|\r\n?)(-+END CERTIFICATE-+)/g;
const publicKeys = [];
// Match all possible certificates, in the order they are in the file. These will form the chain that is used for x5c
let match;
do {
match = certificatePattern.exec(certificateContents);
if (match) {
publicKeys.push(match[3]);
}
} while (match);
if (publicKeys.length === 0) {
throw new Error("The file at the specified path does not contain a PEM-encoded certificate.");
}
const thumbprint = createHash("sha1")
.update(Buffer.from(publicKeys[0], "base64"))
.digest("hex")
.toUpperCase();
const thumbprintSha256 = createHash("sha256")
.update(Buffer.from(publicKeys[0], "base64"))
.digest("hex")
.toUpperCase();
return {
certificateContents,
thumbprintSha256,
thumbprint,
x5c,
};
}
//# sourceMappingURL=clientCertificateCredential.js.map

View File

@@ -0,0 +1,60 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import { createMsalClient } from "../msal/nodeFlows/msalClient.js";
import { processMultiTenantRequest, resolveAdditionallyAllowedTenantIds, } from "../util/tenantIdUtils.js";
import { CredentialUnavailableError } from "../errors.js";
import { credentialLogger } from "../util/logging.js";
import { ensureScopes } from "../util/scopeUtils.js";
import { tracingClient } from "../util/tracing.js";
const logger = credentialLogger("ClientSecretCredential");
/**
* Enables authentication to Microsoft Entra ID using a client secret
* that was generated for an App Registration. More information on how
* to configure a client secret can be found here:
*
* https://learn.microsoft.com/entra/identity-platform/quickstart-configure-app-access-web-apis#add-credentials-to-your-web-application
*
*/
export class ClientSecretCredential {
/**
* Creates an instance of the ClientSecretCredential with the details
* needed to authenticate against Microsoft Entra ID with a client
* secret.
*
* @param tenantId - The Microsoft Entra tenant (directory) ID.
* @param clientId - The client (application) ID of an App Registration in the tenant.
* @param clientSecret - A client secret that was generated for the App Registration.
* @param options - Options for configuring the client which makes the authentication request.
*/
constructor(tenantId, clientId, clientSecret, options = {}) {
if (!tenantId) {
throw new CredentialUnavailableError("ClientSecretCredential: tenantId is a required parameter. To troubleshoot, visit https://aka.ms/azsdk/js/identity/serviceprincipalauthentication/troubleshoot.");
}
if (!clientId) {
throw new CredentialUnavailableError("ClientSecretCredential: clientId is a required parameter. To troubleshoot, visit https://aka.ms/azsdk/js/identity/serviceprincipalauthentication/troubleshoot.");
}
if (!clientSecret) {
throw new CredentialUnavailableError("ClientSecretCredential: clientSecret is a required parameter. To troubleshoot, visit https://aka.ms/azsdk/js/identity/serviceprincipalauthentication/troubleshoot.");
}
this.clientSecret = clientSecret;
this.tenantId = tenantId;
this.additionallyAllowedTenantIds = resolveAdditionallyAllowedTenantIds(options === null || options === void 0 ? void 0 : options.additionallyAllowedTenants);
this.msalClient = createMsalClient(clientId, tenantId, Object.assign(Object.assign({}, options), { logger, tokenCredentialOptions: options }));
}
/**
* Authenticates with Microsoft Entra ID and returns an access token if successful.
* If authentication fails, a {@link CredentialUnavailableError} will be thrown with the details of the failure.
*
* @param scopes - The list of scopes for which the token will have access.
* @param options - The options used to configure any requests this
* TokenCredential implementation might make.
*/
async getToken(scopes, options = {}) {
return tracingClient.withSpan(`${this.constructor.name}.getToken`, options, async (newOptions) => {
newOptions.tenantId = processMultiTenantRequest(this.tenantId, newOptions, this.additionallyAllowedTenantIds, logger);
const arrayScopes = ensureScopes(scopes);
return this.msalClient.getTokenByClientSecret(arrayScopes, this.clientSecret, newOptions);
});
}
}
//# sourceMappingURL=clientSecretCredential.js.map

View File

@@ -0,0 +1,196 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import { ManagedIdentityCredential } from "./managedIdentityCredential/index.js";
import { AzureCliCredential } from "./azureCliCredential.js";
import { AzureDeveloperCliCredential } from "./azureDeveloperCliCredential.js";
import { AzurePowerShellCredential } from "./azurePowerShellCredential.js";
import { ChainedTokenCredential } from "./chainedTokenCredential.js";
import { EnvironmentCredential } from "./environmentCredential.js";
import { WorkloadIdentityCredential } from "./workloadIdentityCredential.js";
import { credentialLogger } from "../util/logging.js";
const logger = credentialLogger("DefaultAzureCredential");
/**
* Creates a {@link ManagedIdentityCredential} from the provided options.
* @param options - Options to configure the credential.
*
* @internal
*/
export function createDefaultManagedIdentityCredential(options = {}) {
var _a, _b, _c, _d;
(_a = options.retryOptions) !== null && _a !== void 0 ? _a : (options.retryOptions = {
maxRetries: 5,
retryDelayInMs: 800,
});
const managedIdentityClientId = (_b = options === null || options === void 0 ? void 0 : options.managedIdentityClientId) !== null && _b !== void 0 ? _b : process.env.AZURE_CLIENT_ID;
const workloadIdentityClientId = (_c = options === null || options === void 0 ? void 0 : options.workloadIdentityClientId) !== null && _c !== void 0 ? _c : managedIdentityClientId;
const managedResourceId = options === null || options === void 0 ? void 0 : options.managedIdentityResourceId;
const workloadFile = process.env.AZURE_FEDERATED_TOKEN_FILE;
const tenantId = (_d = options === null || options === void 0 ? void 0 : options.tenantId) !== null && _d !== void 0 ? _d : process.env.AZURE_TENANT_ID;
if (managedResourceId) {
const managedIdentityResourceIdOptions = Object.assign(Object.assign({}, options), { resourceId: managedResourceId });
return new ManagedIdentityCredential(managedIdentityResourceIdOptions);
}
if (workloadFile && workloadIdentityClientId) {
const workloadIdentityCredentialOptions = Object.assign(Object.assign({}, options), { tenantId: tenantId });
return new ManagedIdentityCredential(workloadIdentityClientId, workloadIdentityCredentialOptions);
}
if (managedIdentityClientId) {
const managedIdentityClientOptions = Object.assign(Object.assign({}, options), { clientId: managedIdentityClientId });
return new ManagedIdentityCredential(managedIdentityClientOptions);
}
// We may be able to return a UnavailableCredential here, but that may be a breaking change
return new ManagedIdentityCredential(options);
}
/**
* Creates a {@link WorkloadIdentityCredential} from the provided options.
* @param options - Options to configure the credential.
*
* @internal
*/
function createDefaultWorkloadIdentityCredential(options) {
var _a, _b, _c;
const managedIdentityClientId = (_a = options === null || options === void 0 ? void 0 : options.managedIdentityClientId) !== null && _a !== void 0 ? _a : process.env.AZURE_CLIENT_ID;
const workloadIdentityClientId = (_b = options === null || options === void 0 ? void 0 : options.workloadIdentityClientId) !== null && _b !== void 0 ? _b : managedIdentityClientId;
const workloadFile = process.env.AZURE_FEDERATED_TOKEN_FILE;
const tenantId = (_c = options === null || options === void 0 ? void 0 : options.tenantId) !== null && _c !== void 0 ? _c : process.env.AZURE_TENANT_ID;
if (workloadFile && workloadIdentityClientId) {
const workloadIdentityCredentialOptions = Object.assign(Object.assign({}, options), { tenantId, clientId: workloadIdentityClientId, tokenFilePath: workloadFile });
return new WorkloadIdentityCredential(workloadIdentityCredentialOptions);
}
if (tenantId) {
const workloadIdentityClientTenantOptions = Object.assign(Object.assign({}, options), { tenantId });
return new WorkloadIdentityCredential(workloadIdentityClientTenantOptions);
}
// We may be able to return a UnavailableCredential here, but that may be a breaking change
return new WorkloadIdentityCredential(options);
}
/**
* Creates a {@link AzureDeveloperCliCredential} from the provided options.
* @param options - Options to configure the credential.
*
* @internal
*/
function createDefaultAzureDeveloperCliCredential(options = {}) {
const processTimeoutInMs = options.processTimeoutInMs;
return new AzureDeveloperCliCredential(Object.assign({ processTimeoutInMs }, options));
}
/**
* Creates a {@link AzureCliCredential} from the provided options.
* @param options - Options to configure the credential.
*
* @internal
*/
function createDefaultAzureCliCredential(options = {}) {
const processTimeoutInMs = options.processTimeoutInMs;
return new AzureCliCredential(Object.assign({ processTimeoutInMs }, options));
}
/**
* Creates a {@link AzurePowerShellCredential} from the provided options.
* @param options - Options to configure the credential.
*
* @internal
*/
function createDefaultAzurePowershellCredential(options = {}) {
const processTimeoutInMs = options.processTimeoutInMs;
return new AzurePowerShellCredential(Object.assign({ processTimeoutInMs }, options));
}
/**
* Creates an {@link EnvironmentCredential} from the provided options.
* @param options - Options to configure the credential.
*
* @internal
*/
export function createEnvironmentCredential(options = {}) {
return new EnvironmentCredential(options);
}
/**
* A no-op credential that logs the reason it was skipped if getToken is called.
* @internal
*/
export class UnavailableDefaultCredential {
constructor(credentialName, message) {
this.credentialName = credentialName;
this.credentialUnavailableErrorMessage = message;
}
getToken() {
logger.getToken.info(`Skipping ${this.credentialName}, reason: ${this.credentialUnavailableErrorMessage}`);
return Promise.resolve(null);
}
}
/**
* Provides a default {@link ChainedTokenCredential} configuration that works for most
* applications that use Azure SDK client libraries. For more information, see
* [DefaultAzureCredential overview](https://aka.ms/azsdk/js/identity/credential-chains#use-defaultazurecredential-for-flexibility).
*
* The following credential types will be tried, in order:
*
* - {@link EnvironmentCredential}
* - {@link WorkloadIdentityCredential}
* - {@link ManagedIdentityCredential}
* - {@link AzureCliCredential}
* - {@link AzurePowerShellCredential}
* - {@link AzureDeveloperCliCredential}
*
* Consult the documentation of these credential types for more information
* on how they attempt authentication.
*/
export class DefaultAzureCredential extends ChainedTokenCredential {
constructor(options) {
// If AZURE_TOKEN_CREDENTIALS is not set, use the default credential chain.
const azureTokenCredentials = process.env.AZURE_TOKEN_CREDENTIALS
? process.env.AZURE_TOKEN_CREDENTIALS.trim().toLowerCase()
: undefined;
const devCredentialFunctions = [
createDefaultAzureCliCredential,
createDefaultAzurePowershellCredential,
createDefaultAzureDeveloperCliCredential,
];
const prodCredentialFunctions = [
createEnvironmentCredential,
createDefaultWorkloadIdentityCredential,
createDefaultManagedIdentityCredential,
];
let credentialFunctions = [];
// If AZURE_TOKEN_CREDENTIALS is set, use it to determine which credentials to use.
// The value of AZURE_TOKEN_CREDENTIALS should be either "dev" or "prod".
if (azureTokenCredentials) {
switch (azureTokenCredentials) {
case "dev":
// If AZURE_TOKEN_CREDENTIALS is set to "dev", use the developer tool-based credential chain.
credentialFunctions = devCredentialFunctions;
break;
case "prod":
// If AZURE_TOKEN_CREDENTIALS is set to "prod", use the production credential chain.
credentialFunctions = prodCredentialFunctions;
break;
default: {
// If AZURE_TOKEN_CREDENTIALS is set to an unsupported value, throw an error.
// We will throw an error here to prevent the creation of the DefaultAzureCredential.
const errorMessage = `Invalid value for AZURE_TOKEN_CREDENTIALS = ${process.env.AZURE_TOKEN_CREDENTIALS}. Valid values are 'prod' or 'dev'.`;
logger.warning(errorMessage);
throw new Error(errorMessage);
}
}
}
else {
// If AZURE_TOKEN_CREDENTIALS is not set, use the default credential chain.
credentialFunctions = [...prodCredentialFunctions, ...devCredentialFunctions];
}
// Errors from individual credentials should not be thrown in the DefaultAzureCredential constructor, instead throwing on getToken() which is handled by ChainedTokenCredential.
// When adding new credentials to the default chain, consider:
// 1. Making the constructor parameters required and explicit
// 2. Validating any required parameters in the factory function
// 3. Returning a UnavailableDefaultCredential from the factory function if a credential is unavailable for any reason
const credentials = credentialFunctions.map((createCredentialFn) => {
try {
return createCredentialFn(options);
}
catch (err) {
logger.warning(`Skipped ${createCredentialFn.name} because of an error creating the credential: ${err}`);
return new UnavailableDefaultCredential(createCredentialFn.name, err.message);
}
});
super(...credentials);
}
}
//# sourceMappingURL=defaultAzureCredential.js.map

View File

@@ -0,0 +1,91 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import { processMultiTenantRequest, resolveAdditionallyAllowedTenantIds, resolveTenantId, } from "../util/tenantIdUtils.js";
import { credentialLogger } from "../util/logging.js";
import { ensureScopes } from "../util/scopeUtils.js";
import { tracingClient } from "../util/tracing.js";
import { createMsalClient } from "../msal/nodeFlows/msalClient.js";
import { DeveloperSignOnClientId } from "../constants.js";
const logger = credentialLogger("DeviceCodeCredential");
/**
* Method that logs the user code from the DeviceCodeCredential.
* @param deviceCodeInfo - The device code.
*/
export function defaultDeviceCodePromptCallback(deviceCodeInfo) {
console.log(deviceCodeInfo.message);
}
/**
* Enables authentication to Microsoft Entra ID using a device code
* that the user can enter into https://microsoft.com/devicelogin.
*/
export class DeviceCodeCredential {
/**
* Creates an instance of DeviceCodeCredential with the details needed
* to initiate the device code authorization flow with Microsoft Entra ID.
*
* A message will be logged, giving users a code that they can use to authenticate once they go to https://microsoft.com/devicelogin
*
* Developers can configure how this message is shown by passing a custom `userPromptCallback`:
*
* ```ts snippet:device_code_credential_example
* import { DeviceCodeCredential } from "@azure/identity";
*
* const credential = new DeviceCodeCredential({
* tenantId: process.env.AZURE_TENANT_ID,
* clientId: process.env.AZURE_CLIENT_ID,
* userPromptCallback: (info) => {
* console.log("CUSTOMIZED PROMPT CALLBACK", info.message);
* },
* });
* ```
*
* @param options - Options for configuring the client which makes the authentication requests.
*/
constructor(options) {
var _a, _b;
this.tenantId = options === null || options === void 0 ? void 0 : options.tenantId;
this.additionallyAllowedTenantIds = resolveAdditionallyAllowedTenantIds(options === null || options === void 0 ? void 0 : options.additionallyAllowedTenants);
const clientId = (_a = options === null || options === void 0 ? void 0 : options.clientId) !== null && _a !== void 0 ? _a : DeveloperSignOnClientId;
const tenantId = resolveTenantId(logger, options === null || options === void 0 ? void 0 : options.tenantId, clientId);
this.userPromptCallback = (_b = options === null || options === void 0 ? void 0 : options.userPromptCallback) !== null && _b !== void 0 ? _b : defaultDeviceCodePromptCallback;
this.msalClient = createMsalClient(clientId, tenantId, Object.assign(Object.assign({}, options), { logger, tokenCredentialOptions: options || {} }));
this.disableAutomaticAuthentication = options === null || options === void 0 ? void 0 : options.disableAutomaticAuthentication;
}
/**
* Authenticates with Microsoft Entra ID and returns an access token if successful.
* If authentication fails, a {@link CredentialUnavailableError} will be thrown with the details of the failure.
*
* If the user provided the option `disableAutomaticAuthentication`,
* once the token can't be retrieved silently,
* this method won't attempt to request user interaction to retrieve the token.
*
* @param scopes - The list of scopes for which the token will have access.
* @param options - The options used to configure any requests this
* TokenCredential implementation might make.
*/
async getToken(scopes, options = {}) {
return tracingClient.withSpan(`${this.constructor.name}.getToken`, options, async (newOptions) => {
newOptions.tenantId = processMultiTenantRequest(this.tenantId, newOptions, this.additionallyAllowedTenantIds, logger);
const arrayScopes = ensureScopes(scopes);
return this.msalClient.getTokenByDeviceCode(arrayScopes, this.userPromptCallback, Object.assign(Object.assign({}, newOptions), { disableAutomaticAuthentication: this.disableAutomaticAuthentication }));
});
}
/**
* Authenticates with Microsoft Entra ID and returns an access token if successful.
* If authentication fails, a {@link CredentialUnavailableError} will be thrown with the details of the failure.
*
* If the token can't be retrieved silently, this method will always generate a challenge for the user.
*
* @param scopes - The list of scopes for which the token will have access.
* @param options - The options used to configure any requests this
* TokenCredential implementation might make.
*/
async authenticate(scopes, options = {}) {
return tracingClient.withSpan(`${this.constructor.name}.authenticate`, options, async (newOptions) => {
const arrayScopes = Array.isArray(scopes) ? scopes : [scopes];
await this.msalClient.getTokenByDeviceCode(arrayScopes, this.userPromptCallback, Object.assign(Object.assign({}, newOptions), { disableAutomaticAuthentication: false }));
return this.msalClient.getActiveAccount();
});
}
}
//# sourceMappingURL=deviceCodeCredential.js.map

View File

@@ -0,0 +1,130 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import { AuthenticationError, CredentialUnavailableError } from "../errors.js";
import { credentialLogger, formatError, formatSuccess, processEnvVars } from "../util/logging.js";
import { ClientCertificateCredential } from "./clientCertificateCredential.js";
import { ClientSecretCredential } from "./clientSecretCredential.js";
import { UsernamePasswordCredential } from "./usernamePasswordCredential.js";
import { checkTenantId } from "../util/tenantIdUtils.js";
import { tracingClient } from "../util/tracing.js";
/**
* Contains the list of all supported environment variable names so that an
* appropriate error message can be generated when no credentials can be
* configured.
*
* @internal
*/
export const AllSupportedEnvironmentVariables = [
"AZURE_TENANT_ID",
"AZURE_CLIENT_ID",
"AZURE_CLIENT_SECRET",
"AZURE_CLIENT_CERTIFICATE_PATH",
"AZURE_CLIENT_CERTIFICATE_PASSWORD",
"AZURE_USERNAME",
"AZURE_PASSWORD",
"AZURE_ADDITIONALLY_ALLOWED_TENANTS",
"AZURE_CLIENT_SEND_CERTIFICATE_CHAIN",
];
function getAdditionallyAllowedTenants() {
var _a;
const additionallyAllowedValues = (_a = process.env.AZURE_ADDITIONALLY_ALLOWED_TENANTS) !== null && _a !== void 0 ? _a : "";
return additionallyAllowedValues.split(";");
}
const credentialName = "EnvironmentCredential";
const logger = credentialLogger(credentialName);
export function getSendCertificateChain() {
var _a;
const sendCertificateChain = ((_a = process.env.AZURE_CLIENT_SEND_CERTIFICATE_CHAIN) !== null && _a !== void 0 ? _a : "").toLowerCase();
const result = sendCertificateChain === "true" || sendCertificateChain === "1";
logger.verbose(`AZURE_CLIENT_SEND_CERTIFICATE_CHAIN: ${process.env.AZURE_CLIENT_SEND_CERTIFICATE_CHAIN}; sendCertificateChain: ${result}`);
return result;
}
/**
* Enables authentication to Microsoft Entra ID using a client secret or certificate.
*/
export class EnvironmentCredential {
/**
* Creates an instance of the EnvironmentCredential class and decides what credential to use depending on the available environment variables.
*
* Required environment variables:
* - `AZURE_TENANT_ID`: The Microsoft Entra tenant (directory) ID.
* - `AZURE_CLIENT_ID`: The client (application) ID of an App Registration in the tenant.
*
* If setting the AZURE_TENANT_ID, then you can also set the additionally allowed tenants
* - `AZURE_ADDITIONALLY_ALLOWED_TENANTS`: For multi-tenant applications, specifies additional tenants for which the credential may acquire tokens with a single semicolon delimited string. Use * to allow all tenants.
*
* Environment variables used for client credential authentication:
* - `AZURE_CLIENT_SECRET`: A client secret that was generated for the App Registration.
* - `AZURE_CLIENT_CERTIFICATE_PATH`: The path to a PEM certificate to use during the authentication, instead of the client secret.
* - `AZURE_CLIENT_CERTIFICATE_PASSWORD`: (optional) password for the certificate file.
* - `AZURE_CLIENT_SEND_CERTIFICATE_CHAIN`: (optional) indicates that the certificate chain should be set in x5c header to support subject name / issuer based authentication.
*
* Username and password authentication is deprecated, since it doesn't support multifactor authentication (MFA). See https://aka.ms/azsdk/identity/mfa for more details. Users can still provide environment variables for this authentication method:
* - `AZURE_USERNAME`: Username to authenticate with.
* - `AZURE_PASSWORD`: Password to authenticate with.
*
* If the environment variables required to perform the authentication are missing, a {@link CredentialUnavailableError} will be thrown.
* If the authentication fails, or if there's an unknown error, an {@link AuthenticationError} will be thrown.
*
* @param options - Options for configuring the client which makes the authentication request.
*/
constructor(options) {
// Keep track of any missing environment variables for error details
this._credential = undefined;
const assigned = processEnvVars(AllSupportedEnvironmentVariables).assigned.join(", ");
logger.info(`Found the following environment variables: ${assigned}`);
const tenantId = process.env.AZURE_TENANT_ID, clientId = process.env.AZURE_CLIENT_ID, clientSecret = process.env.AZURE_CLIENT_SECRET;
const additionallyAllowedTenantIds = getAdditionallyAllowedTenants();
const sendCertificateChain = getSendCertificateChain();
const newOptions = Object.assign(Object.assign({}, options), { additionallyAllowedTenantIds, sendCertificateChain });
if (tenantId) {
checkTenantId(logger, tenantId);
}
if (tenantId && clientId && clientSecret) {
logger.info(`Invoking ClientSecretCredential with tenant ID: ${tenantId}, clientId: ${clientId} and clientSecret: [REDACTED]`);
this._credential = new ClientSecretCredential(tenantId, clientId, clientSecret, newOptions);
return;
}
const certificatePath = process.env.AZURE_CLIENT_CERTIFICATE_PATH;
const certificatePassword = process.env.AZURE_CLIENT_CERTIFICATE_PASSWORD;
if (tenantId && clientId && certificatePath) {
logger.info(`Invoking ClientCertificateCredential with tenant ID: ${tenantId}, clientId: ${clientId} and certificatePath: ${certificatePath}`);
this._credential = new ClientCertificateCredential(tenantId, clientId, { certificatePath, certificatePassword }, newOptions);
return;
}
const username = process.env.AZURE_USERNAME;
const password = process.env.AZURE_PASSWORD;
if (tenantId && clientId && username && password) {
logger.info(`Invoking UsernamePasswordCredential with tenant ID: ${tenantId}, clientId: ${clientId} and username: ${username}`);
logger.warning("Environment is configured to use username and password authentication. This authentication method is deprecated, as it doesn't support multifactor authentication (MFA). Use a more secure credential. For more details, see https://aka.ms/azsdk/identity/mfa.");
this._credential = new UsernamePasswordCredential(tenantId, clientId, username, password, newOptions);
}
}
/**
* Authenticates with Microsoft Entra ID and returns an access token if successful.
*
* @param scopes - The list of scopes for which the token will have access.
* @param options - Optional parameters. See {@link GetTokenOptions}.
*/
async getToken(scopes, options = {}) {
return tracingClient.withSpan(`${credentialName}.getToken`, options, async (newOptions) => {
if (this._credential) {
try {
const result = await this._credential.getToken(scopes, newOptions);
logger.getToken.info(formatSuccess(scopes));
return result;
}
catch (err) {
const authenticationError = new AuthenticationError(400, {
error: `${credentialName} authentication failed. To troubleshoot, visit https://aka.ms/azsdk/js/identity/environmentcredential/troubleshoot.`,
error_description: err.message.toString().split("More details:").join(""),
});
logger.getToken.info(formatError(scopes, authenticationError));
throw authenticationError;
}
}
throw new CredentialUnavailableError(`${credentialName} is unavailable. No underlying credential could be used. To troubleshoot, visit https://aka.ms/azsdk/js/identity/environmentcredential/troubleshoot.`);
});
}
}
//# sourceMappingURL=environmentCredential.js.map

View File

@@ -0,0 +1,91 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import { processMultiTenantRequest, resolveAdditionallyAllowedTenantIds, resolveTenantId, } from "../util/tenantIdUtils.js";
import { credentialLogger } from "../util/logging.js";
import { ensureScopes } from "../util/scopeUtils.js";
import { tracingClient } from "../util/tracing.js";
import { createMsalClient } from "../msal/nodeFlows/msalClient.js";
import { DeveloperSignOnClientId } from "../constants.js";
const logger = credentialLogger("InteractiveBrowserCredential");
/**
* Enables authentication to Microsoft Entra ID inside of the web browser
* using the interactive login flow.
*/
export class InteractiveBrowserCredential {
/**
* Creates an instance of InteractiveBrowserCredential with the details needed.
*
* This credential uses the [Authorization Code Flow](https://learn.microsoft.com/entra/identity-platform/v2-oauth2-auth-code-flow).
* On Node.js, it will open a browser window while it listens for a redirect response from the authentication service.
* On browsers, it authenticates via popups. The `loginStyle` optional parameter can be set to `redirect` to authenticate by redirecting the user to an Azure secure login page, which then will redirect the user back to the web application where the authentication started.
*
* For Node.js, if a `clientId` is provided, the Microsoft Entra application will need to be configured to have a "Mobile and desktop applications" redirect endpoint.
* Follow our guide on [setting up Redirect URIs for Desktop apps that calls to web APIs](https://learn.microsoft.com/entra/identity-platform/scenario-desktop-app-registration#redirect-uris).
*
* @param options - Options for configuring the client which makes the authentication requests.
*/
constructor(options) {
var _a, _b, _c, _d, _e;
this.tenantId = resolveTenantId(logger, options.tenantId, options.clientId);
this.additionallyAllowedTenantIds = resolveAdditionallyAllowedTenantIds(options === null || options === void 0 ? void 0 : options.additionallyAllowedTenants);
const msalClientOptions = Object.assign(Object.assign({}, options), { tokenCredentialOptions: options, logger });
const ibcNodeOptions = options;
this.browserCustomizationOptions = ibcNodeOptions.browserCustomizationOptions;
this.loginHint = ibcNodeOptions.loginHint;
if ((_a = ibcNodeOptions === null || ibcNodeOptions === void 0 ? void 0 : ibcNodeOptions.brokerOptions) === null || _a === void 0 ? void 0 : _a.enabled) {
if (!((_b = ibcNodeOptions === null || ibcNodeOptions === void 0 ? void 0 : ibcNodeOptions.brokerOptions) === null || _b === void 0 ? void 0 : _b.parentWindowHandle)) {
throw new Error("In order to do WAM authentication, `parentWindowHandle` under `brokerOptions` is a required parameter");
}
else {
msalClientOptions.brokerOptions = {
enabled: true,
parentWindowHandle: ibcNodeOptions.brokerOptions.parentWindowHandle,
legacyEnableMsaPassthrough: (_c = ibcNodeOptions.brokerOptions) === null || _c === void 0 ? void 0 : _c.legacyEnableMsaPassthrough,
useDefaultBrokerAccount: (_d = ibcNodeOptions.brokerOptions) === null || _d === void 0 ? void 0 : _d.useDefaultBrokerAccount,
};
}
}
this.msalClient = createMsalClient((_e = options.clientId) !== null && _e !== void 0 ? _e : DeveloperSignOnClientId, this.tenantId, msalClientOptions);
this.disableAutomaticAuthentication = options === null || options === void 0 ? void 0 : options.disableAutomaticAuthentication;
}
/**
* Authenticates with Microsoft Entra ID and returns an access token if successful.
* If authentication fails, a {@link CredentialUnavailableError} will be thrown with the details of the failure.
*
* If the user provided the option `disableAutomaticAuthentication`,
* once the token can't be retrieved silently,
* this method won't attempt to request user interaction to retrieve the token.
*
* @param scopes - The list of scopes for which the token will have access.
* @param options - The options used to configure any requests this
* TokenCredential implementation might make.
*/
async getToken(scopes, options = {}) {
return tracingClient.withSpan(`${this.constructor.name}.getToken`, options, async (newOptions) => {
newOptions.tenantId = processMultiTenantRequest(this.tenantId, newOptions, this.additionallyAllowedTenantIds, logger);
const arrayScopes = ensureScopes(scopes);
return this.msalClient.getTokenByInteractiveRequest(arrayScopes, Object.assign(Object.assign({}, newOptions), { disableAutomaticAuthentication: this.disableAutomaticAuthentication, browserCustomizationOptions: this.browserCustomizationOptions, loginHint: this.loginHint }));
});
}
/**
* Authenticates with Microsoft Entra ID and returns an access token if successful.
* If authentication fails, a {@link CredentialUnavailableError} will be thrown with the details of the failure.
*
* If the token can't be retrieved silently, this method will always generate a challenge for the user.
*
* On Node.js, this credential has [Proof Key for Code Exchange (PKCE)](https://datatracker.ietf.org/doc/html/rfc7636) enabled by default.
* PKCE is a security feature that mitigates authentication code interception attacks.
*
* @param scopes - The list of scopes for which the token will have access.
* @param options - The options used to configure any requests this
* TokenCredential implementation might make.
*/
async authenticate(scopes, options = {}) {
return tracingClient.withSpan(`${this.constructor.name}.authenticate`, options, async (newOptions) => {
const arrayScopes = ensureScopes(scopes);
await this.msalClient.getTokenByInteractiveRequest(arrayScopes, Object.assign(Object.assign({}, newOptions), { disableAutomaticAuthentication: false, browserCustomizationOptions: this.browserCustomizationOptions, loginHint: this.loginHint }));
return this.msalClient.getActiveAccount();
});
}
}
//# sourceMappingURL=interactiveBrowserCredential.js.map

View File

@@ -0,0 +1,99 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import { createHttpHeaders, createPipelineRequest } from "@azure/core-rest-pipeline";
import { isError } from "@azure/core-util";
import { credentialLogger } from "../../util/logging.js";
import { mapScopesToResource } from "./utils.js";
import { tracingClient } from "../../util/tracing.js";
const msiName = "ManagedIdentityCredential - IMDS";
const logger = credentialLogger(msiName);
const imdsHost = "http://169.254.169.254";
const imdsEndpointPath = "/metadata/identity/oauth2/token";
/**
* Generates an invalid request options to get a response quickly from IMDS endpoint.
* The response indicates the availability of IMSD service; otherwise the request would time out.
*/
function prepareInvalidRequestOptions(scopes) {
var _a;
const resource = mapScopesToResource(scopes);
if (!resource) {
throw new Error(`${msiName}: Multiple scopes are not supported.`);
}
// Pod Identity will try to process this request even if the Metadata header is missing.
// We can exclude the request query to ensure no IMDS endpoint tries to process the ping request.
const url = new URL(imdsEndpointPath, (_a = process.env.AZURE_POD_IDENTITY_AUTHORITY_HOST) !== null && _a !== void 0 ? _a : imdsHost);
const rawHeaders = {
Accept: "application/json",
// intentionally leave out the Metadata header to invoke an error from IMDS endpoint.
};
return {
// intentionally not including any query
url: `${url}`,
method: "GET",
headers: createHttpHeaders(rawHeaders),
};
}
/**
* Defines how to determine whether the Azure IMDS MSI is available.
*
* Actually getting the token once we determine IMDS is available is handled by MSAL.
*/
export const imdsMsi = {
name: "imdsMsi",
async isAvailable(options) {
const { scopes, identityClient, getTokenOptions } = options;
const resource = mapScopesToResource(scopes);
if (!resource) {
logger.info(`${msiName}: Unavailable. Multiple scopes are not supported.`);
return false;
}
// if the PodIdentityEndpoint environment variable was set no need to probe the endpoint, it can be assumed to exist
if (process.env.AZURE_POD_IDENTITY_AUTHORITY_HOST) {
return true;
}
if (!identityClient) {
throw new Error("Missing IdentityClient");
}
const requestOptions = prepareInvalidRequestOptions(resource);
return tracingClient.withSpan("ManagedIdentityCredential-pingImdsEndpoint", getTokenOptions !== null && getTokenOptions !== void 0 ? getTokenOptions : {}, async (updatedOptions) => {
var _a, _b;
requestOptions.tracingOptions = updatedOptions.tracingOptions;
// Create a request with a timeout since we expect that
// not having a "Metadata" header should cause an error to be
// returned quickly from the endpoint, proving its availability.
const request = createPipelineRequest(requestOptions);
// Default to 1000 if the default of 0 is used.
// Negative values can still be used to disable the timeout.
request.timeout = ((_a = updatedOptions.requestOptions) === null || _a === void 0 ? void 0 : _a.timeout) || 1000;
// This MSI uses the imdsEndpoint to get the token, which only uses http://
request.allowInsecureConnection = true;
let response;
try {
logger.info(`${msiName}: Pinging the Azure IMDS endpoint`);
response = await identityClient.sendRequest(request);
}
catch (err) {
// If the request failed, or Node.js was unable to establish a connection,
// or the host was down, we'll assume the IMDS endpoint isn't available.
if (isError(err)) {
logger.verbose(`${msiName}: Caught error ${err.name}: ${err.message}`);
}
// This is a special case for Docker Desktop which responds with a 403 with a message that contains "A socket operation was attempted to an unreachable network" or "A socket operation was attempted to an unreachable host"
// rather than just timing out, as expected.
logger.info(`${msiName}: The Azure IMDS endpoint is unavailable`);
return false;
}
if (response.status === 403) {
if ((_b = response.bodyAsText) === null || _b === void 0 ? void 0 : _b.includes("unreachable")) {
logger.info(`${msiName}: The Azure IMDS endpoint is unavailable`);
logger.info(`${msiName}: ${response.bodyAsText}`);
return false;
}
}
// If we received any response, the endpoint is available
logger.info(`${msiName}: The Azure IMDS endpoint is available`);
return true;
});
},
};
//# sourceMappingURL=imdsMsi.js.map

View File

@@ -0,0 +1,33 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import { retryPolicy } from "@azure/core-rest-pipeline";
import { calculateRetryDelay } from "@azure/core-util";
// Matches the default retry configuration in expontentialRetryStrategy.ts
const DEFAULT_CLIENT_MAX_RETRY_INTERVAL = 1000 * 64;
/**
* An additional policy that retries on 404 errors. The default retry policy does not retry on
* 404s, but the IMDS endpoint can return 404s when the token is not yet available. This policy
* will retry on 404s with an exponential backoff.
*
* @param msiRetryConfig - The retry configuration for the MSI credential.
* @returns - The policy that will retry on 404s.
*/
export function imdsRetryPolicy(msiRetryConfig) {
return retryPolicy([
{
name: "imdsRetryPolicy",
retry: ({ retryCount, response }) => {
if ((response === null || response === void 0 ? void 0 : response.status) !== 404) {
return { skipStrategy: true };
}
return calculateRetryDelay(retryCount, {
retryDelayInMs: msiRetryConfig.startDelayInMs,
maxRetryDelayInMs: DEFAULT_CLIENT_MAX_RETRY_INTERVAL,
});
},
},
], {
maxRetries: msiRetryConfig.maxRetries,
});
}
//# sourceMappingURL=imdsRetryPolicy.js.map

View File

@@ -0,0 +1,239 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import { getLogLevel } from "@azure/logger";
import { ManagedIdentityApplication } from "@azure/msal-node";
import { IdentityClient } from "../../client/identityClient.js";
import { AuthenticationRequiredError, CredentialUnavailableError } from "../../errors.js";
import { getMSALLogLevel, defaultLoggerCallback } from "../../msal/utils.js";
import { imdsRetryPolicy } from "./imdsRetryPolicy.js";
import { formatSuccess, formatError, credentialLogger } from "../../util/logging.js";
import { tracingClient } from "../../util/tracing.js";
import { imdsMsi } from "./imdsMsi.js";
import { tokenExchangeMsi } from "./tokenExchangeMsi.js";
import { mapScopesToResource, serviceFabricErrorMessage } from "./utils.js";
const logger = credentialLogger("ManagedIdentityCredential");
/**
* Attempts authentication using a managed identity available at the deployment environment.
* This authentication type works in Azure VMs, App Service instances, Azure Functions applications,
* Azure Kubernetes Services, Azure Service Fabric instances and inside of the Azure Cloud Shell.
*
* More information about configuring managed identities can be found here:
* https://learn.microsoft.com/azure/active-directory/managed-identities-azure-resources/overview
*/
export class ManagedIdentityCredential {
/**
* @internal
* @hidden
*/
constructor(clientIdOrOptions, options) {
var _a, _b;
this.msiRetryConfig = {
maxRetries: 5,
startDelayInMs: 800,
intervalIncrement: 2,
};
let _options;
if (typeof clientIdOrOptions === "string") {
this.clientId = clientIdOrOptions;
_options = options !== null && options !== void 0 ? options : {};
}
else {
this.clientId = clientIdOrOptions === null || clientIdOrOptions === void 0 ? void 0 : clientIdOrOptions.clientId;
_options = clientIdOrOptions !== null && clientIdOrOptions !== void 0 ? clientIdOrOptions : {};
}
this.resourceId = _options === null || _options === void 0 ? void 0 : _options.resourceId;
this.objectId = _options === null || _options === void 0 ? void 0 : _options.objectId;
// For JavaScript users.
const providedIds = [
{ key: "clientId", value: this.clientId },
{ key: "resourceId", value: this.resourceId },
{ key: "objectId", value: this.objectId },
].filter((id) => id.value);
if (providedIds.length > 1) {
throw new Error(`ManagedIdentityCredential: only one of 'clientId', 'resourceId', or 'objectId' can be provided. Received values: ${JSON.stringify({ clientId: this.clientId, resourceId: this.resourceId, objectId: this.objectId })}`);
}
// ManagedIdentity uses http for local requests
_options.allowInsecureConnection = true;
if (((_a = _options.retryOptions) === null || _a === void 0 ? void 0 : _a.maxRetries) !== undefined) {
this.msiRetryConfig.maxRetries = _options.retryOptions.maxRetries;
}
this.identityClient = new IdentityClient(Object.assign(Object.assign({}, _options), { additionalPolicies: [{ policy: imdsRetryPolicy(this.msiRetryConfig), position: "perCall" }] }));
this.managedIdentityApp = new ManagedIdentityApplication({
managedIdentityIdParams: {
userAssignedClientId: this.clientId,
userAssignedResourceId: this.resourceId,
userAssignedObjectId: this.objectId,
},
system: {
disableInternalRetries: true,
networkClient: this.identityClient,
loggerOptions: {
logLevel: getMSALLogLevel(getLogLevel()),
piiLoggingEnabled: (_b = _options.loggingOptions) === null || _b === void 0 ? void 0 : _b.enableUnsafeSupportLogging,
loggerCallback: defaultLoggerCallback(logger),
},
},
});
this.isAvailableIdentityClient = new IdentityClient(Object.assign(Object.assign({}, _options), { retryOptions: {
maxRetries: 0,
} }));
const managedIdentitySource = this.managedIdentityApp.getManagedIdentitySource();
// CloudShell MSI will ignore any user-assigned identity passed as parameters. To avoid confusion, we prevent this from happening as early as possible.
if (managedIdentitySource === "CloudShell") {
if (this.clientId || this.resourceId || this.objectId) {
logger.warning(`CloudShell MSI detected with user-provided IDs - throwing. Received values: ${JSON.stringify({
clientId: this.clientId,
resourceId: this.resourceId,
objectId: this.objectId,
})}.`);
throw new CredentialUnavailableError("ManagedIdentityCredential: Specifying a user-assigned managed identity is not supported for CloudShell at runtime. When using Managed Identity in CloudShell, omit the clientId, resourceId, and objectId parameters.");
}
}
// ServiceFabric does not support specifying user-assigned managed identity by client ID or resource ID. The managed identity selected is based on the resource configuration.
if (managedIdentitySource === "ServiceFabric") {
if (this.clientId || this.resourceId || this.objectId) {
logger.warning(`Service Fabric detected with user-provided IDs - throwing. Received values: ${JSON.stringify({
clientId: this.clientId,
resourceId: this.resourceId,
objectId: this.objectId,
})}.`);
throw new CredentialUnavailableError(`ManagedIdentityCredential: ${serviceFabricErrorMessage}`);
}
}
logger.info(`Using ${managedIdentitySource} managed identity.`);
// Check if either clientId, resourceId or objectId was provided and log the value used
if (providedIds.length === 1) {
const { key, value } = providedIds[0];
logger.info(`${managedIdentitySource} with ${key}: ${value}`);
}
}
/**
* Authenticates with Microsoft Entra ID and returns an access token if successful.
* If authentication fails, a {@link CredentialUnavailableError} will be thrown with the details of the failure.
* If an unexpected error occurs, an {@link AuthenticationError} will be thrown with the details of the failure.
*
* @param scopes - The list of scopes for which the token will have access.
* @param options - The options used to configure any requests this
* TokenCredential implementation might make.
*/
async getToken(scopes, options = {}) {
logger.getToken.info("Using the MSAL provider for Managed Identity.");
const resource = mapScopesToResource(scopes);
if (!resource) {
throw new CredentialUnavailableError(`ManagedIdentityCredential: Multiple scopes are not supported. Scopes: ${JSON.stringify(scopes)}`);
}
return tracingClient.withSpan("ManagedIdentityCredential.getToken", options, async () => {
var _a;
try {
const isTokenExchangeMsi = await tokenExchangeMsi.isAvailable(this.clientId);
// Most scenarios are handled by MSAL except for two:
// AKS pod identity - MSAL does not implement the token exchange flow.
// IMDS Endpoint probing - MSAL does not do any probing before trying to get a token.
// As a DefaultAzureCredential optimization we probe the IMDS endpoint with a short timeout and no retries before actually trying to get a token
// We will continue to implement these features in the Identity library.
const identitySource = this.managedIdentityApp.getManagedIdentitySource();
const isImdsMsi = identitySource === "DefaultToImds" || identitySource === "Imds"; // Neither actually checks that IMDS endpoint is available, just that it's the source the MSAL _would_ try to use.
logger.getToken.info(`MSAL Identity source: ${identitySource}`);
if (isTokenExchangeMsi) {
// In the AKS scenario we will use the existing tokenExchangeMsi indefinitely.
logger.getToken.info("Using the token exchange managed identity.");
const result = await tokenExchangeMsi.getToken({
scopes,
clientId: this.clientId,
identityClient: this.identityClient,
retryConfig: this.msiRetryConfig,
resourceId: this.resourceId,
});
if (result === null) {
throw new CredentialUnavailableError("Attempted to use the token exchange managed identity, but received a null response.");
}
return result;
}
else if (isImdsMsi) {
// In the IMDS scenario we will probe the IMDS endpoint to ensure it's available before trying to get a token.
// If the IMDS endpoint is not available and this is the source that MSAL will use, we will fail-fast with an error that tells DAC to move to the next credential.
logger.getToken.info("Using the IMDS endpoint to probe for availability.");
const isAvailable = await imdsMsi.isAvailable({
scopes,
clientId: this.clientId,
getTokenOptions: options,
identityClient: this.isAvailableIdentityClient,
resourceId: this.resourceId,
});
if (!isAvailable) {
throw new CredentialUnavailableError(`Attempted to use the IMDS endpoint, but it is not available.`);
}
}
// If we got this far, it means:
// - This is not a tokenExchangeMsi,
// - We already probed for IMDS endpoint availability and failed-fast if it's unreachable.
// We can proceed normally by calling MSAL for a token.
logger.getToken.info("Calling into MSAL for managed identity token.");
const token = await this.managedIdentityApp.acquireToken({
resource,
});
this.ensureValidMsalToken(scopes, token, options);
logger.getToken.info(formatSuccess(scopes));
return {
expiresOnTimestamp: token.expiresOn.getTime(),
token: token.accessToken,
refreshAfterTimestamp: (_a = token.refreshOn) === null || _a === void 0 ? void 0 : _a.getTime(),
tokenType: "Bearer",
};
}
catch (err) {
logger.getToken.error(formatError(scopes, err));
// AuthenticationRequiredError described as Error to enforce authentication after trying to retrieve a token silently.
// TODO: why would this _ever_ happen considering we're not trying the silent request in this flow?
if (err.name === "AuthenticationRequiredError") {
throw err;
}
if (isNetworkError(err)) {
throw new CredentialUnavailableError(`ManagedIdentityCredential: Network unreachable. Message: ${err.message}`, { cause: err });
}
throw new CredentialUnavailableError(`ManagedIdentityCredential: Authentication failed. Message ${err.message}`, { cause: err });
}
});
}
/**
* Ensures the validity of the MSAL token
*/
ensureValidMsalToken(scopes, msalToken, getTokenOptions) {
const createError = (message) => {
logger.getToken.info(message);
return new AuthenticationRequiredError({
scopes: Array.isArray(scopes) ? scopes : [scopes],
getTokenOptions,
message,
});
};
if (!msalToken) {
throw createError("No response.");
}
if (!msalToken.expiresOn) {
throw createError(`Response had no "expiresOn" property.`);
}
if (!msalToken.accessToken) {
throw createError(`Response had no "accessToken" property.`);
}
}
}
function isNetworkError(err) {
// MSAL error
if (err.errorCode === "network_error") {
return true;
}
// Probe errors
if (err.code === "ENETUNREACH" || err.code === "EHOSTUNREACH") {
return true;
}
// This is a special case for Docker Desktop which responds with a 403 with a message that contains "A socket operation was attempted to an unreachable network" or "A socket operation was attempted to an unreachable host"
// rather than just timing out, as expected.
if (err.statusCode === 403 || err.code === 403) {
if (err.message.includes("unreachable")) {
return true;
}
}
return false;
}
//# sourceMappingURL=index.js.map

View File

@@ -0,0 +1,32 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import { WorkloadIdentityCredential } from "../workloadIdentityCredential.js";
import { credentialLogger } from "../../util/logging.js";
const msiName = "ManagedIdentityCredential - Token Exchange";
const logger = credentialLogger(msiName);
/**
* Defines how to determine whether the token exchange MSI is available, and also how to retrieve a token from the token exchange MSI.
*
* Token exchange MSI (used by AKS) is the only MSI implementation handled entirely by Azure Identity.
* The rest have been migrated to MSAL.
*/
export const tokenExchangeMsi = {
name: "tokenExchangeMsi",
async isAvailable(clientId) {
const env = process.env;
const result = Boolean((clientId || env.AZURE_CLIENT_ID) &&
env.AZURE_TENANT_ID &&
process.env.AZURE_FEDERATED_TOKEN_FILE);
if (!result) {
logger.info(`${msiName}: Unavailable. The environment variables needed are: AZURE_CLIENT_ID (or the client ID sent through the parameters), AZURE_TENANT_ID and AZURE_FEDERATED_TOKEN_FILE`);
}
return result;
},
async getToken(configuration, getTokenOptions = {}) {
const { scopes, clientId } = configuration;
const identityClientTokenCredentialOptions = {};
const workloadIdentityCredential = new WorkloadIdentityCredential(Object.assign(Object.assign({ clientId, tenantId: process.env.AZURE_TENANT_ID, tokenFilePath: process.env.AZURE_FEDERATED_TOKEN_FILE }, identityClientTokenCredentialOptions), { disableInstanceDiscovery: true }));
return workloadIdentityCredential.getToken(scopes, getTokenOptions);
},
};
//# sourceMappingURL=tokenExchangeMsi.js.map

View File

@@ -0,0 +1,81 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
const DefaultScopeSuffix = "/.default";
/**
* Error message for Service Fabric Managed Identity environment.
*/
export const serviceFabricErrorMessage = "Specifying a `clientId` or `resourceId` is not supported by the Service Fabric managed identity environment. The managed identity configuration is determined by the Service Fabric cluster resource configuration. See https://aka.ms/servicefabricmi for more information";
/**
* Most MSIs send requests to the IMDS endpoint, or a similar endpoint.
* These are GET requests that require sending a `resource` parameter on the query.
* This resource can be derived from the scopes received through the getToken call, as long as only one scope is received.
* Multiple scopes assume that the resulting token will have access to multiple resources, which won't be the case.
*
* For that reason, when we encounter multiple scopes, we return undefined.
* It's up to the individual MSI implementations to throw the errors (which helps us provide less generic errors).
*/
export function mapScopesToResource(scopes) {
let scope = "";
if (Array.isArray(scopes)) {
if (scopes.length !== 1) {
return;
}
scope = scopes[0];
}
else if (typeof scopes === "string") {
scope = scopes;
}
if (!scope.endsWith(DefaultScopeSuffix)) {
return scope;
}
return scope.substr(0, scope.lastIndexOf(DefaultScopeSuffix));
}
/**
* Given a token response, return the expiration timestamp as the number of milliseconds from the Unix epoch.
* @param body - A parsed response body from the authentication endpoint.
*/
export function parseExpirationTimestamp(body) {
if (typeof body.expires_on === "number") {
return body.expires_on * 1000;
}
if (typeof body.expires_on === "string") {
const asNumber = +body.expires_on;
if (!isNaN(asNumber)) {
return asNumber * 1000;
}
const asDate = Date.parse(body.expires_on);
if (!isNaN(asDate)) {
return asDate;
}
}
if (typeof body.expires_in === "number") {
return Date.now() + body.expires_in * 1000;
}
throw new Error(`Failed to parse token expiration from body. expires_in="${body.expires_in}", expires_on="${body.expires_on}"`);
}
/**
* Given a token response, return the expiration timestamp as the number of milliseconds from the Unix epoch.
* @param body - A parsed response body from the authentication endpoint.
*/
export function parseRefreshTimestamp(body) {
if (body.refresh_on) {
if (typeof body.refresh_on === "number") {
return body.refresh_on * 1000;
}
if (typeof body.refresh_on === "string") {
const asNumber = +body.refresh_on;
if (!isNaN(asNumber)) {
return asNumber * 1000;
}
const asDate = Date.parse(body.refresh_on);
if (!isNaN(asDate)) {
return asDate;
}
}
throw new Error(`Failed to parse refresh_on from body. refresh_on="${body.refresh_on}"`);
}
else {
return undefined;
}
}
//# sourceMappingURL=utils.js.map

View File

@@ -0,0 +1,118 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import { createMsalClient } from "../msal/nodeFlows/msalClient.js";
import { credentialLogger, formatError } from "../util/logging.js";
import { processMultiTenantRequest, resolveAdditionallyAllowedTenantIds, } from "../util/tenantIdUtils.js";
import { CredentialUnavailableError } from "../errors.js";
import { createHash } from "node:crypto";
import { ensureScopes } from "../util/scopeUtils.js";
import { readFile } from "node:fs/promises";
import { tracingClient } from "../util/tracing.js";
const credentialName = "OnBehalfOfCredential";
const logger = credentialLogger(credentialName);
/**
* Enables authentication to Microsoft Entra ID using the [On Behalf Of flow](https://learn.microsoft.com/entra/identity-platform/v2-oauth2-on-behalf-of-flow).
*/
export class OnBehalfOfCredential {
constructor(options) {
const { clientSecret } = options;
const { certificatePath, sendCertificateChain } = options;
const { getAssertion } = options;
const { tenantId, clientId, userAssertionToken, additionallyAllowedTenants: additionallyAllowedTenantIds, } = options;
if (!tenantId) {
throw new CredentialUnavailableError(`${credentialName}: tenantId is a required parameter. To troubleshoot, visit https://aka.ms/azsdk/js/identity/serviceprincipalauthentication/troubleshoot.`);
}
if (!clientId) {
throw new CredentialUnavailableError(`${credentialName}: clientId is a required parameter. To troubleshoot, visit https://aka.ms/azsdk/js/identity/serviceprincipalauthentication/troubleshoot.`);
}
if (!clientSecret && !certificatePath && !getAssertion) {
throw new CredentialUnavailableError(`${credentialName}: You must provide one of clientSecret, certificatePath, or a getAssertion callback but none were provided. To troubleshoot, visit https://aka.ms/azsdk/js/identity/serviceprincipalauthentication/troubleshoot.`);
}
if (!userAssertionToken) {
throw new CredentialUnavailableError(`${credentialName}: userAssertionToken is a required parameter. To troubleshoot, visit https://aka.ms/azsdk/js/identity/serviceprincipalauthentication/troubleshoot.`);
}
this.certificatePath = certificatePath;
this.clientSecret = clientSecret;
this.userAssertionToken = userAssertionToken;
this.sendCertificateChain = sendCertificateChain;
this.clientAssertion = getAssertion;
this.tenantId = tenantId;
this.additionallyAllowedTenantIds = resolveAdditionallyAllowedTenantIds(additionallyAllowedTenantIds);
this.msalClient = createMsalClient(clientId, this.tenantId, Object.assign(Object.assign({}, options), { logger, tokenCredentialOptions: options }));
}
/**
* Authenticates with Microsoft Entra ID and returns an access token if successful.
* If authentication fails, a {@link CredentialUnavailableError} will be thrown with the details of the failure.
*
* @param scopes - The list of scopes for which the token will have access.
* @param options - The options used to configure the underlying network requests.
*/
async getToken(scopes, options = {}) {
return tracingClient.withSpan(`${credentialName}.getToken`, options, async (newOptions) => {
newOptions.tenantId = processMultiTenantRequest(this.tenantId, newOptions, this.additionallyAllowedTenantIds, logger);
const arrayScopes = ensureScopes(scopes);
if (this.certificatePath) {
const clientCertificate = await this.buildClientCertificate(this.certificatePath);
return this.msalClient.getTokenOnBehalfOf(arrayScopes, this.userAssertionToken, clientCertificate, newOptions);
}
else if (this.clientSecret) {
return this.msalClient.getTokenOnBehalfOf(arrayScopes, this.userAssertionToken, this.clientSecret, options);
}
else if (this.clientAssertion) {
return this.msalClient.getTokenOnBehalfOf(arrayScopes, this.userAssertionToken, this.clientAssertion, options);
}
else {
// this is an invalid scenario and is a bug, as the constructor should have thrown an error if neither clientSecret nor certificatePath nor clientAssertion were provided
throw new Error("Expected either clientSecret or certificatePath or clientAssertion to be defined.");
}
});
}
async buildClientCertificate(certificatePath) {
try {
const parts = await this.parseCertificate({ certificatePath }, this.sendCertificateChain);
return {
thumbprint: parts.thumbprint,
thumbprintSha256: parts.thumbprintSha256,
privateKey: parts.certificateContents,
x5c: parts.x5c,
};
}
catch (error) {
logger.info(formatError("", error));
throw error;
}
}
async parseCertificate(configuration, sendCertificateChain) {
const certificatePath = configuration.certificatePath;
const certificateContents = await readFile(certificatePath, "utf8");
const x5c = sendCertificateChain ? certificateContents : undefined;
const certificatePattern = /(-+BEGIN CERTIFICATE-+)(\n\r?|\r\n?)([A-Za-z0-9+/\n\r]+=*)(\n\r?|\r\n?)(-+END CERTIFICATE-+)/g;
const publicKeys = [];
// Match all possible certificates, in the order they are in the file. These will form the chain that is used for x5c
let match;
do {
match = certificatePattern.exec(certificateContents);
if (match) {
publicKeys.push(match[3]);
}
} while (match);
if (publicKeys.length === 0) {
throw new Error("The file at the specified path does not contain a PEM-encoded certificate.");
}
const thumbprint = createHash("sha1")
.update(Buffer.from(publicKeys[0], "base64"))
.digest("hex")
.toUpperCase();
const thumbprintSha256 = createHash("sha256")
.update(Buffer.from(publicKeys[0], "base64"))
.digest("hex")
.toUpperCase();
return {
certificateContents,
thumbprintSha256,
thumbprint,
x5c,
};
}
}
//# sourceMappingURL=onBehalfOfCredential.js.map

View File

@@ -0,0 +1,68 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import { createMsalClient } from "../msal/nodeFlows/msalClient.js";
import { processMultiTenantRequest, resolveAdditionallyAllowedTenantIds, } from "../util/tenantIdUtils.js";
import { CredentialUnavailableError } from "../errors.js";
import { credentialLogger } from "../util/logging.js";
import { ensureScopes } from "../util/scopeUtils.js";
import { tracingClient } from "../util/tracing.js";
const logger = credentialLogger("UsernamePasswordCredential");
/**
* Enables authentication to Microsoft Entra ID with a user's
* username and password. This credential requires a high degree of
* trust so you should only use it when other, more secure credential
* types can't be used.
* @deprecated UsernamePasswordCredential is deprecated. Use a more secure credential. See https://aka.ms/azsdk/identity/mfa for details.
*/
export class UsernamePasswordCredential {
/**
* Creates an instance of the UsernamePasswordCredential with the details
* needed to authenticate against Microsoft Entra ID with a username
* and password.
*
* @param tenantId - The Microsoft Entra tenant (directory).
* @param clientId - The client (application) ID of an App Registration in the tenant.
* @param username - The user account's e-mail address (user name).
* @param password - The user account's account password
* @param options - Options for configuring the client which makes the authentication request.
*/
constructor(tenantId, clientId, username, password, options = {}) {
if (!tenantId) {
throw new CredentialUnavailableError("UsernamePasswordCredential: tenantId is a required parameter. To troubleshoot, visit https://aka.ms/azsdk/js/identity/usernamepasswordcredential/troubleshoot.");
}
if (!clientId) {
throw new CredentialUnavailableError("UsernamePasswordCredential: clientId is a required parameter. To troubleshoot, visit https://aka.ms/azsdk/js/identity/usernamepasswordcredential/troubleshoot.");
}
if (!username) {
throw new CredentialUnavailableError("UsernamePasswordCredential: username is a required parameter. To troubleshoot, visit https://aka.ms/azsdk/js/identity/usernamepasswordcredential/troubleshoot.");
}
if (!password) {
throw new CredentialUnavailableError("UsernamePasswordCredential: password is a required parameter. To troubleshoot, visit https://aka.ms/azsdk/js/identity/usernamepasswordcredential/troubleshoot.");
}
this.tenantId = tenantId;
this.additionallyAllowedTenantIds = resolveAdditionallyAllowedTenantIds(options === null || options === void 0 ? void 0 : options.additionallyAllowedTenants);
this.username = username;
this.password = password;
this.msalClient = createMsalClient(clientId, this.tenantId, Object.assign(Object.assign({}, options), { tokenCredentialOptions: options !== null && options !== void 0 ? options : {} }));
}
/**
* Authenticates with Microsoft Entra ID and returns an access token if successful.
* If authentication fails, a {@link CredentialUnavailableError} will be thrown with the details of the failure.
*
* If the user provided the option `disableAutomaticAuthentication`,
* once the token can't be retrieved silently,
* this method won't attempt to request user interaction to retrieve the token.
*
* @param scopes - The list of scopes for which the token will have access.
* @param options - The options used to configure any requests this
* TokenCredential implementation might make.
*/
async getToken(scopes, options = {}) {
return tracingClient.withSpan(`${this.constructor.name}.getToken`, options, async (newOptions) => {
newOptions.tenantId = processMultiTenantRequest(this.tenantId, newOptions, this.additionallyAllowedTenantIds, logger);
const arrayScopes = ensureScopes(scopes);
return this.msalClient.getTokenByUsernamePassword(arrayScopes, this.username, this.password, newOptions);
});
}
}
//# sourceMappingURL=usernamePasswordCredential.js.map

View File

@@ -0,0 +1,196 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import { credentialLogger, formatError, formatSuccess } from "../util/logging.js";
import { processMultiTenantRequest, resolveAdditionallyAllowedTenantIds, } from "../util/tenantIdUtils.js";
import { AzureAuthorityHosts } from "../constants.js";
import { CredentialUnavailableError } from "../errors.js";
import { IdentityClient } from "../client/identityClient.js";
import { checkTenantId } from "../util/tenantIdUtils.js";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
const CommonTenantId = "common";
const AzureAccountClientId = "aebc6443-996d-45c2-90f0-388ff96faa56"; // VSC: 'aebc6443-996d-45c2-90f0-388ff96faa56'
const logger = credentialLogger("VisualStudioCodeCredential");
let findCredentials = undefined;
export const vsCodeCredentialControl = {
setVsCodeCredentialFinder(finder) {
findCredentials = finder;
},
};
// Map of unsupported Tenant IDs and the errors we will be throwing.
const unsupportedTenantIds = {
adfs: "The VisualStudioCodeCredential does not support authentication with ADFS tenants.",
};
function checkUnsupportedTenant(tenantId) {
// If the Tenant ID isn't supported, we throw.
const unsupportedTenantError = unsupportedTenantIds[tenantId];
if (unsupportedTenantError) {
throw new CredentialUnavailableError(unsupportedTenantError);
}
}
const mapVSCodeAuthorityHosts = {
AzureCloud: AzureAuthorityHosts.AzurePublicCloud,
AzureChina: AzureAuthorityHosts.AzureChina,
AzureGermanCloud: AzureAuthorityHosts.AzureGermany,
AzureUSGovernment: AzureAuthorityHosts.AzureGovernment,
};
/**
* Attempts to load a specific property from the VSCode configurations of the current OS.
* If it fails at any point, returns undefined.
*/
export function getPropertyFromVSCode(property) {
const settingsPath = ["User", "settings.json"];
// Eventually we can add more folders for more versions of VSCode.
const vsCodeFolder = "Code";
const homedir = os.homedir();
function loadProperty(...pathSegments) {
const fullPath = path.join(...pathSegments, vsCodeFolder, ...settingsPath);
const settings = JSON.parse(fs.readFileSync(fullPath, { encoding: "utf8" }));
return settings[property];
}
try {
let appData;
switch (process.platform) {
case "win32":
appData = process.env.APPDATA;
return appData ? loadProperty(appData) : undefined;
case "darwin":
return loadProperty(homedir, "Library", "Application Support");
case "linux":
return loadProperty(homedir, ".config");
default:
return;
}
}
catch (e) {
logger.info(`Failed to load the Visual Studio Code configuration file. Error: ${e.message}`);
return;
}
}
/**
* Connects to Azure using the credential provided by the VSCode extension 'Azure Account'.
* Once the user has logged in via the extension, this credential can share the same refresh token
* that is cached by the extension.
*
* It's a [known issue](https://github.com/Azure/azure-sdk-for-js/issues/20500) that this credential doesn't
* work with [Azure Account extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode.azure-account)
* versions newer than **0.9.11**. A long-term fix to this problem is in progress. In the meantime, consider
* authenticating with {@link AzureCliCredential}.
*
* @deprecated This credential is deprecated because the VS Code Azure Account extension on which this credential
* relies has been deprecated. Users should use other dev-time credentials, such as {@link AzureCliCredential},
* {@link AzureDeveloperCliCredential}, or {@link AzurePowerShellCredential} for their
* local development needs. See Azure Account extension deprecation notice [here](https://github.com/microsoft/vscode-azure-account/issues/964).
*
*/
export class VisualStudioCodeCredential {
/**
* Creates an instance of VisualStudioCodeCredential to use for automatically authenticating via VSCode.
*
* **Note**: `VisualStudioCodeCredential` is provided by a plugin package:
* `@azure/identity-vscode`. If this package is not installed and registered
* using the plugin API (`useIdentityPlugin`), then authentication using
* `VisualStudioCodeCredential` will not be available.
*
* @param options - Options for configuring the client which makes the authentication request.
*/
constructor(options) {
// We want to make sure we use the one assigned by the user on the VSCode settings.
// Or just `AzureCloud` by default.
this.cloudName = (getPropertyFromVSCode("azure.cloud") || "AzureCloud");
// Picking an authority host based on the cloud name.
const authorityHost = mapVSCodeAuthorityHosts[this.cloudName];
this.identityClient = new IdentityClient(Object.assign({ authorityHost }, options));
if (options && options.tenantId) {
checkTenantId(logger, options.tenantId);
this.tenantId = options.tenantId;
}
else {
this.tenantId = CommonTenantId;
}
this.additionallyAllowedTenantIds = resolveAdditionallyAllowedTenantIds(options === null || options === void 0 ? void 0 : options.additionallyAllowedTenants);
checkUnsupportedTenant(this.tenantId);
}
/**
* Runs preparations for any further getToken request.
*/
async prepare() {
// Attempts to load the tenant from the VSCode configuration file.
const settingsTenant = getPropertyFromVSCode("azure.tenant");
if (settingsTenant) {
this.tenantId = settingsTenant;
}
checkUnsupportedTenant(this.tenantId);
}
/**
* Runs preparations for any further getToken, but only once.
*/
prepareOnce() {
if (!this.preparePromise) {
this.preparePromise = this.prepare();
}
return this.preparePromise;
}
/**
* Returns the token found by searching VSCode's authentication cache or
* returns null if no token could be found.
*
* @param scopes - The list of scopes for which the token will have access.
* @param options - The options used to configure any requests this
* `TokenCredential` implementation might make.
*/
async getToken(scopes, options) {
var _a, _b;
await this.prepareOnce();
const tenantId = processMultiTenantRequest(this.tenantId, options, this.additionallyAllowedTenantIds, logger) || this.tenantId;
if (findCredentials === undefined) {
throw new CredentialUnavailableError([
"No implementation of `VisualStudioCodeCredential` is available.",
"You must install the identity-vscode plugin package (`npm install --save-dev @azure/identity-vscode`)",
"and enable it by importing `useIdentityPlugin` from `@azure/identity` and calling",
"`useIdentityPlugin(vsCodePlugin)` before creating a `VisualStudioCodeCredential`.",
"To troubleshoot, visit https://aka.ms/azsdk/js/identity/vscodecredential/troubleshoot.",
].join(" "));
}
let scopeString = typeof scopes === "string" ? scopes : scopes.join(" ");
// Check to make sure the scope we get back is a valid scope
if (!scopeString.match(/^[0-9a-zA-Z-.:/]+$/)) {
const error = new Error("Invalid scope was specified by the user or calling client");
logger.getToken.info(formatError(scopes, error));
throw error;
}
if (scopeString.indexOf("offline_access") < 0) {
scopeString += " offline_access";
}
// findCredentials returns an array similar to:
// [
// {
// account: "",
// password: "",
// },
// /* ... */
// ]
const credentials = await findCredentials();
// If we can't find the credential based on the name, we'll pick the first one available.
const { password: refreshToken } = (_b = (_a = credentials.find(({ account }) => account === this.cloudName)) !== null && _a !== void 0 ? _a : credentials[0]) !== null && _b !== void 0 ? _b : {};
if (refreshToken) {
const tokenResponse = await this.identityClient.refreshAccessToken(tenantId, AzureAccountClientId, scopeString, refreshToken, undefined);
if (tokenResponse) {
logger.getToken.info(formatSuccess(scopes));
return tokenResponse.accessToken;
}
else {
const error = new CredentialUnavailableError("Could not retrieve the token associated with Visual Studio Code. Have you connected using the 'Azure Account' extension recently? To troubleshoot, visit https://aka.ms/azsdk/js/identity/vscodecredential/troubleshoot.");
logger.getToken.info(formatError(scopes, error));
throw error;
}
}
else {
const error = new CredentialUnavailableError("Could not retrieve the token associated with Visual Studio Code. Did you connect using the 'Azure Account' extension? To troubleshoot, visit https://aka.ms/azsdk/js/identity/vscodecredential/troubleshoot.");
logger.getToken.info(formatError(scopes, error));
throw error;
}
}
}
//# sourceMappingURL=visualStudioCodeCredential.js.map

View File

@@ -0,0 +1,114 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import { credentialLogger, processEnvVars } from "../util/logging.js";
import { ClientAssertionCredential } from "./clientAssertionCredential.js";
import { CredentialUnavailableError } from "../errors.js";
import { checkTenantId } from "../util/tenantIdUtils.js";
import { readFile } from "node:fs/promises";
const credentialName = "WorkloadIdentityCredential";
/**
* Contains the list of all supported environment variable names so that an
* appropriate error message can be generated when no credentials can be
* configured.
*
* @internal
*/
export const SupportedWorkloadEnvironmentVariables = [
"AZURE_TENANT_ID",
"AZURE_CLIENT_ID",
"AZURE_FEDERATED_TOKEN_FILE",
];
const logger = credentialLogger(credentialName);
/**
* Workload Identity authentication is a feature in Azure that allows applications running on virtual machines (VMs)
* to access other Azure resources without the need for a service principal or managed identity. With Workload Identity
* authentication, applications authenticate themselves using their own identity, rather than using a shared service
* principal or managed identity. Under the hood, Workload Identity authentication uses the concept of Service Account
* Credentials (SACs), which are automatically created by Azure and stored securely in the VM. By using Workload
* Identity authentication, you can avoid the need to manage and rotate service principals or managed identities for
* each application on each VM. Additionally, because SACs are created automatically and managed by Azure, you don't
* need to worry about storing and securing sensitive credentials themselves.
* The WorkloadIdentityCredential supports Microsoft Entra Workload ID authentication on Azure Kubernetes and acquires
* a token using the SACs available in the Azure Kubernetes environment.
* Refer to <a href="https://learn.microsoft.com/azure/aks/workload-identity-overview">Microsoft Entra
* Workload ID</a> for more information.
*/
export class WorkloadIdentityCredential {
/**
* WorkloadIdentityCredential supports Microsoft Entra Workload ID on Kubernetes.
*
* @param options - The identity client options to use for authentication.
*/
constructor(options) {
this.azureFederatedTokenFileContent = undefined;
this.cacheDate = undefined;
// Logging environment variables for error details
const assignedEnv = processEnvVars(SupportedWorkloadEnvironmentVariables).assigned.join(", ");
logger.info(`Found the following environment variables: ${assignedEnv}`);
const workloadIdentityCredentialOptions = options !== null && options !== void 0 ? options : {};
const tenantId = workloadIdentityCredentialOptions.tenantId || process.env.AZURE_TENANT_ID;
const clientId = workloadIdentityCredentialOptions.clientId || process.env.AZURE_CLIENT_ID;
this.federatedTokenFilePath =
workloadIdentityCredentialOptions.tokenFilePath || process.env.AZURE_FEDERATED_TOKEN_FILE;
if (tenantId) {
checkTenantId(logger, tenantId);
}
if (!clientId) {
throw new CredentialUnavailableError(`${credentialName}: is unavailable. clientId is a required parameter. In DefaultAzureCredential and ManagedIdentityCredential, this can be provided as an environment variable - "AZURE_CLIENT_ID".
See the troubleshooting guide for more information: https://aka.ms/azsdk/js/identity/workloadidentitycredential/troubleshoot`);
}
if (!tenantId) {
throw new CredentialUnavailableError(`${credentialName}: is unavailable. tenantId is a required parameter. In DefaultAzureCredential and ManagedIdentityCredential, this can be provided as an environment variable - "AZURE_TENANT_ID".
See the troubleshooting guide for more information: https://aka.ms/azsdk/js/identity/workloadidentitycredential/troubleshoot`);
}
if (!this.federatedTokenFilePath) {
throw new CredentialUnavailableError(`${credentialName}: is unavailable. federatedTokenFilePath is a required parameter. In DefaultAzureCredential and ManagedIdentityCredential, this can be provided as an environment variable - "AZURE_FEDERATED_TOKEN_FILE".
See the troubleshooting guide for more information: https://aka.ms/azsdk/js/identity/workloadidentitycredential/troubleshoot`);
}
logger.info(`Invoking ClientAssertionCredential with tenant ID: ${tenantId}, clientId: ${workloadIdentityCredentialOptions.clientId} and federated token path: [REDACTED]`);
this.client = new ClientAssertionCredential(tenantId, clientId, this.readFileContents.bind(this), options);
}
/**
* Authenticates with Microsoft Entra ID and returns an access token if successful.
* If authentication fails, a {@link CredentialUnavailableError} will be thrown with the details of the failure.
*
* @param scopes - The list of scopes for which the token will have access.
* @param options - The options used to configure any requests this
* TokenCredential implementation might make.
*/
async getToken(scopes, options) {
if (!this.client) {
const errorMessage = `${credentialName}: is unavailable. tenantId, clientId, and federatedTokenFilePath are required parameters.
In DefaultAzureCredential and ManagedIdentityCredential, these can be provided as environment variables -
"AZURE_TENANT_ID",
"AZURE_CLIENT_ID",
"AZURE_FEDERATED_TOKEN_FILE". See the troubleshooting guide for more information: https://aka.ms/azsdk/js/identity/workloadidentitycredential/troubleshoot`;
logger.info(errorMessage);
throw new CredentialUnavailableError(errorMessage);
}
logger.info("Invoking getToken() of Client Assertion Credential");
return this.client.getToken(scopes, options);
}
async readFileContents() {
// Cached assertions expire after 5 minutes
if (this.cacheDate !== undefined && Date.now() - this.cacheDate >= 1000 * 60 * 5) {
this.azureFederatedTokenFileContent = undefined;
}
if (!this.federatedTokenFilePath) {
throw new CredentialUnavailableError(`${credentialName}: is unavailable. Invalid file path provided ${this.federatedTokenFilePath}.`);
}
if (!this.azureFederatedTokenFileContent) {
const file = await readFile(this.federatedTokenFilePath, "utf8");
const value = file.trim();
if (!value) {
throw new CredentialUnavailableError(`${credentialName}: is unavailable. No content on the file ${this.federatedTokenFilePath}.`);
}
else {
this.azureFederatedTokenFileContent = value;
this.cacheDate = Date.now();
}
}
return this.azureFederatedTokenFileContent;
}
}
//# sourceMappingURL=workloadIdentityCredential.js.map