Lifecycle cleanups: M-1 mapping revoke, M-3 visibility warning, M-4 demotion sweep #111

Merged
qwc merged 1 commit from fix/lifecycle-cleanups into main 2026-05-18 22:38:40 +02:00
Owner

Summary

Workstream item #16. Three audit findings under the lifecycle / cascade-gap theme.

M-1 — group-mapping deletion left stale ProjectAccess

Dropping an LDAP/OAuth2 group mapping previously removed the mapping row but left the project_access rows that were granted via that source untouched. Until the affected user's next login (where syncProjectAccess would have revoked the dangling row), they retained access — possibly indefinitely if they never logged in again.

  • New ProjectAccessStore.RevokeProjectBySource(projectID, source).
  • handleAdminDeleteGroupMapping calls it after Delete. Surviving mappings that still cover the same (project, source) re-grant on the user's next login, so the dangling-grant window is bounded.

M-3 — public → private/custom visibility flip leaves users locked out silently

Editors who had implicit access to a public project lose it the moment the project is restricted, with no signal to the admin doing the change.

  • handleAdminUpdateProject records the previous visibility. On a public → non-public transition it appends ?msg=visibility_restricted to the redirect.
  • handleAdminProjects renders the new flash: "Project visibility changed from public — review project access so the intended users still have access."

M-4 — editor demotion left tokens and manual editor grants in place

A demoted user with leftover API tokens + their original auto-grant could still upload to projects via canUpload's project-grant branch despite the role change.

  • New ProjectAccessStore.RevokeManualEditorByUser(userID).
  • handleAdminUpdateUserRole: when the role transitions away from admin/editor to viewer, revoke the user's manual editor grants and delete all of their API tokens.

Tests

Four regression tests in lifecycle_cleanup_test.go:

  • TestDeleteGroupMappingRevokesProjectAccess — pre-seeded oauth2-source access is gone after mapping delete
  • TestVisibilityRestrictionFlagsFlash — public→private redirect carries the flash; private→private edit does not
  • TestDemotionRevokesManualEditorGrantsAndTokens — editor→viewer revokes both
  • TestPromotionDoesNotRevokeGrants — sanity: viewer→editor leaves existing grants alone

setupTestApp gained GroupMappings in Deps (it was missing, which the M-1 test caught by panicking on the nil store).

Test plan

  • go test ./... passes
  • Manual: create an LDAP/OAuth2 group mapping, give a user access via login, delete the mapping → next page load they should lose access
  • Manual: flip a public project to private in the admin UI → confirm the warning flash appears
  • Manual: demote an editor with at least one token to viewer → confirm /admin/robots (or wherever their token was listed) shows zero tokens

🤖 Generated with Claude Code

## Summary Workstream item #16. Three audit findings under the lifecycle / cascade-gap theme. ### M-1 — group-mapping deletion left stale ProjectAccess Dropping an LDAP/OAuth2 group mapping previously removed the mapping row but left the `project_access` rows that were granted via that source untouched. Until the affected user's next login (where `syncProjectAccess` would have revoked the dangling row), they retained access — possibly indefinitely if they never logged in again. - New `ProjectAccessStore.RevokeProjectBySource(projectID, source)`. - `handleAdminDeleteGroupMapping` calls it after `Delete`. Surviving mappings that still cover the same `(project, source)` re-grant on the user's next login, so the dangling-grant window is bounded. ### M-3 — public → private/custom visibility flip leaves users locked out silently Editors who had implicit access to a public project lose it the moment the project is restricted, with no signal to the admin doing the change. - `handleAdminUpdateProject` records the previous visibility. On a `public → non-public` transition it appends `?msg=visibility_restricted` to the redirect. - `handleAdminProjects` renders the new flash: "Project visibility changed from public — review project access so the intended users still have access." ### M-4 — editor demotion left tokens and manual editor grants in place A demoted user with leftover API tokens + their original auto-grant could still upload to projects via `canUpload`'s project-grant branch despite the role change. - New `ProjectAccessStore.RevokeManualEditorByUser(userID)`. - `handleAdminUpdateUserRole`: when the role transitions away from admin/editor to viewer, revoke the user's manual editor grants and delete all of their API tokens. ## Tests Four regression tests in `lifecycle_cleanup_test.go`: - `TestDeleteGroupMappingRevokesProjectAccess` — pre-seeded oauth2-source access is gone after mapping delete - `TestVisibilityRestrictionFlagsFlash` — public→private redirect carries the flash; private→private edit does not - `TestDemotionRevokesManualEditorGrantsAndTokens` — editor→viewer revokes both - `TestPromotionDoesNotRevokeGrants` — sanity: viewer→editor leaves existing grants alone `setupTestApp` gained `GroupMappings` in `Deps` (it was missing, which the M-1 test caught by panicking on the nil store). ## Test plan - [x] `go test ./...` passes - [ ] Manual: create an LDAP/OAuth2 group mapping, give a user access via login, delete the mapping → next page load they should lose access - [ ] Manual: flip a public project to private in the admin UI → confirm the warning flash appears - [ ] Manual: demote an editor with at least one token to viewer → confirm `/admin/robots` (or wherever their token was listed) shows zero tokens 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Lifecycle cleanups: M-1 mapping revoke, M-3 visibility warning, M-4 demotion sweep
All checks were successful
CI / docker (pull_request) Has been skipped
CI / test (pull_request) Successful in 1m17s
CI / build (pull_request) Successful in 54s
e7daa0eb6c
Three audit findings under the lifecycle/cascade-gap theme.

M-1 — Group-mapping deletion previously dropped the mapping row but left
the project_access rows that were granted via that source untouched.
Until the affected user's next login (which would re-run syncProjectAccess
and revoke the dangling row), they retained access — possibly indefinitely
if they never logged in again.

Add ProjectAccessStore.RevokeProjectBySource(projectID, source). The
group-mapping delete handler calls it after Delete. Surviving mappings
that cover the same (project, source) re-grant on the user's next
login, so the dangling-grant window is now bounded.

M-3 — Visibility transition from public to private/custom leaves no
grants in place. Editors who could see/upload to the now-non-public
project lose access silently and the admin gets no signal.

handleAdminUpdateProject captures the previous visibility, and when
the project transitions away from public it appends ?msg=visibility_restricted
to the redirect. The admin projects page renders that as a warning
flash: "Project visibility changed from public — review project access
so the intended users still have access."

M-4 — Demoting an editor or admin to viewer leaves their previously-
created API tokens valid and their manual editor-role project_access
rows in place. canUpload's project-grant branch then accepts them, so
the demoted user can still upload despite the role change.

Add:
  - ProjectAccessStore.RevokeManualEditorByUser(userID)
  - In handleAdminUpdateUserRole, when role transitions away from
    admin/editor to viewer, revoke the user's manual editor grants and
    delete all of their API tokens.

Four regression tests in lifecycle_cleanup_test.go covering each path
plus a sanity test that promotion (viewer → editor) leaves grants
untouched.

setupTestApp gained GroupMappings in Deps (it was missing, which the
M-1 test surfaced by panicking on the nil store).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
qwc merged commit b36da1e7d7 into main 2026-05-18 22:38:40 +02:00
qwc deleted branch fix/lifecycle-cleanups 2026-05-18 22:38:40 +02:00
Sign in to join this conversation.
No reviewers
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
qwc-open/asiakirjat!111
No description provided.