Nebular: ACL with sidebar menu

Created on 26 Feb 2018  路  9Comments  路  Source: akveo/nebular

Feature Request

I'm submitting a ... (check one with "x")

  • [ ] bug report
  • [X ] feature request

Issue description

I could not find any documentation to integrate the menu with ACL.
How to accomplish hiding certain elements in the menu list based on permissions

enhancement faq docs needs docs

Most helpful comment

It could be iterate items of Menu

@Component({
  selector: 'ngx-pages',
  template: `
    <ngx-sample-layout>
      <nb-menu [items]="menu"></nb-menu>
      <router-outlet></router-outlet>
    </ngx-sample-layout>
  `,
})
export class PagesComponent implements OnInit {

  menu = MENU_ITEMS;

  constructor(private accessChecker: NbAccessChecker) {
  }

  ngOnInit() {
    this.authMenuItems();
  }

  authMenuItems() {
    this.menu.forEach(item => {
      this.authMenuItem(item);
    });
  }

  authMenuItem(menuItem: NbMenuItem) {
    if (menuItem.data && menuItem.data['permission'] && menuItem.data['resource']) {
      this.accessChecker.isGranted(menuItem.data['permission'], menuItem.data['resource']).subscribe(granted => {
        menuItem.hidden = !granted;
      });
    } else {
      menuItem.hidden = true;
    }
    if (!menuItem.hidden && menuItem.children != null) {
      menuItem.children.forEach(item => {
        if (item.data && item.data['permission'] && item.data['resource']) {
          this.accessChecker.isGranted(item.data['permission'], item.data['resource']).subscribe(granted => {
            item.hidden = !granted;
          });
        } else {
          // if child item do not config any `data.permission` and `data.resource` just inherit parent item's config
          item.hidden = menuItem.hidden;
        }
      });
    }
  }
}

and MENU_ITEMS carry data object with permission and resource.

export const MENU_ITEMS: NbMenuItem[] = [
...
{
    title: 'UI Features',
    icon: 'nb-keypad',
    link: '/pages/ui-features',
    data: {
      permission: 'view',
      resource: 'ui-features'
    },
}
...
];

Reference: https://akveo.github.io/nebular/docs/security/acl-configuration--usage#usage

All 9 comments

Hi @amanmamgain, you need to filter menu items before passing them into the menu component.

What I ended up doing this like nnixaa mentiones:

export class NbMenuItemWithPermissions extends NbMenuItem {
  permissionRequired?: string;
  featureFlag?: string
  children?: NbMenuItemWithPermissions[];
}

export const MENU_ITEMS: NbMenuItemWithPermissions[] = [
  {
    title: 'Dashboard',
    icon: 'nb-home',
    link: '/pages/dashboard',
    home: true,
  },
  {
    title: 'My Pages',
    icon: 'nb-compose',
    link: '/pages/my-pages',
    permissionRequired: 'VIEW_PAGES',
    featureFlag: 'MyFlag'
  }
]

Then in my Pages component, I just filter those out based on an API call to get the user's info, and what feature flags I have enabled, ex.

constructor(quorumService: QuorumService, menuService: NbMenuService) {
    // Filter out menu items based on Togglz
    service.user().subscribe((userData) => {
        this.userData = userData;
        let filteredItems = MENU_ITEMS.filter((item) => {
          if(item.children) {
            item.children = item.children.filter((childItem) => this.itemAllowed(childItem));
          }
          return this.itemAllowed(item);
        });
        menuService.addItems(filteredItems);
    });
  }

 itemAllowed(menuItem) {
    if(menuItem.featureFlag && !this.flags[menuItem.featureFlag]) {
      return false;
    }
    if(menuItem.permissionRequired && !(this.userData.userType === 'ADMIN' || this.userData.permissions.includes(menuItem.permissionRequired))){
      return false;
    }
    return true;
  }

Is there a way to filter the Menu based on NbSecurity NbAccessChecker.isGranted() Method?

It could be iterate items of Menu

@Component({
  selector: 'ngx-pages',
  template: `
    <ngx-sample-layout>
      <nb-menu [items]="menu"></nb-menu>
      <router-outlet></router-outlet>
    </ngx-sample-layout>
  `,
})
export class PagesComponent implements OnInit {

  menu = MENU_ITEMS;

  constructor(private accessChecker: NbAccessChecker) {
  }

  ngOnInit() {
    this.authMenuItems();
  }

  authMenuItems() {
    this.menu.forEach(item => {
      this.authMenuItem(item);
    });
  }

