diff --git a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnMirrorAuthenticationFactory.java b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnMirrorAuthenticationFactory.java new file mode 100644 index 0000000000..a4d2b4943a --- /dev/null +++ b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnMirrorAuthenticationFactory.java @@ -0,0 +1,109 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sonia.scm.repository.spi; + +import com.google.common.base.Strings; +import org.tmatesoft.svn.core.SVNURL; +import org.tmatesoft.svn.core.auth.BasicAuthenticationManager; +import org.tmatesoft.svn.core.auth.SVNAuthentication; +import org.tmatesoft.svn.core.auth.SVNPasswordAuthentication; +import org.tmatesoft.svn.core.auth.SVNSSLAuthentication; +import sonia.scm.net.GlobalProxyConfiguration; +import sonia.scm.net.ProxyConfiguration; +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; + +class SvnMirrorAuthenticationFactory { + + private final TrustManager trustManager; + private final GlobalProxyConfiguration globalProxyConfiguration; + + SvnMirrorAuthenticationFactory(TrustManager trustManager, GlobalProxyConfiguration globalProxyConfiguration) { + this.trustManager = trustManager; + this.globalProxyConfiguration = globalProxyConfiguration; + } + + BasicAuthenticationManager create(SVNURL url, MirrorCommandRequest request) { + SVNAuthentication[] authentications = createAuthentications(url, request); + + BasicAuthenticationManager authManager = new BasicAuthenticationManager(authentications) { + @Override + public TrustManager getTrustManager(SVNURL url) { + return trustManager; + } + }; + checkAndApplyProxyConfiguration( + authManager, request.getProxyConfiguration().filter(ProxyConfiguration::isEnabled).orElse(globalProxyConfiguration), url + ); + return authManager; + } + + @Nonnull + private SVNAuthentication[] createAuthentications(SVNURL url, MirrorCommandRequest mirrorCommandRequest) { + Collection authentications = new ArrayList<>(); + mirrorCommandRequest.getCredential(Pkcs12ClientCertificateCredential.class) + .map(c -> createTlsAuth(url, c)) + .ifPresent(authentications::add); + mirrorCommandRequest.getCredential(UsernamePasswordCredential.class) + .map(c -> SVNPasswordAuthentication.newInstance(c.username(), c.password(), false, url, false)) + .ifPresent(authentications::add); + return authentications.toArray(new SVNAuthentication[0]); + } + + 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) { + return SVNSSLAuthentication.newInstance( + c.getCertificate(), + c.getPassword(), + false, + url, + true); + } + + +} diff --git a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnMirrorCommand.java b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnMirrorCommand.java index 5ac6274ab0..7c6825e6d6 100644 --- a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnMirrorCommand.java +++ b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnMirrorCommand.java @@ -24,31 +24,20 @@ 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.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; import static java.util.Arrays.asList; import static sonia.scm.repository.api.MirrorCommandResult.ResultType.FAILED; @@ -57,44 +46,62 @@ import static sonia.scm.repository.api.MirrorCommandResult.ResultType.OK; public class SvnMirrorCommand extends AbstractSvnCommand implements MirrorCommand { private static final Logger LOG = LoggerFactory.getLogger(SvnMirrorCommand.class); + private static final int TARGET_NOT_INITIALIZED_ERROR_CODE = 204899; - private final TrustManager trustManager; - private final GlobalProxyConfiguration globalProxyConfiguration; + private final SvnMirrorAuthenticationFactory authenticationFactory; SvnMirrorCommand(SvnContext context, TrustManager trustManager, GlobalProxyConfiguration globalProxyConfiguration) { + this(context, new SvnMirrorAuthenticationFactory(trustManager, globalProxyConfiguration)); + } + + SvnMirrorCommand(SvnContext context, SvnMirrorAuthenticationFactory authenticationFactory) { super(context); - this.trustManager = trustManager; - this.globalProxyConfiguration = globalProxyConfiguration; + this.authenticationFactory = authenticationFactory; } @Override public MirrorCommandResult mirror(MirrorCommandRequest mirrorCommandRequest) { - SVNURL url = createUrlForLocalRepository(); - return withAdminClient(mirrorCommandRequest, admin -> { - SVNURL source = SVNURL.parseURIEncoded(mirrorCommandRequest.getSourceUrl()); - admin.doCompleteSynchronize(source, url); - }); + return mirror(mirrorCommandRequest, SVNAdminClient::doCompleteSynchronize); } @Override public MirrorCommandResult update(MirrorCommandRequest mirrorCommandRequest) { - SVNURL url = createUrlForLocalRepository(); - return withAdminClient(mirrorCommandRequest, admin -> admin.doSynchronize(url)); + return mirror(mirrorCommandRequest, this::doUpdate); } - private MirrorCommandResult withAdminClient(MirrorCommandRequest mirrorCommandRequest, AdminConsumer consumer) { + private void doUpdate(SVNAdminClient admin, SVNURL sourceUrl, SVNURL targetUrl) throws SVNException { + try { + admin.doSynchronize(targetUrl); + } catch (SVNException e) { + if (isTargetNotInitializedException(e)) { + LOG.warn("update failed, because destination repository has not been initialized, start complete synchronize"); + admin.doCompleteSynchronize(sourceUrl, targetUrl); + } else { + throw e; + } + } + } + + private boolean isTargetNotInitializedException(SVNException e) { + return e.getErrorMessage().getErrorCode().getCode() == TARGET_NOT_INITIALIZED_ERROR_CODE; + } + + private MirrorCommandResult mirror(MirrorCommandRequest mirrorCommandRequest, Worker worker) { Stopwatch stopwatch = Stopwatch.createStarted(); long beforeUpdate; long afterUpdate; try { beforeUpdate = context.open().getLatestRevision(); - SVNURL url = createUrlForLocalRepository(); - SVNAdminClient admin = createAdminClient(url, mirrorCommandRequest); - handleConsumer(mirrorCommandRequest, consumer, url, admin); + SVNURL sourceUrl = SVNURL.parseURIEncoded(mirrorCommandRequest.getSourceUrl()); + SVNURL targetUrl = createUrlForLocalRepository(); + SVNAdminClient admin = createAdminClient(sourceUrl, mirrorCommandRequest); + + worker.doWork(admin, sourceUrl, targetUrl); + afterUpdate = context.open().getLatestRevision(); } catch (SVNException e) { - LOG.info("Could not mirror svn repository", e); + LOG.warn("Could not mirror svn repository", e); return new MirrorCommandResult( FAILED, asList( @@ -110,19 +117,6 @@ public class SvnMirrorCommand extends AbstractSvnCommand implements MirrorComman ); } - private void handleConsumer(MirrorCommandRequest mirrorCommandRequest, AdminConsumer consumer, SVNURL url, SVNAdminClient admin) throws SVNException { - try { - consumer.accept(admin); - } catch (SVNException e) { - if (e.getMessage().equals("svn: E204899: Destination repository has not been initialized")) { - SVNURL source = SVNURL.parseURIEncoded(mirrorCommandRequest.getSourceUrl()); - admin.doCompleteSynchronize(source, url); - } else { - throw e; - } - } - } - private SVNURL createUrlForLocalRepository() { try { return SVNURL.fromFile(context.getDirectory()); @@ -131,68 +125,12 @@ public class SvnMirrorCommand extends AbstractSvnCommand implements MirrorComman } } - private SVNAdminClient createAdminClient(SVNURL url, MirrorCommandRequest mirrorCommandRequest) { - BasicAuthenticationManager authenticationManager = createAuthenticationManager(url, mirrorCommandRequest); + private SVNAdminClient createAdminClient(SVNURL sourceUrl, MirrorCommandRequest mirrorCommandRequest) { + BasicAuthenticationManager authenticationManager = authenticationFactory.create(sourceUrl, 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().filter(ProxyConfiguration::isEnabled).orElse(globalProxyConfiguration), url - ); - return authManager; - } - - @Nonnull - private SVNAuthentication[] createAuthentications(SVNURL url, MirrorCommandRequest mirrorCommandRequest) { - Collection authentications = new ArrayList<>(); - mirrorCommandRequest.getCredential(Pkcs12ClientCertificateCredential.class) - .map(c -> createTlsAuth(url, c)) - .ifPresent(authentications::add); - mirrorCommandRequest.getCredential(UsernamePasswordCredential.class) - .map(c -> SVNPasswordAuthentication.newInstance(c.username(), c.password(), false, url, false)) - .ifPresent(authentications::add); - return authentications.toArray(new SVNAuthentication[0]); - } - - 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) { - return SVNSSLAuthentication.newInstance( - c.getCertificate(), - c.getPassword(), - false, - url, - true); - } - - private interface AdminConsumer { - void accept(SVNAdminClient adminClient) throws SVNException; + private interface Worker { + void doWork(SVNAdminClient adminClient, SVNURL sourceUrl, SVNURL targetUrl) throws SVNException; } } diff --git a/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/SvnMirrorAuthenticationFactoryTest.java b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/SvnMirrorAuthenticationFactoryTest.java new file mode 100644 index 0000000000..eedc028aa1 --- /dev/null +++ b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/SvnMirrorAuthenticationFactoryTest.java @@ -0,0 +1,204 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sonia.scm.repository.spi; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +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 sonia.scm.config.ScmConfiguration; +import sonia.scm.net.GlobalProxyConfiguration; +import sonia.scm.net.ProxyConfiguration; +import sonia.scm.repository.api.SimpleUsernamePasswordCredential; + +import javax.net.ssl.X509TrustManager; + +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.lenient; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class SvnMirrorAuthenticationFactoryTest { + + @Mock + private X509TrustManager trustManager; + + private final ScmConfiguration configuration = new ScmConfiguration(); + + private SvnMirrorAuthenticationFactory authenticationFactory; + + @BeforeEach + void setUp() { + authenticationFactory = new SvnMirrorAuthenticationFactory( + trustManager, new GlobalProxyConfiguration(configuration) + ); + } + + + @Test + void shouldUseCredentials() throws SVNException { + SVNURL url = SVNURL.parseURIEncoded("https://svn.hitchhiker.com"); + + MirrorCommandRequest request = new MirrorCommandRequest(); + request.setCredentials( + singletonList(new SimpleUsernamePasswordCredential("trillian", "secret".toCharArray())) + ); + + BasicAuthenticationManager authenticationManager = authenticationFactory.create(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 + void shouldUseTrustManager() throws SVNException { + SVNURL url = SVNURL.parseURIEncoded("https://svn.hitchhiker.com"); + + BasicAuthenticationManager authenticationManager = authenticationFactory.create(url, new MirrorCommandRequest()); + + assertThat(authenticationManager.getTrustManager(url)).isSameAs(trustManager); + } + + @Test + 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 + 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 + void shouldSkipProxySettingsIfDisabled() throws SVNException { + configuration.setEnableProxy(false); + configuration.setProxyServer("proxy.hitchhiker.com"); + configuration.setProxyPort(3128); + + BasicAuthenticationManager authenticationManager = createAuthenticationManager(); + + assertThat(authenticationManager.getProxyHost()).isNull(); + } + + @Test + 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 + 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(); + } + + @Test + void shouldNotApplyDisabledLocalProxySettings() throws SVNException { + MirrorCommandRequest request = new MirrorCommandRequest(); + request.setProxyConfiguration(createDisabledProxyConfiguration()); + + BasicAuthenticationManager authenticationManager = createAuthenticationManager(request); + assertThat(authenticationManager.getProxyHost()).isNull(); + assertThat(authenticationManager.getProxyPort()).isZero(); + 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 ProxyConfiguration createDisabledProxyConfiguration() { + ProxyConfiguration configuration = mock(ProxyConfiguration.class); + when(configuration.isEnabled()).thenReturn(false); + lenient().when(configuration.getHost()).thenReturn("proxy.hitchhiker.com"); + lenient().when(configuration.getPort()).thenReturn(3128); + return configuration; + } + + private BasicAuthenticationManager createAuthenticationManager() throws SVNException { + return createAuthenticationManager(new MirrorCommandRequest()); + } + + private BasicAuthenticationManager createAuthenticationManager(MirrorCommandRequest request) throws SVNException { + SVNURL sourceUrl = SVNURL.parseURIEncoded("https://svn.hitchhiker.com"); + + return authenticationFactory.create(sourceUrl, request); + } + + +} diff --git a/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/SvnMirrorCommandTest.java b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/SvnMirrorCommandTest.java index 811b977832..3af18005e8 100644 --- a/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/SvnMirrorCommandTest.java +++ b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/SvnMirrorCommandTest.java @@ -32,30 +32,21 @@ 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; import javax.net.ssl.X509TrustManager; 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.lenient; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import static org.mockito.Mockito.verify; import static sonia.scm.repository.api.MirrorCommandResult.ResultType.OK; @RunWith(MockitoJUnitRunner.class) @@ -64,6 +55,9 @@ public class SvnMirrorCommandTest extends AbstractSvnCommandTestBase { @Mock private X509TrustManager trustManager; + @Mock + private SvnMirrorAuthenticationFactory authenticationFactory; + private SvnContext emptyContext; private final ScmConfiguration configuration = new ScmConfiguration(); @@ -75,8 +69,7 @@ public class SvnMirrorCommandTest extends AbstractSvnCommandTestBase { @Test public void shouldDoInitialMirror() { - MirrorCommandResult result = callMirror(emptyContext, repositoryDirectory, c -> { - }); + MirrorCommandResult result = callMirror(emptyContext, repositoryDirectory); assertThat(result.getResult()).isEqualTo(OK); } @@ -102,139 +95,13 @@ public class SvnMirrorCommandTest extends AbstractSvnCommandTestBase { } @Test - public void shouldUseCredentials() throws SVNException { - SVNURL url = SVNURL.parseURIEncoded("https://svn.hitchhiker.com"); + public void shouldUseSourceUrlForAuthentication() throws SVNException, IOException { + SvnMirrorCommand command = new SvnMirrorCommand(createEmptyContext(), authenticationFactory); + MirrorCommandRequest request = createRequest(repositoryDirectory); - MirrorCommandRequest request = new MirrorCommandRequest(); - request.setCredentials( - singletonList(new SimpleUsernamePasswordCredential("trillian", "secret".toCharArray())) - ); + command.mirror(request); - 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(); - } - - @Test - public void shouldNotApplyDisabledLocalProxySettings() throws SVNException { - MirrorCommandRequest request = new MirrorCommandRequest(); - request.setProxyConfiguration(createDisabledProxyConfiguration()); - - BasicAuthenticationManager authenticationManager = createAuthenticationManager(request); - assertThat(authenticationManager.getProxyHost()).isNull(); - assertThat(authenticationManager.getProxyPort()).isZero(); - 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 ProxyConfiguration createDisabledProxyConfiguration() { - ProxyConfiguration configuration = mock(ProxyConfiguration.class); - when(configuration.isEnabled()).thenReturn(false); - lenient().when(configuration.getHost()).thenReturn("proxy.hitchhiker.com"); - lenient().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); + verify(authenticationFactory).create(SVNURL.parseURIEncoded(request.getSourceUrl()), request); } private MirrorCommandResult callMirrorUpdate(SvnContext context, File source) { @@ -242,10 +109,8 @@ public class SvnMirrorCommandTest extends AbstractSvnCommandTestBase { return createMirrorCommand(context).update(request); } - private MirrorCommandResult callMirror(SvnContext context, File source, Consumer consumer) { - MirrorCommandRequest request = createRequest(source); - consumer.accept(request); - return createMirrorCommand(context).mirror(request); + private MirrorCommandResult callMirror(SvnContext context, File source) { + return createMirrorCommand(context).mirror(createRequest(source)); } private MirrorCommandRequest createRequest(File source) {