From 667b4213fe2fdc9eb0833594bf58c2fe39cec0f6 Mon Sep 17 00:00:00 2001 From: Lukasz Raczylo Date: Fri, 20 Jun 2025 19:23:26 +0100 Subject: [PATCH] Well.. that escalated quickly. Completely forgot that Traefik uses outdated Yaegi and requires compatibility with 1.20 ( pre-generic Go code ). --- azure_oidc_test.go | 22 ++-- cache.go | 6 +- cache_test.go | 2 +- error_recovery.go | 47 +++++---- error_recovery_test.go | 17 +-- google_session_test.go | 17 +-- helpers.go | 10 +- input_validation.go | 2 +- input_validation_test.go | 4 +- jwt.go | 8 +- main.go | 37 ++++--- main_test.go | 152 +++++++++++++-------------- robustness_test.go | 42 ++++---- security_edge_cases_test.go | 55 ++++++---- security_monitoring.go | 38 +++---- security_monitoring_test.go | 25 +++-- session.go | 22 ++-- session_test.go | 10 +- settings.go | 12 +-- templated_header_execution_test.go | 150 +++++++++++++------------- templated_header_integration_test.go | 52 +++++---- test_tokens.go | 45 ++++---- token_corruption_test.go | 17 +-- token_handling_test.go | 21 ++-- 24 files changed, 439 insertions(+), 374 deletions(-) diff --git a/azure_oidc_test.go b/azure_oidc_test.go index 3159c73..033690b 100644 --- a/azure_oidc_test.go +++ b/azure_oidc_test.go @@ -17,7 +17,7 @@ type mockTraefikOidc struct { // Override VerifyToken to avoid JWKS lookup in tests func (m *mockTraefikOidc) VerifyToken(token string) error { // Cache test claims to avoid "claims not found" errors - testClaims := map[string]any{ + testClaims := map[string]interface{}{ "exp": float64(time.Now().Add(1 * time.Hour).Unix()), "iat": float64(time.Now().Unix()), "sub": "test-user", @@ -30,7 +30,7 @@ func (m *mockTraefikOidc) VerifyToken(token string) error { // Override VerifyJWTSignatureAndClaims to avoid JWKS lookup in tests func (m *mockTraefikOidc) VerifyJWTSignatureAndClaims(jwt *JWT, token string) error { // Cache test claims to avoid "claims not found" errors - testClaims := map[string]any{ + testClaims := map[string]interface{}{ "exp": float64(time.Now().Add(1 * time.Hour).Unix()), "iat": float64(time.Now().Unix()), "sub": "test-user", @@ -80,7 +80,7 @@ func TestAzureOIDCRegression(t *testing.T) { // For test tokens, always return success and cache claims if strings.HasPrefix(token, "eyJhbGciOiJSUzI1NiIsImtpZCI6InRlc3Qta2V5LWlkIiwidHlwIjoiSldUIn0") { // Cache test claims for JWT tokens - testClaims := map[string]any{ + testClaims := map[string]interface{}{ "exp": float64(time.Now().Add(1 * time.Hour).Unix()), "iat": float64(time.Now().Unix()), "sub": "test-user", @@ -94,7 +94,7 @@ func TestAzureOIDCRegression(t *testing.T) { return nil } // For JWT tokens, cache basic claims to avoid cache lookup issues - testClaims := map[string]any{ + testClaims := map[string]interface{}{ "exp": float64(time.Now().Add(1 * time.Hour).Unix()), "iat": float64(time.Now().Unix()), "sub": "test-user", @@ -109,7 +109,7 @@ func TestAzureOIDCRegression(t *testing.T) { tOidc.jwtVerifier = &mockJWTVerifier{ verifyFunc: func(jwt *JWT, token string) error { // Also cache claims here to ensure they're available - testClaims := map[string]any{ + testClaims := map[string]interface{}{ "exp": float64(time.Now().Add(1 * time.Hour).Unix()), "iat": float64(time.Now().Unix()), "sub": "test-user", @@ -163,7 +163,7 @@ func TestAzureOIDCRegression(t *testing.T) { session.SetEmail("user@example.com") // Create a valid JWT access token for testing - accessTokenClaims := map[string]any{ + accessTokenClaims := map[string]interface{}{ "iss": "https://login.microsoftonline.com/tenant-id/v2.0", "aud": "test-client-id", "exp": time.Now().Add(1 * time.Hour).Unix(), @@ -175,7 +175,7 @@ func TestAzureOIDCRegression(t *testing.T) { session.SetAccessToken(accessToken) // Create an invalid/expired ID token - idTokenClaims := map[string]any{ + idTokenClaims := map[string]interface{}{ "iss": "https://login.microsoftonline.com/tenant-id/v2.0", "aud": "test-client-id", "exp": time.Now().Add(-1 * time.Hour).Unix(), // Expired @@ -192,7 +192,7 @@ func TestAzureOIDCRegression(t *testing.T) { verifyFunc: func(token string) error { if token == accessToken { // Access token validation succeeds - cache claims - testClaims := map[string]any{ + testClaims := map[string]interface{}{ "exp": float64(time.Now().Add(1 * time.Hour).Unix()), "iat": float64(time.Now().Unix()), "sub": "test-user", @@ -240,7 +240,7 @@ func TestAzureOIDCRegression(t *testing.T) { session.SetAccessToken(ValidAccessToken) // Create a valid ID token for claims extraction - idTokenClaims := map[string]any{ + idTokenClaims := map[string]interface{}{ "iss": "https://login.microsoftonline.com/tenant-id/v2.0", "aud": "test-client-id", "exp": time.Now().Add(1 * time.Hour).Unix(), @@ -257,7 +257,7 @@ func TestAzureOIDCRegression(t *testing.T) { verifyFunc: func(token string) error { if token == idToken { // ID token is valid - cache claims - testClaims := map[string]any{ + testClaims := map[string]interface{}{ "exp": float64(time.Now().Add(1 * time.Hour).Unix()), "iat": float64(time.Now().Unix()), "sub": "test-user", @@ -336,7 +336,7 @@ func TestAzureOIDCRegression(t *testing.T) { } // createMockJWT creates a basic JWT token for testing purposes -func createMockJWT(claims map[string]any) (string, error) { +func createMockJWT(claims map[string]interface{}) (string, error) { // Simple mock JWT - in real tests you'd use a proper JWT library // For this test, we'll create a basic three-part token structure header := "eyJhbGciOiJSUzI1NiIsImtpZCI6InRlc3Qta2V5LWlkIiwidHlwIjoiSldUIn0" // {"alg":"RS256","kid":"test-key-id","typ":"JWT"} diff --git a/cache.go b/cache.go index 2939184..a4fbc48 100644 --- a/cache.go +++ b/cache.go @@ -9,7 +9,7 @@ import ( // CacheItem represents an item stored in the cache with its associated metadata. type CacheItem struct { // Value is the cached data of any type. - Value any + Value interface{} // ExpiresAt is the timestamp when this item should be considered expired. ExpiresAt time.Time @@ -66,7 +66,7 @@ func NewCacheWithLogger(logger *Logger) *Cache { // If the key does not exist and the cache is full, the least recently used item is evicted // before adding the new item. // The expiration duration is relative to the time Set is called. -func (c *Cache) Set(key string, value any, expiration time.Duration) { +func (c *Cache) Set(key string, value interface{}, expiration time.Duration) { c.mutex.Lock() defer c.mutex.Unlock() @@ -104,7 +104,7 @@ func (c *Cache) Set(key string, value any, expiration time.Duration) { // Accessing an item moves it to the most recently used position in the LRU list. // If the item does not exist or has expired, nil and false are returned, and the // expired item is removed from the cache. -func (c *Cache) Get(key string) (any, bool) { +func (c *Cache) Get(key string) (interface{}, bool) { c.mutex.Lock() defer c.mutex.Unlock() diff --git a/cache_test.go b/cache_test.go index 525e858..ce76c6b 100644 --- a/cache_test.go +++ b/cache_test.go @@ -49,7 +49,7 @@ func TestCache_SetMaxSize(t *testing.T) { newMaxSize := 3 // Add more items than the new max size - for i := range originalMaxSize { + for i := 0; i < originalMaxSize; i++ { key := "key" + string(rune('A'+i)) c.Set(key, i, 1*time.Hour) } diff --git a/error_recovery.go b/error_recovery.go index 2edfd2d..e5bc668 100644 --- a/error_recovery.go +++ b/error_recovery.go @@ -3,7 +3,6 @@ package traefikoidc import ( "context" "fmt" - "maps" "math" "math/rand/v2" "net" @@ -17,7 +16,7 @@ type ErrorRecoveryMechanism interface { // ExecuteWithContext executes a function with error recovery ExecuteWithContext(ctx context.Context, fn func() error) error // GetMetrics returns metrics about the error recovery mechanism - GetMetrics() map[string]any + GetMetrics() map[string]interface{} // Reset resets the state of the error recovery mechanism Reset() // IsAvailable returns whether the mechanism is available for use @@ -74,11 +73,11 @@ func (b *BaseRecoveryMechanism) RecordFailure() { } // GetBaseMetrics returns base metrics common to all recovery mechanisms -func (b *BaseRecoveryMechanism) GetBaseMetrics() map[string]any { +func (b *BaseRecoveryMechanism) GetBaseMetrics() map[string]interface{} { b.mutex.RLock() defer b.mutex.RUnlock() - metrics := map[string]any{ + metrics := map[string]interface{}{ "total_requests": atomic.LoadInt64(&b.totalRequests), "total_failures": atomic.LoadInt64(&b.totalFailures), "total_successes": atomic.LoadInt64(&b.totalSuccesses), @@ -108,23 +107,23 @@ func (b *BaseRecoveryMechanism) GetBaseMetrics() map[string]any { } // LogInfo logs an informational message -func (b *BaseRecoveryMechanism) LogInfo(format string, args ...any) { +func (b *BaseRecoveryMechanism) LogInfo(format string, args ...interface{}) { if b.logger != nil { - b.logger.Infof("%s: "+format, append([]any{b.name}, args...)...) + b.logger.Infof("%s: "+format, append([]interface{}{b.name}, args...)...) } } // LogError logs an error message -func (b *BaseRecoveryMechanism) LogError(format string, args ...any) { +func (b *BaseRecoveryMechanism) LogError(format string, args ...interface{}) { if b.logger != nil { - b.logger.Errorf("%s: "+format, append([]any{b.name}, args...)...) + b.logger.Errorf("%s: "+format, append([]interface{}{b.name}, args...)...) } } // LogDebug logs a debug message -func (b *BaseRecoveryMechanism) LogDebug(format string, args ...any) { +func (b *BaseRecoveryMechanism) LogDebug(format string, args ...interface{}) { if b.logger != nil { - b.logger.Debugf("%s: "+format, append([]any{b.name}, args...)...) + b.logger.Debugf("%s: "+format, append([]interface{}{b.name}, args...)...) } } @@ -296,7 +295,7 @@ func (cb *CircuitBreaker) IsAvailable() bool { } // GetMetrics returns metrics about the circuit breaker -func (cb *CircuitBreaker) GetMetrics() map[string]any { +func (cb *CircuitBreaker) GetMetrics() map[string]interface{} { cb.mutex.RLock() state := cb.state failures := cb.failures @@ -498,7 +497,7 @@ func (re *RetryExecutor) IsAvailable() bool { } // GetMetrics returns metrics about the retry executor -func (re *RetryExecutor) GetMetrics() map[string]any { +func (re *RetryExecutor) GetMetrics() map[string]interface{} { metrics := re.GetBaseMetrics() // Add retry executor specific metrics @@ -526,7 +525,7 @@ func (e *HTTPError) Error() string { // GracefulDegradation implements graceful degradation patterns type GracefulDegradation struct { *BaseRecoveryMechanism - fallbacks map[string]func() (any, error) + fallbacks map[string]func() (interface{}, error) healthChecks map[string]func() bool degradedServices map[string]time.Time config GracefulDegradationConfig @@ -553,7 +552,7 @@ func DefaultGracefulDegradationConfig() GracefulDegradationConfig { func NewGracefulDegradation(config GracefulDegradationConfig, logger *Logger) *GracefulDegradation { gd := &GracefulDegradation{ BaseRecoveryMechanism: NewBaseRecoveryMechanism("graceful-degradation", logger), - fallbacks: make(map[string]func() (any, error)), + fallbacks: make(map[string]func() (interface{}, error)), healthChecks: make(map[string]func() bool), degradedServices: make(map[string]time.Time), config: config, @@ -566,7 +565,7 @@ func NewGracefulDegradation(config GracefulDegradationConfig, logger *Logger) *G } // RegisterFallback registers a fallback function for a service -func (gd *GracefulDegradation) RegisterFallback(serviceName string, fallback func() (any, error)) { +func (gd *GracefulDegradation) RegisterFallback(serviceName string, fallback func() (interface{}, error)) { gd.mutex.Lock() defer gd.mutex.Unlock() gd.fallbacks[serviceName] = fallback @@ -584,7 +583,7 @@ func (gd *GracefulDegradation) ExecuteWithContext(ctx context.Context, fn func() gd.RecordRequest() // Execute with a simple wrapper - _, err := gd.ExecuteWithFallback("default", func() (any, error) { + _, err := gd.ExecuteWithFallback("default", func() (interface{}, error) { return nil, fn() }) @@ -598,7 +597,7 @@ func (gd *GracefulDegradation) ExecuteWithContext(ctx context.Context, fn func() } // ExecuteWithFallback executes a function with fallback support -func (gd *GracefulDegradation) ExecuteWithFallback(serviceName string, primary func() (any, error)) (any, error) { +func (gd *GracefulDegradation) ExecuteWithFallback(serviceName string, primary func() (interface{}, error)) (interface{}, error) { // Check if service is degraded if gd.isServiceDegraded(serviceName) { gd.LogInfo("Service %s is degraded, using fallback", serviceName) @@ -656,7 +655,7 @@ func (gd *GracefulDegradation) markServiceDegraded(serviceName string) { } // executeFallback executes the fallback function for a service -func (gd *GracefulDegradation) executeFallback(serviceName string) (any, error) { +func (gd *GracefulDegradation) executeFallback(serviceName string) (interface{}, error) { gd.mutex.RLock() fallback, exists := gd.fallbacks[serviceName] gd.mutex.RUnlock() @@ -684,7 +683,9 @@ func (gd *GracefulDegradation) startHealthCheckRoutine() { func (gd *GracefulDegradation) performHealthChecks() { gd.mutex.RLock() healthChecks := make(map[string]func() bool) - maps.Copy(healthChecks, gd.healthChecks) + for k, v := range gd.healthChecks { + healthChecks[k] = v + } gd.mutex.RUnlock() for serviceName, healthCheck := range healthChecks { @@ -732,7 +733,7 @@ func (gd *GracefulDegradation) IsAvailable() bool { } // GetMetrics returns metrics about the graceful degradation mechanism -func (gd *GracefulDegradation) GetMetrics() map[string]any { +func (gd *GracefulDegradation) GetMetrics() map[string]interface{} { gd.mutex.RLock() degradedCount := len(gd.degradedServices) @@ -805,14 +806,14 @@ func (erm *ErrorRecoveryManager) ExecuteWithRecovery(ctx context.Context, servic } // GetRecoveryMetrics returns metrics for all recovery mechanisms -func (erm *ErrorRecoveryManager) GetRecoveryMetrics() map[string]any { +func (erm *ErrorRecoveryManager) GetRecoveryMetrics() map[string]interface{} { erm.mutex.RLock() defer erm.mutex.RUnlock() - metrics := make(map[string]any) + metrics := make(map[string]interface{}) // Circuit breaker metrics - cbMetrics := make(map[string]any) + cbMetrics := make(map[string]interface{}) for name, cb := range erm.circuitBreakers { cbMetrics[name] = cb.GetMetrics() } diff --git a/error_recovery_test.go b/error_recovery_test.go index c86b047..9ff5b25 100644 --- a/error_recovery_test.go +++ b/error_recovery_test.go @@ -4,7 +4,6 @@ import ( "context" "errors" "net" - "slices" "testing" "time" ) @@ -208,7 +207,7 @@ func TestGracefulDegradation(t *testing.T) { }() t.Run("Register fallback and health check", func(t *testing.T) { - gd.RegisterFallback("test-service", func() (any, error) { + gd.RegisterFallback("test-service", func() (interface{}, error) { return "fallback-result", nil }) @@ -223,12 +222,12 @@ func TestGracefulDegradation(t *testing.T) { }) t.Run("Execute with fallback on failure", func(t *testing.T) { - gd.RegisterFallback("failing-service", func() (any, error) { + gd.RegisterFallback("failing-service", func() (interface{}, error) { return "fallback-result", nil }) // First call should fail and mark service as degraded - result, err := gd.ExecuteWithFallback("failing-service", func() (any, error) { + result, err := gd.ExecuteWithFallback("failing-service", func() (interface{}, error) { return nil, errors.New("service failure") }) if err != nil { @@ -245,7 +244,7 @@ func TestGracefulDegradation(t *testing.T) { }) t.Run("No fallback available", func(t *testing.T) { - _, err := gd.ExecuteWithFallback("no-fallback-service", func() (any, error) { + _, err := gd.ExecuteWithFallback("no-fallback-service", func() (interface{}, error) { return nil, errors.New("service failure") }) @@ -256,7 +255,13 @@ func TestGracefulDegradation(t *testing.T) { t.Run("Get degraded services", func(t *testing.T) { degraded := gd.GetDegradedServices() - found := slices.Contains(degraded, "failing-service") + found := false + for _, s := range degraded { + if s == "failing-service" { + found = true + break + } + } if !found { t.Error("Expected failing-service to be in degraded list") } diff --git a/google_session_test.go b/google_session_test.go index f48b1ee..208f28d 100644 --- a/google_session_test.go +++ b/google_session_test.go @@ -9,7 +9,6 @@ import ( "math/big" "net/http/httptest" "net/url" - "slices" "strings" "testing" "time" @@ -132,9 +131,9 @@ func TestGoogleOIDCRefreshTokenHandling(t *testing.T) { }, } - tOidc.extractClaimsFunc = func(token string) (map[string]any, error) { + tOidc.extractClaimsFunc = func(token string) (map[string]interface{}, error) { // Return mock claims - return map[string]any{ + return map[string]interface{}{ "email": "test@example.com", "exp": float64(time.Now().Add(1 * time.Hour).Unix()), }, nil @@ -304,7 +303,13 @@ func TestGoogleOIDCRefreshTokenHandling(t *testing.T) { scopeList := strings.Split(scope, " ") expectedScopes := []string{"openid", "profile", "email"} for _, expectedScope := range expectedScopes { - found := slices.Contains(scopeList, expectedScope) + found := false + for _, s := range scopeList { + if s == expectedScope { + found = true + break + } + } if !found { t.Errorf("Expected scope '%s' not found in scope parameter: %s", expectedScope, scope) } @@ -380,7 +385,7 @@ func TestGoogleOIDCRefreshTokenHandling(t *testing.T) { nbf := now.Unix() // Create initial ID token - initialIDToken, err := createTestJWT(rsaPrivateKey, "RS256", "test-key-id", map[string]any{ + initialIDToken, err := createTestJWT(rsaPrivateKey, "RS256", "test-key-id", map[string]interface{}{ "iss": "https://accounts.google.com", "aud": "test-client-id", "exp": exp, @@ -396,7 +401,7 @@ func TestGoogleOIDCRefreshTokenHandling(t *testing.T) { } // Create refresh ID token - refreshedIDToken, err := createTestJWT(rsaPrivateKey, "RS256", "test-key-id", map[string]any{ + refreshedIDToken, err := createTestJWT(rsaPrivateKey, "RS256", "test-key-id", map[string]interface{}{ "iss": "https://accounts.google.com", "aud": "test-client-id", "exp": exp, diff --git a/helpers.go b/helpers.go index 595d7b0..f7534ca 100644 --- a/helpers.go +++ b/helpers.go @@ -187,7 +187,7 @@ func (t *TraefikOidc) getNewTokenWithRefreshToken(refreshToken string) (*TokenRe // Returns: // - A map representing the JSON claims extracted from the token payload. // - An error if the token format is invalid, decoding fails, or JSON unmarshaling fails. -func extractClaims(tokenString string) (map[string]any, error) { +func extractClaims(tokenString string) (map[string]interface{}, error) { parts := strings.Split(tokenString, ".") if len(parts) != 3 { return nil, fmt.Errorf("invalid token format") @@ -198,7 +198,7 @@ func extractClaims(tokenString string) (map[string]any, error) { return nil, fmt.Errorf("failed to decode token payload: %w", err) } - var claims map[string]any + var claims map[string]interface{} if err := json.Unmarshal(payload, &claims); err != nil { return nil, fmt.Errorf("failed to unmarshal claims: %w", err) } @@ -236,7 +236,7 @@ func NewTokenCache() *TokenCache { // - token: The raw token string (used as the key). // - claims: The map of claims associated with the token. // - expiration: The duration for which the cache entry should be valid. -func (tc *TokenCache) Set(token string, claims map[string]any, expiration time.Duration) { +func (tc *TokenCache) Set(token string, claims map[string]interface{}, expiration time.Duration) { token = "t-" + token tc.cache.Set(token, claims, expiration) } @@ -250,13 +250,13 @@ func (tc *TokenCache) Set(token string, claims map[string]any, expiration time.D // Returns: // - The cached claims map if found and valid. // - A boolean indicating whether the token was found in the cache (true if found, false otherwise). -func (tc *TokenCache) Get(token string) (map[string]any, bool) { +func (tc *TokenCache) Get(token string) (map[string]interface{}, bool) { token = "t-" + token value, found := tc.cache.Get(token) if !found { return nil, false } - claims, ok := value.(map[string]any) + claims, ok := value.(map[string]interface{}) return claims, ok } diff --git a/input_validation.go b/input_validation.go index 5c9dc9a..d7794b5 100644 --- a/input_validation.go +++ b/input_validation.go @@ -614,7 +614,7 @@ func (iv *InputValidator) SanitizeInput(input string, maxLength int) string { } // ValidateBoundaryValues validates numeric boundary values -func (iv *InputValidator) ValidateBoundaryValues(value any, min, max int64) ValidationResult { +func (iv *InputValidator) ValidateBoundaryValues(value interface{}, min, max int64) ValidationResult { result := ValidationResult{IsValid: true, Errors: []string{}, Warnings: []string{}} var numValue int64 diff --git a/input_validation_test.go b/input_validation_test.go index cd196c3..ee12846 100644 --- a/input_validation_test.go +++ b/input_validation_test.go @@ -246,7 +246,7 @@ func TestValidateBoundaryValues(t *testing.T) { } t.Run("Valid boundary values", func(t *testing.T) { - validValues := []any{ + validValues := []interface{}{ int(50), int64(100), float64(75.5), @@ -261,7 +261,7 @@ func TestValidateBoundaryValues(t *testing.T) { }) t.Run("Invalid boundary values", func(t *testing.T) { - invalidValues := []any{ + invalidValues := []interface{}{ int(-1), int64(2000), "not a number", diff --git a/jwt.go b/jwt.go index 9f94d49..4c6be46 100644 --- a/jwt.go +++ b/jwt.go @@ -87,8 +87,8 @@ var ClockSkewTolerance = ClockSkewToleranceFuture // JWT represents a JSON Web Token as defined in RFC 7519. type JWT struct { - Header map[string]any - Claims map[string]any + Header map[string]interface{} + Claims map[string]interface{} Token string Signature []byte } @@ -276,13 +276,13 @@ func (j *JWT) Verify(issuerURL, clientID string, skipReplayCheck ...bool) error // Returns: // - nil if the expected audience is found. // - An error if the claim type is invalid or the expected audience is not present. -func verifyAudience(tokenAudience any, expectedAudience string) error { +func verifyAudience(tokenAudience interface{}, expectedAudience string) error { switch aud := tokenAudience.(type) { case string: if aud != expectedAudience { return fmt.Errorf("invalid audience") } - case []any: + case []interface{}: found := false for _, v := range aud { if str, ok := v.(string); ok && str == expectedAudience { diff --git a/main.go b/main.go index 290c7ba..3a4f8f2 100644 --- a/main.go +++ b/main.go @@ -6,7 +6,6 @@ import ( "encoding/json" "fmt" "io" - "maps" "math" "net" "net/http" @@ -14,7 +13,6 @@ import ( "net/url" "os" "runtime" - "slices" "strings" "sync" "text/template" @@ -209,7 +207,7 @@ type TraefikOidc struct { sessionManager *SessionManager tokenCleanupStopChan chan struct{} excludedURLs map[string]struct{} - extractClaimsFunc func(tokenString string) (map[string]any, error) + 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{} @@ -441,7 +439,7 @@ func (t *TraefikOidc) VerifyToken(token string) error { // Parameters: // - token: The raw token string (used as the cache key). // - claims: The map of claims extracted from the verified token. -func (t *TraefikOidc) cacheVerifiedToken(token string, claims map[string]any) { +func (t *TraefikOidc) cacheVerifiedToken(token string, claims map[string]interface{}) { expClaim, ok := claims["exp"].(float64) if !ok { t.logger.Errorf("Failed to cache token: invalid 'exp' claim type") @@ -698,7 +696,9 @@ func New(ctx context.Context, next http.Handler, config *Config, name string) (h } // Add default excluded URLs - maps.Copy(t.excludedURLs, defaultExcludedURLs) + for k, v := range defaultExcludedURLs { + t.excludedURLs[k] = v + } t.tokenVerifier = t t.jwtVerifier = t @@ -848,7 +848,7 @@ func discoverProviderMetadata(providerURL string, httpClient *http.Client, l *Lo start := time.Now() var lastErr error - for attempt := range maxRetries { + for attempt := 0; attempt < maxRetries; attempt++ { if time.Since(start) > totalTimeout { l.Errorf("Timeout exceeded while fetching provider metadata") return nil, fmt.Errorf("timeout exceeded while fetching provider metadata: %w", lastErr) @@ -1048,7 +1048,7 @@ func (t *TraefikOidc) ServeHTTP(rw http.ResponseWriter, req *http.Request) { if idToken != "" { jwt, err := parseJWT(idToken) if err == nil { - // jwt.Claims is already map[string]any, no type assertion needed + // jwt.Claims is already map[string]interface{}, no type assertion needed claims := jwt.Claims // STABILITY FIX: Safe type assertion with proper error handling if expClaim, ok := claims["exp"].(float64); ok { @@ -1205,7 +1205,7 @@ func (t *TraefikOidc) processAuthorizedRequest(rw http.ResponseWriter, req *http } else { // Create template data context with available tokens and claims // Fields must be exported (uppercase) to be accessible in templates - templateData := map[string]any{ + templateData := map[string]interface{}{ "AccessToken": session.GetAccessToken(), "IdToken": session.GetIDToken(), "RefreshToken": session.GetRefreshToken(), @@ -1680,8 +1680,11 @@ func (t *TraefikOidc) buildAuthURL(redirectURL, state, nonce, codeChallenge stri hasOfflineAccess := false - if slices.Contains(scopes, "offline_access") { - hasOfflineAccess = true + for _, scope := range scopes { + if scope == "offline_access" { + hasOfflineAccess = true + break + } } if !hasOfflineAccess { @@ -1690,7 +1693,13 @@ func (t *TraefikOidc) buildAuthURL(redirectURL, state, nonce, codeChallenge stri } } else { // For other providers, use the standard offline_access scope - hasOfflineAccess := slices.Contains(scopes, "offline_access") + hasOfflineAccess := false + for _, scope := range scopes { + if scope == "offline_access" { + hasOfflineAccess = true + break + } + } if !hasOfflineAccess { scopes = append(scopes, "offline_access") @@ -2214,7 +2223,7 @@ func (t *TraefikOidc) extractGroupsAndRoles(idToken string) ([]string, []string, // Extract groups with type checking if groupsClaim, exists := claims["groups"]; exists { - groupsSlice, ok := groupsClaim.([]any) + groupsSlice, ok := groupsClaim.([]interface{}) if !ok { // Strictly expect an array return nil, nil, fmt.Errorf("groups claim is not an array") @@ -2232,7 +2241,7 @@ func (t *TraefikOidc) extractGroupsAndRoles(idToken string) ([]string, []string, // Extract roles with type checking if rolesClaim, exists := claims["roles"]; exists { - rolesSlice, ok := rolesClaim.([]any) + rolesSlice, ok := rolesClaim.([]interface{}) if !ok { // Strictly expect an array return nil, nil, fmt.Errorf("roles claim is not an array") @@ -2316,7 +2325,7 @@ func (t *TraefikOidc) sendErrorResponse(rw http.ResponseWriter, req *http.Reques rw.Header().Set("Content-Type", "application/json") rw.WriteHeader(code) // Use a simple error structure - ensure this matches the expected response format in tests - json.NewEncoder(rw).Encode(map[string]any{ + json.NewEncoder(rw).Encode(map[string]interface{}{ "error": http.StatusText(code), // Use standard text for the code "error_description": message, // Provide specific detail here "status_code": code, diff --git a/main_test.go b/main_test.go index ac608ea..1c8daab 100644 --- a/main_test.go +++ b/main_test.go @@ -74,7 +74,7 @@ func (ts *TestSuite) Setup() { iat := now.Add(-2 * time.Minute).Unix() // Account for clock skew nbf := now.Add(-2 * time.Minute).Unix() // Account for clock skew - ts.token, err = createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]any{ + ts.token, err = createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]interface{}{ "iss": "https://test-issuer.com", "aud": "test-client-id", "exp": exp, @@ -205,8 +205,8 @@ func (m *MockTokenExchanger) RevokeTokenWithProvider(token, tokenType string) er } // Helper function to create a JWT token -func createTestJWT(privateKey *rsa.PrivateKey, alg, kid string, claims map[string]any) (string, error) { - header := map[string]any{ +func createTestJWT(privateKey *rsa.PrivateKey, alg, kid string, claims map[string]interface{}) (string, error) { + header := map[string]interface{}{ "alg": alg, "kid": kid, "typ": "JWT", @@ -333,7 +333,7 @@ func TestVerifyToken(t *testing.T) { if tc.cacheToken { // Use more realistic claims for cached token - ts.tOidc.tokenCache.Set(tc.token, map[string]any{ + ts.tOidc.tokenCache.Set(tc.token, map[string]interface{}{ "iss": "https://test-issuer.com", "sub": "test-subject", "exp": float64(time.Now().Add(1 * time.Hour).Unix()), @@ -376,7 +376,7 @@ func TestServeHTTP(t *testing.T) { exp := time.Now().Add(-1 * time.Hour).Unix() // Expired 1 hour ago iat := time.Now().Add(-2 * time.Hour).Unix() nbf := time.Now().Add(-2 * time.Hour).Unix() - expiredToken, _ := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]any{ + expiredToken, _ := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]interface{}{ "iss": "https://test-issuer.com", "aud": "test-client-id", "exp": exp, @@ -395,7 +395,7 @@ func TestServeHTTP(t *testing.T) { exp := time.Now().Add(1 * time.Hour).Unix() // Valid for 1 hour iat := time.Now().Unix() nbf := time.Now().Unix() - newToken, _ := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]any{ + newToken, _ := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]interface{}{ "iss": "https://test-issuer.com", "aud": "test-client-id", "exp": exp, @@ -410,7 +410,7 @@ func TestServeHTTP(t *testing.T) { } tests := []struct { - sessionValues map[any]any + sessionValues map[interface{}]interface{} setupSession func(*SessionData) mockRefreshTokenFunc func(originalFunc func(refreshToken string) (*TokenResponse, error)) func(refreshToken string) (*TokenResponse, error) assertSessionAfterRequest func(t *testing.T, rr *httptest.ResponseRecorder, req *http.Request, sessionManager *SessionManager) @@ -481,7 +481,7 @@ func TestServeHTTP(t *testing.T) { session.SetAuthenticated(true) session.SetEmail("user@example.com") // Generate a fresh valid token for this test case to avoid replay issues - freshToken, _ := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]any{ + freshToken, _ := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]interface{}{ "iss": "https://test-issuer.com", "aud": "test-client-id", "exp": time.Now().Add(1 * time.Hour).Unix(), "iat": time.Now().Unix(), "nbf": time.Now().Unix(), "sub": "test-subject", "email": "user@example.com", "jti": generateRandomString(16), // Unique JTI @@ -504,7 +504,7 @@ func TestServeHTTP(t *testing.T) { session.SetAuthenticated(true) // Set flag initially, though isUserAuthenticated will override based on token session.SetEmail("user@example.com") // Create an expired token for this test - expiredToken, _ := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]any{ + expiredToken, _ := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]interface{}{ "iss": "https://test-issuer.com", "aud": "test-client-id", "exp": time.Now().Add(-1 * time.Hour).Unix(), "iat": time.Now().Add(-2 * time.Hour).Unix(), "nbf": time.Now().Add(-2 * time.Hour).Unix(), "sub": "test-subject", "email": "test@example.com", "jti": generateRandomString(16), @@ -561,7 +561,7 @@ func TestServeHTTP(t *testing.T) { session.SetAuthenticated(true) session.SetEmail("user@example.com") // Generate a fresh valid token for this test case - freshToken, _ := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]any{ + freshToken, _ := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]interface{}{ "iss": "https://test-issuer.com", "aud": "test-client-id", "exp": time.Now().Add(1 * time.Hour).Unix(), "iat": time.Now().Unix(), "nbf": time.Now().Unix(), "sub": "test-subject", "email": "user@example.com", "jti": generateRandomString(16), // Unique JTI @@ -579,7 +579,7 @@ func TestServeHTTP(t *testing.T) { session.SetAuthenticated(true) // Set flag initially session.SetEmail("user@example.com") // Create an expired token for this test - expiredToken, _ := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]any{ + expiredToken, _ := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]interface{}{ "iss": "https://test-issuer.com", "aud": "test-client-id", "exp": time.Now().Add(-1 * time.Hour).Unix(), "iat": time.Now().Add(-2 * time.Hour).Unix(), "nbf": time.Now().Add(-2 * time.Hour).Unix(), "sub": "test-subject", "email": "test@example.com", "jti": generateRandomString(16), @@ -607,7 +607,7 @@ func TestServeHTTP(t *testing.T) { session.SetAuthenticated(true) // Set flag initially session.SetEmail("user@example.com") // Create an expired token for this test - expiredToken, _ := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]any{ + expiredToken, _ := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]interface{}{ "iss": "https://test-issuer.com", "aud": "test-client-id", "exp": time.Now().Add(-1 * time.Hour).Unix(), "iat": time.Now().Add(-2 * time.Hour).Unix(), "nbf": time.Now().Add(-2 * time.Hour).Unix(), "sub": "test-subject", "email": "test@example.com", "jti": generateRandomString(16), @@ -635,7 +635,7 @@ func TestServeHTTP(t *testing.T) { exp := time.Now().Add(30 * time.Second).Unix() iat := time.Now().Add(-1 * time.Minute).Unix() nbf := time.Now().Add(-1 * time.Minute).Unix() - nearExpiryToken, _ := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]any{ + nearExpiryToken, _ := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]interface{}{ "iss": "https://test-issuer.com", "aud": "test-client-id", "exp": exp, "iat": iat, "nbf": nbf, "sub": "test-subject", "email": "user@example.com", "jti": generateRandomString(16), }) @@ -666,7 +666,7 @@ func TestServeHTTP(t *testing.T) { exp := time.Now().Add(10 * time.Minute).Unix() iat := time.Now().Add(-1 * time.Minute).Unix() nbf := time.Now().Add(-1 * time.Minute).Unix() - validToken, _ := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]any{ + validToken, _ := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]interface{}{ "iss": "https://test-issuer.com", "aud": "test-client-id", "exp": exp, "iat": iat, "nbf": nbf, "sub": "test-subject", "email": "user@example.com", "jti": generateRandomString(16), }) @@ -693,7 +693,7 @@ func TestServeHTTP(t *testing.T) { session.SetAuthenticated(true) session.SetEmail("user@disallowed.com") // Use disallowed domain // Generate a fresh valid token for this test case - freshToken, _ := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]any{ + freshToken, _ := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]interface{}{ "iss": "https://test-issuer.com", "aud": "test-client-id", "exp": time.Now().Add(1 * time.Hour).Unix(), "iat": time.Now().Unix(), "nbf": time.Now().Unix(), "sub": "test-subject", "email": "user@disallowed.com", // Match email "jti": generateRandomString(16), // Unique JTI @@ -715,7 +715,7 @@ func TestServeHTTP(t *testing.T) { session.SetAuthenticated(true) session.SetEmail("user@disallowed.com") // Use disallowed domain // Generate a fresh valid token for this test case - freshToken, _ := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]any{ + freshToken, _ := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]interface{}{ "iss": "https://test-issuer.com", "aud": "test-client-id", "exp": time.Now().Add(1 * time.Hour).Unix(), "iat": time.Now().Unix(), "nbf": time.Now().Unix(), "sub": "test-subject", "email": "user@disallowed.com", // Match email "jti": generateRandomString(16), // Unique JTI @@ -967,11 +967,11 @@ func TestJWTVerify_MissingClaims(t *testing.T) { ts.Setup() jwt := &JWT{ - Header: map[string]any{ + Header: map[string]interface{}{ "alg": "RS256", "kid": "test-key-id", }, - Claims: map[string]any{ + Claims: map[string]interface{}{ // Missing 'iss', 'aud', 'exp', 'iat', 'sub' }, } @@ -990,7 +990,7 @@ func TestHandleCallback(t *testing.T) { tests := []struct { exchangeCodeForToken func(code string, redirectURL string, codeVerifier string) (*TokenResponse, error) - extractClaimsFunc func(tokenString string) (map[string]any, error) + extractClaimsFunc func(tokenString string) (map[string]interface{}, error) sessionSetupFunc func(*SessionData) name string queryParams string @@ -1005,8 +1005,8 @@ func TestHandleCallback(t *testing.T) { RefreshToken: "test-refresh-token", }, nil }, - extractClaimsFunc: func(tokenString string) (map[string]any, error) { - return map[string]any{ + extractClaimsFunc: func(tokenString string) (map[string]interface{}, error) { + return map[string]interface{}{ "email": "user@example.com", "nonce": "test-nonce", }, nil @@ -1060,7 +1060,7 @@ func TestHandleCallback(t *testing.T) { exp := now.Add(1 * time.Hour).Unix() iat := now.Unix() nbf := now.Unix() - disallowedToken, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]any{ + disallowedToken, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]interface{}{ "iss": "https://test-issuer.com", "aud": "test-client-id", "exp": exp, @@ -1102,8 +1102,8 @@ func TestHandleCallback(t *testing.T) { RefreshToken: "test-refresh-token", }, nil }, - extractClaimsFunc: func(tokenString string) (map[string]any, error) { - return map[string]any{ + extractClaimsFunc: func(tokenString string) (map[string]interface{}, error) { + return map[string]interface{}{ "email": "user@example.com", "nonce": "test-nonce", }, nil @@ -1123,8 +1123,8 @@ func TestHandleCallback(t *testing.T) { RefreshToken: "test-refresh-token", }, nil }, - extractClaimsFunc: func(tokenString string) (map[string]any, error) { - return map[string]any{ + extractClaimsFunc: func(tokenString string) (map[string]interface{}, error) { + return map[string]interface{}{ "email": "user@example.com", "nonce": "invalid-nonce", }, nil @@ -1144,8 +1144,8 @@ func TestHandleCallback(t *testing.T) { RefreshToken: "test-refresh-token", }, nil }, - extractClaimsFunc: func(tokenString string) (map[string]any, error) { - return map[string]any{ + extractClaimsFunc: func(tokenString string) (map[string]interface{}, error) { + return map[string]interface{}{ "email": "user@example.com", // Missing nonce }, nil @@ -1344,7 +1344,7 @@ func TestOIDCHandler(t *testing.T) { tests := []struct { exchangeCodeForToken func(code string, redirectURL string, codeVerifier string) (*TokenResponse, error) - extractClaimsFunc func(tokenString string) (map[string]any, error) + extractClaimsFunc func(tokenString string) (map[string]interface{}, error) sessionSetupFunc func(session *sessions.Session) name string queryParams string @@ -1368,9 +1368,9 @@ func TestOIDCHandler(t *testing.T) { RefreshToken: "test-refresh-token", }, nil }, - extractClaimsFunc: func(tokenString string) (map[string]any, error) { + extractClaimsFunc: func(tokenString string) (map[string]interface{}, error) { // Simulate extraction of claims with invalid nonce - return map[string]any{ + return map[string]interface{}{ "email": "user@example.com", "nonce": "invalid-nonce", }, nil @@ -1392,9 +1392,9 @@ func TestOIDCHandler(t *testing.T) { RefreshToken: "test-refresh-token", }, nil }, - extractClaimsFunc: func(tokenString string) (map[string]any, error) { + extractClaimsFunc: func(tokenString string) (map[string]interface{}, error) { // Simulate extraction of claims without nonce - return map[string]any{ + return map[string]interface{}{ "email": "user@example.com", }, nil }, @@ -1415,9 +1415,9 @@ func TestOIDCHandler(t *testing.T) { RefreshToken: "test-refresh-token", }, nil }, - extractClaimsFunc: func(tokenString string) (map[string]any, error) { + extractClaimsFunc: func(tokenString string) (map[string]interface{}, error) { // Simulate extraction of claims - return map[string]any{ + return map[string]interface{}{ "email": "user@example.com", "nonce": "test-nonce", }, nil @@ -1439,9 +1439,9 @@ func TestOIDCHandler(t *testing.T) { RefreshToken: "test-refresh-token", }, nil }, - extractClaimsFunc: func(tokenString string) (map[string]any, error) { + extractClaimsFunc: func(tokenString string) (map[string]interface{}, error) { // Simulate extraction of claims with mismatched nonce - return map[string]any{ + return map[string]interface{}{ "email": "user@example.com", "nonce": "invalid-nonce", }, nil @@ -1471,7 +1471,7 @@ func TestOIDCHandler(t *testing.T) { if tc.cacheToken { // Cache the token with dummy claims - ts.tOidc.tokenCache.Set(ts.token, map[string]any{ + ts.tOidc.tokenCache.Set(ts.token, map[string]interface{}{ "empty": "claim", }, 60) } @@ -1732,7 +1732,7 @@ func TestRevokeToken(t *testing.T) { ts.Setup() token := "test.token.with.claims" - claims := map[string]any{ + claims := map[string]interface{}{ "exp": float64(time.Now().Add(time.Hour).Unix()), } @@ -1833,7 +1833,7 @@ func TestHandleExpiredToken(t *testing.T) { setupSession: func(session *SessionData) { session.SetAuthenticated(true) // Create an expired token for this test - expiredToken, _ := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]any{ + expiredToken, _ := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]interface{}{ "iss": "https://test-issuer.com", "aud": "test-client-id", "exp": time.Now().Add(-1 * time.Hour).Unix(), "iat": time.Now().Add(-2 * time.Hour).Unix(), "nbf": time.Now().Add(-2 * time.Hour).Unix(), "sub": "test-subject", "email": "test@example.com", "jti": generateRandomString(16), @@ -1848,7 +1848,7 @@ func TestHandleExpiredToken(t *testing.T) { setupSession: func(session *SessionData) { session.SetAuthenticated(true) // Create an expired token for this test - expiredToken, _ := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]any{ + expiredToken, _ := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]interface{}{ "iss": "https://test-issuer.com", "aud": "test-client-id", "exp": time.Now().Add(-1 * time.Hour).Unix(), "iat": time.Now().Add(-2 * time.Hour).Unix(), "nbf": time.Now().Add(-2 * time.Hour).Unix(), "sub": "test-subject", "email": "test@example.com", "jti": generateRandomString(16), @@ -1933,16 +1933,16 @@ func TestExtractGroupsAndRoles(t *testing.T) { tests := []struct { name string - claims map[string]any + claims map[string]interface{} expectGroups []string expectRoles []string expectError bool }{ { name: "Valid groups and roles", - claims: map[string]any{ - "groups": []any{"group1", "group2"}, - "roles": []any{"role1", "role2"}, + claims: map[string]interface{}{ + "groups": []interface{}{"group1", "group2"}, + "roles": []interface{}{"role1", "role2"}, }, expectGroups: []string{"group1", "group2"}, expectRoles: []string{"role1", "role2"}, @@ -1950,9 +1950,9 @@ func TestExtractGroupsAndRoles(t *testing.T) { }, { name: "Empty groups and roles", - claims: map[string]any{ - "groups": []any{}, - "roles": []any{}, + claims: map[string]interface{}{ + "groups": []interface{}{}, + "roles": []interface{}{}, }, expectGroups: []string{}, expectRoles: []string{}, @@ -1960,9 +1960,9 @@ func TestExtractGroupsAndRoles(t *testing.T) { }, { name: "Invalid groups format", - claims: map[string]any{ + claims: map[string]interface{}{ "groups": "not-an-array", - "roles": []any{"role1"}, + "roles": []interface{}{"role1"}, }, expectError: true, }, @@ -2105,7 +2105,7 @@ func TestServeHTTPRolesAndGroups(t *testing.T) { tests := []struct { allowedRolesAndGroups map[string]struct{} - claims map[string]any + claims map[string]interface{} setupSession func(*SessionData) expectedHeaders map[string]string name string @@ -2116,15 +2116,15 @@ func TestServeHTTPRolesAndGroups(t *testing.T) { allowedRolesAndGroups: map[string]struct{}{ "admin": {}, }, - claims: map[string]any{ + claims: map[string]interface{}{ "iss": "https://test-issuer.com", "aud": "test-client-id", "exp": exp, "iat": iat, "nbf": nbf, "sub": "test-subject", - "roles": []any{"admin", "user"}, - "groups": []any{"group1"}, + "roles": []interface{}{"admin", "user"}, + "groups": []interface{}{"group1"}, "jti": generateRandomString(16), }, setupSession: func(session *SessionData) { @@ -2142,15 +2142,15 @@ func TestServeHTTPRolesAndGroups(t *testing.T) { allowedRolesAndGroups: map[string]struct{}{ "allowed-group": {}, }, - claims: map[string]any{ + claims: map[string]interface{}{ "iss": "https://test-issuer.com", "aud": "test-client-id", "exp": exp, "iat": iat, "nbf": nbf, "sub": "test-subject", - "roles": []any{"user"}, - "groups": []any{"allowed-group"}, + "roles": []interface{}{"user"}, + "groups": []interface{}{"allowed-group"}, "jti": generateRandomString(16), }, setupSession: func(session *SessionData) { @@ -2169,15 +2169,15 @@ func TestServeHTTPRolesAndGroups(t *testing.T) { "admin": {}, "allowed-group": {}, }, - claims: map[string]any{ + claims: map[string]interface{}{ "iss": "https://test-issuer.com", "aud": "test-client-id", "exp": exp, "iat": iat, "nbf": nbf, "sub": "test-subject", - "roles": []any{"user"}, - "groups": []any{"regular-group"}, + "roles": []interface{}{"user"}, + "groups": []interface{}{"regular-group"}, "jti": generateRandomString(16), }, setupSession: func(session *SessionData) { @@ -2189,15 +2189,15 @@ func TestServeHTTPRolesAndGroups(t *testing.T) { { name: "No role/group restrictions", allowedRolesAndGroups: map[string]struct{}{}, - claims: map[string]any{ + claims: map[string]interface{}{ "iss": "https://test-issuer.com", "aud": "test-client-id", "exp": exp, "iat": iat, "nbf": nbf, "sub": "test-subject", - "roles": []any{"user"}, - "groups": []any{"regular-group"}, + "roles": []interface{}{"user"}, + "groups": []interface{}{"regular-group"}, "jti": generateRandomString(16), }, setupSession: func(session *SessionData) { @@ -2213,7 +2213,7 @@ func TestServeHTTPRolesAndGroups(t *testing.T) { { name: "Claims without roles and groups", allowedRolesAndGroups: map[string]struct{}{}, - claims: map[string]any{ + claims: map[string]interface{}{ "iss": "https://test-issuer.com", "aud": "test-client-id", "exp": exp, @@ -2861,7 +2861,7 @@ func TestJWTVerifyWithSkipReplayCheck(t *testing.T) { iat := now.Unix() nbf := now.Unix() - token, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]any{ + token, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]interface{}{ "iss": "https://test-issuer.com", "aud": "test-client-id", "exp": exp, @@ -2954,7 +2954,7 @@ func TestJWTVerifyBackwardCompatibility(t *testing.T) { iat := now.Unix() nbf := now.Unix() - token, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]any{ + token, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]interface{}{ "iss": "https://test-issuer.com", "aud": "test-client-id", "exp": exp, @@ -3007,7 +3007,7 @@ func TestTokenReplayDetectionFalsePositiveFix(t *testing.T) { iat := now.Unix() nbf := now.Unix() - token, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]any{ + token, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]interface{}{ "iss": "https://test-issuer.com", "aud": "test-client-id", "exp": exp, @@ -3080,7 +3080,7 @@ func TestAuthenticationFlowReplayDetection(t *testing.T) { iat := now.Unix() nbf := now.Unix() - token, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]any{ + token, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]interface{}{ "iss": "https://test-issuer.com", "aud": "test-client-id", "exp": exp, @@ -3155,7 +3155,7 @@ func TestActualReplayAttackDetection(t *testing.T) { iat := now.Unix() nbf := now.Unix() - token, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]any{ + token, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]interface{}{ "iss": "https://test-issuer.com", "aud": "test-client-id", "exp": exp, @@ -3241,7 +3241,7 @@ func TestConcurrentTokenValidation(t *testing.T) { jti := generateRandomString(16) jtis = append(jtis, jti) - token, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]any{ + token, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]interface{}{ "iss": "https://test-issuer.com", "aud": "test-client-id", "exp": exp, @@ -3322,7 +3322,7 @@ func TestJTIBlacklistBehavior(t *testing.T) { iat := now.Unix() nbf := now.Unix() - token, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]any{ + token, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]interface{}{ "iss": "https://test-issuer.com", "aud": "test-client-id", "exp": exp, @@ -3422,7 +3422,7 @@ func TestSessionBasedTokenRevalidation(t *testing.T) { iat := now.Unix() nbf := now.Unix() - token, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]any{ + token, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]interface{}{ "iss": "https://test-issuer.com", "aud": "test-client-id", "exp": exp, @@ -3495,7 +3495,7 @@ func TestEdgeCasesWithDifferentTokenTypes(t *testing.T) { nbf := now.Unix() tests := []struct { - claims map[string]any + claims map[string]interface{} name string tokenType string expectError bool @@ -3503,7 +3503,7 @@ func TestEdgeCasesWithDifferentTokenTypes(t *testing.T) { { name: "ID Token with JTI", tokenType: "id_token", - claims: map[string]any{ + claims: map[string]interface{}{ "iss": "https://test-issuer.com", "aud": "test-client-id", "exp": exp, @@ -3520,7 +3520,7 @@ func TestEdgeCasesWithDifferentTokenTypes(t *testing.T) { { name: "Access Token with JTI", tokenType: "access_token", - claims: map[string]any{ + claims: map[string]interface{}{ "iss": "https://test-issuer.com", "aud": "test-client-id", "exp": exp, @@ -3536,7 +3536,7 @@ func TestEdgeCasesWithDifferentTokenTypes(t *testing.T) { { name: "Token without JTI", tokenType: "no_jti", - claims: map[string]any{ + claims: map[string]interface{}{ "iss": "https://test-issuer.com", "aud": "test-client-id", "exp": exp, diff --git a/robustness_test.go b/robustness_test.go index fd8497e..463d402 100644 --- a/robustness_test.go +++ b/robustness_test.go @@ -22,8 +22,8 @@ func TestConcurrentTokenVerification(t *testing.T) { // Create multiple valid tokens to avoid replay detection tokens := make([]string, 10) - for i := range 10 { - token, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]any{ + for i := 0; i < 10; i++ { + token, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]interface{}{ "iss": "https://test-issuer.com", "aud": "test-client-id", "exp": float64(time.Now().Add(1 * time.Hour).Unix()), @@ -71,11 +71,11 @@ func TestConcurrentTokenVerification(t *testing.T) { var errorCount int64 errors := make(chan error, numGoroutines*verificationsPerGoroutine) - for i := range numGoroutines { + for i := 0; i < numGoroutines; i++ { wg.Add(1) go func(goroutineID int) { defer wg.Done() - for j := range verificationsPerGoroutine { + for j := 0; j < verificationsPerGoroutine; j++ { tokenIndex := (goroutineID*verificationsPerGoroutine + j) % len(tokens) err := tOidc.VerifyToken(tokens[tokenIndex]) if err != nil { @@ -144,8 +144,8 @@ func TestCacheMemoryExhaustion(t *testing.T) { const numTokens = 500 tokens := make([]string, numTokens) - for i := range numTokens { - token, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]any{ + for i := 0; i < numTokens; i++ { + token, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]interface{}{ "iss": "https://test-issuer.com", "aud": "test-client-id", "exp": float64(time.Now().Add(1 * time.Hour).Unix()), @@ -161,7 +161,7 @@ func TestCacheMemoryExhaustion(t *testing.T) { tokens[i] = token // Add to cache - claims := map[string]any{ + claims := map[string]interface{}{ "iss": "https://test-issuer.com", "aud": "test-client-id", "exp": float64(time.Now().Add(1 * time.Hour).Unix()), @@ -210,7 +210,7 @@ func TestSessionConcurrencyProtection(t *testing.T) { var successCount int64 var errorCount int64 - for i := range numGoroutines { + for i := 0; i < numGoroutines; i++ { wg.Add(1) go func(goroutineID int) { defer wg.Done() @@ -218,7 +218,7 @@ func TestSessionConcurrencyProtection(t *testing.T) { // Each goroutine gets its own request and session req := httptest.NewRequest("GET", "/test", nil) - for j := range operationsPerGoroutine { + for j := 0; j < operationsPerGoroutine; j++ { // Get a fresh session for each operation s, err := sessionManager.GetSession(req) if err != nil { @@ -276,11 +276,11 @@ func TestParallelCacheOperations(t *testing.T) { var deleteCount int64 // Start multiple goroutines performing cache operations - for i := range numGoroutines { + for i := 0; i < numGoroutines; i++ { wg.Add(1) go func(goroutineID int) { defer wg.Done() - for j := range operationsPerGoroutine { + for j := 0; j < operationsPerGoroutine; j++ { key := fmt.Sprintf("key-%d-%d", goroutineID, j) value := fmt.Sprintf("value-%d-%d", goroutineID, j) @@ -377,7 +377,7 @@ func TestOversizedTokenHandling(t *testing.T) { // Create an oversized token with large claims largeClaim := strings.Repeat("x", 10000) // 10KB claim - oversizedClaims := map[string]any{ + oversizedClaims := map[string]interface{}{ "iss": "https://test-issuer.com", "aud": "test-client-id", "exp": float64(time.Now().Add(1 * time.Hour).Unix()), @@ -411,7 +411,7 @@ func TestOversizedTokenHandling(t *testing.T) { // Test extremely long token (beyond reasonable limits) extremelyLongClaim := strings.Repeat("y", 100000) // 100KB claim - extremeClaims := map[string]any{ + extremeClaims := map[string]interface{}{ "iss": "https://test-issuer.com", "aud": "test-client-id", "exp": float64(time.Now().Add(1 * time.Hour).Unix()), @@ -528,7 +528,7 @@ func TestMaliciousInputValidation(t *testing.T) { } // Verify the system is still functional after malicious input - validToken, createErr := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]any{ + validToken, createErr := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]interface{}{ "iss": "https://test-issuer.com", "aud": "test-client-id", "exp": float64(time.Now().Add(1 * time.Hour).Unix()), @@ -609,7 +609,7 @@ func TestResourceLimits(t *testing.T) { defer cache.Close() // Try to overwhelm the cache - for i := range 1000 { + for i := 0; i < 1000; i++ { key := fmt.Sprintf("key-%d", i) value := fmt.Sprintf("value-%d", i) cache.Set(key, value, time.Minute) @@ -627,7 +627,7 @@ func TestResourceLimits(t *testing.T) { denied := 0 // Make many requests quickly - for range 100 { + for i := 0; i < 100; i++ { if limiter.Allow() { allowed++ } else { @@ -655,7 +655,7 @@ func TestErrorRecoveryPatterns(t *testing.T) { ts.tOidc.tokenCache.cache.Set("corrupted", "invalid-data", time.Hour) // System should handle corrupted cache gracefully - validToken, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]any{ + validToken, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]interface{}{ "iss": "https://test-issuer.com", "aud": "test-client-id", "exp": float64(time.Now().Add(1 * time.Hour).Unix()), @@ -681,7 +681,7 @@ func TestErrorRecoveryPatterns(t *testing.T) { ts.tOidc.tokenBlacklist.Set("corrupted-entry", "invalid-data", time.Hour) // System should still function - validToken, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]any{ + validToken, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]interface{}{ "iss": "https://test-issuer.com", "aud": "test-client-id", "exp": float64(time.Now().Add(1 * time.Hour).Unix()), @@ -713,8 +713,8 @@ func TestPerformanceUnderLoad(t *testing.T) { // Create multiple valid tokens const numTokens = 100 tokens := make([]string, numTokens) - for i := range numTokens { - token, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]any{ + for i := 0; i < numTokens; i++ { + token, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]interface{}{ "iss": "https://test-issuer.com", "aud": "test-client-id", "exp": float64(time.Now().Add(1 * time.Hour).Unix()), @@ -757,7 +757,7 @@ func TestPerformanceUnderLoad(t *testing.T) { const iterations = 1000 start := time.Now() - for i := range iterations { + for i := 0; i < iterations; i++ { tokenIndex := i % numTokens err := tOidc.VerifyToken(tokens[tokenIndex]) if err != nil { diff --git a/security_edge_cases_test.go b/security_edge_cases_test.go index 7ef0259..ab9dfaa 100644 --- a/security_edge_cases_test.go +++ b/security_edge_cases_test.go @@ -11,7 +11,6 @@ import ( "fmt" "net/http" "net/http/httptest" - "slices" "strings" "testing" "time" @@ -26,7 +25,7 @@ func TestJWTAlgorithmConfusionAttack(t *testing.T) { ts.Setup() // Create a standard JWT with RS256 algorithm - validRS256JWT, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]any{ + validRS256JWT, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]interface{}{ "iss": "https://test-issuer.com", "aud": "test-client-id", "exp": float64(time.Now().Add(1 * time.Hour).Unix()), @@ -52,7 +51,7 @@ func TestJWTAlgorithmConfusionAttack(t *testing.T) { } // Parse header - var header map[string]any + var header map[string]interface{} if err := json.Unmarshal(headerBytes, &header); err != nil { t.Fatalf("Failed to unmarshal header: %v", err) } @@ -91,7 +90,7 @@ func TestJWTNoneAlgorithmAttack(t *testing.T) { ts.Setup() // Create a standard JWT - validJWT, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]any{ + validJWT, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]interface{}{ "iss": "https://test-issuer.com", "aud": "test-client-id", "exp": float64(time.Now().Add(1 * time.Hour).Unix()), @@ -117,7 +116,7 @@ func TestJWTNoneAlgorithmAttack(t *testing.T) { } // Parse header - var header map[string]any + var header map[string]interface{} if err := json.Unmarshal(headerBytes, &header); err != nil { t.Fatalf("Failed to unmarshal header: %v", err) } @@ -155,7 +154,7 @@ func TestJWTTokenTampering(t *testing.T) { ts.Setup() // Create a standard JWT - validJWT, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]any{ + validJWT, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]interface{}{ "iss": "https://test-issuer.com", "aud": "test-client-id", "exp": float64(time.Now().Add(1 * time.Hour).Unix()), @@ -181,7 +180,7 @@ func TestJWTTokenTampering(t *testing.T) { } // Parse claims - var claims map[string]any + var claims map[string]interface{} if err := json.Unmarshal(claimsBytes, &claims); err != nil { t.Fatalf("Failed to unmarshal claims: %v", err) } @@ -220,7 +219,7 @@ func TestJWTExpiredToken(t *testing.T) { ts.Setup() // Create a JWT that is already expired - expiredJWT, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]any{ + expiredJWT, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]interface{}{ "iss": "https://test-issuer.com", "aud": "test-client-id", "exp": float64(time.Now().Add(-1 * time.Hour).Unix()), // Expired 1 hour ago @@ -253,7 +252,7 @@ func TestJWTFutureToken(t *testing.T) { ts.Setup() // Create a JWT with a future issuance time - futureJWT, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]any{ + futureJWT, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]interface{}{ "iss": "https://test-issuer.com", "aud": "test-client-id", "exp": float64(time.Now().Add(2 * time.Hour).Unix()), @@ -316,7 +315,7 @@ func TestJWTReplayAttack(t *testing.T) { fixedJTI := "fixed-test-jti-for-replay-" + generateRandomString(8) // Create a JWT with the fixed JTI - replayJWT, err := createTestJWT(rsaPrivateKey, "RS256", "test-key-id", map[string]any{ + replayJWT, err := createTestJWT(rsaPrivateKey, "RS256", "test-key-id", map[string]interface{}{ "iss": "https://test-issuer.com", "aud": "test-client-id", "exp": float64(time.Now().Add(1 * time.Hour).Unix()), @@ -423,7 +422,7 @@ func TestMissingClaims(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { // Create standard claims - claims := map[string]any{ + claims := map[string]interface{}{ "iss": "https://test-issuer.com", "aud": "test-client-id", "exp": float64(time.Now().Add(1 * time.Hour).Unix()), @@ -599,7 +598,13 @@ func TestSessionFixationAttack(t *testing.T) { // - The response is unauthorized (401), OR // - The token verification failed expectedCodes := []int{http.StatusFound, http.StatusUnauthorized, http.StatusForbidden} - codeFound := slices.Contains(expectedCodes, victimResp.Code) + codeFound := false + for _, code := range expectedCodes { + if victimResp.Code == code { + codeFound = true + break + } + } if !codeFound { t.Errorf("Expected status code to be one of %v, but got %d", expectedCodes, victimResp.Code) @@ -824,7 +829,7 @@ func TestTokenBlacklisting(t *testing.T) { } // Create a valid JWT - validJWT, err := createTestJWT(rsaPrivateKey, "RS256", "test-key-id", map[string]any{ + validJWT, err := createTestJWT(rsaPrivateKey, "RS256", "test-key-id", map[string]interface{}{ "iss": "https://test-issuer.com", "aud": "test-client-id", "exp": float64(time.Now().Add(1 * time.Hour).Unix()), @@ -914,7 +919,7 @@ func TestDifferentSigningAlgorithms(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { // Define standard claims with unique JTI for each test - standardClaims := map[string]any{ + standardClaims := map[string]interface{}{ "iss": "https://test-issuer.com", "aud": "test-client-id", "exp": float64(time.Now().Add(1 * time.Hour).Unix()), @@ -1017,9 +1022,9 @@ func TestDifferentSigningAlgorithms(t *testing.T) { } // createTestJWTWithECKey creates a JWT signed with an EC private key -func createTestJWTWithECKey(privateKey *ecdsa.PrivateKey, alg, kid string, claims map[string]any) (string, error) { +func createTestJWTWithECKey(privateKey *ecdsa.PrivateKey, alg, kid string, claims map[string]interface{}) (string, error) { // Create the header - header := map[string]any{ + header := map[string]interface{}{ "alg": alg, "typ": "JWT", "kid": kid, @@ -1276,7 +1281,7 @@ func TestRateLimiting(t *testing.T) { tOidc.tokenVerifier = tOidc // Create a valid JWT token - validJWT, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]any{ + validJWT, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]interface{}{ "iss": "https://test-issuer.com", "aud": "test-client-id", "exp": float64(time.Now().Add(1 * time.Hour).Unix()), @@ -1296,7 +1301,7 @@ func TestRateLimiting(t *testing.T) { } // Second request should succeed - validJWT2, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]any{ + validJWT2, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]interface{}{ "iss": "https://test-issuer.com", "aud": "test-client-id", "exp": float64(time.Now().Add(1 * time.Hour).Unix()), @@ -1315,7 +1320,7 @@ func TestRateLimiting(t *testing.T) { } // Third request should be rate limited - validJWT3, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]any{ + validJWT3, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]interface{}{ "iss": "https://test-issuer.com", "aud": "test-client-id", "exp": float64(time.Now().Add(1 * time.Hour).Unix()), @@ -1399,7 +1404,13 @@ func TestAuthorizationHeaderBypass(t *testing.T) { // Verify that the response is a redirect to authentication (302) or unauthorized (401) expectedCodes := []int{http.StatusFound, http.StatusUnauthorized} - codeFound := slices.Contains(expectedCodes, resp.Code) + codeFound := false + for _, code := range expectedCodes { + if resp.Code == code { + codeFound = true + break + } + } if !codeFound { t.Errorf("Expected status code to be one of %v, but got %d", expectedCodes, resp.Code) @@ -1412,7 +1423,7 @@ func TestEmptyAudience(t *testing.T) { ts.Setup() // Create a JWT with empty audience - emptyAudJWT, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]any{ + emptyAudJWT, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]interface{}{ "iss": "https://test-issuer.com", "aud": "", // Empty audience "exp": float64(time.Now().Add(1 * time.Hour).Unix()), @@ -1445,7 +1456,7 @@ func TestEmptyIssuer(t *testing.T) { ts.Setup() // Create a JWT with empty issuer - emptyIssJWT, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]any{ + emptyIssJWT, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]interface{}{ "iss": "", // Empty issuer "aud": "test-client-id", "exp": float64(time.Now().Add(1 * time.Hour).Unix()), diff --git a/security_monitoring.go b/security_monitoring.go index e8df0d7..c605367 100644 --- a/security_monitoring.go +++ b/security_monitoring.go @@ -55,14 +55,14 @@ func (t SecurityEventType) IPFailureType() string { // SecurityEvent represents a security-related event that should be logged and monitored type SecurityEvent struct { - Timestamp time.Time `json:"timestamp"` - Details map[string]any `json:"details,omitempty"` - Type string `json:"type"` - Severity string `json:"severity"` - ClientIP string `json:"client_ip"` - UserAgent string `json:"user_agent"` - RequestPath string `json:"request_path"` - Message string `json:"message"` + Timestamp time.Time `json:"timestamp"` + Details map[string]interface{} `json:"details,omitempty"` + Type string `json:"type"` + Severity string `json:"severity"` + ClientIP string `json:"client_ip"` + UserAgent string `json:"user_agent"` + RequestPath string `json:"request_path"` + Message string `json:"message"` } // SecurityMonitor tracks security events and suspicious activity patterns @@ -168,7 +168,7 @@ func (sm *SecurityMonitor) RecordSecurityEvent( eventType SecurityEventType, clientIP, userAgent, requestPath string, message string, - details map[string]any, + details map[string]interface{}, trackIPFailure bool) { // Create event with default values for the event type @@ -193,9 +193,9 @@ func (sm *SecurityMonitor) RecordSecurityEvent( } // RecordAuthenticationFailure records an authentication failure event -func (sm *SecurityMonitor) RecordAuthenticationFailure(clientIP, userAgent, requestPath, reason string, details map[string]any) { +func (sm *SecurityMonitor) RecordAuthenticationFailure(clientIP, userAgent, requestPath, reason string, details map[string]interface{}) { if details == nil { - details = make(map[string]any) + details = make(map[string]interface{}) } details["reason"] = reason @@ -212,7 +212,7 @@ func (sm *SecurityMonitor) RecordAuthenticationFailure(clientIP, userAgent, requ // RecordTokenValidationFailure records a token validation failure func (sm *SecurityMonitor) RecordTokenValidationFailure(clientIP, userAgent, requestPath, reason string, tokenPrefix string) { - details := map[string]any{ + details := map[string]interface{}{ "reason": reason, } if tokenPrefix != "" { @@ -232,7 +232,7 @@ func (sm *SecurityMonitor) RecordTokenValidationFailure(clientIP, userAgent, req // RecordRateLimitHit records when rate limiting is triggered func (sm *SecurityMonitor) RecordRateLimitHit(clientIP, userAgent, requestPath string) { - details := map[string]any{ + details := map[string]interface{}{ "limit_type": "token_verification", } @@ -248,9 +248,9 @@ func (sm *SecurityMonitor) RecordRateLimitHit(clientIP, userAgent, requestPath s } // RecordSuspiciousActivity records suspicious activity that doesn't fit other categories -func (sm *SecurityMonitor) RecordSuspiciousActivity(clientIP, userAgent, requestPath, activityType, description string, details map[string]any) { +func (sm *SecurityMonitor) RecordSuspiciousActivity(clientIP, userAgent, requestPath, activityType, description string, details map[string]interface{}) { if details == nil { - details = make(map[string]any) + details = make(map[string]interface{}) } details["activity_type"] = activityType @@ -302,7 +302,7 @@ func (sm *SecurityMonitor) recordIPFailure(clientIP, failureType string) { Timestamp: time.Now(), ClientIP: clientIP, Message: fmt.Sprintf("IP blocked due to %d failures in %d minutes", tracker.FailureCount, sm.config.FailureWindowMinutes), - Details: map[string]any{ + Details: map[string]interface{}{ "failure_count": tracker.FailureCount, "failure_types": tracker.FailureTypes, "blocked_until": tracker.BlockedUntil, @@ -355,7 +355,7 @@ func (sm *SecurityMonitor) processSecurityEvent(event SecurityEvent) { Severity: "high", Timestamp: time.Now(), Message: fmt.Sprintf("Suspicious pattern detected: %s", pattern), - Details: map[string]any{ + Details: map[string]interface{}{ "pattern_type": pattern, "trigger_event": event, }, @@ -389,8 +389,8 @@ func (sm *SecurityMonitor) AddEventHandler(handler SecurityEventHandler) { // GetSecurityMetrics returns minimal security metrics // This is kept for API compatibility but doesn't collect actual metrics -func (sm *SecurityMonitor) GetSecurityMetrics() map[string]any { - return map[string]any{ +func (sm *SecurityMonitor) GetSecurityMetrics() map[string]interface{} { + return map[string]interface{}{ "tracked_ips": 0, } } diff --git a/security_monitoring_test.go b/security_monitoring_test.go index a286624..3179657 100644 --- a/security_monitoring_test.go +++ b/security_monitoring_test.go @@ -2,7 +2,6 @@ package traefikoidc import ( "net/http/httptest" - "slices" "strconv" "testing" "time" @@ -53,7 +52,7 @@ func TestSecurityMonitor(t *testing.T) { }) t.Run("Suspicious activity", func(t *testing.T) { - details := map[string]any{"pattern": "unusual"} + details := map[string]interface{}{"pattern": "unusual"} // Just verify the method doesn't panic monitor.RecordSuspiciousActivity("192.168.1.5", "test-agent", "/admin", "unusual pattern", "high frequency requests", details) }) @@ -64,7 +63,7 @@ func TestSuspiciousPatternDetector(t *testing.T) { t.Run("Add events and detect patterns", func(t *testing.T) { // Add multiple events from same IP - for range 10 { + for i := 0; i < 10; i++ { event := SecurityEvent{ Type: "authentication_failure", ClientIP: "192.168.1.100", @@ -75,7 +74,13 @@ func TestSuspiciousPatternDetector(t *testing.T) { patterns := detector.DetectSuspiciousPatterns() - found := slices.Contains(patterns, "rapid_failures_from_ip_192.168.1.100") + found := false + for _, p := range patterns { + if p == "rapid_failures_from_ip_192.168.1.100" { + found = true + break + } + } if !found { t.Error("Expected to detect rapid failure pattern") } @@ -83,7 +88,7 @@ func TestSuspiciousPatternDetector(t *testing.T) { t.Run("Detect distributed attack pattern", func(t *testing.T) { // Add failures from many different IPs - for i := range 25 { + for i := 0; i < 25; i++ { event := SecurityEvent{ Type: "authentication_failure", ClientIP: "192.168.1." + strconv.Itoa(100+i), @@ -94,7 +99,13 @@ func TestSuspiciousPatternDetector(t *testing.T) { patterns := detector.DetectSuspiciousPatterns() - found := slices.Contains(patterns, "distributed_attack_pattern") + found := false + for _, p := range patterns { + if p == "distributed_attack_pattern" { + found = true + break + } + } if !found { t.Error("Expected to detect distributed attack pattern") } @@ -266,7 +277,7 @@ func TestSecurityEventTypes(t *testing.T) { monitor.RecordTokenValidationFailure("192.168.1.200", "test-agent", "/api", "expired token", "abc123") monitor.RecordRateLimitHit("192.168.1.200", "test-agent", "/api") - details := map[string]any{"pattern": "test"} + details := map[string]interface{}{"pattern": "test"} monitor.RecordSuspiciousActivity("192.168.1.200", "test-agent", "/admin", "unusual pattern", "multiple failed logins", details) // Just verify GetSecurityMetrics doesn't panic diff --git a/session.go b/session.go index 6526d20..cb67dbc 100644 --- a/session.go +++ b/session.go @@ -238,7 +238,7 @@ func NewSessionManager(encryptionKey string, forceHTTPS bool, logger *Logger) (* logger: logger, } - sm.sessionPool.New = func() any { + sm.sessionPool.New = func() interface{} { sd := &SessionData{ manager: sm, accessTokenChunks: make(map[int]*sessions.Session), @@ -699,7 +699,7 @@ func (sd *SessionData) SetAuthenticated(value bool) error { } maxRetries := 5 - for retry := range maxRetries { + for retry := 0; retry < maxRetries; retry++ { if sd.mainSession.ID != id { break } @@ -869,7 +869,7 @@ func (sd *SessionData) getAccessTokenUnsafe() string { totalSize := 0 // Sequential chunk validation with gap detection - for i := range expectedChunkCount { + for i := 0; i < expectedChunkCount; i++ { session, ok := sd.accessTokenChunks[i] if !ok { sd.manager.logger.Errorf("CRITICAL: Access token chunk %d missing - gap in sequence detected", i) @@ -1205,7 +1205,7 @@ func (sd *SessionData) GetRefreshToken() string { totalSize := 0 // Sequential chunk validation with gap detection - for i := range expectedChunkCount { + for i := 0; i < expectedChunkCount; i++ { session, ok := sd.refreshTokenChunks[i] if !ok { sd.manager.logger.Errorf("CRITICAL: Refresh token chunk %d missing - gap in sequence detected", i) @@ -1460,7 +1460,7 @@ func (sd *SessionData) expireAccessTokenChunksEnhanced(w http.ResponseWriter) { const maxChunkSearchLimit = 50 // Limit search to prevent infinite loops from corrupted state orphanedChunks := 0 - for i := range maxChunkSearchLimit { + for i := 0; i < maxChunkSearchLimit; i++ { sessionName := fmt.Sprintf("%s_%d", accessTokenCookie, i) session, err := sd.manager.store.Get(sd.request, sessionName) if err != nil { @@ -1490,7 +1490,7 @@ func (sd *SessionData) expireAccessTokenChunksEnhanced(w http.ResponseWriter) { // Expire the chunk regardless of orphan status session.Options.MaxAge = -1 - session.Values = make(map[any]any) + session.Values = make(map[interface{}]interface{}) if w != nil { if err := session.Save(sd.request, w); err != nil { sd.manager.logger.Errorf("failed to save expired access token chunk %d: %v", i, err) @@ -1513,7 +1513,7 @@ func (sd *SessionData) expireRefreshTokenChunksEnhanced(w http.ResponseWriter) { const maxChunkSearchLimit = 50 // Limit search to prevent infinite loops from corrupted state orphanedChunks := 0 - for i := range maxChunkSearchLimit { + for i := 0; i < maxChunkSearchLimit; i++ { sessionName := fmt.Sprintf("%s_%d", refreshTokenCookie, i) session, err := sd.manager.store.Get(sd.request, sessionName) if err != nil { @@ -1543,7 +1543,7 @@ func (sd *SessionData) expireRefreshTokenChunksEnhanced(w http.ResponseWriter) { // Expire the chunk regardless of orphan status session.Options.MaxAge = -1 - session.Values = make(map[any]any) + session.Values = make(map[interface{}]interface{}) if w != nil { if err := session.Save(sd.request, w); err != nil { sd.manager.logger.Errorf("failed to save expired refresh token chunk %d: %v", i, err) @@ -1566,7 +1566,7 @@ func (sd *SessionData) expireIDTokenChunksEnhanced(w http.ResponseWriter) { const maxChunkSearchLimit = 50 // Limit search to prevent infinite loops from corrupted state orphanedChunks := 0 - for i := range maxChunkSearchLimit { + for i := 0; i < maxChunkSearchLimit; i++ { sessionName := fmt.Sprintf("%s_%d", idTokenCookie, i) session, err := sd.manager.store.Get(sd.request, sessionName) if err != nil { @@ -1596,7 +1596,7 @@ func (sd *SessionData) expireIDTokenChunksEnhanced(w http.ResponseWriter) { // Expire the chunk regardless of orphan status session.Options.MaxAge = -1 - session.Values = make(map[any]any) + session.Values = make(map[interface{}]interface{}) if w != nil { if err := session.Save(sd.request, w); err != nil { sd.manager.logger.Errorf("failed to save expired ID token chunk %d: %v", i, err) @@ -1896,7 +1896,7 @@ func (sd *SessionData) getIDTokenUnsafe() string { totalSize := 0 // Sequential chunk validation with gap detection - for i := range expectedChunkCount { + for i := 0; i < expectedChunkCount; i++ { session, ok := sd.idTokenChunks[i] if !ok { sd.manager.logger.Errorf("CRITICAL: ID token chunk %d missing - gap in sequence detected", i) diff --git a/session_test.go b/session_test.go index 091fc1f..6c82b35 100644 --- a/session_test.go +++ b/session_test.go @@ -148,7 +148,7 @@ func getPooledObjects(sm *SessionManager) int { var objects []*SessionData maxAttempts := 100 // Safety limit to prevent infinite loops - for range maxAttempts { + for i := 0; i < maxAttempts; i++ { obj := sm.sessionPool.Get() if obj == nil { break @@ -195,7 +195,7 @@ func TestSessionObjectTracking(t *testing.T) { } // Create and discard 5 sessions - for range 5 { + for i := 0; i < 5; i++ { session, err := sm.GetSession(req) if err != nil { t.Fatalf("GetSession failed: %v", err) @@ -609,11 +609,11 @@ func TestConcurrentTokenOperations(t *testing.T) { // Test concurrent access and refresh token operations done := make(chan bool, numGoroutines) - for i := range numGoroutines { + for i := 0; i < numGoroutines; i++ { go func(id int) { defer func() { done <- true }() - for j := range numOperations { + for j := 0; j < numOperations; j++ { // Create unique tokens for each goroutine/operation accessToken := ValidAccessToken refreshToken := fmt.Sprintf("refresh_token_%d_%d", id, j) @@ -637,7 +637,7 @@ func TestConcurrentTokenOperations(t *testing.T) { } // Wait for all goroutines to complete - for range numGoroutines { + for i := 0; i < numGoroutines; i++ { <-done } } diff --git a/settings.go b/settings.go index 49eca6e..b585f77 100644 --- a/settings.go +++ b/settings.go @@ -414,7 +414,7 @@ func NewLogger(logLevel string) *Logger { // Parameters: // - format: The format string (as in fmt.Printf). // - args: The arguments for the format string. -func (l *Logger) Info(format string, args ...any) { +func (l *Logger) Info(format string, args ...interface{}) { l.logInfo.Printf(format, args...) } @@ -424,7 +424,7 @@ func (l *Logger) Info(format string, args ...any) { // Parameters: // - format: The format string (as in fmt.Printf). // - args: The arguments for the format string. -func (l *Logger) Debug(format string, args ...any) { +func (l *Logger) Debug(format string, args ...interface{}) { l.logDebug.Printf(format, args...) } @@ -434,7 +434,7 @@ func (l *Logger) Debug(format string, args ...any) { // Parameters: // - format: The format string (as in fmt.Printf). // - args: The arguments for the format string. -func (l *Logger) Error(format string, args ...any) { +func (l *Logger) Error(format string, args ...interface{}) { l.logError.Printf(format, args...) } @@ -445,7 +445,7 @@ func (l *Logger) Error(format string, args ...any) { // Parameters: // - format: The format string (as in fmt.Printf). // - args: The arguments for the format string. -func (l *Logger) Infof(format string, args ...any) { +func (l *Logger) Infof(format string, args ...interface{}) { l.logInfo.Printf(format, args...) } @@ -456,7 +456,7 @@ func (l *Logger) Infof(format string, args ...any) { // Parameters: // - format: The format string (as in fmt.Printf). // - args: The arguments for the format string. -func (l *Logger) Debugf(format string, args ...any) { +func (l *Logger) Debugf(format string, args ...interface{}) { l.logDebug.Printf(format, args...) } @@ -467,7 +467,7 @@ func (l *Logger) Debugf(format string, args ...any) { // Parameters: // - format: The format string (as in fmt.Printf). // - args: The arguments for the format string. -func (l *Logger) Errorf(format string, args ...any) { +func (l *Logger) Errorf(format string, args ...interface{}) { l.logError.Printf(format, args...) } diff --git a/templated_header_execution_test.go b/templated_header_execution_test.go index e1bff0e..72f04c1 100644 --- a/templated_header_execution_test.go +++ b/templated_header_execution_test.go @@ -11,15 +11,15 @@ func TestTemplateExecution(t *testing.T) { tests := []struct { name string templateText string - data map[string]any + data map[string]interface{} expectedValue string expectError bool }{ { name: "String Claim", templateText: "{{.Claims.email}}", - data: map[string]any{ - "Claims": map[string]any{ + data: map[string]interface{}{ + "Claims": map[string]interface{}{ "email": "user@example.com", }, }, @@ -29,8 +29,8 @@ func TestTemplateExecution(t *testing.T) { { name: "Number Claim", templateText: "{{.Claims.age}}", - data: map[string]any{ - "Claims": map[string]any{ + data: map[string]interface{}{ + "Claims": map[string]interface{}{ "age": 30, }, }, @@ -40,8 +40,8 @@ func TestTemplateExecution(t *testing.T) { { name: "Boolean Claim", templateText: "{{.Claims.admin}}", - data: map[string]any{ - "Claims": map[string]any{ + data: map[string]interface{}{ + "Claims": map[string]interface{}{ "admin": true, }, }, @@ -51,8 +51,8 @@ func TestTemplateExecution(t *testing.T) { { name: "Array Claim", templateText: "{{index .Claims.roles 0}}", - data: map[string]any{ - "Claims": map[string]any{ + data: map[string]interface{}{ + "Claims": map[string]interface{}{ "roles": []string{"admin", "user"}, }, }, @@ -62,9 +62,9 @@ func TestTemplateExecution(t *testing.T) { { name: "Nested Object Claim", templateText: "{{.Claims.user.name}}", - data: map[string]any{ - "Claims": map[string]any{ - "user": map[string]any{ + data: map[string]interface{}{ + "Claims": map[string]interface{}{ + "user": map[string]interface{}{ "name": "John Doe", }, }, @@ -75,7 +75,7 @@ func TestTemplateExecution(t *testing.T) { { name: "Access Token", templateText: "Bearer {{.AccessToken}}", - data: map[string]any{ + data: map[string]interface{}{ "AccessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.Et9HFtf9R3GEMA0IICOfFMVXY7kkTX1wr4qCyhIf58U", }, expectedValue: "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.Et9HFtf9R3GEMA0IICOfFMVXY7kkTX1wr4qCyhIf58U", @@ -84,7 +84,7 @@ func TestTemplateExecution(t *testing.T) { { name: "ID Token", templateText: "{{.IdToken}}", - data: map[string]any{ + data: map[string]interface{}{ "IdToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.Et9HFtf9R3GEMA0IICOfFMVXY7kkTX1wr4qCyhIf58U", }, expectedValue: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.Et9HFtf9R3GEMA0IICOfFMVXY7kkTX1wr4qCyhIf58U", @@ -93,7 +93,7 @@ func TestTemplateExecution(t *testing.T) { { name: "Refresh Token", templateText: "{{.RefreshToken}}", - data: map[string]any{ + data: map[string]interface{}{ "RefreshToken": "refresh-token-value", }, expectedValue: "refresh-token-value", @@ -102,8 +102,8 @@ func TestTemplateExecution(t *testing.T) { { name: "Conditional Template", templateText: "{{if .Claims.admin}}Admin User{{else}}Regular User{{end}}", - data: map[string]any{ - "Claims": map[string]any{ + data: map[string]interface{}{ + "Claims": map[string]interface{}{ "admin": true, }, }, @@ -113,8 +113,8 @@ func TestTemplateExecution(t *testing.T) { { name: "Multiple Claims", templateText: "{{.Claims.firstName}} {{.Claims.lastName}} <{{.Claims.email}}>", - data: map[string]any{ - "Claims": map[string]any{ + data: map[string]interface{}{ + "Claims": map[string]interface{}{ "firstName": "John", "lastName": "Doe", "email": "john.doe@example.com", @@ -126,8 +126,8 @@ func TestTemplateExecution(t *testing.T) { { name: "Missing Claim", templateText: "{{.Claims.missing}}", - data: map[string]any{ - "Claims": map[string]any{}, + data: map[string]interface{}{ + "Claims": map[string]interface{}{}, }, expectedValue: "", expectError: false, // Go templates don't error on missing values @@ -135,8 +135,8 @@ func TestTemplateExecution(t *testing.T) { { name: "Invalid Template Syntax", templateText: "{{.Claims.email", - data: map[string]any{ - "Claims": map[string]any{ + data: map[string]interface{}{ + "Claims": map[string]interface{}{ "email": "user@example.com", }, }, @@ -146,8 +146,8 @@ func TestTemplateExecution(t *testing.T) { { name: "Custom Claims", templateText: "Role: {{.Claims.role}}, Department: {{.Claims.department}}", - data: map[string]any{ - "Claims": map[string]any{ + data: map[string]interface{}{ + "Claims": map[string]interface{}{ "email": "user@example.com", "role": "admin", "department": "engineering", @@ -159,10 +159,10 @@ func TestTemplateExecution(t *testing.T) { { name: "Nested Custom Claims", templateText: "Org: {{.Claims.metadata.organization}}, Team: {{.Claims.metadata.team}}", - data: map[string]any{ - "Claims": map[string]any{ + data: map[string]interface{}{ + "Claims": map[string]interface{}{ "email": "user@example.com", - "metadata": map[string]any{ + "metadata": map[string]interface{}{ "organization": "company-name", "team": "platform", }, @@ -174,8 +174,8 @@ func TestTemplateExecution(t *testing.T) { { name: "Email Claims", templateText: "Email: {{.Claims.email}}, Verified: {{.Claims.email_verified}}", - data: map[string]any{ - "Claims": map[string]any{ + data: map[string]interface{}{ + "Claims": map[string]interface{}{ "email": "user@example.com", "email_verified": true, }, @@ -186,8 +186,8 @@ func TestTemplateExecution(t *testing.T) { { name: "User Identity Claims", templateText: "Name: {{.Claims.name}}, Subject: {{.Claims.sub}}, Username: {{.Claims.preferred_username}}", - data: map[string]any{ - "Claims": map[string]any{ + data: map[string]interface{}{ + "Claims": map[string]interface{}{ "name": "John Doe", "sub": "user123", "preferred_username": "johndoe", @@ -233,16 +233,16 @@ func TestTemplateExecutionContext(t *testing.T) { mapTests := []struct { name string templateText string - data map[string]any + data map[string]interface{} expectedValue string }{ { name: "Access and ID token distinction with map", templateText: "Access: {{.AccessToken}} ID: {{.IdToken}}", - data: map[string]any{ + data: map[string]interface{}{ "AccessToken": "access-token-value", "IdToken": "id-token-value", - "Claims": map[string]any{}, + "Claims": map[string]interface{}{}, "RefreshToken": "refresh-token-value", }, expectedValue: "Access: access-token-value ID: id-token-value", @@ -250,10 +250,10 @@ func TestTemplateExecutionContext(t *testing.T) { { name: "Combining tokens and claims with map", templateText: "User: {{.Claims.sub}} Token: {{.AccessToken}}", - data: map[string]any{ + data: map[string]interface{}{ "AccessToken": "access-token", "IdToken": "id-token", - "Claims": map[string]any{ + "Claims": map[string]interface{}{ "sub": "user123", }, "RefreshToken": "refresh-token", @@ -263,17 +263,17 @@ func TestTemplateExecutionContext(t *testing.T) { { name: "Authorization header with Bearer token", templateText: "Bearer {{.AccessToken}}", - data: map[string]any{ + data: map[string]interface{}{ "AccessToken": "jwt-access-token", "IdToken": "id-token", - "Claims": map[string]any{}, + "Claims": map[string]interface{}{}, }, expectedValue: "Bearer jwt-access-token", }, { name: "Boolean template data with AccessToken", templateText: "Bearer {{.AccessToken}}", - data: map[string]any{ + data: map[string]interface{}{ "AccessToken": true, // Test boolean values to ensure they render correctly }, expectedValue: "Bearer true", @@ -281,10 +281,10 @@ func TestTemplateExecutionContext(t *testing.T) { { name: "Custom non-standard claims in ID token", templateText: "X-User-Role: {{.Claims.role}}, X-User-Permissions: {{.Claims.permissions}}", - data: map[string]any{ + data: map[string]interface{}{ "AccessToken": "access-token-value", "IdToken": "id-token-value", - "Claims": map[string]any{ + "Claims": map[string]interface{}{ "email": "user@example.com", "role": "admin", "permissions": "read:all,write:own", @@ -295,11 +295,11 @@ func TestTemplateExecutionContext(t *testing.T) { { name: "Deeply nested custom claims", templateText: "X-Organization: {{.Claims.app_metadata.organization.name}}, X-Team: {{.Claims.app_metadata.team}}", - data: map[string]any{ + data: map[string]interface{}{ "AccessToken": "access-token-value", - "Claims": map[string]any{ - "app_metadata": map[string]any{ - "organization": map[string]any{ + "Claims": map[string]interface{}{ + "app_metadata": map[string]interface{}{ + "organization": map[string]interface{}{ "name": "acme-corp", "id": "org-123", }, @@ -312,10 +312,10 @@ func TestTemplateExecutionContext(t *testing.T) { { name: "Email in claims", templateText: "X-User-Email: {{.Claims.email}}, X-Email-Verified: {{.Claims.email_verified}}", - data: map[string]any{ + data: map[string]interface{}{ "AccessToken": "access-token-value", "IdToken": "id-token-value", - "Claims": map[string]any{ + "Claims": map[string]interface{}{ "email": "user@example.com", "email_verified": true, }, @@ -325,10 +325,10 @@ func TestTemplateExecutionContext(t *testing.T) { { name: "User info from claims", templateText: "X-User-ID: {{.Claims.sub}}, X-User-Name: {{.Claims.name}}, X-Username: {{.Claims.preferred_username}}", - data: map[string]any{ + data: map[string]interface{}{ "AccessToken": "access-token-value", "IdToken": "id-token-value", - "Claims": map[string]any{ + "Claims": map[string]interface{}{ "sub": "user123456", "name": "Jane Doe", "preferred_username": "jane.doe", @@ -361,7 +361,7 @@ func TestTemplateExecutionContext(t *testing.T) { // For backward compatibility, also test the original struct-based implementation type templateData struct { - Claims map[string]any + Claims map[string]interface{} AccessToken string IdToken string RefreshToken string @@ -380,7 +380,7 @@ func TestTemplateExecutionContext(t *testing.T) { data: templateData{ AccessToken: "access-token-value", IdToken: "id-token-value", // Now these should be distinct values - Claims: map[string]any{}, + Claims: map[string]interface{}{}, }, expectedValue: "Access: access-token-value ID: id-token-value", }, @@ -390,7 +390,7 @@ func TestTemplateExecutionContext(t *testing.T) { data: templateData{ AccessToken: "access-token", IdToken: "access-token", - Claims: map[string]any{ + Claims: map[string]interface{}{ "sub": "user123", }, }, @@ -402,7 +402,7 @@ func TestTemplateExecutionContext(t *testing.T) { data: templateData{ AccessToken: "access-token", IdToken: "id-token", - Claims: map[string]any{ + Claims: map[string]interface{}{ "sub": "user123", "custom_field": "custom-value", "group": "admins", @@ -416,7 +416,7 @@ func TestTemplateExecutionContext(t *testing.T) { data: templateData{ AccessToken: "access-token", IdToken: "id-token", - Claims: map[string]any{ + Claims: map[string]interface{}{ "email": "user@example.com", "name": "John Smith", }, @@ -454,14 +454,14 @@ func TestRegressionBooleanAccessToken(t *testing.T) { testCases := []struct { name string templateText string - dataContext any + dataContext interface{} expectedValue string expectError bool // Added to skip the test that demonstrates the error }{ { name: "Map with boolean as root", templateText: "{{.AccessToken}}", - dataContext: map[string]any{"AccessToken": "token-value"}, + dataContext: map[string]interface{}{"AccessToken": "token-value"}, expectedValue: "token-value", expectError: false, }, @@ -475,17 +475,17 @@ func TestRegressionBooleanAccessToken(t *testing.T) { { name: "Bearer with map context", templateText: "Bearer {{.AccessToken}}", - dataContext: map[string]any{"AccessToken": "token-value"}, + dataContext: map[string]interface{}{"AccessToken": "token-value"}, expectedValue: "Bearer token-value", expectError: false, }, { name: "Complex nesting with authorization", templateText: "Authorization: Bearer {{.AccessToken}}", - dataContext: map[string]any{ + dataContext: map[string]interface{}{ "AccessToken": "jwt-token-123", "something": true, - "anotherField": map[string]any{ + "anotherField": map[string]interface{}{ "nested": "value", }, }, @@ -495,13 +495,13 @@ func TestRegressionBooleanAccessToken(t *testing.T) { { name: "Custom claims access", templateText: "X-User-Role: {{.Claims.role}}, X-User-Groups: {{.Claims.groups}}", - dataContext: map[string]any{ + dataContext: map[string]interface{}{ "AccessToken": "jwt-token-xyz", - "Claims": map[string]any{ + "Claims": map[string]interface{}{ "email": "user@example.com", "role": "admin", "groups": "group1,group2,group3", - "custom_data": map[string]any{ + "custom_data": map[string]interface{}{ "organization": "company-name", "department": "engineering", }, @@ -513,9 +513,9 @@ func TestRegressionBooleanAccessToken(t *testing.T) { { name: "Nested custom claims access", templateText: "X-Organization: {{.Claims.custom_data.organization}}, X-Department: {{.Claims.custom_data.department}}", - dataContext: map[string]any{ - "Claims": map[string]any{ - "custom_data": map[string]any{ + dataContext: map[string]interface{}{ + "Claims": map[string]interface{}{ + "custom_data": map[string]interface{}{ "organization": "company-name", "department": "engineering", }, @@ -527,8 +527,8 @@ func TestRegressionBooleanAccessToken(t *testing.T) { { name: "Azure AD specific claims", templateText: "X-TenantID: {{.Claims.tid}}, X-Roles: {{.Claims.roles}}", - dataContext: map[string]any{ - "Claims": map[string]any{ + dataContext: map[string]interface{}{ + "Claims": map[string]interface{}{ "tid": "tenant-id-12345", "roles": "User,Admin,Developer", }, @@ -539,10 +539,10 @@ func TestRegressionBooleanAccessToken(t *testing.T) { { name: "Auth0 specific claims", templateText: "X-Permissions: {{.Claims.permissions}}, X-AppMetadata: {{.Claims.app_metadata.plan}}", - dataContext: map[string]any{ - "Claims": map[string]any{ + dataContext: map[string]interface{}{ + "Claims": map[string]interface{}{ "permissions": "read:products,write:orders", - "app_metadata": map[string]any{ + "app_metadata": map[string]interface{}{ "plan": "premium", "status": "active", "trial_ended": false, @@ -555,8 +555,8 @@ func TestRegressionBooleanAccessToken(t *testing.T) { { name: "Standard claims with email", templateText: "X-Email: {{.Claims.email}}, X-Name: {{.Claims.name}}, X-Subject: {{.Claims.sub}}", - dataContext: map[string]any{ - "Claims": map[string]any{ + dataContext: map[string]interface{}{ + "Claims": map[string]interface{}{ "email": "user@example.com", "name": "John Doe", "sub": "auth0|12345", @@ -568,8 +568,8 @@ func TestRegressionBooleanAccessToken(t *testing.T) { { name: "Verified email claim", templateText: "X-Email: {{.Claims.email}}, X-Email-Verified: {{.Claims.email_verified}}", - dataContext: map[string]any{ - "Claims": map[string]any{ + dataContext: map[string]interface{}{ + "Claims": map[string]interface{}{ "email": "user@example.com", "email_verified": true, }, diff --git a/templated_header_integration_test.go b/templated_header_integration_test.go index 9b8c17a..e5ce82b 100644 --- a/templated_header_integration_test.go +++ b/templated_header_integration_test.go @@ -2,7 +2,6 @@ package traefikoidc import ( "errors" - "maps" "net/http" "net/http/httptest" "testing" @@ -21,7 +20,7 @@ func TestTemplatedHeadersIntegration(t *testing.T) { tests := []struct { sessionSetup func(*SessionData) - claims map[string]any + claims map[string]interface{} expectedHeaders map[string]string interceptedHeaders map[string]string name string @@ -32,7 +31,7 @@ func TestTemplatedHeadersIntegration(t *testing.T) { headers: []TemplatedHeader{ {Name: "X-User-Email", Value: "{{.Claims.email}}"}, }, - claims: map[string]any{ + claims: map[string]interface{}{ "email": "user@example.com", }, expectedHeaders: map[string]string{ @@ -46,7 +45,7 @@ func TestTemplatedHeadersIntegration(t *testing.T) { {Name: "X-User-ID", Value: "{{.Claims.sub}}"}, {Name: "X-User-Name", Value: "{{.Claims.given_name}} {{.Claims.family_name}}"}, }, - claims: map[string]any{ + claims: map[string]interface{}{ "email": "user@example.com", "sub": "user123", "given_name": "John", @@ -95,7 +94,7 @@ func TestTemplatedHeadersIntegration(t *testing.T) { headers: []TemplatedHeader{ {Name: "X-User-Role", Value: "{{.Claims.role}}"}, }, - claims: map[string]any{ + claims: map[string]interface{}{ "email": "user@example.com", // role claim is missing }, @@ -108,7 +107,7 @@ func TestTemplatedHeadersIntegration(t *testing.T) { headers: []TemplatedHeader{ {Name: "X-User-Admin", Value: "{{if .Claims.is_admin}}true{{else}}false{{end}}"}, }, - claims: map[string]any{ + claims: map[string]interface{}{ "email": "admin@example.com", "is_admin": true, }, @@ -121,7 +120,7 @@ func TestTemplatedHeadersIntegration(t *testing.T) { headers: []TemplatedHeader{ {Name: "X-Auth-Info", Value: "User={{.Claims.email}}, Token={{.AccessToken}}"}, }, - claims: map[string]any{ + claims: map[string]interface{}{ "email": "user@example.com", }, expectedHeaders: map[string]string{ @@ -134,7 +133,7 @@ func TestTemplatedHeadersIntegration(t *testing.T) { headers: []TemplatedHeader{ {Name: "X-User-AccessToken", Value: "{{.AccessToken}}"}, }, - claims: map[string]any{ // For ID Token + claims: map[string]interface{}{ // For ID Token "email": "opaque_user@example.com", "sub": "opaque_sub_for_id_token", }, @@ -150,7 +149,7 @@ func TestTemplatedHeadersIntegration(t *testing.T) { token := ts.token if len(tc.claims) > 0 { var err error - baseClaims := map[string]any{ + baseClaims := map[string]interface{}{ "iss": "https://test-issuer.com", "aud": "test-client-id", "exp": float64(3000000000), // Far future timestamp @@ -162,7 +161,9 @@ func TestTemplatedHeadersIntegration(t *testing.T) { } // Add the test-specific claims - maps.Copy(baseClaims, tc.claims) + for k, v := range tc.claims { + baseClaims[k] = v + } token, err = createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", baseClaims) if err != nil { @@ -266,7 +267,7 @@ func TestTemplatedHeadersIntegration(t *testing.T) { session.SetRefreshToken("test-refresh-token") if tc.name == "ID Token Header" || tc.name == "Both Token Types" { - idTokenClaims := map[string]any{ + idTokenClaims := map[string]interface{}{ "iss": "https://test-issuer.com", "aud": "test-client-id", "exp": float64(3000000000), "iat": float64(1000000000), "nbf": float64(1000000000), "sub": "test-subject", "nonce": "test-nonce", "jti": generateRandomString(16), "type": "id_token", @@ -284,7 +285,7 @@ func TestTemplatedHeadersIntegration(t *testing.T) { t.Fatalf("Failed to create test ID JWT: %v", idErr) } - accessTokenClaims := map[string]any{ + accessTokenClaims := map[string]interface{}{ "iss": "https://test-issuer.com", "aud": "test-client-id", "exp": float64(3000000000), "iat": float64(1000000000), "nbf": float64(1000000000), "sub": "test-subject", "jti": generateRandomString(16), "type": "access_token", "scope": "openid email profile", @@ -315,13 +316,15 @@ func TestTemplatedHeadersIntegration(t *testing.T) { tc.expectedHeaders["X-Access-Token"] = accessTokenForSession } } else if tc.name == "Opaque Access Token with AccessTokenField" { - idTokenClaims := map[string]any{ + idTokenClaims := map[string]interface{}{ "iss": "https://test-issuer.com", "aud": "test-client-id", "exp": float64(3000000000), "iat": float64(1000000000), "nbf": float64(1000000000), "sub": "test-subject", // Default sub "nonce": "test-nonce", "jti": generateRandomString(16), "type": "id_token", } // Populate ID token claims from tc.claims - maps.Copy(idTokenClaims, tc.claims) + for k, v := range tc.claims { + idTokenClaims[k] = v + } // Ensure email from tc.claims is used for the ID token session.SetEmail(tc.claims["email"].(string)) // Also set it directly for initial session state @@ -423,7 +426,7 @@ func TestEdgeCaseTemplatedHeaders(t *testing.T) { ts.Setup() tests := []struct { - claims map[string]any + claims map[string]interface{} name string headers []TemplatedHeader shouldExecuteCheck bool @@ -444,8 +447,8 @@ func TestEdgeCaseTemplatedHeaders(t *testing.T) { headers: []TemplatedHeader{ {Name: "X-Roles", Value: "{{range $i, $e := .Claims.roles}}{{if $i}},{{end}}{{$e}}{{end}}"}, }, - claims: map[string]any{ - "roles": []any{"admin", "user", "manager"}, + claims: map[string]interface{}{ + "roles": []interface{}{"admin", "user", "manager"}, }, shouldExecuteCheck: true, }, @@ -454,7 +457,7 @@ func TestEdgeCaseTemplatedHeaders(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { // Create token with the test claims - claims := map[string]any{ + claims := map[string]interface{}{ "iss": "https://test-issuer.com", "aud": "test-client-id", "exp": float64(3000000000), // Far future timestamp @@ -466,7 +469,9 @@ func TestEdgeCaseTemplatedHeaders(t *testing.T) { } // Add the test-specific claims - maps.Copy(claims, tc.claims) + for k, v := range tc.claims { + claims[k] = v + } token, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", claims) if err != nil { @@ -571,7 +576,8 @@ func TestEdgeCaseTemplatedHeaders(t *testing.T) { // createLargeTemplate creates a template with many variable references func createLargeTemplate(size int) string { template := "{{with .Claims}}" - for i := range size { + for i := 0; i < size; i++ { + if i > 0 { template += "," } @@ -582,9 +588,9 @@ func createLargeTemplate(size int) string { } // createLargeClaims creates a map with many claims for testing large templates -func createLargeClaims(size int) map[string]any { - claims := make(map[string]any) - for i := range size { +func createLargeClaims(size int) map[string]interface{} { + claims := make(map[string]interface{}) + for i := 0; i < size; i++ { claims["email"] = "largeclaimsuser@example.com" // Add email claim key := "field" + string(rune('a'+i%26)) + string(rune('0'+i%10)) claims[key] = "value" + string(rune('a'+i%26)) + string(rune('0'+i%10)) diff --git a/test_tokens.go b/test_tokens.go index 570a416..68f770a 100644 --- a/test_tokens.go +++ b/test_tokens.go @@ -61,10 +61,13 @@ func (tt *TestTokens) CreateLargeValidJWT(targetSize int) string { // Calculate required payload size usedSize := len(header) + len(signature) + 2 // account for dots - payloadSize := max(targetSize-usedSize, 50) + payloadSize := targetSize - usedSize + if payloadSize < 50 { + payloadSize = 50 + } // Create a payload with realistic JWT claims - claims := map[string]any{ + claims := map[string]interface{}{ "sub": "user123", "iss": "https://example.com", "aud": "client123", @@ -72,12 +75,10 @@ func (tt *TestTokens) CreateLargeValidJWT(targetSize int) string { "iat": 1000000000, } - // FIXED: Calculate data size safely - dataSize := max( - // Account for other claims and base64 encoding - payloadSize-100, - // Minimum data size - 10) + dataSize := payloadSize - 100 // Account for other claims and base64 encoding + if dataSize < 10 { + dataSize = 10 // Minimum data size + } claims["data"] = tt.generateRandomString(dataSize) @@ -99,7 +100,7 @@ func (tt *TestTokens) CreateExpiredJWT() string { header := "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9" // Create claims with expired timestamp - claims := map[string]any{ + claims := map[string]interface{}{ "sub": "user123", "iss": "https://example.com", "aud": "client123", @@ -118,7 +119,7 @@ func (tt *TestTokens) CreateExpiredJWT() string { func (tt *TestTokens) CreateUniqueValidJWT(id string) string { header := "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9" - claims := map[string]any{ + claims := map[string]interface{}{ "sub": "user_" + id, "iss": "https://example.com", "aud": "client123", @@ -145,7 +146,7 @@ func (tt *TestTokens) CreateIncompressibleToken(targetSize int) string { payloadSize := max(targetSize-usedSize, 100) // Generate multiple random fields to prevent compression - randomFields := make(map[string]any) + randomFields := make(map[string]interface{}) randomFields["sub"] = "user123" randomFields["iss"] = "https://example.com" randomFields["aud"] = "client123" @@ -154,11 +155,12 @@ func (tt *TestTokens) CreateIncompressibleToken(targetSize int) string { // Add many random fields with random data to prevent compression remainingSize := payloadSize - 200 // Account for base64 encoding and other fields - fieldCount := max( - // ~100 bytes per field - remainingSize/100, 1) + fieldCount := remainingSize / 100 // ~100 bytes per field + if fieldCount < 1 { + fieldCount = 1 + } - for i := range fieldCount { + for i := 0; i < fieldCount; i++ { // Generate truly random data for each field randomBytes := make([]byte, 50) rand.Read(randomBytes) @@ -232,7 +234,7 @@ func (tt *TestTokens) generateRandomString(length int) string { const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" b := make([]byte, length) - for i := range b { + for i := 0; i < length; i++ { randomByte := make([]byte, 1) rand.Read(randomByte) b[i] = charset[int(randomByte[0])%len(charset)] @@ -296,7 +298,7 @@ func (ts *TestScenarios) CorruptionTest() CorruptionTestSet { // ConcurrentTest returns unique tokens for concurrent testing func (ts *TestScenarios) ConcurrentTest(count int) []TokenSet { sets := make([]TokenSet, count) - for i := range count { + for i := 0; i < count; i++ { sets[i] = TokenSet{ AccessToken: ts.tokens.CreateUniqueValidJWT(fmt.Sprintf("concurrent_%d", i)), IDToken: ts.tokens.CreateUniqueValidJWT(fmt.Sprintf("id_%d", i)), @@ -387,5 +389,12 @@ func AssertInvalidTokenRejection(t TestingInterface, session *SessionData, token // TestingInterface provides the minimal interface needed for testing type TestingInterface interface { - Errorf(format string, args ...any) + Errorf(format string, args ...interface{}) +} + +func max(a, b int) int { + if a > b { + return a + } + return b } diff --git a/token_corruption_test.go b/token_corruption_test.go index d0b3a47..1becd00 100644 --- a/token_corruption_test.go +++ b/token_corruption_test.go @@ -7,7 +7,6 @@ import ( "encoding/base64" "fmt" "net/http/httptest" - "slices" "strings" "sync" "testing" @@ -251,8 +250,8 @@ func TestChunkReassemblyEdgeCases(t *testing.T) { corruption: func(chunks map[int]*sessions.Session) { // This test simulates having too many chunks (>50 limit) // We'll create a scenario by adding many fake chunks - for i := range 60 { - fakeSession := &sessions.Session{Values: make(map[any]any)} + for i := 0; i < 60; i++ { + fakeSession := &sessions.Session{Values: make(map[interface{}]interface{})} fakeSession.Values["token_chunk"] = "fake_chunk_data" chunks[i] = fakeSession } @@ -321,12 +320,12 @@ func TestRaceConditionProtection(t *testing.T) { var wg sync.WaitGroup errChan := make(chan error, numGoroutines*numOperations) - for i := range numGoroutines { + for i := 0; i < numGoroutines; i++ { wg.Add(1) go func(goroutineID int) { defer wg.Done() - for j := range numOperations { + for j := 0; j < numOperations; j++ { tokenIndex := (goroutineID + j) % len(tokens) expectedToken := tokens[tokenIndex] @@ -345,7 +344,13 @@ func TestRaceConditionProtection(t *testing.T) { // The retrieved token should be one of the valid tokens we set // (due to concurrent access, it might not be the exact one we just set) - isValidToken := slices.Contains(tokens, retrieved) + isValidToken := false + for _, validToken := range tokens { + if retrieved == validToken { + isValidToken = true + break + } + } if retrieved != "" && !isValidToken { errChan <- fmt.Errorf("goroutine %d, op %d: retrieved unknown token: %q", diff --git a/token_handling_test.go b/token_handling_test.go index f2a30f2..f8f0711 100644 --- a/token_handling_test.go +++ b/token_handling_test.go @@ -19,7 +19,7 @@ import ( func TestTokenTypeDistinction(t *testing.T) { // Define test data where AccessToken and IdToken are deliberately different type templateData struct { - Claims map[string]any + Claims map[string]interface{} AccessToken string IdToken string RefreshToken string @@ -29,7 +29,7 @@ func TestTokenTypeDistinction(t *testing.T) { AccessToken: "test-access-token-abc123", IdToken: "test-id-token-xyz789", RefreshToken: "test-refresh-token", - Claims: map[string]any{ + Claims: map[string]interface{}{ "sub": "test-subject", "email": "user@example.com", }, @@ -91,7 +91,7 @@ func TestTokenTypeIntegration(t *testing.T) { ts.Setup() // Create different tokens for ID and access tokens - idToken, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]any{ + idToken, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]interface{}{ "iss": "https://test-issuer.com", "aud": "test-client-id", "exp": float64(3000000000), @@ -107,7 +107,7 @@ func TestTokenTypeIntegration(t *testing.T) { t.Fatalf("Failed to create test ID JWT: %v", err) } - accessToken, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]any{ + accessToken, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]interface{}{ "iss": "https://test-issuer.com", "aud": "test-client-id", "exp": float64(3000000000), @@ -518,11 +518,11 @@ func TestConcurrentTokenOperationsWithCorruption(t *testing.T) { errorChan := make(chan error, numGoroutines*numOperations) // Start concurrent operations - for i := range numGoroutines { + for i := 0; i < numGoroutines; i++ { go func(goroutineID int) { defer func() { done <- true }() - for j := range numOperations { + for j := 0; j < numOperations; j++ { // Create a unique valid token for each operation token := fmt.Sprintf("eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwib3AiOiIxMjMifQ.sig_%d_%d", goroutineID, j) @@ -563,7 +563,7 @@ func TestConcurrentTokenOperationsWithCorruption(t *testing.T) { } // Wait for all goroutines to complete - for range numGoroutines { + for i := 0; i < numGoroutines; i++ { <-done } close(errorChan) @@ -645,10 +645,13 @@ func createLargeValidJWT(targetSize int) string { // Calculate required payload size usedSize := len(header) + len(signature) + 2 // account for dots - payloadSize := max(targetSize-usedSize, 50) + payloadSize := targetSize - usedSize + if payloadSize < 50 { + payloadSize = 50 + } // Create a payload with realistic JWT claims - claims := map[string]any{ + claims := map[string]interface{}{ "sub": "user123", "iss": "https://example.com", "aud": "client123",