From fd90d425e9944cec198f0bf2a641bd5e94908548 Mon Sep 17 00:00:00 2001 From: Roger Xu Date: Tue, 10 Apr 2018 12:39:54 -0400 Subject: [PATCH] add webstorage support check --- angular-acl.js | 685 +++++++++++++++++++++++-------------------- angular-acl.min.js | 2 +- src/angular-acl.js | 711 ++++++++++++++++++++++++--------------------- 3 files changed, 740 insertions(+), 658 deletions(-) diff --git a/angular-acl.js b/angular-acl.js index 6eee9fb..781af69 100644 --- a/angular-acl.js +++ b/angular-acl.js @@ -4,336 +4,377 @@ var NG_HIDE_CLASS = 'ng-hide'; angular.module('mm.acl', []); -angular.module('mm.acl').provider('AclService', [ - function () { - - /** - * Polyfill for IE8 - * - * http://stackoverflow.com/a/1181586 - */ - if (!Array.prototype.indexOf) { - Array.prototype.indexOf = function (needle) { - var l = this.length; - for (; l--;) { - if (this[l] === needle) { - return l; - } +angular.module('mm.acl').provider('AclService', ['$windowProvider', + function ($windowProvider) { + + /** + * Polyfill for IE8 + * + * http://stackoverflow.com/a/1181586 + */ + if (!Array.prototype.indexOf) { + Array.prototype.indexOf = function (needle) { + var l = this.length; + for (; l--;) { + if (this[l] === needle) { + return l; + } + } + return -1; + }; } - return -1; - }; - } - - var config = { - storage: 'sessionStorage', - storageKey: 'AclService' - }; - - var data = { - roles: [], - abilities: {} - }; - - /** - * Does the given role have abilities granted to it? - * - * @param role - * @returns {boolean} - */ - var roleHasAbilities = function (role) { - return (typeof data.abilities[role] === 'object'); - }; - - /** - * Retrieve the abilities array for the given role - * - * @param role - * @returns {Array} - */ - var getRoleAbilities = function (role) { - return (roleHasAbilities(role)) ? data.abilities[role] : []; - }; - - /** - * Persist data to storage based on config - */ - var save = function () { - switch (config.storage) { - case 'sessionStorage': - saveToStorage('sessionStorage'); - break; - case 'localStorage': - saveToStorage('localStorage'); - break; - default: - // Don't save - return; - } - }; - - var unset = function () { - switch (config.storage) { - case 'sessionStorage': - unsetFromStorage('sessionStorage'); - break; - case 'localStorage': - unsetFromStorage('localStorage'); - break; - default: - // Don't save - return; - } - }; - - /** - * Persist data to web storage - */ - var saveToStorage = function (storagetype) { - window[storagetype].setItem(config.storageKey, JSON.stringify(data)); - }; - - /** - * Unset data from web storage - */ - var unsetFromStorage = function (storagetype) { - window[storagetype].removeItem(config.storageKey); - }; - - /** - * Retrieve data from web storage - */ - var fetchFromStorage = function (storagetype) { - var data = window[storagetype].getItem(config.storageKey); - return (data) ? JSON.parse(data) : false; - }; - - var AclService = {}; - AclService.resume = resume; - - - /** - * Restore data from web storage. - * - * Returns true if web storage exists and false if it doesn't. - * - * @returns {boolean} - */ - function resume() { - var storedData; - - switch (config.storage) { - case 'sessionStorage': - storedData = fetchFromStorage('sessionStorage'); - break; - case 'localStorage': - storedData = fetchFromStorage('localStorage'); - break; - default: - storedData = null; - } - if (storedData) { - angular.extend(data, storedData); - return true; - } - - return false; - } - - /** - * Remove data from web storage - */ - AclService.flushStorage = function () { - unset(); - }; - - /** - * Attach a role to the current user - * - * @param role - */ - AclService.attachRole = function (role) { - if (data.roles.indexOf(role) === -1) { - data.roles.push(role); - save(); - } - }; - - /** - * Remove role from current user - * - * @param role - */ - AclService.detachRole = function (role) { - var i = data.roles.indexOf(role); - if (i > -1) { - data.roles.splice(i, 1); - save(); - } - }; - - /** - * Remove all roles from current user - */ - AclService.flushRoles = function () { - data.roles = []; - save(); - }; - - /** - * Check if the current user has role(s) attached - * - * @param role - * @returns {boolean} - */ - AclService.hasRole = function (role) { - var roles = angular.isArray(role) ? role : [role]; - for (var l = roles.length; l--;) { - if (data.roles.indexOf(roles[l]) === -1) { - return false; + var $window = $windowProvider.$get(); + + var isStorageSupported = function($window, storageType){ + + // Some installations of IE, for an unknown reason, throw "SCRIPT5: Error: Access is denied" + // when accessing window.localStorage. This happens before you try to do anything with it. Catch + // that error and allow execution to continue. + + // fix 'SecurityError: DOM Exception 18' exception in Desktop Safari, Mobile Safari + // when "Block cookies": "Always block" is turned on + var supported; + try { + supported = $window[storageType]; + } + catch(err) { + supported = false; + } + + // When Safari (OS X or iOS) is in private browsing mode, it appears as + // though localStorage and sessionStorage is available, + // but trying to call .setItem throws an exception below: + // "QUOTA_EXCEEDED_ERR: DOM Exception 22: An attempt + // was made to add something to storage that exceeded the quota." + + if(supported) { + var key = '__' + Math.round(Math.random() * 1e7); + try { + $window[storageType].setItem(key, key); + $window[storageType].removeItem(key, key); + } + catch(err) { + supported = false; + } + } + + return supported; + }; + + var config = { + storage: 'sessionStorage', + storageKey: 'AclService' + }; + + var data = { + roles: [], + abilities: {} + }; + + /** + * Does the given role have abilities granted to it? + * + * @param role + * @returns {boolean} + */ + var roleHasAbilities = function (role) { + return (typeof data.abilities[role] === 'object'); + }; + + /** + * Retrieve the abilities array for the given role + * + * @param role + * @returns {Array} + */ + var getRoleAbilities = function (role) { + return (roleHasAbilities(role)) ? data.abilities[role] : []; + }; + + /** + * Persist data to storage based on config + */ + var save = function () { + switch (config.storage) { + case 'sessionStorage': + saveToStorage('sessionStorage'); + break; + case 'localStorage': + saveToStorage('localStorage'); + break; + default: + // Don't save + return; + } + }; + + var unset = function () { + switch (config.storage) { + case 'sessionStorage': + unsetFromStorage('sessionStorage'); + break; + case 'localStorage': + unsetFromStorage('localStorage'); + break; + default: + // Don't save + return; + } + }; + + /** + * Persist data to web storage + */ + var saveToStorage = function (storagetype) { + isStorageSupported($window, config.storage) && + window[storagetype].setItem(config.storageKey, JSON.stringify(data)); + }; + + /** + * Unset data from web storage + */ + var unsetFromStorage = function (storagetype) { + isStorageSupported($window, config.storage) && window[storagetype].removeItem(config.storageKey); + }; + + /** + * Retrieve data from web storage + */ + var fetchFromStorage = function (storagetype) { + if(!isStorageSupported($window, config.storage)) { + return false; + } + var data = window[storagetype].getItem(config.storageKey); + return (data) ? JSON.parse(data) : false; + }; + + var AclService = {}; + AclService.resume = resume; + + + /** + * Restore data from web storage. + * + * Returns true if web storage exists and false if it doesn't. + * + * @returns {boolean} + */ + function resume() { + var storedData; + + switch (config.storage) { + case 'sessionStorage': + storedData = fetchFromStorage('sessionStorage'); + break; + case 'localStorage': + storedData = fetchFromStorage('localStorage'); + break; + default: + storedData = null; + } + if (storedData) { + angular.extend(data, storedData); + return true; + } + + return false; } - } - return !!roles.length; - }; - - /** - * Check if the current user any of the given roles - * - * @param roles - * @returns {boolean} - */ - AclService.hasAnyRole = function (roles) { - for (var l = roles.length; l--;) { - if (AclService.hasRole(roles[l])) { - return true; - } - } - return false; - }; - /** - * Returns the current user roles - * @returns {Array} - */ - AclService.getRoles = function () { - return data.roles; - }; - - /** - * Set the abilities object (overwriting previous abilities) - * - * Each property on the abilities object should be a role. - * Each role should have a value of an array. The array should contain - * a list of all of the roles abilities. - * - * Example: - * - * { + /** + * Remove data from web storage + */ + AclService.flushStorage = function () { + isStorageSupported($window, config.storage) && unset(); + }; + + /** + * Attach a role to the current user + * + * @param role + */ + AclService.attachRole = function (role) { + if (data.roles.indexOf(role) === -1) { + data.roles.push(role); + isStorageSupported($window, config.storage) && save(); + } + }; + + /** + * Remove role from current user + * + * @param role + */ + AclService.detachRole = function (role) { + var i = data.roles.indexOf(role); + if (i > -1) { + data.roles.splice(i, 1); + isStorageSupported($window, config.storage) && save(); + } + }; + + /** + * Remove all roles from current user + */ + AclService.flushRoles = function () { + data.roles = []; + isStorageSupported($window, config.storage) && save(); + }; + + /** + * Check if the current user has role(s) attached + * + * @param role + * @returns {boolean} + */ + AclService.hasRole = function (role) { + var roles = angular.isArray(role) ? role : [role]; + for (var l = roles.length; l--;) { + if (data.roles.indexOf(roles[l]) === -1) { + return false; + } + } + return !!roles.length; + }; + + /** + * Check if the current user any of the given roles + * + * @param roles + * @returns {boolean} + */ + AclService.hasAnyRole = function (roles) { + for (var l = roles.length; l--;) { + if (AclService.hasRole(roles[l])) { + return true; + } + } + return false; + }; + + /** + * Returns the current user roles + * @returns {Array} + */ + AclService.getRoles = function () { + return data.roles; + }; + + /** + * Set the abilities object (overwriting previous abilities) + * + * Each property on the abilities object should be a role. + * Each role should have a value of an array. The array should contain + * a list of all of the roles abilities. + * + * Example: + * + * { * guest: ['login'], * user: ['logout', 'view_content'], * admin: ['logout', 'view_content', 'manage_users'] * } - * - * @param abilities - */ - AclService.setAbilities = function (abilities) { - data.abilities = abilities; - save(); - }; - - /** - * Add an ability to a role - * - * @param role - * @param ability - */ - AclService.addAbility = function (role, ability) { - if (!data.abilities[role]) { - data.abilities[role] = []; - } - data.abilities[role].push(ability); - save(); - }; - - /** - * Does current user have permission to do something? - * - * @param ability - * @returns {boolean} - */ - AclService.can = function (ability) { - var role, abilities; - // Loop through roles - var l = data.roles.length; - for (; l--;) { - // Grab the the current role - role = data.roles[l]; - abilities = getRoleAbilities(role); - if (abilities.indexOf(ability) > -1) { - // Ability is in role abilities - return true; - } - } - // We made it here, so the ability wasn't found in attached roles - return false; - }; + * + * @param abilities + */ + AclService.setAbilities = function (abilities) { + data.abilities = abilities; + isStorageSupported($window, config.storage) && save(); + }; + + /** + * Add an ability to a role + * + * @param role + * @param ability + */ + AclService.addAbility = function (role, ability) { + if (!data.abilities[role]) { + data.abilities[role] = []; + } + data.abilities[role].push(ability); + isStorageSupported($window, config.storage) && save(); + }; + + /** + * Does current user have permission to do something? + * + * @param ability + * @returns {boolean} + */ + AclService.can = function (ability) { + var role, abilities; + // Loop through roles + var l = data.roles.length; + for (; l--;) { + // Grab the the current role + role = data.roles[l]; + abilities = getRoleAbilities(role); + if (abilities.indexOf(ability) > -1) { + // Ability is in role abilities + return true; + } + } + // We made it here, so the ability wasn't found in attached roles + return false; + }; + + /** + * Does current user have any of the required permission to do something? + * + * @param abilities [array] + * @returns {boolean} + */ + AclService.canAny = function (abilities) { + var role, roleAbilities; + // Loop through roles + var l = data.roles.length; + var j = abilities.length; + + for (; l--;) { + // Grab the the current role + role = data.roles[l]; + roleAbilities = getRoleAbilities(role); + + for (; j--;) { + if (roleAbilities.indexOf(abilities[j]) > -1) { + // Ability is in role abilities + return true; + } + } + } + // We made it here, so the ability wasn't found in attached roles + return false; + }; + + return { + config: function (userConfig) { + angular.extend(config, userConfig); + }, + resume: resume, + $get: function () { + return AclService; + } + }; - /** - * Does current user have any of the required permission to do something? - * - * @param abilities [array] - * @returns {boolean} - */ - AclService.canAny = function (abilities) { - var role, roleAbilities; - // Loop through roles - var l = data.roles.length; - var j = abilities.length; - - for (; l--;) { - // Grab the the current role - role = data.roles[l]; - roleAbilities = getRoleAbilities(role); - - for (; j--;) { - if (roleAbilities.indexOf(abilities[j]) > -1) { - // Ability is in role abilities - return true; - } - } - } - // We made it here, so the ability wasn't found in attached roles - return false; - }; - - return { - config: function (userConfig) { - angular.extend(config, userConfig); - }, - resume: resume, - $get: function () { - return AclService; - } - }; - - } + } ]).directive('aclShow', function (AclService) { - return { - restrict: 'A', - link: function (scope, element, attrs) { - scope.$watch(attrs.aclShow, function aclShowWatchAction(value) { - var permissions, can; - if (!value) { - element.addClass(NG_HIDE_CLASS); - return; - } - permissions = value.split(','); - can = AclService.canAny(permissions); - if (!can) { - element.addClass(NG_HIDE_CLASS); - } else { - element.removeClass(NG_HIDE_CLASS); + return { + restrict: 'A', + link: function (scope, element, attrs) { + scope.$watch(attrs.aclShow, function aclShowWatchAction(value) { + var permissions, can; + if (!value) { + element.addClass(NG_HIDE_CLASS); + return; + } + permissions = value.split(','); + can = AclService.canAny(permissions); + if (!can) { + element.addClass(NG_HIDE_CLASS); + } else { + element.removeClass(NG_HIDE_CLASS); + } + }); } - }); - } - }; + }; }); diff --git a/angular-acl.min.js b/angular-acl.min.js index c453307..a4bed2e 100644 --- a/angular-acl.min.js +++ b/angular-acl.min.js @@ -1 +1 @@ -"use strict";var NG_HIDE_CLASS="ng-hide";angular.module("mm.acl",[]),angular.module("mm.acl").provider("AclService",[function(){function a(){var a;switch(b.storage){case"sessionStorage":a=j("sessionStorage");break;case"localStorage":a=j("localStorage");break;default:a=null}return!!a&&(angular.extend(c,a),!0)}Array.prototype.indexOf||(Array.prototype.indexOf=function(a){for(var b=this.length;b--;)if(this[b]===a)return b;return-1});var b={storage:"sessionStorage",storageKey:"AclService"},c={roles:[],abilities:{}},d=function(a){return"object"==typeof c.abilities[a]},e=function(a){return d(a)?c.abilities[a]:[]},f=function(){switch(b.storage){case"sessionStorage":h("sessionStorage");break;case"localStorage":h("localStorage");break;default:return}},g=function(){switch(b.storage){case"sessionStorage":i("sessionStorage");break;case"localStorage":i("localStorage");break;default:return}},h=function(a){window[a].setItem(b.storageKey,JSON.stringify(c))},i=function(a){window[a].removeItem(b.storageKey)},j=function(a){var c=window[a].getItem(b.storageKey);return!!c&&JSON.parse(c)},k={};return k.resume=a,k.flushStorage=function(){g()},k.attachRole=function(a){c.roles.indexOf(a)===-1&&(c.roles.push(a),f())},k.detachRole=function(a){var b=c.roles.indexOf(a);b>-1&&(c.roles.splice(b,1),f())},k.flushRoles=function(){c.roles=[],f()},k.hasRole=function(a){for(var b=angular.isArray(a)?a:[a],d=b.length;d--;)if(c.roles.indexOf(b[d])===-1)return!1;return!!b.length},k.hasAnyRole=function(a){for(var b=a.length;b--;)if(k.hasRole(a[b]))return!0;return!1},k.getRoles=function(){return c.roles},k.setAbilities=function(a){c.abilities=a,f()},k.addAbility=function(a,b){c.abilities[a]||(c.abilities[a]=[]),c.abilities[a].push(b),f()},k.can=function(a){for(var b,d,f=c.roles.length;f--;)if(b=c.roles[f],d=e(b),d.indexOf(a)>-1)return!0;return!1},k.canAny=function(a){for(var b,d,f=c.roles.length,g=a.length;f--;)for(b=c.roles[f],d=e(b);g--;)if(d.indexOf(a[g])>-1)return!0;return!1},{config:function(a){angular.extend(b,a)},resume:a,$get:function(){return k}}}]).directive("aclShow",function(a){return{restrict:"A",link:function(b,c,d){b.$watch(d.aclShow,function(b){var d,e;if(!b)return void c.addClass("ng-hide");d=b.split(","),e=a.canAny(d),e?c.removeClass("ng-hide"):c.addClass("ng-hide")})}}}); \ No newline at end of file +"use strict";var NG_HIDE_CLASS="ng-hide";angular.module("mm.acl",[]),angular.module("mm.acl").provider("AclService",["$windowProvider",function(a){function b(){var a;switch(e.storage){case"sessionStorage":a=m("sessionStorage");break;case"localStorage":a=m("localStorage");break;default:a=null}return!!a&&(angular.extend(f,a),!0)}Array.prototype.indexOf||(Array.prototype.indexOf=function(a){for(var b=this.length;b--;)if(this[b]===a)return b;return-1});var c=a.$get(),d=function(a,b){var c;try{c=a[b]}catch(a){c=!1}if(c){var d="__"+Math.round(1e7*Math.random());try{a[b].setItem(d,d),a[b].removeItem(d,d)}catch(a){c=!1}}return c},e={storage:"sessionStorage",storageKey:"AclService"},f={roles:[],abilities:{}},g=function(a){return"object"==typeof f.abilities[a]},h=function(a){return g(a)?f.abilities[a]:[]},i=function(){switch(e.storage){case"sessionStorage":k("sessionStorage");break;case"localStorage":k("localStorage");break;default:return}},j=function(){switch(e.storage){case"sessionStorage":l("sessionStorage");break;case"localStorage":l("localStorage");break;default:return}},k=function(a){d(c,e.storage)&&window[a].setItem(e.storageKey,JSON.stringify(f))},l=function(a){d(c,e.storage)&&window[a].removeItem(e.storageKey)},m=function(a){if(!d(c,e.storage))return!1;var b=window[a].getItem(e.storageKey);return!!b&&JSON.parse(b)},n={};return n.resume=b,n.flushStorage=function(){d(c,e.storage)&&j()},n.attachRole=function(a){-1===f.roles.indexOf(a)&&(f.roles.push(a),d(c,e.storage)&&i())},n.detachRole=function(a){var b=f.roles.indexOf(a);b>-1&&(f.roles.splice(b,1),d(c,e.storage)&&i())},n.flushRoles=function(){f.roles=[],d(c,e.storage)&&i()},n.hasRole=function(a){for(var b=angular.isArray(a)?a:[a],c=b.length;c--;)if(-1===f.roles.indexOf(b[c]))return!1;return!!b.length},n.hasAnyRole=function(a){for(var b=a.length;b--;)if(n.hasRole(a[b]))return!0;return!1},n.getRoles=function(){return f.roles},n.setAbilities=function(a){f.abilities=a,d(c,e.storage)&&i()},n.addAbility=function(a,b){f.abilities[a]||(f.abilities[a]=[]),f.abilities[a].push(b),d(c,e.storage)&&i()},n.can=function(a){for(var b,c,d=f.roles.length;d--;)if(b=f.roles[d],c=h(b),c.indexOf(a)>-1)return!0;return!1},n.canAny=function(a){for(var b,c,d=f.roles.length,e=a.length;d--;)for(b=f.roles[d],c=h(b);e--;)if(c.indexOf(a[e])>-1)return!0;return!1},{config:function(a){angular.extend(e,a)},resume:b,$get:function(){return n}}}]).directive("aclShow",function(a){return{restrict:"A",link:function(b,c,d){b.$watch(d.aclShow,function(b){var d,e;if(!b)return void c.addClass(NG_HIDE_CLASS);d=b.split(","),e=a.canAny(d),e?c.removeClass(NG_HIDE_CLASS):c.addClass(NG_HIDE_CLASS)})}}}); \ No newline at end of file diff --git a/src/angular-acl.js b/src/angular-acl.js index 5f3ea58..1d6bcc7 100644 --- a/src/angular-acl.js +++ b/src/angular-acl.js @@ -4,349 +4,390 @@ var NG_HIDE_CLASS = 'ng-hide'; angular.module('mm.acl', []); -angular.module('mm.acl').provider('AclService', [ - function () { - - /** - * Polyfill for IE8 - * - * http://stackoverflow.com/a/1181586 - */ - if (!Array.prototype.indexOf) { - Array.prototype.indexOf = function (needle) { - var l = this.length; - for (; l--;) { - if (this[l] === needle) { - return l; - } +angular.module('mm.acl').provider('AclService', ['$windowProvider', + function ($windowProvider) { + + /** + * Polyfill for IE8 + * + * http://stackoverflow.com/a/1181586 + */ + if (!Array.prototype.indexOf) { + Array.prototype.indexOf = function (needle) { + var l = this.length; + for (; l--;) { + if (this[l] === needle) { + return l; + } + } + return -1; + }; } - return -1; - }; - } - - var config = { - storage: 'sessionStorage', - storageKey: 'AclService' - }; - - var data = { - roles: [], - abilities: {} - }; - - /** - * Does the given role have abilities granted to it? - * - * @param role - * @returns {boolean} - */ - var roleHasAbilities = function (role) { - return (typeof data.abilities[role] === 'object'); - }; - - /** - * Retrieve the abilities array for the given role - * - * @param role - * @returns {Array} - */ - var getRoleAbilities = function (role) { - return (roleHasAbilities(role)) ? data.abilities[role] : []; - }; - - /** - * Persist data to storage based on config - */ - var save = function () { - switch (config.storage) { - case 'sessionStorage': - saveToStorage('sessionStorage'); - break; - case 'localStorage': - saveToStorage('localStorage'); - break; - default: - // Don't save - return; - } - }; - - var unset = function () { - switch (config.storage) { - case 'sessionStorage': - unsetFromStorage('sessionStorage'); - break; - case 'localStorage': - unsetFromStorage('localStorage'); - break; - default: - // Don't save - return; - } - }; - - /** - * Persist data to web storage - */ - var saveToStorage = function (storagetype) { - window[storagetype].setItem(config.storageKey, JSON.stringify(data)); - }; - - /** - * Unset data from web storage - */ - var unsetFromStorage = function (storagetype) { - window[storagetype].removeItem(config.storageKey); - }; - - /** - * Retrieve data from web storage - */ - var fetchFromStorage = function (storagetype) { - var data = window[storagetype].getItem(config.storageKey); - return (data) ? JSON.parse(data) : false; - }; - - var AclService = {}; - AclService.resume = resume; - - /* start-test-block */ - - // Add debug annotations for unit testing private functions/variables, - // which will be stripped during the production build - AclService._config = config; - AclService._data = data; - AclService._roleHasAbilities = roleHasAbilities; - AclService._getRoleAbilities = getRoleAbilities; - AclService._save = save; - AclService._saveToStorage = saveToStorage; - AclService._fetchFromStorage = fetchFromStorage; - - /* end-test-block */ - - /** - * Restore data from web storage. - * - * Returns true if web storage exists and false if it doesn't. - * - * @returns {boolean} - */ - function resume() { - var storedData; - - switch (config.storage) { - case 'sessionStorage': - storedData = fetchFromStorage('sessionStorage'); - break; - case 'localStorage': - storedData = fetchFromStorage('localStorage'); - break; - default: - storedData = null; - } - if (storedData) { - angular.extend(data, storedData); - return true; - } - - return false; - } - - /** - * Remove data from web storage - */ - AclService.flushStorage = function () { - unset(); - }; - - /** - * Attach a role to the current user - * - * @param role - */ - AclService.attachRole = function (role) { - if (data.roles.indexOf(role) === -1) { - data.roles.push(role); - save(); - } - }; - - /** - * Remove role from current user - * - * @param role - */ - AclService.detachRole = function (role) { - var i = data.roles.indexOf(role); - if (i > -1) { - data.roles.splice(i, 1); - save(); - } - }; - - /** - * Remove all roles from current user - */ - AclService.flushRoles = function () { - data.roles = []; - save(); - }; - - /** - * Check if the current user has role(s) attached - * - * @param role - * @returns {boolean} - */ - AclService.hasRole = function (role) { - var roles = angular.isArray(role) ? role : [role]; - for (var l = roles.length; l--;) { - if (data.roles.indexOf(roles[l]) === -1) { - return false; + var $window = $windowProvider.$get(); + + var isStorageSupported = function($window, storageType){ + + // Some installations of IE, for an unknown reason, throw "SCRIPT5: Error: Access is denied" + // when accessing window.localStorage. This happens before you try to do anything with it. Catch + // that error and allow execution to continue. + + // fix 'SecurityError: DOM Exception 18' exception in Desktop Safari, Mobile Safari + // when "Block cookies": "Always block" is turned on + var supported; + try { + supported = $window[storageType]; + } + catch(err) { + supported = false; + } + + // When Safari (OS X or iOS) is in private browsing mode, it appears as + // though localStorage and sessionStorage is available, + // but trying to call .setItem throws an exception below: + // "QUOTA_EXCEEDED_ERR: DOM Exception 22: An attempt + // was made to add something to storage that exceeded the quota." + + if(supported) { + var key = '__' + Math.round(Math.random() * 1e7); + try { + $window[storageType].setItem(key, key); + $window[storageType].removeItem(key, key); + } + catch(err) { + supported = false; + } + } + + return supported; + }; + + var config = { + storage: 'sessionStorage', + storageKey: 'AclService' + }; + + var data = { + roles: [], + abilities: {} + }; + + /** + * Does the given role have abilities granted to it? + * + * @param role + * @returns {boolean} + */ + var roleHasAbilities = function (role) { + return (typeof data.abilities[role] === 'object'); + }; + + /** + * Retrieve the abilities array for the given role + * + * @param role + * @returns {Array} + */ + var getRoleAbilities = function (role) { + return (roleHasAbilities(role)) ? data.abilities[role] : []; + }; + + /** + * Persist data to storage based on config + */ + var save = function () { + switch (config.storage) { + case 'sessionStorage': + saveToStorage('sessionStorage'); + break; + case 'localStorage': + saveToStorage('localStorage'); + break; + default: + // Don't save + return; + } + }; + + var unset = function () { + switch (config.storage) { + case 'sessionStorage': + unsetFromStorage('sessionStorage'); + break; + case 'localStorage': + unsetFromStorage('localStorage'); + break; + default: + // Don't save + return; + } + }; + + /** + * Persist data to web storage + */ + var saveToStorage = function (storagetype) { + isStorageSupported($window, config.storage) && + window[storagetype].setItem(config.storageKey, JSON.stringify(data)); + }; + + /** + * Unset data from web storage + */ + var unsetFromStorage = function (storagetype) { + isStorageSupported($window, config.storage) && window[storagetype].removeItem(config.storageKey); + }; + + /** + * Retrieve data from web storage + */ + var fetchFromStorage = function (storagetype) { + if(!isStorageSupported($window, config.storage)) { + return false; + } + var data = window[storagetype].getItem(config.storageKey); + return (data) ? JSON.parse(data) : false; + }; + + var AclService = {}; + AclService.resume = resume; + + /* start-test-block */ + + // Add debug annotations for unit testing private functions/variables, + // which will be stripped during the production build + AclService._config = config; + AclService._data = data; + AclService._roleHasAbilities = roleHasAbilities; + AclService._getRoleAbilities = getRoleAbilities; + AclService._save = save; + AclService._saveToStorage = saveToStorage; + AclService._fetchFromStorage = fetchFromStorage; + + /* end-test-block */ + + /** + * Restore data from web storage. + * + * Returns true if web storage exists and false if it doesn't. + * + * @returns {boolean} + */ + function resume() { + var storedData; + + switch (config.storage) { + case 'sessionStorage': + storedData = fetchFromStorage('sessionStorage'); + break; + case 'localStorage': + storedData = fetchFromStorage('localStorage'); + break; + default: + storedData = null; + } + if (storedData) { + angular.extend(data, storedData); + return true; + } + + return false; } - } - return !!roles.length; - }; - - /** - * Check if the current user any of the given roles - * - * @param roles - * @returns {boolean} - */ - AclService.hasAnyRole = function (roles) { - for (var l = roles.length; l--;) { - if (AclService.hasRole(roles[l])) { - return true; - } - } - return false; - }; - /** - * Returns the current user roles - * @returns {Array} - */ - AclService.getRoles = function () { - return data.roles; - }; - - /** - * Set the abilities object (overwriting previous abilities) - * - * Each property on the abilities object should be a role. - * Each role should have a value of an array. The array should contain - * a list of all of the roles abilities. - * - * Example: - * - * { + /** + * Remove data from web storage + */ + AclService.flushStorage = function () { + isStorageSupported($window, config.storage) && unset(); + }; + + /** + * Attach a role to the current user + * + * @param role + */ + AclService.attachRole = function (role) { + if (data.roles.indexOf(role) === -1) { + data.roles.push(role); + isStorageSupported($window, config.storage) && save(); + } + }; + + /** + * Remove role from current user + * + * @param role + */ + AclService.detachRole = function (role) { + var i = data.roles.indexOf(role); + if (i > -1) { + data.roles.splice(i, 1); + isStorageSupported($window, config.storage) && save(); + } + }; + + /** + * Remove all roles from current user + */ + AclService.flushRoles = function () { + data.roles = []; + isStorageSupported($window, config.storage) && save(); + }; + + /** + * Check if the current user has role(s) attached + * + * @param role + * @returns {boolean} + */ + AclService.hasRole = function (role) { + var roles = angular.isArray(role) ? role : [role]; + for (var l = roles.length; l--;) { + if (data.roles.indexOf(roles[l]) === -1) { + return false; + } + } + return !!roles.length; + }; + + /** + * Check if the current user any of the given roles + * + * @param roles + * @returns {boolean} + */ + AclService.hasAnyRole = function (roles) { + for (var l = roles.length; l--;) { + if (AclService.hasRole(roles[l])) { + return true; + } + } + return false; + }; + + /** + * Returns the current user roles + * @returns {Array} + */ + AclService.getRoles = function () { + return data.roles; + }; + + /** + * Set the abilities object (overwriting previous abilities) + * + * Each property on the abilities object should be a role. + * Each role should have a value of an array. The array should contain + * a list of all of the roles abilities. + * + * Example: + * + * { * guest: ['login'], * user: ['logout', 'view_content'], * admin: ['logout', 'view_content', 'manage_users'] * } - * - * @param abilities - */ - AclService.setAbilities = function (abilities) { - data.abilities = abilities; - save(); - }; - - /** - * Add an ability to a role - * - * @param role - * @param ability - */ - AclService.addAbility = function (role, ability) { - if (!data.abilities[role]) { - data.abilities[role] = []; - } - data.abilities[role].push(ability); - save(); - }; - - /** - * Does current user have permission to do something? - * - * @param ability - * @returns {boolean} - */ - AclService.can = function (ability) { - var role, abilities; - // Loop through roles - var l = data.roles.length; - for (; l--;) { - // Grab the the current role - role = data.roles[l]; - abilities = getRoleAbilities(role); - if (abilities.indexOf(ability) > -1) { - // Ability is in role abilities - return true; - } - } - // We made it here, so the ability wasn't found in attached roles - return false; - }; + * + * @param abilities + */ + AclService.setAbilities = function (abilities) { + data.abilities = abilities; + isStorageSupported($window, config.storage) && save(); + }; + + /** + * Add an ability to a role + * + * @param role + * @param ability + */ + AclService.addAbility = function (role, ability) { + if (!data.abilities[role]) { + data.abilities[role] = []; + } + data.abilities[role].push(ability); + isStorageSupported($window, config.storage) && save(); + }; + + /** + * Does current user have permission to do something? + * + * @param ability + * @returns {boolean} + */ + AclService.can = function (ability) { + var role, abilities; + // Loop through roles + var l = data.roles.length; + for (; l--;) { + // Grab the the current role + role = data.roles[l]; + abilities = getRoleAbilities(role); + if (abilities.indexOf(ability) > -1) { + // Ability is in role abilities + return true; + } + } + // We made it here, so the ability wasn't found in attached roles + return false; + }; + + /** + * Does current user have any of the required permission to do something? + * + * @param abilities [array] + * @returns {boolean} + */ + AclService.canAny = function (abilities) { + var role, roleAbilities; + // Loop through roles + var l = data.roles.length; + var j = abilities.length; + + for (; l--;) { + // Grab the the current role + role = data.roles[l]; + roleAbilities = getRoleAbilities(role); + + for (; j--;) { + if (roleAbilities.indexOf(abilities[j]) > -1) { + // Ability is in role abilities + return true; + } + } + } + // We made it here, so the ability wasn't found in attached roles + return false; + }; + + return { + config: function (userConfig) { + angular.extend(config, userConfig); + }, + resume: resume, + $get: function () { + return AclService; + } + }; - /** - * Does current user have any of the required permission to do something? - * - * @param abilities [array] - * @returns {boolean} - */ - AclService.canAny = function (abilities) { - var role, roleAbilities; - // Loop through roles - var l = data.roles.length; - var j = abilities.length; - - for (; l--;) { - // Grab the the current role - role = data.roles[l]; - roleAbilities = getRoleAbilities(role); - - for (; j--;) { - if (roleAbilities.indexOf(abilities[j]) > -1) { - // Ability is in role abilities - return true; - } - } - } - // We made it here, so the ability wasn't found in attached roles - return false; - }; - - return { - config: function (userConfig) { - angular.extend(config, userConfig); - }, - resume: resume, - $get: function () { - return AclService; - } - }; - - } + } ]).directive('aclShow', function (AclService) { - return { - restrict: 'A', - link: function (scope, element, attrs) { - scope.$watch(attrs.aclShow, function aclShowWatchAction(value) { - var permissions, can; - if (!value) { - element.addClass(NG_HIDE_CLASS); - return; - } - permissions = value.split(','); - can = AclService.canAny(permissions); - if (!can) { - element.addClass(NG_HIDE_CLASS); - } else { - element.removeClass(NG_HIDE_CLASS); + return { + restrict: 'A', + link: function (scope, element, attrs) { + scope.$watch(attrs.aclShow, function aclShowWatchAction(value) { + var permissions, can; + if (!value) { + element.addClass(NG_HIDE_CLASS); + return; + } + permissions = value.split(','); + can = AclService.canAny(permissions); + if (!can) { + element.addClass(NG_HIDE_CLASS); + } else { + element.removeClass(NG_HIDE_CLASS); + } + }); } - }); - } - }; + }; });