You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@solr.apache.org by th...@apache.org on 2021/11/03 19:25:04 UTC

[solr] branch main updated: SOLR-15766: MultiAuthPlugin should send non-AJAX anonymous requests to the plugin that allows anonymous requests (#394)

This is an automated email from the ASF dual-hosted git repository.

thelabdude 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 b3835dc  SOLR-15766: MultiAuthPlugin should send non-AJAX anonymous requests to the plugin that allows anonymous requests (#394)
b3835dc is described below

commit b3835dca64388f158d58a7f743e82304e0931b87
Author: Timothy Potter <th...@gmail.com>
AuthorDate: Wed Nov 3 13:24:55 2021 -0600

    SOLR-15766: MultiAuthPlugin should send non-AJAX anonymous requests to the plugin that allows anonymous requests (#394)
---
 solr/CHANGES.txt                                   |  2 ++
 .../org/apache/solr/security/MultiAuthPlugin.java  | 28 +++++++++++-----
 .../solr/security/multi_auth_plugin_security.json  |  8 ++++-
 .../apache/solr/security/MultiAuthPluginTest.java  | 38 +++++++++++++++++-----
 4 files changed, 59 insertions(+), 17 deletions(-)

diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index fe92035..aaa3db2 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -445,6 +445,8 @@ Bug Fixes
 
 * SOLR-15722: Delete Replica does not delete the Per replica state (noble)
 
+* SOLR-15766: MultiAuthPlugin should send non-AJAX anonymous requests to the plugin that allows anonymous requests (Timothy Potter, Eric Pugh)
+
 Build
 ---------------------
 
diff --git a/solr/core/src/java/org/apache/solr/security/MultiAuthPlugin.java b/solr/core/src/java/org/apache/solr/security/MultiAuthPlugin.java
index 2857005..8d99cfa 100644
--- a/solr/core/src/java/org/apache/solr/security/MultiAuthPlugin.java
+++ b/solr/core/src/java/org/apache/solr/security/MultiAuthPlugin.java
@@ -57,6 +57,7 @@ public class MultiAuthPlugin extends AuthenticationPlugin implements ConfigEdita
 
   private final Map<String, AuthenticationPlugin> pluginMap = new LinkedHashMap<>();
   private final ResourceLoader loader;
+  private AuthenticationPlugin allowsUnknown = null; // the first of our plugins that allows anonymous requests
 
   // Get the loader from the CoreContainer so we can load the sub-plugins, such as the BasicAuthPlugin for Basic
   public MultiAuthPlugin(CoreContainer cc) {
@@ -143,6 +144,13 @@ public class MultiAuthPlugin extends AuthenticationPlugin implements ConfigEdita
     AuthenticationPlugin pluginForScheme = loader.newInstance(clazz, AuthenticationPlugin.class);
     pluginForScheme.init(schemeConfig);
     pluginMap.put(scheme.toLowerCase(Locale.ROOT), pluginForScheme);
+
+    if (allowsUnknown == null) {
+      if (!Boolean.parseBoolean(String.valueOf(schemeConfig.getOrDefault("blockUnknown", true)))) {
+        // plugin allows anonymous requests, so we'll send any non-AJAX requests without an authorization header to it
+        allowsUnknown = pluginForScheme;
+      }
+    }
   }
 
   @Override
@@ -160,21 +168,25 @@ public class MultiAuthPlugin extends AuthenticationPlugin implements ConfigEdita
   @Override
   public boolean doAuthenticate(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws Exception {
     final String authHeader = request.getHeader(AUTHORIZATION_HEADER);
-
-    // if no Authorization header but is an AJAX request, forward to the default scheme so it can handle it
     if (authHeader == null) {
-      if (BasicAuthPlugin.isAjaxRequest(request)) {
-        // use the first scheme listed as the default
-        return pluginMap.values().iterator().next().doAuthenticate(request, response, filterChain);
+      // no Authorization header but if it's an AJAX request, forward to the default scheme so it can handle it
+      // otherwise, send to the first plugin that allows blockUnknown = false
+      final AuthenticationPlugin plugin = BasicAuthPlugin.isAjaxRequest(request) ? pluginMap.values().iterator().next() : allowsUnknown;
+      boolean result = false;
+      if (plugin != null) {
+        pluginInRequest.set(plugin);
+        result = plugin.doAuthenticate(request, response, filterChain);
+      } else {
+        response.sendError(ErrorCode.UNAUTHORIZED.code, "No Authorization header");
       }
-
-      throw new SolrException(ErrorCode.UNAUTHORIZED, "No Authorization header");
+      return result;
     }
 
     final String scheme = getSchemeFromAuthHeader(authHeader);
     final AuthenticationPlugin plugin = pluginMap.get(scheme);
     if (plugin == null) {
-      throw new SolrException(ErrorCode.SERVER_ERROR, "Authorization scheme '" + scheme + "' not supported!");
+      response.sendError(ErrorCode.UNAUTHORIZED.code, "Authorization scheme '" + scheme + "' not supported!");
+      return false;
     }
 
     pluginInRequest.set(plugin);
diff --git a/solr/core/src/test-files/solr/security/multi_auth_plugin_security.json b/solr/core/src/test-files/solr/security/multi_auth_plugin_security.json
index 0fd98fb..7fac044 100644
--- a/solr/core/src/test-files/solr/security/multi_auth_plugin_security.json
+++ b/solr/core/src/test-files/solr/security/multi_auth_plugin_security.json
@@ -3,7 +3,7 @@
     "class": "solr.MultiAuthPlugin",
     "schemes": [{
       "scheme": "basic",
-      "blockUnknown": false,
+      "blockUnknown": true,
       "class": "solr.BasicAuthPlugin",
       "credentials": {
         "admin": "orwp2Ghgj39lmnrZOTm7Qtre1VqHFDfwAEzr0ApbN3Y= Ju5osoAqOX8iafhWpPP01E5P+sg8tK8tHON7rCYZRRw="
@@ -33,6 +33,12 @@
     ],
     "permissions": [
       {
+        "name": "k8s-probe-0",
+        "role": null,
+        "collection": null,
+        "path": "/admin/info/system"
+      },
+      {
         "name": "read",
         "role": [
           "admin",
diff --git a/solr/core/src/test/org/apache/solr/security/MultiAuthPluginTest.java b/solr/core/src/test/org/apache/solr/security/MultiAuthPluginTest.java
index 177a0b4..0599113 100644
--- a/solr/core/src/test/org/apache/solr/security/MultiAuthPluginTest.java
+++ b/solr/core/src/test/org/apache/solr/security/MultiAuthPluginTest.java
@@ -19,6 +19,7 @@ package org.apache.solr.security;
 import javax.servlet.FilterChain;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
 import java.io.Serializable;
 import java.nio.charset.StandardCharsets;
 import java.security.Principal;
@@ -28,7 +29,9 @@ import java.util.Objects;
 import java.util.function.Predicate;
 
 import org.apache.commons.io.FileUtils;
+import org.apache.http.HttpResponse;
 import org.apache.http.client.HttpClient;
+import org.apache.http.client.methods.HttpGet;
 import org.apache.http.message.BasicHeader;
 import org.apache.solr.SolrTestCaseJ4;
 import org.apache.solr.client.solrj.embedded.JettySolrRunner;
@@ -54,7 +57,7 @@ public class MultiAuthPluginTest extends SolrTestCaseJ4 {
 
   private static final String authcPrefix = "/admin/authentication";
   private static final String authzPrefix = "/admin/authorization";
-  
+
   final Predicate<Object> NULL_PREDICATE = Objects::isNull;
   SecurityConfHandlerLocalForTesting securityConfHandler;
   JettySolrRunner jetty;
@@ -102,8 +105,19 @@ public class MultiAuthPluginTest extends SolrTestCaseJ4 {
       verifySecurityStatus(cl, baseUrl + authcPrefix, "authentication/class", "solr.MultiAuthPlugin", 5, user, pass);
       verifySecurityStatus(cl, baseUrl + authzPrefix, "authorization/class", "solr.MultiAuthRuleBasedAuthorizationPlugin", 5, user, pass);
 
-      // For the multi-auth plugin, every command is wrapped with an object that identifies the "scheme"
+      // anonymous requests are blocked by all plugins
+      int statusCode = doHttpGetAnonymous(cl, baseUrl + "/admin/info/system");
+      assertEquals("anonymous get succeeded but should not have", 401, statusCode);
+      // update blockUnknown to allow anonymous for the basic plugin
       String command = "{\n" +
+          "'set-property': { 'basic': {'blockUnknown':false} }\n" +
+          "}";
+      doHttpPost(cl, baseUrl + authcPrefix, command, user, pass, 200);
+      statusCode = doHttpGetAnonymous(cl, baseUrl + "/admin/info/system");
+      assertEquals("anonymous get failed but should have succeeded", 200, statusCode);
+
+      // For the multi-auth plugin, every command is wrapped with an object that identifies the "scheme"
+      command = "{\n" +
           "'set-user': {'harry':'HarryIsCool'}\n" +
           "}";
       // no scheme identified!
@@ -139,25 +153,25 @@ public class MultiAuthPluginTest extends SolrTestCaseJ4 {
       verifySecurityStatus(cl, baseUrl + authzPrefix, "authorization/schemes[0]/user-role/harry", NOT_NULL_PREDICATE, 5, user, pass);
 
       // give the users role a custom permission
-      verifySecurityStatus(cl, baseUrl + authzPrefix, "authorization/permissions[5]", NULL_PREDICATE, 5, user, pass);
+      verifySecurityStatus(cl, baseUrl + authzPrefix, "authorization/permissions[6]", NULL_PREDICATE, 5, user, pass);
       command = "{\n" +
           "'set-permission': { 'name':'k8s-zk', 'role':'users', 'collection':null, 'path':'/admin/zookeeper/status' }\n" +
           "}";
       doHttpPost(cl, baseUrl + authzPrefix, command, user, pass, 200);
-      verifySecurityStatus(cl, baseUrl + authzPrefix, "authorization/permissions[5]/path", new ExpectedValuePredicate("/admin/zookeeper/status"), 5, user, pass);
+      verifySecurityStatus(cl, baseUrl + authzPrefix, "authorization/permissions[6]/path", new ExpectedValuePredicate("/admin/zookeeper/status"), 5, user, pass);
 
       command = "{\n" +
-          "'update-permission': { 'index':'6', 'name':'k8s-zk', 'role':'users', 'collection':null, 'path':'/admin/zookeeper/status2' }\n" +
+          "'update-permission': { 'index':'7', 'name':'k8s-zk', 'role':'users', 'collection':null, 'path':'/admin/zookeeper/status2' }\n" +
           "}";
       doHttpPost(cl, baseUrl + authzPrefix, command, user, pass, 200);
-      verifySecurityStatus(cl, baseUrl + authzPrefix, "authorization/permissions[5]/path", new ExpectedValuePredicate("/admin/zookeeper/status2"), 5, user, pass);
+      verifySecurityStatus(cl, baseUrl + authzPrefix, "authorization/permissions[6]/path", new ExpectedValuePredicate("/admin/zookeeper/status2"), 5, user, pass);
 
       // delete the permission
       command = "{\n" +
-          "'delete-permission': 6\n" +
+          "'delete-permission': 7\n" +
           "}";
       doHttpPost(cl, baseUrl + authzPrefix, command, user, pass, 200);
-      verifySecurityStatus(cl, baseUrl + authzPrefix, "authorization/permissions[5]", NULL_PREDICATE, 5, user, pass);
+      verifySecurityStatus(cl, baseUrl + authzPrefix, "authorization/permissions[6]", NULL_PREDICATE, 5, user, pass);
 
       // delete the user
       command = "{\n" +
@@ -184,6 +198,14 @@ public class MultiAuthPluginTest extends SolrTestCaseJ4 {
     }
   }
 
+  private int doHttpGetAnonymous(HttpClient cl, String url) throws IOException {
+    HttpGet httpPost = new HttpGet(url);
+    HttpResponse r = cl.execute(httpPost);
+    int statusCode = r.getStatusLine().getStatusCode();
+    Utils.consumeFully(r.getEntity());
+    return statusCode;
+  }
+
   private static final class MockPrincipal implements Principal, Serializable {
     @Override
     public String getName() {