using ZB.MOM.WW.Auth.ApiKeys.Admin;
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Security;
namespace ZB.MOM.WW.ScadaBridge.Security;
///
/// Implements the Commons management seam over the shared
/// ZB.MOM.WW.Auth.ApiKeys admin facade (). This is the
/// single shared path through which CLI and CentralUI create / list / enable / disable / delete
/// inbound keys and edit their method-scopes, so all front-ends drive identical library behaviour.
///
///
/// Mapping from the library projection to the app DTO:
///
/// - A key is "enabled" iff its RevokedUtc is null.
/// - "Methods" are the library's Scopes, sorted ordinally for a stable display order.
/// - Delete is best-effort revoke-then-delete: the library only deletes already-revoked keys,
/// so we revoke first (a harmless no-op when already revoked) and the delete is authoritative.
///
///
public sealed class LibraryInboundApiKeyAdmin : IInboundApiKeyAdmin
{
private readonly ApiKeyAdminCommands _admin;
/// Creates the seam over the supplied library admin command set.
/// The shared library admin facade.
public LibraryInboundApiKeyAdmin(ApiKeyAdminCommands admin)
{
ArgumentNullException.ThrowIfNull(admin);
_admin = admin;
}
///
public async Task CreateAsync(
string name, IReadOnlyCollection methods, CancellationToken ct = default)
{
ArgumentException.ThrowIfNullOrWhiteSpace(name);
ArgumentNullException.ThrowIfNull(methods);
// "N" format = 32 hex chars, no hyphens/underscores — the library rejects underscores
// in keyId because they delimit the sbk__ token.
var keyId = Guid.NewGuid().ToString("N");
var result = await _admin.CreateKeyAsync(
keyId, name, methods.ToHashSet(StringComparer.Ordinal),
constraintsJson: null, remoteAddress: null, ct).ConfigureAwait(false);
// Token is non-null on create success; CreateKeyAsync throws rather than returning a
// null token on the failure path, so a successful return always carries the secret.
return new InboundApiKeyCreated(result.KeyId, result.Token!);
}
///
public async Task> ListAsync(CancellationToken ct = default)
{
var items = await _admin.ListKeysAsync(ct).ConfigureAwait(false);
var result = new List(items.Count);
foreach (var item in items)
{
result.Add(new InboundApiKeyInfo(
KeyId: item.KeyId,
Name: item.DisplayName,
Enabled: item.RevokedUtc is null,
Methods: item.Scopes.OrderBy(s => s, StringComparer.Ordinal).ToList(),
CreatedUtc: item.CreatedUtc,
LastUsedUtc: item.LastUsedUtc));
}
return result;
}
///
public async Task SetEnabledAsync(string keyId, bool enabled, CancellationToken ct = default) =>
(await _admin.SetEnabledAsync(keyId, enabled, remoteAddress: null, ct).ConfigureAwait(false)).Succeeded;
///
public async Task SetMethodsAsync(
string keyId, IReadOnlyCollection methods, CancellationToken ct = default)
{
ArgumentNullException.ThrowIfNull(methods);
return (await _admin.SetScopesAsync(
keyId, methods.ToHashSet(StringComparer.Ordinal), remoteAddress: null, ct)
.ConfigureAwait(false)).Succeeded;
}
///
public async Task DeleteAsync(string keyId, CancellationToken ct = default)
{
// Best-effort revoke first so the library permits the delete (it only deletes
// already-revoked keys). Revoking an already-disabled key is a harmless no-op;
// the delete result is authoritative.
await _admin.RevokeKeyAsync(keyId, remoteAddress: null, ct).ConfigureAwait(false);
return (await _admin.DeleteKeyAsync(keyId, remoteAddress: null, ct).ConfigureAwait(false)).Succeeded;
}
///
public async Task> GetMethodsForKeyAsync(string keyId, CancellationToken ct = default)
{
var keys = await ListAsync(ct).ConfigureAwait(false);
var match = keys.FirstOrDefault(k => string.Equals(k.KeyId, keyId, StringComparison.Ordinal));
return match?.Methods ?? (IReadOnlyList)Array.Empty();
}
///
public async Task> GetKeysForMethodAsync(string methodName, CancellationToken ct = default)
{
var keys = await ListAsync(ct).ConfigureAwait(false);
return keys
.Where(k => k.Methods.Contains(methodName, StringComparer.Ordinal))
.Select(k => k.KeyId)
.ToList();
}
}