diff --git a/config-cli/src/test/java/com/quorum/tessera/config/cli/OverrideUtilTest.java b/config-cli/src/test/java/com/quorum/tessera/config/cli/OverrideUtilTest.java index 8eb1861e20..bcc41e8e73 100644 --- a/config-cli/src/test/java/com/quorum/tessera/config/cli/OverrideUtilTest.java +++ b/config-cli/src/test/java/com/quorum/tessera/config/cli/OverrideUtilTest.java @@ -79,7 +79,8 @@ public void buildOptions() { "unixSocketFile", "useWhiteList", "server.sslConfig.clientTrustCertificates", - "server.sslConfig.serverTrustCertificates" + "server.sslConfig.serverTrustCertificates", + "disablePeerDiscovery" ); final Map results = OverrideUtil.buildConfigOptions(); @@ -304,7 +305,7 @@ public void convertTo() { @Test public void initialiseNestedObjects() { - Config config = new Config(null, null, null, null, null, null, true); + Config config = new Config(null, null, null, null, null, null, true,true); OverrideUtil.initialiseNestedObjects(config); @@ -315,6 +316,7 @@ public void initialiseNestedObjects() { assertThat(config.getKeys()).isNotNull(); assertThat(config.getPeers()).isEmpty(); assertThat(config.getAlwaysSendTo()).isEmpty(); + assertThat(config.isDisablePeerDiscovery()).isTrue(); } diff --git a/config-migration/src/main/java/com/quorum/tessera/config/builder/ConfigBuilder.java b/config-migration/src/main/java/com/quorum/tessera/config/builder/ConfigBuilder.java index 7b26f8c441..0d823897b2 100644 --- a/config-migration/src/main/java/com/quorum/tessera/config/builder/ConfigBuilder.java +++ b/config-migration/src/main/java/com/quorum/tessera/config/builder/ConfigBuilder.java @@ -284,7 +284,7 @@ public Config build() { forwardingKeys = Collections.emptyList(); } - return new Config(jdbcConfig, serverConfig, peerList, keyData, forwardingKeys, toPath(workDir, unixSocketFile), useWhiteList); + return new Config(jdbcConfig, serverConfig, peerList, keyData, forwardingKeys, toPath(workDir, unixSocketFile), useWhiteList,false); } } diff --git a/config/src/main/java/com/quorum/tessera/config/Config.java b/config/src/main/java/com/quorum/tessera/config/Config.java index dca376a3c3..57df24cce5 100644 --- a/config/src/main/java/com/quorum/tessera/config/Config.java +++ b/config/src/main/java/com/quorum/tessera/config/Config.java @@ -56,13 +56,17 @@ public class Config extends ConfigItem { @XmlAttribute private final boolean useWhiteList; + @XmlAttribute + private final boolean disablePeerDiscovery; + public Config(final JdbcConfig jdbcConfig, final ServerConfig serverConfig, final List peers, final KeyConfiguration keyConfiguration, final List alwaysSendTo, final Path unixSocketFile, - final boolean useWhiteList) { + final boolean useWhiteList, + final boolean disablePeerDiscovery) { this.jdbcConfig = jdbcConfig; this.serverConfig = serverConfig; this.peers = peers; @@ -70,6 +74,7 @@ public Config(final JdbcConfig jdbcConfig, this.alwaysSendTo = alwaysSendTo; this.unixSocketFile = unixSocketFile; this.useWhiteList = useWhiteList; + this.disablePeerDiscovery = disablePeerDiscovery; } private static Config create() { @@ -77,7 +82,7 @@ private static Config create() { } private Config() { - this(null, null, null, null, null, null, false); + this(null, null, null, null, null, null, false,false); } public JdbcConfig getJdbcConfig() { @@ -108,4 +113,8 @@ public boolean isUseWhiteList() { return this.useWhiteList; } + public boolean isDisablePeerDiscovery() { + return disablePeerDiscovery; + } + } diff --git a/config/src/main/java/com/quorum/tessera/config/JaxbConfigFactory.java b/config/src/main/java/com/quorum/tessera/config/JaxbConfigFactory.java index 34e866c219..38de05ed03 100644 --- a/config/src/main/java/com/quorum/tessera/config/JaxbConfigFactory.java +++ b/config/src/main/java/com/quorum/tessera/config/JaxbConfigFactory.java @@ -20,9 +20,9 @@ public class JaxbConfigFactory implements ConfigFactory { private static final Set NEW_PASSWORD_FILE_PERMS = Stream - .of(PosixFilePermission.OWNER_READ, PosixFilePermission.OWNER_WRITE) - .collect(Collectors.toSet()); - + .of(PosixFilePermission.OWNER_READ, PosixFilePermission.OWNER_WRITE) + .collect(Collectors.toSet()); + @Override public Config create(final InputStream configData, final List newKeys) { @@ -33,12 +33,12 @@ public Config create(final InputStream configData, final List newKeys) if (Objects.nonNull(config.getKeys()) && !newKeys.isEmpty()) { try { final List newPasswords = newKeys - .stream() - .map(KeyData::getConfig) - .map(KeyDataConfig::getPassword) - .map(Optional::ofNullable) - .map(pass -> pass.orElse("")) - .collect(Collectors.toList()); + .stream() + .map(KeyData::getConfig) + .map(KeyDataConfig::getPassword) + .map(Optional::ofNullable) + .map(pass -> pass.orElse("")) + .collect(Collectors.toList()); if (config.getKeys().getPasswords() != null) { config.getKeys().getPasswords().addAll(newPasswords); @@ -47,11 +47,11 @@ public Config create(final InputStream configData, final List newKeys) Files.write(config.getKeys().getPasswordFile(), newPasswords, APPEND); } else if (!newPasswords.stream().allMatch(""::equals)) { final List existingPasswords = config - .getKeys() - .getKeyData() - .stream() - .map(k -> "") - .collect(Collectors.toList()); + .getKeys() + .getKeyData() + .stream() + .map(k -> "") + .collect(Collectors.toList()); existingPasswords.addAll(newPasswords); this.createFile(Paths.get("passwords.txt")); @@ -66,16 +66,17 @@ public Config create(final InputStream configData, final List newKeys) } - if(createdNewPasswordFile) { + if (createdNewPasswordFile) { //return a new object with the password file set return new Config( - config.getJdbcConfig(), - config.getServerConfig(), - config.getPeers(), - new KeyConfiguration(Paths.get("passwords.txt"), null, config.getKeys().getKeyData()), - config.getAlwaysSendTo(), - config.getUnixSocketFile(), - config.isUseWhiteList() + config.getJdbcConfig(), + config.getServerConfig(), + config.getPeers(), + new KeyConfiguration(Paths.get("passwords.txt"), null, config.getKeys().getKeyData()), + config.getAlwaysSendTo(), + config.getUnixSocketFile(), + config.isUseWhiteList(), + config.isDisablePeerDiscovery() ); } else { //leave config untouched since it wasn't needed to make a new one @@ -86,7 +87,7 @@ public Config create(final InputStream configData, final List newKeys) //create a file if it doesn't exist and set the permissions to be only // read/write for the creator private void createFile(final Path fileToMake) throws IOException { - if(Files.notExists(fileToMake)) { + if (Files.notExists(fileToMake)) { Files.createFile(fileToMake); Files.setPosixFilePermissions(fileToMake, NEW_PASSWORD_FILE_PERMS); } diff --git a/config/src/test/java/com/quorum/tessera/config/ValidationTest.java b/config/src/test/java/com/quorum/tessera/config/ValidationTest.java index 4a9cf5c2f0..82464b4d1c 100644 --- a/config/src/test/java/com/quorum/tessera/config/ValidationTest.java +++ b/config/src/test/java/com/quorum/tessera/config/ValidationTest.java @@ -101,7 +101,7 @@ public void invalidAlwaysSendTo() { List alwaysSendTo = Arrays.asList("BOGUS"); - Config config = new Config(null, null, null, null, alwaysSendTo, null, false); + Config config = new Config(null, null, null, null, alwaysSendTo, null, false,false); Set> violations = validator.validateProperty(config, "alwaysSendTo"); @@ -120,7 +120,7 @@ public void validAlwaysSendTo() { List alwaysSendTo = Arrays.asList(value); - Config config = new Config(null, null, null, null, alwaysSendTo, null, false); + Config config = new Config(null, null, null, null, alwaysSendTo, null, false,false); Set> violations = validator.validateProperty(config, "alwaysSendTo"); diff --git a/grpc-service/src/main/java/com/quorum/tessera/api/grpc/StreamObserverTemplate.java b/grpc-service/src/main/java/com/quorum/tessera/api/grpc/StreamObserverTemplate.java index 9edfcbd629..5ee50f53f7 100644 --- a/grpc-service/src/main/java/com/quorum/tessera/api/grpc/StreamObserverTemplate.java +++ b/grpc-service/src/main/java/com/quorum/tessera/api/grpc/StreamObserverTemplate.java @@ -1,5 +1,6 @@ package com.quorum.tessera.api.grpc; +import com.quorum.tessera.node.AutoDiscoveryDisabledException; import io.grpc.stub.StreamObserver; import java.util.Objects; import javax.validation.ConstraintViolationException; @@ -23,6 +24,9 @@ public void handle(StreamObserverCallback callback) { observer.onNext(r); observer.onCompleted(); + } catch(AutoDiscoveryDisabledException ex) { + observer.onError(io.grpc.Status.PERMISSION_DENIED + .withDescription(ex.getMessage()).asRuntimeException()); } catch (ConstraintViolationException validationError) { observer.onError(io.grpc.Status.INVALID_ARGUMENT .withCause(validationError) diff --git a/grpc-service/src/test/java/com/quorum/tessera/api/grpc/StreamObserverTemplateTest.java b/grpc-service/src/test/java/com/quorum/tessera/api/grpc/StreamObserverTemplateTest.java index e567f43c87..6decf70e1d 100644 --- a/grpc-service/src/test/java/com/quorum/tessera/api/grpc/StreamObserverTemplateTest.java +++ b/grpc-service/src/test/java/com/quorum/tessera/api/grpc/StreamObserverTemplateTest.java @@ -1,5 +1,6 @@ package com.quorum.tessera.api.grpc; +import com.quorum.tessera.node.AutoDiscoveryDisabledException; import io.grpc.Status; import io.grpc.StatusRuntimeException; import io.grpc.stub.StreamObserver; @@ -15,6 +16,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; public class StreamObserverTemplateTest { @@ -76,7 +78,6 @@ public void executeValidationError() { @Test public void executeOtherError() { - Throwable exception = new Throwable("OUCH"); template.handle(() -> { @@ -87,4 +88,34 @@ public void executeOtherError() { verify(observer).onError(exception); } + + + @Test + public void executeAutoDiscoveryDisabled() { + + List results = new ArrayList<>(); + doAnswer((iom) -> { + results.add(iom.getArgument(0)); + return null; + }).when(observer) + .onError(any(StatusRuntimeException.class)); + + final String exceptionMessage = "Sorry Dave I cant let you do that"; + + AutoDiscoveryDisabledException exception = mock(AutoDiscoveryDisabledException.class); + when(exception.getMessage()).thenReturn(exceptionMessage); + + + template.handle(() -> { + throw exception; + }); + + StatusRuntimeException result = results.stream().findAny().get(); + + assertThat(result.getStatus().getCode()).isEqualTo(Status.PERMISSION_DENIED.getCode()); + + assertThat(result.getStatus().getDescription()).isEqualTo(exceptionMessage); + + verify(observer).onError(result); + } } diff --git a/jaxrs-service/src/main/java/com/quorum/tessera/api/exception/AutoDiscoveryDisabledExceptionMapper.java b/jaxrs-service/src/main/java/com/quorum/tessera/api/exception/AutoDiscoveryDisabledExceptionMapper.java new file mode 100644 index 0000000000..2c7cafb3f0 --- /dev/null +++ b/jaxrs-service/src/main/java/com/quorum/tessera/api/exception/AutoDiscoveryDisabledExceptionMapper.java @@ -0,0 +1,18 @@ +package com.quorum.tessera.api.exception; + +import com.quorum.tessera.node.AutoDiscoveryDisabledException; +import javax.ws.rs.core.Response; +import javax.ws.rs.ext.ExceptionMapper; +import javax.ws.rs.ext.Provider; + +@Provider +public class AutoDiscoveryDisabledExceptionMapper implements ExceptionMapper { + + @Override + public Response toResponse(AutoDiscoveryDisabledException exception) { + return Response.status(Response.Status.FORBIDDEN) + .entity(exception.getMessage()) + .build(); + } + +} diff --git a/jaxrs-service/src/test/java/com/quorum/tessera/api/exception/AutoDiscoveryDisabledExceptionMapperTest.java b/jaxrs-service/src/test/java/com/quorum/tessera/api/exception/AutoDiscoveryDisabledExceptionMapperTest.java new file mode 100644 index 0000000000..7c4e8905ff --- /dev/null +++ b/jaxrs-service/src/test/java/com/quorum/tessera/api/exception/AutoDiscoveryDisabledExceptionMapperTest.java @@ -0,0 +1,30 @@ +package com.quorum.tessera.api.exception; + +import com.quorum.tessera.node.AutoDiscoveryDisabledException; +import javax.ws.rs.core.Response; +import static org.assertj.core.api.Assertions.assertThat; +import org.junit.Before; +import org.junit.Test; + +public class AutoDiscoveryDisabledExceptionMapperTest { + + private AutoDiscoveryDisabledExceptionMapper mapper; + + @Before + public void onSetUp() { + mapper = new AutoDiscoveryDisabledExceptionMapper(); + } + + @Test + public void handleAutoDiscoveryDisabledException() { + String message = ".. all outta gum"; + AutoDiscoveryDisabledException exception = + new AutoDiscoveryDisabledException(message); + + Response result = mapper.toResponse(exception); + + assertThat(result.getStatus()).isEqualTo(403); + assertThat(result.getEntity()).isEqualTo(message); + } + +} diff --git a/jaxrs-service/src/test/java/com/quorum/tessera/jaxrs/JaxrsITConfig.java b/jaxrs-service/src/test/java/com/quorum/tessera/jaxrs/JaxrsITConfig.java index 36895b1ded..c0b5cdbd2c 100644 --- a/jaxrs-service/src/test/java/com/quorum/tessera/jaxrs/JaxrsITConfig.java +++ b/jaxrs-service/src/test/java/com/quorum/tessera/jaxrs/JaxrsITConfig.java @@ -45,7 +45,7 @@ public Config config() { Path unixSocketFile = mock(Path.class); - Config config = new Config(jdbcConfig,serverConfig,Collections.EMPTY_LIST,keyConfiguration,Collections.EMPTY_LIST,unixSocketFile,false); + Config config = new Config(jdbcConfig,serverConfig,Collections.EMPTY_LIST,keyConfiguration,Collections.EMPTY_LIST,unixSocketFile,false,false); return config; } diff --git a/tessera-core/src/main/java/com/quorum/tessera/core/config/ConfigService.java b/tessera-core/src/main/java/com/quorum/tessera/core/config/ConfigService.java new file mode 100644 index 0000000000..75607ec1d1 --- /dev/null +++ b/tessera-core/src/main/java/com/quorum/tessera/core/config/ConfigService.java @@ -0,0 +1,9 @@ +package com.quorum.tessera.core.config; + +import com.quorum.tessera.config.Config; + +public interface ConfigService { + + Config getConfig(); + +} diff --git a/tessera-core/src/main/java/com/quorum/tessera/core/config/ConfigServiceImpl.java b/tessera-core/src/main/java/com/quorum/tessera/core/config/ConfigServiceImpl.java new file mode 100644 index 0000000000..4dab98c8b0 --- /dev/null +++ b/tessera-core/src/main/java/com/quorum/tessera/core/config/ConfigServiceImpl.java @@ -0,0 +1,20 @@ +package com.quorum.tessera.core.config; + +import com.quorum.tessera.config.Config; +import java.util.Objects; + + +public class ConfigServiceImpl implements ConfigService { + + private final Config config; + + public ConfigServiceImpl(Config initialConfig) { + this.config = Objects.requireNonNull(initialConfig); + } + + @Override + public Config getConfig() { + return config; + } + +} diff --git a/tessera-core/src/main/java/com/quorum/tessera/node/AutoDiscoveryDisabledException.java b/tessera-core/src/main/java/com/quorum/tessera/node/AutoDiscoveryDisabledException.java new file mode 100644 index 0000000000..4fa228db97 --- /dev/null +++ b/tessera-core/src/main/java/com/quorum/tessera/node/AutoDiscoveryDisabledException.java @@ -0,0 +1,11 @@ +package com.quorum.tessera.node; + +import com.quorum.tessera.exception.TesseraException; + +public class AutoDiscoveryDisabledException extends TesseraException { + + public AutoDiscoveryDisabledException(String message) { + super(message); + } + +} diff --git a/tessera-core/src/main/java/com/quorum/tessera/node/PartyInfoServiceImpl.java b/tessera-core/src/main/java/com/quorum/tessera/node/PartyInfoServiceImpl.java index 977ae6e3c5..3242e10ccc 100644 --- a/tessera-core/src/main/java/com/quorum/tessera/node/PartyInfoServiceImpl.java +++ b/tessera-core/src/main/java/com/quorum/tessera/node/PartyInfoServiceImpl.java @@ -2,6 +2,7 @@ import com.quorum.tessera.config.Config; import com.quorum.tessera.config.Peer; +import com.quorum.tessera.core.config.ConfigService; import com.quorum.tessera.key.KeyManager; import com.quorum.tessera.key.exception.KeyNotFoundException; import com.quorum.tessera.nacl.Key; @@ -13,18 +14,27 @@ import java.util.HashSet; import java.util.Objects; import java.util.Set; +import java.util.function.Predicate; import static java.util.stream.Collectors.toSet; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class PartyInfoServiceImpl implements PartyInfoService { private final PartyInfoStore partyInfoStore; + private final ConfigService configService; + + private static final Logger LOGGER = LoggerFactory.getLogger(PartyInfoServiceImpl.class); + public PartyInfoServiceImpl(final PartyInfoStore partyInfoStore, - final Config configuration, + final ConfigService configService, final KeyManager keyManager) { this.partyInfoStore = Objects.requireNonNull(partyInfoStore); - + this.configService = Objects.requireNonNull(configService); + + final Config configuration = configService.getConfig(); final String advertisedUrl = configuration.getServerConfig().getServerUri().toString(); final Set initialParties = configuration @@ -50,6 +60,26 @@ public PartyInfo getPartyInfo() { @Override public PartyInfo updatePartyInfo(final PartyInfo partyInfo) { + + if(configService.getConfig().isDisablePeerDiscovery()) { + + PartyInfo currentPartyInfo = getPartyInfo(); + + if(!Objects.equals(currentPartyInfo.getParties(), partyInfo.getParties())) { + final String message = String.format("Parties found in party info from %s are different.", partyInfo.getUrl()); + throw new AutoDiscoveryDisabledException(message); + } + + Predicate matchUrl = u -> Objects.equals(u, partyInfo.getUrl()); + + if(!currentPartyInfo.getParties().stream() + .map(Party::getUrl) + .anyMatch(matchUrl)) { + final String message = String.format("Peer %s not found in known peer list", partyInfo.getUrl()); + throw new AutoDiscoveryDisabledException(message); + } + + } partyInfoStore.store(partyInfo); diff --git a/tessera-core/src/main/resources/tessera-core-spring.xml b/tessera-core/src/main/resources/tessera-core-spring.xml index 8deb5d1623..5cb06afb38 100644 --- a/tessera-core/src/main/resources/tessera-core-spring.xml +++ b/tessera-core/src/main/resources/tessera-core-spring.xml @@ -39,9 +39,7 @@ - - - + @@ -69,15 +67,13 @@ - + - - - + @@ -139,6 +135,11 @@ + + + + + diff --git a/tessera-core/src/test/java/com/quorum/tessera/core/config/ConfigServiceTest.java b/tessera-core/src/test/java/com/quorum/tessera/core/config/ConfigServiceTest.java new file mode 100644 index 0000000000..ac87f27de7 --- /dev/null +++ b/tessera-core/src/test/java/com/quorum/tessera/core/config/ConfigServiceTest.java @@ -0,0 +1,34 @@ + +package com.quorum.tessera.core.config; + +import com.quorum.tessera.config.Config; +import static org.assertj.core.api.Assertions.assertThat; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import static org.mockito.Mockito.*; + + +public class ConfigServiceTest { + + private ConfigService configService; + + private Config config; + + @Before + public void onSetUp() { + config = mock(Config.class); + configService = new ConfigServiceImpl(config); + } + + @After + public void onTearDown() { + verifyNoMoreInteractions(config); + } + + @Test + public void getConfig() { + assertThat(configService.getConfig()).isSameAs(config); + } + +} diff --git a/tessera-core/src/test/java/com/quorum/tessera/node/PartyInfoServiceTest.java b/tessera-core/src/test/java/com/quorum/tessera/node/PartyInfoServiceTest.java index 4110993c1f..0da37a321d 100644 --- a/tessera-core/src/test/java/com/quorum/tessera/node/PartyInfoServiceTest.java +++ b/tessera-core/src/test/java/com/quorum/tessera/node/PartyInfoServiceTest.java @@ -3,6 +3,7 @@ import com.quorum.tessera.config.Config; import com.quorum.tessera.config.Peer; import com.quorum.tessera.config.ServerConfig; +import com.quorum.tessera.core.config.ConfigService; import com.quorum.tessera.key.KeyManager; import com.quorum.tessera.key.exception.KeyNotFoundException; import com.quorum.tessera.nacl.Key; @@ -21,10 +22,10 @@ import java.util.stream.Stream; import static java.util.Collections.*; +import java.util.stream.Collectors; import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toSet; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.catchThrowable; +import static org.assertj.core.api.Assertions.*; import static org.mockito.Mockito.*; public class PartyInfoServiceTest { @@ -37,6 +38,8 @@ public class PartyInfoServiceTest { private Config configuration; + private ConfigService configService; + private KeyManager keyManager; private PartyInfoService partyInfoService; @@ -47,30 +50,44 @@ public void onSetUp() { this.partyInfoStore = mock(PartyInfoStore.class); this.configuration = mock(Config.class); this.keyManager = mock(KeyManager.class); + this.configService = mock(ConfigService.class); + + when(configService.getConfig()).thenReturn(configuration); - final ServerConfig serverConfig = new ServerConfig("http://localhost", 8080, 50521, null, null,null, null); - doReturn(serverConfig).when(configuration).getServerConfig(); + final ServerConfig serverConfig = new ServerConfig("http://localhost", 8080, 50521, null, null, null, null); + + when(configuration.getServerConfig()).thenReturn(serverConfig); final Peer peer = new Peer("http://other-node.com:8080"); - doReturn(singletonList(peer)).when(configuration).getPeers(); + when(configuration.getPeers()).thenReturn(singletonList(peer)); final Set ourKeys = new HashSet<>( - Arrays.asList( - new Key("some-key".getBytes()), - new Key("another-public-key".getBytes()) - ) + Arrays.asList( + new Key("some-key".getBytes()), + new Key("another-public-key".getBytes()) + ) ); doReturn(ourKeys).when(keyManager).getPublicKeys(); + + this.partyInfoService = new PartyInfoServiceImpl(partyInfoStore, configService, keyManager); } @After public void after() { - verifyNoMoreInteractions(partyInfoStore, keyManager, configuration); + //Called in constructor + verify(keyManager).getPublicKeys(); + verify(configuration).getPeers(); + verify(configuration).getServerConfig(); + + verify(partyInfoStore, atLeast(1)).store(any(PartyInfo.class)); + + verifyNoMoreInteractions(partyInfoStore); + verifyNoMoreInteractions(keyManager); + verifyNoMoreInteractions(configuration); } @Test public void initialPartiesCorrectlyReadFromConfiguration() { - this.partyInfoService = new PartyInfoServiceImpl(partyInfoStore, configuration, keyManager); final PartyInfo partyInfo = new PartyInfo(URI, emptySet(), singleton(new Party("http://other-node.com:8080"))); doReturn(partyInfo).when(partyInfoStore).getPartyInfo(); @@ -85,9 +102,6 @@ public void initialPartiesCorrectlyReadFromConfiguration() { verify(partyInfoStore).store(any(PartyInfo.class)); verify(partyInfoStore, times(3)).getPartyInfo(); - verify(keyManager).getPublicKeys(); - verify(configuration).getPeers(); - verify(configuration).getServerConfig(); //TODO: add a captor for verification } @@ -95,8 +109,6 @@ public void initialPartiesCorrectlyReadFromConfiguration() { @Test public void registeringPublicKeysUsesOurUrl() { - this.partyInfoService = new PartyInfoServiceImpl(partyInfoStore, configuration, keyManager); - final ArgumentCaptor captor = ArgumentCaptor.forClass(PartyInfo.class); verify(partyInfoStore).store(captor.capture()); @@ -105,25 +117,23 @@ public void registeringPublicKeysUsesOurUrl() { verify(configuration).getServerConfig(); final List allRegisteredKeys = captor - .getAllValues() - .stream() - .map(PartyInfo::getRecipients) - .flatMap(Set::stream) - .collect(toList()); + .getAllValues() + .stream() + .map(PartyInfo::getRecipients) + .flatMap(Set::stream) + .collect(toList()); assertThat(allRegisteredKeys) - .hasSize(2) - .containsExactlyInAnyOrder( - new Recipient(new Key("some-key".getBytes()), URI), - new Recipient(new Key("another-public-key".getBytes()), URI) - ); + .hasSize(2) + .containsExactlyInAnyOrder( + new Recipient(new Key("some-key".getBytes()), URI), + new Recipient(new Key("another-public-key".getBytes()), URI) + ); } @Test public void updatePartyInfoDelegatesToStore() { - this.partyInfoService = new PartyInfoServiceImpl(partyInfoStore, configuration, keyManager); - final String secondParty = "http://other-node.com:8080"; final String thirdParty = "http://third-url.com:8080"; @@ -137,16 +147,12 @@ public void updatePartyInfoDelegatesToStore() { verify(partyInfoStore).store(thirdNodePartyInfo); verify(partyInfoStore, times(3)).store(any(PartyInfo.class)); verify(partyInfoStore, times(2)).getPartyInfo(); - verify(keyManager).getPublicKeys(); - verify(configuration).getPeers(); - verify(configuration).getServerConfig(); + verify(configuration, times(2)).isDisablePeerDiscovery(); } @Test public void getRecipientURLFromPartyInfoStore() { - this.partyInfoService = new PartyInfoServiceImpl(partyInfoStore, configuration, keyManager); - final Recipient recipient = new Recipient(new Key("key".getBytes()), "someurl"); final PartyInfo partyInfo = new PartyInfo(URI, singleton(recipient), emptySet()); doReturn(partyInfo).when(partyInfoStore).getPartyInfo(); @@ -154,11 +160,7 @@ public void getRecipientURLFromPartyInfoStore() { final String result = partyInfoService.getURLFromRecipientKey(new Key("key".getBytes())); assertThat(result).isEqualTo("someurl"); - verify(partyInfoStore).store(any(PartyInfo.class)); verify(partyInfoStore).getPartyInfo(); - verify(keyManager).getPublicKeys(); - verify(configuration).getPeers(); - verify(configuration).getServerConfig(); } @Test @@ -166,80 +168,109 @@ public void getRecipientURLFromPartyInfoStoreFailsIfKeyDoesntExist() { doReturn(new PartyInfo("", emptySet(), emptySet())).when(partyInfoStore).getPartyInfo(); - this.partyInfoService = new PartyInfoServiceImpl(partyInfoStore, configuration, keyManager); - final Key failingKey = new Key("otherKey".getBytes()); final Throwable throwable = catchThrowable(() -> partyInfoService.getURLFromRecipientKey(failingKey)); assertThat(throwable).isInstanceOf(KeyNotFoundException.class).hasMessage("Recipient not found"); - verify(partyInfoStore).store(any(PartyInfo.class)); verify(partyInfoStore).getPartyInfo(); - verify(keyManager).getPublicKeys(); - verify(configuration).getPeers(); - verify(configuration).getServerConfig(); } @Test public void diffPartyInfoReturnsFullSetOnEmptyStore() { doReturn(new PartyInfo("", emptySet(), emptySet())).when(partyInfoStore).getPartyInfo(); - this.partyInfoService = new PartyInfoServiceImpl(partyInfoStore, configuration, keyManager); - final PartyInfo incomingInfo = new PartyInfo("", emptySet(), NEW_PARTIES); final Set unsavedParties = this.partyInfoService.findUnsavedParties(incomingInfo); assertThat(unsavedParties) - .hasSize(2) - .containsExactlyInAnyOrder(NEW_PARTIES.toArray(new Party[0])); + .hasSize(2) + .containsExactlyInAnyOrder(NEW_PARTIES.toArray(new Party[0])); - verify(partyInfoStore).store(any(PartyInfo.class)); verify(partyInfoStore).getPartyInfo(); - verify(keyManager).getPublicKeys(); - verify(configuration).getPeers(); - verify(configuration).getServerConfig(); + } @Test public void diffPartyInfoReturnsEmptySetOnFullStore() { doReturn(new PartyInfo("", emptySet(), NEW_PARTIES)).when(partyInfoStore).getPartyInfo(); - this.partyInfoService = new PartyInfoServiceImpl(partyInfoStore, configuration, keyManager); - final PartyInfo incomingInfo = new PartyInfo("", emptySet(), NEW_PARTIES); final Set unsavedParties = this.partyInfoService.findUnsavedParties(incomingInfo); assertThat(unsavedParties).isEmpty(); - verify(partyInfoStore).store(any(PartyInfo.class)); verify(partyInfoStore).getPartyInfo(); - verify(keyManager).getPublicKeys(); - verify(configuration).getPeers(); - verify(configuration).getServerConfig(); + } @Test public void diffPartyInfoReturnsNodesNotInStore() { doReturn(new PartyInfo("", emptySet(), singleton(new Party("url1")))) - .when(partyInfoStore) - .getPartyInfo(); - - this.partyInfoService = new PartyInfoServiceImpl(partyInfoStore, configuration, keyManager); + .when(partyInfoStore) + .getPartyInfo(); final PartyInfo incomingInfo = new PartyInfo("", emptySet(), NEW_PARTIES); final Set unsavedParties = this.partyInfoService.findUnsavedParties(incomingInfo); assertThat(unsavedParties) - .hasSize(1) - .containsExactlyInAnyOrder(new Party("url2")); + .hasSize(1) + .containsExactlyInAnyOrder(new Party("url2")); - verify(partyInfoStore).store(any(PartyInfo.class)); verify(partyInfoStore).getPartyInfo(); - verify(keyManager).getPublicKeys(); - verify(configuration).getPeers(); - verify(configuration).getServerConfig(); + + } + + @Test + public void updatePartyInfoDelegatesToStoreAutoDiscoveryDisabled() { + + when(configuration.isDisablePeerDiscovery()).thenReturn(true); + + Set parties = Stream.of("MyURI", "MyOtherUri") + .map(Party::new).collect(Collectors.toSet()); + + PartyInfo partyInfo = new PartyInfo("MyURI", EMPTY_SET, parties); + + when(partyInfoStore.getPartyInfo()).thenReturn(partyInfo); + + PartyInfo forUpdate = new PartyInfo("UnknownUri", EMPTY_SET, parties); + try { + partyInfoService.updatePartyInfo(forUpdate); + failBecauseExceptionWasNotThrown(AutoDiscoveryDisabledException.class); + + } catch (AutoDiscoveryDisabledException ex) { + verify(configuration).isDisablePeerDiscovery(); + verify(partyInfoStore).getPartyInfo(); + } + } + + @Test + public void updatePartyInfoDelegatesToStoreAutoDiscoveryDisabledDifferentParties() { + + when(configuration.isDisablePeerDiscovery()).thenReturn(true); + + Set parties = Stream.of("MyURI", "MyOtherUri") + .map(Party::new).collect(Collectors.toSet()); + + PartyInfo partyInfo = new PartyInfo("MyURI", EMPTY_SET, parties); + + when(partyInfoStore.getPartyInfo()).thenReturn(partyInfo); + + Set otherParties = Stream.of("OtherURI", "OtherUri") + .map(Party::new).collect(Collectors.toSet()); + + PartyInfo forUpdate = new PartyInfo("MyOtherUri", EMPTY_SET, otherParties); + try { + partyInfoService.updatePartyInfo(forUpdate); + failBecauseExceptionWasNotThrown(AutoDiscoveryDisabledException.class); + + } catch (AutoDiscoveryDisabledException ex) { + verify(configuration).isDisablePeerDiscovery(); + verify(partyInfoStore).getPartyInfo(); + } + } }