Skip to content

Commit 7eae28e

Browse files
committed
IVY-1280 Support preemptive authentication
1 parent ebbc240 commit 7eae28e

7 files changed

+64
-3
lines changed

asciidoc/release-notes.adoc

+2
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ For details about the following changes, check our JIRA install at link:https://
8989
- IMPROVEMENT: Support timestamped SNAPSHOT versions from Maven repository (jira:IVY-1153[]) and (jira:IVY-1476[])
9090
- IMPROVEMENT: Update Commons VFS to 2.2
9191
- IMPROVEMENT: Ivy now supports activating of Maven profiles, in `pom.xml`, by `jdk`, `os`, `property` and `file` (jira:IVY-1558[]) and (jira:IVY-1577[])
92+
- IMPROVEMENT: Support link:https://hc.apache.org/httpcomponents-client-4.5.x/tutorial/html/authentication.html[preemptive authentication]. (jira:IVY-1280[])
9293
9394
- NEW: Lets SSH-based resolvers use an `~/.ssh/config` file to find username/hostname/keyfile options (Thanks to Colin Stanfill)
9495
- NEW: Add ivy.maven.lookup.sources and ivy.maven.lookup.javadoc variables to control the lookup of the additional artifacts. Defaults to true, for backward compatibility (jira:IVY-1529[])
@@ -193,6 +194,7 @@ Here is the list of people who have contributed source code and documentation up
193194
* Antoine Levy-Lambert
194195
* Tony Likhite
195196
* Andrey Lomakin
197+
* Aurélien Lourot
196198
* William Lyvers
197199
* Sakari Maaranen
198200
* Jan Materne

asciidoc/settings/settings.adoc

+1
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ So if there is a setting in the resolver, it always wins against all other setti
5050
|validate|Indicates if Ivy files should be validated against ivy.xsd or not.|No, defaults to true
5151
|useRemoteConfig|true to configure ivyrep and ibiblio resolver from a remote settings file (updated with changes in those repository structure if any) (*__since 1.2__*)|No, defaults to false
5252
|httpRequestMethod|specifies the HTTP method to use to retrieve information about an URL. Possible values are 'GET' and 'HEAD'. This setting can be used to solve problems with firewalls and proxies. (*__since 2.0__*)|No, defaults to 'HEAD'
53+
|preemptiveAuth|true to use link:https://hc.apache.org/httpcomponents-client-4.5.x/tutorial/html/authentication.html[preemptive authentication] whenever possible (only supported with link:https://hc.apache.org/httpcomponents-client-4.5.x[HttpClient] as URL Handler) (*__since 2.5__*)|No, defaults to false
5354
|[line-through]#defaultCache#|a path to a directory to use as default basedir for both resolution and repository cache(s). +
5455
__Deprecated, we recommend using defaultCacheDir on the link:../settings/caches{outfilesuffix}[caches] tag instead__|No, defaults to .ivy2/cache in user home
5556
|[line-through]#checkUpToDate#|Indicates if date should be checked before retrieving artifacts from cache. +

src/java/org/apache/ivy/core/settings/XmlSettingsParser.java

+5
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,11 @@ private void settingsStarted(String qName, Map<String, String> attributes) {
372372
throw new IllegalArgumentException(
373373
"Invalid httpRequestMethod specified, must be one of {'HEAD', 'GET'}");
374374
}
375+
376+
String preemptiveAuth = attributes.get("preemptiveAuth");
377+
if (preemptiveAuth != null) {
378+
URLHandlerRegistry.getHttp().setPreemptiveAuth(Boolean.valueOf(preemptiveAuth));
379+
}
375380
}
376381

