mcp: add optional issuer validator for pre-registered client validation (SEP-2352) (#946)
Fixes #978
This commit is contained in:
parent
189a85ad92
commit
dfb45f1e11
2 changed files with 190 additions and 5 deletions
|
|
@ -128,6 +128,11 @@ func (h *ClientCredentialsHandler) Authorize(ctx context.Context, req *http.Requ
|
|||
}
|
||||
}
|
||||
|
||||
creds := h.config.Credentials
|
||||
if creds.Issuer != "" && !authutil.IssuersEqual(creds.Issuer, asm.Issuer) {
|
||||
return fmt.Errorf("authorization server issuer %q does not match pre-registered credentials issuer %q", asm.Issuer, creds.Issuer)
|
||||
}
|
||||
|
||||
// Determine requestedScopes: use PRM's scopes_supported if available.
|
||||
requestedScopes := scopesFromChallenges(wwwChallenges)
|
||||
if len(requestedScopes) == 0 && len(prm.ScopesSupported) > 0 {
|
||||
|
|
@ -140,7 +145,6 @@ func (h *ClientCredentialsHandler) Authorize(ctx context.Context, req *http.Requ
|
|||
requestedScopes = authutil.UnionScopes(h.grantedScopes[asm.Issuer], requestedScopes)
|
||||
|
||||
// Step 3: Exchange client credentials for an access token.
|
||||
creds := h.config.Credentials
|
||||
cfg := &clientcredentials.Config{
|
||||
ClientID: creds.ClientID,
|
||||
ClientSecret: creds.ClientSecretAuth.ClientSecret,
|
||||
|
|
|
|||
|
|
@ -221,6 +221,51 @@ func TestClientCredentialsHandler_Authorize(t *testing.T) {
|
|||
}
|
||||
})
|
||||
|
||||
t.Run("issuer mismatch", func(t *testing.T) {
|
||||
config := validClientCredentialsConfig()
|
||||
config.Credentials.Issuer = "https://other-issuer.example.com"
|
||||
handler, err := NewClientCredentialsHandler(config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
resp := &http.Response{
|
||||
StatusCode: http.StatusUnauthorized,
|
||||
Header: http.Header{},
|
||||
Body: http.NoBody,
|
||||
}
|
||||
req := httptest.NewRequest("GET", resourceURL, nil)
|
||||
err = handler.Authorize(t.Context(), req, resp)
|
||||
if err == nil {
|
||||
t.Fatal("expected Authorize to fail with issuer mismatch")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "does not match") {
|
||||
t.Errorf("error %q does not mention issuer mismatch", err.Error())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("issuer match ignoring trailing slash", func(t *testing.T) {
|
||||
config := validClientCredentialsConfig()
|
||||
// authServer.URL() has no trailing slash; configure with one to
|
||||
// verify the comparison tolerates the difference (per RFC 8414 §3.3
|
||||
// normalization applied in oauthex.IssuersEqual).
|
||||
config.Credentials.Issuer = authServer.URL() + "/"
|
||||
handler, err := NewClientCredentialsHandler(config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
resp := &http.Response{
|
||||
StatusCode: http.StatusUnauthorized,
|
||||
Header: http.Header{},
|
||||
Body: http.NoBody,
|
||||
}
|
||||
req := httptest.NewRequest("GET", resourceURL, nil)
|
||||
if err := handler.Authorize(t.Context(), req, resp); err != nil {
|
||||
t.Fatalf("Authorize() unexpected error = %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("PRM via resource_metadata in challenge", func(t *testing.T) {
|
||||
prmMux := http.NewServeMux()
|
||||
prmMux.Handle("/custom-prm", auth.ProtectedResourceMetadataHandler(&oauthex.ProtectedResourceMetadata{
|
||||
|
|
|
|||
Loading…
Reference in a new issue