-
Notifications
You must be signed in to change notification settings - Fork 81
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
c895513
commit 343e103
Showing
39 changed files
with
1,341 additions
and
511 deletions.
There are no files selected for viewing
53 changes: 0 additions & 53 deletions
53
Util/src/main/java/io/deephaven/util/auth/AuthContext.java
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
Flight's gRPC call for Handshake() takes a HandshakeRequest, which ostensibly provides both a payload in bytes and a | ||
protocol version int64, but the protocol version value is never written by current FlightClient implementations, leaving | ||
servers to only recognize the authentication details by the payload bytes. | ||
|
||
For the provided Flight.BasicAuth payload, no indicator is included that the payload _is_ a basic username/password pair | ||
to authenticate with, so Deephaven prefers a specific envelope, both to confirm that the payload is the envelope, and | ||
also to provide a specific type to expect the nested payload to be. This does mean that for typed payloads, there will | ||
be three levels of nesting - the HandshakeRequest will contain a Deephaven TypedAuthenticationPayload, which will then | ||
contain the actual provided details. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
plugins { | ||
id 'java-library' | ||
id 'io.deephaven.project.register' | ||
} | ||
|
||
description 'authentication: Deephaven authentication and identity' | ||
|
||
dependencies { | ||
api project(':Base') | ||
api project(':proto:proto-backplane-grpc') | ||
|
||
Classpaths.inheritArrow(project, 'flight-core', 'implementation') | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
io.deephaven.project.ProjectType=JAVA_PUBLIC |
31 changes: 31 additions & 0 deletions
31
authentication/src/main/java/io/deephaven/auth/AnonymousAuthenticationHandler.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
package io.deephaven.auth; | ||
|
||
import java.nio.ByteBuffer; | ||
import java.util.Optional; | ||
|
||
/** | ||
* Handler to accept an empty payload and accept that user as anonymous. To prevent anonymous access, do not enable this | ||
* authentication handler. | ||
*/ | ||
public class AnonymousAuthenticationHandler implements AuthenticationRequestHandler { | ||
@Override | ||
public String getAuthType() { | ||
return "Anonymous"; | ||
} | ||
|
||
@Override | ||
public Optional<AuthContext> login(long protocolVersion, ByteBuffer payload, HandshakeResponseListener listener) { | ||
if (!payload.hasRemaining()) { | ||
return Optional.of(new AuthContext.Anonymous()); | ||
} | ||
return Optional.empty(); | ||
} | ||
|
||
@Override | ||
public Optional<AuthContext> login(String payload, MetadataResponseListener listener) { | ||
if (payload.length() == 0) { | ||
return Optional.of(new AuthContext.Anonymous()); | ||
} | ||
return Optional.empty(); | ||
} | ||
} |
36 changes: 36 additions & 0 deletions
36
authentication/src/main/java/io/deephaven/auth/AuthContext.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
/** | ||
* Copyright (c) 2016-2022 Deephaven Data Labs and Patent Pending | ||
*/ | ||
package io.deephaven.auth; | ||
|
||
import io.deephaven.base.log.LogOutput; | ||
import io.deephaven.base.log.LogOutputAppendable; | ||
import io.deephaven.io.log.impl.LogOutputStringImpl; | ||
|
||
public abstract class AuthContext implements LogOutputAppendable { | ||
|
||
@Override | ||
public abstract LogOutput append(LogOutput logOutput); | ||
|
||
@Override | ||
final public String toString() { | ||
return new LogOutputStringImpl().append(this).toString(); | ||
} | ||
|
||
/** | ||
* A trivial auth context that allows a user to do everything the APIs allow. | ||
*/ | ||
public static class SuperUser extends AuthContext { | ||
@Override | ||
public LogOutput append(LogOutput logOutput) { | ||
return logOutput.append("SuperUser"); | ||
} | ||
} | ||
|
||
public static class Anonymous extends AuthContext { | ||
@Override | ||
public LogOutput append(LogOutput logOutput) { | ||
return logOutput.append("Anonymous"); | ||
} | ||
} | ||
} |
8 changes: 8 additions & 0 deletions
8
authentication/src/main/java/io/deephaven/auth/AuthenticationException.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package io.deephaven.auth; | ||
|
||
/** | ||
* An error occurred and this handshake to authenticate has failed for some reason. Details are not provided to the | ||
* user. | ||
*/ | ||
public class AuthenticationException extends Exception { | ||
} |
68 changes: 68 additions & 0 deletions
68
authentication/src/main/java/io/deephaven/auth/AuthenticationRequestHandler.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
package io.deephaven.auth; | ||
|
||
import java.nio.ByteBuffer; | ||
import java.util.Optional; | ||
|
||
/** | ||
* Simple interface to handle incoming authentication requests from flight/barrage clients, via Handshake or the Flight | ||
* Authentication header. This is intended to be a low-level way to handle incoming payload bytes. | ||
*/ | ||
public interface AuthenticationRequestHandler { | ||
|
||
/** | ||
* This handler can be referred to via both Arrow Flight's original Auth and Auth2. | ||
* | ||
* To use via the original Arrow Flight Handshake, the request should be sent in a | ||
* {@link io.deephaven.proto.backplane.grpc.WrappedAuthenticationRequest} with this handler's identity string. | ||
* | ||
* To use via Arrow Flight Auth 2's metadata header, then the | ||
* {@link org.apache.arrow.flight.auth2.Auth2Constants#AUTHORIZATION_HEADER} should be prefixed with this handler's | ||
* identity string. | ||
* | ||
* @return the type string used to identify the handler | ||
*/ | ||
String getAuthType(); | ||
|
||
/** | ||
* Given a protocol version (very likely to be zero) and payload bytes, if possible authenticate this user. If the | ||
* handler can correctly decode the payload and confirm the user's identity, an appropriate UserContext should be | ||
* returned. If the payload is correctly decoded and definitely isn't a valid user, an exception may be thrown. If | ||
* there is ambiguity in decoding the payload (leading to apparent "not a valid user") or the payload cannot be | ||
* decoded, an empty optional should be returned. | ||
* | ||
* Note that regular arrow flight clients cannot specify the protocolVersion; to be compatible with flight auth | ||
* assume protocolVersion will be zero. | ||
* | ||
* @param protocolVersion Mostly unused, this is an allowed field to set on HandshakeRequests from the Flight gRPC | ||
* call. | ||
* @param payload The byte payload of the handshake, such as an encoded protobuf. | ||
* @param listener The handshake response observer, which enables multi-request authentication. | ||
* @return AuthContext for this user if applicable else Empty | ||
*/ | ||
Optional<AuthContext> login(long protocolVersion, ByteBuffer payload, HandshakeResponseListener listener) | ||
throws AuthenticationException; | ||
|
||
/** | ||
* Given a payload string, if possible authenticate this user. If the handler can correctly decode the payload and | ||
* confirm the user's identity, an appropriate UserContext should be returned. If the payload is correctly decoded | ||
* and definitely isn't a valid user, an exception may be thrown. If there is ambiguity in decoding the payload | ||
* (leading to apparent "not a valid user") or the payload cannot be decoded, an empty optional should be returned. | ||
* | ||
* Note that metadata can only be sent with the initial gRPC response; multi-message authentication via gRPC | ||
* metadata headers require multiple gRPC call attempts. | ||
* | ||
* @param payload The byte payload of the {@code Authorization} header, such as an encoded protobuf or b64 encoded | ||
* string. | ||
* @param listener The metadata response observer, which enables multi-request authentication. | ||
* @return AuthContext for this user if applicable else Empty | ||
*/ | ||
Optional<AuthContext> login(String payload, MetadataResponseListener listener) | ||
throws AuthenticationException; | ||
|
||
interface HandshakeResponseListener { | ||
void respond(long protocolVersion, ByteBuffer payload); | ||
} | ||
interface MetadataResponseListener { | ||
void addHeader(String key, String string); | ||
} | ||
} |
100 changes: 100 additions & 0 deletions
100
authentication/src/main/java/io/deephaven/auth/BasicAuthMarshaller.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
package io.deephaven.auth; | ||
|
||
import com.google.protobuf.CodedInputStream; | ||
import com.google.protobuf.WireFormat; | ||
import org.apache.arrow.flight.auth2.Auth2Constants; | ||
import org.apache.arrow.flight.impl.Flight; | ||
|
||
import java.io.IOException; | ||
import java.nio.ByteBuffer; | ||
import java.nio.charset.StandardCharsets; | ||
import java.util.Base64; | ||
import java.util.Optional; | ||
|
||
import static com.google.protobuf.WireFormat.WIRETYPE_LENGTH_DELIMITED; | ||
|
||
/** | ||
* Manually decode the payload as a BasicAuth message, confirm that only tags 2 and 3 are present as strings, otherwise | ||
* pass. This is stricter than a usual protobuf decode, under the assumption that FlightClient will always only write | ||
* those two fields, and user code couldn't customize the payload further to repeatedly write those fields or any other | ||
* field. | ||
* | ||
* Despite being stricter than a standard protobuf decode, this is also very generic and might accidentally match the | ||
* wrong message type. For this reason, this handler should not run until other more selective handlers have finished. | ||
* | ||
* This class delegates to a typed auth handler once it is certain that the payload appears to be a BasicAuth value. | ||
*/ | ||
public class BasicAuthMarshaller implements AuthenticationRequestHandler { | ||
public interface Handler { | ||
Optional<AuthContext> login(String username, String password) throws AuthenticationException; | ||
} | ||
|
||
private final Handler handler; | ||
|
||
public BasicAuthMarshaller(Handler handler) { | ||
this.handler = handler; | ||
} | ||
|
||
@Override | ||
public String getAuthType() { | ||
return Auth2Constants.BASIC_PREFIX.trim(); | ||
} | ||
|
||
@Override | ||
public Optional<AuthContext> login(long protocolVersion, ByteBuffer payload, HandshakeResponseListener listener) | ||
throws AuthenticationException { | ||
CodedInputStream inputStream = CodedInputStream.newInstance(payload); | ||
|
||
String username = null, password = null; | ||
|
||
try { | ||
while (!inputStream.isAtEnd()) { | ||
int tag = inputStream.readTag(); | ||
switch (WireFormat.getTagFieldNumber(tag)) { | ||
case Flight.BasicAuth.USERNAME_FIELD_NUMBER: { | ||
if (username == null && WireFormat.getTagWireType(tag) == WIRETYPE_LENGTH_DELIMITED) { | ||
username = inputStream.readString(); | ||
} else { | ||
return Optional.empty(); | ||
} | ||
break; | ||
} | ||
case Flight.BasicAuth.PASSWORD_FIELD_NUMBER: { | ||
if (password == null && WireFormat.getTagWireType(tag) == WIRETYPE_LENGTH_DELIMITED) { | ||
password = inputStream.readString(); | ||
} else { | ||
return Optional.empty(); | ||
} | ||
break; | ||
} | ||
default: | ||
// Found an unexpected field; this is not a BasicAuth request. | ||
return Optional.empty(); | ||
} | ||
} | ||
} catch (IOException e) { | ||
return Optional.empty(); | ||
} | ||
if (username != null && password != null) { | ||
// This is likely to be an un-wrapped BasicAuth instance, attempt to read it and login with it | ||
return handler.login(username, password); | ||
} | ||
|
||
return Optional.empty(); | ||
} | ||
|
||
@Override | ||
public Optional<AuthContext> login(String payload, MetadataResponseListener listener) | ||
throws AuthenticationException { | ||
// The value has the format Base64(<username>:<password>) | ||
final String authDecoded = new String(Base64.getDecoder().decode(payload), StandardCharsets.UTF_8); | ||
final int colonPos = authDecoded.indexOf(':'); | ||
if (colonPos == -1) { | ||
return Optional.empty(); | ||
} | ||
|
||
final String username = authDecoded.substring(0, colonPos); | ||
final String password = authDecoded.substring(colonPos + 1); | ||
return handler.login(username, password); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.