You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@drill.apache.org by am...@apache.org on 2017/09/03 06:54:01 UTC

[2/2] drill git commit: DRILL-5726: Support Impersonation without authentication for REST API

DRILL-5726: Support Impersonation without authentication for REST API

DRILL-5726: Changes after code review.

close apache/drill#910


Project: http://git-wip-us.apache.org/repos/asf/drill/repo
Commit: http://git-wip-us.apache.org/repos/asf/drill/commit/789b83d7
Tree: http://git-wip-us.apache.org/repos/asf/drill/tree/789b83d7
Diff: http://git-wip-us.apache.org/repos/asf/drill/diff/789b83d7

Branch: refs/heads/master
Commit: 789b83d773b9022638c3e9841e9f13a7033cb60b
Parents: 2c470de
Author: Arina Ielchiieva <ar...@gmail.com>
Authored: Wed Aug 23 16:17:11 2017 +0300
Committer: Aman Sinha <as...@maprtech.com>
Committed: Sat Sep 2 23:00:07 2017 -0700

----------------------------------------------------------------------
 .../drill/exec/server/rest/DrillRestServer.java | 28 +++++++--
 .../drill/exec/server/rest/QueryResources.java  |  7 ++-
 .../drill/exec/server/rest/UserNameFilter.java  | 61 ++++++++++++++++++++
 .../drill/exec/server/rest/WebServer.java       | 19 +++++-
 .../src/main/resources/rest/query/query.ftl     | 48 ++++++++++++++-
 5 files changed, 153 insertions(+), 10 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/drill/blob/789b83d7/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/DrillRestServer.java
----------------------------------------------------------------------
diff --git a/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/DrillRestServer.java b/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/DrillRestServer.java
index e88d1b0..bd01fea 100644
--- a/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/DrillRestServer.java
+++ b/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/DrillRestServer.java
@@ -1,4 +1,4 @@
-/**
+/*
  * 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
@@ -21,6 +21,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.jaxrs.base.JsonMappingExceptionMapper;
 import com.fasterxml.jackson.jaxrs.base.JsonParseExceptionMapper;
 import com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider;
+import com.google.common.base.Strings;
 import org.apache.drill.common.config.DrillConfig;
 import org.apache.drill.exec.ExecConstants;
 import org.apache.drill.exec.memory.BufferAllocator;
@@ -188,7 +189,6 @@ public class DrillRestServer extends ResourceConfig {
 
     @Override
     public WebUserConnection provide() {
-      final HttpSession session = request.getSession();
       final DrillbitContext drillbitContext = workManager.getContext();
       final DrillConfig config = drillbitContext.getConfig();
 
@@ -198,9 +198,9 @@ public class DrillRestServer extends ResourceConfig {
                       config.getLong(ExecConstants.HTTP_SESSION_MEMORY_RESERVATION),
                       config.getLong(ExecConstants.HTTP_SESSION_MEMORY_MAXIMUM));
 
-      final Principal sessionUserPrincipal = new AnonDrillUserPrincipal();
+      final Principal sessionUserPrincipal = createSessionUserPrincipal(config, request);
 
-      // Create new UserSession for each request from Anonymous user
+      // Create new UserSession for each request from non-authenticated user
       final UserSession drillUserSession = UserSession.Builder.newBuilder()
               .withCredentials(UserBitShared.UserCredentials.newBuilder()
                       .setUserName(sessionUserPrincipal.getName())
@@ -230,6 +230,26 @@ public class DrillRestServer extends ResourceConfig {
     public void dispose(WebUserConnection instance) {
 
     }
+
+    /**
+     * Creates session user principal. If impersonation is enabled without authentication and User-Name header is present and valid,
+     * will create session user principal with provided user name, otherwise anonymous user name will be used.
+     * In both cases session user principal will have admin rights.
+     *
+     * @param config drill config
+     * @param request client request
+     * @return session user principal
+     */
+    private Principal createSessionUserPrincipal(DrillConfig config, HttpServletRequest request) {
+      if (WebServer.isImpersonationOnlyEnabled(config)) {
+        final String userName = request.getHeader("User-Name");
+        if (!Strings.isNullOrEmpty(userName)) {
+          return new DrillUserPrincipal(userName, true);
+        }
+      }
+      return new AnonDrillUserPrincipal();
+    }
+
   }
 
   // Provider which injects DrillUserPrincipal directly instead of getting it from SecurityContext and typecasting

