@holoyan/adonisjs-permissions 0.5.2 → 0.6.8
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 +294 -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/helper.js +3 -3
- package/build/src/services/model_has_role_permissions.d.ts +13 -3
- package/build/src/services/model_has_role_permissions.js +33 -3
- package/build/src/services/permissions/empty_permission.d.ts +7 -2
- package/build/src/services/permissions/empty_permission.js +20 -3
- 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 +7 -2
- package/build/src/services/roles/empty_roles.js +20 -3
- 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 +22 -5
- package/build/src/types.d.ts +22 -1
- package/build/stubs/middlewares/acl_middleware.stub +26 -0
- package/build/stubs/migrations/create_db.stub +23 -24
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
- [Basic Usage](#basic-usage)
|
|
20
20
|
- [Creating roles and permissions](#creating-roles-and-permissions)
|
|
21
21
|
- [Assigning permissions to the roles (Globally)](#assigning-permissions-to-the-roles-globally)
|
|
22
|
+
- [Creating permission on a fly](#creating-permission-on-a-fly)
|
|
22
23
|
- [Assigning permissions and roles to the users (models)](#assigning-permissions-and-roles-to-the-users-models)
|
|
23
24
|
- [Multi-model support](#multi-model-support)
|
|
24
25
|
- [Getting all roles for a user](#getting-all-roles-for-a-user)
|
|
@@ -27,36 +28,39 @@
|
|
|
27
28
|
- [Getting users (models) for a permission](#getting-users-models-for-a-permission)
|
|
28
29
|
- [Getting models for a role](#getting-models-for-a-role)
|
|
29
30
|
- [Checking for a permission](#checking-for-a-permission)
|
|
30
|
-
- [
|
|
31
|
+
- [Middleware](#middleware)
|
|
32
|
+
- [Removing (revoking) roles and permissions from the model](#removing-revokingdetach-roles-and-permissions-from-the-model)
|
|
31
33
|
- [Digging deeper](#digging-deeper)
|
|
32
|
-
- [Restricting a permission to a model (On resource)](#restricting-a-permission-to-a-model-on-resource)
|
|
34
|
+
- [Restricting a permission to a model (On a resource)](#restricting-a-permission-to-a-model-on-a-resource)
|
|
33
35
|
- [Forbidding permissions](#forbidding-permissions)
|
|
34
36
|
- [Forbidding permissions on a resource](#forbidding-permissions-on-a-resource)
|
|
35
37
|
- [Checking for forbidden permissions](#checking-for-forbidden-permissions)
|
|
36
38
|
- [Unforbidding the permissions](#unforbidding-the-permissions)
|
|
37
39
|
- [Global vs resource permissions (Important!)](#global-vs-resource-permissions-important)
|
|
38
40
|
- [containsPermission v hasPermission](#containspermission-v-haspermission)
|
|
41
|
+
- [Scopes or Multi-tenancy](#scopes-or-multi-tenancy)
|
|
42
|
+
- [The Scope middleware](#the-scope-middleware)
|
|
43
|
+
- [Default Scope](#default-scope-tenant)
|
|
44
|
+
- [Cheat sheet](#cheat-sheet)
|
|
45
|
+
- [Todo](#todo)
|
|
39
46
|
- [Test](#test)
|
|
40
47
|
- [License](#license)
|
|
41
48
|
</p></details>
|
|
42
49
|
|
|
43
50
|
## Introduction
|
|
44
51
|
|
|
45
|
-
AdonisJs
|
|
52
|
+
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
53
|
|
|
47
|
-
For a quick, glanceable list of
|
|
54
|
+
For a quick, glanceable list of Acl's features, check out the [cheat sheet](#cheat-sheet)
|
|
48
55
|
|
|
49
56
|
Once installed, you can simply tell the Acl what you want to allow:
|
|
50
57
|
|
|
51
58
|
```typescript
|
|
52
59
|
import {Acl} from '@holoyan/adonisjs-permissions'
|
|
53
60
|
|
|
54
|
-
|
|
55
61
|
// Give a user the permission to edit
|
|
56
62
|
await Acl.model(user).allow('edit');
|
|
57
|
-
|
|
58
|
-
// Alternatively, do it through a permission
|
|
59
|
-
await Acl.permission('edit').attachToModel(user);
|
|
63
|
+
// Behind the scenes Acl will create 'edit' permission and assign to the user if not available
|
|
60
64
|
|
|
61
65
|
// You can also grant a permission only to a specific model
|
|
62
66
|
const post = await Post.first()
|
|
@@ -65,8 +69,7 @@ await Acl.model(user).allow('delete', post);
|
|
|
65
69
|
await user.allow('delete', post)
|
|
66
70
|
```
|
|
67
71
|
|
|
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
|
|
72
|
+
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
73
|
|
|
71
74
|
## Installation
|
|
72
75
|
|
|
@@ -76,7 +79,7 @@ For most of the applications [Basic usage](#basic-usage) will be enough
|
|
|
76
79
|
Next publish config files
|
|
77
80
|
|
|
78
81
|
node ace configure @holoyan/adonisjs-permissions
|
|
79
|
-
this will create permissions.ts file in `configs`
|
|
82
|
+
this will create `permissions.ts` file in `configs` directory, migration file in the `database/migrations` directory
|
|
80
83
|
|
|
81
84
|
Next run migration
|
|
82
85
|
|
|
@@ -85,7 +88,7 @@ Next run migration
|
|
|
85
88
|
|
|
86
89
|
## Configuration
|
|
87
90
|
|
|
88
|
-
All models
|
|
91
|
+
All models that will interact with `Acl` MUST use the `@MorphMap('ALIAS_FOR_CLASS')` decorator and implement the `AclModelInterface` contract.
|
|
89
92
|
|
|
90
93
|
Example.
|
|
91
94
|
|
|
@@ -123,10 +126,9 @@ export default class Post extends BaseModel implements AclModelInterface {
|
|
|
123
126
|
|
|
124
127
|
```
|
|
125
128
|
|
|
126
|
-
|
|
127
129
|
## Mixins
|
|
128
130
|
|
|
129
|
-
If you want to be able to call
|
|
131
|
+
If you want to be able to call `Acl` methods on a `User` model then consider using `hasPermissions` mixin
|
|
130
132
|
|
|
131
133
|
```typescript
|
|
132
134
|
|
|
@@ -149,8 +151,9 @@ export default class User extends compose(BaseModel, hasPermissions()) implement
|
|
|
149
151
|
// then all methods are available on the user
|
|
150
152
|
|
|
151
153
|
const user = await User.first()
|
|
152
|
-
const roles = await user.roles() //
|
|
153
|
-
|
|
154
|
+
const roles = await user.roles() // get user roles
|
|
155
|
+
await user.allow('edit') // give edit permission
|
|
156
|
+
// and so on...
|
|
154
157
|
|
|
155
158
|
```
|
|
156
159
|
|
|
@@ -161,66 +164,93 @@ const roles = await user.roles() // and so on
|
|
|
161
164
|
Currently supported databases: `postgres`, `mysql`, `mssql`
|
|
162
165
|
|
|
163
166
|
### UUID support
|
|
164
|
-
No uuid support *yet
|
|
167
|
+
No uuid support *yet*, check [todo](#todo) list for more details
|
|
165
168
|
|
|
166
169
|
## Basic Usage
|
|
167
170
|
|
|
168
|
-
On this section we will explore basic role permission methods
|
|
171
|
+
On this section, we will explore basic role permission methods.
|
|
169
172
|
|
|
170
173
|
### Creating roles and permissions
|
|
171
174
|
|
|
172
|
-
Let's create `create,update,read,delete` permissions
|
|
175
|
+
Let's manually create `create,update,read,delete` permissions, as well as `admin,manager` roles
|
|
176
|
+
|
|
177
|
+
> Look also [Creating permissions on a fly](#creating-permissions-on-a-fly) section
|
|
173
178
|
|
|
174
179
|
```typescript
|
|
175
180
|
|
|
181
|
+
import { Permission } from '@holoyan/adonisjs-permissions'
|
|
182
|
+
import { Role } from '@holoyan/adonisjs-permissions'
|
|
183
|
+
import {Acl} from "@holoyan/adonisjs-permissions";
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
|
|
176
188
|
// create permissions
|
|
177
|
-
const create = await
|
|
189
|
+
const create = await Permission.create({
|
|
178
190
|
slug:'create',
|
|
179
|
-
title:'Create some resource',
|
|
191
|
+
title:'Create some resource', // optional
|
|
180
192
|
})
|
|
181
193
|
|
|
182
|
-
const update = await
|
|
194
|
+
const update = await Permission.create({
|
|
183
195
|
slug:'update',
|
|
184
|
-
title:'update some resource',
|
|
185
196
|
})
|
|
186
197
|
|
|
198
|
+
// or create using Acl (recomended way)
|
|
187
199
|
const read = await Acl.permission().create({
|
|
188
|
-
slug:'read',
|
|
189
|
-
title:'read some resource',
|
|
200
|
+
slug: 'read',
|
|
190
201
|
})
|
|
191
202
|
|
|
203
|
+
|
|
192
204
|
const delete = await Acl.permission().create({
|
|
193
|
-
slug:'delete',
|
|
194
|
-
title:'delete some resource',
|
|
205
|
+
slug: 'delete',
|
|
195
206
|
})
|
|
196
207
|
|
|
197
208
|
// create roles
|
|
198
|
-
const admin = await
|
|
209
|
+
const admin = await Role.create({
|
|
199
210
|
slug:'admin',
|
|
200
|
-
title:'Cool title for Admin',
|
|
211
|
+
title:'Cool title for Admin', // optional
|
|
201
212
|
})
|
|
202
213
|
|
|
214
|
+
// or create using Acl (recomended way)
|
|
203
215
|
const manager = await Acl.role().create({
|
|
204
|
-
slug:'manager',
|
|
205
|
-
title:'Cool title for Manager',
|
|
216
|
+
slug: 'manager',
|
|
206
217
|
})
|
|
207
218
|
|
|
208
219
|
```
|
|
209
220
|
|
|
210
|
-
|
|
221
|
+
|
|
222
|
+
The next step is to [assign permissions to the roles](#assigning-permissions-to-the-roles-globally)
|
|
211
223
|
|
|
212
224
|
### Assigning permissions to the roles (Globally)
|
|
213
225
|
|
|
214
|
-
Now
|
|
226
|
+
Now that we have created roles and permissions, let's assign them.
|
|
215
227
|
|
|
216
228
|
```typescript
|
|
229
|
+
import {Acl} from "@holoyan/adonisjs-permissions";
|
|
230
|
+
|
|
231
|
+
|
|
217
232
|
await Acl.role(admin).assign('create')
|
|
218
|
-
//
|
|
219
|
-
|
|
220
|
-
|
|
233
|
+
// alternatively you can use allow(), give() method, as they are identical
|
|
234
|
+
|
|
235
|
+
await Acl.role(admin).allow('update')
|
|
221
236
|
await Acl.role(admin).giveAll(['read', 'delete'])
|
|
237
|
+
// alternatively you use giveAll(), assigneAll(), allowAll() for bulk assign
|
|
238
|
+
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### Creating permissions on a fly
|
|
242
|
+
|
|
243
|
+
In case you are assigning a permission that is not already available, `Acl` will create new permission behind the scenes and assign them.
|
|
244
|
+
|
|
245
|
+
```typescript
|
|
246
|
+
|
|
247
|
+
// uploadFile permission not available
|
|
248
|
+
await Acl.role(admin).allow('uploadFile')
|
|
249
|
+
// 'uploadFile' permission created and assigned
|
|
250
|
+
|
|
222
251
|
```
|
|
223
252
|
|
|
253
|
+
|
|
224
254
|
### Assigning permissions and roles to the users (models)
|
|
225
255
|
|
|
226
256
|
Let's see in examples how to assign [roles and permissions](#creating-roles-and-permissions) to the users
|
|
@@ -232,6 +262,8 @@ import User from "#models/user";
|
|
|
232
262
|
const user1 = await User.query().where(condition1).first()
|
|
233
263
|
// give manager role to the user1
|
|
234
264
|
await Acl.model(user1).assignRole(manager)
|
|
265
|
+
// or just use assign() method, they are alias
|
|
266
|
+
// await Acl.model(user1).assign(manager)
|
|
235
267
|
|
|
236
268
|
const user2 = await User.query().where(condition2).first()
|
|
237
269
|
await Acl.model(user2).assignRole(admin)
|
|
@@ -243,19 +275,15 @@ Or we can give permissions directly to users without having any role
|
|
|
243
275
|
|
|
244
276
|
import {Acl} from "@holoyan/adonisjs-permissions";
|
|
245
277
|
|
|
246
|
-
// create new permission
|
|
247
|
-
const uploadFile = await Acl.permission().create({
|
|
248
|
-
slug: 'upload-file-slug',
|
|
249
|
-
title: 'permisison to upload files',
|
|
250
|
-
})
|
|
251
|
-
|
|
278
|
+
// create and assign a new permission
|
|
252
279
|
Acl.model(user1).assignDirectPermission('upload-file-slug')
|
|
253
|
-
|
|
280
|
+
// or use allow() method
|
|
281
|
+
Acl.model(user1).allow('permissionSlug')
|
|
254
282
|
```
|
|
255
283
|
|
|
256
284
|
### Multi-model support
|
|
257
285
|
|
|
258
|
-
We are not
|
|
286
|
+
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
287
|
|
|
260
288
|
|
|
261
289
|
```typescript
|
|
@@ -290,16 +318,16 @@ const roles = await Acl.role(role).permissions()
|
|
|
290
318
|
const roles = await Acl.model(user).permissions()
|
|
291
319
|
```
|
|
292
320
|
|
|
293
|
-
### Getting users (models)
|
|
321
|
+
### Getting users (models) from the permission
|
|
294
322
|
|
|
295
323
|
```typescript
|
|
296
324
|
|
|
297
325
|
const models = await Acl.permission(permission).models()
|
|
298
326
|
|
|
299
327
|
```
|
|
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
|
|
328
|
+
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
329
|
|
|
302
|
-
Most of the
|
|
330
|
+
Most of the time, you will have only one model (User). It's better to use the `modelsFor()` method to get concrete models.
|
|
303
331
|
|
|
304
332
|
```typescript
|
|
305
333
|
|
|
@@ -320,7 +348,7 @@ Or if you want to get for a specific model
|
|
|
320
348
|
|
|
321
349
|
```typescript
|
|
322
350
|
|
|
323
|
-
const models = await Acl.role(permission).modelsFor(
|
|
351
|
+
const models = await Acl.role(permission).modelsFor(User)
|
|
324
352
|
|
|
325
353
|
```
|
|
326
354
|
|
|
@@ -334,7 +362,7 @@ await Acl.model(user).hasRole('admin') // :boolean
|
|
|
334
362
|
|
|
335
363
|
```
|
|
336
364
|
|
|
337
|
-
you can pass
|
|
365
|
+
you can pass list of roles
|
|
338
366
|
|
|
339
367
|
```typescript
|
|
340
368
|
|
|
@@ -343,11 +371,12 @@ await Acl.model(user).hasAllRoles('admin', 'manager')
|
|
|
343
371
|
|
|
344
372
|
```
|
|
345
373
|
|
|
346
|
-
|
|
374
|
+
To check if a user has any of the roles
|
|
347
375
|
|
|
348
376
|
```typescript
|
|
349
377
|
|
|
350
378
|
await Acl.model(user).hasAnyRole('admin', 'manager')
|
|
379
|
+
// it will return true if the user has at least one role.
|
|
351
380
|
|
|
352
381
|
```
|
|
353
382
|
|
|
@@ -361,6 +390,8 @@ await Acl.model(user).hasPermission('update')
|
|
|
361
390
|
// or
|
|
362
391
|
await Acl.model(user).can('update') // alias for hasPermission() method
|
|
363
392
|
|
|
393
|
+
// or simply call
|
|
394
|
+
await user.hasPermission('update')
|
|
364
395
|
```
|
|
365
396
|
|
|
366
397
|
To check array of permissions
|
|
@@ -372,9 +403,11 @@ await Acl.model(user).hasAllPermissions(['update', 'delete'])
|
|
|
372
403
|
// or
|
|
373
404
|
await Acl.model(user).canAll(['update', 'delete']) // alias for hasAllPermissions() method
|
|
374
405
|
|
|
406
|
+
// await user.canAll(['update', 'delete'])
|
|
407
|
+
|
|
375
408
|
```
|
|
376
409
|
|
|
377
|
-
to check if user has any of
|
|
410
|
+
to check if user has any of the permission
|
|
378
411
|
|
|
379
412
|
```typescript
|
|
380
413
|
|
|
@@ -383,9 +416,11 @@ await Acl.model(user).hasAnyPermission(['update', 'delete'])
|
|
|
383
416
|
// or
|
|
384
417
|
await Acl.model(user).canAny(['update', 'delete']) // alias for hasAnyPermission() method
|
|
385
418
|
|
|
419
|
+
// will return true if user has at least one permission
|
|
420
|
+
|
|
386
421
|
```
|
|
387
422
|
|
|
388
|
-
Same applies for
|
|
423
|
+
Same applies for the roles
|
|
389
424
|
|
|
390
425
|
```typescript
|
|
391
426
|
|
|
@@ -395,9 +430,42 @@ await Acl.role(role).hasAnyPermission(['update', 'read'])
|
|
|
395
430
|
|
|
396
431
|
```
|
|
397
432
|
|
|
398
|
-
###
|
|
433
|
+
### Middleware
|
|
434
|
+
|
|
435
|
+
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
|
|
436
|
+
|
|
437
|
+
> don't forget to register your middleware inside kernel.ts
|
|
399
438
|
|
|
400
|
-
|
|
439
|
+
|
|
440
|
+
```typescript
|
|
441
|
+
|
|
442
|
+
// routes.ts
|
|
443
|
+
import { middleware } from '#start/kernel'
|
|
444
|
+
|
|
445
|
+
// routes.ts
|
|
446
|
+
router.get('/posts/:id', [ProductsController, 'show']).use(middleware.acl({permission: 'edit'}))
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
// acl_middleware.ts
|
|
450
|
+
export default class AclMiddleware {
|
|
451
|
+
async handle(ctx: HttpContext, next: NextFn, options: { permission: string }) {
|
|
452
|
+
|
|
453
|
+
const hasPermission = await ctx.auth.user.hasPermission(options.permission)
|
|
454
|
+
|
|
455
|
+
if(!hasPermission) {
|
|
456
|
+
ctx.response.abort({ message: 'Cannot edit post' }, 403)
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
const output = await next()
|
|
460
|
+
return output
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
### Removing (revoking/detach) roles and permissions from the model
|
|
467
|
+
|
|
468
|
+
To remove(detach) role from the user we can use `revoke` method
|
|
401
469
|
|
|
402
470
|
```typescript
|
|
403
471
|
|
|
@@ -413,11 +481,14 @@ Removing permissions from the user
|
|
|
413
481
|
|
|
414
482
|
```typescript
|
|
415
483
|
await Acl.model(user).revokePermission('update')
|
|
484
|
+
await Acl.model(user).revoke('delete') // alias for revokePermission()
|
|
485
|
+
|
|
416
486
|
// await Acl.model(user).hasPermission('update') will return false
|
|
417
487
|
|
|
418
488
|
await Acl.model(user).revokeAllPermissions(['update', 'delete'])
|
|
489
|
+
await Acl.model(user).revokeAll(['update', 'delete']) // alias for revokeAllPermissions()
|
|
419
490
|
|
|
420
|
-
//
|
|
491
|
+
// revoke all assigned permissions
|
|
421
492
|
await Acl.model(user).flushPermissions()
|
|
422
493
|
|
|
423
494
|
// revokes all roles and permissions for a user
|
|
@@ -430,14 +501,14 @@ Removing permissions from the role
|
|
|
430
501
|
```typescript
|
|
431
502
|
await Acl.role(role).revokePermission('update')
|
|
432
503
|
// or
|
|
433
|
-
await Acl.role(role).revoke('update') // alias for revokePermission
|
|
504
|
+
await Acl.role(role).revoke('update') // alias for revokePermission
|
|
434
505
|
|
|
435
506
|
await Acl.role(role).revokeAllPermissions(['update', 'delete'])
|
|
436
|
-
// alias revokeAll(['update', 'delete'])
|
|
507
|
+
// alias revokeAll(['update', 'delete'])
|
|
437
508
|
|
|
438
509
|
// remove all assigned permissions
|
|
439
510
|
await Acl.role(role).flushPermissions()
|
|
440
|
-
// alias flush()
|
|
511
|
+
// alias flush()
|
|
441
512
|
|
|
442
513
|
```
|
|
443
514
|
|
|
@@ -458,40 +529,45 @@ To see in dept usage of this methods check [next section](#digging-deeper)
|
|
|
458
529
|
|
|
459
530
|
## Digging deeper
|
|
460
531
|
|
|
461
|
-
In [previous](#basic-usage) section we looked basic examples and usage
|
|
532
|
+
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
533
|
|
|
463
|
-
### Restricting a permission to a model (On resource)
|
|
534
|
+
### Restricting a permission to a model (On a resource)
|
|
464
535
|
|
|
465
|
-
Sometimes you might want to restrict a permission to a specific model type. Simply pass the model
|
|
536
|
+
Sometimes you might want to restrict a permission to a specific model type. Simply pass the model as a second argument:
|
|
466
537
|
|
|
467
538
|
```typescript
|
|
468
539
|
import Product from "#models/product";
|
|
469
540
|
|
|
470
|
-
await Acl.model(user).
|
|
541
|
+
await Acl.model(user).allow('edit', Product)
|
|
471
542
|
|
|
472
543
|
```
|
|
473
|
-
>
|
|
544
|
+
>Important! - Don't forget to add `MorphMap` decorator and `AclModelInterface` on Product class
|
|
474
545
|
|
|
475
546
|
```typescript
|
|
476
547
|
|
|
477
548
|
@MorphMap('products')
|
|
478
|
-
export default class Product extends BaseModel {
|
|
549
|
+
export default class Product extends BaseModel implements AclModelInterface {
|
|
550
|
+
getModelId(): number {
|
|
551
|
+
return this.id
|
|
552
|
+
}
|
|
479
553
|
// other code
|
|
480
554
|
}
|
|
481
555
|
|
|
482
556
|
```
|
|
483
557
|
|
|
484
|
-
>Warning: All models which interact with Acl **MUST** use [MorphMap]() decorator
|
|
558
|
+
>Warning: All models which interact with Acl **MUST** use [MorphMap]() decorator and implement `AclModelInterface`
|
|
485
559
|
|
|
486
|
-
|
|
560
|
+
Then we can make checking again
|
|
487
561
|
|
|
488
562
|
```typescript
|
|
489
563
|
import Product from "#models/product";
|
|
490
564
|
import Post from "#models/post";
|
|
491
565
|
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
const
|
|
566
|
+
// await Acl.model(user).allow('edit', Product)
|
|
567
|
+
|
|
568
|
+
const productModel1 = Product.find(id1)
|
|
569
|
+
const productModel50 = Product.find(id50)
|
|
570
|
+
const postModel = Post.find(postId)
|
|
495
571
|
|
|
496
572
|
await Acl.model(user).hasPermission('edit', productModel1) // true
|
|
497
573
|
await Acl.model(user).hasPermission('edit', productModel50) // true
|
|
@@ -532,16 +608,17 @@ await Acl.model(user).containsPermission('edit') // true
|
|
|
532
608
|
|
|
533
609
|
```
|
|
534
610
|
|
|
535
|
-
This will behave same way if assign permission through the role instead of
|
|
611
|
+
This will behave the same way if you assign the permission through the role instead of directly
|
|
536
612
|
|
|
537
613
|
```typescript
|
|
538
614
|
|
|
539
615
|
const product1 = Product.find(1)
|
|
540
616
|
|
|
541
|
-
await Acl.role(admin).
|
|
617
|
+
await Acl.role(admin).allow('edit', product1)
|
|
542
618
|
|
|
543
619
|
const user = await User.first()
|
|
544
620
|
|
|
621
|
+
// assign role
|
|
545
622
|
await Acl.model(user).assignRole(role)
|
|
546
623
|
|
|
547
624
|
// then if we start checking, result will be same
|
|
@@ -560,7 +637,9 @@ await Acl.model(user).containsPermission('edit') // true
|
|
|
560
637
|
|
|
561
638
|
### Forbidding permissions
|
|
562
639
|
|
|
563
|
-
Let's imagine a situation
|
|
640
|
+
Let's imagine a situation where `manager` role has `create,update,read,delete` permissions.
|
|
641
|
+
|
|
642
|
+
All your users have `manager` role but there are small amount of users you want to forbid `delete` action.
|
|
564
643
|
Good news!, we can do that
|
|
565
644
|
|
|
566
645
|
```typescript
|
|
@@ -568,18 +647,18 @@ Good news!, we can do that
|
|
|
568
647
|
await Acl.role(manager).giveAll(['create','update','read','delete'])
|
|
569
648
|
|
|
570
649
|
// assigning to the users
|
|
571
|
-
await Acl.model(user1).
|
|
650
|
+
await Acl.model(user1).assign(manager)
|
|
572
651
|
|
|
573
|
-
await Acl.model(user3).
|
|
652
|
+
await Acl.model(user3).assign(manager)
|
|
574
653
|
await Acl.model(user3).forbid('delete')
|
|
575
654
|
|
|
576
655
|
await Acl.model(user1).hasRole(manager) // true
|
|
577
|
-
await Acl.model(user1).
|
|
656
|
+
await Acl.model(user1).can('delete') // true
|
|
578
657
|
|
|
579
658
|
await Acl.model(user3).hasRole(manager) // true
|
|
580
|
-
await Acl.model(user3).
|
|
659
|
+
await Acl.model(user3).can('delete') // false
|
|
581
660
|
|
|
582
|
-
await Acl.model(user3).
|
|
661
|
+
await Acl.model(user3).contains('delete') // true
|
|
583
662
|
|
|
584
663
|
```
|
|
585
664
|
|
|
@@ -589,7 +668,7 @@ You can also forbid single action on a resource
|
|
|
589
668
|
|
|
590
669
|
```typescript
|
|
591
670
|
|
|
592
|
-
const post = Post.find(
|
|
671
|
+
const post = Post.find(id1)
|
|
593
672
|
|
|
594
673
|
await Acl.model(user3).forbid('delete', post)
|
|
595
674
|
|
|
@@ -606,17 +685,15 @@ await Acl.model(user3).forbid('delete')
|
|
|
606
685
|
|
|
607
686
|
await Acl.model(user3).forbidden('delete') // true
|
|
608
687
|
|
|
609
|
-
|
|
610
|
-
const post1 = Post.find(id1ToFind)
|
|
611
|
-
|
|
688
|
+
const post1 = Post.find(id1)
|
|
612
689
|
|
|
613
690
|
await Acl.model(user).allow('edit', Post) // allow for all posts
|
|
614
691
|
await Acl.model(user).forbid('edit', post1) // except post1
|
|
615
692
|
|
|
616
693
|
await Acl.model(user).forbidden('edit', post1) // true
|
|
617
694
|
|
|
618
|
-
const post7 = Post.find(
|
|
619
|
-
await Acl.model(user).forbidden('edit', post7) // false becouse 'edit' action forbidden only for post1 instance
|
|
695
|
+
const post7 = Post.find(id7)
|
|
696
|
+
await Acl.model(user).forbidden('edit', post7) // false becouse 'edit' action forbidden only for the post1 instance
|
|
620
697
|
```
|
|
621
698
|
|
|
622
699
|
### Unforbidding the permissions
|
|
@@ -627,12 +704,12 @@ await Acl.model(user3).assignRole(manager)
|
|
|
627
704
|
await Acl.model(user3).forbid('delete')
|
|
628
705
|
|
|
629
706
|
await Acl.model(user3).forbidden('delete') // true
|
|
630
|
-
await Acl.model(user3).
|
|
631
|
-
await Acl.model(user3).
|
|
707
|
+
await Acl.model(user3).can('delete') // false
|
|
708
|
+
await Acl.model(user3).can('delete') // true
|
|
632
709
|
|
|
633
710
|
await Acl.model(user3).unforbid('delete')
|
|
634
711
|
await Acl.model(user3).forbidden('delete') // false
|
|
635
|
-
await Acl.model(user3).
|
|
712
|
+
await Acl.model(user3).can('delete') // true
|
|
636
713
|
|
|
637
714
|
```
|
|
638
715
|
Same behaviour applies with roles
|
|
@@ -643,7 +720,7 @@ await Acl.role(role).forbid('delete')
|
|
|
643
720
|
|
|
644
721
|
await Acl.role(role).forbidden('delete') // true
|
|
645
722
|
await Acl.role(role).hasPermission('delete') // false
|
|
646
|
-
await Acl.role(role).
|
|
723
|
+
await Acl.role(role).contains('delete') // true
|
|
647
724
|
|
|
648
725
|
```
|
|
649
726
|
|
|
@@ -652,7 +729,7 @@ await Acl.role(role).containsPermission('delete') // true
|
|
|
652
729
|
> Important! Action performed globally will affect on a resource models
|
|
653
730
|
|
|
654
731
|
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
|
|
732
|
+
Look at this way, if there is no `entity` model then actions will be performed **globally**, otherwise **on resource**
|
|
656
733
|
|
|
657
734
|
```
|
|
658
735
|
|
|
@@ -673,13 +750,11 @@ Look at this way, if there is no `entity` model then action will be performed **
|
|
|
673
750
|
|
|
674
751
|
```
|
|
675
752
|
|
|
676
|
-
|
|
677
|
-
|
|
678
753
|
```typescript
|
|
679
754
|
import {Acl} from "@holoyan/adonisjs-permissions";
|
|
680
755
|
import Post from "#models/post";
|
|
681
756
|
|
|
682
|
-
|
|
757
|
+
// first assigning permissions
|
|
683
758
|
// Global level
|
|
684
759
|
await Acl.model(admin).allow('create');
|
|
685
760
|
await Acl.model(admin).allow('edit');
|
|
@@ -692,19 +767,19 @@ await Acl.model(manager).allow('create', Post)
|
|
|
692
767
|
const myPost = await Post.find(id)
|
|
693
768
|
await Acl.model(client).allow('view', myPost)
|
|
694
769
|
|
|
695
|
-
// checking
|
|
770
|
+
// start checking
|
|
696
771
|
// admin
|
|
697
772
|
await Acl.model(admin).hasPermission('create') // true
|
|
698
773
|
await Acl.model(admin).hasPermission('create', Post) // true
|
|
699
774
|
await Acl.model(admin).hasPermission('create', myPost) // true
|
|
700
775
|
|
|
701
|
-
// manager
|
|
776
|
+
// manager - assigned class level
|
|
702
777
|
await Acl.model(manager).hasPermission('create') // false
|
|
703
778
|
await Acl.model(manager).hasPermission('create', Post) // true
|
|
704
779
|
await Acl.model(manager).hasPermission('create', myPost) // true
|
|
705
780
|
await Acl.model(manager).hasPermission('create', myOtherPost) // true
|
|
706
781
|
|
|
707
|
-
//
|
|
782
|
+
// assigned model level
|
|
708
783
|
await Acl.model(client).hasPermission('create') // false
|
|
709
784
|
await Acl.model(client).hasPermission('create', Post) // false
|
|
710
785
|
await Acl.model(client).hasPermission('create', myPost) // true
|
|
@@ -720,8 +795,7 @@ Same is true when using `forbidden` action
|
|
|
720
795
|
// class level
|
|
721
796
|
await Acl.model(manager).allow('edit', Post) // allow to edit all posts
|
|
722
797
|
|
|
723
|
-
await Acl.model(manager).forbid('edit', myPost) // forbid editing on a
|
|
724
|
-
|
|
798
|
+
await Acl.model(manager).forbid('edit', myPost) // forbid editing ONLY on a myPost
|
|
725
799
|
|
|
726
800
|
await Acl.model(client).hasPermission('edit', Post) // true
|
|
727
801
|
await Acl.model(client).hasPermission('edit', myPost) // false
|
|
@@ -731,7 +805,9 @@ await Acl.model(client).hasPermission('edit', myOtherPost) // true
|
|
|
731
805
|
|
|
732
806
|
### containsPermission v hasPermission
|
|
733
807
|
|
|
734
|
-
As you've already seen there are difference between `containsPermission` and `hasPermission
|
|
808
|
+
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*.
|
|
809
|
+
|
|
810
|
+
> `contains()` method is alias for `containsPermission()`
|
|
735
811
|
|
|
736
812
|
Lets in example see this difference
|
|
737
813
|
|
|
@@ -751,6 +827,127 @@ await Acl.model(user).containsPermission('read') // true
|
|
|
751
827
|
|
|
752
828
|
```
|
|
753
829
|
|
|
830
|
+
## Scopes or Multi-tenancy
|
|
831
|
+
|
|
832
|
+
Acl fully supports multi-tenant apps, allowing you to seamlessly integrate roles and permissions for all tenants within the same app.
|
|
833
|
+
|
|
834
|
+
```typescript
|
|
835
|
+
console.log(user.project_id > 0) // true - lets say user.project_id is not equal to zero
|
|
836
|
+
|
|
837
|
+
await Acl.model(user).on(user.project_id).allow('edit')
|
|
838
|
+
await Acl.model(user).on(user.project_id).allow('delete')
|
|
839
|
+
|
|
840
|
+
// checking
|
|
841
|
+
await Acl.model(user).on(user.project_id).hasPermission('edit') // true
|
|
842
|
+
await Acl.model(user).on(user.project_id).hasPermission('delete') // true
|
|
843
|
+
|
|
844
|
+
// checking without scope
|
|
845
|
+
await Acl.model(user).hasPermission('edit') // false - by default scope is equal to 0
|
|
846
|
+
|
|
847
|
+
|
|
848
|
+
```
|
|
849
|
+
|
|
850
|
+
### The Scope middleware
|
|
851
|
+
|
|
852
|
+
`Acl` has built-in middleware to make scope checking easier.
|
|
853
|
+
|
|
854
|
+
|
|
855
|
+
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:
|
|
856
|
+
|
|
857
|
+
```typescript
|
|
858
|
+
|
|
859
|
+
// acl_middleware
|
|
860
|
+
export default class AclScopeMiddleware {
|
|
861
|
+
async handle(ctx: HttpContext, next: NextFn) {
|
|
862
|
+
const scope = new Scope()
|
|
863
|
+
scope.set(auth.user.account_id)
|
|
864
|
+
ctx.acl = new AclManager().scope(scope)
|
|
865
|
+
/**
|
|
866
|
+
* Call next method in the pipeline and return its output
|
|
867
|
+
*/
|
|
868
|
+
const output = await next()
|
|
869
|
+
return output
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
// then on controller you can do
|
|
874
|
+
|
|
875
|
+
// post_controller.ts
|
|
876
|
+
|
|
877
|
+
export default class PostController {
|
|
878
|
+
async show({acl}: HttpContext){
|
|
879
|
+
// will check inside auth.user.account_id scope
|
|
880
|
+
await acl.model().hasPermission('view')
|
|
881
|
+
// this both will be equal
|
|
882
|
+
// await acl.model().on(auth.user.account_id).hasPermission('view')
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
```
|
|
887
|
+
> 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
|
|
888
|
+
|
|
889
|
+
Let's see in example
|
|
890
|
+
|
|
891
|
+
|
|
892
|
+
```typescript
|
|
893
|
+
|
|
894
|
+
// acl_middleware
|
|
895
|
+
export default class AclScopeMiddleware {
|
|
896
|
+
async handle(ctx: HttpContext, next: NextFn) {
|
|
897
|
+
const scope = new Scope()
|
|
898
|
+
scope.set(5) // set scope value to 5 for current request
|
|
899
|
+
ctx.acl = new AclManager().scope(scope)
|
|
900
|
+
/**
|
|
901
|
+
* Call next method in the pipeline and return its output
|
|
902
|
+
*/
|
|
903
|
+
const output = await next()
|
|
904
|
+
return output
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
// post_controller.ts
|
|
909
|
+
// global object
|
|
910
|
+
import {Acl} from "@holoyan/adonisjs-permissions";
|
|
911
|
+
|
|
912
|
+
export default class PostController {
|
|
913
|
+
async show({acl}: HttpContext){
|
|
914
|
+
const scope = acl.getScope()
|
|
915
|
+
console.log(scope.get()) // 5
|
|
916
|
+
// global object
|
|
917
|
+
console.log(Acl.getScope()) // 0
|
|
918
|
+
|
|
919
|
+
acl.scope(7)// update and set new scope
|
|
920
|
+
// for current request it will be 7
|
|
921
|
+
console.log(acl.getScope()) // 7
|
|
922
|
+
|
|
923
|
+
Acl.scope(7) // Throws error
|
|
924
|
+
// you can't update scope on global object, only runtime
|
|
925
|
+
await Acl.model(user).on(8).permissions() // get permissions for user on scope 8
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
```
|
|
930
|
+
|
|
931
|
+
|
|
932
|
+
### Default scope (Tenant)
|
|
933
|
+
|
|
934
|
+
> Default Scope value is equal to 0 (zero)
|
|
935
|
+
|
|
936
|
+
|
|
937
|
+
## Cheat sheet
|
|
938
|
+
|
|
939
|
+
Coming soon
|
|
940
|
+
|
|
941
|
+
|
|
942
|
+
## TODO
|
|
943
|
+
|
|
944
|
+
- [X] Scopes (Multitenancy)
|
|
945
|
+
- [ ] UUID support
|
|
946
|
+
- [ ] Events
|
|
947
|
+
- [ ] More test coverage
|
|
948
|
+
- [ ] Caching
|
|
949
|
+
- [ ] Integration with AdonisJs Bouncer
|
|
950
|
+
|
|
754
951
|
## Test
|
|
755
952
|
|
|
756
953
|
npm run test
|