You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ranger.apache.org by sp...@apache.org on 2020/03/09 20:50:30 UTC

[ranger] branch master updated: RANGER-2723: Support ldap attribute based document level control for solr plugin

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

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


The following commit(s) were added to refs/heads/master by this push:
     new 2ec95d5  RANGER-2723: Support ldap attribute based document level control for solr plugin
2ec95d5 is described below

commit 2ec95d58a9e6b674b7d374f6b1a7a98eeb76195e
Author: Sailaja Polavarapu <sp...@cloudera.com>
AuthorDate: Mon Mar 9 13:50:03 2020 -0700

    RANGER-2723: Support ldap attribute based document level control for solr plugin
---
 .../admin/client/AbstractRangerAdminClient.java    |   5 +
 .../ranger/admin/client/RangerAdminClient.java     |   3 +
 .../ranger/admin/client/RangerAdminRESTClient.java |  82 ++++
 .../RangerAdminUserStoreRetriever.java             |  74 +++
 .../contextenricher/RangerUserStoreEnricher.java   | 505 +++++++++++++++++++++
 .../contextenricher/RangerUserStoreRetriever.java  |  64 +++
 .../plugin/util/RangerAccessRequestUtil.java       |  16 +
 .../apache/ranger/plugin/util/RangerRESTUtils.java |   3 +
 .../solr/authorizer/FieldToAttributeMapping.java   | 106 +++++
 .../solr/authorizer/RangerSolrAuthorizer.java      | 265 ++++++++++-
 .../solr/authorizer/SubsetQueryPlugin.java         |  97 ++++
 11 files changed, 1202 insertions(+), 18 deletions(-)

diff --git a/agents-common/src/main/java/org/apache/ranger/admin/client/AbstractRangerAdminClient.java b/agents-common/src/main/java/org/apache/ranger/admin/client/AbstractRangerAdminClient.java
index 87d0190..1ad5ec0 100644
--- a/agents-common/src/main/java/org/apache/ranger/admin/client/AbstractRangerAdminClient.java
+++ b/agents-common/src/main/java/org/apache/ranger/admin/client/AbstractRangerAdminClient.java
@@ -111,4 +111,9 @@ public abstract class AbstractRangerAdminClient implements RangerAdminClient {
     public List<String> getTagTypes(String tagTypePattern) throws Exception {
         return null;
     }
+
+    @Override
+    public RangerUserStore getUserStoreIfUpdated(long lastKnownUserStoreVersion, long lastActivationTimeInMillis) throws Exception {
+        return null;
+    }
 }
diff --git a/agents-common/src/main/java/org/apache/ranger/admin/client/RangerAdminClient.java b/agents-common/src/main/java/org/apache/ranger/admin/client/RangerAdminClient.java
index 58eb00a..22a8121 100644
--- a/agents-common/src/main/java/org/apache/ranger/admin/client/RangerAdminClient.java
+++ b/agents-common/src/main/java/org/apache/ranger/admin/client/RangerAdminClient.java
@@ -27,6 +27,7 @@ import org.apache.ranger.plugin.util.GrantRevokeRoleRequest;
 import org.apache.ranger.plugin.util.RangerRoles;
 import org.apache.ranger.plugin.util.ServicePolicies;
 import org.apache.ranger.plugin.util.ServiceTags;
+import org.apache.ranger.plugin.util.RangerUserStore;
 
 import java.util.List;
 
@@ -61,4 +62,6 @@ public interface RangerAdminClient {
 
 	List<String> getTagTypes(String tagTypePattern) throws Exception;
 
+	RangerUserStore getUserStoreIfUpdated(long lastKnownUserStoreVersion, long lastActivationTimeInMillis) throws Exception;
+
 }
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 e5f9747..479a50c 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
@@ -908,4 +908,86 @@ public class RangerAdminRESTClient extends AbstractRangerAdminClient {
 		return ret;
 	}
 
+	@Override
+	public RangerUserStore getUserStoreIfUpdated(long lastKnownUserStoreVersion, long lastActivationTimeInMillis) throws Exception {
+		if (LOG.isDebugEnabled()) {
+			LOG.debug("==> RangerAdminRESTClient.getUserStoreIfUpdated(" + lastKnownUserStoreVersion + ", " + lastActivationTimeInMillis + ")");
+		}
+
+		final RangerUserStore ret;
+		final UserGroupInformation user = MiscUtil.getUGILoginUser();
+		final boolean isSecureMode = user != null && UserGroupInformation.isSecurityEnabled();
+		final ClientResponse 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<ClientResponse> action = new PrivilegedAction<ClientResponse>() {
+				public ClientResponse run() {
+					ClientResponse clientRes = null;
+					String relativeURL = RangerRESTUtils.REST_URL_SERVICE_SERCURE_GET_USERSTORE + serviceNameUrlParam;
+					try {
+						clientRes =  restClient.get(relativeURL, queryParams);
+					} catch (Exception e) {
+						LOG.error("Failed to get response, Error is : "+e.getMessage());
+					}
+					return clientRes;
+				}
+			};
+			response = user.doAs(action);
+		} else {
+			if (LOG.isDebugEnabled()) {
+				LOG.debug("Checking UserStore updated as user : " + user);
+			}
+			String relativeURL = RangerRESTUtils.REST_URL_SERVICE_SERCURE_GET_USERSTORE + serviceNameUrlParam;
+			response = restClient.get(relativeURL, queryParams);
+		}
+
+		if (response == null || response.getStatus() == HttpServletResponse.SC_NOT_MODIFIED) {
+			if (response == null) {
+				LOG.error("Error getting UserStore; Received NULL response!!. secureMode=" + isSecureMode + ", user=" + user + ", serviceName=" + serviceName);
+			} else {
+				RESTResponse resp = RESTResponse.fromClientResponse(response);
+				if (LOG.isDebugEnabled()) {
+					LOG.debug("No change in UserStore. secureMode=" + isSecureMode + ", user=" + user
+							+ ", response=" + resp + ", serviceName=" + serviceName
+							+ ", " + "lastKnownUserStoreVersion=" + lastKnownUserStoreVersion
+							+ ", " + "lastActivationTimeInMillis=" + lastActivationTimeInMillis);
+				}
+			}
+			ret = null;
+		} else if (response.getStatus() == HttpServletResponse.SC_OK) {
+			ret = response.getEntity(RangerUserStore.class);
+		} else if (response.getStatus() == HttpServletResponse.SC_NOT_FOUND) {
+			ret = null;
+			LOG.error("Error getting UserStore; service not found. secureMode=" + isSecureMode + ", user=" + user
+					+ ", response=" + response.getStatus() + ", serviceName=" + serviceName
+					+ ", " + "lastKnownUserStoreVersion=" + lastKnownUserStoreVersion
+					+ ", " + "lastActivationTimeInMillis=" + lastActivationTimeInMillis);
+			String exceptionMsg = response.hasEntity() ? response.getEntity(String.class) : null;
+
+			RangerServiceNotFoundException.throwExceptionIfServiceNotFound(serviceName, exceptionMsg);
+
+			LOG.warn("Received 404 error code with body:[" + exceptionMsg + "], Ignoring");
+		} else {
+			RESTResponse resp = RESTResponse.fromClientResponse(response);
+			LOG.warn("Error getting UserStore. secureMode=" + isSecureMode + ", user=" + user + ", response=" + resp + ", serviceName=" + serviceName);
+			ret = null;
+		}
+
+		if(LOG.isDebugEnabled()) {
+			LOG.debug("<== RangerAdminRESTClient.getUserStoreIfUpdated(" + lastKnownUserStoreVersion + ", " + lastActivationTimeInMillis + "): ");
+		}
+
+		return ret;
+	}
+
 }
