From e9872c8ca4d9af41794a851f2f81ed21c65bb85b Mon Sep 17 00:00:00 2001 From: James Moger Date: Wed, 2 Apr 2014 11:44:31 -0400 Subject: Allow specifying accepted PUSH transports --- src/main/distrib/data/gitblit.properties | 10 +++ src/main/java/com/gitblit/Constants.java | 19 ++++++ src/main/java/com/gitblit/GitBlit.java | 76 ++++++++++++++++++++++ .../com/gitblit/git/GitblitReceivePackFactory.java | 46 +++++++++++++ .../java/com/gitblit/models/RepositoryUrl.java | 3 + 5 files changed, 154 insertions(+) (limited to 'src') diff --git a/src/main/distrib/data/gitblit.properties b/src/main/distrib/data/gitblit.properties index c52423b9..c7e3a217 100644 --- a/src/main/distrib/data/gitblit.properties +++ b/src/main/distrib/data/gitblit.properties @@ -173,6 +173,16 @@ git.certificateUsernameOIDs = CN # SINCE 0.9.0 git.onlyAccessBareRepositories = false + +# Specify the list of acceptable transports for pushes. +# If this setting is empty, all transports are acceptable. +# +# Valid choices are: GIT HTTP HTTPS SSH +# +# SINCE 1.5.0 +# SPACE-DELIMITED +git.acceptedPushTransports = HTTP HTTPS SSH + # Allow an authenticated user to create a destination repository on a push if # the repository does not already exist. # diff --git a/src/main/java/com/gitblit/Constants.java b/src/main/java/com/gitblit/Constants.java index 26e0de3c..af533996 100644 --- a/src/main/java/com/gitblit/Constants.java +++ b/src/main/java/com/gitblit/Constants.java @@ -540,6 +540,25 @@ public class Constants { } } + public static enum Transport { + // ordered for url advertisements, assuming equal access permissions + SSH, HTTPS, HTTP, GIT; + + public static Transport fromString(String value) { + for (Transport t : values()) { + if (t.name().equalsIgnoreCase(value)) { + return t; + } + } + return null; + } + + public static Transport fromUrl(String url) { + String scheme = url.substring(0, url.indexOf("://")); + return fromString(scheme); + } + } + @Documented @Retention(RetentionPolicy.RUNTIME) public @interface Unused { diff --git a/src/main/java/com/gitblit/GitBlit.java b/src/main/java/com/gitblit/GitBlit.java index 26ab3f3b..08342521 100644 --- a/src/main/java/com/gitblit/GitBlit.java +++ b/src/main/java/com/gitblit/GitBlit.java @@ -17,12 +17,17 @@ package com.gitblit; import java.text.MessageFormat; import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; import java.util.List; +import java.util.Set; import javax.inject.Singleton; import javax.servlet.http.HttpServletRequest; import com.gitblit.Constants.AccessPermission; +import com.gitblit.Constants.Transport; import com.gitblit.manager.GitblitManager; import com.gitblit.manager.IAuthenticationManager; import com.gitblit.manager.IFederationManager; @@ -116,6 +121,32 @@ public class GitBlit extends GitblitManager { return new Object [] { new GitBlitModule()}; } + protected boolean acceptPush(Transport byTransport) { + if (byTransport == null) { + logger.info("Unknown transport, push rejected!"); + return false; + } + + Set transports = new HashSet(); + for (String value : getSettings().getStrings(Keys.git.acceptedPushTransports)) { + Transport transport = Transport.fromString(value); + if (transport == null) { + logger.info(String.format("Ignoring unknown registered transport %s", value)); + continue; + } + + transports.add(transport); + } + + if (transports.isEmpty()) { + // no transports are explicitly specified, all are acceptable + return true; + } + + // verify that the transport is permitted + return transports.contains(byTransport); + } + /** * Returns a list of repository URLs and the user access permission. * @@ -137,6 +168,12 @@ public class GitBlit extends GitblitManager { if (settings.getBoolean(Keys.git.enableGitServlet, true)) { AccessPermission permission = user.getRepositoryPermission(repository).permission; if (permission.exceeds(AccessPermission.NONE)) { + Transport transport = Transport.fromString(request.getScheme()); + if (permission.atLeast(AccessPermission.PUSH) && !acceptPush(transport)) { + // downgrade the repo permission for this transport + // because it is not an acceptable PUSH transport + permission = AccessPermission.CLONE; + } list.add(new RepositoryUrl(getRepositoryUrl(request, username, repository), permission)); } } @@ -146,6 +183,12 @@ public class GitBlit extends GitblitManager { if (!StringUtils.isEmpty(sshDaemonUrl)) { AccessPermission permission = user.getRepositoryPermission(repository).permission; if (permission.exceeds(AccessPermission.NONE)) { + if (permission.atLeast(AccessPermission.PUSH) && !acceptPush(Transport.SSH)) { + // downgrade the repo permission for this transport + // because it is not an acceptable PUSH transport + permission = AccessPermission.CLONE; + } + list.add(new RepositoryUrl(sshDaemonUrl, permission)); } } @@ -155,6 +198,11 @@ public class GitBlit extends GitblitManager { if (!StringUtils.isEmpty(gitDaemonUrl)) { AccessPermission permission = servicesManager.getGitDaemonAccessPermission(user, repository); if (permission.exceeds(AccessPermission.NONE)) { + if (permission.atLeast(AccessPermission.PUSH) && !acceptPush(Transport.GIT)) { + // downgrade the repo permission for this transport + // because it is not an acceptable PUSH transport + permission = AccessPermission.CLONE; + } list.add(new RepositoryUrl(gitDaemonUrl, permission)); } } @@ -173,6 +221,34 @@ public class GitBlit extends GitblitManager { list.add(new RepositoryUrl(MessageFormat.format(url, repository.name), null)); } } + + // sort transports by highest permission and then by transport security + Collections.sort(list, new Comparator() { + + @Override + public int compare(RepositoryUrl o1, RepositoryUrl o2) { + if (!o1.isExternal() && o2.isExternal()) { + // prefer Gitblit over external + return -1; + } else if (o1.isExternal() && !o2.isExternal()) { + // prefer Gitblit over external + return 1; + } else if (o1.isExternal() && o2.isExternal()) { + // sort by Transport ordinal + return o1.transport.compareTo(o2.transport); + } else if (o1.permission.exceeds(o2.permission)) { + // prefer highest permission + return -1; + } else if (o2.permission.exceeds(o1.permission)) { + // prefer highest permission + return 1; + } + + // prefer more secure transports + return o1.transport.compareTo(o2.transport); + } + }); + return list; } diff --git a/src/main/java/com/gitblit/git/GitblitReceivePackFactory.java b/src/main/java/com/gitblit/git/GitblitReceivePackFactory.java index 41e348ba..afda23b0 100644 --- a/src/main/java/com/gitblit/git/GitblitReceivePackFactory.java +++ b/src/main/java/com/gitblit/git/GitblitReceivePackFactory.java @@ -15,6 +15,9 @@ */ package com.gitblit.git; +import java.util.HashSet; +import java.util.Set; + import javax.servlet.http.HttpServletRequest; import org.eclipse.jgit.lib.PersonIdent; @@ -26,6 +29,7 @@ import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.gitblit.Constants.Transport; import com.gitblit.IStoredSettings; import com.gitblit.Keys; import com.gitblit.manager.IGitblit; @@ -66,6 +70,7 @@ public class GitblitReceivePackFactory implements ReceivePackFactory { String origin = ""; String gitblitUrl = ""; int timeout = 0; + Transport transport = null; if (req instanceof HttpServletRequest) { // http/https request may or may not be authenticated @@ -82,6 +87,13 @@ public class GitblitReceivePackFactory implements ReceivePackFactory { user = u; } } + + // determine the transport + if ("http".equals(client.getScheme())) { + transport = Transport.HTTP; + } else if ("https".equals(client.getScheme())) { + transport = Transport.HTTPS; + } } else if (req instanceof GitDaemonClient) { // git daemon request is always anonymous GitDaemonClient client = (GitDaemonClient) req; @@ -90,12 +102,20 @@ public class GitblitReceivePackFactory implements ReceivePackFactory { // set timeout from Git daemon timeout = client.getDaemon().getTimeout(); + + transport = Transport.GIT; } else if (req instanceof SshDaemonClient) { // SSH request is always authenticated SshDaemonClient client = (SshDaemonClient) req; repositoryName = client.getRepositoryName(); origin = client.getRemoteAddress().toString(); user = client.getUser(); + + transport = Transport.SSH; + } + + if (!acceptPush(transport)) { + throw new ServiceNotAuthorizedException(); } boolean allowAnonymousPushes = settings.getBoolean(Keys.git.allowAnonymousPushes, false); @@ -125,4 +145,30 @@ public class GitblitReceivePackFactory implements ReceivePackFactory { return rp; } + + protected boolean acceptPush(Transport byTransport) { + if (byTransport == null) { + logger.info("Unknown transport, push rejected!"); + return false; + } + + Set transports = new HashSet(); + for (String value : gitblit.getSettings().getStrings(Keys.git.acceptedPushTransports)) { + Transport transport = Transport.fromString(value); + if (transport == null) { + logger.info(String.format("Ignoring unknown registered transport %s", value)); + continue; + } + + transports.add(transport); + } + + if (transports.isEmpty()) { + // no transports are explicitly specified, all are acceptable + return true; + } + + // verify that the transport is permitted + return transports.contains(byTransport); + } } \ No newline at end of file diff --git a/src/main/java/com/gitblit/models/RepositoryUrl.java b/src/main/java/com/gitblit/models/RepositoryUrl.java index a24def57..d155dbda 100644 --- a/src/main/java/com/gitblit/models/RepositoryUrl.java +++ b/src/main/java/com/gitblit/models/RepositoryUrl.java @@ -18,6 +18,7 @@ package com.gitblit.models; import java.io.Serializable; import com.gitblit.Constants.AccessPermission; +import com.gitblit.Constants.Transport; /** * Represents a git repository url and it's associated access permission for the @@ -30,10 +31,12 @@ public class RepositoryUrl implements Serializable { private static final long serialVersionUID = 1L; + public final Transport transport; public final String url; public final AccessPermission permission; public RepositoryUrl(String url, AccessPermission permission) { + this.transport = Transport.fromUrl(url); this.url = url; this.permission = permission; } -- cgit v1.2.3