diff --git a/README.md b/README.md index 2cb218cf..88f88df2 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.11**. +This is a GA released version. The current version is **1.0.12**. 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.11. +CDN will be updated to latest version 1.0.12. Via Bower: diff --git a/bower.json b/bower.json index f65046cd..3263b857 100644 --- a/bower.json +++ b/bower.json @@ -1,13 +1,14 @@ { "name": "adal-angular", - "version": "1.0.11", + "version": "1.0.12", "homepage": "https://github.com/AzureAD/azure-activedirectory-library-for-js", "authors": [ "MSOpentech" ], "description": "Azure Active Directory Client Library for js", - "main": ["./lib/adal.js", - "./lib/adal-angular.js" + "main": [ + "./lib/adal.js", + "./lib/adal-angular.js" ], "moduleType": [ "node" @@ -19,7 +20,7 @@ "directory", "azure" ], - "licenses": [ + "licenses": [ { "type": "Apache 2.0", "url": "http://www.apache.org/licenses/LICENSE-2.0" @@ -39,6 +40,7 @@ "angular-resource": "~1.2.26", "angular-mocks": "~1.2.26", "jasmine": "2.0.0", - "angular-route": "~1.2.26" + "angular-route": "~1.2.26", + "angular-ui-router": "^0.3.1" } } diff --git a/changelog.txt b/changelog.txt index 24b4724a..454fc9f4 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,12 @@ +Version 1.0.12 +========================== +* Adding support for Login using a pop-up instead of a full redirect. Please see this: https://github.com/AzureAD/azure-activedirectory-library-for-js/issues/60 +* Updating anonymousEndpoints feature to handle nested states in ui-router. Please see this: https://github.com/AzureAD/azure-activedirectory-library-for-js/issues/345 +* Fix bug in anonymousEndpoints allowing templateUrl property of the route/state to be declared as a function. Thanks @dhodgin for the Pull Request. Please see this: https://github.com/AzureAD/azure-activedirectory-library-for-js/issues/368 +* Using window.crypto.getRandomValues API to generate version 4 UUID as per RFC 4122. Please see this: https://github.com/AzureAD/azure-activedirectory-library-for-js/issues/88 +* Fix bug in handleWindowCallback to call the callback defined on config after Login operation is completed. Please see this: https://github.com/AzureAD/azure-activedirectory-library-for-js/issues/324 +* Other bug fixes and updates. + 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 diff --git a/dist/adal-angular.min.js b/dist/adal-angular.min.js index f0de0170..0886f8a8 100644 --- a/dist/adal-angular.min.js +++ b/dist/adal-angular.min.js @@ -1,2 +1,2 @@ -/*! 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 +/*! adal-angular v1.0.12 2016-08-31 */ +!function(){"use strict";if("undefined"!=typeof module&&module.exports&&(module.exports.inject=function(a){return new AuthenticationContext(a)}),angular){var a=angular.module("AdalAngular",[]);a.provider("adalAuthenticationService",function(){var a=null,b={isAuthenticated:!1,userName:"",loginError:"",profile:""},c=function(c){var d=a.getCachedToken(c);b.isAuthenticated=null!==d&&d.length>0;var e=a.getCachedUser()||{userName:""};b.userName=e.userName,b.profile=e.profile,b.loginError=a.getLoginError()};this.init=function(b,d){if(!b)throw new Error("You must set configOptions, when calling init");var e=window.location.hash,f=window.location.href;e&&(f=f.replace(e,"")),b.redirectUri=b.redirectUri||f,b.postLogoutRedirectUri=b.postLogoutRedirectUri||f,b.isAngular=!0,d&&d.interceptors&&d.interceptors.push("ProtectedResourceInterceptor"),a=new AuthenticationContext(b),c(a.config.loginResource)},this.$get=["$rootScope","$window","$q","$location","$timeout","$injector",function(d,e,f,g,h,i){function j(a,b){return b.requireADLogin?a.requireADLogin!==!1:!!a.requireADLogin}function k(b){if(a.config&&a.config.anonymousEndpoints)for(var c=0;c-1)return!0;return!1}function l(a){var b=null,c=[];if(a.hasOwnProperty("parent"))for(b=a;b;)c.unshift(b),b=i.get("$state").get(b.parent);else for(var d=a.name.split("."),e=0,f=d[0];e-1&&g.url(n.substring(n.indexOf("#")+1)),e.location=n)}}else d.$broadcast("adal:stateMismatch",a._getItem(a.CONSTANTS.STORAGE.ERROR_DESCRIPTION))}else c(a.config.loginResource),b.isAuthenticated||!b.userName||a._renewActive||(a._renewActive=!0,a.acquireToken(a.config.loginResource,function(c,e){a._renewActive=!1,c?d.$broadcast("adal:loginFailure","auto renew failure"):e&&(b.isAuthenticated=!0)}));h(function(){c(a.config.loginResource),d.userInfo=b},1)},n=function(){a.info("Login event for:"+g.$$url),a.config&&a.config.localLoginUrl?g.path(a.config.localLoginUrl):(a.info("Start login at:"+window.location.href),d.$broadcast("adal:loginRedirect"),a.login())},o=function(c,d){if(d&&d.$$route)if(j(d.$$route,a.config))b.isAuthenticated||a._renewActive||a.loginInProgress()||(a.info("Route change event for:"+g.$$url),n());else{var e;e="function"==typeof d.$$route.templateUrl?d.$$route.templateUrl(d.params):d.$$route.templateUrl,e&&!k(e)&&a.config.anonymousEndpoints.push(e)}},p=function(c,d,e,f,h){if(d)for(var i=l(d),m=null,o=0;o-1},AuthenticationContext.prototype.getCachedToken=function(a){if(!this._hasResource(a))return null;var b=this._getItem(this.CONSTANTS.STORAGE.ACCESS_TOKEN_KEY+a),c=this._getItem(this.CONSTANTS.STORAGE.EXPIRATION_KEY+a),d=this.config.expireOffsetSeconds||120;return c&&c>this._now()+d?b:(this._saveItem(this.CONSTANTS.STORAGE.ACCESS_TOKEN_KEY+a,""),this._saveItem(this.CONSTANTS.STORAGE.EXPIRATION_KEY+a,0),null)},AuthenticationContext.prototype.getCachedUser=function(){if(this._user)return this._user;var a=this._getItem(this.CONSTANTS.STORAGE.IDTOKEN);return this._user=this._createUser(a),this._user},AuthenticationContext.prototype.registerCallback=function(a,b,c){this._activeRenewals[b]=a,window.callBacksMappedToRenewStates[a]||(window.callBacksMappedToRenewStates[a]=[]);var d=this;window.callBacksMappedToRenewStates[a].push(c),window.callBackMappedToRenewStates[a]||(window.callBackMappedToRenewStates[a]=function(c,e){for(var f=0;f-1)){var b=this._user.profile.upn.split("@");a+="&domain_hint="+encodeURIComponent(b[b.length-1])}return a},AuthenticationContext.prototype._createUser=function(a){var b=null,c=this._extractIdToken(a);return c&&c.hasOwnProperty("aud")&&(c.aud.toLowerCase()===this.config.clientId.toLowerCase()?(b={userName:"",profile:c},c.hasOwnProperty("upn")?b.userName=c.upn:c.hasOwnProperty("email")&&(b.userName=c.email)):this.warn("IdToken has invalid aud field")),b},AuthenticationContext.prototype._getHash=function(a){return a.indexOf("#/")>-1?a=a.substring(a.indexOf("#/")+2):a.indexOf("#")>-1&&(a=a.substring(1)),a},AuthenticationContext.prototype.isCallback=function(a){a=this._getHash(a);var b=this._deserialize(a);return b.hasOwnProperty(this.CONSTANTS.ERROR_DESCRIPTION)||b.hasOwnProperty(this.CONSTANTS.ACCESS_TOKEN)||b.hasOwnProperty(this.CONSTANTS.ID_TOKEN)},AuthenticationContext.prototype.getLoginError=function(){return this._getItem(this.CONSTANTS.STORAGE.LOGIN_ERROR)},AuthenticationContext.prototype.getRequestInfo=function(a){a=this._getHash(a);var b=this._deserialize(a),c={valid:!1,parameters:{},stateMatch:!1,stateResponse:"",requestType:this.REQUEST_TYPE.UNKNOWN};if(b&&(c.parameters=b,b.hasOwnProperty(this.CONSTANTS.ERROR_DESCRIPTION)||b.hasOwnProperty(this.CONSTANTS.ACCESS_TOKEN)||b.hasOwnProperty(this.CONSTANTS.ID_TOKEN))){c.valid=!0;var d="";if(!b.hasOwnProperty("state"))return this.warn("No state returned"),c;if(this.verbose("State: "+b.state),d=b.state,c.stateResponse=d,d===this._getItem(this.CONSTANTS.STORAGE.STATE_LOGIN))return c.requestType=this.REQUEST_TYPE.LOGIN,c.stateMatch=!0,c;if(!c.stateMatch&&window.parent&&window.parent.AuthenticationContext())for(var e=window.parent.AuthenticationContext()._renewStates,f=0;f-1&&b+1-1)return this.config.endpoints[b];if(!(a.indexOf("http://")>-1||a.indexOf("https://")>-1)){if(this.config&&this.config.anonymousEndpoints)for(var c=0;c-1)return null;return this.config.loginResource}return this._getHostFromUri(a)===this._getHostFromUri(this.config.redirectUri)?this.config.loginResource:null},AuthenticationContext.prototype._getHostFromUri=function(a){var b=String(a).replace(/^(https?:)\/\//,"");return b=b.split("/")[0]},AuthenticationContext.prototype.handleWindowCallback=function(){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 +/*! adal-angular v1.0.12 2016-08-31 */ +var AuthenticationContext=function(){"use strict";return AuthenticationContext=function(a){if(this.REQUEST_TYPE={LOGIN:"LOGIN",RENEW_TOKEN:"RENEW_TOKEN",UNKNOWN:"UNKNOWN"},this.CONSTANTS={ACCESS_TOKEN:"access_token",EXPIRES_IN:"expires_in",ID_TOKEN:"id_token",ERROR_DESCRIPTION:"error_description",SESSION_STATE:"session_state",STORAGE:{TOKEN_KEYS:"adal.token.keys",ACCESS_TOKEN_KEY:"adal.access.token.key",EXPIRATION_KEY:"adal.expiration.key",STATE_LOGIN:"adal.state.login",STATE_RENEW:"adal.state.renew",NONCE_IDTOKEN:"adal.nonce.idtoken",SESSION_STATE:"adal.session.state",USERNAME:"adal.username",IDTOKEN:"adal.idtoken",ERROR:"adal.error",ERROR_DESCRIPTION:"adal.error.description",LOGIN_REQUEST:"adal.login.request",LOGIN_ERROR:"adal.login.error",RENEW_STATUS:"adal.token.renew.status"},RESOURCE_DELIMETER:"|",LOADFRAME_TIMEOUT:"6000",TOKEN_RENEW_STATUS_CANCELED:"Canceled",TOKEN_RENEW_STATUS_COMPLETED:"Completed",TOKEN_RENEW_STATUS_IN_PROGRESS:"In Progress",LOGGING_LEVEL:{ERROR:0,WARN:1,INFO:2,VERBOSE:3},LEVEL_STRING_MAP:{0:"ERROR:",1:"WARNING:",2:"INFO:",3:"VERBOSE:"},POPUP_WIDTH:483,POPUP_HEIGHT:600},AuthenticationContext.prototype._singletonInstance)return AuthenticationContext.prototype._singletonInstance;if(AuthenticationContext.prototype._singletonInstance=this,this.instance="https://login.microsoftonline.com/",this.config={},this.callback=null,this.popUp=!1,this.isAngular=!1,this._user=null,this._activeRenewals={},this._loginInProgress=!1,this._renewStates=[],window.callBackMappedToRenewStates={},window.callBacksMappedToRenewStates={},a.displayCall&&"function"!=typeof a.displayCall)throw new Error("displayCall is not a function");if(!a.clientId)throw new Error("clientId is required");this.config=this._cloneConfig(a),this.config.popUp&&(this.popUp=!0),this.config.callback&&"function"==typeof this.config.callback&&(this.callback=this.config.callback),this.config.instance&&(this.instance=this.config.instance),this.config.loginResource||(this.config.loginResource=this.config.clientId),this.config.redirectUri||(this.config.redirectUri=window.location.href),this.config.anonymousEndpoints||(this.config.anonymousEndpoints=[]),this.config.isAngular&&(this.isAngular=this.config.isAngular)},window.Logging={level:0,log:function(a){}},AuthenticationContext.prototype.login=function(){if(this._loginInProgress)return void this.info("Login in progress");var a=this._guid();this.config.state=a,this._idTokenNonce=this._guid(),this.verbose("Expected state: "+a+" startPage:"+window.location),this._saveItem(this.CONSTANTS.STORAGE.LOGIN_REQUEST,window.location),this._saveItem(this.CONSTANTS.STORAGE.LOGIN_ERROR,""),this._saveItem(this.CONSTANTS.STORAGE.STATE_LOGIN,a),this._saveItem(this.CONSTANTS.STORAGE.NONCE_IDTOKEN,this._idTokenNonce),this._saveItem(this.CONSTANTS.STORAGE.ERROR,""),this._saveItem(this.CONSTANTS.STORAGE.ERROR_DESCRIPTION,"");var b=this._getNavigateUrl("id_token",null)+"&nonce="+encodeURIComponent(this._idTokenNonce);return this._loginInProgress=!0,this.popUp?void this._loginPopup(b):void(this.config.displayCall?this.config.displayCall(b):this.promptUser(b))},AuthenticationContext.prototype._openPopup=function(a,b,c,d){try{var e=window.screenLeft?window.screenLeft:window.screenX,f=window.screenTop?window.screenTop:window.screenY,g=window.innerWidth||document.documentElement.clientWidth||document.body.clientWidth,h=window.innerHeight||document.documentElement.clientHeight||document.body.clientHeight,i=g/2-c/2+e,j=h/2-d/2+f,k=window.open(a,b,"width="+c+", height="+d+", top="+j+", left="+i);return k.focus&&k.focus(),k}catch(a){return this.warn("Error opening popup, "+a.message),this._loginInProgress=!1,null}},AuthenticationContext.prototype._loginPopup=function(a){var b=this._openPopup(a,"login",this.CONSTANTS.POPUP_WIDTH,this.CONSTANTS.POPUP_HEIGHT);if(null==b)return this.warn("Popup Window is null. This can happen if you are using IE"),this._saveItem(this.CONSTANTS.STORAGE.ERROR,"Error opening popup"),this._saveItem(this.CONSTANTS.STORAGE.ERROR_DESCRIPTION,"Popup Window is null. This can happen if you are using IE"),this._saveItem(this.CONSTANTS.STORAGE.LOGIN_ERROR,"Popup Window is null. This can happen if you are using IE"),void(this.callback&&this.callback(this._getItem(this.CONSTANTS.STORAGE.LOGIN_ERROR),null));if(this.config.redirectUri.indexOf("#")!=-1)var c=this.config.redirectUri.split("#")[0];else var c=this.config.redirectUri;var d=this,e=window.setInterval(function(){b&&!b.closed&&void 0!==b.closed||(d._loginInProgress=!1,window.clearInterval(e));try{b.location.href.indexOf(c)!=-1&&(d.isAngular?window.location.hash=b.location.hash:d.handleWindowCallback(b.location.hash),window.clearInterval(e),d._loginInProgress=!1,d.info("Closing popup window"),b.close())}catch(a){}},20)},AuthenticationContext.prototype.loginInProgress=function(){return this._loginInProgress},AuthenticationContext.prototype._hasResource=function(a){var b=this._getItem(this.CONSTANTS.STORAGE.TOKEN_KEYS);return b&&!this._isEmpty(b)&&b.indexOf(a+this.CONSTANTS.RESOURCE_DELIMETER)>-1},AuthenticationContext.prototype.getCachedToken=function(a){if(!this._hasResource(a))return null;var b=this._getItem(this.CONSTANTS.STORAGE.ACCESS_TOKEN_KEY+a),c=this._getItem(this.CONSTANTS.STORAGE.EXPIRATION_KEY+a),d=this.config.expireOffsetSeconds||120;return c&&c>this._now()+d?b:(this._saveItem(this.CONSTANTS.STORAGE.ACCESS_TOKEN_KEY+a,""),this._saveItem(this.CONSTANTS.STORAGE.EXPIRATION_KEY+a,0),null)},AuthenticationContext.prototype.getCachedUser=function(){if(this._user)return this._user;var a=this._getItem(this.CONSTANTS.STORAGE.IDTOKEN);return this._user=this._createUser(a),this._user},AuthenticationContext.prototype.registerCallback=function(a,b,c){this._activeRenewals[b]=a,window.callBacksMappedToRenewStates[a]||(window.callBacksMappedToRenewStates[a]=[]);var d=this;window.callBacksMappedToRenewStates[a].push(c),window.callBackMappedToRenewStates[a]||(window.callBackMappedToRenewStates[a]=function(c,e){for(var f=0;f-1)){var b=this._user.profile.upn.split("@");a+="&domain_hint="+encodeURIComponent(b[b.length-1])}return a},AuthenticationContext.prototype._createUser=function(a){var b=null,c=this._extractIdToken(a);return c&&c.hasOwnProperty("aud")&&(c.aud.toLowerCase()===this.config.clientId.toLowerCase()?(b={userName:"",profile:c},c.hasOwnProperty("upn")?b.userName=c.upn:c.hasOwnProperty("email")&&(b.userName=c.email)):this.warn("IdToken has invalid aud field")),b},AuthenticationContext.prototype._getHash=function(a){return a.indexOf("#/")>-1?a=a.substring(a.indexOf("#/")+2):a.indexOf("#")>-1&&(a=a.substring(1)),a},AuthenticationContext.prototype.isCallback=function(a){a=this._getHash(a);var b=this._deserialize(a);return b.hasOwnProperty(this.CONSTANTS.ERROR_DESCRIPTION)||b.hasOwnProperty(this.CONSTANTS.ACCESS_TOKEN)||b.hasOwnProperty(this.CONSTANTS.ID_TOKEN)},AuthenticationContext.prototype.getLoginError=function(){return this._getItem(this.CONSTANTS.STORAGE.LOGIN_ERROR)},AuthenticationContext.prototype.getRequestInfo=function(a){a=this._getHash(a);var b=this._deserialize(a),c={valid:!1,parameters:{},stateMatch:!1,stateResponse:"",requestType:this.REQUEST_TYPE.UNKNOWN};if(b&&(c.parameters=b,b.hasOwnProperty(this.CONSTANTS.ERROR_DESCRIPTION)||b.hasOwnProperty(this.CONSTANTS.ACCESS_TOKEN)||b.hasOwnProperty(this.CONSTANTS.ID_TOKEN))){c.valid=!0;var d="";if(!b.hasOwnProperty("state"))return this.warn("No state returned"),c;if(this.verbose("State: "+b.state),d=b.state,c.stateResponse=d,d===this._getItem(this.CONSTANTS.STORAGE.STATE_LOGIN))return c.requestType=this.REQUEST_TYPE.LOGIN,c.stateMatch=!0,c;if(!c.stateMatch&&window.parent&&window.parent.AuthenticationContext())for(var e=window.parent.AuthenticationContext()._renewStates,f=0;f-1&&b+1-1)return this.config.endpoints[b];if(!(a.indexOf("http://")>-1||a.indexOf("https://")>-1)){if(this.config&&this.config.anonymousEndpoints)for(var c=0;c-1)return null;return this.config.loginResource}return this._getHostFromUri(a)===this._getHostFromUri(this.config.redirectUri)?this.config.loginResource:null},AuthenticationContext.prototype._getHostFromUri=function(a){var b=String(a).replace(/^(https?:)\/\//,"");return b=b.split("/")[0]},AuthenticationContext.prototype.handleWindowCallback=function(a){if(null==a&&(a=window.location.hash),this.isCallback(a)){var b=this.getRequestInfo(a);this.info("Returned from redirect url"),this.saveTokenFromHash(b);var c=null;if(b.requestType===this.REQUEST_TYPE.RENEW_TOKEN&&window.parent&&window.parent!==window)return this.verbose("Window is in iframe"),c=window.parent.callBackMappedToRenewStates[b.stateResponse],void(c&&c(this._getItem(this.CONSTANTS.STORAGE.ERROR_DESCRIPTION),b.parameters[this.CONSTANTS.ACCESS_TOKEN]||b.parameters[this.CONSTANTS.ID_TOKEN]));b.requestType===this.REQUEST_TYPE.LOGIN&&(c=this.callback,c&&c(this._getItem(this.CONSTANTS.STORAGE.ERROR_DESCRIPTION),b.parameters[this.CONSTANTS.ID_TOKEN])),this.popUp||(window.location=this._getItem(this.CONSTANTS.STORAGE.LOGIN_REQUEST))}},AuthenticationContext.prototype._getNavigateUrl=function(a,b){var c="common";this.config.tenant&&(c=this.config.tenant);var d=this.instance+c+"/oauth2/authorize"+this._serialize(a,this.config,b)+this._addLibMetadata();return this.info("Navigate url:"+d),d},AuthenticationContext.prototype._extractIdToken=function(a){var b=this._decodeJwt(a);if(!b)return null;try{var c=b.JWSPayload,d=this._base64DecodeStringUrlSafe(c);return d?JSON.parse(d):(this.info("The returned id_token could not be base64 url safe decoded."),null)}catch(a){this.error("The returned id_token could not be decoded",a)}return null},AuthenticationContext.prototype._base64DecodeStringUrlSafe=function(a){return a=a.replace(/-/g,"+").replace(/_/g,"/"),window.atob?decodeURIComponent(escape(window.atob(a))):decodeURIComponent(escape(this._decode(a)))},AuthenticationContext.prototype._decode=function(a){var b="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";a=String(a).replace(/=+$/,"");var c=a.length;if(c%4===1)throw new Error("The token to be decoded is not correctly encoded.");for(var d,e,f,g,h,i,j,k,l="",m=0;m>16&255,j=h>>8&255,l+=String.fromCharCode(i,j);break}if(m+1===c-1){h=d<<18|e<<12,i=h>>16&255,l+=String.fromCharCode(i);break}h=d<<18|e<<12|f<<6|g,i=h>>16&255,j=h>>8&255,k=255&h,l+=String.fromCharCode(i,j,k)}return l},AuthenticationContext.prototype._decodeJwt=function(a){if(this._isEmpty(a))return null;var b=/^([^\.\s]*)\.([^\.\s]+)\.([^\.\s]*)$/,c=b.exec(a);if(!c||c.length<4)return this.warn("The returned id_token is not parseable."),null;var d={header:c[1],JWSPayload:c[2],JWSSig:c[3]};return d},AuthenticationContext.prototype._convertUrlSafeToRegularBase64EncodedString=function(a){return a.replace("-","+").replace("_","/")},AuthenticationContext.prototype._serialize=function(a,b,c){var d=[];if(null!==b){d.push("?response_type="+a),d.push("client_id="+encodeURIComponent(b.clientId)),c&&d.push("resource="+encodeURIComponent(c)),d.push("redirect_uri="+encodeURIComponent(b.redirectUri)),d.push("state="+encodeURIComponent(b.state)),b.hasOwnProperty("slice")&&d.push("slice="+encodeURIComponent(b.slice)),b.hasOwnProperty("extraQueryParameter")&&d.push(b.extraQueryParameter);var e=b.correlationId?b.correlationId:this._guid();d.push("client-request-id="+encodeURIComponent(e))}return d.join("&")},AuthenticationContext.prototype._deserialize=function(a){var b,c=/\+/g,d=/([^&=]+)=([^&]*)/g,e=function(a){return decodeURIComponent(a.replace(c," "))},f={};for(b=d.exec(a);b;)f[e(b[1])]=e(b[2]),b=d.exec(a);return f},AuthenticationContext.prototype._guid=function(){function a(a){for(var b=a.toString(16);b.length<2;)b="0"+b;return b}var b=window.crypto||window.msCrypto;if(b&&b.getRandomValues){var c=new Uint8Array(16);return b.getRandomValues(c),c[6]|=64,c[6]&=79,c[8]|=128,c[8]&=191,a(c[0])+a(c[1])+a(c[2])+a(c[3])+"-"+a(c[4])+a(c[5])+"-"+a(c[6])+a(c[7])+"-"+a(c[8])+a(c[9])+"-"+a(c[10])+a(c[11])+a(c[12])+a(c[13])+a(c[14])+a(c[15])}for(var d="xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx",e="0123456789abcdef",f=0,g="",h=0;h<36;h++)"-"!==d[h]&&"4"!==d[h]&&(f=16*Math.random()|0),"x"===d[h]?g+=e[f]:"y"===d[h]?(f&=3,f|=8,g+=e[f]):g+=d[h];return g},AuthenticationContext.prototype._expiresIn=function(a){return this._now()+parseInt(a,10)},AuthenticationContext.prototype._now=function(){return Math.round((new Date).getTime()/1e3)},AuthenticationContext.prototype._addAdalFrame=function(a){if("undefined"!=typeof a){this.info("Add adal frame to document:"+a);var b=document.getElementById(a);if(!b){if(document.createElement&&document.documentElement&&(window.opera||window.navigator.userAgent.indexOf("MSIE 5.0")===-1)){var c=document.createElement("iframe");c.setAttribute("id",a),c.style.visibility="hidden",c.style.position="absolute",c.style.width=c.style.height=c.borderWidth="0px",b=document.getElementsByTagName("body")[0].appendChild(c)}else document.body&&document.body.insertAdjacentHTML&&document.body.insertAdjacentHTML("beforeEnd",'');window.frames&&window.frames[a]&&(b=window.frames[a])}return b}},AuthenticationContext.prototype._saveItem=function(a,b){return this.config&&this.config.cacheLocation&&"localStorage"===this.config.cacheLocation?this._supportsLocalStorage()?(localStorage.setItem(a,b),!0):(this.info("Local storage is not supported"),!1):this._supportsSessionStorage()?(sessionStorage.setItem(a,b),!0):(this.info("Session storage is not supported"),!1)},AuthenticationContext.prototype._getItem=function(a){return this.config&&this.config.cacheLocation&&"localStorage"===this.config.cacheLocation?this._supportsLocalStorage()?localStorage.getItem(a):(this.info("Local storage is not supported"),null):this._supportsSessionStorage()?sessionStorage.getItem(a):(this.info("Session storage is not supported"),null)},AuthenticationContext.prototype._supportsLocalStorage=function(){try{return"localStorage"in window&&window.localStorage}catch(a){return!1}},AuthenticationContext.prototype._supportsSessionStorage=function(){try{return"sessionStorage"in window&&window.sessionStorage}catch(a){return!1}},AuthenticationContext.prototype._cloneConfig=function(a){if(null===a||"object"!=typeof a)return a;var b={};for(var c in a)a.hasOwnProperty(c)&&(b[c]=a[c]);return b},AuthenticationContext.prototype._addLibMetadata=function(){return"&x-client-SKU=Js&x-client-Ver="+this._libVersion()},AuthenticationContext.prototype.log=function(a,b,c){if(a<=Logging.level){var d=(new Date).toUTCString(),e="";e=this.config.correlationId?d+":"+this.config.correlationId+"-"+this._libVersion()+"-"+this.CONSTANTS.LEVEL_STRING_MAP[a]+" "+b:d+":"+this._libVersion()+"-"+this.CONSTANTS.LEVEL_STRING_MAP[a]+" "+b,c&&(e+="\nstack:\n"+c.stack),Logging.log(e)}},AuthenticationContext.prototype.error=function(a,b){this.log(this.CONSTANTS.LOGGING_LEVEL.ERROR,a,b)},AuthenticationContext.prototype.warn=function(a){this.log(this.CONSTANTS.LOGGING_LEVEL.WARN,a,null)},AuthenticationContext.prototype.info=function(a){this.log(this.CONSTANTS.LOGGING_LEVEL.INFO,a,null)},AuthenticationContext.prototype.verbose=function(a){this.log(this.CONSTANTS.LOGGING_LEVEL.VERBOSE,a,null)},AuthenticationContext.prototype._libVersion=function(){return"1.0.12"},"undefined"!=typeof module&&module.exports&&(module.exports=AuthenticationContext,module.exports.inject=function(a){return new AuthenticationContext(a)}),AuthenticationContext}(); \ No newline at end of file diff --git a/karma.conf.js b/karma.conf.js index 1006a4e2..0feda166 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -13,8 +13,10 @@ module.exports = function(config) { 'bower_components/angular-mocks/angular-mocks.js', 'bower_components/angular-route/angular-route.js', 'bower_components/angular-resource/angular-resource.js', + 'bower_components/angular-ui-router/release/angular-ui-router.js', 'lib/*.js', 'tests/testApp.js', + 'tests/stateApp.js', 'tests/angularModuleSpec.js' ], diff --git a/lib/adal-angular.js b/lib/adal-angular.js index 20f6c0c3..a23f7f62 100644 --- a/lib/adal-angular.js +++ b/lib/adal-angular.js @@ -1,5 +1,5 @@ -//---------------------------------------------------------------------- -// AdalJS v1.0.11 +//---------------------------------------------------------------------- +// AdalJS v1.0.12 // @preserve Copyright (c) Microsoft Open Technologies, Inc. // All Rights Reserved // Apache License 2.0 @@ -55,6 +55,7 @@ } configOptions.redirectUri = configOptions.redirectUri || pathDefault; configOptions.postLogoutRedirectUri = configOptions.postLogoutRedirectUri || pathDefault; + configOptions.isAngular = true; if (httpProvider && httpProvider.interceptors) { httpProvider.interceptors.push('ProtectedResourceInterceptor'); @@ -72,7 +73,7 @@ // special function that exposes methods in Angular controller // $rootScope, $window, $q, $location, $timeout are injected by Angular - this.$get = ['$rootScope', '$window', '$q', '$location', '$timeout', function ($rootScope, $window, $q, $location, $timeout) { + this.$get = ['$rootScope', '$window', '$q', '$location', '$timeout','$injector', function ($rootScope, $window, $q, $location, $timeout,$injector) { var locationChangeHandler = function (event, newUrl, oldUrl) { _adal.verbose('Location change event from ' + oldUrl + ' to ' + newUrl); @@ -84,34 +85,28 @@ var requestInfo = _adal.getRequestInfo(hash); _adal.saveTokenFromHash(requestInfo); - if (requestInfo.requestType !== _adal.REQUEST_TYPE.LOGIN) { - _adal.callback = $window.parent.AuthenticationContext().callback; - if (requestInfo.requestType === _adal.REQUEST_TYPE.RENEW_TOKEN) { - _adal.callback = $window.parent.callBackMappedToRenewStates[requestInfo.stateResponse]; - } - } - // Return to callback if it is sent from iframe if (requestInfo.stateMatch) { - if (typeof _adal.callback === 'function') { + if (requestInfo.requestType === _adal.REQUEST_TYPE.RENEW_TOKEN) { + var callback = $window.parent.callBackMappedToRenewStates[requestInfo.stateResponse]; // 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) { + if (callback && typeof callback === 'function') { // 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']); + callback(_adal._getItem(_adal.CONSTANTS.STORAGE.ERROR_DESCRIPTION), requestInfo.parameters['access_token']); return; } else if (requestInfo.parameters['id_token']) { - _adal.callback(_adal._getItem(_adal.CONSTANTS.STORAGE.ERROR_DESCRIPTION), requestInfo.parameters['id_token']); + callback(_adal._getItem(_adal.CONSTANTS.STORAGE.ERROR_DESCRIPTION), requestInfo.parameters['id_token']); return; } else if (requestInfo.parameters['error']) { - _adal.callback(_adal._getItem(_adal.CONSTANTS.STORAGE.ERROR_DESCRIPTION), null); + callback(_adal._getItem(_adal.CONSTANTS.STORAGE.ERROR_DESCRIPTION), null); return; } } - } else { + } else if (requestInfo.requestType === _adal.REQUEST_TYPE.LOGIN) { // normal full login redirect happened on the page updateDataFromCache(_adal.config.loginResource); if (_oauthData.userName) { @@ -121,21 +116,26 @@ $rootScope.userInfo = _oauthData; }, 1); - $rootScope.$broadcast('adal:loginSuccess'); + $rootScope.$broadcast('adal:loginSuccess', _adal._getItem(_adal.CONSTANTS.STORAGE.IDTOKEN)); } else { $rootScope.$broadcast('adal:loginFailure', _adal._getItem(_adal.CONSTANTS.STORAGE.ERROR_DESCRIPTION)); } + if (_adal.callback && typeof _adal.callback === 'function') + _adal.callback(_adal._getItem(_adal.CONSTANTS.STORAGE.ERROR_DESCRIPTION), _adal._getItem(_adal.CONSTANTS.STORAGE.IDTOKEN)); + + event.preventDefault(); // redirect to login start page - 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)); + if (!_adal.popUp) { + 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); + if (!$location.$$html5 && loginStartPage.indexOf('#') > -1) { + $location.url(loginStartPage.substring(loginStartPage.indexOf('#') + 1)); + } + $window.location = loginStartPage; } - $window.location = loginStartPage; } } } @@ -197,6 +197,29 @@ return false; } + function getStates(toState) { + var state = null; + var states = []; + if (toState.hasOwnProperty('parent')) { + state = toState; + while (state) { + states.unshift(state); + state = $injector.get('$state').get(state.parent); + } + } + else { + var stateNames = toState.name.split('.'); + for (var i = 0, stateName = stateNames[0]; i < stateNames.length; i++) { + state = $injector.get('$state').get(stateName); + if (state) { + states.push(state); + } + stateName += '.' + stateNames[i + 1]; + } + } + return states; + } + var routeChangeHandler = function (e, nextRoute) { if (nextRoute && nextRoute.$$route) { if (isADLoginRequired(nextRoute.$$route, _adal.config)) { @@ -208,8 +231,15 @@ } } else { - if (nextRoute.$$route.templateUrl && !isAnonymousEndpoint(nextRoute.$$route.templateUrl)) { - _adal.config.anonymousEndpoints.push(nextRoute.$$route.templateUrl); + var nextRouteUrl; + if(typeof nextRoute.$$route.templateUrl === "function") { + nextRouteUrl = nextRoute.$$route.templateUrl(nextRoute.params); + } else { + nextRouteUrl = nextRoute.$$route.templateUrl; + } + + if (nextRouteUrl && !isAnonymousEndpoint(nextRouteUrl)) { + _adal.config.anonymousEndpoints.push(nextRouteUrl); } } } @@ -217,17 +247,29 @@ var stateChangeHandler = function (e, toState, toParams, fromState, fromParams) { if (toState) { - if (isADLoginRequired(toState, _adal.config)) { - if (!_oauthData.isAuthenticated) { - if (!_adal._renewActive && !_adal.loginInProgress()) { - _adal.info('State change event for:' + $location.$$url); - loginHandler(); + var states = getStates(toState); + var state = null; + for (var i = 0; i < states.length; i++) { + state = states[i]; + if (isADLoginRequired(state, _adal.config)) { + if (!_oauthData.isAuthenticated) { + if (!_adal._renewActive && !_adal.loginInProgress()) { + _adal.info('State change event for:' + $location.$$url); + loginHandler(); + } } } - } - else { - if (toState.templateUrl && !isAnonymousEndpoint(toState.templateUrl)) { - _adal.config.anonymousEndpoints.push(toState.templateUrl); + else if (state.templateUrl) { + var nextStateUrl; + if (typeof state.templateUrl === 'function'){ + nextStateUrl = state.templateUrl(toParams); + } + else { + nextStateUrl = state.templateUrl; + } + if (nextStateUrl && !isAnonymousEndpoint(nextStateUrl)) { + _adal.config.anonymousEndpoints.push(nextStateUrl); + } } } } @@ -344,9 +386,23 @@ else { // Cancel request if login is starting if (authService.loginInProgress()) { - authService.info('login is in progress.'); - config.data = 'login in progress, cancelling the request for ' + config.url; - return $q.reject(config); + if (authService.config.popUp) { + authService.info('Url: ' + config.url + ' will be loaded after login is successful'); + var delayedRequest = $q.defer(); + $rootScope.$on('adal:loginSuccess', function (event, token) { + if (token) { + authService.info('Login completed, sending request for ' + config.url); + config.headers.Authorization = 'Bearer ' + tokenStored; + delayedRequest.resolve(config); + } + }); + return delayedRequest.promise; + } + 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 diff --git a/lib/adal.js b/lib/adal.js index f9db922a..083390d6 100644 --- a/lib/adal.js +++ b/lib/adal.js @@ -1,5 +1,5 @@ -//---------------------------------------------------------------------- -// AdalJS v1.0.11 +//---------------------------------------------------------------------- +// AdalJS v1.0.12 // @preserve Copyright (c) Microsoft Open Technologies, Inc. // All Rights Reserved // Apache License 2.0 @@ -72,7 +72,6 @@ var AuthenticationContext = (function () { 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', @@ -84,9 +83,6 @@ var AuthenticationContext = (function () { 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', @@ -102,7 +98,9 @@ var AuthenticationContext = (function () { 1: 'WARNING:', 2: 'INFO:', 3: 'VERBOSE:' - } + }, + POPUP_WIDTH: 483, + POPUP_HEIGHT: 600 }; if (AuthenticationContext.prototype._singletonInstance) { @@ -115,6 +113,7 @@ var AuthenticationContext = (function () { this.config = {}; this.callback = null; this.popUp = false; + this.isAngular = false; // private this._user = null; @@ -136,6 +135,12 @@ var AuthenticationContext = (function () { this.config = this._cloneConfig(config); + if (this.config.popUp) + this.popUp = true; + + if (this.config.callback && typeof this.config.callback === 'function') + this.callback = this.config.callback; + if (this.config.instance) { this.instance = this.config.instance; } @@ -152,6 +157,10 @@ var AuthenticationContext = (function () { if (!this.config.anonymousEndpoints) { this.config.anonymousEndpoints = []; } + + if (this.config.isAngular) { + this.isAngular = this.config.isAngular; + } }; window.Logging = { @@ -165,6 +174,10 @@ var AuthenticationContext = (function () { */ AuthenticationContext.prototype.login = function () { // Token is not present and user needs to login + if (this._loginInProgress) { + this.info("Login in progress"); + return; + } var expectedState = this._guid(); this.config.state = expectedState; this._idTokenNonce = this._guid(); @@ -175,18 +188,86 @@ var AuthenticationContext = (function () { 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.popUp) { + this._loginPopup(urlNavigate); + return; + } 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._openPopup = function (urlNavigate, title, popUpWidth, popUpHeight) { + try { + /** + * adding winLeft and winTop to account for dual monitor + * using screenLeft and screenTop for IE8 and earlier + */ + var winLeft = window.screenLeft ? window.screenLeft : window.screenX; + var winTop = window.screenTop ? window.screenTop : window.screenY; + /** + * window.innerWidth displays browser window's height and width excluding toolbars + * using document.documentElement.clientWidth for IE8 and earlier + */ + var width = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth; + var height = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight; + var left = ((width / 2) - (popUpWidth / 2)) + winLeft; + var top = ((height / 2) - (popUpHeight / 2)) + winTop; + + var popupWindow = window.open(urlNavigate, title, 'width=' + popUpWidth + ', height=' + popUpHeight + ', top=' + top + ', left=' + left); + if (popupWindow.focus) { + popupWindow.focus(); + } + return popupWindow; + } catch (e) { + this.warn('Error opening popup, ' + e.message); + this._loginInProgress = false; + return null; + } + } + + AuthenticationContext.prototype._loginPopup = function (urlNavigate) { + var popupWindow = this._openPopup(urlNavigate, "login", this.CONSTANTS.POPUP_WIDTH, this.CONSTANTS.POPUP_HEIGHT); + if (popupWindow == null) { + this.warn('Popup Window is null. This can happen if you are using IE'); + this._saveItem(this.CONSTANTS.STORAGE.ERROR, 'Error opening popup'); + this._saveItem(this.CONSTANTS.STORAGE.ERROR_DESCRIPTION, 'Popup Window is null. This can happen if you are using IE'); + this._saveItem(this.CONSTANTS.STORAGE.LOGIN_ERROR, 'Popup Window is null. This can happen if you are using IE'); + if (this.callback) + this.callback(this._getItem(this.CONSTANTS.STORAGE.LOGIN_ERROR), null); + return; + } + if (this.config.redirectUri.indexOf('#') != -1) + var registeredRedirectUri = this.config.redirectUri.split("#")[0]; + else + var registeredRedirectUri = this.config.redirectUri; + var that = this; + var pollTimer = window.setInterval(function () { + if (!popupWindow || popupWindow.closed || popupWindow.closed === undefined) { + that._loginInProgress = false; + window.clearInterval(pollTimer); + } + try { + if (popupWindow.location.href.indexOf(registeredRedirectUri) != -1) { + if (that.isAngular) { + window.location.hash = popupWindow.location.hash; + } + else { + that.handleWindowCallback(popupWindow.location.hash); + } + window.clearInterval(pollTimer); + that._loginInProgress = false; + that.info("Closing popup window"); + popupWindow.close(); + } + } catch (e) { + } + }, 20); }; AuthenticationContext.prototype.loginInProgress = function () { @@ -278,7 +359,6 @@ var AuthenticationContext = (function () { 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, ''); @@ -491,11 +571,9 @@ var AuthenticationContext = (function () { throw new Error('callback is not a function'); } - this.callback = callback; - // user in memory if (this._user) { - this.callback(null, this._user); + callback(null, this._user); return; } @@ -504,10 +582,10 @@ var AuthenticationContext = (function () { if (!this._isEmpty(idtoken)) { this.info('User exists in cache: '); this._user = this._createUser(idtoken); - this.callback(null, this._user); + callback(null, this._user); } else { this.warn('User information is not available'); - this.callback('User information is not available'); + callback('User information is not available'); } }; @@ -790,10 +868,11 @@ var AuthenticationContext = (function () { }; /*exported oauth2Callback */ - AuthenticationContext.prototype.handleWindowCallback = function () { + AuthenticationContext.prototype.handleWindowCallback = function (hash) { // This is for regular javascript usage for redirect handling // need to make sure this is for callback - var hash = window.location.hash; + if (hash == null) + hash = window.location.hash; if (this.isCallback(hash)) { var requestInfo = this.getRequestInfo(hash); this.info('Returned from redirect url'); @@ -806,11 +885,13 @@ var AuthenticationContext = (function () { 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'); + } else if (requestInfo.requestType === this.REQUEST_TYPE.LOGIN) { callback = this.callback; + if (callback) + callback(this._getItem(this.CONSTANTS.STORAGE.ERROR_DESCRIPTION), requestInfo.parameters[this.CONSTANTS.ID_TOKEN]); } - window.location = this._getItem(this.CONSTANTS.STORAGE.LOGIN_REQUEST); + if (!this.popUp)// No need to redirect user in case of popup + window.location = this._getItem(this.CONSTANTS.STORAGE.LOGIN_REQUEST); } }; @@ -1001,29 +1082,49 @@ var AuthenticationContext = (function () { // 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; + var cryptoObj = window.crypto || window.msCrypto; // for IE 11 + if (cryptoObj && cryptoObj.getRandomValues) { + var buffer = new Uint8Array(16); + cryptoObj.getRandomValues(buffer); + //buffer[6] and buffer[7] represents the time_hi_and_version field. We will set the four most significant bits (4 through 7) of buffer[6] to represent decimal number 4 (UUID version number). + buffer[6] |= 0x40; //buffer[6] | 01000000 will set the 6 bit to 1. + buffer[6] &= 0x4f; //buffer[6] & 01001111 will set the 4, 5, and 7 bit to 0 such that bits 4-7 == 0100 = "4". + //buffer[8] represents the clock_seq_hi_and_reserved field. We will set the two most significant bits (6 and 7) of the clock_seq_hi_and_reserved to zero and one, respectively. + buffer[8] |= 0x80; //buffer[8] | 10000000 will set the 7 bit to 1. + buffer[8] &= 0xbf; //buffer[8] & 10111111 will set the 6 bit to 0. + function decimalToHex(num) { + var hex = num.toString(16); + while (hex.length < 2) { + hex = '0' + hex; + } + return hex; } - - 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 decimalToHex(buffer[0]) + decimalToHex(buffer[1]) + decimalToHex(buffer[2]) + decimalToHex(buffer[3]) + '-' + decimalToHex(buffer[4]) + decimalToHex(buffer[5]) + '-' + decimalToHex(buffer[6]) + decimalToHex(buffer[7]) + '-' + + decimalToHex(buffer[8]) + decimalToHex(buffer[9]) + '-' + decimalToHex(buffer[10]) + decimalToHex(buffer[11]) + decimalToHex(buffer[12]) + decimalToHex(buffer[13]) + decimalToHex(buffer[14]) + decimalToHex(buffer[15]); + } + else { + var guidHolder = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'; + var hex = '0123456789abcdef'; + var r = 0; + var guidResponse = ""; + for (var i = 0; i < 36; i++) { + if (guidHolder[i] !== '-' && guidHolder[i] !== '4') { + // each x and y needs to be random + r = Math.random() * 16 | 0; + } + if (guidHolder[i] === 'x') { + guidResponse += hex[r]; + } else if (guidHolder[i] === 'y') { + // clock-seq-and-reserved first hex is filtered and remaining hex values are random + r &= 0x3; // bit and with 0011 to set pos 2 to zero ?0?? + r |= 0x8; // set pos 3 to 1 as 1??? + guidResponse += hex[r]; + } else { + guidResponse += guidHolder[i]; + } } + return guidResponse; } - - return guidResponse; }; /* jshint ignore:end */ @@ -1182,7 +1283,7 @@ var AuthenticationContext = (function () { }; AuthenticationContext.prototype._libVersion = function () { - return '1.0.11'; + return '1.0.12'; }; if (typeof module !== 'undefined' && module.exports) { @@ -1195,3 +1296,4 @@ var AuthenticationContext = (function () { return AuthenticationContext; }()); + diff --git a/package.json b/package.json index d8b615e3..7e61824e 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.11", + "version": "1.0.12", "description": "Windows Azure Active Directory Client Library for js", "keywords": [ "implicit", diff --git a/tests/angularModuleSpec.js b/tests/angularModuleSpec.js index 9204ac17..dd2456fb 100644 --- a/tests/angularModuleSpec.js +++ b/tests/angularModuleSpec.js @@ -397,3 +397,62 @@ describe('TaskCtl', function () { expect(Logging.level).toEqual(2); }); }); + +describe('StateCtrl', function () { + var $httpBackend, adalServiceProvider, rootScope, $state, location, $templateCache, $stateParams; + + //mock Application to allow us to inject our own dependencies + beforeEach(angular.mock.module('StateApplication')); + + //mock the controller for the same reason and include $scope and $controller + beforeEach(angular.mock.inject(function (_adalAuthenticationService_, _$rootScope_, _$httpBackend_, _$state_, _$location_, _$templateCache_, _$stateParams_) { + adalServiceProvider = _adalAuthenticationService_; + rootScope = _$rootScope_; + $httpBackend = _$httpBackend_; + $state = _$state_; + location = _$location_; + $templateCache = _$templateCache_; + $stateParams = _$stateParams_; + $httpBackend.expectGET('settings.html').respond(200); + $httpBackend.expectGET('profile.html').respond(200); + $httpBackend.expectGET('name.html').respond(200); + $httpBackend.expectGET('account.html').respond(200); + $templateCache.put('profile.html', ''); + $templateCache.put('settings.html', ''); + $templateCache.put('account.html', ''); + $templateCache.put('name.html', ''); + adalServiceProvider.config.anonymousEndpoints = []; + })); + + it('checks if anonymous endpoints are populated on statechange event if states are nested and separated by .', function () { + var state; + rootScope.$on('$stateChangeSuccess', function (event, toState) { + state = toState; + }); + var urlNavigate = 'settings/profile/name'; + location.url(urlNavigate); + rootScope.$digest(); + expect(state.name).toEqual('settings.profile.name'); + var states = urlNavigate.split('/'); + for (var i = 0; i < states.length; i++) { + expect(adalServiceProvider.config.anonymousEndpoints[i]).toEqual(states[i] + '.html'); + } + }); + + it('checks if state is resolved when templateUrl is a function which depends on stateParams and states have parent property', function () { + var state; + rootScope.$on('$stateChangeSuccess', function (event, toState) { + state = toState; + }); + var urlNavigate = 'settings/account/Id/testId/name/Name/testName'; + location.url(urlNavigate); + rootScope.$digest(); + expect($stateParams.accountId).toEqual('testId'); + expect($stateParams.accountName).toEqual('testName'); + expect(state.name).toEqual('settings.account.name'); + var states = state.name.split('.'); + for (var i = 0; i < states.length ; i++) { + expect(adalServiceProvider.config.anonymousEndpoints[i]).toEqual(states[i] + '.html'); + } + }); +}); diff --git a/tests/browser.manual.runner.html b/tests/browser.manual.runner.html index 49c2f78a..91d5bc6a 100644 --- a/tests/browser.manual.runner.html +++ b/tests/browser.manual.runner.html @@ -8,11 +8,13 @@ + + diff --git a/tests/stateApp.js b/tests/stateApp.js new file mode 100644 index 00000000..c593ded4 --- /dev/null +++ b/tests/stateApp.js @@ -0,0 +1,76 @@ +//---------------------------------------------------------------------- +// @preserve Copyright (c) Microsoft Open Technologies, Inc. +// All Rights Reserved +// Apache License 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +//---------------------------------------------------------------------- + +'use strict'; +// Test app Ui-Router +var stateApp = angular.module("StateApplication", ['ui.router', 'AdalAngular']) +.config(['$stateProvider', '$urlRouterProvider', '$httpProvider', 'adalAuthenticationServiceProvider', function ($stateProvider, $urlRouterProvider, $httpProvider, adalProvider) { + $stateProvider + .state('settings', { + url: '/settings', + templateUrl: 'settings.html' + }) + .state('settings.profile', { + url: '/profile', + templateUrl: 'profile.html', + }) + .state('settings.profile.name', { + url: '/name', + templateUrl: 'name.html', + }) + .state('settings.profile.email', { + url: '/email', + templateUrl: 'email.html', + }) + .state('settings.account', { + parent: 'settings', + url: '/account/Id/:accountId', + templateUrl: function (stateParams) { + if (stateParams.accountId == 'testId') + return 'account.html'; + }, + }) + .state('settings.account.name', { + parent: 'settings.account', + url: '/name/Name/:accountName', + templateUrl: function (stateParams) { + if (stateParams.accountName == 'testName') + return 'name.html'; + } + }) + .state('settings.account.email', { + url: '/email', + templateUrl: 'email.html', + }); + + $urlRouterProvider.otherwise('/settings'); + var endpoints = {}; + + adalProvider.init( + { + tenant: 'tenantid123', + clientId: 'clientid123', + loginResource: 'loginResource123', + redirectUri: 'https://myapp.com/page', + endpoints: endpoints // optional + }, + $httpProvider // pass http provider to inject request interceptor to attach tokens + ); +}]); + +app.controller('StateCtrl', ['$scope', '$location', 'adalAuthenticationService', function ($scope, $location, adalAuthenticationService) { }]); diff --git a/tests/unit/spec/AdalSpec.js b/tests/unit/spec/AdalSpec.js index fefdcd89..bf4e7a99 100644 --- a/tests/unit/spec/AdalSpec.js +++ b/tests/unit/spec/AdalSpec.js @@ -36,7 +36,9 @@ describe('Adal', function () { }, localStorage: {}, sessionStorage: {}, - atob: atobHelper + atob: atobHelper, + innerWidth: 100, + innerHeight: 100 }; var mathMock = { random: function () { @@ -69,7 +71,7 @@ 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 STATE = '33333333-3333-4333-b333-333333333333'; 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; @@ -106,7 +108,6 @@ describe('Adal', function () { window.localStorage = storageFake; window.sessionStorage = storageFake; - // Init adal global.window = window; @@ -182,7 +183,7 @@ describe('Adal', function () { it('calls displaycall if given for login', function () { storageFake.setItem(adal.CONSTANTS.STORAGE.USERNAME, 'test user'); - + adal._loginInProgress = false; adal.config.clientId = 'client'; adal.config.redirectUri = 'contoso_site'; var urlToGo = ''; @@ -235,9 +236,9 @@ describe('Adal', function () { token = valToken; }; adal._renewStates = []; - adal._user = { profile: { 'upn': 'test@testuser.com' }, userName: 'test@domain.com'}; + adal._user = { profile: { 'upn': 'test@testuser.com' }, userName: 'test@domain.com' }; adal.acquireToken(RESOURCE1, callback); - expect(adal.callback).toBe(callback); + expect(adal.callback).toBe(null); expect(storageFake.getItem(adal.CONSTANTS.STORAGE.LOGIN_REQUEST)).toBe(''); expect(adal._renewStates.length).toBe(1); // Wait for initial timeout load @@ -718,12 +719,13 @@ describe('Adal', function () { 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'); + + '&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; + var _getRequestInfo = adal.getRequestInfo; adal.getRequestInfo = function (hash) { return { valid: true, @@ -745,12 +747,14 @@ describe('Adal', function () { adal.handleWindowCallback(); expect(err).toBe('error description'); expect(token).toBe(IDTOKEN_MOCK); + adal.getRequestInfo = _getRequestInfo; }); it('tests handleWindowCallback function for LOGIN_REQUEST', function () { window.location = {}; window.location.hash = '#/id_token=' + IDTOKEN_MOCK; + var _getRequestInfo = adal.getRequestInfo; adal.getRequestInfo = function () { return { valid: true, @@ -764,6 +768,7 @@ describe('Adal', function () { window.oauth2Callback = {}; adal.handleWindowCallback(); expect(window.location).toBe('www.test.com'); + adal.getRequestInfo = _getRequestInfo; }); @@ -830,20 +835,20 @@ describe('Adal', function () { 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) { + 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; } obj = adal._deserialize(INVALID_URLFRAGMENT); expect(obj['id_token' + IDTOKEN_MOCK]).toBe('');//This additional property is parsed because of ? operator in regex @@ -852,6 +857,105 @@ describe('Adal', function () { expect(obj.session_state).toBe(SESSION_STATE); adal._deserialize = deserialize;//reassign state to original function }); + + it('tests if callback is called after login, if popup window is null', function () { + adal.popUp = true; + adal.config.clientId = 'client'; + adal.config.redirectUri = 'contoso_site'; + var err; + var token; + var callback = function (valErr, valToken) { + err = valErr; + token = valToken; + }; + window.open = function () { + return null; + } + adal.callback = callback; + adal.login(); + expect(err).toBe('Popup Window is null. This can happen if you are using IE'); + expect(token).toBe(null); + expect(adal.loginInProgress()).toBe(false); + }); + + it('tests login functionality in case of popup window', function () { + var timercallback; + window.clearInterval = function () { + }; + window.setInterval = function (method, timer) { + timercallback = method; + }; + adal.popUp = true; + adal.config.clientId = 'client'; + adal.config.redirectUri = 'contoso_site'; + var popupWindow; + window.open = function () { + popupWindow = { + location: { + hash: VALID_URLFRAGMENT, + href: 'hrefcontoso_site' + }, + closed: false, + close: function () { + this.closed = true; + } + }; + return popupWindow; + }; + var err; + var token; + var callback = function (valErr, valToken) { + err = valErr; + token = valToken; + }; + adal.callback = callback; + mathMock.random = function () { + return 0.2; + }; + adal.login(); + waitsFor(function () { + timercallback(); + storageFake.setItem(adal.CONSTANTS.STORAGE.LOGIN_REQUEST, 'home page'); + return popupWindow.closed == true; + }, 'error closing popup window', 2000); + + runs(function () { + expect(adal.loginInProgress()).toBe(false); + expect(token).toBe(IDTOKEN_MOCK); + expect(window.location.href).not.toBe('home page'); + }); + + }); + + it('ensures that adal.callback is not overridden in calls to getUser', function () { + var _callback = adal.callback; + adal.callback = null; + var err = ''; + var user = {}; + var callback = function (valErr, valResult) { + err = valErr; + user = valResult; + }; + adal._user = { profile: { 'upn': 'test@testuser.com' }, userName: 'test@domain.com' }; + adal.getUser(callback); + expect(user).toBe(adal._user); + expect(adal.callback).toBe(null); + adal.callback = _callback; + }); + + it('tests _guid function if window.crypto is defined in the browser', function () { + var buffer = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]; + window.msCrypto = null; + window.crypto = { + getRandomValues: function (_buffer) { + for (var i = 0; i < _buffer.length; i++) { + _buffer[i] = buffer[i]; + } + } + }; + expect(adal._guid()).toBe('00010203-0405-4607-8809-0a0b0c0d0e0f'); + window.crypto = null; + }); // TODO angular intercepptor // TODO angular authenticationService });