1
1
import { Access , IAccessInfo , Query , IQueryInfo , Permission , AccessControlError } from './core' ;
2
2
import { Action , Possession , actions , possessions } from './enums' ;
3
- import utils from './utils' ;
3
+ import { utils , ERR_LOCK } from './utils' ;
4
4
5
5
/**
6
6
* @classdesc
@@ -12,7 +12,7 @@ import utils from './utils';
12
12
* array fetched from database) or simple omit `grants` parameter if you are
13
13
* willing to build it programmatically.
14
14
*
15
- * <p><pre><code> var grants = {
15
+ * <p><pre><code> const grants = {
16
16
* role1: {
17
17
* resource1: {
18
18
* "create:any": [ attrs ],
@@ -25,39 +25,39 @@ import utils from './utils';
25
25
* },
26
26
* role2: { ... }
27
27
* };
28
- * var ac = new AccessControl(grants);</code></pre></p>
28
+ * const ac = new AccessControl(grants);</code></pre></p>
29
29
*
30
30
* The `grants` object can also be an array, such as a flat list
31
31
* fetched from a database.
32
32
*
33
- * <p><pre><code> var flatList = [
34
- * { role: " role1" , resource: " resource1" , action: " create:any" , attributes: [ attrs ] },
35
- * { role: " role1" , resource: " resource1" , action: " read:own" , attributes: [ attrs ] },
36
- * { role: " role2" , ... },
33
+ * <p><pre><code> const flatList = [
34
+ * { role: ' role1' , resource: ' resource1' , action: ' create:any' , attributes: [ attrs ] },
35
+ * { role: ' role1' , resource: ' resource1' , action: ' read:own' , attributes: [ attrs ] },
36
+ * { role: ' role2' , ... },
37
37
* ...
38
38
* ];</code></pre></p>
39
39
*
40
40
* We turn this list into a hashtable for better performance. We aggregate
41
41
* the list by roles first, resources second. If possession (in action
42
42
* value or as a separate property) is omitted, it will default to `"any"`.
43
- * e.g. `"create"` —> `"create:any"`
43
+ * e.g. `"create"` —» `"create:any"`
44
44
*
45
45
* Below are equivalent:
46
- * <p><pre><code> var grants = { role: " role1" , resource: " resource1" , action: " create:any" , attributes: [ attrs ] }
47
- * var same = { role: " role1" , resource: " resource1" , action: " create" , possession: " any" , attributes: [ attrs ] }</code></pre></p>
46
+ * <p><pre><code> const grants = { role: ' role1' , resource: ' resource1' , action: ' create:any' , attributes: [ attrs ] }
47
+ * const same = { role: ' role1' , resource: ' resource1' , action: ' create' , possession: ' any' , attributes: [ attrs ] }</code></pre></p>
48
48
*
49
49
* So we can also initialize with this flat list of grants:
50
- * <p><pre><code> var ac = new AccessControl(flatList);
50
+ * <p><pre><code> const ac = new AccessControl(flatList);
51
51
* console.log(ac.getGrants());</code></pre></p>
52
52
*
53
- * @author Onur Yıldırım ( onur@cutepilot.com)
53
+ * @author Onur Yıldırım < onur@cutepilot.com>
54
54
* @license MIT
55
55
*
56
56
* @class
57
57
* @global
58
58
*
59
59
* @example
60
- * var ac = new AccessControl(grants);
60
+ * const ac = new AccessControl(grants);
61
61
*
62
62
* ac.grant('admin').createAny('profile');
63
63
*
@@ -83,7 +83,7 @@ import utils from './utils';
83
83
* .deleteOwn('video');
84
84
*
85
85
* // now we can check for granted or denied permissions
86
- * var permission = ac.can('admin').readAny('profile');
86
+ * const permission = ac.can('admin').readAny('profile');
87
87
* permission.granted // true
88
88
* permission.attributes // ["*", "!password"]
89
89
* permission.filter(data) // { uid, email, address, account }
@@ -94,13 +94,17 @@ import utils from './utils';
94
94
* // To add a grant but deny access via attributes
95
95
* ac.grant('admin').createAny('profile', []); // no attributes allowed
96
96
* ac.can('admin').createAny('profile').granted; // false
97
+ *
98
+ * // To prevent any more changes:
99
+ * ac.lock();
97
100
*/
98
101
class AccessControl {
99
102
100
103
/**
101
104
* @private
102
105
*/
103
106
private _grants :any ;
107
+ private _locked :boolean = false ;
104
108
105
109
/**
106
110
* Initializes a new instance of `AccessControl` with the given grants.
@@ -109,10 +113,26 @@ class AccessControl {
109
113
* @param {Object|Array } grants - A list containing the access grant
110
114
* definitions. See the structure of this object in the examples.
111
115
*/
112
- constructor ( grants :any = { } ) {
116
+ constructor ( grants :any ) {
117
+ // explicit undefined is not allowed
118
+ if ( arguments . length === 0 ) grants = { } ;
113
119
this . setGrants ( grants ) ;
114
120
}
115
121
122
+ // -------------------------------
123
+ // PUBLIC PROPERTIES
124
+ // -------------------------------
125
+
126
+ /**
127
+ * Specifies whether the underlying grants object is frozen and all
128
+ * functionality for modifying it is disabled.
129
+ * @name AccessControl#isLocked
130
+ * @type {Boolean }
131
+ */
132
+ get isLocked ( ) :boolean {
133
+ return this . _locked && Object . isFrozen ( this . _grants ) ;
134
+ }
135
+
116
136
// -------------------------------
117
137
// PUBLIC METHODS
118
138
// -------------------------------
@@ -166,23 +186,19 @@ class AccessControl {
166
186
}
167
187
168
188
/**
169
- * Sets all access grants at once, from an object or array.
170
- * Note that this will reset the object and remove all previous grants.
189
+ * Sets all access grants at once, from an object or array. Note that this
190
+ * will reset the object and remove all previous grants.
171
191
* @chainable
172
192
*
173
193
* @param {Object|Array } grantsObject - A list containing the access grant
174
- * definitions.
194
+ * definitions. If `null` or `undefined`; grants will be reset
195
+ * (emptied).
175
196
*
176
197
* @returns {AccessControl } - `AccessControl` instance for chaining.
177
198
*/
178
199
setGrants ( grantsObject :any ) :AccessControl {
179
- this . _grants = { } ;
180
- let type :string = utils . type ( grantsObject ) ;
181
- if ( type === 'object' ) {
182
- this . _grants = grantsObject ;
183
- } else if ( type === 'array' ) {
184
- grantsObject . forEach ( ( item :any ) => utils . commitToGrants ( this . _grants , item , true ) ) ;
185
- }
200
+ if ( this . isLocked ) throw new AccessControlError ( ERR_LOCK ) ;
201
+ this . _grants = utils . getInspectedGrants ( grantsObject ) ;
186
202
return this ;
187
203
}
188
204
@@ -193,10 +209,44 @@ class AccessControl {
193
209
* @returns {AccessControl } - `AccessControl` instance for chaining.
194
210
*/
195
211
reset ( ) :AccessControl {
212
+ if ( this . isLocked ) throw new AccessControlError ( ERR_LOCK ) ;
196
213
this . _grants = { } ;
197
214
return this ;
198
215
}
199
216
217
+ /**
218
+ * Freezes the underlying grants model and disables all functionality for
219
+ * modifying it. This is useful when you want to restrict any changes. Any
220
+ * attempts to modify (such as `#setGrants()`, `#reset()`, `#grant()`,
221
+ * `#deny()`, etc) will throw after grants are locked. Note that <b>there
222
+ * is no `unlock()` method</b>.
223
+ *
224
+ * Remember that this does not prevent the `AccessControl` instance from
225
+ * being altered/replaced. Only the grants inner object is locked.
226
+ *
227
+ * <b>A note about performance</b>: This uses recursive `Object.freeze()`.
228
+ * In NodeJS & V8, enumeration performance is not impacted because of this.
229
+ * In fact, it increases the performance because of V8 optimization.
230
+ * @chainable
231
+ *
232
+ * @returns {AccessControl } - `AccessControl` instance for chaining.
233
+ *
234
+ * @example
235
+ * ac.grant('admin').create('product');
236
+ * ac.lock(); // called on the AccessControl instance.
237
+ * // or
238
+ * ac.grant('admin').create('product').lock(); // called on the chained Access instance.
239
+ *
240
+ * // After this point, any attempt of modification will throw
241
+ * ac.setGrants({}); // throws
242
+ * ac.grant('user'); // throws..
243
+ * // underlying grants model is not changed
244
+ */
245
+ lock ( ) :AccessControl {
246
+ utils . lockAC ( this ) ;
247
+ return this ;
248
+ }
249
+
200
250
/**
201
251
* Extends the given role(s) with privileges of one or more other roles.
202
252
* @chainable
@@ -218,6 +268,7 @@ class AccessControl {
218
268
* If a role is extended by itself or a non-existent role.
219
269
*/
220
270
extendRole ( roles :string | string [ ] , extenderRoles :string | string [ ] ) :AccessControl {
271
+ if ( this . isLocked ) throw new AccessControlError ( ERR_LOCK ) ;
221
272
utils . extendRole ( this . _grants , roles , extenderRoles ) ;
222
273
return this ;
223
274
}
@@ -232,6 +283,8 @@ class AccessControl {
232
283
* @returns {AccessControl } - `AccessControl` instance for chaining.
233
284
*/
234
285
removeRoles ( roles :string | string [ ] ) :AccessControl {
286
+ if ( this . isLocked ) throw new AccessControlError ( ERR_LOCK ) ;
287
+
235
288
let rolesToRemove :string [ ] = utils . toStringArray ( roles ) ;
236
289
rolesToRemove . forEach ( ( role :string ) => {
237
290
delete this . _grants [ role ] ;
@@ -260,6 +313,8 @@ class AccessControl {
260
313
* @returns {AccessControl } - `AccessControl` instance for chaining.
261
314
*/
262
315
removeResources ( resources :string | string [ ] , roles ?:string | string [ ] ) :AccessControl {
316
+ if ( this . isLocked ) throw new AccessControlError ( ERR_LOCK ) ;
317
+
263
318
// _removePermission has a third argument `actionPossession`. if
264
319
// omitted (like below), removes the parent resource object.
265
320
this . _removePermission ( resources , roles ) ;
@@ -279,6 +334,30 @@ class AccessControl {
279
334
return Object . keys ( this . _grants ) ;
280
335
}
281
336
337
+ /**
338
+ * Gets the list of inherited roles by the given role.
339
+ * @name AccessControl#getInheritedRolesOf
340
+ * @alias AccessControl#getExtendedRolesOf
341
+ * @function
342
+ *
343
+ * @param {String } role - Target role name.
344
+ *
345
+ * @returns {Array<String> }
346
+ */
347
+ getInheritedRolesOf ( role :string ) :string [ ] {
348
+ let roles :string [ ] = utils . getInheritedRolesOf ( this . _grants , role ) ;
349
+ roles . shift ( ) ;
350
+ return roles ;
351
+ }
352
+
353
+ /**
354
+ * Alias of `getInheritedRolesOf`
355
+ * @private
356
+ */
357
+ getExtendedRolesOf ( role :string ) :string [ ] {
358
+ return this . getInheritedRolesOf ( role ) ;
359
+ }
360
+
282
361
/**
283
362
* Gets all the unique resources that are granted access for at
284
363
* least one role.
@@ -326,7 +405,7 @@ class AccessControl {
326
405
* This object provides chainable methods to define and query the access
327
406
* permissions to be checked.
328
407
* @name AccessControl#can
329
- * @alias AccessControl#access
408
+ * @alias AccessControl#query
330
409
* @function
331
410
* @chainable
332
411
*
@@ -339,7 +418,7 @@ class AccessControl {
339
418
* See {@link ?api=ac#AccessControl~Query|`Query` inner class}.
340
419
*
341
420
* @example
342
- * var ac = new AccessControl(grants);
421
+ * const ac = new AccessControl(grants);
343
422
*
344
423
* ac.can('admin').createAny('profile');
345
424
* // equivalent to:
@@ -359,7 +438,7 @@ class AccessControl {
359
438
* Alias of `can()`.
360
439
* @private
361
440
*/
362
- access ( role :string | string [ ] | IQueryInfo ) :Query {
441
+ query ( role :string | string [ ] | IQueryInfo ) :Query {
363
442
return this . can ( role ) ;
364
443
}
365
444
@@ -379,8 +458,8 @@ class AccessControl {
379
458
* {@link ?api=ac#AccessControl~Permission|`Permission` inner class}.
380
459
*
381
460
* @example
382
- * var ac = new AccessControl(grants);
383
- * var permission = ac.permission({
461
+ * const ac = new AccessControl(grants);
462
+ * const permission = ac.permission({
384
463
* role: "user",
385
464
* action: "update:own",
386
465
* resource: "profile"
@@ -412,8 +491,8 @@ class AccessControl {
412
491
* See {@link ?api=ac#AccessControl~Access|`Access` inner class}.
413
492
*
414
493
* @example
415
- * var ac = new AccessControl(),
416
- * attributes = ['*'];
494
+ * const ac = new AccessControl();
495
+ * let attributes = ['*'];
417
496
*
418
497
* ac.grant('admin').createAny('profile', attributes);
419
498
* // equivalent to:
@@ -448,7 +527,8 @@ class AccessControl {
448
527
* // which means all attributes (of the resource) are allowed.
449
528
*/
450
529
grant ( role :string | string [ ] | IAccessInfo ) :Access {
451
- return new Access ( this . _grants , role , false ) ;
530
+ if ( this . isLocked ) throw new AccessControlError ( ERR_LOCK ) ;
531
+ return new Access ( this , role , false ) ;
452
532
}
453
533
454
534
/**
@@ -480,7 +560,7 @@ class AccessControl {
480
560
* See {@link ?api=ac#AccessControl~Access|`Access` inner class}.
481
561
*
482
562
* @example
483
- * var ac = new AccessControl();
563
+ * const ac = new AccessControl();
484
564
*
485
565
* ac.deny('admin').createAny('profile');
486
566
* // equivalent to:
@@ -510,7 +590,8 @@ class AccessControl {
510
590
* ac.deny(['admin', 'user']).createOwn('profile');
511
591
*/
512
592
deny ( role :string | string [ ] | IAccessInfo ) :Access {
513
- return new Access ( this . _grants , role , true ) ;
593
+ if ( this . isLocked ) throw new AccessControlError ( ERR_LOCK ) ;
594
+ return new Access ( this , role , true ) ;
514
595
}
515
596
516
597
/**
0 commit comments