Permission & Access-Control Model

Source: Copied from Plings-Docs/admin/permission-model-detailed.md (2025-06-18).

Status: Draft v0.1
Audience: Backend & Front-end engineers
Purpose: Describe how global roles, organisation groups, abilities and container-level ACLs interact to decide who can do what, where.


1. Layers of Authorisation

Layer Scope Implemented with Example
System Role Whole platform system_roles table + JWT role_id claim system_owner, manufacturer_issuer
Org Group Single organisation org_permission_groups, org_group_members Warehouse Staff, Family, External Auditor
Ability Atomic action role_abilities, org_group_abilities OBJECT_READ, SPATIAL_MOVE, RESET_PWD
Container ACL Sub-tree of spatial graph container_acl table Deny OBJECT_READ in CEO Room for Everyone

Decision flow (highest → lowest):

  1. Super-Admin bypasses all checks (role_id ⇒ ability SUPER_ADMIN).
  2. RLS policy calls jwt_has_ability('X'). If false → deny.
    – Abilities come either from the system role or from one/more org groups.
  3. When ability is spatial (read/move), jwt_scope_allows_* helper evaluates current container ACL hierarchy.
    – Deny rows override allow rows deeper in the path.

2. Tables & Helpers

erDiagram roles ||--o{ role_abilities : has system_roles ||--|| roles : "fk" org_permission_groups ||--o{ org_group_members : "members" org_permission_groups ||--o{ org_group_abilities : "grants" container_acl ||--|| org_permission_groups : "fk"

roles / role_abilities (global)

roles(role_id PK, role_name text, is_internal bool)
role_abilities(role_id fk, ability text)

org-scoped groups

org_permission_groups(group_id PK, organisation_id fk, group_name text)
org_group_members(group_id fk, user_id uuid)
org_group_abilities(group_id fk, ability text)

container ACL

container_acl(
  container_id uuid REFERENCES object_instances(id),
  group_id uuid REFERENCES org_permission_groups(group_id),
  ability text,
  mode enum('allow','deny') default 'allow'
)

Helper functions (simplified snippets)

create function jwt_has_ability(_a text) returns boolean ...;
create function jwt_scope_allows_container(_container uuid) returns boolean ...;
create function jwt_can_see_object(_obj uuid) returns boolean ...;

See api_security_guidelines.md → JWT Claim Design for claim format.


3. Ability Catalogue (v1)

Category Ability Key Typical Holders Description
Object CRUD OBJECT_READ all roles View object metadata & images
  OBJECT_CREATE editors, makers Create new object instance or class
  OBJECT_UPDATE editors Edit name/description/properties
  OBJECT_DELETE admins Hard-delete object & graph links
Spatial SPATIAL_MOVE movers, scanners Change current spatial relationship
  SPATIAL_SET_EXPECTED admins, planners Set / change expected (home) location
Functional & Lifecycle FUNCTIONAL_EDIT technicians Add/remove functional relations
  STATUS_UPDATE technicians Add/remove lifecycle statuses
Ownership & Commerce OWNERSHIP_TRANSFER admins Transfer ownership inside org
  OFFER_LEND lend desk Create & accept lend offers
  OFFER_RENT rental desk Create & accept rental agreements
  OFFER_SELL sales Create & accept sale offers
  OFFER_AUCTION auction house Manage auction lots
Identifiers IDENTIFIER_MINT manufacturer issuers Mint new PlingsIdentifier
  IDENTIFIER_REVOKE super-admin Revoke compromised identifiers
Batch & Collection BATCH_CREATE camera workflow Rapid image batch capture
  COLLECTION_BULK_ACTION power users Bulk move / transfer operations
Organisation Admin ORG_USER_INVITE org admin Invite / remove users in org
  ORG_ROLE_ASSIGN org admin Assign users to permission groups
  ORG_BILLING_VIEW org admin View usage & invoices
  ORG_BILLING_PAY finance Approve payments, apply credits
  ORG_AUDIT_VIEW org admin View audit log for their organisation
Support & Super RESET_PWD support Reset password / MFA for any user
  IMPERSONATE_USER support Issue short-lived shadow JWT
  ACL_BYPASS support Temporarily bypass container ACL
  SUPER_ADMIN system owner Full bypass of all checks

Abilities can be granted to system roles or org permission groups. The list is data-driven—adding a new ability only requires inserting a row and referencing jwt_has_ability('NEW_ABILITY') in policies/resolvers.


4. ACL Inheritance Algorithm

  1. Starting from the current spatial parent (IN/ON/UNDER), walk upward.
  2. Accumulate ACL rows (deny overrides allow).
  3. Stop at organisation root.
  4. If no matching ACL row → fall back to organisation-wide visibility.

A misplaced hammer left in the CEO room becomes hidden because its current path passes through a deny ACL; once scanned and moved the path changes and the ACL check re-evaluates.


5. Example Scenarios

5.1 CEO Private Room

Room-123 ACL:  DENY  OBJECT_READ  group Everyone
Room-123 ACL:  ALLOW OBJECT_READ  group CEO_Private

Everyone group loses visibility; CEO’s private group keeps it.

5.2 Family vs. Medicine-Box

MedicineBox ACL: DENY OBJECT_READ group Family

Everything else in the house is visible to Family because it inherits the default allow.

5.3 Support Impersonation

Support role has ability IMPERSONATE_USER. Resolver checks that ability before issuing a short-lived scoped JWT; RLS continues to apply for the impersonated identity.


6. Open Items

  • Decide on complete ability vocabulary (see spreadsheet).
  • JSON vs. table storage for groups[] & abilities claim (size trade-off).
  • Admin UI for inspecting effective ACL of a selected object.