Skip to content

64 Access Control

Преподобный Ален edited this page Feb 18, 2026 · 1 revision

Access Control

The platform implements a comprehensive three-layer access control system that governs visibility and operations at every level: which classes a user can see, which methods a user can invoke, and which specific objects a user can access. This page documents all three layers, how they interact, their key functions, and area-based scoping for documents.

Overview of the Three Layers

Layer Table Scope Bit Width Bits Purpose
ACU db.acu Class + User/Group 5 {a,c,s,u,d} Can the user see this class? Can they create/read/update/delete objects of this class?
AMU db.amu Method + User/Group 3 {x,v,e} Can the user execute this method? Is it visible? Is it enabled?
AOU db.aou Object + User 3 {s,u,d} Can the user select/update/delete this specific object?

Additionally, two supplementary tables provide default masks and method-level object overrides:

Table Scope Purpose
AOM (db.aom) Object UNIX-style default mask (9-bit: user/group/other x select/update/delete)
OMA (db.oma) Object + Method + User Per-object method-level access overrides

Layer 1: ACU (Class-User Access)

ACU controls which classes a user or group can interact with. It is the broadest layer, determining class-level visibility and permissions before any specific object is considered.

Bit Mask (5-bit: {a, c, s, u, d})

Bit Position Code Permission Description
4 (MSB) a access Can see that the class exists
3 c create Can create objects of this class
2 s select Can read/list objects of this class
1 u update Can edit objects of this class
0 (LSB) d delete Can delete objects of this class

Deny/Allow/Mask Computation

Like all access control tables, ACU uses a deny/allow/mask pattern:

mask = allow & ~deny
  • allow bits grant permissions
  • deny bits explicitly revoke permissions (deny takes precedence)
  • mask is the effective permission, computed automatically by the t_acu_before trigger

Default ACU on Class Creation

When a new class is created (inserted into db.class_tree), the t_class_tree_insert trigger automatically populates ACU entries:

Condition User/Group ACU Allow
Root class administrator group B'11111' (full access)
Root class apibot user B'01110' (create, select, update)
document subclass user group B'11000' (access + create)
reference subclass user group B'10100' (access + select)
message subclass mailbot user B'01110' (create, select, update)

Child classes inherit their parent's ACU entries via the trigger: if a parent has ACU records, those are copied to the new child class.

Key Functions

Function Returns Purpose
acu(pUserId) SETOF record(class, mask) All class masks for a user (aggregated across groups via bit_or)
acu(pUserId, pClass) SETOF record(class, mask) Mask for a specific class
GetClassAccessMask(pClass, pUserId) bit(5) Computed mask for class+user
CheckClassAccess(pClass, pMask, pUserId) boolean Test if user has the required mask bits
DecodeClassAccess(pClass, pUserId) record(a,c,s,u,d) Human-readable boolean breakdown
chmodc(pClass, pMask, pUserId, pRecursive, pPropagate) void Set ACU (admin only); optionally recursive (children) and propagate to existing objects
GetClassMembers(pClass) SETOF record All users/groups with ACU entries for a class

REST API

POST /api/v1/workflow/class/access        -- Get access info for a class
POST /api/v1/workflow/class/access/set    -- Set ACU for a class+user
POST /api/v1/workflow/class/access/list   -- List all ACU entries
POST /api/v1/workflow/class/access/decode -- Decode access as boolean fields

Layer 2: AMU (Method-User Access)

AMU controls which workflow methods a user can invoke. It is checked after ACU -- once the system confirms a user can see the class, AMU determines which actions they can perform.

Bit Mask (3-bit: {x, v, e})

Bit Position Code Permission Description
2 (MSB) x execute Can invoke the method
1 v visible Method appears in the UI
0 (LSB) e enable Method button is active/clickable

Default AMU on Method Creation

When a new method is created (inserted into db.method), the t_method_after_insert trigger automatically populates AMU entries based on the class's existing ACU:

  • If the method is visible: AMU allow = B'111' (execute + visible + enable)
  • If the method is hidden: AMU allow = B'101' (execute + enable, but not visible)

This ensures that any user who has class-level access will also have method-level access by default.

Key Functions

Function Returns Purpose
amu(pUserId) SETOF record(method, mask) All method masks for a user (aggregated across groups)
amu(pUserId, pMethod) SETOF record(method, mask) Mask for a specific method
GetMethodAccessMask(pMethod, pUserId) bit(3) Computed mask for method+user
CheckMethodAccess(pMethod, pMask, pUserId) boolean Test if user has the required mask bits
DecodeMethodAccess(pMethod, pUserId) record(x,v,e) Human-readable boolean breakdown
chmodm(pMethod, pMask, pUserId) void Set AMU (admin only)
GetMethodMembers(pMethod) SETOF record All users/groups with AMU entries for a method

The AccessMethod View

The AccessMethod view (kernel schema) filters methods by the current user's AMU permissions. It only returns methods where the user has the execute bit set. The api.method view is based on this, so API consumers only see methods they can actually invoke.

REST API

POST /api/v1/workflow/method/access        -- Get access info for a method
POST /api/v1/workflow/method/access/set    -- Set AMU for a method+user
POST /api/v1/workflow/method/access/list   -- List all AMU entries
POST /api/v1/workflow/method/access/decode -- Decode access as boolean fields

Layer 3: AOU (Object-User Access)

AOU provides per-object, per-user permissions. This is the most granular layer, allowing fine-grained control over who can see and modify individual objects.

Bit Mask (3-bit: {s, u, d})

Bit Position Code Permission Description
2 (MSB) s select Can view/read the object
1 u update Can modify the object
0 (LSB) d delete Can delete the object

AOM: Default Object Masks

