diff --git a/scm-core/src/main/java/sonia/scm/config/ScmConfiguration.java b/scm-core/src/main/java/sonia/scm/config/ScmConfiguration.java index 7885b295eb..7c49da3c9c 100644 --- a/scm-core/src/main/java/sonia/scm/config/ScmConfiguration.java +++ b/scm-core/src/main/java/sonia/scm/config/ScmConfiguration.java @@ -35,6 +35,7 @@ package sonia.scm.config; //~--- non-JDK imports -------------------------------------------------------- +import com.google.common.collect.Sets; import com.google.inject.Singleton; import org.slf4j.Logger; @@ -69,7 +70,7 @@ import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; @XmlRootElement(name = "scm-config") @XmlAccessorType(XmlAccessType.FIELD) public class ScmConfiguration - implements ListenerSupport> + implements ListenerSupport> { /** Default JavaScript date format */ @@ -115,7 +116,7 @@ public class ScmConfiguration */ @Override public void addListeners( - Collection> listeners) + Collection> listeners) { listeners.addAll(listeners); } @@ -162,6 +163,7 @@ public class ScmConfiguration this.proxyServer = other.proxyServer; this.proxyUser = other.proxyUser; this.proxyPassword = other.proxyPassword; + this.proxyExcludes = other.proxyExcludes; this.forceBaseUrl = other.forceBaseUrl; this.baseUrl = other.baseUrl; this.disableGroupingGrid = other.disableGroupingGrid; @@ -265,6 +267,24 @@ public class ScmConfiguration return pluginUrl; } + /** + * Returns a set of glob patterns for urls which should excluded from + * proxy settings. + * + * + * @return set of glob patterns + * @since 1.23 + */ + public Set getProxyExcludes() + { + if (proxyExcludes == null) + { + proxyExcludes = Sets.newHashSet(); + } + + return proxyExcludes; + } + /** * Method description * @@ -573,6 +593,19 @@ public class ScmConfiguration this.pluginUrl = pluginUrl; } + /** + * Set glob patterns for urls which are should be excluded from proxy + * settings. + * + * + * @param proxyExcludes glob patterns + * @since 1.23 + */ + public void setProxyExcludes(Set proxyExcludes) + { + this.proxyExcludes = proxyExcludes; + } + /** * Method description * @@ -675,6 +708,11 @@ public class ScmConfiguration @XmlElement(name = "plugin-url") private String pluginUrl = DEFAULT_PLUGINURL; + /** glob patterns for urls which are excluded from proxy */ + @XmlElement(name = "proxy-excludes") + @XmlJavaTypeAdapter(XmlSetStringAdapter.class) + private Set proxyExcludes; + /** Field description */ private String proxyPassword; diff --git a/scm-core/src/main/java/sonia/scm/net/Proxies.java b/scm-core/src/main/java/sonia/scm/net/Proxies.java new file mode 100644 index 0000000000..596a626bda --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/net/Proxies.java @@ -0,0 +1,127 @@ +/** + * Copyright (c) 2010, Sebastian Sdorra All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. 2. Redistributions in + * binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. 3. Neither the name of SCM-Manager; + * nor the names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + + + +package sonia.scm.net; + +//~--- non-JDK imports -------------------------------------------------------- + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import sonia.scm.config.ScmConfiguration; +import sonia.scm.util.GlobUtil; + +//~--- JDK imports ------------------------------------------------------------ + +import java.net.URL; + +/** + * Util class for proxy settings. + * + * @author Sebastian Sdorra + * @since 1.23 + */ +public class Proxies +{ + + /** + * the logger for Proxies + */ + private static final Logger logger = LoggerFactory.getLogger(Proxies.class); + + //~--- get methods ---------------------------------------------------------- + + /** + * Returns true if proxy settings should be used to access the given url. + * + * + * @param configuration scm-manager main configuration + * @param url url to check + * + * @return true if proxy settings should be used + */ + public static boolean isEnabled(ScmConfiguration configuration, String url) + { + boolean result = false; + + if (configuration.isEnableProxy()) + { + result = true; + + int index = url.indexOf("://"); + + if (index > 0) + { + url = url.substring(index + 3); + } + + index = url.indexOf("/"); + + if (index > 0) + { + url = url.substring(0, index); + } + + for (String exclude : configuration.getProxyExcludes()) + { + if (GlobUtil.matches(exclude, url)) + { + logger.debug( + "disable proxy settings for url {}, because exclude {} matches", + url, exclude); + result = false; + + break; + } + } + } + else + { + logger.trace("proxy settings are disabled"); + } + + return result; + } + + /** + * Returns true if proxy settings should be used to access the given url. + * + * + * @param configuration scm-manager main configuration + * @param url url to check + * + * @return true if proxy settings should be used + */ + public static boolean isEnabled(ScmConfiguration configuration, URL url) + { + return isEnabled(configuration, url.getHost()); + } +} diff --git a/scm-core/src/test/java/sonia/scm/net/ProxiesTest.java b/scm-core/src/test/java/sonia/scm/net/ProxiesTest.java new file mode 100644 index 0000000000..f282bcf137 --- /dev/null +++ b/scm-core/src/test/java/sonia/scm/net/ProxiesTest.java @@ -0,0 +1,131 @@ +/** + * Copyright (c) 2010, Sebastian Sdorra All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. 2. Redistributions in + * binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. 3. Neither the name of SCM-Manager; + * nor the names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + + + +package sonia.scm.net; + +//~--- non-JDK imports -------------------------------------------------------- + +import com.google.common.collect.Sets; + +import org.junit.Test; + +import sonia.scm.config.ScmConfiguration; + +import static org.junit.Assert.*; + +/** + * + * @author Sebastian Sdorra + */ +public class ProxiesTest +{ + + /** + * Method description + * + */ + @Test + public void testDisabledWithoutExcludes() + { + ScmConfiguration config = createConfiguration(false); + + assertFalse(Proxies.isEnabled(config, "localhost")); + assertFalse(Proxies.isEnabled(config, "download.scm-manager.org")); + assertFalse(Proxies.isEnabled(config, "http://127.0.0.1")); + assertFalse(Proxies.isEnabled(config, "http://127.0.0.1/test/ka")); + } + + /** + * Method description + * + */ + @Test + public void testEnabledWithoutExcludes() + { + ScmConfiguration config = createConfiguration(true); + + assertTrue(Proxies.isEnabled(config, "localhost")); + assertTrue(Proxies.isEnabled(config, "download.scm-manager.org")); + assertTrue(Proxies.isEnabled(config, "http://127.0.0.1")); + assertTrue(Proxies.isEnabled(config, "http://127.0.0.1/test/ka")); + } + + /** + * Method description + * + */ + @Test + public void testWithExcludes() + { + ScmConfiguration config = createConfiguration(true, "127.0.0.1", + "localhost"); + + assertFalse(Proxies.isEnabled(config, "localhost")); + assertTrue(Proxies.isEnabled(config, "download.scm-manager.org")); + assertFalse(Proxies.isEnabled(config, "http://127.0.0.1")); + assertFalse(Proxies.isEnabled(config, "http://127.0.0.1/test/ka")); + } + + /** + * Method description + * + */ + @Test + public void testWithGlobExcludes() + { + ScmConfiguration config = createConfiguration(true, "127.*", "*host"); + + assertFalse(Proxies.isEnabled(config, "localhost")); + assertTrue(Proxies.isEnabled(config, "download.scm-manager.org")); + assertFalse(Proxies.isEnabled(config, "http://127.0.0.1")); + assertFalse(Proxies.isEnabled(config, "http://127.0.0.1/test/ka")); + } + + /** + * Method description + * + * + * @param enabled + * @param excludes + * + * @return + */ + private ScmConfiguration createConfiguration(boolean enabled, + String... excludes) + { + ScmConfiguration configuration = new ScmConfiguration(); + + configuration.setEnableProxy(enabled); + configuration.setProxyExcludes(Sets.newHashSet(excludes)); + + return configuration; + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/net/URLHttpClient.java b/scm-webapp/src/main/java/sonia/scm/net/URLHttpClient.java index c2a38223d5..66c1fb9914 100644 --- a/scm-webapp/src/main/java/sonia/scm/net/URLHttpClient.java +++ b/scm-webapp/src/main/java/sonia/scm/net/URLHttpClient.java @@ -127,7 +127,7 @@ public class URLHttpClient implements HttpClient */ @Inject public URLHttpClient(SCMContextProvider context, - ScmConfiguration configuration) + ScmConfiguration configuration) { this.context = context; this.configuration = configuration; @@ -169,7 +169,7 @@ public class URLHttpClient implements HttpClient */ @Override public HttpResponse post(String url, Map> parameters) - throws IOException + throws IOException { HttpURLConnection connection = (HttpURLConnection) openConnection(null, url); @@ -233,7 +233,7 @@ public class URLHttpClient implements HttpClient */ @Override public HttpResponse get(String url, Map> parameters) - throws IOException + throws IOException { url = createGetUrl(url, parameters); @@ -256,7 +256,7 @@ public class URLHttpClient implements HttpClient String url = createGetUrl(request.getUrl(), request.getParameters()); return new URLHttpResponse(openConnection(request, url), - request.isDecodeGZip()); + request.isDecodeGZip()); } //~--- methods -------------------------------------------------------------- @@ -271,8 +271,7 @@ public class URLHttpClient implements HttpClient * @param password */ private void appendBasicAuthHeader(HttpURLConnection connection, - String header, String username, - String password) + String header, String username, String password) { if (Util.isNotEmpty(username) || Util.isNotEmpty(password)) { @@ -288,7 +287,7 @@ public class URLHttpClient implements HttpClient auth = new String(Base64.encode(auth.getBytes())); connection.addRequestProperty(header, - PREFIX_BASIC_AUTHENTICATION.concat(auth)); + PREFIX_BASIC_AUTHENTICATION.concat(auth)); } } @@ -300,7 +299,7 @@ public class URLHttpClient implements HttpClient * @param connection */ private void appendHeaders(Map> headers, - URLConnection connection) + URLConnection connection) { if (Util.isNotEmpty(headers)) { @@ -343,8 +342,8 @@ public class URLHttpClient implements HttpClient * @throws IOException */ private void appendPostParameter(HttpURLConnection connection, - Map> parameters) - throws IOException + Map> parameters) + throws IOException { if (Util.isNotEmpty(parameters)) { @@ -395,7 +394,7 @@ public class URLHttpClient implements HttpClient * @param connection */ private void applySSLSettings(HttpRequest request, - HttpsURLConnection connection) + HttpsURLConnection connection) { if (request.isDisableCertificateValidation()) { @@ -511,7 +510,7 @@ public class URLHttpClient implements HttpClient * @throws IOException */ private HttpURLConnection openConnection(HttpRequest request, String spec) - throws IOException + throws IOException { return openConnection(request, new URL(spec)); } @@ -529,29 +528,30 @@ public class URLHttpClient implements HttpClient * @throws IOException */ private HttpURLConnection openConnection(HttpRequest request, URL url) - throws IOException + throws IOException { if (request == null) { + // TODO improve request = new HttpRequest(url.toExternalForm()); } HttpURLConnection connection = null; - if (!request.isIgnoreProxySettings() && configuration.isEnableProxy()) + if (!request.isIgnoreProxySettings() + && Proxies.isEnabled(configuration, url)) { if (logger.isDebugEnabled()) { logger.debug("fetch '{}' using proxy {}:{}", - new Object[] { url.toExternalForm(), - configuration.getProxyServer(), - configuration.getProxyPort() }); + new Object[] { url.toExternalForm(), + configuration.getProxyServer(), configuration.getProxyPort() }); } SocketAddress address = new InetSocketAddress(configuration.getProxyServer(), - configuration.getProxyPort()); + configuration.getProxyPort()); connection = (HttpURLConnection) url.openConnection(new Proxy(Proxy.Type.HTTP, @@ -590,19 +590,19 @@ public class URLHttpClient implements HttpClient String password = request.getPassword(); appendBasicAuthHeader(connection, HEADER_AUTHORIZATION, username, - password); + password); } connection.setRequestProperty(HEADER_ACCEPT_ENCODING, - HEADER_ACCEPT_ENCODING_VALUE); - connection.setRequestProperty( - HEADER_USERAGENT, HEADER_USERAGENT_VALUE.concat(context.getVersion())); + HEADER_ACCEPT_ENCODING_VALUE); + connection.setRequestProperty(HEADER_USERAGENT, + HEADER_USERAGENT_VALUE.concat(context.getVersion())); String username = configuration.getProxyUser(); String password = configuration.getProxyPassword(); appendBasicAuthHeader(connection, HEADER_PROXY_AUTHORIZATION, username, - password); + password); return connection; } diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/AetherPluginHandler.java b/scm-webapp/src/main/java/sonia/scm/plugin/AetherPluginHandler.java index 367861e7bd..6e4c8bac87 100644 --- a/scm-webapp/src/main/java/sonia/scm/plugin/AetherPluginHandler.java +++ b/scm-webapp/src/main/java/sonia/scm/plugin/AetherPluginHandler.java @@ -84,6 +84,7 @@ import java.util.Set; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; import javax.xml.bind.Marshaller; +import sonia.scm.net.Proxies; /** * @@ -215,7 +216,7 @@ public class AetherPluginHandler RemoteRepository rr = new RemoteRepository(repository.getId(), "default", repository.getUrl()); - if (configuration.isEnableProxy()) + if (Proxies.isEnabled(configuration, rr.getHost())) { Proxy proxy = DefaultProxySelector.createProxy(configuration); diff --git a/scm-webapp/src/main/webapp/resources/js/config/sonia.config.scmconfigpanel.js b/scm-webapp/src/main/webapp/resources/js/config/sonia.config.scmconfigpanel.js index 49bf63a990..f444dcdb31 100644 --- a/scm-webapp/src/main/webapp/resources/js/config/sonia.config.scmconfigpanel.js +++ b/scm-webapp/src/main/webapp/resources/js/config/sonia.config.scmconfigpanel.js @@ -54,6 +54,7 @@ Sonia.config.ScmConfigPanel = Ext.extend(Sonia.config.ConfigPanel,{ proxyPortText: 'Proxy Port', proxyUserText: 'Proxy User', proxyPasswordText: 'Proxy Password', + proxyExcludesText: 'Proxy Excludes', baseUrlText: 'Base Url', forceBaseUrlText: 'Force Base Url', disableGroupingGridText: 'Disable repository Groups', @@ -82,6 +83,7 @@ Sonia.config.ScmConfigPanel = Ext.extend(Sonia.config.ConfigPanel,{ proxyPortHelpText: 'The proxy port', proxyUserHelpText: 'The username for the proxy server authentication.', proxyPasswordHelpText: 'The password for the proxy server authentication.', + proxyExcludesHelpText: 'A comma separated list of glob patterns for hostnames which should be excluded from proxy settings.', baseUrlHelpText: 'The url of the application (with context path) i.e. http://localhost:8080/scm', forceBaseUrlHelpText: 'Redirects to the base url if the request comes from a other url', disableGroupingGridHelpText: 'Disable repository Groups. A complete page reload is required after a change of this value.', @@ -150,6 +152,7 @@ Sonia.config.ScmConfigPanel = Ext.extend(Sonia.config.ConfigPanel,{ Ext.getCmp('proxyPort').setDisabled( ! this.checked ); Ext.getCmp('proxyUser').setDisabled( ! this.checked ); Ext.getCmp('proxyPassword').setDisabled( ! this.checked ); + Ext.getCmp('proxyExcludes').setDisabled( ! this.checked ); } } },{ @@ -185,6 +188,14 @@ Sonia.config.ScmConfigPanel = Ext.extend(Sonia.config.ConfigPanel,{ disabled: true, helpText: this.proxyPasswordHelpText, allowBlank: true + },{ + id: 'proxyExcludes', + xtype: 'textfield', + fieldLabel: this.proxyExcludesText, + name: 'proxy-excludes', + disabled: true, + helpText: this.proxyExcludesHelpText, + allowBlank: true },{ xtype : 'textfield', fieldLabel : this.adminGroupsText, @@ -234,6 +245,9 @@ Sonia.config.ScmConfigPanel = Ext.extend(Sonia.config.ConfigPanel,{ if ( obj.enableProxy ){ Ext.getCmp('proxyServer').setDisabled(false); Ext.getCmp('proxyPort').setDisabled(false); + Ext.getCmp('proxyUser').setDisabled(false); + Ext.getCmp('proxyPassword').setDisabled(false); + Ext.getCmp('proxyExcludes').setDisabled(false); } clearTimeout(tid); el.unmask(); diff --git a/scm-webapp/src/main/webapp/resources/js/i18n/de.js b/scm-webapp/src/main/webapp/resources/js/i18n/de.js index 2ec1853b77..27cba73886 100644 --- a/scm-webapp/src/main/webapp/resources/js/i18n/de.js +++ b/scm-webapp/src/main/webapp/resources/js/i18n/de.js @@ -303,6 +303,7 @@ if (Sonia.config.ScmConfigPanel){ proxyPortText: 'Proxy Port', proxyUserText: 'Proxy User', proxyPasswordText: 'Proxy Passwort', + proxyExcludesText: 'Proxy Ausnahmen', baseUrlText: 'Basis-URL', forceBaseUrlText: 'Basis-URL forcieren', disableGroupingGridText: 'Repository-Gruppierung deaktivieren', @@ -333,6 +334,7 @@ if (Sonia.config.ScmConfigPanel){ proxyPortHelpText: 'Der Proxy-Port', proxyUserHelpText: 'Der Benutzername für die Authentifizierung am Proxy-Server.', proxyPasswordHelpText: 'Das Passwort für die Authentifizierung am Proxy-Server.', + proxyExcludesHelpText: 'Eine Komma-separierte liste von Glob-Patterns für servername die von den Proxy-Einstellungen ausgenommen werden sollen', baseUrlHelpText: 'Die vollständige URL des Server, inclusive Context-Pfad z.B.: http://localhost:8080/scm.', forceBaseUrlHelpText: 'Leitet alle Zugriffe die nicht von der Basis-URL kommen auf die Basis-URL um.', disableGroupingGridHelpText: 'Repository grupierung deaktivieren. Wenn dieser Wert verändert wird muss die Seite neu geladen werden.',