Skip to content

Commit

Permalink
IVY-1280 Support preemptive authentication
Browse files Browse the repository at this point in the history
  • Loading branch information
twogee committed Mar 21, 2018
1 parent 6c6b441 commit 909b7a4
Show file tree
Hide file tree
Showing 7 changed files with 64 additions and 3 deletions.
2 changes: 2 additions & 0 deletions asciidoc/release-notes.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ For details about the following changes, check our JIRA install at link:https://
- IMPROVEMENT: Support timestamped SNAPSHOT versions from Maven repository (jira:IVY-1153[]) and (jira:IVY-1476[])
- IMPROVEMENT: Update Commons VFS to 2.2
- IMPROVEMENT: Ivy now supports activating of Maven profiles, in `pom.xml`, by `jdk`, `os`, `property` and `file` (jira:IVY-1558[]) and (jira:IVY-1577[])
- IMPROVEMENT: Support link:https://hc.apache.org/httpcomponents-client-4.5.x/tutorial/html/authentication.html[preemptive authentication]. (jira:IVY-1280[])
- NEW: Lets SSH-based resolvers use an `~/.ssh/config` file to find username/hostname/keyfile options (Thanks to Colin Stanfill)
- 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[])
Expand Down Expand Up @@ -192,6 +193,7 @@ Here is the list of people who have contributed source code and documentation up
* Antoine Levy-Lambert
* Tony Likhite
* Andrey Lomakin
* Aurélien Lourot
* William Lyvers
* Sakari Maaranen
* Jan Materne
Expand Down
1 change: 1 addition & 0 deletions asciidoc/settings/settings.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ So if there is a setting in the resolver, it always wins against all other setti
|validate|Indicates if Ivy files should be validated against ivy.xsd or not.|No, defaults to true
|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
|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'
|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
|[line-through]#defaultCache#|a path to a directory to use as default basedir for both resolution and repository cache(s). +
__Deprecated, we recommend using defaultCacheDir on the link:../settings/caches{outfilesuffix}[caches] tag instead__|No, defaults to .ivy2/cache in user home
|[line-through]#checkUpToDate#|Indicates if date should be checked before retrieving artifacts from cache. +
Expand Down
5 changes: 5 additions & 0 deletions src/java/org/apache/ivy/core/settings/XmlSettingsParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,11 @@ private void settingsStarted(String qName, Map<String, String> attributes) {
throw new IllegalArgumentException(
"Invalid httpRequestMethod specified, must be one of {'HEAD', 'GET'}");
}

String preemptiveAuth = attributes.get("preemptiveAuth");
if (preemptiveAuth != null) {
URLHandlerRegistry.getHttp().setPreemptiveAuth(Boolean.valueOf(preemptiveAuth));
}
}

