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

saihemanth-cloudera 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 6ca06ca1104 HIVE-29653: Fix SAML bearer token authentication bypass in 
HiveServer2 (#6534)
6ca06ca1104 is described below

commit 6ca06ca1104ff7462363087a867d70d546134774
Author: Sai Hemanth Gantasala 
<[email protected]>
AuthorDate: Wed Jun 17 17:20:08 2026 -0700

    HIVE-29653: Fix SAML bearer token authentication bypass in HiveServer2 
(#6534)
    
    * HIVE-29653: Fix SAML bearer token authentication bypass in HiveServer2
    
    * Apply suggestions from code review
    
    Co-authored-by: Copilot Autofix powered by AI 
<[email protected]>
    
    * Address review comments
    
    * Address issue caused by applying github copilot suggestion
    
    ---------
    
    Co-authored-by: Copilot Autofix powered by AI 
<[email protected]>
---
 .../auth/saml/TestHttpSamlAuthentication.java      | 91 +++++++++++++++++++++-
 .../auth/saml/HiveSamlAuthTokenGenerator.java      | 12 +--
 .../hive/service/cli/thrift/ThriftHttpServlet.java |  3 +-
 3 files changed, 99 insertions(+), 7 deletions(-)

diff --git 
a/itests/hive-unit/src/test/java/org/apache/hive/service/auth/saml/TestHttpSamlAuthentication.java
 
b/itests/hive-unit/src/test/java/org/apache/hive/service/auth/saml/TestHttpSamlAuthentication.java
index 7d119e9372c..f58cf6a0adf 100644
--- 
a/itests/hive-unit/src/test/java/org/apache/hive/service/auth/saml/TestHttpSamlAuthentication.java
+++ 
b/itests/hive-unit/src/test/java/org/apache/hive/service/auth/saml/TestHttpSamlAuthentication.java
@@ -35,6 +35,7 @@
 import java.net.InetAddress;
 import java.net.ServerSocket;
 import java.nio.charset.StandardCharsets;
+import java.util.Base64;
 import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.sql.Statement;
@@ -137,9 +138,17 @@ public void cleanUpIdpEnv() {
       idpContainer.stop();
       idpContainer = null;
     }
-    if (miniHS2 != null) {
+    if (miniHS2 != null && miniHS2.isStarted()) {
       miniHS2.stop();
     }
+    HiveSamlAuthTokenGenerator.shutdown();
+  }
+
+  private static ISAMLAuthTokenGenerator createTokenGenerator(String tokenTtl) 
{
+    HiveSamlAuthTokenGenerator.shutdown();
+    HiveConf conf = new HiveConf();
+    conf.setVar(ConfVars.HIVE_SERVER2_SAML_CALLBACK_TOKEN_TTL, tokenTtl);
+    return HiveSamlAuthTokenGenerator.get(conf);
   }
 
   private void setupIDP(boolean useSignedAssertions, String authMode) throws 
Exception {
@@ -554,6 +563,86 @@ public void testTokenReuse() throws Exception {
     }
   }
 
+  @Test
+  public void testValidTokenRoundTrip() throws Exception {
+    ISAMLAuthTokenGenerator tokenGenerator = createTokenGenerator("30s");
+    String token = tokenGenerator.get("alice", "relay-state-1");
+    assertEquals("alice", tokenGenerator.validate(token));
+  }
+
+  @Test
+  public void testForgedSignatureRejected() throws Exception {
+    ISAMLAuthTokenGenerator tokenGenerator = createTokenGenerator("30s");
+    String forgedPayload = "u=alice;id=1337;time=" + System.currentTimeMillis()
+        + ";rs=deadbeef;sg=bogus";
+    try {
+      String forgedToken = 
Base64.getEncoder().encodeToString(forgedPayload.getBytes(StandardCharsets.UTF_8));
+      tokenGenerator.validate(forgedToken);
+      fail("Expected forged token to be rejected");
+    } catch (HttpSamlAuthenticationException e) {
+      assertEquals("Token could not be verified", e.getMessage());
+    }
+  }
+
+  @Test
+  public void testInvalidTokenRejected() throws Exception {
+    ISAMLAuthTokenGenerator tokenGenerator = createTokenGenerator("30s");
+    try {
+      tokenGenerator.validate("notAValidToken");
+      fail("Expected malformed base64 token to be rejected");
+    } catch (HttpSamlAuthenticationException e) {
+      assertEquals("Invalid token", e.getMessage());
+    }
+    String invalidStructure = 
Base64.getEncoder().encodeToString("foo".getBytes());
+    try {
+      tokenGenerator.validate(invalidStructure);
+      fail("Expected invalid token structure to be rejected");
+    } catch (HttpSamlAuthenticationException e) {
+      assertEquals("Invalid token", e.getMessage());
+    }
+  }
+
+  @Test
+  public void testExpiredTokenRejected() throws Exception {
+    ISAMLAuthTokenGenerator tokenGenerator = createTokenGenerator("1s");
+    String token = tokenGenerator.get("alice", "relay-state-1");
+    Thread.sleep(1100);
+    try {
+      tokenGenerator.validate(token);
+      fail("Expected expired token to be rejected");
+    } catch (HttpSamlAuthenticationException e) {
+      assertEquals("Token is expired", e.getMessage());
+    }
+  }
+
+  @Test
+  public void testParseHandlesBase64PaddingInSignature() {
+    Map<String, String> kv = new HashMap<>();
+    String token = "u=alice;id=1;time=1000;rs=rs1;sg=YWJjZA==";
+    assertTrue(HiveSamlAuthTokenGenerator.parse(token, kv));
+    assertEquals("alice", kv.get("u"));
+    assertEquals("YWJjZA==", kv.get("sg"));
+  }
+
+  @Test
+  public void testParseRejectsEncodedBearerToken() {
+    Map<String, String> kv = new HashMap<>();
+    String encoded = Base64.getEncoder().encodeToString(
+        "u=alice;id=1;time=1000;rs=rs1;sg=abc".getBytes());
+    assertFalse(HiveSamlAuthTokenGenerator.parse(encoded, kv));
+  }
+
+  @Test
+  public void testParseDecodedTokenFromGenerator() throws Exception {
+    ISAMLAuthTokenGenerator tokenGenerator = createTokenGenerator("30s");
+    String encoded = tokenGenerator.get("bob", "relay-42");
+    String decoded = new String(Base64.getDecoder().decode(encoded), 
StandardCharsets.UTF_8);
+    Map<String, String> kv = new HashMap<>();
+    assertTrue(HiveSamlAuthTokenGenerator.parse(decoded, kv));
+    assertEquals("bob", kv.get("u"));
+    assertEquals("relay-42", kv.get(HiveSamlAuthTokenGenerator.RELAY_STATE));
+  }
+
   private static void assertLoggedInUser(HiveConnection connection, String 
expectedUser)
       throws SQLException {
     Statement stmt = connection.createStatement();
diff --git 
a/service/src/java/org/apache/hive/service/auth/saml/HiveSamlAuthTokenGenerator.java
 
b/service/src/java/org/apache/hive/service/auth/saml/HiveSamlAuthTokenGenerator.java
index 51cf646b01e..d9060f44dfc 100644
--- 
a/service/src/java/org/apache/hive/service/auth/saml/HiveSamlAuthTokenGenerator.java
+++ 
b/service/src/java/org/apache/hive/service/auth/saml/HiveSamlAuthTokenGenerator.java
@@ -19,6 +19,7 @@
 package org.apache.hive.service.auth.saml;
 
 import com.google.common.annotations.VisibleForTesting;
+import java.nio.charset.StandardCharsets;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
 import java.security.SecureRandom;
@@ -78,11 +79,11 @@ public String get(String username, String relayStateKey) {
   }
 
   private String encode(String token) {
-    return Base64.getEncoder().encodeToString(token.getBytes());
+    return 
Base64.getEncoder().encodeToString(token.getBytes(StandardCharsets.UTF_8));
   }
 
   private String decode(String encodedToken) {
-    return new String(Base64.getDecoder().decode(encodedToken));
+    return new String(Base64.getDecoder().decode(encodedToken), 
StandardCharsets.UTF_8);
   }
 
   private String getTokenStr(String username, String id, String timestamp,
@@ -100,7 +101,7 @@ private String getTokenStr(String username, String id, 
String timestamp,
   private String getSign(String input) {
     try {
       MessageDigest md = MessageDigest.getInstance("SHA-256");
-      md.update(input.getBytes());
+      md.update(input.getBytes(StandardCharsets.UTF_8));
       md.update(signatureSecret);
       byte[] digest = md.digest();
       return Base64.getEncoder().encodeToString(digest);
@@ -144,7 +145,8 @@ private boolean isExpired(long currentTime, long tokenTime) 
{
   }
 
   private boolean signatureMatches(String origSign, String derivedSign) {
-    return !MessageDigest.isEqual(origSign.getBytes(), derivedSign.getBytes());
+    return MessageDigest.isEqual(origSign.getBytes(StandardCharsets.UTF_8),
+        derivedSign.getBytes(StandardCharsets.UTF_8));
   }
 
   public static boolean parse(String token, Map<String, String> kv) {
@@ -153,7 +155,7 @@ public static boolean parse(String token, Map<String, 
String> kv) {
       return false;
     }
     for (String split : splits) {
-      String[] pair = split.split(SEPARATOR);
+      String[] pair = split.split(SEPARATOR, 2);
       if (pair.length != 2) {
         return false;
       }
diff --git 
a/service/src/java/org/apache/hive/service/cli/thrift/ThriftHttpServlet.java 
b/service/src/java/org/apache/hive/service/cli/thrift/ThriftHttpServlet.java
index 67ebb605d90..43f963107fa 100644
--- a/service/src/java/org/apache/hive/service/cli/thrift/ThriftHttpServlet.java
+++ b/service/src/java/org/apache/hive/service/cli/thrift/ThriftHttpServlet.java
@@ -382,7 +382,8 @@ private String doSamlAuth(HttpServletRequest request, 
HttpServletResponse respon
     LOG.info("Successfully validated the token for user {}", user);
     // token is valid; now confirm if the client identifier matches with the 
relay state.
     Map<String, String> keyValues = new HashMap<>();
-    if (HiveSamlAuthTokenGenerator.parse(token, keyValues)) {
+    String decodedToken = new String(Base64.getDecoder().decode(token), 
java.nio.charset.StandardCharsets.UTF_8);
+    if (HiveSamlAuthTokenGenerator.parse(decodedToken, keyValues)) {
       String relayStateKey = 
keyValues.get(HiveSamlAuthTokenGenerator.RELAY_STATE);
       if (!HiveSamlRelayStateStore.get()
           .validateClientIdentifier(relayStateKey, clientIdentifier)) {

Reply via email to