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();

Reply via email to