mirror of
https://github.com/lukaszraczylo/traefikoidc.git
synced 2026-10-21 04:57:48 +00:00
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
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
package traefikoidc
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// TestBearerIntrospection_RefreshTokenTypeRejected guards the R149 fix in
|
||||
// introspectOnBearerPath: RFC 7662 TokenType is the only classifier for
|
||||
// an opaque credential (no JWT header). An active introspection result
|
||||
// whose token_type is a refresh token must not be honored as a bearer
|
||||
// access token.
|
||||
func TestBearerIntrospection_RefreshTokenTypeRejected(t *testing.T) {
|
||||
refresh := &IntrospectionResponse{Active: true, TokenType: "refresh_token"}
|
||||
tObj := &TraefikOidc{
|
||||
introspectionCache: &stubIntrospectionCache{v: refresh},
|
||||
logger: newNoOpLogger(),
|
||||
clientID: "client-abc", // audience == "" so no audience discriminator
|
||||
}
|
||||
if bErr := tObj.introspectOnBearerPath("tok"); bErr == nil {
|
||||
t.Fatal("an active introspection result classified as a refresh token must be rejected on the bearer path")
|
||||
}
|
||||
}
|
||||
|
||||
// TestBearerIntrospection_AccessTokenTypeAccepted guards the positive side:
|
||||
// a proper access_token type (and an omitted one) must still pass.
|
||||
func TestBearerIntrospection_AccessTokenTypeAccepted(t *testing.T) {
|
||||
for name, tt := range map[string]string{
|
||||
"access_token": "access_token",
|
||||
"omitted": "",
|
||||
} {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
resp := &IntrospectionResponse{Active: true, TokenType: tt, Aud: "https://api.example.com"}
|
||||
tObj := &TraefikOidc{
|
||||
introspectionCache: &stubIntrospectionCache{v: resp},
|
||||
logger: newNoOpLogger(),
|
||||
clientID: "client-abc",
|
||||
audience: "https://api.example.com",
|
||||
}
|
||||
if bErr := tObj.introspectOnBearerPath("tok"); bErr != nil {
|
||||
t.Fatalf("an active access-token/introspection result must pass, got: %v", bErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestOpaqueToken_RefreshTokenTypeRejected guards the session-path
|
||||
// (validateOpaqueToken) counterpart of the same R149 fix.
|
||||
func TestOpaqueToken_RefreshTokenTypeRejected(t *testing.T) {
|
||||
refresh := &IntrospectionResponse{Active: true, TokenType: "refresh_token"}
|
||||
tObj := &TraefikOidc{
|
||||
introspectionCache: &stubIntrospectionCache{v: refresh},
|
||||
logger: newNoOpLogger(),
|
||||
clientID: "client-abc", // audience == "" so no audience discriminator
|
||||
allowOpaqueTokens: true,
|
||||
introspectionURL: "https://introspect.example.com/introspect",
|
||||
}
|
||||
if err := tObj.validateOpaqueToken("tok"); err == nil {
|
||||
t.Fatal("an opaque refresh token must be rejected by validateOpaqueToken (token_type mismatch)")
|
||||
}
|
||||
}
|
||||
|
||||
// TestBackgroundTask_ZeroIntervalNoPanic guards the R149 fix in
|
||||
// NewBackgroundTask: run() calls time.NewTicker(interval), which panics
|
||||
// for interval <= 0 inside the started goroutine, crashing the whole
|
||||
// process. Non-positive intervals are now normalized at construction.
|
||||
func TestBackgroundTask_ZeroIntervalNoPanic(t *testing.T) {
|
||||
bt := NewBackgroundTask("r149-zero", 0, func() {}, newNoOpLogger())
|
||||
bt.Start()
|
||||
// Give the goroutine time to reach NewTicker; on the un-fixed code
|
||||
// this panics and crashes the test process.
|
||||
time.Sleep(20 * time.Millisecond)
|
||||
bt.Stop()
|
||||
|
||||
bt2 := NewBackgroundTask("r149-normal", time.Hour, func() {}, newNoOpLogger())
|
||||
bt2.Start()
|
||||
time.Sleep(5 * time.Millisecond)
|
||||
bt2.Stop()
|
||||
}
|
||||
Reference in New Issue
Block a user