This is an automated email from the ASF dual-hosted git repository.

dengzh pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/hive.git


The following commit(s) were added to refs/heads/master by this push:
     new 6bd45cc6a1c HIVE-28594: Handle the issue on HS2 WebUI's LDAP 
authentication (#5561) (Zhihua Deng, reviewed by Dmitriy Fingerman, Henri 
Biestro)
6bd45cc6a1c is described below

commit 6bd45cc6a1cdfc2272d24251c330497223123d63
Author: dengzh <[email protected]>
AuthorDate: Tue Dec 10 18:09:16 2024 +0800

    HIVE-28594: Handle the issue on HS2 WebUI's LDAP authentication (#5561) 
(Zhihua Deng, reviewed by Dmitriy Fingerman, Henri Biestro)
---
 .../src/java/org/apache/hive/http/HttpServer.java  | 24 ++++++++++++--
 .../hive/service/auth/ldap/LdapAuthService.java    | 30 +++++++-----------
 .../apache/hive/service/server/HiveServer2.java    | 24 +++++---------
 .../service/servlet/LDAPAuthenticationFilter.java  | 37 +++++++++++-----------
 .../apache/hive/service/servlet/LoginServlet.java  | 11 +++++--
 .../hive-webapps/hiveserver2/loginForm.jsp         | 12 ++++++-
 .../hive/service/server/TestHS2HttpServerLDAP.java | 27 ++++++++++++++++
 7 files changed, 108 insertions(+), 57 deletions(-)

diff --git a/common/src/java/org/apache/hive/http/HttpServer.java 
b/common/src/java/org/apache/hive/http/HttpServer.java
index 8b1c8107306..f884ff8df24 100644
--- a/common/src/java/org/apache/hive/http/HttpServer.java
+++ b/common/src/java/org/apache/hive/http/HttpServer.java
@@ -25,12 +25,15 @@ import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.security.KeyStore;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.Enumeration;
 import java.util.HashMap;
