Core concepts
Throughout the section we’re gonna use “organizations” and “users” as typical examples of models within your business domain. Hence we assume that in all the examples below, that there exists a model Organization and a model User.
Permission strings and scopes
The core concept of this library revoles around the notion of a scoped permission string. They (may) look like this:
read
write
update
create
user:1
user:1:read
organization:1
organization:1:create
=organization:1
-organization:1
organization:1:user:2
We say that each string delimited by a colon (:) defines a permission scope. We say that each subpart defined by a new colon of a permission defines a parent scope of the permission.
Take the permission organization:1:create
. Here organization
, organization:1
and organization:1:create
are the various scopes of the permission string. The two first are the parent scopes of the permission string.
While in many typical permission schemes permissions are matched exactly to ensure a grantee has the required access, in this library, permission strings are matched in a “cascading” manner on all parent scopes. We say that scopes are used in one of two contexts: As a required permission or as a granting permission.
Say for instance that you as a user have been granted a permission organization:1
. Then suppose you are trying to access a resource which requires the permission organization:1:setting:user
. Since you have the organization:1
permission, you will be given access to this, as you have access to a higher level scope in the required full scoped permission string.
In other words, the permission organization:1
as a granting permission grants access to organization:1:setting:user
as a required permission.
Similarily, a user with any of the following scoped permission strings would be given access to the resource:
organization
organization:1
organization:1:settings
Let’s take some basic scope examples, and read very roughly what they would mean:
organization
: Has/requires access to all organizationsorganization:1
: Has/requires access to organization with id 1.user:1
: Has/requires access to the user with id 1.organization:1:user:1
: Has/requires access to the user with id 1 within the organization with id 1.
Note that the exact interpretation of the permission strings are up to you within your own business domain.
Scopes and verbs
There are two parts of a scoped permission string, the base and the verb. The verb is optional. Some examples of typical permissions with verbs:
read
user:create
user:1:update
organization:1:delete
These permissions can roughly be read as
Can read everything.
Can create users.
Can update user 1 and any sub-resource of the user.
Can delete organization 1 and delete and any sub-resource of the organization.
Some examples of typical permissions without verbs:
user
organization:1
organization:1:user
These permissions can roughly be read as
Has full access to all users.
Has full access to the organization with id 1 and any sub-resource.
Has full access to all users within the organization with id 1 and any sub-resource of the users.
Verbs are special when used in required permissions. Let’s illustrate with two quick examples.
Example 1
Say you have the permission user:1:read
. Now suppose you are trying to access a resource which requires user:1:settings:read
, where read is a verb.
Notably, the permission you have is not a parent scope of the required permission.
However, the permission internal mechanism will still match these two permissions, as the verb will be attached recursively to each parent scope of your granting permission when trying to match the permissions.
Other permissions which would have granted access in this scenario are the following:
user:1:settings:read
user:1:settings
user:1
user:read
user
read
From this example, we can see that creating a simple read-only user for all resources can be done by simply attaching the permission read
.
Again, this depends on how you structure your business logic, but shows some of the power of the library.
Example 2
Say you have the permission user:setting
and suppose you are trying to access a resource which requires user:1:setting
, where setting is not a verb.
Here, you will not get access, as again the permission you have is not a parent scope of the required permission.
From the above two examples, it should be clear that creating a superuser can be done with something akin to adding the following permissions: read
, update
, create
, delete
.
Exact permissions
One problem with the “cascading” property of the scoped permission system is that it may become hard to limit permissions “higher up” in the hierarchy. Suppose for instance that you want to grant a user access to read information about an organization without granting full read access to everything in the organization.
With the permission organization:1:read
we are likely to have the problem that the user automatically gets read-access to all resources within the organization. That is, if you model permission strings in a manner alike organization:1:user
, organization:5:vehicles
.
We can solve this problem by using an exact operator before the permission =organization:1:read
. This very roughly translates to “Has read access to organization 1, but no data within organization 1”.
Exactly what this means semantically is up to you, but in terms of permission matching, this basically means that the required scope and the granting scope must match exactly (not including the “=”).
To give an example, =organization:1
will not match organization:1:user
.
This can be used on any permission string:
=organization:1:read
=organization:1
=user
Exclusion permissions
Another problem we might have, is revoking specific permissions. Say for instance that you want a user by default to have access to all organizations, so the user has the permission organization
.
But you also want to revoke access to the organization with id 2.
We can achieve this with an exclusion permission: -organization:2
. In combination, these two permissions yield access to all organizations apart from the organization with id 2.
Exact exclusion permissions
We can combine the above two notions, e.g. -=organization:2. This will revoke access to exactly the permission organization:2.
Interestingly, this will still grant access to required permissions such as organization:2:user.
Precedence
Note that in case of conflicts, permissions take precedence in the following order (higher being prioritised).
Exact exclusion
Exact inclusion
Exclusion
Inclusion
Some examples:
-=scope1:scope2 > =scope1:scope2
=scope1:scope2 > -scope1:scope2
-scope:scope2 > scope1:scope2
Hence, if a user has the permission -=scope1:scope2
and =scope1:scope2
, the
user will not be granted access to scope1:scope2
.
Final note
Note that while a lot of the remaining part of the documentation will revolve aronud how to set up permissions using the database, the library can be used fully statelessly.
The library’s primary purpose is to provide helper methods and methodology for using the above schematics to do permission matching. Hence it is fully possible to simply generate a scoped permissions on runtime, and then match with required static or dynamic permissions.
This will be explored in a later chapter.