Constrain access roles and sources at store entry (audit L-1, L-7) #145
No reviewers
Labels
No labels
Compat/Breaking
Kind/Bug
Kind/Documentation
Kind/Enhancement
Kind/Feature
Kind/Security
Kind/Testing
Priority
Critical
Priority
High
Priority
Low
Priority
Medium
Reviewed
Confirmed
Reviewed
Duplicate
Reviewed
Invalid
Reviewed
Won't Fix
Status
Abandoned
Status
Blocked
Status
Need More Info
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
qwc-open/asiakirjat!145
Loading…
Reference in a new issue
No description provided.
Delete branch "fix/constrain-access-roles"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Audit items L-1 and L-7, done together as the audit suggested — they're the same job seen from two sides. Independent of #144.
What was loose
Every path that writes an access role already coerces it to viewer or editor (
admin.go:395,admin.go:980,main.go:287), and every path that writes a source sets one explicitly. But the stores trusted their callers and the column default:ProjectAccessStore.Grantdefaulted an empty source tomanualand validated nothing else.GlobalAccessStore.CreateRule/UpsertGranttook any role and any subject type.AuthGroupMappingStore.Create/Updatetook any role — and a mapping's role is what reachesproject_accesson every LDAP/OAuth2 login.So the invariant held only by convention, in four handlers, and a future caller reintroducing a typo would write a row that nothing matches and no one can see — the silent-no-op shape that produced #126 and #133.
What changed
All of those reject an unrecognised role, source or subject type outright, using the validators that already exist (
database.ValidAccessRole,ValidAccessSource,ValidSubjectType— added incidentally with #132 and #125, which is what made these items cheap now).With the values constrained, the
adminbranches in the read paths are dead — an access rule confers viewer or editor, while "admin" describes a user.CanUpload,GetEffectiveRole,roleRankand the role-priority SQL all drop it.user.Role == "admin"is untouched; that's the legitimate one.The migration matters here
Migration 015 normalises any existing
role = 'admin'row toeditoracrossproject_access,global_access,global_access_grantsandauth_group_mappings.Without it, dropping the read-side branches would be a silent access revocation on any deployment that has such a row rather than a no-op — exactly the class of bug this audit item is meant to close. The down migration is a no-op and says why: a normalised row is indistinguishable from an editor that was always an editor.
Tests
internal/store/sql/access_role_validation_test.go— each store rejectingadmin, an unknown role, an empty role, an unknown source, an unknown subject type; and the valid combinations still working, including an empty source still defaulting tomanual.internal/database/normalize_roles_test.go— insertsadminrows directly (the stores now refuse them), migrates down and back up, and asserts all four tables come out as editors.Full suite green, including the existing checker tests that pin who can upload.
Assisted by Claude Opus 5.