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;
}