summaryrefslogtreecommitdiff
path: root/content/code/users-rolegraph-privs.md
diff options
context:
space:
mode:
authorOwen Jacobson <owen@grimoire.ca>2023-12-18 19:41:51 -0500
committerOwen Jacobson <owen@grimoire.ca>2024-01-03 03:05:23 -0500
commit5562e320736812d1ad309cfaf73383512a87858d (patch)
treed93569bd8831f4ea5b90719a61a9d1b217e76b0f /content/code/users-rolegraph-privs.md
parent27d5717529bf0e7d5806982f1970603bad998eaf (diff)
Migrate to Hugo.
This is a big and somewhat complicated decision, but the crux of it is this: The _mkdocs_ tool embeds a ton of "I am writing a manual" assumptions about document structure. These assumptions include that there is a single, sitewide TOC, that a top nav bar is broadly an appropriate way to skip around in the document, and numerous others. They serve that use case well, but that's not really what this site _is_, or how I intend it to be approached. I'm trying for something more blog-esque (and deliberately a bit haphazard). Hugo is an experiment. This commit migrates most pages to it, but it does drop a few; this is a convenient excuse to forget items I'd prefer not to continue publishing.
Diffstat (limited to 'content/code/users-rolegraph-privs.md')
-rw-r--r--content/code/users-rolegraph-privs.md95
1 files changed, 95 insertions, 0 deletions
diff --git a/content/code/users-rolegraph-privs.md b/content/code/users-rolegraph-privs.md
new file mode 100644
index 0000000..b93380f
--- /dev/null
+++ b/content/code/users-rolegraph-privs.md
@@ -0,0 +1,95 @@
+---
+title: A Users, Roles & Privileges Scheme Using Graphs
+date: 2013-02-06T12:29:39-0500
+---
+
+An SQL schema and associated queries for handling permissions when roles can nest arbitrarily.
+
+<!--more-->
+
+The basic elements:
+
+* Every agent that can interact with a system is represented by a **user**.
+* Every capability the system has is authorized by a distinct **privilege**.
+* Each user has a list of zero or more **roles**.
+ * Roles can **imply** further roles. This relationship is transitive: if role A implies role B, then a member of role A is a member of role B; if role B also implies role C, then a member of role A is also a member of role C. It helps if the resulting role graph is acyclic, but it's not necessary.
+ * Roles can **grant** privileges.
+
+A user's privileges are the union of the privileges granted by the transitive closure of their roles.
+
+```sql
+create table "user" (
+ username varchar
+ primary key
+ -- credentials &c
+);
+
+create table role (
+ name varchar
+ primary key
+);
+
+create table role_member (
+ role varchar
+ not null
+ references role,
+ member varchar
+ not null
+ references "user",
+ primary key (role, member)
+);
+
+create table role_implies (
+ role varchar
+ not null
+ references role,
+ implied_role varchar
+ not null
+);
+
+create table privilege (
+ privilege varchar
+ primary key
+);
+
+create table role_grants (
+ role varchar
+ not null
+ references role,
+ privilege varchar
+ not null
+ references privilege,
+ primary key (role, privilege)
+);
+```
+
+If your database supports recursive CTEs, this schema can be queried in one shot, since we can have the database do all the graph-walking along roles:
+
+```sql
+with recursive user_roles (role) AS (
+ select
+ role
+ from
+ role_member
+ where
+ member = 'SOME USERNAME'
+ union
+ select
+ implied_role as role
+ from
+ user_roles
+ join role_implies on
+ user_roles.role = role_implies.role
+)
+select distinct
+ role_grants.privilege as privilege
+from
+ user_roles
+ join role_grants on
+ user_roles.role = role_grants.role
+order by privilege;
+```
+
+If not, you'll need to pull the entire graph into memory and manipulate it there: this schema doesn't give you any easy handles to identify only the roles transitively included in the role of interest, and repeatedly querying for each step of the graph requires an IO roundtrip at each step, burning whole milliseconds along the way.
+
+Realistic use cases should have fairly simple graphs: elemental privileges are grouped into concrete roles, which are in turn grouped into abstracted roles (by department, for example), which are in turn granted to users. If the average user is in tens of roles and has hundreds of privileges, the entire dataset fits in memory, and PostgreSQL performs well. In PostgreSQL, the above schema handles ~10k privileges and ~10k roles with randomly-generated graph relationships in around 100ms on my laptop, which is pretty slow but not intolerable. Perverse cases (interconnected total subgraphs, deeply-nested linear graphs) can take absurd time but do not reflect any likely permissions scheme.