Well.. that escalated quickly.

Completely forgot that Traefik uses outdated Yaegi and requires compatibility with 1.20 ( pre-generic Go code ).
This commit is contained in:
2025-06-20 19:23:50 +01:00
parent 70443f0855
commit 667b4213fe
24 changed files with 439 additions and 374 deletions
+11 -11
View File
@@ -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("[email protected]")
// 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"}
+3 -3
View File
@@ -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()
+1 -1
View File
@@ -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)
}
+24 -23
View File
@@ -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()
}
+11 -6
View File
@@ -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")
}
+11 -6
View File
@@ -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": "[email protected]",
"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,
+5 -5
View File
@@ -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
}
+1 -1
View File
@@ -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
+2 -2
View File
@@ -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",
+4 -4
View File
@@ -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 {
+23 -14
View File
@@ -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,
+76 -76
View File
@@ -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("[email protected]")
// 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": "[email protected]",
"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("[email protected]")
// 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": "[email protected]", "jti": generateRandomString(16),
@@ -561,7 +561,7 @@ func TestServeHTTP(t *testing.T) {
session.SetAuthenticated(true)
session.SetEmail("[email protected]")
// 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": "[email protected]",
"jti": generateRandomString(16), // Unique JTI
@@ -579,7 +579,7 @@ func TestServeHTTP(t *testing.T) {
session.SetAuthenticated(true) // Set flag initially
session.SetEmail("[email protected]")
// 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": "[email protected]", "jti": generateRandomString(16),
@@ -607,7 +607,7 @@ func TestServeHTTP(t *testing.T) {
session.SetAuthenticated(true) // Set flag initially
session.SetEmail("[email protected]")
// 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": "[email protected]", "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": "[email protected]", "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": "[email protected]", "jti": generateRandomString(16),
})
@@ -693,7 +693,7 @@ func TestServeHTTP(t *testing.T) {
session.SetAuthenticated(true)
session.SetEmail("[email protected]") // 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": "[email protected]", // Match email
"jti": generateRandomString(16), // Unique JTI
@@ -715,7 +715,7 @@ func TestServeHTTP(t *testing.T) {
session.SetAuthenticated(true)
session.SetEmail("[email protected]") // 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": "[email protected]", // 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": "[email protected]",
"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": "[email protected]",
"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": "[email protected]",
"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": "[email protected]",
// 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": "[email protected]",
"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": "[email protected]",
}, 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": "[email protected]",
"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": "[email protected]",
"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": "[email protected]", "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": "[email protected]", "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,
+21 -21
View File
@@ -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 {
+33 -22
View File
@@ -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()),
+19 -19
View File
@@ -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,
}
}
+18 -7
View File
@@ -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
+11 -11
View File
@@ -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)
+5 -5
View File
@@ -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
}
}
+6 -6
View File
@@ -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...)
}
+75 -75
View File
@@ -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": "[email protected]",
},
},
@@ -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": "[email protected]",
@@ -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: "<no value>",
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": "[email protected]",
},
},
@@ -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": "[email protected]",
"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": "[email protected]",
"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": "[email protected]",
"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": "[email protected]",
"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": "[email protected]",
"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": "[email protected]",
"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": "[email protected]",
"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": "[email protected]",
"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": "[email protected]",
"email_verified": true,
},
+29 -23
View File
@@ -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": "[email protected]",
},
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": "[email protected]",
"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": "[email protected]",
// 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": "[email protected]",
"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": "[email protected]",
},
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": "[email protected]",
"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"] = "[email protected]" // 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))
+27 -18
View File
@@ -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
}
+11 -6
View File
@@ -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",
+12 -9
View File
@@ -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": "[email protected]",
},
@@ -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",