From 5d58a05a9843ec90d06ca42061ff638418f73687 Mon Sep 17 00:00:00 2001
From: David Ostrovsky <david@ostrovsky.org>
Date: Thu, 10 Apr 2014 18:58:08 -0400
Subject: [PATCH] Add SSH daemon test

---
 src/test/java/com/gitblit/tests/GitBlitSuite.java                          |   14 ++
 src/test/java/com/gitblit/tests/SshUtils.java                              |   74 ++++++++++++++
 src/main/java/com/gitblit/transport/ssh/SshDaemon.java                     |   49 +++++++++
 src/test/java/com/gitblit/tests/SshDaemonTest.java                         |   90 ++++++++++++++++++
 src/main/java/com/gitblit/transport/ssh/commands/DispatchCommand.java      |    2 
 src/main/distrib/data/gitblit.properties                                   |    5 +
 src/test/java/com/gitblit/tests/BogusPublicKeyAuthenticator.java           |   39 +++++++
 src/test/config/test-gitblit.properties                                    |    2 
 src/main/java/com/gitblit/transport/ssh/CachingPublicKeyAuthenticator.java |    2 
 9 files changed, 272 insertions(+), 5 deletions(-)

diff --git a/src/main/distrib/data/gitblit.properties b/src/main/distrib/data/gitblit.properties
index 64a52f5..52bb252 100644
--- a/src/main/distrib/data/gitblit.properties
+++ b/src/main/distrib/data/gitblit.properties
@@ -129,6 +129,11 @@
 # SINCE 1.5.0
 git.sshBackend = NIO2
 
+# SSH public key authenticator
+#
+# SINCE 1.5.0
+git.sshPublicKeyAuthenticator = com.gitblit.transport.ssh.CachingPublicKeyAuthenticator
+
 # Allow push/pull over http/https with JGit servlet.
 # If you do NOT want to allow Git clients to clone/push to Gitblit set this
 # to false.  You might want to do this if you are only using ssh:// or git://.
diff --git a/src/main/java/com/gitblit/transport/ssh/CachingPublicKeyAuthenticator.java b/src/main/java/com/gitblit/transport/ssh/CachingPublicKeyAuthenticator.java
index ee1de59..7d6066c 100644
--- a/src/main/java/com/gitblit/transport/ssh/CachingPublicKeyAuthenticator.java
+++ b/src/main/java/com/gitblit/transport/ssh/CachingPublicKeyAuthenticator.java
@@ -73,7 +73,7 @@
 		return result;
 	}
 
-	private boolean doAuthenticate(String username, PublicKey suppliedKey,
+	protected boolean doAuthenticate(String username, PublicKey suppliedKey,
 			ServerSession session) {
 		SshDaemonClient client = session.getAttribute(SshDaemonClient.KEY);
 		Preconditions.checkState(client.getUser() == null);
diff --git a/src/main/java/com/gitblit/transport/ssh/SshDaemon.java b/src/main/java/com/gitblit/transport/ssh/SshDaemon.java
index c954b34..40a310e 100644
--- a/src/main/java/com/gitblit/transport/ssh/SshDaemon.java
+++ b/src/main/java/com/gitblit/transport/ssh/SshDaemon.java
@@ -34,6 +34,7 @@
 
 import com.gitblit.IStoredSettings;
 import com.gitblit.Keys;
+import com.gitblit.manager.IAuthenticationManager;
 import com.gitblit.manager.IGitblit;
 import com.gitblit.utils.IdGenerator;
 import com.gitblit.utils.StringUtils;
@@ -104,8 +105,8 @@
 			addr = new InetSocketAddress(bindInterface, port);
 		}
 
-		CachingPublicKeyAuthenticator keyAuthenticator = 
-				new CachingPublicKeyAuthenticator(keyManager, gitblit);
+		CachingPublicKeyAuthenticator keyAuthenticator =
+				getPublicKeyAuthenticator(keyManager, gitblit);
 
 		sshd = SshServer.setUpDefaultServer();
 		sshd.setPort(addr.getPort());
@@ -120,6 +121,27 @@
 		sshd.setCommandFactory(new SshCommandFactory(gitblit, keyAuthenticator, idGenerator));
 
 		run = new AtomicBoolean(false);
+	}
+
+	private CachingPublicKeyAuthenticator getPublicKeyAuthenticator(
+			IKeyManager keyManager, IGitblit gitblit) {
+		IStoredSettings settings = gitblit.getSettings();
+		String clazz = settings.getString(Keys.git.sshPublicKeyAuthenticator,
+				CachingPublicKeyAuthenticator.class.getName());
+		if (StringUtils.isEmpty(clazz)) {
+			clazz = CachingPublicKeyAuthenticator.class.getName();
+		}
+		try {
+			Class<CachingPublicKeyAuthenticator> authClass =
+					(Class<CachingPublicKeyAuthenticator>) Class.forName(clazz);
+			return authClass.getConstructor(
+					new Class[] { IKeyManager.class,
+							IAuthenticationManager.class }).newInstance(
+					keyManager, gitblit);
+		} catch (Exception e) {
+			log.error("failed to create ssh auth manager " + clazz, e);
+		}
+		return null;
 	}
 
 	public String formatUrl(String gituser, String servername, String repository) {
@@ -200,6 +222,29 @@
 		return keyManager;
 	}
 
