Skip to content

Commit 6541892

Browse files
committed
IVY-1280 Support preemptive authentication
1 parent 994fff7 commit 6541892

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
@@ -82,6 +82,7 @@ For details about the following changes, check our JIRA install at link:https://
8282
- IMPROVEMENT: Ivy command line now supports passing `overwriteMode` option to `retrieve` task (jira:IVY-1447[])
8383
- IMPROVEMENT: Support timestamped SNAPSHOT versions from Maven repository (jira:IVY-1476[])
8484
- IMPROVEMENT: Update Commons VFS to 2.1
85+
- IMPROVEMENT: Support link:https://hc.apache.org/httpcomponents-client-4.5.x/tutorial/html/authentication.html[preemptive authentication]. (jira:IVY-1280[])
8586
8687
- NEW: Lets SSH-based resolvers use an `~/.ssh/config` file to find username/hostname/keyfile options (Thanks to Colin Stanfill)
8788
- 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[])
@@ -184,6 +185,7 @@ Here is the list of people who have contributed source code and documentation up
184185
* Antoine Levy-Lambert
185186
* Tony Likhite
186187
* Andrey Lomakin
188+
* Aurélien Lourot
187189
* William Lyvers
188190
* Sakari Maaranen
189191
* 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 this.isReachable(url, null);
@@ -119,6 +121,14 @@ public int getRequestMethod() {
119121
return requestMethod;
120122
}
121123

124+
public void setPreemptiveAuth(boolean preemptive) {
125+
this.preemptiveAuth = preemptive;
126+
}
127+
128+
public boolean getPreemptiveAuth() {
129+
return this.preemptiveAuth;
130+
}
131+
122132
protected String normalizeToString(URL url) throws IOException {
123133
if (!"http".equals(url.getProtocol()) && !"https".equals(url.getProtocol())) {
124134
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
}
@@ -311,7 +316,7 @@ private CloseableHttpResponse doGet(final URL url, final int connectionTimeout,
311316
final HttpGet httpGet = new HttpGet(normalizeToString(url));
312317
httpGet.setConfig(requestConfig);
313318
httpGet.addHeader("Accept-Encoding", "gzip,deflate");
314-
return this.httpClient.execute(httpGet);
319+
return this.httpClient.execute(httpGet, getHttpClientContext(url));
315320
}
316321

317322
private CloseableHttpResponse doHead(final URL url, final int connectionTimeout, final int readTimeout) throws IOException {
@@ -323,13 +328,34 @@ private CloseableHttpResponse doHead(final URL url, final int connectionTimeout,
323328
.build();
324329
final HttpHead httpHead = new HttpHead(normalizeToString(url));
325330
httpHead.setConfig(requestConfig);
326-
return this.httpClient.execute(httpHead);
331+
return this.httpClient.execute(httpHead, getHttpClientContext(url));
327332
}
328333

329334
private boolean hasCredentialsConfigured(final URL url) {
330335
return CredentialsStore.INSTANCE.hasCredentials(url.getHost());
331336
}
332337

338+
private HttpClientContext getHttpClientContext(URL dest) {
339+
if (!getPreemptiveAuth()) {
340+
return null;
341+
}
342+
343+
// Preemptive authentication, see
344+
// https://hc.apache.org/httpcomponents-client-4.5.x/tutorial/html/authentication.html
345+
// Without preemptive authentication, Ivy will first try non-authenticated, get a 4xx, retry
346+
// authenticated and finally get a 2xx. This becomes an issue in an environment where Ivy is
347+
// used a lot, as firewalls might see large amounts of 4xx as a brute-force attack and close
348+
// the connections.
349+
350+
final BasicScheme basicAuth = new BasicScheme();
351+
final HttpHost target = new HttpHost(dest.getHost(), dest.getPort(), dest.getProtocol());
352+
final AuthCache authCache = new BasicAuthCache();
353+
authCache.put(target, basicAuth);
354+
final HttpClientContext localContext = HttpClientContext.create();
355+
localContext.setAuthCache(authCache);
356+
return localContext;
357+
}
358+
333359
@Override
334360
public void close() throws Exception {
335361
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
@@ -290,4 +290,6 @@ public String getBodyCharset() {
290290
void upload(File src, URL dest, CopyProgressListener listener, TimeoutConstraint timeoutConstraint) throws IOException;
291291

292292
void setRequestMethod(int requestMethod);
293+
294+
void setPreemptiveAuth(boolean preemptive);
293295
}

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,13 @@ public void setRequestMethod(int requestMethod) {
135135
}
136136
}
137137

138+
public void setPreemptiveAuth(boolean preemptive) {
139+
defaultHandler.setPreemptiveAuth(preemptive);
140+
for (URLHandler handler : handlers.values()) {
141+
handler.setPreemptiveAuth(preemptive);
142+
}
143+
}
144+
138145
public void setDownloader(String protocol, URLHandler downloader) {
139146
handlers.put(protocol, downloader);
140147
}

0 commit comments

Comments
 (0)