377382
private void includeStarted(Map<String, String> attributes) throws IOException, ParseException {

src/java/org/apache/ivy/util/url/AbstractURLHandler.java

+10
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ public abstract class AbstractURLHandler implements URLHandler {
4242
// the request method to use. TODO: don't use a static here
4343
private static int requestMethod = REQUEST_METHOD_HEAD;
4444

45+
private boolean preemptiveAuth = false;
46+
4547
@Override
4648
public boolean isReachable(final URL url) {
4749
return getURLInfo(url).isReachable();
@@ -106,6 +108,14 @@ public int getRequestMethod() {
106108
return requestMethod;
107109
}
108110

111+
public void setPreemptiveAuth(boolean preemptive) {
112+
this.preemptiveAuth = preemptive;
113+
}
114+
115+
public boolean getPreemptiveAuth() {
116+
return this.preemptiveAuth;
117+
}
118+
109119
protected String normalizeToString(URL url) throws IOException {
110120
if (!"http".equals(url.getProtocol()) && !"https".equals(url.getProtocol())) {
111121
return url.toExternalForm();

src/java/org/apache/ivy/util/url/HttpClientHandler.java

+29-3
Original file line numberDiff line numberDiff line change
@@ -19,28 +19,33 @@
1919

2020
import org.apache.http.Header;
2121
import org.apache.http.HttpEntity;
22+
import org.apache.http.HttpHost;
2223
import org.apache.http.HttpResponse;
2324
import org.apache.http.HttpStatus;
2425
import org.apache.http.auth.AuthSchemeProvider;
2526
import org.apache.http.auth.AuthScope;
2627
import org.apache.http.auth.Credentials;
2728
import org.apache.http.auth.NTCredentials;
2829
import org.apache.http.client.CredentialsProvider;
30+
import org.apache.http.client.AuthCache;
2931
import org.apache.http.client.config.AuthSchemes;
3032
import org.apache.http.client.config.RequestConfig;
3133
import org.apache.http.client.methods.CloseableHttpResponse;
3234
import org.apache.http.client.methods.HttpGet;
3335
import org.apache.http.client.methods.HttpHead;
3436
import org.apache.http.client.methods.HttpPut;
37+
import org.apache.http.client.protocol.HttpClientContext;
3538
import org.apache.http.config.Lookup;
3639
import org.apache.http.config.RegistryBuilder;
3740
import org.apache.http.conn.HttpClientConnectionManager;
3841
import org.apache.http.conn.routing.HttpRoutePlanner;
3942
import org.apache.http.entity.ContentType;
4043
import org.apache.http.entity.FileEntity;
44+
import org.apache.http.impl.auth.BasicScheme;
4145
import org.apache.http.impl.auth.BasicSchemeFactory;
4246
import org.apache.http.impl.auth.DigestSchemeFactory;
4347
import org.apache.http.impl.auth.NTLMSchemeFactory;
48+
import org.apache.http.impl.client.BasicAuthCache;
4449
import org.apache.http.impl.client.CloseableHttpClient;
4550
import org.apache.http.impl.client.HttpClients;
4651
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
@@ -186,7 +191,7 @@ public void upload(final File src, final URL dest, final CopyProgressListener li
186191
final HttpPut put = new HttpPut(normalizeToString(dest));
187192
put.setConfig(requestConfig);
188193
put.setEntity(new FileEntity(src));
189-
try (final CloseableHttpResponse response = this.httpClient.execute(put)) {
194+
try (final CloseableHttpResponse response = this.httpClient.execute(put, getHttpClientContext(dest))) {
190195
validatePutStatusCode(dest, response.getStatusLine().getStatusCode(), response.getStatusLine().getReasonPhrase());
191196
}
192197
}
@@ -332,7 +337,7 @@ private CloseableHttpResponse doGet(final URL url, final int connectionTimeout,
332337
final HttpGet httpGet = new HttpGet(normalizeToString(url));
333338
httpGet.setConfig(requestConfig);
334339
httpGet.addHeader("Accept-Encoding", "gzip,deflate");
335-
return this.httpClient.execute(httpGet);
340+
return this.httpClient.execute(httpGet, getHttpClientContext(url));
336341
}
337342

338343
private CloseableHttpResponse doHead(final URL url, final int connectionTimeout, final int readTimeout) throws IOException {
@@ -344,13 +349,34 @@ private CloseableHttpResponse doHead(final URL url, final int connectionTimeout,
344349
.build();
345350
final HttpHead httpHead = new HttpHead(normalizeToString(url));
346351
httpHead.setConfig(requestConfig);
347-
return this.httpClient.execute(httpHead);
352+
return this.httpClient.execute(httpHead, getHttpClientContext(url));
348353
}
349354

350355
private boolean hasCredentialsConfigured(final URL url) {
351356
return CredentialsStore.INSTANCE.hasCredentials(url.getHost());
352357
}
353358

359+
private HttpClientContext getHttpClientContext(URL dest) {
360+
if (!getPreemptiveAuth()) {
361+
return null;
362+
}
363+
364+
// Preemptive authentication, see
365+
// https://hc.apache.org/httpcomponents-client-4.5.x/tutorial/html/authentication.html
366+
// Without preemptive authentication, Ivy will first try non-authenticated, get a 4xx, retry
367+
// authenticated and finally get a 2xx. This becomes an issue in an environment where Ivy is
368+
// used a lot, as firewalls might see large amounts of 4xx as a brute-force attack and close
369+
// the connections.
370+
371+
final BasicScheme basicAuth = new BasicScheme();
372+
final HttpHost target = new HttpHost(dest.getHost(), dest.getPort(), dest.getProtocol());
373+
final AuthCache authCache = new BasicAuthCache();
374+
authCache.put(target, basicAuth);
375+
final HttpClientContext localContext = HttpClientContext.create();
376+
localContext.setAuthCache(authCache);
377+
return localContext;
378+
}
379+
354380
@Override
355381
public void close() throws Exception {
356382
if (this.httpClient != null) {

src/java/org/apache/ivy/util/url/TimeoutConstrainedURLHandler.java

+8
Original file line numberDiff line numberDiff line change
@@ -126,4 +126,12 @@ public interface TimeoutConstrainedURLHandler extends URLHandler {
126126
* @since 2.5
127127
*/
128128
void upload(File src, URL dest, CopyProgressListener listener, TimeoutConstraint timeoutConstraint) throws IOException;
129+
130+
/**
131+
* Sets the flag enabling or disabling preemptive authentication.
132+
*
133+
* @param preemptive The flag
134+
* @since 2.5
135+
*/
136+
void setPreemptiveAuth(boolean preemptive);
129137
}

src/java/org/apache/ivy/util/url/URLHandlerDispatcher.java

+9
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,15 @@ public void setRequestMethod(int requestMethod) {
179179
}
180180
}
181181

182+
public void setPreemptiveAuth(boolean preemptive) {
183+
((TimeoutConstrainedURLHandler) defaultHandler).setPreemptiveAuth(preemptive);
184+
for (URLHandler handler : handlers.values()) {
185+
if (handler instanceof TimeoutConstrainedURLHandler) {
186+
((TimeoutConstrainedURLHandler) handler).setPreemptiveAuth(preemptive);
187+
}
188+
}
189+
}
190+
182191
@SuppressWarnings("deprecation")
183192
public void setDownloader(String protocol, URLHandler downloader) {
184193
handlers.put(protocol, downloader);

0 commit comments

Comments
 (0)