Skip to content

Additional fixes for user and group#3371

Open
h2zh wants to merge 7 commits into
PelicanPlatform:mainfrom
h2zh:user-group-fix
Open

Additional fixes for user and group#3371
h2zh wants to merge 7 commits into
PelicanPlatform:mainfrom
h2zh:user-group-fix

Conversation

@h2zh
Copy link
Copy Markdown
Contributor

@h2zh h2zh commented Apr 22, 2026

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.

username is a display name, not an identity key

Pelican 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.Sub against UIAdminUsers in CheckAdmin

Before, 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.

h2zh added 5 commits April 21, 2026 13:54
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.
@CannonLock
Copy link
Copy Markdown
Contributor

CannonLock commented Apr 22, 2026

Only check identity.Sub against UIAdminUsers in CheckAdmin

I recall that Patrick added the ID iss as a column as well correct? When you check identity you should use the sub and iss. In the rare case that a Origin switches Identity issuers you could see overlap giving someone access to a previous account.

@h2zh

@h2zh h2zh added this to the v7.27 milestone Apr 22, 2026
@h2zh h2zh added cache Issue relating to the cache component origin Issue relating to the origin component director Issue relating to the director component registry Issue relating to the registry component enhancement New feature or request labels Apr 22, 2026
@h2zh h2zh linked an issue Apr 22, 2026 that may be closed by this pull request
h2zh added 2 commits April 22, 2026 17:19
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
@h2zh h2zh requested a review from brianaydemir May 4, 2026 19:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cache Issue relating to the cache component director Issue relating to the director component enhancement New feature or request origin Issue relating to the origin component registry Issue relating to the registry component

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Fix user and group

3 participants