@azure/identity 4.3.1-alpha.20240618.4 → 4.3.1-alpha.20240620.2
Sign up to get free protection for your applications and to get access to all the features.
- package/dist/index.js +356 -702
- package/dist/index.js.map +1 -1
- package/dist-esm/src/credentials/interactiveBrowserCredential.js +18 -21
- package/dist-esm/src/credentials/interactiveBrowserCredential.js.map +1 -1
- package/dist-esm/src/credentials/managedIdentityCredential/index.js +3 -284
- package/dist-esm/src/credentials/managedIdentityCredential/index.js.map +1 -1
- package/dist-esm/src/credentials/managedIdentityCredential/legacyMsiProvider.js +302 -0
- package/dist-esm/src/credentials/managedIdentityCredential/legacyMsiProvider.js.map +1 -0
- package/dist-esm/src/credentials/onBehalfOfCredential.js +62 -6
- package/dist-esm/src/credentials/onBehalfOfCredential.js.map +1 -1
- package/dist-esm/src/msal/nodeFlows/msalClient.js +102 -0
- package/dist-esm/src/msal/nodeFlows/msalClient.js.map +1 -1
- package/dist-esm/src/msal/nodeFlows/msalPlugins.js.map +1 -1
- package/package.json +1 -1
- package/types/identity.d.ts +11 -25
@@ -0,0 +1,302 @@
|
|
1
|
+
// Copyright (c) Microsoft Corporation.
|
2
|
+
// Licensed under the MIT license.
|
3
|
+
import { ConfidentialClientApplication } from "@azure/msal-node";
|
4
|
+
import { AuthenticationError, AuthenticationRequiredError, CredentialUnavailableError, } from "../../errors";
|
5
|
+
import { cloudShellMsi } from "./cloudShellMsi";
|
6
|
+
import { credentialLogger, formatError, formatSuccess } from "../../util/logging";
|
7
|
+
import { DeveloperSignOnClientId } from "../../constants";
|
8
|
+
import { IdentityClient } from "../../client/identityClient";
|
9
|
+
import { appServiceMsi2017 } from "./appServiceMsi2017";
|
10
|
+
import { appServiceMsi2019 } from "./appServiceMsi2019";
|
11
|
+
import { arcMsi } from "./arcMsi";
|
12
|
+
import { fabricMsi } from "./fabricMsi";
|
13
|
+
import { getLogLevel } from "@azure/logger";
|
14
|
+
import { getMSALLogLevel } from "../../msal/utils";
|
15
|
+
import { imdsMsi } from "./imdsMsi";
|
16
|
+
import { tokenExchangeMsi } from "./tokenExchangeMsi";
|
17
|
+
import { tracingClient } from "../../util/tracing";
|
18
|
+
const logger = credentialLogger("ManagedIdentityCredential");
|
19
|
+
export class LegacyMsiProvider {
|
20
|
+
constructor(clientIdOrOptions, options) {
|
21
|
+
var _a, _b;
|
22
|
+
this.isEndpointUnavailable = null;
|
23
|
+
this.isAppTokenProviderInitialized = false;
|
24
|
+
this.msiRetryConfig = {
|
25
|
+
maxRetries: 5,
|
26
|
+
startDelayInMs: 800,
|
27
|
+
intervalIncrement: 2,
|
28
|
+
};
|
29
|
+
let _options;
|
30
|
+
if (typeof clientIdOrOptions === "string") {
|
31
|
+
this.clientId = clientIdOrOptions;
|
32
|
+
_options = options;
|
33
|
+
}
|
34
|
+
else {
|
35
|
+
this.clientId = clientIdOrOptions === null || clientIdOrOptions === void 0 ? void 0 : clientIdOrOptions.clientId;
|
36
|
+
_options = clientIdOrOptions;
|
37
|
+
}
|
38
|
+
this.resourceId = _options === null || _options === void 0 ? void 0 : _options.resourceId;
|
39
|
+
// For JavaScript users.
|
40
|
+
if (this.clientId && this.resourceId) {
|
41
|
+
throw new Error(`ManagedIdentityCredential - Client Id and Resource Id can't be provided at the same time.`);
|
42
|
+
}
|
43
|
+
if (((_a = _options === null || _options === void 0 ? void 0 : _options.retryOptions) === null || _a === void 0 ? void 0 : _a.maxRetries) !== undefined) {
|
44
|
+
this.msiRetryConfig.maxRetries = _options.retryOptions.maxRetries;
|
45
|
+
}
|
46
|
+
this.identityClient = new IdentityClient(_options);
|
47
|
+
this.isAvailableIdentityClient = new IdentityClient(Object.assign(Object.assign({}, _options), { retryOptions: {
|
48
|
+
maxRetries: 0,
|
49
|
+
} }));
|
50
|
+
/** authority host validation and metadata discovery to be skipped in managed identity
|
51
|
+
* since this wasn't done previously before adding token cache support
|
52
|
+
*/
|
53
|
+
this.confidentialApp = new ConfidentialClientApplication({
|
54
|
+
auth: {
|
55
|
+
authority: "https://login.microsoftonline.com/managed_identity",
|
56
|
+
clientId: (_b = this.clientId) !== null && _b !== void 0 ? _b : DeveloperSignOnClientId,
|
57
|
+
clientSecret: "dummy-secret",
|
58
|
+
cloudDiscoveryMetadata: '{"tenant_discovery_endpoint":"https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration","api-version":"1.1","metadata":[{"preferred_network":"login.microsoftonline.com","preferred_cache":"login.windows.net","aliases":["login.microsoftonline.com","login.windows.net","login.microsoft.com","sts.windows.net"]},{"preferred_network":"login.partner.microsoftonline.cn","preferred_cache":"login.partner.microsoftonline.cn","aliases":["login.partner.microsoftonline.cn","login.chinacloudapi.cn"]},{"preferred_network":"login.microsoftonline.de","preferred_cache":"login.microsoftonline.de","aliases":["login.microsoftonline.de"]},{"preferred_network":"login.microsoftonline.us","preferred_cache":"login.microsoftonline.us","aliases":["login.microsoftonline.us","login.usgovcloudapi.net"]},{"preferred_network":"login-us.microsoftonline.com","preferred_cache":"login-us.microsoftonline.com","aliases":["login-us.microsoftonline.com"]}]}',
|
59
|
+
authorityMetadata: '{"token_endpoint":"https://login.microsoftonline.com/common/oauth2/v2.0/token","token_endpoint_auth_methods_supported":["client_secret_post","private_key_jwt","client_secret_basic"],"jwks_uri":"https://login.microsoftonline.com/common/discovery/v2.0/keys","response_modes_supported":["query","fragment","form_post"],"subject_types_supported":["pairwise"],"id_token_signing_alg_values_supported":["RS256"],"response_types_supported":["code","id_token","code id_token","id_token token"],"scopes_supported":["openid","profile","email","offline_access"],"issuer":"https://login.microsoftonline.com/{tenantid}/v2.0","request_uri_parameter_supported":false,"userinfo_endpoint":"https://graph.microsoft.com/oidc/userinfo","authorization_endpoint":"https://login.microsoftonline.com/common/oauth2/v2.0/authorize","device_authorization_endpoint":"https://login.microsoftonline.com/common/oauth2/v2.0/devicecode","http_logout_supported":true,"frontchannel_logout_supported":true,"end_session_endpoint":"https://login.microsoftonline.com/common/oauth2/v2.0/logout","claims_supported":["sub","iss","cloud_instance_name","cloud_instance_host_name","cloud_graph_host_name","msgraph_host","aud","exp","iat","auth_time","acr","nonce","preferred_username","name","tid","ver","at_hash","c_hash","email"],"kerberos_endpoint":"https://login.microsoftonline.com/common/kerberos","tenant_region_scope":null,"cloud_instance_name":"microsoftonline.com","cloud_graph_host_name":"graph.windows.net","msgraph_host":"graph.microsoft.com","rbac_url":"https://pas.windows.net"}',
|
60
|
+
clientCapabilities: [],
|
61
|
+
},
|
62
|
+
system: {
|
63
|
+
loggerOptions: {
|
64
|
+
logLevel: getMSALLogLevel(getLogLevel()),
|
65
|
+
},
|
66
|
+
},
|
67
|
+
});
|
68
|
+
}
|
69
|
+
async cachedAvailableMSI(scopes, getTokenOptions) {
|
70
|
+
if (this.cachedMSI) {
|
71
|
+
return this.cachedMSI;
|
72
|
+
}
|
73
|
+
const MSIs = [
|
74
|
+
arcMsi,
|
75
|
+
fabricMsi,
|
76
|
+
appServiceMsi2019,
|
77
|
+
appServiceMsi2017,
|
78
|
+
cloudShellMsi,
|
79
|
+
tokenExchangeMsi(),
|
80
|
+
imdsMsi,
|
81
|
+
];
|
82
|
+
for (const msi of MSIs) {
|
83
|
+
if (await msi.isAvailable({
|
84
|
+
scopes,
|
85
|
+
identityClient: this.isAvailableIdentityClient,
|
86
|
+
clientId: this.clientId,
|
87
|
+
resourceId: this.resourceId,
|
88
|
+
getTokenOptions,
|
89
|
+
})) {
|
90
|
+
this.cachedMSI = msi;
|
91
|
+
return msi;
|
92
|
+
}
|
93
|
+
}
|
94
|
+
throw new CredentialUnavailableError(`ManagedIdentityCredential - No MSI credential available`);
|
95
|
+
}
|
96
|
+
async authenticateManagedIdentity(scopes, getTokenOptions) {
|
97
|
+
const { span, updatedOptions } = tracingClient.startSpan(`ManagedIdentityCredential.authenticateManagedIdentity`, getTokenOptions);
|
98
|
+
try {
|
99
|
+
// Determining the available MSI, and avoiding checking for other MSIs while the program is running.
|
100
|
+
const availableMSI = await this.cachedAvailableMSI(scopes, updatedOptions);
|
101
|
+
return availableMSI.getToken({
|
102
|
+
identityClient: this.identityClient,
|
103
|
+
scopes,
|
104
|
+
clientId: this.clientId,
|
105
|
+
resourceId: this.resourceId,
|
106
|
+
retryConfig: this.msiRetryConfig,
|
107
|
+
}, updatedOptions);
|
108
|
+
}
|
109
|
+
catch (err) {
|
110
|
+
span.setStatus({
|
111
|
+
status: "error",
|
112
|
+
error: err,
|
113
|
+
});
|
114
|
+
throw err;
|
115
|
+
}
|
116
|
+
finally {
|
117
|
+
span.end();
|
118
|
+
}
|
119
|
+
}
|
120
|
+
/**
|
121
|
+
* Authenticates with Microsoft Entra ID and returns an access token if successful.
|
122
|
+
* If authentication fails, a {@link CredentialUnavailableError} will be thrown with the details of the failure.
|
123
|
+
* If an unexpected error occurs, an {@link AuthenticationError} will be thrown with the details of the failure.
|
124
|
+
*
|
125
|
+
* @param scopes - The list of scopes for which the token will have access.
|
126
|
+
* @param options - The options used to configure any requests this
|
127
|
+
* TokenCredential implementation might make.
|
128
|
+
*/
|
129
|
+
async getToken(scopes, options) {
|
130
|
+
let result = null;
|
131
|
+
const { span, updatedOptions } = tracingClient.startSpan(`ManagedIdentityCredential.getToken`, options);
|
132
|
+
try {
|
133
|
+
// isEndpointAvailable can be true, false, or null,
|
134
|
+
// If it's null, it means we don't yet know whether
|
135
|
+
// the endpoint is available and need to check for it.
|
136
|
+
if (this.isEndpointUnavailable !== true) {
|
137
|
+
const availableMSI = await this.cachedAvailableMSI(scopes, updatedOptions);
|
138
|
+
if (availableMSI.name === "tokenExchangeMsi") {
|
139
|
+
result = await this.authenticateManagedIdentity(scopes, updatedOptions);
|
140
|
+
}
|
141
|
+
else {
|
142
|
+
const appTokenParameters = {
|
143
|
+
correlationId: this.identityClient.getCorrelationId(),
|
144
|
+
tenantId: (options === null || options === void 0 ? void 0 : options.tenantId) || "managed_identity",
|
145
|
+
scopes: Array.isArray(scopes) ? scopes : [scopes],
|
146
|
+
claims: options === null || options === void 0 ? void 0 : options.claims,
|
147
|
+
};
|
148
|
+
// Added a check to see if SetAppTokenProvider was already defined.
|
149
|
+
this.initializeSetAppTokenProvider();
|
150
|
+
const authenticationResult = await this.confidentialApp.acquireTokenByClientCredential(Object.assign({}, appTokenParameters));
|
151
|
+
result = this.handleResult(scopes, authenticationResult || undefined);
|
152
|
+
}
|
153
|
+
if (result === null) {
|
154
|
+
// If authenticateManagedIdentity returns null,
|
155
|
+
// it means no MSI endpoints are available.
|
156
|
+
// If so, we avoid trying to reach to them in future requests.
|
157
|
+
this.isEndpointUnavailable = true;
|
158
|
+
// It also means that the endpoint answered with either 200 or 201 (see the sendTokenRequest method),
|
159
|
+
// yet we had no access token. For this reason, we'll throw once with a specific message:
|
160
|
+
const error = new CredentialUnavailableError("The managed identity endpoint was reached, yet no tokens were received.");
|
161
|
+
logger.getToken.info(formatError(scopes, error));
|
162
|
+
throw error;
|
163
|
+
}
|
164
|
+
// Since `authenticateManagedIdentity` didn't throw, and the result was not null,
|
165
|
+
// We will assume that this endpoint is reachable from this point forward,
|
166
|
+
// and avoid pinging again to it.
|
167
|
+
this.isEndpointUnavailable = false;
|
168
|
+
}
|
169
|
+
else {
|
170
|
+
// We've previously determined that the endpoint was unavailable,
|
171
|
+
// either because it was unreachable or permanently unable to authenticate.
|
172
|
+
const error = new CredentialUnavailableError("The managed identity endpoint is not currently available");
|
173
|
+
logger.getToken.info(formatError(scopes, error));
|
174
|
+
throw error;
|
175
|
+
}
|
176
|
+
logger.getToken.info(formatSuccess(scopes));
|
177
|
+
return result;
|
178
|
+
}
|
179
|
+
catch (err) {
|
180
|
+
// CredentialUnavailable errors are expected to reach here.
|
181
|
+
// We intend them to bubble up, so that DefaultAzureCredential can catch them.
|
182
|
+
if (err.name === "AuthenticationRequiredError") {
|
183
|
+
throw err;
|
184
|
+
}
|
185
|
+
// Expected errors to reach this point:
|
186
|
+
// - Errors coming from a method unexpectedly breaking.
|
187
|
+
// - When identityClient.sendTokenRequest throws, in which case
|
188
|
+
// if the status code was 400, it means that the endpoint is working,
|
189
|
+
// but no identity is available.
|
190
|
+
span.setStatus({
|
191
|
+
status: "error",
|
192
|
+
error: err,
|
193
|
+
});
|
194
|
+
// If either the network is unreachable,
|
195
|
+
// we can safely assume the credential is unavailable.
|
196
|
+
if (err.code === "ENETUNREACH") {
|
197
|
+
const error = new CredentialUnavailableError(`ManagedIdentityCredential: Unavailable. Network unreachable. Message: ${err.message}`);
|
198
|
+
logger.getToken.info(formatError(scopes, error));
|
199
|
+
throw error;
|
200
|
+
}
|
201
|
+
// If either the host was unreachable,
|
202
|
+
// we can safely assume the credential is unavailable.
|
203
|
+
if (err.code === "EHOSTUNREACH") {
|
204
|
+
const error = new CredentialUnavailableError(`ManagedIdentityCredential: Unavailable. No managed identity endpoint found. Message: ${err.message}`);
|
205
|
+
logger.getToken.info(formatError(scopes, error));
|
206
|
+
throw error;
|
207
|
+
}
|
208
|
+
// If err.statusCode has a value of 400, it comes from sendTokenRequest,
|
209
|
+
// and it means that the endpoint is working, but that no identity is available.
|
210
|
+
if (err.statusCode === 400) {
|
211
|
+
throw new CredentialUnavailableError(`ManagedIdentityCredential: The managed identity endpoint is indicating there's no available identity. Message: ${err.message}`);
|
212
|
+
}
|
213
|
+
// 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"
|
214
|
+
// rather than just timing out, as expected.
|
215
|
+
if (err.statusCode === 403 || err.code === 403) {
|
216
|
+
if (err.message.includes("unreachable")) {
|
217
|
+
const error = new CredentialUnavailableError(`ManagedIdentityCredential: Unavailable. Network unreachable. Message: ${err.message}`);
|
218
|
+
logger.getToken.info(formatError(scopes, error));
|
219
|
+
throw error;
|
220
|
+
}
|
221
|
+
}
|
222
|
+
// If the error has no status code, we can assume there was no available identity.
|
223
|
+
// This will throw silently during any ChainedTokenCredential.
|
224
|
+
if (err.statusCode === undefined) {
|
225
|
+
throw new CredentialUnavailableError(`ManagedIdentityCredential: Authentication failed. Message ${err.message}`);
|
226
|
+
}
|
227
|
+
// Any other error should break the chain.
|
228
|
+
throw new AuthenticationError(err.statusCode, {
|
229
|
+
error: `ManagedIdentityCredential authentication failed.`,
|
230
|
+
error_description: err.message,
|
231
|
+
});
|
232
|
+
}
|
233
|
+
finally {
|
234
|
+
// Finally is always called, both if we return and if we throw in the above try/catch.
|
235
|
+
span.end();
|
236
|
+
}
|
237
|
+
}
|
238
|
+
/**
|
239
|
+
* Handles the MSAL authentication result.
|
240
|
+
* If the result has an account, we update the local account reference.
|
241
|
+
* If the token received is invalid, an error will be thrown depending on what's missing.
|
242
|
+
*/
|
243
|
+
handleResult(scopes, result, getTokenOptions) {
|
244
|
+
this.ensureValidMsalToken(scopes, result, getTokenOptions);
|
245
|
+
logger.getToken.info(formatSuccess(scopes));
|
246
|
+
return {
|
247
|
+
token: result.accessToken,
|
248
|
+
expiresOnTimestamp: result.expiresOn.getTime(),
|
249
|
+
};
|
250
|
+
}
|
251
|
+
/**
|
252
|
+
* Ensures the validity of the MSAL token
|
253
|
+
*/
|
254
|
+
ensureValidMsalToken(scopes, msalToken, getTokenOptions) {
|
255
|
+
const error = (message) => {
|
256
|
+
logger.getToken.info(message);
|
257
|
+
return new AuthenticationRequiredError({
|
258
|
+
scopes: Array.isArray(scopes) ? scopes : [scopes],
|
259
|
+
getTokenOptions,
|
260
|
+
message,
|
261
|
+
});
|
262
|
+
};
|
263
|
+
if (!msalToken) {
|
264
|
+
throw error("No response");
|
265
|
+
}
|
266
|
+
if (!msalToken.expiresOn) {
|
267
|
+
throw error(`Response had no "expiresOn" property.`);
|
268
|
+
}
|
269
|
+
if (!msalToken.accessToken) {
|
270
|
+
throw error(`Response had no "accessToken" property.`);
|
271
|
+
}
|
272
|
+
}
|
273
|
+
initializeSetAppTokenProvider() {
|
274
|
+
if (!this.isAppTokenProviderInitialized) {
|
275
|
+
this.confidentialApp.SetAppTokenProvider(async (appTokenProviderParameters) => {
|
276
|
+
logger.info(`SetAppTokenProvider invoked with parameters- ${JSON.stringify(appTokenProviderParameters)}`);
|
277
|
+
const getTokenOptions = Object.assign({}, appTokenProviderParameters);
|
278
|
+
logger.info(`authenticateManagedIdentity invoked with scopes- ${JSON.stringify(appTokenProviderParameters.scopes)} and getTokenOptions - ${JSON.stringify(getTokenOptions)}`);
|
279
|
+
const resultToken = await this.authenticateManagedIdentity(appTokenProviderParameters.scopes, getTokenOptions);
|
280
|
+
if (resultToken) {
|
281
|
+
logger.info(`SetAppTokenProvider will save the token in cache`);
|
282
|
+
const expiresInSeconds = (resultToken === null || resultToken === void 0 ? void 0 : resultToken.expiresOnTimestamp)
|
283
|
+
? Math.floor((resultToken.expiresOnTimestamp - Date.now()) / 1000)
|
284
|
+
: 0;
|
285
|
+
return {
|
286
|
+
accessToken: resultToken === null || resultToken === void 0 ? void 0 : resultToken.token,
|
287
|
+
expiresInSeconds,
|
288
|
+
};
|
289
|
+
}
|
290
|
+
else {
|
291
|
+
logger.info(`SetAppTokenProvider token has "no_access_token_returned" as the saved token`);
|
292
|
+
return {
|
293
|
+
accessToken: "no_access_token_returned",
|
294
|
+
expiresInSeconds: 0,
|
295
|
+
};
|
296
|
+
}
|
297
|
+
});
|
298
|
+
this.isAppTokenProviderInitialized = true;
|
299
|
+
}
|
300
|
+
}
|
301
|
+
}
|
302
|
+
//# sourceMappingURL=legacyMsiProvider.js.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"legacyMsiProvider.js","sourceRoot":"","sources":["../../../../src/credentials/managedIdentityCredential/legacyMsiProvider.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT license.\n\nimport { AccessToken, GetTokenOptions } from \"@azure/core-auth\";\nimport { AppTokenProviderParameters, ConfidentialClientApplication } from \"@azure/msal-node\";\nimport {\n AuthenticationError,\n AuthenticationRequiredError,\n CredentialUnavailableError,\n} from \"../../errors\";\nimport { MSI, MSIConfiguration, MSIToken } from \"./models\";\nimport { MsalResult, MsalToken, ValidMsalToken } from \"../../msal/types\";\nimport { cloudShellMsi } from \"./cloudShellMsi\";\nimport { credentialLogger, formatError, formatSuccess } from \"../../util/logging\";\n\nimport { DeveloperSignOnClientId } from \"../../constants\";\nimport { IdentityClient } from \"../../client/identityClient\";\nimport { TokenCredentialOptions } from \"../../tokenCredentialOptions\";\nimport { appServiceMsi2017 } from \"./appServiceMsi2017\";\nimport { appServiceMsi2019 } from \"./appServiceMsi2019\";\nimport { arcMsi } from \"./arcMsi\";\nimport { fabricMsi } from \"./fabricMsi\";\nimport { getLogLevel } from \"@azure/logger\";\nimport { getMSALLogLevel } from \"../../msal/utils\";\nimport { imdsMsi } from \"./imdsMsi\";\nimport { tokenExchangeMsi } from \"./tokenExchangeMsi\";\nimport { tracingClient } from \"../../util/tracing\";\n\nconst logger = credentialLogger(\"ManagedIdentityCredential\");\n\n// As part of the migration of Managed Identity to MSAL, this legacy provider captures the existing behavior\n// ported over from the ManagedIdentityCredential verbatim. This is to ensure that the existing behavior\n// is maintained while the new implementation is being tested and validated. Part of the migration (tracked in #25253)\n// should include deleting this provider once it is no longer needed.\n\n/**\n * Options to send on the {@link ManagedIdentityCredential} constructor.\n * Since this is an internal implementation, uses a looser interface than the public one.\n */\ninterface ManagedIdentityCredentialOptions extends TokenCredentialOptions {\n /**\n * The client ID of the user - assigned identity, or app registration(when working with AKS pod - identity).\n */\n clientId?: string;\n\n /**\n * Allows specifying a custom resource Id.\n * In scenarios such as when user assigned identities are created using an ARM template,\n * where the resource Id of the identity is known but the client Id can't be known ahead of time,\n * this parameter allows programs to use these user assigned identities\n * without having to first determine the client Id of the created identity.\n */\n resourceId?: string;\n}\n\nexport class LegacyMsiProvider {\n private identityClient: IdentityClient;\n private clientId: string | undefined;\n private resourceId: string | undefined;\n private isEndpointUnavailable: boolean | null = null;\n private isAvailableIdentityClient: IdentityClient;\n private confidentialApp: ConfidentialClientApplication;\n private isAppTokenProviderInitialized: boolean = false;\n private msiRetryConfig: MSIConfiguration[\"retryConfig\"] = {\n maxRetries: 5,\n startDelayInMs: 800,\n intervalIncrement: 2,\n };\n\n constructor(\n clientIdOrOptions?: string | ManagedIdentityCredentialOptions,\n options?: TokenCredentialOptions,\n ) {\n let _options: TokenCredentialOptions | undefined;\n if (typeof clientIdOrOptions === \"string\") {\n this.clientId = clientIdOrOptions;\n _options = options;\n } else {\n this.clientId = (clientIdOrOptions as ManagedIdentityCredentialOptions)?.clientId;\n _options = clientIdOrOptions;\n }\n this.resourceId = (_options as ManagedIdentityCredentialOptions)?.resourceId;\n // For JavaScript users.\n if (this.clientId && this.resourceId) {\n throw new Error(\n `ManagedIdentityCredential - Client Id and Resource Id can't be provided at the same time.`,\n );\n }\n if (_options?.retryOptions?.maxRetries !== undefined) {\n this.msiRetryConfig.maxRetries = _options.retryOptions.maxRetries;\n }\n this.identityClient = new IdentityClient(_options);\n this.isAvailableIdentityClient = new IdentityClient({\n ..._options,\n retryOptions: {\n maxRetries: 0,\n },\n });\n\n /** authority host validation and metadata discovery to be skipped in managed identity\n * since this wasn't done previously before adding token cache support\n */\n this.confidentialApp = new ConfidentialClientApplication({\n auth: {\n authority: \"https://login.microsoftonline.com/managed_identity\",\n clientId: this.clientId ?? DeveloperSignOnClientId,\n clientSecret: \"dummy-secret\",\n cloudDiscoveryMetadata:\n '{\"tenant_discovery_endpoint\":\"https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration\",\"api-version\":\"1.1\",\"metadata\":[{\"preferred_network\":\"login.microsoftonline.com\",\"preferred_cache\":\"login.windows.net\",\"aliases\":[\"login.microsoftonline.com\",\"login.windows.net\",\"login.microsoft.com\",\"sts.windows.net\"]},{\"preferred_network\":\"login.partner.microsoftonline.cn\",\"preferred_cache\":\"login.partner.microsoftonline.cn\",\"aliases\":[\"login.partner.microsoftonline.cn\",\"login.chinacloudapi.cn\"]},{\"preferred_network\":\"login.microsoftonline.de\",\"preferred_cache\":\"login.microsoftonline.de\",\"aliases\":[\"login.microsoftonline.de\"]},{\"preferred_network\":\"login.microsoftonline.us\",\"preferred_cache\":\"login.microsoftonline.us\",\"aliases\":[\"login.microsoftonline.us\",\"login.usgovcloudapi.net\"]},{\"preferred_network\":\"login-us.microsoftonline.com\",\"preferred_cache\":\"login-us.microsoftonline.com\",\"aliases\":[\"login-us.microsoftonline.com\"]}]}',\n authorityMetadata:\n '{\"token_endpoint\":\"https://login.microsoftonline.com/common/oauth2/v2.0/token\",\"token_endpoint_auth_methods_supported\":[\"client_secret_post\",\"private_key_jwt\",\"client_secret_basic\"],\"jwks_uri\":\"https://login.microsoftonline.com/common/discovery/v2.0/keys\",\"response_modes_supported\":[\"query\",\"fragment\",\"form_post\"],\"subject_types_supported\":[\"pairwise\"],\"id_token_signing_alg_values_supported\":[\"RS256\"],\"response_types_supported\":[\"code\",\"id_token\",\"code id_token\",\"id_token token\"],\"scopes_supported\":[\"openid\",\"profile\",\"email\",\"offline_access\"],\"issuer\":\"https://login.microsoftonline.com/{tenantid}/v2.0\",\"request_uri_parameter_supported\":false,\"userinfo_endpoint\":\"https://graph.microsoft.com/oidc/userinfo\",\"authorization_endpoint\":\"https://login.microsoftonline.com/common/oauth2/v2.0/authorize\",\"device_authorization_endpoint\":\"https://login.microsoftonline.com/common/oauth2/v2.0/devicecode\",\"http_logout_supported\":true,\"frontchannel_logout_supported\":true,\"end_session_endpoint\":\"https://login.microsoftonline.com/common/oauth2/v2.0/logout\",\"claims_supported\":[\"sub\",\"iss\",\"cloud_instance_name\",\"cloud_instance_host_name\",\"cloud_graph_host_name\",\"msgraph_host\",\"aud\",\"exp\",\"iat\",\"auth_time\",\"acr\",\"nonce\",\"preferred_username\",\"name\",\"tid\",\"ver\",\"at_hash\",\"c_hash\",\"email\"],\"kerberos_endpoint\":\"https://login.microsoftonline.com/common/kerberos\",\"tenant_region_scope\":null,\"cloud_instance_name\":\"microsoftonline.com\",\"cloud_graph_host_name\":\"graph.windows.net\",\"msgraph_host\":\"graph.microsoft.com\",\"rbac_url\":\"https://pas.windows.net\"}',\n clientCapabilities: [],\n },\n system: {\n loggerOptions: {\n logLevel: getMSALLogLevel(getLogLevel()),\n },\n },\n });\n }\n\n private cachedMSI: MSI | undefined;\n\n private async cachedAvailableMSI(\n scopes: string | string[],\n getTokenOptions?: GetTokenOptions,\n ): Promise<MSI> {\n if (this.cachedMSI) {\n return this.cachedMSI;\n }\n\n const MSIs = [\n arcMsi,\n fabricMsi,\n appServiceMsi2019,\n appServiceMsi2017,\n cloudShellMsi,\n tokenExchangeMsi(),\n imdsMsi,\n ];\n\n for (const msi of MSIs) {\n if (\n await msi.isAvailable({\n scopes,\n identityClient: this.isAvailableIdentityClient,\n clientId: this.clientId,\n resourceId: this.resourceId,\n getTokenOptions,\n })\n ) {\n this.cachedMSI = msi;\n return msi;\n }\n }\n\n throw new CredentialUnavailableError(`ManagedIdentityCredential - No MSI credential available`);\n }\n\n private async authenticateManagedIdentity(\n scopes: string | string[],\n getTokenOptions?: GetTokenOptions,\n ): Promise<MSIToken | null> {\n const { span, updatedOptions } = tracingClient.startSpan(\n `ManagedIdentityCredential.authenticateManagedIdentity`,\n getTokenOptions,\n );\n\n try {\n // Determining the available MSI, and avoiding checking for other MSIs while the program is running.\n const availableMSI = await this.cachedAvailableMSI(scopes, updatedOptions);\n return availableMSI.getToken(\n {\n identityClient: this.identityClient,\n scopes,\n clientId: this.clientId,\n resourceId: this.resourceId,\n retryConfig: this.msiRetryConfig,\n },\n updatedOptions,\n );\n } catch (err: any) {\n span.setStatus({\n status: \"error\",\n error: err,\n });\n throw err;\n } finally {\n span.end();\n }\n }\n\n /**\n * Authenticates with Microsoft Entra ID and returns an access token if successful.\n * If authentication fails, a {@link CredentialUnavailableError} will be thrown with the details of the failure.\n * If an unexpected error occurs, an {@link AuthenticationError} will be thrown with the details of the failure.\n *\n * @param scopes - The list of scopes for which the token will have access.\n * @param options - The options used to configure any requests this\n * TokenCredential implementation might make.\n */\n public async getToken(\n scopes: string | string[],\n options?: GetTokenOptions,\n ): Promise<AccessToken> {\n let result: AccessToken | null = null;\n const { span, updatedOptions } = tracingClient.startSpan(\n `ManagedIdentityCredential.getToken`,\n options,\n );\n try {\n // isEndpointAvailable can be true, false, or null,\n // If it's null, it means we don't yet know whether\n // the endpoint is available and need to check for it.\n if (this.isEndpointUnavailable !== true) {\n const availableMSI = await this.cachedAvailableMSI(scopes, updatedOptions);\n if (availableMSI.name === \"tokenExchangeMsi\") {\n result = await this.authenticateManagedIdentity(scopes, updatedOptions);\n } else {\n const appTokenParameters: AppTokenProviderParameters = {\n correlationId: this.identityClient.getCorrelationId(),\n tenantId: options?.tenantId || \"managed_identity\",\n scopes: Array.isArray(scopes) ? scopes : [scopes],\n claims: options?.claims,\n };\n\n // Added a check to see if SetAppTokenProvider was already defined.\n this.initializeSetAppTokenProvider();\n const authenticationResult = await this.confidentialApp.acquireTokenByClientCredential({\n ...appTokenParameters,\n });\n result = this.handleResult(scopes, authenticationResult || undefined);\n }\n if (result === null) {\n // If authenticateManagedIdentity returns null,\n // it means no MSI endpoints are available.\n // If so, we avoid trying to reach to them in future requests.\n this.isEndpointUnavailable = true;\n\n // It also means that the endpoint answered with either 200 or 201 (see the sendTokenRequest method),\n // yet we had no access token. For this reason, we'll throw once with a specific message:\n const error = new CredentialUnavailableError(\n \"The managed identity endpoint was reached, yet no tokens were received.\",\n );\n logger.getToken.info(formatError(scopes, error));\n throw error;\n }\n\n // Since `authenticateManagedIdentity` didn't throw, and the result was not null,\n // We will assume that this endpoint is reachable from this point forward,\n // and avoid pinging again to it.\n this.isEndpointUnavailable = false;\n } else {\n // We've previously determined that the endpoint was unavailable,\n // either because it was unreachable or permanently unable to authenticate.\n const error = new CredentialUnavailableError(\n \"The managed identity endpoint is not currently available\",\n );\n logger.getToken.info(formatError(scopes, error));\n throw error;\n }\n\n logger.getToken.info(formatSuccess(scopes));\n return result;\n } catch (err: any) {\n // CredentialUnavailable errors are expected to reach here.\n // We intend them to bubble up, so that DefaultAzureCredential can catch them.\n if (err.name === \"AuthenticationRequiredError\") {\n throw err;\n }\n\n // Expected errors to reach this point:\n // - Errors coming from a method unexpectedly breaking.\n // - When identityClient.sendTokenRequest throws, in which case\n // if the status code was 400, it means that the endpoint is working,\n // but no identity is available.\n\n span.setStatus({\n status: \"error\",\n error: err,\n });\n\n // If either the network is unreachable,\n // we can safely assume the credential is unavailable.\n if (err.code === \"ENETUNREACH\") {\n const error = new CredentialUnavailableError(\n `ManagedIdentityCredential: Unavailable. Network unreachable. Message: ${err.message}`,\n );\n\n logger.getToken.info(formatError(scopes, error));\n throw error;\n }\n\n // If either the host was unreachable,\n // we can safely assume the credential is unavailable.\n if (err.code === \"EHOSTUNREACH\") {\n const error = new CredentialUnavailableError(\n `ManagedIdentityCredential: Unavailable. No managed identity endpoint found. Message: ${err.message}`,\n );\n\n logger.getToken.info(formatError(scopes, error));\n throw error;\n }\n // If err.statusCode has a value of 400, it comes from sendTokenRequest,\n // and it means that the endpoint is working, but that no identity is available.\n if (err.statusCode === 400) {\n throw new CredentialUnavailableError(\n `ManagedIdentityCredential: The managed identity endpoint is indicating there's no available identity. Message: ${err.message}`,\n );\n }\n\n // 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\"\n // rather than just timing out, as expected.\n if (err.statusCode === 403 || err.code === 403) {\n if (err.message.includes(\"unreachable\")) {\n const error = new CredentialUnavailableError(\n `ManagedIdentityCredential: Unavailable. Network unreachable. Message: ${err.message}`,\n );\n\n logger.getToken.info(formatError(scopes, error));\n throw error;\n }\n }\n\n // If the error has no status code, we can assume there was no available identity.\n // This will throw silently during any ChainedTokenCredential.\n if (err.statusCode === undefined) {\n throw new CredentialUnavailableError(\n `ManagedIdentityCredential: Authentication failed. Message ${err.message}`,\n );\n }\n\n // Any other error should break the chain.\n throw new AuthenticationError(err.statusCode, {\n error: `ManagedIdentityCredential authentication failed.`,\n error_description: err.message,\n });\n } finally {\n // Finally is always called, both if we return and if we throw in the above try/catch.\n span.end();\n }\n }\n\n /**\n * Handles the MSAL authentication result.\n * If the result has an account, we update the local account reference.\n * If the token received is invalid, an error will be thrown depending on what's missing.\n */\n private handleResult(\n scopes: string | string[],\n result?: MsalResult,\n getTokenOptions?: GetTokenOptions,\n ): AccessToken {\n this.ensureValidMsalToken(scopes, result, getTokenOptions);\n logger.getToken.info(formatSuccess(scopes));\n return {\n token: result.accessToken,\n expiresOnTimestamp: result.expiresOn.getTime(),\n };\n }\n\n /**\n * Ensures the validity of the MSAL token\n */\n private ensureValidMsalToken(\n scopes: string | string[],\n msalToken?: MsalToken,\n getTokenOptions?: GetTokenOptions,\n ): asserts msalToken is ValidMsalToken {\n const error = (message: string): Error => {\n logger.getToken.info(message);\n return new AuthenticationRequiredError({\n scopes: Array.isArray(scopes) ? scopes : [scopes],\n getTokenOptions,\n message,\n });\n };\n if (!msalToken) {\n throw error(\"No response\");\n }\n if (!msalToken.expiresOn) {\n throw error(`Response had no \"expiresOn\" property.`);\n }\n if (!msalToken.accessToken) {\n throw error(`Response had no \"accessToken\" property.`);\n }\n }\n\n private initializeSetAppTokenProvider(): void {\n if (!this.isAppTokenProviderInitialized) {\n this.confidentialApp.SetAppTokenProvider(async (appTokenProviderParameters) => {\n logger.info(\n `SetAppTokenProvider invoked with parameters- ${JSON.stringify(\n appTokenProviderParameters,\n )}`,\n );\n const getTokenOptions: GetTokenOptions = {\n ...appTokenProviderParameters,\n };\n logger.info(\n `authenticateManagedIdentity invoked with scopes- ${JSON.stringify(\n appTokenProviderParameters.scopes,\n )} and getTokenOptions - ${JSON.stringify(getTokenOptions)}`,\n );\n const resultToken = await this.authenticateManagedIdentity(\n appTokenProviderParameters.scopes,\n getTokenOptions,\n );\n\n if (resultToken) {\n logger.info(`SetAppTokenProvider will save the token in cache`);\n\n const expiresInSeconds = resultToken?.expiresOnTimestamp\n ? Math.floor((resultToken.expiresOnTimestamp - Date.now()) / 1000)\n : 0;\n return {\n accessToken: resultToken?.token,\n expiresInSeconds,\n };\n } else {\n logger.info(\n `SetAppTokenProvider token has \"no_access_token_returned\" as the saved token`,\n );\n return {\n accessToken: \"no_access_token_returned\",\n expiresInSeconds: 0,\n };\n }\n });\n this.isAppTokenProviderInitialized = true;\n }\n }\n}\n"]}
|
@@ -1,10 +1,12 @@
|
|
1
1
|
// Copyright (c) Microsoft Corporation.
|
2
2
|
// Licensed under the MIT license.
|
3
3
|
import { processMultiTenantRequest, resolveAdditionallyAllowedTenantIds, } from "../util/tenantIdUtils";
|
4
|
-
import {
|
5
|
-
import { credentialLogger } from "../util/logging";
|
4
|
+
import { credentialLogger, formatError } from "../util/logging";
|
6
5
|
import { ensureScopes } from "../util/scopeUtils";
|
7
6
|
import { tracingClient } from "../util/tracing";
|
7
|
+
import { createMsalClient } from "../msal/nodeFlows/msalClient";
|
8
|
+
import { readFile } from "node:fs/promises";
|
9
|
+
import { createHash } from "node:crypto";
|
8
10
|
const credentialName = "OnBehalfOfCredential";
|
9
11
|
const logger = credentialLogger(credentialName);
|
10
12
|
/**
|
@@ -12,16 +14,19 @@ const logger = credentialLogger(credentialName);
|
|
12
14
|
*/
|
13
15
|
export class OnBehalfOfCredential {
|
14
16
|
constructor(options) {
|
15
|
-
this.options = options;
|
16
17
|
const { clientSecret } = options;
|
17
|
-
const { certificatePath } = options;
|
18
|
+
const { certificatePath, sendCertificateChain } = options;
|
18
19
|
const { tenantId, clientId, userAssertionToken, additionallyAllowedTenants: additionallyAllowedTenantIds, } = options;
|
19
20
|
if (!tenantId || !clientId || !(clientSecret || certificatePath) || !userAssertionToken) {
|
20
21
|
throw new Error(`${credentialName}: tenantId, clientId, clientSecret (or certificatePath) and userAssertionToken are required parameters.`);
|
21
22
|
}
|
23
|
+
this.certificatePath = certificatePath;
|
24
|
+
this.clientSecret = clientSecret;
|
25
|
+
this.userAssertionToken = userAssertionToken;
|
26
|
+
this.sendCertificateChain = sendCertificateChain;
|
22
27
|
this.tenantId = tenantId;
|
23
28
|
this.additionallyAllowedTenantIds = resolveAdditionallyAllowedTenantIds(additionallyAllowedTenantIds);
|
24
|
-
this.
|
29
|
+
this.msalClient = createMsalClient(clientId, this.tenantId, Object.assign(Object.assign({}, options), { logger, tokenCredentialOptions: options }));
|
25
30
|
}
|
26
31
|
/**
|
27
32
|
* Authenticates with Microsoft Entra ID and returns an access token if successful.
|
@@ -34,8 +39,59 @@ export class OnBehalfOfCredential {
|
|
34
39
|
return tracingClient.withSpan(`${credentialName}.getToken`, options, async (newOptions) => {
|
35
40
|
newOptions.tenantId = processMultiTenantRequest(this.tenantId, newOptions, this.additionallyAllowedTenantIds, logger);
|
36
41
|
const arrayScopes = ensureScopes(scopes);
|
37
|
-
|
42
|
+
if (this.certificatePath) {
|
43
|
+
const clientCertificate = await this.buildClientCertificate(this.certificatePath);
|
44
|
+
return this.msalClient.getTokenOnBehalfOf(arrayScopes, this.userAssertionToken, clientCertificate, newOptions);
|
45
|
+
}
|
46
|
+
else if (this.clientSecret) {
|
47
|
+
return this.msalClient.getTokenOnBehalfOf(arrayScopes, this.userAssertionToken, this.clientSecret, options);
|
48
|
+
}
|
49
|
+
else {
|
50
|
+
// this is a bug, as the constructor should have thrown an error if neither clientSecret nor certificatePath were provided
|
51
|
+
throw new Error("Expected either clientSecret or certificatePath to be defined.");
|
52
|
+
}
|
38
53
|
});
|
39
54
|
}
|
55
|
+
async buildClientCertificate(certificatePath) {
|
56
|
+
try {
|
57
|
+
const parts = await this.parseCertificate({ certificatePath }, this.sendCertificateChain);
|
58
|
+
return {
|
59
|
+
thumbprint: parts.thumbprint,
|
60
|
+
privateKey: parts.certificateContents,
|
61
|
+
x5c: parts.x5c,
|
62
|
+
};
|
63
|
+
}
|
64
|
+
catch (error) {
|
65
|
+
logger.info(formatError("", error));
|
66
|
+
throw error;
|
67
|
+
}
|
68
|
+
}
|
69
|
+
async parseCertificate(configuration, sendCertificateChain) {
|
70
|
+
const certificatePath = configuration.certificatePath;
|
71
|
+
const certificateContents = await readFile(certificatePath, "utf8");
|
72
|
+
const x5c = sendCertificateChain ? certificateContents : undefined;
|
73
|
+
const certificatePattern = /(-+BEGIN CERTIFICATE-+)(\n\r?|\r\n?)([A-Za-z0-9+/\n\r]+=*)(\n\r?|\r\n?)(-+END CERTIFICATE-+)/g;
|
74
|
+
const publicKeys = [];
|
75
|
+
// Match all possible certificates, in the order they are in the file. These will form the chain that is used for x5c
|
76
|
+
let match;
|
77
|
+
do {
|
78
|
+
match = certificatePattern.exec(certificateContents);
|
79
|
+
if (match) {
|
80
|
+
publicKeys.push(match[3]);
|
81
|
+
}
|
82
|
+
} while (match);
|
83
|
+
if (publicKeys.length === 0) {
|
84
|
+
throw new Error("The file at the specified path does not contain a PEM-encoded certificate.");
|
85
|
+
}
|
86
|
+
const thumbprint = createHash("sha1")
|
87
|
+
.update(Buffer.from(publicKeys[0], "base64"))
|
88
|
+
.digest("hex")
|
89
|
+
.toUpperCase();
|
90
|
+
return {
|
91
|
+
certificateContents,
|
92
|
+
thumbprint,
|
93
|
+
x5c,
|
94
|
+
};
|
95
|
+
}
|
40
96
|
}
|
41
97
|
//# sourceMappingURL=onBehalfOfCredential.js.map
|
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"onBehalfOfCredential.js","sourceRoot":"","sources":["../../../src/credentials/onBehalfOfCredential.ts"],"names":[],"mappings":"AAAA,uCAAuC;AACvC,kCAAkC;AAQlC,OAAO,EACL,yBAAyB,EACzB,mCAAmC,GACpC,MAAM,uBAAuB,CAAC;AAG/B,OAAO,EAAE,cAAc,EAAE,MAAM,kCAAkC,CAAC;AAElE,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAEhD,MAAM,cAAc,GAAG,sBAAsB,CAAC;AAC9C,MAAM,MAAM,GAAG,gBAAgB,CAAC,cAAc,CAAC,CAAC;AAEhD;;GAEG;AACH,MAAM,OAAO,oBAAoB;IAyD/B,YAAoB,OAAoC;QAApC,YAAO,GAAP,OAAO,CAA6B;QACtD,MAAM,EAAE,YAAY,EAAE,GAAG,OAA4C,CAAC;QACtE,MAAM,EAAE,eAAe,EAAE,GAAG,OAAiD,CAAC;QAC9E,MAAM,EACJ,QAAQ,EACR,QAAQ,EACR,kBAAkB,EAClB,0BAA0B,EAAE,4BAA4B,GACzD,GAAG,OAAO,CAAC;QACZ,IAAI,CAAC,QAAQ,IAAI,CAAC,QAAQ,IAAI,CAAC,CAAC,YAAY,IAAI,eAAe,CAAC,IAAI,CAAC,kBAAkB,EAAE,CAAC;YACxF,MAAM,IAAI,KAAK,CACb,GAAG,cAAc,yGAAyG,CAC3H,CAAC;QACJ,CAAC;QAED,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,4BAA4B,GAAG,mCAAmC,CACrE,4BAA4B,CAC7B,CAAC;QAEF,IAAI,CAAC,QAAQ,GAAG,IAAI,cAAc,iCAC7B,IAAI,CAAC,OAAO,KACf,MAAM,EACN,sBAAsB,EAAE,IAAI,CAAC,OAAO,IACpC,CAAC;IACL,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,QAAQ,CAAC,MAAyB,EAAE,UAA2B,EAAE;QACrE,OAAO,aAAa,CAAC,QAAQ,CAAC,GAAG,cAAc,WAAW,EAAE,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,EAAE;YACxF,UAAU,CAAC,QAAQ,GAAG,yBAAyB,CAC7C,IAAI,CAAC,QAAQ,EACb,UAAU,EACV,IAAI,CAAC,4BAA4B,EACjC,MAAM,CACP,CAAC;YAEF,MAAM,WAAW,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;YACzC,OAAO,IAAI,CAAC,QAAS,CAAC,QAAQ,CAAC,WAAW,EAAE,UAAU,CAAC,CAAC;QAC1D,CAAC,CAAC,CAAC;IACL,CAAC;CACF","sourcesContent":["// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT license.\n\nimport { AccessToken, GetTokenOptions, TokenCredential } from \"@azure/core-auth\";\nimport {\n OnBehalfOfCredentialCertificateOptions,\n OnBehalfOfCredentialOptions,\n OnBehalfOfCredentialSecretOptions,\n} from \"./onBehalfOfCredentialOptions\";\nimport {\n processMultiTenantRequest,\n resolveAdditionallyAllowedTenantIds,\n} from \"../util/tenantIdUtils\";\nimport { CredentialPersistenceOptions } from \"./credentialPersistenceOptions\";\nimport { MsalFlow } from \"../msal/flows\";\nimport { MsalOnBehalfOf } from \"../msal/nodeFlows/msalOnBehalfOf\";\nimport { MultiTenantTokenCredentialOptions } from \"./multiTenantTokenCredentialOptions\";\nimport { credentialLogger } from \"../util/logging\";\nimport { ensureScopes } from \"../util/scopeUtils\";\nimport { tracingClient } from \"../util/tracing\";\n\nconst credentialName = \"OnBehalfOfCredential\";\nconst logger = credentialLogger(credentialName);\n\n/**\n * 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).\n */\nexport class OnBehalfOfCredential implements TokenCredential {\n private tenantId: string;\n private additionallyAllowedTenantIds: string[];\n private msalFlow: MsalFlow;\n /**\n * Creates an instance of the {@link OnBehalfOfCredential} with the details\n * needed to authenticate against Microsoft Entra ID with path to a PEM certificate,\n * and an user assertion.\n *\n * Example using the `KeyClient` from [\\@azure/keyvault-keys](https://www.npmjs.com/package/\\@azure/keyvault-keys):\n *\n * ```ts\n * const tokenCredential = new OnBehalfOfCredential({\n * tenantId,\n * clientId,\n * certificatePath: \"/path/to/certificate.pem\",\n * userAssertionToken: \"access-token\"\n * });\n * const client = new KeyClient(\"vault-url\", tokenCredential);\n *\n * await client.getKey(\"key-name\");\n * ```\n *\n * @param options - Optional parameters, generally common across credentials.\n */\n constructor(\n options: OnBehalfOfCredentialCertificateOptions &\n MultiTenantTokenCredentialOptions &\n CredentialPersistenceOptions,\n );\n /**\n * Creates an instance of the {@link OnBehalfOfCredential} with the details\n * needed to authenticate against Microsoft Entra ID with a client\n * secret and an user assertion.\n *\n * Example using the `KeyClient` from [\\@azure/keyvault-keys](https://www.npmjs.com/package/\\@azure/keyvault-keys):\n *\n * ```ts\n * const tokenCredential = new OnBehalfOfCredential({\n * tenantId,\n * clientId,\n * clientSecret,\n * userAssertionToken: \"access-token\"\n * });\n * const client = new KeyClient(\"vault-url\", tokenCredential);\n *\n * await client.getKey(\"key-name\");\n * ```\n *\n * @param options - Optional parameters, generally common across credentials.\n */\n constructor(\n options: OnBehalfOfCredentialSecretOptions &\n MultiTenantTokenCredentialOptions &\n CredentialPersistenceOptions,\n );\n\n constructor(private options: OnBehalfOfCredentialOptions) {\n const { clientSecret } = options as OnBehalfOfCredentialSecretOptions;\n const { certificatePath } = options as OnBehalfOfCredentialCertificateOptions;\n const {\n tenantId,\n clientId,\n userAssertionToken,\n additionallyAllowedTenants: additionallyAllowedTenantIds,\n } = options;\n if (!tenantId || !clientId || !(clientSecret || certificatePath) || !userAssertionToken) {\n throw new Error(\n `${credentialName}: tenantId, clientId, clientSecret (or certificatePath) and userAssertionToken are required parameters.`,\n );\n }\n\n this.tenantId = tenantId;\n this.additionallyAllowedTenantIds = resolveAdditionallyAllowedTenantIds(\n additionallyAllowedTenantIds,\n );\n\n this.msalFlow = new MsalOnBehalfOf({\n ...this.options,\n logger,\n tokenCredentialOptions: this.options,\n });\n }\n\n /**\n * Authenticates with Microsoft Entra ID and returns an access token if successful.\n * If authentication fails, a {@link CredentialUnavailableError} will be thrown with the details of the failure.\n *\n * @param scopes - The list of scopes for which the token will have access.\n * @param options - The options used to configure the underlying network requests.\n */\n async getToken(scopes: string | string[], options: GetTokenOptions = {}): Promise<AccessToken> {\n return tracingClient.withSpan(`${credentialName}.getToken`, options, async (newOptions) => {\n newOptions.tenantId = processMultiTenantRequest(\n this.tenantId,\n newOptions,\n this.additionallyAllowedTenantIds,\n logger,\n );\n\n const arrayScopes = ensureScopes(scopes);\n return this.msalFlow!.getToken(arrayScopes, newOptions);\n });\n }\n}\n"]}
|
1
|
+
{"version":3,"file":"onBehalfOfCredential.js","sourceRoot":"","sources":["../../../src/credentials/onBehalfOfCredential.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT license.\n\nimport { AccessToken, GetTokenOptions, TokenCredential } from \"@azure/core-auth\";\nimport {\n OnBehalfOfCredentialCertificateOptions,\n OnBehalfOfCredentialOptions,\n OnBehalfOfCredentialSecretOptions,\n} from \"./onBehalfOfCredentialOptions\";\nimport {\n processMultiTenantRequest,\n resolveAdditionallyAllowedTenantIds,\n} from \"../util/tenantIdUtils\";\nimport { CredentialPersistenceOptions } from \"./credentialPersistenceOptions\";\nimport { MultiTenantTokenCredentialOptions } from \"./multiTenantTokenCredentialOptions\";\nimport { credentialLogger, formatError } from \"../util/logging\";\nimport { ensureScopes } from \"../util/scopeUtils\";\nimport { tracingClient } from \"../util/tracing\";\nimport { MsalClient, createMsalClient } from \"../msal/nodeFlows/msalClient\";\nimport { CertificateParts } from \"../msal/types\";\nimport { ClientCertificatePEMCertificatePath } from \"./clientCertificateCredential\";\nimport { readFile } from \"node:fs/promises\";\nimport { createHash } from \"node:crypto\";\n\nconst credentialName = \"OnBehalfOfCredential\";\nconst logger = credentialLogger(credentialName);\n\n/**\n * 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).\n */\nexport class OnBehalfOfCredential implements TokenCredential {\n private tenantId: string;\n private additionallyAllowedTenantIds: string[];\n private msalClient: MsalClient;\n private sendCertificateChain?: boolean;\n private certificatePath?: string;\n private clientSecret?: string;\n private userAssertionToken: string;\n\n /**\n * Creates an instance of the {@link OnBehalfOfCredential} with the details\n * needed to authenticate against Microsoft Entra ID with path to a PEM certificate,\n * and an user assertion.\n *\n * Example using the `KeyClient` from [\\@azure/keyvault-keys](https://www.npmjs.com/package/\\@azure/keyvault-keys):\n *\n * ```ts\n * const tokenCredential = new OnBehalfOfCredential({\n * tenantId,\n * clientId,\n * certificatePath: \"/path/to/certificate.pem\",\n * userAssertionToken: \"access-token\"\n * });\n * const client = new KeyClient(\"vault-url\", tokenCredential);\n *\n * await client.getKey(\"key-name\");\n * ```\n *\n * @param options - Optional parameters, generally common across credentials.\n */\n constructor(\n options: OnBehalfOfCredentialCertificateOptions &\n MultiTenantTokenCredentialOptions &\n CredentialPersistenceOptions,\n );\n /**\n * Creates an instance of the {@link OnBehalfOfCredential} with the details\n * needed to authenticate against Microsoft Entra ID with a client\n * secret and an user assertion.\n *\n * Example using the `KeyClient` from [\\@azure/keyvault-keys](https://www.npmjs.com/package/\\@azure/keyvault-keys):\n *\n * ```ts\n * const tokenCredential = new OnBehalfOfCredential({\n * tenantId,\n * clientId,\n * clientSecret,\n * userAssertionToken: \"access-token\"\n * });\n * const client = new KeyClient(\"vault-url\", tokenCredential);\n *\n * await client.getKey(\"key-name\");\n * ```\n *\n * @param options - Optional parameters, generally common across credentials.\n */\n constructor(\n options: OnBehalfOfCredentialSecretOptions &\n MultiTenantTokenCredentialOptions &\n CredentialPersistenceOptions,\n );\n\n constructor(options: OnBehalfOfCredentialOptions) {\n const { clientSecret } = options as OnBehalfOfCredentialSecretOptions;\n const { certificatePath, sendCertificateChain } =\n options as OnBehalfOfCredentialCertificateOptions;\n const {\n tenantId,\n clientId,\n userAssertionToken,\n additionallyAllowedTenants: additionallyAllowedTenantIds,\n } = options;\n if (!tenantId || !clientId || !(clientSecret || certificatePath) || !userAssertionToken) {\n throw new Error(\n `${credentialName}: tenantId, clientId, clientSecret (or certificatePath) and userAssertionToken are required parameters.`,\n );\n }\n this.certificatePath = certificatePath;\n this.clientSecret = clientSecret;\n this.userAssertionToken = userAssertionToken;\n this.sendCertificateChain = sendCertificateChain;\n\n this.tenantId = tenantId;\n this.additionallyAllowedTenantIds = resolveAdditionallyAllowedTenantIds(\n additionallyAllowedTenantIds,\n );\n\n this.msalClient = createMsalClient(clientId, this.tenantId, {\n ...options,\n logger,\n tokenCredentialOptions: options,\n });\n }\n\n /**\n * Authenticates with Microsoft Entra ID and returns an access token if successful.\n * If authentication fails, a {@link CredentialUnavailableError} will be thrown with the details of the failure.\n *\n * @param scopes - The list of scopes for which the token will have access.\n * @param options - The options used to configure the underlying network requests.\n */\n async getToken(scopes: string | string[], options: GetTokenOptions = {}): Promise<AccessToken> {\n return tracingClient.withSpan(`${credentialName}.getToken`, options, async (newOptions) => {\n newOptions.tenantId = processMultiTenantRequest(\n this.tenantId,\n newOptions,\n this.additionallyAllowedTenantIds,\n logger,\n );\n\n const arrayScopes = ensureScopes(scopes);\n if (this.certificatePath) {\n const clientCertificate = await this.buildClientCertificate(this.certificatePath);\n\n return this.msalClient.getTokenOnBehalfOf(\n arrayScopes,\n this.userAssertionToken,\n clientCertificate,\n newOptions,\n );\n } else if (this.clientSecret) {\n return this.msalClient.getTokenOnBehalfOf(\n arrayScopes,\n this.userAssertionToken,\n this.clientSecret,\n options,\n );\n } else {\n // this is a bug, as the constructor should have thrown an error if neither clientSecret nor certificatePath were provided\n throw new Error(\"Expected either clientSecret or certificatePath to be defined.\");\n }\n });\n }\n\n private async buildClientCertificate(certificatePath: string): Promise<CertificateParts> {\n try {\n const parts = await this.parseCertificate({ certificatePath }, this.sendCertificateChain);\n return {\n thumbprint: parts.thumbprint,\n privateKey: parts.certificateContents,\n x5c: parts.x5c,\n };\n } catch (error: any) {\n logger.info(formatError(\"\", error));\n throw error;\n }\n }\n\n private async parseCertificate(\n configuration: ClientCertificatePEMCertificatePath,\n sendCertificateChain?: boolean,\n ): Promise<Omit<CertificateParts, \"privateKey\"> & { certificateContents: string }> {\n const certificatePath = configuration.certificatePath;\n const certificateContents = await readFile(certificatePath, \"utf8\");\n const x5c = sendCertificateChain ? certificateContents : undefined;\n\n const certificatePattern =\n /(-+BEGIN CERTIFICATE-+)(\\n\\r?|\\r\\n?)([A-Za-z0-9+/\\n\\r]+=*)(\\n\\r?|\\r\\n?)(-+END CERTIFICATE-+)/g;\n const publicKeys: string[] = [];\n\n // Match all possible certificates, in the order they are in the file. These will form the chain that is used for x5c\n let match;\n do {\n match = certificatePattern.exec(certificateContents);\n if (match) {\n publicKeys.push(match[3]);\n }\n } while (match);\n\n if (publicKeys.length === 0) {\n throw new Error(\"The file at the specified path does not contain a PEM-encoded certificate.\");\n }\n\n const thumbprint = createHash(\"sha1\")\n .update(Buffer.from(publicKeys[0], \"base64\"))\n .digest(\"hex\")\n .toUpperCase();\n\n return {\n certificateContents,\n thumbprint,\n x5c,\n };\n }\n}\n"]}
|
@@ -9,6 +9,7 @@ import { IdentityClient } from "../../client/identityClient";
|
|
9
9
|
import { calculateRegionalAuthority } from "../../regionalAuthority";
|
10
10
|
import { getLogLevel } from "@azure/logger";
|
11
11
|
import { resolveTenantId } from "../../util/tenantIdUtils";
|
12
|
+
import { interactiveBrowserMockable } from "./msalOpenBrowser";
|
12
13
|
/**
|
13
14
|
* The default logger used if no logger was passed in by the credential.
|
14
15
|
*/
|
@@ -313,6 +314,105 @@ To work with multiple accounts for the same Client ID and Tenant ID, please prov
|
|
313
314
|
});
|
314
315
|
});
|
315
316
|
}
|
317
|
+
async function getTokenOnBehalfOf(scopes, userAssertionToken, clientSecretOrCertificate, options = {}) {
|
318
|
+
msalLogger.getToken.info(`Attempting to acquire token on behalf of another user`);
|
319
|
+
if (typeof clientSecretOrCertificate === "string") {
|
320
|
+
// Client secret
|
321
|
+
msalLogger.getToken.info(`Using client secret for on behalf of flow`);
|
322
|
+
state.msalConfig.auth.clientSecret = clientSecretOrCertificate;
|
323
|
+
}
|
324
|
+
else {
|
325
|
+
// Client certificate
|
326
|
+
msalLogger.getToken.info(`Using client certificate for on behalf of flow`);
|
327
|
+
state.msalConfig.auth.clientCertificate = clientSecretOrCertificate;
|
328
|
+
}
|
329
|
+
const msalApp = await getConfidentialApp(options);
|
330
|
+
try {
|
331
|
+
const response = await msalApp.acquireTokenOnBehalfOf({
|
332
|
+
scopes,
|
333
|
+
authority: state.msalConfig.auth.authority,
|
334
|
+
claims: options.claims,
|
335
|
+
oboAssertion: userAssertionToken,
|
336
|
+
});
|
337
|
+
ensureValidMsalToken(scopes, response, options);
|
338
|
+
msalLogger.getToken.info(formatSuccess(scopes));
|
339
|
+
return {
|
340
|
+
token: response.accessToken,
|
341
|
+
expiresOnTimestamp: response.expiresOn.getTime(),
|
342
|
+
};
|
343
|
+
}
|
344
|
+
catch (err) {
|
345
|
+
throw handleMsalError(scopes, err, options);
|
346
|
+
}
|
347
|
+
}
|
348
|
+
async function getTokenByInteractiveRequest(scopes, options = {}) {
|
349
|
+
msalLogger.getToken.info(`Attempting to acquire token interactively`);
|
350
|
+
const app = await getPublicApp(options);
|
351
|
+
/**
|
352
|
+
* A helper function that supports brokered authentication through the MSAL's public application.
|
353
|
+
*
|
354
|
+
* When options.useDefaultBrokerAccount is true, the method will attempt to authenticate using the default broker account.
|
355
|
+
* If the default broker account is not available, the method will fall back to interactive authentication.
|
356
|
+
*/
|
357
|
+
async function getBrokeredToken(useDefaultBrokerAccount) {
|
358
|
+
var _a;
|
359
|
+
msalLogger.verbose("Authentication will resume through the broker");
|
360
|
+
const interactiveRequest = createBaseInteractiveRequest();
|
361
|
+
if (state.pluginConfiguration.broker.parentWindowHandle) {
|
362
|
+
interactiveRequest.windowHandle = Buffer.from(state.pluginConfiguration.broker.parentWindowHandle);
|
363
|
+
}
|
364
|
+
else {
|
365
|
+
// this is a bug, as the pluginConfiguration handler should validate this case.
|
366
|
+
msalLogger.warning("Parent window handle is not specified for the broker. This may cause unexpected behavior. Please provide the parentWindowHandle.");
|
367
|
+
}
|
368
|
+
if (state.pluginConfiguration.broker.enableMsaPassthrough) {
|
369
|
+
((_a = interactiveRequest.tokenQueryParameters) !== null && _a !== void 0 ? _a : (interactiveRequest.tokenQueryParameters = {}))["msal_request_type"] =
|
370
|
+
"consumer_passthrough";
|
371
|
+
}
|
372
|
+
if (useDefaultBrokerAccount) {
|
373
|
+
interactiveRequest.prompt = "none";
|
374
|
+
msalLogger.verbose("Attempting broker authentication using the default broker account");
|
375
|
+
}
|
376
|
+
else {
|
377
|
+
msalLogger.verbose("Attempting broker authentication without the default broker account");
|
378
|
+
}
|
379
|
+
try {
|
380
|
+
return await app.acquireTokenInteractive(interactiveRequest);
|
381
|
+
}
|
382
|
+
catch (e) {
|
383
|
+
msalLogger.verbose(`Failed to authenticate through the broker: ${e.message}`);
|
384
|
+
// If we tried to use the default broker account and failed, fall back to interactive authentication
|
385
|
+
if (useDefaultBrokerAccount) {
|
386
|
+
return getBrokeredToken(/* useDefaultBrokerAccount: */ false);
|
387
|
+
}
|
388
|
+
else {
|
389
|
+
throw e;
|
390
|
+
}
|
391
|
+
}
|
392
|
+
}
|
393
|
+
function createBaseInteractiveRequest() {
|
394
|
+
var _a, _b;
|
395
|
+
return {
|
396
|
+
openBrowser: async (url) => {
|
397
|
+
await interactiveBrowserMockable.open(url, { wait: true, newInstance: true });
|
398
|
+
},
|
399
|
+
scopes,
|
400
|
+
authority: state.msalConfig.auth.authority,
|
401
|
+
claims: options === null || options === void 0 ? void 0 : options.claims,
|
402
|
+
loginHint: options === null || options === void 0 ? void 0 : options.loginHint,
|
403
|
+
errorTemplate: (_a = options === null || options === void 0 ? void 0 : options.browserCustomizationOptions) === null || _a === void 0 ? void 0 : _a.errorMessage,
|
404
|
+
successTemplate: (_b = options === null || options === void 0 ? void 0 : options.browserCustomizationOptions) === null || _b === void 0 ? void 0 : _b.successMessage,
|
405
|
+
};
|
406
|
+
}
|
407
|
+
return withSilentAuthentication(app, scopes, options, async () => {
|
408
|
+
var _a;
|
409
|
+
const interactiveRequest = createBaseInteractiveRequest();
|
410
|
+
if (state.pluginConfiguration.broker.isEnabled) {
|
411
|
+
return getBrokeredToken((_a = state.pluginConfiguration.broker.useDefaultBrokerAccount) !== null && _a !== void 0 ? _a : false);
|
412
|
+
}
|
413
|
+
return app.acquireTokenInteractive(interactiveRequest);
|
414
|
+
});
|
415
|
+
}
|
316
416
|
return {
|
317
417
|
getActiveAccount,
|
318
418
|
getTokenByClientSecret,
|
@@ -321,6 +421,8 @@ To work with multiple accounts for the same Client ID and Tenant ID, please prov
|
|
321
421
|
getTokenByDeviceCode,
|
322
422
|
getTokenByUsernamePassword,
|
323
423
|
getTokenByAuthorizationCode,
|
424
|
+
getTokenOnBehalfOf,
|
425
|
+
getTokenByInteractiveRequest,
|
324
426
|
};
|
325
427
|
}
|
326
428
|
//# sourceMappingURL=msalClient.js.map
|