Memberships
Who belongs to which group, kept by portal itself.
A site’s groups are named in its content: qualifies on a page,
requires_group on a list, invite: { group } on a desk, grants on
a state. Until now the groups themselves lived in Kanidm, and portal
read them from the sign-in token. With memberships they live in portal,
as records in the built-in portal_memberships bucket, and the
identity provider only has to say who someone is. Cedar sees no
difference: a person’s active memberships are the parents of their
User entity, exactly as a groups claim was.
A membership is a case
One record per group and person:
| state | means | how it gets there |
|---|---|---|
invited | a place is held for them | a desk invite, a state that grants, the first person, someone the content names as responsible |
active | they are in | they signed in, and the provider’s subject, phone number or address matched the record |
removed | a desk took them out | a button on the desk that lists the bucket; inviting them again reopens the record |
The record carries name, group, by (who or what put them there:
a username, seed, responsible, or the case as bucket:id), and
what is known of the person: phone, email, sub. At sign-in
everything the provider verified is written onto every record that
matched, so a record made from an address learns the phone number,
and the next sign-in matches on the subject before anything else.
Because it is a case, a desk lists it like any other bucket:
resource:
source: { kind: kv, bucket: portal_memberships }
requires_group: board
transitions:
- { from: invited, to: removed, label: Withdraw }
- { from: active, to: removed, label: Take out }
- { from: removed, to: invited, label: Invite again }
A person is their phone number first
A provider that verifies phone numbers (Vipps) makes the number the
surest thing a record can hold. So a person is known by, in order:
their phone number, their email address, the provider’s subject. A
form that collects phone, telefon, tel or mobil gives the
first; email or epost the second. Numbers are kept as +4791234567
whatever way they were typed; a bare national number gets the site’s
country (PHONE_COUNTRY, +47 unless set). Addresses are lowercased.
A record matches a person on any one of the three, so a desk that invited by address and a decision that granted by number are talking about one person, and there is one record.
Switching a site to memberships
PEOPLE_BACKEND=memberships
Unset, a KANIDM_API_TOKEN means Kanidm and no token means
memberships. With memberships:
- A desk invite writes an
invitedrecord and mails “log in at the site, and your place is there”. The provider makes people, portal makes members: with Vipps Login everyone can already sign in. A Kanidm still configured beside memberships (KANIDM_API_TOKEN) makes the account too, with no group and the one-time link in the mail, so a site can switch to memberships before its provider does. grantswrites a record for the person the case is about, from the form’s name and phone number or address.SEED_ADMIN_PHONEorSEED_ADMIN_EMAILwrites the first person’s.- The people the content names as responsible get theirs in
RESPONSIBLE_GROUP, and lose it when the content stops naming them. - The
groupsclaim, if the provider sends one, still counts: a person’s groups are the union.
With OIDC_EXTRA_SCOPES=phoneNumber the sign-in asks the provider for
the number and reads phone_number from userinfo. Set it for Vipps;
not for Kanidm, which denies a sign-in that asks for a scope the
person does not hold. Without it, matching falls back to the address.
Asking who belongs
Other applications ask portal the same access question they always
could, over NATS on portal.access.<site>.may. A principal that names
a person and lists no groups is looked up:
{ "principal": { "kind": "user", "sub": "vipps:abc", "phone": "912 34 567" },
"action": "transition",
"resource": { "kind": "bucket", "name": "hazards" },
"context": { "from": "open", "to": "fixed" } }
The answer is Cedar’s, with the rule ids that granted it, for a person who may never have opened the site. That is the whole authorization engine: the provider says who, memberships say what they belong to, the policy compiled from content says what they may do.
What Kanidm still does
For a site on Kanidm nothing changes: invites make accounts and add to
Kanidm groups, and portal_invites tracks the one-time links. The two
backends can be swapped by the one variable; the content and the
policy are the same either way.