| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227 |
- package providers
- import (
- "context"
- "fmt"
- "net/mail"
- "time"
- "github.com/seaweedfs/seaweedfs/weed/glog"
- "github.com/seaweedfs/seaweedfs/weed/iam/policy"
- )
- // IdentityProvider defines the interface for external identity providers
- type IdentityProvider interface {
- // Name returns the unique name of the provider
- Name() string
- // Initialize initializes the provider with configuration
- Initialize(config interface{}) error
- // Authenticate authenticates a user with a token and returns external identity
- Authenticate(ctx context.Context, token string) (*ExternalIdentity, error)
- // GetUserInfo retrieves user information by user ID
- GetUserInfo(ctx context.Context, userID string) (*ExternalIdentity, error)
- // ValidateToken validates a token and returns claims
- ValidateToken(ctx context.Context, token string) (*TokenClaims, error)
- }
- // ExternalIdentity represents an identity from an external provider
- type ExternalIdentity struct {
- // UserID is the unique identifier from the external provider
- UserID string `json:"userId"`
- // Email is the user's email address
- Email string `json:"email"`
- // DisplayName is the user's display name
- DisplayName string `json:"displayName"`
- // Groups are the groups the user belongs to
- Groups []string `json:"groups,omitempty"`
- // Attributes are additional user attributes
- Attributes map[string]string `json:"attributes,omitempty"`
- // Provider is the name of the identity provider
- Provider string `json:"provider"`
- }
- // Validate validates the external identity structure
- func (e *ExternalIdentity) Validate() error {
- if e.UserID == "" {
- return fmt.Errorf("user ID is required")
- }
- if e.Provider == "" {
- return fmt.Errorf("provider is required")
- }
- if e.Email != "" {
- if _, err := mail.ParseAddress(e.Email); err != nil {
- return fmt.Errorf("invalid email format: %w", err)
- }
- }
- return nil
- }
- // TokenClaims represents claims from a validated token
- type TokenClaims struct {
- // Subject (sub) - user identifier
- Subject string `json:"sub"`
- // Issuer (iss) - token issuer
- Issuer string `json:"iss"`
- // Audience (aud) - intended audience
- Audience string `json:"aud"`
- // ExpiresAt (exp) - expiration time
- ExpiresAt time.Time `json:"exp"`
- // IssuedAt (iat) - issued at time
- IssuedAt time.Time `json:"iat"`
- // NotBefore (nbf) - not valid before time
- NotBefore time.Time `json:"nbf,omitempty"`
- // Claims are additional claims from the token
- Claims map[string]interface{} `json:"claims,omitempty"`
- }
- // IsValid checks if the token claims are valid (not expired, etc.)
- func (c *TokenClaims) IsValid() bool {
- now := time.Now()
- // Check expiration
- if !c.ExpiresAt.IsZero() && now.After(c.ExpiresAt) {
- return false
- }
- // Check not before
- if !c.NotBefore.IsZero() && now.Before(c.NotBefore) {
- return false
- }
- // Check issued at (shouldn't be in the future)
- if !c.IssuedAt.IsZero() && now.Before(c.IssuedAt) {
- return false
- }
- return true
- }
- // GetClaimString returns a string claim value
- func (c *TokenClaims) GetClaimString(key string) (string, bool) {
- if value, exists := c.Claims[key]; exists {
- if str, ok := value.(string); ok {
- return str, true
- }
- }
- return "", false
- }
- // GetClaimStringSlice returns a string slice claim value
- func (c *TokenClaims) GetClaimStringSlice(key string) ([]string, bool) {
- if value, exists := c.Claims[key]; exists {
- switch v := value.(type) {
- case []string:
- return v, true
- case []interface{}:
- var result []string
- for _, item := range v {
- if str, ok := item.(string); ok {
- result = append(result, str)
- }
- }
- return result, len(result) > 0
- case string:
- // Single string can be treated as slice
- return []string{v}, true
- }
- }
- return nil, false
- }
- // ProviderConfig represents configuration for identity providers
- type ProviderConfig struct {
- // Type of provider (oidc, ldap, saml)
- Type string `json:"type"`
- // Name of the provider instance
- Name string `json:"name"`
- // Enabled indicates if the provider is active
- Enabled bool `json:"enabled"`
- // Config is provider-specific configuration
- Config map[string]interface{} `json:"config"`
- // RoleMapping defines how to map external identities to roles
- RoleMapping *RoleMapping `json:"roleMapping,omitempty"`
- }
- // RoleMapping defines rules for mapping external identities to roles
- type RoleMapping struct {
- // Rules are the mapping rules
- Rules []MappingRule `json:"rules"`
- // DefaultRole is assigned if no rules match
- DefaultRole string `json:"defaultRole,omitempty"`
- }
- // MappingRule defines a single mapping rule
- type MappingRule struct {
- // Claim is the claim key to check
- Claim string `json:"claim"`
- // Value is the expected claim value (supports wildcards)
- Value string `json:"value"`
- // Role is the role ARN to assign
- Role string `json:"role"`
- // Condition is additional condition logic (optional)
- Condition string `json:"condition,omitempty"`
- }
- // Matches checks if a rule matches the given claims
- func (r *MappingRule) Matches(claims *TokenClaims) bool {
- if r.Claim == "" || r.Value == "" {
- glog.V(3).Infof("Rule invalid: claim=%s, value=%s", r.Claim, r.Value)
- return false
- }
- claimValue, exists := claims.GetClaimString(r.Claim)
- if !exists {
- glog.V(3).Infof("Claim '%s' not found as string, trying as string slice", r.Claim)
- // Try as string slice
- if claimSlice, sliceExists := claims.GetClaimStringSlice(r.Claim); sliceExists {
- glog.V(3).Infof("Claim '%s' found as string slice: %v", r.Claim, claimSlice)
- for _, val := range claimSlice {
- glog.V(3).Infof("Checking if '%s' matches rule value '%s'", val, r.Value)
- if r.matchValue(val) {
- glog.V(3).Infof("Match found: '%s' matches '%s'", val, r.Value)
- return true
- }
- }
- } else {
- glog.V(3).Infof("Claim '%s' not found in any format", r.Claim)
- }
- return false
- }
- glog.V(3).Infof("Claim '%s' found as string: '%s'", r.Claim, claimValue)
- return r.matchValue(claimValue)
- }
- // matchValue checks if a value matches the rule value (with wildcard support)
- // Uses AWS IAM-compliant case-insensitive wildcard matching for consistency with policy engine
- func (r *MappingRule) matchValue(value string) bool {
- matched := policy.AwsWildcardMatch(r.Value, value)
- glog.V(3).Infof("AWS IAM pattern match result: '%s' matches '%s' = %t", value, r.Value, matched)
- return matched
- }
|