Files
traefikoidc/review_r158_regression_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

76 lines
2.7 KiB
Go

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)
}
}