You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ranger.apache.org by ma...@apache.org on 2020/08/13 17:08:07 UTC

[ranger] branch master updated: RANGER-2948: Ranger plugin enhancement to support a hook to register plugin chains

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

madhan 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 74d1638  RANGER-2948: Ranger plugin enhancement to support a hook to register plugin chains
74d1638 is described below

commit 74d163806fc1e26f955cc38854a77c94e9959102
Author: Abhay Kulkarni <ab...@apache.org>
AuthorDate: Sun Aug 9 15:16:11 2020 -0700

    RANGER-2948: Ranger plugin enhancement to support a hook to register plugin chains
    
    Signed-off-by: Madhan Neethiraj <ma...@apache.org>
---
 .../ranger/admin/client/RangerAdminRESTClient.java |   6 +
 .../hadoop/config/RangerChainedPluginConfig.java   |  51 +++++++++
 .../hadoop/config/RangerConfiguration.java         |  30 ++++-
 .../ranger/authorization/utils/JsonUtils.java      |  12 +-
 .../ranger/authorization/utils/StringUtil.java     |  16 +++
 .../plugin/contextenricher/RangerTagEnricher.java  |   2 +-
 .../contextenricher/RangerUserStoreEnricher.java   |   1 +
 .../ranger/plugin/model/RangerServiceResource.java |  25 ++++-
 .../ranger/plugin/service/RangerBasePlugin.java    | 125 +++++++++++++++++++--
 .../ranger/plugin/service/RangerChainedPlugin.java |  62 ++++++++++
 .../RangerCustomConditionMatcherTest.java          |   2 +
 .../test_policyengine_tag_hive_filebased.json      |   8 ++
 12 files changed, 315 insertions(+), 25 deletions(-)

diff --git a/agents-common/src/main/java/org/apache/ranger/admin/client/RangerAdminRESTClient.java b/agents-common/src/main/java/org/apache/ranger/admin/client/RangerAdminRESTClient.java
index 479a50c..9d527bf 100644
--- a/agents-common/src/main/java/org/apache/ranger/admin/client/RangerAdminRESTClient.java
+++ b/agents-common/src/main/java/org/apache/ranger/admin/client/RangerAdminRESTClient.java
@@ -30,6 +30,7 @@ import org.apache.hadoop.security.AccessControlException;
 import org.apache.hadoop.security.UserGroupInformation;
 import org.apache.ranger.admin.client.datatype.RESTResponse;
 import org.apache.ranger.audit.provider.MiscUtil;
+import org.apache.ranger.authorization.hadoop.config.RangerPluginConfig;
 import org.apache.ranger.authorization.utils.StringUtil;
 import org.apache.ranger.plugin.model.RangerRole;
 import org.apache.ranger.plugin.util.*;
