This is an automated email from the ASF dual-hosted git repository.
epugh pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/solr.git
The following commit(s) were added to refs/heads/main by this push:
new 54aa923f008 SOLR-12813: subqueries should respect basic auth (#2404)
54aa923f008 is described below
commit 54aa923f008108c54bfa608d4770ea86b15ae291
Author: Rudi Seitz <[email protected]>
AuthorDate: Sat Apr 27 08:09:22 2024 -0400
SOLR-12813: subqueries should respect basic auth (#2404)
Fix issue where a basic auth user principal is lost when issuing a subquery
in a way that spans multiple shards:
- SubQueryAugmenterFactory now passes the user principal from the main
SolrQueryRequest down into each QueryRequest created for the subquery.
- EmbeddedSolrServer now preserves the user principal when it transforms a
SolrRequest into a SolrQueryRequest. The method that does this transformation
is SolrRequestParsers.buildRequestFrom(). This method now accepts a user
principal and returns a SolrQueryRequestBase that returns the same principal
provided.
---------
Co-authored-by: Eric Pugh <[email protected]>
---
solr/CHANGES.txt | 2 +-
.../client/solrj/embedded/EmbeddedSolrServer.java | 5 +-
.../transform/SubQueryAugmenterFactory.java | 14 +++-
.../apache/solr/servlet/SolrRequestParsers.java | 20 ++++-
.../transform/TestSubQueryTransformerDistrib.java | 90 ++++++++++++++++++----
5 files changed, 106 insertions(+), 25 deletions(-)
diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index 1fdfcf6d382..ed71e524e0b 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -107,7 +107,7 @@ Optimizations
Bug Fixes
---------------------
-(No changes)
+* SOLR-12813: subqueries should respect basic auth. (Rudy Seitz via Eric Pugh)
Dependency Upgrades
---------------------
diff --git
a/solr/core/src/java/org/apache/solr/client/solrj/embedded/EmbeddedSolrServer.java
b/solr/core/src/java/org/apache/solr/client/solrj/embedded/EmbeddedSolrServer.java
index eaef35304be..5706543def4 100644
---
a/solr/core/src/java/org/apache/solr/client/solrj/embedded/EmbeddedSolrServer.java
+++
b/solr/core/src/java/org/apache/solr/client/solrj/embedded/EmbeddedSolrServer.java
@@ -215,8 +215,9 @@ public class EmbeddedSolrServer extends SolrClient {
if (handler == null) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "unknown
handler: " + path);
}
-
- req = _parser.buildRequestFrom(core, params, getContentStreams(request));
+ req =
+ _parser.buildRequestFrom(
+ core, params, getContentStreams(request),
request.getUserPrincipal());
req.getContext().put(PATH, path);
req.getContext().put("httpMethod", request.getMethod().name());
SolrQueryResponse rsp = new SolrQueryResponse();
diff --git
a/solr/core/src/java/org/apache/solr/response/transform/SubQueryAugmenterFactory.java
b/solr/core/src/java/org/apache/solr/response/transform/SubQueryAugmenterFactory.java
index 218bdef0998..713dff67053 100644
---
a/solr/core/src/java/org/apache/solr/response/transform/SubQueryAugmenterFactory.java
+++
b/solr/core/src/java/org/apache/solr/response/transform/SubQueryAugmenterFactory.java
@@ -16,6 +16,7 @@
*/
package org.apache.solr.response.transform;
+import java.security.Principal;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
@@ -25,6 +26,7 @@ import org.apache.lucene.search.Query;
import org.apache.lucene.search.TotalHits;
import org.apache.solr.client.solrj.SolrClient;
import org.apache.solr.client.solrj.embedded.EmbeddedSolrServer;
+import org.apache.solr.client.solrj.request.QueryRequest;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.common.SolrDocument;
import org.apache.solr.common.SolrDocumentList;
@@ -113,7 +115,8 @@ public class SubQueryAugmenterFactory extends
TransformerFactory {
field,
field,
subParams,
- params.get(TermsQParserPlugin.SEPARATOR, ","));
+ params.get(TermsQParserPlugin.SEPARATOR, ","),
+ req.getUserPrincipal());
}
@SuppressWarnings("unchecked")
@@ -303,6 +306,7 @@ class SubQueryAugmenter extends DocTransformer {
private final String separator;
private final SolrClient server;
private final String coreName;
+ private final Principal principal;
public SubQueryAugmenter(
SolrClient server,
@@ -310,13 +314,15 @@ class SubQueryAugmenter extends DocTransformer {
String name,
String prefix,
SolrParams baseSubParams,
- String separator) {
+ String separator,
+ Principal principal) {
this.name = name;
this.prefix = prefix;
this.baseSubParams = baseSubParams;
this.separator = separator;
this.server = server;
this.coreName = coreName;
+ this.principal = principal;
}
@Override
@@ -340,7 +346,9 @@ class SubQueryAugmenter extends DocTransformer {
final SolrParams docWithDeprefixed =
SolrParams.wrapDefaults(new DocRowParams(doc, prefix, separator),
baseSubParams);
try {
- QueryResponse rsp = server.query(coreName, docWithDeprefixed);
+ QueryRequest req = new QueryRequest(docWithDeprefixed);
+ req.setUserPrincipal(principal);
+ QueryResponse rsp = req.process(server, coreName);
SolrDocumentList docList = rsp.getResults();
doc.setField(getName(), new Result(docList));
} catch (Exception e) {
diff --git a/solr/core/src/java/org/apache/solr/servlet/SolrRequestParsers.java
b/solr/core/src/java/org/apache/solr/servlet/SolrRequestParsers.java
index 17ddb4b09d1..91d7e2b2498 100644
--- a/solr/core/src/java/org/apache/solr/servlet/SolrRequestParsers.java
+++ b/solr/core/src/java/org/apache/solr/servlet/SolrRequestParsers.java
@@ -168,7 +168,8 @@ public class SolrRequestParsers {
ArrayList<ContentStream> streams = new ArrayList<>(1);
SolrParams params = parser.parseParamsAndFillStreams(req, streams);
- SolrQueryRequest sreq = buildRequestFrom(core, params, streams,
getRequestTimer(req), req);
+ SolrQueryRequest sreq =
+ buildRequestFrom(core, params, streams, getRequestTimer(req), req,
null);
// Handlers and login will want to know the path. If it contains a ':'
// the handler could use it for RESTful URLs
@@ -184,7 +185,13 @@ public class SolrRequestParsers {
/** For embedded Solr use; not related to HTTP. */
public SolrQueryRequest buildRequestFrom(
SolrCore core, SolrParams params, Collection<ContentStream> streams)
throws Exception {
- return buildRequestFrom(core, params, streams, new RTimerTree(), null);
+ return buildRequestFrom(core, params, streams, new RTimerTree(), null,
null);
+ }
+
+ public SolrQueryRequest buildRequestFrom(
+ SolrCore core, SolrParams params, Collection<ContentStream> streams,
Principal principal)
+ throws Exception {
+ return buildRequestFrom(core, params, streams, new RTimerTree(), null,
principal);
}
private SolrQueryRequest buildRequestFrom(
@@ -192,7 +199,8 @@ public class SolrRequestParsers {
SolrParams params,
Collection<ContentStream> streams,
RTimerTree requestTimer,
- final HttpServletRequest req)
+ final HttpServletRequest req,
+ final Principal principal)
throws Exception {
// The content type will be applied to all streaming content
String contentType = params.get(CommonParams.STREAM_CONTENTTYPE);
@@ -252,7 +260,11 @@ public class SolrRequestParsers {
new SolrQueryRequestBase(core, params, requestTimer) {
@Override
public Principal getUserPrincipal() {
- return req == null ? null : req.getUserPrincipal();
+ if (principal != null) {
+ return principal;
+ } else {
+ return req == null ? null : req.getUserPrincipal();
+ }
}
@Override
diff --git
a/solr/core/src/test/org/apache/solr/response/transform/TestSubQueryTransformerDistrib.java
b/solr/core/src/test/org/apache/solr/response/transform/TestSubQueryTransformerDistrib.java
index 0e92d08fcb2..c45541a8764 100644
---
a/solr/core/src/test/org/apache/solr/response/transform/TestSubQueryTransformerDistrib.java
+++
b/solr/core/src/test/org/apache/solr/response/transform/TestSubQueryTransformerDistrib.java
@@ -16,17 +16,26 @@
*/
package org.apache.solr.response.transform;
+import static java.util.Collections.singletonList;
+import static java.util.Collections.singletonMap;
+import static
org.apache.solr.security.Sha256AuthenticationProvider.getSaltedHashedValue;
+
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
+import java.net.URLConnection;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.ArrayList;
+import java.util.Base64;
import java.util.Iterator;
import java.util.List;
+import java.util.Map;
import java.util.Random;
import org.apache.solr.JSONTestUtil;
+import org.apache.solr.client.solrj.SolrRequest;
+import org.apache.solr.client.solrj.SolrResponse;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.impl.CloudSolrClient;
import org.apache.solr.client.solrj.request.CollectionAdminRequest;
@@ -40,6 +49,9 @@ import org.apache.solr.common.SolrDocumentList;
import org.apache.solr.common.cloud.ZkStateReader;
import org.apache.solr.common.params.ModifiableSolrParams;
import org.apache.solr.common.util.ContentStreamBase;
+import org.apache.solr.common.util.Utils;
+import org.apache.solr.security.BasicAuthPlugin;
+import org.apache.solr.security.RuleBasedAuthorizationPlugin;
import org.junit.BeforeClass;
import org.junit.Test;
@@ -51,6 +63,8 @@ public class TestSubQueryTransformerDistrib extends
SolrCloudTestCase {
static final String people = "people";
static final String depts = "departments";
private static boolean differentUniqueId;
+ private static final String USER = "solr";
+ private static final String PASS = "SolrRocksAgain";
@BeforeClass
public static void setupCluster() throws Exception {
@@ -61,22 +75,54 @@ public class TestSubQueryTransformerDistrib extends
SolrCloudTestCase {
String configName = "solrCloudCollectionConfig";
int nodeCount = 5;
- configureCluster(nodeCount).addConfig(configName, configDir).configure();
+
+ final String SECURITY_JSON =
+ Utils.toJSONString(
+ Map.of(
+ "authorization",
+ Map.of(
+ "class",
+ RuleBasedAuthorizationPlugin.class.getName(),
+ "user-role",
+ singletonMap(USER, "admin"),
+ "permissions",
+ singletonList(Map.of("name", "all", "role", "admin"))),
+ "authentication",
+ Map.of(
+ "class",
+ BasicAuthPlugin.class.getName(),
+ "blockUnknown",
+ true,
+ "credentials",
+ singletonMap(USER, getSaltedHashedValue(PASS)),
+ "forwardCredentials", // forward basic auth credentials
during internode
+ // requests instead of relying on PKI authentication; the
test should pass
+ // regardless of the setting here, but "true" is the more
interesting case that
+ // demonstrates that basic auth credentials are being
properly handled in the
+ // context of a distributed subquery
+ true)));
+
+ configureCluster(nodeCount)
+ .addConfig(configName, configDir)
+ .withSecurityJson(SECURITY_JSON)
+ .configure();
int shards = 2;
int replicas = 2;
- CollectionAdminRequest.createCollection(people, configName, shards,
replicas)
- .withProperty("config", "solrconfig-doctransformers.xml")
- .withProperty("schema", "schema-docValuesJoin.xml")
+ withBasicAuth(
+ CollectionAdminRequest.createCollection(people, configName,
shards, replicas)
+ .withProperty("config", "solrconfig-doctransformers.xml")
+ .withProperty("schema", "schema-docValuesJoin.xml"))
.process(cluster.getSolrClient());
- CollectionAdminRequest.createCollection(depts, configName, shards,
replicas)
- .withProperty("config", "solrconfig-doctransformers.xml")
- .withProperty(
- "schema",
- differentUniqueId
- ? "schema-minimal-with-another-uniqkey.xml"
- : "schema-docValuesJoin.xml")
+ withBasicAuth(
+ CollectionAdminRequest.createCollection(depts, configName, shards,
replicas)
+ .withProperty("config", "solrconfig-doctransformers.xml")
+ .withProperty(
+ "schema",
+ differentUniqueId
+ ? "schema-minimal-with-another-uniqkey.xml"
+ : "schema-docValuesJoin.xml"))
.process(cluster.getSolrClient());
CloudSolrClient client = cluster.getSolrClient();
@@ -134,7 +180,7 @@ public class TestSubQueryTransformerDistrib extends
SolrCloudTestCase {
final SolrDocumentList hits;
{
- final QueryRequest qr = new QueryRequest(params);
+ final QueryRequest qr = withBasicAuth(new QueryRequest(params));
final QueryResponse rsp = new QueryResponse();
rsp.setResponse(cluster.getSolrClient().request(qr, people + "," +
depts));
hits = rsp.getResults();
@@ -170,7 +216,17 @@ public class TestSubQueryTransformerDistrib extends
SolrCloudTestCase {
+ "/select"
+ params.toQueryString());
- try (final InputStream jsonResponse = node.openStream()) {
+ final URLConnection urlConnectionWithoutAuth = node.openConnection();
+ assertThrows(Exception.class, () ->
urlConnectionWithoutAuth.getInputStream());
+ final URLConnection urlConnection = node.openConnection();
+ String basicAuth =
+ "Basic "
+ + new String(
+ Base64.getEncoder().encode((USER + ":" +
PASS).getBytes(StandardCharsets.UTF_8)),
+ StandardCharsets.UTF_8);
+ urlConnection.setRequestProperty("Authorization", basicAuth);
+
+ try (final InputStream jsonResponse = urlConnection.getInputStream()) {
final ByteArrayOutputStream outBuffer = new ByteArrayOutputStream();
jsonResponse.transferTo(outBuffer);
@@ -357,12 +413,16 @@ public class TestSubQueryTransformerDistrib extends
SolrCloudTestCase {
}
upd.append("</update>");
- ContentStreamUpdateRequest req = new
ContentStreamUpdateRequest("/update");
+ ContentStreamUpdateRequest req = withBasicAuth(new
ContentStreamUpdateRequest("/update"));
req.addContentStream(new
ContentStreamBase.StringStream(upd.toString(), "text/xml"));
-
cluster.getSolrClient().request(req, collection);
upd.setLength("<update>".length());
}
}
}
+
+ private static <T extends SolrRequest<? extends SolrResponse>> T
withBasicAuth(T req) {
+ req.setBasicAuthCredentials(USER, PASS);
+ return req;
+ }
}