]> source.dussan.org Git - gitblit.git/commitdiff
SSHD: Add support for git pack commands
authorDavid Ostrovsky <david@ostrovsky.org>
Sat, 22 Feb 2014 20:17:03 +0000 (21:17 +0100)
committerJames Moger <james.moger@gitblit.com>
Thu, 10 Apr 2014 22:58:07 +0000 (18:58 -0400)
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

17 files changed:
src/main/java/com/gitblit/Constants.java
src/main/java/com/gitblit/git/GitblitReceivePackFactory.java
src/main/java/com/gitblit/git/RepositoryResolver.java
src/main/java/com/gitblit/manager/AuthenticationManager.java
src/main/java/com/gitblit/manager/GitblitManager.java
src/main/java/com/gitblit/manager/IAuthenticationManager.java
src/main/java/com/gitblit/manager/ServicesManager.java
src/main/java/com/gitblit/transport/ssh/AbstractGitCommand.java [new file with mode: 0644]
src/main/java/com/gitblit/transport/ssh/SshCommandFactory.java
src/main/java/com/gitblit/transport/ssh/SshContext.java [new file with mode: 0644]
src/main/java/com/gitblit/transport/ssh/SshDaemon.java
src/main/java/com/gitblit/transport/ssh/SshSession.java
src/main/java/com/gitblit/transport/ssh/commands/BaseCommand.java
src/main/java/com/gitblit/transport/ssh/commands/DispatchCommand.java
src/main/java/com/gitblit/transport/ssh/commands/Receive.java [new file with mode: 0644]
src/main/java/com/gitblit/transport/ssh/commands/Upload.java [new file with mode: 0644]
src/main/java/com/gitblit/transport/ssh/commands/VersionCommand.java

index 2a98b53f86c0bed6f96533819605ae60296114dc..889e5a30ad838adb4307ceeb52c4eced18e35203 100644 (file)
@@ -501,7 +501,7 @@ public class Constants {
        }\r
 \r
        public static enum AuthenticationType {\r
-               CREDENTIALS, COOKIE, CERTIFICATE, CONTAINER;\r
+               SSH, CREDENTIALS, COOKIE, CERTIFICATE, CONTAINER;\r
 \r
                public boolean isStandard() {\r
                        return ordinal() <= COOKIE.ordinal();\r
index 7976fe5624e0122a6cb0f7b25fd532bebd1e3d62..9911258c3486f5d62738db0c5651d35a6cd380ec 100644 (file)
@@ -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<X> implements ReceivePackFactory<X> {
 
                        // 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);
index 208c1ae1648139f4d514bf69dd52debed5ab1656..c859f6f6bef56944128e99f1990d617e5eae1cdc 100644 (file)
@@ -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<X> extends FileResolver<X> {
                        // 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<X> extends FileResolver<X> {
                        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)) {
index 4f3e652c58f6a82880177b3c1a3bc5abf7675af5..47425ce7b725b79b87794d546a3891abd98833a1 100644 (file)
@@ -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
index b6c2b474636da6829e0e6add358bcee65a592ebd..a5a263797242c1d1a6d7462d50b0ce81fb07d7f2 100644 (file)
@@ -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);
index 3007a303c48b55851e11a7001459eb3383243203..5d98d1275f4774e6a06498b241b719a0fd5da61a 100644 (file)
@@ -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.
         *
index df8918ed382a1c10b79563b4b01f5eeb7d8f9eaf..11083be3d62aad400226ddf492f7c8eb3b95e22f 100644 (file)
@@ -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 (file)
index 0000000..bba6a40
--- /dev/null
@@ -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<SshSession> repositoryResolver;
+       protected ReceivePackFactory<SshSession> receivePackFactory;
+       protected UploadPackFactory<SshSession> 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<SshSession> repositoryResolver) {
+               this.repositoryResolver = repositoryResolver;
+       }
+
+       public void setReceivePackFactory(
+                       GitblitReceivePackFactory<SshSession> receivePackFactory) {
+               this.receivePackFactory = receivePackFactory;
+       }
+
+       public void setUploadPackFactory(
+                       GitblitUploadPackFactory<SshSession> uploadPackFactory) {
+               this.uploadPackFactory = uploadPackFactory;
+       }
+}
\ No newline at end of file
index 056938e9b47a43f34ae669287bcb9efb708718c0..0c8492f7f8616700c41c1bba93c29cd068821386 100644 (file)
@@ -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<SshSession> repositoryResolver;
-
-  private UploadPackFactory<SshSession> uploadPackFactory;
-
-  private ReceivePackFactory<SshSession> receivePackFactory;
   private final ScheduledExecutorService startExecutor;
 
   private DispatchCommand dispatcher;
 
-       public SshCommandFactory(RepositoryResolver<SshSession> repositoryResolver,
-           UploadPackFactory<SshSession> uploadPackFactory,
-           ReceivePackFactory<SshSession> 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<Future<?>> 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 (file)
index 0000000..b137cb8
--- /dev/null
@@ -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;
+       }
+}
index dd4a2d8e4c82ece1928a9047ab1549ee5c3f1d6d..b23ddd588fec9e4d6cbee4bbd190f9cc99fe71bd 100644 (file)
@@ -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<SshSession>(gitblit));
+               root.setUploadPackFactory(new GitblitUploadPackFactory<SshSession>(gitblit));
+               root.setReceivePackFactory(new GitblitReceivePackFactory<SshSession>(gitblit));
 
                SshCommandFactory commandFactory = new SshCommandFactory(
-                               new RepositoryResolver<SshSession>(gitblit),
-                               new GitblitUploadPackFactory<SshSession>(gitblit),
-                               new GitblitReceivePackFactory<SshSession>(gitblit),
                                new WorkQueue(idGenerator),
-                               dispatcher);
+                               root);
 
                sshd.setCommandFactory(commandFactory);
 
index 9f18a19758d691020b491c0a62a426aa642e2d56..ffff8af40394945aeb8154c68a5eb67d7c3d5bb3 100644 (file)
@@ -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;
index fd73ccfdbbe530f54f04b932692fab3800cec47f..a04c505f4db0ca25fd0bdd999fb640f6e9e07ec3 100644 (file)
@@ -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<Future<?>> 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.
    * <p>
index b6944eaf513f14cc05a633e26df50d2d66f688cb..597b9ea18902070a80a666c1a731d07a333a6994 100644 (file)
@@ -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<String>();
+  }
+
+  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<SshSession> repositoryResolver;
+  public void setRepositoryResolver(RepositoryResolver<SshSession> repositoryResolver) {
+         this.repositoryResolver = repositoryResolver;
+  }
+
+  private GitblitUploadPackFactory<SshSession> gitblitUploadPackFactory;
+  public void setUploadPackFactory(GitblitUploadPackFactory<SshSession> gitblitUploadPackFactory) {
+         this.gitblitUploadPackFactory = gitblitUploadPackFactory;
+  }
+
+  private GitblitReceivePackFactory<SshSession> gitblitReceivePackFactory;
+  public void setReceivePackFactory(GitblitReceivePackFactory<SshSession> 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 (file)
index 0000000..dd1e8a0
--- /dev/null
@@ -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 (file)
index 0000000..d6c3f96
--- /dev/null
@@ -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
index baae6a2ce794c95eb4ce6775a15df6e564365c05..fc3e01b3d41cd2fa1e95fd51afac96a8ce6a20a8 100644 (file)
@@ -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));
   }
 }