diff --git a/README.md b/README.md
index 0505bf7f..2cb218cf 100644
--- a/README.md
+++ b/README.md
@@ -21,7 +21,7 @@ If you find a security issue with our libraries or services please report it to
## The Library
-This is a GA released version. The current version is **1.0.10**.
+This is a GA released version. The current version is **1.0.11**.
You have multiple ways of getting ADAL JS:
@@ -32,10 +32,10 @@ Via NPM:
Via CDN:
-
-
+
+
-CDN will be updated to latest version 1.0.10.
+CDN will be updated to latest version 1.0.11.
Via Bower:
diff --git a/bower.json b/bower.json
index 79e7339e..f65046cd 100644
--- a/bower.json
+++ b/bower.json
@@ -1,6 +1,6 @@
{
"name": "adal-angular",
- "version": "1.0.10",
+ "version": "1.0.11",
"homepage": "https://github.com/AzureAD/azure-activedirectory-library-for-js",
"authors": [
"MSOpentech"
@@ -37,7 +37,7 @@
},
"devDependencies": {
"angular-resource": "~1.2.26",
- "angular-mocks": "~1.2.16",
+ "angular-mocks": "~1.2.26",
"jasmine": "2.0.0",
"angular-route": "~1.2.26"
}
diff --git a/changelog.txt b/changelog.txt
index 5d9fdade..24b4724a 100644
--- a/changelog.txt
+++ b/changelog.txt
@@ -1,3 +1,11 @@
+Version 1.0.11
+==========================
+* Adding support for using a special html for iFrames. This prevents app reloading in the iframe. Please see this: https://github.com/AzureAD/azure-activedirectory-library-for-js/wiki/FAQs#q1-my-app-is-re-loading-every-time-adal-renews-a-token
+* Fixing multiple root causes for infinte loops at the time of login or token renewal.
+* Fixing url paramters getting dropped after login.
+* Adding timeout to token renewal requests. Thanks @dmxfee for the Pull Request.
+* Use module pattern in adal.js
+
Version 1.0.10
==========================
Fixing infinite loop when refreshing tokens, adding extensibility for specifying anonymous endpoints
diff --git a/dist/adal-angular.min.js b/dist/adal-angular.min.js
index c7bbc283..f0de0170 100644
--- a/dist/adal-angular.min.js
+++ b/dist/adal-angular.min.js
@@ -1,2 +1,2 @@
-/*! adal-angular v1.0.10 2016-05-09 */
-"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}function j(b){if(a.config&&a.config.anonymousEndpoints)for(var c=0;c-1)return!0;return!1}var k=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);if(i.parameters.error)return a.callback(a._getItem(a.CONSTANTS.STORAGE.ERROR_DESCRIPTION),null),void(a._renewFailed=!0)}}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),b.isAuthenticated||!b.userName||a._renewActive||a._renewFailed||(a._renewActive=!0,a.acquireToken(a.config.loginResource,function(c,e){a._renewActive=!1,c?d.$broadcast("adal:loginFailure","auto renew failure"):e&&(b.isAuthenticated=!0)}));h(function(){c(a.config.loginResource),d.userInfo=b},1)},l=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())},m=function(c,d){d&&d.$$route&&(i(d.$$route,a.config)?b.isAuthenticated||a._renewActive||(a.info("Route change event for:"+g.$$url),l()):d.$$route.templateUrl&&!j(d.$$route.templateUrl)&&a.config.anonymousEndpoints.push(d.$$route.templateUrl))},n=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),l()):d.templateUrl&&!j(d.templateUrl)&&a.config.anonymousEndpoints.push(d.templateUrl))};return d.$on("$routeChangeStart",m),d.$on("$stateChangeStart",n),d.$on("$locationChangeStart",k),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(a.verbose("Url: "+c.url+" maps to resource: "+d),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
+/*! adal-angular v1.0.11 2016-07-19 */
+!function(){"use strict";if("undefined"!=typeof module&&module.exports&&(module.exports.inject=function(a){return new AuthenticationContext(a)}),angular){var a=angular.module("AdalAngular",[]);a.provider("adalAuthenticationService",function(){var a=null,b={isAuthenticated:!1,userName:"",loginError:"",profile:""},c=function(c){var d=a.getCachedToken(c);b.isAuthenticated=null!==d&&d.length>0;var e=a.getCachedUser()||{userName:""};b.userName=e.userName,b.profile=e.profile,b.loginError=a.getLoginError()};this.init=function(b,d){if(!b)throw new Error("You must set configOptions, when calling init");var e=window.location.hash,f=window.location.href;e&&(f=f.replace(e,"")),b.redirectUri=b.redirectUri||f,b.postLogoutRedirectUri=b.postLogoutRedirectUri||f,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}function j(b){if(a.config&&a.config.anonymousEndpoints)for(var c=0;c-1)return!0;return!1}var k=function(f,i,j){a.verbose("Location change event from "+j+" to "+i);var k=e.location.hash;if(a.isCallback(k)){a.verbose("Processing the hash: "+k);var l=a.getRequestInfo(k);if(a.saveTokenFromHash(l),l.requestType!==a.REQUEST_TYPE.LOGIN&&(a.callback=e.parent.AuthenticationContext().callback,l.requestType===a.REQUEST_TYPE.RENEW_TOKEN&&(a.callback=e.parent.callBackMappedToRenewStates[l.stateResponse])),l.stateMatch)if("function"==typeof a.callback){if(f.preventDefault(),l.requestType===a.REQUEST_TYPE.RENEW_TOKEN){if(l.parameters.access_token)return void a.callback(a._getItem(a.CONSTANTS.STORAGE.ERROR_DESCRIPTION),l.parameters.access_token);if(l.parameters.id_token)return void a.callback(a._getItem(a.CONSTANTS.STORAGE.ERROR_DESCRIPTION),l.parameters.id_token);if(l.parameters.error)return void a.callback(a._getItem(a.CONSTANTS.STORAGE.ERROR_DESCRIPTION),null)}}else{c(a.config.loginResource),b.userName?(h(function(){c(a.config.loginResource),d.userInfo=b},1),d.$broadcast("adal:loginSuccess")):d.$broadcast("adal:loginFailure",a._getItem(a.CONSTANTS.STORAGE.ERROR_DESCRIPTION));var m=a._getItem(a.CONSTANTS.STORAGE.LOGIN_REQUEST);m&&(a.verbose("Redirecting to start page: "+m),f.preventDefault(),!g.$$html5&&m.indexOf("#")>-1&&g.url(m.substring(m.indexOf("#")+1)),e.location=m)}else d.$broadcast("adal:stateMismatch",a._getItem(a.CONSTANTS.STORAGE.ERROR_DESCRIPTION))}else c(a.config.loginResource),b.isAuthenticated||!b.userName||a._renewActive||(a._renewActive=!0,a.acquireToken(a.config.loginResource,function(c,e){a._renewActive=!1,c?d.$broadcast("adal:loginFailure","auto renew failure"):e&&(b.isAuthenticated=!0)}));h(function(){c(a.config.loginResource),d.userInfo=b},1)},l=function(){a.info("Login event for:"+g.$$url),a.config&&a.config.localLoginUrl?g.path(a.config.localLoginUrl):(a.info("Start login at:"+window.location.href),d.$broadcast("adal:loginRedirect"),a.login())},m=function(c,d){d&&d.$$route&&(i(d.$$route,a.config)?b.isAuthenticated||a._renewActive||a.loginInProgress()||(a.info("Route change event for:"+g.$$url),l()):d.$$route.templateUrl&&!j(d.$$route.templateUrl)&&a.config.anonymousEndpoints.push(d.$$route.templateUrl))},n=function(c,d,e,f,h){d&&(i(d,a.config)?b.isAuthenticated||a._renewActive||a.loginInProgress()||(a.info("State change event for:"+g.$$url),l()):d.templateUrl&&!j(d.templateUrl)&&a.config.anonymousEndpoints.push(d.templateUrl))},o=function(b,c,d,e,f,g){a.verbose("State change error occured. Error: "+g),g&&g.data&&(a.info("Setting defaultPrevented to true if state change error occured because adal rejected a request. Error: "+g.data),b.preventDefault())};return d.$on("$routeChangeStart",m),d.$on("$stateChangeStart",n),d.$on("$locationChangeStart",k),d.$on("$stateChangeError",o),c(a.config.loginResource),d.userInfo=b,{config:a.config,login:function(){l()},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(a.verbose("Url: "+c.url+" maps to resource: "+d),null===d)return c;var e=a.getCachedToken(d);if(e)return a.info("Token is available for this url "+c.url),c.headers.Authorization="Bearer "+e,c;if(a.loginInProgress())return a.info("login is in progress."),c.data="login in progress, cancelling the request for "+c.url,b.reject(c);var f=b.defer();return a.acquireToken(d).then(function(b){a.verbose("Token is available"),c.headers.Authorization="Bearer "+b,f.resolve(c)},function(a){c.data=a,f.reject(c)}),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 9f99a40a..192de041 100644
--- a/dist/adal.min.js
+++ b/dist/adal.min.js
@@ -1,2 +1,2 @@
-/*! adal-angular v1.0.10 2016-05-09 */
-"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];if(!(a.indexOf("http://")>-1||a.indexOf("https://")>-1)){if(this.config&&this.config.anonymousEndpoints)for(var c=0;c-1)return null;return this.config.loginResource}return this._getHostFromUri(a)===this._getHostFromUri(this.config.redirectUri)?this.config.loginResource:null},AuthenticationContext.prototype._getHostFromUri=function(a){var b=String(a).replace(/^(https?:)\/\//,"");return b=b.split("/")[0]},AuthenticationContext.prototype.handleWindowCallback=function(){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.10"};
\ No newline at end of file
+/*! adal-angular v1.0.11 2016-07-19 */
+var AuthenticationContext=function(){"use strict";return AuthenticationContext=function(a){if(this.REQUEST_TYPE={LOGIN:"LOGIN",RENEW_TOKEN:"RENEW_TOKEN",UNKNOWN:"UNKNOWN"},this.CONSTANTS={ACCESS_TOKEN:"access_token",EXPIRES_IN:"expires_in",ID_TOKEN:"id_token",ERROR_DESCRIPTION:"error_description",SESSION_STATE:"session_state",STORAGE:{TOKEN_KEYS:"adal.token.keys",ACCESS_TOKEN_KEY:"adal.access.token.key",EXPIRATION_KEY:"adal.expiration.key",STATE_LOGIN:"adal.state.login",STATE_RENEW:"adal.state.renew",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",RENEW_STATUS:"adal.token.renew.status"},RESOURCE_DELIMETER:"|",ERR_MESSAGES:{NO_TOKEN:"User is not authorized"},LOADFRAME_TIMEOUT:"6000",TOKEN_RENEW_STATUS_CANCELED:"Canceled",TOKEN_RENEW_STATUS_COMPLETED:"Completed",TOKEN_RENEW_STATUS_IN_PROGRESS:"In Progress",LOGGING_LEVEL:{ERROR:0,WARN:1,INFO:2,VERBOSE:3},LEVEL_STRING_MAP:{0:"ERROR:",1:"WARNING:",2:"INFO:",3:"VERBOSE:"}},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");this.config=this._cloneConfig(a),this.config.instance&&(this.instance=this.config.instance),this.config.loginResource||(this.config.loginResource=this.config.clientId),this.config.redirectUri||(this.config.redirectUri=window.location.href),this.config.anonymousEndpoints||(this.config.anonymousEndpoints=[])},window.Logging={level:0,log:function(a){}},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 b=this._user.profile.upn.split("@");a+="&domain_hint="+encodeURIComponent(b[b.length-1])}return a},AuthenticationContext.prototype._createUser=function(a){var b=null,c=this._extractIdToken(a);return c&&c.hasOwnProperty("aud")&&(c.aud.toLowerCase()===this.config.clientId.toLowerCase()?(b={userName:"",profile:c},c.hasOwnProperty("upn")?b.userName=c.upn:c.hasOwnProperty("email")&&(b.userName=c.email)):this.warn("IdToken has invalid aud field")),b},AuthenticationContext.prototype._getHash=function(a){return a.indexOf("#/")>-1?a=a.substring(a.indexOf("#/")+2):a.indexOf("#")>-1&&(a=a.substring(1)),a},AuthenticationContext.prototype.isCallback=function(a){a=this._getHash(a);var b=this._deserialize(a);return b.hasOwnProperty(this.CONSTANTS.ERROR_DESCRIPTION)||b.hasOwnProperty(this.CONSTANTS.ACCESS_TOKEN)||b.hasOwnProperty(this.CONSTANTS.ID_TOKEN)},AuthenticationContext.prototype.getLoginError=function(){return this._getItem(this.CONSTANTS.STORAGE.LOGIN_ERROR)},AuthenticationContext.prototype.getRequestInfo=function(a){a=this._getHash(a);var b=this._deserialize(a),c={valid:!1,parameters:{},stateMatch:!1,stateResponse:"",requestType:this.REQUEST_TYPE.UNKNOWN};if(b&&(c.parameters=b,b.hasOwnProperty(this.CONSTANTS.ERROR_DESCRIPTION)||b.hasOwnProperty(this.CONSTANTS.ACCESS_TOKEN)||b.hasOwnProperty(this.CONSTANTS.ID_TOKEN))){c.valid=!0;var d="";if(!b.hasOwnProperty("state"))return this.warn("No state returned"),c;if(this.verbose("State: "+b.state),d=b.state,c.stateResponse=d,d===this._getItem(this.CONSTANTS.STORAGE.STATE_LOGIN))return c.requestType=this.REQUEST_TYPE.LOGIN,c.stateMatch=!0,c;if(!c.stateMatch&&window.parent&&window.parent.AuthenticationContext())for(var e=window.parent.AuthenticationContext()._renewStates,f=0;f-1&&b+1-1)return this.config.endpoints[b];if(!(a.indexOf("http://")>-1||a.indexOf("https://")>-1)){if(this.config&&this.config.anonymousEndpoints)for(var c=0;c-1)return null;return this.config.loginResource}return this._getHostFromUri(a)===this._getHostFromUri(this.config.redirectUri)?this.config.loginResource:null},AuthenticationContext.prototype._getHostFromUri=function(a){var b=String(a).replace(/^(https?:)\/\//,"");return b=b.split("/")[0]},AuthenticationContext.prototype.handleWindowCallback=function(){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&&window.parent!==window)return this.verbose("Window is in iframe"),c=window.parent.callBackMappedToRenewStates[b.stateResponse],void(c&&c(this._getItem(this.CONSTANTS.STORAGE.ERROR_DESCRIPTION),b.parameters[this.CONSTANTS.ACCESS_TOKEN]||b.parameters[this.CONSTANTS.ID_TOKEN]));window&&window.oauth2Callback&&(this.verbose("Window is redirecting"),c=this.callback),window.location=this._getItem(this.CONSTANTS.STORAGE.LOGIN_REQUEST)}},AuthenticationContext.prototype._getNavigateUrl=function(a,b){var c="common";this.config.tenant&&(c=this.config.tenant);var d=this.instance+c+"/oauth2/authorize"+this._serialize(a,this.config,b)+this._addLibMetadata();return this.info("Navigate url:"+d),d},AuthenticationContext.prototype._extractIdToken=function(a){var b=this._decodeJwt(a);if(!b)return null;try{var c=b.JWSPayload,d=this._base64DecodeStringUrlSafe(c);return d?JSON.parse(d):(this.info("The returned id_token could not be base64 url safe decoded."),null)}catch(e){this.error("The returned id_token could not be decoded",e)}return null},AuthenticationContext.prototype._base64DecodeStringUrlSafe=function(a){return a=a.replace(/-/g,"+").replace(/_/g,"/"),window.atob?decodeURIComponent(escape(window.atob(a))):decodeURIComponent(escape(this._decode(a)))},AuthenticationContext.prototype._decode=function(a){var b="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";a=String(a).replace(/=+$/,"");var c=a.length;if(c%4===1)throw new Error("The token to be decoded is not correctly encoded.");for(var d,e,f,g,h,i,j,k,l="",m=0;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=[];if(null!==b){d.push("?response_type="+a),d.push("client_id="+encodeURIComponent(b.clientId)),c&&d.push("resource="+encodeURIComponent(c)),d.push("redirect_uri="+encodeURIComponent(b.redirectUri)),d.push("state="+encodeURIComponent(b.state)),b.hasOwnProperty("slice")&&d.push("slice="+encodeURIComponent(b.slice)),b.hasOwnProperty("extraQueryParameter")&&d.push(b.extraQueryParameter);var e=b.correlationId?b.correlationId:this._guid();d.push("client-request-id="+encodeURIComponent(e))}return d.join("&")},AuthenticationContext.prototype._deserialize=function(a){var b,c=/\+/g,d=/([^&=]+)=([^&]*)/g,e=function(a){return decodeURIComponent(a.replace(c," "))},f={};for(b=d.exec(a);b;)f[e(b[1])]=e(b[2]),b=d.exec(a);return f},AuthenticationContext.prototype._guid=function(){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=(new Date).toUTCString(),e="";e=this.config.correlationId?d+":"+this.config.correlationId+"-"+this._libVersion()+"-"+this.CONSTANTS.LEVEL_STRING_MAP[a]+" "+b:d+":"+this._libVersion()+"-"+this.CONSTANTS.LEVEL_STRING_MAP[a]+" "+b,c&&(e+="\nstack:\n"+c.stack),Logging.log(e)}},AuthenticationContext.prototype.error=function(a,b){this.log(this.CONSTANTS.LOGGING_LEVEL.ERROR,a,b)},AuthenticationContext.prototype.warn=function(a){this.log(this.CONSTANTS.LOGGING_LEVEL.WARN,a,null)},AuthenticationContext.prototype.info=function(a){this.log(this.CONSTANTS.LOGGING_LEVEL.INFO,a,null)},AuthenticationContext.prototype.verbose=function(a){this.log(this.CONSTANTS.LOGGING_LEVEL.VERBOSE,a,null)},AuthenticationContext.prototype._libVersion=function(){return"1.0.11"},"undefined"!=typeof module&&module.exports&&(module.exports=AuthenticationContext,module.exports.inject=function(a){return new AuthenticationContext(a)}),AuthenticationContext}();
\ No newline at end of file
diff --git a/lib/adal-angular.js b/lib/adal-angular.js
index 1b72a46f..20f6c0c3 100644
--- a/lib/adal-angular.js
+++ b/lib/adal-angular.js
@@ -1,5 +1,5 @@
//----------------------------------------------------------------------
-// AdalJS v1.0.10
+// AdalJS v1.0.11
// @preserve Copyright (c) Microsoft Open Technologies, Inc.
// All Rights Reserved
// Apache License 2.0
@@ -16,16 +16,17 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//----------------------------------------------------------------------
-'use strict';
-
-if (typeof module !== 'undefined' && module.exports) {
- module.exports.inject = function (conf) {
- return new AuthenticationContext(conf);
- };
-}
(function () {
// ============= Angular modules- Start =============
+ 'use strict';
+
+ if (typeof module !== 'undefined' && module.exports) {
+ module.exports.inject = function (conf) {
+ return new AuthenticationContext(conf);
+ };
+ }
+
if (angular) {
var AdalModule = angular.module('AdalAngular', []);
@@ -65,7 +66,7 @@ if (typeof module !== 'undefined' && module.exports) {
throw new Error('You must set configOptions, when calling init');
}
- // loginresource is used to set authenticated status
+ // loginResource is used to set authenticated status
updateDataFromCache(_adal.config.loginResource);
};
@@ -73,21 +74,16 @@ if (typeof module !== 'undefined' && module.exports) {
// $rootScope, $window, $q, $location, $timeout are injected by Angular
this.$get = ['$rootScope', '$window', '$q', '$location', '$timeout', function ($rootScope, $window, $q, $location, $timeout) {
- var locationChangeHandler = function () {
+ var locationChangeHandler = function (event, newUrl, oldUrl) {
+ _adal.verbose('Location change event from ' + oldUrl + ' to ' + newUrl);
var hash = $window.location.hash;
if (_adal.isCallback(hash)) {
// callback can come from login or iframe request
-
+ _adal.verbose('Processing the hash: ' + hash);
var requestInfo = _adal.getRequestInfo(hash);
_adal.saveTokenFromHash(requestInfo);
- if ($location.$$html5) {
- $window.location = $window.location.origin + $window.location.pathname;
- } else {
- $window.location.hash = '';
- }
-
if (requestInfo.requestType !== _adal.REQUEST_TYPE.LOGIN) {
_adal.callback = $window.parent.AuthenticationContext().callback;
if (requestInfo.requestType === _adal.REQUEST_TYPE.RENEW_TOKEN) {
@@ -95,12 +91,15 @@ if (typeof module !== 'undefined' && module.exports) {
}
}
- // Return to callback if it is send from iframe
+ // Return to callback if it is sent from iframe
if (requestInfo.stateMatch) {
if (typeof _adal.callback === 'function') {
+ // since this is a token renewal request in iFrame, we don't need to proceed with the location change.
+ event.preventDefault();
+
// Call within the same context without full page redirect keeps the callback
if (requestInfo.requestType === _adal.REQUEST_TYPE.RENEW_TOKEN) {
- // Idtoken or Accestoken can be renewed
+ // id_token or access_token can be renewed
if (requestInfo.parameters['access_token']) {
_adal.callback(_adal._getItem(_adal.CONSTANTS.STORAGE.ERROR_DESCRIPTION), requestInfo.parameters['access_token']);
return;
@@ -109,7 +108,6 @@ if (typeof module !== 'undefined' && module.exports) {
return;
} else if (requestInfo.parameters['error']) {
_adal.callback(_adal._getItem(_adal.CONSTANTS.STORAGE.ERROR_DESCRIPTION), null);
- _adal._renewFailed = true;
return;
}
}
@@ -117,29 +115,28 @@ if (typeof module !== 'undefined' && module.exports) {
// normal full login redirect happened on the page
updateDataFromCache(_adal.config.loginResource);
if (_oauthData.userName) {
- //IDtoken is added as token for the app
$timeout(function () {
+ // id_token is added as token for the app
updateDataFromCache(_adal.config.loginResource);
$rootScope.userInfo = _oauthData;
- // redirect to login requested page
- 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);
- if (paramsJSON) {
- // If params were stored redirect to the page and then
- // initialize the params
- var loginStartPageParams = JSON.parse(paramsJSON);
- $location.url(loginStartPage).search(loginStartPageParams);
- } else {
- $location.url(loginStartPage);
- }
- }
}, 1);
+
$rootScope.$broadcast('adal:loginSuccess');
} else {
$rootScope.$broadcast('adal:loginFailure', _adal._getItem(_adal.CONSTANTS.STORAGE.ERROR_DESCRIPTION));
}
+
+ // redirect to login start page
+ var loginStartPage = _adal._getItem(_adal.CONSTANTS.STORAGE.LOGIN_REQUEST);
+ if (loginStartPage) {
+ // prevent the current location change and redirect the user back to the login start page
+ _adal.verbose('Redirecting to start page: ' + loginStartPage);
+ event.preventDefault();
+ if (!$location.$$html5 && loginStartPage.indexOf('#') > -1) {
+ $location.url(loginStartPage.substring(loginStartPage.indexOf('#') + 1));
+ }
+ $window.location = loginStartPage;
+ }
}
}
else {
@@ -150,8 +147,8 @@ if (typeof module !== 'undefined' && module.exports) {
// No callback. App resumes after closing or moving to new page.
// Check token and username
updateDataFromCache(_adal.config.loginResource);
- if (!_oauthData.isAuthenticated && _oauthData.userName && !_adal._renewActive && !_adal._renewFailed) {
- // Idtoken is expired or not present
+ if (!_oauthData.isAuthenticated && _oauthData.userName && !_adal._renewActive) {
+ // id_token is expired or not present
_adal._renewActive = true;
_adal.acquireToken(_adal.config.loginResource, function (error, tokenOut) {
_adal._renewActive = false;
@@ -176,9 +173,9 @@ if (typeof module !== 'undefined' && module.exports) {
_adal.info('Login event for:' + $location.$$url);
if (_adal.config && _adal.config.localLoginUrl) {
$location.path(_adal.config.localLoginUrl);
- } else {
+ }
+ else {
// directly start login flow
- _adal._saveItem(_adal.CONSTANTS.STORAGE.START_PAGE, $location.$$url);
_adal.info('Start login at:' + window.location.href);
$rootScope.$broadcast('adal:loginRedirect');
_adal.login();
@@ -203,9 +200,11 @@ if (typeof module !== 'undefined' && module.exports) {
var routeChangeHandler = function (e, nextRoute) {
if (nextRoute && nextRoute.$$route) {
if (isADLoginRequired(nextRoute.$$route, _adal.config)) {
- if (!_oauthData.isAuthenticated && !_adal._renewActive) {
- _adal.info('Route change event for:' + $location.$$url);
- loginHandler();
+ if (!_oauthData.isAuthenticated) {
+ if (!_adal._renewActive && !_adal.loginInProgress()) {
+ _adal.info('Route change event for:' + $location.$$url);
+ loginHandler();
+ }
}
}
else {
@@ -219,18 +218,11 @@ if (typeof module !== 'undefined' && module.exports) {
var stateChangeHandler = function (e, toState, toParams, fromState, fromParams) {
if (toState) {
if (isADLoginRequired(toState, _adal.config)) {
- 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();
+ if (!_oauthData.isAuthenticated) {
+ if (!_adal._renewActive && !_adal.loginInProgress()) {
+ _adal.info('State change event for:' + $location.$$url);
+ loginHandler();
+ }
}
}
else {
@@ -241,6 +233,17 @@ if (typeof module !== 'undefined' && module.exports) {
}
};
+ var stateChangeErrorHandler = function (event, toState, toParams, fromState, fromParams, error) {
+ _adal.verbose("State change error occured. Error: " + error);
+
+ // adal interceptor sets the error on config.data property. If it is set, it means state change is rejected by adal,
+ // in which case set the defaultPrevented to true to avoid url update as that sometimesleads to infinte loop.
+ if (error && error.data) {
+ _adal.info("Setting defaultPrevented to true if state change error occured because adal rejected a request. Error: " + error.data);
+ event.preventDefault();
+ }
+ };
+
// Route change event tracking to receive fragment and also auto renew tokens
$rootScope.$on('$routeChangeStart', routeChangeHandler);
@@ -248,6 +251,8 @@ if (typeof module !== 'undefined' && module.exports) {
$rootScope.$on('$locationChangeStart', locationChangeHandler);
+ $rootScope.$on('$stateChangeError', stateChangeErrorHandler);
+
updateDataFromCache(_adal.config.loginResource);
$rootScope.userInfo = _oauthData;
@@ -255,7 +260,7 @@ if (typeof module !== 'undefined' && module.exports) {
// public methods will be here that are accessible from Controller
config: _adal.config,
login: function () {
- _adal.login();
+ loginHandler();
},
loginInProgress: function () {
return _adal.loginInProgress();
@@ -323,37 +328,36 @@ if (typeof module !== 'undefined' && module.exports) {
request: function (config) {
if (config) {
- // This interceptor needs to load service, but dependeny definition causes circular reference error.
- // Loading with injector is suggested at github. https://github.com/angular/angular.js/issues/2367
-
config.headers = config.headers || {};
-
var resource = authService.getResourceForEndpoint(config.url);
authService.verbose('Url: ' + config.url + ' maps to resource: ' + resource);
if (resource === null) {
return config;
}
-
var tokenStored = authService.getCachedToken(resource);
if (tokenStored) {
- authService.info('Token is avaliable for this url ' + config.url);
+ authService.info('Token is available for this url ' + config.url);
// check endpoint mapping if provided
config.headers.Authorization = 'Bearer ' + tokenStored;
return config;
- } else {
+ }
+ else {
// Cancel request if login is starting
if (authService.loginInProgress()) {
- authService.info('login already start.');
- return $q.reject('login in progress, cancelling the request');
- } else {
+ authService.info('login is in progress.');
+ config.data = 'login in progress, cancelling the request for ' + config.url;
+ return $q.reject(config);
+ }
+ else {
// delayed request to return after iframe completes
var delayedRequest = $q.defer();
authService.acquireToken(resource).then(function (token) {
- authService.verbose('Token is avaliable');
+ authService.verbose('Token is available');
config.headers.Authorization = 'Bearer ' + token;
delayedRequest.resolve(config);
}, function (err) {
- delayedRequest.reject(err);
+ config.data = err;
+ delayedRequest.reject(config);
});
return delayedRequest.promise;
@@ -364,7 +368,7 @@ if (typeof module !== 'undefined' && module.exports) {
}
},
responseError: function (rejection) {
- authService.info('Getting error in the response');
+ authService.info('Getting error in the response.');
if (rejection) {
if (rejection.status === 401) {
var resource = authService.getResourceForEndpoint(rejection.config.url);
diff --git a/lib/adal.js b/lib/adal.js
index 1051ce1b..f9db922a 100644
--- a/lib/adal.js
+++ b/lib/adal.js
@@ -1,5 +1,5 @@
-//----------------------------------------------------------------------
-// AdalJS v1.0.10
+//----------------------------------------------------------------------
+// AdalJS v1.0.11
// @preserve Copyright (c) Microsoft Open Technologies, Inc.
// All Rights Reserved
// Apache License 2.0
@@ -16,1173 +16,1182 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//----------------------------------------------------------------------
-'use strict';
-
-// node.js usage for tests
-var Logging = {
- level: 0,
- log: function () { }
-};
-
-var AuthenticationContext;
-if (typeof module !== 'undefined' && module.exports) {
- module.exports.inject = function (conf) {
- return new AuthenticationContext(conf);
- };
-}
-
-/**
- * Config information
- * @public
- * @class Config
- * @property {tenant} Your target tenant
- * @property {clientId} Identifier assigned to your app by Azure Active Directory
- * @property {redirectUri} Endpoint at which you expect to receive tokens
- * @property {instance} Azure Active Directory Instance(default:https://login.microsoftonline.com/)
- * @property {endpoints} Collection of {Endpoint-ResourceId} used for autmatically attaching tokens in webApi calls
- */
-
-/**
- * User information from idtoken.
- * @class User
- * @property {string} userName - username assigned from upn or email.
- * @property {object} profile - properties parsed from idtoken.
- */
-
-/**
- * Creates a new AuthenticationContext object.
- * @constructor
- * @param {object} config Configuration options for AuthenticationContext
- *
- **/
-AuthenticationContext = function (config) {
+
+var AuthenticationContext = (function () {
+
+ 'use strict';
+
/**
- * Enum for request type
- * @enum {string}
+ * Config information
+ * @public
+ * @class Config
+ * @property {tenant} Your target tenant
+ * @property {clientId} Identifier assigned to your app by Azure Active Directory
+ * @property {redirectUri} Endpoint at which you expect to receive tokens
+ * @property {instance} Azure Active Directory Instance(default:https://login.microsoftonline.com/)
+ * @property {endpoints} Collection of {Endpoint-ResourceId} used for autmatically attaching tokens in webApi calls
*/
- this.REQUEST_TYPE = {
- LOGIN: 'LOGIN',
- RENEW_TOKEN: 'RENEW_TOKEN',
- UNKNOWN: 'UNKNOWN'
- };
/**
- * Enum for storage constants
- * @enum {string}
+ * User information from idtoken.
+ * @class User
+ * @property {string} userName - username assigned from upn or email.
+ * @property {object} profile - properties parsed from idtoken.
*/
- 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:'
+
+ /**
+ * Creates a new AuthenticationContext object.
+ * @constructor
+ * @param {object} config Configuration options for AuthenticationContext
+ *
+ **/
+ AuthenticationContext = function (config) {
+ /**
+ * Enum for request type
+ * @enum {string}
+ */
+ this.REQUEST_TYPE = {
+ LOGIN: 'LOGIN',
+ RENEW_TOKEN: 'RENEW_TOKEN',
+ UNKNOWN: 'UNKNOWN'
+ };
+
+ /**
+ * Enum for storage constants
+ * @enum {string}
+ */
+ this.CONSTANTS = {
+ ACCESS_TOKEN: 'access_token',
+ EXPIRES_IN: 'expires_in',
+ ID_TOKEN: 'id_token',
+ ERROR_DESCRIPTION: 'error_description',
+ SESSION_STATE: 'session_state',
+ STORAGE: {
+ TOKEN_KEYS: 'adal.token.keys',
+ ACCESS_TOKEN_KEY: 'adal.access.token.key',
+ EXPIRATION_KEY: 'adal.expiration.key',
+ STATE_LOGIN: 'adal.state.login',
+ STATE_RENEW: 'adal.state.renew',
+ 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',
+ RENEW_STATUS: 'adal.token.renew.status'
+ },
+ RESOURCE_DELIMETER: '|',
+ ERR_MESSAGES: {
+ NO_TOKEN: 'User is not authorized'
+ },
+ LOADFRAME_TIMEOUT: '6000',
+ TOKEN_RENEW_STATUS_CANCELED: 'Canceled',
+ TOKEN_RENEW_STATUS_COMPLETED: 'Completed',
+ TOKEN_RENEW_STATUS_IN_PROGRESS: 'In Progress',
+ LOGGING_LEVEL: {
+ ERROR: 0,
+ WARN: 1,
+ INFO: 2,
+ VERBOSE: 3
+ },
+ LEVEL_STRING_MAP: {
+ 0: 'ERROR:',
+ 1: 'WARNING:',
+ 2: 'INFO:',
+ 3: 'VERBOSE:'
+ }
+ };
+
+ 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 = {};
+ this._loginInProgress = false;
+ this._renewStates = [];
+
+ window.callBackMappedToRenewStates = {};
+ window.callBacksMappedToRenewStates = {};
+
+ // validate before constructor assignments
+ if (config.displayCall && typeof config.displayCall !== 'function') {
+ throw new Error('displayCall is not a function');
+ }
+
+ if (!config.clientId) {
+ throw new Error('clientId is required');
+ }
+
+ this.config = this._cloneConfig(config);
+
+ if (this.config.instance) {
+ this.instance = this.config.instance;
+ }
+
+ // App can request idtoken for itself using clientid as resource
+ if (!this.config.loginResource) {
+ this.config.loginResource = this.config.clientId;
+ }
+
+ if (!this.config.redirectUri) {
+ this.config.redirectUri = window.location.href;
+ }
+
+ if (!this.config.anonymousEndpoints) {
+ this.config.anonymousEndpoints = [];
}
};
- 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 = {};
- this._loginInProgress = false;
- this._renewStates = [];
-
- window.callBackMappedToRenewStates = {};
- window.callBacksMappedToRenewStates = {};
-
- // validate before constructor assignments
- if (config.displayCall && typeof config.displayCall !== 'function') {
- throw new Error('displayCall is not a function');
- }
+ window.Logging = {
+ level: 0,
+ log: function (message) { }
+ };
- if (!config.clientId) {
- throw new Error('clientId is required');
- }
+ /**
+ * Gets initial Idtoken for the app backend
+ * Saves the resulting Idtoken in localStorage.
+ */
+ AuthenticationContext.prototype.login = function () {
+ // Token is not present and user needs to login
+ var expectedState = this._guid();
+ this.config.state = expectedState;
+ this._idTokenNonce = this._guid();
+ this.verbose('Expected state: ' + expectedState + ' startPage:' + window.location);
+ this._saveItem(this.CONSTANTS.STORAGE.LOGIN_REQUEST, window.location);
+ this._saveItem(this.CONSTANTS.STORAGE.LOGIN_ERROR, '');
+ this._saveItem(this.CONSTANTS.STORAGE.STATE_LOGIN, expectedState);
+ this._saveItem(this.CONSTANTS.STORAGE.NONCE_IDTOKEN, this._idTokenNonce);
+ this._saveItem(this.CONSTANTS.STORAGE.ERROR, '');
+ this._saveItem(this.CONSTANTS.STORAGE.ERROR_DESCRIPTION, '');
+
+
+ var urlNavigate = this._getNavigateUrl('id_token', null) + '&nonce=' + encodeURIComponent(this._idTokenNonce);
+ this.frameCallInProgress = false;
+ this._loginInProgress = true;
+ if (this.config.displayCall) {
+ // User defined way of handling the navigation
+ this.config.displayCall(urlNavigate);
+ } else {
+ this.promptUser(urlNavigate);
+ }
+ // callback from redirected page will receive fragment. It needs to call oauth2Callback
+ };
- if (!config.correlationId) {
- config.correlationId = this._guid();
- }
+ AuthenticationContext.prototype.loginInProgress = function () {
+ return this._loginInProgress;
+ };
- this.config = this._cloneConfig(config);
+ AuthenticationContext.prototype._hasResource = function (key) {
+ var keys = this._getItem(this.CONSTANTS.STORAGE.TOKEN_KEYS);
+ return keys && !this._isEmpty(keys) && (keys.indexOf(key + this.CONSTANTS.RESOURCE_DELIMETER) > -1);
+ };
- // App can request idtoken for itself using clientid as resource
- if (!this.config.loginResource) {
- this.config.loginResource = this.config.clientId;
- }
+ /**
+ * Gets token for the specified resource from local storage cache
+ * @param {string} resource A URI that identifies the resource for which the token is valid.
+ * @returns {string} token if exists and not expired or null
+ */
+ AuthenticationContext.prototype.getCachedToken = function (resource) {
+ if (!this._hasResource(resource)) {
+ return null;
+ }
- if (!this.config.redirectUri) {
- this.config.redirectUri = window.location.href;
- }
-};
-
-/**
- * Gets initial Idtoken for the app backend
- * Saves the resulting Idtoken in localStorage.
- */
-AuthenticationContext.prototype.login = function () {
- // Token is not present and user needs to login
- var expectedState = this._guid();
- this.config.state = expectedState;
- this._idTokenNonce = this._guid();
- this.verbose('Expected state: ' + expectedState + ' startPage:' + window.location);
- this._saveItem(this.CONSTANTS.STORAGE.LOGIN_REQUEST, window.location);
- this._saveItem(this.CONSTANTS.STORAGE.LOGIN_ERROR, '');
- this._saveItem(this.CONSTANTS.STORAGE.STATE_LOGIN, expectedState);
- this._saveItem(this.CONSTANTS.STORAGE.NONCE_IDTOKEN, this._idTokenNonce);
- this._saveItem(this.CONSTANTS.STORAGE.ERROR, '');
- this._saveItem(this.CONSTANTS.STORAGE.ERROR_DESCRIPTION, '');
-
-
- var urlNavigate = this._getNavigateUrl('id_token', null) + '&nonce=' + encodeURIComponent(this._idTokenNonce);
- this.frameCallInProgress = false;
- this._loginInProgress = true;
- if (this.config.displayCall) {
- // User defined way of handling the navigation
- this.config.displayCall(urlNavigate);
- } else {
- this.promptUser(urlNavigate);
- }
- // callback from redirected page will receive fragment. It needs to call oauth2Callback
-};
-
-AuthenticationContext.prototype.loginInProgress = function () {
- return this._loginInProgress;
-};
-
-AuthenticationContext.prototype._hasResource = function (key) {
- var keys = this._getItem(this.CONSTANTS.STORAGE.TOKEN_KEYS);
- return keys && !this._isEmpty(keys) && (keys.indexOf(key + this.CONSTANTS.RESOURCE_DELIMETER) > -1);
-};
-
-/**
- * Gets token for the specified resource from local storage cache
- * @param {string} resource A URI that identifies the resource for which the token is valid.
- * @returns {string} token if exists and not expired or null
- */
-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);
- 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 expiration is within offset, it will force renew
- var offset = this.config.expireOffsetSeconds || 120;
+ if (expired && (expired > this._now() + offset)) {
+ return token;
+ } else {
+ this._saveItem(this.CONSTANTS.STORAGE.ACCESS_TOKEN_KEY + resource, '');
+ this._saveItem(this.CONSTANTS.STORAGE.EXPIRATION_KEY + resource, 0);
+ return null;
+ }
+ };
+ /**
+ * Retrieves and parse idToken from localstorage
+ * @returns {User} user object
+ */
+ AuthenticationContext.prototype.getCachedUser = function () {
+ if (this._user) {
+ return this._user;
+ }
- if (expired && (expired > this._now() + offset)) {
- return token;
- } else {
- this._saveItem(this.CONSTANTS.STORAGE.ACCESS_TOKEN_KEY + resource, '');
- this._saveItem(this.CONSTANTS.STORAGE.EXPIRATION_KEY + resource, 0);
- return null;
- }
-};
-
-/**
- * Retrieves and parse idToken from localstorage
- * @returns {User} user object
- */
-AuthenticationContext.prototype.getCachedUser = function () {
- if (this._user) {
+ var idtoken = this._getItem(this.CONSTANTS.STORAGE.IDTOKEN);
+ this._user = this._createUser(idtoken);
return this._user;
- }
+ };
- var idtoken = this._getItem(this.CONSTANTS.STORAGE.IDTOKEN);
- this._user = this._createUser(idtoken);
- return this._user;
-};
+ AuthenticationContext.prototype.registerCallback = function (expectedState, resource, callback) {
+ this._activeRenewals[resource] = expectedState;
+ if (!window.callBacksMappedToRenewStates[expectedState]) {
+ window.callBacksMappedToRenewStates[expectedState] = [];
+ }
+ var self = this;
+ window.callBacksMappedToRenewStates[expectedState].push(callback);
+ if (!window.callBackMappedToRenewStates[expectedState]) {
+ window.callBackMappedToRenewStates[expectedState] = function (message, token) {
+ for (var i = 0; i < window.callBacksMappedToRenewStates[expectedState].length; ++i) {
+ window.callBacksMappedToRenewStates[expectedState][i](message, token);
+ }
+ self._activeRenewals[resource] = null;
+ window.callBacksMappedToRenewStates[expectedState] = null;
+ window.callBackMappedToRenewStates[expectedState] = null;
+ };
+ }
+ };
+
+ // var errorResponse = {error:'', error_description:''};
+ // var token = 'string token';
+ // callback(errorResponse, token)
+ // with callback
+ /**
+ * Acquires access token with hidden iframe
+ * @param {string} resource ResourceUri identifying the target resource
+ * @returns {string} access token if request is successful
+ */
+ AuthenticationContext.prototype._renewToken = function (resource, callback) {
+ // use iframe to try refresh token
+ // use given resource to create new authz url
+ this.info('renewToken is called for resource:' + resource);
+ var frameHandle = this._addAdalFrame('adalRenewFrame' + resource);
+ var expectedState = this._guid() + '|' + resource;
+ this.config.state = expectedState;
+ // renew happens in iframe, so it keeps javascript context
+ this._renewStates.push(expectedState);
+
+ this.verbose('Renew token Expected state: ' + expectedState);
+ var urlNavigate = this._getNavigateUrl('token', resource) + '&prompt=none';
+ urlNavigate = this._addHintParameters(urlNavigate);
+
+ this.callback = callback;
+ this.registerCallback(expectedState, resource, callback);
+ this.verbose('Navigate to:' + urlNavigate);
+ this._saveItem(this.CONSTANTS.STORAGE.LOGIN_REQUEST, '');
+ frameHandle.src = 'about:blank';
+ this._loadFrameTimeout(urlNavigate, 'adalRenewFrame' + resource, resource);
-AuthenticationContext.prototype.registerCallback = function (expectedState, resource, callback) {
- this._activeRenewals[resource] = expectedState;
- if (!window.callBacksMappedToRenewStates[expectedState]) {
- window.callBacksMappedToRenewStates[expectedState] = [];
+ };
+
+ AuthenticationContext.prototype._renewIdToken = function (callback) {
+ // use iframe to try refresh token
+ this.info('renewIdToken is called');
+ var frameHandle = this._addAdalFrame('adalIdTokenFrame');
+ var expectedState = this._guid() + '|' + this.config.clientId;
+ this._idTokenNonce = this._guid();
+ this._saveItem(this.CONSTANTS.STORAGE.NONCE_IDTOKEN, this._idTokenNonce);
+ this.config.state = expectedState;
+ // renew happens in iframe, so it keeps javascript context
+ this._renewStates.push(expectedState);
+
+ this.verbose('Renew Idtoken Expected state: ' + expectedState);
+ var urlNavigate = this._getNavigateUrl('id_token', null) + '&prompt=none';
+ urlNavigate = this._addHintParameters(urlNavigate);
+
+ urlNavigate += '&nonce=' + encodeURIComponent(this._idTokenNonce);
+ this.registerCallback(expectedState, this.config.clientId, callback);
+ this.idTokenNonce = null;
+ this.verbose('Navigate to:' + urlNavigate);
+ this._saveItem(this.CONSTANTS.STORAGE.LOGIN_REQUEST, '');
+ frameHandle.src = 'about:blank';
+ this._loadFrameTimeout(urlNavigate, 'adalIdTokenFrame', this.config.clientId);
+ };
+
+ AuthenticationContext.prototype._urlContainsQueryStringParameter = function (name, url) {
+ // regex to detect pattern of a ? or & followed by the name parameter and an equals character
+ var regex = new RegExp("[\\?&]" + name + "=");
+ return regex.test(url);
}
- var self = this;
- window.callBacksMappedToRenewStates[expectedState].push(callback);
- if (!window.callBackMappedToRenewStates[expectedState]) {
- window.callBackMappedToRenewStates[expectedState] = function (message, token) {
- for (var i = 0; i < window.callBacksMappedToRenewStates[expectedState].length; ++i) {
- window.callBacksMappedToRenewStates[expectedState][i](message, token);
+
+ // Calling _loadFrame but with a timeout to signal failure in loadframeStatus. Callbacks are left
+ // registered when network errors occur and subsequent token requests for same resource are registered to the pending request
+ AuthenticationContext.prototype._loadFrameTimeout = function (urlNavigation, frameName, resource) {
+ //set iframe session to pending
+ this.verbose('Set loading state to pending for: ' + resource);
+ this._saveItem(this.CONSTANTS.STORAGE.RENEW_STATUS + resource, this.CONSTANTS.TOKEN_RENEW_STATUS_IN_PROGRESS);
+ this._loadFrame(urlNavigation, frameName);
+ var self = this;
+ setTimeout(function () {
+ if (self._getItem(self.CONSTANTS.STORAGE.RENEW_STATUS + resource) === self.CONSTANTS.TOKEN_RENEW_STATUS_IN_PROGRESS) {
+ // fail the iframe session if it's in pending state
+ self.verbose('Loading frame has timed out after: ' + (self.CONSTANTS.LOADFRAME_TIMEOUT / 1000) + ' seconds for resource ' + resource);
+ var expectedState = self._activeRenewals[resource];
+ if (expectedState && window.callBackMappedToRenewStates[expectedState]) {
+ window.callBackMappedToRenewStates[expectedState]('Token renewal operation failed due to timeout', null);
+ }
+
+ self._saveItem(self.CONSTANTS.STORAGE.RENEW_STATUS + resource, self.CONSTANTS.TOKEN_RENEW_STATUS_CANCELED);
}
- self._activeRenewals[resource] = null;
- window.callBacksMappedToRenewStates[expectedState] = null;
- window.callBackMappedToRenewStates[expectedState] = null;
- };
- }
-};
-
-// var errorResponse = {error:'', errorDescription:''};
-// var token = 'string token';
-// callback(errorResponse, token)
-// with callback
-/**
- * Acquires access token with hidden iframe
- * @param {string} resource ResourceUri identifying the target resource
- * @returns {string} access token if request is successfull
- */
-AuthenticationContext.prototype._renewToken = function (resource, callback) {
- // use iframe to try refresh token
- // use given resource to create new authz url
- this.info('renewToken is called for resource:' + resource);
- var frameHandle = this._addAdalFrame('adalRenewFrame' + resource);
- var expectedState = this._guid() + '|' + resource;
- this._idTokenNonce = this._guid();
- this.config.state = expectedState;
- // renew happens in iframe, so it keeps javascript context
- this._renewStates.push(expectedState);
-
- this.verbose('Renew token Expected state: ' + expectedState);
- var urlNavigate = this._getNavigateUrl('token', resource) + '&prompt=none&login_hint=' + encodeURIComponent(this._user.userName);
-
- // don't add domain_hint twice if user provided it in the extraQueryParameter value
- if (!this._urlContainsQueryStringParameter("domain_hint", urlNavigate)) {
- urlNavigate += '&domain_hint=' + encodeURIComponent(this._getDomainHint());
+ }, self.CONSTANTS.LOADFRAME_TIMEOUT);
}
- urlNavigate += '&nonce=' + encodeURIComponent(this._idTokenNonce);
- this.callback = callback;
- this.registerCallback(expectedState, resource, callback);
- this.idTokenNonce = null;
- this.verbose('Navigate to:' + urlNavigate);
- this._saveItem(this.CONSTANTS.STORAGE.LOGIN_REQUEST, '');
- frameHandle.src = 'about:blank';
- this._loadFrame(urlNavigate, 'adalRenewFrame' + resource);
-};
-
-AuthenticationContext.prototype._renewIdToken = function (callback) {
- // use iframe to try refresh token
- this.info('renewIdToken is called');
- var frameHandle = this._addAdalFrame('adalIdTokenFrame');
- var expectedState = this._guid() + '|' + this.config.clientId;
- this._idTokenNonce = this._guid();
- this._saveItem(this.CONSTANTS.STORAGE.NONCE_IDTOKEN, this._idTokenNonce);
- this.config.state = expectedState;
- // renew happens in iframe, so it keeps javascript context
- this._renewStates.push(expectedState);
-
- this.verbose('Renew Idtoken Expected state: ' + expectedState);
- var urlNavigate = this._getNavigateUrl('id_token', null) + '&prompt=none&login_hint=' + encodeURIComponent(this._user.userName);
-
- // don't add domain_hint twice if user provided it in the extraQueryParameter value
- if (!this._urlContainsQueryStringParameter("domain_hint", urlNavigate)) {
- urlNavigate += '&domain_hint=' + encodeURIComponent(this._getDomainHint());
- }
+ AuthenticationContext.prototype._loadFrame = function (urlNavigate, frameName) {
+ // This trick overcomes iframe navigation in IE
+ // IE does not load the page consistently in iframe
+ var self = this;
+ self.info('LoadFrame: ' + frameName);
+ var frameCheck = frameName;
+ setTimeout(function () {
+ var frameHandle = self._addAdalFrame(frameCheck);
+ if (frameHandle.src === '' || frameHandle.src === 'about:blank') {
+ frameHandle.src = urlNavigate;
+ self._loadFrame(urlNavigate, frameCheck);
+ }
+ }, 500);
+ };
+
+ /**
+ * Acquire token from cache if not expired and available. Acquires token from iframe if expired.
+ * @param {string} resource ResourceUri identifying the target resource
+ * @param {requestCallback} callback
+ */
+ AuthenticationContext.prototype.acquireToken = function (resource, callback) {
+ if (this._isEmpty(resource)) {
+ this.warn('resource is required');
+ callback('resource is required', null);
+ return;
+ }
- urlNavigate += '&nonce=' + encodeURIComponent(this._idTokenNonce);
- this.registerCallback(expectedState, this.config.clientId, callback);
- this.idTokenNonce = null;
- this.verbose('Navigate to:' + urlNavigate);
- this._saveItem(this.CONSTANTS.STORAGE.LOGIN_REQUEST, '');
- frameHandle.src = 'about:blank';
- this._loadFrame(urlNavigate, 'adalIdTokenFrame');
-};
-
-AuthenticationContext.prototype._urlContainsQueryStringParameter = function (name, url) {
- // regex to detect pattern of a ? or & followed by the name parameter and an equals character
- var regex = new RegExp("[\\?&]" + name + "=");
- return regex.test(url);
-}
-
-AuthenticationContext.prototype._loadFrame = function (urlNavigate, frameName) {
- // This trick overcomes iframe navigation in IE
- // IE does not load the page consistently in iframe
- var self = this;
- self.info('LoadFrame: ' + frameName);
- var frameCheck = frameName;
- setTimeout(function () {
- var frameHandle = self._addAdalFrame(frameCheck);
- if (frameHandle.src === '' || frameHandle.src === 'about:blank') {
- frameHandle.src = urlNavigate;
- self._loadFrame(urlNavigate, frameCheck);
+ var token = this.getCachedToken(resource);
+ if (token) {
+ this.info('Token is already in cache for resource:' + resource);
+ callback(null, token);
+ return;
}
- }, 500);
-};
-
-/**
- * Acquire token from cache if not expired and available. Acquires token from iframe if expired.
- * @param {string} resource ResourceUri identifying the target resource
- * @param {requestCallback} callback
- */
-AuthenticationContext.prototype.acquireToken = function (resource, callback) {
- if (this._isEmpty(resource)) {
- this.warn('resource is required');
- 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._user) {
+ this.warn('User login is required');
+ callback('User login is required', 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]) {
+ //Active renewals contains the state for each renewal.
+ this.registerCallback(this._activeRenewals[resource], resource, callback);
+ }
+ else {
+ if (resource === this.config.clientId) {
+ // App uses idtoken to send to api endpoints
+ // Default resource is tracked as clientid to store this token
+ this.verbose('renewing idtoken');
+ this._renewIdToken(callback);
+ } else {
+ this._renewToken(resource, callback);
+ }
+ }
+ };
- // refresh attept with iframe
- //Already renewing for this resource, callback when we get the token.
- if (this._activeRenewals[resource]) {
- //Active renewals contains the state for each renewal.
- this.registerCallback(this._activeRenewals[resource], resource, callback);
- }
- else {
- if (resource === this.config.clientId) {
- // App uses idtoken to send to api endpoints
- // Default resource is tracked as clientid to store this token
- this.verbose('renewing idtoken');
- this._renewIdToken(callback);
+ /**
+ * Redirect the Browser to Azure AD Authorization endpoint
+ * @param {string} urlNavigate The authorization request url
+ */
+ AuthenticationContext.prototype.promptUser = function (urlNavigate) {
+ if (urlNavigate) {
+ this.info('Navigate to:' + urlNavigate);
+ window.location.replace(urlNavigate);
} else {
- this._renewToken(resource, callback);
+ this.info('Navigate url is empty');
}
- }
-};
-
-/**
- * Redirect the Browser to Azure AD Authorization endpoint
- * @param {string} urlNavigate The authorization request url
- */
-AuthenticationContext.prototype.promptUser = function (urlNavigate) {
- if (urlNavigate) {
- this.info('Navigate to:' + urlNavigate);
- window.location.replace(urlNavigate);
- } else {
- this.info('Navigate url is empty');
- }
-};
-
-/**
- * Clear cache items.
- */
-AuthenticationContext.prototype.clearCache = function () {
- this._saveItem(this.CONSTANTS.STORAGE.ACCESS_TOKEN_KEY, '');
- this._saveItem(this.CONSTANTS.STORAGE.EXPIRATION_KEY, 0);
- this._saveItem(this.CONSTANTS.STORAGE.SESSION_STATE, '');
- this._saveItem(this.CONSTANTS.STORAGE.STATE_LOGIN, '');
- this._renewStates = [];
- this._saveItem(this.CONSTANTS.STORAGE.START_PAGE, '');
- this._saveItem(this.CONSTANTS.STORAGE.START_PAGE_PARAMS, '');
- this._saveItem(this.CONSTANTS.STORAGE.USERNAME, '');
- this._saveItem(this.CONSTANTS.STORAGE.IDTOKEN, '');
- this._saveItem(this.CONSTANTS.STORAGE.ERROR, '');
- this._saveItem(this.CONSTANTS.STORAGE.ERROR_DESCRIPTION, '');
- var keys = this._getItem(this.CONSTANTS.STORAGE.TOKEN_KEYS);
-
- if (!this._isEmpty(keys)) {
- keys = keys.split(this.CONSTANTS.RESOURCE_DELIMETER);
- for (var i = 0; i < keys.length; i++) {
- this._saveItem(this.CONSTANTS.STORAGE.ACCESS_TOKEN_KEY + keys[i], '');
- this._saveItem(this.CONSTANTS.STORAGE.EXPIRATION_KEY + keys[i], 0);
+ };
+
+ /**
+ * Clear cache items.
+ */
+ AuthenticationContext.prototype.clearCache = function () {
+ this._saveItem(this.CONSTANTS.STORAGE.ACCESS_TOKEN_KEY, '');
+ this._saveItem(this.CONSTANTS.STORAGE.EXPIRATION_KEY, 0);
+ this._saveItem(this.CONSTANTS.STORAGE.SESSION_STATE, '');
+ this._saveItem(this.CONSTANTS.STORAGE.STATE_LOGIN, '');
+ this._renewStates = [];
+ this._saveItem(this.CONSTANTS.STORAGE.USERNAME, '');
+ this._saveItem(this.CONSTANTS.STORAGE.IDTOKEN, '');
+ this._saveItem(this.CONSTANTS.STORAGE.ERROR, '');
+ this._saveItem(this.CONSTANTS.STORAGE.ERROR_DESCRIPTION, '');
+ var keys = this._getItem(this.CONSTANTS.STORAGE.TOKEN_KEYS);
+
+ if (!this._isEmpty(keys)) {
+ keys = keys.split(this.CONSTANTS.RESOURCE_DELIMETER);
+ for (var i = 0; i < keys.length; i++) {
+ this._saveItem(this.CONSTANTS.STORAGE.ACCESS_TOKEN_KEY + keys[i], '');
+ this._saveItem(this.CONSTANTS.STORAGE.EXPIRATION_KEY + keys[i], 0);
+ }
}
- }
- this._saveItem(this.CONSTANTS.STORAGE.TOKEN_KEYS, '');
-};
-
-/**
- * Clear cache items for a resource.
- */
-AuthenticationContext.prototype.clearCacheForResource = function (resource) {
- this._saveItem(this.CONSTANTS.STORAGE.STATE_RENEW, '');
- this._saveItem(this.CONSTANTS.STORAGE.ERROR, '');
- this._saveItem(this.CONSTANTS.STORAGE.ERROR_DESCRIPTION, '');
- if (this._hasResource(resource)) {
- this._saveItem(this.CONSTANTS.STORAGE.ACCESS_TOKEN_KEY + resource, '');
- this._saveItem(this.CONSTANTS.STORAGE.EXPIRATION_KEY + resource, 0);
- }
-};
-
-/**
- * Logout user will redirect page to logout endpoint.
- * After logout, it will redirect to post_logout page if provided.
- */
-AuthenticationContext.prototype.logOut = function () {
- this.clearCache();
- var tenant = 'common';
- var logout = '';
- this._user = null;
- if (this.config.tenant) {
- tenant = this.config.tenant;
- }
+ this._saveItem(this.CONSTANTS.STORAGE.TOKEN_KEYS, '');
+ };
- if (this.config.instance) {
- this.instance = this.config.instance;
- }
+ /**
+ * Clear cache items for a resource.
+ */
+ AuthenticationContext.prototype.clearCacheForResource = function (resource) {
+ this._saveItem(this.CONSTANTS.STORAGE.STATE_RENEW, '');
+ this._saveItem(this.CONSTANTS.STORAGE.ERROR, '');
+ this._saveItem(this.CONSTANTS.STORAGE.ERROR_DESCRIPTION, '');
+ if (this._hasResource(resource)) {
+ this._saveItem(this.CONSTANTS.STORAGE.ACCESS_TOKEN_KEY + resource, '');
+ this._saveItem(this.CONSTANTS.STORAGE.EXPIRATION_KEY + resource, 0);
+ }
+ };
- if (this.config.postLogoutRedirectUri) {
- logout = 'post_logout_redirect_uri=' + encodeURIComponent(this.config.postLogoutRedirectUri);
- }
+ /**
+ * Logout user will redirect page to logout endpoint.
+ * After logout, it will redirect to post_logout page if provided.
+ */
+ AuthenticationContext.prototype.logOut = function () {
+ this.clearCache();
+ var tenant = 'common';
+ var logout = '';
+ this._user = null;
+ if (this.config.tenant) {
+ tenant = this.config.tenant;
+ }
- var urlNavigate = this.instance + tenant + '/oauth2/logout?' + logout;
- this.info('Logout navigate to: ' + urlNavigate);
- this.promptUser(urlNavigate);
-};
-
-AuthenticationContext.prototype._isEmpty = function (str) {
- return (typeof str === 'undefined' || !str || 0 === str.length);
-};
-
-/**
- * This callback is displayed as part of the Requester class.
- * @callback requestCallback
- * @param {string} error
- * @param {User} user
- */
-
-/**
- * Gets a user profile
- * @param {requestCallback} cb - The callback that handles the response.
- */
-AuthenticationContext.prototype.getUser = function (callback) {
- // IDToken is first call
- if (typeof callback !== 'function') {
- throw new Error('callback is not a function');
- }
+ if (this.config.postLogoutRedirectUri) {
+ logout = 'post_logout_redirect_uri=' + encodeURIComponent(this.config.postLogoutRedirectUri);
+ }
- this.callback = callback;
+ var urlNavigate = this.instance + tenant + '/oauth2/logout?' + logout;
+ this.info('Logout navigate to: ' + urlNavigate);
+ this.promptUser(urlNavigate);
+ };
- // user in memory
- if (this._user) {
- this.callback(null, this._user);
- return;
- }
+ AuthenticationContext.prototype._isEmpty = function (str) {
+ return (typeof str === 'undefined' || !str || 0 === str.length);
+ };
- // frame is used to get idtoken
- var idtoken = this._getItem(this.CONSTANTS.STORAGE.IDTOKEN);
- if (!this._isEmpty(idtoken)) {
- this.info('User exists in cache: ');
- this._user = this._createUser(idtoken);
- this.callback(null, this._user);
- } else {
- this.warn('User information is not available');
- this.callback('User information is not available');
- }
-};
+ /**
+ * This callback is displayed as part of the Requester class.
+ * @callback requestCallback
+ * @param {string} error
+ * @param {User} user
+ */
-AuthenticationContext.prototype._getDomainHint = function () {
- if (this._user && this._user.userName && this._user.userName.indexOf('@') > -1) {
- var parts = this._user.userName.split('@');
- // local part can include @ in quotes. Sending last part handles that.
- return parts[parts.length - 1];
- }
+ /**
+ * Gets a user profile
+ * @param {requestCallback} cb - The callback that handles the response.
+ */
+ AuthenticationContext.prototype.getUser = function (callback) {
+ // IDToken is first call
+ if (typeof callback !== 'function') {
+ throw new Error('callback is not a function');
+ }
- return '';
-};
+ this.callback = callback;
-AuthenticationContext.prototype._createUser = function (idToken) {
- var user = null;
- var parsedJson = this._extractIdToken(idToken);
- if (parsedJson && parsedJson.hasOwnProperty('aud')) {
+ // user in memory
+ if (this._user) {
+ this.callback(null, this._user);
+ return;
+ }
- if (parsedJson.aud.toLowerCase() === this.config.clientId.toLowerCase()) {
+ // frame is used to get idtoken
+ var idtoken = this._getItem(this.CONSTANTS.STORAGE.IDTOKEN);
+ if (!this._isEmpty(idtoken)) {
+ this.info('User exists in cache: ');
+ this._user = this._createUser(idtoken);
+ this.callback(null, this._user);
+ } else {
+ this.warn('User information is not available');
+ this.callback('User information is not available');
+ }
+ };
- user = {
- userName: '',
- profile: parsedJson
- };
+ AuthenticationContext.prototype._addHintParameters = function (urlNavigate) {
+ // include hint params only if upn is present
+ if (this._user && this._user.profile && this._user.profile.hasOwnProperty('upn')) {
+
+ // add login_hint
+ urlNavigate += '&login_hint=' + encodeURIComponent(this._user.profile.upn);
- if (parsedJson.hasOwnProperty('upn')) {
- user.userName = parsedJson.upn;
- } else if (parsedJson.hasOwnProperty('email')) {
- user.userName = parsedJson.email;
+ // don't add domain_hint twice if user provided it in the extraQueryParameter value
+ if (!this._urlContainsQueryStringParameter("domain_hint", urlNavigate) && this._user.profile.upn.indexOf('@') > -1) {
+ var parts = this._user.profile.upn.split('@');
+ // local part can include @ in quotes. Sending last part handles that.
+ urlNavigate += '&domain_hint=' + encodeURIComponent(parts[parts.length - 1]);
}
- } else {
- this.warn('IdToken has invalid aud field');
}
+ return urlNavigate;
}
- return user;
-};
+ AuthenticationContext.prototype._createUser = function (idToken) {
+ var user = null;
+ var parsedJson = this._extractIdToken(idToken);
+ if (parsedJson && parsedJson.hasOwnProperty('aud')) {
+ if (parsedJson.aud.toLowerCase() === this.config.clientId.toLowerCase()) {
+
+ user = {
+ userName: '',
+ profile: parsedJson
+ };
+
+ if (parsedJson.hasOwnProperty('upn')) {
+ user.userName = parsedJson.upn;
+ } else if (parsedJson.hasOwnProperty('email')) {
+ user.userName = parsedJson.email;
+ }
+ } else {
+ this.warn('IdToken has invalid aud field');
+ }
-AuthenticationContext.prototype._getHash = function (hash) {
- if (hash.indexOf('#/') > -1) {
- hash = hash.substring(hash.indexOf('#/') + 2);
- } else if (hash.indexOf('#') > -1) {
- hash = hash.substring(1);
- }
+ }
+
+ return user;
+ };
+
+ AuthenticationContext.prototype._getHash = function (hash) {
+ if (hash.indexOf('#/') > -1) {
+ hash = hash.substring(hash.indexOf('#/') + 2);
+ } else if (hash.indexOf('#') > -1) {
+ hash = hash.substring(1);
+ }
- return hash;
-};
-
-/**
- * Checks if hash contains access token or id token or error_description
- * @param {string} hash - Hash passed from redirect page
- * @returns {Boolean}
- */
-AuthenticationContext.prototype.isCallback = function (hash) {
- hash = this._getHash(hash);
- var parameters = this._deserialize(hash);
- return (
- parameters.hasOwnProperty(this.CONSTANTS.ERROR_DESCRIPTION) ||
- parameters.hasOwnProperty(this.CONSTANTS.ACCESS_TOKEN) ||
- parameters.hasOwnProperty(this.CONSTANTS.ID_TOKEN)
- );
-};
-
-/**
- * Gets login error
- * @returns {string} error message related to login
- */
-AuthenticationContext.prototype.getLoginError = function () {
- return this._getItem(this.CONSTANTS.STORAGE.LOGIN_ERROR);
-};
-
-/**
- * Gets requestInfo from given hash.
- * @returns {string} error message related to login
- */
-AuthenticationContext.prototype.getRequestInfo = function (hash) {
- hash = this._getHash(hash);
- var parameters = this._deserialize(hash);
- var requestInfo = {
- valid: false,
- parameters: {},
- stateMatch: false,
- stateResponse: '',
- requestType: this.REQUEST_TYPE.UNKNOWN
+ return hash;
};
- if (parameters) {
- requestInfo.parameters = parameters;
- if (parameters.hasOwnProperty(this.CONSTANTS.ERROR_DESCRIPTION) ||
+
+ /**
+ * Checks if hash contains access token or id token or error_description
+ * @param {string} hash - Hash passed from redirect page
+ * @returns {Boolean}
+ */
+ AuthenticationContext.prototype.isCallback = function (hash) {
+ hash = this._getHash(hash);
+ var parameters = this._deserialize(hash);
+ return (
+ parameters.hasOwnProperty(this.CONSTANTS.ERROR_DESCRIPTION) ||
parameters.hasOwnProperty(this.CONSTANTS.ACCESS_TOKEN) ||
- parameters.hasOwnProperty(this.CONSTANTS.ID_TOKEN)) {
+ parameters.hasOwnProperty(this.CONSTANTS.ID_TOKEN)
+ );
+ };
- requestInfo.valid = true;
+ /**
+ * Gets login error
+ * @returns {string} error message related to login
+ */
+ AuthenticationContext.prototype.getLoginError = function () {
+ return this._getItem(this.CONSTANTS.STORAGE.LOGIN_ERROR);
+ };
- // which call
- var stateResponse = '';
- if (parameters.hasOwnProperty('state')) {
- this.verbose('State: ' + parameters.state);
- stateResponse = parameters.state;
- } else {
- this.warn('No state returned');
- return requestInfo;
- }
+ /**
+ * Gets requestInfo from given hash.
+ * @returns {string} error message related to login
+ */
+ AuthenticationContext.prototype.getRequestInfo = function (hash) {
+ hash = this._getHash(hash);
+ var parameters = this._deserialize(hash);
+ var requestInfo = {
+ valid: false,
+ parameters: {},
+ stateMatch: false,
+ stateResponse: '',
+ requestType: this.REQUEST_TYPE.UNKNOWN
+ };
+ if (parameters) {
+ requestInfo.parameters = parameters;
+ if (parameters.hasOwnProperty(this.CONSTANTS.ERROR_DESCRIPTION) ||
+ parameters.hasOwnProperty(this.CONSTANTS.ACCESS_TOKEN) ||
+ parameters.hasOwnProperty(this.CONSTANTS.ID_TOKEN)) {
+
+ requestInfo.valid = true;
+
+ // which call
+ var stateResponse = '';
+ if (parameters.hasOwnProperty('state')) {
+ this.verbose('State: ' + parameters.state);
+ stateResponse = parameters.state;
+ } else {
+ this.warn('No state returned');
+ return requestInfo;
+ }
- requestInfo.stateResponse = stateResponse;
+ requestInfo.stateResponse = stateResponse;
- // async calls can fire iframe and login request at the same time if developer does not use the API as expected
- // incoming callback needs to be looked up to find the request type
- if (stateResponse === this._getItem(this.CONSTANTS.STORAGE.STATE_LOGIN)) {
- requestInfo.requestType = this.REQUEST_TYPE.LOGIN;
- requestInfo.stateMatch = true;
- return requestInfo;
- }
+ // async calls can fire iframe and login request at the same time if developer does not use the API as expected
+ // incoming callback needs to be looked up to find the request type
+ if (stateResponse === this._getItem(this.CONSTANTS.STORAGE.STATE_LOGIN)) {
+ requestInfo.requestType = this.REQUEST_TYPE.LOGIN;
+ requestInfo.stateMatch = true;
+ return requestInfo;
+ }
- // external api requests may have many renewtoken requests for different resource
- if (!requestInfo.stateMatch && window.parent && window.parent.AuthenticationContext()) {
- var statesInParentContext = window.parent.AuthenticationContext()._renewStates;
- for (var i = 0; i < statesInParentContext.length; i++) {
- if (statesInParentContext[i] === requestInfo.stateResponse) {
- requestInfo.requestType = this.REQUEST_TYPE.RENEW_TOKEN;
- requestInfo.stateMatch = true;
- break;
+ // external api requests may have many renewtoken requests for different resource
+ if (!requestInfo.stateMatch && window.parent && window.parent.AuthenticationContext()) {
+ var statesInParentContext = window.parent.AuthenticationContext()._renewStates;
+ for (var i = 0; i < statesInParentContext.length; i++) {
+ if (statesInParentContext[i] === requestInfo.stateResponse) {
+ requestInfo.requestType = this.REQUEST_TYPE.RENEW_TOKEN;
+ requestInfo.stateMatch = true;
+ break;
+ }
}
}
}
}
- }
- return requestInfo;
-};
+ return requestInfo;
+ };
-AuthenticationContext.prototype._getResourceFromState = function (state) {
- if (state) {
- var splitIndex = state.indexOf('|');
- if (splitIndex > -1 && splitIndex + 1 < state.length) {
- return state.substring(splitIndex + 1);
+ AuthenticationContext.prototype._getResourceFromState = function (state) {
+ if (state) {
+ var splitIndex = state.indexOf('|');
+ if (splitIndex > -1 && splitIndex + 1 < state.length) {
+ return state.substring(splitIndex + 1);
+ }
}
- }
- return '';
-};
-
-/**
- * Saves token from hash that is received from redirect.
- * @param {string} hash - Hash passed from redirect page
- * @returns {string} error message related to login
- */
-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.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
- this.info('State is right');
- if (requestInfo.parameters.hasOwnProperty(this.CONSTANTS.SESSION_STATE)) {
- this._saveItem(this.CONSTANTS.STORAGE.SESSION_STATE, requestInfo.parameters[this.CONSTANTS.SESSION_STATE]);
+ return '';
+ };
+
+ /**
+ * Saves token from hash that is received from redirect.
+ * @param {string} hash - Hash passed from redirect page
+ * @returns {string} error message related to login
+ */
+ AuthenticationContext.prototype.saveTokenFromHash = function (requestInfo) {
+ this.info('State status:' + requestInfo.stateMatch + '; Request type:' + requestInfo.requestType);
+ this._saveItem(this.CONSTANTS.STORAGE.ERROR, '');
+ this._saveItem(this.CONSTANTS.STORAGE.ERROR_DESCRIPTION, '');
+
+ var resource = this._getResourceFromState(requestInfo.stateResponse);
+
+ // Record error
+ if (requestInfo.parameters.hasOwnProperty(this.CONSTANTS.ERROR_DESCRIPTION)) {
+ this.info('Error :' + requestInfo.parameters.error + '; Error description:' + requestInfo.parameters[this.CONSTANTS.ERROR_DESCRIPTION]);
+ this._saveItem(this.CONSTANTS.STORAGE.ERROR, requestInfo.parameters.error);
+ this._saveItem(this.CONSTANTS.STORAGE.ERROR_DESCRIPTION, requestInfo.parameters[this.CONSTANTS.ERROR_DESCRIPTION]);
+
+ if (requestInfo.requestType === this.REQUEST_TYPE.LOGIN) {
+ this._loginInProgress = false;
+ this._saveItem(this.CONSTANTS.STORAGE.LOGIN_ERROR, requestInfo.parameters.error_description);
}
+ } else {
+ // It must verify the state from redirect
+ if (requestInfo.stateMatch) {
+ // record tokens to storage if exists
+ this.info('State is right');
+ if (requestInfo.parameters.hasOwnProperty(this.CONSTANTS.SESSION_STATE)) {
+ this._saveItem(this.CONSTANTS.STORAGE.SESSION_STATE, requestInfo.parameters[this.CONSTANTS.SESSION_STATE]);
+ }
- var keys, resource;
+ var keys;
- if (requestInfo.parameters.hasOwnProperty(this.CONSTANTS.ACCESS_TOKEN)) {
- this.info('Fragment has access token');
- resource = this._getResourceFromState(requestInfo.stateResponse);
+ if (requestInfo.parameters.hasOwnProperty(this.CONSTANTS.ACCESS_TOKEN)) {
+ this.info('Fragment has access token');
- if (!this._hasResource(resource)) {
- keys = this._getItem(this.CONSTANTS.STORAGE.TOKEN_KEYS) || '';
- this._saveItem(this.CONSTANTS.STORAGE.TOKEN_KEYS, keys + resource + this.CONSTANTS.RESOURCE_DELIMETER);
+ if (!this._hasResource(resource)) {
+ keys = this._getItem(this.CONSTANTS.STORAGE.TOKEN_KEYS) || '';
+ this._saveItem(this.CONSTANTS.STORAGE.TOKEN_KEYS, keys + resource + this.CONSTANTS.RESOURCE_DELIMETER);
+ }
+ // save token with related resource
+ this._saveItem(this.CONSTANTS.STORAGE.ACCESS_TOKEN_KEY + resource, requestInfo.parameters[this.CONSTANTS.ACCESS_TOKEN]);
+ this._saveItem(this.CONSTANTS.STORAGE.EXPIRATION_KEY + resource, this._expiresIn(requestInfo.parameters[this.CONSTANTS.EXPIRES_IN]));
}
- // save token with related resource
- this._saveItem(this.CONSTANTS.STORAGE.ACCESS_TOKEN_KEY + resource, requestInfo.parameters[this.CONSTANTS.ACCESS_TOKEN]);
- this._saveItem(this.CONSTANTS.STORAGE.EXPIRATION_KEY + resource, this._expiresIn(requestInfo.parameters[this.CONSTANTS.EXPIRES_IN]));
- }
- if (requestInfo.parameters.hasOwnProperty(this.CONSTANTS.ID_TOKEN)) {
- this.info('Fragment has id token');
- this._loginInProgress = false;
- this._user = this._createUser(requestInfo.parameters[this.CONSTANTS.ID_TOKEN]);
- if (this._user && this._user.profile) {
- if (this._user.profile.nonce !== this._getItem(this.CONSTANTS.STORAGE.NONCE_IDTOKEN)) {
- this._user = null;
- this._saveItem(this.CONSTANTS.STORAGE.LOGIN_ERROR, 'Nonce is not same as ' + this._idTokenNonce);
- } else {
- this._saveItem(this.CONSTANTS.STORAGE.IDTOKEN, requestInfo.parameters[this.CONSTANTS.ID_TOKEN]);
-
- // Save idtoken as access token for app itself
- resource = this.config.loginResource ? this.config.loginResource : this.config.clientId;
- if (!this._hasResource(resource)) {
- keys = this._getItem(this.CONSTANTS.STORAGE.TOKEN_KEYS) || '';
- this._saveItem(this.CONSTANTS.STORAGE.TOKEN_KEYS, keys + resource + this.CONSTANTS.RESOURCE_DELIMETER);
+ if (requestInfo.parameters.hasOwnProperty(this.CONSTANTS.ID_TOKEN)) {
+ this.info('Fragment has id token');
+ this._loginInProgress = false;
+
+ this._user = this._createUser(requestInfo.parameters[this.CONSTANTS.ID_TOKEN]);
+
+ if (this._user && this._user.profile) {
+ if (this._user.profile.nonce !== this._getItem(this.CONSTANTS.STORAGE.NONCE_IDTOKEN)) {
+ this._user = null;
+ this._saveItem(this.CONSTANTS.STORAGE.LOGIN_ERROR, 'Nonce is not same as ' + this._idTokenNonce);
+ } else {
+ this._saveItem(this.CONSTANTS.STORAGE.IDTOKEN, requestInfo.parameters[this.CONSTANTS.ID_TOKEN]);
+
+ // Save idtoken as access token for app itself
+ resource = this.config.loginResource ? this.config.loginResource : this.config.clientId;
+
+ if (!this._hasResource(resource)) {
+ keys = this._getItem(this.CONSTANTS.STORAGE.TOKEN_KEYS) || '';
+ this._saveItem(this.CONSTANTS.STORAGE.TOKEN_KEYS, keys + resource + this.CONSTANTS.RESOURCE_DELIMETER);
+ }
+ this._saveItem(this.CONSTANTS.STORAGE.ACCESS_TOKEN_KEY + resource, requestInfo.parameters[this.CONSTANTS.ID_TOKEN]);
+ this._saveItem(this.CONSTANTS.STORAGE.EXPIRATION_KEY + resource, this._user.profile.exp);
}
- this._saveItem(this.CONSTANTS.STORAGE.ACCESS_TOKEN_KEY + resource, requestInfo.parameters[this.CONSTANTS.ID_TOKEN]);
- this._saveItem(this.CONSTANTS.STORAGE.EXPIRATION_KEY + resource, this._user.profile.exp);
+ }
+ else {
+ this._saveItem(this.CONSTANTS.STORAGE.ERROR, 'invalid id_token');
+ this._saveItem(this.CONSTANTS.STORAGE.ERROR_DESCRIPTION, 'Invalid id_token. id_token: ' + requestInfo.parameters[this.CONSTANTS.ID_TOKEN]);
}
}
+ } else {
+ this._saveItem(this.CONSTANTS.STORAGE.ERROR, 'Invalid_state');
+ this._saveItem(this.CONSTANTS.STORAGE.ERROR_DESCRIPTION, 'Invalid_state. state: ' + requestInfo.stateResponse);
}
- } else {
- this._saveItem(this.CONSTANTS.STORAGE.ERROR, 'Invalid_state');
- this._saveItem(this.CONSTANTS.STORAGE.ERROR_DESCRIPTION, 'Invalid_state. state: ' + requestInfo.stateResponse);
}
- }
-};
-
-/**
- * Gets resource for given endpoint if mapping is provided with config.
- * @param {string} endpoint - API endoibt
- * @returns {string} resource for this API endpoint
- */
-AuthenticationContext.prototype.getResourceForEndpoint = function (endpoint) {
- if (this.config && this.config.endpoints) {
- for (var configEndpoint in this.config.endpoints) {
- // configEndpoint is like /api/Todo requested endpoint can be /api/Todo/1
- if (endpoint.indexOf(configEndpoint) > -1) {
- return this.config.endpoints[configEndpoint];
+ this._saveItem(this.CONSTANTS.STORAGE.RENEW_STATUS + resource, this.CONSTANTS.TOKEN_RENEW_STATUS_COMPLETED);
+ };
+
+ /**
+ * Gets resource for given endpoint if mapping is provided with config.
+ * @param {string} endpoint - API endoibt
+ * @returns {string} resource for this API endpoint
+ */
+ AuthenticationContext.prototype.getResourceForEndpoint = function (endpoint) {
+ if (this.config && this.config.endpoints) {
+ for (var configEndpoint in this.config.endpoints) {
+ // configEndpoint is like /api/Todo requested endpoint can be /api/Todo/1
+ if (endpoint.indexOf(configEndpoint) > -1) {
+ return this.config.endpoints[configEndpoint];
+ }
}
}
- }
- // default resource will be clientid if nothing specified
- // App will use idtoken for calls to itself
- // check if it's staring from http or https, needs to match with app host
- if (endpoint.indexOf('http://') > -1 || endpoint.indexOf('https://') > -1) {
- if (this._getHostFromUri(endpoint) === this._getHostFromUri(this.config.redirectUri)) {
- return this.config.loginResource;
+ // default resource will be clientid if nothing specified
+ // App will use idtoken for calls to itself
+ // check if it's staring from http or https, needs to match with app host
+ if (endpoint.indexOf('http://') > -1 || endpoint.indexOf('https://') > -1) {
+ if (this._getHostFromUri(endpoint) === this._getHostFromUri(this.config.redirectUri)) {
+ return this.config.loginResource;
+ }
}
- }
- // in angular level, the url for $http interceptor call could be relative url,
- // if it's relative call, we'll treat it as app backend call.
- else {
- // if user specified list of anonymous endpoints, no need to send token to these endpoints, return null.
- if (this.config && this.config.anonymousEndpoints) {
- for (var i = 0; i < this.config.anonymousEndpoints.length; i++) {
- if (endpoint.indexOf(this.config.anonymousEndpoints[i]) > -1) {
- return null;
+ // in angular level, the url for $http interceptor call could be relative url,
+ // if it's relative call, we'll treat it as app backend call.
+ else {
+ // if user specified list of anonymous endpoints, no need to send token to these endpoints, return null.
+ if (this.config && this.config.anonymousEndpoints) {
+ for (var i = 0; i < this.config.anonymousEndpoints.length; i++) {
+ if (endpoint.indexOf(this.config.anonymousEndpoints[i]) > -1) {
+ return null;
+ }
}
}
+ // all other app's backend calls are secured.
+ return this.config.loginResource;
}
- // all other app's backend calls are secured.
- return this.config.loginResource;
- }
- // if not the app's own backend or not a domain listed in the endpoints structure
- return null;
-};
-
-AuthenticationContext.prototype._getHostFromUri = function (uri) {
- // remove http:// or https:// from uri
- var extractedUri = String(uri).replace(/^(https?:)\/\//, '');
-
- extractedUri = extractedUri.split('/')[0];
- return extractedUri;
-};
-
-/*exported oauth2Callback */
-AuthenticationContext.prototype.handleWindowCallback = function () {
- // This is for regular javascript usage for redirect handling
- // need to make sure this is for callback
- var hash = window.location.hash;
- if (this.isCallback(hash)) {
- var requestInfo = this.getRequestInfo(hash);
- this.info('Returned from redirect url');
- this.saveTokenFromHash(requestInfo);
- var callback = null;
- 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];
- window.src = '';
- } else if (window && window.oauth2Callback) {
- this.verbose('Window is redirecting');
- callback = this.callback;
- }
+ // if not the app's own backend or not a domain listed in the endpoints structure
+ return null;
+ };
- 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;
- }
- }
-};
+ AuthenticationContext.prototype._getHostFromUri = function (uri) {
+ // remove http:// or https:// from uri
+ var extractedUri = String(uri).replace(/^(https?:)\/\//, '');
-AuthenticationContext.prototype._getNavigateUrl = function (responseType, resource) {
- var tenant = 'common';
- if (this.config.tenant) {
- tenant = this.config.tenant;
- }
+ extractedUri = extractedUri.split('/')[0];
+ return extractedUri;
+ };
- if (this.config.instance) {
- this.instance = this.config.instance;
- }
+ /*exported oauth2Callback */
+ AuthenticationContext.prototype.handleWindowCallback = function () {
+ // This is for regular javascript usage for redirect handling
+ // need to make sure this is for callback
+ var hash = window.location.hash;
+ if (this.isCallback(hash)) {
+ var requestInfo = this.getRequestInfo(hash);
+ this.info('Returned from redirect url');
+ this.saveTokenFromHash(requestInfo);
+ var callback = null;
+ if ((requestInfo.requestType === this.REQUEST_TYPE.RENEW_TOKEN) && window.parent && (window.parent !== window)) {
+ // iframe call but same single page
+ this.verbose('Window is in iframe');
+ callback = window.parent.callBackMappedToRenewStates[requestInfo.stateResponse];
+ if (callback)
+ callback(this._getItem(this.CONSTANTS.STORAGE.ERROR_DESCRIPTION), requestInfo.parameters[this.CONSTANTS.ACCESS_TOKEN] || requestInfo.parameters[this.CONSTANTS.ID_TOKEN]);
+ return;
+ } else if (window && window.oauth2Callback) {
+ this.verbose('Window is redirecting');
+ callback = this.callback;
+ }
+ window.location = this._getItem(this.CONSTANTS.STORAGE.LOGIN_REQUEST);
+ }
+ };
- var urlNavigate = this.instance + tenant + '/oauth2/authorize' + this._serialize(responseType, this.config, resource) + this._addLibMetadata();
- this.info('Navigate url:' + urlNavigate);
- return urlNavigate;
-};
+ AuthenticationContext.prototype._getNavigateUrl = function (responseType, resource) {
+ var tenant = 'common';
+ if (this.config.tenant) {
+ tenant = this.config.tenant;
+ }
-AuthenticationContext.prototype._extractIdToken = function (encodedIdToken) {
- // id token will be decoded to get the username
- var decodedToken = this._decodeJwt(encodedIdToken);
- if (!decodedToken) {
- return null;
- }
+ var urlNavigate = this.instance + tenant + '/oauth2/authorize' + this._serialize(responseType, this.config, resource) + this._addLibMetadata();
+ this.info('Navigate url:' + urlNavigate);
+ return urlNavigate;
+ };
- try {
- var base64IdToken = decodedToken.JWSPayload;
- var base64Decoded = this._base64DecodeStringUrlSafe(base64IdToken);
- if (!base64Decoded) {
- this.info('The returned id_token could not be base64 url safe decoded.');
+ AuthenticationContext.prototype._extractIdToken = function (encodedIdToken) {
+ // id token will be decoded to get the username
+ var decodedToken = this._decodeJwt(encodedIdToken);
+ if (!decodedToken) {
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;
-};
-
-AuthenticationContext.prototype._extractUserName = function (encodedIdToken) {
- // id token will be decoded to get the username
- try {
- var parsed = this._extractIdToken(encodedIdToken);
- if (parsed) {
- if (parsed.hasOwnProperty('upn')) {
- return parsed.upn;
- } else if (parsed.hasOwnProperty('email')) {
- return parsed.email;
+ try {
+ var base64IdToken = decodedToken.JWSPayload;
+ var base64Decoded = this._base64DecodeStringUrlSafe(base64IdToken);
+ if (!base64Decoded) {
+ this.info('The returned id_token could not be base64 url safe decoded.');
+ return null;
}
+
+ // ECMA script has JSON built-in support
+ return JSON.parse(base64Decoded);
+ } catch (err) {
+ this.error('The returned id_token could not be decoded', err);
}
- } catch (err) {
- this.error('The returned id_token could not be decoded', err);
- }
- return null;
-};
+ return null;
+ };
-AuthenticationContext.prototype._base64DecodeStringUrlSafe = function (base64IdToken) {
- // html5 should support atob function for decoding
- base64IdToken = base64IdToken.replace(/-/g, '+').replace(/_/g, '/');
- if (window.atob) {
- return decodeURIComponent(escape(window.atob(base64IdToken))); // jshint ignore:line
- }
- else {
- return decodeURIComponent(escape(this._decode(base64IdToken)));
- }
-};
+ AuthenticationContext.prototype._base64DecodeStringUrlSafe = function (base64IdToken) {
+ // html5 should support atob function for decoding
+ base64IdToken = base64IdToken.replace(/-/g, '+').replace(/_/g, '/');
+ if (window.atob) {
+ return decodeURIComponent(escape(window.atob(base64IdToken))); // jshint ignore:line
+ }
+ else {
+ return decodeURIComponent(escape(this._decode(base64IdToken)));
+ }
+ };
-//Take https://cdnjs.cloudflare.com/ajax/libs/Base64/0.3.0/base64.js and https://en.wikipedia.org/wiki/Base64 as reference.
-AuthenticationContext.prototype._decode = function (base64IdToken) {
- var codes = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
- base64IdToken = String(base64IdToken).replace(/=+$/, '');
+ //Take https://cdnjs.cloudflare.com/ajax/libs/Base64/0.3.0/base64.js and https://en.wikipedia.org/wiki/Base64 as reference.
+ AuthenticationContext.prototype._decode = function (base64IdToken) {
+ var codes = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
+ base64IdToken = String(base64IdToken).replace(/=+$/, '');
- var length = base64IdToken.length;
- if (length % 4 === 1) {
- throw new Error('The token to be decoded is not correctly encoded.');
- }
+ var length = base64IdToken.length;
+ if (length % 4 === 1) {
+ throw new Error('The token to be decoded is not correctly encoded.');
+ }
+
+ var h1, h2, h3, h4, bits, c1, c2, c3, decoded = '';
+ for (var i = 0; i < length; i += 4) {
+ //Every 4 base64 encoded character will be converted to 3 byte string, which is 24 bits
+ // then 6 bits per base64 encoded character
+ h1 = codes.indexOf(base64IdToken.charAt(i));
+ h2 = codes.indexOf(base64IdToken.charAt(i + 1));
+ h3 = codes.indexOf(base64IdToken.charAt(i + 2));
+ h4 = codes.indexOf(base64IdToken.charAt(i + 3));
+
+ // For padding, if last two are '='
+ if (i + 2 === length - 1) {
+ bits = h1 << 18 | h2 << 12 | h3 << 6;
+ c1 = bits >> 16 & 255;
+ c2 = bits >> 8 & 255;
+ decoded += String.fromCharCode(c1, c2);
+ break;
+ }
+ // if last one is '='
+ else if (i + 1 === length - 1) {
+ bits = h1 << 18 | h2 << 12
+ c1 = bits >> 16 & 255;
+ decoded += String.fromCharCode(c1);
+ break;
+ }
+
+ bits = h1 << 18 | h2 << 12 | h3 << 6 | h4;
- var h1, h2, h3, h4, bits, c1, c2, c3, decoded = '';
- for (var i = 0; i < length; i += 4) {
- //Every 4 base64 encoded character will be converted to 3 byte string, which is 24 bits
- // then 6 bits per base64 encoded character
- h1 = codes.indexOf(base64IdToken.charAt(i));
- h2 = codes.indexOf(base64IdToken.charAt(i + 1));
- h3 = codes.indexOf(base64IdToken.charAt(i + 2));
- h4 = codes.indexOf(base64IdToken.charAt(i + 3));
-
- // For padding, if last two are '='
- if (i + 2 === length - 1) {
- bits = h1 << 18 | h2 << 12 | h3 << 6;
+ // then convert to 3 byte chars
c1 = bits >> 16 & 255;
c2 = bits >> 8 & 255;
- decoded += String.fromCharCode(c1, c2);
- break;
- }
- // if last one is '='
- else if (i + 1 === length - 1) {
- bits = h1 << 18 | h2 << 12
- c1 = bits >> 16 & 255;
- decoded += String.fromCharCode(c1);
- break;
- }
+ c3 = bits & 255;
- bits = h1 << 18 | h2 << 12 | h3 << 6 | h4;
+ decoded += String.fromCharCode(c1, c2, c3);
+ }
- // then convert to 3 byte chars
- c1 = bits >> 16 & 255;
- c2 = bits >> 8 & 255;
- c3 = bits & 255;
+ return decoded;
+ };
- decoded += String.fromCharCode(c1, c2, c3);
- }
+ // Adal.node js crack function
+ AuthenticationContext.prototype._decodeJwt = function (jwtToken) {
+ if (this._isEmpty(jwtToken)) {
+ return null;
+ };
- return decoded;
-};
+ var idTokenPartsRegex = /^([^\.\s]*)\.([^\.\s]+)\.([^\.\s]*)$/;
-// Adal.node js crack function
-AuthenticationContext.prototype._decodeJwt = function (jwtToken) {
- if (this._isEmpty(jwtToken)) {
- return null;
- };
+ var matches = idTokenPartsRegex.exec(jwtToken);
+ if (!matches || matches.length < 4) {
+ this.warn('The returned id_token is not parseable.');
+ return null;
+ }
- var idTokenPartsRegex = /^([^\.\s]*)\.([^\.\s]+)\.([^\.\s]*)$/;
+ var crackedToken = {
+ header: matches[1],
+ JWSPayload: matches[2],
+ JWSSig: matches[3]
+ };
- var matches = idTokenPartsRegex.exec(jwtToken);
- if (!matches || matches.length < 4) {
- this.warn('The returned id_token is not parseable.');
- return null;
- }
+ return crackedToken;
+ };
- var crackedToken = {
- header: matches[1],
- JWSPayload: matches[2],
- JWSSig: matches[3]
+ AuthenticationContext.prototype._convertUrlSafeToRegularBase64EncodedString = function (str) {
+ return str.replace('-', '+').replace('_', '/');
};
- return crackedToken;
-};
+ AuthenticationContext.prototype._serialize = function (responseType, obj, resource) {
+ var str = [];
+ if (obj !== null) {
+ str.push('?response_type=' + responseType);
+ str.push('client_id=' + encodeURIComponent(obj.clientId));
+ if (resource) {
+ str.push('resource=' + encodeURIComponent(resource));
+ }
-AuthenticationContext.prototype._convertUrlSafeToRegularBase64EncodedString = function (str) {
- return str.replace('-', '+').replace('_', '/');
-};
+ str.push('redirect_uri=' + encodeURIComponent(obj.redirectUri));
+ str.push('state=' + encodeURIComponent(obj.state));
-AuthenticationContext.prototype._serialize = function (responseType, obj, resource) {
- var str = [];
- if (obj !== null) {
- str.push('?response_type=' + responseType);
- str.push('client_id=' + encodeURIComponent(obj.clientId));
- if (resource) {
- str.push('resource=' + encodeURIComponent(resource));
- }
+ if (obj.hasOwnProperty('slice')) {
+ str.push('slice=' + encodeURIComponent(obj.slice));
+ }
- str.push('redirect_uri=' + encodeURIComponent(obj.redirectUri));
- str.push('state=' + encodeURIComponent(obj.state));
+ if (obj.hasOwnProperty('extraQueryParameter')) {
+ str.push(obj.extraQueryParameter);
+ }
- if (obj.hasOwnProperty('slice')) {
- str.push('slice=' + encodeURIComponent(obj.slice));
+ var correlationId = obj.correlationId ? obj.correlationId : this._guid();
+ str.push('client-request-id=' + encodeURIComponent(correlationId));
}
- if (obj.hasOwnProperty('extraQueryParameter')) {
- str.push(obj.extraQueryParameter);
- }
+ return str.join('&');
+ };
- if (obj.correlationId) {
- str.push('client-request-id=' + encodeURIComponent(obj.correlationId));
+ AuthenticationContext.prototype._deserialize = function (query) {
+ var match,
+ pl = /\+/g, // Regex for replacing addition symbol with a space
+ search = /([^&=]+)=([^&]*)/g,
+ decode = function (s) {
+ return decodeURIComponent(s.replace(pl, ' '));
+ },
+ obj = {};
+ match = search.exec(query);
+ while (match) {
+ obj[decode(match[1])] = decode(match[2]);
+ match = search.exec(query);
}
- }
- return str.join('&');
-};
-
-AuthenticationContext.prototype._deserialize = function (query) {
- var match,
- pl = /\+/g, // Regex for replacing addition symbol with a space
- search = /([^&=]+)=?([^&]*)/g,
- decode = function (s) {
- return decodeURIComponent(s.replace(pl, ' '));
- },
- obj = {};
- match = search.exec(query);
- while (match) {
- obj[decode(match[1])] = decode(match[2]);
- match = search.exec(query);
- }
+ return obj;
+ };
- return obj;
-};
-
-/* jshint ignore:start */
-AuthenticationContext.prototype._guid = function () {
- // RFC4122: The version 4 UUID is meant for generating UUIDs from truly-random or
- // pseudo-random numbers.
- // The algorithm is as follows:
- // Set the two most significant bits (bits 6 and 7) of the
- // clock_seq_hi_and_reserved to zero and one, respectively.
- // Set the four most significant bits (bits 12 through 15) of the
- // time_hi_and_version field to the 4-bit version number from
- // Section 4.1.3. Version4
- // Set all the other bits to randomly (or pseudo-randomly) chosen
- // values.
- // UUID = time-low "-" time-mid "-"time-high-and-version "-"clock-seq-reserved and low(2hexOctet)"-" node
- // time-low = 4hexOctet
- // time-mid = 2hexOctet
- // time-high-and-version = 2hexOctet
- // clock-seq-and-reserved = hexOctet:
- // clock-seq-low = hexOctet
- // node = 6hexOctet
- // Format: xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
- // y could be 1000, 1001, 1010, 1011 since most significant two bits needs to be 10
- // y values are 8, 9, A, B
- var guidHolder = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx';
- var hex = '0123456789abcdef';
- var r = 0;
- var guidResponse = "";
- for (var i = 0; i < 36; i++) {
- if (guidHolder[i] !== '-' && guidHolder[i] !== '4') {
- // each x and y needs to be random
- r = Math.random() * 16 | 0;
- }
+ /* jshint ignore:start */
+ AuthenticationContext.prototype._guid = function () {
+ // RFC4122: The version 4 UUID is meant for generating UUIDs from truly-random or
+ // pseudo-random numbers.
+ // The algorithm is as follows:
+ // Set the two most significant bits (bits 6 and 7) of the
+ // clock_seq_hi_and_reserved to zero and one, respectively.
+ // Set the four most significant bits (bits 12 through 15) of the
+ // time_hi_and_version field to the 4-bit version number from
+ // Section 4.1.3. Version4
+ // Set all the other bits to randomly (or pseudo-randomly) chosen
+ // values.
+ // UUID = time-low "-" time-mid "-"time-high-and-version "-"clock-seq-reserved and low(2hexOctet)"-" node
+ // time-low = 4hexOctet
+ // time-mid = 2hexOctet
+ // time-high-and-version = 2hexOctet
+ // clock-seq-and-reserved = hexOctet:
+ // clock-seq-low = hexOctet
+ // node = 6hexOctet
+ // Format: xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
+ // y could be 1000, 1001, 1010, 1011 since most significant two bits needs to be 10
+ // y values are 8, 9, A, B
+ var guidHolder = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx';
+ var hex = '0123456789abcdef';
+ var r = 0;
+ var guidResponse = "";
+ for (var i = 0; i < 36; i++) {
+ if (guidHolder[i] !== '-' && guidHolder[i] !== '4') {
+ // each x and y needs to be random
+ r = Math.random() * 16 | 0;
+ }
- if (guidHolder[i] === 'x') {
- guidResponse += hex[r];
- } else if (guidHolder[i] === 'y') {
- // clock-seq-and-reserved first hex is filtered and remaining hex values are random
- r &= 0x3; // bit and with 0011 to set pos 2 to zero ?0??
- r |= 0x8; // set pos 3 to 1 as 1???
- guidResponse += hex[r];
- } else {
- guidResponse += guidHolder[i];
+ if (guidHolder[i] === 'x') {
+ guidResponse += hex[r];
+ } else if (guidHolder[i] === 'y') {
+ // clock-seq-and-reserved first hex is filtered and remaining hex values are random
+ r &= 0x3; // bit and with 0011 to set pos 2 to zero ?0??
+ r |= 0x8; // set pos 3 to 1 as 1???
+ guidResponse += hex[r];
+ } else {
+ guidResponse += guidHolder[i];
+ }
}
- }
- return guidResponse;
-};
-/* jshint ignore:end */
+ return guidResponse;
+ };
+ /* jshint ignore:end */
-AuthenticationContext.prototype._expiresIn = function (expires) {
- return this._now() + parseInt(expires, 10);
-};
+ AuthenticationContext.prototype._expiresIn = function (expires) {
+ return this._now() + parseInt(expires, 10);
+ };
-AuthenticationContext.prototype._now = function () {
- return Math.round(new Date().getTime() / 1000.0);
-};
+ AuthenticationContext.prototype._now = function () {
+ return Math.round(new Date().getTime() / 1000.0);
+ };
-AuthenticationContext.prototype._addAdalFrame = function (iframeId) {
- if (typeof iframeId === 'undefined') {
- return;
- }
+ AuthenticationContext.prototype._addAdalFrame = function (iframeId) {
+ if (typeof iframeId === 'undefined') {
+ return;
+ }
- this.info('Add adal frame to document:' + iframeId);
- var adalFrame = document.getElementById(iframeId);
+ this.info('Add adal frame to document:' + iframeId);
+ var adalFrame = document.getElementById(iframeId);
- if (!adalFrame) {
- if (document.createElement && document.documentElement &&
- (window.opera || window.navigator.userAgent.indexOf('MSIE 5.0') === -1)) {
- var ifr = document.createElement('iframe');
- ifr.setAttribute('id', iframeId);
- ifr.style.visibility = 'hidden';
- ifr.style.position = 'absolute';
- ifr.style.width = ifr.style.height = ifr.borderWidth = '0px';
+ if (!adalFrame) {
+ if (document.createElement && document.documentElement &&
+ (window.opera || window.navigator.userAgent.indexOf('MSIE 5.0') === -1)) {
+ var ifr = document.createElement('iframe');
+ ifr.setAttribute('id', iframeId);
+ ifr.style.visibility = 'hidden';
+ ifr.style.position = 'absolute';
+ ifr.style.width = ifr.style.height = ifr.borderWidth = '0px';
- adalFrame = document.getElementsByTagName('body')[0].appendChild(ifr);
- }
- else if (document.body && document.body.insertAdjacentHTML) {
- document.body.insertAdjacentHTML('beforeEnd', '');
- }
- if (window.frames && window.frames[iframeId]) {
- adalFrame = window.frames[iframeId];
+ adalFrame = document.getElementsByTagName('body')[0].appendChild(ifr);
+ }
+ else if (document.body && document.body.insertAdjacentHTML) {
+ document.body.insertAdjacentHTML('beforeEnd', '');
+ }
+ if (window.frames && window.frames[iframeId]) {
+ adalFrame = window.frames[iframeId];
+ }
}
- }
- return adalFrame;
-};
+ return adalFrame;
+ };
-AuthenticationContext.prototype._saveItem = function (key, obj) {
+ AuthenticationContext.prototype._saveItem = function (key, obj) {
- if (this.config && this.config.cacheLocation && this.config.cacheLocation === 'localStorage') {
+ if (this.config && this.config.cacheLocation && this.config.cacheLocation === 'localStorage') {
- if (!this._supportsLocalStorage()) {
- this.info('Local storage is not supported');
- return false;
+ if (!this._supportsLocalStorage()) {
+ this.info('Local storage is not supported');
+ return false;
+ }
+
+ localStorage.setItem(key, obj);
+
+ return true;
}
- localStorage.setItem(key, obj);
+ // Default as session storage
+ if (!this._supportsSessionStorage()) {
+ this.info('Session storage is not supported');
+ return false;
+ }
+ sessionStorage.setItem(key, obj);
return true;
- }
+ };
- // Default as session storage
- if (!this._supportsSessionStorage()) {
- this.info('Session storage is not supported');
- return false;
- }
+ AuthenticationContext.prototype._getItem = function (key) {
- sessionStorage.setItem(key, obj);
- return true;
-};
+ if (this.config && this.config.cacheLocation && this.config.cacheLocation === 'localStorage') {
-AuthenticationContext.prototype._getItem = function (key) {
+ if (!this._supportsLocalStorage()) {
+ this.info('Local storage is not supported');
+ return null;
+ }
- if (this.config && this.config.cacheLocation && this.config.cacheLocation === 'localStorage') {
+ return localStorage.getItem(key);
+ }
- if (!this._supportsLocalStorage()) {
- this.info('Local storage is not supported');
+ // Default as session storage
+ if (!this._supportsSessionStorage()) {
+ this.info('Session 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);
-};
+ return sessionStorage.getItem(key);
+ };
-AuthenticationContext.prototype._supportsLocalStorage = function () {
- try {
- return 'localStorage' in window && window['localStorage'];
- } catch (e) {
- return false;
- }
-};
+ AuthenticationContext.prototype._supportsLocalStorage = function () {
+ try {
+ return 'localStorage' in window && window['localStorage'];
+ } catch (e) {
+ return false;
+ }
+ };
-AuthenticationContext.prototype._supportsSessionStorage = function () {
- try {
- return 'sessionStorage' in window && window['sessionStorage'];
- } catch (e) {
- return false;
- }
-};
+ AuthenticationContext.prototype._supportsSessionStorage = function () {
+ try {
+ return 'sessionStorage' in window && window['sessionStorage'];
+ } catch (e) {
+ return false;
+ }
+ };
-AuthenticationContext.prototype._cloneConfig = function (obj) {
- if (null === obj || 'object' !== typeof obj) {
- return obj;
- }
+ AuthenticationContext.prototype._cloneConfig = function (obj) {
+ if (null === obj || 'object' !== typeof obj) {
+ return obj;
+ }
- var copy = {};
- for (var attr in obj) {
- if (obj.hasOwnProperty(attr)) {
- copy[attr] = obj[attr];
+ var copy = {};
+ for (var attr in obj) {
+ if (obj.hasOwnProperty(attr)) {
+ copy[attr] = obj[attr];
+ }
}
- }
- return copy;
-};
+ return copy;
+ };
-AuthenticationContext.prototype._addLibMetadata = function () {
- // x-client-SKU
- // x-client-Ver
- return '&x-client-SKU=Js&x-client-Ver=' + this._libVersion();
-};
+ AuthenticationContext.prototype._addLibMetadata = function () {
+ // x-client-SKU
+ // x-client-Ver
+ return '&x-client-SKU=Js&x-client-Ver=' + this._libVersion();
+ };
-AuthenticationContext.prototype.log = function (level, message, error) {
- if (level <= Logging.level) {
- var correlationId = this.config.correlationId;
- var timestamp = new Date().toUTCString();
+ AuthenticationContext.prototype.log = function (level, message, error) {
+ if (level <= Logging.level) {
+ var timestamp = new Date().toUTCString();
+ var formattedMessage = '';
- var formattedMessage = timestamp + ':' + correlationId + '-' + this._libVersion() + '-' + this.CONSTANTS.LEVEL_STRING_MAP[level] + ' ' + message;
+ if (this.config.correlationId)
+ formattedMessage = timestamp + ':' + this.config.correlationId + '-' + this._libVersion() + '-' + this.CONSTANTS.LEVEL_STRING_MAP[level] + ' ' + message;
+ else
+ formattedMessage = timestamp + ':' + this._libVersion() + '-' + this.CONSTANTS.LEVEL_STRING_MAP[level] + ' ' + message;
- if (error) {
- formattedMessage += '\nstack:\n' + error.stack;
+ if (error) {
+ formattedMessage += '\nstack:\n' + error.stack;
+ }
+
+ Logging.log(formattedMessage);
}
+ };
- Logging.log(formattedMessage);
- }
-};
+ AuthenticationContext.prototype.error = function (message, error) {
+ this.log(this.CONSTANTS.LOGGING_LEVEL.ERROR, message, error);
+ };
+
+ AuthenticationContext.prototype.warn = function (message) {
+ this.log(this.CONSTANTS.LOGGING_LEVEL.WARN, message, null);
+ };
+
+ AuthenticationContext.prototype.info = function (message) {
+ this.log(this.CONSTANTS.LOGGING_LEVEL.INFO, message, null);
+ };
-AuthenticationContext.prototype.error = function (message, error) {
- this.log(this.CONSTANTS.LOGGING_LEVEL.ERROR, message, error);
-};
+ AuthenticationContext.prototype.verbose = function (message) {
+ this.log(this.CONSTANTS.LOGGING_LEVEL.VERBOSE, message, null);
+ };
-AuthenticationContext.prototype.warn = function (message) {
- this.log(this.CONSTANTS.LOGGING_LEVEL.WARN, message, null);
-};
+ AuthenticationContext.prototype._libVersion = function () {
+ return '1.0.11';
+ };
-AuthenticationContext.prototype.info = function (message) {
- this.log(this.CONSTANTS.LOGGING_LEVEL.INFO, message, null);
-};
+ if (typeof module !== 'undefined' && module.exports) {
+ module.exports = AuthenticationContext;
+ module.exports.inject = function (conf) {
+ return new AuthenticationContext(conf);
+ };
+ }
-AuthenticationContext.prototype.verbose = function (message) {
- this.log(this.CONSTANTS.LOGGING_LEVEL.VERBOSE, message, null);
-};
+ return AuthenticationContext;
-AuthenticationContext.prototype._libVersion = function () {
- return '1.0.10';
-};
+}());
diff --git a/package.json b/package.json
index 1ed39013..d8b615e3 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.10",
+ "version": "1.0.11",
"description": "Windows Azure Active Directory Client Library for js",
"keywords": [
"implicit",
diff --git a/tests/angularModuleSpec.js b/tests/angularModuleSpec.js
index b7e3c11b..9204ac17 100644
--- a/tests/angularModuleSpec.js
+++ b/tests/angularModuleSpec.js
@@ -21,26 +21,27 @@
/* global describe */
'use strict';
-
+
describe('TaskCtl', function () {
- var scope, $httpBackend, adalServiceProvider, rootScope, controller, q, window;
+ var scope, $httpBackend, adalServiceProvider, rootScope, controller, q, window, route, location;
//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_, _$q_, _$window_) {
+ //mock the controller for the same reason and include $scope and $controller
+ beforeEach(angular.mock.inject(function (_adalAuthenticationService_, _$rootScope_, _$controller_, _$httpBackend_, _$q_, _$window_, _$route_, _$location_) {
adalServiceProvider = _adalAuthenticationService_;
rootScope = _$rootScope_;
controller = _$controller_;
$httpBackend = _$httpBackend_;
q = _$q_;
window = _$window_;
+ route = _$route_;
+ location = _$location_;
//create an empty scope
scope = rootScope.$new();
- adalServiceProvider.userInfo = { userName: 'UserVerify', isAuthenticated: true };
-
+
adalServiceProvider.getCachedToken = function (resource) {
console.log('Requesting token for resource:' + resource);
if (resource === 'resource1') {
@@ -52,7 +53,7 @@ describe('TaskCtl', function () {
}
if (resource === adalServiceProvider.config.loginResource) {
- return 'Token456';
+ return 'Token456';
}
return '';
@@ -79,8 +80,8 @@ describe('TaskCtl', function () {
}));
it('assigns user', function () {
- expect(scope.user.userName).toBe('UserVerify');
- expect(scope.user.isAuthenticated).toBe(true);
+ expect(scope.user.userName).toBe('');
+ expect(scope.user.isAuthenticated).toBe(false);
});
it('send tokens for webapi call in endpoints list', function () {
@@ -115,11 +116,11 @@ describe('TaskCtl', function () {
});
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;
- }).respond(200);
- scope.taskCall2();
- $httpBackend.flush();
+ $httpBackend.expectGET('https://test.com/', function (headers) {
+ return headers.hasOwnProperty('Authorization') === false;
+ }).respond(200);
+ scope.taskCall2();
+ $httpBackend.flush();
});
it('does not send tokens for webapi(http) call not in endpoint list', function () {
@@ -127,10 +128,10 @@ describe('TaskCtl', function () {
return headers.hasOwnProperty('Authorization') === false;
}).respond(200);
scope.taskCall6();
- $httpBackend.flush();
+ $httpBackend.flush();
});
- it ('send tokens for app backend call not in endpoints list', function () {
+ it('send tokens for app backend call not in endpoints list', function () {
$httpBackend.expectGET('/someapi/item', function (headers) {
return headers.Authorization === 'Bearer Token456'
}).respond(200);
@@ -199,29 +200,29 @@ describe('TaskCtl', function () {
return headers.Authorization === 'Bearer Token456'
}).respond(200);
- rootScope.$on('adal:errorResponse', function (event, message) {
+ scope.$on('adal:errorResponse', function (event, message) {
expect(event.name).toBe('adal:errorResponse');
- expect(message).toBe('login in progress, cancelling the request');
+ expect(message.data).toBe('login in progress, cancelling the request for https://myapp.com/someapi/item');
});
scope.taskCall5();
- rootScope.$apply();
- expect(rootScope.$broadcast).toHaveBeenCalledWith('adal:errorResponse', 'login in progress, cancelling the request');
+ scope.$apply();
+ expect(rootScope.$broadcast).toHaveBeenCalled();
});
it('tests stateMismatch broadcast when state does not match', function () {
window.parent.AuthenticationContext = function () {
return {
callback: function () { },
- _renewStates: { }
+ _renewStates: {}
};
};
window.location.hash = 'id_token=sample&state=4343';
spyOn(rootScope, '$broadcast').andCallThrough();
- rootScope.$on('adal:stateMismatch', function (event, message) {
+ scope.$on('adal:stateMismatch', function (event, message) {
expect(event.name).toBe('adal:stateMismatch');
expect(message).toBe('Invalid_state. state: 4343');
});
- rootScope.$apply();
+ scope.$apply();
expect(rootScope.$broadcast).toHaveBeenCalled();
});
@@ -229,7 +230,7 @@ describe('TaskCtl', function () {
window.parent.AuthenticationContext = function () {
return {
callback: function () { },
- _renewStates: [ '4343' ]
+ _renewStates: ['4343']
};
};
window.parent.callBackMappedToRenewStates = {};
@@ -237,6 +238,162 @@ describe('TaskCtl', function () {
expect(error).toBe('renewfailed');
};
window.location.hash = 'error=sample&error_description=renewfailed&state=4343';
- rootScope.$apply();
+ scope.$apply();
+ });
+
+ it('tests callback is called when response contains access token', function () {
+ window.parent.AuthenticationContext = function () {
+ return {
+ callback: function () { },
+ _renewStates: ['4343']
+ };
+ };
+ window.parent.callBackMappedToRenewStates = {};
+ window.parent.callBackMappedToRenewStates['4343'] = function (error, token) {
+ expect(error).toBe('');
+ expect(token).toBe('newAccessToken123');
+ };
+ window.location.hash = 'access_token=newAccessToken123&state=4343';
+ scope.$apply();
+ });
+
+
+ it('tests callback is called when response contains id token', function () {
+ window.parent.AuthenticationContext = function () {
+ return {
+ callback: function () { },
+ _renewStates: ['4343']
+ };
+ };
+ window.parent.callBackMappedToRenewStates = {};
+ window.parent.callBackMappedToRenewStates['4343'] = function (error, token) {
+ expect(error).toBe('Invalid id_token. id_token: newIdToken123');
+ expect(token).toBe('newIdToken123');
+ };
+ window.location.hash = 'id_token=newIdToken123&state=4343';
+ scope.$apply();
+ });
+
+
+ it('tests login failure after users logs in', function () {
+ window.parent.AuthenticationContext = function () {
+ return {
+ callback: 'callback',
+ _renewStates: ['1234']
+ };
+ };
+ window.parent.callBackMappedToRenewStates = {};
+ window.parent.callBackMappedToRenewStates['1234'] = 'callback';
+ var mockInvalidClientIdToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJjbGllbnQxMjMiLCJuYW1lIjoiSm9obiBEb2UiLCJ1cG4iOiJqb2huQGVtYWlsLmNvbSJ9.zNX4vfLzlbFeKHZ9BMN3sYLtEEE-0o1RoL4NUhXz-l8';
+ window.location.hash = 'id_token=' + mockInvalidClientIdToken + '&state=1234';
+ spyOn(rootScope, '$broadcast').andCallThrough();
+ scope.$on('adal:loginFailure', function (event, message) {
+ expect(event.name).toBe('adal:loginFailure');
+ expect(message).toBe('Invalid id_token. id_token: ' + mockInvalidClientIdToken);
+ });
+ scope.$apply();
+ expect(rootScope.$broadcast).toHaveBeenCalled();
+
+ });
+
+ it('tests login success after users logs in', function () {
+ window.parent.AuthenticationContext = function () {
+ return {
+ callback: 'callback',
+ _renewStates: ['1234']
+ };
+ };
+ window.parent.callBackMappedToRenewStates = {};
+ window.parent.callBackMappedToRenewStates['1234'] = 'callback';
+ var mockIdToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJjbGllbnRpZDEyMyIsIm5hbWUiOiJKb2huIERvZSIsInVwbiI6ImpvaG5AZW1haWwuY29tIiwibm9uY2UiOm51bGx9.DLCO6yIWhnNBYfHH8qFPswcH4M2Alpjn6AZy7K6HENY';
+ window.location.hash = 'id_token=' + mockIdToken + '&state=1234';
+ spyOn(rootScope, '$broadcast').andCallThrough();
+ scope.$on('adal:loginSuccess', function (event, message) {
+ expect(event.name).toBe('adal:loginSuccess');
+ expect(adalServiceProvider.userInfo.userName).toBe('john@email.com');
+ expect(adalServiceProvider.userInfo.profile.upn).toBe('john@email.com');
+ expect(adalServiceProvider.userInfo.profile.aud).toBe('clientid123');
+ });
+ scope.$apply();
+ expect(rootScope.$broadcast).toHaveBeenCalled();
+ });
+
+ it('tests auto id token renew when id token expires', function () {
+ window.parent.AuthenticationContext = function () {
+ return {
+ callback: 'callback',
+ _renewStates: ['1234']
+ };
+ };
+ window.parent.callBackMappedToRenewStates = {};
+ window.parent.callBackMappedToRenewStates['1234'] = 'callback';
+ var loginResourceOldValue = adalServiceProvider.config.loginResource;
+ adalServiceProvider.config.loginResource = null;
+ window.location.hash = 'hash';
+ spyOn(rootScope, '$broadcast').andCallThrough();
+ scope.$on('adal:loginFailure', function (event, message) {
+ expect(event.name).toBe('adal:loginFailure');
+ expect(message).toBe('auto renew failure');
+ });
+ scope.$apply();
+ adalServiceProvider.config.loginResource = loginResourceOldValue;
+ expect(rootScope.$broadcast).toHaveBeenCalled();
+ });
+
+ it('tests login handler', function () {
+ spyOn(rootScope, '$broadcast').andCallThrough();
+
+ adalServiceProvider.config.localLoginUrl = 'localLoginUrl';
+ adalServiceProvider.login();
+ scope.$on('$locationChangeStart', function (event, newUrl, oldUrl) {
+ expect(newUrl).toContain('localLoginUrl');
+ console.log('location event called');
+ event.preventDefault();
+ })
+ expect(adalServiceProvider.loginInProgress()).toBe(false);
+ scope.$apply();
+ expect(rootScope.$broadcast).toHaveBeenCalled();
+
+ adalServiceProvider.config.localLoginUrl = null
+ adalServiceProvider.login();
+ scope.$on('adal:loginRedirect', function (event, message) {
+ expect(event.name).toBe('adal:loginRedirect');
+ });
+ expect(adalServiceProvider.loginInProgress()).toBe(true);
+ expect(rootScope.$broadcast).toHaveBeenCalled();
+ });
+
+ it('tests route change handler', function () {
+ var todoRoute = route.routes['/todoList'];
+ var homeRoute = route.routes['/home'];
+ var aboutRoute = route.routes['/about'];
+
+ location.url('/todoList');
+ scope.$apply();
+ expect(route.current.controller).toBe(todoRoute.controller);
+ expect(route.current.template).toBe(todoRoute.template);
+
+ location.url('/home');
+ scope.$apply();
+ expect(route.current.controller).toBe(homeRoute.controller);
+ expect(route.current.template).toBe(homeRoute.template);
+
+ $httpBackend.expectGET('about.html').respond(200);
+ location.url('/about');
+ scope.$apply();
+ expect(route.current.controller).toBe(aboutRoute.controller);
+ expect(route.current.templateUrl).toBe(aboutRoute.templateUrl);
+ expect(adalServiceProvider.config.anonymousEndpoints).toContain(aboutRoute.templateUrl);
+ $httpBackend.flush();
+ });
+
+ it('checks if Logging is defined', function () {
+ Logging.level = 2;
+ Logging.log = function (message) {
+ window.logMessage = message;
+ }
+ adalServiceProvider.info("test message");
+ expect(window.logMessage).toContain("test message");
+ expect(Logging.level).toEqual(2);
});
});
diff --git a/tests/testApp.js b/tests/testApp.js
index ca6ada6c..0917072c 100644
--- a/tests/testApp.js
+++ b/tests/testApp.js
@@ -23,9 +23,17 @@ var app = angular.module('TestApplication', ['ngResource', 'ngRoute', 'AdalAngul
app.config(['$httpProvider', '$routeProvider', 'adalAuthenticationServiceProvider', function ($httpProvider, $routeProvider, adalAuthenticationServiceProvider) {
$routeProvider.
+ when('/home', {
+ controller: 'homeController',
+ template: 'home
'
+ }).
+ when('/about', {
+ controller: 'aboutController',
+ templateUrl: 'about.html'
+ }).
when('/todoList', {
controller: 'todoListController',
- templateUrl: '/App/Views/todoList.html',
+ template: 'todoList
',
requireADLogin: true
}).
otherwise({ redirectTo: '/home' });
diff --git a/tests/unit/spec/AdalSpec.js b/tests/unit/spec/AdalSpec.js
index 92029eb0..fefdcd89 100644
--- a/tests/unit/spec/AdalSpec.js
+++ b/tests/unit/spec/AdalSpec.js
@@ -21,10 +21,12 @@
/* global describe */
var atobHelper = require('atob');
var confighash = { hash: '#' };
+global.window = {};
var AdalModule = require('../../../lib/adal.js');
describe('Adal', function () {
var adal;
+ global.Logging = global.window.Logging;
var window = {
location: {
hash: '#hash',
@@ -44,17 +46,20 @@ describe('Adal', function () {
return 1000;
}
};
- var frameMock = {
- src: 'start'
- };
+
+ var mockFrames = {};
var documentMock = {
- getElementById: function () {
- return frameMock;
+ getElementById: function (frameId) {
+ if (!mockFrames[frameId]) {
+ mockFrames[frameId] = { src: 'start' };
+ }
+ return mockFrames[frameId];
}
};
+
var angularMock = {};
- var conf = { loginResource: 'default resource', tenant: 'testtenant', clientId: 'e9a5a8b6-8af7-4719-9821-0deef255f68e' };
+ var conf = { loginResource: 'defaultResource', tenant: 'testtenant', clientId: 'e9a5a8b6-8af7-4719-9821-0deef255f68e' };
var testPage = 'this is a song';
var STORAGE_PREFIX = 'adal';
var STORAGE_ACCESS_TOKEN_KEY = STORAGE_PREFIX + '.access.token.key';
@@ -64,6 +69,10 @@ describe('Adal', function () {
var SECONDS_TO_EXPIRE = 3600;
var DEFAULT_INSTANCE = "https://login.microsoftonline.com/";
var IDTOKEN_MOCK = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6IjVUa0d0S1JrZ2FpZXpFWTJFc0xDMmdPTGpBNCJ9.eyJhdWQiOiJlOWE1YThiNi04YWY3LTQ3MTktOTgyMS0wZGVlZjI1NWY2OGUiLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLXBwZS5uZXQvNTJkNGIwNzItOTQ3MC00OWZiLTg3MjEtYmMzYTFjOTkxMmExLyIsImlhdCI6MTQxMTk1OTAwMCwibmJmIjoxNDExOTU5MDAwLCJleHAiOjE0MTE5NjI5MDAsInZlciI6IjEuMCIsInRpZCI6IjUyZDRiMDcyLTk0NzAtNDlmYi04NzIxLWJjM2ExYzk5MTJhMSIsImFtciI6WyJwd2QiXSwib2lkIjoiZmEzYzVmYTctN2Q5OC00Zjk3LWJmYzQtZGJkM2E0YTAyNDMxIiwidXBuIjoidXNlckBvYXV0aGltcGxpY2l0LmNjc2N0cC5uZXQiLCJ1bmlxdWVfbmFtZSI6InVzZXJAb2F1dGhpbXBsaWNpdC5jY3NjdHAubmV0Iiwic3ViIjoiWTdUbXhFY09IUzI0NGFHa3RjbWpicnNrdk5tU1I4WHo5XzZmbVc2NXloZyIsImZhbWlseV9uYW1lIjoiYSIsImdpdmVuX25hbWUiOiJ1c2VyIiwibm9uY2UiOiI4MGZmYTkwYS1jYjc0LTRkMGYtYTRhYy1hZTFmOTNlMzJmZTAiLCJwd2RfZXhwIjoiNTc3OTkxMCIsInB3ZF91cmwiOiJodHRwczovL3BvcnRhbC5taWNyb3NvZnRvbmxpbmUuY29tL0NoYW5nZVBhc3N3b3JkLmFzcHgifQ.WHsl8TH1rQ3dQbRkV0TS6GBVAxzNOpG3nGG6mpEBCwAOCbyW6qRsSoo4qq8I5IGyerDf2cvcS-zzatHEROpRC9dcpwkRm6ta5dFZuouFyZ_QiYVKSMwfzEC_FI-6p7eT8gY6FbV51bp-Ah_WKJqEmaXv-lqjIpgsMGeWDgZRlB9cPODXosBq-PEk0q27Be-_A-KefQacJuWTX2eEhECLyuAu-ETVJb7s19jQrs_LJXz_ISib4DdTKPa7XTBDJlVGdCI18ctB67XwGmGi8MevkeKqFI8dkykTxeJ0MXMmEQbE6Fw-gxmP7uJYbZ61Jqwsw24zMDMeXatk2VWMBPCuhA';
+ var STATE = '585bd348-4d52-4689-b9c7-d8480564f368';
+ var SESSION_STATE = '451c6916-27cf-4eae-81cd-accf96126398';
+ var VALID_URLFRAGMENT = 'id_token=' + IDTOKEN_MOCK + '' + '&state=' + STATE + '&session_state=' + SESSION_STATE;
+ var INVALID_URLFRAGMENT = 'id_token' + IDTOKEN_MOCK + '' + '&state=' + STATE + '&session_state=' + SESSION_STATE;
var storageFake = function () {
var store = {};
return {
@@ -72,7 +81,7 @@ describe('Adal', function () {
},
setItem: function (key, value) {
if (typeof value != 'undefined') {
- store[key] = value + '';
+ store[key] = value;
}
},
clear: function () {
@@ -99,6 +108,7 @@ describe('Adal', function () {
window.sessionStorage = storageFake;
// Init adal
+
global.window = window;
global.localStorage = storageFake;
global.sessionStorage = storageFake;
@@ -110,8 +120,9 @@ describe('Adal', function () {
adal._user = null;
adal._renewStates = [];
adal._activeRenewals = {};
+ adal.CONSTANTS.LOADFRAME_TIMEOUT = 800;
});
-
+
it('gets specific resource for defined endpoint mapping', function () {
adal.config.endpoints = { 'a': 'resource for a' };
expect(adal.getResourceForEndpoint('a')).toBe('resource for a');
@@ -120,8 +131,8 @@ describe('Adal', function () {
it('gets default resource for empty endpoint mapping', function () {
adal.config.endpoints = null;
- expect(adal.getResourceForEndpoint('a')).toBe('default resource');
- expect(adal.getResourceForEndpoint('b')).toBe('default resource');
+ expect(adal.getResourceForEndpoint('a')).toBe('defaultResource');
+ expect(adal.getResourceForEndpoint('b')).toBe('defaultResource');
});
it('gets null resource for annonymous endpoints', function () {
@@ -129,7 +140,7 @@ describe('Adal', function () {
expect(adal.getResourceForEndpoint('app/views')).toBe(null);
expect(adal.getResourceForEndpoint('app/views/abc')).toBe(null);
expect(adal.getResourceForEndpoint('default/app/views/abc')).toBe(null);
- expect(adal.getResourceForEndpoint('app/home')).toBe('default resource');
+ expect(adal.getResourceForEndpoint('app/home')).toBe('defaultResource');
});
it('says token expired', function () {
@@ -154,7 +165,6 @@ describe('Adal', function () {
adal.config.clientId = 'client';
adal.config.redirectUri = 'contoso_site';
spyOn(adal, 'promptUser');
- 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._addLibMetadata() + '&nonce=33333333-3333-4333-b333-333333333333');
@@ -182,10 +192,10 @@ describe('Adal', function () {
adal.config.displayCall = displayCallback;
spyOn(adal.config, 'displayCall');
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'
+ 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._addLibMetadata()
- + '&nonce=33333333-3333-4333-b333-333333333333'
+ + '&nonce=33333333-3333-4333-b333-333333333333'
);
expect(adal.config.state).toBe('33333333-3333-4333-b333-333333333333');
});
@@ -225,23 +235,24 @@ describe('Adal', function () {
token = valToken;
};
adal._renewStates = [];
- adal._user = { userName: 'test@testuser.com' };
+ adal._user = { profile: { 'upn': 'test@testuser.com' }, userName: 'test@domain.com'};
adal.acquireToken(RESOURCE1, callback);
expect(adal.callback).toBe(callback);
expect(storageFake.getItem(adal.CONSTANTS.STORAGE.LOGIN_REQUEST)).toBe('');
expect(adal._renewStates.length).toBe(1);
// Wait for initial timeout load
console.log('Waiting for initial timeout');
- waits(2000);
+ waitsFor(function () {
+ return mockFrames['adalRenewFrame' + RESOURCE1].src !== 'about:blank';
+ }, 'iframe src not updated', 2000);
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._addLibMetadata() + '&prompt=none&login_hint=test%40testuser.com&domain_hint=testuser.com&nonce=33333333-3333-4333-b333-333333333333');
+ expect(mockFrames['adalRenewFrame' + RESOURCE1].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._addLibMetadata() + '&prompt=none&login_hint=test%40testuser.com&domain_hint=testuser.com');
});
-
+
});
-
+
//Necessary for integration with Angular when multiple http calls are queued.
it('allows multiple callers to be notified when the token is renewed', function () {
adal.config.redirectUri = 'contoso_site';
@@ -255,13 +266,13 @@ describe('Adal', function () {
err = valErr;
token = valToken;
};
- var callback2 = function(valErr, valToken){
+ var callback2 = function (valErr, valToken) {
err2 = valErr;
token2 = valToken;
};
-
+
adal._renewStates = [];
- adal._user = { userName: 'test@testuser.com' };
+ adal._user = { profile: { 'upn': 'test@testuser.com' }, userName: 'test@domain.com' };
adal.acquireToken(RESOURCE1, callback);
//Simulate second acquire i.e. second service call from Angular.
adal.acquireToken(RESOURCE1, callback2);
@@ -269,21 +280,22 @@ describe('Adal', function () {
expect(adal._renewStates.length).toBe(1);
// Wait for initial timeout load
console.log('Waiting for initial timeout');
- waits(2000);
-
+ waitsFor(function () {
+ return mockFrames['adalRenewFrame' + RESOURCE1].src !== 'about:blank';
+ }, 'iframe src not updated', 2000);
+
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._addLibMetadata() + '&prompt=none&login_hint=test%40testuser.com&domain_hint=testuser.com&nonce=33333333-3333-4333-b333-333333333333');
+ expect(mockFrames['adalRenewFrame' + RESOURCE1].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._addLibMetadata() + '&prompt=none&login_hint=test%40testuser.com&domain_hint=testuser.com');
});
-
+
//Simulate callback from the frame.
//adal.callback(null, '33333333-3333-4333-b333-333333333333');
window.callBackMappedToRenewStates[adal.config.state](null, '33333333-3333-4333-b333-333333333333');
//Both callbacks should have been provided with the token.
expect(token).toBe('33333333-3333-4333-b333-333333333333', 'First callback should be called');
expect(token2).toBe('33333333-3333-4333-b333-333333333333', 'Second callback should be called');
-
+
});
it('check guid masking', function () {
@@ -304,7 +316,7 @@ describe('Adal', function () {
};
// 15->1111 after masked with & 0011 | 1000 1011
expect(adal._guid()).toBe('ffffffff-ffff-4fff-bfff-ffffffffffff');
-
+
mathMock.random = function () {
return 0.9;
};
@@ -335,7 +347,6 @@ describe('Adal', function () {
storageFake.setItem(adal.CONSTANTS.STORAGE.EXPIRATION_KEY, 3);
storageFake.setItem(adal.CONSTANTS.STORAGE.SESSION_STATE, 'session_state');
storageFake.setItem(adal.CONSTANTS.STORAGE.STATE_LOGIN, 'state login');
- storageFake.setItem(adal.CONSTANTS.STORAGE.START_PAGE, 'start page');
storageFake.setItem(adal.CONSTANTS.STORAGE.USERNAME, 'username');
storageFake.setItem(adal.CONSTANTS.STORAGE.ERROR, 'error');
storageFake.setItem(adal.CONSTANTS.STORAGE.ERROR_DESCRIPTION, 'error description');
@@ -492,7 +503,7 @@ describe('Adal', function () {
requestType: adal.REQUEST_TYPE.RENEW_TOKEN
};
adal.saveTokenFromHash(requestInfo);
- expect(storageFake.getItem(adal.CONSTANTS.STORAGE.EXPIRATION_KEY + 'loginResource1')).toBe(mathMock.round(1) + 3589 + '');
+ expect(storageFake.getItem(adal.CONSTANTS.STORAGE.EXPIRATION_KEY + 'loginResource1')).toBe(mathMock.round(1) + 3589);
});
it('saves username after extracting idtoken', function () {
@@ -505,7 +516,7 @@ describe('Adal', function () {
stateMatch: true,
stateResponse: '123',
requestType: adal.REQUEST_TYPE.ID_TOKEN
- };
+ };
storageFake.setItem(adal.CONSTANTS.STORAGE.NONCE_IDTOKEN, '19e67b24-cd99-45b6-a588-840e3f8f2a70');
adal.config.clientId = conf.clientId;
adal._user = null;
@@ -513,7 +524,6 @@ describe('Adal', function () {
var cachedUser = adal.getCachedUser();
expect(cachedUser.userName).toBe('user@oauthimplicit.ccsctp.net');
expect(cachedUser.profile.upn).toBe('user@oauthimplicit.ccsctp.net');
- console.log('test extract idtoken done');
});
it('does not save user for invalid nonce in idtoken', function () {
@@ -568,27 +578,27 @@ describe('Adal', function () {
expect(storageFake.getItem(adal.CONSTANTS.STORAGE.USERNAME)).toBeUndefined();
});
- it ('test decode with no padding', function () {
+ it('test decode with no padding', function () {
expect(adal._decode('ZGVjb2RlIHRlc3Rz')).toBe('decode tests');
});
- it ('test decode with one = padding', function () {
- expect(adal._decode('ZWNvZGUgdGVzdHM=')).toBe('ecode tests');
+ it('test decode with one = padding', function () {
+ expect(adal._decode('ZWNvZGUgdGVzdHM=')).toBe('ecode tests');
});
- it ('test decode with two == padding', function () {
- expect(adal._decode('Y29kZSB0ZXN0cw==')).toBe('code tests');
+ it('test decode with two == padding', function () {
+ expect(adal._decode('Y29kZSB0ZXN0cw==')).toBe('code tests');
})
- it ('test decode throw error', function () {
- try{
- adal._decode('YW55I');
- } catch(e) {
+ it('test decode throw error', function () {
+ try {
+ adal._decode('YW55I');
+ } catch (e) {
expect(e.message).toBe('The token to be decoded is not correctly encoded.');
}
});
- it ('test get resource for endpoint from app backend', function () {
+ it('test get resource for endpoint from app backend', function () {
adal.config.redirectUri = 'https://host.com/page';
expect(adal.getResourceForEndpoint('https://host.com')).toBe(adal.config.loginResource);
expect(adal.getResourceForEndpoint('https://host.com/a/b')).toBe(adal.config.loginResource);
@@ -597,7 +607,7 @@ describe('Adal', function () {
expect(adal.getResourceForEndpoint('/api/todo')).toBe(adal.config.loginResource);
});
- it ('test host extraction', function () {
+ it('test host extraction', function () {
expect(adal._getHostFromUri('https://a.com/b/c')).toBe('a.com');
expect(adal._getHostFromUri('http://a.com')).toBe('a.com');
expect(adal._getHostFromUri('a.com/b/c')).toBe('a.com');
@@ -624,7 +634,224 @@ describe('Adal', function () {
expect(storageFake.getItem(adal.CONSTANTS.STORAGE.ERROR_DESCRIPTION)).toBe('Invalid_state. state: ' + requestInfo.stateResponse);
});
+ it('checks if Logging is defined on window', function () {
+ Logging.level = 2;
+ Logging.log = function (message) {
+ window.logMessage = message;
+ }
+ adal.promptUser();
+ expect(window.logMessage).toContain("Navigate url is empty");
+ expect(Logging.level).toEqual(2);
+ });
+
+ it('tests the load frame timeout method', function () {
+ adal._loadFrameTimeout('urlnavigation', 'frameName', RESOURCE1);
+ expect(storageFake.getItem(adal.CONSTANTS.STORAGE.RENEW_STATUS + RESOURCE1)).toBe(adal.CONSTANTS.TOKEN_RENEW_STATUS_IN_PROGRESS);
+
+ // timeout interval passed
+ waitsFor(function () {
+ return storageFake.getItem(adal.CONSTANTS.STORAGE.RENEW_STATUS + RESOURCE1) === adal.CONSTANTS.TOKEN_RENEW_STATUS_CANCELED;
+ }, 'token renew status not updated', 1000);
+
+ runs(function () {
+ expect(storageFake.getItem(adal.CONSTANTS.STORAGE.RENEW_STATUS + RESOURCE1)).toBe(adal.CONSTANTS.TOKEN_RENEW_STATUS_CANCELED);
+
+ adal._loadFrameTimeout('urlnavigation', 'frameName', RESOURCE1);
+ expect(storageFake.getItem(adal.CONSTANTS.STORAGE.RENEW_STATUS + RESOURCE1)).toBe(adal.CONSTANTS.TOKEN_RENEW_STATUS_IN_PROGRESS);
+ var requestInfo = {
+ valid: true,
+ parameters: { 'access_token': 'token123', 'state': '123', 'expires_in': '23' },
+ stateMatch: true,
+ stateResponse: '64532|' + RESOURCE1,
+ requestType: adal.REQUEST_TYPE.RENEW_TOKEN
+ };
+ adal.saveTokenFromHash(requestInfo);
+ expect(storageFake.getItem(adal.CONSTANTS.STORAGE.RENEW_STATUS + RESOURCE1)).toBe(adal.CONSTANTS.TOKEN_RENEW_STATUS_COMPLETED);
+ });
+ });
+
+ it('tests that callbacks are called when renewal token request was canceled', function () {
+ adal.config.expireOffsetSeconds = SECONDS_TO_EXPIRE + 100;
+ var err = '';
+ var token = '';
+ var callback = function (valErr, valToken) {
+ err = valErr;
+ token = valToken;
+ };
+ adal._renewStates = [];
+ adal._user = { userName: 'test@testuser.com' };
+ adal.acquireToken(RESOURCE1, callback);
+ waitsFor(function () {
+ return storageFake.getItem(adal.CONSTANTS.STORAGE.RENEW_STATUS + RESOURCE1) === adal.CONSTANTS.TOKEN_RENEW_STATUS_CANCELED;
+ }, 'token renew status not updated', 1000);
+ runs(function () {
+ expect(storageFake.getItem(adal.CONSTANTS.STORAGE.RENEW_STATUS + RESOURCE1)).toBe(adal.CONSTANTS.TOKEN_RENEW_STATUS_CANCELED);
+ expect(err).toBe('Token renewal operation failed due to timeout');
+ expect(token).toBe(null);
+
+ });
+ });
+
+ it('attempts to renewidToken if token expired and renew is allowed', function () {
+ adal.config.redirectUri = 'contoso_site';
+ adal.config.clientId = 'client';
+ adal.config.expireOffsetSeconds = SECONDS_TO_EXPIRE + 100;
+ adal.config.tenant = 'testtenant';
+ var err = '';
+ var token = '';
+ var callback = function (valErr, valToken) {
+ err = valErr;
+ token = valToken;
+ };
+ adal._renewStates = [];
+ adal._user = { profile: { 'upn': 'test@testuser.com' }, userName: 'test@domain.com' };
+ adal.acquireToken(adal.config.clientId, callback);
+ expect(storageFake.getItem(adal.CONSTANTS.STORAGE.NONCE_IDTOKEN)).toBe('33333333-3333-4333-b333-333333333333');
+ expect(adal.config.state).toBe('33333333-3333-4333-b333-333333333333' + '|' + 'client');
+ expect(adal._renewStates.length).toBe(1);
+ expect(storageFake.getItem(adal.CONSTANTS.STORAGE.LOGIN_REQUEST)).toBe('');
+ // Wait for initial timeout load
+ console.log('Waiting for initial timeout');
+ waitsFor(function () {
+ return mockFrames['adalIdTokenFrame'].src !== 'about:blank';
+ }, 'iframe src not updated', 2000);
+
+ runs(function () {
+ expect(mockFrames['adalIdTokenFrame'].src).toBe(DEFAULT_INSTANCE + conf.tenant + '/oauth2/authorize?response_type=id_token&client_id=' + adal.config.clientId + '&redirect_uri=contoso_site&state=33333333-3333-4333-b333-333333333333%7Cclient'
+ + '&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');
+ });
+ });
+
+ it('tests handleWindowCallback function for RENEW_TOKEN', function () {
+ window.location.hash = '#/id_token=' + IDTOKEN_MOCK;
+ adal.getRequestInfo = function (hash) {
+ return {
+ valid: true,
+ parameters: { 'error_description': 'error description', 'error': 'invalid', 'id_token': IDTOKEN_MOCK, 'session_state': '61ae5247-eaf8-4496-a667-32b0acbad7a0', 'state': '19537a2a-e9e7-489d-ae7d-3eefab9e4137' },
+ stateMatch: true,
+ stateResponse: '19537a2a-e9e7-489d-ae7d-3eefab9e4137',
+ requestType: adal.REQUEST_TYPE.RENEW_TOKEN
+ };
+ };
+ var err = '';
+ var token = '';
+ var callback = function (valErr, valToken) {
+ err = valErr;
+ token = valToken;
+ };
+ window.parent = {};
+ window.parent.callBackMappedToRenewStates = {};
+ window.parent.callBackMappedToRenewStates[adal.getRequestInfo().stateResponse] = callback;
+ adal.handleWindowCallback();
+ expect(err).toBe('error description');
+ expect(token).toBe(IDTOKEN_MOCK);
+
+ });
+
+ it('tests handleWindowCallback function for LOGIN_REQUEST', function () {
+ window.location = {};
+ window.location.hash = '#/id_token=' + IDTOKEN_MOCK;
+ adal.getRequestInfo = function () {
+ return {
+ valid: true,
+ parameters: { 'error_description': 'error description', 'error': 'invalid', 'id_token': IDTOKEN_MOCK, 'session_state': '61ae5247-eaf8-4496-a667-32b0acbad7a0', 'state': '19537a2a-e9e7-489d-ae7d-3eefab9e4137' },
+ stateMatch: true,
+ stateResponse: '19537a2a-e9e7-489d-ae7d-3eefab9e4137',
+ requestType: adal.REQUEST_TYPE.LOGIN_REQUEST
+ };
+ };
+ storageFake.setItem(adal.CONSTANTS.STORAGE.LOGIN_REQUEST, "www.test.com");
+ window.oauth2Callback = {};
+ adal.handleWindowCallback();
+ expect(window.location).toBe('www.test.com');
+
+ });
+
+ it('use the same correlationId for each request sent to AAD if set by user', function () {
+ adal.config.correlationId = '33333333-3333-4333-b333-333333333333';
+ adal.config.redirectUri = 'contoso_site';
+ adal.config.clientId = 'client';
+ adal.config.expireOffsetSeconds = SECONDS_TO_EXPIRE + 100;
+ var callback = function () {
+ };
+ adal._renewStates = [];
+ adal._user = { profile: { 'upn': 'test@testuser.com' }, userName: 'test@domain.com' };
+ spyOn(adal, '_loadFrameTimeout');
+ adal.acquireToken(RESOURCE1, callback);
+ expect(adal._loadFrameTimeout).toHaveBeenCalledWith(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._addLibMetadata() + '&prompt=none&login_hint=test%40testuser.com&domain_hint=testuser.com', 'adalRenewFrametoken.resource1', 'token.resource1');
+
+ adal._activeRenewals = {};
+ adal._user = { profile: { 'sub': 'test@testuser.com' }, userName: 'test@domain.com' };
+ adal.acquireToken(RESOURCE1, callback);
+ expect(adal._loadFrameTimeout).toHaveBeenCalledWith(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._addLibMetadata() + '&prompt=none', 'adalRenewFrametoken.resource1', 'token.resource1');
+ });
+
+ it('generates new correlationId for each request sent to AAD if not set by user', function () {
+ adal.config.correlationId = null;
+ adal.config.redirectUri = 'contoso_site';
+ adal.config.clientId = 'client';
+ adal.config.expireOffsetSeconds = SECONDS_TO_EXPIRE + 100;
+ var callback = function () {
+ };
+ adal._renewStates = [];
+ adal._user = { profile: { 'upn': 'test@testuser.com' }, userName: 'test@domain.com' };
+ mathMock.random = function () {
+ return 0.1;
+ };
+ spyOn(adal, '_loadFrameTimeout');
+ adal.acquireToken(RESOURCE1, callback);
+ expect(adal._loadFrameTimeout).toHaveBeenCalledWith(DEFAULT_INSTANCE + conf.tenant + '/oauth2/authorize?response_type=token&client_id=client&resource=' + RESOURCE1 + '&redirect_uri=contoso_site&state=11111111-1111-4111-9111-111111111111%7Ctoken.resource1'
+ + '&client-request-id=11111111-1111-4111-9111-111111111111' + adal._addLibMetadata() + '&prompt=none&login_hint=test%40testuser.com&domain_hint=testuser.com', 'adalRenewFrametoken.resource1', 'token.resource1');
+
+ mathMock.random = function () {
+ return 0.3;
+ };
+ adal._activeRenewals = {};
+ adal._user = { profile: { 'sub': 'test@testuser.com' }, userName: 'test@domain.com' };
+ adal.acquireToken(RESOURCE1, callback);
+ expect(adal._loadFrameTimeout).toHaveBeenCalledWith(DEFAULT_INSTANCE + conf.tenant + '/oauth2/authorize?response_type=token&client_id=client&resource=' + RESOURCE1 + '&redirect_uri=contoso_site&state=44444444-4444-4444-8444-444444444444%7Ctoken.resource1'
+ + '&client-request-id=44444444-4444-4444-8444-444444444444' + adal._addLibMetadata() + '&prompt=none', 'adalRenewFrametoken.resource1', 'token.resource1');
+
+ });
+
+ it('checks the deserialize method for extracting idToken', function () {
+ var obj = adal._deserialize(VALID_URLFRAGMENT);
+ expect(obj.id_token).toBe(IDTOKEN_MOCK);
+ expect(obj.state).toBe(STATE);
+ expect(obj.session_state).toBe(SESSION_STATE);
+
+ obj = adal._deserialize(INVALID_URLFRAGMENT);
+ expect(obj.id_token).toBeUndefined;
+ expect(obj.state).toBe(STATE);
+ expect(obj.session_state).toBe(SESSION_STATE);
+ expect(obj['id_token' + IDTOKEN_MOCK]).toBeUndefined;
+ var deserialize = adal._deserialize;//save initial state of function
+
+ adal._deserialize = function (query) {
+ var match,
+ pl = /\+/g, // Regex for replacing addition symbol with a space
+ search = /([^&=]+)=?([^&]*)/g,
+ decode = function (s) {
+ return decodeURIComponent(s.replace(pl, ' '));
+ },
+ obj = {};
+ match = search.exec(query);
+ while (match) {
+ obj[decode(match[1])] = decode(match[2]);
+ match = search.exec(query);
+ }
+
+ return obj;
+ }
+ obj = adal._deserialize(INVALID_URLFRAGMENT);
+ expect(obj['id_token' + IDTOKEN_MOCK]).toBe('');//This additional property is parsed because of ? operator in regex
+ expect(obj.id_token).toBeUndefined;
+ expect(obj.state).toBe(STATE);
+ expect(obj.session_state).toBe(SESSION_STATE);
+ adal._deserialize = deserialize;//reassign state to original function
+ });
// TODO angular intercepptor
-
- // TODO angular authenticaitonService
-});
\ No newline at end of file
+ // TODO angular authenticationService
+});