@azure/identity 4.3.1-alpha.20240618.4 → 4.3.1-alpha.20240619.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -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 { MsalOnBehalfOf } from "../msal/nodeFlows/msalOnBehalfOf";
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.msalFlow = new MsalOnBehalfOf(Object.assign(Object.assign({}, this.options), { logger, tokenCredentialOptions: this.options }));
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
- return this.msalFlow.getToken(arrayScopes, newOptions);
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