Why FOLIO needs a complex permission model?
FOLIO is a highly modular platform with the server-side modules heavily decoupled from the UI and with strong isolation from other modules. It is thus required to control authorization across modules and between modules and the UI in a highly granular and transparent way. FOLIO's middleware, Okapi, must be able to provide enforcement guarantees, so permissions must be exposed to Okapi during module registration. FOLIO clients/UI want to make decisions and choices based existence of particular permissions before the backend perform permission enforcement. This makes permissions an integral part of the module interface, which means things like backward compatibility, API evolution and versioning must be taken into account.
On the other hand, FOLIO needs to be very flexible with respect to how permissions are grouped (which is based e.g., on how the tenant's organization has been modeled into FOLIO) and associated with users. In certain cases, the permission groups or "roles" must be modeled to closely match a hierarchy from an external authorization system. This is likely a converse problem/requirement to the one above.
How are FOLIO permissions structured?
FOLIO permission model is based on a recursive tree-like structure with the following elements (frequently referred to simply as permissions):
-
atomic permissions are very granular, matching API endpoints granularity or going below it, and defined in the form of simple flags like "
users.item.get
" or "users.item.delete
". Atomic permissions can be one of two kinds: required, which are enforced by Okapi and the authentication module in tandem and desired which are enforced by the module themselves. Okapi can provide guaranteed enforcement of required permissions, so those are preferable. In some cases, however -- e.g., to protect resources or operations with more granularity than the API granularity – desired permissions are the only possible solution. In practice, it is possible to avoid desired permissions with careful API design. Atomic permissions are defined in the Module Descriptor for a particular module. They are leaf nodes of the permission tree. -
immutable permission sets (IPS): are groupings of atomic permissions that include a user-friendly name. They may also recursively include other IPSes when a more complex hierarchy is needed. The main role for IPSes is to model logical permissions that make sense to the FOLIO administrator. E.g "Can edit users" is an IPS that includes "
users.item.get
", "users.item.delete
", "users.collection.get
", "login.user.item.read
", and many other atomic permissions for accessing endpoints and sub-resource related to managing users. IPS are defined in the Module Descriptor for a particular module and can include permissions defined in the same module and permissions defined in the dependent modules. IPSes are loaded to the FOLIO permission module automatically when a module is enabled for a particular tenant, they can be further grouped or directly assigned to users but cannot be modified. In the FOLIO UI IPSes are shown simply as "permissions". IPS are immediate parents of leave nodes in the permission tree. - mutable permission sets (MPS): are groups of permissions (IPSes) created by the admin for a particular tenant. Just like IPSes they can be recursive and include other MPSes. They are specific to a particular FOLIO tenant and can be used to model tenant's organizational structure. Some examples are "Catalogers", "System librarians", "Student helpers", etc. They are the root of the permission tree.
Besides the ability to protect access to particular resources, atomic permissions are used to conditionally render elements of the FOLIO UI. E.g in the Users app the "Create new user" button is shown only if the current user has the "users.item.post
" permission. Since UI modules also come with Module Descriptors, they can define their own specific UI-only permissions but this is done very seldom when no matching backend permission can be found or defined.
IPSes can be defined on any level in the module structure (system module, business-logic module or UI modules) but depending what operations they refer to a particular level can be a better place than the other. Currently, most IPSes are defined within the BL modules.
How are FOLIO permissions enforced?
When a user logs in his/her credentials are validated and if the validation is successful an access token is generated. With the access token in hand, the client/UI requests current user permissions: IPSes or MPSes associated with his/her login credentials are expanded all the way down to atomic permissions and returned to the client/UI. This allows the client/UI to make decisions about whether or not to present access to certain operations. Every request from the client/UI made on behalf of the current user is rerouted by Okapi to the "authtoken
" module. The "authtoken
" module checks validity of the user's access token, extracts his credentials from the token and performs a lookup for associated permissions, also expanded down to the atomic permissions. With permissions retrieved "authtoken
" makes an immediate decision if the user request should be allowed to be delivered down to the handler module or rejected, based on the required permissions. If the request is allowed, desired permissions are encoded into a header and attached to the request. The handler module can then perform additional authorization based on the desired permissions.
This is a companion discussion topic for the original entry at https://wiki.folio.org/display/PLATFORM/FOLIO+permission+model