Skip to content

Commit

Permalink
Merge branch 'dev' into onRedirectNavigate
Browse files Browse the repository at this point in the history
  • Loading branch information
jasonnutter committed Jun 3, 2020
2 parents 69bd30c + c6e60bd commit 7b68ffc
Show file tree
Hide file tree
Showing 28 changed files with 244 additions and 99 deletions.
28 changes: 17 additions & 11 deletions lib/msal-browser/src/app/PublicClientApplication.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,23 @@
*/
import {
Account,
SPAClient,
AuthenticationParameters,
Authority,
AuthorityFactory, ClientAuthError,
INetworkModule,
TokenResponse,
UrlString,
TemporaryCacheKeys,
TokenRenewParameters,
StringUtils,
PromptValue,
ServerError,
Authority,
AuthorityFactory,
InteractionRequiredAuthError,
B2cAuthority
SPAClient,
StringUtils,
TemporaryCacheKeys,
TokenRenewParameters,
TokenResponse,
UrlString,
AuthorityType,
B2cAuthority,
InteractionRequiredAuthError
} from "@azure/msal-common";
import { Configuration, buildConfiguration } from "../config/Configuration";
import { buildConfiguration, Configuration } from "../config/Configuration";
import { BrowserStorage } from "../cache/BrowserStorage";
import { CryptoOps } from "../crypto/CryptoOps";
import { RedirectHandler } from "../interaction_handler/RedirectHandler";
Expand Down Expand Up @@ -100,6 +101,11 @@ export class PublicClientApplication {
this.config.system.networkClient
);

// This is temporary. Remove when ADFS is supported for browser
if(this.defaultAuthorityInstance.authorityType === AuthorityType.Adfs){
throw ClientAuthError.createInvalidAuthorityTypeError(this.defaultAuthorityInstance.canonicalAuthority);
}

// Create auth module.
this.authModule = new SPAClient({
authOptions: {
Expand Down
57 changes: 50 additions & 7 deletions lib/msal-browser/test/client/PublicClientApplication.spec.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,42 @@
import * as Mocha from "mocha";
import chai from "chai";
import chaiAsPromised from "chai-as-promised";

chai.use(chaiAsPromised);
const expect = chai.expect;
import { PublicClientApplication } from "../../src/app/PublicClientApplication";
import { TEST_CONFIG, TEST_URIS, TEST_HASHES, TEST_TOKENS, TEST_DATA_CLIENT_INFO, TEST_TOKEN_LIFETIMES, RANDOM_TEST_GUID, DEFAULT_OPENID_CONFIG_RESPONSE, testNavUrl, testLogoutUrl } from "../utils/StringConstants";
import { AuthError, ServerError, AuthResponse, LogLevel, Constants, TemporaryCacheKeys, TokenResponse, Account, TokenExchangeParameters, IdTokenClaims, SPAClient, PromptValue, AuthenticationParameters } from "@azure/msal-common";
import {
TEST_CONFIG,
TEST_URIS,
TEST_HASHES,
TEST_TOKENS,
TEST_DATA_CLIENT_INFO,
TEST_TOKEN_LIFETIMES,
RANDOM_TEST_GUID,
DEFAULT_OPENID_CONFIG_RESPONSE,
testNavUrl,
testLogoutUrl
} from "../utils/StringConstants";
import {
AuthError,
ServerError,
AuthResponse,
LogLevel,
Constants,
TemporaryCacheKeys,
TokenResponse,
Account,
TokenExchangeParameters,
IdTokenClaims,
SPAClient,
PromptValue,
AuthenticationParameters, ClientAuthError, ClientAuthErrorMessage
} from "@azure/msal-common";
import { AuthCallback } from "../../src/types/AuthCallback";
import { BrowserConfigurationAuthErrorMessage, BrowserConfigurationAuthError } from "../../src/error/BrowserConfigurationAuthError";
import {
BrowserConfigurationAuthErrorMessage,
BrowserConfigurationAuthError
} from "../../src/error/BrowserConfigurationAuthError";
import sinon from "sinon";
import { BrowserUtils } from "../../src/utils/BrowserUtils";
import { BrowserConstants } from "../../src/utils/BrowserConstants";
Expand Down Expand Up @@ -74,6 +103,20 @@ describe("PublicClientApplication.ts Class Unit Tests", () => {
});
expect(window.sessionStorage.getItem(`${Constants.CACHE_PREFIX}.${TEST_CONFIG.MSAL_CLIENT_ID}.${TemporaryCacheKeys.URL_HASH}`)).to.be.eq(TEST_HASHES.TEST_SUCCESS_CODE_HASH);
});

it("ADFS authority throws error", () => {

expect(() =>{
new PublicClientApplication({
auth: {
clientId: TEST_CONFIG.MSAL_CLIENT_ID,
authority: TEST_CONFIG.ADFS_AUTHORITY
}
});

}).to.throw(ClientAuthErrorMessage.invalidAuthorityType.desc);

});
});

