Skip to content

Resource Access (ACL) Functions

Functions for resource-based authorization using an Access Control List (ACL) model. This system provides fine-grained, per-resource access control with hierarchical resource types, deny-overrides, and group-based grants.

Source: 035_functions_resource_access.sql

See also: Resource Access concepts for an overview of the ACL model, hierarchical resource types, and the deny-overrides algorithm.

v3: Composite resource keys

As of v3, resource_id is jsonb (not bigint), supporting composite keys like {"project_id": 123, "folder_id": 42}. Each resource type defines a key_schema that specifies the expected key fields. During hierarchy walk-up, ancestor key extraction automatically selects only the keys relevant to each ancestor type.


Access Check Algorithm

All read-path functions (has_resource_access, filter_accessible_resources, get_resource_access_flags, get_resource_access_matrix) follow the same deny-overrides algorithm with hierarchy walk-up:

  1. System user (user ID 1) -- granted unconditionally
  2. Tenant owner -- granted unconditionally
  3. Get cached group IDs for the user/tenant pair
  4. Walk up the type hierarchy (most specific type first):
    • For each ancestor type, extract only the keys defined in that ancestor's key_schema from the full resource_id
    • User-level DENY found -- denied (overrides all grants)
    • User-level GRANT found -- granted
    • Group-level GRANT (via cached group IDs) found -- granted
  5. No grant found -- denied (or throws error if _throw_err is true)

Stability caveat

Read-path functions are NOT marked stable because they call unsecure.get_cached_group_ids(), which performs insert/update on auth.user_group_id_cache on cache miss.


Access Checking

auth.has_resource_access

Checks whether a user has a specific access flag on a single resource. Walks up the resource type hierarchy and applies the deny-overrides algorithm.

Parameter Type Default Description
_user_id bigint -- User performing the access
_correlation_id text -- Correlation ID for tracing
_resource_type text -- Resource type code (e.g. 'project.documents')
_resource_id jsonb -- Composite resource key (e.g. '{"project_id": 123, "folder_id": 42}')
_required_flag text 'read' Access flag to check (e.g. 'read', 'write', 'delete', 'share', 'approve', 'export')
_tenant_id integer 1 Tenant context
_throw_err boolean true When true, throws error 35001 on denial; when false, returns false silently

Returns: boolean -- true if the user has the required access Permission required: None (this is the access check) Source: 035_functions_resource_access.sql:91

auth.filter_accessible_resources

Bulk-filters an array of resource IDs, returning only those the user can access with the given flag. Applies the same deny-overrides algorithm as has_resource_access but processes all resources in a single query.

Parameter Type Default Description
_user_id bigint -- User whose access is being checked
_correlation_id text -- Correlation ID for tracing
_resource_type text -- Resource type code
_resource_ids jsonb[] -- Array of jsonb resource IDs to filter
_required_flag text 'read' Access flag to check
_tenant_id integer 1 Tenant context

Returns: table(__resource_id jsonb) -- the subset of input IDs the user can access Permission required: None (this is the access check) Source: 035_functions_resource_access.sql:215


Effective Flags & Matrix

auth.get_resource_access_flags

Returns all effective access flags a user has on a specific resource, after deny resolution. Includes both direct user grants and group-inherited grants. Each row indicates the source of the grant.

Parameter Type Default Description
_user_id bigint -- User whose effective flags are requested
_correlation_id text -- Correlation ID for tracing
_resource_type text -- Resource type code
_resource_id jsonb -- Composite resource key
_tenant_id integer 1 Tenant context

Returns: table(__access_flag text, __source text) Permission required: None Source: 035_functions_resource_access.sql:307

The __source column indicates how the flag was acquired:

