mirror of https://github.com/apache/lucene.git
Merge remote-tracking branch 'origin/master'
This commit is contained in:
commit
ecfbe51c94
|
@ -28,6 +28,7 @@
|
||||||
</orderEntry>
|
</orderEntry>
|
||||||
<orderEntry type="module" scope="TEST" module-name="lucene-test-framework" />
|
<orderEntry type="module" scope="TEST" module-name="lucene-test-framework" />
|
||||||
<orderEntry type="module" scope="TEST" module-name="solr-test-framework" />
|
<orderEntry type="module" scope="TEST" module-name="solr-test-framework" />
|
||||||
|
<orderEntry type="module" module-name="lucene-core" />
|
||||||
<orderEntry type="module" module-name="solr-core" />
|
<orderEntry type="module" module-name="solr-core" />
|
||||||
<orderEntry type="module" module-name="solrj" />
|
<orderEntry type="module" module-name="solrj" />
|
||||||
</component>
|
</component>
|
||||||
|
|
|
@ -160,6 +160,12 @@ New Features
|
||||||
* SOLR-9654: Add "overrequest" parameter to JSON Facet API to control amount of overrequest
|
* SOLR-9654: Add "overrequest" parameter to JSON Facet API to control amount of overrequest
|
||||||
on a distributed terms facet. (yonik)
|
on a distributed terms facet. (yonik)
|
||||||
|
|
||||||
|
* SOLR-9481: Authentication and Authorization plugins now work in standalone mode if security.json is placed in
|
||||||
|
SOLR_HOME on every node. Editing config through API is supported but affects only that one node.
|
||||||
|
(janhoy)
|
||||||
|
|
||||||
|
* SOLR-2212: Add a factory class corresponding to Lucene's NoMergePolicy. (Lance Norskog, Cao Manh Dat via shalin)
|
||||||
|
|
||||||
Bug Fixes
|
Bug Fixes
|
||||||
----------------------
|
----------------------
|
||||||
|
|
||||||
|
@ -216,6 +222,9 @@ Bug Fixes
|
||||||
|
|
||||||
* SOLR-9536: OldBackupDirectory timestamp field needs to be initialized to avoid NPE.
|
* SOLR-9536: OldBackupDirectory timestamp field needs to be initialized to avoid NPE.
|
||||||
(Hrishikesh Gadre, hossman via Mark Miller)
|
(Hrishikesh Gadre, hossman via Mark Miller)
|
||||||
|
|
||||||
|
* SOLR-2039: Multivalued fields with dynamic names does not work properly with DIH.
|
||||||
|
(K A, ruslan.shv, Cao Manh Dat via shalin)
|
||||||
|
|
||||||
Optimizations
|
Optimizations
|
||||||
----------------------
|
----------------------
|
||||||
|
@ -350,6 +359,10 @@ Other Changes
|
||||||
* SOLR-4531: Add tests to ensure that recovery does not fail on corrupted tlogs.
|
* SOLR-4531: Add tests to ensure that recovery does not fail on corrupted tlogs.
|
||||||
(Simon Scofield, Cao Manh Dat via shalin)
|
(Simon Scofield, Cao Manh Dat via shalin)
|
||||||
|
|
||||||
|
* SOLR-5245: Add a test to ensure that election contexts are keyed off both collection name and coreNodeName
|
||||||
|
so that killing a shard in one collection does not result in leader election in a different collection.
|
||||||
|
See SOLR-5243 for the related bug. (Cao Manh Dat via shalin)
|
||||||
|
|
||||||
================== 6.2.1 ==================
|
================== 6.2.1 ==================
|
||||||
|
|
||||||
Bug Fixes
|
Bug Fixes
|
||||||
|
|
|
@ -501,7 +501,9 @@ public class DocBuilder {
|
||||||
doc.addChildDocument(childDoc);
|
doc.addChildDocument(childDoc);
|
||||||
} else {
|
} else {
|
||||||
handleSpecialCommands(arow, doc);
|
handleSpecialCommands(arow, doc);
|
||||||
|
vr.addNamespace(epw.getEntity().getName(), arow);
|
||||||
addFields(epw.getEntity(), doc, arow, vr);
|
addFields(epw.getEntity(), doc, arow, vr);
|
||||||
|
vr.removeNamespace(epw.getEntity().getName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (epw.getEntity().getChildren() != null) {
|
if (epw.getEntity().getChildren() != null) {
|
||||||
|
|
|
@ -115,6 +115,20 @@ public class TestDocBuilder2 extends AbstractDataImportHandlerTestCase {
|
||||||
assertQ(req("desc:ApacheSolr"), "//*[@numFound='1']");
|
assertQ(req("desc:ApacheSolr"), "//*[@numFound='1']");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public void testDynamicFieldNames() throws Exception {
|
||||||
|
List rows = new ArrayList();
|
||||||
|
rows.add(createMap("mypk", "101", "text", "ApacheSolr"));
|
||||||
|
MockDataSource.setIterator("select * from x", rows.iterator());
|
||||||
|
|
||||||
|
LocalSolrQueryRequest request = lrf.makeRequest("command", "full-import",
|
||||||
|
"debug", "on", "clean", "true", "commit", "true",
|
||||||
|
"dataConfig", dataConfigWithDynamicFieldNames);
|
||||||
|
h.query("/dataimport", request);
|
||||||
|
assertQ(req("id:101"), "//*[@numFound='1']", "//*[@name='101_s']");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public void testRequestParamsAsFieldName() throws Exception {
|
public void testRequestParamsAsFieldName() throws Exception {
|
||||||
|
@ -398,6 +412,15 @@ public class TestDocBuilder2 extends AbstractDataImportHandlerTestCase {
|
||||||
" </document>\n" +
|
" </document>\n" +
|
||||||
"</dataConfig>";
|
"</dataConfig>";
|
||||||
|
|
||||||
|
private final String dataConfigWithDynamicFieldNames = "<dataConfig><dataSource type=\"MockDataSource\"/>\n" +
|
||||||
|
" <document>\n" +
|
||||||
|
" <entity name=\"books\" query=\"select * from x\">\n" +
|
||||||
|
" <field column=\"mypk\" name=\"id\" />\n" +
|
||||||
|
" <field column=\"text\" name=\"${books.mypk}_s\" />\n" +
|
||||||
|
" </entity>\n" +
|
||||||
|
" </document>\n" +
|
||||||
|
"</dataConfig>";
|
||||||
|
|
||||||
private final String dataConfigFileList = "<dataConfig>\n" +
|
private final String dataConfigFileList = "<dataConfig>\n" +
|
||||||
"\t<document>\n" +
|
"\t<document>\n" +
|
||||||
"\t\t<entity name=\"x\" processor=\"FileListEntityProcessor\" \n" +
|
"\t\t<entity name=\"x\" processor=\"FileListEntityProcessor\" \n" +
|
||||||
|
|
|
@ -48,7 +48,6 @@ import org.apache.solr.cloud.Overseer;
|
||||||
import org.apache.solr.cloud.ZkController;
|
import org.apache.solr.cloud.ZkController;
|
||||||
import org.apache.solr.common.SolrException;
|
import org.apache.solr.common.SolrException;
|
||||||
import org.apache.solr.common.SolrException.ErrorCode;
|
import org.apache.solr.common.SolrException.ErrorCode;
|
||||||
import org.apache.solr.common.cloud.ZkStateReader;
|
|
||||||
import org.apache.solr.common.util.ExecutorUtil;
|
import org.apache.solr.common.util.ExecutorUtil;
|
||||||
import org.apache.solr.common.util.IOUtils;
|
import org.apache.solr.common.util.IOUtils;
|
||||||
import org.apache.solr.common.util.Utils;
|
import org.apache.solr.common.util.Utils;
|
||||||
|
@ -60,6 +59,8 @@ import org.apache.solr.handler.admin.ConfigSetsHandler;
|
||||||
import org.apache.solr.handler.admin.CoreAdminHandler;
|
import org.apache.solr.handler.admin.CoreAdminHandler;
|
||||||
import org.apache.solr.handler.admin.InfoHandler;
|
import org.apache.solr.handler.admin.InfoHandler;
|
||||||
import org.apache.solr.handler.admin.SecurityConfHandler;
|
import org.apache.solr.handler.admin.SecurityConfHandler;
|
||||||
|
import org.apache.solr.handler.admin.SecurityConfHandlerLocal;
|
||||||
|
import org.apache.solr.handler.admin.SecurityConfHandlerZk;
|
||||||
import org.apache.solr.handler.admin.ZookeeperInfoHandler;
|
import org.apache.solr.handler.admin.ZookeeperInfoHandler;
|
||||||
import org.apache.solr.handler.component.ShardHandlerFactory;
|
import org.apache.solr.handler.component.ShardHandlerFactory;
|
||||||
import org.apache.solr.logging.LogWatcher;
|
import org.apache.solr.logging.LogWatcher;
|
||||||
|
@ -78,7 +79,6 @@ import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkNotNull;
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
import static java.util.Collections.EMPTY_MAP;
|
|
||||||
import static org.apache.solr.common.params.CommonParams.AUTHC_PATH;
|
import static org.apache.solr.common.params.CommonParams.AUTHC_PATH;
|
||||||
import static org.apache.solr.common.params.CommonParams.AUTHZ_PATH;
|
import static org.apache.solr.common.params.CommonParams.AUTHZ_PATH;
|
||||||
import static org.apache.solr.common.params.CommonParams.COLLECTIONS_HANDLER_PATH;
|
import static org.apache.solr.common.params.CommonParams.COLLECTIONS_HANDLER_PATH;
|
||||||
|
@ -88,7 +88,6 @@ import static org.apache.solr.common.params.CommonParams.INFO_HANDLER_PATH;
|
||||||
import static org.apache.solr.common.params.CommonParams.ZK_PATH;
|
import static org.apache.solr.common.params.CommonParams.ZK_PATH;
|
||||||
import static org.apache.solr.security.AuthenticationPlugin.AUTHENTICATION_PLUGIN_PROP;
|
import static org.apache.solr.security.AuthenticationPlugin.AUTHENTICATION_PLUGIN_PROP;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @since solr 1.3
|
* @since solr 1.3
|
||||||
|
@ -296,10 +295,10 @@ public class CoreContainer {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pluginClassName != null) {
|
if (pluginClassName != null) {
|
||||||
log.info("Authentication plugin class obtained from ZK: "+pluginClassName);
|
log.debug("Authentication plugin class obtained from security.json: "+pluginClassName);
|
||||||
} else if (System.getProperty(AUTHENTICATION_PLUGIN_PROP) != null) {
|
} else if (System.getProperty(AUTHENTICATION_PLUGIN_PROP) != null) {
|
||||||
pluginClassName = System.getProperty(AUTHENTICATION_PLUGIN_PROP);
|
pluginClassName = System.getProperty(AUTHENTICATION_PLUGIN_PROP);
|
||||||
log.info("Authentication plugin class obtained from system property '" +
|
log.debug("Authentication plugin class obtained from system property '" +
|
||||||
AUTHENTICATION_PLUGIN_PROP + "': " + pluginClassName);
|
AUTHENTICATION_PLUGIN_PROP + "': " + pluginClassName);
|
||||||
} else {
|
} else {
|
||||||
log.debug("No authentication plugin used.");
|
log.debug("No authentication plugin used.");
|
||||||
|
@ -309,6 +308,7 @@ public class CoreContainer {
|
||||||
|
|
||||||
// Initialize the plugin
|
// Initialize the plugin
|
||||||
if (pluginClassName != null) {
|
if (pluginClassName != null) {
|
||||||
|
log.info("Initializing authentication plugin: " + pluginClassName);
|
||||||
authenticationPlugin = new SecurityPluginHolder<>(readVersion(authenticationConfig),
|
authenticationPlugin = new SecurityPluginHolder<>(readVersion(authenticationConfig),
|
||||||
getResourceLoader().newInstance(pluginClassName,
|
getResourceLoader().newInstance(pluginClassName,
|
||||||
AuthenticationPlugin.class,
|
AuthenticationPlugin.class,
|
||||||
|
@ -463,14 +463,11 @@ public class CoreContainer {
|
||||||
|
|
||||||
MDCLoggingContext.setNode(this);
|
MDCLoggingContext.setNode(this);
|
||||||
|
|
||||||
ZkStateReader.ConfigData securityConfig = isZooKeeperAware() ? getZkController().getZkStateReader().getSecurityProps(false) : new ZkStateReader.ConfigData(EMPTY_MAP, -1);
|
securityConfHandler = isZooKeeperAware() ? new SecurityConfHandlerZk(this) : new SecurityConfHandlerLocal(this);
|
||||||
initializeAuthorizationPlugin((Map<String, Object>) securityConfig.data.get("authorization"));
|
reloadSecurityProperties();
|
||||||
initializeAuthenticationPlugin((Map<String, Object>) securityConfig.data.get("authentication"));
|
|
||||||
|
|
||||||
this.backupRepoFactory = new BackupRepositoryFactory(cfg.getBackupRepositoryPlugins());
|
this.backupRepoFactory = new BackupRepositoryFactory(cfg.getBackupRepositoryPlugins());
|
||||||
|
|
||||||
containerHandlers.put(ZK_PATH, new ZookeeperInfoHandler(this));
|
containerHandlers.put(ZK_PATH, new ZookeeperInfoHandler(this));
|
||||||
securityConfHandler = new SecurityConfHandler(this);
|
|
||||||
collectionsHandler = createHandler(cfg.getCollectionsHandlerClass(), CollectionsHandler.class);
|
collectionsHandler = createHandler(cfg.getCollectionsHandlerClass(), CollectionsHandler.class);
|
||||||
containerHandlers.put(COLLECTIONS_HANDLER_PATH, collectionsHandler);
|
containerHandlers.put(COLLECTIONS_HANDLER_PATH, collectionsHandler);
|
||||||
infoHandler = createHandler(cfg.getInfoHandlerClass(), InfoHandler.class);
|
infoHandler = createHandler(cfg.getInfoHandlerClass(), InfoHandler.class);
|
||||||
|
@ -567,10 +564,17 @@ public class CoreContainer {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void securityNodeChanged() {
|
public void securityNodeChanged() {
|
||||||
log.info("Security node changed");
|
log.info("Security node changed, reloading security.json");
|
||||||
ZkStateReader.ConfigData securityConfig = getZkController().getZkStateReader().getSecurityProps(false);
|
reloadSecurityProperties();
|
||||||
initializeAuthorizationPlugin((Map<String, Object>) securityConfig.data.get("authorization"));
|
}
|
||||||
initializeAuthenticationPlugin((Map<String, Object>) securityConfig.data.get("authentication"));
|
|
||||||
|
/**
|
||||||
|
* Make sure securityConfHandler is initialized
|
||||||
|
*/
|
||||||
|
private void reloadSecurityProperties() {
|
||||||
|
SecurityConfHandler.SecurityConfig securityConfig = securityConfHandler.getSecurityConfig(false);
|
||||||
|
initializeAuthorizationPlugin((Map<String, Object>) securityConfig.getData().get("authorization"));
|
||||||
|
initializeAuthenticationPlugin((Map<String, Object>) securityConfig.getData().get("authentication"));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void checkForDuplicateCoreNames(List<CoreDescriptor> cds) {
|
private static void checkForDuplicateCoreNames(List<CoreDescriptor> cds) {
|
||||||
|
|
|
@ -17,6 +17,8 @@
|
||||||
package org.apache.solr.handler.admin;
|
package org.apache.solr.handler.admin;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.lang.invoke.MethodHandles;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
|
@ -25,7 +27,6 @@ import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
import org.apache.solr.common.SolrException;
|
import org.apache.solr.common.SolrException;
|
||||||
import org.apache.solr.common.cloud.ZkStateReader.ConfigData;
|
|
||||||
import org.apache.solr.common.params.CommonParams;
|
import org.apache.solr.common.params.CommonParams;
|
||||||
import org.apache.solr.common.util.Utils;
|
import org.apache.solr.common.util.Utils;
|
||||||
import org.apache.solr.core.CoreContainer;
|
import org.apache.solr.core.CoreContainer;
|
||||||
|
@ -37,10 +38,14 @@ import org.apache.solr.security.AuthorizationContext;
|
||||||
import org.apache.solr.security.ConfigEditablePlugin;
|
import org.apache.solr.security.ConfigEditablePlugin;
|
||||||
import org.apache.solr.security.PermissionNameProvider;
|
import org.apache.solr.security.PermissionNameProvider;
|
||||||
import org.apache.solr.util.CommandOperation;
|
import org.apache.solr.util.CommandOperation;
|
||||||
import org.apache.zookeeper.KeeperException;
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
public class SecurityConfHandler extends RequestHandlerBase implements PermissionNameProvider {
|
import static org.apache.solr.common.SolrException.ErrorCode.SERVER_ERROR;
|
||||||
private CoreContainer cores;
|
|
||||||
|
public abstract class SecurityConfHandler extends RequestHandlerBase implements PermissionNameProvider {
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
|
||||||
|
protected CoreContainer cores;
|
||||||
|
|
||||||
public SecurityConfHandler(CoreContainer coreContainer) {
|
public SecurityConfHandler(CoreContainer coreContainer) {
|
||||||
this.cores = coreContainer;
|
this.cores = coreContainer;
|
||||||
|
@ -92,11 +97,12 @@ public class SecurityConfHandler extends RequestHandlerBase implements Permissio
|
||||||
if (ops == null) {
|
if (ops == null) {
|
||||||
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "No commands");
|
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "No commands");
|
||||||
}
|
}
|
||||||
for (; ; ) {
|
for (int count = 1; count <= 3 ; count++ ) {
|
||||||
ConfigData data = getSecurityProps(true);
|
SecurityConfig securityConfig = getSecurityConfig(true);
|
||||||
Map<String, Object> latestConf = (Map<String, Object>) data.data.get(key);
|
Map<String, Object> data = securityConfig.getData();
|
||||||
|
Map<String, Object> latestConf = (Map<String, Object>) data.get(key);
|
||||||
if (latestConf == null) {
|
if (latestConf == null) {
|
||||||
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "No configuration present for " + key);
|
throw new SolrException(SERVER_ERROR, "No configuration present for " + key);
|
||||||
}
|
}
|
||||||
List<CommandOperation> commandsCopy = CommandOperation.clone(ops);
|
List<CommandOperation> commandsCopy = CommandOperation.clone(ops);
|
||||||
Map<String, Object> out = configEditablePlugin.edit(Utils.getDeepCopy(latestConf, 4) , commandsCopy);
|
Map<String, Object> out = configEditablePlugin.edit(Utils.getDeepCopy(latestConf, 4) , commandsCopy);
|
||||||
|
@ -106,20 +112,31 @@ public class SecurityConfHandler extends RequestHandlerBase implements Permissio
|
||||||
rsp.add(CommandOperation.ERR_MSGS, errs);
|
rsp.add(CommandOperation.ERR_MSGS, errs);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
//no edits
|
log.debug("No edits made");
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
if(!Objects.equals(latestConf.get("class") , out.get("class"))){
|
if(!Objects.equals(latestConf.get("class") , out.get("class"))){
|
||||||
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "class cannot be modified");
|
throw new SolrException(SERVER_ERROR, "class cannot be modified");
|
||||||
}
|
}
|
||||||
Map meta = getMapValue(out, "");
|
Map meta = getMapValue(out, "");
|
||||||
meta.put("v", data.version+1);//encode the expected zkversion
|
meta.put("v", securityConfig.getVersion()+1);//encode the expected zkversion
|
||||||
data.data.put(key, out);
|
data.put(key, out);
|
||||||
if(persistConf("/security.json", Utils.toJSON(data.data), data.version)) return;
|
|
||||||
|
if(persistConf(securityConfig)) {
|
||||||
|
securityConfEdited();
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
log.debug("Security edit operation failed {} time(s)" + count);
|
||||||
}
|
}
|
||||||
|
throw new SolrException(SERVER_ERROR, "Failed to persist security config after 3 attempts. Giving up");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook where you can do stuff after a config has been edited. Defaults to NOP
|
||||||
|
*/
|
||||||
|
protected void securityConfEdited() {}
|
||||||
|
|
||||||
Object getPlugin(String key) {
|
Object getPlugin(String key) {
|
||||||
Object plugin = null;
|
Object plugin = null;
|
||||||
if ("authentication".equals(key)) plugin = cores.getAuthenticationPlugin();
|
if ("authentication".equals(key)) plugin = cores.getAuthenticationPlugin();
|
||||||
|
@ -127,38 +144,14 @@ public class SecurityConfHandler extends RequestHandlerBase implements Permissio
|
||||||
return plugin;
|
return plugin;
|
||||||
}
|
}
|
||||||
|
|
||||||
ConfigData getSecurityProps(boolean getFresh) {
|
protected abstract void getConf(SolrQueryResponse rsp, String key);
|
||||||
return cores.getZkController().getZkStateReader().getSecurityProps(getFresh);
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean persistConf(String path, byte[] buf, int version) {
|
|
||||||
try {
|
|
||||||
cores.getZkController().getZkClient().setData(path,buf,version, true);
|
|
||||||
return true;
|
|
||||||
} catch (KeeperException.BadVersionException bdve){
|
|
||||||
return false;
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, " Unable to persist conf",e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void getConf(SolrQueryResponse rsp, String key) {
|
|
||||||
ConfigData map = cores.getZkController().getZkStateReader().getSecurityProps(false);
|
|
||||||
Object o = map == null ? null : map.data.get(key);
|
|
||||||
if (o == null) {
|
|
||||||
rsp.add(CommandOperation.ERR_MSGS, Collections.singletonList("No " + key + " configured"));
|
|
||||||
} else {
|
|
||||||
rsp.add(key+".enabled", getPlugin(key)!=null);
|
|
||||||
rsp.add(key, o);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Map<String, Object> getMapValue(Map<String, Object> lookupMap, String key) {
|
public static Map<String, Object> getMapValue(Map<String, Object> lookupMap, String key) {
|
||||||
Map<String, Object> m = (Map<String, Object>) lookupMap.get(key);
|
Map<String, Object> m = (Map<String, Object>) lookupMap.get(key);
|
||||||
if (m == null) lookupMap.put(key, m = new LinkedHashMap<>());
|
if (m == null) lookupMap.put(key, m = new LinkedHashMap<>());
|
||||||
return m;
|
return m;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List getListValue(Map<String, Object> lookupMap, String key) {
|
public static List getListValue(Map<String, Object> lookupMap, String key) {
|
||||||
List l = (List) lookupMap.get(key);
|
List l = (List) lookupMap.get(key);
|
||||||
if (l == null) lookupMap.put(key, l= new ArrayList());
|
if (l == null) lookupMap.put(key, l= new ArrayList());
|
||||||
|
@ -170,6 +163,77 @@ public class SecurityConfHandler extends RequestHandlerBase implements Permissio
|
||||||
return "Edit or read security configuration";
|
return "Edit or read security configuration";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets security.json from source
|
||||||
|
*/
|
||||||
|
public abstract SecurityConfig getSecurityConfig(boolean getFresh);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Persist security.json to the source, optionally with a version
|
||||||
|
*/
|
||||||
|
protected abstract boolean persistConf(SecurityConfig securityConfig) throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Object to hold security.json as nested <code>Map<String,Object></code> and optionally its version.
|
||||||
|
* The version property is optional and defaults to -1 if not initialized.
|
||||||
|
* The data object defaults to EMPTY_MAP if not set
|
||||||
|
*/
|
||||||
|
public static class SecurityConfig {
|
||||||
|
private Map<String, Object> data = Collections.EMPTY_MAP;
|
||||||
|
private int version = -1;
|
||||||
|
|
||||||
|
public SecurityConfig() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the data as a Map
|
||||||
|
* @param data a Map
|
||||||
|
* @return SecurityConf object (builder pattern)
|
||||||
|
*/
|
||||||
|
public SecurityConfig setData(Map<String, Object> data) {
|
||||||
|
this.data = data;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the data as an Object, but the object needs to be of type Map
|
||||||
|
* @param data an Object of type Map<String,Object>
|
||||||
|
* @return SecurityConf object (builder pattern)
|
||||||
|
*/
|
||||||
|
public SecurityConfig setData(Object data) {
|
||||||
|
if (data instanceof Map) {
|
||||||
|
this.data = (Map<String, Object>) data;
|
||||||
|
return this;
|
||||||
|
} else {
|
||||||
|
throw new SolrException(SERVER_ERROR, "Illegal format when parsing security.json, not object");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets version
|
||||||
|
* @param version integer for version. Depends on underlying storage
|
||||||
|
* @return SecurityConf object (builder pattern)
|
||||||
|
*/
|
||||||
|
public SecurityConfig setVersion(int version) {
|
||||||
|
this.version = version;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Object> getData() {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getVersion() {
|
||||||
|
return version;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set data from input stream
|
||||||
|
* @param securityJsonInputStream an input stream for security.json
|
||||||
|
* @return this (builder pattern)
|
||||||
|
*/
|
||||||
|
public SecurityConfig setData(InputStream securityJsonInputStream) {
|
||||||
|
return setData(Utils.fromJSON(securityJsonInputStream));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,102 @@
|
||||||
|
/*
|
||||||
|
* 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.solr.handler.admin;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.lang.invoke.MethodHandles;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
import org.apache.solr.common.SolrException;
|
||||||
|
import org.apache.solr.common.util.Utils;
|
||||||
|
import org.apache.solr.core.CoreContainer;
|
||||||
|
import org.apache.solr.core.SolrResourceLoader;
|
||||||
|
import org.apache.solr.response.SolrQueryResponse;
|
||||||
|
import org.apache.solr.util.CommandOperation;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Security Configuration Handler which works on standalone local files
|
||||||
|
*/
|
||||||
|
public class SecurityConfHandlerLocal extends SecurityConfHandler {
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
|
||||||
|
protected Path securityJsonPath;
|
||||||
|
|
||||||
|
public SecurityConfHandlerLocal(CoreContainer coreContainer) {
|
||||||
|
super(coreContainer);
|
||||||
|
securityJsonPath = SolrResourceLoader.locateSolrHome().resolve("security.json");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches security props from SOLR_HOME
|
||||||
|
* @param getFresh NOP
|
||||||
|
* @return SecurityConfig whose data property either contains security.json, or an empty map if not found
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public SecurityConfig getSecurityConfig(boolean getFresh) {
|
||||||
|
if (Files.exists(securityJsonPath)) {
|
||||||
|
try (InputStream securityJsonIs = Files.newInputStream(securityJsonPath)) {
|
||||||
|
return new SecurityConfig().setData(securityJsonIs);
|
||||||
|
} catch (IOException e) { /* Fall through */ }
|
||||||
|
}
|
||||||
|
return new SecurityConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void getConf(SolrQueryResponse rsp, String key) {
|
||||||
|
SecurityConfig props = getSecurityConfig(false);
|
||||||
|
Object o = props.getData().get(key);
|
||||||
|
if (o == null) {
|
||||||
|
rsp.add(CommandOperation.ERR_MSGS, Collections.singletonList("No " + key + " configured"));
|
||||||
|
} else {
|
||||||
|
rsp.add(key+".enabled", getPlugin(key)!=null);
|
||||||
|
rsp.add(key, o);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean persistConf(SecurityConfig securityConfig) throws IOException {
|
||||||
|
if (securityConfig == null || securityConfig.getData().isEmpty()) {
|
||||||
|
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,
|
||||||
|
"Failed persisting security.json to SOLR_HOME. Object was empty.");
|
||||||
|
}
|
||||||
|
try(OutputStream securityJsonOs = Files.newOutputStream(securityJsonPath)) {
|
||||||
|
securityJsonOs.write(Utils.toJSON(securityConfig.getData()));
|
||||||
|
log.debug("Persisted security.json to {}", securityJsonPath);
|
||||||
|
return true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,
|
||||||
|
"Failed persisting security.json to " + securityJsonPath, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDescription() {
|
||||||
|
return "Edit or read security configuration locally in SOLR_HOME";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void securityConfEdited() {
|
||||||
|
// Need to call explicitly since we will not get notified of changes to local security.json
|
||||||
|
cores.securityNodeChanged();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,92 @@
|
||||||
|
/*
|
||||||
|
* 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.solr.handler.admin;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.invoke.MethodHandles;
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
import org.apache.solr.common.SolrException;
|
||||||
|
import org.apache.solr.common.cloud.ZkStateReader;
|
||||||
|
import org.apache.solr.common.util.Utils;
|
||||||
|
import org.apache.solr.core.CoreContainer;
|
||||||
|
import org.apache.solr.response.SolrQueryResponse;
|
||||||
|
import org.apache.solr.util.CommandOperation;
|
||||||
|
import org.apache.zookeeper.KeeperException;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import static org.apache.solr.common.SolrException.ErrorCode.SERVER_ERROR;
|
||||||
|
import static org.apache.solr.common.cloud.ZkStateReader.SOLR_SECURITY_CONF_PATH;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Security Configuration Handler which works with Zookeeper
|
||||||
|
*/
|
||||||
|
public class SecurityConfHandlerZk extends SecurityConfHandler {
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
|
||||||
|
public SecurityConfHandlerZk(CoreContainer coreContainer) {
|
||||||
|
super(coreContainer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches security props from Zookeeper and adds version
|
||||||
|
* @param getFresh refresh from ZK
|
||||||
|
* @return SecurityConfig whose data property either contains security.json, or an empty map if not found
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public SecurityConfig getSecurityConfig(boolean getFresh) {
|
||||||
|
ZkStateReader.ConfigData configDataFromZk = cores.getZkController().getZkStateReader().getSecurityProps(getFresh);
|
||||||
|
return configDataFromZk == null ?
|
||||||
|
new SecurityConfig() :
|
||||||
|
new SecurityConfig().setData(configDataFromZk.data).setVersion(configDataFromZk.version);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void getConf(SolrQueryResponse rsp, String key) {
|
||||||
|
ZkStateReader.ConfigData map = cores.getZkController().getZkStateReader().getSecurityProps(false);
|
||||||
|
Object o = map == null ? null : map.data.get(key);
|
||||||
|
if (o == null) {
|
||||||
|
rsp.add(CommandOperation.ERR_MSGS, Collections.singletonList("No " + key + " configured"));
|
||||||
|
} else {
|
||||||
|
rsp.add(key+".enabled", getPlugin(key)!=null);
|
||||||
|
rsp.add(key, o);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean persistConf(SecurityConfig securityConfig) throws IOException {
|
||||||
|
try {
|
||||||
|
cores.getZkController().getZkClient().setData(SOLR_SECURITY_CONF_PATH,
|
||||||
|
Utils.toJSON(securityConfig.getData()),
|
||||||
|
securityConfig.getVersion(), true);
|
||||||
|
log.debug("Persisted security.json to {}", SOLR_SECURITY_CONF_PATH);
|
||||||
|
return true;
|
||||||
|
} catch (KeeperException.BadVersionException bdve){
|
||||||
|
log.warn("Failed persisting security.json to {}", SOLR_SECURITY_CONF_PATH, bdve);
|
||||||
|
return false;
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new SolrException(SERVER_ERROR, "Unable to persist security.json", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDescription() {
|
||||||
|
return "Edit or read security configuration from Zookeeper";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
/*
|
||||||
|
* 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.solr.index;
|
||||||
|
|
||||||
|
import org.apache.lucene.index.MergePolicy;
|
||||||
|
import org.apache.lucene.index.NoMergePolicy;
|
||||||
|
import org.apache.solr.core.SolrResourceLoader;
|
||||||
|
import org.apache.solr.schema.IndexSchema;
|
||||||
|
|
||||||
|
public class NoMergePolicyFactory extends SimpleMergePolicyFactory {
|
||||||
|
public NoMergePolicyFactory(SolrResourceLoader resourceLoader, MergePolicyFactoryArgs args, IndexSchema schema) {
|
||||||
|
super(resourceLoader, args, schema);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected MergePolicy getMergePolicyInstance() {
|
||||||
|
return NoMergePolicy.INSTANCE;
|
||||||
|
}
|
||||||
|
}
|
|
@ -43,12 +43,12 @@ import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
public class BasicAuthPlugin extends AuthenticationPlugin implements ConfigEditablePlugin {
|
public class BasicAuthPlugin extends AuthenticationPlugin implements ConfigEditablePlugin {
|
||||||
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
|
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
|
||||||
private AuthenticationProvider zkAuthentication;
|
private AuthenticationProvider authenticationProvider;
|
||||||
private final static ThreadLocal<Header> authHeader = new ThreadLocal<>();
|
private final static ThreadLocal<Header> authHeader = new ThreadLocal<>();
|
||||||
private boolean blockUnknown = false;
|
private boolean blockUnknown = false;
|
||||||
|
|
||||||
public boolean authenticate(String username, String pwd) {
|
public boolean authenticate(String username, String pwd) {
|
||||||
return zkAuthentication.authenticate(username, pwd);
|
return authenticationProvider.authenticate(username, pwd);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -61,7 +61,7 @@ public class BasicAuthPlugin extends AuthenticationPlugin implements ConfigEdita
|
||||||
log.error(e.getMessage());
|
log.error(e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
zkAuthentication = getAuthenticationProvider(pluginConfig);
|
authenticationProvider = getAuthenticationProvider(pluginConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -79,8 +79,8 @@ public class BasicAuthPlugin extends AuthenticationPlugin implements ConfigEdita
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!CommandOperation.captureErrors(commands).isEmpty()) return null;
|
if (!CommandOperation.captureErrors(commands).isEmpty()) return null;
|
||||||
if (zkAuthentication instanceof ConfigEditablePlugin) {
|
if (authenticationProvider instanceof ConfigEditablePlugin) {
|
||||||
ConfigEditablePlugin editablePlugin = (ConfigEditablePlugin) zkAuthentication;
|
ConfigEditablePlugin editablePlugin = (ConfigEditablePlugin) authenticationProvider;
|
||||||
return editablePlugin.edit(latestConf, commands);
|
return editablePlugin.edit(latestConf, commands);
|
||||||
}
|
}
|
||||||
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "This cannot be edited");
|
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "This cannot be edited");
|
||||||
|
@ -93,7 +93,7 @@ public class BasicAuthPlugin extends AuthenticationPlugin implements ConfigEdita
|
||||||
}
|
}
|
||||||
|
|
||||||
private void authenticationFailure(HttpServletResponse response, String message) throws IOException {
|
private void authenticationFailure(HttpServletResponse response, String message) throws IOException {
|
||||||
for (Map.Entry<String, String> entry : zkAuthentication.getPromptHeaders().entrySet()) {
|
for (Map.Entry<String, String> entry : authenticationProvider.getPromptHeaders().entrySet()) {
|
||||||
response.setHeader(entry.getKey(), entry.getValue());
|
response.setHeader(entry.getKey(), entry.getValue());
|
||||||
}
|
}
|
||||||
response.sendError(401, message);
|
response.sendError(401, message);
|
||||||
|
@ -143,7 +143,7 @@ public class BasicAuthPlugin extends AuthenticationPlugin implements ConfigEdita
|
||||||
if (blockUnknown) {
|
if (blockUnknown) {
|
||||||
authenticationFailure(response, "require authentication");
|
authenticationFailure(response, "require authentication");
|
||||||
} else {
|
} else {
|
||||||
request.setAttribute(AuthenticationPlugin.class.getName(), zkAuthentication.getPromptHeaders());
|
request.setAttribute(AuthenticationPlugin.class.getName(), authenticationProvider.getPromptHeaders());
|
||||||
filterChain.doFilter(request, response);
|
filterChain.doFilter(request, response);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,7 +64,7 @@ public class Sha256AuthenticationProvider implements ConfigEditablePlugin, Basi
|
||||||
credentials = new LinkedHashMap<>();
|
credentials = new LinkedHashMap<>();
|
||||||
Map<String,String> users = (Map<String,String>) pluginConfig.get("credentials");
|
Map<String,String> users = (Map<String,String>) pluginConfig.get("credentials");
|
||||||
if (users == null) {
|
if (users == null) {
|
||||||
log.warn("No users configured yet");
|
log.debug("No users configured yet");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for (Map.Entry<String, String> e : users.entrySet()) {
|
for (Map.Entry<String, String> e : users.entrySet()) {
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
<?xml version="1.0" ?>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<config>
|
||||||
|
<luceneMatchVersion>${tests.luceneMatchVersion:LATEST}</luceneMatchVersion>
|
||||||
|
<directoryFactory name="DirectoryFactory" class="${solr.directoryFactory:solr.RAMDirectoryFactory}"/>
|
||||||
|
<schemaFactory class="ClassicIndexSchemaFactory"/>
|
||||||
|
|
||||||
|
<indexConfig>
|
||||||
|
<useCompoundFile>${useCompoundFile:false}</useCompoundFile>
|
||||||
|
<mergePolicyFactory class="org.apache.solr.index.NoMergePolicyFactory" />
|
||||||
|
</indexConfig>
|
||||||
|
|
||||||
|
<requestHandler name="standard" class="solr.StandardRequestHandler"></requestHandler>
|
||||||
|
|
||||||
|
</config>
|
|
@ -0,0 +1,114 @@
|
||||||
|
/*
|
||||||
|
* 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.solr.cloud;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import org.apache.solr.client.solrj.SolrClient;
|
||||||
|
import org.apache.solr.client.solrj.SolrServerException;
|
||||||
|
import org.apache.solr.client.solrj.impl.HttpSolrClient;
|
||||||
|
import org.apache.solr.client.solrj.request.CollectionAdminRequest;
|
||||||
|
import org.apache.solr.client.solrj.request.CoreAdminRequest;
|
||||||
|
import org.apache.solr.common.cloud.Replica;
|
||||||
|
import org.apache.solr.common.cloud.SolrZkClient;
|
||||||
|
import org.apache.solr.common.cloud.ZkStateReader;
|
||||||
|
import org.apache.zookeeper.KeeperException;
|
||||||
|
import org.hamcrest.CoreMatchers;
|
||||||
|
import org.junit.BeforeClass;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class LeaderElectionContextKeyTest extends SolrCloudTestCase {
|
||||||
|
|
||||||
|
private static final String TEST_COLLECTION_1 = "testCollection1";
|
||||||
|
private static final String TEST_COLLECTION_2 = "testCollection2";
|
||||||
|
|
||||||
|
@BeforeClass
|
||||||
|
public static void setupCluster() throws Exception {
|
||||||
|
configureCluster(1)
|
||||||
|
.addConfig("config", TEST_PATH().resolve("configsets").resolve("cloud-minimal").resolve("conf"))
|
||||||
|
.configure();
|
||||||
|
|
||||||
|
CollectionAdminRequest
|
||||||
|
.createCollection("testCollection1", "config", 2, 1)
|
||||||
|
.setMaxShardsPerNode(1000)
|
||||||
|
.process(cluster.getSolrClient());
|
||||||
|
CollectionAdminRequest
|
||||||
|
.createCollection("testCollection2", "config", 2, 1)
|
||||||
|
.setMaxShardsPerNode(1000)
|
||||||
|
.process(cluster.getSolrClient());
|
||||||
|
AbstractDistribZkTestBase.waitForRecoveriesToFinish("testCollection1", cluster.getSolrClient().getZkStateReader(),
|
||||||
|
false, true, 30);
|
||||||
|
AbstractDistribZkTestBase.waitForRecoveriesToFinish("testCollection2", cluster.getSolrClient().getZkStateReader(),
|
||||||
|
false, true, 30);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() throws KeeperException, InterruptedException, IOException, SolrServerException {
|
||||||
|
ZkStateReader stateReader = cluster.getSolrClient().getZkStateReader();
|
||||||
|
stateReader.forceUpdateCollection(TEST_COLLECTION_1);
|
||||||
|
List<Replica> replicasOfCollection1 = stateReader.getClusterState().getCollection(TEST_COLLECTION_1).getReplicas();
|
||||||
|
List<Replica> replicasOfCollection2 = stateReader.getClusterState().getCollection(TEST_COLLECTION_2).getReplicas();
|
||||||
|
Replica replica = findLeaderReplicaWithDuplicatedName(replicasOfCollection1, replicasOfCollection2);
|
||||||
|
assertNotNull(replica);
|
||||||
|
|
||||||
|
SolrClient shardLeaderClient = new HttpSolrClient.Builder(replica.get("base_url").toString()).build();
|
||||||
|
try {
|
||||||
|
assertEquals(1L, getElectionNodes(TEST_COLLECTION_1, "shard1", stateReader.getZkClient()).size());
|
||||||
|
List<String> collection2Shard1Nodes = getElectionNodes(TEST_COLLECTION_2, "shard1", stateReader.getZkClient());
|
||||||
|
List<String> collection2Shard2Nodes = getElectionNodes(TEST_COLLECTION_2, "shard2", stateReader.getZkClient());
|
||||||
|
CoreAdminRequest.unloadCore(replica.getCoreName(), shardLeaderClient);
|
||||||
|
// Waiting for leader election being kicked off
|
||||||
|
long timeout = System.nanoTime() + TimeUnit.NANOSECONDS.convert(60, TimeUnit.SECONDS);
|
||||||
|
boolean found = false;
|
||||||
|
while (System.nanoTime() < timeout) {
|
||||||
|
try {
|
||||||
|
found = getElectionNodes(TEST_COLLECTION_1, "shard1", stateReader.getZkClient()).size() == 0;
|
||||||
|
break;
|
||||||
|
} catch (KeeperException.NoNodeException nne) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertTrue(found);
|
||||||
|
// There are no leader election was kicked off on testCollection2
|
||||||
|
assertThat(collection2Shard1Nodes, CoreMatchers.is(getElectionNodes(TEST_COLLECTION_2, "shard1", stateReader.getZkClient())));
|
||||||
|
assertThat(collection2Shard2Nodes, CoreMatchers.is(getElectionNodes(TEST_COLLECTION_2, "shard2", stateReader.getZkClient())));
|
||||||
|
} finally {
|
||||||
|
shardLeaderClient.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Replica findLeaderReplicaWithDuplicatedName(List<Replica> replicas1, List<Replica> replicas2) {
|
||||||
|
for (Replica replica1 : replicas1) {
|
||||||
|
if (!replica1.containsKey("leader")) continue;
|
||||||
|
for (Replica replica2 : replicas2) {
|
||||||
|
if (replica1.getName().equals(replica2.getName())
|
||||||
|
&& replica1.get("base_url").equals(replica2.get("base_url"))
|
||||||
|
&& replica2.containsKey("leader")) {
|
||||||
|
return replica1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<String> getElectionNodes(String collection, String shard, SolrZkClient client) throws KeeperException, InterruptedException {
|
||||||
|
return client.getChildren("/collections/"+collection+"/leader_elect/"+shard+LeaderElector.ELECTION_NODE, null, true);
|
||||||
|
}
|
||||||
|
}
|
|
@ -24,6 +24,7 @@ import org.apache.lucene.index.LeafReaderContext;
|
||||||
import org.apache.lucene.index.LogByteSizeMergePolicy;
|
import org.apache.lucene.index.LogByteSizeMergePolicy;
|
||||||
import org.apache.lucene.index.LogDocMergePolicy;
|
import org.apache.lucene.index.LogDocMergePolicy;
|
||||||
import org.apache.lucene.index.LogMergePolicy;
|
import org.apache.lucene.index.LogMergePolicy;
|
||||||
|
import org.apache.lucene.index.NoMergePolicy;
|
||||||
import org.apache.lucene.index.SegmentReader;
|
import org.apache.lucene.index.SegmentReader;
|
||||||
import org.apache.lucene.index.TieredMergePolicy;
|
import org.apache.lucene.index.TieredMergePolicy;
|
||||||
import org.apache.solr.SolrTestCaseJ4;
|
import org.apache.solr.SolrTestCaseJ4;
|
||||||
|
@ -128,6 +129,25 @@ public class TestMergePolicyConfig extends SolrTestCaseJ4 {
|
||||||
assertCompoundSegments(h.getCore(), false);
|
assertCompoundSegments(h.getCore(), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testNoMergePolicyFactoryConfig() throws Exception {
|
||||||
|
initCore("solrconfig-nomergepolicyfactory.xml","schema-minimal.xml");
|
||||||
|
IndexWriterConfig iwc = solrConfig.indexConfig.toIndexWriterConfig(h.getCore());
|
||||||
|
NoMergePolicy mergePolicy = assertAndCast(NoMergePolicy.class,
|
||||||
|
iwc.getMergePolicy());
|
||||||
|
|
||||||
|
assertCommitSomeNewDocs();
|
||||||
|
|
||||||
|
assertCommitSomeNewDocs();
|
||||||
|
assertNumSegments(h.getCore(), 2);
|
||||||
|
|
||||||
|
assertU(optimize());
|
||||||
|
assertNumSegments(h.getCore(), 2);
|
||||||
|
deleteCore();
|
||||||
|
initCore("solrconfig-nomergepolicyfactory.xml","schema-minimal.xml");
|
||||||
|
iwc = solrConfig.indexConfig.toIndexWriterConfig(h.getCore());
|
||||||
|
assertEquals(mergePolicy, iwc.getMergePolicy());
|
||||||
|
}
|
||||||
|
|
||||||
public void testLogMergePolicyConfig() throws Exception {
|
public void testLogMergePolicyConfig() throws Exception {
|
||||||
|
|
||||||
final Class<? extends LogMergePolicy> mpClass = random().nextBoolean()
|
final Class<? extends LogMergePolicy> mpClass = random().nextBoolean()
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
/*
|
||||||
|
* 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.solr.handler.admin;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import org.apache.solr.core.CoreContainer;
|
||||||
|
import org.apache.solr.core.SolrResourceLoader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper for use in tests
|
||||||
|
*/
|
||||||
|
public class SecurityConfHandlerLocalForTesting extends SecurityConfHandlerLocal {
|
||||||
|
|
||||||
|
public SecurityConfHandlerLocalForTesting(CoreContainer coreContainer) {
|
||||||
|
super(coreContainer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean persistConf(SecurityConfig securityConfig) throws IOException {
|
||||||
|
// Set JSON_PATH again since the test may have
|
||||||
|
securityJsonPath = SolrResourceLoader.locateSolrHome().resolve("security.json");
|
||||||
|
return super.persistConf(securityConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void securityConfEdited() {
|
||||||
|
super.securityConfEdited();
|
||||||
|
}
|
||||||
|
}
|
|
@ -23,7 +23,6 @@ import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.apache.solr.SolrTestCaseJ4;
|
import org.apache.solr.SolrTestCaseJ4;
|
||||||
import org.apache.solr.common.cloud.ZkStateReader.ConfigData;
|
|
||||||
import org.apache.solr.common.params.ModifiableSolrParams;
|
import org.apache.solr.common.params.ModifiableSolrParams;
|
||||||
import org.apache.solr.common.util.ContentStreamBase;
|
import org.apache.solr.common.util.ContentStreamBase;
|
||||||
import org.apache.solr.common.util.Utils;
|
import org.apache.solr.common.util.Utils;
|
||||||
|
@ -34,6 +33,7 @@ import org.apache.solr.security.RuleBasedAuthorizationPlugin;
|
||||||
import org.apache.solr.util.CommandOperation;
|
import org.apache.solr.util.CommandOperation;
|
||||||
|
|
||||||
import static org.apache.solr.common.util.Utils.makeMap;
|
import static org.apache.solr.common.util.Utils.makeMap;
|
||||||
|
import static org.apache.solr.handler.admin.SecurityConfHandler.SecurityConfig;
|
||||||
|
|
||||||
public class SecurityConfHandlerTest extends SolrTestCaseJ4 {
|
public class SecurityConfHandlerTest extends SolrTestCaseJ4 {
|
||||||
|
|
||||||
|
@ -51,8 +51,8 @@ public class SecurityConfHandlerTest extends SolrTestCaseJ4 {
|
||||||
handler.handleRequestBody(req,new SolrQueryResponse());
|
handler.handleRequestBody(req,new SolrQueryResponse());
|
||||||
|
|
||||||
BasicAuthPlugin basicAuth = new BasicAuthPlugin();
|
BasicAuthPlugin basicAuth = new BasicAuthPlugin();
|
||||||
ConfigData securityCfg = (ConfigData) handler.m.get("/security.json");
|
SecurityConfig securityCfg = handler.m.get("/security.json");
|
||||||
basicAuth.init((Map<String, Object>) securityCfg.data.get("authentication"));
|
basicAuth.init((Map<String, Object>) securityCfg.getData().get("authentication"));
|
||||||
assertTrue(basicAuth.authenticate("tom", "TomIsUberCool"));
|
assertTrue(basicAuth.authenticate("tom", "TomIsUberCool"));
|
||||||
|
|
||||||
command = "{\n" +
|
command = "{\n" +
|
||||||
|
@ -62,9 +62,9 @@ public class SecurityConfHandlerTest extends SolrTestCaseJ4 {
|
||||||
o = new ContentStreamBase.ByteArrayStream(command.getBytes(StandardCharsets.UTF_8),"");
|
o = new ContentStreamBase.ByteArrayStream(command.getBytes(StandardCharsets.UTF_8),"");
|
||||||
req.setContentStreams(Collections.singletonList(o));
|
req.setContentStreams(Collections.singletonList(o));
|
||||||
handler.handleRequestBody(req,new SolrQueryResponse());
|
handler.handleRequestBody(req,new SolrQueryResponse());
|
||||||
securityCfg = (ConfigData) handler.m.get("/security.json");
|
securityCfg = handler.m.get("/security.json");
|
||||||
assertEquals(3, securityCfg.version);
|
assertEquals(3, securityCfg.getVersion());
|
||||||
Map result = (Map) securityCfg.data.get("authentication");
|
Map result = (Map) securityCfg.getData().get("authentication");
|
||||||
result = (Map) result.get("credentials");
|
result = (Map) result.get("credentials");
|
||||||
assertTrue(result.isEmpty());
|
assertTrue(result.isEmpty());
|
||||||
|
|
||||||
|
@ -86,7 +86,7 @@ public class SecurityConfHandlerTest extends SolrTestCaseJ4 {
|
||||||
SolrQueryResponse rsp = new SolrQueryResponse();
|
SolrQueryResponse rsp = new SolrQueryResponse();
|
||||||
handler.handleRequestBody(req, rsp);
|
handler.handleRequestBody(req, rsp);
|
||||||
assertNull(rsp.getValues().get(CommandOperation.ERR_MSGS));
|
assertNull(rsp.getValues().get(CommandOperation.ERR_MSGS));
|
||||||
Map authzconf = (Map) ((ConfigData) handler.m.get("/security.json")).data.get("authorization");
|
Map authzconf = (Map) handler.m.get("/security.json").getData().get("authorization");
|
||||||
Map userRoles = (Map) authzconf.get("user-role");
|
Map userRoles = (Map) authzconf.get("user-role");
|
||||||
List tomRoles = (List) userRoles.get("tom");
|
List tomRoles = (List) userRoles.get("tom");
|
||||||
assertTrue(tomRoles.contains("admin"));
|
assertTrue(tomRoles.contains("admin"));
|
||||||
|
@ -108,7 +108,7 @@ public class SecurityConfHandlerTest extends SolrTestCaseJ4 {
|
||||||
req.setContentStreams(Collections.singletonList(o));
|
req.setContentStreams(Collections.singletonList(o));
|
||||||
rsp = new SolrQueryResponse();
|
rsp = new SolrQueryResponse();
|
||||||
handler.handleRequestBody(req, rsp);
|
handler.handleRequestBody(req, rsp);
|
||||||
authzconf = (Map) ((ConfigData) handler.m.get("/security.json")).data.get("authorization");
|
authzconf = (Map) handler.m.get("/security.json").getData().get("authorization");
|
||||||
permissions = (List<Map>) authzconf.get("permissions");
|
permissions = (List<Map>) authzconf.get("permissions");
|
||||||
|
|
||||||
Map p = permissions.get(1);
|
Map p = permissions.get(1);
|
||||||
|
@ -128,7 +128,7 @@ public class SecurityConfHandlerTest extends SolrTestCaseJ4 {
|
||||||
req.setContentStreams(Collections.singletonList(o));
|
req.setContentStreams(Collections.singletonList(o));
|
||||||
rsp = new SolrQueryResponse();
|
rsp = new SolrQueryResponse();
|
||||||
handler.handleRequestBody(req, rsp);
|
handler.handleRequestBody(req, rsp);
|
||||||
authzconf = (Map) ((ConfigData) handler.m.get("/security.json")).data.get("authorization");
|
authzconf = (Map) handler.m.get("/security.json").getData().get("authorization");
|
||||||
permissions = (List<Map>) authzconf.get("permissions");
|
permissions = (List<Map>) authzconf.get("permissions");
|
||||||
|
|
||||||
p = permissions.get(0);
|
p = permissions.get(0);
|
||||||
|
@ -151,7 +151,7 @@ public class SecurityConfHandlerTest extends SolrTestCaseJ4 {
|
||||||
rsp = new SolrQueryResponse();
|
rsp = new SolrQueryResponse();
|
||||||
handler.handleRequestBody(req, rsp);
|
handler.handleRequestBody(req, rsp);
|
||||||
assertNull(rsp.getValues().get(CommandOperation.ERR_MSGS));
|
assertNull(rsp.getValues().get(CommandOperation.ERR_MSGS));
|
||||||
authzconf = (Map) ((ConfigData) handler.m.get("/security.json")).data.get("authorization");
|
authzconf = (Map) handler.m.get("/security.json").getData().get("authorization");
|
||||||
userRoles = (Map) authzconf.get("user-role");
|
userRoles = (Map) authzconf.get("user-role");
|
||||||
assertEquals(0, userRoles.size());
|
assertEquals(0, userRoles.size());
|
||||||
permissions = (List<Map>) authzconf.get("permissions");
|
permissions = (List<Map>) authzconf.get("permissions");
|
||||||
|
@ -178,25 +178,26 @@ public class SecurityConfHandlerTest extends SolrTestCaseJ4 {
|
||||||
|
|
||||||
|
|
||||||
public static class MockSecurityHandler extends SecurityConfHandler {
|
public static class MockSecurityHandler extends SecurityConfHandler {
|
||||||
private Map<String, Object> m;
|
private Map<String, SecurityConfig> m;
|
||||||
final BasicAuthPlugin basicAuthPlugin = new BasicAuthPlugin();
|
final BasicAuthPlugin basicAuthPlugin = new BasicAuthPlugin();
|
||||||
final RuleBasedAuthorizationPlugin rulesBasedAuthorizationPlugin = new RuleBasedAuthorizationPlugin();
|
final RuleBasedAuthorizationPlugin rulesBasedAuthorizationPlugin = new RuleBasedAuthorizationPlugin();
|
||||||
|
|
||||||
|
|
||||||
public MockSecurityHandler() {
|
public MockSecurityHandler() {
|
||||||
super(null);
|
super(null);
|
||||||
m = new HashMap<>();
|
m = new HashMap<>();
|
||||||
ConfigData data = new ConfigData(makeMap("authentication", makeMap("class", "solr."+ BasicAuthPlugin.class.getSimpleName())), 1);
|
SecurityConfig sp = new SecurityConfig();
|
||||||
data.data.put("authorization", makeMap("class", "solr."+RuleBasedAuthorizationPlugin.class.getSimpleName()));
|
sp.setData(makeMap("authentication", makeMap("class", "solr."+ BasicAuthPlugin.class.getSimpleName())));
|
||||||
m.put("/security.json", data);
|
sp.setVersion(1);
|
||||||
|
sp.getData().put("authorization", makeMap("class", "solr."+RuleBasedAuthorizationPlugin.class.getSimpleName()));
|
||||||
|
m.put("/security.json", sp);
|
||||||
|
|
||||||
basicAuthPlugin.init(new HashMap<>());
|
basicAuthPlugin.init(new HashMap<>());
|
||||||
|
|
||||||
rulesBasedAuthorizationPlugin.init(new HashMap<>());
|
rulesBasedAuthorizationPlugin.init(new HashMap<>());
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<String, Object> getM() {
|
public Map<String, SecurityConfig> getM() {
|
||||||
return m;
|
return m;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -212,24 +213,25 @@ public class SecurityConfHandlerTest extends SolrTestCaseJ4 {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
ConfigData getSecurityProps(boolean getFresh) {
|
protected void getConf(SolrQueryResponse rsp, String key) {
|
||||||
return (ConfigData) m.get("/security.json");
|
// NOP
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
boolean persistConf(String key, byte[] buf, int version) {
|
public SecurityConfig getSecurityConfig(boolean getFresh) {
|
||||||
Object data = m.get(key);
|
return m.get("/security.json");
|
||||||
if (data instanceof ConfigData) {
|
}
|
||||||
ConfigData configData = (ConfigData) data;
|
|
||||||
if (configData.version == version) {
|
@Override
|
||||||
ConfigData result = new ConfigData((Map<String, Object>) Utils.fromJSON(buf), version + 1);
|
protected boolean persistConf(SecurityConfig props) {
|
||||||
m.put(key, result);
|
SecurityConfig fromMap = m.get("/security.json");
|
||||||
return true;
|
if (fromMap.getVersion() == props.getVersion()) {
|
||||||
} else {
|
props.setVersion(props.getVersion()+1);
|
||||||
return false;
|
m.put("/security.json", props);
|
||||||
}
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
throw new RuntimeException();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -254,7 +256,7 @@ public class SecurityConfHandlerTest extends SolrTestCaseJ4 {
|
||||||
req.setContentStreams(Collections.singletonList(o));
|
req.setContentStreams(Collections.singletonList(o));
|
||||||
SolrQueryResponse rsp = new SolrQueryResponse();
|
SolrQueryResponse rsp = new SolrQueryResponse();
|
||||||
handleRequestBody(req, rsp);
|
handleRequestBody(req, rsp);
|
||||||
Map<String, Object> data = ((ConfigData) m.get("/security.json")).data;
|
Map<String, Object> data = m.get("/security.json").getData();
|
||||||
((Map)data.get("authentication")).remove("");
|
((Map)data.get("authentication")).remove("");
|
||||||
((Map)data.get("authorization")).remove("");
|
((Map)data.get("authorization")).remove("");
|
||||||
return Utils.toJSONString (data);
|
return Utils.toJSONString (data);
|
||||||
|
|
|
@ -292,11 +292,11 @@ public class BasicAuthIntegrationTest extends SolrCloudTestCase {
|
||||||
return l.isEmpty() ? null : l.get(0);
|
return l.isEmpty() ? null : l.get(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
static final Predicate NOT_NULL_PREDICATE = o -> o != null;
|
protected static final Predicate NOT_NULL_PREDICATE = o -> o != null;
|
||||||
|
|
||||||
//the password is 'SolrRocks'
|
//the password is 'SolrRocks'
|
||||||
//this could be generated everytime. But , then we will not know if there is any regression
|
//this could be generated everytime. But , then we will not know if there is any regression
|
||||||
private static final String STD_CONF = "{\n" +
|
protected static final String STD_CONF = "{\n" +
|
||||||
" 'authentication':{\n" +
|
" 'authentication':{\n" +
|
||||||
" 'class':'solr.BasicAuthPlugin',\n" +
|
" 'class':'solr.BasicAuthPlugin',\n" +
|
||||||
" 'credentials':{'solr':'orwp2Ghgj39lmnrZOTm7Qtre1VqHFDfwAEzr0ApbN3Y= Ju5osoAqOX8iafhWpPP01E5P+sg8tK8tHON7rCYZRRw='}},\n" +
|
" 'credentials':{'solr':'orwp2Ghgj39lmnrZOTm7Qtre1VqHFDfwAEzr0ApbN3Y= Ju5osoAqOX8iafhWpPP01E5P+sg8tK8tHON7rCYZRRw='}},\n" +
|
||||||
|
|
|
@ -0,0 +1,220 @@
|
||||||
|
/*
|
||||||
|
* 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.solr.security;
|
||||||
|
|
||||||
|
import java.lang.invoke.MethodHandles;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
import org.apache.http.HttpResponse;
|
||||||
|
import org.apache.http.client.HttpClient;
|
||||||
|
import org.apache.http.client.methods.HttpPost;
|
||||||
|
import org.apache.http.entity.ByteArrayEntity;
|
||||||
|
import org.apache.http.message.AbstractHttpMessage;
|
||||||
|
import org.apache.http.message.BasicHeader;
|
||||||
|
import org.apache.solr.client.solrj.SolrRequest;
|
||||||
|
import org.apache.solr.client.solrj.embedded.JettySolrRunner;
|
||||||
|
import org.apache.solr.client.solrj.impl.HttpClientUtil;
|
||||||
|
import org.apache.solr.client.solrj.impl.HttpSolrClient;
|
||||||
|
import org.apache.solr.client.solrj.request.GenericSolrRequest;
|
||||||
|
import org.apache.solr.common.params.ModifiableSolrParams;
|
||||||
|
import org.apache.solr.common.util.Base64;
|
||||||
|
import org.apache.solr.common.util.ContentStreamBase;
|
||||||
|
import org.apache.solr.common.util.Utils;
|
||||||
|
import org.apache.solr.handler.admin.SecurityConfHandler;
|
||||||
|
import org.apache.solr.handler.admin.SecurityConfHandlerLocalForTesting;
|
||||||
|
import org.apache.solr.util.AbstractSolrTestCase;
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||||
|
import static org.apache.solr.security.BasicAuthIntegrationTest.NOT_NULL_PREDICATE;
|
||||||
|
import static org.apache.solr.security.BasicAuthIntegrationTest.STD_CONF;
|
||||||
|
import static org.apache.solr.security.BasicAuthIntegrationTest.verifySecurityStatus;
|
||||||
|
|
||||||
|
public class BasicAuthStandaloneTest extends AbstractSolrTestCase {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
|
||||||
|
private Path ROOT_DIR = Paths.get(getSolrHome());
|
||||||
|
private Path CONF_DIR = ROOT_DIR.resolve("configsets").resolve("configset-2").resolve("conf");
|
||||||
|
|
||||||
|
SecurityConfHandlerLocalForTesting securityConfHandler;
|
||||||
|
SolrInstance instance = null;
|
||||||
|
JettySolrRunner jetty;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
@Override
|
||||||
|
public void setUp() throws Exception
|
||||||
|
{
|
||||||
|
super.setUp();
|
||||||
|
instance = new SolrInstance("inst", null);
|
||||||
|
instance.setUp();
|
||||||
|
System.setProperty("solr.solr.home", instance.getHomeDir().toString());
|
||||||
|
jetty = createJetty(instance);
|
||||||
|
initCore("solrconfig.xml", "schema.xml", instance.getHomeDir().toString());
|
||||||
|
securityConfHandler = new SecurityConfHandlerLocalForTesting(jetty.getCoreContainer());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@After
|
||||||
|
public void tearDown() throws Exception {
|
||||||
|
jetty.stop();
|
||||||
|
super.tearDown();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBasicAuth() throws Exception {
|
||||||
|
|
||||||
|
String authcPrefix = "/admin/authentication";
|
||||||
|
|
||||||
|
HttpClient cl = null;
|
||||||
|
HttpSolrClient httpSolrClient = null;
|
||||||
|
try {
|
||||||
|
cl = HttpClientUtil.createClient(null);
|
||||||
|
String baseUrl = buildUrl(jetty.getLocalPort(), "/solr");
|
||||||
|
httpSolrClient = getHttpSolrClient(baseUrl);
|
||||||
|
|
||||||
|
verifySecurityStatus(cl, baseUrl + authcPrefix, "/errorMessages", null, 20);
|
||||||
|
|
||||||
|
// Write security.json locally. Should cause security to be initialized
|
||||||
|
securityConfHandler.persistConf(new SecurityConfHandler.SecurityConfig()
|
||||||
|
.setData(Utils.fromJSONString(STD_CONF.replaceAll("'", "\""))));
|
||||||
|
securityConfHandler.securityConfEdited();
|
||||||
|
verifySecurityStatus(cl, baseUrl + authcPrefix, "authentication/class", "solr.BasicAuthPlugin", 20);
|
||||||
|
|
||||||
|
String command = "{\n" +
|
||||||
|
"'set-user': {'harry':'HarryIsCool'}\n" +
|
||||||
|
"}";
|
||||||
|
|
||||||
|
GenericSolrRequest genericReq = new GenericSolrRequest(SolrRequest.METHOD.POST, authcPrefix, new ModifiableSolrParams());
|
||||||
|
genericReq.setContentStreams(Collections.singletonList(new ContentStreamBase.ByteArrayStream(command.getBytes(UTF_8), "")));
|
||||||
|
|
||||||
|
HttpSolrClient finalHttpSolrClient = httpSolrClient;
|
||||||
|
HttpSolrClient.RemoteSolrException exp = expectThrows(HttpSolrClient.RemoteSolrException.class, () -> {
|
||||||
|
finalHttpSolrClient.request(genericReq);
|
||||||
|
});
|
||||||
|
assertEquals(401, exp.code());
|
||||||
|
|
||||||
|
command = "{\n" +
|
||||||
|
"'set-user': {'harry':'HarryIsUberCool'}\n" +
|
||||||
|
"}";
|
||||||
|
|
||||||
|
HttpPost httpPost = new HttpPost(baseUrl + authcPrefix);
|
||||||
|
setBasicAuthHeader(httpPost, "solr", "SolrRocks");
|
||||||
|
httpPost.setEntity(new ByteArrayEntity(command.getBytes(UTF_8)));
|
||||||
|
httpPost.addHeader("Content-Type", "application/json; charset=UTF-8");
|
||||||
|
verifySecurityStatus(cl, baseUrl + authcPrefix, "authentication.enabled", "true", 20);
|
||||||
|
HttpResponse r = cl.execute(httpPost);
|
||||||
|
int statusCode = r.getStatusLine().getStatusCode();
|
||||||
|
Utils.consumeFully(r.getEntity());
|
||||||
|
assertEquals("proper_cred sent, but access denied", 200, statusCode);
|
||||||
|
|
||||||
|
verifySecurityStatus(cl, baseUrl + authcPrefix, "authentication/credentials/harry", NOT_NULL_PREDICATE, 20);
|
||||||
|
|
||||||
|
// Read file from SOLR_HOME and verify that it contains our new user
|
||||||
|
assertTrue(new String(Utils.toJSON(securityConfHandler.getSecurityConfig(false).getData()),
|
||||||
|
Charset.forName("UTF-8")).contains("harry"));
|
||||||
|
} finally {
|
||||||
|
if (cl != null) {
|
||||||
|
HttpClientUtil.close(cl);
|
||||||
|
httpSolrClient.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setBasicAuthHeader(AbstractHttpMessage httpMsg, String user, String pwd) {
|
||||||
|
String userPass = user + ":" + pwd;
|
||||||
|
String encoded = Base64.byteArrayToBase64(userPass.getBytes(UTF_8));
|
||||||
|
httpMsg.setHeader(new BasicHeader("Authorization", "Basic " + encoded));
|
||||||
|
log.info("Added Basic Auth security Header {}",encoded );
|
||||||
|
}
|
||||||
|
|
||||||
|
private JettySolrRunner createJetty(SolrInstance instance) throws Exception {
|
||||||
|
Properties nodeProperties = new Properties();
|
||||||
|
nodeProperties.setProperty("solr.data.dir", instance.getDataDir().toString());
|
||||||
|
JettySolrRunner jetty = new JettySolrRunner(instance.getHomeDir().toString(), nodeProperties, buildJettyConfig("/solr"));
|
||||||
|
jetty.start();
|
||||||
|
return jetty;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private class SolrInstance {
|
||||||
|
String name;
|
||||||
|
Integer port;
|
||||||
|
Path homeDir;
|
||||||
|
Path confDir;
|
||||||
|
Path dataDir;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* if masterPort is null, this instance is a master -- otherwise this instance is a slave, and assumes the master is
|
||||||
|
* on localhost at the specified port.
|
||||||
|
*/
|
||||||
|
public SolrInstance(String name, Integer port) {
|
||||||
|
this.name = name;
|
||||||
|
this.port = port;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Path getHomeDir() {
|
||||||
|
return homeDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Path getSchemaFile() {
|
||||||
|
return CONF_DIR.resolve("schema.xml");
|
||||||
|
}
|
||||||
|
|
||||||
|
public Path getConfDir() {
|
||||||
|
return confDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Path getDataDir() {
|
||||||
|
return dataDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Path getSolrConfigFile() {
|
||||||
|
return CONF_DIR.resolve("solrconfig.xml");
|
||||||
|
}
|
||||||
|
|
||||||
|
public Path getSolrXmlFile() {
|
||||||
|
return ROOT_DIR.resolve("solr.xml");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void setUp() throws Exception {
|
||||||
|
homeDir = createTempDir(name).toAbsolutePath();
|
||||||
|
dataDir = homeDir.resolve("collection1").resolve("data");
|
||||||
|
confDir = homeDir.resolve("collection1").resolve("conf");
|
||||||
|
|
||||||
|
Files.createDirectories(homeDir);
|
||||||
|
Files.createDirectories(dataDir);
|
||||||
|
Files.createDirectories(confDir);
|
||||||
|
|
||||||
|
Files.copy(getSolrXmlFile(), homeDir.resolve("solr.xml"));
|
||||||
|
Files.copy(getSolrConfigFile(), confDir.resolve("solrconfig.xml"));
|
||||||
|
Files.copy(getSchemaFile(), confDir.resolve("schema.xml"));
|
||||||
|
|
||||||
|
Files.createFile(homeDir.resolve("collection1").resolve("core.properties"));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue