Skip to content

Commit ae61e8b

Browse files
GH-5292 Proxy credentials lost when setting repository credentials
Signed-off-by: Manuel Fiorelli <manuel.fiorelli@gmail.com>
1 parent 782ffd8 commit ae61e8b

2 files changed

Lines changed: 169 additions & 2 deletions

File tree

core/http/client/src/main/java/org/eclipse/rdf4j/http/client/SPARQLProtocolSession.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@
5555
import org.apache.http.impl.auth.BasicScheme;
5656
import org.apache.http.impl.client.BasicAuthCache;
5757
import org.apache.http.impl.client.BasicCookieStore;
58-
import org.apache.http.impl.client.BasicCredentialsProvider;
58+
import org.apache.http.impl.client.SystemDefaultCredentialsProvider;
5959
import org.apache.http.message.BasicNameValuePair;
6060
import org.apache.http.params.BasicHttpParams;
6161
import org.apache.http.params.CoreConnectionPNames;
@@ -323,7 +323,7 @@ protected void setUsernameAndPasswordForUrl(String username, String password, St
323323
int port = requestURI.getPort();
324324
AuthScope scope = new AuthScope(host, port);
325325
UsernamePasswordCredentials cred = new UsernamePasswordCredentials(username, password);
326-
CredentialsProvider credsProvider = new BasicCredentialsProvider();
326+
CredentialsProvider credsProvider = new SystemDefaultCredentialsProvider();
327327
credsProvider.setCredentials(scope, cred);
328328
httpContext.setCredentialsProvider(credsProvider);
329329
AuthCache authCache = new BasicAuthCache();
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2025 Eclipse RDF4J contributors.
3+
*
4+
* All rights reserved. This program and the accompanying materials
5+
* are made available under the terms of the Eclipse Distribution License v1.0
6+
* which accompanies this distribution, and is available at
7+
* http://www.eclipse.org/org/documents/edl-v10.php.
8+
*
9+
* SPDX-License-Identifier: BSD-3-Clause
10+
*******************************************************************************/
11+
package org.eclipse.rdf4j.http.client;
12+
13+
import static org.assertj.core.api.Assertions.assertThat;
14+
import static org.mockserver.model.Header.header;
15+
import static org.mockserver.model.HttpRequest.request;
16+
import static org.mockserver.model.HttpResponse.response;
17+
18+
import java.nio.charset.StandardCharsets;
19+
import java.util.Base64;
20+
21+
import org.apache.commons.lang3.StringUtils;
22+
import org.eclipse.rdf4j.http.protocol.Protocol;
23+
import org.eclipse.rdf4j.query.QueryLanguage;
24+
import org.eclipse.rdf4j.query.impl.SimpleDataset;
25+
import org.junit.jupiter.api.AfterEach;
26+
import org.junit.jupiter.api.BeforeEach;
27+
import org.junit.jupiter.api.Test;
28+
import org.junit.jupiter.api.extension.ExtendWith;
29+
import org.mockserver.client.MockServerClient;
30+
import org.mockserver.junit.jupiter.MockServerExtension;
31+
import org.mockserver.matchers.Times;
32+
import org.mockserver.model.MediaType;
33+
import org.mockserver.model.NottableString;
34+
import org.mockserver.verify.VerificationTimes;
35+
36+
/**
37+
* Unit tests for {@link SPARQLProtocolSession} using standard Java properties for proxy configuration.
38+
*
39+
* @author Manuel Fiorelli
40+
*/
41+
@ExtendWith(MockServerExtension.class)
42+
public class ProxyTest {
43+
44+
// the hostname is guaranteed not to exist (https://datatracker.ietf.org/doc/html/rfc6761#section-6.4)
45+
String serverURL = "http://rdf4j.invalid/rdf4j-server";
46+
String repositoryID = "test";
47+
48+
String proxyUser = "proxyUser";
49+
String proxyPassword = "proxyPassword";
50+
51+
/* @Nullable */ String proxyHostOld;
52+
/* @Nullable */ String proxyPortOld;
53+
/* @Nullable */ String proxyUserOld;
54+
/* @Nullable */ String proxyPasswordOld;
55+
56+
RDF4JProtocolSession sparqlSession;
57+
58+
@BeforeEach
59+
public void setUp(MockServerClient client) {
60+
// Set the system properties related to (non-secured) HTTP proxy.
61+
// Keep a copy of the old value, if any, to restore it after the execution of the test.
62+
proxyHostOld = System.setProperty("http.proxyHost", "localhost");
63+
proxyPortOld = System.setProperty("http.proxyPort", String.valueOf(client.getPort()));
64+
proxyUserOld = System.setProperty("http.proxyUser", proxyUser);
65+
proxyPasswordOld = System.setProperty("http.proxyPassword", proxyPassword);
66+
67+
// Instantiate an RDF4JProtocolSession
68+
sparqlSession = new SharedHttpClientSessionManager().createRDF4JProtocolSession(serverURL);
69+
sparqlSession.setQueryURL(Protocol.getRepositoryLocation(serverURL, repositoryID));
70+
sparqlSession.setUpdateURL(
71+
Protocol.getStatementsLocation(Protocol.getRepositoryLocation(serverURL, repositoryID)));
72+
}
73+
74+
@AfterEach
75+
public void tearDown() {
76+
// Restore previous value of the system properties, if any
77+
restoreSystemProperty("http.proxyHost", proxyHostOld);
78+
restoreSystemProperty("http.proxyPort", proxyPortOld);
79+
restoreSystemProperty("http.proxyUser", proxyUserOld);
80+
restoreSystemProperty("http.proxyPassword", proxyPasswordOld);
81+
}
82+
83+
void restoreSystemProperty(String key, /* @Nullable */ String value) {
84+
if (StringUtils.isNotBlank(value)) {
85+
System.setProperty(key, value);
86+
} else {
87+
System.clearProperty(key);
88+
}
89+
}
90+
91+
@Test
92+
public void testUserNameAndPassword(MockServerClient client) throws Exception {
93+
String serverUser = "serverUser";
94+
String serverPassword = "serverPassword";
95+
96+
String proxyCredentialsEncoded = Base64.getEncoder()
97+
.encodeToString((proxyUser + ":" + proxyPassword).getBytes(StandardCharsets.US_ASCII));
98+
String serverCredentialsEncoded = Base64.getEncoder()
99+
.encodeToString((serverUser + ":" + serverPassword).getBytes(StandardCharsets.US_ASCII));
100+
101+
// Mock requests to request proxy and server authentication
102+
103+
client.when(
104+
request()
105+
.withMethod("POST")
106+
.withPath("/rdf4j-server/repositories/test")
107+
.withHeader(header(NottableString.not("Proxy-Authorization")))
108+
)
109+
.respond(
110+
response()
111+
.withStatusCode(407)
112+
.withHeader("Proxy-Authenticate", "Basic realm=\"rdf4j\"")
113+
);
114+
115+
client.when(
116+
request()
117+
.withMethod("POST")
118+
.withPath("/rdf4j-server/repositories/test")
119+
.withHeader("Proxy-Authorization", "Basic " + proxyCredentialsEncoded)
120+
.withHeader(header(NottableString.not("Authorization")))
121+
)
122+
.respond(
123+
response()
124+
.withStatusCode(401)
125+
.withHeader("WWW-Authenticate", "Basic realm=\"rdf4j\"")
126+
);
127+
128+
client.when(
129+
request()
130+
.withMethod("POST")
131+
.withPath("/rdf4j-server/repositories/test")
132+
.withHeader("Proxy-Authorization", "Basic " + proxyCredentialsEncoded)
133+
.withHeader("Authorization", "Basic " + serverCredentialsEncoded),
134+
Times.once()
135+
)
136+
.respond(
137+
response()
138+
.withStatusCode(200)
139+
.withContentType(MediaType.parse("application/sparql-results+xml;charset=UTF-8"))
140+
.withBody("<?xml version='1.0' encoding='UTF-8'?>\n" +
141+
"<sparql xmlns='http://www.w3.org/2005/sparql-results#'>\n" +
142+
" <head>\n" +
143+
" </head>\n" +
144+
" <boolean>true</boolean>\n" +
145+
"</sparql>")
146+
);
147+
148+
// Set server the credentials for the server
149+
sparqlSession.setUsernameAndPassword(serverUser, serverPassword);
150+
151+
// Invoke the test
152+
boolean response = sparqlSession.sendBooleanQuery(QueryLanguage.SPARQL, "ASK {}", new SimpleDataset(), false);
153+
154+
// Verifications
155+
assertThat(response).isTrue();
156+
client.verify(
157+
request()
158+
.withMethod("POST")
159+
.withPath("/rdf4j-server/repositories/test")
160+
.withHeader("Proxy-Authorization", "Basic " + proxyCredentialsEncoded)
161+
.withHeader("Authorization", "Basic " + serverCredentialsEncoded),
162+
VerificationTimes.once()
163+
);
164+
165+
}
166+
167+
}

0 commit comments

Comments
 (0)