You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@solr.apache.org by ep...@apache.org on 2024/05/29 13:30:10 UTC

(solr) branch branch_9x updated: SOLR-17247: Fix bug - 'WWW-Authenticate' headers missing in MultiAuthPlugin (#2416)

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

epugh pushed a commit to branch branch_9x
in repository https://gitbox.apache.org/repos/asf/solr.git


The following commit(s) were added to refs/heads/branch_9x by this push:
     new 4bd265d81f9 SOLR-17247: Fix bug - 'WWW-Authenticate' headers missing in MultiAuthPlugin (#2416)
4bd265d81f9 is described below

commit 4bd265d81f9442b55d86dbb19723cde7ef04cd0f
Author: Lamine <10...@users.noreply.github.com>
AuthorDate: Wed May 29 08:29:35 2024 -0500

    SOLR-17247: Fix bug - 'WWW-Authenticate' headers missing in MultiAuthPlugin (#2416)
    
    Co-authored-by: Lamine Idjeraoui <li...@apple.com>
    Co-authored-by: Eric Pugh <ep...@opensourceconnections.com>
---
 solr/CHANGES.txt                                   |  2 +
 .../org/apache/solr/security/MultiAuthPlugin.java  | 26 +++++++++++++
 .../solr/security/multi_auth_plugin_security.json  |  6 +++
 .../apache/solr/security/MultiAuthPluginTest.java  | 43 ++++++++++++++++++++++
 4 files changed, 77 insertions(+)

diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index dbf5761b8e5..466092c741b 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -51,6 +51,8 @@ Bug Fixes
 
 * SOLR-16659: Properly construct V2 base urls instead of replacing substring "/solr" with "/api" (Andrey Bozhko via Eric Pugh)
 
+* SOLR-17247: 'WWW-Authenticate' headers missing in MultiAuthPlugin (Lamine Idjeraoui via Eric Pugh)
+
 Dependency Upgrades
 ---------------------
 (No changes)
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 96cd2974c21..5027cec6377 100644
--- a/solr/core/src/java/org/apache/solr/security/MultiAuthPlugin.java
+++ b/solr/core/src/java/org/apache/solr/security/MultiAuthPlugin.java
@@ -17,6 +17,7 @@
 package org.apache.solr.security;
 
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.LinkedHashMap;
@@ -26,6 +27,7 @@ import java.util.Map;
 import javax.servlet.FilterChain;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
+import org.apache.http.HttpHeaders;
 import org.apache.http.HttpRequest;
 import org.apache.http.protocol.HttpContext;
 import org.apache.lucene.util.ResourceLoader;
@@ -59,6 +61,8 @@ public class MultiAuthPlugin extends AuthenticationPlugin
   private static final String UNKNOWN_SCHEME = "";
 
   private final Map<String, AuthenticationPlugin> pluginMap = new LinkedHashMap<>();
+  private final Map<String, String> realms = new LinkedHashMap<>();
+  private final List<String> WWWAuthenticateHeaders = new ArrayList<>();
   private final ResourceLoader loader;
   // the first of our plugins that allows anonymous requests
   private AuthenticationPlugin allowsUnknown = null;
@@ -141,6 +145,7 @@ public class MultiAuthPlugin extends AuthenticationPlugin
       }
       initPluginForScheme((Map<String, Object>) s);
     }
+    initWWWAuthenticateHeaders();
   }
 
   protected void initPluginForScheme(Map<String, Object> schemeMap) {
@@ -158,6 +163,11 @@ public class MultiAuthPlugin extends AuthenticationPlugin
           ErrorCode.SERVER_ERROR, "'class' is a required attribute: " + schemeMap);
     }
 
+    String realm = (String) schemeConfig.remove("realm");
+    if (!StrUtils.isNullOrEmpty(realm)) {
+      realms.put(scheme, realm);
+    }
+
     AuthenticationPlugin pluginForScheme = loader.newInstance(clazz, AuthenticationPlugin.class);
     pluginForScheme.init(schemeConfig);
     pluginMap.put(scheme.toLowerCase(Locale.ROOT), pluginForScheme);
@@ -171,6 +181,20 @@ public class MultiAuthPlugin extends AuthenticationPlugin
     }
   }
 
