From 5d58a05a9843ec90d06ca42061ff638418f73687 Mon Sep 17 00:00:00 2001 From: David Ostrovsky Date: Sun, 16 Mar 2014 22:55:30 +0100 Subject: Add SSH daemon test --- .../gitblit/tests/BogusPublicKeyAuthenticator.java | 39 ++++++++++ src/test/java/com/gitblit/tests/GitBlitSuite.java | 14 +++- src/test/java/com/gitblit/tests/SshDaemonTest.java | 90 ++++++++++++++++++++++ src/test/java/com/gitblit/tests/SshUtils.java | 74 ++++++++++++++++++ 4 files changed, 216 insertions(+), 1 deletion(-) create mode 100644 src/test/java/com/gitblit/tests/BogusPublicKeyAuthenticator.java create mode 100644 src/test/java/com/gitblit/tests/SshDaemonTest.java create mode 100644 src/test/java/com/gitblit/tests/SshUtils.java (limited to 'src/test/java') 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 00000000..80be1a01 --- /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 c015c847..17d609e7 100644 --- a/src/test/java/com/gitblit/tests/GitBlitSuite.java +++ b/src/test/java/com/gitblit/tests/GitBlitSuite.java @@ -61,7 +61,7 @@ import com.gitblit.utils.JGitUtils; 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 @@ public class GitBlitSuite { 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 @@ public class GitBlitSuite { "\"" + 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 00000000..5294f691 --- /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 00000000..9760f755 --- /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(); + } + } + +} -- cgit v1.2.3 From 245836904ba5cecdc31773cf7c9616396c8ad8c0 Mon Sep 17 00:00:00 2001 From: James Moger Date: Mon, 17 Mar 2014 21:30:46 -0400 Subject: Elevate the public key manager to a top-level manager --- src/main/distrib/data/gitblit.properties | 5 - src/main/java/com/gitblit/DaggerModule.java | 37 ++++++- src/main/java/com/gitblit/FederationClient.java | 2 +- src/main/java/com/gitblit/GitBlit.java | 5 +- .../java/com/gitblit/manager/GitblitManager.java | 10 ++ src/main/java/com/gitblit/manager/IGitblit.java | 8 ++ .../java/com/gitblit/servlet/GitblitContext.java | 4 +- .../ssh/CachingPublicKeyAuthenticator.java | 15 ++- .../com/gitblit/transport/ssh/FileKeyManager.java | 5 +- .../com/gitblit/transport/ssh/IKeyManager.java | 78 --------------- .../gitblit/transport/ssh/IPublicKeyManager.java | 82 ++++++++++++++++ .../gitblit/transport/ssh/MemoryKeyManager.java | 98 +++++++++++++++++++ .../com/gitblit/transport/ssh/NullKeyManager.java | 5 +- .../gitblit/transport/ssh/SshCommandFactory.java | 8 +- .../java/com/gitblit/transport/ssh/SshDaemon.java | 107 +-------------------- .../transport/ssh/commands/DispatchCommand.java | 13 --- .../transport/ssh/gitblit/BaseKeyCommand.java | 12 +-- .../java/com/gitblit/wicket/GitBlitWebApp.java | 9 ++ src/test/config/test-gitblit.properties | 2 +- .../gitblit/tests/BogusPublicKeyAuthenticator.java | 39 -------- src/test/java/com/gitblit/tests/GitBlitSuite.java | 18 +--- src/test/java/com/gitblit/tests/SshDaemonTest.java | 25 +++++ 22 files changed, 300 insertions(+), 287 deletions(-) delete mode 100644 src/main/java/com/gitblit/transport/ssh/IKeyManager.java create mode 100644 src/main/java/com/gitblit/transport/ssh/IPublicKeyManager.java create mode 100644 src/main/java/com/gitblit/transport/ssh/MemoryKeyManager.java delete mode 100644 src/test/java/com/gitblit/tests/BogusPublicKeyAuthenticator.java (limited to 'src/test/java') diff --git a/src/main/distrib/data/gitblit.properties b/src/main/distrib/data/gitblit.properties index 52bb252b..64a52f5c 100644 --- a/src/main/distrib/data/gitblit.properties +++ b/src/main/distrib/data/gitblit.properties @@ -129,11 +129,6 @@ git.sshKeysFolder= ${baseFolder}/ssh # 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/DaggerModule.java b/src/main/java/com/gitblit/DaggerModule.java index 5ae8b253..b109f1db 100644 --- a/src/main/java/com/gitblit/DaggerModule.java +++ b/src/main/java/com/gitblit/DaggerModule.java @@ -32,6 +32,11 @@ import com.gitblit.manager.ProjectManager; import com.gitblit.manager.RepositoryManager; import com.gitblit.manager.RuntimeManager; import com.gitblit.manager.UserManager; +import com.gitblit.transport.ssh.FileKeyManager; +import com.gitblit.transport.ssh.IPublicKeyManager; +import com.gitblit.transport.ssh.MemoryKeyManager; +import com.gitblit.transport.ssh.NullKeyManager; +import com.gitblit.utils.StringUtils; import com.gitblit.wicket.GitBlitWebApp; import dagger.Module; @@ -53,6 +58,7 @@ import dagger.Provides; INotificationManager.class, IUserManager.class, IAuthenticationManager.class, + IPublicKeyManager.class, IRepositoryManager.class, IProjectManager.class, IFederationManager.class, @@ -62,7 +68,7 @@ import dagger.Provides; // the Gitblit Wicket app GitBlitWebApp.class - } + } ) public class DaggerModule { @@ -91,6 +97,31 @@ public class DaggerModule { userManager); } + @Provides @Singleton IPublicKeyManager providePublicKeyManager( + IStoredSettings settings, + IRuntimeManager runtimeManager) { + + String clazz = settings.getString(Keys.git.sshKeysManager, FileKeyManager.class.getName()); + if (StringUtils.isEmpty(clazz)) { + clazz = FileKeyManager.class.getName(); + } + if (FileKeyManager.class.getName().equals(clazz)) { + return new FileKeyManager(runtimeManager); + } else if (NullKeyManager.class.getName().equals(clazz)) { + return new NullKeyManager(); + } else if (MemoryKeyManager.class.getName().equals(clazz)) { + return new MemoryKeyManager(); + } else { + try { + Class mgrClass = Class.forName(clazz); + return (IPublicKeyManager) mgrClass.newInstance(); + } catch (Exception e) { + + } + return null; + } + } + @Provides @Singleton IRepositoryManager provideRepositoryManager( IRuntimeManager runtimeManager, IUserManager userManager) { @@ -127,6 +158,7 @@ public class DaggerModule { INotificationManager notificationManager, IUserManager userManager, IAuthenticationManager authenticationManager, + IPublicKeyManager publicKeyManager, IRepositoryManager repositoryManager, IProjectManager projectManager, IFederationManager federationManager) { @@ -136,6 +168,7 @@ public class DaggerModule { notificationManager, userManager, authenticationManager, + publicKeyManager, repositoryManager, projectManager, federationManager); @@ -146,6 +179,7 @@ public class DaggerModule { INotificationManager notificationManager, IUserManager userManager, IAuthenticationManager authenticationManager, + IPublicKeyManager publicKeyManager, IRepositoryManager repositoryManager, IProjectManager projectManager, IFederationManager federationManager, @@ -156,6 +190,7 @@ public class DaggerModule { notificationManager, userManager, authenticationManager, + publicKeyManager, repositoryManager, projectManager, federationManager, diff --git a/src/main/java/com/gitblit/FederationClient.java b/src/main/java/com/gitblit/FederationClient.java index 792a6382..d20025f0 100644 --- a/src/main/java/com/gitblit/FederationClient.java +++ b/src/main/java/com/gitblit/FederationClient.java @@ -97,7 +97,7 @@ public class FederationClient { UserManager users = new UserManager(runtime).start(); RepositoryManager repositories = new RepositoryManager(runtime, users).start(); FederationManager federation = new FederationManager(runtime, notifications, repositories).start(); - IGitblit gitblit = new GitblitManager(runtime, notifications, users, null, repositories, null, federation); + IGitblit gitblit = new GitblitManager(runtime, notifications, users, null, null, repositories, null, federation); FederationPullService puller = new FederationPullService(gitblit, federation.getFederationRegistrations()) { @Override diff --git a/src/main/java/com/gitblit/GitBlit.java b/src/main/java/com/gitblit/GitBlit.java index 817d18cb..b223d03c 100644 --- a/src/main/java/com/gitblit/GitBlit.java +++ b/src/main/java/com/gitblit/GitBlit.java @@ -41,6 +41,7 @@ import com.gitblit.tickets.FileTicketService; import com.gitblit.tickets.ITicketService; import com.gitblit.tickets.NullTicketService; import com.gitblit.tickets.RedisTicketService; +import com.gitblit.transport.ssh.IPublicKeyManager; import com.gitblit.utils.StringUtils; import dagger.Module; @@ -67,6 +68,7 @@ public class GitBlit extends GitblitManager { INotificationManager notificationManager, IUserManager userManager, IAuthenticationManager authenticationManager, + IPublicKeyManager publicKeyManager, IRepositoryManager repositoryManager, IProjectManager projectManager, IFederationManager federationManager) { @@ -75,6 +77,7 @@ public class GitBlit extends GitblitManager { notificationManager, userManager, authenticationManager, + publicKeyManager, repositoryManager, projectManager, federationManager); @@ -262,7 +265,7 @@ public class GitBlit extends GitblitManager { FileTicketService.class, BranchTicketService.class, RedisTicketService.class - } + } ) class GitBlitModule { diff --git a/src/main/java/com/gitblit/manager/GitblitManager.java b/src/main/java/com/gitblit/manager/GitblitManager.java index 97e8efc9..8856715a 100644 --- a/src/main/java/com/gitblit/manager/GitblitManager.java +++ b/src/main/java/com/gitblit/manager/GitblitManager.java @@ -69,6 +69,7 @@ import com.gitblit.models.SettingModel; import com.gitblit.models.TeamModel; import com.gitblit.models.UserModel; import com.gitblit.tickets.ITicketService; +import com.gitblit.transport.ssh.IPublicKeyManager; import com.gitblit.utils.ArrayUtils; import com.gitblit.utils.HttpUtils; import com.gitblit.utils.JsonUtils; @@ -107,6 +108,8 @@ public class GitblitManager implements IGitblit { protected final IAuthenticationManager authenticationManager; + protected final IPublicKeyManager publicKeyManager; + protected final IRepositoryManager repositoryManager; protected final IProjectManager projectManager; @@ -118,6 +121,7 @@ public class GitblitManager implements IGitblit { INotificationManager notificationManager, IUserManager userManager, IAuthenticationManager authenticationManager, + IPublicKeyManager publicKeyManager, IRepositoryManager repositoryManager, IProjectManager projectManager, IFederationManager federationManager) { @@ -127,6 +131,7 @@ public class GitblitManager implements IGitblit { this.notificationManager = notificationManager; this.userManager = userManager; this.authenticationManager = authenticationManager; + this.publicKeyManager = publicKeyManager; this.repositoryManager = repositoryManager; this.projectManager = projectManager; this.federationManager = federationManager; @@ -524,6 +529,11 @@ public class GitblitManager implements IGitblit { throw new RuntimeException("This class does not have a ticket service!"); } + @Override + public IPublicKeyManager getPublicKeyManager() { + return publicKeyManager; + } + /* * ISTOREDSETTINGS * diff --git a/src/main/java/com/gitblit/manager/IGitblit.java b/src/main/java/com/gitblit/manager/IGitblit.java index 50210e9d..f4221cf9 100644 --- a/src/main/java/com/gitblit/manager/IGitblit.java +++ b/src/main/java/com/gitblit/manager/IGitblit.java @@ -27,6 +27,7 @@ import com.gitblit.models.RepositoryUrl; import com.gitblit.models.TeamModel; import com.gitblit.models.UserModel; import com.gitblit.tickets.ITicketService; +import com.gitblit.transport.ssh.IPublicKeyManager; public interface IGitblit extends IManager, IRuntimeManager, @@ -109,4 +110,11 @@ public interface IGitblit extends IManager, */ ITicketService getTicketService(); + /** + * Returns the SSH public key manager. + * + * @return the SSH public key manager + */ + IPublicKeyManager getPublicKeyManager(); + } \ No newline at end of file diff --git a/src/main/java/com/gitblit/servlet/GitblitContext.java b/src/main/java/com/gitblit/servlet/GitblitContext.java index d4ec9671..cf8bba01 100644 --- a/src/main/java/com/gitblit/servlet/GitblitContext.java +++ b/src/main/java/com/gitblit/servlet/GitblitContext.java @@ -47,6 +47,7 @@ import com.gitblit.manager.IProjectManager; import com.gitblit.manager.IRepositoryManager; import com.gitblit.manager.IRuntimeManager; import com.gitblit.manager.IUserManager; +import com.gitblit.transport.ssh.IPublicKeyManager; import com.gitblit.utils.ContainerUtils; import com.gitblit.utils.StringUtils; @@ -149,7 +150,7 @@ public class GitblitContext extends DaggerContext { String contextRealPath = context.getRealPath("/"); File contextFolder = (contextRealPath != null) ? new File(contextRealPath) : null; - // if the base folder dosen't match the default assume they don't want to use express, + // if the base folder dosen't match the default assume they don't want to use express, // this allows for other containers to customise the basefolder per context. String defaultBase = Constants.contextFolder$ + "/WEB-INF/data"; String base = lookupBaseFolderFromJndi(); @@ -178,6 +179,7 @@ public class GitblitContext extends DaggerContext { startManager(injector, INotificationManager.class); startManager(injector, IUserManager.class); startManager(injector, IAuthenticationManager.class); + startManager(injector, IPublicKeyManager.class); startManager(injector, IRepositoryManager.class); startManager(injector, IProjectManager.class); startManager(injector, IFederationManager.class); diff --git a/src/main/java/com/gitblit/transport/ssh/CachingPublicKeyAuthenticator.java b/src/main/java/com/gitblit/transport/ssh/CachingPublicKeyAuthenticator.java index 7d6066c7..0120fa65 100644 --- a/src/main/java/com/gitblit/transport/ssh/CachingPublicKeyAuthenticator.java +++ b/src/main/java/com/gitblit/transport/ssh/CachingPublicKeyAuthenticator.java @@ -34,23 +34,23 @@ import com.gitblit.models.UserModel; import com.google.common.base.Preconditions; /** - * + * * @author Eric Myrhe - * + * */ public class CachingPublicKeyAuthenticator implements PublickeyAuthenticator, SessionListener { protected final Logger log = LoggerFactory.getLogger(getClass()); - protected final IKeyManager keyManager; + protected final IPublicKeyManager keyManager; protected final IAuthenticationManager authManager; private final Map> cache = new ConcurrentHashMap>(); - public CachingPublicKeyAuthenticator(IKeyManager keyManager, + public CachingPublicKeyAuthenticator(IPublicKeyManager keyManager, IAuthenticationManager authManager) { this.keyManager = keyManager; this.authManager = authManager; @@ -101,16 +101,15 @@ public class CachingPublicKeyAuthenticator implements PublickeyAuthenticator, return false; } - public IKeyManager getKeyManager() { - return keyManager; - } - + @Override public void sessionCreated(Session session) { } + @Override public void sessionEvent(Session sesssion, Event event) { } + @Override public void sessionClosed(Session session) { cache.remove(session); } diff --git a/src/main/java/com/gitblit/transport/ssh/FileKeyManager.java b/src/main/java/com/gitblit/transport/ssh/FileKeyManager.java index ae0bc9cf..defb4a3e 100644 --- a/src/main/java/com/gitblit/transport/ssh/FileKeyManager.java +++ b/src/main/java/com/gitblit/transport/ssh/FileKeyManager.java @@ -35,12 +35,12 @@ import com.google.common.base.Joiner; import com.google.common.io.Files; /** - * Manages SSH keys on the filesystem. + * Manages public keys on the filesystem. * * @author James Moger * */ -public class FileKeyManager extends IKeyManager { +public class FileKeyManager extends IPublicKeyManager { protected final IRuntimeManager runtimeManager; @@ -59,6 +59,7 @@ public class FileKeyManager extends IKeyManager { @Override public FileKeyManager start() { + log.info(toString()); return this; } diff --git a/src/main/java/com/gitblit/transport/ssh/IKeyManager.java b/src/main/java/com/gitblit/transport/ssh/IKeyManager.java deleted file mode 100644 index 12fce3df..00000000 --- a/src/main/java/com/gitblit/transport/ssh/IKeyManager.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * 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.transport.ssh; - -import java.security.PublicKey; -import java.text.MessageFormat; -import java.util.List; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.google.common.cache.CacheBuilder; -import com.google.common.cache.CacheLoader; -import com.google.common.cache.LoadingCache; - -/** - * - * @author James Moger - * - */ -public abstract class IKeyManager { - - protected final Logger log = LoggerFactory.getLogger(getClass()); - - protected final LoadingCache> keyCache = CacheBuilder - .newBuilder(). - expireAfterAccess(15, TimeUnit.MINUTES). - maximumSize(100) - .build(new CacheLoader>() { - @Override - public List load(String username) { - return getKeysImpl(username); - } - }); - - public abstract IKeyManager start(); - - public abstract boolean isReady(); - - public abstract IKeyManager stop(); - - public final List getKeys(String username) { - try { - if (isStale(username)) { - keyCache.invalidate(username); - } - return keyCache.get(username); - } catch (ExecutionException e) { - log.error(MessageFormat.format("failed to retrieve keys for {0}", username), e); - } - return null; - } - - protected abstract boolean isStale(String username); - - protected abstract List getKeysImpl(String username); - - public abstract boolean addKey(String username, String data); - - public abstract boolean removeKey(String username, String data); - - public abstract boolean removeAllKeys(String username); -} diff --git a/src/main/java/com/gitblit/transport/ssh/IPublicKeyManager.java b/src/main/java/com/gitblit/transport/ssh/IPublicKeyManager.java new file mode 100644 index 00000000..5857a599 --- /dev/null +++ b/src/main/java/com/gitblit/transport/ssh/IPublicKeyManager.java @@ -0,0 +1,82 @@ +/* + * 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.transport.ssh; + +import java.security.PublicKey; +import java.text.MessageFormat; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.gitblit.manager.IManager; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; + +/** + * Parent class for public key managers. + * + * @author James Moger + * + */ +public abstract class IPublicKeyManager implements IManager { + + protected final Logger log = LoggerFactory.getLogger(getClass()); + + protected final LoadingCache> keyCache = CacheBuilder + .newBuilder(). + expireAfterAccess(15, TimeUnit.MINUTES). + maximumSize(100) + .build(new CacheLoader>() { + @Override + public List load(String username) { + return getKeysImpl(username); + } + }); + + @Override + public abstract IPublicKeyManager start(); + + public abstract boolean isReady(); + + @Override + public abstract IPublicKeyManager stop(); + + public final List getKeys(String username) { + try { + if (isStale(username)) { + keyCache.invalidate(username); + } + return keyCache.get(username); + } catch (ExecutionException e) { + log.error(MessageFormat.format("failed to retrieve keys for {0}", username), e); + } + return null; + } + + protected abstract boolean isStale(String username); + + protected abstract List getKeysImpl(String username); + + public abstract boolean addKey(String username, String data); + + public abstract boolean removeKey(String username, String data); + + public abstract boolean removeAllKeys(String username); +} diff --git a/src/main/java/com/gitblit/transport/ssh/MemoryKeyManager.java b/src/main/java/com/gitblit/transport/ssh/MemoryKeyManager.java new file mode 100644 index 00000000..26bd021a --- /dev/null +++ b/src/main/java/com/gitblit/transport/ssh/MemoryKeyManager.java @@ -0,0 +1,98 @@ +/* + * 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.transport.ssh; + +import java.security.PublicKey; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Memory public key manager. + * + * @author James Moger + * + */ +public class MemoryKeyManager extends IPublicKeyManager { + + Map> keys; + + public MemoryKeyManager() { + keys = new HashMap>(); + } + + @Override + public String toString() { + return getClass().getSimpleName(); + } + + @Override + public MemoryKeyManager start() { + log.info(toString()); + return this; + } + + @Override + public boolean isReady() { + return true; + } + + @Override + public MemoryKeyManager stop() { + return this; + } + + @Override + protected boolean isStale(String username) { + return false; + } + + @Override + protected List getKeysImpl(String username) { + String id = username.toLowerCase(); + if (keys.containsKey(id)) { + return keys.get(id); + } + return null; + } + + @Override + public boolean addKey(String username, String data) { + return false; + } + + @Override + public boolean removeKey(String username, String data) { + return false; + } + + @Override + public boolean removeAllKeys(String username) { + String id = username.toLowerCase(); + keys.remove(id.toLowerCase()); + return true; + } + + /* Test method for populating the memory key manager */ + public void addKey(String username, PublicKey key) { + String id = username.toLowerCase(); + if (!keys.containsKey(id)) { + keys.put(id, new ArrayList()); + } + keys.get(id).add(key); + } +} diff --git a/src/main/java/com/gitblit/transport/ssh/NullKeyManager.java b/src/main/java/com/gitblit/transport/ssh/NullKeyManager.java index c76728d8..25860d6c 100644 --- a/src/main/java/com/gitblit/transport/ssh/NullKeyManager.java +++ b/src/main/java/com/gitblit/transport/ssh/NullKeyManager.java @@ -19,12 +19,12 @@ import java.security.PublicKey; import java.util.List; /** - * Rejects all SSH key management requests. + * Rejects all public key management requests. * * @author James Moger * */ -public class NullKeyManager extends IKeyManager { +public class NullKeyManager extends IPublicKeyManager { public NullKeyManager() { } @@ -36,6 +36,7 @@ public class NullKeyManager extends IKeyManager { @Override public NullKeyManager start() { + log.info(toString()); return this; } diff --git a/src/main/java/com/gitblit/transport/ssh/SshCommandFactory.java b/src/main/java/com/gitblit/transport/ssh/SshCommandFactory.java index de7aad1f..2b2093ea 100644 --- a/src/main/java/com/gitblit/transport/ssh/SshCommandFactory.java +++ b/src/main/java/com/gitblit/transport/ssh/SshCommandFactory.java @@ -52,14 +52,10 @@ public class SshCommandFactory implements CommandFactory { private static final Logger logger = LoggerFactory.getLogger(SshCommandFactory.class); private final IGitblit gitblit; - private final CachingPublicKeyAuthenticator keyAuthenticator; private final ScheduledExecutorService startExecutor; - public SshCommandFactory(IGitblit gitblit, - CachingPublicKeyAuthenticator keyAuthenticator, - IdGenerator idGenerator) { + public SshCommandFactory(IGitblit gitblit, IdGenerator idGenerator) { this.gitblit = gitblit; - this.keyAuthenticator = keyAuthenticator; int threads = 2;// cfg.getInt("sshd","commandStartThreads", 2); WorkQueue workQueue = new WorkQueue(idGenerator); @@ -84,8 +80,6 @@ public class SshCommandFactory implements CommandFactory { root.registerDispatcher(user, GitblitDispatchCommand.class); root.registerDispatcher(user, GitDispatchCommand.class); - root.setAuthenticator(keyAuthenticator); - return root; } diff --git a/src/main/java/com/gitblit/transport/ssh/SshDaemon.java b/src/main/java/com/gitblit/transport/ssh/SshDaemon.java index b6c5d680..da9a3726 100644 --- a/src/main/java/com/gitblit/transport/ssh/SshDaemon.java +++ b/src/main/java/com/gitblit/transport/ssh/SshDaemon.java @@ -21,8 +21,6 @@ import java.net.InetSocketAddress; import java.text.MessageFormat; import java.util.concurrent.atomic.AtomicBoolean; -import javax.inject.Singleton; - import org.apache.sshd.SshServer; import org.apache.sshd.common.io.IoServiceFactoryFactory; import org.apache.sshd.common.io.mina.MinaServiceFactoryFactory; @@ -35,15 +33,10 @@ import org.slf4j.LoggerFactory; import com.gitblit.Constants; 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; -import dagger.Module; -import dagger.ObjectGraph; -import dagger.Provides; - /** * Manager for the ssh transport. Roughly analogous to the * {@link com.gitblit.transport.git.GitDaemon} class. @@ -73,7 +66,6 @@ public class SshDaemon { private final IGitblit gitblit; private final SshServer sshd; - private final ObjectGraph injector; /** * Construct the Gitblit SSH daemon. @@ -82,15 +74,12 @@ public class SshDaemon { */ public SshDaemon(IGitblit gitblit, IdGenerator idGenerator) { this.gitblit = gitblit; - this.injector = ObjectGraph.create(new SshModule()); IStoredSettings settings = gitblit.getSettings(); int port = settings.getInteger(Keys.git.sshPort, 0); String bindInterface = settings.getString(Keys.git.sshBindInterface, "localhost"); - IKeyManager keyManager = getKeyManager(); - String sshBackendStr = settings.getString(Keys.git.sshBackend, SshSessionBackend.NIO2.name()); SshSessionBackend backend = SshSessionBackend.valueOf(sshBackendStr); @@ -108,7 +97,7 @@ public class SshDaemon { File hostKeyStore = new File(gitblit.getBaseFolder(), HOST_KEY_STORE); CachingPublicKeyAuthenticator keyAuthenticator = - getPublicKeyAuthenticator(keyManager, gitblit); + new CachingPublicKeyAuthenticator(gitblit.getPublicKeyManager(), gitblit); sshd = SshServer.setUpDefaultServer(); sshd.setPort(addr.getPort()); @@ -119,7 +108,7 @@ public class SshDaemon { sshd.setSessionFactory(new SshServerSessionFactory()); sshd.setFileSystemFactory(new DisabledFilesystemFactory()); sshd.setTcpipForwardingFilter(new NonForwardingFilter()); - sshd.setCommandFactory(new SshCommandFactory(gitblit, keyAuthenticator, idGenerator)); + sshd.setCommandFactory(new SshCommandFactory(gitblit, idGenerator)); sshd.setShellFactory(new WelcomeShell(settings)); String version = Constants.getGitBlitVersion() + " (" + sshd.getVersion() + ")"; @@ -128,27 +117,6 @@ public class SshDaemon { 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 authClass = - (Class) 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) { if (sshd.getPort() == DEFAULT_PORT) { // standard port @@ -203,75 +171,4 @@ public class SshDaemon { } } } - - @SuppressWarnings("unchecked") - protected IKeyManager getKeyManager() { - 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 managerClass = (Class) 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; - } - - @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 managerClass = (Class) 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. - * - * @author James Moger - * - */ - @Module( - library = true, - injects = { - NullKeyManager.class, - FileKeyManager.class - } - ) - class SshModule { - - @Provides @Singleton NullKeyManager provideNullKeyManager() { - return new NullKeyManager(); - } - - @Provides @Singleton FileKeyManager provideFileKeyManager() { - return new FileKeyManager(SshDaemon.this.gitblit); - } - } } 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 38f1a48f..dd581f4d 100644 --- a/src/main/java/com/gitblit/transport/ssh/commands/DispatchCommand.java +++ b/src/main/java/com/gitblit/transport/ssh/commands/DispatchCommand.java @@ -32,8 +32,6 @@ import org.slf4j.LoggerFactory; import com.gitblit.models.UserModel; import com.gitblit.transport.ssh.CommandMetaData; -import com.gitblit.transport.ssh.CachingPublicKeyAuthenticator; -import com.gitblit.transport.ssh.gitblit.BaseKeyCommand; import com.gitblit.utils.StringUtils; import com.gitblit.utils.cli.SubcommandHandler; import com.google.common.base.Charsets; @@ -252,16 +250,5 @@ public abstract class DispatchCommand extends BaseCommand { cmd.setOutputStream(out); cmd.setErrorStream(err); cmd.setExitCallback(exit); - - if (cmd instanceof BaseKeyCommand) { - BaseKeyCommand k = (BaseKeyCommand) cmd; - k.setAuthenticator(authenticator); - } - } - - private CachingPublicKeyAuthenticator authenticator; - - public void setAuthenticator(CachingPublicKeyAuthenticator authenticator) { - this.authenticator = authenticator; } } diff --git a/src/main/java/com/gitblit/transport/ssh/gitblit/BaseKeyCommand.java b/src/main/java/com/gitblit/transport/ssh/gitblit/BaseKeyCommand.java index 09099578..1b7bac11 100644 --- a/src/main/java/com/gitblit/transport/ssh/gitblit/BaseKeyCommand.java +++ b/src/main/java/com/gitblit/transport/ssh/gitblit/BaseKeyCommand.java @@ -21,8 +21,7 @@ import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; import java.util.List; -import com.gitblit.transport.ssh.IKeyManager; -import com.gitblit.transport.ssh.CachingPublicKeyAuthenticator; +import com.gitblit.transport.ssh.IPublicKeyManager; import com.gitblit.transport.ssh.commands.SshCommand; import com.google.common.base.Charsets; @@ -53,12 +52,7 @@ abstract class BaseKeyCommand extends SshCommand { return sshKeys; } - protected CachingPublicKeyAuthenticator authenticator; - public void setAuthenticator(CachingPublicKeyAuthenticator authenticator) { - this.authenticator = authenticator; - } - - protected IKeyManager getKeyManager() { - return authenticator.getKeyManager(); + protected IPublicKeyManager getKeyManager() { + return ctx.getGitblit().getPublicKeyManager(); } } diff --git a/src/main/java/com/gitblit/wicket/GitBlitWebApp.java b/src/main/java/com/gitblit/wicket/GitBlitWebApp.java index 445335ff..6e8aa05f 100644 --- a/src/main/java/com/gitblit/wicket/GitBlitWebApp.java +++ b/src/main/java/com/gitblit/wicket/GitBlitWebApp.java @@ -39,6 +39,7 @@ import com.gitblit.manager.IRepositoryManager; import com.gitblit.manager.IRuntimeManager; import com.gitblit.manager.IUserManager; import com.gitblit.tickets.ITicketService; +import com.gitblit.transport.ssh.IPublicKeyManager; import com.gitblit.utils.StringUtils; import com.gitblit.wicket.pages.ActivityPage; import com.gitblit.wicket.pages.BlamePage; @@ -95,6 +96,8 @@ public class GitBlitWebApp extends WebApplication { private final IAuthenticationManager authenticationManager; + private final IPublicKeyManager publicKeyManager; + private final IRepositoryManager repositoryManager; private final IProjectManager projectManager; @@ -108,6 +111,7 @@ public class GitBlitWebApp extends WebApplication { INotificationManager notificationManager, IUserManager userManager, IAuthenticationManager authenticationManager, + IPublicKeyManager publicKeyManager, IRepositoryManager repositoryManager, IProjectManager projectManager, IFederationManager federationManager, @@ -119,6 +123,7 @@ public class GitBlitWebApp extends WebApplication { this.notificationManager = notificationManager; this.userManager = userManager; this.authenticationManager = authenticationManager; + this.publicKeyManager = publicKeyManager; this.repositoryManager = repositoryManager; this.projectManager = projectManager; this.federationManager = federationManager; @@ -280,6 +285,10 @@ public class GitBlitWebApp extends WebApplication { return authenticationManager; } + public IPublicKeyManager keys() { + return publicKeyManager; + } + public IRepositoryManager repositories() { return repositoryManager; } diff --git a/src/test/config/test-gitblit.properties b/src/test/config/test-gitblit.properties index 7d8e9a79..1a52eaf4 100644 --- a/src/test/config/test-gitblit.properties +++ b/src/test/config/test-gitblit.properties @@ -8,7 +8,7 @@ git.searchRepositoriesSubfolders = true git.enableGitServlet = true git.daemonPort = 8300 git.sshPort = 29418 -git.sshPublicKeyAuthenticator = com.gitblit.tests.BogusPublicKeyAuthenticator +git.sshKeysManager = com.gitblit.transport.ssh.MemoryKeyManager 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 deleted file mode 100644 index 80be1a01..00000000 --- a/src/test/java/com/gitblit/tests/BogusPublicKeyAuthenticator.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * 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 17d609e7..b8d3b181 100644 --- a/src/test/java/com/gitblit/tests/GitBlitSuite.java +++ b/src/test/java/com/gitblit/tests/GitBlitSuite.java @@ -61,7 +61,7 @@ import com.gitblit.utils.JGitUtils; 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, SshDaemonTest.class, + 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 }) @@ -78,20 +78,12 @@ public class GitBlitSuite { 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(); -// } -// } + static int sshPort = 39418; public static String url = "http://localhost:" + port; public static String gitServletUrl = "http://localhost:" + port + "/git"; public static String gitDaemonUrl = "git://localhost:" + gitPort; + public static String sshDaemonUrl = "ssh://admin@localhost:" + sshPort; public static String account = "admin"; public static String password = "admin"; @@ -149,9 +141,7 @@ public class GitBlitSuite { "" + shutdownPort, "--gitPort", "" + gitPort, "--repositoriesFolder", "\"" + GitBlitSuite.REPOSITORIES.getAbsolutePath() + "\"", "--userService", GitBlitSuite.USERSCONF.getAbsolutePath(), "--settings", GitBlitSuite.SETTINGS.getAbsolutePath(), - "--baseFolder", "data"); - // doesn't work - //, "--sshPort", "" + sshPort); + "--baseFolder", "data", "--sshPort", "" + sshPort); } }); diff --git a/src/test/java/com/gitblit/tests/SshDaemonTest.java b/src/test/java/com/gitblit/tests/SshDaemonTest.java index 5294f691..45d31c29 100644 --- a/src/test/java/com/gitblit/tests/SshDaemonTest.java +++ b/src/test/java/com/gitblit/tests/SshDaemonTest.java @@ -26,11 +26,15 @@ 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.junit.Test; import com.gitblit.Constants; +import com.gitblit.transport.ssh.IPublicKeyManager; +import com.gitblit.transport.ssh.MemoryKeyManager; public class SshDaemonTest extends GitblitUnitTest { @@ -50,6 +54,27 @@ public class SshDaemonTest extends GitblitUnitTest { } } + 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", pair.getPublic()); + } + + @After + public void tearDown() { + MemoryKeyManager keyMgr = getKeyManager(); + keyMgr.removeAllKeys("admin"); + } + @Test public void testPublicKeyAuthentication() throws Exception { SshClient client = SshClient.setUpDefaultClient(); -- cgit v1.2.3 From bcc8a015ae552726742b4f437b2cb9e809270f96 Mon Sep 17 00:00:00 2001 From: James Moger Date: Sat, 22 Mar 2014 10:38:06 -0400 Subject: Handle ssh keys as objects, not strings, and improve the ls and rm key commands "gitblit keys ls" now defaults to showing an indexed list of fingerprints which almost matches the output of "sshadd -l". The indexes are useful specifying key(s) to remove using "gitblit keys rm ". This is an important improvement for key management. --- .../com/gitblit/manager/AuthenticationManager.java | 12 +- .../java/com/gitblit/manager/GitblitManager.java | 4 +- .../gitblit/manager/IAuthenticationManager.java | 7 +- .../ssh/CachingPublicKeyAuthenticator.java | 4 +- .../com/gitblit/transport/ssh/FileKeyManager.java | 28 ++-- .../gitblit/transport/ssh/IPublicKeyManager.java | 17 ++- .../gitblit/transport/ssh/MemoryKeyManager.java | 32 ++--- .../com/gitblit/transport/ssh/NullKeyManager.java | 7 +- .../java/com/gitblit/transport/ssh/SshKey.java | 156 +++++++++++++++++++++ .../transport/ssh/gitblit/BaseKeyCommand.java | 9 ++ .../transport/ssh/gitblit/KeysDispatcher.java | 88 ++++++++---- .../transport/ssh/gitblit/SetAccountCommand.java | 13 +- src/test/java/com/gitblit/tests/SshDaemonTest.java | 3 +- 13 files changed, 286 insertions(+), 94 deletions(-) create mode 100644 src/main/java/com/gitblit/transport/ssh/SshKey.java (limited to 'src/test/java') diff --git a/src/main/java/com/gitblit/manager/AuthenticationManager.java b/src/main/java/com/gitblit/manager/AuthenticationManager.java index 10f8fd11..d1b1af0a 100644 --- a/src/main/java/com/gitblit/manager/AuthenticationManager.java +++ b/src/main/java/com/gitblit/manager/AuthenticationManager.java @@ -17,7 +17,6 @@ package com.gitblit.manager; import java.nio.charset.Charset; import java.security.Principal; -import java.security.PublicKey; import java.text.MessageFormat; import java.util.ArrayList; import java.util.HashMap; @@ -48,6 +47,7 @@ import com.gitblit.auth.SalesforceAuthProvider; import com.gitblit.auth.WindowsAuthProvider; import com.gitblit.models.TeamModel; import com.gitblit.models.UserModel; +import com.gitblit.transport.ssh.SshKey; import com.gitblit.utils.Base64; import com.gitblit.utils.HttpUtils; import com.gitblit.utils.StringUtils; @@ -160,7 +160,7 @@ public class AuthenticationManager implements IAuthenticationManager { } return this; } - + public void addAuthenticationProvider(AuthenticationProvider prov) { authenticationProviders.add(prov); } @@ -301,7 +301,7 @@ public class AuthenticationManager implements IAuthenticationManager { * @return a user object or null */ @Override - public UserModel authenticate(String username, PublicKey key) { + public UserModel authenticate(String username, SshKey key) { if (username != null) { if (!StringUtils.isEmpty(username)) { UserModel user = userManager.getUserModel(username); @@ -391,14 +391,14 @@ public class AuthenticationManager implements IAuthenticationManager { } } } - + // could not authenticate locally or with a provider return null; } - + /** * Returns a UserModel if local authentication succeeds. - * + * * @param user * @param password * @return a UserModel if local authentication succeeds, null otherwise diff --git a/src/main/java/com/gitblit/manager/GitblitManager.java b/src/main/java/com/gitblit/manager/GitblitManager.java index cc670ea6..0001706c 100644 --- a/src/main/java/com/gitblit/manager/GitblitManager.java +++ b/src/main/java/com/gitblit/manager/GitblitManager.java @@ -22,7 +22,6 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.lang.reflect.Type; -import java.security.PublicKey; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collection; @@ -72,6 +71,7 @@ import com.gitblit.models.TeamModel; import com.gitblit.models.UserModel; import com.gitblit.tickets.ITicketService; import com.gitblit.transport.ssh.IPublicKeyManager; +import com.gitblit.transport.ssh.SshKey; import com.gitblit.utils.ArrayUtils; import com.gitblit.utils.HttpUtils; import com.gitblit.utils.JsonUtils; @@ -670,7 +670,7 @@ public class GitblitManager implements IGitblit { } @Override - public UserModel authenticate(String username, PublicKey key) { + public UserModel authenticate(String username, SshKey key) { return authenticationManager.authenticate(username, key); } diff --git a/src/main/java/com/gitblit/manager/IAuthenticationManager.java b/src/main/java/com/gitblit/manager/IAuthenticationManager.java index 4f43d928..33546d90 100644 --- a/src/main/java/com/gitblit/manager/IAuthenticationManager.java +++ b/src/main/java/com/gitblit/manager/IAuthenticationManager.java @@ -15,13 +15,12 @@ */ package com.gitblit.manager; -import java.security.PublicKey; - import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.gitblit.models.TeamModel; import com.gitblit.models.UserModel; +import com.gitblit.transport.ssh.SshKey; public interface IAuthenticationManager extends IManager { @@ -36,13 +35,13 @@ public interface IAuthenticationManager extends IManager { UserModel authenticate(HttpServletRequest httpRequest); /** - * Authenticate a user based on a public key. + * Authenticate a user based on a ssh public key. * * @param username * @param key * @return a user object or null */ - UserModel authenticate(String username, PublicKey key); + UserModel authenticate(String username, SshKey key); /** * Authenticate a user based on HTTP request parameters. diff --git a/src/main/java/com/gitblit/transport/ssh/CachingPublicKeyAuthenticator.java b/src/main/java/com/gitblit/transport/ssh/CachingPublicKeyAuthenticator.java index 295275c2..eb6f4b6c 100644 --- a/src/main/java/com/gitblit/transport/ssh/CachingPublicKeyAuthenticator.java +++ b/src/main/java/com/gitblit/transport/ssh/CachingPublicKeyAuthenticator.java @@ -78,14 +78,14 @@ public class CachingPublicKeyAuthenticator implements PublickeyAuthenticator, SshDaemonClient client = session.getAttribute(SshDaemonClient.KEY); Preconditions.checkState(client.getUser() == null); username = username.toLowerCase(Locale.US); - List keys = keyManager.getKeys(username); + List keys = keyManager.getKeys(username); if (keys == null || keys.isEmpty()) { log.info("{} has not added any public keys for ssh authentication", username); return false; } - for (PublicKey key : keys) { + for (SshKey key : keys) { if (key.equals(suppliedKey)) { UserModel user = authManager.authenticate(username, key); if (user != null) { diff --git a/src/main/java/com/gitblit/transport/ssh/FileKeyManager.java b/src/main/java/com/gitblit/transport/ssh/FileKeyManager.java index defb4a3e..8a3d2ff5 100644 --- a/src/main/java/com/gitblit/transport/ssh/FileKeyManager.java +++ b/src/main/java/com/gitblit/transport/ssh/FileKeyManager.java @@ -17,17 +17,12 @@ package com.gitblit.transport.ssh; import java.io.File; import java.io.IOException; -import java.security.PublicKey; import java.text.MessageFormat; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import org.apache.commons.codec.binary.Base64; -import org.apache.sshd.common.util.Buffer; -import org.eclipse.jgit.lib.Constants; - import com.gitblit.Keys; import com.gitblit.manager.IRuntimeManager; import com.google.common.base.Charsets; @@ -92,7 +87,7 @@ public class FileKeyManager extends IPublicKeyManager { } @Override - protected List getKeysImpl(String username) { + protected List getKeysImpl(String username) { try { log.info("loading keystore for {}", username); File keystore = getKeystore(username); @@ -100,7 +95,7 @@ public class FileKeyManager extends IPublicKeyManager { return null; } if (keystore.exists()) { - List list = new ArrayList(); + List list = new ArrayList(); for (String entry : Files.readLines(keystore, Charsets.ISO_8859_1)) { if (entry.trim().length() == 0) { // skip blanks @@ -110,9 +105,8 @@ public class FileKeyManager extends IPublicKeyManager { // skip comments continue; } - final String[] parts = entry.split(" "); - final byte[] bin = Base64.decodeBase64(Constants.encodeASCII(parts[1])); - list.add(new Buffer(bin).getRawPublicKey()); + SshKey key = new SshKey(entry); + list.add(key); } if (list.isEmpty()) { @@ -133,9 +127,9 @@ public class FileKeyManager extends IPublicKeyManager { * by disregarding the comment/description field during key comparisons. */ @Override - public boolean addKey(String username, String data) { + public boolean addKey(String username, SshKey key) { try { - String newKey = stripCommentFromKey(data); + String newKey = stripCommentFromKey(key.getRawData()); List lines = new ArrayList(); File keystore = getKeystore(username); @@ -162,7 +156,7 @@ public class FileKeyManager extends IPublicKeyManager { } // add new key - lines.add(data); + lines.add(key.getRawData()); // write keystore String content = Joiner.on("\n").join(lines).trim().concat("\n"); @@ -177,12 +171,12 @@ public class FileKeyManager extends IPublicKeyManager { } /** - * Removes a key from the keystore. + * Removes the specified key from the keystore. */ @Override - public boolean removeKey(String username, String data) { + public boolean removeKey(String username, SshKey key) { try { - String rmKey = stripCommentFromKey(data); + String rmKey = stripCommentFromKey(key.getRawData()); File keystore = getKeystore(username); if (keystore.exists()) { @@ -244,7 +238,7 @@ public class FileKeyManager extends IPublicKeyManager { /* Strips the comment from the key data and eliminates whitespace diffs */ protected String stripCommentFromKey(String data) { - String [] cols = data.split(" "); + String [] cols = data.split(" ", 3); String key = Joiner.on(" ").join(cols[0], cols[1]); return key; } diff --git a/src/main/java/com/gitblit/transport/ssh/IPublicKeyManager.java b/src/main/java/com/gitblit/transport/ssh/IPublicKeyManager.java index d2135142..956a76ef 100644 --- a/src/main/java/com/gitblit/transport/ssh/IPublicKeyManager.java +++ b/src/main/java/com/gitblit/transport/ssh/IPublicKeyManager.java @@ -15,7 +15,6 @@ */ package com.gitblit.transport.ssh; -import java.security.PublicKey; import java.text.MessageFormat; import java.util.List; import java.util.concurrent.ExecutionException; @@ -31,7 +30,7 @@ import com.google.common.cache.CacheLoader.InvalidCacheLoadException; import com.google.common.cache.LoadingCache; /** - * Parent class for public key managers. + * Parent class for ssh public key managers. * * @author James Moger * @@ -40,13 +39,13 @@ public abstract class IPublicKeyManager implements IManager { protected final Logger log = LoggerFactory.getLogger(getClass()); - protected final LoadingCache> keyCache = CacheBuilder + protected final LoadingCache> keyCache = CacheBuilder .newBuilder(). expireAfterAccess(15, TimeUnit.MINUTES). maximumSize(100) - .build(new CacheLoader>() { + .build(new CacheLoader>() { @Override - public List load(String username) { + public List load(String username) { return getKeysImpl(username); } }); @@ -59,7 +58,7 @@ public abstract class IPublicKeyManager implements IManager { @Override public abstract IPublicKeyManager stop(); - public final List getKeys(String username) { + public final List getKeys(String username) { try { if (isStale(username)) { keyCache.invalidate(username); @@ -77,11 +76,11 @@ public abstract class IPublicKeyManager implements IManager { protected abstract boolean isStale(String username); - protected abstract List getKeysImpl(String username); + protected abstract List getKeysImpl(String username); - public abstract boolean addKey(String username, String data); + public abstract boolean addKey(String username, SshKey key); - public abstract boolean removeKey(String username, String data); + public abstract boolean removeKey(String username, SshKey key); public abstract boolean removeAllKeys(String username); } diff --git a/src/main/java/com/gitblit/transport/ssh/MemoryKeyManager.java b/src/main/java/com/gitblit/transport/ssh/MemoryKeyManager.java index 26bd021a..18f9a4e1 100644 --- a/src/main/java/com/gitblit/transport/ssh/MemoryKeyManager.java +++ b/src/main/java/com/gitblit/transport/ssh/MemoryKeyManager.java @@ -15,7 +15,6 @@ */ package com.gitblit.transport.ssh; -import java.security.PublicKey; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -29,10 +28,10 @@ import java.util.Map; */ public class MemoryKeyManager extends IPublicKeyManager { - Map> keys; + Map> keys; public MemoryKeyManager() { - keys = new HashMap>(); + keys = new HashMap>(); } @Override @@ -62,7 +61,7 @@ public class MemoryKeyManager extends IPublicKeyManager { } @Override - protected List getKeysImpl(String username) { + protected List getKeysImpl(String username) { String id = username.toLowerCase(); if (keys.containsKey(id)) { return keys.get(id); @@ -71,13 +70,21 @@ public class MemoryKeyManager extends IPublicKeyManager { } @Override - public boolean addKey(String username, String data) { - return false; + public boolean addKey(String username, SshKey key) { + String id = username.toLowerCase(); + if (!keys.containsKey(id)) { + keys.put(id, new ArrayList()); + } + return keys.get(id).add(key); } @Override - public boolean removeKey(String username, String data) { - return false; + public boolean removeKey(String username, SshKey key) { + String id = username.toLowerCase(); + if (!keys.containsKey(id)) { + return false; + } + return keys.get(id).remove(key); } @Override @@ -86,13 +93,4 @@ public class MemoryKeyManager extends IPublicKeyManager { keys.remove(id.toLowerCase()); return true; } - - /* Test method for populating the memory key manager */ - public void addKey(String username, PublicKey key) { - String id = username.toLowerCase(); - if (!keys.containsKey(id)) { - keys.put(id, new ArrayList()); - } - keys.get(id).add(key); - } } diff --git a/src/main/java/com/gitblit/transport/ssh/NullKeyManager.java b/src/main/java/com/gitblit/transport/ssh/NullKeyManager.java index 25860d6c..0761d842 100644 --- a/src/main/java/com/gitblit/transport/ssh/NullKeyManager.java +++ b/src/main/java/com/gitblit/transport/ssh/NullKeyManager.java @@ -15,7 +15,6 @@ */ package com.gitblit.transport.ssh; -import java.security.PublicKey; import java.util.List; /** @@ -56,17 +55,17 @@ public class NullKeyManager extends IPublicKeyManager { } @Override - protected List getKeysImpl(String username) { + protected List getKeysImpl(String username) { return null; } @Override - public boolean addKey(String username, String data) { + public boolean addKey(String username, SshKey key) { return false; } @Override - public boolean removeKey(String username, String data) { + public boolean removeKey(String username, SshKey key) { return false; } diff --git a/src/main/java/com/gitblit/transport/ssh/SshKey.java b/src/main/java/com/gitblit/transport/ssh/SshKey.java new file mode 100644 index 00000000..cac6c417 --- /dev/null +++ b/src/main/java/com/gitblit/transport/ssh/SshKey.java @@ -0,0 +1,156 @@ +package com.gitblit.transport.ssh; + +import java.io.Serializable; +import java.security.PublicKey; + +import org.apache.commons.codec.binary.Base64; +import org.apache.sshd.common.SshException; +import org.apache.sshd.common.util.Buffer; +import org.eclipse.jgit.lib.Constants; + +import com.gitblit.utils.StringUtils; + +/** + * Class that encapsulates a public SSH key and it's metadata. + * + * @author James Moger + * + */ +public class SshKey implements Serializable { + + private static final long serialVersionUID = 1L; + + private String rawData; + + private PublicKey publicKey; + + private String comment; + + private String fingerprint; + + public SshKey(String data) { + this.rawData = data; + } + + public SshKey(PublicKey key) { + this.publicKey = key; + this.comment = ""; + } + + public PublicKey getPublicKey() { + if (publicKey == null && rawData != null) { + // instantiate the public key from the raw key data + final String[] parts = rawData.split(" ", 3); + if (comment == null && parts.length == 3) { + comment = parts[2]; + } + final byte[] bin = Base64.decodeBase64(Constants.encodeASCII(parts[1])); + try { + publicKey = new Buffer(bin).getRawPublicKey(); + } catch (SshException e) { + e.printStackTrace(); + } + } + return publicKey; + } + + public String getAlgorithm() { + return getPublicKey().getAlgorithm(); + } + + public String getComment() { + if (comment == null && rawData != null) { + // extract comment from the raw data + final String[] parts = rawData.split(" ", 3); + if (parts.length == 3) { + comment = parts[2]; + } + } + return comment; + } + + public void setComment(String comment) { + this.comment = comment; + } + + public String getRawData() { + if (rawData == null && publicKey != null) { + // build the raw data manually from the public key + Buffer buf = new Buffer(); + + // 1: identify the algorithm + buf.putRawPublicKey(publicKey); + String alg = buf.getString(); + + // 2: encode the key + buf.clear(); + buf.putPublicKey(publicKey); + String b64 = Base64.encodeBase64String(buf.getBytes()); + + String c = getComment(); + rawData = alg + " " + b64 + (StringUtils.isEmpty(c) ? "" : (" " + c)); + } + return rawData; + } + + public String getFingerprint() { + if (fingerprint == null) { + StringBuilder sb = new StringBuilder(); + // TODO append the keysize + int keySize = 0; + if (keySize > 0) { + sb.append(keySize).append(' '); + } + + // append the key hash as colon-separated pairs + String hash; + if (rawData != null) { + final String[] parts = rawData.split(" ", 3); + final byte [] bin = Base64.decodeBase64(Constants.encodeASCII(parts[1])); + hash = StringUtils.getMD5(bin); + } else { + // TODO get hash from publickey + hash = "todo"; + } + for (int i = 0; i < hash.length(); i += 2) { + sb.append(hash.charAt(i)).append(hash.charAt(i + 1)).append(':'); + } + sb.setLength(sb.length() - 1); + + // append the comment + String c = getComment(); + if (!StringUtils.isEmpty(c)) { + sb.append(' '); + sb.append(c); + } + + // append the algorithm + String alg = getAlgorithm(); + if (!StringUtils.isEmpty(alg)) { + sb.append(" (").append(alg).append(")"); + } + fingerprint = sb.toString(); + } + return fingerprint; + } + + @Override + public boolean equals(Object o) { + if (o instanceof PublicKey) { + return getPublicKey().equals(o); + } else if (o instanceof SshKey) { + return getPublicKey().equals(((SshKey) o).getPublicKey()); + } + return false; + } + + @Override + public int hashCode() { + return getPublicKey().hashCode(); + } + + @Override + public String toString() { + return getFingerprint(); + } +} diff --git a/src/main/java/com/gitblit/transport/ssh/gitblit/BaseKeyCommand.java b/src/main/java/com/gitblit/transport/ssh/gitblit/BaseKeyCommand.java index 23e1dfcc..55a87e4f 100644 --- a/src/main/java/com/gitblit/transport/ssh/gitblit/BaseKeyCommand.java +++ b/src/main/java/com/gitblit/transport/ssh/gitblit/BaseKeyCommand.java @@ -22,6 +22,7 @@ import java.io.UnsupportedEncodingException; import java.util.List; import com.gitblit.transport.ssh.IPublicKeyManager; +import com.gitblit.transport.ssh.SshKey; import com.gitblit.transport.ssh.commands.SshCommand; import com.google.common.base.Charsets; @@ -55,4 +56,12 @@ abstract class BaseKeyCommand extends SshCommand { protected IPublicKeyManager getKeyManager() { return getContext().getGitblit().getPublicKeyManager(); } + + protected SshKey parseKey(String rawData) throws UnloggedFailure { + if (rawData.contains("PRIVATE")) { + throw new UnloggedFailure(1, "Please provide a PUBLIC key, not a PRIVATE key!"); + } + SshKey key = new SshKey(rawData); + return key; + } } diff --git a/src/main/java/com/gitblit/transport/ssh/gitblit/KeysDispatcher.java b/src/main/java/com/gitblit/transport/ssh/gitblit/KeysDispatcher.java index 8c1bfd29..52fa875d 100644 --- a/src/main/java/com/gitblit/transport/ssh/gitblit/KeysDispatcher.java +++ b/src/main/java/com/gitblit/transport/ssh/gitblit/KeysDispatcher.java @@ -16,18 +16,17 @@ package com.gitblit.transport.ssh.gitblit; import java.io.IOException; -import java.security.PublicKey; import java.util.ArrayList; import java.util.List; -import org.apache.commons.codec.binary.Base64; -import org.apache.sshd.common.util.Buffer; import org.kohsuke.args4j.Argument; +import org.kohsuke.args4j.Option; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.gitblit.models.UserModel; import com.gitblit.transport.ssh.IPublicKeyManager; +import com.gitblit.transport.ssh.SshKey; import com.gitblit.transport.ssh.commands.CommandMetaData; import com.gitblit.transport.ssh.commands.DispatchCommand; import com.gitblit.transport.ssh.commands.SshCommand; @@ -61,7 +60,8 @@ public class KeysDispatcher extends DispatchCommand { String username = getContext().getClient().getUsername(); List keys = readKeys(addKeys); for (String key : keys) { - getKeyManager().addKey(username, key); + SshKey sshKey = parseKey(key); + getKeyManager().addKey(username, sshKey); log.info("added SSH public key for {}", username); } } @@ -74,51 +74,85 @@ public class KeysDispatcher extends DispatchCommand { private final String ALL = "ALL"; - @Argument(metaVar = "||ALL", usage = "the key to remove") + @Argument(metaVar = "-|||ALL", usage = "the key to remove", required = true) private List removeKeys = new ArrayList(); @Override public void run() throws IOException, UnloggedFailure { 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()) { + throw new UnloggedFailure(1, "There are no registered keys!"); + } + List keys = readKeys(removeKeys); if (keys.contains(ALL)) { - getKeyManager().removeAllKeys(username); - log.info("removed all SSH public keys from {}", username); + if (getKeyManager().removeAllKeys(username)) { + stdout.println("Removed all keys."); + log.info("removed all SSH public keys from {}", username); + } else { + log.warn("failed to remove all SSH public keys from {}", username); + } } else { for (String key : keys) { - getKeyManager().removeKey(username, key); - log.info("removed SSH public key from {}", username); + 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."); + } + throw new UnloggedFailure(1, String.format("Invalid index specified. There are %d registered keys.", keys.size())); + } + SshKey sshKey = currentKeys.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())); + } + } } } } } - @CommandMetaData(name = "list", aliases = { "ls" }, description = "List your public keys") + @CommandMetaData(name = "list", aliases = { "ls" }, description = "List your registered public keys") public static class ListKeys extends SshCommand { + @Option(name = "-L", usage = "list complete public key parameters") + private boolean showRaw; + @Override public void run() { IPublicKeyManager keyManager = getContext().getGitblit().getPublicKeyManager(); String username = getContext().getClient().getUsername(); - List keys = keyManager.getKeys(username); - if (keys == null) { - stdout.println(String.format("%s has not added any public keys for ssh authentication", username)); + List keys = keyManager.getKeys(username); + if (keys == null || keys.isEmpty()) { + stdout.println("You have not registered any public keys for ssh authentication."); return; } - for (PublicKey key : keys) { - // two-steps - perhaps this could be improved - Buffer buf = new Buffer(); - - // 1: identify the algorithm - buf.putRawPublicKey(key); - String alg = buf.getString(); - - // 2: encode the key - buf.clear(); - buf.putPublicKey(key); - String b64 = Base64.encodeBase64String(buf.getBytes()); - - stdout.println(alg + " " + b64); + for (int i = 0; i < keys.size(); i++) { + if (showRaw) { + // output in the same format as authorized_keys + stdout.println(keys.get(i).getRawData()); + } else { + // show 1-based index numbers with the fingerprint + // this is useful for comparing with "ssh-add -l" + stdout.println("#" + (i + 1) + ": " + keys.get(i).getFingerprint()); + } } } } diff --git a/src/main/java/com/gitblit/transport/ssh/gitblit/SetAccountCommand.java b/src/main/java/com/gitblit/transport/ssh/gitblit/SetAccountCommand.java index aebe3b1f..3f98778a 100644 --- a/src/main/java/com/gitblit/transport/ssh/gitblit/SetAccountCommand.java +++ b/src/main/java/com/gitblit/transport/ssh/gitblit/SetAccountCommand.java @@ -22,6 +22,7 @@ import java.util.List; import org.kohsuke.args4j.Argument; import org.kohsuke.args4j.Option; +import com.gitblit.transport.ssh.SshKey; import com.gitblit.transport.ssh.commands.CommandMetaData; /** Set a user's account settings. **/ @@ -66,18 +67,20 @@ public class SetAccountCommand extends BaseKeyCommand { } } - private void addSshKeys(List sshKeys) throws UnloggedFailure, + private void addSshKeys(List keys) throws UnloggedFailure, IOException { - for (String sshKey : sshKeys) { + for (String key : keys) { + SshKey sshKey = new SshKey(key); getKeyManager().addKey(user, sshKey); } } - private void deleteSshKeys(List sshKeys) { - if (sshKeys.contains(ALL)) { + private void deleteSshKeys(List keys) { + if (keys.contains(ALL)) { getKeyManager().removeAllKeys(user); } else { - for (String sshKey : sshKeys) { + for (String key : keys) { + SshKey sshKey = new SshKey(key); getKeyManager().removeKey(user, sshKey); } } diff --git a/src/test/java/com/gitblit/tests/SshDaemonTest.java b/src/test/java/com/gitblit/tests/SshDaemonTest.java index 45d31c29..dbd1d868 100644 --- a/src/test/java/com/gitblit/tests/SshDaemonTest.java +++ b/src/test/java/com/gitblit/tests/SshDaemonTest.java @@ -35,6 +35,7 @@ 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; public class SshDaemonTest extends GitblitUnitTest { @@ -66,7 +67,7 @@ public class SshDaemonTest extends GitblitUnitTest { @Before public void prepare() { MemoryKeyManager keyMgr = getKeyManager(); - keyMgr.addKey("admin", pair.getPublic()); + keyMgr.addKey("admin", new SshKey(pair.getPublic())); } @After -- cgit v1.2.3 From 413e9b486b1a84960d4c8ddac130e87280f64c6a Mon Sep 17 00:00:00 2001 From: James Moger Date: Sun, 30 Mar 2014 14:21:19 -0400 Subject: Split administration commands into a plugin, enhance plugin manager --- build.xml | 42 +- .../java/com/gitblit/manager/GitblitManager.java | 52 ++ .../java/com/gitblit/manager/IPluginManager.java | 23 +- .../java/com/gitblit/manager/PluginManager.java | 32 +- .../com/gitblit/transport/ssh/WelcomeShell.java | 2 +- .../transport/ssh/commands/PluginDispatcher.java | 293 ++++++++++ .../transport/ssh/commands/RootDispatcher.java | 6 +- .../transport/ssh/commands/VersionCommand.java | 28 + .../transport/ssh/gitblit/BaseKeyCommand.java | 67 --- .../transport/ssh/gitblit/ConfigCommand.java | 174 ------ .../transport/ssh/gitblit/GitblitDispatcher.java | 40 -- .../transport/ssh/gitblit/KeysDispatcher.java | 252 --------- .../transport/ssh/gitblit/ListDispatcher.java | 58 -- .../transport/ssh/gitblit/ProjectsDispatcher.java | 94 ---- .../ssh/gitblit/RepositoriesDispatcher.java | 532 ------------------ .../transport/ssh/gitblit/ReviewCommand.java | 89 ---- .../transport/ssh/gitblit/TeamsDispatcher.java | 507 ------------------ .../transport/ssh/gitblit/TicketsDispatcher.java | 157 ------ .../transport/ssh/gitblit/UsersDispatcher.java | 592 --------------------- .../transport/ssh/gitblit/VersionCommand.java | 30 -- .../gitblit/transport/ssh/keys/BaseKeyCommand.java | 67 +++ .../gitblit/transport/ssh/keys/KeysDispatcher.java | 252 +++++++++ src/site/setup_transport_ssh.mkd | 22 +- src/test/java/com/gitblit/tests/SshDaemonTest.java | 2 +- 24 files changed, 777 insertions(+), 2636 deletions(-) create mode 100644 src/main/java/com/gitblit/transport/ssh/commands/PluginDispatcher.java create mode 100644 src/main/java/com/gitblit/transport/ssh/commands/VersionCommand.java delete mode 100644 src/main/java/com/gitblit/transport/ssh/gitblit/BaseKeyCommand.java delete mode 100644 src/main/java/com/gitblit/transport/ssh/gitblit/ConfigCommand.java delete mode 100644 src/main/java/com/gitblit/transport/ssh/gitblit/GitblitDispatcher.java delete mode 100644 src/main/java/com/gitblit/transport/ssh/gitblit/KeysDispatcher.java delete mode 100644 src/main/java/com/gitblit/transport/ssh/gitblit/ListDispatcher.java delete mode 100644 src/main/java/com/gitblit/transport/ssh/gitblit/ProjectsDispatcher.java delete mode 100644 src/main/java/com/gitblit/transport/ssh/gitblit/RepositoriesDispatcher.java delete mode 100644 src/main/java/com/gitblit/transport/ssh/gitblit/ReviewCommand.java delete mode 100644 src/main/java/com/gitblit/transport/ssh/gitblit/TeamsDispatcher.java delete mode 100644 src/main/java/com/gitblit/transport/ssh/gitblit/TicketsDispatcher.java delete mode 100644 src/main/java/com/gitblit/transport/ssh/gitblit/UsersDispatcher.java delete mode 100644 src/main/java/com/gitblit/transport/ssh/gitblit/VersionCommand.java create mode 100644 src/main/java/com/gitblit/transport/ssh/keys/BaseKeyCommand.java create mode 100644 src/main/java/com/gitblit/transport/ssh/keys/KeysDispatcher.java (limited to 'src/test/java') diff --git a/build.xml b/build.xml index a802c827..0baf090e 100644 --- a/build.xml +++ b/build.xml @@ -569,14 +569,14 @@ - - - - + + + + - - - + + + @@ -888,14 +888,14 @@ - - - - - + + + + + - - + + @@ -1046,5 +1046,19 @@ + + + + + + + + + + diff --git a/src/main/java/com/gitblit/manager/GitblitManager.java b/src/main/java/com/gitblit/manager/GitblitManager.java index 0001706c..6b1cc8a5 100644 --- a/src/main/java/com/gitblit/manager/GitblitManager.java +++ b/src/main/java/com/gitblit/manager/GitblitManager.java @@ -42,7 +42,9 @@ import org.eclipse.jgit.transport.RefSpec; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import ro.fortsoft.pf4j.PluginClassLoader; import ro.fortsoft.pf4j.PluginWrapper; +import ro.fortsoft.pf4j.RuntimeMode; import com.gitblit.Constants; import com.gitblit.Constants.AccessPermission; @@ -1187,4 +1189,54 @@ public class GitblitManager implements IGitblit { public PluginWrapper whichPlugin(Class clazz) { return pluginManager.whichPlugin(clazz); } + + @Override + public boolean deletePlugin(PluginWrapper wrapper) { + return pluginManager.deletePlugin(wrapper); + } + + @Override + public List getPlugins() { + return pluginManager.getPlugins(); + } + + @Override + public List getResolvedPlugins() { + return pluginManager.getResolvedPlugins(); + } + + @Override + public List getUnresolvedPlugins() { + return pluginManager.getUnresolvedPlugins(); + } + + @Override + public List getStartedPlugins() { + return pluginManager.getStartedPlugins(); + } + + @Override + public void loadPlugins() { + pluginManager.loadPlugins(); + } + + @Override + public void startPlugins() { + pluginManager.startPlugins(); + } + + @Override + public void stopPlugins() { + pluginManager.stopPlugins(); + } + + @Override + public PluginClassLoader getPluginClassLoader(String pluginId) { + return pluginManager.getPluginClassLoader(pluginId); + } + + @Override + public RuntimeMode getRuntimeMode() { + return pluginManager.getRuntimeMode(); + } } diff --git a/src/main/java/com/gitblit/manager/IPluginManager.java b/src/main/java/com/gitblit/manager/IPluginManager.java index 670e9769..11b81ea3 100644 --- a/src/main/java/com/gitblit/manager/IPluginManager.java +++ b/src/main/java/com/gitblit/manager/IPluginManager.java @@ -15,19 +15,10 @@ */ package com.gitblit.manager; -import java.util.List; - +import ro.fortsoft.pf4j.PluginManager; import ro.fortsoft.pf4j.PluginWrapper; -public interface IPluginManager extends IManager { - - /** - * Retrieves the extension for given class 'clazz'. - * - * @param clazz extension point class to retrieve extension for - * @return list of extensions - */ - public List getExtensions(Class clazz); +public interface IPluginManager extends IManager, PluginManager { /** * Retrieves the {@link PluginWrapper} that loaded the given class 'clazz'. @@ -35,5 +26,13 @@ public interface IPluginManager extends IManager { * @param clazz extension point class to retrieve extension for * @return PluginWrapper that loaded the given class */ - public PluginWrapper whichPlugin(Class clazz); + PluginWrapper whichPlugin(Class clazz); + + /** + * Delete the plugin represented by {@link PluginWrapper}. + * + * @param wrapper + * @return true if successful + */ + boolean deletePlugin(PluginWrapper wrapper); } diff --git a/src/main/java/com/gitblit/manager/PluginManager.java b/src/main/java/com/gitblit/manager/PluginManager.java index 5eb00e92..e23aaec0 100644 --- a/src/main/java/com/gitblit/manager/PluginManager.java +++ b/src/main/java/com/gitblit/manager/PluginManager.java @@ -15,12 +15,16 @@ */ package com.gitblit.manager; +import java.io.File; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ro.fortsoft.pf4j.DefaultPluginManager; +import ro.fortsoft.pf4j.PluginWrapper; import com.gitblit.Keys; +import com.gitblit.utils.FileUtils; /** * The plugin manager maintains the lifecycle of plugins. It is exposed as @@ -30,27 +34,45 @@ import com.gitblit.Keys; * @author David Ostrovsky * */ -public class PluginManager extends DefaultPluginManager implements - IPluginManager { +public class PluginManager extends DefaultPluginManager implements IPluginManager { private final Logger logger = LoggerFactory.getLogger(getClass()); + + private final IRuntimeManager runtimeManager; public PluginManager(IRuntimeManager runtimeManager) { - super(runtimeManager.getFileOrFolder(Keys.plugins.folder, - "${baseFolder}/plugins")); + super(runtimeManager.getFileOrFolder(Keys.plugins.folder, "${baseFolder}/plugins")); + this.runtimeManager = runtimeManager; } @Override public PluginManager start() { - logger.info("Plugin manager started"); + logger.info("Loading plugins..."); loadPlugins(); + logger.info("Starting loaded plugins..."); startPlugins(); return this; } @Override public PluginManager stop() { + logger.info("Stopping loaded plugins..."); stopPlugins(); return null; } + + @Override + public boolean deletePlugin(PluginWrapper pw) { + File folder = runtimeManager.getFileOrFolder(Keys.plugins.folder, "${baseFolder}/plugins"); + File pluginFolder = new File(folder, pw.getPluginPath()); + File pluginZip = new File(folder, pw.getPluginPath() + ".zip"); + + if (pluginFolder.exists()) { + FileUtils.delete(pluginFolder); + } + if (pluginZip.exists()) { + FileUtils.delete(pluginZip); + } + return true; + } } diff --git a/src/main/java/com/gitblit/transport/ssh/WelcomeShell.java b/src/main/java/com/gitblit/transport/ssh/WelcomeShell.java index 6809ba62..4341a3ea 100644 --- a/src/main/java/com/gitblit/transport/ssh/WelcomeShell.java +++ b/src/main/java/com/gitblit/transport/ssh/WelcomeShell.java @@ -165,7 +165,7 @@ public class WelcomeShell implements Factory { msg.append(nl); msg.append(nl); - msg.append(String.format(" cat ~/.ssh/id_rsa.pub | ssh -l %s -p %d %s gitblit keys add", user.username, port, hostname)); + msg.append(String.format(" cat ~/.ssh/id_rsa.pub | ssh -l %s -p %d %s keys add", user.username, port, hostname)); msg.append(nl); msg.append(nl); diff --git a/src/main/java/com/gitblit/transport/ssh/commands/PluginDispatcher.java b/src/main/java/com/gitblit/transport/ssh/commands/PluginDispatcher.java new file mode 100644 index 00000000..5c413db2 --- /dev/null +++ b/src/main/java/com/gitblit/transport/ssh/commands/PluginDispatcher.java @@ -0,0 +1,293 @@ +/* + * 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.transport.ssh.commands; + +import java.util.ArrayList; +import java.util.List; + +import org.kohsuke.args4j.Argument; + +import ro.fortsoft.pf4j.PluginDependency; +import ro.fortsoft.pf4j.PluginDescriptor; +import ro.fortsoft.pf4j.PluginState; +import ro.fortsoft.pf4j.PluginWrapper; + +import com.gitblit.manager.IGitblit; +import com.gitblit.models.UserModel; +import com.gitblit.utils.FlipTable; +import com.gitblit.utils.FlipTable.Borders; + +/** + * The plugin dispatcher and commands for runtime plugin management. + * + * @author James Moger + * + */ +@CommandMetaData(name = "plugin", description = "Plugin management commands", admin = true) +public class PluginDispatcher extends DispatchCommand { + + @Override + protected void setup(UserModel user) { + register(user, ListPlugins.class); + register(user, StartPlugin.class); + register(user, StopPlugin.class); + register(user, ShowPlugin.class); + register(user, RemovePlugin.class); + register(user, UploadPlugin.class); + } + + @CommandMetaData(name = "list", aliases = { "ls" }, description = "List the loaded plugins") + public static class ListPlugins extends ListCommand { + + @Override + protected List getItems() throws UnloggedFailure { + IGitblit gitblit = getContext().getGitblit(); + List list = gitblit.getPlugins(); + return list; + } + + @Override + protected void asTable(List list) { + String[] headers; + if (verbose) { + String [] h = { "#", "Id", "Version", "State", "Mode", "Path", "Provider"}; + headers = h; + } else { + String [] h = { "#", "Id", "Version", "State", "Path"}; + headers = h; + } + Object[][] data = new Object[list.size()][]; + for (int i = 0; i < list.size(); i++) { + PluginWrapper p = list.get(i); + PluginDescriptor d = p.getDescriptor(); + if (verbose) { + data[i] = new Object[] { "" + (i + 1), d.getPluginId(), d.getVersion(), p.getPluginState(), p.getRuntimeMode(), p.getPluginPath(), d.getProvider() }; + } else { + data[i] = new Object[] { "" + (i + 1), d.getPluginId(), d.getVersion(), p.getPluginState(), p.getPluginPath() }; + } + } + + stdout.println(FlipTable.of(headers, data, Borders.BODY_HCOLS)); + } + + @Override + protected void asTabbed(List list) { + for (PluginWrapper pw : list) { + PluginDescriptor d = pw.getDescriptor(); + if (verbose) { + outTabbed(d.getPluginId(), d.getVersion(), pw.getPluginState(), pw.getRuntimeMode(), pw.getPluginPath(), d.getProvider()); + } else { + outTabbed(d.getPluginId(), d.getVersion(), pw.getPluginState(), pw.getPluginPath()); + } + } + } + } + + @CommandMetaData(name = "start", description = "Start a plugin") + public static class StartPlugin extends SshCommand { + + @Argument(index = 0, required = true, metaVar = "ALL|", usage = "the plugin to start") + protected String plugin; + + @Override + public void run() throws UnloggedFailure { + IGitblit gitblit = getContext().getGitblit(); + if (plugin.equalsIgnoreCase("ALL")) { + gitblit.startPlugins(); + stdout.println("All plugins started"); + } else { + try { + int index = Integer.parseInt(plugin); + List plugins = gitblit.getPlugins(); + if (index > plugins.size()) { + throw new UnloggedFailure(1, "Invalid plugin index specified!"); + } + PluginWrapper pw = plugins.get(index - 1); + start(pw); + } catch (NumberFormatException n) { + for (PluginWrapper pw : gitblit.getPlugins()) { + PluginDescriptor pd = pw.getDescriptor(); + if (pd.getPluginId().equalsIgnoreCase(plugin)) { + start(pw); + break; + } + } + } + } + } + + protected void start(PluginWrapper pw) throws UnloggedFailure { + String id = pw.getDescriptor().getPluginId(); + if (pw.getPluginState() == PluginState.STARTED) { + throw new UnloggedFailure(1, String.format("%s is already started.", id)); + } + try { + pw.getPlugin().start(); +// pw.setPluginState(PluginState.STARTED); + stdout.println(String.format("%s started", id)); + } catch (Exception pe) { + throw new UnloggedFailure(1, String.format("Failed to start %s", id), pe); + } + } + } + + + @CommandMetaData(name = "stop", description = "Stop a plugin") + public static class StopPlugin extends SshCommand { + + @Argument(index = 0, required = true, metaVar = "ALL|", usage = "the plugin to stop") + protected String plugin; + + @Override + public void run() throws UnloggedFailure { + IGitblit gitblit = getContext().getGitblit(); + if (plugin.equalsIgnoreCase("ALL")) { + gitblit.stopPlugins(); + stdout.println("All plugins stopped"); + } else { + try { + int index = Integer.parseInt(plugin); + List plugins = gitblit.getPlugins(); + if (index > plugins.size()) { + throw new UnloggedFailure(1, "Invalid plugin index specified!"); + } + PluginWrapper pw = plugins.get(index - 1); + stop(pw); + } catch (NumberFormatException n) { + for (PluginWrapper pw : gitblit.getPlugins()) { + PluginDescriptor pd = pw.getDescriptor(); + if (pd.getPluginId().equalsIgnoreCase(plugin)) { + stop(pw); + break; + } + } + } + } + } + + protected void stop(PluginWrapper pw) throws UnloggedFailure { + String id = pw.getDescriptor().getPluginId(); + if (pw.getPluginState() == PluginState.STOPPED) { + throw new UnloggedFailure(1, String.format("%s is already stopped.", id)); + } + try { + pw.getPlugin().stop(); +// pw.setPluginState(PluginState.STOPPED); + stdout.println(String.format("%s stopped", id)); + } catch (Exception pe) { + throw new UnloggedFailure(1, String.format("Failed to stop %s", id), pe); + } + } + } + + @CommandMetaData(name = "show", description = "Show the details of a plugin") + public static class ShowPlugin extends SshCommand { + + @Argument(index = 0, required = true, metaVar = "", usage = "the plugin to stop") + protected int index; + + @Override + public void run() throws UnloggedFailure { + IGitblit gitblit = getContext().getGitblit(); + List plugins = gitblit.getPlugins(); + if (index > plugins.size()) { + throw new UnloggedFailure(1, "Invalid plugin index specified!"); + } + PluginWrapper pw = plugins.get(index - 1); + PluginDescriptor d = pw.getDescriptor(); + + // FIELDS + StringBuilder sb = new StringBuilder(); + sb.append("Version : ").append(d.getVersion()).append('\n'); + sb.append("Provider : ").append(d.getProvider()).append('\n'); + sb.append("Path : ").append(pw.getPluginPath()).append('\n'); + sb.append("State : ").append(pw.getPluginState()).append('\n'); + final String fields = sb.toString(); + + // TODO EXTENSIONS + sb.setLength(0); + List exts = new ArrayList(); + String extensions; + if (exts.isEmpty()) { + extensions = FlipTable.EMPTY; + } else { + String[] headers = { "Id", "Version" }; + Object[][] data = new Object[exts.size()][]; + for (int i = 0; i < exts.size(); i++) { + String ext = exts.get(i); + data[0] = new Object[] { ext.toString(), ext.toString() }; + } + extensions = FlipTable.of(headers, data, Borders.COLS); + } + + // DEPENDENCIES + sb.setLength(0); + List deps = d.getDependencies(); + String dependencies; + if (deps.isEmpty()) { + dependencies = FlipTable.EMPTY; + } else { + String[] headers = { "Id", "Version" }; + Object[][] data = new Object[deps.size()][]; + for (int i = 0; i < deps.size(); i++) { + PluginDependency dep = deps.get(i); + data[0] = new Object[] { dep.getPluginId(), dep.getPluginVersion() }; + } + dependencies = FlipTable.of(headers, data, Borders.COLS); + } + + String[] headers = { d.getPluginId() }; + Object[][] data = new Object[5][]; + data[0] = new Object[] { fields }; + data[1] = new Object[] { "EXTENSIONS" }; + data[2] = new Object[] { extensions }; + data[3] = new Object[] { "DEPENDENCIES" }; + data[4] = new Object[] { dependencies }; + stdout.println(FlipTable.of(headers, data)); + } + } + + @CommandMetaData(name = "remove", aliases= { "rm", "del" }, description = "Remove a plugin", hidden = true) + public static class RemovePlugin extends SshCommand { + + @Argument(index = 0, required = true, metaVar = "", usage = "the plugin to stop") + protected int index; + + @Override + public void run() throws UnloggedFailure { + IGitblit gitblit = getContext().getGitblit(); + List plugins = gitblit.getPlugins(); + if (index > plugins.size()) { + throw new UnloggedFailure(1, "Invalid plugin index specified!"); + } + PluginWrapper pw = plugins.get(index - 1); + PluginDescriptor d = pw.getDescriptor(); + if (gitblit.deletePlugin(pw)) { + stdout.println(String.format("Deleted %s %s", d.getPluginId(), d.getVersion())); + } else { + throw new UnloggedFailure(1, String.format("Failed to delete %s %s", d.getPluginId(), d.getVersion())); + } + } + } + + @CommandMetaData(name = "receive", aliases= { "upload" }, description = "Upload a plugin to the server", hidden = true) + public static class UploadPlugin extends SshCommand { + + @Override + public void run() throws UnloggedFailure { + } + } +} diff --git a/src/main/java/com/gitblit/transport/ssh/commands/RootDispatcher.java b/src/main/java/com/gitblit/transport/ssh/commands/RootDispatcher.java index 8a871ebb..3c378669 100644 --- a/src/main/java/com/gitblit/transport/ssh/commands/RootDispatcher.java +++ b/src/main/java/com/gitblit/transport/ssh/commands/RootDispatcher.java @@ -24,7 +24,7 @@ import com.gitblit.manager.IGitblit; import com.gitblit.models.UserModel; import com.gitblit.transport.ssh.SshDaemonClient; import com.gitblit.transport.ssh.git.GitDispatcher; -import com.gitblit.transport.ssh.gitblit.GitblitDispatcher; +import com.gitblit.transport.ssh.keys.KeysDispatcher; /** * The root dispatcher is the dispatch command that handles registering all @@ -41,8 +41,10 @@ class RootDispatcher extends DispatchCommand { setContext(new SshCommandContext(gitblit, client, cmdLine)); UserModel user = client.getUser(); - register(user, GitblitDispatcher.class); + register(user, VersionCommand.class); register(user, GitDispatcher.class); + register(user, KeysDispatcher.class); + register(user, PluginDispatcher.class); List exts = gitblit.getExtensions(DispatchCommand.class); for (DispatchCommand ext : exts) { diff --git a/src/main/java/com/gitblit/transport/ssh/commands/VersionCommand.java b/src/main/java/com/gitblit/transport/ssh/commands/VersionCommand.java new file mode 100644 index 00000000..3a2fd5e2 --- /dev/null +++ b/src/main/java/com/gitblit/transport/ssh/commands/VersionCommand.java @@ -0,0 +1,28 @@ +/* + * 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.transport.ssh.commands; + +import com.gitblit.Constants; + +@CommandMetaData(name="version", description = "Display the Gitblit version") +public class VersionCommand extends SshCommand { + + @Override + public void run() { + stdout.println(Constants.getGitBlitVersion()); + } +} diff --git a/src/main/java/com/gitblit/transport/ssh/gitblit/BaseKeyCommand.java b/src/main/java/com/gitblit/transport/ssh/gitblit/BaseKeyCommand.java deleted file mode 100644 index 930c058f..00000000 --- a/src/main/java/com/gitblit/transport/ssh/gitblit/BaseKeyCommand.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * 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.transport.ssh.gitblit; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.UnsupportedEncodingException; -import java.util.List; - -import com.gitblit.transport.ssh.IPublicKeyManager; -import com.gitblit.transport.ssh.SshKey; -import com.gitblit.transport.ssh.commands.SshCommand; -import com.google.common.base.Charsets; - -/** - * - * Base class for commands that read SSH keys from stdin or a parameter list. - * - */ -abstract class BaseKeyCommand extends SshCommand { - - protected List readKeys(List sshKeys) - throws UnsupportedEncodingException, IOException { - int idx = -1; - if (sshKeys.isEmpty() || (idx = sshKeys.indexOf("-")) >= 0) { - String sshKey = ""; - BufferedReader br = new BufferedReader(new InputStreamReader( - in, Charsets.UTF_8)); - String line; - while ((line = br.readLine()) != null) { - sshKey += line + "\n"; - } - if (idx == -1) { - sshKeys.add(sshKey.trim()); - } else { - sshKeys.set(idx, sshKey.trim()); - } - } - return sshKeys; - } - - protected IPublicKeyManager getKeyManager() { - return getContext().getGitblit().getPublicKeyManager(); - } - - protected SshKey parseKey(String rawData) throws UnloggedFailure { - if (rawData.contains("PRIVATE")) { - throw new UnloggedFailure(1, "Please provide a PUBLIC key, not a PRIVATE key!"); - } - SshKey key = new SshKey(rawData); - return key; - } -} diff --git a/src/main/java/com/gitblit/transport/ssh/gitblit/ConfigCommand.java b/src/main/java/com/gitblit/transport/ssh/gitblit/ConfigCommand.java deleted file mode 100644 index f6740349..00000000 --- a/src/main/java/com/gitblit/transport/ssh/gitblit/ConfigCommand.java +++ /dev/null @@ -1,174 +0,0 @@ -package com.gitblit.transport.ssh.gitblit; - -import java.text.MessageFormat; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -import org.kohsuke.args4j.Argument; -import org.kohsuke.args4j.Option; -import org.parboiled.common.StringUtils; - -import com.gitblit.manager.IGitblit; -import com.gitblit.models.ServerSettings; -import com.gitblit.models.SettingModel; -import com.gitblit.transport.ssh.commands.CommandMetaData; -import com.gitblit.transport.ssh.commands.SshCommand; -import com.gitblit.transport.ssh.commands.UsageExample; -import com.gitblit.transport.ssh.commands.UsageExamples; -import com.google.common.collect.Maps; - -@CommandMetaData(name = "config", description = "Administer Gitblit settings", admin = true) -@UsageExamples(examples = { - @UsageExample(syntax = "${cmd} --list", description = "List all settings"), - @UsageExample(syntax = "${cmd} git.sshPort", description = "Describe the git.sshPort setting"), - @UsageExample(syntax = "${cmd} git.sshPort 29418", description = "Set git.sshPort to 29418"), - @UsageExample(syntax = "${cmd} git.sshPort --reset", description = "Reset git.sshPort to it's default value"), -}) -public class ConfigCommand extends SshCommand { - - @Argument(index = 0, metaVar = "KEY", usage = "The setting to describe or update") - protected String setting; - - @Argument(index = 1, metaVar = "VALUE", usage = "The new value for the setting") - protected String value; - - @Option(name = "--list", aliases = { "-l" }, usage = "List all settings") - private boolean listAll; - - @Option(name = "--modified", aliases = { "-m" }, usage = "List modified settings") - private boolean listModified; - - @Option(name = "--reset", usage = "Reset a setting to it's default value") - private boolean reset; - - @Override - public void run() throws UnloggedFailure { - IGitblit gitblit = getContext().getGitblit(); - ServerSettings settings = gitblit.getSettingsModel(); - - if (listAll || listModified) { - /* - * List settings - */ - List list = new ArrayList(); - int maxLen = 0; - for (String key : settings.getKeys()) { - SettingModel model = settings.get(key); - if (listModified) { - if (!model.isDefaultValue()) { - list.add(model); - } else { - continue; - } - } else { - list.add(model); - } - - if (key.length() > maxLen) { - maxLen = key.length(); - } - } - String pattern = MessageFormat.format("%s%-{0,number,0}s : %s", maxLen); - for (SettingModel model : list) { - stdout.println(String.format(pattern, - model.isDefaultValue() ? " " : "*", - model.name, - model.currentValue)); - } - } else if (!StringUtils.isEmpty(setting) && value == null && !reset) { - /* - * Describe a setting - */ - SettingModel model = settings.get(setting); - if (model == null) { - // unknown setting - String value = gitblit.getSettings().getString(setting, null); - if (value == null) { - // setting does not exist, can not describe - stdout.println(String.format("\"%s\" is not a valid setting.", setting)); - return; - } - - model = new SettingModel(); - model.defaultValue = ""; - model.currentValue = value; - } - stdout.println(); - stdout.println(model.name); - if (!StringUtils.isEmpty(model.since)) { - stdout.println(SettingModel.SINCE + " " + model.since); - } - if (model.restartRequired) { - stdout.println(SettingModel.RESTART_REQUIRED); - } - if (model.spaceDelimited) { - stdout.println(SettingModel.SPACE_DELIMITED); - } - if (!StringUtils.isEmpty(model.description)) { - stdout.println(); - stdout.println(model.description); - } - stdout.println(); - if (model.defaultValue != null) { - stdout.println("default: " + model.defaultValue); - } - if (!model.isDefaultValue()) { - stdout.println("current: " + model.currentValue); - } else { - stdout.println("current: "); - } - stdout.println(); - } else if (!StringUtils.isEmpty(setting) && value == null && reset) { - /* - * Reset a setting - */ - SettingModel model = settings.get(setting); - if (model == null) { - stdout.println(String.format("\"%s\" is not a valid setting.", setting)); - return; - } - - if (model.defaultValue == null || model.defaultValue.equals("null")) { - // no default value, remove setting - gitblit.getSettings().removeSetting(setting); - gitblit.getSettings().saveSettings(); - settings.remove(setting); - - stdout.println(String.format("%s removed.", setting)); - } else { - // reset to default value - Map updates = Maps.newHashMap(); - updates.put(setting, model.defaultValue == null ? "" : model.defaultValue); - gitblit.getSettings().saveSettings(updates); - - // confirm reset - String newValue = gitblit.getSettings().getString(setting, null); - if (model.defaultValue.equals(newValue)) { - stdout.println(String.format("%s reset to the default value.", setting)); - } else { - stdout.println(String.format("failed to reset %s!", setting)); - } - } - - } else if (!StringUtils.isEmpty(setting) && value != null) { - /* - * Update a setting - */ - Map updates = Maps.newHashMap(); - updates.put(setting, value); - gitblit.getSettings().saveSettings(updates); - - // confirm update - String newValue = gitblit.getSettings().getString(setting, null); - if (value.equals(newValue)) { - stdout.println(String.format("%s updated.", setting)); - } else { - stdout.println(String.format("failed to update %s!", setting)); - } - } else { - // Display usage - showHelp(); - } - } -} \ No newline at end of file diff --git a/src/main/java/com/gitblit/transport/ssh/gitblit/GitblitDispatcher.java b/src/main/java/com/gitblit/transport/ssh/gitblit/GitblitDispatcher.java deleted file mode 100644 index 67fedeaa..00000000 --- a/src/main/java/com/gitblit/transport/ssh/gitblit/GitblitDispatcher.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * 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.transport.ssh.gitblit; - -import com.gitblit.models.UserModel; -import com.gitblit.transport.ssh.commands.CommandMetaData; -import com.gitblit.transport.ssh.commands.DispatchCommand; - -@CommandMetaData(name = "gitblit", description = "Gitblit server commands") -public class GitblitDispatcher extends DispatchCommand { - - @Override - protected void setup(UserModel user) { - // commands in this dispatcher - register(user, VersionCommand.class); - register(user, ConfigCommand.class); - - // nested dispatchers - register(user, ListDispatcher.class); - register(user, KeysDispatcher.class); - register(user, TicketsDispatcher.class); - register(user, UsersDispatcher.class); - register(user, TeamsDispatcher.class); - register(user, ProjectsDispatcher.class); - register(user, RepositoriesDispatcher.class); - } -} diff --git a/src/main/java/com/gitblit/transport/ssh/gitblit/KeysDispatcher.java b/src/main/java/com/gitblit/transport/ssh/gitblit/KeysDispatcher.java deleted file mode 100644 index 9bb60003..00000000 --- a/src/main/java/com/gitblit/transport/ssh/gitblit/KeysDispatcher.java +++ /dev/null @@ -1,252 +0,0 @@ -/* - * 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.transport.ssh.gitblit; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -import org.kohsuke.args4j.Argument; -import org.kohsuke.args4j.Option; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.gitblit.models.UserModel; -import com.gitblit.transport.ssh.IPublicKeyManager; -import com.gitblit.transport.ssh.SshKey; -import com.gitblit.transport.ssh.commands.CommandMetaData; -import com.gitblit.transport.ssh.commands.DispatchCommand; -import com.gitblit.transport.ssh.commands.SshCommand; -import com.gitblit.transport.ssh.commands.UsageExample; -import com.gitblit.utils.FlipTable; -import com.gitblit.utils.FlipTable.Borders; -import com.google.common.base.Joiner; - -/** - * The dispatcher and it's commands for SSH public key management. - * - * @author James Moger - * - */ -@CommandMetaData(name = "keys", description = "SSH public key management commands") -public class KeysDispatcher extends DispatchCommand { - - @Override - protected void setup(UserModel user) { - register(user, AddKey.class); - register(user, RemoveKey.class); - register(user, ListKeys.class); - register(user, WhichKey.class); - register(user, CommentKey.class); - } - - @CommandMetaData(name = "add", description = "Add an SSH public key to your account") - @UsageExample(syntax = "cat ~/.ssh/id_rsa.pub | ${ssh} ${cmd} -", description = "Upload your SSH public key and add it to your account") - public static class AddKey extends BaseKeyCommand { - - protected final Logger log = LoggerFactory.getLogger(getClass()); - - @Argument(metaVar = "", usage = "the key(s) to add") - private List addKeys = new ArrayList(); - - @Override - public void run() throws IOException, UnloggedFailure { - String username = getContext().getClient().getUsername(); - List keys = readKeys(addKeys); - for (String key : keys) { - SshKey sshKey = parseKey(key); - getKeyManager().addKey(username, sshKey); - log.info("added SSH public key for {}", username); - } - } - } - - @CommandMetaData(name = "remove", aliases = { "rm" }, description = "Remove an SSH public key from your account") - @UsageExample(syntax = "${cmd} 2", description = "Remove the SSH key identified as #2 in `keys list`") - public static class RemoveKey extends BaseKeyCommand { - - protected final Logger log = LoggerFactory.getLogger(getClass()); - - private final String ALL = "ALL"; - - @Argument(metaVar = "||ALL", usage = "the key to remove", required = true) - private List removeKeys = new ArrayList(); - - @Override - public void run() throws IOException, UnloggedFailure { - 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()) { - throw new UnloggedFailure(1, "There are no registered keys!"); - } - - List keys = readKeys(removeKeys); - if (keys.contains(ALL)) { - if (getKeyManager().removeAllKeys(username)) { - stdout.println("Removed all keys."); - log.info("removed all SSH public keys from {}", username); - } else { - log.warn("failed to remove all SSH public keys from {}", username); - } - } else { - for (String key : keys) { - 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."); - } - throw new UnloggedFailure(1, String.format("Invalid index specified. There are %d registered keys.", keys.size())); - } - SshKey sshKey = currentKeys.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())); - } - } - } - } - } - } - - @CommandMetaData(name = "list", aliases = { "ls" }, description = "List your registered SSH public keys") - public static class ListKeys extends SshCommand { - - @Option(name = "-L", usage = "list complete public key parameters") - private boolean showRaw; - - @Override - public void run() { - IPublicKeyManager keyManager = getContext().getGitblit().getPublicKeyManager(); - String username = getContext().getClient().getUsername(); - List keys = keyManager.getKeys(username); - - if (showRaw) { - asRaw(keys); - } else { - asTable(keys); - } - } - - /* output in the same format as authorized_keys */ - protected void asRaw(List keys) { - if (keys == null) { - return; - } - for (SshKey key : keys) { - stdout.println(key.getRawData()); - } - } - - protected void asTable(List keys) { - String[] headers = { "#", "Fingerprint", "Comment", "Type" }; - int len = keys == null ? 0 : keys.size(); - Object[][] data = new Object[len][]; - for (int i = 0; i < len; i++) { - // show 1-based index numbers with the fingerprint - // this is useful for comparing with "ssh-add -l" - SshKey k = keys.get(i); - data[i] = new Object[] { (i + 1), k.getFingerprint(), k.getComment(), k.getAlgorithm() }; - } - - stdout.println(FlipTable.of(headers, data, Borders.BODY_HCOLS)); - } - } - - @CommandMetaData(name = "which", description = "Display the SSH public key used for this session") - public static class WhichKey extends SshCommand { - - @Option(name = "-L", usage = "list complete public key parameters") - private boolean showRaw; - - @Override - public void run() throws UnloggedFailure { - SshKey key = getContext().getClient().getKey(); - if (key == null) { - throw new UnloggedFailure(1, "You have not authenticated with an SSH public key."); - } - - if (showRaw) { - stdout.println(key.getRawData()); - } else { - final String username = getContext().getClient().getUsername(); - List keys = getContext().getGitblit().getPublicKeyManager().getKeys(username); - int index = 0; - for (int i = 0; i < keys.size(); i++) { - if (key.equals(keys.get(i))) { - index = i + 1; - break; - } - } - asTable(index, key); - } - } - - protected void asTable(int index, SshKey key) { - String[] headers = { "#", "Fingerprint", "Comment", "Type" }; - Object[][] data = new Object[1][]; - data[0] = new Object[] { index, key.getFingerprint(), key.getComment(), key.getAlgorithm() }; - - stdout.println(FlipTable.of(headers, data, Borders.BODY_HCOLS)); - } - } - - @CommandMetaData(name = "comment", description = "Set the comment for an SSH public key") - @UsageExample(syntax = "${cmd} 3 Home workstation", description = "Set the comment for key #3") - public static class CommentKey extends SshCommand { - - @Argument(index = 0, metaVar = "INDEX", usage = "the key index", required = true) - private int index; - - @Argument(index = 1, metaVar = "COMMENT", usage = "the new comment", required = true) - private List values = new ArrayList(); - - @Override - public void run() throws UnloggedFailure { - final String username = getContext().getClient().getUsername(); - IPublicKeyManager keyManager = getContext().getGitblit().getPublicKeyManager(); - List keys = keyManager.getKeys(username); - if (index > keys.size()) { - throw new UnloggedFailure(1, "Invalid key index!"); - } - - String comment = Joiner.on(" ").join(values); - SshKey key = keys.get(index - 1); - key.setComment(comment); - 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)); - } - } - - } -} diff --git a/src/main/java/com/gitblit/transport/ssh/gitblit/ListDispatcher.java b/src/main/java/com/gitblit/transport/ssh/gitblit/ListDispatcher.java deleted file mode 100644 index 343e59aa..00000000 --- a/src/main/java/com/gitblit/transport/ssh/gitblit/ListDispatcher.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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.transport.ssh.gitblit; - -import com.gitblit.models.UserModel; -import com.gitblit.transport.ssh.commands.CommandMetaData; -import com.gitblit.transport.ssh.commands.DispatchCommand; - -/** - * The dispatcher and it's commands for Gitblit object listing. - * - * @author James Moger - * - */ -@CommandMetaData(name = "list", aliases = { "ls" }, description = "Gitblit object list commands") -public class ListDispatcher extends DispatchCommand { - - @Override - protected void setup(UserModel user) { - register(user, ListRepositories.class); - register(user, ListProjects.class); - register(user, ListUsers.class); - register(user, ListKeys.class); - } - - /* List SSH public keys */ - @CommandMetaData(name = "keys", description = "List your public keys") - public static class ListKeys extends KeysDispatcher.ListKeys { - } - - /* List repositories */ - @CommandMetaData(name = "repositories", aliases = { "repos" }, description = "List repositories") - public static class ListRepositories extends RepositoriesDispatcher.ListRepositories { - } - - /* List projects */ - @CommandMetaData(name = "projects", description = "List projects") - public static class ListProjects extends ProjectsDispatcher.ListProjects { - } - - /* List users */ - @CommandMetaData(name = "users", description = "List users", admin = true) - public static class ListUsers extends UsersDispatcher.ListUsers { - } -} diff --git a/src/main/java/com/gitblit/transport/ssh/gitblit/ProjectsDispatcher.java b/src/main/java/com/gitblit/transport/ssh/gitblit/ProjectsDispatcher.java deleted file mode 100644 index 97076adf..00000000 --- a/src/main/java/com/gitblit/transport/ssh/gitblit/ProjectsDispatcher.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * 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.transport.ssh.gitblit; - -import java.util.List; - -import com.gitblit.manager.IGitblit; -import com.gitblit.models.ProjectModel; -import com.gitblit.models.UserModel; -import com.gitblit.transport.ssh.commands.CommandMetaData; -import com.gitblit.transport.ssh.commands.DispatchCommand; -import com.gitblit.transport.ssh.commands.ListFilterCommand; -import com.gitblit.utils.FlipTable; -import com.gitblit.utils.FlipTable.Borders; - -@CommandMetaData(name = "projects", description = "Project management commands") -public class ProjectsDispatcher extends DispatchCommand { - - @Override - protected void setup(UserModel user) { - register(user, ListProjects.class); - } - - /* List projects */ - @CommandMetaData(name = "list", aliases= { "ls" }, description = "List projects") - public static class ListProjects extends ListFilterCommand { - - @Override - protected List getItems() { - IGitblit gitblit = getContext().getGitblit(); - UserModel user = getContext().getClient().getUser(); - - List projects = gitblit.getProjectModels(user, false); - return projects; - } - - @Override - protected boolean matches(String filter, ProjectModel p) { - return p.name.matches(filter); - } - - @Override - protected void asTable(List list) { - String[] headers; - if (verbose) { - String[] h = { "Name", "Description", "Last Modified", "# Repos" }; - headers = h; - } else { - String[] h = { "Name", "Last Modified", "# Repos" }; - headers = h; - } - - Object[][] data = new Object[list.size()][]; - for (int i = 0; i < list.size(); i++) { - ProjectModel p = list.get(i); - - if (verbose) { - data[i] = new Object[] { p.name, p.description, formatDate(p.lastChange), p.repositories.size() }; - } else { - data[i] = new Object[] { p.name, formatDate(p.lastChange), p.repositories.size() }; - } - } - stdout.println(FlipTable.of(headers, data, Borders.BODY_HCOLS)); - } - - @Override - protected void asTabbed(List list) { - if (verbose) { - for (ProjectModel project : list) { - outTabbed(project.name, - project.description == null ? "" : project.description, - formatDate(project.lastChange)); - } - } else { - for (ProjectModel project : list) { - outTabbed(project.name); - } - } - } - } -} \ No newline at end of file diff --git a/src/main/java/com/gitblit/transport/ssh/gitblit/RepositoriesDispatcher.java b/src/main/java/com/gitblit/transport/ssh/gitblit/RepositoriesDispatcher.java deleted file mode 100644 index 292c2126..00000000 --- a/src/main/java/com/gitblit/transport/ssh/gitblit/RepositoriesDispatcher.java +++ /dev/null @@ -1,532 +0,0 @@ -/* - * 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.transport.ssh.gitblit; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -import org.kohsuke.args4j.Argument; - -import com.gitblit.GitBlitException; -import com.gitblit.Keys; -import com.gitblit.Constants.AccessRestrictionType; -import com.gitblit.Constants.AuthorizationControl; -import com.gitblit.manager.IGitblit; -import com.gitblit.models.RegistrantAccessPermission; -import com.gitblit.models.RepositoryModel; -import com.gitblit.models.UserModel; -import com.gitblit.transport.ssh.commands.CommandMetaData; -import com.gitblit.transport.ssh.commands.DispatchCommand; -import com.gitblit.transport.ssh.commands.ListFilterCommand; -import com.gitblit.transport.ssh.commands.SshCommand; -import com.gitblit.transport.ssh.commands.UsageExample; -import com.gitblit.utils.ArrayUtils; -import com.gitblit.utils.FlipTable; -import com.gitblit.utils.FlipTable.Borders; -import com.gitblit.utils.StringUtils; -import com.google.common.base.Joiner; - -@CommandMetaData(name = "repositories", aliases = { "repos" }, description = "Repository management commands") -public class RepositoriesDispatcher extends DispatchCommand { - - @Override - protected void setup(UserModel user) { - // primary commands - register(user, NewRepository.class); - register(user, RenameRepository.class); - register(user, RemoveRepository.class); - register(user, ShowRepository.class); - register(user, ListRepositories.class); - - // repository-specific commands - register(user, SetField.class); - } - - public static abstract class RepositoryCommand extends SshCommand { - @Argument(index = 0, required = true, metaVar = "REPOSITORY", usage = "repository") - protected String repository; - - protected RepositoryModel getRepository(boolean requireRepository) throws UnloggedFailure { - IGitblit gitblit = getContext().getGitblit(); - RepositoryModel repo = gitblit.getRepositoryModel(repository); - if (requireRepository && repo == null) { - throw new UnloggedFailure(1, String.format("Repository %s does not exist!", repository)); - } - return repo; - } - - protected String sanitize(String name) throws UnloggedFailure { - // automatically convert backslashes to forward slashes - name = name.replace('\\', '/'); - // Automatically replace // with / - name = name.replace("//", "/"); - - // prohibit folder paths - if (name.startsWith("/")) { - throw new UnloggedFailure(1, "Illegal leading slash"); - } - if (name.startsWith("../")) { - throw new UnloggedFailure(1, "Illegal relative slash"); - } - if (name.contains("/../")) { - throw new UnloggedFailure(1, "Illegal relative slash"); - } - if (name.endsWith("/")) { - name = name.substring(0, name.length() - 1); - } - return name; - } - } - - @CommandMetaData(name = "new", aliases = { "add" }, description = "Create a new repository") - @UsageExample(syntax = "${cmd} myRepo") - public static class NewRepository extends RepositoryCommand { - - @Override - public void run() throws UnloggedFailure { - - UserModel user = getContext().getClient().getUser(); - - String name = sanitize(repository); - - if (!user.canCreate(name)) { - // try to prepend personal path - String path = StringUtils.getFirstPathElement(name); - if ("".equals(path)) { - name = user.getPersonalPath() + "/" + name; - } - } - - if (getRepository(false) != null) { - throw new UnloggedFailure(1, String.format("Repository %s already exists!", name)); - } - - if (!user.canCreate(name)) { - throw new UnloggedFailure(1, String.format("Sorry, you do not have permission to create %s", name)); - } - - IGitblit gitblit = getContext().getGitblit(); - - RepositoryModel repo = new RepositoryModel(); - repo.name = name; - repo.projectPath = StringUtils.getFirstPathElement(name); - String restriction = gitblit.getSettings().getString(Keys.git.defaultAccessRestriction, "PUSH"); - repo.accessRestriction = AccessRestrictionType.fromName(restriction); - String authorization = gitblit.getSettings().getString(Keys.git.defaultAuthorizationControl, null); - repo.authorizationControl = AuthorizationControl.fromName(authorization); - - if (user.isMyPersonalRepository(name)) { - // personal repositories are private by default - repo.addOwner(user.username); - repo.accessRestriction = AccessRestrictionType.VIEW; - repo.authorizationControl = AuthorizationControl.NAMED; - } - - try { - gitblit.updateRepositoryModel(repository, repo, true); - stdout.println(String.format("%s created.", repo.name)); - } catch (GitBlitException e) { - log.error("Failed to add " + repository, e); - throw new UnloggedFailure(1, e.getMessage()); - } - } - } - - @CommandMetaData(name = "rename", aliases = { "mv" }, description = "Rename a repository") - @UsageExample(syntax = "${cmd} myRepo.git otherRepo.git", description = "Rename the repository from myRepo.git to otherRepo.git") - public static class RenameRepository extends RepositoryCommand { - @Argument(index = 1, required = true, metaVar = "NEWNAME", usage = "the new repository name") - protected String newRepositoryName; - - @Override - public void run() throws UnloggedFailure { - RepositoryModel repo = getRepository(true); - IGitblit gitblit = getContext().getGitblit(); - UserModel user = getContext().getClient().getUser(); - - String name = sanitize(newRepositoryName); - if (!user.canCreate(name)) { - // try to prepend personal path - String path = StringUtils.getFirstPathElement(name); - if ("".equals(path)) { - name = user.getPersonalPath() + "/" + name; - } - } - - if (null != gitblit.getRepositoryModel(name)) { - throw new UnloggedFailure(1, String.format("Repository %s already exists!", name)); - } - - if (repo.name.equalsIgnoreCase(name)) { - throw new UnloggedFailure(1, "Repository names are identical"); - } - - if (!user.canAdmin(repo)) { - throw new UnloggedFailure(1, String.format("Sorry, you do not have permission to rename %s", repository)); - } - - if (!user.canCreate(name)) { - throw new UnloggedFailure(1, String.format("Sorry, you don't have permission to move %s to %s/", repository, name)); - } - - // set the new name - repo.name = name; - - try { - gitblit.updateRepositoryModel(repository, repo, false); - stdout.println(String.format("Renamed repository %s to %s.", repository, name)); - } catch (GitBlitException e) { - String msg = String.format("Failed to rename repository from %s to %s", repository, name); - log.error(msg, e); - throw new UnloggedFailure(1, msg); - } - } - } - - @CommandMetaData(name = "set", description = "Set the specified field of a repository") - @UsageExample(syntax = "${cmd} myRepo description John's personal projects", description = "Set the description of a repository") - public static class SetField extends RepositoryCommand { - - @Argument(index = 1, required = true, metaVar = "FIELD", usage = "the field to update") - protected String fieldName; - - @Argument(index = 2, required = true, metaVar = "VALUE", usage = "the new value") - protected List fieldValues = new ArrayList(); - - protected enum Field { - description; - - static Field fromString(String name) { - for (Field field : values()) { - if (field.name().equalsIgnoreCase(name)) { - return field; - } - } - return null; - } - } - - @Override - protected String getUsageText() { - String fields = Joiner.on(", ").join(Field.values()); - StringBuilder sb = new StringBuilder(); - sb.append("Valid fields are:\n ").append(fields); - return sb.toString(); - } - - @Override - public void run() throws UnloggedFailure { - RepositoryModel repo = getRepository(true); - - Field field = Field.fromString(fieldName); - if (field == null) { - throw new UnloggedFailure(1, String.format("Unknown field %s", fieldName)); - } - - if (!getContext().getClient().getUser().canAdmin(repo)) { - throw new UnloggedFailure(1, String.format("Sorry, you do not have permission to administer %s", repository)); - } - - String value = Joiner.on(" ").join(fieldValues).trim(); - IGitblit gitblit = getContext().getGitblit(); - - switch(field) { - case description: - repo.description = value; - break; - default: - throw new UnloggedFailure(1, String.format("Field %s was not properly handled by the set command.", fieldName)); - } - - try { - gitblit.updateRepositoryModel(repo.name, repo, false); - stdout.println(String.format("Set %s.%s = %s", repo.name, fieldName, value)); - } catch (GitBlitException e) { - String msg = String.format("Failed to set %s.%s = %s", repo.name, fieldName, value); - log.error(msg, e); - throw new UnloggedFailure(1, msg); - } - } - - protected boolean toBool(String value) throws UnloggedFailure { - String v = value.toLowerCase(); - if (v.equals("t") - || v.equals("true") - || v.equals("yes") - || v.equals("on") - || v.equals("y") - || v.equals("1")) { - return true; - } else if (v.equals("f") - || v.equals("false") - || v.equals("no") - || v.equals("off") - || v.equals("n") - || v.equals("0")) { - return false; - } - throw new UnloggedFailure(1, String.format("Invalid boolean value %s", value)); - } - } - - @CommandMetaData(name = "remove", aliases = { "rm" }, description = "Remove a repository") - @UsageExample(syntax = "${cmd} myRepo.git", description = "Delete myRepo.git") - public static class RemoveRepository extends RepositoryCommand { - - @Override - public void run() throws UnloggedFailure { - - RepositoryModel repo = getRepository(true); - - if (!getContext().getClient().getUser().canAdmin(repo)) { - throw new UnloggedFailure(1, String.format("Sorry, you do not have permission to delete %s", repository)); - } - - IGitblit gitblit = getContext().getGitblit(); - if (gitblit.deleteRepositoryModel(repo)) { - stdout.println(String.format("%s has been deleted.", repository)); - } else { - throw new UnloggedFailure(1, String.format("Failed to delete %s!", repository)); - } - } - } - - @CommandMetaData(name = "show", description = "Show the details of a repository") - @UsageExample(syntax = "${cmd} myRepo.git", description = "Display myRepo.git") - public static class ShowRepository extends RepositoryCommand { - - @Override - public void run() throws UnloggedFailure { - - RepositoryModel r = getRepository(true); - - if (!getContext().getClient().getUser().canAdmin(r)) { - throw new UnloggedFailure(1, String.format("Sorry, you do not have permission to see the %s settings.", repository)); - } - - IGitblit gitblit = getContext().getGitblit(); - - // fields - StringBuilder fb = new StringBuilder(); - fb.append("Description : ").append(toString(r.description)).append('\n'); - fb.append("Origin : ").append(toString(r.origin)).append('\n'); - fb.append("Default Branch : ").append(toString(r.HEAD)).append('\n'); - fb.append('\n'); - fb.append("GC Period : ").append(r.gcPeriod).append('\n'); - fb.append("GC Threshold : ").append(r.gcThreshold).append('\n'); - fb.append('\n'); - fb.append("Accept Tickets : ").append(toString(r.acceptNewTickets)).append('\n'); - fb.append("Accept Patchsets : ").append(toString(r.acceptNewPatchsets)).append('\n'); - fb.append("Require Approval : ").append(toString(r.requireApproval)).append('\n'); - fb.append("Merge To : ").append(toString(r.mergeTo)).append('\n'); - fb.append('\n'); - fb.append("Incremental push tags : ").append(toString(r.useIncrementalPushTags)).append('\n'); - fb.append("Show remote branches : ").append(toString(r.showRemoteBranches)).append('\n'); - fb.append("Skip size calculations : ").append(toString(r.skipSizeCalculation)).append('\n'); - fb.append("Skip summary metrics : ").append(toString(r.skipSummaryMetrics)).append('\n'); - fb.append("Max activity commits : ").append(r.maxActivityCommits).append('\n'); - fb.append("Author metric exclusions : ").append(toString(r.metricAuthorExclusions)).append('\n'); - fb.append("Commit Message Renderer : ").append(r.commitMessageRenderer).append('\n'); - fb.append("Mailing Lists : ").append(toString(r.mailingLists)).append('\n'); - fb.append('\n'); - fb.append("Access Restriction : ").append(r.accessRestriction).append('\n'); - fb.append("Authorization Control : ").append(r.authorizationControl).append('\n'); - fb.append('\n'); - fb.append("Is Frozen : ").append(toString(r.isFrozen)).append('\n'); - fb.append("Allow Forks : ").append(toString(r.allowForks)).append('\n'); - fb.append("Verify Committer : ").append(toString(r.verifyCommitter)).append('\n'); - fb.append('\n'); - fb.append("Federation Strategy : ").append(r.federationStrategy).append('\n'); - fb.append("Federation Sets : ").append(toString(r.federationSets)).append('\n'); - fb.append('\n'); - fb.append("Indexed Branches : ").append(toString(r.indexedBranches)).append('\n'); - fb.append('\n'); - fb.append("Pre-Receive Scripts : ").append(toString(r.preReceiveScripts)).append('\n'); - fb.append(" inherited : ").append(toString(gitblit.getPreReceiveScriptsInherited(r))).append('\n'); - fb.append("Post-Receive Scripts : ").append(toString(r.postReceiveScripts)).append('\n'); - fb.append(" inherited : ").append(toString(gitblit.getPostReceiveScriptsInherited(r))).append('\n'); - String fields = fb.toString(); - - // owners - String owners; - if (r.owners.isEmpty()) { - owners = FlipTable.EMPTY; - } else { - String[] pheaders = { "Account", "Name" }; - Object [][] pdata = new Object[r.owners.size()][]; - for (int i = 0; i < r.owners.size(); i++) { - String owner = r.owners.get(i); - UserModel u = gitblit.getUserModel(owner); - pdata[i] = new Object[] { owner, u == null ? "" : u.getDisplayName() }; - } - owners = FlipTable.of(pheaders, pdata, Borders.COLS); - } - - // team permissions - List tperms = gitblit.getTeamAccessPermissions(r); - String tpermissions; - if (tperms.isEmpty()) { - tpermissions = FlipTable.EMPTY; - } else { - String[] pheaders = { "Team", "Permission", "Type" }; - Object [][] pdata = new Object[tperms.size()][]; - for (int i = 0; i < tperms.size(); i++) { - RegistrantAccessPermission ap = tperms.get(i); - pdata[i] = new Object[] { ap.registrant, ap.permission, ap.permissionType }; - } - tpermissions = FlipTable.of(pheaders, pdata, Borders.COLS); - } - - // user permissions - List uperms = gitblit.getUserAccessPermissions(r); - String upermissions; - if (uperms.isEmpty()) { - upermissions = FlipTable.EMPTY; - } else { - String[] pheaders = { "Account", "Name", "Permission", "Type", "Source", "Mutable" }; - Object [][] pdata = new Object[uperms.size()][]; - for (int i = 0; i < uperms.size(); i++) { - RegistrantAccessPermission ap = uperms.get(i); - String name = ""; - try { - String dn = gitblit.getUserModel(ap.registrant).displayName; - if (dn != null) { - name = dn; - } - } catch (Exception e) { - } - pdata[i] = new Object[] { ap.registrant, name, ap.permission, ap.permissionType, ap.source, ap.mutable ? "Y":"" }; - } - upermissions = FlipTable.of(pheaders, pdata, Borders.COLS); - } - - // assemble table - String title = r.name; - String [] headers = new String[] { title }; - String[][] data = new String[8][]; - data[0] = new String [] { "FIELDS" }; - data[1] = new String [] {fields }; - data[2] = new String [] { "OWNERS" }; - data[3] = new String [] { owners }; - data[4] = new String [] { "TEAM PERMISSIONS" }; - data[5] = new String [] { tpermissions }; - data[6] = new String [] { "USER PERMISSIONS" }; - data[7] = new String [] { upermissions }; - stdout.println(FlipTable.of(headers, data)); - } - - protected String toString(String val) { - if (val == null) { - return ""; - } - return val; - } - - protected String toString(Collection collection) { - if (collection == null) { - return ""; - } - return Joiner.on(", ").join(collection); - } - - protected String toString(boolean val) { - if (val) { - return "Y"; - } - return ""; - } - - } - - /* List repositories */ - @CommandMetaData(name = "list", aliases = { "ls" }, description = "List repositories") - @UsageExample(syntax = "${cmd} mirror/.* -v", description = "Verbose list of all repositories in the 'mirror' directory") - public static class ListRepositories extends ListFilterCommand { - - @Override - protected List getItems() { - IGitblit gitblit = getContext().getGitblit(); - UserModel user = getContext().getClient().getUser(); - List repositories = gitblit.getRepositoryModels(user); - return repositories; - } - - @Override - protected boolean matches(String filter, RepositoryModel r) { - return r.name.matches(filter); - } - - @Override - protected void asTable(List list) { - String[] headers; - if (verbose) { - String[] h = { "Name", "Description", "Owners", "Last Modified", "Size" }; - headers = h; - } else { - String[] h = { "Name", "Last Modified", "Size" }; - headers = h; - } - - Object[][] data = new Object[list.size()][]; - for (int i = 0; i < list.size(); i++) { - RepositoryModel r = list.get(i); - - String lm = formatDate(r.lastChange); - String size = r.size; - if (!r.hasCommits) { - lm = ""; - size = FlipTable.EMPTY; - } - if (verbose) { - String owners = ""; - if (!ArrayUtils.isEmpty(r.owners)) { - owners = Joiner.on(",").join(r.owners); - } - data[i] = new Object[] { r.name, r.description, owners, lm, size }; - } else { - data[i] = new Object[] { r.name, lm, size }; - } - } - stdout.println(FlipTable.of(headers, data, Borders.BODY_HCOLS)); - } - - @Override - protected void asTabbed(List list) { - if (verbose) { - for (RepositoryModel r : list) { - String lm = formatDate(r.lastChange); - String owners = ""; - if (!ArrayUtils.isEmpty(r.owners)) { - owners = Joiner.on(",").join(r.owners); - } - String size = r.size; - if (!r.hasCommits) { - lm = ""; - size = "(empty)"; - } - - outTabbed(r.name, r.description == null ? "" : r.description, - owners, lm, size); - } - } else { - for (RepositoryModel r : list) { - outTabbed(r.name); - } - } - } - } -} \ No newline at end of file diff --git a/src/main/java/com/gitblit/transport/ssh/gitblit/ReviewCommand.java b/src/main/java/com/gitblit/transport/ssh/gitblit/ReviewCommand.java deleted file mode 100644 index b3691cbb..00000000 --- a/src/main/java/com/gitblit/transport/ssh/gitblit/ReviewCommand.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * 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.transport.ssh.gitblit; - -import java.util.HashSet; -import java.util.Set; - -import org.kohsuke.args4j.Argument; -import org.kohsuke.args4j.Option; - -import com.gitblit.models.TicketModel.Change; -import com.gitblit.models.TicketModel.Patchset; -import com.gitblit.models.TicketModel.Score; -import com.gitblit.models.UserModel; -import com.gitblit.transport.ssh.commands.CommandMetaData; -import com.gitblit.transport.ssh.commands.SshCommand; -import com.gitblit.wicket.GitBlitWebSession; - -@CommandMetaData(name = "review", description = "Verify, approve and/or submit one or more patch sets", hidden = true) -public class ReviewCommand extends SshCommand { - - private final static short REV_ID_LEN = 40; - private final Set patchSets = new HashSet(); - - @Argument(index = 0, required = true, multiValued = true, metaVar = "{COMMIT | CHANGE,PATCHSET}", usage = "list of commits or patch sets to review") - void addPatchSetId(final String token) { - try { - patchSets.add(parsePatchSet(token)); - } catch (UnloggedFailure e) { - throw new IllegalArgumentException(e.getMessage(), e); - } - } - - @Option(name = "--project", required = true, aliases = "-p", usage = "project containing the specified patch set(s)") - private String project; - - @Option(name = "--message", aliases = "-m", usage = "cover message to publish on change(s)", metaVar = "MESSAGE") - private String changeComment; - - @Option(name = "--vote", aliases = "-v", usage = "vote on this patch set", metaVar = "VOTE") - private int vote; - - @Option(name = "--submit", aliases = "-s", usage = "submit the specified patch set(s)") - private boolean submitChange; - - @Override - public void run() throws UnloggedFailure { - UserModel user = GitBlitWebSession.get().getUser(); - // TODO ensure user has permission to score +2/-2 - for (Patchset ps : patchSets) { - // review - Change change = new Change(user.username); - change.review(ps, Score.fromScore(vote), false); - // TODO(davido): add patchset comment - if (submitChange) { - // TODO(davido): merge (when desired and the change is mergeable) - } - } - } - - private Patchset parsePatchSet(String ps) throws UnloggedFailure { - // By commit? - // - if (ps.matches("^([0-9a-fA-F]{4," + REV_ID_LEN + "})$")) { - // TODO; parse - } - - // By older style change,patchset? - // - if (ps.matches("^[1-9][0-9]*,[1-9][0-9]*$")) { - // TODO: parse - } - - throw new UnloggedFailure(1, "fatal: Cannot parse patchset: " + ps); - } -} diff --git a/src/main/java/com/gitblit/transport/ssh/gitblit/TeamsDispatcher.java b/src/main/java/com/gitblit/transport/ssh/gitblit/TeamsDispatcher.java deleted file mode 100644 index d0ec58f0..00000000 --- a/src/main/java/com/gitblit/transport/ssh/gitblit/TeamsDispatcher.java +++ /dev/null @@ -1,507 +0,0 @@ -/* - * 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.transport.ssh.gitblit; - -import java.util.ArrayList; -import java.util.List; - -import org.kohsuke.args4j.Argument; -import org.kohsuke.args4j.Option; - -import com.gitblit.Constants.AccessPermission; -import com.gitblit.GitBlitException; -import com.gitblit.manager.IGitblit; -import com.gitblit.models.RegistrantAccessPermission; -import com.gitblit.models.RepositoryModel; -import com.gitblit.models.TeamModel; -import com.gitblit.models.UserModel; -import com.gitblit.transport.ssh.commands.CommandMetaData; -import com.gitblit.transport.ssh.commands.DispatchCommand; -import com.gitblit.transport.ssh.commands.ListFilterCommand; -import com.gitblit.transport.ssh.commands.SshCommand; -import com.gitblit.transport.ssh.commands.UsageExample; -import com.gitblit.transport.ssh.commands.UsageExamples; -import com.gitblit.utils.ArrayUtils; -import com.gitblit.utils.FlipTable; -import com.gitblit.utils.FlipTable.Borders; -import com.gitblit.utils.StringUtils; -import com.google.common.base.Joiner; - -@CommandMetaData(name = "teams", description = "Team management commands", admin = true) -public class TeamsDispatcher extends DispatchCommand { - - @Override - protected void setup(UserModel user) { - // primary team commands - register(user, NewTeam.class); - register(user, RenameTeam.class); - register(user, RemoveTeam.class); - register(user, ShowTeam.class); - register(user, ListTeams.class); - - // team-specific commands - register(user, SetField.class); - register(user, Permissions.class); - register(user, Members.class); - } - - public static abstract class TeamCommand extends SshCommand { - @Argument(index = 0, required = true, metaVar = "TEAM", usage = "team name") - protected String teamname; - - protected TeamModel getTeam(boolean requireTeam) throws UnloggedFailure { - IGitblit gitblit = getContext().getGitblit(); - TeamModel team = gitblit.getTeamModel(teamname); - if (requireTeam && team == null) { - throw new UnloggedFailure(1, String.format("Team %s does not exist!", teamname)); - } - return team; - } - } - - @CommandMetaData(name = "new", aliases = { "add" }, description = "Create a new team") - @UsageExample(syntax = "${cmd} contributors --canFork --canCreate") - public static class NewTeam extends TeamCommand { - - @Option(name = "--canAdmin", usage = "can administer the server") - protected boolean canAdmin; - - @Option(name = "--canFork", usage = "can fork repositories") - protected boolean canFork; - - @Option(name = "--canCreate", usage = "can create personal repositories") - protected boolean canCreate; - - @Override - public void run() throws UnloggedFailure { - - if (getTeam(false) != null) { - throw new UnloggedFailure(1, String.format("Team %s already exists!", teamname)); - } - - TeamModel team = new TeamModel(teamname); - team.canAdmin = canAdmin; - team.canFork = canFork; - team.canCreate = canCreate; - - IGitblit gitblit = getContext().getGitblit(); - try { - gitblit.addTeam(team); - stdout.println(String.format("%s created.", teamname)); - } catch (GitBlitException e) { - String msg = String.format("Failed to create %s!", teamname); - log.error(msg, e); - throw new UnloggedFailure(1, msg); - } - } - } - - @CommandMetaData(name = "rename", aliases = { "mv" }, description = "Rename a team") - @UsageExample(syntax = "${cmd} contributors friends", description = "Rename the contributors team to the friends team") - public static class RenameTeam extends TeamCommand { - @Argument(index = 1, required = true, metaVar = "NEWNAME", usage = "the new team name") - protected String newTeamName; - - @Override - public void run() throws UnloggedFailure { - TeamModel team = getTeam(true); - IGitblit gitblit = getContext().getGitblit(); - if (null != gitblit.getTeamModel(newTeamName)) { - throw new UnloggedFailure(1, String.format("Team %s already exists!", newTeamName)); - } - - // set the new team name - team.name = newTeamName; - - try { - gitblit.reviseTeam(teamname, team); - stdout.println(String.format("Renamed team %s to %s.", teamname, newTeamName)); - } catch (GitBlitException e) { - String msg = String.format("Failed to rename team from %s to %s", teamname, newTeamName); - log.error(msg, e); - throw new UnloggedFailure(1, msg); - } - } - } - - @CommandMetaData(name = "set", description = "Set the specified field of a team") - @UsageExample(syntax = "${cmd} contributors canFork true", description = "Allow the contributors team to fork repositories") - public static class SetField extends TeamCommand { - - @Argument(index = 1, required = true, metaVar = "FIELD", usage = "the field to update") - protected String fieldName; - - @Argument(index = 2, required = true, metaVar = "VALUE", usage = "the new value") - protected List fieldValues = new ArrayList(); - - protected enum Field { - mailingList, preReceive, postReceive, canAdmin, canFork, canCreate; - - static Field fromString(String name) { - for (Field field : values()) { - if (field.name().equalsIgnoreCase(name)) { - return field; - } - } - return null; - } - } - - @Override - protected String getUsageText() { - String fields = Joiner.on(", ").join(Field.values()); - StringBuilder sb = new StringBuilder(); - sb.append("Valid fields are:\n ").append(fields); - return sb.toString(); - } - - @Override - public void run() throws UnloggedFailure { - TeamModel team = getTeam(true); - - Field field = Field.fromString(fieldName); - if (field == null) { - throw new UnloggedFailure(1, String.format("Unknown field %s", fieldName)); - } - - String value = Joiner.on(" ").join(fieldValues); - IGitblit gitblit = getContext().getGitblit(); - - switch(field) { - case mailingList: - team.mailingLists.clear(); - team.mailingLists.addAll(fieldValues); - break; - case preReceive: - team.preReceiveScripts.clear(); - team.preReceiveScripts.addAll(fieldValues); - break; - case postReceive: - team.postReceiveScripts.clear(); - team.postReceiveScripts.addAll(fieldValues); - break; - case canAdmin: - team.canAdmin = toBool(value); - break; - case canFork: - team.canFork = toBool(value); - break; - case canCreate: - team.canCreate = toBool(value); - break; - default: - throw new UnloggedFailure(1, String.format("Field %s was not properly handled by the set command.", fieldName)); - } - - try { - gitblit.reviseTeam(teamname, team); - stdout.println(String.format("Set %s.%s = %s", teamname, fieldName, value)); - } catch (GitBlitException e) { - String msg = String.format("Failed to set %s.%s = %s", teamname, fieldName, value); - log.error(msg, e); - throw new UnloggedFailure(1, msg); - } - } - - protected boolean toBool(String value) throws UnloggedFailure { - String v = value.toLowerCase(); - if (v.equals("t") - || v.equals("true") - || v.equals("yes") - || v.equals("on") - || v.equals("y") - || v.equals("1")) { - return true; - } else if (v.equals("f") - || v.equals("false") - || v.equals("no") - || v.equals("off") - || v.equals("n") - || v.equals("0")) { - return false; - } - throw new UnloggedFailure(1, String.format("Invalid boolean value %s", value)); - } - } - - @CommandMetaData(name = "permissions", aliases = { "perms" }, description = "Add or remove permissions from a team") - @UsageExample(syntax = "${cmd} contributors RW:alpha/repo.git RWC:alpha/repo2.git", description = "Add or set permissions for contributors") - public static class Permissions extends TeamCommand { - - @Argument(index = 1, multiValued = true, metaVar = "[PERMISSION:]REPOSITORY", usage = "a repository expression") - protected List permissions; - - @Option(name = "--remove", aliases = { "-r" }, metaVar = "REPOSITORY|ALL", usage = "remove a repository permission") - protected List removals; - - @Override - public void run() throws UnloggedFailure { - IGitblit gitblit = getContext().getGitblit(); - TeamModel team = getTeam(true); - - boolean modified = false; - if (!ArrayUtils.isEmpty(removals)) { - if (removals.contains("ALL")) { - team.permissions.clear(); - } else { - for (String repo : removals) { - team.removeRepositoryPermission(repo); - log.info(String.format("Removing permission for %s from %s", repo, teamname)); - } - } - modified = true; - } - - if (!ArrayUtils.isEmpty(permissions)) { - for (String perm : permissions) { - String repo = AccessPermission.repositoryFromRole(perm); - if (StringUtils.findInvalidCharacter(repo) == null) { - // explicit permision, confirm repository - RepositoryModel r = gitblit.getRepositoryModel(repo); - if (r == null) { - throw new UnloggedFailure(1, String.format("Repository %s does not exist!", repo)); - } - } - AccessPermission ap = AccessPermission.permissionFromRole(perm); - team.setRepositoryPermission(repo, ap); - log.info(String.format("Setting %s:%s for %s", ap.name(), repo, teamname)); - } - modified = true; - } - - if (modified && gitblit.updateTeamModel(teamname, team)) { - // reload & display new permissions - team = gitblit.getTeamModel(teamname); - } - - showPermissions(team); - } - - protected void showPermissions(TeamModel team) { - List perms = team.getRepositoryPermissions(); - String[] pheaders = { "Repository", "Permission", "Type" }; - Object [][] pdata = new Object[perms.size()][]; - for (int i = 0; i < perms.size(); i++) { - RegistrantAccessPermission ap = perms.get(i); - pdata[i] = new Object[] { ap.registrant, ap.permission, ap.permissionType }; - } - stdout.println(FlipTable.of(pheaders, pdata, Borders.BODY_HCOLS)); - } - } - - @CommandMetaData(name = "members", aliases = { "users" }, description = "Add or remove team members") - @UsageExample(syntax = "${cmd} contributors RW:alpha/repo.git RWC:alpha/repo2.git", description = "Add or set permissions for contributors") - public static class Members extends TeamCommand { - - @Argument(index = 1, multiValued = true, metaVar = "USERNAME", usage = "a username") - protected List members; - - @Option(name = "--remove", aliases = { "-r" }, metaVar = "USERNAME|ALL", usage = "remove a team member") - protected List removals; - - @Override - public void run() throws UnloggedFailure { - IGitblit gitblit = getContext().getGitblit(); - TeamModel team = getTeam(true); - - boolean canEditMemberships = gitblit.supportsTeamMembershipChanges(team); - if (!canEditMemberships) { - String msg = String.format("Team %s (%s) does not permit membership changes!", team.name, team.accountType); - throw new UnloggedFailure(1, msg); - } - - boolean modified = false; - if (!ArrayUtils.isEmpty(removals)) { - if (removals.contains("ALL")) { - team.users.clear(); - } else { - for (String member : removals) { - team.removeUser(member); - log.info(String.format("Removing member %s from %s", member, teamname)); - } - } - modified = true; - } - - if (!ArrayUtils.isEmpty(members)) { - for (String username : members) { - UserModel u = gitblit.getUserModel(username); - if (u == null) { - throw new UnloggedFailure(1, String.format("Unknown user %s", username)); - } - boolean canEditTeams = gitblit.supportsTeamMembershipChanges(u); - if (!canEditTeams) { - String msg = String.format("User %s (%s) does not allow team membership changes ", u.username, u.accountType); - throw new UnloggedFailure(1, msg); - } - team.addUser(username); - } - modified = true; - } - - if (modified && gitblit.updateTeamModel(teamname, team)) { - // reload & display new permissions - team = gitblit.getTeamModel(teamname); - } - - String[] headers = { "Username", "Display Name" }; - Object [][] data = new Object[team.users.size()][]; - int i = 0; - for (String username : team.users) { - UserModel u = gitblit.getUserModel(username); - data[i] = new Object[] { username, u.displayName }; - i++; - } - stdout.println(FlipTable.of(headers, data, Borders.BODY_HCOLS)); - } - } - - @CommandMetaData(name = "remove", aliases = { "rm" }, description = "Remove a team") - @UsageExample(syntax = "${cmd} contributors", description = "Delete the contributors team") - public static class RemoveTeam extends TeamCommand { - - @Override - public void run() throws UnloggedFailure { - - TeamModel team = getTeam(true); - IGitblit gitblit = getContext().getGitblit(); - if (gitblit.deleteTeamModel(team)) { - stdout.println(String.format("%s has been deleted.", teamname)); - } else { - throw new UnloggedFailure(1, String.format("Failed to delete %s!", teamname)); - } - } - } - - @CommandMetaData(name = "show", description = "Show the details of a team") - @UsageExample(syntax = "${cmd} contributors", description = "Display the 'contributors' team") - public static class ShowTeam extends TeamCommand { - - @Override - public void run() throws UnloggedFailure { - - TeamModel t = getTeam(true); - - // fields - StringBuilder fb = new StringBuilder(); - fb.append("Mailing Lists : ").append(Joiner.on(", ").join(t.mailingLists)).append('\n'); - fb.append("Type : ").append(t.accountType).append('\n'); - fb.append("Can Admin : ").append(t.canAdmin ? "Y":"").append('\n'); - fb.append("Can Fork : ").append(t.canFork ? "Y":"").append('\n'); - fb.append("Can Create : ").append(t.canCreate ? "Y":"").append('\n'); - fb.append("Pre-Receive : ").append(Joiner.on(", ").join(t.preReceiveScripts)).append('\n'); - fb.append("Post-Receive : ").append(Joiner.on(", ").join(t.postReceiveScripts)).append('\n'); - String fields = fb.toString(); - - // members - String members; - if (t.users.size() == 0) { - members = FlipTable.EMPTY; - } else { - IGitblit gitblit = getContext().getGitblit(); - String[] headers = { "Username", "Display Name" }; - Object [][] data = new Object[t.users.size()][]; - int i = 0; - for (String username : t.users) { - UserModel u = gitblit.getUserModel(username); - data[i] = new Object[] { username, u == null ? null : u.displayName }; - i++; - } - members = FlipTable.of(headers, data, Borders.COLS); - } - - // permissions - List perms = t.getRepositoryPermissions(); - String permissions; - if (perms.isEmpty()) { - permissions = FlipTable.EMPTY; - } else { - String[] pheaders = { "Repository", "Permission", "Type" }; - Object [][] pdata = new Object[perms.size()][]; - for (int i = 0; i < perms.size(); i++) { - RegistrantAccessPermission ap = perms.get(i); - pdata[i] = new Object[] { ap.registrant, ap.permission, ap.permissionType }; - } - permissions = FlipTable.of(pheaders, pdata, Borders.COLS); - } - - // assemble team table - String [] headers = new String[] { t.name }; - String[][] data = new String[6][]; - data[0] = new String [] { "FIELDS" }; - data[1] = new String [] { fields }; - data[2] = new String [] { "MEMBERS" }; - data[3] = new String [] { members }; - data[4] = new String [] { "PERMISSIONS" }; - data[5] = new String [] { permissions }; - stdout.println(FlipTable.of(headers, data)); - } - } - - @CommandMetaData(name = "list", aliases= { "ls" }, description = "List teams") - @UsageExamples(examples = { - @UsageExample(syntax = "${cmd}", description = "List teams as a table"), - @UsageExample(syntax = "${cmd} j.*", description = "List all teams that start with 'j'"), - }) - public static class ListTeams extends ListFilterCommand { - - @Override - protected List getItems() { - IGitblit gitblit = getContext().getGitblit(); - List teams = gitblit.getAllTeams(); - return teams; - } - - @Override - protected boolean matches(String filter, TeamModel t) { - return t.name.matches(filter); - } - - @Override - protected void asTable(List list) { - String[] headers = { "Name", "Members", "Type", "Create?", "Fork?"}; - Object[][] data = new Object[list.size()][]; - for (int i = 0; i < list.size(); i++) { - TeamModel t = list.get(i); - data[i] = new Object[] { - (t.canAdmin ? "*" : " ") + t.name, - t.users.isEmpty() ? "" : t.users.size(), - t.accountType + (t.canAdmin ? ",admin":""), - (t.canAdmin || t.canCreate) ? "Y":"", - (t.canAdmin || t.canFork) ? "Y" : ""}; - } - stdout.println(FlipTable.of(headers, data, Borders.BODY_HCOLS)); - } - - @Override - protected void asTabbed(List teams) { - if (verbose) { - for (TeamModel t : teams) { - outTabbed( - t.name, - t.users.isEmpty() ? "" : t.users.size(), - t.accountType + (t.canAdmin ? ",admin":""), - (t.canAdmin || t.canCreate) ? "Y":"", - (t.canAdmin || t.canFork) ? "Y" : ""); - } - } else { - for (TeamModel u : teams) { - outTabbed((u.canAdmin ? "*" : " ") + u.name); - } - } - } - } -} \ No newline at end of file diff --git a/src/main/java/com/gitblit/transport/ssh/gitblit/TicketsDispatcher.java b/src/main/java/com/gitblit/transport/ssh/gitblit/TicketsDispatcher.java deleted file mode 100644 index dd29b6ac..00000000 --- a/src/main/java/com/gitblit/transport/ssh/gitblit/TicketsDispatcher.java +++ /dev/null @@ -1,157 +0,0 @@ -/* - * 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.transport.ssh.gitblit; - -import java.util.List; - -import org.kohsuke.args4j.Argument; - -import com.gitblit.manager.IGitblit; -import com.gitblit.models.RepositoryModel; -import com.gitblit.models.TicketModel.Status; -import com.gitblit.models.UserModel; -import com.gitblit.tickets.ITicketService; -import com.gitblit.tickets.QueryBuilder; -import com.gitblit.tickets.QueryResult; -import com.gitblit.tickets.TicketIndexer.Lucene; -import com.gitblit.transport.ssh.commands.CommandMetaData; -import com.gitblit.transport.ssh.commands.DispatchCommand; -import com.gitblit.transport.ssh.commands.ListCommand; -import com.gitblit.utils.ArrayUtils; -import com.gitblit.utils.FlipTable; -import com.gitblit.utils.FlipTable.Borders; -import com.gitblit.utils.StringUtils; - -@CommandMetaData(name = "tickets", description = "Ticket commands", hidden = true) -public class TicketsDispatcher extends DispatchCommand { - - @Override - protected void setup(UserModel user) { - register(user, ReviewCommand.class); - register(user, ListTickets.class); - } - - /* List tickets */ - @CommandMetaData(name = "list", aliases = { "ls" }, description = "List tickets") - public static class ListTickets extends ListCommand { - - private final String ALL = "ALL"; - - @Argument(index = 0, metaVar = "ALL|REPOSITORY", usage = "the repository or ALL") - protected String repository; - - @Argument(index = 1, multiValued = true, metaVar="CONDITION", usage = "query condition") - protected List query; - - protected String userQuery; - - @Override - protected List getItems() throws UnloggedFailure { - IGitblit gitblit = getContext().getGitblit(); - ITicketService tickets = gitblit.getTicketService(); - - QueryBuilder sb = new QueryBuilder(); - if (ArrayUtils.isEmpty(query)) { - sb.and(Lucene.status.matches(Status.New.toString())).or(Lucene.status.matches(Status.Open.toString())); - } else { - StringBuilder b = new StringBuilder(); - for (String q : query) { - b.append(q).append(' '); - } - b.setLength(b.length() - 1); - sb.and(b.toString()); - } - - QueryBuilder qb; - if (StringUtils.isEmpty(repository) || ALL.equalsIgnoreCase(repository)) { - qb = sb; - userQuery = sb.build(); - } else { - qb = new QueryBuilder(); - RepositoryModel r = gitblit.getRepositoryModel(repository); - if (r == null) { - throw new UnloggedFailure(1, String.format("%s is not a repository!", repository)); - } - qb.and(Lucene.rid.matches(r.getRID())); - qb.and(sb.toSubquery().toString()); - userQuery = sb.build(); - } - - String query = qb.build(); - List list = tickets.queryFor(query, 0, 0, null, true); - return list; - } - - @Override - protected void asTable(List list) { - boolean forRepo = !StringUtils.isEmpty(repository) && !ALL.equalsIgnoreCase(repository); - String[] headers; - if (verbose) { - if (forRepo) { - String[] h = { "ID", "Title", "Status", "Last Modified", "Votes", "Commits" }; - headers = h; - } else { - String[] h = { "Repository", "ID", "Title", "Status", "Last Modified", "Votes", "Commits" }; - headers = h; - } - } else { - if (forRepo) { - String[] h = { "ID", "Title", "Status", "Last Modifed" }; - headers = h; - } else { - String[] h = { "Repository", "ID", "Title", "Status", "Last Modified" }; - headers = h; - } - } - - Object[][] data = new Object[list.size()][]; - for (int i = 0; i < list.size(); i++) { - QueryResult q = list.get(i); - - if (verbose) { - if (forRepo) { - data[i] = new Object[] { q.number, q.title, q.status, formatDate(q.getDate()), q.votesCount, q.patchset == null ? "": q.patchset.commits }; - } else { - data[i] = new Object[] { q.repository, q.number, q.title, q.status, formatDate(q.getDate()), q.votesCount, q.patchset == null ? "": q.patchset.commits }; - } - } else { - if (forRepo) { - data[i] = new Object[] { q.number, q.title, q.status, formatDate(q.getDate()) }; - } else { - data[i] = new Object[] { q.repository, q.number, q.title, q.status, formatDate(q.getDate()) }; - } - } - } - stdout.print(FlipTable.of(headers, data, Borders.BODY_HCOLS)); - stdout.println(" " + repository + ": " + userQuery); - stdout.println(); - } - - @Override - protected void asTabbed(List list) { - if (verbose) { - for (QueryResult q : list) { - outTabbed(q.repository, q.number, q.title, q.status.toString(), - formatDate(q.getDate())); - } - } else { - for (QueryResult q : list) { - outTabbed(q.repository, q.number, q.title); - } - } - } - } -} diff --git a/src/main/java/com/gitblit/transport/ssh/gitblit/UsersDispatcher.java b/src/main/java/com/gitblit/transport/ssh/gitblit/UsersDispatcher.java deleted file mode 100644 index 1a6dee46..00000000 --- a/src/main/java/com/gitblit/transport/ssh/gitblit/UsersDispatcher.java +++ /dev/null @@ -1,592 +0,0 @@ -/* - * 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.transport.ssh.gitblit; - -import java.util.ArrayList; -import java.util.List; - -import org.kohsuke.args4j.Argument; -import org.kohsuke.args4j.Option; - -import com.gitblit.Constants.AccessPermission; -import com.gitblit.GitBlitException; -import com.gitblit.Keys; -import com.gitblit.manager.IGitblit; -import com.gitblit.models.RegistrantAccessPermission; -import com.gitblit.models.RepositoryModel; -import com.gitblit.models.UserModel; -import com.gitblit.transport.ssh.SshKey; -import com.gitblit.transport.ssh.commands.CommandMetaData; -import com.gitblit.transport.ssh.commands.DispatchCommand; -import com.gitblit.transport.ssh.commands.ListFilterCommand; -import com.gitblit.transport.ssh.commands.SshCommand; -import com.gitblit.transport.ssh.commands.UsageExample; -import com.gitblit.transport.ssh.commands.UsageExamples; -import com.gitblit.utils.ArrayUtils; -import com.gitblit.utils.FlipTable; -import com.gitblit.utils.FlipTable.Borders; -import com.gitblit.utils.StringUtils; -import com.google.common.base.Joiner; - -@CommandMetaData(name = "users", description = "User management commands", admin = true) -public class UsersDispatcher extends DispatchCommand { - - @Override - protected void setup(UserModel user) { - // primary user commands - register(user, NewUser.class); - register(user, RenameUser.class); - register(user, RemoveUser.class); - register(user, ShowUser.class); - register(user, ListUsers.class); - - // user-specific commands - register(user, SetField.class); - register(user, Permissions.class); - register(user, DisableUser.class); - register(user, EnableUser.class); - } - - public static abstract class UserCommand extends SshCommand { - @Argument(index = 0, required = true, metaVar = "USERNAME", usage = "username") - protected String username; - - protected UserModel getUser(boolean requireUser) throws UnloggedFailure { - IGitblit gitblit = getContext().getGitblit(); - UserModel user = gitblit.getUserModel(username); - if (requireUser && user == null) { - throw new UnloggedFailure(1, String.format("User %s does not exist!", username)); - } - return user; - } - } - - @CommandMetaData(name = "new", aliases = { "add" }, description = "Create a new user account") - @UsageExample(syntax = "${cmd} john 12345 --email john@smith.com --canFork --canCreate") - public static class NewUser extends UserCommand { - - @Argument(index = 1, required = true, metaVar = "PASSWORD", usage = "password") - protected String password; - - @Option(name = "--email", metaVar = "ADDRESS", usage = "email address") - protected String email; - - @Option(name = "--canAdmin", usage = "can administer the server") - protected boolean canAdmin; - - @Option(name = "--canFork", usage = "can fork repositories") - protected boolean canFork; - - @Option(name = "--canCreate", usage = "can create personal repositories") - protected boolean canCreate; - - @Option(name = "--disabled", usage = "create a disabled user account") - protected boolean disabled; - - @Override - public void run() throws UnloggedFailure { - - if (getUser(false) != null) { - throw new UnloggedFailure(1, String.format("User %s already exists!", username)); - } - - UserModel user = new UserModel(username); - user.password = password; - - if (email != null) { - user.emailAddress = email; - } - - user.canAdmin = canAdmin; - user.canFork = canFork; - user.canCreate = canCreate; - user.disabled = disabled; - - IGitblit gitblit = getContext().getGitblit(); - try { - gitblit.addUser(user); - stdout.println(String.format("%s created.", username)); - } catch (GitBlitException e) { - log.error("Failed to add " + username, e); - throw new UnloggedFailure(1, e.getMessage()); - } - } - } - - @CommandMetaData(name = "rename", aliases = { "mv" }, description = "Rename an account") - @UsageExample(syntax = "${cmd} john frank", description = "Rename the account from john to frank") - public static class RenameUser extends UserCommand { - @Argument(index = 1, required = true, metaVar = "NEWNAME", usage = "the new account name") - protected String newUserName; - - @Override - public void run() throws UnloggedFailure { - UserModel user = getUser(true); - IGitblit gitblit = getContext().getGitblit(); - if (null != gitblit.getTeamModel(newUserName)) { - throw new UnloggedFailure(1, String.format("Team %s already exists!", newUserName)); - } - - // set the new name - user.username = newUserName; - - try { - gitblit.reviseUser(username, user); - stdout.println(String.format("Renamed user %s to %s.", username, newUserName)); - } catch (GitBlitException e) { - String msg = String.format("Failed to rename user from %s to %s", username, newUserName); - log.error(msg, e); - throw new UnloggedFailure(1, msg); - } - } - } - - @CommandMetaData(name = "set", description = "Set the specified field of an account") - @UsageExample(syntax = "${cmd} john name John Smith", description = "Set the display name to \"John Smith\" for john's account") - public static class SetField extends UserCommand { - - @Argument(index = 1, required = true, metaVar = "FIELD", usage = "the field to update") - protected String fieldName; - - @Argument(index = 2, required = true, metaVar = "VALUE", usage = "the new value") - protected List fieldValues = new ArrayList(); - - protected enum Field { - name, displayName, email, password, canAdmin, canFork, canCreate, disabled; - - static Field fromString(String name) { - for (Field field : values()) { - if (field.name().equalsIgnoreCase(name)) { - return field; - } - } - return null; - } - } - - @Override - protected String getUsageText() { - String fields = Joiner.on(", ").join(Field.values()); - StringBuilder sb = new StringBuilder(); - sb.append("Valid fields are:\n ").append(fields); - return sb.toString(); - } - - @Override - public void run() throws UnloggedFailure { - UserModel user = getUser(true); - - Field field = Field.fromString(fieldName); - if (field == null) { - throw new UnloggedFailure(1, String.format("Unknown field %s", fieldName)); - } - - String value = Joiner.on(" ").join(fieldValues).trim(); - IGitblit gitblit = getContext().getGitblit(); - - boolean editCredentials = gitblit.supportsCredentialChanges(user); - boolean editDisplayName = gitblit.supportsDisplayNameChanges(user); - boolean editEmailAddress = gitblit.supportsEmailAddressChanges(user); - - String m = String.format("Can not edit %s for %s (%s)", field, user.username, user.accountType); - - switch(field) { - case name: - case displayName: - if (!editDisplayName) { - throw new UnloggedFailure(1, m); - } - user.displayName = value; - break; - case email: - if (!editEmailAddress) { - throw new UnloggedFailure(1, m); - } - user.emailAddress = value; - break; - case password: - if (!editCredentials) { - throw new UnloggedFailure(1, m); - } - int minLength = gitblit.getSettings().getInteger(Keys.realm.minPasswordLength, 5); - if (minLength < 4) { - minLength = 4; - } - if (value.trim().length() < minLength) { - throw new UnloggedFailure(1, "Password is too short."); - } - - // Optionally store the password MD5 digest. - String type = gitblit.getSettings().getString(Keys.realm.passwordStorage, "md5"); - if (type.equalsIgnoreCase("md5")) { - // store MD5 digest of password - user.password = StringUtils.MD5_TYPE + StringUtils.getMD5(value); - } else if (type.equalsIgnoreCase("combined-md5")) { - // store MD5 digest of username+password - user.password = StringUtils.COMBINED_MD5_TYPE + StringUtils.getMD5(username + value); - } else { - user.password = value; - } - - // reset the cookie - user.cookie = StringUtils.getSHA1(user.username + value); - break; - case canAdmin: - user.canAdmin = toBool(value); - break; - case canFork: - user.canFork = toBool(value); - break; - case canCreate: - user.canCreate = toBool(value); - break; - case disabled: - user.disabled = toBool(value); - break; - default: - throw new UnloggedFailure(1, String.format("Field %s was not properly handled by the set command.", fieldName)); - } - - try { - gitblit.reviseUser(username, user); - stdout.println(String.format("Set %s.%s = %s", username, fieldName, value)); - } catch (GitBlitException e) { - String msg = String.format("Failed to set %s.%s = %s", username, fieldName, value); - log.error(msg, e); - throw new UnloggedFailure(1, msg); - } - } - - protected boolean toBool(String value) throws UnloggedFailure { - String v = value.toLowerCase(); - if (v.equals("t") - || v.equals("true") - || v.equals("yes") - || v.equals("on") - || v.equals("y") - || v.equals("1")) { - return true; - } else if (v.equals("f") - || v.equals("false") - || v.equals("no") - || v.equals("off") - || v.equals("n") - || v.equals("0")) { - return false; - } - throw new UnloggedFailure(1, String.format("Invalid boolean value %s", value)); - } - } - - @CommandMetaData(name = "disable", description = "Prohibit an account from authenticating") - @UsageExample(syntax = "${cmd} john", description = "Prevent John from authenticating") - public static class DisableUser extends UserCommand { - - @Override - public void run() throws UnloggedFailure { - - UserModel user = getUser(true); - user.disabled = true; - - IGitblit gitblit = getContext().getGitblit(); - if (gitblit.updateUserModel(username, user)) { - stdout.println(String.format("%s is not allowed to authenticate.", username)); - } else { - throw new UnloggedFailure(1, String.format("Failed to disable %s!", username)); - } - } - } - - @CommandMetaData(name = "enable", description = "Allow an account to authenticate") - @UsageExample(syntax = "${cmd} john", description = "Allow John to authenticate") - public static class EnableUser extends UserCommand { - - @Override - public void run() throws UnloggedFailure { - - UserModel user = getUser(true); - user.disabled = false; - - IGitblit gitblit = getContext().getGitblit(); - if (gitblit.updateUserModel(username, user)) { - stdout.println(String.format("%s may now authenticate.", username)); - } else { - throw new UnloggedFailure(1, String.format("Failed to enable %s!", username)); - } - } - } - - @CommandMetaData(name = "permissions", aliases = { "perms" }, description = "Add or remove permissions from an account") - @UsageExample(syntax = "${cmd} john RW:alpha/repo.git RWC:alpha/repo2.git", description = "Add or set permissions for John") - public static class Permissions extends UserCommand { - - @Argument(index = 1, multiValued = true, metaVar = "[PERMISSION:]REPOSITORY", usage = "a repository expression") - protected List permissions; - - @Option(name = "--remove", aliases = { "-r" }, metaVar = "REPOSITORY|ALL", usage = "remove a repository permission") - protected List removals; - - @Override - public void run() throws UnloggedFailure { - IGitblit gitblit = getContext().getGitblit(); - UserModel user = getUser(true); - - boolean modified = false; - if (!ArrayUtils.isEmpty(removals)) { - if (removals.contains("ALL")) { - user.permissions.clear(); - } else { - for (String repo : removals) { - user.removeRepositoryPermission(repo); - log.info(String.format("Removing permission for %s from %s", repo, username)); - } - } - modified = true; - } - - if (!ArrayUtils.isEmpty(permissions)) { - for (String perm : permissions) { - String repo = AccessPermission.repositoryFromRole(perm); - if (StringUtils.findInvalidCharacter(repo) == null) { - // explicit permision, confirm repository - RepositoryModel r = gitblit.getRepositoryModel(repo); - if (r == null) { - throw new UnloggedFailure(1, String.format("Repository %s does not exist!", repo)); - } - } - AccessPermission ap = AccessPermission.permissionFromRole(perm); - user.setRepositoryPermission(repo, ap); - log.info(String.format("Setting %s:%s for %s", ap.name(), repo, username)); - } - modified = true; - } - - if (modified && gitblit.updateUserModel(username, user)) { - // reload & display new permissions - user = gitblit.getUserModel(username); - } - - showPermissions(user); - } - - protected void showPermissions(UserModel user) { - List perms = user.getRepositoryPermissions(); - String[] pheaders = { "Repository", "Permission", "Type", "Source", "Mutable" }; - Object [][] pdata = new Object[perms.size()][]; - for (int i = 0; i < perms.size(); i++) { - RegistrantAccessPermission ap = perms.get(i); - pdata[i] = new Object[] { ap.registrant, ap.permission, ap.permissionType, ap.source, ap.mutable ? "Y":"" }; - } - stdout.println(FlipTable.of(pheaders, pdata, Borders.BODY_HCOLS)); - } - } - - @CommandMetaData(name = "remove", aliases = { "rm" }, description = "Remove a user account") - @UsageExample(syntax = "${cmd} john", description = "Delete john's account") - public static class RemoveUser extends UserCommand { - - @Override - public void run() throws UnloggedFailure { - - UserModel user = getUser(true); - IGitblit gitblit = getContext().getGitblit(); - if (gitblit.deleteUserModel(user)) { - stdout.println(String.format("%s has been deleted.", username)); - } else { - throw new UnloggedFailure(1, String.format("Failed to delete %s!", username)); - } - } - } - - @CommandMetaData(name = "show", description = "Show the details of an account") - @UsageExample(syntax = "${cmd} john", description = "Display john's account") - public static class ShowUser extends UserCommand { - - @Override - public void run() throws UnloggedFailure { - - UserModel u = getUser(true); - - // fields - StringBuilder fb = new StringBuilder(); - fb.append("Email : ").append(u.emailAddress == null ? "": u.emailAddress).append('\n'); - fb.append("Type : ").append(u.accountType).append('\n'); - fb.append("Can Admin : ").append(u.canAdmin() ? "Y":"").append('\n'); - fb.append("Can Fork : ").append(u.canFork() ? "Y":"").append('\n'); - fb.append("Can Create : ").append(u.canCreate() ? "Y":"").append('\n'); - String fields = fb.toString(); - - // teams - String teams; - if (u.teams.size() == 0) { - teams = FlipTable.EMPTY; - } else { - teams = Joiner.on(", ").join(u.teams); - } - - // owned repositories - String ownedTable; - List owned = new ArrayList(); - for (RepositoryModel repository : getContext().getGitblit().getRepositoryModels(u)) { - if (repository.isOwner(u.username)) { - owned.add(repository); - } - } - if (owned.isEmpty()) { - ownedTable = FlipTable.EMPTY; - } else { - String [] theaders = new String [] { "Repository", "Description" }; - Object [][] tdata = new Object[owned.size()][]; - int i = 0; - for (RepositoryModel r : owned) { - tdata[i] = new Object [] { r.name, r.description }; - i++; - } - ownedTable = FlipTable.of(theaders, tdata, Borders.COLS); - } - - // permissions - List perms = u.getRepositoryPermissions(); - String permissions; - if (perms.isEmpty()) { - permissions = FlipTable.EMPTY; - } else { - String[] pheaders = { "Repository", "Permission", "Type", "Source", "Mutable" }; - Object [][] pdata = new Object[perms.size()][]; - for (int i = 0; i < perms.size(); i++) { - RegistrantAccessPermission ap = perms.get(i); - pdata[i] = new Object[] { ap.registrant, ap.permission, ap.permissionType, ap.source, ap.mutable ? "Y":"" }; - } - permissions = FlipTable.of(pheaders, pdata, Borders.COLS); - } - - // keys - String keyTable; - List keys = getContext().getGitblit().getPublicKeyManager().getKeys(u.username); - if (ArrayUtils.isEmpty(keys)) { - keyTable = FlipTable.EMPTY; - } else { - String[] headers = { "#", "Fingerprint", "Comment", "Type" }; - int len = keys == null ? 0 : keys.size(); - Object[][] data = new Object[len][]; - for (int i = 0; i < len; i++) { - // show 1-based index numbers with the fingerprint - // this is useful for comparing with "ssh-add -l" - SshKey k = keys.get(i); - data[i] = new Object[] { (i + 1), k.getFingerprint(), k.getComment(), k.getAlgorithm() }; - } - keyTable = FlipTable.of(headers, data, Borders.COLS); - } - - // assemble user table - String userTitle = u.getDisplayName() + (u.username.equals(u.getDisplayName()) ? "" : (" (" + u.username + ")")); - if (u.disabled) { - userTitle += " [DISABLED]"; - } - String [] headers = new String[] { userTitle }; - String[][] data = new String[8][]; - data[0] = new String [] { "FIELDS" }; - data[1] = new String [] { fields }; - data[2] = new String [] { "TEAMS" }; - data[3] = new String [] { teams }; - data[4] = new String [] { "OWNED REPOSITORIES" }; - data[5] = new String [] { ownedTable }; - data[4] = new String [] { "PERMISSIONS" }; - data[5] = new String [] { permissions }; - data[6] = new String [] { "SSH PUBLIC KEYS" }; - data[7] = new String [] { keyTable }; - stdout.println(FlipTable.of(headers, data)); - } - } - - @CommandMetaData(name = "list", aliases= { "ls" }, description = "List accounts") - @UsageExamples(examples = { - @UsageExample(syntax = "${cmd}", description = "List accounts as a table"), - @UsageExample(syntax = "${cmd} j.*", description = "List all accounts that start with 'j'"), - }) - public static class ListUsers extends ListFilterCommand { - - @Override - protected List getItems() { - IGitblit gitblit = getContext().getGitblit(); - List users = gitblit.getAllUsers(); - return users; - } - - @Override - protected boolean matches(String filter, UserModel u) { - return u.username.matches(filter); - } - - @Override - protected void asTable(List list) { - String[] headers; - if (verbose) { - String[] h = { "Name", "Display name", "Email", "Type", "Teams", "Create?", "Fork?"}; - headers = h; - } else { - String[] h = { "Name", "Display name", "Email", "Type"}; - headers = h; - } - - Object[][] data = new Object[list.size()][]; - for (int i = 0; i < list.size(); i++) { - UserModel u = list.get(i); - - String name = (u.disabled ? "-" : ((u.canAdmin() ? "*" : " "))) + u.username; - if (verbose) { - data[i] = new Object[] { - name, - u.displayName, - u.emailAddress, - u.accountType + (u.canAdmin() ? ",admin":""), - u.teams.isEmpty() ? "" : u.teams.size(), - (u.canAdmin() || u.canCreate()) ? "Y":"", - (u.canAdmin() || u.canFork()) ? "Y" : ""}; - } else { - data[i] = new Object[] { - name, - u.displayName, - u.emailAddress, - u.accountType + (u.canAdmin() ? ",admin":"")}; - } - } - stdout.print(FlipTable.of(headers, data, Borders.BODY_HCOLS)); - stdout.println(" * = admin account, - = disabled account"); - stdout.println(); - } - - @Override - protected void asTabbed(List users) { - if (verbose) { - for (UserModel u : users) { - outTabbed( - u.disabled ? "-" : ((u.canAdmin() ? "*" : " ")) + u.username, - u.getDisplayName(), - u.emailAddress == null ? "" : u.emailAddress, - u.accountType + (u.canAdmin() ? ",admin":""), - u.teams.isEmpty() ? "" : u.teams.size(), - (u.canAdmin() || u.canCreate()) ? "Y":"", - (u.canAdmin() || u.canFork()) ? "Y" : ""); - } - } else { - for (UserModel u : users) { - outTabbed(u.disabled ? "-" : ((u.canAdmin() ? "*" : " ")) + u.username); - } - } - } - } -} \ No newline at end of file diff --git a/src/main/java/com/gitblit/transport/ssh/gitblit/VersionCommand.java b/src/main/java/com/gitblit/transport/ssh/gitblit/VersionCommand.java deleted file mode 100644 index 384c6ce4..00000000 --- a/src/main/java/com/gitblit/transport/ssh/gitblit/VersionCommand.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * 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.transport.ssh.gitblit; - -import com.gitblit.Constants; -import com.gitblit.transport.ssh.commands.CommandMetaData; -import com.gitblit.transport.ssh.commands.SshCommand; - -@CommandMetaData(name="version", description = "Display the Gitblit version") -public class VersionCommand extends SshCommand { - - @Override - public void run() { - stdout.println(Constants.getGitBlitVersion()); - } -} diff --git a/src/main/java/com/gitblit/transport/ssh/keys/BaseKeyCommand.java b/src/main/java/com/gitblit/transport/ssh/keys/BaseKeyCommand.java new file mode 100644 index 00000000..588770f4 --- /dev/null +++ b/src/main/java/com/gitblit/transport/ssh/keys/BaseKeyCommand.java @@ -0,0 +1,67 @@ +/* + * 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.transport.ssh.keys; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.UnsupportedEncodingException; +import java.util.List; + +import com.gitblit.transport.ssh.IPublicKeyManager; +import com.gitblit.transport.ssh.SshKey; +import com.gitblit.transport.ssh.commands.SshCommand; +import com.google.common.base.Charsets; + +/** + * + * Base class for commands that read SSH keys from stdin or a parameter list. + * + */ +abstract class BaseKeyCommand extends SshCommand { + + protected List readKeys(List sshKeys) + throws UnsupportedEncodingException, IOException { + int idx = -1; + if (sshKeys.isEmpty() || (idx = sshKeys.indexOf("-")) >= 0) { + String sshKey = ""; + BufferedReader br = new BufferedReader(new InputStreamReader( + in, Charsets.UTF_8)); + String line; + while ((line = br.readLine()) != null) { + sshKey += line + "\n"; + } + if (idx == -1) { + sshKeys.add(sshKey.trim()); + } else { + sshKeys.set(idx, sshKey.trim()); + } + } + return sshKeys; + } + + protected IPublicKeyManager getKeyManager() { + return getContext().getGitblit().getPublicKeyManager(); + } + + protected SshKey parseKey(String rawData) throws UnloggedFailure { + if (rawData.contains("PRIVATE")) { + throw new UnloggedFailure(1, "Please provide a PUBLIC key, not a PRIVATE key!"); + } + SshKey key = new SshKey(rawData); + return key; + } +} diff --git a/src/main/java/com/gitblit/transport/ssh/keys/KeysDispatcher.java b/src/main/java/com/gitblit/transport/ssh/keys/KeysDispatcher.java new file mode 100644 index 00000000..ad373060 --- /dev/null +++ b/src/main/java/com/gitblit/transport/ssh/keys/KeysDispatcher.java @@ -0,0 +1,252 @@ +/* + * 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.transport.ssh.keys; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import org.kohsuke.args4j.Argument; +import org.kohsuke.args4j.Option; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.gitblit.models.UserModel; +import com.gitblit.transport.ssh.IPublicKeyManager; +import com.gitblit.transport.ssh.SshKey; +import com.gitblit.transport.ssh.commands.CommandMetaData; +import com.gitblit.transport.ssh.commands.DispatchCommand; +import com.gitblit.transport.ssh.commands.SshCommand; +import com.gitblit.transport.ssh.commands.UsageExample; +import com.gitblit.utils.FlipTable; +import com.gitblit.utils.FlipTable.Borders; +import com.google.common.base.Joiner; + +/** + * The dispatcher and it's commands for SSH public key management. + * + * @author James Moger + * + */ +@CommandMetaData(name = "keys", description = "SSH public key management commands") +public class KeysDispatcher extends DispatchCommand { + + @Override + protected void setup(UserModel user) { + register(user, AddKey.class); + register(user, RemoveKey.class); + register(user, ListKeys.class); + register(user, WhichKey.class); + register(user, CommentKey.class); + } + + @CommandMetaData(name = "add", description = "Add an SSH public key to your account") + @UsageExample(syntax = "cat ~/.ssh/id_rsa.pub | ${ssh} ${cmd} -", description = "Upload your SSH public key and add it to your account") + public static class AddKey extends BaseKeyCommand { + + protected final Logger log = LoggerFactory.getLogger(getClass()); + + @Argument(metaVar = "", usage = "the key(s) to add") + private List addKeys = new ArrayList(); + + @Override + public void run() throws IOException, UnloggedFailure { + String username = getContext().getClient().getUsername(); + List keys = readKeys(addKeys); + for (String key : keys) { + SshKey sshKey = parseKey(key); + getKeyManager().addKey(username, sshKey); + log.info("added SSH public key for {}", username); + } + } + } + + @CommandMetaData(name = "remove", aliases = { "rm" }, description = "Remove an SSH public key from your account") + @UsageExample(syntax = "${cmd} 2", description = "Remove the SSH key identified as #2 in `keys list`") + public static class RemoveKey extends BaseKeyCommand { + + protected final Logger log = LoggerFactory.getLogger(getClass()); + + private final String ALL = "ALL"; + + @Argument(metaVar = "||ALL", usage = "the key to remove", required = true) + private List removeKeys = new ArrayList(); + + @Override + public void run() throws IOException, UnloggedFailure { + 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()) { + throw new UnloggedFailure(1, "There are no registered keys!"); + } + + List keys = readKeys(removeKeys); + if (keys.contains(ALL)) { + if (getKeyManager().removeAllKeys(username)) { + stdout.println("Removed all keys."); + log.info("removed all SSH public keys from {}", username); + } else { + log.warn("failed to remove all SSH public keys from {}", username); + } + } else { + for (String key : keys) { + 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."); + } + throw new UnloggedFailure(1, String.format("Invalid index specified. There are %d registered keys.", keys.size())); + } + SshKey sshKey = currentKeys.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())); + } + } + } + } + } + } + + @CommandMetaData(name = "list", aliases = { "ls" }, description = "List your registered SSH public keys") + public static class ListKeys extends SshCommand { + + @Option(name = "-L", usage = "list complete public key parameters") + private boolean showRaw; + + @Override + public void run() { + IPublicKeyManager keyManager = getContext().getGitblit().getPublicKeyManager(); + String username = getContext().getClient().getUsername(); + List keys = keyManager.getKeys(username); + + if (showRaw) { + asRaw(keys); + } else { + asTable(keys); + } + } + + /* output in the same format as authorized_keys */ + protected void asRaw(List keys) { + if (keys == null) { + return; + } + for (SshKey key : keys) { + stdout.println(key.getRawData()); + } + } + + protected void asTable(List keys) { + String[] headers = { "#", "Fingerprint", "Comment", "Type" }; + int len = keys == null ? 0 : keys.size(); + Object[][] data = new Object[len][]; + for (int i = 0; i < len; i++) { + // show 1-based index numbers with the fingerprint + // this is useful for comparing with "ssh-add -l" + SshKey k = keys.get(i); + data[i] = new Object[] { (i + 1), k.getFingerprint(), k.getComment(), k.getAlgorithm() }; + } + + stdout.println(FlipTable.of(headers, data, Borders.BODY_HCOLS)); + } + } + + @CommandMetaData(name = "which", description = "Display the SSH public key used for this session") + public static class WhichKey extends SshCommand { + + @Option(name = "-L", usage = "list complete public key parameters") + private boolean showRaw; + + @Override + public void run() throws UnloggedFailure { + SshKey key = getContext().getClient().getKey(); + if (key == null) { + throw new UnloggedFailure(1, "You have not authenticated with an SSH public key."); + } + + if (showRaw) { + stdout.println(key.getRawData()); + } else { + final String username = getContext().getClient().getUsername(); + List keys = getContext().getGitblit().getPublicKeyManager().getKeys(username); + int index = 0; + for (int i = 0; i < keys.size(); i++) { + if (key.equals(keys.get(i))) { + index = i + 1; + break; + } + } + asTable(index, key); + } + } + + protected void asTable(int index, SshKey key) { + String[] headers = { "#", "Fingerprint", "Comment", "Type" }; + Object[][] data = new Object[1][]; + data[0] = new Object[] { index, key.getFingerprint(), key.getComment(), key.getAlgorithm() }; + + stdout.println(FlipTable.of(headers, data, Borders.BODY_HCOLS)); + } + } + + @CommandMetaData(name = "comment", description = "Set the comment for an SSH public key") + @UsageExample(syntax = "${cmd} 3 Home workstation", description = "Set the comment for key #3") + public static class CommentKey extends SshCommand { + + @Argument(index = 0, metaVar = "INDEX", usage = "the key index", required = true) + private int index; + + @Argument(index = 1, metaVar = "COMMENT", usage = "the new comment", required = true) + private List values = new ArrayList(); + + @Override + public void run() throws UnloggedFailure { + final String username = getContext().getClient().getUsername(); + IPublicKeyManager keyManager = getContext().getGitblit().getPublicKeyManager(); + List keys = keyManager.getKeys(username); + if (index > keys.size()) { + throw new UnloggedFailure(1, "Invalid key index!"); + } + + String comment = Joiner.on(" ").join(values); + SshKey key = keys.get(index - 1); + key.setComment(comment); + 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)); + } + } + + } +} diff --git a/src/site/setup_transport_ssh.mkd b/src/site/setup_transport_ssh.mkd index 0f09910e..a671e5af 100644 --- a/src/site/setup_transport_ssh.mkd +++ b/src/site/setup_transport_ssh.mkd @@ -23,8 +23,8 @@ First you'll need to create an SSH key pair, if you don't already have one or if Then you can upload your *public* key right from the command-line. - cat ~/.ssh/id_rsa.pub | ssh -l -p 29418 gitblit keys add - cat c:\\.ssh\id_rsa.pub | ssh -l -p 29418 gitblit keys add + cat ~/.ssh/id_rsa.pub | ssh -l -p 29418 keys add + cat c:\\.ssh\id_rsa.pub | ssh -l -p 29418 keys add **NOTE:** It is important to note that *ssh-keygen* generates a public/private keypair (e.g. id_rsa and id_rsa.pub). You want to upload the *public* key, which is denoted by the *.pub* file extension. @@ -36,7 +36,7 @@ Once you've done both of those steps you should be able to execute the following Typing the following command syntax all the time gets to be rather tedious. - ssh -l -p 29418 gitblit version + ssh -l -p 29418 You can define an alias for your server which will reduce your command syntax to something like this. @@ -54,29 +54,33 @@ Create or modify your `~/.ssh/config` file and add a host entry. If you are on Gitblit supports SSH command plugins and provides several commands out-of-the-box. -#### gitblit +#### keys -The *gitblit* command has many subcommands for interacting with Gitblit. +The *keys* command dispatcher allows you to manage your public ssh keys. You can list keys, add keys, remove keys, and identify the key in-use for the active session. ##### keys add Add an SSH public key to your account. This command accepts a public key piped to stdin. - cat ~/.ssh/id_rsa.pub | ssh -l -p 29418 gitblit keys add + cat ~/.ssh/id_rsa.pub | ssh -l -p 29418 keys add ##### keys list Show the SSH public keys you have added to your account. - ssh -l -p 29418 gitblit keys list + ssh -l -p 29418 keys list ##### keys remove Remove an SSH public key from your account. This command accepts several input values, the most useful one is an index number which matches the index number displayed in the `list` command. - ssh -l -p 29418 gitblit keys remove 2 + ssh -l -p 29418 keys remove 2 You can also remove all your public keys from your account. - ssh -l -p 29418 gitblit keys remove ALL + ssh -l -p 29418 keys remove ALL + +### SSH Command Plugins + +Gitblit supports loading custom SSH command plugins. diff --git a/src/test/java/com/gitblit/tests/SshDaemonTest.java b/src/test/java/com/gitblit/tests/SshDaemonTest.java index dbd1d868..620190ef 100644 --- a/src/test/java/com/gitblit/tests/SshDaemonTest.java +++ b/src/test/java/com/gitblit/tests/SshDaemonTest.java @@ -93,7 +93,7 @@ public class SshDaemonTest extends GitblitUnitTest { pair.getPublic().getEncoded(); assertTrue(session.authPublicKey("admin", pair).await().isSuccess()); - ClientChannel channel = session.createChannel(ClientChannel.CHANNEL_EXEC, "gitblit version"); + ClientChannel channel = session.createChannel(ClientChannel.CHANNEL_EXEC, "version"); ByteArrayOutputStream baos = new ByteArrayOutputStream(); Writer w = new OutputStreamWriter(baos); w.close(); -- cgit v1.2.3 From 521cb6022a9ee30bf3115a8dcb991aa5c7e420e3 Mon Sep 17 00:00:00 2001 From: James Moger Date: Mon, 7 Apr 2014 22:57:47 -0400 Subject: Unit tests for ssh daemon and keys dispatcher --- .../ssh/CachingPublicKeyAuthenticator.java | 27 ++-- .../com/gitblit/transport/ssh/FileKeyManager.java | 4 +- .../gitblit/transport/ssh/IPublicKeyManager.java | 7 +- .../gitblit/transport/ssh/MemoryKeyManager.java | 20 ++- .../java/com/gitblit/transport/ssh/SshKey.java | 4 +- .../transport/ssh/SshServerSessionFactory.java | 4 +- .../gitblit/transport/ssh/keys/KeysDispatcher.java | 52 ++++---- src/main/java/log4j.properties | 4 +- src/test/java/com/gitblit/tests/GitBlitSuite.java | 18 ++- src/test/java/com/gitblit/tests/SshDaemonTest.java | 134 ++++++++------------ .../com/gitblit/tests/SshKeysDispatcherTest.java | 115 +++++++++++++++++ src/test/java/com/gitblit/tests/SshUnitTest.java | 141 +++++++++++++++++++++ src/test/java/com/gitblit/tests/SshUtils.java | 74 ----------- 13 files changed, 388 insertions(+), 216 deletions(-) create mode 100644 src/test/java/com/gitblit/tests/SshKeysDispatcherTest.java create mode 100644 src/test/java/com/gitblit/tests/SshUnitTest.java delete mode 100644 src/test/java/com/gitblit/tests/SshUtils.java (limited to 'src/test/java') 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(); - } - } - -} -- cgit v1.2.3 From e5d6095f0d804cb62d00fbb164f7c49371d412d6 Mon Sep 17 00:00:00 2001 From: James Moger Date: Fri, 11 Apr 2014 13:45:51 -0400 Subject: Add missing unit test utility class --- .../tests/JschConfigTestSessionFactory.java | 33 ++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 src/test/java/com/gitblit/tests/JschConfigTestSessionFactory.java (limited to 'src/test/java') diff --git a/src/test/java/com/gitblit/tests/JschConfigTestSessionFactory.java b/src/test/java/com/gitblit/tests/JschConfigTestSessionFactory.java new file mode 100644 index 00000000..5d24b401 --- /dev/null +++ b/src/test/java/com/gitblit/tests/JschConfigTestSessionFactory.java @@ -0,0 +1,33 @@ +package com.gitblit.tests; + +import java.security.KeyPair; + +import org.eclipse.jgit.transport.JschConfigSessionFactory; +import org.eclipse.jgit.transport.OpenSshConfig; +import org.eclipse.jgit.util.FS; + +import com.jcraft.jsch.JSch; +import com.jcraft.jsch.JSchException; +import com.jcraft.jsch.Session; + +public class JschConfigTestSessionFactory extends JschConfigSessionFactory { + + final KeyPair keyPair; + + public JschConfigTestSessionFactory(KeyPair keyPair) { + this.keyPair = keyPair; + } + + @Override + protected void configure(OpenSshConfig.Host host, Session session) { + session.setConfig("StrictHostKeyChecking", "no"); + } + + @Override + protected JSch getJSch(final OpenSshConfig.Host hc, FS fs) throws JSchException { + JSch jsch = super.getJSch(hc, fs); +// jsch.removeAllIdentity(); +// jsch.addIdentity("unittest", keyPair.getPrivate().getEncoded(), keyPair.getPublic().getEncoded(), null); + return jsch; + } +} \ No newline at end of file -- cgit v1.2.3