Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IVY-1280 Support preemptive authentication #64

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions asciidoc/release-notes.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ For details about the following changes, check our JIRA install at link:https://
- BREAKING: Removed old fr\jayasoft\ivy\ant\antlib.xml AntLib definition file (jira:IVY-1612[])
- FIX:

- IMPROVEMENT:
- IMPROVEMENT: Support link:https://hc.apache.org/httpcomponents-client-4.5.x/tutorial/html/authentication.html[preemptive authentication]. (jira:IVY-1280[])

- NEW:

Expand All @@ -74,7 +74,7 @@ Here is the list of people who have contributed source code and documentation up
* Gintautas Grigelionis
* Xavier Hanin
* Nicolas Lalevée
* Jan Matèrne
* Jan Matèrne
* Jaikiran Pai
* Jon Schneider
* Gilles Scokart
Expand Down Expand Up @@ -152,6 +152,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 @@ -372,6 +372,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 @@ -126,4 +126,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