Refactor auth infrastructure naming

This commit is contained in:
Chris Beams 2020-04-25 09:37:14 +02:00
parent 3fe7848c4e
commit e84123c20a
No known key found for this signature in database
GPG Key ID: 3D214F8F5BC5ED73
6 changed files with 70 additions and 61 deletions

View File

@ -10,23 +10,25 @@ import static io.grpc.Metadata.ASCII_STRING_MARSHALLER;
import static io.grpc.Status.UNAUTHENTICATED;
/**
* Simple credentials implementation for sending cleartext username:password token via rpc call headers.
* Sets the {@value AUTH_HEADER_KEY} rpc call header to a given value.
*/
public class BisqCallCredentials extends CallCredentials {
public class AuthHeaderCallCredentials extends CallCredentials {
private final String apiToken;
public static final String AUTH_HEADER_KEY = "authorization";
public BisqCallCredentials(String apiToken) {
this.apiToken = apiToken;
private final String authHeaderValue;
public AuthHeaderCallCredentials(String authHeaderValue) {
this.authHeaderValue = authHeaderValue;
}
@Override
public void applyRequestMetadata(RequestInfo requestInfo, Executor appExecutor, MetadataApplier metadataApplier) {
appExecutor.execute(() -> {
try {
Metadata headers = new Metadata();
Key<String> apiTokenKey = Key.of("bisq-api-token", ASCII_STRING_MARSHALLER);
headers.put(apiTokenKey, apiToken);
var headers = new Metadata();
var authorizationKey = Key.of(AUTH_HEADER_KEY, ASCII_STRING_MARSHALLER);
headers.put(authorizationKey, authHeaderValue);
metadataApplier.apply(headers);
} catch (Throwable ex) {
metadataApplier.fail(UNAUTHENTICATED.withCause(ex));

View File

@ -61,12 +61,12 @@ public class BisqCliMain {
.withRequiredArg()
.defaultsTo("localhost");
var portOpt = parser.accepts("port", "Bisq node RPC port")
var portOpt = parser.accepts("port", "Bisq node rpc port")
.withRequiredArg()
.ofType(Integer.class)
.defaultsTo(9998);
var authOpt = parser.accepts("auth", "Bisq node RPC authentication token")
var passwordOpt = parser.accepts("password", "Bisq node rpc server password")
.withRequiredArg();
var options = parser.parse(args);
@ -89,9 +89,9 @@ public class BisqCliMain {
var host = options.valueOf(hostOpt);
var port = options.valueOf(portOpt);
var authToken = options.valueOf(authOpt);
if (authToken == null) {
err.println("error: rpc authentication token must not be null");
var password = options.valueOf(passwordOpt);
if (password == null) {
err.println("error: rpc password must not be null");
exit(EXIT_FAILURE);
}
@ -103,7 +103,7 @@ public class BisqCliMain {
}
var channel = ManagedChannelBuilder.forAddress(host, port).usePlaintext().build();
var credentials = new BisqCallCredentials(authToken);
var credentials = new AuthHeaderCallCredentials(password);
var command = nonOptionArgs.get(0);

View File

@ -116,7 +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";
public static final String API_PASSWORD = "apiPassword";
// Default values for certain options
public static final int UNSPECIFIED_PORT = -1;
@ -200,7 +200,7 @@ public class Config {
public final long genesisTotalSupply;
public final boolean dumpDelayedPayoutTxs;
public final boolean allowFaultyDelayedTxs;
public final String apiToken;
public final String apiPassword;
// Properties derived from options but not exposed as options themselves
public final File torDir;
@ -617,8 +617,8 @@ public class Config {
.ofType(boolean.class)
.defaultsTo(false);
ArgumentAcceptingOptionSpec<String> apiTokenOpt =
parser.accepts(API_TOKEN, "Bisq gRPC API authentication token")
ArgumentAcceptingOptionSpec<String> apiPasswordOpt =
parser.accepts(API_PASSWORD, "gRPC API password")
.withRequiredArg()
.defaultsTo("");
@ -734,7 +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);
this.apiPassword = options.valueOf(apiPasswordOpt);
} catch (OptionException ex) {
throw new ConfigException("problem parsing option '%s': %s",
ex.options().get(0),

View File

@ -0,0 +1,48 @@
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 lombok.extern.slf4j.Slf4j;
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 AUTH_HEADER_KEY} header to an expected value set at server startup time.
*
* @see bisq.common.config.Config#apiPassword
*/
@Slf4j
public class AuthorizationInterceptor implements ServerInterceptor {
public static final String AUTH_HEADER_KEY = "authorization";
private final String expectedAuthHeaderValue;
public AuthorizationInterceptor(String expectedAuthHeaderValue) {
this.expectedAuthHeaderValue = expectedAuthHeaderValue;
}
@Override
public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(ServerCall<ReqT, RespT> serverCall, Metadata headers,
ServerCallHandler<ReqT, RespT> serverCallHandler) {
var actualAuthHeaderValue = headers.get(Key.of(AUTH_HEADER_KEY, ASCII_STRING_MARSHALLER));
if (actualAuthHeaderValue == null)
throw new StatusRuntimeException(UNAUTHENTICATED.withDescription(
format("missing '%s' rpc header value", AUTH_HEADER_KEY)));
if (!actualAuthHeaderValue.equals(expectedAuthHeaderValue))
throw new StatusRuntimeException(UNAUTHENTICATED.withDescription(
format("incorrect '%s' rpc header value", AUTH_HEADER_KEY)));
return serverCallHandler.startCall(serverCall, headers);
}
}

View File

@ -215,7 +215,7 @@ public class BisqGrpcServer {
.addService(new GetPaymentAccountsImpl())
.addService(new PlaceOfferImpl())
.addService(new StopServerImpl())
.intercept(new TokenAuthInterceptor(config.apiToken))
.intercept(new AuthorizationInterceptor(config.apiPassword))
.build()
.start();

View File

@ -1,41 +0,0 @@
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 lombok.extern.slf4j.Slf4j;
import static io.grpc.Metadata.ASCII_STRING_MARSHALLER;
import static io.grpc.Metadata.Key;
import static io.grpc.Status.UNAUTHENTICATED;
/**
* Simple authentication interceptor to validate a cleartext token in username:password format.
*/
@Slf4j
public class TokenAuthInterceptor implements ServerInterceptor {
private final String apiToken;
public TokenAuthInterceptor(String apiToken) {
this.apiToken = apiToken;
}
@Override
public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(ServerCall<ReqT, RespT> serverCall, Metadata metadata,
ServerCallHandler<ReqT, RespT> serverCallHandler) {
authenticate(metadata.get(Key.of("bisq-api-token", ASCII_STRING_MARSHALLER)));
return serverCallHandler.startCall(serverCall, metadata);
}
private void authenticate(String authToken) {
if (authToken == null)
throw new StatusRuntimeException(UNAUTHENTICATED.withDescription("API token is missing"));
if (!authToken.equals(apiToken))
throw new StatusRuntimeException(UNAUTHENTICATED.withDescription("Invalid API token"));
}
}