Monday, December 30, 2013

Permissions Modelling in Flex

Permissions is something that most of my projects have needed. I've been iterating on a design for many projects, and I've finally come up with something (leveraging some work that my Twin teammates have produced)  that is scalable, testable, and easy for my team to implement. I've written this up in the context of Flex / Actionscript, but there is no reason why it couldn't apply to other languages.

Just to clarify my requirements, here are the user stories that I'm working with. 
  • Should be testable.
    As the developer, I want to be able to declare which permissions are present during testing and development independent of my actual permissions on the system. (In other words - decoupled)  
  • Should be scalable
    As a client, I want to be able to add/remove permissions at any time without major work/rework in the application
  • Easy to understand
    As a new or jr. developer, I want to be able to understand and implement permissions in a best practices way

What I came up with, was a hierarchical string based system. It looks like the following:
public class PermissionTaskConstants
    {
        public static const DEVELOPER:String = "developer"
        public static const ADMIN:String = "admin";
        public static const ADMIN_USER:String = "admin.user";
 public static const ADMIN_CURRENCY:String = "admin.currency"
        public static const LOGIN:String = "login"
        public static const PROJECT:String = "project"
        public static const PROJECT_EDIT:String = "project.edit"
        public static const STAFFING:String = "staffing";
        public static const STAFFING_EDIT:String = "staffing.edit";
        public static const STAFFING_ALLOCATE:String = "staffing.edit.allocate"

    }

In this way, if you were assigned a permission of "staffing.edit.allocate", you would inherit all of the rights of "staffing" and "staffing.edit". This satisfy's the scalable requirement. It is trivial to add or remove permissions, as it would only mean adding / removing strings from the permission path.

These permissions would live in a "permission registry", which would contain an array of strings. This registry could be populated from a service call to a database, or it could be filled at run time. Filling it at run-time would satisfy the "should be testable" requirement. The code is availabe at the bottom, but I've set it up so that it dispatches events when permissions are changed.  A developer can listen for those events and update the UI. Although the typical workflow is that after login, we retrieve the permissions from the database, and the registry is populated before any of the UI is displayed.

IMPORTANT: Decouple the registry from the view!

So this is super important. In order to make this testable and decoupled, never, ever, ever reference the registry in the view. Said differently MXML files should have NO KNOWLEDGE of the PermissionsRegistry.

Here is the recommended workflow. You have a MXML file that represents the view. Just for clarification, this view is in charge of the layout and interactions with the user. There should be no business logic in the view.

Then you have an presentation model (PM) that is in charge of all business logic. This is likely a pure actionscript file. Business logic means conditionals, domain level events, and in this case permissions. The PM receives a copy of the permission registry through dependency injection. The PM then exposes the specific permissions that the view needs through methods.

The view creates its own Bindable public properties that represent the various permission states, and then reads them from the PM. (Please forgive the incorrect capitalization in the MXML objects, the css "brush" for this formatting strips that out)



    <![CDATA[
        import mx.controls.Alert;
        import mx.events.FlexEvent;

        public var pm:PermissionsPM;
        [Bindable] public var canReadProjects:Boolean;
        [Bindable] public var canEditProjects:Boolean;
        [Bindable] public var canReadStaffing:Boolean;
        [Bindable] public var canEditStaffing:Boolean;

        private function creationCompleteHandler(event:FlexEvent):void
        {
            pm = new PermissionsPM();
            canReadProjects = pm.canSeeProjects();
            canEditProjects = pm.canEditProjects();
            canReadStaffing = pm.canSeeStaffing();
            canEditStaffing = pm.canEditStaffing();

        }
        ]]>
    
    
    

package
{
 import com.squaredi.permissions.PermissionTaskConstants;
 import com.squaredi.permissions.PermissionsRegistry;

 import mx.collections.ArrayCollection;

 public class PermissionsPM
 {
  [Inject]  public var permissionsRegistry: PermissionsRegistry;

  public function PermissionsPM()
  {
   permissionsRegistry = new PermissionsRegistry();
   var permissions:Array = [PermissionTaskConstants.PROJECT_EDIT, PermissionTaskConstants.STAFFING]
   permissionsRegistry.permissionsList = new ArrayCollection(permissions);
  }

  public function canSeeProjects():Boolean
  {
   return permissionsRegistry.hasPermission(PermissionTaskConstants.PROJECT);
  }

  public function canEditProjects():Boolean
  {
   return permissionsRegistry.hasPermission(PermissionTaskConstants.PROJECT_EDIT);
  }

  public function canSeeStaffing():Boolean
  {
   return permissionsRegistry.hasPermission(PermissionTaskConstants.STAFFING);
  }

  public function canEditStaffing():Boolean
  {
   return permissionsRegistry.hasPermission(PermissionTaskConstants.STAFFING_EDIT);
  }
 }
}


A couple of other special features about the code. As a developer I can manually add and remove permissions from the list. When I do this, it sets a "developerUpdated" flag, which will ignore setting the whole array. Here is the use case for that. I login to the system but the service call to get permissions is delayed (for some reason). Then I want to verify how the system looks for other users, and add specific permissions, then the service call comes back and could potentially overwrite the values that I've modified. So there is code in place to prevent that.
public function set permissionSource(v:Array):void
        {
          if (developerUpdated) { return} //Don't allow it to be overwritten if the developer has already set individual permissions for testing
          permissionsList.source = v;
          permObj = createPermObj(_permissionsList)
          notifyUpdate();
        }

        public function addPermission(permission:String):void
        {
            var idx:int = permissionsList.getItemIndex(permission);
            if (idx <0)
            {
                permissionsList.addItem(permission);
                permObj = createPermObj(permissionsList)
                developerUpdated = true;
                notifyUpdate();
            }
        }

The other feature that is in the code, is an interface for extracting the data. This could be used in the case of a value object (VO) that returns from the database that has a list of permissions for a given user. This VO is an object, and doesn't have the necessary string representation for permissions. The permission registry accepts an PermissionExtractor helper, that can convert the VO into permissions strings.
package com.squaredi.permissions
{
    public interface IPermissionExtrator
    {
        function getPermissionString(perm:Object):String; // returns a.b.c
    }
}


All of the code and an example can be found at: https://github.com/dshefman/FlexPermissions

2 comments:

  1. Consider letting your flex app expose capabilities/features then mapping user groups to capabilities/features in the db instead of inside the application.

    ReplyDelete
  2. @CrashCodes. I'm interested in this perspective. Could you elaborate more on what you have described?

    ReplyDelete