UNPKG

msal

Version:
362 lines (317 loc) 12.3 kB
// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. import { Authority } from "./Authority"; import { CryptoUtils } from "./utils/CryptoUtils"; import { AuthenticationParameters, validateClaimsRequest } from "./AuthenticationParameters"; import { StringDict } from "./MsalTypes" import { Account } from "./Account"; import { SSOTypes, Constants, PromptState, BlacklistedEQParams, libraryVersion } from "./utils/Constants"; import { ClientConfigurationError } from "./error/ClientConfigurationError"; import { StringUtils } from "./utils/StringUtils"; /** * Nonce: OIDC Nonce definition: https://openid.net/specs/openid-connect-core-1_0.html#IDToken * State: OAuth Spec: https://tools.ietf.org/html/rfc6749#section-10.12 * @hidden */ export class ServerRequestParameters { authorityInstance: Authority; clientId: string; scopes: Array<string>; nonce: string; state: string; // telemetry information xClientVer: string; xClientSku: string; correlationId: string; responseType: string; redirectUri: string; promptValue: string; claimsValue: string; queryParameters: string; extraQueryParameters: string; public get authority(): string { return this.authorityInstance ? this.authorityInstance.CanonicalAuthority : null; } /** * Constructor * @param authority * @param clientId * @param scope * @param responseType * @param redirectUri * @param state */ constructor (authority: Authority, clientId: string, scope: Array<string>, responseType: string, redirectUri: string, state: string) { this.authorityInstance = authority; this.clientId = clientId; if (!scope) { this.scopes = [clientId]; } else { this.scopes = [ ...scope ]; } this.nonce = CryptoUtils.createNewGuid(); this.state = state && !StringUtils.isEmpty(state) ? CryptoUtils.createNewGuid() + "|" + state : CryptoUtils.createNewGuid(); // TODO: Change this to user passed vs generated with the new PR this.correlationId = CryptoUtils.createNewGuid(); // telemetry information this.xClientSku = "MSAL.JS"; this.xClientVer = libraryVersion(); this.responseType = responseType; this.redirectUri = redirectUri; } /** * @hidden * @ignore * * Utility to populate QueryParameters and ExtraQueryParameters to ServerRequestParamerers * @param request * @param serverAuthenticationRequest */ populateQueryParams(account: Account, request: AuthenticationParameters, adalIdTokenObject?: any): void { let queryParameters: StringDict = {}; if (request) { // add the prompt parameter to serverRequestParameters if passed if (request.prompt) { this.validatePromptParameter(request.prompt); this.promptValue = request.prompt; } // Add claims challenge to serverRequestParameters if passed if (request.claimsRequest) { validateClaimsRequest(request); this.claimsValue = request.claimsRequest; } // if the developer provides one of these, give preference to developer choice if (ServerRequestParameters.isSSOParam(request)) { queryParameters = this.constructUnifiedCacheQueryParameter(request, null); } } if (adalIdTokenObject) { queryParameters = this.constructUnifiedCacheQueryParameter(null, adalIdTokenObject); } // adds sid/login_hint if not populated; populates domain_req, login_req and domain_hint // this.logger.verbose("Calling addHint parameters"); queryParameters = this.addHintParameters(account, queryParameters); // sanity check for developer passed extraQueryParameters let eQParams: StringDict; if (request) { eQParams = this.sanitizeEQParams(request); } // Populate the extraQueryParameters to be sent to the server this.queryParameters = ServerRequestParameters.generateQueryParametersString(queryParameters); this.extraQueryParameters = ServerRequestParameters.generateQueryParametersString(eQParams); } //#region QueryParam helpers /** * @hidden * @ignore * * Utility to test if valid prompt value is passed in the request * @param request */ private validatePromptParameter (prompt: string) { if (!([PromptState.LOGIN, PromptState.SELECT_ACCOUNT, PromptState.CONSENT, PromptState.NONE].indexOf(prompt) >= 0)) { throw ClientConfigurationError.createInvalidPromptError(prompt); } } /** * Constructs extraQueryParameters to be sent to the server for the AuthenticationParameters set by the developer * in any login() or acquireToken() calls * @param idTokenObject * @param extraQueryParameters * @param sid * @param loginHint */ //TODO: check how this behaves when domain_hint only is sent in extraparameters and idToken has no upn. private constructUnifiedCacheQueryParameter(request: AuthenticationParameters, idTokenObject: any): StringDict { // preference order: account > sid > login_hint let ssoType; let ssoData; let serverReqParam: StringDict = {}; // if account info is passed, account.sid > account.login_hint if (request) { if (request.account) { const account: Account = request.account; if (account.sid) { ssoType = SSOTypes.SID; ssoData = account.sid; } else if (account.userName) { ssoType = SSOTypes.LOGIN_HINT; ssoData = account.userName; } } // sid from request else if (request.sid) { ssoType = SSOTypes.SID; ssoData = request.sid; } // loginHint from request else if (request.loginHint) { ssoType = SSOTypes.LOGIN_HINT; ssoData = request.loginHint; } } // adalIdToken retrieved from cache else if (idTokenObject) { if (idTokenObject.hasOwnProperty(Constants.upn)) { ssoType = SSOTypes.ID_TOKEN; ssoData = idTokenObject.upn; } else { ssoType = SSOTypes.ORGANIZATIONS; ssoData = null; } } serverReqParam = this.addSSOParameter(ssoType, ssoData); // add the HomeAccountIdentifier info/ domain_hint if (request && request.account && request.account.homeAccountIdentifier) { serverReqParam = this.addSSOParameter(SSOTypes.HOMEACCOUNT_ID, request.account.homeAccountIdentifier, serverReqParam); } return serverReqParam; } /** * @hidden * * Adds login_hint to authorization URL which is used to pre-fill the username field of sign in page for the user if known ahead of time * domain_hint can be one of users/organizations which when added skips the email based discovery process of the user * domain_req utid received as part of the clientInfo * login_req uid received as part of clientInfo * Also does a sanity check for extraQueryParameters passed by the user to ensure no repeat queryParameters * * @param {@link Account} account - Account for which the token is requested * @param queryparams * @param {@link ServerRequestParameters} * @ignore */ private addHintParameters(account: Account, qParams: StringDict): StringDict { // This is a final check for all queryParams added so far; preference order: sid > login_hint // sid cannot be passed along with login_hint or domain_hint, hence we check both are not populated yet in queryParameters if (account && !qParams[SSOTypes.SID]) { // sid - populate only if login_hint is not already populated and the account has sid const populateSID = !qParams[SSOTypes.LOGIN_HINT] && account.sid && this.promptValue === PromptState.NONE; if (populateSID) { qParams = this.addSSOParameter(SSOTypes.SID, account.sid, qParams); } // login_hint - account.userName else { const populateLoginHint = !qParams[SSOTypes.LOGIN_HINT] && account.userName && !StringUtils.isEmpty(account.userName); if (populateLoginHint) { qParams = this.addSSOParameter(SSOTypes.LOGIN_HINT, account.userName, qParams); } } const populateReqParams = !qParams[SSOTypes.DOMAIN_REQ] && !qParams[SSOTypes.LOGIN_REQ]; if (populateReqParams) { qParams = this.addSSOParameter(SSOTypes.HOMEACCOUNT_ID, account.homeAccountIdentifier, qParams); } } return qParams; } /** * Add SID to extraQueryParameters * @param sid */ private addSSOParameter(ssoType: string, ssoData: string, ssoParam?: StringDict): StringDict { if (!ssoParam) { ssoParam = {}; } if (!ssoData) { return ssoParam; } switch (ssoType) { case SSOTypes.SID: { ssoParam[SSOTypes.SID] = ssoData; break; } case SSOTypes.ID_TOKEN: { ssoParam[SSOTypes.LOGIN_HINT] = ssoData; ssoParam[SSOTypes.DOMAIN_HINT] = SSOTypes.ORGANIZATIONS; break; } case SSOTypes.LOGIN_HINT: { ssoParam[SSOTypes.LOGIN_HINT] = ssoData; break; } case SSOTypes.ORGANIZATIONS: { ssoParam[SSOTypes.DOMAIN_HINT] = SSOTypes.ORGANIZATIONS; break; } case SSOTypes.CONSUMERS: { ssoParam[SSOTypes.DOMAIN_HINT] = SSOTypes.CONSUMERS; break; } case SSOTypes.HOMEACCOUNT_ID: { let homeAccountId = ssoData.split("."); const uid = CryptoUtils.base64Decode(homeAccountId[0]); const utid = CryptoUtils.base64Decode(homeAccountId[1]); // TODO: domain_req and login_req are not needed according to eSTS team ssoParam[SSOTypes.LOGIN_REQ] = uid; ssoParam[SSOTypes.DOMAIN_REQ] = utid; if (utid === Constants.consumersUtid) { ssoParam[SSOTypes.DOMAIN_HINT] = SSOTypes.CONSUMERS; } else { ssoParam[SSOTypes.DOMAIN_HINT] = SSOTypes.ORGANIZATIONS; } break; } case SSOTypes.LOGIN_REQ: { ssoParam[SSOTypes.LOGIN_REQ] = ssoData; break; } case SSOTypes.DOMAIN_REQ: { ssoParam[SSOTypes.DOMAIN_REQ] = ssoData; break; } } return ssoParam; } /** * @hidden * @ignore * Removes unnecessary or duplicate query parameters from extraQueryParameters * @param request */ private sanitizeEQParams(request: AuthenticationParameters) : StringDict { let eQParams : StringDict = request.extraQueryParameters; if (!eQParams) { return null; } if (request.claimsRequest) { // this.logger.warning("Removed duplicate claims from extraQueryParameters. Please use either the claimsRequest field OR pass as extraQueryParameter - not both."); delete eQParams[Constants.claims]; } BlacklistedEQParams.forEach(param => { if (eQParams[param]) { // this.logger.warning("Removed duplicate " + param + " from extraQueryParameters. Please use the " + param + " field in request object."); delete eQParams[param]; } }); return eQParams; } /** * Utility to generate a QueryParameterString from a Key-Value mapping of extraQueryParameters passed * @param extraQueryParameters */ static generateQueryParametersString(queryParameters: StringDict): string { let paramsString: string = null; if (queryParameters) { Object.keys(queryParameters).forEach((key: string) => { if (paramsString == null) { paramsString = `${key}=${encodeURIComponent(queryParameters[key])}`; } else { paramsString += `&${key}=${encodeURIComponent(queryParameters[key])}`; } }); } return paramsString; } //#endregion /** * Check to see if there are SSO params set in the Request * @param request */ static isSSOParam(request: AuthenticationParameters) { return request && (request.account || request.sid || request.loginHint); } }