Additional fixes for user and group#3371
Open
h2zh wants to merge 7 commits into
Open
Conversation
Before this commit, any OIDC user could gain superadmin privileges if their OIDC claim (preferred_username, name, nickname, or email, checked in that order) resolved to 'admin'. This occurred due to a hardcoded username check in the CheckAdmin function.
- So that their OIDC sub values are "claimed" in the database before any attacker can register them - When this user actually logs in, the placeholder username (defaults to OIDC sub) will be overwritten by this user's real username (a.k.a display name)
- Should be the `id` maintained by Pelican, instead of the `username` fetched from four OIDC claims (preferred_username, name, nickname, or email, checked in that fallback order) - No schema changes to the users table. Personal groups are virtual: they are never stored as rows in the groups table. The "user-<identifier>" string is computed at runtime in `ListCollections` and `validateACL` and injected into the groups slice. The only persisted reference is in collection_acls.group_id. - The DB migration updates the following fields: a) collection_acls.group_id where it matches "user-<username>" → "user-<userId>" b) collection_acls.granted_by from username → userId c) collections.owner from username → userId
- No more checking Username or ID - Update tests
Username is a mutable display name, not an identity key. The real identity uniqueness is (sub, issuer), which is enforced by idx_user_sub_issuer.
Contributor
I recall that Patrick added the ID |
Note that in the user's login cookie JWT, `iss` and `oidc_iss` are two distinct concepts. The former is the Pelican server's own URL. It's used by the JWT library during verification (jwt.Parse) to confirm the token was issued by this server. It never touches the identity provider . The latter is the upstream identity provider's URL (e.g. https://cilogon.org) that originally authenticated the user via OIDC. Also update tests and fix linter issues
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
When I was working on this PR, I noticed plenty of confusing concepts and behavior in user and group logics. In this PR description, I am going write them down and the fixes I made, also why I came up with that fix.
usernameis a display name, not an identity keyPelican resolves username from the OIDC UserInfo response by trying these claims in order: preferred_username, name, nickname, email, and finally sub as a last resort. The result is mutable — the provider can change it, and before this PR an admin could change it too. It is also not globally unique: two users from different issuers can share the same display name.
The real, stable identity of an OIDC user is the (sub, issuer) pair, enforced by the idx_user_sub_issuer unique constraint. Pelican also assigns an internal userId (an 8-character hex slug) at user creation that never changes. These are the correct handles to use when referring to a specific user persistently.
Replace username with userId in personal group names and collection references
Problem: personal groups follow the pattern "user-<identifier>". Before this PR, identifier was the mutable username. A display name change — at the OIDC provider or via the admin API — silently orphaned the old "user-" entries in collection_acls, causing the user to lose access to their own collections with no error.
Implementation note: personal groups are virtual. They are never stored as rows in the groups table. The "user-<identifier>" string is computed at runtime in ListCollections and validateACL and injected into the groups slice before any ACL lookup. The only persisted reference is in collection_acls.group_id (despite the column name, it stores a group name string, not a foreign key).
Fix: switch identifier to userId. The DB migration updates the following fields:
a) collection_acls.group_id where it matches "user-<username>" → "user-<userId>"
b) collection_acls.granted_by from username → userId
c) collections.owner from username → userId
Auto create user records for everyone in
Server.UIAdminUsers(a list of OIDC subs)Let the pre-set admin's OIDC sub values to be "claimed" in the database on startup, before any attacker can register them. When this user actually logs in, the placeholder username (defaults to OIDC sub) will be overwritten by this user's real username (a.k.a display name).
Reject "admin" username for OIDC users
Before this commit, any OIDC user could gain superadmin privileges if their OIDC claim (preferred_username, name, nickname, or email, checked in that order) resolved to 'admin'. This occurred due to a hardcoded username check in the CheckAdmin function.
Drop the (username, issuer) unique constraint
The users table had a composite unique index idx_user_issuer on (username, issuer). This implicitly treated username as part of the identity key — it prevented two accounts at the same provider from sharing a display name. That assumption is wrong. Display names can legitimately collide, and the constraint would also reject the username-refresh behavior introduced in this PR (refreshing username on each login to reflect the latest OIDC value fails if another user at the same issuer already holds that display name).
The correct identity uniqueness is already enforced by idx_user_sub_issuer on (sub, issuer), which is unchanged.
Only check
identity.SubagainstUIAdminUsersin CheckAdminBefore, CheckAdmin matched all three of Username, ID (the internal hex slug), and Sub against Server.UIAdminUsers. The intent was flexibility, but checking Username meant the list was effectively matched by mutable display name.
Now, only Sub is checked against UIAdminUsers, which is a list consist by OIDC sub values.