Skip to content

Commit 14a2195

Browse files
laminelamLamine Idjeraouiepugh
authored
SOLR-17247: Fix bug - 'WWW-Authenticate' headers missing in MultiAuthPlugin (#2416)
Co-authored-by: Lamine Idjeraoui <[email protected]> Co-authored-by: Eric Pugh <[email protected]>
1 parent 437e279 commit 14a2195

File tree

4 files changed

+77
-0
lines changed

4 files changed

+77
-0
lines changed

solr/CHANGES.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,8 @@ Bug Fixes
145145

146146
* SOLR-16659: Properly construct V2 base urls instead of replacing substring "/solr" with "/api" (Andrey Bozhko via Eric Pugh)
147147

148+
* SOLR-17247: 'WWW-Authenticate' headers missing in MultiAuthPlugin (Lamine Idjeraoui via Eric Pugh)
149+
148150
Dependency Upgrades
149151
---------------------
150152
(No changes)

solr/core/src/java/org/apache/solr/security/MultiAuthPlugin.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package org.apache.solr.security;
1818

1919
import java.io.IOException;
20+
import java.util.ArrayList;
2021
import java.util.Collections;
2122
import java.util.HashMap;
2223
import java.util.LinkedHashMap;
@@ -26,6 +27,7 @@
2627
import javax.servlet.FilterChain;
2728
import javax.servlet.http.HttpServletRequest;
2829
import javax.servlet.http.HttpServletResponse;
30+
import org.apache.http.HttpHeaders;
2931
import org.apache.http.HttpRequest;
3032
import org.apache.http.protocol.HttpContext;
3133
import org.apache.lucene.util.ResourceLoader;
@@ -59,6 +61,8 @@ public class MultiAuthPlugin extends AuthenticationPlugin
5961
private static final String UNKNOWN_SCHEME = "";
6062

6163
private final Map<String, AuthenticationPlugin> pluginMap = new LinkedHashMap<>();
64+
private final Map<String, String> realms = new LinkedHashMap<>();
65+
private final List<String> WWWAuthenticateHeaders = new ArrayList<>();
6266
private final ResourceLoader loader;
6367
// the first of our plugins that allows anonymous requests
6468
private AuthenticationPlugin allowsUnknown = null;
@@ -141,6 +145,7 @@ public void init(Map<String, Object> pluginConfig) {
141145
}
142146
initPluginForScheme((Map<String, Object>) s);
143147
}
148+
initWWWAuthenticateHeaders();
144149
}
145150

146151
protected void initPluginForScheme(Map<String, Object> schemeMap) {
@@ -158,6 +163,11 @@ protected void initPluginForScheme(Map<String, Object> schemeMap) {
158163
ErrorCode.SERVER_ERROR, "'class' is a required attribute: " + schemeMap);
159164
}
160165

166+
String realm = (String) schemeConfig.remove("realm");
167+
if (!StrUtils.isNullOrEmpty(realm)) {
168+
realms.put(scheme, realm);
169+
}
170+
161171
AuthenticationPlugin pluginForScheme = loader.newInstance(clazz, AuthenticationPlugin.class);
162172
pluginForScheme.init(schemeConfig);
163173
pluginMap.put(scheme.toLowerCase(Locale.ROOT), pluginForScheme);
@@ -171,6 +181,20 @@ protected void initPluginForScheme(Map<String, Object> schemeMap) {
171181
}
172182
}
173183

