docs/02_federation.mkd | ●●●●● patch | view | raw | blame | history | |
src/com/gitblit/Constants.java | ●●●●● patch | view | raw | blame | history | |
src/com/gitblit/FederationPullExecutor.java | ●●●●● patch | view | raw | blame | history | |
src/com/gitblit/FederationServlet.java | ●●●●● patch | view | raw | blame | history | |
src/com/gitblit/models/UserModel.java | ●●●●● patch | view | raw | blame | history | |
src/com/gitblit/utils/FederationUtils.java | ●●●●● patch | view | raw | blame | history | |
tests/com/gitblit/tests/FederationTests.java | ●●●●● patch | view | raw | blame | history |
docs/02_federation.mkd
@@ -13,7 +13,9 @@ ### Important Changes to Note The Gitblit 0.7.0 federation protocol is incompatible with the 0.6.0 federation protocol because of a change in the way timestamps are formatted. The *Gitblit 0.8.0* federation protocol adds retrieval of team definitions. Older clients will not know to request team information. The *Gitblit 0.7.0* federation protocol is incompatible with the 0.6.0 federation protocol because of a change in the way timestamps are formatted. Gitblit 0.6.0 uses the default [google-gson](http://google-gson.googlecode.com) timestamp serializer which generates locally formatted timestamps. Unfortunately, this creates problems for distributed repositories and distributed developers. Gitblit 0.7.0 corrects this error by serializing dates to the [iso8601](http://en.wikipedia.org/wiki/ISO_8601) standard. As a result 0.7.0 is not compatible with 0.6.0. A partial backwards-compatibility fallback was considered but it would only work one direction and since the federation mechanism is bidirectional it was not implemented. @@ -151,13 +153,13 @@ During a federated pull operation, Gitblit does check that the *origin* of the local repository starts with the url of the federation registration. If they do not match, the repository is skipped and this is indicated in the log. #### User Accounts #### User Accounts & Teams By default all user accounts except the *admin* account are automatically pulled when using the *ALL* token or the *USERS_AND_REPOSITORIES* token. You may exclude a user account from being pulled by a federated Gitblit instance by checking *exclude from federation* in the edit user page. By default all user accounts and teams (except the *admin* account) are automatically pulled when using the *ALL* token or the *USERS_AND_REPOSITORIES* token. You may exclude a user account from being pulled by a federated Gitblit instance by checking *exclude from federation* in the edit user page. The pulling Gitblit instance will store a registration-specific `users.conf` file for the pulled user accounts and their repository permissions. This file is stored in the *federation.N.folder* folder. If you specify *federation.N.mergeAccounts=true*, then the user accounts from the origin Gitblit instance will be integrated into the `users.conf` file of your Gitblit instance and allow sign-on of those users. If you specify *federation.N.mergeAccounts=true*, then the user accounts and team definitions from the origin Gitblit instance will be integrated into the `users.conf` file of your Gitblit instance and allow sign-on of those users. **NOTE:** Upgrades from older Gitblit versions will not have the *#notfederated* role assigned to the *admin* account. Without that role, your admin account WILL be transferred with an *ALL* or *USERS_AND_REPOSITORIES* token. src/com/gitblit/Constants.java
@@ -117,7 +117,7 @@ * Enumeration representing the types of federation requests. */ public static enum FederationRequest { POKE, PROPOSAL, PULL_REPOSITORIES, PULL_USERS, PULL_SETTINGS, STATUS; POKE, PROPOSAL, PULL_REPOSITORIES, PULL_USERS, PULL_TEAMS, PULL_SETTINGS, STATUS; public static FederationRequest fromName(String name) { for (FederationRequest type : values()) { src/com/gitblit/FederationPullExecutor.java
@@ -47,6 +47,7 @@ import com.gitblit.GitBlitException.ForbiddenException; import com.gitblit.models.FederationModel; import com.gitblit.models.RepositoryModel; import com.gitblit.models.TeamModel; import com.gitblit.models.UserModel; import com.gitblit.utils.FederationUtils; import com.gitblit.utils.JGitUtils; @@ -282,10 +283,12 @@ try { // Pull USERS // TeamModels are automatically pulled because they are contained // within the UserModel. The UserService creates unknown teams // and updates existing teams. Collection<UserModel> users = FederationUtils.getUsers(registration); if (users != null && users.size() > 0) { File realmFile = new File(registrationFolderFile, registration.name + "_users.conf"); File realmFile = new File(registrationFolderFile, registration.name + "_users.conf"); realmFile.delete(); ConfigUserService userService = new ConfigUserService(realmFile); for (UserModel user : users) { @@ -318,6 +321,31 @@ localUser.canAdmin = user.canAdmin; GitBlit.self().updateUserModel(localUser.username, localUser, false); } for (String teamname : GitBlit.self().getAllTeamnames()) { TeamModel team = GitBlit.self().getTeamModel(teamname); if (user.isTeamMember(teamname) && !team.hasUser(user.username)) { // new team member team.addUser(user.username); GitBlit.self().updateTeamModel(teamname, team, false); } else if (!user.isTeamMember(teamname) && team.hasUser(user.username)) { // remove team member team.removeUser(user.username); GitBlit.self().updateTeamModel(teamname, team, false); } // update team repositories TeamModel remoteTeam = user.getTeam(teamname); if (remoteTeam != null && remoteTeam.repositories != null) { int before = team.repositories.size(); team.addRepositories(remoteTeam.repositories); int after = team.repositories.size(); if (after > before) { // repository count changed, update GitBlit.self().updateTeamModel(teamname, team, false); } } } } } } src/com/gitblit/FederationServlet.java
@@ -27,6 +27,7 @@ import com.gitblit.Constants.FederationRequest; import com.gitblit.models.FederationModel; import com.gitblit.models.FederationProposal; import com.gitblit.models.TeamModel; import com.gitblit.models.UserModel; import com.gitblit.utils.FederationUtils; import com.gitblit.utils.HttpUtils; @@ -198,6 +199,23 @@ } } result = users; } else if (FederationRequest.PULL_TEAMS.equals(reqType)) { // pull teams if (!GitBlit.self().validateFederationRequest(reqType, token)) { // invalid token to pull teams logger.warn(MessageFormat.format( "Federation token from {0} not authorized to pull TEAMS", request.getRemoteAddr())); response.sendError(HttpServletResponse.SC_FORBIDDEN); return; } List<String> teamnames = GitBlit.self().getAllTeamnames(); List<TeamModel> teams = new ArrayList<TeamModel>(); for (String teamname : teamnames) { TeamModel user = GitBlit.self().getTeamModel(teamname); teams.add(user); } result = teams; } } src/com/gitblit/models/UserModel.java
@@ -96,6 +96,18 @@ return false; } public TeamModel getTeam(String teamname) { if (teams == null) { return null; } for (TeamModel team : teams) { if (team.name.equalsIgnoreCase(teamname)) { return team; } } return null; } @Override public String getName() { return username; src/com/gitblit/utils/FederationUtils.java
@@ -38,6 +38,7 @@ import com.gitblit.models.FederationModel; import com.gitblit.models.FederationProposal; import com.gitblit.models.RepositoryModel; import com.gitblit.models.TeamModel; import com.gitblit.models.UserModel; import com.google.gson.reflect.TypeToken; @@ -56,6 +57,9 @@ }.getType(); private static final Type USERS_TYPE = new TypeToken<Collection<UserModel>>() { }.getType(); private static final Type TEAMS_TYPE = new TypeToken<Collection<TeamModel>>() { }.getType(); private static final Logger LOGGER = LoggerFactory.getLogger(FederationUtils.class); @@ -281,6 +285,20 @@ } /** * Tries to pull the gitblit team definitions from the remote gitblit instance. * * @param registration * @return a collection of TeamModel objects * @throws Exception */ public static List<TeamModel> getTeams(FederationModel registration) throws Exception { String url = asLink(registration.url, registration.token, FederationRequest.PULL_TEAMS); Collection<TeamModel> models = JsonUtils.retrieveJson(url, TEAMS_TYPE); List<TeamModel> list = new ArrayList<TeamModel>(models); return list; } /** * Tries to pull the gitblit server settings from the remote gitblit * instance. * tests/com/gitblit/tests/FederationTests.java
@@ -16,10 +16,12 @@ package com.gitblit.tests; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import java.io.IOException; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean; @@ -31,16 +33,21 @@ import com.gitblit.Constants.FederationProposalResult; import com.gitblit.Constants.FederationRequest; import com.gitblit.Constants.FederationToken; import com.gitblit.models.FederationModel; import com.gitblit.models.FederationProposal; import com.gitblit.models.RepositoryModel; import com.gitblit.models.TeamModel; import com.gitblit.models.UserModel; import com.gitblit.utils.FederationUtils; import com.gitblit.utils.JsonUtils; import com.gitblit.utils.RpcUtils; public class FederationTests { String url = GitBlitSuite.url; String account = GitBlitSuite.account; String password = GitBlitSuite.password; String token = "d7cc58921a80b37e0329a4dae2f9af38bf61ef5c"; private static final AtomicBoolean started = new AtomicBoolean(false); @@ -81,15 +88,72 @@ } @Test public void testPullRepositories() throws Exception { try { String requrl = FederationUtils.asLink(url, "d7cc58921a80b37e0329a4dae2f9af38bf61ef5c", FederationRequest.PULL_REPOSITORIES); public void testJsonRepositories() throws Exception { String requrl = FederationUtils.asLink(url, token, FederationRequest.PULL_REPOSITORIES); String json = JsonUtils.retrieveJsonString(requrl, null, null); } catch (IOException e) { if (!e.getMessage().contains("403")) { throw e; assertNotNull(json); } @Test public void testJsonUsers() throws Exception { String requrl = FederationUtils.asLink(url, token, FederationRequest.PULL_USERS); String json = JsonUtils.retrieveJsonString(requrl, null, null); assertNotNull(json); } @Test public void testJsonTeams() throws Exception { String requrl = FederationUtils.asLink(url, token, FederationRequest.PULL_TEAMS); String json = JsonUtils.retrieveJsonString(requrl, null, null); assertNotNull(json); } private FederationModel getRegistration() { FederationModel model = new FederationModel("localhost"); model.url = this.url; model.token = this.token; return model; } @Test public void testPullRepositories() throws Exception { Map<String, RepositoryModel> repos = FederationUtils.getRepositories(getRegistration(), false); assertNotNull(repos); assertTrue(repos.size() > 0); } @Test public void testPullUsers() throws Exception { List<UserModel> users = FederationUtils.getUsers(getRegistration()); assertNotNull(users); // admin is excluded assertEquals(0, users.size()); UserModel newUser = new UserModel("test"); newUser.password = "whocares"; assertTrue(RpcUtils.createUser(newUser, url, account, password.toCharArray())); TeamModel team = new TeamModel("testteam"); team.addUser("test"); team.addRepository("helloworld.git"); assertTrue(RpcUtils.createTeam(team, url, account, password.toCharArray())); users = FederationUtils.getUsers(getRegistration()); assertNotNull(users); assertEquals(1, users.size()); newUser = users.get(0); assertTrue(newUser.isTeamMember("testteam")); assertTrue(RpcUtils.deleteUser(newUser, url, account, password.toCharArray())); assertTrue(RpcUtils.deleteTeam(team, url, account, password.toCharArray())); } @Test public void testPullTeams() throws Exception { List<TeamModel> teams = FederationUtils.getTeams(getRegistration()); assertNotNull(teams); assertTrue(teams.size() > 0); } }