| /* |
| * 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.io.IOException; |
| 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.Header; |
| 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.SolrTestCaseJ4; |
| 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.common.params.MapSolrParams; |
| import org.apache.solr.common.util.Base64; |
| import org.apache.solr.common.util.Utils; |
| import org.apache.solr.handler.admin.SecurityConfHandler; |
| import org.apache.solr.handler.admin.SecurityConfHandlerLocalForTesting; |
| 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.cloud.SolrCloudAuthTestCase.NOT_NULL_PREDICATE; |
| import static org.apache.solr.security.BasicAuthIntegrationTest.STD_CONF; |
| import static org.apache.solr.security.BasicAuthIntegrationTest.verifySecurityStatus; |
| |
| public class BasicAuthStandaloneTest extends SolrTestCaseJ4 { |
| |
| private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); |
| private static final Path ROOT_DIR = Paths.get(TEST_HOME()); |
| private static final 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(); |
| jetty = createAndStartJetty(instance); |
| securityConfHandler = new SecurityConfHandlerLocalForTesting(jetty.getCoreContainer()); |
| HttpClientUtil.clearRequestInterceptors(); // Clear out any old Authorization headers |
| } |
| |
| @Override |
| @After |
| public void tearDown() throws Exception { |
| if (null != jetty) { |
| jetty.stop(); |
| jetty = null; |
| } |
| super.tearDown(); |
| } |
| |
| @Test |
| public void testBasicAuth() throws Exception { |
| |
| String authcPrefix = "/admin/authentication"; |
| String authzPrefix = "/admin/authorization"; |
| |
| 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" + |
| "}"; |
| |
| doHttpPost(cl, baseUrl + authcPrefix, command, null, null, 401); |
| verifySecurityStatus(cl, baseUrl + authcPrefix, "authentication.enabled", "true", 20); |
| |
| command = "{\n" + |
| "'set-user': {'harry':'HarryIsUberCool'}\n" + |
| "}"; |
| |
| |
| doHttpPost(cl, baseUrl + authcPrefix, command, "solr", "SolrRocks"); |
| 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")); |
| |
| // Edit authorization |
| verifySecurityStatus(cl, baseUrl + authzPrefix, "authorization/permissions[1]/role", null, 20); |
| doHttpPost(cl, baseUrl + authzPrefix, "{'set-permission': {'name': 'update', 'role':'updaterole'}}", "solr", "SolrRocks"); |
| command = "{\n" + |
| "'set-permission': {'name': 'read', 'role':'solr'}\n" + |
| "}"; |
| doHttpPost(cl, baseUrl + authzPrefix, command, "solr", "SolrRocks"); |
| try { |
| httpSolrClient.query("collection1", new MapSolrParams(Collections.singletonMap("q", "foo"))); |
| fail("Should return a 401 response"); |
| } catch (Exception e) { |
| // Test that the second doPost request to /security/authorization went through |
| verifySecurityStatus(cl, baseUrl + authzPrefix, "authorization/permissions[2]/role", "solr", 20); |
| } |
| } finally { |
| if (cl != null) { |
| HttpClientUtil.close(cl); |
| httpSolrClient.close(); |
| } |
| } |
| } |
| |
| static void doHttpPost(HttpClient cl, String url, String jsonCommand, String basicUser, String basicPass) throws IOException { |
| doHttpPost(cl, url, jsonCommand, basicUser, basicPass, 200); |
| } |
| |
| static void doHttpPost(HttpClient cl, String url, String jsonCommand, String basicUser, String basicPass, int expectStatusCode) throws IOException { |
| doHttpPostWithHeader(cl, url, jsonCommand, getBasicAuthHeader(basicUser, basicPass), expectStatusCode); |
| } |
| |
| static void doHttpPostWithHeader(HttpClient cl, String url, String jsonCommand, Header header, int expectStatusCode) throws IOException { |
| HttpPost httpPost = new HttpPost(url); |
| httpPost.setHeader(header); |
| httpPost.setEntity(new ByteArrayEntity(jsonCommand.replaceAll("'", "\"").getBytes(UTF_8))); |
| httpPost.addHeader("Content-Type", "application/json; charset=UTF-8"); |
| HttpResponse r = cl.execute(httpPost); |
| int statusCode = r.getStatusLine().getStatusCode(); |
| Utils.consumeFully(r.getEntity()); |
| assertEquals("proper_cred sent, but access denied", expectStatusCode, statusCode); |
| } |
| |
| private static Header getBasicAuthHeader(String user, String pwd) { |
| String userPass = user + ":" + pwd; |
| String encoded = Base64.byteArrayToBase64(userPass.getBytes(UTF_8)); |
| return new BasicHeader("Authorization", "Basic " + encoded); |
| } |
| |
| public static void setBasicAuthHeader(AbstractHttpMessage httpMsg, String user, String pwd) { |
| final Header basicAuthHeader = getBasicAuthHeader(user, pwd); |
| httpMsg.setHeader(basicAuthHeader); |
| if (log.isInfoEnabled()) { |
| log.info("Added Basic Auth security Header {}", basicAuthHeader.getValue()); |
| } |
| } |
| |
| static JettySolrRunner createAndStartJetty(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; |
| } |
| |
| |
| static class SolrInstance { |
| String name; |
| Integer port; |
| Path homeDir; |
| Path confDir; |
| Path dataDir; |
| |
| /** |
| * if leaderPort is null, this instance is a leader -- otherwise this instance is a follower, and assumes the leader 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")); |
| } |
| |
| } |
| } |