mirror of
https://github.com/lukaszraczylo/traefikoidc.git
synced 2026-10-13 04:07:37 +00:00
Cleanup.
This commit is contained in:
+1
-1
@@ -63,7 +63,7 @@ func TestAzureOIDCRegression(t *testing.T) {
|
||||
refreshGracePeriod: 60 * time.Second,
|
||||
limiter: rate.NewLimiter(rate.Every(time.Second), 100), // Add rate limiter
|
||||
logger: mockLogger,
|
||||
httpClient: createDefaultHTTPClient(), // Add HTTP client
|
||||
httpClient: CreateDefaultHTTPClient(), // Add HTTP client
|
||||
jwkCache: &JWKCache{}, // Add JWK cache
|
||||
tokenCache: tokenCache,
|
||||
tokenBlacklist: tokenBlacklist,
|
||||
|
||||
+126
-1013
File diff suppressed because it is too large
Load Diff
@@ -2,12 +2,7 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -97,115 +92,3 @@ func CreateConfig() *Config {
|
||||
Headers: []HeaderConfig{},
|
||||
}
|
||||
}
|
||||
|
||||
// InitializeTraefikOidc would initialize and configure a new TraefikOidc instance
|
||||
// This functionality has been moved to the main New function in main.go
|
||||
// This function is kept for compatibility but should not be used
|
||||
func (s *Settings) InitializeTraefikOidc(ctx context.Context, next http.Handler, config *Config, name string) (interface{}, error) {
|
||||
return nil, fmt.Errorf("InitializeTraefikOidc is deprecated - use New function from main package instead")
|
||||
}
|
||||
|
||||
//lint:ignore U1000 Kept for backward compatibility
|
||||
func (s *Settings) setupHeaderTemplates(t interface{}, config *Config, logger Logger) error {
|
||||
logger.Debug("setupHeaderTemplates is deprecated")
|
||||
return nil
|
||||
}
|
||||
|
||||
//lint:ignore U1000 May be needed for future background service management
|
||||
func (s *Settings) startBackgroundServices(ctx context.Context, logger Logger) {
|
||||
startReplayCacheCleanup(ctx, logger)
|
||||
|
||||
// Start memory monitoring for leak detection and performance insights
|
||||
memoryMonitor := GetGlobalMemoryMonitor()
|
||||
memoryMonitor.StartMonitoring(ctx, 60*time.Second) // Monitor every minute
|
||||
logger.Debug("Started global memory monitoring")
|
||||
}
|
||||
|
||||
// Utility functions
|
||||
|
||||
//lint:ignore U1000 May be needed for future scope processing
|
||||
func deduplicateScopes(scopes []string) []string {
|
||||
seen := make(map[string]bool)
|
||||
result := []string{}
|
||||
for _, scope := range scopes {
|
||||
if !seen[scope] {
|
||||
seen[scope] = true
|
||||
result = append(result, scope)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
//lint:ignore U1000 May be needed for future scope merging operations
|
||||
func mergeScopes(defaultScopes, userScopes []string) []string {
|
||||
result := make([]string, len(defaultScopes))
|
||||
copy(result, defaultScopes)
|
||||
return append(result, userScopes...)
|
||||
}
|
||||
|
||||
//lint:ignore U1000 May be needed for future utility operations
|
||||
func createStringMap(items []string) map[string]struct{} {
|
||||
result := make(map[string]struct{})
|
||||
for _, item := range items {
|
||||
result[item] = struct{}{}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
//lint:ignore U1000 May be needed for future case-insensitive operations
|
||||
func createCaseInsensitiveStringMap(items []string) map[string]struct{} {
|
||||
result := make(map[string]struct{})
|
||||
for _, item := range items {
|
||||
result[strings.ToLower(item)] = struct{}{}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
//lint:ignore U1000 May be needed for future test environment detection
|
||||
func isTestMode() bool {
|
||||
// This function should be implemented based on environment detection logic
|
||||
return false
|
||||
}
|
||||
|
||||
// External dependencies that need to be provided
|
||||
// TraefikOidc struct is defined in types.go
|
||||
|
||||
// These functions need to be provided by external packages
|
||||
func NewLogger(level string) Logger { return nil }
|
||||
func CreateDefaultHTTPClient() *http.Client { return nil }
|
||||
func CreateTokenHTTPClient() *http.Client { return nil }
|
||||
func GetGlobalCacheManager(*sync.WaitGroup) CacheManager { return nil }
|
||||
func NewSessionManager(string, bool, string, Logger) (SessionManager, error) { return nil, nil }
|
||||
func NewErrorRecoveryManager(Logger) ErrorRecoveryManager { return nil }
|
||||
|
||||
//lint:ignore U1000 May be needed for future token claim extraction
|
||||
func extractClaims(string) (map[string]interface{}, error) { return nil, nil }
|
||||
|
||||
//lint:ignore U1000 May be needed for future replay attack prevention
|
||||
func startReplayCacheCleanup(context.Context, Logger) {}
|
||||
func GetGlobalMemoryMonitor() MemoryMonitor { return nil }
|
||||
|
||||
// Interfaces for external dependencies
|
||||
type CacheManager interface {
|
||||
GetSharedTokenBlacklist() CacheInterface
|
||||
GetSharedTokenCache() *TokenCache
|
||||
GetSharedMetadataCache() *MetadataCache
|
||||
GetSharedJWKCache() JWKCacheInterface
|
||||
Close() error
|
||||
}
|
||||
type SessionManager interface{}
|
||||
type ErrorRecoveryManager interface{}
|
||||
type MemoryMonitor interface {
|
||||
StartMonitoring(ctx context.Context, interval time.Duration)
|
||||
}
|
||||
type CacheInterface interface {
|
||||
Set(key string, value interface{}, ttl time.Duration)
|
||||
Get(key string) (interface{}, bool)
|
||||
Delete(key string)
|
||||
SetMaxSize(size int)
|
||||
Cleanup()
|
||||
Close()
|
||||
}
|
||||
type TokenCache struct{}
|
||||
type MetadataCache struct{}
|
||||
type JWKCacheInterface interface{}
|
||||
|
||||
@@ -0,0 +1,247 @@
|
||||
// Package handlers provides HTTP request handlers for OIDC operations
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// SessionHandler manages session-related HTTP operations
|
||||
type SessionHandler struct {
|
||||
sessionManager SessionManager
|
||||
logger Logger
|
||||
logoutURLPath string
|
||||
postLogoutRedirectURI string
|
||||
endSessionURL string
|
||||
clientID string
|
||||
}
|
||||
|
||||
// SessionManager interface for session operations
|
||||
type SessionManager interface {
|
||||
GetSession(req *http.Request) (Session, error)
|
||||
CleanupOldCookies(rw http.ResponseWriter, req *http.Request)
|
||||
}
|
||||
|
||||
// Session interface for session data
|
||||
type Session interface {
|
||||
GetAuthenticated() bool
|
||||
SetAuthenticated(bool) error
|
||||
GetEmail() string
|
||||
SetEmail(string)
|
||||
GetIDToken() string
|
||||
GetAccessToken() string
|
||||
GetRefreshToken() string
|
||||
SetRefreshToken(string)
|
||||
Clear(req *http.Request, rw http.ResponseWriter) error
|
||||
Save(req *http.Request, rw http.ResponseWriter) error
|
||||
ReturnToPoolSafely()
|
||||
}
|
||||
|
||||
// Logger interface for logging operations
|
||||
type Logger interface {
|
||||
Debug(msg string)
|
||||
Debugf(format string, args ...interface{})
|
||||
Info(msg string)
|
||||
Infof(format string, args ...interface{})
|
||||
Error(msg string)
|
||||
Errorf(format string, args ...interface{})
|
||||
}
|
||||
|
||||
// NewSessionHandler creates a new session handler
|
||||
func NewSessionHandler(sessionManager SessionManager, logger Logger, logoutURLPath, postLogoutRedirectURI, endSessionURL, clientID string) *SessionHandler {
|
||||
return &SessionHandler{
|
||||
sessionManager: sessionManager,
|
||||
logger: logger,
|
||||
logoutURLPath: logoutURLPath,
|
||||
postLogoutRedirectURI: postLogoutRedirectURI,
|
||||
endSessionURL: endSessionURL,
|
||||
clientID: clientID,
|
||||
}
|
||||
}
|
||||
|
||||
// HandleLogout processes logout requests
|
||||
func (h *SessionHandler) HandleLogout(rw http.ResponseWriter, req *http.Request) {
|
||||
h.logger.Debug("Processing logout request")
|
||||
|
||||
session, err := h.sessionManager.GetSession(req)
|
||||
if err != nil {
|
||||
h.logger.Errorf("Error getting session during logout: %v", err)
|
||||
// Continue with logout even if session retrieval fails
|
||||
}
|
||||
|
||||
var idToken string
|
||||
if session != nil {
|
||||
defer session.ReturnToPoolSafely()
|
||||
idToken = session.GetIDToken()
|
||||
|
||||
// Clear the session
|
||||
if err := session.Clear(req, rw); err != nil {
|
||||
h.logger.Errorf("Error clearing session during logout: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Build logout URL
|
||||
logoutURL := h.buildLogoutURL(idToken)
|
||||
|
||||
h.logger.Debugf("Redirecting to logout URL: %s", logoutURL)
|
||||
http.Redirect(rw, req, logoutURL, http.StatusFound)
|
||||
}
|
||||
|
||||
// buildLogoutURL constructs the provider logout URL
|
||||
func (h *SessionHandler) buildLogoutURL(idToken string) string {
|
||||
if h.endSessionURL == "" {
|
||||
// If no end session URL, redirect to post-logout redirect URI
|
||||
return h.postLogoutRedirectURI
|
||||
}
|
||||
|
||||
logoutURL := h.endSessionURL
|
||||
|
||||
// Add query parameters
|
||||
params := make([]string, 0, 3)
|
||||
|
||||
if idToken != "" {
|
||||
params = append(params, fmt.Sprintf("id_token_hint=%s", idToken))
|
||||
}
|
||||
|
||||
if h.postLogoutRedirectURI != "" {
|
||||
params = append(params, fmt.Sprintf("post_logout_redirect_uri=%s", h.postLogoutRedirectURI))
|
||||
}
|
||||
|
||||
if h.clientID != "" {
|
||||
params = append(params, fmt.Sprintf("client_id=%s", h.clientID))
|
||||
}
|
||||
|
||||
if len(params) > 0 {
|
||||
separator := "?"
|
||||
if strings.Contains(logoutURL, "?") {
|
||||
separator = "&"
|
||||
}
|
||||
logoutURL += separator + strings.Join(params, "&")
|
||||
}
|
||||
|
||||
return logoutURL
|
||||
}
|
||||
|
||||
// ValidateSession checks if a session is valid and authenticated
|
||||
func (h *SessionHandler) ValidateSession(session Session) SessionValidationResult {
|
||||
if session == nil {
|
||||
return SessionValidationResult{
|
||||
Valid: false,
|
||||
NeedsAuth: true,
|
||||
ErrorMessage: "session is nil",
|
||||
}
|
||||
}
|
||||
|
||||
if !session.GetAuthenticated() {
|
||||
return SessionValidationResult{
|
||||
Valid: false,
|
||||
NeedsAuth: true,
|
||||
ErrorMessage: "session not authenticated",
|
||||
}
|
||||
}
|
||||
|
||||
email := session.GetEmail()
|
||||
if email == "" {
|
||||
return SessionValidationResult{
|
||||
Valid: false,
|
||||
NeedsAuth: true,
|
||||
ErrorMessage: "no email in session",
|
||||
}
|
||||
}
|
||||
|
||||
return SessionValidationResult{
|
||||
Valid: true,
|
||||
NeedsAuth: false,
|
||||
}
|
||||
}
|
||||
|
||||
// SessionValidationResult represents the result of session validation
|
||||
type SessionValidationResult struct {
|
||||
Valid bool
|
||||
NeedsAuth bool
|
||||
ErrorMessage string
|
||||
}
|
||||
|
||||
// CleanupExpiredSession clears an expired session
|
||||
func (h *SessionHandler) CleanupExpiredSession(rw http.ResponseWriter, req *http.Request, session Session) error {
|
||||
h.logger.Debug("Cleaning up expired session")
|
||||
|
||||
if session == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Clear all session data
|
||||
if err := session.SetAuthenticated(false); err != nil {
|
||||
h.logger.Errorf("Failed to set authenticated to false: %v", err)
|
||||
}
|
||||
|
||||
session.SetEmail("")
|
||||
session.SetRefreshToken("")
|
||||
|
||||
// Save the cleared session
|
||||
if err := session.Save(req, rw); err != nil {
|
||||
h.logger.Errorf("Failed to save cleared session: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsAjaxRequest determines if the request is an AJAX/XHR request
|
||||
func (h *SessionHandler) IsAjaxRequest(req *http.Request) bool {
|
||||
// Check X-Requested-With header (commonly used by jQuery and other libraries)
|
||||
if req.Header.Get("X-Requested-With") == "XMLHttpRequest" {
|
||||
return true
|
||||
}
|
||||
|
||||
// Check Accept header for JSON preference
|
||||
accept := req.Header.Get("Accept")
|
||||
if strings.Contains(accept, "application/json") && !strings.Contains(accept, "text/html") {
|
||||
return true
|
||||
}
|
||||
|
||||
// Check for fetch API indication
|
||||
if req.Header.Get("Sec-Fetch-Mode") == "cors" {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// SendErrorResponse sends an appropriate error response based on request type
|
||||
func (h *SessionHandler) SendErrorResponse(rw http.ResponseWriter, req *http.Request, message string, statusCode int) {
|
||||
if h.IsAjaxRequest(req) {
|
||||
// For AJAX requests, send JSON response
|
||||
rw.Header().Set("Content-Type", "application/json")
|
||||
rw.WriteHeader(statusCode)
|
||||
fmt.Fprintf(rw, `{"error": "%s"}`, message)
|
||||
} else {
|
||||
// For browser requests, send HTML response
|
||||
rw.Header().Set("Content-Type", "text/html")
|
||||
rw.WriteHeader(statusCode)
|
||||
fmt.Fprintf(rw, `<html><body><h1>Error %d</h1><p>%s</p></body></html>`, statusCode, message)
|
||||
}
|
||||
}
|
||||
|
||||
// SetSecurityHeaders sets standard security headers
|
||||
func (h *SessionHandler) SetSecurityHeaders(rw http.ResponseWriter, req *http.Request) {
|
||||
rw.Header().Set("X-Frame-Options", "DENY")
|
||||
rw.Header().Set("X-Content-Type-Options", "nosniff")
|
||||
rw.Header().Set("X-XSS-Protection", "1; mode=block")
|
||||
rw.Header().Set("Referrer-Policy", "strict-origin-when-cross-origin")
|
||||
|
||||
// Handle CORS for AJAX requests
|
||||
origin := req.Header.Get("Origin")
|
||||
if origin != "" {
|
||||
rw.Header().Set("Access-Control-Allow-Origin", origin)
|
||||
rw.Header().Set("Access-Control-Allow-Credentials", "true")
|
||||
rw.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
|
||||
rw.Header().Set("Access-Control-Allow-Headers", "Authorization, Content-Type")
|
||||
|
||||
if req.Method == "OPTIONS" {
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,393 @@
|
||||
// Package testing provides unified mock implementations for tests
|
||||
package testing
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// UnifiedMockLogger provides a standard mock logger for all tests
|
||||
type UnifiedMockLogger struct {
|
||||
LoggedMessages []string
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
func NewUnifiedMockLogger() *UnifiedMockLogger {
|
||||
return &UnifiedMockLogger{
|
||||
LoggedMessages: make([]string, 0),
|
||||
}
|
||||
}
|
||||
|
||||
func (l *UnifiedMockLogger) Debug(msg string) {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
l.LoggedMessages = append(l.LoggedMessages, fmt.Sprintf("DEBUG: %s", msg))
|
||||
}
|
||||
|
||||
func (l *UnifiedMockLogger) Debugf(format string, args ...interface{}) {
|
||||
l.Debug(fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
func (l *UnifiedMockLogger) Info(msg string) {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
l.LoggedMessages = append(l.LoggedMessages, fmt.Sprintf("INFO: %s", msg))
|
||||
}
|
||||
|
||||
func (l *UnifiedMockLogger) Infof(format string, args ...interface{}) {
|
||||
l.Info(fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
func (l *UnifiedMockLogger) Error(msg string) {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
l.LoggedMessages = append(l.LoggedMessages, fmt.Sprintf("ERROR: %s", msg))
|
||||
}
|
||||
|
||||
func (l *UnifiedMockLogger) Errorf(format string, args ...interface{}) {
|
||||
l.Error(fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
func (l *UnifiedMockLogger) GetMessages() []string {
|
||||
l.mu.RLock()
|
||||
defer l.mu.RUnlock()
|
||||
result := make([]string, len(l.LoggedMessages))
|
||||
copy(result, l.LoggedMessages)
|
||||
return result
|
||||
}
|
||||
|
||||
func (l *UnifiedMockLogger) Clear() {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
l.LoggedMessages = l.LoggedMessages[:0]
|
||||
}
|
||||
|
||||
// UnifiedMockSession provides a standard mock session for all tests
|
||||
type UnifiedMockSession struct {
|
||||
authenticated bool
|
||||
idToken string
|
||||
accessToken string
|
||||
refreshToken string
|
||||
email string
|
||||
csrf string
|
||||
nonce string
|
||||
codeVerifier string
|
||||
incomingPath string
|
||||
redirectCount int
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
func NewUnifiedMockSession() *UnifiedMockSession {
|
||||
return &UnifiedMockSession{}
|
||||
}
|
||||
|
||||
func (s *UnifiedMockSession) GetAuthenticated() bool {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
return s.authenticated
|
||||
}
|
||||
|
||||
func (s *UnifiedMockSession) SetAuthenticated(auth bool) error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.authenticated = auth
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *UnifiedMockSession) GetIDToken() string {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
return s.idToken
|
||||
}
|
||||
|
||||
func (s *UnifiedMockSession) SetIDToken(token string) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.idToken = token
|
||||
}
|
||||
|
||||
func (s *UnifiedMockSession) GetAccessToken() string {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
return s.accessToken
|
||||
}
|
||||
|
||||
func (s *UnifiedMockSession) SetAccessToken(token string) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.accessToken = token
|
||||
}
|
||||
|
||||
func (s *UnifiedMockSession) GetRefreshToken() string {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
return s.refreshToken
|
||||
}
|
||||
|
||||
func (s *UnifiedMockSession) SetRefreshToken(token string) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.refreshToken = token
|
||||
}
|
||||
|
||||
func (s *UnifiedMockSession) GetEmail() string {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
return s.email
|
||||
}
|
||||
|
||||
func (s *UnifiedMockSession) SetEmail(email string) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.email = email
|
||||
}
|
||||
|
||||
func (s *UnifiedMockSession) GetCSRF() string {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
return s.csrf
|
||||
}
|
||||
|
||||
func (s *UnifiedMockSession) SetCSRF(csrf string) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.csrf = csrf
|
||||
}
|
||||
|
||||
func (s *UnifiedMockSession) GetNonce() string {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
return s.nonce
|
||||
}
|
||||
|
||||
func (s *UnifiedMockSession) SetNonce(nonce string) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.nonce = nonce
|
||||
}
|
||||
|
||||
func (s *UnifiedMockSession) GetCodeVerifier() string {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
return s.codeVerifier
|
||||
}
|
||||
|
||||
func (s *UnifiedMockSession) SetCodeVerifier(verifier string) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.codeVerifier = verifier
|
||||
}
|
||||
|
||||
func (s *UnifiedMockSession) GetIncomingPath() string {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
return s.incomingPath
|
||||
}
|
||||
|
||||
func (s *UnifiedMockSession) SetIncomingPath(path string) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.incomingPath = path
|
||||
}
|
||||
|
||||
func (s *UnifiedMockSession) GetRedirectCount() int {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
return s.redirectCount
|
||||
}
|
||||
|
||||
func (s *UnifiedMockSession) IncrementRedirectCount() {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.redirectCount++
|
||||
}
|
||||
|
||||
func (s *UnifiedMockSession) ResetRedirectCount() {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.redirectCount = 0
|
||||
}
|
||||
|
||||
func (s *UnifiedMockSession) Save(req *http.Request, rw http.ResponseWriter) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *UnifiedMockSession) Clear(req *http.Request, rw http.ResponseWriter) error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.authenticated = false
|
||||
s.idToken = ""
|
||||
s.accessToken = ""
|
||||
s.refreshToken = ""
|
||||
s.email = ""
|
||||
s.csrf = ""
|
||||
s.nonce = ""
|
||||
s.codeVerifier = ""
|
||||
s.incomingPath = ""
|
||||
s.redirectCount = 0
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *UnifiedMockSession) MarkDirty() {}
|
||||
|
||||
func (s *UnifiedMockSession) IsDirty() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *UnifiedMockSession) ReturnToPoolSafely() {}
|
||||
|
||||
// UnifiedMockTokenVerifier provides a standard mock token verifier
|
||||
type UnifiedMockTokenVerifier struct {
|
||||
ShouldFail bool
|
||||
Error error
|
||||
}
|
||||
|
||||
func NewUnifiedMockTokenVerifier() *UnifiedMockTokenVerifier {
|
||||
return &UnifiedMockTokenVerifier{}
|
||||
}
|
||||
|
||||
func (v *UnifiedMockTokenVerifier) VerifyToken(token string) error {
|
||||
if v.ShouldFail {
|
||||
if v.Error != nil {
|
||||
return v.Error
|
||||
}
|
||||
return fmt.Errorf("mock verification failed")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnifiedMockTokenCache provides a standard mock token cache
|
||||
type UnifiedMockTokenCache struct {
|
||||
data map[string]map[string]interface{}
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
func NewUnifiedMockTokenCache() *UnifiedMockTokenCache {
|
||||
return &UnifiedMockTokenCache{
|
||||
data: make(map[string]map[string]interface{}),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *UnifiedMockTokenCache) Get(key string) (map[string]interface{}, bool) {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
value, exists := c.data[key]
|
||||
return value, exists
|
||||
}
|
||||
|
||||
func (c *UnifiedMockTokenCache) Set(key string, claims map[string]interface{}, ttl time.Duration) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
c.data[key] = claims
|
||||
}
|
||||
|
||||
func (c *UnifiedMockTokenCache) Delete(key string) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
delete(c.data, key)
|
||||
}
|
||||
|
||||
func (c *UnifiedMockTokenCache) SetMaxSize(size int) {}
|
||||
|
||||
func (c *UnifiedMockTokenCache) Size() int {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
return len(c.data)
|
||||
}
|
||||
|
||||
func (c *UnifiedMockTokenCache) Clear() {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
c.data = make(map[string]map[string]interface{})
|
||||
}
|
||||
|
||||
func (c *UnifiedMockTokenCache) Cleanup() {}
|
||||
|
||||
func (c *UnifiedMockTokenCache) Close() {}
|
||||
|
||||
func (c *UnifiedMockTokenCache) GetStats() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"size": c.Size(),
|
||||
}
|
||||
}
|
||||
|
||||
// UnifiedMockHTTPClient provides a mock HTTP client for tests
|
||||
type UnifiedMockHTTPClient struct {
|
||||
Responses map[string]*http.Response
|
||||
Errors map[string]error
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
func NewUnifiedMockHTTPClient() *UnifiedMockHTTPClient {
|
||||
return &UnifiedMockHTTPClient{
|
||||
Responses: make(map[string]*http.Response),
|
||||
Errors: make(map[string]error),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *UnifiedMockHTTPClient) Do(req *http.Request) (*http.Response, error) {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
|
||||
url := req.URL.String()
|
||||
if err, exists := c.Errors[url]; exists {
|
||||
return nil, err
|
||||
}
|
||||
if resp, exists := c.Responses[url]; exists {
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// Default response
|
||||
return &http.Response{
|
||||
StatusCode: 200,
|
||||
Body: http.NoBody,
|
||||
Header: make(http.Header),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *UnifiedMockHTTPClient) SetResponse(url string, response *http.Response) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
c.Responses[url] = response
|
||||
}
|
||||
|
||||
func (c *UnifiedMockHTTPClient) SetError(url string, err error) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
c.Errors[url] = err
|
||||
}
|
||||
|
||||
// TestSuite provides a unified test setup and teardown
|
||||
type TestSuite struct {
|
||||
Logger *UnifiedMockLogger
|
||||
Session *UnifiedMockSession
|
||||
TokenVerifier *UnifiedMockTokenVerifier
|
||||
TokenCache *UnifiedMockTokenCache
|
||||
HTTPClient *UnifiedMockHTTPClient
|
||||
}
|
||||
|
||||
func NewTestSuite() *TestSuite {
|
||||
return &TestSuite{
|
||||
Logger: NewUnifiedMockLogger(),
|
||||
Session: NewUnifiedMockSession(),
|
||||
TokenVerifier: NewUnifiedMockTokenVerifier(),
|
||||
TokenCache: NewUnifiedMockTokenCache(),
|
||||
HTTPClient: NewUnifiedMockHTTPClient(),
|
||||
}
|
||||
}
|
||||
|
||||
func (ts *TestSuite) Setup() {
|
||||
// Common test setup
|
||||
ts.Logger.Clear()
|
||||
ts.Session.Clear(nil, nil)
|
||||
ts.TokenCache.Clear()
|
||||
ts.TokenVerifier.ShouldFail = false
|
||||
ts.TokenVerifier.Error = nil
|
||||
}
|
||||
|
||||
func (ts *TestSuite) Teardown() {
|
||||
// Common test teardown
|
||||
ts.TokenCache.Close()
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
// Package token provides token verification and management functionality
|
||||
package token
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Verifier handles token verification operations
|
||||
type Verifier struct {
|
||||
tokenCache TokenCache
|
||||
tokenBlacklist Cache
|
||||
jwkCache JWKCache
|
||||
limiter RateLimiter
|
||||
logger Logger
|
||||
}
|
||||
|
||||
// Cache interface for token operations
|
||||
type Cache interface {
|
||||
Get(key string) (interface{}, bool)
|
||||
Set(key string, value interface{}, ttl time.Duration)
|
||||
}
|
||||
|
||||
// TokenCache interface for verified token storage
|
||||
type TokenCache interface {
|
||||
Get(key string) (map[string]interface{}, bool)
|
||||
Set(key string, claims map[string]interface{}, ttl time.Duration)
|
||||
}
|
||||
|
||||
// JWKCache interface for key management
|
||||
type JWKCache interface {
|
||||
GetJWKS(providerURL string) (*JWKS, error)
|
||||
}
|
||||
|
||||
// RateLimiter interface for request limiting
|
||||
type RateLimiter interface {
|
||||
Allow() bool
|
||||
}
|
||||
|
||||
// Logger interface for logging
|
||||
type Logger interface {
|
||||
Debugf(format string, args ...interface{})
|
||||
Errorf(format string, args ...interface{})
|
||||
}
|
||||
|
||||
// JWKS represents JSON Web Key Set
|
||||
type JWKS struct {
|
||||
Keys []JWK `json:"keys"`
|
||||
}
|
||||
|
||||
// JWK represents a JSON Web Key
|
||||
type JWK struct {
|
||||
Kid string `json:"kid"`
|
||||
Kty string `json:"kty"`
|
||||
Use string `json:"use"`
|
||||
N string `json:"n"`
|
||||
E string `json:"e"`
|
||||
}
|
||||
|
||||
// JWT represents a parsed JWT token
|
||||
type JWT struct {
|
||||
Header map[string]interface{}
|
||||
Claims map[string]interface{}
|
||||
}
|
||||
|
||||
// NewVerifier creates a new token verifier
|
||||
func NewVerifier(tokenCache TokenCache, tokenBlacklist Cache, jwkCache JWKCache, limiter RateLimiter, logger Logger) *Verifier {
|
||||
return &Verifier{
|
||||
tokenCache: tokenCache,
|
||||
tokenBlacklist: tokenBlacklist,
|
||||
jwkCache: jwkCache,
|
||||
limiter: limiter,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// VerifyToken verifies the validity of an ID token or access token
|
||||
func (v *Verifier) VerifyToken(token string, clientID string, jwksURL string, issuerURL string) error {
|
||||
if token == "" {
|
||||
return fmt.Errorf("invalid JWT format: token is empty")
|
||||
}
|
||||
|
||||
if strings.Count(token, ".") != 2 {
|
||||
return fmt.Errorf("invalid JWT format: expected JWT with 3 parts, got %d parts", strings.Count(token, ".")+1)
|
||||
}
|
||||
|
||||
if len(token) < 10 {
|
||||
return fmt.Errorf("token too short to be valid JWT")
|
||||
}
|
||||
|
||||
// Check blacklist
|
||||
if v.tokenBlacklist != nil {
|
||||
if blacklisted, exists := v.tokenBlacklist.Get(token); exists && blacklisted != nil {
|
||||
return fmt.Errorf("token is blacklisted")
|
||||
}
|
||||
}
|
||||
|
||||
// Check cache first
|
||||
if claims, exists := v.tokenCache.Get(token); exists && len(claims) > 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Rate limiting
|
||||
if !v.limiter.Allow() {
|
||||
return fmt.Errorf("rate limit exceeded")
|
||||
}
|
||||
|
||||
// Parse and verify JWT
|
||||
jwt, err := v.parseJWT(token)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse JWT: %w", err)
|
||||
}
|
||||
|
||||
if err := v.verifyJWTSignatureAndClaims(jwt, token, clientID, jwksURL, issuerURL); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Cache successful verification
|
||||
v.cacheVerifiedToken(token, jwt.Claims)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// parseJWT parses a JWT token into its components
|
||||
func (v *Verifier) parseJWT(token string) (*JWT, error) {
|
||||
// This would contain the actual JWT parsing logic
|
||||
// For now, return a placeholder
|
||||
return &JWT{
|
||||
Header: make(map[string]interface{}),
|
||||
Claims: make(map[string]interface{}),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// verifyJWTSignatureAndClaims verifies JWT signature and claims
|
||||
func (v *Verifier) verifyJWTSignatureAndClaims(jwt *JWT, token string, clientID string, jwksURL string, issuerURL string) error {
|
||||
// This would contain the actual signature verification logic
|
||||
// For now, return nil (placeholder)
|
||||
return nil
|
||||
}
|
||||
|
||||
// cacheVerifiedToken stores a successfully verified token
|
||||
func (v *Verifier) cacheVerifiedToken(token string, claims map[string]interface{}) {
|
||||
if expClaim, ok := claims["exp"].(float64); ok {
|
||||
expirationTime := time.Unix(int64(expClaim), 0)
|
||||
duration := time.Until(expirationTime)
|
||||
if duration > 0 {
|
||||
v.tokenCache.Set(token, claims, duration)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
// Package utils provides common utility functions used across the OIDC middleware
|
||||
package utils
|
||||
|
||||
import (
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// CreateStringMap creates a map with string keys for efficient lookups
|
||||
func CreateStringMap(items []string) map[string]struct{} {
|
||||
result := make(map[string]struct{})
|
||||
for _, item := range items {
|
||||
result[item] = struct{}{}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// CreateCaseInsensitiveStringMap creates a map with lowercase keys for case-insensitive matching
|
||||
func CreateCaseInsensitiveStringMap(items []string) map[string]struct{} {
|
||||
result := make(map[string]struct{})
|
||||
for _, item := range items {
|
||||
result[strings.ToLower(item)] = struct{}{}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// DeduplicateScopes removes duplicate scopes from a slice
|
||||
func DeduplicateScopes(scopes []string) []string {
|
||||
seen := make(map[string]bool)
|
||||
result := []string{}
|
||||
for _, scope := range scopes {
|
||||
if !seen[scope] {
|
||||
seen[scope] = true
|
||||
result = append(result, scope)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// MergeScopes combines default scopes with user-provided scopes, removing duplicates
|
||||
func MergeScopes(defaultScopes, userScopes []string) []string {
|
||||
if len(userScopes) == 0 {
|
||||
return append([]string(nil), defaultScopes...)
|
||||
}
|
||||
|
||||
seen := make(map[string]bool)
|
||||
var result []string
|
||||
|
||||
for _, scope := range defaultScopes {
|
||||
if !seen[scope] {
|
||||
seen[scope] = true
|
||||
result = append(result, scope)
|
||||
}
|
||||
}
|
||||
|
||||
for _, scope := range userScopes {
|
||||
if !seen[scope] {
|
||||
seen[scope] = true
|
||||
result = append(result, scope)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// IsTestMode detects if the code is running in a test environment
|
||||
func IsTestMode() bool {
|
||||
if os.Getenv("SUPPRESS_DIAGNOSTIC_LOGS") == "1" {
|
||||
return true
|
||||
}
|
||||
|
||||
if strings.Contains(os.Args[0], ".test") ||
|
||||
strings.Contains(os.Args[0], "go_build_") ||
|
||||
os.Getenv("GO_TEST") == "1" ||
|
||||
runtime.Compiler == "yaegi" {
|
||||
return true
|
||||
}
|
||||
|
||||
for _, arg := range os.Args {
|
||||
if strings.Contains(arg, "-test") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
if runtime.Compiler == "gc" {
|
||||
progName := os.Args[0]
|
||||
if strings.Contains(progName, "test") ||
|
||||
strings.HasSuffix(progName, ".test") ||
|
||||
strings.Contains(progName, "__debug_bin") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// Only use runtime stack check as fallback when no explicit test conditions are being controlled
|
||||
if os.Getenv("DISABLE_RUNTIME_STACK_CHECK") != "1" &&
|
||||
os.Getenv("SUPPRESS_DIAGNOSTIC_LOGS") == "" &&
|
||||
os.Getenv("GO_TEST") == "" {
|
||||
// Check runtime stack for test functions only as last resort
|
||||
buf := make([]byte, 2048)
|
||||
n := runtime.Stack(buf, false)
|
||||
stack := string(buf[:n])
|
||||
if strings.Contains(stack, "testing.tRunner") ||
|
||||
strings.Contains(stack, "testing.(*T)") ||
|
||||
strings.Contains(stack, ".test.") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// KeysFromMap extracts string keys from a map for logging purposes
|
||||
func KeysFromMap(m map[string]struct{}) []string {
|
||||
keys := make([]string, 0, len(m))
|
||||
for k := range m {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
return keys
|
||||
}
|
||||
|
||||
// BuildFullURL constructs a URL from scheme, host, and path components
|
||||
func BuildFullURL(scheme, host, path string) string {
|
||||
return scheme + "://" + host + path
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCreateStringMap(t *testing.T) {
|
||||
items := []string{"apple", "banana", "cherry"}
|
||||
result := CreateStringMap(items)
|
||||
|
||||
expected := map[string]struct{}{
|
||||
"apple": {},
|
||||
"banana": {},
|
||||
"cherry": {},
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(result, expected) {
|
||||
t.Errorf("Expected %v, got %v", expected, result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateCaseInsensitiveStringMap(t *testing.T) {
|
||||
items := []string{"Apple", "BANANA", "Cherry"}
|
||||
result := CreateCaseInsensitiveStringMap(items)
|
||||
|
||||
expected := map[string]struct{}{
|
||||
"apple": {},
|
||||
"banana": {},
|
||||
"cherry": {},
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(result, expected) {
|
||||
t.Errorf("Expected %v, got %v", expected, result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeduplicateScopes(t *testing.T) {
|
||||
scopes := []string{"openid", "profile", "email", "openid", "profile"}
|
||||
result := DeduplicateScopes(scopes)
|
||||
|
||||
expected := []string{"openid", "profile", "email"}
|
||||
|
||||
if !reflect.DeepEqual(result, expected) {
|
||||
t.Errorf("Expected %v, got %v", expected, result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMergeScopes(t *testing.T) {
|
||||
defaultScopes := []string{"openid", "profile"}
|
||||
userScopes := []string{"email", "offline_access"}
|
||||
result := MergeScopes(defaultScopes, userScopes)
|
||||
|
||||
expected := []string{"openid", "profile", "email", "offline_access"}
|
||||
|
||||
if !reflect.DeepEqual(result, expected) {
|
||||
t.Errorf("Expected %v, got %v", expected, result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMergeScopesWithDuplicates(t *testing.T) {
|
||||
defaultScopes := []string{"openid", "profile"}
|
||||
userScopes := []string{"profile", "email", "openid"}
|
||||
result := MergeScopes(defaultScopes, userScopes)
|
||||
|
||||
expected := []string{"openid", "profile", "email"}
|
||||
|
||||
if !reflect.DeepEqual(result, expected) {
|
||||
t.Errorf("Expected %v, got %v", expected, result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMergeScopesEmptyUserScopes(t *testing.T) {
|
||||
defaultScopes := []string{"openid", "profile"}
|
||||
userScopes := []string{}
|
||||
result := MergeScopes(defaultScopes, userScopes)
|
||||
|
||||
expected := []string{"openid", "profile"}
|
||||
|
||||
if !reflect.DeepEqual(result, expected) {
|
||||
t.Errorf("Expected %v, got %v", expected, result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeysFromMap(t *testing.T) {
|
||||
m := map[string]struct{}{
|
||||
"key1": {},
|
||||
"key2": {},
|
||||
"key3": {},
|
||||
}
|
||||
result := KeysFromMap(m)
|
||||
|
||||
// Since map iteration order is not guaranteed, we need to check length and presence
|
||||
if len(result) != 3 {
|
||||
t.Errorf("Expected 3 keys, got %d", len(result))
|
||||
}
|
||||
|
||||
resultMap := make(map[string]bool)
|
||||
for _, key := range result {
|
||||
resultMap[key] = true
|
||||
}
|
||||
|
||||
expectedKeys := []string{"key1", "key2", "key3"}
|
||||
for _, key := range expectedKeys {
|
||||
if !resultMap[key] {
|
||||
t.Errorf("Expected key %s not found in result", key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildFullURL(t *testing.T) {
|
||||
tests := []struct {
|
||||
scheme string
|
||||
host string
|
||||
path string
|
||||
expected string
|
||||
}{
|
||||
{"https", "example.com", "/path", "https://example.com/path"},
|
||||
{"http", "localhost:8080", "/callback", "http://localhost:8080/callback"},
|
||||
{"https", "test.example.com", "/auth/callback", "https://test.example.com/auth/callback"},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
result := BuildFullURL(test.scheme, test.host, test.path)
|
||||
if result != test.expected {
|
||||
t.Errorf("For scheme=%s, host=%s, path=%s: expected %s, got %s",
|
||||
test.scheme, test.host, test.path, test.expected, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,22 +24,11 @@ import (
|
||||
"golang.org/x/time/rate"
|
||||
)
|
||||
|
||||
// Deprecated: Use CreateDefaultHTTPClient from http_client_factory.go instead
|
||||
// createDefaultHTTPClient is kept for backward compatibility
|
||||
func createDefaultHTTPClient() *http.Client {
|
||||
return CreateDefaultHTTPClient()
|
||||
}
|
||||
|
||||
const (
|
||||
ConstSessionTimeout = 86400
|
||||
)
|
||||
|
||||
// isTestMode detects if the code is running in a test environment.
|
||||
// It checks various indicators including environment variables, command-line arguments,
|
||||
// and runtime compiler information to determine test context.
|
||||
// This helps suppress diagnostic logs during testing to keep test output clean.
|
||||
// Returns:
|
||||
// - true if running in test mode, false otherwise.
|
||||
func isTestMode() bool {
|
||||
if os.Getenv("SUPPRESS_DIAGNOSTIC_LOGS") == "1" {
|
||||
return true
|
||||
@@ -58,35 +47,35 @@ func isTestMode() bool {
|
||||
}
|
||||
}
|
||||
|
||||
if runtime.Compiler == "gc" {
|
||||
progName := os.Args[0]
|
||||
if strings.Contains(progName, "test") ||
|
||||
strings.HasSuffix(progName, ".test") ||
|
||||
strings.Contains(progName, "__debug_bin") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// Only use runtime stack check as fallback when no explicit test conditions are being controlled
|
||||
// This prevents interference with unit tests that want to test false conditions
|
||||
// Skip runtime stack check if explicitly disabled for testing
|
||||
if os.Getenv("DISABLE_RUNTIME_STACK_CHECK") != "1" &&
|
||||
os.Getenv("SUPPRESS_DIAGNOSTIC_LOGS") == "" &&
|
||||
os.Getenv("GO_TEST") == "" {
|
||||
// Check runtime stack for test functions only as last resort
|
||||
buf := make([]byte, 2048)
|
||||
n := runtime.Stack(buf, false)
|
||||
stack := string(buf[:n])
|
||||
if strings.Contains(stack, "testing.tRunner") ||
|
||||
strings.Contains(stack, "testing.(*T)") ||
|
||||
strings.Contains(stack, ".test.") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// mergeScopes combines default scopes with user-provided scopes, removing duplicates.
|
||||
func mergeScopes(defaultScopes, userScopes []string) []string {
|
||||
if len(userScopes) == 0 {
|
||||
return append([]string(nil), defaultScopes...)
|
||||
}
|
||||
|
||||
seen := make(map[string]bool)
|
||||
var result []string
|
||||
|
||||
for _, scope := range defaultScopes {
|
||||
if !seen[scope] {
|
||||
seen[scope] = true
|
||||
result = append(result, scope)
|
||||
}
|
||||
}
|
||||
|
||||
for _, scope := range userScopes {
|
||||
if !seen[scope] {
|
||||
seen[scope] = true
|
||||
result = append(result, scope)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// defaultExcludedURLs are the paths that are excluded from authentication
|
||||
var defaultExcludedURLs = map[string]struct{}{
|
||||
"/favicon": {},
|
||||
@@ -304,39 +293,6 @@ func (t *TraefikOidc) VerifyJWTSignatureAndClaims(jwt *JWT, token string) error
|
||||
return nil
|
||||
}
|
||||
|
||||
// mergeScopes combines default scopes with user-provided scopes, removing duplicates.
|
||||
// Default scopes are placed first, followed by user scopes not already present.
|
||||
// Parameters:
|
||||
// - defaultScopes: The default scopes required by the application.
|
||||
// - userScopes: Additional scopes specified by the user.
|
||||
//
|
||||
// Returns:
|
||||
// - A slice containing merged scopes with defaults first, then user scopes, with duplicates removed.
|
||||
func mergeScopes(defaultScopes, userScopes []string) []string {
|
||||
if len(userScopes) == 0 {
|
||||
return append([]string(nil), defaultScopes...)
|
||||
}
|
||||
|
||||
seen := make(map[string]bool)
|
||||
var result []string
|
||||
|
||||
for _, scope := range defaultScopes {
|
||||
if !seen[scope] {
|
||||
seen[scope] = true
|
||||
result = append(result, scope)
|
||||
}
|
||||
}
|
||||
|
||||
for _, scope := range userScopes {
|
||||
if !seen[scope] {
|
||||
seen[scope] = true
|
||||
result = append(result, scope)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// New creates a new TraefikOidc middleware instance.
|
||||
// It initializes all components including caches, HTTP clients, session management,
|
||||
// templates, and starts background processes for metadata discovery.
|
||||
|
||||
+2
-2
@@ -139,7 +139,7 @@ func TestComponentProfilers(t *testing.T) {
|
||||
}
|
||||
|
||||
// Test HTTP Client Profiler
|
||||
httpClient := createDefaultHTTPClient()
|
||||
httpClient := CreateDefaultHTTPClient()
|
||||
hcp := NewHTTPClientProfiler(httpClient, logger)
|
||||
snapshot, err = hcp.TakeSnapshot()
|
||||
if err != nil {
|
||||
@@ -440,7 +440,7 @@ func TestProviderMetadataMemoryLeakDetection(t *testing.T) {
|
||||
defer mockServer.Close()
|
||||
|
||||
providerURL := fmt.Sprintf("http://%s", listener.Addr().String())
|
||||
httpClient := createDefaultHTTPClient()
|
||||
httpClient := CreateDefaultHTTPClient()
|
||||
|
||||
// Create metadata cache
|
||||
metadataCache := NewMetadataCacheWithLogger(nil, logger)
|
||||
|
||||
+2
-2
@@ -324,9 +324,9 @@ func TestTraefikOidcHelperMethods(t *testing.T) {
|
||||
traefikNilLogger.safeLogInfo("test info with nil logger")
|
||||
}
|
||||
|
||||
// Test createDefaultHTTPClient function
|
||||
// Test CreateDefaultHTTPClient function
|
||||
func TestCreateDefaultHTTPClient(t *testing.T) {
|
||||
client := createDefaultHTTPClient()
|
||||
client := CreateDefaultHTTPClient()
|
||||
|
||||
if client == nil {
|
||||
t.Fatal("createDefaultHTTPClient() returned nil")
|
||||
|
||||
Reference in New Issue
Block a user