mirror of
https://github.com/lukaszraczylo/traefikoidc.git
synced 2026-10-14 04:07:46 +00:00
Modernize run
This commit is contained in:
+11
-11
@@ -17,7 +17,7 @@ type mockTraefikOidc struct {
|
|||||||
// Override VerifyToken to avoid JWKS lookup in tests
|
// Override VerifyToken to avoid JWKS lookup in tests
|
||||||
func (m *mockTraefikOidc) VerifyToken(token string) error {
|
func (m *mockTraefikOidc) VerifyToken(token string) error {
|
||||||
// Cache test claims to avoid "claims not found" errors
|
// Cache test claims to avoid "claims not found" errors
|
||||||
testClaims := map[string]interface{}{
|
testClaims := map[string]any{
|
||||||
"exp": float64(time.Now().Add(1 * time.Hour).Unix()),
|
"exp": float64(time.Now().Add(1 * time.Hour).Unix()),
|
||||||
"iat": float64(time.Now().Unix()),
|
"iat": float64(time.Now().Unix()),
|
||||||
"sub": "test-user",
|
"sub": "test-user",
|
||||||
@@ -30,7 +30,7 @@ func (m *mockTraefikOidc) VerifyToken(token string) error {
|
|||||||
// Override VerifyJWTSignatureAndClaims to avoid JWKS lookup in tests
|
// Override VerifyJWTSignatureAndClaims to avoid JWKS lookup in tests
|
||||||
func (m *mockTraefikOidc) VerifyJWTSignatureAndClaims(jwt *JWT, token string) error {
|
func (m *mockTraefikOidc) VerifyJWTSignatureAndClaims(jwt *JWT, token string) error {
|
||||||
// Cache test claims to avoid "claims not found" errors
|
// Cache test claims to avoid "claims not found" errors
|
||||||
testClaims := map[string]interface{}{
|
testClaims := map[string]any{
|
||||||
"exp": float64(time.Now().Add(1 * time.Hour).Unix()),
|
"exp": float64(time.Now().Add(1 * time.Hour).Unix()),
|
||||||
"iat": float64(time.Now().Unix()),
|
"iat": float64(time.Now().Unix()),
|
||||||
"sub": "test-user",
|
"sub": "test-user",
|
||||||
@@ -80,7 +80,7 @@ func TestAzureOIDCRegression(t *testing.T) {
|
|||||||
// For test tokens, always return success and cache claims
|
// For test tokens, always return success and cache claims
|
||||||
if strings.HasPrefix(token, "eyJhbGciOiJSUzI1NiIsImtpZCI6InRlc3Qta2V5LWlkIiwidHlwIjoiSldUIn0") {
|
if strings.HasPrefix(token, "eyJhbGciOiJSUzI1NiIsImtpZCI6InRlc3Qta2V5LWlkIiwidHlwIjoiSldUIn0") {
|
||||||
// Cache test claims for JWT tokens
|
// Cache test claims for JWT tokens
|
||||||
testClaims := map[string]interface{}{
|
testClaims := map[string]any{
|
||||||
"exp": float64(time.Now().Add(1 * time.Hour).Unix()),
|
"exp": float64(time.Now().Add(1 * time.Hour).Unix()),
|
||||||
"iat": float64(time.Now().Unix()),
|
"iat": float64(time.Now().Unix()),
|
||||||
"sub": "test-user",
|
"sub": "test-user",
|
||||||
@@ -94,7 +94,7 @@ func TestAzureOIDCRegression(t *testing.T) {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
// For JWT tokens, cache basic claims to avoid cache lookup issues
|
// For JWT tokens, cache basic claims to avoid cache lookup issues
|
||||||
testClaims := map[string]interface{}{
|
testClaims := map[string]any{
|
||||||
"exp": float64(time.Now().Add(1 * time.Hour).Unix()),
|
"exp": float64(time.Now().Add(1 * time.Hour).Unix()),
|
||||||
"iat": float64(time.Now().Unix()),
|
"iat": float64(time.Now().Unix()),
|
||||||
"sub": "test-user",
|
"sub": "test-user",
|
||||||
@@ -109,7 +109,7 @@ func TestAzureOIDCRegression(t *testing.T) {
|
|||||||
tOidc.jwtVerifier = &mockJWTVerifier{
|
tOidc.jwtVerifier = &mockJWTVerifier{
|
||||||
verifyFunc: func(jwt *JWT, token string) error {
|
verifyFunc: func(jwt *JWT, token string) error {
|
||||||
// Also cache claims here to ensure they're available
|
// Also cache claims here to ensure they're available
|
||||||
testClaims := map[string]interface{}{
|
testClaims := map[string]any{
|
||||||
"exp": float64(time.Now().Add(1 * time.Hour).Unix()),
|
"exp": float64(time.Now().Add(1 * time.Hour).Unix()),
|
||||||
"iat": float64(time.Now().Unix()),
|
"iat": float64(time.Now().Unix()),
|
||||||
"sub": "test-user",
|
"sub": "test-user",
|
||||||
@@ -163,7 +163,7 @@ func TestAzureOIDCRegression(t *testing.T) {
|
|||||||
session.SetEmail("[email protected]")
|
session.SetEmail("[email protected]")
|
||||||
|
|
||||||
// Create a valid JWT access token for testing
|
// Create a valid JWT access token for testing
|
||||||
accessTokenClaims := map[string]interface{}{
|
accessTokenClaims := map[string]any{
|
||||||
"iss": "https://login.microsoftonline.com/tenant-id/v2.0",
|
"iss": "https://login.microsoftonline.com/tenant-id/v2.0",
|
||||||
"aud": "test-client-id",
|
"aud": "test-client-id",
|
||||||
"exp": time.Now().Add(1 * time.Hour).Unix(),
|
"exp": time.Now().Add(1 * time.Hour).Unix(),
|
||||||
@@ -175,7 +175,7 @@ func TestAzureOIDCRegression(t *testing.T) {
|
|||||||
session.SetAccessToken(accessToken)
|
session.SetAccessToken(accessToken)
|
||||||
|
|
||||||
// Create an invalid/expired ID token
|
// Create an invalid/expired ID token
|
||||||
idTokenClaims := map[string]interface{}{
|
idTokenClaims := map[string]any{
|
||||||
"iss": "https://login.microsoftonline.com/tenant-id/v2.0",
|
"iss": "https://login.microsoftonline.com/tenant-id/v2.0",
|
||||||
"aud": "test-client-id",
|
"aud": "test-client-id",
|
||||||
"exp": time.Now().Add(-1 * time.Hour).Unix(), // Expired
|
"exp": time.Now().Add(-1 * time.Hour).Unix(), // Expired
|
||||||
@@ -192,7 +192,7 @@ func TestAzureOIDCRegression(t *testing.T) {
|
|||||||
verifyFunc: func(token string) error {
|
verifyFunc: func(token string) error {
|
||||||
if token == accessToken {
|
if token == accessToken {
|
||||||
// Access token validation succeeds - cache claims
|
// Access token validation succeeds - cache claims
|
||||||
testClaims := map[string]interface{}{
|
testClaims := map[string]any{
|
||||||
"exp": float64(time.Now().Add(1 * time.Hour).Unix()),
|
"exp": float64(time.Now().Add(1 * time.Hour).Unix()),
|
||||||
"iat": float64(time.Now().Unix()),
|
"iat": float64(time.Now().Unix()),
|
||||||
"sub": "test-user",
|
"sub": "test-user",
|
||||||
@@ -240,7 +240,7 @@ func TestAzureOIDCRegression(t *testing.T) {
|
|||||||
session.SetAccessToken(ValidAccessToken)
|
session.SetAccessToken(ValidAccessToken)
|
||||||
|
|
||||||
// Create a valid ID token for claims extraction
|
// Create a valid ID token for claims extraction
|
||||||
idTokenClaims := map[string]interface{}{
|
idTokenClaims := map[string]any{
|
||||||
"iss": "https://login.microsoftonline.com/tenant-id/v2.0",
|
"iss": "https://login.microsoftonline.com/tenant-id/v2.0",
|
||||||
"aud": "test-client-id",
|
"aud": "test-client-id",
|
||||||
"exp": time.Now().Add(1 * time.Hour).Unix(),
|
"exp": time.Now().Add(1 * time.Hour).Unix(),
|
||||||
@@ -257,7 +257,7 @@ func TestAzureOIDCRegression(t *testing.T) {
|
|||||||
verifyFunc: func(token string) error {
|
verifyFunc: func(token string) error {
|
||||||
if token == idToken {
|
if token == idToken {
|
||||||
// ID token is valid - cache claims
|
// ID token is valid - cache claims
|
||||||
testClaims := map[string]interface{}{
|
testClaims := map[string]any{
|
||||||
"exp": float64(time.Now().Add(1 * time.Hour).Unix()),
|
"exp": float64(time.Now().Add(1 * time.Hour).Unix()),
|
||||||
"iat": float64(time.Now().Unix()),
|
"iat": float64(time.Now().Unix()),
|
||||||
"sub": "test-user",
|
"sub": "test-user",
|
||||||
@@ -336,7 +336,7 @@ func TestAzureOIDCRegression(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// createMockJWT creates a basic JWT token for testing purposes
|
// createMockJWT creates a basic JWT token for testing purposes
|
||||||
func createMockJWT(claims map[string]interface{}) (string, error) {
|
func createMockJWT(claims map[string]any) (string, error) {
|
||||||
// Simple mock JWT - in real tests you'd use a proper JWT library
|
// 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
|
// For this test, we'll create a basic three-part token structure
|
||||||
header := "eyJhbGciOiJSUzI1NiIsImtpZCI6InRlc3Qta2V5LWlkIiwidHlwIjoiSldUIn0" // {"alg":"RS256","kid":"test-key-id","typ":"JWT"}
|
header := "eyJhbGciOiJSUzI1NiIsImtpZCI6InRlc3Qta2V5LWlkIiwidHlwIjoiSldUIn0" // {"alg":"RS256","kid":"test-key-id","typ":"JWT"}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
// CacheItem represents an item stored in the cache with its associated metadata.
|
// CacheItem represents an item stored in the cache with its associated metadata.
|
||||||
type CacheItem struct {
|
type CacheItem struct {
|
||||||
// Value is the cached data of any type.
|
// Value is the cached data of any type.
|
||||||
Value interface{}
|
Value any
|
||||||
|
|
||||||
// ExpiresAt is the timestamp when this item should be considered expired.
|
// ExpiresAt is the timestamp when this item should be considered expired.
|
||||||
ExpiresAt time.Time
|
ExpiresAt time.Time
|
||||||
@@ -68,7 +68,7 @@ func NewCache() *Cache {
|
|||||||
// If the key does not exist and the cache is full, the least recently used item is evicted
|
// If the key does not exist and the cache is full, the least recently used item is evicted
|
||||||
// before adding the new item.
|
// before adding the new item.
|
||||||
// The expiration duration is relative to the time Set is called.
|
// The expiration duration is relative to the time Set is called.
|
||||||
func (c *Cache) Set(key string, value interface{}, expiration time.Duration) {
|
func (c *Cache) Set(key string, value any, expiration time.Duration) {
|
||||||
c.mutex.Lock()
|
c.mutex.Lock()
|
||||||
defer c.mutex.Unlock()
|
defer c.mutex.Unlock()
|
||||||
|
|
||||||
@@ -106,7 +106,7 @@ func (c *Cache) Set(key string, value interface{}, expiration time.Duration) {
|
|||||||
// Accessing an item moves it to the most recently used position in the LRU list.
|
// 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
|
// If the item does not exist or has expired, nil and false are returned, and the
|
||||||
// expired item is removed from the cache.
|
// expired item is removed from the cache.
|
||||||
func (c *Cache) Get(key string) (interface{}, bool) {
|
func (c *Cache) Get(key string) (any, bool) {
|
||||||
c.mutex.Lock()
|
c.mutex.Lock()
|
||||||
defer c.mutex.Unlock()
|
defer c.mutex.Unlock()
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -49,7 +49,7 @@ func TestCache_SetMaxSize(t *testing.T) {
|
|||||||
newMaxSize := 3
|
newMaxSize := 3
|
||||||
|
|
||||||
// Add more items than the new max size
|
// Add more items than the new max size
|
||||||
for i := 0; i < originalMaxSize; i++ {
|
for i := range originalMaxSize {
|
||||||
key := "key" + string(rune('A'+i))
|
key := "key" + string(rune('A'+i))
|
||||||
c.Set(key, i, 1*time.Hour)
|
c.Set(key, i, 1*time.Hour)
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-13
@@ -3,6 +3,7 @@ package traefikoidc
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"maps"
|
||||||
"math"
|
"math"
|
||||||
"math/rand/v2"
|
"math/rand/v2"
|
||||||
"net"
|
"net"
|
||||||
@@ -175,11 +176,11 @@ func (cb *CircuitBreaker) GetState() CircuitBreakerState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetMetrics returns circuit breaker metrics
|
// GetMetrics returns circuit breaker metrics
|
||||||
func (cb *CircuitBreaker) GetMetrics() map[string]interface{} {
|
func (cb *CircuitBreaker) GetMetrics() map[string]any {
|
||||||
cb.mutex.RLock()
|
cb.mutex.RLock()
|
||||||
defer cb.mutex.RUnlock()
|
defer cb.mutex.RUnlock()
|
||||||
|
|
||||||
return map[string]interface{}{
|
return map[string]any{
|
||||||
"state": cb.state,
|
"state": cb.state,
|
||||||
"failures": cb.failures,
|
"failures": cb.failures,
|
||||||
"total_requests": atomic.LoadInt64(&cb.totalRequests),
|
"total_requests": atomic.LoadInt64(&cb.totalRequests),
|
||||||
@@ -355,7 +356,7 @@ func (e *HTTPError) Error() string {
|
|||||||
// GracefulDegradation implements graceful degradation patterns
|
// GracefulDegradation implements graceful degradation patterns
|
||||||
type GracefulDegradation struct {
|
type GracefulDegradation struct {
|
||||||
// Fallback functions for different operations
|
// Fallback functions for different operations
|
||||||
fallbacks map[string]func() (interface{}, error)
|
fallbacks map[string]func() (any, error)
|
||||||
|
|
||||||
// Health checks for dependencies
|
// Health checks for dependencies
|
||||||
healthChecks map[string]func() bool
|
healthChecks map[string]func() bool
|
||||||
@@ -389,7 +390,7 @@ func DefaultGracefulDegradationConfig() GracefulDegradationConfig {
|
|||||||
// NewGracefulDegradation creates a new graceful degradation manager
|
// NewGracefulDegradation creates a new graceful degradation manager
|
||||||
func NewGracefulDegradation(config GracefulDegradationConfig, logger *Logger) *GracefulDegradation {
|
func NewGracefulDegradation(config GracefulDegradationConfig, logger *Logger) *GracefulDegradation {
|
||||||
gd := &GracefulDegradation{
|
gd := &GracefulDegradation{
|
||||||
fallbacks: make(map[string]func() (interface{}, error)),
|
fallbacks: make(map[string]func() (any, error)),
|
||||||
healthChecks: make(map[string]func() bool),
|
healthChecks: make(map[string]func() bool),
|
||||||
degradedServices: make(map[string]time.Time),
|
degradedServices: make(map[string]time.Time),
|
||||||
config: config,
|
config: config,
|
||||||
@@ -403,7 +404,7 @@ func NewGracefulDegradation(config GracefulDegradationConfig, logger *Logger) *G
|
|||||||
}
|
}
|
||||||
|
|
||||||
// RegisterFallback registers a fallback function for a service
|
// RegisterFallback registers a fallback function for a service
|
||||||
func (gd *GracefulDegradation) RegisterFallback(serviceName string, fallback func() (interface{}, error)) {
|
func (gd *GracefulDegradation) RegisterFallback(serviceName string, fallback func() (any, error)) {
|
||||||
gd.mutex.Lock()
|
gd.mutex.Lock()
|
||||||
defer gd.mutex.Unlock()
|
defer gd.mutex.Unlock()
|
||||||
gd.fallbacks[serviceName] = fallback
|
gd.fallbacks[serviceName] = fallback
|
||||||
@@ -417,7 +418,7 @@ func (gd *GracefulDegradation) RegisterHealthCheck(serviceName string, healthChe
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ExecuteWithFallback executes a function with fallback support
|
// ExecuteWithFallback executes a function with fallback support
|
||||||
func (gd *GracefulDegradation) ExecuteWithFallback(serviceName string, primary func() (interface{}, error)) (interface{}, error) {
|
func (gd *GracefulDegradation) ExecuteWithFallback(serviceName string, primary func() (any, error)) (any, error) {
|
||||||
// Check if service is degraded
|
// Check if service is degraded
|
||||||
if gd.isServiceDegraded(serviceName) {
|
if gd.isServiceDegraded(serviceName) {
|
||||||
return gd.executeFallback(serviceName)
|
return gd.executeFallback(serviceName)
|
||||||
@@ -472,7 +473,7 @@ func (gd *GracefulDegradation) markServiceDegraded(serviceName string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// executeFallback executes the fallback function for a service
|
// executeFallback executes the fallback function for a service
|
||||||
func (gd *GracefulDegradation) executeFallback(serviceName string) (interface{}, error) {
|
func (gd *GracefulDegradation) executeFallback(serviceName string) (any, error) {
|
||||||
gd.mutex.RLock()
|
gd.mutex.RLock()
|
||||||
fallback, exists := gd.fallbacks[serviceName]
|
fallback, exists := gd.fallbacks[serviceName]
|
||||||
gd.mutex.RUnlock()
|
gd.mutex.RUnlock()
|
||||||
@@ -499,9 +500,7 @@ func (gd *GracefulDegradation) startHealthCheckRoutine() {
|
|||||||
func (gd *GracefulDegradation) performHealthChecks() {
|
func (gd *GracefulDegradation) performHealthChecks() {
|
||||||
gd.mutex.RLock()
|
gd.mutex.RLock()
|
||||||
healthChecks := make(map[string]func() bool)
|
healthChecks := make(map[string]func() bool)
|
||||||
for name, check := range gd.healthChecks {
|
maps.Copy(healthChecks, gd.healthChecks)
|
||||||
healthChecks[name] = check
|
|
||||||
}
|
|
||||||
gd.mutex.RUnlock()
|
gd.mutex.RUnlock()
|
||||||
|
|
||||||
for serviceName, healthCheck := range healthChecks {
|
for serviceName, healthCheck := range healthChecks {
|
||||||
@@ -576,14 +575,14 @@ func (erm *ErrorRecoveryManager) ExecuteWithRecovery(ctx context.Context, servic
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetRecoveryMetrics returns metrics for all recovery mechanisms
|
// GetRecoveryMetrics returns metrics for all recovery mechanisms
|
||||||
func (erm *ErrorRecoveryManager) GetRecoveryMetrics() map[string]interface{} {
|
func (erm *ErrorRecoveryManager) GetRecoveryMetrics() map[string]any {
|
||||||
erm.mutex.RLock()
|
erm.mutex.RLock()
|
||||||
defer erm.mutex.RUnlock()
|
defer erm.mutex.RUnlock()
|
||||||
|
|
||||||
metrics := make(map[string]interface{})
|
metrics := make(map[string]any)
|
||||||
|
|
||||||
// Circuit breaker metrics
|
// Circuit breaker metrics
|
||||||
cbMetrics := make(map[string]interface{})
|
cbMetrics := make(map[string]any)
|
||||||
for name, cb := range erm.circuitBreakers {
|
for name, cb := range erm.circuitBreakers {
|
||||||
cbMetrics[name] = cb.GetMetrics()
|
cbMetrics[name] = cb.GetMetrics()
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-11
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"net"
|
"net"
|
||||||
|
"slices"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@@ -207,7 +208,7 @@ func TestGracefulDegradation(t *testing.T) {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
t.Run("Register fallback and health check", func(t *testing.T) {
|
t.Run("Register fallback and health check", func(t *testing.T) {
|
||||||
gd.RegisterFallback("test-service", func() (interface{}, error) {
|
gd.RegisterFallback("test-service", func() (any, error) {
|
||||||
return "fallback-result", nil
|
return "fallback-result", nil
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -222,12 +223,12 @@ func TestGracefulDegradation(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Execute with fallback on failure", func(t *testing.T) {
|
t.Run("Execute with fallback on failure", func(t *testing.T) {
|
||||||
gd.RegisterFallback("failing-service", func() (interface{}, error) {
|
gd.RegisterFallback("failing-service", func() (any, error) {
|
||||||
return "fallback-result", nil
|
return "fallback-result", nil
|
||||||
})
|
})
|
||||||
|
|
||||||
// First call should fail and mark service as degraded
|
// First call should fail and mark service as degraded
|
||||||
result, err := gd.ExecuteWithFallback("failing-service", func() (interface{}, error) {
|
result, err := gd.ExecuteWithFallback("failing-service", func() (any, error) {
|
||||||
return nil, errors.New("service failure")
|
return nil, errors.New("service failure")
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -244,7 +245,7 @@ func TestGracefulDegradation(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("No fallback available", func(t *testing.T) {
|
t.Run("No fallback available", func(t *testing.T) {
|
||||||
_, err := gd.ExecuteWithFallback("no-fallback-service", func() (interface{}, error) {
|
_, err := gd.ExecuteWithFallback("no-fallback-service", func() (any, error) {
|
||||||
return nil, errors.New("service failure")
|
return nil, errors.New("service failure")
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -255,13 +256,7 @@ func TestGracefulDegradation(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("Get degraded services", func(t *testing.T) {
|
t.Run("Get degraded services", func(t *testing.T) {
|
||||||
degraded := gd.GetDegradedServices()
|
degraded := gd.GetDegradedServices()
|
||||||
found := false
|
found := slices.Contains(degraded, "failing-service")
|
||||||
for _, service := range degraded {
|
|
||||||
if service == "failing-service" {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !found {
|
if !found {
|
||||||
t.Error("Expected failing-service to be in degraded list")
|
t.Error("Expected failing-service to be in degraded list")
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-11
@@ -9,6 +9,7 @@ import (
|
|||||||
"math/big"
|
"math/big"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@@ -131,9 +132,9 @@ func TestGoogleOIDCRefreshTokenHandling(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
tOidc.extractClaimsFunc = func(token string) (map[string]interface{}, error) {
|
tOidc.extractClaimsFunc = func(token string) (map[string]any, error) {
|
||||||
// Return mock claims
|
// Return mock claims
|
||||||
return map[string]interface{}{
|
return map[string]any{
|
||||||
"email": "[email protected]",
|
"email": "[email protected]",
|
||||||
"exp": float64(time.Now().Add(1 * time.Hour).Unix()),
|
"exp": float64(time.Now().Add(1 * time.Hour).Unix()),
|
||||||
}, nil
|
}, nil
|
||||||
@@ -303,13 +304,7 @@ func TestGoogleOIDCRefreshTokenHandling(t *testing.T) {
|
|||||||
scopeList := strings.Split(scope, " ")
|
scopeList := strings.Split(scope, " ")
|
||||||
expectedScopes := []string{"openid", "profile", "email"}
|
expectedScopes := []string{"openid", "profile", "email"}
|
||||||
for _, expectedScope := range expectedScopes {
|
for _, expectedScope := range expectedScopes {
|
||||||
found := false
|
found := slices.Contains(scopeList, expectedScope)
|
||||||
for _, actualScope := range scopeList {
|
|
||||||
if actualScope == expectedScope {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !found {
|
if !found {
|
||||||
t.Errorf("Expected scope '%s' not found in scope parameter: %s", expectedScope, scope)
|
t.Errorf("Expected scope '%s' not found in scope parameter: %s", expectedScope, scope)
|
||||||
}
|
}
|
||||||
@@ -385,7 +380,7 @@ func TestGoogleOIDCRefreshTokenHandling(t *testing.T) {
|
|||||||
nbf := now.Unix()
|
nbf := now.Unix()
|
||||||
|
|
||||||
// Create initial ID token
|
// Create initial ID token
|
||||||
initialIDToken, err := createTestJWT(rsaPrivateKey, "RS256", "test-key-id", map[string]interface{}{
|
initialIDToken, err := createTestJWT(rsaPrivateKey, "RS256", "test-key-id", map[string]any{
|
||||||
"iss": "https://accounts.google.com",
|
"iss": "https://accounts.google.com",
|
||||||
"aud": "test-client-id",
|
"aud": "test-client-id",
|
||||||
"exp": exp,
|
"exp": exp,
|
||||||
@@ -401,7 +396,7 @@ func TestGoogleOIDCRefreshTokenHandling(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create refresh ID token
|
// Create refresh ID token
|
||||||
refreshedIDToken, err := createTestJWT(rsaPrivateKey, "RS256", "test-key-id", map[string]interface{}{
|
refreshedIDToken, err := createTestJWT(rsaPrivateKey, "RS256", "test-key-id", map[string]any{
|
||||||
"iss": "https://accounts.google.com",
|
"iss": "https://accounts.google.com",
|
||||||
"aud": "test-client-id",
|
"aud": "test-client-id",
|
||||||
"exp": exp,
|
"exp": exp,
|
||||||
|
|||||||
+5
-5
@@ -196,7 +196,7 @@ func (t *TraefikOidc) getNewTokenWithRefreshToken(refreshToken string) (*TokenRe
|
|||||||
// Returns:
|
// Returns:
|
||||||
// - A map representing the JSON claims extracted from the token payload.
|
// - 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.
|
// - An error if the token format is invalid, decoding fails, or JSON unmarshaling fails.
|
||||||
func extractClaims(tokenString string) (map[string]interface{}, error) {
|
func extractClaims(tokenString string) (map[string]any, error) {
|
||||||
parts := strings.Split(tokenString, ".")
|
parts := strings.Split(tokenString, ".")
|
||||||
if len(parts) != 3 {
|
if len(parts) != 3 {
|
||||||
return nil, fmt.Errorf("invalid token format")
|
return nil, fmt.Errorf("invalid token format")
|
||||||
@@ -207,7 +207,7 @@ func extractClaims(tokenString string) (map[string]interface{}, error) {
|
|||||||
return nil, fmt.Errorf("failed to decode token payload: %w", err)
|
return nil, fmt.Errorf("failed to decode token payload: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var claims map[string]interface{}
|
var claims map[string]any
|
||||||
if err := json.Unmarshal(payload, &claims); err != nil {
|
if err := json.Unmarshal(payload, &claims); err != nil {
|
||||||
return nil, fmt.Errorf("failed to unmarshal claims: %w", err)
|
return nil, fmt.Errorf("failed to unmarshal claims: %w", err)
|
||||||
}
|
}
|
||||||
@@ -245,7 +245,7 @@ func NewTokenCache() *TokenCache {
|
|||||||
// - token: The raw token string (used as the key).
|
// - token: The raw token string (used as the key).
|
||||||
// - claims: The map of claims associated with the token.
|
// - claims: The map of claims associated with the token.
|
||||||
// - expiration: The duration for which the cache entry should be valid.
|
// - expiration: The duration for which the cache entry should be valid.
|
||||||
func (tc *TokenCache) Set(token string, claims map[string]interface{}, expiration time.Duration) {
|
func (tc *TokenCache) Set(token string, claims map[string]any, expiration time.Duration) {
|
||||||
token = "t-" + token
|
token = "t-" + token
|
||||||
tc.cache.Set(token, claims, expiration)
|
tc.cache.Set(token, claims, expiration)
|
||||||
}
|
}
|
||||||
@@ -259,13 +259,13 @@ func (tc *TokenCache) Set(token string, claims map[string]interface{}, expiratio
|
|||||||
// Returns:
|
// Returns:
|
||||||
// - The cached claims map if found and valid.
|
// - The cached claims map if found and valid.
|
||||||
// - A boolean indicating whether the token was found in the cache (true if found, false otherwise).
|
// - A boolean indicating whether the token was found in the cache (true if found, false otherwise).
|
||||||
func (tc *TokenCache) Get(token string) (map[string]interface{}, bool) {
|
func (tc *TokenCache) Get(token string) (map[string]any, bool) {
|
||||||
token = "t-" + token
|
token = "t-" + token
|
||||||
value, found := tc.cache.Get(token)
|
value, found := tc.cache.Get(token)
|
||||||
if !found {
|
if !found {
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
claims, ok := value.(map[string]interface{})
|
claims, ok := value.(map[string]any)
|
||||||
return claims, ok
|
return claims, ok
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -620,7 +620,7 @@ func (iv *InputValidator) SanitizeInput(input string, maxLength int) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ValidateBoundaryValues validates numeric boundary values
|
// ValidateBoundaryValues validates numeric boundary values
|
||||||
func (iv *InputValidator) ValidateBoundaryValues(value interface{}, min, max int64) ValidationResult {
|
func (iv *InputValidator) ValidateBoundaryValues(value any, min, max int64) ValidationResult {
|
||||||
result := ValidationResult{IsValid: true, Errors: []string{}, Warnings: []string{}}
|
result := ValidationResult{IsValid: true, Errors: []string{}, Warnings: []string{}}
|
||||||
|
|
||||||
var numValue int64
|
var numValue int64
|
||||||
|
|||||||
@@ -246,7 +246,7 @@ func TestValidateBoundaryValues(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
t.Run("Valid boundary values", func(t *testing.T) {
|
t.Run("Valid boundary values", func(t *testing.T) {
|
||||||
validValues := []interface{}{
|
validValues := []any{
|
||||||
int(50),
|
int(50),
|
||||||
int64(100),
|
int64(100),
|
||||||
float64(75.5),
|
float64(75.5),
|
||||||
@@ -261,7 +261,7 @@ func TestValidateBoundaryValues(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Invalid boundary values", func(t *testing.T) {
|
t.Run("Invalid boundary values", func(t *testing.T) {
|
||||||
invalidValues := []interface{}{
|
invalidValues := []any{
|
||||||
int(-1),
|
int(-1),
|
||||||
int64(2000),
|
int64(2000),
|
||||||
"not a number",
|
"not a number",
|
||||||
|
|||||||
@@ -87,8 +87,8 @@ var ClockSkewTolerance = ClockSkewToleranceFuture
|
|||||||
|
|
||||||
// JWT represents a JSON Web Token as defined in RFC 7519.
|
// JWT represents a JSON Web Token as defined in RFC 7519.
|
||||||
type JWT struct {
|
type JWT struct {
|
||||||
Header map[string]interface{}
|
Header map[string]any
|
||||||
Claims map[string]interface{}
|
Claims map[string]any
|
||||||
Signature []byte
|
Signature []byte
|
||||||
Token string
|
Token string
|
||||||
}
|
}
|
||||||
@@ -276,13 +276,13 @@ func (j *JWT) Verify(issuerURL, clientID string, skipReplayCheck ...bool) error
|
|||||||
// Returns:
|
// Returns:
|
||||||
// - nil if the expected audience is found.
|
// - nil if the expected audience is found.
|
||||||
// - An error if the claim type is invalid or the expected audience is not present.
|
// - An error if the claim type is invalid or the expected audience is not present.
|
||||||
func verifyAudience(tokenAudience interface{}, expectedAudience string) error {
|
func verifyAudience(tokenAudience any, expectedAudience string) error {
|
||||||
switch aud := tokenAudience.(type) {
|
switch aud := tokenAudience.(type) {
|
||||||
case string:
|
case string:
|
||||||
if aud != expectedAudience {
|
if aud != expectedAudience {
|
||||||
return fmt.Errorf("invalid audience")
|
return fmt.Errorf("invalid audience")
|
||||||
}
|
}
|
||||||
case []interface{}:
|
case []any:
|
||||||
found := false
|
found := false
|
||||||
for _, v := range aud {
|
for _, v := range aud {
|
||||||
if str, ok := v.(string); ok && str == expectedAudience {
|
if str, ok := v.(string); ok && str == expectedAudience {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"maps"
|
||||||
"math"
|
"math"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -13,6 +14,7 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"text/template"
|
"text/template"
|
||||||
@@ -225,7 +227,7 @@ type TraefikOidc struct {
|
|||||||
allowedRolesAndGroups map[string]struct{}
|
allowedRolesAndGroups map[string]struct{}
|
||||||
initiateAuthenticationFunc func(rw http.ResponseWriter, req *http.Request, session *SessionData, redirectURL string)
|
initiateAuthenticationFunc func(rw http.ResponseWriter, req *http.Request, session *SessionData, redirectURL string)
|
||||||
// exchangeCodeForTokenFunc func(code string, redirectURL string, codeVerifier string) (*TokenResponse, error) // Replaced by interface
|
// exchangeCodeForTokenFunc func(code string, redirectURL string, codeVerifier string) (*TokenResponse, error) // Replaced by interface
|
||||||
extractClaimsFunc func(tokenString string) (map[string]interface{}, error)
|
extractClaimsFunc func(tokenString string) (map[string]any, error)
|
||||||
initComplete chan struct{}
|
initComplete chan struct{}
|
||||||
endSessionURL string
|
endSessionURL string
|
||||||
postLogoutRedirectURI string
|
postLogoutRedirectURI string
|
||||||
@@ -439,7 +441,7 @@ func (t *TraefikOidc) VerifyToken(token string) error {
|
|||||||
// Parameters:
|
// Parameters:
|
||||||
// - token: The raw token string (used as the cache key).
|
// - token: The raw token string (used as the cache key).
|
||||||
// - claims: The map of claims extracted from the verified token.
|
// - claims: The map of claims extracted from the verified token.
|
||||||
func (t *TraefikOidc) cacheVerifiedToken(token string, claims map[string]interface{}) {
|
func (t *TraefikOidc) cacheVerifiedToken(token string, claims map[string]any) {
|
||||||
expClaim, ok := claims["exp"].(float64)
|
expClaim, ok := claims["exp"].(float64)
|
||||||
if !ok {
|
if !ok {
|
||||||
t.logger.Errorf("Failed to cache token: invalid 'exp' claim type")
|
t.logger.Errorf("Failed to cache token: invalid 'exp' claim type")
|
||||||
@@ -690,9 +692,7 @@ func New(ctx context.Context, next http.Handler, config *Config, name string) (h
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add default excluded URLs
|
// Add default excluded URLs
|
||||||
for k, v := range defaultExcludedURLs {
|
maps.Copy(t.excludedURLs, defaultExcludedURLs)
|
||||||
t.excludedURLs[k] = v
|
|
||||||
}
|
|
||||||
|
|
||||||
t.tokenVerifier = t
|
t.tokenVerifier = t
|
||||||
t.jwtVerifier = t
|
t.jwtVerifier = t
|
||||||
@@ -837,7 +837,7 @@ func discoverProviderMetadata(providerURL string, httpClient *http.Client, l *Lo
|
|||||||
start := time.Now()
|
start := time.Now()
|
||||||
|
|
||||||
var lastErr error
|
var lastErr error
|
||||||
for attempt := 0; attempt < maxRetries; attempt++ {
|
for attempt := range maxRetries {
|
||||||
if time.Since(start) > totalTimeout {
|
if time.Since(start) > totalTimeout {
|
||||||
l.Errorf("Timeout exceeded while fetching provider metadata")
|
l.Errorf("Timeout exceeded while fetching provider metadata")
|
||||||
return nil, fmt.Errorf("timeout exceeded while fetching provider metadata: %w", lastErr)
|
return nil, fmt.Errorf("timeout exceeded while fetching provider metadata: %w", lastErr)
|
||||||
@@ -1037,7 +1037,7 @@ func (t *TraefikOidc) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
|||||||
if idToken != "" {
|
if idToken != "" {
|
||||||
jwt, err := parseJWT(idToken)
|
jwt, err := parseJWT(idToken)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// jwt.Claims is already map[string]interface{}, no type assertion needed
|
// jwt.Claims is already map[string]any, no type assertion needed
|
||||||
claims := jwt.Claims
|
claims := jwt.Claims
|
||||||
// STABILITY FIX: Safe type assertion with proper error handling
|
// STABILITY FIX: Safe type assertion with proper error handling
|
||||||
if expClaim, ok := claims["exp"].(float64); ok {
|
if expClaim, ok := claims["exp"].(float64); ok {
|
||||||
@@ -1199,7 +1199,7 @@ func (t *TraefikOidc) processAuthorizedRequest(rw http.ResponseWriter, req *http
|
|||||||
AccessToken string
|
AccessToken string
|
||||||
IdToken string
|
IdToken string
|
||||||
RefreshToken string
|
RefreshToken string
|
||||||
Claims map[string]interface{}
|
Claims map[string]any
|
||||||
}{
|
}{
|
||||||
AccessToken: session.GetAccessToken(), // Provide AccessToken for templates if needed
|
AccessToken: session.GetAccessToken(), // Provide AccessToken for templates if needed
|
||||||
IdToken: session.GetIDToken(),
|
IdToken: session.GetIDToken(),
|
||||||
@@ -1378,7 +1378,7 @@ func (t *TraefikOidc) handleCallback(rw http.ResponseWriter, req *http.Request,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Verify ID token and claims
|
// Verify ID token and claims
|
||||||
if err := t.verifyToken(tokenResponse.IDToken); err != nil {
|
if err = t.verifyToken(tokenResponse.IDToken); err != nil {
|
||||||
t.logger.Errorf("Failed to verify id_token during callback: %v", err)
|
t.logger.Errorf("Failed to verify id_token during callback: %v", err)
|
||||||
t.sendErrorResponse(rw, req, "Authentication failed: Could not verify ID token", http.StatusInternalServerError)
|
t.sendErrorResponse(rw, req, "Authentication failed: Could not verify ID token", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
@@ -1674,11 +1674,9 @@ func (t *TraefikOidc) buildAuthURL(redirectURL, state, nonce, codeChallenge stri
|
|||||||
t.logger.Debug("Azure AD provider detected, added response_mode=query")
|
t.logger.Debug("Azure AD provider detected, added response_mode=query")
|
||||||
|
|
||||||
hasOfflineAccess := false
|
hasOfflineAccess := false
|
||||||
for _, scope := range scopes {
|
|
||||||
if scope == "offline_access" {
|
if slices.Contains(scopes, "offline_access") {
|
||||||
hasOfflineAccess = true
|
hasOfflineAccess = true
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !hasOfflineAccess {
|
if !hasOfflineAccess {
|
||||||
@@ -1687,13 +1685,7 @@ func (t *TraefikOidc) buildAuthURL(redirectURL, state, nonce, codeChallenge stri
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// For other providers, use the standard offline_access scope
|
// For other providers, use the standard offline_access scope
|
||||||
hasOfflineAccess := false
|
hasOfflineAccess := slices.Contains(scopes, "offline_access")
|
||||||
for _, scope := range scopes {
|
|
||||||
if scope == "offline_access" {
|
|
||||||
hasOfflineAccess = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !hasOfflineAccess {
|
if !hasOfflineAccess {
|
||||||
scopes = append(scopes, "offline_access")
|
scopes = append(scopes, "offline_access")
|
||||||
@@ -2031,7 +2023,7 @@ func (t *TraefikOidc) refreshToken(rw http.ResponseWriter, req *http.Request, se
|
|||||||
t.logger.Errorf("Refresh token appears to be expired or revoked: %v", err)
|
t.logger.Errorf("Refresh token appears to be expired or revoked: %v", err)
|
||||||
// Don't keep trying with an invalid refresh token
|
// Don't keep trying with an invalid refresh token
|
||||||
session.SetRefreshToken("")
|
session.SetRefreshToken("")
|
||||||
if err := session.Save(req, rw); err != nil {
|
if err = session.Save(req, rw); err != nil {
|
||||||
t.logger.Errorf("Failed to remove invalid refresh token from session: %v", err)
|
t.logger.Errorf("Failed to remove invalid refresh token from session: %v", err)
|
||||||
}
|
}
|
||||||
} else if strings.Contains(errMsg, "invalid_client") {
|
} else if strings.Contains(errMsg, "invalid_client") {
|
||||||
@@ -2050,7 +2042,7 @@ func (t *TraefikOidc) refreshToken(rw http.ResponseWriter, req *http.Request, se
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Verify the new ID token
|
// Verify the new ID token
|
||||||
if err := t.verifyToken(newToken.IDToken); err != nil {
|
if err = t.verifyToken(newToken.IDToken); err != nil {
|
||||||
truncatedToken := newToken.IDToken
|
truncatedToken := newToken.IDToken
|
||||||
if len(newToken.IDToken) > 10 {
|
if len(newToken.IDToken) > 10 {
|
||||||
truncatedToken = newToken.IDToken[:10]
|
truncatedToken = newToken.IDToken[:10]
|
||||||
@@ -2217,7 +2209,7 @@ func (t *TraefikOidc) extractGroupsAndRoles(idToken string) ([]string, []string,
|
|||||||
|
|
||||||
// Extract groups with type checking
|
// Extract groups with type checking
|
||||||
if groupsClaim, exists := claims["groups"]; exists {
|
if groupsClaim, exists := claims["groups"]; exists {
|
||||||
groupsSlice, ok := groupsClaim.([]interface{})
|
groupsSlice, ok := groupsClaim.([]any)
|
||||||
if !ok {
|
if !ok {
|
||||||
// Strictly expect an array
|
// Strictly expect an array
|
||||||
return nil, nil, fmt.Errorf("groups claim is not an array")
|
return nil, nil, fmt.Errorf("groups claim is not an array")
|
||||||
@@ -2235,7 +2227,7 @@ func (t *TraefikOidc) extractGroupsAndRoles(idToken string) ([]string, []string,
|
|||||||
|
|
||||||
// Extract roles with type checking
|
// Extract roles with type checking
|
||||||
if rolesClaim, exists := claims["roles"]; exists {
|
if rolesClaim, exists := claims["roles"]; exists {
|
||||||
rolesSlice, ok := rolesClaim.([]interface{})
|
rolesSlice, ok := rolesClaim.([]any)
|
||||||
if !ok {
|
if !ok {
|
||||||
// Strictly expect an array
|
// Strictly expect an array
|
||||||
return nil, nil, fmt.Errorf("roles claim is not an array")
|
return nil, nil, fmt.Errorf("roles claim is not an array")
|
||||||
@@ -2319,7 +2311,7 @@ func (t *TraefikOidc) sendErrorResponse(rw http.ResponseWriter, req *http.Reques
|
|||||||
rw.Header().Set("Content-Type", "application/json")
|
rw.Header().Set("Content-Type", "application/json")
|
||||||
rw.WriteHeader(code)
|
rw.WriteHeader(code)
|
||||||
// Use a simple error structure - ensure this matches the expected response format in tests
|
// Use a simple error structure - ensure this matches the expected response format in tests
|
||||||
json.NewEncoder(rw).Encode(map[string]interface{}{
|
json.NewEncoder(rw).Encode(map[string]any{
|
||||||
"error": http.StatusText(code), // Use standard text for the code
|
"error": http.StatusText(code), // Use standard text for the code
|
||||||
"error_description": message, // Provide specific detail here
|
"error_description": message, // Provide specific detail here
|
||||||
"status_code": code,
|
"status_code": code,
|
||||||
|
|||||||
+87
-87
@@ -74,7 +74,7 @@ func (ts *TestSuite) Setup() {
|
|||||||
iat := now.Add(-2 * time.Minute).Unix() // Account for clock skew
|
iat := now.Add(-2 * time.Minute).Unix() // Account for clock skew
|
||||||
nbf := 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]interface{}{
|
ts.token, err = createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]any{
|
||||||
"iss": "https://test-issuer.com",
|
"iss": "https://test-issuer.com",
|
||||||
"aud": "test-client-id",
|
"aud": "test-client-id",
|
||||||
"exp": exp,
|
"exp": exp,
|
||||||
@@ -205,8 +205,8 @@ func (m *MockTokenExchanger) RevokeTokenWithProvider(token, tokenType string) er
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Helper function to create a JWT token
|
// Helper function to create a JWT token
|
||||||
func createTestJWT(privateKey *rsa.PrivateKey, alg, kid string, claims map[string]interface{}) (string, error) {
|
func createTestJWT(privateKey *rsa.PrivateKey, alg, kid string, claims map[string]any) (string, error) {
|
||||||
header := map[string]interface{}{
|
header := map[string]any{
|
||||||
"alg": alg,
|
"alg": alg,
|
||||||
"kid": kid,
|
"kid": kid,
|
||||||
"typ": "JWT",
|
"typ": "JWT",
|
||||||
@@ -333,7 +333,7 @@ func TestVerifyToken(t *testing.T) {
|
|||||||
|
|
||||||
if tc.cacheToken {
|
if tc.cacheToken {
|
||||||
// Use more realistic claims for cached token
|
// Use more realistic claims for cached token
|
||||||
ts.tOidc.tokenCache.Set(tc.token, map[string]interface{}{
|
ts.tOidc.tokenCache.Set(tc.token, map[string]any{
|
||||||
"iss": "https://test-issuer.com",
|
"iss": "https://test-issuer.com",
|
||||||
"sub": "test-subject",
|
"sub": "test-subject",
|
||||||
"exp": float64(time.Now().Add(1 * time.Hour).Unix()),
|
"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
|
exp := time.Now().Add(-1 * time.Hour).Unix() // Expired 1 hour ago
|
||||||
iat := time.Now().Add(-2 * time.Hour).Unix()
|
iat := time.Now().Add(-2 * time.Hour).Unix()
|
||||||
nbf := 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]interface{}{
|
expiredToken, _ := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]any{
|
||||||
"iss": "https://test-issuer.com",
|
"iss": "https://test-issuer.com",
|
||||||
"aud": "test-client-id",
|
"aud": "test-client-id",
|
||||||
"exp": exp,
|
"exp": exp,
|
||||||
@@ -395,7 +395,7 @@ func TestServeHTTP(t *testing.T) {
|
|||||||
exp := time.Now().Add(1 * time.Hour).Unix() // Valid for 1 hour
|
exp := time.Now().Add(1 * time.Hour).Unix() // Valid for 1 hour
|
||||||
iat := time.Now().Unix()
|
iat := time.Now().Unix()
|
||||||
nbf := time.Now().Unix()
|
nbf := time.Now().Unix()
|
||||||
newToken, _ := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]interface{}{
|
newToken, _ := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]any{
|
||||||
"iss": "https://test-issuer.com",
|
"iss": "https://test-issuer.com",
|
||||||
"aud": "test-client-id",
|
"aud": "test-client-id",
|
||||||
"exp": exp,
|
"exp": exp,
|
||||||
@@ -412,7 +412,7 @@ func TestServeHTTP(t *testing.T) {
|
|||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
requestPath string
|
requestPath string
|
||||||
sessionValues map[interface{}]interface{}
|
sessionValues map[any]any
|
||||||
expectedStatus int
|
expectedStatus int
|
||||||
expectedBody string
|
expectedBody string
|
||||||
setupSession func(*SessionData)
|
setupSession func(*SessionData)
|
||||||
@@ -481,7 +481,7 @@ func TestServeHTTP(t *testing.T) {
|
|||||||
session.SetAuthenticated(true)
|
session.SetAuthenticated(true)
|
||||||
session.SetEmail("[email protected]")
|
session.SetEmail("[email protected]")
|
||||||
// Generate a fresh valid token for this test case to avoid replay issues
|
// Generate a fresh valid token for this test case to avoid replay issues
|
||||||
freshToken, _ := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]interface{}{
|
freshToken, _ := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]any{
|
||||||
"iss": "https://test-issuer.com", "aud": "test-client-id", "exp": time.Now().Add(1 * time.Hour).Unix(),
|
"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]",
|
"iat": time.Now().Unix(), "nbf": time.Now().Unix(), "sub": "test-subject", "email": "[email protected]",
|
||||||
"jti": generateRandomString(16), // Unique JTI
|
"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.SetAuthenticated(true) // Set flag initially, though isUserAuthenticated will override based on token
|
||||||
session.SetEmail("[email protected]")
|
session.SetEmail("[email protected]")
|
||||||
// Create an expired token for this test
|
// Create an expired token for this test
|
||||||
expiredToken, _ := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]interface{}{
|
expiredToken, _ := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]any{
|
||||||
"iss": "https://test-issuer.com", "aud": "test-client-id", "exp": time.Now().Add(-1 * time.Hour).Unix(),
|
"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(),
|
"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),
|
"sub": "test-subject", "email": "[email protected]", "jti": generateRandomString(16),
|
||||||
@@ -561,7 +561,7 @@ func TestServeHTTP(t *testing.T) {
|
|||||||
session.SetAuthenticated(true)
|
session.SetAuthenticated(true)
|
||||||
session.SetEmail("[email protected]")
|
session.SetEmail("[email protected]")
|
||||||
// Generate a fresh valid token for this test case
|
// Generate a fresh valid token for this test case
|
||||||
freshToken, _ := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]interface{}{
|
freshToken, _ := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]any{
|
||||||
"iss": "https://test-issuer.com", "aud": "test-client-id", "exp": time.Now().Add(1 * time.Hour).Unix(),
|
"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]",
|
"iat": time.Now().Unix(), "nbf": time.Now().Unix(), "sub": "test-subject", "email": "[email protected]",
|
||||||
"jti": generateRandomString(16), // Unique JTI
|
"jti": generateRandomString(16), // Unique JTI
|
||||||
@@ -579,7 +579,7 @@ func TestServeHTTP(t *testing.T) {
|
|||||||
session.SetAuthenticated(true) // Set flag initially
|
session.SetAuthenticated(true) // Set flag initially
|
||||||
session.SetEmail("[email protected]")
|
session.SetEmail("[email protected]")
|
||||||
// Create an expired token for this test
|
// Create an expired token for this test
|
||||||
expiredToken, _ := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]interface{}{
|
expiredToken, _ := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]any{
|
||||||
"iss": "https://test-issuer.com", "aud": "test-client-id", "exp": time.Now().Add(-1 * time.Hour).Unix(),
|
"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(),
|
"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),
|
"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.SetAuthenticated(true) // Set flag initially
|
||||||
session.SetEmail("[email protected]")
|
session.SetEmail("[email protected]")
|
||||||
// Create an expired token for this test
|
// Create an expired token for this test
|
||||||
expiredToken, _ := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]interface{}{
|
expiredToken, _ := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]any{
|
||||||
"iss": "https://test-issuer.com", "aud": "test-client-id", "exp": time.Now().Add(-1 * time.Hour).Unix(),
|
"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(),
|
"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),
|
"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()
|
exp := time.Now().Add(30 * time.Second).Unix()
|
||||||
iat := time.Now().Add(-1 * time.Minute).Unix()
|
iat := time.Now().Add(-1 * time.Minute).Unix()
|
||||||
nbf := 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]interface{}{
|
nearExpiryToken, _ := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]any{
|
||||||
"iss": "https://test-issuer.com", "aud": "test-client-id", "exp": exp, "iat": iat, "nbf": nbf,
|
"iss": "https://test-issuer.com", "aud": "test-client-id", "exp": exp, "iat": iat, "nbf": nbf,
|
||||||
"sub": "test-subject", "email": "[email protected]", "jti": generateRandomString(16),
|
"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()
|
exp := time.Now().Add(10 * time.Minute).Unix()
|
||||||
iat := time.Now().Add(-1 * time.Minute).Unix()
|
iat := time.Now().Add(-1 * time.Minute).Unix()
|
||||||
nbf := 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]interface{}{
|
validToken, _ := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]any{
|
||||||
"iss": "https://test-issuer.com", "aud": "test-client-id", "exp": exp, "iat": iat, "nbf": nbf,
|
"iss": "https://test-issuer.com", "aud": "test-client-id", "exp": exp, "iat": iat, "nbf": nbf,
|
||||||
"sub": "test-subject", "email": "[email protected]", "jti": generateRandomString(16),
|
"sub": "test-subject", "email": "[email protected]", "jti": generateRandomString(16),
|
||||||
})
|
})
|
||||||
@@ -693,7 +693,7 @@ func TestServeHTTP(t *testing.T) {
|
|||||||
session.SetAuthenticated(true)
|
session.SetAuthenticated(true)
|
||||||
session.SetEmail("[email protected]") // Use disallowed domain
|
session.SetEmail("[email protected]") // Use disallowed domain
|
||||||
// Generate a fresh valid token for this test case
|
// Generate a fresh valid token for this test case
|
||||||
freshToken, _ := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]interface{}{
|
freshToken, _ := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]any{
|
||||||
"iss": "https://test-issuer.com", "aud": "test-client-id", "exp": time.Now().Add(1 * time.Hour).Unix(),
|
"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
|
"iat": time.Now().Unix(), "nbf": time.Now().Unix(), "sub": "test-subject", "email": "[email protected]", // Match email
|
||||||
"jti": generateRandomString(16), // Unique JTI
|
"jti": generateRandomString(16), // Unique JTI
|
||||||
@@ -715,7 +715,7 @@ func TestServeHTTP(t *testing.T) {
|
|||||||
session.SetAuthenticated(true)
|
session.SetAuthenticated(true)
|
||||||
session.SetEmail("[email protected]") // Use disallowed domain
|
session.SetEmail("[email protected]") // Use disallowed domain
|
||||||
// Generate a fresh valid token for this test case
|
// Generate a fresh valid token for this test case
|
||||||
freshToken, _ := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]interface{}{
|
freshToken, _ := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]any{
|
||||||
"iss": "https://test-issuer.com", "aud": "test-client-id", "exp": time.Now().Add(1 * time.Hour).Unix(),
|
"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
|
"iat": time.Now().Unix(), "nbf": time.Now().Unix(), "sub": "test-subject", "email": "[email protected]", // Match email
|
||||||
"jti": generateRandomString(16), // Unique JTI
|
"jti": generateRandomString(16), // Unique JTI
|
||||||
@@ -967,11 +967,11 @@ func TestJWTVerify_MissingClaims(t *testing.T) {
|
|||||||
ts.Setup()
|
ts.Setup()
|
||||||
|
|
||||||
jwt := &JWT{
|
jwt := &JWT{
|
||||||
Header: map[string]interface{}{
|
Header: map[string]any{
|
||||||
"alg": "RS256",
|
"alg": "RS256",
|
||||||
"kid": "test-key-id",
|
"kid": "test-key-id",
|
||||||
},
|
},
|
||||||
Claims: map[string]interface{}{
|
Claims: map[string]any{
|
||||||
// Missing 'iss', 'aud', 'exp', 'iat', 'sub'
|
// Missing 'iss', 'aud', 'exp', 'iat', 'sub'
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -992,7 +992,7 @@ func TestHandleCallback(t *testing.T) {
|
|||||||
name string
|
name string
|
||||||
queryParams string
|
queryParams string
|
||||||
exchangeCodeForToken func(code string, redirectURL string, codeVerifier string) (*TokenResponse, error)
|
exchangeCodeForToken func(code string, redirectURL string, codeVerifier string) (*TokenResponse, error)
|
||||||
extractClaimsFunc func(tokenString string) (map[string]interface{}, error)
|
extractClaimsFunc func(tokenString string) (map[string]any, error)
|
||||||
sessionSetupFunc func(*SessionData)
|
sessionSetupFunc func(*SessionData)
|
||||||
expectedStatus int
|
expectedStatus int
|
||||||
}{
|
}{
|
||||||
@@ -1005,8 +1005,8 @@ func TestHandleCallback(t *testing.T) {
|
|||||||
RefreshToken: "test-refresh-token",
|
RefreshToken: "test-refresh-token",
|
||||||
}, nil
|
}, nil
|
||||||
},
|
},
|
||||||
extractClaimsFunc: func(tokenString string) (map[string]interface{}, error) {
|
extractClaimsFunc: func(tokenString string) (map[string]any, error) {
|
||||||
return map[string]interface{}{
|
return map[string]any{
|
||||||
"email": "[email protected]",
|
"email": "[email protected]",
|
||||||
"nonce": "test-nonce",
|
"nonce": "test-nonce",
|
||||||
}, nil
|
}, nil
|
||||||
@@ -1060,7 +1060,7 @@ func TestHandleCallback(t *testing.T) {
|
|||||||
exp := now.Add(1 * time.Hour).Unix()
|
exp := now.Add(1 * time.Hour).Unix()
|
||||||
iat := now.Unix()
|
iat := now.Unix()
|
||||||
nbf := now.Unix()
|
nbf := now.Unix()
|
||||||
disallowedToken, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]interface{}{
|
disallowedToken, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]any{
|
||||||
"iss": "https://test-issuer.com",
|
"iss": "https://test-issuer.com",
|
||||||
"aud": "test-client-id",
|
"aud": "test-client-id",
|
||||||
"exp": exp,
|
"exp": exp,
|
||||||
@@ -1102,8 +1102,8 @@ func TestHandleCallback(t *testing.T) {
|
|||||||
RefreshToken: "test-refresh-token",
|
RefreshToken: "test-refresh-token",
|
||||||
}, nil
|
}, nil
|
||||||
},
|
},
|
||||||
extractClaimsFunc: func(tokenString string) (map[string]interface{}, error) {
|
extractClaimsFunc: func(tokenString string) (map[string]any, error) {
|
||||||
return map[string]interface{}{
|
return map[string]any{
|
||||||
"email": "[email protected]",
|
"email": "[email protected]",
|
||||||
"nonce": "test-nonce",
|
"nonce": "test-nonce",
|
||||||
}, nil
|
}, nil
|
||||||
@@ -1123,8 +1123,8 @@ func TestHandleCallback(t *testing.T) {
|
|||||||
RefreshToken: "test-refresh-token",
|
RefreshToken: "test-refresh-token",
|
||||||
}, nil
|
}, nil
|
||||||
},
|
},
|
||||||
extractClaimsFunc: func(tokenString string) (map[string]interface{}, error) {
|
extractClaimsFunc: func(tokenString string) (map[string]any, error) {
|
||||||
return map[string]interface{}{
|
return map[string]any{
|
||||||
"email": "[email protected]",
|
"email": "[email protected]",
|
||||||
"nonce": "invalid-nonce",
|
"nonce": "invalid-nonce",
|
||||||
}, nil
|
}, nil
|
||||||
@@ -1144,8 +1144,8 @@ func TestHandleCallback(t *testing.T) {
|
|||||||
RefreshToken: "test-refresh-token",
|
RefreshToken: "test-refresh-token",
|
||||||
}, nil
|
}, nil
|
||||||
},
|
},
|
||||||
extractClaimsFunc: func(tokenString string) (map[string]interface{}, error) {
|
extractClaimsFunc: func(tokenString string) (map[string]any, error) {
|
||||||
return map[string]interface{}{
|
return map[string]any{
|
||||||
"email": "[email protected]",
|
"email": "[email protected]",
|
||||||
// Missing nonce
|
// Missing nonce
|
||||||
}, nil
|
}, nil
|
||||||
@@ -1159,7 +1159,7 @@ func TestHandleCallback(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range tests {
|
for _, tc := range tests {
|
||||||
tc := tc // Capture range variable
|
// Capture range variable
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
// Clear the global replay cache before each test run
|
// Clear the global replay cache before each test run
|
||||||
replayCacheMu.Lock()
|
replayCacheMu.Lock()
|
||||||
@@ -1346,7 +1346,7 @@ func TestOIDCHandler(t *testing.T) {
|
|||||||
name string
|
name string
|
||||||
queryParams string
|
queryParams string
|
||||||
exchangeCodeForToken func(code string, redirectURL string, codeVerifier string) (*TokenResponse, error)
|
exchangeCodeForToken func(code string, redirectURL string, codeVerifier string) (*TokenResponse, error)
|
||||||
extractClaimsFunc func(tokenString string) (map[string]interface{}, error)
|
extractClaimsFunc func(tokenString string) (map[string]any, error)
|
||||||
sessionSetupFunc func(session *sessions.Session)
|
sessionSetupFunc func(session *sessions.Session)
|
||||||
expectedStatus int
|
expectedStatus int
|
||||||
blacklist bool
|
blacklist bool
|
||||||
@@ -1368,9 +1368,9 @@ func TestOIDCHandler(t *testing.T) {
|
|||||||
RefreshToken: "test-refresh-token",
|
RefreshToken: "test-refresh-token",
|
||||||
}, nil
|
}, nil
|
||||||
},
|
},
|
||||||
extractClaimsFunc: func(tokenString string) (map[string]interface{}, error) {
|
extractClaimsFunc: func(tokenString string) (map[string]any, error) {
|
||||||
// Simulate extraction of claims with invalid nonce
|
// Simulate extraction of claims with invalid nonce
|
||||||
return map[string]interface{}{
|
return map[string]any{
|
||||||
"email": "[email protected]",
|
"email": "[email protected]",
|
||||||
"nonce": "invalid-nonce",
|
"nonce": "invalid-nonce",
|
||||||
}, nil
|
}, nil
|
||||||
@@ -1392,9 +1392,9 @@ func TestOIDCHandler(t *testing.T) {
|
|||||||
RefreshToken: "test-refresh-token",
|
RefreshToken: "test-refresh-token",
|
||||||
}, nil
|
}, nil
|
||||||
},
|
},
|
||||||
extractClaimsFunc: func(tokenString string) (map[string]interface{}, error) {
|
extractClaimsFunc: func(tokenString string) (map[string]any, error) {
|
||||||
// Simulate extraction of claims without nonce
|
// Simulate extraction of claims without nonce
|
||||||
return map[string]interface{}{
|
return map[string]any{
|
||||||
"email": "[email protected]",
|
"email": "[email protected]",
|
||||||
}, nil
|
}, nil
|
||||||
},
|
},
|
||||||
@@ -1415,9 +1415,9 @@ func TestOIDCHandler(t *testing.T) {
|
|||||||
RefreshToken: "test-refresh-token",
|
RefreshToken: "test-refresh-token",
|
||||||
}, nil
|
}, nil
|
||||||
},
|
},
|
||||||
extractClaimsFunc: func(tokenString string) (map[string]interface{}, error) {
|
extractClaimsFunc: func(tokenString string) (map[string]any, error) {
|
||||||
// Simulate extraction of claims
|
// Simulate extraction of claims
|
||||||
return map[string]interface{}{
|
return map[string]any{
|
||||||
"email": "[email protected]",
|
"email": "[email protected]",
|
||||||
"nonce": "test-nonce",
|
"nonce": "test-nonce",
|
||||||
}, nil
|
}, nil
|
||||||
@@ -1439,9 +1439,9 @@ func TestOIDCHandler(t *testing.T) {
|
|||||||
RefreshToken: "test-refresh-token",
|
RefreshToken: "test-refresh-token",
|
||||||
}, nil
|
}, nil
|
||||||
},
|
},
|
||||||
extractClaimsFunc: func(tokenString string) (map[string]interface{}, error) {
|
extractClaimsFunc: func(tokenString string) (map[string]any, error) {
|
||||||
// Simulate extraction of claims with mismatched nonce
|
// Simulate extraction of claims with mismatched nonce
|
||||||
return map[string]interface{}{
|
return map[string]any{
|
||||||
"email": "[email protected]",
|
"email": "[email protected]",
|
||||||
"nonce": "invalid-nonce",
|
"nonce": "invalid-nonce",
|
||||||
}, nil
|
}, nil
|
||||||
@@ -1451,7 +1451,7 @@ func TestOIDCHandler(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range tests {
|
for _, tc := range tests {
|
||||||
tc := tc // Capture range variable
|
// Capture range variable
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
// Reset token blacklist and cache
|
// Reset token blacklist and cache
|
||||||
ts.tOidc.tokenBlacklist = NewCache() // Use generic cache for blacklist
|
ts.tOidc.tokenBlacklist = NewCache() // Use generic cache for blacklist
|
||||||
@@ -1471,7 +1471,7 @@ func TestOIDCHandler(t *testing.T) {
|
|||||||
|
|
||||||
if tc.cacheToken {
|
if tc.cacheToken {
|
||||||
// Cache the token with dummy claims
|
// Cache the token with dummy claims
|
||||||
ts.tOidc.tokenCache.Set(ts.token, map[string]interface{}{
|
ts.tOidc.tokenCache.Set(ts.token, map[string]any{
|
||||||
"empty": "claim",
|
"empty": "claim",
|
||||||
}, 60)
|
}, 60)
|
||||||
}
|
}
|
||||||
@@ -1732,7 +1732,7 @@ func TestRevokeToken(t *testing.T) {
|
|||||||
ts.Setup()
|
ts.Setup()
|
||||||
|
|
||||||
token := "test.token.with.claims"
|
token := "test.token.with.claims"
|
||||||
claims := map[string]interface{}{
|
claims := map[string]any{
|
||||||
"exp": float64(time.Now().Add(time.Hour).Unix()),
|
"exp": float64(time.Now().Add(time.Hour).Unix()),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1833,7 +1833,7 @@ func TestHandleExpiredToken(t *testing.T) {
|
|||||||
setupSession: func(session *SessionData) {
|
setupSession: func(session *SessionData) {
|
||||||
session.SetAuthenticated(true)
|
session.SetAuthenticated(true)
|
||||||
// Create an expired token for this test
|
// Create an expired token for this test
|
||||||
expiredToken, _ := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]interface{}{
|
expiredToken, _ := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]any{
|
||||||
"iss": "https://test-issuer.com", "aud": "test-client-id", "exp": time.Now().Add(-1 * time.Hour).Unix(),
|
"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(),
|
"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),
|
"sub": "test-subject", "email": "[email protected]", "jti": generateRandomString(16),
|
||||||
@@ -1848,7 +1848,7 @@ func TestHandleExpiredToken(t *testing.T) {
|
|||||||
setupSession: func(session *SessionData) {
|
setupSession: func(session *SessionData) {
|
||||||
session.SetAuthenticated(true)
|
session.SetAuthenticated(true)
|
||||||
// Create an expired token for this test
|
// Create an expired token for this test
|
||||||
expiredToken, _ := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]interface{}{
|
expiredToken, _ := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]any{
|
||||||
"iss": "https://test-issuer.com", "aud": "test-client-id", "exp": time.Now().Add(-1 * time.Hour).Unix(),
|
"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(),
|
"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),
|
"sub": "test-subject", "email": "[email protected]", "jti": generateRandomString(16),
|
||||||
@@ -1933,16 +1933,16 @@ func TestExtractGroupsAndRoles(t *testing.T) {
|
|||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
claims map[string]interface{}
|
claims map[string]any
|
||||||
expectGroups []string
|
expectGroups []string
|
||||||
expectRoles []string
|
expectRoles []string
|
||||||
expectError bool
|
expectError bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "Valid groups and roles",
|
name: "Valid groups and roles",
|
||||||
claims: map[string]interface{}{
|
claims: map[string]any{
|
||||||
"groups": []interface{}{"group1", "group2"},
|
"groups": []any{"group1", "group2"},
|
||||||
"roles": []interface{}{"role1", "role2"},
|
"roles": []any{"role1", "role2"},
|
||||||
},
|
},
|
||||||
expectGroups: []string{"group1", "group2"},
|
expectGroups: []string{"group1", "group2"},
|
||||||
expectRoles: []string{"role1", "role2"},
|
expectRoles: []string{"role1", "role2"},
|
||||||
@@ -1950,9 +1950,9 @@ func TestExtractGroupsAndRoles(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Empty groups and roles",
|
name: "Empty groups and roles",
|
||||||
claims: map[string]interface{}{
|
claims: map[string]any{
|
||||||
"groups": []interface{}{},
|
"groups": []any{},
|
||||||
"roles": []interface{}{},
|
"roles": []any{},
|
||||||
},
|
},
|
||||||
expectGroups: []string{},
|
expectGroups: []string{},
|
||||||
expectRoles: []string{},
|
expectRoles: []string{},
|
||||||
@@ -1960,9 +1960,9 @@ func TestExtractGroupsAndRoles(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Invalid groups format",
|
name: "Invalid groups format",
|
||||||
claims: map[string]interface{}{
|
claims: map[string]any{
|
||||||
"groups": "not-an-array",
|
"groups": "not-an-array",
|
||||||
"roles": []interface{}{"role1"},
|
"roles": []any{"role1"},
|
||||||
},
|
},
|
||||||
expectError: true,
|
expectError: true,
|
||||||
},
|
},
|
||||||
@@ -2106,7 +2106,7 @@ func TestServeHTTPRolesAndGroups(t *testing.T) {
|
|||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
allowedRolesAndGroups map[string]struct{}
|
allowedRolesAndGroups map[string]struct{}
|
||||||
claims map[string]interface{}
|
claims map[string]any
|
||||||
setupSession func(*SessionData)
|
setupSession func(*SessionData)
|
||||||
expectedStatus int
|
expectedStatus int
|
||||||
expectedHeaders map[string]string
|
expectedHeaders map[string]string
|
||||||
@@ -2116,15 +2116,15 @@ func TestServeHTTPRolesAndGroups(t *testing.T) {
|
|||||||
allowedRolesAndGroups: map[string]struct{}{
|
allowedRolesAndGroups: map[string]struct{}{
|
||||||
"admin": {},
|
"admin": {},
|
||||||
},
|
},
|
||||||
claims: map[string]interface{}{
|
claims: map[string]any{
|
||||||
"iss": "https://test-issuer.com",
|
"iss": "https://test-issuer.com",
|
||||||
"aud": "test-client-id",
|
"aud": "test-client-id",
|
||||||
"exp": exp,
|
"exp": exp,
|
||||||
"iat": iat,
|
"iat": iat,
|
||||||
"nbf": nbf,
|
"nbf": nbf,
|
||||||
"sub": "test-subject",
|
"sub": "test-subject",
|
||||||
"roles": []interface{}{"admin", "user"},
|
"roles": []any{"admin", "user"},
|
||||||
"groups": []interface{}{"group1"},
|
"groups": []any{"group1"},
|
||||||
"jti": generateRandomString(16),
|
"jti": generateRandomString(16),
|
||||||
},
|
},
|
||||||
setupSession: func(session *SessionData) {
|
setupSession: func(session *SessionData) {
|
||||||
@@ -2142,15 +2142,15 @@ func TestServeHTTPRolesAndGroups(t *testing.T) {
|
|||||||
allowedRolesAndGroups: map[string]struct{}{
|
allowedRolesAndGroups: map[string]struct{}{
|
||||||
"allowed-group": {},
|
"allowed-group": {},
|
||||||
},
|
},
|
||||||
claims: map[string]interface{}{
|
claims: map[string]any{
|
||||||
"iss": "https://test-issuer.com",
|
"iss": "https://test-issuer.com",
|
||||||
"aud": "test-client-id",
|
"aud": "test-client-id",
|
||||||
"exp": exp,
|
"exp": exp,
|
||||||
"iat": iat,
|
"iat": iat,
|
||||||
"nbf": nbf,
|
"nbf": nbf,
|
||||||
"sub": "test-subject",
|
"sub": "test-subject",
|
||||||
"roles": []interface{}{"user"},
|
"roles": []any{"user"},
|
||||||
"groups": []interface{}{"allowed-group"},
|
"groups": []any{"allowed-group"},
|
||||||
"jti": generateRandomString(16),
|
"jti": generateRandomString(16),
|
||||||
},
|
},
|
||||||
setupSession: func(session *SessionData) {
|
setupSession: func(session *SessionData) {
|
||||||
@@ -2169,15 +2169,15 @@ func TestServeHTTPRolesAndGroups(t *testing.T) {
|
|||||||
"admin": {},
|
"admin": {},
|
||||||
"allowed-group": {},
|
"allowed-group": {},
|
||||||
},
|
},
|
||||||
claims: map[string]interface{}{
|
claims: map[string]any{
|
||||||
"iss": "https://test-issuer.com",
|
"iss": "https://test-issuer.com",
|
||||||
"aud": "test-client-id",
|
"aud": "test-client-id",
|
||||||
"exp": exp,
|
"exp": exp,
|
||||||
"iat": iat,
|
"iat": iat,
|
||||||
"nbf": nbf,
|
"nbf": nbf,
|
||||||
"sub": "test-subject",
|
"sub": "test-subject",
|
||||||
"roles": []interface{}{"user"},
|
"roles": []any{"user"},
|
||||||
"groups": []interface{}{"regular-group"},
|
"groups": []any{"regular-group"},
|
||||||
"jti": generateRandomString(16),
|
"jti": generateRandomString(16),
|
||||||
},
|
},
|
||||||
setupSession: func(session *SessionData) {
|
setupSession: func(session *SessionData) {
|
||||||
@@ -2189,15 +2189,15 @@ func TestServeHTTPRolesAndGroups(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "No role/group restrictions",
|
name: "No role/group restrictions",
|
||||||
allowedRolesAndGroups: map[string]struct{}{},
|
allowedRolesAndGroups: map[string]struct{}{},
|
||||||
claims: map[string]interface{}{
|
claims: map[string]any{
|
||||||
"iss": "https://test-issuer.com",
|
"iss": "https://test-issuer.com",
|
||||||
"aud": "test-client-id",
|
"aud": "test-client-id",
|
||||||
"exp": exp,
|
"exp": exp,
|
||||||
"iat": iat,
|
"iat": iat,
|
||||||
"nbf": nbf,
|
"nbf": nbf,
|
||||||
"sub": "test-subject",
|
"sub": "test-subject",
|
||||||
"roles": []interface{}{"user"},
|
"roles": []any{"user"},
|
||||||
"groups": []interface{}{"regular-group"},
|
"groups": []any{"regular-group"},
|
||||||
"jti": generateRandomString(16),
|
"jti": generateRandomString(16),
|
||||||
},
|
},
|
||||||
setupSession: func(session *SessionData) {
|
setupSession: func(session *SessionData) {
|
||||||
@@ -2213,7 +2213,7 @@ func TestServeHTTPRolesAndGroups(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "Claims without roles and groups",
|
name: "Claims without roles and groups",
|
||||||
allowedRolesAndGroups: map[string]struct{}{},
|
allowedRolesAndGroups: map[string]struct{}{},
|
||||||
claims: map[string]interface{}{
|
claims: map[string]any{
|
||||||
"iss": "https://test-issuer.com",
|
"iss": "https://test-issuer.com",
|
||||||
"aud": "test-client-id",
|
"aud": "test-client-id",
|
||||||
"exp": exp,
|
"exp": exp,
|
||||||
@@ -2340,7 +2340,7 @@ func TestExchangeTokensWithRedirects(t *testing.T) {
|
|||||||
if len(cookies) != 3 {
|
if len(cookies) != 3 {
|
||||||
t.Errorf("Expected 3 cookies, got %d", len(cookies))
|
t.Errorf("Expected 3 cookies, got %d", len(cookies))
|
||||||
}
|
}
|
||||||
for i := 0; i < 3; i++ {
|
for i := range 3 {
|
||||||
found := false
|
found := false
|
||||||
expectedName := fmt.Sprintf("redirect-cookie-%d", i)
|
expectedName := fmt.Sprintf("redirect-cookie-%d", i)
|
||||||
for _, cookie := range cookies {
|
for _, cookie := range cookies {
|
||||||
@@ -2861,7 +2861,7 @@ func TestJWTVerifyWithSkipReplayCheck(t *testing.T) {
|
|||||||
iat := now.Unix()
|
iat := now.Unix()
|
||||||
nbf := now.Unix()
|
nbf := now.Unix()
|
||||||
|
|
||||||
token, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]interface{}{
|
token, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]any{
|
||||||
"iss": "https://test-issuer.com",
|
"iss": "https://test-issuer.com",
|
||||||
"aud": "test-client-id",
|
"aud": "test-client-id",
|
||||||
"exp": exp,
|
"exp": exp,
|
||||||
@@ -2954,7 +2954,7 @@ func TestJWTVerifyBackwardCompatibility(t *testing.T) {
|
|||||||
iat := now.Unix()
|
iat := now.Unix()
|
||||||
nbf := now.Unix()
|
nbf := now.Unix()
|
||||||
|
|
||||||
token, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]interface{}{
|
token, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]any{
|
||||||
"iss": "https://test-issuer.com",
|
"iss": "https://test-issuer.com",
|
||||||
"aud": "test-client-id",
|
"aud": "test-client-id",
|
||||||
"exp": exp,
|
"exp": exp,
|
||||||
@@ -3007,7 +3007,7 @@ func TestTokenReplayDetectionFalsePositiveFix(t *testing.T) {
|
|||||||
iat := now.Unix()
|
iat := now.Unix()
|
||||||
nbf := now.Unix()
|
nbf := now.Unix()
|
||||||
|
|
||||||
token, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]interface{}{
|
token, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]any{
|
||||||
"iss": "https://test-issuer.com",
|
"iss": "https://test-issuer.com",
|
||||||
"aud": "test-client-id",
|
"aud": "test-client-id",
|
||||||
"exp": exp,
|
"exp": exp,
|
||||||
@@ -3080,7 +3080,7 @@ func TestAuthenticationFlowReplayDetection(t *testing.T) {
|
|||||||
iat := now.Unix()
|
iat := now.Unix()
|
||||||
nbf := now.Unix()
|
nbf := now.Unix()
|
||||||
|
|
||||||
token, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]interface{}{
|
token, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]any{
|
||||||
"iss": "https://test-issuer.com",
|
"iss": "https://test-issuer.com",
|
||||||
"aud": "test-client-id",
|
"aud": "test-client-id",
|
||||||
"exp": exp,
|
"exp": exp,
|
||||||
@@ -3116,7 +3116,7 @@ func TestAuthenticationFlowReplayDetection(t *testing.T) {
|
|||||||
|
|
||||||
// Step 2: Subsequent requests (simulate normal request processing)
|
// Step 2: Subsequent requests (simulate normal request processing)
|
||||||
// These should use the token cache and skip replay detection
|
// These should use the token cache and skip replay detection
|
||||||
for i := 0; i < 3; i++ {
|
for i := range 3 {
|
||||||
err = ts.tOidc.VerifyToken(token)
|
err = ts.tOidc.VerifyToken(token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Subsequent request %d should succeed: %v", i+1, err)
|
t.Errorf("Subsequent request %d should succeed: %v", i+1, err)
|
||||||
@@ -3155,7 +3155,7 @@ func TestActualReplayAttackDetection(t *testing.T) {
|
|||||||
iat := now.Unix()
|
iat := now.Unix()
|
||||||
nbf := now.Unix()
|
nbf := now.Unix()
|
||||||
|
|
||||||
token, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]interface{}{
|
token, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]any{
|
||||||
"iss": "https://test-issuer.com",
|
"iss": "https://test-issuer.com",
|
||||||
"aud": "test-client-id",
|
"aud": "test-client-id",
|
||||||
"exp": exp,
|
"exp": exp,
|
||||||
@@ -3237,11 +3237,11 @@ func TestConcurrentTokenValidation(t *testing.T) {
|
|||||||
iat := now.Unix()
|
iat := now.Unix()
|
||||||
nbf := now.Unix()
|
nbf := now.Unix()
|
||||||
|
|
||||||
for i := 0; i < 10; i++ {
|
for i := range 10 {
|
||||||
jti := generateRandomString(16)
|
jti := generateRandomString(16)
|
||||||
jtis = append(jtis, jti)
|
jtis = append(jtis, jti)
|
||||||
|
|
||||||
token, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]interface{}{
|
token, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]any{
|
||||||
"iss": "https://test-issuer.com",
|
"iss": "https://test-issuer.com",
|
||||||
"aud": "test-client-id",
|
"aud": "test-client-id",
|
||||||
"exp": exp,
|
"exp": exp,
|
||||||
@@ -3264,9 +3264,9 @@ func TestConcurrentTokenValidation(t *testing.T) {
|
|||||||
|
|
||||||
results := make(chan error, numGoroutines*numIterations)
|
results := make(chan error, numGoroutines*numIterations)
|
||||||
|
|
||||||
for g := 0; g < numGoroutines; g++ {
|
for g := range numGoroutines {
|
||||||
go func(goroutineID int) {
|
go func(goroutineID int) {
|
||||||
for i := 0; i < numIterations; i++ {
|
for i := range numIterations {
|
||||||
tokenIndex := (goroutineID + i) % len(tokens)
|
tokenIndex := (goroutineID + i) % len(tokens)
|
||||||
token := tokens[tokenIndex]
|
token := tokens[tokenIndex]
|
||||||
|
|
||||||
@@ -3283,7 +3283,7 @@ func TestConcurrentTokenValidation(t *testing.T) {
|
|||||||
|
|
||||||
// Collect results
|
// Collect results
|
||||||
var errors []error
|
var errors []error
|
||||||
for i := 0; i < numGoroutines*numIterations*2; i++ {
|
for range numGoroutines * numIterations * 2 {
|
||||||
if err := <-results; err != nil {
|
if err := <-results; err != nil {
|
||||||
errors = append(errors, err)
|
errors = append(errors, err)
|
||||||
}
|
}
|
||||||
@@ -3322,7 +3322,7 @@ func TestJTIBlacklistBehavior(t *testing.T) {
|
|||||||
iat := now.Unix()
|
iat := now.Unix()
|
||||||
nbf := now.Unix()
|
nbf := now.Unix()
|
||||||
|
|
||||||
token, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]interface{}{
|
token, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]any{
|
||||||
"iss": "https://test-issuer.com",
|
"iss": "https://test-issuer.com",
|
||||||
"aud": "test-client-id",
|
"aud": "test-client-id",
|
||||||
"exp": exp,
|
"exp": exp,
|
||||||
@@ -3422,7 +3422,7 @@ func TestSessionBasedTokenRevalidation(t *testing.T) {
|
|||||||
iat := now.Unix()
|
iat := now.Unix()
|
||||||
nbf := now.Unix()
|
nbf := now.Unix()
|
||||||
|
|
||||||
token, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]interface{}{
|
token, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]any{
|
||||||
"iss": "https://test-issuer.com",
|
"iss": "https://test-issuer.com",
|
||||||
"aud": "test-client-id",
|
"aud": "test-client-id",
|
||||||
"exp": exp,
|
"exp": exp,
|
||||||
@@ -3448,7 +3448,7 @@ func TestSessionBasedTokenRevalidation(t *testing.T) {
|
|||||||
|
|
||||||
// Step 2: Multiple session-based requests (normal request processing)
|
// Step 2: Multiple session-based requests (normal request processing)
|
||||||
// These should not trigger replay detection false positives
|
// These should not trigger replay detection false positives
|
||||||
for i := 0; i < 5; i++ {
|
for i := range 5 {
|
||||||
err = ts.tOidc.VerifyToken(token)
|
err = ts.tOidc.VerifyToken(token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Session request %d should succeed: %v", i+1, err)
|
t.Errorf("Session request %d should succeed: %v", i+1, err)
|
||||||
@@ -3497,13 +3497,13 @@ func TestEdgeCasesWithDifferentTokenTypes(t *testing.T) {
|
|||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
tokenType string
|
tokenType string
|
||||||
claims map[string]interface{}
|
claims map[string]any
|
||||||
expectError bool
|
expectError bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "ID Token with JTI",
|
name: "ID Token with JTI",
|
||||||
tokenType: "id_token",
|
tokenType: "id_token",
|
||||||
claims: map[string]interface{}{
|
claims: map[string]any{
|
||||||
"iss": "https://test-issuer.com",
|
"iss": "https://test-issuer.com",
|
||||||
"aud": "test-client-id",
|
"aud": "test-client-id",
|
||||||
"exp": exp,
|
"exp": exp,
|
||||||
@@ -3520,7 +3520,7 @@ func TestEdgeCasesWithDifferentTokenTypes(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "Access Token with JTI",
|
name: "Access Token with JTI",
|
||||||
tokenType: "access_token",
|
tokenType: "access_token",
|
||||||
claims: map[string]interface{}{
|
claims: map[string]any{
|
||||||
"iss": "https://test-issuer.com",
|
"iss": "https://test-issuer.com",
|
||||||
"aud": "test-client-id",
|
"aud": "test-client-id",
|
||||||
"exp": exp,
|
"exp": exp,
|
||||||
@@ -3536,7 +3536,7 @@ func TestEdgeCasesWithDifferentTokenTypes(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "Token without JTI",
|
name: "Token without JTI",
|
||||||
tokenType: "no_jti",
|
tokenType: "no_jti",
|
||||||
claims: map[string]interface{}{
|
claims: map[string]any{
|
||||||
"iss": "https://test-issuer.com",
|
"iss": "https://test-issuer.com",
|
||||||
"aud": "test-client-id",
|
"aud": "test-client-id",
|
||||||
"exp": exp,
|
"exp": exp,
|
||||||
@@ -3793,7 +3793,7 @@ func TestScopeMergingEdgeCases(t *testing.T) {
|
|||||||
// generateLargeUserScopes creates a large list of user scopes for performance testing
|
// generateLargeUserScopes creates a large list of user scopes for performance testing
|
||||||
func generateLargeUserScopes() []string {
|
func generateLargeUserScopes() []string {
|
||||||
scopes := make([]string, 100)
|
scopes := make([]string, 100)
|
||||||
for i := 0; i < 100; i++ {
|
for i := range 100 {
|
||||||
scopes[i] = fmt.Sprintf("scope_%d", i)
|
scopes[i] = fmt.Sprintf("scope_%d", i)
|
||||||
}
|
}
|
||||||
// Add some duplicates to test deduplication performance
|
// Add some duplicates to test deduplication performance
|
||||||
@@ -3808,7 +3808,7 @@ func TestScopeMergingPerformance(t *testing.T) {
|
|||||||
|
|
||||||
// Create 1000 user scopes with some duplicates
|
// Create 1000 user scopes with some duplicates
|
||||||
userScopes := make([]string, 1000)
|
userScopes := make([]string, 1000)
|
||||||
for i := 0; i < 1000; i++ {
|
for i := range 1000 {
|
||||||
if i%10 == 0 {
|
if i%10 == 0 {
|
||||||
// Add some duplicates of defaults
|
// Add some duplicates of defaults
|
||||||
userScopes[i] = defaultScopes[i%len(defaultScopes)]
|
userScopes[i] = defaultScopes[i%len(defaultScopes)]
|
||||||
|
|||||||
+13
-14
@@ -1,6 +1,7 @@
|
|||||||
package traefikoidc
|
package traefikoidc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"maps"
|
||||||
"runtime"
|
"runtime"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
@@ -288,7 +289,7 @@ func (pm *PerformanceMetrics) collectSystemMetrics() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetMetrics returns all current performance metrics
|
// GetMetrics returns all current performance metrics
|
||||||
func (pm *PerformanceMetrics) GetMetrics() map[string]interface{} {
|
func (pm *PerformanceMetrics) GetMetrics() map[string]any {
|
||||||
pm.timingMutex.RLock()
|
pm.timingMutex.RLock()
|
||||||
defer pm.timingMutex.RUnlock()
|
defer pm.timingMutex.RUnlock()
|
||||||
|
|
||||||
@@ -317,7 +318,7 @@ func (pm *PerformanceMetrics) GetMetrics() map[string]interface{} {
|
|||||||
refreshErrorRate = float64(atomic.LoadInt64(&pm.refreshErrors)) / float64(refreshes)
|
refreshErrorRate = float64(atomic.LoadInt64(&pm.refreshErrors)) / float64(refreshes)
|
||||||
}
|
}
|
||||||
|
|
||||||
return map[string]interface{}{
|
return map[string]any{
|
||||||
// Cache metrics
|
// Cache metrics
|
||||||
"cache_hits": hits,
|
"cache_hits": hits,
|
||||||
"cache_misses": misses,
|
"cache_misses": misses,
|
||||||
@@ -370,11 +371,11 @@ func (pm *PerformanceMetrics) GetMetrics() map[string]interface{} {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetDetailedTimingMetrics returns detailed timing statistics
|
// GetDetailedTimingMetrics returns detailed timing statistics
|
||||||
func (pm *PerformanceMetrics) GetDetailedTimingMetrics() map[string]interface{} {
|
func (pm *PerformanceMetrics) GetDetailedTimingMetrics() map[string]any {
|
||||||
pm.timingMutex.RLock()
|
pm.timingMutex.RLock()
|
||||||
defer pm.timingMutex.RUnlock()
|
defer pm.timingMutex.RUnlock()
|
||||||
|
|
||||||
return map[string]interface{}{
|
return map[string]any{
|
||||||
"verification_stats": pm.calculateTimingStats(pm.verificationTimes),
|
"verification_stats": pm.calculateTimingStats(pm.verificationTimes),
|
||||||
"verification_timing": pm.calculateTimingStats(pm.verificationTimes),
|
"verification_timing": pm.calculateTimingStats(pm.verificationTimes),
|
||||||
"validation_stats": pm.calculateTimingStats(pm.validationTimes),
|
"validation_stats": pm.calculateTimingStats(pm.validationTimes),
|
||||||
@@ -385,9 +386,9 @@ func (pm *PerformanceMetrics) GetDetailedTimingMetrics() map[string]interface{}
|
|||||||
}
|
}
|
||||||
|
|
||||||
// calculateTimingStats calculates statistical metrics for timing data
|
// calculateTimingStats calculates statistical metrics for timing data
|
||||||
func (pm *PerformanceMetrics) calculateTimingStats(times []time.Duration) map[string]interface{} {
|
func (pm *PerformanceMetrics) calculateTimingStats(times []time.Duration) map[string]any {
|
||||||
if len(times) == 0 {
|
if len(times) == 0 {
|
||||||
return map[string]interface{}{
|
return map[string]any{
|
||||||
"count": 0,
|
"count": 0,
|
||||||
"min_ms": float64(0),
|
"min_ms": float64(0),
|
||||||
"max_ms": float64(0),
|
"max_ms": float64(0),
|
||||||
@@ -404,7 +405,7 @@ func (pm *PerformanceMetrics) calculateTimingStats(times []time.Duration) map[st
|
|||||||
copy(sortedTimes, times)
|
copy(sortedTimes, times)
|
||||||
|
|
||||||
// Simple bubble sort for small arrays
|
// Simple bubble sort for small arrays
|
||||||
for i := 0; i < len(sortedTimes); i++ {
|
for i := range sortedTimes {
|
||||||
for j := i + 1; j < len(sortedTimes); j++ {
|
for j := i + 1; j < len(sortedTimes); j++ {
|
||||||
if sortedTimes[i] > sortedTimes[j] {
|
if sortedTimes[i] > sortedTimes[j] {
|
||||||
sortedTimes[i], sortedTimes[j] = sortedTimes[j], sortedTimes[i]
|
sortedTimes[i], sortedTimes[j] = sortedTimes[j], sortedTimes[i]
|
||||||
@@ -426,7 +427,7 @@ func (pm *PerformanceMetrics) calculateTimingStats(times []time.Duration) map[st
|
|||||||
p95 := sortedTimes[int(float64(len(sortedTimes))*0.95)]
|
p95 := sortedTimes[int(float64(len(sortedTimes))*0.95)]
|
||||||
p99 := sortedTimes[int(float64(len(sortedTimes))*0.99)]
|
p99 := sortedTimes[int(float64(len(sortedTimes))*0.99)]
|
||||||
|
|
||||||
return map[string]interface{}{
|
return map[string]any{
|
||||||
"count": len(sortedTimes),
|
"count": len(sortedTimes),
|
||||||
"min_ms": float64(min.Nanoseconds()) / 1e6,
|
"min_ms": float64(min.Nanoseconds()) / 1e6,
|
||||||
"max_ms": float64(max.Nanoseconds()) / 1e6,
|
"max_ms": float64(max.Nanoseconds()) / 1e6,
|
||||||
@@ -527,9 +528,7 @@ func (rm *ResourceMonitor) GetCacheSizes() map[string]int64 {
|
|||||||
defer rm.cacheMutex.RUnlock()
|
defer rm.cacheMutex.RUnlock()
|
||||||
|
|
||||||
sizes := make(map[string]int64)
|
sizes := make(map[string]int64)
|
||||||
for name, size := range rm.cacheSizes {
|
maps.Copy(sizes, rm.cacheSizes)
|
||||||
sizes[name] = size
|
|
||||||
}
|
|
||||||
return sizes
|
return sizes
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -665,12 +664,12 @@ func (rm *ResourceMonitor) GetAlerts() []ResourceAlert {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetResourceStatus returns current resource status
|
// GetResourceStatus returns current resource status
|
||||||
func (rm *ResourceMonitor) GetResourceStatus() map[string]interface{} {
|
func (rm *ResourceMonitor) GetResourceStatus() map[string]any {
|
||||||
metrics := rm.perfMetrics.GetMetrics()
|
metrics := rm.perfMetrics.GetMetrics()
|
||||||
cacheSizes := rm.GetCacheSizes()
|
cacheSizes := rm.GetCacheSizes()
|
||||||
|
|
||||||
status := map[string]interface{}{
|
status := map[string]any{
|
||||||
"limits": map[string]interface{}{
|
"limits": map[string]any{
|
||||||
"max_memory_bytes": rm.maxMemoryBytes,
|
"max_memory_bytes": rm.maxMemoryBytes,
|
||||||
"max_cache_size": rm.maxCacheSize,
|
"max_cache_size": rm.maxCacheSize,
|
||||||
"max_sessions": rm.maxSessions,
|
"max_sessions": rm.maxSessions,
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ func TestPerformanceMetrics(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("Get detailed timing metrics", func(t *testing.T) {
|
t.Run("Get detailed timing metrics", func(t *testing.T) {
|
||||||
// Add more timing data
|
// Add more timing data
|
||||||
for i := 0; i < 5; i++ {
|
for i := range 5 {
|
||||||
metrics.RecordTokenVerification(time.Duration(i+1)*time.Millisecond, true)
|
metrics.RecordTokenVerification(time.Duration(i+1)*time.Millisecond, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,7 +93,7 @@ func TestPerformanceMetrics(t *testing.T) {
|
|||||||
t.Error("Expected verification stats to be present")
|
t.Error("Expected verification stats to be present")
|
||||||
}
|
}
|
||||||
|
|
||||||
verificationStats := detailed["verification_stats"].(map[string]interface{})
|
verificationStats := detailed["verification_stats"].(map[string]any)
|
||||||
if verificationStats["count"].(int) != 6 { // 1 from previous test + 5 new
|
if verificationStats["count"].(int) != 6 { // 1 from previous test + 5 new
|
||||||
t.Errorf("Expected 6 verifications, got %v", verificationStats["count"])
|
t.Errorf("Expected 6 verifications, got %v", verificationStats["count"])
|
||||||
}
|
}
|
||||||
@@ -154,7 +154,7 @@ func TestPerformanceMetricsCalculations(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
detailed := metrics.GetDetailedTimingMetrics()
|
detailed := metrics.GetDetailedTimingMetrics()
|
||||||
verificationStats := detailed["verification_stats"].(map[string]interface{})
|
verificationStats := detailed["verification_stats"].(map[string]any)
|
||||||
|
|
||||||
// Average should be 20ms
|
// Average should be 20ms
|
||||||
avgMs := verificationStats["average_ms"].(float64)
|
avgMs := verificationStats["average_ms"].(float64)
|
||||||
@@ -178,7 +178,7 @@ func TestPerformanceMetricsCalculations(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
detailed := metrics.GetDetailedTimingMetrics()
|
detailed := metrics.GetDetailedTimingMetrics()
|
||||||
verificationStats := detailed["verification_stats"].(map[string]interface{})
|
verificationStats := detailed["verification_stats"].(map[string]any)
|
||||||
|
|
||||||
minMs := verificationStats["min_ms"].(float64)
|
minMs := verificationStats["min_ms"].(float64)
|
||||||
maxMs := verificationStats["max_ms"].(float64)
|
maxMs := verificationStats["max_ms"].(float64)
|
||||||
@@ -222,11 +222,11 @@ func TestPerformanceMetricsConcurrency(t *testing.T) {
|
|||||||
// Test concurrent access
|
// Test concurrent access
|
||||||
done := make(chan bool, 10)
|
done := make(chan bool, 10)
|
||||||
|
|
||||||
for i := 0; i < 10; i++ {
|
for range 10 {
|
||||||
go func() {
|
go func() {
|
||||||
defer func() { done <- true }()
|
defer func() { done <- true }()
|
||||||
|
|
||||||
for j := 0; j < 100; j++ {
|
for range 100 {
|
||||||
metrics.RecordCacheHit()
|
metrics.RecordCacheHit()
|
||||||
metrics.RecordTokenVerification(time.Millisecond, true)
|
metrics.RecordTokenVerification(time.Millisecond, true)
|
||||||
}
|
}
|
||||||
@@ -234,7 +234,7 @@ func TestPerformanceMetricsConcurrency(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Wait for all goroutines to complete
|
// Wait for all goroutines to complete
|
||||||
for i := 0; i < 10; i++ {
|
for range 10 {
|
||||||
<-done
|
<-done
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -303,7 +303,7 @@ func TestPerformanceMetricsEdgeCases(t *testing.T) {
|
|||||||
metrics.RecordTokenVerification(largeDuration, true)
|
metrics.RecordTokenVerification(largeDuration, true)
|
||||||
|
|
||||||
detailed := metrics.GetDetailedTimingMetrics()
|
detailed := metrics.GetDetailedTimingMetrics()
|
||||||
verificationStats := detailed["verification_stats"].(map[string]interface{})
|
verificationStats := detailed["verification_stats"].(map[string]any)
|
||||||
|
|
||||||
// Should handle large durations without overflow
|
// Should handle large durations without overflow
|
||||||
if verificationStats["max_ms"].(float64) <= 0 {
|
if verificationStats["max_ms"].(float64) <= 0 {
|
||||||
|
|||||||
+21
-21
@@ -22,8 +22,8 @@ func TestConcurrentTokenVerification(t *testing.T) {
|
|||||||
|
|
||||||
// Create multiple valid tokens to avoid replay detection
|
// Create multiple valid tokens to avoid replay detection
|
||||||
tokens := make([]string, 10)
|
tokens := make([]string, 10)
|
||||||
for i := 0; i < 10; i++ {
|
for i := range 10 {
|
||||||
token, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]interface{}{
|
token, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]any{
|
||||||
"iss": "https://test-issuer.com",
|
"iss": "https://test-issuer.com",
|
||||||
"aud": "test-client-id",
|
"aud": "test-client-id",
|
||||||
"exp": float64(time.Now().Add(1 * time.Hour).Unix()),
|
"exp": float64(time.Now().Add(1 * time.Hour).Unix()),
|
||||||
@@ -71,11 +71,11 @@ func TestConcurrentTokenVerification(t *testing.T) {
|
|||||||
var errorCount int64
|
var errorCount int64
|
||||||
errors := make(chan error, numGoroutines*verificationsPerGoroutine)
|
errors := make(chan error, numGoroutines*verificationsPerGoroutine)
|
||||||
|
|
||||||
for i := 0; i < numGoroutines; i++ {
|
for i := range numGoroutines {
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func(goroutineID int) {
|
go func(goroutineID int) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
for j := 0; j < verificationsPerGoroutine; j++ {
|
for j := range verificationsPerGoroutine {
|
||||||
tokenIndex := (goroutineID*verificationsPerGoroutine + j) % len(tokens)
|
tokenIndex := (goroutineID*verificationsPerGoroutine + j) % len(tokens)
|
||||||
err := tOidc.VerifyToken(tokens[tokenIndex])
|
err := tOidc.VerifyToken(tokens[tokenIndex])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -144,8 +144,8 @@ func TestCacheMemoryExhaustion(t *testing.T) {
|
|||||||
const numTokens = 500
|
const numTokens = 500
|
||||||
tokens := make([]string, numTokens)
|
tokens := make([]string, numTokens)
|
||||||
|
|
||||||
for i := 0; i < numTokens; i++ {
|
for i := range numTokens {
|
||||||
token, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]interface{}{
|
token, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]any{
|
||||||
"iss": "https://test-issuer.com",
|
"iss": "https://test-issuer.com",
|
||||||
"aud": "test-client-id",
|
"aud": "test-client-id",
|
||||||
"exp": float64(time.Now().Add(1 * time.Hour).Unix()),
|
"exp": float64(time.Now().Add(1 * time.Hour).Unix()),
|
||||||
@@ -161,7 +161,7 @@ func TestCacheMemoryExhaustion(t *testing.T) {
|
|||||||
tokens[i] = token
|
tokens[i] = token
|
||||||
|
|
||||||
// Add to cache
|
// Add to cache
|
||||||
claims := map[string]interface{}{
|
claims := map[string]any{
|
||||||
"iss": "https://test-issuer.com",
|
"iss": "https://test-issuer.com",
|
||||||
"aud": "test-client-id",
|
"aud": "test-client-id",
|
||||||
"exp": float64(time.Now().Add(1 * time.Hour).Unix()),
|
"exp": float64(time.Now().Add(1 * time.Hour).Unix()),
|
||||||
@@ -210,7 +210,7 @@ func TestSessionConcurrencyProtection(t *testing.T) {
|
|||||||
var successCount int64
|
var successCount int64
|
||||||
var errorCount int64
|
var errorCount int64
|
||||||
|
|
||||||
for i := 0; i < numGoroutines; i++ {
|
for i := range numGoroutines {
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func(goroutineID int) {
|
go func(goroutineID int) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
@@ -218,7 +218,7 @@ func TestSessionConcurrencyProtection(t *testing.T) {
|
|||||||
// Each goroutine gets its own request and session
|
// Each goroutine gets its own request and session
|
||||||
req := httptest.NewRequest("GET", "/test", nil)
|
req := httptest.NewRequest("GET", "/test", nil)
|
||||||
|
|
||||||
for j := 0; j < operationsPerGoroutine; j++ {
|
for j := range operationsPerGoroutine {
|
||||||
// Get a fresh session for each operation
|
// Get a fresh session for each operation
|
||||||
s, err := sessionManager.GetSession(req)
|
s, err := sessionManager.GetSession(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -276,11 +276,11 @@ func TestParallelCacheOperations(t *testing.T) {
|
|||||||
var deleteCount int64
|
var deleteCount int64
|
||||||
|
|
||||||
// Start multiple goroutines performing cache operations
|
// Start multiple goroutines performing cache operations
|
||||||
for i := 0; i < numGoroutines; i++ {
|
for i := range numGoroutines {
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func(goroutineID int) {
|
go func(goroutineID int) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
for j := 0; j < operationsPerGoroutine; j++ {
|
for j := range operationsPerGoroutine {
|
||||||
key := fmt.Sprintf("key-%d-%d", goroutineID, j)
|
key := fmt.Sprintf("key-%d-%d", goroutineID, j)
|
||||||
value := fmt.Sprintf("value-%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
|
// Create an oversized token with large claims
|
||||||
largeClaim := strings.Repeat("x", 10000) // 10KB claim
|
largeClaim := strings.Repeat("x", 10000) // 10KB claim
|
||||||
oversizedClaims := map[string]interface{}{
|
oversizedClaims := map[string]any{
|
||||||
"iss": "https://test-issuer.com",
|
"iss": "https://test-issuer.com",
|
||||||
"aud": "test-client-id",
|
"aud": "test-client-id",
|
||||||
"exp": float64(time.Now().Add(1 * time.Hour).Unix()),
|
"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)
|
// Test extremely long token (beyond reasonable limits)
|
||||||
extremelyLongClaim := strings.Repeat("y", 100000) // 100KB claim
|
extremelyLongClaim := strings.Repeat("y", 100000) // 100KB claim
|
||||||
extremeClaims := map[string]interface{}{
|
extremeClaims := map[string]any{
|
||||||
"iss": "https://test-issuer.com",
|
"iss": "https://test-issuer.com",
|
||||||
"aud": "test-client-id",
|
"aud": "test-client-id",
|
||||||
"exp": float64(time.Now().Add(1 * time.Hour).Unix()),
|
"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
|
// Verify the system is still functional after malicious input
|
||||||
validToken, createErr := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]interface{}{
|
validToken, createErr := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]any{
|
||||||
"iss": "https://test-issuer.com",
|
"iss": "https://test-issuer.com",
|
||||||
"aud": "test-client-id",
|
"aud": "test-client-id",
|
||||||
"exp": float64(time.Now().Add(1 * time.Hour).Unix()),
|
"exp": float64(time.Now().Add(1 * time.Hour).Unix()),
|
||||||
@@ -609,7 +609,7 @@ func TestResourceLimits(t *testing.T) {
|
|||||||
defer cache.Close()
|
defer cache.Close()
|
||||||
|
|
||||||
// Try to overwhelm the cache
|
// Try to overwhelm the cache
|
||||||
for i := 0; i < 1000; i++ {
|
for i := range 1000 {
|
||||||
key := fmt.Sprintf("key-%d", i)
|
key := fmt.Sprintf("key-%d", i)
|
||||||
value := fmt.Sprintf("value-%d", i)
|
value := fmt.Sprintf("value-%d", i)
|
||||||
cache.Set(key, value, time.Minute)
|
cache.Set(key, value, time.Minute)
|
||||||
@@ -627,7 +627,7 @@ func TestResourceLimits(t *testing.T) {
|
|||||||
denied := 0
|
denied := 0
|
||||||
|
|
||||||
// Make many requests quickly
|
// Make many requests quickly
|
||||||
for i := 0; i < 100; i++ {
|
for range 100 {
|
||||||
if limiter.Allow() {
|
if limiter.Allow() {
|
||||||
allowed++
|
allowed++
|
||||||
} else {
|
} else {
|
||||||
@@ -658,7 +658,7 @@ func TestErrorRecoveryPatterns(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// System should handle corrupted cache gracefully
|
// System should handle corrupted cache gracefully
|
||||||
validToken, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]interface{}{
|
validToken, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]any{
|
||||||
"iss": "https://test-issuer.com",
|
"iss": "https://test-issuer.com",
|
||||||
"aud": "test-client-id",
|
"aud": "test-client-id",
|
||||||
"exp": float64(time.Now().Add(1 * time.Hour).Unix()),
|
"exp": float64(time.Now().Add(1 * time.Hour).Unix()),
|
||||||
@@ -684,7 +684,7 @@ func TestErrorRecoveryPatterns(t *testing.T) {
|
|||||||
ts.tOidc.tokenBlacklist.Set("corrupted-entry", "invalid-data", time.Hour)
|
ts.tOidc.tokenBlacklist.Set("corrupted-entry", "invalid-data", time.Hour)
|
||||||
|
|
||||||
// System should still function
|
// System should still function
|
||||||
validToken, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]interface{}{
|
validToken, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]any{
|
||||||
"iss": "https://test-issuer.com",
|
"iss": "https://test-issuer.com",
|
||||||
"aud": "test-client-id",
|
"aud": "test-client-id",
|
||||||
"exp": float64(time.Now().Add(1 * time.Hour).Unix()),
|
"exp": float64(time.Now().Add(1 * time.Hour).Unix()),
|
||||||
@@ -716,8 +716,8 @@ func TestPerformanceUnderLoad(t *testing.T) {
|
|||||||
// Create multiple valid tokens
|
// Create multiple valid tokens
|
||||||
const numTokens = 100
|
const numTokens = 100
|
||||||
tokens := make([]string, numTokens)
|
tokens := make([]string, numTokens)
|
||||||
for i := 0; i < numTokens; i++ {
|
for i := range numTokens {
|
||||||
token, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]interface{}{
|
token, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]any{
|
||||||
"iss": "https://test-issuer.com",
|
"iss": "https://test-issuer.com",
|
||||||
"aud": "test-client-id",
|
"aud": "test-client-id",
|
||||||
"exp": float64(time.Now().Add(1 * time.Hour).Unix()),
|
"exp": float64(time.Now().Add(1 * time.Hour).Unix()),
|
||||||
@@ -760,7 +760,7 @@ func TestPerformanceUnderLoad(t *testing.T) {
|
|||||||
const iterations = 1000
|
const iterations = 1000
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
|
|
||||||
for i := 0; i < iterations; i++ {
|
for i := range iterations {
|
||||||
tokenIndex := i % numTokens
|
tokenIndex := i % numTokens
|
||||||
err := tOidc.VerifyToken(tokens[tokenIndex])
|
err := tOidc.VerifyToken(tokens[tokenIndex])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
+22
-33
@@ -11,6 +11,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@@ -25,7 +26,7 @@ func TestJWTAlgorithmConfusionAttack(t *testing.T) {
|
|||||||
ts.Setup()
|
ts.Setup()
|
||||||
|
|
||||||
// Create a standard JWT with RS256 algorithm
|
// Create a standard JWT with RS256 algorithm
|
||||||
validRS256JWT, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]interface{}{
|
validRS256JWT, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]any{
|
||||||
"iss": "https://test-issuer.com",
|
"iss": "https://test-issuer.com",
|
||||||
"aud": "test-client-id",
|
"aud": "test-client-id",
|
||||||
"exp": float64(time.Now().Add(1 * time.Hour).Unix()),
|
"exp": float64(time.Now().Add(1 * time.Hour).Unix()),
|
||||||
@@ -51,7 +52,7 @@ func TestJWTAlgorithmConfusionAttack(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Parse header
|
// Parse header
|
||||||
var header map[string]interface{}
|
var header map[string]any
|
||||||
if err := json.Unmarshal(headerBytes, &header); err != nil {
|
if err := json.Unmarshal(headerBytes, &header); err != nil {
|
||||||
t.Fatalf("Failed to unmarshal header: %v", err)
|
t.Fatalf("Failed to unmarshal header: %v", err)
|
||||||
}
|
}
|
||||||
@@ -90,7 +91,7 @@ func TestJWTNoneAlgorithmAttack(t *testing.T) {
|
|||||||
ts.Setup()
|
ts.Setup()
|
||||||
|
|
||||||
// Create a standard JWT
|
// Create a standard JWT
|
||||||
validJWT, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]interface{}{
|
validJWT, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]any{
|
||||||
"iss": "https://test-issuer.com",
|
"iss": "https://test-issuer.com",
|
||||||
"aud": "test-client-id",
|
"aud": "test-client-id",
|
||||||
"exp": float64(time.Now().Add(1 * time.Hour).Unix()),
|
"exp": float64(time.Now().Add(1 * time.Hour).Unix()),
|
||||||
@@ -116,7 +117,7 @@ func TestJWTNoneAlgorithmAttack(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Parse header
|
// Parse header
|
||||||
var header map[string]interface{}
|
var header map[string]any
|
||||||
if err := json.Unmarshal(headerBytes, &header); err != nil {
|
if err := json.Unmarshal(headerBytes, &header); err != nil {
|
||||||
t.Fatalf("Failed to unmarshal header: %v", err)
|
t.Fatalf("Failed to unmarshal header: %v", err)
|
||||||
}
|
}
|
||||||
@@ -154,7 +155,7 @@ func TestJWTTokenTampering(t *testing.T) {
|
|||||||
ts.Setup()
|
ts.Setup()
|
||||||
|
|
||||||
// Create a standard JWT
|
// Create a standard JWT
|
||||||
validJWT, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]interface{}{
|
validJWT, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]any{
|
||||||
"iss": "https://test-issuer.com",
|
"iss": "https://test-issuer.com",
|
||||||
"aud": "test-client-id",
|
"aud": "test-client-id",
|
||||||
"exp": float64(time.Now().Add(1 * time.Hour).Unix()),
|
"exp": float64(time.Now().Add(1 * time.Hour).Unix()),
|
||||||
@@ -180,7 +181,7 @@ func TestJWTTokenTampering(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Parse claims
|
// Parse claims
|
||||||
var claims map[string]interface{}
|
var claims map[string]any
|
||||||
if err := json.Unmarshal(claimsBytes, &claims); err != nil {
|
if err := json.Unmarshal(claimsBytes, &claims); err != nil {
|
||||||
t.Fatalf("Failed to unmarshal claims: %v", err)
|
t.Fatalf("Failed to unmarshal claims: %v", err)
|
||||||
}
|
}
|
||||||
@@ -219,7 +220,7 @@ func TestJWTExpiredToken(t *testing.T) {
|
|||||||
ts.Setup()
|
ts.Setup()
|
||||||
|
|
||||||
// Create a JWT that is already expired
|
// Create a JWT that is already expired
|
||||||
expiredJWT, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]interface{}{
|
expiredJWT, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]any{
|
||||||
"iss": "https://test-issuer.com",
|
"iss": "https://test-issuer.com",
|
||||||
"aud": "test-client-id",
|
"aud": "test-client-id",
|
||||||
"exp": float64(time.Now().Add(-1 * time.Hour).Unix()), // Expired 1 hour ago
|
"exp": float64(time.Now().Add(-1 * time.Hour).Unix()), // Expired 1 hour ago
|
||||||
@@ -252,7 +253,7 @@ func TestJWTFutureToken(t *testing.T) {
|
|||||||
ts.Setup()
|
ts.Setup()
|
||||||
|
|
||||||
// Create a JWT with a future issuance time
|
// Create a JWT with a future issuance time
|
||||||
futureJWT, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]interface{}{
|
futureJWT, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]any{
|
||||||
"iss": "https://test-issuer.com",
|
"iss": "https://test-issuer.com",
|
||||||
"aud": "test-client-id",
|
"aud": "test-client-id",
|
||||||
"exp": float64(time.Now().Add(2 * time.Hour).Unix()),
|
"exp": float64(time.Now().Add(2 * time.Hour).Unix()),
|
||||||
@@ -315,7 +316,7 @@ func TestJWTReplayAttack(t *testing.T) {
|
|||||||
fixedJTI := "fixed-test-jti-for-replay-" + generateRandomString(8)
|
fixedJTI := "fixed-test-jti-for-replay-" + generateRandomString(8)
|
||||||
|
|
||||||
// Create a JWT with the fixed JTI
|
// Create a JWT with the fixed JTI
|
||||||
replayJWT, err := createTestJWT(rsaPrivateKey, "RS256", "test-key-id", map[string]interface{}{
|
replayJWT, err := createTestJWT(rsaPrivateKey, "RS256", "test-key-id", map[string]any{
|
||||||
"iss": "https://test-issuer.com",
|
"iss": "https://test-issuer.com",
|
||||||
"aud": "test-client-id",
|
"aud": "test-client-id",
|
||||||
"exp": float64(time.Now().Add(1 * time.Hour).Unix()),
|
"exp": float64(time.Now().Add(1 * time.Hour).Unix()),
|
||||||
@@ -422,7 +423,7 @@ func TestMissingClaims(t *testing.T) {
|
|||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
// Create standard claims
|
// Create standard claims
|
||||||
claims := map[string]interface{}{
|
claims := map[string]any{
|
||||||
"iss": "https://test-issuer.com",
|
"iss": "https://test-issuer.com",
|
||||||
"aud": "test-client-id",
|
"aud": "test-client-id",
|
||||||
"exp": float64(time.Now().Add(1 * time.Hour).Unix()),
|
"exp": float64(time.Now().Add(1 * time.Hour).Unix()),
|
||||||
@@ -598,13 +599,7 @@ func TestSessionFixationAttack(t *testing.T) {
|
|||||||
// - The response is unauthorized (401), OR
|
// - The response is unauthorized (401), OR
|
||||||
// - The token verification failed
|
// - The token verification failed
|
||||||
expectedCodes := []int{http.StatusFound, http.StatusUnauthorized, http.StatusForbidden}
|
expectedCodes := []int{http.StatusFound, http.StatusUnauthorized, http.StatusForbidden}
|
||||||
codeFound := false
|
codeFound := slices.Contains(expectedCodes, victimResp.Code)
|
||||||
for _, code := range expectedCodes {
|
|
||||||
if victimResp.Code == code {
|
|
||||||
codeFound = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !codeFound {
|
if !codeFound {
|
||||||
t.Errorf("Expected status code to be one of %v, but got %d", expectedCodes, victimResp.Code)
|
t.Errorf("Expected status code to be one of %v, but got %d", expectedCodes, victimResp.Code)
|
||||||
@@ -829,7 +824,7 @@ func TestTokenBlacklisting(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create a valid JWT
|
// Create a valid JWT
|
||||||
validJWT, err := createTestJWT(rsaPrivateKey, "RS256", "test-key-id", map[string]interface{}{
|
validJWT, err := createTestJWT(rsaPrivateKey, "RS256", "test-key-id", map[string]any{
|
||||||
"iss": "https://test-issuer.com",
|
"iss": "https://test-issuer.com",
|
||||||
"aud": "test-client-id",
|
"aud": "test-client-id",
|
||||||
"exp": float64(time.Now().Add(1 * time.Hour).Unix()),
|
"exp": float64(time.Now().Add(1 * time.Hour).Unix()),
|
||||||
@@ -919,7 +914,7 @@ func TestDifferentSigningAlgorithms(t *testing.T) {
|
|||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
// Define standard claims with unique JTI for each test
|
// Define standard claims with unique JTI for each test
|
||||||
standardClaims := map[string]interface{}{
|
standardClaims := map[string]any{
|
||||||
"iss": "https://test-issuer.com",
|
"iss": "https://test-issuer.com",
|
||||||
"aud": "test-client-id",
|
"aud": "test-client-id",
|
||||||
"exp": float64(time.Now().Add(1 * time.Hour).Unix()),
|
"exp": float64(time.Now().Add(1 * time.Hour).Unix()),
|
||||||
@@ -1022,9 +1017,9 @@ func TestDifferentSigningAlgorithms(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// createTestJWTWithECKey creates a JWT signed with an EC private key
|
// createTestJWTWithECKey creates a JWT signed with an EC private key
|
||||||
func createTestJWTWithECKey(privateKey *ecdsa.PrivateKey, alg, kid string, claims map[string]interface{}) (string, error) {
|
func createTestJWTWithECKey(privateKey *ecdsa.PrivateKey, alg, kid string, claims map[string]any) (string, error) {
|
||||||
// Create the header
|
// Create the header
|
||||||
header := map[string]interface{}{
|
header := map[string]any{
|
||||||
"alg": alg,
|
"alg": alg,
|
||||||
"typ": "JWT",
|
"typ": "JWT",
|
||||||
"kid": kid,
|
"kid": kid,
|
||||||
@@ -1281,7 +1276,7 @@ func TestRateLimiting(t *testing.T) {
|
|||||||
tOidc.tokenVerifier = tOidc
|
tOidc.tokenVerifier = tOidc
|
||||||
|
|
||||||
// Create a valid JWT token
|
// Create a valid JWT token
|
||||||
validJWT, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]interface{}{
|
validJWT, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]any{
|
||||||
"iss": "https://test-issuer.com",
|
"iss": "https://test-issuer.com",
|
||||||
"aud": "test-client-id",
|
"aud": "test-client-id",
|
||||||
"exp": float64(time.Now().Add(1 * time.Hour).Unix()),
|
"exp": float64(time.Now().Add(1 * time.Hour).Unix()),
|
||||||
@@ -1301,7 +1296,7 @@ func TestRateLimiting(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Second request should succeed
|
// Second request should succeed
|
||||||
validJWT2, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]interface{}{
|
validJWT2, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]any{
|
||||||
"iss": "https://test-issuer.com",
|
"iss": "https://test-issuer.com",
|
||||||
"aud": "test-client-id",
|
"aud": "test-client-id",
|
||||||
"exp": float64(time.Now().Add(1 * time.Hour).Unix()),
|
"exp": float64(time.Now().Add(1 * time.Hour).Unix()),
|
||||||
@@ -1320,7 +1315,7 @@ func TestRateLimiting(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Third request should be rate limited
|
// Third request should be rate limited
|
||||||
validJWT3, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]interface{}{
|
validJWT3, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]any{
|
||||||
"iss": "https://test-issuer.com",
|
"iss": "https://test-issuer.com",
|
||||||
"aud": "test-client-id",
|
"aud": "test-client-id",
|
||||||
"exp": float64(time.Now().Add(1 * time.Hour).Unix()),
|
"exp": float64(time.Now().Add(1 * time.Hour).Unix()),
|
||||||
@@ -1404,13 +1399,7 @@ func TestAuthorizationHeaderBypass(t *testing.T) {
|
|||||||
|
|
||||||
// Verify that the response is a redirect to authentication (302) or unauthorized (401)
|
// Verify that the response is a redirect to authentication (302) or unauthorized (401)
|
||||||
expectedCodes := []int{http.StatusFound, http.StatusUnauthorized}
|
expectedCodes := []int{http.StatusFound, http.StatusUnauthorized}
|
||||||
codeFound := false
|
codeFound := slices.Contains(expectedCodes, resp.Code)
|
||||||
for _, code := range expectedCodes {
|
|
||||||
if resp.Code == code {
|
|
||||||
codeFound = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !codeFound {
|
if !codeFound {
|
||||||
t.Errorf("Expected status code to be one of %v, but got %d", expectedCodes, resp.Code)
|
t.Errorf("Expected status code to be one of %v, but got %d", expectedCodes, resp.Code)
|
||||||
@@ -1423,7 +1412,7 @@ func TestEmptyAudience(t *testing.T) {
|
|||||||
ts.Setup()
|
ts.Setup()
|
||||||
|
|
||||||
// Create a JWT with empty audience
|
// Create a JWT with empty audience
|
||||||
emptyAudJWT, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]interface{}{
|
emptyAudJWT, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]any{
|
||||||
"iss": "https://test-issuer.com",
|
"iss": "https://test-issuer.com",
|
||||||
"aud": "", // Empty audience
|
"aud": "", // Empty audience
|
||||||
"exp": float64(time.Now().Add(1 * time.Hour).Unix()),
|
"exp": float64(time.Now().Add(1 * time.Hour).Unix()),
|
||||||
@@ -1456,7 +1445,7 @@ func TestEmptyIssuer(t *testing.T) {
|
|||||||
ts.Setup()
|
ts.Setup()
|
||||||
|
|
||||||
// Create a JWT with empty issuer
|
// Create a JWT with empty issuer
|
||||||
emptyIssJWT, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]interface{}{
|
emptyIssJWT, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]any{
|
||||||
"iss": "", // Empty issuer
|
"iss": "", // Empty issuer
|
||||||
"aud": "test-client-id",
|
"aud": "test-client-id",
|
||||||
"exp": float64(time.Now().Add(1 * time.Hour).Unix()),
|
"exp": float64(time.Now().Add(1 * time.Hour).Unix()),
|
||||||
|
|||||||
+18
-19
@@ -2,6 +2,7 @@ package traefikoidc
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"maps"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -12,14 +13,14 @@ import (
|
|||||||
|
|
||||||
// SecurityEvent represents a security-related event that should be logged and monitored
|
// SecurityEvent represents a security-related event that should be logged and monitored
|
||||||
type SecurityEvent struct {
|
type SecurityEvent struct {
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
Severity string `json:"severity"`
|
Severity string `json:"severity"`
|
||||||
Timestamp time.Time `json:"timestamp"`
|
Timestamp time.Time `json:"timestamp"`
|
||||||
ClientIP string `json:"client_ip"`
|
ClientIP string `json:"client_ip"`
|
||||||
UserAgent string `json:"user_agent"`
|
UserAgent string `json:"user_agent"`
|
||||||
RequestPath string `json:"request_path"`
|
RequestPath string `json:"request_path"`
|
||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
Details map[string]interface{} `json:"details,omitempty"`
|
Details map[string]any `json:"details,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// SecurityMonitor tracks security events and suspicious activity patterns
|
// SecurityMonitor tracks security events and suspicious activity patterns
|
||||||
@@ -145,7 +146,7 @@ func NewSuspiciousPatternDetector() *SuspiciousPatternDetector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// RecordAuthenticationFailure records an authentication failure event
|
// RecordAuthenticationFailure records an authentication failure event
|
||||||
func (sm *SecurityMonitor) RecordAuthenticationFailure(clientIP, userAgent, requestPath, reason string, details map[string]interface{}) {
|
func (sm *SecurityMonitor) RecordAuthenticationFailure(clientIP, userAgent, requestPath, reason string, details map[string]any) {
|
||||||
atomic.AddInt64(&sm.authFailures, 1)
|
atomic.AddInt64(&sm.authFailures, 1)
|
||||||
|
|
||||||
event := SecurityEvent{
|
event := SecurityEvent{
|
||||||
@@ -167,7 +168,7 @@ func (sm *SecurityMonitor) RecordAuthenticationFailure(clientIP, userAgent, requ
|
|||||||
func (sm *SecurityMonitor) RecordTokenValidationFailure(clientIP, userAgent, requestPath, reason string, tokenPrefix string) {
|
func (sm *SecurityMonitor) RecordTokenValidationFailure(clientIP, userAgent, requestPath, reason string, tokenPrefix string) {
|
||||||
atomic.AddInt64(&sm.tokenValidationFails, 1)
|
atomic.AddInt64(&sm.tokenValidationFails, 1)
|
||||||
|
|
||||||
details := map[string]interface{}{
|
details := map[string]any{
|
||||||
"reason": reason,
|
"reason": reason,
|
||||||
}
|
}
|
||||||
if tokenPrefix != "" {
|
if tokenPrefix != "" {
|
||||||
@@ -201,7 +202,7 @@ func (sm *SecurityMonitor) RecordRateLimitHit(clientIP, userAgent, requestPath s
|
|||||||
UserAgent: userAgent,
|
UserAgent: userAgent,
|
||||||
RequestPath: requestPath,
|
RequestPath: requestPath,
|
||||||
Message: "Rate limit exceeded",
|
Message: "Rate limit exceeded",
|
||||||
Details: map[string]interface{}{
|
Details: map[string]any{
|
||||||
"limit_type": "token_verification",
|
"limit_type": "token_verification",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -211,7 +212,7 @@ func (sm *SecurityMonitor) RecordRateLimitHit(clientIP, userAgent, requestPath s
|
|||||||
}
|
}
|
||||||
|
|
||||||
// RecordSuspiciousActivity records suspicious activity that doesn't fit other categories
|
// RecordSuspiciousActivity records suspicious activity that doesn't fit other categories
|
||||||
func (sm *SecurityMonitor) RecordSuspiciousActivity(clientIP, userAgent, requestPath, activityType, description string, details map[string]interface{}) {
|
func (sm *SecurityMonitor) RecordSuspiciousActivity(clientIP, userAgent, requestPath, activityType, description string, details map[string]any) {
|
||||||
atomic.AddInt64(&sm.suspiciousRequests, 1)
|
atomic.AddInt64(&sm.suspiciousRequests, 1)
|
||||||
|
|
||||||
event := SecurityEvent{
|
event := SecurityEvent{
|
||||||
@@ -266,7 +267,7 @@ func (sm *SecurityMonitor) recordIPFailure(clientIP, failureType string) {
|
|||||||
Timestamp: time.Now(),
|
Timestamp: time.Now(),
|
||||||
ClientIP: clientIP,
|
ClientIP: clientIP,
|
||||||
Message: fmt.Sprintf("IP blocked due to %d failures in %d minutes", tracker.FailureCount, sm.config.FailureWindowMinutes),
|
Message: fmt.Sprintf("IP blocked due to %d failures in %d minutes", tracker.FailureCount, sm.config.FailureWindowMinutes),
|
||||||
Details: map[string]interface{}{
|
Details: map[string]any{
|
||||||
"failure_count": tracker.FailureCount,
|
"failure_count": tracker.FailureCount,
|
||||||
"failure_types": tracker.FailureTypes,
|
"failure_types": tracker.FailureTypes,
|
||||||
"blocked_until": tracker.BlockedUntil,
|
"blocked_until": tracker.BlockedUntil,
|
||||||
@@ -319,7 +320,7 @@ func (sm *SecurityMonitor) processSecurityEvent(event SecurityEvent) {
|
|||||||
Severity: "high",
|
Severity: "high",
|
||||||
Timestamp: time.Now(),
|
Timestamp: time.Now(),
|
||||||
Message: fmt.Sprintf("Suspicious pattern detected: %s", pattern),
|
Message: fmt.Sprintf("Suspicious pattern detected: %s", pattern),
|
||||||
Details: map[string]interface{}{
|
Details: map[string]any{
|
||||||
"pattern_type": pattern,
|
"pattern_type": pattern,
|
||||||
"trigger_event": event,
|
"trigger_event": event,
|
||||||
},
|
},
|
||||||
@@ -352,7 +353,7 @@ func (sm *SecurityMonitor) AddEventHandler(handler SecurityEventHandler) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetSecurityMetrics returns current security metrics
|
// GetSecurityMetrics returns current security metrics
|
||||||
func (sm *SecurityMonitor) GetSecurityMetrics() map[string]interface{} {
|
func (sm *SecurityMonitor) GetSecurityMetrics() map[string]any {
|
||||||
sm.ipMutex.RLock()
|
sm.ipMutex.RLock()
|
||||||
defer sm.ipMutex.RUnlock()
|
defer sm.ipMutex.RUnlock()
|
||||||
|
|
||||||
@@ -367,7 +368,7 @@ func (sm *SecurityMonitor) GetSecurityMetrics() map[string]interface{} {
|
|||||||
tracker.mutex.RUnlock()
|
tracker.mutex.RUnlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
return map[string]interface{}{
|
return map[string]any{
|
||||||
"auth_failures": atomic.LoadInt64(&sm.authFailures),
|
"auth_failures": atomic.LoadInt64(&sm.authFailures),
|
||||||
"token_validation_fails": atomic.LoadInt64(&sm.tokenValidationFails),
|
"token_validation_fails": atomic.LoadInt64(&sm.tokenValidationFails),
|
||||||
"rate_limit_hits": atomic.LoadInt64(&sm.rateLimitHits),
|
"rate_limit_hits": atomic.LoadInt64(&sm.rateLimitHits),
|
||||||
@@ -565,8 +566,6 @@ func (h *MetricsSecurityEventHandler) GetMetrics() map[string]int64 {
|
|||||||
defer h.mutex.RUnlock()
|
defer h.mutex.RUnlock()
|
||||||
|
|
||||||
metrics := make(map[string]int64)
|
metrics := make(map[string]int64)
|
||||||
for k, v := range h.eventCounts {
|
maps.Copy(metrics, h.eventCounts)
|
||||||
metrics[k] = v
|
|
||||||
}
|
|
||||||
return metrics
|
return metrics
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package traefikoidc
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@@ -60,7 +61,7 @@ func TestSecurityMonitor(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Suspicious activity", func(t *testing.T) {
|
t.Run("Suspicious activity", func(t *testing.T) {
|
||||||
details := map[string]interface{}{"pattern": "unusual"}
|
details := map[string]any{"pattern": "unusual"}
|
||||||
monitor.RecordSuspiciousActivity("192.168.1.5", "test-agent", "/admin", "unusual pattern", "high frequency requests", details)
|
monitor.RecordSuspiciousActivity("192.168.1.5", "test-agent", "/admin", "unusual pattern", "high frequency requests", details)
|
||||||
|
|
||||||
metrics := monitor.GetSecurityMetrics()
|
metrics := monitor.GetSecurityMetrics()
|
||||||
@@ -86,7 +87,7 @@ func TestSuspiciousPatternDetector(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("Add events and detect patterns", func(t *testing.T) {
|
t.Run("Add events and detect patterns", func(t *testing.T) {
|
||||||
// Add multiple events from same IP
|
// Add multiple events from same IP
|
||||||
for i := 0; i < 10; i++ {
|
for range 10 {
|
||||||
event := SecurityEvent{
|
event := SecurityEvent{
|
||||||
Type: "authentication_failure",
|
Type: "authentication_failure",
|
||||||
ClientIP: "192.168.1.100",
|
ClientIP: "192.168.1.100",
|
||||||
@@ -97,13 +98,7 @@ func TestSuspiciousPatternDetector(t *testing.T) {
|
|||||||
|
|
||||||
patterns := detector.DetectSuspiciousPatterns()
|
patterns := detector.DetectSuspiciousPatterns()
|
||||||
|
|
||||||
found := false
|
found := slices.Contains(patterns, "rapid_failures_from_ip_192.168.1.100")
|
||||||
for _, pattern := range patterns {
|
|
||||||
if pattern == "rapid_failures_from_ip_192.168.1.100" {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !found {
|
if !found {
|
||||||
t.Error("Expected to detect rapid failure pattern")
|
t.Error("Expected to detect rapid failure pattern")
|
||||||
}
|
}
|
||||||
@@ -111,7 +106,7 @@ func TestSuspiciousPatternDetector(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("Detect distributed attack pattern", func(t *testing.T) {
|
t.Run("Detect distributed attack pattern", func(t *testing.T) {
|
||||||
// Add failures from many different IPs
|
// Add failures from many different IPs
|
||||||
for i := 0; i < 25; i++ {
|
for i := range 25 {
|
||||||
event := SecurityEvent{
|
event := SecurityEvent{
|
||||||
Type: "authentication_failure",
|
Type: "authentication_failure",
|
||||||
ClientIP: "192.168.1." + strconv.Itoa(100+i),
|
ClientIP: "192.168.1." + strconv.Itoa(100+i),
|
||||||
@@ -122,13 +117,7 @@ func TestSuspiciousPatternDetector(t *testing.T) {
|
|||||||
|
|
||||||
patterns := detector.DetectSuspiciousPatterns()
|
patterns := detector.DetectSuspiciousPatterns()
|
||||||
|
|
||||||
found := false
|
found := slices.Contains(patterns, "distributed_attack_pattern")
|
||||||
for _, pattern := range patterns {
|
|
||||||
if pattern == "distributed_attack_pattern" {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !found {
|
if !found {
|
||||||
t.Error("Expected to detect distributed attack pattern")
|
t.Error("Expected to detect distributed attack pattern")
|
||||||
}
|
}
|
||||||
@@ -317,7 +306,7 @@ func TestSecurityEventTypes(t *testing.T) {
|
|||||||
monitor.RecordTokenValidationFailure("192.168.1.200", "test-agent", "/api", "expired token", "abc123")
|
monitor.RecordTokenValidationFailure("192.168.1.200", "test-agent", "/api", "expired token", "abc123")
|
||||||
monitor.RecordRateLimitHit("192.168.1.200", "test-agent", "/api")
|
monitor.RecordRateLimitHit("192.168.1.200", "test-agent", "/api")
|
||||||
|
|
||||||
details := map[string]interface{}{"pattern": "test"}
|
details := map[string]any{"pattern": "test"}
|
||||||
monitor.RecordSuspiciousActivity("192.168.1.200", "test-agent", "/admin", "unusual pattern", "multiple failed logins", details)
|
monitor.RecordSuspiciousActivity("192.168.1.200", "test-agent", "/admin", "unusual pattern", "multiple failed logins", details)
|
||||||
|
|
||||||
metrics := monitor.GetSecurityMetrics()
|
metrics := monitor.GetSecurityMetrics()
|
||||||
|
|||||||
+11
-11
@@ -245,7 +245,7 @@ func NewSessionManager(encryptionKey string, forceHTTPS bool, logger *Logger) (*
|
|||||||
logger: logger,
|
logger: logger,
|
||||||
}
|
}
|
||||||
|
|
||||||
sm.sessionPool.New = func() interface{} {
|
sm.sessionPool.New = func() any {
|
||||||
sd := &SessionData{
|
sd := &SessionData{
|
||||||
manager: sm,
|
manager: sm,
|
||||||
accessTokenChunks: make(map[int]*sessions.Session),
|
accessTokenChunks: make(map[int]*sessions.Session),
|
||||||
@@ -683,7 +683,7 @@ func (sd *SessionData) SetAuthenticated(value bool) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
maxRetries := 5
|
maxRetries := 5
|
||||||
for retry := 0; retry < maxRetries; retry++ {
|
for retry := range maxRetries {
|
||||||
if sd.mainSession.ID != id {
|
if sd.mainSession.ID != id {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -870,7 +870,7 @@ func (sd *SessionData) getAccessTokenUnsafe() string {
|
|||||||
totalSize := 0
|
totalSize := 0
|
||||||
|
|
||||||
// Sequential chunk validation with gap detection
|
// Sequential chunk validation with gap detection
|
||||||
for i := 0; i < expectedChunkCount; i++ {
|
for i := range expectedChunkCount {
|
||||||
session, ok := sd.accessTokenChunks[i]
|
session, ok := sd.accessTokenChunks[i]
|
||||||
if !ok {
|
if !ok {
|
||||||
sd.manager.logger.Errorf("CRITICAL: Access token chunk %d missing - gap in sequence detected", i)
|
sd.manager.logger.Errorf("CRITICAL: Access token chunk %d missing - gap in sequence detected", i)
|
||||||
@@ -1206,7 +1206,7 @@ func (sd *SessionData) GetRefreshToken() string {
|
|||||||
totalSize := 0
|
totalSize := 0
|
||||||
|
|
||||||
// Sequential chunk validation with gap detection
|
// Sequential chunk validation with gap detection
|
||||||
for i := 0; i < expectedChunkCount; i++ {
|
for i := range expectedChunkCount {
|
||||||
session, ok := sd.refreshTokenChunks[i]
|
session, ok := sd.refreshTokenChunks[i]
|
||||||
if !ok {
|
if !ok {
|
||||||
sd.manager.logger.Errorf("CRITICAL: Refresh token chunk %d missing - gap in sequence detected", i)
|
sd.manager.logger.Errorf("CRITICAL: Refresh token chunk %d missing - gap in sequence detected", i)
|
||||||
@@ -1461,7 +1461,7 @@ func (sd *SessionData) expireAccessTokenChunksEnhanced(w http.ResponseWriter) {
|
|||||||
const maxChunkSearchLimit = 50 // Limit search to prevent infinite loops from corrupted state
|
const maxChunkSearchLimit = 50 // Limit search to prevent infinite loops from corrupted state
|
||||||
orphanedChunks := 0
|
orphanedChunks := 0
|
||||||
|
|
||||||
for i := 0; i < maxChunkSearchLimit; i++ {
|
for i := range maxChunkSearchLimit {
|
||||||
sessionName := fmt.Sprintf("%s_%d", accessTokenCookie, i)
|
sessionName := fmt.Sprintf("%s_%d", accessTokenCookie, i)
|
||||||
session, err := sd.manager.store.Get(sd.request, sessionName)
|
session, err := sd.manager.store.Get(sd.request, sessionName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1491,7 +1491,7 @@ func (sd *SessionData) expireAccessTokenChunksEnhanced(w http.ResponseWriter) {
|
|||||||
|
|
||||||
// Expire the chunk regardless of orphan status
|
// Expire the chunk regardless of orphan status
|
||||||
session.Options.MaxAge = -1
|
session.Options.MaxAge = -1
|
||||||
session.Values = make(map[interface{}]interface{})
|
session.Values = make(map[any]any)
|
||||||
if w != nil {
|
if w != nil {
|
||||||
if err := session.Save(sd.request, w); err != 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)
|
sd.manager.logger.Errorf("failed to save expired access token chunk %d: %v", i, err)
|
||||||
@@ -1514,7 +1514,7 @@ func (sd *SessionData) expireRefreshTokenChunksEnhanced(w http.ResponseWriter) {
|
|||||||
const maxChunkSearchLimit = 50 // Limit search to prevent infinite loops from corrupted state
|
const maxChunkSearchLimit = 50 // Limit search to prevent infinite loops from corrupted state
|
||||||
orphanedChunks := 0
|
orphanedChunks := 0
|
||||||
|
|
||||||
for i := 0; i < maxChunkSearchLimit; i++ {
|
for i := range maxChunkSearchLimit {
|
||||||
sessionName := fmt.Sprintf("%s_%d", refreshTokenCookie, i)
|
sessionName := fmt.Sprintf("%s_%d", refreshTokenCookie, i)
|
||||||
session, err := sd.manager.store.Get(sd.request, sessionName)
|
session, err := sd.manager.store.Get(sd.request, sessionName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1544,7 +1544,7 @@ func (sd *SessionData) expireRefreshTokenChunksEnhanced(w http.ResponseWriter) {
|
|||||||
|
|
||||||
// Expire the chunk regardless of orphan status
|
// Expire the chunk regardless of orphan status
|
||||||
session.Options.MaxAge = -1
|
session.Options.MaxAge = -1
|
||||||
session.Values = make(map[interface{}]interface{})
|
session.Values = make(map[any]any)
|
||||||
if w != nil {
|
if w != nil {
|
||||||
if err := session.Save(sd.request, w); err != 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)
|
sd.manager.logger.Errorf("failed to save expired refresh token chunk %d: %v", i, err)
|
||||||
@@ -1567,7 +1567,7 @@ func (sd *SessionData) expireIDTokenChunksEnhanced(w http.ResponseWriter) {
|
|||||||
const maxChunkSearchLimit = 50 // Limit search to prevent infinite loops from corrupted state
|
const maxChunkSearchLimit = 50 // Limit search to prevent infinite loops from corrupted state
|
||||||
orphanedChunks := 0
|
orphanedChunks := 0
|
||||||
|
|
||||||
for i := 0; i < maxChunkSearchLimit; i++ {
|
for i := range maxChunkSearchLimit {
|
||||||
sessionName := fmt.Sprintf("%s_%d", idTokenCookie, i)
|
sessionName := fmt.Sprintf("%s_%d", idTokenCookie, i)
|
||||||
session, err := sd.manager.store.Get(sd.request, sessionName)
|
session, err := sd.manager.store.Get(sd.request, sessionName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1597,7 +1597,7 @@ func (sd *SessionData) expireIDTokenChunksEnhanced(w http.ResponseWriter) {
|
|||||||
|
|
||||||
// Expire the chunk regardless of orphan status
|
// Expire the chunk regardless of orphan status
|
||||||
session.Options.MaxAge = -1
|
session.Options.MaxAge = -1
|
||||||
session.Values = make(map[interface{}]interface{})
|
session.Values = make(map[any]any)
|
||||||
if w != nil {
|
if w != nil {
|
||||||
if err := session.Save(sd.request, w); err != 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)
|
sd.manager.logger.Errorf("failed to save expired ID token chunk %d: %v", i, err)
|
||||||
@@ -1897,7 +1897,7 @@ func (sd *SessionData) getIDTokenUnsafe() string {
|
|||||||
totalSize := 0
|
totalSize := 0
|
||||||
|
|
||||||
// Sequential chunk validation with gap detection
|
// Sequential chunk validation with gap detection
|
||||||
for i := 0; i < expectedChunkCount; i++ {
|
for i := range expectedChunkCount {
|
||||||
session, ok := sd.idTokenChunks[i]
|
session, ok := sd.idTokenChunks[i]
|
||||||
if !ok {
|
if !ok {
|
||||||
sd.manager.logger.Errorf("CRITICAL: ID token chunk %d missing - gap in sequence detected", i)
|
sd.manager.logger.Errorf("CRITICAL: ID token chunk %d missing - gap in sequence detected", i)
|
||||||
|
|||||||
+6
-6
@@ -148,7 +148,7 @@ func getPooledObjects(sm *SessionManager) int {
|
|||||||
var objects []*SessionData
|
var objects []*SessionData
|
||||||
maxAttempts := 100 // Safety limit to prevent infinite loops
|
maxAttempts := 100 // Safety limit to prevent infinite loops
|
||||||
|
|
||||||
for i := 0; i < maxAttempts; i++ {
|
for range maxAttempts {
|
||||||
obj := sm.sessionPool.Get()
|
obj := sm.sessionPool.Get()
|
||||||
if obj == nil {
|
if obj == nil {
|
||||||
break
|
break
|
||||||
@@ -195,7 +195,7 @@ func TestSessionObjectTracking(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create and discard 5 sessions
|
// Create and discard 5 sessions
|
||||||
for i := 0; i < 5; i++ {
|
for range 5 {
|
||||||
session, err := sm.GetSession(req)
|
session, err := sm.GetSession(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("GetSession failed: %v", err)
|
t.Fatalf("GetSession failed: %v", err)
|
||||||
@@ -425,7 +425,7 @@ func TestTokenChunkingCorruptionResistance(t *testing.T) {
|
|||||||
|
|
||||||
// Create a large token that will be chunked
|
// Create a large token that will be chunked
|
||||||
largeToken := "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9." +
|
largeToken := "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9." +
|
||||||
base64.RawURLEncoding.EncodeToString([]byte(fmt.Sprintf(`{"sub":"test","data":"%s"}`, strings.Repeat("A", 5000)))) +
|
base64.RawURLEncoding.EncodeToString(fmt.Appendf(nil, `{"sub":"test","data":"%s"}`, strings.Repeat("A", 5000))) +
|
||||||
".signature"
|
".signature"
|
||||||
|
|
||||||
req := httptest.NewRequest("GET", "http://example.com/foo", nil)
|
req := httptest.NewRequest("GET", "http://example.com/foo", nil)
|
||||||
@@ -609,11 +609,11 @@ func TestConcurrentTokenOperations(t *testing.T) {
|
|||||||
// Test concurrent access and refresh token operations
|
// Test concurrent access and refresh token operations
|
||||||
done := make(chan bool, numGoroutines)
|
done := make(chan bool, numGoroutines)
|
||||||
|
|
||||||
for i := 0; i < numGoroutines; i++ {
|
for i := range numGoroutines {
|
||||||
go func(id int) {
|
go func(id int) {
|
||||||
defer func() { done <- true }()
|
defer func() { done <- true }()
|
||||||
|
|
||||||
for j := 0; j < numOperations; j++ {
|
for j := range numOperations {
|
||||||
// Create unique tokens for each goroutine/operation
|
// Create unique tokens for each goroutine/operation
|
||||||
accessToken := ValidAccessToken
|
accessToken := ValidAccessToken
|
||||||
refreshToken := fmt.Sprintf("refresh_token_%d_%d", id, j)
|
refreshToken := fmt.Sprintf("refresh_token_%d_%d", id, j)
|
||||||
@@ -637,7 +637,7 @@ func TestConcurrentTokenOperations(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Wait for all goroutines to complete
|
// Wait for all goroutines to complete
|
||||||
for i := 0; i < numGoroutines; i++ {
|
for range numGoroutines {
|
||||||
<-done
|
<-done
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-6
@@ -481,7 +481,7 @@ func NewLogger(logLevel string) *Logger {
|
|||||||
// Parameters:
|
// Parameters:
|
||||||
// - format: The format string (as in fmt.Printf).
|
// - format: The format string (as in fmt.Printf).
|
||||||
// - args: The arguments for the format string.
|
// - args: The arguments for the format string.
|
||||||
func (l *Logger) Info(format string, args ...interface{}) {
|
func (l *Logger) Info(format string, args ...any) {
|
||||||
l.logInfo.Printf(format, args...)
|
l.logInfo.Printf(format, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -491,7 +491,7 @@ func (l *Logger) Info(format string, args ...interface{}) {
|
|||||||
// Parameters:
|
// Parameters:
|
||||||
// - format: The format string (as in fmt.Printf).
|
// - format: The format string (as in fmt.Printf).
|
||||||
// - args: The arguments for the format string.
|
// - args: The arguments for the format string.
|
||||||
func (l *Logger) Debug(format string, args ...interface{}) {
|
func (l *Logger) Debug(format string, args ...any) {
|
||||||
l.logDebug.Printf(format, args...)
|
l.logDebug.Printf(format, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -501,7 +501,7 @@ func (l *Logger) Debug(format string, args ...interface{}) {
|
|||||||
// Parameters:
|
// Parameters:
|
||||||
// - format: The format string (as in fmt.Printf).
|
// - format: The format string (as in fmt.Printf).
|
||||||
// - args: The arguments for the format string.
|
// - args: The arguments for the format string.
|
||||||
func (l *Logger) Error(format string, args ...interface{}) {
|
func (l *Logger) Error(format string, args ...any) {
|
||||||
l.logError.Printf(format, args...)
|
l.logError.Printf(format, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -512,7 +512,7 @@ func (l *Logger) Error(format string, args ...interface{}) {
|
|||||||
// Parameters:
|
// Parameters:
|
||||||
// - format: The format string (as in fmt.Printf).
|
// - format: The format string (as in fmt.Printf).
|
||||||
// - args: The arguments for the format string.
|
// - args: The arguments for the format string.
|
||||||
func (l *Logger) Infof(format string, args ...interface{}) {
|
func (l *Logger) Infof(format string, args ...any) {
|
||||||
l.logInfo.Printf(format, args...)
|
l.logInfo.Printf(format, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -523,7 +523,7 @@ func (l *Logger) Infof(format string, args ...interface{}) {
|
|||||||
// Parameters:
|
// Parameters:
|
||||||
// - format: The format string (as in fmt.Printf).
|
// - format: The format string (as in fmt.Printf).
|
||||||
// - args: The arguments for the format string.
|
// - args: The arguments for the format string.
|
||||||
func (l *Logger) Debugf(format string, args ...interface{}) {
|
func (l *Logger) Debugf(format string, args ...any) {
|
||||||
l.logDebug.Printf(format, args...)
|
l.logDebug.Printf(format, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -534,7 +534,7 @@ func (l *Logger) Debugf(format string, args ...interface{}) {
|
|||||||
// Parameters:
|
// Parameters:
|
||||||
// - format: The format string (as in fmt.Printf).
|
// - format: The format string (as in fmt.Printf).
|
||||||
// - args: The arguments for the format string.
|
// - args: The arguments for the format string.
|
||||||
func (l *Logger) Errorf(format string, args ...interface{}) {
|
func (l *Logger) Errorf(format string, args ...any) {
|
||||||
l.logError.Printf(format, args...)
|
l.logError.Printf(format, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,15 +11,15 @@ func TestTemplateExecution(t *testing.T) {
|
|||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
templateText string
|
templateText string
|
||||||
data map[string]interface{}
|
data map[string]any
|
||||||
expectedValue string
|
expectedValue string
|
||||||
expectError bool
|
expectError bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "String Claim",
|
name: "String Claim",
|
||||||
templateText: "{{.Claims.email}}",
|
templateText: "{{.Claims.email}}",
|
||||||
data: map[string]interface{}{
|
data: map[string]any{
|
||||||
"Claims": map[string]interface{}{
|
"Claims": map[string]any{
|
||||||
"email": "[email protected]",
|
"email": "[email protected]",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -29,8 +29,8 @@ func TestTemplateExecution(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "Number Claim",
|
name: "Number Claim",
|
||||||
templateText: "{{.Claims.age}}",
|
templateText: "{{.Claims.age}}",
|
||||||
data: map[string]interface{}{
|
data: map[string]any{
|
||||||
"Claims": map[string]interface{}{
|
"Claims": map[string]any{
|
||||||
"age": 30,
|
"age": 30,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -40,8 +40,8 @@ func TestTemplateExecution(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "Boolean Claim",
|
name: "Boolean Claim",
|
||||||
templateText: "{{.Claims.admin}}",
|
templateText: "{{.Claims.admin}}",
|
||||||
data: map[string]interface{}{
|
data: map[string]any{
|
||||||
"Claims": map[string]interface{}{
|
"Claims": map[string]any{
|
||||||
"admin": true,
|
"admin": true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -51,8 +51,8 @@ func TestTemplateExecution(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "Array Claim",
|
name: "Array Claim",
|
||||||
templateText: "{{index .Claims.roles 0}}",
|
templateText: "{{index .Claims.roles 0}}",
|
||||||
data: map[string]interface{}{
|
data: map[string]any{
|
||||||
"Claims": map[string]interface{}{
|
"Claims": map[string]any{
|
||||||
"roles": []string{"admin", "user"},
|
"roles": []string{"admin", "user"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -62,9 +62,9 @@ func TestTemplateExecution(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "Nested Object Claim",
|
name: "Nested Object Claim",
|
||||||
templateText: "{{.Claims.user.name}}",
|
templateText: "{{.Claims.user.name}}",
|
||||||
data: map[string]interface{}{
|
data: map[string]any{
|
||||||
"Claims": map[string]interface{}{
|
"Claims": map[string]any{
|
||||||
"user": map[string]interface{}{
|
"user": map[string]any{
|
||||||
"name": "John Doe",
|
"name": "John Doe",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -75,7 +75,7 @@ func TestTemplateExecution(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "Access Token",
|
name: "Access Token",
|
||||||
templateText: "Bearer {{.AccessToken}}",
|
templateText: "Bearer {{.AccessToken}}",
|
||||||
data: map[string]interface{}{
|
data: map[string]any{
|
||||||
"AccessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.Et9HFtf9R3GEMA0IICOfFMVXY7kkTX1wr4qCyhIf58U",
|
"AccessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.Et9HFtf9R3GEMA0IICOfFMVXY7kkTX1wr4qCyhIf58U",
|
||||||
},
|
},
|
||||||
expectedValue: "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.Et9HFtf9R3GEMA0IICOfFMVXY7kkTX1wr4qCyhIf58U",
|
expectedValue: "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.Et9HFtf9R3GEMA0IICOfFMVXY7kkTX1wr4qCyhIf58U",
|
||||||
@@ -84,7 +84,7 @@ func TestTemplateExecution(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "ID Token",
|
name: "ID Token",
|
||||||
templateText: "{{.IdToken}}",
|
templateText: "{{.IdToken}}",
|
||||||
data: map[string]interface{}{
|
data: map[string]any{
|
||||||
"IdToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.Et9HFtf9R3GEMA0IICOfFMVXY7kkTX1wr4qCyhIf58U",
|
"IdToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.Et9HFtf9R3GEMA0IICOfFMVXY7kkTX1wr4qCyhIf58U",
|
||||||
},
|
},
|
||||||
expectedValue: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.Et9HFtf9R3GEMA0IICOfFMVXY7kkTX1wr4qCyhIf58U",
|
expectedValue: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.Et9HFtf9R3GEMA0IICOfFMVXY7kkTX1wr4qCyhIf58U",
|
||||||
@@ -93,7 +93,7 @@ func TestTemplateExecution(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "Refresh Token",
|
name: "Refresh Token",
|
||||||
templateText: "{{.RefreshToken}}",
|
templateText: "{{.RefreshToken}}",
|
||||||
data: map[string]interface{}{
|
data: map[string]any{
|
||||||
"RefreshToken": "refresh-token-value",
|
"RefreshToken": "refresh-token-value",
|
||||||
},
|
},
|
||||||
expectedValue: "refresh-token-value",
|
expectedValue: "refresh-token-value",
|
||||||
@@ -102,8 +102,8 @@ func TestTemplateExecution(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "Conditional Template",
|
name: "Conditional Template",
|
||||||
templateText: "{{if .Claims.admin}}Admin User{{else}}Regular User{{end}}",
|
templateText: "{{if .Claims.admin}}Admin User{{else}}Regular User{{end}}",
|
||||||
data: map[string]interface{}{
|
data: map[string]any{
|
||||||
"Claims": map[string]interface{}{
|
"Claims": map[string]any{
|
||||||
"admin": true,
|
"admin": true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -113,8 +113,8 @@ func TestTemplateExecution(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "Multiple Claims",
|
name: "Multiple Claims",
|
||||||
templateText: "{{.Claims.firstName}} {{.Claims.lastName}} <{{.Claims.email}}>",
|
templateText: "{{.Claims.firstName}} {{.Claims.lastName}} <{{.Claims.email}}>",
|
||||||
data: map[string]interface{}{
|
data: map[string]any{
|
||||||
"Claims": map[string]interface{}{
|
"Claims": map[string]any{
|
||||||
"firstName": "John",
|
"firstName": "John",
|
||||||
"lastName": "Doe",
|
"lastName": "Doe",
|
||||||
"email": "[email protected]",
|
"email": "[email protected]",
|
||||||
@@ -126,8 +126,8 @@ func TestTemplateExecution(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "Missing Claim",
|
name: "Missing Claim",
|
||||||
templateText: "{{.Claims.missing}}",
|
templateText: "{{.Claims.missing}}",
|
||||||
data: map[string]interface{}{
|
data: map[string]any{
|
||||||
"Claims": map[string]interface{}{},
|
"Claims": map[string]any{},
|
||||||
},
|
},
|
||||||
expectedValue: "<no value>",
|
expectedValue: "<no value>",
|
||||||
expectError: false, // Go templates don't error on missing values
|
expectError: false, // Go templates don't error on missing values
|
||||||
@@ -135,8 +135,8 @@ func TestTemplateExecution(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "Invalid Template Syntax",
|
name: "Invalid Template Syntax",
|
||||||
templateText: "{{.Claims.email",
|
templateText: "{{.Claims.email",
|
||||||
data: map[string]interface{}{
|
data: map[string]any{
|
||||||
"Claims": map[string]interface{}{
|
"Claims": map[string]any{
|
||||||
"email": "[email protected]",
|
"email": "[email protected]",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -181,7 +181,7 @@ func TestTemplateExecutionContext(t *testing.T) {
|
|||||||
AccessToken string
|
AccessToken string
|
||||||
IdToken string
|
IdToken string
|
||||||
RefreshToken string
|
RefreshToken string
|
||||||
Claims map[string]interface{}
|
Claims map[string]any
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test cases
|
// Test cases
|
||||||
@@ -197,7 +197,7 @@ func TestTemplateExecutionContext(t *testing.T) {
|
|||||||
data: templateData{
|
data: templateData{
|
||||||
AccessToken: "access-token-value",
|
AccessToken: "access-token-value",
|
||||||
IdToken: "id-token-value", // Now these should be distinct values
|
IdToken: "id-token-value", // Now these should be distinct values
|
||||||
Claims: map[string]interface{}{},
|
Claims: map[string]any{},
|
||||||
},
|
},
|
||||||
expectedValue: "Access: access-token-value ID: id-token-value",
|
expectedValue: "Access: access-token-value ID: id-token-value",
|
||||||
},
|
},
|
||||||
@@ -207,7 +207,7 @@ func TestTemplateExecutionContext(t *testing.T) {
|
|||||||
data: templateData{
|
data: templateData{
|
||||||
AccessToken: "access-token",
|
AccessToken: "access-token",
|
||||||
IdToken: "access-token",
|
IdToken: "access-token",
|
||||||
Claims: map[string]interface{}{
|
Claims: map[string]any{
|
||||||
"sub": "user123",
|
"sub": "user123",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package traefikoidc
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"maps"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -22,7 +23,7 @@ func TestTemplatedHeadersIntegration(t *testing.T) {
|
|||||||
name string
|
name string
|
||||||
headers []TemplatedHeader
|
headers []TemplatedHeader
|
||||||
sessionSetup func(*SessionData)
|
sessionSetup func(*SessionData)
|
||||||
claims map[string]interface{}
|
claims map[string]any
|
||||||
expectedHeaders map[string]string
|
expectedHeaders map[string]string
|
||||||
interceptedHeaders map[string]string
|
interceptedHeaders map[string]string
|
||||||
}{
|
}{
|
||||||
@@ -31,7 +32,7 @@ func TestTemplatedHeadersIntegration(t *testing.T) {
|
|||||||
headers: []TemplatedHeader{
|
headers: []TemplatedHeader{
|
||||||
{Name: "X-User-Email", Value: "{{.Claims.email}}"},
|
{Name: "X-User-Email", Value: "{{.Claims.email}}"},
|
||||||
},
|
},
|
||||||
claims: map[string]interface{}{
|
claims: map[string]any{
|
||||||
"email": "[email protected]",
|
"email": "[email protected]",
|
||||||
},
|
},
|
||||||
expectedHeaders: map[string]string{
|
expectedHeaders: map[string]string{
|
||||||
@@ -45,7 +46,7 @@ func TestTemplatedHeadersIntegration(t *testing.T) {
|
|||||||
{Name: "X-User-ID", Value: "{{.Claims.sub}}"},
|
{Name: "X-User-ID", Value: "{{.Claims.sub}}"},
|
||||||
{Name: "X-User-Name", Value: "{{.Claims.given_name}} {{.Claims.family_name}}"},
|
{Name: "X-User-Name", Value: "{{.Claims.given_name}} {{.Claims.family_name}}"},
|
||||||
},
|
},
|
||||||
claims: map[string]interface{}{
|
claims: map[string]any{
|
||||||
"email": "[email protected]",
|
"email": "[email protected]",
|
||||||
"sub": "user123",
|
"sub": "user123",
|
||||||
"given_name": "John",
|
"given_name": "John",
|
||||||
@@ -94,7 +95,7 @@ func TestTemplatedHeadersIntegration(t *testing.T) {
|
|||||||
headers: []TemplatedHeader{
|
headers: []TemplatedHeader{
|
||||||
{Name: "X-User-Role", Value: "{{.Claims.role}}"},
|
{Name: "X-User-Role", Value: "{{.Claims.role}}"},
|
||||||
},
|
},
|
||||||
claims: map[string]interface{}{
|
claims: map[string]any{
|
||||||
"email": "[email protected]",
|
"email": "[email protected]",
|
||||||
// role claim is missing
|
// role claim is missing
|
||||||
},
|
},
|
||||||
@@ -107,7 +108,7 @@ func TestTemplatedHeadersIntegration(t *testing.T) {
|
|||||||
headers: []TemplatedHeader{
|
headers: []TemplatedHeader{
|
||||||
{Name: "X-User-Admin", Value: "{{if .Claims.is_admin}}true{{else}}false{{end}}"},
|
{Name: "X-User-Admin", Value: "{{if .Claims.is_admin}}true{{else}}false{{end}}"},
|
||||||
},
|
},
|
||||||
claims: map[string]interface{}{
|
claims: map[string]any{
|
||||||
"email": "[email protected]",
|
"email": "[email protected]",
|
||||||
"is_admin": true,
|
"is_admin": true,
|
||||||
},
|
},
|
||||||
@@ -120,7 +121,7 @@ func TestTemplatedHeadersIntegration(t *testing.T) {
|
|||||||
headers: []TemplatedHeader{
|
headers: []TemplatedHeader{
|
||||||
{Name: "X-Auth-Info", Value: "User={{.Claims.email}}, Token={{.AccessToken}}"},
|
{Name: "X-Auth-Info", Value: "User={{.Claims.email}}, Token={{.AccessToken}}"},
|
||||||
},
|
},
|
||||||
claims: map[string]interface{}{
|
claims: map[string]any{
|
||||||
"email": "[email protected]",
|
"email": "[email protected]",
|
||||||
},
|
},
|
||||||
expectedHeaders: map[string]string{
|
expectedHeaders: map[string]string{
|
||||||
@@ -133,7 +134,7 @@ func TestTemplatedHeadersIntegration(t *testing.T) {
|
|||||||
headers: []TemplatedHeader{
|
headers: []TemplatedHeader{
|
||||||
{Name: "X-User-AccessToken", Value: "{{.AccessToken}}"},
|
{Name: "X-User-AccessToken", Value: "{{.AccessToken}}"},
|
||||||
},
|
},
|
||||||
claims: map[string]interface{}{ // For ID Token
|
claims: map[string]any{ // For ID Token
|
||||||
"email": "[email protected]",
|
"email": "[email protected]",
|
||||||
"sub": "opaque_sub_for_id_token",
|
"sub": "opaque_sub_for_id_token",
|
||||||
},
|
},
|
||||||
@@ -149,7 +150,7 @@ func TestTemplatedHeadersIntegration(t *testing.T) {
|
|||||||
token := ts.token
|
token := ts.token
|
||||||
if len(tc.claims) > 0 {
|
if len(tc.claims) > 0 {
|
||||||
var err error
|
var err error
|
||||||
baseClaims := map[string]interface{}{
|
baseClaims := map[string]any{
|
||||||
"iss": "https://test-issuer.com",
|
"iss": "https://test-issuer.com",
|
||||||
"aud": "test-client-id",
|
"aud": "test-client-id",
|
||||||
"exp": float64(3000000000), // Far future timestamp
|
"exp": float64(3000000000), // Far future timestamp
|
||||||
@@ -161,9 +162,7 @@ func TestTemplatedHeadersIntegration(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add the test-specific claims
|
// Add the test-specific claims
|
||||||
for k, v := range tc.claims {
|
maps.Copy(baseClaims, tc.claims)
|
||||||
baseClaims[k] = v
|
|
||||||
}
|
|
||||||
|
|
||||||
token, err = createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", baseClaims)
|
token, err = createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", baseClaims)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -267,7 +266,7 @@ func TestTemplatedHeadersIntegration(t *testing.T) {
|
|||||||
session.SetRefreshToken("test-refresh-token")
|
session.SetRefreshToken("test-refresh-token")
|
||||||
|
|
||||||
if tc.name == "ID Token Header" || tc.name == "Both Token Types" {
|
if tc.name == "ID Token Header" || tc.name == "Both Token Types" {
|
||||||
idTokenClaims := map[string]interface{}{
|
idTokenClaims := map[string]any{
|
||||||
"iss": "https://test-issuer.com", "aud": "test-client-id", "exp": float64(3000000000),
|
"iss": "https://test-issuer.com", "aud": "test-client-id", "exp": float64(3000000000),
|
||||||
"iat": float64(1000000000), "nbf": float64(1000000000), "sub": "test-subject",
|
"iat": float64(1000000000), "nbf": float64(1000000000), "sub": "test-subject",
|
||||||
"nonce": "test-nonce", "jti": generateRandomString(16), "type": "id_token",
|
"nonce": "test-nonce", "jti": generateRandomString(16), "type": "id_token",
|
||||||
@@ -285,7 +284,7 @@ func TestTemplatedHeadersIntegration(t *testing.T) {
|
|||||||
t.Fatalf("Failed to create test ID JWT: %v", idErr)
|
t.Fatalf("Failed to create test ID JWT: %v", idErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
accessTokenClaims := map[string]interface{}{
|
accessTokenClaims := map[string]any{
|
||||||
"iss": "https://test-issuer.com", "aud": "test-client-id", "exp": float64(3000000000),
|
"iss": "https://test-issuer.com", "aud": "test-client-id", "exp": float64(3000000000),
|
||||||
"iat": float64(1000000000), "nbf": float64(1000000000), "sub": "test-subject",
|
"iat": float64(1000000000), "nbf": float64(1000000000), "sub": "test-subject",
|
||||||
"jti": generateRandomString(16), "type": "access_token", "scope": "openid email profile",
|
"jti": generateRandomString(16), "type": "access_token", "scope": "openid email profile",
|
||||||
@@ -316,15 +315,13 @@ func TestTemplatedHeadersIntegration(t *testing.T) {
|
|||||||
tc.expectedHeaders["X-Access-Token"] = accessTokenForSession
|
tc.expectedHeaders["X-Access-Token"] = accessTokenForSession
|
||||||
}
|
}
|
||||||
} else if tc.name == "Opaque Access Token with AccessTokenField" {
|
} else if tc.name == "Opaque Access Token with AccessTokenField" {
|
||||||
idTokenClaims := map[string]interface{}{
|
idTokenClaims := map[string]any{
|
||||||
"iss": "https://test-issuer.com", "aud": "test-client-id", "exp": float64(3000000000),
|
"iss": "https://test-issuer.com", "aud": "test-client-id", "exp": float64(3000000000),
|
||||||
"iat": float64(1000000000), "nbf": float64(1000000000), "sub": "test-subject", // Default sub
|
"iat": float64(1000000000), "nbf": float64(1000000000), "sub": "test-subject", // Default sub
|
||||||
"nonce": "test-nonce", "jti": generateRandomString(16), "type": "id_token",
|
"nonce": "test-nonce", "jti": generateRandomString(16), "type": "id_token",
|
||||||
}
|
}
|
||||||
// Populate ID token claims from tc.claims
|
// Populate ID token claims from tc.claims
|
||||||
for k, v := range tc.claims {
|
maps.Copy(idTokenClaims, tc.claims)
|
||||||
idTokenClaims[k] = v
|
|
||||||
}
|
|
||||||
// Ensure email from tc.claims is used for the ID token
|
// 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
|
session.SetEmail(tc.claims["email"].(string)) // Also set it directly for initial session state
|
||||||
|
|
||||||
@@ -428,7 +425,7 @@ func TestEdgeCaseTemplatedHeaders(t *testing.T) {
|
|||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
headers []TemplatedHeader
|
headers []TemplatedHeader
|
||||||
claims map[string]interface{}
|
claims map[string]any
|
||||||
shouldExecuteCheck bool
|
shouldExecuteCheck bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
@@ -447,8 +444,8 @@ func TestEdgeCaseTemplatedHeaders(t *testing.T) {
|
|||||||
headers: []TemplatedHeader{
|
headers: []TemplatedHeader{
|
||||||
{Name: "X-Roles", Value: "{{range $i, $e := .Claims.roles}}{{if $i}},{{end}}{{$e}}{{end}}"},
|
{Name: "X-Roles", Value: "{{range $i, $e := .Claims.roles}}{{if $i}},{{end}}{{$e}}{{end}}"},
|
||||||
},
|
},
|
||||||
claims: map[string]interface{}{
|
claims: map[string]any{
|
||||||
"roles": []interface{}{"admin", "user", "manager"},
|
"roles": []any{"admin", "user", "manager"},
|
||||||
},
|
},
|
||||||
shouldExecuteCheck: true,
|
shouldExecuteCheck: true,
|
||||||
},
|
},
|
||||||
@@ -457,7 +454,7 @@ func TestEdgeCaseTemplatedHeaders(t *testing.T) {
|
|||||||
for _, tc := range tests {
|
for _, tc := range tests {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
// Create token with the test claims
|
// Create token with the test claims
|
||||||
claims := map[string]interface{}{
|
claims := map[string]any{
|
||||||
"iss": "https://test-issuer.com",
|
"iss": "https://test-issuer.com",
|
||||||
"aud": "test-client-id",
|
"aud": "test-client-id",
|
||||||
"exp": float64(3000000000), // Far future timestamp
|
"exp": float64(3000000000), // Far future timestamp
|
||||||
@@ -469,9 +466,7 @@ func TestEdgeCaseTemplatedHeaders(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add the test-specific claims
|
// Add the test-specific claims
|
||||||
for k, v := range tc.claims {
|
maps.Copy(claims, tc.claims)
|
||||||
claims[k] = v
|
|
||||||
}
|
|
||||||
|
|
||||||
token, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", claims)
|
token, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", claims)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -576,7 +571,7 @@ func TestEdgeCaseTemplatedHeaders(t *testing.T) {
|
|||||||
// createLargeTemplate creates a template with many variable references
|
// createLargeTemplate creates a template with many variable references
|
||||||
func createLargeTemplate(size int) string {
|
func createLargeTemplate(size int) string {
|
||||||
template := "{{with .Claims}}"
|
template := "{{with .Claims}}"
|
||||||
for i := 0; i < size; i++ {
|
for i := range size {
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
template += ","
|
template += ","
|
||||||
}
|
}
|
||||||
@@ -587,9 +582,9 @@ func createLargeTemplate(size int) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// createLargeClaims creates a map with many claims for testing large templates
|
// createLargeClaims creates a map with many claims for testing large templates
|
||||||
func createLargeClaims(size int) map[string]interface{} {
|
func createLargeClaims(size int) map[string]any {
|
||||||
claims := make(map[string]interface{})
|
claims := make(map[string]any)
|
||||||
for i := 0; i < size; i++ {
|
for i := range size {
|
||||||
claims["email"] = "[email protected]" // Add email claim
|
claims["email"] = "[email protected]" // Add email claim
|
||||||
key := "field" + string(rune('a'+i%26)) + string(rune('0'+i%10))
|
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))
|
claims[key] = "value" + string(rune('a'+i%26)) + string(rune('0'+i%10))
|
||||||
|
|||||||
+16
-22
@@ -61,13 +61,10 @@ func (tt *TestTokens) CreateLargeValidJWT(targetSize int) string {
|
|||||||
|
|
||||||
// Calculate required payload size
|
// Calculate required payload size
|
||||||
usedSize := len(header) + len(signature) + 2 // account for dots
|
usedSize := len(header) + len(signature) + 2 // account for dots
|
||||||
payloadSize := targetSize - usedSize
|
payloadSize := max(targetSize-usedSize, 50)
|
||||||
if payloadSize < 50 {
|
|
||||||
payloadSize = 50
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a payload with realistic JWT claims
|
// Create a payload with realistic JWT claims
|
||||||
claims := map[string]interface{}{
|
claims := map[string]any{
|
||||||
"sub": "user123",
|
"sub": "user123",
|
||||||
"iss": "https://example.com",
|
"iss": "https://example.com",
|
||||||
"aud": "client123",
|
"aud": "client123",
|
||||||
@@ -76,10 +73,11 @@ func (tt *TestTokens) CreateLargeValidJWT(targetSize int) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// FIXED: Calculate data size safely
|
// FIXED: Calculate data size safely
|
||||||
dataSize := payloadSize - 100 // Account for other claims and base64 encoding
|
dataSize := max(
|
||||||
if dataSize < 10 {
|
// Account for other claims and base64 encoding
|
||||||
dataSize = 10 // Minimum data size
|
payloadSize-100,
|
||||||
}
|
// Minimum data size
|
||||||
|
10)
|
||||||
|
|
||||||
claims["data"] = tt.generateRandomString(dataSize)
|
claims["data"] = tt.generateRandomString(dataSize)
|
||||||
|
|
||||||
@@ -101,7 +99,7 @@ func (tt *TestTokens) CreateExpiredJWT() string {
|
|||||||
header := "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9"
|
header := "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9"
|
||||||
|
|
||||||
// Create claims with expired timestamp
|
// Create claims with expired timestamp
|
||||||
claims := map[string]interface{}{
|
claims := map[string]any{
|
||||||
"sub": "user123",
|
"sub": "user123",
|
||||||
"iss": "https://example.com",
|
"iss": "https://example.com",
|
||||||
"aud": "client123",
|
"aud": "client123",
|
||||||
@@ -120,7 +118,7 @@ func (tt *TestTokens) CreateExpiredJWT() string {
|
|||||||
func (tt *TestTokens) CreateUniqueValidJWT(id string) string {
|
func (tt *TestTokens) CreateUniqueValidJWT(id string) string {
|
||||||
header := "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9"
|
header := "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9"
|
||||||
|
|
||||||
claims := map[string]interface{}{
|
claims := map[string]any{
|
||||||
"sub": "user_" + id,
|
"sub": "user_" + id,
|
||||||
"iss": "https://example.com",
|
"iss": "https://example.com",
|
||||||
"aud": "client123",
|
"aud": "client123",
|
||||||
@@ -144,13 +142,10 @@ func (tt *TestTokens) CreateIncompressibleToken(targetSize int) string {
|
|||||||
|
|
||||||
// Calculate required payload size
|
// Calculate required payload size
|
||||||
usedSize := len(header) + len(signature) + 2 // account for dots
|
usedSize := len(header) + len(signature) + 2 // account for dots
|
||||||
payloadSize := targetSize - usedSize
|
payloadSize := max(targetSize-usedSize, 100)
|
||||||
if payloadSize < 100 {
|
|
||||||
payloadSize = 100
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate multiple random fields to prevent compression
|
// Generate multiple random fields to prevent compression
|
||||||
randomFields := make(map[string]interface{})
|
randomFields := make(map[string]any)
|
||||||
randomFields["sub"] = "user123"
|
randomFields["sub"] = "user123"
|
||||||
randomFields["iss"] = "https://example.com"
|
randomFields["iss"] = "https://example.com"
|
||||||
randomFields["aud"] = "client123"
|
randomFields["aud"] = "client123"
|
||||||
@@ -159,10 +154,9 @@ func (tt *TestTokens) CreateIncompressibleToken(targetSize int) string {
|
|||||||
|
|
||||||
// Add many random fields with random data to prevent compression
|
// Add many random fields with random data to prevent compression
|
||||||
remainingSize := payloadSize - 200 // Account for base64 encoding and other fields
|
remainingSize := payloadSize - 200 // Account for base64 encoding and other fields
|
||||||
fieldCount := remainingSize / 100 // ~100 bytes per field
|
fieldCount := max(
|
||||||
if fieldCount < 1 {
|
// ~100 bytes per field
|
||||||
fieldCount = 1
|
remainingSize/100, 1)
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < fieldCount; i++ {
|
for i := 0; i < fieldCount; i++ {
|
||||||
// Generate truly random data for each field
|
// Generate truly random data for each field
|
||||||
@@ -302,7 +296,7 @@ func (ts *TestScenarios) CorruptionTest() CorruptionTestSet {
|
|||||||
// ConcurrentTest returns unique tokens for concurrent testing
|
// ConcurrentTest returns unique tokens for concurrent testing
|
||||||
func (ts *TestScenarios) ConcurrentTest(count int) []TokenSet {
|
func (ts *TestScenarios) ConcurrentTest(count int) []TokenSet {
|
||||||
sets := make([]TokenSet, count)
|
sets := make([]TokenSet, count)
|
||||||
for i := 0; i < count; i++ {
|
for i := range count {
|
||||||
sets[i] = TokenSet{
|
sets[i] = TokenSet{
|
||||||
AccessToken: ts.tokens.CreateUniqueValidJWT(fmt.Sprintf("concurrent_%d", i)),
|
AccessToken: ts.tokens.CreateUniqueValidJWT(fmt.Sprintf("concurrent_%d", i)),
|
||||||
IDToken: ts.tokens.CreateUniqueValidJWT(fmt.Sprintf("id_%d", i)),
|
IDToken: ts.tokens.CreateUniqueValidJWT(fmt.Sprintf("id_%d", i)),
|
||||||
@@ -393,5 +387,5 @@ func AssertInvalidTokenRejection(t TestingInterface, session *SessionData, token
|
|||||||
|
|
||||||
// TestingInterface provides the minimal interface needed for testing
|
// TestingInterface provides the minimal interface needed for testing
|
||||||
type TestingInterface interface {
|
type TestingInterface interface {
|
||||||
Errorf(format string, args ...interface{})
|
Errorf(format string, args ...any)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -250,8 +251,8 @@ func TestChunkReassemblyEdgeCases(t *testing.T) {
|
|||||||
corruption: func(chunks map[int]*sessions.Session) {
|
corruption: func(chunks map[int]*sessions.Session) {
|
||||||
// This test simulates having too many chunks (>50 limit)
|
// This test simulates having too many chunks (>50 limit)
|
||||||
// We'll create a scenario by adding many fake chunks
|
// We'll create a scenario by adding many fake chunks
|
||||||
for i := 0; i < 60; i++ {
|
for i := range 60 {
|
||||||
fakeSession := &sessions.Session{Values: make(map[interface{}]interface{})}
|
fakeSession := &sessions.Session{Values: make(map[any]any)}
|
||||||
fakeSession.Values["token_chunk"] = "fake_chunk_data"
|
fakeSession.Values["token_chunk"] = "fake_chunk_data"
|
||||||
chunks[i] = fakeSession
|
chunks[i] = fakeSession
|
||||||
}
|
}
|
||||||
@@ -320,12 +321,12 @@ func TestRaceConditionProtection(t *testing.T) {
|
|||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
errChan := make(chan error, numGoroutines*numOperations)
|
errChan := make(chan error, numGoroutines*numOperations)
|
||||||
|
|
||||||
for i := 0; i < numGoroutines; i++ {
|
for i := range numGoroutines {
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func(goroutineID int) {
|
go func(goroutineID int) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
|
|
||||||
for j := 0; j < numOperations; j++ {
|
for j := range numOperations {
|
||||||
tokenIndex := (goroutineID + j) % len(tokens)
|
tokenIndex := (goroutineID + j) % len(tokens)
|
||||||
expectedToken := tokens[tokenIndex]
|
expectedToken := tokens[tokenIndex]
|
||||||
|
|
||||||
@@ -344,13 +345,7 @@ func TestRaceConditionProtection(t *testing.T) {
|
|||||||
|
|
||||||
// The retrieved token should be one of the valid tokens we set
|
// 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)
|
// (due to concurrent access, it might not be the exact one we just set)
|
||||||
isValidToken := false
|
isValidToken := slices.Contains(tokens, retrieved)
|
||||||
for _, validToken := range tokens {
|
|
||||||
if retrieved == validToken {
|
|
||||||
isValidToken = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if retrieved != "" && !isValidToken {
|
if retrieved != "" && !isValidToken {
|
||||||
errChan <- fmt.Errorf("goroutine %d, op %d: retrieved unknown token: %q",
|
errChan <- fmt.Errorf("goroutine %d, op %d: retrieved unknown token: %q",
|
||||||
|
|||||||
+9
-12
@@ -22,14 +22,14 @@ func TestTokenTypeDistinction(t *testing.T) {
|
|||||||
AccessToken string
|
AccessToken string
|
||||||
IdToken string
|
IdToken string
|
||||||
RefreshToken string
|
RefreshToken string
|
||||||
Claims map[string]interface{}
|
Claims map[string]any
|
||||||
}
|
}
|
||||||
|
|
||||||
testData := templateData{
|
testData := templateData{
|
||||||
AccessToken: "test-access-token-abc123",
|
AccessToken: "test-access-token-abc123",
|
||||||
IdToken: "test-id-token-xyz789",
|
IdToken: "test-id-token-xyz789",
|
||||||
RefreshToken: "test-refresh-token",
|
RefreshToken: "test-refresh-token",
|
||||||
Claims: map[string]interface{}{
|
Claims: map[string]any{
|
||||||
"sub": "test-subject",
|
"sub": "test-subject",
|
||||||
"email": "[email protected]",
|
"email": "[email protected]",
|
||||||
},
|
},
|
||||||
@@ -91,7 +91,7 @@ func TestTokenTypeIntegration(t *testing.T) {
|
|||||||
ts.Setup()
|
ts.Setup()
|
||||||
|
|
||||||
// Create different tokens for ID and access tokens
|
// Create different tokens for ID and access tokens
|
||||||
idToken, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]interface{}{
|
idToken, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]any{
|
||||||
"iss": "https://test-issuer.com",
|
"iss": "https://test-issuer.com",
|
||||||
"aud": "test-client-id",
|
"aud": "test-client-id",
|
||||||
"exp": float64(3000000000),
|
"exp": float64(3000000000),
|
||||||
@@ -107,7 +107,7 @@ func TestTokenTypeIntegration(t *testing.T) {
|
|||||||
t.Fatalf("Failed to create test ID JWT: %v", err)
|
t.Fatalf("Failed to create test ID JWT: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
accessToken, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]interface{}{
|
accessToken, err := createTestJWT(ts.rsaPrivateKey, "RS256", "test-key-id", map[string]any{
|
||||||
"iss": "https://test-issuer.com",
|
"iss": "https://test-issuer.com",
|
||||||
"aud": "test-client-id",
|
"aud": "test-client-id",
|
||||||
"exp": float64(3000000000),
|
"exp": float64(3000000000),
|
||||||
@@ -518,11 +518,11 @@ func TestConcurrentTokenOperationsWithCorruption(t *testing.T) {
|
|||||||
errorChan := make(chan error, numGoroutines*numOperations)
|
errorChan := make(chan error, numGoroutines*numOperations)
|
||||||
|
|
||||||
// Start concurrent operations
|
// Start concurrent operations
|
||||||
for i := 0; i < numGoroutines; i++ {
|
for i := range numGoroutines {
|
||||||
go func(goroutineID int) {
|
go func(goroutineID int) {
|
||||||
defer func() { done <- true }()
|
defer func() { done <- true }()
|
||||||
|
|
||||||
for j := 0; j < numOperations; j++ {
|
for j := range numOperations {
|
||||||
// Create a unique valid token for each operation
|
// Create a unique valid token for each operation
|
||||||
token := fmt.Sprintf("eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwib3AiOiIxMjMifQ.sig_%d_%d",
|
token := fmt.Sprintf("eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwib3AiOiIxMjMifQ.sig_%d_%d",
|
||||||
goroutineID, j)
|
goroutineID, j)
|
||||||
@@ -563,7 +563,7 @@ func TestConcurrentTokenOperationsWithCorruption(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Wait for all goroutines to complete
|
// Wait for all goroutines to complete
|
||||||
for i := 0; i < numGoroutines; i++ {
|
for range numGoroutines {
|
||||||
<-done
|
<-done
|
||||||
}
|
}
|
||||||
close(errorChan)
|
close(errorChan)
|
||||||
@@ -645,13 +645,10 @@ func createLargeValidJWT(targetSize int) string {
|
|||||||
|
|
||||||
// Calculate required payload size
|
// Calculate required payload size
|
||||||
usedSize := len(header) + len(signature) + 2 // account for dots
|
usedSize := len(header) + len(signature) + 2 // account for dots
|
||||||
payloadSize := targetSize - usedSize
|
payloadSize := max(targetSize-usedSize, 50)
|
||||||
if payloadSize < 50 {
|
|
||||||
payloadSize = 50
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a payload with realistic JWT claims
|
// Create a payload with realistic JWT claims
|
||||||
claims := map[string]interface{}{
|
claims := map[string]any{
|
||||||
"sub": "user123",
|
"sub": "user123",
|
||||||
"iss": "https://example.com",
|
"iss": "https://example.com",
|
||||||
"aud": "client123",
|
"aud": "client123",
|
||||||
|
|||||||
Reference in New Issue
Block a user