Make global access rules that name a user actually grant access #135
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!135
Loading…
Reference in a new issue
No description provided.
Delete branch "fix/global-access-user-rules"
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?
Groundwork found while starting #125. Independent of #132 and #134 — different files, any merge order works.
The bug
Admin > Global Access accepts three subject types, and
docs/how-to/manage-global-access.mdalready promises that naming a user is one of the ways in:It never worked. Grants are only ever written by the LDAP and OAuth2 login sync (
auth/ldap.go:324,auth/oauth2.go:469), and both skip anything that isn'tldap_group/oauth2_group.access.Checkerdecides private-project access purely fromGetGrantByUser, so a rule withsubject_type='user'had no path to a grant and no path to the checker.main.golooked like it covered this — there was a loop over the config rules under the comment "Resolve user-type rules into direct grants" — but the body was:The admin UI creates a rule and stops; the auth sync ignores user rules. So an admin adding "alice — viewer" (UI or
access.private.viewers.users: [alice]) got a row in the table, a rule visible in the admin list, and alice still got 403 on every private project. Silent, and it looks exactly like a working configuration.Tests first
internal/access/user_rule_test.gostates the promise the docs make. Both fail onmain:The view test also asserts the negative — a user with neither rule nor grant still can't reach private projects — so it can't pass by making the checker permissive.
The fix
Materializing grants would need a resolution step in three places (rule creation, startup config sync, and again whenever a user is created later — a config rule can name someone who doesn't exist yet), each able to drift out of sync. So the checker matches user rules at check time instead, via one new store method
GetUserRule(case-insensitive, matching how group rules are compared in the authenticators).Grants and rules now resolve together in
Checker.globalRole, with the stronger role winning if a user is covered by both. The three sites that each poked at the grant store —CanView,CanUpload,FilterAccessible— share it, so private-access policy lives in one function rather than three copies. That was the drift theaccesspackage was extracted to prevent.main.go's dead loop is gone, replaced by a comment saying where user rules are handled.Cost
One extra indexed lookup on private-project checks for users without a grant.
FilterAccessiblestill does a single global lookup for the whole list, unchanged.Full suite green. (
go vetflagshandler_test.go:1671— pre-existing onmain, untouched here.)On #125
This is why the issue is worth doing carefully: named lists will have the same "a member can be a user or a group" question, and now there's one resolution path to extend rather than a broken one to copy. Next up is the schema for named lists, per your note that a list must be able to be an LDAP group, or an LDAP group plus extra users.
Assisted by Claude Opus 5.