From 38539f127e94de47e7926861ea4b7d33d691c8ce Mon Sep 17 00:00:00 2001 From: twogee Date: Thu, 1 Feb 2018 11:40:10 +0100 Subject: [PATCH] IVY-1280 Support preemptive authentication --- asciidoc/release-notes.adoc | 5 +-- asciidoc/settings/settings.adoc | 1 + .../ivy/core/settings/XmlSettingsParser.java | 5 +++ .../ivy/util/url/AbstractURLHandler.java | 10 ++++++ .../ivy/util/url/HttpClientHandler.java | 32 +++++++++++++++++-- .../url/TimeoutConstrainedURLHandler.java | 8 +++++ .../ivy/util/url/URLHandlerDispatcher.java | 9 ++++++ 7 files changed, 65 insertions(+), 5 deletions(-) diff --git a/asciidoc/release-notes.adoc b/asciidoc/release-notes.adoc index 80b16a2a5..fa01d0a32 100644 --- a/asciidoc/release-notes.adoc +++ b/asciidoc/release-notes.adoc @@ -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: @@ -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 @@ -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 diff --git a/asciidoc/settings/settings.adoc b/asciidoc/settings/settings.adoc index 114334806..08e514a82 100644 --- a/asciidoc/settings/settings.adoc +++ b/asciidoc/settings/settings.adoc @@ -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. + diff --git a/src/java/org/apache/ivy/core/settings/XmlSettingsParser.java b/src/java/org/apache/ivy/core/settings/XmlSettingsParser.java index 0c743c9aa..c4472b778 100644 --- a/src/java/org/apache/ivy/core/settings/XmlSettingsParser.java +++ b/src/java/org/apache/ivy/core/settings/XmlSettingsParser.java @@ -372,6 +372,11 @@ private void settingsStarted(String qName, Map 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 attributes) throws IOException, ParseException { diff --git a/src/java/org/apache/ivy/util/url/AbstractURLHandler.java b/src/java/org/apache/ivy/util/url/AbstractURLHandler.java index 02ff839cf..a7a9b8d57 100644 --- a/src/java/org/apache/ivy/util/url/AbstractURLHandler.java +++ b/src/java/org/apache/ivy/util/url/AbstractURLHandler.java @@ -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(); @@ -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(); diff --git a/src/java/org/apache/ivy/util/url/HttpClientHandler.java b/src/java/org/apache/ivy/util/url/HttpClientHandler.java index d79cd27c1..b5378975f 100644 --- a/src/java/org/apache/ivy/util/url/HttpClientHandler.java +++ b/src/java/org/apache/ivy/util/url/HttpClientHandler.java @@ -19,6 +19,7 @@ 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; @@ -26,21 +27,25 @@ 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; @@ -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()); } } @@ -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 { @@ -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) { diff --git a/src/java/org/apache/ivy/util/url/TimeoutConstrainedURLHandler.java b/src/java/org/apache/ivy/util/url/TimeoutConstrainedURLHandler.java index 08a6ed4f3..4a75c722d 100644 --- a/src/java/org/apache/ivy/util/url/TimeoutConstrainedURLHandler.java +++ b/src/java/org/apache/ivy/util/url/TimeoutConstrainedURLHandler.java @@ -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); } diff --git a/src/java/org/apache/ivy/util/url/URLHandlerDispatcher.java b/src/java/org/apache/ivy/util/url/URLHandlerDispatcher.java index c260c9ea3..4dcf34534 100644 --- a/src/java/org/apache/ivy/util/url/URLHandlerDispatcher.java +++ b/src/java/org/apache/ivy/util/url/URLHandlerDispatcher.java @@ -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);