/**
* angular-permission
* Route permission and access control as simple as it can get
- * @version v2.0.9 - 2016-02-15
- * @link http://www.rafaelvidaurre.com
- * @author Rafael Vidaurre <narzerus@gmail.com>
+ * @version v2.3.6 - 2016-04-11
+ * @link https://github.com/Narzerus/angular-permission
+ * @author Rafael Vidaurre <narzerus@gmail.com> (http://www.rafaelvidaurre.com), Blazej Krysiak <blazej.krysiak@gmail.com>
* @license MIT License, http://www.opensource.org/licenses/MIT
*/
(function () {
'use strict';
- var permission = angular.module('permission', ['ui.router']);
-
/**
- * This decorator is required to access full state object instead of it's configuration
- * when trying to obtain full toState state object not it's configuration
- * Can be removed when implemented https://github.com/angular-ui/ui-router/issues/13.
+ * @namespace permission
*/
- permission.config(['$stateProvider', function ($stateProvider) {
+
+ config.$inject = ['$stateProvider'];
+ run.$inject = ['$rootScope', '$location', '$state', 'TransitionProperties', 'TransitionEvents', 'StateAuthorization', 'StatePermissionMap'];
+ function config($stateProvider) {
+ /**
+ * This decorator is required to access full state object instead of it's configuration
+ * when trying to obtain full toState state object not it's configuration
+ * Can be removed when implemented https://github.com/angular-ui/ui-router/issues/13.
+ */
$stateProvider.decorator('parent', function (state, parentFn) {
- state.self.getState = function () {
+ state.self.$$state = function () {
return state;
};
+
+ state.self.areSetStatePermissions = function () {
+ return angular.isDefined(state.data) && angular.isDefined(state.data.permissions);
+ };
+
return parentFn(state);
});
- }]);
+ }
- permission.run(['$rootScope', '$state', '$q', 'Authorization', 'PermissionMap', function ($rootScope, $state, $q, Authorization, PermissionMap) {
+ function run($rootScope, $location, $state, TransitionProperties, TransitionEvents, StateAuthorization, StatePermissionMap) {
+ /**
+ * State transition interceptor
+ */
$rootScope.$on('$stateChangeStart', function (event, toState, toParams, fromState, fromParams, options) {
- if (toState.$$isAuthorizationFinished) {
- return;
- }
-
- if (areSetStatePermissions(toState)) {
+ if (!isAuthorizationFinished()) {
event.preventDefault();
+
setStateAuthorizationStatus(true);
+ setTransitionProperties();
+
+ if (!TransitionEvents.areStateEventsDefaultPrevented()) {
+ TransitionEvents.broadcastStateChangePermissionStart();
+ var statePermissionMap = new StatePermissionMap();
- if (!areStateEventsDefaultPrevented()) {
- var compensatedPermissionMap = compensatePermissionMap(toState.data.permissions);
- authorizeForState(compensatedPermissionMap);
+ StateAuthorization
+ .authorize(statePermissionMap)
+ .then(function () {
+ handleAuthorizedState();
+ })
+ .catch(function (rejectedPermission) {
+ handleUnauthorizedState(rejectedPermission, statePermissionMap);
+ })
+ .finally(function () {
+ setStateAuthorizationStatus(false);
+ });
}
}
/**
- * Checks if state is qualified to be permission based verified
- *
- * @returns {boolean}
+ * Updates values of `TransitionProperties` holder object
+ * @method
+ * @private
*/
- function areSetStatePermissions(state) {
- return angular.isDefined(state.data) && angular.isDefined(state.data.permissions);
+ function setTransitionProperties() {
+ TransitionProperties.toState = toState;
+ TransitionProperties.toParams = toParams;
+ TransitionProperties.fromState = fromState;
+ TransitionProperties.fromParams = fromParams;
+ TransitionProperties.options = options;
}
/**
* Sets internal state `$$finishedAuthorization` variable to prevent looping
+ * @method
+ * @private
+ *
*
* @param status {boolean} When true authorization has been already preceded
*/
function setStateAuthorizationStatus(status) {
- toState = angular.extend({'$$isAuthorizationFinished': status}, toState);
+ angular.extend(toState, {'$$isAuthorizationFinished': status});
}
/**
- * Checks if state events are not prevented by default
+ * Checks if state has been already checked for authorization
+ * @method
+ * @private
*
* @returns {boolean}
*/
- function areStateEventsDefaultPrevented() {
- return isStateChangePermissionStartDefaultPrevented() || isStateChangeStartDefaultPrevented();
+ function isAuthorizationFinished() {
+ return toState.$$isAuthorizationFinished;
}
/**
- * Builds map of permissions resolving passed values to data.permissions and combine them with all its parents
- * keeping the order of permissions from the newest (children) to the oldest (parent)
- *
- * @param statePermissionMap {Object} Current state permission map
- * @returns {{only: Array, except: Array}} Permission map
+ * Handles redirection for authorized access
+ * @method
+ * @private
*/
- function compensatePermissionMap(statePermissionMap) {
- var permissionMap = new PermissionMap({redirectTo: statePermissionMap.redirectTo});
-
- var toStatePath = $state
- .get(toState.name)
- .getState().path
- .slice()
- .reverse();
-
- angular.forEach(toStatePath, function (state) {
- if (areSetStatePermissions(state)) {
- permissionMap.extendPermissionMap(new PermissionMap(state.data.permissions));
- }
- });
+ function handleAuthorizedState() {
- return permissionMap;
- }
+ TransitionEvents.broadcastStateChangePermissionAccepted();
+ $location.replace();
- /**
- * Handles state authorization
- *
- * @param permissions {Object} Map of "only" or "except" permission names
- */
- function authorizeForState(permissions) {
- Authorization
- .authorize(permissions, toParams)
- .then(function () {
- $rootScope.$broadcast('$stateChangePermissionAccepted', toState, toParams, options);
- goToState(toState.name);
- })
- .catch(function (rejectedPermission) {
- $rootScope.$broadcast('$stateChangePermissionDenied', toState, toParams, options);
- permissions.redirectToState(rejectedPermission);
- });
- }
+ // Overwrite notify option to broadcast it later
+ TransitionProperties.options = angular.extend({}, TransitionProperties.options, {notify: false});
- /**
- * Redirects to states when permissions are met
- *
- * If authorized, use call state.go without triggering the event.
- * Then trigger $stateChangeSuccess manually to resume the rest of the process
- * Note: This is a pseudo-hacky fix which should be fixed in future ui-router versions
- */
- function goToState(name) {
$state
- .go(name, toParams, angular.extend({}, options, {notify: false}))
+ .go(TransitionProperties.toState.name, TransitionProperties.toParams, TransitionProperties.options)
.then(function () {
- $rootScope.$broadcast('$stateChangeSuccess', toState, toParams, fromState, fromParams, options);
+ TransitionEvents.broadcastStateChangeSuccess();
});
}
/**
- * Checks if event $stateChangeStart hasn't been disabled by default
+ * Handles redirection for unauthorized access
+ * @method
+ * @private
*
- * @returns {boolean}
+ * @param rejectedPermission {String} Rejected access right
+ * @param statePermissionMap {permission.StatePermissionMap} State permission map
*/
- function isStateChangeStartDefaultPrevented() {
- return $rootScope.$broadcast('$stateChangeStart', toState, toParams, fromState, fromParams, options).defaultPrevented;
- }
+ function handleUnauthorizedState(rejectedPermission, statePermissionMap) {
+ TransitionEvents.broadcastStateChangePermissionDenied();
- /**
- * Checks if event $stateChangePermissionStart hasn't been disabled by default
- *
- * @returns {boolean}
- */
- function isStateChangePermissionStartDefaultPrevented() {
- return $rootScope.$broadcast('$stateChangePermissionStart', toState, toParams, options).defaultPrevented;
+ statePermissionMap
+ .resolveRedirectState(rejectedPermission)
+ .then(function (redirect) {
+ $state.go(redirect.state, redirect.params, redirect.options);
+ });
}
});
- }]);
+ }
+
+ angular.module('permission', ['ui.router'])
+ .config(config)
+ .run(run);
}());
+
(function () {
'use strict';
- angular
- .module('permission')
- .factory('PermissionMap', ['$q', '$state', function ($q, $state) {
+ /**
+ * Extends $q implementation by A+ *any* method
+ * @name $q
+ * @extends {angular.$q}
+ * @memberOf permission
+ *
+ * @param $delegate {Object} Angular promise implementation
+ */
+ $q.$inject = ['$delegate'];
+ function $q($delegate) {
+
+ $delegate.any = any;
+
+ /**
+ * Implementation of missing $q `any` method that wits for first resolution of provided promise set
+ * @method
+ *
+ * @param promises {Array|promise} Single or set of promises
+ *
+ * @returns {Promise} Returns a single promise that will be rejected with an array/hash of values,
+ * each value corresponding to the promise at the same index/key in the `promises` array/hash.
+ * If any of the promises is resolved, this resulting promise will be returned
+ * with the same resolution value.
+ */
+ function any(promises) {
+ var deferred = $delegate.defer(),
+ counter = 0,
+ results = angular.isArray(promises) ? [] : {};
+
+ angular.forEach(promises, function (promise, key) {
+ counter++;
+ $delegate
+ .when(promise)
+ .then(function (value) {
+ deferred.resolve(value);
+ })
+ .catch(function (reason) {
+ results[key] = reason;
+ if (!(--counter)) {
+ deferred.reject(reason);
+ }
+ });
+ });
- /**
- * Constructs map object instructing authorization service how to handle authorizing
- *
- * @param permissionMap {Object} Map of permissions provided to authorization service
- * @param permissionMap.only {Array} List of exclusive permission/role names allowed for authorization
- * @param permissionMap.except {Array} List of exclusive permission/role names denied for authorization
- * @param permissionMap.redirectTo {String|Function|Object|promise} Handling redirection when rejected
- * authorization
- * @param [toState] {Object} UI-Router transition state object
- * @param [toParams] {Object} UI-Router transition state params
- * @param [options] {Object} UI-Router transition state options
- * @constructor
- */
- function PermissionMap(permissionMap, toState, toParams, options) {
- this.only = resolvePermissionMapProperty(permissionMap.only, toState, toParams, options);
- this.except = resolvePermissionMapProperty(permissionMap.except, toState, toParams, options);
- this.redirectTo = permissionMap.redirectTo;
+ if (counter === 0) {
+ deferred.reject(results);
}
- /**
- * Extends permission map by pushing to it state's permissions
- *
- * @param permissionMap {PermissionMap} Compensated permission map
- */
- PermissionMap.prototype.extendPermissionMap = function (permissionMap) {
- this.only = this.only.concat(permissionMap.only);
- this.except = this.except.concat(permissionMap.except);
- };
+ return deferred.promise;
+ }
+ return $delegate;
+ }
- /**
- * Redirects to fallback states when permissions fail
- *
- * @param rejectedPermissionName {String} Permission name
- */
- PermissionMap.prototype.redirectToState = function (rejectedPermissionName) {
- if (angular.isFunction(this.redirectTo)) {
- handleFunctionRedirect(this.redirectTo, rejectedPermissionName);
- }
+ angular
+ .module('permission')
+ .decorator('$q', $q);
- if (angular.isObject(this.redirectTo)) {
- handleObjectRedirect(this.redirectTo, rejectedPermissionName);
- }
+})();
- if (angular.isString(this.redirectTo)) {
- handleStringRedirect(this.redirectTo, this.toParams, this.options);
- }
- };
- /**
- * Handles function based redirection for rejected permissions
- *
- * @param redirectFunction {Function} Redirection function
- * @param permission {String} Rejected permission
- */
- function handleFunctionRedirect(redirectFunction, permission) {
- $q.when(redirectFunction.call(null, permission))
- .then(function (redirectState) {
- if (!angular.isString(redirectState)) {
- throw new TypeError('When used "redirectTo" as function, returned value must be string with state name');
- }
- handleStringRedirect(redirectState);
- });
- }
+(function () {
+ 'use strict';
- /**
- * Handles object based redirection for rejected permissions
- *
- * @param redirectObject {Object} Redirection function
- * @param permission {String} Rejected permission
- */
- function handleObjectRedirect(redirectObject, permission) {
- if (!angular.isDefined(redirectObject['default'])) {
- throw new ReferenceError('When used "redirectTo" as object, property "default" must be defined');
- }
+ /**
+ * Pre-defined available configurable behaviours of directive `permission`
+ * @name PermissionStrategies
+ * @memberOf permission
+ * @readonly
+ *
+ * @example
+ * <div permission
+ * permission-except="'MANAGER'"
+ * permission-on-authorized="PermissionStrategies.renderContent"
+ * permission-on-unauthorized="PermissionStrategies.removeContent">
+ * </div>
+ *
+ * @property enableElement {Function}
+ * @property disableElement {Function}
+ * @property showElement {Function}
+ * @property hideElement {Function}
+ */
+ var PermissionStrategies = {
+ enableElement: function ($element) {
+ $element.removeAttr('disabled');
+ },
+ disableElement: function ($element) {
+ $element.attr('disabled', 'disabled');
+ },
+ showElement: function ($element) {
+ $element.removeClass('ng-hide');
+ },
+ hideElement: function ($element) {
+ $element.addClass('ng-hide');
+ }
+ };
- var redirectState = redirectObject[permission];
+ angular
+ .module('permission')
+ .constant('PermissionStrategies', PermissionStrategies);
- if (!angular.isDefined(redirectState)) {
- redirectState = redirectObject['default'];
- }
+}());
- if (angular.isFunction(redirectState)) {
- handleFunctionRedirect(redirectState, permission);
- }
- if (angular.isString(redirectState)) {
- handleStringRedirect(redirectState);
- }
- }
+(function () {
+ 'use strict';
- /**
- * Handles string based redirection for rejected permissions
- */
- function handleStringRedirect(state, toParams, options) {
- $state.go(state, toParams, options);
- }
+ /**
+ * Helper object used for storing ui-router transition parameters
+ * @name TransitionProperties
+ * @memberOf permission
+ *
+ * @type {Object.<String,Object>}
+ *
+ * UI-router transition properties:
+ * @property toState {Object} Target state object
+ * @property toParams {Object} Target state params
+ * @property fromState {Object} Source state object
+ * @property fromParams {Object} Source state params
+ * @property options {Object} Transition options
+ */
+ var TransitionProperties = {
+ toState: undefined,
+ toParams: undefined,
+ fromState: undefined,
+ fromParams: undefined,
+ options: undefined
+ };
- /**
- * Handles extraction of permission map "only" and "except" properties
- * @private
- *
- * @param property {Array|Function|promise} Permission map property "only" or "except"
- * @param [toState] {Object} UI-Router transition state object
- * @param [toParams] {Object} UI-Router transition state params
- * @param [options] {Object} UI-Router transition state options
- * @returns {Array} Array of permission "only" or "except" names
- */
- function resolvePermissionMapProperty(property, toState, toParams, options) {
- if (angular.isString(property)) {
- return [property];
- }
+ angular
+ .module('permission')
+ .value('TransitionProperties', TransitionProperties);
- if (angular.isArray(property)) {
- return property;
- }
+}());
- if (angular.isFunction(property)) {
- return property.call(null, toState, toParams, options);
- }
+(function () {
+ 'use strict';
- return [];
- }
+ /**
+ * Service responsible for managing and emitting events
+ * @name TransitionEvents
+ * @memberOf permission
+ *
+ * @param TransitionProperties {permission.TransitionProperties} Helper storing ui-router transition parameters
+ * @param $rootScope {Object} Top-level angular scope
+ */
+ TransitionEvents.$inject = ['$rootScope', 'TransitionProperties'];
+ function TransitionEvents($rootScope, TransitionProperties) {
+
+ this.areStateEventsDefaultPrevented = areStateEventsDefaultPrevented;
+ this.broadcastStateChangePermissionStart = broadcastStateChangePermissionStart;
+ this.broadcastStateChangePermissionAccepted = broadcastStateChangePermissionAccepted;
+ this.broadcastStateChangePermissionDenied = broadcastStateChangePermissionDenied;
+ this.broadcastStateChangeSuccess = broadcastStateChangeSuccess;
+
+ /**
+ * Checks if state events are not prevented by default
+ * @method
+ *
+ * @returns {boolean}
+ */
+ function areStateEventsDefaultPrevented() {
+ return isStateChangePermissionStartDefaultPrevented() || isStateChangeStartDefaultPrevented();
+ }
+
+ /**
+ * Broadcasts "$stateChangePermissionStart" event from $rootScope
+ * @method
+ */
+ function broadcastStateChangePermissionStart() {
+ $rootScope.$broadcast('$stateChangePermissionStart',
+ TransitionProperties.toState, TransitionProperties.toParams,
+ TransitionProperties.options);
+ }
+
+ /**
+ * Broadcasts "$stateChangePermissionAccepted" event from $rootScope
+ * @method
+ */
+ function broadcastStateChangePermissionAccepted() {
+ $rootScope.$broadcast('$stateChangePermissionAccepted',
+ TransitionProperties.toState, TransitionProperties.toParams,
+ TransitionProperties.options);
+ }
+
+ /**
+ * Broadcasts "$stateChangeSuccess" event from $rootScope
+ * @method
+ */
+ function broadcastStateChangeSuccess() {
+ $rootScope.$broadcast('$stateChangeSuccess',
+ TransitionProperties.toState, TransitionProperties.toParams,
+ TransitionProperties.fromState, TransitionProperties.fromParams);
+ }
+
+ /**
+ * Broadcasts "$tateChangePermissionDenied" event from $rootScope
+ * @method
+ */
+ function broadcastStateChangePermissionDenied() {
+ $rootScope.$broadcast('$stateChangePermissionDenied',
+ TransitionProperties.toState, TransitionProperties.toParams,
+ TransitionProperties.options);
+ }
+
+ /**
+ * Checks if event $stateChangeStart hasn't been disabled by default
+ * @method
+ * @private
+ *
+ * @returns {boolean}
+ */
+ function isStateChangeStartDefaultPrevented() {
+ return $rootScope.$broadcast('$stateChangeStart',
+ TransitionProperties.toState, TransitionProperties.toParams,
+ TransitionProperties.fromState, TransitionProperties.fromParams,
+ TransitionProperties.options).defaultPrevented;
+ }
+
+ /**
+ * Checks if event $stateChangePermissionStart hasn't been disabled by default
+ * @method
+ * @private
+ *
+ * @returns {boolean}
+ */
+ function isStateChangePermissionStartDefaultPrevented() {
+ return $rootScope.$broadcast('$stateChangePermissionStart',
+ TransitionProperties.toState, TransitionProperties.toParams,
+ TransitionProperties.options).defaultPrevented;
+ }
+ }
+
+ angular
+ .module('permission')
+ .service('TransitionEvents', TransitionEvents);
- return PermissionMap;
- }]);
}());
+
(function () {
'use strict';
- angular
- .module('permission')
- .factory('Permission', ['$q', function ($q) {
+ /**
+ * Access rights map factory
+ * @name PermissionMapFactory
+ *
+ * @param $q {Object} Angular promise implementation
+ * @param TransitionProperties {permission.TransitionProperties} Helper storing ui-router transition parameters
+ * @param RoleStore {permission.RoleStore} Role definition storage
+ * @param PermissionStore {permission.PermissionStore} Permission definition storage
+ *
+ * @return {permission.PermissionMap}
+ */
+ PermissionMapFactory.$inject = ['$q', 'TransitionProperties', 'RoleStore', 'PermissionStore'];
+ function PermissionMapFactory($q, TransitionProperties, RoleStore, PermissionStore) {
+ /**
+ * Constructs map object instructing authorization service how to handle authorizing
+ * @constructor PermissionMap
+ * @memberOf permission
+ *
+ * @param [permissionMap] {Object} Map of permissions provided to authorization service
+ * @param [permissionMap.only] {Array} List of exclusive access right names allowed for authorization
+ * @param [permissionMap.except] {Array} List of exclusive access right names denied for authorization
+ * @param [permissionMap.redirectTo] {String|Function|Object|promise} Handling redirection when rejected
+ * authorization
+ */
+ function PermissionMap(permissionMap) {
+ // Suppress not defined object errors
+ permissionMap = permissionMap || {};
+
+ this.only = normalizeMapProperty(permissionMap.only);
+ this.except = normalizeMapProperty(permissionMap.except);
+ this.redirectTo = permissionMap.redirectTo;
+ }
+
+ /**
+ * Redirects to fallback states when permissions fail
+ * @method
+ * @methodOf permission.PermissionMap
+ *
+ * @param rejectedPermissionName {String} Permission name
+ *
+ * @return {Promise}
+ */
+ PermissionMap.prototype.resolveRedirectState = function (rejectedPermissionName) {
+ if (angular.isFunction(this.redirectTo)) {
+ return resolveFunctionRedirect(this.redirectTo, rejectedPermissionName);
+ }
- /**
- * Permission definition object constructor
- *
- * @param permissionName {String} Name repressing permission
- * @param validationFunction {Function} Function used to check if permission is valid
- * @constructor
- */
- function Permission(permissionName, validationFunction) {
- validateConstructor(permissionName, validationFunction);
+ if (angular.isObject(this.redirectTo)) {
+ return resolveObjectRedirect(this.redirectTo, rejectedPermissionName);
+ }
- this.permissionName = permissionName;
- this.validationFunction = validationFunction;
+ if (angular.isString(this.redirectTo)) {
+ return $q.resolve({
+ state: this.redirectTo
+ });
}
- /**
- * Checks if permission is still valid
- *
- * @param toParams {Object} UI-Router params object
- * @returns {Promise}
- */
- Permission.prototype.validatePermission = function (toParams) {
- var validationResult = this.validationFunction.call(null, toParams, this.permissionName);
+ // If redirectTo state is not defined stay where you are
+ return $q.reject(null);
+ };
- if (!angular.isFunction(validationResult.then)) {
- validationResult = wrapInPromise(validationResult, this.permissionName);
- }
+ /**
+ * Resolves weather permissions set for "only" or "except" property are valid
+ * @method
+ *
+ * @param property {permissionMap.only|permissionMap.except} "only" or "except" map property
+ * @returns {Array<Promise>}
+ */
+ PermissionMap.prototype.resolvePropertyValidity = function (property) {
- return validationResult;
- };
+ return property.map(function (privilegeName) {
- /**
- * Converts a value into a promise, if the value is truthy it resolves it, otherwise it rejects it
- * @private
- *
- * @param result {Boolean} Function to be wrapped into promise
- * @param permissionName {String} Returned value in promise
- * @return {Promise}
- */
- function wrapInPromise(result, permissionName) {
- var dfd = $q.defer();
+ if (RoleStore.hasRoleDefinition(privilegeName)) {
+ var role = RoleStore.getRoleDefinition(privilegeName);
+ return role.validateRole();
+ }
- if (result) {
- dfd.resolve(permissionName);
- } else {
- dfd.reject(permissionName);
+ if (PermissionStore.hasPermissionDefinition(privilegeName)) {
+ var permission = PermissionStore.getPermissionDefinition(privilegeName);
+ return permission.validatePermission();
}
- return dfd.promise;
- }
+ return $q.reject(privilegeName);
+ });
+ };
+
+ /**
+ * Handles function based redirection for rejected permissions
+ * @method
+ * @methodOf permission.PermissionMap
+ * @throws {TypeError}
+ *
+ * @param redirectFunction {Function} Redirection function
+ * @param permission {String} Rejected permission
+ *
+ * @return {Promise}
+ */
+ function resolveFunctionRedirect(redirectFunction, permission) {
+ return $q
+ .when(redirectFunction.call(null, permission))
+ .then(function (redirectState) {
+ if (angular.isString(redirectState)) {
+ return {
+ state: redirectState
+ };
+ }
- /**
- * Checks if provided permission has accepted parameter types
- * @private
- */
- function validateConstructor(permissionName, validationFunction) {
- if (!angular.isString(permissionName)) {
- throw new TypeError('Parameter "permissionName" name must be String');
- }
- if (!angular.isFunction(validationFunction)) {
- throw new TypeError('Parameter "validationFunction" must be Function');
- }
+ if (angular.isObject(redirectState)) {
+ return redirectState;
+ }
+
+ throw new TypeError('When used "redirectTo" as function, returned value must be string or object');
+ });
+ }
+
+ /**
+ * Handles object based redirection for rejected permissions
+ * @method
+ * @throws {ReferenceError}
+ *
+ * @param redirectObject {Object} Redirection function
+ * @param permission {String} Rejected permission
+ *
+ * @return {Promise}
+ */
+ function resolveObjectRedirect(redirectObject, permission) {
+ if (!angular.isDefined(redirectObject['default'])) {
+ throw new ReferenceError('When used "redirectTo" as object, property "default" must be defined');
}
- return Permission;
- }]);
-}());
-(function () {
- 'use strict';
+ var redirectState = redirectObject[permission];
- angular
- .module('permission')
- .factory('Role', ['$q', 'PermissionStore', function ($q, PermissionStore) {
+ if (!angular.isDefined(redirectState)) {
+ redirectState = redirectObject['default'];
+ }
- /**
- * Role definition constructor
- *
- * @param roleName {String} Name representing role
- * @param permissionNames {Array} List of permission names representing role
- * @param [validationFunction] {Function} Optional function used to validate if permissions are still valid
- * @constructor
- */
- function Role(roleName, permissionNames, validationFunction) {
- validateConstructor(roleName, permissionNames, validationFunction);
- this.roleName = roleName;
- this.permissionNames = permissionNames || [];
- this.validationFunction = validationFunction;
-
- if (validationFunction) {
- PermissionStore.defineManyPermissions(permissionNames, validationFunction);
- }
+ if (angular.isFunction(redirectState)) {
+ return resolveFunctionRedirect(redirectState, permission);
}
- /**
- * Checks if role is still valid
- *
- * @param [toParams] {Object} UI-Router params object
- * @returns {Promise} $q.promise object
- */
- Role.prototype.validateRole = function (toParams) {
+ if (angular.isObject(redirectState)) {
+ return $q.resolve(redirectState);
+ }
- // When set permissions is provided check each of them
- if (this.permissionNames.length) {
- var promises = this.permissionNames.map(function (permissionName) {
- if (PermissionStore.hasPermissionDefinition(permissionName)) {
- var permission = PermissionStore.getPermissionDefinition(permissionName);
- var validationResult = permission.validationFunction.call(null, toParams, permission.permissionName);
+ if (angular.isString(redirectState)) {
+ return $q.resolve({
+ state: redirectState
+ });
+ }
+ }
+
+ /**
+ * Handles extraction of permission map "only" and "except" properties and converts them into array objects
+ * @method
+ * @private
+ *
+ * @param property {String|Array|Function|Promise} Permission map property "only" or "except"
+ *
+ * @returns {Array<String>} Array of permission "only" or "except" names
+ */
+ function normalizeMapProperty(property) {
+ if (angular.isString(property)) {
+ return [property];
+ }
- if (!angular.isFunction(validationResult.then)) {
- validationResult = wrapInPromise(validationResult);
- }
+ if (angular.isArray(property)) {
+ return property;
+ }
- return validationResult;
- }
+ if (angular.isFunction(property)) {
+ return property.call(null, TransitionProperties);
+ }
- return $q.reject(null);
- });
+ return [];
+ }
- return $q.all(promises);
- }
+ return PermissionMap;
+ }
- // If not call validation function manually
- var validationResult = this.validationFunction.call(null, toParams, this.roleName);
- if (!angular.isFunction(validationResult.then)) {
- validationResult = wrapInPromise(validationResult, this.roleName);
- }
+ angular
+ .module('permission')
+ .factory('PermissionMap', PermissionMapFactory);
+}());
- return $q.resolve(validationResult);
+(function () {
+ 'use strict';
- };
+ /**
+ * State Access rights map factory
+ * @name StatePermissionMapFactory
+ *
+ * @param TransitionProperties {permission.TransitionProperties} Helper storing ui-router transition parameters
+ * @param PermissionMap {permission.PermissionMap}
+ *
+ * @return {permission.StatePermissionMap}
+ */
+ StatePermissionMapFactory.$inject = ['TransitionProperties', 'PermissionMap'];
+ function StatePermissionMapFactory(TransitionProperties, PermissionMap) {
- /**
- * Converts a value into a promise, if the value is truthy it resolves it, otherwise it rejects it
- * @private
- *
- * @param result {Boolean} Function to be wrapped into promise
- * @param roleName {String} Returned value in promise
- * @return {Promise}
- */
- function wrapInPromise(result, roleName) {
- var dfd = $q.defer();
+ StatePermissionMap.prototype = new PermissionMap();
+ StatePermissionMap.constructor = StatePermissionMap;
+ StatePermissionMap.prototype.parent = PermissionMap.prototype;
- if (result) {
- dfd.resolve(roleName);
- } else {
- dfd.reject(roleName);
- }
- return dfd.promise;
- }
+ /**
+ * Constructs map object instructing authorization service how to handle authorizing
+ * @constructor StatePermissionMap
+ * @extends PermissionMap
+ * @memberOf permission
+ */
+ function StatePermissionMap() {
+ this.parent.constructor.call(this);
- /**
- * Checks if provided permission has accepted parameter types
- * @private
- */
- function validateConstructor(roleName, permissionNames, validationFunction) {
- if (!angular.isString(roleName)) {
- throw new TypeError('Parameter "roleName" name must be String');
- }
+ var toStateObject = TransitionProperties.toState.$$state();
+ var toStatePath = toStateObject.path.slice().reverse();
- if (!angular.isArray(permissionNames)) {
- throw new TypeError('Parameter "permissionNames" must be Array');
- }
+ angular.forEach(toStatePath, function (state) {
- if (!permissionNames.length && !angular.isFunction(validationFunction)) {
- throw new TypeError('Parameter "validationFunction" must be provided for empty "permissionNames" array');
+ if (state.areSetStatePermissions()) {
+ var permissionMap = new PermissionMap(state.data.permissions);
+ this.extendPermissionMap(permissionMap);
}
+ }, this);
+ }
+
+ /**
+ * Extends permission map by pushing to it state's permissions
+ * @method
+ * @methodOf permission.StatePermissionMap
+ *
+ * @param permissionMap {permission.PermissionMap} Compensated permission map
+ */
+ StatePermissionMap.prototype.extendPermissionMap = function (permissionMap) {
+ if (permissionMap.only.length) {
+ this.only = this.only.concat([permissionMap.only]);
+ }
+ if (permissionMap.except.length) {
+ this.except = this.except.concat([permissionMap.except]);
}
+ this.redirectTo = permissionMap.redirectTo;
+ };
- return Role;
- }]);
-}());
-(function () {
- 'use strict';
+ return StatePermissionMap;
+ }
angular
.module('permission')
- .service('PermissionStore', ['Permission', function (Permission) {
- var permissionStore = {};
+ .factory('StatePermissionMap', StatePermissionMapFactory);
+}());
- this.definePermission = definePermission;
- this.defineManyPermissions = defineManyPermissions;
- this.removePermissionDefinition = removePermissionDefinition;
- this.hasPermissionDefinition = hasPermissionDefinition;
- this.getPermissionDefinition = getPermissionDefinition;
- this.getStore = getStore;
- this.clearStore = clearStore;
+(function () {
+ 'use strict';
- /**
- * Allows to define permission on application configuration
- *
- * @param permissionName {String} Name of defined permission
- * @param validationFunction {Function} Function used to validate if permission is valid
- */
- function definePermission(permissionName, validationFunction) {
- permissionStore[permissionName] = new Permission(permissionName, validationFunction);
+ /**
+ * Permission definition factory
+ * @name PermissionFactory
+ *
+ * @param $q {Object} Angular promise implementation
+ * @param TransitionProperties {permission.TransitionProperties} Helper storing ui-router transition parameters
+ *
+ * @return {permission.Permission}
+ */
+ PermissionFactory.$inject = ['$q', 'TransitionProperties'];
+ function PermissionFactory($q, TransitionProperties) {
+ /**
+ * Permission definition object constructor
+ * @class Permission
+ * @memberOf permission
+ *
+ * @param permissionName {String} Name repressing permission
+ * @param validationFunction {Function} Function used to check if permission is valid
+ */
+ function Permission(permissionName, validationFunction) {
+ validateConstructor(permissionName, validationFunction);
+
+ this.permissionName = permissionName;
+ this.validationFunction = validationFunction;
+ }
+
+ /**
+ * Checks if permission is still valid
+ * @method
+ * @methodOf permission.Permission
+ *
+ * @returns {Promise}
+ */
+ Permission.prototype.validatePermission = function () {
+ var validationResult = this.validationFunction.call(null, this.permissionName, TransitionProperties);
+
+ if (!angular.isFunction(validationResult.then)) {
+ validationResult = wrapInPromise(validationResult, this.permissionName);
}
- /**
- * Allows to define set of permissionNames with shared validation function on application configuration
- *
- * @param permissionNames {Array} Set of permission names
- * @param validationFunction {Function} Function used to validate if permission is valid
- */
- function defineManyPermissions(permissionNames, validationFunction) {
- if (!angular.isArray(permissionNames)) {
- throw new TypeError('Parameter "permissionNames" name must be Array');
- }
-
- angular.forEach(permissionNames, function (permissionName) {
- definePermission(permissionName, validationFunction);
- });
+ return validationResult;
+ };
+
+ /**
+ * Converts a value into a promise, if the value is truthy it resolves it, otherwise it rejects it
+ * @method
+ * @private
+ *
+ * @param result {Boolean} Function to be wrapped into promise
+ * @param permissionName {String} Returned value in promise
+ * @return {Promise}
+ */
+ function wrapInPromise(result, permissionName) {
+ var dfd = $q.defer();
+
+ if (result) {
+ dfd.resolve(permissionName);
+ } else {
+ dfd.reject(permissionName);
}
- /**
- * Deletes permission
- *
- * @param permissionName {String} Name of defined permission
- */
- function removePermissionDefinition(permissionName) {
- delete permissionStore[permissionName];
+ return dfd.promise;
+ }
+
+ /**
+ * Checks if provided permission has accepted parameter types
+ * @method
+ * @private
+ * @throws {TypeError}
+ */
+ function validateConstructor(permissionName, validationFunction) {
+ if (!angular.isString(permissionName)) {
+ throw new TypeError('Parameter "permissionName" name must be String');
}
-
- /**
- * Checks if permission exists
- *
- * @param permissionName {String} Name of defined permission
- * @returns {Boolean}
- */
- function hasPermissionDefinition(permissionName) {
- return angular.isDefined(permissionStore[permissionName]);
+ if (!angular.isFunction(validationFunction)) {
+ throw new TypeError('Parameter "validationFunction" must be Function');
}
+ }
- /**
- * Returns permission by it's name
- *
- * @returns {Object} Permissions collection
- */
- function getPermissionDefinition(permissionName) {
- return permissionStore[permissionName];
- }
+ return Permission;
+ }
- /**
- * Returns all permissions
- *
- * @returns {Object} Permissions collection
- */
- function getStore() {
- return permissionStore;
- }
+ angular
+ .module('permission')
+ .factory('Permission', PermissionFactory);
- /**
- * Removes all permissions
- */
- function clearStore() {
- permissionStore = {};
- }
- }]);
}());
+
(function () {
'use strict';
- angular
- .module('permission')
- .service('RoleStore', ['Role', function (Role) {
- var roleStore = {};
+ /**
+ * Role definition factory
+ * @name RoleFactory
+ *
+ * @param $q {Object} Angular promise implementation
+ * @param PermissionStore {permission.PermissionStore} Permission definition storage
+ * @param TransitionProperties {permission.TransitionProperties} Helper storing ui-router transition parameters
+ *
+ * @return {permission.Role}
+ */
+ RoleFactory.$inject = ['$q', 'PermissionStore', 'TransitionProperties'];
+ function RoleFactory($q, PermissionStore, TransitionProperties) {
+ /**
+ * Role definition constructor
+ * @class Role
+ * @memberOf permission
+ *
+ * @param roleName {String} Name representing role
+ * @param permissionNames {Array} List of permission names representing role
+ * @param [validationFunction] {Function} Optional function used to validate if permissions are still valid
+ */
+ function Role(roleName, permissionNames, validationFunction) {
+ validateConstructor(roleName, permissionNames, validationFunction);
+ this.roleName = roleName;
+ this.permissionNames = permissionNames || [];
+ this.validationFunction = validationFunction;
+
+ if (validationFunction) {
+ PermissionStore.defineManyPermissions(permissionNames, validationFunction);
+ }
+ }
+
+ /**
+ * Checks if role is still valid
+ * @method
+ * @methodOf permission.Role
+ *
+ * @returns {Promise} $q.promise object
+ */
+ Role.prototype.validateRole = function () {
+ // When permission set is provided check each of them
+ if (this.permissionNames.length) {
+ var promises = this.permissionNames.map(function (permissionName) {
+ if (PermissionStore.hasPermissionDefinition(permissionName)) {
+ var permission = PermissionStore.getPermissionDefinition(permissionName);
+ var validationResult = permission.validatePermission();
- this.defineRole = defineRole;
- this.getRoleDefinition = getRoleDefinition;
- this.hasRoleDefinition = hasRoleDefinition;
- this.removeRoleDefinition = removeRoleDefinition;
- this.getStore = getStore;
- this.clearStore = clearStore;
+ if (!angular.isFunction(validationResult.then)) {
+ validationResult = wrapInPromise(validationResult);
+ }
- /**
- * Allows to define role
- *
- * @param roleName {String} Name of defined role
- * @param permissions {Array} Set of permission names
- * @param [validationFunction] {Function} Function used to validate if permissions in role are valid
- */
- function defineRole(roleName, permissions, validationFunction) {
- roleStore[roleName] = new Role(roleName, permissions, validationFunction);
+ return validationResult;
+ }
+
+ return $q.reject();
+ });
+
+ return $q.all(promises);
}
- /**
- * Deletes role from store
- *
- * @param roleName {String} Name of defined permission
- */
- function removeRoleDefinition(roleName) {
- delete roleStore[roleName];
+ // If not call validation function manually
+ var validationResult = this.validationFunction.call(null, this.roleName, TransitionProperties);
+ if (!angular.isFunction(validationResult.then)) {
+ validationResult = wrapInPromise(validationResult, this.roleName);
}
- /**
- * Checks if role is defined in store
- *
- * @param roleName {String} Name of role
- * @returns {Boolean}
- */
- function hasRoleDefinition(roleName) {
- return angular.isDefined(roleStore[roleName]);
+ return $q.resolve(validationResult);
+ };
+
+ /**
+ * Converts a value into a promise, if the value is truthy it resolves it, otherwise it rejects it
+ * @method
+ * @private
+ *
+ * @param result {Boolean} Function to be wrapped into promise
+ * @param [roleName] {String} Returned value in promise
+ *
+ * @return {Promise}
+ */
+ function wrapInPromise(result, roleName) {
+ var dfd = $q.defer();
+
+ if (result) {
+ dfd.resolve(roleName);
+ } else {
+ dfd.reject(roleName);
}
- /**
- * Returns role definition object by it's name
- *
- * @returns {Object} Role definition object
- */
- function getRoleDefinition(roleName) {
- return roleStore[roleName];
+ return dfd.promise;
+ }
+
+ /**
+ * Checks if provided permission has accepted parameter types
+ * @method
+ * @private
+ * @throws {TypeError}
+ */
+ function validateConstructor(roleName, permissionNames, validationFunction) {
+ if (!angular.isString(roleName)) {
+ throw new TypeError('Parameter "roleName" name must be String');
}
- /**
- * Returns all role definitions
- *
- * @returns {Object} Defined roles collection
- */
- function getStore() {
- return roleStore;
+ if (!angular.isArray(permissionNames)) {
+ throw new TypeError('Parameter "permissionNames" must be Array');
}
- /**
- * Removes all role definitions
- */
- function clearStore() {
- roleStore = {};
+ if (!permissionNames.length && !angular.isFunction(validationFunction)) {
+ throw new TypeError('Parameter "validationFunction" must be provided for empty "permissionNames" array');
}
- }]);
+ }
+
+ return Role;
+ }
+
+ angular
+ .module('permission')
+ .factory('Role', RoleFactory);
+
}());
+
(function () {
'use strict';
/**
- * Show/hide elements based on provided permissions
+ * Permission definition storage
+ * @name PermissionStore
+ * @memberOf permission
*
- * @example
- * <div permission only="'USER'"></div>
- * <div permission only="['USER','ADMIN']" except="'MANAGER'"></div>
- * <div permission except="'MANAGER'"></div>
+ * @param Permission {permission.PermissionFactory} Permission definition factory
*/
+ PermissionStore.$inject = ['Permission'];
+ function PermissionStore(Permission) {
+ /**
+ * @property permissionStore
+ *
+ * @type {Object}
+ */
+ var permissionStore = {};
+
+ this.definePermission = definePermission;
+ this.defineManyPermissions = defineManyPermissions;
+ this.removePermissionDefinition = removePermissionDefinition;
+ this.hasPermissionDefinition = hasPermissionDefinition;
+ this.getPermissionDefinition = getPermissionDefinition;
+ this.getStore = getStore;
+ this.clearStore = clearStore;
+
+ /**
+ * Allows to define permission on application configuration
+ * @method
+ *
+ * @param permissionName {String} Name of defined permission
+ * @param validationFunction {Function} Function used to validate if permission is valid
+ */
+ function definePermission(permissionName, validationFunction) {
+ var permission = new Permission(permissionName, validationFunction);
+ permissionStore[permissionName] = permission;
+ }
+
+ /**
+ * Allows to define set of permissionNames with shared validation function on application configuration
+ * @method
+ * @throws {TypeError}
+ *
+ * @param permissionNames {Array<String>} Set of permission names
+ * @param validationFunction {Function} Function used to validate if permission is valid
+ */
+ function defineManyPermissions(permissionNames, validationFunction) {
+ if (!angular.isArray(permissionNames)) {
+ throw new TypeError('Parameter "permissionNames" name must be Array');
+ }
+
+ angular.forEach(permissionNames, function (permissionName) {
+ definePermission(permissionName, validationFunction);
+ });
+ }
+
+ /**
+ * Deletes permission
+ * @method
+ *
+ * @param permissionName {String} Name of defined permission
+ */
+ function removePermissionDefinition(permissionName) {
+ delete permissionStore[permissionName];
+ }
+
+ /**
+ * Checks if permission exists
+ * @method
+ *
+ * @param permissionName {String} Name of defined permission
+ * @returns {Boolean}
+ */
+ function hasPermissionDefinition(permissionName) {
+ return angular.isDefined(permissionStore[permissionName]);
+ }
+
+ /**
+ * Returns permission by it's name
+ * @method
+ *
+ * @returns {permission.Permission} Permissions definition object
+ */
+ function getPermissionDefinition(permissionName) {
+ return permissionStore[permissionName];
+ }
+
+ /**
+ * Returns all permissions
+ * @method
+ *
+ * @returns {Object} Permissions collection
+ */
+ function getStore() {
+ return permissionStore;
+ }
+
+ /**
+ * Removes all permissions
+ * @method
+ */
+ function clearStore() {
+ permissionStore = {};
+ }
+ }
+
angular
.module('permission')
- .directive('permission', ['$log', 'Authorization', 'PermissionMap', function ($log, Authorization, PermissionMap) {
- return {
- restrict: 'A',
- bindToController: {
- only: '=',
- except: '='
- },
- controllerAs: 'permission',
- controller: ['$scope', '$element', function ($scope, $element) {
- var permission = this;
-
- $scope.$watchGroup(['permission.only', 'permission.except'],
- function () {
- try {
- Authorization
- .authorize(new PermissionMap({
- only: permission.only,
- except: permission.except
- }), null)
- .then(function () {
- $element.removeClass('ng-hide');
- })
- .catch(function () {
- $element.addClass('ng-hide');
- });
- } catch (e) {
- $element.addClass('ng-hide');
- $log.error(e.message);
- }
- });
- }]
- };
- }]);
+ .service('PermissionStore', PermissionStore);
}());
(function () {
'use strict';
+ /**
+ * Role definition storage
+ * @name RoleStore
+ * @memberOf permission
+ *
+ * @param Role {permission.Role|Function} Role definition constructor
+ */
+ RoleStore.$inject = ['Role'];
+ function RoleStore(Role) {
+ var roleStore = {};
+
+ this.defineRole = defineRole;
+ this.getRoleDefinition = getRoleDefinition;
+ this.hasRoleDefinition = hasRoleDefinition;
+ this.removeRoleDefinition = removeRoleDefinition;
+ this.getStore = getStore;
+ this.clearStore = clearStore;
+
+ /**
+ * Allows to define role
+ * @method
+ *
+ * @param roleName {String} Name of defined role
+ * @param permissions {Array<String>} Set of permission names
+ * @param [validationFunction] {Function} Function used to validate if permissions in role are valid
+ */
+ function defineRole(roleName, permissions, validationFunction) {
+ roleStore[roleName] = new Role(roleName, permissions, validationFunction);
+ }
+
+ /**
+ * Deletes role from store
+ * @method
+ *
+ * @param roleName {String} Name of defined permission
+ */
+ function removeRoleDefinition(roleName) {
+ delete roleStore[roleName];
+ }
+
+ /**
+ * Checks if role is defined in store
+ * @method
+ *
+ * @param roleName {String} Name of role
+ * @returns {Boolean}
+ */
+ function hasRoleDefinition(roleName) {
+ return angular.isDefined(roleStore[roleName]);
+ }
+
+ /**
+ * Returns role definition object by it's name
+ * @method
+ *
+ * @returns {permission.Role} Role definition object
+ */
+ function getRoleDefinition(roleName) {
+ return roleStore[roleName];
+ }
+
+ /**
+ * Returns all role definitions
+ * @method
+ *
+ * @returns {Object} Defined roles collection
+ */
+ function getStore() {
+ return roleStore;
+ }
+
+ /**
+ * Removes all role definitions
+ * @method
+ */
+ function clearStore() {
+ roleStore = {};
+ }
+ }
+
angular
.module('permission')
- .service('Authorization', ['$q', 'PermissionMap', 'PermissionStore', 'RoleStore', function ($q, PermissionMap, PermissionStore, RoleStore) {
- this.authorize = authorize;
+ .service('RoleStore', RoleStore);
+}());
- /**
- * Checks if provided permissions are acceptable
- *
- * @param permissionsMap {PermissionMap} Map of permission names
- * @param [toParams] {Object} UI-Router params object
- * @returns {promise} $q.promise object
- */
- function authorize(permissionsMap, toParams) {
- return handleAuthorization(permissionsMap, toParams);
- }
+(function () {
+ 'use strict';
- /**
- * Handles authorization based on provided permissions map
- * @private
- *
- * @param permissionsMap {Object} Map of permission names
- * @param toParams {Object} UI-Router params object
- * @returns {promise} $q.promise object
- */
- function handleAuthorization(permissionsMap, toParams) {
- var deferred = $q.defer();
+ /**
+ * Handles authorization based on provided permissions/roles.
+ * @name permissionDirective
+ * @memberOf permission
+ *
+ * Directive accepts single or combined attributes `permission-only` and `permission-except` that checks on
+ * DOM rendering if permissions/roles are met. Attributes can be passed either as String, Array or variable from
+ * parent scope. Directive also will watch for changes if applied and automatically update the view.
+ *
+ * @example
+ * <div permission
+ * permission-only="'USER'">
+ * </div>
+ * <div permission
+ * permission-only="['USER','ADMIN']"
+ * permission-except="'MANAGER'">
+ * </div>
+ *
+ * By default directive will show/hide elements if provided permissions matches.
+ * You can override this behaviour by passing `permission-on-authorized` and `permission-on-unauthorized`
+ * attributes that will pass to your function `$element` as argument that you can freely manipulate your DOM
+ * behaviour.
+ *
+ * Important! Function should be as references - `vm.disableElement` not `vm.disableElement()` to be able to
+ * accept passed $element reference from inside of permissionDirective
+ *
+ * @example
+ * <div permission
+ * permission-only="['USER','ADMIN']"
+ * permission-on-authorized="PermissionStrategies.disableElement"
+ * permission-on-unauthorized="PermissionStrategies.enableElement">
+ * </div>
+ *
+ * @param $log {Object} Logging service
+ * @param Authorization {permission.Authorization} Authorization service
+ * @param PermissionMap {permission.PermissionMap} Map of state access rights
+ * @param PermissionStrategies {permission.PermissionStrategies} Set of pre-defined directive behaviours
+ *
+ * @returns {Object} Directive instance
+ */
+ permissionDirective.$inject = ['$log', 'Authorization', 'PermissionMap', 'PermissionStrategies'];
+ function permissionDirective($log, Authorization, PermissionMap, PermissionStrategies) {
+ return {
+ restrict: 'A',
+ bindToController: {
+ only: '=?permissionOnly',
+ except: '=?permissionExcept',
+ onAuthorized: '&?permissionOnAuthorized',
+ onUnauthorized: '&?permissionOnUnauthorized',
+ // Observing attribute `only` and `except` will be removed with version 2.4.0+
+ deprecatedOnly: '=only',
+ deprecatedExcept: '=except'
+ },
+ controllerAs: 'permission',
+ controller: ['$scope', '$element', function ($scope, $element) {
+ var permission = this;
+
+ if (angular.isDefined(permission.deprecatedOnly) || angular.isDefined(permission.deprecatedExcept)) {
+ $log.warn('Attributes "only" and "except" are deprecated since 2.2.0+ and their support ' +
+ 'will be removed from 2.4.0. Use scoped "permission-only" and "permission-except" instead.');
+ }
- var exceptPromises = findMatchingPermissions(permissionsMap.except, toParams);
+ /**
+ * Observing attribute `only` and `except` will be removed with version 2.4.0+
+ */
+ $scope.$watchGroup(['permission.only', 'permission.except',
+ 'permission.deprecatedOnly', 'permission.deprecatedExcept'],
+ function () {
+ try {
+ var permissionMap = new PermissionMap({
+ only: permission.only || permission.deprecatedOnly,
+ except: permission.except || permission.deprecatedExcept
+ });
- only(exceptPromises)
- .then(function (rejectedPermissions) {
- deferred.reject(rejectedPermissions);
- })
- .catch(function () {
- if (!permissionsMap.only.length) {
- deferred.resolve(null);
+ Authorization
+ .authorize(permissionMap)
+ .then(function () {
+ onAuthorizedAccess();
+ })
+ .catch(function () {
+ onUnauthorizedAccess();
+ });
+ } catch (e) {
+ onUnauthorizedAccess();
+ $log.error(e.message);
}
+ });
- var onlyPromises = findMatchingPermissions(permissionsMap.only, toParams);
+ /**
+ * Calls `onAuthorized` function if provided or show element
+ * @private
+ */
+ function onAuthorizedAccess() {
+ if (angular.isFunction(permission.onAuthorized)) {
+ permission.onAuthorized()($element);
+ } else {
+ PermissionStrategies.showElement($element);
+ }
+ }
- only(onlyPromises)
- .then(function (resolvedPermissions) {
- deferred.resolve(resolvedPermissions);
- })
- .catch(function (rejectedPermission) {
- deferred.reject(rejectedPermission);
- });
- });
+ /**
+ * Calls `onUnauthorized` function if provided or hide element
+ * @private
+ */
+ function onUnauthorizedAccess() {
+ if (angular.isFunction(permission.onUnauthorized)) {
+ permission.onUnauthorized()($element);
+ } else {
+ PermissionStrategies.hideElement($element);
+ }
+ }
+ }]
+ };
+ }
+
+ angular
+ .module('permission')
+ .directive('permission', permissionDirective);
- return deferred.promise;
+}());
+
+
+(function () {
+ 'use strict';
+
+ /**
+ * Service responsible for handling view based authorization
+ * @name Authorization
+ * @memberOf permission
+ *
+ * @param $q {Object} Angular promise implementation
+ */
+ Authorization.$inject = ['$q'];
+ function Authorization($q) {
+
+ this.authorize = authorize;
+
+ /**
+ * Handles authorization based on provided permissions map
+ * @method
+ *
+ * @param permissionsMap {permission.PermissionMap} Map of permission names
+ *
+ * @returns {promise} $q.promise object
+ */
+ function authorize(permissionsMap) {
+
+ return authorizePermissionMap(permissionsMap);
+ }
+
+ /**
+ * Checks authorization for simple view based access
+ * @method
+ * @private
+ *
+ * @param map {permission.PermissionMap} Access rights map
+ *
+ * @returns {promise} $q.promise object
+ */
+ function authorizePermissionMap(map) {
+ var deferred = $q.defer();
+
+ resolveExceptPrivilegeMap(deferred, map);
+
+ return deferred.promise;
+ }
+
+ /**
+ * Resolves flat set of "except" privileges
+ * @method
+ * @private
+ *
+ * @param deferred {Object} Promise defer
+ * @param map {permission.PermissionMap} Access rights map
+ *
+ * @returns {Promise} $q.promise object
+ */
+ function resolveExceptPrivilegeMap(deferred, map) {
+ var exceptPromises = map.resolvePropertyValidity(map.except);
+
+ $q.any(exceptPromises)
+ .then(function (rejectedPermissions) {
+ deferred.reject(rejectedPermissions);
+ })
+ .catch(function () {
+ resolveOnlyPermissionMap(deferred, map);
+ });
+ }
+
+ /**
+ * Resolves flat set of "only" privileges
+ * @method
+ * @private
+ *
+ * @param deferred {Object} Promise defer
+ * @param map {permission.PermissionMap} Access rights map
+ */
+ function resolveOnlyPermissionMap(deferred, map) {
+ if (!map.only.length) {
+ deferred.resolve();
+ return;
}
- /**
- * Implementation of missing $q `only` method that wits for first
- * resolution of provided promise set.
- * @private
- *
- * @param promises {Array|promise} Single or set of promises
- * @returns {Promise} Returns a single promise that will be rejected with an array/hash of values,
- * each value corresponding to the promise at the same index/key in the `promises` array/hash.
- * If any of the promises is resolved, this resulting promise will be returned
- * with the same resolution value.
- */
- function only(promises) {
- var deferred = $q.defer(),
- counter = 0,
- results = angular.isArray(promises) ? [] : {};
-
- angular.forEach(promises, function (promise, key) {
- counter++;
- $q.when(promise)
- .then(function (value) {
- if (results.hasOwnProperty(key)) {
- return;
- }
- deferred.resolve(value);
- })
- .catch(function (reason) {
- if (results.hasOwnProperty(key)) {
- return;
- }
- results[key] = reason;
- if (!(--counter)) {
- deferred.reject(reason);
- }
- });
+ var onlyPromises = map.resolvePropertyValidity(map.only);
+ $q.any(onlyPromises)
+ .then(function (resolvedPermissions) {
+ deferred.resolve(resolvedPermissions);
+ })
+ .catch(function (rejectedPermission) {
+ deferred.reject(rejectedPermission);
});
+ }
+ }
- if (counter === 0) {
- deferred.reject(results);
- }
+ angular
+ .module('permission')
+ .service('Authorization', Authorization);
- return deferred.promise;
- }
+})();
- /**
- * Performs iteration over list of defined permissions looking for matching roles
- * @private
- *
- * @param permissionNames {Array} Set of permission names
- * @param toParams {Object} UI-Router params object
- * @returns {Array} Promise collection
- */
- function findMatchingPermissions(permissionNames, toParams) {
- return permissionNames.map(function (permissionName) {
- if (RoleStore.hasRoleDefinition(permissionName)) {
- return handleRoleValidation(permissionName, toParams);
- }
- if (PermissionStore.hasPermissionDefinition(permissionName)) {
- return handlePermissionValidation(permissionName, toParams);
- }
+(function () {
+ 'use strict';
- if (permissionName) {
- return $q.reject(permissionName);
- }
+ /**
+ * Service responsible for handling state based authorization
+ * @name StateAuthorization
+ * @memberOf permission
+ *
+ * @param $q {Object} Angular promise implementation
+ */
+ StateAuthorization.$inject = ['$q'];
+ function StateAuthorization($q) {
+
+ this.authorize = authorize;
+
+ /**
+ * Handles state authorization
+ * @method {permission.StatePermissionMap}
+ * @param statePermissionMap
+ *
+ * @return {promise}
+ */
+ function authorize(statePermissionMap) {
+ return authorizeStatePermissionMap(statePermissionMap);
+ }
+
+ /**
+ * Checks authorization for complex state inheritance
+ * @method
+ * @private
+ *
+ * @param map {permission.StatePermissionMap} State access rights map
+ *
+ * @returns {promise} $q.promise object
+ */
+ function authorizeStatePermissionMap(map) {
+ var deferred = $q.defer();
+
+ resolveExceptStatePermissionMap(deferred, map);
+
+ return deferred.promise;
+ }
+
+ /**
+ * Resolves compensated set of "except" privileges
+ * @method
+ * @private
+ *
+ * @param deferred {Object} Promise defer
+ * @param map {permission.StatePermissionMap} State access rights map
+ */
+ function resolveExceptStatePermissionMap(deferred, map) {
+ var exceptPromises = resolveStatePermissionMap(map.except, map);
+
+ $q.all(exceptPromises)
+ .then(function (rejectedPermissions) {
+ deferred.reject(rejectedPermissions);
+ })
+ .catch(function () {
+ resolveOnlyStatePermissionMap(deferred, map);
});
+ }
+
+ /**
+ * Resolves compensated set of "only" privileges
+ * @method
+ * @private
+ *
+ * @param deferred {Object} Promise defer
+ * @param map {permission.StatePermissionMap} State access rights map
+ */
+ function resolveOnlyStatePermissionMap(deferred, map) {
+ if (!map.only.length) {
+ deferred.resolve();
+ return;
}
- /**
- * Executes role validation checking
- * @private
- *
- * @param roleName {String} Store permission key
- * @param toParams {Object} UI-Router params object
- * @returns {Promise}
- */
- function handleRoleValidation(roleName, toParams) {
- var role = RoleStore.getRoleDefinition(roleName);
- return role.validateRole(toParams);
- }
+ var onlyPromises = resolveStatePermissionMap(map.only, map);
- /**
- * Executes permission validation checking
- * @private
- *
- * @param permissionName {String} Store permission key
- * @param toParams {Object} UI-Router params object
- * @returns {Promise}
- */
- function handlePermissionValidation(permissionName, toParams) {
- var permission = PermissionStore.getPermissionDefinition(permissionName);
- return permission.validatePermission(toParams);
+ $q.all(onlyPromises)
+ .then(function (resolvedPermissions) {
+ deferred.resolve(resolvedPermissions);
+ })
+ .catch(function (rejectedPermission) {
+ deferred.reject(rejectedPermission);
+ });
+ }
+
+ /**
+ * Performs iteration over list of privileges looking for matches
+ * @method
+ * @private
+ *
+ * @param privilegesNames {Array} Array of sets of access rights
+ * @param map {permission.StatePermissionMap} State access rights map
+ *
+ * @returns {Array<Promise>} Promise collection
+ */
+ function resolveStatePermissionMap(privilegesNames, map) {
+ if (!privilegesNames.length) {
+ return [$q.reject()];
}
- }]);
+
+ return privilegesNames.map(function (statePrivileges) {
+ var resolvedStatePrivileges = map.resolvePropertyValidity(statePrivileges);
+ return $q.any(resolvedStatePrivileges);
+ });
+ }
+ }
+
+ angular
+ .module('permission')
+ .service('StateAuthorization', StateAuthorization);
+
})();