Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
32 changes: 32 additions & 0 deletions lib/msal-common/src/authority/AdfsAuthority.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/

import { Authority } from "./Authority";
import { AuthorityType } from "./AuthorityType";
import { INetworkModule } from "../network/INetworkModule";

/**
* The AdfsAuthority class extends the Authority class and adds functionality specific to ADFS 2019
*/
export class AdfsAuthority extends Authority {

/**
* Return authority type
*/
public get authorityType(): AuthorityType {
return AuthorityType.Adfs;
}

public constructor(authority: string, networkInterface: INetworkModule) {
super(authority, networkInterface);
}

/**
* Returns a promise which resolves to the OIDC endpoint
*/
public async getOpenIdConfigurationEndpointAsync(): Promise<string> {
return `${this.canonicalAuthority}.well-known/openid-configuration`;
}
}
8 changes: 8 additions & 0 deletions lib/msal-common/src/authority/Authority.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,14 @@ export abstract class Authority {
}
}

public get deviceCodeEndpoint(): string {
if(this.discoveryComplete()) {
return this.tenantDiscoveryResponse.token_endpoint.replace("/token", "/devicecode");
} else {
throw ClientAuthError.createEndpointDiscoveryIncompleteError("Discovery incomplete.");
}
}

/**
* OAuth logout endpoint for requests
*/
Expand Down
6 changes: 4 additions & 2 deletions lib/msal-common/src/authority/AuthorityFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ import { INetworkModule } from "./../network/INetworkModule";
import { StringUtils } from "./../utils/StringUtils";
import { UrlString } from "./../url/UrlString";
import { Constants } from "../utils/Constants";
import { AdfsAuthority } from "./AdfsAuthority";

