Modeling Guide
Design a complete authorization schema for a real application, step by step.
This guide walks through designing an authorization model for Workspace — a collaborative document platform where users belong to teams, teams belong to organizations, and documents live in folders with inherited permissions.
By the end, you’ll have a production-quality schema that combines ReBAC, RBAC, and ABAC in a single IPL file.
The Application
Workspace has these requirements:
- Users belong to teams and organizations
- Documents live in folders with cascading permissions
- Org admins can access everything
- Editors can view and edit; viewers can only view
- Explicit deny overrides any grant (e.g., suspended users)
- Access can require business hours or specific IP ranges
Phase 1: Identify Your Entities
List the nouns (types) and verbs (relations) in your application.
| Entity | Relations |
|---|---|
| user | (no relations — subjects, not resources) |
| organization | admin, member |
| team | org (parent), member |
| folder | org (parent), viewer, editor |
| document | folder (parent), viewer, editor, owner |
Start with empty type stubs:
type user {}
type organization {}
type team {}
type folder {}
type document {}
Phase 2: Add Direct Relations
Direct relations are stored facts — the edges in your authorization graph.
type user {}
type organization {
relation admin
relation member
}
type team {
relation org // which organization this team belongs to
relation member
}
type folder {
relation org // which organization owns this folder
relation viewer
relation editor
}
type document {
relation folder // which folder contains this document
relation viewer
relation editor
relation owner
}
Write some test data:
inferadb relationships add user:alice admin organization:acme
inferadb relationships add user:bob member team:engineering
inferadb relationships add team:engineering org organization:acme
inferadb relationships add user:charlie editor document:roadmap
At this point, each relation is independent — no inheritance or computed permissions. Alice is an org admin, but that doesn’t give her document access yet.
Phase 3: Compute Permissions
Derive permissions from relationships using IPL expressions.
Document access:
type document {
relation folder
relation viewer
relation editor
relation owner
// Computed permissions
relation can_view = viewer | editor | owner
relation can_edit = editor | owner
relation can_delete = owner
}
| (union) means “any branch grants access.”
inferadb check document:roadmap can_view user:charlie
# ✓ ALLOWED — charlie is an editor, editors can view
inferadb check document:roadmap can_delete user:charlie
# ✗ DENIED — charlie is an editor, not an owner
Phase 4: Inherit Permissions Through Hierarchies
Folder editors should edit documents in that folder. This is tuple-to-userset — follow a relation to a parent, then check a permission there.
type document {
relation folder
relation viewer
relation editor
relation owner
relation can_view = viewer | editor | owner | viewer from folder | editor from folder
relation can_edit = editor | owner | editor from folder
relation can_delete = owner
}
viewer from folder means: follow the folder relation to the parent, then check viewer there.
inferadb relationships add user:dana viewer folder:engineering
inferadb relationships add document:roadmap folder folder:engineering
inferadb check document:roadmap can_view user:dana
# ✓ ALLOWED — dana is a viewer of folder:engineering, roadmap is in that folder
Apply the same pattern to folders inheriting from organizations:
type folder {
relation org
relation viewer
relation editor
relation can_view = viewer | editor | member from org
relation can_edit = editor | admin from org
}
Org members can now view all folders in their org. Org admins can edit them.
Phase 5: Add Deny Rules
Suspended users must be denied access regardless of other permissions.
type document {
relation folder
relation viewer
relation editor
relation owner
forbid suspended
relation can_view = viewer | editor | owner | viewer from folder | editor from folder
relation can_edit = editor | owner | editor from folder
relation can_delete = owner
}
inferadb relationships add user:charlie suspended document:roadmap
inferadb check document:roadmap can_edit user:charlie
# ✗ DENIED — forbid rules override all permits
Phase 6: Add Contextual Checks with WASM
Business-hours restrictions are ABAC — decisions depend on context, not just relationships. Use a WASM module:
type document {
relation folder
relation viewer
relation editor
relation owner
forbid suspended
relation can_view = viewer | editor | owner | viewer from folder | editor from folder
relation can_edit = (editor | owner | editor from folder) & module("business_hours")
relation can_delete = owner
}
& (intersection) with module("business_hours") requires editor access AND WASM module approval. See WASM Modules for module implementation.
The Complete Schema
type user {}
type organization {
relation admin
relation member
}
type team {
relation org
relation member
}
type folder {
relation org
relation viewer
relation editor
relation can_view = viewer | editor | member from org
relation can_edit = editor | admin from org
}
type document {
relation folder
relation viewer
relation editor
relation owner
forbid suspended
relation can_view = viewer | editor | owner | viewer from folder | editor from folder
relation can_edit = (editor | owner | editor from folder) & module("business_hours")
relation can_delete = owner
}
Validate and push:
inferadb schemas validate schema.ipl
inferadb schemas push schema.ipl
Schema Design Checklist
- Every noun in your domain has a type
- Relations are direct (stored) or computed (derived) — never both
- Hierarchy inheritance uses
from(tuple-to-userset) or->(related object userset) - Deny rules use
forbid— not exclusion (-) on computed permissions - WASM modules are used for context-dependent checks (time, IP, attributes), not for relationship logic
- Schema passes
inferadb schemas validatewith no warnings - You’ve tested both allowed and denied cases for every computed permission
Common Patterns
Team-Based Access
type team {
relation org
relation member
}
type resource {
relation team
relation can_access = member from team
}
Multi-Level Inheritance (Org → Folder → Document)
relation can_view = viewer | viewer from folder | member from folder->org
Conditional Access (Intersection)
relation can_view_sensitive = viewer & has_clearance & module("check_ip")
Public Resources (Wildcard)
inferadb relationships add "user:*" viewer document:public-faq
What’s Next?
- Schema Reference — Full syntax, AST, evaluation semantics
- Schema Patterns — Reusable authorization patterns and anti-patterns
- WASM Modules — Write custom authorization logic
- REST API — Integrate checks into your application
- Troubleshooting — Debug unexpected ALLOW or DENY results