184+
private void initWWWAuthenticateHeaders() {
185+
for (String scheme : pluginMap.keySet()) {
186+
String realm = realms.get(scheme);
187+
String realmStr = realm == null ? "" : " realm=\"" + realm + "\"";
188+
WWWAuthenticateHeaders.add(scheme + realmStr);
189+
}
190+
}
191+
192+
private void addWWWAuthenticateHeaders(HttpServletResponse response) {
193+
for (String wwwAuthHeader : WWWAuthenticateHeaders) {
194+
response.addHeader(HttpHeaders.WWW_AUTHENTICATE, wwwAuthHeader);
195+
}
196+
}
197+
174198
@Override
175199
public void initializeMetrics(SolrMetricsContext parentContext, String scope) {
176200
for (AuthenticationPlugin plugin : pluginMap.values()) {
@@ -202,6 +226,7 @@ public boolean doAuthenticate(
202226
pluginInRequest.set(plugin);
203227
result = plugin.doAuthenticate(request, response, filterChain);
204228
} else {
229+
addWWWAuthenticateHeaders(response);
205230
response.sendError(ErrorCode.UNAUTHORIZED.code, "No Authorization header");
206231
}
207232
return result;
@@ -210,6 +235,7 @@ public boolean doAuthenticate(
210235
final String scheme = getSchemeFromAuthHeader(authHeader);
211236
final AuthenticationPlugin plugin = pluginMap.get(scheme);
212237
if (plugin == null) {
238+
addWWWAuthenticateHeaders(response);
213239
response.sendError(
214240
ErrorCode.UNAUTHORIZED.code, "Authorization scheme '" + scheme + "' not supported!");
215241
return false;

solr/core/src/test-files/solr/security/multi_auth_plugin_security.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"class": "solr.MultiAuthPlugin",
44
"schemes": [{
55
"scheme": "basic",
6+
"realm": "basicRealm",
67
"blockUnknown": true,
78
"class": "solr.BasicAuthPlugin",
89
"credentials": {
@@ -11,6 +12,11 @@
1112
"forwardCredentials": false
1213
},{
1314
"scheme": "mock",
15+
"realm": "mockRealm",
16+
"class": "org.apache.solr.security.MultiAuthPluginTest$MockAuthPluginForTesting",
17+
"blockUnknown": true
18+
},{
19+
"scheme": "mockNoRealm",
1420
"class": "org.apache.solr.security.MultiAuthPluginTest$MockAuthPluginForTesting",
1521
"blockUnknown": true
1622
}]

solr/core/src/test/org/apache/solr/security/MultiAuthPluginTest.java

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,18 @@
2828
import java.nio.charset.StandardCharsets;
2929
import java.nio.file.Files;
3030
import java.security.Principal;
31+
import java.util.Arrays;
3132
import java.util.List;
33+
import java.util.Locale;
3234
import java.util.Map;
3335
import java.util.Objects;
3436
import java.util.function.Predicate;
37+
import java.util.stream.Collectors;
3538
import javax.servlet.FilterChain;
3639
import javax.servlet.http.HttpServletRequest;
3740
import javax.servlet.http.HttpServletResponse;
41+
import org.apache.http.Header;
42+
import org.apache.http.HttpHeaders;
3843
import org.apache.http.HttpResponse;
3944
import org.apache.http.client.HttpClient;
4045
import org.apache.http.client.methods.HttpGet;
@@ -105,6 +110,10 @@ public void testMultiAuthEditAPI() throws Exception {
105110
new SecurityConfHandler.SecurityConfig()
106111
.setData(Utils.fromJSONString(multiAuthPluginSecurityJson)));
107112
securityConfHandler.securityConfEdited();
113+
114+
// verify "WWW-Authenticate" headers are returned
115+
verifyWWWAuthenticateHeaders(httpClient, baseUrl);
116+
108117
verifySecurityStatus(
109118
httpClient,
110119
baseUrl + authcPrefix,
@@ -269,6 +278,40 @@ private int doHttpGetAnonymous(HttpClient cl, String url) throws IOException {
269278
return statusCode;
270279
}
271280

281+
private void verifyWWWAuthenticateHeaders(HttpClient httpClient, String baseUrl)
282+
throws Exception {
283+
HttpGet httpGet = new HttpGet(baseUrl + "/admin/info/system");
284+
HttpResponse response = httpClient.execute(httpGet);
285+
Header[] headers = response.getHeaders(HttpHeaders.WWW_AUTHENTICATE);
286+
List<String> actualSchemes =
287+
Arrays.stream(headers).map(Header::getValue).collect(Collectors.toList());
288+
289+
List<String> expectedSchemes = generateExpectedSchemes();
290+
actualSchemes.sort(String.CASE_INSENSITIVE_ORDER);
291+
expectedSchemes.sort(String.CASE_INSENSITIVE_ORDER);
292+
293+
assertEquals(
294+
"The actual schemes and realms should match the expected ones exactly",
295+
expectedSchemes.stream().map(s -> s.toLowerCase(Locale.ROOT)).collect(Collectors.toList()),
296+
actualSchemes.stream().map(s -> s.toLowerCase(Locale.ROOT)).collect(Collectors.toList()));
297+
}
298+
299+
@SuppressWarnings("unchecked")
300+
private List<String> generateExpectedSchemes() {
301+
Map<String, Object> data = securityConfHandler.getSecurityConfig(false).getData();
302+
Map<String, Object> authentication = (Map<String, Object>) data.get("authentication");
303+
List<Map<String, Object>> schemes = (List<Map<String, Object>>) authentication.get("schemes");
304+
305+
return schemes.stream()
306+
.map(
307+
schemeMap -> {
308+
String scheme = (String) schemeMap.get("scheme");
309+
String realm = (String) schemeMap.get("realm");
310+
return realm != null ? scheme + " realm=\"" + realm + "\"" : scheme;
311+
})
312+
.collect(Collectors.toList());
313+
}
314+
272315
private static final class MockPrincipal implements Principal, Serializable {
273316
@Override
274317
public String getName() {

0 commit comments

Comments
 (0)