James Moger
2011-10-02 31abc26dd0354bc2dafe27c011c2e54934a89486
Fairly complete json rpc interface to view/control Gitblit data objects.
1 files added
11 files modified
937 ■■■■ changed files
src/com/gitblit/Constants.java 12 ●●●●● patch | view | raw | blame | history
src/com/gitblit/FederationPullExecutor.java 44 ●●●● patch | view | raw | blame | history
src/com/gitblit/GitBlit.java 27 ●●●●● patch | view | raw | blame | history
src/com/gitblit/GitBlitException.java 30 ●●●●● patch | view | raw | blame | history
src/com/gitblit/JsonServlet.java 31 ●●●● patch | view | raw | blame | history
src/com/gitblit/RpcServlet.java 75 ●●●●● patch | view | raw | blame | history
src/com/gitblit/models/FederationSet.java 58 ●●●●● patch | view | raw | blame | history
src/com/gitblit/utils/FederationUtils.java 14 ●●●●● patch | view | raw | blame | history
src/com/gitblit/utils/JsonUtils.java 166 ●●●● patch | view | raw | blame | history
src/com/gitblit/utils/RpcUtils.java 290 ●●●●● patch | view | raw | blame | history
tests/com/gitblit/tests/FederationTests.java 12 ●●●● patch | view | raw | blame | history
tests/com/gitblit/tests/RpcTests.java 178 ●●●●● patch | view | raw | blame | history
src/com/gitblit/Constants.java
@@ -52,7 +52,7 @@
    public static final String SYNDICATION_PATH = "/feed/";
    public static final String FEDERATION_PATH = "/federation/";
    public static final String RPC_PATH = "/rpc/";
    public static final String BORDER = "***********************************************************";
@@ -202,8 +202,10 @@
     */
    public static enum RpcRequest {
        LIST_REPOSITORIES, CREATE_REPOSITORY, EDIT_REPOSITORY, DELETE_REPOSITORY,
        LIST_USERS, CREATE_USER, EDIT_USER, DELETE_USER;
        LIST_USERS, CREATE_USER, EDIT_USER, DELETE_USER, LIST_REPOSITORY_MEMBERS,
        SET_REPOSITORY_MEMBERS, LIST_FEDERATION_REGISTRATIONS, LIST_FEDERATION_RESULTS,
        LIST_FEDERATION_PROPOSALS, LIST_FEDERATION_SETS;
        public static RpcRequest fromName(String name) {
            for (RpcRequest type : values()) {
                if (type.name().equalsIgnoreCase(name)) {
@@ -212,11 +214,11 @@
            }
            return LIST_REPOSITORIES;
        }
        public boolean exceeds(RpcRequest type) {
            return this.ordinal() > type.ordinal();
        }
        @Override
        public String toString() {
            return name();
src/com/gitblit/FederationPullExecutor.java
@@ -19,6 +19,7 @@
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.text.MessageFormat;
import java.util.ArrayList;
@@ -43,6 +44,7 @@
import com.gitblit.Constants.FederationPullStatus;
import com.gitblit.Constants.FederationStrategy;
import com.gitblit.GitBlitException.ForbiddenException;
import com.gitblit.models.FederationModel;
import com.gitblit.models.RepositoryModel;
import com.gitblit.models.UserModel;
@@ -81,8 +83,8 @@
     * 
     * @param registrations
     * @param isDaemon
     *            if true, registrations are rescheduled in perpetuity. if false,
     *            the federation pull operation is executed once.
     *            if true, registrations are rescheduled in perpetuity. if
     *            false, the federation pull operation is executed once.
     */
    public FederationPullExecutor(List<FederationModel> registrations, boolean isDaemon) {
        this.registrations = registrations;
@@ -169,7 +171,7 @@
            } else {
                repositoryName = registrationFolder + "/" + repository.name;
            }
            if (registration.bare) {
                // bare repository, ensure .git suffix
                if (!repositoryName.toLowerCase().endsWith(DOT_GIT_EXT)) {
@@ -178,7 +180,8 @@
            } else {
                // normal repository, strip .git suffix
                if (repositoryName.toLowerCase().endsWith(DOT_GIT_EXT)) {
                    repositoryName = repositoryName.substring(0, repositoryName.indexOf(DOT_GIT_EXT));
                    repositoryName = repositoryName.substring(0,
                            repositoryName.indexOf(DOT_GIT_EXT));
                }
            }
@@ -190,7 +193,8 @@
                StoredConfig config = existingRepository.getConfig();
                config.load();
                String origin = config.getString("remote", "origin", "url");
                RevCommit commit = JGitUtils.getCommit(existingRepository, "refs/remotes/origin/master");
                RevCommit commit = JGitUtils.getCommit(existingRepository,
                        "refs/remotes/origin/master");
                if (commit != null) {
                    fetchHead = commit.getName();
                }
@@ -209,7 +213,7 @@
                    Constants.FEDERATION_USER, registration.token);
            logger.info(MessageFormat.format("Pulling federated repository {0} from {1} @ {2}",
                    repository.name, registration.name, registration.url));
            CloneResult result = JGitUtils.cloneRepository(registrationFolderFile, repository.name,
                    cloneUrl, registration.bare, credentials);
            Repository r = GitBlit.self().getRepository(repositoryName);
@@ -255,7 +259,7 @@
                // preserve local settings
                repository.isFrozen = rm.isFrozen;
                repository.federationStrategy = rm.federationStrategy;
                // merge federation sets
                Set<String> federationSets = new HashSet<String>();
                if (rm.federationSets != null) {
@@ -317,13 +321,12 @@
                    }
                }
            }
        } catch (Exception e) {
            // a 403 error code is normal for a PULL_REPOSITORIES token
            if (!e.getMessage().contains("403")) {
                logger.warn(MessageFormat.format(
                        "Failed to retrieve USERS from federated gitblit ({0} @ {1})",
                        registration.name, registration.url), e);
            }
        } catch (ForbiddenException e) {
            // ignore forbidden exceptions
        } catch (IOException e) {
            logger.warn(MessageFormat.format(
                    "Failed to retrieve USERS from federated gitblit ({0} @ {1})",
                    registration.name, registration.url), e);
        }
        try {
@@ -337,13 +340,12 @@
                properties.store(os, null);
                os.close();
            }
        } catch (Exception e) {
            // a 403 error code is normal for a PULL_REPOSITORIES token
            if (!e.getMessage().contains("403")) {
                logger.warn(MessageFormat.format(
                        "Failed to retrieve SETTINGS from federated gitblit ({0} @ {1})",
                        registration.name, registration.url), e);
            }
        } catch (ForbiddenException e) {
            // ignore forbidden exceptions
        } catch (IOException e) {
            logger.warn(MessageFormat.format(
                    "Failed to retrieve SETTINGS from federated gitblit ({0} @ {1})",
                    registration.name, registration.url), e);
        }
    }
