feat(auth): ScadaBridge ManagementActor + CLI + Commons messages onto IInboundApiKeyAdmin seam (re-arch C2; int->string keyId, +Methods, +SetApiKeyMethods)

This commit is contained in:
Joseph Doherty
2026-06-02 04:11:44 -04:00
parent 7f7ea3f3c9
commit 6518e93424
5 changed files with 316 additions and 108 deletions
@@ -37,44 +37,107 @@ public static class SecurityCommands
group.Add(listCmd);
var nameOption = new Option<string>("--name") { Description = "API key name", Required = true };
var createMethodsOption = new Option<string>("--methods")
{
Description = "Comma-separated API method names this key may call (e.g. \"MethodA,MethodB\")",
Required = true
};
var createCmd = new Command("create") { Description = "Create an API key" };
createCmd.Add(nameOption);
createCmd.Add(createMethodsOption);
createCmd.SetAction(async (ParseResult result) =>
{
var name = result.GetValue(nameOption)!;
var methods = ParseMethods(result.GetValue(createMethodsOption));
return await CommandHelpers.ExecuteCommandAsync(
result, urlOption, formatOption, usernameOption, passwordOption, new CreateApiKeyCommand(name));
result, urlOption, formatOption, usernameOption, passwordOption,
new CreateApiKeyCommand(name, methods),
onSuccess: PrintCreatedKey);
});
group.Add(createCmd);
var idOption = new Option<int>("--id") { Description = "API key ID", Required = true };
var deleteKeyIdOption = new Option<string>("--key-id") { Description = "API key ID", Required = true };
var deleteCmd = new Command("delete") { Description = "Delete an API key" };
deleteCmd.Add(idOption);
deleteCmd.Add(deleteKeyIdOption);
deleteCmd.SetAction(async (ParseResult result) =>
{
var id = result.GetValue(idOption);
var keyId = result.GetValue(deleteKeyIdOption)!;
return await CommandHelpers.ExecuteCommandAsync(
result, urlOption, formatOption, usernameOption, passwordOption, new DeleteApiKeyCommand(id));
result, urlOption, formatOption, usernameOption, passwordOption, new DeleteApiKeyCommand(keyId));
});
group.Add(deleteCmd);
var updateIdOption = new Option<int>("--id") { Description = "API key ID", Required = true };
var updateKeyIdOption = new Option<string>("--key-id") { Description = "API key ID", Required = true };
var enabledOption = new Option<bool>("--enabled") { Description = "Enable or disable", Required = true };
var updateCmd = new Command("update") { Description = "Enable or disable an API key" };
updateCmd.Add(updateIdOption);
updateCmd.Add(updateKeyIdOption);
updateCmd.Add(enabledOption);
updateCmd.SetAction(async (ParseResult result) =>
{
var id = result.GetValue(updateIdOption);
var keyId = result.GetValue(updateKeyIdOption)!;
var enabled = result.GetValue(enabledOption);
return await CommandHelpers.ExecuteCommandAsync(
result, urlOption, formatOption, usernameOption, passwordOption, new UpdateApiKeyCommand(id, enabled));
result, urlOption, formatOption, usernameOption, passwordOption, new UpdateApiKeyCommand(keyId, enabled));
});
group.Add(updateCmd);
var setMethodsKeyIdOption = new Option<string>("--key-id") { Description = "API key ID", Required = true };
var setMethodsOption = new Option<string>("--methods")
{
Description = "Comma-separated API method names this key may call (replaces the existing set)",
Required = true
};
var setMethodsCmd = new Command("set-methods") { Description = "Replace the method-scopes on an API key" };
setMethodsCmd.Add(setMethodsKeyIdOption);
setMethodsCmd.Add(setMethodsOption);
setMethodsCmd.SetAction(async (ParseResult result) =>
{
var keyId = result.GetValue(setMethodsKeyIdOption)!;
var methods = ParseMethods(result.GetValue(setMethodsOption));
return await CommandHelpers.ExecuteCommandAsync(
result, urlOption, formatOption, usernameOption, passwordOption,
new SetApiKeyMethodsCommand(keyId, methods));
});
group.Add(setMethodsCmd);
return group;
}
/// <summary>
/// Splits a comma-separated <c>--methods</c> value into a trimmed, non-empty list of
/// method names. A null/empty value yields an empty list (the server rejects an empty
/// scope set if its rules require one).
/// </summary>
/// <param name="raw">The raw delimited option value.</param>
private static IReadOnlyList<string> ParseMethods(string? raw)
{
if (string.IsNullOrWhiteSpace(raw))
return Array.Empty<string>();
return raw
.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
.ToArray();
}
/// <summary>
/// Renders the create-key response, surfacing the one-time bearer token prominently —
/// it is the only moment the secret is available and cannot be retrieved afterwards.
/// </summary>
/// <param name="json">The JSON success body returned by the management API.</param>
private static int PrintCreatedKey(string json)
{
using var doc = System.Text.Json.JsonDocument.Parse(json);
var root = doc.RootElement;
var keyId = root.TryGetProperty("keyId", out var k) ? k.GetString() : null;
var token = root.TryGetProperty("token", out var t) ? t.GetString() : null;
Console.WriteLine($"API key created. KeyId: {keyId}");
Console.WriteLine();
Console.WriteLine("Save this token now — it will not be shown again:");
Console.WriteLine($" {token}");
return 0;
}
private static Command BuildRoleMapping(Option<string> urlOption, Option<string> formatOption, Option<string> usernameOption, Option<string> passwordOption)
{
var group = new Command("role-mapping") { Description = "Manage LDAP role mappings" };