mirror of
https://github.com/lukaszraczylo/traefikoidc.git
synced 2026-10-13 04:07:37 +00:00
Fieldalignment
This commit is contained in:
@@ -23,24 +23,13 @@ type lruEntry struct {
|
||||
// Cache provides a thread-safe in-memory caching mechanism with expiration support.
|
||||
// It implements an LRU (Least Recently Used) eviction policy using a doubly-linked list for efficiency.
|
||||
type Cache struct {
|
||||
// items stores the cached data with string keys.
|
||||
items map[string]CacheItem
|
||||
|
||||
// order maintains the usage order; most recently used items are at the back.
|
||||
order *list.List
|
||||
|
||||
// elems maps keys to their corresponding list elements for O(1) access.
|
||||
elems map[string]*list.Element
|
||||
|
||||
// mutex protects concurrent access to the cache.
|
||||
mutex sync.RWMutex
|
||||
|
||||
// maxSize is the maximum number of items allowed in the cache.
|
||||
maxSize int
|
||||
// autoCleanupInterval defines how often Cleanup is called automatically.
|
||||
items map[string]CacheItem
|
||||
order *list.List
|
||||
elems map[string]*list.Element
|
||||
stopCleanup chan struct{}
|
||||
maxSize int
|
||||
autoCleanupInterval time.Duration
|
||||
// stopCleanup channel to terminate the auto cleanup goroutine.
|
||||
stopCleanup chan struct{}
|
||||
mutex sync.RWMutex
|
||||
}
|
||||
|
||||
// DefaultMaxSize is the default maximum number of items in the cache.
|
||||
|
||||
+17
-32
@@ -26,25 +26,18 @@ const (
|
||||
|
||||
// CircuitBreaker implements the circuit breaker pattern for external service calls
|
||||
type CircuitBreaker struct {
|
||||
// Configuration
|
||||
maxFailures int // Maximum failures before opening
|
||||
timeout time.Duration // How long to wait before trying again
|
||||
resetTimeout time.Duration // How long to wait in half-open state
|
||||
|
||||
// State
|
||||
state CircuitBreakerState
|
||||
failures int64
|
||||
lastFailureTime time.Time
|
||||
lastSuccessTime time.Time
|
||||
logger *Logger
|
||||
maxFailures int
|
||||
timeout time.Duration
|
||||
resetTimeout time.Duration
|
||||
state CircuitBreakerState
|
||||
failures int64
|
||||
totalRequests int64
|
||||
totalFailures int64
|
||||
totalSuccesses int64
|
||||
mutex sync.RWMutex
|
||||
|
||||
// Metrics
|
||||
totalRequests int64
|
||||
totalFailures int64
|
||||
totalSuccesses int64
|
||||
|
||||
// Logger
|
||||
logger *Logger
|
||||
}
|
||||
|
||||
// CircuitBreakerConfig holds configuration for circuit breakers
|
||||
@@ -193,12 +186,12 @@ func (cb *CircuitBreaker) GetMetrics() map[string]any {
|
||||
|
||||
// RetryConfig holds configuration for retry mechanisms
|
||||
type RetryConfig struct {
|
||||
RetryableErrors []string `json:"retryable_errors"`
|
||||
MaxAttempts int `json:"max_attempts"`
|
||||
InitialDelay time.Duration `json:"initial_delay"`
|
||||
MaxDelay time.Duration `json:"max_delay"`
|
||||
BackoffFactor float64 `json:"backoff_factor"`
|
||||
EnableJitter bool `json:"enable_jitter"`
|
||||
RetryableErrors []string `json:"retryable_errors"`
|
||||
}
|
||||
|
||||
// DefaultRetryConfig returns default retry configuration
|
||||
@@ -220,8 +213,8 @@ func DefaultRetryConfig() RetryConfig {
|
||||
|
||||
// RetryExecutor implements retry logic with exponential backoff
|
||||
type RetryExecutor struct {
|
||||
config RetryConfig
|
||||
logger *Logger
|
||||
config RetryConfig
|
||||
}
|
||||
|
||||
// NewRetryExecutor creates a new retry executor
|
||||
@@ -344,8 +337,8 @@ func (re *RetryExecutor) calculateDelay(attempt int) time.Duration {
|
||||
|
||||
// HTTPError represents an HTTP error with status code
|
||||
type HTTPError struct {
|
||||
StatusCode int
|
||||
Message string
|
||||
StatusCode int
|
||||
}
|
||||
|
||||
// Error implements the error interface
|
||||
@@ -355,20 +348,12 @@ func (e *HTTPError) Error() string {
|
||||
|
||||
// GracefulDegradation implements graceful degradation patterns
|
||||
type GracefulDegradation struct {
|
||||
// Fallback functions for different operations
|
||||
fallbacks map[string]func() (any, error)
|
||||
|
||||
// Health checks for dependencies
|
||||
healthChecks map[string]func() bool
|
||||
|
||||
// Configuration
|
||||
config GracefulDegradationConfig
|
||||
|
||||
// State tracking
|
||||
fallbacks map[string]func() (any, error)
|
||||
healthChecks map[string]func() bool
|
||||
degradedServices map[string]time.Time
|
||||
logger *Logger
|
||||
config GracefulDegradationConfig
|
||||
mutex sync.RWMutex
|
||||
|
||||
logger *Logger
|
||||
}
|
||||
|
||||
// GracefulDegradationConfig holds configuration for graceful degradation
|
||||
@@ -537,8 +522,8 @@ type ErrorRecoveryManager struct {
|
||||
circuitBreakers map[string]*CircuitBreaker
|
||||
retryExecutor *RetryExecutor
|
||||
gracefulDegradation *GracefulDegradation
|
||||
mutex sync.RWMutex
|
||||
logger *Logger
|
||||
mutex sync.RWMutex
|
||||
}
|
||||
|
||||
// NewErrorRecoveryManager creates a new error recovery manager
|
||||
|
||||
+4
-13
@@ -72,20 +72,11 @@ func deriveCodeChallenge(codeVerifier string) string {
|
||||
// It contains the various tokens and metadata returned after successful
|
||||
// code exchange or token refresh operations.
|
||||
type TokenResponse struct {
|
||||
// IDToken is the OIDC ID token containing user claims
|
||||
IDToken string `json:"id_token"`
|
||||
|
||||
// AccessToken is the OAuth 2.0 access token for API access
|
||||
AccessToken string `json:"access_token"`
|
||||
|
||||
// RefreshToken is the OAuth 2.0 refresh token for obtaining new tokens
|
||||
IDToken string `json:"id_token"`
|
||||
AccessToken string `json:"access_token"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
|
||||
// ExpiresIn is the lifetime in seconds of the access token
|
||||
ExpiresIn int `json:"expires_in"`
|
||||
|
||||
// TokenType is the type of token, typically "Bearer"
|
||||
TokenType string `json:"token_type"`
|
||||
TokenType string `json:"token_type"`
|
||||
ExpiresIn int `json:"expires_in"`
|
||||
}
|
||||
|
||||
// exchangeTokens performs the OAuth 2.0 token exchange with the OIDC provider's token endpoint.
|
||||
|
||||
+15
-21
@@ -11,35 +11,29 @@ import (
|
||||
|
||||
// InputValidator provides comprehensive input validation and sanitization
|
||||
type InputValidator struct {
|
||||
// Configuration
|
||||
maxTokenLength int
|
||||
maxURLLength int
|
||||
maxHeaderLength int
|
||||
maxClaimLength int
|
||||
maxEmailLength int
|
||||
maxUsernameLength int
|
||||
|
||||
// Compiled regex patterns
|
||||
emailRegex *regexp.Regexp
|
||||
urlRegex *regexp.Regexp
|
||||
tokenRegex *regexp.Regexp
|
||||
usernameRegex *regexp.Regexp
|
||||
|
||||
// Security patterns to detect
|
||||
usernameRegex *regexp.Regexp
|
||||
tokenRegex *regexp.Regexp
|
||||
logger *Logger
|
||||
urlRegex *regexp.Regexp
|
||||
emailRegex *regexp.Regexp
|
||||
sqlInjectionPatterns []string
|
||||
xssPatterns []string
|
||||
pathTraversalPatterns []string
|
||||
|
||||
logger *Logger
|
||||
xssPatterns []string
|
||||
maxUsernameLength int
|
||||
maxURLLength int
|
||||
maxTokenLength int
|
||||
maxEmailLength int
|
||||
maxClaimLength int
|
||||
maxHeaderLength int
|
||||
}
|
||||
|
||||
// ValidationResult represents the result of input validation
|
||||
type ValidationResult struct {
|
||||
IsValid bool `json:"is_valid"`
|
||||
Errors []string `json:"errors,omitempty"`
|
||||
Warnings []string `json:"warnings,omitempty"`
|
||||
SanitizedValue string `json:"sanitized_value,omitempty"`
|
||||
SecurityRisk string `json:"security_risk,omitempty"`
|
||||
Errors []string `json:"errors,omitempty"`
|
||||
Warnings []string `json:"warnings,omitempty"`
|
||||
IsValid bool `json:"is_valid"`
|
||||
}
|
||||
|
||||
// InputValidationConfig holds configuration for input validation
|
||||
|
||||
@@ -204,8 +204,8 @@ func TestSanitizeInput(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
maxLen int
|
||||
expected string
|
||||
maxLen int
|
||||
}{
|
||||
{
|
||||
name: "Normal text",
|
||||
|
||||
@@ -33,13 +33,12 @@ type JWKSet struct {
|
||||
}
|
||||
|
||||
type JWKCache struct {
|
||||
jwks *JWKSet
|
||||
expiresAt time.Time
|
||||
mutex sync.RWMutex
|
||||
// CacheLifetime is configurable to determine how long the JWKS is cached.
|
||||
expiresAt time.Time
|
||||
jwks *JWKSet
|
||||
internalCache *Cache
|
||||
CacheLifetime time.Duration
|
||||
internalCache *Cache // To hold the closable Cache instance from cache.go
|
||||
maxSize int // Maximum number of items in the cache
|
||||
maxSize int
|
||||
mutex sync.RWMutex
|
||||
}
|
||||
|
||||
type JWKCacheInterface interface {
|
||||
|
||||
@@ -89,8 +89,8 @@ var ClockSkewTolerance = ClockSkewToleranceFuture
|
||||
type JWT struct {
|
||||
Header map[string]any
|
||||
Claims map[string]any
|
||||
Signature []byte
|
||||
Token string
|
||||
Signature []byte
|
||||
}
|
||||
|
||||
// parseJWT decodes a raw JWT string into its constituent parts: header, claims, and signature.
|
||||
|
||||
@@ -196,52 +196,51 @@ type TokenExchanger interface {
|
||||
|
||||
// TraefikOidc is the main struct for the OIDC middleware
|
||||
type TraefikOidc struct {
|
||||
jwkCache JWKCacheInterface
|
||||
jwtVerifier JWTVerifier
|
||||
ctx context.Context
|
||||
tokenVerifier TokenVerifier
|
||||
next http.Handler
|
||||
tokenExchanger TokenExchanger
|
||||
initComplete chan struct{}
|
||||
limiter *rate.Limiter
|
||||
tokenBlacklist *Cache
|
||||
headerTemplates map[string]*template.Template
|
||||
sessionManager *SessionManager
|
||||
tokenCleanupStopChan chan struct{}
|
||||
excludedURLs map[string]struct{}
|
||||
extractClaimsFunc func(tokenString string) (map[string]any, error)
|
||||
initiateAuthenticationFunc func(rw http.ResponseWriter, req *http.Request, session *SessionData, redirectURL string)
|
||||
metadataCache *MetadataCache
|
||||
allowedRolesAndGroups map[string]struct{}
|
||||
allowedUsers map[string]struct{}
|
||||
allowedUserDomains map[string]struct{}
|
||||
tokenCache *TokenCache
|
||||
httpClient *http.Client
|
||||
tokenHTTPClient *http.Client
|
||||
logger *Logger
|
||||
metadataRefreshStopChan chan struct{}
|
||||
cancelFunc context.CancelFunc
|
||||
clientSecret string
|
||||
clientID string
|
||||
name string
|
||||
redirURLPath string
|
||||
logoutURLPath string
|
||||
tokenURL string
|
||||
authURL string
|
||||
endSessionURL string
|
||||
postLogoutRedirectURI string
|
||||
scheme string
|
||||
jwksURL string
|
||||
issuerURL string
|
||||
revocationURL string
|
||||
jwkCache JWKCacheInterface
|
||||
metadataCache *MetadataCache
|
||||
tokenBlacklist *Cache // Replaced TokenBlacklist with generic Cache
|
||||
jwksURL string
|
||||
clientID string
|
||||
clientSecret string
|
||||
authURL string
|
||||
tokenURL string
|
||||
scopes []string
|
||||
limiter *rate.Limiter
|
||||
goroutineWG sync.WaitGroup
|
||||
refreshGracePeriod time.Duration
|
||||
shutdownOnce sync.Once
|
||||
forceHTTPS bool
|
||||
enablePKCE bool
|
||||
scheme string
|
||||
tokenCache *TokenCache
|
||||
httpClient *http.Client
|
||||
tokenHTTPClient *http.Client // Reusable HTTP client for token operations
|
||||
logger *Logger
|
||||
tokenVerifier TokenVerifier
|
||||
jwtVerifier JWTVerifier
|
||||
excludedURLs map[string]struct{}
|
||||
allowedUserDomains map[string]struct{}
|
||||
allowedUsers map[string]struct{} // Map for case-insensitive lookup of allowed email addresses
|
||||
allowedRolesAndGroups map[string]struct{}
|
||||
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
|
||||
extractClaimsFunc func(tokenString string) (map[string]any, error)
|
||||
initComplete chan struct{}
|
||||
endSessionURL string
|
||||
postLogoutRedirectURI string
|
||||
sessionManager *SessionManager
|
||||
tokenExchanger TokenExchanger // Added field for mocking
|
||||
refreshGracePeriod time.Duration // Configurable grace period for proactive refresh
|
||||
headerTemplates map[string]*template.Template // Parsed templates for custom headers
|
||||
tokenCleanupStopChan chan struct{} // Channel to stop token cleanup goroutine
|
||||
metadataRefreshStopChan chan struct{} // Channel to stop metadata refresh goroutine
|
||||
goroutineWG sync.WaitGroup // WaitGroup to track background goroutines
|
||||
ctx context.Context
|
||||
cancelFunc context.CancelFunc
|
||||
shutdownOnce sync.Once
|
||||
suppressDiagnosticLogs bool // Flag to suppress diagnostic logs during tests
|
||||
suppressDiagnosticLogs bool
|
||||
}
|
||||
|
||||
// ProviderMetadata holds OIDC provider metadata
|
||||
@@ -1195,11 +1194,10 @@ func (t *TraefikOidc) processAuthorizedRequest(rw http.ResponseWriter, req *http
|
||||
// Create template data context with available tokens and claims
|
||||
// Fields must be exported (uppercase) to be accessible in templates
|
||||
templateData := struct {
|
||||
// These fields need to be exported (uppercase) for template access
|
||||
Claims map[string]any
|
||||
AccessToken string
|
||||
IdToken string
|
||||
RefreshToken string
|
||||
Claims map[string]any
|
||||
}{
|
||||
AccessToken: session.GetAccessToken(), // Provide AccessToken for templates if needed
|
||||
IdToken: session.GetIDToken(),
|
||||
|
||||
+33
-33
@@ -30,8 +30,8 @@ type TestSuite struct {
|
||||
ecPrivateKey *ecdsa.PrivateKey
|
||||
tOidc *TraefikOidc
|
||||
mockJWKCache *MockJWKCache
|
||||
token string
|
||||
sessionManager *SessionManager
|
||||
token string
|
||||
}
|
||||
|
||||
// Setup initializes the test suite
|
||||
@@ -410,15 +410,15 @@ func TestServeHTTP(t *testing.T) {
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
requestPath string
|
||||
sessionValues map[any]any
|
||||
expectedStatus int
|
||||
expectedBody string
|
||||
setupSession func(*SessionData)
|
||||
mockRefreshTokenFunc func(originalFunc func(refreshToken string) (*TokenResponse, error)) func(refreshToken string) (*TokenResponse, error)
|
||||
assertSessionAfterRequest func(t *testing.T, rr *httptest.ResponseRecorder, req *http.Request, sessionManager *SessionManager) // Added for post-request checks
|
||||
requestHeaders map[string]string // Added for setting headers like Accept
|
||||
assertSessionAfterRequest func(t *testing.T, rr *httptest.ResponseRecorder, req *http.Request, sessionManager *SessionManager)
|
||||
requestHeaders map[string]string
|
||||
name string
|
||||
requestPath string
|
||||
expectedBody string
|
||||
expectedStatus int
|
||||
}{
|
||||
{
|
||||
name: "Excluded URL",
|
||||
@@ -873,10 +873,10 @@ func TestJWKToPEM(t *testing.T) {
|
||||
ts.Setup()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
jwk *JWK
|
||||
expectError bool
|
||||
name string
|
||||
errorContains string
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
name: "Unsupported Key Type",
|
||||
@@ -928,8 +928,8 @@ func TestParseJWT(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
token string
|
||||
expectError bool
|
||||
errorContains string
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
name: "Invalid Format",
|
||||
@@ -989,11 +989,11 @@ func TestHandleCallback(t *testing.T) {
|
||||
redirectURL := "http://example.com/"
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
queryParams string
|
||||
exchangeCodeForToken func(code string, redirectURL string, codeVerifier string) (*TokenResponse, error)
|
||||
extractClaimsFunc func(tokenString string) (map[string]any, error)
|
||||
sessionSetupFunc func(*SessionData)
|
||||
name string
|
||||
queryParams string
|
||||
expectedStatus int
|
||||
}{
|
||||
{
|
||||
@@ -1256,12 +1256,12 @@ func TestIsAllowedDomain(t *testing.T) {
|
||||
ts.Setup()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
email string
|
||||
allowedDomains map[string]struct{}
|
||||
allowedUsers map[string]struct{}
|
||||
name string
|
||||
email string
|
||||
expectedLogOutput string
|
||||
allowed bool
|
||||
expectedLogOutput string // For testing log messages
|
||||
}{
|
||||
{
|
||||
name: "Allowed domain",
|
||||
@@ -1343,11 +1343,11 @@ func TestOIDCHandler(t *testing.T) {
|
||||
ts.token = "valid.jwt.token"
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
queryParams string
|
||||
exchangeCodeForToken func(code string, redirectURL string, codeVerifier string) (*TokenResponse, error)
|
||||
extractClaimsFunc func(tokenString string) (map[string]any, error)
|
||||
sessionSetupFunc func(session *sessions.Session)
|
||||
name string
|
||||
queryParams string
|
||||
expectedStatus int
|
||||
blacklist bool
|
||||
rateLimit bool
|
||||
@@ -1504,12 +1504,12 @@ func TestHandleLogout(t *testing.T) {
|
||||
defer mockRevocationServer.Close()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
setupSession func(*SessionData)
|
||||
name string
|
||||
endSessionURL string
|
||||
expectedStatus int
|
||||
expectedURL string
|
||||
host string
|
||||
expectedStatus int
|
||||
}{
|
||||
{
|
||||
name: "Successful logout with end session endpoint",
|
||||
@@ -2104,12 +2104,12 @@ func TestServeHTTPRolesAndGroups(t *testing.T) {
|
||||
nbf := now.Add(-2 * time.Minute).Unix() // Account for clock skew
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
allowedRolesAndGroups map[string]struct{}
|
||||
claims map[string]any
|
||||
setupSession func(*SessionData)
|
||||
expectedStatus int
|
||||
expectedHeaders map[string]string
|
||||
name string
|
||||
expectedStatus int
|
||||
}{
|
||||
{
|
||||
name: "User with allowed role",
|
||||
@@ -2313,10 +2313,10 @@ func TestExchangeTokensWithRedirects(t *testing.T) {
|
||||
ts.Setup()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
setupServer func() *httptest.Server
|
||||
expectError bool
|
||||
name string
|
||||
errorContains string
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
name: "Successful token exchange with redirects",
|
||||
@@ -2424,9 +2424,9 @@ func TestBuildAuthURL(t *testing.T) {
|
||||
redirectURL string
|
||||
state string
|
||||
nonce string
|
||||
enablePKCE bool
|
||||
codeChallenge string
|
||||
expectedPrefix string
|
||||
enablePKCE bool
|
||||
checkPKCE bool
|
||||
}{
|
||||
{
|
||||
@@ -2574,10 +2574,10 @@ func TestExchangeCodeForToken(t *testing.T) {
|
||||
ts.Setup()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
enablePKCE bool
|
||||
codeVerifier string
|
||||
setupMock func(t *testing.T) *httptest.Server
|
||||
name string
|
||||
codeVerifier string
|
||||
enablePKCE bool
|
||||
}{
|
||||
{
|
||||
name: "With PKCE Enabled and Code Verifier",
|
||||
@@ -2883,10 +2883,10 @@ func TestJWTVerifyWithSkipReplayCheck(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
errorContains string
|
||||
skipReplayCheck bool
|
||||
firstCall bool
|
||||
expectError bool
|
||||
errorContains string
|
||||
}{
|
||||
{
|
||||
name: "First verification with skipReplayCheck=false should succeed",
|
||||
@@ -3339,10 +3339,10 @@ func TestJTIBlacklistBehavior(t *testing.T) {
|
||||
|
||||
// Test JTI blacklist behavior
|
||||
tests := []struct {
|
||||
name string
|
||||
action func() error
|
||||
expectError bool
|
||||
name string
|
||||
description string
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
name: "Initial verification adds JTI to blacklist",
|
||||
@@ -3495,9 +3495,9 @@ func TestEdgeCasesWithDifferentTokenTypes(t *testing.T) {
|
||||
nbf := now.Unix()
|
||||
|
||||
tests := []struct {
|
||||
claims map[string]any
|
||||
name string
|
||||
tokenType string
|
||||
claims map[string]any
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
@@ -3697,10 +3697,10 @@ func TestScopeMergingEdgeCases(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
description string
|
||||
defaultScopes []string
|
||||
userScopes []string
|
||||
expectedScopes []string
|
||||
description string
|
||||
}{
|
||||
{
|
||||
name: "Case sensitivity preserved",
|
||||
@@ -3974,8 +3974,8 @@ func TestBuildAuthURLWithMergedScopes(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
scopes []string
|
||||
expectedScopes string
|
||||
scopes []string
|
||||
}{
|
||||
{
|
||||
name: "Default scopes only",
|
||||
|
||||
+3
-3
@@ -8,11 +8,11 @@ import (
|
||||
)
|
||||
|
||||
type MetadataCache struct {
|
||||
metadata *ProviderMetadata
|
||||
expiresAt time.Time
|
||||
mutex sync.RWMutex
|
||||
autoCleanupInterval time.Duration
|
||||
metadata *ProviderMetadata
|
||||
stopCleanup chan struct{}
|
||||
autoCleanupInterval time.Duration
|
||||
mutex sync.RWMutex
|
||||
}
|
||||
|
||||
// NewMetadataCache creates a new MetadataCache instance.
|
||||
|
||||
+38
-70
@@ -10,61 +10,41 @@ import (
|
||||
|
||||
// PerformanceMetrics tracks various performance-related metrics
|
||||
type PerformanceMetrics struct {
|
||||
// Cache metrics
|
||||
cacheHits int64
|
||||
cacheMisses int64
|
||||
cacheEvictions int64
|
||||
cacheSize int64
|
||||
|
||||
// Token operation metrics
|
||||
tokenVerifications int64
|
||||
tokenValidations int64
|
||||
tokenRefreshes int64
|
||||
|
||||
// Success/failure tracking
|
||||
startTime time.Time
|
||||
logger *Logger
|
||||
verificationTimes []time.Duration
|
||||
refreshTimes []time.Duration
|
||||
validationTimes []time.Duration
|
||||
memoryUsage int64
|
||||
heapSize int64
|
||||
successfulVerifications int64
|
||||
successfulValidations int64
|
||||
successfulRefreshes int64
|
||||
failedVerifications int64
|
||||
failedValidations int64
|
||||
failedRefreshes int64
|
||||
|
||||
// Timing metrics
|
||||
avgVerificationTime time.Duration
|
||||
avgValidationTime time.Duration
|
||||
avgRefreshTime time.Duration
|
||||
|
||||
// Resource metrics
|
||||
memoryUsage int64
|
||||
goroutineCount int64
|
||||
memoryPressure int64 // Memory pressure level (0-100)
|
||||
gcPauseTime int64 // Last GC pause time in nanoseconds
|
||||
heapSize int64 // Current heap size
|
||||
heapInUse int64 // Heap memory in use
|
||||
|
||||
// Error metrics (kept for backward compatibility)
|
||||
verificationErrors int64
|
||||
validationErrors int64
|
||||
refreshErrors int64
|
||||
|
||||
// Rate limiting metrics
|
||||
rateLimitedRequests int64
|
||||
|
||||
// Session metrics
|
||||
activeSessions int64
|
||||
sessionCreations int64
|
||||
sessionDeletions int64
|
||||
|
||||
// Timing tracking
|
||||
timingMutex sync.RWMutex
|
||||
verificationTimes []time.Duration
|
||||
validationTimes []time.Duration
|
||||
refreshTimes []time.Duration
|
||||
|
||||
// Start time for uptime calculation
|
||||
startTime time.Time
|
||||
|
||||
logger *Logger
|
||||
avgVerificationTime time.Duration
|
||||
avgValidationTime time.Duration
|
||||
avgRefreshTime time.Duration
|
||||
cacheHits int64
|
||||
goroutineCount int64
|
||||
memoryPressure int64
|
||||
gcPauseTime int64
|
||||
tokenRefreshes int64
|
||||
heapInUse int64
|
||||
verificationErrors int64
|
||||
validationErrors int64
|
||||
refreshErrors int64
|
||||
rateLimitedRequests int64
|
||||
activeSessions int64
|
||||
sessionCreations int64
|
||||
sessionDeletions int64
|
||||
cacheMisses int64
|
||||
tokenValidations int64
|
||||
tokenVerifications int64
|
||||
cacheSize int64
|
||||
cacheEvictions int64
|
||||
timingMutex sync.RWMutex
|
||||
}
|
||||
|
||||
// NewPerformanceMetrics creates a new performance metrics tracker
|
||||
@@ -441,38 +421,26 @@ func (pm *PerformanceMetrics) calculateTimingStats(times []time.Duration) map[st
|
||||
|
||||
// ResourceMonitor tracks resource usage and limits
|
||||
type ResourceMonitor struct {
|
||||
// Memory limits
|
||||
maxMemoryBytes int64
|
||||
|
||||
// Cache limits
|
||||
maxCacheSize int64
|
||||
|
||||
// Session limits
|
||||
maxSessions int64
|
||||
|
||||
// Cache size tracking
|
||||
cacheSizes map[string]int64
|
||||
cacheMutex sync.RWMutex
|
||||
|
||||
// Monitoring state
|
||||
cacheSizes map[string]int64
|
||||
alertThresholds map[string]float64
|
||||
perfMetrics *PerformanceMetrics
|
||||
logger *Logger
|
||||
alerts []ResourceAlert
|
||||
maxMemoryBytes int64
|
||||
maxCacheSize int64
|
||||
maxSessions int64
|
||||
cacheMutex sync.RWMutex
|
||||
alertsMutex sync.RWMutex
|
||||
|
||||
// Performance metrics reference
|
||||
perfMetrics *PerformanceMetrics
|
||||
|
||||
logger *Logger
|
||||
}
|
||||
|
||||
// ResourceAlert represents a resource usage alert
|
||||
type ResourceAlert struct {
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
Type string `json:"type"`
|
||||
Message string `json:"message"`
|
||||
Severity string `json:"severity"`
|
||||
Threshold float64 `json:"threshold"`
|
||||
CurrentValue float64 `json:"current_value"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
Severity string `json:"severity"`
|
||||
}
|
||||
|
||||
// NewResourceMonitor creates a new resource monitor
|
||||
|
||||
@@ -390,8 +390,8 @@ func TestMissingClaims(t *testing.T) {
|
||||
// Test cases for missing claims
|
||||
testCases := []struct {
|
||||
name string
|
||||
omittedClaims []string
|
||||
expectedError string
|
||||
omittedClaims []string
|
||||
}{
|
||||
{
|
||||
name: "Missing Issuer",
|
||||
|
||||
+27
-50
@@ -13,67 +13,51 @@ import (
|
||||
|
||||
// SecurityEvent represents a security-related event that should be logged and monitored
|
||||
type SecurityEvent struct {
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
Details map[string]any `json:"details,omitempty"`
|
||||
Type string `json:"type"`
|
||||
Severity string `json:"severity"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
ClientIP string `json:"client_ip"`
|
||||
UserAgent string `json:"user_agent"`
|
||||
RequestPath string `json:"request_path"`
|
||||
Message string `json:"message"`
|
||||
Details map[string]any `json:"details,omitempty"`
|
||||
}
|
||||
|
||||
// SecurityMonitor tracks security events and suspicious activity patterns
|
||||
type SecurityMonitor struct {
|
||||
// Event counters
|
||||
ipFailures map[string]*IPFailureTracker
|
||||
patternDetector *SuspiciousPatternDetector
|
||||
logger *Logger
|
||||
eventHandlers []SecurityEventHandler
|
||||
config SecurityMonitorConfig
|
||||
authFailures int64
|
||||
tokenValidationFails int64
|
||||
rateLimitHits int64
|
||||
suspiciousRequests int64
|
||||
|
||||
// IP-based tracking
|
||||
ipFailures map[string]*IPFailureTracker
|
||||
ipMutex sync.RWMutex
|
||||
|
||||
// Pattern detection
|
||||
patternDetector *SuspiciousPatternDetector
|
||||
|
||||
// Event handlers
|
||||
eventHandlers []SecurityEventHandler
|
||||
|
||||
// Configuration
|
||||
config SecurityMonitorConfig
|
||||
|
||||
// Logger
|
||||
logger *Logger
|
||||
ipMutex sync.RWMutex
|
||||
}
|
||||
|
||||
// IPFailureTracker tracks failures for a specific IP address
|
||||
type IPFailureTracker struct {
|
||||
FailureCount int64
|
||||
LastFailure time.Time
|
||||
FirstFailure time.Time
|
||||
FailureTypes map[string]int64
|
||||
IsBlocked bool
|
||||
BlockedUntil time.Time
|
||||
FailureTypes map[string]int64
|
||||
FailureCount int64
|
||||
mutex sync.RWMutex
|
||||
IsBlocked bool
|
||||
}
|
||||
|
||||
// SuspiciousPatternDetector identifies patterns that may indicate attacks
|
||||
type SuspiciousPatternDetector struct {
|
||||
// Time-based windows for pattern detection
|
||||
shortWindow time.Duration // 1 minute
|
||||
mediumWindow time.Duration // 5 minutes
|
||||
longWindow time.Duration // 15 minutes
|
||||
|
||||
// Pattern thresholds
|
||||
rapidFailureThreshold int // failures in short window
|
||||
distributedAttackThreshold int // failures across IPs in medium window
|
||||
persistentAttackThreshold int // failures in long window
|
||||
|
||||
// Pattern tracking
|
||||
recentEvents []SecurityEvent
|
||||
eventsMutex sync.RWMutex
|
||||
recentEvents []SecurityEvent
|
||||
shortWindow time.Duration
|
||||
mediumWindow time.Duration
|
||||
longWindow time.Duration
|
||||
rapidFailureThreshold int
|
||||
distributedAttackThreshold int
|
||||
persistentAttackThreshold int
|
||||
eventsMutex sync.RWMutex
|
||||
}
|
||||
|
||||
// SecurityEventHandler defines the interface for handling security events
|
||||
@@ -83,22 +67,15 @@ type SecurityEventHandler interface {
|
||||
|
||||
// SecurityMonitorConfig contains configuration for the security monitor
|
||||
type SecurityMonitorConfig struct {
|
||||
// Failure thresholds
|
||||
MaxFailuresPerIP int `json:"max_failures_per_ip"`
|
||||
FailureWindowMinutes int `json:"failure_window_minutes"`
|
||||
BlockDurationMinutes int `json:"block_duration_minutes"`
|
||||
|
||||
// Pattern detection settings
|
||||
EnablePatternDetection bool `json:"enable_pattern_detection"`
|
||||
MaxFailuresPerIP int `json:"max_failures_per_ip"`
|
||||
FailureWindowMinutes int `json:"failure_window_minutes"`
|
||||
BlockDurationMinutes int `json:"block_duration_minutes"`
|
||||
RapidFailureThreshold int `json:"rapid_failure_threshold"`
|
||||
|
||||
// Monitoring settings
|
||||
EnableDetailedLogging bool `json:"enable_detailed_logging"`
|
||||
LogSuspiciousOnly bool `json:"log_suspicious_only"`
|
||||
|
||||
// Cleanup settings
|
||||
CleanupIntervalMinutes int `json:"cleanup_interval_minutes"`
|
||||
RetentionHours int `json:"retention_hours"`
|
||||
CleanupIntervalMinutes int `json:"cleanup_interval_minutes"`
|
||||
RetentionHours int `json:"retention_hours"`
|
||||
EnablePatternDetection bool `json:"enable_pattern_detection"`
|
||||
EnableDetailedLogging bool `json:"enable_detailed_logging"`
|
||||
LogSuspiciousOnly bool `json:"log_suspicious_only"`
|
||||
}
|
||||
|
||||
// DefaultSecurityMonitorConfig returns a default configuration
|
||||
|
||||
+3
-10
@@ -213,17 +213,10 @@ func decompressTokenInternal(compressed string) string {
|
||||
// It provides functionality for storing and retrieving authentication state, tokens,
|
||||
// and other session-related data across multiple cookies.
|
||||
type SessionManager struct {
|
||||
// store is the underlying session store for cookie management.
|
||||
store sessions.Store
|
||||
|
||||
// forceHTTPS enforces secure cookie attributes regardless of request scheme.
|
||||
forceHTTPS bool
|
||||
|
||||
// logger provides structured logging capabilities.
|
||||
logger *Logger
|
||||
|
||||
// sessionPool is a sync.Pool for reusing SessionData objects.
|
||||
sessionPool sync.Pool
|
||||
store sessions.Store
|
||||
logger *Logger
|
||||
forceHTTPS bool
|
||||
}
|
||||
|
||||
// NewSessionManager creates a new session manager with the specified configuration.
|
||||
|
||||
+1
-1
@@ -441,8 +441,8 @@ func TestTokenChunkingCorruptionResistance(t *testing.T) {
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
corruptChunk func(chunks map[int]*sessions.Session)
|
||||
name string
|
||||
expectEmpty bool
|
||||
}{
|
||||
{
|
||||
|
||||
+21
-90
@@ -26,96 +26,27 @@ type TemplatedHeader struct {
|
||||
// It provides all necessary settings to configure OpenID Connect authentication
|
||||
// with various providers like Auth0, Logto, or any standard OIDC provider.
|
||||
type Config struct {
|
||||
// ProviderURL is the base URL of the OIDC provider (required)
|
||||
// Example: https://accounts.google.com
|
||||
ProviderURL string `json:"providerURL"`
|
||||
|
||||
// RevocationURL is the endpoint for revoking tokens (optional)
|
||||
// If not provided, it will be discovered from provider metadata
|
||||
RevocationURL string `json:"revocationURL"`
|
||||
|
||||
// EnablePKCE enables Proof Key for Code Exchange (PKCE) for the authorization code flow (optional)
|
||||
// This enhances security but might not be supported by all OIDC providers
|
||||
// Default: false
|
||||
EnablePKCE bool `json:"enablePKCE"`
|
||||
|
||||
// CallbackURL is the path where the OIDC provider will redirect after authentication (required)
|
||||
// Example: /oauth2/callback
|
||||
CallbackURL string `json:"callbackURL"`
|
||||
|
||||
// LogoutURL is the path for handling logout requests (optional)
|
||||
// If not provided, it will be set to CallbackURL + "/logout"
|
||||
LogoutURL string `json:"logoutURL"`
|
||||
|
||||
// ClientID is the OAuth 2.0 client identifier (required)
|
||||
ClientID string `json:"clientID"`
|
||||
|
||||
// ClientSecret is the OAuth 2.0 client secret (required)
|
||||
ClientSecret string `json:"clientSecret"`
|
||||
|
||||
// Scopes defines the OAuth 2.0 scopes to request (optional)
|
||||
// Defaults to ["openid", "profile", "email"] if not provided
|
||||
Scopes []string `json:"scopes"`
|
||||
|
||||
// LogLevel sets the logging verbosity (optional)
|
||||
// Valid values: "debug", "info", "error"
|
||||
// Default: "info"
|
||||
LogLevel string `json:"logLevel"`
|
||||
|
||||
// SessionEncryptionKey is used to encrypt session data (required)
|
||||
// Must be a secure random string
|
||||
SessionEncryptionKey string `json:"sessionEncryptionKey"`
|
||||
|
||||
// ForceHTTPS forces the use of HTTPS for all URLs (optional)
|
||||
// Default: false
|
||||
ForceHTTPS bool `json:"forceHTTPS"`
|
||||
|
||||
// RateLimit sets the maximum number of requests per second (optional)
|
||||
// Default: 100
|
||||
RateLimit int `json:"rateLimit"`
|
||||
|
||||
// ExcludedURLs lists paths that bypass authentication (optional)
|
||||
// Example: ["/health", "/metrics"]
|
||||
ExcludedURLs []string `json:"excludedURLs"`
|
||||
|
||||
// AllowedUserDomains restricts access to specific email domains (optional)
|
||||
// Example: ["company.com", "subsidiary.com"]
|
||||
AllowedUserDomains []string `json:"allowedUserDomains"`
|
||||
|
||||
// AllowedUsers restricts access to specific email addresses (optional)
|
||||
// Example: ["[email protected]", "[email protected]"]
|
||||
AllowedUsers []string `json:"allowedUsers"`
|
||||
|
||||
// AllowedRolesAndGroups restricts access to users with specific roles or groups (optional)
|
||||
// Example: ["admin", "developer"]
|
||||
AllowedRolesAndGroups []string `json:"allowedRolesAndGroups"`
|
||||
|
||||
// OIDCEndSessionURL is the provider's end session endpoint (optional)
|
||||
// If not provided, it will be discovered from provider metadata
|
||||
OIDCEndSessionURL string `json:"oidcEndSessionURL"`
|
||||
|
||||
// PostLogoutRedirectURI is the URL to redirect to after logout (optional)
|
||||
// Default: "/"
|
||||
PostLogoutRedirectURI string `json:"postLogoutRedirectURI"`
|
||||
|
||||
// HTTPClient allows customizing the HTTP client used for OIDC operations (optional)
|
||||
HTTPClient *http.Client
|
||||
|
||||
// RefreshGracePeriodSeconds defines how many seconds before a token expires
|
||||
// the plugin should attempt to refresh it proactively (optional)
|
||||
// Default: 60
|
||||
RefreshGracePeriodSeconds int `json:"refreshGracePeriodSeconds"`
|
||||
// Headers defines custom HTTP headers to set with templated values (optional)
|
||||
// Values can reference tokens and claims using Go templates with the following variables:
|
||||
// - {{.AccessToken}} - The access token (ID token)
|
||||
// - {{.IdToken}} - Same as AccessToken (for consistency)
|
||||
// - {{.RefreshToken}} - The refresh token
|
||||
// - {{.Claims.email}} - Access token claims (use proper case for claim names)
|
||||
// Examples:
|
||||
//
|
||||
// [{Name: "X-Forwarded-Email", Value: "{{.Claims.email}}"}]
|
||||
// [{Name: "Authorization", Value: "Bearer {{.AccessToken}}"}]
|
||||
Headers []TemplatedHeader `json:"headers"`
|
||||
HTTPClient *http.Client
|
||||
ProviderURL string `json:"providerURL"`
|
||||
RevocationURL string `json:"revocationURL"`
|
||||
CallbackURL string `json:"callbackURL"`
|
||||
LogoutURL string `json:"logoutURL"`
|
||||
ClientID string `json:"clientID"`
|
||||
ClientSecret string `json:"clientSecret"`
|
||||
PostLogoutRedirectURI string `json:"postLogoutRedirectURI"`
|
||||
LogLevel string `json:"logLevel"`
|
||||
SessionEncryptionKey string `json:"sessionEncryptionKey"`
|
||||
OIDCEndSessionURL string `json:"oidcEndSessionURL"`
|
||||
AllowedRolesAndGroups []string `json:"allowedRolesAndGroups"`
|
||||
ExcludedURLs []string `json:"excludedURLs"`
|
||||
AllowedUserDomains []string `json:"allowedUserDomains"`
|
||||
AllowedUsers []string `json:"allowedUsers"`
|
||||
Scopes []string `json:"scopes"`
|
||||
Headers []TemplatedHeader `json:"headers"`
|
||||
RateLimit int `json:"rateLimit"`
|
||||
RefreshGracePeriodSeconds int `json:"refreshGracePeriodSeconds"`
|
||||
ForceHTTPS bool `json:"forceHTTPS"`
|
||||
EnablePKCE bool `json:"enablePKCE"`
|
||||
}
|
||||
|
||||
const (
|
||||
|
||||
+4
-4
@@ -254,10 +254,10 @@ func TestLogger(t *testing.T) {
|
||||
var debugBuf, infoBuf, errorBuf bytes.Buffer
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
logLevel string
|
||||
testFunc func(*Logger)
|
||||
checkFunc func(t *testing.T, debugOut, infoOut, errorOut string)
|
||||
name string
|
||||
logLevel string
|
||||
}{
|
||||
{
|
||||
name: "Debug Level",
|
||||
@@ -405,9 +405,9 @@ func TestHandleError(t *testing.T) {
|
||||
|
||||
// Test helper types
|
||||
type testResponseRecorder struct {
|
||||
statusCode int
|
||||
body string
|
||||
headers map[string][]string
|
||||
body string
|
||||
statusCode int
|
||||
}
|
||||
|
||||
func (r *testResponseRecorder) Header() http.Header {
|
||||
|
||||
@@ -178,10 +178,10 @@ func TestTemplateExecution(t *testing.T) {
|
||||
func TestTemplateExecutionContext(t *testing.T) {
|
||||
// Define a test struct that matches the one used in processAuthorizedRequest
|
||||
type templateData struct {
|
||||
Claims map[string]any
|
||||
AccessToken string
|
||||
IdToken string
|
||||
RefreshToken string
|
||||
Claims map[string]any
|
||||
}
|
||||
|
||||
// Test cases
|
||||
|
||||
@@ -20,12 +20,12 @@ func TestTemplatedHeadersIntegration(t *testing.T) {
|
||||
ts.Setup()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
headers []TemplatedHeader
|
||||
sessionSetup func(*SessionData)
|
||||
claims map[string]any
|
||||
expectedHeaders map[string]string
|
||||
interceptedHeaders map[string]string
|
||||
name string
|
||||
headers []TemplatedHeader
|
||||
}{
|
||||
{
|
||||
name: "Basic Email Header",
|
||||
@@ -423,9 +423,9 @@ func TestEdgeCaseTemplatedHeaders(t *testing.T) {
|
||||
ts.Setup()
|
||||
|
||||
tests := []struct {
|
||||
claims map[string]any
|
||||
name string
|
||||
headers []TemplatedHeader
|
||||
claims map[string]any
|
||||
shouldExecuteCheck bool
|
||||
}{
|
||||
{
|
||||
|
||||
@@ -28,11 +28,11 @@ func TestTokenCorruptionScenario(t *testing.T) {
|
||||
validJWT := "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImV4cCI6OTk5OTk5OTk5OX0.signature"
|
||||
|
||||
tests := []struct {
|
||||
corruptionScenario func(*SessionData)
|
||||
name string
|
||||
tokenSize int
|
||||
iterations int
|
||||
expectConsistent bool
|
||||
corruptionScenario func(*SessionData) // Function to simulate corruption
|
||||
}{
|
||||
{
|
||||
name: "Small token - multiple retrievals",
|
||||
@@ -207,8 +207,8 @@ func TestChunkReassemblyEdgeCases(t *testing.T) {
|
||||
|
||||
// Test various corruption scenarios
|
||||
corruptionTests := []struct {
|
||||
name string
|
||||
corruption func(map[int]*sessions.Session)
|
||||
name string
|
||||
expectEmpty bool
|
||||
}{
|
||||
{
|
||||
@@ -367,8 +367,8 @@ func TestRaceConditionProtection(t *testing.T) {
|
||||
// TestMemoryExhaustionProtection tests protection against memory exhaustion attacks
|
||||
func TestMemoryExhaustionProtection(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
setupCorruption func() string
|
||||
name string
|
||||
expectRejection bool
|
||||
}{
|
||||
{
|
||||
|
||||
@@ -19,10 +19,10 @@ import (
|
||||
func TestTokenTypeDistinction(t *testing.T) {
|
||||
// Define test data where AccessToken and IdToken are deliberately different
|
||||
type templateData struct {
|
||||
Claims map[string]any
|
||||
AccessToken string
|
||||
IdToken string
|
||||
RefreshToken string
|
||||
Claims map[string]any
|
||||
}
|
||||
|
||||
testData := templateData{
|
||||
@@ -323,12 +323,12 @@ func TestTokenCorruptionIntegrationFlows(t *testing.T) {
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
corruptAction func(*SessionData)
|
||||
name string
|
||||
accessToken string
|
||||
refreshToken string
|
||||
idToken string
|
||||
expectSuccess bool
|
||||
corruptAction func(*SessionData) // Function to corrupt session after storage
|
||||
}{
|
||||
{
|
||||
name: "Normal flow - small tokens",
|
||||
|
||||
Reference in New Issue
Block a user