Skip to content

Commit

Permalink
Merge branch 'dev' into server-request-parameters-populate-null
Browse files Browse the repository at this point in the history
  • Loading branch information
jasonnutter authored Apr 24, 2020
2 parents 96196d4 + ac663a8 commit 407aa0d
Show file tree
Hide file tree
Showing 6 changed files with 75 additions and 24 deletions.
48 changes: 25 additions & 23 deletions lib/msal-core/src/UserAgentApplication.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ import { Constants,
libraryVersion,
TemporaryCacheKeys,
PersistentCacheKeys,
ErrorCacheKeys
ErrorCacheKeys,
FramePrefix
} from "./utils/Constants";
import { CryptoUtils } from "./utils/CryptoUtils";

Expand Down Expand Up @@ -632,6 +633,7 @@ export class UserAgentApplication {
// validate the request
const request = RequestUtils.validateRequest(userRequest, false, this.clientId, Constants.interactionTypeSilent);
const apiEvent: ApiEvent = this.telemetryManager.createAndStartApiEvent(request.correlationId, API_EVENT_IDENTIFIER.AcquireTokenSilent, this.logger);
const requestSignature = RequestUtils.createRequestSignature(request);

return new Promise<AuthResponse>((resolve, reject) => {

Expand Down Expand Up @@ -727,10 +729,10 @@ export class UserAgentApplication {
* refresh attempt with iframe
* Already renewing for this scope, callback when we get the token.
*/
if (window.activeRenewals[scope]) {
this.logger.verbose("Renew token for scope: " + scope + " is in progress. Registering callback");
if (window.activeRenewals[requestSignature]) {
this.logger.verbose("Renew token for scope and authority: " + requestSignature + " is in progress. Registering callback");
// Active renewals contains the state for each renewal.
this.registerCallback(window.activeRenewals[scope], scope, resolve, reject);
this.registerCallback(window.activeRenewals[requestSignature], requestSignature, resolve, reject);
}
else {
if (request.scopes && request.scopes.indexOf(this.clientId) > -1 && request.scopes.length === 1) {
Expand All @@ -740,11 +742,11 @@ export class UserAgentApplication {
*/
this.logger.verbose("renewing idToken");
this.silentLogin = true;
this.renewIdToken(request.scopes, resolve, reject, account, serverAuthenticationRequest);
this.renewIdToken(requestSignature, resolve, reject, account, serverAuthenticationRequest);
} else {
// renew access token
this.logger.verbose("renewing accesstoken");
this.renewToken(request.scopes, resolve, reject, account, serverAuthenticationRequest);
this.renewToken(requestSignature, resolve, reject, account, serverAuthenticationRequest);
}
}
}).catch((err) => {
Expand Down Expand Up @@ -825,10 +827,10 @@ export class UserAgentApplication {
* registered when network errors occur and subsequent token requests for same resource are registered to the pending request.
* @ignore
*/
private async loadIframeTimeout(urlNavigate: string, frameName: string, scope: string): Promise<void> {
private async loadIframeTimeout(urlNavigate: string, frameName: string, requestSignature: string): Promise<void> {
// set iframe session to pending
const expectedState = window.activeRenewals[scope];
this.logger.verbose("Set loading state to pending for: " + scope + ":" + expectedState);
const expectedState = window.activeRenewals[requestSignature];
this.logger.verbose("Set loading state to pending for: " + requestSignature + ":" + expectedState);
this.cacheStorage.setItem(`${TemporaryCacheKeys.RENEW_STATUS}${Constants.resourceDelimiter}${expectedState}`, Constants.inProgress);

// render the iframe synchronously if app chooses no timeout, else wait for the set timer to expire
Expand All @@ -845,7 +847,7 @@ export class UserAgentApplication {
} catch (error) {
if (this.cacheStorage.getItem(`${TemporaryCacheKeys.RENEW_STATUS}${Constants.resourceDelimiter}${expectedState}`) === Constants.inProgress) {
// fail the iframe session if it's in pending state
this.logger.verbose("Loading frame has timed out after: " + (this.config.system.loadFrameTimeout / 1000) + " seconds for scope " + scope + ":" + expectedState);
this.logger.verbose("Loading frame has timed out after: " + (this.config.system.loadFrameTimeout / 1000) + " seconds for scope/authority " + requestSignature + ":" + expectedState);
// Error after timeout
if (expectedState && window.callbackMappedToRenewStates[expectedState]) {
window.callbackMappedToRenewStates[expectedState](null, error);
Expand Down Expand Up @@ -891,9 +893,9 @@ export class UserAgentApplication {
* @param {Function} reject - The reject function of the promise object.
* @ignore
*/
private registerCallback(expectedState: string, scope: string, resolve: Function, reject: Function): void {
private registerCallback(expectedState: string, requestSignature: string, resolve: Function, reject: Function): void {
// track active renewals
window.activeRenewals[scope] = expectedState;
window.activeRenewals[requestSignature] = expectedState;

// initialize callbacks mapped array
if (!window.promiseMappedToRenewStates[expectedState]) {
Expand All @@ -906,7 +908,7 @@ export class UserAgentApplication {
if (!window.callbackMappedToRenewStates[expectedState]) {
window.callbackMappedToRenewStates[expectedState] = (response: AuthResponse, error: AuthError) => {
// reset active renewals
window.activeRenewals[scope] = null;
window.activeRenewals[requestSignature] = null;

// for all promiseMappedtoRenewStates for a given 'state' - call the reject/resolve with error/token respectively
for (let i = 0; i < window.promiseMappedToRenewStates[expectedState].length; ++i) {
Expand Down Expand Up @@ -1337,11 +1339,10 @@ export class UserAgentApplication {
* Acquires access token using a hidden iframe.
* @ignore
*/
private renewToken(scopes: Array<string>, resolve: Function, reject: Function, account: Account, serverAuthenticationRequest: ServerRequestParameters): void {
const scope = scopes.join(" ").toLowerCase();
this.logger.verbose("renewToken is called for scope:" + scope);
private renewToken(requestSignature: string, resolve: Function, reject: Function, account: Account, serverAuthenticationRequest: ServerRequestParameters): void {
this.logger.verbose("renewToken is called for scope and authority: " + requestSignature);

const frameName = `msalRenewFrame${scope}`;
const frameName = WindowUtils.generateFrameName(FramePrefix.TOKEN_FRAME, requestSignature);
const frameHandle = WindowUtils.addHiddenIFrame(frameName, this.logger);

this.updateCacheEntries(serverAuthenticationRequest, account, false);
Expand All @@ -1352,20 +1353,21 @@ export class UserAgentApplication {

window.renewStates.push(serverAuthenticationRequest.state);
window.requestType = Constants.renewToken;
this.registerCallback(serverAuthenticationRequest.state, scope, resolve, reject);
this.registerCallback(serverAuthenticationRequest.state, requestSignature, resolve, reject);
this.logger.infoPii("Navigate to:" + urlNavigate);
frameHandle.src = "about:blank";
this.loadIframeTimeout(urlNavigate, frameName, scope).catch(error => reject(error));
this.loadIframeTimeout(urlNavigate, frameName, requestSignature).catch(error => reject(error));
}

/**
* @hidden
* Renews idtoken for app's own backend when clientId is passed as a single scope in the scopes array.
* @ignore
*/
private renewIdToken(scopes: Array<string>, resolve: Function, reject: Function, account: Account, serverAuthenticationRequest: ServerRequestParameters): void {
private renewIdToken(requestSignature: string, resolve: Function, reject: Function, account: Account, serverAuthenticationRequest: ServerRequestParameters): void {
this.logger.info("renewidToken is called");
const frameName = "msalIdTokenFrame";

const frameName = WindowUtils.generateFrameName(FramePrefix.ID_TOKEN_FRAME, requestSignature);
const frameHandle = WindowUtils.addHiddenIFrame(frameName, this.logger);

this.updateCacheEntries(serverAuthenticationRequest, account, false);
Expand All @@ -1384,10 +1386,10 @@ export class UserAgentApplication {
}

// note: scope here is clientId
this.registerCallback(serverAuthenticationRequest.state, this.clientId, resolve, reject);
this.registerCallback(serverAuthenticationRequest.state, requestSignature, resolve, reject);
this.logger.infoPii("Navigate to:" + urlNavigate);
frameHandle.src = "about:blank";
this.loadIframeTimeout(urlNavigate, frameName, this.clientId).catch(error => reject(error));
this.loadIframeTimeout(urlNavigate, frameName, requestSignature).catch(error => reject(error));
}

/**
Expand Down
10 changes: 9 additions & 1 deletion lib/msal-core/src/utils/Constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,15 @@ export const PromptState = {
LOGIN: "login",
SELECT_ACCOUNT: "select_account",
CONSENT: "consent",
NONE: "none",
NONE: "none"
};

/**
* Frame name prefixes for the hidden iframe created in silent frames
*/
export const FramePrefix = {
ID_TOKEN_FRAME: "msalIdTokenFrame",
TOKEN_FRAME: "msalRenewFrame"
};

/**
Expand Down
8 changes: 8 additions & 0 deletions lib/msal-core/src/utils/RequestUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,4 +203,12 @@ export class RequestUtils {
}
return CryptoUtils.isGuid(correlationId)? correlationId : CryptoUtils.createNewGuid();
}

/**
* Create a request signature
* @param request
*/
static createRequestSignature(request: AuthenticationParameters): string {
return `${request.scopes.join(" ").toLowerCase()}${Constants.resourceDelimiter}${request.authority}`;
}
}
10 changes: 10 additions & 0 deletions lib/msal-core/src/utils/WindowUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,16 @@ export class WindowUtils {
return !!(window.opener && window.opener !== window);
}

/**
* @hidden
* @param prefix
* @param scopes
* @param authority
*/
static generateFrameName(prefix: string, requestSignature: string): string {
return `${prefix}${Constants.resourceDelimiter}${requestSignature}`;
}

/**
* @hidden
* Monitors a window until it loads a url with a hash
Expand Down
7 changes: 7 additions & 0 deletions lib/msal-core/test/utils/RequestUtils.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,4 +164,11 @@ describe("RequestUtils.ts class", () => {
expect(CryptoUtils.isGuid(request.correlationId)).to.be.equal(true);
});

it("generate request signature", () => {
const userRequest: AuthenticationParameters = { scopes: ["s1", "s2", "s3"], authority: TEST_CONFIG.validAuthority};
const requestSignature = RequestUtils.createRequestSignature(userRequest);

expect(requestSignature).to.be.equal("s1 s2 s3|https://login.microsoftonline.com/common");
});

});
16 changes: 16 additions & 0 deletions lib/msal-core/test/utils/WindowUtils.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { expect } from "chai";
import { describe, it } from "mocha";
import { WindowUtils } from "../../src/utils/WindowUtils";
import { FramePrefix } from "../../src/utils/Constants";
import { TEST_CONFIG } from "../TestConstants";
import { ClientAuthError } from "../../src/error/ClientAuthError";

describe("WindowUtils", () => {
Expand Down Expand Up @@ -85,4 +87,18 @@ describe("WindowUtils", () => {
}, 500);
});
});

describe("generateFrameName", () => {
it("test idToken frame name created", () => {
const scopes = ["s1", "s2", "s3"];
const authority = TEST_CONFIG.validAuthority;
const requestSignature = `${scopes.join(" ").toLowerCase()}|${authority}`;

const idTokenFrameName = WindowUtils.generateFrameName(FramePrefix.ID_TOKEN_FRAME, requestSignature);
const tokenFrameName = WindowUtils.generateFrameName(FramePrefix.TOKEN_FRAME, requestSignature);

expect(idTokenFrameName).to.equal(`${FramePrefix.ID_TOKEN_FRAME}|s1 s2 s3|${TEST_CONFIG.validAuthority}`);
expect(tokenFrameName).to.equal(`${FramePrefix.TOKEN_FRAME}|s1 s2 s3|${TEST_CONFIG.validAuthority}`);
});
});
});

0 comments on commit 407aa0d

Please sign in to comment.