From 38515ce7aefe5d856eb48a496235fbcb23f9ec78 Mon Sep 17 00:00:00 2001 From: Alva Swanson Date: Wed, 27 Dec 2023 13:13:38 +0100 Subject: [PATCH 1/3] Add JUnit MockitoExtension library --- .../commons/src/main/groovy/bisq.java-conventions.gradle | 3 ++- gradle/libs.versions.toml | 3 ++- gradle/verification-metadata.xml | 8 ++++++++ 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/build-logic/commons/src/main/groovy/bisq.java-conventions.gradle b/build-logic/commons/src/main/groovy/bisq.java-conventions.gradle index af2524a9aa..db97cf74f2 100644 --- a/build-logic/commons/src/main/groovy/bisq.java-conventions.gradle +++ b/build-logic/commons/src/main/groovy/bisq.java-conventions.gradle @@ -29,7 +29,8 @@ dependencies { testImplementation libs.hamcrest testImplementation libs.junit.jupiter.api testImplementation libs.junit.jupiter.params - testImplementation libs.mockito + testImplementation libs.mockito.core + testImplementation libs.mockito.junit.jupiter testRuntimeOnly libs.junit.jupiter.engine } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 4ec055967a..c8e62518c5 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -98,7 +98,8 @@ logback-core = { module = "ch.qos.logback:logback-core", version.ref = "logback" logback-classic = { module = "ch.qos.logback:logback-classic", version.ref = "logback" } lombok = { module = "org.projectlombok:lombok", version.ref = "lombok" } -mockito = { module = "org.mockito:mockito-core", version.ref = "mockito" } +mockito-core = { module = "org.mockito:mockito-core", version.ref = "mockito" } +mockito-junit-jupiter = { module = "org.mockito:mockito-junit-jupiter", version.ref = "mockito" } natpryce-make-it-easy = { module = "com.natpryce:make-it-easy", version.ref = "natpryce-make-it-easy" } netlayer-tor-external = { module = "com.github.bisq-network.netlayer:tor.external", version.ref = "netlayer" } diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index 5331554b92..4da86e93b9 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -1739,6 +1739,14 @@ + + + + + + + + From bf98531c46aae7b6d994252f565bb922461e308b Mon Sep 17 00:00:00 2001 From: Alva Swanson Date: Wed, 20 Dec 2023 15:45:42 +0100 Subject: [PATCH 2/3] Config: Make configFile field private and add getter --- .../src/main/java/bisq/common/config/Config.java | 5 ++++- .../java/bisq/common/config/ConfigTests.java | 16 ++++++++-------- .../java/bisq/core/filter/FilterManager.java | 2 +- .../settings/network/NetworkSettingsView.java | 2 +- 4 files changed, 14 insertions(+), 11 deletions(-) diff --git a/common/src/main/java/bisq/common/config/Config.java b/common/src/main/java/bisq/common/config/Config.java index d05be693f9..c1153f0467 100644 --- a/common/src/main/java/bisq/common/config/Config.java +++ b/common/src/main/java/bisq/common/config/Config.java @@ -28,6 +28,8 @@ import java.util.Optional; import ch.qos.logback.classic.Level; +import lombok.Getter; + import static com.google.common.base.Preconditions.checkNotNull; import static java.lang.String.format; import static java.util.stream.Collectors.toList; @@ -157,7 +159,8 @@ public class Config { // Options supported only at the command-line interface (cli) public final boolean helpRequested; - public final File configFile; + @Getter + private final File configFile; // Options supported on cmd line and in the config file public final String appName; diff --git a/common/src/test/java/bisq/common/config/ConfigTests.java b/common/src/test/java/bisq/common/config/ConfigTests.java index bd6b7f9c59..ab578d7018 100644 --- a/common/src/test/java/bisq/common/config/ConfigTests.java +++ b/common/src/test/java/bisq/common/config/ConfigTests.java @@ -53,7 +53,7 @@ public class ConfigTests { assertThat(config.appName, equalTo(config.defaultAppName)); assertThat(config.userDataDir, equalTo(config.defaultUserDataDir)); assertThat(config.appDataDir, equalTo(config.defaultAppDataDir)); - assertThat(config.configFile, equalTo(config.defaultConfigFile)); + assertThat(config.getConfigFile(), equalTo(config.defaultConfigFile)); } @Test @@ -62,7 +62,7 @@ public class ConfigTests { assertThat(config.appName, equalTo("My-Bisq")); assertThat(config.userDataDir, equalTo(config.defaultUserDataDir)); assertThat(config.appDataDir, equalTo(new File(config.userDataDir, "My-Bisq"))); - assertThat(config.configFile, equalTo(new File(config.appDataDir, DEFAULT_CONFIG_FILE_NAME))); + assertThat(config.getConfigFile(), equalTo(new File(config.appDataDir, DEFAULT_CONFIG_FILE_NAME))); } @Test @@ -72,7 +72,7 @@ public class ConfigTests { assertThat(config.appName, equalTo(config.defaultAppName)); assertThat(config.userDataDir, equalTo(config.defaultUserDataDir)); assertThat(config.appDataDir, equalTo(appDataDir)); - assertThat(config.configFile, equalTo(new File(config.appDataDir, DEFAULT_CONFIG_FILE_NAME))); + assertThat(config.getConfigFile(), equalTo(new File(config.appDataDir, DEFAULT_CONFIG_FILE_NAME))); } @Test @@ -82,7 +82,7 @@ public class ConfigTests { assertThat(config.appName, equalTo(config.defaultAppName)); assertThat(config.userDataDir, equalTo(userDataDir)); assertThat(config.appDataDir, equalTo(new File(userDataDir, config.defaultAppName))); - assertThat(config.configFile, equalTo(new File(config.appDataDir, DEFAULT_CONFIG_FILE_NAME))); + assertThat(config.getConfigFile(), equalTo(new File(config.appDataDir, DEFAULT_CONFIG_FILE_NAME))); } @Test @@ -92,7 +92,7 @@ public class ConfigTests { assertThat(config.appName, equalTo("My-Bisq")); assertThat(config.userDataDir, equalTo(config.defaultUserDataDir)); assertThat(config.appDataDir, equalTo(appDataDir)); - assertThat(config.configFile, equalTo(new File(config.appDataDir, DEFAULT_CONFIG_FILE_NAME))); + assertThat(config.getConfigFile(), equalTo(new File(config.appDataDir, DEFAULT_CONFIG_FILE_NAME))); } @Test @@ -166,7 +166,7 @@ public class ConfigTests { public void whenConfigFileOptionIsSetToExistingFile_thenConfigFilePropertyReflectsItsValue() throws IOException { File configFile = File.createTempFile("bisq", "properties"); Config config = configWithOpts(opt(CONFIG_FILE, configFile.getAbsolutePath())); - assertThat(config.configFile, equalTo(configFile)); + assertThat(config.getConfigFile(), equalTo(configFile)); } @Test @@ -175,7 +175,7 @@ public class ConfigTests { File appDataDir = configFile.getParentFile(); String relativeConfigFilePath = configFile.getName(); Config config = configWithOpts(opt(APP_DATA_DIR, appDataDir), opt(CONFIG_FILE, relativeConfigFilePath)); - assertThat(config.configFile, equalTo(configFile)); + assertThat(config.getConfigFile(), equalTo(configFile)); } @Test @@ -188,7 +188,7 @@ public class ConfigTests { assertThat(config.appName, equalTo("My-Bisq")); assertThat(config.userDataDir, equalTo(config.defaultUserDataDir)); assertThat(config.appDataDir, equalTo(new File(config.userDataDir, config.appName))); - assertThat(config.configFile, equalTo(configFile)); + assertThat(config.getConfigFile(), equalTo(configFile)); } @Test diff --git a/core/src/main/java/bisq/core/filter/FilterManager.java b/core/src/main/java/bisq/core/filter/FilterManager.java index 0fc5c7a950..68ddab6740 100644 --- a/core/src/main/java/bisq/core/filter/FilterManager.java +++ b/core/src/main/java/bisq/core/filter/FilterManager.java @@ -128,7 +128,7 @@ public class FilterManager { this.keyRing = keyRing; this.user = user; this.preferences = preferences; - this.configFileEditor = new ConfigFileEditor(config.configFile); + this.configFileEditor = new ConfigFileEditor(config.getConfigFile()); this.providersRepository = providersRepository; this.ignoreDevMsg = ignoreDevMsg; diff --git a/desktop/src/main/java/bisq/desktop/main/settings/network/NetworkSettingsView.java b/desktop/src/main/java/bisq/desktop/main/settings/network/NetworkSettingsView.java index 80c1a710d0..19071fb841 100644 --- a/desktop/src/main/java/bisq/desktop/main/settings/network/NetworkSettingsView.java +++ b/desktop/src/main/java/bisq/desktop/main/settings/network/NetworkSettingsView.java @@ -158,7 +158,7 @@ public class NetworkSettingsView extends ActivatableView { this.localBitcoinNode = localBitcoinNode; this.torNetworkSettingsWindow = torNetworkSettingsWindow; this.clockWatcher = clockWatcher; - this.configFileEditor = new ConfigFileEditor(config.configFile); + this.configFileEditor = new ConfigFileEditor(config.getConfigFile()); } public void initialize() { From b255dad5c3f8866269946c4920df6f9a0280ab1a Mon Sep 17 00:00:00 2001 From: Alva Swanson Date: Wed, 27 Dec 2023 13:13:38 +0100 Subject: [PATCH 3/3] Write FilterManagerAddFilterToNetworkTests --- .../FilterManagerAddFilterToNetworkTests.java | 244 ++++++++++++++++++ .../java/bisq/core/filter/TestFilter.java | 113 ++++++++ 2 files changed, 357 insertions(+) create mode 100644 core/src/test/java/bisq/core/filter/FilterManagerAddFilterToNetworkTests.java create mode 100644 core/src/test/java/bisq/core/filter/TestFilter.java diff --git a/core/src/test/java/bisq/core/filter/FilterManagerAddFilterToNetworkTests.java b/core/src/test/java/bisq/core/filter/FilterManagerAddFilterToNetworkTests.java new file mode 100644 index 0000000000..ef7f14788a --- /dev/null +++ b/core/src/test/java/bisq/core/filter/FilterManagerAddFilterToNetworkTests.java @@ -0,0 +1,244 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.filter; + +import bisq.core.provider.ProvidersRepository; +import bisq.core.user.Preferences; +import bisq.core.user.User; + +import bisq.network.p2p.P2PService; +import bisq.network.p2p.network.BanFilter; +import bisq.network.p2p.storage.P2PDataStorage; +import bisq.network.p2p.storage.payload.ProtectedStorageEntry; + +import bisq.common.app.DevEnv; +import bisq.common.config.Config; +import bisq.common.crypto.KeyRing; +import bisq.common.crypto.Sig; + +import org.bitcoinj.core.ECKey; + +import org.bouncycastle.jce.provider.BouncyCastleProvider; + +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.PublicKey; +import java.security.Security; + +import java.nio.file.Path; + +import java.io.File; + +import java.math.BigInteger; + +import java.util.HashMap; +import java.util.Map; + +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.io.TempDir; + +import static org.bitcoinj.core.Utils.HEX; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; + +@ExtendWith(MockitoExtension.class) +public class FilterManagerAddFilterToNetworkTests { + static { + Security.addProvider(new BouncyCastleProvider()); + } + + private Map p2pStorageMap; + private FilterManager filterManager; + + private final PublicKey ownerPublicKey; + private final String privilegedDevPubKeyHex; + private final ECKey privilegedDevEcKey; + + public FilterManagerAddFilterToNetworkTests() throws NoSuchAlgorithmException, NoSuchProviderException { + KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(Sig.KEY_ALGO, "BC"); + KeyPair ownerKeyPair = keyPairGenerator.generateKeyPair(); + ownerPublicKey = ownerKeyPair.getPublic(); + + privilegedDevEcKey = ECKey.fromPrivate(new BigInteger(1, HEX.decode(DevEnv.DEV_PRIVILEGE_PRIV_KEY))); + privilegedDevPubKeyHex = HEX.encode(privilegedDevEcKey.getPubKey()); + } + + @BeforeEach + void beforeEach(@TempDir Path tmpDir, @Mock P2PService p2PService, @Mock P2PDataStorage p2pDataStorage) { + doReturn(p2pDataStorage).when(p2PService).getP2PDataStorage(); + + p2pStorageMap = new HashMap<>(); + doReturn(p2pStorageMap).when(p2pDataStorage).getMap(); + + Config config = mock(Config.class); + File configFile = tmpDir.resolve("configFile").toFile(); + doReturn(configFile).when(config).getConfigFile(); + + filterManager = new FilterManager( + p2PService, + mock(KeyRing.class), + mock(User.class), + mock(Preferences.class), + config, + mock(ProvidersRepository.class), + mock(BanFilter.class), + false, + true + ); + } + + @Test + void addFilterWithInvalidPublicKey() { + // There should exist no filter before we add our filter + assertNull(filterManager.getFilter()); + + Filter filter = TestFilter.createFilter(ownerPublicKey, "invalidPubKeyAsHex"); + p2pStorageMap.put( + new P2PDataStorage.ByteArray(new byte[100]), + TestFilter.createProtectedStorageEntryForFilter(filter) + ); + + filterManager.onAllServicesInitialized(); + + // FilterManager didn't add our filter + assertNull(filterManager.getFilter()); + } + + @Test + void addFilterWithInvalidSignature() { + // No filter before adding our filter + assertNull(filterManager.getFilter()); + + Filter filter = TestFilter.createFilter(ownerPublicKey, privilegedDevPubKeyHex); + p2pStorageMap.put( + new P2PDataStorage.ByteArray(new byte[100]), + TestFilter.createProtectedStorageEntryForFilter(filter) + ); + + filterManager.onAllServicesInitialized(); + + // FilterManager didn't add our filter + assertNull(filterManager.getFilter()); + } + + @Test + void publishValidFilter() { + // No filter before adding our filter + assertNull(filterManager.getFilter()); + + Filter filterWithSig = TestFilter.createSignedFilter(ownerPublicKey, privilegedDevEcKey); + p2pStorageMap.put( + new P2PDataStorage.ByteArray(new byte[100]), + TestFilter.createProtectedStorageEntryForFilter(filterWithSig) + ); + + filterManager.onAllServicesInitialized(); + + // Our filter got set + Filter currentFilter = filterManager.getFilter(); + assertNotNull(currentFilter); + assertEquals(filterWithSig, currentFilter); + } + + @Test + void addTooOldFilter() { + // No filter before adding our filter + assertNull(filterManager.getFilter()); + + long creationTime = System.currentTimeMillis(); + Filter firstFilterWithSig = TestFilter.createSignedFilter(ownerPublicKey, privilegedDevEcKey, creationTime); + Filter secondFilterWithSig = TestFilter.createSignedFilter(ownerPublicKey, privilegedDevEcKey, + creationTime + 100); + + assertNotEquals(firstFilterWithSig, secondFilterWithSig); + + p2pStorageMap.put( + new P2PDataStorage.ByteArray(new byte[100]), + TestFilter.createProtectedStorageEntryForFilter(secondFilterWithSig) + ); + + filterManager.onAllServicesInitialized(); + + // Our filter got set + Filter currentFilter = filterManager.getFilter(); + assertNotNull(currentFilter); + assertEquals(secondFilterWithSig, currentFilter); + + p2pStorageMap.clear(); + p2pStorageMap.put( + new P2PDataStorage.ByteArray(new byte[100]), + TestFilter.createProtectedStorageEntryForFilter(firstFilterWithSig) + ); + + filterManager.onAllServicesInitialized(); + + // Our filter got set + currentFilter = filterManager.getFilter(); + assertNotNull(currentFilter); + assertEquals(secondFilterWithSig, currentFilter); + } + + @Test + void addNewerFilter() { + // No filter before adding our filter + assertNull(filterManager.getFilter()); + + long creationTime = System.currentTimeMillis(); + Filter firstFilterWithSig = TestFilter.createSignedFilter(ownerPublicKey, privilegedDevEcKey, creationTime); + Filter secondFilterWithSig = TestFilter.createSignedFilter(ownerPublicKey, privilegedDevEcKey, + creationTime + 100); + + assertNotEquals(firstFilterWithSig, secondFilterWithSig); + + p2pStorageMap.put( + new P2PDataStorage.ByteArray(new byte[100]), + TestFilter.createProtectedStorageEntryForFilter(firstFilterWithSig) + ); + + filterManager.onAllServicesInitialized(); + + // Our filter got set + Filter currentFilter = filterManager.getFilter(); + assertNotNull(currentFilter); + assertEquals(firstFilterWithSig, currentFilter); + + p2pStorageMap.clear(); + p2pStorageMap.put( + new P2PDataStorage.ByteArray(new byte[100]), + TestFilter.createProtectedStorageEntryForFilter(secondFilterWithSig) + ); + + filterManager.onAllServicesInitialized(); + + // Our filter got set + currentFilter = filterManager.getFilter(); + assertNotNull(currentFilter); + assertEquals(secondFilterWithSig, currentFilter); + } +} diff --git a/core/src/test/java/bisq/core/filter/TestFilter.java b/core/src/test/java/bisq/core/filter/TestFilter.java new file mode 100644 index 0000000000..e7649acba5 --- /dev/null +++ b/core/src/test/java/bisq/core/filter/TestFilter.java @@ -0,0 +1,113 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.filter; + +import bisq.network.p2p.storage.payload.ProtectedStorageEntry; + +import org.bitcoinj.core.ECKey; +import org.bitcoinj.core.Sha256Hash; + +import org.bouncycastle.util.encoders.Base64; + +import java.security.PublicKey; + +import java.time.Clock; + +import java.nio.charset.StandardCharsets; + +import java.util.Collections; + +import static org.bitcoinj.core.Utils.HEX; + +public class TestFilter { + + public static ProtectedStorageEntry createProtectedStorageEntryForFilter(Filter filter) { + return new ProtectedStorageEntry( + filter, + filter.getOwnerPubKey(), + 1000, + new byte[100], + Clock.systemDefaultZone() + ); + } + + public static Filter createSignedFilter(PublicKey ownerPublicKey, ECKey signerKey) { + return createSignedFilter(ownerPublicKey, signerKey, System.currentTimeMillis()); + } + + public static Filter createSignedFilter(PublicKey ownerPublicKey, ECKey signerKey, long creationDate) { + Filter unsignedFilter = createFilter(ownerPublicKey, HEX.encode(signerKey.getPubKey()), creationDate); + return signFilter(unsignedFilter, signerKey); + } + + public static Filter createFilter(PublicKey ownerPublicKey, String signerPubKeyAsHex) { + return createFilter(ownerPublicKey, signerPubKeyAsHex, System.currentTimeMillis()); + } + + public static Filter createFilter(PublicKey ownerPublicKey, String signerPubKeyAsHex, long creationDate) { + return new Filter( + Collections.emptyList(), + Collections.emptyList(), + Collections.emptyList(), + Collections.emptyList(), + Collections.emptyList(), + Collections.emptyList(), + Collections.emptyList(), + Collections.emptyList(), + false, + Collections.emptyList(), + false, + "", + "", + Collections.emptyList(), + Collections.emptyList(), + Collections.emptyList(), + Collections.emptyList(), + ownerPublicKey.getEncoded(), + creationDate, + null, + null, + signerPubKeyAsHex, + Collections.emptyList(), + false, + Collections.emptyList(), + Collections.emptySet(), + false, + false, + false, + 1, + Collections.emptyList(), + 1, + 1, + 1, + 1, + Collections.emptyList() + ); + } + + private static Filter signFilter(Filter unsignedFilter, ECKey signerKey) { + byte[] filterData = unsignedFilter.toProtoMessage().toByteArray(); + Sha256Hash hash = Sha256Hash.of(filterData); + + ECKey.ECDSASignature ecdsaSignature = signerKey.sign(hash); + byte[] encodeToDER = ecdsaSignature.encodeToDER(); + + String signatureAsBase64 = new String(Base64.encode(encodeToDER), StandardCharsets.UTF_8); + return Filter.cloneWithSig(unsignedFilter, signatureAsBase64); + } +}