mirror of
https://github.com/bisq-network/bisq.git
synced 2024-11-19 01:41:11 +01:00
Use a single auth token vs username:password
There is actually no use case for both username and password forming the authentication token. This change simplifies the arrangement such that a single token is passed. --rpcUser and --rpcPassword are no longer used and are replaced by --apiToken on both cli and daemon sides.
This commit is contained in:
parent
ca0658229b
commit
1a133f4b67
@ -4,11 +4,8 @@ import io.grpc.CallCredentials;
|
||||
import io.grpc.Metadata;
|
||||
import io.grpc.Metadata.Key;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import static bisq.cli.app.CliConfig.RPC_PASSWORD;
|
||||
import static bisq.cli.app.CliConfig.RPC_USER;
|
||||
import static io.grpc.Metadata.ASCII_STRING_MARSHALLER;
|
||||
import static io.grpc.Status.UNAUTHENTICATED;
|
||||
|
||||
@ -17,10 +14,10 @@ import static io.grpc.Status.UNAUTHENTICATED;
|
||||
*/
|
||||
public class BisqCallCredentials extends CallCredentials {
|
||||
|
||||
private final Map<String, String> credentials;
|
||||
private final String apiToken;
|
||||
|
||||
public BisqCallCredentials(Map<String, String> credentials) {
|
||||
this.credentials = credentials;
|
||||
public BisqCallCredentials(String apiToken) {
|
||||
this.apiToken = apiToken;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -28,8 +25,8 @@ public class BisqCallCredentials extends CallCredentials {
|
||||
appExecutor.execute(() -> {
|
||||
try {
|
||||
Metadata headers = new Metadata();
|
||||
Key<String> creds = Key.of("bisq-api-token", ASCII_STRING_MARSHALLER);
|
||||
headers.put(creds, encodeCredentials());
|
||||
Key<String> apiTokenKey = Key.of("bisq-api-token", ASCII_STRING_MARSHALLER);
|
||||
headers.put(apiTokenKey, apiToken);
|
||||
metadataApplier.apply(headers);
|
||||
} catch (Throwable ex) {
|
||||
metadataApplier.fail(UNAUTHENTICATED.withCause(ex));
|
||||
@ -37,13 +34,6 @@ public class BisqCallCredentials extends CallCredentials {
|
||||
});
|
||||
}
|
||||
|
||||
private String encodeCredentials() {
|
||||
if (!credentials.containsKey(RPC_USER) || !credentials.containsKey(RPC_PASSWORD))
|
||||
throw new ConfigException("Cannot call rpc service without username:password credentials");
|
||||
|
||||
return credentials.get(RPC_USER) + ":" + credentials.get(RPC_PASSWORD);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void thisUsesUnstableApi() {
|
||||
}
|
||||
|
@ -16,12 +16,10 @@ final class CliConfig {
|
||||
static final String STOPSERVER = "stopserver";
|
||||
|
||||
// Argument accepting name constants
|
||||
static final String RPC_USER = "rpcuser";
|
||||
static final String RPC_PASSWORD = "rpcpassword";
|
||||
static final String API_TOKEN = "apiToken";
|
||||
|
||||
// Argument accepting cmd options
|
||||
final String rpcUser;
|
||||
final String rpcPassword;
|
||||
final String apiToken;
|
||||
|
||||
// The parser that will be used to parse both cmd line and config file options
|
||||
private final OptionParser optionParser = new OptionParser();
|
||||
@ -30,22 +28,17 @@ final class CliConfig {
|
||||
CliConfig(String[] params) {
|
||||
this.params = params;
|
||||
|
||||
ArgumentAcceptingOptionSpec<String> rpcUserOpt =
|
||||
optionParser.accepts(RPC_USER, "Bisq daemon username")
|
||||
.withRequiredArg()
|
||||
.defaultsTo("");
|
||||
ArgumentAcceptingOptionSpec<String> rpcPasswordOpt =
|
||||
optionParser.accepts(RPC_PASSWORD, "Bisq daemon password")
|
||||
.withRequiredArg()
|
||||
.defaultsTo("");
|
||||
ArgumentAcceptingOptionSpec<String> apiTokenOpt =
|
||||
optionParser.accepts(API_TOKEN, "Bisq API token")
|
||||
.withRequiredArg();
|
||||
|
||||
try {
|
||||
CompositeOptionSet options = new CompositeOptionSet();
|
||||
// Parse command line options
|
||||
OptionSet cliOpts = optionParser.parse(params);
|
||||
options.addOptionSet(cliOpts);
|
||||
|
||||
this.rpcUser = options.valueOf(rpcUserOpt);
|
||||
this.rpcPassword = options.valueOf(rpcPasswordOpt);
|
||||
this.apiToken = options.valueOf(apiTokenOpt);
|
||||
|
||||
optionParser.allowsUnrecognizedOptions();
|
||||
optionParser.nonOptions(GETBALANCE).ofType(String.class).describedAs("Get btc balance");
|
||||
@ -69,6 +62,6 @@ final class CliConfig {
|
||||
}
|
||||
|
||||
static void printHelp() {
|
||||
out.println("Usage: bisq-cli --rpcpassword=user --rpcpassword=password getbalance | getversion");
|
||||
out.println("Usage: bisq-cli --apiToken=token getbalance | getversion");
|
||||
}
|
||||
}
|
||||
|
@ -4,19 +4,13 @@ import joptsimple.OptionParser;
|
||||
import joptsimple.OptionSet;
|
||||
import joptsimple.OptionSpec;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static java.lang.System.out;
|
||||
|
||||
final class CommandParser {
|
||||
|
||||
private Optional<String> cmdToken;
|
||||
private final Map<String, String> creds = new HashMap<>();
|
||||
private String apiToken;
|
||||
private final CliConfig config;
|
||||
|
||||
public CommandParser(CliConfig config) {
|
||||
@ -25,37 +19,19 @@ final class CommandParser {
|
||||
}
|
||||
|
||||
public Optional<String> getCmdToken() {
|
||||
return this.cmdToken;
|
||||
return cmdToken;
|
||||
}
|
||||
|
||||
public Map<String, String> getCreds() {
|
||||
return this.creds;
|
||||
public String getApiToken() {
|
||||
return apiToken;
|
||||
}
|
||||
|
||||
private void init() {
|
||||
OptionParser parser = config.getOptionParser();
|
||||
OptionSpec<String> nonOptions = parser.nonOptions().ofType(String.class);
|
||||
OptionSet options = parser.parse(config.getParams());
|
||||
creds.putAll(rpcCredentials.apply(options));
|
||||
// debugOptionsSet(options, nonOptions);
|
||||
apiToken = (String) options.valueOf("apiToken");
|
||||
List<String> detectedOptions = nonOptions.values(options);
|
||||
cmdToken = detectedOptions.isEmpty() ? Optional.empty() : Optional.of(detectedOptions.get(0));
|
||||
}
|
||||
|
||||
final Function<OptionSet, Map<String, String>> rpcCredentials = (opts) ->
|
||||
opts.asMap().entrySet().stream()
|
||||
.filter(e -> e.getKey().options().size() == 1 && e.getKey().options().get(0).startsWith("rpc"))
|
||||
.collect(Collectors.toUnmodifiableMap(m -> m.getKey().options().get(0), m -> (String) m.getValue().get(0)));
|
||||
|
||||
private void debugOptionsSet(OptionSet options, OptionSpec<String> nonOptions) {
|
||||
// https://programtalk.com/java-api-usage-examples/joptsimple.OptionParser
|
||||
out.println("*** BEGIN Debug OptionSet ***");
|
||||
out.println("[argument acceptors]");
|
||||
options.asMap().entrySet().forEach(out::println);
|
||||
out.println("[rpc credentials map]");
|
||||
out.println(rpcCredentials.apply(options));
|
||||
out.println("[non options]");
|
||||
nonOptions.values(options).forEach(out::println);
|
||||
out.println("*** END Debug OptionSet ***");
|
||||
}
|
||||
}
|
||||
|
@ -32,7 +32,7 @@ final class RpcCommand {
|
||||
final Function<Long, String> prettyBalance = (sats) -> btcFormat.format(BigDecimal.valueOf(sats).divide(satoshiDivisor));
|
||||
|
||||
RpcCommand(ManagedChannel channel, CommandParser parser) {
|
||||
this.callCredentials = new BisqCallCredentials(parser.getCreds());
|
||||
this.callCredentials = new BisqCallCredentials(parser.getApiToken());
|
||||
this.getBalanceStub = GetBalanceGrpc.newBlockingStub(channel).withCallCredentials(callCredentials);
|
||||
this.getVersionStub = GetVersionGrpc.newBlockingStub(channel).withCallCredentials(callCredentials);
|
||||
this.stopServerStub = StopServerGrpc.newBlockingStub(channel).withCallCredentials(callCredentials);
|
||||
|
@ -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_TOKEN = "apiToken";
|
||||
|
||||
// Default values for certain options
|
||||
public static final int UNSPECIFIED_PORT = -1;
|
||||
@ -199,6 +200,7 @@ public class Config {
|
||||
public final long genesisTotalSupply;
|
||||
public final boolean dumpDelayedPayoutTxs;
|
||||
public final boolean allowFaultyDelayedTxs;
|
||||
public final String apiToken;
|
||||
|
||||
// Properties derived from options but not exposed as options themselves
|
||||
public final File torDir;
|
||||
@ -615,6 +617,11 @@ public class Config {
|
||||
.ofType(boolean.class)
|
||||
.defaultsTo(false);
|
||||
|
||||
ArgumentAcceptingOptionSpec<String> apiTokenOpt =
|
||||
parser.accepts(API_TOKEN, "Bisq gRPC API authentication token")
|
||||
.withRequiredArg()
|
||||
.defaultsTo("");
|
||||
|
||||
try {
|
||||
CompositeOptionSet options = new CompositeOptionSet();
|
||||
|
||||
@ -727,6 +734,7 @@ public class Config {
|
||||
this.daoActivated = options.valueOf(daoActivatedOpt);
|
||||
this.dumpDelayedPayoutTxs = options.valueOf(dumpDelayedPayoutTxsOpt);
|
||||
this.allowFaultyDelayedTxs = options.valueOf(allowFaultyDelayedTxsOpt);
|
||||
this.apiToken = options.valueOf(apiTokenOpt);
|
||||
} catch (OptionException ex) {
|
||||
throw new ConfigException("problem parsing option '%s': %s",
|
||||
ex.options().get(0),
|
||||
|
@ -205,8 +205,6 @@ public class BisqGrpcServer {
|
||||
private void start() throws IOException {
|
||||
// TODO add to options
|
||||
int port = 9998;
|
||||
String rpcUser = config.rpcUser;
|
||||
String rpcPassword = config.rpcPassword;
|
||||
|
||||
// Config services
|
||||
server = ServerBuilder.forPort(port)
|
||||
@ -217,7 +215,7 @@ public class BisqGrpcServer {
|
||||
.addService(new GetPaymentAccountsImpl())
|
||||
.addService(new PlaceOfferImpl())
|
||||
.addService(new StopServerImpl())
|
||||
.intercept(new TokenAuthInterceptor(rpcUser, rpcPassword))
|
||||
.intercept(new TokenAuthInterceptor(config.apiToken))
|
||||
.build()
|
||||
.start();
|
||||
|
||||
|
@ -18,12 +18,10 @@ import static io.grpc.Status.UNAUTHENTICATED;
|
||||
@Slf4j
|
||||
public class TokenAuthInterceptor implements ServerInterceptor {
|
||||
|
||||
private final String rpcUser;
|
||||
private final String rpcPassword;
|
||||
private final String apiToken;
|
||||
|
||||
public TokenAuthInterceptor(String rpcUser, String rpcPassword) {
|
||||
this.rpcUser = rpcUser;
|
||||
this.rpcPassword = rpcPassword;
|
||||
public TokenAuthInterceptor(String apiToken) {
|
||||
this.apiToken = apiToken;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -35,16 +33,9 @@ public class TokenAuthInterceptor implements ServerInterceptor {
|
||||
|
||||
private void authenticate(String authToken) {
|
||||
if (authToken == null)
|
||||
throw new StatusRuntimeException(UNAUTHENTICATED.withDescription("Authentication token is missing"));
|
||||
throw new StatusRuntimeException(UNAUTHENTICATED.withDescription("API token is missing"));
|
||||
|
||||
if (!isValidToken(authToken))
|
||||
throw new StatusRuntimeException(UNAUTHENTICATED.withDescription("Invalid username or password"));
|
||||
|
||||
log.info("Authenticated user {} with token {}", rpcUser, authToken);
|
||||
}
|
||||
|
||||
private boolean isValidToken(String token) {
|
||||
String[] pair = token.split(":");
|
||||
return pair[0].equals(rpcUser) && pair[1].equals(rpcPassword);
|
||||
if (!authToken.equals(apiToken))
|
||||
throw new StatusRuntimeException(UNAUTHENTICATED.withDescription("Invalid API token"));
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user