  authMenuItem(menuItem: NbMenuItem) {
    if (menuItem.data && menuItem.data['permission'] && menuItem.data['resource']) {
      this.accessChecker.isGranted(menuItem.data['permission'], menuItem.data['resource']).subscribe(granted => {
        menuItem.hidden = !granted;
      });
    } else {
      menuItem.hidden = true;
    }
    if (!menuItem.hidden && menuItem.children != null) {
      menuItem.children.forEach(item => {
        if (item.data && item.data['permission'] && item.data['resource']) {
          this.accessChecker.isGranted(item.data['permission'], item.data['resource']).subscribe(granted => {
            item.hidden = !granted;
          });
        } else {
          // if child item do not config any `data.permission` and `data.resource` just inherit parent item's config
          item.hidden = menuItem.hidden;
        }
      });
    }
  }
}

and MENU_ITEMS carry data object with permission and resource.

export const MENU_ITEMS: NbMenuItem[] = [
...
{
    title: 'UI Features',
    icon: 'nb-keypad',
    link: '/pages/ui-features',
    data: {
      permission: 'view',
      resource: 'ui-features'
    },
}
...
];

Reference: https://akveo.github.io/nebular/docs/security/acl-configuration--usage#usage

I have the same problem.
I followed @pickonefish solution, but it doesn't work.
My configuration:

app.module:

NbSecurityModule.forRoot({
  accessControl: {
      user: {
      view: ['customer-care', 'sent']
    },
    admin: {
      view: ['*']
    }
  }
})

pages.menu:

import { NbMenuItem } from '@nebular/theme';

export const MENU_ITEMS: NbMenuItem[] = [
  {
    title: 'Inbox',
    icon: 'download-outline',
    link: '/pages/customer-care',
    home: true,
    data: {
      permission: 'view',
      resource: 'customer-care'
    }
  },
  {
    title: 'Sent',
    icon: 'upload-outline',
    link: '/pages/customer-care/sent',
    data: {
      permission: 'view',
      resource: 'sent'
    }
  },
  {
    title: 'Mail Template',
    icon: 'file-text-outline',
    link: '/pages/mail-templates',
    data: {
      permission: 'view',
      resource: 'mail-templates'
    }
  }]

pages.component is the same that @pickonefish

Where is the wrong part?

Thanks
Massimo

@viandanteoscuro Try to delete array in view if you set all permissions for admin ['*'] => '*'

NbSecurityModule.forRoot({ accessControl: { user: { view: ['customer-care', 'sent'] }, admin: { view: '*' } } })

@pickonefish thank you for saving my time :)

Hi all.
I'm trying to implement @brgaulin solution that for my purpose is suitable.
In my pages.module.ts providers section is like this:
providers: [NbAuthService, { provide: NbRoleProvider, useClass: RoleProvider }, PagesMenu]
My pages.component.ts has constructor defined like this:
constructor( private pagesMenu: PagesMenu, private tokenService: NbTokenService, protected initUserService: InitUserService, private roleService:RoleProvider )
but when page is loaded i get :
"core.js:6228 ERROR Error: Uncaught (in promise): NullInjectorError: R3InjectorError(PagesModule)[RoleProvider -> RoleProvider -> RoleProvider -> RoleProvider]:
NullInjectorError: No provider for RoleProvider!"

for completeness my role.provider.ts is like that:
`import { NbAuthService, NbAuthOAuth2JWTToken } from '@nebular/auth';
import { NbRoleProvider } from '@nebular/security';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { Injectable } from '@angular/core';

@Injectable()
export class RoleProvider extends NbRoleProvider {

constructor(private authService: NbAuthService) {
super();
}

getLowerCaseRoles(roles: any): string | string[] {
if (Array.isArray(roles)) {
roles = roles.map(element => {
return element.toLowerCase();
});
} else {
roles = roles.toLowerCase();
}
return roles;
}

getRole(): Observable return this.authService.getToken()
.pipe(
map((token: NbAuthOAuth2JWTToken) => {
const payload = token.getAccessTokenPayload();
return !!(token.isValid() && payload && payload['role']) ? this.getLowerCaseRoles(payload['role']) : 'guest';
}),
);
}
}
`

Any idea on what I'm missing?
Thanks for help

Not sure. But the code I posted was for nebular 2 or 3. If you are on latest there might be some changes.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

johnsnow20087349 picture johnsnow20087349  路  3Comments

maihannijat picture maihannijat  路  3Comments

batousik picture batousik  路  4Comments

mmezian picture mmezian  路  3Comments

bnbs picture bnbs  路  4Comments