RBAC effective permissions through group inheritance

Resolve what a user can actually do by walking user-group-role-permission graph paths

All recipes· graph· 6 minutesintermediatecypher

RBAC effective permissions through group inheritance

Objective

In any large system, users inherit permissions through nested groups and roles. "What can Sarah actually do?" becomes a recursive walk across user → group → group → role → permission. Cypher flattens this into a single MATCH that returns the deduplicated list of effective permissions.

Step 1: Create the graph

// Users
MERGE (sarah:User {name: "Sarah Chen",  email: "sarah@acme.com"})
MERGE (raj:User   {name: "Raj Patel",   email: "raj@acme.com"})
MERGE (mia:User   {name: "Mia Rossi",   email: "mia@acme.com"})

// Groups (can be nested)
MERGE (eng:Group     {name: "Engineering"})
MERGE (platform:Group {name: "Platform Team"})
MERGE (mobile:Group   {name: "Mobile Team"})
MERGE (ops:Group     {name: "Ops"})

MERGE (platform)-[:MEMBER_OF]->(eng)
MERGE (mobile)-[:MEMBER_OF]->(eng)

// Direct user memberships
MERGE (sarah)-[:MEMBER_OF]->(platform)
MERGE (raj)-[:MEMBER_OF]->(mobile)
MERGE (mia)-[:MEMBER_OF]->(ops)

// Roles
MERGE (admin:Role  {name: "Admin"})
MERGE (deploy:Role {name: "Deployer"})
MERGE (viewer:Role {name: "Viewer"})
MERGE (oncall:Role {name: "OnCall"})

MERGE (eng)-[:HAS_ROLE]->(viewer)
MERGE (platform)-[:HAS_ROLE]->(deploy)
MERGE (platform)-[:HAS_ROLE]->(admin)
MERGE (mobile)-[:HAS_ROLE]->(deploy)
MERGE (ops)-[:HAS_ROLE]->(oncall)
MERGE (ops)-[:HAS_ROLE]->(viewer)

// Permissions
MERGE (read:Permission   {action: "read:repo"})
MERGE (write:Permission  {action: "write:repo"})
MERGE (deployPerm:Permission {action: "deploy:prod"})
MERGE (manage:Permission {action: "manage:users"})
MERGE (page:Permission   {action: "page:oncall"})

MERGE (viewer)-[:GRANTS]->(read)
MERGE (deploy)-[:GRANTS]->(read)
MERGE (deploy)-[:GRANTS]->(write)
MERGE (deploy)-[:GRANTS]->(deployPerm)
MERGE (admin)-[:GRANTS]->(manage)
MERGE (admin)-[:GRANTS]->(write)
MERGE (oncall)-[:GRANTS]->(page);

Step 2: What can Sarah actually do?

// User -> (zero or more group hops via MEMBER_OF) -> Role -> Permission
MATCH (u:User {email: "sarah@acme.com"})-[:MEMBER_OF*1..5]->(g:Group)
      -[:HAS_ROLE]->(r:Role)-[:GRANTS]->(perm:Permission)
RETURN DISTINCT perm.action AS effective_permission,
       collect(DISTINCT r.name) AS via_roles
ORDER BY effective_permission;

What's happening

  • [:MEMBER_OF*1..5] traverses nested group membership transitively — Sarah is in Platform Team, which is in Engineering, so she inherits both groups' roles in one walk.
  • DISTINCT deduplicates permissions granted via multiple paths (e.g. read:repo via Viewer and Deployer).
  • collect() shows which roles granted each permission — useful for auditors asking "why does this user have access?".
  • In SQL this is the textbook recursive-CTE problem; in Cypher it is one expression. Adding deny rules or time-bound grants (WHERE r.expires > date()) is a property change, not a redesign.
  • The same query works for any user — flip the email and you have a real audit endpoint.

Try this next

MATCH (u:User)-[:MEMBER_OF*]->(:Group)-[:HAS_ROLE]->(:Role)-[:GRANTS]->(p:Permission {action: "deploy:prod"})
RETURN DISTINCT u.email AS who_can_deploy;
MATCH path = (u:User {email: "sarah@acme.com"})-[:MEMBER_OF*]->(g:Group)
RETURN [n IN nodes(path) | coalesce(n.name, n.email)] AS group_chain;
MATCH (r:Role)-[:GRANTS]->(p:Permission)
RETURN r.name AS role, collect(p.action) AS permissions
ORDER BY r.name;

Tags

graphcypherrbacsecurityintermediate

Run this on your own machine

Install SynapCores Community Edition free, paste the SQL or Cypher above into the bundled web UI, and watch it run.

Download Free CE