diff options
author | Thomas Wolf <thomas.wolf@paranor.ch> | 2018-09-14 22:40:08 +0200 |
---|---|---|
committer | Matthias Sohn <matthias.sohn@sap.com> | 2018-11-06 13:17:21 +0100 |
commit | 08b0a8632d54a24d92075b94d0b0134b69146ba2 (patch) | |
tree | c7f85adceb9529545fbfb004a6bbd6559c71dd37 /org.eclipse.jgit.test | |
parent | 3ec3ca0f097cb27b190a17f4fde6880c871951e0 (diff) | |
download | jgit-08b0a8632d54a24d92075b94d0b0134b69146ba2.tar.gz jgit-08b0a8632d54a24d92075b94d0b0134b69146ba2.zip |
Ssh tests with an Apache MINA sshd test git server
Add a simple ssh git server based on Apache MINA sshd, and use it
in new tests that verify ssh operations and in particular a number
of bugs that had cropped up over time in JSch.
The git server supports fetching only, and sftp access.
The tests are all in an abstract base class; the concrete JschSshTest
class only provides ssh-specific test setup. So the same tests could
be run easily also with some other ssh client.
Bug: 520927
Change-Id: Ide6687b717fb497a29fc83f22b07390a26dfce1d
Signed-off-by: Thomas Wolf <thomas.wolf@paranor.ch>
Diffstat (limited to 'org.eclipse.jgit.test')
9 files changed, 635 insertions, 1 deletions
diff --git a/org.eclipse.jgit.test/BUILD b/org.eclipse.jgit.test/BUILD index 186de25d94..07597f3251 100644 --- a/org.eclipse.jgit.test/BUILD +++ b/org.eclipse.jgit.test/BUILD @@ -20,6 +20,7 @@ HELPERS = glob(["src/**/*.java"]) + [PKG + c for c in [ "revwalk/RevWalkTestCase.java", "transport/ObjectIdMatcher.java", "transport/SpiTransport.java", + "transport/SshTestBase.java", "treewalk/FileTreeIteratorWithTimeControl.java", "treewalk/filter/AlwaysCloneTreeFilter.java", "test/resources/SampleDataRepositoryTestCase.java", @@ -44,6 +45,9 @@ java_library( resources = DATA, deps = [ "//lib:junit", + "//lib:jsch", + "//lib:sshd-core", + "//lib:sshd-sftp", "//org.eclipse.jgit:jgit", "//org.eclipse.jgit.junit:junit", ], diff --git a/org.eclipse.jgit.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.test/META-INF/MANIFEST.MF index 6df9e39094..6514277ac5 100644 --- a/org.eclipse.jgit.test/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit.test/META-INF/MANIFEST.MF @@ -10,6 +10,7 @@ Bundle-ActivationPolicy: lazy Bundle-RequiredExecutionEnvironment: JavaSE-1.8 Import-Package: com.googlecode.javaewah;version="[1.1.6,2.0.0)", com.jcraft.jsch;version="[0.1.54,0.2.0)", + org.eclipse.jgit.annotations;version="[5.2.0,5.3.0)", org.eclipse.jgit.api;version="[5.2.0,5.3.0)", org.eclipse.jgit.api.errors;version="[5.2.0,5.3.0)", org.eclipse.jgit.attributes;version="[5.2.0,5.3.0)", @@ -34,6 +35,7 @@ Import-Package: com.googlecode.javaewah;version="[1.1.6,2.0.0)", org.eclipse.jgit.internal.storage.reftree;version="[5.2.0,5.3.0)", org.eclipse.jgit.internal.transport.parser;version="[5.2.0,5.3.0)", org.eclipse.jgit.junit;version="[5.2.0,5.3.0)", + org.eclipse.jgit.junit.ssh;version="[5.2.0,5.3.0)", org.eclipse.jgit.lfs;version="[5.2.0,5.3.0)", org.eclipse.jgit.lib;version="[5.2.0,5.3.0)", org.eclipse.jgit.merge;version="[5.2.0,5.3.0)", diff --git a/org.eclipse.jgit.test/pom.xml b/org.eclipse.jgit.test/pom.xml index 6aa34f5b76..3d5df9a68a 100644 --- a/org.eclipse.jgit.test/pom.xml +++ b/org.eclipse.jgit.test/pom.xml @@ -112,6 +112,18 @@ <artifactId>org.eclipse.jgit.pgm</artifactId> <version>${project.version}</version> </dependency> + + <dependency> + <groupId>org.apache.sshd</groupId> + <artifactId>sshd-core</artifactId> + <version>${apache-sshd-version}</version> + </dependency> + + <dependency> + <groupId>org.apache.sshd</groupId> + <artifactId>sshd-sftp</artifactId> + <version>${apache-sshd-version}</version> + </dependency> </dependencies> <profiles> diff --git a/org.eclipse.jgit.test/tests.bzl b/org.eclipse.jgit.test/tests.bzl index bc06e3ef40..b9ad8b2126 100644 --- a/org.eclipse.jgit.test/tests.bzl +++ b/org.eclipse.jgit.test/tests.bzl @@ -41,7 +41,13 @@ def tests(tests): additional_deps = [ "//lib:jsch", ] - + if src.endswith("JSchSshTest.java"): + additional_deps = [ + "//lib:jsch", + "//lib:jzlib", + "//lib:sshd-core", + "//lib:sshd-sftp", + ] heap_size = "-Xmx256m" if src.endswith("HugeCommitMessageTest.java"): heap_size = "-Xmx512m" diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/transport/id_dsa_test b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/transport/id_dsa_test new file mode 100644 index 0000000000..cc39e8b870 --- /dev/null +++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/transport/id_dsa_test @@ -0,0 +1,15 @@ +-----BEGIN DSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: AES-128-CBC,D7B8FC3F4E304A2A22754B068767081F + +IewkLt6JyqtPccAsnfeLv7IMlLvgm7tqQSYK1/CLhmDE0aZXViD8sqxLA6dVjmkp +BVyk7EBpp43PnVQYsDcMPnyM8H83vNRDtIQ6fxM1PJafiP7Rbn1k1fDh7DwA48PU +FnT6zZ9aYEKYMto0WIdQ86j/uY+LtYygQDDoZ2ohn2NlpykeyrSp0bDRIoW6sdc5 ++LlfDtq2usv3fcxMAJpO/SSN78LvBlyOK4n/JAVSkPawsW1WsIrXA52mk0iUhjYc +aYOCuL+wA7OmHAOpfS5HUXZ4i/7qONnLBkEqeIOcgTmShh1c4oWw9TjWK1AzdSDU +G2nkRJ/8zK/jdm5wcmrjrzuREM1VbCiXHlVoHYI0W1Z9etOgz1cj4KLz/bB8Nf+8 +shCez1Aw5ec33BzwysfwymfAKeXjYaxdKcur3j+UdXAlYRD28BRnWmTL5Jx82eUu +NIh0U9pHkn+PjdzmjSPEUP7wzDjQQacaQTkBRf5gPyOYfv/+Mnq6YyflKaPYmkEr +eztO22VZlpyp/hj2LzBav9wi0++teInNQGU+GxHedsWm4+YpffMhz1bz5ZUQ670A +0WJJH3k/KnxbCY3usj4eJr+CsX+LNZhm+rKyjRDmRwA= +-----END DSA PRIVATE KEY----- diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/transport/id_dsa_test.pub b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/transport/id_dsa_test.pub new file mode 100644 index 0000000000..0528a928c9 --- /dev/null +++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/transport/id_dsa_test.pub @@ -0,0 +1 @@ +ssh-dss AAAAB3NzaC1kc3MAAACBAIsXi0EUiI6GmhHqrwwjvO2wdujW46+uXM/SG2GVI3KxCSf95B2XgXBsgiKH0sy3guyqjDcP4Ph5Mctg1IxqmqugN6xf9YB6lf09bRdIbumVGU6nXW7bZDHdk9nmvWy56vurofwvhoRnQBUJ3L4n7dxxvXhIyRPOxptayOS2ZcnRAAAAFQDsgGxVxcBBM9y0Rm3kNz/R64CYEQAAAIEAgCbyCJNZb66KQBMO7B+NPxx0caSKjZ+3TpWL6pLJGTAu1pztd1wpElECNCEBhTX9p1HEypTIjOUFU2gjgaBLUcWE0JK+/4vJjjvaENvrQardH0EeRfrazhpRY+X6ytUTk0YPDuQn+ZqBhXxAoD8BA+TJMvk7oMpMUTyr6LGBuj4AAACAeXCfOrKY6wHuMkHHpa9Ix95T+7h5ZrSosrV1WO5g9X04LNiPFRXvGyMWYF17VaGqVWID5NbbGP4PqwSw0rjmw7c/xxV2DYNfJ5NFWsDHxhI6RP9AaGTKcdIEykWEkGgJDiVF/DJgjvapGCW4Lo5UB1JJRXEM4YmTiEbyUyahKqw= thomas@Arcturus diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/JSchSshTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/JSchSshTest.java new file mode 100644 index 0000000000..7b0344070e --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/JSchSshTest.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch> + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - 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. + * + * - Neither the name of the Eclipse Foundation, Inc. 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 COPYRIGHT OWNER 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. + */ +package org.eclipse.jgit.transport; + +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.util.Arrays; + +import org.eclipse.jgit.errors.TransportException; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.transport.OpenSshConfig.Host; +import org.eclipse.jgit.util.FS; + +import com.jcraft.jsch.JSch; +import com.jcraft.jsch.JSchException; +import com.jcraft.jsch.Session; + +public class JSchSshTest extends SshTestBase { + + private class TestSshSessionFactory extends JschConfigSessionFactory { + + @Override + protected void configure(Host hc, Session session) { + // Nothing + } + + @Override + public synchronized RemoteSession getSession(URIish uri, + CredentialsProvider credentialsProvider, FS fs, int tms) + throws TransportException { + return super.getSession(uri, credentialsProvider, fs, tms); + } + + @Override + protected JSch createDefaultJSch(FS fs) throws JSchException { + JSch defaultJSch = super.createDefaultJSch(fs); + if (knownHosts.exists()) { + defaultJSch.setKnownHosts(knownHosts.getAbsolutePath()); + } + return defaultJSch; + } + } + + @Override + protected SshSessionFactory createSessionFactory() { + return new TestSshSessionFactory(); + } + + @Override + protected void installConfig(String... config) { + SshSessionFactory factory = getSessionFactory(); + assertTrue(factory instanceof JschConfigSessionFactory); + JschConfigSessionFactory j = (JschConfigSessionFactory) factory; + try { + j.setConfig(createConfig(config)); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + private OpenSshConfig createConfig(String... content) throws IOException { + File configFile = new File(sshDir, Constants.CONFIG); + if (content != null) { + Files.write(configFile.toPath(), Arrays.asList(content)); + } + return new OpenSshConfig(getTemporaryDirectory(), configFile); + } + +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/OpenSshConfigTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/OpenSshConfigTest.java index 19fcbfd7a2..0760585761 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/OpenSshConfigTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/OpenSshConfigTest.java @@ -67,6 +67,7 @@ import org.junit.Before; import org.junit.Test; import com.jcraft.jsch.ConfigRepository; +import com.jcraft.jsch.ConfigRepository.Config; public class OpenSshConfigTest extends RepositoryTestCase { private File home; @@ -164,6 +165,20 @@ public class OpenSshConfigTest extends RepositoryTestCase { } @Test + public void testCaseInsensitiveKeyLookup() throws Exception { + config("Host orcz\n" + "Port 29418\n" + + "\tHostName repo.or.cz\nStrictHostKeyChecking yes\n"); + final Host h = osc.lookup("orcz"); + Config c = h.getConfig(); + String exactCase = c.getValue("StrictHostKeyChecking"); + assertEquals("yes", exactCase); + assertEquals(exactCase, c.getValue("stricthostkeychecking")); + assertEquals(exactCase, c.getValue("STRICTHOSTKEYCHECKING")); + assertEquals(exactCase, c.getValue("sTrIcThostKEYcheckING")); + assertNull(c.getValue("sTrIcThostKEYcheckIN")); + } + + @Test public void testAlias_DoesNotMatch() throws Exception { config("Host orcz\n" + "Port 29418\n" + "\tHostName repo.or.cz\n"); final Host h = osc.lookup("repo.or.cz"); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/SshTestBase.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/SshTestBase.java new file mode 100644 index 0000000000..0d625f319d --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/SshTestBase.java @@ -0,0 +1,466 @@ +/* + * Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch> + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - 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. + * + * - Neither the name of the Eclipse Foundation, Inc. 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 COPYRIGHT OWNER 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. + */ +package org.eclipse.jgit.transport; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.util.Arrays; +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.eclipse.jgit.api.CloneCommand; +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.api.errors.TransportException; +import org.eclipse.jgit.errors.UnsupportedCredentialItem; +import org.eclipse.jgit.junit.RepositoryTestCase; +import org.eclipse.jgit.junit.ssh.SshTestGitServer; +import org.eclipse.jgit.util.FS; +import org.junit.After; +import org.junit.Test; + +import com.jcraft.jsch.JSch; +import com.jcraft.jsch.KeyPair; + +public abstract class SshTestBase extends RepositoryTestCase { + + protected static final String TEST_USER = "testuser"; + + protected File sshDir; + + protected File privateKey1; + + protected File privateKey2; + + private SshTestGitServer server; + + private SshSessionFactory factory; + + protected int testPort; + + protected File knownHosts; + + private File homeDir; + + @Override + public void setUp() throws Exception { + super.setUp(); + writeTrashFile("file.txt", "something"); + try (Git git = new Git(db)) { + git.add().addFilepattern("file.txt").call(); + git.commit().setMessage("Initial commit").call(); + } + mockSystemReader.setProperty("user.home", + getTemporaryDirectory().getAbsolutePath()); + mockSystemReader.setProperty("HOME", + getTemporaryDirectory().getAbsolutePath()); + homeDir = FS.DETECTED.userHome(); + FS.DETECTED.setUserHome(getTemporaryDirectory().getAbsoluteFile()); + sshDir = new File(getTemporaryDirectory(), ".ssh"); + assertTrue(sshDir.mkdir()); + File serverDir = new File(getTemporaryDirectory(), "srv"); + assertTrue(serverDir.mkdir()); + // Create two key pairs. Let's not call them "id_rsa". + privateKey1 = new File(sshDir, "first_key"); + privateKey2 = new File(sshDir, "second_key"); + createKeyPair(privateKey1); + createKeyPair(privateKey2); + ByteArrayOutputStream publicHostKey = new ByteArrayOutputStream(); + // Start a server with our test user and the first key. + server = new SshTestGitServer(TEST_USER, privateKey1.toPath(), db, + createHostKey(publicHostKey)); + testPort = server.start(); + assertTrue(testPort > 0); + knownHosts = new File(sshDir, "known_hosts"); + Files.write(knownHosts.toPath(), Collections.singleton("[localhost]:" + + testPort + ' ' + + publicHostKey.toString(StandardCharsets.US_ASCII.name()))); + factory = createSessionFactory(); + SshSessionFactory.setInstance(factory); + } + + private static void createKeyPair(File privateKeyFile) throws Exception { + // Found no way to do this with MINA sshd except rolling it all + // ourselves... + JSch jsch = new JSch(); + KeyPair pair = KeyPair.genKeyPair(jsch, KeyPair.RSA, 2048); + try (OutputStream out = new FileOutputStream(privateKeyFile)) { + pair.writePrivateKey(out); + } + File publicKeyFile = new File(privateKeyFile.getParentFile(), + privateKeyFile.getName() + ".pub"); + try (OutputStream out = new FileOutputStream(publicKeyFile)) { + pair.writePublicKey(out, TEST_USER); + } + } + + private static byte[] createHostKey(OutputStream publicKey) + throws Exception { + JSch jsch = new JSch(); + KeyPair pair = KeyPair.genKeyPair(jsch, KeyPair.RSA, 2048); + pair.writePublicKey(publicKey, ""); + try (ByteArrayOutputStream out = new ByteArrayOutputStream()) { + pair.writePrivateKey(out); + out.flush(); + return out.toByteArray(); + } + } + + @After + public void shutdownServer() throws Exception { + if (server != null) { + server.stop(); + server = null; + } + FS.DETECTED.setUserHome(homeDir); + SshSessionFactory.setInstance(null); + factory = null; + } + + protected abstract SshSessionFactory createSessionFactory(); + + protected SshSessionFactory getSessionFactory() { + return factory; + } + + protected abstract void installConfig(String... config); + + @Test(expected = TransportException.class) + public void testSshCloneWithoutConfig() throws Exception { + cloneWith("ssh://" + TEST_USER + "@localhost:" + testPort + + "/doesntmatter", null); + } + + @Test + public void testSshCloneWithGlobalIdentity() throws Exception { + cloneWith( + "ssh://" + TEST_USER + "@localhost:" + testPort + + "/doesntmatter", + null, + "IdentityFile " + privateKey1.getAbsolutePath()); + } + + @Test + public void testSshCloneWithDefaultIdentity() throws Exception { + File idRsa = new File(privateKey1.getParentFile(), "id_rsa"); + Files.copy(privateKey1.toPath(), idRsa.toPath()); + // We expect the session factory to pick up these keys... + cloneWith("ssh://" + TEST_USER + "@localhost:" + testPort + + "/doesntmatter", null); + } + + @Test + public void testSshCloneWithConfig() throws Exception { + cloneWith("ssh://localhost/doesntmatter", null, // + "Host localhost", // + "HostName localhost", // + "Port " + testPort, // + "User " + TEST_USER, // + "IdentityFile " + privateKey1.getAbsolutePath()); + } + + @Test + public void testSshCloneWithConfigEncryptedUnusedKey() throws Exception { + // Copy the encrypted test key from the bundle. + File encryptedKey = new File(sshDir, "id_dsa"); + try (InputStream in = SshTestBase.class + .getResourceAsStream("id_dsa_test")) { + Files.copy(in, encryptedKey.toPath()); + } + TestCredentialsProvider provider = new TestCredentialsProvider( + "testpass"); + cloneWith("ssh://localhost/doesntmatter", provider, // + "Host localhost", // + "HostName localhost", // + "Port " + testPort, // + "User " + TEST_USER, // + "IdentityFile " + privateKey1.getAbsolutePath()); + assertEquals("CredentialsProvider should not have been called", 0, + provider.getLog().size()); + } + + @Test(expected = TransportException.class) + public void testSshCloneWithoutKnownHosts() throws Exception { + assertTrue("Could not delete known_hosts", knownHosts.delete()); + cloneWith("ssh://localhost/doesntmatter", null, // + "Host localhost", // + "HostName localhost", // + "Port " + testPort, // + "User " + TEST_USER, // + "IdentityFile " + privateKey1.getAbsolutePath()); + } + + @Test + public void testSshCloneWithoutKnownHostsWithProvider() throws Exception { + File copiedHosts = new File(knownHosts.getParentFile(), + "copiedKnownHosts"); + assertTrue("Failed to rename known_hosts", + knownHosts.renameTo(copiedHosts)); + TestCredentialsProvider provider = new TestCredentialsProvider(); + cloneWith("ssh://localhost/doesntmatter", provider, // + "Host localhost", // + "HostName localhost", // + "Port " + testPort, // + "User " + TEST_USER, // + "IdentityFile " + privateKey1.getAbsolutePath()); + Map<URIish, List<CredentialItem>> messages = provider.getLog(); + assertFalse("Expected user iteraction", messages.isEmpty()); + } + + @Test + public void testSftpCloneWithConfig() throws Exception { + cloneWith("sftp://localhost/.git", null, // + "Host localhost", // + "HostName localhost", // + "Port " + testPort, // + "User " + TEST_USER, // + "IdentityFile " + privateKey1.getAbsolutePath()); + } + + @Test(expected = TransportException.class) + public void testSshCloneWithConfigWrongKey() throws Exception { + cloneWith("ssh://localhost/doesntmatter", null, // + "Host localhost", // + "HostName localhost", // + "Port " + testPort, // + "User " + TEST_USER, // + "IdentityFile " + privateKey2.getAbsolutePath()); + } + + @Test + public void testSshCloneWithWrongUserNameInConfig() throws Exception { + // Bug 526778 + cloneWith( + "ssh://" + TEST_USER + "@localhost:" + testPort + + "/doesntmatter", + null, // + "Host localhost", // + "HostName localhost", // + "User sombody_else", // + "IdentityFile " + privateKey1.getAbsolutePath()); + } + + @Test + public void testSshCloneWithWrongPortInConfig() throws Exception { + // Bug 526778 + cloneWith( + "ssh://" + TEST_USER + "@localhost:" + testPort + + "/doesntmatter", + null, // + "Host localhost", // + "HostName localhost", // + "Port 22", // + "User " + TEST_USER, // + "IdentityFile " + privateKey1.getAbsolutePath()); + } + + @Test + public void testSshCloneWithAliasInConfig() throws Exception { + // Bug 531118 + cloneWith("ssh://git/doesntmatter", null, // + "Host git", // + "HostName localhost", // + "Port " + testPort, // + "User " + TEST_USER, // + "IdentityFile " + privateKey1.getAbsolutePath(), "", // + "Host localhost", // + "HostName localhost", // + "Port 22", // + "User someone_else", // + "IdentityFile " + privateKey2.getAbsolutePath()); + } + + @Test + public void testSshCloneWithUnknownCiphersInConfig() throws Exception { + // Bug 535672 + cloneWith("ssh://git/doesntmatter", null, // + "Host git", // + "HostName localhost", // + "Port " + testPort, // + "User " + TEST_USER, // + "IdentityFile " + privateKey1.getAbsolutePath(), // + "Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr"); + } + + @Test + public void testSshCloneWithUnknownHostKeyAlgorithmsInConfig() + throws Exception { + // Bug 535672 + cloneWith("ssh://git/doesntmatter", null, // + "Host git", // + "HostName localhost", // + "Port " + testPort, // + "User " + TEST_USER, // + "IdentityFile " + privateKey1.getAbsolutePath(), // + "HostKeyAlgorithms foobar,ssh-rsa,ssh-dss"); + } + + @Test + public void testSshCloneWithUnknownKexAlgorithmsInConfig() + throws Exception { + // Bug 535672 + cloneWith("ssh://git/doesntmatter", null, // + "Host git", // + "HostName localhost", // + "Port " + testPort, // + "User " + TEST_USER, // + "IdentityFile " + privateKey1.getAbsolutePath(), // + "KexAlgorithms foobar,diffie-hellman-group14-sha1,ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521"); + } + + @Test + public void testSshCloneWithMinimalHostKeyAlgorithmsInConfig() + throws Exception { + // Bug 537790 + cloneWith("ssh://git/doesntmatter", null, // + "Host git", // + "HostName localhost", // + "Port " + testPort, // + "User " + TEST_USER, // + "IdentityFile " + privateKey1.getAbsolutePath(), // + "HostKeyAlgorithms ssh-rsa,ssh-dss"); + } + + private void cloneWith(String uri, CredentialsProvider provider, + String... config) throws Exception { + installConfig(config); + File cloned = new File(getTemporaryDirectory(), "cloned"); + CloneCommand clone = Git.cloneRepository().setCloneAllBranches(true) + .setDirectory(cloned).setURI(uri); + if (provider != null) { + clone.setCredentialsProvider(provider); + } + try (Git git = clone.call()) { + assertNotNull(git.getRepository().resolve("master")); + assertNotEquals(db.getWorkTree(), + git.getRepository().getWorkTree()); + checkFile(new File(git.getRepository().getWorkTree(), "file.txt"), + "something"); + } + } + + private class TestCredentialsProvider extends CredentialsProvider { + + private final List<String> stringStore; + + private final Iterator<String> strings; + + public TestCredentialsProvider(String... strings) { + if (strings == null || strings.length == 0) { + stringStore = Collections.emptyList(); + } else { + stringStore = Arrays.asList(strings); + } + this.strings = stringStore.iterator(); + } + + @Override + public boolean isInteractive() { + return true; + } + + @Override + public boolean supports(CredentialItem... items) { + return true; + } + + @Override + public boolean get(URIish uri, CredentialItem... items) + throws UnsupportedCredentialItem { + System.out.println("URI: " + uri); + for (CredentialItem item : items) { + System.out.println(item.getClass().getSimpleName() + ' ' + + item.getPromptText()); + } + logItems(uri, items); + for (CredentialItem item : items) { + if (item instanceof CredentialItem.InformationalMessage) { + continue; + } + if (item instanceof CredentialItem.YesNoType) { + ((CredentialItem.YesNoType) item).setValue(true); + } else if (item instanceof CredentialItem.CharArrayType) { + if (strings.hasNext()) { + ((CredentialItem.CharArrayType) item) + .setValue(strings.next().toCharArray()); + } else { + return false; + } + } else if (item instanceof CredentialItem.StringType) { + if (strings.hasNext()) { + ((CredentialItem.StringType) item) + .setValue(strings.next()); + } else { + return false; + } + } else { + return false; + } + } + return true; + } + + private Map<URIish, List<CredentialItem>> log = new LinkedHashMap<>(); + + private void logItems(URIish uri, CredentialItem... items) { + log.put(uri, Arrays.asList(items)); + } + + public Map<URIish, List<CredentialItem>> getLog() { + return log; + } + } +} |