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 jruby
sudo 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:
def get_or_create_sub_ref( name )
result = Neo4j.ref_node.rels.outgoing( name ).nodes.first
if ( result.nil? )
result = Neo4j::Node.new :name => name.to_s.capitalize.gsub("_", " ")
Neo4j.ref_node.rels.outgoing( name ) << result
end
return result
end
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:
def new_principal( name, member_of_groups = [] )
principal = Neo4j::Node.new
principal[ :name ] = name
if member_of_groups.empty?
get_or_create_sub_ref( :PRINCIPALS ).rels.outgoing( :PRINCIPAL ) << principal
else
for group in member_of_groups
principal.rels.outgoing( :IS_MEMBER_OF_GROUP ) << group
end
end
return principal
end
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:
Neo4j::Transaction.run do
all_principals = new_principal( "All principals" )
root = new_principal( "root", [ all_principals ] )
regular_users = new_principal( "Regular users", [ all_principals ] )
user1 = new_principal( "user1", [ regular_users ] )
user2 = new_principal( "user2", [ regular_users ] )
end
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:
def new_content( name, parent = nil )
content = Neo4j::Node.new
content[ :name ] = name
if ( parent.nil? )
get_or_create_sub_ref( :CONTENT_ROOTS ).rels.outgoing( :CONTENT_ROOT ) << content
else
parent.rels.outgoing( :HAS_CHILD_CONTENT ) << content
end
return content
end
Similar to how the principals were created, this is the code to create the content data:
Neo4j::Transaction.run do
root_folder = new_content( "Root folder" )
temp_folder = new_content( "Temp", root_folder )
home_folder = new_content( "Home", root_folder )
user1_home_folder = new_content( "user1 home", home_folder )
user2_home_folder = new_content( "user2 home", home_folder )
a_file = new_content( "MyFile.pdf", user1_home_folder )
end

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:
def apply_security( content, principal, map_with_flags )
security_relationship = Neo4j::Relationship.new( :SECURITY, principal, content )
map_with_flags.each_pair {|key, value| security_relationship[ key ] = value}
end
It’s time to add the security data:
Neo4j::Transaction.run do
apply_security( root_folder, root, { "w" => true } )
apply_security( root_folder, all_principals, { "r" => true } )
apply_security( temp_folder, all_principals, { "w" => true } )
apply_security( user1_home_folder, regular_users, { "r" => false, "w" => false } )
apply_security( user1_home_folder, user1, { "r" => true, "w" => true } )
apply_security( user2_home_folder, user2, { "r" => true, "w" => true } )
end
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:
  1. Move from the content node and upwards through the file system structure and investigate each level for permission information.
  2. On each level, see if there are any principals related to or identical with the principal concerned.
  3. Make sure to use the permission information from the principal closest to the principal concerned.
  4. 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:
def has_access( content, principal, flag )
for current_content in content.incoming( :HAS_CHILD_CONTENT ).depth( :all )
lowest_score = nil
lowest_modifier = nil
for rel in current_content.rels.incoming( :SECURITY )
rel_principal = rel.start_node
if !rel[ flag ].nil?
score = depth_of_principal( rel_principal, principal )
if !score.nil?
modifier = rel[ flag ]
if lowest_score.nil? || score < lowest_score ||
( score == lowest_score && modifier )
lowest_score = score
lowest_modifier = modifier
end
end
end
end
if !lowest_modifier.nil?
return lowest_modifier
end
end
return false
end
Here’s our function to check the distance between principals (and to see if they’re on the same path at all).
def depth_of_principal( principal, reference_principal )
result = reference_principal.outgoing( :IS_MEMBER_OF_GROUP ).depth( :all ).path_to( principal )
return result.nil? ? nil : result.size
end
Finally, we want to see that everything works, so here’s a utility function to print permission information:
def print_has_access( content, principal, flag )
print principal[ :name ] + " +" + flag.upcase + " access to " + content[ :name ] + "? " +
has_access( content, principal, flag ).to_s + "n"
end
And here’s how to use the function:
Neo4j::Transaction.run do
print_has_access( home_folder, root, "w" )
print_has_access( home_folder, user1, "w" )
print_has_access( a_file, root, "r" )
print_has_access( a_file, user2, "r" )
print_has_access( a_file, user1, "w" )
end

Next steps

The full source code is found here
Here’s a few useful resources to help you on your way:
Thanks for reading – any feedback is welcome!  

Keywords:  


12 Comments

ronge says:

Great !<br />I have written another version of it that using the power of neo4j.rb to make it a bit more object oriented, see http://gist.github.com/314816

Anonymous says:

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

@Anonymous:<br /><br />Regarding 1. see the code for this blog post: http://blog.neo4j.org/2010/05/mashups-with-facebook-graph-api-and.html as well as well as this example: http://github.com/andreasronge/neo4j/blob/master/examples/rest/example.rb<br /><br />For 2, I guess not, but you could ask that and other questions here: http://groups.google.com/group/neo4jrb

James says:

Why do you need the &quot;All Principals&quot; group? — couldn&#39;t the &quot;Principals&quot; sub-reference node serve as the &quot;All Principals&quot; group?

Mattias says:

In this example maybe it could, but if there&#39;d be other &quot;top level&quot; groups you could more easily see the difference between those two.

Matt Jones says:

Is it possible to achieve this traversal using a gremlin query?

Matt Jones says:

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.

Matt Jones says:

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.

Matt Jones says:

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.

Matt Jones says:

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

Thank you Matt for the suggestion!

Thank you for this blog, nice love it..<br /><br /><strong><a href="http://www.suikervrijeten.org&quot; rel="nofollow">Suikervrij eten</a></strong><br />

Leave a Reply

Your email address will not be published. Required fields are marked *

Related Posts

Popular Graph Topics

Archives

Have a Graph Question?

Reach out and connect with the Neo4j staff.
Stackoverflow
Contact Us