using System.CommandLine; using System.CommandLine.Parsing; using ZB.MOM.WW.ScadaBridge.Commons.Messages.Management; namespace ZB.MOM.WW.ScadaBridge.CLI.Commands; public static class SecurityCommands { /// /// Builds the security command group with API key, role mapping, and scope rule subcommands. /// /// Shared management URL option. /// Shared output format option. /// Shared username option for authentication. /// Shared password option for authentication. public static Command Build(Option urlOption, Option formatOption, Option usernameOption, Option passwordOption) { var command = new Command("security") { Description = "Manage security settings" }; command.Add(BuildApiKey(urlOption, formatOption, usernameOption, passwordOption)); command.Add(BuildRoleMapping(urlOption, formatOption, usernameOption, passwordOption)); command.Add(BuildScopeRule(urlOption, formatOption, usernameOption, passwordOption)); return command; } private static Command BuildApiKey(Option urlOption, Option formatOption, Option usernameOption, Option passwordOption) { var group = new Command("api-key") { Description = "Manage API keys" }; var listCmd = new Command("list") { Description = "List all API keys" }; listCmd.SetAction(async (ParseResult result) => { return await CommandHelpers.ExecuteCommandAsync( result, urlOption, formatOption, usernameOption, passwordOption, new ListApiKeysCommand()); }); group.Add(listCmd); var nameOption = new Option("--name") { Description = "API key name", Required = true }; var createMethodsOption = new Option("--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, methods), onSuccess: PrintCreatedKey); }); group.Add(createCmd); var deleteKeyIdOption = new Option("--key-id") { Description = "API key ID", Required = true }; var deleteCmd = new Command("delete") { Description = "Delete an API key" }; deleteCmd.Add(deleteKeyIdOption); deleteCmd.SetAction(async (ParseResult result) => { var keyId = result.GetValue(deleteKeyIdOption)!; return await CommandHelpers.ExecuteCommandAsync( result, urlOption, formatOption, usernameOption, passwordOption, new DeleteApiKeyCommand(keyId)); }); group.Add(deleteCmd); var updateKeyIdOption = new Option("--key-id") { Description = "API key ID", Required = true }; var enabledOption = new Option("--enabled") { Description = "Enable or disable", Required = true }; var updateCmd = new Command("update") { Description = "Enable or disable an API key" }; updateCmd.Add(updateKeyIdOption); updateCmd.Add(enabledOption); updateCmd.SetAction(async (ParseResult result) => { var keyId = result.GetValue(updateKeyIdOption)!; var enabled = result.GetValue(enabledOption); return await CommandHelpers.ExecuteCommandAsync( result, urlOption, formatOption, usernameOption, passwordOption, new UpdateApiKeyCommand(keyId, enabled)); }); group.Add(updateCmd); var setMethodsKeyIdOption = new Option("--key-id") { Description = "API key ID", Required = true }; var setMethodsOption = new Option("--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; } /// /// Splits a comma-separated --methods 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). /// /// The raw delimited option value. private static IReadOnlyList ParseMethods(string? raw) { if (string.IsNullOrWhiteSpace(raw)) return Array.Empty(); return raw .Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) .ToArray(); } /// /// 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. /// The advisory line is written to stderr so that piping stdout captures only the token. /// /// The JSON success body returned by the management API. internal 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.Error.WriteLine("Save this token now — it will not be shown again:"); Console.WriteLine($" {token}"); return 0; } private static Command BuildRoleMapping(Option urlOption, Option formatOption, Option usernameOption, Option passwordOption) { var group = new Command("role-mapping") { Description = "Manage LDAP role mappings" }; var listCmd = new Command("list") { Description = "List all role mappings" }; listCmd.SetAction(async (ParseResult result) => { return await CommandHelpers.ExecuteCommandAsync( result, urlOption, formatOption, usernameOption, passwordOption, new ListRoleMappingsCommand()); }); group.Add(listCmd); var ldapGroupOption = new Option("--ldap-group") { Description = "LDAP group name", Required = true }; var roleOption = new Option("--role") { Description = "Role name", Required = true }; var createCmd = new Command("create") { Description = "Create a role mapping" }; createCmd.Add(ldapGroupOption); createCmd.Add(roleOption); createCmd.SetAction(async (ParseResult result) => { var ldapGroup = result.GetValue(ldapGroupOption)!; var role = result.GetValue(roleOption)!; return await CommandHelpers.ExecuteCommandAsync( result, urlOption, formatOption, usernameOption, passwordOption, new CreateRoleMappingCommand(ldapGroup, role)); }); group.Add(createCmd); var idOption = new Option("--id") { Description = "Mapping ID", Required = true }; var deleteCmd = new Command("delete") { Description = "Delete a role mapping" }; deleteCmd.Add(idOption); deleteCmd.SetAction(async (ParseResult result) => { var id = result.GetValue(idOption); return await CommandHelpers.ExecuteCommandAsync( result, urlOption, formatOption, usernameOption, passwordOption, new DeleteRoleMappingCommand(id)); }); group.Add(deleteCmd); var updateIdOption = new Option("--id") { Description = "Mapping ID", Required = true }; var updateLdapGroupOption = new Option("--ldap-group") { Description = "LDAP group name", Required = true }; var updateRoleOption = new Option("--role") { Description = "Role name", Required = true }; var updateCmd = new Command("update") { Description = "Update a role mapping" }; updateCmd.Add(updateIdOption); updateCmd.Add(updateLdapGroupOption); updateCmd.Add(updateRoleOption); updateCmd.SetAction(async (ParseResult result) => { var id = result.GetValue(updateIdOption); var ldapGroup = result.GetValue(updateLdapGroupOption)!; var role = result.GetValue(updateRoleOption)!; return await CommandHelpers.ExecuteCommandAsync( result, urlOption, formatOption, usernameOption, passwordOption, new UpdateRoleMappingCommand(id, ldapGroup, role)); }); group.Add(updateCmd); return group; } private static Command BuildScopeRule(Option urlOption, Option formatOption, Option usernameOption, Option passwordOption) { var group = new Command("scope-rule") { Description = "Manage LDAP scope rules" }; var mappingIdOption = new Option("--mapping-id") { Description = "Role mapping ID", Required = true }; var listCmd = new Command("list") { Description = "List scope rules for a mapping" }; listCmd.Add(mappingIdOption); listCmd.SetAction(async (ParseResult result) => { var mappingId = result.GetValue(mappingIdOption); return await CommandHelpers.ExecuteCommandAsync( result, urlOption, formatOption, usernameOption, passwordOption, new ListScopeRulesCommand(mappingId)); }); group.Add(listCmd); var addMappingIdOption = new Option("--mapping-id") { Description = "Role mapping ID", Required = true }; var siteIdOption = new Option("--site-id") { Description = "Site ID", Required = true }; var addCmd = new Command("add") { Description = "Add a scope rule" }; addCmd.Add(addMappingIdOption); addCmd.Add(siteIdOption); addCmd.SetAction(async (ParseResult result) => { var mappingId = result.GetValue(addMappingIdOption); var siteId = result.GetValue(siteIdOption); return await CommandHelpers.ExecuteCommandAsync( result, urlOption, formatOption, usernameOption, passwordOption, new AddScopeRuleCommand(mappingId, siteId)); }); group.Add(addCmd); var deleteIdOption = new Option("--id") { Description = "Scope rule ID", Required = true }; var deleteCmd = new Command("delete") { Description = "Delete a scope rule" }; deleteCmd.Add(deleteIdOption); deleteCmd.SetAction(async (ParseResult result) => { var id = result.GetValue(deleteIdOption); return await CommandHelpers.ExecuteCommandAsync( result, urlOption, formatOption, usernameOption, passwordOption, new DeleteScopeRuleCommand(id)); }); group.Add(deleteCmd); return group; } }