src/com/gitblit/GitBlit.java
@@ -59,6 +59,7 @@
import com.gitblit.Constants.FederationToken;
import com.gitblit.models.FederationModel;
import com.gitblit.models.FederationProposal;
import com.gitblit.models.FederationSet;
import com.gitblit.models.RepositoryModel;
import com.gitblit.models.UserModel;
import com.gitblit.utils.FederationUtils;
@@ -874,6 +875,29 @@
    }
    /**
     * Returns the list of federation sets.
     *
     * @return list of federation sets
     */
    public List<FederationSet> getFederationSets(String gitblitUrl) {
        List<FederationSet> list = new ArrayList<FederationSet>();
        // generate standard tokens
        for (FederationToken type : FederationToken.values()) {
            FederationSet fset = new FederationSet(type.toString(), type, getFederationToken(type));
            fset.repositories = getRepositories(gitblitUrl, fset.token);
            list.add(fset);
        }
        // generate tokens for federation sets
        for (String set : settings.getStrings(Keys.federation.sets)) {
            FederationSet fset = new FederationSet(set, FederationToken.REPOSITORIES,
                    getFederationToken(set));
            fset.repositories = getRepositories(gitblitUrl, fset.token);
            list.add(fset);
        }
        return list;
    }
    /**
     * Returns the list of possible federation tokens for this Gitblit instance.
     * 
     * @return list of federation tokens
@@ -1025,7 +1049,8 @@
            });
            for (File file : files) {
                String json = com.gitblit.utils.FileUtils.readContent(file, null);
                FederationProposal proposal = JsonUtils.fromJsonString(json, FederationProposal.class);
                FederationProposal proposal = JsonUtils.fromJsonString(json,
                        FederationProposal.class);
                list.add(proposal);
            }
        }
src/com/gitblit/GitBlitException.java
@@ -15,17 +15,45 @@
 */
package com.gitblit;
import java.io.IOException;
/**
 * GitBlitException is a marginally useful class. :)
 * 
 * @author James Moger
 * 
 */