+  private void initWWWAuthenticateHeaders() {
+    for (String scheme : pluginMap.keySet()) {
+      String realm = realms.get(scheme);
+      String realmStr = realm == null ? "" : " realm=\"" + realm + "\"";
+      WWWAuthenticateHeaders.add(scheme + realmStr);
+    }
+  }
+
+  private void addWWWAuthenticateHeaders(HttpServletResponse response) {
+    for (String wwwAuthHeader : WWWAuthenticateHeaders) {
+      response.addHeader(HttpHeaders.WWW_AUTHENTICATE, wwwAuthHeader);
+    }
+  }
+
   @Override
   public void initializeMetrics(SolrMetricsContext parentContext, String scope) {
     for (AuthenticationPlugin plugin : pluginMap.values()) {
@@ -202,6 +226,7 @@ public class MultiAuthPlugin extends AuthenticationPlugin
         pluginInRequest.set(plugin);
         result = plugin.doAuthenticate(request, response, filterChain);
       } else {
+        addWWWAuthenticateHeaders(response);
         response.sendError(ErrorCode.UNAUTHORIZED.code, "No Authorization header");
       }
       return result;
@@ -210,6 +235,7 @@ public class MultiAuthPlugin extends AuthenticationPlugin
     final String scheme = getSchemeFromAuthHeader(authHeader);
     final AuthenticationPlugin plugin = pluginMap.get(scheme);
     if (plugin == null) {
+      addWWWAuthenticateHeaders(response);
       response.sendError(
           ErrorCode.UNAUTHORIZED.code, "Authorization scheme '" + scheme + "' not supported!");
       return false;
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 7fac044e079..618a0ef789c 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,6 +3,7 @@
     "class": "solr.MultiAuthPlugin",
     "schemes": [{
       "scheme": "basic",
+      "realm": "basicRealm",
       "blockUnknown": true,
       "class": "solr.BasicAuthPlugin",
       "credentials": {
@@ -11,6 +12,11 @@
       "forwardCredentials": false
     },{
       "scheme": "mock",
+      "realm": "mockRealm",
+      "class": "org.apache.solr.security.MultiAuthPluginTest$MockAuthPluginForTesting",
+      "blockUnknown": true
+    },{
+      "scheme": "mockNoRealm",
       "class": "org.apache.solr.security.MultiAuthPluginTest$MockAuthPluginForTesting",
       "blockUnknown": true
     }]
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 1f3bd062121..7b6a821134d 100644
--- a/solr/core/src/test/org/apache/solr/security/MultiAuthPluginTest.java
+++ b/solr/core/src/test/org/apache/solr/security/MultiAuthPluginTest.java
@@ -28,13 +28,18 @@ import java.io.Serializable;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.security.Principal;
+import java.util.Arrays;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 import java.util.Objects;
 import java.util.function.Predicate;
+import java.util.stream.Collectors;
 import javax.servlet.FilterChain;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
+import org.apache.http.Header;
+import org.apache.http.HttpHeaders;
 import org.apache.http.HttpResponse;
 import org.apache.http.client.HttpClient;
 import org.apache.http.client.methods.HttpGet;
@@ -105,6 +110,10 @@ public class MultiAuthPluginTest extends SolrTestCaseJ4 {
           new SecurityConfHandler.SecurityConfig()
               .setData(Utils.fromJSONString(multiAuthPluginSecurityJson)));
       securityConfHandler.securityConfEdited();
+
+      // verify "WWW-Authenticate" headers are returned
+      verifyWWWAuthenticateHeaders(httpClient, baseUrl);
+
       verifySecurityStatus(
           httpClient,
           baseUrl + authcPrefix,
@@ -269,6 +278,40 @@ public class MultiAuthPluginTest extends SolrTestCaseJ4 {
     return statusCode;
   }
 
+  private void verifyWWWAuthenticateHeaders(HttpClient httpClient, String baseUrl)
+      throws Exception {
+    HttpGet httpGet = new HttpGet(baseUrl + "/admin/info/system");
+    HttpResponse response = httpClient.execute(httpGet);
+    Header[] headers = response.getHeaders(HttpHeaders.WWW_AUTHENTICATE);
+    List<String> actualSchemes =
+        Arrays.stream(headers).map(Header::getValue).collect(Collectors.toList());
+
+    List<String> expectedSchemes = generateExpectedSchemes();
+    actualSchemes.sort(String.CASE_INSENSITIVE_ORDER);
+    expectedSchemes.sort(String.CASE_INSENSITIVE_ORDER);
+
+    assertEquals(
+        "The actual schemes and realms should match the expected ones exactly",
+        expectedSchemes.stream().map(s -> s.toLowerCase(Locale.ROOT)).collect(Collectors.toList()),
+        actualSchemes.stream().map(s -> s.toLowerCase(Locale.ROOT)).collect(Collectors.toList()));
+  }
+
+  @SuppressWarnings("unchecked")
+  private List<String> generateExpectedSchemes() {
+    Map<String, Object> data = securityConfHandler.getSecurityConfig(false).getData();
+    Map<String, Object> authentication = (Map<String, Object>) data.get("authentication");
+    List<Map<String, Object>> schemes = (List<Map<String, Object>>) authentication.get("schemes");
+
+    return schemes.stream()
+        .map(
+            schemeMap -> {
+              String scheme = (String) schemeMap.get("scheme");
+              String realm = (String) schemeMap.get("realm");
+              return realm != null ? scheme + " realm=\"" + realm + "\"" : scheme;
+            })
+        .collect(Collectors.toList());
+  }
+
   private static final class MockPrincipal implements Principal, Serializable {
     @Override
     public String getName() {