Role Based Access Control

Hi,

I need advice about Role Based Access Control in web apps. I looked on the web and understand that hard coding the conditions for controlling users access to functionalities is not the way to go.

What is not clear though is how to proceed…

Any hint?

1 Like

How you set up permissions in a web app is often somewhat unique to the application. The general principle for role based access is that you define roles or categories of functionality and assign individual functions, actions, or screens to those roles. Then users are assigned to the roles or groups which are assigned to the roles. It’s basically just an abstraction layer to make it less tedious to manage.

If you have a very detailed app and highly specific permission needs it’s going to get complicated no matter what.

Not sure I helped much, but I tried. :slight_smile:

It’s also important to remember that in a Xojo web app, making things not visible is not the same as them not being there. I’ve seen projects where the program just makes admin level controls invisible and the user is confused as to why some users still have access. The more knowledgeable & creative users can always access the source code and make those hidden things visible again. Just make sure that you either don’t send them (like an admin-only toolbar) or that you also limit the functionality of those controls by checking the role before executing them.

As far as role-based permissions go, think of it this way… typically you have certain things that you’d want an admin to have access to, other things for managers, others for standard users and maybe some for guests.

Now you may be thinking these should inherit, but I typically don’t do that. There are times when you have two roles which really don’t inherit from one another, there may be cases when roles don’t overlap at all.

Anyway, you then have groups of permissions (lets say adding users). That permission is added as something an Admin can do. In your code, where the user edit functions are, you check if the current user is allowed to edit users, but at the user level, all you are doing is indicating that particular users are admins. They automatically get access to what admins are allowed to do… and if you add a new admin-level feature later, you just add it to the role and automatically all admins have access to the new feature.

If you end up needing a new combination… lets say salespeople. You create a new role, assign it the features the role has access to and then assign the role to a user. Voila! Typically all of this is stored in a database, so it can be done on the fly without requiring a new release.

What Greg described works well. I used a similar approach in one of my apps.

In a newer apps , I use a user-specific permission approach. It is a bit more complicated and a lot more maintenance than role-based. My application is database-driven. In the database, I have a set of tables for user data, which contains the usual user info, plus some information about responsibilities (role and jurisdiction levels). In another table, the key is page (container) and function code. This is used at the highest level to determine whether a user can see a page at all. Then, in a few other tables, I have the page-function code plus various other specific key fields. The permission is assigned as a parameter. It can be nil or 0 - access denied to 99 - full admin. Access is defined at various levels (page-function code or more specific) specifically for each page or container, function code (create, edit, display, list etc.), transaction-specific parameters and of course, user parameters. Some users simply cannot access a page, some can perform limited actions, some are granted full user access, some are admin level.

I created a function that is called by the menu options before opening a page or creating an instance of the container, that determines the access level based on the user and the parameters relevant to the transaction - this is where the various security tables come into play. My database queries for the page are designed to get only allowed data as the case may be. I use MSSQL, so much of that is in T-SQL in stored procedures. When a user is not allowed at all in a section of the app, the page is of course not shown and a dialog informs the user. Also, the application code is in some places dependent on user access parameters. Each function verifies the access level before executing. Some controls will be hidden and disabled depending on access level. For example, the “Save” button will not be available to a “display-only” user.

You can use a very simple approach or as is the case in my current application, be very detailed. In my current application, I have national level users with admin jurisdiction over national events, provincial users with jurisdiction over provincial events, regional admins also. Some users will be allowed “user access” at some or all levels. Some users are only allowed to see “self” data, or events where their own user code is specified. This is why I need to be very specific.

In short, security should be part of the initial design of an application, not an afterthought. Unless security is very basic and binary - you access it or you don’t. Then it is only a matter of checking before the page is called.

Thanks! That’s what I was looking for… I still need some clarification though, so I understand well. Let see if I get this right :

In my case, I have to make the distinction between “Operators” and “Suppliers” as the app layout has to be different depending of the type of user. For each of them, I have a distinct set of roles and permissions that looks like this :

ROLES table

  • SUPPLIER_ADMIN, SUPPLIER_USER, SUPPLIER_GUEST
  • OPERATOR_ADMIN, OPERATOR_USER, OPERATOR_GUEST

PRIVILEGES table : ADMIN privileges > USER > GUEST.

  • ADMIN : VIEW_ADVANCED, VIEW_BASIC, EDIT_BASIC, EDIT_ADVANCED
  • USER : VIEW_BASIC, EDIT_BASIC
  • GUEST : EDIT_BASIC

For example, in my application I have a container called “UserProfileDetailsWC1” (which is an instance of a custom Class called “UserProfileDetailsWC) that shows user profile informations”. I use a function named “Update_User_Profile(refValue As Integer)” too save the modifications to the profile.

The way a user accesses it is different depending on his ROLE :

  • ADMIN sees a user list in which he can select a user and see his profile
  • USER AND GUEST are directly sent to the see their own profile.

The update functionality behaves differently for each ROLE as well :

  • ADMIN can edit and update any field
  • USER can edit and update some fields
  • GUEST can’t edit neither update his profile

