Files
traefikoidc/review_r141_root_test.go
T
lukaszraczylo 6d96e8869b test: add R-round regression and security suite
- fail-on-old regression tests for every shipped fix (R-rounds)
- fuzz targets (parseJWT, bearer), concurrency-stress, shuffle-flake guards
- coverage-closing contract tests (health-check backend, TLS-posture reuse)
- r154 ID-token fixture built from parts to avoid false-positive secret scan
2026-08-14 16:22:39 +01:00

126 lines
4.4 KiB
Go

package traefikoidc
import (
"bytes"
"compress/gzip"
"encoding/base64"
"testing"
"time"
)
// TestConfigValidate_SessionMaxAgeUpperBound verifies that an oversized
// sessionMaxAge (which overflows the time.Duration (ns) conversion and
// wraps negative, locking every session out) is rejected at Validate time
// rather than failing at runtime (R141).
func TestConfigValidate_SessionMaxAgeUpperBound(t *testing.T) {
base := func() *Config {
c := CreateConfig()
c.ProviderURL = "https://provider.example.com"
c.ClientID = "test-client"
c.ClientSecret = "test-secret"
c.SessionEncryptionKey = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
c.CallbackURL = "/callback"
return c
}
if err := base().Validate(); err != nil {
t.Fatalf("baseline config should validate: %v", err)
}
// ~317 years in seconds: overflows int64 nanoseconds -> would wrap
// negative without the upper bound.
c := base()
c.SessionMaxAge = 10_000_000_000
err := c.Validate()
if err == nil {
t.Fatal("oversized sessionMaxAge must be rejected by Validate")
}
}
// TestUniversalCache_UpdateAtMaxSizeDoesNotEvictLiveEntry verifies that an
// in-place update of an existing key at MaxSize does NOT evict an unrelated
// live LRU entry (there is no net growth), mirroring the shard backend fix
// (R141).
func TestUniversalCache_UpdateAtMaxSizeDoesNotEvictLiveEntry(t *testing.T) {
c := NewUniversalCache(UniversalCacheConfig{
Type: CacheTypeToken,
MaxSize: 2,
MaxMemoryBytes: 1 << 20,
})
defer c.Close()
if err := c.SetLocal("B", "v2", time.Minute); err != nil {
t.Fatalf("set B: %v", err)
}
if err := c.SetLocal("A", "v1", time.Minute); err != nil {
t.Fatalf("set A: %v", err)
}
// Cache is full, and A is the most-recently-used entry (front of the
// LRU). Updating A is a pure in-place update (no net size change); it
// must not evict the least-recently-used live entry B.
if err := c.SetLocal("A", "v2", time.Minute); err != nil {
t.Fatalf("update A: %v", err)
}
if got := c.Size(); got != 2 {
t.Fatalf("Size()=%d, want 2 (in-place update evicted a live entry)", got)
}
if _, ok := c.GetLocal("B"); !ok {
t.Fatal("B was evicted by the in-place update of A")
}
}
// TestDCR_BuildRegistrationRequest_ValidatesURIMetadata verifies that the
// optional URI metadata fields (logo_uri, client_uri, policy_uri,
// tos_uri, jwks_uri) are validated as absolute http(s) URLs like
// redirect_uris, so a malformed jwks_uri cannot reach the IdP (R141).
func TestDCR_BuildRegistrationRequest_ValidatesURIMetadata(t *testing.T) {
base := func() *DynamicClientRegistrationConfig {
return &DynamicClientRegistrationConfig{
ClientMetadata: &ClientRegistrationMetadata{
RedirectURIs: []string{"https://app.example.com/callback"},
},
}
}
// A relative jwks_uri must be rejected.
cfg := base()
cfg.ClientMetadata.JWKSURI = "example.com/keys"
if _, err := NewDynamicClientRegistrar(nil, nil, cfg, "").buildRegistrationRequest(); err == nil {
t.Fatal("relative jwks_uri must be rejected as a non-absolute URL")
}
// A javascript: client_uri must be rejected.
cfg = base()
cfg.ClientMetadata.ClientURI = "javascript:alert(1)"
if _, err := NewDynamicClientRegistrar(nil, nil, cfg, "").buildRegistrationRequest(); err == nil {
t.Fatal("javascript: client_uri must be rejected")
}
// A valid full set must be accepted unchanged.
cfg = base()
cfg.ClientMetadata.LogoURI = "https://app.example.com/logo.png"
cfg.ClientMetadata.TOSURI = "https://app.example.com/tos"
if _, err := NewDynamicClientRegistrar(nil, nil, cfg, "").buildRegistrationRequest(); err != nil {
t.Fatalf("valid URI metadata should be accepted: %v", err)
}
}
// TestDecompressTokenInternal_RoundTrip guards the decompression change
// (drop the single-Read shortcut for the pooled buffer) by confirming a
// gzip round-trip still returns the exact original bytes (R141).
func TestDecompressTokenInternal_RoundTrip(t *testing.T) {
original := "header.payload.signature-part-with-extra-length"
var buf bytes.Buffer
gz := gzip.NewWriter(&buf)
if _, err := gz.Write([]byte(original)); err != nil {
t.Fatalf("gzip write: %v", err)
}
if err := gz.Close(); err != nil {
t.Fatalf("gzip close: %v", err)
}
compressed := base64.StdEncoding.EncodeToString(buf.Bytes())
if got := decompressToken(compressed); got != original {
t.Fatalf("decompress round-trip mismatch:\n got %q\nwant %q", got, original)
}
}