describe("Redirect Flow Unit tests", () => {
Expand Down Expand Up @@ -110,7 +153,7 @@ describe("PublicClientApplication.ts Class Unit Tests", () => {
const testServerTokenResponse = {
headers: null,
status: 200,
body : {
body: {
token_type: TEST_CONFIG.TOKEN_TYPE_BEARER,
scope: TEST_CONFIG.DEFAULT_SCOPES.join(" "),
expires_in: TEST_TOKEN_LIFETIMES.DEFAULT_EXPIRES_IN,
Expand Down Expand Up @@ -213,7 +256,7 @@ describe("PublicClientApplication.ts Class Unit Tests", () => {
const testServerTokenResponse = {
headers: null,
status: 200,
body : {
body: {
token_type: TEST_CONFIG.TOKEN_TYPE_BEARER,
scope: TEST_CONFIG.DEFAULT_SCOPES.join(" "),
expires_in: TEST_TOKEN_LIFETIMES.DEFAULT_EXPIRES_IN,
Expand Down Expand Up @@ -561,7 +604,7 @@ describe("PublicClientApplication.ts Class Unit Tests", () => {
refreshToken: testServerTokenResponse.refresh_token,
expiresOn: new Date(Date.now() + (testServerTokenResponse.expires_in * 1000)),
account: testAccount,
userRequestState: ""
userRequestState: ""
};
sinon.stub(SPAClient.prototype, "createLoginUrl").resolves(testNavUrl);
const loadFrameSyncSpy = sinon.spy(SilentHandler.prototype, <any>"loadFrameSync");
Expand Down Expand Up @@ -666,7 +709,7 @@ describe("PublicClientApplication.ts Class Unit Tests", () => {
refreshToken: testServerTokenResponse.refresh_token,
expiresOn: new Date(Date.now() + (testServerTokenResponse.expires_in * 1000)),
account: testAccount,
userRequestState: ""
userRequestState: ""
};
const createAcqTokenStub = sinon.stub(SPAClient.prototype, "createAcquireTokenUrl").resolves(testNavUrl);
const silentTokenHelperStub = sinon.stub(pca, <any>"silentTokenHelper").resolves(testTokenResponse);
Expand Down
1 change: 1 addition & 0 deletions lib/msal-browser/test/utils/StringConstants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,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_AUTHORITY: "https://authority.com/adfs",
applicationName: "msal.js-tests",
applicationVersion: "msal.js-tests.1.0.fake",
STATE: "1234",
Expand Down
7 changes: 7 additions & 0 deletions lib/msal-common/docs/ADFS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# ADFS Support

MSAL supports connecting to Azure AD, which signs in managed-users (users managed in Azure AD) or federated users (users managed by another identity provider such as ADFS). MSAL does not differentiate between these two types of users. As far as it’s concerned, it talks to Azure AD. The authority that you would pass in this case is the normal Azure AD Authority: `https://login.microsoftonline.com/{Enter_the_Tenant_Info_Here}`

MSAL also supports directly connection to AD FS 2019, which is Open ID Connect compliant and has support scopes and PKCE. This support requires that a service pack [KB 4490481](https://support.microsoft.com/en-us/help/4490481/windows-10-update-kb4490481) is applied to Windows Server. When connecting directly to AD FS, the authority you'll want to use to build your application will be of form `https://mysite.contoso.com/adfs/`

Currently, there are no plans to support a direct connection to ADFS 16 or ADFS v2. ADFS 16 does not support scopes, and ADFS v2 is not OIDC compliant.
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
4 changes: 3 additions & 1 deletion lib/msal-common/src/authority/AuthorityFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ 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 {

Expand Down Expand Up @@ -50,7 +51,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";
import { UnifiedCacheManager } from "../unifiedCache/UnifiedCacheManager";

/**
Expand Down Expand Up @@ -70,7 +71,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
25 changes: 25 additions & 0 deletions lib/msal-common/src/client/SPAClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { StringUtils } from "../utils/StringUtils";
import { UrlString } from "../url/UrlString";
import { Account } from "../account/Account";
import { buildClientInfo } from "../account/ClientInfo";
import { AuthorityType } from "../authority/AuthorityType";

/**
* SPAClient class
Expand Down Expand Up @@ -67,6 +68,12 @@ export class SPAClient extends BaseClient {
private async createUrl(request: AuthenticationParameters, isLoginCall: boolean): Promise<string> {
// Initialize authority or use default, and perform discovery endpoint check.
const acquireTokenAuthority = (request && request.authority) ? AuthorityFactory.createInstance(request.authority, this.networkClient) : this.defaultAuthority;

// This is temporary. Remove when ADFS is supported for browser
if(acquireTokenAuthority.authorityType == AuthorityType.Adfs){
throw ClientAuthError.createInvalidAuthorityTypeError(acquireTokenAuthority.canonicalAuthority);
}

try {
await acquireTokenAuthority.resolveEndpointsAsync();
} catch (e) {
Expand Down Expand Up @@ -143,6 +150,12 @@ export class SPAClient extends BaseClient {

// Initialize authority or use default, and perform discovery endpoint check.
const acquireTokenAuthority = (tokenRequest && tokenRequest.authority) ? AuthorityFactory.createInstance(tokenRequest.authority, this.networkClient) : this.defaultAuthority;

// This is temporary. Remove when ADFS is supported for browser
if(acquireTokenAuthority.authorityType == AuthorityType.Adfs){
throw ClientAuthError.createInvalidAuthorityTypeError(acquireTokenAuthority.canonicalAuthority);
}

if (!acquireTokenAuthority.discoveryComplete()) {
try {
await acquireTokenAuthority.resolveEndpointsAsync();
Expand Down Expand Up @@ -199,6 +212,12 @@ export class SPAClient extends BaseClient {

// Initialize authority or use default, and perform discovery endpoint check.
const acquireTokenAuthority = request.authority ? AuthorityFactory.createInstance(request.authority, this.networkClient) : this.defaultAuthority;

// This is temporary. Remove when ADFS is supported for browser
if(acquireTokenAuthority.authorityType == AuthorityType.Adfs){
throw ClientAuthError.createInvalidAuthorityTypeError(acquireTokenAuthority.canonicalAuthority);
}

if (!acquireTokenAuthority.discoveryComplete()) {
try {
await acquireTokenAuthority.resolveEndpointsAsync();
Expand Down Expand Up @@ -271,6 +290,12 @@ export class SPAClient extends BaseClient {

// Acquire token authorities.
const acquireTokenAuthority = (authorityUri) ? AuthorityFactory.createInstance(authorityUri, this.networkClient) : this.defaultAuthority;

// This is temporary. Remove when ADFS is supported for browser
if(acquireTokenAuthority.authorityType == AuthorityType.Adfs){
throw ClientAuthError.createInvalidAuthorityTypeError(acquireTokenAuthority.canonicalAuthority);
}

if (!acquireTokenAuthority.discoveryComplete()) {
try {
await acquireTokenAuthority.resolveEndpointsAsync();
Expand Down
1 change: 1 addition & 0 deletions lib/msal-common/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export { IdTokenClaims } from "./account/IdTokenClaims";
export { Authority } from "./authority/Authority";
export { B2cAuthority } from "./authority/B2cAuthority";
export { AuthorityFactory } from "./authority/AuthorityFactory";
export { AuthorityType } from "./authority/AuthorityType";
// Cache
export { ICacheStorage } from "./cache/ICacheStorage";
export { UnifiedCacheManager } from "./unifiedCache/UnifiedCacheManager";
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

0 comments on commit 7b68ffc

Please sign in to comment.