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