export class AuthorityFactory {
export class AuthorityFactory {

/**
* Parse the url and determine the type of authority
Expand Down Expand Up @@ -49,7 +50,8 @@ export class AuthorityFactory {
return new AadAuthority(authorityUrl, networkInterface);
case AuthorityType.B2C:
return new B2cAuthority(authorityUrl, networkInterface);
// TODO: Support ADFS here in a later PR
case AuthorityType.Adfs:
return new AdfsAuthority(authorityUrl, networkInterface);
default:
throw ClientAuthError.createInvalidAuthorityTypeError(`${authorityUrl}`);
}
Expand Down
4 changes: 3 additions & 1 deletion lib/msal-common/src/client/BaseClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { Logger } from "../logger/Logger";
import { AADServerParamKeys, Constants, HeaderNames } from "../utils/Constants";
import { NetworkResponse } from "../network/NetworkManager";
import { ServerAuthorizationTokenResponse } from "../server/ServerAuthorizationTokenResponse";
import { B2cAuthority } from "../authority/B2cAuthority";

/**
* 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 @@ -63,7 +64,8 @@ export abstract class BaseClient {
// Set the network interface
this.networkClient = this.config.networkInterface;

// Default authority instance.
B2cAuthority.setKnownAuthorities(this.config.authOptions.knownAuthorities);

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

Expand Down
32 changes: 15 additions & 17 deletions lib/msal-common/src/client/DeviceCodeClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,18 +46,22 @@ export class DeviceCodeClient extends BaseClient {
*/
private async getDeviceCode(request: DeviceCodeRequest): Promise<DeviceCodeResponse> {

const deviceCodeUrl = this.createDeviceCodeUrl(request);
const queryString = this.createQueryString(request);
const headers = this.createDefaultLibraryHeaders();

return this.executeGetRequestToDeviceCodeEndpoint(deviceCodeUrl, headers);
return this.executePostRequestToDeviceCodeEndpoint(this.defaultAuthority.deviceCodeEndpoint, queryString, headers);
}

/**
* Executes GET request to device code endpoint
* @param deviceCodeUrl
* Executes POST request to device code endpoint
* @param deviceCodeEndpoint
* @param queryString
* @param headers
*/
private async executeGetRequestToDeviceCodeEndpoint(deviceCodeUrl: string, headers: Map<string, string>): Promise<DeviceCodeResponse> {
private async executePostRequestToDeviceCodeEndpoint(
deviceCodeEndpoint: string,
queryString: string,
headers: Map<string, string>): Promise<DeviceCodeResponse> {

const {
body: {
Expand All @@ -68,7 +72,12 @@ export class DeviceCodeClient extends BaseClient {
interval,
message
}
} = await this.networkClient.sendGetRequestAsync<ServerDeviceCodeResponse>(deviceCodeUrl, {headers});
} = await this.networkClient.sendPostRequestAsync<ServerDeviceCodeResponse>(
deviceCodeEndpoint,
{
body: queryString,
headers: headers
});

return {
userCode,
Expand All @@ -80,17 +89,6 @@ export class DeviceCodeClient extends BaseClient {
};
}

/**
* Create device code endpoint url
* @param request
*/
private createDeviceCodeUrl(request: DeviceCodeRequest): string {
const queryString: string = this.createQueryString(request);

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

/**
* Create device code endpoint query parameters and returns string
*/
Expand Down
2 changes: 1 addition & 1 deletion lib/msal-common/src/client/RefreshTokenClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export class RefreshTokenClient extends BaseClient {

const scopeSet = new ScopeSet(request.scopes || [],
this.config.authOptions.clientId,
true);
false);
parameterBuilder.addScopes(scopeSet);
parameterBuilder.addClientId(this.config.authOptions.clientId);
parameterBuilder.addGrantType(GrantType.REFRESH_TOKEN_GRANT);
Expand Down
4 changes: 0 additions & 4 deletions lib/msal-common/src/client/SPAClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import { StringUtils } from "../utils/StringUtils";
import { UrlString } from "../url/UrlString";
import { Account } from "../account/Account";
import { buildClientInfo } from "../account/ClientInfo";
import { B2cAuthority } from "../authority/B2cAuthority";

/**
* SPAClient class
Expand All @@ -39,9 +38,6 @@ export class SPAClient extends BaseClient {
constructor(configuration: ClientConfiguration) {
// Implement base module
super(configuration);

// Initialize default authority instance
B2cAuthority.setKnownAuthorities(this.config.authOptions.knownAuthorities);
}

/**
Expand Down
3 changes: 2 additions & 1 deletion lib/msal-common/src/request/ScopeSet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ export class ScopeSet {
clientId: string,
scopesRequired: boolean,
) {
this.clientId = clientId;
// lower case need for replaceDefaultScopes() because ADFS clientids don't have to be GUIDS.
this.clientId = clientId.toLowerCase();
this.scopesRequired = scopesRequired;

// Filter empty string and null/undefined array items
Expand Down
5 changes: 1 addition & 4 deletions lib/msal-common/src/utils/Constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,6 @@ export const Constants = {
ADFS: "adfs",
// Default AAD Instance Discovery Endpoint
AAD_INSTANCE_DISCOVERY_ENDPT: "https://login.microsoftonline.com/common/discovery/instance",

// Device code endpoint path
DEVICE_CODE_ENDPOINT_PATH: "oauth2/v2.0/devicecode",
// Resource delimiter - used for certain cache entries
RESOURCE_DELIM: "|",
// Placeholder for non-existent account ids/objects
Expand Down Expand Up @@ -168,7 +165,7 @@ export enum SSOTypes {
ACCOUNT = "account",
SID = "sid",
LOGIN_HINT = "login_hint",
ID_TOKEN ="id_token",
ID_TOKEN = "id_token",
DOMAIN_HINT = "domain_hint",
ORGANIZATIONS = "organizations",
CONSUMERS = "consumers",
Expand Down
19 changes: 13 additions & 6 deletions lib/msal-common/test/authority/Authority.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,13 @@ import { Authority } from "../../src/authority/Authority";
import { INetworkModule, NetworkRequestOptions } from "../../src/network/INetworkModule";
import { Constants } from "../../src/utils/Constants";
import { AuthorityType } from "../../src/authority/AuthorityType";
import { DEFAULT_TENANT_DISCOVERY_RESPONSE, TEST_URIS, RANDOM_TEST_GUID, TEST_TENANT_DISCOVERY_RESPONSE, DEFAULT_OPENID_CONFIG_RESPONSE } from "../utils/StringConstants";
import {
DEFAULT_TENANT_DISCOVERY_RESPONSE,
TEST_URIS,
RANDOM_TEST_GUID,
TEST_TENANT_DISCOVERY_RESPONSE,
DEFAULT_OPENID_CONFIG_RESPONSE
} from "../utils/StringConstants";
import { ClientConfigurationErrorMessage } from "../../src/error/ClientConfigurationError";
import { ClientAuthErrorMessage } from "../../src";

Expand Down Expand Up @@ -106,19 +112,19 @@ describe("Authority.ts Class Unit Tests", () => {
sinon.restore();
});

it ("Returns authorization_endpoint of tenantDiscoveryResponse", () => {
it("Returns authorization_endpoint of tenantDiscoveryResponse", () => {
expect(authority.authorizationEndpoint).to.be.eq(DEFAULT_OPENID_CONFIG_RESPONSE.body.authorization_endpoint.replace("{tenant}", "common"));
});

it ("Returns token_endpoint of tenantDiscoveryResponse", () => {
it("Returns token_endpoint of tenantDiscoveryResponse", () => {
expect(authority.tokenEndpoint).to.be.eq(DEFAULT_OPENID_CONFIG_RESPONSE.body.token_endpoint.replace("{tenant}", "common"));
});

it ("Returns end_session_endpoint of tenantDiscoveryResponse", () => {
it("Returns end_session_endpoint of tenantDiscoveryResponse", () => {
expect(authority.endSessionEndpoint).to.be.eq(DEFAULT_OPENID_CONFIG_RESPONSE.body.end_session_endpoint.replace("{tenant}", "common"));
});

it ("Returns issuer of tenantDiscoveryResponse for selfSignedJwtAudience", () => {
it("Returns issuer of tenantDiscoveryResponse for selfSignedJwtAudience", () => {
expect(authority.selfSignedJwtAudience).to.be.eq(DEFAULT_OPENID_CONFIG_RESPONSE.body.issuer.replace("{tenant}", "common"));
});

Expand Down Expand Up @@ -168,8 +174,9 @@ describe("Authority.ts Class Unit Tests", () => {
expect(authority.discoveryComplete()).to.be.true;
expect(authority.authorizationEndpoint).to.be.eq(DEFAULT_OPENID_CONFIG_RESPONSE.body.authorization_endpoint.replace("{tenant}", "common"));
expect(authority.tokenEndpoint).to.be.eq(DEFAULT_OPENID_CONFIG_RESPONSE.body.token_endpoint.replace("{tenant}", "common"));
expect(authority.deviceCodeEndpoint).to.be.eq(DEFAULT_OPENID_CONFIG_RESPONSE.body.token_endpoint.replace("/token", "/devicecode"));
expect(authority.endSessionEndpoint).to.be.eq(DEFAULT_OPENID_CONFIG_RESPONSE.body.end_session_endpoint.replace("{tenant}", "common"));
expect(authority.selfSignedJwtAudience).to.be.eq(DEFAULT_OPENID_CONFIG_RESPONSE.body.issuer.replace("{tenant}", "common"));
});
});
});
});
26 changes: 9 additions & 17 deletions lib/msal-common/test/authority/AuthorityFactory.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ import { INetworkModule, NetworkRequestOptions } from "../../src/network/INetwor
import { AadAuthority } from "../../src/authority/AadAuthority";
import { B2cAuthority } from "../../src/authority/B2cAuthority";
import { TEST_CONFIG } from "../utils/StringConstants";
import { ClientAuthErrorMessage, ClientAuthError } from "../../src/error/ClientAuthError";
import { Constants } from "../../src/utils/Constants";
import { ClientConfigurationErrorMessage } from "../../src/error/ClientConfigurationError";
import { Authority } from "../../src/authority/Authority";
import { AdfsAuthority } from "../../src/authority/AdfsAuthority";

describe("AuthorityFactory.ts Class Unit Tests", () => {

Expand All @@ -22,11 +22,11 @@ describe("AuthorityFactory.ts Class Unit Tests", () => {

beforeEach(() => {
// Reinitializes the B2C Trusted Host List between tests
while(B2cAuthority.B2CTrustedHostList.length) {
while (B2cAuthority.B2CTrustedHostList.length) {
B2cAuthority.B2CTrustedHostList.pop();
}
});

it("AuthorityFactory returns null if given url is null or empty", () => {
expect(() => AuthorityFactory.createInstance("", networkInterface)).to.throw(ClientConfigurationErrorMessage.urlEmptyError.desc);
expect(() => AuthorityFactory.createInstance(null, networkInterface)).to.throw(ClientConfigurationErrorMessage.urlEmptyError.desc);
Expand All @@ -52,6 +52,12 @@ describe("AuthorityFactory.ts Class Unit Tests", () => {
expect(authorityInstance instanceof Authority);
});

it("createInstance return ADFS instance if /adfs in path", () => {
const authorityInstance = AuthorityFactory.createInstance(TEST_CONFIG.ADFS_VALID_AUTHORITY, networkInterface);
expect(authorityInstance instanceof AdfsAuthority);
expect(authorityInstance instanceof Authority);
});

it("Do not add additional authorities to trusted host list if it has already been populated", () => {
B2cAuthority.setKnownAuthorities(["fabrikamb2c.b2clogin.com"]);
B2cAuthority.setKnownAuthorities(["fake.b2clogin.com"]);
Expand All @@ -60,18 +66,4 @@ describe("AuthorityFactory.ts Class Unit Tests", () => {
expect(B2cAuthority.B2CTrustedHostList).not.to.include("fake.b2clogin.com");
expect(B2cAuthority.B2CTrustedHostList.length).to.equal(1);
});

it("Throws error if AuthorityType is not AAD or B2C", (done) => {
//Right now only way to throw this is to send adfs authority. This will need to change when we implement ADFS
const errorAuthority = "https://login.microsoftonline.com/adfs"
try{
const authorityInstance = AuthorityFactory.createInstance(errorAuthority, networkInterface);
}
catch(e) {
expect(e).to.be.instanceOf(ClientAuthError)
expect(e.errorCode).to.be.equal(ClientAuthErrorMessage.invalidAuthorityType.code)
expect(e.errorMessage).to.be.equal(`${ClientAuthErrorMessage.invalidAuthorityType.desc} Given Url: ${errorAuthority}`)
done();
}
})
});
16 changes: 7 additions & 9 deletions lib/msal-common/test/client/DeviceCodeClient.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,11 @@ describe("DeviceCodeClient unit tests", async () => {

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

sinon.stub(DeviceCodeClient.prototype, <any>"executeGetRequestToDeviceCodeEndpoint").resolves(DEVICE_CODE_RESPONSE);
sinon.stub(DeviceCodeClient.prototype, <any>"executePostRequestToDeviceCodeEndpoint").resolves(DEVICE_CODE_RESPONSE);
sinon.stub(Authority.prototype, <any>"discoverEndpoints").resolves(DEFAULT_OPENID_CONFIG_RESPONSE);
sinon.stub(BaseClient.prototype, "executePostToTokenEndpoint").resolves(AUTHENTICATION_RESULT);

const createDeviceCodeUrlSpy = sinon.spy(DeviceCodeClient.prototype, <any>"createDeviceCodeUrl");
const queryStringSpy = sinon.spy(DeviceCodeClient.prototype, <any>"createQueryString");
const createTokenRequestBodySpy = sinon.spy(DeviceCodeClient.prototype, <any>"createTokenRequestBody");

let deviceCodeResponse = null;
Expand All @@ -62,10 +62,8 @@ describe("DeviceCodeClient unit tests", async () => {
const authenticationResult = await client.acquireToken(request);

// Check that device code url is correct
expect(createDeviceCodeUrlSpy.returnValues[0]).to.contain(Constants.DEFAULT_AUTHORITY);
expect(createDeviceCodeUrlSpy.returnValues[0]).to.contain("oauth2/v2.0/devicecode");
expect(createDeviceCodeUrlSpy.returnValues[0]).to.contain(`${TEST_CONFIG.DEFAULT_GRAPH_SCOPE}%20${Constants.OPENID_SCOPE}%20${Constants.PROFILE_SCOPE}%20${Constants.OFFLINE_ACCESS_SCOPE}`);
expect(createDeviceCodeUrlSpy.returnValues[0]).to.contain(`${AADServerParamKeys.CLIENT_ID}=${TEST_CONFIG.MSAL_CLIENT_ID}`);
expect(queryStringSpy.returnValues[0]).to.contain(`${TEST_CONFIG.DEFAULT_GRAPH_SCOPE}%20${Constants.OPENID_SCOPE}%20${Constants.PROFILE_SCOPE}%20${Constants.OFFLINE_ACCESS_SCOPE}`);
expect(queryStringSpy.returnValues[0]).to.contain(`${AADServerParamKeys.CLIENT_ID}=${TEST_CONFIG.MSAL_CLIENT_ID}`);

// Check that deviceCodeCallback was called with the right arguments
expect(deviceCodeResponse).to.deep.eq(DEVICE_CODE_RESPONSE);
Expand All @@ -80,7 +78,7 @@ describe("DeviceCodeClient unit tests", async () => {

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

sinon.stub(DeviceCodeClient.prototype, <any>"executeGetRequestToDeviceCodeEndpoint").resolves(DEVICE_CODE_RESPONSE);
sinon.stub(DeviceCodeClient.prototype, <any>"executePostRequestToDeviceCodeEndpoint").resolves(DEVICE_CODE_RESPONSE);
sinon.stub(Authority.prototype, <any>"discoverEndpoints").resolves(DEFAULT_OPENID_CONFIG_RESPONSE);
const tokenRequestStub = sinon.stub(BaseClient.prototype, <any>"executePostToTokenEndpoint");

Expand All @@ -107,7 +105,7 @@ describe("DeviceCodeClient unit tests", async () => {
it("Throw device code flow cancelled exception if DeviceCodeRequest.cancel=true", async () => {

sinon.stub(Authority.prototype, <any>"discoverEndpoints").resolves(DEFAULT_OPENID_CONFIG_RESPONSE);
sinon.stub(DeviceCodeClient.prototype, <any>"executeGetRequestToDeviceCodeEndpoint").resolves(DEVICE_CODE_RESPONSE);
sinon.stub(DeviceCodeClient.prototype, <any>"executePostRequestToDeviceCodeEndpoint").resolves(DEVICE_CODE_RESPONSE);
sinon.stub(BaseClient.prototype, <any>"executePostToTokenEndpoint").resolves(AUTHENTICATION_RESULT);

let deviceCodeResponse = null;
Expand All @@ -124,7 +122,7 @@ describe("DeviceCodeClient unit tests", async () => {

it("Throw device code expired exception if device code is expired", async () => {
sinon.stub(Authority.prototype, <any>"discoverEndpoints").resolves(DEFAULT_OPENID_CONFIG_RESPONSE);
sinon.stub(DeviceCodeClient.prototype, <any>"executeGetRequestToDeviceCodeEndpoint").resolves(DEVICE_CODE_EXPIRED_RESPONSE);
sinon.stub(DeviceCodeClient.prototype, <any>"executePostRequestToDeviceCodeEndpoint").resolves(DEVICE_CODE_EXPIRED_RESPONSE);

let deviceCodeResponse = null;
const request: DeviceCodeRequest = {
Expand Down
2 changes: 1 addition & 1 deletion lib/msal-common/test/client/RefreshTokenClient.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ describe("RefreshTokenClient unit tests", () => {
expect(JSON.parse(authResult)).to.deep.eq(AUTHENTICATION_RESULT.body);
expect(createTokenRequestBodySpy.calledWith(refreshTokenRequest)).to.be.true;

expect(createTokenRequestBodySpy.returnValues[0]).to.contain(`${AADServerParamKeys.SCOPE}=${TEST_CONFIG.DEFAULT_GRAPH_SCOPE}%20${Constants.OFFLINE_ACCESS_SCOPE}`);
expect(createTokenRequestBodySpy.returnValues[0]).to.contain(`${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.REFRESH_TOKEN}=${TEST_TOKENS.REFRESH_TOKEN}`);
expect(createTokenRequestBodySpy.returnValues[0]).to.contain(`${AADServerParamKeys.GRANT_TYPE}=${GrantType.REFRESH_TOKEN_GRANT}`);
Expand Down
1 change: 1 addition & 0 deletions lib/msal-common/test/utils/StringConstants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ export const TEST_CONFIG = {
MSAL_TENANT_ID: "3338040d-6c67-4c5b-b112-36a304b66dad",
validAuthority: TEST_URIS.DEFAULT_INSTANCE + "common",
alternateValidAuthority: TEST_URIS.ALTERNATE_INSTANCE + "common",
ADFS_VALID_AUTHORITY: "https://on.prem/adfs",
b2cValidAuthority: "https://fabrikamb2c.b2clogin.com/fabrikamb2c.onmicrosoft.com/b2c_1_susi",
applicationName: "msal.js-tests",
applicationVersion: "msal.js-tests.1.0.fake",
Expand Down