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

Reply via email to