From f104ccd30c0aada5a580b21e2c95364c4f1de60b Mon Sep 17 00:00:00 2001 From: Jacek Centkowski Date: Thu, 5 Jan 2017 18:34:52 +0100 Subject: [PATCH 1/3] Introduce "git-lfs-authenticate" SSH Command for LFS plugins According to [1] Git LFS can use SSH protocol to either obtain Git LFS endpoint or authorize the following Git LFS request (upload/download). This patchset introduces Git LFS SSH command stub that forwards call to implementation provided by Git LFS plugin. In case no plugin is loaded it exits with 1. Different attempts to solve this problem were submitted for review ([2], [3]) but there were either too generic or simply to controversial to address the problem correctly. I believe that this attempt is superior as: 1. it is specific to the problem - doesn't open any Pandora's box 2. it comes along with existing solution that adds Git LFS HTTP Servlet endpoint that transfers Git LFS HTTP calls to plugin [1] https://github.com/git-lfs/git-lfs/blob/master/docs/api/server-discovery.md [2] https://gerrit-review.googlesource.com/93496 [3] https://gerrit-review.googlesource.com/93373 Change-Id: I5f9b95c4b29cd8c0d64053e986851500f147eb5a Signed-off-by: Jacek Centkowski --- .../sshd/commands/DefaultCommandModule.java | 3 + .../sshd/plugin/LfsPluginAuthCommand.java | 66 +++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 gerrit-sshd/src/main/java/com/google/gerrit/sshd/plugin/LfsPluginAuthCommand.java diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/DefaultCommandModule.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/DefaultCommandModule.java index c6eaebb1b0..62e929750c 100644 --- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/DefaultCommandModule.java +++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/DefaultCommandModule.java @@ -21,6 +21,7 @@ import com.google.gerrit.sshd.CommandName; import com.google.gerrit.sshd.Commands; import com.google.gerrit.sshd.DispatchCommandProvider; import com.google.gerrit.sshd.SuExec; +import com.google.gerrit.sshd.plugin.LfsPluginAuthCommand; /** Register the commands a Gerrit server supports. */ @@ -122,6 +123,8 @@ public class DefaultCommandModule extends CommandModule { command(logging, ListLoggingLevelCommand.class); alias(logging, "ls", ListLoggingLevelCommand.class); alias(logging, "set", SetLoggingLevelCommand.class); + + install(new LfsPluginAuthCommand.Module()); } private boolean sshEnabled() { diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/plugin/LfsPluginAuthCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/plugin/LfsPluginAuthCommand.java new file mode 100644 index 0000000000..e79f8bbf42 --- /dev/null +++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/plugin/LfsPluginAuthCommand.java @@ -0,0 +1,66 @@ +// Copyright (C) 2017 The Android Open Source Project +// +// 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.google.gerrit.sshd.plugin; + +import com.google.gerrit.extensions.registration.DynamicItem; +import com.google.gerrit.server.CurrentUser; +import com.google.gerrit.sshd.CommandModule; +import com.google.gerrit.sshd.SshCommand; +import com.google.inject.Inject; +import com.google.inject.Provider; + +import org.kohsuke.args4j.Argument; + +import java.util.ArrayList; +import java.util.List; + +public class LfsPluginAuthCommand extends SshCommand { + public interface LfsSshPluginAuth { + String authenticate(CurrentUser user, List args) + throws UnloggedFailure, Failure; + } + + public static class Module extends CommandModule { + @Override + protected void configure() { + command("git-lfs-authenticate").to(LfsPluginAuthCommand.class); + DynamicItem.itemOf(binder(), LfsSshPluginAuth.class); + } + } + + private final DynamicItem auth; + private final Provider user; + + @Argument(index = 0, multiValued = true, metaVar = "PARAMS") + private List args = new ArrayList<>(); + + @Inject + LfsPluginAuthCommand(DynamicItem auth, + Provider user) { + this.auth = auth; + this.user = user; + } + + @Override + protected void run() throws UnloggedFailure, Failure, Exception { + LfsSshPluginAuth pluginAuth = auth.get(); + if (pluginAuth == null) { + throw new Failure(1, "Server configuration error:" + + " LFS auth over SSH is not properly configured."); + } + + stdout.print(pluginAuth.authenticate(user.get(), args)); + } +} From 31235233d0b140e01129b767b56b78eb315a0290 Mon Sep 17 00:00:00 2001 From: Jacek Centkowski Date: Mon, 16 Jan 2017 11:11:30 +0100 Subject: [PATCH 2/3] Install "git-lfs-authenticate" SSH command when lfs.plugin is configured SSH command registration was extended so that command gets registered only in case when plugin is configured in gerrit.config. Change-Id: Iffaf00775bdf1242e1fbe1dea15e5ce0bf912079 Signed-off-by: Jacek Centkowski --- .../main/java/com/google/gerrit/pgm/Daemon.java | 4 +++- .../sshd/commands/DefaultCommandModule.java | 7 +++++-- .../gerrit/sshd/plugin/LfsPluginAuthCommand.java | 15 +++++++++++++-- .../google/gerrit/httpd/WebAppInitializer.java | 4 +++- 4 files changed, 24 insertions(+), 6 deletions(-) diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java index 3ff6451057..09fe5a3665 100644 --- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java +++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java @@ -87,6 +87,7 @@ import com.google.gerrit.sshd.SshKeyCacheImpl; import com.google.gerrit.sshd.SshModule; import com.google.gerrit.sshd.commands.DefaultCommandModule; import com.google.gerrit.sshd.commands.IndexCommandsModule; +import com.google.gerrit.sshd.plugin.LfsPluginAuthCommand; import com.google.inject.AbstractModule; import com.google.inject.Guice; import com.google.inject.Injector; @@ -439,7 +440,8 @@ public class Daemon extends SiteProgram { modules.add(new SshHostKeyModule()); } modules.add(new DefaultCommandModule(slave, - sysInjector.getInstance(DownloadConfig.class))); + sysInjector.getInstance(DownloadConfig.class), + sysInjector.getInstance(LfsPluginAuthCommand.Module.class))); if (!slave && indexType == IndexType.LUCENE) { modules.add(new IndexCommandsModule()); } diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/DefaultCommandModule.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/DefaultCommandModule.java index 62e929750c..c9cdbe05d5 100644 --- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/DefaultCommandModule.java +++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/DefaultCommandModule.java @@ -27,10 +27,13 @@ import com.google.gerrit.sshd.plugin.LfsPluginAuthCommand; /** Register the commands a Gerrit server supports. */ public class DefaultCommandModule extends CommandModule { private final DownloadConfig downloadConfig; + private final LfsPluginAuthCommand.Module lfsPluginAuthModule; - public DefaultCommandModule(boolean slave, DownloadConfig downloadCfg) { + public DefaultCommandModule(boolean slave, DownloadConfig downloadCfg, + LfsPluginAuthCommand.Module module) { slaveMode = slave; downloadConfig = downloadCfg; + lfsPluginAuthModule = module; } @Override @@ -124,7 +127,7 @@ public class DefaultCommandModule extends CommandModule { alias(logging, "ls", ListLoggingLevelCommand.class); alias(logging, "set", SetLoggingLevelCommand.class); - install(new LfsPluginAuthCommand.Module()); + install(lfsPluginAuthModule); } private boolean sshEnabled() { diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/plugin/LfsPluginAuthCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/plugin/LfsPluginAuthCommand.java index e79f8bbf42..0c8d024f1a 100644 --- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/plugin/LfsPluginAuthCommand.java +++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/plugin/LfsPluginAuthCommand.java @@ -16,11 +16,13 @@ package com.google.gerrit.sshd.plugin; import com.google.gerrit.extensions.registration.DynamicItem; import com.google.gerrit.server.CurrentUser; +import com.google.gerrit.server.config.GerritServerConfig; import com.google.gerrit.sshd.CommandModule; import com.google.gerrit.sshd.SshCommand; import com.google.inject.Inject; import com.google.inject.Provider; +import org.eclipse.jgit.lib.Config; import org.kohsuke.args4j.Argument; import java.util.ArrayList; @@ -33,10 +35,19 @@ public class LfsPluginAuthCommand extends SshCommand { } public static class Module extends CommandModule { + private final boolean pluginProvided; + + @Inject + Module(@GerritServerConfig Config cfg) { + pluginProvided = cfg.getString("lfs", null, "plugin") != null; + } + @Override protected void configure() { - command("git-lfs-authenticate").to(LfsPluginAuthCommand.class); - DynamicItem.itemOf(binder(), LfsSshPluginAuth.class); + if (pluginProvided) { + command("git-lfs-authenticate").to(LfsPluginAuthCommand.class); + DynamicItem.itemOf(binder(), LfsSshPluginAuth.class); + } } } diff --git a/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java b/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java index 71eed63832..fb7d0e97ef 100644 --- a/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java +++ b/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java @@ -72,6 +72,7 @@ import com.google.gerrit.sshd.SshKeyCacheImpl; import com.google.gerrit.sshd.SshModule; import com.google.gerrit.sshd.commands.DefaultCommandModule; import com.google.gerrit.sshd.commands.IndexCommandsModule; +import com.google.gerrit.sshd.plugin.LfsPluginAuthCommand; import com.google.inject.AbstractModule; import com.google.inject.CreationException; import com.google.inject.Guice; @@ -356,7 +357,8 @@ public class WebAppInitializer extends GuiceServletContextListener modules.add(sysInjector.getInstance(SshModule.class)); modules.add(new SshHostKeyModule()); modules.add(new DefaultCommandModule(false, - sysInjector.getInstance(DownloadConfig.class))); + sysInjector.getInstance(DownloadConfig.class), + sysInjector.getInstance(LfsPluginAuthCommand.Module.class))); if (indexType == IndexType.LUCENE) { modules.add(new IndexCommandsModule()); } From 2897e9d2e394848ebdbb98abb7b4d422e282776e Mon Sep 17 00:00:00 2001 From: Hector Oswaldo Caballero Date: Wed, 18 Jan 2017 06:33:01 -0500 Subject: [PATCH 3/3] Fix gitweb review link I99d426c8a caused a regression where the review link in a commit does not link to the review page for a change. Change-Id: I106b23d0d5e82313005f8252c86431c5d9004bcb --- .../java/com/google/gerrit/httpd/gitweb/GitwebServlet.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitwebServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitwebServlet.java index 79d298cd2e..4b66023ced 100644 --- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitwebServlet.java +++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitwebServlet.java @@ -232,10 +232,11 @@ class GitwebServlet extends HttpServlet { p.print(" if (( $secure && $ENV{'SERVER_PORT'} != 443)\n"); p.print(" || (!$secure && $ENV{'SERVER_PORT'} != 80)\n"); p.print(" );\n"); - p.print(" $http_url .= qq{$ENV{'GERRIT_CONTEXT_PATH'}};\n"); + p.print(" my $context = $ENV{'GERRIT_CONTEXT_PATH'};\n"); + p.print(" chop($context);\n"); + p.print(" $http_url .= qq{$context};\n"); p.print(" $http_url .= qq{/a}\n"); p.print(" unless $ENV{'GERRIT_ANONYMOUS_READ'};\n"); - p.print(" \n"); p.print(" push @git_base_url_list, $http_url;\n"); p.print("}\n"); @@ -545,7 +546,7 @@ class GitwebServlet extends HttpServlet { env.set("HTTP_" + name.toUpperCase().replace('-', '_'), value); } - env.set("GERRIT_CONTEXT_PATH", req.getContextPath()); + env.set("GERRIT_CONTEXT_PATH", req.getContextPath() + "/"); env.set("GERRIT_PROJECT_NAME", project.getProject().getName()); env.set("GITWEB_PROJECTROOT",