diff --git a/docs/articles/security-multiple-roles.md b/docs/articles/security-multiple-roles.md new file mode 100644 index 0000000000..19dbb9737b --- /dev/null +++ b/docs/articles/security-multiple-roles.md @@ -0,0 +1,39 @@ +# Assign multiple roles to a user + +In a case when you have a slightly more complicated requirements, when the user have more than one role, you can configure the `NbRoleProvider` service to return an array of user roles. +
+ +## Roles configuration + +In a simplest form you just need to modify the `getRole` method to return an array of roles: +```ts +// ... + +import { of as observableOf } from 'rxjs/observable/of'; +import { NbSecurityModule, NbRoleProvider } from '@nebular/security'; + + +@NgModule({ + imports: [ + // ... + + NbSecurityModule.forRoot({ + // ... + }), + + ], + providers: [ + // ... + { + provide: NbRoleProvider, + useValue: { + getRole: () => { + // here we simply return a list of roles for current user + return observableOf(['guest', 'user', 'editor']); + }, + }, + }, + ], +``` + +Thus, once a user is accessing some secured resource, `isGranted` method returns `true` if at least one of the roles can access the resource. diff --git a/src/framework/security/services/access-checker.service.ts b/src/framework/security/services/access-checker.service.ts index e8bd422e89..b160dd792e 100644 --- a/src/framework/security/services/access-checker.service.ts +++ b/src/framework/security/services/access-checker.service.ts @@ -30,8 +30,9 @@ export class NbAccessChecker { isGranted(permission: string, resource: string): Observable { return this.roleProvider.getRole() .pipe( - map((role: string) => { - return this.acl.can(role, permission, resource); + map((role: string|string[]) => Array.isArray(role) ? role : [role]), + map((roles: string[]) => { + return roles.some(role => this.acl.can(role, permission, resource)); }), ); } diff --git a/src/framework/security/services/access-checker.spec.ts b/src/framework/security/services/access-checker.spec.ts index 7dd2cd6813..5c61eb8db0 100644 --- a/src/framework/security/services/access-checker.spec.ts +++ b/src/framework/security/services/access-checker.spec.ts @@ -12,7 +12,7 @@ import { NbAccessChecker } from './access-checker.service'; let accessChecker: NbAccessChecker; -function setupAcl(can) { +function setupAcl(can, roles: string|string[]) { beforeEach(() => { // Configure testbed to prepare services TestBed.configureTestingModule({ @@ -21,7 +21,7 @@ function setupAcl(can) { provide: NbRoleProvider, useValue: { getRole: () => { - return observableOf('admin'); + return observableOf(roles); }, }, }, @@ -29,7 +29,7 @@ function setupAcl(can) { provide: NbAclService, useValue: { can: (role, permission, resource) => { - return can; // this is a simple mocked ACL implementation + return can[role]; // this is a simple mocked ACL implementation }, }, }, @@ -50,7 +50,7 @@ function setupAcl(can) { describe('authorization checker', () => { describe('acl returns true', () => { - setupAcl(true); + setupAcl({ admin: true }, 'admin'); it(`checks against provided role`, (done) => { accessChecker.isGranted('delete', 'users').subscribe((result: boolean) => { @@ -61,7 +61,7 @@ describe('authorization checker', () => { }); describe('acl returns false', () => { - setupAcl(false); + setupAcl({ admin: false }, 'admin'); it(`checks against provided role`, (done) => { accessChecker.isGranted('delete', 'users').subscribe((result: boolean) => { @@ -70,4 +70,37 @@ describe('authorization checker', () => { }) }); }); + + describe('acl returns false (both roles return false)', () => { + setupAcl({ admin: false, user: false }, ['user', 'admin']); + + it(`checks against provided roles`, (done) => { + accessChecker.isGranted('delete', 'users').subscribe((result: boolean) => { + expect(result).toBe(false); + done(); + }) + }); + }); + + describe('acl returns true (both roles return true)', () => { + setupAcl({ admin: true, user: true }, ['user', 'admin']); + + it(`checks against provided roles`, (done) => { + accessChecker.isGranted('delete', 'users').subscribe((result: boolean) => { + expect(result).toBe(true); + done(); + }) + }); + }); + + describe('acl returns true (one of the roles return true)', () => { + setupAcl({ admin: true, user: false }, ['user', 'admin']); + + it(`checks against provided roles`, (done) => { + accessChecker.isGranted('delete', 'users').subscribe((result: boolean) => { + expect(result).toBe(true); + done(); + }) + }); + }); }); diff --git a/src/framework/security/services/role.provider.ts b/src/framework/security/services/role.provider.ts index dbfca7cfcb..5648119213 100644 --- a/src/framework/security/services/role.provider.ts +++ b/src/framework/security/services/role.provider.ts @@ -6,5 +6,10 @@ import { Observable } from 'rxjs'; export abstract class NbRoleProvider { - abstract getRole(): Observable; + + /** + * Returns current user role + * @returns {Observable} + */ + abstract getRole(): Observable; }