Repository: tinkerpop Updated Branches: refs/heads/tp32 014575a00 -> 021831eda
Abstract over http auth for extensibility Abstracting over the http authentication allows for easy extensibility for users/implementors to provide their own classes for http auth beyond basic auth. The general issue is that there is a fixed overhead to hashing passwords securely. This change allows for implementing things like HMAC token auth and plugging them in easily to the gremlin server. Project: http://git-wip-us.apache.org/repos/asf/tinkerpop/repo Commit: http://git-wip-us.apache.org/repos/asf/tinkerpop/commit/69dd924d Tree: http://git-wip-us.apache.org/repos/asf/tinkerpop/tree/69dd924d Diff: http://git-wip-us.apache.org/repos/asf/tinkerpop/diff/69dd924d Branch: refs/heads/tp32 Commit: 69dd924dc9219791330007e9a5a2d1dabda4cfb1 Parents: f73d7ca Author: Keith Lohnes <krloh...@us.ibm.com> Authored: Mon Mar 20 13:37:40 2017 -0400 Committer: Keith Lohnes <krloh...@us.ibm.com> Committed: Tue Apr 4 09:22:19 2017 -0400 ---------------------------------------------------------------------- .../gremlin/server/AbstractChannelizer.java | 25 ++++++++++++-- .../tinkerpop/gremlin/server/Settings.java | 18 +++++++++- .../gremlin/server/channel/HttpChannelizer.java | 25 ++++++++++++-- .../handler/AbstractAuthenticationHandler.java | 35 +++++++++++++++++++ .../handler/HttpBasicAuthenticationHandler.java | 5 ++- .../server/GremlinServerHttpIntegrateTest.java | 36 ++++++++++++++++++++ 6 files changed, 135 insertions(+), 9 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/69dd924d/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/AbstractChannelizer.java ---------------------------------------------------------------------- diff --git a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/AbstractChannelizer.java b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/AbstractChannelizer.java index d28fd4f..8887363 100644 --- a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/AbstractChannelizer.java +++ b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/AbstractChannelizer.java @@ -30,6 +30,7 @@ import org.apache.tinkerpop.gremlin.driver.ser.GraphSONMessageSerializerV1d0; import org.apache.tinkerpop.gremlin.driver.ser.GryoMessageSerializerV1d0; import org.apache.tinkerpop.gremlin.groovy.engine.GremlinExecutor; import org.apache.tinkerpop.gremlin.server.auth.Authenticator; +import org.apache.tinkerpop.gremlin.server.handler.AbstractAuthenticationHandler; import org.apache.tinkerpop.gremlin.server.handler.IteratorHandler; import org.apache.tinkerpop.gremlin.server.handler.OpExecutorHandler; import org.apache.tinkerpop.gremlin.server.handler.OpSelectorHandler; @@ -153,15 +154,33 @@ public abstract class AbstractChannelizer extends ChannelInitializer<SocketChann finalize(pipeline); } + protected AbstractAuthenticationHandler createAuthenticationHandler(final Settings.AuthenticationSettings config) { + try { + final Class<?> clazz = Class.forName(config.authenticationHandler); + final Class[] constructorArgs = new Class[1]; + constructorArgs[0] = Authenticator.class; + return (AbstractAuthenticationHandler) clazz.getDeclaredConstructor(constructorArgs).newInstance(authenticator); + } catch (Exception ex) { + logger.warn(ex.getMessage()); + throw new IllegalStateException(String.format("Could not create/configure AuthenticationHandler %s", config.authenticationHandler), ex); + } + } + private Authenticator createAuthenticator(final Settings.AuthenticationSettings config) { + String authenticatorClass = null; + if (config.authenticator == null) { + authenticatorClass = config.className; + } else { + authenticatorClass = config.authenticator; + } try { - final Class<?> clazz = Class.forName(config.className); + final Class<?> clazz = Class.forName(authenticatorClass); final Authenticator authenticator = (Authenticator) clazz.newInstance(); authenticator.setup(config.config); return authenticator; } catch (Exception ex) { logger.warn(ex.getMessage()); - throw new IllegalStateException(String.format("Could not create/configure Authenticator %s", config.className), ex); + throw new IllegalStateException(String.format("Could not create/configure Authenticator %s", authenticator), ex); } } @@ -254,4 +273,4 @@ public abstract class AbstractChannelizer extends ChannelInitializer<SocketChann return null; } } -} \ No newline at end of file +} http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/69dd924d/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/Settings.java ---------------------------------------------------------------------- diff --git a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/Settings.java b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/Settings.java index e2f2ad5..030c2e6 100644 --- a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/Settings.java +++ b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/Settings.java @@ -384,9 +384,25 @@ public class Settings { * used to load the implementation from the classpath. Defaults to {@link AllowAllAuthenticator} when * not specified. */ + public String authenticator = AllowAllAuthenticator.class.getName(); + + /** + * The fully qualified class name of the {@link Authenticator} implementation. This class name will be + * used to load the implementation from the classpath. Defaults to {@link AllowAllAuthenticator} when + * not specified. + * @deprecated As of release 3.2.5, replaced by {@link authenticator}. + */ + @Deprecated public String className = AllowAllAuthenticator.class.getName(); /** + * The fully qualified class name of the {@link HttpAuthenticationHandler} implementation. + * This class name will be used to load the implementation from the classpath. + * Defaults to null when not specified. + */ + public String authenticationHandler = null; + + /** * A {@link Map} containing {@link Authenticator} specific configurations. Consult the * {@link Authenticator} implementation for specifics on what configurations are expected. */ @@ -424,7 +440,7 @@ public class Settings { * contain an X.509 certificate chain in PEM format. {@code null} uses the system default. */ public String trustCertChainFile = null; - + /** * Require client certificate authentication */ http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/69dd924d/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/channel/HttpChannelizer.java ---------------------------------------------------------------------- diff --git a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/channel/HttpChannelizer.java b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/channel/HttpChannelizer.java index 9e58a40..eca52a0 100644 --- a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/channel/HttpChannelizer.java +++ b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/channel/HttpChannelizer.java @@ -21,7 +21,10 @@ package org.apache.tinkerpop.gremlin.server.channel; import io.netty.channel.EventLoopGroup; import org.apache.tinkerpop.gremlin.server.AbstractChannelizer; import org.apache.tinkerpop.gremlin.server.Channelizer; +import org.apache.tinkerpop.gremlin.server.Settings; import org.apache.tinkerpop.gremlin.server.auth.AllowAllAuthenticator; +import org.apache.tinkerpop.gremlin.server.auth.Authenticator; +import org.apache.tinkerpop.gremlin.server.handler.AbstractAuthenticationHandler; import org.apache.tinkerpop.gremlin.server.handler.HttpBasicAuthenticationHandler; import org.apache.tinkerpop.gremlin.server.handler.HttpGremlinEndpointHandler; import io.netty.channel.ChannelPipeline; @@ -42,7 +45,7 @@ public class HttpChannelizer extends AbstractChannelizer { private static final Logger logger = LoggerFactory.getLogger(HttpChannelizer.class); private HttpGremlinEndpointHandler httpGremlinEndpointHandler; - private HttpBasicAuthenticationHandler authenticationHandler; + private AbstractAuthenticationHandler authenticationHandler; @Override public void init(final ServerGremlinExecutor<EventLoopGroup> serverGremlinExecutor) { @@ -68,7 +71,7 @@ public class HttpChannelizer extends AbstractChannelizer { // not occur. It may not be a safe assumption that the handler // is sharable so create a new handler each time. authenticationHandler = authenticator.getClass() == AllowAllAuthenticator.class ? - null : new HttpBasicAuthenticationHandler(authenticator); + null : createAuthenticationHandler(settings); if (authenticationHandler != null) pipeline.addLast(PIPELINE_AUTHENTICATOR, authenticationHandler); } @@ -76,6 +79,24 @@ public class HttpChannelizer extends AbstractChannelizer { pipeline.addLast("http-gremlin-handler", httpGremlinEndpointHandler); } + private AbstractAuthenticationHandler instantiateAuthenticationHandler(final Settings.AuthenticationSettings authSettings) { + final String authHandlerClass = authSettings.authenticationHandler; + if (authHandlerClass == null) { + //Keep things backwards compatible + return new HttpBasicAuthenticationHandler(authenticator); + } else { + try { + final Class<?> clazz = Class.forName(handlerClassName); + final Class[] constructorArgs = new Class[1]; + constructorArgs[0] = Authenticator.class; + return (HttpAuthenticationHandler) clazz.getDeclaredConstructor(constructorArgs).newInstance(authenticator); + } catch (Exception ex) { + logger.warn(ex.getMessage()); + throw new IllegalStateException(String.format("Could not create/configure HttpAuthenticationHandler %s", handlerClassName), ex); + } + } + } + @Override public void finalize(final ChannelPipeline pipeline) { pipeline.remove(PIPELINE_OP_SELECTOR); http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/69dd924d/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/handler/AbstractAuthenticationHandler.java ---------------------------------------------------------------------- diff --git a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/handler/AbstractAuthenticationHandler.java b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/handler/AbstractAuthenticationHandler.java new file mode 100644 index 0000000..026ad59 --- /dev/null +++ b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/handler/AbstractAuthenticationHandler.java @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.tinkerpop.gremlin.server.handler; + +import org.apache.tinkerpop.gremlin.server.auth.Authenticator; + +import io.netty.channel.ChannelInboundHandlerAdapter; + +/** + * Provides an abstraction point to allow for http auth schemes beyond basic auth. + */ +public abstract class AbstractAuthenticationHandler extends ChannelInboundHandlerAdapter { + protected final Authenticator authenticator; + + public AbstractAuthenticationHandler(final Authenticator authenticator) { + this.authenticator = authenticator; + } + +} http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/69dd924d/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/handler/HttpBasicAuthenticationHandler.java ---------------------------------------------------------------------- diff --git a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/handler/HttpBasicAuthenticationHandler.java b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/handler/HttpBasicAuthenticationHandler.java index 8732268..2370c92 100644 --- a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/handler/HttpBasicAuthenticationHandler.java +++ b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/handler/HttpBasicAuthenticationHandler.java @@ -42,13 +42,12 @@ import static org.apache.tinkerpop.gremlin.groovy.plugin.dsl.credential.Credenti * * @author Stephen Mallette (http://stephen.genoprime.com) */ -public class HttpBasicAuthenticationHandler extends ChannelInboundHandlerAdapter { - private final Authenticator authenticator; +public class HttpBasicAuthenticationHandler extends AbstractAuthenticationHandler { private final Base64.Decoder decoder = Base64.getUrlDecoder(); public HttpBasicAuthenticationHandler(final Authenticator authenticator) { - this.authenticator = authenticator; + super(authenticator); } @Override http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/69dd924d/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinServerHttpIntegrateTest.java ---------------------------------------------------------------------- diff --git a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinServerHttpIntegrateTest.java b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinServerHttpIntegrateTest.java index 78109e6..b64a7b5 100644 --- a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinServerHttpIntegrateTest.java +++ b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinServerHttpIntegrateTest.java @@ -21,6 +21,7 @@ package org.apache.tinkerpop.gremlin.server; import org.apache.tinkerpop.gremlin.driver.ser.GraphSONMessageSerializerV2d0; import org.apache.tinkerpop.gremlin.server.auth.SimpleAuthenticator; import org.apache.tinkerpop.gremlin.server.channel.HttpChannelizer; +import org.apache.tinkerpop.gremlin.server.handler.HttpBasicAuthenticationHandler; import org.apache.http.Consts; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; @@ -92,6 +93,9 @@ public class GremlinServerHttpIntegrateTest extends AbstractGremlinServerIntegra case "should401OnGETWithInvalidPasswordAuthorizationHeader": case "should401OnPOSTWithInvalidPasswordAuthorizationHeader": case "should200OnGETWithAuthorizationHeader": + case "should200OnPOSTWithAuthorizationHeaderExplicitHandlerSetting": + configureForAuthenticationWithHandlerClass(settings); + break; case "should200OnPOSTWithAuthorizationHeader": configureForAuthentication(settings); break; @@ -115,6 +119,21 @@ public class GremlinServerHttpIntegrateTest extends AbstractGremlinServerIntegra settings.authentication = authSettings; } + private void configureForAuthenticationWithHandlerClass(final Settings settings) { + final Settings.AuthenticationSettings authSettings = new Settings.AuthenticationSettings(); + authSettings.className = SimpleAuthenticator.class.getName(); + + //Add basic auth handler to make sure the reflection code path works. + authSettings.authenticationHandler = HttpBasicAuthenticationHandler.class.getName(); + + // use a credentials graph with one user in it: stephen/password + final Map<String,Object> authConfig = new HashMap<>(); + authConfig.put(SimpleAuthenticator.CONFIG_CREDENTIALS_DB, "conf/tinkergraph-credentials.properties"); + + authSettings.config = authConfig; + settings.authentication = authSettings; + } + @Deprecated private void configureForAuthenticationOld(final Settings settings) { final Settings.AuthenticationSettings authSettings = new Settings.AuthenticationSettings(); @@ -270,6 +289,23 @@ public class GremlinServerHttpIntegrateTest extends AbstractGremlinServerIntegra } @Test + public void should200OnPOSTWithAuthorizationHeaderExplicitHandlerSetting() throws Exception { + final CloseableHttpClient httpclient = HttpClients.createDefault(); + final HttpPost httppost = new HttpPost(TestClientFactory.createURLString()); + httppost.addHeader("Content-Type", "application/json"); + httppost.addHeader("Authorization", "Basic " + encoder.encodeToString("stephen:password".getBytes())); + httppost.setEntity(new StringEntity("{\"gremlin\":\"1-1\"}", Consts.UTF_8)); + + try (final CloseableHttpResponse response = httpclient.execute(httppost)) { + assertEquals(200, response.getStatusLine().getStatusCode()); + assertEquals("application/json", response.getEntity().getContentType().getValue()); + final String json = EntityUtils.toString(response.getEntity()); + final JsonNode node = mapper.readTree(json); + assertEquals(0, node.get("result").get("data").get(0).intValue()); + } + } + + @Test @Deprecated public void should200OnPOSTWithAuthorizationHeaderOld() throws Exception { final CloseableHttpClient httpclient = HttpClients.createDefault();