diff --git a/agents-common/src/main/java/org/apache/ranger/plugin/contextenricher/RangerAdminUserStoreRetriever.java b/agents-common/src/main/java/org/apache/ranger/plugin/contextenricher/RangerAdminUserStoreRetriever.java
new file mode 100644
index 0000000..ed96336
--- /dev/null
+++ b/agents-common/src/main/java/org/apache/ranger/plugin/contextenricher/RangerAdminUserStoreRetriever.java
@@ -0,0 +1,74 @@
+/*
+ * 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.contextenricher;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.ranger.admin.client.RangerAdminClient;
+import org.apache.ranger.authorization.hadoop.config.RangerPluginConfig;
+import org.apache.ranger.plugin.service.RangerBasePlugin;
+import org.apache.ranger.plugin.util.RangerUserStore;
+
+import java.nio.channels.ClosedByInterruptException;
+import java.util.Map;
+
+public class RangerAdminUserStoreRetriever extends RangerUserStoreRetriever {
+    private static final Log LOG = LogFactory.getLog(RangerAdminUserStoreRetriever.class);
+
+    private RangerAdminClient adminClient;
+
+    @Override
+    public void init(Map<String, String> options) {
+
+        if (StringUtils.isNotBlank(serviceName) && serviceDef != null && StringUtils.isNotBlank(appId)) {
+            RangerPluginConfig pluginConfig = super.pluginConfig;
+
+            if (pluginConfig == null) {
+                pluginConfig = new RangerPluginConfig(serviceDef.getName(), serviceName, appId, null, null, null);
+            }
+
+            adminClient = RangerBasePlugin.createAdminClient(pluginConfig);
+        } else {
+            LOG.error("FATAL: Cannot find service/serviceDef to use for retrieving userstore. Will NOT be able to retrieve userstore.");
+        }
+    }
+
+    @Override
+    public RangerUserStore retrieveUserStoreInfo(long lastKnownVersion, long lastActivationTimeInMillis) throws Exception {
+
+        RangerUserStore rangerUserStore = null;
+
+        if (adminClient != null) {
+            try {
+                rangerUserStore = adminClient.getUserStoreIfUpdated(lastKnownVersion, lastActivationTimeInMillis);
+            } catch (ClosedByInterruptException closedByInterruptException) {
+                LOG.error("UserStore-retriever thread was interrupted while blocked on I/O");
+                throw new InterruptedException();
+            } catch (Exception e) {
+                LOG.error("UserStore-retriever encounterd exception, exception=", e);
+                LOG.error("Returning null userstore info");
+            }
+        }
+        return rangerUserStore;
+    }
+
+}
+
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
new file mode 100644
index 0000000..fd4e11e
--- /dev/null
+++ b/agents-common/src/main/java/org/apache/ranger/plugin/contextenricher/RangerUserStoreEnricher.java
@@ -0,0 +1,505 @@
+/*
+ * 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.contextenricher;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import org.apache.commons.lang.StringUtils;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.ranger.plugin.policyengine.RangerAccessRequest;
+import org.apache.ranger.plugin.service.RangerAuthContext;
+import org.apache.ranger.plugin.util.DownloaderTask;
+import org.apache.ranger.plugin.util.DownloadTrigger;
+import org.apache.ranger.plugin.util.RangerUserStore;
+import org.apache.ranger.plugin.util.RangerPerfTracer;
+import org.apache.ranger.plugin.util.RangerAccessRequestUtil;
+import org.apache.ranger.plugin.util.RangerServiceNotFoundException;
+
+import java.io.File;
+import java.io.Reader;
+import java.io.Writer;
+import java.io.FileWriter;
+import java.io.FileReader;
+import java.util.Timer;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+
+public class RangerUserStoreEnricher extends RangerAbstractContextEnricher {
+    private static final Log LOG = LogFactory.getLog(RangerUserStoreEnricher.class);
+
+    private static final Log 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;
+
+    @Override
+    public void init() {
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("==> RangerUserStoreEnricher.init()");
+        }
+
+        super.init();
+
+        String userStoreRetrieverClassName = getOption(USERSTORE_RETRIEVER_CLASSNAME_OPTION);
+
+        long pollingIntervalMs = getLongOption(USERSTORE_REFRESHER_POLLINGINTERVAL_OPTION, 3600 * 1000);
+
+        if (StringUtils.isNotBlank(userStoreRetrieverClassName)) {
+
+            try {
+                @SuppressWarnings("unchecked")
+                Class<RangerUserStoreRetriever> userStoreRetriverClass = (Class<RangerUserStoreRetriever>) Class.forName(userStoreRetrieverClassName);
+
+                userStoreRetriever = userStoreRetriverClass.newInstance();
+
+            } catch (ClassNotFoundException exception) {
+                LOG.error("Class " + userStoreRetrieverClassName + " not found, exception=" + exception);
+            } catch (ClassCastException exception) {
+                LOG.error("Class " + userStoreRetrieverClassName + " is not a type of RangerUserStoreRetriever, exception=" + exception);
+            } catch (IllegalAccessException exception) {
+                LOG.error("Class " + userStoreRetrieverClassName + " illegally accessed, exception=" + exception);
+            } catch (InstantiationException exception) {
+                LOG.error("Class " + userStoreRetrieverClassName + " could not be instantiated, exception=" + exception);
+            }
+
+            if (userStoreRetriever != null) {
+                String propertyPrefix    = "ranger.plugin." + serviceDef.getName();
+                disableCacheIfServiceNotFound = getBooleanConfig(propertyPrefix + ".disable.cache.if.servicenotfound", true);
+                String cacheDir      = getConfig(propertyPrefix + ".policy.cache.dir", null);
+                String cacheFilename = String.format("%s_%s_userstore.json", appId, serviceName);
+
+                cacheFilename = cacheFilename.replace(File.separatorChar,  '_');
+                cacheFilename = cacheFilename.replace(File.pathSeparatorChar,  '_');
+
+                String cacheFile = cacheDir == null ? null : (cacheDir + File.separator + cacheFilename);
+
+                userStoreRetriever.setServiceName(serviceName);
+                userStoreRetriever.setServiceDef(serviceDef);
+                userStoreRetriever.setAppId(appId);
+                userStoreRetriever.setPluginConfig(getPluginConfig());
+                userStoreRetriever.init(enricherDef.getEnricherOptions());
+
+                userStoreRefresher = new RangerUserStoreRefresher(userStoreRetriever, this, -1L, userStoreDownloadQueue, cacheFile);
+
+                try {
+                    userStoreRefresher.populateUserStoreInfo();
+                } catch (Throwable exception) {
+                    LOG.error("Exception when retrieving userstore information for this enricher", exception);
+                }
+
+                userStoreRefresher.setDaemon(true);
+                userStoreRefresher.startRefresher();
+
+                userStoreDownloadTimer = new Timer("userStoreDownloadTimer", true);
+
+                try {
+                    userStoreDownloadTimer.schedule(new DownloaderTask(userStoreDownloadQueue), pollingIntervalMs, pollingIntervalMs);
+                    if (LOG.isDebugEnabled()) {
+                        LOG.debug("Scheduled userStoreDownloadRefresher to download userstore every " + pollingIntervalMs + " milliseconds");
+                    }
+                } catch (IllegalStateException exception) {
+                    LOG.error("Error scheduling userStoreDownloadTimer:", exception);
+                    LOG.error("*** UserStore information will NOT be downloaded every " + pollingIntervalMs + " milliseconds ***");
+                    userStoreDownloadTimer = null;
+                }
+            }
+        } else {
+            LOG.error("No value specified for " + USERSTORE_RETRIEVER_CLASSNAME_OPTION + " in the RangerUserStoreEnricher options");
+        }
+
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("<== RangerUserStoreEnricher.init()");
+        }
+    }
+
+    @Override
+    public void enrich(RangerAccessRequest request) {
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("==> RangerUserStoreEnricher.enrich(" + request + ")");
+        }
+
+        enrich(request, null);
+
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("<== RangerUserStoreEnricher.enrich(" + request + ")");
+        }
+    }
+
+    @Override
+    public void enrich(RangerAccessRequest request, Object dataStore) {
+
+        // Unused by Solr plugin as document level authorization gets RangerUserStore from AuthContext
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("==> RangerUserStoreEnricher.enrich(" + request + ") with dataStore:[" + dataStore + "]");
+        }
+        final RangerUserStore rangerUserStore;
+
+        if (dataStore instanceof RangerUserStore) {
+            rangerUserStore = (RangerUserStore) dataStore;
+        } else {
+            rangerUserStore = this.rangerUserStore;
+
+            if (dataStore != null) {
+                LOG.warn("Incorrect type of dataStore :[" + dataStore.getClass().getName() + "], falling back to original enrich");
+            }
+        }
+
+        RangerAccessRequestUtil.setRequestUserStoreInContext(request.getContext(), rangerUserStore);
+
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("<== RangerUserStoreEnricher.enrich(" + request + ") with dataStore:[" + dataStore + "])");
+        }
+    }
+
+    public RangerUserStore getRangerUserStore() {return this.rangerUserStore;}
+
+    public void setRangerUserStore(final RangerUserStore rangerUserStore) {
+
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("==> RangerUserStoreEnricher.setRangerUserStore(rangerUserStore=" + rangerUserStore + ")");
+        }
+
+        if (rangerUserStore == null) {
+            LOG.info("UserStore information is null for service " + serviceName);
+            this.rangerUserStore = null;
+        } else  {
+            RangerPerfTracer perf = null;
+
+            if(RangerPerfTracer.isPerfTraceEnabled(PERF_SET_USERSTORE_LOG)) {
+                perf = RangerPerfTracer.getPerfTracer(PERF_SET_USERSTORE_LOG, "RangerUserStoreEnricher.setRangerUserStore(newUserStoreVersion=" + rangerUserStore.getUserStoreVersion() + ")");
+            }
+
+            this.rangerUserStore = rangerUserStore;
+            RangerPerfTracer.logAlways(perf);
+        }
+
+        setRangerUserStoreInPlugin();
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("<== RangerUserStoreEnricher.setRangerUserStore(rangerUserStore=" + rangerUserStore + ")");
+        }
+
+    }
+
+    @Override
+    public boolean preCleanup() {
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("==> RangerUserStoreEnricher.preCleanup()");
+        }
+
+        super.preCleanup();
+
+        if (userStoreDownloadTimer != null) {
+            userStoreDownloadTimer.cancel();
+            userStoreDownloadTimer = null;
+        }
+
+        if (userStoreRefresher != null) {
+            userStoreRefresher.cleanup();
+            userStoreRefresher = null;
+        }
+
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("<== RangerUserStoreEnricher.preCleanup() : result=" + true);
+        }
+        return true;
+    }
+
+    private void setRangerUserStoreInPlugin() {
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("==> setRangerUserStoreInPlugin()");
+        }
+
+        RangerAuthContext authContext = getAuthContext();
+
+        if (authContext != null) {
+            authContext.addOrReplaceRequestContextEnricher(this, rangerUserStore);
+
+            notifyAuthContextChanged();
+        }
+
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("<== setRangerUserStoreInPlugin()");
+        }
+    }
+
+    static class RangerUserStoreRefresher extends Thread {
+        private static final Log LOG = LogFactory.getLog(RangerUserStoreRefresher.class);
+        private static final Log PERF_REFRESHER_INIT_LOG = RangerPerfTracer.getPerfLogger("userstore.init");
+
+        //private final RangerAdminClient adminClient;
+        private final RangerUserStoreRetriever userStoreRetriever;
+        private final RangerUserStoreEnricher userStoreEnricher;
+        private long lastKnownVersion;
+        private final BlockingQueue<DownloadTrigger> userStoreDownloadQueue;
+        private long lastActivationTimeInMillis;
+
+        private final String cacheFile;
+        private boolean hasProvidedUserStoreToReceiver;
+        private Gson gson;
+
+        RangerUserStoreRefresher(RangerUserStoreRetriever userStoreRetriever, RangerUserStoreEnricher userStoreEnricher,
+                                 long lastKnownVersion, BlockingQueue<DownloadTrigger> userStoreDownloadQueue,
+                                 String cacheFile) {
+            this.userStoreRetriever = userStoreRetriever;
+            this.userStoreEnricher = userStoreEnricher;
+            this.lastKnownVersion = lastKnownVersion;
+            this.userStoreDownloadQueue = userStoreDownloadQueue;
+            this.cacheFile = cacheFile;
+            try {
+                gson = new GsonBuilder().setDateFormat("yyyyMMdd-HH:mm:ss.SSS-Z").create();
+            } catch(Throwable excp) {
+                LOG.fatal("failed to create GsonBuilder object", excp);
+            }
+        }
+
+        public long getLastActivationTimeInMillis() {
+            return lastActivationTimeInMillis;
+        }
+
+        public void setLastActivationTimeInMillis(long lastActivationTimeInMillis) {
+            this.lastActivationTimeInMillis = lastActivationTimeInMillis;
+        }
+
+        @Override
+        public void run() {
+
+            if (LOG.isDebugEnabled()) {
+                LOG.debug("==> RangerUserStoreRefresher().run()");
+            }
+
+            while (true) {
+
+                try {
+                    RangerPerfTracer perf = null;
+
+                    if(RangerPerfTracer.isPerfTraceEnabled(PERF_REFRESHER_INIT_LOG)) {
+                        perf = RangerPerfTracer.getPerfTracer(PERF_REFRESHER_INIT_LOG,
+                                "RangerUserStoreRefresher.run(lastKnownVersion=" + lastKnownVersion + ")");
+                    }
+                    DownloadTrigger trigger = userStoreDownloadQueue.take();
+                    populateUserStoreInfo();
+                    trigger.signalCompletion();
+
+                    RangerPerfTracer.log(perf);
+
+                } catch (InterruptedException excp) {
+                    LOG.debug("RangerUserStoreRefresher().run() : interrupted! Exiting thread", excp);
+                    break;
+                }
+            }
+
+            if (LOG.isDebugEnabled()) {
+                LOG.debug("<== RangerUserStoreRefresher().run()");
+            }
+        }
+
+        private void populateUserStoreInfo() throws InterruptedException {
+
+            RangerUserStore rangerUserStore = null;
+            if (userStoreEnricher != null) {
+                try {
+                    rangerUserStore = userStoreRetriever.retrieveUserStoreInfo(lastKnownVersion, lastActivationTimeInMillis);
+
+                    if (rangerUserStore == null) {
+                        if (!hasProvidedUserStoreToReceiver) {
+                            rangerUserStore = loadFromCache();
+                        }
+                    }
+
+                    if (rangerUserStore != null) {
+                        userStoreEnricher.setRangerUserStore(rangerUserStore);
+                        if (rangerUserStore.getUserStoreVersion() != -1L) {
+                            saveToCache(rangerUserStore);
+                        }
+                        LOG.info("RangerUserStoreRefresher.populateUserStoreInfo() - Updated userstore-cache to new version, lastKnownVersion=" + lastKnownVersion + "; newVersion="
+                                + (rangerUserStore.getUserStoreVersion() == null ? -1L : rangerUserStore.getUserStoreVersion()));
+                        hasProvidedUserStoreToReceiver = true;
+                        lastKnownVersion = rangerUserStore.getUserStoreVersion() == null ? -1L : rangerUserStore.getUserStoreVersion();
+                        setLastActivationTimeInMillis(System.currentTimeMillis());
+                    } else {
+                        if (LOG.isDebugEnabled()) {
+                            LOG.debug("RangerUserStoreRefresher.populateUserStoreInfo() - No need to update userstore-cache. lastKnownVersion=" + lastKnownVersion);
+                        }
+                    }
+                } catch (RangerServiceNotFoundException snfe) {
+                    LOG.error("Caught ServiceNotFound exception :", snfe);
+
+                    // Need to clean up local userstore cache
+                    if (userStoreEnricher.disableCacheIfServiceNotFound) {
+                        disableCache();
+                        setLastActivationTimeInMillis(System.currentTimeMillis());
+                        lastKnownVersion = -1L;
+                    }
+                } catch (InterruptedException interruptedException) {
+                    throw interruptedException;
+                } catch (Exception e) {
+                    LOG.error("Encountered unexpected exception. Ignoring", e);
+                }
+            } else {
+                LOG.error("RangerUserStoreRefresher.populateUserStoreInfo() - no userstore receiver to update userstore-cache");
+            }
+        }
+
+        public void cleanup() {
+            if (LOG.isDebugEnabled()) {
+                LOG.debug("==> RangerUserStoreRefresher.cleanup()");
+            }
+
+            stopRefresher();
+
+            if (LOG.isDebugEnabled()) {
+                LOG.debug("<== RangerUserStoreRefresher.cleanup()");
+            }
+        }
+
+        public void startRefresher() {
+            try {
+                super.start();
+            } catch (Exception excp) {
+                LOG.error("RangerUserStoreRefresher.startRetriever() - failed to start, exception=" + excp);
+            }
+        }
+
+        public void stopRefresher() {
+
+            if (super.isAlive()) {
+                super.interrupt();
+
+                try {
+                    super.join();
+                } catch (InterruptedException excp) {
+                    LOG.error("RangerUserStoreRefresher.stopRefresher(): error while waiting for thread to exit", excp);
+                }
+            }
+        }
+
+
+        private RangerUserStore loadFromCache() {
+            RangerUserStore rangerUserStore = null;
+
+            if (LOG.isDebugEnabled()) {
+                LOG.debug("==> RangerUserStoreRefreher.loadFromCache()");
+            }
+
+            File cacheFile = StringUtils.isEmpty(this.cacheFile) ? null : new File(this.cacheFile);
+
+            if (cacheFile != null && cacheFile.isFile() && cacheFile.canRead()) {
+                Reader reader = null;
+
+                try {
+                    reader = new FileReader(cacheFile);
+
+                    rangerUserStore = gson.fromJson(reader, RangerUserStore.class);
+
+                } catch (Exception excp) {
+                    LOG.error("failed to load userstore information from cache file " + cacheFile.getAbsolutePath(), excp);
+                } finally {
+                    if (reader != null) {
+                        try {
+                            reader.close();
+                        } catch (Exception excp) {
+                            LOG.error("error while closing opened cache file " + cacheFile.getAbsolutePath(), excp);
+                        }
+                    }
+                }
+            } else {
+                LOG.warn("cache file does not exist or not readable '" + (cacheFile == null ? null : cacheFile.getAbsolutePath()) + "'");
+            }
+
+            if (LOG.isDebugEnabled()) {
+                LOG.debug("<== RangerUserStoreRefreher.loadFromCache()");
+            }
+
+            return rangerUserStore;
+        }
+
+        public void saveToCache(RangerUserStore rangerUserStore) {
+            if (LOG.isDebugEnabled()) {
+                LOG.debug("==> RangerUserStoreRefreher.saveToCache()");
+            }
+
+            if (rangerUserStore != null) {
+                File cacheFile = StringUtils.isEmpty(this.cacheFile) ? null : new File(this.cacheFile);
+
+                if (cacheFile != null) {
+                    Writer writer = null;
+
+                    try {
+                        writer = new FileWriter(cacheFile);
+
+                        gson.toJson(rangerUserStore, writer);
+                    } catch (Exception excp) {
+                        LOG.error("failed to save userstore information to cache file '" + cacheFile.getAbsolutePath() + "'", excp);
+                    } finally {
+                        if (writer != null) {
+                            try {
+                                writer.close();
+                            } catch (Exception excp) {
+                                LOG.error("error while closing opened cache file '" + cacheFile.getAbsolutePath() + "'", excp);
+                            }
+                        }
+                    }
+                }
+            } else {
+                LOG.info("userstore information is null. Nothing to save in cache");
+            }
+
+            if (LOG.isDebugEnabled()) {
+                LOG.debug("<== RangerUserStoreRefreher.saveToCache()");
+            }
+        }
+
+        private void disableCache() {
+            if (LOG.isDebugEnabled()) {
+                LOG.debug("==> RangerUserStoreRefreher.disableCache()");
+            }
+
+            File cacheFile = StringUtils.isEmpty(this.cacheFile) ? null : new File(this.cacheFile);
+            if (cacheFile != null && cacheFile.isFile() && cacheFile.canRead()) {
+                LOG.warn("Cleaning up local userstore cache");
+                String renamedCacheFile = cacheFile.getAbsolutePath() + "_" + System.currentTimeMillis();
+                if (!cacheFile.renameTo(new File(renamedCacheFile))) {
+                    LOG.error("Failed to move " + cacheFile.getAbsolutePath() + " to " + renamedCacheFile);
+                } else {
+                    LOG.warn("moved " + cacheFile.getAbsolutePath() + " to " + renamedCacheFile);
+                }
+            } else {
+                if (LOG.isDebugEnabled()) {
+                    LOG.debug("No local userstore cache found. No need to disable it!");
+                }
+            }
+
+            if (LOG.isDebugEnabled()) {
+                LOG.debug("<== RangerUserStoreRefreher.disableCache()");
+            }
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/agents-common/src/main/java/org/apache/ranger/plugin/contextenricher/RangerUserStoreRetriever.java b/agents-common/src/main/java/org/apache/ranger/plugin/contextenricher/RangerUserStoreRetriever.java
new file mode 100644
index 0000000..1addbc4
--- /dev/null
+++ b/agents-common/src/main/java/org/apache/ranger/plugin/contextenricher/RangerUserStoreRetriever.java
@@ -0,0 +1,64 @@
+/*
+ * 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.contextenricher;
+
+import org.apache.ranger.authorization.hadoop.config.RangerPluginConfig;
+import org.apache.ranger.plugin.model.RangerServiceDef;
+import org.apache.ranger.plugin.util.RangerUserStore;
+
+import java.util.Map;
+
+public abstract class RangerUserStoreRetriever {
+
+    protected String             serviceName;
+    protected RangerServiceDef   serviceDef;
+    protected String             appId;
+    protected RangerPluginConfig pluginConfig;
+
+    public abstract void init(Map<String, String> options);
+
+    public abstract RangerUserStore retrieveUserStoreInfo(long lastKnownVersion, long lastActivationTimeInMillis) throws Exception;
+
+    public String getServiceName() {
+        return serviceName;
+    }
+
+    public void setServiceName(String serviceName) {
+        this.serviceName = serviceName;
+    }
+
+    public RangerServiceDef getServiceDef() {
+        return serviceDef;
+    }
+
+    public void setServiceDef(RangerServiceDef serviceDef) {
+        this.serviceDef = serviceDef;
+    }
+
+    public String getAppId() {
+        return appId;
+    }
+
+    public void setAppId(String appId) {
+        this.appId = appId;
+    }
+
+    public void setPluginConfig(RangerPluginConfig pluginConfig) { this.pluginConfig = pluginConfig; }
+}
\ No newline at end of file
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 bd980ce..bc52bdb 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
@@ -38,6 +38,7 @@ public class RangerAccessRequestUtil {
 	public static final String KEY_CONTEXT_TAG_OBJECT          = "TAG_OBJECT";
 	public static final String KEY_CONTEXT_RESOURCE            = "RESOURCE";
 	public static final String KEY_CONTEXT_REQUESTED_RESOURCES = "REQUESTED_RESOURCES";
+	public static final String KEY_CONTEXT_USERSTORE           = "USERSTORE";
 	public static final String KEY_TOKEN_NAMESPACE = "token:";
 	public static final String KEY_USER = "USER";
 	public static final String KEY_OWNER = "OWNER";
@@ -158,4 +159,19 @@ public class RangerAccessRequestUtil {
 		Object ret = getTokenFromContext(context, KEY_ROLES);
 		return ret != null ? (Set<String>) ret : Collections.EMPTY_SET;
 	}
+
+	public static void setRequestUserStoreInContext(Map<String, Object> context, RangerUserStore rangerUserStore) {
+		context.put(KEY_CONTEXT_USERSTORE, rangerUserStore);
+	}
+
+	public static RangerUserStore getRequestUserStoreFromContext(Map<String, Object> context) {
+		RangerUserStore ret = null;
+		Object    val = context.get(KEY_CONTEXT_USERSTORE);
+
+		if(val instanceof RangerUserStore) {
+			ret = (RangerUserStore) val;
+		}
+
+		return ret;
+	}
 }
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 0b492ab..3e402aa 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
@@ -76,6 +76,9 @@ 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/";
+
 	private static final int MAX_PLUGIN_ID_LEN = 255;
 	
 	public static final String REST_PARAM_CLUSTER_NAME   = "clusterName";
diff --git a/plugin-solr/src/main/java/org/apache/ranger/authorization/solr/authorizer/FieldToAttributeMapping.java b/plugin-solr/src/main/java/org/apache/ranger/authorization/solr/authorizer/FieldToAttributeMapping.java
new file mode 100644
index 0000000..ba5f7d4
--- /dev/null
+++ b/plugin-solr/src/main/java/org/apache/ranger/authorization/solr/authorizer/FieldToAttributeMapping.java
@@ -0,0 +1,106 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.ranger.authorization.solr.authorizer;
+
+import com.google.common.base.Splitter;
+import com.google.common.collect.Sets;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.regex.Pattern;
+
+/**
+ * Helper class to hold the configuration which maps fields in Solr to one or more
+ * LDAP attributes. Includes details such as whether empty values in the doc should be permitted, whether there is a
+ * default value for all users, whether any RegExs should be applied and any extra options that should be passed
+ * to the fq
+ */
+public class FieldToAttributeMapping {
+
+    private static final Splitter ATTR_NAME_SPLITTER = Splitter.on(',').trimResults().omitEmptyStrings();
+
+    private final String fieldName;
+    private final Collection<String> attributes;
+    private final FilterType filterType;
+    private final boolean acceptEmpty;
+    private final String allUsersValue;
+    private final Pattern attrValueRegex;
+    private final String extraOpts;
+
+    /**
+     * The four filter types currently supported, AND, OR, LessThanOrEqualTo (LTE), GreaterThanOrEqualTo (GTE)
+     * Expected to be expanded in the future
+     */
+    enum FilterType {
+        AND,
+        OR,
+        LTE,
+        GTE
+    }
+
+    /**
+     * @param fieldName The field being mapped
+     * @param ldapAttributeNames comma delimited list of attributes which will be used to acquire values for this field
+     * @param filterType filter type can be any one of {@link FilterType}
+     * @param acceptEmpty true if an empty value in the Solr field should be counted as a match (i.e. doc returned)
+     * @param allUsersValue the value which the field may contain that would indicate that all users should see this doc
+     * @param valueFilterRegex String representation of a {@link Pattern} that will be applied to attributes retrieved
+     *                         from the attribute source. Note: If match groups are used, the last non-null match-group
+     *                         will be applied as the value for this filter
+     * @param extraOpts Any extra options that should be passed to the filter as constructed before appending to the fq
+     */
+    public FieldToAttributeMapping(String fieldName, String ldapAttributeNames, String filterType, boolean acceptEmpty, String allUsersValue, String valueFilterRegex, String extraOpts) {
+        this.fieldName = fieldName;
+        this.attributes = Collections.unmodifiableSet(Sets.newHashSet(ATTR_NAME_SPLITTER.split(ldapAttributeNames)));
+        this.filterType = FilterType.valueOf(filterType);
+        this.acceptEmpty = acceptEmpty;
+        this.allUsersValue = allUsersValue;
+        this.attrValueRegex = Pattern.compile(valueFilterRegex);
+        this.extraOpts = extraOpts;
+    }
+
+    public String getFieldName() {
+        return fieldName;
+    }
+
+    public Collection<String> getAttributes() {
+        return attributes;
+    }
+
+    public FilterType getFilterType() {
+        return filterType;
+    }
+
+    public boolean getAcceptEmpty() {
+        return acceptEmpty;
+    }
+
+    public String getAllUsersValue() {
+        return allUsersValue;
+    }
+
+    public String getExtraOpts() {
+        return extraOpts;
+    }
+
+    public Pattern getAttrValueRegex() {
+    	return attrValueRegex;
+    }
+}
diff --git a/plugin-solr/src/main/java/org/apache/ranger/authorization/solr/authorizer/RangerSolrAuthorizer.java b/plugin-solr/src/main/java/org/apache/ranger/authorization/solr/authorizer/RangerSolrAuthorizer.java
index 4538a5b..30b9861 100644
--- a/plugin-solr/src/main/java/org/apache/ranger/authorization/solr/authorizer/RangerSolrAuthorizer.java
+++ b/plugin-solr/src/main/java/org/apache/ranger/authorization/solr/authorizer/RangerSolrAuthorizer.java
@@ -21,21 +21,30 @@ package org.apache.ranger.authorization.solr.authorizer;
 
 import java.io.IOException;
 import java.security.Principal;
-import java.util.ArrayList;
+import java.util.Map;
 import java.util.Date;
+import java.util.Set;
+import java.util.Iterator;
 import java.util.Enumeration;
+import java.util.Collection;
 import java.util.List;
-import java.util.Map;
-import java.util.Set;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.HashSet;
 
 import com.google.common.base.Joiner;
+import com.google.common.base.Preconditions;
 import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.collections.MapUtils;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 import org.apache.ranger.audit.provider.MiscUtil;
+import org.apache.ranger.plugin.contextenricher.RangerContextEnricher;
+import org.apache.ranger.plugin.contextenricher.RangerUserStoreEnricher;
 import org.apache.ranger.plugin.policyengine.RangerAccessRequestImpl;
 import org.apache.ranger.plugin.policyengine.RangerAccessResourceImpl;
 import org.apache.ranger.plugin.policyengine.RangerAccessResult;
+import org.apache.ranger.plugin.service.RangerAuthContext;
 import org.apache.ranger.plugin.service.RangerBasePlugin;
 import org.apache.ranger.plugin.util.RangerPerfTracer;
 import org.apache.solr.common.SolrException;
@@ -85,6 +94,20 @@ public class RangerSolrAuthorizer extends SearchComponent implements Authorizati
 	public static final String ACCESS_TYPE_OTHERS = "others";
 	public static final String ACCESS_TYPE_ADMIN = "solr_admin";
 
+	public static final String ATTRS_ENABLED_PROP = "attrs_enabled";
+	public static final String FIELD_ATTR_MAPPINGS = "field_attr_mappings";
+	public static final String FIELD_FILTER_TYPE = "filter_type";
+	public static final String ATTR_NAMES = "attr_names";
+	public static final String PERMIT_EMPTY_VALUES = "permit_empty";
+	public static final String ALL_USERS_VALUE = "all_users_value";
+	public static final String ATTRIBUTE_FILTER_REGEX = "value_filter_regex";
+	public static final String AND_OP_QPARSER = "andQParser";
+	public static final String EXTRA_OPTS = "extra_opts";
+
+	private List<FieldToAttributeMapping> fieldAttributeMappings = new LinkedList<>();
+
+	private String andQParserName;
+
 	private static volatile RangerBasePlugin solrPlugin = null;
 
 	private RangerSolrAuditHandler auditHandler = null;
@@ -100,6 +123,7 @@ public class RangerSolrAuthorizer extends SearchComponent implements Authorizati
 	private String tokenCountField;
 	private boolean allowMissingValue;
 	private String qParserName;
+	private boolean attrsEnabled;
 
 	private enum MatchType {
 		DISJUNCTIVE,
@@ -123,9 +147,39 @@ public class RangerSolrAuthorizer extends SearchComponent implements Authorizati
 			this.allowMissingValue = params.getBool(ALLOW_MISSING_VAL_PROP, false);
 			this.tokenCountField = params.get(TOKEN_COUNT_PROP, DEFAULT_TOKEN_COUNT_FIELD_PROP);
 		}
