mirror of
https://github.com/lukaszraczylo/traefikoidc.git
synced 2026-10-14 04:07:46 +00:00
217 lines
6.2 KiB
Go
217 lines
6.2 KiB
Go
package traefikoidc
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/gorilla/sessions"
|
|
)
|
|
|
|
// TokenConfig holds validation rules for different token types
|
|
type TokenConfig struct {
|
|
Type string
|
|
MinLength int
|
|
MaxLength int
|
|
AllowOpaqueTokens bool
|
|
RequireJWTFormat bool
|
|
}
|
|
|
|
// Predefined configurations for each token type
|
|
var (
|
|
AccessTokenConfig = TokenConfig{
|
|
Type: "access",
|
|
MinLength: 5,
|
|
MaxLength: 50 * 1024,
|
|
AllowOpaqueTokens: true,
|
|
RequireJWTFormat: false,
|
|
}
|
|
|
|
RefreshTokenConfig = TokenConfig{
|
|
Type: "refresh",
|
|
MinLength: 5,
|
|
MaxLength: 50 * 1024,
|
|
AllowOpaqueTokens: true,
|
|
RequireJWTFormat: false,
|
|
}
|
|
|
|
IDTokenConfig = TokenConfig{
|
|
Type: "id",
|
|
MinLength: 5,
|
|
MaxLength: 50 * 1024,
|
|
AllowOpaqueTokens: false,
|
|
RequireJWTFormat: true,
|
|
}
|
|
)
|
|
|
|
// TokenRetrievalResult encapsulates the result of token retrieval
|
|
type TokenRetrievalResult struct {
|
|
Token string
|
|
Error error
|
|
}
|
|
|
|
// ChunkManager handles token chunking operations
|
|
type ChunkManager struct {
|
|
logger *Logger
|
|
mutex *sync.RWMutex
|
|
}
|
|
|
|
// NewChunkManager creates a new ChunkManager instance
|
|
func NewChunkManager(logger *Logger) *ChunkManager {
|
|
if logger == nil {
|
|
logger = newNoOpLogger()
|
|
}
|
|
|
|
return &ChunkManager{
|
|
logger: logger,
|
|
mutex: &sync.RWMutex{},
|
|
}
|
|
}
|
|
|
|
// GetToken retrieves and validates a token from either single storage or chunks
|
|
func (cm *ChunkManager) GetToken(
|
|
singleToken string,
|
|
compressed bool,
|
|
chunks map[int]*sessions.Session,
|
|
config TokenConfig,
|
|
) TokenRetrievalResult {
|
|
cm.mutex.RLock()
|
|
defer cm.mutex.RUnlock()
|
|
|
|
// Handle single-token storage
|
|
if singleToken != "" {
|
|
return cm.processSingleToken(singleToken, compressed, config)
|
|
}
|
|
|
|
// Handle chunked storage
|
|
if len(chunks) == 0 {
|
|
return TokenRetrievalResult{Token: "", Error: nil}
|
|
}
|
|
|
|
return cm.processChunkedToken(chunks, config)
|
|
}
|
|
|
|
// processSingleToken handles tokens stored in a single cookie
|
|
func (cm *ChunkManager) processSingleToken(token string, compressed bool, config TokenConfig) TokenRetrievalResult {
|
|
// Detect corruption markers
|
|
if isCorruptionMarker(token) {
|
|
err := fmt.Errorf("CRITICAL: %s token contains corruption marker", config.Type)
|
|
cm.logger.Error(err.Error())
|
|
return TokenRetrievalResult{Token: "", Error: err}
|
|
}
|
|
|
|
var finalToken string
|
|
if compressed {
|
|
decompressed := decompressToken(token)
|
|
if isCorruptionMarker(decompressed) {
|
|
err := fmt.Errorf("CRITICAL: Decompressed %s token contains corruption marker", config.Type)
|
|
cm.logger.Error(err.Error())
|
|
return TokenRetrievalResult{Token: "", Error: err}
|
|
}
|
|
finalToken = decompressed
|
|
} else {
|
|
finalToken = token
|
|
}
|
|
|
|
return cm.validateToken(finalToken, config)
|
|
}
|
|
|
|
// validateToken performs comprehensive token validation
|
|
func (cm *ChunkManager) validateToken(token string, config TokenConfig) TokenRetrievalResult {
|
|
// Length validation
|
|
if len(token) < config.MinLength || len(token) > config.MaxLength {
|
|
err := fmt.Errorf("CRITICAL: %s token has invalid length %d", config.Type, len(token))
|
|
cm.logger.Error(err.Error())
|
|
return TokenRetrievalResult{Token: "", Error: err}
|
|
}
|
|
|
|
// JWT format validation (if required)
|
|
if config.RequireJWTFormat && !config.AllowOpaqueTokens {
|
|
dotCount := strings.Count(token, ".")
|
|
if dotCount != 2 {
|
|
err := fmt.Errorf("CRITICAL: %s token invalid JWT format (dots: %d)", config.Type, dotCount)
|
|
cm.logger.Error(err.Error())
|
|
return TokenRetrievalResult{Token: "", Error: err}
|
|
}
|
|
} else if config.RequireJWTFormat && config.AllowOpaqueTokens {
|
|
// For tokens that can be either JWT or opaque, validate JWT format only if it has dots
|
|
dotCount := strings.Count(token, ".")
|
|
if dotCount > 0 && dotCount != 2 {
|
|
err := fmt.Errorf("CRITICAL: %s token invalid JWT format (dots: %d)", config.Type, dotCount)
|
|
cm.logger.Error(err.Error())
|
|
return TokenRetrievalResult{Token: "", Error: err}
|
|
}
|
|
}
|
|
|
|
return TokenRetrievalResult{Token: token, Error: nil}
|
|
}
|
|
|
|
// processChunkedToken handles tokens stored across multiple chunks
|
|
func (cm *ChunkManager) processChunkedToken(chunks map[int]*sessions.Session, config TokenConfig) TokenRetrievalResult {
|
|
// Pre-validate chunk count to prevent excessive memory usage
|
|
if len(chunks) > 50 {
|
|
err := fmt.Errorf("CRITICAL: Too many %s token chunks (%d)", config.Type, len(chunks))
|
|
cm.logger.Error(err.Error())
|
|
return TokenRetrievalResult{Token: "", Error: err}
|
|
}
|
|
|
|
// Sequential chunk validation and assembly
|
|
var tokenParts []string
|
|
totalSize := 0
|
|
|
|
for i := 0; i < len(chunks); i++ {
|
|
session, ok := chunks[i]
|
|
if !ok {
|
|
err := fmt.Errorf("CRITICAL: %s token chunk %d missing", config.Type, i)
|
|
cm.logger.Error(err.Error())
|
|
return TokenRetrievalResult{Token: "", Error: err}
|
|
}
|
|
|
|
chunk, chunkOk := session.Values["token_chunk"].(string)
|
|
if !chunkOk || chunk == "" {
|
|
err := fmt.Errorf("CRITICAL: %s token chunk %d invalid", config.Type, i)
|
|
cm.logger.Error(err.Error())
|
|
return TokenRetrievalResult{Token: "", Error: err}
|
|
}
|
|
|
|
if isCorruptionMarker(chunk) {
|
|
err := fmt.Errorf("CRITICAL: %s token chunk %d corrupted", config.Type, i)
|
|
cm.logger.Error(err.Error())
|
|
return TokenRetrievalResult{Token: "", Error: err}
|
|
}
|
|
|
|
if len(chunk) > maxCookieSize+100 {
|
|
err := fmt.Errorf("CRITICAL: %s token chunk %d too large", config.Type, i)
|
|
cm.logger.Error(err.Error())
|
|
return TokenRetrievalResult{Token: "", Error: err}
|
|
}
|
|
|
|
totalSize += len(chunk)
|
|
if totalSize > config.MaxLength {
|
|
err := fmt.Errorf("CRITICAL: %s token total size exceeds limit", config.Type)
|
|
cm.logger.Error(err.Error())
|
|
return TokenRetrievalResult{Token: "", Error: err}
|
|
}
|
|
|
|
tokenParts = append(tokenParts, chunk)
|
|
}
|
|
|
|
// Reassemble token
|
|
reassembledToken := strings.Join(tokenParts, "")
|
|
|
|
// Check compression flag from first chunk
|
|
compressed, _ := chunks[0].Values["compressed"].(bool)
|
|
|
|
if compressed {
|
|
decompressed := decompressToken(reassembledToken)
|
|
if isCorruptionMarker(decompressed) {
|
|
err := fmt.Errorf("CRITICAL: Decompressed chunked %s token corrupted", config.Type)
|
|
cm.logger.Error(err.Error())
|
|
return TokenRetrievalResult{Token: "", Error: err}
|
|
}
|
|
return cm.validateToken(decompressed, config)
|
|
}
|
|
|
|
return cm.validateToken(reassembledToken, config)
|
|
}
|