package providers import ( "fmt" "net/url" "strings" ) // ConfigValidator provides common configuration validation utilities for providers. type ConfigValidator struct{} // NewConfigValidator creates a new configuration validator. func NewConfigValidator() *ConfigValidator { return &ConfigValidator{} } // ValidateIssuerURL validates that an issuer URL is properly formatted and accessible. func (v *ConfigValidator) ValidateIssuerURL(issuerURL string) error { if issuerURL == "" { return fmt.Errorf("issuer URL cannot be empty") } parsedURL, err := url.Parse(issuerURL) if err != nil { return fmt.Errorf("invalid issuer URL format: %w", err) } if parsedURL.Scheme == "" { return fmt.Errorf("issuer URL must include scheme (http/https)") } if parsedURL.Scheme != "http" && parsedURL.Scheme != "https" { return fmt.Errorf("issuer URL scheme must be http or https") } if parsedURL.Host == "" { return fmt.Errorf("issuer URL must include host") } return nil } // ValidateClientID validates that a client ID is properly formatted. func (v *ConfigValidator) ValidateClientID(clientID string) error { if clientID == "" { return fmt.Errorf("client ID cannot be empty") } if len(clientID) < 3 { return fmt.Errorf("client ID appears to be too short") } return nil } // ValidateScopes validates that the provided scopes are reasonable. func (v *ConfigValidator) ValidateScopes(scopes []string) error { if len(scopes) == 0 { return fmt.Errorf("at least one scope must be provided") } hasOpenIDScope := false for _, scope := range scopes { if strings.TrimSpace(scope) == ScopeOpenID { hasOpenIDScope = true break } } if !hasOpenIDScope { return fmt.Errorf("'openid' scope is required for OIDC authentication") } return nil } // ValidateRedirectURL validates that a redirect URL is properly formatted. func (v *ConfigValidator) ValidateRedirectURL(redirectURL string) error { if redirectURL == "" { return fmt.Errorf("redirect URL cannot be empty") } parsedURL, err := url.Parse(redirectURL) if err != nil { return fmt.Errorf("invalid redirect URL format: %w", err) } if parsedURL.Scheme == "" { return fmt.Errorf("redirect URL must include scheme (http/https)") } return nil } // ValidateProviderSpecificConfig performs provider-specific validation. func (v *ConfigValidator) ValidateProviderSpecificConfig(provider OIDCProvider, config map[string]interface{}) error { switch provider.GetType() { case ProviderTypeGoogle: return v.validateGoogleConfig(config) case ProviderTypeAzure: return v.validateAzureConfig(config) case ProviderTypeGeneric: return v.validateGenericConfig(config) default: return fmt.Errorf("unknown provider type: %d", provider.GetType()) } } // validateGoogleConfig validates Google-specific configuration. func (v *ConfigValidator) validateGoogleConfig(config map[string]interface{}) error { if issuerURL, ok := config["issuer_url"].(string); ok { if !strings.Contains(issuerURL, "accounts.google.com") { return fmt.Errorf("google provider requires issuer URL to contain accounts.google.com") } } return nil } // validateAzureConfig validates Azure-specific configuration. func (v *ConfigValidator) validateAzureConfig(config map[string]interface{}) error { if issuerURL, ok := config["issuer_url"].(string); ok { if !strings.Contains(issuerURL, "login.microsoftonline.com") && !strings.Contains(issuerURL, "sts.windows.net") { return fmt.Errorf("azure provider requires issuer URL to contain login.microsoftonline.com or sts.windows.net") } } if issuerURL, ok := config["issuer_url"].(string); ok { parsedURL, err := url.Parse(issuerURL) if err == nil { pathParts := strings.Split(parsedURL.Path, "/") hasTenantID := false for _, part := range pathParts { if len(part) == 36 && strings.Count(part, "-") == 4 { hasTenantID = true break } } if !hasTenantID { return fmt.Errorf("azure issuer URL should include tenant ID") } } } return nil } // validateGenericConfig validates generic OIDC provider configuration. func (v *ConfigValidator) validateGenericConfig(config map[string]interface{}) error { return nil }