pgmorbacMulti-OrBAC for PostgreSQL
    DocumentationDownloadSource
    Use cases
    • How do I...?
    Getting Started
    • Introduction
    • Quick start
    • How pgmorbac compares
    • Upgrade
    Concepts
    • Organizations
    • Roles
    • Activities
    • Views
    • Contexts
    • Rules & Modalities
    • Global Rules
    • System Principals
    • Constraints
    • Row-Level Security
    • Decision Cache
    Resolution
    • is_allowed
    Recipes
    • Org Hierarchies
    • Multi-Tenant Isolation
    • Wildcard Admin
    • Delegation
    • Service Accounts
    • Deny Rules
    • Separation of Duty
    • Derived Roles
    • Cross-Org Access
    • Obligations & Recommendations
    • Temporal Access
    Integration
    • Fastify
    • PostgREST & RLS Context
    • SQL Only
    Reference
    • Function Reference
    Concepts

    Constraints

    Separation of duty, cardinality, negative assignments, derived roles.

    Four constraint tables shape who can hold which roles, upstream of any rule evaluation.

    Negative role assignments

    An explicit "never this role for this user", stronger than every positive path: direct assignment, delegation and derivation are all filtered against it.

    INSERT INTO morbac.negative_role_assignments (user_id, role_id, org_id, reason)
    VALUES (:olivier, :billing-clerk_id, :nord_id, 'Under investigation');

    Even if someone re-assigns Olivier the role tomorrow, or a colleague delegates it to him, his effective role set never contains it.

    Separation of duty (SoD)

    morbac.sod_conflicts declares mutually exclusive role pairs per org. At Valmont, the person who writes a prescription must not be the person who fills it:

    INSERT INTO morbac.sod_conflicts (role_a_id, role_b_id, org_id, description)
    VALUES (:physician_id, :pharmacist_id, :nord_id,
            'Prescriber and dispenser must differ');

    assign_role() checks the table and raises on violation. The SoD recipe shows the full flow.

    Role cardinality

    Bounds on how many users may hold a role:

    INSERT INTO morbac.role_cardinality (role_id, min_users, max_users, description)
    VALUES (:auditor_id, NULL, 2, 'At most two auditors at Group level');

    assign_role() rejects the third auditor. min_users is informational (reported by check_cardinality_violation); the engine cannot conjure a missing user.

    Derived roles

    Membership computed at decision time instead of stored. The role exists normally and carries rules normally; morbac.derived_roles attaches a predicate function(user_id, org_id) returns boolean:

    CREATE OR REPLACE FUNCTION app.is_department_head(p_user UUID, p_org UUID)
    RETURNS BOOLEAN LANGUAGE sql STABLE AS $$
        SELECT EXISTS (SELECT 1 FROM app.departments s
                       WHERE s.head_user_id = p_user AND s.org_id = p_org)
    $$;
     
    INSERT INTO morbac.derived_roles (role_id, condition_evaluator, description)
    VALUES (:department_head_role_id, 'app.is_department_head'::regproc,
            'Computed from the services table');

    Whoever the services table says is head is department-head, with no assignment to forget to revoke. Negative assignments still win over derivation. Full example: derived roles recipe.

    PreviousSystem PrincipalsNext Row-Level Security
    pgmorbac - Multi-OrBAC permission engine for PostgreSQL
    DocumentationDownloadGitea