664 lines
18 KiB
TypeScript
664 lines
18 KiB
TypeScript
/*
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* Licensed under the MIT License.
|
|
*/
|
|
|
|
import {
|
|
Constants,
|
|
ResponseMode,
|
|
CLIENT_INFO,
|
|
AuthenticationScheme,
|
|
ClaimsRequestKeys,
|
|
PasswordGrantConstants,
|
|
OIDC_DEFAULT_SCOPES,
|
|
ThrottlingConstants,
|
|
HeaderNames,
|
|
} from "../utils/Constants.js";
|
|
import * as AADServerParamKeys from "../constants/AADServerParamKeys.js";
|
|
import { ScopeSet } from "./ScopeSet.js";
|
|
import {
|
|
createClientConfigurationError,
|
|
ClientConfigurationErrorCodes,
|
|
} from "../error/ClientConfigurationError.js";
|
|
import { StringDict } from "../utils/MsalTypes.js";
|
|
import { RequestValidator } from "./RequestValidator.js";
|
|
import {
|
|
ApplicationTelemetry,
|
|
LibraryInfo,
|
|
} from "../config/ClientConfiguration.js";
|
|
import { ServerTelemetryManager } from "../telemetry/server/ServerTelemetryManager.js";
|
|
import { ClientInfo } from "../account/ClientInfo.js";
|
|
import { IPerformanceClient } from "../telemetry/performance/IPerformanceClient.js";
|
|
|
|
function instrumentBrokerParams(
|
|
parameters: Map<string, string>,
|
|
correlationId?: string,
|
|
performanceClient?: IPerformanceClient
|
|
) {
|
|
if (!correlationId) {
|
|
return;
|
|
}
|
|
|
|
const clientId = parameters.get(AADServerParamKeys.CLIENT_ID);
|
|
if (clientId && parameters.has(AADServerParamKeys.BROKER_CLIENT_ID)) {
|
|
performanceClient?.addFields(
|
|
{
|
|
embeddedClientId: clientId,
|
|
embeddedRedirectUri: parameters.get(
|
|
AADServerParamKeys.REDIRECT_URI
|
|
),
|
|
},
|
|
correlationId
|
|
);
|
|
}
|
|
}
|
|
|
|
/** @internal */
|
|
export class RequestParameterBuilder {
|
|
private parameters: Map<string, string>;
|
|
private readonly performanceClient?: IPerformanceClient;
|
|
private readonly correlationId?: string;
|
|
|
|
constructor(
|
|
correlationId?: string,
|
|
performanceClient?: IPerformanceClient
|
|
) {
|
|
this.parameters = new Map<string, string>();
|
|
this.performanceClient = performanceClient;
|
|
this.correlationId = correlationId;
|
|
}
|
|
|
|
/**
|
|
* add response_type = code
|
|
*/
|
|
addResponseTypeCode(): void {
|
|
this.parameters.set(
|
|
AADServerParamKeys.RESPONSE_TYPE,
|
|
encodeURIComponent(Constants.CODE_RESPONSE_TYPE)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* add response_type = token id_token
|
|
*/
|
|
addResponseTypeForTokenAndIdToken(): void {
|
|
this.parameters.set(
|
|
AADServerParamKeys.RESPONSE_TYPE,
|
|
encodeURIComponent(
|
|
`${Constants.TOKEN_RESPONSE_TYPE} ${Constants.ID_TOKEN_RESPONSE_TYPE}`
|
|
)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* add response_mode. defaults to query.
|
|
* @param responseMode
|
|
*/
|
|
addResponseMode(responseMode?: ResponseMode): void {
|
|
this.parameters.set(
|
|
AADServerParamKeys.RESPONSE_MODE,
|
|
encodeURIComponent(responseMode ? responseMode : ResponseMode.QUERY)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Add flag to indicate STS should attempt to use WAM if available
|
|
*/
|
|
addNativeBroker(): void {
|
|
this.parameters.set(
|
|
AADServerParamKeys.NATIVE_BROKER,
|
|
encodeURIComponent("1")
|
|
);
|
|
}
|
|
|
|
/**
|
|
* add scopes. set addOidcScopes to false to prevent default scopes in non-user scenarios
|
|
* @param scopeSet
|
|
* @param addOidcScopes
|
|
*/
|
|
addScopes(
|
|
scopes: string[],
|
|
addOidcScopes: boolean = true,
|
|
defaultScopes: Array<string> = OIDC_DEFAULT_SCOPES
|
|
): void {
|
|
// Always add openid to the scopes when adding OIDC scopes
|
|
if (
|
|
addOidcScopes &&
|
|
!defaultScopes.includes("openid") &&
|
|
!scopes.includes("openid")
|
|
) {
|
|
defaultScopes.push("openid");
|
|
}
|
|
const requestScopes = addOidcScopes
|
|
? [...(scopes || []), ...defaultScopes]
|
|
: scopes || [];
|
|
const scopeSet = new ScopeSet(requestScopes);
|
|
this.parameters.set(
|
|
AADServerParamKeys.SCOPE,
|
|
encodeURIComponent(scopeSet.printScopes())
|
|
);
|
|
}
|
|
|
|
/**
|
|
* add clientId
|
|
* @param clientId
|
|
*/
|
|
addClientId(clientId: string): void {
|
|
this.parameters.set(
|
|
AADServerParamKeys.CLIENT_ID,
|
|
encodeURIComponent(clientId)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* add redirect_uri
|
|
* @param redirectUri
|
|
*/
|
|
addRedirectUri(redirectUri: string): void {
|
|
RequestValidator.validateRedirectUri(redirectUri);
|
|
this.parameters.set(
|
|
AADServerParamKeys.REDIRECT_URI,
|
|
encodeURIComponent(redirectUri)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* add post logout redirectUri
|
|
* @param redirectUri
|
|
*/
|
|
addPostLogoutRedirectUri(redirectUri: string): void {
|
|
RequestValidator.validateRedirectUri(redirectUri);
|
|
this.parameters.set(
|
|
AADServerParamKeys.POST_LOGOUT_URI,
|
|
encodeURIComponent(redirectUri)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* add id_token_hint to logout request
|
|
* @param idTokenHint
|
|
*/
|
|
addIdTokenHint(idTokenHint: string): void {
|
|
this.parameters.set(
|
|
AADServerParamKeys.ID_TOKEN_HINT,
|
|
encodeURIComponent(idTokenHint)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* add domain_hint
|
|
* @param domainHint
|
|
*/
|
|
addDomainHint(domainHint: string): void {
|
|
this.parameters.set(
|
|
AADServerParamKeys.DOMAIN_HINT,
|
|
encodeURIComponent(domainHint)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* add login_hint
|
|
* @param loginHint
|
|
*/
|
|
addLoginHint(loginHint: string): void {
|
|
this.parameters.set(
|
|
AADServerParamKeys.LOGIN_HINT,
|
|
encodeURIComponent(loginHint)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Adds the CCS (Cache Credential Service) query parameter for login_hint
|
|
* @param loginHint
|
|
*/
|
|
addCcsUpn(loginHint: string): void {
|
|
this.parameters.set(
|
|
HeaderNames.CCS_HEADER,
|
|
encodeURIComponent(`UPN:${loginHint}`)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Adds the CCS (Cache Credential Service) query parameter for account object
|
|
* @param loginHint
|
|
*/
|
|
addCcsOid(clientInfo: ClientInfo): void {
|
|
this.parameters.set(
|
|
HeaderNames.CCS_HEADER,
|
|
encodeURIComponent(`Oid:${clientInfo.uid}@${clientInfo.utid}`)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* add sid
|
|
* @param sid
|
|
*/
|
|
addSid(sid: string): void {
|
|
this.parameters.set(AADServerParamKeys.SID, encodeURIComponent(sid));
|
|
}
|
|
|
|
/**
|
|
* add claims
|
|
* @param claims
|
|
*/
|
|
addClaims(claims?: string, clientCapabilities?: Array<string>): void {
|
|
const mergedClaims = this.addClientCapabilitiesToClaims(
|
|
claims,
|
|
clientCapabilities
|
|
);
|
|
RequestValidator.validateClaims(mergedClaims);
|
|
this.parameters.set(
|
|
AADServerParamKeys.CLAIMS,
|
|
encodeURIComponent(mergedClaims)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* add correlationId
|
|
* @param correlationId
|
|
*/
|
|
addCorrelationId(correlationId: string): void {
|
|
this.parameters.set(
|
|
AADServerParamKeys.CLIENT_REQUEST_ID,
|
|
encodeURIComponent(correlationId)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* add library info query params
|
|
* @param libraryInfo
|
|
*/
|
|
addLibraryInfo(libraryInfo: LibraryInfo): void {
|
|
// Telemetry Info
|
|
this.parameters.set(AADServerParamKeys.X_CLIENT_SKU, libraryInfo.sku);
|
|
this.parameters.set(
|
|
AADServerParamKeys.X_CLIENT_VER,
|
|
libraryInfo.version
|
|
);
|
|
if (libraryInfo.os) {
|
|
this.parameters.set(AADServerParamKeys.X_CLIENT_OS, libraryInfo.os);
|
|
}
|
|
if (libraryInfo.cpu) {
|
|
this.parameters.set(
|
|
AADServerParamKeys.X_CLIENT_CPU,
|
|
libraryInfo.cpu
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Add client telemetry parameters
|
|
* @param appTelemetry
|
|
*/
|
|
addApplicationTelemetry(appTelemetry: ApplicationTelemetry): void {
|
|
if (appTelemetry?.appName) {
|
|
this.parameters.set(
|
|
AADServerParamKeys.X_APP_NAME,
|
|
appTelemetry.appName
|
|
);
|
|
}
|
|
|
|
if (appTelemetry?.appVersion) {
|
|
this.parameters.set(
|
|
AADServerParamKeys.X_APP_VER,
|
|
appTelemetry.appVersion
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* add prompt
|
|
* @param prompt
|
|
*/
|
|
addPrompt(prompt: string): void {
|
|
RequestValidator.validatePrompt(prompt);
|
|
this.parameters.set(
|
|
`${AADServerParamKeys.PROMPT}`,
|
|
encodeURIComponent(prompt)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* add state
|
|
* @param state
|
|
*/
|
|
addState(state: string): void {
|
|
if (state) {
|
|
this.parameters.set(
|
|
AADServerParamKeys.STATE,
|
|
encodeURIComponent(state)
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* add nonce
|
|
* @param nonce
|
|
*/
|
|
addNonce(nonce: string): void {
|
|
this.parameters.set(
|
|
AADServerParamKeys.NONCE,
|
|
encodeURIComponent(nonce)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* add code_challenge and code_challenge_method
|
|
* - throw if either of them are not passed
|
|
* @param codeChallenge
|
|
* @param codeChallengeMethod
|
|
*/
|
|
addCodeChallengeParams(
|
|
codeChallenge: string,
|
|
codeChallengeMethod: string
|
|
): void {
|
|
RequestValidator.validateCodeChallengeParams(
|
|
codeChallenge,
|
|
codeChallengeMethod
|
|
);
|
|
if (codeChallenge && codeChallengeMethod) {
|
|
this.parameters.set(
|
|
AADServerParamKeys.CODE_CHALLENGE,
|
|
encodeURIComponent(codeChallenge)
|
|
);
|
|
this.parameters.set(
|
|
AADServerParamKeys.CODE_CHALLENGE_METHOD,
|
|
encodeURIComponent(codeChallengeMethod)
|
|
);
|
|
} else {
|
|
throw createClientConfigurationError(
|
|
ClientConfigurationErrorCodes.pkceParamsMissing
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* add the `authorization_code` passed by the user to exchange for a token
|
|
* @param code
|
|
*/
|
|
addAuthorizationCode(code: string): void {
|
|
this.parameters.set(AADServerParamKeys.CODE, encodeURIComponent(code));
|
|
}
|
|
|
|
/**
|
|
* add the `authorization_code` passed by the user to exchange for a token
|
|
* @param code
|
|
*/
|
|
addDeviceCode(code: string): void {
|
|
this.parameters.set(
|
|
AADServerParamKeys.DEVICE_CODE,
|
|
encodeURIComponent(code)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* add the `refreshToken` passed by the user
|
|
* @param refreshToken
|
|
*/
|
|
addRefreshToken(refreshToken: string): void {
|
|
this.parameters.set(
|
|
AADServerParamKeys.REFRESH_TOKEN,
|
|
encodeURIComponent(refreshToken)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* add the `code_verifier` passed by the user to exchange for a token
|
|
* @param codeVerifier
|
|
*/
|
|
addCodeVerifier(codeVerifier: string): void {
|
|
this.parameters.set(
|
|
AADServerParamKeys.CODE_VERIFIER,
|
|
encodeURIComponent(codeVerifier)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* add client_secret
|
|
* @param clientSecret
|
|
*/
|
|
addClientSecret(clientSecret: string): void {
|
|
this.parameters.set(
|
|
AADServerParamKeys.CLIENT_SECRET,
|
|
encodeURIComponent(clientSecret)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* add clientAssertion for confidential client flows
|
|
* @param clientAssertion
|
|
*/
|
|
addClientAssertion(clientAssertion: string): void {
|
|
if (clientAssertion) {
|
|
this.parameters.set(
|
|
AADServerParamKeys.CLIENT_ASSERTION,
|
|
encodeURIComponent(clientAssertion)
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* add clientAssertionType for confidential client flows
|
|
* @param clientAssertionType
|
|
*/
|
|
addClientAssertionType(clientAssertionType: string): void {
|
|
if (clientAssertionType) {
|
|
this.parameters.set(
|
|
AADServerParamKeys.CLIENT_ASSERTION_TYPE,
|
|
encodeURIComponent(clientAssertionType)
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* add OBO assertion for confidential client flows
|
|
* @param clientAssertion
|
|
*/
|
|
addOboAssertion(oboAssertion: string): void {
|
|
this.parameters.set(
|
|
AADServerParamKeys.OBO_ASSERTION,
|
|
encodeURIComponent(oboAssertion)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* add grant type
|
|
* @param grantType
|
|
*/
|
|
addRequestTokenUse(tokenUse: string): void {
|
|
this.parameters.set(
|
|
AADServerParamKeys.REQUESTED_TOKEN_USE,
|
|
encodeURIComponent(tokenUse)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* add grant type
|
|
* @param grantType
|
|
*/
|
|
addGrantType(grantType: string): void {
|
|
this.parameters.set(
|
|
AADServerParamKeys.GRANT_TYPE,
|
|
encodeURIComponent(grantType)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* add client info
|
|
*
|
|
*/
|
|
addClientInfo(): void {
|
|
this.parameters.set(CLIENT_INFO, "1");
|
|
}
|
|
|
|
/**
|
|
* add extraQueryParams
|
|
* @param eQParams
|
|
*/
|
|
addExtraQueryParameters(eQParams: StringDict): void {
|
|
Object.entries(eQParams).forEach(([key, value]) => {
|
|
if (!this.parameters.has(key) && value) {
|
|
this.parameters.set(key, value);
|
|
}
|
|
});
|
|
}
|
|
|
|
addClientCapabilitiesToClaims(
|
|
claims?: string,
|
|
clientCapabilities?: Array<string>
|
|
): string {
|
|
let mergedClaims: object;
|
|
|
|
// Parse provided claims into JSON object or initialize empty object
|
|
if (!claims) {
|
|
mergedClaims = {};
|
|
} else {
|
|
try {
|
|
mergedClaims = JSON.parse(claims);
|
|
} catch (e) {
|
|
throw createClientConfigurationError(
|
|
ClientConfigurationErrorCodes.invalidClaims
|
|
);
|
|
}
|
|
}
|
|
|
|
if (clientCapabilities && clientCapabilities.length > 0) {
|
|
if (!mergedClaims.hasOwnProperty(ClaimsRequestKeys.ACCESS_TOKEN)) {
|
|
// Add access_token key to claims object
|
|
mergedClaims[ClaimsRequestKeys.ACCESS_TOKEN] = {};
|
|
}
|
|
|
|
// Add xms_cc claim with provided clientCapabilities to access_token key
|
|
mergedClaims[ClaimsRequestKeys.ACCESS_TOKEN][
|
|
ClaimsRequestKeys.XMS_CC
|
|
] = {
|
|
values: clientCapabilities,
|
|
};
|
|
}
|
|
|
|
return JSON.stringify(mergedClaims);
|
|
}
|
|
|
|
/**
|
|
* adds `username` for Password Grant flow
|
|
* @param username
|
|
*/
|
|
addUsername(username: string): void {
|
|
this.parameters.set(
|
|
PasswordGrantConstants.username,
|
|
encodeURIComponent(username)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* adds `password` for Password Grant flow
|
|
* @param password
|
|
*/
|
|
addPassword(password: string): void {
|
|
this.parameters.set(
|
|
PasswordGrantConstants.password,
|
|
encodeURIComponent(password)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* add pop_jwk to query params
|
|
* @param cnfString
|
|
*/
|
|
addPopToken(cnfString: string): void {
|
|
if (cnfString) {
|
|
this.parameters.set(
|
|
AADServerParamKeys.TOKEN_TYPE,
|
|
AuthenticationScheme.POP
|
|
);
|
|
this.parameters.set(
|
|
AADServerParamKeys.REQ_CNF,
|
|
encodeURIComponent(cnfString)
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* add SSH JWK and key ID to query params
|
|
*/
|
|
addSshJwk(sshJwkString: string): void {
|
|
if (sshJwkString) {
|
|
this.parameters.set(
|
|
AADServerParamKeys.TOKEN_TYPE,
|
|
AuthenticationScheme.SSH
|
|
);
|
|
this.parameters.set(
|
|
AADServerParamKeys.REQ_CNF,
|
|
encodeURIComponent(sshJwkString)
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* add server telemetry fields
|
|
* @param serverTelemetryManager
|
|
*/
|
|
addServerTelemetry(serverTelemetryManager: ServerTelemetryManager): void {
|
|
this.parameters.set(
|
|
AADServerParamKeys.X_CLIENT_CURR_TELEM,
|
|
serverTelemetryManager.generateCurrentRequestHeaderValue()
|
|
);
|
|
this.parameters.set(
|
|
AADServerParamKeys.X_CLIENT_LAST_TELEM,
|
|
serverTelemetryManager.generateLastRequestHeaderValue()
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Adds parameter that indicates to the server that throttling is supported
|
|
*/
|
|
addThrottling(): void {
|
|
this.parameters.set(
|
|
AADServerParamKeys.X_MS_LIB_CAPABILITY,
|
|
ThrottlingConstants.X_MS_LIB_CAPABILITY_VALUE
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Adds logout_hint parameter for "silent" logout which prevent server account picker
|
|
*/
|
|
addLogoutHint(logoutHint: string): void {
|
|
this.parameters.set(
|
|
AADServerParamKeys.LOGOUT_HINT,
|
|
encodeURIComponent(logoutHint)
|
|
);
|
|
}
|
|
|
|
addBrokerParameters(params: {
|
|
brokerClientId: string;
|
|
brokerRedirectUri: string;
|
|
}): void {
|
|
const brokerParams: StringDict = {};
|
|
brokerParams[AADServerParamKeys.BROKER_CLIENT_ID] =
|
|
params.brokerClientId;
|
|
brokerParams[AADServerParamKeys.BROKER_REDIRECT_URI] =
|
|
params.brokerRedirectUri;
|
|
|
|
this.addExtraQueryParameters(brokerParams);
|
|
}
|
|
|
|
/**
|
|
* Utility to create a URL from the params map
|
|
*/
|
|
createQueryString(): string {
|
|
const queryParameterArray: Array<string> = new Array<string>();
|
|
|
|
this.parameters.forEach((value, key) => {
|
|
queryParameterArray.push(`${key}=${value}`);
|
|
});
|
|
|
|
instrumentBrokerParams(
|
|
this.parameters,
|
|
this.correlationId,
|
|
this.performanceClient
|
|
);
|
|
|
|
return queryParameterArray.join("&");
|
|
}
|
|
}
|