Skip to content

Instantly share code, notes, and snippets.

@gregzuro
Last active August 29, 2015 14:09
Show Gist options
  • Save gregzuro/91c70eefc9c2f81a6259 to your computer and use it in GitHub Desktop.
Save gregzuro/91c70eefc9c2f81a6259 to your computer and use it in GitHub Desktop.
== CakePHP ACL example from link:http://book.cakephp.org/2.0/en/core-libraries/components/access-control-lists.html[CAKE] in Neo4j
:neo4j-version: 2.0.0
:author: Greg Zuro
:twitter: @softboyled
:tags: LOTR
Simple implementation of the ACO-ARO tree with some permission resolution queries.
//setup
[source,cypher]
----
CREATE
//users
(Gandalf:user {name:"Gandalf"}),
(Aragorn:user {name:"Aragorn"}),
(Bilbo:user {name:"Bilbo"}),
(Frodo:user {name:"Frodo"}),
(Gollum:user {name:"Gollum"}),
(Legolas:user {name:"Legolas"}),
(Gimli:user {name:"Gimli"}),
(Pippin:user {name:"Pippin"}),
(Merry:user {name:"Merry"}),
// resources
(Weapons:resource {name:"Weapons"}),
(TheOneRing:resource {name:"TheOneRing"}),
(SaltedPork:resource {name:"SaltedPork"}),
(ElvenRations:resource {name:"ElvenRations"}),
(Diplomacy:resource {name:"Diplomacy"}),
(Ale:resource {name:"Ale"}),
// groups
(Fellowship:group {name:"Fellowship"}),
(Warriors:group {name:"Warriors"}),
(Wizards:group {name:"Wizards"}),
(Hobbits:group {name:"Hobbits"}),
(Visitors:group {name:"Visitors"}),
// group and user relationships
(Warriors)-[:CHILDOF]->(Fellowship),
(Aragorn)-[:CHILDOF]->(Warriors),
(Legolas)-[:CHILDOF]->(Warriors),
(Gimli)-[:CHILDOF]->(Warriors),
(Wizards)-[:CHILDOF]->(Fellowship),
(Gandalf)-[:CHILDOF]->(Wizards),
(Hobbits)-[:CHILDOF]->(Fellowship),
(Frodo)-[:CHILDOF]->(Hobbits),
(Bilbo)-[:CHILDOF]->(Hobbits),
(Merry)-[:CHILDOF]->(Hobbits),
(Pippin)-[:CHILDOF]->(Hobbits),
(Visitors)-[:CHILDOF]->(Fellowship),
(Gollum)-[:CHILDOF]->(Visitors),
// permissions
(Warriors)-[:ALLOW]->(Weapons),
(Warriors)-[:ALLOW]->(Ale),
(Warriors)-[:ALLOW]->(ElvenRations),
(Warriors)-[:ALLOW]->(SaltedPork),
(Wizards)-[:ALLOW]->(SaltedPork),
(Wizards)-[:ALLOW]->(Diplomacy),
(Wizards)-[:ALLOW]->(Ale),
(Hobbits)-[:ALLOW]->(Ale),
(Visitors)-[:ALLOW]->(SaltedPork),
(Merry)-[:DENY]->(Ale),
(Frodo)-[:DENY {priority: 0}]->(TheOneRing),
(Frodo)-[:ALLOW {priority: 1}]->(TheOneRing)
;
----
//graph
=== resources for which Bilbo has :ALLOW
NB: This does not handle :DENY.
//output
[source,cypher]
----
match (n:user {name: 'Bilbo'})-[:CHILDOF*0..9]->()-[:ALLOW]->(aco)
return aco.name ;
----
=== resources for which Legolas has :ALLOW
NB: This does not handle :DENY.
//output
[source,cypher]
----
match (n:user {name: 'Legolas'})-[:CHILDOF*0..9]->()-[:ALLOW]->(aco)
return aco.name ;
----
=== does Legolas have :ALLOW for Weapons ?
NB: This does not handle :DENY.
Returns one record with element having value 1 for true and 0 for false.
//output
[source,cypher]
----
match (n:user {name: 'Legolas'})-[:CHILDOF*0..9]->()-[r:ALLOW]->(aco:resource {name: 'Weapons'})
return r
limit 1;
----
=== does Bilbo have :ALLOW for Weapons ?
NB: This does not handle :DENY.
Returns one record with element having value 1 for true and 0 for false.
//output
[source,cypher]
----
match (n:user {name: 'Bilbo'})-[:CHILDOF*0..9]->()-[r:ALLOW]->(aco:resource {name: 'Weapons'})
return r
limit 1;
----
=== does Merry have :ALLOW for Ale ?
The previous queries have looked only for paths between the ARO and ACO that contain :ALLOW.
Here, we take the shortest path with either :ALLOW or :DENY.
In this way, we can have a more specific :DENY take precedence over any :ALLOW that appears elsewhere.
We still need to do a tie-break for equal path lengths:
For ties with a path length of 1, this implies a direct link between the ARO and the ACO, so a :DENY should take precedence.
This shouldn't be allowed, since it indicates confusion regarding the user's specific rights, but should be handled in a deterministic way.
For ties with a path length of >1, this implies a link that includes some group.
In this case it is possible that :ALLOW should trump a :DENY.
Further discussion is warranted.
//output
[source,cypher]
----
match p=(n:user {name: 'Merry'})-[:CHILDOF*0..9]->()-[r:ALLOW|:DENY]->(aco:resource {name: 'Ale'})
return r, aco, length(p)
order by length(p) ;
----
We can refine this to give a direct answer similar to the earlier queries:
//output
[source,cypher]
----
match p=(n:user {name: 'Merry'})-[:CHILDOF*0..9]->()-[r:ALLOW|:DENY]->(aco:resource {name: 'Ale'})
return r
order by length(p)
limit 1;
----
=== Handle ties
In order to handle ties, we've attached a label to the permission relations for Frodo's :ALLOW and :DENY relationship with TheOneRing upon which we can sort since one cannot sort results based upon the relation's identifier (:ALLOW, etc.):
```
(Frodo)-[:DENY {priority: 0}]->(TheOneRing),
(Frodo)-[:ALLOW {priority: 1}]->(TheOneRing)
```
This approach allows one to give priority on a case-by-case basis, but seems a bit clunky.
//output
[source,cypher]
----
match p=(n:user {name: 'Frodo'})-[:CHILDOF*0..9]->()-[r:ALLOW|:DENY]->(aco:resource {name: 'TheOneRing'})
return r
order by length(p), r.priority
limit 1;
----
## Summary
Oh, Sam!
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment