Replies: 3 comments 2 replies
-
For reference, if it helps, here is a summary of my ReBAC implementation. The core of zima is an ACL with the ability to "inherit" access. zima API consists of only 5 endpoints:
Zima is not designed as a primary source of data. It's limited API does not allow complex operations like listing or search. This is on purpose - otherwise zima turns into an ugly NoSQL graph store. Store your records in a primary database such that zima ACLs can be built asynchronously. For this reason all zima endpoints are idempotent. ExampleBob and alice are employed at "the paper company" and are using an online file storage application. The file structure looks like this:
Flat ACLs, RBACAlice wants to make herself the owner of "salaries.pdf": addSubset("file_owners:/salaries.pdf", "alice") Checking if alice is an owner looks like this: check("file_owners:/salaries.pdf", "alice")
=> true If you are in need of a role+permission model, this has to be done at the application layer: function canViewFile(file, user) {
return check(`file_owners:${file}`, user)
|| check(`file_viewers:${file}`, user)
} Given a single person is not granted by lots of different roles and Nested ACLsSuppose we want users who own a folder to also own its children. We can do this by asynchronously listening to when a file is created and adding the set of folder owners as a subset of the file owners. function onCreateFile(folderId, fileId, ownerId) {
z.addSubset(
`file_owners:${fileId}`,
`folder_owners:${folderId}`
)
z.label(`file_owners:${fileId}`, ownerId)
} This means that now any Groups (also nested ACLs)Zima would work very similarly if we had "groups" of users with access to a given resource. In such a case you might create the following sql tables:
You'd then listen to when:
Resulting in:
Meaning the following is true: check("file_owners:F", "U")
=> true |
Beta Was this translation helpful? Give feedback.
-
From my understanding, the problem is that subsets are not computed, which is the solution that you have proposed in
The optimal solution to strive for is a data structure that can support subsets. This way when querying e.g., is jane a viewer of a object, it would be able to return yes,
i.e. a function is needed s.t.
|
Beta Was this translation helpful? Give feedback.
-
ReBAC implementation is tracked in #73. |
Beta Was this translation helpful? Give feedback.
-
👉 Should we support full ReBAC?
1. Problem
Currently, very limited ReBAC capabilities can be achieved by creating authorisation grants with relation attributes. But this doesn't allow inherited permissions.
Let's consider the following scenario.
user:jane
is a member of groupgroup:editors
.group:editors
can edit documentdocument:notes.txt
.user:jane
is a member ofgroup:editors
, they can editdocument:notes.txt
.With current capabilities we will need to explicitly grant each member of
group:editors
access todocument:notes.txt
(e.g.document:notes.txt#editor@user:jane
1).However, this approach can create inconsistencies. e.g. if access rights from
group:editors
todocument:notes.txt
changes, we will need to keep track of which authorisation grants have been created by thedocument:notes.txt#editor@group:editors#member
relationship and update them accordingly. Similarly, whengroup:editors
members change we will need to update authorisation grants accordingly.Since Sentium doesn't have knowledge of
document:notes.txt#editor@group:editors#member
relationship orgroup:editor#member@user:*
relationships, a system outside of Sentium will need to keep track of these relationships and update authorisation grants accordingly.2. Proposed solution(s)
2.1 Solution A - Relation Tuples
In order to store relations between entities, we can use relation tuples. A relation tuple can be expressed using
'['⟨strand⟩']'⟨entity⟩'/'⟨relation⟩'/'⟨entity⟩
text notation, where;⟨strand⟩
is a relation that connects two tuples together which can be empty⟨entity⟩
is a principal or an outside entity with a type and an id⟨relation⟩
is a string describing the relation between the two entitiesTo be able to connect two tuples and consider them as a single relation chain,
For example,
[]user:alice/member/team:writers
can be chained with[member]team:writers/edit/doc:notes.txt
but not with[owner]team:writers/edit/doc:notes.txt
(since first tuple's relation,member
, doesn't match second tuple's strand,owner
).Table 2.1/a - Example relation tuples
[]user:alice/member/team:writers
user:alice
is a member ofteam:writers
[member]team:writers/edit/doc:notes.txt
team:writers
can editdoc:notes.txt
[owner]folder:F/owner/doc:notes.txt
folder:F
are also owners ofdoc:notes.txt
[]folder:F/parent/doc:notes.txt
folder:F
has a parent relation todoc:notes.txt
Database schema for storing relation tuples
2.1.1 Computed relations
Given the following tuples,
[]user:jane/member/group:editors
- Useruser:jane
is a member of groupgroup:editors
[member]group:editors/member/group:viewers
-group:editors
is a member of groupgroup:viewers
and all members ofgroup:editors
are also members ofgroup:viewers
[member]group:viewers/view/doc:notes.txt
- Members ofgroup:viewers
can view documentdoc:notes.txt
We can compute that
user:jane
has view relation todoc:notes.txt
([]user:jane/view/doc:notes.txt
) using the following logic.r_entity_id = 'doc:notes.txt'
, which results intuple:3
r_entity_id = 'group:viewers' and relation = 'member'
, which results intuple:2
r_entity_id = 'group:editors' and relation = 'member'
, which results intuple:1
tuple:1
,tuple:2
andtuple:3
we can infer[]user:jane/view/doc:notes.txt
2.1.2 Optimisations
Computing indirect relations can be expensive when there are deeply nested relations (which essentially results in graph traversal algorithms).
To counter for such scenarios and to keep lookup costs low, we can compute inferred relations between the user and deeply nested groups during inserts and store them as computed tuples.
For example if
tuple:1
andtuple:2
existed when insertingtuple:3
, we can compute an additional relation as[]user:jane/member/group:viewer
and store that as a computed tuple entry.Note the
_rid
value intuple:4
. Computed tuples are stored with a reference to the entry that produced the computed tuple so they can be removed if the relation chain changes.Having the computed tuple simplifies the logic to check if
user:jane
has a view relation todoc:notes.txt
as following.[]user:jane/view/doc:notes.txt
) - no resultsdoc:notes.txt
- results intuple:3
user:jane
has a relation to the group that matches the strand - results intuple:4
Steps (2) and (3) can be further optimised by retrieving the relevant groups with view relation to
doc:notes.txt
and matching those with relavant groupsuser:jane
is related to.2.1.3 Constraints
Computing indirect relations and storing them as additional computed tuples during inserts can be expensive, specially when there are deeply nested hierarchies or large number of entities linked.
To limit the number of rows inserted as computed tuples we can first compute additional relations and assign a cost. This cost can then be used to reject modifications if it's above a set threshold.
Footnotes
Represented using Zanzibar's text notation for relationship tuples (i.e.
⟨object⟩‘#’⟨relation⟩‘@’⟨user⟩
) ↩Beta Was this translation helpful? Give feedback.
All reactions