Skip to content
🛡️ adminadmin@example.com · Domain admin in Acme Corp

Chapter 5 — Access through groups

The chapter in one line

Access is granted to roles, not to people.

The next six slides prove it by granting nothing to a user and watching the user gain access anyway.

A Project to share

bash
PROJECT=$(plexctl project create \
  --domain "$DOMAIN_ID" \
  --slug group-demo \
  --display-name "Group Demo" \
  --output json)
PROJECT_ID=$(echo "$PROJECT" | jq -r '.id')
echo "$PROJECT" | jq '{id, domain_id, slug, name}'

Establish the baseline — you have nothing

bash
plexctl rebac check \
  --subject "user:$USER_ID" \
  --relation maintainer \
  --resource "project:$PROJECT_ID"
text
plexctl: rebac check: denied (reason=insufficient_relation, correlation_id=f8da0886-3ddc-430e-9f89-9a1778037a63)

You created this Project moments ago and you are the Domain admin, and you still hold no maintainer on it.

Creating a thing does not silently grant you every relation on it.

A Group, and you in it

bash
GROUP=$(plexctl group create \
  --domain "$DOMAIN_ID" \
  --slug group-demo-operators \
  --display-name "Group Demo Operators" \
  --source manual \
  --output json)
GROUP_ID=$(echo "$GROUP" | jq -r '.id')
echo "$GROUP" | jq '{id, domain_id, slug, display_name, source}'
bash
plexctl group member add \
  --group "$GROUP_ID" \
  --principal "$USER_ID" \
  --kind user \
  --source manual

Still nothing granted. The Group holds no relation on anything.

The key move

bash
plexctl rebac tuple add \
  --project "$PROJECT_ID" \
  --resource "project:$PROJECT_ID" \
  --relation maintainer \
  --subject "group:$GROUP_ID#member"

Read the subject: group:<id>#member.

Not the Group object — the Group's member set. "Everyone who is a member of this Group holds maintainer on this Project."

That #member suffix is the whole idea.

The same check, a different answer

bash
plexctl rebac check \
  --subject "user:$USER_ID" \
  --relation maintainer \
  --resource "project:$PROJECT_ID"
text
DECISION  REASON  CORRELATION_ID
allowed           788330f3-d36e-4577-b187-eab964129c6e

Character for character the command from three slides ago. denied became allowed, and nothing was granted to you.

Onboarding the next joiner is one group member add — not one tuple per Project.

What can this subject reach

bash
plexctl rebac lookup-resources \
  --subject "user:$USER_ID" \
  --relation maintainer \
  --resource-type project
text
OBJECT
019ecc67-2b3c-7d4e-8f50-1a2b3c4d5e6f

Who can reach this resource

bash
plexctl rebac lookup-subjects \
  --resource "project:$PROJECT_ID" \
  --relation maintainer \
  --subject-type user
text
OBJECT
019ecc65-9f3a-7c14-8d2b-5f3e0a7b1c40

Neither query mentions the Group.

The engine has already expanded membership into concrete subjects and resources — which is exactly the shape an access review needs. Not "who is in which group", but "who can actually reach this".

Leave the Group

bash
plexctl group member remove \
  --group "$GROUP_ID" \
  --principal "$USER_ID" \
  --kind user
bash
plexctl rebac check \
  --subject "user:$USER_ID" \
  --relation maintainer \
  --resource "project:$PROJECT_ID"
text
plexctl: rebac check: denied (reason=insufficient_relation, correlation_id=f8da0886-3ddc-430e-9f89-9a1778037a63)

The Project's grant never changed. You stopped being a member of the thing it was granted to.

Revoking a leaver across every Project a Group governs is one command.