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

[msal-node][msal-common] unit tests for msal-node changes #1449

Merged
merged 12 commits into from Apr 9, 2020
3 changes: 3 additions & 0 deletions .gitignore
Expand Up @@ -255,6 +255,9 @@ paket-files/
.DS_Store
.DS_Store?

# Visual Studio code
.vscode/
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/.vscode/settings.json

I like having this in the repo because it we can use to setup things like linting, etc in the repo.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok cool I'll remove this from .gitignore


# MSAL specific ignore files
node_modules/
.nyc_output/
Expand Down
12 changes: 5 additions & 7 deletions lib/msal-common/src/client/BaseClient.ts
Expand Up @@ -28,7 +28,7 @@ export abstract class BaseClient {
protected config: Configuration;

// Crypto Interface
protected cryptoObj: ICrypto;
protected cryptoUtils: ICrypto;

// Storage Interface
protected cacheStorage: ICacheStorage;
Expand All @@ -53,7 +53,7 @@ export abstract class BaseClient {
this.logger = new Logger(this.config.loggerOptions);

// Initialize crypto
this.cryptoObj = this.config.cryptoInterface;
this.cryptoUtils = this.config.cryptoInterface;

// Initialize storage interface
this.cacheStorage = this.config.storageInterface;
Expand Down Expand Up @@ -82,11 +82,9 @@ export abstract class BaseClient {
? AuthorityFactory.createInstance(authorityString, this.networkClient)
: this.defaultAuthorityInstance;

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

return authority;
}
Expand All @@ -105,7 +103,7 @@ export abstract class BaseClient {
/**
* addLibraryData
*/
createDefaultLibraryHeaders(): Map<string, string> {
protected createDefaultLibraryHeaders(): Map<string, string> {
sameerag marked this conversation as resolved.
Show resolved Hide resolved
const headers = new Map<string, string>();
// library version
headers.set(`${AADServerParamKeys.X_CLIENT_SKU}`, Constants.LIBRARY_NAME);
Expand Down
24 changes: 12 additions & 12 deletions lib/msal-common/src/client/PublicClientSPA.ts
Expand Up @@ -99,7 +99,7 @@ export class PublicClientSPA extends BaseClient {
request,
this.getAccount(),
this.getRedirectUri(),
this.cryptoObj,
this.cryptoUtils,
isLoginCall
);

Expand All @@ -109,7 +109,7 @@ export class PublicClientSPA extends BaseClient {
// Only check for adal token if no SSO params are being used
const adalIdTokenString = this.cacheStorage.getItem(PersistentCacheKeys.ADAL_ID_TOKEN);
if (!StringUtils.isEmpty(adalIdTokenString)) {
adalIdToken = new IdToken(adalIdTokenString, this.cryptoObj);
adalIdToken = new IdToken(adalIdTokenString, this.cryptoUtils);
this.cacheStorage.removeItem(PersistentCacheKeys.ADAL_ID_TOKEN);
}
}
Expand All @@ -132,7 +132,7 @@ export class PublicClientSPA extends BaseClient {
authority: requestParameters.authorityInstance.canonicalAuthority,
correlationId: requestParameters.correlationId
};
this.cacheStorage.setItem(TemporaryCacheKeys.REQUEST_PARAMS, this.cryptoObj.base64Encode(JSON.stringify(tokenRequest)));
this.cacheStorage.setItem(TemporaryCacheKeys.REQUEST_PARAMS, this.cryptoUtils.base64Encode(JSON.stringify(tokenRequest)));

return urlNavigate;
} catch (e) {
Expand Down Expand Up @@ -176,7 +176,7 @@ export class PublicClientSPA extends BaseClient {
tokenRequest,
codeResponse,
this.getRedirectUri(),
this.cryptoObj
this.cryptoUtils
);

// User helper to retrieve token response.
Expand Down Expand Up @@ -247,7 +247,7 @@ export class PublicClientSPA extends BaseClient {

// Only populate id token if it exists in cache item.
return StringUtils.isEmpty(cachedTokenItem.value.idToken) ? defaultTokenResponse :
ResponseHandler.setResponseIdToken(defaultTokenResponse, new IdToken(cachedTokenItem.value.idToken, this.cryptoObj));
ResponseHandler.setResponseIdToken(defaultTokenResponse, new IdToken(cachedTokenItem.value.idToken, this.cryptoUtils));
} else {
// Renew the tokens.
request.authority = cachedTokenItem.key.authority;
Expand All @@ -259,7 +259,7 @@ export class PublicClientSPA extends BaseClient {
request,
null,
this.getRedirectUri(),
this.cryptoObj,
this.cryptoUtils,
cachedTokenItem.value.refreshToken
);

Expand Down Expand Up @@ -325,7 +325,7 @@ export class PublicClientSPA extends BaseClient {
*/
public handleFragmentResponse(hashFragment: string): CodeResponse {
// Handle responses.
const responseHandler = new ResponseHandler(this.clientConfig.auth.clientId, this.cacheStorage, this.cacheManager, this.cryptoObj, this.logger);
const responseHandler = new ResponseHandler(this.clientConfig.auth.clientId, this.cacheStorage, this.cacheManager, this.cryptoUtils, this.logger);
// Deserialize hash fragment response parameters.
const hashUrlString = new UrlString(hashFragment);
const serverParams = hashUrlString.getDeserializedHash<ServerAuthorizationCodeResponse>();
Expand All @@ -352,7 +352,7 @@ export class PublicClientSPA extends BaseClient {
try {
// Get token request from cache and parse as TokenExchangeParameters.
const encodedTokenRequest = this.cacheStorage.getItem(TemporaryCacheKeys.REQUEST_PARAMS);
const parsedRequest = JSON.parse(this.cryptoObj.base64Decode(encodedTokenRequest)) as TokenExchangeParameters;
const parsedRequest = JSON.parse(this.cryptoUtils.base64Decode(encodedTokenRequest)) as TokenExchangeParameters;
this.cacheStorage.removeItem(TemporaryCacheKeys.REQUEST_PARAMS);
// Get cached authority and use if no authority is cached with request.
if (StringUtils.isEmpty(parsedRequest.authority)) {
Expand Down Expand Up @@ -415,7 +415,7 @@ export class PublicClientSPA extends BaseClient {
);

// Create response handler
const responseHandler = new ResponseHandler(this.clientConfig.auth.clientId, this.cacheStorage, this.cacheManager, this.cryptoObj, this.logger);
const responseHandler = new ResponseHandler(this.clientConfig.auth.clientId, this.cacheStorage, this.cacheManager, this.cryptoUtils, this.logger);
// Validate response. This function throws a server error if an error is returned by the server.
responseHandler.validateServerAuthorizationTokenResponse(acquiredTokenResponse.body);
// Return token response with given parameters
Expand Down Expand Up @@ -482,10 +482,10 @@ export class PublicClientSPA extends BaseClient {
const rawClientInfo = this.cacheStorage.getItem(PersistentCacheKeys.CLIENT_INFO);

if(!StringUtils.isEmpty(rawIdToken) && !StringUtils.isEmpty(rawClientInfo)) {
const idToken = new IdToken(rawIdToken, this.cryptoObj);
const clientInfo = buildClientInfo(rawClientInfo, this.cryptoObj);
const idToken = new IdToken(rawIdToken, this.cryptoUtils);
const clientInfo = buildClientInfo(rawClientInfo, this.cryptoUtils);

this.account = Account.createAccount(idToken, clientInfo, this.cryptoObj);
this.account = Account.createAccount(idToken, clientInfo, this.cryptoUtils);
return this.account;
}

Expand Down
2 changes: 1 addition & 1 deletion lib/msal-common/src/error/ClientConfigurationError.ts
Expand Up @@ -183,7 +183,7 @@ export class ClientConfigurationError extends ClientAuthError {
/**
* Throws error when both params: code_challenge and code_challenge_method are not passed together
*/
static createInvalidCodeChallengeParams(): ClientConfigurationError {
static createInvalidCodeChallengeParamsError(): ClientConfigurationError {
return new ClientConfigurationError(
ClientConfigurationErrorMessage.invalidCodeChallengeParams.code,
ClientConfigurationErrorMessage.invalidCodeChallengeParams.desc
Expand Down
2 changes: 1 addition & 1 deletion lib/msal-common/src/request/RequestValidator.ts
Expand Up @@ -58,7 +58,7 @@ export class RequestValidator {
*/
static validateCodeChallengeParams(codeChallenge: string, codeChallengeMethod: string) : void {
if (!(codeChallenge && codeChallengeMethod)) {
throw ClientConfigurationError.createInvalidCodeChallengeParams();
throw ClientConfigurationError.createInvalidCodeChallengeParamsError();
} else {
this.validateCodeChallengeMethod(codeChallengeMethod);
}
Expand Down
8 changes: 6 additions & 2 deletions lib/msal-common/src/server/RequestParameterBuilder.ts
Expand Up @@ -153,7 +153,7 @@ export class RequestParameterBuilder {
encodeURIComponent(codeChallengeMethod)
);
} else {
throw ClientConfigurationError.createInvalidCodeChallengeParams();
throw ClientConfigurationError.createInvalidCodeChallengeParamsError();
}
}

Expand Down Expand Up @@ -190,7 +190,11 @@ export class RequestParameterBuilder {
// params.set(`${AADServerParamKeys.CLIENT_SECRET}`, clientSecret);
// }

addGrantType( grantType: string): void {
/**
* add grant type
* @param grantType
*/
addGrantType(grantType: string): void {
this.parameters.set(`${AADServerParamKeys.GRANT_TYPE}`, encodeURIComponent(grantType));
}

Expand Down
157 changes: 157 additions & 0 deletions lib/msal-common/test/client/AuthorizationCodeClient.spec.ts
@@ -0,0 +1,157 @@
import chai from "chai";
import chaiAsPromised from "chai-as-promised";
import sinon from "sinon";
import {
Authority,
AuthorizationCodeClient,
AuthorizationCodeRequest,
AuthorizationCodeUrlRequest,
Configuration,
Constants
} from "../../src";
import {
ALTERNATE_OPENID_CONFIG_RESPONSE,
AUTHENTICATION_RESULT,
DEFAULT_OPENID_CONFIG_RESPONSE,
TEST_CONFIG,
TEST_TOKENS,
TEST_URIS
} from "../utils/StringConstants";
import {BaseClient} from "../../src/client/BaseClient";
import {AADServerParamKeys, PromptValue, ResponseMode, SSOTypes} from "../../src/utils/Constants";
import {ClientTestUtils} from "./ClientTestUtils";

const expect = chai.expect;
chai.use(chaiAsPromised);

describe("AuthorizationCodeClient unit tests", () => {

let config: Configuration;

beforeEach(() => {
config = ClientTestUtils.createTestClientConfiguration();
});

describe("Constructor", () => {

it("creates a AuthorizationCodeClient", () => {
const client = new AuthorizationCodeClient(config);
expect(client).to.be.not.null;
expect(client instanceof AuthorizationCodeClient).to.be.true;
expect(client instanceof BaseClient).to.be.true;
});
});

describe("Authorization url creation", () => {

afterEach(() => {
sinon.restore();
});


it("Creates an authorization url with default parameters", async () => {

sinon.stub(Authority.prototype, <any>"discoverEndpoints").resolves(DEFAULT_OPENID_CONFIG_RESPONSE);
let client = new AuthorizationCodeClient(config);

const authCodeUrlRequest: AuthorizationCodeUrlRequest = {
redirectUri: TEST_URIS.TEST_REDIRECT_URI_LOCALHOST,
scopes: TEST_CONFIG.DEFAULT_GRAPH_SCOPE
};
const loginUrl = await client.getAuthCodeUrl(authCodeUrlRequest);
expect(loginUrl).to.contain(Constants.DEFAULT_AUTHORITY);
expect(loginUrl).to.contain(DEFAULT_OPENID_CONFIG_RESPONSE.body.authorization_endpoint.replace("{tenant}", "common"));
expect(loginUrl).to.contain(`${AADServerParamKeys.SCOPE}=${TEST_CONFIG.DEFAULT_GRAPH_SCOPE}%20${Constants.OPENID_SCOPE}%20${Constants.PROFILE_SCOPE}%20${Constants.OFFLINE_ACCESS_SCOPE}`);
expect(loginUrl).to.contain(`${AADServerParamKeys.RESPONSE_TYPE}=${Constants.CODE_RESPONSE_TYPE}`);
expect(loginUrl).to.contain(`${AADServerParamKeys.CLIENT_ID}=${TEST_CONFIG.MSAL_CLIENT_ID}`);
expect(loginUrl).to.contain(`${AADServerParamKeys.REDIRECT_URI}=${encodeURIComponent(TEST_URIS.TEST_REDIRECT_URI_LOCALHOST)}`);
expect(loginUrl).to.contain(`${AADServerParamKeys.RESPONSE_MODE}=${encodeURIComponent(Constants.QUERY_RESPONSE_MODE)}`)
});

it("Creates an authorization url passing in a default scope", async () => {

sinon.stub(Authority.prototype, <any>"discoverEndpoints").resolves(DEFAULT_OPENID_CONFIG_RESPONSE);
let client = new AuthorizationCodeClient(config);

const authCodeUrlRequest: AuthorizationCodeUrlRequest = {
redirectUri: TEST_URIS.TEST_REDIRECT_URI_LOCALHOST,
scopes: [Constants.OPENID_SCOPE]
};
const loginUrl = await client.getAuthCodeUrl(authCodeUrlRequest);
expect(loginUrl).to.contain(Constants.DEFAULT_AUTHORITY);
expect(loginUrl).to.contain(DEFAULT_OPENID_CONFIG_RESPONSE.body.authorization_endpoint.replace("{tenant}", "common"));
expect(loginUrl).to.contain(`${AADServerParamKeys.SCOPE}=${Constants.OPENID_SCOPE}%20${Constants.PROFILE_SCOPE}%20${Constants.OFFLINE_ACCESS_SCOPE}`);
expect(loginUrl).to.contain(`${AADServerParamKeys.RESPONSE_TYPE}=${Constants.CODE_RESPONSE_TYPE}`);
expect(loginUrl).to.contain(`${AADServerParamKeys.CLIENT_ID}=${TEST_CONFIG.MSAL_CLIENT_ID}`);
expect(loginUrl).to.contain(`${AADServerParamKeys.REDIRECT_URI}=${encodeURIComponent(TEST_URIS.TEST_REDIRECT_URI_LOCALHOST)}`);
expect(loginUrl).to.contain(`${AADServerParamKeys.RESPONSE_MODE}=${encodeURIComponent(Constants.QUERY_RESPONSE_MODE)}`)
});

it("Creates an authorization url passing in optional parameters", async () => {

// Override with alternate authority openid_config
sinon.stub(Authority.prototype, <any>"discoverEndpoints").resolves(ALTERNATE_OPENID_CONFIG_RESPONSE);
let client = new AuthorizationCodeClient(config);

const authCodeUrlRequest: AuthorizationCodeUrlRequest = {
redirectUri: TEST_URIS.TEST_REDIRECT_URI_LOCALHOST,
scopes: TEST_CONFIG.DEFAULT_GRAPH_SCOPE,
authority: TEST_CONFIG.alternateValidAuthority,
responseMode: ResponseMode.FORM_POST,
codeChallenge: TEST_CONFIG.TEST_CHALLENGE,
codeChallengeMethod: TEST_CONFIG.CODE_CHALLENGE_METHOD,
state: TEST_CONFIG.STATE,
prompt: PromptValue.SELECT_ACCOUNT,
loginHint: TEST_CONFIG.LOGIN_HINT,
domainHint: TEST_CONFIG.DOMAIN_HINT,
claims: TEST_CONFIG.CLAIMS,
nonce: TEST_CONFIG.NONCE,
};
const loginUrl = await client.getAuthCodeUrl(authCodeUrlRequest);
expect(loginUrl).to.contain(TEST_CONFIG.alternateValidAuthority);
expect(loginUrl).to.contain(ALTERNATE_OPENID_CONFIG_RESPONSE.body.authorization_endpoint.replace("{tenant}", "common"));
expect(loginUrl).to.contain(`${AADServerParamKeys.SCOPE}=${TEST_CONFIG.DEFAULT_GRAPH_SCOPE}%20${Constants.OPENID_SCOPE}%20${Constants.PROFILE_SCOPE}%20${Constants.OFFLINE_ACCESS_SCOPE}`);
expect(loginUrl).to.contain(`${AADServerParamKeys.RESPONSE_TYPE}=${Constants.CODE_RESPONSE_TYPE}`);
expect(loginUrl).to.contain(`${AADServerParamKeys.CLIENT_ID}=${TEST_CONFIG.MSAL_CLIENT_ID}`);
expect(loginUrl).to.contain(`${AADServerParamKeys.REDIRECT_URI}=${encodeURIComponent(TEST_URIS.TEST_REDIRECT_URI_LOCALHOST)}`);
expect(loginUrl).to.contain(`${AADServerParamKeys.RESPONSE_MODE}=${encodeURIComponent(ResponseMode.FORM_POST)}`)
expect(loginUrl).to.contain(`${AADServerParamKeys.STATE}=${encodeURIComponent(TEST_CONFIG.STATE)}`);
expect(loginUrl).to.contain(`${AADServerParamKeys.NONCE}=${encodeURIComponent(TEST_CONFIG.NONCE)}`);
expect(loginUrl).to.contain(`${AADServerParamKeys.CODE_CHALLENGE}=${encodeURIComponent(TEST_CONFIG.TEST_CHALLENGE)}`);
expect(loginUrl).to.contain(`${AADServerParamKeys.CODE_CHALLENGE_METHOD}=${encodeURIComponent(TEST_CONFIG.CODE_CHALLENGE_METHOD)}`);
expect(loginUrl).to.contain(`${SSOTypes.LOGIN_HINT}=${encodeURIComponent(TEST_CONFIG.LOGIN_HINT)}`);
expect(loginUrl).to.contain(`${SSOTypes.DOMAIN_HINT}=${encodeURIComponent(TEST_CONFIG.DOMAIN_HINT)}`);
expect(loginUrl).to.contain(`${AADServerParamKeys.CLAIMS}=${encodeURIComponent(TEST_CONFIG.CLAIMS)}`);
});
});

describe("Acquire a token", () => {

it("Acquires a token successfully", async () => {

sinon.stub(Authority.prototype, <any>"discoverEndpoints").resolves(DEFAULT_OPENID_CONFIG_RESPONSE);
sinon.stub(AuthorizationCodeClient.prototype, "executePostToTokenEndpoint").resolves(AUTHENTICATION_RESULT);
const createTokenRequestBodySpy = sinon.spy(AuthorizationCodeClient.prototype, "createTokenRequestBody");

const client = new AuthorizationCodeClient(config);
const authCodeRequest: AuthorizationCodeRequest = {
scopes: TEST_CONFIG.DEFAULT_GRAPH_SCOPE,
redirectUri: TEST_URIS.TEST_REDIRECT_URI_LOCALHOST,
code: TEST_TOKENS.AUTHORIZATION_CODE
};

const authenticationResult = await client.acquireToken(authCodeRequest);

expect(JSON.parse(authenticationResult)).to.deep.eq(AUTHENTICATION_RESULT.body);
expect(createTokenRequestBodySpy.calledWith(authCodeRequest)).to.be.ok;

console.log(createTokenRequestBodySpy.returnValues);

expect(createTokenRequestBodySpy.returnValues[0]).to.contain(`${AADServerParamKeys.SCOPE}=${TEST_CONFIG.DEFAULT_GRAPH_SCOPE}%20${Constants.OPENID_SCOPE}%20${Constants.PROFILE_SCOPE}%20${Constants.OFFLINE_ACCESS_SCOPE}`);
expect(createTokenRequestBodySpy.returnValues[0]).to.contain(`${AADServerParamKeys.CLIENT_ID}=${TEST_CONFIG.MSAL_CLIENT_ID}`);
expect(createTokenRequestBodySpy.returnValues[0]).to.contain(`${AADServerParamKeys.REDIRECT_URI}=${encodeURIComponent(TEST_URIS.TEST_REDIRECT_URI_LOCALHOST)}`);
expect(createTokenRequestBodySpy.returnValues[0]).to.contain(`${AADServerParamKeys.CODE}=${TEST_TOKENS.AUTHORIZATION_CODE}`);
expect(createTokenRequestBodySpy.returnValues[0]).to.contain(`${AADServerParamKeys.GRANT_TYPE}=${Constants.CODE_GRANT_TYPE}`);
});
});
});