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 2021/12/21 01:48:28 UTC

[ranger] 02/02: RANGER-3550: enhancement to support use of user/tag attributes in row-filter/condition expressions

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

commit 52a855892fab138652255b9f492c06725c82d7be
Author: Madhan Neethiraj <ma...@apache.org>
AuthorDate: Sun Nov 28 16:59:27 2021 -0800

    RANGER-3550: enhancement to support use of user/tag attributes in row-filter/condition expressions
    
    (cherry picked from commit e198d01e3c23b89c9fdbcb2915931a4714185b4e)
---
 .../RangerScriptConditionEvaluator.java            |  12 +-
 .../policyengine/RangerRequestScriptEvaluator.java |  20 +--
 .../RangerDefaultRowFilterPolicyItemEvaluator.java |  30 +++-
 .../ranger/plugin/util/RangerCommonConstants.java  |   6 +-
 .../plugin/util/RangerRequestExprResolver.java     |  88 +++++++++++
 .../ranger/plugin/util/ScriptEngineUtil.java       |   2 +-
 .../RangerCustomConditionMatcherTest.java          |  35 ++++-
 .../RangerRequestScriptEvaluatorTest.java          |  35 +++--
 .../plugin/util/RangerRequestExprResolverTest.java | 170 +++++++++++++++++++++
 9 files changed, 357 insertions(+), 41 deletions(-)

diff --git a/agents-common/src/main/java/org/apache/ranger/plugin/conditionevaluator/RangerScriptConditionEvaluator.java b/agents-common/src/main/java/org/apache/ranger/plugin/conditionevaluator/RangerScriptConditionEvaluator.java
index b94225b..2b6b75f 100644
--- a/agents-common/src/main/java/org/apache/ranger/plugin/conditionevaluator/RangerScriptConditionEvaluator.java
+++ b/agents-common/src/main/java/org/apache/ranger/plugin/conditionevaluator/RangerScriptConditionEvaluator.java
@@ -38,7 +38,7 @@ public class RangerScriptConditionEvaluator extends RangerAbstractConditionEvalu
 	private static final Log LOG = LogFactory.getLog(RangerScriptConditionEvaluator.class);
 
 	private ScriptEngine scriptEngine;
