Skip to content

[msal-common][msal-node] Authority changes #1424

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 20 commits into from
Apr 17, 2020
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/msal-common/src/authority/AadAuthority.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export class AadAuthority extends Authority {

// for custom domains in AAD where we query the service for the Instance discovery
const response = await this.networkInterface.sendGetRequestAsync<OpenIdConfigResponse>(this.aadInstanceDiscoveryEndpointUrl);
return response.tenant_discovery_endpoint;
return response.body.tenant_discovery_endpoint;
}

/**
Expand Down
8 changes: 5 additions & 3 deletions lib/msal-common/src/authority/Authority.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { UrlString } from "./../url/UrlString";
import { IUri } from "./../url/IUri";
import { ClientAuthError } from "./../error/ClientAuthError";
import { INetworkModule } from "./../network/INetworkModule";
import {NetworkResponse} from "..";

/**
* The authority class validates the authority URIs used by the user, and retrieves the OpenID Configuration Data from the
Expand Down Expand Up @@ -110,7 +111,7 @@ export abstract class Authority {
* @param urlString
*/
private replaceTenant(urlString: string): string {
return urlString.replace("{tenant}", this.tenant);
return urlString.replace(/{tenant}|{tenantid}/g, this.tenant);
}

/**
Expand Down Expand Up @@ -138,7 +139,7 @@ export abstract class Authority {
* Gets OAuth endpoints from the given OpenID configuration endpoint.
* @param openIdConfigurationEndpoint
*/
private async discoverEndpoints(openIdConfigurationEndpoint: string): Promise<TenantDiscoveryResponse> {
private async discoverEndpoints(openIdConfigurationEndpoint: string): Promise<NetworkResponse<TenantDiscoveryResponse>> {
return this.networkInterface.sendGetRequestAsync<TenantDiscoveryResponse>(openIdConfigurationEndpoint);
}

Expand All @@ -152,6 +153,7 @@ export abstract class Authority {
*/
public async resolveEndpointsAsync(): Promise<void> {
const openIdConfigEndpoint = await this.getOpenIdConfigurationEndpointAsync();
this.tenantDiscoveryResponse = await this.discoverEndpoints(openIdConfigEndpoint);
const response = await this.discoverEndpoints(openIdConfigEndpoint);
this.tenantDiscoveryResponse = response.body;
}
}
45 changes: 37 additions & 8 deletions lib/msal-common/src/authority/AuthorityFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ import { ClientAuthError } from "./../error/ClientAuthError";
import { INetworkModule } from "./../network/INetworkModule";
import { StringUtils } from "./../utils/StringUtils";
import { UrlString } from "./../url/UrlString";
import { B2cAuthority } from "./B2cAuthority";

/**
* Initialize B2CTrustedHostList
*/
export const B2CTrustedHostList = {};

export class AuthorityFactory {

Expand All @@ -20,13 +26,31 @@ export class AuthorityFactory {
const authorityUrl = new UrlString(authorityString);
const components = authorityUrl.getUrlComponents();
const pathSegments = components.PathSegments;
switch (pathSegments[0]) {
case "tfp":
// tfp denotes a b2c url
return AuthorityType.B2C;
default:
// default authority is always AAD
return AuthorityType.Aad;

if (pathSegments[0] === "adfs")
return AuthorityType.Adfs;
else if (Object.keys(B2CTrustedHostList).length)
return AuthorityType.B2C;

// defaults to Aad
return AuthorityType.Aad;
}

/**
* @hidden
* @ignore
* Use when Authority is B2C is set to True to provide list of allowed domains.
* @param knownAuthorities
*/
public static setKnownAuthorities(knownAuthorities: Array<string>): void {
if (!Object.keys(B2CTrustedHostList).length){
if (!knownAuthorities.length) {
throw ClientConfigurationError.createKnownAuthoritiesNotSetError();
}

knownAuthorities.forEach(function(authority){
B2CTrustedHostList[authority] = authority;
});
}
}

Expand All @@ -45,8 +69,13 @@ export class AuthorityFactory {
switch (type) {
case AuthorityType.Aad:
return new AadAuthority(authorityUrl, networkInterface);
case AuthorityType.B2C:
return new B2cAuthority(authorityUrl, networkInterface);
// TODO: Support ADFS here in a later PR
default:
throw ClientAuthError.createInvalidAuthorityTypeError(`Given Url: ${authorityUrl}`);
throw ClientAuthError.createInvalidAuthorityTypeError(
`Given Url: ${authorityUrl}`
);
}
}
}
47 changes: 47 additions & 0 deletions lib/msal-common/src/authority/B2cAuthority.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
import { Authority } from "./Authority";
import { B2CTrustedHostList } from "./AuthorityFactory";
import { INetworkModule } from "../network/INetworkModule";
import { ClientConfigurationError } from "../error/ClientConfigurationError";
import { AuthorityType } from "./AuthorityType";

/**
* The B2cAuthority class extends the Authority class and adds functionality specific to the Azure AD OAuth Authority.
*/
export class B2cAuthority extends Authority {
/**
* Set authority type to B2C
*/
public get authorityType(): AuthorityType {
return AuthorityType.B2C;
}
public constructor(authority: string, networkInterface: INetworkModule) {
super(authority, networkInterface);
}

/**
* Returns a promise with the TenantDiscoveryEndpoint
*/
public async getOpenIdConfigurationEndpointAsync(): Promise<string> {
if (this.isInTrustedHostList(this.canonicalAuthorityUrlComponents.HostNameAndPort)) {
return this.defaultOpenIdConfigurationEndpoint;
}

throw ClientConfigurationError.createUnsupportedAuthorityValidationError();
}

/**
* Checks to see if the host is in a list of trusted hosts
* @param {string} The host to look up
*/
public isInTrustedHostList(host: string): boolean {
if (!Object.keys(B2CTrustedHostList).length) {
throw ClientConfigurationError.createKnownAuthoritiesNotSetError();
}

return B2CTrustedHostList[host.toLowerCase()];
}
}
96 changes: 38 additions & 58 deletions lib/msal-common/src/client/AuthorizationCodeClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,20 @@ import { BaseClient } from "./BaseClient";
import { AuthorizationCodeUrlRequest } from "../request/AuthorizationCodeUrlRequest";
import { AuthorizationCodeRequest } from "../request/AuthorizationCodeRequest";
import { Authority } from "../authority/Authority";
import { RequestUtils } from "../utils/RequestUtils";
import { RequestParameterBuilder } from "../server/RequestParameterBuilder";
import { RequestValidator } from "../request/RequestValidator";
import { GrantType } from "../utils/Constants";
import { Configuration } from "../config/Configuration";
import {ServerAuthorizationTokenResponse} from "../server/ServerAuthorizationTokenResponse";
import {NetworkResponse} from "../network/NetworkManager";

/**
* Oauth2.0 Authorization Code client
*/
export class AuthorizationCodeClient extends BaseClient {

constructor(configuration: Configuration) {

super({
authOptions: configuration.authOptions,
systemOptions: configuration.systemOptions,
loggerOptions: configuration.loggerOptions,
storageInterface: configuration.storageInterface,
networkInterface: configuration.networkInterface,
cryptoInterface: configuration.cryptoInterface
});
super(configuration);
}

/**
Expand All @@ -42,9 +36,8 @@ export class AuthorizationCodeClient extends BaseClient {
async getAuthCodeUrl(request: AuthorizationCodeUrlRequest): Promise<string> {

const authority: Authority = await this.createAuthority(request && request.authority);
const queryParams: Map<string, string> = this.generateAuthCodeUrlParams(request);
const queryString = RequestUtils.createQueryString(queryParams);
return authority.authorizationEndpoint + "?" + queryString;
const queryString = this.createAuthCodeUrlQueryString(request);
return `${authority.authorizationEndpoint}?${queryString}`;
}

/**
Expand All @@ -55,132 +48,119 @@ export class AuthorizationCodeClient extends BaseClient {

this.logger.info("in acquireToken call");
const authority: Authority = await this.createAuthority(request && request.authority);
const acquiredTokenResponse = this.executeTokenRequest(authority, request);
return acquiredTokenResponse;

// add response_handler here to send the response
const response = await this.executeTokenRequest(authority, request);
return JSON.stringify(response.body);
// TODO add response_handler here to send the response
}

/**
* Executes POST request to token endpoint
* @param authority
* @param request
*/
private async executeTokenRequest(authority: Authority, request: AuthorizationCodeRequest): Promise<string> {
private async executeTokenRequest(authority: Authority, request: AuthorizationCodeRequest): Promise<NetworkResponse<ServerAuthorizationTokenResponse>> {

const tokenParameters: Map<string, string> = this.generateAuthCodeParams(request);
const requestBody = RequestUtils.createQueryString(tokenParameters);
const requestBody = this.createTokenRequestBody(request);
const headers: Map<string, string> = this.createDefaultTokenRequestHeaders();

let acquiredTokenResponse;
try {
acquiredTokenResponse = this.executePostToTokenEndpoint(
authority.tokenEndpoint,
requestBody,
headers);

return acquiredTokenResponse;
} catch (error) {
console.log(error.response.data); // TODO use logger
return error.response.data;
}
return this.executePostToTokenEndpoint(authority.tokenEndpoint, requestBody, headers);
}

/**
* Generates a map for all the params to be sent to the service
* @param request
*/
private generateAuthCodeParams(request: AuthorizationCodeRequest) : Map<string, string> {
const paramsMap: Map<string, string> = new Map<string, string>();
private createTokenRequestBody(request: AuthorizationCodeRequest) : string {
const parameterBuilder = new RequestParameterBuilder();

RequestUtils.addClientId(paramsMap, this.config.authOptions.clientId);
parameterBuilder.addClientId(this.config.authOptions.clientId);

// validate and add scopes
const scopes = RequestValidator.validateAndGenerateScopes(
request.scopes,
this.config.authOptions.clientId
);
RequestUtils.addScopes(paramsMap, scopes);
parameterBuilder.addScopes(scopes);

// validate the redirectUri (to be a non null value)
RequestValidator.validateRedirectUri(request.redirectUri);
RequestUtils.addRedirectUri(paramsMap, request.redirectUri);
parameterBuilder.addRedirectUri(request.redirectUri);

// add code: user set, not validated
RequestUtils.addAuthorizationCode(paramsMap, request.code);
parameterBuilder.addAuthorizationCode(request.code);

// add code_verifier if passed
if (request.codeVerifier) {
RequestUtils.addCodeVerifier(paramsMap, request.codeVerifier);
parameterBuilder.addCodeVerifier(request.codeVerifier);
}

RequestUtils.addGrantType(paramsMap, GrantType.AUTHORIZATION_CODE_GRANT);
parameterBuilder.addGrantType(GrantType.AUTHORIZATION_CODE_GRANT);

return paramsMap;
return parameterBuilder.createQueryString();
}

/**
* This API validates the `AuthorizationCodeUrlRequest` and creates a URL
* @param request
*/
private generateAuthCodeUrlParams(request: AuthorizationCodeUrlRequest): Map<string, string>{
const paramsMap = new Map<string, string>();
private createAuthCodeUrlQueryString(request: AuthorizationCodeUrlRequest): string {
const parameterBuilder = new RequestParameterBuilder();

RequestUtils.addClientId(paramsMap, this.config.authOptions.clientId);
parameterBuilder.addClientId(this.config.authOptions.clientId);

// validate and add scopes
const scopes = RequestValidator.validateAndGenerateScopes(
request.scopes,
this.config.authOptions.clientId
);
RequestUtils.addScopes(paramsMap, scopes);
parameterBuilder.addScopes(scopes);

// validate the redirectUri (to be a non null value)
RequestValidator.validateRedirectUri(request.redirectUri);
RequestUtils.addRedirectUri(paramsMap, request.redirectUri);
parameterBuilder.addRedirectUri(request.redirectUri);

// generate the correlationId if not set by the user and add
const correlationId = request.correlationId
? request.correlationId
: this.config.cryptoInterface.createNewGuid();
RequestUtils.addCorrelationId(paramsMap, correlationId);
parameterBuilder.addCorrelationId(correlationId);

// add response_mode = fragment (currently hardcoded, have a future option to pass 'query' if the user chooses to)
RequestUtils.addResponseMode(paramsMap, request.responseMode);
// add response_mode. If not passed in it defaults to query.
parameterBuilder.addResponseMode(request.responseMode);

// add response_type = code
RequestUtils.addResponseTypeCode(paramsMap);
parameterBuilder.addResponseTypeCode();

if (request.codeChallenge) {
RequestValidator.validateCodeChallengeParams(request.codeChallenge, request.codeChallengeMethod);
RequestUtils.addCodeChallengeParams(paramsMap, request.codeChallenge, request.codeChallengeMethod);
parameterBuilder.addCodeChallengeParams(request.codeChallenge, request.codeChallengeMethod);
}

if (request.state) {
RequestUtils.addState(paramsMap, request.state);
parameterBuilder.addState(request.state);
}

if (request.prompt) {
RequestValidator.validatePrompt(request.prompt);
RequestUtils.addPrompt(paramsMap, request.prompt);
parameterBuilder.addPrompt(request.prompt);
}

if (request.loginHint) {
RequestUtils.addLoginHint(paramsMap, request.loginHint);
parameterBuilder.addLoginHint(request.loginHint);
}

if (request.domainHint) {
RequestUtils.addDomainHint(paramsMap, request.domainHint);
parameterBuilder.addDomainHint(request.domainHint);
}

if (request.nonce) {
RequestUtils.addNonce(paramsMap, request.nonce);
parameterBuilder.addNonce(request.nonce);
}

if(request.claims) {
RequestUtils.addClaims(paramsMap, request.claims);
parameterBuilder.addClaims(request.claims);
}

return paramsMap;
return parameterBuilder.createQueryString();
}
}
Loading