Skip to content

Commit 18c4efc

Browse files
committed
IVY-1280 Support preemptive authentication
1 parent 1a19f2f commit 18c4efc

File tree

8 files changed

+59
-4
lines changed

8 files changed

+59
-4
lines changed

asciidoc/release-notes.adoc

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ Key features of this 2.5.0 release are:
3838
3939
* The minimum runtime Java version required is now Java 7
4040
* Ivy now uses BouncyCastle 1.58. Due to the non backward compatibility of that library, earlier versions are not supported.
41-
* Ivy now uses HttpComponents HttpClient 4.5.x version when dealing with HTTP backed resolvers. Users are expected to have this version of the library (and its dependencies) in their runtime classpath if they want to use such resolvers. The previous (similarly named but not the same) commons-httpclient library is no longer used or supported. (jira:IVY-1563[])
41+
* Ivy now uses HttpComponents HttpClient 4.5.x version with HTTP backed resolvers. Users are expected to have this version of the library (and its dependencies) in their runtime classpath if they want to use such resolvers. The previous (similarly named but not the same) commons-httpclient library is no longer used or supported. (jira:IVY-1563[])
4242
4343
4444
== List of Changes in this Release
@@ -86,6 +86,7 @@ For details about the following changes, check our JIRA install at link:https://
8686
- IMPROVEMENT: Support timestamped SNAPSHOT versions from Maven repository (jira:IVY-1476[])
8787
- IMPROVEMENT: Update Commons VFS to 2.1
8888
- IMPROVEMENT: Ivy now supports activating of Maven profiles, in `pom.xml`, by `jdk`, `os`, `property` and `file` (jira:IVY-1558[]) and (jira:IVY-1577[])
89+
- IMPROVEMENT: Support link:https://hc.apache.org/httpcomponents-client-4.5.x/tutorial/html/authentication.html[preemptive authentication]. (jira:IVY-1280[])
8990
9091
- NEW: Lets SSH-based resolvers use an `~/.ssh/config` file to find username/hostname/keyfile options (Thanks to Colin Stanfill)
9192
- 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[])
@@ -188,6 +189,7 @@ Here is the list of people who have contributed source code and documentation up
188189
* Antoine Levy-Lambert
189190
* Tony Likhite
190191
* Andrey Lomakin
192+
* Aurélien Lourot
191193
* William Lyvers
192194
* Sakari Maaranen
193195
* Jan Materne

asciidoc/settings/settings.adoc

Lines changed: 1 addition & 0 deletions
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.html[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. +

doc/settings/settings.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ <h1>Attributes</h1>
6161
<td>No, defaults to false</td></tr>
6262
<tr><td>httpRequestMethod</td><td>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. (<span class="since">since 2.0</span>)</td>
6363
<td>No, defaults to 'HEAD'</td></tr>
64+
<tr><td>preemptiveAuth</td><td>true to use <a href="https://hc.apache.org/httpcomponents-client-4.5.x/tutorial/html/authentication.html">preemptive authentication</a> whenever possible (only supported with <a href="https://hc.apache.org/httpcomponents-client-4.5.x">HttpClient</a> as URL Handler) (<span class="since">since 2.5</span>)</td>
65+
<td>No, defaults to false</td></tr>
6466
<tr><td><s>defaultCache</s></td><td>a path to a directory to use as default basedir for both resolution and repository cache(s).
6567
<i>Deprecated, we recommend using defaultCacheDir on the [[settings/caches]] tag instead</i></td>
6668
<td>No, defaults to .ivy2/cache in user home</td></tr>

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,11 @@ private void settingsStarted(String qName, Map<String, String> attributes) {
380380
throw new IllegalArgumentException(
381381
"Invalid httpRequestMethod specified, must be one of {'HEAD', 'GET'}");
382382
}
383+
384+
String preemptiveAuth = attributes.get("preemptiveAuth");
385+
if (preemptiveAuth != null) {
386+
URLHandlerRegistry.getHttp().setPreemptiveAuth(Boolean.valueOf(preemptiveAuth));
387+
}
383388
}
384389

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

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ public abstract class AbstractURLHandler implements URLHandler {
4040
// the request method to use. TODO: don't use a static here
4141
private static int requestMethod = REQUEST_METHOD_HEAD;
4242

43+
private boolean preemptiveAuth = false;
44+
4345
@Override
4446
public boolean isReachable(final URL url) {
4547
return getURLInfo(url).isReachable();
@@ -105,6 +107,14 @@ public int getRequestMethod() {
105107
return requestMethod;
106108
}
107109

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

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

Lines changed: 29 additions & 3 deletions
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
}
@@ -326,7 +331,7 @@ private CloseableHttpResponse doGet(final URL url, final int connectionTimeout,
326331
final HttpGet httpGet = new HttpGet(normalizeToString(url));
327332
httpGet.setConfig(requestConfig);
328333
httpGet.addHeader("Accept-Encoding", "gzip,deflate");
329-
return this.httpClient.execute(httpGet);
334+
return this.httpClient.execute(httpGet, getHttpClientContext(url));
330335
}
331336

332337
private CloseableHttpResponse doHead(final URL url, final int connectionTimeout, final int readTimeout) throws IOException {
@@ -338,13 +343,34 @@ private CloseableHttpResponse doHead(final URL url, final int connectionTimeout,
338343
.build();
339344
final HttpHead httpHead = new HttpHead(normalizeToString(url));
340345
httpHead.setConfig(requestConfig);
341-
return this.httpClient.execute(httpHead);
346+
return this.httpClient.execute(httpHead, getHttpClientContext(url));
342347
}
343348

344349
private boolean hasCredentialsConfigured(final URL url) {
345350
return CredentialsStore.INSTANCE.hasCredentials(url.getHost());
346351
}
347352

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

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,4 +177,6 @@ public String getBodyCharset() {
177177
void upload(File src, URL dest, CopyProgressListener l) throws IOException;
178178

179179
void setRequestMethod(int requestMethod);
180+
181+
void setPreemptiveAuth(boolean preemptive);
180182
}

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,13 @@ public void setRequestMethod(int requestMethod) {
167167
}
168168
}
169169

170+
public void setPreemptiveAuth(boolean preemptive) {
171+
defaultHandler.setPreemptiveAuth(preemptive);
172+
for (URLHandler handler : handlers.values()) {
173+
handler.setPreemptiveAuth(preemptive);
174+
}
175+
}
176+
170177
public void setDownloader(String protocol, URLHandler downloader) {
171178
handlers.put(protocol, downloader);
172179
}

0 commit comments

Comments
 (0)