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:
Sebastian Sdorra
2021-08-19 11:27:51 +02:00
committed by GitHub
parent a7bb67f36b
commit 7f9f4e566c
54 changed files with 2996 additions and 1098 deletions

View File

@@ -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) {

View File

@@ -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);
}
}

View File

@@ -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;

View File

@@ -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 {