-	private boolean      enableJsonCtx = false;
+	private Boolean      enableJsonCtx = null;
 
 	@Override
 	public void init() {
@@ -54,7 +54,11 @@ public class RangerScriptConditionEvaluator extends RangerAbstractConditionEvalu
 		if (MapUtils.isNotEmpty(evalOptions)) {
 			engineName = evalOptions.get("engineName");
 
-			enableJsonCtx = Boolean.parseBoolean(evalOptions.getOrDefault(SCRIPT_OPTION_ENABLE_JSON_CTX, Boolean.toString(enableJsonCtx)));
+			String strEnableJsonCtx = evalOptions.get(SCRIPT_OPTION_ENABLE_JSON_CTX);
+
+			if (StringUtils.isNotEmpty(strEnableJsonCtx)) {
+				enableJsonCtx = Boolean.parseBoolean(strEnableJsonCtx);
+			}
 		}
 
 		if (StringUtils.isBlank(engineName)) {
@@ -98,6 +102,10 @@ public class RangerScriptConditionEvaluator extends RangerAbstractConditionEvalu
 
 				RangerRequestScriptEvaluator evaluator = new RangerRequestScriptEvaluator(request);
 
+				if (enableJsonCtx == null) { // if not specified in evaluatorOptions, set it on first call to isMatched()
+					enableJsonCtx = RangerRequestScriptEvaluator.needsJsonCtxEnabled(script);
+				}
+
 				evaluator.evaluateConditionScript(scriptEngine, script, enableJsonCtx);
 
 				result = evaluator.getResult();
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 64f01c9..d99a7d5 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
@@ -69,10 +69,10 @@ public final class RangerRequestScriptEvaluator {
                                                                                  SCRIPT_VAR_REQ + "=" + SCRIPT_VAR__CTX + "." + SCRIPT_FIELD_REQUEST + ";" +
                                                                                  SCRIPT_VAR_RES + "=" + SCRIPT_VAR_REQ + "." + SCRIPT_FIELD_RESOURCE + ";" +
                                                                                  SCRIPT_VAR_USER + "=" + SCRIPT_VAR_REQ + "." + SCRIPT_FIELD_USER_ATTRIBUTES + ";" +
-                                                                                 SCRIPT_VAR_UGROUPS + "=" + SCRIPT_VAR_REQ + "." + SCRIPT_FIELD_USER_GROUPS + ";" +
-                                                                                 SCRIPT_VAR_UGROUP + "=" + SCRIPT_VAR_REQ + "." + SCRIPT_FIELD_USER_GROUP_ATTRIBUTES + ";" +
+                                                                                 SCRIPT_VAR_UGNAMES + "=" + SCRIPT_VAR_REQ + "." + SCRIPT_FIELD_USER_GROUPS + ";" +
+                                                                                 SCRIPT_VAR_UG + "=" + SCRIPT_VAR_REQ + "." + SCRIPT_FIELD_USER_GROUP_ATTRIBUTES + ";" +
                                                                                  SCRIPT_VAR_UGA + "=" + SCRIPT_VAR_REQ + "." + SCRIPT_FIELD_UGA + ";" +
-                                                                                 SCRIPT_VAR_UROLES + "=" + SCRIPT_VAR_REQ + "." + SCRIPT_FIELD_USER_ROLES + ";" +
+                                                                                 SCRIPT_VAR_URNAMES + "=" + SCRIPT_VAR_REQ + "." + SCRIPT_FIELD_USER_ROLES + ";" +
                                                                                  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 + ";";
@@ -262,11 +262,11 @@ public final class RangerRequestScriptEvaluator {
 		Set<RangerTagForEval> requestTags = RangerAccessRequestUtil.getRequestTagsFromContext(getRequestContext());
 
 		if (CollectionUtils.isNotEmpty(requestTags)) {
-			Set<Map<String, Object>> tags     = new HashSet<>();
-			Set<String>              tagNames = new HashSet<>();
+			Map<String, Map<String, Object>> tags     = new HashMap();
+			Set<String>                      tagNames = new HashSet<>();
 
 			for (RangerTagForEval tag : requestTags) {
-				tags.add(toMap(tag));
+				tags.put(tag.getType(), toMap(tag));
 				tagNames.add(tag.getType());
 			}
 
@@ -279,7 +279,7 @@ public final class RangerRequestScriptEvaluator {
 				ret.put(SCRIPT_FIELD_TAG, toMap(currentTag));
 			}
 		} else {
-			ret.put(SCRIPT_FIELD_TAGS, Collections.emptySet());
+			ret.put(SCRIPT_FIELD_TAGS, Collections.emptyMap());
 			ret.put(SCRIPT_FIELD_TAG_NAMES, Collections.emptySet());
 		}
 
@@ -659,9 +659,9 @@ public final class RangerRequestScriptEvaluator {
 		varNames.add(SCRIPT_VAR_TAGNAMES);
 		varNames.add(SCRIPT_VAR_TAGS);
 		varNames.add(SCRIPT_VAR_UGA);
-		varNames.add(SCRIPT_VAR_UGROUP);
-		varNames.add(SCRIPT_VAR_UGROUPS);
-		varNames.add(SCRIPT_VAR_UROLES);
+		varNames.add(SCRIPT_VAR_UG);
+		varNames.add(SCRIPT_VAR_UGNAMES);
+		varNames.add(SCRIPT_VAR_URNAMES);
 		varNames.add(SCRIPT_VAR_USER);
 
 		return ".*(" + StringUtils.join(varNames, '|') + ").*";
diff --git a/agents-common/src/main/java/org/apache/ranger/plugin/policyevaluator/RangerDefaultRowFilterPolicyItemEvaluator.java b/agents-common/src/main/java/org/apache/ranger/plugin/policyevaluator/RangerDefaultRowFilterPolicyItemEvaluator.java
index 0831dde..841fee3 100644
--- a/agents-common/src/main/java/org/apache/ranger/plugin/policyevaluator/RangerDefaultRowFilterPolicyItemEvaluator.java
+++ b/agents-common/src/main/java/org/apache/ranger/plugin/policyevaluator/RangerDefaultRowFilterPolicyItemEvaluator.java
@@ -25,15 +25,32 @@ import org.apache.ranger.plugin.model.RangerServiceDef;
 import org.apache.ranger.plugin.policyengine.RangerAccessResult;
 import org.apache.ranger.plugin.policyengine.RangerPolicyEngineOptions;
 import org.apache.ranger.plugin.policyresourcematcher.RangerPolicyResourceMatcher;
+import org.apache.ranger.plugin.util.RangerRequestExprResolver;
 
 
 public class RangerDefaultRowFilterPolicyItemEvaluator extends RangerDefaultPolicyItemEvaluator implements RangerRowFilterPolicyItemEvaluator {
 	final private RangerRowFilterPolicyItem rowFilterPolicyItem;
+	final private String                    rowFilterExpr;
+	final private RangerRequestExprResolver exprResolver;
 
 	public RangerDefaultRowFilterPolicyItemEvaluator(RangerServiceDef serviceDef, RangerPolicy policy, RangerRowFilterPolicyItem policyItem, int policyItemIndex, RangerPolicyEngineOptions options) {
 		super(serviceDef, policy, policyItem, RangerPolicyItemEvaluator.POLICY_ITEM_TYPE_DATAMASK, policyItemIndex, options);
 
 		rowFilterPolicyItem = policyItem;
+
+		RangerPolicyItemRowFilterInfo rowFilterInfo = getRowFilterInfo();
+
+		if (rowFilterInfo != null && rowFilterInfo.getFilterExpr() != null) {
+			rowFilterExpr = rowFilterInfo.getFilterExpr();
+		} else {
+			rowFilterExpr = null;
+		}
+
+		if (rowFilterExpr != null && RangerRequestExprResolver.hasExpressions(rowFilterExpr)) {
+			exprResolver = new RangerRequestExprResolver(rowFilterExpr, getServiceType());
+		} else {
+			exprResolver = null;
+		}
 	}
 
 	@Override
@@ -43,11 +60,16 @@ public class RangerDefaultRowFilterPolicyItemEvaluator extends RangerDefaultPoli
 
 	@Override
 	public void updateAccessResult(RangerPolicyEvaluator policyEvaluator, RangerAccessResult result, RangerPolicyResourceMatcher.MatchType matchType) {
-		RangerPolicyItemRowFilterInfo rowFilterInfo = getRowFilterInfo();
+		if (result.getFilterExpr() == null) {
+			if (exprResolver != null) {
+				result.setFilterExpr(exprResolver.resolveExpressions(result.getAccessRequest()));
+			} else if (rowFilterExpr != null) {
+				result.setFilterExpr(rowFilterExpr);
+			}
 
-		if (result.getFilterExpr() == null && rowFilterInfo != null) {
-			result.setFilterExpr(rowFilterInfo.getFilterExpr());
-			policyEvaluator.updateAccessResult(result, matchType, true, getComments());
+			if (result.getFilterExpr() != null) {
+				policyEvaluator.updateAccessResult(result, matchType, true, getComments());
+			}
 		}
 	}
 }
diff --git a/agents-common/src/main/java/org/apache/ranger/plugin/util/RangerCommonConstants.java b/agents-common/src/main/java/org/apache/ranger/plugin/util/RangerCommonConstants.java
index 63bed50..4152170 100644
--- a/agents-common/src/main/java/org/apache/ranger/plugin/util/RangerCommonConstants.java
+++ b/agents-common/src/main/java/org/apache/ranger/plugin/util/RangerCommonConstants.java
@@ -63,9 +63,9 @@ public class RangerCommonConstants {
 	public static final String SCRIPT_VAR_TAGNAMES                  = "TAGNAMES";
 	public static final String SCRIPT_VAR_TAGS                      = "TAGS";
 	public static final String SCRIPT_VAR_UGA                       = "UGA";
-	public static final String SCRIPT_VAR_UGROUP                    = "UGROUP";
-	public static final String SCRIPT_VAR_UGROUPS                   = "UGROUPS";
-	public static final String SCRIPT_VAR_UROLES                    = "UROLES";
+	public static final String SCRIPT_VAR_UG                        = "UG";
+	public static final String SCRIPT_VAR_UGNAMES                   = "UGNAMES";
+	public static final String SCRIPT_VAR_URNAMES                   = "URNAMES";
 	public static final String SCRIPT_VAR_USER                      = "USER";
 
 	public static final String SCRIPT_FIELD_ACCESS_TIME             = "accessTime";
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
new file mode 100644
index 0000000..d70430b
--- /dev/null
+++ b/agents-common/src/main/java/org/apache/ranger/plugin/util/RangerRequestExprResolver.java
@@ -0,0 +1,88 @@
+/*
+ * 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.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.ranger.plugin.policyengine.RangerAccessRequest;
+import org.apache.ranger.plugin.policyengine.RangerRequestScriptEvaluator;
+
+import javax.script.ScriptEngine;
+import java.util.Objects;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+
+public class RangerRequestExprResolver {
+    private static final Log LOG = LogFactory.getLog(RangerRequestExprResolver.class);
+
+    private static final String  REGEX_GROUP_EXPR   = "expr";
+    private static final String  SCRIPT_ENGINE_NAME = "JavaScript";
+    private static final Pattern PATTERN            = Pattern.compile("\\$\\{\\{(?<" + REGEX_GROUP_EXPR + ">.*?)\\}\\}");
+
+    private final String  str;
+    private final String  serviceType;
+    private final boolean hasTokens;
+
+
+    public RangerRequestExprResolver(String str, String serviceType) {
+        this.str         = str;
+        this.serviceType = serviceType;
+        this.hasTokens   = hasExpressions(str);
+
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("RangerRequestExprResolver(" + str + "): hasTokens=" + hasTokens);
+        }
+    }
+
+    public String resolveExpressions(RangerAccessRequest request) {
+        String ret = str;
+
+        if (hasTokens) {
+            RangerRequestScriptEvaluator scriptEvaluator = new RangerRequestScriptEvaluator(request);
+            ScriptEngine                 scriptEngine    = ScriptEngineUtil.createScriptEngine(SCRIPT_ENGINE_NAME, serviceType);
+            StringBuffer                 sb              = new StringBuffer();
+            Matcher                      matcher         = PATTERN.matcher(str);
+
+            while (matcher.find()) {
+                String expr = matcher.group(REGEX_GROUP_EXPR);
+                String val  = Objects.toString(scriptEvaluator.evaluateScript(scriptEngine, expr));
+
+                matcher.appendReplacement(sb, val);
+            }
+
+            matcher.appendTail(sb);
+
+            ret = sb.toString();
+
+            if (LOG.isDebugEnabled()) {
+                LOG.debug("RangerRequestExprResolver.processExpressions(" + str + "): ret=" + ret);
+            }
+        }
+
+        return ret;
+    }
+
+    public static boolean hasExpressions(String str) {
+        Matcher matcher = PATTERN.matcher(str);
+
+        return matcher.find();
+    }
+}
diff --git a/agents-common/src/main/java/org/apache/ranger/plugin/util/ScriptEngineUtil.java b/agents-common/src/main/java/org/apache/ranger/plugin/util/ScriptEngineUtil.java
index b0782ec..983c578 100644
--- a/agents-common/src/main/java/org/apache/ranger/plugin/util/ScriptEngineUtil.java
+++ b/agents-common/src/main/java/org/apache/ranger/plugin/util/ScriptEngineUtil.java
@@ -58,7 +58,7 @@ public class ScriptEngineUtil {
             LOG.error("RangerScriptConditionEvaluator.init() failed with exception=" + exp);
         }
 
-        if (ret == null) {
+        if (ret == null && serviceType != null) {
             LOG.warn("failed to initialize script engine '" + engineName + "' in a default manner." +
                      " Will try to get script-engine from plugin-class-loader");
 
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 d5f1564..5b98574 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
@@ -30,6 +30,7 @@ import org.apache.ranger.plugin.policyengine.RangerAccessRequestImpl;
 import org.apache.ranger.plugin.policyengine.RangerAccessResource;
 import org.apache.ranger.plugin.policyresourcematcher.RangerPolicyResourceMatcher;
 import org.apache.ranger.plugin.util.RangerAccessRequestUtil;
+import org.apache.ranger.plugin.util.RangerUserStore;
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
@@ -75,11 +76,11 @@ public class RangerCustomConditionMatcherTest {
 		RangerScriptConditionEvaluator userGroup1Attr2Condition = createScriptConditionEvaluator("_ctx.request.userGroupAttributes['test-group1']['attr2'].equals('test-group1-value2')");
 		RangerScriptConditionEvaluator userGroup2Attr1Condition = createScriptConditionEvaluator("_ctx.request.userGroupAttributes['test-group2']['attr1'].equals('test-group2-value1')");
 		RangerScriptConditionEvaluator userGroup2Attr2Condition = createScriptConditionEvaluator("_ctx.request.userGroupAttributes['test-group2']['attr2'].equals('test-group2-value2')");
-		RangerScriptConditionEvaluator tagsLengthCondition     = createScriptConditionEvaluator("_ctx.tags.length == 2");
+		RangerScriptConditionEvaluator tagsLengthCondition     = createScriptConditionEvaluator("Object.keys(_ctx.tags).length == 2");
 		RangerScriptConditionEvaluator tagTypeCondition        = createScriptConditionEvaluator("_ctx.tag._type.equals('PCI')");
 		RangerScriptConditionEvaluator tagAttributesCondition  = createScriptConditionEvaluator("_ctx.tag.attr1.equals('PCI_value')");
-		RangerScriptConditionEvaluator tagsTypeCondition       = createScriptConditionEvaluator("switch(_ctx.tags[0]._type) { case 'PCI': _ctx.tags[1]._type.equals('PII'); break; case 'PII': _ctx.tags[1]._type.equals('PCI'); break; default: false; }");
-		RangerScriptConditionEvaluator tagsAttributesCondition = createScriptConditionEvaluator("switch(_ctx.tags[0]._type) { case 'PCI': _ctx.tags[0].attr1.equals('PCI_value') && _ctx.tags[1].attr1.equals('PII_value'); break; case 'PII': _ctx.tags[0].attr1.equals('PII_value') && _ctx.tags[1].attr1.equals('PCI_value'); break; default: false; }");
+		RangerScriptConditionEvaluator tagsTypeCondition       = createScriptConditionEvaluator("_ctx.tags['PII']._type == 'PII' && _ctx.tags['PCI']._type == 'PCI'");
+		RangerScriptConditionEvaluator tagsAttributesCondition = createScriptConditionEvaluator("_ctx.tags['PII'].attr1.equals('PII_value') && _ctx.tags['PCI'].attr1.equals('PCI_value')");
 
 		Assert.assertTrue("request.resource.database should be db1", resourceDbCondition.isMatched(request));
 		Assert.assertTrue("request.resource.database should not be db2", resourceDbCondition2.isMatched(request));
@@ -265,8 +266,8 @@ public class RangerCustomConditionMatcherTest {
 		request.setAccessType("select");
 		request.setAction("query");
 		request.setUser("test-user");
-		request.setUserGroups(Collections.singleton("test-group"));
-		request.setUserRoles(Collections.singleton("test-role"));
+		request.setUserGroups(new HashSet<>(Arrays.asList("test-group1", "test-group2")));
+		request.setUserRoles(new HashSet<>(Arrays.asList("test-role1", "test-role2")));
 
 		if (resourceTags != null) {
 			Set<RangerTagForEval> rangerTagForEvals = new HashSet<>();
@@ -289,6 +290,30 @@ public class RangerCustomConditionMatcherTest {
 			RangerAccessRequestUtil.setRequestTagsInContext(request.getContext(), null);
 		}
 
+		Map<String, Map<String, String>> userAttrMapping  = new HashMap<>();
+		Map<String, Map<String, String>> groupAttrMapping = new HashMap<>();
+		Map<String, String>              testUserAttrs    = new HashMap<>();
+		Map<String, String>              testGroup1Attrs  = new HashMap<>();
+		Map<String, String>              testGroup2Attrs  = new HashMap<>();
+
+		testUserAttrs.put("attr1", "test-user-value1");
+		testUserAttrs.put("attr2", "test-user-value2");
+		testGroup1Attrs.put("attr1", "test-group1-value1");
+		testGroup1Attrs.put("attr2", "test-group1-value2");
+		testGroup2Attrs.put("attr1", "test-group2-value1");
+		testGroup2Attrs.put("attr2", "test-group2-value2");
+
+		userAttrMapping.put("test-user", testUserAttrs);
+		groupAttrMapping.put("test-group1", testGroup1Attrs);
+		groupAttrMapping.put("test-group2", testGroup2Attrs);
+
+		RangerUserStore userStore = mock(RangerUserStore.class);
+
+		when(userStore.getUserAttrMapping()).thenReturn(userAttrMapping);
+		when(userStore.getGroupAttrMapping()).thenReturn(groupAttrMapping);
+
+		RangerAccessRequestUtil.setRequestUserStoreInContext(request.getContext(), userStore);
+
 		return request;
 	}
 }
diff --git a/agents-common/src/test/java/org/apache/ranger/plugin/conditionevaluator/RangerRequestScriptEvaluatorTest.java b/agents-common/src/test/java/org/apache/ranger/plugin/conditionevaluator/RangerRequestScriptEvaluatorTest.java
index 798da05..ff638b2 100644
--- a/agents-common/src/test/java/org/apache/ranger/plugin/conditionevaluator/RangerRequestScriptEvaluatorTest.java
+++ b/agents-common/src/test/java/org/apache/ranger/plugin/conditionevaluator/RangerRequestScriptEvaluatorTest.java
@@ -59,19 +59,20 @@ public class RangerRequestScriptEvaluatorTest {
         Assert.assertTrue("test: USER['state'] is 'CA'", (Boolean) evaluator.evaluateScript(scriptEngine, "USER['state'] == 'CA'"));
         Assert.assertTrue("test: USER.state is 'CA'", (Boolean) evaluator.evaluateScript(scriptEngine, "USER.state == 'CA'"));
 
-        Assert.assertTrue("test: UGROUPS has test-group1", (Boolean)evaluator.evaluateScript(scriptEngine, "UGROUPS.indexOf('test-group1') != -1"));
-        Assert.assertTrue("test: UGROUPS has test-group2", (Boolean)evaluator.evaluateScript(scriptEngine, "UGROUPS.indexOf('test-group2') != -1"));
-        Assert.assertTrue("test: UGROUPS doesn't have test-group3", (Boolean)evaluator.evaluateScript(scriptEngine, "UGROUPS.indexOf('test-group3') == -1"));
-        Assert.assertTrue("test: UGROUP['test-group1'].dept is 'ENGG'", (Boolean) evaluator.evaluateScript(scriptEngine, "UGROUP['test-group1'].dept == 'ENGG'"));
-        Assert.assertTrue("test: UGROUP['test-group1'].site is 10", (Boolean) evaluator.evaluateScript(scriptEngine, "UGROUP['test-group1'].site == 10"));
-        Assert.assertTrue("test: UGROUP['test-group2'].dept is 'PROD'", (Boolean) evaluator.evaluateScript(scriptEngine, "UGROUP['test-group2'].dept == 'PROD'"));
-        Assert.assertTrue("test: UGROUP['test-group2'].site is 20", (Boolean) evaluator.evaluateScript(scriptEngine, "UGROUP['test-group2'].site == 20"));
-        Assert.assertTrue("test: UGROUP['test-group3'] is null", (Boolean) evaluator.evaluateScript(scriptEngine, "UGROUP['test-group3'] == null"));
-        Assert.assertTrue("test: UGROUP['test-group1'].notExists is null", (Boolean) evaluator.evaluateScript(scriptEngine, "UGROUP['test-group1'].notExists == null"));
-
-        Assert.assertTrue("test: UROLES has test-role1", (Boolean)evaluator.evaluateScript(scriptEngine, "UROLES.indexOf('test-role1') != -1"));
-        Assert.assertTrue("test: UROLES has test-role2", (Boolean)evaluator.evaluateScript(scriptEngine, "UROLES.indexOf('test-role2') != -1"));
-        Assert.assertTrue("test: UROLES doesn't have test-role3", (Boolean)evaluator.evaluateScript(scriptEngine, "UROLES.indexOf('test-role3') == -1"));
+        Assert.assertTrue("test: UGNAMES has test-group1", (Boolean)evaluator.evaluateScript(scriptEngine, "UGNAMES.indexOf('test-group1') != -1"));
+        Assert.assertTrue("test: UGNAMES has test-group2", (Boolean)evaluator.evaluateScript(scriptEngine, "UGNAMES.indexOf('test-group2') != -1"));
+        Assert.assertTrue("test: UGNAMES doesn't have test-group3", (Boolean)evaluator.evaluateScript(scriptEngine, "UGNAMES.indexOf('test-group3') == -1"));
+
+        Assert.assertTrue("test: UG['test-group1'].dept is 'ENGG'", (Boolean) evaluator.evaluateScript(scriptEngine, "UG['test-group1'].dept == 'ENGG'"));
+        Assert.assertTrue("test: UG['test-group1'].site is 10", (Boolean) evaluator.evaluateScript(scriptEngine, "UG['test-group1'].site == 10"));
+        Assert.assertTrue("test: UG['test-group2'].dept is 'PROD'", (Boolean) evaluator.evaluateScript(scriptEngine, "UG['test-group2'].dept == 'PROD'"));
+        Assert.assertTrue("test: UG['test-group2'].site is 20", (Boolean) evaluator.evaluateScript(scriptEngine, "UG['test-group2'].site == 20"));
+        Assert.assertTrue("test: UG['test-group3'] is null", (Boolean) evaluator.evaluateScript(scriptEngine, "UG['test-group3'] == null"));
+        Assert.assertTrue("test: UG['test-group1'].notExists is null", (Boolean) evaluator.evaluateScript(scriptEngine, "UG['test-group1'].notExists == null"));
+
+        Assert.assertTrue("test: URNAMES has test-role1", (Boolean)evaluator.evaluateScript(scriptEngine, "URNAMES.indexOf('test-role1') != -1"));
+        Assert.assertTrue("test: URNAMES has test-role2", (Boolean)evaluator.evaluateScript(scriptEngine, "URNAMES.indexOf('test-role2') != -1"));
+        Assert.assertTrue("test: URNAMES doesn't have test-role3", (Boolean)evaluator.evaluateScript(scriptEngine, "URNAMES.indexOf('test-role3') == -1"));
 
         Assert.assertTrue("test: UGA.sVal['dept'] is 'ENGG'", (Boolean)evaluator.evaluateScript(scriptEngine, "UGA.sVal['dept'] == 'ENGG'"));
         Assert.assertTrue("test: UGA.sVal['site'] is 10", (Boolean) evaluator.evaluateScript(scriptEngine, "UGA.sVal['site'] == 10"));
@@ -93,9 +94,11 @@ public class RangerRequestScriptEvaluatorTest {
 
         Assert.assertTrue("test: TAG._type is 'PII'", (Boolean) evaluator.evaluateScript(scriptEngine, "TAG._type == 'PII'"));
         Assert.assertTrue("test: TAG.attr1 is 'PII_value'", (Boolean) evaluator.evaluateScript(scriptEngine, "TAG.attr1 == 'PII_value'"));
-        Assert.assertTrue("test: TAGS.length is 2", (Boolean) evaluator.evaluateScript(scriptEngine, "TAGS.length == 2"));
-        Assert.assertTrue("test: TAGS has PII and PCI", (Boolean) evaluator.evaluateScript(scriptEngine, "TAGS[0]._type == 'PCI' ? TAGS[1]._type == 'PII' : TAGS[0]._type == 'PII' && TAGS[1]._type == 'PCI'"));
-        Assert.assertTrue("test: TAGS has PII.attr1=PII_value and PCI.attr1=PCI_value", (Boolean) evaluator.evaluateScript(scriptEngine, "TAGS[0]._type == 'PCI' ? (TAGS[0].attr1 == 'PCI_value' && TAGS[1].attr1 == 'PII_value') : (TAGS[0].attr1 == 'PII_value' && TAGS[1].attr1 == 'PCI_value')"));
+        Assert.assertTrue("test: TAGS.length is 2", (Boolean) evaluator.evaluateScript(scriptEngine, "Object.keys(TAGS).length == 2"));
+        Assert.assertEquals("test: TAGS has PII", evaluator.evaluateScript(scriptEngine, "TAGS.PII._type"), "PII");
+        Assert.assertEquals("test: TAGS has PCI", evaluator.evaluateScript(scriptEngine, "TAGS.PCI._type"), "PCI");
+        Assert.assertEquals("test: TAGS has PII.attr1=PII_value", evaluator.evaluateScript(scriptEngine, "TAGS['PII'].attr1"), "PII_value");
+        Assert.assertEquals("test: TAGS has PCI.attr1=PCI_value", evaluator.evaluateScript(scriptEngine, "TAGS['PCI'].attr1"), "PCI_value");
 
         Assert.assertTrue("test: TAGNAMES.length is 2", (Boolean) evaluator.evaluateScript(scriptEngine, "TAGNAMES.length == 2"));
         Assert.assertTrue("test: TAGNAMES has 'PII'", (Boolean)evaluator.evaluateScript(scriptEngine, "TAGNAMES.indexOf('PII') != -1"));
diff --git a/agents-common/src/test/java/org/apache/ranger/plugin/util/RangerRequestExprResolverTest.java b/agents-common/src/test/java/org/apache/ranger/plugin/util/RangerRequestExprResolverTest.java
new file mode 100644
index 0000000..6d5edc0
--- /dev/null
+++ b/agents-common/src/test/java/org/apache/ranger/plugin/util/RangerRequestExprResolverTest.java
@@ -0,0 +1,170 @@
+/*
+ * 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.RangerTagForEval;
+import org.apache.ranger.plugin.model.RangerTag;
+import org.apache.ranger.plugin.policyengine.RangerAccessRequest;
+import org.apache.ranger.plugin.policyengine.RangerAccessRequestImpl;
+import org.apache.ranger.plugin.policyengine.RangerAccessResource;
+import org.apache.ranger.plugin.policyresourcematcher.RangerPolicyResourceMatcher;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.*;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class RangerRequestExprResolverTest {
+    @Test
+    public void testRequestAttributes() {
+        RangerAccessRequest request = createRequest(Arrays.asList("PII", "PCI"));
+
+        Map<String, String> exprValue = new HashMap<>();
+
+        exprValue.put("s3://mybucket/users/${{USER._name}}/${{USER.state}}/test.txt", "s3://mybucket/users/test-user/CA/test.txt");
+        exprValue.put("state == '${{USER.state}}' AND dept == '${{UGA.sVal.dept}}'", "state == 'CA' AND dept == 'ENGG'");
+        exprValue.put("attr1 == '${{TAG.attr1}}'", "attr1 == 'PII_value'");
+
+        exprValue.put("${{USER._name}}", "test-user");
+        exprValue.put("${{USER['state']}}", "CA");
+        exprValue.put("${{USER.state}}", "CA");
+
+        exprValue.put("${{UGNAMES.indexOf('test-group1') != -1}}", "true");
+        exprValue.put("${{UGNAMES.indexOf('test-group2') != -1}}", "true");
+        exprValue.put("${{UGNAMES.indexOf('test-group3') == -1}}", "true");
+
+        exprValue.put("${{UG['test-group1'].dept}}", "ENGG");
+        exprValue.put("${{UG['test-group1'].site}}", "10");
+        exprValue.put("${{UG['test-group2'].dept}}", "PROD");
+        exprValue.put("${{UG['test-group2'].site}}", "20");
+        exprValue.put("${{UG['test-group3']}}", "null");
+        exprValue.put("${{UG['test-group1'].notExists}}", "null");
+
+        exprValue.put("${{URNAMES.indexOf('test-role1') != -1}}", "true");
+        exprValue.put("${{URNAMES.indexOf('test-role2') != -1}}", "true");
+        exprValue.put("${{URNAMES.indexOf('test-role3') == -1}}", "true");
+
+        exprValue.put("${{UGA.sVal.dept}}", "ENGG");
+        exprValue.put("${{UGA.sVal.site}}", "10");
+        exprValue.put("${{UGA.sVal.notExists}}", "null");
+        exprValue.put("${{J(UGA.mVal.dept)}}", "[\"ENGG\",\"PROD\"]");
+        exprValue.put("${{J(UGA.mVal.site)}}", "[\"10\",\"20\"]");
+        exprValue.put("${{J(UGA.mVal.notExists)}}", "null");
+        exprValue.put("${{UGA.mVal['dept'].indexOf('ENGG') != -1}}", "true");
+        exprValue.put("${{UGA.mVal['dept'].indexOf('PROD') != -1}}", "true");
+        exprValue.put("${{UGA.mVal['dept'].indexOf('EXEC') == -1}}", "true");
+
+        exprValue.put("${{REQ.accessType}}", "select");
+        exprValue.put("${{REQ.action}}", "query");
+
+        exprValue.put("${{RES._ownerUser}}", "testUser");
+        exprValue.put("${{RES.database}}", "db1");
+        exprValue.put("${{RES.table}}", "tbl1");
+        exprValue.put("${{RES.column}}", "col1");
+
+        exprValue.put("${{TAG._type}}", "PII");
+        exprValue.put("${{TAG.attr1}}", "PII_value");
+        exprValue.put("${{Object.keys(TAGS).length}}", "2");
+        exprValue.put("${{TAGS.PCI._type}}", "PCI");
+        exprValue.put("${{TAGS.PCI.attr1}}", "PCI_value");
+        exprValue.put("${{TAGS.PII._type}}", "PII");
+        exprValue.put("${{TAGS.PII.attr1}}", "PII_value");
+
+        exprValue.put("${{TAGNAMES.length}}", "2");
+        exprValue.put("${{TAGNAMES.indexOf('PII') != -1}}", "true");
+        exprValue.put("${{TAGNAMES.indexOf('PCI') != -1}}", "true");
+
+        for (Map.Entry<String, String> entry : exprValue.entrySet()) {
+            String                    expr        = entry.getKey();
+            String                    value       = entry.getValue();
+            RangerRequestExprResolver resolver    = new RangerRequestExprResolver(expr, null);
+            String                    resolvedVal = resolver.resolveExpressions(request);
+
+            Assert.assertEquals(expr, value, resolvedVal);
+        }
+    }
+
+
+    RangerAccessRequest createRequest(List<String> resourceTags) {
+        RangerAccessResource resource = mock(RangerAccessResource.class);
+
+        Map<String, Object> resourceMap = new HashMap<>();
+
+        resourceMap.put("database", "db1");
+        resourceMap.put("table", "tbl1");
+        resourceMap.put("column", "col1");
+
+        when(resource.getAsString()).thenReturn("db1/tbl1/col1");
+        when(resource.getOwnerUser()).thenReturn("testUser");
+        when(resource.getAsMap()).thenReturn(resourceMap);
+        when(resource.getReadOnlyCopy()).thenReturn(resource);
+
+        RangerAccessRequestImpl request = new RangerAccessRequestImpl();
+
+        request.setResource(resource);
+        request.setResourceMatchingScope(RangerAccessRequest.ResourceMatchingScope.SELF);
+        request.setAccessType("select");
+        request.setAction("query");
+        request.setUser("test-user");
+        request.setUserGroups(new HashSet<>(Arrays.asList("test-group1", "test-group2")));
+        request.setUserRoles(new HashSet<>(Arrays.asList("test-role1", "test-role2")));
+
+        RangerAccessRequestUtil.setCurrentResourceInContext(request.getContext(), resource);
+
+        if (resourceTags != null) {
+            Set<RangerTagForEval> rangerTagForEvals = new HashSet<>();
+            RangerTagForEval      currentTag        = null;
+
+            for (String resourceTag : resourceTags) {
+                RangerTag tag        = new RangerTag(UUID.randomUUID().toString(), resourceTag, Collections.singletonMap("attr1", resourceTag + "_value"), null, null, null);
+                RangerTagForEval tagForEval = new RangerTagForEval(tag, RangerPolicyResourceMatcher.MatchType.SELF);
+
+                rangerTagForEvals.add(tagForEval);
+
+                if (currentTag == null) {
+                    currentTag = tagForEval;
+                }
+            }
+
+            RangerAccessRequestUtil.setRequestTagsInContext(request.getContext(), rangerTagForEvals);
+            RangerAccessRequestUtil.setCurrentTagInContext(request.getContext(), currentTag);
+        }  else {
+            RangerAccessRequestUtil.setRequestTagsInContext(request.getContext(), null);
+        }
+
+        RangerUserStore userStore = mock(RangerUserStore.class);
+
+        RangerAccessRequestUtil.setRequestUserStoreInContext(request.getContext(), userStore);
+
+        Map<String, Map<String, String>> userAttrMapping  = Collections.singletonMap("test-user", Collections.singletonMap("state", "CA"));
+        Map<String, Map<String, String>> groupAttrMapping = new HashMap<>();
+
+        groupAttrMapping.put("test-group1", new HashMap<String, String>() {{ put("dept", "ENGG"); put("site", "10"); }});
+        groupAttrMapping.put("test-group2", new HashMap<String, String>() {{ put("dept", "PROD"); put("site", "20"); }});
+
+        when(userStore.getUserAttrMapping()).thenReturn(userAttrMapping);
+        when(userStore.getGroupAttrMapping()).thenReturn(groupAttrMapping);
+
+        return request;
+    }
+
+}
\ No newline at end of file