+	@SuppressWarnings("unchecked")
+	protected IKeyManager getKeyAuthenticator() {
+		IKeyManager keyManager = null;
+		IStoredSettings settings = gitblit.getSettings();
+		String clazz = settings.getString(Keys.git.sshKeysManager, FileKeyManager.class.getName());
+		if (StringUtils.isEmpty(clazz)) {
+			clazz = FileKeyManager.class.getName();
+		}
+		try {
+			Class<? extends IKeyManager> managerClass = (Class<? extends IKeyManager>) Class.forName(clazz);
+			keyManager = injector.get(managerClass).start();
+			if (keyManager.isReady()) {
+				log.info("{} is ready.", keyManager);
+			} else {
+				log.warn("{} is disabled.", keyManager);
+			}
+		} catch (Exception e) {
+			log.error("failed to create ssh key manager " + clazz, e);
+			keyManager = injector.get(NullKeyManager.class).start();
+		}
+		return keyManager;
+	}
+
 	/**
 	 * A nested Dagger graph is used for constructor dependency injection of
 	 * complex classes.
diff --git a/src/main/java/com/gitblit/transport/ssh/commands/DispatchCommand.java b/src/main/java/com/gitblit/transport/ssh/commands/DispatchCommand.java
index 3c041af..8e13be0 100644
--- a/src/main/java/com/gitblit/transport/ssh/commands/DispatchCommand.java
+++ b/src/main/java/com/gitblit/transport/ssh/commands/DispatchCommand.java
@@ -79,7 +79,7 @@
 					CommandMetaData.class.getName()));
 		}
 		CommandMetaData meta = cmd.getAnnotation(CommandMetaData.class);
-		if (meta.admin() && user.canAdmin()) {
+		if (meta.admin() && user != null && user.canAdmin()) {
 			log.debug(MessageFormat.format("excluding admin command {0} for {1}", meta.name(), user.username));
 			return;
 		}
diff --git a/src/test/config/test-gitblit.properties b/src/test/config/test-gitblit.properties
index e636469..7d8e9a7 100644
--- a/src/test/config/test-gitblit.properties
+++ b/src/test/config/test-gitblit.properties
@@ -7,6 +7,8 @@
 git.searchRepositoriesSubfolders = true
 git.enableGitServlet = true
 git.daemonPort = 8300
+git.sshPort = 29418
+git.sshPublicKeyAuthenticator = com.gitblit.tests.BogusPublicKeyAuthenticator
 groovy.scriptsFolder = src/main/distrib/data/groovy
 groovy.preReceiveScripts = blockpush
 groovy.postReceiveScripts = sendmail
diff --git a/src/test/java/com/gitblit/tests/BogusPublicKeyAuthenticator.java b/src/test/java/com/gitblit/tests/BogusPublicKeyAuthenticator.java
new file mode 100644
index 0000000..80be1a0
--- /dev/null
+++ b/src/test/java/com/gitblit/tests/BogusPublicKeyAuthenticator.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2014 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit.tests;
+
+import java.security.PublicKey;
+
+import org.apache.sshd.server.session.ServerSession;
+
+import com.gitblit.manager.IAuthenticationManager;
+import com.gitblit.transport.ssh.CachingPublicKeyAuthenticator;
+import com.gitblit.transport.ssh.IKeyManager;
+
+public class BogusPublicKeyAuthenticator extends CachingPublicKeyAuthenticator {
+
+	public BogusPublicKeyAuthenticator(IKeyManager keyManager,
+			IAuthenticationManager authManager) {
+		super(keyManager, authManager);
+	}
+
+	@Override
+	protected boolean doAuthenticate(String username, PublicKey suppliedKey,
+			ServerSession session) {
+		// TODO(davido): put authenticated user in session
+		return true;
+	}
+}
diff --git a/src/test/java/com/gitblit/tests/GitBlitSuite.java b/src/test/java/com/gitblit/tests/GitBlitSuite.java
index c015c84..17d609e 100644
--- a/src/test/java/com/gitblit/tests/GitBlitSuite.java
+++ b/src/test/java/com/gitblit/tests/GitBlitSuite.java
@@ -61,7 +61,7 @@
 		MarkdownUtilsTest.class, JGitUtilsTest.class, SyndicationUtilsTest.class,
 		DiffUtilsTest.class, MetricUtilsTest.class, X509UtilsTest.class,
 		GitBlitTest.class, FederationTests.class, RpcTests.class, GitServletTest.class, GitDaemonTest.class,
-		GroovyScriptTest.class, LuceneExecutorTest.class, RepositoryModelTest.class,
+		GroovyScriptTest.class, LuceneExecutorTest.class, RepositoryModelTest.class, SshDaemonTest.class,
 		FanoutServiceTest.class, Issue0259Test.class, Issue0271Test.class, HtpasswdAuthenticationTest.class,
 		ModelUtilsTest.class, JnaUtilsTest.class, LdapSyncServiceTest.class, FileTicketServiceTest.class,
 		BranchTicketServiceTest.class, RedisTicketServiceTest.class, AuthenticationManagerTest.class })
@@ -78,6 +78,16 @@
 	static int port = 8280;
 	static int gitPort = 8300;
 	static int shutdownPort = 8281;
+	static int sshPort = 29418;
+
+// Overriding of keys doesn't seem to work
+//	static {
+//		try {
+//			sshPort = SshUtils.getFreePort();
+//		} catch (Exception e) {
+//			e.printStackTrace();
+//		}
+//	}
 
 	public static String url = "http://localhost:" + port;
 	public static String gitServletUrl = "http://localhost:" + port + "/git";
@@ -140,6 +150,8 @@
 						"\"" + GitBlitSuite.REPOSITORIES.getAbsolutePath() + "\"", "--userService",
 						GitBlitSuite.USERSCONF.getAbsolutePath(), "--settings", GitBlitSuite.SETTINGS.getAbsolutePath(),
 						"--baseFolder", "data");
+				// doesn't work
+				//, "--sshPort", "" + sshPort);
 			}
 		});
 
diff --git a/src/test/java/com/gitblit/tests/SshDaemonTest.java b/src/test/java/com/gitblit/tests/SshDaemonTest.java
new file mode 100644
index 0000000..5294f69
--- /dev/null
+++ b/src/test/java/com/gitblit/tests/SshDaemonTest.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2014 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit.tests;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.security.KeyPair;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.apache.sshd.ClientChannel;
+import org.apache.sshd.ClientSession;
+import org.apache.sshd.SshClient;
+import org.apache.sshd.common.KeyPairProvider;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import com.gitblit.Constants;
+
+public class SshDaemonTest extends GitblitUnitTest {
+
+	private static final AtomicBoolean started = new AtomicBoolean(false);
+	private static KeyPair pair;
+
+	@BeforeClass
+	public static void startGitblit() throws Exception {
+		started.set(GitBlitSuite.startGitblit());
+		pair = SshUtils.createTestHostKeyProvider().loadKey(KeyPairProvider.SSH_RSA);
+	}
+
+	@AfterClass
+	public static void stopGitblit() throws Exception {
+		if (started.get()) {
+			GitBlitSuite.stopGitblit();
+		}
+	}
+
+	@Test
+	public void testPublicKeyAuthentication() throws Exception {
+		SshClient client = SshClient.setUpDefaultClient();
+        client.start();
+        ClientSession session = client.connect("localhost", GitBlitSuite.sshPort).await().getSession();
+        pair.getPublic().getEncoded();
+        assertTrue(session.authPublicKey("admin", pair).await().isSuccess());
+	}
+
+	@Test
+	public void testVersionCommand() throws Exception {
+		SshClient client = SshClient.setUpDefaultClient();
+        client.start();
+        ClientSession session = client.connect("localhost", GitBlitSuite.sshPort).await().getSession();
+        pair.getPublic().getEncoded();
+        assertTrue(session.authPublicKey("admin", pair).await().isSuccess());
+
+        ClientChannel channel = session.createChannel(ClientChannel.CHANNEL_EXEC, "gitblit version");
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        Writer w = new OutputStreamWriter(baos);
+        w.close();
+        channel.setIn(new ByteArrayInputStream(baos.toByteArray()));
+
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        ByteArrayOutputStream err = new ByteArrayOutputStream();
+        channel.setOut(out);
+        channel.setErr(err);
+        channel.open();
+
+        channel.waitFor(ClientChannel.CLOSED, 0);
+
+        String result = out.toString().trim();
+        channel.close(false);
+        client.stop();
+
+        assertEquals(Constants.getGitBlitVersion(), result);
+     }
+}
diff --git a/src/test/java/com/gitblit/tests/SshUtils.java b/src/test/java/com/gitblit/tests/SshUtils.java
new file mode 100644
index 0000000..9760f75
--- /dev/null
+++ b/src/test/java/com/gitblit/tests/SshUtils.java
@@ -0,0 +1,74 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package com.gitblit.tests;
+
+import java.io.File;
+import java.net.ServerSocket;
+import java.net.URISyntaxException;
+import java.net.URL;
+
+import org.apache.sshd.common.KeyPairProvider;
+import org.apache.sshd.common.keyprovider.FileKeyPairProvider;
+import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
+
+public class SshUtils {
+
+    public static KeyPairProvider createTestHostKeyProvider() {
+        return new SimpleGeneratorHostKeyProvider("target/hostkey.rsa", "RSA");
+    }
+
+    public static FileKeyPairProvider createTestKeyPairProvider(String resource) {
+        return new FileKeyPairProvider(new String[] { getFile(resource) });
+    }
+
+    public static int getFreePort() throws Exception {
+        ServerSocket s = new ServerSocket(0);
+        try {
+            return s.getLocalPort();
+        } finally {
+            s.close();
+        }
+    }
+
+    private static String getFile(String resource) {
+        URL url = SshUtils.class.getClassLoader().getResource(resource);
+        File f;
+        try {
+            f = new File(url.toURI());
+        } catch(URISyntaxException e) {
+            f = new File(url.getPath());
+        }
+        return f.toString();
+    }
+
+    public static void deleteRecursive(File file) {
+        if (file != null) {
+            if (file.isDirectory()) {
+                File[] children = file.listFiles();
+                if (children != null) {
+                    for (File child : children) {
+                        deleteRecursive(child);
+                    }
+                }
+            }
+            file.delete();
+        }
+    }
+
+}

--
Gitblit v1.9.1