Skip to content

Instantly share code, notes, and snippets.

@durango
Forked from jexp/acl.adoc
Created January 3, 2019 20:10
Show Gist options
  • Save durango/7df50d0ffc6a290a56a17b9c232d129d to your computer and use it in GitHub Desktop.
Save durango/7df50d0ffc6a290a56a17b9c232d129d to your computer and use it in GitHub Desktop.

Revisions

  1. @jexp jexp revised this gist May 31, 2014. 1 changed file with 2 additions and 0 deletions.
    2 changes: 2 additions & 0 deletions acl.adoc
    Original file line number Diff line number Diff line change
    @@ -81,6 +81,8 @@ RETURN path

    //graph_result

    //table


    I take this query to mean something like: "Give me user Bob, and any +[:AXO{read:true}]+ relationship that he has to resource +eVar 33+. You may go to through zero or more +[:IN]+ to access the resource through Bob's groups, and through zero or more +[:HAS]+, since resources inherit permissions".

  2. @jexp jexp revised this gist May 31, 2014. 1 changed file with 13 additions and 4 deletions.
    17 changes: 13 additions & 4 deletions acl.adoc
    Original file line number Diff line number Diff line change
    @@ -68,11 +68,20 @@ Does this do what you want?
    [source,cypher]
    ----
    MATCH (bob:User { name:"Bob" })-[:IN*0..]->(group)-[:AXO { read:true }]->(res1)-[:HAS*0..]->(res2 { name:"eVar 33" })
    RETURN count(*)
    RETURN count(*) as allowed
    ----

    //table

    [source,cypher]
    ----
    MATCH path=(bob:User { name:"Bob" })-[:IN*0..]->(group)-[:AXO { read:true }]->(res1)-[:HAS*0..]->(res2 { name:"eVar 33" })
    RETURN path
    ----

    //graph_result


    I take this query to mean something like: "Give me user Bob, and any +[:AXO{read:true}]+ relationship that he has to resource +eVar 33+. You may go to through zero or more +[:IN]+ to access the resource through Bob's groups, and through zero or more +[:HAS]+, since resources inherit permissions".

    >1 means read access, 0 means not.
    @@ -89,7 +98,7 @@ Now the path from Bob to resource is a predicate on which the Bobs in your +MATC
    ----
    MATCH (bob:User { name:"Bob" })
    WHERE bob-[:IN*0..]->()-[:AXO { read:true }]->()-[:HAS*0..]->({ name:"eVar 33" })
    RETURN true
    RETURN true as allowed
    ----

    //table
    @@ -100,7 +109,7 @@ This will not return anything if the path predicate evaluates false. If you want
    [source,cypher]
    ----
    MATCH (bob:User { name:"Bob" })
    RETURN 1 = count (bob-[:IN*0..]->()-[:AXO { read:true }]->()-[:HAS*0..]->({ name:"eVar 33" }))
    RETURN 1 = count (bob-[:IN*0..]->()-[:AXO { read:true }]->()-[:HAS*0..]->({ name:"eVar 33" })) as allowed
    ----

    //table
    @@ -110,7 +119,7 @@ It may 'feel' like counting the path predicate would mean counting the paths, bu
    [source,cypher]
    ----
    MATCH (bob:User { name:"Bob" })
    RETURN 1 = count (bob-[:IN*0..]->()-[:AXO]->()-[:HAS*0..]->({ name:"eVar 33" }))
    RETURN 1 = count (bob-[:IN*0..]->()-[:AXO]->()-[:HAS*0..]->({ name:"eVar 33" })) as allowed
    ----

    //table
  3. @jexp jexp revised this gist May 31, 2014. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions acl.adoc
    Original file line number Diff line number Diff line change
    @@ -31,7 +31,7 @@ Now I want to see if a path exists from Bob to the eVar 33 resource where Bob ha

    [source,cypher]
    ----
    MATCH p =(usr:Usr)-[:AXO {update: true}]->(aco:ACO)
    MATCH p =(usr:User)-[:AXO {update: true}]->(aco:Resource)
    WHERE usr.name = 'Bob' AND aco.name = 'eVar 33'
    RETURN p
    ----
    @@ -44,7 +44,7 @@ I have tried:

    [source,cypher]
    ----
    MATCH p =(usr:Usr)-[:IN|:HAS|:AXO {read: true}]->(aco:ACO)
    MATCH p =(usr:User)-[:IN|:HAS|:AXO {read: true}]->(aco:Resource)
    WHERE usr.name = 'Bob' AND aco.name = 'eVar 33'
    RETURN p
    ----
  4. @jexp jexp created this gist May 31, 2014.
    118 changes: 118 additions & 0 deletions acl.adoc
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,118 @@
    == Neo4j directed path through multiple relationships with property filter

    From http://stackoverflow.com/questions/23917939/neo4j-directed-path-through-multiple-relationships-with-property-filter[Stackoverflow Question]

    Being new to Cypher and Neo4j, I am having trouble constructing my query for my use-case. I am building a simple ACL (access control list) and am looking for a path through permission relationships an up a hierarchy as well. A picture may better explain it:

    image:http://i.stack.imgur.com/cC8KN.png[]

    ----
    Key:
    Users -> Blue
    Groups -> Yellow, Green
    Resource Tree -> Red
    ----

    === Setup

    //setup
    [source,cypher]
    ----
    CREATE (_6:User { name:"Bob" }),(_7:User { name:"Linda" }),(_8:Corp { name:"Nike Corp" }),(_9:UserGroup { name:"Media Mgmt" }),(_10:Resource { name:"Analytics" }),(_11:Resource { name:"Metrics Dimensions" }),(_12:Resource { name:"Conversion" }),(_13:Resource { name:"eVar 33" }), _6-[:IN]->_9, _6-[:AXO {
    DELETE :true,update:true }]->_13, _7-[:IN]->_9, _7-[:AXO]->_12, _9-[:IN]->_8, _9-[:AXO { read:true }]->_12, _10-[:HAS]->_11, _11-[:HAS]->_12, _12-[:HAS]->_13
    ----

    //graph


    === Questions

    Now I want to see if a path exists from Bob to the eVar 33 resource where Bob has update access. Because there is a direct path, I can get what I am looking for by running

    [source,cypher]
    ----
    MATCH p =(usr:Usr)-[:AXO {update: true}]->(aco:ACO)
    WHERE usr.name = 'Bob' AND aco.name = 'eVar 33'
    RETURN p
    ----

    //graph_result

    But now, Bob is also a member of the *Media Mgmt group* which grants him read access to the *Conversion* resource. And because Conversion is further up the resource tree than +eVar 33+, +eVar 33+ should inherit this permission. But when I run the same query looking for +{read: true}+ instead, no path is found. I know this is because I am not allowing traversal through the +:IN+ and +:HAS+ relationships, but how can I do this?

    I have tried:

    [source,cypher]
    ----
    MATCH p =(usr:Usr)-[:IN|:HAS|:AXO {read: true}]->(aco:ACO)
    WHERE usr.name = 'Bob' AND aco.name = 'eVar 33'
    RETURN p
    ----

    //graph_result

    thinking this would allow those relationships to be traversed, but it still does not find a path (because I am not allowing more than a depth of 1?).

    So here are my needs:

    Unknown depth of path
    Any path(s) I get back are fine (all I really care about is "Is there a path or not?")
    Must be able to get from a user to a resource AND when an AXO relationship is being followed it must match a property filter.
    Must follow the directed graph (i.g. Bob has no permissions for Analytics)
    And no, I do not work for Nike. Just an example use-case here :)

    === Answer by Jonatan Jaderberg

    Does this do what you want?

    [source,cypher]
    ----
    MATCH (bob:User { name:"Bob" })-[:IN*0..]->(group)-[:AXO { read:true }]->(res1)-[:HAS*0..]->(res2 { name:"eVar 33" })
    RETURN count(*)
    ----

    //table

    I take this query to mean something like: "Give me user Bob, and any +[:AXO{read:true}]+ relationship that he has to resource +eVar 33+. You may go to through zero or more +[:IN]+ to access the resource through Bob's groups, and through zero or more +[:HAS]+, since resources inherit permissions".

    >1 means read access, 0 means not.

    If your +[:IN]+ or +[:HAS]+ trees are very complex you may want to cap depth.

    === Edit

    Wrt comment about optimizing by returning on first path found, it's not always obvious how to control query execution this way, sometimes you have to know when and how Cypher is lazy. Limiting result to 1 may suffice, but in this case reformulating the query slightly may be more to the point, something like: "Give me user Bob if he has any +[:AXO{read:true}]+ relationship to resource +eVar 33+. You may go through..."

    Now the path from Bob to resource is a predicate on which the Bobs in your +MATCH+ clause are filtered. In Cypher, something like

    [source,cypher]
    ----
    MATCH (bob:User { name:"Bob" })
    WHERE bob-[:IN*0..]->()-[:AXO { read:true }]->()-[:HAS*0..]->({ name:"eVar 33" })
    RETURN true
    ----

    //table

    This will not return anything if the path predicate evaluates false. If you want to determine permission based on what is returned rather than whether something is returned, don't use +WHERE+ but just return a count of the predicate, or better an assertion that the count of the predicate is 1. Since the pattern is not part of the +MATCH+ clause it will not expand your result, so counting will be 0 or 1 (if there is only one Bob).


    [source,cypher]
    ----
    MATCH (bob:User { name:"Bob" })
    RETURN 1 = count (bob-[:IN*0..]->()-[:AXO { read:true }]->()-[:HAS*0..]->({ name:"eVar 33" }))
    ----

    //table

    It may 'feel' like counting the path predicate would mean counting the paths, but it doesn't. Try removing +{read:true}+ to get a path pattern with more than one matches in the graph–counting it as a predicate still gives 1.

    [source,cypher]
    ----
    MATCH (bob:User { name:"Bob" })
    RETURN 1 = count (bob-[:IN*0..]->()-[:AXO]->()-[:HAS*0..]->({ name:"eVar 33" }))
    ----

    //table

    Try profiling such a query and compare to the first query with a +LIMIT 1+ to see which execution plan makes most sense.