Skip to content

Commit

Permalink
Merge branch 'dev' into release-1.3.1
Browse files Browse the repository at this point in the history
  • Loading branch information
jasonnutter authored May 12, 2020
2 parents 3e11682 + d259eba commit 55c5587
Show file tree
Hide file tree
Showing 25 changed files with 405 additions and 274 deletions.
82 changes: 56 additions & 26 deletions lib/msal-browser/src/app/PublicClientApplication.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,22 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
import { Account, SPAClient, AuthenticationParameters, INetworkModule, TokenResponse, UrlString, TemporaryCacheKeys, TokenRenewParameters, StringUtils, PromptValue, ServerError, InteractionRequiredAuthError } from "@azure/msal-common";
import {
Account,
SPAClient,
AuthenticationParameters,
INetworkModule,
TokenResponse,
UrlString,
TemporaryCacheKeys,
TokenRenewParameters,
StringUtils,
PromptValue,
ServerError,
Authority,
AuthorityFactory,
InteractionRequiredAuthError
} from "@azure/msal-common";
import { Configuration, buildConfiguration } from "../config/Configuration";
import { BrowserStorage } from "../cache/BrowserStorage";
import { CryptoOps } from "../crypto/CryptoOps";
Expand All @@ -22,23 +37,25 @@ import { version } from "../../package.json";
*/
export class PublicClientApplication {

// Input configuration by developer/user
private config: Configuration;

// auth functions imported from @azure/msal-common module
private authModule: SPAClient;
private readonly authModule: SPAClient;

// Crypto interface implementation
private browserCrypto: CryptoOps;
private readonly browserCrypto: CryptoOps;

// Storage interface implementation
private browserStorage: BrowserStorage;
private readonly browserStorage: BrowserStorage;

// Network interface implementation
private networkClient: INetworkModule;
private readonly networkClient: INetworkModule;

// Response promise
private tokenExchangePromise: Promise<TokenResponse>;
private readonly tokenExchangePromise: Promise<TokenResponse>;

// Input configuration by developer/user
private config: Configuration;

protected defaultAuthorityInstance: Authority;

/**
* @constructor
Expand Down Expand Up @@ -74,9 +91,22 @@ export class PublicClientApplication {
// Initialize the browser storage class.
this.browserStorage = new BrowserStorage(this.config.auth.clientId, this.config.cache);

this.defaultAuthorityInstance = AuthorityFactory.createInstance(
this.config.auth.authority || "https://login.microsoftonline.com/common",
this.config.system.networkClient
);

// Create auth module.
this.authModule = new SPAClient({
authOptions: this.config.auth,
authOptions: {
clientId: this.config.auth.clientId,
authority: this.config.auth.authority ?
AuthorityFactory.createInstance(this.config.auth.authority, this.config.system.networkClient) :
this.defaultAuthorityInstance,
knownAuthorities: this.config.auth.knownAuthorities,
redirectUri: this.config.auth.redirectUri,
postLogoutRedirectUri: this.config.auth.postLogoutRedirectUri
},
systemOptions: {
tokenRenewalOffsetSeconds: this.config.system.tokenRenewalOffsetSeconds,
telemetry: this.config.system.telemetry
Expand Down Expand Up @@ -132,12 +162,12 @@ export class PublicClientApplication {
}

/**
* Event handler function which allows users to fire events after the PublicClientApplication object
* has loaded during redirect flows. This should be invoked on all page loads involved in redirect
* Event handler function which allows users to fire events after the PublicClientApplication object
* has loaded during redirect flows. This should be invoked on all page loads involved in redirect
* auth flows.
* @returns token response or null. If the return value is null, then no auth redirect was detected.
*/
async handleRedirectPromise(): Promise<TokenResponse|null> {
async handleRedirectPromise(): Promise<TokenResponse | null> {
return this.tokenExchangePromise;
}

Expand All @@ -148,7 +178,7 @@ export class PublicClientApplication {
*/
private async handleRedirectResponse(): Promise<TokenResponse> {
// Get current location hash from window or cache.
const { location: { hash } } = window;
const {location: {hash}} = window;
const cachedHash = this.browserStorage.getItem(TemporaryCacheKeys.URL_HASH);
const isResponseHash = UrlString.hashContainsKnownProperties(hash);

Expand All @@ -168,7 +198,7 @@ export class PublicClientApplication {
if (this.config.auth.navigateToLoginRequestUrl && isResponseHash && !BrowserUtils.isInIframe()) {
// Returned from authority using redirect - need to perform navigation before processing response
this.browserStorage.setItem(TemporaryCacheKeys.URL_HASH, hash);

if (StringUtils.isEmpty(loginRequestUrl) || loginRequestUrl === "null") {
// Redirect to home page if login request url is null (real null or the string null)
this.authModule.logger.warning("Unable to get valid login request url from cache, redirecting to home page");
Expand Down Expand Up @@ -324,20 +354,20 @@ export class PublicClientApplication {
// #endregion

// #region Silent Flow

/**
* This function uses a hidden iframe to fetch an authorization code from the eSTS. There are cases where this may not work:
* - Any browser using a form of Intelligent Tracking Prevention
* - If there is not an established session with the service
*
* In these cases, the request must be done inside a popup or full frame redirect.
*
*
* In these cases, the request must be done inside a popup or full frame redirect.
*
* For the cases where interaction is required, you cannot send a request with prompt=none.
*
* If your refresh token has expired, you can use this function to fetch a new set of tokens silently as long as
*
* If your refresh token has expired, you can use this function to fetch a new set of tokens silently as long as
* you session on the server still exists.
* @param {@link AuthenticationParameters}
*
* @param {@link AuthenticationParameters}
*
* To renew idToken, please pass clientId as the only scope in the Authentication Parameters.
* @returns {Promise.<TokenResponse>} - a promise that is fulfilled when this function has completed, or rejected if an error was raised. Returns the {@link AuthResponse} object
*/
Expand Down Expand Up @@ -413,9 +443,9 @@ export class PublicClientApplication {
}

/**
* Helper which acquires an authorization code silently using a hidden iframe from given url
* Helper which acquires an authorization code silently using a hidden iframe from given url
* using the scopes requested as part of the id, and exchanges the code for a set of OAuth tokens.
* @param navigateUrl
* @param navigateUrl
* @param userRequestScopes
*/
private async silentTokenHelper(navigateUrl: string, userRequestScopes: string): Promise<TokenResponse> {
Expand All @@ -428,7 +458,7 @@ export class PublicClientApplication {
const hash = await silentHandler.monitorFrameForHash(msalFrame, this.config.system.iframeHashTimeout, navigateUrl);
// Handle response from hash string.
return await silentHandler.handleCodeResponse(hash);
} catch(e) {
} catch (e) {
throw e;
}
}
Expand Down
19 changes: 12 additions & 7 deletions lib/msal-browser/src/config/Configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,20 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
import { AuthOptions, SystemOptions, LoggerOptions, INetworkModule, LogLevel, DEFAULT_SYSTEM_OPTIONS } from "@azure/msal-common";
import { SystemOptions, LoggerOptions, INetworkModule, LogLevel, DEFAULT_SYSTEM_OPTIONS } from "@azure/msal-common";
import { BrowserUtils } from "../utils/BrowserUtils";
import { BrowserConstants } from "../utils/BrowserConstants";

// Default timeout for popup windows and iframes in milliseconds
const DEFAULT_POPUP_TIMEOUT_MS = 60000;
const DEFAULT_IFRAME_TIMEOUT_MS = 6000;

export type BrowserAuthOptions = AuthOptions & {
export type BrowserAuthOptions = {
clientId: string;
authority?: string;
knownAuthorities?: Array<string>;
redirectUri?: string | (() => string);
postLogoutRedirectUri?: string | (() => string);
navigateToLoginRequestUrl?: boolean;
};

Expand Down Expand Up @@ -60,6 +65,7 @@ export type Configuration = {
const DEFAULT_AUTH_OPTIONS: BrowserAuthOptions = {
clientId: "",
authority: null,
knownAuthorities: [],
redirectUri: () => BrowserUtils.getCurrentUri(),
postLogoutRedirectUri: () => BrowserUtils.getCurrentUri(),
navigateToLoginRequestUrl: true
Expand Down Expand Up @@ -108,12 +114,11 @@ const DEFAULT_BROWSER_SYSTEM_OPTIONS: BrowserSystemOptions = {
/**
* MSAL function that sets the default options when not explicitly configured from app developer
*
* @param TAuthOptions
* @param TCacheOptions
* @param TSystemOptions
* @param TFrameworkOptions
* @param auth
* @param cache
* @param system
*
* @returns TConfiguration object
* @returns Configuration object
*/
export function buildConfiguration({ auth: userInputAuth, cache: userInputCache, system: userInputSystem }: Configuration): Configuration {
const overlayedConfig: Configuration = {
Expand Down
17 changes: 8 additions & 9 deletions lib/msal-common/src/client/AuthorizationCodeClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,20 +36,18 @@ export class AuthorizationCodeClient extends BaseClient {
*/
async getAuthCodeUrl(request: AuthorizationCodeUrlRequest): Promise<string> {

const authority: Authority = await this.createAuthority(request && request.authority);
const queryString = this.createAuthCodeUrlQueryString(request);
return `${authority.authorizationEndpoint}?${queryString}`;
return `${this.defaultAuthority.authorizationEndpoint}?${queryString}`;
}

/**
* API to acquire a token in exchange of 'authorization_code` acquired by the user in the first leg of the authorization_code_grant
* API to acquire a token in exchange of 'authorization_code` acquired by the user in the first leg of the
* authorization_code_grant
* @param request
*/
async acquireToken(request: AuthorizationCodeRequest): Promise<string> {

this.logger.info("in acquireToken call");
const authority: Authority = await this.createAuthority(request && request.authority);
const response = await this.executeTokenRequest(authority, request);
const response = await this.executeTokenRequest(this.defaultAuthority, request);
return JSON.stringify(response.body);
// TODO add response_handler here to send the response
}
Expand All @@ -59,7 +57,8 @@ export class AuthorizationCodeClient extends BaseClient {
* @param authority
* @param request
*/
private async executeTokenRequest(authority: Authority, request: AuthorizationCodeRequest): Promise<NetworkResponse<ServerAuthorizationTokenResponse>> {
private async executeTokenRequest(authority: Authority, request: AuthorizationCodeRequest)
: Promise<NetworkResponse<ServerAuthorizationTokenResponse>> {

const requestBody = this.createTokenRequestBody(request);
const headers: Map<string, string> = this.createDefaultTokenRequestHeaders();
Expand All @@ -71,7 +70,7 @@ export class AuthorizationCodeClient extends BaseClient {
* Generates a map for all the params to be sent to the service
* @param request
*/
private createTokenRequestBody(request: AuthorizationCodeRequest) : string {
private createTokenRequestBody(request: AuthorizationCodeRequest): string {
const parameterBuilder = new RequestParameterBuilder();

parameterBuilder.addClientId(this.config.authOptions.clientId);
Expand Down Expand Up @@ -153,7 +152,7 @@ export class AuthorizationCodeClient extends BaseClient {
parameterBuilder.addNonce(request.nonce);
}

if(request.claims) {
if (request.claims) {
parameterBuilder.addClaims(request.claims);
}

Expand Down
34 changes: 6 additions & 28 deletions lib/msal-common/src/client/BaseClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/

import { ClientConfiguration, buildClientConfiguration } from "../config/ClientConfiguration";
import { ICacheStorage } from "../cache/ICacheStorage";
import { CacheHelpers } from "../cache/CacheHelpers";
Expand All @@ -10,11 +11,9 @@ import { ICrypto } from "../crypto/ICrypto";
import { Account } from "../account/Account";
import { Authority } from "../authority/Authority";
import { Logger } from "../logger/Logger";
import { AuthorityFactory } from "../authority/AuthorityFactory";
import {AADServerParamKeys, Constants, HeaderNames} from "../utils/Constants";
import {ClientAuthError} from "../error/ClientAuthError";
import {NetworkResponse} from "../network/NetworkManager";
import {ServerAuthorizationTokenResponse} from "../server/ServerAuthorizationTokenResponse";
import { AADServerParamKeys, Constants, HeaderNames } from "../utils/Constants";
import { NetworkResponse } from "../network/NetworkManager";
import { ServerAuthorizationTokenResponse } from "../server/ServerAuthorizationTokenResponse";

/**
* Base application class which will construct requests to send to and handle responses from the Microsoft STS using the authorization code flow.
Expand Down Expand Up @@ -43,7 +42,7 @@ export abstract class BaseClient {
protected account: Account;

// Default authority object
protected defaultAuthorityInstance: Authority;
protected defaultAuthority: Authority;

protected constructor(configuration: ClientConfiguration) {
// Set the configuration
Expand All @@ -65,28 +64,7 @@ export abstract class BaseClient {
this.networkClient = this.config.networkInterface;

// Default authority instance.
this.defaultAuthorityInstance = AuthorityFactory.createInstance(
this.config.authOptions.authority || Constants.DEFAULT_AUTHORITY,
this.networkClient
);
}

/**
* Create authority instance if not set already, resolve well-known-endpoint
* @param authorityString
*/
protected async createAuthority(authorityString: string): Promise<Authority> {

// TODO expensive to resolve authority endpoints every time.
const authority: Authority = authorityString
? AuthorityFactory.createInstance(authorityString, this.networkClient)
: this.defaultAuthorityInstance;

await authority.resolveEndpointsAsync().catch(error => {
throw ClientAuthError.createEndpointDiscoveryIncompleteError(error);
});

return authority;
this.defaultAuthority = this.config.authOptions.authority;
}

/**
Expand Down
11 changes: 4 additions & 7 deletions lib/msal-common/src/client/DeviceCodeClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import { DeviceCodeResponse, ServerDeviceCodeResponse } from "../response/DeviceCodeResponse";
import { BaseClient } from "./BaseClient";
import { DeviceCodeRequest } from "../request/DeviceCodeRequest";
import { Authority } from "../authority/Authority";
import { ClientAuthError } from "../error/ClientAuthError";
import { RequestParameterBuilder } from "../server/RequestParameterBuilder";
import { Constants, GrantType } from "../utils/Constants";
Expand All @@ -20,8 +19,6 @@ import { ScopeSet } from "../request/ScopeSet";
*/
export class DeviceCodeClient extends BaseClient {

private authority: Authority;

constructor(configuration: ClientConfiguration) {
super(configuration);
}
Expand All @@ -32,7 +29,7 @@ export class DeviceCodeClient extends BaseClient {
* @param request
*/
public async acquireToken(request: DeviceCodeRequest): Promise<string> {
this.authority = await this.createAuthority(request.authority);

const deviceCodeResponse: DeviceCodeResponse = await this.getDeviceCode(request);
request.deviceCodeCallback(deviceCodeResponse);
const response: ServerAuthorizationTokenResponse = await this.acquireTokenWithDeviceCode(
Expand Down Expand Up @@ -91,7 +88,7 @@ export class DeviceCodeClient extends BaseClient {
const queryString: string = this.createQueryString(request);

// TODO add device code endpoint to authority class
return `${this.authority.canonicalAuthority}${Constants.DEVICE_CODE_ENDPOINT_PATH}?${queryString}`;
return `${this.defaultAuthority.canonicalAuthority}${Constants.DEVICE_CODE_ENDPOINT_PATH}?${queryString}`;
}

/**
Expand Down Expand Up @@ -145,7 +142,7 @@ export class DeviceCodeClient extends BaseClient {

} else {
const response = await this.executePostToTokenEndpoint(
this.authority.tokenEndpoint,
this.defaultAuthority.tokenEndpoint,
requestBody,
headers);

Expand Down Expand Up @@ -176,7 +173,7 @@ export class DeviceCodeClient extends BaseClient {

const scopeSet = new ScopeSet(request.scopes || [],
this.config.authOptions.clientId,
true);
false);
requestParameters.addScopes(scopeSet);
requestParameters.addClientId(this.config.authOptions.clientId);
requestParameters.addGrantType(GrantType.DEVICE_CODE_GRANT);
Expand Down
3 changes: 1 addition & 2 deletions lib/msal-common/src/client/RefreshTokenClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,7 @@ export class RefreshTokenClient extends BaseClient {
}

public async acquireToken(request: RefreshTokenRequest): Promise<string>{
const authority = await this.createAuthority(request.authority);
const response = await this.executeTokenRequest(request, authority);
const response = await this.executeTokenRequest(request, this.defaultAuthority);
// TODO add response_handler here to send the response
return JSON.stringify(response.body);
}
Expand Down
Loading

0 comments on commit 55c5587

Please sign in to comment.