@holoyan/adonisjs-permissions 0.5.2 → 0.6.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +277 -97
- package/build/configure.js +9 -0
- package/build/index.d.ts +2 -1
- package/build/index.js +2 -1
- package/build/providers/role_permission_provider.js +5 -3
- package/build/src/acl.d.ts +14 -6
- package/build/src/acl.js +33 -12
- package/build/src/mixins/has_permissions.d.ts +1 -2
- package/build/src/mixins/has_permissions.js +0 -3
- package/build/src/model_manager.d.ts +3 -3
- package/build/src/scope.d.ts +7 -0
- package/build/src/scope.js +14 -0
- package/build/src/services/model_has_role_permissions.d.ts +12 -3
- package/build/src/services/model_has_role_permissions.js +29 -2
- package/build/src/services/permissions/empty_permission.d.ts +5 -1
- package/build/src/services/permissions/empty_permission.js +10 -1
- package/build/src/services/permissions/permission_has_model_roles.d.ts +5 -2
- package/build/src/services/permissions/permission_has_model_roles.js +10 -1
- package/build/src/services/permissions/permissions_service.d.ts +11 -3
- package/build/src/services/permissions/permissions_service.js +48 -13
- package/build/src/services/query_helper.d.ts +4 -4
- package/build/src/services/query_helper.js +8 -8
- package/build/src/services/roles/empty_roles.d.ts +5 -1
- package/build/src/services/roles/empty_roles.js +10 -1
- package/build/src/services/roles/role_has_model_permissions.d.ts +7 -6
- package/build/src/services/roles/role_has_model_permissions.js +16 -6
- package/build/src/services/roles/roles_service.d.ts +10 -5
- package/build/src/services/roles/roles_service.js +16 -4
- package/build/src/types.d.ts +22 -1
- package/build/stubs/middlewares/acl_middleware.stub +26 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -27,36 +27,38 @@
|
|
|
27
27
|
- [Getting users (models) for a permission](#getting-users-models-for-a-permission)
|
|
28
28
|
- [Getting models for a role](#getting-models-for-a-role)
|
|
29
29
|
- [Checking for a permission](#checking-for-a-permission)
|
|
30
|
-
- [
|
|
30
|
+
- [Middleware](#middleware)
|
|
31
|
+
- [Removing (revoking) roles and permissions from the model](#removing-revokingdetach-roles-and-permissions-from-the-model)
|
|
31
32
|
- [Digging deeper](#digging-deeper)
|
|
32
|
-
- [Restricting a permission to a model (On resource)](#restricting-a-permission-to-a-model-on-resource)
|
|
33
|
+
- [Restricting a permission to a model (On a resource)](#restricting-a-permission-to-a-model-on-a-resource)
|
|
33
34
|
- [Forbidding permissions](#forbidding-permissions)
|
|
34
35
|
- [Forbidding permissions on a resource](#forbidding-permissions-on-a-resource)
|
|
35
36
|
- [Checking for forbidden permissions](#checking-for-forbidden-permissions)
|
|
36
37
|
- [Unforbidding the permissions](#unforbidding-the-permissions)
|
|
37
38
|
- [Global vs resource permissions (Important!)](#global-vs-resource-permissions-important)
|
|
38
39
|
- [containsPermission v hasPermission](#containspermission-v-haspermission)
|
|
40
|
+
- [Scopes or Multi-tenancy](#scopes-or-multi-tenancy)
|
|
41
|
+
- [The Scope middleware](#the-scope-middleware)
|
|
42
|
+
- [Default Scope](#default-scope-tenant)
|
|
43
|
+
- [Todo](#todo)
|
|
39
44
|
- [Test](#test)
|
|
40
45
|
- [License](#license)
|
|
41
46
|
</p></details>
|
|
42
47
|
|
|
43
48
|
## Introduction
|
|
44
49
|
|
|
45
|
-
AdonisJs
|
|
50
|
+
AdonisJs Acl is an elegant and powerful package for managing roles and permissions in any AdonisJs app. With an expressive and fluent syntax, it stays out of your way as much as possible: use it when you want, ignore it when you don't.
|
|
46
51
|
|
|
47
|
-
For a quick, glanceable list of
|
|
52
|
+
For a quick, glanceable list of Acl's features, check out the [cheat sheet](#cheat-sheet)
|
|
48
53
|
|
|
49
54
|
Once installed, you can simply tell the Acl what you want to allow:
|
|
50
55
|
|
|
51
56
|
```typescript
|
|
52
57
|
import {Acl} from '@holoyan/adonisjs-permissions'
|
|
53
58
|
|
|
54
|
-
|
|
55
59
|
// Give a user the permission to edit
|
|
56
60
|
await Acl.model(user).allow('edit');
|
|
57
|
-
|
|
58
|
-
// Alternatively, do it through a permission
|
|
59
|
-
await Acl.permission('edit').attachToModel(user);
|
|
61
|
+
// Behind the scenes Acl will create 'edit' permission and assign to the user if not available
|
|
60
62
|
|
|
61
63
|
// You can also grant a permission only to a specific model
|
|
62
64
|
const post = await Post.first()
|
|
@@ -65,8 +67,7 @@ await Acl.model(user).allow('delete', post);
|
|
|
65
67
|
await user.allow('delete', post)
|
|
66
68
|
```
|
|
67
69
|
|
|
68
|
-
To be able to use full power of Acl you should have clear understanding how it is structured and works
|
|
69
|
-
For most of the applications [Basic usage](#basic-usage) will be enough
|
|
70
|
+
To be able to use the full power of Acl, you should have a clear understanding of how it is structured and how it works. That's why the documentation will be divided into two parts: [Basic usage](#basic-usage) and [Advanced usage](#digging-deeper). For most applications, Basic Usage will be enough.
|
|
70
71
|
|
|
71
72
|
## Installation
|
|
72
73
|
|
|
@@ -76,7 +77,7 @@ For most of the applications [Basic usage](#basic-usage) will be enough
|
|
|
76
77
|
Next publish config files
|
|
77
78
|
|
|
78
79
|
node ace configure @holoyan/adonisjs-permissions
|
|
79
|
-
this will create permissions.ts file in `configs`
|
|
80
|
+
this will create `permissions.ts` file in `configs` directory, migration file in the `database/migrations` directory
|
|
80
81
|
|
|
81
82
|
Next run migration
|
|
82
83
|
|
|
@@ -85,7 +86,7 @@ Next run migration
|
|
|
85
86
|
|
|
86
87
|
## Configuration
|
|
87
88
|
|
|
88
|
-
All models
|
|
89
|
+
All models that will interact with `Acl` MUST use the `@MorphMap('ALIAS_FOR_CLASS')` decorator and implement the `AclModelInterface` contract.
|
|
89
90
|
|
|
90
91
|
Example.
|
|
91
92
|
|
|
@@ -123,10 +124,9 @@ export default class Post extends BaseModel implements AclModelInterface {
|
|
|
123
124
|
|
|
124
125
|
```
|
|
125
126
|
|
|
126
|
-
|
|
127
127
|
## Mixins
|
|
128
128
|
|
|
129
|
-
If you want to be able to call
|
|
129
|
+
If you want to be able to call `Acl` methods on a `User` model then consider using `hasPermissions` mixin
|
|
130
130
|
|
|
131
131
|
```typescript
|
|
132
132
|
|
|
@@ -149,8 +149,9 @@ export default class User extends compose(BaseModel, hasPermissions()) implement
|
|
|
149
149
|
// then all methods are available on the user
|
|
150
150
|
|
|
151
151
|
const user = await User.first()
|
|
152
|
-
const roles = await user.roles() //
|
|
153
|
-
|
|
152
|
+
const roles = await user.roles() // get user roles
|
|
153
|
+
await user.allow('edit') // give edit permission
|
|
154
|
+
// and so on...
|
|
154
155
|
|
|
155
156
|
```
|
|
156
157
|
|
|
@@ -161,66 +162,83 @@ const roles = await user.roles() // and so on
|
|
|
161
162
|
Currently supported databases: `postgres`, `mysql`, `mssql`
|
|
162
163
|
|
|
163
164
|
### UUID support
|
|
164
|
-
No uuid support *yet
|
|
165
|
+
No uuid support *yet*, check [todo](#todo) list for more details
|
|
165
166
|
|
|
166
167
|
## Basic Usage
|
|
167
168
|
|
|
168
|
-
On this section we will explore basic role permission methods
|
|
169
|
+
On this section, we will explore basic role permission methods.
|
|
169
170
|
|
|
170
171
|
### Creating roles and permissions
|
|
171
172
|
|
|
172
|
-
Let's create `create,update,read,delete` permissions
|
|
173
|
+
Let's manually create `create,update,read,delete` permissions, as well as `admin,manager` roles
|
|
173
174
|
|
|
174
175
|
```typescript
|
|
175
176
|
|
|
177
|
+
import { Permission } from '@holoyan/adonisjs-permissions'
|
|
178
|
+
import { Role } from '@holoyan/adonisjs-permissions'
|
|
179
|
+
|
|
180
|
+
|
|
176
181
|
// create permissions
|
|
177
|
-
const create = await
|
|
182
|
+
const create = await Permission.create({
|
|
178
183
|
slug:'create',
|
|
179
|
-
title:'Create some resource',
|
|
184
|
+
title:'Create some resource', // optional
|
|
180
185
|
})
|
|
181
186
|
|
|
182
|
-
const update = await
|
|
187
|
+
const update = await Permission.create({
|
|
183
188
|
slug:'update',
|
|
184
|
-
title:'update some resource',
|
|
185
189
|
})
|
|
186
190
|
|
|
187
|
-
const read = await
|
|
191
|
+
const read = await Permission.create({
|
|
188
192
|
slug:'read',
|
|
189
|
-
title:'read some resource',
|
|
190
193
|
})
|
|
191
194
|
|
|
192
|
-
const delete = await
|
|
195
|
+
const delete = await Permission.create({
|
|
193
196
|
slug:'delete',
|
|
194
|
-
title:'delete some resource',
|
|
195
197
|
})
|
|
196
198
|
|
|
197
199
|
// create roles
|
|
198
|
-
const admin = await
|
|
200
|
+
const admin = await Role.create({
|
|
199
201
|
slug:'admin',
|
|
200
|
-
title:'Cool title for Admin',
|
|
202
|
+
title:'Cool title for Admin', // optional
|
|
201
203
|
})
|
|
202
204
|
|
|
203
|
-
const manager = await
|
|
205
|
+
const manager = await Role.create({
|
|
204
206
|
slug:'manager',
|
|
205
|
-
title:'Cool title for Manager',
|
|
206
207
|
})
|
|
207
208
|
|
|
208
209
|
```
|
|
209
210
|
|
|
210
|
-
|
|
211
|
+
|
|
212
|
+
The next step is to [assign permissions to the roles](#assigning-permissions-to-the-roles-globally)
|
|
211
213
|
|
|
212
214
|
### Assigning permissions to the roles (Globally)
|
|
213
215
|
|
|
214
|
-
Now
|
|
216
|
+
Now that we have created roles and permissions, let's assign them.
|
|
215
217
|
|
|
216
218
|
```typescript
|
|
219
|
+
import {Acl} from "@holoyan/adonisjs-permissions";
|
|
220
|
+
|
|
221
|
+
|
|
217
222
|
await Acl.role(admin).assign('create')
|
|
218
|
-
//
|
|
219
|
-
|
|
220
|
-
|
|
223
|
+
// alternatively you can use allow(), give() method, as they are identical
|
|
224
|
+
|
|
225
|
+
await Acl.role(admin).allow('update')
|
|
221
226
|
await Acl.role(admin).giveAll(['read', 'delete'])
|
|
227
|
+
// alternatively you use giveAll(), assigneAll(), allowAll() for bulk assign
|
|
228
|
+
|
|
222
229
|
```
|
|
223
230
|
|
|
231
|
+
In case you are assigning a permission that is not already available, `Acl` will create new permission behind the scenes and assign them.
|
|
232
|
+
|
|
233
|
+
```typescript
|
|
234
|
+
|
|
235
|
+
// uploadFile permissions not available
|
|
236
|
+
await Acl.role(admin).allow('uploadFile')
|
|
237
|
+
// uploadFile permission created and assigned
|
|
238
|
+
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
|
|
224
242
|
### Assigning permissions and roles to the users (models)
|
|
225
243
|
|
|
226
244
|
Let's see in examples how to assign [roles and permissions](#creating-roles-and-permissions) to the users
|
|
@@ -232,6 +250,8 @@ import User from "#models/user";
|
|
|
232
250
|
const user1 = await User.query().where(condition1).first()
|
|
233
251
|
// give manager role to the user1
|
|
234
252
|
await Acl.model(user1).assignRole(manager)
|
|
253
|
+
// or just use assign() method, they are alias
|
|
254
|
+
// await Acl.model(user1).assign(manager)
|
|
235
255
|
|
|
236
256
|
const user2 = await User.query().where(condition2).first()
|
|
237
257
|
await Acl.model(user2).assignRole(admin)
|
|
@@ -243,19 +263,15 @@ Or we can give permissions directly to users without having any role
|
|
|
243
263
|
|
|
244
264
|
import {Acl} from "@holoyan/adonisjs-permissions";
|
|
245
265
|
|
|
246
|
-
// create new permission
|
|
247
|
-
const uploadFile = await Acl.permission().create({
|
|
248
|
-
slug: 'upload-file-slug',
|
|
249
|
-
title: 'permisison to upload files',
|
|
250
|
-
})
|
|
251
|
-
|
|
266
|
+
// create and assign a new permission
|
|
252
267
|
Acl.model(user1).assignDirectPermission('upload-file-slug')
|
|
253
|
-
|
|
268
|
+
// or use allow method
|
|
269
|
+
Acl.model(user1).allow('permissionSlug')
|
|
254
270
|
```
|
|
255
271
|
|
|
256
272
|
### Multi-model support
|
|
257
273
|
|
|
258
|
-
We are not
|
|
274
|
+
We are not limited to using only the User model. If you have a multi-auth system like User and Admin, you are free to use both of them with Acl.
|
|
259
275
|
|
|
260
276
|
|
|
261
277
|
```typescript
|
|
@@ -290,16 +306,16 @@ const roles = await Acl.role(role).permissions()
|
|
|
290
306
|
const roles = await Acl.model(user).permissions()
|
|
291
307
|
```
|
|
292
308
|
|
|
293
|
-
### Getting users (models)
|
|
309
|
+
### Getting users (models) from the permission
|
|
294
310
|
|
|
295
311
|
```typescript
|
|
296
312
|
|
|
297
313
|
const models = await Acl.permission(permission).models()
|
|
298
314
|
|
|
299
315
|
```
|
|
300
|
-
this will return array of `ModelPermission` which will contain `modelType,modelId` attributes, where `modelType` is *alias* which you had specified in [morphMap decorator](#configuration), `modelId` is the value of column, you've specified
|
|
316
|
+
this will return array of `ModelPermission` which will contain `modelType,modelId` attributes, where `modelType` is *alias* which you had specified in [morphMap decorator](#configuration), `modelId` is the value of column, you've specified inside [getModelId](#configuration) method.
|
|
301
317
|
|
|
302
|
-
Most of the
|
|
318
|
+
Most of the time, you will have only one model (User). It's better to use the `modelsFor()` method to get concrete models.
|
|
303
319
|
|
|
304
320
|
```typescript
|
|
305
321
|
|
|
@@ -320,7 +336,7 @@ Or if you want to get for a specific model
|
|
|
320
336
|
|
|
321
337
|
```typescript
|
|
322
338
|
|
|
323
|
-
const models = await Acl.role(permission).modelsFor(
|
|
339
|
+
const models = await Acl.role(permission).modelsFor(User)
|
|
324
340
|
|
|
325
341
|
```
|
|
326
342
|
|
|
@@ -334,7 +350,7 @@ await Acl.model(user).hasRole('admin') // :boolean
|
|
|
334
350
|
|
|
335
351
|
```
|
|
336
352
|
|
|
337
|
-
you can pass
|
|
353
|
+
you can pass list of roles
|
|
338
354
|
|
|
339
355
|
```typescript
|
|
340
356
|
|
|
@@ -343,11 +359,12 @@ await Acl.model(user).hasAllRoles('admin', 'manager')
|
|
|
343
359
|
|
|
344
360
|
```
|
|
345
361
|
|
|
346
|
-
|
|
362
|
+
To check if a user has any of the roles
|
|
347
363
|
|
|
348
364
|
```typescript
|
|
349
365
|
|
|
350
366
|
await Acl.model(user).hasAnyRole('admin', 'manager')
|
|
367
|
+
// it will return true if the user has at least one role.
|
|
351
368
|
|
|
352
369
|
```
|
|
353
370
|
|
|
@@ -361,6 +378,8 @@ await Acl.model(user).hasPermission('update')
|
|
|
361
378
|
// or
|
|
362
379
|
await Acl.model(user).can('update') // alias for hasPermission() method
|
|
363
380
|
|
|
381
|
+
// or simply call
|
|
382
|
+
await user.hasPermission('update')
|
|
364
383
|
```
|
|
365
384
|
|
|
366
385
|
To check array of permissions
|
|
@@ -372,9 +391,11 @@ await Acl.model(user).hasAllPermissions(['update', 'delete'])
|
|
|
372
391
|
// or
|
|
373
392
|
await Acl.model(user).canAll(['update', 'delete']) // alias for hasAllPermissions() method
|
|
374
393
|
|
|
394
|
+
// await user.canAll(['update', 'delete'])
|
|
395
|
+
|
|
375
396
|
```
|
|
376
397
|
|
|
377
|
-
to check if user has any of
|
|
398
|
+
to check if user has any of the permission
|
|
378
399
|
|
|
379
400
|
```typescript
|
|
380
401
|
|
|
@@ -383,9 +404,11 @@ await Acl.model(user).hasAnyPermission(['update', 'delete'])
|
|
|
383
404
|
// or
|
|
384
405
|
await Acl.model(user).canAny(['update', 'delete']) // alias for hasAnyPermission() method
|
|
385
406
|
|
|
407
|
+
// will return true if user has at least one permission
|
|
408
|
+
|
|
386
409
|
```
|
|
387
410
|
|
|
388
|
-
Same applies for
|
|
411
|
+
Same applies for the roles
|
|
389
412
|
|
|
390
413
|
```typescript
|
|
391
414
|
|
|
@@ -395,9 +418,42 @@ await Acl.role(role).hasAnyPermission(['update', 'read'])
|
|
|
395
418
|
|
|
396
419
|
```
|
|
397
420
|
|
|
398
|
-
###
|
|
421
|
+
### Middleware
|
|
422
|
+
|
|
423
|
+
You are free to do your check anywhere, for example we can create [named](https://docs.adonisjs.com/guides/middleware#named-middleware-collection) middleware and do checking
|
|
424
|
+
|
|
425
|
+
> don't forget to register your middleware inside kernel.ts
|
|
399
426
|
|
|
400
|
-
|
|
427
|
+
|
|
428
|
+
```typescript
|
|
429
|
+
|
|
430
|
+
// routes.ts
|
|
431
|
+
import { middleware } from '#start/kernel'
|
|
432
|
+
|
|
433
|
+
// routes.ts
|
|
434
|
+
router.get('/posts/:id', [ProductsController, 'show']).use(middleware.acl({permission: 'edit'}))
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
// acl_middleware.ts
|
|
438
|
+
export default class AclMiddleware {
|
|
439
|
+
async handle(ctx: HttpContext, next: NextFn, options: { permission: string }) {
|
|
440
|
+
|
|
441
|
+
const hasPermission = await ctx.auth.user.hasPermission(options.permission)
|
|
442
|
+
|
|
443
|
+
if(!hasPermission) {
|
|
444
|
+
ctx.response.abort({ message: 'Cannot edit post' }, 403)
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
const output = await next()
|
|
448
|
+
return output
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
### Removing (revoking/detach) roles and permissions from the model
|
|
455
|
+
|
|
456
|
+
To remove(detach) role from the user we can use `revoke` method
|
|
401
457
|
|
|
402
458
|
```typescript
|
|
403
459
|
|
|
@@ -413,11 +469,14 @@ Removing permissions from the user
|
|
|
413
469
|
|
|
414
470
|
```typescript
|
|
415
471
|
await Acl.model(user).revokePermission('update')
|
|
472
|
+
await Acl.model(user).revoke('delete') // alias for revokePermission()
|
|
473
|
+
|
|
416
474
|
// await Acl.model(user).hasPermission('update') will return false
|
|
417
475
|
|
|
418
476
|
await Acl.model(user).revokeAllPermissions(['update', 'delete'])
|
|
477
|
+
await Acl.model(user).revokeAll(['update', 'delete']) // alias for revokeAllPermissions()
|
|
419
478
|
|
|
420
|
-
//
|
|
479
|
+
// revoke all assigned permissions
|
|
421
480
|
await Acl.model(user).flushPermissions()
|
|
422
481
|
|
|
423
482
|
// revokes all roles and permissions for a user
|
|
@@ -430,14 +489,14 @@ Removing permissions from the role
|
|
|
430
489
|
```typescript
|
|
431
490
|
await Acl.role(role).revokePermission('update')
|
|
432
491
|
// or
|
|
433
|
-
await Acl.role(role).revoke('update') // alias for revokePermission
|
|
492
|
+
await Acl.role(role).revoke('update') // alias for revokePermission
|
|
434
493
|
|
|
435
494
|
await Acl.role(role).revokeAllPermissions(['update', 'delete'])
|
|
436
|
-
// alias revokeAll(['update', 'delete'])
|
|
495
|
+
// alias revokeAll(['update', 'delete'])
|
|
437
496
|
|
|
438
497
|
// remove all assigned permissions
|
|
439
498
|
await Acl.role(role).flushPermissions()
|
|
440
|
-
// alias flush()
|
|
499
|
+
// alias flush()
|
|
441
500
|
|
|
442
501
|
```
|
|
443
502
|
|
|
@@ -458,40 +517,45 @@ To see in dept usage of this methods check [next section](#digging-deeper)
|
|
|
458
517
|
|
|
459
518
|
## Digging deeper
|
|
460
519
|
|
|
461
|
-
In [previous](#basic-usage) section we looked basic examples and usage
|
|
520
|
+
In the [previous](#basic-usage) section, we looked at basic examples and usage. Most of the time, basic usage will probably be enough for your project. However, there is much more we can do with `Acl`.
|
|
462
521
|
|
|
463
|
-
### Restricting a permission to a model (On resource)
|
|
522
|
+
### Restricting a permission to a model (On a resource)
|
|
464
523
|
|
|
465
|
-
Sometimes you might want to restrict a permission to a specific model type. Simply pass the model
|
|
524
|
+
Sometimes you might want to restrict a permission to a specific model type. Simply pass the model as a second argument:
|
|
466
525
|
|
|
467
526
|
```typescript
|
|
468
527
|
import Product from "#models/product";
|
|
469
528
|
|
|
470
|
-
await Acl.model(user).
|
|
529
|
+
await Acl.model(user).allow('edit', Product)
|
|
471
530
|
|
|
472
531
|
```
|
|
473
|
-
>
|
|
532
|
+
>Important! - Don't forget to add `MorphMap` decorator and `AclModelInterface` on Product class
|
|
474
533
|
|
|
475
534
|
```typescript
|
|
476
535
|
|
|
477
536
|
@MorphMap('products')
|
|
478
|
-
export default class Product extends BaseModel {
|
|
537
|
+
export default class Product extends BaseModel implements AclModelInterface {
|
|
538
|
+
getModelId(): number {
|
|
539
|
+
return this.id
|
|
540
|
+
}
|
|
479
541
|
// other code
|
|
480
542
|
}
|
|
481
543
|
|
|
482
544
|
```
|
|
483
545
|
|
|
484
|
-
>Warning: All models which interact with Acl **MUST** use [MorphMap]() decorator
|
|
546
|
+
>Warning: All models which interact with Acl **MUST** use [MorphMap]() decorator and implement `AclModelInterface`
|
|
485
547
|
|
|
486
|
-
|
|
548
|
+
Then we can make checking again
|
|
487
549
|
|
|
488
550
|
```typescript
|
|
489
551
|
import Product from "#models/product";
|
|
490
552
|
import Post from "#models/post";
|
|
491
553
|
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
const
|
|
554
|
+
// await Acl.model(user).allow('edit', Product)
|
|
555
|
+
|
|
556
|
+
const productModel1 = Product.find(id1)
|
|
557
|
+
const productModel50 = Product.find(id50)
|
|
558
|
+
const postModel = Post.find(postId)
|
|
495
559
|
|
|
496
560
|
await Acl.model(user).hasPermission('edit', productModel1) // true
|
|
497
561
|
await Acl.model(user).hasPermission('edit', productModel50) // true
|
|
@@ -532,16 +596,17 @@ await Acl.model(user).containsPermission('edit') // true
|
|
|
532
596
|
|
|
533
597
|
```
|
|
534
598
|
|
|
535
|
-
This will behave same way if assign permission through the role instead of
|
|
599
|
+
This will behave the same way if you assign the permission through the role instead of directly
|
|
536
600
|
|
|
537
601
|
```typescript
|
|
538
602
|
|
|
539
603
|
const product1 = Product.find(1)
|
|
540
604
|
|
|
541
|
-
await Acl.role(admin).
|
|
605
|
+
await Acl.role(admin).allow('edit', product1)
|
|
542
606
|
|
|
543
607
|
const user = await User.first()
|
|
544
608
|
|
|
609
|
+
// assign role
|
|
545
610
|
await Acl.model(user).assignRole(role)
|
|
546
611
|
|
|
547
612
|
// then if we start checking, result will be same
|
|
@@ -560,7 +625,9 @@ await Acl.model(user).containsPermission('edit') // true
|
|
|
560
625
|
|
|
561
626
|
### Forbidding permissions
|
|
562
627
|
|
|
563
|
-
Let's imagine a situation
|
|
628
|
+
Let's imagine a situation where `manager` role has `create,update,read,delete` permissions.
|
|
629
|
+
|
|
630
|
+
All your users have `manager` role but there are small amount of users you want to forbid `delete` action.
|
|
564
631
|
Good news!, we can do that
|
|
565
632
|
|
|
566
633
|
```typescript
|
|
@@ -568,18 +635,18 @@ Good news!, we can do that
|
|
|
568
635
|
await Acl.role(manager).giveAll(['create','update','read','delete'])
|
|
569
636
|
|
|
570
637
|
// assigning to the users
|
|
571
|
-
await Acl.model(user1).
|
|
638
|
+
await Acl.model(user1).assign(manager)
|
|
572
639
|
|
|
573
|
-
await Acl.model(user3).
|
|
640
|
+
await Acl.model(user3).assign(manager)
|
|
574
641
|
await Acl.model(user3).forbid('delete')
|
|
575
642
|
|
|
576
643
|
await Acl.model(user1).hasRole(manager) // true
|
|
577
|
-
await Acl.model(user1).
|
|
644
|
+
await Acl.model(user1).can('delete') // true
|
|
578
645
|
|
|
579
646
|
await Acl.model(user3).hasRole(manager) // true
|
|
580
|
-
await Acl.model(user3).
|
|
647
|
+
await Acl.model(user3).can('delete') // false
|
|
581
648
|
|
|
582
|
-
await Acl.model(user3).
|
|
649
|
+
await Acl.model(user3).contains('delete') // true
|
|
583
650
|
|
|
584
651
|
```
|
|
585
652
|
|
|
@@ -589,7 +656,7 @@ You can also forbid single action on a resource
|
|
|
589
656
|
|
|
590
657
|
```typescript
|
|
591
658
|
|
|
592
|
-
const post = Post.find(
|
|
659
|
+
const post = Post.find(id1)
|
|
593
660
|
|
|
594
661
|
await Acl.model(user3).forbid('delete', post)
|
|
595
662
|
|
|
@@ -606,17 +673,15 @@ await Acl.model(user3).forbid('delete')
|
|
|
606
673
|
|
|
607
674
|
await Acl.model(user3).forbidden('delete') // true
|
|
608
675
|
|
|
609
|
-
|
|
610
|
-
const post1 = Post.find(id1ToFind)
|
|
611
|
-
|
|
676
|
+
const post1 = Post.find(id1)
|
|
612
677
|
|
|
613
678
|
await Acl.model(user).allow('edit', Post) // allow for all posts
|
|
614
679
|
await Acl.model(user).forbid('edit', post1) // except post1
|
|
615
680
|
|
|
616
681
|
await Acl.model(user).forbidden('edit', post1) // true
|
|
617
682
|
|
|
618
|
-
const post7 = Post.find(
|
|
619
|
-
await Acl.model(user).forbidden('edit', post7) // false becouse 'edit' action forbidden only for post1 instance
|
|
683
|
+
const post7 = Post.find(id7)
|
|
684
|
+
await Acl.model(user).forbidden('edit', post7) // false becouse 'edit' action forbidden only for the post1 instance
|
|
620
685
|
```
|
|
621
686
|
|
|
622
687
|
### Unforbidding the permissions
|
|
@@ -627,12 +692,12 @@ await Acl.model(user3).assignRole(manager)
|
|
|
627
692
|
await Acl.model(user3).forbid('delete')
|
|
628
693
|
|
|
629
694
|
await Acl.model(user3).forbidden('delete') // true
|
|
630
|
-
await Acl.model(user3).
|
|
631
|
-
await Acl.model(user3).
|
|
695
|
+
await Acl.model(user3).can('delete') // false
|
|
696
|
+
await Acl.model(user3).can('delete') // true
|
|
632
697
|
|
|
633
698
|
await Acl.model(user3).unforbid('delete')
|
|
634
699
|
await Acl.model(user3).forbidden('delete') // false
|
|
635
|
-
await Acl.model(user3).
|
|
700
|
+
await Acl.model(user3).can('delete') // true
|
|
636
701
|
|
|
637
702
|
```
|
|
638
703
|
Same behaviour applies with roles
|
|
@@ -643,7 +708,7 @@ await Acl.role(role).forbid('delete')
|
|
|
643
708
|
|
|
644
709
|
await Acl.role(role).forbidden('delete') // true
|
|
645
710
|
await Acl.role(role).hasPermission('delete') // false
|
|
646
|
-
await Acl.role(role).
|
|
711
|
+
await Acl.role(role).contains('delete') // true
|
|
647
712
|
|
|
648
713
|
```
|
|
649
714
|
|
|
@@ -652,7 +717,7 @@ await Acl.role(role).containsPermission('delete') // true
|
|
|
652
717
|
> Important! Action performed globally will affect on a resource models
|
|
653
718
|
|
|
654
719
|
It is very important to understood difference between global and resource permissions and their scope.
|
|
655
|
-
Look at this way, if there is no `entity` model then
|
|
720
|
+
Look at this way, if there is no `entity` model then actions will be performed **globally**, otherwise **on resource**
|
|
656
721
|
|
|
657
722
|
```
|
|
658
723
|
|
|
@@ -673,13 +738,11 @@ Look at this way, if there is no `entity` model then action will be performed **
|
|
|
673
738
|
|
|
674
739
|
```
|
|
675
740
|
|
|
676
|
-
|
|
677
|
-
|
|
678
741
|
```typescript
|
|
679
742
|
import {Acl} from "@holoyan/adonisjs-permissions";
|
|
680
743
|
import Post from "#models/post";
|
|
681
744
|
|
|
682
|
-
|
|
745
|
+
// first assigning permissions
|
|
683
746
|
// Global level
|
|
684
747
|
await Acl.model(admin).allow('create');
|
|
685
748
|
await Acl.model(admin).allow('edit');
|
|
@@ -692,19 +755,19 @@ await Acl.model(manager).allow('create', Post)
|
|
|
692
755
|
const myPost = await Post.find(id)
|
|
693
756
|
await Acl.model(client).allow('view', myPost)
|
|
694
757
|
|
|
695
|
-
// checking
|
|
758
|
+
// start checking
|
|
696
759
|
// admin
|
|
697
760
|
await Acl.model(admin).hasPermission('create') // true
|
|
698
761
|
await Acl.model(admin).hasPermission('create', Post) // true
|
|
699
762
|
await Acl.model(admin).hasPermission('create', myPost) // true
|
|
700
763
|
|
|
701
|
-
// manager
|
|
764
|
+
// manager - assigned class level
|
|
702
765
|
await Acl.model(manager).hasPermission('create') // false
|
|
703
766
|
await Acl.model(manager).hasPermission('create', Post) // true
|
|
704
767
|
await Acl.model(manager).hasPermission('create', myPost) // true
|
|
705
768
|
await Acl.model(manager).hasPermission('create', myOtherPost) // true
|
|
706
769
|
|
|
707
|
-
//
|
|
770
|
+
// assigned model level
|
|
708
771
|
await Acl.model(client).hasPermission('create') // false
|
|
709
772
|
await Acl.model(client).hasPermission('create', Post) // false
|
|
710
773
|
await Acl.model(client).hasPermission('create', myPost) // true
|
|
@@ -720,8 +783,7 @@ Same is true when using `forbidden` action
|
|
|
720
783
|
// class level
|
|
721
784
|
await Acl.model(manager).allow('edit', Post) // allow to edit all posts
|
|
722
785
|
|
|
723
|
-
await Acl.model(manager).forbid('edit', myPost) // forbid editing on a
|
|
724
|
-
|
|
786
|
+
await Acl.model(manager).forbid('edit', myPost) // forbid editing ONLY on a myPost
|
|
725
787
|
|
|
726
788
|
await Acl.model(client).hasPermission('edit', Post) // true
|
|
727
789
|
await Acl.model(client).hasPermission('edit', myPost) // false
|
|
@@ -731,7 +793,9 @@ await Acl.model(client).hasPermission('edit', myOtherPost) // true
|
|
|
731
793
|
|
|
732
794
|
### containsPermission v hasPermission
|
|
733
795
|
|
|
734
|
-
As you've already seen there are difference between `containsPermission` and `hasPermission
|
|
796
|
+
As you've already seen there are difference between `containsPermission` and `hasPermission` methods. `containsPermission()` method will return `true` if user has that permission, it doesn't matter if it's *global*, *on resource* or *forbidden*.
|
|
797
|
+
|
|
798
|
+
> `contains()` method is alias for `containsPermission()`
|
|
735
799
|
|
|
736
800
|
Lets in example see this difference
|
|
737
801
|
|
|
@@ -751,6 +815,122 @@ await Acl.model(user).containsPermission('read') // true
|
|
|
751
815
|
|
|
752
816
|
```
|
|
753
817
|
|
|
818
|
+
## Scopes or Multi-tenancy
|
|
819
|
+
|
|
820
|
+
Acl fully supports multi-tenant apps, allowing you to seamlessly integrate roles and permissions for all tenants within the same app.
|
|
821
|
+
|
|
822
|
+
```typescript
|
|
823
|
+
console.log(user.project_id > 0) // true - lets say user.project_id is not equal to zero
|
|
824
|
+
|
|
825
|
+
await Acl.model(user).on(user.project_id).allow('edit')
|
|
826
|
+
await Acl.model(user).on(user.project_id).allow('delete')
|
|
827
|
+
|
|
828
|
+
// checking
|
|
829
|
+
await Acl.model(user).on(user.project_id).hasPermission('edit') // true
|
|
830
|
+
await Acl.model(user).on(user.project_id).hasPermission('delete') // true
|
|
831
|
+
|
|
832
|
+
// checking without scope
|
|
833
|
+
await Acl.model(user).hasPermission('edit') // false - by default scope is equal to 0
|
|
834
|
+
|
|
835
|
+
|
|
836
|
+
```
|
|
837
|
+
|
|
838
|
+
### The Scope middleware
|
|
839
|
+
|
|
840
|
+
`Acl` has built-in middleware to make scope checking easier.
|
|
841
|
+
|
|
842
|
+
|
|
843
|
+
This middleware is where you tell `Acl` which tenant to use for the current request. For example, assuming your users all have an account_id attribute, this is what your middleware would look like:
|
|
844
|
+
|
|
845
|
+
```typescript
|
|
846
|
+
|
|
847
|
+
// acl_middleware
|
|
848
|
+
export default class AclScopeMiddleware {
|
|
849
|
+
async handle(ctx: HttpContext, next: NextFn) {
|
|
850
|
+
const scope = new Scope()
|
|
851
|
+
scope.set(auth.user.account_id)
|
|
852
|
+
ctx.acl = new AclManager().scope(scope)
|
|
853
|
+
/**
|
|
854
|
+
* Call next method in the pipeline and return its output
|
|
855
|
+
*/
|
|
856
|
+
const output = await next()
|
|
857
|
+
return output
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
// then on controller you can do
|
|
862
|
+
|
|
863
|
+
// post_controller.ts
|
|
864
|
+
|
|
865
|
+
export default class PostController {
|
|
866
|
+
async show({acl}: HttpContext){
|
|
867
|
+
// will check inside auth.user.account_id scope
|
|
868
|
+
await acl.model().hasPermission('view')
|
|
869
|
+
// this both will be equal
|
|
870
|
+
// await acl.model().on(auth.user.account_id).hasPermission('view')
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
```
|
|
875
|
+
> Important! If you are using `AclScopeMiddleware` and want to have scope functional per-request then use `acl` from the `ctx` instead of using global `Acl` object, otherwise changes inside `AclScopeMiddleware` will not make effect
|
|
876
|
+
|
|
877
|
+
Let's see in example
|
|
878
|
+
|
|
879
|
+
|
|
880
|
+
```typescript
|
|
881
|
+
|
|
882
|
+
// acl_middleware
|
|
883
|
+
export default class AclScopeMiddleware {
|
|
884
|
+
async handle(ctx: HttpContext, next: NextFn) {
|
|
885
|
+
const scope = new Scope()
|
|
886
|
+
scope.set(5) // set scope value to 5 for current request
|
|
887
|
+
ctx.acl = new AclManager().scope(scope)
|
|
888
|
+
/**
|
|
889
|
+
* Call next method in the pipeline and return its output
|
|
890
|
+
*/
|
|
891
|
+
const output = await next()
|
|
892
|
+
return output
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
// post_controller.ts
|
|
897
|
+
// global object
|
|
898
|
+
import {Acl} from "@holoyan/adonisjs-permissions";
|
|
899
|
+
|
|
900
|
+
export default class PostController {
|
|
901
|
+
async show({acl}: HttpContext){
|
|
902
|
+
const scope = acl.getScope()
|
|
903
|
+
console.log(scope.get()) // 5
|
|
904
|
+
// global object
|
|
905
|
+
console.log(Acl.getScope()) // 0
|
|
906
|
+
|
|
907
|
+
acl.scope(7)// update and set new scope
|
|
908
|
+
// for current request it will be 7
|
|
909
|
+
console.log(acl.getScope()) // 7
|
|
910
|
+
|
|
911
|
+
Acl.scope(7) // Throws error
|
|
912
|
+
// you can't update scope on global object, only runtime
|
|
913
|
+
await Acl.model(user).on(8).permissions() // get permissions for user on scope 8
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
```
|
|
918
|
+
|
|
919
|
+
|
|
920
|
+
### Default scope (Tenant)
|
|
921
|
+
|
|
922
|
+
> Default Scope value is equal to 0 (zero)
|
|
923
|
+
|
|
924
|
+
|
|
925
|
+
## TODO
|
|
926
|
+
|
|
927
|
+
- [X] Scopes (Multitenancy)
|
|
928
|
+
- [ ] UUID support
|
|
929
|
+
- [ ] Events
|
|
930
|
+
- [ ] More test coverage
|
|
931
|
+
- [ ] Caching
|
|
932
|
+
- [ ] Integration with AdonisJs Bouncer
|
|
933
|
+
|
|
754
934
|
## Test
|
|
755
935
|
|
|
756
936
|
npm run test
|