Neo4j Blog

Access control lists the graph database way

Access control lists the graph database way

In many contexts you need to handle user permissions to access, create or change some kind of resources. A common example is a file system, and that’s what we are going to dive into in this blog post. We’re going to use Ruby bindings for the Neo4j graph database to create a small – but working – example application.

Preparation

To set up the environment for this example on Ubuntu, I used the following commands:

sudo apt-get install jrubysudo jruby -S gem install neo4j

To import the libraries, the following code was used:

require 'rubygems'require 'neo4j'require 'neo4j/extensions/find_path'

Heading for the node space

So user permissions, what are they all about? Obviously it’s about users, and usually user groups as well. We’ll abstract this away a bit and use the term principals, which can be single users or groups.The other side of user permissions are the resources which are to be protected. In our case we’ll have a file system, so there will be folders and files. Here we’ll use the term content.Let’s start out building a graph to support the application from what we have gathered so far! When working with a graph it’s beneficial to think in a graphy manner, so that’s where we’ll begin. Graphs are presumably about connecting things, so our first step is to create some relationships. Neo4j comes with a built-in reference node, which is easily accessible at all times. We use this to create our own “subreference nodes”, one for principals and one for content. This is how our graph looks so far:To create (and get) the subreference nodes, we use this function:

This function is then called whenever we need to use a subreference node. The important parts here are:

ref_node: the built-in reference node

rels: relationships connected to a node

outgoing: the direction of the relationship (the relationships are always directed, but you can choose to ignore the direction in traversals)

( name ): the type of relationships to follow (the type can be ignored in traversals as well, but in our case we want to use it)

nodes: the nodes in the other end of the relationships

first: the first node found – there sould only be one subreference node of each type

If the subreference node isn’t found, it will be created and connected to the reference node. As you can see, we’re adding a property with the key name to the nodes as well, which is there solely for the purpose of visualization (the images in this post are created using Neoclipse).

Basic structure

For the principals part, we are going to connect the top-level ones to the corresponding subreference node using a PRINCIPAL type of relationship. Other than that, there’s just users and groups, so let’s use a IS_MEMBER_OF_GROUP relationship type to encode that. This is how that looks in the graph:And here’s the code to create it:

If a new principal isn’t member of any groups, it’s added as a top-level principal, connected to the principals subrefererence node. In other case, it’s simply added to the groups.With Neo4j all operations on the graph have to be encapsulated in a transaction, so this is how we’ll call the above function:

For the content part, things are very similar to the principals part. The main difference is that in this case, an item can have only a single parent item. Here’s the graphical view on that:And this is the code to create the structure:

At the core

Now that we have the basic structure in place, what’s left regarding our data is a small but crucial part: the permissions information! We’re using a simple scheme: adding security relationships with optional boolean flags for read and write permission. Not much to say here, this is what we want the full graph to look like (click for a bigger version):A small function will help us add the security information:

To check the permission for some action by an actual principal for some content, there’s some work to do. This is the algorithm we use to retrieve a permission flag:

Move from the content node and upwards through the file system structure and investigate each level for permission information.

On each level, see if there are any principals related to or identical with the principal concerned.

Make sure to use the permission information from the principal closest to the principal concerned.

If permission information was found, return it; otherwise, continue traversing to the next level in the file system.

In the code for this, we’ll use a function named depth_of_principal() to calculate the distance between the principal we have traversed to and the principal concerned. More on that later, here’s the code to check the permissions:

Next steps

Thanks for reading – any feedback is welcome! Want to learn more about graph databases? Click below to get your free copy of O’Reilly’s Graph Databases ebook and discover how to use graph technologies for your application today.Download My Ebook

Nice article!<br /><br />I have some questions about neo4j<br />1. i will used neo4j with sinatra.rb together but i did not know to introducte neo4j to sinatra?<br /><br />2. To use neo4j in a app must i create a migration for use it or it is enough to declareted the propertys?<br /><br />Thanks

I&#39;m new to Neo4j, and wondering, Is it possible to do this query traversal using Gremlin? It seems that this is the type of thing that you&#39;d want to give to the server to process instead of making several requests.

I&#39;m new to Neo4j, and wondering, Is it possible to do this query traversal using Gremlin? It seems that this is the type of thing that you&#39;d want to give to the server to process instead of making several requests.

I&#39;m new to Neo4j, and wondering, Is it possible to do this query traversal using Gremlin? It seems that this is the type of thing that you&#39;d want to give to the server to process instead of making several requests.

It turns out that for this problem, access to a folder can be determined by parsing the shortest paths between the user and the user and the folder.<br /><br />Here is a cypher query that returns the shortest paths between two nodes, where the user is at node 157 and the folder is at node 160.<br /><br />START u=node(157),f=node(160) <br />MATCH p=allShortestPaths(u-[*]-&gt;f) <br />RETURN p