This is a very simple approach to doing role-based access control with Neo4j. It is optimistic, in the sense that all items are assumed to be world-readable unless they have specific constraints. Item visibility can be constrained to either individual users or all users who belong to a role. Roles are also hierarchical, so can inherit privileges from other roles.
First, lets create our basic example data:
CREATE
(admins { type: 'role', name: 'admins' }),
(role1 { type: 'role', name: 'role1' }),
role1-[r1:BELONGS_TO]->admins,
(role2 { type: 'role', name: 'role2' }),
(user1 { type: 'user', name: 'user1' }),
user1-[r2:BELONGS_TO]->role1,
(user2 { type: 'user', name: 'user2' }),
user2-[r3:BELONGS_TO]->role2,
(item1 { type: 'item', name: 'item1' }),
item1-[r4:ACCESSIBLE_TO]->admins,
(item2 { type: 'item', name: 'item2' }),
item2-[r5:ACCESSIBLE_TO]->role2,
(item3 { type: 'item', name: 'item3' })
RETURN admins, role1, role2, user1, user2, item1, item2, item3
First, check what items are accessible to everyone, because they have no constraints. This should return just item3.
START items = node(*)
MATCH access = items-[r?:ACCESSIBLE_TO]->accessors
WHERE items.type! = 'item' AND access IS NULL
RETURN DISTINCT items
Now lets list all items accessible to 'user1'. The result should include 'item1' (because it is ACCESSIBLE_TO 'admins', and 'user1' belongs to 'role1', which in turn belongs to 'admins') and 'item3' which has no access constraints at all.
START items = node(*)
MATCH access = items-[r1?:ACCESSIBLE_TO]->accessors, users = user-[r2?:BELONGS_TO*]->accessors
WHERE items.type! = 'item' AND (access IS NULL OR user.name! = 'user1')
RETURN DISTINCT items
Okay, that seems to work. Likewise, if we try the same thing with 'user2' we should be 'item2' and 'item3':
START items = node(*)
MATCH access = items-[r1?:ACCESSIBLE_TO]->accessors, users = user-[r2?:BELONGS_TO*]->accessors
WHERE items.type! = 'item' AND (access IS NULL OR user.name! = 'user2')
RETURN DISTINCT items
Check if item is 'item1' is accessible to 'user1':
START item = node(*)
MATCH access = item-[r1?:ACCESSIBLE_TO]->accessor, users = user-[r2?:BELONGS_TO*]->accessor
WHERE item.type! = 'item' AND item.name! = 'item1' AND (access IS NULL OR user.name! = 'user1')
RETURN DISTINCT item, access
Right, now let’s create a new user and grant them exclusive access to 'item3':
MATCH item
WHERE item.name! = 'item3'
CREATE (user3 { type: 'user', name: 'user3' }), item-[r:ACCESSIBLE_TO]->user3
RETURN user3
Now we’ve added a constraint to 'item3', 'user1' should only have access to 'item1':
START items = node(*)
MATCH access = items-[r1?:ACCESSIBLE_TO]->accessors, users = user-[r2?:BELONGS_TO*]->accessors
WHERE items.type! = 'item' AND (access IS NULL OR user.name! = 'user1')
RETURN DISTINCT items
The above queries should now change so that 'user1' only has access to 'item1', 'user2' to 'item2', and 'user3' to 'item3'. Note that the method of access is different:
-
'user1' belongs to 'role1', which belongs to 'admin', which has access to 'item1'
-
'user2' belongs to 'role2', which has direct access to 'item2'
-
'user3' has direct access to 'item3'
Hi @mikesname! This looks nice! Did you use it in a real project, did you run into any issues (performance, manageability or whatnot)?