Skip to content

Commit

Permalink
feat(security): add ability to assign multiple roles to a user (#549)
Browse files Browse the repository at this point in the history
Closes #222
  • Loading branch information
nnixaa authored Jul 5, 2018
1 parent ac41765 commit c3402a6
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 8 deletions.
39 changes: 39 additions & 0 deletions docs/articles/security-multiple-roles.md
Original file line number Diff line number Diff line change
@@ -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.
<hr>

## 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.
5 changes: 3 additions & 2 deletions src/framework/security/services/access-checker.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,9 @@ export class NbAccessChecker {
isGranted(permission: string, resource: string): Observable<boolean> {
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));
}),
);
}
Expand Down
43 changes: 38 additions & 5 deletions src/framework/security/services/access-checker.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand All @@ -21,15 +21,15 @@ function setupAcl(can) {
provide: NbRoleProvider,
useValue: {
getRole: () => {
return observableOf('admin');
return observableOf(roles);
},
},
},
{
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
},
},
},
Expand All @@ -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) => {
Expand All @@ -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) => {
Expand All @@ -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();
})
});
});
});
7 changes: 6 additions & 1 deletion src/framework/security/services/role.provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,10 @@
import { Observable } from 'rxjs';

export abstract class NbRoleProvider {
abstract getRole(): Observable<string>;

/**
* Returns current user role
* @returns {Observable<string | string[]>}
*/
abstract getRole(): Observable<string|string[]>;
}

0 comments on commit c3402a6

Please sign in to comment.