From 31abc26dd0354bc2dafe27c011c2e54934a89486 Mon Sep 17 00:00:00 2001 From: James Moger Date: Sun, 2 Oct 2011 15:37:24 -0400 Subject: [PATCH] Fairly complete json rpc interface to view/control Gitblit data objects. --- src/com/gitblit/Constants.java | 12 +- src/com/gitblit/FederationPullExecutor.java | 44 +-- src/com/gitblit/GitBlit.java | 27 +- src/com/gitblit/GitBlitException.java | 30 +- src/com/gitblit/JsonServlet.java | 31 +- src/com/gitblit/RpcServlet.java | 75 +++++ src/com/gitblit/models/FederationSet.java | 58 ++++ src/com/gitblit/utils/FederationUtils.java | 14 +- src/com/gitblit/utils/JsonUtils.java | 166 ++++++++--- src/com/gitblit/utils/RpcUtils.java | 290 ++++++++++++++++++- tests/com/gitblit/tests/FederationTests.java | 12 +- tests/com/gitblit/tests/RpcTests.java | 176 ++++++++++- 12 files changed, 820 insertions(+), 115 deletions(-) create mode 100644 src/com/gitblit/models/FederationSet.java diff --git a/src/com/gitblit/Constants.java b/src/com/gitblit/Constants.java index 3862a9db..5b4b0b6a 100644 --- a/src/com/gitblit/Constants.java +++ b/src/com/gitblit/Constants.java @@ -52,7 +52,7 @@ public class Constants { 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 class Constants { */ 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 @@ public class Constants { } return LIST_REPOSITORIES; } - + public boolean exceeds(RpcRequest type) { return this.ordinal() > type.ordinal(); } - + @Override public String toString() { return name(); diff --git a/src/com/gitblit/FederationPullExecutor.java b/src/com/gitblit/FederationPullExecutor.java index ef089d03..b190e7b5 100644 --- a/src/com/gitblit/FederationPullExecutor.java +++ b/src/com/gitblit/FederationPullExecutor.java @@ -19,6 +19,7 @@ import static org.eclipse.jgit.lib.Constants.DOT_GIT_EXT; 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 org.slf4j.LoggerFactory; 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 @@ public class FederationPullExecutor implements Runnable { * * @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 registrations, boolean isDaemon) { this.registrations = registrations; @@ -169,7 +171,7 @@ public class FederationPullExecutor implements Runnable { } else { repositoryName = registrationFolder + "/" + repository.name; } - + if (registration.bare) { // bare repository, ensure .git suffix if (!repositoryName.toLowerCase().endsWith(DOT_GIT_EXT)) { @@ -178,7 +180,8 @@ public class FederationPullExecutor implements Runnable { } 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 @@ public class FederationPullExecutor implements Runnable { 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 @@ public class FederationPullExecutor implements Runnable { 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 @@ public class FederationPullExecutor implements Runnable { // preserve local settings repository.isFrozen = rm.isFrozen; repository.federationStrategy = rm.federationStrategy; - + // merge federation sets Set federationSets = new HashSet(); if (rm.federationSets != null) { @@ -317,13 +321,12 @@ public class FederationPullExecutor implements Runnable { } } } - } 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 @@ public class FederationPullExecutor implements Runnable { 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); } } diff --git a/src/com/gitblit/GitBlit.java b/src/com/gitblit/GitBlit.java index e86fcf60..73ec29e0 100644 --- a/src/com/gitblit/GitBlit.java +++ b/src/com/gitblit/GitBlit.java @@ -59,6 +59,7 @@ import com.gitblit.Constants.FederationStrategy; 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; @@ -873,6 +874,29 @@ public class GitBlit implements ServletContextListener { return null; } + /** + * Returns the list of federation sets. + * + * @return list of federation sets + */ + public List getFederationSets(String gitblitUrl) { + List list = new ArrayList(); + // 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. * @@ -1025,7 +1049,8 @@ public class GitBlit implements ServletContextListener { }); 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); } } diff --git a/src/com/gitblit/GitBlitException.java b/src/com/gitblit/GitBlitException.java index 032e41f7..af32003a 100644 --- a/src/com/gitblit/GitBlitException.java +++ b/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); + } + } } diff --git a/src/com/gitblit/JsonServlet.java b/src/com/gitblit/JsonServlet.java index b1d1053d..ad1d67b6 100644 --- a/src/com/gitblit/JsonServlet.java +++ b/src/com/gitblit/JsonServlet.java @@ -17,6 +17,7 @@ package com.gitblit; 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 javax.servlet.http.HttpServletResponse; 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 @@ public abstract class JsonServlet extends HttpServlet { protected X deserialize(HttpServletRequest request, HttpServletResponse response, Class 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 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 @@ public abstract class JsonServlet extends HttpServlet { 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 { diff --git a/src/com/gitblit/RpcServlet.java b/src/com/gitblit/RpcServlet.java index 9d26ee03..4dee3190 100644 --- a/src/com/gitblit/RpcServlet.java +++ b/src/com/gitblit/RpcServlet.java @@ -18,6 +18,7 @@ package com.gitblit; 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.Constants.RpcRequest; 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 @@ public class RpcServlet extends JsonServlet { 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 @@ public class RpcServlet extends JsonServlet { 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 names = deserialize(request, response, RpcUtils.NAMES_TYPE); + List users = new ArrayList(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 diff --git a/src/com/gitblit/models/FederationSet.java b/src/com/gitblit/models/FederationSet.java new file mode 100644 index 00000000..357689c9 --- /dev/null +++ b/src/com/gitblit/models/FederationSet.java @@ -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 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 + ")"; + } +} diff --git a/src/com/gitblit/utils/FederationUtils.java b/src/com/gitblit/utils/FederationUtils.java index d04a7a31..324aa67e 100644 --- a/src/com/gitblit/utils/FederationUtils.java +++ b/src/com/gitblit/utils/FederationUtils.java @@ -49,16 +49,13 @@ import com.google.gson.reflect.TypeToken; */ public class FederationUtils { - public static final Type REPOSITORIES_TYPE = new TypeToken>() { + private static final Type REPOSITORIES_TYPE = new TypeToken>() { }.getType(); - public static final Type SETTINGS_TYPE = new TypeToken>() { + private static final Type SETTINGS_TYPE = new TypeToken>() { }.getType(); - public static final Type USERS_TYPE = new TypeToken>() { - }.getType(); - - public static final Type RESULTS_TYPE = new TypeToken>() { + private static final Type USERS_TYPE = new TypeToken>() { }.getType(); private static final Logger LOGGER = LoggerFactory.getLogger(FederationUtils.class); @@ -276,10 +273,11 @@ public class FederationUtils { * @return a collection of UserModel objects * @throws Exception */ - public static Collection getUsers(FederationModel registration) throws Exception { + public static List getUsers(FederationModel registration) throws Exception { String url = asLink(registration.url, registration.token, FederationRequest.PULL_USERS); Collection models = JsonUtils.retrieveJson(url, USERS_TYPE); - return models; + List list = new ArrayList(models); + return list; } /** diff --git a/src/com/gitblit/utils/JsonUtils.java b/src/com/gitblit/utils/JsonUtils.java index a697b7a5..1edfc583 100644 --- a/src/com/gitblit/utils/JsonUtils.java +++ b/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.SSLSession; 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.GsonBuilder; 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 @@ public class JsonUtils { Gson gson = new Gson(); return gson.fromJson(json, clazz); } - + /** * Convert a json string to an object of the specified type. * @@ -116,11 +121,27 @@ public class JsonUtils { * * @param url * @param type - * @return - * @throws Exception + * @return the deserialized object + * @throws {@link IOException} */ - public static X retrieveJson(String url, Type type) throws Exception { - String json = retrieveJsonString(url); + public static 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 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 @@ public class JsonUtils { * * @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); - } - 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); + 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; } - is.close(); - return json.toString(); } /** @@ -166,29 +200,67 @@ public class JsonUtils { * @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); + } - // write json body - OutputStream os = conn.getOutputStream(); - os.write(jsonBytes); - os.close(); + /** + * 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); + } - int status = ((HttpURLConnection) conn).getResponseCode(); - return status; + // 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; + } + } + + 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())); + } } /** diff --git a/src/com/gitblit/utils/RpcUtils.java b/src/com/gitblit/utils/RpcUtils.java index 919c7bb3..715ecb57 100644 --- a/src/com/gitblit/utils/RpcUtils.java +++ b/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 @@ import com.google.gson.reflect.TypeToken; */ public class RpcUtils { - public static final Type REPOSITORIES_TYPE = new TypeToken>() { + public static final Type NAMES_TYPE = new TypeToken>() { }.getType(); - public static final Type USERS_TYPE = new TypeToken>() { + private static final Type REPOSITORIES_TYPE = new TypeToken>() { + }.getType(); + + private static final Type USERS_TYPE = new TypeToken>() { + }.getType(); + + private static final Type REGISTRATIONS_TYPE = new TypeToken>() { + }.getType(); + + private static final Type PROPOSALS_TYPE = new TypeToken>() { + }.getType(); + + private static final Type SETS_TYPE = new TypeToken>() { }.getType(); /** @@ -48,26 +66,45 @@ public class RpcUtils { * @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 getRepositories(String serverUrl) throws Exception { + public static Map getRepositories(String serverUrl, String account, + char[] password) throws IOException { String url = asLink(serverUrl, RpcRequest.LIST_REPOSITORIES); - Map models = JsonUtils.retrieveJson(url, REPOSITORIES_TYPE); + Map models = JsonUtils.retrieveJson(url, REPOSITORIES_TYPE, + account, password); return models; } @@ -75,12 +112,243 @@ public class RpcUtils { * 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 getUsers(String serverUrl) throws Exception { + public static List getUsers(String serverUrl, String account, char[] password) + throws IOException { String url = asLink(serverUrl, RpcRequest.LIST_USERS); - Collection models = JsonUtils.retrieveJson(url, USERS_TYPE); - return models; + Collection models = JsonUtils.retrieveJson(url, USERS_TYPE, account, password); + List list = new ArrayList(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 getRepositoryMembers(RepositoryModel repository, String serverUrl, + String account, char[] password) throws IOException { + String url = asLink(serverUrl, RpcRequest.LIST_REPOSITORY_MEMBERS, repository.name); + Collection list = JsonUtils.retrieveJson(url, NAMES_TYPE, account, password); + return new ArrayList(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 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 getFederationRegistrations(String serverUrl, + String account, char[] password) throws IOException { + String url = asLink(serverUrl, RpcRequest.LIST_FEDERATION_REGISTRATIONS); + Collection registrations = JsonUtils.retrieveJson(url, REGISTRATIONS_TYPE, + account, password); + List list = new ArrayList(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 getFederationResultRegistrations(String serverUrl, + String account, char[] password) throws IOException { + String url = asLink(serverUrl, RpcRequest.LIST_FEDERATION_RESULTS); + Collection registrations = JsonUtils.retrieveJson(url, REGISTRATIONS_TYPE, + account, password); + List list = new ArrayList(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 getFederationProposals(String serverUrl, + String account, char[] password) throws IOException { + String url = asLink(serverUrl, RpcRequest.LIST_FEDERATION_PROPOSALS); + Collection proposals = JsonUtils.retrieveJson(url, PROPOSALS_TYPE, + account, password); + List list = new ArrayList(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 getFederationSets(String serverUrl, + String account, char[] password) throws IOException { + String url = asLink(serverUrl, RpcRequest.LIST_FEDERATION_SETS); + Collection sets = JsonUtils.retrieveJson(url, SETS_TYPE, + account, password); + List list = new ArrayList(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; } } diff --git a/tests/com/gitblit/tests/FederationTests.java b/tests/com/gitblit/tests/FederationTests.java index 8af31671..66bb949c 100644 --- a/tests/com/gitblit/tests/FederationTests.java +++ b/tests/com/gitblit/tests/FederationTests.java @@ -64,12 +64,6 @@ public class FederationTests extends TestCase { 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 models = JsonUtils.fromJsonString(json, FederationUtils.REPOSITORIES_TYPE); - assertEquals(8, models.size()); - } - public void testProposal() throws Exception { // create dummy repository data Map repositories = new HashMap(); @@ -96,9 +90,9 @@ public class FederationTests extends TestCase { 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; diff --git a/tests/com/gitblit/tests/RpcTests.java b/tests/com/gitblit/tests/RpcTests.java index e140fb46..cb98a560 100644 --- a/tests/com/gitblit/tests/RpcTests.java +++ b/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 map = null; + String url = "https://localhost:8443"; + String account = "admin"; + String password = "admin"; + + public void testListRepositories() throws IOException { + Map 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 list = null; try { - map = RpcUtils.getRepositories("https://localhost:8443"); - } catch (IOException e) { - if (!e.getMessage().contains("403")) { - throw e; + 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 users = RpcUtils.getUsers(url, account, password.toCharArray()); + UserModel retrievedUser = null; + for (UserModel model : users) { + if (model.username.equalsIgnoreCase(name)) { + retrievedUser = model; + break; } } - assertTrue("Repository list is null!", map != null); - assertTrue("Repository list is empty!", map.size() > 0); + 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 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 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 registrations = RpcUtils.getFederationRegistrations(url, account, + password.toCharArray()); + assertTrue("No federation registrations wre retrieved!", registrations.size() > 0); + } + + public void testFederationResultRegistrations() throws Exception { + List registrations = RpcUtils.getFederationResultRegistrations(url, + account, password.toCharArray()); + assertTrue("No federation result registrations were retrieved!", registrations.size() > 0); + } + + public void testFederationProposals() throws Exception { + List proposals = RpcUtils.getFederationProposals(url, + account, password.toCharArray()); + assertTrue("No federation proposals were retrieved!", proposals.size() > 0); + } + + public void testFederationSets() throws Exception { + List sets = RpcUtils.getFederationSets(url, + account, password.toCharArray()); + assertTrue("No federation sets were retrieved!", sets.size() > 0); } } -- 2.39.5