Skip to content

Relationships

Applications often grant access based on how actors and resources are connected in your domain, such as who owns a document, which organization a document belongs to, or which outside collaborators have been shared on a single file. Rather than copying role assignments onto every individual resource, record those relationships as facts and derive access across them in rules.

Suppose members of an organization should be able to edit every document belonging to that organization. Connect the document’s parent relationship (has_relation) with the user’s organization role (has_role) by repeating a shared org: Organization variable across both conditions:

declare has_role(User, string, Organization);
declare has_relation(Document, string, Organization);
has_role(user: User, "editor", document: Document) if
has_relation(document, "organization", org: Organization) and
has_role(user, "member", org);

When using has_relation(resource, relation, related), name the relation string from the first term’s perspective: Document:handbook has "organization" Organization:acme. Because org appears in both conditions, linking a new document to Organization:acme immediately grants existing organization members the "editor" role on it.

Combining inherited roles and direct grants

Section titled “Combining inherited roles and direct grants”

You can combine organization-inherited roles with one-off collaborator shares on a single document by routing both into has_permission:

declare has_permission(User, string, Document);
// Document editors can read and edit
has_permission(user: User, "read", document: Document) if
has_role(user, "editor", document);
has_permission(user: User, "edit", document: Document) if
has_role(user, "editor", document);
allow(actor, action, resource) if
has_permission(actor, action, resource);
  • Inherited access: User:alice is a "member" of Organization:acme, which derives has_role(User:alice, "editor", Document:handbook) and grants "read" and "edit".
  • Direct share: Inserting has_permission(User:bob, "read", Document:handbook) grants User:bob read access to that single document without granting "edit" or access to other documents in Organization:acme.

Deriving an intermediate document role (has_role(user, "editor", document)) is useful when multiple rules or direct per-document role assignments also target "editor". If your product never assigns document-level roles directly, derive has_permission straight from the organization relationship:

has_permission(user: User, "edit", document: Document) if
has_relation(document, "organization", org: Organization) and
has_role(user, "member", org);

Stored facts and rule-derived conclusions are additive: an authorization check succeeds if any path proves allow(actor, action, resource) and no deny(actor, action, resource) matches.

If User:alice is a member of Organization:acme and also holds a direct has_permission(User:alice, "read", Document:handbook) fact, removing her organization membership stops the inherited path, but the direct has_permission fact will still allow her to "read" Document:handbook until that fact is also deleted:

// Removing organization membership stops this rule:
has_role(user: User, "editor", document: Document) if
has_relation(document, "organization", org: Organization) and
has_role(user, "member", org);
// But this stored fact continues to allow read access:
has_permission(User:alice, "read", Document:handbook);

When revoking access, your application can proceed in two ways:

  • Targeted grant deletion: Delete each stored fact providing an independent path to that resource as covered in Managing Facts.
  • Blanket access denial: When an actor must be blocked across all resources immediately (such as during account suspension), define an optional deny rule as shown in Designing Policies. A matching deny eliminates access across all additive paths without requiring you to locate and delete every individual grant upfront.