TINKERPOP-1600 Added base64 encoded string to sasl challenge
Project: http://git-wip-us.apache.org/repos/asf/tinkerpop/repo Commit: http://git-wip-us.apache.org/repos/asf/tinkerpop/commit/e80a4cd1 Tree: http://git-wip-us.apache.org/repos/asf/tinkerpop/tree/e80a4cd1 Diff: http://git-wip-us.apache.org/repos/asf/tinkerpop/diff/e80a4cd1 Branch: refs/heads/TINKERPOP-1602 Commit: e80a4cd172746dc393711cc095e229a4d5e78629 Parents: 802ff9f Author: Stephen Mallette <sp...@genoprime.com> Authored: Mon Jan 16 15:22:07 2017 -0500 Committer: Stephen Mallette <sp...@genoprime.com> Committed: Wed Jan 18 06:56:11 2017 -0500 ---------------------------------------------------------------------- CHANGELOG.asciidoc | 2 +- docs/src/dev/io/graphson.asciidoc | 5 ++--- docs/src/upgrade/release-3.2.x-incubating.asciidoc | 16 ++++++++++++++++ .../apache/tinkerpop/gremlin/driver/Handler.java | 15 +++++++++++++-- .../server/handler/SaslAuthenticationHandler.java | 14 ++++++++++++-- 5 files changed, 44 insertions(+), 8 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/e80a4cd1/CHANGELOG.asciidoc ---------------------------------------------------------------------- diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 9471b9f..4f3f9ce 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -26,6 +26,7 @@ image::https://raw.githubusercontent.com/apache/tinkerpop/master/docs/static/ima TinkerPop 3.2.4 (Release Date: NOT OFFICIALLY RELEASED YET) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +* SASL negotiation supports both a byte array and Base64 encoded bytes as a string for authentication to Gremlin Server. * Deprecated `TinkerIoRegistry` replacing it with the more consistently named `TinkerIoRegistryV1d0`. * Made error messaging more consistent during result iteration timeouts in Gremlin Server. * `PathRetractionStrategy` does not add a `NoOpBarrierStep` to the end of local children as its wasted computation in 99% of traversals. @@ -580,7 +581,6 @@ image::https://raw.githubusercontent.com/apache/tinkerpop/master/docs/static/ima TinkerPop 3.1.6 (Release Date: NOT OFFICIALLY RELEASED YET) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -* Bumped to Groovy 2.4.8. * Fixed bug in `IncidentToAdjacentStrategy`, it was missing some invalidating steps. * Returned a confirmation on session close from Gremlin Server. * Use non-default port for running tests on Gremlin Server. http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/e80a4cd1/docs/src/dev/io/graphson.asciidoc ---------------------------------------------------------------------- diff --git a/docs/src/dev/io/graphson.asciidoc b/docs/src/dev/io/graphson.asciidoc index cdc9880..fe56c27 100644 --- a/docs/src/dev/io/graphson.asciidoc +++ b/docs/src/dev/io/graphson.asciidoc @@ -1299,7 +1299,7 @@ ResponseMessage Authentication Challenge ^^^^^^^^^^^^^^^^^^^^^^^^ -When authentication is enabled, an initial request to the server will result in an authentication challenge. The typical response message will appear as follows, but handling it could be different dependending on the SASL implementation (e.g. multiple challenges maybe requested in some cases, but no in the default provided by Gremlin Server). +When authentication is enabled, an initial request to the server will result in an authentication challenge. The typical response message will appear as follows, but handling it could be different dependending on the SASL implementation (e.g. multiple challenges maybe requested in some cases, but not in the default provided by Gremlin Server). [source,json] ---- @@ -4262,14 +4262,13 @@ The following `RequestMessage` is an example of a sessionless request for a scri } ---- - ResponseMessage ~~~~~~~~~~~~~~~ Authentication Challenge ^^^^^^^^^^^^^^^^^^^^^^^^ -When authentication is enabled, an initial request to the server will result in an authentication challenge. The typical response message will appear as follows, but handling it could be different dependending on the SASL implementation (e.g. multiple challenges maybe requested in some cases, but no in the default provided by Gremlin Server). +When authentication is enabled, an initial request to the server will result in an authentication challenge. The typical response message will appear as follows, but handling it could be different dependending on the SASL implementation (e.g. multiple challenges maybe requested in some cases, but not in the default provided by Gremlin Server). [source,json] ---- http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/e80a4cd1/docs/src/upgrade/release-3.2.x-incubating.asciidoc ---------------------------------------------------------------------- diff --git a/docs/src/upgrade/release-3.2.x-incubating.asciidoc b/docs/src/upgrade/release-3.2.x-incubating.asciidoc index 8d73baed..b478b96 100644 --- a/docs/src/upgrade/release-3.2.x-incubating.asciidoc +++ b/docs/src/upgrade/release-3.2.x-incubating.asciidoc @@ -240,6 +240,22 @@ and defaults to `false`. See: link:https://issues.apache.org/jira/browse/TINKERPOP-932[TINKERPOP-932], link:http://tinkerpop.apache.org/docs/current/dev/provider/#_session_opprocessor[Provider Documentation - Session OpProcessor] +SASL Authentication ++++++++++++++++++++ + +Gremlin Supports SASL based authentication. The server accepts either a byte array or Base64 encoded String as the in +the `sasl` argument on the `RequestMessage`, however it sends back a byte array only. Some serializers or serializer +configurations don't work well with that approach (specifically the "toString" configuration on the Gryo serializer) as +the byte array is returned in the `ResponseMessage` result. In the case of the "toString" serializer the byte array +gets "toString'd" and the can't be read by the client. + +In 3.2.4, the byte array is still returned in the `ResponseMessage` result, but is also returned in the status +attributes under a `sasl` key as a Base64 encoded string. In this way, the client has options on how it chooses to +process the authentication response and the change remains backward compatible. Drivers should upgrade to using the +Base64 encoded string however as the old approach will likely be removed in the future. + +See: link:https://issues.apache.org/jira/browse/TINKERPOP-1600[TINKERPOP-1600] + TinkerPop 3.2.3 --------------- http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/e80a4cd1/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/Handler.java ---------------------------------------------------------------------- diff --git a/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/Handler.java b/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/Handler.java index 681354c..650bc2f 100644 --- a/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/Handler.java +++ b/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/Handler.java @@ -36,6 +36,7 @@ import org.slf4j.LoggerFactory; import java.net.InetSocketAddress; import java.security.PrivilegedExceptionAction; import java.security.PrivilegedActionException; +import java.util.Base64; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -70,6 +71,9 @@ final class Handler { private static final Map<String, String> SASL_PROPERTIES = new HashMap<String, String>() {{ put(Sasl.SERVER_AUTH, "true"); }}; private static final byte[] NULL_CHALLENGE = new byte[0]; + private static final Base64.Encoder BASE64_ENCODER = Base64.getEncoder(); + private static final Base64.Decoder BASE64_DECODER = Base64.getDecoder(); + private final AuthProperties authProps; public GremlinSaslAuthenticationHandler(final AuthProperties authProps) { @@ -103,9 +107,16 @@ final class Handler { messageBuilder.addArg(Tokens.ARGS_SASL_MECHANISM, getMechanism()); messageBuilder.addArg(Tokens.ARGS_SASL, saslClient.get().hasInitialResponse() ? - evaluateChallenge(subject, saslClient, NULL_CHALLENGE) : null); + BASE64_ENCODER.encodeToString(evaluateChallenge(subject, saslClient, NULL_CHALLENGE)) : null); } else { - messageBuilder.addArg(Tokens.ARGS_SASL, evaluateChallenge(subject, saslClient, (byte[])response.getResult().getData())); + // the server sends base64 encoded sasl as well as the byte array. the byte array will eventually be + // phased out, but is present now for backward compatibility in 3.2.x + final String base64sasl = response.getStatus().getAttributes().containsKey(Tokens.ARGS_SASL) ? + response.getStatus().getAttributes().get(Tokens.ARGS_SASL).toString() : + BASE64_ENCODER.encodeToString((byte[]) response.getResult().getData()); + + messageBuilder.addArg(Tokens.ARGS_SASL, BASE64_ENCODER.encodeToString( + evaluateChallenge(subject, saslClient, BASE64_DECODER.decode(base64sasl)))); } channelHandlerContext.writeAndFlush(messageBuilder.create()); } else { http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/e80a4cd1/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/handler/SaslAuthenticationHandler.java ---------------------------------------------------------------------- diff --git a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/handler/SaslAuthenticationHandler.java b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/handler/SaslAuthenticationHandler.java index 4bb7879..6cb0ddb 100644 --- a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/handler/SaslAuthenticationHandler.java +++ b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/handler/SaslAuthenticationHandler.java @@ -28,6 +28,9 @@ import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.util.Base64; +import java.util.HashMap; +import java.util.Map; + import org.apache.tinkerpop.gremlin.driver.Tokens; import org.apache.tinkerpop.gremlin.driver.message.RequestMessage; import org.apache.tinkerpop.gremlin.driver.message.ResponseMessage; @@ -50,6 +53,8 @@ import org.slf4j.LoggerFactory; @ChannelHandler.Sharable public class SaslAuthenticationHandler extends ChannelInboundHandlerAdapter { private static final Logger logger = LoggerFactory.getLogger(SaslAuthenticationHandler.class); + private static final Base64.Decoder BASE64_DECODER = Base64.getDecoder(); + private static final Base64.Encoder BASE64_ENCODER = Base64.getEncoder(); private final Authenticator authenticator; @@ -80,7 +85,7 @@ public class SaslAuthenticationHandler extends ChannelInboundHandlerAdapter { if (saslObject instanceof byte[]) { saslResponse = (byte[]) saslObject; } else if(saslObject instanceof String) { - saslResponse = Base64.getDecoder().decode((String) saslObject); + saslResponse = BASE64_DECODER.decode((String) saslObject); } else { final ResponseMessage error = ResponseMessage.build(request.get()) .statusMessage("Incorrect type for : " + Tokens.ARGS_SASL + " - byte[] or base64 encoded String is expected") @@ -101,8 +106,13 @@ public class SaslAuthenticationHandler extends ChannelInboundHandlerAdapter { final RequestMessage original = request.get(); ctx.fireChannelRead(original); } else { - // not done here - send back the sasl message for next challenge + // not done here - send back the sasl message for next challenge. note that we send back + // the base64 encoded sasl as well as the byte array. the byte array will eventually be + // phased out, but is present now for backward compatibility in 3.2.x + final Map<String,Object> metadata = new HashMap<>(); + metadata.put(Tokens.ARGS_SASL, BASE64_ENCODER.encodeToString(saslMessage)); final ResponseMessage authenticate = ResponseMessage.build(requestMessage) + .statusAttributes(metadata) .code(ResponseStatusCode.AUTHENTICATE).result(saslMessage).create(); ctx.writeAndFlush(authenticate); }