diff --git a/build.gradle b/build.gradle index f12aa2d6db3..a81b44604f2 100644 --- a/build.gradle +++ b/build.gradle @@ -335,7 +335,7 @@ configure(project(':core')) { } configure(project(':cli')) { - mainClassName = 'bisq.cli.app.BisqCliMain' + mainClassName = 'bisq.cli.CliMain' dependencies { compile project(':proto') diff --git a/cli/src/main/java/bisq/cli/CliMain.java b/cli/src/main/java/bisq/cli/CliMain.java new file mode 100644 index 00000000000..27f40b68b6c --- /dev/null +++ b/cli/src/main/java/bisq/cli/CliMain.java @@ -0,0 +1,189 @@ +/* + * 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.cli; + +import bisq.proto.grpc.GetBalanceGrpc; +import bisq.proto.grpc.GetBalanceRequest; +import bisq.proto.grpc.GetVersionGrpc; +import bisq.proto.grpc.GetVersionRequest; + +import io.grpc.ManagedChannelBuilder; +import io.grpc.StatusRuntimeException; + +import joptsimple.OptionException; +import joptsimple.OptionParser; +import joptsimple.OptionSet; + +import java.text.DecimalFormat; + +import java.io.IOException; +import java.io.PrintStream; + +import java.math.BigDecimal; + +import java.util.List; +import java.util.concurrent.TimeUnit; + +import lombok.extern.slf4j.Slf4j; + +import static java.lang.System.err; +import static java.lang.System.exit; +import static java.lang.System.out; + +/** + * A command-line client for the Bisq gRPC API. + */ +@Slf4j +public class CliMain { + + private static final int EXIT_SUCCESS = 0; + private static final int EXIT_FAILURE = 1; + + private enum Method { + getversion, + getbalance + } + + public static void main(String[] args) { + var parser = new OptionParser(); + + var helpOpt = parser.accepts("help", "Print this help text") + .forHelp(); + + var hostOpt = parser.accepts("host", "rpc server hostname or IP") + .withRequiredArg() + .defaultsTo("localhost"); + + var portOpt = parser.accepts("port", "rpc server port") + .withRequiredArg() + .ofType(Integer.class) + .defaultsTo(9998); + + var passwordOpt = parser.accepts("password", "rpc server password") + .withRequiredArg(); + + OptionSet options = null; + try { + options = parser.parse(args); + } catch (OptionException ex) { + err.println("Error: " + ex.getMessage()); + exit(EXIT_FAILURE); + } + + if (options.has(helpOpt)) { + printHelp(parser, out); + exit(EXIT_SUCCESS); + } + + @SuppressWarnings("unchecked") + var nonOptionArgs = (List) options.nonOptionArguments(); + if (nonOptionArgs.isEmpty()) { + printHelp(parser, err); + err.println("Error: no method specified"); + exit(EXIT_FAILURE); + } + + var methodName = nonOptionArgs.get(0); + Method method = null; + try { + method = Method.valueOf(methodName); + } catch (IllegalArgumentException ex) { + err.printf("Error: '%s' is not a supported method\n", methodName); + exit(EXIT_FAILURE); + } + + var host = options.valueOf(hostOpt); + var port = options.valueOf(portOpt); + var password = options.valueOf(passwordOpt); + if (password == null) { + err.println("Error: missing required 'password' option"); + exit(EXIT_FAILURE); + } + + var credentials = new PasswordCallCredentials(password); + + var channel = ManagedChannelBuilder.forAddress(host, port).usePlaintext().build(); + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + try { + channel.shutdown().awaitTermination(1, TimeUnit.SECONDS); + } catch (InterruptedException ex) { + ex.printStackTrace(err); + exit(EXIT_FAILURE); + } + })); + + try { + switch (method) { + case getversion: { + var stub = GetVersionGrpc.newBlockingStub(channel).withCallCredentials(credentials); + var request = GetVersionRequest.newBuilder().build(); + var version = stub.getVersion(request).getVersion(); + out.println(version); + exit(EXIT_SUCCESS); + } + case getbalance: { + var stub = GetBalanceGrpc.newBlockingStub(channel).withCallCredentials(credentials); + var request = GetBalanceRequest.newBuilder().build(); + var balance = stub.getBalance(request).getBalance(); + if (balance == -1) { + err.println("Error: server is still initializing"); + exit(EXIT_FAILURE); + } + out.println(formatBalance(balance)); + exit(EXIT_SUCCESS); + } + default: { + err.printf("Error: unhandled method '%s'\n", method); + exit(EXIT_FAILURE); + } + } + } catch (StatusRuntimeException ex) { + // This exception is thrown if the client-provided password credentials do not + // match the value set on the server. The actual error message is in a nested + // exception and we clean it up a bit to make it more presentable. + Throwable t = ex.getCause() == null ? ex : ex.getCause(); + err.println("Error: " + t.getMessage().replace("UNAUTHENTICATED: ", "")); + exit(EXIT_FAILURE); + } + } + + private static void printHelp(OptionParser parser, PrintStream stream) { + try { + stream.println("Bisq RPC Client"); + stream.println(); + stream.println("Usage: bisq-cli [options] "); + stream.println(); + parser.printHelpOn(stream); + stream.println(); + stream.println("Method Description"); + stream.println("------ -----------"); + stream.println("getversion Get server version"); + stream.println("getbalance Get server wallet balance"); + stream.println(); + } catch (IOException ex) { + ex.printStackTrace(stream); + } + } + + @SuppressWarnings("BigDecimalMethodWithoutRoundingCalled") + private static String formatBalance(long satoshis) { + var btcFormat = new DecimalFormat("###,##0.00000000"); + var satoshiDivisor = new BigDecimal(100000000); + return btcFormat.format(BigDecimal.valueOf(satoshis).divide(satoshiDivisor)); + } +} diff --git a/cli/src/main/java/bisq/cli/PasswordCallCredentials.java b/cli/src/main/java/bisq/cli/PasswordCallCredentials.java new file mode 100644 index 00000000000..14b451d28f8 --- /dev/null +++ b/cli/src/main/java/bisq/cli/PasswordCallCredentials.java @@ -0,0 +1,45 @@ +package bisq.cli; + +import io.grpc.CallCredentials; +import io.grpc.Metadata; +import io.grpc.Metadata.Key; + +import java.util.concurrent.Executor; + +import static io.grpc.Metadata.ASCII_STRING_MARSHALLER; +import static io.grpc.Status.UNAUTHENTICATED; +import static java.lang.String.format; + +/** + * Sets the {@value PASSWORD_KEY} rpc call header to a given value. + */ +class PasswordCallCredentials extends CallCredentials { + + public static final String PASSWORD_KEY = "password"; + + private final String passwordValue; + + public PasswordCallCredentials(String passwordValue) { + if (passwordValue == null) + throw new IllegalArgumentException(format("'%s' value must not be null", PASSWORD_KEY)); + this.passwordValue = passwordValue; + } + + @Override + public void applyRequestMetadata(RequestInfo requestInfo, Executor appExecutor, MetadataApplier metadataApplier) { + appExecutor.execute(() -> { + try { + var headers = new Metadata(); + var passwordKey = Key.of(PASSWORD_KEY, ASCII_STRING_MARSHALLER); + headers.put(passwordKey, passwordValue); + metadataApplier.apply(headers); + } catch (Throwable ex) { + metadataApplier.fail(UNAUTHENTICATED.withCause(ex)); + } + }); + } + + @Override + public void thisUsesUnstableApi() { + } +} diff --git a/cli/src/main/java/bisq/cli/app/BisqCliMain.java b/cli/src/main/java/bisq/cli/app/BisqCliMain.java deleted file mode 100644 index 015051bdc32..00000000000 --- a/cli/src/main/java/bisq/cli/app/BisqCliMain.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * 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.cli.app; - -import io.grpc.ManagedChannel; -import io.grpc.ManagedChannelBuilder; - -import joptsimple.OptionParser; -import joptsimple.OptionSet; -import joptsimple.OptionSpec; - -import java.util.List; -import java.util.concurrent.TimeUnit; - -import lombok.extern.slf4j.Slf4j; - -import static bisq.cli.app.CommandParser.GETBALANCE; -import static bisq.cli.app.CommandParser.GETVERSION; -import static bisq.cli.app.CommandParser.HELP; -import static bisq.cli.app.CommandParser.STOPSERVER; -import static java.lang.String.format; -import static java.lang.System.exit; -import static java.lang.System.out; - -/** - * gRPC client. - */ -@Slf4j -public class BisqCliMain { - - private static final int EXIT_SUCCESS = 0; - private static final int EXIT_FAILURE = 1; - - private final ManagedChannel channel; - private final CliCommand cmd; - private final OptionParser parser; - - public static void main(String[] args) { - new BisqCliMain("localhost", 9998, args); - } - - private BisqCliMain(String host, int port, String[] args) { - // Channels are secure by default (via SSL/TLS); for the example disable TLS to avoid needing certificates. - this(ManagedChannelBuilder.forAddress(host, port).usePlaintext().build()); - String command = parseCommand(args); - String result = runCommand(command); - out.println(result); - try { - shutdown(); // Orderly channel shutdown - } catch (InterruptedException ignored) { - } - } - - /** - * Construct client for accessing server using the existing channel. - */ - private BisqCliMain(ManagedChannel channel) { - this.channel = channel; - this.cmd = new CliCommand(channel); - this.parser = new CommandParser().configure(); - } - - private String runCommand(String command) { - final String result; - switch (command) { - case HELP: - CommandParser.printHelp(); - exit(EXIT_SUCCESS); - case GETBALANCE: - long satoshis = cmd.getBalance(); - result = satoshis == -1 ? "Server initializing..." : cmd.prettyBalance.apply(satoshis); - break; - case GETVERSION: - result = cmd.getVersion(); - break; - case STOPSERVER: - cmd.stopServer(); - result = "Server stopped"; - break; - default: - result = format("Unknown command '%s'", command); - } - return result; - } - - private String parseCommand(String[] params) { - OptionSpec nonOptions = parser.nonOptions().ofType(String.class); - OptionSet options = parser.parse(params); - List detectedOptions = nonOptions.values(options); - if (detectedOptions.isEmpty()) { - CommandParser.printHelp(); - exit(EXIT_FAILURE); - } - return detectedOptions.get(0); - } - - private void shutdown() throws InterruptedException { - channel.shutdown().awaitTermination(1, TimeUnit.SECONDS); - exit(EXIT_SUCCESS); - } -} diff --git a/cli/src/main/java/bisq/cli/app/CliCommand.java b/cli/src/main/java/bisq/cli/app/CliCommand.java deleted file mode 100644 index e3b0bc813fe..00000000000 --- a/cli/src/main/java/bisq/cli/app/CliCommand.java +++ /dev/null @@ -1,66 +0,0 @@ -package bisq.cli.app; - -import bisq.proto.grpc.GetBalanceGrpc; -import bisq.proto.grpc.GetBalanceRequest; -import bisq.proto.grpc.GetVersionGrpc; -import bisq.proto.grpc.GetVersionRequest; -import bisq.proto.grpc.StopServerGrpc; -import bisq.proto.grpc.StopServerRequest; - -import io.grpc.ManagedChannel; -import io.grpc.StatusRuntimeException; - -import java.text.DecimalFormat; - -import java.math.BigDecimal; - -import java.util.function.Function; - -import lombok.extern.slf4j.Slf4j; - -@Slf4j -final class CliCommand { - - private final GetBalanceGrpc.GetBalanceBlockingStub getBalanceStub; - private final GetVersionGrpc.GetVersionBlockingStub getVersionStub; - private final StopServerGrpc.StopServerBlockingStub stopServerStub; - - private final DecimalFormat btcFormat = new DecimalFormat("###,##0.00000000"); - private final BigDecimal satoshiDivisor = new BigDecimal(100000000); - @SuppressWarnings("BigDecimalMethodWithoutRoundingCalled") - final Function prettyBalance = (sats) -> btcFormat.format(BigDecimal.valueOf(sats).divide(satoshiDivisor)); - - CliCommand(ManagedChannel channel) { - getBalanceStub = GetBalanceGrpc.newBlockingStub(channel); - getVersionStub = GetVersionGrpc.newBlockingStub(channel); - stopServerStub = StopServerGrpc.newBlockingStub(channel); - } - - String getVersion() { - GetVersionRequest request = GetVersionRequest.newBuilder().build(); - try { - return getVersionStub.getVersion(request).getVersion(); - } catch (StatusRuntimeException e) { - return "RPC failed: " + e.getStatus(); - } - } - - long getBalance() { - GetBalanceRequest request = GetBalanceRequest.newBuilder().build(); - try { - return getBalanceStub.getBalance(request).getBalance(); - } catch (StatusRuntimeException e) { - log.warn("RPC failed: {}", e.getStatus()); - return -1; - } - } - - void stopServer() { - StopServerRequest request = StopServerRequest.newBuilder().build(); - try { - stopServerStub.stopServer(request); - } catch (StatusRuntimeException e) { - log.warn("RPC failed: {}", e.getStatus()); - } - } -} diff --git a/cli/src/main/java/bisq/cli/app/CommandParser.java b/cli/src/main/java/bisq/cli/app/CommandParser.java deleted file mode 100644 index b1615d81165..00000000000 --- a/cli/src/main/java/bisq/cli/app/CommandParser.java +++ /dev/null @@ -1,27 +0,0 @@ -package bisq.cli.app; - -import joptsimple.OptionParser; - -import static java.lang.System.out; - -final class CommandParser { - - // Option name constants - static final String HELP = "help"; - static final String GETBALANCE = "getbalance"; - static final String GETVERSION = "getversion"; - static final String STOPSERVER = "stopserver"; - - OptionParser configure() { - OptionParser parser = new OptionParser(); - parser.allowsUnrecognizedOptions(); - parser.nonOptions(GETBALANCE).ofType(String.class).describedAs("get btc balance"); - parser.nonOptions(GETVERSION).ofType(String.class).describedAs("get bisq version"); - return parser; - } - - static void printHelp() { - out.println("Usage: bisq-cli getbalance | getversion"); - } - -} diff --git a/cli/test.sh b/cli/test.sh new file mode 100755 index 00000000000..046cbd910aa --- /dev/null +++ b/cli/test.sh @@ -0,0 +1,156 @@ +#!/bin/bash +# +# References & examples for expect: +# +# - https://pantz.org/software/expect/expect_examples_and_tips.html +# - https://stackoverflow.com/questions/13982310/else-string-matching-in-expect +# - https://gist.github.com/Fluidbyte/6294378 +# - https://www.oreilly.com/library/view/exploring-expect/9781565920903/ch04.html +# +# Prior to running this script, run: +# +# ./bisq-daemon --apiPassword=xyz +# +# The data directory used must contain an unencrypted wallet with a 0 BTC balance + +# Ensure project root is the current working directory +cd $(dirname $0)/.. + +OUTPUT=$(expect -c ' + # exp_internal 1 + puts "TEST unsupported cmd error" + set expected "Error: '\''bogus'\'' is not a supported method" + spawn ./bisq-cli --password=xyz bogus + expect { + $expected { puts "PASS" } + default { + set results $expect_out(buffer) + puts "FAIL expected = $expected" + puts " actual = $results" + } + } +') +echo "$OUTPUT" +echo "========================================================================" + +OUTPUT=$(expect -c ' + puts "TEST unrecognized option error" + set expected "Error: bogus is not a recognized option" + spawn ./bisq-cli --bogus getversion + expect { + $expected { puts "PASS" } + default { + set results $expect_out(buffer) + puts "FAIL expected = $expected" + puts " actual = $results" + } + } +') +echo "$OUTPUT" +echo "========================================================================" + +OUTPUT=$(expect -c ' + # exp_internal 1 + puts "TEST missing required password option error" + set expected "Error: missing required '\''password'\'' option" + spawn ./bisq-cli getversion + expect { + $expected { puts "PASS" } + default { + set results $expect_out(buffer) + puts "FAIL expected = $expected" + puts " actual = $results" + } + } +') +echo "$OUTPUT" +echo "========================================================================" + +OUTPUT=$(expect -c ' + # exp_internal 1 + puts "TEST getversion (incorrect password error)" + set expected "Error: incorrect '\''password'\'' rpc header value" + spawn ./bisq-cli --password=bogus getversion + expect { + $expected { puts "PASS\n" } + default { + set results $expect_out(buffer) + puts "FAIL expected = $expected" + puts " actual = $results" + } + } +') +echo "$OUTPUT" +echo "========================================================================" + +OUTPUT=$(expect -c ' + # exp_internal 1 + puts "TEST getversion (password value in quotes)" + set expected "1.3.2" + # Note: have to define quoted argument in a variable as "''value''" + set pwd_in_quotes "''xyz''" + spawn ./bisq-cli --password=$pwd_in_quotes getversion + expect { + $expected { puts "PASS" } + default { + set results $expect_out(buffer) + puts "FAIL expected = $expected" + puts " actual = $results" + } + } +') +echo "$OUTPUT" +echo "========================================================================" + +OUTPUT=$(expect -c ' + puts "TEST getversion" + set expected "1.3.2" + spawn ./bisq-cli --password=xyz getversion + expect { + $expected { puts "PASS" } + default { + set results $expect_out(buffer) + puts "FAIL expected = $expected" + puts " actual = $results" + } + } +') +echo "$OUTPUT" +echo "========================================================================" + +OUTPUT=$(expect -c ' + puts "TEST getbalance" + # exp_internal 1 + set expected "0.00000000" + spawn ./bisq-cli --password=xyz getbalance + expect { + $expected { puts "PASS" } + default { + set results $expect_out(buffer) + puts "FAIL expected = $expected" + puts " actual = $results" + } + } +') +echo "$OUTPUT" +echo "========================================================================" + +OUTPUT=$(expect -c ' + puts "TEST running with no options or arguments prints help text" + # exp_internal 1 + set expected "Bisq RPC Client" + spawn ./bisq-cli + expect { + $expected { puts "PASS" } + default { + set results $expect_out(buffer) + puts "FAIL expected = $expected" + puts " actual = $results" + } + } +') +echo "$OUTPUT" +echo "========================================================================" + +echo "TEST --help option prints help text" +./bisq-cli --help diff --git a/common/src/main/java/bisq/common/config/Config.java b/common/src/main/java/bisq/common/config/Config.java index b959d4ec1c4..b04c58e73ef 100644 --- a/common/src/main/java/bisq/common/config/Config.java +++ b/common/src/main/java/bisq/common/config/Config.java @@ -116,6 +116,7 @@ public class Config { public static final String DAO_ACTIVATED = "daoActivated"; public static final String DUMP_DELAYED_PAYOUT_TXS = "dumpDelayedPayoutTxs"; public static final String ALLOW_FAULTY_DELAYED_TXS = "allowFaultyDelayedTxs"; + public static final String API_PASSWORD = "apiPassword"; // Default values for certain options public static final int UNSPECIFIED_PORT = -1; @@ -140,7 +141,7 @@ public class Config { public final boolean helpRequested; public final File configFile; - // Options supported both at the cli and in the config file + // Options supported on cmd line and in the config file public final String appName; public final File userDataDir; public final File appDataDir; @@ -199,6 +200,7 @@ public class Config { public final long genesisTotalSupply; public final boolean dumpDelayedPayoutTxs; public final boolean allowFaultyDelayedTxs; + public final String apiPassword; // Properties derived from options but not exposed as options themselves public final File torDir; @@ -206,7 +208,7 @@ public class Config { public final File storageDir; public final File keyStorageDir; - // The parser that will be used to parse both cli and config file options + // The parser that will be used to parse both cmd line and config file options private final OptionParser parser = new OptionParser(); /** @@ -615,6 +617,11 @@ public Config(String defaultAppName, File defaultUserDataDir, String... args) { .ofType(boolean.class) .defaultsTo(false); + ArgumentAcceptingOptionSpec apiPasswordOpt = + parser.accepts(API_PASSWORD, "gRPC API password") + .withRequiredArg() + .defaultsTo(""); + try { CompositeOptionSet options = new CompositeOptionSet(); @@ -727,6 +734,7 @@ public Config(String defaultAppName, File defaultUserDataDir, String... args) { this.daoActivated = options.valueOf(daoActivatedOpt); this.dumpDelayedPayoutTxs = options.valueOf(dumpDelayedPayoutTxsOpt); this.allowFaultyDelayedTxs = options.valueOf(allowFaultyDelayedTxsOpt); + this.apiPassword = options.valueOf(apiPasswordOpt); } catch (OptionException ex) { throw new ConfigException("problem parsing option '%s': %s", ex.options().get(0), diff --git a/core/src/main/java/bisq/core/grpc/BisqGrpcServer.java b/core/src/main/java/bisq/core/grpc/BisqGrpcServer.java index ef6631462f9..9ce22b72db8 100644 --- a/core/src/main/java/bisq/core/grpc/BisqGrpcServer.java +++ b/core/src/main/java/bisq/core/grpc/BisqGrpcServer.java @@ -22,6 +22,8 @@ import bisq.core.trade.handlers.TransactionResultHandler; import bisq.core.trade.statistics.TradeStatistics2; +import bisq.common.config.Config; + import bisq.proto.grpc.GetBalanceGrpc; import bisq.proto.grpc.GetBalanceReply; import bisq.proto.grpc.GetBalanceRequest; @@ -65,6 +67,7 @@ public class BisqGrpcServer { private Server server; private static BisqGrpcServer instance; + private static Config config; private static CoreApi coreApi; @@ -170,9 +173,10 @@ public void stopServer(StopServerRequest req, StreamObserver re // Constructor /////////////////////////////////////////////////////////////////////////////////////////// - public BisqGrpcServer(CoreApi coreApi) { + public BisqGrpcServer(Config config, CoreApi coreApi) { instance = this; + BisqGrpcServer.config = config; BisqGrpcServer.coreApi = coreApi; try { @@ -211,15 +215,16 @@ private void start() throws IOException { .addService(new GetPaymentAccountsImpl()) .addService(new PlaceOfferImpl()) .addService(new StopServerImpl()) + .intercept(new PasswordAuthInterceptor(config.apiPassword)) .build() .start(); log.info("Server started, listening on " + port); Runtime.getRuntime().addShutdownHook(new Thread(() -> { // Use stderr here since the logger may have been reset by its JVM shutdown hook. - log.error("*** shutting down gRPC server since JVM is shutting down"); + log.error("Shutting down gRPC server"); BisqGrpcServer.this.stop(); - log.error("*** server shut down"); + log.error("Server shut down"); })); } } diff --git a/core/src/main/java/bisq/core/grpc/PasswordAuthInterceptor.java b/core/src/main/java/bisq/core/grpc/PasswordAuthInterceptor.java new file mode 100644 index 00000000000..2ab29bcdc95 --- /dev/null +++ b/core/src/main/java/bisq/core/grpc/PasswordAuthInterceptor.java @@ -0,0 +1,45 @@ +package bisq.core.grpc; + +import io.grpc.Metadata; +import io.grpc.ServerCall; +import io.grpc.ServerCallHandler; +import io.grpc.ServerInterceptor; +import io.grpc.StatusRuntimeException; + +import static io.grpc.Metadata.ASCII_STRING_MARSHALLER; +import static io.grpc.Metadata.Key; +import static io.grpc.Status.UNAUTHENTICATED; +import static java.lang.String.format; + +/** + * Authorizes rpc server calls by comparing the value of the caller's + * {@value PASSWORD_KEY} header to an expected value set at server startup time. + * + * @see bisq.common.config.Config#apiPassword + */ +class PasswordAuthInterceptor implements ServerInterceptor { + + public static final String PASSWORD_KEY = "password"; + + private final String expectedPasswordValue; + + public PasswordAuthInterceptor(String expectedPasswordValue) { + this.expectedPasswordValue = expectedPasswordValue; + } + + @Override + public ServerCall.Listener interceptCall(ServerCall serverCall, Metadata headers, + ServerCallHandler serverCallHandler) { + var actualPasswordValue = headers.get(Key.of(PASSWORD_KEY, ASCII_STRING_MARSHALLER)); + + if (actualPasswordValue == null) + throw new StatusRuntimeException(UNAUTHENTICATED.withDescription( + format("missing '%s' rpc header value", PASSWORD_KEY))); + + if (!actualPasswordValue.equals(expectedPasswordValue)) + throw new StatusRuntimeException(UNAUTHENTICATED.withDescription( + format("incorrect '%s' rpc header value", PASSWORD_KEY))); + + return serverCallHandler.startCall(serverCall, headers); + } +} diff --git a/daemon/src/main/java/bisq/daemon/app/BisqDaemonMain.java b/daemon/src/main/java/bisq/daemon/app/BisqDaemonMain.java index bcdd930d006..1e19ed23c5e 100644 --- a/daemon/src/main/java/bisq/daemon/app/BisqDaemonMain.java +++ b/daemon/src/main/java/bisq/daemon/app/BisqDaemonMain.java @@ -99,6 +99,6 @@ protected void onApplicationStarted() { super.onApplicationStarted(); CoreApi coreApi = injector.getInstance(CoreApi.class); - new BisqGrpcServer(coreApi); + new BisqGrpcServer(config, coreApi); } } diff --git a/daemon/src/main/java/resources/logback.xml b/daemon/src/main/resources/logback.xml similarity index 100% rename from daemon/src/main/java/resources/logback.xml rename to daemon/src/main/resources/logback.xml