Built motion from commit 99feb03.|0.0.140
[motion.git] / public / bower_components / angular-permission / angular-permission.js
1 /**
2  * angular-permission
3  * Route permission and access control as simple as it can get
4  * @version v2.3.6 - 2016-04-11
5  * @link https://github.com/Narzerus/angular-permission
6  * @author Rafael Vidaurre <narzerus@gmail.com> (http://www.rafaelvidaurre.com), Blazej Krysiak <blazej.krysiak@gmail.com>
7  * @license MIT License, http://www.opensource.org/licenses/MIT
8  */
9
10 (function () {
11   'use strict';
12
13   /**
14    * @namespace permission
15    */
16
17   config.$inject = ['$stateProvider'];
18   run.$inject = ['$rootScope', '$location', '$state', 'TransitionProperties', 'TransitionEvents', 'StateAuthorization', 'StatePermissionMap'];
19   function config($stateProvider) {
20     /**
21      * This decorator is required to access full state object instead of it's configuration
22      * when trying to obtain full toState state object not it's configuration
23      * Can be removed when implemented https://github.com/angular-ui/ui-router/issues/13.
24      */
25     $stateProvider.decorator('parent', function (state, parentFn) {
26       state.self.$$state = function () {
27         return state;
28       };
29
30       state.self.areSetStatePermissions = function () {
31         return angular.isDefined(state.data) && angular.isDefined(state.data.permissions);
32       };
33
34       return parentFn(state);
35     });
36   }
37
38   function run($rootScope, $location, $state, TransitionProperties, TransitionEvents, StateAuthorization, StatePermissionMap) {
39     /**
40      * State transition interceptor
41      */
42     $rootScope.$on('$stateChangeStart', function (event, toState, toParams, fromState, fromParams, options) {
43
44       if (!isAuthorizationFinished()) {
45         event.preventDefault();
46
47         setStateAuthorizationStatus(true);
48         setTransitionProperties();
49
50         if (!TransitionEvents.areStateEventsDefaultPrevented()) {
51           TransitionEvents.broadcastStateChangePermissionStart();
52
53           var statePermissionMap = new StatePermissionMap();
54
55           StateAuthorization
56             .authorize(statePermissionMap)
57             .then(function () {
58               handleAuthorizedState();
59             })
60             .catch(function (rejectedPermission) {
61               handleUnauthorizedState(rejectedPermission, statePermissionMap);
62             })
63             .finally(function () {
64               setStateAuthorizationStatus(false);
65             });
66         }
67       }
68
69       /**
70        * Updates values of `TransitionProperties` holder object
71        * @method
72        * @private
73        */
74       function setTransitionProperties() {
75         TransitionProperties.toState = toState;
76         TransitionProperties.toParams = toParams;
77         TransitionProperties.fromState = fromState;
78         TransitionProperties.fromParams = fromParams;
79         TransitionProperties.options = options;
80       }
81
82       /**
83        * Sets internal state `$$finishedAuthorization` variable to prevent looping
84        * @method
85        * @private
86        *
87        *
88        * @param status {boolean} When true authorization has been already preceded
89        */
90       function setStateAuthorizationStatus(status) {
91         angular.extend(toState, {'$$isAuthorizationFinished': status});
92       }
93
94       /**
95        * Checks if state has been already checked for authorization
96        * @method
97        * @private
98        *
99        * @returns {boolean}
100        */
101       function isAuthorizationFinished() {
102         return toState.$$isAuthorizationFinished;
103       }
104
105       /**
106        * Handles redirection for authorized access
107        * @method
108        * @private
109        */
110       function handleAuthorizedState() {
111
112         TransitionEvents.broadcastStateChangePermissionAccepted();
113         $location.replace();
114
115         // Overwrite notify option to broadcast it later
116         TransitionProperties.options = angular.extend({}, TransitionProperties.options, {notify: false});
117
118         $state
119           .go(TransitionProperties.toState.name, TransitionProperties.toParams, TransitionProperties.options)
120           .then(function () {
121             TransitionEvents.broadcastStateChangeSuccess();
122           });
123       }
124
125       /**
126        * Handles redirection for unauthorized access
127        * @method
128        * @private
129        *
130        * @param rejectedPermission {String} Rejected access right
131        * @param statePermissionMap {permission.StatePermissionMap} State permission map
132        */
133       function handleUnauthorizedState(rejectedPermission, statePermissionMap) {
134         TransitionEvents.broadcastStateChangePermissionDenied();
135
136         statePermissionMap
137           .resolveRedirectState(rejectedPermission)
138           .then(function (redirect) {
139             $state.go(redirect.state, redirect.params, redirect.options);
140           });
141       }
142     });
143   }
144
145   angular.module('permission', ['ui.router'])
146     .config(config)
147     .run(run);
148 }());
149
150
151 (function () {
152   'use strict';
153
154   /**
155    * Extends $q implementation by A+ *any* method
156    * @name $q
157    * @extends {angular.$q}
158    * @memberOf permission
159    *
160    * @param $delegate {Object} Angular promise implementation
161    */
162   $q.$inject = ['$delegate'];
163   function $q($delegate) {
164
165     $delegate.any = any;
166
167     /**
168      * Implementation of missing $q `any` method that wits for first resolution of provided promise set
169      * @method
170      *
171      * @param promises {Array|promise} Single or set of promises
172      *
173      * @returns {Promise} Returns a single promise that will be rejected with an array/hash of values,
174      *  each value corresponding to the promise at the same index/key in the `promises` array/hash.
175      *  If any of the promises is resolved, this resulting promise will be returned
176      *  with the same resolution value.
177      */
178     function any(promises) {
179       var deferred = $delegate.defer(),
180         counter = 0,
181         results = angular.isArray(promises) ? [] : {};
182
183       angular.forEach(promises, function (promise, key) {
184         counter++;
185         $delegate
186           .when(promise)
187           .then(function (value) {
188             deferred.resolve(value);
189           })
190           .catch(function (reason) {
191             results[key] = reason;
192             if (!(--counter)) {
193               deferred.reject(reason);
194             }
195           });
196       });
197
198       if (counter === 0) {
199         deferred.reject(results);
200       }
201
202       return deferred.promise;
203     }
204
205     return $delegate;
206   }
207
208   angular
209     .module('permission')
210     .decorator('$q', $q);
211
212 })();
213
214
215 (function () {
216   'use strict';
217
218   /**
219    * Pre-defined available configurable behaviours of directive `permission`
220    * @name PermissionStrategies
221    * @memberOf permission
222    * @readonly
223    *
224    * @example
225    * <div permission
226    *      permission-except="'MANAGER'"
227    *      permission-on-authorized="PermissionStrategies.renderContent"
228    *      permission-on-unauthorized="PermissionStrategies.removeContent">
229    * </div>
230    *
231    * @property enableElement {Function}
232    * @property disableElement {Function}
233    * @property showElement {Function}
234    * @property hideElement {Function}
235    */
236   var PermissionStrategies = {
237     enableElement: function ($element) {
238       $element.removeAttr('disabled');
239     },
240     disableElement: function ($element) {
241       $element.attr('disabled', 'disabled');
242     },
243     showElement: function ($element) {
244       $element.removeClass('ng-hide');
245     },
246     hideElement: function ($element) {
247       $element.addClass('ng-hide');
248     }
249   };
250
251   angular
252     .module('permission')
253     .constant('PermissionStrategies', PermissionStrategies);
254
255 }());
256
257
258 (function () {
259   'use strict';
260
261   /**
262    * Helper object used for storing ui-router transition parameters
263    * @name TransitionProperties
264    * @memberOf permission
265    *
266    * @type {Object.<String,Object>}
267    *
268    * UI-router transition properties:
269    * @property toState {Object} Target state object
270    * @property toParams {Object} Target state params
271    * @property fromState {Object} Source state object
272    * @property fromParams {Object} Source state params
273    * @property options {Object} Transition options
274    */
275   var TransitionProperties = {
276     toState: undefined,
277     toParams: undefined,
278     fromState: undefined,
279     fromParams: undefined,
280     options: undefined
281   };
282
283   angular
284     .module('permission')
285     .value('TransitionProperties', TransitionProperties);
286
287 }());
288
289 (function () {
290   'use strict';
291
292   /**
293    * Service responsible for managing and emitting events
294    * @name TransitionEvents
295    * @memberOf permission
296    *
297    * @param TransitionProperties {permission.TransitionProperties} Helper storing ui-router transition parameters
298    * @param $rootScope {Object} Top-level angular scope
299    */
300   TransitionEvents.$inject = ['$rootScope', 'TransitionProperties'];
301   function TransitionEvents($rootScope, TransitionProperties) {
302
303     this.areStateEventsDefaultPrevented = areStateEventsDefaultPrevented;
304     this.broadcastStateChangePermissionStart = broadcastStateChangePermissionStart;
305     this.broadcastStateChangePermissionAccepted = broadcastStateChangePermissionAccepted;
306     this.broadcastStateChangePermissionDenied = broadcastStateChangePermissionDenied;
307     this.broadcastStateChangeSuccess = broadcastStateChangeSuccess;
308
309     /**
310      * Checks if state events are not prevented by default
311      * @method
312      *
313      * @returns {boolean}
314      */
315     function areStateEventsDefaultPrevented() {
316       return isStateChangePermissionStartDefaultPrevented() || isStateChangeStartDefaultPrevented();
317     }
318
319     /**
320      * Broadcasts "$stateChangePermissionStart" event from $rootScope
321      * @method
322      */
323     function broadcastStateChangePermissionStart() {
324       $rootScope.$broadcast('$stateChangePermissionStart',
325         TransitionProperties.toState, TransitionProperties.toParams,
326         TransitionProperties.options);
327     }
328
329     /**
330      * Broadcasts "$stateChangePermissionAccepted" event from $rootScope
331      * @method
332      */
333     function broadcastStateChangePermissionAccepted() {
334       $rootScope.$broadcast('$stateChangePermissionAccepted',
335         TransitionProperties.toState, TransitionProperties.toParams,
336         TransitionProperties.options);
337     }
338
339     /**
340      * Broadcasts "$stateChangeSuccess" event from $rootScope
341      * @method
342      */
343     function broadcastStateChangeSuccess() {
344       $rootScope.$broadcast('$stateChangeSuccess',
345         TransitionProperties.toState, TransitionProperties.toParams,
346         TransitionProperties.fromState, TransitionProperties.fromParams);
347     }
348
349     /**
350      * Broadcasts "$tateChangePermissionDenied" event from $rootScope
351      * @method
352      */
353     function broadcastStateChangePermissionDenied() {
354       $rootScope.$broadcast('$stateChangePermissionDenied',
355         TransitionProperties.toState, TransitionProperties.toParams,
356         TransitionProperties.options);
357     }
358
359     /**
360      * Checks if event $stateChangeStart hasn't been disabled by default
361      * @method
362      * @private
363      *
364      * @returns {boolean}
365      */
366     function isStateChangeStartDefaultPrevented() {
367       return $rootScope.$broadcast('$stateChangeStart',
368         TransitionProperties.toState, TransitionProperties.toParams,
369         TransitionProperties.fromState, TransitionProperties.fromParams,
370         TransitionProperties.options).defaultPrevented;
371     }
372
373     /**
374      * Checks if event $stateChangePermissionStart hasn't been disabled by default
375      * @method
376      * @private
377      *
378      * @returns {boolean}
379      */
380     function isStateChangePermissionStartDefaultPrevented() {
381       return $rootScope.$broadcast('$stateChangePermissionStart',
382         TransitionProperties.toState, TransitionProperties.toParams,
383         TransitionProperties.options).defaultPrevented;
384     }
385   }
386
387   angular
388     .module('permission')
389     .service('TransitionEvents', TransitionEvents);
390
391 }());
392
393 (function () {
394   'use strict';
395
396   /**
397    * Access rights map factory
398    * @name PermissionMapFactory
399    *
400    * @param $q {Object} Angular promise implementation
401    * @param TransitionProperties {permission.TransitionProperties} Helper storing ui-router transition parameters
402    * @param RoleStore {permission.RoleStore} Role definition storage
403    * @param PermissionStore {permission.PermissionStore} Permission definition storage
404    *
405    * @return {permission.PermissionMap}
406    */
407   PermissionMapFactory.$inject = ['$q', 'TransitionProperties', 'RoleStore', 'PermissionStore'];
408   function PermissionMapFactory($q, TransitionProperties, RoleStore, PermissionStore) {
409     /**
410      * Constructs map object instructing authorization service how to handle authorizing
411      * @constructor PermissionMap
412      * @memberOf permission
413      *
414      * @param [permissionMap] {Object} Map of permissions provided to authorization service
415      * @param [permissionMap.only] {Array} List of exclusive access right names allowed for authorization
416      * @param [permissionMap.except] {Array} List of exclusive access right names denied for authorization
417      * @param [permissionMap.redirectTo] {String|Function|Object|promise} Handling redirection when rejected
418      *   authorization
419      */
420     function PermissionMap(permissionMap) {
421       // Suppress not defined object errors
422       permissionMap = permissionMap || {};
423
424       this.only = normalizeMapProperty(permissionMap.only);
425       this.except = normalizeMapProperty(permissionMap.except);
426       this.redirectTo = permissionMap.redirectTo;
427     }
428
429     /**
430      * Redirects to fallback states when permissions fail
431      * @method
432      * @methodOf permission.PermissionMap
433      *
434      * @param rejectedPermissionName {String} Permission name
435      *
436      * @return {Promise}
437      */
438     PermissionMap.prototype.resolveRedirectState = function (rejectedPermissionName) {
439       if (angular.isFunction(this.redirectTo)) {
440         return resolveFunctionRedirect(this.redirectTo, rejectedPermissionName);
441       }
442
443       if (angular.isObject(this.redirectTo)) {
444         return resolveObjectRedirect(this.redirectTo, rejectedPermissionName);
445       }
446
447       if (angular.isString(this.redirectTo)) {
448         return $q.resolve({
449           state: this.redirectTo
450         });
451       }
452
453       // If redirectTo state is not defined stay where you are
454       return $q.reject(null);
455     };
456
457     /**
458      * Resolves weather permissions set for "only" or "except" property are valid
459      * @method
460      *
461      * @param property {permissionMap.only|permissionMap.except} "only" or "except" map property
462      * @returns {Array<Promise>}
463      */
464     PermissionMap.prototype.resolvePropertyValidity = function (property) {
465
466       return property.map(function (privilegeName) {
467
468         if (RoleStore.hasRoleDefinition(privilegeName)) {
469           var role = RoleStore.getRoleDefinition(privilegeName);
470           return role.validateRole();
471         }
472
473         if (PermissionStore.hasPermissionDefinition(privilegeName)) {
474           var permission = PermissionStore.getPermissionDefinition(privilegeName);
475           return permission.validatePermission();
476         }
477
478         return $q.reject(privilegeName);
479       });
480     };
481
482     /**
483      * Handles function based redirection for rejected permissions
484      * @method
485      * @methodOf permission.PermissionMap
486      * @throws {TypeError}
487      *
488      * @param redirectFunction {Function} Redirection function
489      * @param permission {String} Rejected permission
490      *
491      * @return {Promise}
492      */
493     function resolveFunctionRedirect(redirectFunction, permission) {
494       return $q
495         .when(redirectFunction.call(null, permission))
496         .then(function (redirectState) {
497           if (angular.isString(redirectState)) {
498             return {
499               state: redirectState
500             };
501           }
502
503           if (angular.isObject(redirectState)) {
504             return redirectState;
505           }
506
507           throw new TypeError('When used "redirectTo" as function, returned value must be string or object');
508         });
509     }
510
511     /**
512      * Handles object based redirection for rejected permissions
513      * @method
514      * @throws {ReferenceError}
515      *
516      * @param redirectObject {Object} Redirection function
517      * @param permission {String} Rejected permission
518      *
519      * @return {Promise}
520      */
521     function resolveObjectRedirect(redirectObject, permission) {
522       if (!angular.isDefined(redirectObject['default'])) {
523         throw new ReferenceError('When used "redirectTo" as object, property "default" must be defined');
524       }
525
526       var redirectState = redirectObject[permission];
527
528       if (!angular.isDefined(redirectState)) {
529         redirectState = redirectObject['default'];
530       }
531
532       if (angular.isFunction(redirectState)) {
533         return resolveFunctionRedirect(redirectState, permission);
534       }
535
536       if (angular.isObject(redirectState)) {
537         return $q.resolve(redirectState);
538       }
539
540       if (angular.isString(redirectState)) {
541         return $q.resolve({
542           state: redirectState
543         });
544       }
545     }
546
547     /**
548      * Handles extraction of permission map "only" and "except" properties and converts them into array objects
549      * @method
550      * @private
551      *
552      * @param property {String|Array|Function|Promise} Permission map property "only" or "except"
553      *
554      * @returns {Array<String>} Array of permission "only" or "except" names
555      */
556     function normalizeMapProperty(property) {
557       if (angular.isString(property)) {
558         return [property];
559       }
560
561       if (angular.isArray(property)) {
562         return property;
563       }
564
565       if (angular.isFunction(property)) {
566         return property.call(null, TransitionProperties);
567       }
568
569       return [];
570     }
571
572     return PermissionMap;
573   }
574
575   angular
576     .module('permission')
577     .factory('PermissionMap', PermissionMapFactory);
578 }());
579
580 (function () {
581   'use strict';
582
583   /**
584    * State Access rights map factory
585    * @name StatePermissionMapFactory
586    *
587    * @param TransitionProperties {permission.TransitionProperties} Helper storing ui-router transition parameters
588    * @param PermissionMap {permission.PermissionMap}
589    *
590    * @return {permission.StatePermissionMap}
591    */
592   StatePermissionMapFactory.$inject = ['TransitionProperties', 'PermissionMap'];
593   function StatePermissionMapFactory(TransitionProperties, PermissionMap) {
594
595     StatePermissionMap.prototype = new PermissionMap();
596     StatePermissionMap.constructor = StatePermissionMap;
597     StatePermissionMap.prototype.parent = PermissionMap.prototype;
598
599
600     /**
601      * Constructs map object instructing authorization service how to handle authorizing
602      * @constructor StatePermissionMap
603      * @extends PermissionMap
604      * @memberOf permission
605      */
606     function StatePermissionMap() {
607       this.parent.constructor.call(this);
608
609       var toStateObject = TransitionProperties.toState.$$state();
610       var toStatePath = toStateObject.path.slice().reverse();
611
612       angular.forEach(toStatePath, function (state) {
613
614         if (state.areSetStatePermissions()) {
615           var permissionMap = new PermissionMap(state.data.permissions);
616           this.extendPermissionMap(permissionMap);
617         }
618       }, this);
619     }
620
621     /**
622      * Extends permission map by pushing to it state's permissions
623      * @method
624      * @methodOf permission.StatePermissionMap
625      *
626      * @param permissionMap {permission.PermissionMap} Compensated permission map
627      */
628     StatePermissionMap.prototype.extendPermissionMap = function (permissionMap) {
629       if (permissionMap.only.length) {
630         this.only = this.only.concat([permissionMap.only]);
631       }
632       if (permissionMap.except.length) {
633         this.except = this.except.concat([permissionMap.except]);
634       }
635       this.redirectTo = permissionMap.redirectTo;
636     };
637
638     return StatePermissionMap;
639   }
640
641   angular
642     .module('permission')
643     .factory('StatePermissionMap', StatePermissionMapFactory);
644 }());
645
646 (function () {
647   'use strict';
648
649   /**
650    * Permission definition factory
651    * @name PermissionFactory
652    *
653    * @param $q {Object} Angular promise implementation
654    * @param TransitionProperties {permission.TransitionProperties} Helper storing ui-router transition parameters
655    *
656    * @return {permission.Permission}
657    */
658   PermissionFactory.$inject = ['$q', 'TransitionProperties'];
659   function PermissionFactory($q, TransitionProperties) {
660     /**
661      * Permission definition object constructor
662      * @class Permission
663      * @memberOf permission
664      *
665      * @param permissionName {String} Name repressing permission
666      * @param validationFunction {Function} Function used to check if permission is valid
667      */
668     function Permission(permissionName, validationFunction) {
669       validateConstructor(permissionName, validationFunction);
670
671       this.permissionName = permissionName;
672       this.validationFunction = validationFunction;
673     }
674
675     /**
676      * Checks if permission is still valid
677      * @method
678      * @methodOf permission.Permission
679      *
680      * @returns {Promise}
681      */
682     Permission.prototype.validatePermission = function () {
683       var validationResult = this.validationFunction.call(null, this.permissionName, TransitionProperties);
684
685       if (!angular.isFunction(validationResult.then)) {
686         validationResult = wrapInPromise(validationResult, this.permissionName);
687       }
688
689       return validationResult;
690     };
691
692     /**
693      * Converts a value into a promise, if the value is truthy it resolves it, otherwise it rejects it
694      * @method
695      * @private
696      *
697      * @param result {Boolean} Function to be wrapped into promise
698      * @param permissionName {String} Returned value in promise
699      * @return {Promise}
700      */
701     function wrapInPromise(result, permissionName) {
702       var dfd = $q.defer();
703
704       if (result) {
705         dfd.resolve(permissionName);
706       } else {
707         dfd.reject(permissionName);
708       }
709
710       return dfd.promise;
711     }
712
713     /**
714      * Checks if provided permission has accepted parameter types
715      * @method
716      * @private
717      * @throws {TypeError}
718      */
719     function validateConstructor(permissionName, validationFunction) {
720       if (!angular.isString(permissionName)) {
721         throw new TypeError('Parameter "permissionName" name must be String');
722       }
723       if (!angular.isFunction(validationFunction)) {
724         throw new TypeError('Parameter "validationFunction" must be Function');
725       }
726     }
727
728     return Permission;
729   }
730
731   angular
732     .module('permission')
733     .factory('Permission', PermissionFactory);
734
735 }());
736
737 (function () {
738   'use strict';
739
740   /**
741    * Role definition factory
742    * @name RoleFactory
743    *
744    * @param $q {Object} Angular promise implementation
745    * @param PermissionStore {permission.PermissionStore} Permission definition storage
746    * @param TransitionProperties {permission.TransitionProperties} Helper storing ui-router transition parameters
747    *
748    * @return {permission.Role}
749    */
750   RoleFactory.$inject = ['$q', 'PermissionStore', 'TransitionProperties'];
751   function RoleFactory($q, PermissionStore, TransitionProperties) {
752     /**
753      * Role definition constructor
754      * @class Role
755      * @memberOf permission
756      *
757      * @param roleName {String} Name representing role
758      * @param permissionNames {Array} List of permission names representing role
759      * @param [validationFunction] {Function} Optional function used to validate if permissions are still valid
760      */
761     function Role(roleName, permissionNames, validationFunction) {
762       validateConstructor(roleName, permissionNames, validationFunction);
763       this.roleName = roleName;
764       this.permissionNames = permissionNames || [];
765       this.validationFunction = validationFunction;
766
767       if (validationFunction) {
768         PermissionStore.defineManyPermissions(permissionNames, validationFunction);
769       }
770     }
771
772     /**
773      * Checks if role is still valid
774      * @method
775      * @methodOf permission.Role
776      *
777      * @returns {Promise} $q.promise object
778      */
779     Role.prototype.validateRole = function () {
780       // When permission set is provided check each of them
781       if (this.permissionNames.length) {
782         var promises = this.permissionNames.map(function (permissionName) {
783           if (PermissionStore.hasPermissionDefinition(permissionName)) {
784             var permission = PermissionStore.getPermissionDefinition(permissionName);
785             var validationResult = permission.validatePermission();
786
787             if (!angular.isFunction(validationResult.then)) {
788               validationResult = wrapInPromise(validationResult);
789             }
790
791             return validationResult;
792           }
793
794           return $q.reject();
795         });
796
797         return $q.all(promises);
798       }
799
800       // If not call validation function manually
801       var validationResult = this.validationFunction.call(null, this.roleName, TransitionProperties);
802       if (!angular.isFunction(validationResult.then)) {
803         validationResult = wrapInPromise(validationResult, this.roleName);
804       }
805
806       return $q.resolve(validationResult);
807     };
808
809     /**
810      * Converts a value into a promise, if the value is truthy it resolves it, otherwise it rejects it
811      * @method
812      * @private
813      *
814      * @param result {Boolean} Function to be wrapped into promise
815      * @param [roleName] {String} Returned value in promise
816      *
817      * @return {Promise}
818      */
819     function wrapInPromise(result, roleName) {
820       var dfd = $q.defer();
821
822       if (result) {
823         dfd.resolve(roleName);
824       } else {
825         dfd.reject(roleName);
826       }
827
828       return dfd.promise;
829     }
830
831     /**
832      * Checks if provided permission has accepted parameter types
833      * @method
834      * @private
835      * @throws {TypeError}
836      */
837     function validateConstructor(roleName, permissionNames, validationFunction) {
838       if (!angular.isString(roleName)) {
839         throw new TypeError('Parameter "roleName" name must be String');
840       }
841
842       if (!angular.isArray(permissionNames)) {
843         throw new TypeError('Parameter "permissionNames" must be Array');
844       }
845
846       if (!permissionNames.length && !angular.isFunction(validationFunction)) {
847         throw new TypeError('Parameter "validationFunction" must be provided for empty "permissionNames" array');
848       }
849     }
850
851     return Role;
852   }
853
854   angular
855     .module('permission')
856     .factory('Role', RoleFactory);
857
858 }());
859
860 (function () {
861   'use strict';
862
863   /**
864    * Permission definition storage
865    * @name PermissionStore
866    * @memberOf permission
867    *
868    * @param Permission {permission.PermissionFactory} Permission definition factory
869    */
870   PermissionStore.$inject = ['Permission'];
871   function PermissionStore(Permission) {
872     /**
873      * @property permissionStore
874      *
875      * @type {Object}
876      */
877     var permissionStore = {};
878
879     this.definePermission = definePermission;
880     this.defineManyPermissions = defineManyPermissions;
881     this.removePermissionDefinition = removePermissionDefinition;
882     this.hasPermissionDefinition = hasPermissionDefinition;
883     this.getPermissionDefinition = getPermissionDefinition;
884     this.getStore = getStore;
885     this.clearStore = clearStore;
886
887     /**
888      * Allows to define permission on application configuration
889      * @method
890      *
891      * @param permissionName {String} Name of defined permission
892      * @param validationFunction {Function} Function used to validate if permission is valid
893      */
894     function definePermission(permissionName, validationFunction) {
895       var permission = new Permission(permissionName, validationFunction);
896       permissionStore[permissionName] = permission;
897     }
898
899     /**
900      * Allows to define set of permissionNames with shared validation function on application configuration
901      * @method
902      * @throws {TypeError}
903      *
904      * @param permissionNames {Array<String>} Set of permission names
905      * @param validationFunction {Function} Function used to validate if permission is valid
906      */
907     function defineManyPermissions(permissionNames, validationFunction) {
908       if (!angular.isArray(permissionNames)) {
909         throw new TypeError('Parameter "permissionNames" name must be Array');
910       }
911
912       angular.forEach(permissionNames, function (permissionName) {
913         definePermission(permissionName, validationFunction);
914       });
915     }
916
917     /**
918      * Deletes permission
919      * @method
920      *
921      * @param permissionName {String} Name of defined permission
922      */
923     function removePermissionDefinition(permissionName) {
924       delete permissionStore[permissionName];
925     }
926
927     /**
928      * Checks if permission exists
929      * @method
930      *
931      * @param permissionName {String} Name of defined permission
932      * @returns {Boolean}
933      */
934     function hasPermissionDefinition(permissionName) {
935       return angular.isDefined(permissionStore[permissionName]);
936     }
937
938     /**
939      * Returns permission by it's name
940      * @method
941      *
942      * @returns {permission.Permission} Permissions definition object
943      */
944     function getPermissionDefinition(permissionName) {
945       return permissionStore[permissionName];
946     }
947
948     /**
949      * Returns all permissions
950      * @method
951      *
952      * @returns {Object} Permissions collection
953      */
954     function getStore() {
955       return permissionStore;
956     }
957
958     /**
959      * Removes all permissions
960      * @method
961      */
962     function clearStore() {
963       permissionStore = {};
964     }
965   }
966
967   angular
968     .module('permission')
969     .service('PermissionStore', PermissionStore);
970 }());
971
972 (function () {
973   'use strict';
974
975   /**
976    * Role definition storage
977    * @name RoleStore
978    * @memberOf permission
979    *
980    * @param Role {permission.Role|Function} Role definition constructor
981    */
982   RoleStore.$inject = ['Role'];
983   function RoleStore(Role) {
984     var roleStore = {};
985
986     this.defineRole = defineRole;
987     this.getRoleDefinition = getRoleDefinition;
988     this.hasRoleDefinition = hasRoleDefinition;
989     this.removeRoleDefinition = removeRoleDefinition;
990     this.getStore = getStore;
991     this.clearStore = clearStore;
992
993     /**
994      * Allows to define role
995      * @method
996      *
997      * @param roleName {String} Name of defined role
998      * @param permissions {Array<String>} Set of permission names
999      * @param [validationFunction] {Function} Function used to validate if permissions in role are valid
1000      */
1001     function defineRole(roleName, permissions, validationFunction) {
1002       roleStore[roleName] = new Role(roleName, permissions, validationFunction);
1003     }
1004
1005     /**
1006      * Deletes role from store
1007      * @method
1008      *
1009      * @param roleName {String} Name of defined permission
1010      */
1011     function removeRoleDefinition(roleName) {
1012       delete roleStore[roleName];
1013     }
1014
1015     /**
1016      * Checks if role is defined in store
1017      * @method
1018      *
1019      * @param roleName {String} Name of role
1020      * @returns {Boolean}
1021      */
1022     function hasRoleDefinition(roleName) {
1023       return angular.isDefined(roleStore[roleName]);
1024     }
1025
1026     /**
1027      * Returns role definition object by it's name
1028      * @method
1029      *
1030      * @returns {permission.Role} Role definition object
1031      */
1032     function getRoleDefinition(roleName) {
1033       return roleStore[roleName];
1034     }
1035
1036     /**
1037      * Returns all role definitions
1038      * @method
1039      *
1040      * @returns {Object} Defined roles collection
1041      */
1042     function getStore() {
1043       return roleStore;
1044     }
1045
1046     /**
1047      * Removes all role definitions
1048      * @method
1049      */
1050     function clearStore() {
1051       roleStore = {};
1052     }
1053   }
1054
1055   angular
1056     .module('permission')
1057     .service('RoleStore', RoleStore);
1058 }());
1059
1060 (function () {
1061   'use strict';
1062
1063   /**
1064    * Handles authorization based on provided permissions/roles.
1065    * @name permissionDirective
1066    * @memberOf permission
1067    *
1068    * Directive accepts single or combined attributes `permission-only` and `permission-except` that checks on
1069    * DOM rendering if permissions/roles are met. Attributes can be passed either as String, Array or variable from
1070    * parent scope. Directive also will watch for changes if applied and automatically update the view.
1071    *
1072    * @example
1073    * <div permission
1074    *      permission-only="'USER'">
1075    * </div>
1076    * <div permission
1077    *      permission-only="['USER','ADMIN']"
1078    *      permission-except="'MANAGER'">
1079    * </div>
1080    *
1081    * By default directive will show/hide elements if provided permissions matches.
1082    * You can override this behaviour by passing `permission-on-authorized` and `permission-on-unauthorized`
1083    *   attributes that will pass to your function `$element` as argument that you can freely manipulate your DOM
1084    *   behaviour.
1085    *
1086    * Important! Function should be as references - `vm.disableElement` not `vm.disableElement()` to be able to
1087    *   accept passed $element reference from inside of permissionDirective
1088    *
1089    * @example
1090    * <div permission
1091    *      permission-only="['USER','ADMIN']"
1092    *      permission-on-authorized="PermissionStrategies.disableElement"
1093    *      permission-on-unauthorized="PermissionStrategies.enableElement">
1094    * </div>
1095    *
1096    * @param $log {Object} Logging service
1097    * @param Authorization {permission.Authorization} Authorization service
1098    * @param PermissionMap {permission.PermissionMap} Map of state access rights
1099    * @param PermissionStrategies {permission.PermissionStrategies} Set of pre-defined directive behaviours
1100    *
1101    * @returns {Object} Directive instance
1102    */
1103   permissionDirective.$inject = ['$log', 'Authorization', 'PermissionMap', 'PermissionStrategies'];
1104   function permissionDirective($log, Authorization, PermissionMap, PermissionStrategies) {
1105     return {
1106       restrict: 'A',
1107       bindToController: {
1108         only: '=?permissionOnly',
1109         except: '=?permissionExcept',
1110         onAuthorized: '&?permissionOnAuthorized',
1111         onUnauthorized: '&?permissionOnUnauthorized',
1112         // Observing attribute `only` and `except` will be removed with version 2.4.0+
1113         deprecatedOnly: '=only',
1114         deprecatedExcept: '=except'
1115       },
1116       controllerAs: 'permission',
1117       controller: ['$scope', '$element', function ($scope, $element) {
1118         var permission = this;
1119
1120         if (angular.isDefined(permission.deprecatedOnly) || angular.isDefined(permission.deprecatedExcept)) {
1121           $log.warn('Attributes "only" and "except" are deprecated since 2.2.0+ and their support ' +
1122             'will be removed from 2.4.0. Use scoped "permission-only" and "permission-except" instead.');
1123         }
1124
1125         /**
1126          * Observing attribute `only` and `except` will be removed with version 2.4.0+
1127          */
1128         $scope.$watchGroup(['permission.only', 'permission.except',
1129             'permission.deprecatedOnly', 'permission.deprecatedExcept'],
1130           function () {
1131             try {
1132               var permissionMap = new PermissionMap({
1133                 only: permission.only || permission.deprecatedOnly,
1134                 except: permission.except || permission.deprecatedExcept
1135               });
1136
1137               Authorization
1138                 .authorize(permissionMap)
1139                 .then(function () {
1140                   onAuthorizedAccess();
1141                 })
1142                 .catch(function () {
1143                   onUnauthorizedAccess();
1144                 });
1145             } catch (e) {
1146               onUnauthorizedAccess();
1147               $log.error(e.message);
1148             }
1149           });
1150
1151         /**
1152          * Calls `onAuthorized` function if provided or show element
1153          * @private
1154          */
1155         function onAuthorizedAccess() {
1156           if (angular.isFunction(permission.onAuthorized)) {
1157             permission.onAuthorized()($element);
1158           } else {
1159             PermissionStrategies.showElement($element);
1160           }
1161         }
1162
1163         /**
1164          * Calls `onUnauthorized` function if provided or hide element
1165          * @private
1166          */
1167         function onUnauthorizedAccess() {
1168           if (angular.isFunction(permission.onUnauthorized)) {
1169             permission.onUnauthorized()($element);
1170           } else {
1171             PermissionStrategies.hideElement($element);
1172           }
1173         }
1174       }]
1175     };
1176   }
1177
1178   angular
1179     .module('permission')
1180     .directive('permission', permissionDirective);
1181
1182 }());
1183
1184
1185 (function () {
1186   'use strict';
1187
1188   /**
1189    * Service responsible for handling view based authorization
1190    * @name Authorization
1191    * @memberOf permission
1192    *
1193    * @param $q {Object} Angular promise implementation
1194    */
1195   Authorization.$inject = ['$q'];
1196   function Authorization($q) {
1197
1198     this.authorize = authorize;
1199
1200     /**
1201      * Handles authorization based on provided permissions map
1202      * @method
1203      *
1204      * @param permissionsMap {permission.PermissionMap} Map of permission names
1205      *
1206      * @returns {promise} $q.promise object
1207      */
1208     function authorize(permissionsMap) {
1209
1210       return authorizePermissionMap(permissionsMap);
1211     }
1212
1213     /**
1214      * Checks authorization for simple view based access
1215      * @method
1216      * @private
1217      *
1218      * @param map {permission.PermissionMap} Access rights map
1219      *
1220      * @returns {promise} $q.promise object
1221      */
1222     function authorizePermissionMap(map) {
1223       var deferred = $q.defer();
1224
1225       resolveExceptPrivilegeMap(deferred, map);
1226
1227       return deferred.promise;
1228     }
1229
1230     /**
1231      * Resolves flat set of "except" privileges
1232      * @method
1233      * @private
1234      *
1235      * @param deferred {Object} Promise defer
1236      * @param map {permission.PermissionMap} Access rights map
1237      *
1238      * @returns {Promise} $q.promise object
1239      */
1240     function resolveExceptPrivilegeMap(deferred, map) {
1241       var exceptPromises = map.resolvePropertyValidity(map.except);
1242
1243       $q.any(exceptPromises)
1244         .then(function (rejectedPermissions) {
1245           deferred.reject(rejectedPermissions);
1246         })
1247         .catch(function () {
1248           resolveOnlyPermissionMap(deferred, map);
1249         });
1250     }
1251
1252     /**
1253      * Resolves flat set of "only" privileges
1254      * @method
1255      * @private
1256      *
1257      * @param deferred {Object} Promise defer
1258      * @param map {permission.PermissionMap} Access rights map
1259      */
1260     function resolveOnlyPermissionMap(deferred, map) {
1261       if (!map.only.length) {
1262         deferred.resolve();
1263         return;
1264       }
1265
1266       var onlyPromises = map.resolvePropertyValidity(map.only);
1267       $q.any(onlyPromises)
1268         .then(function (resolvedPermissions) {
1269           deferred.resolve(resolvedPermissions);
1270         })
1271         .catch(function (rejectedPermission) {
1272           deferred.reject(rejectedPermission);
1273         });
1274     }
1275   }
1276
1277   angular
1278     .module('permission')
1279     .service('Authorization', Authorization);
1280
1281 })();
1282
1283
1284 (function () {
1285   'use strict';
1286
1287   /**
1288    * Service responsible for handling state based authorization
1289    * @name StateAuthorization
1290    * @memberOf permission
1291    *
1292    * @param $q {Object} Angular promise implementation
1293    */
1294   StateAuthorization.$inject = ['$q'];
1295   function StateAuthorization($q) {
1296
1297     this.authorize = authorize;
1298
1299     /**
1300      * Handles state authorization
1301      * @method {permission.StatePermissionMap}
1302      * @param statePermissionMap
1303      *
1304      * @return {promise}
1305      */
1306     function authorize(statePermissionMap) {
1307       return authorizeStatePermissionMap(statePermissionMap);
1308     }
1309
1310     /**
1311      * Checks authorization for complex state inheritance
1312      * @method
1313      * @private
1314      *
1315      * @param map {permission.StatePermissionMap} State access rights map
1316      *
1317      * @returns {promise} $q.promise object
1318      */
1319     function authorizeStatePermissionMap(map) {
1320       var deferred = $q.defer();
1321
1322       resolveExceptStatePermissionMap(deferred, map);
1323
1324       return deferred.promise;
1325     }
1326
1327     /**
1328      * Resolves compensated set of "except" privileges
1329      * @method
1330      * @private
1331      *
1332      * @param deferred {Object} Promise defer
1333      * @param map {permission.StatePermissionMap} State access rights map
1334      */
1335     function resolveExceptStatePermissionMap(deferred, map) {
1336       var exceptPromises = resolveStatePermissionMap(map.except, map);
1337
1338       $q.all(exceptPromises)
1339         .then(function (rejectedPermissions) {
1340           deferred.reject(rejectedPermissions);
1341         })
1342         .catch(function () {
1343           resolveOnlyStatePermissionMap(deferred, map);
1344         });
1345     }
1346
1347     /**
1348      * Resolves compensated set of "only" privileges
1349      * @method
1350      * @private
1351      *
1352      * @param deferred {Object} Promise defer
1353      * @param map {permission.StatePermissionMap} State access rights map
1354      */
1355     function resolveOnlyStatePermissionMap(deferred, map) {
1356       if (!map.only.length) {
1357         deferred.resolve();
1358         return;
1359       }
1360
1361       var onlyPromises = resolveStatePermissionMap(map.only, map);
1362
1363       $q.all(onlyPromises)
1364         .then(function (resolvedPermissions) {
1365           deferred.resolve(resolvedPermissions);
1366         })
1367         .catch(function (rejectedPermission) {
1368           deferred.reject(rejectedPermission);
1369         });
1370     }
1371
1372     /**
1373      * Performs iteration over list of privileges looking for matches
1374      * @method
1375      * @private
1376      *
1377      * @param privilegesNames {Array} Array of sets of access rights
1378      * @param map {permission.StatePermissionMap} State access rights map
1379      *
1380      * @returns {Array<Promise>} Promise collection
1381      */
1382     function resolveStatePermissionMap(privilegesNames, map) {
1383       if (!privilegesNames.length) {
1384         return [$q.reject()];
1385       }
1386
1387       return privilegesNames.map(function (statePrivileges) {
1388         var resolvedStatePrivileges = map.resolvePropertyValidity(statePrivileges);
1389         return $q.any(resolvedStatePrivileges);
1390       });
1391     }
1392   }
1393
1394   angular
1395     .module('permission')
1396     .service('StateAuthorization', StateAuthorization);
1397
1398 })();