Conversation
| import { UpdatePermissionRuleArgs } from '../../resolvers/mutations/UpdatePermissionRuleMutation'; | ||
| import { PermissionRulesArgs } from '../../resolvers/queries/PermissionsQuery'; | ||
|
|
||
| export const actions = ['update', 'read', 'delete'] as const; |
There was a problem hiding this comment.
Instead of just defining actions and subjects as string, I've provided a list of possible actions and subject to try to help with type detection. This is intended as a small, illustrative example
| return rules; | ||
| } | ||
|
|
||
| substituteConditionsValues(conditions: any, object?: any | undefined) { |
There was a problem hiding this comment.
A condition may look like the following in the database:
{ "user.userId": { "$in": "fap.fapChairUserIds" } }
When we convert this into an object the object values will still be string literals instead of the values they are supposed to be (e.g. "fap.fapChairUserIds" instead of [12345]) so we have to do the substitution ourselves, looking for context object key references in the conditions object and replacing them with the actual values. In the provided example, the resulting conditions object would be:
{ 12345: { "$in": [12345] } }
| return ['()', []]; | ||
| } | ||
|
|
||
| const [sql, replacements] = interpret(condition, { |
There was a problem hiding this comment.
Converts from ast to SQL.
| ('read', 'fap_proposal_assignment', NULL, false), | ||
| ('read', 'fap_proposal_assignment', '{"user_id": "userId"}', true), |
There was a problem hiding this comment.
This seems nice if I've interpreted it correctly: all of the permission filtering is handled at db level, so that the main check can be written without a condition for simplicity and performance.
In Casbin, if the filtering takes place and provides the exact outcome, it's still necessary to collect the context and evaluate each proposal again.
| ('read', 'proposal', '{"isVisitorOfProposal": true}', false), | ||
| ('read', 'proposal', '{"isDataAccessUserOfProposal": true}', false), | ||
| ('read', 'proposal', '{"isMemberOfProposal": true}', false), |
There was a problem hiding this comment.
By having conditions for a subject split across separate rows, is it somehow possible to know which particular condition has returned false for clearer error messaging?
There was a problem hiding this comment.
separate rules collected into a single ability object are treated with the OR operator. So here the logic would be: "is the user a visitor of the proposal OR a data access user of the proposal OR a member of the proposal"
We could probably use something like this to see which conditions aren't met.
| header: string; | ||
| children: React.ReactNode; | ||
| }) => { | ||
| //caching would be very useful here |
There was a problem hiding this comment.
Is this referring to caching the list of dashboard read permissions for each role so that there's no delay in loading the menu items on switching roles?
| proposal.primaryKey | ||
| ); | ||
| permissionContext = { | ||
| isMemberOfFapProposal: this.isMemberOfFapProposal(agent, proposal.primaryKey) |
There was a problem hiding this comment.
In case there's any weirdness with this permission - looks like there's an await forgotten. Happens to me often and usually has me scratching my head for far too long

Description
This is a proof of concept for implementing casl in UOP.
It covers the following use cases:
Motivation and Context
STFC is investigating how to offer more granular and customisable permissions for facilities. casl is an Attribute Based Access Control (ABAC) system. It tests whether the user is allowed to do (action) what they're asking to do to a resource (subject), depending on a specified set of criteria (conditions). These tuples of action, subject and condition are to be stored in the database to allow facilities to define them themselves, instead of relying on how their behaviour defined defined in the code.
Design
Database
Two tables have been added:
permissionsandrole_has_permission.permissionsdefines what action a role can do on a subject and under what conditions. For example, a User Officer can view any proposal, a FAP chair can update a FAP if they chair that particular FAP.role_has_permissionties these permissions to user roles.Backend
My initial thought was that an authorisation class is added for every entity where we perform authorisation checks whose role it is to construct the context object, and test the user's permissions (fetched from the permissions datasource) against said context object, returning a
trueorfalseif the user is allowed to do what they're asking.Frontend
A user interface has been added for interacting with permissions. This is very basic and not very user friendly. Ideally we'd abstract away the syntax for conditions and make it as hard as possible for users to define incorrect permissions. Please note that the table has some visual bugs.
Calls.-.Google.Chrome.2026-03-11.09-08-54.mp4
The menu items are shown based on the permissions the user currently has. This use case could simplify the code (currently a react component is defined for each user role) and would be a good candidate for permissions caching as currently a user's permissions are fetched on every page load.
Conclusion
Pros
Cons
proposer_idon a proposal is the same as the user's ID, see)Please let me know your thoughts and feedback. If there's a use case you'd like to be explored, please post them here.