Self-service spaces: create, own, and manage member access from the web UI #94

Closed
opened 2026-08-03 20:30:00 +00:00 by Cordy · 3 comments
Owner

Dogfood finding (Nikola, 2026-08-03): there is no way to create a space from Cairn — spaces only mirror IdP groups, so "create case folder 1 and give my colleagues access" currently requires the Keycloak admin console. Unacceptable UX for the target persona (records clerk / team lead).

Requirements (Nikola's spec):

  • "Spaces" in the left nav below Personal (nav exists; management doesn't).
  • Any user can create a space (e.g. "case folder 1"); the creator is its owner.
  • The owner grants access to users and/or user groups, as read or read + write, and can revoke.

Design decision needed — two routes:

A. Broker to the IdP (recommended). Cairn's UI creates/manages Keycloak groups through the Admin API using the existing cairn-keycustody service account (already holds manage-users; #18 extended it to groups for key custody). Mapping: read+write = membership of group <space>; read-only = membership of <space>-ro (the #64 convention, already enforced by scope); owner = group attribute (e.g. cairnSpaceOwner: <username>). Keycloak/AD stays the single source of truth — preserves the "identity is consumed, not implemented" thesis (§5.4), keeps the LDAP/AD deployment story, and grant/revoke history lives in the IdP where compliance expects it. New admin-API-backed endpoints in cairnd: create space, list/add/remove members, set role, transfer ownership; UI on the Spaces view.

  • Known v1 caveat: OIDC groups ride in the session, so membership changes take effect at the member's next login/session refresh (12h TTL), not instantly. Document it; a later enhancement can do live group resolution.
  • Group-key custody (#18) needs no changes — a new space's key lazy-provisions on first write.

B. In-app membership store. Faster to build, but forks authorization into a second system parallel to the IdP, breaks AD/LDAP parity, and contradicts the documented architecture. Listed for completeness; recommended against.

Out of scope here: the shared per-space trash — split into its own issue since it is independently shippable and fixes a #18 wart.

Blocked on: Nikola choosing the route (A/B) before any code.

Dogfood finding (Nikola, 2026-08-03): there is no way to create a space from Cairn — spaces only mirror IdP groups, so "create case folder 1 and give my colleagues access" currently requires the Keycloak admin console. Unacceptable UX for the target persona (records clerk / team lead). **Requirements (Nikola's spec):** - "Spaces" in the left nav below Personal (nav exists; management doesn't). - Any user can create a space (e.g. "case folder 1"); the creator is its **owner**. - The owner grants access to users and/or user groups, as **read** or **read + write**, and can revoke. **Design decision needed — two routes:** **A. Broker to the IdP (recommended).** Cairn's UI creates/manages Keycloak groups through the Admin API using the existing `cairn-keycustody` service account (already holds manage-users; #18 extended it to groups for key custody). Mapping: read+write = membership of group `<space>`; read-only = membership of `<space>-ro` (the #64 convention, already enforced by scope); owner = group attribute (e.g. `cairnSpaceOwner: <username>`). Keycloak/AD stays the single source of truth — preserves the "identity is consumed, not implemented" thesis (§5.4), keeps the LDAP/AD deployment story, and grant/revoke history lives in the IdP where compliance expects it. New admin-API-backed endpoints in cairnd: create space, list/add/remove members, set role, transfer ownership; UI on the Spaces view. - **Known v1 caveat:** OIDC groups ride in the session, so membership changes take effect at the member's next login/session refresh (12h TTL), not instantly. Document it; a later enhancement can do live group resolution. - Group-key custody (#18) needs no changes — a new space's key lazy-provisions on first write. **B. In-app membership store.** Faster to build, but forks authorization into a second system parallel to the IdP, breaks AD/LDAP parity, and contradicts the documented architecture. Listed for completeness; recommended against. **Out of scope here:** the shared per-space trash — split into its own issue since it is independently shippable and fixes a #18 wart. Blocked on: Nikola choosing the route (A/B) before any code.
Author
Owner

Decision (Nikola, 2026-08-03): Route A — spaces broker to the IdP; Keycloak stays the single source of truth. The session-staleness caveat is split out as #96, sequenced after #95. Work order: #95 (shared trash) → #96 (live group resolution) → this issue's UI + endpoints.

**Decision (Nikola, 2026-08-03): Route A** — spaces broker to the IdP; Keycloak stays the single source of truth. The session-staleness caveat is split out as #96, sequenced after #95. Work order: #95 (shared trash) → #96 (live group resolution) → this issue's UI + endpoints.
Author
Owner

Re-sequenced (Nikola, 2026-08-03): this issue moves ahead of #96. Manual Keycloak group creation was tried and confirmed as the wrong UX — and it also exposed a discovery quirk: an admin user is unscoped, so a group-backed space is invisible to them until the backend /spaces/<name> directory exists (non-admins get it synthesized by scope). Self-service creation will create the backend directory at space creation, eliminating the confusion. Greenlit — building route A v1 now: create space (creator = owner via group attribute), owner manages members as read or read+write (<name> / <name>-ro groups), all brokered through the Keycloak Admin API. Session staleness remains the documented v1 trade until #96.

**Re-sequenced (Nikola, 2026-08-03): this issue moves ahead of #96.** Manual Keycloak group creation was tried and confirmed as the wrong UX — and it also exposed a discovery quirk: an admin user is unscoped, so a group-backed space is invisible to them until the backend `/spaces/<name>` directory exists (non-admins get it synthesized by scope). Self-service creation will create the backend directory at space creation, eliminating the confusion. Greenlit — building route A v1 now: create space (creator = owner via group attribute), owner manages members as read or read+write (`<name>` / `<name>-ro` groups), all brokered through the Keycloak Admin API. Session staleness remains the documented v1 trade until #96.
Author
Owner

Shipped in v0.3.27 (PR #98), live on .249.

How it works

  • Go to SpacesNew folder → the name becomes a space: Cairn creates Keycloak groups <name> (write) + <name>-ro (read-only, #64), records you as owner (group attribute cairnSpaceOwner), joins you to the write group, and creates the backend /spaces/<name> directory — which also fixes what you saw in the manual test: admins are unscoped and saw no /spaces because nothing existed on the raw backend.
  • At a space's root, the owner (or an admin) sees a Members button: add by username, flip read-only ↔ read+write (moves between the two groups, never both), remove. Owner can't be demoted or removed.
  • Keycloak stays the source of truth (route A) — access changes apply at each member's next sign-in until #96; the UI says so.

Dogfood checklist (nikola-test)

  1. Spaces → New folder → e.g. case-folder-1 → expect the created toast. As admin you should see it immediately (backend dir now exists).
  2. Check Keycloak: groups case-folder-1 + case-folder-1-ro, owner attribute, nikola-test in the write group.
  3. Members button at /spaces/case-folder-1 → add a second user as read-only → that user re-logs in → sees the space, can read, gets 403 on writes, and sees (but can't restore from) the space trash.
  4. Upload + delete a file in the space → lands in the space trash (#95 behavior unchanged).

Possible snag: if space creation returns 502 and the pod log shows a Keycloak 403 on POST /groups, the cairn-keycustody service account needs group-creation rights — extend its realm-management roles (query-groups + manage-users at minimum; some versions gate group creation behind manage-realm). That's a Keycloak admin-console change on .248, your side.

Closing as shipped; reopen or file follow-ups if dogfood disagrees.

Shipped in **v0.3.27** (PR #98), live on .249. **How it works** - Go to **Spaces** → **New folder** → the name becomes a space: Cairn creates Keycloak groups `<name>` (write) + `<name>-ro` (read-only, #64), records you as owner (group attribute `cairnSpaceOwner`), joins you to the write group, and creates the backend `/spaces/<name>` directory — which also fixes what you saw in the manual test: admins are unscoped and saw no `/spaces` because nothing existed on the raw backend. - At a space's root, the owner (or an admin) sees a **Members** button: add by username, flip read-only ↔ read+write (moves between the two groups, never both), remove. Owner can't be demoted or removed. - Keycloak stays the source of truth (route A) — access changes apply at each member's next sign-in until #96; the UI says so. **Dogfood checklist (nikola-test)** 1. Spaces → New folder → e.g. `case-folder-1` → expect the created toast. As admin you should see it immediately (backend dir now exists). 2. Check Keycloak: groups `case-folder-1` + `case-folder-1-ro`, owner attribute, nikola-test in the write group. 3. Members button at `/spaces/case-folder-1` → add a second user as read-only → that user re-logs in → sees the space, can read, gets 403 on writes, and sees (but can't restore from) the space trash. 4. Upload + delete a file in the space → lands in the space trash (#95 behavior unchanged). **Possible snag:** if space creation returns 502 and the pod log shows a Keycloak 403 on `POST /groups`, the `cairn-keycustody` service account needs group-creation rights — extend its realm-management roles (query-groups + manage-users at minimum; some versions gate group creation behind manage-realm). That's a Keycloak admin-console change on .248, your side. Closing as shipped; reopen or file follow-ups if dogfood disagrees.
Cordy closed this issue 2026-08-04 09:57:33 +00:00
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: Cordy/Cairn#94
No description provided.