Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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:

statemeanshow it gets there
inviteda place is held for thema desk invite, a state that grants, the first person, someone the content names as responsible
activethey are inthey signed in, and the provider’s subject, phone number or address matched the record
removeda desk took them outa 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 invited record 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.
  • grants writes a record for the person the case is about, from the form’s name and phone number or address.
  • SEED_ADMIN_PHONE or SEED_ADMIN_EMAIL writes 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 groups claim, 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.