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/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/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..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/config-cli/src/main/java/com/quorum/tessera/config/cli/ArgonOptionsConverter.java b/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/ArgonOptionsConverter.java
new file mode 100644
index 0000000000..875d4cbb27
--- /dev/null
+++ b/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/ArgonOptionsConverter.java
@@ -0,0 +1,27 @@
+package com.quorum.tessera.config.cli;
+
+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/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 8ed7a941f1..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/EncryptorOptions.java b/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/EncryptorOptions.java
new file mode 100644
index 0000000000..a1af182c1d
--- /dev/null
+++ b/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/EncryptorOptions.java
@@ -0,0 +1,53 @@
+package com.quorum.tessera.config.cli;
+
+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.Objects;
+import java.util.Optional;
+
+class EncryptorOptions {
+
+ @CommandLine.Option(
+ names = {"--encryptor.type"},
+ description = "Valid values: ${COMPLETION-CANDIDATES}")
+ EncryptorType type;
+
+ @CommandLine.Option(names = {"--encryptor.symmetricCipher"})
+ String symmetricCipher;
+
+ @CommandLine.Option(names = {"--encryptor.ellipticCurve"})
+ String ellipticCurve;
+
+ @CommandLine.Option(names = {"--encryptor.nonceLength"})
+ String nonceLength;
+
+ @CommandLine.Option(names = {"--encryptor.sharedKeyLength"})
+ String sharedKeyLength;
+
+ 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 annotations
+ if (Objects.isNull(type)) {
+ type = EncryptorType.NACL;
+ }
+
+ Map properties = new HashMap<>();
+ if (type == EncryptorType.EC) {
+
+ 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.setType(type);
+ encryptorConfig.setProperties(properties);
+
+ return encryptorConfig;
+ }
+}
diff --git a/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/KeyGenCommand.java b/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/KeyGenCommand.java
new file mode 100644
index 0000000000..d74062c888
--- /dev/null
+++ b/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/KeyGenCommand.java
@@ -0,0 +1,181 @@
+package com.quorum.tessera.config.cli;
+
+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 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(
+ 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 KeyGeneratorFactory factory;
+
+ private final Validator validator =
+ Validation.byDefaultProvider().configure().ignoreXmlConfiguration().buildValidatorFactory().getValidator();
+
+ // TODO(cjh) changes have been made to CLI option descriptions. should these be raised as a separate change ?
+
+ @CommandLine.Option(
+ names = {"--keyout", "-filename"},
+ 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;
+
+ @CommandLine.Option(
+ 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"},
+ 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})")
+ 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;
+
+ @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 =
+ "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;
+
+ KeyGenCommand(KeyGeneratorFactory keyGeneratorFactory) {
+ this.factory = keyGeneratorFactory;
+ }
+
+ // 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;
+
+ 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);
+
+ if (Objects.isNull(keyOut) || keyOut.isEmpty()) {
+ generator.generate("", argonOptions, keyVaultOptions);
+ } else {
+ keyOut.forEach(name -> generator.generate(name, argonOptions, 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();
+ }
+
+ if (Objects.isNull(vaultType)) {
+ throw new CliException("Key vault type either not provided or not recognised");
+ }
+
+ 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 (Objects.isNull(keyOut) || keyOut.isEmpty()) {
+ 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/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/KeyGenCommandFactory.java b/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/KeyGenCommandFactory.java
new file mode 100644
index 0000000000..b7761717f2
--- /dev/null
+++ b/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/KeyGenCommandFactory.java
@@ -0,0 +1,23 @@
+package com.quorum.tessera.config.cli;
+
+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/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..2a76b718c3
--- /dev/null
+++ b/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/KeyUpdateCommand.java
@@ -0,0 +1,178 @@
+package com.quorum.tessera.config.cli;
+
+import com.quorum.tessera.cli.CliException;
+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.Base64;
+import java.util.List;
+import java.util.Optional;
+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);
+
+ @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'";
+
+ @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;
+
+ // 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;
+
+ @CommandLine.Option(names = {"--keys.passwordFile"})
+ public Path passwordFile;
+
+ @CommandLine.Option(
+ names = {"--configfile", "-configfile"},
+ description = "Path to node configuration file")
+ public Config config;
+
+ @CommandLine.Mixin public EncryptorOptions encryptorOptions;
+
+ private KeyEncryptorFactory keyEncryptorFactory;
+
+ // TODO(cjh) is package-private for migrated apache commons CLI tests
+ 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 = encryptorOptions.parseEncryptorConfig();
+ }
+
+ 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 new CliResult(0, true, null);
+ }
+
+ 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");
+ }
+ }
+
+ Path privateKeyPath() {
+ if (Files.notExists(privateKeyPath)) {
+ throw new IllegalArgumentException("Private key path must exist when updating key password");
+ }
+
+ return privateKeyPath;
+ }
+
+ List passwords() throws IOException {
+ if (password != null) {
+ return singletonList(password);
+ } else if (passwordFile != null) {
+ return Files.readAllLines(passwordFile);
+ } else {
+ return emptyList();
+ }
+ }
+
+ ArgonOptions argonOptions() {
+ 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/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..205d2101ed
--- /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/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/NoTesseraCmdArgsException.java b/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/NoTesseraCmdArgsException.java
new file mode 100644
index 0000000000..9a6667a908
--- /dev/null
+++ b/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/NoTesseraCmdArgsException.java
@@ -0,0 +1,3 @@
+package com.quorum.tessera.config.cli;
+
+public class NoTesseraCmdArgsException extends RuntimeException {}
diff --git a/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/NoTesseraConfigfileOptionException.java b/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/NoTesseraConfigfileOptionException.java
new file mode 100644
index 0000000000..5fd156824c
--- /dev/null
+++ b/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/NoTesseraConfigfileOptionException.java
@@ -0,0 +1,3 @@
+package com.quorum.tessera.config.cli;
+
+public class NoTesseraConfigfileOptionException extends RuntimeException {}
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..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
@@ -1,9 +1,11 @@
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;
import org.slf4j.LoggerFactory;
+
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import java.lang.reflect.Array;
@@ -15,6 +17,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 +32,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 +146,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 +162,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 updated = new ArrayList(list);
+
+ while (updated.size() <= i) {
+ Class convertedType = PRIMITIVE_LOOKUP.getOrDefault(fieldType, fieldType);
+ Object emptyValue = convertTo(convertedType, null);
+
+ updated.add(emptyValue);
+ }
- List merged = new ArrayList(list);
- merged.addAll(convertedValues);
+ updated.set(i, convertedValue);
- setValue(root, field, merged);
+ setValue(root, field, updated);
} else {
@@ -184,34 +218,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/main/java/com/quorum/tessera/config/cli/PicoCliDelegate.java b/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/PicoCliDelegate.java
new file mode 100644
index 0000000000..6db0fc180e
--- /dev/null
+++ b/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/PicoCliDelegate.java
@@ -0,0 +1,215 @@
+package com.quorum.tessera.config.cli;
+
+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.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.reflect.ReflectException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import picocli.CommandLine;
+import picocli.CommandLine.Model.CommandSpec;
+
+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.*;
+
+import static java.nio.charset.StandardCharsets.UTF_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);
+
+ private final Validator validator =
+ Validation.byDefaultProvider().configure().ignoreXmlConfiguration().buildValidatorFactory().getValidator();
+
+ private final KeyPasswordResolver keyPasswordResolver;
+
+ public PicoCliDelegate() {
+ this(ServiceLoaderUtil.load(KeyPasswordResolver.class).orElse(new CliKeyPasswordResolver()));
+ }
+
+ private PicoCliDelegate(final KeyPasswordResolver keyPasswordResolver) {
+ this.keyPasswordResolver = Objects.requireNonNull(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);
+
+ final CommandLine.IFactory keyUpdateCommandFactory = new KeyUpdateCommandFactory();
+ 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);
+
+ final CommandLine commandLine = new CommandLine(command);
+ commandLine
+ .registerConverter(Config.class, new ConfigConverter())
+ .registerConverter(ArgonOptions.class, new ArgonOptionsConverter())
+ .setSeparator(" ")
+ .setCaseInsensitiveEnumValuesAllowed(true)
+ .setExecutionExceptionHandler(mapper)
+ .setParameterExceptionHandler(mapper)
+ .setStopAtUnmatched(false);
+
+ final CommandLine.ParseResult parseResult;
+ try {
+ parseResult = commandLine.parseArgs(args);
+ } catch (CommandLine.ParameterException ex) {
+ try {
+ commandLine.getParameterExceptionHandler().handleParseException(ex, args);
+ throw new CliException(ex.getMessage());
+ } catch (Exception e) {
+ throw new CliException(ex.getMessage());
+ }
+ }
+
+ if (CommandLine.printHelpIfRequested(parseResult)) {
+ return new CliResult(0, true, null);
+ }
+
+ if (!parseResult.hasSubcommand()) {
+ // the node is being started
+ final Config config;
+ try {
+ config = getConfigFromCLI(parseResult);
+ } 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);
+
+ } else {
+ // there is a subcommand
+ 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);
+
+ // 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);
+ }
+ }
+
+ private Config getConfigFromCLI(CommandLine.ParseResult parseResult) throws Exception {
+ List parsedArgs = parseResult.matchedArgs();
+
+ if (parsedArgs.size() == 0) {
+ throw new NoTesseraCmdArgsException();
+ }
+
+ final Config config;
+
+ // start with any config read from the file
+ if (parseResult.hasMatchedOption("configfile")) {
+ config = parseResult.matchedOption("configfile").getValue();
+ } else {
+ throw new NoTesseraConfigfileOptionException();
+ }
+
+ 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);
+ }
+ }
+
+ 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;
+ }
+ i = nextIndex;
+ 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);
+ 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 (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));
+ }
+ }
+}
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
new file mode 100644
index 0000000000..a65d5a878a
--- /dev/null
+++ b/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/TesseraCommand.java
@@ -0,0 +1,41 @@
+package com.quorum.tessera.config.cli;
+
+import com.quorum.tessera.config.Config;
+import picocli.CommandLine;
+
+import java.nio.file.Path;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+@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)
+public class TesseraCommand {
+
+ @CommandLine.Option(
+ 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")
+ public Path pidFilePath;
+
+ @CommandLine.Option(
+ names = {"-o", "--override"},
+ 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/admin-cli/src/main/java/com/quorum/tessera/admin/cli/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/admin/cli/AdminCliAdapter.java
rename to cli/config-cli/src/main/java/com/quorum/tessera/config/cli/admin/AdminCliAdapter.java
index 1aaf015aaa..70962a26ae 100644
--- a/cli/admin-cli/src/main/java/com/quorum/tessera/admin/cli/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.admin.cli;
+package com.quorum.tessera.config.cli.admin;
-import com.quorum.tessera.admin.cli.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/admin/cli/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/admin/cli/subcommands/AddPeerCommand.java
rename to cli/config-cli/src/main/java/com/quorum/tessera/config/cli/admin/subcommands/AddPeerCommand.java
index 571c456400..252bd40731 100644
--- a/cli/admin-cli/src/main/java/com/quorum/tessera/admin/cli/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.admin.cli.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/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/ArgonOptionsConverterTest.java b/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/ArgonOptionsConverterTest.java
new file mode 100644
index 0000000000..d5c1ca584a
--- /dev/null
+++ b/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/ArgonOptionsConverterTest.java
@@ -0,0 +1,55 @@
+package com.quorum.tessera.config.cli;
+
+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/config-cli/src/test/java/com/quorum/tessera/config/cli/EncryptorOptionsTest.java b/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/EncryptorOptionsTest.java
new file mode 100644
index 0000000000..212e0af040
--- /dev/null
+++ b/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/EncryptorOptionsTest.java
@@ -0,0 +1,53 @@
+package com.quorum.tessera.config.cli;
+
+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/config-cli/src/test/java/com/quorum/tessera/config/cli/KeyGenCommandTest.java b/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/KeyGenCommandTest.java
new file mode 100644
index 0000000000..e3d6c9e858
--- /dev/null
+++ b/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/KeyGenCommandTest.java
@@ -0,0 +1,501 @@
+package com.quorum.tessera.config.cli;
+
+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.argonOptions = 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/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/KeyUpdateCommandTest.java b/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/KeyUpdateCommandTest.java
new file mode 100644
index 0000000000..3741ee9c9b
--- /dev/null
+++ b/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/KeyUpdateCommandTest.java
@@ -0,0 +1,392 @@
+package com.quorum.tessera.config.cli;
+
+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;
+import com.quorum.tessera.config.util.JaxbUtil;
+import com.quorum.tessera.encryption.PrivateKey;
+import com.quorum.tessera.passwords.PasswordReader;
+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 Exception {
+ // 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);
+ }
+
+ @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 {
+ 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;
+ }
+}
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..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
@@ -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;
@@ -17,6 +18,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 +332,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 +355,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 +389,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 +412,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 +427,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 +439,291 @@ public String getValue() {
}
};
- OverrideUtil.setValue(annon, "value", "SOMETHING", "SOMETHINGELSE");
+ OverrideUtil.setValue(anon, "value", "SOMETHING");
}
interface SomeIFace {
String getValue();
}
+
+ @Test
+ public void setValueCollectionButNoPositionProvided() {
+ 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);
+
+ 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);
+
+ 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 setValuePropertyOfElementInComplexCollectionReplaced() {
+ final int initialValue = 11;
+ final int overriddenValue = 20;
+
+ final ToOverride toOverride = new ToOverride();
+
+ final ToOverride.OtherTestClass otherClass = new ToOverride.OtherTestClass();
+ otherClass.setCount(initialValue);
+
+ final 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 setValueElementOfSimpleCollectionInComplexCollectionReplaced() {
+ final String initialValue = "initial test value";
+ final String overriddenValue = "updated test value";
+
+ final ToOverride toOverride = new ToOverride();
+
+ final List otherList = Arrays.asList("some value", initialValue);
+ final ToOverride.OtherTestClass otherClass = new ToOverride.OtherTestClass();
+ otherClass.setOtherList(otherList);
+
+ final 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 setValueSimplePropertyReplaced() {
+ final String initialValue = "the initial value";
+ final String overriddenValue = "the overridden value";
+
+ final ToOverride toOverride = new ToOverride();
+ toOverride.setOtherValue(initialValue);
+
+ OverrideUtil.setValue(toOverride, "otherValue", overriddenValue);
+
+ assertThat(toOverride.getOtherValue()).isEqualTo(overriddenValue);
+ }
+
+ @Test
+ public void setValuePropertyOfComplexPropertyReplaced() {
+ final int initialValue = 11;
+ final int overriddenValue = 20;
+
+ final ToOverride.OtherTestClass complexProperty = new ToOverride.OtherTestClass();
+ complexProperty.setCount(initialValue);
+
+ final 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 setValueSimpleCollectionCreated() {
+ final String overriddenValue = "overridden test value";
+
+ final 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 setValueComplexCollectionCreated() {
+ final int overriddenCount = 11;
+ final int otherOverriddenCount = 22;
+ final String overriddenValue = "overridden test value";
+
+ final 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 setValueSimpleCollectionInComplexCollectionCreated() {
+ final String overriddenValue = "overridden test value";
+
+ final 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 setValueNullSimplePropertySet() {
+ final String overriddenValue = "overridden test value";
+
+ final ToOverride toOverride = new ToOverride();
+
+ OverrideUtil.setValue(toOverride, "otherValue", overriddenValue);
+
+ assertThat(toOverride.getOtherValue()).isNotNull();
+ assertThat(toOverride.getOtherValue()).isEqualTo(overriddenValue);
+ }
+
+ @Test
+ public void setValueNullPropertyOfComplexPropertySet() {
+ final String overriddenValue = "overridden test value";
+
+ final ToOverride toOverride = new ToOverride();
+ final 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 setValueSimpleCollectionExtended() {
+ final String overriddenValue = "overridden test value";
+
+ final ToOverride toOverride = new ToOverride();
+ final 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 setValueComplexCollectionExtended() {
+ final String overriddenValue = "overridden test value";
+
+ final ToOverride toOverride = new ToOverride();
+ final ToOverride.OtherTestClass otherTestClass = new ToOverride.OtherTestClass();
+ otherTestClass.setStrVal("element1");
+
+ final 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 setValueSimpleCollectionInComplexCollectionExtended() {
+ final String overriddenValue = "overridden test value";
+
+ final ToOverride toOverride = new ToOverride();
+ final List otherList = new ArrayList<>();
+ otherList.add("otherElement1");
+
+ final ToOverride.OtherTestClass otherTestClass = new ToOverride.OtherTestClass();
+ otherTestClass.setOtherList(otherList);
+
+ final 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 setValuePeersAppended() {
+ assertThat(true).isFalse();
+ }
}
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/PicoCliDelegateTest.java
similarity index 61%
rename from cli/config-cli/src/test/java/com/quorum/tessera/config/cli/DefaultCliAdapterTest.java
rename to cli/config-cli/src/test/java/com/quorum/tessera/config/cli/PicoCliDelegateTest.java
index ca6ca4eafa..1c5689510a 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/PicoCliDelegateTest.java
@@ -2,7 +2,7 @@
import com.quorum.tessera.cli.CliException;
import com.quorum.tessera.cli.CliResult;
-import com.quorum.tessera.cli.CliType;
+import com.quorum.tessera.config.Config;
import com.quorum.tessera.config.KeyDataConfig;
import com.quorum.tessera.config.Peer;
import com.quorum.tessera.config.PrivateKeyType;
@@ -13,6 +13,7 @@
import com.quorum.tessera.config.util.JaxbUtil;
import com.quorum.tessera.key.generation.KeyGenerator;
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;
@@ -36,29 +37,24 @@
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.ArgumentMatchers.eq;
import static org.mockito.Mockito.*;
-public class DefaultCliAdapterTest {
+public class PicoCliDelegateTest {
- private static final Logger LOGGER = LoggerFactory.getLogger(DefaultCliAdapterTest.class);
+ private static final Logger LOGGER = LoggerFactory.getLogger(PicoCliDelegateTest.class);
- private DefaultCliAdapter cliAdapter;
+ private PicoCliDelegate cliDelegate;
@Before
public void setUp() {
- MockKeyGeneratorFactory.reset();
- this.cliAdapter = new DefaultCliAdapter();
- }
-
- @Test
- public void getType() {
- assertThat(cliAdapter.getType()).isEqualTo(CliType.CONFIG);
+ cliDelegate = new PicoCliDelegate();
}
@Test
public void help() throws Exception {
- final CliResult result = cliAdapter.execute("help");
+ final CliResult result = cliDelegate.execute("help");
assertThat(result).isNotNull();
assertThat(result.getConfig()).isNotPresent();
@@ -67,9 +63,9 @@ public void help() throws Exception {
}
@Test
- public void helpViaCall() throws Exception {
- cliAdapter.setAllParameters(new String[] {"help"});
- final CliResult result = cliAdapter.call();
+ public void noArgsPrintsHelp() throws Exception {
+
+ final CliResult result = cliDelegate.execute();
assertThat(result).isNotNull();
assertThat(result.getConfig()).isNotPresent();
@@ -78,9 +74,9 @@ public void helpViaCall() throws Exception {
}
@Test
- public void noArgsPrintsHelp() throws Exception {
+ public void subcommandWithNoArgsPrintsHelp() throws Exception {
- final CliResult result = cliAdapter.execute();
+ final CliResult result = cliDelegate.execute("keygen");
assertThat(result).isNotNull();
assertThat(result.getConfig()).isNotPresent();
@@ -92,7 +88,49 @@ public void noArgsPrintsHelp() throws Exception {
public void withValidConfig() throws Exception {
Path configFile = createAndPopulatePaths(getClass().getResource("/sample-config.json"));
- CliResult result = cliAdapter.execute("-configfile", configFile.toString());
+ CliResult result = cliDelegate.execute("-configfile", configFile.toString());
+
+ assertThat(result).isNotNull();
+ assertThat(result.getConfig()).isPresent();
+ assertThat(result.getStatus()).isEqualTo(0);
+ 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();
@@ -102,16 +140,15 @@ public void withValidConfig() throws Exception {
@Test(expected = CliException.class)
public void processArgsMissing() throws Exception {
- cliAdapter.execute("-configfile");
+ cliDelegate.execute("-configfile");
}
@Test
public void withConstraintViolations() throws Exception {
-
Path configFile = createAndPopulatePaths(getClass().getResource("/missing-config.json"));
try {
- cliAdapter.execute("-configfile", configFile.toString());
+ cliDelegate.execute("-configfile", configFile.toString());
failBecauseExceptionWasNotThrown(ConstraintViolationException.class);
} catch (ConstraintViolationException ex) {
assertThat(ex.getConstraintViolations()).isNotEmpty();
@@ -141,16 +178,16 @@ public void keygenWithConfig() throws Exception {
Map params = new HashMap<>();
params.put("unixSocketPath", unixSocketPath.toString());
- Path configFilePath = ElUtil.createTempFileFromTemplate(getClass().getResource("/keygen-sample.json"), params);
+ Path configFilePath = ElUtil.createTempFileFromTemplate(getClass().getResource("/sample-config.json"), params);
CliResult result =
- cliAdapter.execute(
+ 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()).isFalse();
+ assertThat(result.isSuppressStartup()).isTrue();
verify(keyGenerator).generate(anyString(), eq(null), eq(null));
verifyNoMoreInteractions(keyGenerator);
@@ -159,32 +196,23 @@ public void keygenWithConfig() throws Exception {
@Test
public void keygenThenExit() throws Exception {
- final CliResult result = cliAdapter.execute("-keygen", "--encryptor.type", "NACL");
+ final CliResult result = cliDelegate.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() {
+ public void noConfigfileOption() {
- final Throwable throwable = catchThrowable(() -> cliAdapter.execute("-output", "bogus"));
+ final Throwable throwable = catchThrowable(() -> cliDelegate.execute("--pidfile", "bogus"));
assertThat(throwable)
.isInstanceOf(CliException.class)
- .hasMessage("One or more: -configfile or -keygen or -updatepassword options are required.");
+ .hasMessage("Missing required option '--configfile '");
}
+ // TODO (cjh) remove ignore once implemented
+ @Ignore
@Test
public void output() throws Exception {
@@ -212,7 +240,6 @@ public void output() throws Exception {
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");
@@ -222,21 +249,27 @@ public void output() throws Exception {
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());
+ cliDelegate.execute(
+ "-keygen",
+ "-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());
+ 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);
@@ -249,7 +282,7 @@ public void dynOption() throws Exception {
Path configFile = createAndPopulatePaths(getClass().getResource("/sample-config.json"));
- CliResult result = cliAdapter.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();
@@ -257,7 +290,7 @@ public void dynOption() throws Exception {
assertThat(result.getConfig().get().getJdbcConfig().getPassword()).isEqualTo("tiger");
}
- @Ignore
+ @Test
public void withInvalidPath() throws Exception {
// unixSocketPath
Map params = new HashMap<>();
@@ -268,13 +301,13 @@ public void withInvalidPath() throws Exception {
ElUtil.createTempFileFromTemplate(getClass().getResource("/sample-config-invalidpath.json"), params);
try {
- cliAdapter.execute("-configfile", configFile.toString());
+ cliDelegate.execute("-configfile", configFile.toString());
failBecauseExceptionWasNotThrown(ConstraintViolationException.class);
} catch (ConstraintViolationException ex) {
assertThat(ex.getConstraintViolations())
- .hasSize(1)
+ .hasSize(2)
.extracting("messageTemplate")
- .containsExactly("{UnsupportedKeyPair.message}");
+ .containsExactly("File does not exist", "File does not exist");
}
}
@@ -289,13 +322,13 @@ public void withEmptyConfigOverrideAll() throws Exception {
Files.write(configFile, "{}".getBytes());
try {
CliResult result =
- cliAdapter.execute(
+ 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);
@@ -312,7 +345,12 @@ public void overrideAlwaysSendTo() throws Exception {
Path configFile = createAndPopulatePaths(getClass().getResource("/sample-config.json"));
CliResult result = null;
try {
- result = cliAdapter.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());
}
@@ -329,13 +367,10 @@ 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");
+ cliDelegate.execute(
+ "-configfile", configFile.toString(),
+ "-o", "peer[2].url=anotherpeer",
+ "--override", "peer[3].url=yetanotherpeer");
assertThat(result).isNotNull();
assertThat(result.getConfig()).isPresent();
@@ -360,7 +395,7 @@ public void updatingPasswordsDoesntProcessOtherOptions() throws Exception {
Files.write(key, JaxbUtil.marshalToString(startingKey).getBytes());
final CliResult result =
- cliAdapter.execute(
+ cliDelegate.execute(
"-updatepassword",
"--keys.keyData.privateKeyPath",
key.toString(),
@@ -375,13 +410,13 @@ public void updatingPasswordsDoesntProcessOtherOptions() throws Exception {
@Test
public void suppressStartupForKeygenOption() throws Exception {
- final CliResult cliResult = cliAdapter.execute("-keygen", "--encryptor.type", "NACL");
+ final CliResult cliResult = cliDelegate.execute("-keygen", "--encryptor.type", "NACL");
assertThat(cliResult.isSuppressStartup()).isTrue();
}
@Test
- public void allowStartupForKeygenAndConfigfileOptions() throws Exception {
+ public void suppressStartupForKeygenOptionWithConfigfile() throws Exception {
final KeyGenerator keyGenerator = MockKeyGeneratorFactory.getMockKeyGenerator();
Path publicKeyPath = Files.createTempFile(UUID.randomUUID().toString(), "");
Path privateKeyPath = Files.createTempFile(UUID.randomUUID().toString(), "");
@@ -394,31 +429,83 @@ public void allowStartupForKeygenAndConfigfileOptions() throws Exception {
final Path configFile = createAndPopulatePaths(getClass().getResource("/sample-config.json"));
- final CliResult cliResult = cliAdapter.execute("-keygen", "-configfile", configFile.toString());
+ final CliResult cliResult = cliDelegate.execute("-keygen", "-configfile", configFile.toString());
- assertThat(cliResult.isSuppressStartup()).isFalse();
+ assertThat(cliResult.isSuppressStartup()).isTrue();
}
@Test
- public void suppressStartupForKeygenAndVaultUrlAndConfigfileOptions() throws Exception {
- final KeyGenerator keyGenerator = MockKeyGeneratorFactory.getMockKeyGenerator();
+ public void subcommandExceptionIsThrown() {
+ Throwable ex = catchThrowable(() -> cliDelegate.execute("-keygen", "-keygenvaulturl", "urlButNoVaultType"));
- final FilesystemKeyPair keypair = new FilesystemKeyPair(Paths.get(""), Paths.get(""), null);
- when(keyGenerator.generate(anyString(), eq(null), eq(null))).thenReturn(keypair);
+ assertThat(ex).isNotNull();
+ assertThat(ex).isInstanceOf(CliException.class);
+ }
- final Path configFile = createAndPopulatePaths(getClass().getResource("/sample-config.json"));
- final String vaultUrl = "https://test.vault.azure.net";
+ @Test
+ public void withValidConfigAndJdbcOveride() throws Exception {
- final CliResult cliResult =
- cliAdapter.execute(
- "-keygen",
- "-keygenvaulttype",
- "AZURE",
- "-keygenvaulturl",
- vaultUrl,
- "-configfile",
- configFile.toString());
+ Path configFile = createAndPopulatePaths(getClass().getResource("/sample-config.json"));
+ CliResult result = cliDelegate.execute("-configfile", configFile.toString(), "-jdbc.autoCreateTables", "true");
- assertThat(cliResult.isSuppressStartup()).isTrue();
+ 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();
+
+ }
+
+ @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");
}
}
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/admin-cli/src/test/java/com/quorum/tessera/admin/cli/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/admin/cli/AdminCliAdapterTest.java
rename to cli/config-cli/src/test/java/com/quorum/tessera/config/cli/admin/AdminCliAdapterTest.java
index 6c378fc2cb..43c8f2a9a3 100644
--- a/cli/admin-cli/src/test/java/com/quorum/tessera/admin/cli/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.admin.cli;
+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/admin/cli/subcommands/AddPeerCommandTest.java b/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/admin/subcommands/AddPeerCommandTest.java
similarity index 98%
rename from cli/admin-cli/src/test/java/com/quorum/tessera/admin/cli/subcommands/AddPeerCommandTest.java
rename to cli/config-cli/src/test/java/com/quorum/tessera/config/cli/admin/subcommands/AddPeerCommandTest.java
index 5869dd7238..f012fe87e1 100644
--- a/cli/admin-cli/src/test/java/com/quorum/tessera/admin/cli/subcommands/AddPeerCommandTest.java
+++ b/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/admin/subcommands/AddPeerCommandTest.java
@@ -1,4 +1,4 @@
-package com.quorum.tessera.admin.cli.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/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 b439f1cb27..0000000000
--- a/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/parsers/EncryptorConfigParserTest.java
+++ /dev/null
@@ -1,133 +0,0 @@
-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.*;
-
-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 elipticalCurveNoPropertiesDefined() 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 elipticalCurveWithDefinedProperties() 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));
- }
-}
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 00e1c283f5..0000000000
--- a/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/parsers/KeyGenerationParserTest.java
+++ /dev/null
@@ -1,378 +0,0 @@
-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 {
-
- 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 69b5b95fba..0000000000
--- a/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/parsers/KeyUpdateParserTest.java
+++ /dev/null
@@ -1,336 +0,0 @@
-package com.quorum.tessera.config.cli.parsers;
-
-import com.quorum.tessera.config.*;
-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.*;
-
-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/pom.xml b/cli/pom.xml
index 02010d98fc..520fdb2eea 100644
--- a/cli/pom.xml
+++ b/cli/pom.xml
@@ -5,7 +5,6 @@
cli-api
config-cli
- admin-cli
@@ -19,11 +18,6 @@
-
- commons-cli
- commons-cli
-
-
com.jpmorgan.quorum
config
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..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
@@ -1,8 +1,10 @@
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.Config;
+import picocli.CommandLine;
public class Main {
@@ -11,8 +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 {
- final CliResult result = CliDelegate.instance().execute(args);
- System.exit(result.getStatus());
+ final CommandLine commandLine = new CommandLine(new LegacyCliAdapter());
+ 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(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
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 ea6360fb6d..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
@@ -1,17 +1,18 @@
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.CommunicationType;
import com.quorum.tessera.config.Config;
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,18 @@ 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);
+
+ final CommandLine commandLine = new CommandLine(new EnclaveCliAdapter());
+ commandLine
+ .registerConverter(Config.class, new ConfigConverter())
+ .setSeparator(" ")
+ .setCaseInsensitiveEnumValuesAllowed(true);
+
+ commandLine.execute(args);
+ final CliResult cliResult = commandLine.getExecutionResult();
+
if (!cliResult.getConfig().isPresent()) {
System.exit(cliResult.getStatus());
}
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);
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/key-generation/pom.xml b/key-generation/pom.xml
index e65ab10414..af01ff16d7 100644
--- a/key-generation/pom.xml
+++ b/key-generation/pom.xml
@@ -24,6 +24,14 @@
com.jpmorgan.quorum
key-vault-api
+
+ info.picocli
+ picocli
+
+
+ com.jpmorgan.quorum
+ cli-api
+
diff --git a/pom.xml b/pom.xml
index d985592aa8..aa240f8dd5 100644
--- a/pom.xml
+++ b/pom.xml
@@ -56,6 +56,7 @@
github
7.0
1.9.3
+ 4.0.4
@@ -515,11 +516,6 @@
0.11-SNAPSHOT
-
- com.jpmorgan.quorum
- admin-cli
- 0.11-SNAPSHOT
-
com.jpmorgan.quorum
@@ -694,6 +690,24 @@
0.11-SNAPSHOT
+
+ com.jpmorgan.quorum
+ grpc-server
+ 0.11-SNAPSHOT
+
+
+
+ com.jpmorgan.quorum
+ grpc
+ 0.11-SNAPSHOT
+
+
+
+ com.jpmorgan.quorum
+ grpc-api
+ 0.11-SNAPSHOT
+
+
com.jpmorgan.quorum
@@ -968,12 +982,6 @@
1.5.4
-
- commons-cli
- commons-cli
- 1.4
-
-
org.apache.commons
commons-lang3
@@ -986,14 +994,12 @@
1.2.2
-
com.github.stefanbirkner
system-rules
1.18.0
-
javax.xml.bind
jaxb-api
@@ -1049,18 +1055,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
@@ -1093,7 +1102,6 @@
${jetty.version}
-
com.github.jnr
jnr-constants
@@ -1131,7 +1139,6 @@
native
-
org.glassfish.grizzly
grizzly-http-server
@@ -1139,7 +1146,6 @@
test
-
@@ -1172,7 +1178,6 @@
2.7
-
commons-codec
commons-codec
@@ -1257,13 +1262,17 @@
${asm.version}
-
org.jasypt
jasypt
${jasypt.version}
+
+ info.picocli
+ picocli
+ ${picocli.version}
+
@@ -1301,7 +1310,7 @@
system-rules
test
-
+
com.google.code.gson
gson
@@ -1339,8 +1348,6 @@
test
-
-
diff --git a/settings.gradle b/settings.gradle
index d221a6997d..0c00259abc 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -3,7 +3,6 @@ include(':argon2')
include(':config')
include(':cli:cli-api')
include(':cli:config-cli')
-include(':cli:admin-cli')
include(':cli')
include(':tests:acceptance-test')
include(':tests:test-util')
@@ -58,7 +57,6 @@ include(':tessera-jaxrs')
include(':tessera-data')
project(':cli:cli-api').projectDir = file('cli/cli-api')
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-core/pom.xml b/tessera-core/pom.xml
index 5bb9f8a20f..4df29ab5cc 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,14 @@
test
jar
-
+
com.jpmorgan.quorum
mock-service-locator
test
jar
-
+
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..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.cli.CliDelegate;
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,13 +25,14 @@ public class CoreIT {
@BeforeClass
public static void onSetup() throws Exception {
String configPath = CoreIT.class.getResource("/config1.json").getPath();
- CliDelegate.INSTANCE.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/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 5695fa2884..bf776c1faf 100644
--- a/tessera-dist/tessera-launcher/build.gradle
+++ b/tessera-dist/tessera-launcher/build.gradle
@@ -2,6 +2,7 @@
dependencies {
compile project(':config')
compile project(':cli:cli-api')
+ 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 f464b8cda8..cf1f5f5e76 100644
--- a/tessera-dist/tessera-launcher/pom.xml
+++ b/tessera-dist/tessera-launcher/pom.xml
@@ -19,11 +19,17 @@
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 b3d60e81fe..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,6 +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.config.cli.PicoCliDelegate;
import com.quorum.tessera.server.TesseraServer;
import com.quorum.tessera.server.TesseraServerFactory;
import org.apache.commons.lang3.exception.ExceptionUtils;
@@ -26,12 +27,14 @@ 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");
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));
if (cliResult.isSuppressStartup()) {
System.exit(0);
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
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/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",
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());