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