fengling-console/Services/OAuthClientService.cs
movingsam f5d6e0652c refactor: align TenantRepository with CleanDDD/NetCorePal规范
- remove duplicate ITenantRepository/TenantRepository from Console
- extend Platform ITenantRepository with GetByIdAsync, GetPagedAsync, CountAsync
- update Console services to use Platform.Infrastructure.Repositories
- fix nullable warnings (UserDto, OAuthClientService)
- fix YarpGateway Directory.Build.props duplicate import
- fix DynamicProxyConfigProvider CS8618 warning
2026-02-19 19:20:06 +08:00

312 lines
13 KiB
C#

using Fengling.Console.Models.Dtos;
using OpenIddict.Abstractions;
using System.Security.Cryptography;
using System.Text.Json;
namespace Fengling.Console.Services;
public interface IOAuthClientService
{
Task<(IEnumerable<OAuthClientDto> Items, int TotalCount)> GetClientsAsync(int page, int pageSize, string? displayName = null, string? clientId = null, string? status = null);
Task<OAuthClientDto?> GetClientAsync(string id);
Task<OAuthClientDto> CreateClientAsync(CreateClientDto dto);
Task<object> GenerateSecretAsync(string id);
Task<object> UpdateClientAsync(string id, UpdateClientDto dto);
Task DeleteClientAsync(string id);
object GetClientOptions();
}
public class OAuthClientService : IOAuthClientService
{
private readonly IOpenIddictApplicationManager _applicationManager;
private readonly HttpClient _httpClient;
private readonly IConfiguration _configuration;
private readonly ILogger<OAuthClientService> _logger;
public OAuthClientService(
IOpenIddictApplicationManager applicationManager,
HttpClient httpClient,
IConfiguration configuration,
ILogger<OAuthClientService> logger)
{
_applicationManager = applicationManager;
_httpClient = httpClient;
_configuration = configuration;
_logger = logger;
}
public async Task<(IEnumerable<OAuthClientDto> Items, int TotalCount)> GetClientsAsync(int page, int pageSize, string? displayName = null, string? clientId = null, string? status = null)
{
var applications = _applicationManager.ListAsync();
var clientList = new List<OAuthClientDto>();
await foreach (var application in applications)
{
var clientIdValue = await _applicationManager.GetClientIdAsync(application);
var displayNameValue = await _applicationManager.GetDisplayNameAsync(application);
if (!string.IsNullOrEmpty(displayName) && (string.IsNullOrEmpty(displayNameValue) || !displayNameValue.Contains(displayName, StringComparison.OrdinalIgnoreCase)))
continue;
if (!string.IsNullOrEmpty(clientId) && (string.IsNullOrEmpty(clientIdValue) || !clientIdValue.Contains(clientId, StringComparison.OrdinalIgnoreCase)))
continue;
var clientType = await _applicationManager.GetClientTypeAsync(application);
var consentType = await _applicationManager.GetConsentTypeAsync(application);
var permissions = await _applicationManager.GetPermissionsAsync(application);
var redirectUris = await _applicationManager.GetRedirectUrisAsync(application);
var postLogoutRedirectUris = await _applicationManager.GetPostLogoutRedirectUrisAsync(application);
var applicationId = await _applicationManager.GetIdAsync(application);
clientList.Add(new OAuthClientDto
{
Id = applicationId ?? clientIdValue ?? string.Empty,
ClientId = clientIdValue ?? string.Empty,
DisplayName = displayNameValue ?? string.Empty,
RedirectUris = redirectUris.ToArray(),
PostLogoutRedirectUris = postLogoutRedirectUris.ToArray(),
Scopes = permissions
.Where(p => p.StartsWith(OpenIddictConstants.Permissions.Prefixes.Scope))
.Select(p => p.Substring(OpenIddictConstants.Permissions.Prefixes.Scope.Length))
.ToArray(),
GrantTypes = permissions
.Where(p => p.StartsWith(OpenIddictConstants.Permissions.Prefixes.GrantType))
.Select(p => p.Substring(OpenIddictConstants.Permissions.Prefixes.GrantType.Length))
.ToArray(),
ClientType = clientType?.ToString(),
ConsentType = consentType?.ToString(),
Status = "active"
});
}
var sortedClients = clientList
.OrderByDescending(c => c.ClientId)
.Skip((page - 1) * pageSize)
.Take(pageSize)
.ToList();
return (sortedClients, clientList.Count);
}
public async Task<OAuthClientDto?> GetClientAsync(string id)
{
var application = await _applicationManager.FindByIdAsync(id);
if (application == null)
{
return null;
}
var clientIdValue = await _applicationManager.GetClientIdAsync(application);
var displayNameValue = await _applicationManager.GetDisplayNameAsync(application);
var clientType = await _applicationManager.GetClientTypeAsync(application);
var consentType = await _applicationManager.GetConsentTypeAsync(application);
var permissions = await _applicationManager.GetPermissionsAsync(application);
var redirectUris = await _applicationManager.GetRedirectUrisAsync(application);
var postLogoutRedirectUris = await _applicationManager.GetPostLogoutRedirectUrisAsync(application);
return new OAuthClientDto
{
Id = id,
ClientId = clientIdValue ?? string.Empty,
DisplayName = displayNameValue ?? string.Empty,
RedirectUris = redirectUris.ToArray(),
PostLogoutRedirectUris = postLogoutRedirectUris.ToArray(),
Scopes = permissions
.Where(p => p.StartsWith(OpenIddictConstants.Permissions.Prefixes.Scope))
.Select(p => p.Substring(OpenIddictConstants.Permissions.Prefixes.Scope.Length))
.ToArray(),
GrantTypes = permissions
.Where(p => p.StartsWith(OpenIddictConstants.Permissions.Prefixes.GrantType))
.Select(p => p.Substring(OpenIddictConstants.Permissions.Prefixes.GrantType.Length))
.ToArray(),
ClientType = clientType?.ToString(),
ConsentType = consentType?.ToString(),
Status = "active"
};
}
public async Task<OAuthClientDto> CreateClientAsync(CreateClientDto dto)
{
var authServiceUrl = _configuration["AuthService:Url"] ?? "http://localhost:5132";
var token = await GetAuthTokenAsync();
var clientSecret = string.IsNullOrEmpty(dto.ClientSecret) ? GenerateSecureSecret() : dto.ClientSecret;
var requestBody = new
{
dto.ClientId,
ClientSecret = clientSecret,
dto.DisplayName,
dto.RedirectUris,
dto.PostLogoutRedirectUris,
dto.Scopes,
dto.GrantTypes,
dto.ClientType,
dto.ConsentType,
dto.Status,
dto.Description
};
var request = new HttpRequestMessage(HttpMethod.Post, $"{authServiceUrl}/api/OAuthClients")
{
Content = new StringContent(
System.Text.Json.JsonSerializer.Serialize(requestBody),
System.Text.Encoding.UTF8,
"application/json")
};
request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
var response = await _httpClient.SendAsync(request);
response.EnsureSuccessStatusCode();
var content = await response.Content.ReadAsStringAsync();
var result = System.Text.Json.JsonSerializer.Deserialize<OAuthClientDto>(content);
_logger.LogInformation("Created OAuth client {ClientId}", dto.ClientId);
return result!;
}
public async Task<object> GenerateSecretAsync(string id)
{
var authServiceUrl = _configuration["AuthService:Url"] ?? "http://localhost:5132";
var token = await GetAuthTokenAsync();
var request = new HttpRequestMessage(HttpMethod.Post, $"{authServiceUrl}/api/OAuthClients/{id}/generate-secret");
request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
var response = await _httpClient.SendAsync(request);
response.EnsureSuccessStatusCode();
var content = await response.Content.ReadAsStringAsync();
var result = System.Text.Json.JsonSerializer.Deserialize<object>(content);
_logger.LogInformation("Generated new secret for OAuth client {Id}", id);
return result!;
}
public async Task<object> UpdateClientAsync(string id, UpdateClientDto dto)
{
var authServiceUrl = _configuration["AuthService:Url"] ?? "http://localhost:5132";
var token = await GetAuthTokenAsync();
var requestBody = new
{
dto.DisplayName,
dto.RedirectUris,
dto.PostLogoutRedirectUris,
dto.Scopes,
dto.GrantTypes,
dto.ClientType,
dto.ConsentType,
dto.Status,
dto.Description
};
var request = new HttpRequestMessage(HttpMethod.Put, $"{authServiceUrl}/api/OAuthClients/{id}")
{
Content = new StringContent(
System.Text.Json.JsonSerializer.Serialize(requestBody),
System.Text.Encoding.UTF8,
"application/json")
};
request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
var response = await _httpClient.SendAsync(request);
response.EnsureSuccessStatusCode();
_logger.LogInformation("Updated OAuth client {Id}", id);
return new { message = "Client updated successfully" };
}
public async Task DeleteClientAsync(string id)
{
var authServiceUrl = _configuration["AuthService:Url"] ?? "http://localhost:5132";
var token = await GetAuthTokenAsync();
var request = new HttpRequestMessage(HttpMethod.Delete, $"{authServiceUrl}/api/OAuthClients/{id}");
request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
var response = await _httpClient.SendAsync(request);
response.EnsureSuccessStatusCode();
_logger.LogInformation("Deleted OAuth client {Id}", id);
}
public object GetClientOptions()
{
return new
{
clientTypes = new[]
{
new { value = "public", label = "Public (SPA, Mobile App)" },
new { value = "confidential", label = "Confidential (Server-side)" }
},
consentTypes = new[]
{
new { value = "implicit", label = "Implicit" },
new { value = "explicit", label = "Explicit" },
new { value = "system", label = "System (Pre-authorized)" }
},
grantTypes = new[]
{
new { value = "authorization_code", label = "Authorization Code" },
new { value = "client_credentials", label = "Client Credentials" },
new { value = "refresh_token", label = "Refresh Token" },
new { value = "password", label = "Resource Owner Password Credentials" }
},
scopes = new[]
{
new { value = "openid", label = "OpenID Connect" },
new { value = "profile", label = "Profile" },
new { value = "email", label = "Email" },
new { value = "api", label = "API Access" },
new { value = "offline_access", label = "Offline Access" }
},
statuses = new[]
{
new { value = "active", label = "Active" },
new { value = "inactive", label = "Inactive" },
new { value = "suspended", label = "Suspended" }
}
};
}
private async Task<string> GetAuthTokenAsync()
{
var authServiceUrl = _configuration["AuthService:Url"] ?? "http://localhost:5132";
var clientId = _configuration["AuthService:ClientId"] ?? "console-client";
var clientSecret = _configuration["AuthService:ClientSecret"] ?? "console-secret";
var requestBody = new Dictionary<string, string>
{
{ "grant_type", "client_credentials" },
{ "client_id", clientId },
{ "client_secret", clientSecret },
{ "scope", "api" }
};
var request = new HttpRequestMessage(HttpMethod.Post, $"{authServiceUrl}/connect/token")
{
Content = new FormUrlEncodedContent(requestBody)
};
var response = await _httpClient.SendAsync(request);
response.EnsureSuccessStatusCode();
var content = await response.Content.ReadAsStringAsync();
var tokenResponse = System.Text.Json.JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(content);
return tokenResponse!["access_token"].GetString() ?? string.Empty;
}
private static string GenerateSecureSecret(int length = 32)
{
var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
var bytes = RandomNumberGenerator.GetBytes(length);
return new string(bytes.Select(b => chars[b % chars.Length]).ToArray());
}
}