James Moger
2014-04-02 e9872c8ca4d9af41794a851f2f81ed21c65bb85b
Allow specifying accepted PUSH transports
6 files modified
157 ■■■■■ changed files
releases.moxie 3 ●●●●● patch | view | raw | blame | history
src/main/distrib/data/gitblit.properties 10 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/Constants.java 19 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/GitBlit.java 76 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/git/GitblitReceivePackFactory.java 46 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/models/RepositoryUrl.java 3 ●●●●● patch | view | raw | blame | history
releases.moxie
@@ -23,10 +23,12 @@
    - 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 @@
    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' }
src/main/distrib/data/gitblit.properties
@@ -173,6 +173,16 @@
# 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.
#
src/main/java/com/gitblit/Constants.java
@@ -540,6 +540,25 @@
        }
    }
    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 {
src/main/java/com/gitblit/GitBlit.java
@@ -17,12 +17,17 @@
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 @@
        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 @@
        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 @@
        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 @@
        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 @@
                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;
    }
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.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 @@
        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 @@
                    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 @@
            // 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 @@
        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);
    }
}
src/main/java/com/gitblit/models/RepositoryUrl.java
@@ -18,6 +18,7 @@
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 @@
    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;
    }