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);
+ }
+ };
+ }
+
+}