When an object is created, the ft_object_after_insert trigger inserts a default AOM (Access Object Mask) record into db.aom. The AOM uses a UNIX-style 9-bit mask:

rwx rwx rwx
user group other

Where each rwx corresponds to select, update, delete. The default mask is typically B'111110100' (user: all, group: select+update, other: select).

The AOM serves as a fallback: when no specific AOU entry exists for a user+object pair, the system checks the AOM mask based on whether the user is the owner (user bits), a group member (group bits), or other (other bits).

AOU: User-Specific Overrides

The db.aou table allows per-object, per-user permissions that override the AOM defaults:

Column Purpose
object The object
userid The user (or group)
deny Bits to explicitly deny
allow Bits to explicitly allow
mask Computed: allow & ~deny (via trigger)

AOU entries are typically created automatically:

  • When an object is created, the owner gets full access (B'111')
  • For documents, area members may receive access entries
  • For messages, the mailbot and relevant users receive appropriate access

OMA: Object-Method-Level Access

The db.oma table provides the finest granularity: per-object, per-method, per-user permissions. It uses the same 3-bit mask as AMU ({x, v, e}).

OMA entries are auto-populated the first time CheckObjectMethodAccess is called for an object+method+user combination (lazy initialization from AMU).

Key Functions

Function Returns Purpose
aou(pUserId) SETOF record(object, mask) All objects accessible to user (aggregated across groups via bit_or)
aou(pUserId, pObject) SETOF record(object, mask) Mask for specific object+user
access_entity(pUserId, pEntity) SETOF record(object, mask) All accessible objects of an entity type
GetObjectAccessMask(pObject, pUserId) bit(3) Computed mask for object+user
CheckObjectAccess(pObject, pMask, pUserId) boolean Test permission (falls back to AOM if no AOU entry)
DecodeObjectAccess(pObject, pUserId) record(s,u,d) Human-readable boolean breakdown
chmodo(pObject, pMask, pUserId) void Set AOU from 6-bit mask (admin only). The 6 bits encode deny+allow: {deny_s, deny_u, deny_d, allow_s, allow_u, allow_d}
AccessObjectUser(pEntity, pUserId, pScope) TABLE(object uuid) All accessible object IDs in scope

Object Method Access Functions

Function Returns Purpose
GetObjectMethodAccessMask(pObject, pMethod, pUserId) bit(3) OMA mask for object+method+user
CheckObjectMethodAccess(pObject, pMethod, pMask, pUserId) boolean Check; auto-populates OMA from AMU on first check

REST API

POST /api/v1/object/access        -- Get access info for an object
POST /api/v1/object/access/set    -- Set AOU for an object+user
POST /api/v1/object/access/decode -- Decode access as boolean fields

How the Three Layers Interact

When a user performs an operation on an object, the system checks all three layers in sequence:

Step 1: Class Access (ACU)

Before any object-level operation, the system verifies that the user has the required class-level permission:

-- Can the user see objects of this class?
PERFORM CheckClassAccess(pClass, B'10000', pUserId);  -- 'a' (access) bit

-- Can the user create objects of this class?
PERFORM CheckClassAccess(pClass, B'01000', pUserId);  -- 'c' (create) bit

If the ACU check fails, the operation is denied immediately. The AccessObject view pre-filters objects by applying ACU checks, so users never see objects from classes they lack access to.

Step 2: Method Access (AMU)

When executing a method on an object, the system checks that the user has permission to invoke that method:

-- Can the user execute this method?
PERFORM CheckMethodAccess(pMethod, B'100', pUserId);  -- 'x' (execute) bit

The AccessMethod view filters methods by AMU, so the API only presents methods the user can actually execute.

Step 3: Object Access (AOU)

For per-object operations (select, update, delete), the system checks the user's specific permissions on that object:

-- Can the user view this object?
PERFORM CheckObjectAccess(pObject, B'100', pUserId);  -- 's' (select) bit

-- Can the user modify this object?
PERFORM CheckObjectAccess(pObject, B'010', pUserId);  -- 'u' (update) bit

The CheckObjectAccess function implements a fallback chain:

  1. Check db.aou for a direct user entry
  2. Check db.aou for entries matching the user's groups
  3. Fall back to db.aom default mask (user/group/other bits based on ownership)

Area-Based Scoping for Documents

Documents have an additional access dimension: the area. Each document belongs to an area (organizational unit), and users can only see documents in their assigned area and its sub-areas.

How Area Scoping Works

  1. Each user's profile includes an area field (set via SetSessionArea)
  2. Documents have an area column that determines their organizational scope
  3. The DocumentAreaTree view uses a recursive CTE to expand the area hierarchy
  4. The AccessDocument view intersects AOU permissions with area membership

Area Hierarchy

Areas form a tree:

root
+-- system
+-- guest
+-- all
    +-- {dbname} (default)
        +-- Branch A
        |   +-- Department A1
        |   +-- Department A2
        +-- Branch B

Users in "Branch A" can see documents in "Branch A", "Department A1", and "Department A2", but not "Branch B". Users in "all" can see everything.

Changing Document Area

The ChangeDocumentArea(pDocument, pArea) function moves a document (and its children) to a new area, recursively updating the area tree. This is exposed via the REST endpoint:

POST /api/v1/document/change/area

User ACL (Admin Module)

In addition to the three-layer entity access control, the admin module provides a separate ACL system for administrative operations. The db.acl table uses a 14-bit mask:

s L l E I D U C p d u c o i
Bit Permission
s Substitute user
L Unlock user
l Lock user
E Exclude from group
I Include in group
D Delete group
U Update group
C Create group
p Set password
d Delete user
u Update user
c Create user
o Logout
i Login

This ACL is checked by the admin module's functions (e.g., CheckAccessControlList) and is separate from the entity-level ACU/AMU/AOU system.

Clone this wiki locally