Require typed issuer refusals in live registration verification
All checks were successful
Build and Publish Container Image / build-and-push (push) Successful in 40s
All checks were successful
Build and Publish Container Image / build-and-push (push) Successful in 40s
Assistant: codex Assistant-Model: gpt-5.6-luna Assistant-Session: 01a07ff8-19d0-7820-b4d0-1353833cb7fc
This commit is contained in:
parent
471465df22
commit
dcebd46fa6
6 changed files with 168 additions and 13 deletions
|
|
@ -3,6 +3,10 @@ package authclient
|
|||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
|
|
@ -160,3 +164,62 @@ func TestHasAllRolesNamesWhatIsMissing(t *testing.T) {
|
|||
}
|
||||
|
||||
var _ = domain.Client{}
|
||||
|
||||
type verifyTransport func(*http.Request) (*http.Response, error)
|
||||
|
||||
func (f verifyTransport) RoundTrip(r *http.Request) (*http.Response, error) { return f(r) }
|
||||
|
||||
func TestVerifyClientDoesNotConfuseFailuresWithRefusals(t *testing.T) {
|
||||
for _, target := range []string{"scope", "predecessor"} {
|
||||
for _, failure := range []string{"transport", "server", "invalid_token", "unrelated_refusal", "malformed_refusal"} {
|
||||
t.Run(target+"/"+failure, func(t *testing.T) {
|
||||
c, _, _ := provider(t)
|
||||
original := c.HTTP.Transport
|
||||
o := verifyOpts()
|
||||
if target == "predecessor" {
|
||||
o.PreviousNamed, o.Previous = true, "old-secret"
|
||||
}
|
||||
c.HTTP.Transport = verifyTransport(func(r *http.Request) (*http.Response, error) {
|
||||
if r.Method == http.MethodPost && r.URL.Path == "/token" {
|
||||
body, _ := io.ReadAll(r.Body)
|
||||
r.Body = io.NopCloser(bytes.NewReader(body))
|
||||
form, _ := url.ParseQuery(string(body))
|
||||
_, password, _ := r.BasicAuth()
|
||||
password, _ = url.QueryUnescape(password)
|
||||
negative := target == "scope" && form.Get("scope") != o.Scope || target == "predecessor" && password == o.Previous
|
||||
if negative {
|
||||
status, payload := 503, `{"error":"unavailable"}`
|
||||
switch failure {
|
||||
case "transport":
|
||||
return nil, errors.New("transport failed")
|
||||
case "invalid_token":
|
||||
status, payload = 200, `{"token_type":"Bearer","access_token":"invalid","expires_in":900}`
|
||||
case "unrelated_refusal":
|
||||
status, payload = 400, `{"error":"invalid_profile_usage","feature":"client_id","description":"must not disclose this"}`
|
||||
case "malformed_refusal":
|
||||
status, payload = 400, `not-json-sensitive-body`
|
||||
}
|
||||
return &http.Response{StatusCode: status, Header: http.Header{}, Body: io.NopCloser(strings.NewReader(payload)), Request: r}, nil
|
||||
}
|
||||
}
|
||||
return original.RoundTrip(r)
|
||||
})
|
||||
var out bytes.Buffer
|
||||
if err := runVerify(context.Background(), c, o, &out); err == nil {
|
||||
t.Fatal("a failed negative check was accepted as issuer refusal")
|
||||
}
|
||||
if !strings.Contains(out.String(), "PASS exchange and JWKS signature for granted scopes") {
|
||||
t.Fatalf("test did not reach the negative checks: %s", out.String())
|
||||
}
|
||||
if !strings.Contains(out.String(), "expected token-endpoint refusal was not proved") {
|
||||
t.Fatalf("wrong failure: %s", out.String())
|
||||
}
|
||||
for _, value := range []string{o.Secret, o.Previous, "must not disclose this", "not-json-sensitive-body", "eyJ"} {
|
||||
if value != "" && strings.Contains(out.String(), value) {
|
||||
t.Fatal("verification disclosed input or provider payload")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue