Skip to content

Instantly share code, notes, and snippets.

@chrisvest
Forked from mikesname/GraphGist-SimpleRBAC.adoc
Last active August 29, 2015 14:08
Show Gist options
  • Save chrisvest/3b2037e55e7a8c96815a to your computer and use it in GitHub Desktop.
Save chrisvest/3b2037e55e7a8c96815a to your computer and use it in GitHub Desktop.

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, item3

And 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 item

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.

match (user:User { name: 'user1' })-[:HAS_ACCESS*]->(item:Item)
return distinct item
union
match (item:Item)
where not (item)<-[:HAS_ACCESS]-()
return item

Okay, 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 item

Check 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_access

Right, 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 user3

Now 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 item

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'

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment