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 <[email protected]>
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);
+            }
+        };
+    }
+
+}

Reply via email to