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
    Recipes

    Deny Rules

    Prohibitions, priorities, and suspending access without deleting grants.

    Situation. Olivier Fontaine, a billing-clerk at Clinique Valmont Nord, is under internal investigation. His access to invoices must stop today, but nothing about his account should be deleted: the investigation may clear him, and HR wants the grant history intact.

    Assumes the shared Valmont setup.

    Do not delete the grant, prohibit the action

    INSERT INTO morbac.user_roles (user_id, role_id, org_id)
    SELECT '66666666-6666-4666-8666-666666666666', r.id, r.org_id
    FROM morbac.roles r JOIN morbac.orgs o ON o.id = r.org_id
    WHERE o.name = 'Clinique Valmont Nord' AND r.name = 'billing-clerk'
    ON CONFLICT DO NOTHING;
     
    INSERT INTO morbac.rules (org_id, role_id, activity, view, context_id, modality)
    SELECT o.id, r.id, 'read', 'invoices',
           (SELECT id FROM morbac.contexts WHERE name = 'always'), 'permission'
    FROM morbac.orgs o JOIN morbac.roles r ON r.org_id = o.id AND r.name = 'billing-clerk'
    WHERE o.name = 'Clinique Valmont Nord'
    ON CONFLICT DO NOTHING;
     
    SELECT morbac.is_allowed('66666666-6666-4666-8666-666666666666',
        (SELECT id FROM morbac.orgs WHERE name = 'Clinique Valmont Nord'),
        'read', 'invoices');                                    -- true

    Suspend him with a user-level prohibition:

    INSERT INTO morbac.user_rules (user_id, org_id, activity, view, context_id, modality, priority)
    SELECT '66666666-6666-4666-8666-666666666666', o.id, 'read', 'invoices',
           (SELECT id FROM morbac.contexts WHERE name = 'always'), 'prohibition', 100
    FROM morbac.orgs o WHERE o.name = 'Clinique Valmont Nord'
    ON CONFLICT DO NOTHING;
     
    SELECT morbac.is_allowed('66666666-6666-4666-8666-666666666666',
        (SELECT id FROM morbac.orgs WHERE name = 'Clinique Valmont Nord'),
        'read', 'invoices');                                    -- false

    His colleague Nathalie Boucher, same role, is unaffected: the prohibition is bound to a user, not to billing-clerk.

    Why priority matters

    Priority defaults to 0 (NULL). Ties go to prohibition, so a bare prohibition already beats a bare permission. Setting 100 here makes the intent explicit and survives someone later adding a permission at priority 1.

    Choosing the right level of ban

    Scope of the banStoreExample
    this user, this orguser_rulesthe suspension above
    this user, every orgglobal_rules with user_id setcompany-wide lockout
    everyone in a role, this orgrulesfreeze billing during an audit
    everyone, everywhereglobal_rules with user_id NULLemergency stop on a view

    An emergency stop on a whole view, all orgs:

    INSERT INTO morbac.global_rules (user_id, activity, view, context_id, modality, priority)
    VALUES (NULL, NULL, 'invoices',
            (SELECT id FROM morbac.contexts WHERE name = 'always'), 'prohibition', 900)
    ON CONFLICT DO NOTHING;

    Remember it does not stop system principals, by design.

    A wildcard stop like this outranks every permission in every organization, so treat it as an incident tool and remove it as soon as the incident is over:

    DELETE FROM morbac.global_rules
    WHERE user_id IS NULL AND view = 'invoices' AND modality = 'prohibition';

    Lifting the ban

    DELETE FROM morbac.user_rules
    WHERE user_id = '66666666-6666-4666-8666-666666666666'
      AND modality = 'prohibition' AND view = 'invoices';

    Access returns instantly; the original role grant was never touched. Enable auditing to keep the paper trail:

    SELECT morbac.enable_audit('user_rules');
    PreviousService AccountsNext Separation of Duty
    pgmorbac - Multi-OrBAC permission engine for PostgreSQL
    DocumentationDownloadGitea