From: James Moger Date: Tue, 8 Apr 2014 02:57:47 +0000 (-0400) Subject: Unit tests for ssh daemon and keys dispatcher X-Git-Tag: v1.5.0~68^2~9 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=521cb6022a9ee30bf3115a8dcb991aa5c7e420e3;p=gitblit.git Unit tests for ssh daemon and keys dispatcher --- diff --git a/src/main/java/com/gitblit/transport/ssh/CachingPublicKeyAuthenticator.java b/src/main/java/com/gitblit/transport/ssh/CachingPublicKeyAuthenticator.java index 48e5aa28..4ce26d0f 100644 --- a/src/main/java/com/gitblit/transport/ssh/CachingPublicKeyAuthenticator.java +++ b/src/main/java/com/gitblit/transport/ssh/CachingPublicKeyAuthenticator.java @@ -38,8 +38,7 @@ import com.google.common.base.Preconditions; * @author Eric Myrhe * */ -public class CachingPublicKeyAuthenticator implements PublickeyAuthenticator, - SessionListener { +public class CachingPublicKeyAuthenticator implements PublickeyAuthenticator, SessionListener { protected final Logger log = LoggerFactory.getLogger(getClass()); @@ -47,18 +46,15 @@ public class CachingPublicKeyAuthenticator implements PublickeyAuthenticator, protected final IAuthenticationManager authManager; - private final Map> cache = - new ConcurrentHashMap>(); + private final Map> cache = new ConcurrentHashMap>(); - public CachingPublicKeyAuthenticator(IPublicKeyManager keyManager, - IAuthenticationManager authManager) { + public CachingPublicKeyAuthenticator(IPublicKeyManager keyManager, IAuthenticationManager authManager) { this.keyManager = keyManager; this.authManager = authManager; } @Override - public boolean authenticate(String username, PublicKey key, - ServerSession session) { + public boolean authenticate(String username, PublicKey key, ServerSession session) { Map map = cache.get(session); if (map == null) { map = new HashMap(); @@ -73,19 +69,21 @@ public class CachingPublicKeyAuthenticator implements PublickeyAuthenticator, return result; } - private boolean doAuthenticate(String username, PublicKey suppliedKey, - ServerSession session) { + private boolean doAuthenticate(String username, PublicKey suppliedKey, ServerSession session) { SshDaemonClient client = session.getAttribute(SshDaemonClient.KEY); Preconditions.checkState(client.getUser() == null); username = username.toLowerCase(Locale.US); List keys = keyManager.getKeys(username); - if (keys == null || keys.isEmpty()) { - log.info("{} has not added any public keys for ssh authentication", - username); + if (keys.isEmpty()) { + log.info("{} has not added any public keys for ssh authentication", username); return false; } + SshKey pk = new SshKey(suppliedKey); + log.debug("auth supplied {}", pk.getFingerprint()); + for (SshKey key : keys) { + log.debug("auth compare to {}", key.getFingerprint()); if (key.equals(suppliedKey)) { UserModel user = authManager.authenticate(username, key); if (user != null) { @@ -96,8 +94,7 @@ public class CachingPublicKeyAuthenticator implements PublickeyAuthenticator, } } - log.warn("could not authenticate {} for SSH using the supplied public key", - username); + log.warn("could not authenticate {} for SSH using the supplied public key", username); return false; } diff --git a/src/main/java/com/gitblit/transport/ssh/FileKeyManager.java b/src/main/java/com/gitblit/transport/ssh/FileKeyManager.java index ae4588ae..a063dc7d 100644 --- a/src/main/java/com/gitblit/transport/ssh/FileKeyManager.java +++ b/src/main/java/com/gitblit/transport/ssh/FileKeyManager.java @@ -90,7 +90,7 @@ public class FileKeyManager extends IPublicKeyManager { @Override protected List getKeysImpl(String username) { try { - log.info("loading keystore for {}", username); + log.info("loading ssh keystore for {}", username); File keystore = getKeystore(username); if (!keystore.exists()) { return null; @@ -128,7 +128,7 @@ public class FileKeyManager extends IPublicKeyManager { return list; } } catch (IOException e) { - throw new RuntimeException("Canot read ssh keys", e); + throw new RuntimeException("Cannot read ssh keys", e); } return null; } diff --git a/src/main/java/com/gitblit/transport/ssh/IPublicKeyManager.java b/src/main/java/com/gitblit/transport/ssh/IPublicKeyManager.java index 956a76ef..0dbee637 100644 --- a/src/main/java/com/gitblit/transport/ssh/IPublicKeyManager.java +++ b/src/main/java/com/gitblit/transport/ssh/IPublicKeyManager.java @@ -16,6 +16,7 @@ package com.gitblit.transport.ssh; import java.text.MessageFormat; +import java.util.Collections; import java.util.List; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; @@ -46,7 +47,11 @@ public abstract class IPublicKeyManager implements IManager { .build(new CacheLoader>() { @Override public List load(String username) { - return getKeysImpl(username); + List keys = getKeysImpl(username); + if (keys == null) { + return Collections.emptyList(); + } + return Collections.unmodifiableList(keys); } }); diff --git a/src/main/java/com/gitblit/transport/ssh/MemoryKeyManager.java b/src/main/java/com/gitblit/transport/ssh/MemoryKeyManager.java index 18f9a4e1..357b34a2 100644 --- a/src/main/java/com/gitblit/transport/ssh/MemoryKeyManager.java +++ b/src/main/java/com/gitblit/transport/ssh/MemoryKeyManager.java @@ -28,7 +28,7 @@ import java.util.Map; */ public class MemoryKeyManager extends IPublicKeyManager { - Map> keys; + final Map> keys; public MemoryKeyManager() { keys = new HashMap>(); @@ -57,7 +57,8 @@ public class MemoryKeyManager extends IPublicKeyManager { @Override protected boolean isStale(String username) { - return false; + // always return true so we gets keys from our hashmap + return true; } @Override @@ -75,6 +76,7 @@ public class MemoryKeyManager extends IPublicKeyManager { if (!keys.containsKey(id)) { keys.put(id, new ArrayList()); } + log.info("added {} key {}", username, key.getFingerprint()); return keys.get(id).add(key); } @@ -82,15 +84,27 @@ public class MemoryKeyManager extends IPublicKeyManager { public boolean removeKey(String username, SshKey key) { String id = username.toLowerCase(); if (!keys.containsKey(id)) { + log.info("can't remove keys for {}", username); return false; } - return keys.get(id).remove(key); + List list = keys.get(id); + boolean success = list.remove(key); + if (success) { + log.info("removed {} key {}", username, key.getFingerprint()); + } + + if (list.isEmpty()) { + keys.remove(id); + log.info("no {} keys left, removed {}", username, username); + } + return success; } @Override public boolean removeAllKeys(String username) { String id = username.toLowerCase(); keys.remove(id.toLowerCase()); + log.info("removed all keys for {}", username); return true; } } diff --git a/src/main/java/com/gitblit/transport/ssh/SshKey.java b/src/main/java/com/gitblit/transport/ssh/SshKey.java index 6ac0cdcb..6a20d7dd 100644 --- a/src/main/java/com/gitblit/transport/ssh/SshKey.java +++ b/src/main/java/com/gitblit/transport/ssh/SshKey.java @@ -155,8 +155,8 @@ public class SshKey implements Serializable { final byte [] bin = Base64.decodeBase64(Constants.encodeASCII(parts[1])); hash = StringUtils.getMD5(bin); } else { - // TODO get hash from publickey - hash = "todo"; + // TODO calculate the correct hash from a PublicKey instance + hash = StringUtils.getMD5(getPublicKey().getEncoded()); } for (int i = 0; i < hash.length(); i += 2) { sb.append(hash.charAt(i)).append(hash.charAt(i + 1)).append(':'); diff --git a/src/main/java/com/gitblit/transport/ssh/SshServerSessionFactory.java b/src/main/java/com/gitblit/transport/ssh/SshServerSessionFactory.java index dd3c139d..0c018f02 100644 --- a/src/main/java/com/gitblit/transport/ssh/SshServerSessionFactory.java +++ b/src/main/java/com/gitblit/transport/ssh/SshServerSessionFactory.java @@ -41,7 +41,7 @@ public class SshServerSessionFactory extends SessionFactory { @Override protected AbstractSession createSession(final IoSession io) throws Exception { - log.info("connection accepted on " + io); + log.info("creating ssh session from {}", io.getRemoteAddress()); if (io instanceof MinaSession) { if (((MinaSession) io).getSession().getConfig() instanceof SocketSessionConfig) { @@ -59,7 +59,7 @@ public class SshServerSessionFactory extends SessionFactory { session.addCloseSessionListener(new SshFutureListener() { @Override public void operationComplete(CloseFuture future) { - log.info("connection closed on " + io); + log.info("closed ssh session from {}", io.getRemoteAddress()); } }); return session; diff --git a/src/main/java/com/gitblit/transport/ssh/keys/KeysDispatcher.java b/src/main/java/com/gitblit/transport/ssh/keys/KeysDispatcher.java index 62daec6a..3f581462 100644 --- a/src/main/java/com/gitblit/transport/ssh/keys/KeysDispatcher.java +++ b/src/main/java/com/gitblit/transport/ssh/keys/KeysDispatcher.java @@ -61,7 +61,7 @@ public class KeysDispatcher extends DispatchCommand { protected final Logger log = LoggerFactory.getLogger(getClass()); - @Argument(metaVar = "", usage = "the key(s) to add") + @Argument(metaVar = "", usage = "the key to add") private List addKeys = new ArrayList(); @Option(name = "--permission", aliases = { "-p" }, metaVar = "PERMISSION", usage = "set the key access permission") @@ -76,7 +76,7 @@ public class KeysDispatcher extends DispatchCommand { } @Override - public void run() throws IOException, UnloggedFailure { + public void run() throws IOException, Failure { String username = getContext().getClient().getUsername(); List keys = readKeys(addKeys); for (String key : keys) { @@ -87,7 +87,7 @@ public class KeysDispatcher extends DispatchCommand { try { sshKey.setPermission(ap); } catch (IllegalArgumentException e) { - throw new UnloggedFailure(1, e.getMessage()); + throw new Failure(1, e.getMessage()); } } } @@ -105,22 +105,21 @@ public class KeysDispatcher extends DispatchCommand { private final String ALL = "ALL"; - @Argument(metaVar = "||ALL", usage = "the key to remove", required = true) - private List removeKeys = new ArrayList(); + @Argument(metaVar = "|ALL", usage = "the key to remove", required = true) + private List keyParameters = new ArrayList(); @Override - public void run() throws IOException, UnloggedFailure { + public void run() throws IOException, Failure { String username = getContext().getClient().getUsername(); // remove a key that has been piped to the command // or remove all keys - List currentKeys = getKeyManager().getKeys(username); - if (currentKeys == null || currentKeys.isEmpty()) { + List registeredKeys = new ArrayList(getKeyManager().getKeys(username)); + if (registeredKeys.isEmpty()) { throw new UnloggedFailure(1, "There are no registered keys!"); } - List keys = readKeys(removeKeys); - if (keys.contains(ALL)) { + if (keyParameters.contains(ALL)) { if (getKeyManager().removeAllKeys(username)) { stdout.println("Removed all keys."); log.info("removed all SSH public keys from {}", username); @@ -128,32 +127,25 @@ public class KeysDispatcher extends DispatchCommand { log.warn("failed to remove all SSH public keys from {}", username); } } else { - for (String key : keys) { + for (String keyParameter : keyParameters) { try { // remove a key by it's index (1-based indexing) - int index = Integer.parseInt(key); - if (index > keys.size()) { - if (keys.size() == 1) { - throw new UnloggedFailure(1, "Invalid index specified. There is only 1 registered key."); + int index = Integer.parseInt(keyParameter); + if (index > registeredKeys.size()) { + if (keyParameters.size() == 1) { + throw new Failure(1, "Invalid index specified. There is only 1 registered key."); } - throw new UnloggedFailure(1, String.format("Invalid index specified. There are %d registered keys.", keys.size())); + throw new Failure(1, String.format("Invalid index specified. There are %d registered keys.", registeredKeys.size())); } - SshKey sshKey = currentKeys.get(index - 1); + SshKey sshKey = registeredKeys.get(index - 1); if (getKeyManager().removeKey(username, sshKey)) { stdout.println(String.format("Removed %s", sshKey.getFingerprint())); } else { - throw new UnloggedFailure(1, String.format("failed to remove #%s: %s", key, sshKey.getFingerprint())); - } - } catch (Exception e) { - // remove key by raw key data - SshKey sshKey = parseKey(key); - if (getKeyManager().removeKey(username, sshKey)) { - stdout.println(String.format("Removed %s", sshKey.getFingerprint())); - log.info("removed SSH public key {} from {}", sshKey.getFingerprint(), username); - } else { - log.warn("failed to remove SSH public key {} from {}", sshKey.getFingerprint(), username); - throw new UnloggedFailure(1, String.format("failed to remove %s", sshKey.getFingerprint())); + throw new Failure(1, String.format("failed to remove #%s: %s", keyParameter, sshKey.getFingerprint())); } + } catch (NumberFormatException e) { + log.warn("failed to remove SSH public key {} from {}", keyParameter, username); + throw new Failure(1, String.format("failed to remove key %s", keyParameter)); } } } @@ -254,7 +246,7 @@ public class KeysDispatcher extends DispatchCommand { private List values = new ArrayList(); @Override - public void run() throws UnloggedFailure { + public void run() throws Failure { final String username = getContext().getClient().getUsername(); IPublicKeyManager keyManager = getContext().getGitblit().getPublicKeyManager(); List keys = keyManager.getKeys(username); @@ -268,7 +260,7 @@ public class KeysDispatcher extends DispatchCommand { if (keyManager.addKey(username, key)) { stdout.println(String.format("Updated the comment for key #%d.", index)); } else { - throw new UnloggedFailure(1, String.format("Failed to update the comment for key #%d!", index)); + throw new Failure(1, String.format("Failed to update the comment for key #%d!", index)); } } diff --git a/src/main/java/log4j.properties b/src/main/java/log4j.properties index 115dcd01..43d31d80 100644 --- a/src/main/java/log4j.properties +++ b/src/main/java/log4j.properties @@ -25,7 +25,9 @@ log4j.rootCategory=INFO, S #log4j.logger.net=INFO #log4j.logger.com.gitblit=DEBUG -log4j.logger.org.apache.sshd=ERROR +log4j.logger.com.gitblit.transport.ssh.SshServerSession=WARN +log4j.logger.org.apache.sshd=WARN +log4j.logger.org.apache.mina=WARN log4j.logger.org.apache.wicket=INFO log4j.logger.org.apache.wicket.RequestListenerInterface=WARN diff --git a/src/test/java/com/gitblit/tests/GitBlitSuite.java b/src/test/java/com/gitblit/tests/GitBlitSuite.java index b8d3b181..5a7dcea1 100644 --- a/src/test/java/com/gitblit/tests/GitBlitSuite.java +++ b/src/test/java/com/gitblit/tests/GitBlitSuite.java @@ -64,7 +64,8 @@ import com.gitblit.utils.JGitUtils; SshDaemonTest.class, GroovyScriptTest.class, LuceneExecutorTest.class, RepositoryModelTest.class, FanoutServiceTest.class, Issue0259Test.class, Issue0271Test.class, HtpasswdAuthenticationTest.class, ModelUtilsTest.class, JnaUtilsTest.class, LdapSyncServiceTest.class, FileTicketServiceTest.class, - BranchTicketServiceTest.class, RedisTicketServiceTest.class, AuthenticationManagerTest.class }) + BranchTicketServiceTest.class, RedisTicketServiceTest.class, AuthenticationManagerTest.class, + SshKeysDispatcherTest.class }) public class GitBlitSuite { public static final File BASEFOLDER = new File("data"); @@ -137,11 +138,16 @@ public class GitBlitSuite { Executors.newSingleThreadExecutor().execute(new Runnable() { @Override public void run() { - GitBlitServer.main("--httpPort", "" + port, "--httpsPort", "0", "--shutdownPort", - "" + shutdownPort, "--gitPort", "" + gitPort, "--repositoriesFolder", - "\"" + GitBlitSuite.REPOSITORIES.getAbsolutePath() + "\"", "--userService", - GitBlitSuite.USERSCONF.getAbsolutePath(), "--settings", GitBlitSuite.SETTINGS.getAbsolutePath(), - "--baseFolder", "data", "--sshPort", "" + sshPort); + GitBlitServer.main( + "--httpPort", "" + port, + "--httpsPort", "0", + "--shutdownPort", "" + shutdownPort, + "--gitPort", "" + gitPort, + "--sshPort", "" + sshPort, + "--repositoriesFolder", GitBlitSuite.REPOSITORIES.getAbsolutePath(), + "--userService", GitBlitSuite.USERSCONF.getAbsolutePath(), + "--settings", GitBlitSuite.SETTINGS.getAbsolutePath(), + "--baseFolder", "data"); } }); diff --git a/src/test/java/com/gitblit/tests/SshDaemonTest.java b/src/test/java/com/gitblit/tests/SshDaemonTest.java index 620190ef..dcaeaff8 100644 --- a/src/test/java/com/gitblit/tests/SshDaemonTest.java +++ b/src/test/java/com/gitblit/tests/SshDaemonTest.java @@ -15,102 +15,76 @@ */ 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 java.io.File; +import java.text.MessageFormat; +import java.util.List; -import org.apache.sshd.ClientChannel; import org.apache.sshd.ClientSession; import org.apache.sshd.SshClient; -import org.apache.sshd.common.KeyPairProvider; -import org.junit.After; -import org.junit.AfterClass; -import org.junit.Before; -import org.junit.BeforeClass; +import org.eclipse.jgit.api.CloneCommand; +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.transport.SshSessionFactory; +import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider; +import org.eclipse.jgit.util.FileUtils; import org.junit.Test; import com.gitblit.Constants; -import com.gitblit.transport.ssh.IPublicKeyManager; -import com.gitblit.transport.ssh.MemoryKeyManager; -import com.gitblit.transport.ssh.SshKey; +import com.gitblit.Constants.AccessRestrictionType; +import com.gitblit.Constants.AuthorizationControl; +import com.gitblit.models.RepositoryModel; +import com.gitblit.utils.JGitUtils; -public class SshDaemonTest extends GitblitUnitTest { +public class SshDaemonTest extends SshUnitTest { - private static final AtomicBoolean started = new AtomicBoolean(false); - private static KeyPair pair; + static File ticgitFolder = new File(GitBlitSuite.REPOSITORIES, "working/ticgit"); - @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(); - } - } - - protected MemoryKeyManager getKeyManager() { - IPublicKeyManager mgr = gitblit().getPublicKeyManager(); - if (mgr instanceof MemoryKeyManager) { - return (MemoryKeyManager) gitblit().getPublicKeyManager(); - } else { - throw new RuntimeException("unexpected key manager type " + mgr.getClass().getName()); - } - } - - @Before - public void prepare() { - MemoryKeyManager keyMgr = getKeyManager(); - keyMgr.addKey("admin", new SshKey(pair.getPublic())); - } - - @After - public void tearDown() { - MemoryKeyManager keyMgr = getKeyManager(); - keyMgr.removeAllKeys("admin"); - } + String url = GitBlitSuite.sshDaemonUrl; @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()); + SshClient client = getClient(); + ClientSession session = client.connect(username, "localhost", GitBlitSuite.sshPort).await().getSession(); + session.addPublicKeyIdentity(rwKeyPair); + assertTrue(session.auth().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, "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 = testSshCommand("version"); + assertEquals(Constants.getGitBlitVersion(), result); + } - String result = out.toString().trim(); - channel.close(false); - client.stop(); + @Test + public void testCloneCommand() throws Exception { + if (ticgitFolder.exists()) { + GitBlitSuite.close(ticgitFolder); + FileUtils.delete(ticgitFolder, FileUtils.RECURSIVE); + } - assertEquals(Constants.getGitBlitVersion(), result); - } + // set clone restriction + RepositoryModel model = repositories().getRepositoryModel("ticgit.git"); + model.accessRestriction = AccessRestrictionType.CLONE; + model.authorizationControl = AuthorizationControl.NAMED; + repositories().updateRepositoryModel(model.name, model, false); + + JschConfigTestSessionFactory sessionFactory = new JschConfigTestSessionFactory(roKeyPair); + SshSessionFactory.setInstance(sessionFactory); + + CloneCommand clone = Git.cloneRepository(); + clone.setCredentialsProvider(new UsernamePasswordCredentialsProvider(username, password)); + clone.setURI(MessageFormat.format("{0}/ticgit.git", url)); + clone.setDirectory(ticgitFolder); + clone.setBare(false); + clone.setCloneAllBranches(true); + Git git = clone.call(); + List commits = JGitUtils.getRevLog(git.getRepository(), 10); + GitBlitSuite.close(git); + assertEquals(10, commits.size()); + + // restore anonymous repository access + model.accessRestriction = AccessRestrictionType.NONE; + model.authorizationControl = AuthorizationControl.NAMED; + repositories().updateRepositoryModel(model.name, model, false); + } } diff --git a/src/test/java/com/gitblit/tests/SshKeysDispatcherTest.java b/src/test/java/com/gitblit/tests/SshKeysDispatcherTest.java new file mode 100644 index 00000000..8ccdc5bf --- /dev/null +++ b/src/test/java/com/gitblit/tests/SshKeysDispatcherTest.java @@ -0,0 +1,115 @@ +/* + * 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.KeyPair; +import java.util.List; + +import org.junit.Test; +import org.parboiled.common.StringUtils; + +import com.gitblit.Constants.AccessPermission; +import com.gitblit.transport.ssh.SshKey; + +/** + * Tests the Keys Dispatcher and it's commands. + * + * @author James Moger + * + */ +public class SshKeysDispatcherTest extends SshUnitTest { + + @Test + public void testKeysListCommand() throws Exception { + String result = testSshCommand("keys ls -L"); + List keys = getKeyManager().getKeys(username); + assertEquals(String.format("There are %d keys!", keys.size()), 2, keys.size()); + assertEquals(keys.get(0).getRawData() + "\n" + keys.get(1).getRawData(), result); + } + + @Test + public void testKeysWhichCommand() throws Exception { + String result = testSshCommand("keys which -L"); + List keys = getKeyManager().getKeys(username); + assertEquals(String.format("There are %d keys!", keys.size()), 2, keys.size()); + assertEquals(keys.get(0).getRawData(), result); + } + + @Test + public void testKeysRmCommand() throws Exception { + testSshCommand("keys rm 2"); + String result = testSshCommand("keys ls -L"); + List keys = getKeyManager().getKeys(username); + assertEquals(String.format("There are %d keys!", keys.size()), 1, keys.size()); + assertEquals(keys.get(0).getRawData(), result); + } + + @Test + public void testKeysRmAllByIndexCommand() throws Exception { + testSshCommand("keys rm 1 2"); + List keys = getKeyManager().getKeys(username); + assertEquals(String.format("There are %d keys!", keys.size()), 0, keys.size()); + try { + testSshCommand("keys ls -L"); + assertTrue("Authentication worked without a public key?!", false); + } catch (AssertionError e) { + assertTrue(true); + } + } + + @Test + public void testKeysRmAllCommand() throws Exception { + testSshCommand("keys rm ALL"); + List keys = getKeyManager().getKeys(username); + assertEquals(String.format("There are %d keys!", keys.size()), 0, keys.size()); + try { + testSshCommand("keys ls -L"); + assertTrue("Authentication worked without a public key?!", false); + } catch (AssertionError e) { + assertTrue(true); + } + } + + @Test + public void testKeysAddCommand() throws Exception { + KeyPair kp = generator.generateKeyPair(); + SshKey key = new SshKey(kp.getPublic()); + testSshCommand("keys add --permission R", key.getRawData()); + List keys = getKeyManager().getKeys(username); + assertEquals(String.format("There are %d keys!", keys.size()), 3, keys.size()); + assertEquals(AccessPermission.CLONE, keys.get(2).getPermission()); + + String result = testSshCommand("keys ls -L"); + StringBuilder sb = new StringBuilder(); + for (SshKey sk : keys) { + sb.append(sk.getRawData()); + sb.append('\n'); + } + sb.setLength(sb.length() - 1); + assertEquals(sb.toString(), result); + } + + @Test + public void testKeysCommentCommand() throws Exception { + List keys = getKeyManager().getKeys(username); + assertTrue(StringUtils.isEmpty(keys.get(0).getComment())); + String comment = "this is my comment"; + testSshCommand(String.format("keys comment 1 %s", comment)); + + keys = getKeyManager().getKeys(username); + assertEquals(comment, keys.get(0).getComment()); + } +} diff --git a/src/test/java/com/gitblit/tests/SshUnitTest.java b/src/test/java/com/gitblit/tests/SshUnitTest.java new file mode 100644 index 00000000..43b51b74 --- /dev/null +++ b/src/test/java/com/gitblit/tests/SshUnitTest.java @@ -0,0 +1,141 @@ +/* + * 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.IOException; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.net.SocketAddress; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.PublicKey; +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.client.ServerKeyVerifier; +import org.apache.sshd.common.util.SecurityUtils; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; + +import com.gitblit.Constants.AccessPermission; +import com.gitblit.transport.ssh.IPublicKeyManager; +import com.gitblit.transport.ssh.MemoryKeyManager; +import com.gitblit.transport.ssh.SshKey; + +/** + * Base class for SSH unit tests. + */ +public abstract class SshUnitTest extends GitblitUnitTest { + + protected static final AtomicBoolean started = new AtomicBoolean(false); + protected static KeyPairGenerator generator; + protected KeyPair rwKeyPair; + protected KeyPair roKeyPair; + protected String username = "admin"; + protected String password = "admin"; + + @BeforeClass + public static void startGitblit() throws Exception { + generator = SecurityUtils.getKeyPairGenerator("RSA"); + started.set(GitBlitSuite.startGitblit()); + } + + @AfterClass + public static void stopGitblit() throws Exception { + if (started.get()) { + GitBlitSuite.stopGitblit(); + } + } + + protected MemoryKeyManager getKeyManager() { + IPublicKeyManager mgr = gitblit().getPublicKeyManager(); + if (mgr instanceof MemoryKeyManager) { + return (MemoryKeyManager) gitblit().getPublicKeyManager(); + } else { + throw new RuntimeException("unexpected key manager type " + mgr.getClass().getName()); + } + } + + @Before + public void prepare() { + rwKeyPair = generator.generateKeyPair(); + + MemoryKeyManager keyMgr = getKeyManager(); + keyMgr.addKey(username, new SshKey(rwKeyPair.getPublic())); + + roKeyPair = generator.generateKeyPair(); + SshKey sshKey = new SshKey(roKeyPair.getPublic()); + sshKey.setPermission(AccessPermission.CLONE); + keyMgr.addKey(username, sshKey); + } + + @After + public void tearDown() { + MemoryKeyManager keyMgr = getKeyManager(); + keyMgr.removeAllKeys(username); + } + + protected SshClient getClient() { + SshClient client = SshClient.setUpDefaultClient(); + client.setServerKeyVerifier(new ServerKeyVerifier() { + @Override + public boolean verifyServerKey(ClientSession sshClientSession, SocketAddress remoteAddress, PublicKey serverKey) { + return true; + } + }); + client.start(); + return client; + } + + protected String testSshCommand(String cmd) throws IOException, InterruptedException { + return testSshCommand(cmd, null); + } + + protected String testSshCommand(String cmd, String stdin) throws IOException, InterruptedException { + SshClient client = getClient(); + ClientSession session = client.connect(username, "localhost", GitBlitSuite.sshPort).await().getSession(); + session.addPublicKeyIdentity(rwKeyPair); + assertTrue(session.auth().await().isSuccess()); + + ClientChannel channel = session.createChannel(ClientChannel.CHANNEL_EXEC, cmd); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + if (stdin != null) { + Writer w = new OutputStreamWriter(baos); + w.write(stdin); + 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(); + return result; + } +} diff --git a/src/test/java/com/gitblit/tests/SshUtils.java b/src/test/java/com/gitblit/tests/SshUtils.java deleted file mode 100644 index 9760f755..00000000 --- a/src/test/java/com/gitblit/tests/SshUtils.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * 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(); - } - } - -}