James Moger
2014-04-07 521cb6022a9ee30bf3115a8dcb991aa5c7e420e3
Unit tests for ssh daemon and keys dispatcher
1 files deleted
2 files added
10 files modified
598 ■■■■■ changed files
src/main/java/com/gitblit/transport/ssh/CachingPublicKeyAuthenticator.java 27 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/transport/ssh/FileKeyManager.java 4 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/transport/ssh/IPublicKeyManager.java 7 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/transport/ssh/MemoryKeyManager.java 20 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/transport/ssh/SshKey.java 4 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/transport/ssh/SshServerSessionFactory.java 4 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/transport/ssh/keys/KeysDispatcher.java 52 ●●●●● patch | view | raw | blame | history
src/main/java/log4j.properties 4 ●●● patch | view | raw | blame | history
src/test/java/com/gitblit/tests/GitBlitSuite.java 18 ●●●●● patch | view | raw | blame | history
src/test/java/com/gitblit/tests/SshDaemonTest.java 128 ●●●●● patch | view | raw | blame | history
src/test/java/com/gitblit/tests/SshKeysDispatcherTest.java 115 ●●●●● patch | view | raw | blame | history
src/test/java/com/gitblit/tests/SshUnitTest.java 141 ●●●●● patch | view | raw | blame | history
src/test/java/com/gitblit/tests/SshUtils.java 74 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/transport/ssh/CachingPublicKeyAuthenticator.java
@@ -38,8 +38,7 @@
 * @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 @@
    protected final IAuthenticationManager authManager;
    private final Map<ServerSession, Map<PublicKey, Boolean>> cache =
            new ConcurrentHashMap<ServerSession, Map<PublicKey, Boolean>>();
    private final Map<ServerSession, Map<PublicKey, Boolean>> cache = new ConcurrentHashMap<ServerSession, Map<PublicKey, Boolean>>();
    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<PublicKey, Boolean> map = cache.get(session);
        if (map == null) {
            map = new HashMap<PublicKey, Boolean>();
@@ -73,19 +69,21 @@
        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<SshKey> 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 @@
            }
        }
        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;
    }
src/main/java/com/gitblit/transport/ssh/FileKeyManager.java
@@ -90,7 +90,7 @@
    @Override
    protected List<SshKey> 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 @@
                return list;
            }
        } catch (IOException e) {
            throw new RuntimeException("Canot read ssh keys", e);
            throw new RuntimeException("Cannot read ssh keys", e);
        }
        return null;
    }
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 @@
            .build(new CacheLoader<String, List<SshKey>>() {
                @Override
                public List<SshKey> load(String username) {
                    return getKeysImpl(username);
                    List<SshKey> keys = getKeysImpl(username);
                    if (keys == null) {
                        return Collections.emptyList();
                    }
                    return Collections.unmodifiableList(keys);
                }
            });
src/main/java/com/gitblit/transport/ssh/MemoryKeyManager.java
@@ -28,7 +28,7 @@
 */
public class MemoryKeyManager extends IPublicKeyManager {
    Map<String, List<SshKey>> keys;
    final Map<String, List<SshKey>> keys;
    public MemoryKeyManager() {
        keys = new HashMap<String, List<SshKey>>();
@@ -57,7 +57,8 @@
    @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 @@
        if (!keys.containsKey(id)) {
            keys.put(id, new ArrayList<SshKey>());
        }
        log.info("added {} key {}", username, key.getFingerprint());
        return keys.get(id).add(key);
    }
@@ -82,15 +84,27 @@
    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<SshKey> 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;
    }
}
src/main/java/com/gitblit/transport/ssh/SshKey.java
@@ -155,8 +155,8 @@
                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(':');
src/main/java/com/gitblit/transport/ssh/SshServerSessionFactory.java
@@ -41,7 +41,7 @@
    @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 @@
        session.addCloseSessionListener(new SshFutureListener<CloseFuture>() {
            @Override
            public void operationComplete(CloseFuture future) {
                log.info("connection closed on " + io);
                log.info("closed ssh session from {}", io.getRemoteAddress());
            }
        });
        return session;
src/main/java/com/gitblit/transport/ssh/keys/KeysDispatcher.java
@@ -61,7 +61,7 @@
        protected final Logger log = LoggerFactory.getLogger(getClass());
        @Argument(metaVar = "<KEY>", usage = "the key(s) to add")
        @Argument(metaVar = "<STDIN>", usage = "the key to add")
        private List<String> addKeys = new ArrayList<String>();
        @Option(name = "--permission", aliases = { "-p" }, metaVar = "PERMISSION", usage = "set the key access permission")
