mirror of
https://github.com/lukaszraczylo/traefikoidc.git
synced 2026-10-15 04:18:03 +00:00
Splitting code into multiple files with reasonable testing coverage.
```
ok github.com/lukaszraczylo/traefikoidc 117.017s coverage: 72.6% of statements
ok github.com/lukaszraczylo/traefikoidc/auth 0.505s coverage: 87.1% of statements
ok github.com/lukaszraczylo/traefikoidc/circuit_breaker 0.283s coverage: 99.0% of statements
github.com/lukaszraczylo/traefikoidc/config coverage: 0.0% of statements
ok github.com/lukaszraczylo/traefikoidc/handlers 0.349s coverage: 98.2% of statements
ok github.com/lukaszraczylo/traefikoidc/internal/providers (cached) coverage: 94.3% of statements
ok github.com/lukaszraczylo/traefikoidc/middleware 0.808s coverage: 78.0% of statements
ok github.com/lukaszraczylo/traefikoidc/recovery 0.653s coverage: 100.0% of statements
ok github.com/lukaszraczylo/traefikoidc/session/chunking (cached) coverage: 87.8% of statements
ok github.com/lukaszraczylo/traefikoidc/session/core (cached) coverage: 85.6% of statements
ok github.com/lukaszraczylo/traefikoidc/session/crypto (cached) coverage: 81.8% of statements
ok github.com/lukaszraczylo/traefikoidc/session/storage (cached) coverage: 93.5% of statements
ok github.com/lukaszraczylo/traefikoidc/session/validators (cached) coverage: 98.8% of statements
````
This commit is contained in:
@@ -31,251 +31,9 @@ func createDefaultHTTPClient() *http.Client {
|
||||
}
|
||||
|
||||
const (
|
||||
ConstSessionTimeout = 86400
|
||||
defaultBlacklistDuration = 24 * time.Hour
|
||||
defaultMaxBlacklistSize = 10000
|
||||
ConstSessionTimeout = 86400
|
||||
)
|
||||
|
||||
var (
|
||||
globalCacheManager *CacheManager
|
||||
cacheManagerOnce sync.Once
|
||||
cacheManagerMutex sync.RWMutex
|
||||
)
|
||||
|
||||
// CacheManager manages all caching components for the OIDC middleware.
|
||||
// It provides thread-safe access to token blacklist, token cache, metadata cache,
|
||||
// and JWK cache. This centralizes cache management to ensure efficient memory
|
||||
// usage and consistent cache behavior across the application.
|
||||
// CacheInterface defines the common cache operations
|
||||
type CacheInterface interface {
|
||||
Set(key string, value interface{}, ttl time.Duration)
|
||||
Get(key string) (interface{}, bool)
|
||||
Delete(key string)
|
||||
SetMaxSize(size int)
|
||||
Cleanup()
|
||||
Close()
|
||||
}
|
||||
|
||||
type CacheManager struct {
|
||||
tokenBlacklist CacheInterface
|
||||
tokenCache *TokenCache
|
||||
metadataCache *MetadataCache
|
||||
jwkCache JWKCacheInterface
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
// GetGlobalCacheManager returns a singleton instance of CacheManager.
|
||||
// It initializes all cache components on first call and reuses the same instance
|
||||
// for subsequent calls. This ensures consistent cache state and efficient memory
|
||||
// usage across the entire application lifecycle.
|
||||
// Parameters:
|
||||
// - wg: WaitGroup for coordinating cache cleanup during shutdown.
|
||||
//
|
||||
// Returns:
|
||||
// - The global CacheManager instance.
|
||||
func GetGlobalCacheManager(wg *sync.WaitGroup) *CacheManager {
|
||||
cacheManagerOnce.Do(func() {
|
||||
globalCacheManager = &CacheManager{
|
||||
tokenBlacklist: func() CacheInterface {
|
||||
config := DefaultUnifiedCacheConfig()
|
||||
config.MaxSize = defaultMaxBlacklistSize
|
||||
c := NewUnifiedCache(config)
|
||||
return NewCacheAdapter(c)
|
||||
}(),
|
||||
tokenCache: NewTokenCache(),
|
||||
metadataCache: NewMetadataCache(wg),
|
||||
jwkCache: &JWKCache{},
|
||||
}
|
||||
})
|
||||
return globalCacheManager
|
||||
}
|
||||
|
||||
// GetSharedTokenBlacklist returns the shared token blacklist cache.
|
||||
// This cache stores revoked or expired tokens to prevent replay attacks.
|
||||
// Access is protected by read lock to ensure thread safety.
|
||||
// Returns:
|
||||
// - The shared token blacklist CacheInterface instance.
|
||||
func (cm *CacheManager) GetSharedTokenBlacklist() CacheInterface {
|
||||
cm.mu.RLock()
|
||||
defer cm.mu.RUnlock()
|
||||
return cm.tokenBlacklist
|
||||
}
|
||||
|
||||
// GetSharedTokenCache returns the shared token cache for verified tokens.
|
||||
// This cache stores claims from successfully verified tokens to avoid
|
||||
// repeated verification of the same tokens.
|
||||
// Access is protected by read lock to ensure thread safety.
|
||||
// Returns:
|
||||
// - The shared TokenCache instance.
|
||||
func (cm *CacheManager) GetSharedTokenCache() *TokenCache {
|
||||
cm.mu.RLock()
|
||||
defer cm.mu.RUnlock()
|
||||
return cm.tokenCache
|
||||
}
|
||||
|
||||
// GetSharedMetadataCache returns the shared metadata cache.
|
||||
// This cache stores OIDC provider metadata (endpoints, keys) to avoid
|
||||
// repeated requests to the provider's .well-known configuration endpoint.
|
||||
// Access is protected by read lock to ensure thread safety.
|
||||
// Returns:
|
||||
// - The shared MetadataCache instance.
|
||||
func (cm *CacheManager) GetSharedMetadataCache() *MetadataCache {
|
||||
cm.mu.RLock()
|
||||
defer cm.mu.RUnlock()
|
||||
return cm.metadataCache
|
||||
}
|
||||
|
||||
// GetSharedJWKCache returns the shared JWK (JSON Web Key) cache.
|
||||
// This cache stores public keys from the provider's JWKS endpoint
|
||||
// for token signature verification.
|
||||
// Access is protected by read lock to ensure thread safety.
|
||||
// Returns:
|
||||
// - The shared JWKCacheInterface instance.
|
||||
func (cm *CacheManager) GetSharedJWKCache() JWKCacheInterface {
|
||||
cm.mu.RLock()
|
||||
defer cm.mu.RUnlock()
|
||||
return cm.jwkCache
|
||||
}
|
||||
|
||||
// Close gracefully shuts down all cache components and releases resources.
|
||||
// It closes all individual caches and cleans up their associated goroutines.
|
||||
// This method should be called when the middleware is shutting down.
|
||||
// Safe to call multiple times.
|
||||
// Returns:
|
||||
// - An error if any cache fails to close properly, nil otherwise.
|
||||
func (cm *CacheManager) Close() error {
|
||||
cm.mu.Lock()
|
||||
defer cm.mu.Unlock()
|
||||
|
||||
if cm.tokenBlacklist != nil {
|
||||
cm.tokenBlacklist.Close()
|
||||
cm.tokenBlacklist = nil
|
||||
}
|
||||
if cm.tokenCache != nil {
|
||||
cm.tokenCache.Close()
|
||||
cm.tokenCache = nil
|
||||
}
|
||||
if cm.metadataCache != nil {
|
||||
cm.metadataCache.Close()
|
||||
cm.metadataCache = nil
|
||||
}
|
||||
if cm.jwkCache != nil {
|
||||
cm.jwkCache.Close()
|
||||
cm.jwkCache = nil
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanupGlobalCacheManager cleans up the global cache manager instance.
|
||||
// It closes all cache components but does NOT reset the singleton for re-initialization
|
||||
// to avoid race conditions with sync.Once. It's safe to call multiple times.
|
||||
// Returns:
|
||||
// - An error if cache cleanup fails, nil otherwise.
|
||||
func CleanupGlobalCacheManager() error {
|
||||
cacheManagerMutex.Lock()
|
||||
defer cacheManagerMutex.Unlock()
|
||||
|
||||
if globalCacheManager != nil {
|
||||
err := globalCacheManager.Close()
|
||||
// Don't reset globalCacheManager to nil or cacheManagerOnce to avoid race conditions
|
||||
// The cache manager remains closed and the Close() method handles multiple calls
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// TokenVerifier interface defines token verification capabilities.
|
||||
// Implementations should validate token format, signature, and claims.
|
||||
type TokenVerifier interface {
|
||||
VerifyToken(token string) error
|
||||
}
|
||||
|
||||
// JWTVerifier interface defines JWT-specific verification capabilities.
|
||||
// Implementations should validate JWT structure, signature using JWKs, and standard claims.
|
||||
type JWTVerifier interface {
|
||||
VerifyJWTSignatureAndClaims(jwt *JWT, token string) error
|
||||
}
|
||||
|
||||
// TokenExchanger interface defines OAuth 2.0 and OpenID Connect token exchange capabilities.
|
||||
// Implementations should handle authorization code exchange, refresh tokens, and revocation
|
||||
// according to the OAuth 2.0 and OpenID Connect specifications.
|
||||
type TokenExchanger interface {
|
||||
ExchangeCodeForToken(ctx context.Context, grantType string, codeOrToken string, redirectURL string, codeVerifier string) (*TokenResponse, error)
|
||||
GetNewTokenWithRefreshToken(refreshToken string) (*TokenResponse, error)
|
||||
RevokeTokenWithProvider(token, tokenType string) error
|
||||
}
|
||||
|
||||
// TraefikOidc is the main middleware struct that implements OIDC authentication for Traefik.
|
||||
// It integrates with various OIDC providers, manages sessions, caches tokens, and handles
|
||||
// the complete authentication flow. It's designed to work seamlessly with Traefik's
|
||||
// plugin system and provides flexible configuration options.
|
||||
type TraefikOidc struct {
|
||||
jwkCache JWKCacheInterface
|
||||
jwtVerifier JWTVerifier
|
||||
ctx context.Context
|
||||
tokenVerifier TokenVerifier
|
||||
next http.Handler
|
||||
tokenExchanger TokenExchanger
|
||||
initComplete chan struct{}
|
||||
limiter *rate.Limiter
|
||||
tokenBlacklist CacheInterface
|
||||
headerTemplates map[string]*template.Template
|
||||
sessionManager *SessionManager
|
||||
tokenCleanupStopChan chan struct{}
|
||||
excludedURLs map[string]struct{}
|
||||
extractClaimsFunc func(tokenString string) (map[string]interface{}, error)
|
||||
initiateAuthenticationFunc func(rw http.ResponseWriter, req *http.Request, session *SessionData, redirectURL string)
|
||||
metadataCache *MetadataCache
|
||||
allowedRolesAndGroups map[string]struct{}
|
||||
allowedUsers map[string]struct{}
|
||||
allowedUserDomains map[string]struct{}
|
||||
tokenCache *TokenCache
|
||||
httpClient *http.Client
|
||||
tokenHTTPClient *http.Client
|
||||
logger *Logger
|
||||
metadataRefreshStopChan chan struct{}
|
||||
cancelFunc context.CancelFunc
|
||||
errorRecoveryManager *ErrorRecoveryManager
|
||||
goroutineWG *sync.WaitGroup
|
||||
clientSecret string
|
||||
clientID string
|
||||
name string
|
||||
redirURLPath string
|
||||
logoutURLPath string
|
||||
tokenURL string
|
||||
authURL string
|
||||
endSessionURL string
|
||||
postLogoutRedirectURI string
|
||||
scheme string
|
||||
jwksURL string
|
||||
issuerURL string
|
||||
revocationURL string
|
||||
providerURL string
|
||||
scopes []string
|
||||
refreshGracePeriod time.Duration
|
||||
shutdownOnce sync.Once
|
||||
firstRequestMutex sync.Mutex
|
||||
forceHTTPS bool
|
||||
enablePKCE bool
|
||||
overrideScopes bool
|
||||
suppressDiagnosticLogs bool
|
||||
firstRequestReceived bool
|
||||
metadataRefreshStarted bool
|
||||
}
|
||||
|
||||
// ProviderMetadata represents OIDC provider configuration data.
|
||||
// This data is typically retrieved from the provider's .well-known/openid-configuration endpoint
|
||||
// and contains essential URLs for authentication, token exchange, and key retrieval.
|
||||
type ProviderMetadata struct {
|
||||
Issuer string `json:"issuer"`
|
||||
AuthURL string `json:"authorization_endpoint"`
|
||||
TokenURL string `json:"token_endpoint"`
|
||||
JWKSURL string `json:"jwks_uri"`
|
||||
RevokeURL string `json:"revocation_endpoint"`
|
||||
EndSessionURL string `json:"end_session_endpoint"`
|
||||
}
|
||||
|
||||
// isTestMode detects if the code is running in a test environment.
|
||||
// It checks various indicators including environment variables, command-line arguments,
|
||||
// and runtime compiler information to determine test context.
|
||||
|
||||
Reference in New Issue
Block a user