From 2895778dbe1b399ee47f5338786592728a975bfb Mon Sep 17 00:00:00 2001 From: Saravanan Muthukrishnan Date: Tue, 1 Mar 2016 17:33:43 -0800 Subject: [PATCH 1/6] Fix for the issue #195 - Token renew only works if endpoints are passed to config, even thought it's marked optional --- lib/adal-angular.js | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/lib/adal-angular.js b/lib/adal-angular.js index 4bb0b2e1..4bddbcb0 100644 --- a/lib/adal-angular.js +++ b/lib/adal-angular.js @@ -300,27 +300,17 @@ if (typeof module !== 'undefined' && module.exports) { } var tokenStored = authService.getCachedToken(resource); - var isEndpoint = false; if (tokenStored) { authService.info('Token is avaliable for this url ' + config.url); // check endpoint mapping if provided config.headers.Authorization = 'Bearer ' + tokenStored; return config; } else { - - if (authService.config) { - for (var endpointUrl in authService.config.endpoints) { - if (config.url.indexOf(endpointUrl) > -1) { - isEndpoint = true; - } - } - } - // Cancel request if login is starting if (authService.loginInProgress()) { authService.info('login already start.'); return $q.reject(); - } else if (authService.config && isEndpoint) { + } else { // external endpoints // delayed request to return after iframe completes var delayedRequest = $q.defer(); From f7b78cc481e5c02731dab13d4a29400e38c58812 Mon Sep 17 00:00:00 2001 From: Tushar Gupta Date: Sat, 19 Mar 2016 15:12:01 -0700 Subject: [PATCH 2/6] bug fixes for 1.0.9 --- lib/adal-angular.js | 73 +++++----- lib/adal.js | 272 +++++++++++++++--------------------- package.json | 9 +- tests/angularModuleSpec.js | 124 ++++++++++++++-- tests/testApp.js | 37 ++--- tests/unit/spec/AdalSpec.js | 51 +++---- 6 files changed, 301 insertions(+), 265 deletions(-) diff --git a/lib/adal-angular.js b/lib/adal-angular.js index 4bddbcb0..e50d5c2f 100644 --- a/lib/adal-angular.js +++ b/lib/adal-angular.js @@ -89,6 +89,7 @@ if (typeof module !== 'undefined' && module.exports) { } if (requestInfo.requestType !== _adal.REQUEST_TYPE.LOGIN) { + _adal._renewActive = false; _adal.callback = $window.parent.AuthenticationContext().callback; if (requestInfo.requestType === _adal.REQUEST_TYPE.RENEW_TOKEN) { _adal.callback = $window.parent.callBackMappedToRenewStates[requestInfo.stateResponse]; @@ -121,8 +122,7 @@ if (typeof module !== 'undefined' && module.exports) { var loginStartPage = _adal._getItem(_adal.CONSTANTS.STORAGE.START_PAGE); if (loginStartPage) { // Check to see if any params were stored - var paramsJSON = _adal._getItem(_adal.CONSTANTS.STORAGE.START_PAGE_PARAMS); - + var paramsJSON = _adal._getItem(_adal.CONSTANTS.STORAGE.START_PAGE_PARAMS); if (paramsJSON) { // If params were stored redirect to the page and then // initialize the params @@ -139,23 +139,26 @@ if (typeof module !== 'undefined' && module.exports) { } } } + else { + // state did not match, broadcast an error + $rootScope.$broadcast('adal:stateMismatch', _adal._getItem(_adal.CONSTANTS.STORAGE.ERROR_DESCRIPTION)); + } } else { // No callback. App resumes after closing or moving to new page. // Check token and username updateDataFromCache(_adal.config.loginResource); if (!_adal._renewActive && !_oauthData.isAuthenticated && _oauthData.userName) { - if (!_adal._getItem(_adal.CONSTANTS.STORAGE.FAILED_RENEW)) { - // Idtoken is expired or not present - _adal.acquireToken(_adal.config.loginResource, function (error, tokenOut) { - if (error) { - $rootScope.$broadcast('adal:loginFailure', 'auto renew failure'); - } else { - if (tokenOut) { - _oauthData.isAuthenticated = true; - } + // Idtoken is expired or not present + _adal._renewActive = true; + _adal.acquireToken(_adal.config.loginResource, function (error, tokenOut) { + if (error) { + $rootScope.$broadcast('adal:loginFailure', 'auto renew failure'); + } else { + if (tokenOut) { + _oauthData.isAuthenticated = true; } - }); - } + } + }); } } @@ -184,25 +187,25 @@ if (typeof module !== 'undefined' && module.exports) { var routeChangeHandler = function (e, nextRoute) { if (nextRoute && nextRoute.$$route && isADLoginRequired(nextRoute.$$route, _adal.config)) { - if (!_oauthData.isAuthenticated) { + if (!_oauthData.isAuthenticated && !_adal._renewActive) { _adal.info('Route change event for:' + $location.$$url); loginHandler(); } } }; - var stateChangeHandler = function (e, toState, toParams, fromState, fromParams){ + var stateChangeHandler = function (e, toState, toParams, fromState, fromParams) { if (toState && isADLoginRequired(toState, _adal.config)) { - if (!_oauthData.isAuthenticated) { + if (!_oauthData.isAuthenticated && !_adal._renewActive) { // $location.$$url is set as the page we are coming from // Update it so we can store the actual location we want to // redirect to upon returning $location.$$url = toState.url; - + // Parameters are not stored in the url on stateChange so // we store them _adal._saveItem(_adal.CONSTANTS.STORAGE.START_PAGE_PARAMS, JSON.stringify(toParams)); - + _adal.info('State change event for:' + $location.$$url); loginHandler(); } @@ -239,7 +242,9 @@ if (typeof module !== 'undefined' && module.exports) { acquireToken: function (resource) { // automated token request call var deferred = $q.defer(); + _adal._renewActive = true; _adal.acquireToken(resource, function (error, tokenOut) { + _adal._renewActive = false; if (error) { _adal.error('Error when acquiring token for resource: ' + resource, error); deferred.reject(error); @@ -271,11 +276,11 @@ if (typeof module !== 'undefined' && module.exports) { }, clearCacheForResource: function (resource) { _adal.clearCacheForResource(resource); - }, - info: function(message) { + }, + info: function (message) { _adal.info(message); - }, - verbose: function(message) { + }, + verbose: function (message) { _adal.verbose(message); } }; @@ -309,7 +314,7 @@ if (typeof module !== 'undefined' && module.exports) { // Cancel request if login is starting if (authService.loginInProgress()) { authService.info('login already start.'); - return $q.reject(); + return $q.reject('login in progress, cancelling the request'); } else { // external endpoints // delayed request to return after iframe completes @@ -318,9 +323,9 @@ if (typeof module !== 'undefined' && module.exports) { authService.verbose('Token is avaliable'); config.headers.Authorization = 'Bearer ' + token; delayedRequest.resolve(config); - }, function (err) { - delayedRequest.reject(err); - }); + }, function (err) { + delayedRequest.reject(err); + }); return delayedRequest.promise; } @@ -331,13 +336,17 @@ if (typeof module !== 'undefined' && module.exports) { }, responseError: function (rejection) { authService.info('Getting error in the response'); - if (rejection && rejection.status === 401) { - var resource = authService.getResourceForEndpoint(rejection.config.url); - authService.clearCacheForResource(resource); - $rootScope.$broadcast('adal:notAuthorized', rejection, resource); + if (rejection) { + if (rejection.status === 401) { + var resource = authService.getResourceForEndpoint(rejection.config.url); + authService.clearCacheForResource(resource); + $rootScope.$broadcast('adal:notAuthorized', rejection, resource); + } + else { + $rootScope.$broadcast('adal:errorResponse', rejection); + } + return $q.reject(rejection); } - - return $q.reject(rejection); } }; }]); diff --git a/lib/adal.js b/lib/adal.js index 25b79dd6..b4f7099d 100644 --- a/lib/adal.js +++ b/lib/adal.js @@ -63,10 +63,9 @@ AuthenticationContext = function (config) { this.REQUEST_TYPE = { LOGIN: 'LOGIN', RENEW_TOKEN: 'RENEW_TOKEN', - ID_TOKEN: 'ID_TOKEN', UNKNOWN: 'UNKNOWN' }; - + /** * Enum for storage constants * @enum {string} @@ -83,11 +82,9 @@ AuthenticationContext = function (config) { EXPIRATION_KEY: 'adal.expiration.key', START_PAGE: 'adal.start.page', START_PAGE_PARAMS: 'adal.start.page.params', - FAILED_RENEW: 'adal.failed.renew', STATE_LOGIN: 'adal.state.login', STATE_RENEW: 'adal.state.renew', STATE_RENEW_RESOURCE: 'adal.state.renew.resource', - STATE_IDTOKEN: 'adal.state.idtoken', NONCE_IDTOKEN: 'adal.nonce.idtoken', SESSION_STATE: 'adal.session.state', USERNAME: 'adal.username', @@ -114,18 +111,18 @@ AuthenticationContext = function (config) { 3: 'VERBOSE:' } }; - + 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; - + // private this._user = null; this._activeRenewals = {}; @@ -139,27 +136,25 @@ AuthenticationContext = function (config) { if (config.displayCall && typeof config.displayCall !== 'function') { throw new Error('displayCall is not a function'); } - + if (!config.clientId) { throw new Error('clientId is required'); } - + if (!config.correlationId) { config.correlationId = this._guid(); } - + this.config = this._cloneConfig(config); - + // App can request idtoken for itself using clientid as resource if (!this.config.loginResource) { this.config.loginResource = this.config.clientId; } - + if (!this.config.redirectUri) { this.config.redirectUri = window.location.href; } - - this.config.resource = this.config.loginResource || ''; }; /** @@ -176,11 +171,10 @@ AuthenticationContext.prototype.login = function () { 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.FAILED_RENEW, ''); 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.frameCallInProgress = false; this._loginInProgress = true; @@ -211,13 +205,13 @@ AuthenticationContext.prototype.getCachedToken = function (resource) { if (!this._hasResource(resource)) { return null; } - + var token = this._getItem(this.CONSTANTS.STORAGE.ACCESS_TOKEN_KEY + resource); var expired = 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)) { return token; } else { @@ -235,7 +229,7 @@ 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; @@ -273,20 +267,13 @@ 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); - if (!this._hasResource(resource)) { - var keys = this._getItem(this.CONSTANTS.STORAGE.TOKEN_KEYS) || ''; - this._saveItem(this.CONSTANTS.STORAGE.TOKEN_KEYS, keys + resource + this.CONSTANTS.RESOURCE_DELIMETER); - } - var frameHandle = this._addAdalFrame('adalRenewFrame' + resource); var expectedState = this._guid() + '|' + resource; this._idTokenNonce = this._guid(); this.config.state = expectedState; // renew happens in iframe, so it keeps javascript context this._renewStates.push(expectedState); - - this._saveItem(this.CONSTANTS.STORAGE.FAILED_RENEW, ''); - + this.verbose('Renew token Expected state: ' + expectedState); var urlNavigate = this._getNavigateUrl('token', resource) + '&prompt=none&login_hint=' + encodeURIComponent(this._user.userName); @@ -308,11 +295,6 @@ AuthenticationContext.prototype._renewToken = function (resource, callback) { AuthenticationContext.prototype._renewIdToken = function (callback) { // use iframe to try refresh token this.info('renewIdToken is called'); - if (!this._hasResource(this.config.clientId)) { - var keys = this._getItem(this.CONSTANTS.STORAGE.TOKEN_KEYS) || ''; - this._saveItem(this.CONSTANTS.STORAGE.TOKEN_KEYS, keys + this.config.clientId + this.CONSTANTS.RESOURCE_DELIMETER); - } - var frameHandle = this._addAdalFrame('adalIdTokenFrame'); var expectedState = this._guid() + '|' + this.config.clientId; this._idTokenNonce = this._guid(); @@ -320,9 +302,7 @@ AuthenticationContext.prototype._renewIdToken = function (callback) { this.config.state = expectedState; // renew happens in iframe, so it keeps javascript context this._renewStates.push(expectedState); - this._saveItem(this.CONSTANTS.STORAGE.STATE_RENEW, expectedState); - this._saveItem(this.CONSTANTS.STORAGE.FAILED_RENEW, ''); - + this.verbose('Renew Idtoken Expected state: ' + expectedState); var urlNavigate = this._getNavigateUrl('id_token', null) + '&prompt=none&login_hint=' + encodeURIComponent(this._user.userName); @@ -372,26 +352,20 @@ AuthenticationContext.prototype.acquireToken = function (resource, callback) { callback('resource is required', null); return; } - + var token = this.getCachedToken(resource); if (token) { this.info('Token is already in cache for resource:' + resource); callback(null, token); return; } - - if (this._getItem(this.CONSTANTS.STORAGE.FAILED_RENEW)) { - this.info('renewToken is failed for resource ' + resource + ':' + this._getItem(this.CONSTANTS.STORAGE.FAILED_RENEW)); - callback(this._getItem(this.CONSTANTS.STORAGE.FAILED_RENEW), null); - return; - } - + if (!this._user) { this.warn('User login is required'); callback('User login is required', null); return; } - + // refresh attept with iframe //Already renewing for this resource, callback when we get the token. if (this._activeRenewals[resource]) { @@ -429,11 +403,9 @@ AuthenticationContext.prototype.promptUser = function (urlNavigate) { 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.FAILED_RENEW, ''); this._saveItem(this.CONSTANTS.STORAGE.SESSION_STATE, ''); this._saveItem(this.CONSTANTS.STORAGE.STATE_LOGIN, ''); this._renewStates = []; - this._saveItem(this.CONSTANTS.STORAGE.STATE_IDTOKEN, ''); this._saveItem(this.CONSTANTS.STORAGE.START_PAGE, ''); this._saveItem(this.CONSTANTS.STORAGE.START_PAGE_PARAMS, ''); this._saveItem(this.CONSTANTS.STORAGE.USERNAME, ''); @@ -441,7 +413,7 @@ AuthenticationContext.prototype.clearCache = function () { 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++) { @@ -456,9 +428,7 @@ AuthenticationContext.prototype.clearCache = function () { * Clear cache items for a resource. */ AuthenticationContext.prototype.clearCacheForResource = function (resource) { - this._saveItem(this.CONSTANTS.STORAGE.FAILED_RENEW, ''); this._saveItem(this.CONSTANTS.STORAGE.STATE_RENEW, ''); - this._saveItem(this.CONSTANTS.STORAGE.STATE_IDTOKEN, ''); this._saveItem(this.CONSTANTS.STORAGE.ERROR, ''); this._saveItem(this.CONSTANTS.STORAGE.ERROR_DESCRIPTION, ''); if (this._hasResource(resource)) { @@ -479,15 +449,15 @@ AuthenticationContext.prototype.logOut = function () { if (this.config.tenant) { tenant = this.config.tenant; } - + if (this.config.instance) { this.instance = this.config.instance; } - + if (this.config.postLogoutRedirectUri) { logout = 'post_logout_redirect_uri=' + encodeURIComponent(this.config.postLogoutRedirectUri); } - + var urlNavigate = this.instance + tenant + '/oauth2/logout?' + logout; this.info('Logout navigate to: ' + urlNavigate); this.promptUser(urlNavigate); @@ -513,15 +483,15 @@ AuthenticationContext.prototype.getUser = function (callback) { if (typeof callback !== 'function') { throw new Error('callback is not a function'); } - + this.callback = callback; - + // user in memory if (this._user) { this.callback(null, this._user); return; } - + // frame is used to get idtoken var idtoken = this._getItem(this.CONSTANTS.STORAGE.IDTOKEN); if (!this._isEmpty(idtoken)) { @@ -540,7 +510,7 @@ AuthenticationContext.prototype._getDomainHint = function () { // local part can include @ in quotes. Sending last part handles that. return parts[parts.length - 1]; } - + return ''; }; @@ -548,14 +518,14 @@ 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')) { @@ -566,7 +536,7 @@ AuthenticationContext.prototype._createUser = function (idToken) { } } - + return user; }; @@ -576,7 +546,7 @@ AuthenticationContext.prototype._getHash = function (hash) { } else if (hash.indexOf('#') > -1) { hash = hash.substring(1); } - + return hash; }; @@ -622,35 +592,29 @@ AuthenticationContext.prototype.getRequestInfo = function (hash) { 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.verbose('No state returned'); + 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 - switch (stateResponse) { - case this._getItem(this.CONSTANTS.STORAGE.STATE_LOGIN): - requestInfo.requestType = this.REQUEST_TYPE.LOGIN; - requestInfo.stateMatch = true; - break; - - case this._getItem(this.CONSTANTS.STORAGE.STATE_IDTOKEN): - requestInfo.requestType = this.REQUEST_TYPE.ID_TOKEN; - this._saveItem(this.CONSTANTS.STORAGE.STATE_IDTOKEN, ''); - requestInfo.stateMatch = true; - break; + 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; @@ -664,7 +628,7 @@ AuthenticationContext.prototype.getRequestInfo = function (hash) { } } } - + return requestInfo; }; @@ -675,7 +639,7 @@ AuthenticationContext.prototype._getResourceFromState = function (state) { return state.substring(splitIndex + 1); } } - + return ''; }; @@ -688,20 +652,19 @@ 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, ''); - + // 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.FAILED_RENEW, 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.errorDescription); } } else { - + // It must verify the state from redirect if (requestInfo.stateMatch) { // record tokens to storage if exists @@ -709,28 +672,28 @@ AuthenticationContext.prototype.saveTokenFromHash = function (requestInfo) { if (requestInfo.parameters.hasOwnProperty(this.CONSTANTS.SESSION_STATE)) { this._saveItem(this.CONSTANTS.STORAGE.SESSION_STATE, requestInfo.parameters[this.CONSTANTS.SESSION_STATE]); } - + var keys, resource; - + if (requestInfo.parameters.hasOwnProperty(this.CONSTANTS.ACCESS_TOKEN)) { this.info('Fragment has access token'); // default resource resource = this.config.loginResource; + if (requestInfo.requestType === this.REQUEST_TYPE.RENEW_TOKEN) { + resource = this._getResourceFromState(requestInfo.stateResponse); + } + 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); } - - if (requestInfo.requestType === this.REQUEST_TYPE.RENEW_TOKEN) { - resource = this._getResourceFromState(requestInfo.stateResponse); - } - // 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) { @@ -739,7 +702,7 @@ AuthenticationContext.prototype.saveTokenFromHash = function (requestInfo) { 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)) { @@ -753,10 +716,7 @@ AuthenticationContext.prototype.saveTokenFromHash = function (requestInfo) { } } else { this._saveItem(this.CONSTANTS.STORAGE.ERROR, 'Invalid_state'); - this._saveItem(this.CONSTANTS.STORAGE.ERROR_DESCRIPTION, 'Invalid_state'); - if (requestInfo.requestType === this.REQUEST_TYPE.LOGIN) { - this._saveItem(this.CONSTANTS.STORAGE.LOGIN_ERROR, 'State is not same as ' + requestInfo.stateResponse); - } + this._saveItem(this.CONSTANTS.STORAGE.ERROR_DESCRIPTION, 'Invalid_state. state: ' + requestInfo.stateResponse); } } }; @@ -775,7 +735,7 @@ AuthenticationContext.prototype.getResourceForEndpoint = function (endpoint) { } } } - + // 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 @@ -784,12 +744,12 @@ AuthenticationContext.prototype.getResourceForEndpoint = function (endpoint) { 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 { - // 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; }; @@ -812,9 +772,7 @@ AuthenticationContext.prototype.handleWindowCallback = function () { this.info('Returned from redirect url'); this.saveTokenFromHash(requestInfo); var callback = null; - if ((requestInfo.requestType === this.REQUEST_TYPE.RENEW_TOKEN || - requestInfo.requestType === this.REQUEST_TYPE.ID_TOKEN) && - window.parent) { + if ((requestInfo.requestType === this.REQUEST_TYPE.RENEW_TOKEN && window.parent)) { // iframe call but same single page this.verbose('Window is in iframe'); callback = window.parent.callBackMappedToRenewStates[requestInfo.stateResponse]; @@ -823,16 +781,12 @@ AuthenticationContext.prototype.handleWindowCallback = function () { this.verbose('Window is redirecting'); callback = this.callback; } - + window.location.hash = ''; window.location = this._getItem(this.CONSTANTS.STORAGE.LOGIN_REQUEST); if (requestInfo.requestType === this.REQUEST_TYPE.RENEW_TOKEN) { callback(this._getItem(this.CONSTANTS.STORAGE.ERROR_DESCRIPTION), requestInfo.parameters[this.CONSTANTS.ACCESS_TOKEN] || requestInfo.parameters[this.CONSTANTS.ID_TOKEN]); return; - } else if (requestInfo.requestType === this.REQUEST_TYPE.ID_TOKEN) { - // JS context may not have the user if callback page was different, so parse idtoken again to callback - callback(this._getItem(this.CONSTANTS.STORAGE.ERROR_DESCRIPTION), this._createUser(this._getItem(this.CONSTANTS.STORAGE.IDTOKEN))); - return; } } }; @@ -842,12 +796,12 @@ AuthenticationContext.prototype._getNavigateUrl = function (responseType, resour if (this.config.tenant) { tenant = this.config.tenant; } - + if (this.config.instance) { this.instance = this.config.instance; } - - var urlNavigate = this.instance + tenant + '/oauth2/authorize' + this._serialize(responseType, this.config, resource) + this._addClientId(); + + var urlNavigate = this.instance + tenant + '/oauth2/authorize' + this._serialize(responseType, this.config, resource) + this._addXClientParameters(); this.info('Navigate url:' + urlNavigate); return urlNavigate; }; @@ -858,7 +812,7 @@ AuthenticationContext.prototype._extractIdToken = function (encodedIdToken) { if (!decodedToken) { return null; } - + try { var base64IdToken = decodedToken.JWSPayload; var base64Decoded = this._base64DecodeStringUrlSafe(base64IdToken); @@ -866,13 +820,13 @@ AuthenticationContext.prototype._extractIdToken = function (encodedIdToken) { 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; }; @@ -890,7 +844,7 @@ AuthenticationContext.prototype._extractUserName = function (encodedIdToken) { } catch (err) { this.error('The returned id_token could not be decoded', err); } - + return null; }; @@ -914,7 +868,7 @@ AuthenticationContext.prototype._decode = function (base64IdToken) { 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 @@ -923,7 +877,7 @@ AuthenticationContext.prototype._decode = function (base64IdToken) { 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; @@ -939,40 +893,40 @@ AuthenticationContext.prototype._decode = function (base64IdToken) { 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; }; // Adal.node js crack function AuthenticationContext.prototype._decodeJwt = function (jwtToken) { - if (jwtToken === null) { - return null; + 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; }; @@ -988,23 +942,23 @@ AuthenticationContext.prototype._serialize = function (responseType, obj, resour 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); } - + if (obj.correlationId) { str.push('client-request-id=' + encodeURIComponent(obj.correlationId)); } } - + return str.join('&'); }; @@ -1021,7 +975,7 @@ AuthenticationContext.prototype._deserialize = function (query) { obj[decode(match[1])] = decode(match[2]); match = search.exec(query); } - + return obj; }; @@ -1056,7 +1010,7 @@ AuthenticationContext.prototype._guid = function () { // 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') { @@ -1068,7 +1022,7 @@ AuthenticationContext.prototype._guid = function () { guidResponse += guidHolder[i]; } } - + return guidResponse; }; /* jshint ignore:end */ @@ -1086,10 +1040,10 @@ 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)) { @@ -1098,7 +1052,7 @@ AuthenticationContext.prototype._addAdalFrame = function (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) { @@ -1108,52 +1062,52 @@ AuthenticationContext.prototype._addAdalFrame = function (iframeId) { adalFrame = window.frames[iframeId]; } } - + return adalFrame; }; 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; }; 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); }; @@ -1177,7 +1131,7 @@ AuthenticationContext.prototype._cloneConfig = function (obj) { if (null === obj || 'object' !== typeof obj) { return obj; } - + var copy = {}; for (var attr in obj) { if (obj.hasOwnProperty(attr)) { @@ -1187,7 +1141,7 @@ AuthenticationContext.prototype._cloneConfig = function (obj) { return copy; }; -AuthenticationContext.prototype._addClientId = function () { +AuthenticationContext.prototype._addXClientParameters = function () { // x-client-SKU // x-client-Ver return '&x-client-SKU=Js&x-client-Ver=' + this._libVersion(); @@ -1197,13 +1151,13 @@ AuthenticationContext.prototype.log = function (level, message, error) { if (level <= Logging.level) { var correlationId = this.config.correlationId; var timestamp = new Date().toUTCString(); - - var formattedMessage = timestamp + ':' + correlationId + '-' + this.CONSTANTS.LEVEL_STRING_MAP[level] + ' ' + message; - + + var formattedMessage = timestamp + ':' + correlationId + '-' + this._libVersion() + '-' + this.CONSTANTS.LEVEL_STRING_MAP[level] + ' ' + message; + if (error) { formattedMessage += '\nstack:\n' + error.stack; } - + Logging.log(formattedMessage); } }; @@ -1225,5 +1179,5 @@ AuthenticationContext.prototype.verbose = function (message) { }; AuthenticationContext.prototype._libVersion = function () { - return '1.0.8'; + return '1.0.9'; }; \ No newline at end of file diff --git a/package.json b/package.json index 7005593d..00fd77e2 100644 --- a/package.json +++ b/package.json @@ -5,15 +5,10 @@ "email": "msopentech@microsoft.com", "url": "https://github.com/AzureAD/azure-activedirectory-library-for-js" }, - "licenses": [ - { - "type": "Apache 2.0", - "url": "http://www.apache.org/licenses/LICENSE-2.0" - } - ], + "license": "Apache-2.0", "repository": { "type": "git", - "url": "" + "url": "https://github.com/AzureAD/azure-activedirectory-library-for-js.git" }, "version": "1.0.8", "description": "Windows Azure Active Directory Client Library for js", diff --git a/tests/angularModuleSpec.js b/tests/angularModuleSpec.js index 97f95c0f..769df3ee 100644 --- a/tests/angularModuleSpec.js +++ b/tests/angularModuleSpec.js @@ -23,17 +23,19 @@ 'use strict'; describe('TaskCtl', function () { - var scope, $httpBackend, adalServiceProvider, rootScope, controller; + var scope, $httpBackend, adalServiceProvider, rootScope, controller, q, window; //mock Application to allow us to inject our own dependencies beforeEach(angular.mock.module('TestApplication')); //mock the controller for the same reason and include $rootScope and $controller - beforeEach(angular.mock.inject(function (_adalAuthenticationService_, _$rootScope_, _$controller_, _$httpBackend_) { + beforeEach(angular.mock.inject(function (_adalAuthenticationService_, _$rootScope_, _$controller_, _$httpBackend_, _$q_, _$window_) { adalServiceProvider = _adalAuthenticationService_; rootScope = _$rootScope_; controller = _$controller_; $httpBackend = _$httpBackend_; + q = _$q_; + window = _$window_; //create an empty scope scope = rootScope.$new(); @@ -56,6 +58,23 @@ describe('TaskCtl', function () { return ''; }; + adalServiceProvider.acquireToken = function (resource) { + console.log('acquire token for resource:' + resource); + var token = ''; + if (resource === 'resource1') { + token = 'RenewToken3434'; + } + + if (resource === 'resource2') { + token = 'RenewToken123'; + } + + if (resource === adalServiceProvider.config.loginResource) { + token = 'RenewToken456'; + } + return q.when(token); + }; + controller('TaskCtl', { $scope: scope, adalAuthenticationService: adalServiceProvider }); })); @@ -64,7 +83,7 @@ describe('TaskCtl', function () { expect(scope.user.isAuthenticated).toBe(true); }); - it('injects tokens for webapi call for given endpoint', function () { + it('send tokens for webapi call in endpoints list', function () { $httpBackend.expectGET('/api/Todo/5', function (headers) { return headers.Authorization === 'Bearer Token3434'; }).respond(200, { id: 5, name: 'TODOItem1' }); @@ -75,7 +94,7 @@ describe('TaskCtl', function () { expect(task.name).toBe('TODOItem1'); }); - it('does not sent tokens for other webapi calls', function () { + it('send tokens for webapi call in endpoints list', function () { $httpBackend.expectGET('/anotherApi/Item/13', function (headers) { console.log('headers test' + headers.Authorization); return headers.Authorization === 'Bearer Token123'; @@ -87,6 +106,14 @@ describe('TaskCtl', function () { expect(task.itemName).toBe('ItemWithoutAuth'); }); + it('send tokens for webapi call in endpoints list', function () { + $httpBackend.expectGET('https://testapi.com/', function (headers) { + return headers.Authorization === 'Bearer Token3434'; + }).respond(200); + scope.taskCall3(); + $httpBackend.flush(); + }); + it('does not send tokens for webapi(https) call not in endpoints list', function () { $httpBackend.expectGET('https://test.com/', function (headers) { return headers.hasOwnProperty('Authorization') === false; @@ -103,14 +130,6 @@ describe('TaskCtl', function () { $httpBackend.flush(); }); - it('send tokens for webapi call in endpoints list', function () { - $httpBackend.expectGET('https://testapi.com/', function (headers) { - return headers.Authorization === 'Bearer Token3434'; - }).respond(200); - scope.taskCall3(); - $httpBackend.flush(); - }); - it ('send tokens for app backend call not in endpoints list', function () { $httpBackend.expectGET('/someapi/item', function (headers) { return headers.Authorization === 'Bearer Token456' @@ -126,4 +145,83 @@ describe('TaskCtl', function () { scope.taskCall5(); $httpBackend.flush(); }); -}); \ No newline at end of file + + it('renews tokens for app backend', function () { + // This makes adal to try renewing the token since no token is returned from cache + adalServiceProvider.getCachedToken = function () { + return ''; + }; + $httpBackend.expectGET('https://myapp.com/someapi/item', function (headers) { + return headers.Authorization === 'Bearer RenewToken456'; + }).respond(200, { id: 5, name: 'TODOItem2' }); + scope.taskCall5(); + $httpBackend.flush(); + + var task = scope.task; + expect(task.name).toBe('TODOItem2'); + }); + + it('renews tokens for webapi in endpoint list', function () { + adalServiceProvider.getCachedToken = function () { + return ''; + }; + $httpBackend.expectGET('/anotherApi/Item/13', function (headers) { + console.log('headers test' + headers.Authorization); + return headers.Authorization === 'Bearer RenewToken123'; + }).respond(200, { id: 5, itemName: 'ItemWithoutAuth' }); + scope.itemCall(); + $httpBackend.flush(); + + var task = scope.item; + expect(task.itemName).toBe('ItemWithoutAuth'); + }); + + it('renews tokens for webapi in endpoint list', function () { + adalServiceProvider.getCachedToken = function () { + return ''; + }; + $httpBackend.expectGET('https://testapi.com/', function (headers) { + return headers.Authorization === 'Bearer RenewToken3434'; + }).respond(200); + scope.taskCall3(); + $httpBackend.flush(); + }); + + it('tests errorResponse broadcast when login is in progress', function () { + adalServiceProvider.getCachedToken = function () { + return ''; + }; + adalServiceProvider.loginInProgress = function () { + return true; + }; + spyOn(rootScope, '$broadcast').andCallThrough(); + $httpBackend.expectGET('https://myapp.com/someapi/item', function (headers) { + return headers.Authorization === 'Bearer Token456' + }).respond(200); + + rootScope.$on('adal:errorResponse', function (event, message) { + expect(event.name).toBe('adal:errorResponse'); + expect(message).toBe('login in progress, cancelling the request'); + }); + scope.taskCall5(); + rootScope.$apply(); + expect(rootScope.$broadcast).toHaveBeenCalledWith('adal:errorResponse', 'login in progress, cancelling the request'); + }); + + it('tests stateMismatch broadcast when state does not match', function () { + window.parent.AuthenticationContext = function () { + return { + callback: function () { }, + _renewStates: { } + }; + }; + window.location.hash = 'id_token=sample&state=4343'; + spyOn(rootScope, '$broadcast').andCallThrough(); + rootScope.$on('adal:stateMismatch', function (event, message) { + expect(event.name).toBe('adal:stateMismatch'); + expect(message).toBe('Invalid_state. state: 4343'); + }); + rootScope.$apply(); + expect(rootScope.$broadcast).toHaveBeenCalled(); + }); +}); diff --git a/tests/testApp.js b/tests/testApp.js index de4ce0c1..ca6ada6c 100644 --- a/tests/testApp.js +++ b/tests/testApp.js @@ -32,8 +32,8 @@ app.config(['$httpProvider', '$routeProvider', 'adalAuthenticationServiceProvide var endpoints = { '/api/Todo/': 'resource1', - '/anotherApi/Item/': 'resource2', - 'https://testapi.com/' : 'resource1' + '/anotherApi/Item/': 'resource2', + 'https://testapi.com/': 'resource1' }; adalAuthenticationServiceProvider.init( @@ -72,22 +72,6 @@ app.factory('TaskFactory', ['$http', function ($http) { return serviceFactory; }]); -app.factory('TaskFactory2', ['$http', function ($http) { - var serviceFactory = {}; - var _getItem = function () { - return $http.get('https://test.com'); - }; - - serviceFactory.getItem = _getItem; - return serviceFactory; -}]); - -app.controller('WidgetCtl', function ($scope, WidgetFactory) { - $scope.text = 'Hello Test!'; - - $scope.widget = WidgetFactory.get(); -}); - app.controller('TaskCtl', ['$scope', '$location', 'adalAuthenticationService', 'TaskFactory', 'ItemFactory', function ($scope, $location, adalAuthenticationService, TaskFactory, ItemFactory) { $scope.taskCall = function () { @@ -113,17 +97,17 @@ app.controller('TaskCtl', ['$scope', '$location', 'adalAuthenticationService', ' $scope.task = data; }).error(function (err) { $scope.error = err; - $scope.loaingMsg = ""; - }); + $scope.loadingMsg = ""; + }); } - + $scope.taskCall3 = function () { TaskFactory.getItem2('https://testapi.com/').success(function (data) { $scope.task = data; - }).error(function (err) { + }).error(function (err) { $scope.error = err; - $scope.loaingMsg = ""; - }); + $scope.loadingMsg = ""; + }); } $scope.taskCall4 = function () { @@ -131,7 +115,7 @@ app.controller('TaskCtl', ['$scope', '$location', 'adalAuthenticationService', ' $scope.task = data; }).error(function (err) { $scope.error = err; - $scope.loaingMsg = ""; + $scope.loadingMsg = ""; }); } @@ -140,7 +124,7 @@ app.controller('TaskCtl', ['$scope', '$location', 'adalAuthenticationService', ' $scope.task = data; }).error(function (err) { $scope.error = err; - $scope.loaingMsg = ""; + $scope.loadingMsg = ""; }); } @@ -152,5 +136,6 @@ app.controller('TaskCtl', ['$scope', '$location', 'adalAuthenticationService', ' $scope.loaingMsg = ""; }); } + $scope.user = adalAuthenticationService.userInfo; }]); diff --git a/tests/unit/spec/AdalSpec.js b/tests/unit/spec/AdalSpec.js index 7d4b3cd9..36822e6a 100644 --- a/tests/unit/spec/AdalSpec.js +++ b/tests/unit/spec/AdalSpec.js @@ -124,10 +124,6 @@ describe('Adal', function () { expect(adal.getResourceForEndpoint('b')).toBe('default resource'); }); - it('sets default resource', function () { - expect(adal.config.resource).toBe('default resource'); - }); - it('says token expired', function () { adal.config.expireOffsetSeconds = SECONDS_TO_EXPIRE - 100; expect(adal.getCachedToken(RESOURCE1)).toEqual('access_token_in_cache' + RESOURCE1); @@ -153,7 +149,7 @@ describe('Adal', function () { console.log('instance:' + adal.instance); adal.login(); expect(adal.promptUser).toHaveBeenCalledWith(DEFAULT_INSTANCE + conf.tenant + '/oauth2/authorize?response_type=id_token&client_id=client&redirect_uri=contoso_site&state=33333333-3333-4333-b333-333333333333' - + '&client-request-id=33333333-3333-4333-b333-333333333333' + adal._addClientId() + '&nonce=33333333-3333-4333-b333-333333333333'); + + '&client-request-id=33333333-3333-4333-b333-333333333333' + adal._addXClientParameters() + '&nonce=33333333-3333-4333-b333-333333333333'); expect(adal.config.state).toBe('33333333-3333-4333-b333-333333333333'); }); @@ -180,7 +176,7 @@ describe('Adal', function () { adal.login(); expect(adal.config.displayCall).toHaveBeenCalledWith(DEFAULT_INSTANCE + conf.tenant + '/oauth2/authorize?response_type=id_token&client_id=client&redirect_uri=contoso_site&state=33333333-3333-4333-b333-333333333333' + '&client-request-id=33333333-3333-4333-b333-333333333333' - + adal._addClientId() + + adal._addXClientParameters() + '&nonce=33333333-3333-4333-b333-333333333333' ); expect(adal.config.state).toBe('33333333-3333-4333-b333-333333333333'); @@ -210,21 +206,7 @@ describe('Adal', function () { expect(err).toBe('resource is required'); }); - it('returns err msg if token expired and renew failed before', function () { - storageFake.setItem(adal.CONSTANTS.STORAGE.FAILED_RENEW, 'renew has failed'); - adal.config.expireOffsetSeconds = SECONDS_TO_EXPIRE + 100; - var err = ''; - var token = ''; - var callback = function (valErr, valToken) { - err = valErr; - token = valToken; - }; - adal.acquireToken(RESOURCE1, callback); - expect(err).toBe('renew has failed'); - }); - it('attempts to renew if token expired and renew is allowed', function () { - storageFake.setItem(adal.CONSTANTS.STORAGE.FAILED_RENEW, ''); adal.config.redirectUri = 'contoso_site'; adal.config.clientId = 'client'; adal.config.expireOffsetSeconds = SECONDS_TO_EXPIRE + 100; @@ -247,14 +229,13 @@ describe('Adal', function () { runs(function () { console.log('Frame src:' + frameMock.src); expect(frameMock.src).toBe(DEFAULT_INSTANCE + conf.tenant + '/oauth2/authorize?response_type=token&client_id=client&resource=' + RESOURCE1 + '&redirect_uri=contoso_site&state=33333333-3333-4333-b333-333333333333%7Ctoken.resource1' - + '&client-request-id=33333333-3333-4333-b333-333333333333' + adal._addClientId() + '&prompt=none&login_hint=test%40testuser.com&domain_hint=testuser.com&nonce=33333333-3333-4333-b333-333333333333'); + + '&client-request-id=33333333-3333-4333-b333-333333333333' + adal._addXClientParameters() + '&prompt=none&login_hint=test%40testuser.com&domain_hint=testuser.com&nonce=33333333-3333-4333-b333-333333333333'); }); }); //Necessary for integration with Angular when multiple http calls are queued. it('allows multiple callers to be notified when the token is renewed', function () { - storageFake.setItem(adal.CONSTANTS.STORAGE.FAILED_RENEW, ''); adal.config.redirectUri = 'contoso_site'; adal.config.clientId = 'client'; adal.config.expireOffsetSeconds = SECONDS_TO_EXPIRE + 100; @@ -285,7 +266,7 @@ describe('Adal', function () { runs(function () { console.log('Frame src:' + frameMock.src); expect(frameMock.src).toBe(DEFAULT_INSTANCE + conf.tenant + '/oauth2/authorize?response_type=token&client_id=client&resource=' + RESOURCE1 + '&redirect_uri=contoso_site&state=33333333-3333-4333-b333-333333333333%7Ctoken.resource1' - + '&client-request-id=33333333-3333-4333-b333-333333333333' + adal._addClientId() + '&prompt=none&login_hint=test%40testuser.com&domain_hint=testuser.com&nonce=33333333-3333-4333-b333-333333333333'); + + '&client-request-id=33333333-3333-4333-b333-333333333333' + adal._addXClientParameters() + '&prompt=none&login_hint=test%40testuser.com&domain_hint=testuser.com&nonce=33333333-3333-4333-b333-333333333333'); }); //Simulate callback from the frame. @@ -344,10 +325,8 @@ describe('Adal', function () { storageFake.setItem(adal.CONSTANTS.STORAGE.ACCESS_TOKEN_KEY + 'key2', 'value2'); storageFake.setItem(adal.CONSTANTS.STORAGE.EXPIRATION_KEY, 3); storageFake.setItem(adal.CONSTANTS.STORAGE.EXPIRATION_KEY, 3); - storageFake.setItem(adal.CONSTANTS.STORAGE.FAILED_RENEW, 'failed renew'); storageFake.setItem(adal.CONSTANTS.STORAGE.SESSION_STATE, 'session_state'); storageFake.setItem(adal.CONSTANTS.STORAGE.STATE_LOGIN, 'state login'); - storageFake.setItem(adal.CONSTANTS.STORAGE.STATE_IDTOKEN, 'state idtoken'); storageFake.setItem(adal.CONSTANTS.STORAGE.START_PAGE, 'start page'); storageFake.setItem(adal.CONSTANTS.STORAGE.USERNAME, 'username'); storageFake.setItem(adal.CONSTANTS.STORAGE.ERROR, 'error'); @@ -364,9 +343,7 @@ describe('Adal', function () { storageFake.setItem(adal.CONSTANTS.STORAGE.TOKEN_KEYS, 'key1|' + RESOURCE1 + '|'); storageFake.setItem(adal.CONSTANTS.STORAGE.ACCESS_TOKEN_KEY + 'key1', 'value1'); storageFake.setItem(adal.CONSTANTS.STORAGE.EXPIRATION_KEY + 'key1', 3); - storageFake.setItem(adal.CONSTANTS.STORAGE.FAILED_RENEW, 'failed renew'); storageFake.setItem(adal.CONSTANTS.STORAGE.STATE_RENEW, 'state renew'); - storageFake.setItem(adal.CONSTANTS.STORAGE.STATE_IDTOKEN, 'state idtoken'); storageFake.setItem(adal.CONSTANTS.STORAGE.ERROR, 'error'); storageFake.setItem(adal.CONSTANTS.STORAGE.ERROR_DESCRIPTION, 'error description'); adal.clearCacheForResource(RESOURCE1); @@ -458,7 +435,6 @@ describe('Adal', function () { expect(requestInfo.stateMatch).toBe(false); checkStateType(adal.CONSTANTS.STORAGE.STATE_LOGIN, '1234', adal.REQUEST_TYPE.LOGIN); - checkStateType(adal.CONSTANTS.STORAGE.STATE_IDTOKEN, '1236', adal.REQUEST_TYPE.ID_TOKEN); }); var checkStateType = function (state, stateExpected, requestType) { @@ -623,6 +599,25 @@ describe('Adal', function () { expect(adal._getHostFromUri('http://localhost:8080')).toBe('localhost:8080'); }); + it('test decode jwt', function () { + expect(adal._decodeJwt('')).toBe(null); + expect(adal._decodeJwt(null)).toBe(null); + }) + + it('saves error if state mismatch', function () { + var requestInfo = { + valid: true, + parameters: { 'access_token': 'token123', 'state': '123' }, + stateMatch: false, + stateResponse: '64532', + requestType: adal.REQUEST_TYPE.UNKNOWN + }; + adal.config.loginResource = 'loginResource1'; + adal.saveTokenFromHash(requestInfo); + + expect(storageFake.getItem(adal.CONSTANTS.STORAGE.ERROR_DESCRIPTION)).toBe('Invalid_state. state: ' + requestInfo.stateResponse); + }); + // TODO angular intercepptor // TODO angular authenticaitonService From 5b4e3e9e5f15d366e3f5992ccf4d2790b5fd6e0e Mon Sep 17 00:00:00 2001 From: Tushar Gupta Date: Wed, 30 Mar 2016 12:38:51 -0700 Subject: [PATCH 3/6] updating lib version to 1.0.9 --- .gitignore | 5 ++++- README.md | 8 ++++---- bower.json | 2 +- changelog.txt | 4 ++++ lib/adal-angular.js | 2 +- lib/adal.js | 14 ++++---------- package.json | 2 +- tests/unit/spec/AdalSpec.js | 18 ++++++++---------- 8 files changed, 27 insertions(+), 28 deletions(-) diff --git a/.gitignore b/.gitignore index 0b016ab5..8de9a7af 100644 --- a/.gitignore +++ b/.gitignore @@ -90,4 +90,7 @@ bower_components samples/owin/OwinSample/Scripts/adal.js #webstorm -.idea \ No newline at end of file +.idea + +#codecoverage +coverage \ No newline at end of file diff --git a/README.md b/README.md index 0d616e78..9753ff6e 100644 --- a/README.md +++ b/README.md @@ -7,17 +7,17 @@ This library is optimized for working together with AngularJS. ## The Library -This is a GA released version. The current version is **1.0.8**. +This is a GA released version. The current version is **1.0.9**. You have multiple ways of getting ADAL JS: Via CDN: - - + + -CDN will be updated to latest version 1.0.8. +CDN will be updated to latest version 1.0.9. Via Bower: diff --git a/bower.json b/bower.json index fa29ceaa..7687ca65 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "adal-angular", - "version": "1.0.8", + "version": "1.0.9", "homepage": "https://github.com/AzureAD/azure-activedirectory-library-for-js", "authors": [ "MSOpentech" diff --git a/changelog.txt b/changelog.txt index ab569256..87a977fc 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,7 @@ +Version 1.0.9 +========================== +Adding events for state mismatch and error responses. Fix for token renewal for app's backend. Making library available on NPM + Version 1.0.8 ========================== Fix for persisting route parameters of protected state diff --git a/lib/adal-angular.js b/lib/adal-angular.js index e50d5c2f..565da7e6 100644 --- a/lib/adal-angular.js +++ b/lib/adal-angular.js @@ -1,5 +1,5 @@ //---------------------------------------------------------------------- -// AdalJS v1.0.8 +// AdalJS v1.0.9 // @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 b4f7099d..d02d88f0 100644 --- a/lib/adal.js +++ b/lib/adal.js @@ -1,5 +1,5 @@ //---------------------------------------------------------------------- -// AdalJS v1.0.8 +// AdalJS v1.0.9 // @preserve Copyright (c) Microsoft Open Technologies, Inc. // All Rights Reserved // Apache License 2.0 @@ -174,7 +174,6 @@ AuthenticationContext.prototype.login = function () { 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.frameCallInProgress = false; this._loginInProgress = true; @@ -677,12 +676,7 @@ AuthenticationContext.prototype.saveTokenFromHash = function (requestInfo) { if (requestInfo.parameters.hasOwnProperty(this.CONSTANTS.ACCESS_TOKEN)) { this.info('Fragment has access token'); - // default resource - resource = this.config.loginResource; - if (requestInfo.requestType === this.REQUEST_TYPE.RENEW_TOKEN) { - resource = this._getResourceFromState(requestInfo.stateResponse); - } - + resource = this._getResourceFromState(requestInfo.stateResponse); 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); @@ -801,7 +795,7 @@ AuthenticationContext.prototype._getNavigateUrl = function (responseType, resour this.instance = this.config.instance; } - var urlNavigate = this.instance + tenant + '/oauth2/authorize' + this._serialize(responseType, this.config, resource) + this._addXClientParameters(); + var urlNavigate = this.instance + tenant + '/oauth2/authorize' + this._serialize(responseType, this.config, resource) + this._addLibMetadata(); this.info('Navigate url:' + urlNavigate); return urlNavigate; }; @@ -1141,7 +1135,7 @@ AuthenticationContext.prototype._cloneConfig = function (obj) { return copy; }; -AuthenticationContext.prototype._addXClientParameters = function () { +AuthenticationContext.prototype._addLibMetadata = function () { // x-client-SKU // x-client-Ver return '&x-client-SKU=Js&x-client-Ver=' + this._libVersion(); diff --git a/package.json b/package.json index 00fd77e2..3b4ae5a4 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.8", + "version": "1.0.9", "description": "Windows Azure Active Directory Client Library for js", "keywords": [ "implicit", diff --git a/tests/unit/spec/AdalSpec.js b/tests/unit/spec/AdalSpec.js index 36822e6a..0a5efa7d 100644 --- a/tests/unit/spec/AdalSpec.js +++ b/tests/unit/spec/AdalSpec.js @@ -149,7 +149,7 @@ describe('Adal', function () { console.log('instance:' + adal.instance); adal.login(); expect(adal.promptUser).toHaveBeenCalledWith(DEFAULT_INSTANCE + conf.tenant + '/oauth2/authorize?response_type=id_token&client_id=client&redirect_uri=contoso_site&state=33333333-3333-4333-b333-333333333333' - + '&client-request-id=33333333-3333-4333-b333-333333333333' + adal._addXClientParameters() + '&nonce=33333333-3333-4333-b333-333333333333'); + + '&client-request-id=33333333-3333-4333-b333-333333333333' + adal._addLibMetadata() + '&nonce=33333333-3333-4333-b333-333333333333'); expect(adal.config.state).toBe('33333333-3333-4333-b333-333333333333'); }); @@ -176,7 +176,7 @@ describe('Adal', function () { adal.login(); expect(adal.config.displayCall).toHaveBeenCalledWith(DEFAULT_INSTANCE + conf.tenant + '/oauth2/authorize?response_type=id_token&client_id=client&redirect_uri=contoso_site&state=33333333-3333-4333-b333-333333333333' + '&client-request-id=33333333-3333-4333-b333-333333333333' - + adal._addXClientParameters() + + adal._addLibMetadata() + '&nonce=33333333-3333-4333-b333-333333333333' ); expect(adal.config.state).toBe('33333333-3333-4333-b333-333333333333'); @@ -229,7 +229,7 @@ describe('Adal', function () { runs(function () { console.log('Frame src:' + frameMock.src); expect(frameMock.src).toBe(DEFAULT_INSTANCE + conf.tenant + '/oauth2/authorize?response_type=token&client_id=client&resource=' + RESOURCE1 + '&redirect_uri=contoso_site&state=33333333-3333-4333-b333-333333333333%7Ctoken.resource1' - + '&client-request-id=33333333-3333-4333-b333-333333333333' + adal._addXClientParameters() + '&prompt=none&login_hint=test%40testuser.com&domain_hint=testuser.com&nonce=33333333-3333-4333-b333-333333333333'); + + '&client-request-id=33333333-3333-4333-b333-333333333333' + adal._addLibMetadata() + '&prompt=none&login_hint=test%40testuser.com&domain_hint=testuser.com&nonce=33333333-3333-4333-b333-333333333333'); }); }); @@ -266,7 +266,7 @@ describe('Adal', function () { runs(function () { console.log('Frame src:' + frameMock.src); expect(frameMock.src).toBe(DEFAULT_INSTANCE + conf.tenant + '/oauth2/authorize?response_type=token&client_id=client&resource=' + RESOURCE1 + '&redirect_uri=contoso_site&state=33333333-3333-4333-b333-333333333333%7Ctoken.resource1' - + '&client-request-id=33333333-3333-4333-b333-333333333333' + adal._addXClientParameters() + '&prompt=none&login_hint=test%40testuser.com&domain_hint=testuser.com&nonce=33333333-3333-4333-b333-333333333333'); + + '&client-request-id=33333333-3333-4333-b333-333333333333' + adal._addLibMetadata() + '&prompt=none&login_hint=test%40testuser.com&domain_hint=testuser.com&nonce=33333333-3333-4333-b333-333333333333'); }); //Simulate callback from the frame. @@ -467,10 +467,9 @@ describe('Adal', function () { valid: true, parameters: { 'access_token': 'token123', 'state': '123' }, stateMatch: true, - stateResponse: '123', - requestType: adal.REQUEST_TYPE.LOGIN + stateResponse: '123|loginResource1', + requestType: adal.REQUEST_TYPE.RENEW_TOKEN }; - adal.config.loginResource = 'loginResource1'; adal.saveTokenFromHash(requestInfo); expect(storageFake.getItem(adal.CONSTANTS.STORAGE.ACCESS_TOKEN_KEY + 'loginResource1')).toBe('token123'); @@ -481,10 +480,9 @@ describe('Adal', function () { valid: true, parameters: { 'access_token': 'token123', 'state': '123', 'expires_in': 3589 }, stateMatch: true, - stateResponse: '123', - requestType: adal.REQUEST_TYPE.LOGIN + stateResponse: '123|loginResource1', + requestType: adal.REQUEST_TYPE.RENEW_TOKEN }; - adal.config.loginResource = 'loginResource1'; adal.saveTokenFromHash(requestInfo); expect(storageFake.getItem(adal.CONSTANTS.STORAGE.EXPIRATION_KEY + 'loginResource1')).toBe(mathMock.round(1) + 3589 + ''); }); From 35c4684e889c451aee26d2efe67a032a3e57b184 Mon Sep 17 00:00:00 2001 From: Tushar Gupta Date: Mon, 4 Apr 2016 13:23:07 -0700 Subject: [PATCH 4/6] update package.json --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 3b4ae5a4..86bdd03f 100644 --- a/package.json +++ b/package.json @@ -1,9 +1,9 @@ { "name": "adal-angular", "author": { - "name": "Microsoft Open Technologies Inc", - "email": "msopentech@microsoft.com", - "url": "https://github.com/AzureAD/azure-activedirectory-library-for-js" + "name": "Microsoft", + "email": "nugetaad@microsoft.com", + "url": "https://www.microsoft.com" }, "license": "Apache-2.0", "repository": { From 80821cb8018ab0ee8c1f3c8888a99ecfee4318bb Mon Sep 17 00:00:00 2001 From: Tushar Gupta Date: Mon, 4 Apr 2016 14:56:41 -0700 Subject: [PATCH 5/6] updating minified files --- dist/adal-angular.min.js | 4 ++-- dist/adal.min.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/dist/adal-angular.min.js b/dist/adal-angular.min.js index 8257a5d7..5ff878b4 100644 --- a/dist/adal-angular.min.js +++ b/dist/adal-angular.min.js @@ -1,2 +1,2 @@ -/*! adal-angular v1.0.8 2016-02-22 */ -"use strict";"undefined"!=typeof module&&module.exports&&(module.exports.inject=function(a){return new AuthenticationContext(a)}),function(){if(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,d&&d.interceptors&&d.interceptors.push("ProtectedResourceInterceptor"),a=new AuthenticationContext(b),c(a.config.loginResource)},this.$get=["$rootScope","$window","$q","$location","$timeout",function(d,e,f,g,h){function i(a,b){return b.requireADLogin?a.requireADLogin!==!1:!!a.requireADLogin}var j=function(){var f=e.location.hash;if(a.isCallback(f)){var i=a.getRequestInfo(f);if(a.saveTokenFromHash(i),g.$$html5?e.location=e.location.origin+e.location.pathname:e.location.hash="",i.requestType!==a.REQUEST_TYPE.LOGIN&&(a.callback=e.parent.AuthenticationContext().callback,i.requestType===a.REQUEST_TYPE.RENEW_TOKEN&&(a.callback=e.parent.callBackMappedToRenewStates[i.stateResponse])),i.stateMatch)if("function"==typeof a.callback){if(i.requestType===a.REQUEST_TYPE.RENEW_TOKEN){if(i.parameters.access_token)return void a.callback(a._getItem(a.CONSTANTS.STORAGE.ERROR_DESCRIPTION),i.parameters.access_token);if(i.parameters.id_token)return void a.callback(a._getItem(a.CONSTANTS.STORAGE.ERROR_DESCRIPTION),i.parameters.id_token)}}else c(a.config.loginResource),b.userName?(h(function(){c(a.config.loginResource),d.userInfo=b;var e=a._getItem(a.CONSTANTS.STORAGE.START_PAGE);if(e){var f=a._getItem(a.CONSTANTS.STORAGE.START_PAGE_PARAMS);if(f){var h=JSON.parse(f);g.url(e).search(h)}else g.url(e)}},1),d.$broadcast("adal:loginSuccess")):d.$broadcast("adal:loginFailure",a._getItem(a.CONSTANTS.STORAGE.ERROR_DESCRIPTION))}else c(a.config.loginResource),a._renewActive||b.isAuthenticated||!b.userName||a._getItem(a.CONSTANTS.STORAGE.FAILED_RENEW)||a.acquireToken(a.config.loginResource,function(a,c){a?d.$broadcast("adal:loginFailure","auto renew failure"):c&&(b.isAuthenticated=!0)});h(function(){c(a.config.loginResource),d.userInfo=b},1)},k=function(){a.info("Login event for:"+g.$$url),a.config&&a.config.localLoginUrl?g.path(a.config.localLoginUrl):(a._saveItem(a.CONSTANTS.STORAGE.START_PAGE,g.$$url),a.info("Start login at:"+window.location.href),d.$broadcast("adal:loginRedirect"),a.login())},l=function(c,d){d&&d.$$route&&i(d.$$route,a.config)&&(b.isAuthenticated||(a.info("Route change event for:"+g.$$url),k()))},m=function(c,d,e,f,h){d&&i(d,a.config)&&(b.isAuthenticated||(g.$$url=d.url,a._saveItem(a.CONSTANTS.STORAGE.START_PAGE_PARAMS,JSON.stringify(e)),a.info("State change event for:"+g.$$url),k()))};return d.$on("$routeChangeStart",l),d.$on("$stateChangeStart",m),d.$on("$locationChangeStart",j),c(a.config.loginResource),d.userInfo=b,{config:a.config,login:function(){a.login()},loginInProgress:function(){return a.loginInProgress()},logOut:function(){a.logOut()},getCachedToken:function(b){return a.getCachedToken(b)},userInfo:b,acquireToken:function(b){var c=f.defer();return a.acquireToken(b,function(d,e){d?(a.error("Error when acquiring token for resource: "+b,d),c.reject(d)):c.resolve(e)}),c.promise},getUser:function(){var b=f.defer();return a.getUser(function(c,d){c?(a.error("Error when getting user",c),b.reject(c)):b.resolve(d)}),b.promise},getResourceForEndpoint:function(b){return a.getResourceForEndpoint(b)},clearCache:function(){a.clearCache()},clearCacheForResource:function(b){a.clearCacheForResource(b)},info:function(b){a.info(b)},verbose:function(b){a.verbose(b)}}}]}),a.factory("ProtectedResourceInterceptor",["adalAuthenticationService","$q","$rootScope",function(a,b,c){return{request:function(c){if(c){c.headers=c.headers||{};var d=a.getResourceForEndpoint(c.url);if(null===d)return c;var e=a.getCachedToken(d),f=!1;if(e)return a.info("Token is avaliable for this url "+c.url),c.headers.Authorization="Bearer "+e,c;if(a.config)for(var g in a.config.endpoints)c.url.indexOf(g)>-1&&(f=!0);if(a.loginInProgress())return a.info("login already start."),b.reject();if(a.config&&f){var h=b.defer();return a.acquireToken(d).then(function(b){a.verbose("Token is avaliable"),c.headers.Authorization="Bearer "+b,h.resolve(c)},function(a){h.reject(a)}),h.promise}return c}},responseError:function(d){if(a.info("Getting error in the response"),d&&401===d.status){var e=a.getResourceForEndpoint(d.config.url);a.clearCacheForResource(e),c.$broadcast("adal:notAuthorized",d,e)}return b.reject(d)}}}])}else console.error("Angular.JS is not included")}(); \ No newline at end of file +/*! adal-angular v1.0.9 2016-04-04 */ +"use strict";"undefined"!=typeof module&&module.exports&&(module.exports.inject=function(a){return new AuthenticationContext(a)}),function(){if(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,d&&d.interceptors&&d.interceptors.push("ProtectedResourceInterceptor"),a=new AuthenticationContext(b),c(a.config.loginResource)},this.$get=["$rootScope","$window","$q","$location","$timeout",function(d,e,f,g,h){function i(a,b){return b.requireADLogin?a.requireADLogin!==!1:!!a.requireADLogin}var j=function(){var f=e.location.hash;if(a.isCallback(f)){var i=a.getRequestInfo(f);if(a.saveTokenFromHash(i),g.$$html5?e.location=e.location.origin+e.location.pathname:e.location.hash="",i.requestType!==a.REQUEST_TYPE.LOGIN&&(a._renewActive=!1,a.callback=e.parent.AuthenticationContext().callback,i.requestType===a.REQUEST_TYPE.RENEW_TOKEN&&(a.callback=e.parent.callBackMappedToRenewStates[i.stateResponse])),i.stateMatch)if("function"==typeof a.callback){if(i.requestType===a.REQUEST_TYPE.RENEW_TOKEN){if(i.parameters.access_token)return void a.callback(a._getItem(a.CONSTANTS.STORAGE.ERROR_DESCRIPTION),i.parameters.access_token);if(i.parameters.id_token)return void a.callback(a._getItem(a.CONSTANTS.STORAGE.ERROR_DESCRIPTION),i.parameters.id_token)}}else c(a.config.loginResource),b.userName?(h(function(){c(a.config.loginResource),d.userInfo=b;var e=a._getItem(a.CONSTANTS.STORAGE.START_PAGE);if(e){var f=a._getItem(a.CONSTANTS.STORAGE.START_PAGE_PARAMS);if(f){var h=JSON.parse(f);g.url(e).search(h)}else g.url(e)}},1),d.$broadcast("adal:loginSuccess")):d.$broadcast("adal:loginFailure",a._getItem(a.CONSTANTS.STORAGE.ERROR_DESCRIPTION));else d.$broadcast("adal:stateMismatch",a._getItem(a.CONSTANTS.STORAGE.ERROR_DESCRIPTION))}else c(a.config.loginResource),a._renewActive||b.isAuthenticated||!b.userName||(a._renewActive=!0,a.acquireToken(a.config.loginResource,function(a,c){a?d.$broadcast("adal:loginFailure","auto renew failure"):c&&(b.isAuthenticated=!0)}));h(function(){c(a.config.loginResource),d.userInfo=b},1)},k=function(){a.info("Login event for:"+g.$$url),a.config&&a.config.localLoginUrl?g.path(a.config.localLoginUrl):(a._saveItem(a.CONSTANTS.STORAGE.START_PAGE,g.$$url),a.info("Start login at:"+window.location.href),d.$broadcast("adal:loginRedirect"),a.login())},l=function(c,d){d&&d.$$route&&i(d.$$route,a.config)&&(b.isAuthenticated||a._renewActive||(a.info("Route change event for:"+g.$$url),k()))},m=function(c,d,e,f,h){d&&i(d,a.config)&&(b.isAuthenticated||a._renewActive||(g.$$url=d.url,a._saveItem(a.CONSTANTS.STORAGE.START_PAGE_PARAMS,JSON.stringify(e)),a.info("State change event for:"+g.$$url),k()))};return d.$on("$routeChangeStart",l),d.$on("$stateChangeStart",m),d.$on("$locationChangeStart",j),c(a.config.loginResource),d.userInfo=b,{config:a.config,login:function(){a.login()},loginInProgress:function(){return a.loginInProgress()},logOut:function(){a.logOut()},getCachedToken:function(b){return a.getCachedToken(b)},userInfo:b,acquireToken:function(b){var c=f.defer();return a._renewActive=!0,a.acquireToken(b,function(d,e){a._renewActive=!1,d?(a.error("Error when acquiring token for resource: "+b,d),c.reject(d)):c.resolve(e)}),c.promise},getUser:function(){var b=f.defer();return a.getUser(function(c,d){c?(a.error("Error when getting user",c),b.reject(c)):b.resolve(d)}),b.promise},getResourceForEndpoint:function(b){return a.getResourceForEndpoint(b)},clearCache:function(){a.clearCache()},clearCacheForResource:function(b){a.clearCacheForResource(b)},info:function(b){a.info(b)},verbose:function(b){a.verbose(b)}}}]}),a.factory("ProtectedResourceInterceptor",["adalAuthenticationService","$q","$rootScope",function(a,b,c){return{request:function(c){if(c){c.headers=c.headers||{};var d=a.getResourceForEndpoint(c.url);if(null===d)return c;var e=a.getCachedToken(d);if(e)return a.info("Token is avaliable for this url "+c.url),c.headers.Authorization="Bearer "+e,c;if(a.loginInProgress())return a.info("login already start."),b.reject("login in progress, cancelling the request");var f=b.defer();return a.acquireToken(d).then(function(b){a.verbose("Token is avaliable"),c.headers.Authorization="Bearer "+b,f.resolve(c)},function(a){f.reject(a)}),f.promise}},responseError:function(d){if(a.info("Getting error in the response"),d){if(401===d.status){var e=a.getResourceForEndpoint(d.config.url);a.clearCacheForResource(e),c.$broadcast("adal:notAuthorized",d,e)}else c.$broadcast("adal:errorResponse",d);return b.reject(d)}}}}])}else console.error("Angular.JS is not included")}(); \ No newline at end of file diff --git a/dist/adal.min.js b/dist/adal.min.js index b12bcd6d..28047928 100644 --- a/dist/adal.min.js +++ b/dist/adal.min.js @@ -1,2 +1,2 @@ -/*! adal-angular v1.0.8 2016-02-22 */ -"use strict";var Logging={level:0,log:function(){}},AuthenticationContext;"undefined"!=typeof module&&module.exports&&(module.exports.inject=function(a){return new AuthenticationContext(a)}),AuthenticationContext=function(a){if(this.REQUEST_TYPE={LOGIN:"LOGIN",RENEW_TOKEN:"RENEW_TOKEN",ID_TOKEN:"ID_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",START_PAGE:"adal.start.page",START_PAGE_PARAMS:"adal.start.page.params",FAILED_RENEW:"adal.failed.renew",STATE_LOGIN:"adal.state.login",STATE_RENEW:"adal.state.renew",STATE_RENEW_RESOURCE:"adal.state.renew.resource",STATE_IDTOKEN:"adal.state.idtoken",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"},RESOURCE_DELIMETER:"|",ERR_MESSAGES:{NO_TOKEN:"User is not authorized"},LOGGING_LEVEL:{ERROR:0,WARN:1,INFO:2,VERBOSE:3},LEVEL_STRING_MAP:{0:"ERROR:",1:"WARNING:",2:"INFO:",3:"VERBOSE:"}},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._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");a.correlationId||(a.correlationId=this._guid()),this.config=this._cloneConfig(a),this.config.loginResource||(this.config.loginResource=this.config.clientId),this.config.redirectUri||(this.config.redirectUri=window.location.href),this.config.resource=this.config.loginResource||""},AuthenticationContext.prototype.login=function(){var a=this._guid();this.config.state=a,this._idTokenNonce=this._guid(),this.verbose("Expected state: "+a+" startPage:"+window.location),this._saveItem(this.CONSTANTS.STORAGE.LOGIN_REQUEST,window.location),this._saveItem(this.CONSTANTS.STORAGE.LOGIN_ERROR,""),this._saveItem(this.CONSTANTS.STORAGE.STATE_LOGIN,a),this._saveItem(this.CONSTANTS.STORAGE.NONCE_IDTOKEN,this._idTokenNonce),this._saveItem(this.CONSTANTS.STORAGE.FAILED_RENEW,""),this._saveItem(this.CONSTANTS.STORAGE.ERROR,""),this._saveItem(this.CONSTANTS.STORAGE.ERROR_DESCRIPTION,"");var b=this._getNavigateUrl("id_token",null)+"&nonce="+encodeURIComponent(this._idTokenNonce);this.frameCallInProgress=!1,this._loginInProgress=!0,this.config.displayCall?this.config.displayCall(b):this.promptUser(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||120;return c&&c>this._now()+d?b:(this._saveItem(this.CONSTANTS.STORAGE.ACCESS_TOKEN_KEY+a,""),this._saveItem(this.CONSTANTS.STORAGE.EXPIRATION_KEY+a,0),null)},AuthenticationContext.prototype.getCachedUser=function(){if(this._user)return this._user;var a=this._getItem(this.CONSTANTS.STORAGE.IDTOKEN);return this._user=this._createUser(a),this._user},AuthenticationContext.prototype.registerCallback=function(a,b,c){this._activeRenewals[b]=a,window.callBacksMappedToRenewStates[a]||(window.callBacksMappedToRenewStates[a]=[]);var d=this;window.callBacksMappedToRenewStates[a].push(c),window.callBackMappedToRenewStates[a]||(window.callBackMappedToRenewStates[a]=function(c,e){for(var f=0;f-1){var a=this._user.userName.split("@");return a[a.length-1]}return""},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="";switch(b.hasOwnProperty("state")?(this.verbose("State: "+b.state),d=b.state):this.verbose("No state returned"),c.stateResponse=d,d){case this._getItem(this.CONSTANTS.STORAGE.STATE_LOGIN):c.requestType=this.REQUEST_TYPE.LOGIN,c.stateMatch=!0;break;case this._getItem(this.CONSTANTS.STORAGE.STATE_IDTOKEN):c.requestType=this.REQUEST_TYPE.ID_TOKEN,this._saveItem(this.CONSTANTS.STORAGE.STATE_IDTOKEN,""),c.stateMatch=!0}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];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(){var a=window.location.hash;if(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&&b.requestType!==this.REQUEST_TYPE.ID_TOKEN||!window.parent?window&&window.oauth2Callback&&(this.verbose("Window is redirecting"),c=this.callback):(this.verbose("Window is in iframe"),c=window.parent.callBackMappedToRenewStates[b.stateResponse],window.src=""),window.location.hash="",window.location=this._getItem(this.CONSTANTS.STORAGE.LOGIN_REQUEST),b.requestType===this.REQUEST_TYPE.RENEW_TOKEN)return void c(this._getItem(this.CONSTANTS.STORAGE.ERROR_DESCRIPTION),b.parameters[this.CONSTANTS.ACCESS_TOKEN]||b.parameters[this.CONSTANTS.ID_TOKEN]);if(b.requestType===this.REQUEST_TYPE.ID_TOKEN)return void c(this._getItem(this.CONSTANTS.STORAGE.ERROR_DESCRIPTION),this._createUser(this._getItem(this.CONSTANTS.STORAGE.IDTOKEN)))}},AuthenticationContext.prototype._getNavigateUrl=function(a,b){var c="common";this.config.tenant&&(c=this.config.tenant),this.config.instance&&(this.instance=this.config.instance);var d=this.instance+c+"/oauth2/authorize"+this._serialize(a,this.config,b)+this._addClientId();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._extractUserName=function(a){try{var b=this._extractIdToken(a);if(b){if(b.hasOwnProperty("upn"))return b.upn;if(b.hasOwnProperty("email"))return b.email}}catch(c){this.error("The returned id_token could not be decoded",c)}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;c>m;m+=4){if(d=b.indexOf(a.charAt(m)),e=b.indexOf(a.charAt(m+1)),f=b.indexOf(a.charAt(m+2)),g=b.indexOf(a.charAt(m+3)),m+2===c-1){h=d<<18|e<<12|f<<6,i=h>>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(null===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=[];return 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),b.correlationId&&d.push("client-request-id="+encodeURIComponent(b.correlationId))),d.join("&")},AuthenticationContext.prototype._deserialize=function(a){var b,c=/\+/g,d=/([^&=]+)=?([^&]*)/g,e=function(a){return decodeURIComponent(a.replace(c," "))},f={};for(b=d.exec(a);b;)f[e(b[1])]=e(b[2]),b=d.exec(a);return f},AuthenticationContext.prototype._guid=function(){for(var a="xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx",b="0123456789abcdef",c=0,d="",e=0;36>e;e++)"-"!==a[e]&&"4"!==a[e]&&(c=16*Math.random()|0),"x"===a[e]?d+=b[c]:"y"===a[e]?(c&=3,c|=8,d+=b[c]):d+=a[e];return d},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||-1===window.navigator.userAgent.indexOf("MSIE 5.0"))){var c=document.createElement("iframe");c.setAttribute("id",a),c.style.visibility="hidden",c.style.position="absolute",c.style.width=c.style.height=c.borderWidth="0px",b=document.getElementsByTagName("body")[0].appendChild(c)}else document.body&&document.body.insertAdjacentHTML&&document.body.insertAdjacentHTML("beforeEnd",'');window.frames&&window.frames[a]&&(b=window.frames[a])}return b}},AuthenticationContext.prototype._saveItem=function(a,b){return this.config&&this.config.cacheLocation&&"localStorage"===this.config.cacheLocation?this._supportsLocalStorage()?(localStorage.setItem(a,b),!0):(this.info("Local storage is not supported"),!1):this._supportsSessionStorage()?(sessionStorage.setItem(a,b),!0):(this.info("Session storage is not supported"),!1)},AuthenticationContext.prototype._getItem=function(a){return this.config&&this.config.cacheLocation&&"localStorage"===this.config.cacheLocation?this._supportsLocalStorage()?localStorage.getItem(a):(this.info("Local storage is not supported"),null):this._supportsSessionStorage()?sessionStorage.getItem(a):(this.info("Session storage is not supported"),null)},AuthenticationContext.prototype._supportsLocalStorage=function(){try{return"localStorage"in window&&window.localStorage}catch(a){return!1}},AuthenticationContext.prototype._supportsSessionStorage=function(){try{return"sessionStorage"in window&&window.sessionStorage}catch(a){return!1}},AuthenticationContext.prototype._cloneConfig=function(a){if(null===a||"object"!=typeof a)return a;var b={};for(var c in a)a.hasOwnProperty(c)&&(b[c]=a[c]);return b},AuthenticationContext.prototype._addClientId=function(){return"&x-client-SKU=Js&x-client-Ver="+this._libVersion()},AuthenticationContext.prototype.log=function(a,b,c){if(a<=Logging.level){var d=this.config.correlationId,e=(new Date).toUTCString(),f=e+":"+d+"-"+this.CONSTANTS.LEVEL_STRING_MAP[a]+" "+b;c&&(f+="\nstack:\n"+c.stack),Logging.log(f)}},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.8"}; \ No newline at end of file +/*! adal-angular v1.0.9 2016-04-04 */ +"use strict";var Logging={level:0,log:function(){}},AuthenticationContext;"undefined"!=typeof module&&module.exports&&(module.exports.inject=function(a){return new AuthenticationContext(a)}),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",START_PAGE:"adal.start.page",START_PAGE_PARAMS:"adal.start.page.params",STATE_LOGIN:"adal.state.login",STATE_RENEW:"adal.state.renew",STATE_RENEW_RESOURCE:"adal.state.renew.resource",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"},RESOURCE_DELIMETER:"|",ERR_MESSAGES:{NO_TOKEN:"User is not authorized"},LOGGING_LEVEL:{ERROR:0,WARN:1,INFO:2,VERBOSE:3},LEVEL_STRING_MAP:{0:"ERROR:",1:"WARNING:",2:"INFO:",3:"VERBOSE:"}},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._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");a.correlationId||(a.correlationId=this._guid()),this.config=this._cloneConfig(a),this.config.loginResource||(this.config.loginResource=this.config.clientId),this.config.redirectUri||(this.config.redirectUri=window.location.href)},AuthenticationContext.prototype.login=function(){var a=this._guid();this.config.state=a,this._idTokenNonce=this._guid(),this.verbose("Expected state: "+a+" startPage:"+window.location),this._saveItem(this.CONSTANTS.STORAGE.LOGIN_REQUEST,window.location),this._saveItem(this.CONSTANTS.STORAGE.LOGIN_ERROR,""),this._saveItem(this.CONSTANTS.STORAGE.STATE_LOGIN,a),this._saveItem(this.CONSTANTS.STORAGE.NONCE_IDTOKEN,this._idTokenNonce),this._saveItem(this.CONSTANTS.STORAGE.ERROR,""),this._saveItem(this.CONSTANTS.STORAGE.ERROR_DESCRIPTION,"");var b=this._getNavigateUrl("id_token",null)+"&nonce="+encodeURIComponent(this._idTokenNonce);this.frameCallInProgress=!1,this._loginInProgress=!0,this.config.displayCall?this.config.displayCall(b):this.promptUser(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||120;return c&&c>this._now()+d?b:(this._saveItem(this.CONSTANTS.STORAGE.ACCESS_TOKEN_KEY+a,""),this._saveItem(this.CONSTANTS.STORAGE.EXPIRATION_KEY+a,0),null)},AuthenticationContext.prototype.getCachedUser=function(){if(this._user)return this._user;var a=this._getItem(this.CONSTANTS.STORAGE.IDTOKEN);return this._user=this._createUser(a),this._user},AuthenticationContext.prototype.registerCallback=function(a,b,c){this._activeRenewals[b]=a,window.callBacksMappedToRenewStates[a]||(window.callBacksMappedToRenewStates[a]=[]);var d=this;window.callBacksMappedToRenewStates[a].push(c),window.callBackMappedToRenewStates[a]||(window.callBackMappedToRenewStates[a]=function(c,e){for(var f=0;f-1){var a=this._user.userName.split("@");return a[a.length-1]}return""},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];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(){var a=window.location.hash;if(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?(this.verbose("Window is in iframe"),c=window.parent.callBackMappedToRenewStates[b.stateResponse],window.src=""):window&&window.oauth2Callback&&(this.verbose("Window is redirecting"),c=this.callback),window.location.hash="",window.location=this._getItem(this.CONSTANTS.STORAGE.LOGIN_REQUEST),b.requestType===this.REQUEST_TYPE.RENEW_TOKEN)return void c(this._getItem(this.CONSTANTS.STORAGE.ERROR_DESCRIPTION),b.parameters[this.CONSTANTS.ACCESS_TOKEN]||b.parameters[this.CONSTANTS.ID_TOKEN])}},AuthenticationContext.prototype._getNavigateUrl=function(a,b){var c="common";this.config.tenant&&(c=this.config.tenant),this.config.instance&&(this.instance=this.config.instance);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._extractUserName=function(a){try{var b=this._extractIdToken(a);if(b){if(b.hasOwnProperty("upn"))return b.upn;if(b.hasOwnProperty("email"))return b.email}}catch(c){this.error("The returned id_token could not be decoded",c)}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;c>m;m+=4){if(d=b.indexOf(a.charAt(m)),e=b.indexOf(a.charAt(m+1)),f=b.indexOf(a.charAt(m+2)),g=b.indexOf(a.charAt(m+3)),m+2===c-1){h=d<<18|e<<12|f<<6,i=h>>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=[];return 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),b.correlationId&&d.push("client-request-id="+encodeURIComponent(b.correlationId))),d.join("&")},AuthenticationContext.prototype._deserialize=function(a){var b,c=/\+/g,d=/([^&=]+)=?([^&]*)/g,e=function(a){return decodeURIComponent(a.replace(c," "))},f={};for(b=d.exec(a);b;)f[e(b[1])]=e(b[2]),b=d.exec(a);return f},AuthenticationContext.prototype._guid=function(){for(var a="xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx",b="0123456789abcdef",c=0,d="",e=0;36>e;e++)"-"!==a[e]&&"4"!==a[e]&&(c=16*Math.random()|0),"x"===a[e]?d+=b[c]:"y"===a[e]?(c&=3,c|=8,d+=b[c]):d+=a[e];return d},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||-1===window.navigator.userAgent.indexOf("MSIE 5.0"))){var c=document.createElement("iframe");c.setAttribute("id",a),c.style.visibility="hidden",c.style.position="absolute",c.style.width=c.style.height=c.borderWidth="0px",b=document.getElementsByTagName("body")[0].appendChild(c)}else document.body&&document.body.insertAdjacentHTML&&document.body.insertAdjacentHTML("beforeEnd",'');window.frames&&window.frames[a]&&(b=window.frames[a])}return b}},AuthenticationContext.prototype._saveItem=function(a,b){return this.config&&this.config.cacheLocation&&"localStorage"===this.config.cacheLocation?this._supportsLocalStorage()?(localStorage.setItem(a,b),!0):(this.info("Local storage is not supported"),!1):this._supportsSessionStorage()?(sessionStorage.setItem(a,b),!0):(this.info("Session storage is not supported"),!1)},AuthenticationContext.prototype._getItem=function(a){return this.config&&this.config.cacheLocation&&"localStorage"===this.config.cacheLocation?this._supportsLocalStorage()?localStorage.getItem(a):(this.info("Local storage is not supported"),null):this._supportsSessionStorage()?sessionStorage.getItem(a):(this.info("Session storage is not supported"),null)},AuthenticationContext.prototype._supportsLocalStorage=function(){try{return"localStorage"in window&&window.localStorage}catch(a){return!1}},AuthenticationContext.prototype._supportsSessionStorage=function(){try{return"sessionStorage"in window&&window.sessionStorage}catch(a){return!1}},AuthenticationContext.prototype._cloneConfig=function(a){if(null===a||"object"!=typeof a)return a;var b={};for(var c in a)a.hasOwnProperty(c)&&(b[c]=a[c]);return b},AuthenticationContext.prototype._addLibMetadata=function(){return"&x-client-SKU=Js&x-client-Ver="+this._libVersion()},AuthenticationContext.prototype.log=function(a,b,c){if(a<=Logging.level){var d=this.config.correlationId,e=(new Date).toUTCString(),f=e+":"+d+"-"+this._libVersion()+"-"+this.CONSTANTS.LEVEL_STRING_MAP[a]+" "+b;c&&(f+="\nstack:\n"+c.stack),Logging.log(f)}},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.9"}; \ No newline at end of file From b48d5959b024e0948efc80c8a891d3ae2007e91b Mon Sep 17 00:00:00 2001 From: Tushar Gupta Date: Mon, 4 Apr 2016 16:28:27 -0700 Subject: [PATCH 6/6] update readme.md --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 9753ff6e..2b71e338 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,10 @@ This is a GA released version. The current version is **1.0.9**. You have multiple ways of getting ADAL JS: +Via NPM: + + npm install adal-angular + Via CDN: