is_allowed
The full decision algorithm, step by step.
morbac.is_allowed(user_id, org_id, activity, view) is the single entry
point. This page specifies exactly what it does, in order. The uncached twin
is_allowed_nocache implements the algorithm; is_allowed adds the
cache in front.
Inputs are expanded first
- Roles:
get_comprehensive_roles(user, org)= direct + delegated + inherited + derived, minus negative assignments. - Activities:
get_effective_activities(a)= the activity plus every senior activity that implies it. - Views:
get_effective_views(v)= the view plus its hierarchy expansion. - Org scope: each org rule matches when
org_in_scope(asked_org, rule_org, rule_scope)holds, so subtree rules apply without copies.
The steps
Steps 1-3.5 collect the highest-priority applicable prohibition; steps
4-6.5 collect the highest-priority applicable permission. "Applicable"
means: matches the expanded roles/activities/views, its validity window
covers now (is_rule_valid), and its context
evaluates true. Within each store, candidates are visited in descending
priority and the first whose context passes wins that store.
| Step | Store | Notes |
|---|---|---|
| 1 | rules (prohibition) | org scope resolved via org_in_scope |
| 2 | cross_org_rules (prohibition) | role held in source_org_id, resource in target_org_id |
| 3 | user_rules (prohibition) | direct user bans in this org |
| 3.5 | global_rules (prohibition) | NULL columns are wildcards |
| 4 | rules (permission) | |
| 5 | cross_org_rules (permission) | |
| 6 | user_rules (permission) | |
| 6.5 | global_rules (permission) | the wildcard-admin path |
Step 7: priority resolution
no prohibition found -> allow iff any permission found
prohibition at priority P -> allow iff a permission has priority > PEqual priority denies: ties go to prohibition, and no rules at all means deny. The engine fails closed in both directions.
System principals
For users registered in morbac.system_principals, steps 1-3.5 are skipped
entirely: no prohibition of any kind can stop them. They still need a
permission from steps 4-6.5.
NULL org
Calling is_allowed(user, NULL, activity, view) evaluates only the global
stores (org, cross-org and user rules all require an org). This is how
global resources with no organization are authorized.
Debugging a decision
-- bypass the cache
SELECT morbac.is_allowed_nocache(:user, :org, 'read', 'prescriptions');
-- what roles did the engine see?
SELECT r.name, cr.source FROM morbac.get_comprehensive_roles(:user, :org) cr
JOIN morbac.roles r ON r.id = cr.role_id;
-- which rules could match?
SELECT * FROM morbac.rules
WHERE activity IN (SELECT activity FROM morbac.get_effective_activities('read'))
AND view IN (SELECT view FROM morbac.get_effective_views('prescriptions'));Every function used above, and the rest of the morbac API, is listed in the
function reference.