Quick start
From empty database to an enforced permission in five minutes.
From an empty database to an enforced permission in about five minutes. Every step is plain SQL; there is nothing to compile and no service to run.
1. Install the schema
unzip pgmorbac-0.1.0.zip && cd pgmorbac-0.1.0
make install # uses pg_config / PGXSCREATE EXTENSION pgmorbac;No filesystem access to the server (managed PostgreSQL)? Run the script directly instead; it produces the identical schema:
psql -v ON_ERROR_STOP=1 -d mydb -f pgmorbac--0.1.0.sqlRunning a Node.js backend? The @crudy/pgmorbac npm package ships the same
SQL, so a migration runner can apply it at boot instead of shelling out to
psql:
npm install @crudy/pgmorbacSELECT morbac.get_config('schema_version'); -- 0.1.02. Describe your organizations
INSERT INTO morbac.orgs (name) VALUES ('Groupe Hospitalier Valmont');
INSERT INTO morbac.orgs (name, parent_id)
SELECT 'Clinique Valmont Nord', id FROM morbac.orgs
WHERE name = 'Groupe Hospitalier Valmont';3. Declare what exists and who acts
Activities are actions, views are categories of resources, roles are scoped to an organization:
INSERT INTO morbac.views (name) VALUES ('patients') ON CONFLICT DO NOTHING;
INSERT INTO morbac.roles (org_id, name)
SELECT id, 'nurse' FROM morbac.orgs WHERE name = 'Clinique Valmont Nord';
INSERT INTO morbac.user_roles (user_id, role_id, org_id)
SELECT '22222222-2222-4222-8222-222222222222', 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 = 'nurse';read, create, update and delete already exist; add your own domain
verbs whenever an action deserves one.
4. Write the rule
INSERT INTO morbac.rules (org_id, role_id, activity, view, context_id, modality, scope)
SELECT o.id, r.id, 'read', 'patients',
(SELECT id FROM morbac.contexts WHERE name = 'always'),
'permission', 'subtree'
FROM morbac.orgs o
JOIN morbac.roles r ON r.org_id = o.id AND r.name = 'nurse'
WHERE o.name = 'Clinique Valmont Nord';5. Ask
SELECT morbac.is_allowed(
'22222222-2222-4222-8222-222222222222',
(SELECT id FROM morbac.orgs WHERE name = 'Clinique Valmont Nord'),
'read', 'patients'); -- true
SELECT morbac.is_allowed(
'22222222-2222-4222-8222-222222222222',
(SELECT id FROM morbac.orgs WHERE name = 'Groupe Hospitalier Valmont'),
'read', 'patients'); -- falseThat second answer is the point: nothing is granted implicitly, and a
subtree scope points downward, never up.
6. Let the database enforce it
Answering questions is useful; enforcing them per row is the reason to keep authorization in the database:
ALTER TABLE app.patients ENABLE ROW LEVEL SECURITY;
CREATE POLICY patients_select ON app.patients FOR SELECT
USING (morbac.rls_check('read', 'patients', org_id));
GRANT USAGE ON SCHEMA morbac TO my_app_role;
GRANT EXECUTE ON FUNCTION morbac.rls_check(TEXT, TEXT, UUID, UUID) TO my_app_role;Then set the acting user for each request, transaction-locally:
SELECT set_config('morbac.user_id', $1, true),
set_config('morbac.org_id', $2, true);Now SELECT * FROM app.patients returns only the rows that user may see, from
any client.
Where to go next
- How do I...? - the situation catalogue, searchable and filterable. Start here if you have a concrete problem.
- How pgmorbac compares - RBAC, ABAC, ReBAC, PBAC, and where this fits.
- Concepts - the model, one page per idea.
- Upgrading - version stamps and upgrade scripts.
What just got created
The morbac schema only: organizations, roles and assignments, activities and
views with their hierarchies, contexts, four rule stores, constraint tables,
the decision cache and the is_allowed family. Nothing outside it is touched.