From e3b636e7fa2a823cfe90ea75e88034a60f7e59e6 Mon Sep 17 00:00:00 2001 From: David Ostrovsky Date: Sat, 22 Feb 2014 21:17:03 +0100 Subject: [PATCH] SSHD: Add support for git pack commands Add git-upload-pack and git-receive-pack commands. Conflicts: src/main/java/com/gitblit/manager/ServicesManager.java src/main/java/com/gitblit/transport/ssh/CommandDispatcher.java src/main/java/com/gitblit/transport/ssh/SshCommandFactory.java Change-Id: I8c057b41f1dfad6d004e6aa91f96c8c673be9be2 --- src/main/java/com/gitblit/Constants.java | 2 +- .../git/GitblitReceivePackFactory.java | 8 + .../com/gitblit/git/RepositoryResolver.java | 10 ++ .../manager/AuthenticationManager.java | 29 ++++ .../com/gitblit/manager/GitblitManager.java | 7 + .../manager/IAuthenticationManager.java | 3 + .../com/gitblit/manager/ServicesManager.java | 1 - .../transport/ssh/AbstractGitCommand.java | 108 +++++++++++++ .../transport/ssh/SshCommandFactory.java | 147 ++---------------- .../com/gitblit/transport/ssh/SshContext.java | 35 +++++ .../com/gitblit/transport/ssh/SshDaemon.java | 24 +-- .../com/gitblit/transport/ssh/SshSession.java | 9 ++ .../transport/ssh/commands/BaseCommand.java | 38 +++-- .../ssh/commands/DispatchCommand.java | 48 +++++- .../transport/ssh/commands/Receive.java | 34 ++++ .../transport/ssh/commands/Upload.java | 39 +++++ .../ssh/commands/VersionCommand.java | 2 +- 17 files changed, 379 insertions(+), 165 deletions(-) create mode 100644 src/main/java/com/gitblit/transport/ssh/AbstractGitCommand.java create mode 100644 src/main/java/com/gitblit/transport/ssh/SshContext.java create mode 100644 src/main/java/com/gitblit/transport/ssh/commands/Receive.java create mode 100644 src/main/java/com/gitblit/transport/ssh/commands/Upload.java diff --git a/src/main/java/com/gitblit/Constants.java b/src/main/java/com/gitblit/Constants.java index 2a98b53f..889e5a30 100644 --- a/src/main/java/com/gitblit/Constants.java +++ b/src/main/java/com/gitblit/Constants.java @@ -501,7 +501,7 @@ public class Constants { } public static enum AuthenticationType { - CREDENTIALS, COOKIE, CERTIFICATE, CONTAINER; + SSH, CREDENTIALS, COOKIE, CERTIFICATE, CONTAINER; public boolean isStandard() { return ordinal() <= COOKIE.ordinal(); diff --git a/src/main/java/com/gitblit/git/GitblitReceivePackFactory.java b/src/main/java/com/gitblit/git/GitblitReceivePackFactory.java index 7976fe56..9911258c 100644 --- a/src/main/java/com/gitblit/git/GitblitReceivePackFactory.java +++ b/src/main/java/com/gitblit/git/GitblitReceivePackFactory.java @@ -31,6 +31,7 @@ import com.gitblit.Keys; import com.gitblit.manager.IGitblit; import com.gitblit.models.RepositoryModel; import com.gitblit.models.UserModel; +import com.gitblit.transport.ssh.SshSession; import com.gitblit.utils.HttpUtils; import com.gitblit.utils.StringUtils; @@ -88,6 +89,13 @@ public class GitblitReceivePackFactory implements ReceivePackFactory { // set timeout from Git daemon timeout = client.getDaemon().getTimeout(); + } else if (req instanceof SshSession) { + // SSH request is always authenticated + SshSession s = (SshSession) req; + repositoryName = s.getRepositoryName(); + origin = s.getRemoteAddress().toString(); + String username = s.getRemoteUser(); + user = gitblit.getUserModel(username); } boolean allowAnonymousPushes = settings.getBoolean(Keys.git.allowAnonymousPushes, false); diff --git a/src/main/java/com/gitblit/git/RepositoryResolver.java b/src/main/java/com/gitblit/git/RepositoryResolver.java index 208c1ae1..c859f6f6 100644 --- a/src/main/java/com/gitblit/git/RepositoryResolver.java +++ b/src/main/java/com/gitblit/git/RepositoryResolver.java @@ -30,6 +30,7 @@ import org.slf4j.LoggerFactory; import com.gitblit.manager.IGitblit; import com.gitblit.models.RepositoryModel; import com.gitblit.models.UserModel; +import com.gitblit.transport.ssh.SshSession; /** * Resolves repositories and grants export access. @@ -67,6 +68,9 @@ public class RepositoryResolver extends FileResolver { // git request GitDaemonClient client = (GitDaemonClient) req; client.setRepositoryName(name); + } else if (req instanceof SshSession) { + SshSession s = (SshSession)req; + s.setRepositoryName(name); } return repo; } @@ -98,6 +102,12 @@ public class RepositoryResolver extends FileResolver { if (user == null) { user = UserModel.ANONYMOUS; } + } else if (req instanceof SshSession) { + SshSession s = (SshSession) req; + user = gitblit.authenticate(s); + if (user == null) { + throw new IOException(String.format("User %s not found", s.getRemoteUser())); + } } if (user.canClone(model)) { diff --git a/src/main/java/com/gitblit/manager/AuthenticationManager.java b/src/main/java/com/gitblit/manager/AuthenticationManager.java index 4f3e652c..47425ce7 100644 --- a/src/main/java/com/gitblit/manager/AuthenticationManager.java +++ b/src/main/java/com/gitblit/manager/AuthenticationManager.java @@ -47,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.SshSession; import com.gitblit.utils.Base64; import com.gitblit.utils.HttpUtils; import com.gitblit.utils.StringUtils; @@ -289,6 +290,34 @@ public class AuthenticationManager implements IAuthenticationManager { return null; } + /** + * Authenticate a user based on SSH session. + * + * @param SshSession + * @return a user object or null + */ + @Override + public UserModel authenticate(SshSession sshSession) { + String username = sshSession.getRemoteUser(); + if (username != null) { + if (!StringUtils.isEmpty(username)) { + UserModel user = userManager.getUserModel(username); + if (user != null) { + // existing user + logger.debug(MessageFormat.format("{0} authenticated by servlet container principal from {1}", + user.username, sshSession.getRemoteAddress())); + return validateAuthentication(user, AuthenticationType.SSH); + } + logger.warn(MessageFormat.format("Failed to find UserModel for {0}, attempted ssh authentication from {1}", + username, sshSession.getRemoteAddress())); + } + } else { + logger.warn("Empty user in SSH session"); + } + return null; + } + + /** * This method allows the authentication manager to reject authentication * attempts. It is called after the username/secret have been verified to diff --git a/src/main/java/com/gitblit/manager/GitblitManager.java b/src/main/java/com/gitblit/manager/GitblitManager.java index b6c2b474..a5a26379 100644 --- a/src/main/java/com/gitblit/manager/GitblitManager.java +++ b/src/main/java/com/gitblit/manager/GitblitManager.java @@ -68,6 +68,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.SshSession; import com.gitblit.utils.ArrayUtils; import com.gitblit.utils.HttpUtils; import com.gitblit.utils.JsonUtils; @@ -651,6 +652,12 @@ public class GitblitManager implements IGitblit { } return user; } + + @Override + public UserModel authenticate(SshSession sshSession) { + return authenticationManager.authenticate(sshSession); + } + @Override public UserModel authenticate(HttpServletRequest httpRequest, boolean requiresCertificate) { UserModel user = authenticationManager.authenticate(httpRequest, requiresCertificate); diff --git a/src/main/java/com/gitblit/manager/IAuthenticationManager.java b/src/main/java/com/gitblit/manager/IAuthenticationManager.java index 3007a303..5d98d127 100644 --- a/src/main/java/com/gitblit/manager/IAuthenticationManager.java +++ b/src/main/java/com/gitblit/manager/IAuthenticationManager.java @@ -20,6 +20,7 @@ import javax.servlet.http.HttpServletResponse; import com.gitblit.models.TeamModel; import com.gitblit.models.UserModel; +import com.gitblit.transport.ssh.SshSession; public interface IAuthenticationManager extends IManager { @@ -33,6 +34,8 @@ public interface IAuthenticationManager extends IManager { */ UserModel authenticate(HttpServletRequest httpRequest); + public UserModel authenticate(SshSession sshSession); + /** * Authenticate a user based on HTTP request parameters. * diff --git a/src/main/java/com/gitblit/manager/ServicesManager.java b/src/main/java/com/gitblit/manager/ServicesManager.java index df8918ed..11083be3 100644 --- a/src/main/java/com/gitblit/manager/ServicesManager.java +++ b/src/main/java/com/gitblit/manager/ServicesManager.java @@ -247,6 +247,5 @@ public class ServicesManager implements IManager { "Next pull of {0} @ {1} scheduled for {2,date,yyyy-MM-dd HH:mm}", registration.name, registration.url, registration.nextPull)); } - } } diff --git a/src/main/java/com/gitblit/transport/ssh/AbstractGitCommand.java b/src/main/java/com/gitblit/transport/ssh/AbstractGitCommand.java new file mode 100644 index 00000000..bba6a402 --- /dev/null +++ b/src/main/java/com/gitblit/transport/ssh/AbstractGitCommand.java @@ -0,0 +1,108 @@ +/* + * 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.io.IOException; + +import org.apache.sshd.server.Environment; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.transport.resolver.ReceivePackFactory; +import org.eclipse.jgit.transport.resolver.UploadPackFactory; +import org.kohsuke.args4j.Argument; + +import com.gitblit.git.GitblitReceivePackFactory; +import com.gitblit.git.GitblitUploadPackFactory; +import com.gitblit.git.RepositoryResolver; +import com.gitblit.transport.ssh.commands.BaseCommand; + +/** + * @author Eric Myhre + * + */ +public abstract class AbstractGitCommand extends BaseCommand { + @Argument(index = 0, metaVar = "PROJECT.git", required = true, usage = "project name") + protected String repository; + + protected RepositoryResolver repositoryResolver; + protected ReceivePackFactory receivePackFactory; + protected UploadPackFactory uploadPackFactory; + + protected Repository repo; + + @Override + public void start(final Environment env) { + startThread(new RepositoryCommandRunnable() { + @Override + public void run() throws Exception { + parseCommandLine(); + AbstractGitCommand.this.service(); + } + + @Override + public String getRepository() { + return repository; + } + }); + } + + private void service() throws IOException, Failure { + try { + repo = openRepository(); + runImpl(); + } finally { + if (repo != null) { + repo.close(); + } + } + } + + protected abstract void runImpl() throws IOException, Failure; + + protected Repository openRepository() throws Failure { + // Assume any attempt to use \ was by a Windows client + // and correct to the more typical / used in Git URIs. + // + repository = repository.replace('\\', '/'); + // ssh://git@thishost/path should always be name="/path" here + // + if (!repository.startsWith("/")) { + throw new Failure(1, "fatal: '" + repository + + "': not starts with / character"); + } + repository = repository.substring(1); + try { + return repositoryResolver.open(ctx.getSession(), repository); + } catch (Exception e) { + throw new Failure(1, "fatal: '" + repository + + "': not a git archive", e); + } + } + + public void setRepositoryResolver( + RepositoryResolver repositoryResolver) { + this.repositoryResolver = repositoryResolver; + } + + public void setReceivePackFactory( + GitblitReceivePackFactory receivePackFactory) { + this.receivePackFactory = receivePackFactory; + } + + public void setUploadPackFactory( + GitblitUploadPackFactory uploadPackFactory) { + this.uploadPackFactory = uploadPackFactory; + } +} \ No newline at end of file diff --git a/src/main/java/com/gitblit/transport/ssh/SshCommandFactory.java b/src/main/java/com/gitblit/transport/ssh/SshCommandFactory.java index 056938e9..0c8492f7 100644 --- a/src/main/java/com/gitblit/transport/ssh/SshCommandFactory.java +++ b/src/main/java/com/gitblit/transport/ssh/SshCommandFactory.java @@ -31,20 +31,9 @@ import org.apache.sshd.server.Environment; import org.apache.sshd.server.ExitCallback; import org.apache.sshd.server.SessionAware; import org.apache.sshd.server.session.ServerSession; -import org.eclipse.jgit.errors.RepositoryNotFoundException; -import org.eclipse.jgit.lib.Repository; -import org.eclipse.jgit.transport.PacketLineOut; -import org.eclipse.jgit.transport.ReceivePack; -import org.eclipse.jgit.transport.ServiceMayNotContinueException; -import org.eclipse.jgit.transport.UploadPack; -import org.eclipse.jgit.transport.resolver.ReceivePackFactory; -import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException; -import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException; -import org.eclipse.jgit.transport.resolver.UploadPackFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.gitblit.git.RepositoryResolver; import com.gitblit.transport.ssh.commands.DispatchCommand; import com.gitblit.utils.WorkQueue; import com.google.common.util.concurrent.Atomics; @@ -57,23 +46,13 @@ import com.google.common.util.concurrent.Atomics; public class SshCommandFactory implements CommandFactory { private static final Logger logger = LoggerFactory .getLogger(SshCommandFactory.class); - private RepositoryResolver repositoryResolver; - - private UploadPackFactory uploadPackFactory; - - private ReceivePackFactory receivePackFactory; private final ScheduledExecutorService startExecutor; private DispatchCommand dispatcher; - public SshCommandFactory(RepositoryResolver repositoryResolver, - UploadPackFactory uploadPackFactory, - ReceivePackFactory receivePackFactory, + public SshCommandFactory( WorkQueue workQueue, DispatchCommand d) { - this.repositoryResolver = repositoryResolver; - this.uploadPackFactory = uploadPackFactory; - this.receivePackFactory = receivePackFactory; this.dispatcher = d; int threads = 2;//cfg.getInt("sshd","commandStartThreads", 2); startExecutor = workQueue.createQueue(threads, "SshCommandStart"); @@ -82,35 +61,34 @@ public class SshCommandFactory implements CommandFactory { @Override public Command createCommand(final String commandLine) { return new Trampoline(commandLine); - /* - if ("git-upload-pack".equals(command)) - return new UploadPackCommand(argument); - if ("git-receive-pack".equals(command)) - return new ReceivePackCommand(argument); - return new NonCommand(); - */ } private class Trampoline implements Command, SessionAware { private final String[] argv; + private ServerSession session; private InputStream in; private OutputStream out; private OutputStream err; private ExitCallback exit; private Environment env; + private String cmdLine; private DispatchCommand cmd; private final AtomicBoolean logged; private final AtomicReference> task; - Trampoline(final String cmdLine) { - argv = split(cmdLine); + Trampoline(String line) { + if (line.startsWith("git-")) { + line = "git " + line; + } + cmdLine = line; + argv = split(line); logged = new AtomicBoolean(); task = Atomics.newReference(); } @Override public void setSession(ServerSession session) { - // TODO Auto-generated method stub + this.session = session; } @Override @@ -148,18 +126,18 @@ public class SshCommandFactory implements CommandFactory { @Override public String toString() { - //return "start (user " + ctx.getSession().getUsername() + ")"; - return "start (user TODO)"; + return "start (user " + session.getUsername() + ")"; } })); } private void onStart() throws IOException { synchronized (this) { - //final Context old = sshScope.set(ctx); + SshContext ctx = new SshContext(session.getAttribute(SshSession.KEY), cmdLine); try { cmd = dispatcher; cmd.setArguments(argv); + cmd.setContext(ctx); cmd.setInputStream(in); cmd.setOutputStream(out); cmd.setErrorStream(err); @@ -178,7 +156,7 @@ public class SshCommandFactory implements CommandFactory { }); cmd.start(env); } finally { - //sshScope.set(old); + ctx = null; } } } @@ -286,101 +264,4 @@ public class SshCommandFactory implements CommandFactory { } return list.toArray(new String[list.size()]); } - - public abstract class RepositoryCommand extends AbstractSshCommand { - protected final String repositoryName; - - public RepositoryCommand(String repositoryName) { - this.repositoryName = repositoryName; - } - - @Override - public void start(Environment env) throws IOException { - Repository db = null; - try { - SshSession client = session.getAttribute(SshSession.KEY); - db = selectRepository(client, repositoryName); - if (db == null) return; - run(client, db); - exit.onExit(0); - } catch (ServiceNotEnabledException e) { - // Ignored. Client cannot use this repository. - } catch (ServiceNotAuthorizedException e) { - // Ignored. Client cannot use this repository. - } finally { - if (db != null) - db.close(); - exit.onExit(1); - } - } - - protected Repository selectRepository(SshSession client, String name) throws IOException { - try { - return openRepository(client, name); - } catch (ServiceMayNotContinueException e) { - // An error when opening the repo means the client is expecting a ref - // advertisement, so use that style of error. - PacketLineOut pktOut = new PacketLineOut(out); - pktOut.writeString("ERR " + e.getMessage() + "\n"); //$NON-NLS-1$ //$NON-NLS-2$ - return null; - } - } - - protected Repository openRepository(SshSession client, String name) - throws ServiceMayNotContinueException { - // Assume any attempt to use \ was by a Windows client - // and correct to the more typical / used in Git URIs. - // - name = name.replace('\\', '/'); - - // ssh://git@thishost/path should always be name="/path" here - // - if (!name.startsWith("/")) //$NON-NLS-1$ - return null; - - try { - return repositoryResolver.open(client, name.substring(1)); - } catch (RepositoryNotFoundException e) { - // null signals it "wasn't found", which is all that is suitable - // for the remote client to know. - return null; - } catch (ServiceNotEnabledException e) { - // null signals it "wasn't found", which is all that is suitable - // for the remote client to know. - return null; - } - } - - protected abstract void run(SshSession client, Repository db) - throws IOException, ServiceNotEnabledException, ServiceNotAuthorizedException; - } - - public class UploadPackCommand extends RepositoryCommand { - public UploadPackCommand(String repositoryName) { super(repositoryName); } - - @Override - protected void run(SshSession client, Repository db) - throws IOException, ServiceNotEnabledException, ServiceNotAuthorizedException { - UploadPack up = uploadPackFactory.create(client, db); - up.upload(in, out, null); - } - } - - public class ReceivePackCommand extends RepositoryCommand { - public ReceivePackCommand(String repositoryName) { super(repositoryName); } - - @Override - protected void run(SshSession client, Repository db) - throws IOException, ServiceNotEnabledException, ServiceNotAuthorizedException { - ReceivePack rp = receivePackFactory.create(client, db); - rp.receive(in, out, null); - } - } - - public static class NonCommand extends AbstractSshCommand { - @Override - public void start(Environment env) { - exit.onExit(127); - } - } } diff --git a/src/main/java/com/gitblit/transport/ssh/SshContext.java b/src/main/java/com/gitblit/transport/ssh/SshContext.java new file mode 100644 index 00000000..b137cb87 --- /dev/null +++ b/src/main/java/com/gitblit/transport/ssh/SshContext.java @@ -0,0 +1,35 @@ +/* + * 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; + +public class SshContext { + + private final SshSession session; + private final String commandLine; + + public SshContext(SshSession session, String commandLine) { + this.session = session; + this.commandLine = commandLine; + } + + public SshSession getSession() { + return session; + } + + public String getCommandLine() { + return commandLine; + } +} diff --git a/src/main/java/com/gitblit/transport/ssh/SshDaemon.java b/src/main/java/com/gitblit/transport/ssh/SshDaemon.java index dd4a2d8e..b23ddd58 100644 --- a/src/main/java/com/gitblit/transport/ssh/SshDaemon.java +++ b/src/main/java/com/gitblit/transport/ssh/SshDaemon.java @@ -35,6 +35,8 @@ import com.gitblit.git.RepositoryResolver; import com.gitblit.manager.IGitblit; import com.gitblit.transport.ssh.commands.CreateRepository; import com.gitblit.transport.ssh.commands.DispatchCommand; +import com.gitblit.transport.ssh.commands.Receive; +import com.gitblit.transport.ssh.commands.Upload; import com.gitblit.transport.ssh.commands.VersionCommand; import com.gitblit.utils.IdGenerator; import com.gitblit.utils.StringUtils; @@ -65,9 +67,6 @@ public class SshDaemon { @SuppressWarnings("unused") private final IGitblit gitblit; - - private final IdGenerator idGenerator; - private final SshServer sshd; /** @@ -77,7 +76,6 @@ public class SshDaemon { */ public SshDaemon(IGitblit gitblit, IdGenerator idGenerator) { this.gitblit = gitblit; - this.idGenerator = idGenerator; IStoredSettings settings = gitblit.getSettings(); int port = settings.getInteger(Keys.git.sshPort, 0); @@ -106,15 +104,21 @@ public class SshDaemon { gitblitCmd.registerCommand(CreateRepository.class); gitblitCmd.registerCommand(VersionCommand.class); - DispatchCommand dispatcher = new DispatchCommand(); - dispatcher.registerDispatcher("gitblit", gitblitCmd); + DispatchCommand gitCmd = new DispatchCommand(); + gitCmd.registerCommand(Upload.class); + gitCmd.registerCommand(Receive.class); + + DispatchCommand root = new DispatchCommand(); + root.registerDispatcher("gitblit", gitblitCmd); + root.registerDispatcher("git", gitCmd); + + root.setRepositoryResolver(new RepositoryResolver(gitblit)); + root.setUploadPackFactory(new GitblitUploadPackFactory(gitblit)); + root.setReceivePackFactory(new GitblitReceivePackFactory(gitblit)); SshCommandFactory commandFactory = new SshCommandFactory( - new RepositoryResolver(gitblit), - new GitblitUploadPackFactory(gitblit), - new GitblitReceivePackFactory(gitblit), new WorkQueue(idGenerator), - dispatcher); + root); sshd.setCommandFactory(commandFactory); diff --git a/src/main/java/com/gitblit/transport/ssh/SshSession.java b/src/main/java/com/gitblit/transport/ssh/SshSession.java index 9f18a197..ffff8af4 100644 --- a/src/main/java/com/gitblit/transport/ssh/SshSession.java +++ b/src/main/java/com/gitblit/transport/ssh/SshSession.java @@ -36,6 +36,7 @@ public class SshSession { private volatile String username; private volatile String authError; + private volatile String repositoryName; SshSession(int sessionId, SocketAddress peer) { this.sessionId = sessionId; @@ -78,6 +79,14 @@ public class SshSession { authError = error; } + public void setRepositoryName(String repositoryName) { + this.repositoryName = repositoryName; + } + + public String getRepositoryName() { + return repositoryName; + } + /** @return {@code true} if the authentication did not succeed. */ boolean isAuthenticationError() { return authError != null; diff --git a/src/main/java/com/gitblit/transport/ssh/commands/BaseCommand.java b/src/main/java/com/gitblit/transport/ssh/commands/BaseCommand.java index fd73ccfd..a04c505f 100644 --- a/src/main/java/com/gitblit/transport/ssh/commands/BaseCommand.java +++ b/src/main/java/com/gitblit/transport/ssh/commands/BaseCommand.java @@ -33,8 +33,10 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.gitblit.transport.ssh.AbstractSshCommand; +import com.gitblit.transport.ssh.SshContext; import com.gitblit.utils.IdGenerator; import com.gitblit.utils.WorkQueue; +import com.gitblit.utils.WorkQueue.CancelableRunnable; import com.gitblit.utils.cli.CmdLineParser; import com.google.common.base.Charsets; import com.google.common.util.concurrent.Atomics; @@ -49,6 +51,9 @@ public abstract class BaseCommand extends AbstractSshCommand { /** Unparsed command line options. */ private String[] argv; + /** Ssh context */ + protected SshContext ctx; + /** The task, as scheduled on a worker thread. */ private final AtomicReference> task; @@ -61,6 +66,10 @@ public abstract class BaseCommand extends AbstractSshCommand { this.executor = w.getDefaultQueue(); } + public void setContext(SshContext ctx) { + this.ctx = ctx; + } + public void setInputStream(final InputStream in) { this.in = in; } @@ -77,7 +86,10 @@ public abstract class BaseCommand extends AbstractSshCommand { this.exit = callback; } - protected void provideStateTo(final Command cmd) { + protected void provideBaseStateTo(final Command cmd) { + if (cmd instanceof BaseCommand) { + ((BaseCommand)cmd).setContext(ctx); + } cmd.setInputStream(in); cmd.setOutputStream(out); cmd.setErrorStream(err); @@ -155,31 +167,25 @@ public abstract class BaseCommand extends AbstractSshCommand { return ""; } - private final class TaskThunk implements com.gitblit.utils.WorkQueue.CancelableRunnable { + private final class TaskThunk implements CancelableRunnable { private final CommandRunnable thunk; private final String taskName; private TaskThunk(final CommandRunnable thunk) { this.thunk = thunk; - // TODO -// StringBuilder m = new StringBuilder("foo"); -// m.append(context.getCommandLine()); -// if (userProvider.get().isIdentifiedUser()) { -// IdentifiedUser u = (IdentifiedUser) userProvider.get(); -// m.append(" (").append(u.getAccount().getUserName()).append(")"); -// } - this.taskName = "foo";//m.toString(); + StringBuilder m = new StringBuilder(); + m.append(ctx.getCommandLine()); + this.taskName = m.toString(); } @Override public void cancel() { synchronized (this) { - //final Context old = sshScope.set(context); try { //onExit(/*STATUS_CANCEL*/); } finally { - //sshScope.set(old); + ctx = null; } } } @@ -190,11 +196,8 @@ public abstract class BaseCommand extends AbstractSshCommand { final Thread thisThread = Thread.currentThread(); final String thisName = thisThread.getName(); int rc = 0; - //final Context old = sshScope.set(context); try { - //context.started = TimeUtil.nowMs(); thisThread.setName("SSH " + taskName); - thunk.run(); out.flush(); @@ -231,6 +234,11 @@ public abstract class BaseCommand extends AbstractSshCommand { } + /** Runnable function which can retrieve a project name related to the task */ + public static interface RepositoryCommandRunnable extends CommandRunnable { + public String getRepository(); + } + /** * Spawn a function into its own thread. *

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 b6944eaf..597b9ea1 100644 --- a/src/main/java/com/gitblit/transport/ssh/commands/DispatchCommand.java +++ b/src/main/java/com/gitblit/transport/ssh/commands/DispatchCommand.java @@ -27,7 +27,12 @@ import org.apache.sshd.server.Command; import org.apache.sshd.server.Environment; import org.kohsuke.args4j.Argument; +import com.gitblit.git.GitblitReceivePackFactory; +import com.gitblit.git.GitblitUploadPackFactory; +import com.gitblit.git.RepositoryResolver; +import com.gitblit.transport.ssh.AbstractGitCommand; import com.gitblit.transport.ssh.CommandMetaData; +import com.gitblit.transport.ssh.SshSession; import com.gitblit.utils.cli.SubcommandHandler; import com.google.common.base.Charsets; import com.google.common.base.Strings; @@ -95,11 +100,11 @@ public class DispatchCommand extends BaseCommand { bc.setName(getName() + " " + commandName); } bc.setArguments(args.toArray(new String[args.size()])); - } else if (!args.isEmpty()) { - throw new UnloggedFailure(1, commandName + " does not take arguments"); } - provideStateTo(cmd); + provideBaseStateTo(cmd); + provideGitState(cmd); + reset(); //atomicCmd.set(cmd); cmd.start(env); @@ -136,7 +141,7 @@ public class DispatchCommand extends BaseCommand { } @Override -protected String usage() { + protected String usage() { final StringBuilder usage = new StringBuilder(); usage.append("Available commands"); if (!getName().isEmpty()) { @@ -173,4 +178,39 @@ protected String usage() { usage.append("\n"); return usage.toString(); } + + // This is needed because we are not using provider or + // clazz.newInstance() for DispatchCommand + private void reset() { + args = new ArrayList(); + } + + private void provideGitState(Command cmd) { + if (cmd instanceof AbstractGitCommand) { + AbstractGitCommand a = (AbstractGitCommand) cmd; + a.setRepositoryResolver(repositoryResolver); + a.setUploadPackFactory(gitblitUploadPackFactory); + a.setReceivePackFactory(gitblitReceivePackFactory); + } else if (cmd instanceof DispatchCommand) { + DispatchCommand d = (DispatchCommand)cmd; + d.setRepositoryResolver(repositoryResolver); + d.setUploadPackFactory(gitblitUploadPackFactory); + d.setReceivePackFactory(gitblitReceivePackFactory); + } + } + + private RepositoryResolver repositoryResolver; + public void setRepositoryResolver(RepositoryResolver repositoryResolver) { + this.repositoryResolver = repositoryResolver; + } + + private GitblitUploadPackFactory gitblitUploadPackFactory; + public void setUploadPackFactory(GitblitUploadPackFactory gitblitUploadPackFactory) { + this.gitblitUploadPackFactory = gitblitUploadPackFactory; + } + + private GitblitReceivePackFactory gitblitReceivePackFactory; + public void setReceivePackFactory(GitblitReceivePackFactory gitblitReceivePackFactory) { + this.gitblitReceivePackFactory = gitblitReceivePackFactory; + } } diff --git a/src/main/java/com/gitblit/transport/ssh/commands/Receive.java b/src/main/java/com/gitblit/transport/ssh/commands/Receive.java new file mode 100644 index 00000000..dd1e8a06 --- /dev/null +++ b/src/main/java/com/gitblit/transport/ssh/commands/Receive.java @@ -0,0 +1,34 @@ +/* + * 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 org.eclipse.jgit.transport.ReceivePack; + +import com.gitblit.transport.ssh.AbstractGitCommand; +import com.gitblit.transport.ssh.CommandMetaData; + +@CommandMetaData(name = "git-receive-pack", description = "Receive pack") +public class Receive extends AbstractGitCommand { + @Override + protected void runImpl() throws Failure { + try { + ReceivePack rp = receivePackFactory.create(ctx.getSession(), repo); + rp.receive(in, out, null); + } catch (Exception e) { + throw new Failure(1, "fatal: Cannot receive pack: ", e); + } + } +} diff --git a/src/main/java/com/gitblit/transport/ssh/commands/Upload.java b/src/main/java/com/gitblit/transport/ssh/commands/Upload.java new file mode 100644 index 00000000..d6c3f961 --- /dev/null +++ b/src/main/java/com/gitblit/transport/ssh/commands/Upload.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.transport.ssh.commands; + +import javax.inject.Inject; + +import org.eclipse.jgit.transport.UploadPack; +import org.eclipse.jgit.transport.resolver.UploadPackFactory; + +import com.gitblit.git.RepositoryResolver; +import com.gitblit.transport.ssh.AbstractGitCommand; +import com.gitblit.transport.ssh.CommandMetaData; +import com.gitblit.transport.ssh.SshSession; + +@CommandMetaData(name = "git-upload-pack", description = "Upload pack") +public class Upload extends AbstractGitCommand { + @Override + protected void runImpl() throws Failure { + try { + UploadPack up = uploadPackFactory.create(ctx.getSession(), repo); + up.upload(in, out, null); + } catch (Exception e) { + throw new Failure(1, "fatal: Cannot upload pack: ", e); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/gitblit/transport/ssh/commands/VersionCommand.java b/src/main/java/com/gitblit/transport/ssh/commands/VersionCommand.java index baae6a2c..fc3e01b3 100644 --- a/src/main/java/com/gitblit/transport/ssh/commands/VersionCommand.java +++ b/src/main/java/com/gitblit/transport/ssh/commands/VersionCommand.java @@ -29,7 +29,7 @@ public class VersionCommand extends SshCommand { @Override public void run() { - stdout.println(String.format("Version: %s", Constants.getGitBlitVersion(), + stdout.println(String.format("Version: %s", Constants.getGitBlitVersion(), verbose)); } } -- 2.39.5