Source value Meaning
'system' User ID 1 (system user) -- gets all flags automatically
'owner' Tenant owner -- gets all flags automatically
'direct' Explicitly granted to the user
group title Inherited from a group grant (shows the group's title)

auth.get_resource_access_matrix

Returns the full resource-type-by-flag permission matrix for a resource in a single call. Given a root or parent resource type, returns all descendant types with their effective flags. Designed for building permission UIs (toggle grids, access cards, etc.).

Parameter Type Default Description
_user_id bigint -- User whose matrix is requested
_correlation_id text -- Correlation ID for tracing
_resource_type text -- Root or parent resource type code (e.g. 'project')
_resource_id jsonb -- Composite resource key (e.g. '{"project_id": 123}')
_tenant_id integer 1 Tenant context

Returns: table(__resource_type text, __access_flag text, __source text) Permission required: None Source: 035_functions_resource_access.sql:408

The matrix includes inherited grants: a grant on a parent type (e.g. 'project') cascades to all children (e.g. 'project.documents', 'project.invoices') unless the child has an explicit deny or its own explicit grant. The __source column uses the same values as get_resource_access_flags.

Only flags that are valid for each resource type (per const.resource_type_flag) are included in the matrix output. This applies to all sources including system user and tenant owner grants. If a resource type has no entries in resource_type_flag, all flags are returned (backward compatible).


Grant, Deny & Revoke

auth.assign_resource_access

Grants one or more access flags to a user or group on a resource. Uses upsert semantics: if the target already has a deny row for the flag, it flips is_deny to false. Exactly one of _target_user_id or _user_group_id must be provided. Validates _resource_id against the resource type's key_schema and validates _access_flags against the per-type flag mapping (const.resource_type_flag) before granting. If the resource type has no entries in resource_type_flag, all flags are allowed.

Parameter Type Default Description
_created_by text -- Audit trail: who initiated the grant
_user_id bigint -- User performing the operation (permission check runs against this user)
_correlation_id text -- Correlation ID for tracing
_resource_type text -- Resource type code
_resource_id jsonb -- Composite resource key (validated against key_schema)
_target_user_id bigint null User receiving the grant (mutually exclusive with _user_group_id)
_user_group_id integer null Group receiving the grant (mutually exclusive with _target_user_id)
_access_flags text[] array['read'] Flags to grant
_tenant_id integer 1 Tenant context

Returns: table(__resource_access_id bigint, __access_flag text) -- one row per granted flag Permission required: resources.grant_access Source: 035_functions_resource_access.sql:555 Journal event: 18010 (resource_access_granted)

auth.deny_resource_access

Denies one or more access flags for a specific user on a resource. Deny entries override group-level grants. Uses upsert semantics: if the user already has a grant row for the flag, it flips is_deny to true. Deny is user-level only -- denying on groups is not supported. Validates _resource_id against the resource type's key_schema and validates _access_flags against the per-type flag mapping (const.resource_type_flag) before denying. If the resource type has no entries in resource_type_flag, all flags are allowed.

Parameter Type Default Description
_created_by text -- Audit trail: who initiated the deny
_user_id bigint -- User performing the operation
_correlation_id text -- Correlation ID for tracing
_resource_type text -- Resource type code
_resource_id jsonb -- Composite resource key (validated against key_schema)
_target_user_id bigint -- User being denied (required, no default)
_access_flags text[] array['read'] Flags to deny
_tenant_id integer 1 Tenant context

Returns: table(__resource_access_id bigint, __access_flag text) -- one row per denied flag Permission required: resources.deny_access Source: 035_functions_resource_access.sql:672 Journal event: 18012 (resource_access_denied)

auth.revoke_resource_access

Revokes specific access flags by deleting rows from the ACL table. Works for both grants and denies. If _access_flags is null, revokes all flags for the target. Exactly one of _target_user_id or _user_group_id must be provided.

Parameter Type Default Description
_deleted_by text -- Audit trail: who initiated the revoke
_user_id bigint -- User performing the operation
_correlation_id text -- Correlation ID for tracing
_resource_type text -- Resource type code
_resource_id jsonb -- Composite resource key
_target_user_id bigint null User whose access is being revoked
_user_group_id integer null Group whose access is being revoked
_access_flags text[] null Flags to revoke; null means revoke all flags
_tenant_id integer 1 Tenant context

Returns: bigint -- number of rows deleted Permission required: resources.revoke_access Source: 035_functions_resource_access.sql:760 Journal event: 18011 (resource_access_revoked)

auth.revoke_all_resource_access

Revokes all access entries (grants and denies, for all users and groups) on a specific resource. Uses jsonb containment (@>) for matching, so revoking on a parent key cascades to sub-resource grants. Intended for cleanup when a resource is being deleted.

Parameter Type Default Description
_deleted_by text -- Audit trail: who initiated the revoke
_user_id bigint -- User performing the operation
_correlation_id text -- Correlation ID for tracing
_resource_type text -- Resource type code
_resource_id jsonb -- Composite resource key (containment match for cascading)
_tenant_id integer 1 Tenant context

Returns: bigint -- number of rows deleted Permission required: resources.revoke_access Source: 035_functions_resource_access.sql:845 Journal event: 18013 (resource_access_bulk_revoked)


Query Functions

auth.get_resource_grants

Lists all grant and deny entries for a specific resource, including user display names, group titles, and who issued each grant.

Parameter Type Default Description
_user_id bigint -- User performing the query
_correlation_id text -- Correlation ID for tracing
_resource_type text -- Resource type code
_resource_id jsonb -- Composite resource key
_tenant_id integer 1 Tenant context

Returns: table(__resource_access_id bigint, __user_id bigint, __user_display_name text, __user_group_id integer, __group_title text, __access_flag text, __is_deny boolean, __granted_by bigint, __granted_by_name text, __created_at timestamptz) Permission required: resources.get_grants Source: 035_functions_resource_access.sql:896

auth.get_user_accessible_resources

Lists all resources of a given type that a user can access, with the effective flags and source for each. Users can query their own accessible resources without extra permissions; querying another user requires the resources.get_grants permission.

Parameter Type Default Description
_user_id bigint -- User performing the query
_correlation_id text -- Correlation ID for tracing
_target_user_id bigint -- User whose accessible resources to list
_resource_type text -- Resource type code to filter by
_access_flag text 'read' Filter by specific flag; null returns all flags
_tenant_id integer 1 Tenant context

Returns: table(__resource_id jsonb, __access_flags text[], __source text) Permission required: resources.get_grants (unless querying self) Source: 035_functions_resource_access.sql:957


Resource Type Management

Resource types define what kinds of resources can have ACL entries. Types are hierarchical using PostgreSQL's ltree extension -- a grant on a parent type cascades to all child types at check time.

auth.create_resource_type

Registers a new resource type and auto-creates a partition for root types. Child types share their root type's partition. The code itself encodes the hierarchy using dots (e.g. 'project.documents' has path project.documents).

Parameter Type Default Description
_created_by text -- Audit trail
_user_id bigint -- User performing the operation
_correlation_id text -- Correlation ID for tracing
_code text -- Resource type code; dots encode hierarchy (e.g. 'project.documents')
_title text -- Display title
_parent_code text null Parent resource type code (must exist and be active)
_description text null Optional description
_tenant_id integer 1 Tenant context
_source text null Optional source identifier (e.g. application name)
_key_schema jsonb '{}' Defines expected resource_id structure (e.g. '{"project_id": "bigint"}')
_access_flags text[] null Access flags valid for this type; stored in const.resource_type_flag. When null, all flags are allowed (backward compatible).

Returns: setof const.resource_type -- the created or existing row Permission required: resources.create_resource_type Source: 035_functions_resource_access.sql:1100 Journal event: 18001 (resource_type_created)

auth.update_resource_type

Updates an existing resource type's title, description, active status, or source. The code and parent_code are immutable because they define the hierarchy. A trigger automatically recalculates full_title when the title changes.

Parameter Type Default Description
_updated_by text -- Audit trail
_user_id bigint -- User performing the operation
_correlation_id text -- Correlation ID for tracing
_code text -- Resource type code to update
_title text null New title (unchanged if null)
_description text null New description (unchanged if null)
_is_active boolean null New active status (unchanged if null)
_source text null New source identifier (unchanged if null)
_tenant_id integer 1 Tenant context

Returns: setof const.resource_type -- the updated row Permission required: resources.create_resource_type (reuses create permission) Source: 035_functions_resource_access.sql:1164 Journal event: 18002 (resource_type_updated)

auth.ensure_resource_types

Bulk-ensures resource types from a JSONB array. Skips types that already exist, validates parent references, and auto-creates partitions. Items are automatically sorted by hierarchy depth (parents before children), so callers do not need to worry about ordering.

Parameter Type Default Description
_created_by text -- Audit trail
_user_id bigint -- User performing the operation
_correlation_id text -- Correlation ID for tracing
_resource_types jsonb -- JSONB array of objects (see format below)
_source text null Default source for items without their own source
_tenant_id integer 1 Tenant context

Returns: setof const.resource_type -- all processed resource types (including pre-existing ones) Permission required: resources.create_resource_type Source: 035_functions_resource_access.sql:1233 Journal event: 18001 (resource_type_created) per new type

Each object in the JSONB array accepts:

Field Required Description
code yes Resource type code (dots encode hierarchy)
title yes Display title
parent_code no Parent resource type code
description no Description
source no Overrides the function's _source parameter for this item
key_schema no Defines expected resource_id structure for this type
access_flags no Array of access flag codes valid for this type (e.g. ["read", "write"]); when omitted, all flags are allowed

Example:

select * from auth.ensure_resource_types('app', 1, 'setup', '[
    {"code": "project",           "title": "Project",
     "key_schema": {"project_id": "bigint"},
     "access_flags": ["read", "write", "delete", "share"]},
    {"code": "project.documents", "title": "Project Documents", "parent_code": "project",
     "key_schema": {"project_id": "bigint", "folder_id": "bigint"},
     "access_flags": ["read", "write", "delete", "export"]},
    {"code": "project.invoices",  "title": "Project Invoices",  "parent_code": "project",
     "key_schema": {"project_id": "bigint", "invoice_id": "bigint"},
     "access_flags": ["read", "approve", "export"]}
]'::jsonb, _source := 'my_app');

auth.get_resource_types

Lists registered resource types with optional filtering. No permission check is performed -- resource types are public metadata.

Parameter Type Default Description
_source text null Filter by source (e.g. 'projects_app')
_parent_code text null Filter by parent code (returns only direct children)
_active_only boolean true When true, only active types are returned

Returns: table(__code text, __title text, __full_title text, __description text, __is_active boolean, __source text, __parent_code text, __path ltree, __key_schema jsonb, __access_flags text[]) Permission required: None Source: 035_functions_resource_access.sql:1422

auth.ensure_access_flags

Bulk-ensure global access flags exist in const.resource_access_flag. Idempotent -- existing flags are not modified, new flags are created. Use this at application startup to register custom flags.

Parameter Type Default Description
_created_by text -- Actor identifier
_user_id bigint -- User performing the operation
_correlation_id text -- Correlation ID for tracing
_flags jsonb -- JSON array of objects, each with code (required) and title (required), optionally source
_source text null Default source for flags that don't specify their own
_tenant_id integer 1 Tenant context for permission check

Returns: table(__code text, __title text, __source text) -- the processed flags Permission required: resources.create_resource_type Source: 035_functions_resource_access.sql:1458

select * from auth.ensure_access_flags('app', _user_id, _correlation_id, '[
    {"code": "comment", "title": "Comment"},
    {"code": "subscribe", "title": "Subscribe"}
]'::jsonb, _source := 'my_app');

auth.ensure_resource_type_flags

Ensure a resource type has exactly the specified set of valid access flags. Adds missing flags and removes flags not in the list. All flags must exist in const.resource_access_flag.

Parameter Type Default Description
_created_by text -- Actor identifier
_user_id bigint -- User performing the operation
_correlation_id text -- Correlation ID for tracing
_resource_type text -- Resource type code
_access_flags text[] -- Desired set of flags. Empty array = clear all mappings (allows all flags). Null = no-op.
_tenant_id integer 1 Tenant context for permission check

Returns: table(__resource_type_code text, __access_flag_code text) -- the resulting flag mappings Permission required: resources.create_resource_type Source: 035_functions_resource_access.sql:1510

-- Set invoice to allow only read, write, approve
select * from auth.ensure_resource_type_flags('app', _user_id, _correlation_id,
    'invoice', array['read', 'write', 'approve']);

-- Remove all mappings (revert to "all flags allowed")
select * from auth.ensure_resource_type_flags('app', _user_id, _correlation_id,
    'invoice', array[]::text[]);

Use at application startup

Call ensure_resource_type_flags after ensure_resource_types to declaratively set which flags each type supports. The function is idempotent and handles both additions and removals, so the database always matches the application's flag definitions.

auth.get_access_flags

Lists all globally registered access flags. No permission check -- access flags are public metadata.

Parameter Type Default Description
_source text null Filter by source (e.g. 'my_app'). Null returns all flags.

Returns: table(__code text, __title text, __source text) Permission required: None Source: 035_functions_resource_access.sql:1548


Resource Role Management

Resource roles are named bundles of access flags scoped to a resource_type. Instead of granting N individual flags, assign one role that expands to flags at check time. Redefining a role's flags takes effect instantly for all assigned users.

auth.create_resource_role

Register a new resource role with its flags. Validates that all flags are valid for the role's resource_type (via const.resource_type_flag).

Parameter Type Default Description
_created_by text -- Creator identifier
_user_id bigint -- User performing the action (permission check)
_correlation_id text -- Correlation ID
_code text -- Unique role code (e.g., 'folder_editor')
_resource_type text -- Resource type this role is for
_title text -- Display title (stored as translation)
_description text null Description (stored as translation)
_access_flags text[] null Array of flag codes to include in the role
_source text null Source identifier for _is_final_state scoping
_tenant_id integer 1 Tenant for permission check
_language_code text 'en' Language for title/description translations

Returns: table(__code, __resource_type, __title, __description, __is_active, __source, __access_flags)

Permission required: resources.create_resource_type

Journal event: 18003 (resource_role_created)

Source: 046_drop_inline_titles.sql


auth.ensure_resource_roles

Bulk-ensure roles from a JSONB array. Upserts existing roles, creates missing ones. With _is_final_state = true, deactivates roles sharing the same _source that are not in the input.

Each element: {"code": "...", "resource_type": "...", "title": "...", "description": "...", "access_flags": ["read", "write"]}

Permission required: resources.create_resource_type

Source: 046_drop_inline_titles.sql


auth.assign_resource_role

Assign one or more roles to a user or group on a specific resource. Validates role exists, is active, and its resource_type matches the assignment's resource_type. Idempotent (existing assignments are updated, not duplicated).

Parameter Type Default Description
_created_by text -- Creator identifier
_user_id bigint -- User performing the action
_correlation_id text -- Correlation ID
_resource_type text -- Resource type
_resource_id jsonb -- Resource identifier
_target_user_id bigint null Target user (mutually exclusive with _user_group_id)
_user_group_id integer null Target group
_role_codes text[] null Array of role codes to assign
_tenant_id integer 1 Tenant scope

Returns: table(__resource_role_assignment_id, __role_code)

Permission required: resources.grant_access

Journal event: 18020 (resource_role_assigned)

Raises: 35009 if role's resource_type doesn't match assignment's resource_type.

Source: 044_functions_resource_roles.sql


auth.revoke_resource_role

Revoke specific roles (or all roles if _role_codes is null) from a user/group on a resource.

Permission required: resources.revoke_access

Journal event: 18021 (resource_role_revoked)

Source: 044_functions_resource_roles.sql


auth.get_resource_roles

List registered resource roles. Public metadata — no RBAC check. Reads titles from mv_translation.

Parameter Type Default Description
_source text null Filter by source
_resource_type text null Filter by resource type
_active_only boolean true Only active roles
_language_code text 'en' Language for translations

Returns: table(__code, __resource_type, __title, __description, __is_active, __source, __access_flags)

Source: 046_drop_inline_titles.sql


auth.get_resource_role_assignments

List all role assignments on a specific resource, with user/group info, role title (from translations), expanded flags, and grantor info.

Permission required: resources.get_grants

Source: 046_drop_inline_titles.sql


Validation Helpers

Internal helper functions in the unsecure schema used by the grant/deny/revoke functions. Not intended for direct application use.

unsecure.validate_resource_type

Validates that a resource type code exists in const.resource_type and is active. Throws error 35003 if not found.

Parameter Type Default Description
_resource_type text -- Resource type code to validate

Returns: void Permission required: None (internal helper) Source: 035_functions_resource_access.sql:39

unsecure.validate_access_flags

Validates that all access flags in an array exist in const.resource_access_flag. Throws error 35004 for the first invalid flag.

Parameter Type Default Description
_access_flags text[] -- Array of flag codes to validate

Returns: void Permission required: None (internal helper) Source: 035_functions_resource_access.sql:54

unsecure.validate_resource_id

Validates that a resource_id jsonb object contains all required keys defined in the resource type's key_schema. Called automatically by assign_resource_access and deny_resource_access. Throws error 35005 if a required key is missing.

Parameter Type Default Description
_resource_type text -- Resource type code (used to look up key_schema)
_resource_id jsonb -- Resource ID to validate

Returns: void Permission required: None (internal helper) Source: 034_tables_resource_access.sql:188

unsecure.validate_access_flags_for_type

Validates that all access flags in an array are valid for the given resource type, checking against const.resource_type_flag. If the resource type has no entries in resource_type_flag, all flags are allowed (backward compatible). Throws error 35006 for the first flag that is not valid for the type.

Parameter Type Default Description
_resource_type text -- Resource type code to validate against
_access_flags text[] -- Array of flag codes to validate

Returns: void Permission required: None (internal helper)

unsecure.update_resource_type_full_title

Recalculates the full_title column for a resource type and all its descendants. The full title is the ancestor titles joined by > (e.g. 'Project > Project Documents'). Called automatically by the trg_resource_type_full_title trigger.

Parameter Type Default Description
_path ltree -- ltree path of the resource type to update

Returns: void Permission required: None (internal helper) Source: 035_functions_resource_access.sql:1062


Error Codes

Code Function Description
35001 error.raise_35001 User has no access to the resource in the given tenant
35002 error.raise_35002 Either _target_user_id or _user_group_id must be provided
35003 error.raise_35003 Resource type does not exist or is not active
35004 error.raise_35004 Access flag does not exist
35005 error.raise_35005 Resource ID is missing a required key for the resource type's key_schema
35006 error.raise_35006 Access flag is not valid for resource type
35007 error.raise_35007 Resource role does not exist or is not active
35008 error.raise_35008 Role flag not valid for resource type
35009 error.raise_35009 Role resource_type mismatch at assignment

Required Permissions Summary

Function Permission Code
auth.has_resource_access None
auth.filter_accessible_resources None
auth.get_resource_access_flags None
auth.get_resource_access_matrix None
auth.assign_resource_access resources.grant_access
auth.deny_resource_access resources.deny_access
auth.revoke_resource_access resources.revoke_access
auth.revoke_all_resource_access resources.revoke_access
auth.get_resource_grants resources.get_grants
auth.get_user_accessible_resources resources.get_grants (unless querying self)
auth.create_resource_type resources.create_resource_type
auth.update_resource_type resources.create_resource_type
auth.ensure_resource_types resources.create_resource_type
auth.get_resource_types None
auth.ensure_access_flags resources.create_resource_type
auth.ensure_resource_type_flags resources.create_resource_type
auth.get_access_flags None
auth.create_resource_role resources.create_resource_type
auth.ensure_resource_roles resources.create_resource_type
auth.update_resource_role resources.create_resource_type
auth.delete_resource_role resources.create_resource_type
auth.ensure_resource_role_flags resources.create_resource_type
auth.get_resource_roles None
auth.get_resource_role_flags None
auth.assign_resource_role resources.grant_access
auth.revoke_resource_role resources.revoke_access
auth.revoke_all_resource_roles resources.revoke_access
auth.get_resource_role_assignments resources.get_grants

Journal Events

Code Event Logged By
18001 resource_type_created auth.create_resource_type, auth.ensure_resource_types
18002 resource_type_updated auth.update_resource_type
18003 resource_role_created auth.create_resource_role, auth.ensure_resource_roles
18004 resource_role_updated auth.update_resource_role, auth.ensure_resource_role_flags
18005 resource_role_deleted auth.delete_resource_role
18010 resource_access_granted auth.assign_resource_access
18011 resource_access_revoked auth.revoke_resource_access
18012 resource_access_denied auth.deny_resource_access
18013 resource_access_bulk_revoked auth.revoke_all_resource_access
18020 resource_role_assigned auth.assign_resource_role
18021 resource_role_revoked auth.revoke_resource_role, auth.revoke_all_resource_roles