SOLR-7275: Authorization framework for Solr. It defines an interface and a mechanism to create, load and use an Authorization plugin.

git-svn-id: https://svn.apache.org/repos/asf/lucene/dev/trunk@1679316 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Anshum Gupta 2015-05-14 06:20:04 +00:00
parent dcb19cd007
commit acb40f6225
11 changed files with 536 additions and 50 deletions

View File

@ -180,6 +180,8 @@ New Features
* SOLR-7522: Facet Module - Implement field/terms faceting over single-valued
numeric fields. (yonik)
* SOLR-7275: Authorization framework for Solr. It defines an interface and a mechanism to create,
load, and use an Authorization plugin. (Noble Paul, Ishan Chattopadhyaya, Anshum Gupta)
Bug Fixes
----------------------

View File

@ -17,9 +17,8 @@
package org.apache.solr.core;
import static com.google.common.base.Preconditions.*;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@ -30,8 +29,9 @@ import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import org.apache.solr.cloud.ZkController;
import org.apache.solr.cloud.ZkSolrResourceLoader;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.SolrException.ErrorCode;
import org.apache.solr.common.util.ExecutorUtil;
@ -43,6 +43,7 @@ import org.apache.solr.handler.admin.InfoHandler;
import org.apache.solr.handler.component.ShardHandlerFactory;
import org.apache.solr.logging.LogWatcher;
import org.apache.solr.request.SolrRequestHandler;
import org.apache.solr.security.AuthorizationPlugin;
import org.apache.solr.update.UpdateShardHandler;
import org.apache.solr.util.DefaultSolrThreadFactory;
import org.apache.solr.util.FileUtils;
@ -50,8 +51,7 @@ import org.apache.zookeeper.KeeperException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import static com.google.common.base.Preconditions.checkNotNull;
/**
@ -64,6 +64,8 @@ public class CoreContainer {
final SolrCores solrCores = new SolrCores(this);
protected AuthorizationPlugin authorizationPlugin;
public static class CoreLoadFailure {
public final CoreDescriptor cd;
@ -176,6 +178,27 @@ public class CoreContainer {
this.containerProperties = new Properties(properties);
}
private void intializeAuthorizationPlugin() {
//Initialize the Authorization module
Map securityProps = getZkController().getZkStateReader().getSecurityProps();
if(securityProps != null) {
Map authorizationConf = (Map) securityProps.get("authorization");
if(authorizationConf == null) return;
String klas = (String) authorizationConf.get("class");
if(klas == null){
throw new SolrException(ErrorCode.SERVER_ERROR, "class is required for authorization plugin");
}
log.info("Initializing authorization plugin: " + klas);
authorizationPlugin = getResourceLoader().newInstance((String) klas,
AuthorizationPlugin.class);
// Read and pass the authorization context to the plugin
authorizationPlugin.init(authorizationConf);
} else {
log.info("Security conf doesn't exist. Skipping setup for authorization module.");
}
}
/**
* This method allows subclasses to construct a CoreContainer
* without any default init behavior.
@ -246,6 +269,9 @@ public class CoreContainer {
log.info("Node Name: " + hostName);
zkSys.initZooKeeper(this, solrHome, cfg.getCloudConfig());
if (isZooKeeperAware()) {
intializeAuthorizationPlugin();
}
collectionsHandler = createHandler(cfg.getCollectionsHandlerClass(), CollectionsHandler.class);
containerHandlers.put(COLLECTIONS_HANDLER_PATH, collectionsHandler);
@ -396,6 +422,16 @@ public class CoreContainer {
}
}
}
// It should be safe to close the authorization plugin at this point.
try {
if(authorizationPlugin != null) {
authorizationPlugin.close();
}
} catch (IOException e) {
log.warn("Exception while closing authorization plugin.", e);
}
org.apache.lucene.util.IOUtils.closeWhileHandlingException(loader); // best effort
}
@ -448,7 +484,7 @@ public class CoreContainer {
solrCores.putDynamicDescriptor(name, cd);
}
SolrCore old = null;
SolrCore old;
if (isShutDown) {
core.close();
@ -640,7 +676,7 @@ public class CoreContainer {
coresLocator.swap(this, solrCores.getCoreDescriptor(n0), solrCores.getCoreDescriptor(n1));
log.info("swapped: "+n0 + " with " + n1);
log.info("swapped: " + n0 + " with " + n1);
}
/**
@ -689,8 +725,7 @@ public class CoreContainer {
// cancel recovery in cloud mode
core.getSolrCoreState().cancelRecovery();
}
String configSetZkPath = core.getResourceLoader() instanceof ZkSolrResourceLoader ? ((ZkSolrResourceLoader)core.getResourceLoader()).getConfigSetZkPath() : null;
core.unloadOnClose(deleteIndexDir, deleteDataDir, deleteInstanceDir);
if (close)
core.close();
@ -705,7 +740,6 @@ public class CoreContainer {
throw new SolrException(ErrorCode.SERVER_ERROR, "Error unregistering core [" + name + "] from cloud state", e);
}
}
}
public void rename(String name, String toName) {
@ -885,6 +919,11 @@ public class CoreContainer {
public SolrResourceLoader getResourceLoader() {
return loader;
}
public AuthorizationPlugin getAuthorizationPlugin() {
return authorizationPlugin;
}
}
class CloserThread extends Thread {

View File

@ -17,6 +17,37 @@
package org.apache.solr.core;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.naming.NoInitialContextException;
import java.io.Closeable;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Constructor;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.lucene.analysis.util.CharFilterFactory;
import org.apache.lucene.analysis.util.ResourceLoader;
import org.apache.lucene.analysis.util.ResourceLoaderAware;
@ -44,38 +75,6 @@ import org.apache.solr.util.plugin.SolrCoreAware;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.naming.NoInitialContextException;
import java.io.Closeable;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.lang.reflect.Constructor;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @since solr 1.3
*/
@ -88,7 +87,7 @@ public class SolrResourceLoader implements ResourceLoader,Closeable
static final String[] packages = {
"", "analysis.", "schema.", "handler.", "search.", "update.", "core.", "response.", "request.",
"update.processor.", "util.", "spelling.", "handler.component.", "handler.dataimport.",
"spelling.suggest.", "spelling.suggest.fst.", "rest.schema.analysis."
"spelling.suggest.", "spelling.suggest.fst.", "rest.schema.analysis.", "security."
};
protected URLClassLoader classLoader;

