From 997c16d6826cfa1bef33ba08e15055cc407b9398 Mon Sep 17 00:00:00 2001
From: James Moger <james.moger@gitblit.com>
Date: Tue, 13 Dec 2011 17:36:58 -0500
Subject: [PATCH] Federation support for Teams
---
src/com/gitblit/FederationServlet.java | 20 ++++++
docs/02_federation.mkd | 10 ++-
src/com/gitblit/FederationPullExecutor.java | 32 ++++++++++
tests/com/gitblit/tests/FederationTests.java | 84 ++++++++++++++++++++++++---
src/com/gitblit/models/UserModel.java | 12 ++++
src/com/gitblit/Constants.java | 2
src/com/gitblit/utils/FederationUtils.java | 18 ++++++
7 files changed, 160 insertions(+), 18 deletions(-)
diff --git a/docs/02_federation.mkd b/docs/02_federation.mkd
index a592c1e..6525000 100644
--- a/docs/02_federation.mkd
+++ b/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.
diff --git a/src/com/gitblit/Constants.java b/src/com/gitblit/Constants.java
index 3279980..c2d5eb2 100644
--- a/src/com/gitblit/Constants.java
+++ b/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()) {
diff --git a/src/com/gitblit/FederationPullExecutor.java b/src/com/gitblit/FederationPullExecutor.java
index 20fd67c..c84761b 100644
--- a/src/com/gitblit/FederationPullExecutor.java
+++ b/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);
+ }
+ }
+ }
}
}
}
diff --git a/src/com/gitblit/FederationServlet.java b/src/com/gitblit/FederationServlet.java
index 0be1066..f2ed903 100644
--- a/src/com/gitblit/FederationServlet.java
+++ b/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;
@@ -90,7 +91,7 @@
if (proposal == null) {
return;
}
-
+
// reject proposal, if not receipt prohibited
if (!GitBlit.getBoolean(Keys.federation.allowProposals, false)) {
logger.error(MessageFormat.format("Rejected {0} federation proposal from {1}",
@@ -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;
}
}
diff --git a/src/com/gitblit/models/UserModel.java b/src/com/gitblit/models/UserModel.java
index bd8974d..ecb97cf 100644
--- a/src/com/gitblit/models/UserModel.java
+++ b/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;
diff --git a/src/com/gitblit/utils/FederationUtils.java b/src/com/gitblit/utils/FederationUtils.java
index 324aa67..8207962 100644
--- a/src/com/gitblit/utils/FederationUtils.java
+++ b/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.
*
diff --git a/tests/com/gitblit/tests/FederationTests.java b/tests/com/gitblit/tests/FederationTests.java
index ed65100..499c610 100644
--- a/tests/com/gitblit/tests/FederationTests.java
+++ b/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 testJsonRepositories() throws Exception {
+ String requrl = FederationUtils.asLink(url, token, FederationRequest.PULL_REPOSITORIES);
+ String json = JsonUtils.retrieveJsonString(requrl, null, null);
+ 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 {
- try {
- String requrl = FederationUtils.asLink(url, "d7cc58921a80b37e0329a4dae2f9af38bf61ef5c",
- FederationRequest.PULL_REPOSITORIES);
- String json = JsonUtils.retrieveJsonString(requrl, null, null);
- } catch (IOException e) {
- if (!e.getMessage().contains("403")) {
- throw e;
- }
- }
+ 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);
}
}
--
Gitblit v1.9.1