package providers import ( "fmt" "net/url" "strings" ) // ProviderFactory encapsulates the logic for creating and configuring OIDC providers. type ProviderFactory struct { registry *ProviderRegistry } // NewProviderFactory creates a new factory with a pre-configured registry. func NewProviderFactory() *ProviderFactory { registry := NewProviderRegistry() registry.RegisterProvider(NewGenericProvider()) registry.RegisterProvider(NewGoogleProvider()) registry.RegisterProvider(NewAzureProvider()) registry.RegisterProvider(NewGitHubProvider()) registry.RegisterProvider(NewAuth0Provider()) registry.RegisterProvider(NewOktaProvider()) registry.RegisterProvider(NewKeycloakProvider()) registry.RegisterProvider(NewAWSCognitoProvider()) registry.RegisterProvider(NewGitLabProvider()) return &ProviderFactory{ registry: registry, } } // CreateProvider creates an OIDC provider based on the issuer URL. // It automatically detects the provider type and returns a configured instance. func (f *ProviderFactory) CreateProvider(issuerURL string) (OIDCProvider, error) { if issuerURL == "" { return nil, fmt.Errorf("issuer URL cannot be empty") } parsedURL, err := url.Parse(issuerURL) if err != nil { return nil, fmt.Errorf("invalid issuer URL format: %w", err) } // Check if the URL has a valid scheme and host if parsedURL.Scheme == "" || parsedURL.Host == "" { return nil, fmt.Errorf("invalid issuer URL format: URL must have a valid scheme and host") } provider := f.registry.DetectProvider(issuerURL) if provider == nil { return nil, fmt.Errorf("unable to detect provider for issuer URL: %s", issuerURL) } if err := provider.ValidateConfig(); err != nil { return nil, fmt.Errorf("provider configuration validation failed: %w", err) } return provider, nil } // CreateProviderByType creates a provider instance of the specified type. // This is useful when you want to force a specific provider type regardless of URL. func (f *ProviderFactory) CreateProviderByType(providerType ProviderType) (OIDCProvider, error) { var provider OIDCProvider switch providerType { case ProviderTypeGeneric: provider = NewGenericProvider() case ProviderTypeGoogle: provider = NewGoogleProvider() case ProviderTypeAzure: provider = NewAzureProvider() case ProviderTypeGitHub: provider = NewGitHubProvider() case ProviderTypeAuth0: provider = NewAuth0Provider() case ProviderTypeOkta: provider = NewOktaProvider() case ProviderTypeKeycloak: provider = NewKeycloakProvider() case ProviderTypeAWSCognito: provider = NewAWSCognitoProvider() case ProviderTypeGitLab: provider = NewGitLabProvider() default: return nil, fmt.Errorf("unsupported provider type: %d", providerType) } if err := provider.ValidateConfig(); err != nil { return nil, fmt.Errorf("provider configuration validation failed: %w", err) } return provider, nil } // GetSupportedProviders returns a list of all supported provider types and their detection patterns. func (f *ProviderFactory) GetSupportedProviders() map[ProviderType][]string { return map[ProviderType][]string{ ProviderTypeGeneric: {"*"}, ProviderTypeGoogle: {"accounts.google.com"}, ProviderTypeAzure: {"login.microsoftonline.com", "sts.windows.net"}, ProviderTypeGitHub: {"github.com"}, ProviderTypeAuth0: {".auth0.com"}, ProviderTypeOkta: {".okta.com", ".oktapreview.com", ".okta-emea.com"}, ProviderTypeKeycloak: {"keycloak"}, ProviderTypeAWSCognito: {"cognito-idp", ".amazonaws.com"}, ProviderTypeGitLab: {"gitlab.com"}, } } // DetectProviderType determines the provider type for a given issuer URL. // This is useful for diagnostic purposes or UI display. func (f *ProviderFactory) DetectProviderType(issuerURL string) (ProviderType, error) { provider, err := f.CreateProvider(issuerURL) if err != nil { return ProviderTypeGeneric, err } return provider.GetType(), nil } // IsProviderSupported checks if a given issuer URL is supported by any registered provider. func (f *ProviderFactory) IsProviderSupported(issuerURL string) bool { if issuerURL == "" { return false } normalizedURL, err := url.Parse(issuerURL) if err != nil { return false } // Check if the URL has a valid scheme and host if normalizedURL.Scheme == "" || normalizedURL.Host == "" { return false } host := strings.ToLower(normalizedURL.Host) supportedProviders := f.GetSupportedProviders() for _, patterns := range supportedProviders { for _, pattern := range patterns { if pattern == "*" || strings.Contains(host, strings.ToLower(pattern)) { return true } } } return false }