http://git-wip-us.apache.org/repos/asf/drill/blob/789b83d7/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/QueryResources.java
----------------------------------------------------------------------
diff --git a/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/QueryResources.java b/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/QueryResources.java
index 99e26ff..c46c4b5 100644
--- a/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/QueryResources.java
+++ b/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/QueryResources.java
@@ -54,7 +54,12 @@ public class QueryResources {
   @Path("/query")
   @Produces(MediaType.TEXT_HTML)
   public Viewable getQuery() {
-    return ViewableWithPermissions.create(authEnabled.get(), "/rest/query/query.ftl", sc);
+    return ViewableWithPermissions.create(
+        authEnabled.get(),
+        "/rest/query/query.ftl",
+        sc,
+        // if impersonation is enabled without authentication, will provide mechanism to add user name to request header from Web UI
+        WebServer.isImpersonationOnlyEnabled(work.getContext().getConfig()));
   }
 
   @POST

http://git-wip-us.apache.org/repos/asf/drill/blob/789b83d7/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/UserNameFilter.java
----------------------------------------------------------------------
diff --git a/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/UserNameFilter.java b/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/UserNameFilter.java
new file mode 100644
index 0000000..b8c898c
--- /dev/null
+++ b/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/UserNameFilter.java
@@ -0,0 +1,61 @@
+/*
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.drill.exec.server.rest;
+
+import com.google.common.base.Strings;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.ws.rs.HttpMethod;
+import java.io.IOException;
+
+/**
+ * Responsible for filtering out POST requests that do not contain valid <b>User-Name</b> header.
+ */
+public class UserNameFilter implements Filter {
+
+  @Override
+  public void init(FilterConfig filterConfig) throws ServletException {
+
+  }
+
+  @Override
+  public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
+    HttpServletRequest request = (HttpServletRequest) req;
+    HttpServletResponse response = (HttpServletResponse) resp;
+
+    if (HttpMethod.POST.equalsIgnoreCase(request.getMethod()) && Strings.isNullOrEmpty(request.getHeader("User-Name"))) {
+      response.reset();
+      response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED, "User-Name header is not set");
+      return;
+    }
+
+    chain.doFilter(request, response);
+  }
+
+  @Override
+  public void destroy() {
+
+  }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/drill/blob/789b83d7/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/WebServer.java
----------------------------------------------------------------------
diff --git a/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/WebServer.java b/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/WebServer.java
index 1706b71..3fc95cd 100644
--- a/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/WebServer.java
+++ b/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/WebServer.java
@@ -1,4 +1,4 @@
-/**
+/*
  * 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
@@ -20,7 +20,6 @@ package org.apache.drill.exec.server.rest;
 import com.codahale.metrics.MetricRegistry;
 import com.codahale.metrics.servlets.MetricsServlet;
 import com.codahale.metrics.servlets.ThreadDumpServlet;
-import com.google.common.base.Strings;
 import com.google.common.collect.ImmutableSet;
 import org.apache.commons.lang3.RandomStringUtils;
 import org.apache.commons.lang3.StringUtils;
@@ -126,6 +125,16 @@ public class WebServer implements AutoCloseable {
   private static final String DRILL_ICON_RESOURCE_RELATIVE_PATH = "img/drill.ico";
 
   /**
+   * Checks if only impersonation is enabled.
+   *
+   * @param config Drill configuration
+   * @return true if impersonation without authentication is enabled, false otherwise
+   */
+  public static boolean isImpersonationOnlyEnabled(DrillConfig config) {
+    return !config.getBoolean(ExecConstants.USER_AUTHENTICATION_ENABLED) && config.getBoolean(ExecConstants.IMPERSONATION_ENABLED);
+  }
+
+  /**
    * Start the web server including setup.
    * @throws Exception
    */
