How to cleanly implement permission based feature access

complexityfeaturespermissions

I have been tasked with writing an on/off control for features in our product based on who is signed in, in principle with one on/off flag for each feature. Put simply, this is a permission based feature access that can be activated at the user level.

The problem is that I can see this becoming a complexity nightmare. For example, person A is apart of group G but has special per missions 1, 2, 3 but A is completely different than person B who is apart of Group H who has special permissions 2, 3 and 4 etc etc.

Managing the permissions is not going to be the hard part : that is just setting a value in the database. The hard part is going to be allowing person A to be able to use feature F if they have permission P. I do not want this to become hundreds of if statements scattered across the code base whose combinations are exponential.

One solution that I have in mind is to separate the feature from the permission by having a control layer right above the feature that everything else uses, similar to writing a library and using a scripting language for the control structures. This would separate the if/else statements from the actual feature, but the problem is that they still exist, and their combinations are still there.

Do you know of any elegant solutions to this problem or ones similar to it?

Best Answer

1.Organize the permissions and their control

Indeed, if you define individual feature access permissions, you will have, as in feature toggles, an if-clause in each feature to check if the user has the right permission.

If you abstract the feature from the permission, for example by using a level of indirection, such as a feature group (e.g. one feature belongs to a configurable group) or an autorisation profile (e.g. several user belongs to one profile, which has several feature permissions), you will still need an if clause for each feature.

In order to make this easy to handle, you should in anyway encapsulate the permission check in a permission object. This would allow you to implement the link between the feature and the user with any of the above mentioned strategy, without having to change the code:

user.permission("feature_X").mandatory_check(); // if needed, throws a permission exception 
                                                // to be catched at right level. 

or

if (user.permission("feature_Y").optional_check() ) {
   ...                                         // execute optional feature 
}                                              // do nothing if access is not granted

2.Control features access upfront

If the features are independent functions, you could control access upfront.

This means that the UI shall ensure that user can only invoke fonctions and commands for which the user has permission. This allows to isolate permission controls in your architecture. The various feature will then not have to implement additional checks.

Unfortunately, this will not work if the feature is optional, i.e. the user can invoke a function, and the function is executed differently if the feature is there or not.

Manage features as a strategy

This approach uses runtime strategies. The strategy either invokes a feature or nothing. The Context would in this case be a kind of user profile object that is initialized at login. For each feature there would be a strategy (this could remain flexible, by using an associative container such as a feature map). The strategies would be initialized depending on the user permissions.

Later on, when the different functions of the software are executed, they would use the relevant strategy (e.g. either an object that will execute the right feature, or an empty strategy object that does nothing).

This approach seems rather elegant. However, instead of adding an if in the code of each feature, it would require to implement each feature as a self-contained strategy, which might be extremely challenging.

So what ?

Personally, even if the second seems more elegant, I'd go for option 1: this is much more flexible. Proper encapsulation of the permission can reduce the permission control in the feature to a single statement.