James Moger
2014-04-02 c78b25d102fe700617011a4c8acc0d35f9a9e6ca
Support specifying permission levels for SSH public keys
6 files modified
141 ■■■■ changed files
src/main/java/com/gitblit/Constants.java 2 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/transport/ssh/FileKeyManager.java 48 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/transport/ssh/SshKey.java 47 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/transport/ssh/git/Receive.java 5 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/transport/ssh/git/Upload.java 5 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/transport/ssh/keys/KeysDispatcher.java 34 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/Constants.java
@@ -423,6 +423,8 @@
        public static final AccessPermission [] NEWPERMISSIONS = { EXCLUDE, VIEW, CLONE, PUSH, CREATE, DELETE, REWIND };
        public static final AccessPermission [] SSHPERMISSIONS = { VIEW, CLONE, PUSH };
        public static AccessPermission LEGACY = REWIND;
        public final String code;
src/main/java/com/gitblit/transport/ssh/FileKeyManager.java
@@ -23,6 +23,7 @@
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import com.gitblit.Constants.AccessPermission;
import com.gitblit.Keys;
import com.gitblit.manager.IRuntimeManager;
import com.google.common.base.Charsets;
@@ -105,8 +106,18 @@
                        // skip comments
                        continue;
                    }
                    SshKey key = new SshKey(entry);
                    list.add(key);
                    String [] parts = entry.split(" ", 2);
                    AccessPermission perm = AccessPermission.fromCode(parts[0]);
                    if (perm.equals(AccessPermission.NONE)) {
                        // ssh-rsa DATA COMMENT
                        SshKey key = new SshKey(entry);
                        list.add(key);
                    } else if (perm.exceeds(AccessPermission.NONE)) {
                        // PERMISSION ssh-rsa DATA COMMENT
                        SshKey key = new SshKey(parts[1]);
                        key.setPermission(perm);
                        list.add(key);
                    }
                }
                if (list.isEmpty()) {
@@ -129,7 +140,6 @@
    @Override
    public boolean addKey(String username, SshKey key) {
        try {
            String newKey = stripCommentFromKey(key.getRawData());
            boolean replaced = false;
            List<String> lines = new ArrayList<String>();
            File keystore = getKeystore(username);
@@ -147,10 +157,10 @@
                        continue;
                    }
                    String oldKey = stripCommentFromKey(line);
                    if (newKey.equals(oldKey)) {
                    SshKey oldKey = parseKey(line);
                    if (key.equals(oldKey)) {
                        // replace key
                        lines.add(key.getRawData());
                        lines.add(key.getPermission() + " " + key.getRawData());
                        replaced = true;
                    } else {
                        // retain key
@@ -161,7 +171,7 @@
            if (!replaced) {
                // new key, append
                lines.add(key.getRawData());
                lines.add(key.getPermission() + " " + key.getRawData());
            }
            // write keystore
@@ -182,8 +192,6 @@
    @Override
    public boolean removeKey(String username, SshKey key) {
        try {
            String rmKey = stripCommentFromKey(key.getRawData());
            File keystore = getKeystore(username);
            if (keystore.exists()) {
                List<String> lines = new ArrayList<String>();
@@ -201,8 +209,8 @@
                    }
                    // only include keys that are NOT rmKey
                    String oldKey = stripCommentFromKey(line);
                    if (!rmKey.equals(oldKey)) {
                    SshKey oldKey = parseKey(line);
                    if (!key.equals(oldKey)) {
                        lines.add(entry);
                    }
                }
@@ -242,10 +250,18 @@
        return keys;
    }
    /* Strips the comment from the key data and eliminates whitespace diffs */
    protected String stripCommentFromKey(String data) {
        String [] cols = data.split(" ", 3);
        String key = Joiner.on(" ").join(cols[0], cols[1]);
        return key;
    protected SshKey parseKey(String line) {
        String [] parts = line.split(" ", 2);
        AccessPermission perm = AccessPermission.fromCode(parts[0]);
        if (perm.equals(AccessPermission.NONE)) {
            // ssh-rsa DATA COMMENT
            SshKey key = new SshKey(line);
            return key;
        } else {
            // PERMISSION ssh-rsa DATA COMMENT
            SshKey key = new SshKey(parts[1]);
            key.setPermission(perm);
            return key;
        }
    }
}
src/main/java/com/gitblit/transport/ssh/SshKey.java
@@ -2,12 +2,15 @@
import java.io.Serializable;
import java.security.PublicKey;
import java.util.Arrays;
import java.util.List;
import org.apache.commons.codec.binary.Base64;
import org.apache.sshd.common.SshException;
import org.apache.sshd.common.util.Buffer;
import org.eclipse.jgit.lib.Constants;
import com.gitblit.Constants.AccessPermission;
import com.gitblit.utils.StringUtils;
/**
@@ -30,13 +33,17 @@
    private String toString;
    private AccessPermission permission;
    public SshKey(String data) {
        this.rawData = data;
        this.permission = AccessPermission.PUSH;
    }
    public SshKey(PublicKey key) {
        this.publicKey = key;
        this.comment = "";
        this.permission = AccessPermission.PUSH;
    }
    public PublicKey getPublicKey() {
@@ -78,6 +85,46 @@
        }
    }
    /**
     * Returns true if this key may be used to clone or fetch.
     *
     * @return true if this key can be used to clone or fetch
     */
    public boolean canClone() {
        return permission.atLeast(AccessPermission.CLONE);
    }
    /**
     * Returns true if this key may be used to push changes.
     *
     * @return true if this key can be used to push changes
     */
    public boolean canPush() {
        return permission.atLeast(AccessPermission.PUSH);
    }
    /**
     * Returns the access permission for the key.
     *
     * @return the access permission for the key
     */
    public AccessPermission getPermission() {
        return permission;
    }
    /**
     * Control the access permission assigned to this key.
     *
     * @param value
     */
    public void setPermission(AccessPermission value) throws IllegalArgumentException {
        List<AccessPermission> permitted = Arrays.asList(AccessPermission.SSHPERMISSIONS);
        if (!permitted.contains(value)) {
            throw new IllegalArgumentException("Illegal SSH public key permission specified: " + value);
        }
        this.permission = value;
    }
    public String getRawData() {
        if (rawData == null && publicKey != null) {
            // build the raw data manually from the public key
src/main/java/com/gitblit/transport/ssh/git/Receive.java
@@ -17,12 +17,17 @@
import org.eclipse.jgit.transport.ReceivePack;
import com.gitblit.transport.ssh.SshKey;
import com.gitblit.transport.ssh.commands.CommandMetaData;
@CommandMetaData(name = "git-receive-pack", description = "Receives pushes from a client", hidden = true)
public class Receive extends BaseGitCommand {
    @Override
    protected void runImpl() throws Failure {
        SshKey key = getContext().getClient().getKey();
        if (key != null && !key.canPush()) {
            throw new Failure(1, "Sorry, your SSH public key is not allowed to push changes!");
        }
        try {
            ReceivePack rp = receivePackFactory.create(getContext().getClient(), repo);
            rp.receive(in, out, null);
src/main/java/com/gitblit/transport/ssh/git/Upload.java
@@ -17,6 +17,7 @@
import org.eclipse.jgit.transport.UploadPack;
import com.gitblit.transport.ssh.SshKey;
import com.gitblit.transport.ssh.commands.CommandMetaData;
@CommandMetaData(name = "git-upload-pack", description = "Sends packs to a client for clone and fetch", hidden = true)
@@ -24,6 +25,10 @@
    @Override
    protected void runImpl() throws Failure {
        try {
            SshKey key = getContext().getClient().getKey();
            if (key != null && !key.canClone()) {
                throw new Failure(1, "Sorry, your SSH public key is not allowed to clone!");
            }
            UploadPack up = uploadPackFactory.create(getContext().getClient(), repo);
            up.upload(in, out, null);
        } catch (Exception e) {
src/main/java/com/gitblit/transport/ssh/keys/KeysDispatcher.java
@@ -24,6 +24,7 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.gitblit.Constants.AccessPermission;
import com.gitblit.models.UserModel;
import com.gitblit.transport.ssh.IPublicKeyManager;
import com.gitblit.transport.ssh.SshKey;
@@ -33,6 +34,7 @@
import com.gitblit.transport.ssh.commands.UsageExample;
import com.gitblit.utils.FlipTable;
import com.gitblit.utils.FlipTable.Borders;
import com.gitblit.utils.StringUtils;
import com.google.common.base.Joiner;
/**
@@ -54,7 +56,7 @@
    }
    @CommandMetaData(name = "add", description = "Add an SSH public key to your account")
    @UsageExample(syntax = "cat ~/.ssh/id_rsa.pub | ${ssh} ${cmd} -", description = "Upload your SSH public key and add it to your account")
    @UsageExample(syntax = "cat ~/.ssh/id_rsa.pub | ${ssh} ${cmd}", description = "Upload your SSH public key and add it to your account")
    public static class AddKey extends BaseKeyCommand {
        protected final Logger log = LoggerFactory.getLogger(getClass());
@@ -62,12 +64,33 @@
        @Argument(metaVar = "<KEY>", usage = "the key(s) to add")
        private List<String> addKeys = new ArrayList<String>();
        @Option(name = "--permission", aliases = { "-p" }, metaVar = "PERMISSION", usage = "set the key access permission")
        private String permission;
        @Override
        protected String getUsageText() {
            String permissions = Joiner.on(", ").join(AccessPermission.SSHPERMISSIONS);
            StringBuilder sb = new StringBuilder();
            sb.append("Valid SSH public key permissions are:\n   ").append(permissions);
            return sb.toString();
        }
        @Override
        public void run() throws IOException, UnloggedFailure {
            String username = getContext().getClient().getUsername();
            List<String> keys = readKeys(addKeys);
            for (String key : keys) {
                SshKey sshKey = parseKey(key);
                if (!StringUtils.isEmpty(permission)) {
                    AccessPermission ap = AccessPermission.fromCode(permission);
                    if (ap.exceeds(AccessPermission.NONE)) {
                        try {
                            sshKey.setPermission(ap);
                        } catch (IllegalArgumentException e) {
                            throw new UnloggedFailure(1, e.getMessage());
                        }
                    }
                }
                getKeyManager().addKey(username, sshKey);
                log.info("added SSH public key for {}", username);
            }
@@ -167,14 +190,15 @@
        }
        protected void asTable(List<SshKey> keys) {
            String[] headers = { "#", "Fingerprint", "Comment", "Type" };
            String[] headers = { "#", "Fingerprint", "Comment", "Permission", "Type" };
            int len = keys == null ? 0 : keys.size();
            Object[][] data = new Object[len][];
            for (int i = 0; i < len; i++) {
                // show 1-based index numbers with the fingerprint
                // this is useful for comparing with "ssh-add -l"
                SshKey k = keys.get(i);
                data[i] = new Object[] { (i + 1), k.getFingerprint(), k.getComment(), k.getAlgorithm() };
                data[i] = new Object[] { (i + 1), k.getFingerprint(), k.getComment(),
                        k.getPermission(), k.getAlgorithm() };
            }
            stdout.println(FlipTable.of(headers, data, Borders.BODY_HCOLS));
@@ -211,9 +235,9 @@
        }
        protected void asTable(int index, SshKey key) {
            String[] headers = { "#", "Fingerprint", "Comment", "Type" };
            String[] headers = { "#", "Fingerprint", "Comment", "Permission", "Type" };
            Object[][] data = new Object[1][];
            data[0] = new Object[] { index, key.getFingerprint(), key.getComment(), key.getAlgorithm() };
            data[0] = new Object[] { index, key.getFingerprint(), key.getComment(), key.getPermission(), key.getAlgorithm() };
            stdout.println(FlipTable.of(headers, data, Borders.BODY_HCOLS));
        }