@@ -76,7 +76,7 @@
        }
        @Override
        public void run() throws IOException, UnloggedFailure {
        public void run() throws IOException, Failure {
            String username = getContext().getClient().getUsername();
            List<String> keys = readKeys(addKeys);
            for (String key : keys) {
@@ -87,7 +87,7 @@
                        try {
                            sshKey.setPermission(ap);
                        } catch (IllegalArgumentException e) {
                            throw new UnloggedFailure(1, e.getMessage());
                            throw new Failure(1, e.getMessage());
                        }
                    }
                }
@@ -105,22 +105,21 @@
        private final String ALL = "ALL";
        @Argument(metaVar = "<INDEX>|<KEY>|ALL", usage = "the key to remove", required = true)
        private List<String> removeKeys = new ArrayList<String>();
        @Argument(metaVar = "<INDEX>|ALL", usage = "the key to remove", required = true)
        private List<String> keyParameters = new ArrayList<String>();
        @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<SshKey> currentKeys = getKeyManager().getKeys(username);
            if (currentKeys == null || currentKeys.isEmpty()) {
            List<SshKey> registeredKeys = new ArrayList<SshKey>(getKeyManager().getKeys(username));
            if (registeredKeys.isEmpty()) {
                throw new UnloggedFailure(1, "There are no registered keys!");
            }
            List<String> 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 @@
                    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()));
                            throw new Failure(1,  String.format("failed to remove #%s: %s", keyParameter, 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()));
                        }
                    } 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 @@
        private List<String> values = new ArrayList<String>();
        @Override
        public void run() throws UnloggedFailure {
        public void run() throws Failure {
            final String username = getContext().getClient().getUsername();
            IPublicKeyManager keyManager = getContext().getGitblit().getPublicKeyManager();
            List<SshKey> keys = keyManager.getKeys(username);
@@ -268,7 +260,7 @@
            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));
            }
        }
src/main/java/log4j.properties
@@ -25,7 +25,9 @@
#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
src/test/java/com/gitblit/tests/GitBlitSuite.java
@@ -64,7 +64,8 @@
        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 @@
        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");
            }
        });
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());
        String result = testSshCommand("version");
        assertEquals(Constants.getGitBlitVersion(), result);
    }
        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()));
    @Test
    public void testCloneCommand() throws Exception {
        if (ticgitFolder.exists()) {
            GitBlitSuite.close(ticgitFolder);
            FileUtils.delete(ticgitFolder, FileUtils.RECURSIVE);
        }
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        ByteArrayOutputStream err = new ByteArrayOutputStream();
        channel.setOut(out);
        channel.setErr(err);
        channel.open();
        // set clone restriction
        RepositoryModel model = repositories().getRepositoryModel("ticgit.git");
        model.accessRestriction = AccessRestrictionType.CLONE;
        model.authorizationControl = AuthorizationControl.NAMED;
        repositories().updateRepositoryModel(model.name, model, false);
        channel.waitFor(ClientChannel.CLOSED, 0);
        JschConfigTestSessionFactory sessionFactory = new JschConfigTestSessionFactory(roKeyPair);
        SshSessionFactory.setInstance(sessionFactory);
        String result = out.toString().trim();
        channel.close(false);
        client.stop();
        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<RevCommit> commits = JGitUtils.getRevLog(git.getRepository(), 10);
        GitBlitSuite.close(git);
        assertEquals(10, commits.size());
        assertEquals(Constants.getGitBlitVersion(), result);
     }
        // restore anonymous repository access
        model.accessRestriction = AccessRestrictionType.NONE;
        model.authorizationControl = AuthorizationControl.NAMED;
        repositories().updateRepositoryModel(model.name, model, false);
    }
}
src/test/java/com/gitblit/tests/SshKeysDispatcherTest.java
New file
@@ -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<SshKey> 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<SshKey> 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<SshKey> 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<SshKey> 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<SshKey> 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<SshKey> 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<SshKey> 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());
    }
}
src/test/java/com/gitblit/tests/SshUnitTest.java
New file
@@ -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;
    }
}
src/test/java/com/gitblit/tests/SshUtils.java
File was deleted