mirror of
https://github.com/lukaszraczylo/traefikoidc.git
synced 2026-10-16 04:29:15 +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
76 lines
2.7 KiB
Go
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)
|
|
}
|
|
}
|