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-accessible 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:Role { name: 'admins' }),
(role1:Role { name: 'role1' }),
(role1)-[:HAS_ACCESS]->(admins),
(role2:Role { name: 'role2' }),
(user1:User { name: 'user1' }),
(user1)-[:HAS_ACCESS]->(role1),
(user2:User { name: 'user2' }),
(user2)-[:HAS_ACCESS]->(role2),
(item1:Item { name: 'item1' }),
(item1)<-[:HAS_ACCESS]-(admins),
(item2:Item { name: 'item2' }),
(item2)<-[:HAS_ACCESS]-(role2),
(item3:Item { name: 'item3' })
return admins, role1, role2, user1, user2, item1, item2, item3And a bit of schema to help lookups.
create constraint on (user:User) assert user.name is unique;
create constraint on (item:Item) assert item.name is unique;First, check what items are accessible to everyone, because they have no constraints. This should return just item3.
match (item:Item)
where not (item)<-[:HAS_ACCESS]-()
return itemNow 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.
match (user:User { name: 'user1' })-[:HAS_ACCESS*]->(item:Item)
return distinct item
union
match (item:Item)
where not (item)<-[:HAS_ACCESS]-()
return itemOkay, that seems to work. Likewise, if we try the same thing with 'user2' we should be 'item2' and 'item3':
match (user:User { name: 'user2' })-[:HAS_ACCESS*]->(item:Item)
return distinct item
union
match (item:Item)
where not (item)<-[:HAS_ACCESS]-()
return itemCheck if item is 'item1' is accessible to 'user1':
match (item:Item { name: 'item1' })
optional match (item:Item)<-[r:HAS_ACCESS]-()
optional match (item:Item)<-[:HAS_ACCESS*]-(user:User { name: 'user1' })
return count(r) = 0 or user is not null as has_accessRight, now let’s create a new user and grant them exclusive access to 'item3':
match (item:Item { name: 'item3' })
create (user3:User { name: 'user3' }), (item)<-[:HAS_ACCESS]-(user3)
return user3Now we’ve added a constraint to 'item3', 'user1' should only have access to 'item1':
match (user:User { name: 'user1' })-[:HAS_ACCESS*]->(item:Item)
return distinct item
union
match (item:Item)
where not (item)<-[:HAS_ACCESS]-()
return itemThe 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'