public class GitBlitException extends Exception {
public class GitBlitException extends IOException {
    private static final long serialVersionUID = 1L;
    public GitBlitException(String message) {
        super(message);
    }
    /**
     * Exception to indicate that the client should prompt for credentials
     * because the requested action requires authentication.
     */
    public static class UnauthorizedException extends GitBlitException {
        private static final long serialVersionUID = 1L;
        public UnauthorizedException(String message) {
            super(message);
        }
    }
    /**
     * Exception to indicate that the requested action can not be executed by
     * the specified user.
     */
    public static class ForbiddenException extends GitBlitException {
        private static final long serialVersionUID = 1L;
        public ForbiddenException(String message) {
            super(message);
        }
    }
}
src/com/gitblit/JsonServlet.java
@@ -17,6 +17,7 @@
import java.io.BufferedReader;
import java.io.IOException;
import java.lang.reflect.Type;
import java.text.MessageFormat;
import javax.servlet.ServletException;
@@ -27,6 +28,7 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.gitblit.utils.StringUtils;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
@@ -72,6 +74,30 @@
    protected <X> X deserialize(HttpServletRequest request, HttpServletResponse response,
            Class<X> clazz) throws IOException {
        String json = readJson(request, response);
        if (StringUtils.isEmpty(json)) {
            return null;
        }
        Gson gson = new Gson();
        X object = gson.fromJson(json.toString(), clazz);
        return object;
    }
    protected <X> X deserialize(HttpServletRequest request, HttpServletResponse response, Type type)
            throws IOException {
        String json = readJson(request, response);
        if (StringUtils.isEmpty(json)) {
            return null;
        }
        Gson gson = new Gson();
        X object = gson.fromJson(json.toString(), type);
        return object;
    }
    private String readJson(HttpServletRequest request, HttpServletResponse response)
            throws IOException {
        BufferedReader reader = request.getReader();
        StringBuilder json = new StringBuilder();
        String line = null;
@@ -86,10 +112,7 @@
            response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
            return null;
        }
        Gson gson = new Gson();
        X object = gson.fromJson(json.toString(), clazz);
        return object;
        return json.toString();
    }
    protected void serialize(HttpServletResponse response, Object o) throws IOException {
src/com/gitblit/RpcServlet.java
@@ -18,6 +18,7 @@
import java.io.IOException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -30,6 +31,7 @@
import com.gitblit.models.RepositoryModel;
import com.gitblit.models.UserModel;
import com.gitblit.utils.HttpUtils;
import com.gitblit.utils.RpcUtils;
/**
 * Handles remote procedure calls.
@@ -57,6 +59,7 @@
    protected void processRequest(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        RpcRequest reqType = RpcRequest.fromName(request.getParameter("req"));
        String objectName = request.getParameter("name");
        logger.info(MessageFormat.format("Rpc {0} request from {1}", reqType,
                request.getRemoteAddr()));
@@ -88,6 +91,78 @@
                users.add(GitBlit.self().getUserModel(name));
            }
            result = users;
        } else if (RpcRequest.CREATE_REPOSITORY.equals(reqType)) {
            // create repository
            RepositoryModel model = deserialize(request, response, RepositoryModel.class);
            GitBlit.self().updateRepositoryModel(model.name, model, true);
        } else if (RpcRequest.EDIT_REPOSITORY.equals(reqType)) {
            // edit repository
            RepositoryModel model = deserialize(request, response, RepositoryModel.class);
            // name parameter specifies original repository name in event of
            // rename
            String repoName = objectName;
            if (repoName == null) {
                repoName = model.name;
            }
            GitBlit.self().updateRepositoryModel(repoName, model, false);
        } else if (RpcRequest.DELETE_REPOSITORY.equals(reqType)) {
            // delete repository
            RepositoryModel model = deserialize(request, response, RepositoryModel.class);
            GitBlit.self().deleteRepositoryModel(model);
        } else if (RpcRequest.CREATE_USER.equals(reqType)) {
            // create user
            UserModel model = deserialize(request, response, UserModel.class);
            GitBlit.self().updateUserModel(model.username, model, true);
        } else if (RpcRequest.EDIT_USER.equals(reqType)) {
            // edit user
            UserModel model = deserialize(request, response, UserModel.class);
            // name parameter specifies original user name in event of rename
            String username = objectName;
            if (username == null) {
                username = model.username;
            }
            GitBlit.self().updateUserModel(username, model, false);
        } else if (RpcRequest.DELETE_USER.equals(reqType)) {
            // delete user
            UserModel model = deserialize(request, response, UserModel.class);
            GitBlit.self().deleteUser(model.username);
        } else if (RpcRequest.LIST_REPOSITORY_MEMBERS.equals(reqType)) {
            // get repository members
            RepositoryModel model = GitBlit.self().getRepositoryModel(objectName);
            result = GitBlit.self().getRepositoryUsers(model);
        } else if (RpcRequest.SET_REPOSITORY_MEMBERS.equals(reqType)) {
            // update repository access list
            RepositoryModel model = GitBlit.self().getRepositoryModel(objectName);
            Collection<String> names = deserialize(request, response, RpcUtils.NAMES_TYPE);
            List<String> users = new ArrayList<String>(names);
            if (!GitBlit.self().setRepositoryUsers(model, users)) {
                response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
            }
        } else if (RpcRequest.LIST_FEDERATION_REGISTRATIONS.equals(reqType)) {
            // return the list of federation registrations
            result = GitBlit.self().getFederationRegistrations();
        } else if (RpcRequest.LIST_FEDERATION_RESULTS.equals(reqType)) {
            // return the list of federation result registrations
            if (GitBlit.canFederate()) {
                result = GitBlit.self().getFederationResultRegistrations();
            } else {
                response.sendError(HttpServletResponse.SC_FORBIDDEN);
            }
        } else if (RpcRequest.LIST_FEDERATION_PROPOSALS.equals(reqType)) {
            // return the list of federation proposals
            if (GitBlit.canFederate()) {
                result = GitBlit.self().getPendingFederationProposals();
            } else {
                response.sendError(HttpServletResponse.SC_FORBIDDEN);
            }
        } else if (RpcRequest.LIST_FEDERATION_SETS.equals(reqType)) {
            // return the list of federation sets
            if (GitBlit.canFederate()) {
                String gitblitUrl = HttpUtils.getGitblitURL(request);
                result = GitBlit.self().getFederationSets(gitblitUrl);
            } else {
                response.sendError(HttpServletResponse.SC_FORBIDDEN);
            }
        }
        // send the result of the request
src/com/gitblit/models/FederationSet.java
New file
@@ -0,0 +1,58 @@
/*
 * Copyright 2011 gitblit.com.
 *
 * 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.gitblit.models;
import java.io.Serializable;
import java.util.Map;
import com.gitblit.Constants.FederationToken;
/**
 * Represents a group of repositories.
 */
public class FederationSet implements Serializable {
    private static final long serialVersionUID = 1L;
    public String name;
    public String token;
    public FederationToken tokenType;
    public Map<String, RepositoryModel> repositories;
    /**
     * The constructor for a federation set.
     *
     * @param name
     *            the name of this federation set
     * @param tokenType
     *            the type of token of this federation set
     * @param token
     *            the federation token
     */
    public FederationSet(String name, FederationToken tokenType, String token) {
        this.name = name;
        this.tokenType = tokenType;
        this.token = token;
    }
    @Override
    public String toString() {
        return "Federation Set (" + name + ")";
    }
}
src/com/gitblit/utils/FederationUtils.java
@@ -49,16 +49,13 @@
 */
public class FederationUtils {
    public static final Type REPOSITORIES_TYPE = new TypeToken<Map<String, RepositoryModel>>() {
    private static final Type REPOSITORIES_TYPE = new TypeToken<Map<String, RepositoryModel>>() {
    }.getType();
    public static final Type SETTINGS_TYPE = new TypeToken<Map<String, String>>() {
    private static final Type SETTINGS_TYPE = new TypeToken<Map<String, String>>() {
    }.getType();
    public static final Type USERS_TYPE = new TypeToken<Collection<UserModel>>() {
    }.getType();
    public static final Type RESULTS_TYPE = new TypeToken<List<FederationModel>>() {
    private static final Type USERS_TYPE = new TypeToken<Collection<UserModel>>() {
    }.getType();
    private static final Logger LOGGER = LoggerFactory.getLogger(FederationUtils.class);
@@ -276,10 +273,11 @@
     * @return a collection of UserModel objects
     * @throws Exception
     */
    public static Collection<UserModel> getUsers(FederationModel registration) throws Exception {
    public static List<UserModel> getUsers(FederationModel registration) throws Exception {
        String url = asLink(registration.url, registration.token, FederationRequest.PULL_USERS);
        Collection<UserModel> models = JsonUtils.retrieveJson(url, USERS_TYPE);
        return models;
        List<UserModel> list = new ArrayList<UserModel>(models);
        return list;
    }
    /**
src/com/gitblit/utils/JsonUtils.java
@@ -16,6 +16,7 @@
package com.gitblit.utils;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
@@ -36,6 +37,10 @@
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import org.eclipse.jgit.util.Base64;
import com.gitblit.GitBlitException.ForbiddenException;
import com.gitblit.GitBlitException.UnauthorizedException;
import com.gitblit.models.RepositoryModel;
import com.gitblit.models.UserModel;
import com.google.gson.Gson;
@@ -43,7 +48,7 @@
import com.google.gson.reflect.TypeToken;
/**
 * Utility methods for gson calls to a Gitblit server.
 * Utility methods for json calls to a Gitblit server.
 * 
 * @author James Moger
 * 
@@ -98,7 +103,7 @@
        Gson gson = new Gson();
        return gson.fromJson(json, clazz);
    }
    /**
     * Convert a json string to an object of the specified type.
     * 
@@ -116,11 +121,27 @@
     * 
     * @param url
     * @param type
     * @return
     * @throws Exception
     * @return the deserialized object
     * @throws {@link IOException}
     */
    public static <X> X retrieveJson(String url, Type type) throws Exception {
        String json = retrieveJsonString(url);
    public static <X> X retrieveJson(String url, Type type) throws IOException,
            UnauthorizedException {
        return retrieveJson(url, type, null, null);
    }
    /**
     * Reads a gson object from the specified url.
     *
     * @param url
     * @param type
     * @param username
     * @param password
     * @return the deserialized object
     * @throws {@link IOException}
     */
    public static <X> X retrieveJson(String url, Type type, String username, char[] password)
            throws IOException {
        String json = retrieveJsonString(url, username, password);
        if (StringUtils.isEmpty(json)) {
            return null;
        }
@@ -133,29 +154,42 @@
     * 
     * @param url
     * @return the JSON message as a string
     * @throws Exception
     * @throws {@link IOException}
     */
    public static String retrieveJsonString(String url) throws Exception {
        URL urlObject = new URL(url);
        URLConnection conn = urlObject.openConnection();
        conn.setRequestProperty("Accept-Charset", CHARSET);
        conn.setUseCaches(false);
        conn.setDoInput(true);
        if (conn instanceof HttpsURLConnection) {
            HttpsURLConnection secureConn = (HttpsURLConnection) conn;
            secureConn.setSSLSocketFactory(SSL_CONTEXT.getSocketFactory());
            secureConn.setHostnameVerifier(HOSTNAME_VERIFIER);
    public static String retrieveJsonString(String url, String username, char[] password)
            throws IOException {
        try {
            URL urlObject = new URL(url);
            URLConnection conn = urlObject.openConnection();
            conn.setRequestProperty("Accept-Charset", CHARSET);
            setAuthorization(conn, username, password);
            conn.setUseCaches(false);
            conn.setDoInput(true);
            if (conn instanceof HttpsURLConnection) {
                HttpsURLConnection secureConn = (HttpsURLConnection) conn;
                secureConn.setSSLSocketFactory(SSL_CONTEXT.getSocketFactory());
                secureConn.setHostnameVerifier(HOSTNAME_VERIFIER);
            }
            InputStream is = conn.getInputStream();
            BufferedReader reader = new BufferedReader(new InputStreamReader(is, CHARSET));
            StringBuilder json = new StringBuilder();
            char[] buffer = new char[4096];
            int len = 0;
            while ((len = reader.read(buffer)) > -1) {
                json.append(buffer, 0, len);
            }
            is.close();
            return json.toString();
        } catch (IOException e) {
            if (e.getMessage().indexOf("401") > -1) {
                // unauthorized
                throw new UnauthorizedException(url);
            } else if (e.getMessage().indexOf("403") > -1) {
                // requested url is forbidden by the requesting user
                throw new ForbiddenException(url);
            }
            throw e;
        }
        InputStream is = conn.getInputStream();
        BufferedReader reader = new BufferedReader(new InputStreamReader(is, CHARSET));
        StringBuilder json = new StringBuilder();
        char[] buffer = new char[4096];
        int len = 0;
        while ((len = reader.read(buffer)) > -1) {
            json.append(buffer, 0, len);
        }
        is.close();
        return json.toString();
    }
    /**
@@ -166,29 +200,67 @@
     * @param json
     *            the json message to send
     * @return the http request result code
     * @throws Exception
     * @throws {@link IOException}
     */
    public static int sendJsonString(String url, String json) throws Exception {
        byte[] jsonBytes = json.getBytes(CHARSET);
        URL urlObject = new URL(url);
        URLConnection conn = urlObject.openConnection();
        conn.setRequestProperty("Content-Type", "text/plain;charset=" + CHARSET);
        conn.setRequestProperty("Content-Length", "" + jsonBytes.length);
        conn.setUseCaches(false);
        conn.setDoOutput(true);
        if (conn instanceof HttpsURLConnection) {
            HttpsURLConnection secureConn = (HttpsURLConnection) conn;
            secureConn.setSSLSocketFactory(SSL_CONTEXT.getSocketFactory());
            secureConn.setHostnameVerifier(HOSTNAME_VERIFIER);
    public static int sendJsonString(String url, String json) throws IOException {
        return sendJsonString(url, json, null, null);
    }
    /**
     * Sends a JSON message.
     *
     * @param url
     *            the url to write to
     * @param json
     *            the json message to send
     * @param username
     * @param password
     * @return the http request result code
     * @throws {@link IOException}
     */
    public static int sendJsonString(String url, String json, String username, char[] password)
            throws IOException {
        try {
            byte[] jsonBytes = json.getBytes(CHARSET);
            URL urlObject = new URL(url);
            URLConnection conn = urlObject.openConnection();
            conn.setRequestProperty("Content-Type", "text/plain;charset=" + CHARSET);
            conn.setRequestProperty("Content-Length", "" + jsonBytes.length);
            setAuthorization(conn, username, password);
            conn.setUseCaches(false);
            conn.setDoOutput(true);
            if (conn instanceof HttpsURLConnection) {
                HttpsURLConnection secureConn = (HttpsURLConnection) conn;
                secureConn.setSSLSocketFactory(SSL_CONTEXT.getSocketFactory());
                secureConn.setHostnameVerifier(HOSTNAME_VERIFIER);
            }
            // write json body
            OutputStream os = conn.getOutputStream();
            os.write(jsonBytes);
            os.close();
            int status = ((HttpURLConnection) conn).getResponseCode();
            return status;
        } catch (IOException e) {
            if (e.getMessage().indexOf("401") > -1) {
                // unauthorized
                throw new UnauthorizedException(url);
            } else if (e.getMessage().indexOf("403") > -1) {
                // requested url is forbidden by the requesting user
                throw new ForbiddenException(url);
            }
            throw e;
        }
    }
        // write json body
        OutputStream os = conn.getOutputStream();
        os.write(jsonBytes);
        os.close();
        int status = ((HttpURLConnection) conn).getResponseCode();
        return status;
    private static void setAuthorization(URLConnection conn, String username, char[] password) {
        if (!StringUtils.isEmpty(username) && (password != null && password.length > 0)) {
            conn.setRequestProperty(
                    "Authorization",
                    "Basic "
                            + Base64.encodeBytes((username + ":" + new String(password)).getBytes()));
        }
    }
    /**
src/com/gitblit/utils/RpcUtils.java
@@ -15,12 +15,18 @@
 */
package com.gitblit.utils;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import com.gitblit.Constants;
import com.gitblit.Constants.RpcRequest;
import com.gitblit.models.FederationModel;
import com.gitblit.models.FederationProposal;
import com.gitblit.models.FederationSet;
import com.gitblit.models.RepositoryModel;
import com.gitblit.models.UserModel;
import com.google.gson.reflect.TypeToken;
@@ -33,10 +39,22 @@
 */
public class RpcUtils {
    public static final Type REPOSITORIES_TYPE = new TypeToken<Map<String, RepositoryModel>>() {
    public static final Type NAMES_TYPE = new TypeToken<Collection<String>>() {
    }.getType();
    public static final Type USERS_TYPE = new TypeToken<Collection<UserModel>>() {
    private static final Type REPOSITORIES_TYPE = new TypeToken<Map<String, RepositoryModel>>() {
    }.getType();
    private static final Type USERS_TYPE = new TypeToken<Collection<UserModel>>() {
    }.getType();
    private static final Type REGISTRATIONS_TYPE = new TypeToken<Collection<FederationModel>>() {
    }.getType();
    private static final Type PROPOSALS_TYPE = new TypeToken<Collection<FederationProposal>>() {
    }.getType();
    private static final Type SETS_TYPE = new TypeToken<Collection<FederationSet>>() {
    }.getType();
    /**
@@ -48,26 +66,45 @@
     * @return
     */
    public static String asLink(String remoteURL, RpcRequest req) {
        return asLink(remoteURL, req, null);
    }
    /**
     *
     * @param remoteURL
     *            the url of the remote gitblit instance
     * @param req
     *            the rpc request type
     * @param name
     *            the name of the actionable object
     * @return
     */
    public static String asLink(String remoteURL, RpcRequest req, String name) {
        if (remoteURL.length() > 0 && remoteURL.charAt(remoteURL.length() - 1) == '/') {
            remoteURL = remoteURL.substring(0, remoteURL.length() - 1);
        }
        if (req == null) {
            req = RpcRequest.LIST_REPOSITORIES;
        }
        return remoteURL + Constants.RPC_PATH + "?req=" + req.name().toLowerCase();
        return remoteURL + Constants.RPC_PATH + "?req=" + req.name().toLowerCase()
                + (name == null ? "" : ("&name=" + name));
    }
    /**
     * Retrieves a map of the repositories at the remote gitblit instance keyed
     * by the repository clone url.
     * 
     * @param serverUrl
     * @param account
     * @param password
     * @return a map of cloneable repositories
     * @throws Exception
     * @throws IOException
     */
    public static Map<String, RepositoryModel> getRepositories(String serverUrl) throws Exception {
    public static Map<String, RepositoryModel> getRepositories(String serverUrl, String account,
            char[] password) throws IOException {
        String url = asLink(serverUrl, RpcRequest.LIST_REPOSITORIES);
        Map<String, RepositoryModel> models = JsonUtils.retrieveJson(url, REPOSITORIES_TYPE);
        Map<String, RepositoryModel> models = JsonUtils.retrieveJson(url, REPOSITORIES_TYPE,
                account, password);
        return models;
    }
@@ -75,12 +112,243 @@
     * Tries to pull the gitblit user accounts from the remote gitblit instance.
     * 
     * @param serverUrl
     * @param account
     * @param password
     * @return a collection of UserModel objects
     * @throws Exception
     * @throws IOException
     */
    public static Collection<UserModel> getUsers(String serverUrl) throws Exception {
    public static List<UserModel> getUsers(String serverUrl, String account, char[] password)
            throws IOException {
        String url = asLink(serverUrl, RpcRequest.LIST_USERS);
        Collection<UserModel> models = JsonUtils.retrieveJson(url, USERS_TYPE);
        return models;
        Collection<UserModel> models = JsonUtils.retrieveJson(url, USERS_TYPE, account, password);
        List<UserModel> list = new ArrayList<UserModel>(models);
        return list;
    }
    /**
     * Create a repository on the Gitblit server.
     *
     * @param repository
     * @param serverUrl
     * @param account
     * @param password
     * @return true if the action succeeded
     * @throws IOException
     */
    public static boolean createRepository(RepositoryModel repository, String serverUrl,
            String account, char[] password) throws IOException {
        return doAction(RpcRequest.CREATE_REPOSITORY, null, repository, serverUrl, account,
                password);
    }
    /**
     * Send a revised version of the repository model to the Gitblit server.
     *
     * @param repository
     * @param serverUrl
     * @param account
     * @param password
     * @return true if the action succeeded
     * @throws IOException
     */
    public static boolean updateRepository(String repositoryName, RepositoryModel repository,
            String serverUrl, String account, char[] password) throws IOException {
        return doAction(RpcRequest.EDIT_REPOSITORY, repositoryName, repository, serverUrl, account,
                password);
    }
    /**
     * Delete a repository from the Gitblit server.
     *
     * @param repository
     * @param serverUrl
     * @param account
     * @param password
     * @return true if the action succeeded
     * @throws IOException
     */
    public static boolean deleteRepository(RepositoryModel repository, String serverUrl,
            String account, char[] password) throws IOException {
        return doAction(RpcRequest.DELETE_REPOSITORY, null, repository, serverUrl, account,
                password);
    }
    /**
     * Create a user on the Gitblit server.
     *
     * @param user
     * @param serverUrl
     * @param account
     * @param password
     * @return true if the action succeeded
     * @throws IOException
     */
    public static boolean createUser(UserModel user, String serverUrl, String account,
            char[] password) throws IOException {
        return doAction(RpcRequest.CREATE_USER, null, user, serverUrl, account, password);
    }
    /**
     * Send a revised version of the user model to the Gitblit server.
     *
     * @param user
     * @param serverUrl
     * @param account
     * @param password
     * @return true if the action succeeded
     * @throws IOException
     */
    public static boolean updateUser(String username, UserModel user, String serverUrl,
            String account, char[] password) throws IOException {
        return doAction(RpcRequest.EDIT_USER, username, user, serverUrl, account, password);
    }
    /**
     * Deletes a user from the Gitblit server.
     *
     * @param user
     * @param serverUrl
     * @param account
     * @param password
     * @return true if the action succeeded
     * @throws IOException
     */
    public static boolean deleteUser(UserModel user, String serverUrl, String account,
            char[] password) throws IOException {
        return doAction(RpcRequest.DELETE_USER, null, user, serverUrl, account, password);
    }
    /**
     * Retrieves the list of users that can access the specified repository.
     *
     * @param repository
     * @param serverUrl
     * @param account
     * @param password
     * @return list of members
     * @throws IOException
     */
    public static List<String> getRepositoryMembers(RepositoryModel repository, String serverUrl,
            String account, char[] password) throws IOException {
        String url = asLink(serverUrl, RpcRequest.LIST_REPOSITORY_MEMBERS, repository.name);
        Collection<String> list = JsonUtils.retrieveJson(url, NAMES_TYPE, account, password);
        return new ArrayList<String>(list);
    }
    /**
     * Sets the repository membership list.
     *
     * @param repository
     * @param memberships
     * @param serverUrl
     * @param account
     * @param password
     * @return true if the action succeeded
     * @throws IOException
     */
    public static boolean setRepositoryMembers(RepositoryModel repository,
            List<String> memberships, String serverUrl, String account, char[] password)
            throws IOException {
        return doAction(RpcRequest.SET_REPOSITORY_MEMBERS, repository.name, memberships, serverUrl,
                account, password);
    }
    /**
     * Retrieves the list of federation registrations. These are the list of
     * registrations that this Gitblit instance is pulling from.
     *
     * @param serverUrl
     * @param account
     * @param password
     * @return a collection of FederationRegistration objects
     * @throws IOException
     */
    public static List<FederationModel> getFederationRegistrations(String serverUrl,
            String account, char[] password) throws IOException {
        String url = asLink(serverUrl, RpcRequest.LIST_FEDERATION_REGISTRATIONS);
        Collection<FederationModel> registrations = JsonUtils.retrieveJson(url, REGISTRATIONS_TYPE,
                account, password);
        List<FederationModel> list = new ArrayList<FederationModel>(registrations);
        return list;
    }
    /**
     * Retrieves the list of federation result registrations. These are the
     * results reported back to this Gitblit instance from a federation client.
     *
     * @param serverUrl
     * @param account
     * @param password
     * @return a collection of FederationRegistration objects
     * @throws IOException
     */
    public static List<FederationModel> getFederationResultRegistrations(String serverUrl,
            String account, char[] password) throws IOException {
        String url = asLink(serverUrl, RpcRequest.LIST_FEDERATION_RESULTS);
        Collection<FederationModel> registrations = JsonUtils.retrieveJson(url, REGISTRATIONS_TYPE,
                account, password);
        List<FederationModel> list = new ArrayList<FederationModel>(registrations);
        return list;
    }
    /**
     * Retrieves the list of federation proposals.
     *
     * @param serverUrl
     * @param account
     * @param password
     * @return a collection of FederationProposal objects
     * @throws IOException
     */
    public static List<FederationProposal> getFederationProposals(String serverUrl,
            String account, char[] password) throws IOException {
        String url = asLink(serverUrl, RpcRequest.LIST_FEDERATION_PROPOSALS);
        Collection<FederationProposal> proposals = JsonUtils.retrieveJson(url, PROPOSALS_TYPE,
                account, password);
        List<FederationProposal> list = new ArrayList<FederationProposal>(proposals);
        return list;
    }
    /**
     * Retrieves the list of federation repository sets.
     *
     * @param serverUrl
     * @param account
     * @param password
     * @return a collection of FederationSet objects
     * @throws IOException
     */
    public static List<FederationSet> getFederationSets(String serverUrl,
            String account, char[] password) throws IOException {
        String url = asLink(serverUrl, RpcRequest.LIST_FEDERATION_SETS);
        Collection<FederationSet> sets = JsonUtils.retrieveJson(url, SETS_TYPE,
                account, password);
        List<FederationSet> list = new ArrayList<FederationSet>(sets);
        return list;
    }
    /**
     * Do the specified administrative action on the Gitblit server.
     *
     * @param request
     * @param name
     *            the name of the object (may be null)
     * @param object
     * @param serverUrl
     * @param account
     * @param password
     * @return true if the action succeeded
     * @throws IOException
     */
    protected static boolean doAction(RpcRequest request, String name, Object object,
            String serverUrl, String account, char[] password) throws IOException {
        String url = asLink(serverUrl, request, name);
        String json = JsonUtils.toJsonString(object);
        int resultCode = JsonUtils.sendJsonString(url, json, account, password);
        return resultCode == 200;
    }
}
tests/com/gitblit/tests/FederationTests.java
@@ -64,12 +64,6 @@
        Thread.sleep(2500);
    }
    public void testDeserialization() throws Exception {
        String json = "{\"https://localhost:8443/git/a.b.c.orphan.git\":{\"name\":\"a.b.c.orphan.git\",\"description\":\"\",\"owner\":\"\",\"lastChange\":\"Jul 22, 2011 3:15:07 PM\",\"hasCommits\":true,\"showRemoteBranches\":false,\"useTickets\":false,\"useDocs\":false,\"accessRestriction\":\"NONE\",\"isFrozen\":false,\"showReadme\":false,\"isFederated\":false},\"https://localhost:8443/git/test/jgit.git\":{\"name\":\"test/jgit.git\",\"description\":\"\",\"owner\":\"\",\"lastChange\":\"Jul 13, 2011 9:42:33 AM\",\"hasCommits\":true,\"showRemoteBranches\":true,\"useTickets\":false,\"useDocs\":false,\"accessRestriction\":\"NONE\",\"isFrozen\":false,\"showReadme\":false,\"isFederated\":false},\"https://localhost:8443/git/test/helloworld.git\":{\"name\":\"test/helloworld.git\",\"description\":\"\",\"owner\":\"\",\"lastChange\":\"Jul 15, 2008 7:26:48 PM\",\"hasCommits\":true,\"showRemoteBranches\":false,\"useTickets\":false,\"useDocs\":false,\"accessRestriction\":\"NONE\",\"isFrozen\":false,\"showReadme\":false,\"isFederated\":false},\"https://localhost:8443/git/working/ticgit\":{\"name\":\"working/ticgit\",\"description\":\"\",\"owner\":\"\",\"lastChange\":\"Jul 22, 2011 10:35:27 AM\",\"hasCommits\":true,\"showRemoteBranches\":false,\"useTickets\":false,\"useDocs\":false,\"accessRestriction\":\"NONE\",\"isFrozen\":false,\"showReadme\":false,\"isFederated\":false},\"https://localhost:8443/git/ticgit.git\":{\"name\":\"ticgit.git\",\"description\":\"\",\"owner\":\"\",\"lastChange\":\"Jul 22, 2011 10:35:27 AM\",\"hasCommits\":true,\"showRemoteBranches\":true,\"useTickets\":true,\"useDocs\":true,\"accessRestriction\":\"NONE\",\"isFrozen\":false,\"showReadme\":false,\"isFederated\":false},\"https://localhost:8443/git/helloworld.git\":{\"name\":\"helloworld.git\",\"description\":\"\",\"owner\":\"\",\"lastChange\":\"Jul 15, 2008 7:26:48 PM\",\"hasCommits\":true,\"showRemoteBranches\":false,\"useTickets\":false,\"useDocs\":false,\"accessRestriction\":\"NONE\",\"isFrozen\":false,\"showReadme\":false,\"isFederated\":false},\"https://localhost:8443/git/test/helloworld3.git\":{\"name\":\"test/helloworld3.git\",\"description\":\"\",\"owner\":\"\",\"lastChange\":\"Jul 15, 2008 7:26:48 PM\",\"hasCommits\":true,\"showRemoteBranches\":false,\"useTickets\":false,\"useDocs\":false,\"accessRestriction\":\"NONE\",\"isFrozen\":false,\"showReadme\":false,\"isFederated\":false},\"https://localhost:8443/git/test/bluez-gnome.git\":{\"name\":\"test/bluez-gnome.git\",\"description\":\"\",\"owner\":\"\",\"lastChange\":\"Dec 19, 2008 6:35:33 AM\",\"hasCommits\":true,\"showRemoteBranches\":false,\"useTickets\":false,\"useDocs\":false,\"accessRestriction\":\"NONE\",\"isFrozen\":false,\"showReadme\":false,\"isFederated\":false}}";
        Map<String, RepositoryModel> models = JsonUtils.fromJsonString(json, FederationUtils.REPOSITORIES_TYPE);
        assertEquals(8, models.size());
    }
    public void testProposal() throws Exception {
        // create dummy repository data
        Map<String, RepositoryModel> repositories = new HashMap<String, RepositoryModel>();
@@ -96,9 +90,9 @@
    public void testPullRepositories() throws Exception {
        try {
            String url = FederationUtils.asLink("http://localhost:" + port,
                    "testtoken", FederationRequest.PULL_REPOSITORIES);
            String json = JsonUtils.retrieveJsonString(url);
            String url = FederationUtils.asLink("http://localhost:" + port, "testtoken",
                    FederationRequest.PULL_REPOSITORIES);
            String json = JsonUtils.retrieveJsonString(url, null, null);
        } catch (IOException e) {
            if (!e.getMessage().contains("403")) {
                throw e;
tests/com/gitblit/tests/RpcTests.java
@@ -16,25 +16,185 @@
package com.gitblit.tests;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import junit.framework.TestCase;
import com.gitblit.Constants.AccessRestrictionType;
import com.gitblit.GitBlitException.UnauthorizedException;
import com.gitblit.models.FederationModel;
import com.gitblit.models.FederationProposal;
import com.gitblit.models.FederationSet;
import com.gitblit.models.RepositoryModel;
import com.gitblit.models.UserModel;
import com.gitblit.utils.RpcUtils;
/**
 * Tests all the rpc client utility methods, the rpc filter and rpc servlet.
 *
 * @author James Moger
 *
 */
public class RpcTests extends TestCase {
    public void testListRepositories() throws Exception {
        Map<String, RepositoryModel> map = null;
        try {
            map = RpcUtils.getRepositories("https://localhost:8443");
        } catch (IOException e) {
            if (!e.getMessage().contains("403")) {
                throw e;
            }
        }
    String url = "https://localhost:8443";
    String account = "admin";
    String password = "admin";
    public void testListRepositories() throws IOException {
        Map<String, RepositoryModel> map = RpcUtils.getRepositories(url, null, null);
        assertTrue("Repository list is null!", map != null);
        assertTrue("Repository list is empty!", map.size() > 0);
    }
    public void testListUsers() throws IOException {
        List<UserModel> list = null;
        try {
            list = RpcUtils.getUsers(url, null, null);
        } catch (UnauthorizedException e) {
        }
        assertTrue("Server allows anyone to admin!", list == null);
        list = RpcUtils.getUsers(url, "admin", "admin".toCharArray());
        assertTrue("User list is empty!", list.size() > 0);
    }
    public void testUserAdministration() throws IOException {
        UserModel user = new UserModel("garbage");
        user.canAdmin = true;
        user.password = "whocares";
        // create
        assertTrue("Failed to create user!",
                RpcUtils.createUser(user, url, account, password.toCharArray()));
        UserModel retrievedUser = findUser(user.username);
        assertTrue("Failed to find " + user.username, retrievedUser != null);
        assertTrue("Retrieved user can not administer Gitblit", retrievedUser.canAdmin);
        // rename and toggle admin permission
        String originalName = user.username;
        user.username = "garbage2";
        user.canAdmin = false;
        assertTrue("Failed to update user!",
                RpcUtils.updateUser(originalName, user, url, account, password.toCharArray()));
        retrievedUser = findUser(user.username);
        assertTrue("Failed to find " + user.username, retrievedUser != null);
        assertTrue("Retrieved user did not update", !retrievedUser.canAdmin);
        // delete
        assertTrue("Failed to delete " + user.username,
                RpcUtils.deleteUser(retrievedUser, url, account, password.toCharArray()));
        retrievedUser = findUser(user.username);
        assertTrue("Failed to delete " + user.username, retrievedUser == null);
    }
    private UserModel findUser(String name) throws IOException {
        List<UserModel> users = RpcUtils.getUsers(url, account, password.toCharArray());
        UserModel retrievedUser = null;
        for (UserModel model : users) {
            if (model.username.equalsIgnoreCase(name)) {
                retrievedUser = model;
                break;
            }
        }
        return retrievedUser;
    }
    public void testRepositoryAdministration() throws IOException {
        RepositoryModel model = new RepositoryModel();
        model.name = "garbagerepo.git";
        model.description = "created by RpcUtils";
        model.owner = "garbage";
        model.accessRestriction = AccessRestrictionType.VIEW;
        // create
        assertTrue("Failed to create repository!",
                RpcUtils.createRepository(model, url, account, password.toCharArray()));
        RepositoryModel retrievedRepository = findRepository(model.name);
        assertTrue("Failed to find " + model.name, retrievedRepository != null);
        assertTrue("Access retriction type is wrong",
                AccessRestrictionType.VIEW.equals(retrievedRepository.accessRestriction));
        // rename and change access restriciton
        String originalName = model.name;
        model.name = "garbagerepo2.git";
        model.accessRestriction = AccessRestrictionType.PUSH;
        assertTrue("Failed to update repository!", RpcUtils.updateRepository(originalName, model,
                url, account, password.toCharArray()));
        retrievedRepository = findRepository(model.name);
        assertTrue("Failed to find " + model.name, retrievedRepository != null);
        assertTrue("Access retriction type is wrong",
                AccessRestrictionType.PUSH.equals(retrievedRepository.accessRestriction));
        // memberships
        String testMember = "justadded";
        List<String> members = RpcUtils.getRepositoryMembers(retrievedRepository, url, account,
                password.toCharArray());
        assertTrue("Membership roster is not empty!", members.size() == 0);
        members.add(testMember);
        assertTrue(
                "Failed to set memberships!",
                RpcUtils.setRepositoryMembers(retrievedRepository, members, url, account,
                        password.toCharArray()));
        members = RpcUtils.getRepositoryMembers(retrievedRepository, url, account,
                password.toCharArray());
        boolean foundMember = false;
        for (String member : members) {
            if (member.equalsIgnoreCase(testMember)) {
                foundMember = true;
                break;
            }
        }
        assertTrue("Failed to find member!", foundMember);
        // delete
        assertTrue("Failed to delete " + model.name, RpcUtils.deleteRepository(retrievedRepository,
                url, account, password.toCharArray()));
        retrievedRepository = findRepository(model.name);
        assertTrue("Failed to delete " + model.name, retrievedRepository == null);
    }
    private RepositoryModel findRepository(String name) throws IOException {
        Map<String, RepositoryModel> repositories = RpcUtils.getRepositories(url, account,
                password.toCharArray());
        RepositoryModel retrievedRepository = null;
        for (RepositoryModel model : repositories.values()) {
            if (model.name.equalsIgnoreCase(name)) {
                retrievedRepository = model;
                break;
            }
        }
        return retrievedRepository;
    }
    public void testFederationRegistrations() throws Exception {
        List<FederationModel> registrations = RpcUtils.getFederationRegistrations(url, account,
                password.toCharArray());
        assertTrue("No federation registrations wre retrieved!", registrations.size() > 0);
    }
    public void testFederationResultRegistrations() throws Exception {
        List<FederationModel> registrations = RpcUtils.getFederationResultRegistrations(url,
                account, password.toCharArray());
        assertTrue("No federation result registrations were retrieved!", registrations.size() > 0);
    }
    public void testFederationProposals() throws Exception {
        List<FederationProposal> proposals = RpcUtils.getFederationProposals(url,
                account, password.toCharArray());
        assertTrue("No federation proposals were retrieved!", proposals.size() > 0);
    }
    public void testFederationSets() throws Exception {
        List<FederationSet> sets = RpcUtils.getFederationSets(url,
                account, password.toCharArray());
        assertTrue("No federation sets were retrieved!", sets.size() > 0);
    }
}