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 2022/02/11 19:50:24 UTC

[ranger] branch ranger-2.3 updated: RANGER-3609: plugins to automatically add userStoreEnricher when any policy has references to user/group attributes

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

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


The following commit(s) were added to refs/heads/ranger-2.3 by this push:
     new 6cca6db  RANGER-3609: plugins to automatically add userStoreEnricher when any policy has references to user/group attributes
6cca6db is described below

commit 6cca6db3d11601e04faaeee95d796e2ca2d59570
Author: Madhan Neethiraj <ma...@apache.org>
AuthorDate: Sun Feb 6 12:05:19 2022 -0800

    RANGER-3609: plugins to automatically add userStoreEnricher when any policy has references to user/group attributes
    
    (cherry picked from commit 07c6875a859a4ac4a5b1feb66b6f7bdc83d99878)
---
 .../ranger/admin/client/RangerAdminRESTClient.java |   2 +-
 .../contextenricher/RangerUserStoreEnricher.java   |  27 +-
 .../policyengine/RangerRequestScriptEvaluator.java |  76 ++++-
 .../ranger/plugin/service/RangerBasePlugin.java    |  43 +++
 .../plugin/util/RangerAccessRequestUtil.java       |   1 +
 .../ranger/plugin/util/RangerPluginCapability.java |   3 +-
 .../apache/ranger/plugin/util/RangerRESTUtils.java |   3 +-
 .../plugin/util/RangerRequestExprResolver.java     | 100 ++++++
 .../apache/ranger/plugin/util/ServiceDefUtil.java  | 249 ++++++++++++++-
 .../ranger/plugin/util/ServiceDefUtilTest.java     | 355 +++++++++++++++++++++
 .../admin/client/RangerAdminJersey2RESTClient.java |  99 ++++++
 .../java/org/apache/ranger/rest/XUserREST.java     |  83 ++++-
 .../conf.dist/security-applicationContext.xml      |   1 +
 13 files changed, 1015 insertions(+), 27 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 a787c78..8edb3cb 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