@@ -185,6 +194,12 @@ public class WebServer implements AutoCloseable {
       servletContextHandler.setSessionHandler(createSessionHandler(servletContextHandler.getSecurityHandler()));
     }
 
+    if (isImpersonationOnlyEnabled(workManager.getContext().getConfig())) {
+      for (String path : new String[]{"/query", "/query.json"}) {
+        servletContextHandler.addFilter(UserNameFilter.class, path, EnumSet.of(DispatcherType.REQUEST));
+      }
+    }
+
     if (config.getBoolean(ExecConstants.HTTP_CORS_ENABLED)) {
       FilterHolder holder = new FilterHolder(CrossOriginFilter.class);
       holder.setInitParameter(CrossOriginFilter.ALLOWED_ORIGINS_PARAM,

http://git-wip-us.apache.org/repos/asf/drill/blob/789b83d7/exec/java-exec/src/main/resources/rest/query/query.ftl
----------------------------------------------------------------------
diff --git a/exec/java-exec/src/main/resources/rest/query/query.ftl b/exec/java-exec/src/main/resources/rest/query/query.ftl
index 5033aca..f9765eb 100644
--- a/exec/java-exec/src/main/resources/rest/query/query.ftl
+++ b/exec/java-exec/src/main/resources/rest/query/query.ftl
@@ -11,6 +11,9 @@
 
 <#include "*/generic.ftl">
 <#macro page_head>
+    <#if model?? && model>
+      <script src="/static/js/jquery.form.js"></script>
+    </#if>
 </#macro>
 
 <#macro page_body>
@@ -21,8 +24,16 @@
     <button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times;</button>
     Sample SQL query: <strong>SELECT * FROM cp.`employee.json` LIMIT 20</strong>
   </div>
-  <form role="form" action="/query" method="POST">
-    <div class="form-group">
+
+  <#if model?? && model>
+     <div class="form-group">
+       <label for="userName">User Name</label>
+       <input type="text" size="30" name="userName" id="userName" placeholder="User Name">
+     </div>
+  </#if>
+
+  <form role="form" id="queryForm" action="/query" method="POST">
+      <div class="form-group">
       <label for="queryType">Query Type</label>
       <div class="radio">
         <label>
@@ -47,8 +58,39 @@
       <label for="query">Query</label>
       <textarea class="form-control" id="query" rows="5" name="query" style="font-family: Courier;"></textarea>
     </div>
-    <button type="submit" class="btn btn-default">Submit</button>
+
+    <button class="btn btn-default" type=<#if model?? && model>"button" onclick="doSubmit()"<#else>"submit"</#if>>
+      Submit
+    </button>
   </form>
+
+    <#if model?? && model>
+      <script>
+        function doSubmit() {
+          var userName = document.getElementById("userName").value;
+          if (!userName.trim()) {
+              alert("Please fill in User Name field");
+              return;
+          }
+          $.ajax({
+            type: "POST",
+            beforeSend: function (request) {
+              request.setRequestHeader("User-Name", userName);
+            },
+            url: "/query",
+            data: $("#queryForm").serializeArray(),
+            success: function (response) {
+              var newDoc = document.open("text/html", "replace");
+              newDoc.write(response);
+              newDoc.close();
+            },
+            error: function (request, textStatus, errorThrown) {
+              alert(errorThrown);
+            }
+          });
+        }
+      </script>
+    </#if>
 </#macro>
 
 <@page_html/>