mirror of
				https://github.com/scm-manager/scm-manager.git
				synced 2025-10-31 10:35:56 +01:00 
			
		
		
		
	Proxy support for pull, push and mirror commands (#1773)
Apply proxy support for jGit by extracting the required functionality from the DefaultAdvancedHttpClient into its own class HttpURLConnectionFactory. This new class is now used by the DefaultAdvancedHttpClient and jGit. The HttpURLConnection also fixes proxy server authentication, which was non functional in DefaultAdvancedHttpClient. The proxy support for SVNKit is implemented by using the provided method of the BasicAuthenticationManager. For mercurial the support is configured by writing the required settings to a temporary hgrc file.
This commit is contained in:
		| @@ -24,24 +24,28 @@ | ||||
|  | ||||
| package sonia.scm.repository.spi; | ||||
|  | ||||
| import com.google.common.annotations.VisibleForTesting; | ||||
| import com.google.common.base.Stopwatch; | ||||
| import com.google.common.base.Strings; | ||||
| import com.google.common.collect.ImmutableList; | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
| import org.tmatesoft.svn.core.SVNException; | ||||
| import org.tmatesoft.svn.core.SVNURL; | ||||
| import org.tmatesoft.svn.core.auth.BasicAuthenticationManager; | ||||
| import org.tmatesoft.svn.core.auth.ISVNAuthenticationManager; | ||||
| import org.tmatesoft.svn.core.auth.SVNAuthentication; | ||||
| import org.tmatesoft.svn.core.auth.SVNPasswordAuthentication; | ||||
| import org.tmatesoft.svn.core.auth.SVNSSLAuthentication; | ||||
| import org.tmatesoft.svn.core.wc.SVNWCUtil; | ||||
| import org.tmatesoft.svn.core.wc.admin.SVNAdminClient; | ||||
| import sonia.scm.net.GlobalProxyConfiguration; | ||||
| import sonia.scm.net.ProxyConfiguration; | ||||
| import sonia.scm.repository.InternalRepositoryException; | ||||
| import sonia.scm.repository.api.MirrorCommandResult; | ||||
| import sonia.scm.repository.api.Pkcs12ClientCertificateCredential; | ||||
| import sonia.scm.repository.api.UsernamePasswordCredential; | ||||
|  | ||||
| import javax.annotation.Nonnull; | ||||
| import javax.net.ssl.TrustManager; | ||||
| import java.util.ArrayList; | ||||
| import java.util.Collection; | ||||
| @@ -55,10 +59,12 @@ public class SvnMirrorCommand extends AbstractSvnCommand implements MirrorComman | ||||
|   private static final Logger LOG = LoggerFactory.getLogger(SvnMirrorCommand.class); | ||||
|  | ||||
|   private final TrustManager trustManager; | ||||
|   private final GlobalProxyConfiguration globalProxyConfiguration; | ||||
|  | ||||
|   SvnMirrorCommand(SvnContext context, TrustManager trustManager) { | ||||
|   SvnMirrorCommand(SvnContext context, TrustManager trustManager, GlobalProxyConfiguration globalProxyConfiguration) { | ||||
|     super(context); | ||||
|     this.trustManager = trustManager; | ||||
|     this.globalProxyConfiguration = globalProxyConfiguration; | ||||
|   } | ||||
|  | ||||
|   @Override | ||||
| @@ -126,6 +132,28 @@ public class SvnMirrorCommand extends AbstractSvnCommand implements MirrorComman | ||||
|   } | ||||
|  | ||||
|   private SVNAdminClient createAdminClient(SVNURL url, MirrorCommandRequest mirrorCommandRequest) { | ||||
|     BasicAuthenticationManager authenticationManager = createAuthenticationManager(url, mirrorCommandRequest); | ||||
|     return new SVNAdminClient(authenticationManager, SVNWCUtil.createDefaultOptions(true)); | ||||
|   } | ||||
|  | ||||
|   @VisibleForTesting | ||||
|   BasicAuthenticationManager createAuthenticationManager(SVNURL url, MirrorCommandRequest mirrorCommandRequest) { | ||||
|     SVNAuthentication[] authentications = createAuthentications(url, mirrorCommandRequest); | ||||
|  | ||||
|     BasicAuthenticationManager authManager = new BasicAuthenticationManager(authentications) { | ||||
|       @Override | ||||
|       public TrustManager getTrustManager(SVNURL url) { | ||||
|         return trustManager; | ||||
|       } | ||||
|     }; | ||||
|     checkAndApplyProxyConfiguration( | ||||
|       authManager, mirrorCommandRequest.getProxyConfiguration().orElse(globalProxyConfiguration), url | ||||
|     ); | ||||
|     return authManager; | ||||
|   } | ||||
|  | ||||
|   @Nonnull | ||||
|   private SVNAuthentication[] createAuthentications(SVNURL url, MirrorCommandRequest mirrorCommandRequest) { | ||||
|     Collection<SVNAuthentication> authentications = new ArrayList<>(); | ||||
|     mirrorCommandRequest.getCredential(Pkcs12ClientCertificateCredential.class) | ||||
|       .map(c -> createTlsAuth(url, c)) | ||||
| @@ -133,15 +161,26 @@ public class SvnMirrorCommand extends AbstractSvnCommand implements MirrorComman | ||||
|     mirrorCommandRequest.getCredential(UsernamePasswordCredential.class) | ||||
|       .map(c -> SVNPasswordAuthentication.newInstance(c.username(), c.password(), false, url, false)) | ||||
|       .ifPresent(authentications::add); | ||||
|     ISVNAuthenticationManager authManager = new BasicAuthenticationManager( | ||||
|       authentications.toArray(new SVNAuthentication[authentications.size()])) { | ||||
|       @Override | ||||
|       public TrustManager getTrustManager(SVNURL url) { | ||||
|         return trustManager; | ||||
|       } | ||||
|     }; | ||||
|     return authentications.toArray(new SVNAuthentication[0]); | ||||
|   } | ||||
|  | ||||
|     return new SVNAdminClient(authManager, SVNWCUtil.createDefaultOptions(true)); | ||||
|   private void checkAndApplyProxyConfiguration(BasicAuthenticationManager authManager, ProxyConfiguration proxyConfiguration, SVNURL url) { | ||||
|     if (proxyConfiguration.isEnabled() && !proxyConfiguration.getExcludes().contains(url.getHost())) { | ||||
|       applyProxyConfiguration(authManager, proxyConfiguration); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   private void applyProxyConfiguration(BasicAuthenticationManager authManager, ProxyConfiguration proxyConfiguration) { | ||||
|     char[] password = null; | ||||
|     if (!Strings.isNullOrEmpty(proxyConfiguration.getPassword())){ | ||||
|       password = proxyConfiguration.getPassword().toCharArray(); | ||||
|     } | ||||
|     authManager.setProxy( | ||||
|       proxyConfiguration.getHost(), | ||||
|       proxyConfiguration.getPort(), | ||||
|       Strings.emptyToNull(proxyConfiguration.getUsername()), | ||||
|       password | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   private SVNSSLAuthentication createTlsAuth(SVNURL url, Pkcs12ClientCertificateCredential c) { | ||||
|   | ||||
| @@ -26,6 +26,7 @@ package sonia.scm.repository.spi; | ||||
|  | ||||
| import com.google.common.collect.ImmutableSet; | ||||
| import com.google.common.io.Closeables; | ||||
| import sonia.scm.net.GlobalProxyConfiguration; | ||||
| import sonia.scm.repository.Feature; | ||||
| import sonia.scm.repository.Repository; | ||||
| import sonia.scm.repository.SvnRepositoryHandler; | ||||
| @@ -65,16 +66,19 @@ public class SvnRepositoryServiceProvider extends RepositoryServiceProvider { | ||||
|   private final SvnWorkingCopyFactory workingCopyFactory; | ||||
|   private final HookContextFactory hookContextFactory; | ||||
|   private final TrustManager trustManager; | ||||
|   private final GlobalProxyConfiguration globalProxyConfiguration; | ||||
|  | ||||
|   SvnRepositoryServiceProvider(SvnRepositoryHandler handler, | ||||
|                                Repository repository, | ||||
|                                SvnWorkingCopyFactory workingCopyFactory, | ||||
|                                HookContextFactory hookContextFactory, | ||||
|                                TrustManager trustManager) { | ||||
|                                TrustManager trustManager, | ||||
|                                GlobalProxyConfiguration globalProxyConfiguration) { | ||||
|     this.context = new SvnContext(repository, handler.getDirectory(repository.getId())); | ||||
|     this.workingCopyFactory = workingCopyFactory; | ||||
|     this.hookContextFactory = hookContextFactory; | ||||
|     this.trustManager = trustManager; | ||||
|     this.globalProxyConfiguration = globalProxyConfiguration; | ||||
|   } | ||||
|  | ||||
|   @Override | ||||
| @@ -149,6 +153,6 @@ public class SvnRepositoryServiceProvider extends RepositoryServiceProvider { | ||||
|  | ||||
|   @Override | ||||
|   public MirrorCommand getMirrorCommand() { | ||||
|     return new SvnMirrorCommand(context, trustManager); | ||||
|     return new SvnMirrorCommand(context, trustManager, globalProxyConfiguration); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -25,6 +25,7 @@ | ||||
| package sonia.scm.repository.spi; | ||||
|  | ||||
| import com.google.inject.Inject; | ||||
| import sonia.scm.net.GlobalProxyConfiguration; | ||||
| import sonia.scm.plugin.Extension; | ||||
| import sonia.scm.repository.Repository; | ||||
| import sonia.scm.repository.SvnRepositoryHandler; | ||||
| @@ -40,16 +41,19 @@ public class SvnRepositoryServiceResolver implements RepositoryServiceResolver { | ||||
|   private final SvnWorkingCopyFactory workingCopyFactory; | ||||
|   private final HookContextFactory hookContextFactory; | ||||
|   private final TrustManager trustManager; | ||||
|   private final GlobalProxyConfiguration globalProxyConfiguration; | ||||
|  | ||||
|   @Inject | ||||
|   public SvnRepositoryServiceResolver(SvnRepositoryHandler handler, | ||||
|                                       SvnWorkingCopyFactory workingCopyFactory, | ||||
|                                       HookContextFactory hookContextFactory, | ||||
|                                       TrustManager trustManager) { | ||||
|                                       TrustManager trustManager, | ||||
|                                       GlobalProxyConfiguration globalProxyConfiguration) { | ||||
|     this.handler = handler; | ||||
|     this.workingCopyFactory = workingCopyFactory; | ||||
|     this.hookContextFactory = hookContextFactory; | ||||
|     this.trustManager = trustManager; | ||||
|     this.globalProxyConfiguration = globalProxyConfiguration; | ||||
|   } | ||||
|  | ||||
|   @Override | ||||
| @@ -57,7 +61,9 @@ public class SvnRepositoryServiceResolver implements RepositoryServiceResolver { | ||||
|     SvnRepositoryServiceProvider provider = null; | ||||
|  | ||||
|     if (SvnRepositoryHandler.TYPE_NAME.equalsIgnoreCase(repository.getType())) { | ||||
|       provider = new SvnRepositoryServiceProvider(handler, repository, workingCopyFactory, hookContextFactory, trustManager); | ||||
|       provider = new SvnRepositoryServiceProvider( | ||||
|         handler, repository, workingCopyFactory, hookContextFactory, trustManager, globalProxyConfiguration | ||||
|       ); | ||||
|     } | ||||
|  | ||||
|     return provider; | ||||
|   | ||||
| @@ -32,10 +32,15 @@ import org.mockito.junit.MockitoJUnitRunner; | ||||
| import org.tmatesoft.svn.core.SVNException; | ||||
| import org.tmatesoft.svn.core.SVNURL; | ||||
| import org.tmatesoft.svn.core.auth.BasicAuthenticationManager; | ||||
| import org.tmatesoft.svn.core.auth.ISVNAuthenticationManager; | ||||
| import org.tmatesoft.svn.core.auth.SVNAuthentication; | ||||
| import org.tmatesoft.svn.core.auth.SVNPasswordAuthentication; | ||||
| import org.tmatesoft.svn.core.io.SVNRepositoryFactory; | ||||
| import org.tmatesoft.svn.core.wc.SVNWCUtil; | ||||
| import org.tmatesoft.svn.core.wc.admin.SVNAdminClient; | ||||
| import sonia.scm.config.ScmConfiguration; | ||||
| import sonia.scm.net.GlobalProxyConfiguration; | ||||
| import sonia.scm.net.ProxyConfiguration; | ||||
| import sonia.scm.repository.RepositoryTestData; | ||||
| import sonia.scm.repository.api.MirrorCommandResult; | ||||
| import sonia.scm.repository.api.SimpleUsernamePasswordCredential; | ||||
| @@ -45,8 +50,11 @@ import java.io.File; | ||||
| import java.io.IOException; | ||||
| import java.util.function.Consumer; | ||||
|  | ||||
| import static java.util.Collections.singleton; | ||||
| import static java.util.Collections.singletonList; | ||||
| import static org.assertj.core.api.Assertions.assertThat; | ||||
| import static org.mockito.Mockito.mock; | ||||
| import static org.mockito.Mockito.when; | ||||
| import static sonia.scm.repository.api.MirrorCommandResult.ResultType.OK; | ||||
|  | ||||
| @RunWith(MockitoJUnitRunner.class) | ||||
| @@ -57,6 +65,8 @@ public class SvnMirrorCommandTest extends AbstractSvnCommandTestBase { | ||||
|  | ||||
|   private SvnContext emptyContext; | ||||
|  | ||||
|   private final ScmConfiguration configuration = new ScmConfiguration(); | ||||
|  | ||||
|   @Before | ||||
|   public void bendContextToNewRepository() throws IOException, SVNException { | ||||
|     emptyContext = createEmptyContext(); | ||||
| @@ -91,10 +101,119 @@ public class SvnMirrorCommandTest extends AbstractSvnCommandTestBase { | ||||
|   } | ||||
|  | ||||
|   @Test | ||||
|   public void shouldUseCredentials() { | ||||
|     MirrorCommandResult result = callMirror(emptyContext, repositoryDirectory, createCredential("svnadmin", "secret")); | ||||
|   public void shouldUseCredentials() throws SVNException { | ||||
|     SVNURL url = SVNURL.parseURIEncoded("https://svn.hitchhiker.com"); | ||||
|  | ||||
|     assertThat(result.getResult()).isEqualTo(OK); | ||||
|     MirrorCommandRequest request = new MirrorCommandRequest(); | ||||
|     request.setCredentials( | ||||
|       singletonList(new SimpleUsernamePasswordCredential("trillian", "secret".toCharArray())) | ||||
|     ); | ||||
|  | ||||
|     SvnMirrorCommand mirrorCommand = createMirrorCommand(emptyContext); | ||||
|     BasicAuthenticationManager authenticationManager = mirrorCommand.createAuthenticationManager(url, request); | ||||
|  | ||||
|     SVNAuthentication authentication = authenticationManager.getFirstAuthentication( | ||||
|       ISVNAuthenticationManager.PASSWORD, "Hitchhiker Auth Gate", url | ||||
|     ); | ||||
|  | ||||
|     assertThat(authentication).isInstanceOfSatisfying(SVNPasswordAuthentication.class, passwordAuth -> { | ||||
|       assertThat(passwordAuth.getUserName()).isEqualTo("trillian"); | ||||
|       assertThat(passwordAuth.getPasswordValue()).isEqualTo("secret".toCharArray()); | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   @Test | ||||
|   public void shouldUseTrustManager() throws SVNException { | ||||
|     SVNURL url = SVNURL.parseURIEncoded("https://svn.hitchhiker.com"); | ||||
|  | ||||
|     SvnMirrorCommand mirrorCommand = createMirrorCommand(emptyContext); | ||||
|     BasicAuthenticationManager authenticationManager = mirrorCommand.createAuthenticationManager(url, new MirrorCommandRequest()); | ||||
|  | ||||
|     assertThat(authenticationManager.getTrustManager(url)).isSameAs(trustManager); | ||||
|   } | ||||
|  | ||||
|   @Test | ||||
|   public void shouldApplySimpleProxySettings() throws SVNException { | ||||
|     configuration.setEnableProxy(true); | ||||
|     configuration.setProxyServer("proxy.hitchhiker.com"); | ||||
|     configuration.setProxyPort(3128); | ||||
|  | ||||
|     BasicAuthenticationManager authenticationManager = createAuthenticationManager(); | ||||
|  | ||||
|     assertThat(authenticationManager.getProxyHost()).isEqualTo("proxy.hitchhiker.com"); | ||||
|     assertThat(authenticationManager.getProxyPort()).isEqualTo(3128); | ||||
|     assertThat(authenticationManager.getProxyUserName()).isNull(); | ||||
|     assertThat(authenticationManager.getProxyPasswordValue()).isNull(); | ||||
|   } | ||||
|  | ||||
|   @Test | ||||
|   public void shouldApplyProxySettingsWithCredentials() throws SVNException { | ||||
|     configuration.setEnableProxy(true); | ||||
|     configuration.setProxyServer("proxy.hitchhiker.com"); | ||||
|     configuration.setProxyPort(3128); | ||||
|     configuration.setProxyUser("trillian"); | ||||
|     configuration.setProxyPassword("secret"); | ||||
|  | ||||
|     BasicAuthenticationManager authenticationManager = createAuthenticationManager(); | ||||
|  | ||||
|     assertThat(authenticationManager.getProxyHost()).isEqualTo("proxy.hitchhiker.com"); | ||||
|     assertThat(authenticationManager.getProxyPort()).isEqualTo(3128); | ||||
|     assertThat(authenticationManager.getProxyUserName()).isEqualTo("trillian"); | ||||
|     assertThat(authenticationManager.getProxyPasswordValue()).isEqualTo("secret".toCharArray()); | ||||
|   } | ||||
|  | ||||
|   @Test | ||||
|   public void shouldSkipProxySettingsIfDisabled() throws SVNException { | ||||
|     configuration.setEnableProxy(false); | ||||
|     configuration.setProxyServer("proxy.hitchhiker.com"); | ||||
|     configuration.setProxyPort(3128); | ||||
|  | ||||
|     BasicAuthenticationManager authenticationManager = createAuthenticationManager(); | ||||
|  | ||||
|     assertThat(authenticationManager.getProxyHost()).isNull(); | ||||
|   } | ||||
|  | ||||
|   @Test | ||||
|   public void shouldSkipProxySettingsIfHostIsOnExcludeList() throws SVNException { | ||||
|     configuration.setEnableProxy(true); | ||||
|     configuration.setProxyServer("proxy.hitchhiker.com"); | ||||
|     configuration.setProxyPort(3128); | ||||
|     configuration.setProxyExcludes(singleton("svn.hitchhiker.com")); | ||||
|  | ||||
|     BasicAuthenticationManager authenticationManager = createAuthenticationManager(); | ||||
|  | ||||
|     assertThat(authenticationManager.getProxyHost()).isNull(); | ||||
|   } | ||||
|  | ||||
|   @Test | ||||
|   public void shouldApplyLocalProxySettings() throws SVNException { | ||||
|     MirrorCommandRequest request = new MirrorCommandRequest(); | ||||
|     request.setProxyConfiguration(createProxyConfiguration()); | ||||
|  | ||||
|     BasicAuthenticationManager authenticationManager = createAuthenticationManager(request); | ||||
|     assertThat(authenticationManager.getProxyHost()).isEqualTo("proxy.hitchhiker.com"); | ||||
|     assertThat(authenticationManager.getProxyPort()).isEqualTo(3128); | ||||
|     assertThat(authenticationManager.getProxyUserName()).isNull(); | ||||
|     assertThat(authenticationManager.getProxyPasswordValue()).isNull(); | ||||
|   } | ||||
|  | ||||
|   private ProxyConfiguration createProxyConfiguration() { | ||||
|     ProxyConfiguration configuration = mock(ProxyConfiguration.class); | ||||
|     when(configuration.isEnabled()).thenReturn(true); | ||||
|     when(configuration.getHost()).thenReturn("proxy.hitchhiker.com"); | ||||
|     when(configuration.getPort()).thenReturn(3128); | ||||
|     return configuration; | ||||
|   } | ||||
|  | ||||
|   private BasicAuthenticationManager createAuthenticationManager() throws SVNException { | ||||
|     return createAuthenticationManager(new MirrorCommandRequest()); | ||||
|   } | ||||
|  | ||||
|   private BasicAuthenticationManager createAuthenticationManager(MirrorCommandRequest request) throws SVNException { | ||||
|     SVNURL url = SVNURL.parseURIEncoded("https://svn.hitchhiker.com"); | ||||
|  | ||||
|     SvnMirrorCommand mirrorCommand = createMirrorCommand(emptyContext); | ||||
|     return mirrorCommand.createAuthenticationManager(url, request); | ||||
|   } | ||||
|  | ||||
|   private MirrorCommandResult callMirrorUpdate(SvnContext context, File source) { | ||||
| @@ -115,11 +234,7 @@ public class SvnMirrorCommandTest extends AbstractSvnCommandTestBase { | ||||
|   } | ||||
|  | ||||
|   private SvnMirrorCommand createMirrorCommand(SvnContext context) { | ||||
|     return new SvnMirrorCommand(context, trustManager); | ||||
|   } | ||||
|  | ||||
|   private Consumer<MirrorCommandRequest> createCredential(String username, String password) { | ||||
|     return request -> request.setCredentials(singletonList(new SimpleUsernamePasswordCredential(username, password.toCharArray()))); | ||||
|     return new SvnMirrorCommand(context, trustManager, new GlobalProxyConfiguration(configuration)); | ||||
|   } | ||||
|  | ||||
|   private SvnContext createEmptyContext() throws SVNException, IOException { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user