@@ -786,7 +786,7 @@ public class RangerAdminRESTClient extends AbstractRangerAdminClient {
 			if (LOG.isDebugEnabled()) {
 				LOG.debug("Checking UserStore updated as user : " + user);
 			}
-			String relativeURL = RangerRESTUtils.REST_URL_SERVICE_SERCURE_GET_USERSTORE + serviceNameUrlParam;
+			String relativeURL = RangerRESTUtils.REST_URL_SERVICE_GET_USERSTORE + serviceNameUrlParam;
 			response = restClient.get(relativeURL, queryParams);
 		}
 
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 9a39b2f..e7a3174 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
@@ -35,21 +35,18 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 public class RangerUserStoreEnricher extends RangerAbstractContextEnricher {
-    private static final Logger LOG = LoggerFactory.getLogger(RangerUserStoreEnricher.class);
-
-    private static final Logger PERF_SET_USERSTORE_LOG      = RangerPerfTracer.getPerfLogger("userstoreenricher.setuserstore");
-
-
-    private static final String USERSTORE_REFRESHER_POLLINGINTERVAL_OPTION = "userStoreRefresherPollingInterval";
-    private static final String USERSTORE_RETRIEVER_CLASSNAME_OPTION       = "userStoreRetrieverClassName";
-
-    private RangerUserStoreRefresher                 userStoreRefresher;
-    private RangerUserStoreRetriever                 userStoreRetriever;
-    private RangerUserStore							 rangerUserStore;
-    private boolean                                  disableCacheIfServiceNotFound = true;
-
-    private final BlockingQueue<DownloadTrigger>     userStoreDownloadQueue = new LinkedBlockingQueue<>();
-    private Timer                                    userStoreDownloadTimer;
+    private static final Logger LOG                    = LoggerFactory.getLogger(RangerUserStoreEnricher.class);
+    private static final Logger PERF_SET_USERSTORE_LOG = RangerPerfTracer.getPerfLogger("userstoreenricher.setuserstore");
+
+    public static final String USERSTORE_REFRESHER_POLLINGINTERVAL_OPTION = "userStoreRefresherPollingInterval";
+    public static final String USERSTORE_RETRIEVER_CLASSNAME_OPTION       = "userStoreRetrieverClassName";
+
+    private       RangerUserStoreRefresher       userStoreRefresher;
+    private       RangerUserStoreRetriever       userStoreRetriever;
+    private       RangerUserStore                rangerUserStore;
+    private       boolean                        disableCacheIfServiceNotFound = true;
+    private final BlockingQueue<DownloadTrigger> userStoreDownloadQueue = new LinkedBlockingQueue<>();
+    private       Timer                          userStoreDownloadTimer;
 
     @Override
     public void init() {
diff --git a/agents-common/src/main/java/org/apache/ranger/plugin/policyengine/RangerRequestScriptEvaluator.java b/agents-common/src/main/java/org/apache/ranger/plugin/policyengine/RangerRequestScriptEvaluator.java
index 46d8739..4e312f8 100644
--- a/agents-common/src/main/java/org/apache/ranger/plugin/policyengine/RangerRequestScriptEvaluator.java
+++ b/agents-common/src/main/java/org/apache/ranger/plugin/policyengine/RangerRequestScriptEvaluator.java
@@ -77,7 +77,9 @@ public final class RangerRequestScriptEvaluator {
                                                                                  SCRIPT_VAR_TAG + "=" + SCRIPT_VAR__CTX + "." + SCRIPT_FIELD_TAG + ";" +
                                                                                  SCRIPT_VAR_TAGS + "=" + SCRIPT_VAR__CTX + "." + SCRIPT_FIELD_TAGS + ";" +
                                                                                  SCRIPT_VAR_TAGNAMES + "=" + SCRIPT_VAR__CTX + "." + SCRIPT_FIELD_TAG_NAMES + ";";
-	private static final Pattern JSON_VAR_NAMES_PATTERN = Pattern.compile(getJsonVarNamesPattern());
+	private static final Pattern JSON_VAR_NAMES_PATTERN   = Pattern.compile(getJsonVarNamesPattern());
+	private static final Pattern USER_ATTRIBUTES_PATTERN  = Pattern.compile(getUserAttributesPattern());
+	private static final Pattern GROUP_ATTRIBUTES_PATTERN = Pattern.compile(getGroupAttributesPattern());
 	private static final Character CHAR_QUOTE = '\'';
 	private static final Character CHAR_COMMA = ',';
 
@@ -128,7 +130,43 @@ public final class RangerRequestScriptEvaluator {
 	public static boolean needsJsonCtxEnabled(String script) {
 		Matcher matcher = JSON_VAR_NAMES_PATTERN.matcher(script);
 
-		boolean ret = matcher.matches();
+		boolean ret = matcher.find();
+
+		return ret;
+	}
+
+	public static boolean hasUserAttributeReference(String script) {
+		Matcher matcher = USER_ATTRIBUTES_PATTERN.matcher(script);
+
+		boolean ret = matcher.find();
+
+		return ret;
+	}
+
+	public static boolean hasGroupAttributeReference(String script) {
+		Matcher matcher = GROUP_ATTRIBUTES_PATTERN.matcher(script);
+
+		boolean ret = matcher.find();
+
+		return ret;
+	}
+
+	public static boolean hasUserGroupAttributeReference(String script) {
+		return hasUserAttributeReference(script) || hasGroupAttributeReference(script);
+	}
+
+	public static boolean hasUserGroupAttributeReference(Collection<String> scripts) {
+		boolean ret = false;
+
+		if (scripts != null) {
+			for (String script : scripts) {
+				if (hasUserGroupAttributeReference(script)) {
+					ret = true;
+
+					break;
+				}
+			}
+		}
 
 		return ret;
 	}
@@ -890,7 +928,39 @@ public final class RangerRequestScriptEvaluator {
 		varNames.add(SCRIPT_VAR_URNAMES);
 		varNames.add(SCRIPT_VAR_USER);
 
-		return ".*(" + StringUtils.join(varNames, '|') + ").*";
+		return "\\b(" + StringUtils.join(varNames, '|') + ")\\b";
+	}
+
+	private static String getUserAttributesPattern() {
+		List<String> varNames = new ArrayList<>();
+
+		varNames.add(SCRIPT_VAR_USER);
+		varNames.add(SCRIPT_MACRO_USER_ATTR_NAMES_CSV);
+		varNames.add(SCRIPT_MACRO_USER_ATTR_NAMES_Q_CSV);
+
+		varNames.add("userAttrNamesCsv");
+		varNames.add("userAttrNamesCsvQ");
+
+		return "\\b(" + StringUtils.join(varNames, '|') + ")\\b";
+	}
+
+	private static String getGroupAttributesPattern() {
+		List<String> varNames = new ArrayList<>();
+
+		varNames.add(SCRIPT_VAR_UG);
+		varNames.add(SCRIPT_VAR_UGA);
+
+		varNames.add(SCRIPT_MACRO_GET_UG_ATTR_CSV);
+		varNames.add(SCRIPT_MACRO_GET_UG_ATTR_Q_CSV);
+		varNames.add(SCRIPT_MACRO_UG_ATTR_NAMES_CSV);
+		varNames.add(SCRIPT_MACRO_UG_ATTR_NAMES_Q_CSV);
+
+		varNames.add("ugAttrCsv");
+		varNames.add("ugAttrCsvQ");
+		varNames.add("ugAttrNamesCsv");
+		varNames.add("ugAttrNamesCsvQ");
+
+		return "\\b(" + StringUtils.join(varNames, '|') + ")\\b";
 	}
 
 	private static Map<String, String> getMacrosMap() {
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 827fb1f..12fecd0 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
@@ -31,6 +31,8 @@ import org.apache.ranger.audit.provider.StandAloneAuditProviderFactory;
 import org.apache.ranger.authorization.hadoop.config.RangerAuditConfig;
 import org.apache.ranger.authorization.hadoop.config.RangerPluginConfig;
 import org.apache.ranger.authorization.utils.StringUtil;
+import org.apache.ranger.plugin.contextenricher.RangerAdminUserStoreRetriever;
+import org.apache.ranger.plugin.contextenricher.RangerUserStoreEnricher;
 import org.apache.ranger.plugin.policyengine.RangerRequestScriptEvaluator;
 import org.apache.ranger.plugin.contextenricher.RangerContextEnricher;
 import org.apache.ranger.plugin.contextenricher.RangerTagEnricher;
@@ -68,6 +70,8 @@ public class RangerBasePlugin {
 	private       RangerAccessResultProcessor resultProcessor;
 	private       RangerRoles                 roles;
 	private final List<RangerChainedPlugin>   chainedPlugins;
+	private final boolean                     enableImplicitUserStoreEnricher;
+	private       boolean                     isUserStoreEnricherAddedImplcitly = false;
 
 
 	public RangerBasePlugin(String serviceType, String appId) {
@@ -96,6 +100,8 @@ public class RangerBasePlugin {
 
 		RangerRequestScriptEvaluator.init(pluginConfig);
 
+		this.enableImplicitUserStoreEnricher = pluginConfig.getBoolean(pluginConfig.getPropertyPrefix() + ".enable.implicit.userstore.enricher", false);
+
 		this.chainedPlugins = initChainedPlugins();
 	}
 
@@ -254,6 +260,18 @@ public class RangerBasePlugin {
 			LOG.debug("==> setPolicies(" + policies + ")");
 		}
 
+		if (enableImplicitUserStoreEnricher && policies != null && !ServiceDefUtil.isUserStoreEnricherPresent(policies)) {
+			String retrieverClassName = getConfig().get(RangerUserStoreEnricher.USERSTORE_RETRIEVER_CLASSNAME_OPTION, RangerAdminUserStoreRetriever.class.getCanonicalName());
+			String retrieverPollIntMs = getConfig().get(RangerUserStoreEnricher.USERSTORE_REFRESHER_POLLINGINTERVAL_OPTION, Integer.toString(60 * 1000));
+
+			// in case of delta, policies will only have changes; hence add userStoreEnricher if it was implicitly added previous calls to setPolicies()
+			if (RangerPolicyDeltaUtil.hasPolicyDeltas(policies) == Boolean.TRUE && isUserStoreEnricherAddedImplcitly) {
+				ServiceDefUtil.addUserStoreEnricher(policies, retrieverClassName, retrieverPollIntMs);
+			} else {
+				isUserStoreEnricherAddedImplcitly = ServiceDefUtil.addUserStoreEnricherIfNeeded(policies, retrieverClassName, retrieverPollIntMs);
+			}
+		}
+
 		// guard against catastrophic failure during policy engine Initialization or
 		try {
 			RangerPolicyEngine oldPolicyEngine = this.policyEngine;
@@ -994,6 +1012,31 @@ public class RangerBasePlugin {
 		return ret;
 	}
 
+	public RangerUserStoreEnricher getUserStoreEnricher() {
+		RangerUserStoreEnricher ret         = null;
+		RangerAuthContext       authContext = getCurrentRangerAuthContext();
+
+		if (authContext != null) {
+			Map<RangerContextEnricher, Object> contextEnricherMap = authContext.getRequestContextEnrichers();
+
+			if (MapUtils.isNotEmpty(contextEnricherMap)) {
+				Set<RangerContextEnricher> contextEnrichers = contextEnricherMap.keySet();
+
+				for (RangerContextEnricher enricher : contextEnrichers) {
+					if (enricher instanceof RangerUserStoreEnricher) {
+						ret = (RangerUserStoreEnricher) enricher;
+
+						ret.getRangerUserStore();
+
+						break;
+					}
+				}
+			}
+		}
+
+		return ret;
+	}
+
 	public static RangerResourceACLs getMergedResourceACLs(RangerResourceACLs baseACLs, RangerResourceACLs chainedACLs) {
 		if (LOG.isDebugEnabled()) {
 			LOG.debug("==> RangerBasePlugin.getMergedResourceACLs()");
diff --git a/agents-common/src/main/java/org/apache/ranger/plugin/util/RangerAccessRequestUtil.java b/agents-common/src/main/java/org/apache/ranger/plugin/util/RangerAccessRequestUtil.java
index 4415b6c..c4beabf 100644
--- a/agents-common/src/main/java/org/apache/ranger/plugin/util/RangerAccessRequestUtil.java
+++ b/agents-common/src/main/java/org/apache/ranger/plugin/util/RangerAccessRequestUtil.java
@@ -130,6 +130,7 @@ public class RangerAccessRequestUtil {
 			ret.remove(KEY_CONTEXT_TAGS);
 			ret.remove(KEY_CONTEXT_TAG_OBJECT);
 			ret.remove(KEY_CONTEXT_RESOURCE);
+			ret.remove(KEY_CONTEXT_REQUEST);
 			// don't remove REQUESTED_RESOURCES
 		}
 
diff --git a/agents-common/src/main/java/org/apache/ranger/plugin/util/RangerPluginCapability.java b/agents-common/src/main/java/org/apache/ranger/plugin/util/RangerPluginCapability.java
index b2cecc1..bac6e26 100644
--- a/agents-common/src/main/java/org/apache/ranger/plugin/util/RangerPluginCapability.java
+++ b/agents-common/src/main/java/org/apache/ranger/plugin/util/RangerPluginCapability.java
@@ -69,7 +69,8 @@ public class RangerPluginCapability {
         RANGER_PLUGIN_CAPABILITY_CHAINED_PLUGINS("Chained Plugins"),
         RANGER_PLUGIN_CAPABILITY_SUPERUSER_PERMISSIONS("Super-user Permissions"),
         RANGER_PLUGIN_CAPABILITY_USERSTORE_DOWNLOAD("UserStore Download"),
-        RANGER_PLUGIN_CAPABILITY_AUDIT_POLICY("Audit Policy");
+        RANGER_PLUGIN_CAPABILITY_AUDIT_POLICY("Audit Policy"),
+        RANGER_PLUGIN_CAPABILITY_UGT_ATTRIBUTES_IN_POLICY("User/group/tag attributes in policy");
 
         private final String name;
         RangerPluginFeature(String name) {
diff --git a/agents-common/src/main/java/org/apache/ranger/plugin/util/RangerRESTUtils.java b/agents-common/src/main/java/org/apache/ranger/plugin/util/RangerRESTUtils.java
index ba57f70..207ef6f 100644
--- a/agents-common/src/main/java/org/apache/ranger/plugin/util/RangerRESTUtils.java
+++ b/agents-common/src/main/java/org/apache/ranger/plugin/util/RangerRESTUtils.java
@@ -73,7 +73,8 @@ public class RangerRESTUtils {
 	public static final String REST_PARAM_LAST_KNOWN_ROLE_VERSION = "lastKnownRoleVersion";
 
 	public static final String REST_PARAM_LAST_KNOWN_USERSTORE_VERSION = "lastKnownUserStoreVersion";
-	public static final String REST_URL_SERVICE_SERCURE_GET_USERSTORE = "/service/xusers/secure/download/";
+	public static final String REST_URL_SERVICE_GET_USERSTORE          = "/service/xusers/download/";
+	public static final String REST_URL_SERVICE_SERCURE_GET_USERSTORE  = "/service/xusers/secure/download/";
 
 	private static final int MAX_PLUGIN_ID_LEN = 255;
 	
diff --git a/agents-common/src/main/java/org/apache/ranger/plugin/util/RangerRequestExprResolver.java b/agents-common/src/main/java/org/apache/ranger/plugin/util/RangerRequestExprResolver.java
index 2e486ed..4e5d949 100644
--- a/agents-common/src/main/java/org/apache/ranger/plugin/util/RangerRequestExprResolver.java
+++ b/agents-common/src/main/java/org/apache/ranger/plugin/util/RangerRequestExprResolver.java
@@ -25,6 +25,7 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import javax.script.ScriptEngine;
+import java.util.Collection;
 import java.util.Objects;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -86,4 +87,103 @@ public class RangerRequestExprResolver {
 
         return matcher.find();
     }
+
+    public static boolean hasUserAttributeInExpression(String str) {
+        boolean ret = false;
+        Matcher matcher = PATTERN.matcher(str);
+
+        while (matcher.find()) {
+            String expr = matcher.group(REGEX_GROUP_EXPR);
+
+            if (RangerRequestScriptEvaluator.hasUserAttributeReference(expr)) {
+                ret = true;
+
+                break;
+            }
+        }
+
+        return ret;
+    }
+
+    public static boolean hasGroupAttributeInExpression(String str) {
+        boolean ret     = false;
+        Matcher matcher = PATTERN.matcher(str);
+
+        while (matcher.find()) {
+            String expr = matcher.group(REGEX_GROUP_EXPR);
+
+            if (RangerRequestScriptEvaluator.hasGroupAttributeReference(expr)) {
+                ret = true;
+
+                break;
+            }
+        }
+
+        return ret;
+    }
+
+    public static boolean hasUserGroupAttributeInExpression(String str) {
+        boolean ret     = false;
+        Matcher matcher = PATTERN.matcher(str);
+
+        while (matcher.find()) {
+            String expr = matcher.group(REGEX_GROUP_EXPR);
+
+            if (RangerRequestScriptEvaluator.hasUserGroupAttributeReference(expr)) {
+                ret = true;
+
+                break;
+            }
+        }
+
+        return ret;
+    }
+
+    public static boolean hasUserAttributeInExpression(Collection<String> values) {
+        boolean ret = false;
+
+        if (values != null) {
+            for (String value : values) {
+                if (hasUserAttributeInExpression(value)) {
+                    ret = true;
+
+                    break;
+                }
+            }
+        }
+
+        return ret;
+    }
+
+    public static boolean hasGroupAttributeInExpression(Collection<String> values) {
+        boolean ret = false;
+
+        if (values != null) {
+            for (String value : values) {
+                if (hasGroupAttributeInExpression(value)) {
+                    ret = true;
+
+                    break;
+                }
+            }
+        }
+
+        return ret;
+    }
+
+    public static boolean hasUserGroupAttributeInExpression(Collection<String> values) {
+        boolean ret = false;
+
+        if (values != null) {
+            for (String value : values) {
+                if (hasUserGroupAttributeInExpression(value)) {
+                    ret = true;
+
+                    break;
+                }
+            }
+        }
+
+        return ret;
+    }
 }
diff --git a/agents-common/src/main/java/org/apache/ranger/plugin/util/ServiceDefUtil.java b/agents-common/src/main/java/org/apache/ranger/plugin/util/ServiceDefUtil.java
index b76c96e..fe1cf92 100644
--- a/agents-common/src/main/java/org/apache/ranger/plugin/util/ServiceDefUtil.java
+++ b/agents-common/src/main/java/org/apache/ranger/plugin/util/ServiceDefUtil.java
@@ -23,14 +23,26 @@ import org.apache.commons.collections.CollectionUtils;
 import org.apache.commons.collections.MapUtils;
 import org.apache.commons.lang.StringUtils;
 import org.apache.hadoop.conf.Configuration;
+import org.apache.ranger.plugin.contextenricher.RangerUserStoreEnricher;
 import org.apache.ranger.plugin.model.RangerPolicy;
+import org.apache.ranger.plugin.model.RangerPolicy.RangerPolicyItem;
+import org.apache.ranger.plugin.model.RangerPolicy.RangerPolicyItemCondition;
+import org.apache.ranger.plugin.model.RangerPolicy.RangerPolicyItemRowFilterInfo;
+import org.apache.ranger.plugin.model.RangerPolicy.RangerPolicyResource;
+import org.apache.ranger.plugin.model.RangerPolicy.RangerRowFilterPolicyItem;
+import org.apache.ranger.plugin.model.RangerPolicyDelta;
 import org.apache.ranger.plugin.model.RangerServiceDef;
 import org.apache.ranger.plugin.model.RangerServiceDef.RangerAccessTypeDef;
+import org.apache.ranger.plugin.model.RangerServiceDef.RangerContextEnricherDef;
 import org.apache.ranger.plugin.model.RangerServiceDef.RangerDataMaskTypeDef;
 import org.apache.ranger.plugin.model.RangerServiceDef.RangerResourceDef;
 import org.apache.ranger.plugin.policyengine.RangerPluginContext;
+import org.apache.ranger.plugin.policyengine.RangerRequestScriptEvaluator;
 import org.apache.ranger.plugin.store.AbstractServiceStore;
 import org.apache.ranger.plugin.store.EmbeddedServiceDefsUtil;
+import org.apache.ranger.plugin.util.ServicePolicies.SecurityZoneInfo;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import java.util.ArrayList;
 import java.util.Collection;
@@ -41,6 +53,9 @@ import java.util.HashMap;
 import java.util.Map;
 
 public class ServiceDefUtil {
+    private static final Logger LOG = LoggerFactory.getLogger(ServiceDefUtil.class);
+
+    private static final String USER_STORE_ENRICHER = RangerUserStoreEnricher.class.getCanonicalName();
 
     public static boolean getOption_enableDenyAndExceptionsInPolicies(RangerServiceDef serviceDef, RangerPluginContext pluginContext) {
         boolean ret = false;
@@ -97,7 +112,7 @@ public class ServiceDefUtil {
         return ret;
     }
 
-    public static Integer getLeafResourceLevel(RangerServiceDef serviceDef, Map<String, RangerPolicy.RangerPolicyResource> policyResource) {
+    public static Integer getLeafResourceLevel(RangerServiceDef serviceDef, Map<String, RangerPolicyResource> policyResource) {
         Integer ret = null;
 
         RangerResourceDef resourceDef = getLeafResourceDef(serviceDef, policyResource);
@@ -109,11 +124,11 @@ public class ServiceDefUtil {
         return ret;
     }
 
-    public static RangerResourceDef getLeafResourceDef(RangerServiceDef serviceDef, Map<String, RangerPolicy.RangerPolicyResource> policyResource) {
+    public static RangerResourceDef getLeafResourceDef(RangerServiceDef serviceDef, Map<String, RangerPolicyResource> policyResource) {
         RangerResourceDef ret = null;
 
         if(serviceDef != null && policyResource != null) {
-            for(Map.Entry<String, RangerPolicy.RangerPolicyResource> entry : policyResource.entrySet()) {
+            for(Map.Entry<String, RangerPolicyResource> entry : policyResource.entrySet()) {
                 if (!isEmpty(entry.getValue())) {
                     String            resource    = entry.getKey();
                     RangerResourceDef resourceDef = ServiceDefUtil.getResourceDef(serviceDef, resource);
@@ -150,7 +165,7 @@ public class ServiceDefUtil {
         return ret;
     }
 
-    public static boolean isEmpty(RangerPolicy.RangerPolicyResource policyResource) {
+    public static boolean isEmpty(RangerPolicyResource policyResource) {
         boolean ret = true;
         if (policyResource != null) {
             List<String> resourceValues = policyResource.getValues();
@@ -466,4 +481,230 @@ public class ServiceDefUtil {
         return ret;
     }
 
+    public static boolean isUserStoreEnricherPresent(ServicePolicies policies) {
+        boolean                        ret          = false;
+        RangerServiceDef               serviceDef   = policies != null ? policies.getServiceDef() : null;
+        List<RangerContextEnricherDef> enricherDefs = serviceDef != null ? serviceDef.getContextEnrichers() : null;
+
+        if (enricherDefs != null) {
+            for (RangerContextEnricherDef enricherDef : enricherDefs) {
+                if (StringUtils.equals(enricherDef.getEnricher(), USER_STORE_ENRICHER)) {
+                    ret = true;
+
+                    break;
+                }
+            }
+        }
+
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("isUserStoreEnricherPresent(service={}): ret={}", policies.getServiceName(), ret);
+        }
+
+        return ret;
+    }
+
+    public static boolean addUserStoreEnricher(ServicePolicies policies, String retrieverClassName, String retrieverPollIntMs) {
+        boolean          ret         = false;
+        RangerServiceDef serviceDef = policies != null ? policies.getServiceDef() : null;
+
+        if (serviceDef != null && !isUserStoreEnricherPresent(policies)) {
+            List<RangerContextEnricherDef> enricherDefs = serviceDef.getContextEnrichers();
+
+            if (enricherDefs == null) {
+                enricherDefs = new ArrayList<>();
+            }
+
+            long enricherItemId = enricherDefs.size() + 1;
+
+            for (RangerServiceDef.RangerContextEnricherDef enricherDef : enricherDefs) {
+                if (enricherDef.getItemId() >= enricherItemId) {
+                    enricherItemId = enricherDef.getItemId() + 1;
+                }
+            }
+
+            Map<String, String> enricherOptions = new HashMap<>();
+
+            enricherOptions.put(RangerUserStoreEnricher.USERSTORE_RETRIEVER_CLASSNAME_OPTION, retrieverClassName);
+            enricherOptions.put(RangerUserStoreEnricher.USERSTORE_REFRESHER_POLLINGINTERVAL_OPTION, retrieverPollIntMs);
+
+            RangerServiceDef.RangerContextEnricherDef userStoreEnricher = new RangerServiceDef.RangerContextEnricherDef(enricherItemId, "userStoreEnricher", USER_STORE_ENRICHER, enricherOptions);
+
+            enricherDefs.add(userStoreEnricher);
+
+            serviceDef.setContextEnrichers(enricherDefs);
+
+            LOG.info("addUserStoreEnricher(serviceName={}): added userStoreEnricher {}", policies.getServiceName(), userStoreEnricher);
+        }
+
+        return ret;
+    }
+
+
+    public static boolean addUserStoreEnricherIfNeeded(ServicePolicies policies, String retrieverClassName, String retrieverPollIntMs) {
+        boolean          ret        = false;
+        RangerServiceDef serviceDef = policies != null ? policies.getServiceDef() : null;
+
+        if (serviceDef != null && !isUserStoreEnricherPresent(policies)) {
+            boolean addEnricher = anyPolicyHasUserGroupAttributeExpression(policies.getPolicies());
+
+            if (!addEnricher) {
+                List<RangerPolicy> tagPolicies = policies.getTagPolicies() != null ? policies.getTagPolicies().getPolicies() : null;
+
+                addEnricher = anyPolicyHasUserGroupAttributeExpression(tagPolicies);
+            }
+
+            if (!addEnricher) {
+                addEnricher = anyPolicyDeltaHasUserGroupAttributeExpression(policies.getPolicyDeltas());
+            }
+
+            if (!addEnricher) {
+                Map<String, SecurityZoneInfo> zoneInfos = policies.getSecurityZones();
+
+                if (zoneInfos != null) {
+                    for (SecurityZoneInfo zoneInfo : zoneInfos.values()) {
+                        addEnricher = anyPolicyHasUserGroupAttributeExpression(zoneInfo.getPolicies());
+
+                        if (!addEnricher) {
+                            addEnricher = anyPolicyDeltaHasUserGroupAttributeExpression(zoneInfo.getPolicyDeltas());
+                        }
+
+                        if (addEnricher) {
+                            break;
+                        }
+                    }
+                }
+            }
+
+            if (addEnricher) {
+                addUserStoreEnricher(policies, retrieverClassName, retrieverPollIntMs);
+
+                ret = true;
+            }
+        }
+
+        return ret;
+    }
+
+    private static boolean anyPolicyHasUserGroupAttributeExpression(List<RangerPolicy> policies) {
+        boolean ret = false;
+
+        if (policies != null) {
+            for (RangerPolicy policy : policies) {
+                if (policyHasUserGroupAttributeExpression(policy)) {
+                    if (LOG.isDebugEnabled()) {
+                        LOG.debug("addUserStoreEnricherIfNeeded(service={}): policy(id={}, name={}) has reference to user/group attribute. Adding enricher", policy.getService(), policy.getId(), policy.getName());
+                    }
+
+                    ret = true;
+
+                    break;
+                }
+            }
+        }
+
+        return ret;
+    }
+
+    private static boolean anyPolicyDeltaHasUserGroupAttributeExpression(List<RangerPolicyDelta> policyDeltas) {
+        boolean ret = false;
+
+        if (policyDeltas != null) {
+            for (RangerPolicyDelta policyDelta : policyDeltas) {
+                RangerPolicy policy = policyDelta.getPolicy();
+
+                if (policyHasUserGroupAttributeExpression(policy)) {
+                    if (LOG.isDebugEnabled()) {
+                        LOG.debug("addUserStoreEnricherIfNeeded(service={}): policy(id={}, name={}) has reference to user/group attribute. Adding enricher", policy.getService(), policy.getId(), policy.getName());
+                    }
+
+                    ret = true;
+
+                    break;
+                }
+            }
+        }
+
+        return ret;
+    }
+
+    private static boolean policyHasUserGroupAttributeExpression(RangerPolicy policy) {
+        boolean ret = false;
+
+        if (policy != null) {
+            if (MapUtils.isNotEmpty(policy.getResources())) {
+                for (RangerPolicyResource resource : policy.getResources().values()) {
+                    ret = RangerRequestExprResolver.hasUserGroupAttributeInExpression(resource.getValues());
+
+                    if (ret) {
+                        break;
+                    }
+                }
+            }
+
+            if (!ret) {
+                ret = anyPolicyConditionHasUserGroupAttributeReference(policy.getConditions());
+            }
+
+            if (!ret) {
+                ret = anyPolicyItemHasUserGroupAttributeExpression(policy.getPolicyItems()) ||
+                      anyPolicyItemHasUserGroupAttributeExpression(policy.getDenyPolicyItems()) ||
+                      anyPolicyItemHasUserGroupAttributeExpression(policy.getAllowExceptions()) ||
+                      anyPolicyItemHasUserGroupAttributeExpression(policy.getDenyExceptions()) ||
+                      anyPolicyItemHasUserGroupAttributeExpression(policy.getDataMaskPolicyItems()) ||
+                      anyPolicyItemHasUserGroupAttributeExpression(policy.getRowFilterPolicyItems());
+            }
+        }
+
+        return ret;
+    }
+
+    private static boolean anyPolicyItemHasUserGroupAttributeExpression(List<? extends RangerPolicyItem> policyItems) {
+        boolean ret = false;
+
+        if (policyItems != null) {
+            for (RangerPolicyItem policyItem : policyItems) {
+                if (policyItemHasUserGroupAttributeExpression(policyItem)) {
+                    ret = true;
+
+                    break;
+                }
+            }
+        }
+
+        return ret;
+    }
+
+    private static boolean policyItemHasUserGroupAttributeExpression(RangerPolicyItem policyItem) {
+        boolean ret = false;
+
+        if (policyItem != null) {
+            ret = anyPolicyConditionHasUserGroupAttributeReference(policyItem.getConditions());
+
+            if (!ret && policyItem instanceof RangerRowFilterPolicyItem) {
+                RangerRowFilterPolicyItem rowFilterPolicyItem = (RangerRowFilterPolicyItem) policyItem;
+                RangerPolicyItemRowFilterInfo rowFilterInfo       = rowFilterPolicyItem.getRowFilterInfo();
+                String                        filterExpr          = rowFilterInfo != null ? rowFilterInfo.getFilterExpr() : "";
+
+                ret = RangerRequestExprResolver.hasUserGroupAttributeInExpression(filterExpr);
+            }
+        }
+
+        return ret;
+    }
+
+    private static boolean anyPolicyConditionHasUserGroupAttributeReference(List<RangerPolicyItemCondition> conditions) {
+        boolean ret = false;
+
+        if (conditions != null) {
+            for (RangerPolicyItemCondition condition : conditions) {
+                if (RangerRequestScriptEvaluator.hasUserGroupAttributeReference(condition.getValues())) {
+                    ret = true;
+
+                    break;
+                }
+            }
+        }
+
+        return ret;
+    }
 }
diff --git a/agents-common/src/test/java/org/apache/ranger/plugin/util/ServiceDefUtilTest.java b/agents-common/src/test/java/org/apache/ranger/plugin/util/ServiceDefUtilTest.java
new file mode 100644
index 0000000..3cd42f4
--- /dev/null
+++ b/agents-common/src/test/java/org/apache/ranger/plugin/util/ServiceDefUtilTest.java
@@ -0,0 +1,355 @@
+/*
+ * 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.util;
+
+import org.apache.ranger.plugin.contextenricher.RangerAdminUserStoreRetriever;
+import org.apache.ranger.plugin.model.RangerPolicy;
+import org.apache.ranger.plugin.model.RangerPolicy.RangerDataMaskPolicyItem;
+import org.apache.ranger.plugin.model.RangerPolicy.RangerPolicyItem;
+import org.apache.ranger.plugin.model.RangerPolicy.RangerPolicyItemCondition;
+import org.apache.ranger.plugin.model.RangerPolicy.RangerPolicyItemDataMaskInfo;
+import org.apache.ranger.plugin.model.RangerPolicy.RangerPolicyItemRowFilterInfo;
+import org.apache.ranger.plugin.model.RangerPolicy.RangerRowFilterPolicyItem;
+import org.apache.ranger.plugin.model.RangerPolicy.RangerPolicyResource;
+import org.apache.ranger.plugin.model.RangerPolicyDelta;
+import org.apache.ranger.plugin.model.RangerServiceDef;
+import org.apache.ranger.plugin.store.EmbeddedServiceDefsUtil;
+import org.apache.ranger.plugin.util.ServicePolicies.SecurityZoneInfo;
+import org.junit.Test;
+
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+public class ServiceDefUtilTest {
+	private static final String REF_USER                    = "USER.dept";
+	private static final String REF_UG                      = "UG['test-group1'].dept";
+	private static final String REF_UGA                     = "UGA.sVal['dept']";
+	private static final String REF_GET_UG_ATTR_CSV         = "GET_UG_ATTR_CSV('dept')";
+	private static final String REF_GET_UG_ATTR_Q_CSV       = "GET_UG_ATTR_Q_CSV('dept')";
+	private static final String REF_UG_ATTR_NAMES_CSV       = "UG_ATTR_NAMES_CSV";
+	private static final String REF_UG_ATTR_NAMES_Q_CSV     = "UG_ATTR_NAMES_Q_CSV";
+	private static final String REF_USER_ATTR_NAMES_CSV     = "USER_ATTR_NAMES_CSV";
+	private static final String REF_USER_ATTR_NAMES_Q_CSV   = "USER_ATTR_NAMES_Q_CSV";
+	private static final String REF_GET_UG_ATTR_CSV_F       = "ctx.ugAttrCsv('dept')";
+	private static final String REF_GET_UG_ATTR_Q_CSV_F     = "ctx.ugAttrCsvQ('dept')";
+	private static final String REF_UG_ATTR_NAMES_CSV_F     = "ctx.ugAttrNamesCsv()";
+	private static final String REF_UG_ATTR_NAMES_Q_CSV_F   = "ctx.ugAttrNamesCsvQ()";
+	private static final String REF_USER_ATTR_NAMES_CSV_F   = "ctx.userAttrNamesCsv()";
+	private static final String REF_USER_ATTR_NAMES_Q_CSV_F = "ctx.userAttrNamesCsvQ()";
+
+	private static final String[] UGA_ATTR_EXPRESSIONS = new String[] {
+			REF_USER, REF_UG, REF_UGA,
+			REF_GET_UG_ATTR_CSV, REF_GET_UG_ATTR_Q_CSV,
+			REF_UG_ATTR_NAMES_CSV, REF_UG_ATTR_NAMES_Q_CSV,
+			REF_USER_ATTR_NAMES_CSV, REF_USER_ATTR_NAMES_Q_CSV,
+			REF_GET_UG_ATTR_CSV_F, REF_GET_UG_ATTR_Q_CSV_F,
+			REF_UG_ATTR_NAMES_CSV_F, REF_UG_ATTR_NAMES_Q_CSV_F,
+			REF_USER_ATTR_NAMES_CSV_F, REF_USER_ATTR_NAMES_Q_CSV_F
+	};
+
+	@Test
+	public void testNoUserGroupAttrRef() {
+		ServicePolicies svcPolicies = getServicePolicies();
+		RangerPolicy    policy      = getPolicy(svcPolicies);
+
+		svcPolicies.getPolicies().add(policy);
+		assertFalse("policy doesn't have any reference to user/group attribute", ServiceDefUtil.addUserStoreEnricherIfNeeded(svcPolicies, RangerAdminUserStoreRetriever.class.getCanonicalName(), "60000"));
+
+		policy.getResources().put("database", new RangerPolicyResource("/departments/USER.dept/")); // expressions must be within ${{}}
+		assertFalse("policy doesn't have any reference to user/group attribute", ServiceDefUtil.addUserStoreEnricherIfNeeded(svcPolicies, RangerAdminUserStoreRetriever.class.getCanonicalName(), "60000"));
+
+		policy.getRowFilterPolicyItems().get(0).getRowFilterInfo().setFilterExpr("dept in USER.dept"); // expressions must be within ${{}}
+		assertFalse("policy doesn't have any reference to user/group attribute", ServiceDefUtil.addUserStoreEnricherIfNeeded(svcPolicies, RangerAdminUserStoreRetriever.class.getCanonicalName(), "60000"));
+	}
+
+	@Test
+	public void testResourceUserGroupAttrRef() {
+		for (String attrExpr : UGA_ATTR_EXPRESSIONS) {
+			String          resource    = "test_" + "${{" + attrExpr + "}}";
+			ServicePolicies svcPolicies = getServicePolicies();
+			RangerPolicy    policy      = getPolicy(svcPolicies);
+
+			policy.getResources().put("database", new RangerPolicyResource(resource));
+
+			svcPolicies.getPolicies().add(policy);
+			assertTrue("policy resource refers to user/group attribute: " + resource, ServiceDefUtil.addUserStoreEnricherIfNeeded(svcPolicies, RangerAdminUserStoreRetriever.class.getCanonicalName(), "60000"));
+
+			svcPolicies.getServiceDef().getContextEnrichers().clear();
+			svcPolicies.getPolicies().clear();
+			svcPolicies.getPolicyDeltas().add(new RangerPolicyDelta(1L, RangerPolicyDelta.CHANGE_TYPE_POLICY_CREATE, 1L, policy));
+			assertTrue("policy-delta resource refers to user/group attribute: " + resource, ServiceDefUtil.addUserStoreEnricherIfNeeded(svcPolicies, RangerAdminUserStoreRetriever.class.getCanonicalName(), "60000"));
+
+			svcPolicies.getServiceDef().getContextEnrichers().clear();
+			svcPolicies.getPolicyDeltas().clear();
+			svcPolicies.getSecurityZones().put("zone1", getSecurityZoneInfo("zone1"));
+			svcPolicies.getSecurityZones().get("zone1").getPolicies().add(policy);
+			assertTrue("zone-policy resource refers to user/group attribute: " + resource, ServiceDefUtil.addUserStoreEnricherIfNeeded(svcPolicies, RangerAdminUserStoreRetriever.class.getCanonicalName(), "60000"));
+
+			svcPolicies.getServiceDef().getContextEnrichers().clear();
+			svcPolicies.getSecurityZones().get("zone1").getPolicies().clear();
+			svcPolicies.getSecurityZones().get("zone1").getPolicyDeltas().add(new RangerPolicyDelta(1L, RangerPolicyDelta.CHANGE_TYPE_POLICY_CREATE, 1L, policy));
+			assertTrue("zone-policy-delta resource refers to user/group attribute: " + resource, ServiceDefUtil.addUserStoreEnricherIfNeeded(svcPolicies, RangerAdminUserStoreRetriever.class.getCanonicalName(), "60000"));
+		}
+	}
+
+	@Test
+	public void testPolicyConditionUserGroupAttrRef() {
+		for (String attrExpr : UGA_ATTR_EXPRESSIONS) {
+			String          condExpr    = attrExpr + " != null";
+			ServicePolicies svcPolicies = getServicePolicies();
+			RangerPolicy    policy      = getPolicy(svcPolicies);
+
+			policy.getConditions().add(new RangerPolicyItemCondition("expr", Collections.singletonList(condExpr)));
+
+			svcPolicies.getPolicies().add(policy);
+			assertTrue("policy condition refers to user/group attribute: " + condExpr, ServiceDefUtil.addUserStoreEnricherIfNeeded(svcPolicies, RangerAdminUserStoreRetriever.class.getCanonicalName(), "60000"));
+
+			svcPolicies.getServiceDef().getContextEnrichers().clear();
+			svcPolicies.getPolicies().clear();
+			svcPolicies.getPolicyDeltas().add(new RangerPolicyDelta(1L, RangerPolicyDelta.CHANGE_TYPE_POLICY_CREATE, 1L, policy));
+			assertTrue("policy-delta condition refers to user/group attribute: " + condExpr, ServiceDefUtil.addUserStoreEnricherIfNeeded(svcPolicies, RangerAdminUserStoreRetriever.class.getCanonicalName(), "60000"));
+
+			svcPolicies.getServiceDef().getContextEnrichers().clear();
+			svcPolicies.getPolicyDeltas().clear();
+			svcPolicies.getSecurityZones().put("zone1", getSecurityZoneInfo("zone1"));
+			svcPolicies.getSecurityZones().get("zone1").getPolicies().add(policy);
+			assertTrue("zone-policy condition refers to user/group attribute: " + condExpr, ServiceDefUtil.addUserStoreEnricherIfNeeded(svcPolicies, RangerAdminUserStoreRetriever.class.getCanonicalName(), "60000"));
+
+			svcPolicies.getServiceDef().getContextEnrichers().clear();
+			svcPolicies.getSecurityZones().get("zone1").getPolicies().clear();
+			svcPolicies.getSecurityZones().get("zone1").getPolicyDeltas().add(new RangerPolicyDelta(1L, RangerPolicyDelta.CHANGE_TYPE_POLICY_CREATE, 1L, policy));
+			assertTrue("zone-policy-delta condition refers to user/group attribute: " + condExpr, ServiceDefUtil.addUserStoreEnricherIfNeeded(svcPolicies, RangerAdminUserStoreRetriever.class.getCanonicalName(), "60000"));
+
+			svcPolicies.getServiceDef().getContextEnrichers().clear();
+			svcPolicies.getPolicies().clear();
+			svcPolicies.getPolicyDeltas().clear();
+			svcPolicies.getSecurityZones().clear();
+			svcPolicies.getTagPolicies().getPolicies().add(policy);
+			assertTrue("tag-policy condition refers to user/group attribute: " + condExpr, ServiceDefUtil.addUserStoreEnricherIfNeeded(svcPolicies, RangerAdminUserStoreRetriever.class.getCanonicalName(), "60000"));
+		}
+	}
+
+	@Test
+	public void testPolicyItemConditionUserGroupRef() {
+		int i = 0;
+
+		for (String attrExpr : UGA_ATTR_EXPRESSIONS) {
+			String          condExpr    = attrExpr + " != null";
+			ServicePolicies svcPolicies = getServicePolicies();
+			RangerPolicy    policy      = getPolicy(svcPolicies);
+
+			final List<? extends RangerPolicyItem> policyItems;
+
+			switch (i % 6) {
+				case 0:
+					policyItems = policy.getPolicyItems();
+				break;
+
+				case 1:
+					policyItems = policy.getDenyPolicyItems();
+				break;
+
+				case 2:
+					policyItems = policy.getAllowExceptions();
+				break;
+
+				case 3:
+					policyItems = policy.getDenyExceptions();
+				break;
+
+				case 4:
+					policyItems = policy.getRowFilterPolicyItems();
+				break;
+
+				case 5:
+					policyItems = policy.getDataMaskPolicyItems();
+				break;
+
+				default:
+					policyItems = policy.getPolicyItems();
+				break;
+			}
+
+			policyItems.get(0).getConditions().add(new RangerPolicyItemCondition("expr", Collections.singletonList(condExpr)));
+
+			svcPolicies.getPolicies().add(policy);
+			assertTrue("policyItem condition refers to user/group attribute: " + condExpr, ServiceDefUtil.addUserStoreEnricherIfNeeded(svcPolicies, RangerAdminUserStoreRetriever.class.getCanonicalName(), "60000"));
+
+			svcPolicies.getServiceDef().getContextEnrichers().clear();
+			svcPolicies.getPolicies().clear();
+			svcPolicies.getPolicyDeltas().add(new RangerPolicyDelta(1L, RangerPolicyDelta.CHANGE_TYPE_POLICY_CREATE, 1L, policy));
+			assertTrue("policy-delta-item condition refers to user/group attribute: " + condExpr, ServiceDefUtil.addUserStoreEnricherIfNeeded(svcPolicies, RangerAdminUserStoreRetriever.class.getCanonicalName(), "60000"));
+
+			svcPolicies.getServiceDef().getContextEnrichers().clear();
+			svcPolicies.getPolicyDeltas().clear();
+			svcPolicies.getSecurityZones().put("zone1", getSecurityZoneInfo("zone1"));
+			svcPolicies.getSecurityZones().get("zone1").getPolicies().add(policy);
+			assertTrue("zone-policy-item condition refers to user/group attribute: " + condExpr, ServiceDefUtil.addUserStoreEnricherIfNeeded(svcPolicies, RangerAdminUserStoreRetriever.class.getCanonicalName(), "60000"));
+
+			svcPolicies.getServiceDef().getContextEnrichers().clear();
+			svcPolicies.getSecurityZones().get("zone1").getPolicies().clear();
+			svcPolicies.getSecurityZones().get("zone1").getPolicyDeltas().add(new RangerPolicyDelta(1L, RangerPolicyDelta.CHANGE_TYPE_POLICY_CREATE, 1L, policy));
+			assertTrue("zone-policy-delta-item condition refers to user/group attribute: " + condExpr, ServiceDefUtil.addUserStoreEnricherIfNeeded(svcPolicies, RangerAdminUserStoreRetriever.class.getCanonicalName(), "60000"));
+
+			svcPolicies.getServiceDef().getContextEnrichers().clear();
+			svcPolicies.getPolicies().clear();
+			svcPolicies.getTagPolicies().getPolicies().add(policy);
+
+			assertTrue("tag-policyItem condition refers to user/group attribute: " + condExpr, ServiceDefUtil.addUserStoreEnricherIfNeeded(svcPolicies, RangerAdminUserStoreRetriever.class.getCanonicalName(), "60000"));
+
+			i++;
+		}
+	}
+
+	@Test
+	public void testPolicyItemRowFilterExprUserGroupRef() {
+		for (String attrExpr : UGA_ATTR_EXPRESSIONS) {
+			String          filterExpr  = "${{" + attrExpr + "}}";
+			ServicePolicies svcPolicies = getServicePolicies();
+			RangerPolicy    policy      = getPolicy(svcPolicies);
+
+			policy.getRowFilterPolicyItems().get(0).setRowFilterInfo(new RangerPolicyItemRowFilterInfo("dept in (" + filterExpr + ")"));
+
+			svcPolicies.getPolicies().add(policy);
+			assertTrue("policy row-filter refers to user/group attribute: " + filterExpr, ServiceDefUtil.addUserStoreEnricherIfNeeded(svcPolicies, RangerAdminUserStoreRetriever.class.getCanonicalName(), "60000"));
+
+			svcPolicies.getServiceDef().getContextEnrichers().clear();
+			svcPolicies.getPolicies().clear();
+			svcPolicies.getPolicyDeltas().add(new RangerPolicyDelta(1L, RangerPolicyDelta.CHANGE_TYPE_POLICY_CREATE, 1L, policy));
+			assertTrue("policy-delta row-filter refers to user/group attribute: " + filterExpr, ServiceDefUtil.addUserStoreEnricherIfNeeded(svcPolicies, RangerAdminUserStoreRetriever.class.getCanonicalName(), "60000"));
+
+			svcPolicies.getServiceDef().getContextEnrichers().clear();
+			svcPolicies.getPolicyDeltas().clear();
+			svcPolicies.getSecurityZones().put("zone1", getSecurityZoneInfo("zone1"));
+			svcPolicies.getSecurityZones().get("zone1").getPolicies().add(policy);
+			assertTrue("zone-policy row-filter refers to user/group attribute: " + filterExpr, ServiceDefUtil.addUserStoreEnricherIfNeeded(svcPolicies, RangerAdminUserStoreRetriever.class.getCanonicalName(), "60000"));
+
+			svcPolicies.getServiceDef().getContextEnrichers().clear();
+			svcPolicies.getSecurityZones().get("zone1").getPolicies().clear();
+			svcPolicies.getSecurityZones().get("zone1").getPolicyDeltas().add(new RangerPolicyDelta(1L, RangerPolicyDelta.CHANGE_TYPE_POLICY_CREATE, 1L, policy));
+			assertTrue("zone-policy-delta row-filter refers to user/group attribute: " + filterExpr, ServiceDefUtil.addUserStoreEnricherIfNeeded(svcPolicies, RangerAdminUserStoreRetriever.class.getCanonicalName(), "60000"));
+		}
+	}
+
+
+	private ServicePolicies getServicePolicies() {
+		ServicePolicies ret = new ServicePolicies();
+
+		ret.setServiceName("dev_hive");
+		ret.setServiceId(1L);
+		ret.setPolicyVersion(1L);
+		ret.setServiceDef(getServiceDef("hive"));
+		ret.setPolicies(new ArrayList<>());
+		ret.setPolicyDeltas(new ArrayList<>());
+		ret.setSecurityZones(new HashMap<>());
+
+		ret.setTagPolicies(new ServicePolicies.TagPolicies());
+		ret.getTagPolicies().setServiceDef(getServiceDef("tag"));
+		ret.getTagPolicies().setPolicies(new ArrayList<>());
+
+		return ret;
+	}
+
+	private RangerServiceDef getServiceDef(String serviceType) {
+		RangerServiceDef ret = null;
+
+		try {
+			RangerServiceDef serviceDef = EmbeddedServiceDefsUtil.instance().getEmbeddedServiceDef(serviceType);
+
+			if (serviceDef != null) { // make a copy
+				ret = new RangerServiceDef();
+
+				ret.updateFrom(serviceDef);
+			}
+		} catch (Exception excp) {
+			// ignore
+		}
+
+		return ret;
+	}
+
+	private RangerPolicy getPolicy(ServicePolicies svcPolicies) {
+		RangerPolicy ret = new RangerPolicy();
+
+		ret.setConditions(new ArrayList<>());
+
+		ret.setService(svcPolicies.getServiceName());
+		ret.setServiceType(svcPolicies.getServiceDef().getName());
+		ret.getResources().put("database", new RangerPolicyResource("testdb"));
+		ret.getConditions().add(new RangerPolicyItemCondition("expr", Collections.singletonList("TAG.attr1 == 'value1'")));
+		ret.getPolicyItems().add(getPolicyItem());
+		ret.getAllowExceptions().add(getPolicyItem());
+		ret.getDenyPolicyItems().add(getPolicyItem());
+		ret.getDenyExceptions().add(getPolicyItem());
+		ret.getDataMaskPolicyItems().add(getDataMaskPolicyItem());
+		ret.getRowFilterPolicyItems().add(getRowFilterPolicyItem());
+
+		return ret;
+	}
+
+	private RangerPolicyItem getPolicyItem() {
+		RangerPolicyItem ret = new RangerPolicyItem();
+
+		ret.getUsers().add("testUser");
+		ret.getGroups().add("testGroup");
+		ret.getRoles().add("testRole");
+		ret.getConditions().add(new RangerPolicyItemCondition("expr", Collections.singletonList("TAG.attr1 == 'value1'")));
+
+		return ret;
+	}
+
+	private RangerDataMaskPolicyItem getDataMaskPolicyItem() {
+		RangerDataMaskPolicyItem ret = new RangerDataMaskPolicyItem();
+
+		ret.getUsers().add("testUser");
+		ret.getGroups().add("testGroup");
+		ret.getRoles().add("testRole");
+		ret.getConditions().add(new RangerPolicyItemCondition("expr", Collections.singletonList("TAG.attr1 == 'value1'")));
+		ret.setDataMaskInfo(new RangerPolicyItemDataMaskInfo("MASK_NULL", null, null));
+
+		return ret;
+	}
+
+	private RangerRowFilterPolicyItem getRowFilterPolicyItem() {
+		RangerRowFilterPolicyItem ret = new RangerRowFilterPolicyItem();
+
+		ret.getUsers().add("testUser");
+		ret.getGroups().add("testGroup");
+		ret.getRoles().add("testRole");
+		ret.getConditions().add(new RangerPolicyItemCondition("expr", Collections.singletonList("TAG.attr1 == 'value1'")));
+		ret.setRowFilterInfo(new RangerPolicyItemRowFilterInfo("dept in ('dept1','dept2')"));
+
+		return ret;
+	}
+
+	private SecurityZoneInfo getSecurityZoneInfo(String zoneName) {
+		SecurityZoneInfo ret = new SecurityZoneInfo();
+
+		ret.setZoneName(zoneName);
+		ret.setPolicies(new ArrayList<>());
+		ret.setPolicyDeltas(new ArrayList<>());
+
+		return ret;
+	}
+}
diff --git a/knox-agent/src/main/java/org/apache/ranger/admin/client/RangerAdminJersey2RESTClient.java b/knox-agent/src/main/java/org/apache/ranger/admin/client/RangerAdminJersey2RESTClient.java
index 624228e..c60d647 100644
--- a/knox-agent/src/main/java/org/apache/ranger/admin/client/RangerAdminJersey2RESTClient.java
+++ b/knox-agent/src/main/java/org/apache/ranger/admin/client/RangerAdminJersey2RESTClient.java
@@ -19,6 +19,7 @@
 
 package org.apache.ranger.admin.client;
 
+import java.io.UnsupportedEncodingException;
 import java.lang.reflect.Type;
 import java.net.InetAddress;
 import java.net.UnknownHostException;
@@ -73,6 +74,7 @@ public class RangerAdminJersey2RESTClient extends AbstractRangerAdminClient {
 	HostnameVerifier _hv;
 	String _sslConfigFileName = null;
 	String _serviceName = null;
+	String _serviceNameUrlParam = null;
 	String _clusterName = null;
 	boolean _supportsPolicyDeltas = false;
 	boolean _supportsTagDeltas = false;
@@ -134,6 +136,13 @@ public class RangerAdminJersey2RESTClient extends AbstractRangerAdminClient {
 		if(LOG.isDebugEnabled()) {
 			LOG.debug("<== RangerAdminJersey2RESTClient.init(" + configPropertyPrefix + "): " + _client.toString());
 		}
+
+		try {
+			this._serviceNameUrlParam = URLEncoderUtil.encodeURIParam(serviceName);
+		} catch (UnsupportedEncodingException e) {
+			LOG.warn("Unsupported encoding, serviceName=" + serviceName);
+			this._serviceNameUrlParam = serviceName;
+		}
 	}
 
 	@Override
@@ -273,6 +282,96 @@ public class RangerAdminJersey2RESTClient extends AbstractRangerAdminClient {
 		throw new Exception("RangerAdminjersey2RESTClient.getTagTypes() -- *** NOT IMPLEMENTED *** ");
 	}
 
+	@Override
+	public RangerUserStore getUserStoreIfUpdated(long lastKnownUserStoreVersion, long lastActivationTimeInMillis) throws Exception {
+		if (LOG.isDebugEnabled()) {
+			LOG.debug("==> RangerAdminjersey2RESTClient.getUserStoreIfUpdated(lastKnownUserStoreVersion={}, lastActivationTimeInMillis={})", lastKnownUserStoreVersion, lastActivationTimeInMillis);
+		}
+
+		final RangerUserStore      ret;
+		final UserGroupInformation user         = MiscUtil.getUGILoginUser();
+		final boolean              isSecureMode = user != null && UserGroupInformation.isSecurityEnabled();
+		final Response             response;
+
+		Map<String, String> queryParams = new HashMap<String, String>();
+
+		queryParams.put(RangerRESTUtils.REST_PARAM_LAST_KNOWN_USERSTORE_VERSION, Long.toString(lastKnownUserStoreVersion));
+		queryParams.put(RangerRESTUtils.REST_PARAM_LAST_ACTIVATION_TIME, Long.toString(lastActivationTimeInMillis));
+		queryParams.put(RangerRESTUtils.REST_PARAM_PLUGIN_ID, _pluginId);
+		queryParams.put(RangerRESTUtils.REST_PARAM_CLUSTER_NAME, _clusterName);
+		queryParams.put(RangerRESTUtils.REST_PARAM_CAPABILITIES, pluginCapabilities);
+
+		if (isSecureMode) {
+			if (LOG.isDebugEnabled()) {
+				LOG.debug("Checking UserStore updated as user: {}", user);
+			}
+
+			PrivilegedAction<Response> action = () -> {
+				Response resp        = null;
+				String   relativeURL = RangerRESTUtils.REST_URL_SERVICE_SERCURE_GET_USERSTORE + _serviceNameUrlParam;
+
+				try {
+					resp = get(queryParams, relativeURL);
+				} catch (Exception e) {
+					LOG.error("Failed to get response", e);
+				}
+
+				return resp;
+			};
+
+			response = user.doAs(action);
+		} else {
+			if (LOG.isDebugEnabled()) {
+				LOG.debug("Checking UserStore updated as user: {}", user);
+			}
+
+			String relativeURL = RangerRESTUtils.REST_URL_SERVICE_GET_USERSTORE + _serviceNameUrlParam;
+
+			response = get(queryParams, relativeURL);
+		}
+
+		if (response == null || response.getStatus() == 304) { // NOT_MODIFIED
+			if (response == null) {
+				LOG.error("Error getting UserStore; Received NULL response!!. secureMode={}, user={}, serviceName={}", isSecureMode, user, _serviceName);
+			} else {
+				String resp = response.hasEntity() ? response.readEntity(String.class) : null;
+
+				if (LOG.isDebugEnabled()) {
+					LOG.debug("No change in UserStore. secureMode={}, user={}, response={}, serviceName={}, lastKnownUserStoreVersion={}, lastActivationTimeInMillis={}",
+							  isSecureMode, user, resp, _serviceName, lastKnownUserStoreVersion, lastActivationTimeInMillis);
+				}
+			}
+
+			ret = null;
+		} else if (response.getStatus() == 200) { // OK
+			ret = response.readEntity(RangerUserStore.class);
+		} else if (response.getStatus() == 404) { // NOT_FOUND
+			ret = null;
+
+			LOG.error("Error getting UserStore; service not found. secureMode={}, user={}, response={}, serviceName={}, lastKnownUserStoreVersion={}, lastActivationTimeInMillis={}",
+					  isSecureMode, user, response.getStatus(), _serviceName, lastKnownUserStoreVersion, lastActivationTimeInMillis);
+
+			String exceptionMsg = response.hasEntity() ? response.readEntity(String.class) : null;
+
+			RangerServiceNotFoundException.throwExceptionIfServiceNotFound(_serviceName, exceptionMsg);
+
+			LOG.warn("Received 404 error code with body:[{}], Ignoring", exceptionMsg);
+		} else {
+			String resp = response.hasEntity() ? response.readEntity(String.class) : null;
+
+			LOG.warn("Error getting UserStore. secureMode={}, user={}, response={}, serviceName={}, lastKnownUserStoreVersion={}, lastActivationTimeInMillis={}",
+					 isSecureMode, user, resp, _serviceName, lastKnownUserStoreVersion, lastActivationTimeInMillis);
+
+			ret = null;
+		}
+
+		if (LOG.isDebugEnabled()) {
+			LOG.debug("<== RangerAdminjersey2RESTClient.getUserStoreIfUpdated(lastKnownUserStoreVersion={}, lastActivationTimeInMillis={}): ret={}", lastKnownUserStoreVersion, lastActivationTimeInMillis, ret);
+		}
+
+		return ret;
+	}
+
 	// We get date from the policy manager as unix long!  This deserializer exists to deal with it.  Remove this class once we start send date/time per RFC 3339
 	public static class GsonUnixDateDeserializer implements JsonDeserializer<Date> {
 
diff --git a/security-admin/src/main/java/org/apache/ranger/rest/XUserREST.java b/security-admin/src/main/java/org/apache/ranger/rest/XUserREST.java
index 4b9085d..6fa50ac 100644
--- a/security-admin/src/main/java/org/apache/ranger/rest/XUserREST.java
+++ b/security-admin/src/main/java/org/apache/ranger/rest/XUserREST.java
@@ -27,6 +27,7 @@ import java.util.Map;
 
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
+import javax.ws.rs.DefaultValue;
 import javax.ws.rs.DELETE;
 import javax.ws.rs.GET;
 import javax.ws.rs.POST;
@@ -34,9 +35,9 @@ import javax.ws.rs.PUT;
 import javax.ws.rs.Path;
 import javax.ws.rs.PathParam;
 import javax.ws.rs.Produces;
-import javax.ws.rs.core.Context;
 import javax.ws.rs.QueryParam;
-import javax.ws.rs.DefaultValue;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.Context;
 
 import org.apache.commons.collections.CollectionUtils;
 import org.apache.commons.lang.StringUtils;
@@ -1273,6 +1274,84 @@ public class XUserREST {
                 }
         }
 
+    @GET
+    @Path("/download/{serviceName}")
+    @Produces({ "application/xml", "application/json" })
+    public RangerUserStore getRangerUserStoreIfUpdated(@PathParam("serviceName") String serviceName,
+                                                       @QueryParam("lastKnownUserStoreVersion") Long lastKnownUserStoreVersion,
+                                                       @DefaultValue("0") @QueryParam("lastActivationTime") Long lastActivationTime,
+                                                       @QueryParam("pluginId") String pluginId,
+                                                       @DefaultValue("") @QueryParam("clusterName") String clusterName,
+                                                       @DefaultValue("") @QueryParam(RangerRESTUtils.REST_PARAM_CAPABILITIES) String pluginCapabilities,
+                                                       @Context HttpServletRequest request) throws Exception {
+        if (logger.isDebugEnabled()) {
+            logger.debug("==> XUserREST.getRangerUserStoreIfUpdated(serviceName={}, lastKnownUserStoreVersion={}, lastActivationTime={})", serviceName, lastKnownUserStoreVersion, lastActivationTime);
+        }
+
+        RangerUserStore ret      = null;
+        boolean         isValid  = false;
+        int             httpCode = HttpServletResponse.SC_OK;
+        String          logMsg   = null;
+
+        try {
+            bizUtil.failUnauthenticatedIfNotAllowed();
+
+            isValid = serviceUtil.isValidService(serviceName, request);
+        } catch (WebApplicationException webException) {
+            httpCode = webException.getResponse().getStatus();
+            logMsg   = webException.getResponse().getEntity().toString();
+        } catch (Exception e) {
+            httpCode = HttpServletResponse.SC_BAD_REQUEST;
+            logMsg   = e.getMessage();
+        }
+
+        Long downloadedVersion = null;
+
+        if (isValid) {
+            try {
+                XXService xService = rangerDaoManager.getXXService().findByName(serviceName);
+
+                if (xService != null) {
+                    if (lastKnownUserStoreVersion == null) {
+                        lastKnownUserStoreVersion = Long.valueOf(-1);
+                    }
+
+                    RangerUserStore rangerUserStore = xUserMgr.getRangerUserStore(lastKnownUserStoreVersion);
+
+                    if (rangerUserStore == null) {
+                        downloadedVersion = lastKnownUserStoreVersion;
+                        httpCode          = HttpServletResponse.SC_NOT_MODIFIED;
+                        logMsg            = "No change since last update";
+                    } else {
+                        downloadedVersion = rangerUserStore.getUserStoreVersion();
+                        ret               = rangerUserStore;
+                        httpCode          = HttpServletResponse.SC_OK;
+                        logMsg            = "Returning RangerUserStore version " + downloadedVersion;
+                    }
+                }
+            } catch (Throwable excp) {
+                logger.error("getRangerUserStoreIfUpdated(serviceName={}, lastKnownUserStoreVersion={}, lastActivationTime={}) failed", serviceName, lastKnownUserStoreVersion, lastActivationTime, excp);
+
+                httpCode = HttpServletResponse.SC_BAD_REQUEST;
+                logMsg   = excp.getMessage();
+            }
+        }
+
+        assetMgr.createPluginInfo(serviceName, pluginId, request, RangerPluginInfo.ENTITY_TYPE_USERSTORE, downloadedVersion, lastKnownUserStoreVersion, lastActivationTime, httpCode, clusterName, pluginCapabilities);
+
+        if (httpCode != HttpServletResponse.SC_OK) {
+            boolean logError = httpCode != HttpServletResponse.SC_NOT_MODIFIED;
+
+            throw restErrorUtil.createRESTException(httpCode, logMsg, logError);
+        }
+
+        if (logger.isDebugEnabled()) {
+            logger.debug("<== XUserREST.getRangerUserStoreIfUpdated(serviceName={}, lastKnownUserStoreVersion={}, lastActivationTime={}): {}", serviceName, lastKnownUserStoreVersion, lastActivationTime, ret);
+        }
+
+        return ret;
+    }
+
 	@GET
 	@Path("/secure/download/{serviceName}")
 	@Produces({ "application/xml", "application/json" })
diff --git a/security-admin/src/main/resources/conf.dist/security-applicationContext.xml b/security-admin/src/main/resources/conf.dist/security-applicationContext.xml
index 0e28a97..256c2bf 100644
--- a/security-admin/src/main/resources/conf.dist/security-applicationContext.xml
+++ b/security-admin/src/main/resources/conf.dist/security-applicationContext.xml
@@ -45,6 +45,7 @@ http://www.springframework.org/schema/security/spring-security-oauth2-2.0.xsd">
 	<security:http pattern="/service/plugins/services/revoke/*" security="none"/>
 	<security:http pattern="/service/tags/download/*" security="none"/>
 	<security:http pattern="/service/roles/download/*" security="none"/>
+	<security:http pattern="/service/xusers/download/*" security="none"/>
 	<security:http pattern="/service/metrics/status" security="none" />
 	<security:http disable-url-rewriting="true" use-expressions="true" create-session="always" entry-point-ref="authenticationProcessingFilterEntryPoint">
 		<csrf disabled="true"/>