private void includeStarted(Map<String, String> attributes) throws IOException, ParseException {
Expand Down
10 changes: 10 additions & 0 deletions src/java/org/apache/ivy/util/url/AbstractURLHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ public abstract class AbstractURLHandler implements URLHandler {
// the request method to use. TODO: don't use a static here
private static int requestMethod = REQUEST_METHOD_HEAD;

private boolean preemptiveAuth = false;

@Override
public boolean isReachable(final URL url) {
return getURLInfo(url).isReachable();
Expand Down Expand Up @@ -106,6 +108,14 @@ public int getRequestMethod() {
return requestMethod;
}

public void setPreemptiveAuth(boolean preemptive) {
this.preemptiveAuth = preemptive;
}

public boolean getPreemptiveAuth() {
return this.preemptiveAuth;
}

protected String normalizeToString(URL url) throws IOException {
if (!"http".equals(url.getProtocol()) && !"https".equals(url.getProtocol())) {
return url.toExternalForm();
Expand Down
32 changes: 29 additions & 3 deletions src/java/org/apache/ivy/util/url/HttpClientHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,28 +19,33 @@

import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.auth.AuthSchemeProvider;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.Credentials;
import org.apache.http.auth.NTCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.AuthCache;
import org.apache.http.client.config.AuthSchemes;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpHead;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.config.Lookup;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.HttpClientConnectionManager;
import org.apache.http.conn.routing.HttpRoutePlanner;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.FileEntity;
import org.apache.http.impl.auth.BasicScheme;
import org.apache.http.impl.auth.BasicSchemeFactory;
import org.apache.http.impl.auth.DigestSchemeFactory;
import org.apache.http.impl.auth.NTLMSchemeFactory;
import org.apache.http.impl.client.BasicAuthCache;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
Expand Down Expand Up @@ -186,7 +191,7 @@ public void upload(final File src, final URL dest, final CopyProgressListener li
final HttpPut put = new HttpPut(normalizeToString(dest));
put.setConfig(requestConfig);
put.setEntity(new FileEntity(src));
try (final CloseableHttpResponse response = this.httpClient.execute(put)) {
try (final CloseableHttpResponse response = this.httpClient.execute(put, getHttpClientContext(dest))) {
validatePutStatusCode(dest, response.getStatusLine().getStatusCode(), response.getStatusLine().getReasonPhrase());
}
}
Expand Down Expand Up @@ -332,7 +337,7 @@ private CloseableHttpResponse doGet(final URL url, final int connectionTimeout,
final HttpGet httpGet = new HttpGet(normalizeToString(url));
httpGet.setConfig(requestConfig);
httpGet.addHeader("Accept-Encoding", "gzip,deflate");
return this.httpClient.execute(httpGet);
return this.httpClient.execute(httpGet, getHttpClientContext(url));
}

private CloseableHttpResponse doHead(final URL url, final int connectionTimeout, final int readTimeout) throws IOException {
Expand All @@ -344,13 +349,34 @@ private CloseableHttpResponse doHead(final URL url, final int connectionTimeout,
.build();
final HttpHead httpHead = new HttpHead(normalizeToString(url));
httpHead.setConfig(requestConfig);
return this.httpClient.execute(httpHead);
return this.httpClient.execute(httpHead, getHttpClientContext(url));
}

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

private HttpClientContext getHttpClientContext(URL dest) {
if (!getPreemptiveAuth()) {
return null;
}

// Preemptive authentication, see
// https://hc.apache.org/httpcomponents-client-4.5.x/tutorial/html/authentication.html
// Without preemptive authentication, Ivy will first try non-authenticated, get a 4xx, retry
// authenticated and finally get a 2xx. This becomes an issue in an environment where Ivy is
// used a lot, as firewalls might see large amounts of 4xx as a brute-force attack and close
// the connections.

final BasicScheme basicAuth = new BasicScheme();
final HttpHost target = new HttpHost(dest.getHost(), dest.getPort(), dest.getProtocol());
final AuthCache authCache = new BasicAuthCache();
authCache.put(target, basicAuth);
final HttpClientContext localContext = HttpClientContext.create();
localContext.setAuthCache(authCache);
return localContext;
}

@Override
public void close() throws Exception {
if (this.httpClient != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,4 +125,12 @@ public interface TimeoutConstrainedURLHandler extends URLHandler {
* @since 2.5
*/
void upload(File src, URL dest, CopyProgressListener listener, TimeoutConstraint timeoutConstraint) throws IOException;

/**
* Sets the flag enabling or disabling preemptive authentication.
*
* @param preemptive The flag
* @since 2.5
*/
void setPreemptiveAuth(boolean preemptive);
}
9 changes: 9 additions & 0 deletions src/java/org/apache/ivy/util/url/URLHandlerDispatcher.java
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,15 @@ public void setRequestMethod(int requestMethod) {
}
}

public void setPreemptiveAuth(boolean preemptive) {
((TimeoutConstrainedURLHandler) defaultHandler).setPreemptiveAuth(preemptive);
for (URLHandler handler : handlers.values()) {
if (handler instanceof TimeoutConstrainedURLHandler) {
((TimeoutConstrainedURLHandler) handler).setPreemptiveAuth(preemptive);
}
}
}

@SuppressWarnings("deprecation")
public void setDownloader(String protocol, URLHandler downloader) {
handlers.put(protocol, downloader);
Expand Down

0 comments on commit 909b7a4

Please sign in to comment.