Add auth regression test for missing resource in PRM (#1265)
This commit is contained in:
parent
53e9ed8f65
commit
b8ae4fec2b
2 changed files with 52 additions and 2 deletions
|
|
@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.WebUtilities;
|
using Microsoft.AspNetCore.WebUtilities;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using ModelContextProtocol;
|
||||||
using ModelContextProtocol.AspNetCore.Authentication;
|
using ModelContextProtocol.AspNetCore.Authentication;
|
||||||
using ModelContextProtocol.Authentication;
|
using ModelContextProtocol.Authentication;
|
||||||
using ModelContextProtocol.Client;
|
using ModelContextProtocol.Client;
|
||||||
|
|
@ -528,6 +529,47 @@ public class AuthTests : OAuthTestBase
|
||||||
transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken));
|
transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CannotAuthenticate_WhenProtectedResourceMetadataMissingResource()
|
||||||
|
{
|
||||||
|
TestOAuthServer.RequireResource = false;
|
||||||
|
|
||||||
|
Builder.Services.Configure<McpAuthenticationOptions>(McpAuthenticationDefaults.AuthenticationScheme, options =>
|
||||||
|
{
|
||||||
|
options.Events.OnResourceMetadataRequest = async context =>
|
||||||
|
{
|
||||||
|
context.HandleResponse();
|
||||||
|
|
||||||
|
var metadata = new ProtectedResourceMetadata
|
||||||
|
{
|
||||||
|
AuthorizationServers = { new Uri(OAuthServerUrl) },
|
||||||
|
ScopesSupported = ["mcp:tools"],
|
||||||
|
};
|
||||||
|
|
||||||
|
await Results.Json(metadata, McpJsonUtilities.DefaultOptions).ExecuteAsync(context.HttpContext);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
await using var app = await StartMcpServerAsync();
|
||||||
|
|
||||||
|
await using var transport = new HttpClientTransport(new()
|
||||||
|
{
|
||||||
|
Endpoint = new(McpServerUrl),
|
||||||
|
OAuth = new()
|
||||||
|
{
|
||||||
|
ClientId = "demo-client",
|
||||||
|
ClientSecret = "demo-secret",
|
||||||
|
RedirectUri = new Uri("http://localhost:1179/callback"),
|
||||||
|
AuthorizationRedirectDelegate = HandleAuthorizationUrlAsync,
|
||||||
|
},
|
||||||
|
}, HttpClient, LoggerFactory);
|
||||||
|
|
||||||
|
var ex = await Assert.ThrowsAsync<McpException>(() => McpClient.CreateAsync(
|
||||||
|
transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken));
|
||||||
|
|
||||||
|
Assert.Contains("Resource URI in metadata", ex.Message);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task CanAuthenticate_WithAuthorizationServerPathInsertionMetadata()
|
public async Task CanAuthenticate_WithAuthorizationServerPathInsertionMetadata()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,14 @@ public sealed class Program
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public bool ClientIdMetadataDocumentSupported { get; set; } = true;
|
public bool ClientIdMetadataDocumentSupported { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether the authorization server requires a resource parameter.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// The default value is <c>true</c>.
|
||||||
|
/// </remarks>
|
||||||
|
public bool RequireResource { get; set; } = true;
|
||||||
|
|
||||||
public HashSet<string> DisabledMetadataPaths { get; } = new(StringComparer.OrdinalIgnoreCase);
|
public HashSet<string> DisabledMetadataPaths { get; } = new(StringComparer.OrdinalIgnoreCase);
|
||||||
public IReadOnlyCollection<string> MetadataRequests => _metadataRequests.ToArray();
|
public IReadOnlyCollection<string> MetadataRequests => _metadataRequests.ToArray();
|
||||||
|
|
||||||
|
|
@ -290,7 +298,7 @@ public sealed class Program
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate resource in accordance with RFC 8707
|
// Validate resource in accordance with RFC 8707
|
||||||
if (string.IsNullOrEmpty(resource) || !ValidResources.Contains(resource))
|
if (RequireResource && (string.IsNullOrEmpty(resource) || !ValidResources.Contains(resource)))
|
||||||
{
|
{
|
||||||
return Results.Redirect($"{redirect_uri}?error=invalid_target&error_description=The+specified+resource+is+not+valid&state={state}");
|
return Results.Redirect($"{redirect_uri}?error=invalid_target&error_description=The+specified+resource+is+not+valid&state={state}");
|
||||||
}
|
}
|
||||||
|
|
@ -338,7 +346,7 @@ public sealed class Program
|
||||||
|
|
||||||
// Validate resource in accordance with RFC 8707
|
// Validate resource in accordance with RFC 8707
|
||||||
var resource = form["resource"].ToString();
|
var resource = form["resource"].ToString();
|
||||||
if (string.IsNullOrEmpty(resource) || !ValidResources.Contains(resource))
|
if (RequireResource && (string.IsNullOrEmpty(resource) || !ValidResources.Contains(resource)))
|
||||||
{
|
{
|
||||||
return Results.BadRequest(new OAuthErrorResponse
|
return Results.BadRequest(new OAuthErrorResponse
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue