Skip to content

Commit 0724e24

Browse files
committed
factory for inner classes
1 parent 4db0e63 commit 0724e24

File tree

1 file changed

+375
-0
lines changed

1 file changed

+375
-0
lines changed

src/Factory.js

Lines changed: 375 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,375 @@
1+
// dep modules
2+
import Notation from 'notation';
3+
// own modules
4+
import enums from './enums';
5+
import helper from './lib/helper';
6+
7+
export default (ac) => {
8+
9+
// -------------------------------
10+
// CLASS: Permission
11+
// -------------------------------
12+
13+
// below are equivalent
14+
// var permission = AccessControl.access(role).createAny(resource);
15+
// var permission = AccessControl.access(role).resource(resource).createAny();
16+
// var permission = AccessControl.access().role(role).resource(resource).createAny();
17+
// var permission = new AccessControl.Permission(role, action, possession, resource);
18+
19+
// AccessControl.access('ADMIN').createAny('PROFILE').granted; // Boolean
20+
// AccessControl.access('ADMIN').createAny('PROFILE').attributes; // Array
21+
22+
// var can = AccessControl.access;
23+
// var permission = can('USER').createOwn('PROFILE');
24+
// permission.granted // boolean
25+
// permission.attributes // Array
26+
27+
// See AccessControl#permission
28+
class Permission {
29+
30+
constructor(perm) {
31+
this._attributes = this._getUnionAttrsOfRoles(perm);
32+
}
33+
34+
get attributes() {
35+
return this._attributes;
36+
}
37+
38+
get granted() {
39+
// check for a non-negated attribute
40+
return this.attributes.some(attr => {
41+
return attr.slice(0, 1) !== '!';
42+
});
43+
}
44+
45+
// equivalent to AccessControl.filter(data, attributes);
46+
filter(data) {
47+
return helper.filterAll(data, this.attributes);
48+
}
49+
50+
/**
51+
* Gets roles and extended roles in a flat array.
52+
* @private
53+
*/
54+
_getFlatRoles(roles) {
55+
roles = helper.asArray(roles);
56+
let arr = roles.concat();
57+
roles.forEach(roleName => {
58+
let role = ac._grants[roleName];
59+
if (Array.isArray(role.$extend)) {
60+
arr = helper.uniqConcat(arr, role.$extend);
61+
}
62+
});
63+
return arr;
64+
}
65+
66+
/**
67+
* When more than one role is passed, we union the permitted attributes
68+
* for all given roles; so we can check whether "at least one of these
69+
* roles" have the permission to execute this action.
70+
* e.g. `can(['admin', 'user']).createAny('video')`
71+
* @private
72+
*/
73+
_getUnionAttrsOfRoles(access) {
74+
if (!ac._grants) {
75+
throw new Error('AccessControl: Grants are not set.');
76+
}
77+
// throws if has any invalid property value
78+
access = helper.normalizeAccessObject(access);
79+
80+
let grantItem, _resource,
81+
attrsList = [],
82+
// get roles and extended roles in a flat array
83+
roles = this._getFlatRoles(access.role);
84+
// iterate through roles and add permission attributes (array) of
85+
// each role to attrsList (array).
86+
roles.forEach((role, index) => {
87+
grantItem = ac._grants[role];
88+
if (grantItem) {
89+
_resource = grantItem[access.resource];
90+
if (_resource) {
91+
// e.g. _resource['create:own']
92+
// If action has possession "any", it will also return
93+
// `granted=true` for "own", if "own" is not defined.
94+
attrsList.push(
95+
_resource[access.action + ':' + access.possession]
96+
|| _resource[access.action + ':any']
97+
|| []
98+
);
99+
// console.log(_resource, 'for:', action + '.' + possession);
100+
}
101+
}
102+
});
103+
104+
// union all arrays of (permitted resource) attributes (for each role)
105+
// into a single array.
106+
let attrs = [],
107+
len = attrsList.length;
108+
if (len > 0) {
109+
attrs = attrsList[0];
110+
let i = 1;
111+
while (i < len) {
112+
attrs = Notation.Glob.union(attrs, attrsList[i]);
113+
i++;
114+
}
115+
}
116+
return attrs;
117+
}
118+
}
119+
120+
// -------------------------------
121+
// CLASS: Access
122+
// -------------------------------
123+
124+
// See AccessControl#can
125+
class Access {
126+
127+
constructor(rolesOrAccess) {
128+
// if this is a (permission) object, we directly build attributes from
129+
// grants.
130+
if (helper.type(rolesOrAccess) === 'object') {
131+
this._access = rolesOrAccess;
132+
} else {
133+
// if this is just role(s); a string or array; we start building
134+
// the grant object for this.
135+
this._access = {
136+
role: rolesOrAccess
137+
};
138+
}
139+
}
140+
141+
role(roles) {
142+
this._access.role = roles;
143+
return this;
144+
}
145+
146+
resource(resource) {
147+
this._access.resource = resource;
148+
return this;
149+
}
150+
}
151+
152+
// -------------------------------
153+
// CLASS: Grant
154+
// -------------------------------
155+
156+
// See AccessControl#grant
157+
class Grant {
158+
159+
// If a grant object is passed, possession and attributes properties are
160+
// optional. CAUTION: if attributes is omitted, it will default to `['*']`
161+
// which means "all attributes allowed". If possession is omitted, it will
162+
// default to "any".
163+
constructor(rolesOrGrant) {
164+
// if this is a (access grant) object, we directly add it to grants
165+
if (helper.type(rolesOrGrant) === 'object') {
166+
this._grant = rolesOrGrant;
167+
// Execute immediately if action is set. Otherwise,
168+
// action/possession will be set by action methods such as
169+
// `.createAny()`, `.readOwn()`, etc...
170+
if (helper.hasDefined(this._grant, 'action')) {
171+
ac._grantAccess(this._grant);
172+
}
173+
} else {
174+
// if this is just role(s); a string or array; we start building
175+
// the grant object for this.
176+
this._grant = {
177+
role: rolesOrGrant
178+
};
179+
}
180+
}
181+
182+
role(roles) {
183+
this._grant.role = roles;
184+
return this;
185+
}
186+
187+
resource(resource) {
188+
this._grant.resource = resource;
189+
return this;
190+
}
191+
192+
attributes(attributes) {
193+
this._grant.attributes = attributes;
194+
return this;
195+
}
196+
197+
extend(roles) {
198+
ac.extendRole(this._grant.role, roles);
199+
return this;
200+
}
201+
202+
/**
203+
* Shorthand to switch to a new `Grant` instance with a different role
204+
* within the method chain.
205+
* @example
206+
* ac.grant('user').createOwn('video')
207+
* .grant('admin').updateAny('video');
208+
*/
209+
grant(rolesOrGrant) {
210+
if (!rolesOrGrant) rolesOrGrant = this._grant.role;
211+
return new Grant(rolesOrGrant);
212+
}
213+
214+
/**
215+
* Shorthand to switch to a new `Deny` instance with a different
216+
* (or same) role within the method chain.
217+
* @example
218+
* ac.grant('user').createOwn('video')
219+
* .grant('admin').updateAny('video');
220+
*/
221+
deny(rolesOrDeny) {
222+
if (!rolesOrDeny) rolesOrDeny = this._grant.role;
223+
return new Deny(rolesOrDeny); // eslint-disable-line
224+
}
225+
}
226+
227+
// -------------------------------
228+
// CLASS: Deny
229+
// -------------------------------
230+
231+
// See AccessControl#deny
232+
class Deny {
233+
234+
// See AccessControl.Deny
235+
constructor(rolesOrDeny) {
236+
// if this is a (access grant) object, we directly add it to grants
237+
if (helper.type(rolesOrDeny) === 'object') {
238+
this._deny = rolesOrDeny;
239+
if (helper.hasDefined(this._deny, 'action')) {
240+
ac._denyAccess(this._deny);
241+
}
242+
} else {
243+
// if this is just role(s); a string or array; we start building
244+
// the grant object for this.
245+
this._deny = {
246+
role: rolesOrDeny
247+
};
248+
}
249+
}
250+
role(roles) {
251+
this._deny.role = roles;
252+
return this;
253+
}
254+
255+
resource(resource) {
256+
this._deny.resource = resource;
257+
return this;
258+
}
259+
260+
/**
261+
* Shorthand to switch to a new `Deny` instance with a different role
262+
* within the method chain.
263+
* @example
264+
* ac.grant('user').createOwn('video')
265+
* .grant('admin').updateAny('video');
266+
*/
267+
deny(rolesOrDeny) {
268+
if (!rolesOrDeny) rolesOrDeny = this._deny.role;
269+
return new Deny(rolesOrDeny);
270+
}
271+
272+
/**
273+
* Shorthand to switch to a new `Grant` instance with a different
274+
* (or same) role within the method chain.
275+
* @example
276+
* ac.grant('user').createOwn('video')
277+
* .grant('admin').updateAny('video');
278+
*/
279+
grant(rolesOrGrant) {
280+
if (!rolesOrGrant) rolesOrGrant = this._deny.role;
281+
return new Grant(rolesOrGrant);
282+
}
283+
}
284+
285+
// -------------------------------
286+
// INSTANCE (PROTOTYPE) METHODS
287+
// -------------------------------
288+
289+
// Creating action (Prototype) Methods for
290+
// `Access`, `Grant` and `Deny` classes such as:
291+
// ---------------------------------------------
292+
// .createAny() .readAny() .updateAny() .deleteAny()
293+
// .createOwn() .readOwn() .updateOwn() .deleteOwn()
294+
// ---------------------------------------------
295+
// Also assigning aliases to <action>Any() methods:
296+
// .create() .read() .update() .delete()
297+
298+
let method;
299+
enums.actions.forEach(action => { // create|read|update|delete
300+
enums.possessions.forEach(possession => { // any|own
301+
method = helper.getMethodName(action, possession);
302+
// Access.prototype.<action+Possession>
303+
// e.g. Access.prototype.createAny
304+
/**
305+
* Action methods of `Access` prototype return a `Permission`
306+
* object that defines the granted permission (attributes).
307+
* These methods end the chain and throws if any invalid values
308+
* are passed previously (via the rest of the chain-methods).
309+
*/
310+
Access.prototype[method] = function (resource) {
311+
this._access.action = action;
312+
this._access.possession = possession;
313+
this._access.resource = resource || this._access.resource;
314+
return new Permission(this._access);
315+
};
316+
// assign aliases: Access.prototype.create = Access.prototype.createAny
317+
if (possession === 'any') {
318+
Access.prototype[action] = Access.prototype[method];
319+
}
320+
// Grant.prototype.<action+Possession>
321+
// e.g. Grant.prototype.createAny
322+
/**
323+
* Action methods of `Grant` prototype add (grant) permission(s)
324+
* for the defined role(s) and resource. These methods end the
325+
* chain and throws if any invalid values are passed previously
326+
* (via the rest of the chain-methods).
327+
*/
328+
Grant.prototype[method] = function (resource, attributes) {
329+
this._grant.action = action;
330+
this._grant.possession = possession;
331+
this._grant.resource = resource || this._grant.resource;
332+
this._grant.attributes = attributes || this._grant.attributes;
333+
ac._grantAccess(this._grant);
334+
// important: reset attributes for chained methods
335+
this._grant.attributes = undefined;
336+
return this;
337+
};
338+
// assign aliases: Grant.prototype.create = Grant.prototype.createAny
339+
if (possession === 'any') {
340+
Grant.prototype[action] = Grant.prototype[method];
341+
}
342+
// Deny.prototype.<action+Possession>
343+
// e.g. Deny.prototype.createAny
344+
/**
345+
* Action methods of `Deny` prototype remove (deny) permission(s)
346+
* for the defined role(s) and resource. These methods end the
347+
* chain and throws if any invalid values are passed previously
348+
* (via the rest of the chain-methods).
349+
*/
350+
Deny.prototype[method] = function (resource) {
351+
this._deny.action = action;
352+
this._deny.possession = possession;
353+
this._deny.resource = resource || this._deny.resource;
354+
ac._denyAccess(this._deny);
355+
return this;
356+
};
357+
// assign aliases: Deny.prototype.create = Deny.prototype.createAny
358+
if (possession === 'any') {
359+
Deny.prototype[action] = Deny.prototype[method];
360+
}
361+
});
362+
});
363+
364+
// -------------------------------
365+
// EXPORT
366+
// -------------------------------
367+
368+
return {
369+
Permission,
370+
Access,
371+
Grant,
372+
Deny
373+
};
374+
375+
};

0 commit comments

Comments
 (0)