294 lines
10 KiB
C#
294 lines
10 KiB
C#
using Fengling.AuthService.Models;
|
|
using Fengling.Console.Repositories;
|
|
using OpenIddict.Abstractions;
|
|
using OpenIddict.Server;
|
|
using System.Security.Cryptography;
|
|
|
|
namespace Fengling.Console.Services;
|
|
|
|
public interface IOAuthClientService
|
|
{
|
|
Task<(IEnumerable<OAuthApplication> Items, int TotalCount)> GetClientsAsync(int page, int pageSize, string? displayName = null, string? clientId = null, string? status = null);
|
|
Task<OAuthApplication?> GetClientAsync(long id);
|
|
Task<OAuthApplication> CreateClientAsync(OAuthApplication client);
|
|
Task<OAuthApplication> GenerateSecretAsync(long id);
|
|
Task<OAuthApplication> ToggleStatusAsync(long id);
|
|
Task<OAuthApplication> UpdateClientAsync(long id, OAuthApplication updatedClient);
|
|
Task DeleteClientAsync(long id);
|
|
object GetClientOptions();
|
|
}
|
|
|
|
public class OAuthClientService : IOAuthClientService
|
|
{
|
|
private readonly IOAuthClientRepository _repository;
|
|
private readonly IOpenIddictApplicationManager _applicationManager;
|
|
private readonly ILogger<OAuthClientService> _logger;
|
|
|
|
public OAuthClientService(
|
|
IOAuthClientRepository repository,
|
|
IOpenIddictApplicationManager applicationManager,
|
|
ILogger<OAuthClientService> logger)
|
|
{
|
|
_repository = repository;
|
|
_applicationManager = applicationManager;
|
|
_logger = logger;
|
|
}
|
|
|
|
public async Task<(IEnumerable<OAuthApplication> Items, int TotalCount)> GetClientsAsync(int page, int pageSize, string? displayName = null, string? clientId = null, string? status = null)
|
|
{
|
|
var items = await _repository.GetPagedAsync(page, pageSize, displayName, clientId, status);
|
|
var totalCount = await _repository.CountAsync(displayName, clientId, status);
|
|
return (items, totalCount);
|
|
}
|
|
|
|
public async Task<OAuthApplication?> GetClientAsync(long id)
|
|
{
|
|
return await _repository.GetByIdAsync(id);
|
|
}
|
|
|
|
public async Task<OAuthApplication> CreateClientAsync(OAuthApplication client)
|
|
{
|
|
var existing = await _repository.GetByClientIdAsync(client.ClientId);
|
|
if (existing != null)
|
|
{
|
|
throw new InvalidOperationException($"Client ID '{client.ClientId}' 已存在");
|
|
}
|
|
|
|
var descriptor = new OpenIddictApplicationDescriptor
|
|
{
|
|
ClientId = client.ClientId,
|
|
ClientSecret = client.ClientSecret,
|
|
DisplayName = client.DisplayName
|
|
};
|
|
|
|
foreach (var uri in client.RedirectUris.Where(u => Uri.TryCreate(u, UriKind.Absolute, out _)))
|
|
{
|
|
descriptor.RedirectUris.Add(new Uri(uri));
|
|
}
|
|
|
|
foreach (var uri in client.PostLogoutRedirectUris.Where(u => Uri.TryCreate(u, UriKind.Absolute, out _)))
|
|
{
|
|
descriptor.PostLogoutRedirectUris.Add(new Uri(uri));
|
|
}
|
|
|
|
foreach (var grantType in client.GrantTypes)
|
|
{
|
|
descriptor.Permissions.Add(grantType);
|
|
}
|
|
|
|
foreach (var scope in client.Scopes)
|
|
{
|
|
descriptor.Permissions.Add(scope);
|
|
}
|
|
|
|
await _applicationManager.CreateAsync(descriptor);
|
|
await _repository.AddAsync(client);
|
|
|
|
_logger.LogInformation("Created OAuth client {ClientId}", client.ClientId);
|
|
|
|
return client;
|
|
}
|
|
|
|
public async Task<OAuthApplication> GenerateSecretAsync(long id)
|
|
{
|
|
var client = await _repository.GetByIdAsync(id);
|
|
if (client == null)
|
|
{
|
|
throw new KeyNotFoundException($"Client with ID {id} not found");
|
|
}
|
|
|
|
var application = await _applicationManager.FindByClientIdAsync(client.ClientId);
|
|
if (application == null)
|
|
{
|
|
throw new KeyNotFoundException($"OpenIddict application for client {client.ClientId} not found");
|
|
}
|
|
|
|
var newSecret = GenerateSecureSecret();
|
|
|
|
var descriptor = new OpenIddictApplicationDescriptor
|
|
{
|
|
ClientId = client.ClientId,
|
|
ClientSecret = newSecret,
|
|
DisplayName = client.DisplayName
|
|
};
|
|
|
|
foreach (var uri in client.RedirectUris.Where(u => Uri.TryCreate(u, UriKind.Absolute, out _)))
|
|
{
|
|
descriptor.RedirectUris.Add(new Uri(uri));
|
|
}
|
|
|
|
foreach (var uri in client.PostLogoutRedirectUris.Where(u => Uri.TryCreate(u, UriKind.Absolute, out _)))
|
|
{
|
|
descriptor.PostLogoutRedirectUris.Add(new Uri(uri));
|
|
}
|
|
|
|
foreach (var grantType in client.GrantTypes)
|
|
{
|
|
descriptor.Permissions.Add(grantType);
|
|
}
|
|
|
|
foreach (var scope in client.Scopes)
|
|
{
|
|
descriptor.Permissions.Add(scope);
|
|
}
|
|
|
|
await _applicationManager.UpdateAsync(application, descriptor);
|
|
|
|
client.ClientSecret = newSecret;
|
|
client.UpdatedAt = DateTime.UtcNow;
|
|
await _repository.UpdateAsync(client);
|
|
|
|
_logger.LogInformation("Generated new secret for OAuth client {ClientId}", client.ClientId);
|
|
|
|
return client;
|
|
}
|
|
|
|
public async Task<OAuthApplication> ToggleStatusAsync(long id)
|
|
{
|
|
var client = await _repository.GetByIdAsync(id);
|
|
if (client == null)
|
|
{
|
|
throw new KeyNotFoundException($"Client with ID {id} not found");
|
|
}
|
|
|
|
var oldStatus = client.Status;
|
|
client.Status = client.Status == "active" ? "inactive" : "active";
|
|
client.UpdatedAt = DateTime.UtcNow;
|
|
await _repository.UpdateAsync(client);
|
|
|
|
_logger.LogInformation("Toggled OAuth client {ClientId} status from {OldStatus} to {NewStatus}", client.ClientId, oldStatus, client.Status);
|
|
|
|
return client;
|
|
}
|
|
|
|
public async Task<OAuthApplication> UpdateClientAsync(long id, OAuthApplication updatedClient)
|
|
{
|
|
var client = await _repository.GetByIdAsync(id);
|
|
if (client == null)
|
|
{
|
|
throw new KeyNotFoundException($"Client with ID {id} not found");
|
|
}
|
|
|
|
var application = await _applicationManager.FindByClientIdAsync(client.ClientId);
|
|
if (application == null)
|
|
{
|
|
throw new KeyNotFoundException($"OpenIddict application for client {client.ClientId} not found");
|
|
}
|
|
|
|
var descriptor = new OpenIddictApplicationDescriptor
|
|
{
|
|
ClientId = client.ClientId,
|
|
ClientSecret = client.ClientSecret,
|
|
DisplayName = updatedClient.DisplayName ?? client.DisplayName
|
|
};
|
|
|
|
var redirectUris = updatedClient.RedirectUris ?? client.RedirectUris;
|
|
foreach (var uri in redirectUris.Where(u => Uri.TryCreate(u, UriKind.Absolute, out _)))
|
|
{
|
|
descriptor.RedirectUris.Add(new Uri(uri));
|
|
}
|
|
|
|
var postLogoutUris = updatedClient.PostLogoutRedirectUris ?? client.PostLogoutRedirectUris;
|
|
foreach (var uri in postLogoutUris.Where(u => Uri.TryCreate(u, UriKind.Absolute, out _)))
|
|
{
|
|
descriptor.PostLogoutRedirectUris.Add(new Uri(uri));
|
|
}
|
|
|
|
var grantTypes = updatedClient.GrantTypes ?? client.GrantTypes;
|
|
foreach (var grantType in grantTypes)
|
|
{
|
|
descriptor.Permissions.Add(grantType);
|
|
}
|
|
|
|
var scopes = updatedClient.Scopes ?? client.Scopes;
|
|
foreach (var scope in scopes)
|
|
{
|
|
descriptor.Permissions.Add(scope);
|
|
}
|
|
|
|
await _applicationManager.UpdateAsync(application, descriptor);
|
|
|
|
client.DisplayName = updatedClient.DisplayName ?? client.DisplayName;
|
|
client.RedirectUris = redirectUris;
|
|
client.PostLogoutRedirectUris = postLogoutUris;
|
|
client.Scopes = scopes;
|
|
client.GrantTypes = grantTypes;
|
|
client.ClientType = updatedClient.ClientType ?? client.ClientType;
|
|
client.ConsentType = updatedClient.ConsentType ?? client.ConsentType;
|
|
client.Status = updatedClient.Status ?? client.Status;
|
|
client.Description = updatedClient.Description ?? client.Description;
|
|
client.UpdatedAt = DateTime.UtcNow;
|
|
await _repository.UpdateAsync(client);
|
|
|
|
_logger.LogInformation("Updated OAuth client {ClientId}", client.ClientId);
|
|
|
|
return client;
|
|
}
|
|
|
|
public async Task DeleteClientAsync(long id)
|
|
{
|
|
var client = await _repository.GetByIdAsync(id);
|
|
if (client == null)
|
|
{
|
|
throw new KeyNotFoundException($"Client with ID {id} not found");
|
|
}
|
|
|
|
var application = await _applicationManager.FindByClientIdAsync(client.ClientId);
|
|
if (application != null)
|
|
{
|
|
await _applicationManager.DeleteAsync(application);
|
|
_logger.LogInformation("Deleted OpenIddict application for client {ClientId}", client.ClientId);
|
|
}
|
|
|
|
await _repository.DeleteAsync(client);
|
|
|
|
_logger.LogInformation("Deleted OAuth client {ClientId}", client.ClientId);
|
|
}
|
|
|
|
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 static string GenerateSecureSecret(int length = 32)
|
|
{
|
|
var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
var bytes = System.Security.Cryptography.RandomNumberGenerator.GetBytes(length);
|
|
return new string(bytes.Select(b => chars[b % chars.Length]).ToArray());
|
|
}
|
|
}
|