Fix UserInfo canonical subject resolution
All checks were successful
Build and Publish Container Image / build-and-push (push) Successful in 23s

Assistant: codex
Assistant-Model: gpt-5.6-sol
Assistant-Session: 01a058f3-8ba0-7692-a042-9a870fc3d663
This commit is contained in:
tegwick 2026-08-31 23:41:29 +02:00
parent 49dabb2d5c
commit 153258b9d3
3 changed files with 112 additions and 2 deletions

View file

@ -1,6 +1,7 @@
package oidc
import (
"context"
"crypto"
"crypto/rsa"
"crypto/sha256"
@ -52,8 +53,11 @@ func (h *UserinfoHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return
}
// 4. Look up user by sub (sub IS the username per spec §3.1).
user, err := h.Users.LookupUser(ctx, sub)
// 4. Resolve the canonical subject. Human tokens use the stable directory
// ID (the LDAP DN) as sub, while LookupUser accepts an LDAP uid. Prefer the
// scoped username hint but require it to resolve back to the signed subject;
// fall back to an ID scan for openid-only tokens and renamed users.
user, err := lookupUserBySubject(ctx, h.Users, claims, sub)
if err != nil {
// User referenced in token but not found → treat as invalid token.
http.Error(w, `{"error":"invalid_token","description":"subject not found"}`, http.StatusUnauthorized)
@ -97,6 +101,36 @@ func (h *UserinfoHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
_ = json.NewEncoder(w).Encode(resp)
}
func lookupUserBySubject(
ctx context.Context,
users domain.UserRepository,
claims map[string]interface{},
sub string,
) (*domain.User, error) {
if username, _ := claims["preferred_username"].(string); username != "" {
if user, err := users.LookupUser(ctx, username); err == nil && user.ID == sub {
return user, nil
}
}
// Retain compatibility with older signed tokens whose subject was the uid.
if user, err := users.LookupUser(ctx, sub); err == nil &&
(user.ID == sub || user.Username == sub) {
return user, nil
}
all, err := users.ListUsers(ctx)
if err != nil {
return nil, err
}
for i := range all {
if all[i].ID == sub {
return &all[i], nil
}
}
return nil, domain.ErrUserNotFound
}
// ---------------------------------------------------------------------------
// JWT validation (stdlib only — no external JWT library)
// ---------------------------------------------------------------------------