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,75 @@
|
||||
package traefikoidc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// R158 review-round regressions.
|
||||
|
||||
// TestR158_DiscoveredSplitHostEndpointsPreserved guards the removal of the
|
||||
// same-host pin on the discovered token/revocation endpoints (main.go
|
||||
// updateMetadataEndpoints). The pin cleared any token endpoint on a
|
||||
// different host than providerURL, but mainstream providers split them
|
||||
// (Google: issuer accounts.google.com, token_endpoint
|
||||
// oauth2.googleapis.com) — so the real endpoint was discarded, tokenURL
|
||||
// stayed empty and every code exchange failed. SSRF defense is still
|
||||
// applied via validateDiscoveredEndpoint.
|
||||
// Fail-on-old: the split-host token/revoke URLs are cleared.
|
||||
func TestR158_DiscoveredSplitHostEndpointsPreserved(t *testing.T) {
|
||||
tObj := &TraefikOidc{
|
||||
logger: GetSingletonNoOpLogger(),
|
||||
providerURL: "https://accounts.google.com",
|
||||
}
|
||||
|
||||
meta := &ProviderMetadata{
|
||||
Issuer: "https://accounts.google.com",
|
||||
AuthURL: "https://accounts.google.com/o/oauth2/v2/auth",
|
||||
TokenURL: "https://oauth2.googleapis.com/token", // split host
|
||||
RevokeURL: "https://oauth2.googleapis.com/revoke", // split host
|
||||
}
|
||||
|
||||
tObj.updateMetadataEndpoints(meta)
|
||||
|
||||
if meta.TokenURL == "" {
|
||||
t.Error("split-host discovered token endpoint must be preserved; got empty")
|
||||
}
|
||||
if meta.RevokeURL == "" {
|
||||
t.Error("split-host discovered revocation endpoint must be preserved; got empty")
|
||||
}
|
||||
}
|
||||
|
||||
// TestR158_ExchangeTokensRejectsBlankAccessToken guards the RFC 6749 §5.1
|
||||
// presence gate in exchangeTokens (helpers.go). A 200 response must
|
||||
// carry an access_token; previously any JSON-decodable 200 body was
|
||||
// treated as success even with a blank token, and each caller had to
|
||||
// re-check "AccessToken == \"\"" (several paths missed it).
|
||||
// Fail-on-old: a 200 with no access_token returns success (nil error).
|
||||
func TestR158_ExchangeTokensRejectsBlankAccessToken(t *testing.T) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, _ = w.Write([]byte(`{}`)) // 200 but no tokens
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
tObj := &TraefikOidc{
|
||||
logger: GetSingletonNoOpLogger(),
|
||||
providerURL: "https://issuer.example.com",
|
||||
clientID: "cid",
|
||||
clientSecret: "secret",
|
||||
clientAuthMethod: "client_secret_post",
|
||||
tokenURL: ts.URL,
|
||||
tokenHTTPClient: ts.Client(),
|
||||
}
|
||||
|
||||
_, err := tObj.exchangeTokens(context.Background(), "authorization_code", "the-code", "https://issuer.example.com/cb", "")
|
||||
if err == nil {
|
||||
t.Error("token endpoint returning 200 without access_token must be an error (RFC 6749 §5.1); got success")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "access_token") {
|
||||
t.Errorf("expected access_token-related error, got: %v", err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user