diff --git a/itests/hive-unit/src/test/java/org/apache/hive/service/cli/thrift/TestThriftHttpCLIService.java b/itests/hive-unit/src/test/java/org/apache/hive/service/cli/thrift/TestThriftHttpCLIService.java index 647925e..b1e53a6 100644 --- a/itests/hive-unit/src/test/java/org/apache/hive/service/cli/thrift/TestThriftHttpCLIService.java +++ b/itests/hive-unit/src/test/java/org/apache/hive/service/cli/thrift/TestThriftHttpCLIService.java @@ -21,11 +21,20 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + import org.apache.hadoop.hive.conf.HiveConf.ConfVars; import org.apache.hive.jdbc.HttpBasicAuthInterceptor; import org.apache.hive.service.auth.HiveAuthFactory; import org.apache.hive.service.auth.HiveAuthFactory.AuthTypes; +import org.apache.http.HttpException; +import org.apache.http.HttpRequest; +import org.apache.http.client.CookieStore; import org.apache.http.impl.client.DefaultHttpClient; +import org.apache.http.protocol.HttpContext; import org.apache.thrift.protocol.TBinaryProtocol; import org.apache.thrift.protocol.TProtocol; import org.apache.thrift.transport.THttpClient; @@ -49,6 +58,39 @@ private static String thriftHttpPath = "cliservice"; /** + * HttpBasicAuthInterceptorWithLogging + * This adds httpRequestHeaders to the BasicAuthInterceptor + */ + public class HttpBasicAuthInterceptorWithLogging extends HttpBasicAuthInterceptor { + + ArrayList requestHeaders; + + public HttpBasicAuthInterceptorWithLogging(String username, + String password, CookieStore cookieStore, String cn, boolean isSSL, + Map additionalHeaders) { + super(username, password, cookieStore, cn, isSSL, additionalHeaders); + requestHeaders = new ArrayList(); + } + + @Override + public void process(HttpRequest httpRequest, HttpContext httpContext) + throws HttpException, IOException { + super.process(httpRequest, httpContext); + + String currHeaders = ""; + + for (org.apache.http.Header h : httpRequest.getAllHeaders()) { + currHeaders += h.getName() + ":" + h.getValue() + " "; + } + requestHeaders.add(currHeaders); + } + + public ArrayList getRequestHeaders() { + return requestHeaders; + } + } + + /** * @throws java.lang.Exception */ @BeforeClass @@ -160,8 +202,38 @@ private static TTransport getHttpTransport() throws Exception { String httpUrl = transportMode + "://" + host + ":" + port + "/" + thriftHttpPath + "/"; httpClient.addRequestInterceptor( - new HttpBasicAuthInterceptor(USERNAME, PASSWORD, null, null, false)); + new HttpBasicAuthInterceptor(USERNAME, PASSWORD, null, null, false, null)); return new THttpClient(httpUrl, httpClient); } + /** + * Test additional http headers passed to request interceptor. + * @throws Exception + */ + @Test + public void testAdditionalHttpHeaders() throws Exception { + TTransport transport; + DefaultHttpClient hClient = new DefaultHttpClient(); + String httpUrl = transportMode + "://" + host + ":" + port + + "/" + thriftHttpPath + "/"; + Map additionalHeaders = new HashMap(); + additionalHeaders.put("key1", "value1"); + additionalHeaders.put("key2", "value2"); + HttpBasicAuthInterceptorWithLogging authInt = + new HttpBasicAuthInterceptorWithLogging(USERNAME, PASSWORD, null, null, + false, additionalHeaders); + hClient.addRequestInterceptor(authInt); + transport = new THttpClient(httpUrl, hClient); + TCLIService.Client httpClient = getClient(transport); + + // Create a new open session request object + TOpenSessionReq openReq = new TOpenSessionReq(); + httpClient.OpenSession(openReq).getSessionHandle(); + ArrayList headers = authInt.getRequestHeaders(); + + for (String h : headers) { + assertTrue(h.contains("key1:value1")); + assertTrue(h.contains("key2:value2")); + } + } } \ No newline at end of file diff --git a/jdbc/src/java/org/apache/hive/jdbc/HiveConnection.java b/jdbc/src/java/org/apache/hive/jdbc/HiveConnection.java index 64957d9..b83a3bf 100644 --- a/jdbc/src/java/org/apache/hive/jdbc/HiveConnection.java +++ b/jdbc/src/java/org/apache/hive/jdbc/HiveConnection.java @@ -280,7 +280,17 @@ private CloseableHttpClient getHttpClient(Boolean useSsl) throws SQLException { HttpClientBuilder httpClientBuilder; // Request interceptor for any request pre-processing logic HttpRequestInterceptor requestInterceptor; + Map additionalHttpHeaders = new HashMap(); + // Retrieve the additional HttpHeaders + for (Map.Entry entry : sessConfMap.entrySet()) { + String key = entry.getKey(); + + if (key.startsWith(JdbcConnectionParams.HTTP_HEADER_PREFIX)) { + additionalHttpHeaders.put(key.substring(JdbcConnectionParams.HTTP_HEADER_PREFIX.length()), + entry.getValue()); + } + } // Configure http client for kerberos/password based authentication if (isKerberosAuthMode()) { /** @@ -291,7 +301,8 @@ private CloseableHttpClient getHttpClient(Boolean useSsl) throws SQLException { */ requestInterceptor = new HttpKerberosRequestInterceptor(sessConfMap.get(JdbcConnectionParams.AUTH_PRINCIPAL), - host, getServerHttpUrl(useSsl), assumeSubject, cookieStore, cookieName, useSsl); + host, getServerHttpUrl(useSsl), assumeSubject, cookieStore, cookieName, useSsl, + additionalHttpHeaders); } else { /** @@ -299,7 +310,8 @@ private CloseableHttpClient getHttpClient(Boolean useSsl) throws SQLException { * In https mode, the entire information is encrypted */ requestInterceptor = new HttpBasicAuthInterceptor(getUserName(), getPassword(), - cookieStore, cookieName, useSsl); + cookieStore, cookieName, useSsl, + additionalHttpHeaders); } // Configure http client for cookie based authentication if (isCookieEnabled) { diff --git a/jdbc/src/java/org/apache/hive/jdbc/HttpBasicAuthInterceptor.java b/jdbc/src/java/org/apache/hive/jdbc/HttpBasicAuthInterceptor.java index e66a9a4..8cb7a69 100644 --- a/jdbc/src/java/org/apache/hive/jdbc/HttpBasicAuthInterceptor.java +++ b/jdbc/src/java/org/apache/hive/jdbc/HttpBasicAuthInterceptor.java @@ -18,15 +18,12 @@ package org.apache.hive.jdbc; -import java.io.IOException; +import java.util.Map; import org.apache.http.Header; -import org.apache.http.HttpException; import org.apache.http.HttpRequest; -import org.apache.http.HttpRequestInterceptor; import org.apache.http.auth.UsernamePasswordCredentials; import org.apache.http.client.CookieStore; -import org.apache.http.client.protocol.ClientContext; import org.apache.http.impl.auth.AuthSchemeBase; import org.apache.http.impl.auth.BasicScheme; import org.apache.http.protocol.HttpContext; @@ -36,48 +33,23 @@ * used to add header with these credentials to HTTP requests * */ -public class HttpBasicAuthInterceptor implements HttpRequestInterceptor { +public class HttpBasicAuthInterceptor extends HttpRequestInterceptorBase { UsernamePasswordCredentials credentials; AuthSchemeBase authScheme; - CookieStore cookieStore; - boolean isCookieEnabled; - String cookieName; - boolean isSSL; public HttpBasicAuthInterceptor(String username, String password, CookieStore cookieStore, - String cn, boolean isSSL) { - if(username != null){ - credentials = new UsernamePasswordCredentials(username, password); + String cn, boolean isSSL, Map additionalHeaders) { + super(cookieStore, cn, isSSL, additionalHeaders); + this.authScheme = new BasicScheme(); + if (username != null){ + this.credentials = new UsernamePasswordCredentials(username, password); } - authScheme = new BasicScheme(); - this.cookieStore = cookieStore; - isCookieEnabled = (cookieStore != null); - cookieName = cn; - this.isSSL = isSSL; } @Override - public void process(HttpRequest httpRequest, HttpContext httpContext) - throws HttpException, IOException { - if (isCookieEnabled) { - httpContext.setAttribute(ClientContext.COOKIE_STORE, cookieStore); - } - // Add the authentication details under the following scenarios: - // 1. Cookie Authentication is disabled OR - // 2. The first time when the request is sent OR - // 3. The server returns a 401, which sometimes means the cookie has expired - // 4. The cookie is secured where as the client connect does not use SSL - if (!isCookieEnabled || ((httpContext.getAttribute(Utils.HIVE_SERVER2_RETRY_KEY) == null && - (cookieStore == null || (cookieStore != null && - Utils.needToSendCredentials(cookieStore, cookieName, isSSL)))) || - (httpContext.getAttribute(Utils.HIVE_SERVER2_RETRY_KEY) != null && - httpContext.getAttribute(Utils.HIVE_SERVER2_RETRY_KEY). - equals(Utils.HIVE_SERVER2_RETRY_TRUE)))) { - Header basicAuthHeader = authScheme.authenticate(credentials, httpRequest, httpContext); - httpRequest.addHeader(basicAuthHeader); - } - if (isCookieEnabled) { - httpContext.setAttribute(Utils.HIVE_SERVER2_RETRY_KEY, Utils.HIVE_SERVER2_RETRY_FALSE); - } + protected void addHttpAuthHeader(HttpRequest httpRequest, HttpContext httpContext) + throws Exception { + Header basicAuthHeader = authScheme.authenticate(credentials, httpRequest, httpContext); + httpRequest.addHeader(basicAuthHeader); } -} +} \ No newline at end of file diff --git a/jdbc/src/java/org/apache/hive/jdbc/HttpKerberosRequestInterceptor.java b/jdbc/src/java/org/apache/hive/jdbc/HttpKerberosRequestInterceptor.java index a59054a..3509cab 100644 --- a/jdbc/src/java/org/apache/hive/jdbc/HttpKerberosRequestInterceptor.java +++ b/jdbc/src/java/org/apache/hive/jdbc/HttpKerberosRequestInterceptor.java @@ -18,100 +18,56 @@ package org.apache.hive.jdbc; -import java.io.IOException; +import java.util.Map; import java.util.concurrent.locks.ReentrantLock; import org.apache.hive.service.auth.HttpAuthUtils; import org.apache.http.HttpException; import org.apache.http.HttpRequest; -import org.apache.http.HttpRequestInterceptor; import org.apache.http.client.CookieStore; -import org.apache.http.client.protocol.ClientContext; import org.apache.http.protocol.HttpContext; /** - * * Authentication interceptor which adds Base64 encoded payload, * containing the username and kerberos service ticket, * to the outgoing http request header. - * */ -public class HttpKerberosRequestInterceptor implements HttpRequestInterceptor { +public class HttpKerberosRequestInterceptor extends HttpRequestInterceptorBase { String principal; String host; String serverHttpUrl; boolean assumeSubject; - CookieStore cookieStore; - boolean isCookieEnabled; - // NB: The purpose of isSSL flag is as follows: - // This flag is useful when the HS2 server sends a secure cookie and - // the client is in a non-ssl mode. Here, the client replay of the cookie - // doesnt reach the server. If we don't send credentials in such a scenario, - // the server would send a 401 error back to the client. - // Thus, we would need 2 cycles instead of 1 cycle to process an incoming request if - // isSSL is absent. - boolean isSSL; - String cookieName; // A fair reentrant lock private static ReentrantLock kerberosLock = new ReentrantLock(true); public HttpKerberosRequestInterceptor(String principal, String host, String serverHttpUrl, boolean assumeSubject, CookieStore cs, String cn, - boolean isSSL) { + boolean isSSL, Map additionalHeaders) { + super(cs, cn, isSSL, additionalHeaders); this.principal = principal; this.host = host; this.serverHttpUrl = serverHttpUrl; this.assumeSubject = assumeSubject; - this.cookieStore = cs; - this.isSSL = isSSL; - isCookieEnabled = (cs != null); - cookieName = cn; } @Override - public void process(HttpRequest httpRequest, HttpContext httpContext) - throws HttpException, IOException { - String kerberosAuthHeader; - - try { + protected void addHttpAuthHeader(HttpRequest httpRequest, + HttpContext httpContext) throws Exception { + try { // Generate the service ticket for sending to the server. // Locking ensures the tokens are unique in case of concurrent requests kerberosLock.lock(); - // If cookie based authentication is allowed, generate ticket only when necessary. - // The necessary condition is either when there are no server side cookies in the - // cookiestore which can be send back or when the server returns a 401 error code - // indicating that the previous cookie has expired. - if (isCookieEnabled) { - httpContext.setAttribute(ClientContext.COOKIE_STORE, cookieStore); - } - // Generate the kerberos ticket under the following scenarios: - // 1. Cookie Authentication is disabled OR - // 2. The first time when the request is sent OR - // 3. The server returns a 401, which sometimes means the cookie has expired OR - // 4. The cookie is secured where as the client connect does not use SSL - if (!isCookieEnabled || - ((httpContext.getAttribute(Utils.HIVE_SERVER2_RETRY_KEY) == null && - (cookieStore == null || (cookieStore != null && - Utils.needToSendCredentials(cookieStore, cookieName, isSSL)))) || - (httpContext.getAttribute(Utils.HIVE_SERVER2_RETRY_KEY) != null && - httpContext.getAttribute(Utils.HIVE_SERVER2_RETRY_KEY). - equals(Utils.HIVE_SERVER2_RETRY_TRUE)))) { - kerberosAuthHeader = HttpAuthUtils.getKerberosServiceTicket( - principal, host, serverHttpUrl, assumeSubject); - // Set the session key token (Base64 encoded) in the headers - httpRequest.addHeader(HttpAuthUtils.AUTHORIZATION + ": " + - HttpAuthUtils.NEGOTIATE + " ", kerberosAuthHeader); - } - if (isCookieEnabled) { - httpContext.setAttribute(Utils.HIVE_SERVER2_RETRY_KEY, Utils.HIVE_SERVER2_RETRY_FALSE); - } + String kerberosAuthHeader = HttpAuthUtils.getKerberosServiceTicket( + principal, host, serverHttpUrl, assumeSubject); + // Set the session key token (Base64 encoded) in the headers + httpRequest.addHeader(HttpAuthUtils.AUTHORIZATION + ": " + + HttpAuthUtils.NEGOTIATE + " ", kerberosAuthHeader); } catch (Exception e) { throw new HttpException(e.getMessage(), e); - } - finally { + } finally { kerberosLock.unlock(); } } -} +} \ No newline at end of file diff --git a/jdbc/src/java/org/apache/hive/jdbc/HttpRequestInterceptorBase.java b/jdbc/src/java/org/apache/hive/jdbc/HttpRequestInterceptorBase.java new file mode 100644 index 0000000..62f5369 --- /dev/null +++ b/jdbc/src/java/org/apache/hive/jdbc/HttpRequestInterceptorBase.java @@ -0,0 +1,71 @@ +package org.apache.hive.jdbc; + +import java.io.IOException; +import java.util.Map; +import java.util.concurrent.locks.ReentrantLock; + +import org.apache.http.HttpException; +import org.apache.http.HttpRequest; +import org.apache.http.HttpRequestInterceptor; +import org.apache.http.client.CookieStore; +import org.apache.http.client.protocol.ClientContext; +import org.apache.http.protocol.HttpContext; + +public abstract class HttpRequestInterceptorBase implements HttpRequestInterceptor { + CookieStore cookieStore; + boolean isCookieEnabled; + String cookieName; + boolean isSSL; + Map additionalHeaders; + + // Abstract function to add HttpAuth Header + protected abstract void addHttpAuthHeader(HttpRequest httpRequest, HttpContext httpContext) + throws Exception; + + public HttpRequestInterceptorBase(CookieStore cs, String cn, boolean isSSL, + Map additionalHeaders) { + this.cookieStore = cs; + this.isCookieEnabled = (cs != null); + this.cookieName = cn; + this.isSSL = isSSL; + this.additionalHeaders = additionalHeaders; + } + + @Override + public void process(HttpRequest httpRequest, HttpContext httpContext) + throws HttpException, IOException { + try { + // If cookie based authentication is allowed, generate ticket only when necessary. + // The necessary condition is either when there are no server side cookies in the + // cookiestore which can be send back or when the server returns a 401 error code + // indicating that the previous cookie has expired. + if (isCookieEnabled) { + httpContext.setAttribute(ClientContext.COOKIE_STORE, cookieStore); + } + // Generate the kerberos ticket under the following scenarios: + // 1. Cookie Authentication is disabled OR + // 2. The first time when the request is sent OR + // 3. The server returns a 401, which sometimes means the cookie has expired + // 4. The cookie is secured where as the client connect does not use SSL + if (!isCookieEnabled || ((httpContext.getAttribute(Utils.HIVE_SERVER2_RETRY_KEY) == null && + (cookieStore == null || (cookieStore != null && + Utils.needToSendCredentials(cookieStore, cookieName, isSSL)))) || + (httpContext.getAttribute(Utils.HIVE_SERVER2_RETRY_KEY) != null && + httpContext.getAttribute(Utils.HIVE_SERVER2_RETRY_KEY). + equals(Utils.HIVE_SERVER2_RETRY_TRUE)))) { + addHttpAuthHeader(httpRequest, httpContext); + } + if (isCookieEnabled) { + httpContext.setAttribute(Utils.HIVE_SERVER2_RETRY_KEY, Utils.HIVE_SERVER2_RETRY_FALSE); + } + // Insert the additional http headers + if (additionalHeaders != null) { + for (Map.Entry entry : additionalHeaders.entrySet()) { + httpRequest.addHeader(entry.getKey(), entry.getValue()); + } + } + } catch (Exception e) { + throw new HttpException(e.getMessage(), e); + } + } +} diff --git a/jdbc/src/java/org/apache/hive/jdbc/Utils.java b/jdbc/src/java/org/apache/hive/jdbc/Utils.java index 7ad0710..04bba06 100644 --- a/jdbc/src/java/org/apache/hive/jdbc/Utils.java +++ b/jdbc/src/java/org/apache/hive/jdbc/Utils.java @@ -110,6 +110,8 @@ static final String COOKIE_NAME = "cookieName"; // The default value of the cookie name when CookieAuth=true static final String DEFAULT_COOKIE_NAMES_HS2 = "hive.server2.auth"; + // The http header prefix for additional headers which have to be appended to the request + static final String HTTP_HEADER_PREFIX = "http.header."; // Non-configurable params: // Currently supports JKS keystore format