Managing Access Control – A Crash Course

During my career, especially when involved in development of some kind of platform, I’ve run into the need to explore and make decisions on how to manage access control in all kinds of situations. I’ve done my research, but I still haven’t found a simple and clear explanation of access control models, specifically how to manage access control in a platform.

So my purpose here is to try and give a solid yet simple enough explanation of how access control models work. This is not intended necessarily to be a comprehensive view, nor a very precise and rigorous view. My aim here is to provide a structured conceptual model and facilitate further discussions and decisions on how to model the access control in applications/frameworks. I cover here mainly the declarative part of managing access control, implementation of an access control mechanism is beyond the scope of this post.

Let’s dive right in.

The Access Control Question

At the fundamental level, all access control models try to answer a simple question: for any given user, what actions can he/she perform on what objects, at the time of the query. 

To put it slightly more formally, given a set of users U, a set of objects O and a set of actions A (associated with the objects), what is the relation P of tuples <u,a,o> (u∈U, a∈A, o∈O)?

If a tuple exists in the relation, then we say that user u is allowed to do a with object o.

Some examples:

  1. <Joe,READ, ClientsTable> means that Joe is allowed to READ the Clients table.
  2. <Alice,VIEW, Tenant MgmtScreen> means that the user named Alice is allowed to view the tenant management screen.
  3. <Alice, EXECUTE, DeleteUsers> means that the user named Alice is allowed to execute the function called ‘DeleteUsers’.
  4. <Bob, READ, Device[id=123]> means that the user named Bob can read the record with id 123 from the Device table.
  5. <*,VIEW, LoginScreen> means that any user can view the login screen (adopting ‘*’ as a notation for wildcard)

The only question1 an authorization service aims to answer is the existence of such tuples at the time of querying – whether the user in question can execute a given action on a given object.

Note that the definition of users, actions and objects is anything we choose to be. Objects can be of course data objects (tables, records, documents), with common actions such as CREATE, READ, UPDATE, DELETE. But objects are also functions to execute (stored procedures, RPCs, actions accessed over URLs, … ) and it can also be screens to view.

Terminology-wise, objects are sometimes referred to as resources and users are more generally referred to as security principals (so it’s not just individual persons). A tuple of <action, object> is referred to as a permission or privilege.

Managing Permissions

Now that we’ve established the main question that is answered, the question becomes how to manage permissions effectively and efficiently. This is more than a question of convenience. An overly complicated permission management model might lead to confusion and assignment of permissions where they should not be assigned to. It’s important to be able to reason about a given user’s permissions so we can make sure a user (usually a group of users) doesn’t get permissions they shouldn’t have.

A straightforward optimization is to assign a permission (action, object tuples) to a set of users at once. So access control systems usually let us define arbitrary groups of users to allow this convenience. Still, the definition and assignment of a large set of permissions is cumbersome.

Roles

Another common and useful way to organize permission assignment is to group permissions and assign (groups of) users to these sets of permissions.

Remember that a permission is simply a tuple of <action,object>. A role is simply a set of such tuples.

For example, an Account Manager role (AccountManagerRole) might be defined as: 

  <View, Account Configuration Screen>,
  <Execute, UpdateAccount>,
  <View, Account Dashboard>
  …

Assuming we have such a role defined in the system, we can now assign users to it.

So <Bob, AccountManagerRole> is simply a shortcut for: 

  <Bob, View, Account Configuration Screen>,
  <Bob, Execute, UpdateAccount>,
  <Bob, View, Account Dashboard>
  …

Naturally, role definitions usually correlate with business-related roles. So a user that is assigned to a role is expected to fulfill some function in a business process, which the given permissions allow him to perform.

This is also true for operational roles over management applications/resources. We define a role of “administrator” or “operator” with the necessary privileges to accomplish the role. It’s important to have even operational functionality access-controlled with the same mechanism so definitions are assigned effectively. A common example is a “user administrator” role, which is allowed to create/update/delete user records (other principles) and assign them with privileges. This role definition is not different than any other, it’s simply defined over the “Roles” and “Users” objects.

Assignment of users/principles to roles, whether mechanized or manual, is usually a human-driven process. Assignment of (groups of) users to roles happens with some utility or GUI-based tool, but essentially human driven and performed as needed, e.g. as part of some onboarding process.

