Files

69 lines
2.8 KiB
Markdown
Raw Permalink Normal View History

2026-09-25 21:35:25 +02:00
# Access
Who may do what to what is compiled from content on every load into
one Cedar policy set, and every server entry point asks that one
policy. Identity stays Kanidm's; portal only decides.
## Where the groups come from
The policy names groups; it does not hold them. A `User` entity's
parents are the person's groups, and those come from the sign-in: the
`groups` claim the provider sent, joined with the person's active
[memberships](memberships.md) when portal keeps them itself. Cedar
sees one list either way.
2026-09-25 21:35:25 +02:00
## What content compiles to
| content | rule |
|---|---|
| a page with no `qualifies` | anyone may open and submit |
| `qualifies: case_officers` | that group may open and submit |
| `requires_chain: /start` | the visitor's verified answer lineage must reach it |
| a resource with `public: true` | anyone may read it |
| a resource with `requires_group: g` | that group may read it |
| a desk resource's `transitions` | that group may make those moves |
| an alternative's `self_transition` | the submitter may make it, from each state in `from`, with their link and their matching email |
| an alternative's `invite: {group}` | the page's own group may invite into that group |
Mutations always need a group, whatever `public` says. A resource with
neither `public` nor `requires_group` is unreachable rather than open:
the policy fails closed.
## Reading the policy
```sh
iris check --path questions --access
```
prints every rule the content compiled to, as a table of who may do
what from where to where. It is the fastest way to find a rule a
content repo did not mean to write — a refusal available on the day a
case arrives, a board that can decide a case it never received.
## Asking it
Every decision carries the rule ids that granted it. Refusals and
every mutation are published on `portal.access.decided`.
Other applications ask the same question over NATS request-reply on
`portal.access.<site>.may`, with the same principal, action, resource
and context the server uses internally — so an automation acting as a
group gets exactly the answer a person in that group would.
A principal may also name a person by `sub`, `phone` or `email` and
list no groups; portal then looks their [memberships](memberships.md)
up before deciding. That is what makes the policy usable for someone
who has never opened the site.
2026-09-25 21:35:25 +02:00
## The submitter
A person who sent a form has no account. Their credential is the link:
an unguessable chain hash, plus the email they gave, which the policy
compares against the one stored on the record.
What they may do, and from where, is `self_transition.from`. Its
default is the initial state alone. A refusal reads "not found" while
the record is somewhere the move could have been made from — so the
endpoint cannot be used to discover which records or addresses exist —
and "already processed" once the case has moved past it.