@@ -23,10 +23,12 @@ r22: { | |||
- Redirect to summary page on edit repository (issue-405) | |||
- Option to allow LDAP users to directly authenticate without performing LDAP searches (pr-162) | |||
- Replace JCommander with args4j to be consistent with other tools (ticket-28) | |||
- Sort repository urls by descending permissions and by transport security within equal permissions | |||
additions: | |||
- Added an SSH daemon with public key authentication (issue-369, ticket-6) | |||
- Added beginnings of a plugin framework for extending Gitblit (issue-381, ticket-23) | |||
- Added a French translation (pr-163) | |||
- Added a setting to control what transports may be used for pushes | |||
dependencyChanges: | |||
- args4j 2.0.26 | |||
- JGit 3.3.1 | |||
@@ -41,6 +43,7 @@ r22: { | |||
settings: | |||
- { name: 'realm.ldap.bindpattern', defaultValue: ' ' } | |||
- { name: 'tickets.closeOnPushCommitMessageRegex', defaultValue: '(?:fixes|closes)[\\s-]+#?(\\d+)' } | |||
- { name: 'git.acceptedPushTransports', defaultValue: ' ' } | |||
- { name: 'git.sshPort', defaultValue: '29418' } | |||
- { name: 'git.sshBindInterface', defaultValue: ' ' } | |||
- { name: 'git.sshKeysManager', defaultValue: 'com.gitblit.transport.ssh.FileKeyManager' } |
@@ -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. | |||
# |
@@ -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 { |
@@ -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<Transport> transports = new HashSet<Transport>(); | |||
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<RepositoryUrl>() { | |||
@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; | |||
} | |||
@@ -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<X> implements ReceivePackFactory<X> { | |||
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<X> implements ReceivePackFactory<X> { | |||
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<X> implements ReceivePackFactory<X> { | |||
// 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<X> implements ReceivePackFactory<X> { | |||
return rp; | |||
} | |||
protected boolean acceptPush(Transport byTransport) { | |||
if (byTransport == null) { | |||
logger.info("Unknown transport, push rejected!"); | |||
return false; | |||
} | |||
Set<Transport> transports = new HashSet<Transport>(); | |||
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); | |||
} | |||
} |
@@ -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; | |||
} |