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

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


The following commit(s) were added to refs/heads/master by this push:
     new d6b244343 RANGER-5812: Extend service config validation to 
lookupResource and saved-service check (#1266)
d6b244343 is described below

commit d6b244343a3f51c0f5ca24fcba9855e702514901
Author: PradeeP AgrawaL <[email protected]>
AuthorDate: Fri Oct 16 18:52:32 2026 +0530

    RANGER-5812: Extend service config validation to lookupResource and 
saved-service check (#1266)
    
    - Refactor validateConfig SSRF checks into validateOutboundServiceConfigs 
and reuse
      for lookupResource before Admin initiates outbound resource lookups.
    - Enforce service-level hasAccess in ServiceREST.lookupResource.
    - Add optional ranger.serviceconfig.validate.require.saved.service 
validateConfig
      hardening (default false).
---
 .../java/org/apache/ranger/biz/ServiceMgr.java     | 337 ++++++++++++--
 .../java/org/apache/ranger/rest/ServiceREST.java   |   9 +
 .../conf.dist/ranger-admin-default-site.xml        |   6 +
 .../java/org/apache/ranger/biz/TestServiceMgr.java | 495 +++++++++++++++++++++
 .../org/apache/ranger/rest/TestServiceREST.java    |  51 +++
 5 files changed, 870 insertions(+), 28 deletions(-)

diff --git a/security-admin/src/main/java/org/apache/ranger/biz/ServiceMgr.java 
b/security-admin/src/main/java/org/apache/ranger/biz/ServiceMgr.java
index 515dd98d9..cf9ff309c 100755
--- a/security-admin/src/main/java/org/apache/ranger/biz/ServiceMgr.java
+++ b/security-admin/src/main/java/org/apache/ranger/biz/ServiceMgr.java
@@ -59,7 +59,9 @@
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.Callable;
 import java.util.concurrent.TimeUnit;
@@ -79,6 +81,8 @@ public class ServiceMgr {
     private static final String NAME_RULES           = 
"hadoop.security.auth_to_local";
     private static final String HOST_NAME            = "ranger.service.host";
 
+    public static final String 
PROP_SERVICE_CONFIG_VALIDATE_REQUIRE_SAVED_SERVICE = 
"ranger.serviceconfig.validate.require.saved.service";
+
     private static final long _DefaultTimeoutValue_Lookp          = 1000; // 1 
s
     private static final long _DefaultTimeoutValue_ValidateConfig = 10000; // 
10 s
 
@@ -137,15 +141,17 @@ public List<String> lookupResource(String serviceName, 
ResourceLookupContext con
             }
         }
 
-        RangerBaseService svc = null;
+        if (service == null) {
+            throw new Exception("Service not found: " + serviceName);
+        }
 
-        if (service != null) {
-            Map<String, String> newConfigs = 
rangerSvcService.getConfigsWithDecryptedPassword(service);
+        Map<String, String> newConfigs = 
rangerSvcService.getConfigsWithDecryptedPassword(service);
 
-            service.setConfigs(newConfigs);
+        service.setConfigs(newConfigs);
 
-            svc = getRangerServiceByService(service, svcStore);
-        }
+        validateOutboundServiceConfigs(newConfigs);
+
+        RangerBaseService svc = getRangerServiceByService(service, svcStore);
 
         LOG.debug("==> ServiceMgr.lookupResource for Service: ({}Context: 
{})", svc, context);
 
@@ -204,29 +210,9 @@ public VXResponse validateConfig(RangerService service, 
ServiceStore svcStore) t
 
         LOG.debug("==> ServiceMgr.validateConfig for Service: ({})", svc);
 
-        // check if service configs contains localhost/127.0.0.1
         if (service != null && service.getConfigs() != null) {
-            for (Map.Entry<String, String> entry : 
service.getConfigs().entrySet()) {
-                String configValue = entry.getValue();
-                if (configValue != null && !configValue.trim().isEmpty()) {
-                    String userInfo = extractUserInfo(configValue);
-                    if (userInfoContainsBlockedHost(userInfo)) {
-                        throw new Exception("Invalid value for configuration " 
+ entry.getKey() + ": userinfo contains blocked host (blocked host detected)");
-                    }
-
-                    String host = extractHost(configValue);
-                    if (host != null) {
-                        if (isBlockedHost(host)) {
-                            throw new Exception("Invalid value for 
configuration " + entry.getKey() + ": host " + host + " is not allowed (blocked 
host detected)");
-                        }
-                    } else {
-                        String lowerVal = configValue.toLowerCase().trim();
-                        if (lowerVal.contains("localhost") || 
lowerVal.contains("127.0.0.1") || lowerVal.contains("0.0.0.0") || 
lowerVal.contains("::1")) {
-                            throw new Exception("Invalid value for 
configuration " + entry.getKey() + ": contains blocked keywords but could not 
be parsed securely.");
-                        }
-                    }
-                }
-            }
+            enforceSavedServiceEndpointConfigs(service);
+            validateOutboundServiceConfigs(service.getConfigs());
         }
 
         if (svc != null) {
@@ -460,6 +446,301 @@ Long parseLong(String str) {
         }
     }
 
+    private void enforceSavedServiceEndpointConfigs(RangerService service) 
throws Exception {
+        if 
(!Boolean.parseBoolean(PropertiesUtil.getProperty(PROP_SERVICE_CONFIG_VALIDATE_REQUIRE_SAVED_SERVICE,
 "false"))) {
+            return;
+        }
+
+        String serviceName = service.getName();
+        if (StringUtils.isBlank(serviceName)) {
+            return;
+        }
+
+        RangerService savedService = svcDBStore.getServiceByName(serviceName);
+        if (savedService == null) {
+            throw new Exception("Configuration validation requires a saved 
service: " + serviceName);
+        }
+
+        if (StringUtils.isNotBlank(service.getType()) && 
StringUtils.isNotBlank(savedService.getType())
+                && !StringUtils.equals(service.getType(), 
savedService.getType())) {
+            throw new Exception("Configuration validation service type does 
not match saved service: " + serviceName);
+        }
+
+        Map<String, String> savedConfigs = 
rangerSvcService.getConfigsWithDecryptedPassword(savedService);
+        if (savedConfigs == null) {
+            savedConfigs = Collections.emptyMap();
+        }
+
+        for (Map.Entry<String, String> entry : 
service.getConfigs().entrySet()) {
+            String configKey    = entry.getKey();
+            String requestValue = entry.getValue();
+            if (requestValue == null || requestValue.trim().isEmpty()) {
+                continue;
+            }
+
+            String savedValue = savedConfigs.get(configKey);
+            boolean savedIsEndpoint   = savedValue != null && 
isConnectionEndpoint(savedValue);
+            boolean requestIsEndpoint = isConnectionEndpoint(requestValue);
+
+            if (!savedIsEndpoint && !requestIsEndpoint) {
+                continue;
+            }
+
+            if (savedValue == null) {
+                throw new Exception("Invalid value for configuration " + 
configKey + ": endpoint property is not present in saved service 
configuration");
+            }
+
+            if (!savedIsEndpoint) {
+                throw new Exception("Invalid value for configuration " + 
configKey + ": endpoint must match the saved service configuration");
+            }
+
+            ConnectionEndpointPin savedPin   = 
parseConnectionEndpointPin(configKey, savedValue);
+            ConnectionEndpointPin requestPin = 
parseConnectionEndpointPin(configKey, requestValue);
+            if (!requestPin.matches(savedPin)) {
+                throw new Exception("Invalid value for configuration " + 
configKey + ": endpoint must match the saved service configuration");
+            }
+        }
+    }
+
+    private static boolean isConnectionEndpoint(String value) {
+        if (value == null) {
+            return false;
+        }
+
+        String trimmed = value.trim();
+        return looksLikeConnectionUrl(trimmed) || looksLikeJdbcAtHost(trimmed) 
|| looksLikeHostPortEndpoint(trimmed);
+    }
+
+    private static boolean looksLikeConnectionUrl(String value) {
+        return value != null && value.contains("://");
+    }
+
+    private static boolean looksLikeJdbcAtHost(String value) {
+        if (StringUtils.isBlank(value) || looksLikeConnectionUrl(value)) {
+            return false;
+        }
+
+        return value.startsWith("jdbc:") && value.contains("@");
+    }
+
+    private static boolean looksLikeHostPortEndpoint(String value) {
+        if (StringUtils.isBlank(value) || looksLikeConnectionUrl(value) || 
value.contains("@")) {
+            return false;
+        }
+
+        if (value.contains(",")) {
+            for (String segment : value.split(",")) {
+                if (!looksLikeHostPortSegment(segment.trim())) {
+                    return false;
+                }
+            }
+
+            return true;
+        }
+
+        return looksLikeHostPortSegment(value.trim());
+    }
+
+    private static boolean looksLikeHostPortSegment(String segment) {
+        if (StringUtils.isBlank(segment)) {
+            return false;
+        }
+
+        if (segment.startsWith("[")) {
+            int closeBracket = segment.indexOf(']');
+            if (closeBracket <= 0) {
+                return false;
+            }
+
+            if (closeBracket + 1 >= segment.length() || 
segment.charAt(closeBracket + 1) != ':') {
+                return false;
+            }
+
+            return StringUtils.isNumeric(segment.substring(closeBracket + 2));
+        }
+
+        int colon = segment.lastIndexOf(':');
+        if (colon <= 0 || colon >= segment.length() - 1) {
+            return false;
+        }
+
+        return StringUtils.isNumeric(segment.substring(colon + 1));
+    }
+
+    private static ConnectionEndpointPin parseConnectionEndpointPin(String 
configKey, String rawValue) throws Exception {
+        String trimmed = rawValue.trim();
+        if (looksLikeConnectionUrl(trimmed)) {
+            return parseConnectionUrlPin(configKey, trimmed);
+        }
+
+        if (looksLikeJdbcAtHost(trimmed)) {
+            int at = trimmed.lastIndexOf('@');
+            String afterAt = trimmed.substring(at + 1);
+            if (afterAt.startsWith("//")) {
+                return parseConnectionUrlPin(configKey, "jdbc://" + 
afterAt.substring(2));
+            }
+
+            List<String> authorityHostPorts = 
parseCommaSeparatedHostPorts(afterAt);
+            if (authorityHostPorts.isEmpty()) {
+                throw new Exception("Invalid value for configuration " + 
configKey + ": connection URL cannot be parsed securely for endpoint pinning");
+            }
+
+            return new ConnectionEndpointPin("jdbc-at", null, 
authorityHostPorts, "");
+        }
+
+        if (looksLikeHostPortEndpoint(trimmed)) {
+            List<String> authorityHostPorts = 
parseCommaSeparatedHostPorts(trimmed);
+            if (authorityHostPorts.isEmpty()) {
+                throw new Exception("Invalid value for configuration " + 
configKey + ": connection URL cannot be parsed securely for endpoint pinning");
+            }
+
+            return new ConnectionEndpointPin("", null, authorityHostPorts, "");
+        }
+
+        throw new Exception("Invalid value for configuration " + configKey + 
": connection URL cannot be parsed securely for endpoint pinning");
+    }
+
+    private static ConnectionEndpointPin parseConnectionUrlPin(String 
configKey, String rawValue) throws Exception {
+        String trimmed = rawValue.trim();
+        int    schemeSep = trimmed.indexOf("://");
+        if (schemeSep < 0) {
+            throw new Exception("Invalid value for configuration " + configKey 
+ ": connection URL cannot be parsed securely for endpoint pinning");
+        }
+
+        String scheme    = trimmed.substring(0, 
schemeSep).toLowerCase(Locale.ROOT);
+        String remainder = trimmed.substring(schemeSep + 3);
+
+        String path       = "";
+        String authority  = remainder;
+        int    pathStart  = remainder.indexOf('/');
+        if (pathStart >= 0) {
+            authority = remainder.substring(0, pathStart);
+            path      = 
normalizePathForPinning(remainder.substring(pathStart));
+        }
+
+        String userInfo     = null;
+        String hostPortPart = authority;
+        if (authority.contains("@")) {
+            int at = authority.lastIndexOf('@');
+            userInfo     = authority.substring(0, at);
+            hostPortPart = authority.substring(at + 1);
+        }
+
+        List<String> authorityHostPorts = 
parseCommaSeparatedHostPorts(hostPortPart);
+        if (authorityHostPorts.isEmpty() && StringUtils.isBlank(hostPortPart)) 
{
+            if (StringUtils.isBlank(path) || "/".equals(path)) {
+                throw new Exception("Invalid value for configuration " + 
configKey + ": connection URL cannot be parsed securely for endpoint pinning");
+            }
+        } else if (authorityHostPorts.isEmpty()) {
+            throw new Exception("Invalid value for configuration " + configKey 
+ ": connection URL cannot be parsed securely for endpoint pinning");
+        }
+
+        return new ConnectionEndpointPin(scheme, userInfo, authorityHostPorts, 
path);
+    }
+
+    private static String normalizePathForPinning(String path) {
+        if (path == null) {
+            return "";
+        }
+        int cut = path.length();
+        for (int i = 0; i < path.length(); i++) {
+            char c = path.charAt(i);
+            if (c == '?' || c == '#') {
+                cut = i;
+                break;
+            }
+        }
+        return path.substring(0, cut);
+    }
+
+    private static List<String> parseCommaSeparatedHostPorts(String 
hostPortPart) {
+        List<String> ret = new ArrayList<>();
+        if (StringUtils.isBlank(hostPortPart)) {
+            return ret;
+        }
+
+        for (String segment : hostPortPart.split(",")) {
+            String normalized = normalizeHostPortSegment(segment.trim());
+            if (StringUtils.isNotBlank(normalized)) {
+                ret.add(normalized);
+            }
+        }
+
+        return ret;
+    }
+
+    private static String normalizeHostPortSegment(String segment) {
+        if (StringUtils.isBlank(segment)) {
+            return segment;
+        }
+
+        if (segment.startsWith("[")) {
+            int closeBracket = segment.indexOf(']');
+            if (closeBracket > 0) {
+                String host     = segment.substring(1, 
closeBracket).toLowerCase(Locale.ROOT);
+                String portSuffix = closeBracket + 1 < segment.length() ? 
segment.substring(closeBracket + 1) : "";
+                return host + portSuffix;
+            }
+        }
+
+        int colonCount = StringUtils.countMatches(segment, ":");
+        if (colonCount == 1) {
+            int colon = segment.indexOf(':');
+            return segment.substring(0, colon).toLowerCase(Locale.ROOT) + 
segment.substring(colon);
+        }
+
+        return segment.toLowerCase(Locale.ROOT);
+    }
+
+    private static final class ConnectionEndpointPin {
+        private final String       scheme;
+        private final String       userInfo;
+        private final List<String> authorityHostPorts;
+        private final String       path;
+
+        private ConnectionEndpointPin(String scheme, String userInfo, 
List<String> authorityHostPorts, String path) {
+            this.scheme              = scheme;
+            this.userInfo            = StringUtils.isBlank(userInfo) ? null : 
userInfo;
+            this.authorityHostPorts  = authorityHostPorts == null ? 
Collections.emptyList() : authorityHostPorts;
+            this.path                = path == null ? "" : path;
+        }
+
+        private boolean matches(ConnectionEndpointPin saved) {
+            return Objects.equals(scheme, saved.scheme)
+                    && Objects.equals(userInfo, saved.userInfo)
+                    && Objects.equals(path, saved.path)
+                    && authorityHostPorts.equals(saved.authorityHostPorts);
+        }
+    }
+
+    private static void validateOutboundServiceConfigs(Map<String, String> 
configs) throws Exception {
+        if (configs == null) {
+            return;
+        }
+
+        for (Map.Entry<String, String> entry : configs.entrySet()) {
+            String configValue = entry.getValue();
+            if (configValue != null && !configValue.trim().isEmpty()) {
+                String userInfo = extractUserInfo(configValue);
+                if (userInfoContainsBlockedHost(userInfo)) {
+                    throw new Exception("Invalid value for configuration " + 
entry.getKey() + ": userinfo contains blocked host (blocked host detected)");
+                }
+
+                String host = extractHost(configValue);
+                if (host != null) {
+                    if (isBlockedHost(host)) {
+                        throw new Exception("Invalid value for configuration " 
+ entry.getKey() + ": host " + host + " is not allowed (blocked host 
detected)");
+                    }
+                } else {
+                    String lowerVal = configValue.toLowerCase().trim();
+                    if (lowerVal.contains("localhost") || 
lowerVal.contains("127.0.0.1") || lowerVal.contains("0.0.0.0") || 
lowerVal.contains("::1")) {
+                        throw new Exception("Invalid value for configuration " 
+ entry.getKey() + ": contains blocked keywords but could not be parsed 
securely.");
+                    }
+                }
+            }
+        }
+    }
+
     private static URI toConfigUri(String urlString) throws Exception {
         String processedUrl = urlString.trim().replaceFirst("(?i)^jdbc:", "");
         if (!processedUrl.contains("://")) {
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 5197b2aff..665cd44f1 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
@@ -1135,6 +1135,15 @@ public List<String> 
lookupResource(@PathParam("serviceName") String serviceName,
         RangerPerfTracer perf = null;
 
         try {
+            XXService xxService = 
daoManager.getXXService().findByName(serviceName);
+            if (xxService == null) {
+                throw 
restErrorUtil.createRESTException(HttpServletResponse.SC_NOT_FOUND, "Not 
found", true);
+            }
+            if (!Boolean.TRUE.equals(bizUtil.hasAccess(xxService, null))) {
+                throw 
restErrorUtil.createRESTException(HttpServletResponse.SC_FORBIDDEN,
+                        "Operation denied. User is not permitted to lookup 
resources for service " + serviceName, true);
+            }
+
             if (RangerPerfTracer.isPerfTraceEnabled(PERF_LOG)) {
                 perf = RangerPerfTracer.getPerfTracer(PERF_LOG, 
"ServiceREST.lookupResource(serviceName=" + serviceName + ")");
             }
diff --git 
a/security-admin/src/main/resources/conf.dist/ranger-admin-default-site.xml 
b/security-admin/src/main/resources/conf.dist/ranger-admin-default-site.xml
index aa71f7dff..4f7bc87ac 100644
--- a/security-admin/src/main/resources/conf.dist/ranger-admin-default-site.xml
+++ b/security-admin/src/main/resources/conf.dist/ranger-admin-default-site.xml
@@ -508,6 +508,12 @@
                <description></description>
        </property>
 
+       <property>
+               <name>ranger.serviceconfig.validate.require.saved.service</name>
+               <value>false</value>
+               <description>When true, validateConfig for a named service 
requires a saved service record and rejects endpoint URLs that differ from the 
saved configuration.</description>
+       </property>
+
        <property>
                <name>ranger.timed.executor.max.threadpool.size</name>
                <value>10</value>
diff --git 
a/security-admin/src/test/java/org/apache/ranger/biz/TestServiceMgr.java 
b/security-admin/src/test/java/org/apache/ranger/biz/TestServiceMgr.java
index ecf16c510..b0f075076 100644
--- a/security-admin/src/test/java/org/apache/ranger/biz/TestServiceMgr.java
+++ b/security-admin/src/test/java/org/apache/ranger/biz/TestServiceMgr.java
@@ -17,6 +17,7 @@
 
 package org.apache.ranger.biz;
 
+import org.apache.ranger.common.PropertiesUtil;
 import org.apache.ranger.common.TimedExecutor;
 import org.apache.ranger.db.XXGroupUserDao;
 import org.apache.ranger.plugin.model.RangerRole;
@@ -53,6 +54,7 @@
 import java.util.stream.Stream;
 
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
@@ -69,6 +71,20 @@ private static void setField(Object target, Class<?> 
declaring, String fieldName
         f.set(target, value);
     }
 
+    private static void wireValidateConfigSuccessPipeline(ServiceMgr mgr, 
ServiceStore store, String serviceType) throws Exception {
+        TimedExecutor exec = mock(TimedExecutor.class);
+        setField(mgr, ServiceMgr.class, "timedExecutor", exec);
+
+        RangerServiceDef def = new RangerServiceDef();
+        def.setName(serviceType);
+        def.setImplClass(RangerDefaultService.class.getName());
+        when(store.getServiceDefByName(serviceType)).thenReturn(def);
+
+        Map<String, Object> resp = new HashMap<>();
+        resp.put("connectivityStatus", true);
+        when(exec.timedTask(any(ServiceMgr.ValidateCallable.class), anyLong(), 
any())).thenReturn(resp);
+    }
+
     @Test
     public void test01_parseLong_andTimeoutFallbacks() throws Exception {
         ServiceMgr mgr = new ServiceMgr();
@@ -385,4 +401,483 @@ public void 
test13_validateConfig_allowsLegitimateExternalUrls() throws Exceptio
         Assertions.assertNotNull(response);
         Assertions.assertNotEquals(VXResponse.STATUS_ERROR, 
response.getStatusCode());
     }
+
+    @ParameterizedTest
+    @MethodSource("ssrfBypassConfigValues")
+    public void test14_lookupResource_rejectsSSRFBypassAttempts(String 
configKey, String configValue) throws Exception {
+        ServiceMgr mgr = new ServiceMgr();
+        ServiceDBStore svcDBStore = mock(ServiceDBStore.class);
+        RangerServiceService svcService = mock(RangerServiceService.class);
+        TimedExecutor exec = mock(TimedExecutor.class);
+        setField(mgr, ServiceMgr.class, "svcDBStore", svcDBStore);
+        setField(mgr, ServiceMgr.class, "rangerSvcService", svcService);
+        setField(mgr, ServiceMgr.class, "timedExecutor", exec);
+
+        Map<String, String> bypassConfigs = new HashMap<>();
+        bypassConfigs.put(configKey, configValue);
+
+        RangerService svc = new RangerService();
+        svc.setType("knox");
+        svc.setName("knox-test");
+        svc.setConfigs(bypassConfigs);
+
+        when(svcDBStore.getServiceByName("knox-test")).thenReturn(svc);
+        
when(svcService.getConfigsWithDecryptedPassword(any(RangerService.class))).thenReturn(bypassConfigs);
+
+        ResourceLookupContext ctx = new ResourceLookupContext();
+        Exception thrown = Assertions.assertThrows(Exception.class, () -> 
mgr.lookupResource("knox-test", ctx, null));
+        Assertions.assertTrue(
+                thrown.getMessage().contains("is not allowed") || 
thrown.getMessage().contains("blocked host detected"),
+                "Unexpected message: " + thrown.getMessage());
+    }
+
+    @Test
+    public void 
test15_validateConfig_requireSavedService_rejectsMissingService() throws 
Exception {
+        ServiceMgr mgr = new ServiceMgr();
+        ServiceDBStore db = mock(ServiceDBStore.class);
+        RangerServiceService svcService = mock(RangerServiceService.class);
+        setField(mgr, ServiceMgr.class, "svcDBStore", db);
+        setField(mgr, ServiceMgr.class, "rangerSvcService", svcService);
+
+        
PropertiesUtil.getPropertiesMap().put(ServiceMgr.PROP_SERVICE_CONFIG_VALIDATE_REQUIRE_SAVED_SERVICE,
 "true");
+        try {
+            Map<String, String> cfg = new HashMap<>();
+            cfg.put("knox.url", "https://knox.example.com:8443/gateway";);
+
+            RangerService svc = new RangerService();
+            svc.setName("knox-prod");
+            svc.setType("knox");
+            svc.setConfigs(cfg);
+
+            
when(svcService.getConfigsWithDecryptedPassword(any(RangerService.class))).thenReturn(cfg);
+            when(db.getServiceByName("knox-prod")).thenReturn(null);
+
+            Exception thrown = Assertions.assertThrows(Exception.class, () -> 
mgr.validateConfig(svc, null));
+            Assertions.assertTrue(thrown.getMessage().contains("requires a 
saved service"));
+        } finally {
+            
PropertiesUtil.getPropertiesMap().remove(ServiceMgr.PROP_SERVICE_CONFIG_VALIDATE_REQUIRE_SAVED_SERVICE);
+        }
+    }
+
+    @Test
+    public void 
test16_validateConfig_requireSavedService_rejectsEndpointMismatch() throws 
Exception {
+        ServiceMgr mgr = new ServiceMgr();
+        ServiceDBStore db = mock(ServiceDBStore.class);
+        RangerServiceService svcService = mock(RangerServiceService.class);
+        setField(mgr, ServiceMgr.class, "svcDBStore", db);
+        setField(mgr, ServiceMgr.class, "rangerSvcService", svcService);
+
+        
PropertiesUtil.getPropertiesMap().put(ServiceMgr.PROP_SERVICE_CONFIG_VALIDATE_REQUIRE_SAVED_SERVICE,
 "true");
+        try {
+            Map<String, String> savedCfg = new HashMap<>();
+            savedCfg.put("knox.url", "https://knox.example.com:8443/gateway";);
+
+            Map<String, String> requestCfg = new HashMap<>();
+            requestCfg.put("knox.url", 
"https://attacker.example.com:8443/gateway";);
+
+            RangerService saved = new RangerService();
+            saved.setName("knox-prod");
+            saved.setType("knox");
+            saved.setConfigs(savedCfg);
+
+            RangerService request = new RangerService();
+            request.setName("knox-prod");
+            request.setType("knox");
+            request.setConfigs(requestCfg);
+
+            when(db.getServiceByName("knox-prod")).thenReturn(saved);
+            
when(svcService.getConfigsWithDecryptedPassword(saved)).thenReturn(savedCfg);
+            
when(svcService.getConfigsWithDecryptedPassword(request)).thenReturn(requestCfg);
+
+            Exception thrown = Assertions.assertThrows(Exception.class, () -> 
mgr.validateConfig(request, null));
+            Assertions.assertTrue(thrown.getMessage().contains("must match the 
saved service configuration"));
+        } finally {
+            
PropertiesUtil.getPropertiesMap().remove(ServiceMgr.PROP_SERVICE_CONFIG_VALIDATE_REQUIRE_SAVED_SERVICE);
+        }
+    }
+
+    @Test
+    public void 
test17_validateConfig_requireSavedService_allowsMatchingEndpoint() throws 
Exception {
+        ServiceMgr mgr = new ServiceMgr();
+        ServiceDBStore db = mock(ServiceDBStore.class);
+        RangerServiceService svcService = mock(RangerServiceService.class);
+        ServiceStore store = mock(ServiceStore.class);
+        setField(mgr, ServiceMgr.class, "svcDBStore", db);
+        setField(mgr, ServiceMgr.class, "rangerSvcService", svcService);
+        wireValidateConfigSuccessPipeline(mgr, store, "knox");
+
+        
PropertiesUtil.getPropertiesMap().put(ServiceMgr.PROP_SERVICE_CONFIG_VALIDATE_REQUIRE_SAVED_SERVICE,
 "true");
+        try {
+            Map<String, String> cfg = new HashMap<>();
+            cfg.put("knox.url", "https://knox.example.com:8443/gateway";);
+            cfg.put("username", "admin");
+
+            RangerService saved = new RangerService();
+            saved.setName("knox-prod");
+            saved.setType("knox");
+            saved.setConfigs(cfg);
+
+            RangerService request = new RangerService();
+            request.setName("knox-prod");
+            request.setType("knox");
+            request.setConfigs(new HashMap<>(cfg));
+
+            when(db.getServiceByName("knox-prod")).thenReturn(saved);
+            
when(svcService.getConfigsWithDecryptedPassword(any(RangerService.class)))
+                    .thenAnswer(invocation -> new 
HashMap<>(invocation.<RangerService>getArgument(0).getConfigs()));
+
+            VXResponse response = mgr.validateConfig(request, store);
+            Assertions.assertNotNull(response);
+            Assertions.assertEquals(VXResponse.STATUS_SUCCESS, 
response.getStatusCode());
+        } finally {
+            
PropertiesUtil.getPropertiesMap().remove(ServiceMgr.PROP_SERVICE_CONFIG_VALIDATE_REQUIRE_SAVED_SERVICE);
+        }
+    }
+
+    @Test
+    public void 
test18_validateConfig_requireSavedService_rejectsPortOrPathChange() throws 
Exception {
+        ServiceMgr mgr = new ServiceMgr();
+        ServiceDBStore db = mock(ServiceDBStore.class);
+        RangerServiceService svcService = mock(RangerServiceService.class);
+        setField(mgr, ServiceMgr.class, "svcDBStore", db);
+        setField(mgr, ServiceMgr.class, "rangerSvcService", svcService);
+
+        
PropertiesUtil.getPropertiesMap().put(ServiceMgr.PROP_SERVICE_CONFIG_VALIDATE_REQUIRE_SAVED_SERVICE,
 "true");
+        try {
+            Map<String, String> savedCfg = new HashMap<>();
+            savedCfg.put("knox.url", "https://knox.example.com:8443/gateway";);
+
+            RangerService saved = new RangerService();
+            saved.setName("knox-prod");
+            saved.setType("knox");
+            saved.setConfigs(savedCfg);
+
+            when(db.getServiceByName("knox-prod")).thenReturn(saved);
+            
when(svcService.getConfigsWithDecryptedPassword(saved)).thenReturn(savedCfg);
+
+            Map<String, String> wrongPort = new HashMap<>();
+            wrongPort.put("knox.url", "https://knox.example.com:9/gateway";);
+            RangerService requestPort = new RangerService();
+            requestPort.setName("knox-prod");
+            requestPort.setType("knox");
+            requestPort.setConfigs(wrongPort);
+            
when(svcService.getConfigsWithDecryptedPassword(requestPort)).thenReturn(wrongPort);
+
+            Exception portEx = Assertions.assertThrows(Exception.class, () -> 
mgr.validateConfig(requestPort, null));
+            Assertions.assertTrue(portEx.getMessage().contains("must match the 
saved service configuration"));
+
+            Map<String, String> wrongPath = new HashMap<>();
+            wrongPath.put("knox.url", "https://knox.example.com:8443/other";);
+            RangerService requestPath = new RangerService();
+            requestPath.setName("knox-prod");
+            requestPath.setType("knox");
+            requestPath.setConfigs(wrongPath);
+            
when(svcService.getConfigsWithDecryptedPassword(requestPath)).thenReturn(wrongPath);
+
+            Exception pathEx = Assertions.assertThrows(Exception.class, () -> 
mgr.validateConfig(requestPath, null));
+            Assertions.assertTrue(pathEx.getMessage().contains("must match the 
saved service configuration"));
+        } finally {
+            
PropertiesUtil.getPropertiesMap().remove(ServiceMgr.PROP_SERVICE_CONFIG_VALIDATE_REQUIRE_SAVED_SERVICE);
+        }
+    }
+
+    @Test
+    public void 
test19_validateConfig_requireSavedService_rejectsUserInfoBypass() throws 
Exception {
+        ServiceMgr mgr = new ServiceMgr();
+        ServiceDBStore db = mock(ServiceDBStore.class);
+        RangerServiceService svcService = mock(RangerServiceService.class);
+        setField(mgr, ServiceMgr.class, "svcDBStore", db);
+        setField(mgr, ServiceMgr.class, "rangerSvcService", svcService);
+
+        
PropertiesUtil.getPropertiesMap().put(ServiceMgr.PROP_SERVICE_CONFIG_VALIDATE_REQUIRE_SAVED_SERVICE,
 "true");
+        try {
+            Map<String, String> savedCfg = new HashMap<>();
+            savedCfg.put("knox.url", "https://knox.example.com:8443/gateway";);
+
+            Map<String, String> requestCfg = new HashMap<>();
+            requestCfg.put("knox.url", 
"https://[email protected]:8443/gateway";);
+
+            RangerService saved = new RangerService();
+            saved.setName("knox-prod");
+            saved.setType("knox");
+            saved.setConfigs(savedCfg);
+
+            RangerService request = new RangerService();
+            request.setName("knox-prod");
+            request.setType("knox");
+            request.setConfigs(requestCfg);
+
+            when(db.getServiceByName("knox-prod")).thenReturn(saved);
+            
when(svcService.getConfigsWithDecryptedPassword(saved)).thenReturn(savedCfg);
+            
when(svcService.getConfigsWithDecryptedPassword(request)).thenReturn(requestCfg);
+
+            Exception thrown = Assertions.assertThrows(Exception.class, () -> 
mgr.validateConfig(request, null));
+            Assertions.assertTrue(thrown.getMessage().contains("must match the 
saved service configuration"));
+        } finally {
+            
PropertiesUtil.getPropertiesMap().remove(ServiceMgr.PROP_SERVICE_CONFIG_VALIDATE_REQUIRE_SAVED_SERVICE);
+        }
+    }
+
+    @Test
+    public void 
test20_validateConfig_requireSavedService_rejectsJdbcMultiHostMismatch() throws 
Exception {
+        ServiceMgr mgr = new ServiceMgr();
+        ServiceDBStore db = mock(ServiceDBStore.class);
+        RangerServiceService svcService = mock(RangerServiceService.class);
+        setField(mgr, ServiceMgr.class, "svcDBStore", db);
+        setField(mgr, ServiceMgr.class, "rangerSvcService", svcService);
+
+        
PropertiesUtil.getPropertiesMap().put(ServiceMgr.PROP_SERVICE_CONFIG_VALIDATE_REQUIRE_SAVED_SERVICE,
 "true");
+        try {
+            Map<String, String> savedCfg = new HashMap<>();
+            savedCfg.put("jdbc.url", "jdbc:mysql://knox.example.com:3306/db");
+
+            Map<String, String> requestCfg = new HashMap<>();
+            requestCfg.put("jdbc.url", 
"jdbc:mysql://knox.example.com:3306,evil.example.com:3306/db");
+
+            RangerService saved = new RangerService();
+            saved.setName("hive-prod");
+            saved.setType("hive");
+            saved.setConfigs(savedCfg);
+
+            RangerService request = new RangerService();
+            request.setName("hive-prod");
+            request.setType("hive");
+            request.setConfigs(requestCfg);
+
+            when(db.getServiceByName("hive-prod")).thenReturn(saved);
+            
when(svcService.getConfigsWithDecryptedPassword(saved)).thenReturn(savedCfg);
+            
when(svcService.getConfigsWithDecryptedPassword(request)).thenReturn(requestCfg);
+
+            Exception thrown = Assertions.assertThrows(Exception.class, () -> 
mgr.validateConfig(request, null));
+            Assertions.assertTrue(thrown.getMessage().contains("must match the 
saved service configuration"));
+        } finally {
+            
PropertiesUtil.getPropertiesMap().remove(ServiceMgr.PROP_SERVICE_CONFIG_VALIDATE_REQUIRE_SAVED_SERVICE);
+        }
+    }
+
+    @Test
+    public void 
test21_validateConfig_requireSavedService_allowsUsernameChange() throws 
Exception {
+        ServiceMgr mgr = new ServiceMgr();
+        ServiceDBStore db = mock(ServiceDBStore.class);
+        RangerServiceService svcService = mock(RangerServiceService.class);
+        ServiceStore store = mock(ServiceStore.class);
+        setField(mgr, ServiceMgr.class, "svcDBStore", db);
+        setField(mgr, ServiceMgr.class, "rangerSvcService", svcService);
+        wireValidateConfigSuccessPipeline(mgr, store, "knox");
+
+        
PropertiesUtil.getPropertiesMap().put(ServiceMgr.PROP_SERVICE_CONFIG_VALIDATE_REQUIRE_SAVED_SERVICE,
 "true");
+        try {
+            Map<String, String> savedCfg = new HashMap<>();
+            savedCfg.put("knox.url", "https://knox.example.com:8443/gateway";);
+            savedCfg.put("username", "admin");
+
+            Map<String, String> requestCfg = new HashMap<>(savedCfg);
+            requestCfg.put("username", "admin2");
+
+            RangerService saved = new RangerService();
+            saved.setName("knox-prod");
+            saved.setType("knox");
+            saved.setConfigs(savedCfg);
+
+            RangerService request = new RangerService();
+            request.setName("knox-prod");
+            request.setType("knox");
+            request.setConfigs(requestCfg);
+
+            when(db.getServiceByName("knox-prod")).thenReturn(saved);
+            
when(svcService.getConfigsWithDecryptedPassword(any(RangerService.class)))
+                    .thenAnswer(invocation -> new 
HashMap<>(invocation.<RangerService>getArgument(0).getConfigs()));
+
+            VXResponse response = mgr.validateConfig(request, store);
+            Assertions.assertNotNull(response);
+            Assertions.assertEquals(VXResponse.STATUS_SUCCESS, 
response.getStatusCode());
+        } finally {
+            
PropertiesUtil.getPropertiesMap().remove(ServiceMgr.PROP_SERVICE_CONFIG_VALIDATE_REQUIRE_SAVED_SERVICE);
+        }
+    }
+
+    @Test
+    public void 
test22_validateConfig_requireSavedService_rejectsOpaqueJdbcUrlChange() throws 
Exception {
+        ServiceMgr mgr = new ServiceMgr();
+        ServiceDBStore db = mock(ServiceDBStore.class);
+        RangerServiceService svcService = mock(RangerServiceService.class);
+        setField(mgr, ServiceMgr.class, "svcDBStore", db);
+        setField(mgr, ServiceMgr.class, "rangerSvcService", svcService);
+
+        
PropertiesUtil.getPropertiesMap().put(ServiceMgr.PROP_SERVICE_CONFIG_VALIDATE_REQUIRE_SAVED_SERVICE,
 "true");
+        try {
+            Map<String, String> savedCfg = new HashMap<>();
+            savedCfg.put("jdbc.url", 
"jdbc:mysql:loadbalance://knox.example.com:3306/db");
+
+            Map<String, String> requestCfg = new HashMap<>();
+            requestCfg.put("jdbc.url", 
"jdbc:mysql:loadbalance://evil.example.com:3306/db");
+
+            RangerService saved = new RangerService();
+            saved.setName("hive-prod");
+            saved.setType("hive");
+            saved.setConfigs(savedCfg);
+
+            RangerService request = new RangerService();
+            request.setName("hive-prod");
+            request.setType("hive");
+            request.setConfigs(requestCfg);
+
+            when(db.getServiceByName("hive-prod")).thenReturn(saved);
+            
when(svcService.getConfigsWithDecryptedPassword(saved)).thenReturn(savedCfg);
+            
when(svcService.getConfigsWithDecryptedPassword(request)).thenReturn(requestCfg);
+
+            Exception thrown = Assertions.assertThrows(Exception.class, () -> 
mgr.validateConfig(request, null));
+            Assertions.assertTrue(thrown.getMessage().contains("must match the 
saved service configuration"));
+        } finally {
+            
PropertiesUtil.getPropertiesMap().remove(ServiceMgr.PROP_SERVICE_CONFIG_VALIDATE_REQUIRE_SAVED_SERVICE);
+        }
+    }
+
+    @Test
+    public void 
test23_validateConfig_requireSavedService_rejectsHive2PathWhenHostNotParsed() 
throws Exception {
+        ServiceMgr mgr = new ServiceMgr();
+        ServiceDBStore db = mock(ServiceDBStore.class);
+        RangerServiceService svcService = mock(RangerServiceService.class);
+        setField(mgr, ServiceMgr.class, "svcDBStore", db);
+        setField(mgr, ServiceMgr.class, "rangerSvcService", svcService);
+
+        
PropertiesUtil.getPropertiesMap().put(ServiceMgr.PROP_SERVICE_CONFIG_VALIDATE_REQUIRE_SAVED_SERVICE,
 "true");
+        try {
+            Map<String, String> savedCfg = new HashMap<>();
+            savedCfg.put("jdbc.url", "jdbc:hive2:///default");
+
+            Map<String, String> requestCfg = new HashMap<>();
+            requestCfg.put("jdbc.url", "jdbc:hive2:///attacker");
+
+            RangerService saved = new RangerService();
+            saved.setName("hive-prod");
+            saved.setType("hive");
+            saved.setConfigs(savedCfg);
+
+            RangerService request = new RangerService();
+            request.setName("hive-prod");
+            request.setType("hive");
+            request.setConfigs(requestCfg);
+
+            when(db.getServiceByName("hive-prod")).thenReturn(saved);
+            
when(svcService.getConfigsWithDecryptedPassword(saved)).thenReturn(savedCfg);
+            
when(svcService.getConfigsWithDecryptedPassword(request)).thenReturn(requestCfg);
+
+            Exception thrown = Assertions.assertThrows(Exception.class, () -> 
mgr.validateConfig(request, null));
+            Assertions.assertTrue(thrown.getMessage().contains("must match the 
saved service configuration"));
+        } finally {
+            
PropertiesUtil.getPropertiesMap().remove(ServiceMgr.PROP_SERVICE_CONFIG_VALIDATE_REQUIRE_SAVED_SERVICE);
+        }
+    }
+
+    @Test
+    public void 
test24_validateConfig_requireSavedService_rejectsHostPortWithoutScheme() throws 
Exception {
+        ServiceMgr mgr = new ServiceMgr();
+        ServiceDBStore db = mock(ServiceDBStore.class);
+        RangerServiceService svcService = mock(RangerServiceService.class);
+        setField(mgr, ServiceMgr.class, "svcDBStore", db);
+        setField(mgr, ServiceMgr.class, "rangerSvcService", svcService);
+
+        
PropertiesUtil.getPropertiesMap().put(ServiceMgr.PROP_SERVICE_CONFIG_VALIDATE_REQUIRE_SAVED_SERVICE,
 "true");
+        try {
+            Map<String, String> savedCfg = new HashMap<>();
+            savedCfg.put("knox.url", "https://knox.example.com:8443/gateway";);
+
+            Map<String, String> requestCfg = new HashMap<>();
+            requestCfg.put("knox.url", "evil.example.com:8443");
+
+            RangerService saved = new RangerService();
+            saved.setName("knox-prod");
+            saved.setType("knox");
+            saved.setConfigs(savedCfg);
+
+            RangerService request = new RangerService();
+            request.setName("knox-prod");
+            request.setType("knox");
+            request.setConfigs(requestCfg);
+
+            when(db.getServiceByName("knox-prod")).thenReturn(saved);
+            
when(svcService.getConfigsWithDecryptedPassword(saved)).thenReturn(savedCfg);
+            
when(svcService.getConfigsWithDecryptedPassword(request)).thenReturn(requestCfg);
+
+            Exception thrown = Assertions.assertThrows(Exception.class, () -> 
mgr.validateConfig(request, null));
+            Assertions.assertTrue(thrown.getMessage().contains("must match the 
saved service configuration"));
+        } finally {
+            
PropertiesUtil.getPropertiesMap().remove(ServiceMgr.PROP_SERVICE_CONFIG_VALIDATE_REQUIRE_SAVED_SERVICE);
+        }
+    }
+
+    @Test
+    public void 
test25_validateConfig_requireSavedService_rejectsOracleThinHostChange() throws 
Exception {
+        ServiceMgr mgr = new ServiceMgr();
+        ServiceDBStore db = mock(ServiceDBStore.class);
+        RangerServiceService svcService = mock(RangerServiceService.class);
+        setField(mgr, ServiceMgr.class, "svcDBStore", db);
+        setField(mgr, ServiceMgr.class, "rangerSvcService", svcService);
+
+        
PropertiesUtil.getPropertiesMap().put(ServiceMgr.PROP_SERVICE_CONFIG_VALIDATE_REQUIRE_SAVED_SERVICE,
 "true");
+        try {
+            Map<String, String> savedCfg = new HashMap<>();
+            savedCfg.put("jdbc.url", 
"jdbc:oracle:thin:@knox.example.com:1521:ORCL");
+
+            Map<String, String> requestCfg = new HashMap<>();
+            requestCfg.put("jdbc.url", 
"jdbc:oracle:thin:@evil.example.com:1521:ORCL");
+
+            RangerService saved = new RangerService();
+            saved.setName("oracle-prod");
+            saved.setType("oracle");
+            saved.setConfigs(savedCfg);
+
+            RangerService request = new RangerService();
+            request.setName("oracle-prod");
+            request.setType("oracle");
+            request.setConfigs(requestCfg);
+
+            when(db.getServiceByName("oracle-prod")).thenReturn(saved);
+            
when(svcService.getConfigsWithDecryptedPassword(saved)).thenReturn(savedCfg);
+            
when(svcService.getConfigsWithDecryptedPassword(request)).thenReturn(requestCfg);
+
+            Exception thrown = Assertions.assertThrows(Exception.class, () -> 
mgr.validateConfig(request, null));
+            Assertions.assertTrue(thrown.getMessage().contains("must match the 
saved service configuration"));
+        } finally {
+            
PropertiesUtil.getPropertiesMap().remove(ServiceMgr.PROP_SERVICE_CONFIG_VALIDATE_REQUIRE_SAVED_SERVICE);
+        }
+    }
+
+    @Test
+    public void 
test26_validateConfig_requireSavedService_rejectsZkQuorumChange() throws 
Exception {
+        ServiceMgr mgr = new ServiceMgr();
+        ServiceDBStore db = mock(ServiceDBStore.class);
+        RangerServiceService svcService = mock(RangerServiceService.class);
+        setField(mgr, ServiceMgr.class, "svcDBStore", db);
+        setField(mgr, ServiceMgr.class, "rangerSvcService", svcService);
+
+        
PropertiesUtil.getPropertiesMap().put(ServiceMgr.PROP_SERVICE_CONFIG_VALIDATE_REQUIRE_SAVED_SERVICE,
 "true");
+        try {
+            Map<String, String> savedCfg = new HashMap<>();
+            savedCfg.put("zookeeper.connect", 
"zk1.example.com:2181,zk2.example.com:2181");
+
+            Map<String, String> requestCfg = new HashMap<>();
+            requestCfg.put("zookeeper.connect", 
"zk1.example.com:2181,evil.example.com:2181");
+
+            RangerService saved = new RangerService();
+            saved.setName("kafka-prod");
+            saved.setType("kafka");
+            saved.setConfigs(savedCfg);
+
+            RangerService request = new RangerService();
+            request.setName("kafka-prod");
+            request.setType("kafka");
+            request.setConfigs(requestCfg);
+
+            when(db.getServiceByName("kafka-prod")).thenReturn(saved);
+            
when(svcService.getConfigsWithDecryptedPassword(saved)).thenReturn(savedCfg);
+            
when(svcService.getConfigsWithDecryptedPassword(request)).thenReturn(requestCfg);
+
+            Exception thrown = Assertions.assertThrows(Exception.class, () -> 
mgr.validateConfig(request, null));
+            Assertions.assertTrue(thrown.getMessage().contains("must match the 
saved service configuration"));
+        } finally {
+            
PropertiesUtil.getPropertiesMap().remove(ServiceMgr.PROP_SERVICE_CONFIG_VALIDATE_REQUIRE_SAVED_SERVICE);
+        }
+    }
 }
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 b08a4f52d..bc208f314 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
@@ -633,10 +633,56 @@ public void test13lookupResource() throws Exception {
         ResourceLookupContext context     = new ResourceLookupContext();
         context.setResourceName(serviceName);
         context.setUserInput("HDFS");
+        XXService    xService    = xService();
+        XXServiceDao xServiceDao = Mockito.mock(XXServiceDao.class);
+        Mockito.when(daoManager.getXXService()).thenReturn(xServiceDao);
+        Mockito.when(xServiceDao.findByName(serviceName)).thenReturn(xService);
+        Mockito.when(bizUtil.hasAccess(xService, null)).thenReturn(true);
         List<String> list = serviceREST.lookupResource(serviceName, context);
         Assertions.assertNotNull(list);
     }
 
+    @Test
+    public void test13bLookupResourceNotFound() throws Exception {
+        String                serviceName = "missing_service";
+        ResourceLookupContext context     = new ResourceLookupContext();
+        XXServiceDao          xServiceDao = Mockito.mock(XXServiceDao.class);
+
+        Mockito.when(daoManager.getXXService()).thenReturn(xServiceDao);
+        Mockito.when(xServiceDao.findByName(serviceName)).thenReturn(null);
+        
Mockito.when(restErrorUtil.createRESTException(Mockito.eq(HttpServletResponse.SC_NOT_FOUND),
 Mockito.eq("Not found"), Mockito.eq(true)))
+                .thenReturn(new 
WebApplicationException(HttpServletResponse.SC_NOT_FOUND));
+
+        WebApplicationException exception = 
Assertions.assertThrows(WebApplicationException.class,
+                () -> serviceREST.lookupResource(serviceName, context));
+
+        Assertions.assertEquals(HttpServletResponse.SC_NOT_FOUND, 
exception.getResponse().getStatus());
+        
Mockito.verify(restErrorUtil).createRESTException(HttpServletResponse.SC_NOT_FOUND,
 "Not found", true);
+        Mockito.verify(serviceMgr, 
Mockito.never()).lookupResource(Mockito.anyString(), Mockito.any(), 
Mockito.any());
+    }
+
+    @Test
+    public void test13cLookupResourceForbiddenWhenNoAccess() throws Exception {
+        String                serviceName = "HDFS_1";
+        ResourceLookupContext context     = new ResourceLookupContext();
+        XXService             xService    = xService();
+        XXServiceDao          xServiceDao = Mockito.mock(XXServiceDao.class);
+        String                deniedMsg   = "Operation denied. User is not 
permitted to lookup resources for service " + serviceName;
+
+        Mockito.when(daoManager.getXXService()).thenReturn(xServiceDao);
+        Mockito.when(xServiceDao.findByName(serviceName)).thenReturn(xService);
+        Mockito.when(bizUtil.hasAccess(xService, null)).thenReturn(false);
+        
Mockito.when(restErrorUtil.createRESTException(Mockito.eq(HttpServletResponse.SC_FORBIDDEN),
 Mockito.eq(deniedMsg), Mockito.eq(true)))
+                .thenReturn(new 
WebApplicationException(HttpServletResponse.SC_FORBIDDEN));
+
+        WebApplicationException exception = 
Assertions.assertThrows(WebApplicationException.class,
+                () -> serviceREST.lookupResource(serviceName, context));
+
+        Assertions.assertEquals(HttpServletResponse.SC_FORBIDDEN, 
exception.getResponse().getStatus());
+        
Mockito.verify(restErrorUtil).createRESTException(HttpServletResponse.SC_FORBIDDEN,
 deniedMsg, true);
+        Mockito.verify(serviceMgr, 
Mockito.never()).lookupResource(Mockito.anyString(), Mockito.any(), 
Mockito.any());
+    }
+
     @Test
     public void test14grantAccess() throws Exception {
         HttpServletRequest request         = 
Mockito.mock(HttpServletRequest.class);
@@ -3302,6 +3348,11 @@ public void test116LookupResourceWithException() throws 
Exception {
         context.setResourceName(serviceName);
         context.setUserInput("HDFS");
 
+        XXService    xService    = xService();
+        XXServiceDao xServiceDao = Mockito.mock(XXServiceDao.class);
+        Mockito.when(daoManager.getXXService()).thenReturn(xServiceDao);
+        Mockito.when(xServiceDao.findByName(serviceName)).thenReturn(xService);
+        Mockito.when(bizUtil.hasAccess(xService, null)).thenReturn(true);
         Mockito.when(serviceMgr.lookupResource(serviceName, context, 
svcStore)).thenThrow(new RuntimeException("Lookup failed"));
         
Mockito.when(restErrorUtil.createRESTException(Mockito.anyString())).thenReturn(new
 WebApplicationException());
 

Reply via email to