@@ -88,6 +89,11 @@ public class RangerAdminRESTClient extends AbstractRangerAdminClient {
 		clusterName       				= config.get(propertyPrefix + ".access.cluster.name", "");
 		if(StringUtil.isEmpty(clusterName)){
 			clusterName =config.get(propertyPrefix + ".ambari.cluster.name", "");
+			if (StringUtil.isEmpty(clusterName)) {
+				if (config instanceof RangerPluginConfig) {
+					clusterName = ((RangerPluginConfig)config).getClusterName();
+				}
+			}
 		}
 		int	 restClientConnTimeOutMs	= config.getInt(propertyPrefix + ".policy.rest.client.connection.timeoutMs", 120 * 1000);
 		int	 restClientReadTimeOutMs	= config.getInt(propertyPrefix + ".policy.rest.client.read.timeoutMs", 30 * 1000);
diff --git a/agents-common/src/main/java/org/apache/ranger/authorization/hadoop/config/RangerChainedPluginConfig.java b/agents-common/src/main/java/org/apache/ranger/authorization/hadoop/config/RangerChainedPluginConfig.java
new file mode 100644
index 0000000..9e25ae1
--- /dev/null
+++ b/agents-common/src/main/java/org/apache/ranger/authorization/hadoop/config/RangerChainedPluginConfig.java
@@ -0,0 +1,51 @@
+/*
+ * 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.authorization.hadoop.config;
+
+public class RangerChainedPluginConfig extends RangerPluginConfig {
+    public RangerChainedPluginConfig(String serviceType, String serviceName, String appId, RangerPluginConfig sourcePluginConfig) {
+        super(serviceType,  serviceName, appId, sourcePluginConfig.getClusterName(), sourcePluginConfig.getClusterType(), null);
+
+        // add necessary config "overrides", so that RangerAdminClient implementations (like RangerAdminRESTClient)
+        // will use configurations from ranger-<source-service-type>-security.xml (sourcePluginConfig) to connect to Ranger Admin
+
+        set(getPropertyPrefix() + ".service.name", serviceName);
+        copyProperty(sourcePluginConfig, ".policy.source.impl");
+        copyProperty(sourcePluginConfig, ".policy.cache.dir");
+        copyProperty(sourcePluginConfig, ".policy.rest.url");
+        copyProperty(sourcePluginConfig, ".policy.rest.ssl.config.file");
+        copyProperty(sourcePluginConfig, ".policy.pollIntervalMs", 30 * 1000);
+        copyProperty(sourcePluginConfig, ".policy.rest.client.connection.timeoutMs", 120 * 1000);
+        copyProperty(sourcePluginConfig, ".policy.rest.read.timeoutMs", 30 * 1000);
+        copyProperty(sourcePluginConfig, ".policy.rest.supports.policy.deltas");
+        copyProperty(sourcePluginConfig, ".tag.rest.supports.tag.deltas");
+    }
+
+    protected void copyProperty(RangerPluginConfig sourcePluginConfig, String propertySuffix) {
+        String value = sourcePluginConfig.get("ranger.plugin." + sourcePluginConfig.getServiceType() + propertySuffix);
+        if (value != null) {
+            set(getPropertyPrefix() + propertySuffix, sourcePluginConfig.get("ranger.plugin." + sourcePluginConfig.getServiceType() + propertySuffix));
+        }
+    }
+
+    protected void copyProperty(RangerPluginConfig sourcePluginConfig, String propertySuffix, int defaultValue) {
+        setInt(getPropertyPrefix() + propertySuffix, sourcePluginConfig.getInt("ranger.plugin" + sourcePluginConfig.getServiceType() + propertySuffix, defaultValue));
+    }
+}
diff --git a/agents-common/src/main/java/org/apache/ranger/authorization/hadoop/config/RangerConfiguration.java b/agents-common/src/main/java/org/apache/ranger/authorization/hadoop/config/RangerConfiguration.java
index 43ddf0b..d837a69 100644
--- a/agents-common/src/main/java/org/apache/ranger/authorization/hadoop/config/RangerConfiguration.java
+++ b/agents-common/src/main/java/org/apache/ranger/authorization/hadoop/config/RangerConfiguration.java
@@ -20,9 +20,12 @@
 
 package org.apache.ranger.authorization.hadoop.config;
 
+import java.io.File;
+import java.net.MalformedURLException;
 import java.net.URL;
 import java.util.Properties;
 
+import org.apache.commons.lang.StringUtils;
 import org.apache.hadoop.conf.Configuration;
 import org.apache.log4j.Logger;
 
@@ -70,12 +73,29 @@ public class RangerConfiguration extends Configuration {
 		return getProps();
 	}
 
-
 	private URL getFileLocation(String fileName) {
-		URL lurl = RangerConfiguration.class.getClassLoader().getResource(fileName);
-		
-		if (lurl == null ) {
-			lurl = RangerConfiguration.class.getClassLoader().getResource("/" + fileName);
+		URL lurl = null;
+		if (!StringUtils.isEmpty(fileName)) {
+			lurl = RangerConfiguration.class.getClassLoader().getResource(fileName);
+
+			if (lurl == null ) {
+				lurl = RangerConfiguration.class.getClassLoader().getResource("/" + fileName);
+			}
+
+			if (lurl == null ) {
+				File f = new File(fileName);
+				if (f.exists()) {
+					try {
+						lurl=f.toURI().toURL();
+					} catch (MalformedURLException e) {
+						LOG.error("Unable to load the resource name [" + fileName + "]. Ignoring the resource:" + f.getPath());
+					}
+				} else {
+					if(LOG.isDebugEnabled()) {
+						LOG.debug("Conf file path " + fileName + " does not exists");
+					}
+				}
+			}
 		}
 		return lurl;
 	}
diff --git a/agents-common/src/main/java/org/apache/ranger/authorization/utils/JsonUtils.java b/agents-common/src/main/java/org/apache/ranger/authorization/utils/JsonUtils.java
index 74555ee..994d394 100644
--- a/agents-common/src/main/java/org/apache/ranger/authorization/utils/JsonUtils.java
+++ b/agents-common/src/main/java/org/apache/ranger/authorization/utils/JsonUtils.java
@@ -29,20 +29,16 @@ import org.apache.ranger.plugin.model.RangerValidityRecurrence;
 import org.apache.ranger.plugin.model.RangerValiditySchedule;
 
 import java.lang.reflect.Type;
-import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
 public class JsonUtils {
     private static final Log LOG = LogFactory.getLog(JsonUtils.class);
 
-    private static final HashMap<String, String> MAP_STRING_STRING = new HashMap<>();
-
     private static final Gson gson;
 
     static {
-        gson = new GsonBuilder().setDateFormat("yyyyMMdd-HH:mm:ss.SSS-Z")
-                .create();
+        gson = new GsonBuilder().setDateFormat("yyyyMMdd-HH:mm:ss.SSS-Z").create();
     }
 
     public static String mapToJson(Map<?, ?> map) {
@@ -102,7 +98,8 @@ public class JsonUtils {
 
         if(StringUtils.isNotEmpty(jsonStr)) {
             try {
-                ret = gson.fromJson(jsonStr, MAP_STRING_STRING.getClass());
+                Type mapType = new TypeToken<Map<String, String>>() {}.getType();
+                ret = gson.fromJson(jsonStr, mapType);
             } catch(Exception excp) {
                 LOG.warn("jsonToObject() failed to convert json to object: " + jsonStr, excp);
             }
@@ -113,8 +110,7 @@ public class JsonUtils {
 
     public static List<RangerValiditySchedule> jsonToRangerValiditySchedule(String jsonStr) {
         try {
-            Type listType = new TypeToken<List<RangerValiditySchedule>>() {
-            }.getType();
+            Type listType = new TypeToken<List<RangerValiditySchedule>>() {}.getType();
             return gson.fromJson(jsonStr, listType);
         } catch (Exception e) {
             LOG.error("Cannot get List<RangerValiditySchedule> from " + jsonStr, e);
diff --git a/agents-common/src/main/java/org/apache/ranger/authorization/utils/StringUtil.java b/agents-common/src/main/java/org/apache/ranger/authorization/utils/StringUtil.java
index 4e959bc..e6c9602 100644
--- a/agents-common/src/main/java/org/apache/ranger/authorization/utils/StringUtil.java
+++ b/agents-common/src/main/java/org/apache/ranger/authorization/utils/StringUtil.java
@@ -22,6 +22,7 @@
 import java.util.ArrayList;
 import java.util.Calendar;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.Date;
 import java.util.GregorianCalendar;
 import java.util.HashMap;
@@ -307,6 +308,21 @@ public class StringUtil {
 		return values;
 	}
 
+	public static List<String> toList(String str) {
+		List<String> values;
+		if (StringUtils.isNotBlank(str)) {
+			values = new ArrayList<>();
+			for (String item : str.split(",")) {
+				if (StringUtils.isNotBlank(item)) {
+					values.add(StringUtils.trim(item));
+				}
+			}
+		} else {
+			values = Collections.emptyList();
+		}
+		return values;
+	}
+
 	public static List<String> getURLs(String configURLs) {
 		List<String> configuredURLs = new ArrayList<>();
 		if(configURLs!=null) {
diff --git a/agents-common/src/main/java/org/apache/ranger/plugin/contextenricher/RangerTagEnricher.java b/agents-common/src/main/java/org/apache/ranger/plugin/contextenricher/RangerTagEnricher.java
index 4c60efe..94ac749 100644
--- a/agents-common/src/main/java/org/apache/ranger/plugin/contextenricher/RangerTagEnricher.java
+++ b/agents-common/src/main/java/org/apache/ranger/plugin/contextenricher/RangerTagEnricher.java
@@ -213,7 +213,7 @@ public class RangerTagEnricher extends RangerAbstractContextEnricher {
 	 * combinations in a service-def will be much smaller than the number of service-resources.
 	 */
 
-	static class ResourceHierarchies {
+	static public class ResourceHierarchies {
 		private final Map<Collection<String>, Boolean> accessHierarchies    = new HashMap<>();
 		private final Map<Collection<String>, Boolean> dataMaskHierarchies  = new HashMap<>();
 		private final Map<Collection<String>, Boolean> rowFilterHierarchies = new HashMap<>();
diff --git a/agents-common/src/main/java/org/apache/ranger/plugin/contextenricher/RangerUserStoreEnricher.java b/agents-common/src/main/java/org/apache/ranger/plugin/contextenricher/RangerUserStoreEnricher.java
index cc892d8..1061633 100644
--- a/agents-common/src/main/java/org/apache/ranger/plugin/contextenricher/RangerUserStoreEnricher.java
+++ b/agents-common/src/main/java/org/apache/ranger/plugin/contextenricher/RangerUserStoreEnricher.java
@@ -99,6 +99,7 @@ public class RangerUserStoreEnricher extends RangerAbstractContextEnricher {
                 userStoreRetriever.init(enricherDef.getEnricherOptions());
 
                 userStoreRefresher = new RangerUserStoreRefresher(userStoreRetriever, this, null, -1L, userStoreDownloadQueue, cacheFile);
+                LOG.info("Created Thread(RangerUserStoreRefresher(" + getName() + ")");
 
                 try {
                     userStoreRefresher.populateUserStoreInfo();
diff --git a/agents-common/src/main/java/org/apache/ranger/plugin/model/RangerServiceResource.java b/agents-common/src/main/java/org/apache/ranger/plugin/model/RangerServiceResource.java
index 67230c6..bdd3e54 100644
--- a/agents-common/src/main/java/org/apache/ranger/plugin/model/RangerServiceResource.java
+++ b/agents-common/src/main/java/org/apache/ranger/plugin/model/RangerServiceResource.java
@@ -39,24 +39,31 @@ public class RangerServiceResource extends RangerBaseModelObject {
 
 	private String                                         serviceName;
 	private Map<String, RangerPolicy.RangerPolicyResource> resourceElements;
+	private String                                         ownerUser;
 	private String                                         resourceSignature;
 
-	public RangerServiceResource(String guid, String serviceName, Map<String, RangerPolicy.RangerPolicyResource> resourceElements, String resourceSignature) {
+	public RangerServiceResource(String guid, String serviceName, Map<String, RangerPolicy.RangerPolicyResource> resourceElements, String resourceSignature, String ownerUser) {
 		super();
 		setGuid(guid);
 		setServiceName(serviceName);
 		setResourceElements(resourceElements);
 		setResourceSignature(resourceSignature);
+		setOwnerUser(ownerUser);
 	}
+	public RangerServiceResource(String guid, String serviceName, Map<String, RangerPolicy.RangerPolicyResource> resourceElements, String resourceSignature) {
+		this(guid, serviceName, resourceElements, resourceSignature, null);
+
+	}
+
 	public RangerServiceResource(String guid, String serviceName, Map<String, RangerPolicy.RangerPolicyResource> resourceElements) {
-		this(guid, serviceName, resourceElements, null);
+		this(guid, serviceName, resourceElements, null, null);
 	}
 	public RangerServiceResource(String serviceName, Map<String, RangerPolicy.RangerPolicyResource> resourceElements) {
-		this(null, serviceName, resourceElements, null);
+		this(null, serviceName, resourceElements, null, null);
 	}
 
 	public RangerServiceResource() {
-		this(null, null, null, null);
+		this(null, null, null, null, null);
 	}
 
 	public String getServiceName() { return serviceName; }
@@ -67,6 +74,10 @@ public class RangerServiceResource extends RangerBaseModelObject {
 		return resourceSignature;
 	}
 
+	public String getOwnerUser() {
+		return ownerUser;
+	}
+
 	public void setServiceName(String serviceName) {
 		this.serviceName = serviceName;
 	}
@@ -79,6 +90,10 @@ public class RangerServiceResource extends RangerBaseModelObject {
 		this.resourceSignature = resourceSignature;
 	}
 
+	public void setOwnerUser(String ownerUser) {
+		this.ownerUser = ownerUser;
+	}
+
 	@Override
 	public String toString() {
 		StringBuilder sb = new StringBuilder();
@@ -107,6 +122,8 @@ public class RangerServiceResource extends RangerBaseModelObject {
 		}
 		sb.append("} ");
 
+		sb.append("ownerUser={").append(ownerUser).append("} ");
+
 		sb.append("resourceSignature={").append(resourceSignature).append("} ");
 
 		sb.append(" }");
diff --git a/agents-common/src/main/java/org/apache/ranger/plugin/service/RangerBasePlugin.java b/agents-common/src/main/java/org/apache/ranger/plugin/service/RangerBasePlugin.java
index 794d3cb..2d9bc73 100644
--- a/agents-common/src/main/java/org/apache/ranger/plugin/service/RangerBasePlugin.java
+++ b/agents-common/src/main/java/org/apache/ranger/plugin/service/RangerBasePlugin.java
@@ -66,6 +66,7 @@ public class RangerBasePlugin {
 	private       RangerAuthContext           currentAuthContext;
 	private       RangerAccessResultProcessor resultProcessor;
 	private       RangerRoles                 roles;
+	private final List<RangerChainedPlugin>   chainedPlugins;
 
 
 	public RangerBasePlugin(String serviceType, String appId) {
@@ -90,6 +91,8 @@ public class RangerBasePlugin {
 		setAuditExcludedUsersGroupsRoles(auditExcludeUsers, auditExcludeGroups, auditExcludeRoles);
 
 		RangerScriptExecutionContext.init(pluginConfig);
+
+		this.chainedPlugins = initChainedPlugins();
 	}
 
 	public static AuditHandler getAuditProvider(String serviceName) {
@@ -180,6 +183,10 @@ public class RangerBasePlugin {
 		LOG.info("Created PolicyRefresher Thread(" + refresher.getName() + ")");
 		refresher.setDaemon(true);
 		refresher.startRefresher();
+
+		for (RangerChainedPlugin chainedPlugin : chainedPlugins) {
+			chainedPlugin.init();
+		}
 	}
 
 	public void setPolicies(ServicePolicies policies) {
@@ -339,23 +346,64 @@ public class RangerBasePlugin {
 	}
 
 	public RangerAccessResult isAccessAllowed(RangerAccessRequest request, RangerAccessResultProcessor resultProcessor) {
+		RangerAccessResult ret          = null;
 		RangerPolicyEngine policyEngine = this.policyEngine;
 
-		if(policyEngine != null) {
-			return policyEngine.evaluatePolicies(request, RangerPolicy.POLICY_TYPE_ACCESS, resultProcessor);
+		if (policyEngine != null) {
+			ret = policyEngine.evaluatePolicies(request, RangerPolicy.POLICY_TYPE_ACCESS, null);
 		}
 
-		return null;
+		if (ret != null) {
+			for (RangerChainedPlugin chainedPlugin : chainedPlugins) {
+				RangerAccessResult chainedResult = chainedPlugin.isAccessAllowed(request);
+
+				if (chainedResult != null) {
+					updateResultFromChainedResult(ret, chainedResult);
+				}
+			}
+
+		}
+
+		if (resultProcessor != null) {
+			resultProcessor.processResult(ret);
+		}
+
+		return ret;
 	}
 
 	public Collection<RangerAccessResult> isAccessAllowed(Collection<RangerAccessRequest> requests, RangerAccessResultProcessor resultProcessor) {
-		RangerPolicyEngine policyEngine = this.policyEngine;
+		Collection<RangerAccessResult> ret          = null;
+		RangerPolicyEngine             policyEngine = this.policyEngine;
 
-		if(policyEngine != null) {
-			return policyEngine.evaluatePolicies(requests, RangerPolicy.POLICY_TYPE_ACCESS, resultProcessor);
+		if (policyEngine != null) {
+			ret = policyEngine.evaluatePolicies(requests, RangerPolicy.POLICY_TYPE_ACCESS, null);
 		}
 
-		return null;
+		if (CollectionUtils.isNotEmpty(ret)) {
+			for (RangerChainedPlugin chainedPlugin : chainedPlugins) {
+				Collection<RangerAccessResult> chainedResults = chainedPlugin.isAccessAllowed(requests);
+
+				if (CollectionUtils.isNotEmpty(chainedResults)) {
+					Iterator<RangerAccessResult> iterRet            = ret.iterator();
+					Iterator<RangerAccessResult> iterChainedResults = chainedResults.iterator();
+
+					while (iterRet.hasNext() && iterChainedResults.hasNext()) {
+						RangerAccessResult result        = iterRet.next();
+						RangerAccessResult chainedResult = iterChainedResults.next();
+
+						if (result != null && chainedResult != null) {
+							updateResultFromChainedResult(result, chainedResult);
+						}
+					}
+				}
+			}
+		}
+
+		if (resultProcessor != null) {
+			resultProcessor.processResults(ret);
+		}
+
+		return ret;
 	}
 
 	public RangerAccessResult evalDataMaskPolicies(RangerAccessRequest request, RangerAccessResultProcessor resultProcessor) {
@@ -750,6 +798,69 @@ public class RangerBasePlugin {
 		return admin;
 	}
 
+	private List<RangerChainedPlugin> initChainedPlugins() {
+		List<RangerChainedPlugin> ret                      = new ArrayList<>();
+		String                    chainedServicePropPrefix = pluginConfig.getPropertyPrefix() + ".chained.services";
+
+		for (String chainedService : StringUtil.toList(pluginConfig.get(chainedServicePropPrefix))) {
+			if (StringUtils.isBlank(chainedService)) {
+				continue;
+			}
+
+			String className = pluginConfig.get(chainedServicePropPrefix + "." + chainedService + ".impl");
+
+			if (StringUtils.isBlank(className)) {
+				LOG.error("Ignoring chained service " + chainedService + ": no impl class specified");
+
+				continue;
+			}
+
+			try {
+				@SuppressWarnings("unchecked")
+				Class<RangerChainedPlugin> pluginClass   = (Class<RangerChainedPlugin>) Class.forName(className);
+				RangerChainedPlugin        chainedPlugin = pluginClass.getConstructor(RangerBasePlugin.class, String.class).newInstance(this, chainedService);
+
+				ret.add(chainedPlugin);
+			} catch (Throwable t) {
+				LOG.error("initChainedPlugins(): error instantiating plugin impl " + className, t);
+			}
+		}
+
+		return ret;
+	}
+
+	private void updateResultFromChainedResult(RangerAccessResult result, RangerAccessResult chainedResult) {
+		boolean overrideResult = false;
+
+		if (chainedResult.getIsAccessDetermined()) { // only if chained-result is definitive
+			// override if result is not definitive or chained-result is by a higher priority policy
+			overrideResult = !result.getIsAccessDetermined() || chainedResult.getPolicyPriority() > result.getPolicyPriority();
+
+			if (!overrideResult) {
+				// override if chained-result is from the same policy priority, and if denies access
+				if (chainedResult.getPolicyPriority() == result.getPolicyPriority() && !chainedResult.getIsAllowed()) {
+					// let's not override if result is already denied
+					if (result.getIsAllowed()) {
+						overrideResult = true;
+					}
+				}
+			}
+		}
+
+		if (overrideResult) {
+			result.setIsAllowed(chainedResult.getIsAllowed());
+			result.setIsAccessDetermined(chainedResult.getIsAccessDetermined());
+			result.setPolicyId(chainedResult.getPolicyId());
+			result.setPolicyVersion(chainedResult.getPolicyVersion());
+			result.setPolicyPriority(chainedResult.getPolicyPriority());
+		}
+
+		if (!result.getIsAuditedDetermined() && chainedResult.getIsAuditedDetermined()) {
+			result.setIsAudited(chainedResult.getIsAudited());
+			result.setAuditPolicyId(chainedResult.getAuditPolicyId());
+		}
+	}
+
 	private static AuditProviderFactory getAuditProviderFactory(String serviceName) {
 		AuditProviderFactory ret = AuditProviderFactory.getInstance();
 
diff --git a/agents-common/src/main/java/org/apache/ranger/plugin/service/RangerChainedPlugin.java b/agents-common/src/main/java/org/apache/ranger/plugin/service/RangerChainedPlugin.java
new file mode 100644
index 0000000..0fca2d9
--- /dev/null
+++ b/agents-common/src/main/java/org/apache/ranger/plugin/service/RangerChainedPlugin.java
@@ -0,0 +1,62 @@
+/*
+ * 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.plugin.service;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.ranger.plugin.policyengine.RangerAccessRequest;
+import org.apache.ranger.plugin.policyengine.RangerAccessResult;
+
+import java.util.Collection;
+
+public abstract class RangerChainedPlugin {
+    private static final Log LOG = LogFactory.getLog(RangerChainedPlugin.class);
+
+    protected final RangerBasePlugin rootPlugin;
+    protected final String           serviceType;
+    protected final String           serviceName;
+    protected final RangerBasePlugin plugin;
+
+    protected RangerChainedPlugin(RangerBasePlugin rootPlugin, String serviceType, String serviceName) {
+        LOG.info("RangerChainedPlugin(" + serviceType + ", " + serviceName + ")");
+
+        this.rootPlugin  = rootPlugin;
+        this.serviceType = serviceType;
+        this.serviceName = serviceName;
+        this.plugin      = buildChainedPlugin(serviceType, serviceName, rootPlugin.getAppId());
+    }
+
+    public void init() {
+        LOG.info("==> RangerChainedPlugin.init(" + serviceType + ", " + serviceName + ")");
+
+        this.plugin.init();
+
+        LOG.info("<== RangerChainedPlugin.init(" + serviceType + ", " + serviceName + ")");
+    }
+
+    protected RangerBasePlugin buildChainedPlugin(String serviceType, String serviceName, String appId) {
+        return new RangerBasePlugin(serviceType, serviceName, appId);
+    }
+
+    public abstract RangerAccessResult isAccessAllowed(RangerAccessRequest request);
+
+    public abstract Collection<RangerAccessResult> isAccessAllowed(Collection<RangerAccessRequest> requests);
+
+}
diff --git a/agents-common/src/test/java/org/apache/ranger/plugin/conditionevaluator/RangerCustomConditionMatcherTest.java b/agents-common/src/test/java/org/apache/ranger/plugin/conditionevaluator/RangerCustomConditionMatcherTest.java
index 2c708d7..0a4dd4a 100644
--- a/agents-common/src/test/java/org/apache/ranger/plugin/conditionevaluator/RangerCustomConditionMatcherTest.java
+++ b/agents-common/src/test/java/org/apache/ranger/plugin/conditionevaluator/RangerCustomConditionMatcherTest.java
@@ -169,6 +169,8 @@ public class RangerCustomConditionMatcherTest {
 
 	RangerAccessRequest createRequest(List<String> resourceTags) {
 		RangerAccessResource                  resource          = mock(RangerAccessResource.class);
+		when(resource.getAsString()).thenReturn("Dummy resource");
+
 		RangerAccessRequest                   request           = new RangerAccessRequestImpl(resource,"dummy","test", null, null);
 		Set<RangerTagForEval>                 rangerTagForEvals = new HashSet<>();
 		RangerPolicyResourceMatcher.MatchType matchType         = RangerPolicyResourceMatcher.MatchType.NONE;
diff --git a/agents-common/src/test/resources/policyengine/test_policyengine_tag_hive_filebased.json b/agents-common/src/test/resources/policyengine/test_policyengine_tag_hive_filebased.json
index 21d936a..7beb5ab 100644
--- a/agents-common/src/test/resources/policyengine/test_policyengine_tag_hive_filebased.json
+++ b/agents-common/src/test/resources/policyengine/test_policyengine_tag_hive_filebased.json
@@ -225,6 +225,14 @@
   },
 
   "tests":[
+    {"name":"DENY 'select from employee.personal;' for user1 using EXPIRES_ON tag",
+      "request":{
+        "resource":{"elements":{"database":"employee", "table":"personal"}}, "resourceMatchingScope": "SELF_OR_DESCENDANTS",
+        "accessType":"select","user":"user1","userGroups":[],"requestData":"select from employee.personal;' for user1"
+
+      },
+      "result":{"isAudited":true,"isAllowed":false,"policyId":5}
+    },
     {"name":"ALLOW 'select ssn from employee.personal;' for user1 using EXPIRES_ON tag",
       "request":{
         "resource":{"elements":{"database":"employee", "table":"personal", "column":"ssn"}},