+import java.util.LinkedHashMap;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 import java.util.Set;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -175,6 +178,7 @@ public class HttpServer {
     private final List<Pair<String, Class<? extends HttpServlet>>> servlets =
         new LinkedList<Pair<String, Class<? extends HttpServlet>>>();
     private boolean disableDirListing = false;
+    private final Map<String, Pair<String, Filter>> globalFilters = new 
LinkedHashMap<>();
 
     public Builder(String name) {
       Preconditions.checkArgument(name != null && !name.isEmpty(), "Name must 
be specified");
@@ -304,6 +308,12 @@ public class HttpServer {
       servlets.add(new Pair<String, Class<? extends HttpServlet>>(endpoint, 
servlet));
       return this;
     }
+
+    public Builder addGlobalFilter(String name, String pathSpec, Filter 
filter) {
+      globalFilters.put(name, Pair.create(pathSpec, filter));
+      return this;
+    }
+
     /**
      * Adds the ability to control X_FRAME_OPTIONS on HttpServer2.
      * @param xFrameEnabled - True enables X_FRAME_OPTIONS false disables it.
@@ -683,6 +693,8 @@ public class HttpServer {
       addServlet(p.getFirst(), "/" + p.getFirst(), p.getSecond());
     }
 
+    b.globalFilters.forEach((k, v) -> addFilter(k, v.getFirst(), 
v.getSecond(), webAppContext.getServletHandler()));
+
     ServletContextHandler staticCtx =
       new ServletContextHandler(contexts, "/static");
     staticCtx.setResourceBase(appDir + "/static");
@@ -702,6 +714,13 @@ public class HttpServer {
       logCtx.setResourceBase(logDir);
       logCtx.setDisplayName("logs");
     }
+
+    // Define the global filers for each servlet context except the 
staticCtx(css style).
+    Optional<Handler[]> handlers = Optional.ofNullable(contexts.getHandlers());
+    handlers.ifPresent(hs -> Arrays.stream(hs)
+        .filter(h -> h instanceof ServletContextHandler && 
!"static".equals(((ServletContextHandler) h).getDisplayName()))
+        .forEach(h -> b.globalFilters.forEach((k, v) ->
+            addFilter(k, v.getFirst(), v.getSecond(), ((ServletContextHandler) 
h).getServletHandler()))));
   }
 
   private Map<String, String> setHeaders() {
@@ -799,11 +818,12 @@ public class HttpServer {
     webAppContext.addServlet(holder, pathSpec);
   }
 
-  public void addFilter(String name, FilterHolder holder) {
+  public void addFilter(String name, String pathSpec, Filter filter, 
ServletHandler handler) {
+    FilterHolder holder = new FilterHolder(filter);
     if (name != null) {
       holder.setName(name);
     }
-    webAppContext.getServletHandler().addFilterWithMapping(holder, "/*", 
FilterMapping.ALL);
+    handler.addFilterWithMapping(holder, pathSpec, FilterMapping.ALL);
   }
 
   private static void disableDirectoryListingOnServlet(ServletContextHandler 
contextHandler) {
diff --git 
a/service/src/java/org/apache/hive/service/auth/ldap/LdapAuthService.java 
b/service/src/java/org/apache/hive/service/auth/ldap/LdapAuthService.java
index 42d38facd83..b72fe7698a4 100644
--- a/service/src/java/org/apache/hive/service/auth/ldap/LdapAuthService.java
+++ b/service/src/java/org/apache/hive/service/auth/ldap/LdapAuthService.java
@@ -38,22 +38,6 @@ public class LdapAuthService extends HttpAuthService {
   private static final Logger LOG = 
LoggerFactory.getLogger(LdapAuthService.class);
   private final PasswdAuthenticationProvider authProvider;
   
-  public LdapAuthService(HiveConf hiveConf) {
-    super(
-        
hiveConf.getVar(HiveConf.ConfVars.HIVE_SERVER2_WEBUI_HTTP_COOKIE_DOMAIN),
-        
hiveConf.getVar(HiveConf.ConfVars.HIVE_SERVER2_WEBUI_HTTP_COOKIE_PATH), 
-        (int) 
hiveConf.getTimeVar(HiveConf.ConfVars.HIVE_SERVER2_WEBUI_HTTP_COOKIE_MAX_AGE, 
TimeUnit.SECONDS),
-        hiveConf.getBoolVar(HiveConf.ConfVars.HIVE_SERVER2_USE_SSL),
-        HIVE_SERVER2_WEBUI_AUTH_COOKIE_NAME);
-    try {
-      this.authProvider = AuthenticationProviderFactory
-          
.getAuthenticationProvider(AuthenticationProviderFactory.AuthMethods.LDAP, 
hiveConf);
-      // always send secure cookies for SSL mode
-    } catch (AuthenticationException e) {
-      throw new ServiceException(e);
-    }
-  }
-
   public LdapAuthService(HiveConf hiveConf, PasswdAuthenticationProvider 
provider) {
     super(
         
hiveConf.getVar(HiveConf.ConfVars.HIVE_SERVER2_WEBUI_HTTP_COOKIE_DOMAIN),
@@ -61,7 +45,17 @@ public class LdapAuthService extends HttpAuthService {
         (int) 
hiveConf.getTimeVar(HiveConf.ConfVars.HIVE_SERVER2_WEBUI_HTTP_COOKIE_MAX_AGE, 
TimeUnit.SECONDS),
         hiveConf.getBoolVar(HiveConf.ConfVars.HIVE_SERVER2_USE_SSL),
         HIVE_SERVER2_WEBUI_AUTH_COOKIE_NAME);
-    this.authProvider = provider;
+    if (provider != null) {
+      this.authProvider = provider;
+    } else {
+      try {
+        this.authProvider = AuthenticationProviderFactory
+            
.getAuthenticationProvider(AuthenticationProviderFactory.AuthMethods.LDAP, 
hiveConf);
+        // always send secure cookies for SSL mode
+      } catch (AuthenticationException e) {
+        throw new ServiceException(e);
+      }
+    }
   }
   
   public boolean authenticate(HttpServletRequest request, HttpServletResponse 
response) {
@@ -77,7 +71,7 @@ public class LdapAuthService extends HttpAuthService {
         response.addCookie(hs2Cookie);
       }
     } catch (HttpAuthenticationException | AuthenticationException | 
UnsupportedEncodingException e) {
-      LOG.error("Error in authenticating HTTP request", e);
+      LOG.debug("Error in authenticating HTTP request", e);
       return false;
     }
     return true;
diff --git a/service/src/java/org/apache/hive/service/server/HiveServer2.java 
b/service/src/java/org/apache/hive/service/server/HiveServer2.java
index 540df7beb4e..7a4b846c83d 100644
--- a/service/src/java/org/apache/hive/service/server/HiveServer2.java
+++ b/service/src/java/org/apache/hive/service/server/HiveServer2.java
@@ -138,10 +138,8 @@ import org.apache.zookeeper.ZooDefs.Perms;
 import org.apache.zookeeper.data.ACL;
 
 import io.opentelemetry.api.GlobalOpenTelemetry;
-import org.eclipse.jetty.servlet.FilterHolder;
 import org.eclipse.jetty.servlet.ServletHolder;
 
-import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -488,23 +486,17 @@ public class HiveServer2 extends CompositeService {
           builder.addServlet("jdbcjar", JdbcJarDownloadServlet.class);
           builder.setContextRootRewriteTarget(HS2_WEBUI_ROOT_URI);
 
+          String webUIAuthMethodConfig = 
hiveConf.getVar(ConfVars.HIVE_SERVER2_WEBUI_AUTH_METHOD);
+          WebUIAuthMethod webUIAuthMethod = 
getWebUIAuthMethod(webUIAuthMethodConfig);
+          if (WebUIAuthMethod.LDAP == webUIAuthMethod) {
+            ldapAuthService = new LdapAuthService(hiveConf, 
passwdAuthenticationProvider);
+            builder.addGlobalFilter("ldap", "/*", new 
LDAPAuthenticationFilter(ldapAuthService));
+          }
           webServer = builder.build();
           webServer.addServlet("query_page", "/query_page.html", 
QueryProfileServlet.class);
           webServer.addServlet("api", "/api/*", 
QueriesRESTfulAPIServlet.class);
-
-          String webUIAuthMethodConfig = 
hiveConf.getVar(ConfVars.HIVE_SERVER2_WEBUI_AUTH_METHOD);
-          WebUIAuthMethod webUIAuthMethod = 
getWebUIAuthMethod(webUIAuthMethodConfig);
-              
-          switch (webUIAuthMethod) {
-            case LDAP:
-              if (passwdAuthenticationProvider == null) {
-                ldapAuthService = new LdapAuthService(hiveConf);
-              } else {
-                ldapAuthService = new LdapAuthService(hiveConf, 
passwdAuthenticationProvider);
-              }
-              webServer.addServlet("login", "/login", new ServletHolder(new 
LoginServlet(ldapAuthService)));
-              webServer.addFilter("ldap", new FilterHolder(new 
LDAPAuthenticationFilter(ldapAuthService)));
-              break;
+          if (ldapAuthService != null) {
+            webServer.addServlet("login", "/login", new ServletHolder(new 
LoginServlet(ldapAuthService)));
           }
         }
       }
diff --git 
a/service/src/java/org/apache/hive/service/servlet/LDAPAuthenticationFilter.java
 
b/service/src/java/org/apache/hive/service/servlet/LDAPAuthenticationFilter.java
index 7a5b2c22ccf..99016a6841e 100644
--- 
a/service/src/java/org/apache/hive/service/servlet/LDAPAuthenticationFilter.java
+++ 
b/service/src/java/org/apache/hive/service/servlet/LDAPAuthenticationFilter.java
@@ -20,12 +20,14 @@ package org.apache.hive.service.servlet;
 
 import org.apache.hive.service.auth.ldap.LdapAuthService;
 import org.apache.hive.service.server.HiveServer2;
+import org.eclipse.jetty.http.HttpMethod;
 
 import java.io.IOException;
 import javax.servlet.Filter;
 import javax.servlet.FilterChain;
 import javax.servlet.FilterConfig;
 import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletContext;
 import javax.servlet.ServletException;
 import javax.servlet.ServletRequest;
 import javax.servlet.ServletResponse;
@@ -34,8 +36,8 @@ import javax.servlet.http.HttpServletResponse;
 
 public class LDAPAuthenticationFilter implements Filter {
 
-  private static final String LOGIN_FORM_URI = "loginForm.jsp";
-  private static final String LOGIN_SERVLET_URI = "login";
+  public static final String LOGIN_FORM_URI = "/loginForm.jsp";
+  public static final String LOGIN_SERVLET_URI = "/login";
   private final LdapAuthService ldapAuthService;
 
   public LDAPAuthenticationFilter(LdapAuthService ldapAuthService) {
@@ -44,29 +46,28 @@ public class LDAPAuthenticationFilter implements Filter {
 
   public void doFilter(ServletRequest request, ServletResponse response, 
FilterChain chain)
       throws IOException, ServletException {
-
     HttpServletRequest httpRequest = (HttpServletRequest) request;
-    String requestURI = httpRequest.getRequestURI();
-
-    boolean isLoginFormRequest = requestURI.endsWith(LOGIN_FORM_URI);
-    boolean isLoginServletRequest = requestURI.endsWith(LOGIN_SERVLET_URI);
     boolean isLoggedIn = ldapAuthService.authenticate(httpRequest, 
(HttpServletResponse) response);
-
-    if (isLoggedIn && (isLoginFormRequest || isLoginServletRequest)) {
-      // User is already logged in, and is trying to login again; forward to 
the main homepage
-      RequestDispatcher dispatcher = 
request.getRequestDispatcher(HiveServer2.HS2_WEBUI_ROOT_URI);
+    boolean forwardRequest = isLoginRequest(httpRequest) == isLoggedIn;
+    if (forwardRequest) {
+      ServletContext rootContext = request.getServletContext().getContext("/");
+      // if the request is trying to login, forward to the main homepage in 
case
+      // the user has already logged in, otherwise the login page.
+      String forwardUri = isLoggedIn ? HiveServer2.HS2_WEBUI_ROOT_URI : 
LOGIN_FORM_URI;
+      RequestDispatcher dispatcher = 
rootContext.getRequestDispatcher(forwardUri);
       dispatcher.forward(request, response);
-    } else if (isLoggedIn || isLoginFormRequest || isLoginServletRequest) {
-      // User is either already logged in or this is a request for the login 
page or processing of a login attempt, 
-      // in all these cases allow to continue the request as is without 
changes 
-      chain.doFilter(request, response);
     } else {
-      // User is not logged in, so authentication is required; forwards to the 
login page
-      RequestDispatcher dispatcher = 
request.getRequestDispatcher(LOGIN_FORM_URI);
-      dispatcher.forward(request, response);
+      chain.doFilter(request, response);
     }
   }
 
+  public boolean isLoginRequest(HttpServletRequest request) {
+    String method = request.getMethod();
+    String servletPath = request.getServletPath();
+    return LOGIN_FORM_URI.equals(servletPath) ||
+        HttpMethod.POST.name().equalsIgnoreCase(method) && 
LOGIN_SERVLET_URI.equals(servletPath);
+  }
+
   public void destroy() {
     // A default filter destroy method
   }
diff --git a/service/src/java/org/apache/hive/service/servlet/LoginServlet.java 
b/service/src/java/org/apache/hive/service/servlet/LoginServlet.java
index 428dac84c47..8317a6ca045 100644
--- a/service/src/java/org/apache/hive/service/servlet/LoginServlet.java
+++ b/service/src/java/org/apache/hive/service/servlet/LoginServlet.java
@@ -28,6 +28,7 @@ import java.io.PrintWriter;
 import java.util.Optional;
 
 public class LoginServlet extends HttpServlet {
+  public static final String LOGIN_FAILURE_MESSAGE = "login_failure_message";
   private final LdapAuthService ldapAuthService;
 
   public LoginServlet(LdapAuthService ldapAuthService) {
@@ -36,11 +37,17 @@ public class LoginServlet extends HttpServlet {
 
   @Override
   public void doPost(HttpServletRequest request, HttpServletResponse response) 
{
-    ldapAuthService.authenticate(request, response);
-    RequestDispatcher dispatcher = 
request.getRequestDispatcher(HiveServer2.HS2_WEBUI_ROOT_URI);
+    boolean success = ldapAuthService.authenticate(request, response);
     PrintWriter out = null;
     try {
       out = response.getWriter();
+      final RequestDispatcher dispatcher;
+      if (success) {
+        dispatcher = 
request.getRequestDispatcher(HiveServer2.HS2_WEBUI_ROOT_URI);
+      } else {
+        request.setAttribute(LOGIN_FAILURE_MESSAGE, "Invalid username or 
password");
+        dispatcher = 
request.getRequestDispatcher(LDAPAuthenticationFilter.LOGIN_FORM_URI);
+      }
       dispatcher.forward(request, response);
     } catch (Exception e) {
       if (out == null) {
diff --git a/service/src/resources/hive-webapps/hiveserver2/loginForm.jsp 
b/service/src/resources/hive-webapps/hiveserver2/loginForm.jsp
index 22efe05d5c1..01abd8d1404 100644
--- a/service/src/resources/hive-webapps/hiveserver2/loginForm.jsp
+++ b/service/src/resources/hive-webapps/hiveserver2/loginForm.jsp
@@ -17,7 +17,9 @@
  * limitations under the License.
  */
 --%>
-<%@ page contentType="text/html;charset=UTF-8" %>
+<%@ page contentType="text/html;charset=UTF-8"
+  import="org.apache.hive.service.servlet.LoginServlet"
+%>
 <!--[if IE]>
 <!DOCTYPE html>
 <![endif]-->
@@ -43,6 +45,14 @@
                 <div class="form-group">
                     <input id="password" name="password" type="password" 
class="form-control" placeholder="Password" required="required">
                 </div>
+                 <%
+                        String status = 
(String)request.getAttribute(LoginServlet.LOGIN_FAILURE_MESSAGE);
+                        if(status != null) {
+                 %>
+                          <p style="color:red;"><%= status %></p>
+                 <%
+                        }
+                 %>
                 <input id="redirectPath" name="redirectPath" type="hidden">
                 <div class="form-group">
                     <button id="submit" type="submit" class="btn btn-primary 
btn-block">Log In</button>
diff --git 
a/service/src/test/org/apache/hive/service/server/TestHS2HttpServerLDAP.java 
b/service/src/test/org/apache/hive/service/server/TestHS2HttpServerLDAP.java
index 8300519f761..2eb09b1907d 100644
--- a/service/src/test/org/apache/hive/service/server/TestHS2HttpServerLDAP.java
+++ b/service/src/test/org/apache/hive/service/server/TestHS2HttpServerLDAP.java
@@ -16,12 +16,15 @@
  */
 package org.apache.hive.service.server;
 
+import org.apache.commons.io.IOUtils;
 import org.apache.hadoop.hive.conf.HiveConf;
 import org.apache.hadoop.hive.conf.HiveConf.ConfVars;
 import org.apache.hadoop.hive.metastore.MetaStoreTestUtils;
 import org.apache.hive.service.auth.HttpAuthService;
 import org.apache.hive.service.auth.HttpAuthUtils;
 import org.apache.hive.service.auth.PasswdAuthenticationProvider;
+import org.apache.http.HttpStatus;
+import org.apache.http.client.methods.CloseableHttpResponse;
 import org.apache.http.client.methods.HttpGet;
 import org.apache.http.client.methods.HttpPost;
 import org.apache.http.client.CookieStore;
@@ -34,6 +37,8 @@ import org.apache.http.params.HttpParams;
 import org.eclipse.jetty.http.HttpHeader;
 import org.eclipse.jetty.util.B64Code;
 import org.eclipse.jetty.util.StringUtil;
+
+import java.nio.charset.StandardCharsets;
 import java.util.List;
 import java.util.Optional;
 import org.junit.AfterClass;
@@ -162,6 +167,28 @@ public class TestHS2HttpServerLDAP {
     }
   }
 
+  @Test
+  public void testEscapeAuthentication() throws Exception {
+    // Verify any un-authenticated requests are forwarding to the login page
+    try (CloseableHttpClient httpclient = HttpClientBuilder.create().build()) {
+      try (CloseableHttpResponse response =
+               httpclient.execute(new HttpGet("http://"; + HOST + ":" + 
webUIPort + "/hiveserver2.jsp;login"))) {
+        checkForwardToLoginPage(response);
+      }
+      try (CloseableHttpResponse response =
+               httpclient.execute(new HttpGet("http://"; + HOST + ":" + 
webUIPort + "/logs"))) {
+        checkForwardToLoginPage(response);
+      }
+    }
+  }
+
+  private void checkForwardToLoginPage(CloseableHttpResponse response) throws 
Exception {
+    Assert.assertEquals(HttpStatus.SC_OK, 
response.getStatusLine().getStatusCode());
+    String content = IOUtils.toString(response.getEntity().getContent(), 
StandardCharsets.UTF_8);
+    Assert.assertTrue(content.contains("<meta name=\"description\" 
content=\"Login - Hive\">"));
+    Assert.assertTrue(content.contains("<div class=\"login-form\">"));
+  }
+
   public static boolean isAuthorized(List<Cookie> cookies) {
     Optional<Cookie> cookie = cookies.stream()
         .filter(c -> 
c.getName().equals(HttpAuthService.HIVE_SERVER2_WEBUI_AUTH_COOKIE_NAME))

Reply via email to