Skip to content

SynapCores / Recipe

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
Instance: localhost:8080

Opens your running SynapCores (RBAC effective permissions through group inheritance will be staged for a preview — nothing runs until you click Run). No instance yet? Install free in ~30s.

Share

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 role;

Tags

graphcypherrbacsecurityintermediate