Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to deal with hierarchical roles/permissions using Apache Shiro?

I'm trying to use Apache Shiro framework to secure my web application (UI is based on Vaadin 6). Looked through all the examples on Shiro's site and also googled for hours, but I can't find a clean way to deal with the following requirements.

Assuming application is a kind of project management tool, where users are creating activities, which belongs to particular departments in company hierarchy. Each user may work in several departments and has different security roles in each department. Example:

Department A       - User is 'Manager' here
Department B
  Department C     - User is 'Admin' here
    Department D

User is 'Manager' in Department A User is 'Admin' in Department C User should also inherit 'Admin' role for Department D (which is ancestor of Department C).

So, basic permission check (assuming I want to view activity belonging to some department) would be to:

  1. Check if activity user is trying to view belongs to department user has a role in;
  2. Check that user has required permission basing on his role in this department.

I'm current stuck in understanding of how to implement not only just "system wide role", but "role in this particular department" concept.

How can I transform above example to permission string like "activity:view:123"? And how will I check the permission in my business logic?

One more doubt is implementation with Shiro, I'd like to use some out-of-the-box solution will minimal efforts of providing my own implementations. However, it seems that Shiro's built-in implementations are designed for simple cases only. Is there any example of complex authorization implementation to start with (which can cover above case)?

like image 686
Sergey Makarov Avatar asked Jul 01 '13 13:07

Sergey Makarov


1 Answers

Just want to describe my solution to this problem, which may be useful to someone. I feel this may not be optimal, so still open to any suggestions on cleaner implementation.

Assuming I need to secure the following actions:

  • activity:edit
  • activity:view

And I also need to make sure that permissions are not system wide, but depends on my role in particular department. What I did is explicitly added 'department-dependant' permission for user in my Realm. Example (see hierarchy in the post):

  • DEP_A:activity:view
  • DEP_C:activity:view
  • DEP_C:activity:edit

Each time I want to check if action against some activity is allowed or not, I'm creating a list of permissions to check. Example:

Activity A belongs to Department D, I want to 'view' it. Permissions to check will be:

  • DEP_D:activity:view
  • DEP_C:activity:view
  • DEP_B:activity:view

If I'm Admin in department C, I would have 'DEP_C:activity:view' permission and hence the check will be passed. Thats allows to implement rights inheritance in company structure hierarchy.

Here is the code snipplet from my service class responsible for permission checks:

   @Override
   public void checkIfOperationPermitted( SecurityOperation operation, 
      Object object )
   {
      final Subject currentUser = SecurityUtils.getSubject();

      if(currentUser.isPermitted(
         SecurityOperation.SYSTEM_ADMIN.getPermissionString()) ||
         currentUser.hasRole( "admin" ))
      {
         // no need to check anything else,
         // admin is system wide role.
         return;
      }

      if(object instanceof Activity)
      {
         // Activity permissions fully depends on organization and
         // product hierarchies. PermissionResolver is just a class
         // which generates list of permission strings based on 
         // department activity is belonging to.
         Activity a = (Activity) object;
         List<String> permissionsToCheck = 
            permissionResolver.resolveHierarchicalPermissions(operation, a);
         boolean permitted = false;
         for(String permission: permissionsToCheck)
         {
            if(currentUser.isPermitted( permission ))
            {
               permitted = true;
               break;
            }
         }
         if(!permitted)
         {
            throw new UnauthorizedException( "Access denied" );
         }
      }
      else
      {
         // Check for system wide permissions
         currentUser.checkPermission( operation.getPermissionString() );
      }
   }

Another way I was thinking about is add all such permissions for user in my Realm, but declined this since company hierarchy can in general contain N tiers - which greatly increases duplication in permissions list for particular user (memory usage).

like image 90
Sergey Makarov Avatar answered Oct 07 '22 01:10

Sergey Makarov