From 891cf4616c0f1c7e5ac78b26480f3c67091f9bac Mon Sep 17 00:00:00 2001 From: chris-j-h Date: Fri, 29 Nov 2019 11:54:15 +0000 Subject: [PATCH 01/41] Declare picocli dependency version in root pom's dep management section --- cli/cli-api/pom.xml | 1 - pom.xml | 23 +++++++++++++---------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/cli/cli-api/pom.xml b/cli/cli-api/pom.xml index 8bef00af4a..c8bc3b6ed8 100644 --- a/cli/cli-api/pom.xml +++ b/cli/cli-api/pom.xml @@ -20,7 +20,6 @@ info.picocli picocli - 4.0.4 diff --git a/pom.xml b/pom.xml index 8b4b529be9..cae5187a7d 100644 --- a/pom.xml +++ b/pom.xml @@ -57,6 +57,7 @@ github 7.0 1.9.3 + 4.0.4 @@ -696,7 +697,6 @@ 0.11-SNAPSHOT - com.jpmorgan.quorum grpc-server @@ -1006,7 +1006,6 @@ 1.2.2 - com.github.stefanbirkner system-rules @@ -1019,21 +1018,25 @@ grpc-netty ${grpc.version} + io.grpc grpc-protobuf ${grpc.version} + io.grpc grpc-stub ${grpc.version} + com.google.api.grpc googleapis-common-protos 0.0.3 + com.google.protobuf protobuf-java @@ -1053,7 +1056,6 @@ 2.1.3 - io.netty netty-all @@ -1121,18 +1123,21 @@ ${jetty.version} jar + org.eclipse.jetty jetty-client ${jetty.version} jar + org.eclipse.jetty jetty-servlet ${jetty.version} jar + org.eclipse.jetty jetty-unixsocket @@ -1165,7 +1170,6 @@ ${jetty.version} - com.github.jnr jnr-constants @@ -1203,7 +1207,6 @@ native - org.glassfish.grizzly grizzly-http-server @@ -1211,7 +1214,6 @@ test - @@ -1244,7 +1246,6 @@ 2.7 - commons-codec commons-codec @@ -1323,13 +1324,17 @@ ${asm.version} - org.jasypt jasypt ${jasypt.version} + + info.picocli + picocli + ${picocli.version} + @@ -1399,8 +1404,6 @@ test - - From 1a26213c15bd9c0fe40f2512fdec2c9972a3afa8 Mon Sep 17 00:00:00 2001 From: chris-j-h Date: Fri, 29 Nov 2019 22:00:17 +0000 Subject: [PATCH 02/41] [WIP] Replace DefaultCli Apache CLI with PicoCLI Create a PicoCli cmd with usage and help, and dynamically produce CLI options from config object fields --- .../quorum/tessera/config/migration/Main.java | 9 +- tessera-dist/tessera-launcher/pom.xml | 6 +- .../com/quorum/tessera/launcher/Main.java | 28 +++--- .../tessera/launcher/PicoCliDelegate.java | 86 +++++++++++++++++++ 4 files changed, 111 insertions(+), 18 deletions(-) create mode 100644 tessera-dist/tessera-launcher/src/main/java/com/quorum/tessera/launcher/PicoCliDelegate.java diff --git a/config-migration/src/main/java/com/quorum/tessera/config/migration/Main.java b/config-migration/src/main/java/com/quorum/tessera/config/migration/Main.java index b559796c51..c11a69f989 100644 --- a/config-migration/src/main/java/com/quorum/tessera/config/migration/Main.java +++ b/config-migration/src/main/java/com/quorum/tessera/config/migration/Main.java @@ -1,8 +1,7 @@ package com.quorum.tessera.config.migration; -import com.quorum.tessera.cli.CliDelegate; -import com.quorum.tessera.cli.CliResult; import com.quorum.tessera.cli.CliType; +import picocli.CommandLine; public class Main { @@ -11,8 +10,10 @@ public static void main(String... args) { System.setProperty("javax.xml.bind.context.factory", "org.eclipse.persistence.jaxb.JAXBContextFactory"); System.setProperty(CliType.CLI_TYPE_KEY, CliType.CONFIG_MIGRATION.name()); try { - final CliResult result = CliDelegate.instance().execute(args); - System.exit(result.getStatus()); + int status = new CommandLine(new LegacyCliAdapter()).execute(args); + + // final CliResult result = CliDelegate.instance().execute(args); + System.exit(status); } catch (final Exception ex) { System.err.println(ex.toString()); System.exit(1); diff --git a/tessera-dist/tessera-launcher/pom.xml b/tessera-dist/tessera-launcher/pom.xml index f464b8cda8..6f5c37314c 100644 --- a/tessera-dist/tessera-launcher/pom.xml +++ b/tessera-dist/tessera-launcher/pom.xml @@ -19,11 +19,15 @@ server-api jar - + com.jpmorgan.quorum common-jaxrs 0.11-SNAPSHOT + + com.jpmorgan.quorum + config-cli + diff --git a/tessera-dist/tessera-launcher/src/main/java/com/quorum/tessera/launcher/Main.java b/tessera-dist/tessera-launcher/src/main/java/com/quorum/tessera/launcher/Main.java index 616ffdaa7f..c840087d3e 100644 --- a/tessera-dist/tessera-launcher/src/main/java/com/quorum/tessera/launcher/Main.java +++ b/tessera-dist/tessera-launcher/src/main/java/com/quorum/tessera/launcher/Main.java @@ -1,6 +1,5 @@ package com.quorum.tessera.launcher; -import com.quorum.tessera.cli.CliDelegate; import com.quorum.tessera.cli.CliException; import com.quorum.tessera.cli.CliResult; import com.quorum.tessera.config.AppType; @@ -19,10 +18,7 @@ import java.util.*; import java.util.stream.Collectors; -/** - * The main entry point for the application. This just starts up the application - * in the embedded container. - */ +/** The main entry point for the application. This just starts up the application in the embedded container. */ public class Main { private static final Logger LOGGER = LoggerFactory.getLogger(Main.class); @@ -33,7 +29,10 @@ public static void main(final String... args) throws Exception { System.setProperty("javax.xml.bind.context.factory", "org.eclipse.persistence.jaxb.JAXBContextFactory"); try { - final CliResult cliResult = CliDelegate.instance().execute(args); + // final CliResult cliResult = CliDelegate.instance().execute(args); + + PicoCliDelegate picoCliDelegate = new PicoCliDelegate(); + final CliResult cliResult = picoCliDelegate.execute(args); if (cliResult.isSuppressStartup()) { System.exit(0); @@ -43,8 +42,8 @@ public static void main(final String... args) throws Exception { System.exit(cliResult.getStatus()); } - final Config config - = cliResult + final Config config = + cliResult .getConfig() .orElseThrow(() -> new NoSuchElementException("No config found. Tessera will not run.")); @@ -89,13 +88,17 @@ public static void main(final String... args) throws Exception { private static void runWebServer(final Config config) throws Exception { - final List servers - = config.getServerConfigs().stream() + final List servers = + config.getServerConfigs().stream() .filter(server -> !AppType.ENCLAVE.equals(server.getApp())) .map( conf -> { - Object app = TesseraAppFactory.create(conf.getCommunicationType(), conf.getApp()) - .orElseThrow(() -> new IllegalStateException("Cant create app for " + conf.getApp())); + Object app = + TesseraAppFactory.create(conf.getCommunicationType(), conf.getApp()) + .orElseThrow( + () -> + new IllegalStateException( + "Cant create app for " + conf.getApp())); return TesseraServerFactory.create(conf.getCommunicationType()) .createServer(conf, Collections.singleton(app)); @@ -120,5 +123,4 @@ private static void runWebServer(final Config config) throws Exception { ts.start(); } } - } diff --git a/tessera-dist/tessera-launcher/src/main/java/com/quorum/tessera/launcher/PicoCliDelegate.java b/tessera-dist/tessera-launcher/src/main/java/com/quorum/tessera/launcher/PicoCliDelegate.java new file mode 100644 index 0000000000..71d41ab82c --- /dev/null +++ b/tessera-dist/tessera-launcher/src/main/java/com/quorum/tessera/launcher/PicoCliDelegate.java @@ -0,0 +1,86 @@ +package com.quorum.tessera.launcher; + +import com.quorum.tessera.cli.CLIExceptionCapturer; +import com.quorum.tessera.cli.CliResult; +import com.quorum.tessera.cli.parsers.ConfigConverter; +import com.quorum.tessera.config.Config; +import com.quorum.tessera.config.cli.OverrideUtil; +import picocli.CommandLine; +import picocli.CommandLine.Model.CommandSpec; +import picocli.CommandLine.Model.OptionSpec; + +import java.io.File; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class PicoCliDelegate { + public CliResult execute(String[] args) { + final CommandSpec command = CommandSpec.create(); + + // TODO(cjh) add options and positional parameters + // Options options = this.buildBaseOptions(); + + Map overrideOptions = OverrideUtil.buildConfigOptions(); + + command.usageMessage() + .headerHeading("Usage:%n%n") + .header("Tessera private transaction manager for Quorum") + .synopsisHeading("%n") + .descriptionHeading("%nDescription:%n%n") + .description("Commands to start a Tessera node or manage Tessera encryption keys") + .optionListHeading("%nOptions:%n") + .parameterListHeading("%nParameters:%n") + .abbreviateSynopsis(true); + + // TODO(cjh) most usage options have empty lines between them, but not all. Need to remove the empty lines. + overrideOptions.forEach( + (optionName, optionType) -> { + OptionSpec.Builder optionBuilder = + OptionSpec.builder(String.format("--%s", optionName)) + .paramLabel(optionType.getSimpleName()) + .type(optionType); + +// final boolean isCollection = optionType.isArray(); +// if (isCollection) { +// optionBuilder +// .type(List.class) +// .auxiliaryTypes(optionType); +// } else { +// optionBuilder +// .type(optionType); +// } + + command.addOption(optionBuilder.build()); + }); + + command.addSubcommand(null, new CommandLine(CommandLine.HelpCommand.class)); + + final CommandLine commandLine = new CommandLine(command); + final CLIExceptionCapturer mapper = new CLIExceptionCapturer(); + commandLine + .registerConverter(Config.class, new ConfigConverter()) + .setSeparator(" ") + .setCaseInsensitiveEnumValuesAllowed(true) + .setExecutionExceptionHandler(mapper) + .setParameterExceptionHandler(mapper); + try { + CommandLine.ParseResult pr = commandLine.parseArgs(args); + if (CommandLine.printHelpIfRequested(pr)) { + return new CliResult(0, true, null); + } + int count = pr.matchedOptionValue('c', 1); + List files = pr.matchedPositionalValue(0, Collections.emptyList()); + for (File f : files) { + for (int i = 0; i < count; i++) { + System.out.printf("%d: %s%n", i, f); + } + } + } catch (CommandLine.ParameterException invalidInput) { + System.err.println(invalidInput.getMessage()); + invalidInput.getCommandLine().usage(System.err); + } + + return new CliResult(1, true, null); + } +} From d4429d8b80cf79c45dbad04ad2c5573ca33e5206 Mon Sep 17 00:00:00 2001 From: chris-j-h Date: Fri, 29 Nov 2019 22:39:03 +0000 Subject: [PATCH 03/41] [KEYGEN-WIP] Replace DefaultCli Apache CLI with PicoCLI Add keygen as a subcommand to the main tessera command --- key-generation/pom.xml | 4 + .../tessera/key/generation/KeyGenCommand.java | 73 +++++++++++++++++++ .../com/quorum/tessera/launcher/Main.java | 2 +- .../tessera/launcher/PicoCliDelegate.java | 21 +++--- 4 files changed, 90 insertions(+), 10 deletions(-) create mode 100644 key-generation/src/main/java/com/quorum/tessera/key/generation/KeyGenCommand.java diff --git a/key-generation/pom.xml b/key-generation/pom.xml index e65ab10414..9fc2d84903 100644 --- a/key-generation/pom.xml +++ b/key-generation/pom.xml @@ -24,6 +24,10 @@ com.jpmorgan.quorum key-vault-api + + info.picocli + picocli + diff --git a/key-generation/src/main/java/com/quorum/tessera/key/generation/KeyGenCommand.java b/key-generation/src/main/java/com/quorum/tessera/key/generation/KeyGenCommand.java new file mode 100644 index 0000000000..c551bfbba3 --- /dev/null +++ b/key-generation/src/main/java/com/quorum/tessera/key/generation/KeyGenCommand.java @@ -0,0 +1,73 @@ +package com.quorum.tessera.key.generation; + +import com.quorum.tessera.config.KeyVaultType; +import picocli.CommandLine; + +import java.nio.file.Path; +import java.util.List; + +@CommandLine.Command( + name = "keygen", + aliases = {"-keygen"}, + headerHeading = "Usage:%n%n", + synopsisHeading = "%n", + descriptionHeading = "%nDescription:%n%n", + parameterListHeading = "%nParameters:%n", + optionListHeading = "%nOptions:%n", + header = "Generate Tessera encryption keys", + abbreviateSynopsis = true, + subcommands = {CommandLine.HelpCommand.class} +) +public class KeyGenCommand { + // TODO(cjh) raise CLI usage wording changes as separate change + + @CommandLine.Option( + names = {"--output", "-filename"}, + description = "Comma-separated list of paths to save generated key files. Can also be used with keyvault. Number of args determines number of key-pairs generated." + ) + public List output; + + @CommandLine.Option( + names = {"--encryptionconfig", "-keygenconfig"}, + description = "File containing Argon2 encryption config used to secure the new private key" + ) + public Path encryptionConfig; + + @CommandLine.Option( + names = {"--vault.type", "-keygenvaulttype"}, + description = "Specify the key vault provider the generated key is to be saved in. If not set, the key will be encrypted and stored on the local filesystem" + ) + //TODO(cjh) get possible enum values to show in the usage + public KeyVaultType vaultType; + + @CommandLine.Option( + names = {"--vault.url", "-keygenvaulturl"}, + description = "Base url for key vault" + ) + public String vaultUrl; + + @CommandLine.Option( + names = {"--vault.hashicorp.approlepath", "-keygenvaultapprole"}, + description = "AppRole path for Hashicorp Vault authentication (defaults to 'approle')" + ) + public String hashicorpApprolePath; + + @CommandLine.Option( + names = {"--vault.hashicorp.secretenginepath", "-keygenvaultsecretengine"}, + description = "Name of already enabled Hashicorp v2 kv secret engine" + ) + public String hashicorpSecretEnginePath; + + @CommandLine.Option( + names = {"--vault.hashicorp.tlskeystore", "-keygenvaultkeystore"}, + description = "Path to JKS keystore for TLS Hashicorp Vault communication" + ) + public Path hashicorpTlsKeystore; + + @CommandLine.Option( + names = {"--vault.hashicorp.tlstruststore", "-keygenvaulttruststore"}, + description = "Path to JKS truststore for TLS Hashicorp Vault communication" + ) + public Path hashicorpTlsTruststore; + +} diff --git a/tessera-dist/tessera-launcher/src/main/java/com/quorum/tessera/launcher/Main.java b/tessera-dist/tessera-launcher/src/main/java/com/quorum/tessera/launcher/Main.java index c840087d3e..24247d9b2c 100644 --- a/tessera-dist/tessera-launcher/src/main/java/com/quorum/tessera/launcher/Main.java +++ b/tessera-dist/tessera-launcher/src/main/java/com/quorum/tessera/launcher/Main.java @@ -29,7 +29,7 @@ public static void main(final String... args) throws Exception { System.setProperty("javax.xml.bind.context.factory", "org.eclipse.persistence.jaxb.JAXBContextFactory"); try { - // final CliResult cliResult = CliDelegate.instance().execute(args); +// final CliResult cliResult = CliDelegate.instance().execute(args); PicoCliDelegate picoCliDelegate = new PicoCliDelegate(); final CliResult cliResult = picoCliDelegate.execute(args); diff --git a/tessera-dist/tessera-launcher/src/main/java/com/quorum/tessera/launcher/PicoCliDelegate.java b/tessera-dist/tessera-launcher/src/main/java/com/quorum/tessera/launcher/PicoCliDelegate.java index 71d41ab82c..76e1543986 100644 --- a/tessera-dist/tessera-launcher/src/main/java/com/quorum/tessera/launcher/PicoCliDelegate.java +++ b/tessera-dist/tessera-launcher/src/main/java/com/quorum/tessera/launcher/PicoCliDelegate.java @@ -5,6 +5,7 @@ import com.quorum.tessera.cli.parsers.ConfigConverter; import com.quorum.tessera.config.Config; import com.quorum.tessera.config.cli.OverrideUtil; +import com.quorum.tessera.key.generation.KeyGenCommand; import picocli.CommandLine; import picocli.CommandLine.Model.CommandSpec; import picocli.CommandLine.Model.OptionSpec; @@ -19,6 +20,7 @@ public CliResult execute(String[] args) { final CommandSpec command = CommandSpec.create(); // TODO(cjh) add options and positional parameters + // Options options = this.buildBaseOptions(); Map overrideOptions = OverrideUtil.buildConfigOptions(); @@ -41,20 +43,21 @@ public CliResult execute(String[] args) { .paramLabel(optionType.getSimpleName()) .type(optionType); -// final boolean isCollection = optionType.isArray(); -// if (isCollection) { -// optionBuilder -// .type(List.class) -// .auxiliaryTypes(optionType); -// } else { -// optionBuilder -// .type(optionType); -// } + // final boolean isCollection = optionType.isArray(); + // if (isCollection) { + // optionBuilder + // .type(List.class) + // .auxiliaryTypes(optionType); + // } else { + // optionBuilder + // .type(optionType); + // } command.addOption(optionBuilder.build()); }); command.addSubcommand(null, new CommandLine(CommandLine.HelpCommand.class)); + command.addSubcommand(null, new CommandLine(KeyGenCommand.class)); final CommandLine commandLine = new CommandLine(command); final CLIExceptionCapturer mapper = new CLIExceptionCapturer(); From 5259cae3813cbdd12aee7a995cc1b7d5398ac9f6 Mon Sep 17 00:00:00 2001 From: chris-j-h Date: Sat, 30 Nov 2019 00:52:20 +0000 Subject: [PATCH 04/41] [KEYGEN-WIP] Replace DefaultCli Apache CLI with PicoCLI Add keygen as a subcommand to the main tessera command --- key-generation/pom.xml | 4 + .../tessera/key/generation/KeyGenCommand.java | 36 +++++++- .../key/generation/TesseraCommand.java | 24 +++++ .../tessera/launcher/PicoCliDelegate.java | 92 +++++++++++-------- 4 files changed, 119 insertions(+), 37 deletions(-) create mode 100644 key-generation/src/main/java/com/quorum/tessera/key/generation/TesseraCommand.java diff --git a/key-generation/pom.xml b/key-generation/pom.xml index 9fc2d84903..af01ff16d7 100644 --- a/key-generation/pom.xml +++ b/key-generation/pom.xml @@ -28,6 +28,10 @@ info.picocli picocli + + com.jpmorgan.quorum + cli-api + diff --git a/key-generation/src/main/java/com/quorum/tessera/key/generation/KeyGenCommand.java b/key-generation/src/main/java/com/quorum/tessera/key/generation/KeyGenCommand.java index c551bfbba3..401f70c666 100644 --- a/key-generation/src/main/java/com/quorum/tessera/key/generation/KeyGenCommand.java +++ b/key-generation/src/main/java/com/quorum/tessera/key/generation/KeyGenCommand.java @@ -1,10 +1,13 @@ package com.quorum.tessera.key.generation; +import com.quorum.tessera.cli.CliResult; +import com.quorum.tessera.config.Config; import com.quorum.tessera.config.KeyVaultType; import picocli.CommandLine; import java.nio.file.Path; import java.util.List; +import java.util.concurrent.Callable; @CommandLine.Command( name = "keygen", @@ -18,7 +21,18 @@ abbreviateSynopsis = true, subcommands = {CommandLine.HelpCommand.class} ) -public class KeyGenCommand { +public class KeyGenCommand implements Callable { +// // Doesn't work :( +// @CommandLine.ParentCommand +// public TesseraCommand parent; + + //TODO(cjh) do something about the duplication of the configfile option in each relevant command + @CommandLine.Option( + names = {"--configfile", "-configfile"}, + description = "Path to node configuration file" + ) + public Config config; + // TODO(cjh) raise CLI usage wording changes as separate change @CommandLine.Option( @@ -70,4 +84,24 @@ public class KeyGenCommand { ) public Path hashicorpTlsTruststore; + @Override + public CliResult call() throws Exception { +// final Object configObj = Optional +// .ofNullable(parent.findOption("-configfile")) +// .map(CommandLine.Model.OptionSpec::getValue) +// .orElse(null); +// +// final Config config; +// +// if (configObj != null) { +// if (configObj.getClass() != Config.class) { +// throw new RuntimeException("converted -configfile option is not of type Config"); +// } +// config = (Config) configObj; +// } + + + + return null; + } } diff --git a/key-generation/src/main/java/com/quorum/tessera/key/generation/TesseraCommand.java b/key-generation/src/main/java/com/quorum/tessera/key/generation/TesseraCommand.java new file mode 100644 index 0000000000..ea6a1c1825 --- /dev/null +++ b/key-generation/src/main/java/com/quorum/tessera/key/generation/TesseraCommand.java @@ -0,0 +1,24 @@ +package com.quorum.tessera.key.generation; + +import com.quorum.tessera.config.Config; +import picocli.CommandLine; + +@CommandLine.Command( + headerHeading = "Usage:%n%n", + header = "Tessera private transaction manager for Quorum", + synopsisHeading = "%n", + descriptionHeading = "%nDescription:%n%n", + description = "Start a Tessera node. Other commands exist to manage Tessera encryption keys", + parameterListHeading = "%nParameters:%n", + optionListHeading = "%nOptions:%n", + abbreviateSynopsis = true +) +public class TesseraCommand { + + @CommandLine.Option( + names = {"--configfile", "-configfile"}, + description = "Path to node configuration file" + ) + public Config config; + +} diff --git a/tessera-dist/tessera-launcher/src/main/java/com/quorum/tessera/launcher/PicoCliDelegate.java b/tessera-dist/tessera-launcher/src/main/java/com/quorum/tessera/launcher/PicoCliDelegate.java index 76e1543986..6e384c0bee 100644 --- a/tessera-dist/tessera-launcher/src/main/java/com/quorum/tessera/launcher/PicoCliDelegate.java +++ b/tessera-dist/tessera-launcher/src/main/java/com/quorum/tessera/launcher/PicoCliDelegate.java @@ -6,36 +6,21 @@ import com.quorum.tessera.config.Config; import com.quorum.tessera.config.cli.OverrideUtil; import com.quorum.tessera.key.generation.KeyGenCommand; +import com.quorum.tessera.key.generation.TesseraCommand; import picocli.CommandLine; import picocli.CommandLine.Model.CommandSpec; import picocli.CommandLine.Model.OptionSpec; -import java.io.File; -import java.util.Collections; -import java.util.List; import java.util.Map; public class PicoCliDelegate { public CliResult execute(String[] args) { - final CommandSpec command = CommandSpec.create(); + final CommandSpec command = CommandSpec.forAnnotatedObject(TesseraCommand.class); // TODO(cjh) add options and positional parameters - - // Options options = this.buildBaseOptions(); - - Map overrideOptions = OverrideUtil.buildConfigOptions(); - - command.usageMessage() - .headerHeading("Usage:%n%n") - .header("Tessera private transaction manager for Quorum") - .synopsisHeading("%n") - .descriptionHeading("%nDescription:%n%n") - .description("Commands to start a Tessera node or manage Tessera encryption keys") - .optionListHeading("%nOptions:%n") - .parameterListHeading("%nParameters:%n") - .abbreviateSynopsis(true); - // TODO(cjh) most usage options have empty lines between them, but not all. Need to remove the empty lines. + // add config override options, dynamically generated from the config object + Map overrideOptions = OverrideUtil.buildConfigOptions(); overrideOptions.forEach( (optionName, optionType) -> { OptionSpec.Builder optionBuilder = @@ -56,8 +41,10 @@ public CliResult execute(String[] args) { command.addOption(optionBuilder.build()); }); + CommandLine keyGenCommandLine = new CommandLine(KeyGenCommand.class); + command.addSubcommand(null, new CommandLine(CommandLine.HelpCommand.class)); - command.addSubcommand(null, new CommandLine(KeyGenCommand.class)); + command.addSubcommand(null, keyGenCommandLine); final CommandLine commandLine = new CommandLine(command); final CLIExceptionCapturer mapper = new CLIExceptionCapturer(); @@ -67,23 +54,56 @@ public CliResult execute(String[] args) { .setCaseInsensitiveEnumValuesAllowed(true) .setExecutionExceptionHandler(mapper) .setParameterExceptionHandler(mapper); - try { - CommandLine.ParseResult pr = commandLine.parseArgs(args); - if (CommandLine.printHelpIfRequested(pr)) { - return new CliResult(0, true, null); - } - int count = pr.matchedOptionValue('c', 1); - List files = pr.matchedPositionalValue(0, Collections.emptyList()); - for (File f : files) { - for (int i = 0; i < count; i++) { - System.out.printf("%d: %s%n", i, f); - } - } - } catch (CommandLine.ParameterException invalidInput) { - System.err.println(invalidInput.getMessage()); - invalidInput.getCommandLine().usage(System.err); - } + + commandLine.execute(args); + +// try { +// CommandLine.ParseResult pr = commandLine.parseArgs(args); +// if (CommandLine.printHelpIfRequested(pr)) { +// return new CliResult(0, true, null); +// } +// +// if (pr.hasSubcommand()) { +// CommandLine.ParseResult subPr = pr.subcommand(); +// CommandSpec subCommand = subPr.commandSpec(); +// +// List matchedStr = new ArrayList<>(); +// List matchedArgs = subPr.matchedArgs(); +// +// matchedArgs +// .stream() +// .map(CommandLine.Model.ArgSpec::originalStringValues) +// .forEachOrdered(matchedStr::addAll); +// +//// List commands = subPr.asCommandLineList(); +//// if (commands.size() != 1) { +//// throw new RuntimeException("at the moment exactly 1 subcommand has to be specified"); +//// } +//// +//// commands.get(0).execute(); +// +// List originalArgs = subPr.originalArgs(); +// +// if ("keygen".equals(subCommand.name())) { +// System.out.println("name = " + subCommand.name()); +// keyGenCommandLine.execute(originalArgs.toArray(new String[0])); +// } +// } + +// int count = pr.matchedOptionValue('c', 1); +// List files = pr.matchedPositionalValue(0, Collections.emptyList()); +// for (File f : files) { +// for (int i = 0; i < count; i++) { +// System.out.printf("%d: %s%n", i, f); +// } +// } +// } catch (CommandLine.ParameterException invalidInput) { +// System.err.println(invalidInput.getMessage()); +// invalidInput.getCommandLine().usage(System.err); +// } return new CliResult(1, true, null); } + + } From 237209b023e8588f2f4e021eb85de0c622fe37c4 Mon Sep 17 00:00:00 2001 From: chris-j-h Date: Sat, 30 Nov 2019 11:56:59 +0000 Subject: [PATCH 05/41] [KEYUPDATE-WIP] Replace DefaultCli Apache CLI with PicoCLI Add keyupdate as a subcommand to the main tessera command --- .../tessera/config/cli/KeyUpdateCommand.java | 208 ++++++++++++++++++ .../config/cli/KeyUpdateCommandFactory.java | 26 +++ .../tessera/launcher/PicoCliDelegate.java | 104 ++++----- 3 files changed, 289 insertions(+), 49 deletions(-) create mode 100644 cli/config-cli/src/main/java/com/quorum/tessera/config/cli/KeyUpdateCommand.java create mode 100644 cli/config-cli/src/main/java/com/quorum/tessera/config/cli/KeyUpdateCommandFactory.java diff --git a/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/KeyUpdateCommand.java b/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/KeyUpdateCommand.java new file mode 100644 index 0000000000..1942a47e01 --- /dev/null +++ b/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/KeyUpdateCommand.java @@ -0,0 +1,208 @@ +package com.quorum.tessera.config.cli; + +import com.quorum.tessera.cli.CliResult; +import com.quorum.tessera.config.*; +import com.quorum.tessera.config.keys.KeyEncryptor; +import com.quorum.tessera.config.keys.KeyEncryptorFactory; +import com.quorum.tessera.config.util.JaxbUtil; +import com.quorum.tessera.encryption.PrivateKey; +import com.quorum.tessera.io.SystemAdapter; +import com.quorum.tessera.passwords.PasswordReader; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import picocli.CommandLine; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.*; +import java.util.concurrent.Callable; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; + +@CommandLine.Command( + name = "keyupdate", + aliases = {"-updatepassword"}, + headerHeading = "Usage:%n%n", + synopsisHeading = "%n", + descriptionHeading = "%nDescription:%n%n", + parameterListHeading = "%nParameters:%n", + optionListHeading = "%nOptions:%n", + header = "Update the password for a key", + description = + "Change the password or update the encryption options for an already locked key, or apply a new password to an unlocked key", + subcommands = {CommandLine.HelpCommand.class}, + abbreviateSynopsis = true) +public class KeyUpdateCommand implements Callable { + + private static final Logger LOGGER = LoggerFactory.getLogger(KeyUpdateCommand.class); + + // TODO(cjh) don't hardcode these options (?) + + @CommandLine.Option(names = "--keys.keyData.privateKeyPath", required = true) + public Path privateKeyPath; + + // @Pattern(regexp = "^(id|i|d)$") + // @XmlAttribute(name = "variant") + // TODO(cjh) validation on the CLI values - the above is the validation applied to the Config ArgonOptions object + // fields + @CommandLine.Option(names = "--keys.keyData.config.data.aopts.algorithm", defaultValue = "i") + public String algorithm; + + @CommandLine.Option(names = "--keys.keyData.config.data.aopts.iterations", defaultValue = "10") + public Integer iterations; + + @CommandLine.Option(names = "--keys.keyData.config.data.aopts.memory", defaultValue = "1048576") + public Integer memory; + + @CommandLine.Option(names = "--keys.keyData.config.data.aopts.parallelism", defaultValue = "4") + public Integer parallelism; + + @CommandLine.Option(names = {"--keys.passwords"}) + public String password; + + @CommandLine.Option(names = {"--keys.passwordFile"}) + public Path passwordFile; + + @CommandLine.Option( + names = {"--configfile", "-configfile"}, + description = "Path to node configuration file") + public Config config; + + // TODO(cjh) default value using enum instead of hardcoding + @CommandLine.Option( + names = {"--encryptor.type"}, + defaultValue = "NACL") + public EncryptorType encryptorType; + + @CommandLine.Option(names = {"--encryptor.symmetricCipher"}) + public String encryptorSymmetricCipher; + + @CommandLine.Option(names = {"--encryptor.ellipticCurve"}) + public String encryptorEllipticCurve; + + @CommandLine.Option(names = {"--encryptor.nonceLength"}) + public String encryptorNonceLength; + + @CommandLine.Option(names = {"--encryptor.sharedKeyLength"}) + public String encryptorSharedKeyLength; + + private KeyEncryptorFactory keyEncryptorFactory; + + private KeyEncryptor keyEncryptor; + + private PasswordReader passwordReader; + + KeyUpdateCommand(KeyEncryptorFactory keyEncryptorFactory, PasswordReader passwordReader) { + this.keyEncryptorFactory = keyEncryptorFactory; + this.passwordReader = passwordReader; + } + + @Override + public CliResult call() throws Exception { + final EncryptorConfig encryptorConfig; + + if (Optional.ofNullable(config).map(Config::getEncryptor).isPresent()) { + encryptorConfig = config.getEncryptor(); + } else { + encryptorConfig = new EncryptorConfig(); + encryptorConfig.setType(encryptorType); + + if (encryptorType == EncryptorType.EC) { + Map properties = new HashMap<>(); + + Optional.ofNullable(encryptorSymmetricCipher).ifPresent(v -> properties.put("symmetricCipher", v)); + + Optional.ofNullable(encryptorEllipticCurve).ifPresent(v -> properties.put("ellipticCurve", v)); + + Optional.ofNullable(encryptorNonceLength).ifPresent(v -> properties.put("nonceLength", v)); + + Optional.ofNullable(encryptorSharedKeyLength).ifPresent(v -> properties.put("sharedKeyLength", v)); + + encryptorConfig.setProperties(properties); + } + } + + this.keyEncryptor = keyEncryptorFactory.create(encryptorConfig); + + return execute(); + } + + public CliResult execute() throws IOException { + final ArgonOptions argonOptions = argonOptions(); + final List passwords = passwords(); + final Path keypath = privateKeyPath(); + + final KeyDataConfig keyDataConfig = JaxbUtil.unmarshal(Files.newInputStream(keypath), KeyDataConfig.class); + final PrivateKey privateKey = this.getExistingKey(keyDataConfig, passwords); + + final String newPassword = passwordReader.requestUserPassword(); + + final KeyDataConfig updatedKey; + if (newPassword.isEmpty()) { + final PrivateKeyData privateKeyData = + new PrivateKeyData(privateKey.encodeToBase64(), null, null, null, null); + updatedKey = new KeyDataConfig(privateKeyData, PrivateKeyType.UNLOCKED); + } else { + final PrivateKeyData privateKeyData = keyEncryptor.encryptPrivateKey(privateKey, newPassword, argonOptions); + updatedKey = new KeyDataConfig(privateKeyData, PrivateKeyType.LOCKED); + } + + // write the key to file + Files.write(keypath, JaxbUtil.marshalToString(updatedKey).getBytes(UTF_8)); + SystemAdapter.INSTANCE.out().println("Private key at " + keypath.toString() + " updated."); + + // return Optional.empty(); + // TODO(cjh) compare with existing behaviour + return new CliResult(1, true, null); + } + + private PrivateKey getExistingKey(final KeyDataConfig kdc, final List passwords) { + + if (kdc.getType() == PrivateKeyType.UNLOCKED) { + byte[] privateKeyData = Base64.getDecoder().decode(kdc.getValue().getBytes(UTF_8)); + return PrivateKey.from(privateKeyData); + } else { + + for (final String pass : passwords) { + try { + return PrivateKey.from(keyEncryptor.decryptPrivateKey(kdc.getPrivateKeyData(), pass).getKeyBytes()); + } catch (final Exception e) { + LOGGER.debug("Password failed to decrypt. Trying next if available."); + } + } + + throw new IllegalArgumentException("Locked key but no valid password given"); + } + } + + private Path privateKeyPath() { + //// TODO(cjh)shouldn't need this as the option should be marked as required - CHECK! + // if (privateKeyPath == null) { + // throw new IllegalArgumentException("Private key path cannot be null when updating key password"); + // } + + if (Files.notExists(privateKeyPath)) { + throw new IllegalArgumentException("Private key path must exist when updating key password"); + } + + return privateKeyPath; + } + + private List passwords() throws IOException { + if (password != null) { + return singletonList(password); + } else if (passwordFile != null) { + return Files.readAllLines(passwordFile); + } else { + return emptyList(); + } + } + + private ArgonOptions argonOptions() { + return new ArgonOptions( + algorithm, Integer.valueOf(iterations), Integer.valueOf(memory), Integer.valueOf(parallelism)); + } +} diff --git a/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/KeyUpdateCommandFactory.java b/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/KeyUpdateCommandFactory.java new file mode 100644 index 0000000000..da89434313 --- /dev/null +++ b/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/KeyUpdateCommandFactory.java @@ -0,0 +1,26 @@ +package com.quorum.tessera.config.cli; + +import com.quorum.tessera.config.keys.KeyEncryptorFactory; +import com.quorum.tessera.passwords.PasswordReader; +import com.quorum.tessera.passwords.PasswordReaderFactory; +import picocli.CommandLine; + +public class KeyUpdateCommandFactory implements CommandLine.IFactory { + + + @Override + public K create(Class cls) throws Exception { + try { + if (cls != KeyUpdateCommand.class) { + throw new RuntimeException(this.getClass().getSimpleName() + " cannot create instance of type " + cls.getSimpleName()); + } + + KeyEncryptorFactory keyEncryptorFactory = KeyEncryptorFactory.newFactory(); + PasswordReader passwordReader = PasswordReaderFactory.create(); + + return (K) new KeyUpdateCommand(keyEncryptorFactory, passwordReader); + } catch (Exception e) { + return CommandLine.defaultFactory().create(cls); // fallback if missing + } + } +} diff --git a/tessera-dist/tessera-launcher/src/main/java/com/quorum/tessera/launcher/PicoCliDelegate.java b/tessera-dist/tessera-launcher/src/main/java/com/quorum/tessera/launcher/PicoCliDelegate.java index 6e384c0bee..a0e2680cfd 100644 --- a/tessera-dist/tessera-launcher/src/main/java/com/quorum/tessera/launcher/PicoCliDelegate.java +++ b/tessera-dist/tessera-launcher/src/main/java/com/quorum/tessera/launcher/PicoCliDelegate.java @@ -4,6 +4,8 @@ import com.quorum.tessera.cli.CliResult; import com.quorum.tessera.cli.parsers.ConfigConverter; import com.quorum.tessera.config.Config; +import com.quorum.tessera.config.cli.KeyUpdateCommand; +import com.quorum.tessera.config.cli.KeyUpdateCommandFactory; import com.quorum.tessera.config.cli.OverrideUtil; import com.quorum.tessera.key.generation.KeyGenCommand; import com.quorum.tessera.key.generation.TesseraCommand; @@ -14,7 +16,7 @@ import java.util.Map; public class PicoCliDelegate { - public CliResult execute(String[] args) { + public CliResult execute(String[] args) throws Exception { final CommandSpec command = CommandSpec.forAnnotatedObject(TesseraCommand.class); // TODO(cjh) add options and positional parameters @@ -41,10 +43,11 @@ public CliResult execute(String[] args) { command.addOption(optionBuilder.build()); }); - CommandLine keyGenCommandLine = new CommandLine(KeyGenCommand.class); - command.addSubcommand(null, new CommandLine(CommandLine.HelpCommand.class)); - command.addSubcommand(null, keyGenCommandLine); + command.addSubcommand(null, new CommandLine(KeyGenCommand.class)); + + final CommandLine.IFactory keyUpdateCommandFactory = new KeyUpdateCommandFactory(); + command.addSubcommand(null, new CommandLine(KeyUpdateCommand.class, keyUpdateCommandFactory)); final CommandLine commandLine = new CommandLine(command); final CLIExceptionCapturer mapper = new CLIExceptionCapturer(); @@ -57,53 +60,56 @@ public CliResult execute(String[] args) { commandLine.execute(args); -// try { -// CommandLine.ParseResult pr = commandLine.parseArgs(args); -// if (CommandLine.printHelpIfRequested(pr)) { -// return new CliResult(0, true, null); -// } -// -// if (pr.hasSubcommand()) { -// CommandLine.ParseResult subPr = pr.subcommand(); -// CommandSpec subCommand = subPr.commandSpec(); -// -// List matchedStr = new ArrayList<>(); -// List matchedArgs = subPr.matchedArgs(); -// -// matchedArgs -// .stream() -// .map(CommandLine.Model.ArgSpec::originalStringValues) -// .forEachOrdered(matchedStr::addAll); -// -//// List commands = subPr.asCommandLineList(); -//// if (commands.size() != 1) { -//// throw new RuntimeException("at the moment exactly 1 subcommand has to be specified"); -//// } -//// -//// commands.get(0).execute(); -// -// List originalArgs = subPr.originalArgs(); -// -// if ("keygen".equals(subCommand.name())) { -// System.out.println("name = " + subCommand.name()); -// keyGenCommandLine.execute(originalArgs.toArray(new String[0])); -// } -// } + // if an exception occurred, throw it to to the upper levels where it gets handled + if (mapper.getThrown() != null) { + throw mapper.getThrown(); + } + + // try { + // CommandLine.ParseResult pr = commandLine.parseArgs(args); + // if (CommandLine.printHelpIfRequested(pr)) { + // return new CliResult(0, true, null); + // } + // + // if (pr.hasSubcommand()) { + // CommandLine.ParseResult subPr = pr.subcommand(); + // CommandSpec subCommand = subPr.commandSpec(); + // + // List matchedStr = new ArrayList<>(); + // List matchedArgs = subPr.matchedArgs(); + // + // matchedArgs + // .stream() + // .map(CommandLine.Model.ArgSpec::originalStringValues) + // .forEachOrdered(matchedStr::addAll); + // + //// List commands = subPr.asCommandLineList(); + //// if (commands.size() != 1) { + //// throw new RuntimeException("at the moment exactly 1 subcommand has to be specified"); + //// } + //// + //// commands.get(0).execute(); + // + // List originalArgs = subPr.originalArgs(); + // + // if ("keygen".equals(subCommand.name())) { + // System.out.println("name = " + subCommand.name()); + // keyGenCommandLine.execute(originalArgs.toArray(new String[0])); + // } + // } -// int count = pr.matchedOptionValue('c', 1); -// List files = pr.matchedPositionalValue(0, Collections.emptyList()); -// for (File f : files) { -// for (int i = 0; i < count; i++) { -// System.out.printf("%d: %s%n", i, f); -// } -// } -// } catch (CommandLine.ParameterException invalidInput) { -// System.err.println(invalidInput.getMessage()); -// invalidInput.getCommandLine().usage(System.err); -// } + // int count = pr.matchedOptionValue('c', 1); + // List files = pr.matchedPositionalValue(0, Collections.emptyList()); + // for (File f : files) { + // for (int i = 0; i < count; i++) { + // System.out.printf("%d: %s%n", i, f); + // } + // } + // } catch (CommandLine.ParameterException invalidInput) { + // System.err.println(invalidInput.getMessage()); + // invalidInput.getCommandLine().usage(System.err); + // } return new CliResult(1, true, null); } - - } From 44e802b9e4ba6d1a20fda092c3a05559641bd824 Mon Sep 17 00:00:00 2001 From: chris-j-h Date: Tue, 3 Dec 2019 19:46:20 +0000 Subject: [PATCH 06/41] [WIP] Use picoCLI for main tessera cmd - dynamically generate options and apply CLI overrides --- .../com/quorum/tessera/launcher/Main.java | 3 +- .../tessera/launcher/PicoCliDelegate.java | 87 ++++++++++++++++--- 2 files changed, 78 insertions(+), 12 deletions(-) diff --git a/tessera-dist/tessera-launcher/src/main/java/com/quorum/tessera/launcher/Main.java b/tessera-dist/tessera-launcher/src/main/java/com/quorum/tessera/launcher/Main.java index 24247d9b2c..baacfc1913 100644 --- a/tessera-dist/tessera-launcher/src/main/java/com/quorum/tessera/launcher/Main.java +++ b/tessera-dist/tessera-launcher/src/main/java/com/quorum/tessera/launcher/Main.java @@ -81,7 +81,8 @@ public static void main(final String... args) throws Exception { System.exit(5); } catch (final Throwable ex) { - Optional.ofNullable(ex.getMessage()).ifPresent(System.err::println); +// Optional.ofNullable(ex.getMessage()).ifPresent(System.err::println); + ex.printStackTrace(); System.exit(2); } } diff --git a/tessera-dist/tessera-launcher/src/main/java/com/quorum/tessera/launcher/PicoCliDelegate.java b/tessera-dist/tessera-launcher/src/main/java/com/quorum/tessera/launcher/PicoCliDelegate.java index a0e2680cfd..c71356c7d5 100644 --- a/tessera-dist/tessera-launcher/src/main/java/com/quorum/tessera/launcher/PicoCliDelegate.java +++ b/tessera-dist/tessera-launcher/src/main/java/com/quorum/tessera/launcher/PicoCliDelegate.java @@ -9,13 +9,18 @@ import com.quorum.tessera.config.cli.OverrideUtil; import com.quorum.tessera.key.generation.KeyGenCommand; import com.quorum.tessera.key.generation.TesseraCommand; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import picocli.CommandLine; import picocli.CommandLine.Model.CommandSpec; import picocli.CommandLine.Model.OptionSpec; +import java.util.List; import java.util.Map; public class PicoCliDelegate { + private static final Logger LOGGER = LoggerFactory.getLogger(PicoCliDelegate.class); + public CliResult execute(String[] args) throws Exception { final CommandSpec command = CommandSpec.forAnnotatedObject(TesseraCommand.class); @@ -30,16 +35,6 @@ public CliResult execute(String[] args) throws Exception { .paramLabel(optionType.getSimpleName()) .type(optionType); - // final boolean isCollection = optionType.isArray(); - // if (isCollection) { - // optionBuilder - // .type(List.class) - // .auxiliaryTypes(optionType); - // } else { - // optionBuilder - // .type(optionType); - // } - command.addOption(optionBuilder.build()); }); @@ -58,7 +53,77 @@ public CliResult execute(String[] args) throws Exception { .setExecutionExceptionHandler(mapper) .setParameterExceptionHandler(mapper); - commandLine.execute(args); + final CommandLine.ParseResult parseResult; + try { + parseResult = commandLine.parseArgs(args); + } catch (CommandLine.ParameterException ex) { + // TODO(cjh) this is ripped from commandLine.execute(...) - check whether it is sufficient + try { + int exitCode = commandLine.getParameterExceptionHandler().handleParseException(ex, args); + return new CliResult(exitCode, true, null); + } catch (Exception e) { + throw e; + } + } + + if (CommandLine.printHelpIfRequested(parseResult)) { + return new CliResult(0, true, null); + } + + if (!parseResult.hasSubcommand()) { + // the node is being started + if (parseResult.originalArgs().size() == 0) { + System.out.println("no options were provided"); // TODO(cjh) delete + commandLine.execute("help"); + } else { + System.out.println("at least one option was provided"); // TODO(cjh) delete + List parsedArgs = parseResult.matchedArgs(); + + final Config config; + + // start with any config read from the file + if (parseResult.hasMatchedOption("configfile")) { + config = parseResult.matchedOption("configfile").getValue(); + } else { + config = new Config(); + } + + parsedArgs.forEach( + parsedArg -> { + // unnamed/positional CLI flags are ignored + if (!parsedArg.isOption()) { + return; + } + + OptionSpec parsedOption = (OptionSpec) parsedArg; + + // configfile CLI option is ignored as it was already parsed + // TODO(cjh) improve, checks all names for all provided options + for (String name : parsedOption.names()) { + if ("--configfile".equals(name)) { + return; + } + } + + String optionName = parsedOption.longestName().replaceFirst("^--", ""); + String[] values = parsedOption.stringValues().toArray(new String[0]); +// List values = parsedOption.typedValues(); // TODO(cjh) better to use this? + + LOGGER.debug("Setting : {} with value(s) {}", optionName, values); + OverrideUtil.setValue(config, optionName, values); + LOGGER.debug("Set : {} with value(s) {}", optionName, values); + }); + + System.out.println("all args parsed"); // TODO(cjh) delete + System.out.println("keys count = " + config.getKeys().getKeyData().size()); + System.out.println("useWhiteList = " + config.isUseWhiteList()); + } +// System.out.println(cmd); + } else { + // there is a subcommand + + parseResult.subcommand(); + } // if an exception occurred, throw it to to the upper levels where it gets handled if (mapper.getThrown() != null) { From dc116504f8b136e98e24b0d585c1f4e72d7f32e0 Mon Sep 17 00:00:00 2001 From: chris-j-h Date: Tue, 3 Dec 2019 20:08:09 +0000 Subject: [PATCH 07/41] [WIP TESSERA-CMD] Prompt user for pwds if necessary, validate config then start node --- .../com/quorum/tessera/cli/CliDelegate.java | 9 ++- .../key/generation/TesseraCommand.java | 8 +++ .../com/quorum/tessera/launcher/Main.java | 5 +- .../tessera/launcher/PicoCliDelegate.java | 68 ++++++++++++++++++- 4 files changed, 85 insertions(+), 5 deletions(-) diff --git a/cli/cli-api/src/main/java/com/quorum/tessera/cli/CliDelegate.java b/cli/cli-api/src/main/java/com/quorum/tessera/cli/CliDelegate.java index 9c0a18a365..83cf304d94 100644 --- a/cli/cli-api/src/main/java/com/quorum/tessera/cli/CliDelegate.java +++ b/cli/cli-api/src/main/java/com/quorum/tessera/cli/CliDelegate.java @@ -7,9 +7,12 @@ import org.slf4j.LoggerFactory; import picocli.CommandLine; -import java.util.*; +import java.util.List; +import java.util.Objects; +import java.util.Optional; import java.util.function.Predicate; import java.util.stream.Collectors; + import static picocli.CommandLine.Model.CommandSpec.DEFAULT_COMMAND_NAME; public enum CliDelegate { @@ -33,6 +36,10 @@ public Config getConfig() { () -> new IllegalStateException("Execute must be invoked before attempting to fetch config")); } + public void setConfig(Config config) { + this.config = config; + } + public CliResult execute(String... args) throws Exception { if (args.length > 0) { diff --git a/key-generation/src/main/java/com/quorum/tessera/key/generation/TesseraCommand.java b/key-generation/src/main/java/com/quorum/tessera/key/generation/TesseraCommand.java index ea6a1c1825..a32d88f884 100644 --- a/key-generation/src/main/java/com/quorum/tessera/key/generation/TesseraCommand.java +++ b/key-generation/src/main/java/com/quorum/tessera/key/generation/TesseraCommand.java @@ -3,6 +3,8 @@ import com.quorum.tessera.config.Config; import picocli.CommandLine; +import java.nio.file.Path; + @CommandLine.Command( headerHeading = "Usage:%n%n", header = "Tessera private transaction manager for Quorum", @@ -21,4 +23,10 @@ public class TesseraCommand { ) public Config config; + @CommandLine.Option( + names = {"--pidfile", "-pidfile"}, + description = "the path to write the PID to" + ) + public Path pidFilePath; + } diff --git a/tessera-dist/tessera-launcher/src/main/java/com/quorum/tessera/launcher/Main.java b/tessera-dist/tessera-launcher/src/main/java/com/quorum/tessera/launcher/Main.java index baacfc1913..1c905bbdbf 100644 --- a/tessera-dist/tessera-launcher/src/main/java/com/quorum/tessera/launcher/Main.java +++ b/tessera-dist/tessera-launcher/src/main/java/com/quorum/tessera/launcher/Main.java @@ -1,5 +1,6 @@ package com.quorum.tessera.launcher; +import com.quorum.tessera.cli.CliDelegate; import com.quorum.tessera.cli.CliException; import com.quorum.tessera.cli.CliResult; import com.quorum.tessera.config.AppType; @@ -33,6 +34,7 @@ public static void main(final String... args) throws Exception { PicoCliDelegate picoCliDelegate = new PicoCliDelegate(); final CliResult cliResult = picoCliDelegate.execute(args); + CliDelegate.instance().setConfig(cliResult.getConfig().orElse(null)); if (cliResult.isSuppressStartup()) { System.exit(0); @@ -81,8 +83,7 @@ public static void main(final String... args) throws Exception { System.exit(5); } catch (final Throwable ex) { -// Optional.ofNullable(ex.getMessage()).ifPresent(System.err::println); - ex.printStackTrace(); + Optional.ofNullable(ex.getMessage()).ifPresent(System.err::println); System.exit(2); } } diff --git a/tessera-dist/tessera-launcher/src/main/java/com/quorum/tessera/launcher/PicoCliDelegate.java b/tessera-dist/tessera-launcher/src/main/java/com/quorum/tessera/launcher/PicoCliDelegate.java index c71356c7d5..c6118afc2f 100644 --- a/tessera-dist/tessera-launcher/src/main/java/com/quorum/tessera/launcher/PicoCliDelegate.java +++ b/tessera-dist/tessera-launcher/src/main/java/com/quorum/tessera/launcher/PicoCliDelegate.java @@ -1,7 +1,10 @@ package com.quorum.tessera.launcher; +import com.quorum.tessera.ServiceLoaderUtil; import com.quorum.tessera.cli.CLIExceptionCapturer; import com.quorum.tessera.cli.CliResult; +import com.quorum.tessera.cli.keypassresolver.CliKeyPasswordResolver; +import com.quorum.tessera.cli.keypassresolver.KeyPasswordResolver; import com.quorum.tessera.cli.parsers.ConfigConverter; import com.quorum.tessera.config.Config; import com.quorum.tessera.config.cli.KeyUpdateCommand; @@ -15,12 +18,39 @@ import picocli.CommandLine.Model.CommandSpec; import picocli.CommandLine.Model.OptionSpec; +import javax.validation.ConstraintViolation; +import javax.validation.ConstraintViolationException; +import javax.validation.Validation; +import javax.validation.Validator; +import java.io.OutputStream; +import java.lang.management.ManagementFactory; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.List; import java.util.Map; +import java.util.Objects; +import java.util.Set; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static java.nio.file.StandardOpenOption.CREATE; +import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING; public class PicoCliDelegate { private static final Logger LOGGER = LoggerFactory.getLogger(PicoCliDelegate.class); + private final Validator validator = + Validation.byDefaultProvider().configure().ignoreXmlConfiguration().buildValidatorFactory().getValidator(); + + private final KeyPasswordResolver keyPasswordResolver; + + public PicoCliDelegate() { + this(ServiceLoaderUtil.load(KeyPasswordResolver.class).orElse(new CliKeyPasswordResolver())); + } + + public PicoCliDelegate(final KeyPasswordResolver keyPasswordResolver) { + this.keyPasswordResolver = Objects.requireNonNull(keyPasswordResolver); + } + public CliResult execute(String[] args) throws Exception { final CommandSpec command = CommandSpec.forAnnotatedObject(TesseraCommand.class); @@ -98,9 +128,10 @@ public CliResult execute(String[] args) throws Exception { OptionSpec parsedOption = (OptionSpec) parsedArg; // configfile CLI option is ignored as it was already parsed + // pidfile CLI option is ignored as it is parsed later // TODO(cjh) improve, checks all names for all provided options for (String name : parsedOption.names()) { - if ("--configfile".equals(name)) { + if ("--configfile".equals(name) || "--pidfile".equals(name)) { return; } } @@ -117,12 +148,26 @@ public CliResult execute(String[] args) throws Exception { System.out.println("all args parsed"); // TODO(cjh) delete System.out.println("keys count = " + config.getKeys().getKeyData().size()); System.out.println("useWhiteList = " + config.isUseWhiteList()); + + keyPasswordResolver.resolveKeyPasswords(config); + + final Set> violations = validator.validate(config); + if (!violations.isEmpty()) { + throw new ConstraintViolationException(violations); + } + + if (parseResult.hasMatchedOption("pidfile")) { + createPidFile(parseResult.matchedOption("pidfile").getValue()); + } + + return new CliResult(0, false, config); } -// System.out.println(cmd); } else { // there is a subcommand parseResult.subcommand(); + + // TODO(cjh) } // if an exception occurred, throw it to to the upper levels where it gets handled @@ -177,4 +222,23 @@ public CliResult execute(String[] args) throws Exception { return new CliResult(1, true, null); } + + private void createPidFile(Path pidFilePath) throws Exception { + if (pidFilePath == null) { + return; + } + + if (Files.exists(pidFilePath)) { + LOGGER.info("File already exists {}", pidFilePath); + } else { + LOGGER.info("Created pid file {}", pidFilePath); + } + + final String pid = ManagementFactory.getRuntimeMXBean().getName().split("@")[0]; + + try (OutputStream stream = Files.newOutputStream(pidFilePath, CREATE, TRUNCATE_EXISTING)) { + stream.write(pid.getBytes(UTF_8)); + } + } + } From 41e2bed592ca6fceba4546acd20541715ff0a52b Mon Sep 17 00:00:00 2001 From: chris-j-h Date: Wed, 4 Dec 2019 09:49:24 +0000 Subject: [PATCH 08/41] Clean up --- .../tessera/launcher/PicoCliDelegate.java | 51 ++----------------- 1 file changed, 3 insertions(+), 48 deletions(-) diff --git a/tessera-dist/tessera-launcher/src/main/java/com/quorum/tessera/launcher/PicoCliDelegate.java b/tessera-dist/tessera-launcher/src/main/java/com/quorum/tessera/launcher/PicoCliDelegate.java index c6118afc2f..eb7782b62f 100644 --- a/tessera-dist/tessera-launcher/src/main/java/com/quorum/tessera/launcher/PicoCliDelegate.java +++ b/tessera-dist/tessera-launcher/src/main/java/com/quorum/tessera/launcher/PicoCliDelegate.java @@ -87,7 +87,7 @@ public CliResult execute(String[] args) throws Exception { try { parseResult = commandLine.parseArgs(args); } catch (CommandLine.ParameterException ex) { - // TODO(cjh) this is ripped from commandLine.execute(...) - check whether it is sufficient +// TODO(cjh) this is ripped from commandLine.execute(...) - check whether it is sufficient, or if it can be replaced by using the mapper try { int exitCode = commandLine.getParameterExceptionHandler().handleParseException(ex, args); return new CliResult(exitCode, true, null); @@ -118,9 +118,10 @@ public CliResult execute(String[] args) throws Exception { config = new Config(); } + // apply CLI overrides parsedArgs.forEach( parsedArg -> { - // unnamed/positional CLI flags are ignored + // positional (i.e. unnamed) CLI flags are ignored if (!parsedArg.isOption()) { return; } @@ -138,7 +139,6 @@ public CliResult execute(String[] args) throws Exception { String optionName = parsedOption.longestName().replaceFirst("^--", ""); String[] values = parsedOption.stringValues().toArray(new String[0]); -// List values = parsedOption.typedValues(); // TODO(cjh) better to use this? LOGGER.debug("Setting : {} with value(s) {}", optionName, values); OverrideUtil.setValue(config, optionName, values); @@ -175,51 +175,6 @@ public CliResult execute(String[] args) throws Exception { throw mapper.getThrown(); } - // try { - // CommandLine.ParseResult pr = commandLine.parseArgs(args); - // if (CommandLine.printHelpIfRequested(pr)) { - // return new CliResult(0, true, null); - // } - // - // if (pr.hasSubcommand()) { - // CommandLine.ParseResult subPr = pr.subcommand(); - // CommandSpec subCommand = subPr.commandSpec(); - // - // List matchedStr = new ArrayList<>(); - // List matchedArgs = subPr.matchedArgs(); - // - // matchedArgs - // .stream() - // .map(CommandLine.Model.ArgSpec::originalStringValues) - // .forEachOrdered(matchedStr::addAll); - // - //// List commands = subPr.asCommandLineList(); - //// if (commands.size() != 1) { - //// throw new RuntimeException("at the moment exactly 1 subcommand has to be specified"); - //// } - //// - //// commands.get(0).execute(); - // - // List originalArgs = subPr.originalArgs(); - // - // if ("keygen".equals(subCommand.name())) { - // System.out.println("name = " + subCommand.name()); - // keyGenCommandLine.execute(originalArgs.toArray(new String[0])); - // } - // } - - // int count = pr.matchedOptionValue('c', 1); - // List files = pr.matchedPositionalValue(0, Collections.emptyList()); - // for (File f : files) { - // for (int i = 0; i < count; i++) { - // System.out.printf("%d: %s%n", i, f); - // } - // } - // } catch (CommandLine.ParameterException invalidInput) { - // System.err.println(invalidInput.getMessage()); - // invalidInput.getCommandLine().usage(System.err); - // } - return new CliResult(1, true, null); } From 2023be5dc060f9eddc55ff64c4639d06c68cbeab Mon Sep 17 00:00:00 2001 From: chris-j-h Date: Wed, 4 Dec 2019 10:46:31 +0000 Subject: [PATCH 09/41] Parse and execute subcommands --- .../tessera/launcher/PicoCliDelegate.java | 33 +++++++++++++------ 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/tessera-dist/tessera-launcher/src/main/java/com/quorum/tessera/launcher/PicoCliDelegate.java b/tessera-dist/tessera-launcher/src/main/java/com/quorum/tessera/launcher/PicoCliDelegate.java index eb7782b62f..b5cb0c3fec 100644 --- a/tessera-dist/tessera-launcher/src/main/java/com/quorum/tessera/launcher/PicoCliDelegate.java +++ b/tessera-dist/tessera-launcher/src/main/java/com/quorum/tessera/launcher/PicoCliDelegate.java @@ -68,14 +68,18 @@ public CliResult execute(String[] args) throws Exception { command.addOption(optionBuilder.build()); }); - command.addSubcommand(null, new CommandLine(CommandLine.HelpCommand.class)); - command.addSubcommand(null, new CommandLine(KeyGenCommand.class)); + final CLIExceptionCapturer mapper = new CLIExceptionCapturer(); + + CommandLine keyGenCommandLine = new CommandLine(KeyGenCommand.class); final CommandLine.IFactory keyUpdateCommandFactory = new KeyUpdateCommandFactory(); - command.addSubcommand(null, new CommandLine(KeyUpdateCommand.class, keyUpdateCommandFactory)); + CommandLine keyUpdateCommandLine = new CommandLine(KeyUpdateCommand.class, keyUpdateCommandFactory); + + command.addSubcommand(null, new CommandLine(CommandLine.HelpCommand.class)); + command.addSubcommand(null, keyGenCommandLine); + command.addSubcommand(null, keyUpdateCommandLine); final CommandLine commandLine = new CommandLine(command); - final CLIExceptionCapturer mapper = new CLIExceptionCapturer(); commandLine .registerConverter(Config.class, new ConfigConverter()) .setSeparator(" ") @@ -88,6 +92,7 @@ public CliResult execute(String[] args) throws Exception { parseResult = commandLine.parseArgs(args); } catch (CommandLine.ParameterException ex) { // TODO(cjh) this is ripped from commandLine.execute(...) - check whether it is sufficient, or if it can be replaced by using the mapper + // exception mapper can't be used here as we haven't called commandLine.execute() try { int exitCode = commandLine.getParameterExceptionHandler().handleParseException(ex, args); return new CliResult(exitCode, true, null); @@ -164,15 +169,23 @@ public CliResult execute(String[] args) throws Exception { } } else { // there is a subcommand + CommandLine.ParseResult subParseResult = parseResult.subcommand(); - parseResult.subcommand(); + String[] subCmdAndArgs = subParseResult.originalArgs().toArray(new String[0]); + String subCmd = subCmdAndArgs[0]; + String[] subArgs = new String[subCmdAndArgs.length - 1]; + System.arraycopy(subCmdAndArgs, 1, subArgs, 0, subArgs.length); - // TODO(cjh) - } + if ("keyupdate".equals(subCmd)) { + keyUpdateCommandLine.execute(subArgs); + } else if ("keygen".equals(subCmd)) { + keyGenCommandLine.execute(subArgs); + } - // if an exception occurred, throw it to to the upper levels where it gets handled - if (mapper.getThrown() != null) { - throw mapper.getThrown(); + // if an exception occurred, throw it to to the upper levels where it gets handled + if (mapper.getThrown() != null) { + throw mapper.getThrown(); + } } return new CliResult(1, true, null); From 10a855848f2674b3dfbd1fbb1f7804413250438c Mon Sep 17 00:00:00 2001 From: chris-j-h Date: Wed, 4 Dec 2019 13:33:21 +0000 Subject: [PATCH 10/41] Implement keygen behaviour. Create a mixin for encryptor options. --- .../cli/parsers/ArgonOptionsConverter.java | 27 ++++ .../tessera/cli/parsers/EncryptorOptions.java | 49 +++++++ .../tessera/config/cli/KeyUpdateCommand.java | 41 +----- .../tessera/key/generation/KeyGenCommand.java | 117 ++++++++++++---- .../launcher/NoTesseraCmdArgsException.java | 5 + .../tessera/launcher/PicoCliDelegate.java | 131 ++++++++++-------- 6 files changed, 250 insertions(+), 120 deletions(-) create mode 100644 cli/cli-api/src/main/java/com/quorum/tessera/cli/parsers/ArgonOptionsConverter.java create mode 100644 cli/cli-api/src/main/java/com/quorum/tessera/cli/parsers/EncryptorOptions.java create mode 100644 tessera-dist/tessera-launcher/src/main/java/com/quorum/tessera/launcher/NoTesseraCmdArgsException.java diff --git a/cli/cli-api/src/main/java/com/quorum/tessera/cli/parsers/ArgonOptionsConverter.java b/cli/cli-api/src/main/java/com/quorum/tessera/cli/parsers/ArgonOptionsConverter.java new file mode 100644 index 0000000000..6059bbaefa --- /dev/null +++ b/cli/cli-api/src/main/java/com/quorum/tessera/cli/parsers/ArgonOptionsConverter.java @@ -0,0 +1,27 @@ +package com.quorum.tessera.cli.parsers; + +import com.quorum.tessera.config.ArgonOptions; +import com.quorum.tessera.config.util.JaxbUtil; +import picocli.CommandLine; + +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +public class ArgonOptionsConverter implements CommandLine.ITypeConverter { + + @Override + public ArgonOptions convert(String value) throws Exception { + final Path path = Paths.get(value); + + if (!Files.exists(path)) { + throw new FileNotFoundException(String.format("%s not found.", path)); + } + + try (InputStream in = Files.newInputStream(path)) { + return JaxbUtil.unmarshal(in, ArgonOptions.class); + } + } +} diff --git a/cli/cli-api/src/main/java/com/quorum/tessera/cli/parsers/EncryptorOptions.java b/cli/cli-api/src/main/java/com/quorum/tessera/cli/parsers/EncryptorOptions.java new file mode 100644 index 0000000000..1bfa92e6b4 --- /dev/null +++ b/cli/cli-api/src/main/java/com/quorum/tessera/cli/parsers/EncryptorOptions.java @@ -0,0 +1,49 @@ +package com.quorum.tessera.cli.parsers; + +import com.quorum.tessera.config.EncryptorConfig; +import com.quorum.tessera.config.EncryptorType; +import picocli.CommandLine; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +public class EncryptorOptions { + + // TODO(cjh) default value using enum instead of hardcoding + @CommandLine.Option( + names = {"--encryptor.type"}, + defaultValue = "NACL") + public EncryptorType type; + + @CommandLine.Option(names = {"--encryptor.symmetricCipher"}) + public String symmetricCipher; + + @CommandLine.Option(names = {"--encryptor.ellipticCurve"}) + public String ellipticCurve; + + @CommandLine.Option(names = {"--encryptor.nonceLength"}) + public String nonceLength; + + @CommandLine.Option(names = {"--encryptor.sharedKeyLength"}) + public String sharedKeyLength; + + public EncryptorConfig parseEncryptorConfig() { + final EncryptorConfig encryptorConfig = new EncryptorConfig(); + encryptorConfig.setType(type); + + if (type == EncryptorType.EC) { + Map properties = new HashMap<>(); + + Optional.ofNullable(symmetricCipher).ifPresent(v -> properties.put("symmetricCipher", v)); + Optional.ofNullable(ellipticCurve).ifPresent(v -> properties.put("ellipticCurve", v)); + Optional.ofNullable(nonceLength).ifPresent(v -> properties.put("nonceLength", v)); + Optional.ofNullable(sharedKeyLength).ifPresent(v -> properties.put("sharedKeyLength", v)); + + encryptorConfig.setProperties(properties); + } + + return encryptorConfig; + } + +} diff --git a/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/KeyUpdateCommand.java b/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/KeyUpdateCommand.java index 1942a47e01..46a66db550 100644 --- a/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/KeyUpdateCommand.java +++ b/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/KeyUpdateCommand.java @@ -1,6 +1,7 @@ package com.quorum.tessera.config.cli; import com.quorum.tessera.cli.CliResult; +import com.quorum.tessera.cli.parsers.EncryptorOptions; import com.quorum.tessera.config.*; import com.quorum.tessera.config.keys.KeyEncryptor; import com.quorum.tessera.config.keys.KeyEncryptorFactory; @@ -15,7 +16,9 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.util.*; +import java.util.Base64; +import java.util.List; +import java.util.Optional; import java.util.concurrent.Callable; import static java.nio.charset.StandardCharsets.UTF_8; @@ -71,23 +74,8 @@ public class KeyUpdateCommand implements Callable { description = "Path to node configuration file") public Config config; - // TODO(cjh) default value using enum instead of hardcoding - @CommandLine.Option( - names = {"--encryptor.type"}, - defaultValue = "NACL") - public EncryptorType encryptorType; - - @CommandLine.Option(names = {"--encryptor.symmetricCipher"}) - public String encryptorSymmetricCipher; - - @CommandLine.Option(names = {"--encryptor.ellipticCurve"}) - public String encryptorEllipticCurve; - - @CommandLine.Option(names = {"--encryptor.nonceLength"}) - public String encryptorNonceLength; - - @CommandLine.Option(names = {"--encryptor.sharedKeyLength"}) - public String encryptorSharedKeyLength; + @CommandLine.Mixin + public EncryptorOptions encryptorOptions; private KeyEncryptorFactory keyEncryptorFactory; @@ -107,22 +95,7 @@ public CliResult call() throws Exception { if (Optional.ofNullable(config).map(Config::getEncryptor).isPresent()) { encryptorConfig = config.getEncryptor(); } else { - encryptorConfig = new EncryptorConfig(); - encryptorConfig.setType(encryptorType); - - if (encryptorType == EncryptorType.EC) { - Map properties = new HashMap<>(); - - Optional.ofNullable(encryptorSymmetricCipher).ifPresent(v -> properties.put("symmetricCipher", v)); - - Optional.ofNullable(encryptorEllipticCurve).ifPresent(v -> properties.put("ellipticCurve", v)); - - Optional.ofNullable(encryptorNonceLength).ifPresent(v -> properties.put("nonceLength", v)); - - Optional.ofNullable(encryptorSharedKeyLength).ifPresent(v -> properties.put("sharedKeyLength", v)); - - encryptorConfig.setProperties(properties); - } + encryptorConfig = encryptorOptions.parseEncryptorConfig(); } this.keyEncryptor = keyEncryptorFactory.create(encryptorConfig); diff --git a/key-generation/src/main/java/com/quorum/tessera/key/generation/KeyGenCommand.java b/key-generation/src/main/java/com/quorum/tessera/key/generation/KeyGenCommand.java index 401f70c666..d2b3e7ae66 100644 --- a/key-generation/src/main/java/com/quorum/tessera/key/generation/KeyGenCommand.java +++ b/key-generation/src/main/java/com/quorum/tessera/key/generation/KeyGenCommand.java @@ -1,12 +1,20 @@ package com.quorum.tessera.key.generation; +import com.quorum.tessera.cli.CliException; import com.quorum.tessera.cli.CliResult; -import com.quorum.tessera.config.Config; -import com.quorum.tessera.config.KeyVaultType; +import com.quorum.tessera.cli.parsers.EncryptorOptions; +import com.quorum.tessera.config.*; import picocli.CommandLine; +import javax.validation.ConstraintViolation; +import javax.validation.ConstraintViolationException; +import javax.validation.Validation; +import javax.validation.Validator; import java.nio.file.Path; import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; import java.util.concurrent.Callable; @CommandLine.Command( @@ -22,16 +30,10 @@ subcommands = {CommandLine.HelpCommand.class} ) public class KeyGenCommand implements Callable { -// // Doesn't work :( -// @CommandLine.ParentCommand -// public TesseraCommand parent; + private final KeyGeneratorFactory factory = KeyGeneratorFactory.newFactory(); - //TODO(cjh) do something about the duplication of the configfile option in each relevant command - @CommandLine.Option( - names = {"--configfile", "-configfile"}, - description = "Path to node configuration file" - ) - public Config config; + private final Validator validator = + Validation.byDefaultProvider().configure().ignoreXmlConfiguration().buildValidatorFactory().getValidator(); // TODO(cjh) raise CLI usage wording changes as separate change @@ -41,11 +43,12 @@ public class KeyGenCommand implements Callable { ) public List output; + // TODO(cjh) review description and name @CommandLine.Option( names = {"--encryptionconfig", "-keygenconfig"}, description = "File containing Argon2 encryption config used to secure the new private key" ) - public Path encryptionConfig; + public ArgonOptions encryptionConfig; @CommandLine.Option( names = {"--vault.type", "-keygenvaulttype"}, @@ -84,24 +87,80 @@ public class KeyGenCommand implements Callable { ) public Path hashicorpTlsTruststore; + //TODO(cjh) do something about the duplication of the configfile option in each relevant command + @CommandLine.Option( + names = {"--configfile", "-configfile"}, + description = "Path to node configuration file" + ) + public Config config; + + @CommandLine.Mixin + public EncryptorOptions encryptorOptions; + @Override public CliResult call() throws Exception { -// final Object configObj = Optional -// .ofNullable(parent.findOption("-configfile")) -// .map(CommandLine.Model.OptionSpec::getValue) -// .orElse(null); -// -// final Config config; -// -// if (configObj != null) { -// if (configObj.getClass() != Config.class) { -// throw new RuntimeException("converted -configfile option is not of type Config"); -// } -// config = (Config) configObj; -// } - - - - return null; + final EncryptorConfig encryptorConfig; + + if (Optional.ofNullable(config).map(Config::getEncryptor).isPresent()) { + encryptorConfig = config.getEncryptor(); + } else { + encryptorConfig = encryptorOptions.parseEncryptorConfig(); + } + + final KeyVaultOptions keyVaultOptions = this.keyVaultOptions().orElse(null); + final KeyVaultConfig keyVaultConfig = this.keyVaultConfig().orElse(null); + + final KeyGenerator generator = factory.create(keyVaultConfig, encryptorConfig); + + output.forEach( + name -> generator.generate(name, encryptionConfig, keyVaultOptions) + ); + + return new CliResult(0, true, null); + } + + private Optional keyVaultOptions() { + if (Objects.isNull(hashicorpSecretEnginePath)) { + return Optional.empty(); + } + + return Optional.of(new KeyVaultOptions(hashicorpSecretEnginePath)); + } + + private Optional keyVaultConfig() { + if (Objects.isNull(vaultType) && Objects.isNull(vaultUrl)) { + return Optional.empty(); + } + + final KeyVaultConfig keyVaultConfig; + + if (vaultType.equals(KeyVaultType.AZURE)) { + keyVaultConfig = new AzureKeyVaultConfig(vaultUrl); + + Set> violations = + validator.validate((AzureKeyVaultConfig) keyVaultConfig); + + if (!violations.isEmpty()) { + throw new ConstraintViolationException(violations); + } + } else { + if (output.size() == 0) { + throw new CliException( + "At least one -filename must be provided when saving generated keys in a Hashicorp Vault"); + } + + keyVaultConfig = + new HashicorpKeyVaultConfig( + vaultUrl, hashicorpApprolePath, hashicorpTlsKeystore, hashicorpTlsTruststore); + + Set> violations = + validator.validate((HashicorpKeyVaultConfig) keyVaultConfig); + + if (!violations.isEmpty()) { + throw new ConstraintViolationException(violations); + } + } + + return Optional.of(keyVaultConfig); } } diff --git a/tessera-dist/tessera-launcher/src/main/java/com/quorum/tessera/launcher/NoTesseraCmdArgsException.java b/tessera-dist/tessera-launcher/src/main/java/com/quorum/tessera/launcher/NoTesseraCmdArgsException.java new file mode 100644 index 0000000000..7f524d4c1f --- /dev/null +++ b/tessera-dist/tessera-launcher/src/main/java/com/quorum/tessera/launcher/NoTesseraCmdArgsException.java @@ -0,0 +1,5 @@ +package com.quorum.tessera.launcher; + +public class NoTesseraCmdArgsException extends RuntimeException { + +} diff --git a/tessera-dist/tessera-launcher/src/main/java/com/quorum/tessera/launcher/PicoCliDelegate.java b/tessera-dist/tessera-launcher/src/main/java/com/quorum/tessera/launcher/PicoCliDelegate.java index b5cb0c3fec..a3d1ab6728 100644 --- a/tessera-dist/tessera-launcher/src/main/java/com/quorum/tessera/launcher/PicoCliDelegate.java +++ b/tessera-dist/tessera-launcher/src/main/java/com/quorum/tessera/launcher/PicoCliDelegate.java @@ -5,7 +5,9 @@ import com.quorum.tessera.cli.CliResult; import com.quorum.tessera.cli.keypassresolver.CliKeyPasswordResolver; import com.quorum.tessera.cli.keypassresolver.KeyPasswordResolver; +import com.quorum.tessera.cli.parsers.ArgonOptionsConverter; import com.quorum.tessera.cli.parsers.ConfigConverter; +import com.quorum.tessera.config.ArgonOptions; import com.quorum.tessera.config.Config; import com.quorum.tessera.config.cli.KeyUpdateCommand; import com.quorum.tessera.config.cli.KeyUpdateCommandFactory; @@ -82,6 +84,7 @@ public CliResult execute(String[] args) throws Exception { final CommandLine commandLine = new CommandLine(command); commandLine .registerConverter(Config.class, new ConfigConverter()) + .registerConverter(ArgonOptions.class, new ArgonOptionsConverter()) .setSeparator(" ") .setCaseInsensitiveEnumValuesAllowed(true) .setExecutionExceptionHandler(mapper) @@ -107,66 +110,16 @@ public CliResult execute(String[] args) throws Exception { if (!parseResult.hasSubcommand()) { // the node is being started - if (parseResult.originalArgs().size() == 0) { - System.out.println("no options were provided"); // TODO(cjh) delete + final Config config; + try { + config = getConfigFromCLI(parseResult); + } catch (NoTesseraCmdArgsException e) { commandLine.execute("help"); - } else { - System.out.println("at least one option was provided"); // TODO(cjh) delete - List parsedArgs = parseResult.matchedArgs(); - - final Config config; - - // start with any config read from the file - if (parseResult.hasMatchedOption("configfile")) { - config = parseResult.matchedOption("configfile").getValue(); - } else { - config = new Config(); - } - - // apply CLI overrides - parsedArgs.forEach( - parsedArg -> { - // positional (i.e. unnamed) CLI flags are ignored - if (!parsedArg.isOption()) { - return; - } - - OptionSpec parsedOption = (OptionSpec) parsedArg; - - // configfile CLI option is ignored as it was already parsed - // pidfile CLI option is ignored as it is parsed later - // TODO(cjh) improve, checks all names for all provided options - for (String name : parsedOption.names()) { - if ("--configfile".equals(name) || "--pidfile".equals(name)) { - return; - } - } - - String optionName = parsedOption.longestName().replaceFirst("^--", ""); - String[] values = parsedOption.stringValues().toArray(new String[0]); - - LOGGER.debug("Setting : {} with value(s) {}", optionName, values); - OverrideUtil.setValue(config, optionName, values); - LOGGER.debug("Set : {} with value(s) {}", optionName, values); - }); - - System.out.println("all args parsed"); // TODO(cjh) delete - System.out.println("keys count = " + config.getKeys().getKeyData().size()); - System.out.println("useWhiteList = " + config.isUseWhiteList()); - - keyPasswordResolver.resolveKeyPasswords(config); - - final Set> violations = validator.validate(config); - if (!violations.isEmpty()) { - throw new ConstraintViolationException(violations); - } + return new CliResult(1, true, null); + } - if (parseResult.hasMatchedOption("pidfile")) { - createPidFile(parseResult.matchedOption("pidfile").getValue()); - } + return new CliResult(0, false, config); - return new CliResult(0, false, config); - } } else { // there is a subcommand CommandLine.ParseResult subParseResult = parseResult.subcommand(); @@ -176,9 +129,11 @@ public CliResult execute(String[] args) throws Exception { String[] subArgs = new String[subCmdAndArgs.length - 1]; System.arraycopy(subCmdAndArgs, 1, subArgs, 0, subArgs.length); + // TODO(cjh) account for the aliases, e.g. -updatepassword if ("keyupdate".equals(subCmd)) { keyUpdateCommandLine.execute(subArgs); } else if ("keygen".equals(subCmd)) { + // TODO(cjh) document the change of behaviour meaning node cannot start after keygen keyGenCommandLine.execute(subArgs); } @@ -191,6 +146,68 @@ public CliResult execute(String[] args) throws Exception { return new CliResult(1, true, null); } + private Config getConfigFromCLI(CommandLine.ParseResult parseResult) throws Exception { + List parsedArgs = parseResult.matchedArgs(); + + if (parsedArgs.size() == 0) { + System.out.println("no options were provided"); // TODO(cjh) delete + throw new NoTesseraCmdArgsException(); + } + + System.out.println("at least one option was provided"); // TODO(cjh) delete + + final Config config; + + // start with any config read from the file + if (parseResult.hasMatchedOption("configfile")) { + config = parseResult.matchedOption("configfile").getValue(); + } else { + config = new Config(); + } + + // apply CLI overrides + parsedArgs.forEach( + parsedArg -> { + // positional (i.e. unnamed) CLI flags are ignored + if (!parsedArg.isOption()) { + return; + } + + OptionSpec parsedOption = (OptionSpec) parsedArg; + + // configfile CLI option is ignored as it was already parsed + // pidfile CLI option is ignored as it is parsed later + // TODO(cjh) improve, checks all names for all provided options + for (String name : parsedOption.names()) { + if ("--configfile".equals(name) || "--pidfile".equals(name)) { + return; + } + } + + String optionName = parsedOption.longestName().replaceFirst("^--", ""); + String[] values = parsedOption.stringValues().toArray(new String[0]); + + LOGGER.debug("Setting : {} with value(s) {}", optionName, values); + OverrideUtil.setValue(config, optionName, values); + LOGGER.debug("Set : {} with value(s) {}", optionName, values); + }); + + System.out.println("all args parsed"); // TODO(cjh) delete + + keyPasswordResolver.resolveKeyPasswords(config); + + final Set> violations = validator.validate(config); + if (!violations.isEmpty()) { + throw new ConstraintViolationException(violations); + } + + if (parseResult.hasMatchedOption("pidfile")) { + createPidFile(parseResult.matchedOption("pidfile").getValue()); + } + + return config; + } + private void createPidFile(Path pidFilePath) throws Exception { if (pidFilePath == null) { return; From 44735253963c6adff408bef535542aa9fa3eb986 Mon Sep 17 00:00:00 2001 From: chris-j-h Date: Wed, 4 Dec 2019 13:44:00 +0000 Subject: [PATCH 11/41] Execute subcmd without having to manually determine subcmd being called --- .../com/quorum/tessera/launcher/PicoCliDelegate.java | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/tessera-dist/tessera-launcher/src/main/java/com/quorum/tessera/launcher/PicoCliDelegate.java b/tessera-dist/tessera-launcher/src/main/java/com/quorum/tessera/launcher/PicoCliDelegate.java index a3d1ab6728..c6263abfac 100644 --- a/tessera-dist/tessera-launcher/src/main/java/com/quorum/tessera/launcher/PicoCliDelegate.java +++ b/tessera-dist/tessera-launcher/src/main/java/com/quorum/tessera/launcher/PicoCliDelegate.java @@ -125,17 +125,11 @@ public CliResult execute(String[] args) throws Exception { CommandLine.ParseResult subParseResult = parseResult.subcommand(); String[] subCmdAndArgs = subParseResult.originalArgs().toArray(new String[0]); - String subCmd = subCmdAndArgs[0]; String[] subArgs = new String[subCmdAndArgs.length - 1]; System.arraycopy(subCmdAndArgs, 1, subArgs, 0, subArgs.length); - // TODO(cjh) account for the aliases, e.g. -updatepassword - if ("keyupdate".equals(subCmd)) { - keyUpdateCommandLine.execute(subArgs); - } else if ("keygen".equals(subCmd)) { - // TODO(cjh) document the change of behaviour meaning node cannot start after keygen - keyGenCommandLine.execute(subArgs); - } + // TODO(cjh) document the change of behaviour meaning node cannot start after keygen + subParseResult.asCommandLineList().get(0).execute(subArgs); // if an exception occurred, throw it to to the upper levels where it gets handled if (mapper.getThrown() != null) { From 8c69997642efec0556b8c6f82a76362c8462f630 Mon Sep 17 00:00:00 2001 From: chris-j-h Date: Wed, 4 Dec 2019 14:33:00 +0000 Subject: [PATCH 12/41] Change main cmd name to tessera --- .../java/com/quorum/tessera/key/generation/TesseraCommand.java | 1 + 1 file changed, 1 insertion(+) diff --git a/key-generation/src/main/java/com/quorum/tessera/key/generation/TesseraCommand.java b/key-generation/src/main/java/com/quorum/tessera/key/generation/TesseraCommand.java index a32d88f884..aafa639eac 100644 --- a/key-generation/src/main/java/com/quorum/tessera/key/generation/TesseraCommand.java +++ b/key-generation/src/main/java/com/quorum/tessera/key/generation/TesseraCommand.java @@ -6,6 +6,7 @@ import java.nio.file.Path; @CommandLine.Command( + name = "tessera", headerHeading = "Usage:%n%n", header = "Tessera private transaction manager for Quorum", synopsisHeading = "%n", From 9ab8d7a3c50c6f364e8f8083d9cb1dd054b5517c Mon Sep 17 00:00:00 2001 From: chris-j-h Date: Wed, 4 Dec 2019 14:35:29 +0000 Subject: [PATCH 13/41] Show help if no options are provided for a subcommand --- .../java/com/quorum/tessera/launcher/PicoCliDelegate.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tessera-dist/tessera-launcher/src/main/java/com/quorum/tessera/launcher/PicoCliDelegate.java b/tessera-dist/tessera-launcher/src/main/java/com/quorum/tessera/launcher/PicoCliDelegate.java index c6263abfac..bb378c17c6 100644 --- a/tessera-dist/tessera-launcher/src/main/java/com/quorum/tessera/launcher/PicoCliDelegate.java +++ b/tessera-dist/tessera-launcher/src/main/java/com/quorum/tessera/launcher/PicoCliDelegate.java @@ -125,6 +125,13 @@ public CliResult execute(String[] args) throws Exception { CommandLine.ParseResult subParseResult = parseResult.subcommand(); String[] subCmdAndArgs = subParseResult.originalArgs().toArray(new String[0]); + + // print help as no args provided + if (subCmdAndArgs.length == 1) { + subParseResult.asCommandLineList().get(0).execute("help"); + return new CliResult(0, true, null); + } + String[] subArgs = new String[subCmdAndArgs.length - 1]; System.arraycopy(subCmdAndArgs, 1, subArgs, 0, subArgs.length); From 3b84ad5aa6cbd462f0367796dd0c7b2ab918d74b Mon Sep 17 00:00:00 2001 From: chris-j-h Date: Wed, 4 Dec 2019 15:28:50 +0000 Subject: [PATCH 14/41] Remove hardcoded default for encryptor type CLI option and clean up --- .../tessera/cli/parsers/EncryptorOptions.java | 13 +++++++++---- .../tessera/key/generation/KeyGenCommand.java | 2 +- .../quorum/tessera/launcher/PicoCliDelegate.java | 6 ------ 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/cli/cli-api/src/main/java/com/quorum/tessera/cli/parsers/EncryptorOptions.java b/cli/cli-api/src/main/java/com/quorum/tessera/cli/parsers/EncryptorOptions.java index 1bfa92e6b4..4dc5296481 100644 --- a/cli/cli-api/src/main/java/com/quorum/tessera/cli/parsers/EncryptorOptions.java +++ b/cli/cli-api/src/main/java/com/quorum/tessera/cli/parsers/EncryptorOptions.java @@ -6,14 +6,14 @@ import java.util.HashMap; import java.util.Map; +import java.util.Objects; import java.util.Optional; public class EncryptorOptions { - // TODO(cjh) default value using enum instead of hardcoding @CommandLine.Option( - names = {"--encryptor.type"}, - defaultValue = "NACL") + names = {"--encryptor.type"}, + description = "Valid values: ${COMPLETION-CANDIDATES}") public EncryptorType type; @CommandLine.Option(names = {"--encryptor.symmetricCipher"}) @@ -30,6 +30,12 @@ public class EncryptorOptions { public EncryptorConfig parseEncryptorConfig() { final EncryptorConfig encryptorConfig = new EncryptorConfig(); + + // we set the default here instead of in the option annotation as enum values cannot be used in annotationss + if (Objects.isNull(type)) { + type = EncryptorType.NACL; + } + encryptorConfig.setType(type); if (type == EncryptorType.EC) { @@ -45,5 +51,4 @@ public EncryptorConfig parseEncryptorConfig() { return encryptorConfig; } - } diff --git a/key-generation/src/main/java/com/quorum/tessera/key/generation/KeyGenCommand.java b/key-generation/src/main/java/com/quorum/tessera/key/generation/KeyGenCommand.java index d2b3e7ae66..59d04dc534 100644 --- a/key-generation/src/main/java/com/quorum/tessera/key/generation/KeyGenCommand.java +++ b/key-generation/src/main/java/com/quorum/tessera/key/generation/KeyGenCommand.java @@ -52,7 +52,7 @@ public class KeyGenCommand implements Callable { @CommandLine.Option( names = {"--vault.type", "-keygenvaulttype"}, - description = "Specify the key vault provider the generated key is to be saved in. If not set, the key will be encrypted and stored on the local filesystem" + description = "Specify the key vault provider the generated key is to be saved in. If not set, the key will be encrypted and stored on the local filesystem. Valid values: ${COMPLETION-CANDIDATES})" ) //TODO(cjh) get possible enum values to show in the usage public KeyVaultType vaultType; diff --git a/tessera-dist/tessera-launcher/src/main/java/com/quorum/tessera/launcher/PicoCliDelegate.java b/tessera-dist/tessera-launcher/src/main/java/com/quorum/tessera/launcher/PicoCliDelegate.java index bb378c17c6..c4bcc873ea 100644 --- a/tessera-dist/tessera-launcher/src/main/java/com/quorum/tessera/launcher/PicoCliDelegate.java +++ b/tessera-dist/tessera-launcher/src/main/java/com/quorum/tessera/launcher/PicoCliDelegate.java @@ -56,7 +56,6 @@ public PicoCliDelegate(final KeyPasswordResolver keyPasswordResolver) { public CliResult execute(String[] args) throws Exception { final CommandSpec command = CommandSpec.forAnnotatedObject(TesseraCommand.class); - // TODO(cjh) add options and positional parameters // TODO(cjh) most usage options have empty lines between them, but not all. Need to remove the empty lines. // add config override options, dynamically generated from the config object Map overrideOptions = OverrideUtil.buildConfigOptions(); @@ -151,12 +150,9 @@ private Config getConfigFromCLI(CommandLine.ParseResult parseResult) throws Exce List parsedArgs = parseResult.matchedArgs(); if (parsedArgs.size() == 0) { - System.out.println("no options were provided"); // TODO(cjh) delete throw new NoTesseraCmdArgsException(); } - System.out.println("at least one option was provided"); // TODO(cjh) delete - final Config config; // start with any config read from the file @@ -193,8 +189,6 @@ private Config getConfigFromCLI(CommandLine.ParseResult parseResult) throws Exce LOGGER.debug("Set : {} with value(s) {}", optionName, values); }); - System.out.println("all args parsed"); // TODO(cjh) delete - keyPasswordResolver.resolveKeyPasswords(config); final Set> violations = validator.validate(config); From 5468edc33b0d8a53edb8197918bb273228eca076 Mon Sep 17 00:00:00 2001 From: chris-j-h Date: Wed, 4 Dec 2019 16:43:39 +0000 Subject: [PATCH 15/41] Create picocli module for new files. This is a temporary stopgap until the cli module is flattened and further simplified --- cli/pico-cli/pom.xml | 25 +++++++++++++++++++ .../picocli}/ArgonOptionsConverter.java | 2 +- .../tessera/picocli}/EncryptorOptions.java | 2 +- .../tessera/picocli}/KeyGenCommand.java | 6 +++-- .../tessera/picocli}/KeyUpdateCommand.java | 3 +-- .../picocli}/KeyUpdateCommandFactory.java | 2 +- .../picocli}/NoTesseraCmdArgsException.java | 2 +- .../tessera/picocli}/PicoCliDelegate.java | 7 +----- .../tessera/picocli}/TesseraCommand.java | 2 +- cli/pom.xml | 1 + tessera-dist/tessera-launcher/pom.xml | 6 +++++ .../com/quorum/tessera/launcher/Main.java | 3 ++- 12 files changed, 45 insertions(+), 16 deletions(-) create mode 100644 cli/pico-cli/pom.xml rename cli/{cli-api/src/main/java/com/quorum/tessera/cli/parsers => pico-cli/src/main/java/com/quorum/tessera/picocli}/ArgonOptionsConverter.java (94%) rename cli/{cli-api/src/main/java/com/quorum/tessera/cli/parsers => pico-cli/src/main/java/com/quorum/tessera/picocli}/EncryptorOptions.java (97%) rename {key-generation/src/main/java/com/quorum/tessera/key/generation => cli/pico-cli/src/main/java/com/quorum/tessera/picocli}/KeyGenCommand.java (96%) rename cli/{config-cli/src/main/java/com/quorum/tessera/config/cli => pico-cli/src/main/java/com/quorum/tessera/picocli}/KeyUpdateCommand.java (98%) rename cli/{config-cli/src/main/java/com/quorum/tessera/config/cli => pico-cli/src/main/java/com/quorum/tessera/picocli}/KeyUpdateCommandFactory.java (96%) rename {tessera-dist/tessera-launcher/src/main/java/com/quorum/tessera/launcher => cli/pico-cli/src/main/java/com/quorum/tessera/picocli}/NoTesseraCmdArgsException.java (65%) rename {tessera-dist/tessera-launcher/src/main/java/com/quorum/tessera/launcher => cli/pico-cli/src/main/java/com/quorum/tessera/picocli}/PicoCliDelegate.java (96%) rename {key-generation/src/main/java/com/quorum/tessera/key/generation => cli/pico-cli/src/main/java/com/quorum/tessera/picocli}/TesseraCommand.java (95%) diff --git a/cli/pico-cli/pom.xml b/cli/pico-cli/pom.xml new file mode 100644 index 0000000000..2279cc78fd --- /dev/null +++ b/cli/pico-cli/pom.xml @@ -0,0 +1,25 @@ + + + + cli + com.jpmorgan.quorum + 0.11-SNAPSHOT + + 4.0.0 + + pico-cli + + + com.jpmorgan.quorum + cli-api + + + com.jpmorgan.quorum + config-cli + + + + + diff --git a/cli/cli-api/src/main/java/com/quorum/tessera/cli/parsers/ArgonOptionsConverter.java b/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/ArgonOptionsConverter.java similarity index 94% rename from cli/cli-api/src/main/java/com/quorum/tessera/cli/parsers/ArgonOptionsConverter.java rename to cli/pico-cli/src/main/java/com/quorum/tessera/picocli/ArgonOptionsConverter.java index 6059bbaefa..952d0c4c70 100644 --- a/cli/cli-api/src/main/java/com/quorum/tessera/cli/parsers/ArgonOptionsConverter.java +++ b/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/ArgonOptionsConverter.java @@ -1,4 +1,4 @@ -package com.quorum.tessera.cli.parsers; +package com.quorum.tessera.picocli; import com.quorum.tessera.config.ArgonOptions; import com.quorum.tessera.config.util.JaxbUtil; diff --git a/cli/cli-api/src/main/java/com/quorum/tessera/cli/parsers/EncryptorOptions.java b/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/EncryptorOptions.java similarity index 97% rename from cli/cli-api/src/main/java/com/quorum/tessera/cli/parsers/EncryptorOptions.java rename to cli/pico-cli/src/main/java/com/quorum/tessera/picocli/EncryptorOptions.java index 4dc5296481..7bf9c4dd89 100644 --- a/cli/cli-api/src/main/java/com/quorum/tessera/cli/parsers/EncryptorOptions.java +++ b/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/EncryptorOptions.java @@ -1,4 +1,4 @@ -package com.quorum.tessera.cli.parsers; +package com.quorum.tessera.picocli; import com.quorum.tessera.config.EncryptorConfig; import com.quorum.tessera.config.EncryptorType; diff --git a/key-generation/src/main/java/com/quorum/tessera/key/generation/KeyGenCommand.java b/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/KeyGenCommand.java similarity index 96% rename from key-generation/src/main/java/com/quorum/tessera/key/generation/KeyGenCommand.java rename to cli/pico-cli/src/main/java/com/quorum/tessera/picocli/KeyGenCommand.java index 59d04dc534..895ce05f89 100644 --- a/key-generation/src/main/java/com/quorum/tessera/key/generation/KeyGenCommand.java +++ b/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/KeyGenCommand.java @@ -1,9 +1,11 @@ -package com.quorum.tessera.key.generation; +package com.quorum.tessera.picocli; import com.quorum.tessera.cli.CliException; import com.quorum.tessera.cli.CliResult; -import com.quorum.tessera.cli.parsers.EncryptorOptions; import com.quorum.tessera.config.*; +import com.quorum.tessera.key.generation.KeyGenerator; +import com.quorum.tessera.key.generation.KeyGeneratorFactory; +import com.quorum.tessera.key.generation.KeyVaultOptions; import picocli.CommandLine; import javax.validation.ConstraintViolation; diff --git a/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/KeyUpdateCommand.java b/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/KeyUpdateCommand.java similarity index 98% rename from cli/config-cli/src/main/java/com/quorum/tessera/config/cli/KeyUpdateCommand.java rename to cli/pico-cli/src/main/java/com/quorum/tessera/picocli/KeyUpdateCommand.java index 46a66db550..66eb75c4c8 100644 --- a/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/KeyUpdateCommand.java +++ b/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/KeyUpdateCommand.java @@ -1,7 +1,6 @@ -package com.quorum.tessera.config.cli; +package com.quorum.tessera.picocli; import com.quorum.tessera.cli.CliResult; -import com.quorum.tessera.cli.parsers.EncryptorOptions; import com.quorum.tessera.config.*; import com.quorum.tessera.config.keys.KeyEncryptor; import com.quorum.tessera.config.keys.KeyEncryptorFactory; diff --git a/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/KeyUpdateCommandFactory.java b/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/KeyUpdateCommandFactory.java similarity index 96% rename from cli/config-cli/src/main/java/com/quorum/tessera/config/cli/KeyUpdateCommandFactory.java rename to cli/pico-cli/src/main/java/com/quorum/tessera/picocli/KeyUpdateCommandFactory.java index da89434313..68f827b7cb 100644 --- a/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/KeyUpdateCommandFactory.java +++ b/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/KeyUpdateCommandFactory.java @@ -1,4 +1,4 @@ -package com.quorum.tessera.config.cli; +package com.quorum.tessera.picocli; import com.quorum.tessera.config.keys.KeyEncryptorFactory; import com.quorum.tessera.passwords.PasswordReader; diff --git a/tessera-dist/tessera-launcher/src/main/java/com/quorum/tessera/launcher/NoTesseraCmdArgsException.java b/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/NoTesseraCmdArgsException.java similarity index 65% rename from tessera-dist/tessera-launcher/src/main/java/com/quorum/tessera/launcher/NoTesseraCmdArgsException.java rename to cli/pico-cli/src/main/java/com/quorum/tessera/picocli/NoTesseraCmdArgsException.java index 7f524d4c1f..226a7d8960 100644 --- a/tessera-dist/tessera-launcher/src/main/java/com/quorum/tessera/launcher/NoTesseraCmdArgsException.java +++ b/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/NoTesseraCmdArgsException.java @@ -1,4 +1,4 @@ -package com.quorum.tessera.launcher; +package com.quorum.tessera.picocli; public class NoTesseraCmdArgsException extends RuntimeException { diff --git a/tessera-dist/tessera-launcher/src/main/java/com/quorum/tessera/launcher/PicoCliDelegate.java b/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/PicoCliDelegate.java similarity index 96% rename from tessera-dist/tessera-launcher/src/main/java/com/quorum/tessera/launcher/PicoCliDelegate.java rename to cli/pico-cli/src/main/java/com/quorum/tessera/picocli/PicoCliDelegate.java index c4bcc873ea..5b8baa8b72 100644 --- a/tessera-dist/tessera-launcher/src/main/java/com/quorum/tessera/launcher/PicoCliDelegate.java +++ b/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/PicoCliDelegate.java @@ -1,19 +1,14 @@ -package com.quorum.tessera.launcher; +package com.quorum.tessera.picocli; import com.quorum.tessera.ServiceLoaderUtil; import com.quorum.tessera.cli.CLIExceptionCapturer; import com.quorum.tessera.cli.CliResult; import com.quorum.tessera.cli.keypassresolver.CliKeyPasswordResolver; import com.quorum.tessera.cli.keypassresolver.KeyPasswordResolver; -import com.quorum.tessera.cli.parsers.ArgonOptionsConverter; import com.quorum.tessera.cli.parsers.ConfigConverter; import com.quorum.tessera.config.ArgonOptions; import com.quorum.tessera.config.Config; -import com.quorum.tessera.config.cli.KeyUpdateCommand; -import com.quorum.tessera.config.cli.KeyUpdateCommandFactory; import com.quorum.tessera.config.cli.OverrideUtil; -import com.quorum.tessera.key.generation.KeyGenCommand; -import com.quorum.tessera.key.generation.TesseraCommand; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import picocli.CommandLine; diff --git a/key-generation/src/main/java/com/quorum/tessera/key/generation/TesseraCommand.java b/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/TesseraCommand.java similarity index 95% rename from key-generation/src/main/java/com/quorum/tessera/key/generation/TesseraCommand.java rename to cli/pico-cli/src/main/java/com/quorum/tessera/picocli/TesseraCommand.java index aafa639eac..94646cc682 100644 --- a/key-generation/src/main/java/com/quorum/tessera/key/generation/TesseraCommand.java +++ b/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/TesseraCommand.java @@ -1,4 +1,4 @@ -package com.quorum.tessera.key.generation; +package com.quorum.tessera.picocli; import com.quorum.tessera.config.Config; import picocli.CommandLine; diff --git a/cli/pom.xml b/cli/pom.xml index 02010d98fc..890a1c3ba8 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -6,6 +6,7 @@ cli-api config-cli admin-cli + pico-cli diff --git a/tessera-dist/tessera-launcher/pom.xml b/tessera-dist/tessera-launcher/pom.xml index 6f5c37314c..c77852470c 100644 --- a/tessera-dist/tessera-launcher/pom.xml +++ b/tessera-dist/tessera-launcher/pom.xml @@ -29,5 +29,11 @@ com.jpmorgan.quorum config-cli + + com.jpmorgan.quorum + pico-cli + 0.11-SNAPSHOT + compile + diff --git a/tessera-dist/tessera-launcher/src/main/java/com/quorum/tessera/launcher/Main.java b/tessera-dist/tessera-launcher/src/main/java/com/quorum/tessera/launcher/Main.java index 1c905bbdbf..4d93f759b0 100644 --- a/tessera-dist/tessera-launcher/src/main/java/com/quorum/tessera/launcher/Main.java +++ b/tessera-dist/tessera-launcher/src/main/java/com/quorum/tessera/launcher/Main.java @@ -7,6 +7,7 @@ import com.quorum.tessera.config.Config; import com.quorum.tessera.config.ConfigException; import com.quorum.tessera.config.apps.TesseraAppFactory; +import com.quorum.tessera.picocli.PicoCliDelegate; import com.quorum.tessera.server.TesseraServer; import com.quorum.tessera.server.TesseraServerFactory; import org.apache.commons.lang3.exception.ExceptionUtils; @@ -30,7 +31,7 @@ public static void main(final String... args) throws Exception { System.setProperty("javax.xml.bind.context.factory", "org.eclipse.persistence.jaxb.JAXBContextFactory"); try { -// final CliResult cliResult = CliDelegate.instance().execute(args); + // final CliResult cliResult = CliDelegate.instance().execute(args); PicoCliDelegate picoCliDelegate = new PicoCliDelegate(); final CliResult cliResult = picoCliDelegate.execute(args); From 965a6b906c1743371473b906e2f71af25f3ac9f4 Mon Sep 17 00:00:00 2001 From: chris-j-h Date: Thu, 5 Dec 2019 09:59:20 +0000 Subject: [PATCH 16/41] [WIP] Migrate existing CLI tests to new picocli implementation --- .../config/cli/DefaultCliAdapterTest.java | 99 +++++++++---------- .../tessera/picocli/PicoCliDelegate.java | 6 +- .../tessera/picocli/PicoCliDelegateTest.java | 61 ++++++++++++ .../src/test/resources/sample-config.json | 80 +++++++++++++++ 4 files changed, 193 insertions(+), 53 deletions(-) create mode 100644 cli/pico-cli/src/test/java/com/quorum/tessera/picocli/PicoCliDelegateTest.java create mode 100644 cli/pico-cli/src/test/resources/sample-config.json diff --git a/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/DefaultCliAdapterTest.java b/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/DefaultCliAdapterTest.java index 4c537ff86f..b52b498cb8 100644 --- a/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/DefaultCliAdapterTest.java +++ b/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/DefaultCliAdapterTest.java @@ -2,7 +2,6 @@ import com.quorum.tessera.cli.CliException; import com.quorum.tessera.cli.CliResult; -import com.quorum.tessera.cli.CliType; import com.quorum.tessera.config.KeyDataConfig; import com.quorum.tessera.config.Peer; import com.quorum.tessera.config.PrivateKeyType; @@ -49,55 +48,55 @@ public void setUp() { this.cliAdapter = new DefaultCliAdapter(); } - @Test - public void getType() { - assertThat(cliAdapter.getType()).isEqualTo(CliType.CONFIG); - } - - @Test - public void help() throws Exception { - - final CliResult result = cliAdapter.execute("help"); - - assertThat(result).isNotNull(); - assertThat(result.getConfig()).isNotPresent(); - assertThat(result.getStatus()).isEqualTo(0); - assertThat(result.isSuppressStartup()).isTrue(); - } - - @Test - public void helpViaCall() throws Exception { - cliAdapter.setAllParameters(new String[] {"help"}); - final CliResult result = cliAdapter.call(); - - assertThat(result).isNotNull(); - assertThat(result.getConfig()).isNotPresent(); - assertThat(result.getStatus()).isEqualTo(0); - assertThat(result.isSuppressStartup()).isTrue(); - } - - @Test - public void noArgsPrintsHelp() throws Exception { - - final CliResult result = cliAdapter.execute(); - - assertThat(result).isNotNull(); - assertThat(result.getConfig()).isNotPresent(); - assertThat(result.getStatus()).isEqualTo(0); - assertThat(result.isSuppressStartup()).isTrue(); - } - - @Test - public void withValidConfig() throws Exception { - - Path configFile = createAndPopulatePaths(getClass().getResource("/sample-config.json")); - CliResult result = cliAdapter.execute("-configfile", configFile.toString()); - - assertThat(result).isNotNull(); - assertThat(result.getConfig()).isPresent(); - assertThat(result.getStatus()).isEqualTo(0); - assertThat(result.isSuppressStartup()).isFalse(); - } +// @Test +// public void getType() { +// assertThat(cliAdapter.getType()).isEqualTo(CliType.CONFIG); +// } +// +// @Test +// public void help() throws Exception { +// +// final CliResult result = cliAdapter.execute("help"); +// +// assertThat(result).isNotNull(); +// assertThat(result.getConfig()).isNotPresent(); +// assertThat(result.getStatus()).isEqualTo(0); +// assertThat(result.isSuppressStartup()).isTrue(); +// } +// +// @Test +// public void helpViaCall() throws Exception { +// cliAdapter.setAllParameters(new String[] {"help"}); +// final CliResult result = cliAdapter.call(); +// +// assertThat(result).isNotNull(); +// assertThat(result.getConfig()).isNotPresent(); +// assertThat(result.getStatus()).isEqualTo(0); +// assertThat(result.isSuppressStartup()).isTrue(); +// } +// +// @Test +// public void noArgsPrintsHelp() throws Exception { +// +// final CliResult result = cliAdapter.execute(); +// +// assertThat(result).isNotNull(); +// assertThat(result.getConfig()).isNotPresent(); +// assertThat(result.getStatus()).isEqualTo(0); +// assertThat(result.isSuppressStartup()).isTrue(); +// } +// +// @Test +// public void withValidConfig() throws Exception { +// +// Path configFile = createAndPopulatePaths(getClass().getResource("/sample-config.json")); +// CliResult result = cliAdapter.execute("-configfile", configFile.toString()); +// +// assertThat(result).isNotNull(); +// assertThat(result.getConfig()).isPresent(); +// assertThat(result.getStatus()).isEqualTo(0); +// assertThat(result.isSuppressStartup()).isFalse(); +// } @Test(expected = CliException.class) public void processArgsMissing() throws Exception { diff --git a/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/PicoCliDelegate.java b/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/PicoCliDelegate.java index 5b8baa8b72..46c8c60377 100644 --- a/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/PicoCliDelegate.java +++ b/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/PicoCliDelegate.java @@ -44,11 +44,11 @@ public PicoCliDelegate() { this(ServiceLoaderUtil.load(KeyPasswordResolver.class).orElse(new CliKeyPasswordResolver())); } - public PicoCliDelegate(final KeyPasswordResolver keyPasswordResolver) { + private PicoCliDelegate(final KeyPasswordResolver keyPasswordResolver) { this.keyPasswordResolver = Objects.requireNonNull(keyPasswordResolver); } - public CliResult execute(String[] args) throws Exception { + public CliResult execute(String... args) throws Exception { final CommandSpec command = CommandSpec.forAnnotatedObject(TesseraCommand.class); // TODO(cjh) most usage options have empty lines between them, but not all. Need to remove the empty lines. @@ -109,7 +109,7 @@ public CliResult execute(String[] args) throws Exception { config = getConfigFromCLI(parseResult); } catch (NoTesseraCmdArgsException e) { commandLine.execute("help"); - return new CliResult(1, true, null); + return new CliResult(0, true, null); } return new CliResult(0, false, config); diff --git a/cli/pico-cli/src/test/java/com/quorum/tessera/picocli/PicoCliDelegateTest.java b/cli/pico-cli/src/test/java/com/quorum/tessera/picocli/PicoCliDelegateTest.java new file mode 100644 index 0000000000..2f7ba6efe4 --- /dev/null +++ b/cli/pico-cli/src/test/java/com/quorum/tessera/picocli/PicoCliDelegateTest.java @@ -0,0 +1,61 @@ +package com.quorum.tessera.picocli; + +import com.quorum.tessera.cli.CliException; +import com.quorum.tessera.cli.CliResult; +import org.junit.Before; +import org.junit.Test; + +import java.nio.file.Path; + +import static com.quorum.tessera.test.util.ElUtil.createAndPopulatePaths; +import static org.assertj.core.api.Assertions.assertThat; + +public class PicoCliDelegateTest { + + private PicoCliDelegate cliDelegate; + + @Before + public void setUp() { + cliDelegate = new PicoCliDelegate(); + } + + @Test + public void help() throws Exception { + + final CliResult result = cliDelegate.execute("help"); + + assertThat(result).isNotNull(); + assertThat(result.getConfig()).isNotPresent(); + assertThat(result.getStatus()).isEqualTo(0); + assertThat(result.isSuppressStartup()).isTrue(); + } + + @Test + public void noArgsPrintsHelp() throws Exception { + + final CliResult result = cliDelegate.execute(); + + assertThat(result).isNotNull(); + assertThat(result.getConfig()).isNotPresent(); + assertThat(result.getStatus()).isEqualTo(0); + assertThat(result.isSuppressStartup()).isTrue(); + } + + @Test + public void withValidConfig() throws Exception { + + Path configFile = createAndPopulatePaths(getClass().getResource("/sample-config.json")); + CliResult result = cliDelegate.execute("-configfile", configFile.toString()); + + assertThat(result).isNotNull(); + assertThat(result.getConfig()).isPresent(); + assertThat(result.getStatus()).isEqualTo(0); + assertThat(result.isSuppressStartup()).isFalse(); + } + + @Test(expected = CliException.class) + public void processArgsMissing() throws Exception { + cliDelegate.execute("-configfile"); + } + +} diff --git a/cli/pico-cli/src/test/resources/sample-config.json b/cli/pico-cli/src/test/resources/sample-config.json new file mode 100644 index 0000000000..f4a16719f1 --- /dev/null +++ b/cli/pico-cli/src/test/resources/sample-config.json @@ -0,0 +1,80 @@ +{ + "useWhiteList": false, + "encryptor": { + "type":"NACL" + }, + "jdbc": { + "username": "scott", + "password": "tiger", + "url": "foo:bar" + }, + "serverConfigs": [ + { + "app": "ThirdParty", + "enabled": true, + "serverAddress": "http://localhost:8090", + + "communicationType": "REST" + }, + { + "app": "ADMIN", + "enabled": true, + "serverAddress": "http://localhost:18090", + "communicationType": "REST" + }, + { + "app": "Q2T", + "enabled": true, + "serverAddress": "unix:/tmp/test.ipc", + "communicationType": "REST" + }, + { + "app": "P2P", + "enabled": true, + "serverAddress": "http://localhost:8091", + "sslConfig": { + "tls": "OFF", + "generateKeyStoreIfNotExisted": "false", + "serverKeyStore": "./ssl/server1-keystore", + "serverKeyStorePassword": "quorum", + "serverTrustStore": "./ssl/server-truststore", + "serverTrustStorePassword": "quorum", + "serverTrustMode": "CA", + "clientKeyStore": "./ssl/client1-keystore", + "clientKeyStorePassword": "quorum", + "clientTrustStore": "./ssl/client-truststore", + "clientTrustStorePassword": "quorum", + "clientTrustMode": "CA", + "knownClientsFile": "./ssl/knownClients1", + "knownServersFile": "./ssl/knownServers1" + }, + "communicationType": "REST" + } + ], + "peer": [ + { + "url": "http://bogus1.com" + }, + { + "url": "http://bogus2.com" + } + ], + "keys": { + "passwords": [], + "keyData": [ + { + "config": { + "data": { + "bytes": "Wl+xSyXVuuqzpvznOS7dOobhcn4C5auxkFRi7yLtgtA=" + }, + "type": "unlocked" + }, + "publicKey": "/+UuD63zItL1EbjxkKUljMgG8Z1w0AJ8pNOR4iq2yQc=" + } + ] + }, + "alwaysSendTo": [ + "/+UuD63zItL1EbjxkKUljMgG8Z1w0AJ8pNOR4iq2yQc=" + ], + "unixSocketFile": "${unixSocketPath}" +} From 99f399f5e9ae62b8739c6012389f2e10f0cddbde Mon Sep 17 00:00:00 2001 From: Mark Lowe Date: Tue, 10 Dec 2019 18:02:32 +0000 Subject: [PATCH 17/41] Add gradle buld files. just enough to build. --- cli/pico-cli/build.gradle | 12 ++++++++++++ key-generation/build.gradle | 8 ++++---- settings.gradle | 2 ++ tessera-dist/tessera-launcher/build.gradle | 11 ++++++----- 4 files changed, 24 insertions(+), 9 deletions(-) create mode 100644 cli/pico-cli/build.gradle diff --git a/cli/pico-cli/build.gradle b/cli/pico-cli/build.gradle new file mode 100644 index 0000000000..8f9658648c --- /dev/null +++ b/cli/pico-cli/build.gradle @@ -0,0 +1,12 @@ + +dependencies { + compile 'info.picocli:picocli' + compile project(':cli:cli-api') + compile project(':cli:config-cli') + compile project(':shared') + compile project(':config') + compile project(':encryption:encryption-api') + compile project(':key-generation') + testCompile project(':tests:test-util') + compile "org.hibernate:hibernate-validator" +} diff --git a/key-generation/build.gradle b/key-generation/build.gradle index fbc2bdb917..6d126e84d8 100644 --- a/key-generation/build.gradle +++ b/key-generation/build.gradle @@ -1,7 +1,7 @@ dependencies { - implementation project(':encryption:encryption-api') - implementation project(':config') - implementation project(':shared') - implementation project(':key-vault:key-vault-api') + compile project(':encryption:encryption-api') + compile project(':config') + compile project(':shared') + compile project(':key-vault:key-vault-api') } diff --git a/settings.gradle b/settings.gradle index 434eb0e379..22224105b4 100644 --- a/settings.gradle +++ b/settings.gradle @@ -4,6 +4,7 @@ include(':config') include(':cli:cli-api') include(':cli:config-cli') include(':cli:admin-cli') +include(':cli:pico-cli') include(':cli') include(':tests:acceptance-test') include(':tests:test-util') @@ -56,6 +57,7 @@ include(':tessera-jaxrs:jaxrs-client') include(':tessera-jaxrs') include(':tessera-data') project(':cli:cli-api').projectDir = file('cli/cli-api') +project(':cli:pico-cli').projectDir = file('cli/pico-cli') project(':cli:config-cli').projectDir = file('cli/config-cli') project(':cli:admin-cli').projectDir = file('cli/admin-cli') project(':tests:acceptance-test').projectDir = file('tests/acceptance-test') diff --git a/tessera-dist/tessera-launcher/build.gradle b/tessera-dist/tessera-launcher/build.gradle index be6afd62ab..c914e2edc0 100644 --- a/tessera-dist/tessera-launcher/build.gradle +++ b/tessera-dist/tessera-launcher/build.gradle @@ -1,8 +1,9 @@ dependencies { - implementation project(':config') - implementation project(':cli:cli-api') - implementation project(':server:server-api') - implementation project(':tessera-jaxrs:common-jaxrs') - implementation 'org.apache.commons:commons-lang3:3.7' + compile project(':config') + compile project(':cli:cli-api') + compile project(':cli:pico-cli') + compile project(':server:server-api') + compile project(':tessera-jaxrs:common-jaxrs') + compile 'org.apache.commons:commons-lang3:3.7' } From 3145f49418adb57121a48966d5b9c981cdf37e94 Mon Sep 17 00:00:00 2001 From: chris-j-h Date: Thu, 12 Dec 2019 11:09:47 +0000 Subject: [PATCH 18/41] Fix tessera-launcher dependencies after incorrect merge with master --- tessera-dist/tessera-launcher/build.gradle | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tessera-dist/tessera-launcher/build.gradle b/tessera-dist/tessera-launcher/build.gradle index c914e2edc0..b01b31add9 100644 --- a/tessera-dist/tessera-launcher/build.gradle +++ b/tessera-dist/tessera-launcher/build.gradle @@ -5,5 +5,6 @@ dependencies { compile project(':cli:pico-cli') compile project(':server:server-api') compile project(':tessera-jaxrs:common-jaxrs') - compile 'org.apache.commons:commons-lang3:3.7' + compile 'org.apache.commons:commons-lang3' + compile 'org.glassfish:javax.json' } From 222221295d36a7179da5d6eb76d3c97f15b4d416 Mon Sep 17 00:00:00 2001 From: chris-j-h Date: Thu, 12 Dec 2019 13:41:58 +0000 Subject: [PATCH 19/41] Start to migrate DefaultCliAdapter tests for use with PicoCliDelegate Make minor changes to the pico implementation code in response to test migration --- .../config/cli/DefaultCliAdapterTest.java | 525 +++++++++--------- .../quorum/tessera/picocli/KeyGenCommand.java | 103 ++-- .../NoTesseraConfigfileOptionException.java | 5 + .../tessera/picocli/PicoCliDelegate.java | 62 ++- .../tessera/picocli/TesseraCommand.java | 30 +- .../tessera/picocli/PicoCliDelegateTest.java | 257 ++++++++- .../picocli/keys/MockKeyGeneratorFactory.java | 30 + ...tessera.key.generation.KeyGeneratorFactory | 1 + .../src/test/resources/keygen-sample.json | 62 +++ .../src/test/resources/lockedprivatekey.json | 14 + .../src/test/resources/missing-config.json | 38 ++ .../resources/sample-config-invalidpath.json | 68 +++ 12 files changed, 829 insertions(+), 366 deletions(-) create mode 100644 cli/pico-cli/src/main/java/com/quorum/tessera/picocli/NoTesseraConfigfileOptionException.java create mode 100644 cli/pico-cli/src/test/java/com/quorum/tessera/picocli/keys/MockKeyGeneratorFactory.java create mode 100644 cli/pico-cli/src/test/resources/META-INF/services/com.quorum.tessera.key.generation.KeyGeneratorFactory create mode 100644 cli/pico-cli/src/test/resources/keygen-sample.json create mode 100644 cli/pico-cli/src/test/resources/lockedprivatekey.json create mode 100644 cli/pico-cli/src/test/resources/missing-config.json create mode 100644 cli/pico-cli/src/test/resources/sample-config-invalidpath.json diff --git a/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/DefaultCliAdapterTest.java b/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/DefaultCliAdapterTest.java index 51d50a2369..cba3c3a3e4 100644 --- a/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/DefaultCliAdapterTest.java +++ b/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/DefaultCliAdapterTest.java @@ -1,19 +1,13 @@ package com.quorum.tessera.config.cli; -import com.quorum.tessera.cli.CliException; import com.quorum.tessera.cli.CliResult; import com.quorum.tessera.config.KeyDataConfig; import com.quorum.tessera.config.Peer; -import com.quorum.tessera.config.PrivateKeyType; import com.quorum.tessera.config.cli.keys.MockKeyGeneratorFactory; import com.quorum.tessera.config.keypairs.FilesystemKeyPair; -import com.quorum.tessera.config.keypairs.InlineKeypair; -import com.quorum.tessera.config.keys.KeyEncryptor; import com.quorum.tessera.config.util.JaxbUtil; import com.quorum.tessera.key.generation.KeyGenerator; -import com.quorum.tessera.test.util.ElUtil; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.mockito.Mockito; import org.slf4j.Logger; @@ -22,20 +16,17 @@ import javax.validation.ConstraintViolationException; import java.io.ByteArrayInputStream; import java.io.InputStream; -import java.io.UncheckedIOException; -import java.nio.file.FileAlreadyExistsException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; import java.util.UUID; import static com.quorum.tessera.test.util.ElUtil.createAndPopulatePaths; import static org.assertj.core.api.Assertions.*; import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.when; public class DefaultCliAdapterTest { @@ -49,279 +40,281 @@ public void setUp() { this.cliAdapter = new DefaultCliAdapter(); } -// @Test -// public void getType() { -// assertThat(cliAdapter.getType()).isEqualTo(CliType.CONFIG); -// } + // @Test + // public void getType() { + // assertThat(cliAdapter.getType()).isEqualTo(CliType.CONFIG); + // } + // + // @Test + // public void help() throws Exception { + // + // final CliResult result = cliAdapter.execute("help"); + // + // assertThat(result).isNotNull(); + // assertThat(result.getConfig()).isNotPresent(); + // assertThat(result.getStatus()).isEqualTo(0); + // assertThat(result.isSuppressStartup()).isTrue(); + // } + // + // @Test + // public void helpViaCall() throws Exception { + // cliAdapter.setAllParameters(new String[] {"help"}); + // final CliResult result = cliAdapter.call(); + // + // assertThat(result).isNotNull(); + // assertThat(result.getConfig()).isNotPresent(); + // assertThat(result.getStatus()).isEqualTo(0); + // assertThat(result.isSuppressStartup()).isTrue(); + // } + // + // @Test + // public void noArgsPrintsHelp() throws Exception { + // + // final CliResult result = cliAdapter.execute(); + // + // assertThat(result).isNotNull(); + // assertThat(result.getConfig()).isNotPresent(); + // assertThat(result.getStatus()).isEqualTo(0); + // assertThat(result.isSuppressStartup()).isTrue(); + // } + // + // @Test + // public void withValidConfig() throws Exception { + // + // Path configFile = createAndPopulatePaths(getClass().getResource("/sample-config.json")); + // CliResult result = cliAdapter.execute("-configfile", configFile.toString()); + // + // assertThat(result).isNotNull(); + // assertThat(result.getConfig()).isPresent(); + // assertThat(result.getStatus()).isEqualTo(0); + // assertThat(result.isSuppressStartup()).isFalse(); + // } + // + // @Test(expected = CliException.class) + // public void processArgsMissing() throws Exception { + // cliAdapter.execute("-configfile"); + // } + // + // @Test + // public void withConstraintViolations() throws Exception { + // + // Path configFile = createAndPopulatePaths(getClass().getResource("/missing-config.json")); + // + // try { + // cliAdapter.execute("-configfile", configFile.toString()); + // failBecauseExceptionWasNotThrown(ConstraintViolationException.class); + // } catch (ConstraintViolationException ex) { + // assertThat(ex.getConstraintViolations()).isNotEmpty(); + // } + // } + // + // @Test + // public void keygenWithConfig() throws Exception { + // + // final KeyGenerator keyGenerator = MockKeyGeneratorFactory.getMockKeyGenerator(); + // + // Path publicKeyPath = Files.createTempFile(UUID.randomUUID().toString(), ""); + // Path privateKeyPath = Files.createTempFile(UUID.randomUUID().toString(), ""); + // + // Files.write(privateKeyPath, Arrays.asList("SOMEDATA")); + // Files.write(publicKeyPath, Arrays.asList("SOMEDATA")); + // + // KeyEncryptor keyEncryptor = mock(KeyEncryptor.class); + // KeyDataConfig keyDataConfig = mock(KeyDataConfig.class); + // when(keyDataConfig.getType()).thenReturn(PrivateKeyType.UNLOCKED); + // + // FilesystemKeyPair keypair = new FilesystemKeyPair(publicKeyPath, privateKeyPath, keyEncryptor); + // when(keyGenerator.generate(anyString(), eq(null), eq(null))).thenReturn(keypair); + // + // Path unixSocketPath = Files.createTempFile(UUID.randomUUID().toString(), ".ipc"); + // + // Map params = new HashMap<>(); + // params.put("unixSocketPath", unixSocketPath.toString()); + // + // Path configFilePath = ElUtil.createTempFileFromTemplate(getClass().getResource("/keygen-sample.json"), + // params); + // + // CliResult result = + // cliAdapter.execute( + // "-keygen", "-filename", UUID.randomUUID().toString(), "-configfile", + // configFilePath.toString()); + // + // assertThat(result).isNotNull(); + // assertThat(result.getStatus()).isEqualTo(0); + // assertThat(result.getConfig()).isNotNull(); + // assertThat(result.isSuppressStartup()).isFalse(); + // + // verify(keyGenerator).generate(anyString(), eq(null), eq(null)); + // verifyNoMoreInteractions(keyGenerator); + // } + // + // @Test + // public void keygenThenExit() throws Exception { + // + // final CliResult result = cliAdapter.execute("-keygen", "--encryptor.type", "NACL"); + // + // assertThat(result).isNotNull(); + // assertThat(result.isSuppressStartup()).isTrue(); + // } + // + // @Test + // public void fileNameWithoutKeygenArgThenExit() throws Exception { + // + // try { + // cliAdapter.execute("-filename"); + // failBecauseExceptionWasNotThrown(CliException.class); + // } catch (CliException ex) { + // assertThat(ex).hasMessage("Missing argument for option: filename"); + // } + // } + // + // @Test + // public void outputWithoutKeygenOrConfig() { + // + // final Throwable throwable = catchThrowable(() -> cliAdapter.execute("-output", "bogus")); + // assertThat(throwable) + // .isInstanceOf(CliException.class) + // .hasMessage("One or more: -configfile or -keygen or -updatepassword options are required."); + // } + // + // @Test + // public void output() throws Exception { + // + // final KeyGenerator keyGenerator = MockKeyGeneratorFactory.getMockKeyGenerator(); + // + // Path publicKeyPath = Files.createTempFile(UUID.randomUUID().toString(), ""); + // Path privateKeyPath = Files.createTempFile(UUID.randomUUID().toString(), ""); + // + // Files.write(privateKeyPath, Arrays.asList("SOMEDATA")); + // Files.write(publicKeyPath, Arrays.asList("SOMEDATA")); + // + // InlineKeypair inlineKeypair = mock(InlineKeypair.class); + // + // KeyDataConfig keyDataConfig = mock(KeyDataConfig.class); + // when(keyDataConfig.getType()).thenReturn(PrivateKeyType.UNLOCKED); + // when(inlineKeypair.getPrivateKeyConfig()).thenReturn(keyDataConfig); + // + // KeyEncryptor keyEncryptor = mock(KeyEncryptor.class); + // + // FilesystemKeyPair keypair = new FilesystemKeyPair(publicKeyPath, privateKeyPath, keyEncryptor); + // when(keyGenerator.generate(anyString(), eq(null), eq(null))).thenReturn(keypair); + // + // Path generatedKey = Files.createTempFile(UUID.randomUUID().toString(), ".tmp"); + // + // Files.deleteIfExists(generatedKey); + // assertThat(Files.exists(generatedKey)).isFalse(); + // + // Path keyConfigPath = Paths.get(getClass().getResource("/lockedprivatekey.json").toURI()); + // Path tempKeyFile = Files.createTempFile(UUID.randomUUID().toString(), ""); + // + // Path unixSocketPath = Files.createTempFile(UUID.randomUUID().toString(), ".ipc"); + // Map params = new HashMap<>(); + // params.put("unixSocketPath", unixSocketPath.toString()); + // + // Path configFile = createAndPopulatePaths(getClass().getResource("/keygen-sample.json")); + // + // CliResult result = + // cliAdapter.execute( + // "-keygen", keyConfigPath.toString(), + // "-filename", tempKeyFile.toAbsolutePath().toString(), + // "-output", generatedKey.toFile().getPath(), + // "-configfile", configFile.toString()); + // + // assertThat(result).isNotNull(); + // assertThat(Files.exists(generatedKey)).isTrue(); + // + // try { + // cliAdapter.execute( + // "-keygen", keyConfigPath.toString(), + // "-filename", UUID.randomUUID().toString(), + // "-output", generatedKey.toFile().getPath(), + // "-configfile", configFile.toString()); + // failBecauseExceptionWasNotThrown(Exception.class); + // } catch (Exception ex) { + // assertThat(ex).isInstanceOf(UncheckedIOException.class); + // assertThat(ex.getCause()).isExactlyInstanceOf(FileAlreadyExistsException.class); + // } + // } + // + // @Test + // public void dynOption() throws Exception { + // + // Path configFile = createAndPopulatePaths(getClass().getResource("/sample-config.json")); + // + // CliResult result = cliAdapter.execute("-configfile", configFile.toString(), "-jdbc.username", "somename"); + // + // assertThat(result).isNotNull(); + // assertThat(result.getConfig()).isPresent(); + // assertThat(result.getConfig().get().getJdbcConfig().getUsername()).isEqualTo("somename"); + // assertThat(result.getConfig().get().getJdbcConfig().getPassword()).isEqualTo("tiger"); + // } // -// @Test -// public void help() throws Exception { +// @Ignore +// public void withInvalidPath() throws Exception { +// // unixSocketPath +// Map params = new HashMap<>(); +// params.put("publicKeyPath", "BOGUS.bogus"); +// params.put("privateKeyPath", "BOGUS.bogus"); // -// final CliResult result = cliAdapter.execute("help"); +// Path configFile = +// ElUtil.createTempFileFromTemplate(getClass().getResource("/sample-config-invalidpath.json"), params); // -// assertThat(result).isNotNull(); -// assertThat(result.getConfig()).isNotPresent(); -// assertThat(result.getStatus()).isEqualTo(0); -// assertThat(result.isSuppressStartup()).isTrue(); +// try { +// cliAdapter.execute("-configfile", configFile.toString()); +// failBecauseExceptionWasNotThrown(ConstraintViolationException.class); +// } catch (ConstraintViolationException ex) { +// assertThat(ex.getConstraintViolations()) +// .hasSize(1) +// .extracting("messageTemplate") +// .containsExactly("{UnsupportedKeyPair.message}"); +// } // } // // @Test -// public void helpViaCall() throws Exception { -// cliAdapter.setAllParameters(new String[] {"help"}); -// final CliResult result = cliAdapter.call(); +// public void withEmptyConfigOverrideAll() throws Exception { // -// assertThat(result).isNotNull(); -// assertThat(result.getConfig()).isNotPresent(); -// assertThat(result.getStatus()).isEqualTo(0); -// assertThat(result.isSuppressStartup()).isTrue(); -// } +// Path unixSocketFile = Files.createTempFile("unixSocketFile", ".ipc"); +// unixSocketFile.toFile().deleteOnExit(); // -// @Test -// public void noArgsPrintsHelp() throws Exception { +// Path configFile = Files.createTempFile("withEmptyConfigOverrideAll", ".json"); +// configFile.toFile().deleteOnExit(); +// Files.write(configFile, "{}".getBytes()); +// try { +// CliResult result = +// cliAdapter.execute( +// "-configfile", +// configFile.toString(), +// "--unixSocketFile", +// unixSocketFile.toString(), +// "--encryptor.type", +// "NACL"); // -// final CliResult result = cliAdapter.execute(); -// -// assertThat(result).isNotNull(); -// assertThat(result.getConfig()).isNotPresent(); -// assertThat(result.getStatus()).isEqualTo(0); -// assertThat(result.isSuppressStartup()).isTrue(); +// assertThat(result).isNotNull(); +// failBecauseExceptionWasNotThrown(ConstraintViolationException.class); +// } catch (ConstraintViolationException ex) { +// ex.getConstraintViolations().forEach(v -> LOGGER.info("{}", v)); +// } // } // // @Test -// public void withValidConfig() throws Exception { +// public void overrideAlwaysSendTo() throws Exception { // -// Path configFile = createAndPopulatePaths(getClass().getResource("/sample-config.json")); -// CliResult result = cliAdapter.execute("-configfile", configFile.toString()); +// String alwaysSendToKey = "giizjhZQM6peq52O7icVFxdTmTYinQSUsvyhXzgZqkE="; // +// Path configFile = createAndPopulatePaths(getClass().getResource("/sample-config.json")); +// CliResult result = null; +// try { +// result = cliAdapter.execute("-configfile", configFile.toString(), "-alwaysSendTo", alwaysSendToKey); +// } catch (Exception ex) { +// fail(ex.getMessage()); +// } // assertThat(result).isNotNull(); // assertThat(result.getConfig()).isPresent(); -// assertThat(result.getStatus()).isEqualTo(0); -// assertThat(result.isSuppressStartup()).isFalse(); +// assertThat(result.getConfig().get().getAlwaysSendTo()).hasSize(2); +// assertThat(result.getConfig().get().getAlwaysSendTo()) +// .containsExactly("/+UuD63zItL1EbjxkKUljMgG8Z1w0AJ8pNOR4iq2yQc=", alwaysSendToKey); // } - @Test(expected = CliException.class) - public void processArgsMissing() throws Exception { - cliAdapter.execute("-configfile"); - } - - @Test - public void withConstraintViolations() throws Exception { - - Path configFile = createAndPopulatePaths(getClass().getResource("/missing-config.json")); - - try { - cliAdapter.execute("-configfile", configFile.toString()); - failBecauseExceptionWasNotThrown(ConstraintViolationException.class); - } catch (ConstraintViolationException ex) { - assertThat(ex.getConstraintViolations()).isNotEmpty(); - } - } - - @Test - public void keygenWithConfig() throws Exception { - - final KeyGenerator keyGenerator = MockKeyGeneratorFactory.getMockKeyGenerator(); - - Path publicKeyPath = Files.createTempFile(UUID.randomUUID().toString(), ""); - Path privateKeyPath = Files.createTempFile(UUID.randomUUID().toString(), ""); - - Files.write(privateKeyPath, Arrays.asList("SOMEDATA")); - Files.write(publicKeyPath, Arrays.asList("SOMEDATA")); - - KeyEncryptor keyEncryptor = mock(KeyEncryptor.class); - KeyDataConfig keyDataConfig = mock(KeyDataConfig.class); - when(keyDataConfig.getType()).thenReturn(PrivateKeyType.UNLOCKED); - - FilesystemKeyPair keypair = new FilesystemKeyPair(publicKeyPath, privateKeyPath, keyEncryptor); - when(keyGenerator.generate(anyString(), eq(null), eq(null))).thenReturn(keypair); - - Path unixSocketPath = Files.createTempFile(UUID.randomUUID().toString(), ".ipc"); - - Map params = new HashMap<>(); - params.put("unixSocketPath", unixSocketPath.toString()); - - Path configFilePath = ElUtil.createTempFileFromTemplate(getClass().getResource("/keygen-sample.json"), params); - - CliResult result = - cliAdapter.execute( - "-keygen", "-filename", UUID.randomUUID().toString(), "-configfile", configFilePath.toString()); - - assertThat(result).isNotNull(); - assertThat(result.getStatus()).isEqualTo(0); - assertThat(result.getConfig()).isNotNull(); - assertThat(result.isSuppressStartup()).isFalse(); - - verify(keyGenerator).generate(anyString(), eq(null), eq(null)); - verifyNoMoreInteractions(keyGenerator); - } - - @Test - public void keygenThenExit() throws Exception { - - final CliResult result = cliAdapter.execute("-keygen", "--encryptor.type", "NACL"); - - assertThat(result).isNotNull(); - assertThat(result.isSuppressStartup()).isTrue(); - } - - @Test - public void fileNameWithoutKeygenArgThenExit() throws Exception { - - try { - cliAdapter.execute("-filename"); - failBecauseExceptionWasNotThrown(CliException.class); - } catch (CliException ex) { - assertThat(ex).hasMessage("Missing argument for option: filename"); - } - } - - @Test - public void outputWithoutKeygenOrConfig() { - - final Throwable throwable = catchThrowable(() -> cliAdapter.execute("-output", "bogus")); - assertThat(throwable) - .isInstanceOf(CliException.class) - .hasMessage("One or more: -configfile or -keygen or -updatepassword options are required."); - } - - @Test - public void output() throws Exception { - - final KeyGenerator keyGenerator = MockKeyGeneratorFactory.getMockKeyGenerator(); - - Path publicKeyPath = Files.createTempFile(UUID.randomUUID().toString(), ""); - Path privateKeyPath = Files.createTempFile(UUID.randomUUID().toString(), ""); - - Files.write(privateKeyPath, Arrays.asList("SOMEDATA")); - Files.write(publicKeyPath, Arrays.asList("SOMEDATA")); - - InlineKeypair inlineKeypair = mock(InlineKeypair.class); - - KeyDataConfig keyDataConfig = mock(KeyDataConfig.class); - when(keyDataConfig.getType()).thenReturn(PrivateKeyType.UNLOCKED); - when(inlineKeypair.getPrivateKeyConfig()).thenReturn(keyDataConfig); - - KeyEncryptor keyEncryptor = mock(KeyEncryptor.class); - - FilesystemKeyPair keypair = new FilesystemKeyPair(publicKeyPath, privateKeyPath, keyEncryptor); - when(keyGenerator.generate(anyString(), eq(null), eq(null))).thenReturn(keypair); - - Path generatedKey = Files.createTempFile(UUID.randomUUID().toString(), ".tmp"); - - Files.deleteIfExists(generatedKey); - assertThat(Files.exists(generatedKey)).isFalse(); - - Path keyConfigPath = Paths.get(getClass().getResource("/lockedprivatekey.json").toURI()); - Path tempKeyFile = Files.createTempFile(UUID.randomUUID().toString(), ""); - - Path unixSocketPath = Files.createTempFile(UUID.randomUUID().toString(), ".ipc"); - Map params = new HashMap<>(); - params.put("unixSocketPath", unixSocketPath.toString()); - - Path configFile = createAndPopulatePaths(getClass().getResource("/keygen-sample.json")); - - CliResult result = - cliAdapter.execute( - "-keygen", keyConfigPath.toString(), - "-filename", tempKeyFile.toAbsolutePath().toString(), - "-output", generatedKey.toFile().getPath(), - "-configfile", configFile.toString()); - - assertThat(result).isNotNull(); - assertThat(Files.exists(generatedKey)).isTrue(); - - try { - cliAdapter.execute( - "-keygen", keyConfigPath.toString(), - "-filename", UUID.randomUUID().toString(), - "-output", generatedKey.toFile().getPath(), - "-configfile", configFile.toString()); - failBecauseExceptionWasNotThrown(Exception.class); - } catch (Exception ex) { - assertThat(ex).isInstanceOf(UncheckedIOException.class); - assertThat(ex.getCause()).isExactlyInstanceOf(FileAlreadyExistsException.class); - } - } - - @Test - public void dynOption() throws Exception { - - Path configFile = createAndPopulatePaths(getClass().getResource("/sample-config.json")); - - CliResult result = cliAdapter.execute("-configfile", configFile.toString(), "-jdbc.username", "somename"); - - assertThat(result).isNotNull(); - assertThat(result.getConfig()).isPresent(); - assertThat(result.getConfig().get().getJdbcConfig().getUsername()).isEqualTo("somename"); - assertThat(result.getConfig().get().getJdbcConfig().getPassword()).isEqualTo("tiger"); - } - - @Ignore - public void withInvalidPath() throws Exception { - // unixSocketPath - Map params = new HashMap<>(); - params.put("publicKeyPath", "BOGUS.bogus"); - params.put("privateKeyPath", "BOGUS.bogus"); - - Path configFile = - ElUtil.createTempFileFromTemplate(getClass().getResource("/sample-config-invalidpath.json"), params); - - try { - cliAdapter.execute("-configfile", configFile.toString()); - failBecauseExceptionWasNotThrown(ConstraintViolationException.class); - } catch (ConstraintViolationException ex) { - assertThat(ex.getConstraintViolations()) - .hasSize(1) - .extracting("messageTemplate") - .containsExactly("{UnsupportedKeyPair.message}"); - } - } - - @Test - public void withEmptyConfigOverrideAll() throws Exception { - - Path unixSocketFile = Files.createTempFile("unixSocketFile", ".ipc"); - unixSocketFile.toFile().deleteOnExit(); - - Path configFile = Files.createTempFile("withEmptyConfigOverrideAll", ".json"); - configFile.toFile().deleteOnExit(); - Files.write(configFile, "{}".getBytes()); - try { - CliResult result = - cliAdapter.execute( - "-configfile", - configFile.toString(), - "--unixSocketFile", - unixSocketFile.toString(), - "--encryptor.type", - "NACL"); - - assertThat(result).isNotNull(); - failBecauseExceptionWasNotThrown(ConstraintViolationException.class); - } catch (ConstraintViolationException ex) { - ex.getConstraintViolations().forEach(v -> LOGGER.info("{}", v)); - } - } - - @Test - public void overrideAlwaysSendTo() throws Exception { - - String alwaysSendToKey = "giizjhZQM6peq52O7icVFxdTmTYinQSUsvyhXzgZqkE="; - - Path configFile = createAndPopulatePaths(getClass().getResource("/sample-config.json")); - CliResult result = null; - try { - result = cliAdapter.execute("-configfile", configFile.toString(), "-alwaysSendTo", alwaysSendToKey); - } catch (Exception ex) { - fail(ex.getMessage()); - } - assertThat(result).isNotNull(); - assertThat(result.getConfig()).isPresent(); - assertThat(result.getConfig().get().getAlwaysSendTo()).hasSize(2); - assertThat(result.getConfig().get().getAlwaysSendTo()) - .containsExactly("/+UuD63zItL1EbjxkKUljMgG8Z1w0AJ8pNOR4iq2yQc=", alwaysSendToKey); - } - @Test public void overridePeers() throws Exception { diff --git a/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/KeyGenCommand.java b/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/KeyGenCommand.java index 895ce05f89..a4c0452ce6 100644 --- a/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/KeyGenCommand.java +++ b/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/KeyGenCommand.java @@ -20,84 +20,83 @@ import java.util.concurrent.Callable; @CommandLine.Command( - name = "keygen", - aliases = {"-keygen"}, - headerHeading = "Usage:%n%n", - synopsisHeading = "%n", - descriptionHeading = "%nDescription:%n%n", - parameterListHeading = "%nParameters:%n", - optionListHeading = "%nOptions:%n", - header = "Generate Tessera encryption keys", - abbreviateSynopsis = true, - subcommands = {CommandLine.HelpCommand.class} -) + name = "keygen", + aliases = {"-keygen"}, + headerHeading = "Usage:%n%n", + synopsisHeading = "%n", + descriptionHeading = "%nDescription:%n%n", + parameterListHeading = "%nParameters:%n", + optionListHeading = "%nOptions:%n", + header = "Generate Tessera encryption keys", + abbreviateSynopsis = true, + subcommands = {CommandLine.HelpCommand.class}) public class KeyGenCommand implements Callable { private final KeyGeneratorFactory factory = KeyGeneratorFactory.newFactory(); private final Validator validator = - Validation.byDefaultProvider().configure().ignoreXmlConfiguration().buildValidatorFactory().getValidator(); + Validation.byDefaultProvider().configure().ignoreXmlConfiguration().buildValidatorFactory().getValidator(); // TODO(cjh) raise CLI usage wording changes as separate change @CommandLine.Option( - names = {"--output", "-filename"}, - description = "Comma-separated list of paths to save generated key files. Can also be used with keyvault. Number of args determines number of key-pairs generated." - ) - public List output; + names = {"--keyout", "-filename"}, + defaultValue = ".", + description = + "Comma-separated list of paths to save generated key files. Can also be used with keyvault. Number of args determines number of key-pairs generated (default = ${DEFAULT-VALUE})") + public List keyOut; // TODO(cjh) review description and name @CommandLine.Option( - names = {"--encryptionconfig", "-keygenconfig"}, - description = "File containing Argon2 encryption config used to secure the new private key" - ) + names = {"--encryptionconfig", "-keygenconfig"}, + description = "File containing Argon2 encryption config used to secure the new private key") public ArgonOptions encryptionConfig; @CommandLine.Option( - names = {"--vault.type", "-keygenvaulttype"}, - description = "Specify the key vault provider the generated key is to be saved in. If not set, the key will be encrypted and stored on the local filesystem. Valid values: ${COMPLETION-CANDIDATES})" - ) - //TODO(cjh) get possible enum values to show in the usage + names = {"--vault.type", "-keygenvaulttype"}, + description = + "Specify the key vault provider the generated key is to be saved in. If not set, the key will be encrypted and stored on the local filesystem. Valid values: ${COMPLETION-CANDIDATES})") + // TODO(cjh) get possible enum values to show in the usage public KeyVaultType vaultType; @CommandLine.Option( - names = {"--vault.url", "-keygenvaulturl"}, - description = "Base url for key vault" - ) + names = {"--vault.url", "-keygenvaulturl"}, + description = "Base url for key vault") public String vaultUrl; @CommandLine.Option( - names = {"--vault.hashicorp.approlepath", "-keygenvaultapprole"}, - description = "AppRole path for Hashicorp Vault authentication (defaults to 'approle')" - ) + names = {"--vault.hashicorp.approlepath", "-keygenvaultapprole"}, + description = "AppRole path for Hashicorp Vault authentication (defaults to 'approle')") public String hashicorpApprolePath; @CommandLine.Option( - names = {"--vault.hashicorp.secretenginepath", "-keygenvaultsecretengine"}, - description = "Name of already enabled Hashicorp v2 kv secret engine" - ) + names = {"--vault.hashicorp.secretenginepath", "-keygenvaultsecretengine"}, + description = "Name of already enabled Hashicorp v2 kv secret engine") public String hashicorpSecretEnginePath; @CommandLine.Option( - names = {"--vault.hashicorp.tlskeystore", "-keygenvaultkeystore"}, - description = "Path to JKS keystore for TLS Hashicorp Vault communication" - ) + names = {"--vault.hashicorp.tlskeystore", "-keygenvaultkeystore"}, + description = "Path to JKS keystore for TLS Hashicorp Vault communication") public Path hashicorpTlsKeystore; @CommandLine.Option( - names = {"--vault.hashicorp.tlstruststore", "-keygenvaulttruststore"}, - description = "Path to JKS truststore for TLS Hashicorp Vault communication" - ) + names = {"--vault.hashicorp.tlstruststore", "-keygenvaulttruststore"}, + description = "Path to JKS truststore for TLS Hashicorp Vault communication") public Path hashicorpTlsTruststore; - //TODO(cjh) do something about the duplication of the configfile option in each relevant command + // TODO(cjh) do something about the duplication of the configfile option in each relevant command @CommandLine.Option( - names = {"--configfile", "-configfile"}, - description = "Path to node configuration file" - ) + names = {"--configfile", "-configfile"}, + description = "Path to node configuration file") public Config config; - @CommandLine.Mixin - public EncryptorOptions encryptorOptions; + // TODO(cjh) implement config output + @CommandLine.Option( + names = {"--configout", "-output"}, + description = + "Path to save updated configfile to. Updated config will be printed to terminal if not provided. Only valid if --configfile option also provided.") + public List configOut; + + @CommandLine.Mixin public EncryptorOptions encryptorOptions; @Override public CliResult call() throws Exception { @@ -114,9 +113,7 @@ public CliResult call() throws Exception { final KeyGenerator generator = factory.create(keyVaultConfig, encryptorConfig); - output.forEach( - name -> generator.generate(name, encryptionConfig, keyVaultOptions) - ); + keyOut.forEach(name -> generator.generate(name, encryptionConfig, keyVaultOptions)); return new CliResult(0, true, null); } @@ -140,23 +137,23 @@ private Optional keyVaultConfig() { keyVaultConfig = new AzureKeyVaultConfig(vaultUrl); Set> violations = - validator.validate((AzureKeyVaultConfig) keyVaultConfig); + validator.validate((AzureKeyVaultConfig) keyVaultConfig); if (!violations.isEmpty()) { throw new ConstraintViolationException(violations); } } else { - if (output.size() == 0) { + if (keyOut.size() == 0) { throw new CliException( - "At least one -filename must be provided when saving generated keys in a Hashicorp Vault"); + "At least one -filename must be provided when saving generated keys in a Hashicorp Vault"); } keyVaultConfig = - new HashicorpKeyVaultConfig( - vaultUrl, hashicorpApprolePath, hashicorpTlsKeystore, hashicorpTlsTruststore); + new HashicorpKeyVaultConfig( + vaultUrl, hashicorpApprolePath, hashicorpTlsKeystore, hashicorpTlsTruststore); Set> violations = - validator.validate((HashicorpKeyVaultConfig) keyVaultConfig); + validator.validate((HashicorpKeyVaultConfig) keyVaultConfig); if (!violations.isEmpty()) { throw new ConstraintViolationException(violations); diff --git a/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/NoTesseraConfigfileOptionException.java b/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/NoTesseraConfigfileOptionException.java new file mode 100644 index 0000000000..1d7079639f --- /dev/null +++ b/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/NoTesseraConfigfileOptionException.java @@ -0,0 +1,5 @@ +package com.quorum.tessera.picocli; + +public class NoTesseraConfigfileOptionException extends RuntimeException { + +} diff --git a/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/PicoCliDelegate.java b/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/PicoCliDelegate.java index 46c8c60377..00e27cd7c5 100644 --- a/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/PicoCliDelegate.java +++ b/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/PicoCliDelegate.java @@ -2,6 +2,7 @@ import com.quorum.tessera.ServiceLoaderUtil; import com.quorum.tessera.cli.CLIExceptionCapturer; +import com.quorum.tessera.cli.CliException; import com.quorum.tessera.cli.CliResult; import com.quorum.tessera.cli.keypassresolver.CliKeyPasswordResolver; import com.quorum.tessera.cli.keypassresolver.KeyPasswordResolver; @@ -32,11 +33,12 @@ import static java.nio.file.StandardOpenOption.CREATE; import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING; +// TODO(cjh) make sure recent changes to old CLI are included where needed public class PicoCliDelegate { private static final Logger LOGGER = LoggerFactory.getLogger(PicoCliDelegate.class); private final Validator validator = - Validation.byDefaultProvider().configure().ignoreXmlConfiguration().buildValidatorFactory().getValidator(); + Validation.byDefaultProvider().configure().ignoreXmlConfiguration().buildValidatorFactory().getValidator(); private final KeyPasswordResolver keyPasswordResolver; @@ -88,13 +90,14 @@ public CliResult execute(String... args) throws Exception { try { parseResult = commandLine.parseArgs(args); } catch (CommandLine.ParameterException ex) { -// TODO(cjh) this is ripped from commandLine.execute(...) - check whether it is sufficient, or if it can be replaced by using the mapper + // TODO(cjh) this is ripped from commandLine.execute(...) - check whether it is sufficient, or + // if it can be replaced by using the mapper // exception mapper can't be used here as we haven't called commandLine.execute() try { - int exitCode = commandLine.getParameterExceptionHandler().handleParseException(ex, args); - return new CliResult(exitCode, true, null); + commandLine.getParameterExceptionHandler().handleParseException(ex, args); + throw new CliException(ex.getMessage()); } catch (Exception e) { - throw e; + throw new CliException(ex.getMessage()); } } @@ -110,6 +113,8 @@ public CliResult execute(String... args) throws Exception { } catch (NoTesseraCmdArgsException e) { commandLine.execute("help"); return new CliResult(0, true, null); + } catch (NoTesseraConfigfileOptionException e) { + throw new CliException("Missing required option '--configfile '"); } return new CliResult(0, false, config); @@ -136,9 +141,9 @@ public CliResult execute(String... args) throws Exception { if (mapper.getThrown() != null) { throw mapper.getThrown(); } - } - return new CliResult(1, true, null); + return new CliResult(0, true, null); + } } private Config getConfigFromCLI(CommandLine.ParseResult parseResult) throws Exception { @@ -154,35 +159,35 @@ private Config getConfigFromCLI(CommandLine.ParseResult parseResult) throws Exce if (parseResult.hasMatchedOption("configfile")) { config = parseResult.matchedOption("configfile").getValue(); } else { - config = new Config(); + throw new NoTesseraConfigfileOptionException(); } // apply CLI overrides parsedArgs.forEach( - parsedArg -> { - // positional (i.e. unnamed) CLI flags are ignored - if (!parsedArg.isOption()) { - return; - } - - OptionSpec parsedOption = (OptionSpec) parsedArg; - - // configfile CLI option is ignored as it was already parsed - // pidfile CLI option is ignored as it is parsed later - // TODO(cjh) improve, checks all names for all provided options - for (String name : parsedOption.names()) { - if ("--configfile".equals(name) || "--pidfile".equals(name)) { + parsedArg -> { + // positional (i.e. unnamed) CLI flags are ignored + if (!parsedArg.isOption()) { return; } - } - String optionName = parsedOption.longestName().replaceFirst("^--", ""); - String[] values = parsedOption.stringValues().toArray(new String[0]); + OptionSpec parsedOption = (OptionSpec) parsedArg; + + // configfile CLI option is ignored as it was already parsed + // pidfile CLI option is ignored as it is parsed later + // TODO(cjh) improve, checks all names for all provided options + for (String name : parsedOption.names()) { + if ("--configfile".equals(name) || "--pidfile".equals(name)) { + return; + } + } + + String optionName = parsedOption.longestName().replaceFirst("^--", ""); + String[] values = parsedOption.stringValues().toArray(new String[0]); - LOGGER.debug("Setting : {} with value(s) {}", optionName, values); - OverrideUtil.setValue(config, optionName, values); - LOGGER.debug("Set : {} with value(s) {}", optionName, values); - }); + LOGGER.debug("Setting : {} with value(s) {}", optionName, values); + OverrideUtil.setValue(config, optionName, values); + LOGGER.debug("Set : {} with value(s) {}", optionName, values); + }); keyPasswordResolver.resolveKeyPasswords(config); @@ -215,5 +220,4 @@ private void createPidFile(Path pidFilePath) throws Exception { stream.write(pid.getBytes(UTF_8)); } } - } diff --git a/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/TesseraCommand.java b/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/TesseraCommand.java index 94646cc682..3b02bbd793 100644 --- a/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/TesseraCommand.java +++ b/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/TesseraCommand.java @@ -6,28 +6,24 @@ import java.nio.file.Path; @CommandLine.Command( - name = "tessera", - headerHeading = "Usage:%n%n", - header = "Tessera private transaction manager for Quorum", - synopsisHeading = "%n", - descriptionHeading = "%nDescription:%n%n", - description = "Start a Tessera node. Other commands exist to manage Tessera encryption keys", - parameterListHeading = "%nParameters:%n", - optionListHeading = "%nOptions:%n", - abbreviateSynopsis = true -) + name = "tessera", + headerHeading = "Usage:%n%n", + header = "Tessera private transaction manager for Quorum", + synopsisHeading = "%n", + descriptionHeading = "%nDescription:%n%n", + description = "Start a Tessera node. Other commands exist to manage Tessera encryption keys", + parameterListHeading = "%nParameters:%n", + optionListHeading = "%nOptions:%n", + abbreviateSynopsis = true) public class TesseraCommand { @CommandLine.Option( - names = {"--configfile", "-configfile"}, - description = "Path to node configuration file" - ) + names = {"--configfile", "-configfile"}, + description = "Path to node configuration file") public Config config; @CommandLine.Option( - names = {"--pidfile", "-pidfile"}, - description = "the path to write the PID to" - ) + names = {"--pidfile", "-pidfile"}, + description = "the path to write the PID to") public Path pidFilePath; - } diff --git a/cli/pico-cli/src/test/java/com/quorum/tessera/picocli/PicoCliDelegateTest.java b/cli/pico-cli/src/test/java/com/quorum/tessera/picocli/PicoCliDelegateTest.java index 2f7ba6efe4..fb0ccde0ab 100644 --- a/cli/pico-cli/src/test/java/com/quorum/tessera/picocli/PicoCliDelegateTest.java +++ b/cli/pico-cli/src/test/java/com/quorum/tessera/picocli/PicoCliDelegateTest.java @@ -2,16 +2,40 @@ import com.quorum.tessera.cli.CliException; import com.quorum.tessera.cli.CliResult; +import com.quorum.tessera.config.KeyDataConfig; +import com.quorum.tessera.config.Peer; +import com.quorum.tessera.config.PrivateKeyType; +import com.quorum.tessera.config.keypairs.FilesystemKeyPair; +import com.quorum.tessera.config.keypairs.InlineKeypair; +import com.quorum.tessera.config.keys.KeyEncryptor; +import com.quorum.tessera.key.generation.KeyGenerator; +import com.quorum.tessera.picocli.keys.MockKeyGeneratorFactory; +import com.quorum.tessera.test.util.ElUtil; import org.junit.Before; import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import javax.validation.ConstraintViolationException; +import java.io.UncheckedIOException; +import java.nio.file.FileAlreadyExistsException; +import java.nio.file.Files; import java.nio.file.Path; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; import static com.quorum.tessera.test.util.ElUtil.createAndPopulatePaths; -import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.*; public class PicoCliDelegateTest { + private static final Logger LOGGER = LoggerFactory.getLogger(PicoCliDelegateTest.class); + private PicoCliDelegate cliDelegate; @Before @@ -58,4 +82,235 @@ public void processArgsMissing() throws Exception { cliDelegate.execute("-configfile"); } + @Test + public void withConstraintViolations() throws Exception { + Path configFile = createAndPopulatePaths(getClass().getResource("/missing-config.json")); + + try { + cliDelegate.execute("-configfile", configFile.toString()); + failBecauseExceptionWasNotThrown(ConstraintViolationException.class); + } catch (ConstraintViolationException ex) { + assertThat(ex.getConstraintViolations()).isNotEmpty(); + } + } + + @Test + public void keygenWithConfig() throws Exception { + + final KeyGenerator keyGenerator = MockKeyGeneratorFactory.getMockKeyGenerator(); + + Path publicKeyPath = Files.createTempFile(UUID.randomUUID().toString(), ""); + Path privateKeyPath = Files.createTempFile(UUID.randomUUID().toString(), ""); + + Files.write(privateKeyPath, Arrays.asList("SOMEDATA")); + Files.write(publicKeyPath, Arrays.asList("SOMEDATA")); + + KeyEncryptor keyEncryptor = mock(KeyEncryptor.class); + KeyDataConfig keyDataConfig = mock(KeyDataConfig.class); + when(keyDataConfig.getType()).thenReturn(PrivateKeyType.UNLOCKED); + + FilesystemKeyPair keypair = new FilesystemKeyPair(publicKeyPath, privateKeyPath, keyEncryptor); + when(keyGenerator.generate(anyString(), eq(null), eq(null))).thenReturn(keypair); + + Path unixSocketPath = Files.createTempFile(UUID.randomUUID().toString(), ".ipc"); + + Map params = new HashMap<>(); + params.put("unixSocketPath", unixSocketPath.toString()); + + Path configFilePath = ElUtil.createTempFileFromTemplate(getClass().getResource("/sample-config.json"), params); + + CliResult result = + cliDelegate.execute( + "-keygen", "-filename", UUID.randomUUID().toString(), "-configfile", configFilePath.toString()); + + assertThat(result).isNotNull(); + assertThat(result.getStatus()).isEqualTo(0); + assertThat(result.getConfig()).isNotNull(); + assertThat(result.isSuppressStartup()).isTrue(); + + verify(keyGenerator).generate(anyString(), eq(null), eq(null)); + verifyNoMoreInteractions(keyGenerator); + } + + @Test + public void keygenThenExit() throws Exception { + + final CliResult result = cliDelegate.execute("-keygen", "--encryptor.type", "NACL"); + + assertThat(result).isNotNull(); + assertThat(result.isSuppressStartup()).isTrue(); + } + + @Test + public void noConfigfileOption() { + + final Throwable throwable = catchThrowable(() -> cliDelegate.execute("--pidfile", "bogus")); + assertThat(throwable) + .isInstanceOf(CliException.class) + .hasMessage("Missing required option '--configfile '"); + } + + @Test + public void output() throws Exception { + + final KeyGenerator keyGenerator = MockKeyGeneratorFactory.getMockKeyGenerator(); + + Path publicKeyPath = Files.createTempFile(UUID.randomUUID().toString(), ""); + Path privateKeyPath = Files.createTempFile(UUID.randomUUID().toString(), ""); + + Files.write(privateKeyPath, Arrays.asList("SOMEDATA")); + Files.write(publicKeyPath, Arrays.asList("SOMEDATA")); + + InlineKeypair inlineKeypair = mock(InlineKeypair.class); + + KeyDataConfig keyDataConfig = mock(KeyDataConfig.class); + when(keyDataConfig.getType()).thenReturn(PrivateKeyType.UNLOCKED); + when(inlineKeypair.getPrivateKeyConfig()).thenReturn(keyDataConfig); + + KeyEncryptor keyEncryptor = mock(KeyEncryptor.class); + + FilesystemKeyPair keypair = new FilesystemKeyPair(publicKeyPath, privateKeyPath, keyEncryptor); + when(keyGenerator.generate(anyString(), eq(null), eq(null))).thenReturn(keypair); + + Path generatedKey = Files.createTempFile(UUID.randomUUID().toString(), ".tmp"); + + Files.deleteIfExists(generatedKey); + assertThat(Files.exists(generatedKey)).isFalse(); + + Path tempKeyFile = Files.createTempFile(UUID.randomUUID().toString(), ""); + + Path unixSocketPath = Files.createTempFile(UUID.randomUUID().toString(), ".ipc"); + Map params = new HashMap<>(); + params.put("unixSocketPath", unixSocketPath.toString()); + + Path configFile = createAndPopulatePaths(getClass().getResource("/keygen-sample.json")); + + CliResult result = + cliDelegate.execute( + "-keygen", + "-filename", + tempKeyFile.toAbsolutePath().toString(), + "-output", + generatedKey.toFile().getPath(), + "-configfile", + configFile.toString()); + + assertThat(result).isNotNull(); + assertThat(Files.exists(generatedKey)).isTrue(); + + try { + cliDelegate.execute( + "-keygen", + "-filename", + UUID.randomUUID().toString(), + "-output", + generatedKey.toFile().getPath(), + "-configfile", + configFile.toString()); + failBecauseExceptionWasNotThrown(Exception.class); + } catch (Exception ex) { + assertThat(ex).isInstanceOf(UncheckedIOException.class); + assertThat(ex.getCause()).isExactlyInstanceOf(FileAlreadyExistsException.class); + } + } + + @Test + public void dynOption() throws Exception { + + Path configFile = createAndPopulatePaths(getClass().getResource("/sample-config.json")); + + CliResult result = cliDelegate.execute("-configfile", configFile.toString(), "--jdbc.username", "somename"); + + assertThat(result).isNotNull(); + assertThat(result.getConfig()).isPresent(); + assertThat(result.getConfig().get().getJdbcConfig().getUsername()).isEqualTo("somename"); + assertThat(result.getConfig().get().getJdbcConfig().getPassword()).isEqualTo("tiger"); + } + + @Test + public void withInvalidPath() throws Exception { + // unixSocketPath + Map params = new HashMap<>(); + params.put("publicKeyPath", "BOGUS.bogus"); + params.put("privateKeyPath", "BOGUS.bogus"); + + Path configFile = + ElUtil.createTempFileFromTemplate(getClass().getResource("/sample-config-invalidpath.json"), params); + + try { + cliDelegate.execute("-configfile", configFile.toString()); + failBecauseExceptionWasNotThrown(ConstraintViolationException.class); + } catch (ConstraintViolationException ex) { + assertThat(ex.getConstraintViolations()) + .hasSize(2) + .extracting("messageTemplate") + .containsExactly("File does not exist", "File does not exist"); + } + } + + @Test + public void withEmptyConfigOverrideAll() throws Exception { + + Path unixSocketFile = Files.createTempFile("unixSocketFile", ".ipc"); + unixSocketFile.toFile().deleteOnExit(); + + Path configFile = Files.createTempFile("withEmptyConfigOverrideAll", ".json"); + configFile.toFile().deleteOnExit(); + Files.write(configFile, "{}".getBytes()); + try { + CliResult result = + cliDelegate.execute( + "-configfile", + configFile.toString(), + "--unixSocketFile", + unixSocketFile.toString(), + "--encryptor.type", + "NACL"); + + assertThat(result).isNotNull(); + failBecauseExceptionWasNotThrown(ConstraintViolationException.class); + } catch (ConstraintViolationException ex) { + ex.getConstraintViolations().forEach(v -> LOGGER.info("{}", v)); + } + } + + @Test + public void overrideAlwaysSendTo() throws Exception { + + String alwaysSendToKey = "giizjhZQM6peq52O7icVFxdTmTYinQSUsvyhXzgZqkE="; + + Path configFile = createAndPopulatePaths(getClass().getResource("/sample-config.json")); + CliResult result = null; + try { + result = cliDelegate.execute("-configfile", configFile.toString(), "--alwaysSendTo", alwaysSendToKey); + } catch (Exception ex) { + fail(ex.getMessage()); + } + assertThat(result).isNotNull(); + assertThat(result.getConfig()).isPresent(); + assertThat(result.getConfig().get().getAlwaysSendTo()).hasSize(2); + assertThat(result.getConfig().get().getAlwaysSendTo()) + .containsExactly("/+UuD63zItL1EbjxkKUljMgG8Z1w0AJ8pNOR4iq2yQc=", alwaysSendToKey); + } + + @Test + public void overridePeers() throws Exception { + + Path configFile = createAndPopulatePaths(getClass().getResource("/sample-config.json")); + + CliResult result = + cliDelegate.execute( + "-configfile", + configFile.toString(), + "--peer.url", + "anotherpeer", + "--peer.url", + "yetanotherpeer"); + + assertThat(result).isNotNull(); + assertThat(result.getConfig()).isPresent(); + assertThat(result.getConfig().get().getPeers()).hasSize(4); + assertThat(result.getConfig().get().getPeers().stream().map(Peer::getUrl)) + .containsExactlyInAnyOrder("anotherpeer", "yetanotherpeer", "http://bogus1.com", "http://bogus2.com"); + } } diff --git a/cli/pico-cli/src/test/java/com/quorum/tessera/picocli/keys/MockKeyGeneratorFactory.java b/cli/pico-cli/src/test/java/com/quorum/tessera/picocli/keys/MockKeyGeneratorFactory.java new file mode 100644 index 0000000000..79b4747cf5 --- /dev/null +++ b/cli/pico-cli/src/test/java/com/quorum/tessera/picocli/keys/MockKeyGeneratorFactory.java @@ -0,0 +1,30 @@ +package com.quorum.tessera.picocli.keys; + +import com.quorum.tessera.config.EncryptorConfig; +import com.quorum.tessera.config.KeyVaultConfig; +import com.quorum.tessera.key.generation.KeyGenerator; +import com.quorum.tessera.key.generation.KeyGeneratorFactory; + +import static org.mockito.Mockito.mock; + +public class MockKeyGeneratorFactory implements KeyGeneratorFactory { + + public enum KeyGeneratorHolder { + INSTANCE; + + KeyGenerator keyGenerator = mock(KeyGenerator.class); + } + + @Override + public KeyGenerator create(KeyVaultConfig keyVaultConfig, EncryptorConfig encryptorConfig) { + return getMockKeyGenerator(); + } + + public static KeyGenerator getMockKeyGenerator() { + return KeyGeneratorHolder.INSTANCE.keyGenerator; + } + + public static void reset() { + KeyGeneratorHolder.INSTANCE.keyGenerator = mock(KeyGenerator.class); + } +} diff --git a/cli/pico-cli/src/test/resources/META-INF/services/com.quorum.tessera.key.generation.KeyGeneratorFactory b/cli/pico-cli/src/test/resources/META-INF/services/com.quorum.tessera.key.generation.KeyGeneratorFactory new file mode 100644 index 0000000000..32bc9ace90 --- /dev/null +++ b/cli/pico-cli/src/test/resources/META-INF/services/com.quorum.tessera.key.generation.KeyGeneratorFactory @@ -0,0 +1 @@ +com.quorum.tessera.picocli.keys.MockKeyGeneratorFactory diff --git a/cli/pico-cli/src/test/resources/keygen-sample.json b/cli/pico-cli/src/test/resources/keygen-sample.json new file mode 100644 index 0000000000..a3f4555734 --- /dev/null +++ b/cli/pico-cli/src/test/resources/keygen-sample.json @@ -0,0 +1,62 @@ +{ + "useWhiteList": false, + "encryptor": { + "type":"NACL" + }, + "jdbc": { + "username": "scott", + "password": "tiger", + "url": "foo:bar" + }, + "serverConfigs": [ + { + "app": "ThirdParty", + "enabled": true, + "serverAddress": "http://localhost:8090", + "communicationType": "REST" + }, + { + "app": "Q2T", + "enabled": true, + "serverAddress": "unix:/tmp/test.ipc", + "communicationType": "REST" + }, + { + "app": "P2P", + "enabled": true, + "serverAddress": "http://localhost:8091", + "sslConfig": { + "tls": "OFF", + "generateKeyStoreIfNotExisted": "false", + "serverKeyStore": "./ssl/server1-keystore", + "serverKeyStorePassword": "quorum", + "serverTrustStore": "./ssl/server-truststore", + "serverTrustStorePassword": "quorum", + "serverTrustMode": "CA", + "clientKeyStore": "./ssl/client1-keystore", + "clientKeyStorePassword": "quorum", + "clientTrustStore": "./ssl/client-truststore", + "clientTrustStorePassword": "quorum", + "clientTrustMode": "CA", + "knownClientsFile": "./ssl/knownClients1", + "knownServersFile": "./ssl/knownServers1" + }, + "communicationType": "REST" + } + ], + "peer": [ + { + "url": "http://bogus1.com" + }, + { + "url": "http://bogus2.com" + } + ], + "keys": { + "passwords": [ + ], + "keyData": [] + }, + "alwaysSendTo": [], + "unixSocketFile": "${unixSocketPath}" +} diff --git a/cli/pico-cli/src/test/resources/lockedprivatekey.json b/cli/pico-cli/src/test/resources/lockedprivatekey.json new file mode 100644 index 0000000000..26497430a9 --- /dev/null +++ b/cli/pico-cli/src/test/resources/lockedprivatekey.json @@ -0,0 +1,14 @@ +{ + "data": { + "aopts": { + "variant": "id", + "memory": 1024, + "iterations": 1, + "parallelism": 1 + }, + "snonce": "dwixVoY+pOI2FMuu4k0jLqN/naQiTzWe", + "asalt": "JoPVq9G6NdOb+Ugv+HnUeA==", + "sbox": "6Jd/MXn29fk6jcrFYGPb75l7sDJae06I3Y1Op+bZSZqlYXsMpa/8lLE29H0sX3yw" + }, + "type": "argon2sbox" +} diff --git a/cli/pico-cli/src/test/resources/missing-config.json b/cli/pico-cli/src/test/resources/missing-config.json new file mode 100644 index 0000000000..e627f10f00 --- /dev/null +++ b/cli/pico-cli/src/test/resources/missing-config.json @@ -0,0 +1,38 @@ +{ + "useWhiteList": false, + "encryptor": { + "type":"NACL" + }, + "server": { + "port": 99 + }, + "peer": [ + { + "url": "http://bogus1.com" + }, + { + "url": "http://bogus2.com" + } + ], + "keys": { + "passwords": [], + "keyData": [ + { + "privateKey": { + "value": "PRIVATEKEY", + "argonOptions": { + "algorithm": "SHA", + "iterations": 1, + "memory": 256, + "parallelism": 1 + } + }, + "publicKey": { + "path": "/somepath" + } + } + ] + }, + "alwaysSendTo": [], + "unixSocketFile": "${unixSocketPath}" +} diff --git a/cli/pico-cli/src/test/resources/sample-config-invalidpath.json b/cli/pico-cli/src/test/resources/sample-config-invalidpath.json new file mode 100644 index 0000000000..34bdf12e6f --- /dev/null +++ b/cli/pico-cli/src/test/resources/sample-config-invalidpath.json @@ -0,0 +1,68 @@ +{ + "useWhiteList": false, + "encryptor": { + "type":"NACL" + }, + "jdbc": { + "username": "scott", + "password": "tiger", + "url": "foo:bar" + }, + "serverConfigs": [ + { + "app": "ThirdParty", + "enabled": true, + "serverAddress": "http://localhost:8090", + "communicationType": "REST" + }, + { + "app": "Q2T", + "enabled": true, + "serverAddress": "unix:/tmp/test.ipc", + "communicationType": "REST" + }, + { + "app": "P2P", + "enabled": true, + "serverAddress": "http://localhost:8091", + "sslConfig": { + "tls": "OFF", + "generateKeyStoreIfNotExisted": "false", + "serverKeyStore": "./ssl/server1-keystore", + "serverKeyStorePassword": "quorum", + "serverTrustStore": "./ssl/server-truststore", + "serverTrustStorePassword": "quorum", + "serverTrustMode": "CA", + "clientKeyStore": "./ssl/client1-keystore", + "clientKeyStorePassword": "quorum", + "clientTrustStore": "./ssl/client-truststore", + "clientTrustStorePassword": "quorum", + "clientTrustMode": "CA", + "knownClientsFile": "./ssl/knownClients1", + "knownServersFile": "./ssl/knownServers1" + }, + "communicationType": "REST" + } + ], + "peer": [ + { + "url": "http://bogus1.com" + }, + { + "url": "http://bogus2.com" + } + ], + "keys": { + "passwords": [], + "keyData": [ + { + "publicKeyPath": "${publicKeyPath}", + "privateKeyPath": "${privateKeyPath}" + } + ] + }, + "alwaysSendTo": [ + "/+UuD63zItL1EbjxkKUljMgG8Z1w0AJ8pNOR4iq2yQc=" + ], + "unixSocketFile": "tm.ipc" +} From 0f79b73816a16c2b72dfab2a0ad87d9614b244a2 Mon Sep 17 00:00:00 2001 From: chris-j-h Date: Fri, 13 Dec 2019 16:27:03 +0000 Subject: [PATCH 20/41] Update OverrideUtil to enable positional selection of override targets e.g. --override root.branch[1].property=overridden-value. Previously, this was not possible. --- .../tessera/config/cli/DefaultCliAdapter.java | 2 +- .../tessera/config/cli/OverrideUtil.java | 81 +++-- .../tessera/config/cli/OverrideUtilTest.java | 292 +++++++++++++++++- .../quorum/tessera/config/cli/ToOverride.java | 96 ++++++ .../tessera/picocli/PicoCliDelegate.java | 58 ++-- .../tessera/picocli/TesseraCommand.java | 7 + 6 files changed, 456 insertions(+), 80 deletions(-) create mode 100644 cli/config-cli/src/test/java/com/quorum/tessera/config/cli/ToOverride.java diff --git a/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/DefaultCliAdapter.java b/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/DefaultCliAdapter.java index 8ed7a941f1..16045e40d0 100644 --- a/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/DefaultCliAdapter.java +++ b/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/DefaultCliAdapter.java @@ -122,7 +122,7 @@ public CliResult execute(String... args) throws Exception { if (line.hasOption(optionName)) { String[] values = line.getOptionValues(optionName); LOGGER.debug("Setting : {} with value(s) {}", optionName, values); - OverrideUtil.setValue(config, optionName, values); +// OverrideUtil.setValue(config, optionName, values); LOGGER.debug("Set : {} with value(s) {}", optionName, values); } }); diff --git a/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/OverrideUtil.java b/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/OverrideUtil.java index b33832e7bb..39b22ac0c8 100644 --- a/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/OverrideUtil.java +++ b/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/OverrideUtil.java @@ -1,5 +1,6 @@ package com.quorum.tessera.config.cli; +import com.quorum.tessera.cli.CliException; import com.quorum.tessera.config.Config; import com.quorum.tessera.reflect.ReflectCallback; import org.slf4j.Logger; @@ -15,6 +16,8 @@ import java.nio.file.Paths; import java.util.*; import java.util.function.Predicate; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -28,7 +31,7 @@ public interface OverrideUtil { Collections.unmodifiableList( Arrays.asList(String.class, Path.class, Integer.class, Boolean.class, Long.class)); - Map, Class> PRIMATIVE_LOOKUP = + Map, Class> PRIMITIVE_LOOKUP = Collections.unmodifiableMap( new HashMap, Class>() { { @@ -142,7 +145,7 @@ static Class toArrayType(Class t) { * @param path * @param value */ - static void setValue(Object root, String path, String... value) { + static void setValue(Object root, String path, String value) { if (root == null) { return; @@ -158,25 +161,55 @@ static void setValue(Object root, String path, String... value) { while (pathTokens.hasNext()) { final String token = pathTokens.next(); - final Field field = resolveField(rootType, token); + + final String target; + final String position; + + final String collectionPattern = "^(.*)\\[([0-9].*)\\]$"; + final Pattern r = Pattern.compile(collectionPattern); + final Matcher m = r.matcher(token); + + if (m.matches()) { + target = m.group(1); + position = m.group(2); + LOGGER.debug("Setting {} at position {}", target, position); + } else { + target = token; + position = null; + } + + final Field field = resolveField(rootType, target); field.setAccessible(true); final Class fieldType = field.getType(); if (Collection.class.isAssignableFrom(fieldType)) { + if (Objects.isNull(position)) { + throw new CliException(path + ": position not provided for Collection parameter override " + token); + } + + final int i = Integer.parseInt(position); final Class genericType = resolveCollectionParameter(field.getGenericType()); List list = (List) Optional.ofNullable(getValue(root, field)).orElse(new ArrayList<>()); + if (isSimple(genericType)) { - List convertedValues = - (List) Stream.of(value).map(v -> convertTo(genericType, v)).collect(Collectors.toList()); + Object convertedValue = convertTo(genericType, value); - List merged = new ArrayList(list); - merged.addAll(convertedValues); + List updated = new ArrayList(list); - setValue(root, field, merged); + while (updated.size() <= i) { + Class convertedType = PRIMITIVE_LOOKUP.getOrDefault(fieldType, fieldType); + Object emptyValue = convertTo(convertedType, null); + + updated.add(emptyValue); + } + + updated.set(i, convertedValue); + + setValue(root, field, updated); } else { @@ -184,34 +217,24 @@ static void setValue(Object root, String path, String... value) { pathTokens.forEachRemaining(builder::add); String nestedPath = builder.stream().collect(Collectors.joining(".")); - final Object[] newList; - if (ADDITIVE_COLLECTION_FIELDS.contains(field.getName())) { - newList = new Object[value.length]; - } else { - newList = Arrays.copyOf(list.toArray(), value.length); + while (list.size() <= i) { + final Object newObject = createInstance(genericType); + initialiseNestedObjects(newObject); + list.add(newObject); } - for (int i = 0; i < value.length; i++) { - final String v = value[i]; + final Object nestedObject = list.get(i); - final Object nestedObject = Optional.ofNullable(newList[i]).orElse(createInstance(genericType)); + // update the collection's complex object + setValue(nestedObject, nestedPath, value); - initialiseNestedObjects(nestedObject); - - setValue(nestedObject, nestedPath, v); - newList[i] = nestedObject; - } - List merged = new ArrayList(); - if (ADDITIVE_COLLECTION_FIELDS.contains(field.getName())) { - merged.addAll(list); - } - merged.addAll(Arrays.asList(newList)); - setValue(root, field, merged); + // update the root object with the updated collection + setValue(root, field, list); } } else if (isSimple(fieldType)) { - Class convertedType = PRIMATIVE_LOOKUP.getOrDefault(fieldType, fieldType); - Object convertedValue = convertTo(convertedType, value[0]); + Class convertedType = PRIMITIVE_LOOKUP.getOrDefault(fieldType, fieldType); + Object convertedValue = convertTo(convertedType, value); setValue(root, field, convertedValue); } else { diff --git a/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/OverrideUtilTest.java b/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/OverrideUtilTest.java index a44319b864..3abe811a4e 100644 --- a/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/OverrideUtilTest.java +++ b/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/OverrideUtilTest.java @@ -17,6 +17,7 @@ import java.lang.reflect.Field; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; @@ -330,12 +331,19 @@ public void createConfigInstanceWithInterfaceReturnsNull() { assertThat(interfaceObject).isNull(); } + @Test + public void convertToByteArray() { + final byte[] result = OverrideUtil.convertTo(byte[].class, "HELLOW"); + assertThat(result).isEqualTo("HELLOW".getBytes()); + } + @Test public void setValue() { Config config = OverrideUtil.createInstance(Config.class); OverrideUtil.setValue(config, "jdbc.username", "someuser"); - OverrideUtil.setValue(config, "peers.url", "snonce1", "snonce2"); + OverrideUtil.setValue(config, "peers[0].url", "snonce1"); + OverrideUtil.setValue(config, "peers[1].url", "snonce2"); assertThat(config.getJdbcConfig().getUsername()).isEqualTo("someuser"); @@ -346,7 +354,8 @@ public void setValue() { @Test public void setValueWithoutAdditions() { final OtherClass someList = new OtherClass(); - OverrideUtil.setValue(someList, "someList.someValue", "password1", "password2"); + OverrideUtil.setValue(someList, "someList[0].someValue", "password1"); + OverrideUtil.setValue(someList, "someList[1].someValue", "password2"); assertThat(someList.someList.get(0).someValue).isEqualTo("password1"); assertThat(someList.someList.get(1).someValue).isEqualTo("password2"); } @@ -379,8 +388,8 @@ public void definePrivateAndPublicKeyWithOverridesOnly() throws Exception { Config config = OverrideUtil.createInstance(Config.class); - OverrideUtil.setValue(config, "keys.keyData.publicKey", "PUBLICKEY"); - OverrideUtil.setValue(config, "keys.keyData.privateKey", "PRIVATEKEY"); + OverrideUtil.setValue(config, "keys[0].keyData.publicKey", "PUBLICKEY"); + OverrideUtil.setValue(config, "keys[0].keyData.privateKey", "PRIVATEKEY"); // UNmarshlling to COnfig to try (ByteArrayOutputStream bout = new ByteArrayOutputStream()) { JaxbUtil.marshalWithNoValidation(config, bout); @@ -402,7 +411,8 @@ public void defineAlwaysSendToWithOverridesOnly() throws Exception { Config config = OverrideUtil.createInstance(Config.class); - OverrideUtil.setValue(config, "alwaysSendTo", "ONE", "TWO"); + OverrideUtil.setValue(config, "alwaysSendTo[0]", "ONE"); + OverrideUtil.setValue(config, "alwaysSendTo[1]", "TWO"); try (ByteArrayOutputStream bout = new ByteArrayOutputStream()) { JaxbUtil.marshalWithNoValidation(config, bout); @@ -416,15 +426,9 @@ public void defineAlwaysSendToWithOverridesOnly() throws Exception { } @Test - public void convertToByteArray() { - final byte[] result = OverrideUtil.convertTo(byte[].class, "HELLOW"); - assertThat(result).isEqualTo("HELLOW".getBytes()); - } - - @Test - public void setValueWithAnnoClass() throws Exception { + public void setValueWithAnonClassDoesNothing() { - SomeIFace annon = + SomeIFace anon = new SomeIFace() { private String value = "HEllow"; @@ -434,11 +438,271 @@ public String getValue() { } }; - OverrideUtil.setValue(annon, "value", "SOMETHING", "SOMETHINGELSE"); + OverrideUtil.setValue(anon, "value", "SOMETHING"); } interface SomeIFace { String getValue(); } + + @Test + public void setValue_elementOfSimpleCollectionReplaced() { + String initialValue = "initial test value"; + String overriddenValue = "overridden test value"; + + ToOverride toOverride = new ToOverride(); + + List simpleList = Arrays.asList("element 1", initialValue, "element 3"); + toOverride.setSimpleList(simpleList); + + OverrideUtil.setValue(toOverride, "simpleList[1]", overriddenValue); + + assertThat(toOverride.getSimpleList()).hasSize(3); + assertThat(toOverride.getSimpleList().get(0)).isEqualTo("element 1"); + assertThat(toOverride.getSimpleList().get(1)).isEqualTo(overriddenValue); + assertThat(toOverride.getSimpleList().get(2)).isEqualTo("element 3"); + } + + @Test + public void setValue_propertyOfElementInComplexCollectionReplaced() { + int initialValue = 11; + int overriddenValue = 20; + + ToOverride toOverride = new ToOverride(); + + ToOverride.OtherTestClass otherClass = new ToOverride.OtherTestClass(); + otherClass.setCount(initialValue); + + List someList = new ArrayList<>(); + someList.add(otherClass); + + toOverride.setSomeList(someList); + + OverrideUtil.setValue(toOverride, "someList[0].count", Integer.toString(overriddenValue)); + + assertThat(toOverride.getSomeList()).hasSize(1); + assertThat(toOverride.getSomeList().get(0).getCount()).isEqualTo(overriddenValue); + } + + @Test + public void setValue_elementOfSimpleCollectionInComplexCollectionReplaced() { + String initialValue = "initial test value"; + String overriddenValue = "updated test value"; + + ToOverride toOverride = new ToOverride(); + + List otherList = Arrays.asList("some value", initialValue); + ToOverride.OtherTestClass otherClass = new ToOverride.OtherTestClass(); + otherClass.setOtherList(otherList); + + List someList = new ArrayList<>(); + someList.add(otherClass); + + toOverride.setSomeList(someList); + + OverrideUtil.setValue(toOverride, "someList[0].otherList[1]", overriddenValue); + + assertThat(toOverride.getSomeList()).hasSize(1); + assertThat(toOverride.getSomeList().get(0).getOtherList()).hasSize(2); + assertThat(toOverride.getSomeList().get(0).getOtherList().get(1)).isEqualTo(overriddenValue); + } + + @Test + public void setValue_simplePropertyReplaced() { + String initialValue = "the initial value"; + String overriddenValue = "the overridden value"; + + ToOverride toOverride = new ToOverride(); + toOverride.setOtherValue(initialValue); + + OverrideUtil.setValue(toOverride, "otherValue", overriddenValue); + + assertThat(toOverride.getOtherValue()).isEqualTo(overriddenValue); + } + + @Test + public void setValue_propertyOfComplexPropertyReplaced() { + int initialValue = 11; + int overriddenValue = 20; + + ToOverride.OtherTestClass complexProperty = new ToOverride.OtherTestClass(); + complexProperty.setCount(initialValue); + + ToOverride toOverride = new ToOverride(); + toOverride.setComplexProperty(complexProperty); + + OverrideUtil.setValue(toOverride, "complexProperty.count", Integer.toString(overriddenValue)); + + assertThat(toOverride.getComplexProperty()).isNotNull(); + assertThat(toOverride.getComplexProperty().getCount()).isEqualTo(overriddenValue); + } + + @Test + public void setValue_simpleCollectionCreated() { + String overriddenValue = "overridden test value"; + + ToOverride toOverride = new ToOverride(); + + OverrideUtil.setValue(toOverride, "simpleList[2]", overriddenValue); + + assertThat(toOverride.getSimpleList()).isNotNull(); + assertThat(toOverride.getSimpleList()).hasSize(3); + assertThat(toOverride.getSimpleList().get(0)).isNull(); + assertThat(toOverride.getSimpleList().get(1)).isNull(); + assertThat(toOverride.getSimpleList().get(2)).isEqualTo(overriddenValue); + } + + @Test + public void setValue_complexCollectionCreated() { + int overriddenCount = 11; + int otherOverriddenCount = 22; + String overriddenValue = "overridden test value"; + + ToOverride toOverride = new ToOverride(); + + OverrideUtil.setValue(toOverride, "someList[1].count", Integer.toString(overriddenCount)); + OverrideUtil.setValue(toOverride, "someList[2].count", Integer.toString(otherOverriddenCount)); + OverrideUtil.setValue(toOverride, "someList[2].strVal", overriddenValue); + + assertThat(toOverride.getSomeList()).isNotNull(); + assertThat(toOverride.getSomeList()).hasSize(3); + + assertThat(toOverride.getSomeList().get(0)).isNotNull(); + assertThat(toOverride.getSomeList().get(1)).isNotNull(); + assertThat(toOverride.getSomeList().get(2)).isNotNull(); + + assertThat(toOverride.getSomeList().get(0).getCount()).isZero(); + assertThat(toOverride.getSomeList().get(0).getStrVal()).isNull(); + assertThat(toOverride.getSomeList().get(1).getCount()).isEqualTo(overriddenCount); + assertThat(toOverride.getSomeList().get(1).getStrVal()).isNull(); + assertThat(toOverride.getSomeList().get(2).getCount()).isEqualTo(otherOverriddenCount); + assertThat(toOverride.getSomeList().get(2).getStrVal()).isEqualTo(overriddenValue); + } + + @Test + public void setValue_simpleCollectionInComplexCollectionCreated() { + String overriddenValue = "overridden test value"; + + ToOverride toOverride = new ToOverride(); + + OverrideUtil.setValue(toOverride, "someList[0].otherList[1]", overriddenValue); + + assertThat(toOverride.getSomeList()).isNotNull(); + assertThat(toOverride.getSomeList()).hasSize(1); + assertThat(toOverride.getSomeList().get(0)).isNotNull(); + + assertThat(toOverride.getSomeList().get(0).getOtherList()).isNotNull(); + assertThat(toOverride.getSomeList().get(0).getOtherList()).hasSize(2); + + assertThat(toOverride.getSomeList().get(0).getOtherList().get(0)).isNull(); + assertThat(toOverride.getSomeList().get(0).getOtherList().get(1)).isEqualTo(overriddenValue); + } + + @Test + public void setValue_nullSimplePropertySet() { + String overriddenValue = "overridden test value"; + + ToOverride toOverride = new ToOverride(); + + OverrideUtil.setValue(toOverride, "otherValue", overriddenValue); + + assertThat(toOverride.getOtherValue()).isNotNull(); + assertThat(toOverride.getOtherValue()).isEqualTo(overriddenValue); + } + + @Test + public void setValue_nullPropertyOfComplexPropertySet() { + String overriddenValue = "overridden test value"; + + ToOverride toOverride = new ToOverride(); + ToOverride.OtherTestClass complexProperty = new ToOverride.OtherTestClass(); + toOverride.setComplexProperty(complexProperty); + + OverrideUtil.setValue(toOverride, "complexProperty.strVal", overriddenValue); + + assertThat(toOverride.getComplexProperty()).isNotNull(); + assertThat(toOverride.getComplexProperty().getStrVal()).isNotNull(); + assertThat(toOverride.getComplexProperty().getStrVal()).isEqualTo(overriddenValue); + } + + @Test + public void setValue_simpleCollectionExtended() { + String overriddenValue = "overridden test value"; + + ToOverride toOverride = new ToOverride(); + List simpleList = new ArrayList<>(); + simpleList.add("element1"); + toOverride.setSimpleList(simpleList); + + assertThat(toOverride.getSimpleList()).hasSize(1); + + OverrideUtil.setValue(toOverride, "simpleList[1]", overriddenValue); + + assertThat(toOverride.getSimpleList()).isNotNull(); + assertThat(toOverride.getSimpleList()).hasSize(2); + assertThat(toOverride.getSimpleList().get(0)).isEqualTo("element1"); + assertThat(toOverride.getSimpleList().get(1)).isEqualTo(overriddenValue); + } + + @Test + public void setValue_complexCollectionExtended() { + String overriddenValue = "overridden test value"; + + ToOverride toOverride = new ToOverride(); + ToOverride.OtherTestClass otherTestClass = new ToOverride.OtherTestClass(); + otherTestClass.setStrVal("element1"); + + List someList = new ArrayList<>(); + someList.add(otherTestClass); + + toOverride.setSomeList(someList); + + assertThat(toOverride.getSomeList()).hasSize(1); + + OverrideUtil.setValue(toOverride, "someList[1].strVal", overriddenValue); + + assertThat(toOverride.getSomeList()).isNotNull(); + assertThat(toOverride.getSomeList()).hasSize(2); + assertThat(toOverride.getSomeList().get(0).getStrVal()).isEqualTo("element1"); + assertThat(toOverride.getSomeList().get(1).getStrVal()).isEqualTo(overriddenValue); + } + + @Test + public void setValue_simpleCollectionInComplexCollectionExtended() { + String overriddenValue = "overridden test value"; + + ToOverride toOverride = new ToOverride(); + List otherList = new ArrayList<>(); + otherList.add("otherElement1"); + + ToOverride.OtherTestClass otherTestClass = new ToOverride.OtherTestClass(); + otherTestClass.setOtherList(otherList); + + List someList = new ArrayList<>(); + someList.add(otherTestClass); + + toOverride.setSomeList(someList); + + assertThat(toOverride.getSomeList()).hasSize(1); + assertThat(toOverride.getSomeList().get(0).getOtherList()).hasSize(1); + + OverrideUtil.setValue(toOverride, "someList[0].otherList[1]", overriddenValue); + + assertThat(toOverride.getSomeList()).isNotNull(); + assertThat(toOverride.getSomeList()).hasSize(1); + assertThat(toOverride.getSomeList().get(0)).isNotNull(); + assertThat(toOverride.getSomeList().get(0).getOtherList()).hasSize(2); + + assertThat(toOverride.getSomeList().get(0).getOtherList().get(0)).isEqualTo("otherElement1"); + assertThat(toOverride.getSomeList().get(0).getOtherList().get(1)).isEqualTo(overriddenValue); + } + + @Ignore + @Test + // TODO (cjh) Previously, peer overrides would be appended to any existing peers list. This has now been disabled so that behaviour is consistent across all options. It is now possible to overwrite existing peers or append the existing list depending on the position provided when calling the CLI, i.e. --peers[i]. It might be worth introducing an additional mode to always append so that the position doesn't have to be provided in these simpler situations? + public void setValue_peersAppended() { + assertThat(true).isFalse(); + } + } diff --git a/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/ToOverride.java b/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/ToOverride.java new file mode 100644 index 0000000000..93705e3a61 --- /dev/null +++ b/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/ToOverride.java @@ -0,0 +1,96 @@ +package com.quorum.tessera.config.cli; + +import javax.xml.bind.annotation.XmlElement; +import java.util.List; + +class ToOverride { + @XmlElement(name = "some_value") + private String someValue; + + @XmlElement + private String otherValue; + + @XmlElement + private OtherTestClass complexProperty; + + @XmlElement + private List someList; + + @XmlElement + private List simpleList; + + String getSomeValue() { + return someValue; + } + + String getOtherValue() { + return otherValue; + } + + List getSimpleList() { + return simpleList; + } + + OtherTestClass getComplexProperty() { + return complexProperty; + } + + List getSomeList() { + return someList; + } + + void setSomeValue(String someValue) { + this.someValue = someValue; + } + + void setOtherValue(String otherValue) { + this.otherValue = otherValue; + } + + void setSomeList(List someList) { + this.someList = someList; + } + + void setSimpleList(List simpleList) { + this.simpleList = simpleList; + } + + void setComplexProperty(OtherTestClass otherTestClass) { + complexProperty = otherTestClass; + } + + static class OtherTestClass { + @XmlElement + private int count; + + @XmlElement + private String strVal; + + @XmlElement + private List otherList; + + int getCount() { + return count; + } + + void setCount(int count) { + this.count = count; + } + + String getStrVal() { + return strVal; + } + + void setStrVal(String strVal) { + this.strVal = strVal; + } + + List getOtherList() { + return otherList; + } + + void setOtherList(List otherList) { + this.otherList = otherList; + } + } +} diff --git a/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/PicoCliDelegate.java b/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/PicoCliDelegate.java index 00e27cd7c5..7d270b3a0d 100644 --- a/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/PicoCliDelegate.java +++ b/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/PicoCliDelegate.java @@ -55,16 +55,16 @@ public CliResult execute(String... args) throws Exception { // TODO(cjh) most usage options have empty lines between them, but not all. Need to remove the empty lines. // add config override options, dynamically generated from the config object - Map overrideOptions = OverrideUtil.buildConfigOptions(); - overrideOptions.forEach( - (optionName, optionType) -> { - OptionSpec.Builder optionBuilder = - OptionSpec.builder(String.format("--%s", optionName)) - .paramLabel(optionType.getSimpleName()) - .type(optionType); - - command.addOption(optionBuilder.build()); - }); +// Map overrideOptions = OverrideUtil.buildConfigOptions(); +// overrideOptions.forEach( +// (optionName, optionType) -> { +// OptionSpec.Builder optionBuilder = +// OptionSpec.builder(String.format("--%s", optionName)) +// .paramLabel(optionType.getSimpleName()) +// .type(optionType); +// +// command.addOption(optionBuilder.build()); +// }); final CLIExceptionCapturer mapper = new CLIExceptionCapturer(); @@ -162,32 +162,18 @@ private Config getConfigFromCLI(CommandLine.ParseResult parseResult) throws Exce throw new NoTesseraConfigfileOptionException(); } - // apply CLI overrides - parsedArgs.forEach( - parsedArg -> { - // positional (i.e. unnamed) CLI flags are ignored - if (!parsedArg.isOption()) { - return; - } - - OptionSpec parsedOption = (OptionSpec) parsedArg; - - // configfile CLI option is ignored as it was already parsed - // pidfile CLI option is ignored as it is parsed later - // TODO(cjh) improve, checks all names for all provided options - for (String name : parsedOption.names()) { - if ("--configfile".equals(name) || "--pidfile".equals(name)) { - return; - } - } - - String optionName = parsedOption.longestName().replaceFirst("^--", ""); - String[] values = parsedOption.stringValues().toArray(new String[0]); - - LOGGER.debug("Setting : {} with value(s) {}", optionName, values); - OverrideUtil.setValue(config, optionName, values); - LOGGER.debug("Set : {} with value(s) {}", optionName, values); - }); + if (parseResult.hasMatchedOption("override")) { + Map overrides = parseResult.matchedOption("override").getValue(); + + for (String target : overrides.keySet()) { + String value = overrides.get(target); + + // apply CLI overrides + LOGGER.debug("Setting : {} with value(s) {}", target, value); + OverrideUtil.setValue(config, target, value); + LOGGER.debug("Set : {} with value(s) {}", target, value); + } + } keyPasswordResolver.resolveKeyPasswords(config); diff --git a/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/TesseraCommand.java b/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/TesseraCommand.java index 3b02bbd793..80d9facb3e 100644 --- a/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/TesseraCommand.java +++ b/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/TesseraCommand.java @@ -4,6 +4,8 @@ import picocli.CommandLine; import java.nio.file.Path; +import java.util.LinkedHashMap; +import java.util.Map; @CommandLine.Command( name = "tessera", @@ -26,4 +28,9 @@ public class TesseraCommand { names = {"--pidfile", "-pidfile"}, description = "the path to write the PID to") public Path pidFilePath; + + @CommandLine.Option(names = {"-o", "--override"}, paramLabel = "KEY=VALUE") + private Map overrides = new LinkedHashMap<>(); + + // TODO(cjh) dry run option to print effective config to terminal to allow review of CLI overrides } From fd8f39e1653d4cb1bdf95873108a66cb749be42d Mon Sep 17 00:00:00 2001 From: chris-j-h Date: Sun, 15 Dec 2019 10:23:16 +0000 Subject: [PATCH 21/41] Update pico cli delegate tests with new override syntax --- .../tessera/picocli/PicoCliDelegateTest.java | 43 +++++++++++++------ 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/cli/pico-cli/src/test/java/com/quorum/tessera/picocli/PicoCliDelegateTest.java b/cli/pico-cli/src/test/java/com/quorum/tessera/picocli/PicoCliDelegateTest.java index fb0ccde0ab..d7615e05da 100644 --- a/cli/pico-cli/src/test/java/com/quorum/tessera/picocli/PicoCliDelegateTest.java +++ b/cli/pico-cli/src/test/java/com/quorum/tessera/picocli/PicoCliDelegateTest.java @@ -11,7 +11,9 @@ import com.quorum.tessera.key.generation.KeyGenerator; import com.quorum.tessera.picocli.keys.MockKeyGeneratorFactory; import com.quorum.tessera.test.util.ElUtil; +import org.assertj.core.util.Strings; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -121,7 +123,12 @@ public void keygenWithConfig() throws Exception { CliResult result = cliDelegate.execute( - "-keygen", "-filename", UUID.randomUUID().toString(), "-configfile", configFilePath.toString()); + "-keygen", + "-filename", + UUID.randomUUID().toString(), + "-configfile", + configFilePath.toString() + ); assertThat(result).isNotNull(); assertThat(result.getStatus()).isEqualTo(0); @@ -150,6 +157,8 @@ public void noConfigfileOption() { .hasMessage("Missing required option '--configfile '"); } + // TODO (cjh) remove ignore once implemented + @Ignore @Test public void output() throws Exception { @@ -219,7 +228,12 @@ public void dynOption() throws Exception { Path configFile = createAndPopulatePaths(getClass().getResource("/sample-config.json")); - CliResult result = cliDelegate.execute("-configfile", configFile.toString(), "--jdbc.username", "somename"); + CliResult result = cliDelegate.execute( + "-configfile", + configFile.toString(), + "-o", + "jdbc.username=somename" + ); assertThat(result).isNotNull(); assertThat(result.getConfig()).isPresent(); @@ -262,10 +276,11 @@ public void withEmptyConfigOverrideAll() throws Exception { cliDelegate.execute( "-configfile", configFile.toString(), - "--unixSocketFile", - unixSocketFile.toString(), - "--encryptor.type", - "NACL"); + "-o", + Strings.join("unixSocketFile=", unixSocketFile.toString()).with(""), + "-o", + "encryptor.type=NACL" + ); assertThat(result).isNotNull(); failBecauseExceptionWasNotThrown(ConstraintViolationException.class); @@ -282,7 +297,12 @@ public void overrideAlwaysSendTo() throws Exception { Path configFile = createAndPopulatePaths(getClass().getResource("/sample-config.json")); CliResult result = null; try { - result = cliDelegate.execute("-configfile", configFile.toString(), "--alwaysSendTo", alwaysSendToKey); + result = cliDelegate.execute( + "-configfile", + configFile.toString(), + "-o", + Strings.join("alwaysSendTo[1]=", alwaysSendToKey).with("") + ); } catch (Exception ex) { fail(ex.getMessage()); } @@ -300,12 +320,9 @@ public void overridePeers() throws Exception { CliResult result = cliDelegate.execute( - "-configfile", - configFile.toString(), - "--peer.url", - "anotherpeer", - "--peer.url", - "yetanotherpeer"); + "-configfile", configFile.toString(), + "-o", "peer[2].url=anotherpeer", + "--override", "peer[3].url=yetanotherpeer"); assertThat(result).isNotNull(); assertThat(result.getConfig()).isPresent(); From 3812037a128d0ae64f119bfd9619805f154da3af Mon Sep 17 00:00:00 2001 From: chris-j-h Date: Sun, 15 Dec 2019 10:39:22 +0000 Subject: [PATCH 22/41] Migrate remainder of DefaultCliAdapter tests for pico CLI --- .../config/cli/DefaultCliAdapterTest.java | 198 +++++++++--------- .../tessera/picocli/PicoCliDelegateTest.java | 61 ++++++ 2 files changed, 160 insertions(+), 99 deletions(-) diff --git a/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/DefaultCliAdapterTest.java b/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/DefaultCliAdapterTest.java index cba3c3a3e4..205798bdd4 100644 --- a/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/DefaultCliAdapterTest.java +++ b/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/DefaultCliAdapterTest.java @@ -314,103 +314,103 @@ public void setUp() { // assertThat(result.getConfig().get().getAlwaysSendTo()) // .containsExactly("/+UuD63zItL1EbjxkKUljMgG8Z1w0AJ8pNOR4iq2yQc=", alwaysSendToKey); // } - - @Test - public void overridePeers() throws Exception { - - Path configFile = createAndPopulatePaths(getClass().getResource("/sample-config.json")); - - CliResult result = - cliAdapter.execute( - "-configfile", - configFile.toString(), - "-peer.url", - "anotherpeer", - "-peer.url", - "yetanotherpeer"); - - assertThat(result).isNotNull(); - assertThat(result.getConfig()).isPresent(); - assertThat(result.getConfig().get().getPeers()).hasSize(4); - assertThat(result.getConfig().get().getPeers().stream().map(Peer::getUrl)) - .containsExactlyInAnyOrder("anotherpeer", "yetanotherpeer", "http://bogus1.com", "http://bogus2.com"); - } - - @Test - public void updatingPasswordsDoesntProcessOtherOptions() throws Exception { - MockKeyGeneratorFactory.reset(); - - final InputStream oldIn = System.in; - final InputStream inputStream = - new ByteArrayInputStream((System.lineSeparator() + System.lineSeparator()).getBytes()); - System.setIn(inputStream); - - final KeyDataConfig startingKey = - JaxbUtil.unmarshal(getClass().getResourceAsStream("/lockedprivatekey.json"), KeyDataConfig.class); - - final Path key = Files.createTempFile("key", ".key"); - Files.write(key, JaxbUtil.marshalToString(startingKey).getBytes()); - - final CliResult result = - cliAdapter.execute( - "-updatepassword", - "--keys.keyData.privateKeyPath", - key.toString(), - "--keys.passwords", - "testpassword"); - - assertThat(result).isNotNull(); - - Mockito.verifyZeroInteractions(MockKeyGeneratorFactory.getMockKeyGenerator()); - System.setIn(oldIn); - } - - @Test - public void suppressStartupForKeygenOption() throws Exception { - final CliResult cliResult = cliAdapter.execute("-keygen", "--encryptor.type", "NACL"); - - assertThat(cliResult.isSuppressStartup()).isTrue(); - } - - @Test - public void allowStartupForKeygenAndConfigfileOptions() throws Exception { - final KeyGenerator keyGenerator = MockKeyGeneratorFactory.getMockKeyGenerator(); - Path publicKeyPath = Files.createTempFile(UUID.randomUUID().toString(), ""); - Path privateKeyPath = Files.createTempFile(UUID.randomUUID().toString(), ""); - - Files.write(privateKeyPath, Arrays.asList("SOMEDATA")); - Files.write(publicKeyPath, Arrays.asList("SOMEDATA")); - - FilesystemKeyPair keypair = new FilesystemKeyPair(publicKeyPath, privateKeyPath, null); - when(keyGenerator.generate(anyString(), eq(null), eq(null))).thenReturn(keypair); - - final Path configFile = createAndPopulatePaths(getClass().getResource("/sample-config.json")); - - final CliResult cliResult = cliAdapter.execute("-keygen", "-configfile", configFile.toString()); - - assertThat(cliResult.isSuppressStartup()).isFalse(); - } - - @Test - public void suppressStartupForKeygenAndVaultUrlAndConfigfileOptions() throws Exception { - final KeyGenerator keyGenerator = MockKeyGeneratorFactory.getMockKeyGenerator(); - - final FilesystemKeyPair keypair = new FilesystemKeyPair(Paths.get(""), Paths.get(""), null); - when(keyGenerator.generate(anyString(), eq(null), eq(null))).thenReturn(keypair); - - final Path configFile = createAndPopulatePaths(getClass().getResource("/sample-config.json")); - final String vaultUrl = "https://test.vault.azure.net"; - - final CliResult cliResult = - cliAdapter.execute( - "-keygen", - "-keygenvaulttype", - "AZURE", - "-keygenvaulturl", - vaultUrl, - "-configfile", - configFile.toString()); - - assertThat(cliResult.isSuppressStartup()).isTrue(); - } +// +// @Test +// public void overridePeers() throws Exception { +// +// Path configFile = createAndPopulatePaths(getClass().getResource("/sample-config.json")); +// +// CliResult result = +// cliAdapter.execute( +// "-configfile", +// configFile.toString(), +// "-peer.url", +// "anotherpeer", +// "-peer.url", +// "yetanotherpeer"); +// +// assertThat(result).isNotNull(); +// assertThat(result.getConfig()).isPresent(); +// assertThat(result.getConfig().get().getPeers()).hasSize(4); +// assertThat(result.getConfig().get().getPeers().stream().map(Peer::getUrl)) +// .containsExactlyInAnyOrder("anotherpeer", "yetanotherpeer", "http://bogus1.com", "http://bogus2.com"); +// } +// +// @Test +// public void updatingPasswordsDoesntProcessOtherOptions() throws Exception { +// MockKeyGeneratorFactory.reset(); +// +// final InputStream oldIn = System.in; +// final InputStream inputStream = +// new ByteArrayInputStream((System.lineSeparator() + System.lineSeparator()).getBytes()); +// System.setIn(inputStream); +// +// final KeyDataConfig startingKey = +// JaxbUtil.unmarshal(getClass().getResourceAsStream("/lockedprivatekey.json"), KeyDataConfig.class); +// +// final Path key = Files.createTempFile("key", ".key"); +// Files.write(key, JaxbUtil.marshalToString(startingKey).getBytes()); +// +// final CliResult result = +// cliAdapter.execute( +// "-updatepassword", +// "--keys.keyData.privateKeyPath", +// key.toString(), +// "--keys.passwords", +// "testpassword"); +// +// assertThat(result).isNotNull(); +// +// Mockito.verifyZeroInteractions(MockKeyGeneratorFactory.getMockKeyGenerator()); +// System.setIn(oldIn); +// } +// +// @Test +// public void suppressStartupForKeygenOption() throws Exception { +// final CliResult cliResult = cliAdapter.execute("-keygen", "--encryptor.type", "NACL"); +// +// assertThat(cliResult.isSuppressStartup()).isTrue(); +// } +// +// @Test +// public void allowStartupForKeygenAndConfigfileOptions() throws Exception { +// final KeyGenerator keyGenerator = MockKeyGeneratorFactory.getMockKeyGenerator(); +// Path publicKeyPath = Files.createTempFile(UUID.randomUUID().toString(), ""); +// Path privateKeyPath = Files.createTempFile(UUID.randomUUID().toString(), ""); +// +// Files.write(privateKeyPath, Arrays.asList("SOMEDATA")); +// Files.write(publicKeyPath, Arrays.asList("SOMEDATA")); +// +// FilesystemKeyPair keypair = new FilesystemKeyPair(publicKeyPath, privateKeyPath, null); +// when(keyGenerator.generate(anyString(), eq(null), eq(null))).thenReturn(keypair); +// +// final Path configFile = createAndPopulatePaths(getClass().getResource("/sample-config.json")); +// +// final CliResult cliResult = cliAdapter.execute("-keygen", "-configfile", configFile.toString()); +// +// assertThat(cliResult.isSuppressStartup()).isFalse(); +// } +// +// @Test +// public void suppressStartupForKeygenAndVaultUrlAndConfigfileOptions() throws Exception { +// final KeyGenerator keyGenerator = MockKeyGeneratorFactory.getMockKeyGenerator(); +// +// final FilesystemKeyPair keypair = new FilesystemKeyPair(Paths.get(""), Paths.get(""), null); +// when(keyGenerator.generate(anyString(), eq(null), eq(null))).thenReturn(keypair); +// +// final Path configFile = createAndPopulatePaths(getClass().getResource("/sample-config.json")); +// final String vaultUrl = "https://test.vault.azure.net"; +// +// final CliResult cliResult = +// cliAdapter.execute( +// "-keygen", +// "-keygenvaulttype", +// "AZURE", +// "-keygenvaulturl", +// vaultUrl, +// "-configfile", +// configFile.toString()); +// +// assertThat(cliResult.isSuppressStartup()).isTrue(); +// } } diff --git a/cli/pico-cli/src/test/java/com/quorum/tessera/picocli/PicoCliDelegateTest.java b/cli/pico-cli/src/test/java/com/quorum/tessera/picocli/PicoCliDelegateTest.java index d7615e05da..98c978556e 100644 --- a/cli/pico-cli/src/test/java/com/quorum/tessera/picocli/PicoCliDelegateTest.java +++ b/cli/pico-cli/src/test/java/com/quorum/tessera/picocli/PicoCliDelegateTest.java @@ -8,6 +8,7 @@ import com.quorum.tessera.config.keypairs.FilesystemKeyPair; import com.quorum.tessera.config.keypairs.InlineKeypair; import com.quorum.tessera.config.keys.KeyEncryptor; +import com.quorum.tessera.config.util.JaxbUtil; import com.quorum.tessera.key.generation.KeyGenerator; import com.quorum.tessera.picocli.keys.MockKeyGeneratorFactory; import com.quorum.tessera.test.util.ElUtil; @@ -15,14 +16,18 @@ import org.junit.Before; import org.junit.Ignore; import org.junit.Test; +import org.mockito.Mockito; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.validation.ConstraintViolationException; +import java.io.ByteArrayInputStream; +import java.io.InputStream; import java.io.UncheckedIOException; import java.nio.file.FileAlreadyExistsException; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.Paths; import java.util.Arrays; import java.util.HashMap; import java.util.Map; @@ -330,4 +335,60 @@ public void overridePeers() throws Exception { assertThat(result.getConfig().get().getPeers().stream().map(Peer::getUrl)) .containsExactlyInAnyOrder("anotherpeer", "yetanotherpeer", "http://bogus1.com", "http://bogus2.com"); } + + @Test + public void updatingPasswordsDoesntProcessOtherOptions() throws Exception { + MockKeyGeneratorFactory.reset(); + + final InputStream oldIn = System.in; + final InputStream inputStream = + new ByteArrayInputStream((System.lineSeparator() + System.lineSeparator()).getBytes()); + System.setIn(inputStream); + + final KeyDataConfig startingKey = + JaxbUtil.unmarshal(getClass().getResourceAsStream("/lockedprivatekey.json"), KeyDataConfig.class); + + final Path key = Files.createTempFile("key", ".key"); + Files.write(key, JaxbUtil.marshalToString(startingKey).getBytes()); + + final CliResult result = + cliDelegate.execute( + "-updatepassword", + "--keys.keyData.privateKeyPath", + key.toString(), + "--keys.passwords", + "testpassword"); + + assertThat(result).isNotNull(); + + Mockito.verifyZeroInteractions(MockKeyGeneratorFactory.getMockKeyGenerator()); + System.setIn(oldIn); + } + + @Test + public void suppressStartupForKeygenOption() throws Exception { + final CliResult cliResult = cliDelegate.execute("-keygen", "--encryptor.type", "NACL"); + + assertThat(cliResult.isSuppressStartup()).isTrue(); + } + + @Test + public void suppressStartupForKeygenOptionWithConfigfile() throws Exception { + final KeyGenerator keyGenerator = MockKeyGeneratorFactory.getMockKeyGenerator(); + Path publicKeyPath = Files.createTempFile(UUID.randomUUID().toString(), ""); + Path privateKeyPath = Files.createTempFile(UUID.randomUUID().toString(), ""); + + Files.write(privateKeyPath, Arrays.asList("SOMEDATA")); + Files.write(publicKeyPath, Arrays.asList("SOMEDATA")); + + FilesystemKeyPair keypair = new FilesystemKeyPair(publicKeyPath, privateKeyPath, null); + when(keyGenerator.generate(anyString(), eq(null), eq(null))).thenReturn(keypair); + + final Path configFile = createAndPopulatePaths(getClass().getResource("/sample-config.json")); + + final CliResult cliResult = cliDelegate.execute("-keygen", "-configfile", configFile.toString()); + + assertThat(cliResult.isSuppressStartup()).isTrue(); + } + } From bf55f36b2d2a65921706f537cd3874ba24c903ba Mon Sep 17 00:00:00 2001 From: chris-j-h Date: Sun, 15 Dec 2019 11:46:15 +0000 Subject: [PATCH 23/41] Remove unused CliDelegate code and tests & execute enclave CLI directly --- .../com/quorum/tessera/cli/CliDelegate.java | 84 ----- .../quorum/tessera/cli/CliDelegateTest.java | 131 +------ .../com/quorum/tessera/cli/CliResultTest.java | 18 + .../tessera/config/cli/OverrideUtil.java | 1 + .../config/cli/DefaultCliAdapterTest.java | 355 ++++++++---------- .../tessera/config/cli/OverrideUtilTest.java | 147 ++++---- .../parsers/EncryptorConfigParserTest.java | 34 +- .../tessera/picocli/PicoCliDelegate.java | 14 - .../tessera/picocli/PicoCliDelegateTest.java | 71 ++-- .../com/quorum/tessera/enclave/rest/Main.java | 11 +- pom.xml | 1 + tessera-core/pom.xml | 16 +- .../java/com/quorum/tessera/core/CoreIT.java | 5 +- .../com/quorum/tessera/launcher/Main.java | 2 - 14 files changed, 358 insertions(+), 532 deletions(-) diff --git a/cli/cli-api/src/main/java/com/quorum/tessera/cli/CliDelegate.java b/cli/cli-api/src/main/java/com/quorum/tessera/cli/CliDelegate.java index c3348e07a7..d80facc56a 100644 --- a/cli/cli-api/src/main/java/com/quorum/tessera/cli/CliDelegate.java +++ b/cli/cli-api/src/main/java/com/quorum/tessera/cli/CliDelegate.java @@ -1,29 +1,12 @@ package com.quorum.tessera.cli; -import com.quorum.tessera.ServiceLoaderUtil; -import com.quorum.tessera.cli.parsers.ConfigConverter; import com.quorum.tessera.config.Config; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import picocli.CommandLine; -import java.util.List; -import java.util.Objects; import java.util.Optional; -import java.util.function.Predicate; -import java.util.stream.Collectors; - -import static picocli.CommandLine.Model.CommandSpec.DEFAULT_COMMAND_NAME; public enum CliDelegate { INSTANCE; - private static final Logger LOGGER = LoggerFactory.getLogger(CliDelegate.class); - - private static final CliResult HELP_RESULT = new CliResult(0, true, null); - - private static final CliResult DEFAULT_RESULT = new CliResult(1, true, null); - private Config config; public static CliDelegate instance() { @@ -39,71 +22,4 @@ public Config getConfig() { public void setConfig(Config config) { this.config = config; } - - public CliResult execute(String... args) throws Exception { - - final List adapters = ServiceLoaderUtil.loadAll(CliAdapter.class).collect(Collectors.toList()); - - LOGGER.debug("Loaded adapters {}", adapters); - - CliType cliType = CliType.valueOf(System.getProperty(CliType.CLI_TYPE_KEY, CliType.CONFIG.name())); - - LOGGER.debug("cliType {}", cliType); - - Predicate isTopLevel = - a -> a.getClass().getAnnotation(CommandLine.Command.class).name().equals(DEFAULT_COMMAND_NAME); - - // Finds the top level adapter that we want to start with. Exactly one is expected to be on the classpath. - final CliAdapter adapter = - adapters.stream() - .filter(a -> a.getClass().isAnnotationPresent(CommandLine.Command.class)) - .filter(isTopLevel) - .filter(a -> a.getType() == cliType) - .findFirst() - .get(); - - LOGGER.debug("Loaded adapter {}", adapter); - - // Then we find all the others and attach them as sub-commands. It is expected that they have defined their - // own hierarchy and command names. - final List subcommands = - adapters.stream() - // .filter(isTopLevel) - .filter(a -> a != adapter) - .filter(a -> a.getType() != CliType.ENCLAVE) - .collect(Collectors.toList()); - - // the mapper will give us access to the exception from the outside, if one occurred. - // mostly since we have an existing system, and this is a workaround - final CLIExceptionCapturer mapper = new CLIExceptionCapturer(); - final CommandLine commandLine = new CommandLine(adapter); - - subcommands.stream().peek(sc -> LOGGER.debug("Adding subcommand {}", sc)).forEach(commandLine::addSubcommand); - - commandLine - .registerConverter(Config.class, new ConfigConverter()) - .setSeparator(" ") - .setCaseInsensitiveEnumValuesAllowed(true) - .setUnmatchedArgumentsAllowed(true) - .setExecutionExceptionHandler(mapper) - .setParameterExceptionHandler(mapper); - - commandLine.execute(args); - - // if an exception occurred, throw it to to the upper levels where it gets handled - if (mapper.getThrown() != null) { - throw mapper.getThrown(); - } - - // otherwise, set the config object (if there is one) and return - final CliResult result = - commandLine.getParseResult().asCommandLineList().stream() - .map(cl -> cl.isUsageHelpRequested() ? HELP_RESULT : cl.getExecutionResult()) - .filter(Objects::nonNull) - .findFirst() - .orElse(DEFAULT_RESULT); - - this.config = result.getConfig().orElse(null); - return result; - } } diff --git a/cli/cli-api/src/test/java/com/quorum/tessera/cli/CliDelegateTest.java b/cli/cli-api/src/test/java/com/quorum/tessera/cli/CliDelegateTest.java index 315e003aa8..70f2bad34e 100644 --- a/cli/cli-api/src/test/java/com/quorum/tessera/cli/CliDelegateTest.java +++ b/cli/cli-api/src/test/java/com/quorum/tessera/cli/CliDelegateTest.java @@ -1,151 +1,30 @@ package com.quorum.tessera.cli; import com.quorum.tessera.config.Config; -import org.junit.After; -import org.junit.Before; import org.junit.Test; -import java.util.NoSuchElementException; - import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.catchThrowable; public class CliDelegateTest { private final CliDelegate instance = CliDelegate.INSTANCE; - @Before - public void setUp() { - MockCliAdapter.reset(); - MockSubcommandCliAdapter.reset(); - MockAdminSubcommandCliAdapter.reset(); - } - - @After - public void onTearDown() { - MockCliAdapter.reset(); - MockSubcommandCliAdapter.reset(); - MockAdminSubcommandCliAdapter.reset(); - } - @Test public void createInstance() { assertThat(CliDelegate.instance()).isSameAs(instance); } - @Test - public void adminCliOptionCreatesAdminInstance() throws Exception { - MockCliAdapter.setType(CliType.CONFIG); - - int status = 111; - MockAdminSubcommandCliAdapter.setResult(new CliResult(status, true, null)); - - CliResult result = instance.execute("admin", "-configfile", "/path/to/file"); - - assertThat(result).isNotNull(); - assertThat(result.getStatus()).isEqualTo(status); - } - - @Test - public void standardCliOptionsCreatesConfigInstance() throws Exception { - MockCliAdapter.setType(CliType.CONFIG); - int status = 111; - Config config = new Config(); - MockCliAdapter.setResult(new CliResult(status, true, config)); - - CliResult result = instance.execute("-configfile", "path/to/file"); - - assertThat(result).isNotNull(); - assertThat(result.getStatus()).isEqualTo(status); - assertThat(result.getConfig().get()).isEqualTo(config); - } - - @Test - public void configFieldUpdatedAfterExecution() throws Exception { - MockCliAdapter.setType(CliType.CONFIG); - int status = 111; - Config config = new Config(); - MockCliAdapter.setResult(new CliResult(status, false, config)); - - instance.execute("-configfile", "/path/to/file"); - - assertThat(instance.getConfig()).isEqualTo(new Config()); - } - @Test(expected = IllegalStateException.class) - public void fetchConfigWithoutExecution() { + public void fetchConfigBeforeSet() { instance.getConfig(); } - // PicoCLI tests @Test - public void nonNullResultIsReturned() throws Exception { - MockCliAdapter.setType(CliType.CONFIG); - MockSubcommandCliAdapter.setType(CliType.ADMIN); - - final CliResult result = new CliResult(0, true, null); - MockSubcommandCliAdapter.setResult(result); - - assertThat(instance.execute("some-subcommand")).isSameAs(result); - } - - @Test - public void helpOptionGivenReturnsSuccessCliResult() throws Exception { - MockCliAdapter.setType(CliType.CONFIG); - - final CliResult expected = new CliResult(0, true, null); - assertThat(instance.execute("help")).isEqualToComparingFieldByField(expected); - } - - @Test - public void helpOptionGivenOnSubcommandReturnsSuccessCliResult() throws Exception { - MockCliAdapter.setType(CliType.CONFIG); - MockSubcommandCliAdapter.setType(CliType.ADMIN); - - final CliResult expected = new CliResult(0, true, null); - assertThat(instance.execute("some-subcommand", "help")).isEqualToComparingFieldByField(expected); - } - - @Test - public void exceptionFromCommandBubblesUp() { - System.setProperty("tessera.cli.type", CliType.ADMIN.name()); - MockCliAdapter.setType(CliType.ADMIN); - MockSubcommandCliAdapter.setType(CliType.ADMIN); - final Exception exception = new Exception(); - MockSubcommandCliAdapter.setExceptionToBeThrown(exception); - - final Throwable throwable = catchThrowable(() -> instance.execute("some-subcommand")); - - assertThat(throwable).isSameAs(exception); - - MockSubcommandCliAdapter.setExceptionToBeThrown(null); - MockSubcommandCliAdapter.setType(null); - System.clearProperty("tessera.cli.type"); - } - - @Test - public void noArgsDefaultsTonConfigCli() throws Exception { - MockCliAdapter.setType(CliType.CONFIG); - CliResult result = instance.execute(); - - assertThat(result).isNotNull(); - } - - @Test(expected = NoSuchElementException.class) - public void unknownType() throws Exception { - MockCliAdapter.setType(CliType.ENCLAVE); - MockSubcommandCliAdapter.setType(CliType.ENCLAVE); - CliResult result = instance.execute(); - } - - @Test - public void filterEnclaveFromSubcommand() throws Exception { - MockCliAdapter.setType(CliType.CONFIG); - MockSubcommandCliAdapter.setType(CliType.ENCLAVE); + public void fetchConfigAfterSet() { + Config config = new Config(); - // some-subcommand is not recognised as an option because the subcommand has been filtered due to its ENCLAVE type. Therefore, the standard help cli result should be expected - final CliResult expected = new CliResult(0, true, null); + instance.setConfig(config); - assertThat(instance.execute("some-subcommand", "help")).isEqualToComparingFieldByField(expected); + assertThat(instance.getConfig()).isEqualTo(config); } } diff --git a/cli/cli-api/src/test/java/com/quorum/tessera/cli/CliResultTest.java b/cli/cli-api/src/test/java/com/quorum/tessera/cli/CliResultTest.java index 4a41a58519..207f3f7726 100644 --- a/cli/cli-api/src/test/java/com/quorum/tessera/cli/CliResultTest.java +++ b/cli/cli-api/src/test/java/com/quorum/tessera/cli/CliResultTest.java @@ -1,5 +1,6 @@ package com.quorum.tessera.cli; +import com.quorum.tessera.config.Config; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; @@ -13,4 +14,21 @@ public void isSuppressStartup() { assertThat(result.isSuppressStartup()).isEqualTo(expected); } + + @Test + public void getStatus() { + int expected = 1; + CliResult result = new CliResult(1, false, null); + + assertThat(result.getStatus()).isEqualTo(expected); + } + + @Test + public void getConfig() { + Config config = new Config(); + CliResult result = new CliResult(1, false, config); + + assertThat(result.getConfig()).isNotEmpty(); + assertThat(result.getConfig().get()).isEqualTo(config); + } } diff --git a/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/OverrideUtil.java b/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/OverrideUtil.java index 39b22ac0c8..c7e9687a13 100644 --- a/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/OverrideUtil.java +++ b/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/OverrideUtil.java @@ -5,6 +5,7 @@ import com.quorum.tessera.reflect.ReflectCallback; import org.slf4j.Logger; import org.slf4j.LoggerFactory; + import javax.xml.bind.annotation.XmlAttribute; import javax.xml.bind.annotation.XmlElement; import java.lang.reflect.Array; diff --git a/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/DefaultCliAdapterTest.java b/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/DefaultCliAdapterTest.java index 205798bdd4..c8151c8c97 100644 --- a/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/DefaultCliAdapterTest.java +++ b/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/DefaultCliAdapterTest.java @@ -1,33 +1,10 @@ package com.quorum.tessera.config.cli; -import com.quorum.tessera.cli.CliResult; -import com.quorum.tessera.config.KeyDataConfig; -import com.quorum.tessera.config.Peer; import com.quorum.tessera.config.cli.keys.MockKeyGeneratorFactory; -import com.quorum.tessera.config.keypairs.FilesystemKeyPair; -import com.quorum.tessera.config.util.JaxbUtil; -import com.quorum.tessera.key.generation.KeyGenerator; import org.junit.Before; -import org.junit.Test; -import org.mockito.Mockito; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.validation.ConstraintViolationException; -import java.io.ByteArrayInputStream; -import java.io.InputStream; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.Arrays; -import java.util.UUID; - -import static com.quorum.tessera.test.util.ElUtil.createAndPopulatePaths; -import static org.assertj.core.api.Assertions.*; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.eq; -import static org.mockito.Mockito.when; - public class DefaultCliAdapterTest { private static final Logger LOGGER = LoggerFactory.getLogger(DefaultCliAdapterTest.class); @@ -248,169 +225,171 @@ public void setUp() { // assertThat(result.getConfig().get().getJdbcConfig().getUsername()).isEqualTo("somename"); // assertThat(result.getConfig().get().getJdbcConfig().getPassword()).isEqualTo("tiger"); // } -// -// @Ignore -// public void withInvalidPath() throws Exception { -// // unixSocketPath -// Map params = new HashMap<>(); -// params.put("publicKeyPath", "BOGUS.bogus"); -// params.put("privateKeyPath", "BOGUS.bogus"); -// -// Path configFile = -// ElUtil.createTempFileFromTemplate(getClass().getResource("/sample-config-invalidpath.json"), params); -// -// try { -// cliAdapter.execute("-configfile", configFile.toString()); -// failBecauseExceptionWasNotThrown(ConstraintViolationException.class); -// } catch (ConstraintViolationException ex) { -// assertThat(ex.getConstraintViolations()) -// .hasSize(1) -// .extracting("messageTemplate") -// .containsExactly("{UnsupportedKeyPair.message}"); -// } -// } -// -// @Test -// public void withEmptyConfigOverrideAll() throws Exception { -// -// Path unixSocketFile = Files.createTempFile("unixSocketFile", ".ipc"); -// unixSocketFile.toFile().deleteOnExit(); -// -// Path configFile = Files.createTempFile("withEmptyConfigOverrideAll", ".json"); -// configFile.toFile().deleteOnExit(); -// Files.write(configFile, "{}".getBytes()); -// try { -// CliResult result = -// cliAdapter.execute( -// "-configfile", -// configFile.toString(), -// "--unixSocketFile", -// unixSocketFile.toString(), -// "--encryptor.type", -// "NACL"); -// -// assertThat(result).isNotNull(); -// failBecauseExceptionWasNotThrown(ConstraintViolationException.class); -// } catch (ConstraintViolationException ex) { -// ex.getConstraintViolations().forEach(v -> LOGGER.info("{}", v)); -// } -// } -// -// @Test -// public void overrideAlwaysSendTo() throws Exception { -// -// String alwaysSendToKey = "giizjhZQM6peq52O7icVFxdTmTYinQSUsvyhXzgZqkE="; -// -// Path configFile = createAndPopulatePaths(getClass().getResource("/sample-config.json")); -// CliResult result = null; -// try { -// result = cliAdapter.execute("-configfile", configFile.toString(), "-alwaysSendTo", alwaysSendToKey); -// } catch (Exception ex) { -// fail(ex.getMessage()); -// } -// assertThat(result).isNotNull(); -// assertThat(result.getConfig()).isPresent(); -// assertThat(result.getConfig().get().getAlwaysSendTo()).hasSize(2); -// assertThat(result.getConfig().get().getAlwaysSendTo()) -// .containsExactly("/+UuD63zItL1EbjxkKUljMgG8Z1w0AJ8pNOR4iq2yQc=", alwaysSendToKey); -// } -// -// @Test -// public void overridePeers() throws Exception { -// -// Path configFile = createAndPopulatePaths(getClass().getResource("/sample-config.json")); -// -// CliResult result = -// cliAdapter.execute( -// "-configfile", -// configFile.toString(), -// "-peer.url", -// "anotherpeer", -// "-peer.url", -// "yetanotherpeer"); -// -// assertThat(result).isNotNull(); -// assertThat(result.getConfig()).isPresent(); -// assertThat(result.getConfig().get().getPeers()).hasSize(4); -// assertThat(result.getConfig().get().getPeers().stream().map(Peer::getUrl)) -// .containsExactlyInAnyOrder("anotherpeer", "yetanotherpeer", "http://bogus1.com", "http://bogus2.com"); -// } -// -// @Test -// public void updatingPasswordsDoesntProcessOtherOptions() throws Exception { -// MockKeyGeneratorFactory.reset(); -// -// final InputStream oldIn = System.in; -// final InputStream inputStream = -// new ByteArrayInputStream((System.lineSeparator() + System.lineSeparator()).getBytes()); -// System.setIn(inputStream); -// -// final KeyDataConfig startingKey = -// JaxbUtil.unmarshal(getClass().getResourceAsStream("/lockedprivatekey.json"), KeyDataConfig.class); -// -// final Path key = Files.createTempFile("key", ".key"); -// Files.write(key, JaxbUtil.marshalToString(startingKey).getBytes()); -// -// final CliResult result = -// cliAdapter.execute( -// "-updatepassword", -// "--keys.keyData.privateKeyPath", -// key.toString(), -// "--keys.passwords", -// "testpassword"); -// -// assertThat(result).isNotNull(); -// -// Mockito.verifyZeroInteractions(MockKeyGeneratorFactory.getMockKeyGenerator()); -// System.setIn(oldIn); -// } -// -// @Test -// public void suppressStartupForKeygenOption() throws Exception { -// final CliResult cliResult = cliAdapter.execute("-keygen", "--encryptor.type", "NACL"); -// -// assertThat(cliResult.isSuppressStartup()).isTrue(); -// } -// -// @Test -// public void allowStartupForKeygenAndConfigfileOptions() throws Exception { -// final KeyGenerator keyGenerator = MockKeyGeneratorFactory.getMockKeyGenerator(); -// Path publicKeyPath = Files.createTempFile(UUID.randomUUID().toString(), ""); -// Path privateKeyPath = Files.createTempFile(UUID.randomUUID().toString(), ""); -// -// Files.write(privateKeyPath, Arrays.asList("SOMEDATA")); -// Files.write(publicKeyPath, Arrays.asList("SOMEDATA")); -// -// FilesystemKeyPair keypair = new FilesystemKeyPair(publicKeyPath, privateKeyPath, null); -// when(keyGenerator.generate(anyString(), eq(null), eq(null))).thenReturn(keypair); -// -// final Path configFile = createAndPopulatePaths(getClass().getResource("/sample-config.json")); -// -// final CliResult cliResult = cliAdapter.execute("-keygen", "-configfile", configFile.toString()); -// -// assertThat(cliResult.isSuppressStartup()).isFalse(); -// } -// -// @Test -// public void suppressStartupForKeygenAndVaultUrlAndConfigfileOptions() throws Exception { -// final KeyGenerator keyGenerator = MockKeyGeneratorFactory.getMockKeyGenerator(); -// -// final FilesystemKeyPair keypair = new FilesystemKeyPair(Paths.get(""), Paths.get(""), null); -// when(keyGenerator.generate(anyString(), eq(null), eq(null))).thenReturn(keypair); -// -// final Path configFile = createAndPopulatePaths(getClass().getResource("/sample-config.json")); -// final String vaultUrl = "https://test.vault.azure.net"; -// -// final CliResult cliResult = -// cliAdapter.execute( -// "-keygen", -// "-keygenvaulttype", -// "AZURE", -// "-keygenvaulturl", -// vaultUrl, -// "-configfile", -// configFile.toString()); -// -// assertThat(cliResult.isSuppressStartup()).isTrue(); -// } + // + // @Ignore + // public void withInvalidPath() throws Exception { + // // unixSocketPath + // Map params = new HashMap<>(); + // params.put("publicKeyPath", "BOGUS.bogus"); + // params.put("privateKeyPath", "BOGUS.bogus"); + // + // Path configFile = + // ElUtil.createTempFileFromTemplate(getClass().getResource("/sample-config-invalidpath.json"), + // params); + // + // try { + // cliAdapter.execute("-configfile", configFile.toString()); + // failBecauseExceptionWasNotThrown(ConstraintViolationException.class); + // } catch (ConstraintViolationException ex) { + // assertThat(ex.getConstraintViolations()) + // .hasSize(1) + // .extracting("messageTemplate") + // .containsExactly("{UnsupportedKeyPair.message}"); + // } + // } + // + // @Test + // public void withEmptyConfigOverrideAll() throws Exception { + // + // Path unixSocketFile = Files.createTempFile("unixSocketFile", ".ipc"); + // unixSocketFile.toFile().deleteOnExit(); + // + // Path configFile = Files.createTempFile("withEmptyConfigOverrideAll", ".json"); + // configFile.toFile().deleteOnExit(); + // Files.write(configFile, "{}".getBytes()); + // try { + // CliResult result = + // cliAdapter.execute( + // "-configfile", + // configFile.toString(), + // "--unixSocketFile", + // unixSocketFile.toString(), + // "--encryptor.type", + // "NACL"); + // + // assertThat(result).isNotNull(); + // failBecauseExceptionWasNotThrown(ConstraintViolationException.class); + // } catch (ConstraintViolationException ex) { + // ex.getConstraintViolations().forEach(v -> LOGGER.info("{}", v)); + // } + // } + // + // @Test + // public void overrideAlwaysSendTo() throws Exception { + // + // String alwaysSendToKey = "giizjhZQM6peq52O7icVFxdTmTYinQSUsvyhXzgZqkE="; + // + // Path configFile = createAndPopulatePaths(getClass().getResource("/sample-config.json")); + // CliResult result = null; + // try { + // result = cliAdapter.execute("-configfile", configFile.toString(), "-alwaysSendTo", alwaysSendToKey); + // } catch (Exception ex) { + // fail(ex.getMessage()); + // } + // assertThat(result).isNotNull(); + // assertThat(result.getConfig()).isPresent(); + // assertThat(result.getConfig().get().getAlwaysSendTo()).hasSize(2); + // assertThat(result.getConfig().get().getAlwaysSendTo()) + // .containsExactly("/+UuD63zItL1EbjxkKUljMgG8Z1w0AJ8pNOR4iq2yQc=", alwaysSendToKey); + // } + // + // @Test + // public void overridePeers() throws Exception { + // + // Path configFile = createAndPopulatePaths(getClass().getResource("/sample-config.json")); + // + // CliResult result = + // cliAdapter.execute( + // "-configfile", + // configFile.toString(), + // "-peer.url", + // "anotherpeer", + // "-peer.url", + // "yetanotherpeer"); + // + // assertThat(result).isNotNull(); + // assertThat(result.getConfig()).isPresent(); + // assertThat(result.getConfig().get().getPeers()).hasSize(4); + // assertThat(result.getConfig().get().getPeers().stream().map(Peer::getUrl)) + // .containsExactlyInAnyOrder("anotherpeer", "yetanotherpeer", "http://bogus1.com", + // "http://bogus2.com"); + // } + // + // @Test + // public void updatingPasswordsDoesntProcessOtherOptions() throws Exception { + // MockKeyGeneratorFactory.reset(); + // + // final InputStream oldIn = System.in; + // final InputStream inputStream = + // new ByteArrayInputStream((System.lineSeparator() + System.lineSeparator()).getBytes()); + // System.setIn(inputStream); + // + // final KeyDataConfig startingKey = + // JaxbUtil.unmarshal(getClass().getResourceAsStream("/lockedprivatekey.json"), KeyDataConfig.class); + // + // final Path key = Files.createTempFile("key", ".key"); + // Files.write(key, JaxbUtil.marshalToString(startingKey).getBytes()); + // + // final CliResult result = + // cliAdapter.execute( + // "-updatepassword", + // "--keys.keyData.privateKeyPath", + // key.toString(), + // "--keys.passwords", + // "testpassword"); + // + // assertThat(result).isNotNull(); + // + // Mockito.verifyZeroInteractions(MockKeyGeneratorFactory.getMockKeyGenerator()); + // System.setIn(oldIn); + // } + // + // @Test + // public void suppressStartupForKeygenOption() throws Exception { + // final CliResult cliResult = cliAdapter.execute("-keygen", "--encryptor.type", "NACL"); + // + // assertThat(cliResult.isSuppressStartup()).isTrue(); + // } + // + // @Test + // public void allowStartupForKeygenAndConfigfileOptions() throws Exception { + // final KeyGenerator keyGenerator = MockKeyGeneratorFactory.getMockKeyGenerator(); + // Path publicKeyPath = Files.createTempFile(UUID.randomUUID().toString(), ""); + // Path privateKeyPath = Files.createTempFile(UUID.randomUUID().toString(), ""); + // + // Files.write(privateKeyPath, Arrays.asList("SOMEDATA")); + // Files.write(publicKeyPath, Arrays.asList("SOMEDATA")); + // + // FilesystemKeyPair keypair = new FilesystemKeyPair(publicKeyPath, privateKeyPath, null); + // when(keyGenerator.generate(anyString(), eq(null), eq(null))).thenReturn(keypair); + // + // final Path configFile = createAndPopulatePaths(getClass().getResource("/sample-config.json")); + // + // final CliResult cliResult = cliAdapter.execute("-keygen", "-configfile", configFile.toString()); + // + // assertThat(cliResult.isSuppressStartup()).isFalse(); + // } + // + // @Test + // public void suppressStartupForKeygenAndVaultUrlAndConfigfileOptions() throws Exception { + // final KeyGenerator keyGenerator = MockKeyGeneratorFactory.getMockKeyGenerator(); + // + // final FilesystemKeyPair keypair = new FilesystemKeyPair(Paths.get(""), Paths.get(""), null); + // when(keyGenerator.generate(anyString(), eq(null), eq(null))).thenReturn(keypair); + // + // final Path configFile = createAndPopulatePaths(getClass().getResource("/sample-config.json")); + // final String vaultUrl = "https://test.vault.azure.net"; + // + // final CliResult cliResult = + // cliAdapter.execute( + // "-keygen", + // "-keygenvaulttype", + // "AZURE", + // "-keygenvaulturl", + // vaultUrl, + // "-configfile", + // configFile.toString()); + // + // assertThat(cliResult.isSuppressStartup()).isTrue(); + // } } diff --git a/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/OverrideUtilTest.java b/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/OverrideUtilTest.java index 3abe811a4e..7f3d14bcb2 100644 --- a/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/OverrideUtilTest.java +++ b/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/OverrideUtilTest.java @@ -1,5 +1,6 @@ package com.quorum.tessera.config.cli; +import com.quorum.tessera.cli.CliException; import com.quorum.tessera.config.Config; import com.quorum.tessera.config.KeyConfiguration; import com.quorum.tessera.config.Peer; @@ -447,13 +448,30 @@ interface SomeIFace { } @Test - public void setValue_elementOfSimpleCollectionReplaced() { - String initialValue = "initial test value"; - String overriddenValue = "overridden test value"; + public void setValueCollectionButNoPositionProvided() { + final String initialValue = "initial test value"; + final String overriddenValue = "overridden test value"; - ToOverride toOverride = new ToOverride(); + final ToOverride toOverride = new ToOverride(); - List simpleList = Arrays.asList("element 1", initialValue, "element 3"); + final List simpleList = Arrays.asList("element 1", initialValue, "element 3"); + toOverride.setSimpleList(simpleList); + + Throwable ex = catchThrowable(() -> OverrideUtil.setValue(toOverride, "simpleList", overriddenValue)); + + assertThat(ex).isNotNull(); + assertThat(ex).isExactlyInstanceOf(CliException.class); + assertThat(ex).hasMessage("simpleList: position not provided for Collection parameter override simpleList"); + } + + @Test + public void setValueElementOfSimpleCollectionReplaced() { + final String initialValue = "initial test value"; + final String overriddenValue = "overridden test value"; + + final ToOverride toOverride = new ToOverride(); + + final List simpleList = Arrays.asList("element 1", initialValue, "element 3"); toOverride.setSimpleList(simpleList); OverrideUtil.setValue(toOverride, "simpleList[1]", overriddenValue); @@ -465,16 +483,16 @@ public void setValue_elementOfSimpleCollectionReplaced() { } @Test - public void setValue_propertyOfElementInComplexCollectionReplaced() { - int initialValue = 11; - int overriddenValue = 20; + public void setValuePropertyOfElementInComplexCollectionReplaced() { + final int initialValue = 11; + final int overriddenValue = 20; - ToOverride toOverride = new ToOverride(); + final ToOverride toOverride = new ToOverride(); - ToOverride.OtherTestClass otherClass = new ToOverride.OtherTestClass(); + final ToOverride.OtherTestClass otherClass = new ToOverride.OtherTestClass(); otherClass.setCount(initialValue); - List someList = new ArrayList<>(); + final List someList = new ArrayList<>(); someList.add(otherClass); toOverride.setSomeList(someList); @@ -486,17 +504,17 @@ public void setValue_propertyOfElementInComplexCollectionReplaced() { } @Test - public void setValue_elementOfSimpleCollectionInComplexCollectionReplaced() { - String initialValue = "initial test value"; - String overriddenValue = "updated test value"; + public void setValueElementOfSimpleCollectionInComplexCollectionReplaced() { + final String initialValue = "initial test value"; + final String overriddenValue = "updated test value"; - ToOverride toOverride = new ToOverride(); + final ToOverride toOverride = new ToOverride(); - List otherList = Arrays.asList("some value", initialValue); - ToOverride.OtherTestClass otherClass = new ToOverride.OtherTestClass(); + final List otherList = Arrays.asList("some value", initialValue); + final ToOverride.OtherTestClass otherClass = new ToOverride.OtherTestClass(); otherClass.setOtherList(otherList); - List someList = new ArrayList<>(); + final List someList = new ArrayList<>(); someList.add(otherClass); toOverride.setSomeList(someList); @@ -509,11 +527,11 @@ public void setValue_elementOfSimpleCollectionInComplexCollectionReplaced() { } @Test - public void setValue_simplePropertyReplaced() { - String initialValue = "the initial value"; - String overriddenValue = "the overridden value"; + public void setValueSimplePropertyReplaced() { + final String initialValue = "the initial value"; + final String overriddenValue = "the overridden value"; - ToOverride toOverride = new ToOverride(); + final ToOverride toOverride = new ToOverride(); toOverride.setOtherValue(initialValue); OverrideUtil.setValue(toOverride, "otherValue", overriddenValue); @@ -522,14 +540,14 @@ public void setValue_simplePropertyReplaced() { } @Test - public void setValue_propertyOfComplexPropertyReplaced() { - int initialValue = 11; - int overriddenValue = 20; + public void setValuePropertyOfComplexPropertyReplaced() { + final int initialValue = 11; + final int overriddenValue = 20; - ToOverride.OtherTestClass complexProperty = new ToOverride.OtherTestClass(); + final ToOverride.OtherTestClass complexProperty = new ToOverride.OtherTestClass(); complexProperty.setCount(initialValue); - ToOverride toOverride = new ToOverride(); + final ToOverride toOverride = new ToOverride(); toOverride.setComplexProperty(complexProperty); OverrideUtil.setValue(toOverride, "complexProperty.count", Integer.toString(overriddenValue)); @@ -539,10 +557,10 @@ public void setValue_propertyOfComplexPropertyReplaced() { } @Test - public void setValue_simpleCollectionCreated() { - String overriddenValue = "overridden test value"; + public void setValueSimpleCollectionCreated() { + final String overriddenValue = "overridden test value"; - ToOverride toOverride = new ToOverride(); + final ToOverride toOverride = new ToOverride(); OverrideUtil.setValue(toOverride, "simpleList[2]", overriddenValue); @@ -554,12 +572,12 @@ public void setValue_simpleCollectionCreated() { } @Test - public void setValue_complexCollectionCreated() { - int overriddenCount = 11; - int otherOverriddenCount = 22; - String overriddenValue = "overridden test value"; + public void setValueComplexCollectionCreated() { + final int overriddenCount = 11; + final int otherOverriddenCount = 22; + final String overriddenValue = "overridden test value"; - ToOverride toOverride = new ToOverride(); + final ToOverride toOverride = new ToOverride(); OverrideUtil.setValue(toOverride, "someList[1].count", Integer.toString(overriddenCount)); OverrideUtil.setValue(toOverride, "someList[2].count", Integer.toString(otherOverriddenCount)); @@ -581,10 +599,10 @@ public void setValue_complexCollectionCreated() { } @Test - public void setValue_simpleCollectionInComplexCollectionCreated() { - String overriddenValue = "overridden test value"; + public void setValueSimpleCollectionInComplexCollectionCreated() { + final String overriddenValue = "overridden test value"; - ToOverride toOverride = new ToOverride(); + final ToOverride toOverride = new ToOverride(); OverrideUtil.setValue(toOverride, "someList[0].otherList[1]", overriddenValue); @@ -600,10 +618,10 @@ public void setValue_simpleCollectionInComplexCollectionCreated() { } @Test - public void setValue_nullSimplePropertySet() { - String overriddenValue = "overridden test value"; + public void setValueNullSimplePropertySet() { + final String overriddenValue = "overridden test value"; - ToOverride toOverride = new ToOverride(); + final ToOverride toOverride = new ToOverride(); OverrideUtil.setValue(toOverride, "otherValue", overriddenValue); @@ -612,11 +630,11 @@ public void setValue_nullSimplePropertySet() { } @Test - public void setValue_nullPropertyOfComplexPropertySet() { - String overriddenValue = "overridden test value"; + public void setValueNullPropertyOfComplexPropertySet() { + final String overriddenValue = "overridden test value"; - ToOverride toOverride = new ToOverride(); - ToOverride.OtherTestClass complexProperty = new ToOverride.OtherTestClass(); + final ToOverride toOverride = new ToOverride(); + final ToOverride.OtherTestClass complexProperty = new ToOverride.OtherTestClass(); toOverride.setComplexProperty(complexProperty); OverrideUtil.setValue(toOverride, "complexProperty.strVal", overriddenValue); @@ -627,11 +645,11 @@ public void setValue_nullPropertyOfComplexPropertySet() { } @Test - public void setValue_simpleCollectionExtended() { - String overriddenValue = "overridden test value"; + public void setValueSimpleCollectionExtended() { + final String overriddenValue = "overridden test value"; - ToOverride toOverride = new ToOverride(); - List simpleList = new ArrayList<>(); + final ToOverride toOverride = new ToOverride(); + final List simpleList = new ArrayList<>(); simpleList.add("element1"); toOverride.setSimpleList(simpleList); @@ -646,14 +664,14 @@ public void setValue_simpleCollectionExtended() { } @Test - public void setValue_complexCollectionExtended() { - String overriddenValue = "overridden test value"; + public void setValueComplexCollectionExtended() { + final String overriddenValue = "overridden test value"; - ToOverride toOverride = new ToOverride(); - ToOverride.OtherTestClass otherTestClass = new ToOverride.OtherTestClass(); + final ToOverride toOverride = new ToOverride(); + final ToOverride.OtherTestClass otherTestClass = new ToOverride.OtherTestClass(); otherTestClass.setStrVal("element1"); - List someList = new ArrayList<>(); + final List someList = new ArrayList<>(); someList.add(otherTestClass); toOverride.setSomeList(someList); @@ -669,17 +687,17 @@ public void setValue_complexCollectionExtended() { } @Test - public void setValue_simpleCollectionInComplexCollectionExtended() { - String overriddenValue = "overridden test value"; + public void setValueSimpleCollectionInComplexCollectionExtended() { + final String overriddenValue = "overridden test value"; - ToOverride toOverride = new ToOverride(); - List otherList = new ArrayList<>(); + final ToOverride toOverride = new ToOverride(); + final List otherList = new ArrayList<>(); otherList.add("otherElement1"); - ToOverride.OtherTestClass otherTestClass = new ToOverride.OtherTestClass(); + final ToOverride.OtherTestClass otherTestClass = new ToOverride.OtherTestClass(); otherTestClass.setOtherList(otherList); - List someList = new ArrayList<>(); + final List someList = new ArrayList<>(); someList.add(otherTestClass); toOverride.setSomeList(someList); @@ -700,9 +718,12 @@ public void setValue_simpleCollectionInComplexCollectionExtended() { @Ignore @Test - // TODO (cjh) Previously, peer overrides would be appended to any existing peers list. This has now been disabled so that behaviour is consistent across all options. It is now possible to overwrite existing peers or append the existing list depending on the position provided when calling the CLI, i.e. --peers[i]. It might be worth introducing an additional mode to always append so that the position doesn't have to be provided in these simpler situations? - public void setValue_peersAppended() { + // TODO (cjh) Previously, peer overrides would be appended to any existing peers list. This has now been disabled + // so that behaviour is consistent across all options. It is now possible to overwrite existing peers or append the + // existing list depending on the position provided when calling the CLI, i.e. --peers[i]. It might be worth + // introducing an additional mode to always append so that the position doesn't have to be provided in these simpler + // situations? + public void setValuePeersAppended() { assertThat(true).isFalse(); } - } diff --git a/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/parsers/EncryptorConfigParserTest.java b/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/parsers/EncryptorConfigParserTest.java index b439f1cb27..2fcdb253f6 100644 --- a/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/parsers/EncryptorConfigParserTest.java +++ b/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/parsers/EncryptorConfigParserTest.java @@ -36,7 +36,13 @@ public void onTearDown() { } @Test - public void elipticalCurveNoPropertiesDefined() throws IOException { + public void constructor() { + parser = new EncryptorConfigParser(); + assertThat(parser).isNotNull(); + } + + @Test + public void ellipticalCurveNoPropertiesDefined() throws IOException { when(commandLine.hasOption("configfile")).thenReturn(false); when(commandLine.getOptionValue("encryptor.type", EncryptorType.NACL.name())) @@ -57,7 +63,7 @@ public void elipticalCurveNoPropertiesDefined() throws IOException { } @Test - public void elipticalCurveWithDefinedProperties() throws IOException { + public void ellipticalCurveWithDefinedProperties() throws IOException { when(commandLine.getOptionValue("encryptor.type", EncryptorType.NACL.name())) .thenReturn(EncryptorType.EC.name()); @@ -130,4 +136,28 @@ public void keyGenUsedDefaulIfNoTypeDefined() throws Exception { verify(filesDelegate).newInputStream(any(Path.class)); } + + @Test + public void encryptorConfigFromFile() throws Exception { + when(commandLine.getOptionValue("encryptor.type", EncryptorType.NACL.name())) + .thenReturn(EncryptorType.NACL.name()); + when(commandLine.hasOption("configfile")).thenReturn(true); + when(commandLine.getOptionValue("configfile")).thenReturn("somepath"); + + String jsonConfig = "{\"encryptor\": {\"type\": \"EC\"}}"; + + InputStream inputStream = new ByteArrayInputStream(jsonConfig.getBytes()); + when(filesDelegate.newInputStream(any(Path.class))).thenReturn(inputStream); + + EncryptorConfig result = parser.parse(commandLine); + + assertThat(result.getType()).isEqualTo(EncryptorType.EC); + + verify(commandLine).getOptionValue("encryptor.type", EncryptorType.NACL.name()); + verify(commandLine).hasOption("configfile"); + verify(commandLine).getOptionValue("configfile"); + verify(filesDelegate).newInputStream(any(Path.class)); + + verifyNoMoreInteractions(filesDelegate); + } } diff --git a/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/PicoCliDelegate.java b/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/PicoCliDelegate.java index 7d270b3a0d..9287941e21 100644 --- a/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/PicoCliDelegate.java +++ b/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/PicoCliDelegate.java @@ -14,7 +14,6 @@ import org.slf4j.LoggerFactory; import picocli.CommandLine; import picocli.CommandLine.Model.CommandSpec; -import picocli.CommandLine.Model.OptionSpec; import javax.validation.ConstraintViolation; import javax.validation.ConstraintViolationException; @@ -53,19 +52,6 @@ private PicoCliDelegate(final KeyPasswordResolver keyPasswordResolver) { public CliResult execute(String... args) throws Exception { final CommandSpec command = CommandSpec.forAnnotatedObject(TesseraCommand.class); - // TODO(cjh) most usage options have empty lines between them, but not all. Need to remove the empty lines. - // add config override options, dynamically generated from the config object -// Map overrideOptions = OverrideUtil.buildConfigOptions(); -// overrideOptions.forEach( -// (optionName, optionType) -> { -// OptionSpec.Builder optionBuilder = -// OptionSpec.builder(String.format("--%s", optionName)) -// .paramLabel(optionType.getSimpleName()) -// .type(optionType); -// -// command.addOption(optionBuilder.build()); -// }); - final CLIExceptionCapturer mapper = new CLIExceptionCapturer(); CommandLine keyGenCommandLine = new CommandLine(KeyGenCommand.class); diff --git a/cli/pico-cli/src/test/java/com/quorum/tessera/picocli/PicoCliDelegateTest.java b/cli/pico-cli/src/test/java/com/quorum/tessera/picocli/PicoCliDelegateTest.java index 98c978556e..9897c05327 100644 --- a/cli/pico-cli/src/test/java/com/quorum/tessera/picocli/PicoCliDelegateTest.java +++ b/cli/pico-cli/src/test/java/com/quorum/tessera/picocli/PicoCliDelegateTest.java @@ -27,7 +27,6 @@ import java.nio.file.FileAlreadyExistsException; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; import java.util.Arrays; import java.util.HashMap; import java.util.Map; @@ -128,12 +127,7 @@ public void keygenWithConfig() throws Exception { CliResult result = cliDelegate.execute( - "-keygen", - "-filename", - UUID.randomUUID().toString(), - "-configfile", - configFilePath.toString() - ); + "-keygen", "-filename", UUID.randomUUID().toString(), "-configfile", configFilePath.toString()); assertThat(result).isNotNull(); assertThat(result.getStatus()).isEqualTo(0); @@ -233,12 +227,7 @@ public void dynOption() throws Exception { Path configFile = createAndPopulatePaths(getClass().getResource("/sample-config.json")); - CliResult result = cliDelegate.execute( - "-configfile", - configFile.toString(), - "-o", - "jdbc.username=somename" - ); + CliResult result = cliDelegate.execute("-configfile", configFile.toString(), "-o", "jdbc.username=somename"); assertThat(result).isNotNull(); assertThat(result.getConfig()).isPresent(); @@ -278,14 +267,13 @@ public void withEmptyConfigOverrideAll() throws Exception { Files.write(configFile, "{}".getBytes()); try { CliResult result = - cliDelegate.execute( - "-configfile", - configFile.toString(), - "-o", - Strings.join("unixSocketFile=", unixSocketFile.toString()).with(""), - "-o", - "encryptor.type=NACL" - ); + cliDelegate.execute( + "-configfile", + configFile.toString(), + "-o", + Strings.join("unixSocketFile=", unixSocketFile.toString()).with(""), + "-o", + "encryptor.type=NACL"); assertThat(result).isNotNull(); failBecauseExceptionWasNotThrown(ConstraintViolationException.class); @@ -302,12 +290,12 @@ public void overrideAlwaysSendTo() throws Exception { Path configFile = createAndPopulatePaths(getClass().getResource("/sample-config.json")); CliResult result = null; try { - result = cliDelegate.execute( - "-configfile", - configFile.toString(), - "-o", - Strings.join("alwaysSendTo[1]=", alwaysSendToKey).with("") - ); + result = + cliDelegate.execute( + "-configfile", + configFile.toString(), + "-o", + Strings.join("alwaysSendTo[1]=", alwaysSendToKey).with("")); } catch (Exception ex) { fail(ex.getMessage()); } @@ -315,7 +303,7 @@ public void overrideAlwaysSendTo() throws Exception { assertThat(result.getConfig()).isPresent(); assertThat(result.getConfig().get().getAlwaysSendTo()).hasSize(2); assertThat(result.getConfig().get().getAlwaysSendTo()) - .containsExactly("/+UuD63zItL1EbjxkKUljMgG8Z1w0AJ8pNOR4iq2yQc=", alwaysSendToKey); + .containsExactly("/+UuD63zItL1EbjxkKUljMgG8Z1w0AJ8pNOR4iq2yQc=", alwaysSendToKey); } @Test @@ -324,16 +312,16 @@ public void overridePeers() throws Exception { Path configFile = createAndPopulatePaths(getClass().getResource("/sample-config.json")); CliResult result = - cliDelegate.execute( - "-configfile", configFile.toString(), - "-o", "peer[2].url=anotherpeer", - "--override", "peer[3].url=yetanotherpeer"); + cliDelegate.execute( + "-configfile", configFile.toString(), + "-o", "peer[2].url=anotherpeer", + "--override", "peer[3].url=yetanotherpeer"); assertThat(result).isNotNull(); assertThat(result.getConfig()).isPresent(); assertThat(result.getConfig().get().getPeers()).hasSize(4); assertThat(result.getConfig().get().getPeers().stream().map(Peer::getUrl)) - .containsExactlyInAnyOrder("anotherpeer", "yetanotherpeer", "http://bogus1.com", "http://bogus2.com"); + .containsExactlyInAnyOrder("anotherpeer", "yetanotherpeer", "http://bogus1.com", "http://bogus2.com"); } @Test @@ -342,22 +330,22 @@ public void updatingPasswordsDoesntProcessOtherOptions() throws Exception { final InputStream oldIn = System.in; final InputStream inputStream = - new ByteArrayInputStream((System.lineSeparator() + System.lineSeparator()).getBytes()); + new ByteArrayInputStream((System.lineSeparator() + System.lineSeparator()).getBytes()); System.setIn(inputStream); final KeyDataConfig startingKey = - JaxbUtil.unmarshal(getClass().getResourceAsStream("/lockedprivatekey.json"), KeyDataConfig.class); + JaxbUtil.unmarshal(getClass().getResourceAsStream("/lockedprivatekey.json"), KeyDataConfig.class); final Path key = Files.createTempFile("key", ".key"); Files.write(key, JaxbUtil.marshalToString(startingKey).getBytes()); final CliResult result = - cliDelegate.execute( - "-updatepassword", - "--keys.keyData.privateKeyPath", - key.toString(), - "--keys.passwords", - "testpassword"); + cliDelegate.execute( + "-updatepassword", + "--keys.keyData.privateKeyPath", + key.toString(), + "--keys.passwords", + "testpassword"); assertThat(result).isNotNull(); @@ -390,5 +378,4 @@ public void suppressStartupForKeygenOptionWithConfigfile() throws Exception { assertThat(cliResult.isSuppressStartup()).isTrue(); } - } diff --git a/enclave/enclave-jaxrs/src/main/java/com/quorum/tessera/enclave/rest/Main.java b/enclave/enclave-jaxrs/src/main/java/com/quorum/tessera/enclave/rest/Main.java index ea6360fb6d..a14be8d81a 100644 --- a/enclave/enclave-jaxrs/src/main/java/com/quorum/tessera/enclave/rest/Main.java +++ b/enclave/enclave-jaxrs/src/main/java/com/quorum/tessera/enclave/rest/Main.java @@ -1,6 +1,5 @@ package com.quorum.tessera.enclave.rest; -import com.quorum.tessera.cli.CliDelegate; import com.quorum.tessera.cli.CliResult; import com.quorum.tessera.cli.CliType; import com.quorum.tessera.config.CommunicationType; @@ -8,10 +7,12 @@ import com.quorum.tessera.config.ServerConfig; import com.quorum.tessera.enclave.Enclave; import com.quorum.tessera.enclave.EnclaveFactory; +import com.quorum.tessera.enclave.server.EnclaveCliAdapter; import com.quorum.tessera.server.TesseraServer; import com.quorum.tessera.server.TesseraServerFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import picocli.CommandLine; import java.util.Collections; import java.util.concurrent.CountDownLatch; @@ -21,11 +22,13 @@ public class Main { private static final Logger LOGGER = LoggerFactory.getLogger(Main.class); public static void main(String... args) throws Exception { - - System.setProperty(CliType.CLI_TYPE_KEY, CliType.ENCLAVE.name()); System.setProperty("javax.xml.bind.JAXBContextFactory", "org.eclipse.persistence.jaxb.JAXBContextFactory"); System.setProperty("javax.xml.bind.context.factory", "org.eclipse.persistence.jaxb.JAXBContextFactory"); - CliResult cliResult = CliDelegate.INSTANCE.execute(args); + + CommandLine commandLine = new CommandLine(new EnclaveCliAdapter()); + commandLine.execute(args); + CliResult cliResult = commandLine.getExecutionResult(); + if (!cliResult.getConfig().isPresent()) { System.exit(cliResult.getStatus()); } diff --git a/pom.xml b/pom.xml index d2f7bc3d24..bcf6672510 100644 --- a/pom.xml +++ b/pom.xml @@ -321,6 +321,7 @@ com/quorum/tessera/enclave/rest/Main* com/quorum/tessera/enclave/websockets/Main* + com/quorum/tessera/config/cli/DefaultCliAdapter* diff --git a/tessera-core/pom.xml b/tessera-core/pom.xml index 5bb9f8a20f..1603faf39d 100644 --- a/tessera-core/pom.xml +++ b/tessera-core/pom.xml @@ -12,8 +12,8 @@ com.jpmorgan.quorum tessera-data - - + + io.swagger swagger-annotations @@ -67,7 +67,7 @@ spring-orm runtime - + org.springframework spring-test @@ -86,14 +86,20 @@ test jar - + com.jpmorgan.quorum mock-service-locator test jar - + + com.jpmorgan.quorum + pico-cli + 0.11-SNAPSHOT + test + + tessera-core diff --git a/tessera-core/src/test/java/com/quorum/tessera/core/CoreIT.java b/tessera-core/src/test/java/com/quorum/tessera/core/CoreIT.java index d48416820b..50fca1f4a0 100644 --- a/tessera-core/src/test/java/com/quorum/tessera/core/CoreIT.java +++ b/tessera-core/src/test/java/com/quorum/tessera/core/CoreIT.java @@ -1,7 +1,7 @@ package com.quorum.tessera.core; -import com.quorum.tessera.cli.CliDelegate; +import com.quorum.tessera.picocli.PicoCliDelegate; import com.quorum.tessera.transaction.TransactionManager; import org.junit.BeforeClass; import org.junit.Test; @@ -29,7 +29,8 @@ public class CoreIT { @BeforeClass public static void onSetup() throws Exception { String configPath = CoreIT.class.getResource("/config1.json").getPath(); - CliDelegate.INSTANCE.execute("-configfile",configPath); + PicoCliDelegate picoCliDelegate = new PicoCliDelegate(); + picoCliDelegate.execute("-configfile", configPath); } @Test diff --git a/tessera-dist/tessera-launcher/src/main/java/com/quorum/tessera/launcher/Main.java b/tessera-dist/tessera-launcher/src/main/java/com/quorum/tessera/launcher/Main.java index 9e5399ccd2..c3c7796818 100644 --- a/tessera-dist/tessera-launcher/src/main/java/com/quorum/tessera/launcher/Main.java +++ b/tessera-dist/tessera-launcher/src/main/java/com/quorum/tessera/launcher/Main.java @@ -32,8 +32,6 @@ public static void main(final String... args) throws Exception { System.setProperty("javax.xml.bind.context.factory", "org.eclipse.persistence.jaxb.JAXBContextFactory"); try { - // final CliResult cliResult = CliDelegate.instance().execute(args); - PicoCliDelegate picoCliDelegate = new PicoCliDelegate(); final CliResult cliResult = picoCliDelegate.execute(args); CliDelegate.instance().setConfig(cliResult.getConfig().orElse(null)); From 2625aa353c7d9555d9a9c84ad8b6b76a2410bcb5 Mon Sep 17 00:00:00 2001 From: chris-j-h Date: Sun, 15 Dec 2019 15:09:08 +0000 Subject: [PATCH 24/41] Migrate keygen command tests to picocli --- .../parsers/EncryptorConfigParserTest.java | 251 +++++---- .../cli/parsers/KeyGenerationParserTest.java | 491 +++++++++-------- .../tessera/picocli/EncryptorOptions.java | 23 +- .../quorum/tessera/picocli/KeyGenCommand.java | 21 +- .../tessera/picocli/KeyGenCommandFactory.java | 22 + .../tessera/picocli/PicoCliDelegate.java | 5 +- .../tessera/picocli/EncryptorOptionsTest.java | 54 ++ .../tessera/picocli/KeyGenCommandTest.java | 501 ++++++++++++++++++ pom.xml | 2 + 9 files changed, 971 insertions(+), 399 deletions(-) create mode 100644 cli/pico-cli/src/main/java/com/quorum/tessera/picocli/KeyGenCommandFactory.java create mode 100644 cli/pico-cli/src/test/java/com/quorum/tessera/picocli/EncryptorOptionsTest.java create mode 100644 cli/pico-cli/src/test/java/com/quorum/tessera/picocli/KeyGenCommandTest.java diff --git a/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/parsers/EncryptorConfigParserTest.java b/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/parsers/EncryptorConfigParserTest.java index 2fcdb253f6..125b0f2224 100644 --- a/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/parsers/EncryptorConfigParserTest.java +++ b/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/parsers/EncryptorConfigParserTest.java @@ -1,19 +1,14 @@ package com.quorum.tessera.config.cli.parsers; -import com.quorum.tessera.config.Config; -import com.quorum.tessera.config.EncryptorConfig; -import com.quorum.tessera.config.EncryptorType; import com.quorum.tessera.io.FilesDelegate; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.nio.file.Path; import org.apache.commons.cli.CommandLine; -import static org.assertj.core.api.Assertions.*; import org.junit.After; import org.junit.Before; import org.junit.Test; -import static org.mockito.Mockito.*; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verifyNoMoreInteractions; public class EncryptorConfigParserTest { @@ -41,123 +36,123 @@ public void constructor() { assertThat(parser).isNotNull(); } - @Test - public void ellipticalCurveNoPropertiesDefined() throws IOException { - when(commandLine.hasOption("configfile")).thenReturn(false); - - when(commandLine.getOptionValue("encryptor.type", EncryptorType.NACL.name())) - .thenReturn(EncryptorType.EC.name()); - - EncryptorConfig result = parser.parse(commandLine); - - assertThat(result.getType()).isEqualTo(EncryptorType.EC); - assertThat(result.getProperties()).isEmpty(); - - verify(commandLine).getOptionValue("encryptor.type", EncryptorType.NACL.name()); - verify(commandLine).hasOption("configfile"); - - verify(commandLine).getOptionValue("encryptor.symmetricCipher"); - verify(commandLine).getOptionValue("encryptor.ellipticCurve"); - verify(commandLine).getOptionValue("encryptor.nonceLength"); - verify(commandLine).getOptionValue("encryptor.sharedKeyLength"); - } - - @Test - public void ellipticalCurveWithDefinedProperties() throws IOException { - - when(commandLine.getOptionValue("encryptor.type", EncryptorType.NACL.name())) - .thenReturn(EncryptorType.EC.name()); - - Config config = new Config(); - config.setEncryptor(new EncryptorConfig()); - config.getEncryptor().setType(EncryptorType.EC); - - when(commandLine.hasOption("configfile")).thenReturn(false); - - when(commandLine.getOptionValue("encryptor.type")).thenReturn(EncryptorType.EC.name()); - - when(commandLine.getOptionValue("encryptor.symmetricCipher")).thenReturn("somecipher"); - when(commandLine.getOptionValue("encryptor.ellipticCurve")).thenReturn("somecurve"); - when(commandLine.getOptionValue("encryptor.nonceLength")).thenReturn("3"); - when(commandLine.getOptionValue("encryptor.sharedKeyLength")).thenReturn("2"); - - EncryptorConfig result = parser.parse(commandLine); - - assertThat(result.getType()).isEqualTo(EncryptorType.EC); - assertThat(result.getProperties()) - .containsOnlyKeys("symmetricCipher", "ellipticCurve", "nonceLength", "sharedKeyLength"); - - assertThat(result.getProperties().get("symmetricCipher")).isEqualTo("somecipher"); - assertThat(result.getProperties().get("ellipticCurve")).isEqualTo("somecurve"); - assertThat(result.getProperties().get("nonceLength")).isEqualTo("3"); - assertThat(result.getProperties().get("sharedKeyLength")).isEqualTo("2"); - - verify(commandLine).getOptionValue("encryptor.symmetricCipher"); - verify(commandLine).getOptionValue("encryptor.ellipticCurve"); - verify(commandLine).getOptionValue("encryptor.nonceLength"); - verify(commandLine).getOptionValue("encryptor.sharedKeyLength"); - - verify(commandLine).getOptionValue("encryptor.type", EncryptorType.NACL.name()); - verify(commandLine).hasOption("configfile"); - } - - @Test - public void noEncryptorTypeDefinedAndNoConfigFile() throws IOException { - - when(commandLine.getOptionValue("encryptor.type", EncryptorType.NACL.name())) - .thenReturn(EncryptorType.NACL.name()); - when(commandLine.hasOption("configfile")).thenReturn(false); - EncryptorConfig result = parser.parse(commandLine); - assertThat(result.getType()).isEqualTo(EncryptorType.NACL); - assertThat(result.getProperties()).isEmpty(); - - verify(commandLine).getOptionValue("encryptor.type", EncryptorType.NACL.name()); - verify(commandLine).hasOption("configfile"); - } - - @Test - public void keyGenUsedDefaulIfNoTypeDefined() throws Exception { - when(commandLine.getOptionValue("encryptor.type", EncryptorType.NACL.name())) - .thenReturn(EncryptorType.NACL.name()); - when(commandLine.hasOption("configfile")).thenReturn(true); - when(commandLine.getOptionValue("configfile")).thenReturn("somepath"); - - InputStream inputStream = new ByteArrayInputStream("{}".getBytes()); - when(filesDelegate.newInputStream(any(Path.class))).thenReturn(inputStream); - - EncryptorConfig result = parser.parse(commandLine); - - assertThat(result.getType()).isEqualTo(EncryptorType.NACL); - assertThat(result.getProperties()).isEmpty(); - - verify(commandLine).getOptionValue("encryptor.type", EncryptorType.NACL.name()); - verify(commandLine).getOptionValue("configfile"); - verify(commandLine).hasOption("configfile"); - - verify(filesDelegate).newInputStream(any(Path.class)); - } - - @Test - public void encryptorConfigFromFile() throws Exception { - when(commandLine.getOptionValue("encryptor.type", EncryptorType.NACL.name())) - .thenReturn(EncryptorType.NACL.name()); - when(commandLine.hasOption("configfile")).thenReturn(true); - when(commandLine.getOptionValue("configfile")).thenReturn("somepath"); - - String jsonConfig = "{\"encryptor\": {\"type\": \"EC\"}}"; - - InputStream inputStream = new ByteArrayInputStream(jsonConfig.getBytes()); - when(filesDelegate.newInputStream(any(Path.class))).thenReturn(inputStream); - - EncryptorConfig result = parser.parse(commandLine); - - assertThat(result.getType()).isEqualTo(EncryptorType.EC); - - verify(commandLine).getOptionValue("encryptor.type", EncryptorType.NACL.name()); - verify(commandLine).hasOption("configfile"); - verify(commandLine).getOptionValue("configfile"); - verify(filesDelegate).newInputStream(any(Path.class)); - - verifyNoMoreInteractions(filesDelegate); - } + // @Test + // public void ellipticalCurveNoPropertiesDefined() throws IOException { + // when(commandLine.hasOption("configfile")).thenReturn(false); + // + // when(commandLine.getOptionValue("encryptor.type", EncryptorType.NACL.name())) + // .thenReturn(EncryptorType.EC.name()); + // + // EncryptorConfig result = parser.parse(commandLine); + // + // assertThat(result.getType()).isEqualTo(EncryptorType.EC); + // assertThat(result.getProperties()).isEmpty(); + // + // verify(commandLine).getOptionValue("encryptor.type", EncryptorType.NACL.name()); + // verify(commandLine).hasOption("configfile"); + // + // verify(commandLine).getOptionValue("encryptor.symmetricCipher"); + // verify(commandLine).getOptionValue("encryptor.ellipticCurve"); + // verify(commandLine).getOptionValue("encryptor.nonceLength"); + // verify(commandLine).getOptionValue("encryptor.sharedKeyLength"); + // } + // + // @Test + // public void ellipticalCurveWithDefinedProperties() throws IOException { + // + // when(commandLine.getOptionValue("encryptor.type", EncryptorType.NACL.name())) + // .thenReturn(EncryptorType.EC.name()); + // + // Config config = new Config(); + // config.setEncryptor(new EncryptorConfig()); + // config.getEncryptor().setType(EncryptorType.EC); + // + // when(commandLine.hasOption("configfile")).thenReturn(false); + // + // when(commandLine.getOptionValue("encryptor.type")).thenReturn(EncryptorType.EC.name()); + // + // when(commandLine.getOptionValue("encryptor.symmetricCipher")).thenReturn("somecipher"); + // when(commandLine.getOptionValue("encryptor.ellipticCurve")).thenReturn("somecurve"); + // when(commandLine.getOptionValue("encryptor.nonceLength")).thenReturn("3"); + // when(commandLine.getOptionValue("encryptor.sharedKeyLength")).thenReturn("2"); + // + // EncryptorConfig result = parser.parse(commandLine); + // + // assertThat(result.getType()).isEqualTo(EncryptorType.EC); + // assertThat(result.getProperties()) + // .containsOnlyKeys("symmetricCipher", "ellipticCurve", "nonceLength", "sharedKeyLength"); + // + // assertThat(result.getProperties().get("symmetricCipher")).isEqualTo("somecipher"); + // assertThat(result.getProperties().get("ellipticCurve")).isEqualTo("somecurve"); + // assertThat(result.getProperties().get("nonceLength")).isEqualTo("3"); + // assertThat(result.getProperties().get("sharedKeyLength")).isEqualTo("2"); + // + // verify(commandLine).getOptionValue("encryptor.symmetricCipher"); + // verify(commandLine).getOptionValue("encryptor.ellipticCurve"); + // verify(commandLine).getOptionValue("encryptor.nonceLength"); + // verify(commandLine).getOptionValue("encryptor.sharedKeyLength"); + // + // verify(commandLine).getOptionValue("encryptor.type", EncryptorType.NACL.name()); + // verify(commandLine).hasOption("configfile"); + // } + // + // @Test + // public void noEncryptorTypeDefinedAndNoConfigFile() throws IOException { + // + // when(commandLine.getOptionValue("encryptor.type", EncryptorType.NACL.name())) + // .thenReturn(EncryptorType.NACL.name()); + // when(commandLine.hasOption("configfile")).thenReturn(false); + // EncryptorConfig result = parser.parse(commandLine); + // assertThat(result.getType()).isEqualTo(EncryptorType.NACL); + // assertThat(result.getProperties()).isEmpty(); + // + // verify(commandLine).getOptionValue("encryptor.type", EncryptorType.NACL.name()); + // verify(commandLine).hasOption("configfile"); + // } + // + // @Test + // public void keyGenUsedDefaulIfNoTypeDefined() throws Exception { + // when(commandLine.getOptionValue("encryptor.type", EncryptorType.NACL.name())) + // .thenReturn(EncryptorType.NACL.name()); + // when(commandLine.hasOption("configfile")).thenReturn(true); + // when(commandLine.getOptionValue("configfile")).thenReturn("somepath"); + // + // InputStream inputStream = new ByteArrayInputStream("{}".getBytes()); + // when(filesDelegate.newInputStream(any(Path.class))).thenReturn(inputStream); + // + // EncryptorConfig result = parser.parse(commandLine); + // + // assertThat(result.getType()).isEqualTo(EncryptorType.NACL); + // assertThat(result.getProperties()).isEmpty(); + // + // verify(commandLine).getOptionValue("encryptor.type", EncryptorType.NACL.name()); + // verify(commandLine).getOptionValue("configfile"); + // verify(commandLine).hasOption("configfile"); + // + // verify(filesDelegate).newInputStream(any(Path.class)); + // } + // + // @Test + // public void encryptorConfigFromFile() throws Exception { + // when(commandLine.getOptionValue("encryptor.type", EncryptorType.NACL.name())) + // .thenReturn(EncryptorType.NACL.name()); + // when(commandLine.hasOption("configfile")).thenReturn(true); + // when(commandLine.getOptionValue("configfile")).thenReturn("somepath"); + // + // String jsonConfig = "{\"encryptor\": {\"type\": \"EC\"}}"; + // + // InputStream inputStream = new ByteArrayInputStream(jsonConfig.getBytes()); + // when(filesDelegate.newInputStream(any(Path.class))).thenReturn(inputStream); + // + // EncryptorConfig result = parser.parse(commandLine); + // + // assertThat(result.getType()).isEqualTo(EncryptorType.EC); + // + // verify(commandLine).getOptionValue("encryptor.type", EncryptorType.NACL.name()); + // verify(commandLine).hasOption("configfile"); + // verify(commandLine).getOptionValue("configfile"); + // verify(filesDelegate).newInputStream(any(Path.class)); + // + // verifyNoMoreInteractions(filesDelegate); + // } } diff --git a/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/parsers/KeyGenerationParserTest.java b/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/parsers/KeyGenerationParserTest.java index 00e1c283f5..deb743dadb 100644 --- a/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/parsers/KeyGenerationParserTest.java +++ b/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/parsers/KeyGenerationParserTest.java @@ -1,29 +1,15 @@ package com.quorum.tessera.config.cli.parsers; -import com.quorum.tessera.cli.CliException; -import com.quorum.tessera.config.ArgonOptions; import com.quorum.tessera.config.EncryptorConfig; import com.quorum.tessera.config.EncryptorType; -import com.quorum.tessera.config.cli.keys.MockKeyGeneratorFactory; -import com.quorum.tessera.config.keypairs.ConfigKeyPair; -import com.quorum.tessera.key.generation.KeyGenerator; import org.apache.commons.cli.CommandLine; import org.junit.Test; -import org.mockito.ArgumentCaptor; -import javax.validation.ConstraintViolation; -import javax.validation.ConstraintViolationException; import java.nio.file.Files; import java.nio.file.Path; import java.util.Collections; -import java.util.Iterator; -import java.util.List; -import java.util.Set; import java.util.UUID; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.catchThrowable; -import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.*; public class KeyGenerationParserTest { @@ -39,98 +25,98 @@ public class KeyGenerationParserTest { private CommandLine commandLine = mock(CommandLine.class); - @Test - public void notProvidingArgonOptionsGivesNull() throws Exception { - final Path keyLocation = Files.createTempFile(UUID.randomUUID().toString(), ""); - - when(commandLine.hasOption("keygen")).thenReturn(true); - when(commandLine.hasOption("filename")).thenReturn(true); - when(commandLine.getOptionValue("filename")).thenReturn(keyLocation.toString()); - when(commandLine.hasOption("keygenconfig")).thenReturn(false); - - final List result = parser.parse(commandLine); - - assertThat(result).isNotNull().hasSize(1); - - final KeyGenerator keyGenerator = MockKeyGeneratorFactory.getMockKeyGenerator(); - - final ArgumentCaptor captor = ArgumentCaptor.forClass(ArgonOptions.class); - verify(keyGenerator).generate(eq(keyLocation.toString()), captor.capture(), eq(null)); - - assertThat(captor.getAllValues()).hasSize(1); - assertThat(captor.getValue()).isNull(); - } - - @Test - public void providingArgonOptionsGetSentCorrectly() throws Exception { - final String options = "{\"variant\": \"id\",\"memory\": 100,\"iterations\": 7,\"parallelism\": 22}"; - final Path argonOptions = Files.createTempFile(UUID.randomUUID().toString(), ""); - Files.write(argonOptions, options.getBytes()); - - final Path keyLocation = Files.createTempFile(UUID.randomUUID().toString(), ""); - - when(commandLine.hasOption("keygen")).thenReturn(true); - when(commandLine.hasOption("filename")).thenReturn(true); - when(commandLine.getOptionValue("filename")).thenReturn(keyLocation.toString()); - when(commandLine.hasOption("keygenconfig")).thenReturn(true); - when(commandLine.getOptionValue("keygenconfig")).thenReturn(argonOptions.toString()); - - final List result = parser.parse(commandLine); - - assertThat(result).isNotNull().hasSize(1); - - final KeyGenerator keyGenerator = MockKeyGeneratorFactory.getMockKeyGenerator(); - - final ArgumentCaptor captor = ArgumentCaptor.forClass(ArgonOptions.class); - verify(keyGenerator).generate(eq(keyLocation.toString()), captor.capture(), eq(null)); - - assertThat(captor.getAllValues()).hasSize(1); - assertThat(captor.getValue().getAlgorithm()).isEqualTo("id"); - assertThat(captor.getValue().getIterations()).isEqualTo(7); - assertThat(captor.getValue().getMemory()).isEqualTo(100); - assertThat(captor.getValue().getParallelism()).isEqualTo(22); - } - - @Test - public void keygenWithNoName() throws Exception { - - when(commandLine.hasOption("keygen")).thenReturn(true); - when(commandLine.hasOption("filename")).thenReturn(false); - when(commandLine.hasOption("keygenconfig")).thenReturn(false); - - final List result = this.parser.parse(commandLine); - - assertThat(result).isNotNull().hasSize(1); - - final KeyGenerator keyGenerator = MockKeyGeneratorFactory.getMockKeyGenerator(); - verify(keyGenerator).generate("", null, null); - } - - @Test - public void keygenNotGivenReturnsEmptyList() throws Exception { - - when(commandLine.hasOption("keygen")).thenReturn(false); - when(commandLine.hasOption("filename")).thenReturn(false); - when(commandLine.hasOption("keygenconfig")).thenReturn(false); - - final List result = this.parser.parse(commandLine); - - assertThat(result).isNotNull().hasSize(0); - - final KeyGenerator keyGenerator = MockKeyGeneratorFactory.getMockKeyGenerator(); - verifyZeroInteractions(keyGenerator); - } - - @Test - public void vaultOptionsNotUsedIfNoneProvided() throws Exception { - when(commandLine.hasOption("keygenvaulttype")).thenReturn(false); - when(commandLine.hasOption("keygenvaulturl")).thenReturn(false); - - this.parser.parse(commandLine); - - verify(commandLine, times(0)).getOptionValue("keygenvaulttype"); - verify(commandLine, times(0)).getOptionValue("keygenvaulturl"); - } + // @Test + // public void notProvidingArgonOptionsGivesNull() throws Exception { + // final Path keyLocation = Files.createTempFile(UUID.randomUUID().toString(), ""); + // + // when(commandLine.hasOption("keygen")).thenReturn(true); + // when(commandLine.hasOption("filename")).thenReturn(true); + // when(commandLine.getOptionValue("filename")).thenReturn(keyLocation.toString()); + // when(commandLine.hasOption("keygenconfig")).thenReturn(false); + // + // final List result = parser.parse(commandLine); + // + // assertThat(result).isNotNull().hasSize(1); + // + // final KeyGenerator keyGenerator = MockKeyGeneratorFactory.getMockKeyGenerator(); + // + // final ArgumentCaptor captor = ArgumentCaptor.forClass(ArgonOptions.class); + // verify(keyGenerator).generate(eq(keyLocation.toString()), captor.capture(), eq(null)); + // + // assertThat(captor.getAllValues()).hasSize(1); + // assertThat(captor.getValue()).isNull(); + // } + // + // @Test + // public void providingArgonOptionsGetSentCorrectly() throws Exception { + // final String options = "{\"variant\": \"id\",\"memory\": 100,\"iterations\": 7,\"parallelism\": 22}"; + // final Path argonOptions = Files.createTempFile(UUID.randomUUID().toString(), ""); + // Files.write(argonOptions, options.getBytes()); + // + // final Path keyLocation = Files.createTempFile(UUID.randomUUID().toString(), ""); + // + // when(commandLine.hasOption("keygen")).thenReturn(true); + // when(commandLine.hasOption("filename")).thenReturn(true); + // when(commandLine.getOptionValue("filename")).thenReturn(keyLocation.toString()); + // when(commandLine.hasOption("keygenconfig")).thenReturn(true); + // when(commandLine.getOptionValue("keygenconfig")).thenReturn(argonOptions.toString()); + // + // final List result = parser.parse(commandLine); + // + // assertThat(result).isNotNull().hasSize(1); + // + // final KeyGenerator keyGenerator = MockKeyGeneratorFactory.getMockKeyGenerator(); + // + // final ArgumentCaptor captor = ArgumentCaptor.forClass(ArgonOptions.class); + // verify(keyGenerator).generate(eq(keyLocation.toString()), captor.capture(), eq(null)); + // + // assertThat(captor.getAllValues()).hasSize(1); + // assertThat(captor.getValue().getAlgorithm()).isEqualTo("id"); + // assertThat(captor.getValue().getIterations()).isEqualTo(7); + // assertThat(captor.getValue().getMemory()).isEqualTo(100); + // assertThat(captor.getValue().getParallelism()).isEqualTo(22); + // } + // + // @Test + // public void keygenWithNoName() throws Exception { + // + // when(commandLine.hasOption("keygen")).thenReturn(true); + // when(commandLine.hasOption("filename")).thenReturn(false); + // when(commandLine.hasOption("keygenconfig")).thenReturn(false); + // + // final List result = this.parser.parse(commandLine); + // + // assertThat(result).isNotNull().hasSize(1); + // + // final KeyGenerator keyGenerator = MockKeyGeneratorFactory.getMockKeyGenerator(); + // verify(keyGenerator).generate("", null, null); + // } + // + // @Test + // public void keygenNotGivenReturnsEmptyList() throws Exception { + // + // when(commandLine.hasOption("keygen")).thenReturn(false); + // when(commandLine.hasOption("filename")).thenReturn(false); + // when(commandLine.hasOption("keygenconfig")).thenReturn(false); + // + // final List result = this.parser.parse(commandLine); + // + // assertThat(result).isNotNull().hasSize(0); + // + // final KeyGenerator keyGenerator = MockKeyGeneratorFactory.getMockKeyGenerator(); + // verifyZeroInteractions(keyGenerator); + // } + // + // @Test + // public void vaultOptionsNotUsedIfNoneProvided() throws Exception { + // when(commandLine.hasOption("keygenvaulttype")).thenReturn(false); + // when(commandLine.hasOption("keygenvaulturl")).thenReturn(false); + // + // this.parser.parse(commandLine); + // + // verify(commandLine, times(0)).getOptionValue("keygenvaulttype"); + // verify(commandLine, times(0)).getOptionValue("keygenvaulturl"); + // } @Test public void ifAllVaultOptionsProvidedAndValidThenOkay() throws Exception { @@ -145,152 +131,153 @@ public void ifAllVaultOptionsProvidedAndValidThenOkay() throws Exception { verify(commandLine, times(1)).getOptionValue("keygenvaulturl"); } - @Test - public void ifAzureVaultTypeOptionProvidedButNoVaultUrlThenValidationException() { - when(commandLine.hasOption("keygenvaulttype")).thenReturn(true); - when(commandLine.hasOption("keygenvaulturl")).thenReturn(false); - when(commandLine.getOptionValue("keygenvaulttype")).thenReturn("AZURE"); - - Throwable ex = catchThrowable(() -> this.parser.parse(commandLine)); - - verify(commandLine, times(1)).getOptionValue("keygenvaulttype"); - verify(commandLine, times(1)).getOptionValue("keygenvaulturl"); - - assertThat(ex).isInstanceOf(ConstraintViolationException.class); - - Set> violations = ((ConstraintViolationException) ex).getConstraintViolations(); - - assertThat(violations.size()).isEqualTo(1); - - ConstraintViolation violation = violations.iterator().next(); - - assertThat(violation.getPropertyPath().toString()).isEqualTo("url"); - assertThat(violation.getMessage()).isEqualTo("may not be null"); - } - - @Test - public void ifHashicorpVaultTypeOptionAndFilenameProvidedButNoVaultUrlThenValidationException() { - when(commandLine.hasOption("keygenvaulttype")).thenReturn(true); - when(commandLine.hasOption("keygenvaulturl")).thenReturn(false); - when(commandLine.getOptionValue("keygenvaulttype")).thenReturn("HASHICORP"); - when(commandLine.hasOption("filename")).thenReturn(true); - when(commandLine.getOptionValue("filename")).thenReturn("secret/path"); - - Throwable ex = catchThrowable(() -> this.parser.parse(commandLine)); - - verify(commandLine, times(1)).getOptionValue("keygenvaulttype"); - verify(commandLine, times(1)).getOptionValue("keygenvaulturl"); - - assertThat(ex).isInstanceOf(ConstraintViolationException.class); - - Set> violations = ((ConstraintViolationException) ex).getConstraintViolations(); - - assertThat(violations.size()).isEqualTo(1); - - ConstraintViolation violation = violations.iterator().next(); - - assertThat(violation.getPropertyPath().toString()).isEqualTo("url"); - assertThat(violation.getMessage()).isEqualTo("may not be null"); - } - - @Test - public void ifOnlyVaultUrlOptionProvidedThenException() { - when(commandLine.hasOption("keygenvaulttype")).thenReturn(false); - when(commandLine.hasOption("keygenvaulturl")).thenReturn(true); - when(commandLine.getOptionValue("keygenvaulturl")).thenReturn("someurl"); - - Throwable ex = catchThrowable(() -> this.parser.parse(commandLine)); - - assertThat(ex).isInstanceOf(CliException.class); - assertThat(ex.getMessage()).isEqualTo("Key vault type either not provided or not recognised"); - } - - @Test - public void ifVaultOptionsProvidedButTypeUnknownThenException() { - when(commandLine.hasOption("keygenvaulttype")).thenReturn(true); - when(commandLine.hasOption("keygenvaulturl")).thenReturn(true); - when(commandLine.getOptionValue("keygenvaulttype")).thenReturn("unknown"); - - Throwable ex = catchThrowable(() -> this.parser.parse(commandLine)); - - assertThat(ex).isInstanceOf(CliException.class); - assertThat(ex.getMessage()).isEqualTo("Key vault type either not provided or not recognised"); - } - - @Test - public void noFilenameProvidedWhenUsingHashicorpVaultThrowsException() { - when(commandLine.hasOption("keygenvaulttype")).thenReturn(true); - when(commandLine.hasOption("keygenvaulturl")).thenReturn(true); - when(commandLine.getOptionValue("keygenvaulttype")).thenReturn("HASHICORP"); - when(commandLine.hasOption("filename")).thenReturn(false); - - Throwable ex = catchThrowable(() -> this.parser.parse(commandLine)); - - assertThat(ex).isInstanceOf(CliException.class); - assertThat(ex.getMessage()) - .isEqualTo("At least one -filename must be provided when saving generated keys in a Hashicorp Vault"); - } - - @Test - public void ifAllVaultOptionsAndFilenameProvidedForHashicorpThenOkay() throws Exception { - when(commandLine.hasOption("keygenvaulttype")).thenReturn(true); - when(commandLine.hasOption("keygenvaulturl")).thenReturn(true); - when(commandLine.hasOption("filename")).thenReturn(true); - when(commandLine.getOptionValue("keygenvaulturl")).thenReturn("someurl"); - when(commandLine.getOptionValue("keygenvaulttype")).thenReturn("HASHICORP"); - when(commandLine.getOptionValue("filename")).thenReturn("secret/path"); - when(commandLine.getOptionValue("keygenvaultapprole")).thenReturn("approle"); - when(commandLine.getOptionValue("keygenvaultsecretengine")).thenReturn("secretEngine"); - - Path tempPath = Files.createTempFile(UUID.randomUUID().toString(), ""); - tempPath.toFile().deleteOnExit(); - - when(commandLine.getOptionValue("keygenvaultkeystore")).thenReturn(tempPath.toString()); - when(commandLine.getOptionValue("keygenvaulttruststore")).thenReturn(tempPath.toString()); - - this.parser.parse(commandLine); - - verify(commandLine, times(1)).getOptionValue("keygenvaulttype"); - verify(commandLine, times(1)).getOptionValue("keygenvaulturl"); - verify(commandLine, times(1)).getOptionValue("keygenvaultapprole"); - verify(commandLine, times(1)).getOptionValue("keygenvaultkeystore"); - verify(commandLine, times(1)).getOptionValue("keygenvaulttruststore"); - verify(commandLine, times(1)).getOptionValue("keygenvaultsecretengine"); - } - - @Test - public void ifHashicorpTlsOptionsProvidedButPathsDontExistThenValidationException() { - when(commandLine.hasOption("keygenvaulttype")).thenReturn(true); - when(commandLine.hasOption("keygenvaulturl")).thenReturn(true); - when(commandLine.getOptionValue("keygenvaulttype")).thenReturn("HASHICORP"); - when(commandLine.getOptionValue("keygenvaulturl")).thenReturn("someurl"); - when(commandLine.hasOption("filename")).thenReturn(true); - when(commandLine.getOptionValue("filename")).thenReturn("secret/path"); - when(commandLine.getOptionValue("keygenvaultsecretengine")).thenReturn("secretEngine"); - when(commandLine.getOptionValue("keygenvaultapprole")).thenReturn("approle"); - when(commandLine.getOptionValue("keygenvaultkeystore")).thenReturn("non/existent/path"); - when(commandLine.getOptionValue("keygenvaulttruststore")).thenReturn("non/existent/path"); - - Throwable ex = catchThrowable(() -> this.parser.parse(commandLine)); - - verify(commandLine, times(1)).getOptionValue("keygenvaulttype"); - verify(commandLine, times(1)).getOptionValue("keygenvaulturl"); - verify(commandLine, times(1)).getOptionValue("keygenvaultapprole"); - verify(commandLine, times(1)).getOptionValue("keygenvaultkeystore"); - verify(commandLine, times(1)).getOptionValue("keygenvaulttruststore"); - verify(commandLine, times(1)).getOptionValue("keygenvaultsecretengine"); - - assertThat(ex).isInstanceOf(ConstraintViolationException.class); - - Set> violations = ((ConstraintViolationException) ex).getConstraintViolations(); - - assertThat(violations.size()).isEqualTo(2); - - Iterator> iterator = violations.iterator(); - - assertThat(iterator.next().getMessage()).isEqualTo("File does not exist"); - assertThat(iterator.next().getMessage()).isEqualTo("File does not exist"); - } + // @Test + // public void ifAzureVaultTypeOptionProvidedButNoVaultUrlThenValidationException() { + // when(commandLine.hasOption("keygenvaulttype")).thenReturn(true); + // when(commandLine.hasOption("keygenvaulturl")).thenReturn(false); + // when(commandLine.getOptionValue("keygenvaulttype")).thenReturn("AZURE"); + // + // Throwable ex = catchThrowable(() -> this.parser.parse(commandLine)); + // + // verify(commandLine, times(1)).getOptionValue("keygenvaulttype"); + // verify(commandLine, times(1)).getOptionValue("keygenvaulturl"); + // + // assertThat(ex).isInstanceOf(ConstraintViolationException.class); + // + // Set> violations = ((ConstraintViolationException) ex).getConstraintViolations(); + // + // assertThat(violations.size()).isEqualTo(1); + // + // ConstraintViolation violation = violations.iterator().next(); + // + // assertThat(violation.getPropertyPath().toString()).isEqualTo("url"); + // assertThat(violation.getMessage()).isEqualTo("may not be null"); + // } + // + // @Test + // public void ifHashicorpVaultTypeOptionAndFilenameProvidedButNoVaultUrlThenValidationException() { + // when(commandLine.hasOption("keygenvaulttype")).thenReturn(true); + // when(commandLine.hasOption("keygenvaulturl")).thenReturn(false); + // when(commandLine.getOptionValue("keygenvaulttype")).thenReturn("HASHICORP"); + // when(commandLine.hasOption("filename")).thenReturn(true); + // when(commandLine.getOptionValue("filename")).thenReturn("secret/path"); + // + // Throwable ex = catchThrowable(() -> this.parser.parse(commandLine)); + // + // verify(commandLine, times(1)).getOptionValue("keygenvaulttype"); + // verify(commandLine, times(1)).getOptionValue("keygenvaulturl"); + // + // assertThat(ex).isInstanceOf(ConstraintViolationException.class); + // + // Set> violations = ((ConstraintViolationException) ex).getConstraintViolations(); + // + // assertThat(violations.size()).isEqualTo(1); + // + // ConstraintViolation violation = violations.iterator().next(); + // + // assertThat(violation.getPropertyPath().toString()).isEqualTo("url"); + // assertThat(violation.getMessage()).isEqualTo("may not be null"); + // } + // + // @Test + // public void ifOnlyVaultUrlOptionProvidedThenException() { + // when(commandLine.hasOption("keygenvaulttype")).thenReturn(false); + // when(commandLine.hasOption("keygenvaulturl")).thenReturn(true); + // when(commandLine.getOptionValue("keygenvaulturl")).thenReturn("someurl"); + // + // Throwable ex = catchThrowable(() -> this.parser.parse(commandLine)); + // + // assertThat(ex).isInstanceOf(CliException.class); + // assertThat(ex.getMessage()).isEqualTo("Key vault type either not provided or not recognised"); + // } + // + // @Test + // public void ifVaultOptionsProvidedButTypeUnknownThenException() { + // when(commandLine.hasOption("keygenvaulttype")).thenReturn(true); + // when(commandLine.hasOption("keygenvaulturl")).thenReturn(true); + // when(commandLine.getOptionValue("keygenvaulttype")).thenReturn("unknown"); + // + // Throwable ex = catchThrowable(() -> this.parser.parse(commandLine)); + // + // assertThat(ex).isInstanceOf(CliException.class); + // assertThat(ex.getMessage()).isEqualTo("Key vault type either not provided or not recognised"); + // } + // + // @Test + // public void noFilenameProvidedWhenUsingHashicorpVaultThrowsException() { + // when(commandLine.hasOption("keygenvaulttype")).thenReturn(true); + // when(commandLine.hasOption("keygenvaulturl")).thenReturn(true); + // when(commandLine.getOptionValue("keygenvaulttype")).thenReturn("HASHICORP"); + // when(commandLine.hasOption("filename")).thenReturn(false); + // + // Throwable ex = catchThrowable(() -> this.parser.parse(commandLine)); + // + // assertThat(ex).isInstanceOf(CliException.class); + // assertThat(ex.getMessage()) + // .isEqualTo("At least one -filename must be provided when saving generated keys in a Hashicorp + // Vault"); + // } + // + // @Test + // public void ifAllVaultOptionsAndFilenameProvidedForHashicorpThenOkay() throws Exception { + // when(commandLine.hasOption("keygenvaulttype")).thenReturn(true); + // when(commandLine.hasOption("keygenvaulturl")).thenReturn(true); + // when(commandLine.hasOption("filename")).thenReturn(true); + // when(commandLine.getOptionValue("keygenvaulturl")).thenReturn("someurl"); + // when(commandLine.getOptionValue("keygenvaulttype")).thenReturn("HASHICORP"); + // when(commandLine.getOptionValue("filename")).thenReturn("secret/path"); + // when(commandLine.getOptionValue("keygenvaultapprole")).thenReturn("approle"); + // when(commandLine.getOptionValue("keygenvaultsecretengine")).thenReturn("secretEngine"); + // + // Path tempPath = Files.createTempFile(UUID.randomUUID().toString(), ""); + // tempPath.toFile().deleteOnExit(); + // + // when(commandLine.getOptionValue("keygenvaultkeystore")).thenReturn(tempPath.toString()); + // when(commandLine.getOptionValue("keygenvaulttruststore")).thenReturn(tempPath.toString()); + // + // this.parser.parse(commandLine); + // + // verify(commandLine, times(1)).getOptionValue("keygenvaulttype"); + // verify(commandLine, times(1)).getOptionValue("keygenvaulturl"); + // verify(commandLine, times(1)).getOptionValue("keygenvaultapprole"); + // verify(commandLine, times(1)).getOptionValue("keygenvaultkeystore"); + // verify(commandLine, times(1)).getOptionValue("keygenvaulttruststore"); + // verify(commandLine, times(1)).getOptionValue("keygenvaultsecretengine"); + // } + // + // @Test + // public void ifHashicorpTlsOptionsProvidedButPathsDontExistThenValidationException() { + // when(commandLine.hasOption("keygenvaulttype")).thenReturn(true); + // when(commandLine.hasOption("keygenvaulturl")).thenReturn(true); + // when(commandLine.getOptionValue("keygenvaulttype")).thenReturn("HASHICORP"); + // when(commandLine.getOptionValue("keygenvaulturl")).thenReturn("someurl"); + // when(commandLine.hasOption("filename")).thenReturn(true); + // when(commandLine.getOptionValue("filename")).thenReturn("secret/path"); + // when(commandLine.getOptionValue("keygenvaultsecretengine")).thenReturn("secretEngine"); + // when(commandLine.getOptionValue("keygenvaultapprole")).thenReturn("approle"); + // when(commandLine.getOptionValue("keygenvaultkeystore")).thenReturn("non/existent/path"); + // when(commandLine.getOptionValue("keygenvaulttruststore")).thenReturn("non/existent/path"); + // + // Throwable ex = catchThrowable(() -> this.parser.parse(commandLine)); + // + // verify(commandLine, times(1)).getOptionValue("keygenvaulttype"); + // verify(commandLine, times(1)).getOptionValue("keygenvaulturl"); + // verify(commandLine, times(1)).getOptionValue("keygenvaultapprole"); + // verify(commandLine, times(1)).getOptionValue("keygenvaultkeystore"); + // verify(commandLine, times(1)).getOptionValue("keygenvaulttruststore"); + // verify(commandLine, times(1)).getOptionValue("keygenvaultsecretengine"); + // + // assertThat(ex).isInstanceOf(ConstraintViolationException.class); + // + // Set> violations = ((ConstraintViolationException) ex).getConstraintViolations(); + // + // assertThat(violations.size()).isEqualTo(2); + // + // Iterator> iterator = violations.iterator(); + // + // assertThat(iterator.next().getMessage()).isEqualTo("File does not exist"); + // assertThat(iterator.next().getMessage()).isEqualTo("File does not exist"); + // } @Test public void lowercaseVaultTypeIsOkay() throws Exception { diff --git a/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/EncryptorOptions.java b/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/EncryptorOptions.java index 7bf9c4dd89..b4649d35dd 100644 --- a/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/EncryptorOptions.java +++ b/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/EncryptorOptions.java @@ -9,26 +9,26 @@ import java.util.Objects; import java.util.Optional; -public class EncryptorOptions { +class EncryptorOptions { @CommandLine.Option( names = {"--encryptor.type"}, description = "Valid values: ${COMPLETION-CANDIDATES}") - public EncryptorType type; + EncryptorType type; @CommandLine.Option(names = {"--encryptor.symmetricCipher"}) - public String symmetricCipher; + String symmetricCipher; @CommandLine.Option(names = {"--encryptor.ellipticCurve"}) - public String ellipticCurve; + String ellipticCurve; @CommandLine.Option(names = {"--encryptor.nonceLength"}) - public String nonceLength; + String nonceLength; @CommandLine.Option(names = {"--encryptor.sharedKeyLength"}) - public String sharedKeyLength; + String sharedKeyLength; - public EncryptorConfig parseEncryptorConfig() { + EncryptorConfig parseEncryptorConfig() { final EncryptorConfig encryptorConfig = new EncryptorConfig(); // we set the default here instead of in the option annotation as enum values cannot be used in annotationss @@ -36,19 +36,18 @@ public EncryptorConfig parseEncryptorConfig() { type = EncryptorType.NACL; } - encryptorConfig.setType(type); - + Map properties = new HashMap<>(); if (type == EncryptorType.EC) { - Map properties = new HashMap<>(); Optional.ofNullable(symmetricCipher).ifPresent(v -> properties.put("symmetricCipher", v)); Optional.ofNullable(ellipticCurve).ifPresent(v -> properties.put("ellipticCurve", v)); Optional.ofNullable(nonceLength).ifPresent(v -> properties.put("nonceLength", v)); Optional.ofNullable(sharedKeyLength).ifPresent(v -> properties.put("sharedKeyLength", v)); - - encryptorConfig.setProperties(properties); } + encryptorConfig.setType(type); + encryptorConfig.setProperties(properties); + return encryptorConfig; } } diff --git a/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/KeyGenCommand.java b/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/KeyGenCommand.java index a4c0452ce6..5b13a0bde0 100644 --- a/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/KeyGenCommand.java +++ b/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/KeyGenCommand.java @@ -31,7 +31,7 @@ abbreviateSynopsis = true, subcommands = {CommandLine.HelpCommand.class}) public class KeyGenCommand implements Callable { - private final KeyGeneratorFactory factory = KeyGeneratorFactory.newFactory(); + private KeyGeneratorFactory factory; private final Validator validator = Validation.byDefaultProvider().configure().ignoreXmlConfiguration().buildValidatorFactory().getValidator(); @@ -55,7 +55,6 @@ public class KeyGenCommand implements Callable { names = {"--vault.type", "-keygenvaulttype"}, description = "Specify the key vault provider the generated key is to be saved in. If not set, the key will be encrypted and stored on the local filesystem. Valid values: ${COMPLETION-CANDIDATES})") - // TODO(cjh) get possible enum values to show in the usage public KeyVaultType vaultType; @CommandLine.Option( @@ -98,8 +97,12 @@ public class KeyGenCommand implements Callable { @CommandLine.Mixin public EncryptorOptions encryptorOptions; + KeyGenCommand(KeyGeneratorFactory keyGeneratorFactory) { + this.factory = keyGeneratorFactory; + } + @Override - public CliResult call() throws Exception { + public CliResult call() { final EncryptorConfig encryptorConfig; if (Optional.ofNullable(config).map(Config::getEncryptor).isPresent()) { @@ -113,7 +116,11 @@ public CliResult call() throws Exception { final KeyGenerator generator = factory.create(keyVaultConfig, encryptorConfig); - keyOut.forEach(name -> generator.generate(name, encryptionConfig, keyVaultOptions)); + if (Objects.isNull(keyOut) || keyOut.isEmpty()) { + generator.generate("", encryptionConfig, keyVaultOptions); + } else { + keyOut.forEach(name -> generator.generate(name, encryptionConfig, keyVaultOptions)); + } return new CliResult(0, true, null); } @@ -131,6 +138,10 @@ private Optional keyVaultConfig() { return Optional.empty(); } + if (Objects.isNull(vaultType)) { + throw new CliException("Key vault type either not provided or not recognised"); + } + final KeyVaultConfig keyVaultConfig; if (vaultType.equals(KeyVaultType.AZURE)) { @@ -143,7 +154,7 @@ private Optional keyVaultConfig() { throw new ConstraintViolationException(violations); } } else { - if (keyOut.size() == 0) { + if (Objects.isNull(keyOut) || keyOut.isEmpty()) { throw new CliException( "At least one -filename must be provided when saving generated keys in a Hashicorp Vault"); } diff --git a/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/KeyGenCommandFactory.java b/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/KeyGenCommandFactory.java new file mode 100644 index 0000000000..287362d7de --- /dev/null +++ b/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/KeyGenCommandFactory.java @@ -0,0 +1,22 @@ +package com.quorum.tessera.picocli; + +import com.quorum.tessera.key.generation.KeyGeneratorFactory; +import picocli.CommandLine; + +public class KeyGenCommandFactory implements CommandLine.IFactory { + + @Override + public K create(Class cls) throws Exception { + try { + if (cls != KeyGenCommand.class) { + throw new RuntimeException(this.getClass().getSimpleName() + " cannot create instance of type " + cls.getSimpleName()); + } + + KeyGeneratorFactory keyGeneratorFactory = KeyGeneratorFactory.newFactory(); + + return (K) new KeyGenCommand(keyGeneratorFactory); + } catch (Exception e) { + return CommandLine.defaultFactory().create(cls); // fallback if missing + } + } +} diff --git a/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/PicoCliDelegate.java b/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/PicoCliDelegate.java index 9287941e21..151320b909 100644 --- a/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/PicoCliDelegate.java +++ b/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/PicoCliDelegate.java @@ -32,7 +32,7 @@ import static java.nio.file.StandardOpenOption.CREATE; import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING; -// TODO(cjh) make sure recent changes to old CLI are included where needed +// TODO(cjh) make sure recent changes to old CLI (e.g. parser behaviour) are included where needed public class PicoCliDelegate { private static final Logger LOGGER = LoggerFactory.getLogger(PicoCliDelegate.class); @@ -54,7 +54,8 @@ public CliResult execute(String... args) throws Exception { final CLIExceptionCapturer mapper = new CLIExceptionCapturer(); - CommandLine keyGenCommandLine = new CommandLine(KeyGenCommand.class); + final CommandLine.IFactory keyGenCommandFactory = new KeyGenCommandFactory(); + CommandLine keyGenCommandLine = new CommandLine(KeyGenCommand.class, keyGenCommandFactory); final CommandLine.IFactory keyUpdateCommandFactory = new KeyUpdateCommandFactory(); CommandLine keyUpdateCommandLine = new CommandLine(KeyUpdateCommand.class, keyUpdateCommandFactory); diff --git a/cli/pico-cli/src/test/java/com/quorum/tessera/picocli/EncryptorOptionsTest.java b/cli/pico-cli/src/test/java/com/quorum/tessera/picocli/EncryptorOptionsTest.java new file mode 100644 index 0000000000..2bb7b3fa16 --- /dev/null +++ b/cli/pico-cli/src/test/java/com/quorum/tessera/picocli/EncryptorOptionsTest.java @@ -0,0 +1,54 @@ +package com.quorum.tessera.picocli; + +import com.quorum.tessera.config.EncryptorConfig; +import com.quorum.tessera.config.EncryptorType; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class EncryptorOptionsTest { + + @Test + public void ellipticalCurveNoPropertiesDefined() { + EncryptorOptions encryptorOptions = new EncryptorOptions(); + encryptorOptions.type = EncryptorType.EC; + + EncryptorConfig result = encryptorOptions.parseEncryptorConfig(); + + assertThat(result).isNotNull(); + assertThat(result.getType()).isEqualTo(EncryptorType.EC); + assertThat(result.getProperties()).isEmpty(); + } + + @Test + public void ellipticalCurveWithDefinedProperties() { + EncryptorOptions encryptorOptions = new EncryptorOptions(); + encryptorOptions.type = EncryptorType.EC; + encryptorOptions.symmetricCipher = "somecipher"; + encryptorOptions.ellipticCurve = "somecurve"; + encryptorOptions.nonceLength = "3"; + encryptorOptions.sharedKeyLength = "2"; + + EncryptorConfig result = encryptorOptions.parseEncryptorConfig(); + + assertThat(result.getType()).isEqualTo(EncryptorType.EC); + assertThat(result.getProperties()) + .containsOnlyKeys("symmetricCipher", "ellipticCurve", "nonceLength", "sharedKeyLength"); + + assertThat(result.getProperties().get("symmetricCipher")).isEqualTo("somecipher"); + assertThat(result.getProperties().get("ellipticCurve")).isEqualTo("somecurve"); + assertThat(result.getProperties().get("nonceLength")).isEqualTo("3"); + assertThat(result.getProperties().get("sharedKeyLength")).isEqualTo("2"); + } + + @Test + public void encryptorTypeDefaultsToNACL() { + EncryptorOptions encryptorOptions = new EncryptorOptions(); + + EncryptorConfig result = encryptorOptions.parseEncryptorConfig(); + + assertThat(result.getType()).isEqualTo(EncryptorType.NACL); + assertThat(result.getProperties()).isEmpty(); + } + +} diff --git a/cli/pico-cli/src/test/java/com/quorum/tessera/picocli/KeyGenCommandTest.java b/cli/pico-cli/src/test/java/com/quorum/tessera/picocli/KeyGenCommandTest.java new file mode 100644 index 0000000000..3be6238f8c --- /dev/null +++ b/cli/pico-cli/src/test/java/com/quorum/tessera/picocli/KeyGenCommandTest.java @@ -0,0 +1,501 @@ +package com.quorum.tessera.picocli; + +import com.quorum.tessera.cli.CliException; +import com.quorum.tessera.cli.CliResult; +import com.quorum.tessera.config.*; +import com.quorum.tessera.key.generation.KeyGenerator; +import com.quorum.tessera.key.generation.KeyGeneratorFactory; +import com.quorum.tessera.key.generation.KeyVaultOptions; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import javax.validation.ConstraintViolation; +import javax.validation.ConstraintViolationException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.*; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.catchThrowable; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +public class KeyGenCommandTest { + + private KeyGenCommand command; + + private KeyGeneratorFactory keyGeneratorFactory; + + private final CliResult wantResult = new CliResult(0, true, null); + + @Before + public void onSetup() { + keyGeneratorFactory = mock(KeyGeneratorFactory.class); + command = new KeyGenCommand(keyGeneratorFactory); + } + + @After + public void onTearDown() { + verifyNoMoreInteractions(keyGeneratorFactory); + } + + @Test + public void usesDefaultEncryptorConfigIfNoneInConfig() { + final EncryptorConfig encryptorConfig = new EncryptorConfig(); + final Map properties = new HashMap<>(); + encryptorConfig.setType(EncryptorType.NACL); + encryptorConfig.setProperties(properties); + + final EncryptorOptions encryptorOptions = mock(EncryptorOptions.class); + when(encryptorOptions.parseEncryptorConfig()).thenReturn(encryptorConfig); + + command.encryptorOptions = encryptorOptions; + + final KeyGenerator keyGenerator = mock(KeyGenerator.class); + when(keyGeneratorFactory.create(any(), any())).thenReturn(keyGenerator); + + CliResult result = command.call(); + + // verify the correct config is used + verify(keyGeneratorFactory).create(null, encryptorConfig); + assertThat(result).isEqualToComparingFieldByField(wantResult); + + verify(encryptorOptions).parseEncryptorConfig(); + verify(keyGenerator).generate(anyString(), any(), any()); + verifyNoMoreInteractions(encryptorOptions, keyGenerator); + } + + @Test + public void doNotUseEncryptorOptionsIfConfigHasEncryptorConfig() { + final EncryptorOptions encryptorOptions = mock(EncryptorOptions.class); + + final EncryptorConfig encryptorConfig = new EncryptorConfig(); + final Map properties = new HashMap<>(); + encryptorConfig.setType(EncryptorType.NACL); + encryptorConfig.setProperties(properties); + final Config config = new Config(); + config.setEncryptor(encryptorConfig); + + command.encryptorOptions = encryptorOptions; + command.config = config; + + final KeyGenerator keyGenerator = mock(KeyGenerator.class); + when(keyGeneratorFactory.create(any(), any())).thenReturn(keyGenerator); + + CliResult result = command.call(); + + // verify the correct config is used + verify(keyGeneratorFactory).create(null, encryptorConfig); + assertThat(result).isEqualToComparingFieldByField(wantResult); + + verify(keyGenerator).generate(anyString(), any(), any()); + verifyNoMoreInteractions(encryptorOptions, keyGenerator); + } + + @Test + public void noKeyEncryptionConfigUsesDefault() { + final ArgonOptions defaultArgonOptions = null; + + final EncryptorConfig encryptorConfig = new EncryptorConfig(); + final Map properties = new HashMap<>(); + encryptorConfig.setType(EncryptorType.NACL); + encryptorConfig.setProperties(properties); + + final EncryptorOptions encryptorOptions = mock(EncryptorOptions.class); + when(encryptorOptions.parseEncryptorConfig()).thenReturn(encryptorConfig); + + command.encryptorOptions = encryptorOptions; + + final KeyGenerator keyGenerator = mock(KeyGenerator.class); + when(keyGeneratorFactory.create(any(), any())).thenReturn(keyGenerator); + + CliResult result = command.call(); + + // verify the correct config is used + verify(keyGeneratorFactory).create(null, encryptorConfig); + assertThat(result).isEqualToComparingFieldByField(wantResult); + verify(keyGenerator).generate(anyString(), eq(defaultArgonOptions), any()); + + verify(encryptorOptions).parseEncryptorConfig(); + verifyNoMoreInteractions(encryptorOptions, keyGenerator); + } + + @Test + public void providedKeyEncryptionConfigIsUsed() { + final ArgonOptions argonOptions = new ArgonOptions(); + + final EncryptorConfig encryptorConfig = new EncryptorConfig(); + final Map properties = new HashMap<>(); + encryptorConfig.setType(EncryptorType.NACL); + encryptorConfig.setProperties(properties); + + final EncryptorOptions encryptorOptions = mock(EncryptorOptions.class); + when(encryptorOptions.parseEncryptorConfig()).thenReturn(encryptorConfig); + + command.encryptorOptions = encryptorOptions; + command.encryptionConfig = argonOptions; + + final KeyGenerator keyGenerator = mock(KeyGenerator.class); + when(keyGeneratorFactory.create(any(), any())).thenReturn(keyGenerator); + + CliResult result = command.call(); + + // verify the correct config is used + verify(keyGeneratorFactory).create(null, encryptorConfig); + assertThat(result).isEqualToComparingFieldByField(wantResult); + verify(keyGenerator).generate(anyString(), eq(argonOptions), any()); + + verify(encryptorOptions).parseEncryptorConfig(); + verifyNoMoreInteractions(encryptorOptions, keyGenerator); + } + + @Test + public void noKeyOutputPathUsesDefault() { + final String defaultOutputPath = ""; + + final EncryptorConfig encryptorConfig = new EncryptorConfig(); + final Map properties = new HashMap<>(); + encryptorConfig.setType(EncryptorType.NACL); + encryptorConfig.setProperties(properties); + + final EncryptorOptions encryptorOptions = mock(EncryptorOptions.class); + when(encryptorOptions.parseEncryptorConfig()).thenReturn(encryptorConfig); + + command.encryptorOptions = encryptorOptions; + + final KeyGenerator keyGenerator = mock(KeyGenerator.class); + when(keyGeneratorFactory.create(any(), any())).thenReturn(keyGenerator); + + CliResult result = command.call(); + + // verify the correct config is used + verify(keyGeneratorFactory).create(null, encryptorConfig); + assertThat(result).isEqualToComparingFieldByField(wantResult); + verify(keyGenerator).generate(eq(defaultOutputPath), any(), any()); + + verify(encryptorOptions).parseEncryptorConfig(); + verifyNoMoreInteractions(encryptorOptions, keyGenerator); + } + + @Test + public void providedKeyOutputPathIsUsed() { + final String outputPath = "mynewkey"; + + final EncryptorConfig encryptorConfig = new EncryptorConfig(); + final Map properties = new HashMap<>(); + encryptorConfig.setType(EncryptorType.NACL); + encryptorConfig.setProperties(properties); + + final EncryptorOptions encryptorOptions = mock(EncryptorOptions.class); + when(encryptorOptions.parseEncryptorConfig()).thenReturn(encryptorConfig); + + command.encryptorOptions = encryptorOptions; + command.keyOut = Arrays.asList(outputPath); + + final KeyGenerator keyGenerator = mock(KeyGenerator.class); + when(keyGeneratorFactory.create(any(), any())).thenReturn(keyGenerator); + + CliResult result = command.call(); + + // verify the correct config is used + verify(keyGeneratorFactory).create(null, encryptorConfig); + assertThat(result).isEqualToComparingFieldByField(wantResult); + verify(keyGenerator).generate(eq(outputPath), any(), any()); + + verify(encryptorOptions).parseEncryptorConfig(); + verifyNoMoreInteractions(encryptorOptions, keyGenerator); + } + + @Test + public void multipleKeyOutputPathsGeneratesMultipleKeys() { + final String outputPath = "mynewkey"; + final String otherOutputPath = "myothernewkey"; + + final EncryptorConfig encryptorConfig = new EncryptorConfig(); + final Map properties = new HashMap<>(); + encryptorConfig.setType(EncryptorType.NACL); + encryptorConfig.setProperties(properties); + + final EncryptorOptions encryptorOptions = mock(EncryptorOptions.class); + when(encryptorOptions.parseEncryptorConfig()).thenReturn(encryptorConfig); + + command.encryptorOptions = encryptorOptions; + command.keyOut = Arrays.asList(outputPath, otherOutputPath); + + final KeyGenerator keyGenerator = mock(KeyGenerator.class); + when(keyGeneratorFactory.create(any(), any())).thenReturn(keyGenerator); + + CliResult result = command.call(); + + // verify the correct config is used + verify(keyGeneratorFactory).create(null, encryptorConfig); + assertThat(result).isEqualToComparingFieldByField(wantResult); + verify(keyGenerator).generate(eq(outputPath), any(), any()); + verify(keyGenerator).generate(eq(otherOutputPath), any(), any()); + + verify(encryptorOptions).parseEncryptorConfig(); + verifyNoMoreInteractions(encryptorOptions, keyGenerator); + } + + @Test + public void noKeyVaultOptionsUsesDefault() { + final KeyVaultOptions defaultKeyVaultOptions = null; + + final EncryptorConfig encryptorConfig = new EncryptorConfig(); + final Map properties = new HashMap<>(); + encryptorConfig.setType(EncryptorType.NACL); + encryptorConfig.setProperties(properties); + + final EncryptorOptions encryptorOptions = mock(EncryptorOptions.class); + when(encryptorOptions.parseEncryptorConfig()).thenReturn(encryptorConfig); + + command.encryptorOptions = encryptorOptions; + + final KeyGenerator keyGenerator = mock(KeyGenerator.class); + when(keyGeneratorFactory.create(any(), any())).thenReturn(keyGenerator); + + CliResult result = command.call(); + + // verify the correct config is used + verify(keyGeneratorFactory).create(null, encryptorConfig); + assertThat(result).isEqualToComparingFieldByField(wantResult); + verify(keyGenerator).generate(anyString(), any(), eq(defaultKeyVaultOptions)); + + verify(encryptorOptions).parseEncryptorConfig(); + verifyNoMoreInteractions(encryptorOptions, keyGenerator); + } + + @Test + public void providedKeyVaultOptionsAreUsed() { + final String keyVaultOptionValue = "somevalue"; + final KeyVaultOptions keyVaultOptions = new KeyVaultOptions(keyVaultOptionValue); + + final EncryptorConfig encryptorConfig = new EncryptorConfig(); + final Map properties = new HashMap<>(); + encryptorConfig.setType(EncryptorType.NACL); + encryptorConfig.setProperties(properties); + + final EncryptorOptions encryptorOptions = mock(EncryptorOptions.class); + when(encryptorOptions.parseEncryptorConfig()).thenReturn(encryptorConfig); + + command.encryptorOptions = encryptorOptions; + command.hashicorpSecretEnginePath = keyVaultOptionValue; + + final KeyGenerator keyGenerator = mock(KeyGenerator.class); + when(keyGeneratorFactory.create(any(), any())).thenReturn(keyGenerator); + + CliResult result = command.call(); + + // verify the correct config is used + verify(keyGeneratorFactory).create(null, encryptorConfig); + assertThat(result).isEqualToComparingFieldByField(wantResult); + verify(keyGenerator).generate(anyString(), any(), refEq(keyVaultOptions)); + + verify(encryptorOptions).parseEncryptorConfig(); + verifyNoMoreInteractions(encryptorOptions, keyGenerator); + } + + @Test + public void validAzureKeyVaultConfig() { + final KeyVaultConfig keyVaultConfig = new AzureKeyVaultConfig("someurl"); + + final EncryptorOptions encryptorOptions = mock(EncryptorOptions.class); + when(encryptorOptions.parseEncryptorConfig()).thenReturn(null); + + command.encryptorOptions = encryptorOptions; + command.vaultType = KeyVaultType.AZURE; + command.vaultUrl = "someurl"; + + final KeyGenerator keyGenerator = mock(KeyGenerator.class); + when(keyGeneratorFactory.create(any(), any())).thenReturn(keyGenerator); + + CliResult result = command.call(); + + // verify the correct config is used + verify(keyGeneratorFactory).create(refEq(keyVaultConfig), isNull()); + assertThat(result).isEqualToComparingFieldByField(wantResult); + + verify(keyGenerator).generate(anyString(), any(), any()); + verify(encryptorOptions).parseEncryptorConfig(); + verifyNoMoreInteractions(encryptorOptions, keyGenerator); + } + + @Test + public void invalidAzureKeyVaultConfigThrowsException() { + final EncryptorOptions encryptorOptions = mock(EncryptorOptions.class); + when(encryptorOptions.parseEncryptorConfig()).thenReturn(null); + + command.encryptorOptions = encryptorOptions; + command.vaultType = KeyVaultType.AZURE; + + Throwable ex = catchThrowable(() -> command.call()); + + assertThat(ex).isInstanceOf(ConstraintViolationException.class); + + Set> violations = ((ConstraintViolationException) ex).getConstraintViolations(); + + assertThat(violations.size()).isEqualTo(1); + + ConstraintViolation violation = violations.iterator().next(); + + assertThat(violation.getPropertyPath().toString()).isEqualTo("url"); + assertThat(violation.getMessage()).isEqualTo("may not be null"); + + verify(encryptorOptions).parseEncryptorConfig(); + verifyNoMoreInteractions(encryptorOptions); + } + + @Test + public void validHashicorpKeyVaultConfig() throws Exception { + final String vaultUrl = "someurl"; + final String approlePath = "someapprole"; + Path tempPath = Files.createTempFile(UUID.randomUUID().toString(), ""); + tempPath.toFile().deleteOnExit(); + + final KeyVaultConfig keyVaultConfig = new HashicorpKeyVaultConfig(vaultUrl, approlePath, tempPath, tempPath); + + final EncryptorOptions encryptorOptions = mock(EncryptorOptions.class); + when(encryptorOptions.parseEncryptorConfig()).thenReturn(null); + + command.encryptorOptions = encryptorOptions; + command.vaultType = KeyVaultType.HASHICORP; + command.vaultUrl = vaultUrl; + command.hashicorpApprolePath = approlePath; + command.hashicorpTlsKeystore = tempPath; + command.hashicorpTlsTruststore = tempPath; + command.keyOut = Collections.singletonList("out"); + + final KeyGenerator keyGenerator = mock(KeyGenerator.class); + when(keyGeneratorFactory.create(any(), any())).thenReturn(keyGenerator); + + CliResult result = command.call(); + + // verify the correct config is used + verify(keyGeneratorFactory).create(refEq(keyVaultConfig), isNull()); + assertThat(result).isEqualToComparingFieldByField(wantResult); + + verify(keyGenerator).generate(anyString(), any(), any()); + verify(encryptorOptions).parseEncryptorConfig(); + verifyNoMoreInteractions(encryptorOptions, keyGenerator); + } + + @Test + public void hashicorpKeyVaultConfigNoOutputPathsThrowsException() throws Exception { + final String vaultUrl = "someurl"; + final String approlePath = "someapprole"; + Path tempPath = Files.createTempFile(UUID.randomUUID().toString(), ""); + tempPath.toFile().deleteOnExit(); + + final EncryptorOptions encryptorOptions = mock(EncryptorOptions.class); + when(encryptorOptions.parseEncryptorConfig()).thenReturn(null); + + command.encryptorOptions = encryptorOptions; + command.vaultType = KeyVaultType.HASHICORP; + command.vaultUrl = vaultUrl; + command.hashicorpApprolePath = approlePath; + command.hashicorpTlsKeystore = tempPath; + command.hashicorpTlsTruststore = tempPath; + + final KeyGenerator keyGenerator = mock(KeyGenerator.class); + when(keyGeneratorFactory.create(any(), any())).thenReturn(keyGenerator); + + Throwable ex = catchThrowable(() -> command.call()); + + assertThat(ex).isInstanceOf(CliException.class); + assertThat(ex) + .hasMessage("At least one -filename must be provided when saving generated keys in a Hashicorp Vault"); + + verify(encryptorOptions).parseEncryptorConfig(); + verifyNoMoreInteractions(encryptorOptions); + } + + @Test + public void invalidHashicorpKeyVaultConfigThrowsException() { + final EncryptorOptions encryptorOptions = mock(EncryptorOptions.class); + when(encryptorOptions.parseEncryptorConfig()).thenReturn(null); + + command.encryptorOptions = encryptorOptions; + + command.vaultType = KeyVaultType.HASHICORP; + command.keyOut = Collections.singletonList("out"); + + final KeyGenerator keyGenerator = mock(KeyGenerator.class); + when(keyGeneratorFactory.create(any(), any())).thenReturn(keyGenerator); + + Throwable ex = catchThrowable(() -> command.call()); + + assertThat(ex).isInstanceOf(ConstraintViolationException.class); + + Set> violations = ((ConstraintViolationException) ex).getConstraintViolations(); + + assertThat(violations.size()).isEqualTo(1); + + ConstraintViolation violation = violations.iterator().next(); + + assertThat(violation.getPropertyPath().toString()).isEqualTo("url"); + assertThat(violation.getMessage()).isEqualTo("may not be null"); + + verify(encryptorOptions).parseEncryptorConfig(); + verifyNoMoreInteractions(encryptorOptions); + } + + @Test + public void hashicorpTlsPathsDontExistThrowsException() throws Exception { + final String vaultUrl = "someurl"; + final String approlePath = "someapprole"; + final Path nonExistentPath = Paths.get(UUID.randomUUID().toString()); + + final EncryptorOptions encryptorOptions = mock(EncryptorOptions.class); + when(encryptorOptions.parseEncryptorConfig()).thenReturn(null); + + command.encryptorOptions = encryptorOptions; + command.vaultType = KeyVaultType.HASHICORP; + command.vaultUrl = vaultUrl; + command.hashicorpApprolePath = approlePath; + command.hashicorpTlsKeystore = nonExistentPath; + command.hashicorpTlsTruststore = nonExistentPath; + command.keyOut = Collections.singletonList("out"); + + final KeyGenerator keyGenerator = mock(KeyGenerator.class); + when(keyGeneratorFactory.create(any(), any())).thenReturn(keyGenerator); + + Throwable ex = catchThrowable(() -> command.call()); + + assertThat(ex).isInstanceOf(ConstraintViolationException.class); + + Set> violations = ((ConstraintViolationException) ex).getConstraintViolations(); + + assertThat(violations.size()).isEqualTo(2); + + Iterator> iterator = violations.iterator(); + + assertThat(iterator.next().getMessage()).isEqualTo("File does not exist"); + assertThat(iterator.next().getMessage()).isEqualTo("File does not exist"); + + // verify the correct config is used + verify(encryptorOptions).parseEncryptorConfig(); + verifyNoMoreInteractions(encryptorOptions, keyGenerator); + } + + @Test + public void vaultUrlButNoVaultTypeThrowsException() { + final EncryptorOptions encryptorOptions = mock(EncryptorOptions.class); + when(encryptorOptions.parseEncryptorConfig()).thenReturn(null); + + command.encryptorOptions = encryptorOptions; + command.vaultUrl = "someurl"; + + final KeyGenerator keyGenerator = mock(KeyGenerator.class); + when(keyGeneratorFactory.create(any(), any())).thenReturn(keyGenerator); + + Throwable ex = catchThrowable(() -> command.call()); + + assertThat(ex).isInstanceOf(CliException.class); + assertThat(ex.getMessage()).isEqualTo("Key vault type either not provided or not recognised"); + + verify(encryptorOptions).parseEncryptorConfig(); + verifyNoMoreInteractions(encryptorOptions); + } +} diff --git a/pom.xml b/pom.xml index bcf6672510..40d797d971 100644 --- a/pom.xml +++ b/pom.xml @@ -322,6 +322,8 @@ com/quorum/tessera/enclave/rest/Main* com/quorum/tessera/enclave/websockets/Main* com/quorum/tessera/config/cli/DefaultCliAdapter* + com/quorum/tessera/config/cli/parsers/KeyGenerationParser* + com/quorum/tessera/config/cli/parsers/EncryptorConfigParser* From 9f941704ff5e69816f27c9758bb96b619089104b Mon Sep 17 00:00:00 2001 From: chris-j-h Date: Mon, 16 Dec 2019 09:57:07 +0000 Subject: [PATCH 25/41] Migrate update password command tests to picocli --- .../cli/parsers/KeyUpdateParserTest.java | 550 +++++++++--------- .../tessera/picocli/KeyUpdateCommand.java | 12 +- .../tessera/picocli/KeyUpdateCommandTest.java | 376 ++++++++++++ 3 files changed, 652 insertions(+), 286 deletions(-) create mode 100644 cli/pico-cli/src/test/java/com/quorum/tessera/picocli/KeyUpdateCommandTest.java diff --git a/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/parsers/KeyUpdateParserTest.java b/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/parsers/KeyUpdateParserTest.java index 69b5b95fba..37022e7ab8 100644 --- a/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/parsers/KeyUpdateParserTest.java +++ b/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/parsers/KeyUpdateParserTest.java @@ -1,27 +1,15 @@ package com.quorum.tessera.config.cli.parsers; -import com.quorum.tessera.config.*; +import com.quorum.tessera.config.ArgonOptions; import com.quorum.tessera.config.keys.KeyEncryptor; -import com.quorum.tessera.config.util.JaxbUtil; -import com.quorum.tessera.encryption.PrivateKey; import com.quorum.tessera.passwords.PasswordReader; import org.apache.commons.cli.*; import org.junit.Before; import org.junit.Test; -import javax.xml.bind.UnmarshalException; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Arrays; -import java.util.Base64; -import java.util.List; - -import static java.util.Collections.emptyList; -import static java.util.Collections.singletonList; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.catchThrowable; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; public class KeyUpdateParserTest { @@ -67,270 +55,270 @@ public void noArgonOptionsGivenHasDefaults() throws ParseException { assertThat(argonOptions.getIterations()).isEqualTo(10); } - @Test - public void argonOptionsGivenHasOverrides() throws ParseException { - final String[] args = - new String[] { - "--keys.keyData.config.data.aopts.algorithm", "d", - "--keys.keyData.config.data.aopts.memory", "100", - "--keys.keyData.config.data.aopts.iterations", "100", - "--keys.keyData.config.data.aopts.parallelism", "100" - }; - final CommandLine commandLine = new DefaultParser().parse(options, args); - - final ArgonOptions argonOptions = KeyUpdateParser.argonOptions(commandLine); - - assertThat(argonOptions.getAlgorithm()).isEqualTo("d"); - assertThat(argonOptions.getParallelism()).isEqualTo(100); - assertThat(argonOptions.getMemory()).isEqualTo(100); - assertThat(argonOptions.getIterations()).isEqualTo(100); - } - - // Password reading tests - @Test - public void inlinePasswordParsed() throws ParseException, IOException { - final String[] args = new String[] {"--keys.passwords", "pass"}; - final CommandLine commandLine = new DefaultParser().parse(options, args); - - final List passwords = KeyUpdateParser.passwords(commandLine); - - assertThat(passwords).isNotNull().hasSize(1).containsExactly("pass"); - } - - @Test - public void passwordFileParsedAndRead() throws ParseException, IOException { - final Path passwordFile = Files.createTempFile("passwords", ".txt"); - Files.write(passwordFile, "passwordInsideFile\nsecondPassword".getBytes()); - - final String[] args = new String[] {"--keys.passwordFile", passwordFile.toAbsolutePath().toString()}; - final CommandLine commandLine = new DefaultParser().parse(options, args); - - final List passwords = KeyUpdateParser.passwords(commandLine); - - assertThat(passwords).isNotNull().hasSize(2).containsExactly("passwordInsideFile", "secondPassword"); - } - - @Test - public void passwordFileThrowsErrorIfCantBeRead() throws ParseException { - final String[] args = new String[] {"--keys.passwordFile", "/tmp/passwords.txt"}; - final CommandLine commandLine = new DefaultParser().parse(options, args); - - final Throwable throwable = catchThrowable(() -> KeyUpdateParser.passwords(commandLine)); - - assertThat(throwable).isNotNull().isInstanceOf(IOException.class); - } - - @Test - public void emptyListGivenForNoPasswords() throws ParseException, IOException { - final String[] args = new String[] {}; - final CommandLine commandLine = new DefaultParser().parse(options, args); - - final List passwords = KeyUpdateParser.passwords(commandLine); - - assertThat(passwords).isNotNull().isEmpty(); - } - - // key file tests - @Test - public void noPrivateKeyGivenThrowsError() throws ParseException { - final String[] args = new String[] {}; - final CommandLine commandLine = new DefaultParser().parse(options, args); - - final Throwable throwable = catchThrowable(() -> KeyUpdateParser.privateKeyPath(commandLine)); - - assertThat(throwable) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("Private key path cannot be null when updating key password"); - } - - @Test - public void cantReadPrivateKeyThrowsError() throws ParseException { - final String[] args = new String[] {"--keys.keyData.privateKeyPath", "/tmp/nonexisting.txt"}; - final CommandLine commandLine = new DefaultParser().parse(options, args); - - final Throwable throwable = catchThrowable(() -> KeyUpdateParser.privateKeyPath(commandLine)); - - assertThat(throwable).isInstanceOf(IllegalArgumentException.class); - } - - @Test - public void privateKeyExistsReturnsPath() throws ParseException, IOException { - final Path key = Files.createTempFile("key", ".key"); - - final String[] args = new String[] {"--keys.keyData.privateKeyPath", key.toString()}; - final CommandLine commandLine = new DefaultParser().parse(options, args); - - final Path path = KeyUpdateParser.privateKeyPath(commandLine); - - assertThat(path).isEqualTo(key); - } - - // key fetching tests - @Test - public void unlockedKeyReturnedProperly() { - final KeyDataConfig kdc = - new KeyDataConfig( - new PrivateKeyData("/+UuD63zItL1EbjxkKUljMgG8Z1w0AJ8pNOR4iq2yQc=", null, null, null, null), - PrivateKeyType.UNLOCKED); - - final PrivateKey key = this.parser.getExistingKey(kdc, emptyList()); - - String encodedKeyValue = Base64.getEncoder().encodeToString(key.getKeyBytes()); - - assertThat(encodedKeyValue).isEqualTo("/+UuD63zItL1EbjxkKUljMgG8Z1w0AJ8pNOR4iq2yQc="); - } - - @Test - public void lockedKeyFailsWithNoPasswordsMatching() { - - final KeyDataConfig kdc = - new KeyDataConfig( - new PrivateKeyData( - null, - "dwixVoY+pOI2FMuu4k0jLqN/naQiTzWe", - "JoPVq9G6NdOb+Ugv+HnUeA==", - "6Jd/MXn29fk6jcrFYGPb75l7sDJae06I3Y1Op+bZSZqlYXsMpa/8lLE29H0sX3yw", - new ArgonOptions("id", 1, 1024, 1)), - PrivateKeyType.LOCKED); - - final Throwable throwable = catchThrowable(() -> this.parser.getExistingKey(kdc, singletonList("wrong"))); - - assertThat(throwable) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("Locked key but no valid password given"); - } - - @Test - public void lockedKeySucceedsWithPasswordsMatching() { - PrivateKeyData privateKeyData = - new PrivateKeyData( - null, - "dwixVoY+pOI2FMuu4k0jLqN/naQiTzWe", - "JoPVq9G6NdOb+Ugv+HnUeA==", - "6Jd/MXn29fk6jcrFYGPb75l7sDJae06I3Y1Op+bZSZqlYXsMpa/8lLE29H0sX3yw", - new ArgonOptions("id", 1, 1024, 1)); - - final KeyDataConfig kdc = - new KeyDataConfig( - new PrivateKeyData( - null, - "dwixVoY+pOI2FMuu4k0jLqN/naQiTzWe", - "JoPVq9G6NdOb+Ugv+HnUeA==", - "6Jd/MXn29fk6jcrFYGPb75l7sDJae06I3Y1Op+bZSZqlYXsMpa/8lLE29H0sX3yw", - new ArgonOptions("id", 1, 1024, 1)), - PrivateKeyType.LOCKED); - - PrivateKey privateKey = mock(PrivateKey.class); - when(privateKey.getKeyBytes()).thenReturn("SUCCESS".getBytes()); - when(keyEncryptor.decryptPrivateKey(privateKeyData, "testpassword")).thenReturn(privateKey); - - final PrivateKey result = this.parser.getExistingKey(kdc, singletonList("testpassword")); - - assertThat(result.getKeyBytes()).isEqualTo("SUCCESS".getBytes()); - } - - @Test - public void lockedKeySucceedsWithAtleastOnePasswordsMatching() { - - PrivateKeyData privateKeyData = mock(PrivateKeyData.class); - - final KeyDataConfig kdc = new KeyDataConfig(privateKeyData, PrivateKeyType.LOCKED); - - PrivateKey privateKey = mock(PrivateKey.class); - when(privateKey.getKeyBytes()).thenReturn("SUCCESS".getBytes()); - - when(keyEncryptor.decryptPrivateKey(privateKeyData, "wrong")).thenReturn(null); - when(keyEncryptor.decryptPrivateKey(privateKeyData, "testpassword")).thenReturn(privateKey); - - final PrivateKey key = this.parser.getExistingKey(kdc, Arrays.asList("wrong", "testpassword")); - - assertThat(key).isNotNull(); - - assertThat(key.getKeyBytes()).isEqualTo("SUCCESS".getBytes()); - } - - @Test - public void loadingMalformedKeyfileThrowsError() throws IOException, ParseException { - final Path key = Files.createTempFile("key", ".key"); - Files.write(key, "BAD JSON DATA".getBytes()); - - final String[] args = new String[] {"--keys.keyData.privateKeyPath", key.toString()}; - final CommandLine commandLine = new DefaultParser().parse(options, args); - - final Throwable throwable = catchThrowable(() -> this.parser.parse(commandLine)); - - assertThat(throwable).isInstanceOf(ConfigException.class).hasCauseExactlyInstanceOf(UnmarshalException.class); - } - - @Test - public void keyGetsUpdated() throws IOException, ParseException { - final KeyDataConfig startingKey = - JaxbUtil.unmarshal(getClass().getResourceAsStream("/lockedprivatekey.json"), KeyDataConfig.class); - - final Path key = Files.createTempFile("key", ".key"); - Files.write(key, JaxbUtil.marshalToString(startingKey).getBytes()); - - final String[] args = - new String[] { - "--keys.keyData.privateKeyPath", key.toString(), - "--keys.passwords", "testpassword", - "--keys.keyData.config.data.aopts.algorithm", "id", - "--keys.keyData.config.data.aopts.memory", "2048", - "--keys.keyData.config.data.aopts.iterations", "1", - "--keys.keyData.config.data.aopts.parallelism", "1" - }; - final CommandLine commandLine = new DefaultParser().parse(options, args); - - PrivateKey privatekey = mock(PrivateKey.class); - when(keyEncryptor.decryptPrivateKey(any(PrivateKeyData.class), anyString())).thenReturn(privatekey); - - PrivateKeyData privateKeyData = mock(PrivateKeyData.class); - - when(keyEncryptor.encryptPrivateKey(any(PrivateKey.class), anyString(), any(ArgonOptions.class))) - .thenReturn(privateKeyData); - - this.parser.parse(commandLine); - - final KeyDataConfig endingKey = JaxbUtil.unmarshal(Files.newInputStream(key), KeyDataConfig.class); - - assertThat(endingKey.getSbox()).isNotEqualTo(startingKey.getSbox()); - assertThat(endingKey.getSnonce()).isNotEqualTo(startingKey.getSnonce()); - assertThat(endingKey.getAsalt()).isNotEqualTo(startingKey.getAsalt()); - } - - @Test - public void keyGetsUpdatedToNoPassword() throws IOException, ParseException { - final KeyDataConfig startingKey = - JaxbUtil.unmarshal(getClass().getResourceAsStream("/lockedprivatekey.json"), KeyDataConfig.class); - - when(passwordReader.requestUserPassword()).thenReturn(""); - - final Path key = Files.createTempFile("key", ".key"); - Files.write(key, JaxbUtil.marshalToString(startingKey).getBytes()); - - final String[] args = - new String[] { - "--keys.keyData.privateKeyPath", key.toString(), - "--keys.passwords", "testpassword", - "--keys.keyData.config.data.aopts.algorithm", "id", - "--keys.keyData.config.data.aopts.memory", "2048", - "--keys.keyData.config.data.aopts.iterations", "1", - "--keys.keyData.config.data.aopts.parallelism", "1" - }; - final CommandLine commandLine = new DefaultParser().parse(options, args); - - byte[] privateKeyData = "SOME PRIVATE DATA".getBytes(); - PrivateKey privateKey = PrivateKey.from(privateKeyData); - when(keyEncryptor.decryptPrivateKey(any(PrivateKeyData.class), anyString())).thenReturn(privateKey); - - this.parser.parse(commandLine); - - final KeyDataConfig endingKey = JaxbUtil.unmarshal(Files.newInputStream(key), KeyDataConfig.class); - - assertThat(endingKey.getSbox()).isNotEqualTo(startingKey.getSbox()); - assertThat(endingKey.getSnonce()).isNotEqualTo(startingKey.getSnonce()); - assertThat(endingKey.getAsalt()).isNotEqualTo(startingKey.getAsalt()); - assertThat(endingKey.getPrivateKeyData().getValue()) - .isEqualTo(Base64.getEncoder().encodeToString(privateKeyData)); - } + // @Test + // public void argonOptionsGivenHasOverrides() throws ParseException { + // final String[] args = + // new String[] { + // "--keys.keyData.config.data.aopts.algorithm", "d", + // "--keys.keyData.config.data.aopts.memory", "100", + // "--keys.keyData.config.data.aopts.iterations", "100", + // "--keys.keyData.config.data.aopts.parallelism", "100" + // }; + // final CommandLine commandLine = new DefaultParser().parse(options, args); + // + // final ArgonOptions argonOptions = KeyUpdateParser.argonOptions(commandLine); + // + // assertThat(argonOptions.getAlgorithm()).isEqualTo("d"); + // assertThat(argonOptions.getParallelism()).isEqualTo(100); + // assertThat(argonOptions.getMemory()).isEqualTo(100); + // assertThat(argonOptions.getIterations()).isEqualTo(100); + // } + // + // // Password reading tests + // @Test + // public void inlinePasswordParsed() throws ParseException, IOException { + // final String[] args = new String[] {"--keys.passwords", "pass"}; + // final CommandLine commandLine = new DefaultParser().parse(options, args); + // + // final List passwords = KeyUpdateParser.passwords(commandLine); + // + // assertThat(passwords).isNotNull().hasSize(1).containsExactly("pass"); + // } + // + // @Test + // public void passwordFileParsedAndRead() throws ParseException, IOException { + // final Path passwordFile = Files.createTempFile("passwords", ".txt"); + // Files.write(passwordFile, "passwordInsideFile\nsecondPassword".getBytes()); + // + // final String[] args = new String[] {"--keys.passwordFile", passwordFile.toAbsolutePath().toString()}; + // final CommandLine commandLine = new DefaultParser().parse(options, args); + // + // final List passwords = KeyUpdateParser.passwords(commandLine); + // + // assertThat(passwords).isNotNull().hasSize(2).containsExactly("passwordInsideFile", "secondPassword"); + // } + // + // @Test + // public void passwordFileThrowsErrorIfCantBeRead() throws ParseException { + // final String[] args = new String[] {"--keys.passwordFile", "/tmp/passwords.txt"}; + // final CommandLine commandLine = new DefaultParser().parse(options, args); + // + // final Throwable throwable = catchThrowable(() -> KeyUpdateParser.passwords(commandLine)); + // + // assertThat(throwable).isNotNull().isInstanceOf(IOException.class); + // } + // + // @Test + // public void emptyListGivenForNoPasswords() throws ParseException, IOException { + // final String[] args = new String[] {}; + // final CommandLine commandLine = new DefaultParser().parse(options, args); + // + // final List passwords = KeyUpdateParser.passwords(commandLine); + // + // assertThat(passwords).isNotNull().isEmpty(); + // } +// +// // key file tests +// @Test +// public void noPrivateKeyGivenThrowsError() throws ParseException { +// final String[] args = new String[] {}; +// final CommandLine commandLine = new DefaultParser().parse(options, args); +// +// final Throwable throwable = catchThrowable(() -> KeyUpdateParser.privateKeyPath(commandLine)); +// +// assertThat(throwable) +// .isInstanceOf(IllegalArgumentException.class) +// .hasMessage("Private key path cannot be null when updating key password"); +// } +// +// @Test +// public void cantReadPrivateKeyThrowsError() throws ParseException { +// final String[] args = new String[] {"--keys.keyData.privateKeyPath", "/tmp/nonexisting.txt"}; +// final CommandLine commandLine = new DefaultParser().parse(options, args); +// +// final Throwable throwable = catchThrowable(() -> KeyUpdateParser.privateKeyPath(commandLine)); +// +// assertThat(throwable).isInstanceOf(IllegalArgumentException.class); +// } +// +// @Test +// public void privateKeyExistsReturnsPath() throws ParseException, IOException { +// final Path key = Files.createTempFile("key", ".key"); +// +// final String[] args = new String[] {"--keys.keyData.privateKeyPath", key.toString()}; +// final CommandLine commandLine = new DefaultParser().parse(options, args); +// +// final Path path = KeyUpdateParser.privateKeyPath(commandLine); +// +// assertThat(path).isEqualTo(key); +// } +// +// // key fetching tests +// @Test +// public void unlockedKeyReturnedProperly() { +// final KeyDataConfig kdc = +// new KeyDataConfig( +// new PrivateKeyData("/+UuD63zItL1EbjxkKUljMgG8Z1w0AJ8pNOR4iq2yQc=", null, null, null, null), +// PrivateKeyType.UNLOCKED); +// +// final PrivateKey key = this.parser.getExistingKey(kdc, emptyList()); +// +// String encodedKeyValue = Base64.getEncoder().encodeToString(key.getKeyBytes()); +// +// assertThat(encodedKeyValue).isEqualTo("/+UuD63zItL1EbjxkKUljMgG8Z1w0AJ8pNOR4iq2yQc="); +// } +// +// @Test +// public void lockedKeyFailsWithNoPasswordsMatching() { +// +// final KeyDataConfig kdc = +// new KeyDataConfig( +// new PrivateKeyData( +// null, +// "dwixVoY+pOI2FMuu4k0jLqN/naQiTzWe", +// "JoPVq9G6NdOb+Ugv+HnUeA==", +// "6Jd/MXn29fk6jcrFYGPb75l7sDJae06I3Y1Op+bZSZqlYXsMpa/8lLE29H0sX3yw", +// new ArgonOptions("id", 1, 1024, 1)), +// PrivateKeyType.LOCKED); +// +// final Throwable throwable = catchThrowable(() -> this.parser.getExistingKey(kdc, singletonList("wrong"))); +// +// assertThat(throwable) +// .isInstanceOf(IllegalArgumentException.class) +// .hasMessage("Locked key but no valid password given"); +// } +// +// @Test +// public void lockedKeySucceedsWithPasswordsMatching() { +// PrivateKeyData privateKeyData = +// new PrivateKeyData( +// null, +// "dwixVoY+pOI2FMuu4k0jLqN/naQiTzWe", +// "JoPVq9G6NdOb+Ugv+HnUeA==", +// "6Jd/MXn29fk6jcrFYGPb75l7sDJae06I3Y1Op+bZSZqlYXsMpa/8lLE29H0sX3yw", +// new ArgonOptions("id", 1, 1024, 1)); +// +// final KeyDataConfig kdc = +// new KeyDataConfig( +// new PrivateKeyData( +// null, +// "dwixVoY+pOI2FMuu4k0jLqN/naQiTzWe", +// "JoPVq9G6NdOb+Ugv+HnUeA==", +// "6Jd/MXn29fk6jcrFYGPb75l7sDJae06I3Y1Op+bZSZqlYXsMpa/8lLE29H0sX3yw", +// new ArgonOptions("id", 1, 1024, 1)), +// PrivateKeyType.LOCKED); +// +// PrivateKey privateKey = mock(PrivateKey.class); +// when(privateKey.getKeyBytes()).thenReturn("SUCCESS".getBytes()); +// when(keyEncryptor.decryptPrivateKey(privateKeyData, "testpassword")).thenReturn(privateKey); +// +// final PrivateKey result = this.parser.getExistingKey(kdc, singletonList("testpassword")); +// +// assertThat(result.getKeyBytes()).isEqualTo("SUCCESS".getBytes()); +// } +// +// @Test +// public void lockedKeySucceedsWithAtleastOnePasswordsMatching() { +// +// PrivateKeyData privateKeyData = mock(PrivateKeyData.class); +// +// final KeyDataConfig kdc = new KeyDataConfig(privateKeyData, PrivateKeyType.LOCKED); +// +// PrivateKey privateKey = mock(PrivateKey.class); +// when(privateKey.getKeyBytes()).thenReturn("SUCCESS".getBytes()); +// +// when(keyEncryptor.decryptPrivateKey(privateKeyData, "wrong")).thenReturn(null); +// when(keyEncryptor.decryptPrivateKey(privateKeyData, "testpassword")).thenReturn(privateKey); +// +// final PrivateKey key = this.parser.getExistingKey(kdc, Arrays.asList("wrong", "testpassword")); +// +// assertThat(key).isNotNull(); +// +// assertThat(key.getKeyBytes()).isEqualTo("SUCCESS".getBytes()); +// } +// +// @Test +// public void loadingMalformedKeyfileThrowsError() throws IOException, ParseException { +// final Path key = Files.createTempFile("key", ".key"); +// Files.write(key, "BAD JSON DATA".getBytes()); +// +// final String[] args = new String[] {"--keys.keyData.privateKeyPath", key.toString()}; +// final CommandLine commandLine = new DefaultParser().parse(options, args); +// +// final Throwable throwable = catchThrowable(() -> this.parser.parse(commandLine)); +// +// assertThat(throwable).isInstanceOf(ConfigException.class).hasCauseExactlyInstanceOf(UnmarshalException.class); +// } +// +// @Test +// public void keyGetsUpdated() throws IOException, ParseException { +// final KeyDataConfig startingKey = +// JaxbUtil.unmarshal(getClass().getResourceAsStream("/lockedprivatekey.json"), KeyDataConfig.class); +// +// final Path key = Files.createTempFile("key", ".key"); +// Files.write(key, JaxbUtil.marshalToString(startingKey).getBytes()); +// +// final String[] args = +// new String[] { +// "--keys.keyData.privateKeyPath", key.toString(), +// "--keys.passwords", "testpassword", +// "--keys.keyData.config.data.aopts.algorithm", "id", +// "--keys.keyData.config.data.aopts.memory", "2048", +// "--keys.keyData.config.data.aopts.iterations", "1", +// "--keys.keyData.config.data.aopts.parallelism", "1" +// }; +// final CommandLine commandLine = new DefaultParser().parse(options, args); +// +// PrivateKey privatekey = mock(PrivateKey.class); +// when(keyEncryptor.decryptPrivateKey(any(PrivateKeyData.class), anyString())).thenReturn(privatekey); +// +// PrivateKeyData privateKeyData = mock(PrivateKeyData.class); +// +// when(keyEncryptor.encryptPrivateKey(any(PrivateKey.class), anyString(), any(ArgonOptions.class))) +// .thenReturn(privateKeyData); +// +// this.parser.parse(commandLine); +// +// final KeyDataConfig endingKey = JaxbUtil.unmarshal(Files.newInputStream(key), KeyDataConfig.class); +// +// assertThat(endingKey.getSbox()).isNotEqualTo(startingKey.getSbox()); +// assertThat(endingKey.getSnonce()).isNotEqualTo(startingKey.getSnonce()); +// assertThat(endingKey.getAsalt()).isNotEqualTo(startingKey.getAsalt()); +// } +// +// @Test +// public void keyGetsUpdatedToNoPassword() throws IOException, ParseException { +// final KeyDataConfig startingKey = +// JaxbUtil.unmarshal(getClass().getResourceAsStream("/lockedprivatekey.json"), KeyDataConfig.class); +// +// when(passwordReader.requestUserPassword()).thenReturn(""); +// +// final Path key = Files.createTempFile("key", ".key"); +// Files.write(key, JaxbUtil.marshalToString(startingKey).getBytes()); +// +// final String[] args = +// new String[] { +// "--keys.keyData.privateKeyPath", key.toString(), +// "--keys.passwords", "testpassword", +// "--keys.keyData.config.data.aopts.algorithm", "id", +// "--keys.keyData.config.data.aopts.memory", "2048", +// "--keys.keyData.config.data.aopts.iterations", "1", +// "--keys.keyData.config.data.aopts.parallelism", "1" +// }; +// final CommandLine commandLine = new DefaultParser().parse(options, args); +// +// byte[] privateKeyData = "SOME PRIVATE DATA".getBytes(); +// PrivateKey privateKey = PrivateKey.from(privateKeyData); +// when(keyEncryptor.decryptPrivateKey(any(PrivateKeyData.class), anyString())).thenReturn(privateKey); +// +// this.parser.parse(commandLine); +// +// final KeyDataConfig endingKey = JaxbUtil.unmarshal(Files.newInputStream(key), KeyDataConfig.class); +// +// assertThat(endingKey.getSbox()).isNotEqualTo(startingKey.getSbox()); +// assertThat(endingKey.getSnonce()).isNotEqualTo(startingKey.getSnonce()); +// assertThat(endingKey.getAsalt()).isNotEqualTo(startingKey.getAsalt()); +// assertThat(endingKey.getPrivateKeyData().getValue()) +// .isEqualTo(Base64.getEncoder().encodeToString(privateKeyData)); +// } } diff --git a/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/KeyUpdateCommand.java b/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/KeyUpdateCommand.java index 66eb75c4c8..6c902bd6ff 100644 --- a/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/KeyUpdateCommand.java +++ b/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/KeyUpdateCommand.java @@ -62,6 +62,7 @@ public class KeyUpdateCommand implements Callable { @CommandLine.Option(names = "--keys.keyData.config.data.aopts.parallelism", defaultValue = "4") public Integer parallelism; + //TODO(cjh) remove plaintext passwords being provided on CLI, replace with prompt/password file @CommandLine.Option(names = {"--keys.passwords"}) public String password; @@ -78,7 +79,8 @@ public class KeyUpdateCommand implements Callable { private KeyEncryptorFactory keyEncryptorFactory; - private KeyEncryptor keyEncryptor; + // TODO(cjh) is package-private for migrated apache commons CLI tests + KeyEncryptor keyEncryptor; private PasswordReader passwordReader; @@ -131,7 +133,7 @@ public CliResult execute() throws IOException { return new CliResult(1, true, null); } - private PrivateKey getExistingKey(final KeyDataConfig kdc, final List passwords) { + PrivateKey getExistingKey(final KeyDataConfig kdc, final List passwords) { if (kdc.getType() == PrivateKeyType.UNLOCKED) { byte[] privateKeyData = Base64.getDecoder().decode(kdc.getValue().getBytes(UTF_8)); @@ -150,7 +152,7 @@ private PrivateKey getExistingKey(final KeyDataConfig kdc, final List pa } } - private Path privateKeyPath() { + Path privateKeyPath() { //// TODO(cjh)shouldn't need this as the option should be marked as required - CHECK! // if (privateKeyPath == null) { // throw new IllegalArgumentException("Private key path cannot be null when updating key password"); @@ -163,7 +165,7 @@ private Path privateKeyPath() { return privateKeyPath; } - private List passwords() throws IOException { + List passwords() throws IOException { if (password != null) { return singletonList(password); } else if (passwordFile != null) { @@ -173,7 +175,7 @@ private List passwords() throws IOException { } } - private ArgonOptions argonOptions() { + ArgonOptions argonOptions() { return new ArgonOptions( algorithm, Integer.valueOf(iterations), Integer.valueOf(memory), Integer.valueOf(parallelism)); } diff --git a/cli/pico-cli/src/test/java/com/quorum/tessera/picocli/KeyUpdateCommandTest.java b/cli/pico-cli/src/test/java/com/quorum/tessera/picocli/KeyUpdateCommandTest.java new file mode 100644 index 0000000000..ea23c3657a --- /dev/null +++ b/cli/pico-cli/src/test/java/com/quorum/tessera/picocli/KeyUpdateCommandTest.java @@ -0,0 +1,376 @@ +package com.quorum.tessera.picocli; + +import com.quorum.tessera.config.*; +import com.quorum.tessera.config.keys.KeyEncryptor; +import com.quorum.tessera.config.keys.KeyEncryptorFactory; +import com.quorum.tessera.config.util.JaxbUtil; +import com.quorum.tessera.encryption.PrivateKey; +import com.quorum.tessera.passwords.PasswordReader; +import org.apache.commons.cli.ParseException; +import org.junit.After; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; + +import javax.xml.bind.UnmarshalException; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Base64; +import java.util.List; + +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.catchThrowable; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +public class KeyUpdateCommandTest { + + private KeyUpdateCommand command; + + private KeyEncryptorFactory keyEncryptorFactory; + + private KeyEncryptor keyEncryptor; + + private PasswordReader passwordReader; + + @Before + public void onSetup() { + keyEncryptorFactory = mock(KeyEncryptorFactory.class); + keyEncryptor = mock(KeyEncryptor.class); + passwordReader = mock(PasswordReader.class); + + when(keyEncryptorFactory.create(any())).thenReturn(keyEncryptor); + when(passwordReader.requestUserPassword()).thenReturn("newPassword"); + + command = new KeyUpdateCommand(keyEncryptorFactory, passwordReader); + command.keyEncryptor = keyEncryptor; + } + + @After + public void onTeardown() { + verifyNoMoreInteractions(keyEncryptorFactory, keyEncryptor, passwordReader); + } + + // Argon Option tests + // TODO(cjh) re-enable this once the tests have become more integration-based (i.e. I think defaults will only be set when creating a command line object and calling parseArgs or execute + @Ignore + @Test + public void noArgonOptionsGivenHasDefaults() throws ParseException { +// final CommandLine commandLine = new DefaultParser().parse(options, new String[] {}); +// +// final ArgonOptions argonOptions = KeyUpdateParser.argonOptions(commandLine); +// +// assertThat(argonOptions.getAlgorithm()).isEqualTo("i"); +// assertThat(argonOptions.getParallelism()).isEqualTo(4); +// assertThat(argonOptions.getMemory()).isEqualTo(1048576); +// assertThat(argonOptions.getIterations()).isEqualTo(10); + } + + @Test + public void argonOptionsGivenHasOverrides() { + command.algorithm = "d"; + command.memory = 100; + command.iterations = 100; + command.parallelism = 100; + + final ArgonOptions argonOptions = command.argonOptions(); + + assertThat(argonOptions.getAlgorithm()).isEqualTo("d"); + assertThat(argonOptions.getParallelism()).isEqualTo(100); + assertThat(argonOptions.getMemory()).isEqualTo(100); + assertThat(argonOptions.getIterations()).isEqualTo(100); + } + + // Password reading tests + @Test + public void inlinePasswordParsed() throws IOException { + command.password = "pass"; + + final List passwords = command.passwords(); + + assertThat(passwords).isNotNull().hasSize(1).containsExactly("pass"); + } + + @Test + public void passwordFileParsedAndRead() throws IOException { + final Path passwordFile = Files.createTempFile("passwords", ".txt"); + Files.write(passwordFile, "passwordInsideFile\nsecondPassword".getBytes()); + + command.passwordFile = passwordFile; + + final List passwords = command.passwords(); + + assertThat(passwords).isNotNull().hasSize(2).containsExactly("passwordInsideFile", "secondPassword"); + } + + @Test + public void passwordFileThrowsErrorIfCantBeRead() { + command.passwordFile = Paths.get("/tmp/passwords.txt"); + + final Throwable throwable = catchThrowable(() -> command.passwords()); + + assertThat(throwable).isNotNull().isInstanceOf(IOException.class); + } + + @Test + public void emptyListGivenForNoPasswords() throws IOException { + final List passwords = command.passwords(); + + assertThat(passwords).isNotNull().isEmpty(); + } + + + // key file tests +// TODO(cjh) re-enable this once the tests have become more integration-based (i.e. required fields can be tested when creating a command line object and calling parseArgs or execute + @Ignore + @Test + public void noPrivateKeyGivenThrowsError() { +// final Throwable throwable = catchThrowable(() -> KeyUpdateParser.privateKeyPath(commandLine)); +// +// assertThat(throwable) +// .isInstanceOf(IllegalArgumentException.class) +// .hasMessage("Private key path cannot be null when updating key password"); + } + + @Test + public void cantReadPrivateKeyThrowsError() { + command.privateKeyPath = Paths.get("/tmp/nonexisting.txt"); + + final Throwable throwable = catchThrowable(() -> command.privateKeyPath()); + + assertThat(throwable).isInstanceOf(IllegalArgumentException.class); + } + + @Test + public void privateKeyExistsReturnsPath() throws IOException { + final Path key = Files.createTempFile("key", ".key"); + + command.privateKeyPath = key; + + final Path path = command.privateKeyPath(); + + assertThat(path).isEqualTo(key); + } + + // key fetching tests + @Test + public void unlockedKeyReturnedProperly() { + final KeyDataConfig kdc = + new KeyDataConfig( + new PrivateKeyData("/+UuD63zItL1EbjxkKUljMgG8Z1w0AJ8pNOR4iq2yQc=", null, null, null, null), + PrivateKeyType.UNLOCKED); + + final PrivateKey key = command.getExistingKey(kdc, emptyList()); + + String encodedKeyValue = Base64.getEncoder().encodeToString(key.getKeyBytes()); + + assertThat(encodedKeyValue).isEqualTo("/+UuD63zItL1EbjxkKUljMgG8Z1w0AJ8pNOR4iq2yQc="); + } + + @Test + public void lockedKeyFailsWithNoPasswordsMatching() { + + final KeyDataConfig kdc = + new KeyDataConfig( + new PrivateKeyData( + null, + "dwixVoY+pOI2FMuu4k0jLqN/naQiTzWe", + "JoPVq9G6NdOb+Ugv+HnUeA==", + "6Jd/MXn29fk6jcrFYGPb75l7sDJae06I3Y1Op+bZSZqlYXsMpa/8lLE29H0sX3yw", + new ArgonOptions("id", 1, 1024, 1)), + PrivateKeyType.LOCKED); + + final Throwable throwable = catchThrowable(() -> command.getExistingKey(kdc, singletonList("wrong"))); + + assertThat(throwable) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Locked key but no valid password given"); + + verify(keyEncryptor).decryptPrivateKey(kdc.getPrivateKeyData(), "wrong"); + } + + @Test + public void lockedKeySucceedsWithPasswordsMatching() { + PrivateKeyData privateKeyData = + new PrivateKeyData( + null, + "dwixVoY+pOI2FMuu4k0jLqN/naQiTzWe", + "JoPVq9G6NdOb+Ugv+HnUeA==", + "6Jd/MXn29fk6jcrFYGPb75l7sDJae06I3Y1Op+bZSZqlYXsMpa/8lLE29H0sX3yw", + new ArgonOptions("id", 1, 1024, 1)); + + final KeyDataConfig kdc = + new KeyDataConfig( + new PrivateKeyData( + null, + "dwixVoY+pOI2FMuu4k0jLqN/naQiTzWe", + "JoPVq9G6NdOb+Ugv+HnUeA==", + "6Jd/MXn29fk6jcrFYGPb75l7sDJae06I3Y1Op+bZSZqlYXsMpa/8lLE29H0sX3yw", + new ArgonOptions("id", 1, 1024, 1)), + PrivateKeyType.LOCKED); + + PrivateKey privateKey = mock(PrivateKey.class); + when(privateKey.getKeyBytes()).thenReturn("SUCCESS".getBytes()); + when(keyEncryptor.decryptPrivateKey(privateKeyData, "testpassword")).thenReturn(privateKey); + + final PrivateKey result = command.getExistingKey(kdc, singletonList("testpassword")); + + assertThat(result.getKeyBytes()).isEqualTo("SUCCESS".getBytes()); + + verify(keyEncryptor).decryptPrivateKey(privateKeyData, "testpassword"); + } + + @Test + public void loadingMalformedKeyfileThrowsError() throws Exception { + final Path key = Files.createTempFile("key", ".key"); + Files.write(key, "BAD JSON DATA".getBytes()); + + command.privateKeyPath = key; + + addEmptyEncryptorConfigToCommand(); + addDefaultArgonConfigToCommand(); + + final Throwable throwable = catchThrowable(() -> command.call()); + + assertThat(throwable).isInstanceOf(ConfigException.class).hasCauseExactlyInstanceOf(UnmarshalException.class); + + verify(keyEncryptorFactory).create(any()); + } + + @Test + public void keyGetsUpdated() throws Exception { + final KeyDataConfig startingKey = + JaxbUtil.unmarshal(getClass().getResourceAsStream("/lockedprivatekey.json"), KeyDataConfig.class); + + final Path key = Files.createTempFile("key", ".key"); + Files.write(key, JaxbUtil.marshalToString(startingKey).getBytes()); + + command.privateKeyPath = key; + command.password = "testpassword"; + + addDefaultArgonConfigToCommand(); + addEmptyEncryptorConfigToCommand(); + + PrivateKey privatekey = mock(PrivateKey.class); + when(keyEncryptor.decryptPrivateKey(any(PrivateKeyData.class), anyString())).thenReturn(privatekey); + + PrivateKeyData privateKeyData = mock(PrivateKeyData.class); + + when(keyEncryptor.encryptPrivateKey(any(PrivateKey.class), anyString(), any(ArgonOptions.class))) + .thenReturn(privateKeyData); + + command.call(); + + final KeyDataConfig endingKey = JaxbUtil.unmarshal(Files.newInputStream(key), KeyDataConfig.class); + + assertThat(endingKey.getSbox()).isNotEqualTo(startingKey.getSbox()); + assertThat(endingKey.getSnonce()).isNotEqualTo(startingKey.getSnonce()); + assertThat(endingKey.getAsalt()).isNotEqualTo(startingKey.getAsalt()); + + verify(keyEncryptorFactory).create(any()); + verify(keyEncryptor).decryptPrivateKey(any(PrivateKeyData.class), anyString()); + verify(keyEncryptor).encryptPrivateKey(any(PrivateKey.class), anyString(), any(ArgonOptions.class)); + verify(passwordReader).requestUserPassword(); + } + + @Test + public void keyGetsUpdatedUsingEncryptorOptions() throws Exception { + final KeyDataConfig startingKey = + JaxbUtil.unmarshal(getClass().getResourceAsStream("/lockedprivatekey.json"), KeyDataConfig.class); + + final Path key = Files.createTempFile("key", ".key"); + Files.write(key, JaxbUtil.marshalToString(startingKey).getBytes()); + + command.privateKeyPath = key; + command.password = "testpassword"; + + addDefaultArgonConfigToCommand(); + addEncryptorOptionsToCommand(); + + PrivateKey privatekey = mock(PrivateKey.class); + when(keyEncryptor.decryptPrivateKey(any(PrivateKeyData.class), anyString())).thenReturn(privatekey); + + PrivateKeyData privateKeyData = mock(PrivateKeyData.class); + + when(keyEncryptor.encryptPrivateKey(any(PrivateKey.class), anyString(), any(ArgonOptions.class))) + .thenReturn(privateKeyData); + + command.call(); + + final KeyDataConfig endingKey = JaxbUtil.unmarshal(Files.newInputStream(key), KeyDataConfig.class); + + assertThat(endingKey.getSbox()).isNotEqualTo(startingKey.getSbox()); + assertThat(endingKey.getSnonce()).isNotEqualTo(startingKey.getSnonce()); + assertThat(endingKey.getAsalt()).isNotEqualTo(startingKey.getAsalt()); + + verify(keyEncryptorFactory).create(any()); + verify(keyEncryptor).decryptPrivateKey(any(PrivateKeyData.class), anyString()); + verify(keyEncryptor).encryptPrivateKey(any(PrivateKey.class), anyString(), any(ArgonOptions.class)); + verify(passwordReader).requestUserPassword(); + } + + @Test + public void keyGetsUpdatedToNoPassword() throws Exception { + final KeyDataConfig startingKey = + JaxbUtil.unmarshal(getClass().getResourceAsStream("/lockedprivatekey.json"), KeyDataConfig.class); + + when(passwordReader.requestUserPassword()).thenReturn(""); + + final Path key = Files.createTempFile("key", ".key"); + Files.write(key, JaxbUtil.marshalToString(startingKey).getBytes()); + + command.privateKeyPath = key; + command.password = "testpassword"; + + addDefaultArgonConfigToCommand(); + addEmptyEncryptorConfigToCommand(); + + byte[] privateKeyData = "SOME PRIVATE DATA".getBytes(); + PrivateKey privateKey = PrivateKey.from(privateKeyData); + when(keyEncryptor.decryptPrivateKey(any(PrivateKeyData.class), anyString())).thenReturn(privateKey); + + command.call(); + + final KeyDataConfig endingKey = JaxbUtil.unmarshal(Files.newInputStream(key), KeyDataConfig.class); + + assertThat(endingKey.getSbox()).isNotEqualTo(startingKey.getSbox()); + assertThat(endingKey.getSnonce()).isNotEqualTo(startingKey.getSnonce()); + assertThat(endingKey.getAsalt()).isNotEqualTo(startingKey.getAsalt()); + assertThat(endingKey.getPrivateKeyData().getValue()) + .isEqualTo(Base64.getEncoder().encodeToString(privateKeyData)); + + verify(keyEncryptorFactory).create(any()); + verify(keyEncryptor).decryptPrivateKey(any(PrivateKeyData.class), anyString()); + verify(keyEncryptor, never()).encryptPrivateKey(any(PrivateKey.class), anyString(), any(ArgonOptions.class)); + verify(passwordReader).requestUserPassword(); + } + + private void addEmptyEncryptorConfigToCommand() { + final Config config = new Config(); + final EncryptorConfig encryptorConfig = new EncryptorConfig(); + config.setEncryptor(encryptorConfig); + command.config = config; + } + + private void addEncryptorOptionsToCommand() { + command.encryptorOptions = new EncryptorOptions(); + } + + private void addDefaultArgonConfigToCommand() { + command.algorithm = "d"; + command.memory = 100; + command.iterations = 100; + command.parallelism = 100; + } + + + + + + + +} From 3c2a26183b5b9ecdf3d23159ee77f954d51b3fc1 Mon Sep 17 00:00:00 2001 From: chris-j-h Date: Mon, 16 Dec 2019 20:18:42 +0000 Subject: [PATCH 26/41] Fix enclave cmd tests --- .../tessera/picocli/KeyUpdateCommandTest.java | 106 +++++++++--------- .../com/quorum/tessera/enclave/rest/Main.java | 7 +- .../tessera/enclave/rest/EnclaveRestIT.java | 14 ++- .../enclave/server/EnclaveCliAdapterTest.java | 33 ++++-- 4 files changed, 92 insertions(+), 68 deletions(-) diff --git a/cli/pico-cli/src/test/java/com/quorum/tessera/picocli/KeyUpdateCommandTest.java b/cli/pico-cli/src/test/java/com/quorum/tessera/picocli/KeyUpdateCommandTest.java index ea23c3657a..1a7b126473 100644 --- a/cli/pico-cli/src/test/java/com/quorum/tessera/picocli/KeyUpdateCommandTest.java +++ b/cli/pico-cli/src/test/java/com/quorum/tessera/picocli/KeyUpdateCommandTest.java @@ -56,18 +56,19 @@ public void onTeardown() { } // Argon Option tests - // TODO(cjh) re-enable this once the tests have become more integration-based (i.e. I think defaults will only be set when creating a command line object and calling parseArgs or execute + // TODO(cjh) re-enable this once the tests have become more integration-based (i.e. I think defaults will only be + // set when creating a command line object and calling parseArgs or execute @Ignore @Test public void noArgonOptionsGivenHasDefaults() throws ParseException { -// final CommandLine commandLine = new DefaultParser().parse(options, new String[] {}); -// -// final ArgonOptions argonOptions = KeyUpdateParser.argonOptions(commandLine); -// -// assertThat(argonOptions.getAlgorithm()).isEqualTo("i"); -// assertThat(argonOptions.getParallelism()).isEqualTo(4); -// assertThat(argonOptions.getMemory()).isEqualTo(1048576); -// assertThat(argonOptions.getIterations()).isEqualTo(10); + // final CommandLine commandLine = new DefaultParser().parse(options, new String[] {}); + // + // final ArgonOptions argonOptions = KeyUpdateParser.argonOptions(commandLine); + // + // assertThat(argonOptions.getAlgorithm()).isEqualTo("i"); + // assertThat(argonOptions.getParallelism()).isEqualTo(4); + // assertThat(argonOptions.getMemory()).isEqualTo(1048576); + // assertThat(argonOptions.getIterations()).isEqualTo(10); } @Test @@ -123,17 +124,17 @@ public void emptyListGivenForNoPasswords() throws IOException { assertThat(passwords).isNotNull().isEmpty(); } - // key file tests -// TODO(cjh) re-enable this once the tests have become more integration-based (i.e. required fields can be tested when creating a command line object and calling parseArgs or execute + // TODO(cjh) re-enable this once the tests have become more integration-based (i.e. required fields can be tested + // when creating a command line object and calling parseArgs or execute @Ignore @Test public void noPrivateKeyGivenThrowsError() { -// final Throwable throwable = catchThrowable(() -> KeyUpdateParser.privateKeyPath(commandLine)); -// -// assertThat(throwable) -// .isInstanceOf(IllegalArgumentException.class) -// .hasMessage("Private key path cannot be null when updating key password"); + // final Throwable throwable = catchThrowable(() -> KeyUpdateParser.privateKeyPath(commandLine)); + // + // assertThat(throwable) + // .isInstanceOf(IllegalArgumentException.class) + // .hasMessage("Private key path cannot be null when updating key password"); } @Test @@ -160,9 +161,9 @@ public void privateKeyExistsReturnsPath() throws IOException { @Test public void unlockedKeyReturnedProperly() { final KeyDataConfig kdc = - new KeyDataConfig( - new PrivateKeyData("/+UuD63zItL1EbjxkKUljMgG8Z1w0AJ8pNOR4iq2yQc=", null, null, null, null), - PrivateKeyType.UNLOCKED); + new KeyDataConfig( + new PrivateKeyData("/+UuD63zItL1EbjxkKUljMgG8Z1w0AJ8pNOR4iq2yQc=", null, null, null, null), + PrivateKeyType.UNLOCKED); final PrivateKey key = command.getExistingKey(kdc, emptyList()); @@ -175,20 +176,20 @@ public void unlockedKeyReturnedProperly() { public void lockedKeyFailsWithNoPasswordsMatching() { final KeyDataConfig kdc = - new KeyDataConfig( - new PrivateKeyData( - null, - "dwixVoY+pOI2FMuu4k0jLqN/naQiTzWe", - "JoPVq9G6NdOb+Ugv+HnUeA==", - "6Jd/MXn29fk6jcrFYGPb75l7sDJae06I3Y1Op+bZSZqlYXsMpa/8lLE29H0sX3yw", - new ArgonOptions("id", 1, 1024, 1)), - PrivateKeyType.LOCKED); + new KeyDataConfig( + new PrivateKeyData( + null, + "dwixVoY+pOI2FMuu4k0jLqN/naQiTzWe", + "JoPVq9G6NdOb+Ugv+HnUeA==", + "6Jd/MXn29fk6jcrFYGPb75l7sDJae06I3Y1Op+bZSZqlYXsMpa/8lLE29H0sX3yw", + new ArgonOptions("id", 1, 1024, 1)), + PrivateKeyType.LOCKED); final Throwable throwable = catchThrowable(() -> command.getExistingKey(kdc, singletonList("wrong"))); assertThat(throwable) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("Locked key but no valid password given"); + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Locked key but no valid password given"); verify(keyEncryptor).decryptPrivateKey(kdc.getPrivateKeyData(), "wrong"); } @@ -196,22 +197,22 @@ public void lockedKeyFailsWithNoPasswordsMatching() { @Test public void lockedKeySucceedsWithPasswordsMatching() { PrivateKeyData privateKeyData = - new PrivateKeyData( - null, - "dwixVoY+pOI2FMuu4k0jLqN/naQiTzWe", - "JoPVq9G6NdOb+Ugv+HnUeA==", - "6Jd/MXn29fk6jcrFYGPb75l7sDJae06I3Y1Op+bZSZqlYXsMpa/8lLE29H0sX3yw", - new ArgonOptions("id", 1, 1024, 1)); + new PrivateKeyData( + null, + "dwixVoY+pOI2FMuu4k0jLqN/naQiTzWe", + "JoPVq9G6NdOb+Ugv+HnUeA==", + "6Jd/MXn29fk6jcrFYGPb75l7sDJae06I3Y1Op+bZSZqlYXsMpa/8lLE29H0sX3yw", + new ArgonOptions("id", 1, 1024, 1)); final KeyDataConfig kdc = - new KeyDataConfig( - new PrivateKeyData( - null, - "dwixVoY+pOI2FMuu4k0jLqN/naQiTzWe", - "JoPVq9G6NdOb+Ugv+HnUeA==", - "6Jd/MXn29fk6jcrFYGPb75l7sDJae06I3Y1Op+bZSZqlYXsMpa/8lLE29H0sX3yw", - new ArgonOptions("id", 1, 1024, 1)), - PrivateKeyType.LOCKED); + new KeyDataConfig( + new PrivateKeyData( + null, + "dwixVoY+pOI2FMuu4k0jLqN/naQiTzWe", + "JoPVq9G6NdOb+Ugv+HnUeA==", + "6Jd/MXn29fk6jcrFYGPb75l7sDJae06I3Y1Op+bZSZqlYXsMpa/8lLE29H0sX3yw", + new ArgonOptions("id", 1, 1024, 1)), + PrivateKeyType.LOCKED); PrivateKey privateKey = mock(PrivateKey.class); when(privateKey.getKeyBytes()).thenReturn("SUCCESS".getBytes()); @@ -244,7 +245,7 @@ public void loadingMalformedKeyfileThrowsError() throws Exception { @Test public void keyGetsUpdated() throws Exception { final KeyDataConfig startingKey = - JaxbUtil.unmarshal(getClass().getResourceAsStream("/lockedprivatekey.json"), KeyDataConfig.class); + JaxbUtil.unmarshal(getClass().getResourceAsStream("/lockedprivatekey.json"), KeyDataConfig.class); final Path key = Files.createTempFile("key", ".key"); Files.write(key, JaxbUtil.marshalToString(startingKey).getBytes()); @@ -261,7 +262,7 @@ public void keyGetsUpdated() throws Exception { PrivateKeyData privateKeyData = mock(PrivateKeyData.class); when(keyEncryptor.encryptPrivateKey(any(PrivateKey.class), anyString(), any(ArgonOptions.class))) - .thenReturn(privateKeyData); + .thenReturn(privateKeyData); command.call(); @@ -280,7 +281,7 @@ public void keyGetsUpdated() throws Exception { @Test public void keyGetsUpdatedUsingEncryptorOptions() throws Exception { final KeyDataConfig startingKey = - JaxbUtil.unmarshal(getClass().getResourceAsStream("/lockedprivatekey.json"), KeyDataConfig.class); + JaxbUtil.unmarshal(getClass().getResourceAsStream("/lockedprivatekey.json"), KeyDataConfig.class); final Path key = Files.createTempFile("key", ".key"); Files.write(key, JaxbUtil.marshalToString(startingKey).getBytes()); @@ -297,7 +298,7 @@ public void keyGetsUpdatedUsingEncryptorOptions() throws Exception { PrivateKeyData privateKeyData = mock(PrivateKeyData.class); when(keyEncryptor.encryptPrivateKey(any(PrivateKey.class), anyString(), any(ArgonOptions.class))) - .thenReturn(privateKeyData); + .thenReturn(privateKeyData); command.call(); @@ -316,7 +317,7 @@ public void keyGetsUpdatedUsingEncryptorOptions() throws Exception { @Test public void keyGetsUpdatedToNoPassword() throws Exception { final KeyDataConfig startingKey = - JaxbUtil.unmarshal(getClass().getResourceAsStream("/lockedprivatekey.json"), KeyDataConfig.class); + JaxbUtil.unmarshal(getClass().getResourceAsStream("/lockedprivatekey.json"), KeyDataConfig.class); when(passwordReader.requestUserPassword()).thenReturn(""); @@ -341,7 +342,7 @@ public void keyGetsUpdatedToNoPassword() throws Exception { assertThat(endingKey.getSnonce()).isNotEqualTo(startingKey.getSnonce()); assertThat(endingKey.getAsalt()).isNotEqualTo(startingKey.getAsalt()); assertThat(endingKey.getPrivateKeyData().getValue()) - .isEqualTo(Base64.getEncoder().encodeToString(privateKeyData)); + .isEqualTo(Base64.getEncoder().encodeToString(privateKeyData)); verify(keyEncryptorFactory).create(any()); verify(keyEncryptor).decryptPrivateKey(any(PrivateKeyData.class), anyString()); @@ -366,11 +367,4 @@ private void addDefaultArgonConfigToCommand() { command.iterations = 100; command.parallelism = 100; } - - - - - - - } diff --git a/enclave/enclave-jaxrs/src/main/java/com/quorum/tessera/enclave/rest/Main.java b/enclave/enclave-jaxrs/src/main/java/com/quorum/tessera/enclave/rest/Main.java index a14be8d81a..6a777fb095 100644 --- a/enclave/enclave-jaxrs/src/main/java/com/quorum/tessera/enclave/rest/Main.java +++ b/enclave/enclave-jaxrs/src/main/java/com/quorum/tessera/enclave/rest/Main.java @@ -1,7 +1,7 @@ package com.quorum.tessera.enclave.rest; import com.quorum.tessera.cli.CliResult; -import com.quorum.tessera.cli.CliType; +import com.quorum.tessera.cli.parsers.ConfigConverter; import com.quorum.tessera.config.CommunicationType; import com.quorum.tessera.config.Config; import com.quorum.tessera.config.ServerConfig; @@ -26,6 +26,11 @@ public static void main(String... args) throws Exception { System.setProperty("javax.xml.bind.context.factory", "org.eclipse.persistence.jaxb.JAXBContextFactory"); CommandLine commandLine = new CommandLine(new EnclaveCliAdapter()); + commandLine + .registerConverter(Config.class, new ConfigConverter()) + .setSeparator(" ") + .setCaseInsensitiveEnumValuesAllowed(true); + commandLine.execute(args); CliResult cliResult = commandLine.getExecutionResult(); diff --git a/enclave/enclave-jaxrs/src/test/java/com/quorum/tessera/enclave/rest/EnclaveRestIT.java b/enclave/enclave-jaxrs/src/test/java/com/quorum/tessera/enclave/rest/EnclaveRestIT.java index 1c04c15bc1..78d74d1dbd 100644 --- a/enclave/enclave-jaxrs/src/test/java/com/quorum/tessera/enclave/rest/EnclaveRestIT.java +++ b/enclave/enclave-jaxrs/src/test/java/com/quorum/tessera/enclave/rest/EnclaveRestIT.java @@ -1,17 +1,20 @@ package com.quorum.tessera.enclave.rest; -import com.quorum.tessera.cli.CliDelegate; import com.quorum.tessera.cli.CliResult; import com.quorum.tessera.cli.CliType; +import com.quorum.tessera.cli.parsers.ConfigConverter; import com.quorum.tessera.config.Config; import com.quorum.tessera.enclave.Enclave; import com.quorum.tessera.enclave.EnclaveFactory; +import com.quorum.tessera.enclave.server.EnclaveCliAdapter; import com.quorum.tessera.encryption.PublicKey; import com.quorum.tessera.service.Service; import org.glassfish.jersey.test.JerseyTest; import org.junit.After; import org.junit.Before; import org.junit.Test; +import picocli.CommandLine; + import java.net.URL; import java.util.Set; @@ -35,7 +38,14 @@ public void setUp() throws Exception { System.setProperty(CliType.CLI_TYPE_KEY, CliType.ENCLAVE.name()); URL url = EnclaveRestIT.class.getResource("/sample-config.json"); - CliResult cliResult = CliDelegate.INSTANCE.execute("-configfile", url.getFile()); + final CommandLine commandLine = new CommandLine(new EnclaveCliAdapter()); + commandLine + .registerConverter(Config.class, new ConfigConverter()) + .setSeparator(" ") + .setCaseInsensitiveEnumValuesAllowed(true); + + commandLine.execute("-configfile", url.getFile()); + CliResult cliResult = commandLine.getExecutionResult(); EnclaveFactory enclaveFactory = EnclaveFactory.create(); diff --git a/enclave/enclave-server/src/test/java/com/quorum/tessera/enclave/server/EnclaveCliAdapterTest.java b/enclave/enclave-server/src/test/java/com/quorum/tessera/enclave/server/EnclaveCliAdapterTest.java index 0fdcd77bee..37c210fff3 100644 --- a/enclave/enclave-server/src/test/java/com/quorum/tessera/enclave/server/EnclaveCliAdapterTest.java +++ b/enclave/enclave-server/src/test/java/com/quorum/tessera/enclave/server/EnclaveCliAdapterTest.java @@ -1,19 +1,21 @@ package com.quorum.tessera.enclave.server; -import com.quorum.tessera.cli.CliDelegate; import com.quorum.tessera.cli.CliResult; import com.quorum.tessera.cli.CliType; +import com.quorum.tessera.cli.parsers.ConfigConverter; +import com.quorum.tessera.config.Config; +import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.contrib.java.lang.system.SystemErrRule; import org.junit.contrib.java.lang.system.SystemOutRule; +import picocli.CommandLine; import java.nio.file.Path; import java.nio.file.Paths; import static org.assertj.core.api.Assertions.assertThat; -import org.junit.After; public class EnclaveCliAdapterTest { @@ -21,10 +23,18 @@ public class EnclaveCliAdapterTest { @Rule public SystemOutRule systemOutOutput = new SystemOutRule().enableLog(); + private CommandLine commandLine; + @Before public void onSetUp() { System.setProperty(CliType.CLI_TYPE_KEY, CliType.ENCLAVE.name()); this.systemErrOutput.clearLog(); + + commandLine = new CommandLine(new EnclaveCliAdapter()); + commandLine + .registerConverter(Config.class, new ConfigConverter()) + .setSeparator(" ") + .setCaseInsensitiveEnumValuesAllowed(true); } @After @@ -38,22 +48,26 @@ public void getType() { } @Test - public void missingConfigurationOutputsErrorMessage() throws Exception { - final CliResult result = CliDelegate.instance().execute(); + public void missingConfigurationOutputsErrorMessage() { + commandLine.execute(); + final CliResult result = commandLine.getExecutionResult(); final String output = systemErrOutput.getLog(); - assertThat(result).isEqualToComparingFieldByField(new CliResult(1, true, null)); + assertThat(result).isNull(); +// assertThat(result).isEqualToComparingFieldByField(new CliResult(1, true, null)); assertThat(output).contains("Missing required option '-configfile '"); } @Test - public void helpOptionOutputsUsageMessage() throws Exception { - final CliResult result = CliDelegate.instance().execute("help"); + public void helpOptionOutputsUsageMessage() { + commandLine.execute("help"); + final CliResult result = commandLine.getExecutionResult(); final String output = systemOutOutput.getLog(); - assertThat(result).isEqualToComparingFieldByField(new CliResult(0, true, null)); +// assertThat(result).isEqualToComparingFieldByField(new CliResult(0, true, null)); + assertThat(result).isNull(); assertThat(output) .contains( "Usage:", @@ -79,7 +93,8 @@ public void helpOptionOutputsUsageMessage() throws Exception { public void configPassedToResolver() throws Exception { final Path inputFile = Paths.get(getClass().getResource("/sample-config.json").toURI()); - final CliResult result = CliDelegate.instance().execute("-configfile", inputFile.toString()); + commandLine.execute("-configfile", inputFile.toString()); + final CliResult result = commandLine.getExecutionResult(); assertThat(result).isNotNull(); assertThat(result.getStatus()).isEqualTo(0); From ddcec46f253339995351d293d737ae3333cb4bab Mon Sep 17 00:00:00 2001 From: chris-j-h Date: Mon, 16 Dec 2019 20:46:04 +0000 Subject: [PATCH 27/41] Config migration CLI test fixes --- .../quorum/tessera/config/migration/Main.java | 15 ++++++-- .../migration/LegacyCliAdapterTest.java | 38 +++++++++++++------ 2 files changed, 39 insertions(+), 14 deletions(-) diff --git a/config-migration/src/main/java/com/quorum/tessera/config/migration/Main.java b/config-migration/src/main/java/com/quorum/tessera/config/migration/Main.java index c11a69f989..152d1d6402 100644 --- a/config-migration/src/main/java/com/quorum/tessera/config/migration/Main.java +++ b/config-migration/src/main/java/com/quorum/tessera/config/migration/Main.java @@ -1,6 +1,9 @@ package com.quorum.tessera.config.migration; +import com.quorum.tessera.cli.CliResult; import com.quorum.tessera.cli.CliType; +import com.quorum.tessera.cli.parsers.ConfigConverter; +import com.quorum.tessera.config.Config; import picocli.CommandLine; public class Main { @@ -10,10 +13,16 @@ public static void main(String... args) { System.setProperty("javax.xml.bind.context.factory", "org.eclipse.persistence.jaxb.JAXBContextFactory"); System.setProperty(CliType.CLI_TYPE_KEY, CliType.CONFIG_MIGRATION.name()); try { - int status = new CommandLine(new LegacyCliAdapter()).execute(args); + CommandLine commandLine = new CommandLine(new LegacyCliAdapter()); + commandLine + .registerConverter(Config.class, new ConfigConverter()) + .setSeparator(" ") + .setCaseInsensitiveEnumValuesAllowed(true); - // final CliResult result = CliDelegate.instance().execute(args); - System.exit(status); + commandLine.execute(args); + CliResult cliResult = commandLine.getExecutionResult(); + + System.exit(cliResult.getStatus()); } catch (final Exception ex) { System.err.println(ex.toString()); System.exit(1); diff --git a/config-migration/src/test/java/com/quorum/tessera/config/migration/LegacyCliAdapterTest.java b/config-migration/src/test/java/com/quorum/tessera/config/migration/LegacyCliAdapterTest.java index 72f0ad4cc3..a49d969f0e 100644 --- a/config-migration/src/test/java/com/quorum/tessera/config/migration/LegacyCliAdapterTest.java +++ b/config-migration/src/test/java/com/quorum/tessera/config/migration/LegacyCliAdapterTest.java @@ -1,8 +1,8 @@ package com.quorum.tessera.config.migration; -import com.quorum.tessera.cli.CliDelegate; import com.quorum.tessera.cli.CliResult; import com.quorum.tessera.cli.CliType; +import com.quorum.tessera.cli.parsers.ConfigConverter; import com.quorum.tessera.config.*; import com.quorum.tessera.config.builder.ConfigBuilder; import com.quorum.tessera.config.builder.KeyDataBuilder; @@ -15,6 +15,7 @@ import org.junit.Rule; import org.junit.Test; import org.junit.contrib.java.lang.system.SystemErrRule; +import picocli.CommandLine; import java.io.ByteArrayOutputStream; import java.io.File; @@ -30,7 +31,6 @@ import static com.quorum.tessera.config.AppType.Q2T; import static java.util.stream.Collectors.toList; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.catchThrowable; import static org.mockito.Mockito.mock; public class LegacyCliAdapterTest { @@ -39,14 +39,25 @@ public class LegacyCliAdapterTest { private final ConfigBuilder builderWithValidValues = FixtureUtil.builderWithValidValues(); + // TODO(cjh) remove this and do all testing through the CommandLine instance private final LegacyCliAdapter instance = new LegacyCliAdapter(); + private CommandLine commandLine; + private Path dataDirectory; @Before public void onSetUp() throws IOException { System.setProperty(CliType.CLI_TYPE_KEY, CliType.CONFIG_MIGRATION.name()); + commandLine = new CommandLine(new LegacyCliAdapter()); + commandLine + .registerConverter(Config.class, new ConfigConverter()) + .setSeparator(" ") + .setCaseInsensitiveEnumValuesAllowed(true); + + systemErrRule.clearLog(); + dataDirectory = Files.createTempDirectory("data"); Files.createFile(dataDirectory.resolve("foo.pub")); @@ -91,7 +102,8 @@ public void withoutCliArgsAllConfigIsSetFromTomlFile() throws Exception { Path configFile = Files.createTempFile("noOptions", ".txt"); Files.write(configFile, data.getBytes()); - CliResult result = CliDelegate.instance().execute("--tomlfile", configFile.toString()); + commandLine.execute("--tomlfile", configFile.toString()); + final CliResult result = commandLine.getExecutionResult(); assertThat(result).isNotNull(); assertThat(result.getConfig()).isPresent(); @@ -211,7 +223,8 @@ public void providingCliArgsOverridesTomlFileConfig() throws Exception { "over-known-servers" }; - CliResult result = CliDelegate.instance().execute(args); + commandLine.execute(args); + final CliResult result = commandLine.getExecutionResult(); assertThat(result).isNotNull(); assertThat(result.getConfig()).isPresent(); @@ -270,7 +283,8 @@ public void ifConfigParameterIsNotSetInTomlOrCliThenDefaultIsUsed() throws Excep "--privatekeys", keysFile.toString() }; - CliResult result = CliDelegate.instance().execute(requiredParams); + commandLine.execute(requiredParams); + final CliResult result = commandLine.getExecutionResult(); assertThat(result).isNotNull(); assertThat(result.getConfig()).isPresent(); @@ -334,7 +348,8 @@ public void ifWorkDirCliOverrideIsProvidedThenItIsAppliedToBothTomlAndCliSetPara "--privatekeys", "new.key" }; - CliResult result = CliDelegate.instance().execute(args); + commandLine.execute(args); + final CliResult result = commandLine.getExecutionResult(); assertThat(result).isNotNull(); assertThat(result.getConfig()).isPresent(); @@ -405,7 +420,8 @@ public void urlWithPortSet() throws Exception { "--tomlfile", configFile.toString(), "--url", "http://127.0.0.1:9001", "--port", "9001" }; - final CliResult result = CliDelegate.instance().execute(requiredParams); + commandLine.execute(requiredParams); + final CliResult result = commandLine.getExecutionResult(); assertThat(result).isNotNull(); assertThat(result.getConfig()).isPresent(); @@ -419,11 +435,11 @@ public void invalidUrlProvided() throws Exception { String[] requiredParams = {"--tomlfile", configFile.toString(), "--url", "htt://invalidHost", "--port", "9001"}; - final Throwable throwable = catchThrowable(() -> CliDelegate.instance().execute(requiredParams)); + commandLine.execute(requiredParams); + + final String output = systemErrRule.getLog(); - assertThat(throwable) - .isInstanceOf(RuntimeException.class) - .hasMessage("Bad server url given: unknown protocol: htt"); + assertThat(output).contains("Bad server url given: unknown protocol: htt"); } @Test From 2edf395d8b4bad355ba835f7e773091395868a27 Mon Sep 17 00:00:00 2001 From: chris-j-h Date: Mon, 16 Dec 2019 20:59:07 +0000 Subject: [PATCH 28/41] Database migration CLI test fixes --- .../quorum/tessera/config/migration/Main.java | 4 +- .../quorum/tessera/data/migration/Main.java | 16 ++++- .../data/migration/CmdLineExecutorTest.java | 66 +++++++++++++------ .../com/quorum/tessera/enclave/rest/Main.java | 10 +-- 4 files changed, 67 insertions(+), 29 deletions(-) diff --git a/config-migration/src/main/java/com/quorum/tessera/config/migration/Main.java b/config-migration/src/main/java/com/quorum/tessera/config/migration/Main.java index 152d1d6402..d6dd8363f6 100644 --- a/config-migration/src/main/java/com/quorum/tessera/config/migration/Main.java +++ b/config-migration/src/main/java/com/quorum/tessera/config/migration/Main.java @@ -13,14 +13,14 @@ public static void main(String... args) { System.setProperty("javax.xml.bind.context.factory", "org.eclipse.persistence.jaxb.JAXBContextFactory"); System.setProperty(CliType.CLI_TYPE_KEY, CliType.CONFIG_MIGRATION.name()); try { - CommandLine commandLine = new CommandLine(new LegacyCliAdapter()); + final CommandLine commandLine = new CommandLine(new LegacyCliAdapter()); commandLine .registerConverter(Config.class, new ConfigConverter()) .setSeparator(" ") .setCaseInsensitiveEnumValuesAllowed(true); commandLine.execute(args); - CliResult cliResult = commandLine.getExecutionResult(); + final CliResult cliResult = commandLine.getExecutionResult(); System.exit(cliResult.getStatus()); } catch (final Exception ex) { diff --git a/data-migration/src/main/java/com/quorum/tessera/data/migration/Main.java b/data-migration/src/main/java/com/quorum/tessera/data/migration/Main.java index ac99fa29fc..08030b93c7 100644 --- a/data-migration/src/main/java/com/quorum/tessera/data/migration/Main.java +++ b/data-migration/src/main/java/com/quorum/tessera/data/migration/Main.java @@ -1,8 +1,10 @@ package com.quorum.tessera.data.migration; -import com.quorum.tessera.cli.CliDelegate; import com.quorum.tessera.cli.CliResult; import com.quorum.tessera.cli.CliType; +import com.quorum.tessera.cli.parsers.ConfigConverter; +import com.quorum.tessera.config.Config; +import picocli.CommandLine; import java.util.Arrays; @@ -14,10 +16,18 @@ private Main() { public static void main(final String... args) { - System.setProperty(CliType.CLI_TYPE_KEY,CliType.DATA_MIGRATION.name()); + System.setProperty(CliType.CLI_TYPE_KEY, CliType.DATA_MIGRATION.name()); try { - final CliResult cliResult = CliDelegate.instance().execute(args); + final CommandLine commandLine = new CommandLine(new CmdLineExecutor()); + commandLine + .registerConverter(Config.class, new ConfigConverter()) + .setSeparator(" ") + .setCaseInsensitiveEnumValuesAllowed(true); + + commandLine.execute(args); + final CliResult cliResult = commandLine.getExecutionResult(); + System.exit(cliResult.getStatus()); } catch (final Exception ex) { System.err.println("An error has occurred: " + ex.getMessage()); diff --git a/data-migration/src/test/java/com/quorum/tessera/data/migration/CmdLineExecutorTest.java b/data-migration/src/test/java/com/quorum/tessera/data/migration/CmdLineExecutorTest.java index 19c22f8e0e..44825a5915 100644 --- a/data-migration/src/test/java/com/quorum/tessera/data/migration/CmdLineExecutorTest.java +++ b/data-migration/src/test/java/com/quorum/tessera/data/migration/CmdLineExecutorTest.java @@ -1,9 +1,10 @@ package com.quorum.tessera.data.migration; import com.mockrunner.mock.jdbc.JDBCMockObjectFactory; -import com.quorum.tessera.cli.CliDelegate; import com.quorum.tessera.cli.CliResult; import com.quorum.tessera.cli.CliType; +import com.quorum.tessera.cli.parsers.ConfigConverter; +import com.quorum.tessera.config.Config; import com.sun.management.UnixOperatingSystemMXBean; import org.apache.commons.codec.binary.Base32; import org.apache.commons.io.IOUtils; @@ -13,6 +14,7 @@ import org.junit.contrib.java.lang.system.SystemErrRule; import org.junit.contrib.java.lang.system.SystemOutRule; import org.junit.rules.TestName; +import picocli.CommandLine; import java.io.InputStream; import java.lang.management.ManagementFactory; @@ -32,12 +34,20 @@ public class CmdLineExecutorTest { private Path outputPath; + private CommandLine commandLine; + @Before public void onSetup() throws Exception { - System.setProperty(CliType.CLI_TYPE_KEY,CliType.DATA_MIGRATION.name()); + System.setProperty(CliType.CLI_TYPE_KEY, CliType.DATA_MIGRATION.name()); systemErrRule.clearLog(); systemOutRule.clearLog(); this.outputPath = Files.createTempFile(testName.getMethodName(), ".db"); + + commandLine = new CommandLine(new CmdLineExecutor()); + commandLine + .registerConverter(Config.class, new ConfigConverter()) + .setSeparator(" ") + .setCaseInsensitiveEnumValuesAllowed(true); } @Test @@ -46,12 +56,14 @@ public void correctCliType() { } @Test - public void help() throws Exception { + public void help() { final String[] args = new String[] {"help"}; - final CliResult result = CliDelegate.instance().execute(args); + commandLine.execute(args); + final CliResult result = commandLine.getExecutionResult(); - assertThat(result).isEqualToComparingFieldByField(new CliResult(0, true, null)); + assertThat(result).isNull(); + // assertThat(result).isEqualToComparingFieldByField(new CliResult(0, true, null)); assertThat(systemOutRule.getLog()) .contains( "Usage:", @@ -62,18 +74,20 @@ public void help() throws Exception { } @Test - public void noOptions() throws Exception { - final CliResult result = CliDelegate.instance().execute(); + public void noOptions() { + commandLine.execute(); + final CliResult result = commandLine.getExecutionResult(); final String expectedLog = "Missing required options [-storetype , -inputpath , -exporttype , -outputfile ]"; - assertThat(result).isEqualToComparingFieldByField(new CliResult(1, true, null)); + // assertThat(result).isEqualToComparingFieldByField(new CliResult(1, true, null)); + assertThat(result).isNull(); assertThat(systemErrRule.getLog()).contains(expectedLog); } @Test - public void missingStoreTypeOption() throws Exception { + public void missingStoreTypeOption() { final String[] args = new String[] { "-inputpath", "somefile.txt", @@ -82,9 +96,11 @@ public void missingStoreTypeOption() throws Exception { "-dbpass", "-dbuser" }; - final CliResult result = CliDelegate.instance().execute(args); + commandLine.execute(args); + final CliResult result = commandLine.getExecutionResult(); - assertThat(result).isEqualToComparingFieldByField(new CliResult(1, true, null)); + // assertThat(result).isEqualToComparingFieldByField(new CliResult(1, true, null)); + assertThat(result).isNull(); assertThat(systemErrRule.getLog()).contains("Missing required option '-storetype '"); } @@ -98,9 +114,11 @@ public void missingInputFileOption() throws Exception { "-dbpass", "-dbuser" }; - final CliResult result = CliDelegate.instance().execute(args); + commandLine.execute(args); + final CliResult result = commandLine.getExecutionResult(); - assertThat(result).isEqualToComparingFieldByField(new CliResult(1, true, null)); + // assertThat(result).isEqualToComparingFieldByField(new CliResult(1, true, null)); + assertThat(result).isNull(); assertThat(systemErrRule.getLog()).contains("Missing required option '-inputpath '"); } @@ -117,7 +135,8 @@ public void bdbStoreType() throws Exception { "-dbpass", "-dbuser" }; - CliDelegate.instance().execute(args); + commandLine.execute(args); + final CliResult result = commandLine.getExecutionResult(); } @Test @@ -133,10 +152,11 @@ public void dirStoreType() throws Exception { "-dbpass", "-dbuser" }; - CliDelegate.instance().execute(args); + commandLine.execute(args); + final CliResult result = commandLine.getExecutionResult(); } - @Test(expected = IllegalArgumentException.class) + @Test() public void exportTypeJdbcNoDbConfigProvided() throws Exception { final Path inputFile = Paths.get(getClass().getResource("/dir/").toURI()); @@ -149,7 +169,13 @@ public void exportTypeJdbcNoDbConfigProvided() throws Exception { "-dbpass", "-dbuser" }; - CliDelegate.instance().execute(args); + commandLine.execute(args); + final CliResult result = commandLine.getExecutionResult(); + + String output = systemErrRule.getLog(); + assertThat(output) + .contains( + "java.lang.IllegalArgumentException: dbconfig file path is required when no export type is defined."); } @Test @@ -180,7 +206,8 @@ public void exportTypeJdbc() throws Exception { "-dbuser" }; - CliDelegate.instance().execute(args); + commandLine.execute(args); + final CliResult result = commandLine.getExecutionResult(); } finally { mockObjectFactory.restoreDrivers(); } @@ -233,6 +260,7 @@ public void directoryStoreAndSqliteWithLotsOfFilesWorks() throws Exception { "-dbpass", "-dbuser" }; - CliDelegate.instance().execute(args); + commandLine.execute(args); + final CliResult result = commandLine.getExecutionResult(); } } diff --git a/enclave/enclave-jaxrs/src/main/java/com/quorum/tessera/enclave/rest/Main.java b/enclave/enclave-jaxrs/src/main/java/com/quorum/tessera/enclave/rest/Main.java index 6a777fb095..06c001341a 100644 --- a/enclave/enclave-jaxrs/src/main/java/com/quorum/tessera/enclave/rest/Main.java +++ b/enclave/enclave-jaxrs/src/main/java/com/quorum/tessera/enclave/rest/Main.java @@ -25,14 +25,14 @@ public static void main(String... args) throws Exception { System.setProperty("javax.xml.bind.JAXBContextFactory", "org.eclipse.persistence.jaxb.JAXBContextFactory"); System.setProperty("javax.xml.bind.context.factory", "org.eclipse.persistence.jaxb.JAXBContextFactory"); - CommandLine commandLine = new CommandLine(new EnclaveCliAdapter()); + final CommandLine commandLine = new CommandLine(new EnclaveCliAdapter()); commandLine - .registerConverter(Config.class, new ConfigConverter()) - .setSeparator(" ") - .setCaseInsensitiveEnumValuesAllowed(true); + .registerConverter(Config.class, new ConfigConverter()) + .setSeparator(" ") + .setCaseInsensitiveEnumValuesAllowed(true); commandLine.execute(args); - CliResult cliResult = commandLine.getExecutionResult(); + final CliResult cliResult = commandLine.getExecutionResult(); if (!cliResult.getConfig().isPresent()) { System.exit(cliResult.getStatus()); From 204bab3355496187fffd9054d582adcc3b8c7158 Mon Sep 17 00:00:00 2001 From: chris-j-h Date: Mon, 16 Dec 2019 22:02:23 +0000 Subject: [PATCH 29/41] Pidfile CLI option and ArgonOptionsConverter tests --- .../tessera/picocli/PicoCliDelegate.java | 16 +----- .../picocli/ArgonOptionsConverterTest.java | 55 +++++++++++++++++++ .../tessera/picocli/PicoCliDelegateTest.java | 54 ++++++++++++++++++ pom.xml | 1 + 4 files changed, 111 insertions(+), 15 deletions(-) create mode 100644 cli/pico-cli/src/test/java/com/quorum/tessera/picocli/ArgonOptionsConverterTest.java diff --git a/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/PicoCliDelegate.java b/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/PicoCliDelegate.java index 151320b909..045f5a1687 100644 --- a/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/PicoCliDelegate.java +++ b/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/PicoCliDelegate.java @@ -1,7 +1,6 @@ package com.quorum.tessera.picocli; import com.quorum.tessera.ServiceLoaderUtil; -import com.quorum.tessera.cli.CLIExceptionCapturer; import com.quorum.tessera.cli.CliException; import com.quorum.tessera.cli.CliResult; import com.quorum.tessera.cli.keypassresolver.CliKeyPasswordResolver; @@ -52,8 +51,6 @@ private PicoCliDelegate(final KeyPasswordResolver keyPasswordResolver) { public CliResult execute(String... args) throws Exception { final CommandSpec command = CommandSpec.forAnnotatedObject(TesseraCommand.class); - final CLIExceptionCapturer mapper = new CLIExceptionCapturer(); - final CommandLine.IFactory keyGenCommandFactory = new KeyGenCommandFactory(); CommandLine keyGenCommandLine = new CommandLine(KeyGenCommand.class, keyGenCommandFactory); @@ -69,9 +66,7 @@ public CliResult execute(String... args) throws Exception { .registerConverter(Config.class, new ConfigConverter()) .registerConverter(ArgonOptions.class, new ArgonOptionsConverter()) .setSeparator(" ") - .setCaseInsensitiveEnumValuesAllowed(true) - .setExecutionExceptionHandler(mapper) - .setParameterExceptionHandler(mapper); + .setCaseInsensitiveEnumValuesAllowed(true); final CommandLine.ParseResult parseResult; try { @@ -124,11 +119,6 @@ public CliResult execute(String... args) throws Exception { // TODO(cjh) document the change of behaviour meaning node cannot start after keygen subParseResult.asCommandLineList().get(0).execute(subArgs); - // if an exception occurred, throw it to to the upper levels where it gets handled - if (mapper.getThrown() != null) { - throw mapper.getThrown(); - } - return new CliResult(0, true, null); } } @@ -177,10 +167,6 @@ private Config getConfigFromCLI(CommandLine.ParseResult parseResult) throws Exce } private void createPidFile(Path pidFilePath) throws Exception { - if (pidFilePath == null) { - return; - } - if (Files.exists(pidFilePath)) { LOGGER.info("File already exists {}", pidFilePath); } else { diff --git a/cli/pico-cli/src/test/java/com/quorum/tessera/picocli/ArgonOptionsConverterTest.java b/cli/pico-cli/src/test/java/com/quorum/tessera/picocli/ArgonOptionsConverterTest.java new file mode 100644 index 0000000000..a47dde6080 --- /dev/null +++ b/cli/pico-cli/src/test/java/com/quorum/tessera/picocli/ArgonOptionsConverterTest.java @@ -0,0 +1,55 @@ +package com.quorum.tessera.picocli; + +import com.quorum.tessera.config.ArgonOptions; +import org.junit.Before; +import org.junit.Test; + +import java.io.FileNotFoundException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ArgonOptionsConverterTest { + + private ArgonOptionsConverter argonOptionsConverter; + + @Before + public void onSetUp() { + argonOptionsConverter = new ArgonOptionsConverter(); + } + + @Test(expected = FileNotFoundException.class) + public void fileNotFound() throws Exception { + argonOptionsConverter.convert("path/to/nothing"); + } + + @Test + public void fileContainsValidArgonJsonConfig() throws Exception { + final String algorithm = "id"; + final Integer iterations = 10; + final Integer memory = 10; + final Integer parallelism = 10; + + final String config = + String.format( + "{\"variant\": \"%s\", \"iterations\":%s, \"memory\":%s, \"parallelism\":%s}", + algorithm, iterations, memory, parallelism); + + final Path argonPath = Files.createTempFile(UUID.randomUUID().toString(), ""); + argonPath.toFile().deleteOnExit(); + + Files.write(argonPath, config.getBytes()); + + final ArgonOptions result = argonOptionsConverter.convert(argonPath.toString()); + + final ArgonOptions expected = new ArgonOptions(); + expected.setAlgorithm(algorithm); + expected.setIterations(iterations); + expected.setMemory(memory); + expected.setParallelism(parallelism); + + assertThat(result).isEqualToComparingFieldByField(expected); + } +} diff --git a/cli/pico-cli/src/test/java/com/quorum/tessera/picocli/PicoCliDelegateTest.java b/cli/pico-cli/src/test/java/com/quorum/tessera/picocli/PicoCliDelegateTest.java index 9897c05327..6f91f11e73 100644 --- a/cli/pico-cli/src/test/java/com/quorum/tessera/picocli/PicoCliDelegateTest.java +++ b/cli/pico-cli/src/test/java/com/quorum/tessera/picocli/PicoCliDelegateTest.java @@ -27,6 +27,7 @@ import java.nio.file.FileAlreadyExistsException; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.Paths; import java.util.Arrays; import java.util.HashMap; import java.util.Map; @@ -71,6 +72,17 @@ public void noArgsPrintsHelp() throws Exception { assertThat(result.isSuppressStartup()).isTrue(); } + @Test + public void subcommandWithNoArgsPrintsHelp() throws Exception { + + final CliResult result = cliDelegate.execute("keygen"); + + assertThat(result).isNotNull(); + assertThat(result.getConfig()).isNotPresent(); + assertThat(result.getStatus()).isEqualTo(0); + assertThat(result.isSuppressStartup()).isTrue(); + } + @Test public void withValidConfig() throws Exception { @@ -83,6 +95,48 @@ public void withValidConfig() throws Exception { assertThat(result.isSuppressStartup()).isFalse(); } + @Test + public void withValidConfigAndPidfile() throws Exception { + + Path configFile = createAndPopulatePaths(getClass().getResource("/sample-config.json")); + + String tempDir = System.getProperty("java.io.tmpdir"); + Path pidFilePath = Paths.get(tempDir, UUID.randomUUID().toString()); + + assertThat(pidFilePath).doesNotExist(); + + CliResult result = + cliDelegate.execute("-configfile", configFile.toString(), "-pidfile", pidFilePath.toString()); + + assertThat(pidFilePath).exists(); + pidFilePath.toFile().deleteOnExit(); + + assertThat(result).isNotNull(); + assertThat(result.getConfig()).isPresent(); + assertThat(result.getStatus()).isEqualTo(0); + assertThat(result.isSuppressStartup()).isFalse(); + } + + @Test + public void withValidConfigAndPidfileAlreadyExists() throws Exception { + + Path configFile = createAndPopulatePaths(getClass().getResource("/sample-config.json")); + Path pidFilePath = Files.createTempFile(UUID.randomUUID().toString(), ""); + pidFilePath.toFile().deleteOnExit(); + + assertThat(pidFilePath).exists(); + + CliResult result = + cliDelegate.execute("-configfile", configFile.toString(), "-pidfile", pidFilePath.toString()); + + assertThat(pidFilePath).exists(); + + assertThat(result).isNotNull(); + assertThat(result.getConfig()).isPresent(); + assertThat(result.getStatus()).isEqualTo(0); + assertThat(result.isSuppressStartup()).isFalse(); + } + @Test(expected = CliException.class) public void processArgsMissing() throws Exception { cliDelegate.execute("-configfile"); diff --git a/pom.xml b/pom.xml index 40d797d971..eab5421905 100644 --- a/pom.xml +++ b/pom.xml @@ -324,6 +324,7 @@ com/quorum/tessera/config/cli/DefaultCliAdapter* com/quorum/tessera/config/cli/parsers/KeyGenerationParser* com/quorum/tessera/config/cli/parsers/EncryptorConfigParser* + com/quorum/tessera/config/cli/parsers/KeyUpdateParser* From 940365cbbbb026c0857efc145527aa10bed02223 Mon Sep 17 00:00:00 2001 From: chris-j-h Date: Tue, 17 Dec 2019 09:42:09 +0000 Subject: [PATCH 30/41] Enable admin CLI subcommand and fix tests --- cli/pico-cli/pom.xml | 4 ++++ .../tessera/picocli/PicoCliDelegate.java | 2 ++ tessera-core/pom.xml | 6 ------ .../java/com/quorum/tessera/core/CoreIT.java | 14 +++++-------- tests/acceptance-test/pom.xml | 14 ++++++------- .../src/test/java/admin/cmd/Utils.java | 20 ++++++++----------- .../src/test/java/exec/ExecArgsBuilder.java | 16 +++++++++++++++ 7 files changed, 42 insertions(+), 34 deletions(-) diff --git a/cli/pico-cli/pom.xml b/cli/pico-cli/pom.xml index 2279cc78fd..506baf8efd 100644 --- a/cli/pico-cli/pom.xml +++ b/cli/pico-cli/pom.xml @@ -19,6 +19,10 @@ com.jpmorgan.quorum config-cli + + com.jpmorgan.quorum + admin-cli + diff --git a/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/PicoCliDelegate.java b/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/PicoCliDelegate.java index 045f5a1687..c58a106899 100644 --- a/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/PicoCliDelegate.java +++ b/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/PicoCliDelegate.java @@ -1,6 +1,7 @@ package com.quorum.tessera.picocli; import com.quorum.tessera.ServiceLoaderUtil; +import com.quorum.tessera.admin.cli.AdminCliAdapter; import com.quorum.tessera.cli.CliException; import com.quorum.tessera.cli.CliResult; import com.quorum.tessera.cli.keypassresolver.CliKeyPasswordResolver; @@ -58,6 +59,7 @@ public CliResult execute(String... args) throws Exception { CommandLine keyUpdateCommandLine = new CommandLine(KeyUpdateCommand.class, keyUpdateCommandFactory); command.addSubcommand(null, new CommandLine(CommandLine.HelpCommand.class)); + command.addSubcommand(null, new CommandLine(AdminCliAdapter.class)); command.addSubcommand(null, keyGenCommandLine); command.addSubcommand(null, keyUpdateCommandLine); diff --git a/tessera-core/pom.xml b/tessera-core/pom.xml index 1603faf39d..4df29ab5cc 100644 --- a/tessera-core/pom.xml +++ b/tessera-core/pom.xml @@ -93,12 +93,6 @@ test jar - - com.jpmorgan.quorum - pico-cli - 0.11-SNAPSHOT - test - tessera-core diff --git a/tessera-core/src/test/java/com/quorum/tessera/core/CoreIT.java b/tessera-core/src/test/java/com/quorum/tessera/core/CoreIT.java index 50fca1f4a0..ab9d23c33a 100644 --- a/tessera-core/src/test/java/com/quorum/tessera/core/CoreIT.java +++ b/tessera-core/src/test/java/com/quorum/tessera/core/CoreIT.java @@ -1,7 +1,5 @@ - package com.quorum.tessera.core; -import com.quorum.tessera.picocli.PicoCliDelegate; import com.quorum.tessera.transaction.TransactionManager; import org.junit.BeforeClass; import org.junit.Test; @@ -19,9 +17,7 @@ @ContextConfiguration(locations = "classpath:tessera-core-spring.xml") public class CoreIT { - - @Inject - private TransactionManager transactionManager; + @Inject private TransactionManager transactionManager; @PersistenceContext(unitName = "tessera") private EntityManager entityManager; @@ -29,14 +25,14 @@ public class CoreIT { @BeforeClass public static void onSetup() throws Exception { String configPath = CoreIT.class.getResource("/config1.json").getPath(); - PicoCliDelegate picoCliDelegate = new PicoCliDelegate(); - picoCliDelegate.execute("-configfile", configPath); -} + // TODO(cjh) introduces a circular dependency between jaxrs-client module and picocli module + // PicoCliDelegate picoCliDelegate = new PicoCliDelegate(); + // picoCliDelegate.execute("-configfile", configPath); + } @Test public void doStuff() throws Exception { assertThat(transactionManager).isNotNull(); assertThat(entityManager).isNotNull(); } - } diff --git a/tests/acceptance-test/pom.xml b/tests/acceptance-test/pom.xml index e8b4a4669b..7941601738 100644 --- a/tests/acceptance-test/pom.xml +++ b/tests/acceptance-test/pom.xml @@ -198,14 +198,14 @@ ${com.jpmorgan.quorum:config-migration:jar:cli} - CucumberFileKeyGenerationIT + AdminRestSuite - RestSuiteHttp* - RestSuiteUnixH2 - CucumberWhitelistIT - ConfigMigrationIT - P2pTestSuite - SendWithRemoteEnclaveReconnectIT + + + + + + diff --git a/tests/acceptance-test/src/test/java/admin/cmd/Utils.java b/tests/acceptance-test/src/test/java/admin/cmd/Utils.java index 4160958198..9773370d1b 100644 --- a/tests/acceptance-test/src/test/java/admin/cmd/Utils.java +++ b/tests/acceptance-test/src/test/java/admin/cmd/Utils.java @@ -22,8 +22,6 @@ public class Utils { private static final Logger LOGGER = LoggerFactory.getLogger(Utils.class); - - public static ExecutionResult start(Party party) throws IOException, InterruptedException { List args = @@ -91,16 +89,14 @@ public static ExecutionResult start(Party party) throws IOException, Interrupted public static int addPeer(Party party, String url) throws IOException, InterruptedException { - List args = new ExecArgsBuilder() - .withJvmArg(String.format("-Dnode.number=%S", party.getAlias())) - .withStartScriptOrExecutableJarFile(Paths.get(jarPath)) - .withConfigFile(party.getConfigFilePath()) - .withArg("admin") - .withArg("-addpeer",url) - .withArg("-configfile",party.getConfigFilePath().toAbsolutePath().toString()) - .build(); - - + List args = + new ExecArgsBuilder() + .withJvmArg(String.format("-Dnode.number=%S", party.getAlias())) + .withStartScriptOrExecutableJarFile(Paths.get(jarPath)) + .withConfigFile(party.getConfigFilePath()) + .withSubcommands("admin", "addpeer") + .withArg(url) + .build(); LOGGER.info("exec : {}", String.join(" ", args)); ProcessBuilder processBuilder = new ProcessBuilder(args); diff --git a/tests/acceptance-test/src/test/java/exec/ExecArgsBuilder.java b/tests/acceptance-test/src/test/java/exec/ExecArgsBuilder.java index 727e9370b5..df36b4e7b8 100644 --- a/tests/acceptance-test/src/test/java/exec/ExecArgsBuilder.java +++ b/tests/acceptance-test/src/test/java/exec/ExecArgsBuilder.java @@ -1,6 +1,7 @@ package exec; import com.quorum.tessera.config.Config; + import java.io.File; import java.nio.file.Path; import java.nio.file.Paths; @@ -13,6 +14,8 @@ public class ExecArgsBuilder { private Path configFile; + private List subcommands; + private Path pidFile; private Class mainClass; @@ -78,6 +81,15 @@ private ExecArgsBuilder withStartScript(Path startScript) { return this; } + public ExecArgsBuilder withSubcommands(String subcommand, String... s) { + List subcommands = new ArrayList<>(); + subcommands.add(subcommand); + subcommands.addAll(Arrays.asList(s)); + + this.subcommands = subcommands; + return this; + } + public ExecArgsBuilder withArg(String name) { argList.put(name, null); return this; @@ -122,6 +134,10 @@ public List build() { tokens.add(startScript.toAbsolutePath().toString()); } + if (Objects.nonNull(subcommands)) { + tokens.addAll(subcommands); + } + tokens.add("-configfile"); tokens.add(configFile.toAbsolutePath().toString()); From 445af90224e6506a5fe8a3a648c3845fcf37220d Mon Sep 17 00:00:00 2001 From: chris-j-h Date: Tue, 17 Dec 2019 09:42:33 +0000 Subject: [PATCH 31/41] Update ConfigFileStore during startup for use in config update methods --- .../java/com/quorum/tessera/cli/parsers/ConfigConverter.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cli/cli-api/src/main/java/com/quorum/tessera/cli/parsers/ConfigConverter.java b/cli/cli-api/src/main/java/com/quorum/tessera/cli/parsers/ConfigConverter.java index 67d817f397..d32ddd4a32 100644 --- a/cli/cli-api/src/main/java/com/quorum/tessera/cli/parsers/ConfigConverter.java +++ b/cli/cli-api/src/main/java/com/quorum/tessera/cli/parsers/ConfigConverter.java @@ -2,6 +2,7 @@ import com.quorum.tessera.config.Config; import com.quorum.tessera.config.ConfigFactory; +import com.quorum.tessera.config.util.ConfigFileStore; import picocli.CommandLine; import java.io.FileNotFoundException; @@ -22,6 +23,8 @@ public Config convert(final String value) throws Exception { throw new FileNotFoundException(String.format("%s not found.", path)); } + ConfigFileStore.create(path); + try (InputStream in = Files.newInputStream(path)) { return configFactory.create(in); } From e96de75650cac66b509b1699eec00fba16384614 Mon Sep 17 00:00:00 2001 From: chris-j-h Date: Tue, 17 Dec 2019 19:38:48 +0000 Subject: [PATCH 32/41] Add comma split on multi keygen option & subcommand exception mapper --- .../com/quorum/tessera/picocli/KeyGenCommand.java | 9 +++++---- .../quorum/tessera/picocli/PicoCliDelegate.java | 12 +++++++++++- .../java/com/quorum/tessera/launcher/Main.java | 2 +- tests/acceptance-test/pom.xml | 14 +++++++------- .../tessera/test/cli/keygen/FileKeygenSteps.java | 4 +--- 5 files changed, 25 insertions(+), 16 deletions(-) diff --git a/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/KeyGenCommand.java b/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/KeyGenCommand.java index 5b13a0bde0..0decfe7ef2 100644 --- a/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/KeyGenCommand.java +++ b/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/KeyGenCommand.java @@ -40,7 +40,7 @@ public class KeyGenCommand implements Callable { @CommandLine.Option( names = {"--keyout", "-filename"}, - defaultValue = ".", + split = ",", description = "Comma-separated list of paths to save generated key files. Can also be used with keyvault. Number of args determines number of key-pairs generated (default = ${DEFAULT-VALUE})") public List keyOut; @@ -90,9 +90,9 @@ public class KeyGenCommand implements Callable { // TODO(cjh) implement config output @CommandLine.Option( - names = {"--configout", "-output"}, - description = - "Path to save updated configfile to. Updated config will be printed to terminal if not provided. Only valid if --configfile option also provided.") + names = {"--configout", "-output"}, + description = + "Path to save updated configfile to. Updated config will be printed to terminal if not provided. Only valid if --configfile option also provided.") public List configOut; @CommandLine.Mixin public EncryptorOptions encryptorOptions; @@ -101,6 +101,7 @@ public class KeyGenCommand implements Callable { this.factory = keyGeneratorFactory; } + // TODO no args prints help, should generate default location keys to keep same behaviour as before @Override public CliResult call() { final EncryptorConfig encryptorConfig; diff --git a/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/PicoCliDelegate.java b/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/PicoCliDelegate.java index c58a106899..b56b533f4e 100644 --- a/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/PicoCliDelegate.java +++ b/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/PicoCliDelegate.java @@ -2,6 +2,7 @@ import com.quorum.tessera.ServiceLoaderUtil; import com.quorum.tessera.admin.cli.AdminCliAdapter; +import com.quorum.tessera.cli.CLIExceptionCapturer; import com.quorum.tessera.cli.CliException; import com.quorum.tessera.cli.CliResult; import com.quorum.tessera.cli.keypassresolver.CliKeyPasswordResolver; @@ -52,6 +53,8 @@ private PicoCliDelegate(final KeyPasswordResolver keyPasswordResolver) { public CliResult execute(String... args) throws Exception { final CommandSpec command = CommandSpec.forAnnotatedObject(TesseraCommand.class); + final CLIExceptionCapturer mapper = new CLIExceptionCapturer(); + final CommandLine.IFactory keyGenCommandFactory = new KeyGenCommandFactory(); CommandLine keyGenCommandLine = new CommandLine(KeyGenCommand.class, keyGenCommandFactory); @@ -68,7 +71,9 @@ public CliResult execute(String... args) throws Exception { .registerConverter(Config.class, new ConfigConverter()) .registerConverter(ArgonOptions.class, new ArgonOptionsConverter()) .setSeparator(" ") - .setCaseInsensitiveEnumValuesAllowed(true); + .setCaseInsensitiveEnumValuesAllowed(true) + .setExecutionExceptionHandler(mapper) + .setParameterExceptionHandler(mapper); final CommandLine.ParseResult parseResult; try { @@ -121,6 +126,11 @@ public CliResult execute(String... args) throws Exception { // TODO(cjh) document the change of behaviour meaning node cannot start after keygen subParseResult.asCommandLineList().get(0).execute(subArgs); + // if an exception occurred, throw it to to the upper levels where it gets handled + if (mapper.getThrown() != null) { + throw mapper.getThrown(); + } + return new CliResult(0, true, null); } } diff --git a/tessera-dist/tessera-launcher/src/main/java/com/quorum/tessera/launcher/Main.java b/tessera-dist/tessera-launcher/src/main/java/com/quorum/tessera/launcher/Main.java index c3c7796818..9017829f52 100644 --- a/tessera-dist/tessera-launcher/src/main/java/com/quorum/tessera/launcher/Main.java +++ b/tessera-dist/tessera-launcher/src/main/java/com/quorum/tessera/launcher/Main.java @@ -27,7 +27,7 @@ public class Main { private static final Logger LOGGER = LoggerFactory.getLogger(Main.class); public static void main(final String... args) throws Exception { - System.setProperty(CliType.CLI_TYPE_KEY,CliType.CONFIG.name()); + System.setProperty(CliType.CLI_TYPE_KEY, CliType.CONFIG.name()); System.setProperty("javax.xml.bind.JAXBContextFactory", "org.eclipse.persistence.jaxb.JAXBContextFactory"); System.setProperty("javax.xml.bind.context.factory", "org.eclipse.persistence.jaxb.JAXBContextFactory"); diff --git a/tests/acceptance-test/pom.xml b/tests/acceptance-test/pom.xml index 7941601738..e8b4a4669b 100644 --- a/tests/acceptance-test/pom.xml +++ b/tests/acceptance-test/pom.xml @@ -198,14 +198,14 @@ ${com.jpmorgan.quorum:config-migration:jar:cli} - + CucumberFileKeyGenerationIT AdminRestSuite - - - - - - + RestSuiteHttp* + RestSuiteUnixH2 + CucumberWhitelistIT + ConfigMigrationIT + P2pTestSuite + SendWithRemoteEnclaveReconnectIT diff --git a/tests/acceptance-test/src/test/java/com/quorum/tessera/test/cli/keygen/FileKeygenSteps.java b/tests/acceptance-test/src/test/java/com/quorum/tessera/test/cli/keygen/FileKeygenSteps.java index 154474f223..e7cfe7c11a 100644 --- a/tests/acceptance-test/src/test/java/com/quorum/tessera/test/cli/keygen/FileKeygenSteps.java +++ b/tests/acceptance-test/src/test/java/com/quorum/tessera/test/cli/keygen/FileKeygenSteps.java @@ -80,9 +80,7 @@ public FileKeygenSteps() { // here to explicitly state we are doing nothing Given("no file path is provided", () -> {}); - Given( - "a file path of {string}", - (String path) -> this.args.addAll(Arrays.asList("-filename", path, "--encryptor.type", "NACL"))); + Given("a file path of {string}", (String path) -> this.args.addAll(Arrays.asList("-filename", path))); When( "new keys are generated", From ce568536be6f1c624aa15be0422280ba83d5c8e1 Mon Sep 17 00:00:00 2001 From: chris-j-h Date: Wed, 18 Dec 2019 09:29:41 +0000 Subject: [PATCH 33/41] Test subcommand exception handling --- .../com/quorum/tessera/picocli/PicoCliDelegateTest.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/cli/pico-cli/src/test/java/com/quorum/tessera/picocli/PicoCliDelegateTest.java b/cli/pico-cli/src/test/java/com/quorum/tessera/picocli/PicoCliDelegateTest.java index 6f91f11e73..3f1b84e707 100644 --- a/cli/pico-cli/src/test/java/com/quorum/tessera/picocli/PicoCliDelegateTest.java +++ b/cli/pico-cli/src/test/java/com/quorum/tessera/picocli/PicoCliDelegateTest.java @@ -432,4 +432,12 @@ public void suppressStartupForKeygenOptionWithConfigfile() throws Exception { assertThat(cliResult.isSuppressStartup()).isTrue(); } + + @Test + public void subcommandExceptionIsThrown() { + Throwable ex = catchThrowable(() -> cliDelegate.execute("-keygen", "-keygenvaulturl", "urlButNoVaultType")); + + assertThat(ex).isNotNull(); + assertThat(ex).isInstanceOf(CliException.class); + } } From 3f5d446c569575f850b2126c29f5ec2a491585f0 Mon Sep 17 00:00:00 2001 From: chris-j-h Date: Wed, 18 Dec 2019 09:40:27 +0000 Subject: [PATCH 34/41] Add validation on argon algorithm in keyupdate cmd Update todos --- .../tessera/config/cli/OverrideUtilTest.java | 8 ++--- .../tessera/picocli/EncryptorOptions.java | 2 +- .../quorum/tessera/picocli/KeyGenCommand.java | 2 +- .../tessera/picocli/KeyUpdateCommand.java | 24 +++++++-------- .../tessera/picocli/PicoCliDelegate.java | 4 --- .../tessera/picocli/KeyUpdateCommandTest.java | 30 ++++++++++++++++--- 6 files changed, 42 insertions(+), 28 deletions(-) diff --git a/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/OverrideUtilTest.java b/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/OverrideUtilTest.java index 7f3d14bcb2..dadafcff9e 100644 --- a/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/OverrideUtilTest.java +++ b/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/OverrideUtilTest.java @@ -719,10 +719,10 @@ public void setValueSimpleCollectionInComplexCollectionExtended() { @Ignore @Test // TODO (cjh) Previously, peer overrides would be appended to any existing peers list. This has now been disabled - // so that behaviour is consistent across all options. It is now possible to overwrite existing peers or append the - // existing list depending on the position provided when calling the CLI, i.e. --peers[i]. It might be worth - // introducing an additional mode to always append so that the position doesn't have to be provided in these simpler - // situations? + // so that behaviour is consistent across all options. It is now possible to overwrite existing peers or append the + // existing list depending on the position provided when calling the CLI, i.e. --peers[i]. It might be worth + // introducing an additional mode to always append so that the position doesn't have to be provided in these simpler + // situations? public void setValuePeersAppended() { assertThat(true).isFalse(); } diff --git a/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/EncryptorOptions.java b/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/EncryptorOptions.java index b4649d35dd..3eaf29dced 100644 --- a/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/EncryptorOptions.java +++ b/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/EncryptorOptions.java @@ -31,7 +31,7 @@ class EncryptorOptions { EncryptorConfig parseEncryptorConfig() { final EncryptorConfig encryptorConfig = new EncryptorConfig(); - // we set the default here instead of in the option annotation as enum values cannot be used in annotationss + // we set the default here instead of in the option annotation as enum values cannot be used in annotations if (Objects.isNull(type)) { type = EncryptorType.NACL; } diff --git a/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/KeyGenCommand.java b/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/KeyGenCommand.java index 0decfe7ef2..4b4504c420 100644 --- a/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/KeyGenCommand.java +++ b/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/KeyGenCommand.java @@ -88,7 +88,7 @@ public class KeyGenCommand implements Callable { description = "Path to node configuration file") public Config config; - // TODO(cjh) implement config output + // TODO(cjh) implement config output and password file update ? @CommandLine.Option( names = {"--configout", "-output"}, description = diff --git a/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/KeyUpdateCommand.java b/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/KeyUpdateCommand.java index 6c902bd6ff..99f69bad65 100644 --- a/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/KeyUpdateCommand.java +++ b/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/KeyUpdateCommand.java @@ -1,5 +1,6 @@ package com.quorum.tessera.picocli; +import com.quorum.tessera.cli.CliException; import com.quorum.tessera.cli.CliResult; import com.quorum.tessera.config.*; import com.quorum.tessera.config.keys.KeyEncryptor; @@ -41,15 +42,11 @@ public class KeyUpdateCommand implements Callable { private static final Logger LOGGER = LoggerFactory.getLogger(KeyUpdateCommand.class); - // TODO(cjh) don't hardcode these options (?) - @CommandLine.Option(names = "--keys.keyData.privateKeyPath", required = true) public Path privateKeyPath; - // @Pattern(regexp = "^(id|i|d)$") - // @XmlAttribute(name = "variant") - // TODO(cjh) validation on the CLI values - the above is the validation applied to the Config ArgonOptions object - // fields + static String invalidArgonAlgorithmMsg = "Allowed values for --keys.keyData.config.data.aopts.algorithm are 'i', 'd' or 'id'"; + @CommandLine.Option(names = "--keys.keyData.config.data.aopts.algorithm", defaultValue = "i") public String algorithm; @@ -62,7 +59,7 @@ public class KeyUpdateCommand implements Callable { @CommandLine.Option(names = "--keys.keyData.config.data.aopts.parallelism", defaultValue = "4") public Integer parallelism; - //TODO(cjh) remove plaintext passwords being provided on CLI, replace with prompt/password file + //TODO(cjh) remove plaintext passwords being provided on CLI, replace with prompt and password file @CommandLine.Option(names = {"--keys.passwords"}) public String password; @@ -153,11 +150,6 @@ PrivateKey getExistingKey(final KeyDataConfig kdc, final List passwords) } Path privateKeyPath() { - //// TODO(cjh)shouldn't need this as the option should be marked as required - CHECK! - // if (privateKeyPath == null) { - // throw new IllegalArgumentException("Private key path cannot be null when updating key password"); - // } - if (Files.notExists(privateKeyPath)) { throw new IllegalArgumentException("Private key path must exist when updating key password"); } @@ -176,7 +168,11 @@ List passwords() throws IOException { } ArgonOptions argonOptions() { - return new ArgonOptions( - algorithm, Integer.valueOf(iterations), Integer.valueOf(memory), Integer.valueOf(parallelism)); + if ("i".equals(algorithm) || "d".equals(algorithm) || "id".equals(algorithm)) { + return new ArgonOptions( + algorithm, Integer.valueOf(iterations), Integer.valueOf(memory), Integer.valueOf(parallelism)); + } + + throw new CliException(invalidArgonAlgorithmMsg); } } diff --git a/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/PicoCliDelegate.java b/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/PicoCliDelegate.java index b56b533f4e..8780380108 100644 --- a/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/PicoCliDelegate.java +++ b/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/PicoCliDelegate.java @@ -33,7 +33,6 @@ import static java.nio.file.StandardOpenOption.CREATE; import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING; -// TODO(cjh) make sure recent changes to old CLI (e.g. parser behaviour) are included where needed public class PicoCliDelegate { private static final Logger LOGGER = LoggerFactory.getLogger(PicoCliDelegate.class); @@ -79,9 +78,6 @@ public CliResult execute(String... args) throws Exception { try { parseResult = commandLine.parseArgs(args); } catch (CommandLine.ParameterException ex) { - // TODO(cjh) this is ripped from commandLine.execute(...) - check whether it is sufficient, or - // if it can be replaced by using the mapper - // exception mapper can't be used here as we haven't called commandLine.execute() try { commandLine.getParameterExceptionHandler().handleParseException(ex, args); throw new CliException(ex.getMessage()); diff --git a/cli/pico-cli/src/test/java/com/quorum/tessera/picocli/KeyUpdateCommandTest.java b/cli/pico-cli/src/test/java/com/quorum/tessera/picocli/KeyUpdateCommandTest.java index 1a7b126473..5bc8aa82c7 100644 --- a/cli/pico-cli/src/test/java/com/quorum/tessera/picocli/KeyUpdateCommandTest.java +++ b/cli/pico-cli/src/test/java/com/quorum/tessera/picocli/KeyUpdateCommandTest.java @@ -1,5 +1,6 @@ package com.quorum.tessera.picocli; +import com.quorum.tessera.cli.CliException; import com.quorum.tessera.config.*; import com.quorum.tessera.config.keys.KeyEncryptor; import com.quorum.tessera.config.keys.KeyEncryptorFactory; @@ -22,8 +23,7 @@ import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.catchThrowable; +import static org.assertj.core.api.Assertions.*; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; @@ -57,7 +57,7 @@ public void onTeardown() { // Argon Option tests // TODO(cjh) re-enable this once the tests have become more integration-based (i.e. I think defaults will only be - // set when creating a command line object and calling parseArgs or execute + // set when creating a command line object and calling parseArgs or execute @Ignore @Test public void noArgonOptionsGivenHasDefaults() throws ParseException { @@ -86,6 +86,28 @@ public void argonOptionsGivenHasOverrides() { assertThat(argonOptions.getIterations()).isEqualTo(100); } + @Test + public void argonOptionsInvalidTypeThrowsException() { + command.memory = 100; + command.iterations = 100; + command.parallelism = 100; + + command.algorithm = "i"; + command.argonOptions(); + + command.algorithm = "d"; + command.argonOptions(); + + command.algorithm = "id"; + command.argonOptions(); + + command.algorithm = "invalid"; + Throwable ex = catchThrowable(() -> command.argonOptions()); + + assertThat(ex).isInstanceOf(CliException.class); + assertThat(ex).hasMessage(KeyUpdateCommand.invalidArgonAlgorithmMsg); + } + // Password reading tests @Test public void inlinePasswordParsed() throws IOException { @@ -126,7 +148,7 @@ public void emptyListGivenForNoPasswords() throws IOException { // key file tests // TODO(cjh) re-enable this once the tests have become more integration-based (i.e. required fields can be tested - // when creating a command line object and calling parseArgs or execute + // when creating a command line object and calling parseArgs or execute @Ignore @Test public void noPrivateKeyGivenThrowsError() { From 3615821881c9ceb8d2f05d8927499f3c88acb32d Mon Sep 17 00:00:00 2001 From: chris-j-h Date: Wed, 18 Dec 2019 10:19:09 +0000 Subject: [PATCH 35/41] Update keygen argon file option name --- .../quorum/tessera/picocli/KeyGenCommand.java | 16 ++++++++-------- .../tessera/picocli/KeyGenCommandTest.java | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/KeyGenCommand.java b/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/KeyGenCommand.java index 4b4504c420..382c03fbfd 100644 --- a/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/KeyGenCommand.java +++ b/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/KeyGenCommand.java @@ -45,11 +45,10 @@ public class KeyGenCommand implements Callable { "Comma-separated list of paths to save generated key files. Can also be used with keyvault. Number of args determines number of key-pairs generated (default = ${DEFAULT-VALUE})") public List keyOut; - // TODO(cjh) review description and name @CommandLine.Option( - names = {"--encryptionconfig", "-keygenconfig"}, - description = "File containing Argon2 encryption config used to secure the new private key") - public ArgonOptions encryptionConfig; + names = {"--argonconfig", "-keygenconfig"}, + description = "File containing Argon2 encryption config used to secure the new private key when storing to the filesystem") + public ArgonOptions argonOptions; @CommandLine.Option( names = {"--vault.type", "-keygenvaulttype"}, @@ -82,13 +81,14 @@ public class KeyGenCommand implements Callable { description = "Path to JKS truststore for TLS Hashicorp Vault communication") public Path hashicorpTlsTruststore; - // TODO(cjh) do something about the duplication of the configfile option in each relevant command @CommandLine.Option( names = {"--configfile", "-configfile"}, description = "Path to node configuration file") public Config config; // TODO(cjh) implement config output and password file update ? + // we've removed the ability to start the node straight away after generating keys. Not sure if updating configfile + // and password file is something we want to still support or put onus on users to go and update as required @CommandLine.Option( names = {"--configout", "-output"}, description = @@ -101,7 +101,7 @@ public class KeyGenCommand implements Callable { this.factory = keyGeneratorFactory; } - // TODO no args prints help, should generate default location keys to keep same behaviour as before + // TODO(cjh) no args prints help, should generate default location keys to keep same behaviour as before @Override public CliResult call() { final EncryptorConfig encryptorConfig; @@ -118,9 +118,9 @@ public CliResult call() { final KeyGenerator generator = factory.create(keyVaultConfig, encryptorConfig); if (Objects.isNull(keyOut) || keyOut.isEmpty()) { - generator.generate("", encryptionConfig, keyVaultOptions); + generator.generate("", argonOptions, keyVaultOptions); } else { - keyOut.forEach(name -> generator.generate(name, encryptionConfig, keyVaultOptions)); + keyOut.forEach(name -> generator.generate(name, argonOptions, keyVaultOptions)); } return new CliResult(0, true, null); diff --git a/cli/pico-cli/src/test/java/com/quorum/tessera/picocli/KeyGenCommandTest.java b/cli/pico-cli/src/test/java/com/quorum/tessera/picocli/KeyGenCommandTest.java index 3be6238f8c..3646a2d44c 100644 --- a/cli/pico-cli/src/test/java/com/quorum/tessera/picocli/KeyGenCommandTest.java +++ b/cli/pico-cli/src/test/java/com/quorum/tessera/picocli/KeyGenCommandTest.java @@ -135,7 +135,7 @@ public void providedKeyEncryptionConfigIsUsed() { when(encryptorOptions.parseEncryptorConfig()).thenReturn(encryptorConfig); command.encryptorOptions = encryptorOptions; - command.encryptionConfig = argonOptions; + command.argonOptions = argonOptions; final KeyGenerator keyGenerator = mock(KeyGenerator.class); when(keyGeneratorFactory.create(any(), any())).thenReturn(keyGenerator); From 8952c2c6a227b9c47ff99a3d3e7349f6a1a68a59 Mon Sep 17 00:00:00 2001 From: chris-j-h Date: Wed, 18 Dec 2019 10:24:46 +0000 Subject: [PATCH 36/41] Return correct status code for updatepassword cmd Update todos --- .../src/main/java/com/quorum/tessera/cli/CliDelegate.java | 1 + .../java/com/quorum/tessera/picocli/KeyGenCommand.java | 6 ++++-- .../java/com/quorum/tessera/picocli/KeyUpdateCommand.java | 7 +++---- .../java/com/quorum/tessera/picocli/PicoCliDelegate.java | 2 ++ 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/cli/cli-api/src/main/java/com/quorum/tessera/cli/CliDelegate.java b/cli/cli-api/src/main/java/com/quorum/tessera/cli/CliDelegate.java index d80facc56a..0575f4097d 100644 --- a/cli/cli-api/src/main/java/com/quorum/tessera/cli/CliDelegate.java +++ b/cli/cli-api/src/main/java/com/quorum/tessera/cli/CliDelegate.java @@ -4,6 +4,7 @@ import java.util.Optional; +// TODO(cjh) still using CliDelegate as a config store so that config can be injected by spring public enum CliDelegate { INSTANCE; diff --git a/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/KeyGenCommand.java b/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/KeyGenCommand.java index 382c03fbfd..5419a65532 100644 --- a/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/KeyGenCommand.java +++ b/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/KeyGenCommand.java @@ -36,7 +36,7 @@ public class KeyGenCommand implements Callable { private final Validator validator = Validation.byDefaultProvider().configure().ignoreXmlConfiguration().buildValidatorFactory().getValidator(); - // TODO(cjh) raise CLI usage wording changes as separate change + // TODO(cjh) changes have been made to CLI option descriptions. should these be raised as a separate change ? @CommandLine.Option( names = {"--keyout", "-filename"}, @@ -101,7 +101,9 @@ public class KeyGenCommand implements Callable { this.factory = keyGeneratorFactory; } - // TODO(cjh) no args prints help, should generate default location keys to keep same behaviour as before + // TODO(cjh) 'tessera keygen' with no args prints help. this is consistent with the other commands' behaviour, but + // previously this would generate keys at the default location '.'. do we want to reintroduce this or keep + // consistency with the other commands? @Override public CliResult call() { final EncryptorConfig encryptorConfig; diff --git a/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/KeyUpdateCommand.java b/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/KeyUpdateCommand.java index 99f69bad65..e097918b35 100644 --- a/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/KeyUpdateCommand.java +++ b/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/KeyUpdateCommand.java @@ -59,7 +59,8 @@ public class KeyUpdateCommand implements Callable { @CommandLine.Option(names = "--keys.keyData.config.data.aopts.parallelism", defaultValue = "4") public Integer parallelism; - //TODO(cjh) remove plaintext passwords being provided on CLI, replace with prompt and password file + //TODO(cjh) remove plaintext passwords being provided on CLI, replace with prompt and password file - can be done as + // a separate change @CommandLine.Option(names = {"--keys.passwords"}) public String password; @@ -125,9 +126,7 @@ public CliResult execute() throws IOException { Files.write(keypath, JaxbUtil.marshalToString(updatedKey).getBytes(UTF_8)); SystemAdapter.INSTANCE.out().println("Private key at " + keypath.toString() + " updated."); - // return Optional.empty(); - // TODO(cjh) compare with existing behaviour - return new CliResult(1, true, null); + return new CliResult(0, true, null); } PrivateKey getExistingKey(final KeyDataConfig kdc, final List passwords) { diff --git a/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/PicoCliDelegate.java b/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/PicoCliDelegate.java index 8780380108..6875a02af0 100644 --- a/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/PicoCliDelegate.java +++ b/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/PicoCliDelegate.java @@ -33,6 +33,8 @@ import static java.nio.file.StandardOpenOption.CREATE; import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING; +// TODO(cjh) clean up cli-api and config-cli modules. the parser and adapter behaviour should be encapsulated in these commands so are no longer needed + public class PicoCliDelegate { private static final Logger LOGGER = LoggerFactory.getLogger(PicoCliDelegate.class); From 5f8b58429e39077713225fb7cf2236c8dbd2c9dc Mon Sep 17 00:00:00 2001 From: Mark Lowe Date: Wed, 18 Dec 2019 16:18:31 +0000 Subject: [PATCH 37/41] Add admin cli as as dependency for pico cli module (reflect maven changes). Ignore coverage for parsers and other code that's due for removal. --- build.gradle | 4 +++- cli/pico-cli/build.gradle | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index f73d8d56e2..fa99f9e182 100644 --- a/build.gradle +++ b/build.gradle @@ -190,7 +190,9 @@ subprojects { 'com.quorum.tessera.passwords.PasswordReaderFactory', 'com.quorum.tessera.enclave.rest.Main', 'com.quorum.tessera.key.vault.azure.AzureKeyVaultClientDelegate', - 'com.quorum.tessera.key.vault.hashicorp.KeyValueOperationsDelegateFactory' + 'com.quorum.tessera.key.vault.hashicorp.KeyValueOperationsDelegateFactory', + 'com.quorum.tessera.config.cli.parsers.*', + 'com.quorum.tessera.config.cli.DefaultCliAdapter' ] } diff --git a/cli/pico-cli/build.gradle b/cli/pico-cli/build.gradle index 8f9658648c..b6c5dad4d9 100644 --- a/cli/pico-cli/build.gradle +++ b/cli/pico-cli/build.gradle @@ -3,10 +3,12 @@ dependencies { compile 'info.picocli:picocli' compile project(':cli:cli-api') compile project(':cli:config-cli') + compile project(':cli:admin-cli') compile project(':shared') compile project(':config') compile project(':encryption:encryption-api') compile project(':key-generation') testCompile project(':tests:test-util') compile "org.hibernate:hibernate-validator" + } From 8ed083f8c7bfdb769684797b7c529f911d6d39e3 Mon Sep 17 00:00:00 2001 From: Mark Lowe Date: Thu, 19 Dec 2019 12:35:15 +0000 Subject: [PATCH 38/41] Remove commons cli dependnecy and change admin package name. --- build.gradle | 4 +- .../cli => cli/admin}/AdminCliAdapter.java | 4 +- .../admin}/subcommands/AddPeerCommand.java | 2 +- .../com.quorum.tessera.cli.CliAdapter | 2 +- .../admin}/AdminCliAdapterTest.java | 2 +- .../subcommands/AddPeerCommandTest.java | 3 +- cli/cli-api/build.gradle | 2 +- .../cli/parsers/ConfigurationParser.java | 134 ---- .../quorum/tessera/cli/parsers/Parser.java | 25 - .../cli/parsers/ConfigurationParserTest.java | 602 ------------------ cli/config-cli/build.gradle | 2 +- .../tessera/config/cli/DefaultCliAdapter.java | 300 --------- .../cli/parsers/EncryptorConfigParser.java | 71 --- .../cli/parsers/KeyGenerationParser.java | 150 ----- .../config/cli/parsers/KeyUpdateParser.java | 129 ---- .../com.quorum.tessera.cli.CliAdapter | 1 - .../config/cli/DefaultCliAdapterTest.java | 395 ------------ .../parsers/EncryptorConfigParserTest.java | 158 ----- .../cli/parsers/KeyGenerationParserTest.java | 365 ----------- .../cli/parsers/KeyUpdateParserTest.java | 324 ---------- .../tessera/picocli/PicoCliDelegate.java | 5 +- .../tessera/picocli/KeyUpdateCommandTest.java | 3 +- cli/pom.xml | 5 - pom.xml | 10 - 24 files changed, 14 insertions(+), 2684 deletions(-) rename cli/admin-cli/src/main/java/com/quorum/tessera/{admin/cli => cli/admin}/AdminCliAdapter.java (93%) rename cli/admin-cli/src/main/java/com/quorum/tessera/{admin/cli => cli/admin}/subcommands/AddPeerCommand.java (98%) rename cli/admin-cli/src/test/java/com/quorum/tessera/{admin/cli => cli/admin}/AdminCliAdapterTest.java (96%) rename cli/admin-cli/src/test/java/com/quorum/tessera/{admin/cli => cli/admin}/subcommands/AddPeerCommandTest.java (97%) delete mode 100644 cli/cli-api/src/main/java/com/quorum/tessera/cli/parsers/ConfigurationParser.java delete mode 100644 cli/cli-api/src/main/java/com/quorum/tessera/cli/parsers/Parser.java delete mode 100644 cli/cli-api/src/test/java/com/quorum/tessera/cli/parsers/ConfigurationParserTest.java delete mode 100644 cli/config-cli/src/main/java/com/quorum/tessera/config/cli/DefaultCliAdapter.java delete mode 100644 cli/config-cli/src/main/java/com/quorum/tessera/config/cli/parsers/EncryptorConfigParser.java delete mode 100644 cli/config-cli/src/main/java/com/quorum/tessera/config/cli/parsers/KeyGenerationParser.java delete mode 100644 cli/config-cli/src/main/java/com/quorum/tessera/config/cli/parsers/KeyUpdateParser.java delete mode 100644 cli/config-cli/src/main/resources/META-INF/services/com.quorum.tessera.cli.CliAdapter delete mode 100644 cli/config-cli/src/test/java/com/quorum/tessera/config/cli/DefaultCliAdapterTest.java delete mode 100644 cli/config-cli/src/test/java/com/quorum/tessera/config/cli/parsers/EncryptorConfigParserTest.java delete mode 100644 cli/config-cli/src/test/java/com/quorum/tessera/config/cli/parsers/KeyGenerationParserTest.java delete mode 100644 cli/config-cli/src/test/java/com/quorum/tessera/config/cli/parsers/KeyUpdateParserTest.java diff --git a/build.gradle b/build.gradle index fa99f9e182..f73d8d56e2 100644 --- a/build.gradle +++ b/build.gradle @@ -190,9 +190,7 @@ subprojects { 'com.quorum.tessera.passwords.PasswordReaderFactory', 'com.quorum.tessera.enclave.rest.Main', 'com.quorum.tessera.key.vault.azure.AzureKeyVaultClientDelegate', - 'com.quorum.tessera.key.vault.hashicorp.KeyValueOperationsDelegateFactory', - 'com.quorum.tessera.config.cli.parsers.*', - 'com.quorum.tessera.config.cli.DefaultCliAdapter' + 'com.quorum.tessera.key.vault.hashicorp.KeyValueOperationsDelegateFactory' ] } diff --git a/cli/admin-cli/src/main/java/com/quorum/tessera/admin/cli/AdminCliAdapter.java b/cli/admin-cli/src/main/java/com/quorum/tessera/cli/admin/AdminCliAdapter.java similarity index 93% rename from cli/admin-cli/src/main/java/com/quorum/tessera/admin/cli/AdminCliAdapter.java rename to cli/admin-cli/src/main/java/com/quorum/tessera/cli/admin/AdminCliAdapter.java index 1aaf015aaa..6ac5fc9077 100644 --- a/cli/admin-cli/src/main/java/com/quorum/tessera/admin/cli/AdminCliAdapter.java +++ b/cli/admin-cli/src/main/java/com/quorum/tessera/cli/admin/AdminCliAdapter.java @@ -1,6 +1,6 @@ -package com.quorum.tessera.admin.cli; +package com.quorum.tessera.cli.admin; -import com.quorum.tessera.admin.cli.subcommands.AddPeerCommand; +import com.quorum.tessera.cli.admin.subcommands.AddPeerCommand; import com.quorum.tessera.cli.CliAdapter; import com.quorum.tessera.cli.CliResult; import com.quorum.tessera.cli.CliType; diff --git a/cli/admin-cli/src/main/java/com/quorum/tessera/admin/cli/subcommands/AddPeerCommand.java b/cli/admin-cli/src/main/java/com/quorum/tessera/cli/admin/subcommands/AddPeerCommand.java similarity index 98% rename from cli/admin-cli/src/main/java/com/quorum/tessera/admin/cli/subcommands/AddPeerCommand.java rename to cli/admin-cli/src/main/java/com/quorum/tessera/cli/admin/subcommands/AddPeerCommand.java index 571c456400..cccc80e2f9 100644 --- a/cli/admin-cli/src/main/java/com/quorum/tessera/admin/cli/subcommands/AddPeerCommand.java +++ b/cli/admin-cli/src/main/java/com/quorum/tessera/cli/admin/subcommands/AddPeerCommand.java @@ -1,4 +1,4 @@ -package com.quorum.tessera.admin.cli.subcommands; +package com.quorum.tessera.cli.admin.subcommands; import com.quorum.tessera.cli.CliResult; import com.quorum.tessera.cli.parsers.ConfigurationMixin; diff --git a/cli/admin-cli/src/main/resources/META-INF/services/com.quorum.tessera.cli.CliAdapter b/cli/admin-cli/src/main/resources/META-INF/services/com.quorum.tessera.cli.CliAdapter index d9e8355f71..4dcf28f0f0 100644 --- a/cli/admin-cli/src/main/resources/META-INF/services/com.quorum.tessera.cli.CliAdapter +++ b/cli/admin-cli/src/main/resources/META-INF/services/com.quorum.tessera.cli.CliAdapter @@ -1 +1 @@ -com.quorum.tessera.admin.cli.AdminCliAdapter +com.quorum.tessera.cli.admin.AdminCliAdapter diff --git a/cli/admin-cli/src/test/java/com/quorum/tessera/admin/cli/AdminCliAdapterTest.java b/cli/admin-cli/src/test/java/com/quorum/tessera/cli/admin/AdminCliAdapterTest.java similarity index 96% rename from cli/admin-cli/src/test/java/com/quorum/tessera/admin/cli/AdminCliAdapterTest.java rename to cli/admin-cli/src/test/java/com/quorum/tessera/cli/admin/AdminCliAdapterTest.java index 6c378fc2cb..06c736a50a 100644 --- a/cli/admin-cli/src/test/java/com/quorum/tessera/admin/cli/AdminCliAdapterTest.java +++ b/cli/admin-cli/src/test/java/com/quorum/tessera/cli/admin/AdminCliAdapterTest.java @@ -1,4 +1,4 @@ -package com.quorum.tessera.admin.cli; +package com.quorum.tessera.cli.admin; import com.quorum.tessera.cli.CliResult; import com.quorum.tessera.cli.CliType; diff --git a/cli/admin-cli/src/test/java/com/quorum/tessera/admin/cli/subcommands/AddPeerCommandTest.java b/cli/admin-cli/src/test/java/com/quorum/tessera/cli/admin/subcommands/AddPeerCommandTest.java similarity index 97% rename from cli/admin-cli/src/test/java/com/quorum/tessera/admin/cli/subcommands/AddPeerCommandTest.java rename to cli/admin-cli/src/test/java/com/quorum/tessera/cli/admin/subcommands/AddPeerCommandTest.java index 5869dd7238..5d2eba48f1 100644 --- a/cli/admin-cli/src/test/java/com/quorum/tessera/admin/cli/subcommands/AddPeerCommandTest.java +++ b/cli/admin-cli/src/test/java/com/quorum/tessera/cli/admin/subcommands/AddPeerCommandTest.java @@ -1,6 +1,7 @@ -package com.quorum.tessera.admin.cli.subcommands; +package com.quorum.tessera.cli.admin.subcommands; import com.quorum.tessera.cli.CliResult; +import com.quorum.tessera.cli.admin.subcommands.AddPeerCommand; import com.quorum.tessera.cli.parsers.ConfigurationMixin; import com.quorum.tessera.config.Config; import com.quorum.tessera.config.ConfigFactory; diff --git a/cli/cli-api/build.gradle b/cli/cli-api/build.gradle index 26195d6fc1..b21d6be32e 100644 --- a/cli/cli-api/build.gradle +++ b/cli/cli-api/build.gradle @@ -2,7 +2,7 @@ dependencies { compile 'info.picocli:picocli:4.0.4' compile 'org.apache.commons:commons-lang3:3.7' - compile 'commons-cli:commons-cli:1.4' + //compile 'commons-cli:commons-cli:1.4' compile project(':config') compile project(':shared') compile project(':encryption:encryption-api') diff --git a/cli/cli-api/src/main/java/com/quorum/tessera/cli/parsers/ConfigurationParser.java b/cli/cli-api/src/main/java/com/quorum/tessera/cli/parsers/ConfigurationParser.java deleted file mode 100644 index b2f10cab79..0000000000 --- a/cli/cli-api/src/main/java/com/quorum/tessera/cli/parsers/ConfigurationParser.java +++ /dev/null @@ -1,134 +0,0 @@ -package com.quorum.tessera.cli.parsers; - -import com.quorum.tessera.config.Config; -import com.quorum.tessera.config.ConfigException; -import com.quorum.tessera.config.KeyConfiguration; -import com.quorum.tessera.config.keypairs.ConfigKeyPair; -import com.quorum.tessera.config.util.ConfigFileStore; -import com.quorum.tessera.config.util.JaxbUtil; -import com.quorum.tessera.io.FilesDelegate; -import com.quorum.tessera.io.SystemAdapter; -import org.apache.commons.cli.CommandLine; - -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.attribute.PosixFilePermission; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; -import java.util.Set; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import static java.nio.file.StandardOpenOption.APPEND; -import static java.nio.file.StandardOpenOption.CREATE_NEW; - -public class ConfigurationParser implements Parser { - - protected static final Set NEW_PASSWORD_FILE_PERMS = - Stream.of(PosixFilePermission.OWNER_READ, PosixFilePermission.OWNER_WRITE).collect(Collectors.toSet()); - - protected static final String passwordsMessage = "Configfile must contain \"passwordFile\" field. The \"passwords\" field is no longer supported."; - - private final List newlyGeneratedKeys; - - private final FilesDelegate filesDelegate; - - public ConfigurationParser(List newlyGeneratedKeys) { - this(newlyGeneratedKeys, FilesDelegate.create()); - } - - protected ConfigurationParser(List newlyGeneratedKeys, FilesDelegate filesDelegate) { - this.newlyGeneratedKeys = Objects.requireNonNull(newlyGeneratedKeys); - this.filesDelegate = Objects.requireNonNull(filesDelegate); - } - - @Override - public Config parse(final CommandLine commandLine) throws IOException { - - Config config = null; - - final boolean isGeneratingWithKeyVault = - commandLine.hasOption("keygen") && commandLine.hasOption("keygenvaulturl"); - - if (commandLine.hasOption("configfile") && !isGeneratingWithKeyVault) { - final Path path = Paths.get(commandLine.getOptionValue("configfile")); - - if (!filesDelegate.exists(path)) { - throw new FileNotFoundException(String.format("%s not found.", path)); - } - - try (InputStream in = filesDelegate.newInputStream(path)) { - config = JaxbUtil.unmarshal(in, Config.class); - - if (!newlyGeneratedKeys.isEmpty()) { - if (config.getKeys() == null) { - config.setKeys(new KeyConfiguration()); - config.getKeys().setKeyData(new ArrayList<>()); - } - if (config.getKeys().getKeyData() == null) { - config.getKeys().setKeyData(new ArrayList<>()); - } - doPasswordStuff(config); - config.getKeys().getKeyData().addAll(newlyGeneratedKeys); - } - } - - if (!newlyGeneratedKeys.isEmpty()) { - // we have generated new keys, so we need to output the new configuration - output(commandLine, config); - } - - ConfigFileStore.create(path); - } - - return config; - } - - private void output(CommandLine commandLine, Config config) throws IOException { - - if (commandLine.hasOption("output")) { - final Path outputConfigFile = Paths.get(commandLine.getOptionValue("output")); - - try (OutputStream out = filesDelegate.newOutputStream(outputConfigFile, CREATE_NEW)) { - JaxbUtil.marshal(config, out); - } - } else { - JaxbUtil.marshal(config, SystemAdapter.INSTANCE.out()); - } - } - - // create a file if it doesn't exist and set the permissions to be only - // read/write for the creator - private void createFile(Path fileToMake) { - boolean notExists = filesDelegate.notExists(fileToMake); - if (notExists) { - filesDelegate.createFile(fileToMake); - filesDelegate.setPosixFilePermissions(fileToMake, NEW_PASSWORD_FILE_PERMS); - } - } - - public Config doPasswordStuff(Config config) throws ConfigException { - final List newPasswords = - newlyGeneratedKeys.stream().map(ConfigKeyPair::getPassword).collect(Collectors.toList()); - - boolean hasNewPasswords = newPasswords.stream().anyMatch(p -> Objects.nonNull(p) && !p.isEmpty()); - boolean isUsingPasswordFile = Objects.nonNull(config.getKeys().getPasswordFile()); - - if (hasNewPasswords) { - if (!isUsingPasswordFile) { - throw new ConfigException(new RuntimeException(passwordsMessage)); - } - - Path passwordFile = config.getKeys().getPasswordFile(); - createFile(passwordFile); - filesDelegate.write(passwordFile, newPasswords, APPEND); - } - - return config; - } -} diff --git a/cli/cli-api/src/main/java/com/quorum/tessera/cli/parsers/Parser.java b/cli/cli-api/src/main/java/com/quorum/tessera/cli/parsers/Parser.java deleted file mode 100644 index e24f4ade24..0000000000 --- a/cli/cli-api/src/main/java/com/quorum/tessera/cli/parsers/Parser.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.quorum.tessera.cli.parsers; - -import org.apache.commons.cli.CommandLine; - -/** - * A parser that checks for CLI options and takes actions based upon them - *

- * The actions may have side-effects, and may choose to return a value - * - * @param The return type from parsing the CLI options - */ -public interface Parser { - - /** - * Parses the CLI arguments and performs actions based upon whether the - * arguments relevant to it are present or not - * - * @param commandLine the command line object that has parsed the configuration - * @return the output of the parser, if any - * @throws Exception if there is a problem with the supplied configuration, - * any exception could be thrown - */ - T parse(CommandLine commandLine) throws Exception; - -} diff --git a/cli/cli-api/src/test/java/com/quorum/tessera/cli/parsers/ConfigurationParserTest.java b/cli/cli-api/src/test/java/com/quorum/tessera/cli/parsers/ConfigurationParserTest.java deleted file mode 100644 index fb8d4a16e8..0000000000 --- a/cli/cli-api/src/test/java/com/quorum/tessera/cli/parsers/ConfigurationParserTest.java +++ /dev/null @@ -1,602 +0,0 @@ -package com.quorum.tessera.cli.parsers; - -import com.quorum.tessera.config.Config; -import com.quorum.tessera.config.ConfigException; -import com.quorum.tessera.config.KeyConfiguration; -import com.quorum.tessera.config.keypairs.ConfigKeyPair; -import com.quorum.tessera.config.keypairs.DirectKeyPair; -import com.quorum.tessera.io.FilesDelegate; -import org.apache.commons.cli.CommandLine; -import org.junit.Before; -import org.junit.Test; - -import java.io.ByteArrayOutputStream; -import java.io.FileNotFoundException; -import java.io.InputStream; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.*; - -import static com.quorum.tessera.cli.parsers.ConfigurationParser.NEW_PASSWORD_FILE_PERMS; -import static com.quorum.tessera.cli.parsers.ConfigurationParser.passwordsMessage; -import static com.quorum.tessera.test.util.ElUtil.createAndPopulatePaths; -import static com.quorum.tessera.test.util.ElUtil.createTempFileFromTemplate; -import static java.nio.file.StandardOpenOption.APPEND; -import static java.nio.file.StandardOpenOption.CREATE_NEW; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.catchThrowable; -import static org.mockito.Mockito.*; - -public class ConfigurationParserTest { - - private CommandLine commandLine; - private FilesDelegate filesDelegate; - - @Before - public void setUp() { - commandLine = mock(CommandLine.class); - filesDelegate = mock(FilesDelegate.class); - } - - @Test - public void noConfigfileOptionThenDoNothing() throws Exception { - when(commandLine.hasOption("configfile")).thenReturn(false); - ConfigurationParser configParser = new ConfigurationParser(Collections.EMPTY_LIST, filesDelegate); - Config result = configParser.parse(commandLine); - - assertThat(result).isNull(); - - verifyNoMoreInteractions(filesDelegate); - } - - @Test - public void configReadFromFile() throws Exception { - Path configFile = createAndPopulatePaths(getClass().getResource("/sample-config.json")); - - configFile.toFile().deleteOnExit(); - - when(commandLine.hasOption("configfile")).thenReturn(true); - when(commandLine.getOptionValue("configfile")).thenReturn(configFile.toString()); - ConfigurationParser configParser = new ConfigurationParser(Collections.EMPTY_LIST); - Config result = configParser.parse(commandLine); - - assertThat(result).isNotNull(); - } - - @Test - public void configfileDoesNotExistThrowsException() { - String path = "does/not/exist.config"; - - when(commandLine.hasOption("configfile")).thenReturn(true); - when(commandLine.getOptionValue("configfile")).thenReturn(path); - ConfigurationParser configParser = new ConfigurationParser(Collections.EMPTY_LIST); - Throwable throwable = catchThrowable(() -> configParser.parse(commandLine)); - - assertThat(throwable).isInstanceOf(FileNotFoundException.class); - assertThat(throwable).hasMessage(path + " not found."); - } - - @Test - public void providingKeygenAndVaultOptionsThenConfigfileNotParsed() throws Exception { - when(commandLine.hasOption("configfile")).thenReturn(true); - when(commandLine.hasOption("keygen")).thenReturn(true); - when(commandLine.hasOption("keygenvaulturl")).thenReturn(true); - ConfigurationParser configParser = new ConfigurationParser(Collections.EMPTY_LIST); - Config result = configParser.parse(commandLine); - - assertThat(result).isNull(); - } - - @Test - public void providingKeygenOptionThenConfigfileIsParsed() { - when(commandLine.hasOption("configfile")).thenReturn(true); - when(commandLine.hasOption("keygen")).thenReturn(true); - when(commandLine.hasOption("keygenvaulturl")).thenReturn(false); - - when(commandLine.getOptionValue("configfile")).thenReturn("some/path"); - - ConfigurationParser configParser = new ConfigurationParser(Collections.EMPTY_LIST); - Throwable throwable = catchThrowable(() -> configParser.parse(commandLine)); - - // FileNotFoundException thrown as "some/path" does not exist - assertThat(throwable).isInstanceOf(FileNotFoundException.class); - } - - @Test - public void providingVaultOptionThenConfigfileIsParsed() { - when(commandLine.hasOption("configfile")).thenReturn(true); - when(commandLine.hasOption("keygen")).thenReturn(false); - when(commandLine.hasOption("keygenvaulturl")).thenReturn(true); - - when(commandLine.getOptionValue("configfile")).thenReturn("some/path"); - ConfigurationParser configParser = new ConfigurationParser(Collections.EMPTY_LIST); - Throwable throwable = catchThrowable(() -> configParser.parse(commandLine)); - - // FileNotFoundException thrown as "some/path" does not exist - assertThat(throwable).isInstanceOf(FileNotFoundException.class); - } - - @Test - public void providingNeitherKeygenOptionsThenConfigfileIsParsed() { - when(commandLine.hasOption("configfile")).thenReturn(true); - when(commandLine.hasOption("keygen")).thenReturn(false); - when(commandLine.hasOption("keygenvaulturl")).thenReturn(false); - - when(commandLine.getOptionValue("configfile")).thenReturn("some/path"); - ConfigurationParser configParser = new ConfigurationParser(Collections.EMPTY_LIST); - Throwable throwable = catchThrowable(() -> configParser.parse(commandLine)); - - // FileNotFoundException thrown as "some/path" does not exist - assertThat(throwable).isInstanceOf(FileNotFoundException.class); - } - - @Test - public void withNewKeysOutputsNewConfigToSystemAdapter() throws Exception { - Path configFile = createAndPopulatePaths(getClass().getResource("/sample-config.json")); - - configFile.toFile().deleteOnExit(); - - when(commandLine.hasOption("configfile")).thenReturn(true); - when(commandLine.getOptionValue("configfile")).thenReturn(configFile.toString()); - - ConfigKeyPair newKey = new DirectKeyPair("pub", "priv"); - - FilesDelegate filesDelegate = mock(FilesDelegate.class); - FilesDelegate fd = new FilesDelegate() {}; - InputStream in = fd.newInputStream(configFile); - - when(filesDelegate.exists(configFile)).thenReturn(true); - when(filesDelegate.newInputStream(configFile)).thenReturn(in); - - ConfigurationParser configParser = new ConfigurationParser(Arrays.asList(newKey), filesDelegate); - Config result = configParser.parse(commandLine); - - in.close(); - assertThat(result).isNotNull(); - assertThat(result.getKeys().getKeyData()).contains(newKey); - - verify(filesDelegate).exists(configFile); - verify(filesDelegate).newInputStream(configFile); - verifyNoMoreInteractions(filesDelegate); - } - - @Test - public void withNewKeysAndOutputOptionWritesNewConfigToFile() throws Exception { - Path configFile = createAndPopulatePaths(getClass().getResource("/sample-config.json")); - - configFile.toFile().deleteOnExit(); - - when(commandLine.hasOption("configfile")).thenReturn(true); - when(commandLine.getOptionValue("configfile")).thenReturn(configFile.toString()); - - when(commandLine.hasOption("output")).thenReturn(true); - - Path output = Paths.get(System.getProperty("java.io.tmpdir"), UUID.randomUUID().toString() + ".conf"); - - when(commandLine.getOptionValue("output")).thenReturn(output.toString()); - - ConfigKeyPair newKey = new DirectKeyPair("pub", "priv"); - - FilesDelegate filesDelegate = mock(FilesDelegate.class); - FilesDelegate fd = new FilesDelegate() {}; - InputStream in = fd.newInputStream(configFile); - - when(filesDelegate.exists(configFile)).thenReturn(true); - when(filesDelegate.newInputStream(configFile)).thenReturn(in); - - ByteArrayOutputStream os = new ByteArrayOutputStream(); - when(filesDelegate.newOutputStream(output, CREATE_NEW)).thenReturn(os); - - ConfigurationParser configParser = new ConfigurationParser(Arrays.asList(newKey), filesDelegate); - - Config result = configParser.parse(commandLine); - - in.close(); - - assertThat(result).isNotNull(); - assertThat(result.getKeys().getKeyData()).contains(newKey); - - verify(filesDelegate).exists(configFile); - verify(filesDelegate).newInputStream(configFile); - verify(filesDelegate).newOutputStream(output, CREATE_NEW); - verifyNoMoreInteractions(filesDelegate); - - byte[] bytesOut = os.toByteArray(); - assertThat(bytesOut).isNotEmpty(); - } - - @Test - public void withNewKeysAndNullKeyConfig() throws Exception { - - Path configFile = createAndPopulatePaths(getClass().getResource("/sample-config-no-keyconfig.json")); - - configFile.toFile().deleteOnExit(); - - when(commandLine.hasOption("configfile")).thenReturn(true); - when(commandLine.getOptionValue("configfile")).thenReturn(configFile.toString()); - - when(commandLine.hasOption("output")).thenReturn(true); - - String tempDir = System.getProperty("java.io.tmpdir"); - - Path output = Paths.get(tempDir, UUID.randomUUID().toString() + ".conf"); - - when(commandLine.getOptionValue("output")).thenReturn(output.toString()); - - ConfigKeyPair newKey = new DirectKeyPair("pub", "priv"); - - ConfigurationParser configParser = new ConfigurationParser(Arrays.asList(newKey)); - - Config result = configParser.parse(commandLine); - - assertThat(result).isNotNull(); - assertThat(result.getKeys().getKeyData()).contains(newKey); - - assertThat(output).exists(); - output.toFile().deleteOnExit(); - } - - @Test - public void withNewPasswordProtectedKeysAndPasswordsListInConfigThrowsException() throws Exception { - Path configFile = createAndPopulatePaths(getClass().getResource("/sample-config.json")); - - configFile.toFile().deleteOnExit(); - - when(commandLine.hasOption("configfile")).thenReturn(true); - when(commandLine.getOptionValue("configfile")).thenReturn(configFile.toString()); - - when(commandLine.hasOption("output")).thenReturn(true); - - String tempDir = System.getProperty("java.io.tmpdir"); - - Path output = Paths.get(tempDir, UUID.randomUUID().toString() + ".conf"); - - when(commandLine.getOptionValue("output")).thenReturn(output.toString()); - - FilesDelegate fd = new FilesDelegate() {}; - InputStream in = fd.newInputStream(configFile); - - when(filesDelegate.exists(configFile)).thenReturn(true); - when(filesDelegate.newInputStream(configFile)).thenReturn(in); - - ConfigKeyPair newKey = mock(ConfigKeyPair.class); - when(newKey.getPassword()).thenReturn("A TEST PASSWORD"); - - ConfigurationParser configParser = new ConfigurationParser(Arrays.asList(newKey), filesDelegate); - - Throwable ex = catchThrowable(() -> configParser.parse(commandLine)); - - in.close(); - - assertThat(ex).isExactlyInstanceOf(ConfigException.class); - assertThat(ex.getMessage()).contains(passwordsMessage); - - verify(filesDelegate).exists(configFile); - verify(filesDelegate).newInputStream(configFile); - verifyNoMoreInteractions(filesDelegate); - } - - @Test - public void withNewPasswordProtectedKeysAndNullKeyConfigThrowsException() throws Exception { - Path configFile = createAndPopulatePaths(getClass().getResource("/sample-config-no-keyconfig.json")); - - configFile.toFile().deleteOnExit(); - - when(commandLine.hasOption("configfile")).thenReturn(true); - when(commandLine.getOptionValue("configfile")).thenReturn(configFile.toString()); - - when(commandLine.hasOption("output")).thenReturn(true); - - String tempDir = System.getProperty("java.io.tmpdir"); - - Path output = Paths.get(tempDir, UUID.randomUUID().toString() + ".conf"); - - when(commandLine.getOptionValue("output")).thenReturn(output.toString()); - - FilesDelegate fd = new FilesDelegate() {}; - InputStream in = fd.newInputStream(configFile); - - when(filesDelegate.exists(configFile)).thenReturn(true); - when(filesDelegate.newInputStream(configFile)).thenReturn(in); - - ConfigKeyPair newKey = mock(ConfigKeyPair.class); - when(newKey.getPassword()).thenReturn("A TEST PASSWORD"); - - ConfigurationParser configParser = new ConfigurationParser(Arrays.asList(newKey), filesDelegate); - - Throwable ex = catchThrowable(() -> configParser.parse(commandLine)); - - in.close(); - - assertThat(ex).isExactlyInstanceOf(ConfigException.class); - assertThat(ex.getMessage()).contains(passwordsMessage); - - verify(filesDelegate).exists(configFile); - verify(filesDelegate).newInputStream(configFile); - verifyNoMoreInteractions(filesDelegate); - } - - @Test - public void withNewPasswordProtectedKeysAndConfigExistingPasswordFileInConfigUpdatesConfigfileAndPasswordFile() - throws Exception { - - Path unixSocketPath = Files.createTempFile(UUID.randomUUID().toString(), ".ipc"); - Path passwordFilePath = Files.createTempFile(UUID.randomUUID().toString(), ".pwds"); - - Map params = new HashMap<>(); - params.put("unixSocketPath", unixSocketPath.toString()); - params.put("passwordFilePath", passwordFilePath.toString()); - - Path configFile = - createTempFileFromTemplate(getClass().getResource("/sample-config-password-file.json"), params); - - configFile.toFile().deleteOnExit(); - - when(commandLine.hasOption("configfile")).thenReturn(true); - when(commandLine.getOptionValue("configfile")).thenReturn(configFile.toString()); - - when(commandLine.hasOption("output")).thenReturn(true); - - String tempDir = System.getProperty("java.io.tmpdir"); - - Path output = Paths.get(tempDir, UUID.randomUUID().toString() + ".conf"); - - when(commandLine.getOptionValue("output")).thenReturn(output.toString()); - - FilesDelegate fd = new FilesDelegate() {}; - InputStream in = fd.newInputStream(configFile); - - when(filesDelegate.exists(configFile)).thenReturn(true); - when(filesDelegate.notExists(passwordFilePath)).thenReturn(false); - when(filesDelegate.newInputStream(configFile)).thenReturn(in); - - ByteArrayOutputStream os = new ByteArrayOutputStream(); - when(filesDelegate.newOutputStream(output, CREATE_NEW)).thenReturn(os); - - ConfigKeyPair newKey = mock(ConfigKeyPair.class); - final String testPassword = "A TEST PASSWORD"; - when(newKey.getPassword()).thenReturn(testPassword); - - ConfigurationParser configParser = new ConfigurationParser(Arrays.asList(newKey), filesDelegate); - - Config result = configParser.parse(commandLine); - - in.close(); - - assertThat(result).isNotNull(); - - verify(filesDelegate).exists(configFile); - verify(filesDelegate).notExists(passwordFilePath); - verify(filesDelegate).newInputStream(configFile); - verify(filesDelegate).newOutputStream(output, CREATE_NEW); - verify(filesDelegate).write(passwordFilePath, Arrays.asList(testPassword), APPEND); - verifyNoMoreInteractions(filesDelegate); - } - - @Test - public void withNewPasswordProtectedKeysPasswordFileOnlyInKeyConfigUpdatesConfigfileAndPasswordFile() - throws Exception { - - Path unixSocketPath = Files.createTempFile(UUID.randomUUID().toString(), ".ipc"); - Path passwordFilePath = Files.createTempFile(UUID.randomUUID().toString(), ".pwds"); - - Map params = new HashMap<>(); - params.put("unixSocketPath", unixSocketPath.toString()); - params.put("passwordFilePath", passwordFilePath.toString()); - - Path configFile = - createTempFileFromTemplate(getClass().getResource("/sample-config-password-file-only.json"), params); - - configFile.toFile().deleteOnExit(); - - when(commandLine.hasOption("configfile")).thenReturn(true); - when(commandLine.getOptionValue("configfile")).thenReturn(configFile.toString()); - - when(commandLine.hasOption("output")).thenReturn(true); - - String tempDir = System.getProperty("java.io.tmpdir"); - - Path output = Paths.get(tempDir, UUID.randomUUID().toString() + ".conf"); - - when(commandLine.getOptionValue("output")).thenReturn(output.toString()); - - FilesDelegate fd = new FilesDelegate() {}; - InputStream in = fd.newInputStream(configFile); - - when(filesDelegate.exists(configFile)).thenReturn(true); - when(filesDelegate.notExists(passwordFilePath)).thenReturn(false); - when(filesDelegate.newInputStream(configFile)).thenReturn(in); - - ByteArrayOutputStream os = new ByteArrayOutputStream(); - when(filesDelegate.newOutputStream(output, CREATE_NEW)).thenReturn(os); - - ConfigKeyPair newKey = mock(ConfigKeyPair.class); - final String testPassword = "A TEST PASSWORD"; - when(newKey.getPassword()).thenReturn(testPassword); - - ConfigurationParser configParser = new ConfigurationParser(Arrays.asList(newKey), filesDelegate); - - Config result = configParser.parse(commandLine); - - in.close(); - - assertThat(result).isNotNull(); - - verify(filesDelegate).exists(configFile); - verify(filesDelegate).notExists(passwordFilePath); - verify(filesDelegate).newInputStream(configFile); - verify(filesDelegate).newOutputStream(output, CREATE_NEW); - verify(filesDelegate).write(passwordFilePath, Arrays.asList(testPassword), APPEND); - verifyNoMoreInteractions(filesDelegate); - } - - @Test - public void withNewPasswordProtectedKeysAndNonExistingPasswordFileInConfigUpdatesConfigfileAndCreatesPasswordFile() - throws Exception { - - Path unixSocketPath = Files.createTempFile(UUID.randomUUID().toString(), ".ipc"); - Path passwordFilePath = Files.createTempFile(UUID.randomUUID().toString(), ".pwds"); - - Map params = new HashMap<>(); - params.put("unixSocketPath", unixSocketPath.toString()); - params.put("passwordFilePath", passwordFilePath.toString()); - - Path configFile = - createTempFileFromTemplate(getClass().getResource("/sample-config-password-file.json"), params); - - configFile.toFile().deleteOnExit(); - - when(commandLine.hasOption("configfile")).thenReturn(true); - when(commandLine.getOptionValue("configfile")).thenReturn(configFile.toString()); - - when(commandLine.hasOption("output")).thenReturn(true); - - String tempDir = System.getProperty("java.io.tmpdir"); - - Path output = Paths.get(tempDir, UUID.randomUUID().toString() + ".conf"); - - when(commandLine.getOptionValue("output")).thenReturn(output.toString()); - - FilesDelegate fd = new FilesDelegate() {}; - InputStream in = fd.newInputStream(configFile); - - when(filesDelegate.exists(configFile)).thenReturn(true); - when(filesDelegate.notExists(passwordFilePath)).thenReturn(true); - when(filesDelegate.newInputStream(configFile)).thenReturn(in); - - ByteArrayOutputStream os = new ByteArrayOutputStream(); - when(filesDelegate.newOutputStream(output, CREATE_NEW)).thenReturn(os); - - ConfigKeyPair newKey = mock(ConfigKeyPair.class); - final String testPassword = "A TEST PASSWORD"; - when(newKey.getPassword()).thenReturn(testPassword); - - ConfigurationParser configParser = new ConfigurationParser(Arrays.asList(newKey), filesDelegate); - - Config result = configParser.parse(commandLine); - - in.close(); - - assertThat(result).isNotNull(); - - verify(filesDelegate).exists(configFile); - verify(filesDelegate).notExists(passwordFilePath); - verify(filesDelegate).createFile(passwordFilePath); - verify(filesDelegate).setPosixFilePermissions(passwordFilePath, NEW_PASSWORD_FILE_PERMS); - verify(filesDelegate).newInputStream(configFile); - verify(filesDelegate).newOutputStream(output, CREATE_NEW); - verify(filesDelegate).write(passwordFilePath, Arrays.asList(testPassword), APPEND); - verifyNoMoreInteractions(filesDelegate); - } - - @Test - public void doPasswordStuffWithEmptyPasswordsElement() throws Exception { - - final String password = "I LOVE SPARROWS!"; - final ConfigKeyPair newKey = mock(ConfigKeyPair.class); - when(newKey.getPassword()).thenReturn(password); - - final List newKeys = Arrays.asList(newKey); - - FilesDelegate filesDelegate = mock(FilesDelegate.class); - - final ConfigurationParser configParser = new ConfigurationParser(newKeys, filesDelegate); - - Config config = mock(Config.class); - KeyConfiguration keyConfiguration = mock(KeyConfiguration.class); - when(keyConfiguration.getPasswords()).thenReturn(new ArrayList<>()); - when(config.getKeys()).thenReturn(keyConfiguration); - - Throwable ex = catchThrowable(() -> configParser.doPasswordStuff(config)); - - assertThat(ex).isInstanceOf(ConfigException.class); - verifyZeroInteractions(filesDelegate); - } - - @Test - public void doPasswordStuffWithPasswordFileDefined() throws Exception { - - final String password = "I LOVE SPARROWS!"; - - final ConfigKeyPair newKey = mock(ConfigKeyPair.class); - when(newKey.getPassword()).thenReturn(password); - - FilesDelegate filesDelegate = mock(FilesDelegate.class); - - final List newKeys = Arrays.asList(newKey); - - final ConfigurationParser configParser = new ConfigurationParser(newKeys, filesDelegate); - - Config config = mock(Config.class); - KeyConfiguration keyConfiguration = mock(KeyConfiguration.class); - when(keyConfiguration.getPasswords()).thenReturn(null); - - when(config.getKeys()).thenReturn(keyConfiguration); - - final Path passwordFile = mock(Path.class); - when(keyConfiguration.getPasswordFile()).thenReturn(passwordFile); - - when(filesDelegate.notExists(passwordFile)).thenReturn(true); - - when(filesDelegate.setPosixFilePermissions(passwordFile, NEW_PASSWORD_FILE_PERMS)).thenReturn(passwordFile); - - Config result = configParser.doPasswordStuff(config); - - assertThat(result).isSameAs(config); - - verify(filesDelegate).notExists(passwordFile); - verify(filesDelegate).setPosixFilePermissions(passwordFile, NEW_PASSWORD_FILE_PERMS); - verify(filesDelegate).createFile(passwordFile); - - verify(filesDelegate).write(passwordFile, Arrays.asList(password), APPEND); - - verifyNoMoreInteractions(filesDelegate); - } - - @Test - public void doPasswordStuffNewPasswordsOnly() { - final String password = "I LOVE SPARROWS!"; - - final ConfigKeyPair newKey = mock(ConfigKeyPair.class); - when(newKey.getPassword()).thenReturn(password); - - FilesDelegate filesDelegate = mock(FilesDelegate.class); - - final List newKeys = Arrays.asList(newKey); - - final ConfigurationParser configParser = new ConfigurationParser(newKeys, filesDelegate); - - Config config = mock(Config.class); - - KeyConfiguration keyConfiguration = mock(KeyConfiguration.class); - when(keyConfiguration.getKeyData()).thenReturn(Collections.emptyList()); - - when(keyConfiguration.getPasswords()).thenReturn(null); - when(keyConfiguration.getPasswordFile()).thenReturn(null); - when(config.getKeys()).thenReturn(keyConfiguration); - - Throwable ex = catchThrowable(() -> configParser.doPasswordStuff(config)); - - assertThat(ex).isInstanceOf(ConfigException.class); - verifyZeroInteractions(filesDelegate); - } - - @Test - public void doPasswordStuffNoNewPasswords() { - - FilesDelegate filesDelegate = mock(FilesDelegate.class); - - final ConfigurationParser configParser = new ConfigurationParser(Collections.emptyList(), filesDelegate); - - Config config = mock(Config.class); - KeyConfiguration keyConfiguration = mock(KeyConfiguration.class); - when(keyConfiguration.getPasswordFile()).thenReturn(null); - when(keyConfiguration.getPasswords()).thenReturn(null); - - when(config.getKeys()).thenReturn(keyConfiguration); - - Config result = configParser.doPasswordStuff(config); - assertThat(result).isSameAs(config); - } -} diff --git a/cli/config-cli/build.gradle b/cli/config-cli/build.gradle index 7c8743cb68..a65f01e87d 100644 --- a/cli/config-cli/build.gradle +++ b/cli/config-cli/build.gradle @@ -1,7 +1,7 @@ dependencies { compile 'info.picocli:picocli' - compile 'commons-cli:commons-cli:1.4' + //compile 'commons-cli:commons-cli:1.4' compile project(':encryption:encryption-api') compile project(':config') compile project(':shared') diff --git a/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/DefaultCliAdapter.java b/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/DefaultCliAdapter.java deleted file mode 100644 index 16045e40d0..0000000000 --- a/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/DefaultCliAdapter.java +++ /dev/null @@ -1,300 +0,0 @@ -package com.quorum.tessera.config.cli; - -import com.quorum.tessera.ServiceLoaderUtil; -import com.quorum.tessera.cli.CliAdapter; -import com.quorum.tessera.cli.CliException; -import com.quorum.tessera.cli.CliResult; -import com.quorum.tessera.cli.CliType; -import com.quorum.tessera.cli.keypassresolver.CliKeyPasswordResolver; -import com.quorum.tessera.cli.keypassresolver.KeyPasswordResolver; -import com.quorum.tessera.cli.parsers.ConfigurationParser; -import com.quorum.tessera.cli.parsers.PidFileMixin; -import com.quorum.tessera.config.Config; -import com.quorum.tessera.config.EncryptorConfig; -import com.quorum.tessera.config.cli.parsers.EncryptorConfigParser; -import com.quorum.tessera.config.cli.parsers.KeyGenerationParser; -import com.quorum.tessera.config.cli.parsers.KeyUpdateParser; -import com.quorum.tessera.config.keypairs.ConfigKeyPair; -import com.quorum.tessera.config.keys.KeyEncryptor; -import com.quorum.tessera.config.keys.KeyEncryptorFactory; -import com.quorum.tessera.passwords.PasswordReaderFactory; -import org.apache.commons.cli.*; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.validation.ConstraintViolation; -import javax.validation.ConstraintViolationException; -import javax.validation.Validation; -import javax.validation.Validator; -import java.io.IOException; -import java.io.PrintWriter; -import java.util.*; -import java.util.concurrent.Callable; - -@picocli.CommandLine.Command -public class DefaultCliAdapter implements CliAdapter, Callable { - - private static final Logger LOGGER = LoggerFactory.getLogger(DefaultCliAdapter.class); - - private final KeyPasswordResolver keyPasswordResolver; - - private final Validator validator = - Validation.byDefaultProvider().configure().ignoreXmlConfiguration().buildValidatorFactory().getValidator(); - - @picocli.CommandLine.Mixin private PidFileMixin pidFileMixin = new PidFileMixin(); - - @picocli.CommandLine.Unmatched private String[] allParameters = new String[0]; - - public DefaultCliAdapter() { - this(ServiceLoaderUtil.load(KeyPasswordResolver.class).orElse(new CliKeyPasswordResolver())); - } - - public DefaultCliAdapter(final KeyPasswordResolver keyPasswordResolver) { - this.keyPasswordResolver = Objects.requireNonNull(keyPasswordResolver); - } - - @Override - public CliResult call() throws Exception { - return this.execute(allParameters); - } - - @Override - public CliType getType() { - return CliType.CONFIG; - } - - @Override - public CliResult execute(String... args) throws Exception { - - Options options = this.buildBaseOptions(); - - Map overrideOptions = OverrideUtil.buildConfigOptions(); - - overrideOptions.forEach( - (optionName, optionType) -> { - final boolean isCollection = optionType.isArray(); - - Option.Builder optionBuilder = - Option.builder() - .longOpt(optionName) - .desc( - String.format( - "Override option for %s , type: %s", - optionName, optionType.getSimpleName())); - - if (isCollection) { - optionBuilder.hasArgs().argName(optionType.getSimpleName().toUpperCase() + "..."); - } else { - optionBuilder.hasArg().argName(optionType.getSimpleName().toUpperCase()); - } - - options.addOption(optionBuilder.build()); - }); - - final List argsList = Arrays.asList(args); - if (argsList.contains("help") || argsList.isEmpty()) { - HelpFormatter formatter = new HelpFormatter(); - PrintWriter pw = new PrintWriter(sys().out()); - formatter.printHelp( - pw, - 200, - "tessera -configfile [-keygen ] [-pidfile ]", - null, - options, - formatter.getLeftPadding(), - formatter.getDescPadding(), - null, - false); - pw.flush(); - return new CliResult(0, true, null); - } - - try { - - final CommandLine line = new DefaultParser().parse(options, args); - - final Config config = parseConfig(line); - - if (Objects.nonNull(config)) { - - overrideOptions.forEach( - (optionName, value) -> { - if (line.hasOption(optionName)) { - String[] values = line.getOptionValues(optionName); - LOGGER.debug("Setting : {} with value(s) {}", optionName, values); -// OverrideUtil.setValue(config, optionName, values); - LOGGER.debug("Set : {} with value(s) {}", optionName, values); - } - }); - - final Set> violations = validator.validate(config); - if (!violations.isEmpty()) { - throw new ConstraintViolationException(violations); - } - - keyPasswordResolver.resolveKeyPasswords(config); - } - - pidFileMixin.call(); - - boolean suppressStartup = line.hasOption("keygen") && Objects.isNull(config); - - return new CliResult(0, suppressStartup, config); - - } catch (ParseException exp) { - throw new CliException(exp.getMessage()); - } - } - - private Config parseConfig(CommandLine commandLine) throws IOException { - - if (!commandLine.hasOption("configfile") - && !commandLine.hasOption("keygen") - && !commandLine.hasOption("updatepassword")) { - throw new CliException("One or more: -configfile or -keygen or -updatepassword options are required."); - } - - EncryptorConfig encryptorConfig = new EncryptorConfigParser().parse(commandLine); - KeyEncryptorFactory keyEncryptorFactory = KeyEncryptorFactory.newFactory(); - KeyEncryptor keyEncryptor = keyEncryptorFactory.create(encryptorConfig); - // Handle update password stuff - if (commandLine.hasOption("updatepassword")) { - - if (!commandLine.hasOption("encryptor.type")) { - System.out.println("No encryptor type defined NACL will be used as default"); - } - - new KeyUpdateParser(keyEncryptor, PasswordReaderFactory.create()).parse(commandLine); - - // return early so other options don't get processed - return null; - } // end update password stuff - - final List newKeys = new KeyGenerationParser(encryptorConfig).parse(commandLine); - - final Config config = new ConfigurationParser(newKeys).parse(commandLine); - Optional.ofNullable(config).ifPresent(c -> c.setEncryptor(encryptorConfig)); - return config; - } - - private Options buildBaseOptions() { - - final Options options = new Options(); - - options.addOption( - Option.builder("configfile") - .desc("Path to node configuration file") - .hasArg(true) - .optionalArg(false) - .numberOfArgs(1) - .argName("PATH") - .build()); - - options.addOption( - Option.builder("keygen") - .desc("Use this option to generate public/private keypair") - .hasArg(false) - .build()); - - options.addOption( - Option.builder("filename") - .desc( - "Comma-separated list of paths to save generated key files. Can also be used with keyvault. Number of args equals number of key-pairs generated.") - .hasArgs() - .optionalArg(false) - .argName("PATH") - .build()); - - options.addOption( - Option.builder("keygenconfig") - .desc("Path to private key config for generation of missing key files") - .hasArg(true) - .optionalArg(false) - .argName("PATH") - .build()); - - options.addOption( - Option.builder("output") - .desc("Generate updated config file with generated keys") - .hasArg(true) - .numberOfArgs(1) - .build()); - - options.addOption( - Option.builder("keygenvaulttype") - .desc("Type of key vault the generated key is to be saved in") - .hasArg() - .optionalArg(false) - .argName("KEYVAULTTYPE") - .build()); - - options.addOption( - Option.builder("keygenvaulturl") - .desc("Base url for key vault") - .hasArg() - .optionalArg(false) - .argName("STRING") - .build()); - - options.addOption( - Option.builder("keygenvaultapprole") - .desc("AppRole path for Hashicorp Vault authentication (defaults to 'approle')") - .hasArg() - .optionalArg(false) - .argName("STRING") - .build()); - - options.addOption( - Option.builder("keygenvaultkeystore") - .desc("Path to JKS keystore for TLS Hashicorp Vault communication") - .hasArg() - .optionalArg(false) - .argName("PATH") - .build()); - - options.addOption( - Option.builder("keygenvaulttruststore") - .desc("Path to JKS truststore for TLS Hashicorp Vault communication") - .hasArg() - .optionalArg(false) - .argName("PATH") - .build()); - - options.addOption( - Option.builder("keygenvaultsecretengine") - .desc("Name of already enabled Hashicorp v2 kv secret engine") - .hasArg() - .optionalArg(false) - .argName("STRING") - .build()); - - // Moved already to PicoCLI, but kept here for the help option - options.addOption( - Option.builder("pidfile") - .desc("Path to pid file") - .hasArg(true) - .optionalArg(false) - .numberOfArgs(1) - .argName("PATH") - .build()); - - options.addOption( - Option.builder("updatepassword").desc("Update the password for a locked key").hasArg(false).build()); - - options.addOption( - Option.builder() - .longOpt("encryptor.type") - .desc("Encryptor type NACL or EC") - .hasArg(true) - .optionalArg(false) - .numberOfArgs(1) - .build()); - - return options; - } - - // TODO: for testing, remove if possible - public void setAllParameters(final String[] allParameters) { - this.allParameters = allParameters; - } -} diff --git a/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/parsers/EncryptorConfigParser.java b/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/parsers/EncryptorConfigParser.java deleted file mode 100644 index 2ee948bb98..0000000000 --- a/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/parsers/EncryptorConfigParser.java +++ /dev/null @@ -1,71 +0,0 @@ -package com.quorum.tessera.config.cli.parsers; - -import com.quorum.tessera.cli.parsers.Parser; -import com.quorum.tessera.config.Config; -import com.quorum.tessera.config.EncryptorConfig; -import com.quorum.tessera.config.EncryptorType; -import com.quorum.tessera.config.util.JaxbUtil; -import com.quorum.tessera.io.FilesDelegate; -import java.io.IOException; -import java.nio.file.Paths; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import org.apache.commons.cli.CommandLine; - -public class EncryptorConfigParser implements Parser { - - protected static final String NO_ENCRYPTOR_DEFINED_ERROR_MESSAGE = - "Encryptor type hasn't been defined in the config file or as a cli arg"; - - private final FilesDelegate filesDelegate; - - public EncryptorConfigParser() { - this(FilesDelegate.create()); - } - - protected EncryptorConfigParser(FilesDelegate filesDelegate) { - this.filesDelegate = Objects.requireNonNull(filesDelegate); - } - - @Override - public EncryptorConfig parse(CommandLine commandLine) throws IOException { - - final String encryptorTypeValue = commandLine.getOptionValue("encryptor.type", EncryptorType.NACL.name()); - - if (commandLine.hasOption("configfile")) { - final String path = commandLine.getOptionValue("configfile"); - final Config config = JaxbUtil.unmarshal(filesDelegate.newInputStream(Paths.get(path)), Config.class); - - if (Objects.nonNull(config.getEncryptor())) { - return config.getEncryptor(); - } - } - - final EncryptorConfig encryptorConfig = new EncryptorConfig(); - - final EncryptorType encryptorType = EncryptorType.valueOf(encryptorTypeValue.toUpperCase()); - encryptorConfig.setType(encryptorType); - - Map properties = new HashMap<>(); - if (encryptorType == EncryptorType.EC) { - - Optional.ofNullable(commandLine.getOptionValue("encryptor.symmetricCipher")) - .ifPresent(v -> properties.put("symmetricCipher", v)); - - Optional.ofNullable(commandLine.getOptionValue("encryptor.ellipticCurve")) - .ifPresent(v -> properties.put("ellipticCurve", v)); - - Optional.ofNullable(commandLine.getOptionValue("encryptor.nonceLength")) - .ifPresent(v -> properties.put("nonceLength", v)); - - Optional.ofNullable(commandLine.getOptionValue("encryptor.sharedKeyLength")) - .ifPresent(v -> properties.put("sharedKeyLength", v)); - } - - encryptorConfig.setProperties(properties); - - return encryptorConfig; - } -} diff --git a/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/parsers/KeyGenerationParser.java b/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/parsers/KeyGenerationParser.java deleted file mode 100644 index 103fdb6478..0000000000 --- a/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/parsers/KeyGenerationParser.java +++ /dev/null @@ -1,150 +0,0 @@ -package com.quorum.tessera.config.cli.parsers; - -import com.quorum.tessera.cli.CliException; -import com.quorum.tessera.cli.parsers.Parser; -import com.quorum.tessera.config.*; -import com.quorum.tessera.config.keypairs.ConfigKeyPair; -import com.quorum.tessera.config.util.JaxbUtil; -import com.quorum.tessera.key.generation.KeyGenerator; -import com.quorum.tessera.key.generation.KeyGeneratorFactory; -import com.quorum.tessera.key.generation.KeyVaultOptions; -import org.apache.commons.cli.CommandLine; - -import javax.validation.ConstraintViolation; -import javax.validation.ConstraintViolationException; -import javax.validation.Validation; -import javax.validation.Validator; -import java.io.IOException; -import java.io.InputStream; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import static java.util.Collections.singletonList; -import java.util.Objects; - -public class KeyGenerationParser implements Parser> { - - private final KeyGeneratorFactory factory = KeyGeneratorFactory.newFactory(); - - private final Validator validator = - Validation.byDefaultProvider().configure().ignoreXmlConfiguration().buildValidatorFactory().getValidator(); - - private final EncryptorConfig encryptorConfig; - - public KeyGenerationParser(EncryptorConfig encryptorConfig) { - this.encryptorConfig = Objects.requireNonNull(encryptorConfig); - } - - @Override - public List parse(final CommandLine commandLine) throws IOException { - - final ArgonOptions argonOptions = this.argonOptions(commandLine).orElse(null); - final KeyVaultOptions keyVaultOptions = this.keyVaultOptions(commandLine).orElse(null); - final KeyVaultConfig keyVaultConfig = this.keyVaultConfig(commandLine).orElse(null); - - final KeyGenerator generator = factory.create(keyVaultConfig, encryptorConfig); - - if (commandLine.hasOption("keygen")) { - return this.filenames(commandLine).stream() - .map(name -> generator.generate(name, argonOptions, keyVaultOptions)) - .collect(Collectors.toList()); - } - - return new ArrayList<>(); - } - - private Optional argonOptions(final CommandLine commandLine) throws IOException { - - if (commandLine.hasOption("keygenconfig")) { - final String pathName = commandLine.getOptionValue("keygenconfig"); - final InputStream configStream = Files.newInputStream(Paths.get(pathName)); - - final ArgonOptions argonOptions = JaxbUtil.unmarshal(configStream, ArgonOptions.class); - return Optional.of(argonOptions); - } - - return Optional.empty(); - } - - private Optional keyVaultOptions(final CommandLine commandLine) { - Optional secretEngineName = Optional.ofNullable(commandLine.getOptionValue("keygenvaultsecretengine")); - - return secretEngineName.map(KeyVaultOptions::new); - } - - private List filenames(final CommandLine commandLine) { - - if (commandLine.hasOption("filename")) { - - final String keyNames = commandLine.getOptionValue("filename"); - if (keyNames != null) { - return Stream.of(keyNames.split(",")).collect(Collectors.toList()); - } - } - - return singletonList(""); - } - - private Optional keyVaultConfig(CommandLine commandLine) { - if (!commandLine.hasOption("keygenvaulttype") && !commandLine.hasOption("keygenvaulturl")) { - return Optional.empty(); - } - - final String t = commandLine.getOptionValue("keygenvaulttype"); - - KeyVaultType keyVaultType; - try { - keyVaultType = KeyVaultType.valueOf(t.trim().toUpperCase()); - } catch (IllegalArgumentException | NullPointerException e) { - throw new CliException("Key vault type either not provided or not recognised"); - } - - String keyVaultUrl = commandLine.getOptionValue("keygenvaulturl"); - - KeyVaultConfig keyVaultConfig; - - if (keyVaultType.equals(KeyVaultType.AZURE)) { - keyVaultConfig = new AzureKeyVaultConfig(keyVaultUrl); - - Set> violations = - validator.validate((AzureKeyVaultConfig) keyVaultConfig); - - if (!violations.isEmpty()) { - throw new ConstraintViolationException(violations); - } - } else { - if (!commandLine.hasOption("filename")) { - throw new CliException( - "At least one -filename must be provided when saving generated keys in a Hashicorp Vault"); - } - - String approlePath = commandLine.getOptionValue("keygenvaultapprole"); - - Optional tlsKeyStorePath = - Optional.ofNullable(commandLine.getOptionValue("keygenvaultkeystore")).map(Paths::get); - - Optional tlsTrustStorePath = - Optional.ofNullable(commandLine.getOptionValue("keygenvaulttruststore")).map(Paths::get); - - keyVaultConfig = - new HashicorpKeyVaultConfig( - keyVaultUrl, approlePath, tlsKeyStorePath.orElse(null), tlsTrustStorePath.orElse(null)); - - Set> violations = - validator.validate((HashicorpKeyVaultConfig) keyVaultConfig); - - if (!violations.isEmpty()) { - throw new ConstraintViolationException(violations); - } - } - - return Optional.of(keyVaultConfig); - } -} diff --git a/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/parsers/KeyUpdateParser.java b/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/parsers/KeyUpdateParser.java deleted file mode 100644 index 856ebc6bb4..0000000000 --- a/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/parsers/KeyUpdateParser.java +++ /dev/null @@ -1,129 +0,0 @@ -package com.quorum.tessera.config.cli.parsers; - -import com.quorum.tessera.cli.parsers.Parser; -import com.quorum.tessera.config.ArgonOptions; -import com.quorum.tessera.config.KeyDataConfig; -import com.quorum.tessera.config.PrivateKeyData; -import com.quorum.tessera.config.PrivateKeyType; -import com.quorum.tessera.config.keys.KeyEncryptor; -import com.quorum.tessera.config.util.JaxbUtil; -import com.quorum.tessera.encryption.PrivateKey; -import com.quorum.tessera.io.SystemAdapter; -import com.quorum.tessera.passwords.PasswordReader; -import org.apache.commons.cli.CommandLine; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.Base64; -import java.util.List; -import java.util.Objects; -import java.util.Optional; - -import static java.nio.charset.StandardCharsets.UTF_8; -import static java.util.Collections.emptyList; -import static java.util.Collections.singletonList; - -public class KeyUpdateParser implements Parser { - - private static final Logger LOGGER = LoggerFactory.getLogger(KeyUpdateParser.class); - - private final KeyEncryptor keyEncryptor; - - private final PasswordReader passwordReader; - - public KeyUpdateParser(final KeyEncryptor keyEncryptor, final PasswordReader passwordReader) { - this.keyEncryptor = Objects.requireNonNull(keyEncryptor); - this.passwordReader = Objects.requireNonNull(passwordReader); - } - - @Override - public Optional parse(final CommandLine commandLine) throws IOException { - final ArgonOptions argonOptions = argonOptions(commandLine); - final List passwords = passwords(commandLine); - final Path keypath = privateKeyPath(commandLine); - - final KeyDataConfig keyDataConfig = JaxbUtil.unmarshal(Files.newInputStream(keypath), KeyDataConfig.class); - final PrivateKey privateKey = this.getExistingKey(keyDataConfig, passwords); - - final String newPassword = passwordReader.requestUserPassword(); - - final KeyDataConfig updatedKey; - if(newPassword.isEmpty()) { - final PrivateKeyData privateKeyData = new PrivateKeyData(privateKey.encodeToBase64(), null, null, null, null); - updatedKey = new KeyDataConfig(privateKeyData, PrivateKeyType.UNLOCKED); - } else { - final PrivateKeyData privateKeyData = keyEncryptor.encryptPrivateKey(privateKey, newPassword, argonOptions); - updatedKey = new KeyDataConfig(privateKeyData, PrivateKeyType.LOCKED); - } - - //write the key to file - Files.write(keypath, JaxbUtil.marshalToString(updatedKey).getBytes(UTF_8)); - SystemAdapter.INSTANCE.out().println("Private key at " + keypath.toString() + " updated."); - - return Optional.empty(); - } - - PrivateKey getExistingKey(final KeyDataConfig kdc, final List passwords) { - - if (kdc.getType() == PrivateKeyType.UNLOCKED) { - byte[] privateKeyData = Base64.getDecoder().decode(kdc.getValue().getBytes(UTF_8)); - return PrivateKey.from(privateKeyData); - } else { - - for (final String pass : passwords) { - try { - return PrivateKey.from(keyEncryptor.decryptPrivateKey(kdc.getPrivateKeyData(), pass).getKeyBytes()); - } catch (final Exception e) { - LOGGER.debug("Password failed to decrypt. Trying next if available."); - } - } - - throw new IllegalArgumentException("Locked key but no valid password given"); - } - } - - static Path privateKeyPath(final CommandLine commandLine) { - final String privateKeyPath = commandLine.getOptionValue("keys.keyData.privateKeyPath"); - - if (privateKeyPath == null) { - throw new IllegalArgumentException("Private key path cannot be null when updating key password"); - } - - final Path keypath = Paths.get(privateKeyPath); - if (Files.notExists(keypath)) { - throw new IllegalArgumentException("Private key path must exist when updating key password"); - } - - return keypath; - } - - static List passwords(final CommandLine commandLine) throws IOException { - final String password = commandLine.getOptionValue("keys.passwords"); - final String passwordFile = commandLine.getOptionValue("keys.passwordFile"); - - if (password != null) { - return singletonList(password); - } else if (passwordFile != null) { - return Files.readAllLines(Paths.get(passwordFile)); - } else { - return emptyList(); - } - - } - - static ArgonOptions argonOptions(final CommandLine commandLine) { - final String algorithm = commandLine.getOptionValue("keys.keyData.config.data.aopts.algorithm", "i"); - final String iterations = commandLine.getOptionValue("keys.keyData.config.data.aopts.iterations", "10"); - final String memory = commandLine.getOptionValue("keys.keyData.config.data.aopts.memory", "1048576"); - final String parallelism = commandLine.getOptionValue("keys.keyData.config.data.aopts.parallelism", "4"); - - return new ArgonOptions( - algorithm, Integer.valueOf(iterations), Integer.valueOf(memory), Integer.valueOf(parallelism) - ); - } - -} diff --git a/cli/config-cli/src/main/resources/META-INF/services/com.quorum.tessera.cli.CliAdapter b/cli/config-cli/src/main/resources/META-INF/services/com.quorum.tessera.cli.CliAdapter deleted file mode 100644 index 023e8dc70e..0000000000 --- a/cli/config-cli/src/main/resources/META-INF/services/com.quorum.tessera.cli.CliAdapter +++ /dev/null @@ -1 +0,0 @@ -com.quorum.tessera.config.cli.DefaultCliAdapter diff --git a/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/DefaultCliAdapterTest.java b/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/DefaultCliAdapterTest.java deleted file mode 100644 index c8151c8c97..0000000000 --- a/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/DefaultCliAdapterTest.java +++ /dev/null @@ -1,395 +0,0 @@ -package com.quorum.tessera.config.cli; - -import com.quorum.tessera.config.cli.keys.MockKeyGeneratorFactory; -import org.junit.Before; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class DefaultCliAdapterTest { - - private static final Logger LOGGER = LoggerFactory.getLogger(DefaultCliAdapterTest.class); - - private DefaultCliAdapter cliAdapter; - - @Before - public void setUp() { - MockKeyGeneratorFactory.reset(); - this.cliAdapter = new DefaultCliAdapter(); - } - - // @Test - // public void getType() { - // assertThat(cliAdapter.getType()).isEqualTo(CliType.CONFIG); - // } - // - // @Test - // public void help() throws Exception { - // - // final CliResult result = cliAdapter.execute("help"); - // - // assertThat(result).isNotNull(); - // assertThat(result.getConfig()).isNotPresent(); - // assertThat(result.getStatus()).isEqualTo(0); - // assertThat(result.isSuppressStartup()).isTrue(); - // } - // - // @Test - // public void helpViaCall() throws Exception { - // cliAdapter.setAllParameters(new String[] {"help"}); - // final CliResult result = cliAdapter.call(); - // - // assertThat(result).isNotNull(); - // assertThat(result.getConfig()).isNotPresent(); - // assertThat(result.getStatus()).isEqualTo(0); - // assertThat(result.isSuppressStartup()).isTrue(); - // } - // - // @Test - // public void noArgsPrintsHelp() throws Exception { - // - // final CliResult result = cliAdapter.execute(); - // - // assertThat(result).isNotNull(); - // assertThat(result.getConfig()).isNotPresent(); - // assertThat(result.getStatus()).isEqualTo(0); - // assertThat(result.isSuppressStartup()).isTrue(); - // } - // - // @Test - // public void withValidConfig() throws Exception { - // - // Path configFile = createAndPopulatePaths(getClass().getResource("/sample-config.json")); - // CliResult result = cliAdapter.execute("-configfile", configFile.toString()); - // - // assertThat(result).isNotNull(); - // assertThat(result.getConfig()).isPresent(); - // assertThat(result.getStatus()).isEqualTo(0); - // assertThat(result.isSuppressStartup()).isFalse(); - // } - // - // @Test(expected = CliException.class) - // public void processArgsMissing() throws Exception { - // cliAdapter.execute("-configfile"); - // } - // - // @Test - // public void withConstraintViolations() throws Exception { - // - // Path configFile = createAndPopulatePaths(getClass().getResource("/missing-config.json")); - // - // try { - // cliAdapter.execute("-configfile", configFile.toString()); - // failBecauseExceptionWasNotThrown(ConstraintViolationException.class); - // } catch (ConstraintViolationException ex) { - // assertThat(ex.getConstraintViolations()).isNotEmpty(); - // } - // } - // - // @Test - // public void keygenWithConfig() throws Exception { - // - // final KeyGenerator keyGenerator = MockKeyGeneratorFactory.getMockKeyGenerator(); - // - // Path publicKeyPath = Files.createTempFile(UUID.randomUUID().toString(), ""); - // Path privateKeyPath = Files.createTempFile(UUID.randomUUID().toString(), ""); - // - // Files.write(privateKeyPath, Arrays.asList("SOMEDATA")); - // Files.write(publicKeyPath, Arrays.asList("SOMEDATA")); - // - // KeyEncryptor keyEncryptor = mock(KeyEncryptor.class); - // KeyDataConfig keyDataConfig = mock(KeyDataConfig.class); - // when(keyDataConfig.getType()).thenReturn(PrivateKeyType.UNLOCKED); - // - // FilesystemKeyPair keypair = new FilesystemKeyPair(publicKeyPath, privateKeyPath, keyEncryptor); - // when(keyGenerator.generate(anyString(), eq(null), eq(null))).thenReturn(keypair); - // - // Path unixSocketPath = Files.createTempFile(UUID.randomUUID().toString(), ".ipc"); - // - // Map params = new HashMap<>(); - // params.put("unixSocketPath", unixSocketPath.toString()); - // - // Path configFilePath = ElUtil.createTempFileFromTemplate(getClass().getResource("/keygen-sample.json"), - // params); - // - // CliResult result = - // cliAdapter.execute( - // "-keygen", "-filename", UUID.randomUUID().toString(), "-configfile", - // configFilePath.toString()); - // - // assertThat(result).isNotNull(); - // assertThat(result.getStatus()).isEqualTo(0); - // assertThat(result.getConfig()).isNotNull(); - // assertThat(result.isSuppressStartup()).isFalse(); - // - // verify(keyGenerator).generate(anyString(), eq(null), eq(null)); - // verifyNoMoreInteractions(keyGenerator); - // } - // - // @Test - // public void keygenThenExit() throws Exception { - // - // final CliResult result = cliAdapter.execute("-keygen", "--encryptor.type", "NACL"); - // - // assertThat(result).isNotNull(); - // assertThat(result.isSuppressStartup()).isTrue(); - // } - // - // @Test - // public void fileNameWithoutKeygenArgThenExit() throws Exception { - // - // try { - // cliAdapter.execute("-filename"); - // failBecauseExceptionWasNotThrown(CliException.class); - // } catch (CliException ex) { - // assertThat(ex).hasMessage("Missing argument for option: filename"); - // } - // } - // - // @Test - // public void outputWithoutKeygenOrConfig() { - // - // final Throwable throwable = catchThrowable(() -> cliAdapter.execute("-output", "bogus")); - // assertThat(throwable) - // .isInstanceOf(CliException.class) - // .hasMessage("One or more: -configfile or -keygen or -updatepassword options are required."); - // } - // - // @Test - // public void output() throws Exception { - // - // final KeyGenerator keyGenerator = MockKeyGeneratorFactory.getMockKeyGenerator(); - // - // Path publicKeyPath = Files.createTempFile(UUID.randomUUID().toString(), ""); - // Path privateKeyPath = Files.createTempFile(UUID.randomUUID().toString(), ""); - // - // Files.write(privateKeyPath, Arrays.asList("SOMEDATA")); - // Files.write(publicKeyPath, Arrays.asList("SOMEDATA")); - // - // InlineKeypair inlineKeypair = mock(InlineKeypair.class); - // - // KeyDataConfig keyDataConfig = mock(KeyDataConfig.class); - // when(keyDataConfig.getType()).thenReturn(PrivateKeyType.UNLOCKED); - // when(inlineKeypair.getPrivateKeyConfig()).thenReturn(keyDataConfig); - // - // KeyEncryptor keyEncryptor = mock(KeyEncryptor.class); - // - // FilesystemKeyPair keypair = new FilesystemKeyPair(publicKeyPath, privateKeyPath, keyEncryptor); - // when(keyGenerator.generate(anyString(), eq(null), eq(null))).thenReturn(keypair); - // - // Path generatedKey = Files.createTempFile(UUID.randomUUID().toString(), ".tmp"); - // - // Files.deleteIfExists(generatedKey); - // assertThat(Files.exists(generatedKey)).isFalse(); - // - // Path keyConfigPath = Paths.get(getClass().getResource("/lockedprivatekey.json").toURI()); - // Path tempKeyFile = Files.createTempFile(UUID.randomUUID().toString(), ""); - // - // Path unixSocketPath = Files.createTempFile(UUID.randomUUID().toString(), ".ipc"); - // Map params = new HashMap<>(); - // params.put("unixSocketPath", unixSocketPath.toString()); - // - // Path configFile = createAndPopulatePaths(getClass().getResource("/keygen-sample.json")); - // - // CliResult result = - // cliAdapter.execute( - // "-keygen", keyConfigPath.toString(), - // "-filename", tempKeyFile.toAbsolutePath().toString(), - // "-output", generatedKey.toFile().getPath(), - // "-configfile", configFile.toString()); - // - // assertThat(result).isNotNull(); - // assertThat(Files.exists(generatedKey)).isTrue(); - // - // try { - // cliAdapter.execute( - // "-keygen", keyConfigPath.toString(), - // "-filename", UUID.randomUUID().toString(), - // "-output", generatedKey.toFile().getPath(), - // "-configfile", configFile.toString()); - // failBecauseExceptionWasNotThrown(Exception.class); - // } catch (Exception ex) { - // assertThat(ex).isInstanceOf(UncheckedIOException.class); - // assertThat(ex.getCause()).isExactlyInstanceOf(FileAlreadyExistsException.class); - // } - // } - // - // @Test - // public void dynOption() throws Exception { - // - // Path configFile = createAndPopulatePaths(getClass().getResource("/sample-config.json")); - // - // CliResult result = cliAdapter.execute("-configfile", configFile.toString(), "-jdbc.username", "somename"); - // - // assertThat(result).isNotNull(); - // assertThat(result.getConfig()).isPresent(); - // assertThat(result.getConfig().get().getJdbcConfig().getUsername()).isEqualTo("somename"); - // assertThat(result.getConfig().get().getJdbcConfig().getPassword()).isEqualTo("tiger"); - // } - // - // @Ignore - // public void withInvalidPath() throws Exception { - // // unixSocketPath - // Map params = new HashMap<>(); - // params.put("publicKeyPath", "BOGUS.bogus"); - // params.put("privateKeyPath", "BOGUS.bogus"); - // - // Path configFile = - // ElUtil.createTempFileFromTemplate(getClass().getResource("/sample-config-invalidpath.json"), - // params); - // - // try { - // cliAdapter.execute("-configfile", configFile.toString()); - // failBecauseExceptionWasNotThrown(ConstraintViolationException.class); - // } catch (ConstraintViolationException ex) { - // assertThat(ex.getConstraintViolations()) - // .hasSize(1) - // .extracting("messageTemplate") - // .containsExactly("{UnsupportedKeyPair.message}"); - // } - // } - // - // @Test - // public void withEmptyConfigOverrideAll() throws Exception { - // - // Path unixSocketFile = Files.createTempFile("unixSocketFile", ".ipc"); - // unixSocketFile.toFile().deleteOnExit(); - // - // Path configFile = Files.createTempFile("withEmptyConfigOverrideAll", ".json"); - // configFile.toFile().deleteOnExit(); - // Files.write(configFile, "{}".getBytes()); - // try { - // CliResult result = - // cliAdapter.execute( - // "-configfile", - // configFile.toString(), - // "--unixSocketFile", - // unixSocketFile.toString(), - // "--encryptor.type", - // "NACL"); - // - // assertThat(result).isNotNull(); - // failBecauseExceptionWasNotThrown(ConstraintViolationException.class); - // } catch (ConstraintViolationException ex) { - // ex.getConstraintViolations().forEach(v -> LOGGER.info("{}", v)); - // } - // } - // - // @Test - // public void overrideAlwaysSendTo() throws Exception { - // - // String alwaysSendToKey = "giizjhZQM6peq52O7icVFxdTmTYinQSUsvyhXzgZqkE="; - // - // Path configFile = createAndPopulatePaths(getClass().getResource("/sample-config.json")); - // CliResult result = null; - // try { - // result = cliAdapter.execute("-configfile", configFile.toString(), "-alwaysSendTo", alwaysSendToKey); - // } catch (Exception ex) { - // fail(ex.getMessage()); - // } - // assertThat(result).isNotNull(); - // assertThat(result.getConfig()).isPresent(); - // assertThat(result.getConfig().get().getAlwaysSendTo()).hasSize(2); - // assertThat(result.getConfig().get().getAlwaysSendTo()) - // .containsExactly("/+UuD63zItL1EbjxkKUljMgG8Z1w0AJ8pNOR4iq2yQc=", alwaysSendToKey); - // } - // - // @Test - // public void overridePeers() throws Exception { - // - // Path configFile = createAndPopulatePaths(getClass().getResource("/sample-config.json")); - // - // CliResult result = - // cliAdapter.execute( - // "-configfile", - // configFile.toString(), - // "-peer.url", - // "anotherpeer", - // "-peer.url", - // "yetanotherpeer"); - // - // assertThat(result).isNotNull(); - // assertThat(result.getConfig()).isPresent(); - // assertThat(result.getConfig().get().getPeers()).hasSize(4); - // assertThat(result.getConfig().get().getPeers().stream().map(Peer::getUrl)) - // .containsExactlyInAnyOrder("anotherpeer", "yetanotherpeer", "http://bogus1.com", - // "http://bogus2.com"); - // } - // - // @Test - // public void updatingPasswordsDoesntProcessOtherOptions() throws Exception { - // MockKeyGeneratorFactory.reset(); - // - // final InputStream oldIn = System.in; - // final InputStream inputStream = - // new ByteArrayInputStream((System.lineSeparator() + System.lineSeparator()).getBytes()); - // System.setIn(inputStream); - // - // final KeyDataConfig startingKey = - // JaxbUtil.unmarshal(getClass().getResourceAsStream("/lockedprivatekey.json"), KeyDataConfig.class); - // - // final Path key = Files.createTempFile("key", ".key"); - // Files.write(key, JaxbUtil.marshalToString(startingKey).getBytes()); - // - // final CliResult result = - // cliAdapter.execute( - // "-updatepassword", - // "--keys.keyData.privateKeyPath", - // key.toString(), - // "--keys.passwords", - // "testpassword"); - // - // assertThat(result).isNotNull(); - // - // Mockito.verifyZeroInteractions(MockKeyGeneratorFactory.getMockKeyGenerator()); - // System.setIn(oldIn); - // } - // - // @Test - // public void suppressStartupForKeygenOption() throws Exception { - // final CliResult cliResult = cliAdapter.execute("-keygen", "--encryptor.type", "NACL"); - // - // assertThat(cliResult.isSuppressStartup()).isTrue(); - // } - // - // @Test - // public void allowStartupForKeygenAndConfigfileOptions() throws Exception { - // final KeyGenerator keyGenerator = MockKeyGeneratorFactory.getMockKeyGenerator(); - // Path publicKeyPath = Files.createTempFile(UUID.randomUUID().toString(), ""); - // Path privateKeyPath = Files.createTempFile(UUID.randomUUID().toString(), ""); - // - // Files.write(privateKeyPath, Arrays.asList("SOMEDATA")); - // Files.write(publicKeyPath, Arrays.asList("SOMEDATA")); - // - // FilesystemKeyPair keypair = new FilesystemKeyPair(publicKeyPath, privateKeyPath, null); - // when(keyGenerator.generate(anyString(), eq(null), eq(null))).thenReturn(keypair); - // - // final Path configFile = createAndPopulatePaths(getClass().getResource("/sample-config.json")); - // - // final CliResult cliResult = cliAdapter.execute("-keygen", "-configfile", configFile.toString()); - // - // assertThat(cliResult.isSuppressStartup()).isFalse(); - // } - // - // @Test - // public void suppressStartupForKeygenAndVaultUrlAndConfigfileOptions() throws Exception { - // final KeyGenerator keyGenerator = MockKeyGeneratorFactory.getMockKeyGenerator(); - // - // final FilesystemKeyPair keypair = new FilesystemKeyPair(Paths.get(""), Paths.get(""), null); - // when(keyGenerator.generate(anyString(), eq(null), eq(null))).thenReturn(keypair); - // - // final Path configFile = createAndPopulatePaths(getClass().getResource("/sample-config.json")); - // final String vaultUrl = "https://test.vault.azure.net"; - // - // final CliResult cliResult = - // cliAdapter.execute( - // "-keygen", - // "-keygenvaulttype", - // "AZURE", - // "-keygenvaulturl", - // vaultUrl, - // "-configfile", - // configFile.toString()); - // - // assertThat(cliResult.isSuppressStartup()).isTrue(); - // } -} diff --git a/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/parsers/EncryptorConfigParserTest.java b/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/parsers/EncryptorConfigParserTest.java deleted file mode 100644 index 125b0f2224..0000000000 --- a/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/parsers/EncryptorConfigParserTest.java +++ /dev/null @@ -1,158 +0,0 @@ -package com.quorum.tessera.config.cli.parsers; - -import com.quorum.tessera.io.FilesDelegate; -import org.apache.commons.cli.CommandLine; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verifyNoMoreInteractions; - -public class EncryptorConfigParserTest { - - private EncryptorConfigParser parser; - - private CommandLine commandLine; - - private FilesDelegate filesDelegate; - - @Before - public void onSetup() { - commandLine = mock(CommandLine.class); - filesDelegate = mock(FilesDelegate.class); - this.parser = new EncryptorConfigParser(filesDelegate); - } - - @After - public void onTearDown() { - verifyNoMoreInteractions(commandLine); - } - - @Test - public void constructor() { - parser = new EncryptorConfigParser(); - assertThat(parser).isNotNull(); - } - - // @Test - // public void ellipticalCurveNoPropertiesDefined() throws IOException { - // when(commandLine.hasOption("configfile")).thenReturn(false); - // - // when(commandLine.getOptionValue("encryptor.type", EncryptorType.NACL.name())) - // .thenReturn(EncryptorType.EC.name()); - // - // EncryptorConfig result = parser.parse(commandLine); - // - // assertThat(result.getType()).isEqualTo(EncryptorType.EC); - // assertThat(result.getProperties()).isEmpty(); - // - // verify(commandLine).getOptionValue("encryptor.type", EncryptorType.NACL.name()); - // verify(commandLine).hasOption("configfile"); - // - // verify(commandLine).getOptionValue("encryptor.symmetricCipher"); - // verify(commandLine).getOptionValue("encryptor.ellipticCurve"); - // verify(commandLine).getOptionValue("encryptor.nonceLength"); - // verify(commandLine).getOptionValue("encryptor.sharedKeyLength"); - // } - // - // @Test - // public void ellipticalCurveWithDefinedProperties() throws IOException { - // - // when(commandLine.getOptionValue("encryptor.type", EncryptorType.NACL.name())) - // .thenReturn(EncryptorType.EC.name()); - // - // Config config = new Config(); - // config.setEncryptor(new EncryptorConfig()); - // config.getEncryptor().setType(EncryptorType.EC); - // - // when(commandLine.hasOption("configfile")).thenReturn(false); - // - // when(commandLine.getOptionValue("encryptor.type")).thenReturn(EncryptorType.EC.name()); - // - // when(commandLine.getOptionValue("encryptor.symmetricCipher")).thenReturn("somecipher"); - // when(commandLine.getOptionValue("encryptor.ellipticCurve")).thenReturn("somecurve"); - // when(commandLine.getOptionValue("encryptor.nonceLength")).thenReturn("3"); - // when(commandLine.getOptionValue("encryptor.sharedKeyLength")).thenReturn("2"); - // - // EncryptorConfig result = parser.parse(commandLine); - // - // assertThat(result.getType()).isEqualTo(EncryptorType.EC); - // assertThat(result.getProperties()) - // .containsOnlyKeys("symmetricCipher", "ellipticCurve", "nonceLength", "sharedKeyLength"); - // - // assertThat(result.getProperties().get("symmetricCipher")).isEqualTo("somecipher"); - // assertThat(result.getProperties().get("ellipticCurve")).isEqualTo("somecurve"); - // assertThat(result.getProperties().get("nonceLength")).isEqualTo("3"); - // assertThat(result.getProperties().get("sharedKeyLength")).isEqualTo("2"); - // - // verify(commandLine).getOptionValue("encryptor.symmetricCipher"); - // verify(commandLine).getOptionValue("encryptor.ellipticCurve"); - // verify(commandLine).getOptionValue("encryptor.nonceLength"); - // verify(commandLine).getOptionValue("encryptor.sharedKeyLength"); - // - // verify(commandLine).getOptionValue("encryptor.type", EncryptorType.NACL.name()); - // verify(commandLine).hasOption("configfile"); - // } - // - // @Test - // public void noEncryptorTypeDefinedAndNoConfigFile() throws IOException { - // - // when(commandLine.getOptionValue("encryptor.type", EncryptorType.NACL.name())) - // .thenReturn(EncryptorType.NACL.name()); - // when(commandLine.hasOption("configfile")).thenReturn(false); - // EncryptorConfig result = parser.parse(commandLine); - // assertThat(result.getType()).isEqualTo(EncryptorType.NACL); - // assertThat(result.getProperties()).isEmpty(); - // - // verify(commandLine).getOptionValue("encryptor.type", EncryptorType.NACL.name()); - // verify(commandLine).hasOption("configfile"); - // } - // - // @Test - // public void keyGenUsedDefaulIfNoTypeDefined() throws Exception { - // when(commandLine.getOptionValue("encryptor.type", EncryptorType.NACL.name())) - // .thenReturn(EncryptorType.NACL.name()); - // when(commandLine.hasOption("configfile")).thenReturn(true); - // when(commandLine.getOptionValue("configfile")).thenReturn("somepath"); - // - // InputStream inputStream = new ByteArrayInputStream("{}".getBytes()); - // when(filesDelegate.newInputStream(any(Path.class))).thenReturn(inputStream); - // - // EncryptorConfig result = parser.parse(commandLine); - // - // assertThat(result.getType()).isEqualTo(EncryptorType.NACL); - // assertThat(result.getProperties()).isEmpty(); - // - // verify(commandLine).getOptionValue("encryptor.type", EncryptorType.NACL.name()); - // verify(commandLine).getOptionValue("configfile"); - // verify(commandLine).hasOption("configfile"); - // - // verify(filesDelegate).newInputStream(any(Path.class)); - // } - // - // @Test - // public void encryptorConfigFromFile() throws Exception { - // when(commandLine.getOptionValue("encryptor.type", EncryptorType.NACL.name())) - // .thenReturn(EncryptorType.NACL.name()); - // when(commandLine.hasOption("configfile")).thenReturn(true); - // when(commandLine.getOptionValue("configfile")).thenReturn("somepath"); - // - // String jsonConfig = "{\"encryptor\": {\"type\": \"EC\"}}"; - // - // InputStream inputStream = new ByteArrayInputStream(jsonConfig.getBytes()); - // when(filesDelegate.newInputStream(any(Path.class))).thenReturn(inputStream); - // - // EncryptorConfig result = parser.parse(commandLine); - // - // assertThat(result.getType()).isEqualTo(EncryptorType.EC); - // - // verify(commandLine).getOptionValue("encryptor.type", EncryptorType.NACL.name()); - // verify(commandLine).hasOption("configfile"); - // verify(commandLine).getOptionValue("configfile"); - // verify(filesDelegate).newInputStream(any(Path.class)); - // - // verifyNoMoreInteractions(filesDelegate); - // } -} diff --git a/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/parsers/KeyGenerationParserTest.java b/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/parsers/KeyGenerationParserTest.java deleted file mode 100644 index deb743dadb..0000000000 --- a/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/parsers/KeyGenerationParserTest.java +++ /dev/null @@ -1,365 +0,0 @@ -package com.quorum.tessera.config.cli.parsers; - -import com.quorum.tessera.config.EncryptorConfig; -import com.quorum.tessera.config.EncryptorType; -import org.apache.commons.cli.CommandLine; -import org.junit.Test; - -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Collections; -import java.util.UUID; - -import static org.mockito.Mockito.*; - -public class KeyGenerationParserTest { - - private KeyGenerationParser parser = - new KeyGenerationParser( - new EncryptorConfig() { - { - setType(EncryptorType.NACL); - setProperties(Collections.emptyMap()); - } - }); - - private CommandLine commandLine = mock(CommandLine.class); - - // @Test - // public void notProvidingArgonOptionsGivesNull() throws Exception { - // final Path keyLocation = Files.createTempFile(UUID.randomUUID().toString(), ""); - // - // when(commandLine.hasOption("keygen")).thenReturn(true); - // when(commandLine.hasOption("filename")).thenReturn(true); - // when(commandLine.getOptionValue("filename")).thenReturn(keyLocation.toString()); - // when(commandLine.hasOption("keygenconfig")).thenReturn(false); - // - // final List result = parser.parse(commandLine); - // - // assertThat(result).isNotNull().hasSize(1); - // - // final KeyGenerator keyGenerator = MockKeyGeneratorFactory.getMockKeyGenerator(); - // - // final ArgumentCaptor captor = ArgumentCaptor.forClass(ArgonOptions.class); - // verify(keyGenerator).generate(eq(keyLocation.toString()), captor.capture(), eq(null)); - // - // assertThat(captor.getAllValues()).hasSize(1); - // assertThat(captor.getValue()).isNull(); - // } - // - // @Test - // public void providingArgonOptionsGetSentCorrectly() throws Exception { - // final String options = "{\"variant\": \"id\",\"memory\": 100,\"iterations\": 7,\"parallelism\": 22}"; - // final Path argonOptions = Files.createTempFile(UUID.randomUUID().toString(), ""); - // Files.write(argonOptions, options.getBytes()); - // - // final Path keyLocation = Files.createTempFile(UUID.randomUUID().toString(), ""); - // - // when(commandLine.hasOption("keygen")).thenReturn(true); - // when(commandLine.hasOption("filename")).thenReturn(true); - // when(commandLine.getOptionValue("filename")).thenReturn(keyLocation.toString()); - // when(commandLine.hasOption("keygenconfig")).thenReturn(true); - // when(commandLine.getOptionValue("keygenconfig")).thenReturn(argonOptions.toString()); - // - // final List result = parser.parse(commandLine); - // - // assertThat(result).isNotNull().hasSize(1); - // - // final KeyGenerator keyGenerator = MockKeyGeneratorFactory.getMockKeyGenerator(); - // - // final ArgumentCaptor captor = ArgumentCaptor.forClass(ArgonOptions.class); - // verify(keyGenerator).generate(eq(keyLocation.toString()), captor.capture(), eq(null)); - // - // assertThat(captor.getAllValues()).hasSize(1); - // assertThat(captor.getValue().getAlgorithm()).isEqualTo("id"); - // assertThat(captor.getValue().getIterations()).isEqualTo(7); - // assertThat(captor.getValue().getMemory()).isEqualTo(100); - // assertThat(captor.getValue().getParallelism()).isEqualTo(22); - // } - // - // @Test - // public void keygenWithNoName() throws Exception { - // - // when(commandLine.hasOption("keygen")).thenReturn(true); - // when(commandLine.hasOption("filename")).thenReturn(false); - // when(commandLine.hasOption("keygenconfig")).thenReturn(false); - // - // final List result = this.parser.parse(commandLine); - // - // assertThat(result).isNotNull().hasSize(1); - // - // final KeyGenerator keyGenerator = MockKeyGeneratorFactory.getMockKeyGenerator(); - // verify(keyGenerator).generate("", null, null); - // } - // - // @Test - // public void keygenNotGivenReturnsEmptyList() throws Exception { - // - // when(commandLine.hasOption("keygen")).thenReturn(false); - // when(commandLine.hasOption("filename")).thenReturn(false); - // when(commandLine.hasOption("keygenconfig")).thenReturn(false); - // - // final List result = this.parser.parse(commandLine); - // - // assertThat(result).isNotNull().hasSize(0); - // - // final KeyGenerator keyGenerator = MockKeyGeneratorFactory.getMockKeyGenerator(); - // verifyZeroInteractions(keyGenerator); - // } - // - // @Test - // public void vaultOptionsNotUsedIfNoneProvided() throws Exception { - // when(commandLine.hasOption("keygenvaulttype")).thenReturn(false); - // when(commandLine.hasOption("keygenvaulturl")).thenReturn(false); - // - // this.parser.parse(commandLine); - // - // verify(commandLine, times(0)).getOptionValue("keygenvaulttype"); - // verify(commandLine, times(0)).getOptionValue("keygenvaulturl"); - // } - - @Test - public void ifAllVaultOptionsProvidedAndValidThenOkay() throws Exception { - when(commandLine.hasOption("keygenvaulttype")).thenReturn(true); - when(commandLine.hasOption("keygenvaulturl")).thenReturn(true); - when(commandLine.getOptionValue("keygenvaulturl")).thenReturn("someurl"); - when(commandLine.getOptionValue("keygenvaulttype")).thenReturn("AZURE"); - - this.parser.parse(commandLine); - - verify(commandLine, times(1)).getOptionValue("keygenvaulttype"); - verify(commandLine, times(1)).getOptionValue("keygenvaulturl"); - } - - // @Test - // public void ifAzureVaultTypeOptionProvidedButNoVaultUrlThenValidationException() { - // when(commandLine.hasOption("keygenvaulttype")).thenReturn(true); - // when(commandLine.hasOption("keygenvaulturl")).thenReturn(false); - // when(commandLine.getOptionValue("keygenvaulttype")).thenReturn("AZURE"); - // - // Throwable ex = catchThrowable(() -> this.parser.parse(commandLine)); - // - // verify(commandLine, times(1)).getOptionValue("keygenvaulttype"); - // verify(commandLine, times(1)).getOptionValue("keygenvaulturl"); - // - // assertThat(ex).isInstanceOf(ConstraintViolationException.class); - // - // Set> violations = ((ConstraintViolationException) ex).getConstraintViolations(); - // - // assertThat(violations.size()).isEqualTo(1); - // - // ConstraintViolation violation = violations.iterator().next(); - // - // assertThat(violation.getPropertyPath().toString()).isEqualTo("url"); - // assertThat(violation.getMessage()).isEqualTo("may not be null"); - // } - // - // @Test - // public void ifHashicorpVaultTypeOptionAndFilenameProvidedButNoVaultUrlThenValidationException() { - // when(commandLine.hasOption("keygenvaulttype")).thenReturn(true); - // when(commandLine.hasOption("keygenvaulturl")).thenReturn(false); - // when(commandLine.getOptionValue("keygenvaulttype")).thenReturn("HASHICORP"); - // when(commandLine.hasOption("filename")).thenReturn(true); - // when(commandLine.getOptionValue("filename")).thenReturn("secret/path"); - // - // Throwable ex = catchThrowable(() -> this.parser.parse(commandLine)); - // - // verify(commandLine, times(1)).getOptionValue("keygenvaulttype"); - // verify(commandLine, times(1)).getOptionValue("keygenvaulturl"); - // - // assertThat(ex).isInstanceOf(ConstraintViolationException.class); - // - // Set> violations = ((ConstraintViolationException) ex).getConstraintViolations(); - // - // assertThat(violations.size()).isEqualTo(1); - // - // ConstraintViolation violation = violations.iterator().next(); - // - // assertThat(violation.getPropertyPath().toString()).isEqualTo("url"); - // assertThat(violation.getMessage()).isEqualTo("may not be null"); - // } - // - // @Test - // public void ifOnlyVaultUrlOptionProvidedThenException() { - // when(commandLine.hasOption("keygenvaulttype")).thenReturn(false); - // when(commandLine.hasOption("keygenvaulturl")).thenReturn(true); - // when(commandLine.getOptionValue("keygenvaulturl")).thenReturn("someurl"); - // - // Throwable ex = catchThrowable(() -> this.parser.parse(commandLine)); - // - // assertThat(ex).isInstanceOf(CliException.class); - // assertThat(ex.getMessage()).isEqualTo("Key vault type either not provided or not recognised"); - // } - // - // @Test - // public void ifVaultOptionsProvidedButTypeUnknownThenException() { - // when(commandLine.hasOption("keygenvaulttype")).thenReturn(true); - // when(commandLine.hasOption("keygenvaulturl")).thenReturn(true); - // when(commandLine.getOptionValue("keygenvaulttype")).thenReturn("unknown"); - // - // Throwable ex = catchThrowable(() -> this.parser.parse(commandLine)); - // - // assertThat(ex).isInstanceOf(CliException.class); - // assertThat(ex.getMessage()).isEqualTo("Key vault type either not provided or not recognised"); - // } - // - // @Test - // public void noFilenameProvidedWhenUsingHashicorpVaultThrowsException() { - // when(commandLine.hasOption("keygenvaulttype")).thenReturn(true); - // when(commandLine.hasOption("keygenvaulturl")).thenReturn(true); - // when(commandLine.getOptionValue("keygenvaulttype")).thenReturn("HASHICORP"); - // when(commandLine.hasOption("filename")).thenReturn(false); - // - // Throwable ex = catchThrowable(() -> this.parser.parse(commandLine)); - // - // assertThat(ex).isInstanceOf(CliException.class); - // assertThat(ex.getMessage()) - // .isEqualTo("At least one -filename must be provided when saving generated keys in a Hashicorp - // Vault"); - // } - // - // @Test - // public void ifAllVaultOptionsAndFilenameProvidedForHashicorpThenOkay() throws Exception { - // when(commandLine.hasOption("keygenvaulttype")).thenReturn(true); - // when(commandLine.hasOption("keygenvaulturl")).thenReturn(true); - // when(commandLine.hasOption("filename")).thenReturn(true); - // when(commandLine.getOptionValue("keygenvaulturl")).thenReturn("someurl"); - // when(commandLine.getOptionValue("keygenvaulttype")).thenReturn("HASHICORP"); - // when(commandLine.getOptionValue("filename")).thenReturn("secret/path"); - // when(commandLine.getOptionValue("keygenvaultapprole")).thenReturn("approle"); - // when(commandLine.getOptionValue("keygenvaultsecretengine")).thenReturn("secretEngine"); - // - // Path tempPath = Files.createTempFile(UUID.randomUUID().toString(), ""); - // tempPath.toFile().deleteOnExit(); - // - // when(commandLine.getOptionValue("keygenvaultkeystore")).thenReturn(tempPath.toString()); - // when(commandLine.getOptionValue("keygenvaulttruststore")).thenReturn(tempPath.toString()); - // - // this.parser.parse(commandLine); - // - // verify(commandLine, times(1)).getOptionValue("keygenvaulttype"); - // verify(commandLine, times(1)).getOptionValue("keygenvaulturl"); - // verify(commandLine, times(1)).getOptionValue("keygenvaultapprole"); - // verify(commandLine, times(1)).getOptionValue("keygenvaultkeystore"); - // verify(commandLine, times(1)).getOptionValue("keygenvaulttruststore"); - // verify(commandLine, times(1)).getOptionValue("keygenvaultsecretengine"); - // } - // - // @Test - // public void ifHashicorpTlsOptionsProvidedButPathsDontExistThenValidationException() { - // when(commandLine.hasOption("keygenvaulttype")).thenReturn(true); - // when(commandLine.hasOption("keygenvaulturl")).thenReturn(true); - // when(commandLine.getOptionValue("keygenvaulttype")).thenReturn("HASHICORP"); - // when(commandLine.getOptionValue("keygenvaulturl")).thenReturn("someurl"); - // when(commandLine.hasOption("filename")).thenReturn(true); - // when(commandLine.getOptionValue("filename")).thenReturn("secret/path"); - // when(commandLine.getOptionValue("keygenvaultsecretengine")).thenReturn("secretEngine"); - // when(commandLine.getOptionValue("keygenvaultapprole")).thenReturn("approle"); - // when(commandLine.getOptionValue("keygenvaultkeystore")).thenReturn("non/existent/path"); - // when(commandLine.getOptionValue("keygenvaulttruststore")).thenReturn("non/existent/path"); - // - // Throwable ex = catchThrowable(() -> this.parser.parse(commandLine)); - // - // verify(commandLine, times(1)).getOptionValue("keygenvaulttype"); - // verify(commandLine, times(1)).getOptionValue("keygenvaulturl"); - // verify(commandLine, times(1)).getOptionValue("keygenvaultapprole"); - // verify(commandLine, times(1)).getOptionValue("keygenvaultkeystore"); - // verify(commandLine, times(1)).getOptionValue("keygenvaulttruststore"); - // verify(commandLine, times(1)).getOptionValue("keygenvaultsecretengine"); - // - // assertThat(ex).isInstanceOf(ConstraintViolationException.class); - // - // Set> violations = ((ConstraintViolationException) ex).getConstraintViolations(); - // - // assertThat(violations.size()).isEqualTo(2); - // - // Iterator> iterator = violations.iterator(); - // - // assertThat(iterator.next().getMessage()).isEqualTo("File does not exist"); - // assertThat(iterator.next().getMessage()).isEqualTo("File does not exist"); - // } - - @Test - public void lowercaseVaultTypeIsOkay() throws Exception { - when(commandLine.hasOption("keygenvaulttype")).thenReturn(true); - when(commandLine.hasOption("keygenvaulturl")).thenReturn(true); - when(commandLine.hasOption("filename")).thenReturn(true); - when(commandLine.getOptionValue("keygenvaulturl")).thenReturn("someurl"); - when(commandLine.getOptionValue("filename")).thenReturn("secret/path"); - when(commandLine.getOptionValue("keygenvaultapprole")).thenReturn("approle"); - when(commandLine.getOptionValue("keygenvaultsecretengine")).thenReturn("secretEngine"); - - when(commandLine.getOptionValue("keygenvaulttype")).thenReturn("hashicorp"); - - Path tempPath = Files.createTempFile(UUID.randomUUID().toString(), ""); - tempPath.toFile().deleteOnExit(); - - when(commandLine.getOptionValue("keygenvaultkeystore")).thenReturn(tempPath.toString()); - when(commandLine.getOptionValue("keygenvaulttruststore")).thenReturn(tempPath.toString()); - - this.parser.parse(commandLine); - - verify(commandLine, times(1)).getOptionValue("keygenvaulttype"); - verify(commandLine, times(1)).getOptionValue("keygenvaulturl"); - verify(commandLine, times(1)).getOptionValue("keygenvaultapprole"); - verify(commandLine, times(1)).getOptionValue("keygenvaultkeystore"); - verify(commandLine, times(1)).getOptionValue("keygenvaulttruststore"); - verify(commandLine, times(1)).getOptionValue("keygenvaultsecretengine"); - } - - @Test - public void leadingWhitespaceVaultTypeIsOkay() throws Exception { - when(commandLine.hasOption("keygenvaulttype")).thenReturn(true); - when(commandLine.hasOption("keygenvaulturl")).thenReturn(true); - when(commandLine.hasOption("filename")).thenReturn(true); - when(commandLine.getOptionValue("keygenvaulturl")).thenReturn("someurl"); - when(commandLine.getOptionValue("filename")).thenReturn("secret/path"); - when(commandLine.getOptionValue("keygenvaultapprole")).thenReturn("approle"); - when(commandLine.getOptionValue("keygenvaultsecretengine")).thenReturn("secretEngine"); - - when(commandLine.getOptionValue("keygenvaulttype")).thenReturn(" HASHICORP"); - - Path tempPath = Files.createTempFile(UUID.randomUUID().toString(), ""); - tempPath.toFile().deleteOnExit(); - - when(commandLine.getOptionValue("keygenvaultkeystore")).thenReturn(tempPath.toString()); - when(commandLine.getOptionValue("keygenvaulttruststore")).thenReturn(tempPath.toString()); - - this.parser.parse(commandLine); - - verify(commandLine, times(1)).getOptionValue("keygenvaulttype"); - verify(commandLine, times(1)).getOptionValue("keygenvaulturl"); - verify(commandLine, times(1)).getOptionValue("keygenvaultapprole"); - verify(commandLine, times(1)).getOptionValue("keygenvaultkeystore"); - verify(commandLine, times(1)).getOptionValue("keygenvaulttruststore"); - verify(commandLine, times(1)).getOptionValue("keygenvaultsecretengine"); - } - - @Test - public void trailingWhitespaceVaultTypeIsOkay() throws Exception { - when(commandLine.hasOption("keygenvaulttype")).thenReturn(true); - when(commandLine.hasOption("keygenvaulturl")).thenReturn(true); - when(commandLine.hasOption("filename")).thenReturn(true); - when(commandLine.getOptionValue("keygenvaulturl")).thenReturn("someurl"); - when(commandLine.getOptionValue("filename")).thenReturn("secret/path"); - when(commandLine.getOptionValue("keygenvaultapprole")).thenReturn("approle"); - when(commandLine.getOptionValue("keygenvaultsecretengine")).thenReturn("secretEngine"); - - when(commandLine.getOptionValue("keygenvaulttype")).thenReturn("HASHICORP "); - - Path tempPath = Files.createTempFile(UUID.randomUUID().toString(), ""); - tempPath.toFile().deleteOnExit(); - - when(commandLine.getOptionValue("keygenvaultkeystore")).thenReturn(tempPath.toString()); - when(commandLine.getOptionValue("keygenvaulttruststore")).thenReturn(tempPath.toString()); - - this.parser.parse(commandLine); - - verify(commandLine, times(1)).getOptionValue("keygenvaulttype"); - verify(commandLine, times(1)).getOptionValue("keygenvaulturl"); - verify(commandLine, times(1)).getOptionValue("keygenvaultapprole"); - verify(commandLine, times(1)).getOptionValue("keygenvaultkeystore"); - verify(commandLine, times(1)).getOptionValue("keygenvaulttruststore"); - verify(commandLine, times(1)).getOptionValue("keygenvaultsecretengine"); - } -} diff --git a/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/parsers/KeyUpdateParserTest.java b/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/parsers/KeyUpdateParserTest.java deleted file mode 100644 index 37022e7ab8..0000000000 --- a/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/parsers/KeyUpdateParserTest.java +++ /dev/null @@ -1,324 +0,0 @@ -package com.quorum.tessera.config.cli.parsers; - -import com.quorum.tessera.config.ArgonOptions; -import com.quorum.tessera.config.keys.KeyEncryptor; -import com.quorum.tessera.passwords.PasswordReader; -import org.apache.commons.cli.*; -import org.junit.Before; -import org.junit.Test; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -public class KeyUpdateParserTest { - - private PasswordReader passwordReader; - - private KeyUpdateParser parser; - - private Options options; - - private KeyEncryptor keyEncryptor; - - @Before - public void init() { - this.passwordReader = mock(PasswordReader.class); - when(passwordReader.requestUserPassword()).thenReturn("newPassword"); - - this.keyEncryptor = mock(KeyEncryptor.class); - this.parser = new KeyUpdateParser(keyEncryptor, passwordReader); - - this.options = new Options(); - - this.options.addOption(Option.builder().longOpt("keys.keyData.config.data.aopts.algorithm").hasArg().build()); - this.options.addOption(Option.builder().longOpt("keys.keyData.config.data.aopts.iterations").hasArg().build()); - this.options.addOption(Option.builder().longOpt("keys.keyData.config.data.aopts.memory").hasArg().build()); - this.options.addOption(Option.builder().longOpt("keys.keyData.config.data.aopts.parallelism").hasArg().build()); - - this.options.addOption(Option.builder().longOpt("keys.passwords").hasArg().build()); - this.options.addOption(Option.builder().longOpt("keys.passwordFile").hasArg().build()); - - this.options.addOption(Option.builder().longOpt("keys.keyData.privateKeyPath").hasArg().build()); - } - - // Argon Option tests - @Test - public void noArgonOptionsGivenHasDefaults() throws ParseException { - final CommandLine commandLine = new DefaultParser().parse(options, new String[] {}); - - final ArgonOptions argonOptions = KeyUpdateParser.argonOptions(commandLine); - - assertThat(argonOptions.getAlgorithm()).isEqualTo("i"); - assertThat(argonOptions.getParallelism()).isEqualTo(4); - assertThat(argonOptions.getMemory()).isEqualTo(1048576); - assertThat(argonOptions.getIterations()).isEqualTo(10); - } - - // @Test - // public void argonOptionsGivenHasOverrides() throws ParseException { - // final String[] args = - // new String[] { - // "--keys.keyData.config.data.aopts.algorithm", "d", - // "--keys.keyData.config.data.aopts.memory", "100", - // "--keys.keyData.config.data.aopts.iterations", "100", - // "--keys.keyData.config.data.aopts.parallelism", "100" - // }; - // final CommandLine commandLine = new DefaultParser().parse(options, args); - // - // final ArgonOptions argonOptions = KeyUpdateParser.argonOptions(commandLine); - // - // assertThat(argonOptions.getAlgorithm()).isEqualTo("d"); - // assertThat(argonOptions.getParallelism()).isEqualTo(100); - // assertThat(argonOptions.getMemory()).isEqualTo(100); - // assertThat(argonOptions.getIterations()).isEqualTo(100); - // } - // - // // Password reading tests - // @Test - // public void inlinePasswordParsed() throws ParseException, IOException { - // final String[] args = new String[] {"--keys.passwords", "pass"}; - // final CommandLine commandLine = new DefaultParser().parse(options, args); - // - // final List passwords = KeyUpdateParser.passwords(commandLine); - // - // assertThat(passwords).isNotNull().hasSize(1).containsExactly("pass"); - // } - // - // @Test - // public void passwordFileParsedAndRead() throws ParseException, IOException { - // final Path passwordFile = Files.createTempFile("passwords", ".txt"); - // Files.write(passwordFile, "passwordInsideFile\nsecondPassword".getBytes()); - // - // final String[] args = new String[] {"--keys.passwordFile", passwordFile.toAbsolutePath().toString()}; - // final CommandLine commandLine = new DefaultParser().parse(options, args); - // - // final List passwords = KeyUpdateParser.passwords(commandLine); - // - // assertThat(passwords).isNotNull().hasSize(2).containsExactly("passwordInsideFile", "secondPassword"); - // } - // - // @Test - // public void passwordFileThrowsErrorIfCantBeRead() throws ParseException { - // final String[] args = new String[] {"--keys.passwordFile", "/tmp/passwords.txt"}; - // final CommandLine commandLine = new DefaultParser().parse(options, args); - // - // final Throwable throwable = catchThrowable(() -> KeyUpdateParser.passwords(commandLine)); - // - // assertThat(throwable).isNotNull().isInstanceOf(IOException.class); - // } - // - // @Test - // public void emptyListGivenForNoPasswords() throws ParseException, IOException { - // final String[] args = new String[] {}; - // final CommandLine commandLine = new DefaultParser().parse(options, args); - // - // final List passwords = KeyUpdateParser.passwords(commandLine); - // - // assertThat(passwords).isNotNull().isEmpty(); - // } -// -// // key file tests -// @Test -// public void noPrivateKeyGivenThrowsError() throws ParseException { -// final String[] args = new String[] {}; -// final CommandLine commandLine = new DefaultParser().parse(options, args); -// -// final Throwable throwable = catchThrowable(() -> KeyUpdateParser.privateKeyPath(commandLine)); -// -// assertThat(throwable) -// .isInstanceOf(IllegalArgumentException.class) -// .hasMessage("Private key path cannot be null when updating key password"); -// } -// -// @Test -// public void cantReadPrivateKeyThrowsError() throws ParseException { -// final String[] args = new String[] {"--keys.keyData.privateKeyPath", "/tmp/nonexisting.txt"}; -// final CommandLine commandLine = new DefaultParser().parse(options, args); -// -// final Throwable throwable = catchThrowable(() -> KeyUpdateParser.privateKeyPath(commandLine)); -// -// assertThat(throwable).isInstanceOf(IllegalArgumentException.class); -// } -// -// @Test -// public void privateKeyExistsReturnsPath() throws ParseException, IOException { -// final Path key = Files.createTempFile("key", ".key"); -// -// final String[] args = new String[] {"--keys.keyData.privateKeyPath", key.toString()}; -// final CommandLine commandLine = new DefaultParser().parse(options, args); -// -// final Path path = KeyUpdateParser.privateKeyPath(commandLine); -// -// assertThat(path).isEqualTo(key); -// } -// -// // key fetching tests -// @Test -// public void unlockedKeyReturnedProperly() { -// final KeyDataConfig kdc = -// new KeyDataConfig( -// new PrivateKeyData("/+UuD63zItL1EbjxkKUljMgG8Z1w0AJ8pNOR4iq2yQc=", null, null, null, null), -// PrivateKeyType.UNLOCKED); -// -// final PrivateKey key = this.parser.getExistingKey(kdc, emptyList()); -// -// String encodedKeyValue = Base64.getEncoder().encodeToString(key.getKeyBytes()); -// -// assertThat(encodedKeyValue).isEqualTo("/+UuD63zItL1EbjxkKUljMgG8Z1w0AJ8pNOR4iq2yQc="); -// } -// -// @Test -// public void lockedKeyFailsWithNoPasswordsMatching() { -// -// final KeyDataConfig kdc = -// new KeyDataConfig( -// new PrivateKeyData( -// null, -// "dwixVoY+pOI2FMuu4k0jLqN/naQiTzWe", -// "JoPVq9G6NdOb+Ugv+HnUeA==", -// "6Jd/MXn29fk6jcrFYGPb75l7sDJae06I3Y1Op+bZSZqlYXsMpa/8lLE29H0sX3yw", -// new ArgonOptions("id", 1, 1024, 1)), -// PrivateKeyType.LOCKED); -// -// final Throwable throwable = catchThrowable(() -> this.parser.getExistingKey(kdc, singletonList("wrong"))); -// -// assertThat(throwable) -// .isInstanceOf(IllegalArgumentException.class) -// .hasMessage("Locked key but no valid password given"); -// } -// -// @Test -// public void lockedKeySucceedsWithPasswordsMatching() { -// PrivateKeyData privateKeyData = -// new PrivateKeyData( -// null, -// "dwixVoY+pOI2FMuu4k0jLqN/naQiTzWe", -// "JoPVq9G6NdOb+Ugv+HnUeA==", -// "6Jd/MXn29fk6jcrFYGPb75l7sDJae06I3Y1Op+bZSZqlYXsMpa/8lLE29H0sX3yw", -// new ArgonOptions("id", 1, 1024, 1)); -// -// final KeyDataConfig kdc = -// new KeyDataConfig( -// new PrivateKeyData( -// null, -// "dwixVoY+pOI2FMuu4k0jLqN/naQiTzWe", -// "JoPVq9G6NdOb+Ugv+HnUeA==", -// "6Jd/MXn29fk6jcrFYGPb75l7sDJae06I3Y1Op+bZSZqlYXsMpa/8lLE29H0sX3yw", -// new ArgonOptions("id", 1, 1024, 1)), -// PrivateKeyType.LOCKED); -// -// PrivateKey privateKey = mock(PrivateKey.class); -// when(privateKey.getKeyBytes()).thenReturn("SUCCESS".getBytes()); -// when(keyEncryptor.decryptPrivateKey(privateKeyData, "testpassword")).thenReturn(privateKey); -// -// final PrivateKey result = this.parser.getExistingKey(kdc, singletonList("testpassword")); -// -// assertThat(result.getKeyBytes()).isEqualTo("SUCCESS".getBytes()); -// } -// -// @Test -// public void lockedKeySucceedsWithAtleastOnePasswordsMatching() { -// -// PrivateKeyData privateKeyData = mock(PrivateKeyData.class); -// -// final KeyDataConfig kdc = new KeyDataConfig(privateKeyData, PrivateKeyType.LOCKED); -// -// PrivateKey privateKey = mock(PrivateKey.class); -// when(privateKey.getKeyBytes()).thenReturn("SUCCESS".getBytes()); -// -// when(keyEncryptor.decryptPrivateKey(privateKeyData, "wrong")).thenReturn(null); -// when(keyEncryptor.decryptPrivateKey(privateKeyData, "testpassword")).thenReturn(privateKey); -// -// final PrivateKey key = this.parser.getExistingKey(kdc, Arrays.asList("wrong", "testpassword")); -// -// assertThat(key).isNotNull(); -// -// assertThat(key.getKeyBytes()).isEqualTo("SUCCESS".getBytes()); -// } -// -// @Test -// public void loadingMalformedKeyfileThrowsError() throws IOException, ParseException { -// final Path key = Files.createTempFile("key", ".key"); -// Files.write(key, "BAD JSON DATA".getBytes()); -// -// final String[] args = new String[] {"--keys.keyData.privateKeyPath", key.toString()}; -// final CommandLine commandLine = new DefaultParser().parse(options, args); -// -// final Throwable throwable = catchThrowable(() -> this.parser.parse(commandLine)); -// -// assertThat(throwable).isInstanceOf(ConfigException.class).hasCauseExactlyInstanceOf(UnmarshalException.class); -// } -// -// @Test -// public void keyGetsUpdated() throws IOException, ParseException { -// final KeyDataConfig startingKey = -// JaxbUtil.unmarshal(getClass().getResourceAsStream("/lockedprivatekey.json"), KeyDataConfig.class); -// -// final Path key = Files.createTempFile("key", ".key"); -// Files.write(key, JaxbUtil.marshalToString(startingKey).getBytes()); -// -// final String[] args = -// new String[] { -// "--keys.keyData.privateKeyPath", key.toString(), -// "--keys.passwords", "testpassword", -// "--keys.keyData.config.data.aopts.algorithm", "id", -// "--keys.keyData.config.data.aopts.memory", "2048", -// "--keys.keyData.config.data.aopts.iterations", "1", -// "--keys.keyData.config.data.aopts.parallelism", "1" -// }; -// final CommandLine commandLine = new DefaultParser().parse(options, args); -// -// PrivateKey privatekey = mock(PrivateKey.class); -// when(keyEncryptor.decryptPrivateKey(any(PrivateKeyData.class), anyString())).thenReturn(privatekey); -// -// PrivateKeyData privateKeyData = mock(PrivateKeyData.class); -// -// when(keyEncryptor.encryptPrivateKey(any(PrivateKey.class), anyString(), any(ArgonOptions.class))) -// .thenReturn(privateKeyData); -// -// this.parser.parse(commandLine); -// -// final KeyDataConfig endingKey = JaxbUtil.unmarshal(Files.newInputStream(key), KeyDataConfig.class); -// -// assertThat(endingKey.getSbox()).isNotEqualTo(startingKey.getSbox()); -// assertThat(endingKey.getSnonce()).isNotEqualTo(startingKey.getSnonce()); -// assertThat(endingKey.getAsalt()).isNotEqualTo(startingKey.getAsalt()); -// } -// -// @Test -// public void keyGetsUpdatedToNoPassword() throws IOException, ParseException { -// final KeyDataConfig startingKey = -// JaxbUtil.unmarshal(getClass().getResourceAsStream("/lockedprivatekey.json"), KeyDataConfig.class); -// -// when(passwordReader.requestUserPassword()).thenReturn(""); -// -// final Path key = Files.createTempFile("key", ".key"); -// Files.write(key, JaxbUtil.marshalToString(startingKey).getBytes()); -// -// final String[] args = -// new String[] { -// "--keys.keyData.privateKeyPath", key.toString(), -// "--keys.passwords", "testpassword", -// "--keys.keyData.config.data.aopts.algorithm", "id", -// "--keys.keyData.config.data.aopts.memory", "2048", -// "--keys.keyData.config.data.aopts.iterations", "1", -// "--keys.keyData.config.data.aopts.parallelism", "1" -// }; -// final CommandLine commandLine = new DefaultParser().parse(options, args); -// -// byte[] privateKeyData = "SOME PRIVATE DATA".getBytes(); -// PrivateKey privateKey = PrivateKey.from(privateKeyData); -// when(keyEncryptor.decryptPrivateKey(any(PrivateKeyData.class), anyString())).thenReturn(privateKey); -// -// this.parser.parse(commandLine); -// -// final KeyDataConfig endingKey = JaxbUtil.unmarshal(Files.newInputStream(key), KeyDataConfig.class); -// -// assertThat(endingKey.getSbox()).isNotEqualTo(startingKey.getSbox()); -// assertThat(endingKey.getSnonce()).isNotEqualTo(startingKey.getSnonce()); -// assertThat(endingKey.getAsalt()).isNotEqualTo(startingKey.getAsalt()); -// assertThat(endingKey.getPrivateKeyData().getValue()) -// .isEqualTo(Base64.getEncoder().encodeToString(privateKeyData)); -// } -} diff --git a/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/PicoCliDelegate.java b/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/PicoCliDelegate.java index 6875a02af0..561a4d5ee7 100644 --- a/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/PicoCliDelegate.java +++ b/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/PicoCliDelegate.java @@ -1,7 +1,7 @@ package com.quorum.tessera.picocli; import com.quorum.tessera.ServiceLoaderUtil; -import com.quorum.tessera.admin.cli.AdminCliAdapter; +import com.quorum.tessera.cli.admin.AdminCliAdapter; import com.quorum.tessera.cli.CLIExceptionCapturer; import com.quorum.tessera.cli.CliException; import com.quorum.tessera.cli.CliResult; @@ -33,7 +33,8 @@ import static java.nio.file.StandardOpenOption.CREATE; import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING; -// TODO(cjh) clean up cli-api and config-cli modules. the parser and adapter behaviour should be encapsulated in these commands so are no longer needed +// TODO(cjh) clean up cli-api and config-cli modules. the parser and adapter behaviour should be encapsulated in these +// commands so are no longer needed public class PicoCliDelegate { private static final Logger LOGGER = LoggerFactory.getLogger(PicoCliDelegate.class); diff --git a/cli/pico-cli/src/test/java/com/quorum/tessera/picocli/KeyUpdateCommandTest.java b/cli/pico-cli/src/test/java/com/quorum/tessera/picocli/KeyUpdateCommandTest.java index 5bc8aa82c7..01d54b88c7 100644 --- a/cli/pico-cli/src/test/java/com/quorum/tessera/picocli/KeyUpdateCommandTest.java +++ b/cli/pico-cli/src/test/java/com/quorum/tessera/picocli/KeyUpdateCommandTest.java @@ -7,7 +7,6 @@ import com.quorum.tessera.config.util.JaxbUtil; import com.quorum.tessera.encryption.PrivateKey; import com.quorum.tessera.passwords.PasswordReader; -import org.apache.commons.cli.ParseException; import org.junit.After; import org.junit.Before; import org.junit.Ignore; @@ -60,7 +59,7 @@ public void onTeardown() { // set when creating a command line object and calling parseArgs or execute @Ignore @Test - public void noArgonOptionsGivenHasDefaults() throws ParseException { + public void noArgonOptionsGivenHasDefaults() throws Exception { // final CommandLine commandLine = new DefaultParser().parse(options, new String[] {}); // // final ArgonOptions argonOptions = KeyUpdateParser.argonOptions(commandLine); diff --git a/cli/pom.xml b/cli/pom.xml index 890a1c3ba8..e575d62ca0 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -20,11 +20,6 @@ - - commons-cli - commons-cli - - com.jpmorgan.quorum config diff --git a/pom.xml b/pom.xml index eab5421905..ba3d1f6fdd 100644 --- a/pom.xml +++ b/pom.xml @@ -321,10 +321,6 @@ com/quorum/tessera/enclave/rest/Main* com/quorum/tessera/enclave/websockets/Main* - com/quorum/tessera/config/cli/DefaultCliAdapter* - com/quorum/tessera/config/cli/parsers/KeyGenerationParser* - com/quorum/tessera/config/cli/parsers/EncryptorConfigParser* - com/quorum/tessera/config/cli/parsers/KeyUpdateParser* @@ -991,12 +987,6 @@ 1.5.4 - - commons-cli - commons-cli - 1.4 - - org.apache.commons commons-lang3 From 4c7ff1ad92f72441bf71dbce9d7d7e315dba4c8d Mon Sep 17 00:00:00 2001 From: Mark Lowe Date: Thu, 19 Dec 2019 16:08:55 +0000 Subject: [PATCH 39/41] Colapse admin-cli and pico-cli modules into config-cli --- cli/admin-cli/build.gradle | 12 --- cli/admin-cli/pom.xml | 34 -------- .../com.quorum.tessera.cli.CliAdapter | 1 - .../com.quorum.tessera.io.SystemAdapter | 1 - .../src/test/resources/sample-config.json | 77 ------------------ cli/cli-api/build.gradle | 3 +- cli/config-cli/build.gradle | 5 +- cli/config-cli/pom.xml | 6 ++ .../config/cli}/ArgonOptionsConverter.java | 2 +- .../tessera/config/cli}/EncryptorOptions.java | 2 +- .../tessera/config/cli}/KeyGenCommand.java | 8 +- .../config/cli}/KeyGenCommandFactory.java | 7 +- .../tessera/config/cli}/KeyUpdateCommand.java | 11 +-- .../config/cli}/KeyUpdateCommandFactory.java | 6 +- .../cli}/NoTesseraCmdArgsException.java | 6 +- .../NoTesseraConfigfileOptionException.java | 6 +- .../tessera/config/cli}/PicoCliDelegate.java | 5 +- .../tessera/config/cli}/TesseraCommand.java | 6 +- .../config}/cli/admin/AdminCliAdapter.java | 4 +- .../cli/admin/subcommands/AddPeerCommand.java | 2 +- .../cli}/ArgonOptionsConverterTest.java | 2 +- .../config/cli}/EncryptorOptionsTest.java | 5 +- .../config/cli}/KeyGenCommandTest.java | 2 +- .../config/cli}/KeyUpdateCommandTest.java | 5 +- .../config/cli}/PicoCliDelegateTest.java | 4 +- .../cli/admin/AdminCliAdapterTest.java | 2 +- .../admin/subcommands/AddPeerCommandTest.java | 3 +- cli/pico-cli/build.gradle | 14 ---- cli/pico-cli/pom.xml | 29 ------- .../picocli/keys/MockKeyGeneratorFactory.java | 30 ------- ...tessera.key.generation.KeyGeneratorFactory | 1 - .../src/test/resources/keygen-sample.json | 62 -------------- .../src/test/resources/lockedprivatekey.json | 14 ---- .../src/test/resources/missing-config.json | 38 --------- .../resources/sample-config-invalidpath.json | 68 ---------------- .../src/test/resources/sample-config.json | 80 ------------------- cli/pom.xml | 2 - pom.xml | 5 -- settings.gradle | 4 - tessera-dist/tessera-app/build.gradle | 1 - tessera-dist/tessera-app/pom.xml | 6 -- tessera-dist/tessera-launcher/build.gradle | 2 +- tessera-dist/tessera-launcher/pom.xml | 8 +- .../com/quorum/tessera/launcher/Main.java | 2 +- tessera-dist/tessera-simple/pom.xml | 6 -- 45 files changed, 58 insertions(+), 541 deletions(-) delete mode 100644 cli/admin-cli/build.gradle delete mode 100644 cli/admin-cli/pom.xml delete mode 100644 cli/admin-cli/src/main/resources/META-INF/services/com.quorum.tessera.cli.CliAdapter delete mode 100644 cli/admin-cli/src/test/resources/META-INF/services/com.quorum.tessera.io.SystemAdapter delete mode 100644 cli/admin-cli/src/test/resources/sample-config.json rename cli/{pico-cli/src/main/java/com/quorum/tessera/picocli => config-cli/src/main/java/com/quorum/tessera/config/cli}/ArgonOptionsConverter.java (95%) rename cli/{pico-cli/src/main/java/com/quorum/tessera/picocli => config-cli/src/main/java/com/quorum/tessera/config/cli}/EncryptorOptions.java (97%) rename cli/{pico-cli/src/main/java/com/quorum/tessera/picocli => config-cli/src/main/java/com/quorum/tessera/config/cli}/KeyGenCommand.java (96%) rename cli/{pico-cli/src/main/java/com/quorum/tessera/picocli => config-cli/src/main/java/com/quorum/tessera/config/cli}/KeyGenCommandFactory.java (63%) rename cli/{pico-cli/src/main/java/com/quorum/tessera/picocli => config-cli/src/main/java/com/quorum/tessera/config/cli}/KeyUpdateCommand.java (94%) rename cli/{pico-cli/src/main/java/com/quorum/tessera/picocli => config-cli/src/main/java/com/quorum/tessera/config/cli}/KeyUpdateCommandFactory.java (80%) rename cli/{pico-cli/src/main/java/com/quorum/tessera/picocli => config-cli/src/main/java/com/quorum/tessera/config/cli}/NoTesseraCmdArgsException.java (60%) rename cli/{pico-cli/src/main/java/com/quorum/tessera/picocli => config-cli/src/main/java/com/quorum/tessera/config/cli}/NoTesseraConfigfileOptionException.java (56%) rename cli/{pico-cli/src/main/java/com/quorum/tessera/picocli => config-cli/src/main/java/com/quorum/tessera/config/cli}/PicoCliDelegate.java (98%) rename cli/{pico-cli/src/main/java/com/quorum/tessera/picocli => config-cli/src/main/java/com/quorum/tessera/config/cli}/TesseraCommand.java (88%) rename cli/{admin-cli/src/main/java/com/quorum/tessera => config-cli/src/main/java/com/quorum/tessera/config}/cli/admin/AdminCliAdapter.java (92%) rename cli/{admin-cli/src/main/java/com/quorum/tessera => config-cli/src/main/java/com/quorum/tessera/config}/cli/admin/subcommands/AddPeerCommand.java (98%) rename cli/{pico-cli/src/test/java/com/quorum/tessera/picocli => config-cli/src/test/java/com/quorum/tessera/config/cli}/ArgonOptionsConverterTest.java (97%) rename cli/{pico-cli/src/test/java/com/quorum/tessera/picocli => config-cli/src/test/java/com/quorum/tessera/config/cli}/EncryptorOptionsTest.java (92%) rename cli/{pico-cli/src/test/java/com/quorum/tessera/picocli => config-cli/src/test/java/com/quorum/tessera/config/cli}/KeyGenCommandTest.java (99%) rename cli/{pico-cli/src/test/java/com/quorum/tessera/picocli => config-cli/src/test/java/com/quorum/tessera/config/cli}/KeyUpdateCommandTest.java (98%) rename cli/{pico-cli/src/test/java/com/quorum/tessera/picocli => config-cli/src/test/java/com/quorum/tessera/config/cli}/PicoCliDelegateTest.java (99%) rename cli/{admin-cli/src/test/java/com/quorum/tessera => config-cli/src/test/java/com/quorum/tessera/config}/cli/admin/AdminCliAdapterTest.java (95%) rename cli/{admin-cli/src/test/java/com/quorum/tessera => config-cli/src/test/java/com/quorum/tessera/config}/cli/admin/subcommands/AddPeerCommandTest.java (97%) delete mode 100644 cli/pico-cli/build.gradle delete mode 100644 cli/pico-cli/pom.xml delete mode 100644 cli/pico-cli/src/test/java/com/quorum/tessera/picocli/keys/MockKeyGeneratorFactory.java delete mode 100644 cli/pico-cli/src/test/resources/META-INF/services/com.quorum.tessera.key.generation.KeyGeneratorFactory delete mode 100644 cli/pico-cli/src/test/resources/keygen-sample.json delete mode 100644 cli/pico-cli/src/test/resources/lockedprivatekey.json delete mode 100644 cli/pico-cli/src/test/resources/missing-config.json delete mode 100644 cli/pico-cli/src/test/resources/sample-config-invalidpath.json delete mode 100644 cli/pico-cli/src/test/resources/sample-config.json diff --git a/cli/admin-cli/build.gradle b/cli/admin-cli/build.gradle deleted file mode 100644 index 1d1c2a5ac5..0000000000 --- a/cli/admin-cli/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ - -dependencies { - - compile 'info.picocli:picocli:4.0.4' - compile project(':config') - compile project(':shared') - compile project(':cli:cli-api') - compile project(':tessera-jaxrs:jaxrs-client') - compile 'javax.validation:validation-api' - compile 'javax.ws.rs:javax.ws.rs-api' - testImplementation project(':tests:test-util') -} diff --git a/cli/admin-cli/pom.xml b/cli/admin-cli/pom.xml deleted file mode 100644 index 9a7b496344..0000000000 --- a/cli/admin-cli/pom.xml +++ /dev/null @@ -1,34 +0,0 @@ - - - - cli - com.jpmorgan.quorum - 0.11-SNAPSHOT - - 4.0.0 - - admin-cli - - - - com.jpmorgan.quorum - cli-api - - - - com.jpmorgan.quorum - jaxrs-client - - - - javax.validation - validation-api - - - - javax.ws.rs - javax.ws.rs-api - - - - diff --git a/cli/admin-cli/src/main/resources/META-INF/services/com.quorum.tessera.cli.CliAdapter b/cli/admin-cli/src/main/resources/META-INF/services/com.quorum.tessera.cli.CliAdapter deleted file mode 100644 index 4dcf28f0f0..0000000000 --- a/cli/admin-cli/src/main/resources/META-INF/services/com.quorum.tessera.cli.CliAdapter +++ /dev/null @@ -1 +0,0 @@ -com.quorum.tessera.cli.admin.AdminCliAdapter diff --git a/cli/admin-cli/src/test/resources/META-INF/services/com.quorum.tessera.io.SystemAdapter b/cli/admin-cli/src/test/resources/META-INF/services/com.quorum.tessera.io.SystemAdapter deleted file mode 100644 index 29b992460a..0000000000 --- a/cli/admin-cli/src/test/resources/META-INF/services/com.quorum.tessera.io.SystemAdapter +++ /dev/null @@ -1 +0,0 @@ -com.quorum.tessera.io.NoopSystemAdapter \ No newline at end of file diff --git a/cli/admin-cli/src/test/resources/sample-config.json b/cli/admin-cli/src/test/resources/sample-config.json deleted file mode 100644 index 4407f1b349..0000000000 --- a/cli/admin-cli/src/test/resources/sample-config.json +++ /dev/null @@ -1,77 +0,0 @@ -{ - "useWhiteList": false, - "jdbc": { - "username": "scott", - "password": "tiger", - "url": "foo:bar" - }, - "serverConfigs": [ - { - "app": "ThirdParty", - "enabled": true, - "serverAddress": "http://localhost:8090", - - "communicationType": "REST" - }, - { - "app": "ADMIN", - "enabled": true, - "serverAddress": "http://localhost:18090", - "communicationType": "REST" - }, - { - "app": "Q2T", - "enabled": true, - "serverAddress": "unix:/tmp/test.ipc", - "communicationType": "REST" - }, - { - "app": "P2P", - "enabled": true, - "serverAddress": "http://localhost:8091", - "sslConfig": { - "tls": "OFF", - "generateKeyStoreIfNotExisted": "false", - "serverKeyStore": "./ssl/server1-keystore", - "serverKeyStorePassword": "quorum", - "serverTrustStore": "./ssl/server-truststore", - "serverTrustStorePassword": "quorum", - "serverTrustMode": "CA", - "clientKeyStore": "./ssl/client1-keystore", - "clientKeyStorePassword": "quorum", - "clientTrustStore": "./ssl/client-truststore", - "clientTrustStorePassword": "quorum", - "clientTrustMode": "CA", - "knownClientsFile": "./ssl/knownClients1", - "knownServersFile": "./ssl/knownServers1" - }, - "communicationType": "REST" - } - ], - "peer": [ - { - "url": "http://bogus1.com" - }, - { - "url": "http://bogus2.com" - } - ], - "keys": { - "passwords": [], - "keyData": [ - { - "config": { - "data": { - "bytes": "Wl+xSyXVuuqzpvznOS7dOobhcn4C5auxkFRi7yLtgtA=" - }, - "type": "unlocked" - }, - "publicKey": "/+UuD63zItL1EbjxkKUljMgG8Z1w0AJ8pNOR4iq2yQc=" - } - ] - }, - "alwaysSendTo": [ - "/+UuD63zItL1EbjxkKUljMgG8Z1w0AJ8pNOR4iq2yQc=" - ], - "unixSocketFile": "${unixSocketPath}" -} diff --git a/cli/cli-api/build.gradle b/cli/cli-api/build.gradle index b21d6be32e..1ee25bded0 100644 --- a/cli/cli-api/build.gradle +++ b/cli/cli-api/build.gradle @@ -1,8 +1,7 @@ dependencies { - compile 'info.picocli:picocli:4.0.4' + compile 'info.picocli:picocli' compile 'org.apache.commons:commons-lang3:3.7' - //compile 'commons-cli:commons-cli:1.4' compile project(':config') compile project(':shared') compile project(':encryption:encryption-api') diff --git a/cli/config-cli/build.gradle b/cli/config-cli/build.gradle index a65f01e87d..a2d728af80 100644 --- a/cli/config-cli/build.gradle +++ b/cli/config-cli/build.gradle @@ -1,12 +1,13 @@ dependencies { - compile 'info.picocli:picocli' - //compile 'commons-cli:commons-cli:1.4' + compile 'info.picocli:picocli:4.0' compile project(':encryption:encryption-api') compile project(':config') compile project(':shared') compile project(':cli:cli-api') compile project(':key-generation') + compile project(':tessera-jaxrs:jaxrs-client') + compile 'javax.ws.rs:javax.ws.rs-api' runtimeOnly project(':encryption:encryption-jnacl') diff --git a/cli/config-cli/pom.xml b/cli/config-cli/pom.xml index a3a1a0892a..35c9e257d4 100644 --- a/cli/config-cli/pom.xml +++ b/cli/config-cli/pom.xml @@ -14,10 +14,16 @@ cli-api + + com.jpmorgan.quorum + jaxrs-client + + com.jpmorgan.quorum key-generation + diff --git a/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/ArgonOptionsConverter.java b/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/ArgonOptionsConverter.java similarity index 95% rename from cli/pico-cli/src/main/java/com/quorum/tessera/picocli/ArgonOptionsConverter.java rename to cli/config-cli/src/main/java/com/quorum/tessera/config/cli/ArgonOptionsConverter.java index 952d0c4c70..875d4cbb27 100644 --- a/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/ArgonOptionsConverter.java +++ b/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/ArgonOptionsConverter.java @@ -1,4 +1,4 @@ -package com.quorum.tessera.picocli; +package com.quorum.tessera.config.cli; import com.quorum.tessera.config.ArgonOptions; import com.quorum.tessera.config.util.JaxbUtil; diff --git a/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/EncryptorOptions.java b/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/EncryptorOptions.java similarity index 97% rename from cli/pico-cli/src/main/java/com/quorum/tessera/picocli/EncryptorOptions.java rename to cli/config-cli/src/main/java/com/quorum/tessera/config/cli/EncryptorOptions.java index 3eaf29dced..a1af182c1d 100644 --- a/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/EncryptorOptions.java +++ b/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/EncryptorOptions.java @@ -1,4 +1,4 @@ -package com.quorum.tessera.picocli; +package com.quorum.tessera.config.cli; import com.quorum.tessera.config.EncryptorConfig; import com.quorum.tessera.config.EncryptorType; diff --git a/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/KeyGenCommand.java b/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/KeyGenCommand.java similarity index 96% rename from cli/pico-cli/src/main/java/com/quorum/tessera/picocli/KeyGenCommand.java rename to cli/config-cli/src/main/java/com/quorum/tessera/config/cli/KeyGenCommand.java index 5419a65532..d74062c888 100644 --- a/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/KeyGenCommand.java +++ b/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/KeyGenCommand.java @@ -1,4 +1,4 @@ -package com.quorum.tessera.picocli; +package com.quorum.tessera.config.cli; import com.quorum.tessera.cli.CliException; import com.quorum.tessera.cli.CliResult; @@ -47,7 +47,8 @@ public class KeyGenCommand implements Callable { @CommandLine.Option( names = {"--argonconfig", "-keygenconfig"}, - description = "File containing Argon2 encryption config used to secure the new private key when storing to the filesystem") + description = + "File containing Argon2 encryption config used to secure the new private key when storing to the filesystem") public ArgonOptions argonOptions; @CommandLine.Option( @@ -87,7 +88,8 @@ public class KeyGenCommand implements Callable { public Config config; // TODO(cjh) implement config output and password file update ? - // we've removed the ability to start the node straight away after generating keys. Not sure if updating configfile + // we've removed the ability to start the node straight away after generating keys. Not sure if updating + // configfile // and password file is something we want to still support or put onus on users to go and update as required @CommandLine.Option( names = {"--configout", "-output"}, diff --git a/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/KeyGenCommandFactory.java b/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/KeyGenCommandFactory.java similarity index 63% rename from cli/pico-cli/src/main/java/com/quorum/tessera/picocli/KeyGenCommandFactory.java rename to cli/config-cli/src/main/java/com/quorum/tessera/config/cli/KeyGenCommandFactory.java index 287362d7de..b7761717f2 100644 --- a/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/KeyGenCommandFactory.java +++ b/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/KeyGenCommandFactory.java @@ -1,4 +1,4 @@ -package com.quorum.tessera.picocli; +package com.quorum.tessera.config.cli; import com.quorum.tessera.key.generation.KeyGeneratorFactory; import picocli.CommandLine; @@ -9,10 +9,11 @@ public class KeyGenCommandFactory implements CommandLine.IFactory { public K create(Class cls) throws Exception { try { if (cls != KeyGenCommand.class) { - throw new RuntimeException(this.getClass().getSimpleName() + " cannot create instance of type " + cls.getSimpleName()); + throw new RuntimeException( + this.getClass().getSimpleName() + " cannot create instance of type " + cls.getSimpleName()); } - KeyGeneratorFactory keyGeneratorFactory = KeyGeneratorFactory.newFactory(); + KeyGeneratorFactory keyGeneratorFactory = KeyGeneratorFactory.newFactory(); return (K) new KeyGenCommand(keyGeneratorFactory); } catch (Exception e) { diff --git a/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/KeyUpdateCommand.java b/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/KeyUpdateCommand.java similarity index 94% rename from cli/pico-cli/src/main/java/com/quorum/tessera/picocli/KeyUpdateCommand.java rename to cli/config-cli/src/main/java/com/quorum/tessera/config/cli/KeyUpdateCommand.java index e097918b35..2a76b718c3 100644 --- a/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/KeyUpdateCommand.java +++ b/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/KeyUpdateCommand.java @@ -1,4 +1,4 @@ -package com.quorum.tessera.picocli; +package com.quorum.tessera.config.cli; import com.quorum.tessera.cli.CliException; import com.quorum.tessera.cli.CliResult; @@ -45,7 +45,8 @@ public class KeyUpdateCommand implements Callable { @CommandLine.Option(names = "--keys.keyData.privateKeyPath", required = true) public Path privateKeyPath; - static String invalidArgonAlgorithmMsg = "Allowed values for --keys.keyData.config.data.aopts.algorithm are 'i', 'd' or 'id'"; + static String invalidArgonAlgorithmMsg = + "Allowed values for --keys.keyData.config.data.aopts.algorithm are 'i', 'd' or 'id'"; @CommandLine.Option(names = "--keys.keyData.config.data.aopts.algorithm", defaultValue = "i") public String algorithm; @@ -59,7 +60,8 @@ public class KeyUpdateCommand implements Callable { @CommandLine.Option(names = "--keys.keyData.config.data.aopts.parallelism", defaultValue = "4") public Integer parallelism; - //TODO(cjh) remove plaintext passwords being provided on CLI, replace with prompt and password file - can be done as + // TODO(cjh) remove plaintext passwords being provided on CLI, replace with prompt and password file - can be done + // as // a separate change @CommandLine.Option(names = {"--keys.passwords"}) public String password; @@ -72,8 +74,7 @@ public class KeyUpdateCommand implements Callable { description = "Path to node configuration file") public Config config; - @CommandLine.Mixin - public EncryptorOptions encryptorOptions; + @CommandLine.Mixin public EncryptorOptions encryptorOptions; private KeyEncryptorFactory keyEncryptorFactory; diff --git a/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/KeyUpdateCommandFactory.java b/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/KeyUpdateCommandFactory.java similarity index 80% rename from cli/pico-cli/src/main/java/com/quorum/tessera/picocli/KeyUpdateCommandFactory.java rename to cli/config-cli/src/main/java/com/quorum/tessera/config/cli/KeyUpdateCommandFactory.java index 68f827b7cb..205d2101ed 100644 --- a/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/KeyUpdateCommandFactory.java +++ b/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/KeyUpdateCommandFactory.java @@ -1,4 +1,4 @@ -package com.quorum.tessera.picocli; +package com.quorum.tessera.config.cli; import com.quorum.tessera.config.keys.KeyEncryptorFactory; import com.quorum.tessera.passwords.PasswordReader; @@ -7,12 +7,12 @@ public class KeyUpdateCommandFactory implements CommandLine.IFactory { - @Override public K create(Class cls) throws Exception { try { if (cls != KeyUpdateCommand.class) { - throw new RuntimeException(this.getClass().getSimpleName() + " cannot create instance of type " + cls.getSimpleName()); + throw new RuntimeException( + this.getClass().getSimpleName() + " cannot create instance of type " + cls.getSimpleName()); } KeyEncryptorFactory keyEncryptorFactory = KeyEncryptorFactory.newFactory(); diff --git a/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/NoTesseraCmdArgsException.java b/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/NoTesseraCmdArgsException.java similarity index 60% rename from cli/pico-cli/src/main/java/com/quorum/tessera/picocli/NoTesseraCmdArgsException.java rename to cli/config-cli/src/main/java/com/quorum/tessera/config/cli/NoTesseraCmdArgsException.java index 226a7d8960..9a6667a908 100644 --- a/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/NoTesseraCmdArgsException.java +++ b/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/NoTesseraCmdArgsException.java @@ -1,5 +1,3 @@ -package com.quorum.tessera.picocli; +package com.quorum.tessera.config.cli; -public class NoTesseraCmdArgsException extends RuntimeException { - -} +public class NoTesseraCmdArgsException extends RuntimeException {} diff --git a/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/NoTesseraConfigfileOptionException.java b/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/NoTesseraConfigfileOptionException.java similarity index 56% rename from cli/pico-cli/src/main/java/com/quorum/tessera/picocli/NoTesseraConfigfileOptionException.java rename to cli/config-cli/src/main/java/com/quorum/tessera/config/cli/NoTesseraConfigfileOptionException.java index 1d7079639f..5fd156824c 100644 --- a/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/NoTesseraConfigfileOptionException.java +++ b/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/NoTesseraConfigfileOptionException.java @@ -1,5 +1,3 @@ -package com.quorum.tessera.picocli; +package com.quorum.tessera.config.cli; -public class NoTesseraConfigfileOptionException extends RuntimeException { - -} +public class NoTesseraConfigfileOptionException extends RuntimeException {} diff --git a/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/PicoCliDelegate.java b/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/PicoCliDelegate.java similarity index 98% rename from cli/pico-cli/src/main/java/com/quorum/tessera/picocli/PicoCliDelegate.java rename to cli/config-cli/src/main/java/com/quorum/tessera/config/cli/PicoCliDelegate.java index 561a4d5ee7..723a22688a 100644 --- a/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/PicoCliDelegate.java +++ b/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/PicoCliDelegate.java @@ -1,16 +1,15 @@ -package com.quorum.tessera.picocli; +package com.quorum.tessera.config.cli; import com.quorum.tessera.ServiceLoaderUtil; -import com.quorum.tessera.cli.admin.AdminCliAdapter; import com.quorum.tessera.cli.CLIExceptionCapturer; import com.quorum.tessera.cli.CliException; import com.quorum.tessera.cli.CliResult; +import com.quorum.tessera.config.cli.admin.AdminCliAdapter; import com.quorum.tessera.cli.keypassresolver.CliKeyPasswordResolver; import com.quorum.tessera.cli.keypassresolver.KeyPasswordResolver; import com.quorum.tessera.cli.parsers.ConfigConverter; import com.quorum.tessera.config.ArgonOptions; import com.quorum.tessera.config.Config; -import com.quorum.tessera.config.cli.OverrideUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import picocli.CommandLine; diff --git a/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/TesseraCommand.java b/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/TesseraCommand.java similarity index 88% rename from cli/pico-cli/src/main/java/com/quorum/tessera/picocli/TesseraCommand.java rename to cli/config-cli/src/main/java/com/quorum/tessera/config/cli/TesseraCommand.java index 80d9facb3e..362c2b4f17 100644 --- a/cli/pico-cli/src/main/java/com/quorum/tessera/picocli/TesseraCommand.java +++ b/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/TesseraCommand.java @@ -1,4 +1,4 @@ -package com.quorum.tessera.picocli; +package com.quorum.tessera.config.cli; import com.quorum.tessera.config.Config; import picocli.CommandLine; @@ -29,7 +29,9 @@ public class TesseraCommand { description = "the path to write the PID to") public Path pidFilePath; - @CommandLine.Option(names = {"-o", "--override"}, paramLabel = "KEY=VALUE") + @CommandLine.Option( + names = {"-o", "--override"}, + paramLabel = "KEY=VALUE") private Map overrides = new LinkedHashMap<>(); // TODO(cjh) dry run option to print effective config to terminal to allow review of CLI overrides diff --git a/cli/admin-cli/src/main/java/com/quorum/tessera/cli/admin/AdminCliAdapter.java b/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/admin/AdminCliAdapter.java similarity index 92% rename from cli/admin-cli/src/main/java/com/quorum/tessera/cli/admin/AdminCliAdapter.java rename to cli/config-cli/src/main/java/com/quorum/tessera/config/cli/admin/AdminCliAdapter.java index 6ac5fc9077..70962a26ae 100644 --- a/cli/admin-cli/src/main/java/com/quorum/tessera/cli/admin/AdminCliAdapter.java +++ b/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/admin/AdminCliAdapter.java @@ -1,9 +1,9 @@ -package com.quorum.tessera.cli.admin; +package com.quorum.tessera.config.cli.admin; -import com.quorum.tessera.cli.admin.subcommands.AddPeerCommand; import com.quorum.tessera.cli.CliAdapter; import com.quorum.tessera.cli.CliResult; import com.quorum.tessera.cli.CliType; +import com.quorum.tessera.config.cli.admin.subcommands.AddPeerCommand; import picocli.CommandLine; import java.util.concurrent.Callable; diff --git a/cli/admin-cli/src/main/java/com/quorum/tessera/cli/admin/subcommands/AddPeerCommand.java b/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/admin/subcommands/AddPeerCommand.java similarity index 98% rename from cli/admin-cli/src/main/java/com/quorum/tessera/cli/admin/subcommands/AddPeerCommand.java rename to cli/config-cli/src/main/java/com/quorum/tessera/config/cli/admin/subcommands/AddPeerCommand.java index cccc80e2f9..252bd40731 100644 --- a/cli/admin-cli/src/main/java/com/quorum/tessera/cli/admin/subcommands/AddPeerCommand.java +++ b/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/admin/subcommands/AddPeerCommand.java @@ -1,4 +1,4 @@ -package com.quorum.tessera.cli.admin.subcommands; +package com.quorum.tessera.config.cli.admin.subcommands; import com.quorum.tessera.cli.CliResult; import com.quorum.tessera.cli.parsers.ConfigurationMixin; diff --git a/cli/pico-cli/src/test/java/com/quorum/tessera/picocli/ArgonOptionsConverterTest.java b/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/ArgonOptionsConverterTest.java similarity index 97% rename from cli/pico-cli/src/test/java/com/quorum/tessera/picocli/ArgonOptionsConverterTest.java rename to cli/config-cli/src/test/java/com/quorum/tessera/config/cli/ArgonOptionsConverterTest.java index a47dde6080..d5c1ca584a 100644 --- a/cli/pico-cli/src/test/java/com/quorum/tessera/picocli/ArgonOptionsConverterTest.java +++ b/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/ArgonOptionsConverterTest.java @@ -1,4 +1,4 @@ -package com.quorum.tessera.picocli; +package com.quorum.tessera.config.cli; import com.quorum.tessera.config.ArgonOptions; import org.junit.Before; diff --git a/cli/pico-cli/src/test/java/com/quorum/tessera/picocli/EncryptorOptionsTest.java b/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/EncryptorOptionsTest.java similarity index 92% rename from cli/pico-cli/src/test/java/com/quorum/tessera/picocli/EncryptorOptionsTest.java rename to cli/config-cli/src/test/java/com/quorum/tessera/config/cli/EncryptorOptionsTest.java index 2bb7b3fa16..212e0af040 100644 --- a/cli/pico-cli/src/test/java/com/quorum/tessera/picocli/EncryptorOptionsTest.java +++ b/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/EncryptorOptionsTest.java @@ -1,4 +1,4 @@ -package com.quorum.tessera.picocli; +package com.quorum.tessera.config.cli; import com.quorum.tessera.config.EncryptorConfig; import com.quorum.tessera.config.EncryptorType; @@ -33,7 +33,7 @@ public void ellipticalCurveWithDefinedProperties() { assertThat(result.getType()).isEqualTo(EncryptorType.EC); assertThat(result.getProperties()) - .containsOnlyKeys("symmetricCipher", "ellipticCurve", "nonceLength", "sharedKeyLength"); + .containsOnlyKeys("symmetricCipher", "ellipticCurve", "nonceLength", "sharedKeyLength"); assertThat(result.getProperties().get("symmetricCipher")).isEqualTo("somecipher"); assertThat(result.getProperties().get("ellipticCurve")).isEqualTo("somecurve"); @@ -50,5 +50,4 @@ public void encryptorTypeDefaultsToNACL() { assertThat(result.getType()).isEqualTo(EncryptorType.NACL); assertThat(result.getProperties()).isEmpty(); } - } diff --git a/cli/pico-cli/src/test/java/com/quorum/tessera/picocli/KeyGenCommandTest.java b/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/KeyGenCommandTest.java similarity index 99% rename from cli/pico-cli/src/test/java/com/quorum/tessera/picocli/KeyGenCommandTest.java rename to cli/config-cli/src/test/java/com/quorum/tessera/config/cli/KeyGenCommandTest.java index 3646a2d44c..e3d6c9e858 100644 --- a/cli/pico-cli/src/test/java/com/quorum/tessera/picocli/KeyGenCommandTest.java +++ b/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/KeyGenCommandTest.java @@ -1,4 +1,4 @@ -package com.quorum.tessera.picocli; +package com.quorum.tessera.config.cli; import com.quorum.tessera.cli.CliException; import com.quorum.tessera.cli.CliResult; diff --git a/cli/pico-cli/src/test/java/com/quorum/tessera/picocli/KeyUpdateCommandTest.java b/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/KeyUpdateCommandTest.java similarity index 98% rename from cli/pico-cli/src/test/java/com/quorum/tessera/picocli/KeyUpdateCommandTest.java rename to cli/config-cli/src/test/java/com/quorum/tessera/config/cli/KeyUpdateCommandTest.java index 01d54b88c7..3741ee9c9b 100644 --- a/cli/pico-cli/src/test/java/com/quorum/tessera/picocli/KeyUpdateCommandTest.java +++ b/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/KeyUpdateCommandTest.java @@ -1,4 +1,4 @@ -package com.quorum.tessera.picocli; +package com.quorum.tessera.config.cli; import com.quorum.tessera.cli.CliException; import com.quorum.tessera.config.*; @@ -22,7 +22,8 @@ import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; -import static org.assertj.core.api.Assertions.*; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.catchThrowable; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; diff --git a/cli/pico-cli/src/test/java/com/quorum/tessera/picocli/PicoCliDelegateTest.java b/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/PicoCliDelegateTest.java similarity index 99% rename from cli/pico-cli/src/test/java/com/quorum/tessera/picocli/PicoCliDelegateTest.java rename to cli/config-cli/src/test/java/com/quorum/tessera/config/cli/PicoCliDelegateTest.java index 3f1b84e707..9bfd8c77ad 100644 --- a/cli/pico-cli/src/test/java/com/quorum/tessera/picocli/PicoCliDelegateTest.java +++ b/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/PicoCliDelegateTest.java @@ -1,16 +1,16 @@ -package com.quorum.tessera.picocli; +package com.quorum.tessera.config.cli; import com.quorum.tessera.cli.CliException; import com.quorum.tessera.cli.CliResult; import com.quorum.tessera.config.KeyDataConfig; import com.quorum.tessera.config.Peer; import com.quorum.tessera.config.PrivateKeyType; +import com.quorum.tessera.config.cli.keys.MockKeyGeneratorFactory; import com.quorum.tessera.config.keypairs.FilesystemKeyPair; import com.quorum.tessera.config.keypairs.InlineKeypair; import com.quorum.tessera.config.keys.KeyEncryptor; import com.quorum.tessera.config.util.JaxbUtil; import com.quorum.tessera.key.generation.KeyGenerator; -import com.quorum.tessera.picocli.keys.MockKeyGeneratorFactory; import com.quorum.tessera.test.util.ElUtil; import org.assertj.core.util.Strings; import org.junit.Before; diff --git a/cli/admin-cli/src/test/java/com/quorum/tessera/cli/admin/AdminCliAdapterTest.java b/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/admin/AdminCliAdapterTest.java similarity index 95% rename from cli/admin-cli/src/test/java/com/quorum/tessera/cli/admin/AdminCliAdapterTest.java rename to cli/config-cli/src/test/java/com/quorum/tessera/config/cli/admin/AdminCliAdapterTest.java index 06c736a50a..43c8f2a9a3 100644 --- a/cli/admin-cli/src/test/java/com/quorum/tessera/cli/admin/AdminCliAdapterTest.java +++ b/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/admin/AdminCliAdapterTest.java @@ -1,4 +1,4 @@ -package com.quorum.tessera.cli.admin; +package com.quorum.tessera.config.cli.admin; import com.quorum.tessera.cli.CliResult; import com.quorum.tessera.cli.CliType; diff --git a/cli/admin-cli/src/test/java/com/quorum/tessera/cli/admin/subcommands/AddPeerCommandTest.java b/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/admin/subcommands/AddPeerCommandTest.java similarity index 97% rename from cli/admin-cli/src/test/java/com/quorum/tessera/cli/admin/subcommands/AddPeerCommandTest.java rename to cli/config-cli/src/test/java/com/quorum/tessera/config/cli/admin/subcommands/AddPeerCommandTest.java index 5d2eba48f1..f012fe87e1 100644 --- a/cli/admin-cli/src/test/java/com/quorum/tessera/cli/admin/subcommands/AddPeerCommandTest.java +++ b/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/admin/subcommands/AddPeerCommandTest.java @@ -1,7 +1,6 @@ -package com.quorum.tessera.cli.admin.subcommands; +package com.quorum.tessera.config.cli.admin.subcommands; import com.quorum.tessera.cli.CliResult; -import com.quorum.tessera.cli.admin.subcommands.AddPeerCommand; import com.quorum.tessera.cli.parsers.ConfigurationMixin; import com.quorum.tessera.config.Config; import com.quorum.tessera.config.ConfigFactory; diff --git a/cli/pico-cli/build.gradle b/cli/pico-cli/build.gradle deleted file mode 100644 index b6c5dad4d9..0000000000 --- a/cli/pico-cli/build.gradle +++ /dev/null @@ -1,14 +0,0 @@ - -dependencies { - compile 'info.picocli:picocli' - compile project(':cli:cli-api') - compile project(':cli:config-cli') - compile project(':cli:admin-cli') - compile project(':shared') - compile project(':config') - compile project(':encryption:encryption-api') - compile project(':key-generation') - testCompile project(':tests:test-util') - compile "org.hibernate:hibernate-validator" - -} diff --git a/cli/pico-cli/pom.xml b/cli/pico-cli/pom.xml deleted file mode 100644 index 506baf8efd..0000000000 --- a/cli/pico-cli/pom.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - cli - com.jpmorgan.quorum - 0.11-SNAPSHOT - - 4.0.0 - - pico-cli - - - com.jpmorgan.quorum - cli-api - - - com.jpmorgan.quorum - config-cli - - - com.jpmorgan.quorum - admin-cli - - - - - diff --git a/cli/pico-cli/src/test/java/com/quorum/tessera/picocli/keys/MockKeyGeneratorFactory.java b/cli/pico-cli/src/test/java/com/quorum/tessera/picocli/keys/MockKeyGeneratorFactory.java deleted file mode 100644 index 79b4747cf5..0000000000 --- a/cli/pico-cli/src/test/java/com/quorum/tessera/picocli/keys/MockKeyGeneratorFactory.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.quorum.tessera.picocli.keys; - -import com.quorum.tessera.config.EncryptorConfig; -import com.quorum.tessera.config.KeyVaultConfig; -import com.quorum.tessera.key.generation.KeyGenerator; -import com.quorum.tessera.key.generation.KeyGeneratorFactory; - -import static org.mockito.Mockito.mock; - -public class MockKeyGeneratorFactory implements KeyGeneratorFactory { - - public enum KeyGeneratorHolder { - INSTANCE; - - KeyGenerator keyGenerator = mock(KeyGenerator.class); - } - - @Override - public KeyGenerator create(KeyVaultConfig keyVaultConfig, EncryptorConfig encryptorConfig) { - return getMockKeyGenerator(); - } - - public static KeyGenerator getMockKeyGenerator() { - return KeyGeneratorHolder.INSTANCE.keyGenerator; - } - - public static void reset() { - KeyGeneratorHolder.INSTANCE.keyGenerator = mock(KeyGenerator.class); - } -} diff --git a/cli/pico-cli/src/test/resources/META-INF/services/com.quorum.tessera.key.generation.KeyGeneratorFactory b/cli/pico-cli/src/test/resources/META-INF/services/com.quorum.tessera.key.generation.KeyGeneratorFactory deleted file mode 100644 index 32bc9ace90..0000000000 --- a/cli/pico-cli/src/test/resources/META-INF/services/com.quorum.tessera.key.generation.KeyGeneratorFactory +++ /dev/null @@ -1 +0,0 @@ -com.quorum.tessera.picocli.keys.MockKeyGeneratorFactory diff --git a/cli/pico-cli/src/test/resources/keygen-sample.json b/cli/pico-cli/src/test/resources/keygen-sample.json deleted file mode 100644 index a3f4555734..0000000000 --- a/cli/pico-cli/src/test/resources/keygen-sample.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "useWhiteList": false, - "encryptor": { - "type":"NACL" - }, - "jdbc": { - "username": "scott", - "password": "tiger", - "url": "foo:bar" - }, - "serverConfigs": [ - { - "app": "ThirdParty", - "enabled": true, - "serverAddress": "http://localhost:8090", - "communicationType": "REST" - }, - { - "app": "Q2T", - "enabled": true, - "serverAddress": "unix:/tmp/test.ipc", - "communicationType": "REST" - }, - { - "app": "P2P", - "enabled": true, - "serverAddress": "http://localhost:8091", - "sslConfig": { - "tls": "OFF", - "generateKeyStoreIfNotExisted": "false", - "serverKeyStore": "./ssl/server1-keystore", - "serverKeyStorePassword": "quorum", - "serverTrustStore": "./ssl/server-truststore", - "serverTrustStorePassword": "quorum", - "serverTrustMode": "CA", - "clientKeyStore": "./ssl/client1-keystore", - "clientKeyStorePassword": "quorum", - "clientTrustStore": "./ssl/client-truststore", - "clientTrustStorePassword": "quorum", - "clientTrustMode": "CA", - "knownClientsFile": "./ssl/knownClients1", - "knownServersFile": "./ssl/knownServers1" - }, - "communicationType": "REST" - } - ], - "peer": [ - { - "url": "http://bogus1.com" - }, - { - "url": "http://bogus2.com" - } - ], - "keys": { - "passwords": [ - ], - "keyData": [] - }, - "alwaysSendTo": [], - "unixSocketFile": "${unixSocketPath}" -} diff --git a/cli/pico-cli/src/test/resources/lockedprivatekey.json b/cli/pico-cli/src/test/resources/lockedprivatekey.json deleted file mode 100644 index 26497430a9..0000000000 --- a/cli/pico-cli/src/test/resources/lockedprivatekey.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "data": { - "aopts": { - "variant": "id", - "memory": 1024, - "iterations": 1, - "parallelism": 1 - }, - "snonce": "dwixVoY+pOI2FMuu4k0jLqN/naQiTzWe", - "asalt": "JoPVq9G6NdOb+Ugv+HnUeA==", - "sbox": "6Jd/MXn29fk6jcrFYGPb75l7sDJae06I3Y1Op+bZSZqlYXsMpa/8lLE29H0sX3yw" - }, - "type": "argon2sbox" -} diff --git a/cli/pico-cli/src/test/resources/missing-config.json b/cli/pico-cli/src/test/resources/missing-config.json deleted file mode 100644 index e627f10f00..0000000000 --- a/cli/pico-cli/src/test/resources/missing-config.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "useWhiteList": false, - "encryptor": { - "type":"NACL" - }, - "server": { - "port": 99 - }, - "peer": [ - { - "url": "http://bogus1.com" - }, - { - "url": "http://bogus2.com" - } - ], - "keys": { - "passwords": [], - "keyData": [ - { - "privateKey": { - "value": "PRIVATEKEY", - "argonOptions": { - "algorithm": "SHA", - "iterations": 1, - "memory": 256, - "parallelism": 1 - } - }, - "publicKey": { - "path": "/somepath" - } - } - ] - }, - "alwaysSendTo": [], - "unixSocketFile": "${unixSocketPath}" -} diff --git a/cli/pico-cli/src/test/resources/sample-config-invalidpath.json b/cli/pico-cli/src/test/resources/sample-config-invalidpath.json deleted file mode 100644 index 34bdf12e6f..0000000000 --- a/cli/pico-cli/src/test/resources/sample-config-invalidpath.json +++ /dev/null @@ -1,68 +0,0 @@ -{ - "useWhiteList": false, - "encryptor": { - "type":"NACL" - }, - "jdbc": { - "username": "scott", - "password": "tiger", - "url": "foo:bar" - }, - "serverConfigs": [ - { - "app": "ThirdParty", - "enabled": true, - "serverAddress": "http://localhost:8090", - "communicationType": "REST" - }, - { - "app": "Q2T", - "enabled": true, - "serverAddress": "unix:/tmp/test.ipc", - "communicationType": "REST" - }, - { - "app": "P2P", - "enabled": true, - "serverAddress": "http://localhost:8091", - "sslConfig": { - "tls": "OFF", - "generateKeyStoreIfNotExisted": "false", - "serverKeyStore": "./ssl/server1-keystore", - "serverKeyStorePassword": "quorum", - "serverTrustStore": "./ssl/server-truststore", - "serverTrustStorePassword": "quorum", - "serverTrustMode": "CA", - "clientKeyStore": "./ssl/client1-keystore", - "clientKeyStorePassword": "quorum", - "clientTrustStore": "./ssl/client-truststore", - "clientTrustStorePassword": "quorum", - "clientTrustMode": "CA", - "knownClientsFile": "./ssl/knownClients1", - "knownServersFile": "./ssl/knownServers1" - }, - "communicationType": "REST" - } - ], - "peer": [ - { - "url": "http://bogus1.com" - }, - { - "url": "http://bogus2.com" - } - ], - "keys": { - "passwords": [], - "keyData": [ - { - "publicKeyPath": "${publicKeyPath}", - "privateKeyPath": "${privateKeyPath}" - } - ] - }, - "alwaysSendTo": [ - "/+UuD63zItL1EbjxkKUljMgG8Z1w0AJ8pNOR4iq2yQc=" - ], - "unixSocketFile": "tm.ipc" -} diff --git a/cli/pico-cli/src/test/resources/sample-config.json b/cli/pico-cli/src/test/resources/sample-config.json deleted file mode 100644 index f4a16719f1..0000000000 --- a/cli/pico-cli/src/test/resources/sample-config.json +++ /dev/null @@ -1,80 +0,0 @@ -{ - "useWhiteList": false, - "encryptor": { - "type":"NACL" - }, - "jdbc": { - "username": "scott", - "password": "tiger", - "url": "foo:bar" - }, - "serverConfigs": [ - { - "app": "ThirdParty", - "enabled": true, - "serverAddress": "http://localhost:8090", - - "communicationType": "REST" - }, - { - "app": "ADMIN", - "enabled": true, - "serverAddress": "http://localhost:18090", - "communicationType": "REST" - }, - { - "app": "Q2T", - "enabled": true, - "serverAddress": "unix:/tmp/test.ipc", - "communicationType": "REST" - }, - { - "app": "P2P", - "enabled": true, - "serverAddress": "http://localhost:8091", - "sslConfig": { - "tls": "OFF", - "generateKeyStoreIfNotExisted": "false", - "serverKeyStore": "./ssl/server1-keystore", - "serverKeyStorePassword": "quorum", - "serverTrustStore": "./ssl/server-truststore", - "serverTrustStorePassword": "quorum", - "serverTrustMode": "CA", - "clientKeyStore": "./ssl/client1-keystore", - "clientKeyStorePassword": "quorum", - "clientTrustStore": "./ssl/client-truststore", - "clientTrustStorePassword": "quorum", - "clientTrustMode": "CA", - "knownClientsFile": "./ssl/knownClients1", - "knownServersFile": "./ssl/knownServers1" - }, - "communicationType": "REST" - } - ], - "peer": [ - { - "url": "http://bogus1.com" - }, - { - "url": "http://bogus2.com" - } - ], - "keys": { - "passwords": [], - "keyData": [ - { - "config": { - "data": { - "bytes": "Wl+xSyXVuuqzpvznOS7dOobhcn4C5auxkFRi7yLtgtA=" - }, - "type": "unlocked" - }, - "publicKey": "/+UuD63zItL1EbjxkKUljMgG8Z1w0AJ8pNOR4iq2yQc=" - } - ] - }, - "alwaysSendTo": [ - "/+UuD63zItL1EbjxkKUljMgG8Z1w0AJ8pNOR4iq2yQc=" - ], - "unixSocketFile": "${unixSocketPath}" -} diff --git a/cli/pom.xml b/cli/pom.xml index e575d62ca0..520fdb2eea 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -5,8 +5,6 @@ cli-api config-cli - admin-cli - pico-cli diff --git a/pom.xml b/pom.xml index ba3d1f6fdd..aa240f8dd5 100644 --- a/pom.xml +++ b/pom.xml @@ -516,11 +516,6 @@ 0.11-SNAPSHOT - - com.jpmorgan.quorum - admin-cli - 0.11-SNAPSHOT - com.jpmorgan.quorum diff --git a/settings.gradle b/settings.gradle index 10dcf0a1cf..0c00259abc 100644 --- a/settings.gradle +++ b/settings.gradle @@ -3,8 +3,6 @@ include(':argon2') include(':config') include(':cli:cli-api') include(':cli:config-cli') -include(':cli:admin-cli') -include(':cli:pico-cli') include(':cli') include(':tests:acceptance-test') include(':tests:test-util') @@ -58,9 +56,7 @@ include(':tessera-jaxrs:jaxrs-client') include(':tessera-jaxrs') include(':tessera-data') project(':cli:cli-api').projectDir = file('cli/cli-api') -project(':cli:pico-cli').projectDir = file('cli/pico-cli') project(':cli:config-cli').projectDir = file('cli/config-cli') -project(':cli:admin-cli').projectDir = file('cli/admin-cli') project(':tests:acceptance-test').projectDir = file('tests/acceptance-test') project(':tests:test-util').projectDir = file('tests/test-util') project(':tests:jmeter-test').projectDir = file('tests/jmeter-test') diff --git a/tessera-dist/tessera-app/build.gradle b/tessera-dist/tessera-app/build.gradle index 011060ee4a..3572c44cb8 100644 --- a/tessera-dist/tessera-app/build.gradle +++ b/tessera-dist/tessera-app/build.gradle @@ -23,7 +23,6 @@ dependencies { compile project(':tessera-core') compile project(':cli:cli-api') compile project(':cli:config-cli') - compile project(':cli:admin-cli') compile project(':tessera-jaxrs:admin-jaxrs') compile project(':tessera-jaxrs:sync-jaxrs') compile project(':tessera-jaxrs:transaction-jaxrs') diff --git a/tessera-dist/tessera-app/pom.xml b/tessera-dist/tessera-app/pom.xml index e3e868586b..0a6eae6a23 100644 --- a/tessera-dist/tessera-app/pom.xml +++ b/tessera-dist/tessera-app/pom.xml @@ -30,12 +30,6 @@ runtime - - com.jpmorgan.quorum - admin-cli - runtime - - com.jpmorgan.quorum encryption-api diff --git a/tessera-dist/tessera-launcher/build.gradle b/tessera-dist/tessera-launcher/build.gradle index b01b31add9..bf776c1faf 100644 --- a/tessera-dist/tessera-launcher/build.gradle +++ b/tessera-dist/tessera-launcher/build.gradle @@ -2,7 +2,7 @@ dependencies { compile project(':config') compile project(':cli:cli-api') - compile project(':cli:pico-cli') + compile project(':cli:config-cli') compile project(':server:server-api') compile project(':tessera-jaxrs:common-jaxrs') compile 'org.apache.commons:commons-lang3' diff --git a/tessera-dist/tessera-launcher/pom.xml b/tessera-dist/tessera-launcher/pom.xml index c77852470c..cf1f5f5e76 100644 --- a/tessera-dist/tessera-launcher/pom.xml +++ b/tessera-dist/tessera-launcher/pom.xml @@ -25,15 +25,11 @@ common-jaxrs 0.11-SNAPSHOT + com.jpmorgan.quorum config-cli - - com.jpmorgan.quorum - pico-cli - 0.11-SNAPSHOT - compile - + diff --git a/tessera-dist/tessera-launcher/src/main/java/com/quorum/tessera/launcher/Main.java b/tessera-dist/tessera-launcher/src/main/java/com/quorum/tessera/launcher/Main.java index 9017829f52..d137f0e60e 100644 --- a/tessera-dist/tessera-launcher/src/main/java/com/quorum/tessera/launcher/Main.java +++ b/tessera-dist/tessera-launcher/src/main/java/com/quorum/tessera/launcher/Main.java @@ -8,7 +8,7 @@ import com.quorum.tessera.config.Config; import com.quorum.tessera.config.ConfigException; import com.quorum.tessera.config.apps.TesseraAppFactory; -import com.quorum.tessera.picocli.PicoCliDelegate; +import com.quorum.tessera.config.cli.PicoCliDelegate; import com.quorum.tessera.server.TesseraServer; import com.quorum.tessera.server.TesseraServerFactory; import org.apache.commons.lang3.exception.ExceptionUtils; diff --git a/tessera-dist/tessera-simple/pom.xml b/tessera-dist/tessera-simple/pom.xml index 9a538ce65b..d65ea86d6e 100644 --- a/tessera-dist/tessera-simple/pom.xml +++ b/tessera-dist/tessera-simple/pom.xml @@ -34,12 +34,6 @@ runtime - - com.jpmorgan.quorum - admin-cli - runtime - - com.jpmorgan.quorum encryption-api From 075505b4d981225990f38707f81fd01ecff8873f Mon Sep 17 00:00:00 2001 From: Mark Lowe Date: Fri, 20 Dec 2019 16:05:35 +0000 Subject: [PATCH 40/41] Populate unmatched cli options and search for config override matches. Ignore unmatchable items, log refelct exception at debug level and move on to next. --- .../tessera/config/cli/PicoCliDelegate.java | 32 ++++++++++-- .../tessera/config/cli/TesseraCommand.java | 3 ++ .../config/cli/PicoCliDelegateTest.java | 49 +++++++++++++++++++ 3 files changed, 79 insertions(+), 5 deletions(-) diff --git a/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/PicoCliDelegate.java b/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/PicoCliDelegate.java index 723a22688a..9d7696de1e 100644 --- a/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/PicoCliDelegate.java +++ b/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/PicoCliDelegate.java @@ -10,6 +10,7 @@ import com.quorum.tessera.cli.parsers.ConfigConverter; import com.quorum.tessera.config.ArgonOptions; import com.quorum.tessera.config.Config; +import com.quorum.tessera.reflect.ReflectException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import picocli.CommandLine; @@ -23,10 +24,7 @@ import java.lang.management.ManagementFactory; import java.nio.file.Files; import java.nio.file.Path; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; +import java.util.*; import static java.nio.charset.StandardCharsets.UTF_8; import static java.nio.file.StandardOpenOption.CREATE; @@ -74,7 +72,8 @@ public CliResult execute(String... args) throws Exception { .setSeparator(" ") .setCaseInsensitiveEnumValuesAllowed(true) .setExecutionExceptionHandler(mapper) - .setParameterExceptionHandler(mapper); + .setParameterExceptionHandler(mapper) + .setStopAtUnmatched(false); final CommandLine.ParseResult parseResult; try { @@ -162,6 +161,29 @@ private Config getConfigFromCLI(CommandLine.ParseResult parseResult) throws Exce } } + if (Objects.nonNull(parseResult.unmatched())) { + List unmatched = new ArrayList<>(parseResult.unmatched()); + + for (int i = 0; i < unmatched.size(); i++) { + String line = unmatched.get(i); + if (line.startsWith("-")) { + final String name = line.replaceFirst("-{1,2}", ""); + final int nextIndex = i + 1; + if(nextIndex > (unmatched.size() -1)) { + break; + } + final String value = unmatched.get(nextIndex); + try { + OverrideUtil.setValue(config, name, value); + } catch(ReflectException ex) { + //Ignore error + LOGGER.debug("",ex); + continue; + } + } + } + } + keyPasswordResolver.resolveKeyPasswords(config); final Set> violations = validator.validate(config); diff --git a/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/TesseraCommand.java b/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/TesseraCommand.java index 362c2b4f17..a65d5a878a 100644 --- a/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/TesseraCommand.java +++ b/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/TesseraCommand.java @@ -5,6 +5,7 @@ import java.nio.file.Path; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; @CommandLine.Command( @@ -34,5 +35,7 @@ public class TesseraCommand { paramLabel = "KEY=VALUE") private Map overrides = new LinkedHashMap<>(); + @CommandLine.Unmatched public List unmatchedEntries; + // TODO(cjh) dry run option to print effective config to terminal to allow review of CLI overrides } diff --git a/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/PicoCliDelegateTest.java b/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/PicoCliDelegateTest.java index 9bfd8c77ad..2dcec4726e 100644 --- a/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/PicoCliDelegateTest.java +++ b/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/PicoCliDelegateTest.java @@ -2,6 +2,7 @@ import com.quorum.tessera.cli.CliException; import com.quorum.tessera.cli.CliResult; +import com.quorum.tessera.config.Config; import com.quorum.tessera.config.KeyDataConfig; import com.quorum.tessera.config.Peer; import com.quorum.tessera.config.PrivateKeyType; @@ -440,4 +441,52 @@ public void subcommandExceptionIsThrown() { assertThat(ex).isNotNull(); assertThat(ex).isInstanceOf(CliException.class); } + + @Test + public void withValidConfigAndJdbcOveride() throws Exception { + + Path configFile = createAndPopulatePaths(getClass().getResource("/sample-config.json")); + CliResult result = cliDelegate.execute("-configfile", configFile.toString(), "-jdbc.autoCreateTables", "true"); + + assertThat(result).isNotNull(); + assertThat(result.getConfig()).isPresent(); + assertThat(result.getConfig()).isPresent(); + assertThat(result.getStatus()).isEqualTo(0); + + assertThat(result.isSuppressStartup()).isFalse(); + + Config config = result.getConfig().get(); + assertThat(config.getJdbcConfig()).isNotNull(); + assertThat(config.getJdbcConfig().isAutoCreateTables()).isTrue(); + } + + @Test + public void withValidConfigAndUnmatchableDynamicOption() throws Exception { + + Path configFile = createAndPopulatePaths(getClass().getResource("/sample-config.json")); + CliResult result = cliDelegate.execute("-configfile", configFile.toString(), "-bogus"); + + assertThat(result).isNotNull(); + assertThat(result.getConfig()).isPresent(); + assertThat(result.getConfig()).isPresent(); + assertThat(result.getStatus()).isEqualTo(0); + + assertThat(result.isSuppressStartup()).isFalse(); + + } + + @Test + public void withValidConfigAndUnmatchableDynamicOptionWithValue() throws Exception { + + Path configFile = createAndPopulatePaths(getClass().getResource("/sample-config.json")); + CliResult result = cliDelegate.execute("-configfile", configFile.toString(), "-bogus","bogus value"); + + assertThat(result).isNotNull(); + assertThat(result.getConfig()).isPresent(); + assertThat(result.getConfig()).isPresent(); + assertThat(result.getStatus()).isEqualTo(0); + + assertThat(result.isSuppressStartup()).isFalse(); + + } } From 8bd8b9412aefaabc4ed58e837ab1ac62e0940e69 Mon Sep 17 00:00:00 2001 From: Mark Lowe Date: Fri, 20 Dec 2019 17:12:05 +0000 Subject: [PATCH 41/41] move index forward when value has neen extracted from unmatched option. --- .../tessera/config/cli/PicoCliDelegate.java | 1 + .../config/cli/PicoCliDelegateTest.java | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/PicoCliDelegate.java b/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/PicoCliDelegate.java index 9d7696de1e..6db0fc180e 100644 --- a/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/PicoCliDelegate.java +++ b/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/PicoCliDelegate.java @@ -172,6 +172,7 @@ private Config getConfigFromCLI(CommandLine.ParseResult parseResult) throws Exce if(nextIndex > (unmatched.size() -1)) { break; } + i = nextIndex; final String value = unmatched.get(nextIndex); try { OverrideUtil.setValue(config, name, value); diff --git a/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/PicoCliDelegateTest.java b/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/PicoCliDelegateTest.java index 2dcec4726e..1c5689510a 100644 --- a/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/PicoCliDelegateTest.java +++ b/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/PicoCliDelegateTest.java @@ -489,4 +489,23 @@ public void withValidConfigAndUnmatchableDynamicOptionWithValue() throws Excepti assertThat(result.isSuppressStartup()).isFalse(); } + + @Test + public void withValidConfigAndJdbcOverides() throws Exception { + + Path configFile = createAndPopulatePaths(getClass().getResource("/sample-config.json")); + CliResult result = cliDelegate.execute("-configfile", configFile.toString(), "-jdbc.autoCreateTables", "true", "-jdbc.url", "someurl"); + + assertThat(result).isNotNull(); + assertThat(result.getConfig()).isPresent(); + assertThat(result.getConfig()).isPresent(); + assertThat(result.getStatus()).isEqualTo(0); + + assertThat(result.isSuppressStartup()).isFalse(); + + Config config = result.getConfig().get(); + assertThat(config.getJdbcConfig()).isNotNull(); + assertThat(config.getJdbcConfig().isAutoCreateTables()).isTrue(); + assertThat(config.getJdbcConfig().getUrl()).isEqualTo("someurl"); + } }