@@ -20,60 +20,51 @@ import (
2020 "golang.org/x/oauth2"
2121)
2222
23- // userInfo struct to hold user claims from either UserInfo or ID token
23+ // userInfo holds all claims dynamically, plus pre-parsed Groups.
2424type userInfo struct {
25- Name string `json:"name"`
26- PreferredUsername string `json:"preferred_username"`
27- Username string `json:"username"`
28- Email string `json:"email"`
29- Sub string `json:"sub"`
30- Phone string `json:"phone_number"`
31- Groups []string `json:"-"` // Handled manually by userInfoUnmarshaller
25+ Claims map [string ]interface {}
26+ Groups []string
3227}
3328
34- // userInfoUnmarshaller is a custom unmarshaller that handles configurable groups claim
29+ // userInfoUnmarshaller handles unmarshalling all claims dynamically,
30+ // while optionally parsing a configurable groups claim.
3531type userInfoUnmarshaller struct {
3632 userInfo * userInfo
3733 groupsClaim string
3834}
3935
4036// UnmarshalJSON implements the json.Unmarshaler interface
4137func (u * userInfoUnmarshaller ) UnmarshalJSON (data []byte ) error {
42- // First, unmarshal the basic userInfo fields
43- if err := json .Unmarshal (data , u . userInfo ); err != nil {
38+ var raw map [ string ] interface {}
39+ if err := json .Unmarshal (data , & raw ); err != nil {
4440 return err
4541 }
4642
47- // Parse the JSON to access the groups claim field
48- var rawData map [string ]interface {}
49- if err := json .Unmarshal (data , & rawData ); err != nil {
50- return err
51- }
52-
53- // Look for the groups claim using the configured field name
54- if groupsValue , exists := rawData [u .groupsClaim ]; exists {
55- switch v := groupsValue .(type ) {
56- case []interface {}:
57- // It's already an array, convert to []string
58- groups := make ([]string , len (v ))
59- for i , group := range v {
60- if str , ok := group .(string ); ok {
61- groups [i ] = str
43+ // Extract groups if configured
44+ if u .groupsClaim != "" {
45+ if v , ok := raw [u .groupsClaim ]; ok {
46+ switch val := v .(type ) {
47+ case []interface {}:
48+ groups := make ([]string , len (val ))
49+ for i , g := range val {
50+ if s , ok := g .(string ); ok {
51+ groups [i ] = strings .TrimSpace (s )
52+ }
6253 }
63- }
64- u .userInfo .Groups = groups
65- case string :
66- // It's a string, split by commas
67- if v != "" {
68- u .userInfo .Groups = strings .Split (v , "," )
69- // Trim whitespace from each group
70- for i , group := range u .userInfo .Groups {
71- u .userInfo .Groups [i ] = strings .TrimSpace (group )
54+ u .userInfo .Groups = groups
55+ case string :
56+ if val != "" {
57+ parts := strings .Split (val , "," )
58+ for i := range parts {
59+ parts [i ] = strings .TrimSpace (parts [i ])
60+ }
61+ u .userInfo .Groups = parts
7262 }
7363 }
7464 }
7565 }
7666
67+ u .userInfo .Claims = raw
7768 return nil
7869}
7970
@@ -100,7 +91,7 @@ func oidcLoginHandler(w http.ResponseWriter, r *http.Request, d *requestContext)
10091 ClientSecret : oidcCfg .ClientSecret ,
10192 Endpoint : oidcCfg .Provider .Endpoint (),
10293 RedirectURL : fmt .Sprintf ("%s%sapi/auth/oidc/callback" , origin , config .Server .BaseURL ),
103- Scopes : strings .Split (oidcCfg .Scopes , " " ),
94+ Scopes : strings .Fields (oidcCfg .Scopes ),
10495 }
10596
10697 nonce := utils .InsecureRandomIdentifier (16 )
@@ -157,7 +148,7 @@ func oidcCallbackHandler(w http.ResponseWriter, r *http.Request, d *requestConte
157148 ClientSecret : oidcCfg .ClientSecret ,
158149 Endpoint : oidcCfg .Provider .Endpoint (), // Use endpoint from discovered provider
159150 RedirectURL : redirectURL , // Use the dynamically determined redirect URL
160- Scopes : strings .Split (oidcCfg .Scopes , " " ),
151+ Scopes : strings .Fields (oidcCfg .Scopes ),
161152 }
162153
163154 // Exchange the authorization code for tokens
@@ -186,10 +177,10 @@ func oidcCallbackHandler(w http.ResponseWriter, r *http.Request, d *requestConte
186177
187178 // Verify the ID token
188179 // This uses the verifier initialized with the provider's JWKS endpoint and client ID
189- idToken , err := oidcCfg .Verifier .Verify (ctx , rawIDToken )
190- if err != nil {
180+ idToken , verify_err := oidcCfg .Verifier .Verify (ctx , rawIDToken )
181+ if verify_err != nil {
191182 // this might not be necessary for certain providers like authentik
192- logger .Debugf ("failed to verify ID token: %v. This might be expected, falling back to UserInfo endpoint." , err )
183+ logger .Debugf ("failed to verify ID token: %v. This might be expected, falling back to UserInfo endpoint." , verify_err )
193184 // Verification failed, claimsFromIDToken remains false
194185 } else {
195186 // Decode the ID token claims using custom unmarshaller
@@ -203,30 +194,13 @@ func oidcCallbackHandler(w http.ResponseWriter, r *http.Request, d *requestConte
203194
204195 // Decide if we rely on ID token claims or still need UserInfo
205196 // Even if parsing succeeded, if essential claims are missing, use UserInfo
206- switch oidcCfg .UserIdentifier {
207- case "email" :
208- if userdata .Email != "" {
209- claimsFromIDToken = true
210- loginUsername = userdata .Email
211- }
212- case "username" :
213- if userdata .Username != "" {
214- claimsFromIDToken = true
215- loginUsername = userdata .Username
216- }
217- case "preferred_username" :
218- if userdata .PreferredUsername != "" {
219- claimsFromIDToken = true
220- loginUsername = userdata .PreferredUsername
221- }
222- case "phone" :
223- if userdata .Phone != "" {
224- claimsFromIDToken = true
225- loginUsername = userdata .Phone
226- }
197+ if _ , ok := userdata .Claims [oidcCfg .UserIdentifier ]; ok {
198+ claimsFromIDToken = true
227199 }
228200 }
201+ logger .Debugf ("Failed to verify ID token: %v" , verify_err )
229202 }
203+
230204 } else {
231205 logger .Debug ("No ID token found in token response or it was empty. Falling back to UserInfo endpoint." )
232206 // claimsFromIDToken remains false
@@ -247,22 +221,17 @@ func oidcCallbackHandler(w http.ResponseWriter, r *http.Request, d *requestConte
247221 logger .Errorf ("failed to decode user info from endpoint: %v" , err )
248222 return http .StatusInternalServerError , fmt .Errorf ("failed to decode user info from endpoint: %v" , err )
249223 }
250- // Decide if we rely on ID token claims or still need UserInfo
251- // Even if parsing succeeded, if essential claims are missing, use UserInfo
252- switch oidcCfg .UserIdentifier {
253- case "email" :
254- loginUsername = userdata .Email
255- case "username" :
256- loginUsername = userdata .Username
257- case "preferred_username" :
258- loginUsername = userdata .PreferredUsername
259- case "phone" :
260- loginUsername = userdata .Phone
224+ }
225+
226+ // --- Determine login username dynamically ---
227+ if val , ok := userdata .Claims [oidcCfg .UserIdentifier ]; ok {
228+ if v , ok := val .(string ); ok {
229+ loginUsername = v
261230 }
262231 }
263232 if loginUsername == "" {
264- logger .Errorf ("No valid username found for identifier '%v' in ID token or UserInfo response ." , oidcCfg .UserIdentifier )
265- return http .StatusInternalServerError , fmt .Errorf ("no valid username found in ID token or UserInfo response from claims" )
233+ logger .Errorf ("No valid username found for identifier '%v' in claims ." , oidcCfg .UserIdentifier )
234+ return http .StatusInternalServerError , fmt .Errorf ("no valid username found for identifier '%v'" , oidcCfg . UserIdentifier )
266235 }
267236
268237 // Proceed to log the user in with the OIDC data
0 commit comments