oidc_provider.go 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670
  1. package oidc
  2. import (
  3. "context"
  4. "crypto/ecdsa"
  5. "crypto/elliptic"
  6. "crypto/rsa"
  7. "encoding/base64"
  8. "encoding/json"
  9. "fmt"
  10. "math/big"
  11. "net/http"
  12. "strings"
  13. "time"
  14. "github.com/golang-jwt/jwt/v5"
  15. "github.com/seaweedfs/seaweedfs/weed/glog"
  16. "github.com/seaweedfs/seaweedfs/weed/iam/providers"
  17. )
  18. // OIDCProvider implements OpenID Connect authentication
  19. type OIDCProvider struct {
  20. name string
  21. config *OIDCConfig
  22. initialized bool
  23. jwksCache *JWKS
  24. httpClient *http.Client
  25. jwksFetchedAt time.Time
  26. jwksTTL time.Duration
  27. }
  28. // OIDCConfig holds OIDC provider configuration
  29. type OIDCConfig struct {
  30. // Issuer is the OIDC issuer URL
  31. Issuer string `json:"issuer"`
  32. // ClientID is the OAuth2 client ID
  33. ClientID string `json:"clientId"`
  34. // ClientSecret is the OAuth2 client secret (optional for public clients)
  35. ClientSecret string `json:"clientSecret,omitempty"`
  36. // JWKSUri is the JSON Web Key Set URI
  37. JWKSUri string `json:"jwksUri,omitempty"`
  38. // UserInfoUri is the UserInfo endpoint URI
  39. UserInfoUri string `json:"userInfoUri,omitempty"`
  40. // Scopes are the OAuth2 scopes to request
  41. Scopes []string `json:"scopes,omitempty"`
  42. // RoleMapping defines how to map OIDC claims to roles
  43. RoleMapping *providers.RoleMapping `json:"roleMapping,omitempty"`
  44. // ClaimsMapping defines how to map OIDC claims to identity attributes
  45. ClaimsMapping map[string]string `json:"claimsMapping,omitempty"`
  46. // JWKSCacheTTLSeconds sets how long to cache JWKS before refresh (default 3600 seconds)
  47. JWKSCacheTTLSeconds int `json:"jwksCacheTTLSeconds,omitempty"`
  48. }
  49. // JWKS represents JSON Web Key Set
  50. type JWKS struct {
  51. Keys []JWK `json:"keys"`
  52. }
  53. // JWK represents a JSON Web Key
  54. type JWK struct {
  55. Kty string `json:"kty"` // Key Type (RSA, EC, etc.)
  56. Kid string `json:"kid"` // Key ID
  57. Use string `json:"use"` // Usage (sig for signature)
  58. Alg string `json:"alg"` // Algorithm (RS256, etc.)
  59. N string `json:"n"` // RSA public key modulus
  60. E string `json:"e"` // RSA public key exponent
  61. X string `json:"x"` // EC public key x coordinate
  62. Y string `json:"y"` // EC public key y coordinate
  63. Crv string `json:"crv"` // EC curve
  64. }
  65. // NewOIDCProvider creates a new OIDC provider
  66. func NewOIDCProvider(name string) *OIDCProvider {
  67. return &OIDCProvider{
  68. name: name,
  69. httpClient: &http.Client{Timeout: 30 * time.Second},
  70. }
  71. }
  72. // Name returns the provider name
  73. func (p *OIDCProvider) Name() string {
  74. return p.name
  75. }
  76. // GetIssuer returns the configured issuer URL for efficient provider lookup
  77. func (p *OIDCProvider) GetIssuer() string {
  78. if p.config == nil {
  79. return ""
  80. }
  81. return p.config.Issuer
  82. }
  83. // Initialize initializes the OIDC provider with configuration
  84. func (p *OIDCProvider) Initialize(config interface{}) error {
  85. if config == nil {
  86. return fmt.Errorf("config cannot be nil")
  87. }
  88. oidcConfig, ok := config.(*OIDCConfig)
  89. if !ok {
  90. return fmt.Errorf("invalid config type for OIDC provider")
  91. }
  92. if err := p.validateConfig(oidcConfig); err != nil {
  93. return fmt.Errorf("invalid OIDC configuration: %w", err)
  94. }
  95. p.config = oidcConfig
  96. p.initialized = true
  97. // Configure JWKS cache TTL
  98. if oidcConfig.JWKSCacheTTLSeconds > 0 {
  99. p.jwksTTL = time.Duration(oidcConfig.JWKSCacheTTLSeconds) * time.Second
  100. } else {
  101. p.jwksTTL = time.Hour
  102. }
  103. // For testing, we'll skip the actual OIDC client initialization
  104. return nil
  105. }
  106. // validateConfig validates the OIDC configuration
  107. func (p *OIDCProvider) validateConfig(config *OIDCConfig) error {
  108. if config.Issuer == "" {
  109. return fmt.Errorf("issuer is required")
  110. }
  111. if config.ClientID == "" {
  112. return fmt.Errorf("client ID is required")
  113. }
  114. // Basic URL validation for issuer
  115. if config.Issuer != "" && config.Issuer != "https://accounts.google.com" && config.Issuer[0:4] != "http" {
  116. return fmt.Errorf("invalid issuer URL format")
  117. }
  118. return nil
  119. }
  120. // Authenticate authenticates a user with an OIDC token
  121. func (p *OIDCProvider) Authenticate(ctx context.Context, token string) (*providers.ExternalIdentity, error) {
  122. if !p.initialized {
  123. return nil, fmt.Errorf("provider not initialized")
  124. }
  125. if token == "" {
  126. return nil, fmt.Errorf("token cannot be empty")
  127. }
  128. // Validate token and get claims
  129. claims, err := p.ValidateToken(ctx, token)
  130. if err != nil {
  131. return nil, err
  132. }
  133. // Map claims to external identity
  134. email, _ := claims.GetClaimString("email")
  135. displayName, _ := claims.GetClaimString("name")
  136. groups, _ := claims.GetClaimStringSlice("groups")
  137. // Debug: Log available claims
  138. glog.V(3).Infof("Available claims: %+v", claims.Claims)
  139. if rolesFromClaims, exists := claims.GetClaimStringSlice("roles"); exists {
  140. glog.V(3).Infof("Roles claim found as string slice: %v", rolesFromClaims)
  141. } else if roleFromClaims, exists := claims.GetClaimString("roles"); exists {
  142. glog.V(3).Infof("Roles claim found as string: %s", roleFromClaims)
  143. } else {
  144. glog.V(3).Infof("No roles claim found in token")
  145. }
  146. // Map claims to roles using configured role mapping
  147. roles := p.mapClaimsToRolesWithConfig(claims)
  148. // Create attributes map and add roles
  149. attributes := make(map[string]string)
  150. if len(roles) > 0 {
  151. // Store roles as a comma-separated string in attributes
  152. attributes["roles"] = strings.Join(roles, ",")
  153. }
  154. return &providers.ExternalIdentity{
  155. UserID: claims.Subject,
  156. Email: email,
  157. DisplayName: displayName,
  158. Groups: groups,
  159. Attributes: attributes,
  160. Provider: p.name,
  161. }, nil
  162. }
  163. // GetUserInfo retrieves user information from the UserInfo endpoint
  164. func (p *OIDCProvider) GetUserInfo(ctx context.Context, userID string) (*providers.ExternalIdentity, error) {
  165. if !p.initialized {
  166. return nil, fmt.Errorf("provider not initialized")
  167. }
  168. if userID == "" {
  169. return nil, fmt.Errorf("user ID cannot be empty")
  170. }
  171. // For now, we'll use a token-based approach since OIDC UserInfo typically requires a token
  172. // In a real implementation, this would need an access token from the authentication flow
  173. return p.getUserInfoWithToken(ctx, userID, "")
  174. }
  175. // GetUserInfoWithToken retrieves user information using an access token
  176. func (p *OIDCProvider) GetUserInfoWithToken(ctx context.Context, accessToken string) (*providers.ExternalIdentity, error) {
  177. if !p.initialized {
  178. return nil, fmt.Errorf("provider not initialized")
  179. }
  180. if accessToken == "" {
  181. return nil, fmt.Errorf("access token cannot be empty")
  182. }
  183. return p.getUserInfoWithToken(ctx, "", accessToken)
  184. }
  185. // getUserInfoWithToken is the internal implementation for UserInfo endpoint calls
  186. func (p *OIDCProvider) getUserInfoWithToken(ctx context.Context, userID, accessToken string) (*providers.ExternalIdentity, error) {
  187. // Determine UserInfo endpoint URL
  188. userInfoUri := p.config.UserInfoUri
  189. if userInfoUri == "" {
  190. // Use standard OIDC discovery endpoint convention
  191. userInfoUri = strings.TrimSuffix(p.config.Issuer, "/") + "/userinfo"
  192. }
  193. // Create HTTP request
  194. req, err := http.NewRequestWithContext(ctx, "GET", userInfoUri, nil)
  195. if err != nil {
  196. return nil, fmt.Errorf("failed to create UserInfo request: %v", err)
  197. }
  198. // Set authorization header if access token is provided
  199. if accessToken != "" {
  200. req.Header.Set("Authorization", "Bearer "+accessToken)
  201. }
  202. req.Header.Set("Accept", "application/json")
  203. // Make HTTP request
  204. resp, err := p.httpClient.Do(req)
  205. if err != nil {
  206. return nil, fmt.Errorf("failed to call UserInfo endpoint: %v", err)
  207. }
  208. defer resp.Body.Close()
  209. // Check response status
  210. if resp.StatusCode != http.StatusOK {
  211. return nil, fmt.Errorf("UserInfo endpoint returned status %d", resp.StatusCode)
  212. }
  213. // Parse JSON response
  214. var userInfo map[string]interface{}
  215. if err := json.NewDecoder(resp.Body).Decode(&userInfo); err != nil {
  216. return nil, fmt.Errorf("failed to decode UserInfo response: %v", err)
  217. }
  218. glog.V(4).Infof("Received UserInfo response: %+v", userInfo)
  219. // Map UserInfo claims to ExternalIdentity
  220. identity := p.mapUserInfoToIdentity(userInfo)
  221. // If userID was provided but not found in claims, use it
  222. if userID != "" && identity.UserID == "" {
  223. identity.UserID = userID
  224. }
  225. glog.V(3).Infof("Retrieved user info from OIDC provider: %s", identity.UserID)
  226. return identity, nil
  227. }
  228. // ValidateToken validates an OIDC JWT token
  229. func (p *OIDCProvider) ValidateToken(ctx context.Context, token string) (*providers.TokenClaims, error) {
  230. if !p.initialized {
  231. return nil, fmt.Errorf("provider not initialized")
  232. }
  233. if token == "" {
  234. return nil, fmt.Errorf("token cannot be empty")
  235. }
  236. // Parse token without verification first to get header info
  237. parsedToken, _, err := new(jwt.Parser).ParseUnverified(token, jwt.MapClaims{})
  238. if err != nil {
  239. return nil, fmt.Errorf("failed to parse JWT token: %v", err)
  240. }
  241. // Get key ID from header
  242. kid, ok := parsedToken.Header["kid"].(string)
  243. if !ok {
  244. return nil, fmt.Errorf("missing key ID in JWT header")
  245. }
  246. // Get signing key from JWKS
  247. publicKey, err := p.getPublicKey(ctx, kid)
  248. if err != nil {
  249. return nil, fmt.Errorf("failed to get public key: %v", err)
  250. }
  251. // Parse and validate token with proper signature verification
  252. claims := jwt.MapClaims{}
  253. validatedToken, err := jwt.ParseWithClaims(token, claims, func(token *jwt.Token) (interface{}, error) {
  254. // Verify signing method
  255. switch token.Method.(type) {
  256. case *jwt.SigningMethodRSA:
  257. return publicKey, nil
  258. default:
  259. return nil, fmt.Errorf("unsupported signing method: %v", token.Header["alg"])
  260. }
  261. })
  262. if err != nil {
  263. return nil, fmt.Errorf("failed to validate JWT token: %v", err)
  264. }
  265. if !validatedToken.Valid {
  266. return nil, fmt.Errorf("JWT token is invalid")
  267. }
  268. // Validate required claims
  269. issuer, ok := claims["iss"].(string)
  270. if !ok || issuer != p.config.Issuer {
  271. return nil, fmt.Errorf("invalid or missing issuer claim")
  272. }
  273. // Check audience claim (aud) or authorized party (azp) - Keycloak uses azp
  274. // Per RFC 7519, aud can be either a string or an array of strings
  275. var audienceMatched bool
  276. if audClaim, ok := claims["aud"]; ok {
  277. switch aud := audClaim.(type) {
  278. case string:
  279. if aud == p.config.ClientID {
  280. audienceMatched = true
  281. }
  282. case []interface{}:
  283. for _, a := range aud {
  284. if str, ok := a.(string); ok && str == p.config.ClientID {
  285. audienceMatched = true
  286. break
  287. }
  288. }
  289. }
  290. }
  291. if !audienceMatched {
  292. if azp, ok := claims["azp"].(string); ok && azp == p.config.ClientID {
  293. audienceMatched = true
  294. }
  295. }
  296. if !audienceMatched {
  297. return nil, fmt.Errorf("invalid or missing audience claim for client ID %s", p.config.ClientID)
  298. }
  299. subject, ok := claims["sub"].(string)
  300. if !ok {
  301. return nil, fmt.Errorf("missing subject claim")
  302. }
  303. // Convert to our TokenClaims structure
  304. tokenClaims := &providers.TokenClaims{
  305. Subject: subject,
  306. Issuer: issuer,
  307. Claims: make(map[string]interface{}),
  308. }
  309. // Copy all claims
  310. for key, value := range claims {
  311. tokenClaims.Claims[key] = value
  312. }
  313. return tokenClaims, nil
  314. }
  315. // mapClaimsToRoles maps token claims to SeaweedFS roles (legacy method)
  316. func (p *OIDCProvider) mapClaimsToRoles(claims *providers.TokenClaims) []string {
  317. roles := []string{}
  318. // Get groups from claims
  319. groups, _ := claims.GetClaimStringSlice("groups")
  320. // Basic role mapping based on groups
  321. for _, group := range groups {
  322. switch group {
  323. case "admins":
  324. roles = append(roles, "admin")
  325. case "developers":
  326. roles = append(roles, "readwrite")
  327. case "users":
  328. roles = append(roles, "readonly")
  329. }
  330. }
  331. if len(roles) == 0 {
  332. roles = []string{"readonly"} // Default role
  333. }
  334. return roles
  335. }
  336. // mapClaimsToRolesWithConfig maps token claims to roles using configured role mapping
  337. func (p *OIDCProvider) mapClaimsToRolesWithConfig(claims *providers.TokenClaims) []string {
  338. glog.V(3).Infof("mapClaimsToRolesWithConfig: RoleMapping is nil? %t", p.config.RoleMapping == nil)
  339. if p.config.RoleMapping == nil {
  340. glog.V(2).Infof("No role mapping configured for provider %s, using legacy mapping", p.name)
  341. // Fallback to legacy mapping if no role mapping configured
  342. return p.mapClaimsToRoles(claims)
  343. }
  344. glog.V(3).Infof("Applying %d role mapping rules", len(p.config.RoleMapping.Rules))
  345. roles := []string{}
  346. // Apply role mapping rules
  347. for i, rule := range p.config.RoleMapping.Rules {
  348. glog.V(3).Infof("Rule %d: claim=%s, value=%s, role=%s", i, rule.Claim, rule.Value, rule.Role)
  349. if rule.Matches(claims) {
  350. glog.V(2).Infof("Rule %d matched! Adding role: %s", i, rule.Role)
  351. roles = append(roles, rule.Role)
  352. } else {
  353. glog.V(3).Infof("Rule %d did not match", i)
  354. }
  355. }
  356. // Use default role if no rules matched
  357. if len(roles) == 0 && p.config.RoleMapping.DefaultRole != "" {
  358. glog.V(2).Infof("No rules matched, using default role: %s", p.config.RoleMapping.DefaultRole)
  359. roles = []string{p.config.RoleMapping.DefaultRole}
  360. }
  361. glog.V(2).Infof("Role mapping result: %v", roles)
  362. return roles
  363. }
  364. // getPublicKey retrieves the public key for the given key ID from JWKS
  365. func (p *OIDCProvider) getPublicKey(ctx context.Context, kid string) (interface{}, error) {
  366. // Fetch JWKS if not cached or refresh if expired
  367. if p.jwksCache == nil || (!p.jwksFetchedAt.IsZero() && time.Since(p.jwksFetchedAt) > p.jwksTTL) {
  368. if err := p.fetchJWKS(ctx); err != nil {
  369. return nil, fmt.Errorf("failed to fetch JWKS: %v", err)
  370. }
  371. }
  372. // Find the key with matching kid
  373. for _, key := range p.jwksCache.Keys {
  374. if key.Kid == kid {
  375. return p.parseJWK(&key)
  376. }
  377. }
  378. // Key not found in cache. Refresh JWKS once to handle key rotation and retry.
  379. if err := p.fetchJWKS(ctx); err != nil {
  380. return nil, fmt.Errorf("failed to refresh JWKS after key miss: %v", err)
  381. }
  382. for _, key := range p.jwksCache.Keys {
  383. if key.Kid == kid {
  384. return p.parseJWK(&key)
  385. }
  386. }
  387. return nil, fmt.Errorf("key with ID %s not found in JWKS after refresh", kid)
  388. }
  389. // fetchJWKS fetches the JWKS from the provider
  390. func (p *OIDCProvider) fetchJWKS(ctx context.Context) error {
  391. jwksURL := p.config.JWKSUri
  392. if jwksURL == "" {
  393. jwksURL = strings.TrimSuffix(p.config.Issuer, "/") + "/.well-known/jwks.json"
  394. }
  395. req, err := http.NewRequestWithContext(ctx, "GET", jwksURL, nil)
  396. if err != nil {
  397. return fmt.Errorf("failed to create JWKS request: %v", err)
  398. }
  399. resp, err := p.httpClient.Do(req)
  400. if err != nil {
  401. return fmt.Errorf("failed to fetch JWKS: %v", err)
  402. }
  403. defer resp.Body.Close()
  404. if resp.StatusCode != http.StatusOK {
  405. return fmt.Errorf("JWKS endpoint returned status: %d", resp.StatusCode)
  406. }
  407. var jwks JWKS
  408. if err := json.NewDecoder(resp.Body).Decode(&jwks); err != nil {
  409. return fmt.Errorf("failed to decode JWKS response: %v", err)
  410. }
  411. p.jwksCache = &jwks
  412. p.jwksFetchedAt = time.Now()
  413. glog.V(3).Infof("Fetched JWKS with %d keys from %s", len(jwks.Keys), jwksURL)
  414. return nil
  415. }
  416. // parseJWK converts a JWK to a public key
  417. func (p *OIDCProvider) parseJWK(key *JWK) (interface{}, error) {
  418. switch key.Kty {
  419. case "RSA":
  420. return p.parseRSAKey(key)
  421. case "EC":
  422. return p.parseECKey(key)
  423. default:
  424. return nil, fmt.Errorf("unsupported key type: %s", key.Kty)
  425. }
  426. }
  427. // parseRSAKey parses an RSA key from JWK
  428. func (p *OIDCProvider) parseRSAKey(key *JWK) (*rsa.PublicKey, error) {
  429. // Decode the modulus (n)
  430. nBytes, err := base64.RawURLEncoding.DecodeString(key.N)
  431. if err != nil {
  432. return nil, fmt.Errorf("failed to decode RSA modulus: %v", err)
  433. }
  434. // Decode the exponent (e)
  435. eBytes, err := base64.RawURLEncoding.DecodeString(key.E)
  436. if err != nil {
  437. return nil, fmt.Errorf("failed to decode RSA exponent: %v", err)
  438. }
  439. // Convert exponent bytes to int
  440. var exponent int
  441. for _, b := range eBytes {
  442. exponent = exponent*256 + int(b)
  443. }
  444. // Create RSA public key
  445. pubKey := &rsa.PublicKey{
  446. E: exponent,
  447. }
  448. pubKey.N = new(big.Int).SetBytes(nBytes)
  449. return pubKey, nil
  450. }
  451. // parseECKey parses an Elliptic Curve key from JWK
  452. func (p *OIDCProvider) parseECKey(key *JWK) (*ecdsa.PublicKey, error) {
  453. // Validate required fields
  454. if key.X == "" || key.Y == "" || key.Crv == "" {
  455. return nil, fmt.Errorf("incomplete EC key: missing x, y, or crv parameter")
  456. }
  457. // Get the curve
  458. var curve elliptic.Curve
  459. switch key.Crv {
  460. case "P-256":
  461. curve = elliptic.P256()
  462. case "P-384":
  463. curve = elliptic.P384()
  464. case "P-521":
  465. curve = elliptic.P521()
  466. default:
  467. return nil, fmt.Errorf("unsupported EC curve: %s", key.Crv)
  468. }
  469. // Decode x coordinate
  470. xBytes, err := base64.RawURLEncoding.DecodeString(key.X)
  471. if err != nil {
  472. return nil, fmt.Errorf("failed to decode EC x coordinate: %v", err)
  473. }
  474. // Decode y coordinate
  475. yBytes, err := base64.RawURLEncoding.DecodeString(key.Y)
  476. if err != nil {
  477. return nil, fmt.Errorf("failed to decode EC y coordinate: %v", err)
  478. }
  479. // Create EC public key
  480. pubKey := &ecdsa.PublicKey{
  481. Curve: curve,
  482. X: new(big.Int).SetBytes(xBytes),
  483. Y: new(big.Int).SetBytes(yBytes),
  484. }
  485. // Validate that the point is on the curve
  486. if !curve.IsOnCurve(pubKey.X, pubKey.Y) {
  487. return nil, fmt.Errorf("EC key coordinates are not on the specified curve")
  488. }
  489. return pubKey, nil
  490. }
  491. // mapUserInfoToIdentity maps UserInfo response to ExternalIdentity
  492. func (p *OIDCProvider) mapUserInfoToIdentity(userInfo map[string]interface{}) *providers.ExternalIdentity {
  493. identity := &providers.ExternalIdentity{
  494. Provider: p.name,
  495. Attributes: make(map[string]string),
  496. }
  497. // Map standard OIDC claims
  498. if sub, ok := userInfo["sub"].(string); ok {
  499. identity.UserID = sub
  500. }
  501. if email, ok := userInfo["email"].(string); ok {
  502. identity.Email = email
  503. }
  504. if name, ok := userInfo["name"].(string); ok {
  505. identity.DisplayName = name
  506. }
  507. // Handle groups claim (can be array of strings or single string)
  508. if groupsData, exists := userInfo["groups"]; exists {
  509. switch groups := groupsData.(type) {
  510. case []interface{}:
  511. // Array of groups
  512. for _, group := range groups {
  513. if groupStr, ok := group.(string); ok {
  514. identity.Groups = append(identity.Groups, groupStr)
  515. }
  516. }
  517. case []string:
  518. // Direct string array
  519. identity.Groups = groups
  520. case string:
  521. // Single group as string
  522. identity.Groups = []string{groups}
  523. }
  524. }
  525. // Map configured custom claims
  526. if p.config.ClaimsMapping != nil {
  527. for identityField, oidcClaim := range p.config.ClaimsMapping {
  528. if value, exists := userInfo[oidcClaim]; exists {
  529. if strValue, ok := value.(string); ok {
  530. switch identityField {
  531. case "email":
  532. if identity.Email == "" {
  533. identity.Email = strValue
  534. }
  535. case "displayName":
  536. if identity.DisplayName == "" {
  537. identity.DisplayName = strValue
  538. }
  539. case "userID":
  540. if identity.UserID == "" {
  541. identity.UserID = strValue
  542. }
  543. default:
  544. identity.Attributes[identityField] = strValue
  545. }
  546. }
  547. }
  548. }
  549. }
  550. // Store all additional claims as attributes
  551. for key, value := range userInfo {
  552. if key != "sub" && key != "email" && key != "name" && key != "groups" {
  553. if strValue, ok := value.(string); ok {
  554. identity.Attributes[key] = strValue
  555. } else if jsonValue, err := json.Marshal(value); err == nil {
  556. identity.Attributes[key] = string(jsonValue)
  557. }
  558. }
  559. }
  560. return identity
  561. }