+
+		this.attrsEnabled = params.getBool(ATTRS_ENABLED_PROP, false);
+
 		logger.info("RangerSolrAuthorizer.init(): authField={" + authField + "}, allRolesToken={" + allRolesToken +
 				"}, enabled={" + enabled + "}, matchType={" + matchMode + "}, qParserName={" + qParserName +
-				"}, allowMissingValue={" + allowMissingValue + "}, tokenCountField={" + tokenCountField + "}");
+				"}, allowMissingValue={" + allowMissingValue + "}, tokenCountField={" + tokenCountField + "}, attrsEnabled={" + attrsEnabled + "}");
+
+		if (attrsEnabled) {
+
+			if (params.get(FIELD_ATTR_MAPPINGS) != null) {
+				logger.info("Solr params = " + params.get(FIELD_ATTR_MAPPINGS));
+
+				NamedList mappings = checkAndGet(args, FIELD_ATTR_MAPPINGS);
+
+				Iterator<Map.Entry<String, NamedList>> iter = mappings.iterator();
+				while (iter.hasNext()) {
+					Map.Entry<String, NamedList> entry = iter.next();
+					String solrFieldName = entry.getKey();
+					String attributeNames = checkAndGet(entry.getValue(), ATTR_NAMES);
+					String filterType = checkAndGet(entry.getValue(), FIELD_FILTER_TYPE);
+					boolean acceptEmpty = false;
+					if (entry.getValue().getBooleanArg(PERMIT_EMPTY_VALUES) != null) {
+						acceptEmpty = entry.getValue().getBooleanArg(PERMIT_EMPTY_VALUES);
+					}
+					String allUsersValue = getWithDefault(entry.getValue(), ALL_USERS_VALUE, "");
+					String regex = getWithDefault(entry.getValue(), ATTRIBUTE_FILTER_REGEX, "");
+					String extraOpts = getWithDefault(entry.getValue(), EXTRA_OPTS, "");
+					FieldToAttributeMapping mapping = new FieldToAttributeMapping(solrFieldName, attributeNames, filterType, acceptEmpty, allUsersValue, regex, extraOpts);
+					fieldAttributeMappings.add(mapping);
+				}
+			}
+			this.andQParserName = this.<String>checkAndGet(args, AND_OP_QPARSER).trim();
+		}
 	}
 
 	/*
@@ -313,6 +367,9 @@ public class RangerSolrAuthorizer extends SearchComponent implements Authorizati
 	@Override
 	public void prepare(ResponseBuilder rb) throws IOException {
 		if (!enabled) {
+			if (logger.isDebugEnabled()) {
+				logger.debug("Solr Document level Authorization is not enabled!");
+			}
 			return;
 		}
 
@@ -321,24 +378,53 @@ public class RangerSolrAuthorizer extends SearchComponent implements Authorizati
 			return;
 		}
 
-		Set<String> roles = getRolesForUser(userName);
-		if (roles != null && !roles.isEmpty()) {
-			String filterQuery;
-			if (matchMode == MatchType.DISJUNCTIVE) {
-				filterQuery = getDisjunctiveFilterQueryStr(roles);
-			} else {
-				filterQuery = getConjunctiveFilterQueryStr(roles);
-			}
-			ModifiableSolrParams newParams = new ModifiableSolrParams(rb.req.getParams());
-			newParams.add("fq", filterQuery);
-			rb.req.setParams(newParams);
+		if (attrsEnabled) {
 			if (logger.isDebugEnabled()) {
-				logger.debug("Adding filter query {" + filterQuery + "} for user {" + userName + "} with roles {" + roles + "}");
+				logger.debug("Checking Ldap attributes to be added to the query filter");
+			}
+			if (getUserStoreEnricher() == null || getUserStoreEnricher().getRangerUserStore() == null) {
+				logger.error("No User store enricher to read the ldap attributes");
+				return;
 			}
+			// Ranger UserStore info for user/group attributes
+			Map<String, Map<String, String>> userAttrMapping = getUserStoreEnricher().getRangerUserStore().getUserAttrMapping();
+			if (MapUtils.isNotEmpty(userAttrMapping)) {
+				ModifiableSolrParams newParams = new ModifiableSolrParams(rb.req.getParams());
+				Map<String, String> userAttributes = userAttrMapping.get(userName);
+				for (FieldToAttributeMapping mapping : fieldAttributeMappings) {
+					String filterQuery = buildFilterQueryString(userName, userAttributes, mapping);
+					if (logger.isDebugEnabled()) {
+						logger.debug("Adding filter clause : {}" + filterQuery);
+					}
+					newParams.add("fq", filterQuery);
+				}
 
+				rb.req.setParams(newParams);
+			}
 		} else {
-			throw new SolrException(SolrException.ErrorCode.UNAUTHORIZED,
-					"Request from user: " + userName + " rejected because user is not associated with any roles");
+			if (logger.isDebugEnabled()) {
+				logger.debug("Checking User roles to be added to the query filter");
+			}
+
+			Set<String> roles = getRolesForUser(userName);
+			if (roles != null && !roles.isEmpty()) {
+				String filterQuery;
+				if (matchMode == MatchType.DISJUNCTIVE) {
+					filterQuery = getDisjunctiveFilterQueryStr(roles);
+				} else {
+					filterQuery = getConjunctiveFilterQueryStr(roles);
+				}
+				ModifiableSolrParams newParams = new ModifiableSolrParams(rb.req.getParams());
+				newParams.add("fq", filterQuery);
+				rb.req.setParams(newParams);
+				if (logger.isDebugEnabled()) {
+					logger.debug("Adding filter query {" + filterQuery + "} for user {" + userName + "} with roles {" + roles + "}");
+				}
+
+			} else {
+				throw new SolrException(SolrException.ErrorCode.UNAUTHORIZED,
+						"Request from user: " + userName + " rejected because user is not associated with any roles");
+			}
 		}
 	}
 
@@ -596,4 +682,147 @@ public class RangerSolrAuthorizer extends SearchComponent implements Authorizati
 
 		return userName;
 	}
+
+	private RangerUserStoreEnricher getUserStoreEnricher() {
+		RangerUserStoreEnricher ret         = null;
+		RangerAuthContext authContext = solrPlugin.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;
+
+						break;
+					}
+				}
+			}
+		}
+		return ret;
+	}
+
+	private <T> T checkAndGet(NamedList args, String key) {
+		logger.info("checkAndGet() " + key);
+		return (T) Preconditions.checkNotNull(args.get(key));
+	}
+
+	private <T> T getWithDefault(NamedList args, String key, T defaultValue) {
+		T value = (T) args.get(key);
+		if (value == null) {
+			return defaultValue;
+		} else {
+			return value;
+		}
+	}
+
+	private String buildFilterQueryString(String userName, Map<String, String> userAttributes, FieldToAttributeMapping mapping) {
+		String fieldName = mapping.getFieldName();
+		Collection<String> attributeValues = getUserAttributesForField(userName, userAttributes, mapping);
+		switch (mapping.getFilterType()) {
+			case OR:
+				return buildSimpleORFilterQuery(fieldName, attributeValues, mapping.getAcceptEmpty(), mapping.getAllUsersValue(), mapping.getExtraOpts());
+			case AND:
+				return buildSubsetFilterQuery(fieldName, attributeValues, mapping.getAcceptEmpty(), mapping.getAllUsersValue(), mapping.getExtraOpts());
+			case GTE:
+				return buildGreaterThanFilterQuery(fieldName, attributeValues, mapping.getAcceptEmpty(), mapping.getAllUsersValue(), mapping.getExtraOpts());
+			case LTE:
+				return buildLessThanFilterQuery(fieldName, attributeValues, mapping.getAcceptEmpty(), mapping.getAllUsersValue(), mapping.getExtraOpts());
+			default:
+				return null;
+		}
+	}
+
+	private Collection<String> getUserAttributesForField(String userName, Map<String, String> userAttributes, FieldToAttributeMapping mapping) {
+		Set<String> userAttributesSubset = new HashSet<>();
+		if (CollectionUtils.isNotEmpty(mapping.getAttributes())) {
+			if (mapping.getAttributes().contains("groups")) {
+				userAttributesSubset.addAll(getGroupsForUser(userName));
+			}
+		}
+		for (String attributeName : mapping.getAttributes()) {
+			userAttributesSubset.add(userAttributes.get(attributeName));
+		}
+		return userAttributesSubset;
+	}
+
+	private String buildSimpleORFilterQuery(String fieldName, Collection<String> attributeValues, boolean allowEmptyField, String allUsersValue, String extraOpts) {
+		StringBuilder s = new StringBuilder();
+		for (String attributeValue : attributeValues) {
+			s.append(fieldName).append(":\"").append(attributeValue).append("\" ");
+		}
+		if (allUsersValue != null && !allUsersValue.equals("")) {
+			s.append(fieldName).append(":\"").append(allUsersValue).append("\" ");
+		}
+		if (allowEmptyField) {
+			s.append("(*:* AND -").append(fieldName).append(":*) ");
+		}
+		if (extraOpts != null && !extraOpts.equals("")) {
+			s.append(extraOpts + " ");
+		}
+		s.deleteCharAt(s.length() - 1);
+		return s.toString();
+	}
+
+	private String buildSubsetFilterQuery(String fieldName, Collection<String> attributeValues, boolean allowEmptyField, String allUsersValue, String extraOpts) {
+		StringBuilder s = new StringBuilder();
+		s.append("{!").append(andQParserName)
+				.append(" set_field=").append(fieldName)
+				.append(" set_value=").append(Joiner.on(',').join(attributeValues));
+		if (allUsersValue != null && !allUsersValue.equals("")) {
+			s.append(" wildcard_token=").append(allUsersValue);
+		}
+		if (allowEmptyField) {
+			s.append(" allow_missing_val=true");
+		} else {
+			s.append(" allow_missing_val=false");
+		}
+		if (extraOpts != null && !extraOpts.equals("")) {
+			s.append(" " + extraOpts);
+		}
+		s.append("}");
+		return s.toString();
+	}
+
+	private String buildGreaterThanFilterQuery(String fieldName, Collection<String> attributeValues, boolean allowEmptyField, String allUsersValue, String extraOpts) {
+		String value;
+		if (attributeValues.size() == 1) {
+			value = attributeValues.iterator().next();
+		} else if (allUsersValue != null && !allUsersValue.equals("")) {
+			value = allUsersValue;
+		} else {
+			throw new IllegalArgumentException("Greater Than Filter Query cannot be built for field " + fieldName);
+		}
+		StringBuilder extraClause = new StringBuilder();
+		if (allowEmptyField) {
+			extraClause.append(" (*:* AND -").append(fieldName).append(":*)");
+		}
+		if (extraOpts != null && !extraOpts.equals("")) {
+			extraClause.append(" ").append(extraOpts);
+		}
+		return fieldName + ":[" + value + " TO *]" + extraClause.toString();
+	}
+
+	private String buildLessThanFilterQuery(String fieldName, Collection<String> attributeValues, boolean allowEmptyField, String allUsersValue, String extraOpts) {
+		String value;
+		if (attributeValues.size() == 1) {
+			value = attributeValues.iterator().next();
+		} else if (allUsersValue != null && !allUsersValue.equals("")) {
+			value = allUsersValue;
+		} else {
+			throw new IllegalArgumentException("Less Than Filter Query cannot be built for field " + fieldName);
+		}
+		StringBuilder extraClause = new StringBuilder();
+		if (allowEmptyField) {
+			extraClause.append(" (*:* AND -").append(fieldName).append(":*)");
+		}
+		if (extraOpts != null && !extraOpts.equals("")) {
+			extraClause.append(" ").append(extraOpts);
+		}
+		return fieldName + ":[* TO " + value + "]" + extraClause.toString();
+	}
+
 }
diff --git a/plugin-solr/src/main/java/org/apache/ranger/authorization/solr/authorizer/SubsetQueryPlugin.java b/plugin-solr/src/main/java/org/apache/ranger/authorization/solr/authorizer/SubsetQueryPlugin.java
new file mode 100644
index 0000000..1ea4d28
--- /dev/null
+++ b/plugin-solr/src/main/java/org/apache/ranger/authorization/solr/authorizer/SubsetQueryPlugin.java
@@ -0,0 +1,97 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.ranger.authorization.solr.authorizer;
+
+import com.google.common.base.Preconditions;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.search.BooleanClause;
+import org.apache.lucene.search.BooleanQuery;
+import org.apache.lucene.search.CoveringQuery;
+import org.apache.lucene.search.LongValuesSource;
+import org.apache.lucene.search.MatchAllDocsQuery;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.TermQuery;
+import org.apache.lucene.search.WildcardQuery;
+import org.apache.solr.common.params.SolrParams;
+import org.apache.solr.common.util.NamedList;
+import org.apache.solr.request.SolrQueryRequest;
+import org.apache.solr.search.QParser;
+import org.apache.solr.search.QParserPlugin;
+import org.apache.solr.search.SyntaxError;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+/**
+ * A custom {@linkplain QParserPlugin} which supports subset queries on a given Solr index.
+ * This filter accepts the name of the field whose value should be used for subset matching
+ * and the set against which subset queries are to be run ( as a comma separated string values).
+ */
+public class SubsetQueryPlugin extends QParserPlugin {
+    public static final String SETVAL_PARAM_NAME = "set_value";
+    public static final String SETVAL_FIELD_NAME = "set_field";
+    public static final String COUNT_FIELD_NAME = "count_field";
+    public static final String MISSING_VAL_ALLOWED = "allow_missing_val";
+    public static final String WILDCARD_CHAR = "wildcard_token";
+
+    @SuppressWarnings("rawtypes")
+    @Override
+    public void init(NamedList arg0) {
+    }
+
+    @Override
+    public QParser createParser(String qstr, SolrParams localParams, SolrParams params, SolrQueryRequest req) {
+        return new QParser(qstr, localParams, params, req) {
+
+            @Override
+            public Query parse() throws SyntaxError {
+                String fieldName = Preconditions.checkNotNull(localParams.get(SETVAL_FIELD_NAME));
+                String countFieldName = Preconditions.checkNotNull(localParams.get(COUNT_FIELD_NAME));
+                boolean allowMissingValues = Boolean.parseBoolean(Preconditions.checkNotNull(localParams.get(MISSING_VAL_ALLOWED)));
+                String wildcardToken = localParams.get(WILDCARD_CHAR);
+
+                LongValuesSource minimumNumberMatch = LongValuesSource.fromIntField(countFieldName);
+                Collection<Query> queries = new ArrayList<>();
+
+                String fieldVals = Preconditions.checkNotNull(localParams.get(SETVAL_PARAM_NAME));
+                for (String v : fieldVals.split(",")) {
+                    queries.add(new TermQuery(new Term(fieldName, v)));
+                }
+                if (wildcardToken != null && !wildcardToken.equals("")) {
+                    queries.add(new TermQuery(new Term(fieldName, wildcardToken)));
+                }
+                if (allowMissingValues) {
+                    // To construct this query we need to do a little trick tho construct a test for an empty field as follows:
+                    // (*:* AND -fieldName:*) ==> parses as: (+*:* -fieldName:*)
+                    // It is a feature of Lucene that pure negative queries are not allowed (although Solr allows them as a top level construct)
+                    // therefore we need to AND with *:*
+                    // We can then pass this BooleanQuery to the CoveringQuery as one of its allowed matches.
+                    BooleanQuery.Builder builder = new BooleanQuery.Builder();
+                    builder.add(new BooleanClause(new MatchAllDocsQuery(), BooleanClause.Occur.SHOULD));
+                    builder.add(new BooleanClause(new WildcardQuery(new Term(fieldName, "*")), BooleanClause.Occur.MUST_NOT));
+
+                    queries.add(builder.build());
+                }
+                return new CoveringQuery(queries, minimumNumberMatch);
+            }
+        };
+    }
+
+}