sozsoft-platform/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.HttpApi/Controllers/McpOAuthController.cs

102 lines
4.3 KiB
C#
Raw Normal View History

2026-09-10 14:40:43 +00:00
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);
}