102 lines
4.3 KiB
C#
102 lines
4.3 KiB
C#
|
|
using System.Collections.Generic;
|
|||
|
|
using System.Text.Json.Serialization;
|
|||
|
|
using System.Threading.Tasks;
|
|||
|
|
using Microsoft.AspNetCore.Authorization;
|
|||
|
|
using Microsoft.AspNetCore.Http;
|
|||
|
|
using Microsoft.AspNetCore.Mvc;
|
|||
|
|
using Microsoft.Extensions.Options;
|
|||
|
|
using Sozsoft.Mcp.Dtos;
|
|||
|
|
using Volo.Abp.AspNetCore.Mvc;
|
|||
|
|
|
|||
|
|
namespace Sozsoft.Mcp.Controllers;
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// Uzak MCP istemcilerinin baglanti kesfi. Istemci /mcp'den 401 alir, <c>WWW-Authenticate</c>
|
|||
|
|
/// basligindaki meta veri adresinden yetkilendirme sunucusunu ogrenir, kayit ucundan istemci
|
|||
|
|
/// kimligini alir ve kullaniciyi platformun giris ekranina yonlendirir.
|
|||
|
|
/// Adresler ve yanit bicimi RFC'lerle sabittir; bu yuzden ABP'nin konvansiyonel uclari yerine
|
|||
|
|
/// acik rotali bir controller kullanilir.
|
|||
|
|
/// </summary>
|
|||
|
|
[AllowAnonymous]
|
|||
|
|
[IgnoreAntiforgeryToken]
|
|||
|
|
[ApiExplorerSettings(IgnoreApi = true)]
|
|||
|
|
public class McpOAuthController(
|
|||
|
|
IOptions<McpOAuthOptions> options,
|
|||
|
|
IMcpOAuthAppService oAuthAppService) : AbpControllerBase
|
|||
|
|
{
|
|||
|
|
/// <summary>Korumali kaynak meta verisi (RFC 9728).</summary>
|
|||
|
|
[HttpGet(McpConsts.ProtectedResourceMetadataPath)]
|
|||
|
|
[HttpGet(McpConsts.ProtectedResourceMetadataPath + McpConsts.EndpointPath)]
|
|||
|
|
public IActionResult GetProtectedResourceMetadata()
|
|||
|
|
{
|
|||
|
|
var value = options.Value;
|
|||
|
|
|
|||
|
|
return new JsonResult(new ProtectedResourceMetadata(
|
|||
|
|
value.ResourceUrl,
|
|||
|
|
[value.AuthorizationServer],
|
|||
|
|
McpConsts.OAuthScopes,
|
|||
|
|
["header"],
|
|||
|
|
McpServerInfoDefaults.DisplayName));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>Dinamik istemci kaydi (RFC 7591).</summary>
|
|||
|
|
[HttpPost(McpConsts.RegistrationEndpointPath)]
|
|||
|
|
public async Task<IActionResult> RegisterAsync([FromBody] ClientRegistrationRequest request)
|
|||
|
|
{
|
|||
|
|
var result = await oAuthAppService.RegisterClientAsync(new McpClientRegistrationRequestDto
|
|||
|
|
{
|
|||
|
|
ClientName = request.ClientName,
|
|||
|
|
RedirectUris = request.RedirectUris ?? []
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
if (result.Error is not null)
|
|||
|
|
{
|
|||
|
|
return new JsonResult(new ClientRegistrationError(result.Error, result.ErrorDescription))
|
|||
|
|
{
|
|||
|
|
StatusCode = StatusCodes.Status400BadRequest
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return new JsonResult(new ClientRegistrationResponse(
|
|||
|
|
result.ClientId!,
|
|||
|
|
result.ClientIdIssuedAt,
|
|||
|
|
result.ClientName,
|
|||
|
|
result.RedirectUris,
|
|||
|
|
result.GrantTypes,
|
|||
|
|
result.ResponseTypes,
|
|||
|
|
result.TokenEndpointAuthMethod,
|
|||
|
|
result.Scope))
|
|||
|
|
{
|
|||
|
|
StatusCode = StatusCodes.Status201Created
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Protokol sozlesmeleri snake_case alan adlariyla sabittir; ABP'nin camelCase JSON
|
|||
|
|
// politikasina birakilmaz.
|
|||
|
|
|
|||
|
|
public sealed record ClientRegistrationRequest(
|
|||
|
|
[property: JsonPropertyName("client_name")] string? ClientName,
|
|||
|
|
[property: JsonPropertyName("redirect_uris")] List<string>? RedirectUris);
|
|||
|
|
|
|||
|
|
private sealed record ClientRegistrationResponse(
|
|||
|
|
[property: JsonPropertyName("client_id")] string ClientId,
|
|||
|
|
[property: JsonPropertyName("client_id_issued_at")] long ClientIdIssuedAt,
|
|||
|
|
[property: JsonPropertyName("client_name")] string? ClientName,
|
|||
|
|
[property: JsonPropertyName("redirect_uris")] IReadOnlyList<string> RedirectUris,
|
|||
|
|
[property: JsonPropertyName("grant_types")] IReadOnlyList<string> GrantTypes,
|
|||
|
|
[property: JsonPropertyName("response_types")] IReadOnlyList<string> ResponseTypes,
|
|||
|
|
[property: JsonPropertyName("token_endpoint_auth_method")] string TokenEndpointAuthMethod,
|
|||
|
|
[property: JsonPropertyName("scope")] string? Scope);
|
|||
|
|
|
|||
|
|
private sealed record ClientRegistrationError(
|
|||
|
|
[property: JsonPropertyName("error")] string Error,
|
|||
|
|
[property: JsonPropertyName("error_description")] string? ErrorDescription);
|
|||
|
|
|
|||
|
|
private sealed record ProtectedResourceMetadata(
|
|||
|
|
[property: JsonPropertyName("resource")] string Resource,
|
|||
|
|
[property: JsonPropertyName("authorization_servers")] IReadOnlyList<string> AuthorizationServers,
|
|||
|
|
[property: JsonPropertyName("scopes_supported")] IReadOnlyList<string> ScopesSupported,
|
|||
|
|
[property: JsonPropertyName("bearer_methods_supported")] IReadOnlyList<string> BearerMethodsSupported,
|
|||
|
|
[property: JsonPropertyName("resource_name")] string ResourceName);
|
|||
|
|
}
|