A word of caution: I’ve seen in some discussions how roles get mixed up with user groups. In other words, roles are conceptualized as sets of users. While technically you can view a role as designating a set of users (the principles assigned to the role at a given point in time), this is actually a byproduct of the access control system, and not the original purpose and motivation for defining a role. A role should ideally represent a business function, with a set of privileges.

Data-Level Permissions

A common and useful extension to this permission management model is that of data-level permissions, often referred to as row-level permissions, because of its root in RDBMSs. In this extension we usually define permissions at a lower granularity of objects – individual data objects (rows/documents) defined in our application.

Note that this is not a different model then what we presented as the basic permissions model. We’re still assigning actions, usually READ/UPDATE/DELETE, to individual objects. The difference is in how the assignment happens – how we connect a user to the object in question.

Up until now, the user was assigned arbitrarily/manually to a set of permissions, probably through a role. With this extension we’re also configuring the authorization service2 to match a specific property of the user to a property of the accessed object.

For example, we might want to limit Charlie’s access only to Clients from his region. We can define the permission as:

   <READ,Clients[region=user.region]>

Where user here refers to the “current” user being matched (the principal trying to access the Clients table). This assumes of course that the user object in the system has such a property (region) defined.

This allows us to assign this permission to a group of users, but still having each user seeing only the appropriate set of data he should be allowed to access, without defining explicit roles for each such subset. It’s both easier to manage and more robust since new property values (e.g new regions) are automatically accessed only by allowed users, without changing role assignment.

The choice of properties to match can of course be anything that can be matched between the data object and the user. A common use case for such a mechanism is to separate the data between different business units or tenants, in the same data store. So a “BU”/”Tenant” property is defined for each data object (row), and users are assigned to business units/tenants, allowing this match to happen when examined.

Note that there’s nothing that prevents this definition from being part of a role definition. It is, however, often impractical/inefficient to enforce this kind of restriction at the same place where more general access control is enforced. This is simply because enforcement of data-level permissions requires intimate knowledge of the underlying data model to be queried, which usually doesn’t exist in a generic access control service. It’s also more efficient to “push” the data-level permissions to underlying query engines to reduce retrieved data volumes.

Extending the Accessible Object Set

It’s often useful/desired to extend the set of permitted data objects to a user, either individually or through a role. We do this by extending the set of matched values on the user’s side. In other words, the user will be able to access data objects (rows) that match a larger set of values.

The most straightforward way to achieve this is to explicitly specify more values in the permission definition. But this is often impractical from an operational perspective.

Another way is to rely on some other structure that exists between (sets of) users and leverage its definition to extend the set of matched property values for that user.

A common example of such a mechanism is the definition and assignment of users to business units, which often form a hierarchy derived from its business/organization hierarchy.

For example, assume ACME Corporation is structured as follows:

ACME Global

⤷ ACME US

   ⤷ ACME West Coast

   ⤷ ACME East Coast

⤷ ACME EMEA

   ⤷ ACME Middle East

   ⤷ ACME Africa

   ⤷ ACME EU

⤷ ACME APAC

And assuming Charlie is assigned to ACME US, i.e. has a property that says that his business unit is ACME US. Then we can easily extend Charlie’s set of accessible objects by matching also on descendant business units (ACME West Coast, ACME East Coast), so objects that have these business units set to them are also viewable.

It’s important to note that this is simply leveraging the assignment of users to business units and their hierarchy in order to extend the sets of accessible objects. But there’s nothing that prevents us from using any other existing relationship between users. For example, consider users that are mapped to an organization hierarchy (a Manager-Employees relationship). We can define that a user has access to all data objects where their “category” matches the user’s “expertise” value (whatever that means); plus all the ‘expertise’ values of the users managed by this manager. In this manner we allowed the manager to automatically view his employees’ data, e.g. assigned tickets. Changes in the organizational structure, or ticket categories will automatically change the permissions assigned to said users.


To summarize, we defined a simple (semi) formal model for thinking about the access control problem, and specifically how to manage it. Different systems and paradigms exist to ease the management of this kind of access control.

When considering how to manage access control, especially when creating platforms, it’s usually helpful to think of it in terms how principles are assigned to <action,object> tuples effectively. 

Hope this helps.


Notes

  1. It can actually get slightly more complicated than this if we want to have more complicated queries or more granular control, but the basic question is still the same, and extensions can be made later.
  2.  Note that this is not necessarily one specific authorization service. Enforcement of the access control policy can happen at different places. For simplicity, it’s usually better to have it one place, but this is not always practical.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.