mirror of
https://github.com/lukaszraczylo/traefikoidc.git
synced 2026-10-15 04:18:03 +00:00
- 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
126 lines
4.4 KiB
Go
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)
|
|
}
|
|
}
|