View File

@ -0,0 +1,55 @@
package org.apache.solr.security;
/*
* 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.
*/
import java.security.Principal;
import java.util.Enumeration;
import java.util.List;
import org.apache.solr.common.params.SolrParams;
/**
* Request context for Solr to be used by Authorization plugin.
*/
public abstract class AuthorizationContext {
public static class CollectionRequest {
final public String collectionName;
public CollectionRequest(String collectionName) {
this.collectionName = collectionName;
}
}
public abstract SolrParams getParams() ;
public abstract Principal getUserPrincipal() ;
public abstract String getHttpHeader(String header);
public abstract Enumeration getHeaderNames();
public abstract List<CollectionRequest> getCollectionRequests() ;
public abstract RequestType getRequestType();
public abstract String getResource();
public enum RequestType {READ, WRITE, ADMIN, UNKNOWN}
}

View File

@ -0,0 +1,31 @@
package org.apache.solr.security;
/*
* 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.
*/
import java.io.Closeable;
import java.util.Map;
/**
* Authorization interface that needs to be implemented to write an authorization
* plugin.
*/
public interface AuthorizationPlugin extends Closeable {
AuthorizationResponse authorize(AuthorizationContext context);
void init(Map<String, Object> initInfo);
}

View File

@ -0,0 +1,38 @@
package org.apache.solr.security;
/*
* 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.
*/
/* This class currently only stores an int statusCode (HttpStatus) value and a message but can
be used to return ACLs and other information from the authorization plugin.
*/
public class AuthorizationResponse {
public final int statusCode;
String message;
public AuthorizationResponse(int httpStatusCode) {
this.statusCode = httpStatusCode;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}

View File

@ -0,0 +1,22 @@
/*
* 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.
*/
/**
* Commonly used classes for Solr security framework.
*/
package org.apache.solr.security;

View File

@ -24,6 +24,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.security.Principal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@ -42,6 +43,7 @@ import org.apache.http.HeaderIterator;
import org.apache.http.HttpEntity;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
import org.apache.http.client.methods.HttpGet;
@ -79,16 +81,26 @@ import org.apache.solr.request.SolrRequestInfo;
import org.apache.solr.response.QueryResponseWriter;
import org.apache.solr.response.QueryResponseWriterUtil;
import org.apache.solr.response.SolrQueryResponse;
import org.apache.solr.security.AuthorizationContext;
import org.apache.solr.security.AuthorizationContext.CollectionRequest;
import org.apache.solr.security.AuthorizationContext.RequestType;
import org.apache.solr.security.AuthorizationResponse;
import org.apache.solr.servlet.cache.HttpCacheHeaderUtil;
import org.apache.solr.servlet.cache.Method;
import org.apache.solr.update.processor.DistributingUpdateProcessorFactory;
import org.apache.solr.util.RTimer;
import org.apache.zookeeper.KeeperException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import static org.apache.solr.common.cloud.ZkStateReader.BASE_URL_PROP;
import static org.apache.solr.common.cloud.ZkStateReader.COLLECTION_PROP;
import static org.apache.solr.common.cloud.ZkStateReader.CORE_NAME_PROP;
import static org.apache.solr.common.cloud.ZkStateReader.NODE_NAME_PROP;
import static org.apache.solr.common.params.CollectionParams.CollectionAction.CREATE;
import static org.apache.solr.common.params.CollectionParams.CollectionAction.DELETE;
import static org.apache.solr.common.params.CollectionParams.CollectionAction.RELOAD;
import static org.apache.solr.servlet.SolrDispatchFilter.Action;
import static org.apache.solr.servlet.SolrDispatchFilter.Action.ADMIN;
import static org.apache.solr.servlet.SolrDispatchFilter.Action.FORWARD;
@ -101,7 +113,9 @@ import static org.apache.solr.servlet.SolrDispatchFilter.Action.RETURN;
/**
* This class represents a call made to Solr
**/
class HttpSolrCall {
public class HttpSolrCall {
private static Logger log = LoggerFactory.getLogger(HttpSolrCall.class);
private final SolrDispatchFilter solrDispatchFilter;
private final CoreContainer cores;
private final HttpServletRequest req;
@ -117,6 +131,19 @@ class HttpSolrCall {
private SolrConfig config;
private Map<String, Integer> invalidStates;
public RequestType getRequestType() {
return requestType;
}
private RequestType requestType;
public List<String> getCollectionsList() {
return collectionsList;
}
private List<String> collectionsList;
HttpSolrCall(SolrDispatchFilter solrDispatchFilter, CoreContainer cores,
HttpServletRequest request, HttpServletResponse response, boolean retry) {
this.solrDispatchFilter = solrDispatchFilter;
@ -124,6 +151,7 @@ class HttpSolrCall {
this.req = request;
this.response = response;
this.retry = retry;
this.requestType = RequestType.UNKNOWN;
queryParams = SolrRequestParsers.parseQueryString(req.getQueryString());
}
@ -131,7 +159,20 @@ class HttpSolrCall {
return path;
}
private void setContext() throws Exception {
public HttpServletRequest getReq() {
return req;
}
public SolrCore getCore() {
return core;
}
public SolrParams getQueryParams() {
return queryParams;
}
private void init() throws Exception {
//The states of client that is invalid in this request
Aliases aliases = null;
String corename = "";
@ -158,13 +199,13 @@ class HttpSolrCall {
}
boolean usingAliases = false;
List<String> collectionsList = null;
// Check for container handlers
handler = cores.getRequestHandler(path);
if (handler != null) {
solrReq = SolrRequestParsers.DEFAULT.parse(null, path, req);
solrReq.getContext().put(CoreContainer.class.getName(), cores);
requestType = RequestType.ADMIN;
action = ADMIN;
return;
} else {
@ -210,6 +251,9 @@ class HttpSolrCall {
// we found a core, update the path
path = path.substring(idx);
addMDCValues();
if (collectionsList == null)
collectionsList = new ArrayList<>();
collectionsList.add(corename);
}
// if we couldn't find it locally, look on other nodes
@ -351,8 +395,28 @@ class HttpSolrCall {
}
try {
setContext();
init();
/* Authorize the request if
1. Authorization is enabled, and
2. The requested resource is not a known static file
*/
// TODO: There should be a better way to ignore the static files.
if (cores.getAuthorizationPlugin() != null &&
!(req.getRequestURI().endsWith(".html")
|| req.getRequestURI().endsWith(".png")
|| req.getRequestURI().endsWith(".ico")
|| req.getRequestURI().endsWith(".css")
)) {
AuthorizationContext context = getAuthCtx();
log.info(context.toString());
AuthorizationResponse authResponse = cores.getAuthorizationPlugin().authorize(context);
if (!(authResponse.statusCode == HttpStatus.SC_ACCEPTED) && !(authResponse.statusCode == HttpStatus.SC_OK)) {
sendError(authResponse.statusCode,
"Unauthorized request, Response code: " + authResponse.statusCode);
return RETURN;
}
}
HttpServletResponse resp = response;
switch (action) {
case ADMIN:
@ -593,7 +657,7 @@ class HttpSolrCall {
private void processAliases(Aliases aliases,
List<String> collectionsList) {
String collection = solrReq.getParams().get("collection");
String collection = solrReq.getParams().get(COLLECTION_PROP);
if (collection != null) {
collectionsList = StrUtils.splitSmart(collection, ",", true);
}
@ -621,7 +685,7 @@ class HttpSolrCall {
}
ModifiableSolrParams params = new ModifiableSolrParams(
solrReq.getParams());
params.set("collection", collectionString.toString());
params.set(COLLECTION_PROP, collectionString.toString());
solrReq.setParams(params);
}
}
@ -760,6 +824,10 @@ class HttpSolrCall {
return null;
}
if (collectionsList == null)
collectionsList = new ArrayList<>();
collectionsList.add(collectionName);
String coreUrl = getCoreUrl(collectionName, origCorename, clusterState,
slices, byCoreName, true);
@ -807,6 +875,105 @@ class HttpSolrCall {
return null;
}
private AuthorizationContext getAuthCtx() {
String resource = getPath();
SolrParams params = getQueryParams();
final ArrayList<CollectionRequest> collectionRequests = new ArrayList<>();
if (getCollectionsList() != null) {
for (String collection : getCollectionsList()) {
collectionRequests.add(new CollectionRequest(collection));
}
}
// Extract collection name from the params in case of a Collection Admin request
if (getPath().equals("/admin/collections")) {
if (CREATE.isEqual(params.get("action"))||
RELOAD.isEqual(params.get("action"))||
DELETE.isEqual(params.get("action")))
collectionRequests.add(new CollectionRequest(params.get("name")));
else if (params.get(COLLECTION_PROP) != null)
collectionRequests.add(new CollectionRequest(params.get(COLLECTION_PROP)));
}
// Handle the case when it's a /select request and collections are specified as a param
if(resource.equals("/select") && params.get("collection") != null) {
collectionRequests.clear();
for(String collection:params.get("collection").split(",")) {
collectionRequests.add(new CollectionRequest(collection));
}
}
// Populate the request type if the request is select or update
if(requestType == RequestType.UNKNOWN) {
if(resource.startsWith("/select"))
requestType = RequestType.READ;
if(resource.startsWith("/update"))
requestType = RequestType.WRITE;
}
// There's no collection explicitly mentioned, let's try and extract it from the core if one exists for
// the purpose of processing this request.
if (getCore() != null && (getCollectionsList() == null || getCollectionsList().size() == 0)) {
collectionRequests.add(new CollectionRequest(getCore().getCoreDescriptor().getCollectionName()));
}
if (getQueryParams().get(COLLECTION_PROP) != null)
collectionRequests.add(new CollectionRequest(getQueryParams().get(COLLECTION_PROP)));
return new AuthorizationContext() {
@Override
public SolrParams getParams() {
return getQueryParams();
}
@Override
public Principal getUserPrincipal() {
return getReq().getUserPrincipal();
}
@Override
public String getHttpHeader(String s) {
return getReq().getHeader(s);
}
@Override
public Enumeration getHeaderNames() {
return getReq().getHeaderNames();
}
@Override
public List<CollectionRequest> getCollectionRequests() {
return collectionRequests;
}
@Override
public RequestType getRequestType() {
return requestType;
}
public String getResource() {
return path;
}
@Override
public String toString() {
StringBuilder response = new StringBuilder("userPrincipal: [").append(getUserPrincipal()).append("]")
.append(" type: [").append(requestType.toString()).append("], collections: [");
for (CollectionRequest collectionRequest : collectionRequests) {
response.append(collectionRequest.collectionName).append(", ");
}
if(collectionRequests.size() > 0)
response.delete(response.length() - 1, response.length());
response.append("], Path: [").append(resource).append("]");
return response.toString();
}
};
}
static final String CONNECTION_HEADER = "Connection";
static final String TRANSFER_ENCODING_HEADER = "Transfer-Encoding";
static final String CONTENT_LENGTH_HEADER = "Content-Length";

View File

@ -0,0 +1,52 @@
package org.apache.solr.security;
/*
* 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.
*/
import java.io.IOException;
import java.util.HashSet;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class MockAuthorizationPlugin implements AuthorizationPlugin{
private Logger log = LoggerFactory.getLogger(MockAuthorizationPlugin.class);
HashSet<String> denyUsers;
@Override
public AuthorizationResponse authorize(AuthorizationContext context) {
log.info("User request: " + context.getParams().get("uname"));
if(denyUsers.contains(context.getParams().get("uname")))
return new AuthorizationResponse(403);
else
return new AuthorizationResponse(200);
}
@Override
public void init(Map<String, Object> initInfo) {
denyUsers = new HashSet();
denyUsers.add("user1");
denyUsers.add("user2");
}
@Override
public void close() throws IOException {
}
}

View File

@ -0,0 +1,63 @@
package org.apache.solr.security;
/*
* 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.
*/
import org.apache.commons.io.Charsets;
import org.apache.lucene.util.LuceneTestCase;
import org.apache.solr.cloud.AbstractFullDistribZkTestBase;
import org.apache.solr.common.cloud.ZkStateReader;
import org.apache.solr.common.params.ModifiableSolrParams;
import org.apache.zookeeper.CreateMode;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@LuceneTestCase.Slow
public class TestAuthorizationFramework extends AbstractFullDistribZkTestBase {
final private Logger log = LoggerFactory.getLogger(TestAuthorizationFramework.class);
static final int TIMEOUT = 10000;
public void distribSetUp() throws Exception {
super.distribSetUp();
try (ZkStateReader zkStateReader = new ZkStateReader(zkServer.getZkAddress(),
TIMEOUT, TIMEOUT)) {
zkStateReader.getZkClient().create(ZkStateReader.SOLR_SECURITY_CONF_PATH,
"{\"authorization\":{\"class\":\"org.apache.solr.security.MockAuthorizationPlugin\"}}".getBytes(Charsets.UTF_8),
CreateMode.PERSISTENT, true);
}
}
@Test
public void authorizationFrameworkTest() throws Exception {
waitForThingsToLevelOut(10);
log.info("Starting test");
ModifiableSolrParams params = new ModifiableSolrParams();
params.add("q", "*:*");
// This should work fine.
cloudClient.query(params);
// This user is blacklisted in the mock. The request should return a 403.
params.add("uname", "user1");
try {
cloudClient.query(params);
fail("This should have failed");
} catch (Exception e) {}
log.info("Ending test");
}
}

View File

@ -84,6 +84,7 @@ public class ZkStateReader implements Closeable {
public static final String CLUSTER_STATE = "/clusterstate.json";
public static final String CLUSTER_PROPS = "/clusterprops.json";
public static final String REJOIN_AT_HEAD_PROP = "rejoinAtHead";
public static final String SOLR_SECURITY_CONF_PATH = "/security.json";
public static final String REPLICATION_FACTOR = "replicationFactor";
public static final String MAX_SHARDS_PER_NODE = "maxShardsPerNode";
@ -830,6 +831,23 @@ public class ZkStateReader implements Closeable {
}
}
/**
* Returns the content of /security.json from ZooKeeper as a Map
* If the files doesn't exist, it returns null.
*/
public Map getSecurityProps() {
try {
if(getZkClient().exists(SOLR_SECURITY_CONF_PATH, true)) {
return (Map) ZkStateReader.fromJSON(getZkClient()
.getData(ZkStateReader.SOLR_SECURITY_CONF_PATH, null, new Stat(), true)) ;
}
} catch (KeeperException | InterruptedException e) {
throw new SolrException(ErrorCode.SERVER_ERROR,"Error reading security properties",e) ;
}
return null;
}
/**
* Returns the baseURL corresponding to a given node's nodeName --
* NOTE: does not (currently) imply that the nodeName (or resulting