When you say : [quote=339486:@Louis Desjardins] In another table, the key is page (container) and function code. This is used at the highest level to determine whether a user can see a page at all. Then, in a few other tables, I have the page-function code plus various other specific key fields. The permission is assigned as a parameter. It can be nil or 0 - access denied to 99 - full admin. Access is defined at various levels (page-function code or more specific) specifically for each page or container, function code (create, edit, display, list etc.), transaction-specific parameters and of course, user parameters.[/quote]

What would i find is in this other_table and the other_tables, what is the key? Would it be the container name and the function name?

How I see it for the other_table

  • In the other_table , I would have a key made of two columns ( col1 = “UserProfileDetailsWC1”, col2 = “Update_User_Profile”)
  • plus an additional column for each combination “role-privilege” (SUPPLIER_ADMIN_VIEW_ADVANCED, SUPPLIER_ADMIN_VIEW_BASIC, SUPPLIER_ADMIN_EDIT_ADVANCED, SUPPLIER_ADMIN_EDIT_BASIC, OPERATOR_ADMIN_VIEW_ADVANCED… )
  • a value for each field that indicates if the user can or can’t see the container for every col1-col2 key and role-privilege combination
  • a Function (that could use Introspection) that retrieves the Container.Name and the Function.Name and checks in the other_table what right is attributed to the user’s role-privilege combination.
  • based on the value returned by the Function, the code will show a MsgBox, or open the container.

How I see it for the other_tables CASE OTHER TABLE 1 = go to list Or go directly to profile

  • In the other_table , I would have a key made of two columns ( col1 = “UserProfileDetailsWC1”, col2 = “Update_User_Profile”)
  • plus an additional column for each combination “role-privilege” (SUPPLIER_ADMIN_VIEW_ADVANCED, SUPPLIER_ADMIN_VIEW_BASIC, SUPPLIER_ADMIN_EDIT_ADVANCED, SUPPLIER_ADMIN_EDIT_BASIC, OPERATOR_ADMIN_VIEW_ADVANCED… )
  • a value for each field that indicates if the user opens the list prior to access the profile view or if he is directly sent to the profile view container and that for every col1-col2 key and role-privilege combination
  • a Function (that could use Introspection) that retrieves the Container.Name and the Function.Name and checks in the other_tables 1 what right is attributed to the user’s role-privilege combination.
  • based on the value returned by the Function, the code will redirect the user to the list or the profile view.

How I see it for the other_tables CASE OTHER TABLE 2 = “updatable fields”, update Or NOT update profile

  • In the other_table , I would have a key made of two columns ( col1 = “UserProfileDetailsWC1”, col2 = “Update_User_Profile”)
  • plus an additional column for each combination “role-privilege” (SUPPLIER_ADMIN_VIEW_ADVANCED, SUPPLIER_ADMIN_VIEW_BASIC, SUPPLIER_ADMIN_EDIT_ADVANCED, SUPPLIER_ADMIN_EDIT_BASIC, OPERATOR_ADMIN_VIEW_ADVANCED… )
  • a value for each field that indicates fields set, can update or not and that for every col1-col2 key and role-privilege combination
  • a Function (that could use Introspection) that retrieves the Container.Name and the Function.Name and checks in the other_tables 2 what right is attributed to the user’s role-privilege combination.
  • based on the value returned by the Function, the code will enable all some or no fields and the save button.

Is that the kind of pattern you were describing?

At first glance, It appears to be a lot of work… :slight_smile: If I’m right, is there a way to simplify that?

Thanks again!

often it helps to conceive it as a grid of ROLES & PRIVILEGES
and then implement the tables required for that

                                              PRIVLEGES
                   VIEW_ADVANCED, VIEW_BASIC, EDIT_BASIC, EDIT_ADVANCED  EDIT_BASIC
ROLES             
SUPPLIER_ADMIN          x                                    x 
SUPPLIER_USER                                                                        
SUPPLIER_GUEST                                                                       
OPERATOR_ADMIN                                                                       
OPERATOR_USER                        x                                       x        
OPERATOR_GUEST                                                               x      

then a table that joins the ROLES to PRIVILEGES makes it fairly easy to define what roles have what privileges
And finding out if a role HAS a privilege is similarly a straightforward join

In short, yes.

I elected instead to manage field access with methods called by the shown event of my containers, where the rules are hard coded. It is less flexible than your design, but also less work overall. Your’s is better if you plan to make it a class and reuse the concept in many applications.

I failed to mention that all my security tables include the userid in the key. Your role-based approach replaces the user with the user role, which is just fine.

I think that you now have the basis of a solid security management system. As I said, it is a lot of work and a lot of initial maintenance.

I typically don’t add permissions to the tables themselves. I create a function in a module called Security which can check if the current user has a particular permission. Something like:

If Security.hasPermission("write_users") then

Oh and those permissions, I use constants or enums to avoid typos.

Thanks for spending time making it clear for me! I’ll see if it worths building an independent module to avoid hardcoding security rules or if I’ll allow myself some hardcoding to accelerate development.

It is often said that hardcoding security is “bad programming” but I can see form your examples and tips that it can be done with various levels of it without being a “heretic” :).

It appears to me that equilibrium is not always easy to achieve.

Thanks again for your precious time and advices!