2010-03-14

System permissions are important. Defining what people can and can't do with your application is a significant part of security.

There are two perspectives I tend to care about with permissioning. The first is user-orientated and the second is data-orientated. In this article I will talk about designing a user-orientated permission system.

For the purposes of this post a permission will be considered a boolean value that represents whether a person can or can't perform an operation. In other systems you might go as far as to consider the extent to which they have permission which ends up working like a priority based permissiong system. This is only really useful in my opinion if you've an operation two people can perform at once and you wish to provide a fine grained hints to the system as to who should have the operation performed first. It's something to consider but usually unnecessary and out of the scope of this article.

Users and user-level permissioning

In a user-orientated permission system what we care about is what users can do with our system as a whole. Example permissions under this context include CanCreateUsers, CanViewUsers, CanUpdateUsers, CanDeleteUsers, CanInitiateReconciliation, etc.

Concepts that exist in this permission system include:

User

PermissionSet

The relationship between these concepts can be described as a one-to-one as every user should have a set of permissions (even if all those permissions are set to false!). At this point we can make a decision that because our permissioning system is simply we can combine these concepts into one system entity. This entity is described in the following ER diagram.



User groups and group-level permissioning

This will work well for simply systems with a few users. What happens when our systems become more complex and busy. Let's say that our system has so many users that we want to permission groups of them at a time. This might be by their role in the system or simply that an identified collection of users will always need the same permissions. We can create the concept of a UserGroup which defines global attributes available to all Users such as permissions.

Now we have three concepts of note.

User

PermissionSet

UserGroup

Since none of these can be combined we end up with a structure that is described in the following ER diagram.



The relationship between UserGroup and User is a one-to-many. One UserGroup can have many Users. In this case we can note that the relationship between UserGroup and PermissionSet is a one-to-one relationship. We could combine these into a single UserGroup entity as we did in the previous structure. However if we keep them separate we can allow fine grain control over the user permissions.

User groups and user-level permissioning

Shown below we have created a subset of permissions specific to the user. This allows the UserGroup entity to take on a more simple role-based function within our application. Used to set blanket default permissions over a large collection of users that can then be fine tuned for a single user without having to create a new very-simplier but specialised UserGroup just for that single user.



You can see here how a user has a one-to-one relationship with a permission set and that a user group has a one-to-one relationship with a permission set. The relationship between user group and user remains one-to-many. In cases where we don't care about specific user permissions we decide to allow User.userPermissionSetId to be null as indicated by the empty white circle on the relationship between user and permission set. In the application there must be some logic to determine which permission set to use. If userPermissionSetId is null then follow through the user group to find the permission set.

Imagine this scenario. A user is set into a user group with no specific user-level permissions. The administrator then gives the user specific user-level permissions. At a later date the administrator alters the permissions of the user group. Since the user has specific user-level permissions these changes don't affect the user. This could probably be a problem in that it defeats the point of user groups. To keep the relevance of user groups after a user has been given user-level permissions we allow that our permissions in the permission set may be null. What this means to our application logic is that if a user-level permission is null then look for that permission at group-level regardless of whether user-level permissions were assigned as a whole. If we get up to group-level and find a null we just assume false for safetys sake.

Example program logic that resolves these permissions:

If you want to see the above functional structure in action you can use the following code for demonstration.

Now we have a pretty flexible user-orientated permission system. It can be made even more reflexible if you allow users to have multiple groups or roles but when we get to this level of flexibility we introduce the concept of conflict resolution.

Imagine if you will a user with two roles, user manager and reconciliation administrator. The former role has the first four permissions set to true and canInitiateReconciliation remains null. The latter role has canInitiateReconciliation permission set to true but the rest to null. This is fine as is but what were to happen if somebody decided that the user manager role can definitely not initiate reconciliation and sets that permission to false? We end up with two permission sets for our user with a contradicting setting for canInitiateReconciliation. One possibly resolution is to simply say that if any role permission is true, then the user has permission to perform that operation. This decision lies in the application designer and out of the scope of this artile.

Generalising to hierarchical user entities

It would be nicer if we could create a hierarchy of permission levels rather than the two level system we had before where only user-level and group-level existed. To do this we generalise the concept of what a user and group is to what I'm going to call a user entity. Simply put the entity model can have a single parent and permissions set at any level of the entities parentage. You could then organise your permission scheme into a tree where the terminal leafs represent the users themselves. An example instance view could be a small business where the following diagram describes the users and groupings that exist.

Here we see that Randall and Doug are both part of the London development group, Andrew is part of the London sales group, Joey is part of the New York marketting group and Nigel is part of the New York branch of Example Enterprises. Each of these users inherit the permissions of their parent groups all the way to the very root group.

This allows us greater flexibility about how we organise and inherit permissions. Allowing us to specialise permissions near the bottom and generalising permissions near the top. For instance in the organisation hierarchy the overall policy of the company might deny everything except read permissions. The London development division might grant full permissions to faciliate their business role. The London Sales division might need to update records only so with inheritance they can read and update. Perhaps the marketting department are responsible for initiating a reconciliation but have no other interest in the system. They can enable reconciliation and disable read access.

In order to archieve this we can modify our system to the following:

We generalise both users and groups of users to simply be some sort of user entity that has permissions associated with it. We define users to be entities where no other entity inherits from them. We define groups to be entities that are referred to by at least one other entity. In reality in order to support empty groups we'd need some sort of flag to define the entity as a user or group.

The end result of this generalisation allows us to create this implementation of a hierarchical user-orientated permissioning system that supports permission inheritance.

Example usage of this code is given below.

That's all folks! If I get motiviated I'll discuss data-orientated permissioning and the use of access control lists.

Show more