Skip to content
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

Turn library state into encoded string that contains guid and timestamp #1395

Merged
merged 13 commits into from
Mar 30, 2020
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
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
4 changes: 2 additions & 2 deletions lib/msal-core/src/ScopeSet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*/

import { ClientConfigurationError } from "./error/ClientConfigurationError";
import { AuthenticationParameters } from "./AuthenticationParameters";
import { Constants } from "./utils/Constants";

export class ScopeSet {

Expand Down Expand Up @@ -117,7 +117,7 @@ export class ScopeSet {
*/
static getScopeFromState(state: string): string {
if (state) {
const splitIndex = state.indexOf("|");
const splitIndex = state.indexOf(Constants.resourceDelimiter);
if (splitIndex > -1 && splitIndex + 1 < state.length) {
return state.substring(splitIndex + 1);
}
Expand Down
1 change: 1 addition & 0 deletions lib/msal-core/src/ServerRequestParameters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { StringDict } from "./MsalTypes";
import { Account } from "./Account";
import { SSOTypes, Constants, PromptState, libraryVersion } from "./utils/Constants";
import { StringUtils } from "./utils/StringUtils";
import { RequestUtils } from "./utils/RequestUtils";

/**
* Nonce: OIDC Nonce definition: https://openid.net/specs/openid-connect-core-1_0.html#IDToken
Expand Down
9 changes: 7 additions & 2 deletions lib/msal-core/src/UserAgentApplication.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ export interface CacheResult {
*/
export type ResponseStateInfo = {
state: string;
timestamp: number,
stateMatch: boolean;
requestType: string;
};
Expand Down Expand Up @@ -1103,9 +1104,12 @@ export class UserAgentApplication {
throw AuthError.createUnexpectedError("Hash was not parsed correctly.");
}
if (parameters.hasOwnProperty("state")) {
const parsedState = RequestUtils.parseLibraryState(parameters.state);

stateResponse = {
requestType: Constants.unknown,
state: parameters.state,
timestamp: parsedState.ts,
stateMatch: false
};
} else {
Expand Down Expand Up @@ -1389,7 +1393,8 @@ export class UserAgentApplication {

// Generate and cache accessTokenKey and accessTokenValue
const expiresIn = TimeUtils.parseExpiresIn(parameters[ServerHashParamKeys.EXPIRES_IN]);
expiration = TimeUtils.now() + expiresIn;
const parsedState = RequestUtils.parseLibraryState(parameters[ServerHashParamKeys.STATE]);
expiration = parsedState.ts + expiresIn;
const accessTokenKey = new AccessTokenKey(authority, this.clientId, scope, clientObj.uid, clientObj.utid);
const accessTokenValue = new AccessTokenValue(parameters[ServerHashParamKeys.ACCESS_TOKEN], idTokenObj.rawIdToken, expiration.toString(), clientInfo);

Expand Down Expand Up @@ -1701,7 +1706,7 @@ export class UserAgentApplication {
*/
getAccountState (state: string) {
if (state) {
const splitIndex = state.indexOf("|");
const splitIndex = state.indexOf(Constants.resourceDelimiter);
if (splitIndex > -1 && splitIndex + 1 < state.length) {
return state.substring(splitIndex + 1);
}
Expand Down
1 change: 1 addition & 0 deletions lib/msal-core/src/utils/Constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ export class Constants {
*/
export enum ServerHashParamKeys {
SCOPE = "scope",
STATE = "state",
ERROR = "error",
ERROR_DESCRIPTION = "error_description",
ACCESS_TOKEN = "access_token",
Expand Down
59 changes: 55 additions & 4 deletions lib/msal-core/src/utils/RequestUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@ import { ScopeSet } from "../ScopeSet";
import { StringDict } from "../MsalTypes";
import { StringUtils } from "../utils/StringUtils";
import { CryptoUtils } from "../utils/CryptoUtils";
import { TimeUtils } from "./TimeUtils";
import { ClientAuthError } from "../error/ClientAuthError";

export type LibraryStateObject = {
state: string,
jasonnutter marked this conversation as resolved.
Show resolved Hide resolved
ts: number
};

/**
* @hidden
Expand Down Expand Up @@ -136,11 +143,55 @@ export class RequestUtils {
* @ignore
*
* generate unique state per request
* @param request
* @param userState User-provided state value
* @returns State string include library state and user state
*/
static validateAndGenerateState(userState: string): string {
// append GUID to user set state or set one for the user if null
return !StringUtils.isEmpty(userState) ? `${RequestUtils.generateLibraryState()}${Constants.resourceDelimiter}${userState}` : RequestUtils.generateLibraryState();
}

/**
* Generates the state value used by the library.
*
* @returns Base64 encoded string representing the state
*/
static generateLibraryState(): string {
const stateObject: LibraryStateObject = {
state: CryptoUtils.createNewGuid(),
ts: TimeUtils.now()
};

const stateString = JSON.stringify(stateObject);

return CryptoUtils.base64Encode(stateString);
}

/**
* Decodes the state value into a StateObject
*
* @param state State value returned in the request
* @returns Parsed values from the encoded state value
*/
static validateAndGenerateState(state: string): string {
// append GUID to user set state or set one for the user if null
return !StringUtils.isEmpty(state) ? CryptoUtils.createNewGuid() + "|" + state : CryptoUtils.createNewGuid();
static parseLibraryState(state: string): LibraryStateObject {
const libraryState = state.split(Constants.resourceDelimiter)[0];

if (CryptoUtils.isGuid(libraryState)) {
return {
state,
ts: TimeUtils.now()
};
}
jasonnutter marked this conversation as resolved.
Show resolved Hide resolved

try {
const stateString = CryptoUtils.base64Decode(libraryState);

const stateObject = JSON.parse(stateString);

return stateObject;
} catch (e) {
throw ClientAuthError.createInvalidStateError(state, null);
}
}

/**
Expand Down
2 changes: 1 addition & 1 deletion lib/msal-core/src/utils/TimeUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export class TimeUtils {
}

/**
* return the current time in Unix time. Date.getTime() returns in milliseconds.
* Return the current time in Unix time (seconds). Date.getTime() returns in milliseconds.
*/
static now(): number {
return Math.round(new Date().getTime() / 1000.0);
Expand Down
24 changes: 13 additions & 11 deletions lib/msal-core/test/TestConstants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,18 +38,20 @@ export const TEST_TOKEN_LIFETIMES = {
TEST_ACCESS_TOKEN_EXP: 1537234948
};



// Test Hashes
export const TEST_HASHES = {
TEST_SUCCESS_ID_TOKEN_HASH: `#id_token=${TEST_TOKENS.IDTOKEN_V2}&client_info=${TEST_DATA_CLIENT_INFO.TEST_RAW_CLIENT_INFO}&state=RANDOM-GUID-HERE|`,
TEST_SUCCESS_ACCESS_TOKEN_HASH: `#access_token=${TEST_TOKENS.ACCESSTOKEN}&id_token=${TEST_TOKENS.IDTOKEN_V2}&scope=test&expiresIn=${TEST_TOKEN_LIFETIMES.DEFAULT_EXPIRES_IN}&client_info=${TEST_DATA_CLIENT_INFO.TEST_RAW_CLIENT_INFO}&state=RANDOM-GUID-HERE|`,
TEST_ERROR_HASH: "#error=error_code&error_description=msal+error+description&state=RANDOM-GUID-HERE|",
TEST_INTERACTION_REQ_ERROR_HASH1: "#error=interaction_required&error_description=msal+error+description&state=RANDOM-GUID-HERE|",
TEST_INTERACTION_REQ_ERROR_HASH2: "#error=interaction_required&error_description=msal+error+description+interaction_required&state=RANDOM-GUID-HERE|",
TEST_LOGIN_REQ_ERROR_HASH1: "#error=login_required&error_description=msal+error+description&state=RANDOM-GUID-HERE|",
TEST_LOGIN_REQ_ERROR_HASH2: "#error=login_required&error_description=msal+error+description+login_required&state=RANDOM-GUID-HERE|",
TEST_CONSENT_REQ_ERROR_HASH1: "#error=consent_required&error_description=msal+error+description&state=RANDOM-GUID-HERE|",
TEST_CONSENT_REQ_ERROR_HASH2: "#error=consent_required&error_description=msal+error+description+consent_required&state=RANDOM-GUID-HERE|"
};
export const testHashesForState = state => ({
TEST_SUCCESS_ID_TOKEN_HASH: `#id_token=${TEST_TOKENS.IDTOKEN_V2}&client_info=${TEST_DATA_CLIENT_INFO.TEST_RAW_CLIENT_INFO}&state=${state}|`,
TEST_SUCCESS_ACCESS_TOKEN_HASH: `#access_token=${TEST_TOKENS.ACCESSTOKEN}&id_token=${TEST_TOKENS.IDTOKEN_V2}&scope=test&expiresIn=${TEST_TOKEN_LIFETIMES.DEFAULT_EXPIRES_IN}&client_info=${TEST_DATA_CLIENT_INFO.TEST_RAW_CLIENT_INFO}&state=${state}|`,
TEST_ERROR_HASH: `#error=error_code&error_description=msal+error+description&state=${state}|`,
TEST_INTERACTION_REQ_ERROR_HASH1: `#error=interaction_required&error_description=msal+error+description&state=${state}|`,
TEST_INTERACTION_REQ_ERROR_HASH2: `#error=interaction_required&error_description=msal+error+description+interaction_required&state=${state}|`,
TEST_LOGIN_REQ_ERROR_HASH1: `#error=login_required&error_description=msal+error+description&state=${state}|`,
TEST_LOGIN_REQ_ERROR_HASH2: `#error=login_required&error_description=msal+error+description+login_required&state=${state}|`,
TEST_CONSENT_REQ_ERROR_HASH1: `#error=consent_required&error_description=msal+error+description&state=${state}|`,
TEST_CONSENT_REQ_ERROR_HASH2: `#error=consent_required&error_description=msal+error+description+consent_required&state=${state}|`
});

// Test MSAL config params
export const TEST_CONFIG = {
Expand Down
Loading