From f9e099fc47127f58447ea9e42efa303a1875a563 Mon Sep 17 00:00:00 2001 From: Rohit Narula Date: Tue, 6 Sep 2016 12:54:47 -0700 Subject: [PATCH 01/14] removed nested function from guid method --- lib/adal.js | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/lib/adal.js b/lib/adal.js index 083390d6..7cd4c308 100644 --- a/lib/adal.js +++ b/lib/adal.js @@ -1060,6 +1060,14 @@ var AuthenticationContext = (function () { return obj; }; + AuthenticationContext.prototype._decimalToHex = function (number) { + var hex = number.toString(16); + while (hex.length < 2) { + hex = '0' + hex; + } + return hex; + } + /* jshint ignore:start */ AuthenticationContext.prototype._guid = function () { // RFC4122: The version 4 UUID is meant for generating UUIDs from truly-random or @@ -1092,15 +1100,8 @@ var AuthenticationContext = (function () { //buffer[8] represents the clock_seq_hi_and_reserved field. We will set the two most significant bits (6 and 7) of the clock_seq_hi_and_reserved to zero and one, respectively. buffer[8] |= 0x80; //buffer[8] | 10000000 will set the 7 bit to 1. buffer[8] &= 0xbf; //buffer[8] & 10111111 will set the 6 bit to 0. - function decimalToHex(num) { - var hex = num.toString(16); - while (hex.length < 2) { - hex = '0' + hex; - } - return hex; - } - return decimalToHex(buffer[0]) + decimalToHex(buffer[1]) + decimalToHex(buffer[2]) + decimalToHex(buffer[3]) + '-' + decimalToHex(buffer[4]) + decimalToHex(buffer[5]) + '-' + decimalToHex(buffer[6]) + decimalToHex(buffer[7]) + '-' + - decimalToHex(buffer[8]) + decimalToHex(buffer[9]) + '-' + decimalToHex(buffer[10]) + decimalToHex(buffer[11]) + decimalToHex(buffer[12]) + decimalToHex(buffer[13]) + decimalToHex(buffer[14]) + decimalToHex(buffer[15]); + return this._decimalToHex(buffer[0]) + this._decimalToHex(buffer[1]) + this._decimalToHex(buffer[2]) + this._decimalToHex(buffer[3]) + '-' + this._decimalToHex(buffer[4]) + this._decimalToHex(buffer[5]) + '-' + this._decimalToHex(buffer[6]) + this._decimalToHex(buffer[7]) + '-' + + this._decimalToHex(buffer[8]) + this._decimalToHex(buffer[9]) + '-' + this._decimalToHex(buffer[10]) + this._decimalToHex(buffer[11]) + this._decimalToHex(buffer[12]) + this._decimalToHex(buffer[13]) + this._decimalToHex(buffer[14]) + this._decimalToHex(buffer[15]); } else { var guidHolder = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'; From 7669af4d0e66246f5b29517cf9e96ecdad45991a Mon Sep 17 00:00:00 2001 From: Rohit Pagariya Date: Tue, 27 Sep 2016 10:07:25 -0700 Subject: [PATCH 02/14] fix: inreachable return statement The return statement at the end of the function is unreachable due to the if-else control flow --- lib/adal-angular.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/adal-angular.js b/lib/adal-angular.js index a23f7f62..64e1f009 100644 --- a/lib/adal-angular.js +++ b/lib/adal-angular.js @@ -419,8 +419,6 @@ return delayedRequest.promise; } } - - return config; } }, responseError: function (rejection) { From a6b8fa2eca565fccc7a432dfaf83653cf76fed5d Mon Sep 17 00:00:00 2001 From: Rich McNeary Date: Thu, 29 Sep 2016 16:19:55 -0400 Subject: [PATCH 03/14] Check for the existence of AuthenticationContext, don't invoke it. --- lib/adal.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/adal.js b/lib/adal.js index 7cd4c308..50032136 100644 --- a/lib/adal.js +++ b/lib/adal.js @@ -708,7 +708,7 @@ var AuthenticationContext = (function () { } // external api requests may have many renewtoken requests for different resource - if (!requestInfo.stateMatch && window.parent && window.parent.AuthenticationContext()) { + if (!requestInfo.stateMatch && window.parent && window.parent.AuthenticationContext) { var statesInParentContext = window.parent.AuthenticationContext()._renewStates; for (var i = 0; i < statesInParentContext.length; i++) { if (statesInParentContext[i] === requestInfo.stateResponse) { From 54df95a4bd892e4cf8c89f95a804637acf610fb5 Mon Sep 17 00:00:00 2001 From: Rohit Narula Date: Mon, 3 Oct 2016 18:25:13 -0700 Subject: [PATCH 04/14] ensure all registered acquireToken callbacks are called in case of error --- lib/adal.js | 7 ++++++- tests/unit/spec/AdalSpec.js | 4 +++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/adal.js b/lib/adal.js index 50032136..4b578f7e 100644 --- a/lib/adal.js +++ b/lib/adal.js @@ -327,7 +327,12 @@ var AuthenticationContext = (function () { if (!window.callBackMappedToRenewStates[expectedState]) { window.callBackMappedToRenewStates[expectedState] = function (message, token) { for (var i = 0; i < window.callBacksMappedToRenewStates[expectedState].length; ++i) { - window.callBacksMappedToRenewStates[expectedState][i](message, token); + try { + window.callBacksMappedToRenewStates[expectedState][i](message, token); + } + catch (error) { + self.warn(error); + } } self._activeRenewals[resource] = null; window.callBacksMappedToRenewStates[expectedState] = null; diff --git a/tests/unit/spec/AdalSpec.js b/tests/unit/spec/AdalSpec.js index bf4e7a99..96b32482 100644 --- a/tests/unit/spec/AdalSpec.js +++ b/tests/unit/spec/AdalSpec.js @@ -255,7 +255,7 @@ describe('Adal', function () { }); //Necessary for integration with Angular when multiple http calls are queued. - it('allows multiple callers to be notified when the token is renewed', function () { + it('allows multiple callers to be notified when the token is renewed. Also checks if all registered acquireToken callbacks are called in the case when one of the callbacks throws an error', function () { adal.config.redirectUri = 'contoso_site'; adal.config.clientId = 'client'; adal.config.expireOffsetSeconds = SECONDS_TO_EXPIRE + 100; @@ -266,6 +266,7 @@ describe('Adal', function () { var callback = function (valErr, valToken) { err = valErr; token = valToken; + throw new Error("Error occurred in callback function"); }; var callback2 = function (valErr, valToken) { err2 = valErr; @@ -956,6 +957,7 @@ describe('Adal', function () { expect(adal._guid()).toBe('00010203-0405-4607-8809-0a0b0c0d0e0f'); window.crypto = null; }); + // TODO angular intercepptor // TODO angular authenticationService }); From 2b318b7df55f1af07d1d9baa6d186b9913b9bec5 Mon Sep 17 00:00:00 2001 From: Rohit Narula Date: Mon, 3 Oct 2016 13:52:40 -0700 Subject: [PATCH 05/14] reload loginStartPage after redirection updated check for loginStartPage --- lib/adal-angular.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/adal-angular.js b/lib/adal-angular.js index 64e1f009..af734433 100644 --- a/lib/adal-angular.js +++ b/lib/adal-angular.js @@ -73,7 +73,7 @@ // special function that exposes methods in Angular controller // $rootScope, $window, $q, $location, $timeout are injected by Angular - this.$get = ['$rootScope', '$window', '$q', '$location', '$timeout','$injector', function ($rootScope, $window, $q, $location, $timeout,$injector) { + this.$get = ['$rootScope', '$window', '$q', '$location', '$timeout', '$injector', function ($rootScope, $window, $q, $location, $timeout, $injector) { var locationChangeHandler = function (event, newUrl, oldUrl) { _adal.verbose('Location change event from ' + oldUrl + ' to ' + newUrl); @@ -124,11 +124,10 @@ if (_adal.callback && typeof _adal.callback === 'function') _adal.callback(_adal._getItem(_adal.CONSTANTS.STORAGE.ERROR_DESCRIPTION), _adal._getItem(_adal.CONSTANTS.STORAGE.IDTOKEN)); - event.preventDefault(); // redirect to login start page if (!_adal.popUp) { var loginStartPage = _adal._getItem(_adal.CONSTANTS.STORAGE.LOGIN_REQUEST); - if (loginStartPage) { + if (typeof loginStartPage !== 'undefined' && loginStartPage && loginStartPage.length !==0) { // prevent the current location change and redirect the user back to the login start page _adal.verbose('Redirecting to start page: ' + loginStartPage); if (!$location.$$html5 && loginStartPage.indexOf('#') > -1) { @@ -137,6 +136,8 @@ $window.location = loginStartPage; } } + else + event.preventDefault(); } } else { @@ -232,7 +233,7 @@ } else { var nextRouteUrl; - if(typeof nextRoute.$$route.templateUrl === "function") { + if (typeof nextRoute.$$route.templateUrl === "function") { nextRouteUrl = nextRoute.$$route.templateUrl(nextRoute.params); } else { nextRouteUrl = nextRoute.$$route.templateUrl; @@ -261,7 +262,7 @@ } else if (state.templateUrl) { var nextStateUrl; - if (typeof state.templateUrl === 'function'){ + if (typeof state.templateUrl === 'function') { nextStateUrl = state.templateUrl(toParams); } else { From 3a4eefcbb41870d7bdd4c039c9763a35aaed0193 Mon Sep 17 00:00:00 2001 From: Rohit Narula Date: Tue, 4 Oct 2016 11:57:41 -0700 Subject: [PATCH 06/14] call adal.login directly when localLoginUrl is specified --- lib/adal-angular.js | 5 +- lib/adal.js | 2 - tests/angularModuleSpec.js | 110 +++++++++++++----------------------- tests/testApp.js | 6 +- tests/unit/spec/AdalSpec.js | 3 - 5 files changed, 45 insertions(+), 81 deletions(-) diff --git a/lib/adal-angular.js b/lib/adal-angular.js index af734433..c9a5813d 100644 --- a/lib/adal-angular.js +++ b/lib/adal-angular.js @@ -154,7 +154,7 @@ _adal.acquireToken(_adal.config.loginResource, function (error, tokenOut) { _adal._renewActive = false; if (error) { - $rootScope.$broadcast('adal:loginFailure', 'auto renew failure'); + $rootScope.$broadcast('adal:loginFailure', error); } else { if (tokenOut) { _oauthData.isAuthenticated = true; @@ -238,7 +238,6 @@ } else { nextRouteUrl = nextRoute.$$route.templateUrl; } - if (nextRouteUrl && !isAnonymousEndpoint(nextRouteUrl)) { _adal.config.anonymousEndpoints.push(nextRouteUrl); } @@ -303,7 +302,7 @@ // public methods will be here that are accessible from Controller config: _adal.config, login: function () { - loginHandler(); + _adal.login(); }, loginInProgress: function () { return _adal.loginInProgress(); diff --git a/lib/adal.js b/lib/adal.js index 4b578f7e..b54fe4e8 100644 --- a/lib/adal.js +++ b/lib/adal.js @@ -366,7 +366,6 @@ var AuthenticationContext = (function () { this.registerCallback(expectedState, resource, callback); this.verbose('Navigate to:' + urlNavigate); - this._saveItem(this.CONSTANTS.STORAGE.LOGIN_REQUEST, ''); frameHandle.src = 'about:blank'; this._loadFrameTimeout(urlNavigate, 'adalRenewFrame' + resource, resource); @@ -391,7 +390,6 @@ var AuthenticationContext = (function () { this.registerCallback(expectedState, this.config.clientId, callback); this.idTokenNonce = null; this.verbose('Navigate to:' + urlNavigate); - this._saveItem(this.CONSTANTS.STORAGE.LOGIN_REQUEST, ''); frameHandle.src = 'about:blank'; this._loadFrameTimeout(urlNavigate, 'adalIdTokenFrame', this.config.clientId); }; diff --git a/tests/angularModuleSpec.js b/tests/angularModuleSpec.js index dd2456fb..45ce1bef 100644 --- a/tests/angularModuleSpec.js +++ b/tests/angularModuleSpec.js @@ -76,6 +76,8 @@ describe('TaskCtl', function () { return q.when(token); }; + window.parent.AuthenticationContext = window.AuthenticationContext; + controller('TaskCtl', { $scope: scope, adalAuthenticationService: adalServiceProvider }); })); @@ -210,12 +212,8 @@ describe('TaskCtl', function () { }); it('tests stateMismatch broadcast when state does not match', function () { - window.parent.AuthenticationContext = function () { - return { - callback: function () { }, - _renewStates: {} - }; - }; + window.parent.AuthenticationContext.callback = function () { }; + window.parent.AuthenticationContext._renewStates = {}; window.location.hash = 'id_token=sample&state=4343'; spyOn(rootScope, '$broadcast').andCallThrough(); scope.$on('adal:stateMismatch', function (event, message) { @@ -227,12 +225,8 @@ describe('TaskCtl', function () { }); it('tests callback is called when response contains error', function () { - window.parent.AuthenticationContext = function () { - return { - callback: function () { }, - _renewStates: ['4343'] - }; - }; + window.parent.AuthenticationContext.callback = function () { }; + window.parent.AuthenticationContext._renewStates = ['4343']; window.parent.callBackMappedToRenewStates = {}; window.parent.callBackMappedToRenewStates['4343'] = function (error, token) { expect(error).toBe('renewfailed'); @@ -242,12 +236,8 @@ describe('TaskCtl', function () { }); it('tests callback is called when response contains access token', function () { - window.parent.AuthenticationContext = function () { - return { - callback: function () { }, - _renewStates: ['4343'] - }; - }; + window.parent.AuthenticationContext.callback = function () { }; + window.parent.AuthenticationContext._renewStates = ['4343']; window.parent.callBackMappedToRenewStates = {}; window.parent.callBackMappedToRenewStates['4343'] = function (error, token) { expect(error).toBe(''); @@ -259,12 +249,8 @@ describe('TaskCtl', function () { it('tests callback is called when response contains id token', function () { - window.parent.AuthenticationContext = function () { - return { - callback: function () { }, - _renewStates: ['4343'] - }; - }; + window.parent.AuthenticationContext.callback = function () { }; + window.parent.AuthenticationContext._renewStates = ['4343']; window.parent.callBackMappedToRenewStates = {}; window.parent.callBackMappedToRenewStates['4343'] = function (error, token) { expect(error).toBe('Invalid id_token. id_token: newIdToken123'); @@ -276,12 +262,8 @@ describe('TaskCtl', function () { it('tests login failure after users logs in', function () { - window.parent.AuthenticationContext = function () { - return { - callback: 'callback', - _renewStates: ['1234'] - }; - }; + window.parent.AuthenticationContext.callback = function () { }; + window.parent.AuthenticationContext._renewStates = ['1234']; window.parent.callBackMappedToRenewStates = {}; window.parent.callBackMappedToRenewStates['1234'] = 'callback'; var mockInvalidClientIdToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJjbGllbnQxMjMiLCJuYW1lIjoiSm9obiBEb2UiLCJ1cG4iOiJqb2huQGVtYWlsLmNvbSJ9.zNX4vfLzlbFeKHZ9BMN3sYLtEEE-0o1RoL4NUhXz-l8'; @@ -297,12 +279,8 @@ describe('TaskCtl', function () { }); it('tests login success after users logs in', function () { - window.parent.AuthenticationContext = function () { - return { - callback: 'callback', - _renewStates: ['1234'] - }; - }; + window.parent.AuthenticationContext.callback = function () { }; + window.parent.AuthenticationContext._renewStates = ['1234']; window.parent.callBackMappedToRenewStates = {}; window.parent.callBackMappedToRenewStates['1234'] = 'callback'; var mockIdToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJjbGllbnRpZDEyMyIsIm5hbWUiOiJKb2huIERvZSIsInVwbiI6ImpvaG5AZW1haWwuY29tIiwibm9uY2UiOm51bGx9.DLCO6yIWhnNBYfHH8qFPswcH4M2Alpjn6AZy7K6HENY'; @@ -319,12 +297,8 @@ describe('TaskCtl', function () { }); it('tests auto id token renew when id token expires', function () { - window.parent.AuthenticationContext = function () { - return { - callback: 'callback', - _renewStates: ['1234'] - }; - }; + window.parent.AuthenticationContext.callback = function () { }; + window.parent.AuthenticationContext._renewStates = ['1234']; window.parent.callBackMappedToRenewStates = {}; window.parent.callBackMappedToRenewStates['1234'] = 'callback'; var loginResourceOldValue = adalServiceProvider.config.loginResource; @@ -333,46 +307,17 @@ describe('TaskCtl', function () { spyOn(rootScope, '$broadcast').andCallThrough(); scope.$on('adal:loginFailure', function (event, message) { expect(event.name).toBe('adal:loginFailure'); - expect(message).toBe('auto renew failure'); + expect(message).toBe('resource is required'); }); scope.$apply(); adalServiceProvider.config.loginResource = loginResourceOldValue; expect(rootScope.$broadcast).toHaveBeenCalled(); }); - it('tests login handler', function () { - spyOn(rootScope, '$broadcast').andCallThrough(); - - adalServiceProvider.config.localLoginUrl = 'localLoginUrl'; - adalServiceProvider.login(); - scope.$on('$locationChangeStart', function (event, newUrl, oldUrl) { - expect(newUrl).toContain('localLoginUrl'); - console.log('location event called'); - event.preventDefault(); - }) - expect(adalServiceProvider.loginInProgress()).toBe(false); - scope.$apply(); - expect(rootScope.$broadcast).toHaveBeenCalled(); - - adalServiceProvider.config.localLoginUrl = null - adalServiceProvider.login(); - scope.$on('adal:loginRedirect', function (event, message) { - expect(event.name).toBe('adal:loginRedirect'); - }); - expect(adalServiceProvider.loginInProgress()).toBe(true); - expect(rootScope.$broadcast).toHaveBeenCalled(); - }); - it('tests route change handler', function () { - var todoRoute = route.routes['/todoList']; var homeRoute = route.routes['/home']; var aboutRoute = route.routes['/about']; - location.url('/todoList'); - scope.$apply(); - expect(route.current.controller).toBe(todoRoute.controller); - expect(route.current.template).toBe(todoRoute.template); - location.url('/home'); scope.$apply(); expect(route.current.controller).toBe(homeRoute.controller); @@ -396,6 +341,27 @@ describe('TaskCtl', function () { expect(window.logMessage).toContain("test message"); expect(Logging.level).toEqual(2); }); + + it('checks if user is redirected to the custom Login Page when localLoginUrl is specified', function () { + spyOn(rootScope, '$broadcast').andCallThrough(); + + adalServiceProvider.config.localLoginUrl = '/login'; + $httpBackend.expectGET('login.html').respond(200); + var loginRoute = route.routes['/login']; + location.url('/todoList'); + scope.$apply(); + expect(route.current.controller).toBe(loginRoute.controller); + expect(route.current.templateUrl).toBe(loginRoute.templateUrl); + + adalServiceProvider.config.localLoginUrl = null; + location.url('/todoList'); + scope.$apply(); + scope.$on('adal:loginRedirect', function (event, message) { + expect(event.name).toBe('adal:loginRedirect'); + }); + expect(adalServiceProvider.loginInProgress()).toBe(true); + expect(rootScope.$broadcast).toHaveBeenCalled(); + }); }); describe('StateCtrl', function () { diff --git a/tests/testApp.js b/tests/testApp.js index 0917072c..0180a0a9 100644 --- a/tests/testApp.js +++ b/tests/testApp.js @@ -36,6 +36,10 @@ app.config(['$httpProvider', '$routeProvider', 'adalAuthenticationServiceProvide template: '
todoList
', requireADLogin: true }). + when('/login', { + controller: 'loginController', + templateUrl: 'login.html', + }). otherwise({ redirectTo: '/home' }); var endpoints = { @@ -50,7 +54,7 @@ app.config(['$httpProvider', '$routeProvider', 'adalAuthenticationServiceProvide clientId: 'clientid123', loginResource: 'loginResource123', redirectUri: 'https://myapp.com/page', - endpoints: endpoints // optional + endpoints: endpoints, // optional }, $httpProvider // pass http provider to inject request interceptor to attach tokens ); diff --git a/tests/unit/spec/AdalSpec.js b/tests/unit/spec/AdalSpec.js index 96b32482..6675b274 100644 --- a/tests/unit/spec/AdalSpec.js +++ b/tests/unit/spec/AdalSpec.js @@ -239,7 +239,6 @@ describe('Adal', function () { adal._user = { profile: { 'upn': 'test@testuser.com' }, userName: 'test@domain.com' }; adal.acquireToken(RESOURCE1, callback); expect(adal.callback).toBe(null); - expect(storageFake.getItem(adal.CONSTANTS.STORAGE.LOGIN_REQUEST)).toBe(''); expect(adal._renewStates.length).toBe(1); // Wait for initial timeout load console.log('Waiting for initial timeout'); @@ -278,7 +277,6 @@ describe('Adal', function () { adal.acquireToken(RESOURCE1, callback); //Simulate second acquire i.e. second service call from Angular. adal.acquireToken(RESOURCE1, callback2); - expect(storageFake.getItem(adal.CONSTANTS.STORAGE.LOGIN_REQUEST)).toBe(''); expect(adal._renewStates.length).toBe(1); // Wait for initial timeout load console.log('Waiting for initial timeout'); @@ -711,7 +709,6 @@ describe('Adal', function () { expect(storageFake.getItem(adal.CONSTANTS.STORAGE.NONCE_IDTOKEN)).toBe('33333333-3333-4333-b333-333333333333'); expect(adal.config.state).toBe('33333333-3333-4333-b333-333333333333' + '|' + 'client'); expect(adal._renewStates.length).toBe(1); - expect(storageFake.getItem(adal.CONSTANTS.STORAGE.LOGIN_REQUEST)).toBe(''); // Wait for initial timeout load console.log('Waiting for initial timeout'); waitsFor(function () { From 559c6ff8ad614180bf22b253e83347ed5b84b61e Mon Sep 17 00:00:00 2001 From: Rohit Pagariya Date: Tue, 27 Sep 2016 09:57:24 -0700 Subject: [PATCH 07/14] Stringify the object when logging --- lib/adal-angular.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/adal-angular.js b/lib/adal-angular.js index c9a5813d..540a6b76 100644 --- a/lib/adal-angular.js +++ b/lib/adal-angular.js @@ -276,7 +276,7 @@ }; var stateChangeErrorHandler = function (event, toState, toParams, fromState, fromParams, error) { - _adal.verbose("State change error occured. Error: " + error); + _adal.verbose("State change error occured. Error: " + JSON.stringify(error)); // adal interceptor sets the error on config.data property. If it is set, it means state change is rejected by adal, // in which case set the defaultPrevented to true to avoid url update as that sometimesleads to infinte loop. @@ -422,7 +422,7 @@ } }, responseError: function (rejection) { - authService.info('Getting error in the response.'); + authService.info('Getting error in the response: ' + JSON.stringify(rejection)); if (rejection) { if (rejection.status === 401) { var resource = authService.getResourceForEndpoint(rejection.config.url); From a9512e1b1eccbc0740f6e8501cc346b90826ddd1 Mon Sep 17 00:00:00 2001 From: Rohit Narula Date: Tue, 11 Oct 2016 16:39:08 -0700 Subject: [PATCH 08/14] adding scope event for token renewal --- lib/adal-angular.js | 17 +++++------ tests/angularModuleSpec.js | 61 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 9 deletions(-) diff --git a/lib/adal-angular.js b/lib/adal-angular.js index 540a6b76..a84206fc 100644 --- a/lib/adal-angular.js +++ b/lib/adal-angular.js @@ -150,16 +150,13 @@ updateDataFromCache(_adal.config.loginResource); if (!_oauthData.isAuthenticated && _oauthData.userName && !_adal._renewActive) { // id_token is expired or not present - _adal._renewActive = true; - _adal.acquireToken(_adal.config.loginResource, function (error, tokenOut) { - _adal._renewActive = false; - if (error) { - $rootScope.$broadcast('adal:loginFailure', error); - } else { - if (tokenOut) { - _oauthData.isAuthenticated = true; - } + var self = $injector.get('adalAuthenticationService'); + self.acquireToken(_adal.config.loginResource).then(function (token) { + if (token) { + _oauthData.isAuthenticated = true; } + }, function (error) { + $rootScope.$broadcast('adal:loginFailure', error); }); } } @@ -322,9 +319,11 @@ _adal.acquireToken(resource, function (error, tokenOut) { _adal._renewActive = false; if (error) { + $rootScope.$broadcast('adal:acquireTokenFailure', error); _adal.error('Error when acquiring token for resource: ' + resource, error); deferred.reject(error); } else { + $rootScope.$broadcast('adal:acquireTokenSuccess', tokenOut); deferred.resolve(tokenOut); } }); diff --git a/tests/angularModuleSpec.js b/tests/angularModuleSpec.js index 45ce1bef..6fdb27f2 100644 --- a/tests/angularModuleSpec.js +++ b/tests/angularModuleSpec.js @@ -422,3 +422,64 @@ describe('StateCtrl', function () { } }); }); + +describe('AcquireTokenCtl', function () { + var scope,adalServiceProvider, rootScope, controller,window; + var store = {}; + //mock Application to allow us to inject our own dependencies + beforeEach(angular.mock.module('TestApplication')); + + //mock the controller for the same reason and include $scope and $controller + beforeEach(angular.mock.inject(function (_adalAuthenticationService_, _$rootScope_, _$controller_, _$window_) { + adalServiceProvider = _adalAuthenticationService_; + rootScope = _$rootScope_; + controller = _$controller_; + window = _$window_; + //create an empty scope + scope = rootScope.$new(); + + spyOn(sessionStorage, 'getItem').andCallFake(function (key) { + return store[key]; + }); + spyOn(sessionStorage, 'setItem').andCallFake(function (key, value) { + store[key] = value; + }); + spyOn(window, 'Date').andCallFake(function () { + return { + getTime: function () { + return 1000; + }, + toUTCString: function () { + return ""; + } + }; + }); + })); + + afterEach(function () { + store = {}; + }); + + it('checks if acquireTokenSuccess/acquireTokenFailure events are broadcasted in case of acquireToken', function () { + var error = ''; + var tokenOut = ''; + var resource = adalServiceProvider.config.loginResource; + var token = 'token123'; + spyOn(rootScope, '$broadcast').andCallThrough(); + scope.$on('adal:acquireTokenFailure', function (event, message) { + error = message; + }); + adalServiceProvider.acquireToken(adalServiceProvider.config.loginResource); + expect(error).toBe('User login is required'); + store = { + 'adal.token.keys': resource + '|', + 'adal.access.token.keyloginResource123': token, + 'adal.expiration.keyloginResource123': 122 + }; + scope.$on('adal:acquireTokenSuccess', function (event, message) { + tokenOut = message; + }); + adalServiceProvider.acquireToken(adalServiceProvider.config.loginResource); + expect(tokenOut).toBe(token); + }); +}); From d472846b3b275216b9cdc7abb756271b0f744b8c Mon Sep 17 00:00:00 2001 From: Nick Abalov Date: Mon, 24 Oct 2016 09:48:02 +0700 Subject: [PATCH 09/14] use adfs specific logout url when tenant is adfs (#403) * use adfs specific logout url when tenant is adfs * add logOutUri config option and use it, not adfs to set adfs logout url * fix to treat logOutUri as absolute --- lib/adal.js | 24 ++++++++++++++++-------- tests/unit/spec/AdalSpec.js | 10 ++++++++++ 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/lib/adal.js b/lib/adal.js index b54fe4e8..5e160986 100644 --- a/lib/adal.js +++ b/lib/adal.js @@ -537,18 +537,26 @@ var AuthenticationContext = (function () { */ AuthenticationContext.prototype.logOut = function () { this.clearCache(); - var tenant = 'common'; - var logout = ''; this._user = null; - if (this.config.tenant) { - tenant = this.config.tenant; - } + var urlNavigate; + + if (this.config.logOutUri) { + urlNavigate = this.config.logOutUri; + } else { + var tenant = 'common'; + var logout = ''; + + if (this.config.tenant) { + tenant = this.config.tenant; + } + + if (this.config.postLogoutRedirectUri) { + logout = 'post_logout_redirect_uri=' + encodeURIComponent(this.config.postLogoutRedirectUri); + } - if (this.config.postLogoutRedirectUri) { - logout = 'post_logout_redirect_uri=' + encodeURIComponent(this.config.postLogoutRedirectUri); + urlNavigate = this.instance + tenant + '/oauth2/logout?' + logout; } - var urlNavigate = this.instance + tenant + '/oauth2/logout?' + logout; this.info('Logout navigate to: ' + urlNavigate); this.promptUser(urlNavigate); }; diff --git a/tests/unit/spec/AdalSpec.js b/tests/unit/spec/AdalSpec.js index 6675b274..8bf826f7 100644 --- a/tests/unit/spec/AdalSpec.js +++ b/tests/unit/spec/AdalSpec.js @@ -409,6 +409,16 @@ describe('Adal', function () { expect(adal.promptUser).toHaveBeenCalledWith(DEFAULT_INSTANCE + 'common/oauth2/logout?post_logout_redirect_uri=https%3A%2F%2Fcontoso.com%2Flogout'); }); + it('uses logout uri if given', function (){ + storageFake.setItem(adal.CONSTANTS.STORAGE.USERNAME, 'test user'); + adal.config.displayCall = null; + adal.config.clientId = 'client'; + adal.config.logOutUri = 'https://login.microsoftonline.com/adfs/ls/?wa=wsignout1.0' + spyOn(adal, 'promptUser'); + adal.logOut(); + expect(adal.promptUser).toHaveBeenCalledWith('https://login.microsoftonline.com/adfs/ls/?wa=wsignout1.0'); + }) + it('gets user from cache', function () { storageFake.setItem(adal.CONSTANTS.STORAGE.IDTOKEN, IDTOKEN_MOCK); adal.config.clientId = 'e9a5a8b6-8af7-4719-9821-0deef255f68e'; From a845c25cf45239b5ba103107cf95612a2bc7f4e1 Mon Sep 17 00:00:00 2001 From: Tushar Gupta Date: Fri, 4 Nov 2016 10:47:16 -0700 Subject: [PATCH 10/14] passing error parameter to the callback and fixing tests --- lib/adal-angular.js | 27 ++-- lib/adal.js | 24 ++-- tests/angularModuleSpec.js | 246 +++++++++++++++++++++++------------- tests/unit/spec/AdalSpec.js | 134 ++++++++++++-------- 4 files changed, 265 insertions(+), 166 deletions(-) diff --git a/lib/adal-angular.js b/lib/adal-angular.js index a84206fc..e164687f 100644 --- a/lib/adal-angular.js +++ b/lib/adal-angular.js @@ -96,13 +96,13 @@ if (callback && typeof callback === 'function') { // id_token or access_token can be renewed if (requestInfo.parameters['access_token']) { - callback(_adal._getItem(_adal.CONSTANTS.STORAGE.ERROR_DESCRIPTION), requestInfo.parameters['access_token']); + callback(_adal._getItem(_adal.CONSTANTS.STORAGE.ERROR_DESCRIPTION), requestInfo.parameters['access_token'], _adal._getItem(_adal.CONSTANTS.STORAGE.ERROR)); return; } else if (requestInfo.parameters['id_token']) { - callback(_adal._getItem(_adal.CONSTANTS.STORAGE.ERROR_DESCRIPTION), requestInfo.parameters['id_token']); + callback(_adal._getItem(_adal.CONSTANTS.STORAGE.ERROR_DESCRIPTION), requestInfo.parameters['id_token'], _adal._getItem(_adal.CONSTANTS.STORAGE.ERROR)); return; } else if (requestInfo.parameters['error']) { - callback(_adal._getItem(_adal.CONSTANTS.STORAGE.ERROR_DESCRIPTION), null); + callback(_adal._getItem(_adal.CONSTANTS.STORAGE.ERROR_DESCRIPTION), null, _adal._getItem(_adal.CONSTANTS.STORAGE.ERROR)); return; } } @@ -118,16 +118,16 @@ $rootScope.$broadcast('adal:loginSuccess', _adal._getItem(_adal.CONSTANTS.STORAGE.IDTOKEN)); } else { - $rootScope.$broadcast('adal:loginFailure', _adal._getItem(_adal.CONSTANTS.STORAGE.ERROR_DESCRIPTION)); + $rootScope.$broadcast('adal:loginFailure', _adal._getItem(_adal.CONSTANTS.STORAGE.ERROR_DESCRIPTION), _adal._getItem(_adal.CONSTANTS.STORAGE.ERROR)); } if (_adal.callback && typeof _adal.callback === 'function') - _adal.callback(_adal._getItem(_adal.CONSTANTS.STORAGE.ERROR_DESCRIPTION), _adal._getItem(_adal.CONSTANTS.STORAGE.IDTOKEN)); + _adal.callback(_adal._getItem(_adal.CONSTANTS.STORAGE.ERROR_DESCRIPTION), _adal._getItem(_adal.CONSTANTS.STORAGE.IDTOKEN), _adal._getItem(_adal.CONSTANTS.STORAGE.ERROR)); // redirect to login start page if (!_adal.popUp) { var loginStartPage = _adal._getItem(_adal.CONSTANTS.STORAGE.LOGIN_REQUEST); - if (typeof loginStartPage !== 'undefined' && loginStartPage && loginStartPage.length !==0) { + if (typeof loginStartPage !== 'undefined' && loginStartPage && loginStartPage.length !== 0) { // prevent the current location change and redirect the user back to the login start page _adal.verbose('Redirecting to start page: ' + loginStartPage); if (!$location.$$html5 && loginStartPage.indexOf('#') > -1) { @@ -142,7 +142,7 @@ } else { // state did not match, broadcast an error - $rootScope.$broadcast('adal:stateMismatch', _adal._getItem(_adal.CONSTANTS.STORAGE.ERROR_DESCRIPTION)); + $rootScope.$broadcast('adal:stateMismatch', _adal._getItem(_adal.CONSTANTS.STORAGE.ERROR_DESCRIPTION), _adal._getItem(_adal.CONSTANTS.STORAGE.ERROR)); } } else { // No callback. App resumes after closing or moving to new page. @@ -156,7 +156,8 @@ _oauthData.isAuthenticated = true; } }, function (error) { - $rootScope.$broadcast('adal:loginFailure', error); + var errorParts = error.split('|'); + $rootScope.$broadcast('adal:loginFailure', errorParts[0], errorParts[1]); }); } } @@ -316,12 +317,12 @@ // automated token request call var deferred = $q.defer(); _adal._renewActive = true; - _adal.acquireToken(resource, function (error, tokenOut) { + _adal.acquireToken(resource, function (errorDesc, tokenOut, error) { _adal._renewActive = false; if (error) { - $rootScope.$broadcast('adal:acquireTokenFailure', error); + $rootScope.$broadcast('adal:acquireTokenFailure', errorDesc, error); _adal.error('Error when acquiring token for resource: ' + resource, error); - deferred.reject(error); + deferred.reject(errorDesc + "|" + error); } else { $rootScope.$broadcast('adal:acquireTokenSuccess', tokenOut); deferred.resolve(tokenOut); @@ -410,8 +411,8 @@ authService.verbose('Token is available'); config.headers.Authorization = 'Bearer ' + token; delayedRequest.resolve(config); - }, function (err) { - config.data = err; + }, function (errDesc, error) { + config.data = errDesc + "|" + error; delayedRequest.reject(config); }); diff --git a/lib/adal.js b/lib/adal.js index 5e160986..d7f13b47 100644 --- a/lib/adal.js +++ b/lib/adal.js @@ -239,7 +239,7 @@ var AuthenticationContext = (function () { this._saveItem(this.CONSTANTS.STORAGE.ERROR_DESCRIPTION, 'Popup Window is null. This can happen if you are using IE'); this._saveItem(this.CONSTANTS.STORAGE.LOGIN_ERROR, 'Popup Window is null. This can happen if you are using IE'); if (this.callback) - this.callback(this._getItem(this.CONSTANTS.STORAGE.LOGIN_ERROR), null); + this.callback(this._getItem(this.CONSTANTS.STORAGE.LOGIN_ERROR), null, this._getItem(this.CONSTANTS.STORAGE.ERROR)); return; } if (this.config.redirectUri.indexOf('#') != -1) @@ -325,10 +325,10 @@ var AuthenticationContext = (function () { var self = this; window.callBacksMappedToRenewStates[expectedState].push(callback); if (!window.callBackMappedToRenewStates[expectedState]) { - window.callBackMappedToRenewStates[expectedState] = function (message, token) { + window.callBackMappedToRenewStates[expectedState] = function (errorDesc, token, error) { for (var i = 0; i < window.callBacksMappedToRenewStates[expectedState].length; ++i) { try { - window.callBacksMappedToRenewStates[expectedState][i](message, token); + window.callBacksMappedToRenewStates[expectedState][i](errorDesc, token, error); } catch (error) { self.warn(error); @@ -414,7 +414,7 @@ var AuthenticationContext = (function () { self.verbose('Loading frame has timed out after: ' + (self.CONSTANTS.LOADFRAME_TIMEOUT / 1000) + ' seconds for resource ' + resource); var expectedState = self._activeRenewals[resource]; if (expectedState && window.callBackMappedToRenewStates[expectedState]) { - window.callBackMappedToRenewStates[expectedState]('Token renewal operation failed due to timeout', null); + window.callBackMappedToRenewStates[expectedState]('Token renewal operation failed due to timeout', null, 'Token Renewal Failed'); } self._saveItem(self.CONSTANTS.STORAGE.RENEW_STATUS + resource, self.CONSTANTS.TOKEN_RENEW_STATUS_CANCELED); @@ -445,20 +445,20 @@ var AuthenticationContext = (function () { AuthenticationContext.prototype.acquireToken = function (resource, callback) { if (this._isEmpty(resource)) { this.warn('resource is required'); - callback('resource is required', null); + callback('resource is required', null, 'resource is required'); return; } var token = this.getCachedToken(resource); if (token) { this.info('Token is already in cache for resource:' + resource); - callback(null, token); + callback(null, token, null); return; } if (!this._user) { this.warn('User login is required'); - callback('User login is required', null); + callback('User login is required', null, 'login required'); return; } @@ -539,13 +539,13 @@ var AuthenticationContext = (function () { this.clearCache(); this._user = null; var urlNavigate; - + if (this.config.logOutUri) { urlNavigate = this.config.logOutUri; } else { var tenant = 'common'; var logout = ''; - + if (this.config.tenant) { tenant = this.config.tenant; } @@ -596,7 +596,7 @@ var AuthenticationContext = (function () { callback(null, this._user); } else { this.warn('User information is not available'); - callback('User information is not available'); + callback('User information is not available', null); } }; @@ -894,12 +894,12 @@ var AuthenticationContext = (function () { this.verbose('Window is in iframe'); callback = window.parent.callBackMappedToRenewStates[requestInfo.stateResponse]; if (callback) - callback(this._getItem(this.CONSTANTS.STORAGE.ERROR_DESCRIPTION), requestInfo.parameters[this.CONSTANTS.ACCESS_TOKEN] || requestInfo.parameters[this.CONSTANTS.ID_TOKEN]); + callback(this._getItem(this.CONSTANTS.STORAGE.ERROR_DESCRIPTION), requestInfo.parameters[this.CONSTANTS.ACCESS_TOKEN] || requestInfo.parameters[this.CONSTANTS.ID_TOKEN], this._getItem(this.CONSTANTS.STORAGE.ERROR)); return; } else if (requestInfo.requestType === this.REQUEST_TYPE.LOGIN) { callback = this.callback; if (callback) - callback(this._getItem(this.CONSTANTS.STORAGE.ERROR_DESCRIPTION), requestInfo.parameters[this.CONSTANTS.ID_TOKEN]); + callback(this._getItem(this.CONSTANTS.STORAGE.ERROR_DESCRIPTION), requestInfo.parameters[this.CONSTANTS.ID_TOKEN], this._getItem(this.CONSTANTS.STORAGE.ERROR)); } if (!this.popUp)// No need to redirect user in case of popup window.location = this._getItem(this.CONSTANTS.STORAGE.LOGIN_REQUEST); diff --git a/tests/angularModuleSpec.js b/tests/angularModuleSpec.js index 6fdb27f2..0c4ab047 100644 --- a/tests/angularModuleSpec.js +++ b/tests/angularModuleSpec.js @@ -77,7 +77,10 @@ describe('TaskCtl', function () { }; window.parent.AuthenticationContext = window.AuthenticationContext; + window.location.hash = ''; + // to prevent full page reload error in karma + window.onbeforeunload = function () { return }; controller('TaskCtl', { $scope: scope, adalAuthenticationService: adalServiceProvider }); })); @@ -202,116 +205,144 @@ describe('TaskCtl', function () { return headers.Authorization === 'Bearer Token456' }).respond(200); + var eventName = '', msg = ''; scope.$on('adal:errorResponse', function (event, message) { - expect(event.name).toBe('adal:errorResponse'); - expect(message.data).toBe('login in progress, cancelling the request for https://myapp.com/someapi/item'); + eventName = event.name; + msg = message; }); scope.taskCall5(); scope.$apply(); expect(rootScope.$broadcast).toHaveBeenCalled(); + expect(eventName).toBe('adal:errorResponse'); + expect(msg.data).toBe('login in progress, cancelling the request for https://myapp.com/someapi/item'); + }); it('tests stateMismatch broadcast when state does not match', function () { - window.parent.AuthenticationContext.callback = function () { }; - window.parent.AuthenticationContext._renewStates = {}; + window.parent = { + AuthenticationContext: function () { + return { + _renewStates: {} + } + }, + }; window.location.hash = 'id_token=sample&state=4343'; spyOn(rootScope, '$broadcast').andCallThrough(); + + var eventName = '', msg = ''; scope.$on('adal:stateMismatch', function (event, message) { - expect(event.name).toBe('adal:stateMismatch'); - expect(message).toBe('Invalid_state. state: 4343'); + eventName = event.name; + msg = message; }); + scope.$apply(); expect(rootScope.$broadcast).toHaveBeenCalled(); + expect(eventName).toBe('adal:stateMismatch'); + expect(msg).toBe('Invalid_state. state: 4343'); }); it('tests callback is called when response contains error', function () { - window.parent.AuthenticationContext.callback = function () { }; - window.parent.AuthenticationContext._renewStates = ['4343']; - window.parent.callBackMappedToRenewStates = {}; - window.parent.callBackMappedToRenewStates['4343'] = function (error, token) { - expect(error).toBe('renewfailed'); + var error = '', errorDesc = ''; + var callback = function (valErrorDesc, valToken, valError) { + error = valError; + errorDesc = valErrorDesc; + }; + window.parent = { + AuthenticationContext: function () { + return { + _renewStates: ['4343'] + } + }, + callBackMappedToRenewStates: { "4343": callback } }; window.location.hash = 'error=sample&error_description=renewfailed&state=4343'; scope.$apply(); + expect(error).toBe('sample'); + expect(errorDesc).toBe('renewfailed'); }); it('tests callback is called when response contains access token', function () { - window.parent.AuthenticationContext.callback = function () { }; - window.parent.AuthenticationContext._renewStates = ['4343']; - window.parent.callBackMappedToRenewStates = {}; - window.parent.callBackMappedToRenewStates['4343'] = function (error, token) { - expect(error).toBe(''); - expect(token).toBe('newAccessToken123'); + var error = null, errorDesc = null, token = ''; + var callback = function (valErrorDesc, valToken, valError) { + error = valError; + errorDesc = valErrorDesc; + token = valToken; + }; + window.parent = { + AuthenticationContext: function () { + return { + _renewStates: ['4343'] + } + }, + callBackMappedToRenewStates: { "4343": callback } }; window.location.hash = 'access_token=newAccessToken123&state=4343'; scope.$apply(); + expect(error).toBe(''); + expect(errorDesc).toBe(''); + expect(token).toBe('newAccessToken123'); }); - it('tests callback is called when response contains id token', function () { - window.parent.AuthenticationContext.callback = function () { }; - window.parent.AuthenticationContext._renewStates = ['4343']; - window.parent.callBackMappedToRenewStates = {}; - window.parent.callBackMappedToRenewStates['4343'] = function (error, token) { - expect(error).toBe('Invalid id_token. id_token: newIdToken123'); - expect(token).toBe('newIdToken123'); + var error = '', errorDesc = '', token = ''; + var callback = function (valErrorDesc, valToken, valError) { + error = valError; + errorDesc = valErrorDesc; + token = valToken; + }; + window.parent = { + AuthenticationContext: function () { + return { + _renewStates: ['4343'] + } + }, + callBackMappedToRenewStates: { "4343": callback } }; window.location.hash = 'id_token=newIdToken123&state=4343'; scope.$apply(); + expect(errorDesc).toBe('Invalid id_token. id_token: newIdToken123'); + expect(error).toBe('invalid id_token'); + expect(token).toBe('newIdToken123'); }); it('tests login failure after users logs in', function () { - window.parent.AuthenticationContext.callback = function () { }; - window.parent.AuthenticationContext._renewStates = ['1234']; - window.parent.callBackMappedToRenewStates = {}; - window.parent.callBackMappedToRenewStates['1234'] = 'callback'; var mockInvalidClientIdToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJjbGllbnQxMjMiLCJuYW1lIjoiSm9obiBEb2UiLCJ1cG4iOiJqb2huQGVtYWlsLmNvbSJ9.zNX4vfLzlbFeKHZ9BMN3sYLtEEE-0o1RoL4NUhXz-l8'; window.location.hash = 'id_token=' + mockInvalidClientIdToken + '&state=1234'; + window.sessionStorage.setItem('adal.state.login', '1234'); spyOn(rootScope, '$broadcast').andCallThrough(); - scope.$on('adal:loginFailure', function (event, message) { - expect(event.name).toBe('adal:loginFailure'); - expect(message).toBe('Invalid id_token. id_token: ' + mockInvalidClientIdToken); + var eventName = '', error = '', errorDesc = '', token = ''; + scope.$on('adal:loginFailure', function (event, valErrorDesc, valError) { + eventName = event.name; + errorDesc = valErrorDesc; + error = valError; }); scope.$apply(); expect(rootScope.$broadcast).toHaveBeenCalled(); - + expect(eventName).toBe('adal:loginFailure'); + expect(errorDesc).toBe('Invalid id_token. id_token: ' + mockInvalidClientIdToken); + expect(error).toBe('invalid id_token'); + window.sessionStorage.setItem('adal.state.login', ''); }); it('tests login success after users logs in', function () { - window.parent.AuthenticationContext.callback = function () { }; - window.parent.AuthenticationContext._renewStates = ['1234']; - window.parent.callBackMappedToRenewStates = {}; - window.parent.callBackMappedToRenewStates['1234'] = 'callback'; var mockIdToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJjbGllbnRpZDEyMyIsIm5hbWUiOiJKb2huIERvZSIsInVwbiI6ImpvaG5AZW1haWwuY29tIiwibm9uY2UiOm51bGx9.DLCO6yIWhnNBYfHH8qFPswcH4M2Alpjn6AZy7K6HENY'; window.location.hash = 'id_token=' + mockIdToken + '&state=1234'; + window.sessionStorage.setItem('adal.state.login', '1234'); spyOn(rootScope, '$broadcast').andCallThrough(); - scope.$on('adal:loginSuccess', function (event, message) { - expect(event.name).toBe('adal:loginSuccess'); - expect(adalServiceProvider.userInfo.userName).toBe('john@email.com'); - expect(adalServiceProvider.userInfo.profile.upn).toBe('john@email.com'); - expect(adalServiceProvider.userInfo.profile.aud).toBe('clientid123'); + var eventName = '', token = ''; + scope.$on('adal:loginSuccess', function (event, valToken) { + eventName = event.name; + token = valToken; }); scope.$apply(); expect(rootScope.$broadcast).toHaveBeenCalled(); - }); - - it('tests auto id token renew when id token expires', function () { - window.parent.AuthenticationContext.callback = function () { }; - window.parent.AuthenticationContext._renewStates = ['1234']; - window.parent.callBackMappedToRenewStates = {}; - window.parent.callBackMappedToRenewStates['1234'] = 'callback'; - var loginResourceOldValue = adalServiceProvider.config.loginResource; - adalServiceProvider.config.loginResource = null; - window.location.hash = 'hash'; - spyOn(rootScope, '$broadcast').andCallThrough(); - scope.$on('adal:loginFailure', function (event, message) { - expect(event.name).toBe('adal:loginFailure'); - expect(message).toBe('resource is required'); - }); - scope.$apply(); - adalServiceProvider.config.loginResource = loginResourceOldValue; - expect(rootScope.$broadcast).toHaveBeenCalled(); + expect(eventName).toBe('adal:loginSuccess'); + expect(adalServiceProvider.userInfo.userName).toBe('john@email.com'); + expect(adalServiceProvider.userInfo.profile.upn).toBe('john@email.com'); + expect(adalServiceProvider.userInfo.profile.aud).toBe('clientid123'); + expect(token).toBe(mockIdToken); + adalServiceProvider.logOut(); }); it('tests route change handler', function () { @@ -341,27 +372,6 @@ describe('TaskCtl', function () { expect(window.logMessage).toContain("test message"); expect(Logging.level).toEqual(2); }); - - it('checks if user is redirected to the custom Login Page when localLoginUrl is specified', function () { - spyOn(rootScope, '$broadcast').andCallThrough(); - - adalServiceProvider.config.localLoginUrl = '/login'; - $httpBackend.expectGET('login.html').respond(200); - var loginRoute = route.routes['/login']; - location.url('/todoList'); - scope.$apply(); - expect(route.current.controller).toBe(loginRoute.controller); - expect(route.current.templateUrl).toBe(loginRoute.templateUrl); - - adalServiceProvider.config.localLoginUrl = null; - location.url('/todoList'); - scope.$apply(); - scope.$on('adal:loginRedirect', function (event, message) { - expect(event.name).toBe('adal:loginRedirect'); - }); - expect(adalServiceProvider.loginInProgress()).toBe(true); - expect(rootScope.$broadcast).toHaveBeenCalled(); - }); }); describe('StateCtrl', function () { @@ -424,17 +434,20 @@ describe('StateCtrl', function () { }); describe('AcquireTokenCtl', function () { - var scope,adalServiceProvider, rootScope, controller,window; + var scope, adalServiceProvider, rootScope, controller, window, $httpBackend, route, location; var store = {}; //mock Application to allow us to inject our own dependencies beforeEach(angular.mock.module('TestApplication')); //mock the controller for the same reason and include $scope and $controller - beforeEach(angular.mock.inject(function (_adalAuthenticationService_, _$rootScope_, _$controller_, _$window_) { + beforeEach(angular.mock.inject(function (_adalAuthenticationService_, _$rootScope_, _$controller_, _$window_, _$httpBackend_, _$route_, _$location_) { adalServiceProvider = _adalAuthenticationService_; rootScope = _$rootScope_; controller = _$controller_; window = _$window_; + $httpBackend = _$httpBackend_; + route = _$route_; + location = _$location_; //create an empty scope scope = rootScope.$new(); @@ -461,18 +474,19 @@ describe('AcquireTokenCtl', function () { }); it('checks if acquireTokenSuccess/acquireTokenFailure events are broadcasted in case of acquireToken', function () { - var error = ''; + var error = '', errorDesc = ''; var tokenOut = ''; - var resource = adalServiceProvider.config.loginResource; var token = 'token123'; spyOn(rootScope, '$broadcast').andCallThrough(); - scope.$on('adal:acquireTokenFailure', function (event, message) { - error = message; + scope.$on('adal:acquireTokenFailure', function (event, valErrorDesc, valError) { + errorDesc = valErrorDesc; + error = valError; }); adalServiceProvider.acquireToken(adalServiceProvider.config.loginResource); - expect(error).toBe('User login is required'); + expect(errorDesc).toBe('User login is required'); + expect(error).toBe('login required'); store = { - 'adal.token.keys': resource + '|', + 'adal.token.keys': adalServiceProvider.config.loginResource + '|', 'adal.access.token.keyloginResource123': token, 'adal.expiration.keyloginResource123': 122 }; @@ -482,4 +496,60 @@ describe('AcquireTokenCtl', function () { adalServiceProvider.acquireToken(adalServiceProvider.config.loginResource); expect(tokenOut).toBe(token); }); + + + it('checks if user is redirected to the custom Login Page when localLoginUrl is specified', function () { + spyOn(rootScope, '$broadcast').andCallThrough(); + + adalServiceProvider.config.localLoginUrl = '/login'; + $httpBackend.expectGET('login.html').respond(200); + var loginRoute = route.routes['/login']; + location.url('/todoList'); + scope.$apply(); + expect(route.current.controller).toBe(loginRoute.controller); + expect(route.current.templateUrl).toBe(loginRoute.templateUrl); + expect(adalServiceProvider.loginInProgress()).toBe(false); + adalServiceProvider.config.localLoginUrl = null; + }); + + it('checks if loginRedirect event is fired when localLoginUrl is not specified', function () { + spyOn(rootScope, '$broadcast').andCallThrough(); + + adalServiceProvider.config.localLoginUrl = null; + location.url('/todoList'); + var eventName = ''; + scope.$on('adal:loginRedirect', function (event) { + eventName = event.name; + }); + scope.$apply(); + expect(adalServiceProvider.loginInProgress()).toBe(true); + expect(rootScope.$broadcast).toHaveBeenCalled(); + expect(eventName).toBe('adal:loginRedirect'); + }); + + it('tests auto id token renew when id token expires', function () { + spyOn(rootScope, '$broadcast').andCallThrough(); + + var loginResourceOldValue = adalServiceProvider.config.loginResource; + adalServiceProvider.config.loginResource = null; + window.location.hash = 'hash'; + var eventName = '', error = '', errorDesc = '', token = ''; + scope.$on('adal:loginFailure', function (event, valErrorDesc, valError) { + eventName = event.name; + errorDesc = valErrorDesc; + error = valError; + }); + + store = { + 'adal.idtoken': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJjbGllbnRpZDEyMyIsIm5hbWUiOiJKb2huIERvZSIsInVwbiI6ImpvaG5AZW1haWwuY29tIiwibm9uY2UiOm51bGx9.DLCO6yIWhnNBYfHH8qFPswcH4M2Alpjn6AZy7K6HENY' + } + scope.$apply(); + + adalServiceProvider.config.loginResource = loginResourceOldValue; + expect(rootScope.$broadcast).toHaveBeenCalled(); + expect(eventName).toBe('adal:loginFailure'); + expect(errorDesc).toBe('resource is required'); + expect(error).toBe('resource is required'); + adalServiceProvider.logOut(); + }); }); diff --git a/tests/unit/spec/AdalSpec.js b/tests/unit/spec/AdalSpec.js index 8bf826f7..ffec224c 100644 --- a/tests/unit/spec/AdalSpec.js +++ b/tests/unit/spec/AdalSpec.js @@ -203,25 +203,29 @@ describe('Adal', function () { it('returns from cache for auto renewable if not expired', function () { adal.config.expireOffsetSeconds = SECONDS_TO_EXPIRE - 100; - var err = ''; - var token = ''; - var callback = function (valErr, valToken) { - err = valErr; + var errDesc = '', token = '', err = ''; + var callback = function (valErrDesc, valToken, valErr) { + errDesc = valErrDesc; token = valToken; + err = valErr; }; adal.acquireToken(RESOURCE1, callback); expect(token).toBe('access_token_in_cache' + RESOURCE1); + expect(errDesc).toBe(null); + expect(err).toBe(null); }); it('returns error for acquireToken without resource', function () { adal.config.expireOffsetSeconds = SECONDS_TO_EXPIRE - 100; - var err = ''; - var token = ''; - var callback = function (valErr, valToken) { - err = valErr; + var errDesc = '', token = '', err = ''; + var callback = function (valErrDesc, valToken, valErr) { + errDesc = valErrDesc; token = valToken; + err = valErr; }; adal.acquireToken(null, callback); + expect(errDesc).toBe('resource is required'); + expect(token).toBe(null); expect(err).toBe('resource is required'); }); @@ -229,11 +233,11 @@ describe('Adal', function () { adal.config.redirectUri = 'contoso_site'; adal.config.clientId = 'client'; adal.config.expireOffsetSeconds = SECONDS_TO_EXPIRE + 100; - var err = ''; - var token = ''; - var callback = function (valErr, valToken) { - err = valErr; + var errDesc = '', token = '', err = ''; + var callback = function (valErrDesc, valToken, valErr) { + errDesc = valErrDesc; token = valToken; + err = valErr; }; adal._renewStates = []; adal._user = { profile: { 'upn': 'test@testuser.com' }, userName: 'test@domain.com' }; @@ -258,20 +262,18 @@ describe('Adal', function () { adal.config.redirectUri = 'contoso_site'; adal.config.clientId = 'client'; adal.config.expireOffsetSeconds = SECONDS_TO_EXPIRE + 100; - var err = null; - var token = null; - var err2 = null; - var token2 = null; - var callback = function (valErr, valToken) { - err = valErr; + var errDesc = '', token = '', err = ''; + var errDesc2 = '', token2 = '', err2 = ''; + var callback = function (valErrDesc, valToken, valErr) { + errDesc = valErrDesc; token = valToken; - throw new Error("Error occurred in callback function"); + err = valErr; }; - var callback2 = function (valErr, valToken) { - err2 = valErr; + var callback2 = function (valErrDesc, valToken, valErr) { + errDesc2 = valErrDesc; token2 = valToken; + err2 = valErr; }; - adal._renewStates = []; adal._user = { profile: { 'upn': 'test@testuser.com' }, userName: 'test@domain.com' }; adal.acquireToken(RESOURCE1, callback); @@ -291,11 +293,14 @@ describe('Adal', function () { //Simulate callback from the frame. //adal.callback(null, '33333333-3333-4333-b333-333333333333'); - window.callBackMappedToRenewStates[adal.config.state](null, '33333333-3333-4333-b333-333333333333'); + window.callBackMappedToRenewStates[adal.config.state](null, '33333333-3333-4333-b333-333333333333', null); //Both callbacks should have been provided with the token. expect(token).toBe('33333333-3333-4333-b333-333333333333', 'First callback should be called'); + expect(errDesc).toBe(null); + expect(err).toBe(null); expect(token2).toBe('33333333-3333-4333-b333-333333333333', 'Second callback should be called'); - + expect(errDesc2).toBe(null); + expect(err2).toBe(null); }); it('check guid masking', function () { @@ -409,7 +414,7 @@ describe('Adal', function () { expect(adal.promptUser).toHaveBeenCalledWith(DEFAULT_INSTANCE + 'common/oauth2/logout?post_logout_redirect_uri=https%3A%2F%2Fcontoso.com%2Flogout'); }); - it('uses logout uri if given', function (){ + it('uses logout uri if given', function () { storageFake.setItem(adal.CONSTANTS.STORAGE.USERNAME, 'test user'); adal.config.displayCall = null; adal.config.clientId = 'client'; @@ -424,8 +429,7 @@ describe('Adal', function () { adal.config.clientId = 'e9a5a8b6-8af7-4719-9821-0deef255f68e'; adal.config.loginResource = RESOURCE1; adal.config.expireOffsetSeconds = SECONDS_TO_EXPIRE - 100; - var err = ''; - var user = {}; + var err = '', user = ''; var callback = function (valErr, valResult) { err = valErr; user = valResult; @@ -682,11 +686,11 @@ describe('Adal', function () { it('tests that callbacks are called when renewal token request was canceled', function () { adal.config.expireOffsetSeconds = SECONDS_TO_EXPIRE + 100; - var err = ''; - var token = ''; - var callback = function (valErr, valToken) { - err = valErr; + var errDesc = '', token = '', err = ''; + var callback = function (valErrDesc, valToken, valErr) { + errDesc = valErrDesc; token = valToken; + err = valErr; }; adal._renewStates = []; adal._user = { userName: 'test@testuser.com' }; @@ -696,8 +700,9 @@ describe('Adal', function () { }, 'token renew status not updated', 1000); runs(function () { expect(storageFake.getItem(adal.CONSTANTS.STORAGE.RENEW_STATUS + RESOURCE1)).toBe(adal.CONSTANTS.TOKEN_RENEW_STATUS_CANCELED); - expect(err).toBe('Token renewal operation failed due to timeout'); + expect(errDesc).toBe('Token renewal operation failed due to timeout'); expect(token).toBe(null); + expect(err).toBe('Token Renewal Failed'); }); }); @@ -707,11 +712,11 @@ describe('Adal', function () { adal.config.clientId = 'client'; adal.config.expireOffsetSeconds = SECONDS_TO_EXPIRE + 100; adal.config.tenant = 'testtenant'; - var err = ''; - var token = ''; - var callback = function (valErr, valToken) { - err = valErr; + var errDesc = '', token = '', err = ''; + var callback = function (valErrDesc, valToken, valErr) { + errDesc = valErrDesc; token = valToken; + err = valErr; }; adal._renewStates = []; adal._user = { profile: { 'upn': 'test@testuser.com' }, userName: 'test@domain.com' }; @@ -743,17 +748,18 @@ describe('Adal', function () { requestType: adal.REQUEST_TYPE.RENEW_TOKEN }; }; - var err = ''; - var token = ''; - var callback = function (valErr, valToken) { - err = valErr; + var errDesc = '', token = '', err = ''; + var callback = function (valErrDesc, valToken, valErr) { + errDesc = valErrDesc; token = valToken; + err = valErr; }; window.parent = {}; window.parent.callBackMappedToRenewStates = {}; window.parent.callBackMappedToRenewStates[adal.getRequestInfo().stateResponse] = callback; adal.handleWindowCallback(); - expect(err).toBe('error description'); + expect(errDesc).toBe('error description'); + expect(err).toBe('invalid'); expect(token).toBe(IDTOKEN_MOCK); adal.getRequestInfo = _getRequestInfo; @@ -870,18 +876,19 @@ describe('Adal', function () { adal.popUp = true; adal.config.clientId = 'client'; adal.config.redirectUri = 'contoso_site'; - var err; - var token; - var callback = function (valErr, valToken) { - err = valErr; + var errDesc = '', token = '', err = ''; + var callback = function (valErrDesc, valToken, valErr) { + errDesc = valErrDesc; token = valToken; + err = valErr; }; window.open = function () { return null; } adal.callback = callback; adal.login(); - expect(err).toBe('Popup Window is null. This can happen if you are using IE'); + expect(errDesc).toBe('Popup Window is null. This can happen if you are using IE'); + expect(err).toBe('Error opening popup'); expect(token).toBe(null); expect(adal.loginInProgress()).toBe(false); }); @@ -910,11 +917,11 @@ describe('Adal', function () { }; return popupWindow; }; - var err; - var token; - var callback = function (valErr, valToken) { - err = valErr; + var errDesc = '', token = '', err = ''; + var callback = function (valErrDesc, valToken, valErr) { + errDesc = valErrDesc; token = valToken; + err = valErr; }; adal.callback = callback; mathMock.random = function () { @@ -930,6 +937,8 @@ describe('Adal', function () { runs(function () { expect(adal.loginInProgress()).toBe(false); expect(token).toBe(IDTOKEN_MOCK); + expect(err).toBe('invalid id_token'); + expect(errDesc).toBe('Invalid id_token. id_token: ' + IDTOKEN_MOCK); expect(window.location.href).not.toBe('home page'); }); @@ -952,7 +961,7 @@ describe('Adal', function () { }); it('tests _guid function if window.crypto is defined in the browser', function () { - var buffer = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]; + var buffer = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]; window.msCrypto = null; window.crypto = { getRandomValues: function (_buffer) { @@ -965,6 +974,25 @@ describe('Adal', function () { window.crypto = null; }); - // TODO angular intercepptor - // TODO angular authenticationService + it('tests if error parameter is passed to acquireToken callback', function () { + var errorHash = '#error=interaction_required&error_description=some_description&state=someState'; + var errDesc = '', token = '', err = ''; + var callback = function (valErrDesc, valToken, valErr) { + errDesc = valErrDesc; + token = valToken; + err = valErr; + } + window.parent = { + AuthenticationContext: function () { + return { + _renewStates: ['someState'] + } + }, + callBackMappedToRenewStates: { "someState": callback } + }; + adal.handleWindowCallback(errorHash); + expect(err).toBe('interaction_required'); + expect(token).toBe(undefined); + expect(errDesc).toBe('some_description'); + }); }); From cbc4ce03b650fad0edd294e55155a827006046bc Mon Sep 17 00:00:00 2001 From: Richie Casto Date: Mon, 7 Nov 2016 15:29:05 -0800 Subject: [PATCH 11/14] add stronger check for supports localStorage and sessionStorage (#428) * add stronger check for supports localStorage and sessionStorage * setItem and removeItem have no return value, correct * fix tests, add removeItem mock --- lib/adal.js | 14 ++++++++++++-- tests/unit/spec/AdalSpec.js | 5 +++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/lib/adal.js b/lib/adal.js index d7f13b47..b8532cea 100644 --- a/lib/adal.js +++ b/lib/adal.js @@ -1226,7 +1226,12 @@ var AuthenticationContext = (function () { AuthenticationContext.prototype._supportsLocalStorage = function () { try { - return 'localStorage' in window && window['localStorage']; + var supportsLocalStorage = 'localStorage' in window && window['localStorage']; + if (supportsLocalStorage) { + window.localStorage.setItem('storageTest', ''); + window.localStorage.removeItem('storageTest'); + } + return supportsLocalStorage; } catch (e) { return false; } @@ -1234,7 +1239,12 @@ var AuthenticationContext = (function () { AuthenticationContext.prototype._supportsSessionStorage = function () { try { - return 'sessionStorage' in window && window['sessionStorage']; + var supportsSessionStorage = 'sessionStorage' in window && window['sessionStorage']; + if (supportsSessionStorage) { + window.sessionStorage.setItem('storageTest', ''); + window.sessionStorage.removeItem('storageTest'); + } + return supportsSessionStorage; } catch (e) { return false; } diff --git a/tests/unit/spec/AdalSpec.js b/tests/unit/spec/AdalSpec.js index ffec224c..e94ea224 100644 --- a/tests/unit/spec/AdalSpec.js +++ b/tests/unit/spec/AdalSpec.js @@ -86,6 +86,11 @@ describe('Adal', function () { store[key] = value; } }, + removeItem: function (key) { + if (typeof store[key] != 'undefined') { + delete store[key]; + } + }, clear: function () { store = {}; }, From 0070f311d57c3649591d4718848fb1582d2c0eb8 Mon Sep 17 00:00:00 2001 From: Rohit Narula Date: Wed, 19 Oct 2016 18:13:28 -0700 Subject: [PATCH 12/14] adding api doc for adaljs --- lib/adal.js | 291 ++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 238 insertions(+), 53 deletions(-) diff --git a/lib/adal.js b/lib/adal.js index b8532cea..3011e2a4 100644 --- a/lib/adal.js +++ b/lib/adal.js @@ -22,29 +22,29 @@ var AuthenticationContext = (function () { 'use strict'; /** - * Config information - * @public - * @class Config - * @property {tenant} Your target tenant - * @property {clientId} Identifier assigned to your app by Azure Active Directory - * @property {redirectUri} Endpoint at which you expect to receive tokens - * @property {instance} Azure Active Directory Instance(default:https://login.microsoftonline.com/) - * @property {endpoints} Collection of {Endpoint-ResourceId} used for autmatically attaching tokens in webApi calls - */ - - /** - * User information from idtoken. - * @class User - * @property {string} userName - username assigned from upn or email. - * @property {object} profile - properties parsed from idtoken. + * Configuration options for Authentication Context. + * @class config + * @property {string} tenant - Your target tenant. + * @property {string} clientID - Client ID assigned to your app by Azure Active Directory. + * @property {string} redirectUri - Endpoint at which you expect to receive tokens.Defaults to `window.location.href`. + * @property {string} instance - Azure Active Directory Instance.Defaults to `https://login.microsoftonline.com/`. + * @property {Array} endpoints - Collection of {Endpoint-ResourceId} used for automatically attaching tokens in webApi calls. + * @property {Boolean} popUp - Set this to true to enable login in a popup winodow instead of a full redirect.Defaults to `false`. + * @property {string} localLoginUrl - Set this to redirect the user to a custom login page. + * @property {function} displayCall - User defined function of handling the navigation to Azure AD authorization endpoint in case of login. Defaults to 'null'. + * @property {string} postLogoutRedirectUri - Redirects the user to postLogoutRedirectUri after logout. Defaults to 'null'. + * @property {string} cacheLocation - Sets browser storage to either 'localStorage' or sessionStorage'. Defaults to 'sessionStorage'. + * @property {Array.} anonymousEndpoints Array of keywords or URI's. Adal will not attach a token to outgoing requests that have these keywords or uri. Defaults to 'null'. + * @property {number} expireOffsetSeconds If the cached token is about to be expired in the expireOffsetSeconds (in seconds), Adal will renew the token instead of using the cached token. Defaults to 120 seconds. + * @property {string} correlationId Unique identifier used to map the request with the response. Defaults to RFC4122 version 4 guid (128 bits). */ /** * Creates a new AuthenticationContext object. * @constructor - * @param {object} config Configuration options for AuthenticationContext - * - **/ + * @param {config} config Configuration options for AuthenticationContext + */ + AuthenticationContext = function (config) { /** * Enum for request type @@ -169,8 +169,7 @@ var AuthenticationContext = (function () { }; /** - * Gets initial Idtoken for the app backend - * Saves the resulting Idtoken in localStorage. + * Initiates the login process by redirecting the user to Azure AD authorization endpoint. */ AuthenticationContext.prototype.login = function () { // Token is not present and user needs to login @@ -202,6 +201,10 @@ var AuthenticationContext = (function () { } }; + /** + * Configures popup window for login. + * @ignore + */ AuthenticationContext.prototype._openPopup = function (urlNavigate, title, popUpWidth, popUpHeight) { try { /** @@ -231,6 +234,11 @@ var AuthenticationContext = (function () { } } + /** + * After authorization, the user will be sent to your specified redirect_uri with the user's bearer token + * attached to the URI fragment as an id_token field. It closes popup window after redirection. + * @ignore + */ AuthenticationContext.prototype._loginPopup = function (urlNavigate) { var popupWindow = this._openPopup(urlNavigate, "login", this.CONSTANTS.POPUP_WIDTH, this.CONSTANTS.POPUP_HEIGHT); if (popupWindow == null) { @@ -270,19 +278,28 @@ var AuthenticationContext = (function () { }, 20); }; + /** + * Checks if login is in progress. + * @returns {Boolean} true if login is in progress, false otherwise. + */ AuthenticationContext.prototype.loginInProgress = function () { return this._loginInProgress; }; + /** + * Checks for the resource in the cache. By default, cache location is Session Storage + * @ignore + * @returns {Boolean} 'true' if login is in progress, else returns 'false'. + */ AuthenticationContext.prototype._hasResource = function (key) { var keys = this._getItem(this.CONSTANTS.STORAGE.TOKEN_KEYS); return keys && !this._isEmpty(keys) && (keys.indexOf(key + this.CONSTANTS.RESOURCE_DELIMETER) > -1); }; /** - * Gets token for the specified resource from local storage cache - * @param {string} resource A URI that identifies the resource for which the token is valid. - * @returns {string} token if exists and not expired or null + * Gets token for the specified resource from the cache. + * @param {string} resource A URI that identifies the resource for which the token is requested. + * @returns {string} token if if it exists and not expired, otherwise null. */ AuthenticationContext.prototype.getCachedToken = function (resource) { if (!this._hasResource(resource)) { @@ -303,8 +320,16 @@ var AuthenticationContext = (function () { return null; } }; + + /** + * User information from idtoken. + * @class User + * @property {string} userName - username assigned from upn or email. + * @property {object} profile - properties parsed from idtoken. + */ + /** - * Retrieves and parse idToken from localstorage + * If user object exists, returns it. Else creates a new user object by decoding id_token from the cache. * @returns {User} user object */ AuthenticationContext.prototype.getCachedUser = function () { @@ -316,7 +341,13 @@ var AuthenticationContext = (function () { this._user = this._createUser(idtoken); return this._user; }; - + + /** + * Adds the passed callback to the array of callbacks for the specified resource and puts the array on the window object. + * @param {string} resource A URI that identifies the resource for which the token is requested. + * @param {string} expectedState A unique identifier (guid). + * @param {tokenCallback} callback - The callback provided by the caller. It will be called with token or error. + */ AuthenticationContext.prototype.registerCallback = function (expectedState, resource, callback) { this._activeRenewals[resource] = expectedState; if (!window.callBacksMappedToRenewStates[expectedState]) { @@ -347,8 +378,7 @@ var AuthenticationContext = (function () { // with callback /** * Acquires access token with hidden iframe - * @param {string} resource ResourceUri identifying the target resource - * @returns {string} access token if request is successful + * @ignore */ AuthenticationContext.prototype._renewToken = function (resource, callback) { // use iframe to try refresh token @@ -371,6 +401,10 @@ var AuthenticationContext = (function () { }; + /** + * Renews idtoken for app's own backend when resource is clientId and calls the callback with token/error + * @ignore + */ AuthenticationContext.prototype._renewIdToken = function (callback) { // use iframe to try refresh token this.info('renewIdToken is called'); @@ -394,6 +428,10 @@ var AuthenticationContext = (function () { this._loadFrameTimeout(urlNavigate, 'adalIdTokenFrame', this.config.clientId); }; + /** + * Checks if the authorization endpoint URL contains query string parameters + * @ignore + */ AuthenticationContext.prototype._urlContainsQueryStringParameter = function (name, url) { // regex to detect pattern of a ? or & followed by the name parameter and an equals character var regex = new RegExp("[\\?&]" + name + "="); @@ -402,6 +440,9 @@ var AuthenticationContext = (function () { // Calling _loadFrame but with a timeout to signal failure in loadframeStatus. Callbacks are left // registered when network errors occur and subsequent token requests for same resource are registered to the pending request + /** + * @ignore + */ AuthenticationContext.prototype._loadFrameTimeout = function (urlNavigation, frameName, resource) { //set iframe session to pending this.verbose('Set loading state to pending for: ' + resource); @@ -422,6 +463,10 @@ var AuthenticationContext = (function () { }, self.CONSTANTS.LOADFRAME_TIMEOUT); } + /** + * Loads iframe with authorization endpoint URL + * @ignore + */ AuthenticationContext.prototype._loadFrame = function (urlNavigate, frameName) { // This trick overcomes iframe navigation in IE // IE does not load the page consistently in iframe @@ -438,9 +483,15 @@ var AuthenticationContext = (function () { }; /** - * Acquire token from cache if not expired and available. Acquires token from iframe if expired. + * @callback tokenCallback + * @param {string} error error message returned from AAD if token request fails. + * @param {string} token token returned from AAD if token request is successful. + */ + + /** + * Acquires token from the cache if it is not expired. Otherwise sends request to AAD to obtain a new token. * @param {string} resource ResourceUri identifying the target resource - * @param {requestCallback} callback + * @param {tokenCallback} callback - The callback provided by the caller. It will be called with token or error. */ AuthenticationContext.prototype.acquireToken = function (resource, callback) { if (this._isEmpty(resource)) { @@ -481,8 +532,8 @@ var AuthenticationContext = (function () { }; /** - * Redirect the Browser to Azure AD Authorization endpoint - * @param {string} urlNavigate The authorization request url + * Redirects the browser to Azure AD authorization endpoint. + * @param {string} urlNavigate Url of the authorization endpoint. */ AuthenticationContext.prototype.promptUser = function (urlNavigate) { if (urlNavigate) { @@ -494,7 +545,7 @@ var AuthenticationContext = (function () { }; /** - * Clear cache items. + * Clears cache items. */ AuthenticationContext.prototype.clearCache = function () { this._saveItem(this.CONSTANTS.STORAGE.ACCESS_TOKEN_KEY, ''); @@ -519,7 +570,8 @@ var AuthenticationContext = (function () { }; /** - * Clear cache items for a resource. + * Clears cache items for a given resource. + * @param {string} resource a URI that identifies the resource. */ AuthenticationContext.prototype.clearCacheForResource = function (resource) { this._saveItem(this.CONSTANTS.STORAGE.STATE_RENEW, ''); @@ -532,8 +584,8 @@ var AuthenticationContext = (function () { }; /** - * Logout user will redirect page to logout endpoint. - * After logout, it will redirect to post_logout page if provided. + * Redirects user to logout endpoint. + * After logout, it will redirect to postLogoutRedirectUri if added as a property on the config object. */ AuthenticationContext.prototype.logOut = function () { this.clearCache(); @@ -566,15 +618,14 @@ var AuthenticationContext = (function () { }; /** - * This callback is displayed as part of the Requester class. - * @callback requestCallback - * @param {string} error - * @param {User} user + * @callback userCallback + * @param {string} error error message if user info is not available. + * @param {User} user user object retrieved from the cache. */ /** - * Gets a user profile - * @param {requestCallback} cb - The callback that handles the response. + * Calls the passed in callback with the user object or error message related to the user. + * @param {userCallback} callback - The callback provided by the caller. It will be called with user or error. */ AuthenticationContext.prototype.getUser = function (callback) { // IDToken is first call @@ -600,6 +651,11 @@ var AuthenticationContext = (function () { } }; + /** + * Adds login_hint to authorization URL which is used to pre-fill the username field of sign in page for the user if known ahead of time. + * domain_hint can be one of users/organisations which when added skips the email based discovery process of the user. + * @ignore + */ AuthenticationContext.prototype._addHintParameters = function (urlNavigate) { // include hint params only if upn is present if (this._user && this._user.profile && this._user.profile.hasOwnProperty('upn')) { @@ -618,6 +674,10 @@ var AuthenticationContext = (function () { return urlNavigate; } + /** + * Creates a user object by decoding the id_token + * @ignore + */ AuthenticationContext.prototype._createUser = function (idToken) { var user = null; var parsedJson = this._extractIdToken(idToken); @@ -643,6 +703,10 @@ var AuthenticationContext = (function () { return user; }; + /** + * Returns the anchor part(#) of the URL + * @ignore + */ AuthenticationContext.prototype._getHash = function (hash) { if (hash.indexOf('#/') > -1) { hash = hash.substring(hash.indexOf('#/') + 2); @@ -654,9 +718,9 @@ var AuthenticationContext = (function () { }; /** - * Checks if hash contains access token or id token or error_description + * Checks if the URL fragment contains access token, id token or error_description. * @param {string} hash - Hash passed from redirect page - * @returns {Boolean} + * @returns {Boolean} true if response contains id_token, access_token or error, false otherwise. */ AuthenticationContext.prototype.isCallback = function (hash) { hash = this._getHash(hash); @@ -670,15 +734,25 @@ var AuthenticationContext = (function () { /** * Gets login error - * @returns {string} error message related to login + * @returns {string} error message related to login. */ AuthenticationContext.prototype.getLoginError = function () { return this._getItem(this.CONSTANTS.STORAGE.LOGIN_ERROR); }; /** - * Gets requestInfo from given hash. - * @returns {string} error message related to login + * Request info object created from the response received from AAD. + * @class RequestInfo + * @property {object} parameters - object comprising of fields such as id_token/error, session_state, state, e.t.c. + * @property {REQUEST_TYPE} requestType - either LOGIN, RENEW_TOKEN or UNKNOWN. + * @property {boolean} stateMatch - true if state is valid, false otherwise. + * @property {string} stateResponse - unique guid used to match the response with the request. + * @property {boolean} valid - true if requestType contains id_token, access_token or error, false otherwise. + */ + + /** + * Creates a requestInfo object from the URL fragment and returns it. + * @returns {RequestInfo} an object created from the redirect response from AAD comprising of the keys - parameters, requestType, stateMatch, stateResponse and valid. */ AuthenticationContext.prototype.getRequestInfo = function (hash) { hash = this._getHash(hash); @@ -735,6 +809,10 @@ var AuthenticationContext = (function () { return requestInfo; }; + /** + * Extracts resource value from state. + * @ignore + */ AuthenticationContext.prototype._getResourceFromState = function (state) { if (state) { var splitIndex = state.indexOf('|'); @@ -747,9 +825,7 @@ var AuthenticationContext = (function () { }; /** - * Saves token from hash that is received from redirect. - * @param {string} hash - Hash passed from redirect page - * @returns {string} error message related to login + * Saves token or error received in the response from AAD in the cache. In case of id_token, it also creates the user object. */ AuthenticationContext.prototype.saveTokenFromHash = function (requestInfo) { this.info('State status:' + requestInfo.stateMatch + '; Request type:' + requestInfo.requestType); @@ -830,8 +906,8 @@ var AuthenticationContext = (function () { /** * Gets resource for given endpoint if mapping is provided with config. - * @param {string} endpoint - API endoibt - * @returns {string} resource for this API endpoint + * @param {string} endpoint - The URI for which the resource Id is requested. + * @returns {string} resource for this API endpoint. */ AuthenticationContext.prototype.getResourceForEndpoint = function (endpoint) { if (this.config && this.config.endpoints) { @@ -870,6 +946,10 @@ var AuthenticationContext = (function () { return null; }; + /** + * Strips the protocol part of the URL and returns it. + * @ignore + */ AuthenticationContext.prototype._getHostFromUri = function (uri) { // remove http:// or https:// from uri var extractedUri = String(uri).replace(/^(https?:)\/\//, ''); @@ -878,7 +958,10 @@ var AuthenticationContext = (function () { return extractedUri; }; - /*exported oauth2Callback */ + /** + * This method must be called for processing the response received from AAD. It extracts the hash, processes the token or error, saves it in the cache and calls the registered callbacks with the result. + * @param {string} [hash=window.location.hash] - Hash fragment of Url. + */ AuthenticationContext.prototype.handleWindowCallback = function (hash) { // This is for regular javascript usage for redirect handling // need to make sure this is for callback @@ -906,6 +989,10 @@ var AuthenticationContext = (function () { } }; + /** + * Constructs the authorization endpoint URL and returns it. + * @ignore + */ AuthenticationContext.prototype._getNavigateUrl = function (responseType, resource) { var tenant = 'common'; if (this.config.tenant) { @@ -917,6 +1004,10 @@ var AuthenticationContext = (function () { return urlNavigate; }; + /** + * Returns the decoded id_token. + * @ignore + */ AuthenticationContext.prototype._extractIdToken = function (encodedIdToken) { // id token will be decoded to get the username var decodedToken = this._decodeJwt(encodedIdToken); @@ -941,6 +1032,10 @@ var AuthenticationContext = (function () { return null; }; + /** + * Decodes a string of data which has been encoded using base-64 encoding. + * @ignore + */ AuthenticationContext.prototype._base64DecodeStringUrlSafe = function (base64IdToken) { // html5 should support atob function for decoding base64IdToken = base64IdToken.replace(/-/g, '+').replace(/_/g, '/'); @@ -1000,6 +1095,10 @@ var AuthenticationContext = (function () { return decoded; }; + /** + * Decodes an id token into an object with header, payload and signature fields. + * @ignore + */ // Adal.node js crack function AuthenticationContext.prototype._decodeJwt = function (jwtToken) { if (this._isEmpty(jwtToken)) { @@ -1023,10 +1122,18 @@ var AuthenticationContext = (function () { return crackedToken; }; + /** + * Converts string to represent binary data in ASCII string format by translating it into a radix-64 representation and returns it + * @ignore + */ AuthenticationContext.prototype._convertUrlSafeToRegularBase64EncodedString = function (str) { return str.replace('-', '+').replace('_', '/'); }; + /** + * Serializes the parameters for the authorization endpoint URL and returns the serialized uri string. + * @ignore + */ AuthenticationContext.prototype._serialize = function (responseType, obj, resource) { var str = []; if (obj !== null) { @@ -1054,6 +1161,10 @@ var AuthenticationContext = (function () { return str.join('&'); }; + /** + * Parses the query string parameters into a key-value pair object. + * @ignore + */ AuthenticationContext.prototype._deserialize = function (query) { var match, pl = /\+/g, // Regex for replacing addition symbol with a space @@ -1071,6 +1182,10 @@ var AuthenticationContext = (function () { return obj; }; + /** + * Converts decimal value to hex equivalent + * @ignore + */ AuthenticationContext.prototype._decimalToHex = function (number) { var hex = number.toString(16); while (hex.length < 2) { @@ -1079,6 +1194,10 @@ var AuthenticationContext = (function () { return hex; } + /** + * Generates RFC4122 version 4 guid (128 bits) + * @ignore + */ /* jshint ignore:start */ AuthenticationContext.prototype._guid = function () { // RFC4122: The version 4 UUID is meant for generating UUIDs from truly-random or @@ -1140,15 +1259,26 @@ var AuthenticationContext = (function () { }; /* jshint ignore:end */ + /** + * Calculates the expires in value in milliseconds for the acquired token + * @ignore + */ AuthenticationContext.prototype._expiresIn = function (expires) { return this._now() + parseInt(expires, 10); }; + /** + * Return the number of milliseconds since 1970/01/01 + * @ignore + */ AuthenticationContext.prototype._now = function () { return Math.round(new Date().getTime() / 1000.0); }; - + /** + * Adds the hidden iframe for silent token renewal + * @ignore + */ AuthenticationContext.prototype._addAdalFrame = function (iframeId) { if (typeof iframeId === 'undefined') { return; @@ -1179,6 +1309,10 @@ var AuthenticationContext = (function () { return adalFrame; }; + /** + * Saves the key-value pair in the cache + * @ignore + */ AuthenticationContext.prototype._saveItem = function (key, obj) { if (this.config && this.config.cacheLocation && this.config.cacheLocation === 'localStorage') { @@ -1203,6 +1337,10 @@ var AuthenticationContext = (function () { return true; }; + /** + * Searches the value for the given key in the cache + * @ignore + */ AuthenticationContext.prototype._getItem = function (key) { if (this.config && this.config.cacheLocation && this.config.cacheLocation === 'localStorage') { @@ -1224,6 +1362,10 @@ var AuthenticationContext = (function () { return sessionStorage.getItem(key); }; + /** + * Returns true if browser supports localStorage, false otherwise. + * @ignore + */ AuthenticationContext.prototype._supportsLocalStorage = function () { try { var supportsLocalStorage = 'localStorage' in window && window['localStorage']; @@ -1237,6 +1379,10 @@ var AuthenticationContext = (function () { } }; + /** + * Returns true if browser supports sessionStorage, false otherwise. + * @ignore + */ AuthenticationContext.prototype._supportsSessionStorage = function () { try { var supportsSessionStorage = 'sessionStorage' in window && window['sessionStorage']; @@ -1250,6 +1396,10 @@ var AuthenticationContext = (function () { } }; + /** + * Returns a cloned copy of the passed object. + * @ignore + */ AuthenticationContext.prototype._cloneConfig = function (obj) { if (null === obj || 'object' !== typeof obj) { return obj; @@ -1264,12 +1414,22 @@ var AuthenticationContext = (function () { return copy; }; + /** + * Adds the library version and returns it. + * @ignore + */ AuthenticationContext.prototype._addLibMetadata = function () { // x-client-SKU // x-client-Ver return '&x-client-SKU=Js&x-client-Ver=' + this._libVersion(); }; + /** + * Checks the Logging Level, constructs the Log message and logs it. Users need to implement/override this method to turn on Logging. + * @param {number} level - Level can be set 0,1,2 and 3 which turns on 'error', 'warning', 'info' or 'verbose' level logging respectively. + * @param {string} message - Message to log. + * @param {string} error - Error to log. + */ AuthenticationContext.prototype.log = function (level, message, error) { if (level <= Logging.level) { var timestamp = new Date().toUTCString(); @@ -1288,26 +1448,51 @@ var AuthenticationContext = (function () { } }; + /** + * Logs messages when Logging Level is set to 0. + * @param {string} message - Message to log. + * @param {string} error - Error to log. + */ AuthenticationContext.prototype.error = function (message, error) { this.log(this.CONSTANTS.LOGGING_LEVEL.ERROR, message, error); }; + /** + * Logs messages when Logging Level is set to 1. + * @param {string} message - Message to log. + */ AuthenticationContext.prototype.warn = function (message) { this.log(this.CONSTANTS.LOGGING_LEVEL.WARN, message, null); }; + /** + * Logs messages when Logging Level is set to 2. + * @param {string} message - Message to log. + */ AuthenticationContext.prototype.info = function (message) { this.log(this.CONSTANTS.LOGGING_LEVEL.INFO, message, null); }; + /** + * Logs messages when Logging Level is set to 3. + * @param {string} message - Message to log. + */ AuthenticationContext.prototype.verbose = function (message) { this.log(this.CONSTANTS.LOGGING_LEVEL.VERBOSE, message, null); }; + /** + * Returns the library version. + * @ignore + */ AuthenticationContext.prototype._libVersion = function () { return '1.0.12'; }; + /** + * Returns a reference of Authentication Context as a result of a require call. + * @ignore + */ if (typeof module !== 'undefined' && module.exports) { module.exports = AuthenticationContext; module.exports.inject = function (conf) { From f1bee28abd86924efb980753da17da795619cbda Mon Sep 17 00:00:00 2001 From: Tushar Gupta Date: Tue, 8 Nov 2016 15:29:14 -0800 Subject: [PATCH 13/14] update version to 1.0.13 --- README.md | 10 +++++----- bower.json | 2 +- changelog.txt | 7 +++++++ lib/adal-angular.js | 2 +- lib/adal.js | 4 ++-- package.json | 8 ++++---- 6 files changed, 20 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 40c4ad91..a4425eab 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Active Directory Authentication Library for JavaScript (ADAL JS) helps you to us This library is optimized for working together with AngularJS. ## Versions -Current version - 1.0.12 +Current version - 1.0.13 Minimum recommended version - 1.0.11 You can find the changes for each version in the [change log](https://github.com/AzureAD/azure-activedirectory-library-for-js/blob/master/changelog.txt). @@ -26,7 +26,7 @@ If you find a security issue with our libraries or services please report it to ## The Library -This is a GA released version. The current version is **1.0.12**. +This is a GA released version. The current version is **1.0.13**. You have multiple ways of getting ADAL JS: @@ -37,10 +37,10 @@ Via NPM: Via CDN: - - + + -CDN will be updated to latest version 1.0.12. +CDN will be updated to latest version 1.0.13. Via Bower: diff --git a/bower.json b/bower.json index 3263b857..874f4bd7 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "adal-angular", - "version": "1.0.12", + "version": "1.0.13", "homepage": "https://github.com/AzureAD/azure-activedirectory-library-for-js", "authors": [ "MSOpentech" diff --git a/changelog.txt b/changelog.txt index 454fc9f4..95392ffd 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,10 @@ +Version 1.0.13 +========================= +* Pass 'error' parameter to the callback besides 'error_description': #424 +* Adding API documentation of adal.js +* Adding 'acquireTokenSuccess' and 'acquireTokenFailure' events in adal-angular.js +* Other bug fixes and updates + Version 1.0.12 ========================== * Adding support for Login using a pop-up instead of a full redirect. Please see this: https://github.com/AzureAD/azure-activedirectory-library-for-js/issues/60 diff --git a/lib/adal-angular.js b/lib/adal-angular.js index e164687f..956941ce 100644 --- a/lib/adal-angular.js +++ b/lib/adal-angular.js @@ -1,5 +1,5 @@ //---------------------------------------------------------------------- -// AdalJS v1.0.12 +// AdalJS v1.0.13 // @preserve Copyright (c) Microsoft Open Technologies, Inc. // All Rights Reserved // Apache License 2.0 diff --git a/lib/adal.js b/lib/adal.js index 3011e2a4..45bc0cd1 100644 --- a/lib/adal.js +++ b/lib/adal.js @@ -1,5 +1,5 @@ //---------------------------------------------------------------------- -// AdalJS v1.0.12 +// AdalJS v1.0.13 // @preserve Copyright (c) Microsoft Open Technologies, Inc. // All Rights Reserved // Apache License 2.0 @@ -1486,7 +1486,7 @@ var AuthenticationContext = (function () { * @ignore */ AuthenticationContext.prototype._libVersion = function () { - return '1.0.12'; + return '1.0.13'; }; /** diff --git a/package.json b/package.json index 7e61824e..55aca42c 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "type": "git", "url": "https://github.com/AzureAD/azure-activedirectory-library-for-js.git" }, - "version": "1.0.12", + "version": "1.0.13", "description": "Windows Azure Active Directory Client Library for js", "keywords": [ "implicit", @@ -39,9 +39,9 @@ "grunt-contrib-watch": "~0.2.0", "grunt-karma": "^0.9.x", "atob": "~1.1.2", - "karma-chrome-launcher": "~0.1.5", - "karma": "~0.12.24", - "karma-jasmine": "~0.1.5", + "karma-chrome-launcher": "^0.1.5", + "karma": "^0.12.24", + "karma-jasmine": "^0.1.5", "bower": "^1.3.3", "grunt-jasmine-node": "~0.2.1" }, From fd183cf0849b331c631550c7207b3d3ad727ffed Mon Sep 17 00:00:00 2001 From: Tushar Gupta Date: Tue, 8 Nov 2016 15:36:46 -0800 Subject: [PATCH 14/14] update the minified files --- dist/adal-angular.min.js | 4 ++-- dist/adal.min.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/dist/adal-angular.min.js b/dist/adal-angular.min.js index 0886f8a8..66cf4c52 100644 --- a/dist/adal-angular.min.js +++ b/dist/adal-angular.min.js @@ -1,2 +1,2 @@ -/*! adal-angular v1.0.12 2016-08-31 */ -!function(){"use strict";if("undefined"!=typeof module&&module.exports&&(module.exports.inject=function(a){return new AuthenticationContext(a)}),angular){var a=angular.module("AdalAngular",[]);a.provider("adalAuthenticationService",function(){var a=null,b={isAuthenticated:!1,userName:"",loginError:"",profile:""},c=function(c){var d=a.getCachedToken(c);b.isAuthenticated=null!==d&&d.length>0;var e=a.getCachedUser()||{userName:""};b.userName=e.userName,b.profile=e.profile,b.loginError=a.getLoginError()};this.init=function(b,d){if(!b)throw new Error("You must set configOptions, when calling init");var e=window.location.hash,f=window.location.href;e&&(f=f.replace(e,"")),b.redirectUri=b.redirectUri||f,b.postLogoutRedirectUri=b.postLogoutRedirectUri||f,b.isAngular=!0,d&&d.interceptors&&d.interceptors.push("ProtectedResourceInterceptor"),a=new AuthenticationContext(b),c(a.config.loginResource)},this.$get=["$rootScope","$window","$q","$location","$timeout","$injector",function(d,e,f,g,h,i){function j(a,b){return b.requireADLogin?a.requireADLogin!==!1:!!a.requireADLogin}function k(b){if(a.config&&a.config.anonymousEndpoints)for(var c=0;c-1)return!0;return!1}function l(a){var b=null,c=[];if(a.hasOwnProperty("parent"))for(b=a;b;)c.unshift(b),b=i.get("$state").get(b.parent);else for(var d=a.name.split("."),e=0,f=d[0];e-1&&g.url(n.substring(n.indexOf("#")+1)),e.location=n)}}else d.$broadcast("adal:stateMismatch",a._getItem(a.CONSTANTS.STORAGE.ERROR_DESCRIPTION))}else c(a.config.loginResource),b.isAuthenticated||!b.userName||a._renewActive||(a._renewActive=!0,a.acquireToken(a.config.loginResource,function(c,e){a._renewActive=!1,c?d.$broadcast("adal:loginFailure","auto renew failure"):e&&(b.isAuthenticated=!0)}));h(function(){c(a.config.loginResource),d.userInfo=b},1)},n=function(){a.info("Login event for:"+g.$$url),a.config&&a.config.localLoginUrl?g.path(a.config.localLoginUrl):(a.info("Start login at:"+window.location.href),d.$broadcast("adal:loginRedirect"),a.login())},o=function(c,d){if(d&&d.$$route)if(j(d.$$route,a.config))b.isAuthenticated||a._renewActive||a.loginInProgress()||(a.info("Route change event for:"+g.$$url),n());else{var e;e="function"==typeof d.$$route.templateUrl?d.$$route.templateUrl(d.params):d.$$route.templateUrl,e&&!k(e)&&a.config.anonymousEndpoints.push(e)}},p=function(c,d,e,f,h){if(d)for(var i=l(d),m=null,o=0;o0;var e=a.getCachedUser()||{userName:""};b.userName=e.userName,b.profile=e.profile,b.loginError=a.getLoginError()};this.init=function(b,d){if(!b)throw new Error("You must set configOptions, when calling init");var e=window.location.hash,f=window.location.href;e&&(f=f.replace(e,"")),b.redirectUri=b.redirectUri||f,b.postLogoutRedirectUri=b.postLogoutRedirectUri||f,b.isAngular=!0,d&&d.interceptors&&d.interceptors.push("ProtectedResourceInterceptor"),a=new AuthenticationContext(b),c(a.config.loginResource)},this.$get=["$rootScope","$window","$q","$location","$timeout","$injector",function(d,e,f,g,h,i){function j(a,b){return b.requireADLogin?a.requireADLogin!==!1:!!a.requireADLogin}function k(b){if(a.config&&a.config.anonymousEndpoints)for(var c=0;c-1)return!0;return!1}function l(a){var b=null,c=[];if(a.hasOwnProperty("parent"))for(b=a;b;)c.unshift(b),b=i.get("$state").get(b.parent);else for(var d=a.name.split("."),e=0,f=d[0];e-1&&g.url(o.substring(o.indexOf("#")+1)),e.location=o)}}else d.$broadcast("adal:stateMismatch",a._getItem(a.CONSTANTS.STORAGE.ERROR_DESCRIPTION),a._getItem(a.CONSTANTS.STORAGE.ERROR))}else if(c(a.config.loginResource),!b.isAuthenticated&&b.userName&&!a._renewActive){var p=i.get("adalAuthenticationService");p.acquireToken(a.config.loginResource).then(function(a){a&&(b.isAuthenticated=!0)},function(a){var b=a.split("|");d.$broadcast("adal:loginFailure",b[0],b[1])})}h(function(){c(a.config.loginResource),d.userInfo=b},1)},n=function(){a.info("Login event for:"+g.$$url),a.config&&a.config.localLoginUrl?g.path(a.config.localLoginUrl):(a.info("Start login at:"+window.location.href),d.$broadcast("adal:loginRedirect"),a.login())},o=function(c,d){if(d&&d.$$route)if(j(d.$$route,a.config))b.isAuthenticated||a._renewActive||a.loginInProgress()||(a.info("Route change event for:"+g.$$url),n());else{var e;e="function"==typeof d.$$route.templateUrl?d.$$route.templateUrl(d.params):d.$$route.templateUrl,e&&!k(e)&&a.config.anonymousEndpoints.push(e)}},p=function(c,d,e,f,h){if(d)for(var i=l(d),m=null,o=0;o-1},AuthenticationContext.prototype.getCachedToken=function(a){if(!this._hasResource(a))return null;var b=this._getItem(this.CONSTANTS.STORAGE.ACCESS_TOKEN_KEY+a),c=this._getItem(this.CONSTANTS.STORAGE.EXPIRATION_KEY+a),d=this.config.expireOffsetSeconds||120;return c&&c>this._now()+d?b:(this._saveItem(this.CONSTANTS.STORAGE.ACCESS_TOKEN_KEY+a,""),this._saveItem(this.CONSTANTS.STORAGE.EXPIRATION_KEY+a,0),null)},AuthenticationContext.prototype.getCachedUser=function(){if(this._user)return this._user;var a=this._getItem(this.CONSTANTS.STORAGE.IDTOKEN);return this._user=this._createUser(a),this._user},AuthenticationContext.prototype.registerCallback=function(a,b,c){this._activeRenewals[b]=a,window.callBacksMappedToRenewStates[a]||(window.callBacksMappedToRenewStates[a]=[]);var d=this;window.callBacksMappedToRenewStates[a].push(c),window.callBackMappedToRenewStates[a]||(window.callBackMappedToRenewStates[a]=function(c,e){for(var f=0;f-1)){var b=this._user.profile.upn.split("@");a+="&domain_hint="+encodeURIComponent(b[b.length-1])}return a},AuthenticationContext.prototype._createUser=function(a){var b=null,c=this._extractIdToken(a);return c&&c.hasOwnProperty("aud")&&(c.aud.toLowerCase()===this.config.clientId.toLowerCase()?(b={userName:"",profile:c},c.hasOwnProperty("upn")?b.userName=c.upn:c.hasOwnProperty("email")&&(b.userName=c.email)):this.warn("IdToken has invalid aud field")),b},AuthenticationContext.prototype._getHash=function(a){return a.indexOf("#/")>-1?a=a.substring(a.indexOf("#/")+2):a.indexOf("#")>-1&&(a=a.substring(1)),a},AuthenticationContext.prototype.isCallback=function(a){a=this._getHash(a);var b=this._deserialize(a);return b.hasOwnProperty(this.CONSTANTS.ERROR_DESCRIPTION)||b.hasOwnProperty(this.CONSTANTS.ACCESS_TOKEN)||b.hasOwnProperty(this.CONSTANTS.ID_TOKEN)},AuthenticationContext.prototype.getLoginError=function(){return this._getItem(this.CONSTANTS.STORAGE.LOGIN_ERROR)},AuthenticationContext.prototype.getRequestInfo=function(a){a=this._getHash(a);var b=this._deserialize(a),c={valid:!1,parameters:{},stateMatch:!1,stateResponse:"",requestType:this.REQUEST_TYPE.UNKNOWN};if(b&&(c.parameters=b,b.hasOwnProperty(this.CONSTANTS.ERROR_DESCRIPTION)||b.hasOwnProperty(this.CONSTANTS.ACCESS_TOKEN)||b.hasOwnProperty(this.CONSTANTS.ID_TOKEN))){c.valid=!0;var d="";if(!b.hasOwnProperty("state"))return this.warn("No state returned"),c;if(this.verbose("State: "+b.state),d=b.state,c.stateResponse=d,d===this._getItem(this.CONSTANTS.STORAGE.STATE_LOGIN))return c.requestType=this.REQUEST_TYPE.LOGIN,c.stateMatch=!0,c;if(!c.stateMatch&&window.parent&&window.parent.AuthenticationContext())for(var e=window.parent.AuthenticationContext()._renewStates,f=0;f-1&&b+1-1)return this.config.endpoints[b];if(!(a.indexOf("http://")>-1||a.indexOf("https://")>-1)){if(this.config&&this.config.anonymousEndpoints)for(var c=0;c-1)return null;return this.config.loginResource}return this._getHostFromUri(a)===this._getHostFromUri(this.config.redirectUri)?this.config.loginResource:null},AuthenticationContext.prototype._getHostFromUri=function(a){var b=String(a).replace(/^(https?:)\/\//,"");return b=b.split("/")[0]},AuthenticationContext.prototype.handleWindowCallback=function(a){if(null==a&&(a=window.location.hash),this.isCallback(a)){var b=this.getRequestInfo(a);this.info("Returned from redirect url"),this.saveTokenFromHash(b);var c=null;if(b.requestType===this.REQUEST_TYPE.RENEW_TOKEN&&window.parent&&window.parent!==window)return this.verbose("Window is in iframe"),c=window.parent.callBackMappedToRenewStates[b.stateResponse],void(c&&c(this._getItem(this.CONSTANTS.STORAGE.ERROR_DESCRIPTION),b.parameters[this.CONSTANTS.ACCESS_TOKEN]||b.parameters[this.CONSTANTS.ID_TOKEN]));b.requestType===this.REQUEST_TYPE.LOGIN&&(c=this.callback,c&&c(this._getItem(this.CONSTANTS.STORAGE.ERROR_DESCRIPTION),b.parameters[this.CONSTANTS.ID_TOKEN])),this.popUp||(window.location=this._getItem(this.CONSTANTS.STORAGE.LOGIN_REQUEST))}},AuthenticationContext.prototype._getNavigateUrl=function(a,b){var c="common";this.config.tenant&&(c=this.config.tenant);var d=this.instance+c+"/oauth2/authorize"+this._serialize(a,this.config,b)+this._addLibMetadata();return this.info("Navigate url:"+d),d},AuthenticationContext.prototype._extractIdToken=function(a){var b=this._decodeJwt(a);if(!b)return null;try{var c=b.JWSPayload,d=this._base64DecodeStringUrlSafe(c);return d?JSON.parse(d):(this.info("The returned id_token could not be base64 url safe decoded."),null)}catch(a){this.error("The returned id_token could not be decoded",a)}return null},AuthenticationContext.prototype._base64DecodeStringUrlSafe=function(a){return a=a.replace(/-/g,"+").replace(/_/g,"/"),window.atob?decodeURIComponent(escape(window.atob(a))):decodeURIComponent(escape(this._decode(a)))},AuthenticationContext.prototype._decode=function(a){var b="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";a=String(a).replace(/=+$/,"");var c=a.length;if(c%4===1)throw new Error("The token to be decoded is not correctly encoded.");for(var d,e,f,g,h,i,j,k,l="",m=0;m>16&255,j=h>>8&255,l+=String.fromCharCode(i,j);break}if(m+1===c-1){h=d<<18|e<<12,i=h>>16&255,l+=String.fromCharCode(i);break}h=d<<18|e<<12|f<<6|g,i=h>>16&255,j=h>>8&255,k=255&h,l+=String.fromCharCode(i,j,k)}return l},AuthenticationContext.prototype._decodeJwt=function(a){if(this._isEmpty(a))return null;var b=/^([^\.\s]*)\.([^\.\s]+)\.([^\.\s]*)$/,c=b.exec(a);if(!c||c.length<4)return this.warn("The returned id_token is not parseable."),null;var d={header:c[1],JWSPayload:c[2],JWSSig:c[3]};return d},AuthenticationContext.prototype._convertUrlSafeToRegularBase64EncodedString=function(a){return a.replace("-","+").replace("_","/")},AuthenticationContext.prototype._serialize=function(a,b,c){var d=[];if(null!==b){d.push("?response_type="+a),d.push("client_id="+encodeURIComponent(b.clientId)),c&&d.push("resource="+encodeURIComponent(c)),d.push("redirect_uri="+encodeURIComponent(b.redirectUri)),d.push("state="+encodeURIComponent(b.state)),b.hasOwnProperty("slice")&&d.push("slice="+encodeURIComponent(b.slice)),b.hasOwnProperty("extraQueryParameter")&&d.push(b.extraQueryParameter);var e=b.correlationId?b.correlationId:this._guid();d.push("client-request-id="+encodeURIComponent(e))}return d.join("&")},AuthenticationContext.prototype._deserialize=function(a){var b,c=/\+/g,d=/([^&=]+)=([^&]*)/g,e=function(a){return decodeURIComponent(a.replace(c," "))},f={};for(b=d.exec(a);b;)f[e(b[1])]=e(b[2]),b=d.exec(a);return f},AuthenticationContext.prototype._guid=function(){function a(a){for(var b=a.toString(16);b.length<2;)b="0"+b;return b}var b=window.crypto||window.msCrypto;if(b&&b.getRandomValues){var c=new Uint8Array(16);return b.getRandomValues(c),c[6]|=64,c[6]&=79,c[8]|=128,c[8]&=191,a(c[0])+a(c[1])+a(c[2])+a(c[3])+"-"+a(c[4])+a(c[5])+"-"+a(c[6])+a(c[7])+"-"+a(c[8])+a(c[9])+"-"+a(c[10])+a(c[11])+a(c[12])+a(c[13])+a(c[14])+a(c[15])}for(var d="xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx",e="0123456789abcdef",f=0,g="",h=0;h<36;h++)"-"!==d[h]&&"4"!==d[h]&&(f=16*Math.random()|0),"x"===d[h]?g+=e[f]:"y"===d[h]?(f&=3,f|=8,g+=e[f]):g+=d[h];return g},AuthenticationContext.prototype._expiresIn=function(a){return this._now()+parseInt(a,10)},AuthenticationContext.prototype._now=function(){return Math.round((new Date).getTime()/1e3)},AuthenticationContext.prototype._addAdalFrame=function(a){if("undefined"!=typeof a){this.info("Add adal frame to document:"+a);var b=document.getElementById(a);if(!b){if(document.createElement&&document.documentElement&&(window.opera||window.navigator.userAgent.indexOf("MSIE 5.0")===-1)){var c=document.createElement("iframe");c.setAttribute("id",a),c.style.visibility="hidden",c.style.position="absolute",c.style.width=c.style.height=c.borderWidth="0px",b=document.getElementsByTagName("body")[0].appendChild(c)}else document.body&&document.body.insertAdjacentHTML&&document.body.insertAdjacentHTML("beforeEnd",'');window.frames&&window.frames[a]&&(b=window.frames[a])}return b}},AuthenticationContext.prototype._saveItem=function(a,b){return this.config&&this.config.cacheLocation&&"localStorage"===this.config.cacheLocation?this._supportsLocalStorage()?(localStorage.setItem(a,b),!0):(this.info("Local storage is not supported"),!1):this._supportsSessionStorage()?(sessionStorage.setItem(a,b),!0):(this.info("Session storage is not supported"),!1)},AuthenticationContext.prototype._getItem=function(a){return this.config&&this.config.cacheLocation&&"localStorage"===this.config.cacheLocation?this._supportsLocalStorage()?localStorage.getItem(a):(this.info("Local storage is not supported"),null):this._supportsSessionStorage()?sessionStorage.getItem(a):(this.info("Session storage is not supported"),null)},AuthenticationContext.prototype._supportsLocalStorage=function(){try{return"localStorage"in window&&window.localStorage}catch(a){return!1}},AuthenticationContext.prototype._supportsSessionStorage=function(){try{return"sessionStorage"in window&&window.sessionStorage}catch(a){return!1}},AuthenticationContext.prototype._cloneConfig=function(a){if(null===a||"object"!=typeof a)return a;var b={};for(var c in a)a.hasOwnProperty(c)&&(b[c]=a[c]);return b},AuthenticationContext.prototype._addLibMetadata=function(){return"&x-client-SKU=Js&x-client-Ver="+this._libVersion()},AuthenticationContext.prototype.log=function(a,b,c){if(a<=Logging.level){var d=(new Date).toUTCString(),e="";e=this.config.correlationId?d+":"+this.config.correlationId+"-"+this._libVersion()+"-"+this.CONSTANTS.LEVEL_STRING_MAP[a]+" "+b:d+":"+this._libVersion()+"-"+this.CONSTANTS.LEVEL_STRING_MAP[a]+" "+b,c&&(e+="\nstack:\n"+c.stack),Logging.log(e)}},AuthenticationContext.prototype.error=function(a,b){this.log(this.CONSTANTS.LOGGING_LEVEL.ERROR,a,b)},AuthenticationContext.prototype.warn=function(a){this.log(this.CONSTANTS.LOGGING_LEVEL.WARN,a,null)},AuthenticationContext.prototype.info=function(a){this.log(this.CONSTANTS.LOGGING_LEVEL.INFO,a,null)},AuthenticationContext.prototype.verbose=function(a){this.log(this.CONSTANTS.LOGGING_LEVEL.VERBOSE,a,null)},AuthenticationContext.prototype._libVersion=function(){return"1.0.12"},"undefined"!=typeof module&&module.exports&&(module.exports=AuthenticationContext,module.exports.inject=function(a){return new AuthenticationContext(a)}),AuthenticationContext}(); \ No newline at end of file +/*! adal-angular v1.0.13 2016-11-08 */ +var AuthenticationContext=function(){"use strict";return AuthenticationContext=function(a){if(this.REQUEST_TYPE={LOGIN:"LOGIN",RENEW_TOKEN:"RENEW_TOKEN",UNKNOWN:"UNKNOWN"},this.CONSTANTS={ACCESS_TOKEN:"access_token",EXPIRES_IN:"expires_in",ID_TOKEN:"id_token",ERROR_DESCRIPTION:"error_description",SESSION_STATE:"session_state",STORAGE:{TOKEN_KEYS:"adal.token.keys",ACCESS_TOKEN_KEY:"adal.access.token.key",EXPIRATION_KEY:"adal.expiration.key",STATE_LOGIN:"adal.state.login",STATE_RENEW:"adal.state.renew",NONCE_IDTOKEN:"adal.nonce.idtoken",SESSION_STATE:"adal.session.state",USERNAME:"adal.username",IDTOKEN:"adal.idtoken",ERROR:"adal.error",ERROR_DESCRIPTION:"adal.error.description",LOGIN_REQUEST:"adal.login.request",LOGIN_ERROR:"adal.login.error",RENEW_STATUS:"adal.token.renew.status"},RESOURCE_DELIMETER:"|",LOADFRAME_TIMEOUT:"6000",TOKEN_RENEW_STATUS_CANCELED:"Canceled",TOKEN_RENEW_STATUS_COMPLETED:"Completed",TOKEN_RENEW_STATUS_IN_PROGRESS:"In Progress",LOGGING_LEVEL:{ERROR:0,WARN:1,INFO:2,VERBOSE:3},LEVEL_STRING_MAP:{0:"ERROR:",1:"WARNING:",2:"INFO:",3:"VERBOSE:"},POPUP_WIDTH:483,POPUP_HEIGHT:600},AuthenticationContext.prototype._singletonInstance)return AuthenticationContext.prototype._singletonInstance;if(AuthenticationContext.prototype._singletonInstance=this,this.instance="https://login.microsoftonline.com/",this.config={},this.callback=null,this.popUp=!1,this.isAngular=!1,this._user=null,this._activeRenewals={},this._loginInProgress=!1,this._renewStates=[],window.callBackMappedToRenewStates={},window.callBacksMappedToRenewStates={},a.displayCall&&"function"!=typeof a.displayCall)throw new Error("displayCall is not a function");if(!a.clientId)throw new Error("clientId is required");this.config=this._cloneConfig(a),this.config.popUp&&(this.popUp=!0),this.config.callback&&"function"==typeof this.config.callback&&(this.callback=this.config.callback),this.config.instance&&(this.instance=this.config.instance),this.config.loginResource||(this.config.loginResource=this.config.clientId),this.config.redirectUri||(this.config.redirectUri=window.location.href),this.config.anonymousEndpoints||(this.config.anonymousEndpoints=[]),this.config.isAngular&&(this.isAngular=this.config.isAngular)},window.Logging={level:0,log:function(a){}},AuthenticationContext.prototype.login=function(){if(this._loginInProgress)return void this.info("Login in progress");var a=this._guid();this.config.state=a,this._idTokenNonce=this._guid(),this.verbose("Expected state: "+a+" startPage:"+window.location),this._saveItem(this.CONSTANTS.STORAGE.LOGIN_REQUEST,window.location),this._saveItem(this.CONSTANTS.STORAGE.LOGIN_ERROR,""),this._saveItem(this.CONSTANTS.STORAGE.STATE_LOGIN,a),this._saveItem(this.CONSTANTS.STORAGE.NONCE_IDTOKEN,this._idTokenNonce),this._saveItem(this.CONSTANTS.STORAGE.ERROR,""),this._saveItem(this.CONSTANTS.STORAGE.ERROR_DESCRIPTION,"");var b=this._getNavigateUrl("id_token",null)+"&nonce="+encodeURIComponent(this._idTokenNonce);return this._loginInProgress=!0,this.popUp?void this._loginPopup(b):void(this.config.displayCall?this.config.displayCall(b):this.promptUser(b))},AuthenticationContext.prototype._openPopup=function(a,b,c,d){try{var e=window.screenLeft?window.screenLeft:window.screenX,f=window.screenTop?window.screenTop:window.screenY,g=window.innerWidth||document.documentElement.clientWidth||document.body.clientWidth,h=window.innerHeight||document.documentElement.clientHeight||document.body.clientHeight,i=g/2-c/2+e,j=h/2-d/2+f,k=window.open(a,b,"width="+c+", height="+d+", top="+j+", left="+i);return k.focus&&k.focus(),k}catch(l){return this.warn("Error opening popup, "+l.message),this._loginInProgress=!1,null}},AuthenticationContext.prototype._loginPopup=function(a){var b=this._openPopup(a,"login",this.CONSTANTS.POPUP_WIDTH,this.CONSTANTS.POPUP_HEIGHT);if(null==b)return this.warn("Popup Window is null. This can happen if you are using IE"),this._saveItem(this.CONSTANTS.STORAGE.ERROR,"Error opening popup"),this._saveItem(this.CONSTANTS.STORAGE.ERROR_DESCRIPTION,"Popup Window is null. This can happen if you are using IE"),this._saveItem(this.CONSTANTS.STORAGE.LOGIN_ERROR,"Popup Window is null. This can happen if you are using IE"),void(this.callback&&this.callback(this._getItem(this.CONSTANTS.STORAGE.LOGIN_ERROR),null,this._getItem(this.CONSTANTS.STORAGE.ERROR)));if(this.config.redirectUri.indexOf("#")!=-1)var c=this.config.redirectUri.split("#")[0];else var c=this.config.redirectUri;var d=this,e=window.setInterval(function(){b&&!b.closed&&void 0!==b.closed||(d._loginInProgress=!1,window.clearInterval(e));try{b.location.href.indexOf(c)!=-1&&(d.isAngular?window.location.hash=b.location.hash:d.handleWindowCallback(b.location.hash),window.clearInterval(e),d._loginInProgress=!1,d.info("Closing popup window"),b.close())}catch(a){}},20)},AuthenticationContext.prototype.loginInProgress=function(){return this._loginInProgress},AuthenticationContext.prototype._hasResource=function(a){var b=this._getItem(this.CONSTANTS.STORAGE.TOKEN_KEYS);return b&&!this._isEmpty(b)&&b.indexOf(a+this.CONSTANTS.RESOURCE_DELIMETER)>-1},AuthenticationContext.prototype.getCachedToken=function(a){if(!this._hasResource(a))return null;var b=this._getItem(this.CONSTANTS.STORAGE.ACCESS_TOKEN_KEY+a),c=this._getItem(this.CONSTANTS.STORAGE.EXPIRATION_KEY+a),d=this.config.expireOffsetSeconds||120;return c&&c>this._now()+d?b:(this._saveItem(this.CONSTANTS.STORAGE.ACCESS_TOKEN_KEY+a,""),this._saveItem(this.CONSTANTS.STORAGE.EXPIRATION_KEY+a,0),null)},AuthenticationContext.prototype.getCachedUser=function(){if(this._user)return this._user;var a=this._getItem(this.CONSTANTS.STORAGE.IDTOKEN);return this._user=this._createUser(a),this._user},AuthenticationContext.prototype.registerCallback=function(a,b,c){this._activeRenewals[b]=a,window.callBacksMappedToRenewStates[a]||(window.callBacksMappedToRenewStates[a]=[]);var d=this;window.callBacksMappedToRenewStates[a].push(c),window.callBackMappedToRenewStates[a]||(window.callBackMappedToRenewStates[a]=function(c,e,f){for(var g=0;g-1)){var b=this._user.profile.upn.split("@");a+="&domain_hint="+encodeURIComponent(b[b.length-1])}return a},AuthenticationContext.prototype._createUser=function(a){var b=null,c=this._extractIdToken(a);return c&&c.hasOwnProperty("aud")&&(c.aud.toLowerCase()===this.config.clientId.toLowerCase()?(b={userName:"",profile:c},c.hasOwnProperty("upn")?b.userName=c.upn:c.hasOwnProperty("email")&&(b.userName=c.email)):this.warn("IdToken has invalid aud field")),b},AuthenticationContext.prototype._getHash=function(a){return a.indexOf("#/")>-1?a=a.substring(a.indexOf("#/")+2):a.indexOf("#")>-1&&(a=a.substring(1)),a},AuthenticationContext.prototype.isCallback=function(a){a=this._getHash(a);var b=this._deserialize(a);return b.hasOwnProperty(this.CONSTANTS.ERROR_DESCRIPTION)||b.hasOwnProperty(this.CONSTANTS.ACCESS_TOKEN)||b.hasOwnProperty(this.CONSTANTS.ID_TOKEN)},AuthenticationContext.prototype.getLoginError=function(){return this._getItem(this.CONSTANTS.STORAGE.LOGIN_ERROR)},AuthenticationContext.prototype.getRequestInfo=function(a){a=this._getHash(a);var b=this._deserialize(a),c={valid:!1,parameters:{},stateMatch:!1,stateResponse:"",requestType:this.REQUEST_TYPE.UNKNOWN};if(b&&(c.parameters=b,b.hasOwnProperty(this.CONSTANTS.ERROR_DESCRIPTION)||b.hasOwnProperty(this.CONSTANTS.ACCESS_TOKEN)||b.hasOwnProperty(this.CONSTANTS.ID_TOKEN))){c.valid=!0;var d="";if(!b.hasOwnProperty("state"))return this.warn("No state returned"),c;if(this.verbose("State: "+b.state),d=b.state,c.stateResponse=d,d===this._getItem(this.CONSTANTS.STORAGE.STATE_LOGIN))return c.requestType=this.REQUEST_TYPE.LOGIN,c.stateMatch=!0,c;if(!c.stateMatch&&window.parent&&window.parent.AuthenticationContext)for(var e=window.parent.AuthenticationContext()._renewStates,f=0;f-1&&b+1-1)return this.config.endpoints[b];if(!(a.indexOf("http://")>-1||a.indexOf("https://")>-1)){if(this.config&&this.config.anonymousEndpoints)for(var c=0;c-1)return null;return this.config.loginResource}return this._getHostFromUri(a)===this._getHostFromUri(this.config.redirectUri)?this.config.loginResource:null},AuthenticationContext.prototype._getHostFromUri=function(a){var b=String(a).replace(/^(https?:)\/\//,"");return b=b.split("/")[0]},AuthenticationContext.prototype.handleWindowCallback=function(a){if(null==a&&(a=window.location.hash),this.isCallback(a)){var b=this.getRequestInfo(a);this.info("Returned from redirect url"),this.saveTokenFromHash(b);var c=null;if(b.requestType===this.REQUEST_TYPE.RENEW_TOKEN&&window.parent&&window.parent!==window)return this.verbose("Window is in iframe"),c=window.parent.callBackMappedToRenewStates[b.stateResponse],void(c&&c(this._getItem(this.CONSTANTS.STORAGE.ERROR_DESCRIPTION),b.parameters[this.CONSTANTS.ACCESS_TOKEN]||b.parameters[this.CONSTANTS.ID_TOKEN],this._getItem(this.CONSTANTS.STORAGE.ERROR)));b.requestType===this.REQUEST_TYPE.LOGIN&&(c=this.callback,c&&c(this._getItem(this.CONSTANTS.STORAGE.ERROR_DESCRIPTION),b.parameters[this.CONSTANTS.ID_TOKEN],this._getItem(this.CONSTANTS.STORAGE.ERROR))),this.popUp||(window.location=this._getItem(this.CONSTANTS.STORAGE.LOGIN_REQUEST))}},AuthenticationContext.prototype._getNavigateUrl=function(a,b){var c="common";this.config.tenant&&(c=this.config.tenant);var d=this.instance+c+"/oauth2/authorize"+this._serialize(a,this.config,b)+this._addLibMetadata();return this.info("Navigate url:"+d),d},AuthenticationContext.prototype._extractIdToken=function(a){var b=this._decodeJwt(a);if(!b)return null;try{var c=b.JWSPayload,d=this._base64DecodeStringUrlSafe(c);return d?JSON.parse(d):(this.info("The returned id_token could not be base64 url safe decoded."),null)}catch(e){this.error("The returned id_token could not be decoded",e)}return null},AuthenticationContext.prototype._base64DecodeStringUrlSafe=function(a){return a=a.replace(/-/g,"+").replace(/_/g,"/"),window.atob?decodeURIComponent(escape(window.atob(a))):decodeURIComponent(escape(this._decode(a)))},AuthenticationContext.prototype._decode=function(a){var b="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";a=String(a).replace(/=+$/,"");var c=a.length;if(c%4===1)throw new Error("The token to be decoded is not correctly encoded.");for(var d,e,f,g,h,i,j,k,l="",m=0;m>16&255,j=h>>8&255,l+=String.fromCharCode(i,j);break}if(m+1===c-1){h=d<<18|e<<12,i=h>>16&255,l+=String.fromCharCode(i);break}h=d<<18|e<<12|f<<6|g,i=h>>16&255,j=h>>8&255,k=255&h,l+=String.fromCharCode(i,j,k)}return l},AuthenticationContext.prototype._decodeJwt=function(a){if(this._isEmpty(a))return null;var b=/^([^\.\s]*)\.([^\.\s]+)\.([^\.\s]*)$/,c=b.exec(a);if(!c||c.length<4)return this.warn("The returned id_token is not parseable."),null;var d={header:c[1],JWSPayload:c[2],JWSSig:c[3]};return d},AuthenticationContext.prototype._convertUrlSafeToRegularBase64EncodedString=function(a){return a.replace("-","+").replace("_","/")},AuthenticationContext.prototype._serialize=function(a,b,c){var d=[];if(null!==b){d.push("?response_type="+a),d.push("client_id="+encodeURIComponent(b.clientId)),c&&d.push("resource="+encodeURIComponent(c)),d.push("redirect_uri="+encodeURIComponent(b.redirectUri)),d.push("state="+encodeURIComponent(b.state)),b.hasOwnProperty("slice")&&d.push("slice="+encodeURIComponent(b.slice)),b.hasOwnProperty("extraQueryParameter")&&d.push(b.extraQueryParameter);var e=b.correlationId?b.correlationId:this._guid();d.push("client-request-id="+encodeURIComponent(e))}return d.join("&")},AuthenticationContext.prototype._deserialize=function(a){var b,c=/\+/g,d=/([^&=]+)=([^&]*)/g,e=function(a){return decodeURIComponent(a.replace(c," "))},f={};for(b=d.exec(a);b;)f[e(b[1])]=e(b[2]),b=d.exec(a);return f},AuthenticationContext.prototype._decimalToHex=function(a){for(var b=a.toString(16);b.length<2;)b="0"+b;return b},AuthenticationContext.prototype._guid=function(){var a=window.crypto||window.msCrypto;if(a&&a.getRandomValues){var b=new Uint8Array(16);return a.getRandomValues(b),b[6]|=64,b[6]&=79,b[8]|=128,b[8]&=191,this._decimalToHex(b[0])+this._decimalToHex(b[1])+this._decimalToHex(b[2])+this._decimalToHex(b[3])+"-"+this._decimalToHex(b[4])+this._decimalToHex(b[5])+"-"+this._decimalToHex(b[6])+this._decimalToHex(b[7])+"-"+this._decimalToHex(b[8])+this._decimalToHex(b[9])+"-"+this._decimalToHex(b[10])+this._decimalToHex(b[11])+this._decimalToHex(b[12])+this._decimalToHex(b[13])+this._decimalToHex(b[14])+this._decimalToHex(b[15])}for(var c="xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx",d="0123456789abcdef",e=0,f="",g=0;g<36;g++)"-"!==c[g]&&"4"!==c[g]&&(e=16*Math.random()|0),"x"===c[g]?f+=d[e]:"y"===c[g]?(e&=3,e|=8,f+=d[e]):f+=c[g];return f},AuthenticationContext.prototype._expiresIn=function(a){return this._now()+parseInt(a,10)},AuthenticationContext.prototype._now=function(){return Math.round((new Date).getTime()/1e3)},AuthenticationContext.prototype._addAdalFrame=function(a){if("undefined"!=typeof a){this.info("Add adal frame to document:"+a);var b=document.getElementById(a);if(!b){if(document.createElement&&document.documentElement&&(window.opera||window.navigator.userAgent.indexOf("MSIE 5.0")===-1)){var c=document.createElement("iframe");c.setAttribute("id",a),c.style.visibility="hidden",c.style.position="absolute",c.style.width=c.style.height=c.borderWidth="0px",b=document.getElementsByTagName("body")[0].appendChild(c)}else document.body&&document.body.insertAdjacentHTML&&document.body.insertAdjacentHTML("beforeEnd",'');window.frames&&window.frames[a]&&(b=window.frames[a])}return b}},AuthenticationContext.prototype._saveItem=function(a,b){return this.config&&this.config.cacheLocation&&"localStorage"===this.config.cacheLocation?this._supportsLocalStorage()?(localStorage.setItem(a,b),!0):(this.info("Local storage is not supported"),!1):this._supportsSessionStorage()?(sessionStorage.setItem(a,b),!0):(this.info("Session storage is not supported"),!1)},AuthenticationContext.prototype._getItem=function(a){return this.config&&this.config.cacheLocation&&"localStorage"===this.config.cacheLocation?this._supportsLocalStorage()?localStorage.getItem(a):(this.info("Local storage is not supported"),null):this._supportsSessionStorage()?sessionStorage.getItem(a):(this.info("Session storage is not supported"),null)},AuthenticationContext.prototype._supportsLocalStorage=function(){try{var a="localStorage"in window&&window.localStorage;return a&&(window.localStorage.setItem("storageTest",""),window.localStorage.removeItem("storageTest")),a}catch(b){return!1}},AuthenticationContext.prototype._supportsSessionStorage=function(){try{var a="sessionStorage"in window&&window.sessionStorage;return a&&(window.sessionStorage.setItem("storageTest",""),window.sessionStorage.removeItem("storageTest")),a}catch(b){return!1}},AuthenticationContext.prototype._cloneConfig=function(a){if(null===a||"object"!=typeof a)return a;var b={};for(var c in a)a.hasOwnProperty(c)&&(b[c]=a[c]);return b},AuthenticationContext.prototype._addLibMetadata=function(){return"&x-client-SKU=Js&x-client-Ver="+this._libVersion()},AuthenticationContext.prototype.log=function(a,b,c){if(a<=Logging.level){var d=(new Date).toUTCString(),e="";e=this.config.correlationId?d+":"+this.config.correlationId+"-"+this._libVersion()+"-"+this.CONSTANTS.LEVEL_STRING_MAP[a]+" "+b:d+":"+this._libVersion()+"-"+this.CONSTANTS.LEVEL_STRING_MAP[a]+" "+b,c&&(e+="\nstack:\n"+c.stack),Logging.log(e)}},AuthenticationContext.prototype.error=function(a,b){this.log(this.CONSTANTS.LOGGING_LEVEL.ERROR,a,b)},AuthenticationContext.prototype.warn=function(a){this.log(this.CONSTANTS.LOGGING_LEVEL.WARN,a,null)},AuthenticationContext.prototype.info=function(a){this.log(this.CONSTANTS.LOGGING_LEVEL.INFO,a,null)},AuthenticationContext.prototype.verbose=function(a){this.log(this.CONSTANTS.LOGGING_LEVEL.VERBOSE,a,null)},AuthenticationContext.prototype._libVersion=function(){return"1.0.13"},"undefined"!=typeof module&&module.exports&&(module.exports=AuthenticationContext,module.exports.inject=function(a){return new AuthenticationContext(a)}),AuthenticationContext}(); \ No newline at end of file