From 9179cdbf45f658d5696c77761d85b5f56058be23 Mon Sep 17 00:00:00 2001 From: Magnus Fjell Date: Fri, 20 Jan 2017 11:05:00 +0100 Subject: [PATCH 01/19] Rename variable for clarity Make it more clear that the variable is the expiration time, and not if it is expired or not. --- lib/adal.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/adal.js b/lib/adal.js index 45bc0cd1..af44e0de 100644 --- a/lib/adal.js +++ b/lib/adal.js @@ -307,12 +307,12 @@ var AuthenticationContext = (function () { } var token = this._getItem(this.CONSTANTS.STORAGE.ACCESS_TOKEN_KEY + resource); - var expired = this._getItem(this.CONSTANTS.STORAGE.EXPIRATION_KEY + resource); + var expiry = this._getItem(this.CONSTANTS.STORAGE.EXPIRATION_KEY + resource); // If expiration is within offset, it will force renew var offset = this.config.expireOffsetSeconds || 120; - if (expired && (expired > this._now() + offset)) { + if (expiry && (expiry > this._now() + offset)) { return token; } else { this._saveItem(this.CONSTANTS.STORAGE.ACCESS_TOKEN_KEY + resource, ''); From 30cf32211482c817bc4f7609c060eb2d36ab00d6 Mon Sep 17 00:00:00 2001 From: Rohit Narula Date: Tue, 31 Jan 2017 13:59:58 -0800 Subject: [PATCH 02/19] Fixed issue 474 regarding silent exception in callback method --- lib/adal-angular.js | 2 +- lib/adal.js | 21 +++++++++-------- tests/testApp.js | 46 ++++++++++++++++++------------------- tests/unit/spec/AdalSpec.js | 39 ++++++++++++++++++++++++++++--- 4 files changed, 72 insertions(+), 36 deletions(-) diff --git a/lib/adal-angular.js b/lib/adal-angular.js index 956941ce..64c10c9d 100644 --- a/lib/adal-angular.js +++ b/lib/adal-angular.js @@ -133,7 +133,7 @@ if (!$location.$$html5 && loginStartPage.indexOf('#') > -1) { $location.url(loginStartPage.substring(loginStartPage.indexOf('#') + 1)); } - $window.location = loginStartPage; + $window.location.href = loginStartPage; } } else diff --git a/lib/adal.js b/lib/adal.js index 45bc0cd1..d2e86c72 100644 --- a/lib/adal.js +++ b/lib/adal.js @@ -180,8 +180,8 @@ var AuthenticationContext = (function () { var expectedState = this._guid(); this.config.state = expectedState; this._idTokenNonce = this._guid(); - this.verbose('Expected state: ' + expectedState + ' startPage:' + window.location); - this._saveItem(this.CONSTANTS.STORAGE.LOGIN_REQUEST, window.location); + this.verbose('Expected state: ' + expectedState + ' startPage:' + window.location.href); + this._saveItem(this.CONSTANTS.STORAGE.LOGIN_REQUEST, window.location.href); this._saveItem(this.CONSTANTS.STORAGE.LOGIN_ERROR, ''); this._saveItem(this.CONSTANTS.STORAGE.STATE_LOGIN, expectedState); this._saveItem(this.CONSTANTS.STORAGE.NONCE_IDTOKEN, this._idTokenNonce); @@ -341,7 +341,7 @@ 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. @@ -971,21 +971,24 @@ var AuthenticationContext = (function () { var requestInfo = this.getRequestInfo(hash); this.info('Returned from redirect url'); this.saveTokenFromHash(requestInfo); - var callback = null; + var token = null, callback = null; if ((requestInfo.requestType === this.REQUEST_TYPE.RENEW_TOKEN) && window.parent && (window.parent !== window)) { // iframe call but same single page 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], this._getItem(this.CONSTANTS.STORAGE.ERROR)); - return; + token = requestInfo.parameters[this.CONSTANTS.ACCESS_TOKEN] || requestInfo.parameters[this.CONSTANTS.ID_TOKEN]; } else if (requestInfo.requestType === this.REQUEST_TYPE.LOGIN) { callback = this.callback; + token = requestInfo.parameters[this.CONSTANTS.ID_TOKEN]; + } + try { if (callback) - callback(this._getItem(this.CONSTANTS.STORAGE.ERROR_DESCRIPTION), requestInfo.parameters[this.CONSTANTS.ID_TOKEN], this._getItem(this.CONSTANTS.STORAGE.ERROR)); + callback(this._getItem(this.CONSTANTS.STORAGE.ERROR_DESCRIPTION), token, this._getItem(this.CONSTANTS.STORAGE.ERROR)); + } catch (err) { + this.error('Error occurred in user defined callback function', err) } if (!this.popUp)// No need to redirect user in case of popup - window.location = this._getItem(this.CONSTANTS.STORAGE.LOGIN_REQUEST); + window.location.href = this._getItem(this.CONSTANTS.STORAGE.LOGIN_REQUEST); } }; diff --git a/tests/testApp.js b/tests/testApp.js index 0180a0a9..78321555 100644 --- a/tests/testApp.js +++ b/tests/testApp.js @@ -37,8 +37,8 @@ app.config(['$httpProvider', '$routeProvider', 'adalAuthenticationServiceProvide requireADLogin: true }). when('/login', { - controller: 'loginController', - templateUrl: 'login.html', + controller: 'loginController', + templateUrl: 'login.html', }). otherwise({ redirectTo: '/home' }); @@ -87,63 +87,63 @@ app.factory('TaskFactory', ['$http', function ($http) { app.controller('TaskCtl', ['$scope', '$location', 'adalAuthenticationService', 'TaskFactory', 'ItemFactory', function ($scope, $location, adalAuthenticationService, TaskFactory, ItemFactory) { $scope.taskCall = function () { - TaskFactory.getItem(5).success(function (data) { - $scope.task = data; - }).error(function (err) { + TaskFactory.getItem(5).then(function (response) { + $scope.task = response.data; + },function (err) { $scope.error = err; $scope.loadingMsg = ""; }); } $scope.itemCall = function () { - ItemFactory.getItem(13).success(function (data) { - $scope.item = data; - }).error(function (err) { + ItemFactory.getItem(13).then(function (response) { + $scope.item = response.data; + },function (err) { $scope.error = err; $scope.loadingMsg = ""; }); } $scope.taskCall2 = function () { - TaskFactory.getItem2('https://test.com/').success(function (data) { - $scope.task = data; - }).error(function (err) { + TaskFactory.getItem2('https://test.com/').then(function (response) { + $scope.task = response.data; + },function (err) { $scope.error = err; $scope.loadingMsg = ""; }); } $scope.taskCall3 = function () { - TaskFactory.getItem2('https://testapi.com/').success(function (data) { - $scope.task = data; - }).error(function (err) { + TaskFactory.getItem2('https://testapi.com/').then(function (response) { + $scope.task = response.data; + },function (err) { $scope.error = err; $scope.loadingMsg = ""; }); } $scope.taskCall4 = function () { - TaskFactory.getItem2('/someapi/item').success(function (data) { - $scope.task = data; - }).error(function (err) { + TaskFactory.getItem2('/someapi/item').then(function (response) { + $scope.task = response.data; + },function (err) { $scope.error = err; $scope.loadingMsg = ""; }); } $scope.taskCall5 = function () { - TaskFactory.getItem2('https://myapp.com/someapi/item').success(function (data) { - $scope.task = data; - }).error(function (err) { + TaskFactory.getItem2('https://myapp.com/someapi/item').then(function (response) { + $scope.task = response.data; + },function (err) { $scope.error = err; $scope.loadingMsg = ""; }); } $scope.taskCall6 = function () { - TaskFactory.getItem2('http://testwebapi.com/').success(function (data) { - $scope.task = data; - }).error(function (err) { + TaskFactory.getItem2('http://testwebapi.com/').then(function (response) { + $scope.task = response; + }, function (err) { $scope.error = err; $scope.loaingMsg = ""; }); diff --git a/tests/unit/spec/AdalSpec.js b/tests/unit/spec/AdalSpec.js index e94ea224..4610fb74 100644 --- a/tests/unit/spec/AdalSpec.js +++ b/tests/unit/spec/AdalSpec.js @@ -773,6 +773,7 @@ describe('Adal', function () { it('tests handleWindowCallback function for LOGIN_REQUEST', function () { window.location = {}; window.location.hash = '#/id_token=' + IDTOKEN_MOCK; + window.location.href = 'www.test.com' + '#/id_token=' + IDTOKEN_MOCK; var _getRequestInfo = adal.getRequestInfo; adal.getRequestInfo = function () { return { @@ -780,13 +781,13 @@ describe('Adal', function () { parameters: { 'error_description': 'error description', 'error': 'invalid', 'id_token': IDTOKEN_MOCK, 'session_state': '61ae5247-eaf8-4496-a667-32b0acbad7a0', 'state': '19537a2a-e9e7-489d-ae7d-3eefab9e4137' }, stateMatch: true, stateResponse: '19537a2a-e9e7-489d-ae7d-3eefab9e4137', - requestType: adal.REQUEST_TYPE.LOGIN_REQUEST + requestType: adal.REQUEST_TYPE.LOGIN }; }; storageFake.setItem(adal.CONSTANTS.STORAGE.LOGIN_REQUEST, "www.test.com"); window.oauth2Callback = {}; adal.handleWindowCallback(); - expect(window.location).toBe('www.test.com'); + expect(window.location.href).toBe('www.test.com'); adal.getRequestInfo = _getRequestInfo; }); @@ -896,6 +897,7 @@ describe('Adal', function () { expect(err).toBe('Error opening popup'); expect(token).toBe(null); expect(adal.loginInProgress()).toBe(false); + adal.popUp = false; }); it('tests login functionality in case of popup window', function () { @@ -946,7 +948,7 @@ describe('Adal', function () { expect(errDesc).toBe('Invalid id_token. id_token: ' + IDTOKEN_MOCK); expect(window.location.href).not.toBe('home page'); }); - + adal.popUp = false; }); it('ensures that adal.callback is not overridden in calls to getUser', function () { @@ -1000,4 +1002,35 @@ describe('Adal', function () { expect(token).toBe(undefined); expect(errDesc).toBe('some_description'); }); + + it('tests if error is logged and code flow is completed when there is a failure in the user defined callback function in case of login', function () { + window.location = {}; + window.location.href = 'www.test.com' + '#/id_token=' + IDTOKEN_MOCK; + window.location.hash = '#/id_token=' + IDTOKEN_MOCK; + var _getRequestInfo = adal.getRequestInfo; + Logging.level = 0; + Logging.log = function (message) { + window.logMessage = message; + } + adal.getRequestInfo = function () { + return { + valid: true, + parameters: { 'id_token': IDTOKEN_MOCK, 'session_state': '61ae5247-eaf8-4496-a667-32b0acbad7a0', 'state': '19537a2a-e9e7-489d-ae7d-3eefab9e4137' }, + stateMatch: true, + stateResponse: '19537a2a-e9e7-489d-ae7d-3eefab9e4137', + requestType: adal.REQUEST_TYPE.LOGIN + }; + }; + var callback = function () { + throw new Error("Error in callback function"); + } + adal.callback = callback; + storageFake.setItem(adal.CONSTANTS.STORAGE.LOGIN_REQUEST, 'www.test.com'); + adal.handleWindowCallback(); + expect(window.logMessage).toContain("Error occurred in user defined callback function"); + expect(window.location.href).toBe('www.test.com'); + adal.getRequestInfo = _getRequestInfo; + Logging.level = 2; + + }); }); From 8a690935f81182793cfadc1c04d451edeab841e3 Mon Sep 17 00:00:00 2001 From: Rohit Narula Date: Sun, 5 Feb 2017 12:55:48 -0800 Subject: [PATCH 03/19] call displayCall before any default behavior in login method --- lib/adal.js | 10 +++++----- tests/testApp.js | 46 +++++++++++++++++++++++----------------------- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/lib/adal.js b/lib/adal.js index 45bc0cd1..5f440aa0 100644 --- a/lib/adal.js +++ b/lib/adal.js @@ -189,14 +189,14 @@ var AuthenticationContext = (function () { this._saveItem(this.CONSTANTS.STORAGE.ERROR_DESCRIPTION, ''); var urlNavigate = this._getNavigateUrl('id_token', null) + '&nonce=' + encodeURIComponent(this._idTokenNonce); this._loginInProgress = true; - if (this.popUp) { - this._loginPopup(urlNavigate); - return; - } if (this.config.displayCall) { // User defined way of handling the navigation this.config.displayCall(urlNavigate); - } else { + } + else if (this.popUp) { + this._loginPopup(urlNavigate); + } + else { this.promptUser(urlNavigate); } }; diff --git a/tests/testApp.js b/tests/testApp.js index 0180a0a9..29217ff6 100644 --- a/tests/testApp.js +++ b/tests/testApp.js @@ -37,8 +37,8 @@ app.config(['$httpProvider', '$routeProvider', 'adalAuthenticationServiceProvide requireADLogin: true }). when('/login', { - controller: 'loginController', - templateUrl: 'login.html', + controller: 'loginController', + templateUrl: 'login.html', }). otherwise({ redirectTo: '/home' }); @@ -87,63 +87,63 @@ app.factory('TaskFactory', ['$http', function ($http) { app.controller('TaskCtl', ['$scope', '$location', 'adalAuthenticationService', 'TaskFactory', 'ItemFactory', function ($scope, $location, adalAuthenticationService, TaskFactory, ItemFactory) { $scope.taskCall = function () { - TaskFactory.getItem(5).success(function (data) { - $scope.task = data; - }).error(function (err) { + TaskFactory.getItem(5).then(function (response) { + $scope.task = response.data; + }, function (err) { $scope.error = err; $scope.loadingMsg = ""; }); } $scope.itemCall = function () { - ItemFactory.getItem(13).success(function (data) { - $scope.item = data; - }).error(function (err) { + ItemFactory.getItem(13).then(function (response) { + $scope.item = response.data; + }, function (err) { $scope.error = err; $scope.loadingMsg = ""; }); } $scope.taskCall2 = function () { - TaskFactory.getItem2('https://test.com/').success(function (data) { - $scope.task = data; - }).error(function (err) { + TaskFactory.getItem2('https://test.com/').then(function (response) { + $scope.task = response.data; + }, function (err) { $scope.error = err; $scope.loadingMsg = ""; }); } $scope.taskCall3 = function () { - TaskFactory.getItem2('https://testapi.com/').success(function (data) { - $scope.task = data; - }).error(function (err) { + TaskFactory.getItem2('https://testapi.com/').then(function (response) { + $scope.task = response.data; + }, function (err) { $scope.error = err; $scope.loadingMsg = ""; }); } $scope.taskCall4 = function () { - TaskFactory.getItem2('/someapi/item').success(function (data) { - $scope.task = data; - }).error(function (err) { + TaskFactory.getItem2('/someapi/item').then(function (response) { + $scope.task = response.data; + }, function (err) { $scope.error = err; $scope.loadingMsg = ""; }); } $scope.taskCall5 = function () { - TaskFactory.getItem2('https://myapp.com/someapi/item').success(function (data) { - $scope.task = data; - }).error(function (err) { + TaskFactory.getItem2('https://myapp.com/someapi/item').then(function (response) { + $scope.task = response.data; + }, function (err) { $scope.error = err; $scope.loadingMsg = ""; }); } $scope.taskCall6 = function () { - TaskFactory.getItem2('http://testwebapi.com/').success(function (data) { - $scope.task = data; - }).error(function (err) { + TaskFactory.getItem2('http://testwebapi.com/').then(function (response) { + $scope.task = response.data; + }, function (err) { $scope.error = err; $scope.loaingMsg = ""; }); From 181dca0a24fec830d50f5e80653e5af9a4e8cae3 Mon Sep 17 00:00:00 2001 From: Rohit Narula Date: Sun, 5 Feb 2017 13:29:48 -0800 Subject: [PATCH 04/19] fixed double error pipeline in Angular acquireToken --- lib/adal-angular.js | 4 ++-- tests/testApp.js | 46 ++++++++++++++++++++++----------------------- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/lib/adal-angular.js b/lib/adal-angular.js index 956941ce..be1ee3dd 100644 --- a/lib/adal-angular.js +++ b/lib/adal-angular.js @@ -411,8 +411,8 @@ authService.verbose('Token is available'); config.headers.Authorization = 'Bearer ' + token; delayedRequest.resolve(config); - }, function (errDesc, error) { - config.data = errDesc + "|" + error; + }, function (error) { + config.data = error; delayedRequest.reject(config); }); diff --git a/tests/testApp.js b/tests/testApp.js index 0180a0a9..29217ff6 100644 --- a/tests/testApp.js +++ b/tests/testApp.js @@ -37,8 +37,8 @@ app.config(['$httpProvider', '$routeProvider', 'adalAuthenticationServiceProvide requireADLogin: true }). when('/login', { - controller: 'loginController', - templateUrl: 'login.html', + controller: 'loginController', + templateUrl: 'login.html', }). otherwise({ redirectTo: '/home' }); @@ -87,63 +87,63 @@ app.factory('TaskFactory', ['$http', function ($http) { app.controller('TaskCtl', ['$scope', '$location', 'adalAuthenticationService', 'TaskFactory', 'ItemFactory', function ($scope, $location, adalAuthenticationService, TaskFactory, ItemFactory) { $scope.taskCall = function () { - TaskFactory.getItem(5).success(function (data) { - $scope.task = data; - }).error(function (err) { + TaskFactory.getItem(5).then(function (response) { + $scope.task = response.data; + }, function (err) { $scope.error = err; $scope.loadingMsg = ""; }); } $scope.itemCall = function () { - ItemFactory.getItem(13).success(function (data) { - $scope.item = data; - }).error(function (err) { + ItemFactory.getItem(13).then(function (response) { + $scope.item = response.data; + }, function (err) { $scope.error = err; $scope.loadingMsg = ""; }); } $scope.taskCall2 = function () { - TaskFactory.getItem2('https://test.com/').success(function (data) { - $scope.task = data; - }).error(function (err) { + TaskFactory.getItem2('https://test.com/').then(function (response) { + $scope.task = response.data; + }, function (err) { $scope.error = err; $scope.loadingMsg = ""; }); } $scope.taskCall3 = function () { - TaskFactory.getItem2('https://testapi.com/').success(function (data) { - $scope.task = data; - }).error(function (err) { + TaskFactory.getItem2('https://testapi.com/').then(function (response) { + $scope.task = response.data; + }, function (err) { $scope.error = err; $scope.loadingMsg = ""; }); } $scope.taskCall4 = function () { - TaskFactory.getItem2('/someapi/item').success(function (data) { - $scope.task = data; - }).error(function (err) { + TaskFactory.getItem2('/someapi/item').then(function (response) { + $scope.task = response.data; + }, function (err) { $scope.error = err; $scope.loadingMsg = ""; }); } $scope.taskCall5 = function () { - TaskFactory.getItem2('https://myapp.com/someapi/item').success(function (data) { - $scope.task = data; - }).error(function (err) { + TaskFactory.getItem2('https://myapp.com/someapi/item').then(function (response) { + $scope.task = response.data; + }, function (err) { $scope.error = err; $scope.loadingMsg = ""; }); } $scope.taskCall6 = function () { - TaskFactory.getItem2('http://testwebapi.com/').success(function (data) { - $scope.task = data; - }).error(function (err) { + TaskFactory.getItem2('http://testwebapi.com/').then(function (response) { + $scope.task = response.data; + }, function (err) { $scope.error = err; $scope.loaingMsg = ""; }); From 96c611551fbb62ab534abf4700f219e5faad1a11 Mon Sep 17 00:00:00 2001 From: Rohit Narula Date: Wed, 8 Feb 2017 14:21:32 -0800 Subject: [PATCH 05/19] added loginStartPage param to loginmethod --- lib/adal-angular.js | 4 ++-- lib/adal.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/adal-angular.js b/lib/adal-angular.js index f7c59fbd..22cae5d3 100644 --- a/lib/adal-angular.js +++ b/lib/adal-angular.js @@ -175,9 +175,9 @@ } else { // directly start login flow - _adal.info('Start login at:' + window.location.href); + _adal.info('Start login at:' + $location.$$absUrl); $rootScope.$broadcast('adal:loginRedirect'); - _adal.login(); + _adal.login($location.$$absUrl); } }; diff --git a/lib/adal.js b/lib/adal.js index d2e75fc2..f80c10e8 100644 --- a/lib/adal.js +++ b/lib/adal.js @@ -171,7 +171,7 @@ var AuthenticationContext = (function () { /** * Initiates the login process by redirecting the user to Azure AD authorization endpoint. */ - AuthenticationContext.prototype.login = function () { + AuthenticationContext.prototype.login = function (loginStartPage) { // Token is not present and user needs to login if (this._loginInProgress) { this.info("Login in progress"); @@ -181,7 +181,7 @@ var AuthenticationContext = (function () { this.config.state = expectedState; this._idTokenNonce = this._guid(); this.verbose('Expected state: ' + expectedState + ' startPage:' + window.location.href); - this._saveItem(this.CONSTANTS.STORAGE.LOGIN_REQUEST, window.location.href); + this._saveItem(this.CONSTANTS.STORAGE.LOGIN_REQUEST, loginStartPage || window.location.href); this._saveItem(this.CONSTANTS.STORAGE.LOGIN_ERROR, ''); this._saveItem(this.CONSTANTS.STORAGE.STATE_LOGIN, expectedState); this._saveItem(this.CONSTANTS.STORAGE.NONCE_IDTOKEN, this._idTokenNonce); From e32aee0d4ff8de6fd9a04d39ba4bfcc7c763c79a Mon Sep 17 00:00:00 2001 From: Tushar Gupta Date: Wed, 8 Feb 2017 15:12:56 -0800 Subject: [PATCH 06/19] move anonymousEndpoints check to top --- lib/adal.js | 23 ++++++++++++----------- tests/unit/spec/AdalSpec.js | 5 +++++ 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/lib/adal.js b/lib/adal.js index d2e75fc2..4291ca57 100644 --- a/lib/adal.js +++ b/lib/adal.js @@ -910,6 +910,16 @@ var AuthenticationContext = (function () { * @returns {string} resource for this API endpoint. */ AuthenticationContext.prototype.getResourceForEndpoint = function (endpoint) { + + // if user specified list of anonymous endpoints, no need to send token to these endpoints, return null. + if (this.config && this.config.anonymousEndpoints) { + for (var i = 0; i < this.config.anonymousEndpoints.length; i++) { + if (endpoint.indexOf(this.config.anonymousEndpoints[i]) > -1) { + return null; + } + } + } + if (this.config && this.config.endpoints) { for (var configEndpoint in this.config.endpoints) { // configEndpoint is like /api/Todo requested endpoint can be /api/Todo/1 @@ -927,18 +937,9 @@ var AuthenticationContext = (function () { return this.config.loginResource; } } - // in angular level, the url for $http interceptor call could be relative url, - // if it's relative call, we'll treat it as app backend call. else { - // if user specified list of anonymous endpoints, no need to send token to these endpoints, return null. - if (this.config && this.config.anonymousEndpoints) { - for (var i = 0; i < this.config.anonymousEndpoints.length; i++) { - if (endpoint.indexOf(this.config.anonymousEndpoints[i]) > -1) { - return null; - } - } - } - // all other app's backend calls are secured. + // in angular level, the url for $http interceptor call could be relative url, + // if it's relative call, we'll treat it as app backend call. return this.config.loginResource; } diff --git a/tests/unit/spec/AdalSpec.js b/tests/unit/spec/AdalSpec.js index 4610fb74..9e8cc09c 100644 --- a/tests/unit/spec/AdalSpec.js +++ b/tests/unit/spec/AdalSpec.js @@ -147,6 +147,11 @@ describe('Adal', function () { expect(adal.getResourceForEndpoint('app/views/abc')).toBe(null); expect(adal.getResourceForEndpoint('default/app/views/abc')).toBe(null); expect(adal.getResourceForEndpoint('app/home')).toBe('defaultResource'); + + adal.config.endpoints = { 'abc': 'resourceABC' }; + expect(adal.getResourceForEndpoint('abc')).toBe('resourceABC'); + adal.config.anonymousEndpoints = ['abc']; + expect(adal.getResourceForEndpoint('abc')).toBe(null); }); it('says token expired', function () { From 467bd539a8499aa8dc2ec997d1c00dbe74b9b47d Mon Sep 17 00:00:00 2001 From: Tushar Gupta Date: Wed, 8 Feb 2017 15:49:27 -0800 Subject: [PATCH 07/19] update the expires offset from 2 to 5 minutes, use default value for "expires_in" --- lib/adal.js | 4 +++- tests/unit/spec/AdalSpec.js | 12 ++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/lib/adal.js b/lib/adal.js index d2e75fc2..4977eb49 100644 --- a/lib/adal.js +++ b/lib/adal.js @@ -310,7 +310,7 @@ var AuthenticationContext = (function () { var expiry = this._getItem(this.CONSTANTS.STORAGE.EXPIRATION_KEY + resource); // If expiration is within offset, it will force renew - var offset = this.config.expireOffsetSeconds || 120; + var offset = this.config.expireOffsetSeconds || 300; if (expiry && (expiry > this._now() + offset)) { return token; @@ -1267,6 +1267,8 @@ var AuthenticationContext = (function () { * @ignore */ AuthenticationContext.prototype._expiresIn = function (expires) { + // if AAD did not send "expires_in" property, use default expiration of 3599 seconds, for some reason AAD sends 3599 as "expires_in" value + if (!expires) expires = 3599; return this._now() + parseInt(expires, 10); }; diff --git a/tests/unit/spec/AdalSpec.js b/tests/unit/spec/AdalSpec.js index 4610fb74..e5981f8f 100644 --- a/tests/unit/spec/AdalSpec.js +++ b/tests/unit/spec/AdalSpec.js @@ -1033,4 +1033,16 @@ describe('Adal', function () { Logging.level = 2; }); + + it('tests default value for expires_in', function () { + var requestInfo = { + valid: true, + parameters: { 'access_token': 'token123', 'state': '123' }, + stateMatch: true, + stateResponse: '123|loginResource1', + requestType: adal.REQUEST_TYPE.RENEW_TOKEN + }; + adal.saveTokenFromHash(requestInfo); + expect(storageFake.getItem(adal.CONSTANTS.STORAGE.EXPIRATION_KEY + 'loginResource1')).toBe(mathMock.round(1) + 3599); + }); }); From 86373b733798108fe4d03b5c5f3e68c6f3f41d44 Mon Sep 17 00:00:00 2001 From: Tushar Gupta Date: Thu, 9 Feb 2017 16:22:31 -0800 Subject: [PATCH 08/19] update change comment --- lib/adal.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/adal.js b/lib/adal.js index 4977eb49..f2d11958 100644 --- a/lib/adal.js +++ b/lib/adal.js @@ -1267,7 +1267,7 @@ var AuthenticationContext = (function () { * @ignore */ AuthenticationContext.prototype._expiresIn = function (expires) { - // if AAD did not send "expires_in" property, use default expiration of 3599 seconds, for some reason AAD sends 3599 as "expires_in" value + // if AAD did not send "expires_in" property, use default expiration of 3599 seconds, for some reason AAD sends 3599 as "expires_in" value instead of 3600 if (!expires) expires = 3599; return this._now() + parseInt(expires, 10); }; From aafe1818c40bd6a813983aca057f34ecdf95cd18 Mon Sep 17 00:00:00 2001 From: Tushar Gupta Date: Wed, 8 Feb 2017 17:17:24 -0800 Subject: [PATCH 09/19] removes query and hash parameters from redirect uri --- lib/adal-angular.js | 3 ++- lib/adal.js | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/adal-angular.js b/lib/adal-angular.js index 22cae5d3..82dd2ce7 100644 --- a/lib/adal-angular.js +++ b/lib/adal-angular.js @@ -48,8 +48,9 @@ this.init = function (configOptions, httpProvider) { if (configOptions) { // redirect and logout_redirect are set to current location by default + // strip off query parameters or hashes from the redirecturi as AAD does not allow those. var existingHash = window.location.hash; - var pathDefault = window.location.href; + var pathDefault = window.location.href.split('?')[0]; if (existingHash) { pathDefault = pathDefault.replace(existingHash, ''); } diff --git a/lib/adal.js b/lib/adal.js index 89241dfd..b8512615 100644 --- a/lib/adal.js +++ b/lib/adal.js @@ -151,7 +151,10 @@ var AuthenticationContext = (function () { } if (!this.config.redirectUri) { - this.config.redirectUri = window.location.href; + // strip off query parameters or hashes from the redirect uri as AAD does not allow those. + var hash = window.location.hash; + this.config.redirectUri = window.location.href.split('?')[0]; + this.config.redirectUri = this.config.redirectUri.replace(hash, ''); } if (!this.config.anonymousEndpoints) { From 92f2c674c142c5c5b444ebf18ce28d8cfe3f0b8b Mon Sep 17 00:00:00 2001 From: Tushar Gupta Date: Thu, 9 Feb 2017 16:20:42 -0800 Subject: [PATCH 10/19] adding tests --- lib/adal-angular.js | 9 -------- lib/adal.js | 7 +++++- tests/unit/spec/AdalSpec.js | 45 ++++++++++++++++++++++++------------- 3 files changed, 36 insertions(+), 25 deletions(-) diff --git a/lib/adal-angular.js b/lib/adal-angular.js index 82dd2ce7..29fe2196 100644 --- a/lib/adal-angular.js +++ b/lib/adal-angular.js @@ -47,15 +47,6 @@ this.init = function (configOptions, httpProvider) { if (configOptions) { - // redirect and logout_redirect are set to current location by default - // strip off query parameters or hashes from the redirecturi as AAD does not allow those. - var existingHash = window.location.hash; - var pathDefault = window.location.href.split('?')[0]; - if (existingHash) { - pathDefault = pathDefault.replace(existingHash, ''); - } - configOptions.redirectUri = configOptions.redirectUri || pathDefault; - configOptions.postLogoutRedirectUri = configOptions.postLogoutRedirectUri || pathDefault; configOptions.isAngular = true; if (httpProvider && httpProvider.interceptors) { diff --git a/lib/adal.js b/lib/adal.js index b8512615..79bc2b30 100644 --- a/lib/adal.js +++ b/lib/adal.js @@ -32,7 +32,7 @@ var AuthenticationContext = (function () { * @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} postLogoutRedirectUri - Redirects the user to postLogoutRedirectUri after logout. Defaults is 'redirectUri'. * @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. @@ -150,6 +150,7 @@ var AuthenticationContext = (function () { this.config.loginResource = this.config.clientId; } + // redirect and logout_redirect are set to current location by default if (!this.config.redirectUri) { // strip off query parameters or hashes from the redirect uri as AAD does not allow those. var hash = window.location.hash; @@ -157,6 +158,10 @@ var AuthenticationContext = (function () { this.config.redirectUri = this.config.redirectUri.replace(hash, ''); } + if (!this.config.postLogoutRedirectUri) { + this.config.postLogoutRedirectUri = this.config.redirectUri; + } + if (!this.config.anonymousEndpoints) { this.config.anonymousEndpoints = []; } diff --git a/tests/unit/spec/AdalSpec.js b/tests/unit/spec/AdalSpec.js index e5981f8f..28fc3d4f 100644 --- a/tests/unit/spec/AdalSpec.js +++ b/tests/unit/spec/AdalSpec.js @@ -25,21 +25,8 @@ global.window = {}; var AdalModule = require('../../../lib/adal.js'); describe('Adal', function () { - var adal; + var adal, window; global.Logging = global.window.Logging; - var window = { - location: { - hash: '#hash', - href: 'href', - replace: function (val) { - } - }, - localStorage: {}, - sessionStorage: {}, - atob: atobHelper, - innerWidth: 100, - innerHeight: 100 - }; var mathMock = { random: function () { return 0.2; @@ -111,6 +98,19 @@ describe('Adal', function () { // add key storageFake.setItem(STORAGE_TOKEN_KEYS, RESOURCE1 + '|'); + window = { + location: { + hash: '#hash', + href: 'href', + replace: function (val) { + } + }, + localStorage: {}, + sessionStorage: {}, + atob: atobHelper, + innerWidth: 100, + innerHeight: 100 + }; window.localStorage = storageFake; window.sessionStorage = storageFake; // Init adal @@ -122,7 +122,8 @@ describe('Adal', function () { global.Math = mathMock; global.angular = angularMock; - adal = new AdalModule.inject(conf); + AdalModule.prototype._singletonInstance = null; + adal = new AdalModule(conf); adal._user = null; adal._renewStates = []; adal._activeRenewals = {}; @@ -704,6 +705,7 @@ describe('Adal', function () { return storageFake.getItem(adal.CONSTANTS.STORAGE.RENEW_STATUS + RESOURCE1) === adal.CONSTANTS.TOKEN_RENEW_STATUS_CANCELED; }, 'token renew status not updated', 1000); runs(function () { + window.callBackMappedToRenewStates[adal.config.state]('Token renewal operation failed due to timeout', null, 'Token Renewal Failed'); expect(storageFake.getItem(adal.CONSTANTS.STORAGE.RENEW_STATUS + RESOURCE1)).toBe(adal.CONSTANTS.TOKEN_RENEW_STATUS_CANCELED); expect(errDesc).toBe('Token renewal operation failed due to timeout'); expect(token).toBe(null); @@ -1045,4 +1047,17 @@ describe('Adal', function () { adal.saveTokenFromHash(requestInfo); expect(storageFake.getItem(adal.CONSTANTS.STORAGE.EXPIRATION_KEY + 'loginResource1')).toBe(mathMock.round(1) + 3599); }); + + it('tests default value of redirect uri', function () { + global.window = { + location: { + hash: '#/hash', + href: 'https://www.testurl.com/#/hash?q1=p1' + } + }; + AdalModule.prototype._singletonInstance = null; + var localConfig = { clientId: 'e9a5a8b6-8af7-4719-9821-0deef255f68e' }; + var localAdal = new AdalModule.inject(localConfig); + expect(localAdal.config.redirectUri).toBe('https://www.testurl.com/'); + }) }); From ee10bdf5371425359a9efd2aa6583efb070d3315 Mon Sep 17 00:00:00 2001 From: Rohit Narula Date: Tue, 14 Feb 2017 13:56:38 -0800 Subject: [PATCH 11/19] update callback params description for acquireToken --- lib/adal.js | 3 ++- tests/angularModuleSpec.js | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/adal.js b/lib/adal.js index 1c2b8357..116a980e 100644 --- a/lib/adal.js +++ b/lib/adal.js @@ -492,8 +492,9 @@ var AuthenticationContext = (function () { /** * @callback tokenCallback - * @param {string} error error message returned from AAD if token request fails. + * @param {string} error_description error description returned from AAD if token request fails. * @param {string} token token returned from AAD if token request is successful. + * @param {string} error error message returned from AAD if token request fails. */ /** diff --git a/tests/angularModuleSpec.js b/tests/angularModuleSpec.js index 0c4ab047..4d9a3ccc 100644 --- a/tests/angularModuleSpec.js +++ b/tests/angularModuleSpec.js @@ -488,7 +488,7 @@ describe('AcquireTokenCtl', function () { store = { 'adal.token.keys': adalServiceProvider.config.loginResource + '|', 'adal.access.token.keyloginResource123': token, - 'adal.expiration.keyloginResource123': 122 + 'adal.expiration.keyloginResource123': 302 }; scope.$on('adal:acquireTokenSuccess', function (event, message) { tokenOut = message; From 6d3af3a45aca28ce5f02bb4742f7ff7fbbbe0b42 Mon Sep 17 00:00:00 2001 From: Rohit Narula Date: Tue, 14 Feb 2017 14:23:50 -0800 Subject: [PATCH 12/19] update node version for travis.yml --- .travis.yml | 3 +++ tests/angularModuleSpec.js | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 18ae2d89..fb027fb2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,3 +2,6 @@ language: node_js node_js: - "0.11" - "0.10" + - "4.7.3" + - "5.12.0" + - "6.1.0" diff --git a/tests/angularModuleSpec.js b/tests/angularModuleSpec.js index 0c4ab047..4d9a3ccc 100644 --- a/tests/angularModuleSpec.js +++ b/tests/angularModuleSpec.js @@ -488,7 +488,7 @@ describe('AcquireTokenCtl', function () { store = { 'adal.token.keys': adalServiceProvider.config.loginResource + '|', 'adal.access.token.keyloginResource123': token, - 'adal.expiration.keyloginResource123': 122 + 'adal.expiration.keyloginResource123': 302 }; scope.$on('adal:acquireTokenSuccess', function (event, message) { tokenOut = message; From 9af15033a4c01b7ca00b2b4f5ea4345b9905e57c Mon Sep 17 00:00:00 2001 From: Rohit Narula Date: Thu, 15 Sep 2016 18:40:05 -0700 Subject: [PATCH 13/19] switch off navigation to Login_Request Url --- lib/adal-angular.js | 52 ++++++++++++++++++++++++++----------- lib/adal.js | 22 +++++++++++----- tests/angularModuleSpec.js | 21 ++++++++++----- tests/unit/spec/AdalSpec.js | 2 +- 4 files changed, 68 insertions(+), 29 deletions(-) diff --git a/lib/adal-angular.js b/lib/adal-angular.js index 29fe2196..e893e833 100644 --- a/lib/adal-angular.js +++ b/lib/adal-angular.js @@ -69,14 +69,26 @@ var locationChangeHandler = function (event, newUrl, oldUrl) { _adal.verbose('Location change event from ' + oldUrl + ' to ' + newUrl); - var hash = $window.location.hash; + if ($location.$$html5) { + var hash = $location.hash(); + } + else { + var hash = '#' + $location.path(); + } + processHash(hash); + + $timeout(function () { + updateDataFromCache(_adal.config.loginResource); + $rootScope.userInfo = _oauthData; + }, 1); + }; + var processHash = function (hash) { if (_adal.isCallback(hash)) { // callback can come from login or iframe request _adal.verbose('Processing the hash: ' + hash); var requestInfo = _adal.getRequestInfo(hash); _adal.saveTokenFromHash(requestInfo); - // Return to callback if it is sent from iframe if (requestInfo.stateMatch) { if (requestInfo.requestType === _adal.REQUEST_TYPE.RENEW_TOKEN) { @@ -118,18 +130,27 @@ // 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) { - // 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) { - $location.url(loginStartPage.substring(loginStartPage.indexOf('#') + 1)); + if (_adal.config.navigateToLoginRequestUrl) { + var loginStartPage = _adal._getItem(_adal.CONSTANTS.STORAGE.LOGIN_REQUEST); + 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) { + $location.url(loginStartPage.substring(loginStartPage.indexOf('#') + 1)); + } + $window.location.href = loginStartPage; + } + } + else { + // resetting the hash to null + if ($location.$$html5) { + $location.hash(''); + } + else { + $location.path(''); } - $window.location.href = loginStartPage; } } - else - event.preventDefault(); } } else { @@ -154,10 +175,6 @@ } } - $timeout(function () { - updateDataFromCache(_adal.config.loginResource); - $rootScope.userInfo = _oauthData; - }, 1); }; var loginHandler = function () { @@ -285,6 +302,11 @@ $rootScope.$on('$stateChangeError', stateChangeErrorHandler); + //Event to track hash change of + $window.addEventListener('adal:popUpHashChanged', function (e) { + processHash(e.detail); + }); + updateDataFromCache(_adal.config.loginResource); $rootScope.userInfo = _oauthData; diff --git a/lib/adal.js b/lib/adal.js index 116a980e..57f25b71 100644 --- a/lib/adal.js +++ b/lib/adal.js @@ -135,6 +135,9 @@ var AuthenticationContext = (function () { this.config = this._cloneConfig(config); + if (this.config.navigateToLoginRequestUrl === undefined) + this.config.navigateToLoginRequestUrl = true; + if (this.config.popUp) this.popUp = true; @@ -271,7 +274,7 @@ var AuthenticationContext = (function () { try { if (popupWindow.location.href.indexOf(registeredRedirectUri) != -1) { if (that.isAngular) { - window.location.hash = popupWindow.location.hash; + that._onPopUpHashChanged(popupWindow.location.hash); } else { that.handleWindowCallback(popupWindow.location.hash); @@ -286,10 +289,11 @@ var AuthenticationContext = (function () { }, 20); }; - /** - * Checks if login is in progress. - * @returns {Boolean} true if login is in progress, false otherwise. - */ + AuthenticationContext.prototype._onPopUpHashChanged = function (hash) { + var evt = new CustomEvent('adal:popUpHashChanged', { detail: hash }); + window.dispatchEvent(evt); + }; + AuthenticationContext.prototype.loginInProgress = function () { return this._loginInProgress; }; @@ -997,8 +1001,11 @@ var AuthenticationContext = (function () { } catch (err) { this.error('Error occurred in user defined callback function', err) } - if (!this.popUp)// No need to redirect user in case of popup - window.location.href = this._getItem(this.CONSTANTS.STORAGE.LOGIN_REQUEST); + if (!this.popUp) { + window.location.hash = ''; + if (this.config.navigateToLoginRequestUrl && window.location.href.replace('#', '') !== this._getItem(this.CONSTANTS.STORAGE.LOGIN_REQUEST)) + window.location.href = this._getItem(this.CONSTANTS.STORAGE.LOGIN_REQUEST); + } } }; @@ -1519,3 +1526,4 @@ var AuthenticationContext = (function () { }()); + diff --git a/tests/angularModuleSpec.js b/tests/angularModuleSpec.js index 4d9a3ccc..78186486 100644 --- a/tests/angularModuleSpec.js +++ b/tests/angularModuleSpec.js @@ -82,6 +82,14 @@ describe('TaskCtl', function () { // to prevent full page reload error in karma window.onbeforeunload = function () { return }; controller('TaskCtl', { $scope: scope, adalAuthenticationService: adalServiceProvider }); + + location.$$html5 = true; + window.event = { + preventDefault: function () { + return; + } + }; + })); it('assigns user', function () { @@ -226,7 +234,7 @@ describe('TaskCtl', function () { } }, }; - window.location.hash = 'id_token=sample&state=4343'; + location.hash('#id_token=sample&state=4343'); spyOn(rootScope, '$broadcast').andCallThrough(); var eventName = '', msg = ''; @@ -255,7 +263,7 @@ describe('TaskCtl', function () { }, callBackMappedToRenewStates: { "4343": callback } }; - window.location.hash = 'error=sample&error_description=renewfailed&state=4343'; + location.hash('#error=sample&error_description=renewfailed&state=4343'); scope.$apply(); expect(error).toBe('sample'); expect(errorDesc).toBe('renewfailed'); @@ -276,7 +284,7 @@ describe('TaskCtl', function () { }, callBackMappedToRenewStates: { "4343": callback } }; - window.location.hash = 'access_token=newAccessToken123&state=4343'; + location.hash('#access_token=newAccessToken123&state=4343'); scope.$apply(); expect(error).toBe(''); expect(errorDesc).toBe(''); @@ -298,7 +306,8 @@ describe('TaskCtl', function () { }, callBackMappedToRenewStates: { "4343": callback } }; - window.location.hash = 'id_token=newIdToken123&state=4343'; + location.hash('#id_token=newIdToken123&state=4343'); + scope.$apply(); expect(errorDesc).toBe('Invalid id_token. id_token: newIdToken123'); expect(error).toBe('invalid id_token'); @@ -308,7 +317,7 @@ describe('TaskCtl', function () { it('tests login failure after users logs in', function () { var mockInvalidClientIdToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJjbGllbnQxMjMiLCJuYW1lIjoiSm9obiBEb2UiLCJ1cG4iOiJqb2huQGVtYWlsLmNvbSJ9.zNX4vfLzlbFeKHZ9BMN3sYLtEEE-0o1RoL4NUhXz-l8'; - window.location.hash = 'id_token=' + mockInvalidClientIdToken + '&state=1234'; + location.hash('#'+ 'id_token=' + mockInvalidClientIdToken + '&state=1234'); window.sessionStorage.setItem('adal.state.login', '1234'); spyOn(rootScope, '$broadcast').andCallThrough(); var eventName = '', error = '', errorDesc = '', token = ''; @@ -327,7 +336,7 @@ describe('TaskCtl', function () { it('tests login success after users logs in', function () { var mockIdToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJjbGllbnRpZDEyMyIsIm5hbWUiOiJKb2huIERvZSIsInVwbiI6ImpvaG5AZW1haWwuY29tIiwibm9uY2UiOm51bGx9.DLCO6yIWhnNBYfHH8qFPswcH4M2Alpjn6AZy7K6HENY'; - window.location.hash = 'id_token=' + mockIdToken + '&state=1234'; + location.hash('#' + 'id_token=' + mockIdToken + '&state=1234'); window.sessionStorage.setItem('adal.state.login', '1234'); spyOn(rootScope, '$broadcast').andCallThrough(); var eventName = '', token = ''; diff --git a/tests/unit/spec/AdalSpec.js b/tests/unit/spec/AdalSpec.js index 5503773a..631a2e3e 100644 --- a/tests/unit/spec/AdalSpec.js +++ b/tests/unit/spec/AdalSpec.js @@ -48,7 +48,7 @@ describe('Adal', function () { }; var angularMock = {}; - var conf = { loginResource: 'defaultResource', tenant: 'testtenant', clientId: 'e9a5a8b6-8af7-4719-9821-0deef255f68e' }; + var conf = { loginResource: 'defaultResource', tenant: 'testtenant', clientId: 'e9a5a8b6-8af7-4719-9821-0deef255f68e', navigateToLoginRequestUrl:true }; var testPage = 'this is a song'; var STORAGE_PREFIX = 'adal'; var STORAGE_ACCESS_TOKEN_KEY = STORAGE_PREFIX + '.access.token.key'; From d1c07ff97e1f417d8c9c2712bee5bd2f4df96b33 Mon Sep 17 00:00:00 2001 From: Rohit Narula Date: Thu, 16 Feb 2017 16:24:53 -0800 Subject: [PATCH 14/19] setting postLogoutUri to window.location.href --- lib/adal.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/adal.js b/lib/adal.js index 57f25b71..2c9f8a5e 100644 --- a/lib/adal.js +++ b/lib/adal.js @@ -156,13 +156,12 @@ var AuthenticationContext = (function () { // redirect and logout_redirect are set to current location by default if (!this.config.redirectUri) { // strip off query parameters or hashes from the redirect uri as AAD does not allow those. - var hash = window.location.hash; - this.config.redirectUri = window.location.href.split('?')[0]; - this.config.redirectUri = this.config.redirectUri.replace(hash, ''); + this.config.redirectUri = window.location.href.split("?")[0].split("#")[0]; } if (!this.config.postLogoutRedirectUri) { - this.config.postLogoutRedirectUri = this.config.redirectUri; + // strip off query parameters or hashes from the post logout redirect uri as AAD does not allow those. + this.config.postLogoutRedirectUri = window.location.href.split("?")[0].split("#")[0]; } if (!this.config.anonymousEndpoints) { From 4ee94f715b02048dfe3c4acf4793ec005cef4eba Mon Sep 17 00:00:00 2001 From: Tushar Gupta Date: Mon, 13 Feb 2017 16:32:45 -0800 Subject: [PATCH 15/19] skip adding tokens to request served by $templateCache --- lib/adal-angular.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/adal-angular.js b/lib/adal-angular.js index e893e833..a9eadabc 100644 --- a/lib/adal-angular.js +++ b/lib/adal-angular.js @@ -378,13 +378,17 @@ }); // Interceptor for http if needed - AdalModule.factory('ProtectedResourceInterceptor', ['adalAuthenticationService', '$q', '$rootScope', function (authService, $q, $rootScope) { + AdalModule.factory('ProtectedResourceInterceptor', ['adalAuthenticationService', '$q', '$rootScope', '$templateCache', function (authService, $q, $rootScope, $templateCache) { return { request: function (config) { if (config) { config.headers = config.headers || {}; + + // if the request can be served via templateCache, no need to token + if ($templateCache.get(config.url)) return config; + var resource = authService.getResourceForEndpoint(config.url); authService.verbose('Url: ' + config.url + ' maps to resource: ' + resource); if (resource === null) { From b6ccd96e80beab5f00ae78cb64869073d88a713c Mon Sep 17 00:00:00 2001 From: Rohit Narula Date: Thu, 16 Feb 2017 17:42:21 -0800 Subject: [PATCH 16/19] release Adaljs --- README.md | 10 +++++----- bower.json | 2 +- changelog.txt | 8 ++++++++ dist/adal-angular.min.js | 4 ++-- dist/adal.min.js | 4 ++-- lib/adal-angular.js | 2 +- lib/adal.js | 4 ++-- package.json | 2 +- 8 files changed, 22 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index a4425eab..5879b0c8 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.13 +Current version - 1.0.14 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.13**. +This is a GA released version. The current version is **1.0.14**. 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.13. +CDN will be updated to latest version 1.0.14. Via Bower: diff --git a/bower.json b/bower.json index 874f4bd7..7b64defd 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "adal-angular", - "version": "1.0.13", + "version": "1.0.14", "homepage": "https://github.com/AzureAD/azure-activedirectory-library-for-js", "authors": [ "MSOpentech" diff --git a/changelog.txt b/changelog.txt index 95392ffd..704a84b0 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,11 @@ +Version 1.0.14 +========================= +* Adding ability to specify anonymous sub-urls for a configured endpoint +* Fixing infinite loop issues when using popUp for angular versions 1.5.8 or above +* Fixing silent exception error in case of user defined callback function +* Fixing double error pipeline in angular acquireToken method +* Other bug fixes and updates + Version 1.0.13 ========================= * Pass 'error' parameter to the callback besides 'error_description': #424 diff --git a/dist/adal-angular.min.js b/dist/adal-angular.min.js index 66cf4c52..b2b1f72f 100644 --- a/dist/adal-angular.min.js +++ b/dist/adal-angular.min.js @@ -1,2 +1,2 @@ -/*! adal-angular v1.0.13 2016-11-08 */ -!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(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;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");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(l.substring(l.indexOf("#")+1)),e.location.href=l)}else g.$$html5?g.hash(""):g.path("")}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 m=i.get("adalAuthenticationService");m.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])})}},o=function(){a.info("Login event for:"+g.$$url),a.config&&a.config.localLoginUrl?g.path(a.config.localLoginUrl):(a.info("Start login at:"+g.$$absUrl),d.$broadcast("adal:loginRedirect"),a.login(g.$$absUrl))},p=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),o());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)}},q=function(c,d,e,f,h){if(d)for(var i=l(d),m=null,n=0;n-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 +/*! adal-angular v1.0.14 2017-02-16 */ +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),void 0===this.config.navigateToLoginRequestUrl&&(this.config.navigateToLoginRequestUrl=!0),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.split("?")[0].split("#")[0]),this.config.postLogoutRedirectUri||(this.config.postLogoutRedirectUri=window.location.href.split("?")[0].split("#")[0]),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(a){if(this._loginInProgress)return void this.info("Login in progress");var b=this._guid();this.config.state=b,this._idTokenNonce=this._guid(),this.verbose("Expected state: "+b+" startPage:"+window.location.href),this._saveItem(this.CONSTANTS.STORAGE.LOGIN_REQUEST,a||window.location.href),this._saveItem(this.CONSTANTS.STORAGE.LOGIN_ERROR,""),this._saveItem(this.CONSTANTS.STORAGE.STATE_LOGIN,b),this._saveItem(this.CONSTANTS.STORAGE.NONCE_IDTOKEN,this._idTokenNonce),this._saveItem(this.CONSTANTS.STORAGE.ERROR,""),this._saveItem(this.CONSTANTS.STORAGE.ERROR_DESCRIPTION,"");var c=this._getNavigateUrl("id_token",null)+"&nonce="+encodeURIComponent(this._idTokenNonce);this._loginInProgress=!0,this.config.displayCall?this.config.displayCall(c):this.popUp?this._loginPopup(c):this.promptUser(c)},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(a){return this.warn("Error opening popup, "+a.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?d._onPopUpHashChanged(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._onPopUpHashChanged=function(a){var b=new CustomEvent("adal:popUpHashChanged",{detail:a});window.dispatchEvent(b)},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||300;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 null;if(this.config&&this.config.endpoints)for(var c in this.config.endpoints)if(a.indexOf(c)>-1)return this.config.endpoints[c];return a.indexOf("http://")>-1||a.indexOf("https://")>-1?this._getHostFromUri(a)===this._getHostFromUri(this.config.redirectUri)?this.config.loginResource:null:this.config.loginResource},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,d=null;b.requestType===this.REQUEST_TYPE.RENEW_TOKEN&&window.parent&&window.parent!==window?(this.verbose("Window is in iframe"),d=window.parent.callBackMappedToRenewStates[b.stateResponse],c=b.parameters[this.CONSTANTS.ACCESS_TOKEN]||b.parameters[this.CONSTANTS.ID_TOKEN]):b.requestType===this.REQUEST_TYPE.LOGIN&&(d=this.callback,c=b.parameters[this.CONSTANTS.ID_TOKEN]);try{d&&d(this._getItem(this.CONSTANTS.STORAGE.ERROR_DESCRIPTION),c,this._getItem(this.CONSTANTS.STORAGE.ERROR))}catch(a){this.error("Error occurred in user defined callback function",a)}this.popUp||(window.location.hash="",this.config.navigateToLoginRequestUrl&&window.location.href.replace("#","")!==this._getItem(this.CONSTANTS.STORAGE.LOGIN_REQUEST)&&(window.location.href=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._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 a||(a=3599),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(a){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(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.14"},"undefined"!=typeof module&&module.exports&&(module.exports=AuthenticationContext,module.exports.inject=function(a){return new AuthenticationContext(a)}),AuthenticationContext}(); \ No newline at end of file diff --git a/lib/adal-angular.js b/lib/adal-angular.js index a9eadabc..e23183dc 100644 --- a/lib/adal-angular.js +++ b/lib/adal-angular.js @@ -1,5 +1,5 @@ //---------------------------------------------------------------------- -// AdalJS v1.0.13 +// AdalJS v1.0.14 // @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 2c9f8a5e..12a839f5 100644 --- a/lib/adal.js +++ b/lib/adal.js @@ -1,5 +1,5 @@ //---------------------------------------------------------------------- -// AdalJS v1.0.13 +// AdalJS v1.0.14 // @preserve Copyright (c) Microsoft Open Technologies, Inc. // All Rights Reserved // Apache License 2.0 @@ -1507,7 +1507,7 @@ var AuthenticationContext = (function () { * @ignore */ AuthenticationContext.prototype._libVersion = function () { - return '1.0.13'; + return '1.0.14'; }; /** diff --git a/package.json b/package.json index 55aca42c..69c5357f 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.13", + "version": "1.0.14", "description": "Windows Azure Active Directory Client Library for js", "keywords": [ "implicit", From 25cb28336f3835e4dbe513bc97c2541bf8d2cc9f Mon Sep 17 00:00:00 2001 From: Rohit Narula Date: Thu, 16 Feb 2017 18:03:11 -0800 Subject: [PATCH 17/19] updating minified files --- dist/adal-angular.min.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dist/adal-angular.min.js b/dist/adal-angular.min.js index b2b1f72f..bc4d0724 100644 --- a/dist/adal-angular.min.js +++ b/dist/adal-angular.min.js @@ -1,2 +1,2 @@ /*! adal-angular v1.0.14 2017-02-16 */ -!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");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(l.substring(l.indexOf("#")+1)),e.location.href=l)}else g.$$html5?g.hash(""):g.path("")}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 m=i.get("adalAuthenticationService");m.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])})}},o=function(){a.info("Login event for:"+g.$$url),a.config&&a.config.localLoginUrl?g.path(a.config.localLoginUrl):(a.info("Start login at:"+g.$$absUrl),d.$broadcast("adal:loginRedirect"),a.login(g.$$absUrl))},p=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),o());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)}},q=function(c,d,e,f,h){if(d)for(var i=l(d),m=null,n=0;n0;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");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(l.substring(l.indexOf("#")+1)),e.location.href=l)}else g.$$html5?g.hash(""):g.path("")}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 m=i.get("adalAuthenticationService");m.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])})}},o=function(){a.info("Login event for:"+g.$$url),a.config&&a.config.localLoginUrl?g.path(a.config.localLoginUrl):(a.info("Start login at:"+g.$$absUrl),d.$broadcast("adal:loginRedirect"),a.login(g.$$absUrl))},p=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),o());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)}},q=function(c,d,e,f,h){if(d)for(var i=l(d),m=null,n=0;n Date: Thu, 16 Feb 2017 18:07:29 -0800 Subject: [PATCH 18/19] Update changelog.txt --- changelog.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/changelog.txt b/changelog.txt index 704a84b0..d48b608e 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,6 +1,7 @@ Version 1.0.14 ========================= * Adding ability to specify anonymous sub-urls for a configured endpoint +* Fixing http interceptor to look into templateCache first * Fixing infinite loop issues when using popUp for angular versions 1.5.8 or above * Fixing silent exception error in case of user defined callback function * Fixing double error pipeline in angular acquireToken method From 08191652a483388f99f41c6bc61a46bb7fd33f7c Mon Sep 17 00:00:00 2001 From: Rohit Narula Date: Thu, 16 Feb 2017 18:48:53 -0800 Subject: [PATCH 19/19] adding api doc --- .gitignore | 1 - changelog.txt | 3 + doc/AuthenticationContext.html | 3327 +++++++++++++++++++ doc/RequestInfo.html | 293 ++ doc/User.html | 224 ++ doc/adal.js.html | 1578 +++++++++ doc/config.html | 477 +++ doc/global.html | 414 +++ doc/index.html | 63 + doc/scripts/linenumber.js | 17 + doc/scripts/prettify/Apache-License-2.0.txt | 202 ++ doc/scripts/prettify/lang-css.js | 2 + doc/scripts/prettify/prettify.js | 28 + doc/styles/jsdoc-default.css | 290 ++ doc/styles/prettify-jsdoc.css | 111 + doc/styles/prettify-tomorrow.css | 132 + 16 files changed, 7161 insertions(+), 1 deletion(-) create mode 100644 doc/AuthenticationContext.html create mode 100644 doc/RequestInfo.html create mode 100644 doc/User.html create mode 100644 doc/adal.js.html create mode 100644 doc/config.html create mode 100644 doc/global.html create mode 100644 doc/index.html create mode 100644 doc/scripts/linenumber.js create mode 100644 doc/scripts/prettify/Apache-License-2.0.txt create mode 100644 doc/scripts/prettify/lang-css.js create mode 100644 doc/scripts/prettify/prettify.js create mode 100644 doc/styles/jsdoc-default.css create mode 100644 doc/styles/prettify-jsdoc.css create mode 100644 doc/styles/prettify-tomorrow.css diff --git a/.gitignore b/.gitignore index 8de9a7af..55c592d9 100644 --- a/.gitignore +++ b/.gitignore @@ -14,7 +14,6 @@ node_modules *~ /.c9revisions/ -doc/ build/ samples/ diff --git a/changelog.txt b/changelog.txt index d48b608e..8790d7ae 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,10 +1,13 @@ Version 1.0.14 ========================= * Adding ability to specify anonymous sub-urls for a configured endpoint +* Adding api documentation +* Adding ability to turn off default navigation to start page after login. Set navigateToLoginRequestUrl:false. * Fixing http interceptor to look into templateCache first * Fixing infinite loop issues when using popUp for angular versions 1.5.8 or above * Fixing silent exception error in case of user defined callback function * Fixing double error pipeline in angular acquireToken method +* Updating the default token expiry offset from 2 minutes to 5 minutes * Other bug fixes and updates Version 1.0.13 diff --git a/doc/AuthenticationContext.html b/doc/AuthenticationContext.html new file mode 100644 index 00000000..63595ef2 --- /dev/null +++ b/doc/AuthenticationContext.html @@ -0,0 +1,3327 @@ + + + + + JSDoc: Class: AuthenticationContext + + + + + + + + + + +
+ +

Class: AuthenticationContext

+ + + + + +
+ +
+

+ AuthenticationContext +

+ +
+ +
+
+ + + + +
+

new AuthenticationContext(config)

+ + +
+
+ + +
+ Creates a new AuthenticationContext object. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
config + + +config + + + + Configuration options for AuthenticationContext
+ + + +
+ + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
+ + +
+ + + + + + + + + + + + +

Members

+ +
+ +
+

CONSTANTS :string

+ + +
+
+ +
+ Enum for storage constants +
+ + + +
Type:
+
    +
  • + +string + + +
  • +
+ + + +
+ + +
Properties:
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDefaultDescription
ACCESS_TOKEN + + +string + + + + + + access_token + +
EXPIRES_IN + + +string + + + + + + expires_in + +
ID_TOKEN + + +string + + + + + + id_token + +
ERROR_DESCRIPTION + + +string + + + + + + error_description + +
SESSION_STATE + + +string + + + + + + session_state + +
STORAGE + + +string + + + + + + OBJECTLIT + +
RESOURCE_DELIMETER + + +string + + + + + + | + +
LOADFRAME_TIMEOUT + + +string + + + + + + 6000 + +
TOKEN_RENEW_STATUS_CANCELED + + +string + + + + + + Canceled + +
TOKEN_RENEW_STATUS_COMPLETED + + +string + + + + + + Completed + +
TOKEN_RENEW_STATUS_IN_PROGRESS + + +string + + + + + + In Progress + +
LOGGING_LEVEL + + +string + + + + + + OBJECTLIT + +
LEVEL_STRING_MAP + + +string + + + + + + OBJECTLIT + +
POPUP_WIDTH + + +string + + + + + + 483 + +
POPUP_HEIGHT + + +string + + + + + + 600 + +
+ + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + +
+ + + +
+

REQUEST_TYPE :string

+ + +
+
+ +
+ Enum for request type +
+ + + +
Type:
+
    +
  • + +string + + +
  • +
+ + + +
+ + +
Properties:
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDefaultDescription
LOGIN + + +string + + + + + + LOGIN + +
RENEW_TOKEN + + +string + + + + + + RENEW_TOKEN + +
UNKNOWN + + +string + + + + + + UNKNOWN + +
+ + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + +
+ +
+ + + +

Methods

+ +
+ +
+

acquireToken(resource, callback)

+ + +
+
+ + +
+ Acquires token from the cache if it is not expired. Otherwise sends request to AAD to obtain a new token. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
resource + + +string + + + + ResourceUri identifying the target resource
callback + + +tokenCallback + + + + The callback provided by the caller. It will be called with token or error.
+ + + +
+ + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
+ + + +
+

clearCache()

+ + +
+
+ + +
+ Clears cache items. +
+ + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
+ + + +
+

clearCacheForResource(resource)

+ + +
+
+ + +
+ Clears cache items for a given resource. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
resource + + +string + + + + a URI that identifies the resource.
+ + + +
+ + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
+ + + +
+

error(message, error)

+ + +
+
+ + +
+ Logs messages when Logging Level is set to 0. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
message + + +string + + + + Message to log.
error + + +string + + + + Error to log.
+ + + +
+ + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
+ + + +
+

getCachedToken(resource) → {string}

+ + +
+
+ + +
+ Gets token for the specified resource from the cache. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
resource + + +string + + + + A URI that identifies the resource for which the token is requested.
+ + + +
+ + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + +
Returns:
+ + +
+ token if if it exists and not expired, otherwise null. +
+ + + +
+
+ Type +
+
+ +string + + +
+
+ + + + +
+ + + +
+

getCachedUser() → {User}

+ + +
+
+ + +
+ If user object exists, returns it. Else creates a new user object by decoding id_token from the cache. +
+ + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + +
Returns:
+ + +
+ user object +
+ + + +
+
+ Type +
+
+ +User + + +
+
+ + + + +
+ + + +
+

getLoginError() → {string}

+ + +
+
+ + +
+ Gets login error +
+ + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + +
Returns:
+ + +
+ error message related to login. +
+ + + +
+
+ Type +
+
+ +string + + +
+
+ + + + +
+ + + +
+

getRequestInfo() → {RequestInfo}

+ + +
+
+ + +
+ Creates a requestInfo object from the URL fragment and returns it. +
+ + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + +
Returns:
+ + +
+ an object created from the redirect response from AAD comprising of the keys - parameters, requestType, stateMatch, stateResponse and valid. +
+ + + +
+
+ Type +
+
+ +RequestInfo + + +
+
+ + + + +
+ + + +
+

getResourceForEndpoint(endpoint) → {string}

+ + +
+
+ + +
+ Gets resource for given endpoint if mapping is provided with config. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
endpoint + + +string + + + + The URI for which the resource Id is requested.
+ + + +
+ + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + +
Returns:
+ + +
+ resource for this API endpoint. +
+ + + +
+
+ Type +
+
+ +string + + +
+
+ + + + +
+ + + +
+

getUser(callback)

+ + +
+
+ + +
+ Calls the passed in callback with the user object or error message related to the user. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
callback + + +userCallback + + + + The callback provided by the caller. It will be called with user or error.
+ + + +
+ + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
+ + + +
+

handleWindowCallback(hash)

+ + +
+
+ + +
+ 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. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeArgumentDefaultDescription
hash + + +string + + + + + + <optional>
+ + + + + +
+ + window.location.hash + + Hash fragment of Url.
+ + + +
+ + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
+ + + +
+

info(message)

+ + +
+
+ + +
+ Logs messages when Logging Level is set to 2. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
message + + +string + + + + Message to log.
+ + + +
+ + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
+ + + +
+

isCallback(hash) → {Boolean}

+ + +
+
+ + +
+ Checks if the URL fragment contains access token, id token or error_description. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
hash + + +string + + + + Hash passed from redirect page
+ + + +
+ + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + +
Returns:
+ + +
+ true if response contains id_token, access_token or error, false otherwise. +
+ + + +
+
+ Type +
+
+ +Boolean + + +
+
+ + + + +
+ + + +
+

log(level, message, error)

+ + +
+
+ + +
+ Checks the Logging Level, constructs the Log message and logs it. Users need to implement/override this method to turn on Logging. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
level + + +number + + + + Level can be set 0,1,2 and 3 which turns on 'error', 'warning', 'info' or 'verbose' level logging respectively.
message + + +string + + + + Message to log.
error + + +string + + + + Error to log.
+ + + +
+ + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
+ + + +
+

login()

+ + +
+
+ + +
+ Initiates the login process by redirecting the user to Azure AD authorization endpoint. +
+ + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
+ + + +
+

logOut()

+ + +
+
+ + +
+ Redirects user to logout endpoint. After logout, it will redirect to postLogoutRedirectUri if added as a property on the config object. +
+ + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
+ + + +
+

promptUser(urlNavigate)

+ + +
+
+ + +
+ Redirects the browser to Azure AD authorization endpoint. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
urlNavigate + + +string + + + + Url of the authorization endpoint.
+ + + +
+ + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
+ + + +
+

registerCallback(resource, expectedState, callback)

+ + +
+
+ + +
+ Adds the passed callback to the array of callbacks for the specified resource and puts the array on the window object. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
resource + + +string + + + + A URI that identifies the resource for which the token is requested.
expectedState + + +string + + + + A unique identifier (guid).
callback + + +tokenCallback + + + + The callback provided by the caller. It will be called with token or error.
+ + + +
+ + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
+ + + +
+

saveTokenFromHash()

+ + +
+
+ + +
+ Saves token or error received in the response from AAD in the cache. In case of id_token, it also creates the user object. +
+ + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
+ + + +
+

verbose(message)

+ + +
+
+ + +
+ Logs messages when Logging Level is set to 3. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
message + + +string + + + + Message to log.
+ + + +
+ + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
+ + + +
+

warn(message)

+ + +
+
+ + +
+ Logs messages when Logging Level is set to 1. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
message + + +string + + + + Message to log.
+ + + +
+ + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
+ +
+ + + + + +
+ +
+ + + + +
+ + + +
+ +
+ Documentation generated by JSDoc 3.2.2 on Thu Feb 16 2017 18:45:41 GMT-0800 (PST) +
+ + + + + \ No newline at end of file diff --git a/doc/RequestInfo.html b/doc/RequestInfo.html new file mode 100644 index 00000000..3678d3c4 --- /dev/null +++ b/doc/RequestInfo.html @@ -0,0 +1,293 @@ + + + + + JSDoc: Class: RequestInfo + + + + + + + + + + +
+ +

Class: RequestInfo

+ + + + + +
+ +
+

+ RequestInfo +

+ +
+ +
+
+ + + + +
+

new RequestInfo()

+ + +
+
+ + +
+ Request info object created from the response received from AAD. +
+ + + + + + + + + +
+ + +
Properties:
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
parameters + + +object + + + + object comprising of fields such as id_token/error, session_state, state, e.t.c.
requestType + + +REQUEST_TYPE + + + + either LOGIN, RENEW_TOKEN or UNKNOWN.
stateMatch + + +boolean + + + + true if state is valid, false otherwise.
stateResponse + + +string + + + + unique guid used to match the response with the request.
valid + + +boolean + + + + true if requestType contains id_token, access_token or error, false otherwise.
+ + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
+ + +
+ + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + + +
+ +
+ Documentation generated by JSDoc 3.2.2 on Thu Feb 16 2017 18:45:41 GMT-0800 (PST) +
+ + + + + \ No newline at end of file diff --git a/doc/User.html b/doc/User.html new file mode 100644 index 00000000..e274214d --- /dev/null +++ b/doc/User.html @@ -0,0 +1,224 @@ + + + + + JSDoc: Class: User + + + + + + + + + + +
+ +

Class: User

+ + + + + +
+ +
+

+ User +

+ +
+ +
+
+ + + + +
+

new User()

+ + +
+
+ + +
+ User information from idtoken. +
+ + + + + + + + + +
+ + +
Properties:
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
userName + + +string + + + + username assigned from upn or email.
profile + + +object + + + + properties parsed from idtoken.
+ + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
+ + +
+ + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + + +
+ +
+ Documentation generated by JSDoc 3.2.2 on Thu Feb 16 2017 18:45:41 GMT-0800 (PST) +
+ + + + + \ No newline at end of file diff --git a/doc/adal.js.html b/doc/adal.js.html new file mode 100644 index 00000000..acff3226 --- /dev/null +++ b/doc/adal.js.html @@ -0,0 +1,1578 @@ + + + + + JSDoc: Source: adal.js + + + + + + + + + + +
+ +

Source: adal.js

+ + + + + +
+
+
//----------------------------------------------------------------------
+// AdalJS v1.0.14
+// @preserve Copyright (c) Microsoft Open Technologies, Inc.
+// All Rights Reserved
+// Apache License 2.0
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//----------------------------------------------------------------------
+
+var AuthenticationContext = (function () {
+
+    'use strict';
+
+    /**
+     * 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 is 'redirectUri'.
+     *  @property {string} cacheLocation - Sets browser storage to either 'localStorage' or sessionStorage'. Defaults to 'sessionStorage'.
+     *  @property {Array.<string>} 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 {config}  config               Configuration options for AuthenticationContext
+     */
+
+    AuthenticationContext = function (config) {
+        /**
+         * Enum for request type
+         * @enum {string}
+         */
+        this.REQUEST_TYPE = {
+            LOGIN: 'LOGIN',
+            RENEW_TOKEN: 'RENEW_TOKEN',
+            UNKNOWN: 'UNKNOWN'
+        };
+
+        /**
+         * Enum for storage constants
+         * @enum {string}
+         */
+        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
+        };
+
+        if (AuthenticationContext.prototype._singletonInstance) {
+            return AuthenticationContext.prototype._singletonInstance;
+        }
+        AuthenticationContext.prototype._singletonInstance = this;
+
+        // public
+        this.instance = 'https://login.microsoftonline.com/';
+        this.config = {};
+        this.callback = null;
+        this.popUp = false;
+        this.isAngular = false;
+
+        // private
+        this._user = null;
+        this._activeRenewals = {};
+        this._loginInProgress = false;
+        this._renewStates = [];
+
+        window.callBackMappedToRenewStates = {};
+        window.callBacksMappedToRenewStates = {};
+
+        // validate before constructor assignments
+        if (config.displayCall && typeof config.displayCall !== 'function') {
+            throw new Error('displayCall is not a function');
+        }
+
+        if (!config.clientId) {
+            throw new Error('clientId is required');
+        }
+
+        this.config = this._cloneConfig(config);
+
+        if (this.config.navigateToLoginRequestUrl === undefined)
+            this.config.navigateToLoginRequestUrl = true;
+
+        if (this.config.popUp)
+            this.popUp = true;
+
+        if (this.config.callback && typeof this.config.callback === 'function')
+            this.callback = this.config.callback;
+
+        if (this.config.instance) {
+            this.instance = this.config.instance;
+        }
+
+        // App can request idtoken for itself using clientid as resource
+        if (!this.config.loginResource) {
+            this.config.loginResource = this.config.clientId;
+        }
+
+        // redirect and logout_redirect are set to current location by default
+        if (!this.config.redirectUri) {
+            // strip off query parameters or hashes from the redirect uri as AAD does not allow those.
+            this.config.redirectUri = window.location.href.split("?")[0].split("#")[0];
+        }
+
+        if (!this.config.postLogoutRedirectUri) {
+            // strip off query parameters or hashes from the post logout redirect uri as AAD does not allow those.
+            this.config.postLogoutRedirectUri = window.location.href.split("?")[0].split("#")[0];
+        }
+
+        if (!this.config.anonymousEndpoints) {
+            this.config.anonymousEndpoints = [];
+        }
+
+        if (this.config.isAngular) {
+            this.isAngular = this.config.isAngular;
+        }
+    };
+
+    window.Logging = {
+        level: 0,
+        log: function (message) { }
+    };
+
+    /**
+     * Initiates the login process by redirecting the user to Azure AD authorization endpoint.
+     */
+    AuthenticationContext.prototype.login = function (loginStartPage) {
+        // Token is not present and user needs to login
+        if (this._loginInProgress) {
+            this.info("Login in progress");
+            return;
+        }
+        var expectedState = this._guid();
+        this.config.state = expectedState;
+        this._idTokenNonce = this._guid();
+        this.verbose('Expected state: ' + expectedState + ' startPage:' + window.location.href);
+        this._saveItem(this.CONSTANTS.STORAGE.LOGIN_REQUEST, loginStartPage || window.location.href);
+        this._saveItem(this.CONSTANTS.STORAGE.LOGIN_ERROR, '');
+        this._saveItem(this.CONSTANTS.STORAGE.STATE_LOGIN, expectedState);
+        this._saveItem(this.CONSTANTS.STORAGE.NONCE_IDTOKEN, this._idTokenNonce);
+        this._saveItem(this.CONSTANTS.STORAGE.ERROR, '');
+        this._saveItem(this.CONSTANTS.STORAGE.ERROR_DESCRIPTION, '');
+        var urlNavigate = this._getNavigateUrl('id_token', null) + '&nonce=' + encodeURIComponent(this._idTokenNonce);
+        this._loginInProgress = true;
+        if (this.config.displayCall) {
+            // User defined way of handling the navigation
+            this.config.displayCall(urlNavigate);
+        }
+        else if (this.popUp) {
+            this._loginPopup(urlNavigate);
+        }
+       else {
+            this.promptUser(urlNavigate);
+        }
+    };
+
+    /**
+     * Configures popup window for login.
+     * @ignore
+     */
+    AuthenticationContext.prototype._openPopup = function (urlNavigate, title, popUpWidth, popUpHeight) {
+        try {
+            /**
+            * adding winLeft and winTop to account for dual monitor
+            * using screenLeft and screenTop for IE8 and earlier
+            */
+            var winLeft = window.screenLeft ? window.screenLeft : window.screenX;
+            var winTop = window.screenTop ? window.screenTop : window.screenY;
+            /**
+            * window.innerWidth displays browser window's height and width excluding toolbars
+            * using document.documentElement.clientWidth for IE8 and earlier
+            */
+            var width = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
+            var height = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;
+            var left = ((width / 2) - (popUpWidth / 2)) + winLeft;
+            var top = ((height / 2) - (popUpHeight / 2)) + winTop;
+
+            var popupWindow = window.open(urlNavigate, title, 'width=' + popUpWidth + ', height=' + popUpHeight + ', top=' + top + ', left=' + left);
+            if (popupWindow.focus) {
+                popupWindow.focus();
+            }
+            return popupWindow;
+        } catch (e) {
+            this.warn('Error opening popup, ' + e.message);
+            this._loginInProgress = false;
+            return null;
+        }
+    }
+
+    /**
+     * 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) {
+            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');
+            if (this.callback)
+                this.callback(this._getItem(this.CONSTANTS.STORAGE.LOGIN_ERROR), null, this._getItem(this.CONSTANTS.STORAGE.ERROR));
+            return;
+        }
+        if (this.config.redirectUri.indexOf('#') != -1)
+            var registeredRedirectUri = this.config.redirectUri.split("#")[0];
+        else
+            var registeredRedirectUri = this.config.redirectUri;
+        var that = this;
+        var pollTimer = window.setInterval(function () {
+            if (!popupWindow || popupWindow.closed || popupWindow.closed === undefined) {
+                that._loginInProgress = false;
+                window.clearInterval(pollTimer);
+            }
+            try {
+                if (popupWindow.location.href.indexOf(registeredRedirectUri) != -1) {
+                    if (that.isAngular) {
+                        that._onPopUpHashChanged(popupWindow.location.hash);
+                    }
+                    else {
+                        that.handleWindowCallback(popupWindow.location.hash);
+                    }
+                    window.clearInterval(pollTimer);
+                    that._loginInProgress = false;
+                    that.info("Closing popup window");
+                    popupWindow.close();
+                }
+            } catch (e) {
+            }
+        }, 20);
+    };
+
+    AuthenticationContext.prototype._onPopUpHashChanged = function (hash) {
+        var evt = new CustomEvent('adal:popUpHashChanged', { detail: hash });
+        window.dispatchEvent(evt);
+    };
+
+    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 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)) {
+            return null;
+        }
+
+        var token = this._getItem(this.CONSTANTS.STORAGE.ACCESS_TOKEN_KEY + resource);
+        var expiry = this._getItem(this.CONSTANTS.STORAGE.EXPIRATION_KEY + resource);
+
+        // If expiration is within offset, it will force renew
+        var offset = this.config.expireOffsetSeconds || 300;
+
+        if (expiry && (expiry > this._now() + offset)) {
+            return token;
+        } else {
+            this._saveItem(this.CONSTANTS.STORAGE.ACCESS_TOKEN_KEY + resource, '');
+            this._saveItem(this.CONSTANTS.STORAGE.EXPIRATION_KEY + resource, 0);
+            return null;
+        }
+    };
+
+    /**
+    * User information from idtoken.
+    *  @class User
+    *  @property {string} userName - username assigned from upn or email.
+    *  @property {object} profile - properties parsed from idtoken.
+    */
+
+    /**
+     * 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 () {
+        if (this._user) {
+            return this._user;
+        }
+
+        var idtoken = this._getItem(this.CONSTANTS.STORAGE.IDTOKEN);
+        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]) {
+            window.callBacksMappedToRenewStates[expectedState] = [];
+        }
+        var self = this;
+        window.callBacksMappedToRenewStates[expectedState].push(callback);
+        if (!window.callBackMappedToRenewStates[expectedState]) {
+            window.callBackMappedToRenewStates[expectedState] = function (errorDesc, token, error) {
+                for (var i = 0; i < window.callBacksMappedToRenewStates[expectedState].length; ++i) {
+                    try {
+                        window.callBacksMappedToRenewStates[expectedState][i](errorDesc, token, error);
+                    }
+                    catch (error) {
+                        self.warn(error);
+                    }
+                }
+                self._activeRenewals[resource] = null;
+                window.callBacksMappedToRenewStates[expectedState] = null;
+                window.callBackMappedToRenewStates[expectedState] = null;
+            };
+        }
+    };
+
+    // var errorResponse = {error:'', error_description:''};
+    // var token = 'string token';
+    // callback(errorResponse, token)
+    // with callback
+    /**
+     * Acquires access token with hidden iframe
+     * @ignore
+     */
+    AuthenticationContext.prototype._renewToken = function (resource, callback) {
+        // use iframe to try refresh token
+        // use given resource to create new authz url
+        this.info('renewToken is called for resource:' + resource);
+        var frameHandle = this._addAdalFrame('adalRenewFrame' + resource);
+        var expectedState = this._guid() + '|' + resource;
+        this.config.state = expectedState;
+        // renew happens in iframe, so it keeps javascript context
+        this._renewStates.push(expectedState);
+
+        this.verbose('Renew token Expected state: ' + expectedState);
+        var urlNavigate = this._getNavigateUrl('token', resource) + '&prompt=none';
+        urlNavigate = this._addHintParameters(urlNavigate);
+
+        this.registerCallback(expectedState, resource, callback);
+        this.verbose('Navigate to:' + urlNavigate);
+        frameHandle.src = 'about:blank';
+        this._loadFrameTimeout(urlNavigate, 'adalRenewFrame' + resource, resource);
+
+    };
+
+    /**
+     * 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');
+        var frameHandle = this._addAdalFrame('adalIdTokenFrame');
+        var expectedState = this._guid() + '|' + this.config.clientId;
+        this._idTokenNonce = this._guid();
+        this._saveItem(this.CONSTANTS.STORAGE.NONCE_IDTOKEN, this._idTokenNonce);
+        this.config.state = expectedState;
+        // renew happens in iframe, so it keeps javascript context
+        this._renewStates.push(expectedState);
+
+        this.verbose('Renew Idtoken Expected state: ' + expectedState);
+        var urlNavigate = this._getNavigateUrl('id_token', null) + '&prompt=none';
+        urlNavigate = this._addHintParameters(urlNavigate);
+
+        urlNavigate += '&nonce=' + encodeURIComponent(this._idTokenNonce);
+        this.registerCallback(expectedState, this.config.clientId, callback);
+        this.idTokenNonce = null;
+        this.verbose('Navigate to:' + urlNavigate);
+        frameHandle.src = 'about:blank';
+        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 + "=");
+        return regex.test(url);
+    }
+
+    // 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);
+        this._saveItem(this.CONSTANTS.STORAGE.RENEW_STATUS + resource, this.CONSTANTS.TOKEN_RENEW_STATUS_IN_PROGRESS);
+        this._loadFrame(urlNavigation, frameName);
+        var self = this;
+        setTimeout(function () {
+            if (self._getItem(self.CONSTANTS.STORAGE.RENEW_STATUS + resource) === self.CONSTANTS.TOKEN_RENEW_STATUS_IN_PROGRESS) {
+                // fail the iframe session if it's in pending state
+                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, 'Token Renewal Failed');
+                }
+
+                self._saveItem(self.CONSTANTS.STORAGE.RENEW_STATUS + resource, self.CONSTANTS.TOKEN_RENEW_STATUS_CANCELED);
+            }
+        }, 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
+        var self = this;
+        self.info('LoadFrame: ' + frameName);
+        var frameCheck = frameName;
+        setTimeout(function () {
+            var frameHandle = self._addAdalFrame(frameCheck);
+            if (frameHandle.src === '' || frameHandle.src === 'about:blank') {
+                frameHandle.src = urlNavigate;
+                self._loadFrame(urlNavigate, frameCheck);
+            }
+        }, 500);
+    };
+
+    /**
+     * @callback tokenCallback
+     * @param {string} error_description error description returned from AAD if token request fails.
+     * @param {string} token token returned from AAD if token request is successful.
+     * @param {string} error error message returned from AAD if token request fails.
+     */
+
+    /**
+     * 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 {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)) {
+            this.warn('resource is required');
+            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, null);
+            return;
+        }
+
+        if (!this._user) {
+            this.warn('User login is required');
+            callback('User login is required', null, 'login required');
+            return;
+        }
+
+        // refresh attept with iframe
+        //Already renewing for this resource, callback when we get the token.
+        if (this._activeRenewals[resource]) {
+            //Active renewals contains the state for each renewal.
+            this.registerCallback(this._activeRenewals[resource], resource, callback);
+        }
+        else {
+            if (resource === this.config.clientId) {
+                // App uses idtoken to send to api endpoints
+                // Default resource is tracked as clientid to store this token
+                this.verbose('renewing idtoken');
+                this._renewIdToken(callback);
+            } else {
+                this._renewToken(resource, callback);
+            }
+        }
+    };
+
+    /**
+     * Redirects the browser to Azure AD authorization endpoint.
+     * @param {string}   urlNavigate  Url of the authorization endpoint.
+     */
+    AuthenticationContext.prototype.promptUser = function (urlNavigate) {
+        if (urlNavigate) {
+            this.info('Navigate to:' + urlNavigate);
+            window.location.replace(urlNavigate);
+        } else {
+            this.info('Navigate url is empty');
+        }
+    };
+
+    /**
+     * Clears cache items.
+     */
+    AuthenticationContext.prototype.clearCache = function () {
+        this._saveItem(this.CONSTANTS.STORAGE.ACCESS_TOKEN_KEY, '');
+        this._saveItem(this.CONSTANTS.STORAGE.EXPIRATION_KEY, 0);
+        this._saveItem(this.CONSTANTS.STORAGE.SESSION_STATE, '');
+        this._saveItem(this.CONSTANTS.STORAGE.STATE_LOGIN, '');
+        this._renewStates = [];
+        this._saveItem(this.CONSTANTS.STORAGE.USERNAME, '');
+        this._saveItem(this.CONSTANTS.STORAGE.IDTOKEN, '');
+        this._saveItem(this.CONSTANTS.STORAGE.ERROR, '');
+        this._saveItem(this.CONSTANTS.STORAGE.ERROR_DESCRIPTION, '');
+        var keys = this._getItem(this.CONSTANTS.STORAGE.TOKEN_KEYS);
+
+        if (!this._isEmpty(keys)) {
+            keys = keys.split(this.CONSTANTS.RESOURCE_DELIMETER);
+            for (var i = 0; i < keys.length; i++) {
+                this._saveItem(this.CONSTANTS.STORAGE.ACCESS_TOKEN_KEY + keys[i], '');
+                this._saveItem(this.CONSTANTS.STORAGE.EXPIRATION_KEY + keys[i], 0);
+            }
+        }
+        this._saveItem(this.CONSTANTS.STORAGE.TOKEN_KEYS, '');
+    };
+
+    /**
+     * 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, '');
+        this._saveItem(this.CONSTANTS.STORAGE.ERROR, '');
+        this._saveItem(this.CONSTANTS.STORAGE.ERROR_DESCRIPTION, '');
+        if (this._hasResource(resource)) {
+            this._saveItem(this.CONSTANTS.STORAGE.ACCESS_TOKEN_KEY + resource, '');
+            this._saveItem(this.CONSTANTS.STORAGE.EXPIRATION_KEY + resource, 0);
+        }
+    };
+
+    /**
+     * 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();
+        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;
+            }
+
+            if (this.config.postLogoutRedirectUri) {
+                logout = 'post_logout_redirect_uri=' + encodeURIComponent(this.config.postLogoutRedirectUri);
+            }
+
+            urlNavigate = this.instance + tenant + '/oauth2/logout?' + logout;
+        }
+
+        this.info('Logout navigate to: ' + urlNavigate);
+        this.promptUser(urlNavigate);
+    };
+
+    AuthenticationContext.prototype._isEmpty = function (str) {
+        return (typeof str === 'undefined' || !str || 0 === str.length);
+    };
+
+    /**
+     * @callback userCallback
+     * @param {string} error error message if user info is not available.
+     * @param {User} user user object retrieved from the cache.
+     */
+
+    /**
+     * 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
+        if (typeof callback !== 'function') {
+            throw new Error('callback is not a function');
+        }
+
+        // user in memory
+        if (this._user) {
+            callback(null, this._user);
+            return;
+        }
+
+        // frame is used to get idtoken
+        var idtoken = this._getItem(this.CONSTANTS.STORAGE.IDTOKEN);
+        if (!this._isEmpty(idtoken)) {
+            this.info('User exists in cache: ');
+            this._user = this._createUser(idtoken);
+            callback(null, this._user);
+        } else {
+            this.warn('User information is not available');
+            callback('User information is not available', null);
+        }
+    };
+
+    /**
+     * 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')) {
+
+            // add login_hint
+            urlNavigate += '&login_hint=' + encodeURIComponent(this._user.profile.upn);
+
+            // don't add domain_hint twice if user provided it in the extraQueryParameter value
+            if (!this._urlContainsQueryStringParameter("domain_hint", urlNavigate) && this._user.profile.upn.indexOf('@') > -1) {
+                var parts = this._user.profile.upn.split('@');
+                // local part can include @ in quotes. Sending last part handles that.
+                urlNavigate += '&domain_hint=' + encodeURIComponent(parts[parts.length - 1]);
+            }
+        }
+
+        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);
+        if (parsedJson && parsedJson.hasOwnProperty('aud')) {
+            if (parsedJson.aud.toLowerCase() === this.config.clientId.toLowerCase()) {
+
+                user = {
+                    userName: '',
+                    profile: parsedJson
+                };
+
+                if (parsedJson.hasOwnProperty('upn')) {
+                    user.userName = parsedJson.upn;
+                } else if (parsedJson.hasOwnProperty('email')) {
+                    user.userName = parsedJson.email;
+                }
+            } else {
+                this.warn('IdToken has invalid aud field');
+            }
+
+        }
+
+        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);
+        } else if (hash.indexOf('#') > -1) {
+            hash = hash.substring(1);
+        }
+
+        return hash;
+    };
+
+    /**
+     * Checks if the URL fragment contains access token, id token or error_description.
+     * @param {string} hash  -  Hash passed from redirect page
+     * @returns {Boolean} true if response contains id_token, access_token or error, false otherwise.
+     */
+    AuthenticationContext.prototype.isCallback = function (hash) {
+        hash = this._getHash(hash);
+        var parameters = this._deserialize(hash);
+        return (
+            parameters.hasOwnProperty(this.CONSTANTS.ERROR_DESCRIPTION) ||
+            parameters.hasOwnProperty(this.CONSTANTS.ACCESS_TOKEN) ||
+            parameters.hasOwnProperty(this.CONSTANTS.ID_TOKEN)
+        );
+    };
+
+    /**
+     * Gets login error
+     * @returns {string} error message related to login.
+     */
+    AuthenticationContext.prototype.getLoginError = function () {
+        return this._getItem(this.CONSTANTS.STORAGE.LOGIN_ERROR);
+    };
+
+    /**
+     * 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);
+        var parameters = this._deserialize(hash);
+        var requestInfo = {
+            valid: false,
+            parameters: {},
+            stateMatch: false,
+            stateResponse: '',
+            requestType: this.REQUEST_TYPE.UNKNOWN
+        };
+        if (parameters) {
+            requestInfo.parameters = parameters;
+            if (parameters.hasOwnProperty(this.CONSTANTS.ERROR_DESCRIPTION) ||
+                parameters.hasOwnProperty(this.CONSTANTS.ACCESS_TOKEN) ||
+                parameters.hasOwnProperty(this.CONSTANTS.ID_TOKEN)) {
+
+                requestInfo.valid = true;
+
+                // which call
+                var stateResponse = '';
+                if (parameters.hasOwnProperty('state')) {
+                    this.verbose('State: ' + parameters.state);
+                    stateResponse = parameters.state;
+                } else {
+                    this.warn('No state returned');
+                    return requestInfo;
+                }
+
+                requestInfo.stateResponse = stateResponse;
+
+                // async calls can fire iframe and login request at the same time if developer does not use the API as expected
+                // incoming callback needs to be looked up to find the request type
+                if (stateResponse === this._getItem(this.CONSTANTS.STORAGE.STATE_LOGIN)) {
+                    requestInfo.requestType = this.REQUEST_TYPE.LOGIN;
+                    requestInfo.stateMatch = true;
+                    return requestInfo;
+                }
+
+                // external api requests may have many renewtoken requests for different resource
+                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) {
+                            requestInfo.requestType = this.REQUEST_TYPE.RENEW_TOKEN;
+                            requestInfo.stateMatch = true;
+                            break;
+                        }
+                    }
+                }
+            }
+        }
+
+        return requestInfo;
+    };
+
+    /**
+     * Extracts resource value from state.
+     * @ignore
+     */
+    AuthenticationContext.prototype._getResourceFromState = function (state) {
+        if (state) {
+            var splitIndex = state.indexOf('|');
+            if (splitIndex > -1 && splitIndex + 1 < state.length) {
+                return state.substring(splitIndex + 1);
+            }
+        }
+
+        return '';
+    };
+
+    /**
+     * 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);
+        this._saveItem(this.CONSTANTS.STORAGE.ERROR, '');
+        this._saveItem(this.CONSTANTS.STORAGE.ERROR_DESCRIPTION, '');
+
+        var resource = this._getResourceFromState(requestInfo.stateResponse);
+
+        // Record error
+        if (requestInfo.parameters.hasOwnProperty(this.CONSTANTS.ERROR_DESCRIPTION)) {
+            this.info('Error :' + requestInfo.parameters.error + '; Error description:' + requestInfo.parameters[this.CONSTANTS.ERROR_DESCRIPTION]);
+            this._saveItem(this.CONSTANTS.STORAGE.ERROR, requestInfo.parameters.error);
+            this._saveItem(this.CONSTANTS.STORAGE.ERROR_DESCRIPTION, requestInfo.parameters[this.CONSTANTS.ERROR_DESCRIPTION]);
+
+            if (requestInfo.requestType === this.REQUEST_TYPE.LOGIN) {
+                this._loginInProgress = false;
+                this._saveItem(this.CONSTANTS.STORAGE.LOGIN_ERROR, requestInfo.parameters.error_description);
+            }
+        } else {
+            // It must verify the state from redirect
+            if (requestInfo.stateMatch) {
+                // record tokens to storage if exists
+                this.info('State is right');
+                if (requestInfo.parameters.hasOwnProperty(this.CONSTANTS.SESSION_STATE)) {
+                    this._saveItem(this.CONSTANTS.STORAGE.SESSION_STATE, requestInfo.parameters[this.CONSTANTS.SESSION_STATE]);
+                }
+
+                var keys;
+
+                if (requestInfo.parameters.hasOwnProperty(this.CONSTANTS.ACCESS_TOKEN)) {
+                    this.info('Fragment has access token');
+
+                    if (!this._hasResource(resource)) {
+                        keys = this._getItem(this.CONSTANTS.STORAGE.TOKEN_KEYS) || '';
+                        this._saveItem(this.CONSTANTS.STORAGE.TOKEN_KEYS, keys + resource + this.CONSTANTS.RESOURCE_DELIMETER);
+                    }
+                    // save token with related resource
+                    this._saveItem(this.CONSTANTS.STORAGE.ACCESS_TOKEN_KEY + resource, requestInfo.parameters[this.CONSTANTS.ACCESS_TOKEN]);
+                    this._saveItem(this.CONSTANTS.STORAGE.EXPIRATION_KEY + resource, this._expiresIn(requestInfo.parameters[this.CONSTANTS.EXPIRES_IN]));
+                }
+
+                if (requestInfo.parameters.hasOwnProperty(this.CONSTANTS.ID_TOKEN)) {
+                    this.info('Fragment has id token');
+                    this._loginInProgress = false;
+
+                    this._user = this._createUser(requestInfo.parameters[this.CONSTANTS.ID_TOKEN]);
+
+                    if (this._user && this._user.profile) {
+                        if (this._user.profile.nonce !== this._getItem(this.CONSTANTS.STORAGE.NONCE_IDTOKEN)) {
+                            this._user = null;
+                            this._saveItem(this.CONSTANTS.STORAGE.LOGIN_ERROR, 'Nonce is not same as ' + this._idTokenNonce);
+                        } else {
+                            this._saveItem(this.CONSTANTS.STORAGE.IDTOKEN, requestInfo.parameters[this.CONSTANTS.ID_TOKEN]);
+
+                            // Save idtoken as access token for app itself
+                            resource = this.config.loginResource ? this.config.loginResource : this.config.clientId;
+
+                            if (!this._hasResource(resource)) {
+                                keys = this._getItem(this.CONSTANTS.STORAGE.TOKEN_KEYS) || '';
+                                this._saveItem(this.CONSTANTS.STORAGE.TOKEN_KEYS, keys + resource + this.CONSTANTS.RESOURCE_DELIMETER);
+                            }
+                            this._saveItem(this.CONSTANTS.STORAGE.ACCESS_TOKEN_KEY + resource, requestInfo.parameters[this.CONSTANTS.ID_TOKEN]);
+                            this._saveItem(this.CONSTANTS.STORAGE.EXPIRATION_KEY + resource, this._user.profile.exp);
+                        }
+                    }
+                    else {
+                        this._saveItem(this.CONSTANTS.STORAGE.ERROR, 'invalid id_token');
+                        this._saveItem(this.CONSTANTS.STORAGE.ERROR_DESCRIPTION, 'Invalid id_token. id_token: ' + requestInfo.parameters[this.CONSTANTS.ID_TOKEN]);
+                    }
+                }
+            } else {
+                this._saveItem(this.CONSTANTS.STORAGE.ERROR, 'Invalid_state');
+                this._saveItem(this.CONSTANTS.STORAGE.ERROR_DESCRIPTION, 'Invalid_state. state: ' + requestInfo.stateResponse);
+            }
+        }
+        this._saveItem(this.CONSTANTS.STORAGE.RENEW_STATUS + resource, this.CONSTANTS.TOKEN_RENEW_STATUS_COMPLETED);
+    };
+
+    /**
+     * Gets resource for given endpoint if mapping is provided with config.
+     * @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 user specified list of anonymous endpoints, no need to send token to these endpoints, return null.
+        if (this.config && this.config.anonymousEndpoints) {
+            for (var i = 0; i < this.config.anonymousEndpoints.length; i++) {
+                if (endpoint.indexOf(this.config.anonymousEndpoints[i]) > -1) {
+                    return null;
+                }
+            }
+        }
+
+        if (this.config && this.config.endpoints) {
+            for (var configEndpoint in this.config.endpoints) {
+                // configEndpoint is like /api/Todo requested endpoint can be /api/Todo/1
+                if (endpoint.indexOf(configEndpoint) > -1) {
+                    return this.config.endpoints[configEndpoint];
+                }
+            }
+        }
+
+        // default resource will be clientid if nothing specified
+        // App will use idtoken for calls to itself
+        // check if it's staring from http or https, needs to match with app host
+        if (endpoint.indexOf('http://') > -1 || endpoint.indexOf('https://') > -1) {
+            if (this._getHostFromUri(endpoint) === this._getHostFromUri(this.config.redirectUri)) {
+                return this.config.loginResource;
+            }
+        }
+        else {
+            // in angular level, the url for $http interceptor call could be relative url,
+            // if it's relative call, we'll treat it as app backend call.            
+            return this.config.loginResource;
+        }
+
+        // if not the app's own backend or not a domain listed in the endpoints structure
+        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?:)\/\//, '');
+
+        extractedUri = extractedUri.split('/')[0];
+        return extractedUri;
+    };
+
+    /**
+     * 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
+        if (hash == null)
+            hash = window.location.hash;
+        if (this.isCallback(hash)) {
+            var requestInfo = this.getRequestInfo(hash);
+            this.info('Returned from redirect url');
+            this.saveTokenFromHash(requestInfo);
+            var token = null, callback = null;
+            if ((requestInfo.requestType === this.REQUEST_TYPE.RENEW_TOKEN) && window.parent && (window.parent !== window)) {
+                // iframe call but same single page
+                this.verbose('Window is in iframe');
+                callback = window.parent.callBackMappedToRenewStates[requestInfo.stateResponse];
+                token = requestInfo.parameters[this.CONSTANTS.ACCESS_TOKEN] || requestInfo.parameters[this.CONSTANTS.ID_TOKEN];
+            } else if (requestInfo.requestType === this.REQUEST_TYPE.LOGIN) {
+                callback = this.callback;
+                token = requestInfo.parameters[this.CONSTANTS.ID_TOKEN];
+            }
+            try {
+                if (callback)
+                    callback(this._getItem(this.CONSTANTS.STORAGE.ERROR_DESCRIPTION), token, this._getItem(this.CONSTANTS.STORAGE.ERROR));
+            } catch (err) {
+                this.error('Error occurred in user defined callback function', err)
+            }
+            if (!this.popUp) {
+                window.location.hash = '';
+                if (this.config.navigateToLoginRequestUrl && window.location.href.replace('#', '') !== this._getItem(this.CONSTANTS.STORAGE.LOGIN_REQUEST))
+                    window.location.href = this._getItem(this.CONSTANTS.STORAGE.LOGIN_REQUEST);
+            }
+        }
+    };
+
+    /**
+     * Constructs the authorization endpoint URL and returns it.
+     * @ignore
+     */
+    AuthenticationContext.prototype._getNavigateUrl = function (responseType, resource) {
+        var tenant = 'common';
+        if (this.config.tenant) {
+            tenant = this.config.tenant;
+        }
+
+        var urlNavigate = this.instance + tenant + '/oauth2/authorize' + this._serialize(responseType, this.config, resource) + this._addLibMetadata();
+        this.info('Navigate url:' + urlNavigate);
+        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);
+        if (!decodedToken) {
+            return null;
+        }
+
+        try {
+            var base64IdToken = decodedToken.JWSPayload;
+            var base64Decoded = this._base64DecodeStringUrlSafe(base64IdToken);
+            if (!base64Decoded) {
+                this.info('The returned id_token could not be base64 url safe decoded.');
+                return null;
+            }
+
+            // ECMA script has JSON built-in support
+            return JSON.parse(base64Decoded);
+        } catch (err) {
+            this.error('The returned id_token could not be decoded', err);
+        }
+
+        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, '/');
+        if (window.atob) {
+            return decodeURIComponent(escape(window.atob(base64IdToken))); // jshint ignore:line
+        }
+        else {
+            return decodeURIComponent(escape(this._decode(base64IdToken)));
+        }
+    };
+
+    //Take https://cdnjs.cloudflare.com/ajax/libs/Base64/0.3.0/base64.js and https://en.wikipedia.org/wiki/Base64 as reference. 
+    AuthenticationContext.prototype._decode = function (base64IdToken) {
+        var codes = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
+        base64IdToken = String(base64IdToken).replace(/=+$/, '');
+
+        var length = base64IdToken.length;
+        if (length % 4 === 1) {
+            throw new Error('The token to be decoded is not correctly encoded.');
+        }
+
+        var h1, h2, h3, h4, bits, c1, c2, c3, decoded = '';
+        for (var i = 0; i < length; i += 4) {
+            //Every 4 base64 encoded character will be converted to 3 byte string, which is 24 bits
+            // then 6 bits per base64 encoded character
+            h1 = codes.indexOf(base64IdToken.charAt(i));
+            h2 = codes.indexOf(base64IdToken.charAt(i + 1));
+            h3 = codes.indexOf(base64IdToken.charAt(i + 2));
+            h4 = codes.indexOf(base64IdToken.charAt(i + 3));
+
+            // For padding, if last two are '='
+            if (i + 2 === length - 1) {
+                bits = h1 << 18 | h2 << 12 | h3 << 6;
+                c1 = bits >> 16 & 255;
+                c2 = bits >> 8 & 255;
+                decoded += String.fromCharCode(c1, c2);
+                break;
+            }
+                // if last one is '='
+            else if (i + 1 === length - 1) {
+                bits = h1 << 18 | h2 << 12
+                c1 = bits >> 16 & 255;
+                decoded += String.fromCharCode(c1);
+                break;
+            }
+
+            bits = h1 << 18 | h2 << 12 | h3 << 6 | h4;
+
+            // then convert to 3 byte chars
+            c1 = bits >> 16 & 255;
+            c2 = bits >> 8 & 255;
+            c3 = bits & 255;
+
+            decoded += String.fromCharCode(c1, c2, c3);
+        }
+
+        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)) {
+            return null;
+        };
+
+        var idTokenPartsRegex = /^([^\.\s]*)\.([^\.\s]+)\.([^\.\s]*)$/;
+
+        var matches = idTokenPartsRegex.exec(jwtToken);
+        if (!matches || matches.length < 4) {
+            this.warn('The returned id_token is not parseable.');
+            return null;
+        }
+
+        var crackedToken = {
+            header: matches[1],
+            JWSPayload: matches[2],
+            JWSSig: matches[3]
+        };
+
+        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) {
+            str.push('?response_type=' + responseType);
+            str.push('client_id=' + encodeURIComponent(obj.clientId));
+            if (resource) {
+                str.push('resource=' + encodeURIComponent(resource));
+            }
+
+            str.push('redirect_uri=' + encodeURIComponent(obj.redirectUri));
+            str.push('state=' + encodeURIComponent(obj.state));
+
+            if (obj.hasOwnProperty('slice')) {
+                str.push('slice=' + encodeURIComponent(obj.slice));
+            }
+
+            if (obj.hasOwnProperty('extraQueryParameter')) {
+                str.push(obj.extraQueryParameter);
+            }
+
+            var correlationId = obj.correlationId ? obj.correlationId : this._guid();
+            str.push('client-request-id=' + encodeURIComponent(correlationId));
+        }
+
+        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
+            search = /([^&=]+)=([^&]*)/g,
+            decode = function (s) {
+                return decodeURIComponent(s.replace(pl, ' '));
+            },
+            obj = {};
+        match = search.exec(query);
+        while (match) {
+            obj[decode(match[1])] = decode(match[2]);
+            match = search.exec(query);
+        }
+
+        return obj;
+    };
+
+    /**
+     * Converts decimal value to hex equivalent
+     * @ignore
+     */
+    AuthenticationContext.prototype._decimalToHex = function (number) {
+        var hex = number.toString(16);
+        while (hex.length < 2) {
+            hex = '0' + hex;
+        }
+        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
+        // pseudo-random numbers.
+        // The algorithm is as follows:
+        //     Set the two most significant bits (bits 6 and 7) of the
+        //        clock_seq_hi_and_reserved to zero and one, respectively.
+        //     Set the four most significant bits (bits 12 through 15) of the
+        //        time_hi_and_version field to the 4-bit version number from
+        //        Section 4.1.3. Version4
+        //     Set all the other bits to randomly (or pseudo-randomly) chosen
+        //     values.
+        // UUID                   = time-low "-" time-mid "-"time-high-and-version "-"clock-seq-reserved and low(2hexOctet)"-" node
+        // time-low               = 4hexOctet
+        // time-mid               = 2hexOctet
+        // time-high-and-version  = 2hexOctet
+        // clock-seq-and-reserved = hexOctet:
+        // clock-seq-low          = hexOctet
+        // node                   = 6hexOctet
+        // Format: xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
+        // y could be 1000, 1001, 1010, 1011 since most significant two bits needs to be 10
+        // y values are 8, 9, A, B
+        var cryptoObj = window.crypto || window.msCrypto; // for IE 11
+        if (cryptoObj && cryptoObj.getRandomValues) {
+            var buffer = new Uint8Array(16);
+            cryptoObj.getRandomValues(buffer);
+            //buffer[6] and buffer[7] represents the time_hi_and_version field. We will set the four most significant bits (4 through 7) of buffer[6] to represent decimal number 4 (UUID version number).
+            buffer[6] |= 0x40; //buffer[6] | 01000000 will set the 6 bit to 1.
+            buffer[6] &= 0x4f; //buffer[6] & 01001111 will set the 4, 5, and 7 bit to 0 such that bits 4-7 == 0100 = "4".
+            //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.
+            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';
+            var hex = '0123456789abcdef';
+            var r = 0;
+            var guidResponse = "";
+            for (var i = 0; i < 36; i++) {
+                if (guidHolder[i] !== '-' && guidHolder[i] !== '4') {
+                    // each x and y needs to be random
+                    r = Math.random() * 16 | 0;
+                }
+                if (guidHolder[i] === 'x') {
+                    guidResponse += hex[r];
+                } else if (guidHolder[i] === 'y') {
+                    // clock-seq-and-reserved first hex is filtered and remaining hex values are random
+                    r &= 0x3; // bit and with 0011 to set pos 2 to zero ?0??
+                    r |= 0x8; // set pos 3 to 1 as 1???
+                    guidResponse += hex[r];
+                } else {
+                    guidResponse += guidHolder[i];
+                }
+            }
+            return guidResponse;
+        }
+    };
+    /* jshint ignore:end */
+
+    /**
+     * Calculates the expires in value in milliseconds for the acquired token
+     * @ignore
+     */
+    AuthenticationContext.prototype._expiresIn = function (expires) {
+        // if AAD did not send "expires_in" property, use default expiration of 3599 seconds, for some reason AAD sends 3599 as "expires_in" value instead of 3600
+        if (!expires) expires = 3599;
+        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;
+        }
+
+        this.info('Add adal frame to document:' + iframeId);
+        var adalFrame = document.getElementById(iframeId);
+
+        if (!adalFrame) {
+            if (document.createElement && document.documentElement &&
+                (window.opera || window.navigator.userAgent.indexOf('MSIE 5.0') === -1)) {
+                var ifr = document.createElement('iframe');
+                ifr.setAttribute('id', iframeId);
+                ifr.style.visibility = 'hidden';
+                ifr.style.position = 'absolute';
+                ifr.style.width = ifr.style.height = ifr.borderWidth = '0px';
+
+                adalFrame = document.getElementsByTagName('body')[0].appendChild(ifr);
+            }
+            else if (document.body && document.body.insertAdjacentHTML) {
+                document.body.insertAdjacentHTML('beforeEnd', '<iframe name="' + iframeId + '" id="' + iframeId + '" style="display:none"></iframe>');
+            }
+            if (window.frames && window.frames[iframeId]) {
+                adalFrame = window.frames[iframeId];
+            }
+        }
+
+        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') {
+
+            if (!this._supportsLocalStorage()) {
+                this.info('Local storage is not supported');
+                return false;
+            }
+
+            localStorage.setItem(key, obj);
+
+            return true;
+        }
+
+        // Default as session storage
+        if (!this._supportsSessionStorage()) {
+            this.info('Session storage is not supported');
+            return false;
+        }
+
+        sessionStorage.setItem(key, obj);
+        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') {
+
+            if (!this._supportsLocalStorage()) {
+                this.info('Local storage is not supported');
+                return null;
+            }
+
+            return localStorage.getItem(key);
+        }
+
+        // Default as session storage
+        if (!this._supportsSessionStorage()) {
+            this.info('Session storage is not supported');
+            return null;
+        }
+
+        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'];
+            if (supportsLocalStorage) {
+                window.localStorage.setItem('storageTest', '');
+                window.localStorage.removeItem('storageTest');
+            }
+            return supportsLocalStorage;
+        } catch (e) {
+            return false;
+        }
+    };
+
+    /**
+     * Returns true if browser supports sessionStorage, false otherwise.
+     * @ignore
+     */
+    AuthenticationContext.prototype._supportsSessionStorage = function () {
+        try {
+            var supportsSessionStorage = 'sessionStorage' in window && window['sessionStorage'];
+            if (supportsSessionStorage) {
+                window.sessionStorage.setItem('storageTest', '');
+                window.sessionStorage.removeItem('storageTest');
+            }
+            return supportsSessionStorage;
+        } catch (e) {
+            return false;
+        }
+    };
+
+    /**
+     * Returns a cloned copy of the passed object.
+     * @ignore
+     */
+    AuthenticationContext.prototype._cloneConfig = function (obj) {
+        if (null === obj || 'object' !== typeof obj) {
+            return obj;
+        }
+
+        var copy = {};
+        for (var attr in obj) {
+            if (obj.hasOwnProperty(attr)) {
+                copy[attr] = obj[attr];
+            }
+        }
+        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();
+            var formattedMessage = '';
+
+            if (this.config.correlationId)
+                formattedMessage = timestamp + ':' + this.config.correlationId + '-' + this._libVersion() + '-' + this.CONSTANTS.LEVEL_STRING_MAP[level] + ' ' + message;
+            else
+                formattedMessage = timestamp + ':' + this._libVersion() + '-' + this.CONSTANTS.LEVEL_STRING_MAP[level] + ' ' + message;
+
+            if (error) {
+                formattedMessage += '\nstack:\n' + error.stack;
+            }
+
+            Logging.log(formattedMessage);
+        }
+    };
+
+    /**
+     * 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.14';
+    };
+
+    /**
+     * 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) {
+            return new AuthenticationContext(conf);
+        };
+    }
+
+    return AuthenticationContext;
+
+}());
+
+
+
+
+
+ + + + +
+ + + +
+ +
+ Documentation generated by JSDoc 3.2.2 on Thu Feb 16 2017 18:45:41 GMT-0800 (PST) +
+ + + + + diff --git a/doc/config.html b/doc/config.html new file mode 100644 index 00000000..c735138b --- /dev/null +++ b/doc/config.html @@ -0,0 +1,477 @@ + + + + + JSDoc: Class: config + + + + + + + + + + +
+ +

Class: config

+ + + + + +
+ +
+

+ config +

+ +
+ +
+
+ + + + +
+

new config()

+ + +
+
+ + +
+ Configuration options for Authentication Context. +
+ + + + + + + + + +
+ + +
Properties:
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
tenant + + +string + + + + Your target tenant.
clientID + + +string + + + + Client ID assigned to your app by Azure Active Directory.
redirectUri + + +string + + + + Endpoint at which you expect to receive tokens.Defaults to `window.location.href`.
instance + + +string + + + + Azure Active Directory Instance.Defaults to `https://login.microsoftonline.com/`.
endpoints + + +Array + + + + Collection of {Endpoint-ResourceId} used for automatically attaching tokens in webApi calls.
popUp + + +Boolean + + + + Set this to true to enable login in a popup winodow instead of a full redirect.Defaults to `false`.
localLoginUrl + + +string + + + + Set this to redirect the user to a custom login page.
displayCall + + +function + + + + User defined function of handling the navigation to Azure AD authorization endpoint in case of login. Defaults to 'null'.
postLogoutRedirectUri + + +string + + + + Redirects the user to postLogoutRedirectUri after logout. Defaults is 'redirectUri'.
cacheLocation + + +string + + + + Sets browser storage to either 'localStorage' or sessionStorage'. Defaults to 'sessionStorage'.
anonymousEndpoints + + +Array.<string> + + + + Array of keywords or URI's. Adal will not attach a token to outgoing requests that have these keywords or uri. Defaults to 'null'.
expireOffsetSeconds + + +number + + + + 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.
correlationId + + +string + + + + Unique identifier used to map the request with the response. Defaults to RFC4122 version 4 guid (128 bits).
+ + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
+ + +
+ + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + + +
+ +
+ Documentation generated by JSDoc 3.2.2 on Thu Feb 16 2017 18:45:41 GMT-0800 (PST) +
+ + + + + \ No newline at end of file diff --git a/doc/global.html b/doc/global.html new file mode 100644 index 00000000..7315732e --- /dev/null +++ b/doc/global.html @@ -0,0 +1,414 @@ + + + + + JSDoc: Global + + + + + + + + + + +
+ +

Global

+ + + + + +
+ +
+

+ +

+ +
+ +
+
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+ + + + + + + + + + + + + + + + +

Type Definitions

+ +
+ +
+

tokenCallback(error_description, token, error)

+ + +
+
+ + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
error_description + + +string + + + + error description returned from AAD if token request fails.
token + + +string + + + + token returned from AAD if token request is successful.
error + + +string + + + + error message returned from AAD if token request fails.
+ + + +
+ + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
+ + + +
+

userCallback(error, user)

+ + +
+
+ + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
error + + +string + + + + error message if user info is not available.
user + + +User + + + + user object retrieved from the cache.
+ + + +
+ + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
+ +
+ + + +
+ +
+ + + + +
+ + + +
+ +
+ Documentation generated by JSDoc 3.2.2 on Thu Feb 16 2017 18:45:41 GMT-0800 (PST) +
+ + + + + \ No newline at end of file diff --git a/doc/index.html b/doc/index.html new file mode 100644 index 00000000..85497a8b --- /dev/null +++ b/doc/index.html @@ -0,0 +1,63 @@ + + + + + JSDoc: Index + + + + + + + + + + +
+ +

Index

+ + + + + + + +

+ + + + + + + + + + + + + + + + + + + +
+ + + +
+ +
+ Documentation generated by JSDoc 3.2.2 on Thu Feb 16 2017 18:45:41 GMT-0800 (PST) +
+ + + + + \ No newline at end of file diff --git a/doc/scripts/linenumber.js b/doc/scripts/linenumber.js new file mode 100644 index 00000000..613865d0 --- /dev/null +++ b/doc/scripts/linenumber.js @@ -0,0 +1,17 @@ +(function() { + var counter = 0; + var numbered; + var source = document.getElementsByClassName('prettyprint source'); + + if (source && source[0]) { + source = source[0].getElementsByTagName('code')[0]; + + numbered = source.innerHTML.split('\n'); + numbered = numbered.map(function(item) { + counter++; + return '' + item; + }); + + source.innerHTML = numbered.join('\n'); + } +})(); diff --git a/doc/scripts/prettify/Apache-License-2.0.txt b/doc/scripts/prettify/Apache-License-2.0.txt new file mode 100644 index 00000000..d6456956 --- /dev/null +++ b/doc/scripts/prettify/Apache-License-2.0.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/doc/scripts/prettify/lang-css.js b/doc/scripts/prettify/lang-css.js new file mode 100644 index 00000000..041e1f59 --- /dev/null +++ b/doc/scripts/prettify/lang-css.js @@ -0,0 +1,2 @@ +PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\f\r ]+/,null," \t\r\n "]],[["str",/^"(?:[^\n\f\r"\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*"/,null],["str",/^'(?:[^\n\f\r'\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*'/,null],["lang-css-str",/^url\(([^"')]*)\)/i],["kwd",/^(?:url|rgb|!important|@import|@page|@media|@charset|inherit)(?=[^\w-]|$)/i,null],["lang-css-kw",/^(-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*)\s*:/i],["com",/^\/\*[^*]*\*+(?:[^*/][^*]*\*+)*\//],["com", +/^(?:<\!--|--\>)/],["lit",/^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i],["lit",/^#[\da-f]{3,6}/i],["pln",/^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i],["pun",/^[^\s\w"']+/]]),["css"]);PR.registerLangHandler(PR.createSimpleLexer([],[["kwd",/^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i]]),["css-kw"]);PR.registerLangHandler(PR.createSimpleLexer([],[["str",/^[^"')]+/]]),["css-str"]); diff --git a/doc/scripts/prettify/prettify.js b/doc/scripts/prettify/prettify.js new file mode 100644 index 00000000..eef5ad7e --- /dev/null +++ b/doc/scripts/prettify/prettify.js @@ -0,0 +1,28 @@ +var q=null;window.PR_SHOULD_USE_CONTINUATION=!0; +(function(){function L(a){function m(a){var f=a.charCodeAt(0);if(f!==92)return f;var b=a.charAt(1);return(f=r[b])?f:"0"<=b&&b<="7"?parseInt(a.substring(1),8):b==="u"||b==="x"?parseInt(a.substring(2),16):a.charCodeAt(1)}function e(a){if(a<32)return(a<16?"\\x0":"\\x")+a.toString(16);a=String.fromCharCode(a);if(a==="\\"||a==="-"||a==="["||a==="]")a="\\"+a;return a}function h(a){for(var f=a.substring(1,a.length-1).match(/\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\[0-3][0-7]{0,2}|\\[0-7]{1,2}|\\[\S\s]|[^\\]/g),a= +[],b=[],o=f[0]==="^",c=o?1:0,i=f.length;c122||(d<65||j>90||b.push([Math.max(65,j)|32,Math.min(d,90)|32]),d<97||j>122||b.push([Math.max(97,j)&-33,Math.min(d,122)&-33]))}}b.sort(function(a,f){return a[0]-f[0]||f[1]-a[1]});f=[];j=[NaN,NaN];for(c=0;ci[0]&&(i[1]+1>i[0]&&b.push("-"),b.push(e(i[1])));b.push("]");return b.join("")}function y(a){for(var f=a.source.match(/\[(?:[^\\\]]|\\[\S\s])*]|\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\\d+|\\[^\dux]|\(\?[!:=]|[()^]|[^()[\\^]+/g),b=f.length,d=[],c=0,i=0;c=2&&a==="["?f[c]=h(j):a!=="\\"&&(f[c]=j.replace(/[A-Za-z]/g,function(a){a=a.charCodeAt(0);return"["+String.fromCharCode(a&-33,a|32)+"]"}));return f.join("")}for(var t=0,s=!1,l=!1,p=0,d=a.length;p=5&&"lang-"===b.substring(0,5))&&!(o&&typeof o[1]==="string"))c=!1,b="src";c||(r[f]=b)}i=d;d+=f.length;if(c){c=o[1];var j=f.indexOf(c),k=j+c.length;o[2]&&(k=f.length-o[2].length,j=k-c.length);b=b.substring(5);B(l+i,f.substring(0,j),e,p);B(l+i+j,c,C(b,c),p);B(l+i+k,f.substring(k),e,p)}else p.push(l+i,b)}a.e=p}var h={},y;(function(){for(var e=a.concat(m), +l=[],p={},d=0,g=e.length;d=0;)h[n.charAt(k)]=r;r=r[1];n=""+r;p.hasOwnProperty(n)||(l.push(r),p[n]=q)}l.push(/[\S\s]/);y=L(l)})();var t=m.length;return e}function u(a){var m=[],e=[];a.tripleQuotedStrings?m.push(["str",/^(?:'''(?:[^'\\]|\\[\S\s]|''?(?=[^']))*(?:'''|$)|"""(?:[^"\\]|\\[\S\s]|""?(?=[^"]))*(?:"""|$)|'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$))/,q,"'\""]):a.multiLineStrings?m.push(["str",/^(?:'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$)|`(?:[^\\`]|\\[\S\s])*(?:`|$))/, +q,"'\"`"]):m.push(["str",/^(?:'(?:[^\n\r'\\]|\\.)*(?:'|$)|"(?:[^\n\r"\\]|\\.)*(?:"|$))/,q,"\"'"]);a.verbatimStrings&&e.push(["str",/^@"(?:[^"]|"")*(?:"|$)/,q]);var h=a.hashComments;h&&(a.cStyleComments?(h>1?m.push(["com",/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,q,"#"]):m.push(["com",/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\n\r]*)/,q,"#"]),e.push(["str",/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,q])):m.push(["com",/^#[^\n\r]*/, +q,"#"]));a.cStyleComments&&(e.push(["com",/^\/\/[^\n\r]*/,q]),e.push(["com",/^\/\*[\S\s]*?(?:\*\/|$)/,q]));a.regexLiterals&&e.push(["lang-regex",/^(?:^^\.?|[!+-]|!=|!==|#|%|%=|&|&&|&&=|&=|\(|\*|\*=|\+=|,|-=|->|\/|\/=|:|::|;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|[?@[^]|\^=|\^\^|\^\^=|{|\||\|=|\|\||\|\|=|~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\s*(\/(?=[^*/])(?:[^/[\\]|\\[\S\s]|\[(?:[^\\\]]|\\[\S\s])*(?:]|$))+\/)/]);(h=a.types)&&e.push(["typ",h]);a=(""+a.keywords).replace(/^ | $/g, +"");a.length&&e.push(["kwd",RegExp("^(?:"+a.replace(/[\s,]+/g,"|")+")\\b"),q]);m.push(["pln",/^\s+/,q," \r\n\t\xa0"]);e.push(["lit",/^@[$_a-z][\w$@]*/i,q],["typ",/^(?:[@_]?[A-Z]+[a-z][\w$@]*|\w+_t\b)/,q],["pln",/^[$_a-z][\w$@]*/i,q],["lit",/^(?:0x[\da-f]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+-]?\d+)?)[a-z]*/i,q,"0123456789"],["pln",/^\\[\S\s]?/,q],["pun",/^.[^\s\w"-$'./@\\`]*/,q]);return x(m,e)}function D(a,m){function e(a){switch(a.nodeType){case 1:if(k.test(a.className))break;if("BR"===a.nodeName)h(a), +a.parentNode&&a.parentNode.removeChild(a);else for(a=a.firstChild;a;a=a.nextSibling)e(a);break;case 3:case 4:if(p){var b=a.nodeValue,d=b.match(t);if(d){var c=b.substring(0,d.index);a.nodeValue=c;(b=b.substring(d.index+d[0].length))&&a.parentNode.insertBefore(s.createTextNode(b),a.nextSibling);h(a);c||a.parentNode.removeChild(a)}}}}function h(a){function b(a,d){var e=d?a.cloneNode(!1):a,f=a.parentNode;if(f){var f=b(f,1),g=a.nextSibling;f.appendChild(e);for(var h=g;h;h=g)g=h.nextSibling,f.appendChild(h)}return e} +for(;!a.nextSibling;)if(a=a.parentNode,!a)return;for(var a=b(a.nextSibling,0),e;(e=a.parentNode)&&e.nodeType===1;)a=e;d.push(a)}var k=/(?:^|\s)nocode(?:\s|$)/,t=/\r\n?|\n/,s=a.ownerDocument,l;a.currentStyle?l=a.currentStyle.whiteSpace:window.getComputedStyle&&(l=s.defaultView.getComputedStyle(a,q).getPropertyValue("white-space"));var p=l&&"pre"===l.substring(0,3);for(l=s.createElement("LI");a.firstChild;)l.appendChild(a.firstChild);for(var d=[l],g=0;g=0;){var h=m[e];A.hasOwnProperty(h)?window.console&&console.warn("cannot override language handler %s",h):A[h]=a}}function C(a,m){if(!a||!A.hasOwnProperty(a))a=/^\s*=o&&(h+=2);e>=c&&(a+=2)}}catch(w){"console"in window&&console.log(w&&w.stack?w.stack:w)}}var v=["break,continue,do,else,for,if,return,while"],w=[[v,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"], +"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"],F=[w,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"],G=[w,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"], +H=[G,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"],w=[w,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"],I=[v,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"], +J=[v,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"],v=[v,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"],K=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/,N=/\S/,O=u({keywords:[F,H,w,"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END"+ +I,J,v],hashComments:!0,cStyleComments:!0,multiLineStrings:!0,regexLiterals:!0}),A={};k(O,["default-code"]);k(x([],[["pln",/^[^]*(?:>|$)/],["com",/^<\!--[\S\s]*?(?:--\>|$)/],["lang-",/^<\?([\S\s]+?)(?:\?>|$)/],["lang-",/^<%([\S\s]+?)(?:%>|$)/],["pun",/^(?:<[%?]|[%?]>)/],["lang-",/^]*>([\S\s]+?)<\/xmp\b[^>]*>/i],["lang-js",/^]*>([\S\s]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\S\s]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]), +["default-markup","htm","html","mxml","xhtml","xml","xsl"]);k(x([["pln",/^\s+/,q," \t\r\n"],["atv",/^(?:"[^"]*"?|'[^']*'?)/,q,"\"'"]],[["tag",/^^<\/?[a-z](?:[\w-.:]*\w)?|\/?>$/i],["atn",/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^\s"'>]*(?:[^\s"'/>]|\/(?=\s)))/],["pun",/^[/<->]+/],["lang-js",/^on\w+\s*=\s*"([^"]+)"/i],["lang-js",/^on\w+\s*=\s*'([^']+)'/i],["lang-js",/^on\w+\s*=\s*([^\s"'>]+)/i],["lang-css",/^style\s*=\s*"([^"]+)"/i],["lang-css",/^style\s*=\s*'([^']+)'/i],["lang-css", +/^style\s*=\s*([^\s"'>]+)/i]]),["in.tag"]);k(x([],[["atv",/^[\S\s]+/]]),["uq.val"]);k(u({keywords:F,hashComments:!0,cStyleComments:!0,types:K}),["c","cc","cpp","cxx","cyc","m"]);k(u({keywords:"null,true,false"}),["json"]);k(u({keywords:H,hashComments:!0,cStyleComments:!0,verbatimStrings:!0,types:K}),["cs"]);k(u({keywords:G,cStyleComments:!0}),["java"]);k(u({keywords:v,hashComments:!0,multiLineStrings:!0}),["bsh","csh","sh"]);k(u({keywords:I,hashComments:!0,multiLineStrings:!0,tripleQuotedStrings:!0}), +["cv","py"]);k(u({keywords:"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END",hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["perl","pl","pm"]);k(u({keywords:J,hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["rb"]);k(u({keywords:w,cStyleComments:!0,regexLiterals:!0}),["js"]);k(u({keywords:"all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes", +hashComments:3,cStyleComments:!0,multilineStrings:!0,tripleQuotedStrings:!0,regexLiterals:!0}),["coffee"]);k(x([],[["str",/^[\S\s]+/]]),["regex"]);window.prettyPrintOne=function(a,m,e){var h=document.createElement("PRE");h.innerHTML=a;e&&D(h,e);E({g:m,i:e,h:h});return h.innerHTML};window.prettyPrint=function(a){function m(){for(var e=window.PR_SHOULD_USE_CONTINUATION?l.now()+250:Infinity;p=0){var k=k.match(g),f,b;if(b= +!k){b=n;for(var o=void 0,c=b.firstChild;c;c=c.nextSibling)var i=c.nodeType,o=i===1?o?b:c:i===3?N.test(c.nodeValue)?b:o:o;b=(f=o===b?void 0:o)&&"CODE"===f.tagName}b&&(k=f.className.match(g));k&&(k=k[1]);b=!1;for(o=n.parentNode;o;o=o.parentNode)if((o.tagName==="pre"||o.tagName==="code"||o.tagName==="xmp")&&o.className&&o.className.indexOf("prettyprint")>=0){b=!0;break}b||((b=(b=n.className.match(/\blinenums\b(?::(\d+))?/))?b[1]&&b[1].length?+b[1]:!0:!1)&&D(n,b),d={g:k,h:n,i:b},E(d))}}p