Last active
August 29, 2015 14:09
-
-
Save gregzuro/91c70eefc9c2f81a6259 to your computer and use it in GitHub Desktop.
Neo4j: CakePHP ACL example from http://book.cakephp.org/2.0/en/core-libraries/components/access-control-lists.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
== 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