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(); } }