provider.go 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. package providers
  2. import (
  3. "context"
  4. "fmt"
  5. "net/mail"
  6. "time"
  7. "github.com/seaweedfs/seaweedfs/weed/glog"
  8. "github.com/seaweedfs/seaweedfs/weed/iam/policy"
  9. )
  10. // IdentityProvider defines the interface for external identity providers
  11. type IdentityProvider interface {
  12. // Name returns the unique name of the provider
  13. Name() string
  14. // Initialize initializes the provider with configuration
  15. Initialize(config interface{}) error
  16. // Authenticate authenticates a user with a token and returns external identity
  17. Authenticate(ctx context.Context, token string) (*ExternalIdentity, error)
  18. // GetUserInfo retrieves user information by user ID
  19. GetUserInfo(ctx context.Context, userID string) (*ExternalIdentity, error)
  20. // ValidateToken validates a token and returns claims
  21. ValidateToken(ctx context.Context, token string) (*TokenClaims, error)
  22. }
  23. // ExternalIdentity represents an identity from an external provider
  24. type ExternalIdentity struct {
  25. // UserID is the unique identifier from the external provider
  26. UserID string `json:"userId"`
  27. // Email is the user's email address
  28. Email string `json:"email"`
  29. // DisplayName is the user's display name
  30. DisplayName string `json:"displayName"`
  31. // Groups are the groups the user belongs to
  32. Groups []string `json:"groups,omitempty"`
  33. // Attributes are additional user attributes
  34. Attributes map[string]string `json:"attributes,omitempty"`
  35. // Provider is the name of the identity provider
  36. Provider string `json:"provider"`
  37. }
  38. // Validate validates the external identity structure
  39. func (e *ExternalIdentity) Validate() error {
  40. if e.UserID == "" {
  41. return fmt.Errorf("user ID is required")
  42. }
  43. if e.Provider == "" {
  44. return fmt.Errorf("provider is required")
  45. }
  46. if e.Email != "" {
  47. if _, err := mail.ParseAddress(e.Email); err != nil {
  48. return fmt.Errorf("invalid email format: %w", err)
  49. }
  50. }
  51. return nil
  52. }
  53. // TokenClaims represents claims from a validated token
  54. type TokenClaims struct {
  55. // Subject (sub) - user identifier
  56. Subject string `json:"sub"`
  57. // Issuer (iss) - token issuer
  58. Issuer string `json:"iss"`
  59. // Audience (aud) - intended audience
  60. Audience string `json:"aud"`
  61. // ExpiresAt (exp) - expiration time
  62. ExpiresAt time.Time `json:"exp"`
  63. // IssuedAt (iat) - issued at time
  64. IssuedAt time.Time `json:"iat"`
  65. // NotBefore (nbf) - not valid before time
  66. NotBefore time.Time `json:"nbf,omitempty"`
  67. // Claims are additional claims from the token
  68. Claims map[string]interface{} `json:"claims,omitempty"`
  69. }
  70. // IsValid checks if the token claims are valid (not expired, etc.)
  71. func (c *TokenClaims) IsValid() bool {
  72. now := time.Now()
  73. // Check expiration
  74. if !c.ExpiresAt.IsZero() && now.After(c.ExpiresAt) {
  75. return false
  76. }
  77. // Check not before
  78. if !c.NotBefore.IsZero() && now.Before(c.NotBefore) {
  79. return false
  80. }
  81. // Check issued at (shouldn't be in the future)
  82. if !c.IssuedAt.IsZero() && now.Before(c.IssuedAt) {
  83. return false
  84. }
  85. return true
  86. }
  87. // GetClaimString returns a string claim value
  88. func (c *TokenClaims) GetClaimString(key string) (string, bool) {
  89. if value, exists := c.Claims[key]; exists {
  90. if str, ok := value.(string); ok {
  91. return str, true
  92. }
  93. }
  94. return "", false
  95. }
  96. // GetClaimStringSlice returns a string slice claim value
  97. func (c *TokenClaims) GetClaimStringSlice(key string) ([]string, bool) {
  98. if value, exists := c.Claims[key]; exists {
  99. switch v := value.(type) {
  100. case []string:
  101. return v, true
  102. case []interface{}:
  103. var result []string
  104. for _, item := range v {
  105. if str, ok := item.(string); ok {
  106. result = append(result, str)
  107. }
  108. }
  109. return result, len(result) > 0
  110. case string:
  111. // Single string can be treated as slice
  112. return []string{v}, true
  113. }
  114. }
  115. return nil, false
  116. }
  117. // ProviderConfig represents configuration for identity providers
  118. type ProviderConfig struct {
  119. // Type of provider (oidc, ldap, saml)
  120. Type string `json:"type"`
  121. // Name of the provider instance
  122. Name string `json:"name"`
  123. // Enabled indicates if the provider is active
  124. Enabled bool `json:"enabled"`
  125. // Config is provider-specific configuration
  126. Config map[string]interface{} `json:"config"`
  127. // RoleMapping defines how to map external identities to roles
  128. RoleMapping *RoleMapping `json:"roleMapping,omitempty"`
  129. }
  130. // RoleMapping defines rules for mapping external identities to roles
  131. type RoleMapping struct {
  132. // Rules are the mapping rules
  133. Rules []MappingRule `json:"rules"`
  134. // DefaultRole is assigned if no rules match
  135. DefaultRole string `json:"defaultRole,omitempty"`
  136. }
  137. // MappingRule defines a single mapping rule
  138. type MappingRule struct {
  139. // Claim is the claim key to check
  140. Claim string `json:"claim"`
  141. // Value is the expected claim value (supports wildcards)
  142. Value string `json:"value"`
  143. // Role is the role ARN to assign
  144. Role string `json:"role"`
  145. // Condition is additional condition logic (optional)
  146. Condition string `json:"condition,omitempty"`
  147. }
  148. // Matches checks if a rule matches the given claims
  149. func (r *MappingRule) Matches(claims *TokenClaims) bool {
  150. if r.Claim == "" || r.Value == "" {
  151. glog.V(3).Infof("Rule invalid: claim=%s, value=%s", r.Claim, r.Value)
  152. return false
  153. }
  154. claimValue, exists := claims.GetClaimString(r.Claim)
  155. if !exists {
  156. glog.V(3).Infof("Claim '%s' not found as string, trying as string slice", r.Claim)
  157. // Try as string slice
  158. if claimSlice, sliceExists := claims.GetClaimStringSlice(r.Claim); sliceExists {
  159. glog.V(3).Infof("Claim '%s' found as string slice: %v", r.Claim, claimSlice)
  160. for _, val := range claimSlice {
  161. glog.V(3).Infof("Checking if '%s' matches rule value '%s'", val, r.Value)
  162. if r.matchValue(val) {
  163. glog.V(3).Infof("Match found: '%s' matches '%s'", val, r.Value)
  164. return true
  165. }
  166. }
  167. } else {
  168. glog.V(3).Infof("Claim '%s' not found in any format", r.Claim)
  169. }
  170. return false
  171. }
  172. glog.V(3).Infof("Claim '%s' found as string: '%s'", r.Claim, claimValue)
  173. return r.matchValue(claimValue)
  174. }
  175. // matchValue checks if a value matches the rule value (with wildcard support)
  176. // Uses AWS IAM-compliant case-insensitive wildcard matching for consistency with policy engine
  177. func (r *MappingRule) matchValue(value string) bool {
  178. matched := policy.AwsWildcardMatch(r.Value, value)
  179. glog.V(3).Infof("AWS IAM pattern match result: '%s' matches '%s' = %t", value, r.Value, matched)
  180. return matched
  181. }