Teams simplify the management of user-repository access permissions. Teams have a list of restricted repositories. Users are also added to teams and that grants them access to those repositories.
Federation and RPC support are still in-progress.
- Gitweb inspired web UI\r
- Administrators may create, edit, rename, or delete repositories through the web UI or RPC interface\r
- Administrators may create, edit, rename, or delete users through the web UI or RPC interface\r
+- Administrators may create, edit, rename, or delete teams through the web UI or RPC interface\r
- Repository Owners may edit repositories through the web UI\r
- Git-notes display support\r
- Branch metrics (uses Google Charts)\r
#### Repository Owner\r
The *Repository Owner* has the special permission of being able to edit a repository through the web UI. The Repository Owner is not permitted to rename the repository, delete the repository, or reassign ownership to another user.\r
\r
-### Administering Users (Gitblit v0.8.0+)\r
-All users are stored in the `users.conf` file or in the file you specified in `gitblit.properties`.<br/>\r
+### Teams\r
+\r
+Since v0.8.0, Gitblit supports *teams* for the original `users.properties` user service and the current default user service `users.conf`. Teams have assigned users and assigned repositories. A user can be a member of multiple teams and a repository may belong to multiple teams. This allows the administrator to quickly add a user to a team without having to keep track of all the appropriate repositories. \r
+\r
+### Administering Users (users.conf, Gitblit v0.8.0+)\r
+All users are stored in the `users.conf` file or in the file you specified in `gitblit.properties`. Your file extension must be *.conf* in order to use this user service.\r
+\r
The `users.conf` file uses a Git-style configuration format:\r
\r
[user "admin"]\r
role = "#notfederated"\r
repository = repo1.git\r
repository = repo2.git\r
+ \r
+ [user "hannibal"]\r
+ password = bossman\r
+\r
+ [user "faceman"]\r
+ password = vanity\r
+\r
+ [user "murdock"]\r
+ password = crazy \r
+ \r
+ [user "babaracus"]\r
+ password = grrrr\r
+ \r
+ [team "ateam"]\r
+ user = hannibal\r
+ user = faceman\r
+ user = murdock\r
+ user = babaracus\r
+ repository = topsecret.git\r
\r
The `users.conf` file allows flexibility for adding new fields to a UserModel object that the original `users.properties` file does not afford without imposing the complexity of relying on an embedded SQL database. \r
\r
-### Administering Users (Gitblit v0.5.0 - v0.7.0)\r
-All users are stored in the `users.properties` file or in the file you specified in `gitblit.properties`.<br/>\r
+### Administering Users (users.properties, Gitblit v0.5.0 - v0.7.0)\r
+All users are stored in the `users.properties` file or in the file you specified in `gitblit.properties`. Your file extension must be *.properties* in order to use this user service.\r
+\r
The format of `users.properties` follows Jetty's convention for HashRealms:\r
\r
username,password,role1,role2,role3...\r
+ @teamname,!username1,!username2,!username3,repository1,repository2,repository3...\r
\r
#### Usernames\r
Usernames must be unique and are case-insensitive. \r
\r
You may use your own custom *com.gitblit.IUserService* implementation by specifying its fully qualified classname in the *realm.userService* setting.\r
\r
-Your user service class must be on Gitblit's classpath and must have a public default constructor. \r
-\r
-%BEGINCODE%\r
-public interface IUserService {\r
-\r
- /**\r
- * Setup the user service.\r
- * \r
- * @param settings\r
- * @since 0.7.0\r
- */\r
- @Override\r
- public void setup(IStoredSettings settings) {\r
- }\r
- \r
- /**\r
- * Does the user service support cookie authentication?\r
- * \r
- * @return true or false\r
- */\r
- boolean supportsCookies();\r
-\r
- /**\r
- * Returns the cookie value for the specified user.\r
- * \r
- * @param model\r
- * @return cookie value\r
- */\r
- char[] getCookie(UserModel model);\r
-\r
- /**\r
- * Authenticate a user based on their cookie.\r
- * \r
- * @param cookie\r
- * @return a user object or null\r
- */\r
- UserModel authenticate(char[] cookie);\r
-\r
- /**\r
- * Authenticate a user based on a username and password.\r
- * \r
- * @param username\r
- * @param password\r
- * @return a user object or null\r
- */\r
- UserModel authenticate(String username, char[] password);\r
-\r
- /**\r
- * Retrieve the user object for the specified username.\r
- * \r
- * @param username\r
- * @return a user object or null\r
- */\r
- UserModel getUserModel(String username);\r
-\r
- /**\r
- * Updates/writes a complete user object.\r
- * \r
- * @param model\r
- * @return true if update is successful\r
- */\r
- boolean updateUserModel(UserModel model);\r
-\r
- /**\r
- * Adds/updates a user object keyed by username. This method allows for\r
- * renaming a user.\r
- * \r
- * @param username\r
- * the old username\r
- * @param model\r
- * the user object to use for username\r
- * @return true if update is successful\r
- */\r
- boolean updateUserModel(String username, UserModel model);\r
-\r
- /**\r
- * Deletes the user object from the user service.\r
- * \r
- * @param model\r
- * @return true if successful\r
- */\r
- boolean deleteUserModel(UserModel model);\r
-\r
- /**\r
- * Delete the user object with the specified username\r
- * \r
- * @param username\r
- * @return true if successful\r
- */\r
- boolean deleteUser(String username);\r
-\r
- /**\r
- * Returns the list of all users available to the login service.\r
- * \r
- * @return list of all usernames\r
- */\r
- List<String> getAllUsernames();\r
-\r
- /**\r
- * Returns the list of all users who are allowed to bypass the access\r
- * restriction placed on the specified repository.\r
- * \r
- * @param role\r
- * the repository name\r
- * @return list of all usernames that can bypass the access restriction\r
- */\r
- List<String> getUsernamesForRepositoryRole(String role);\r
-\r
- /**\r
- * Sets the list of all uses who are allowed to bypass the access\r
- * restriction placed on the specified repository.\r
- * \r
- * @param role\r
- * the repository name\r
- * @param usernames\r
- * @return true if successful\r
- */\r
- boolean setUsernamesForRepositoryRole(String role, List<String> usernames);\r
-\r
- /**\r
- * Renames a repository role.\r
- * \r
- * @param oldRole\r
- * @param newRole\r
- * @return true if successful\r
- */\r
- boolean renameRepositoryRole(String oldRole, String newRole);\r
-\r
- /**\r
- * Removes a repository role from all users.\r
- * \r
- * @param role\r
- * @return true if successful\r
- */\r
- boolean deleteRepositoryRole(String role);\r
-\r
- /**\r
- * @See java.lang.Object.toString();\r
- * @return string representation of the login service\r
- */\r
- String toString();\r
-}\r
-%ENDCODE%\r
+Your user service class must be on Gitblit's classpath and must have a public default constructor. \r
+Please see the following interface definition [com.gitblit.IUserService](https://github.com/gitblit/gitblit/blob/master/src/com/gitblit/IUserService.java).\r
\r
## Client Setup and Configuration\r
### Https with Self-Signed Certificates\r
- added: new default user service implementation: com.gitblit.ConfigUserService (users.conf) \r
This user service implementation allows for serialization and deserialization of more sophisticated Gitblit User objects and will open the door for more advanced Gitblit features. For upgrading installations, a `users.conf` file will automatically be created for you from your existing `users.properties` file on your first launch of Gitblit. You will have to manually set *realm.userService=users.conf* to switch to the new user service. The original `users.properties` file and it's corresponding implementation are deprecated. \r
**New:** *realm.userService = users.conf*\r
+- added: Teams for specifying user-repository access\r
- added: Gitblit Express bundle to get started running Gitblit on RedHat's OpenShift cloud\r
- added: optional Gravatar integration \r
**New:** *web.allowGravatar = true* \r
import org.slf4j.Logger;\r
import org.slf4j.LoggerFactory;\r
\r
+import com.gitblit.models.TeamModel;\r
import com.gitblit.models.UserModel;\r
+import com.gitblit.utils.DeepCopier;\r
import com.gitblit.utils.StringUtils;\r
\r
/**\r
*/\r
public class ConfigUserService implements IUserService {\r
\r
+ private static final String TEAM = "team";\r
+\r
+ private static final String USER = "user";\r
+\r
+ private static final String PASSWORD = "password";\r
+\r
+ private static final String REPOSITORY = "repository";\r
+\r
+ private static final String ROLE = "role";\r
+\r
private final File realmFile;\r
\r
private final Logger logger = LoggerFactory.getLogger(ConfigUserService.class);\r
\r
private final Map<String, UserModel> cookies = new ConcurrentHashMap<String, UserModel>();\r
\r
- private final String userSection = "user";\r
-\r
- private final String passwordField = "password";\r
-\r
- private final String repositoryField = "repository";\r
-\r
- private final String roleField = "role";\r
+ private final Map<String, TeamModel> teams = new ConcurrentHashMap<String, TeamModel>();\r
\r
private volatile long lastModified;\r
\r
* Setup the user service.\r
* \r
* @param settings\r
- * @since 0.6.1\r
+ * @since 0.7.0\r
*/\r
@Override\r
public void setup(IStoredSettings settings) {\r
public UserModel getUserModel(String username) {\r
read();\r
UserModel model = users.get(username.toLowerCase());\r
+ if (model != null) {\r
+ // clone the model, otherwise all changes to this object are\r
+ // live and unpersisted\r
+ model = DeepCopier.copy(model);\r
+ }\r
return model;\r
}\r
\r
public boolean updateUserModel(String username, UserModel model) {\r
try {\r
read();\r
- users.remove(username.toLowerCase());\r
+ UserModel oldUser = users.remove(username.toLowerCase());\r
users.put(model.username.toLowerCase(), model);\r
+ // null check on "final" teams because JSON-sourced UserModel\r
+ // can have a null teams object\r
+ if (model.teams != null) {\r
+ for (TeamModel team : model.teams) {\r
+ TeamModel t = teams.get(team.name.toLowerCase());\r
+ if (t == null) {\r
+ // new team\r
+ team.addUser(username);\r
+ teams.put(team.name.toLowerCase(), team);\r
+ } else {\r
+ // do not clobber existing team definition\r
+ // maybe because this is a federated user\r
+ t.removeUser(username);\r
+ t.addUser(model.username);\r
+ }\r
+ }\r
+\r
+ // check for implicit team removal\r
+ if (oldUser != null) {\r
+ for (TeamModel team : oldUser.teams) {\r
+ if (!model.isTeamMember(team.name)) {\r
+ team.removeUser(username);\r
+ }\r
+ }\r
+ }\r
+ }\r
write();\r
return true;\r
} catch (Throwable t) {\r
try {\r
// Read realm file\r
read();\r
- users.remove(username.toLowerCase());\r
+ UserModel model = users.remove(username.toLowerCase());\r
+ // remove user from team\r
+ for (TeamModel team : model.teams) {\r
+ TeamModel t = teams.get(team.name);\r
+ if (t == null) {\r
+ // new team\r
+ team.removeUser(username);\r
+ teams.put(team.name.toLowerCase(), team);\r
+ } else {\r
+ // existing team\r
+ t.removeUser(username);\r
+ }\r
+ }\r
write();\r
return true;\r
} catch (Throwable t) {\r
return false;\r
}\r
\r
+ /**\r
+ * Returns the list of all teams available to the login service.\r
+ * \r
+ * @return list of all teams\r
+ * @since 0.8.0\r
+ */\r
+ @Override\r
+ public List<String> getAllTeamNames() {\r
+ read();\r
+ List<String> list = new ArrayList<String>(teams.keySet());\r
+ return list;\r
+ }\r
+ \r
+ /**\r
+ * Returns the list of all users who are allowed to bypass the access\r
+ * restriction placed on the specified repository.\r
+ * \r
+ * @param role\r
+ * the repository name\r
+ * @return list of all usernames that can bypass the access restriction\r
+ */\r
+ @Override\r
+ public List<String> getTeamnamesForRepositoryRole(String role) {\r
+ List<String> list = new ArrayList<String>();\r
+ try {\r
+ read();\r
+ for (Map.Entry<String, TeamModel> entry : teams.entrySet()) {\r
+ TeamModel model = entry.getValue();\r
+ if (model.hasRepository(role)) {\r
+ list.add(model.name);\r
+ }\r
+ }\r
+ } catch (Throwable t) {\r
+ logger.error(MessageFormat.format("Failed to get teamnames for role {0}!", role), t);\r
+ }\r
+ return list;\r
+ }\r
+\r
+ /**\r
+ * Sets the list of all teams who are allowed to bypass the access\r
+ * restriction placed on the specified repository.\r
+ * \r
+ * @param role\r
+ * the repository name\r
+ * @param teamnames\r
+ * @return true if successful\r
+ */\r
+ @Override\r
+ public boolean setTeamnamesForRepositoryRole(String role, List<String> teamnames) {\r
+ try {\r
+ Set<String> specifiedTeams = new HashSet<String>();\r
+ for (String teamname : teamnames) {\r
+ specifiedTeams.add(teamname.toLowerCase());\r
+ }\r
+\r
+ read();\r
+\r
+ // identify teams which require add or remove role\r
+ for (TeamModel team : teams.values()) {\r
+ // team has role, check against revised team list\r
+ if (specifiedTeams.contains(team.name.toLowerCase())) {\r
+ team.addRepository(role);\r
+ } else {\r
+ // remove role from team\r
+ team.removeRepository(role);\r
+ }\r
+ }\r
+\r
+ // persist changes\r
+ write();\r
+ return true;\r
+ } catch (Throwable t) {\r
+ logger.error(MessageFormat.format("Failed to set teams for role {0}!", role), t);\r
+ }\r
+ return false;\r
+ }\r
+\r
+ /**\r
+ * Retrieve the team object for the specified team name.\r
+ * \r
+ * @param teamname\r
+ * @return a team object or null\r
+ * @since 0.8.0\r
+ */\r
+ @Override\r
+ public TeamModel getTeamModel(String teamname) {\r
+ read();\r
+ TeamModel model = teams.get(teamname.toLowerCase());\r
+ if (model != null) {\r
+ // clone the model, otherwise all changes to this object are\r
+ // live and unpersisted\r
+ model = DeepCopier.copy(model);\r
+ }\r
+ return model;\r
+ }\r
+\r
+ /**\r
+ * Updates/writes a complete team object.\r
+ * \r
+ * @param model\r
+ * @return true if update is successful\r
+ * @since 0.8.0\r
+ */\r
+ @Override\r
+ public boolean updateTeamModel(TeamModel model) {\r
+ return updateTeamModel(model.name, model);\r
+ }\r
+\r
+ /**\r
+ * Updates/writes and replaces a complete team object keyed by teamname.\r
+ * This method allows for renaming a team.\r
+ * \r
+ * @param teamname\r
+ * the old teamname\r
+ * @param model\r
+ * the team object to use for teamname\r
+ * @return true if update is successful\r
+ * @since 0.8.0\r
+ */\r
+ @Override\r
+ public boolean updateTeamModel(String teamname, TeamModel model) {\r
+ try {\r
+ read();\r
+ teams.remove(teamname.toLowerCase());\r
+ teams.put(model.name.toLowerCase(), model);\r
+ write();\r
+ return true;\r
+ } catch (Throwable t) {\r
+ logger.error(MessageFormat.format("Failed to update team model {0}!", model.name), t);\r
+ }\r
+ return false;\r
+ }\r
+\r
+ /**\r
+ * Deletes the team object from the user service.\r
+ * \r
+ * @param model\r
+ * @return true if successful\r
+ * @since 0.8.0\r
+ */\r
+ @Override\r
+ public boolean deleteTeamModel(TeamModel model) {\r
+ return deleteTeam(model.name);\r
+ }\r
+\r
+ /**\r
+ * Delete the team object with the specified teamname\r
+ * \r
+ * @param teamname\r
+ * @return true if successful\r
+ * @since 0.8.0\r
+ */\r
+ @Override\r
+ public boolean deleteTeam(String teamname) {\r
+ try {\r
+ // Read realm file\r
+ read();\r
+ teams.remove(teamname.toLowerCase());\r
+ write();\r
+ return true;\r
+ } catch (Throwable t) {\r
+ logger.error(MessageFormat.format("Failed to delete team {0}!", teamname), t);\r
+ }\r
+ return false;\r
+ }\r
+\r
/**\r
* Returns the list of all users available to the login service.\r
* \r
}\r
}\r
\r
+ // identify teams which require role rename\r
+ for (TeamModel model : teams.values()) {\r
+ if (model.hasRepository(oldRole)) {\r
+ model.removeRepository(oldRole);\r
+ model.addRepository(newRole);\r
+ }\r
+ }\r
// persist changes\r
write();\r
return true;\r
user.removeRepository(role);\r
}\r
\r
+ // identify teams which require role rename\r
+ for (TeamModel team : teams.values()) {\r
+ team.removeRepository(role);\r
+ }\r
+\r
// persist changes\r
write();\r
return true;\r
File realmFileCopy = new File(realmFile.getAbsolutePath() + ".tmp");\r
\r
StoredConfig config = new FileBasedConfig(realmFileCopy, FS.detect());\r
+\r
+ // write users\r
for (UserModel model : users.values()) {\r
- config.setString(userSection, model.username, passwordField, model.password);\r
+ config.setString(USER, model.username, PASSWORD, model.password);\r
\r
// user roles\r
List<String> roles = new ArrayList<String>();\r
if (model.excludeFromFederation) {\r
roles.add(Constants.NOT_FEDERATED_ROLE);\r
}\r
- config.setStringList(userSection, model.username, roleField, roles);\r
+ config.setStringList(USER, model.username, ROLE, roles);\r
\r
// repository memberships\r
- config.setStringList(userSection, model.username, repositoryField,\r
- new ArrayList<String>(model.repositories));\r
+ // null check on "final" repositories because JSON-sourced UserModel\r
+ // can have a null repositories object\r
+ if (model.repositories != null) {\r
+ config.setStringList(USER, model.username, REPOSITORY, new ArrayList<String>(\r
+ model.repositories));\r
+ }\r
+ }\r
+\r
+ // write teams\r
+ for (TeamModel model : teams.values()) {\r
+ // null check on "final" repositories because JSON-sourced TeamModel\r
+ // can have a null repositories object\r
+ if (model.repositories != null) {\r
+ config.setStringList(TEAM, model.name, REPOSITORY, new ArrayList<String>(\r
+ model.repositories));\r
+ }\r
+\r
+ // null check on "final" users because JSON-sourced TeamModel\r
+ // can have a null users object\r
+ if (model.users != null) {\r
+ config.setStringList(TEAM, model.name, USER, new ArrayList<String>(model.users));\r
+ }\r
}\r
+\r
config.save();\r
\r
// If the write is successful, delete the current file and rename\r
lastModified = realmFile.lastModified();\r
users.clear();\r
cookies.clear();\r
+ teams.clear();\r
+\r
try {\r
StoredConfig config = new FileBasedConfig(realmFile, FS.detect());\r
config.load();\r
- Set<String> usernames = config.getSubsections(userSection);\r
+ Set<String> usernames = config.getSubsections(USER);\r
for (String username : usernames) {\r
UserModel user = new UserModel(username);\r
- user.password = config.getString(userSection, username, passwordField);\r
+ user.password = config.getString(USER, username, PASSWORD);\r
\r
// user roles\r
Set<String> roles = new HashSet<String>(Arrays.asList(config.getStringList(\r
- userSection, username, roleField)));\r
+ USER, username, ROLE)));\r
user.canAdmin = roles.contains(Constants.ADMIN_ROLE);\r
user.excludeFromFederation = roles.contains(Constants.NOT_FEDERATED_ROLE);\r
\r
// repository memberships\r
Set<String> repositories = new HashSet<String>(Arrays.asList(config\r
- .getStringList(userSection, username, repositoryField)));\r
+ .getStringList(USER, username, REPOSITORY)));\r
for (String repository : repositories) {\r
user.addRepository(repository);\r
}\r
users.put(username, user);\r
cookies.put(StringUtils.getSHA1(username + user.password), user);\r
}\r
+\r
+ // load the teams\r
+ Set<String> teamnames = config.getSubsections(TEAM);\r
+ for (String teamname : teamnames) {\r
+ TeamModel team = new TeamModel(teamname);\r
+ team.addRepositories(Arrays.asList(config.getStringList(TEAM, teamname,\r
+ REPOSITORY)));\r
+ team.addUsers(Arrays.asList(config.getStringList(TEAM, teamname, USER)));\r
+\r
+ teams.put(team.name.toLowerCase(), team);\r
+\r
+ // set the teams on the users\r
+ for (String user : team.users) {\r
+ UserModel model = users.get(user);\r
+ if (model != null) {\r
+ model.teams.add(team);\r
+ }\r
+ }\r
+ }\r
} catch (Exception e) {\r
logger.error(MessageFormat.format("Failed to read {0}", realmFile), e);\r
}\r
import org.slf4j.Logger;\r
import org.slf4j.LoggerFactory;\r
\r
+import com.gitblit.models.TeamModel;\r
import com.gitblit.models.UserModel;\r
+import com.gitblit.utils.DeepCopier;\r
import com.gitblit.utils.StringUtils;\r
\r
/**\r
\r
private final Map<String, String> cookies = new ConcurrentHashMap<String, String>();\r
\r
+ private final Map<String, TeamModel> teams = new ConcurrentHashMap<String, TeamModel>();\r
+\r
public FileUserService(File realmFile) {\r
super(realmFile.getAbsolutePath());\r
}\r
* Setup the user service.\r
* \r
* @param settings\r
- * @since 0.6.1\r
+ * @since 0.7.0\r
*/\r
@Override\r
public void setup(IStoredSettings settings) {\r
model.addRepository(role);\r
}\r
}\r
+ // set the teams for the user\r
+ for (TeamModel team : teams.values()) {\r
+ if (team.hasUser(username)) {\r
+ model.teams.add(DeepCopier.copy(team));\r
+ }\r
+ }\r
return model;\r
}\r
\r
public boolean updateUserModel(String username, UserModel model) {\r
try {\r
Properties allUsers = read();\r
+ UserModel oldUser = getUserModel(username);\r
ArrayList<String> roles = new ArrayList<String>(model.repositories);\r
\r
// Permissions\r
allUsers.remove(username);\r
allUsers.put(model.username, sb.toString());\r
\r
+ // null check on "final" teams because JSON-sourced UserModel\r
+ // can have a null teams object\r
+ if (model.teams != null) {\r
+ // update team cache\r
+ for (TeamModel team : model.teams) {\r
+ TeamModel t = getTeamModel(team.name);\r
+ if (t == null) {\r
+ // new team\r
+ t = team;\r
+ }\r
+ t.removeUser(username);\r
+ t.addUser(model.username);\r
+ updateTeamCache(allUsers, t.name, t);\r
+ }\r
+\r
+ // check for implicit team removal\r
+ if (oldUser != null) {\r
+ for (TeamModel team : oldUser.teams) {\r
+ if (!model.isTeamMember(team.name)) {\r
+ team.removeUser(username);\r
+ updateTeamCache(allUsers, team.name, team);\r
+ }\r
+ }\r
+ }\r
+ }\r
+\r
write(allUsers);\r
return true;\r
} catch (Throwable t) {\r
try {\r
// Read realm file\r
Properties allUsers = read();\r
+ UserModel user = getUserModel(username);\r
allUsers.remove(username);\r
+ for (TeamModel team : user.teams) {\r
+ TeamModel t = getTeamModel(team.name);\r
+ if (t == null) {\r
+ // new team\r
+ t = team;\r
+ }\r
+ t.removeUser(username);\r
+ updateTeamCache(allUsers, t.name, t);\r
+ }\r
write(allUsers);\r
return true;\r
} catch (Throwable t) {\r
@Override\r
public List<String> getAllUsernames() {\r
Properties allUsers = read();\r
- List<String> list = new ArrayList<String>(allUsers.stringPropertyNames());\r
+ List<String> list = new ArrayList<String>();\r
+ for (String user : allUsers.stringPropertyNames()) {\r
+ if (user.charAt(0) == '@') {\r
+ // skip team user definitions\r
+ continue;\r
+ }\r
+ list.add(user);\r
+ }\r
return list;\r
}\r
\r
try {\r
Properties allUsers = read();\r
for (String username : allUsers.stringPropertyNames()) {\r
+ if (username.charAt(0) == '@') {\r
+ continue;\r
+ }\r
String value = allUsers.getProperty(username);\r
String[] values = value.split(",");\r
// skip first value (password)\r
}\r
\r
/**\r
- * Sets the list of all uses who are allowed to bypass the access\r
+ * Sets the list of all users who are allowed to bypass the access\r
* restriction placed on the specified repository.\r
* \r
* @param role\r
sb.append(',');\r
sb.append(newRole);\r
sb.append(',');\r
- \r
+\r
// skip first value (password)\r
for (int i = 1; i < values.length; i++) {\r
String value = values[i];\r
FileWriter writer = new FileWriter(realmFileCopy);\r
properties\r
.store(writer,\r
- "# Gitblit realm file format: username=password,\\#permission,repository1,repository2...");\r
+ " Gitblit realm file format:\n username=password,\\#permission,repository1,repository2...\n @teamname=!username1,!username2,!username3,repository1,repository2...");\r
writer.close();\r
// If the write is successful, delete the current file and rename\r
// the temporary copy to the original filename.\r
if (lastRead != lastModified()) {\r
// reload hash cache\r
cookies.clear();\r
+ teams.clear();\r
+\r
for (String username : allUsers.stringPropertyNames()) {\r
String value = allUsers.getProperty(username);\r
String[] roles = value.split(",");\r
- String password = roles[0];\r
- cookies.put(StringUtils.getSHA1(username + password), username);\r
+ if (username.charAt(0) == '@') {\r
+ // team definition\r
+ TeamModel team = new TeamModel(username.substring(1));\r
+ List<String> repositories = new ArrayList<String>();\r
+ List<String> users = new ArrayList<String>();\r
+ for (String role : roles) {\r
+ if (role.charAt(0) == '!') {\r
+ users.add(role.substring(1));\r
+ } else {\r
+ repositories.add(role);\r
+ }\r
+ }\r
+ team.addRepositories(repositories);\r
+ team.addUsers(users);\r
+ teams.put(team.name.toLowerCase(), team);\r
+ } else {\r
+ // user definition\r
+ String password = roles[0];\r
+ cookies.put(StringUtils.getSHA1(username + password), username);\r
+ }\r
}\r
}\r
return allUsers;\r
public String toString() {\r
return getClass().getSimpleName() + "(" + propertiesFile.getAbsolutePath() + ")";\r
}\r
+\r
+ /**\r
+ * Returns the list of all teams available to the login service.\r
+ * \r
+ * @return list of all teams\r
+ * @since 0.8.0\r
+ */\r
+ @Override\r
+ public List<String> getAllTeamNames() {\r
+ List<String> list = new ArrayList<String>(teams.keySet());\r
+ return list;\r
+ }\r
+\r
+ /**\r
+ * Returns the list of all teams who are allowed to bypass the access\r
+ * restriction placed on the specified repository.\r
+ * \r
+ * @param role\r
+ * the repository name\r
+ * @return list of all teamnames that can bypass the access restriction\r
+ */\r
+ @Override\r
+ public List<String> getTeamnamesForRepositoryRole(String role) {\r
+ List<String> list = new ArrayList<String>();\r
+ try {\r
+ Properties allUsers = read();\r
+ for (String team : allUsers.stringPropertyNames()) {\r
+ if (team.charAt(0) != '@') {\r
+ // skip users\r
+ continue;\r
+ }\r
+ String value = allUsers.getProperty(team);\r
+ String[] values = value.split(",");\r
+ for (int i = 0; i < values.length; i++) {\r
+ String r = values[i];\r
+ if (r.equalsIgnoreCase(role)) {\r
+ // strip leading @\r
+ list.add(team.substring(1));\r
+ break;\r
+ }\r
+ }\r
+ }\r
+ } catch (Throwable t) {\r
+ logger.error(MessageFormat.format("Failed to get teamnames for role {0}!", role), t);\r
+ }\r
+ return list;\r
+ }\r
+\r
+ /**\r
+ * Sets the list of all teams who are allowed to bypass the access\r
+ * restriction placed on the specified repository.\r
+ * \r
+ * @param role\r
+ * the repository name\r
+ * @param teamnames\r
+ * @return true if successful\r
+ */\r
+ @Override\r
+ public boolean setTeamnamesForRepositoryRole(String role, List<String> teamnames) {\r
+ try {\r
+ Set<String> specifiedTeams = new HashSet<String>(teamnames);\r
+ Set<String> needsAddRole = new HashSet<String>(specifiedTeams);\r
+ Set<String> needsRemoveRole = new HashSet<String>();\r
+\r
+ // identify teams which require add and remove role\r
+ Properties allUsers = read();\r
+ for (String team : allUsers.stringPropertyNames()) {\r
+ if (team.charAt(0) != '@') {\r
+ // skip users\r
+ continue;\r
+ }\r
+ String name = team.substring(1);\r
+ String value = allUsers.getProperty(team);\r
+ String[] values = value.split(",");\r
+ for (int i = 0; i < values.length; i++) {\r
+ String r = values[i];\r
+ if (r.equalsIgnoreCase(role)) {\r
+ // team has role, check against revised team list\r
+ if (specifiedTeams.contains(name)) {\r
+ needsAddRole.remove(name);\r
+ } else {\r
+ // remove role from team\r
+ needsRemoveRole.add(name);\r
+ }\r
+ break;\r
+ }\r
+ }\r
+ }\r
+\r
+ // add roles to teams\r
+ for (String name : needsAddRole) {\r
+ String team = "@" + name;\r
+ String teamValues = allUsers.getProperty(team);\r
+ teamValues += "," + role;\r
+ allUsers.put(team, teamValues);\r
+ }\r
+\r
+ // remove role from team\r
+ for (String name : needsRemoveRole) {\r
+ String team = "@" + name;\r
+ String[] values = allUsers.getProperty(team).split(","); \r
+ StringBuilder sb = new StringBuilder();\r
+ for (int i = 0; i < values.length; i++) {\r
+ String value = values[i];\r
+ if (!value.equalsIgnoreCase(role)) {\r
+ sb.append(value);\r
+ sb.append(',');\r
+ }\r
+ }\r
+ sb.setLength(sb.length() - 1);\r
+\r
+ // update properties\r
+ allUsers.put(team, sb.toString());\r
+ }\r
+\r
+ // persist changes\r
+ write(allUsers);\r
+ return true;\r
+ } catch (Throwable t) {\r
+ logger.error(MessageFormat.format("Failed to set teamnames for role {0}!", role), t);\r
+ }\r
+ return false;\r
+ }\r
+\r
+ /**\r
+ * Retrieve the team object for the specified team name.\r
+ * \r
+ * @param teamname\r
+ * @return a team object or null\r
+ * @since 0.8.0\r
+ */\r
+ @Override\r
+ public TeamModel getTeamModel(String teamname) {\r
+ read();\r
+ TeamModel team = teams.get(teamname.toLowerCase());\r
+ if (team != null) {\r
+ // clone the model, otherwise all changes to this object are\r
+ // live and unpersisted\r
+ team = DeepCopier.copy(team);\r
+ }\r
+ return team;\r
+ }\r
+\r
+ /**\r
+ * Updates/writes a complete team object.\r
+ * \r
+ * @param model\r
+ * @return true if update is successful\r
+ * @since 0.8.0\r
+ */\r
+ @Override\r
+ public boolean updateTeamModel(TeamModel model) {\r
+ return updateTeamModel(model.name, model);\r
+ }\r
+\r
+ /**\r
+ * Updates/writes and replaces a complete team object keyed by teamname.\r
+ * This method allows for renaming a team.\r
+ * \r
+ * @param teamname\r
+ * the old teamname\r
+ * @param model\r
+ * the team object to use for teamname\r
+ * @return true if update is successful\r
+ * @since 0.8.0\r
+ */\r
+ @Override\r
+ public boolean updateTeamModel(String teamname, TeamModel model) {\r
+ try {\r
+ Properties allUsers = read();\r
+ updateTeamCache(allUsers, teamname, model);\r
+ write(allUsers);\r
+ return true;\r
+ } catch (Throwable t) {\r
+ logger.error(MessageFormat.format("Failed to update team model {0}!", model.name), t);\r
+ }\r
+ return false;\r
+ }\r
+\r
+ private void updateTeamCache(Properties allUsers, String teamname, TeamModel model) {\r
+ StringBuilder sb = new StringBuilder();\r
+ for (String repository : model.repositories) {\r
+ sb.append(repository);\r
+ sb.append(',');\r
+ }\r
+ for (String user : model.users) {\r
+ sb.append('!');\r
+ sb.append(user);\r
+ sb.append(',');\r
+ }\r
+ // trim trailing comma\r
+ sb.setLength(sb.length() - 1);\r
+ allUsers.remove("@" + teamname);\r
+ allUsers.put("@" + model.name, sb.toString());\r
+\r
+ // update team cache\r
+ teams.remove(teamname.toLowerCase());\r
+ teams.put(model.name.toLowerCase(), model);\r
+ }\r
+\r
+ /**\r
+ * Deletes the team object from the user service.\r
+ * \r
+ * @param model\r
+ * @return true if successful\r
+ * @since 0.8.0\r
+ */\r
+ @Override\r
+ public boolean deleteTeamModel(TeamModel model) {\r
+ return deleteTeam(model.name);\r
+ }\r
+\r
+ /**\r
+ * Delete the team object with the specified teamname\r
+ * \r
+ * @param teamname\r
+ * @return true if successful\r
+ * @since 0.8.0\r
+ */\r
+ @Override\r
+ public boolean deleteTeam(String teamname) {\r
+ Properties allUsers = read();\r
+ teams.remove(teamname.toLowerCase());\r
+ allUsers.remove("@" + teamname);\r
+ try {\r
+ write(allUsers);\r
+ return true;\r
+ } catch (Throwable t) {\r
+ logger.error(MessageFormat.format("Failed to delete team {0}!", teamname), t);\r
+ }\r
+ return false;\r
+ }\r
}\r
import com.gitblit.models.ServerSettings;\r
import com.gitblit.models.ServerStatus;\r
import com.gitblit.models.SettingModel;\r
+import com.gitblit.models.TeamModel;\r
import com.gitblit.models.UserModel;\r
import com.gitblit.utils.ByteFormat;\r
import com.gitblit.utils.FederationUtils;\r
}\r
}\r
\r
+ /**\r
+ * Returns the list of available teams that a user or repository may be\r
+ * assigned to.\r
+ * \r
+ * @return the list of teams\r
+ */\r
+ public List<String> getAllTeamnames() {\r
+ List<String> teams = new ArrayList<String>(userService.getAllTeamNames());\r
+ Collections.sort(teams);\r
+ return teams;\r
+ }\r
+\r
+ /**\r
+ * Returns the TeamModel object for the specified name.\r
+ * \r
+ * @param teamname\r
+ * @return a TeamModel object or null\r
+ */\r
+ public TeamModel getTeamModel(String teamname) {\r
+ return userService.getTeamModel(teamname);\r
+ }\r
+\r
+ /**\r
+ * Returns the list of all teams who are allowed to bypass the access\r
+ * restriction placed on the specified repository.\r
+ * \r
+ * @see IUserService.getTeamnamesForRepositoryRole(String)\r
+ * @param repository\r
+ * @return list of all teamnames that can bypass the access restriction\r
+ */\r
+ public List<String> getRepositoryTeams(RepositoryModel repository) {\r
+ return userService.getTeamnamesForRepositoryRole(repository.name);\r
+ }\r
+\r
+ /**\r
+ * Sets the list of all uses who are allowed to bypass the access\r
+ * restriction placed on the specified repository.\r
+ * \r
+ * @see IUserService.setTeamnamesForRepositoryRole(String, List<String>)\r
+ * @param repository\r
+ * @param teamnames\r
+ * @return true if successful\r
+ */\r
+ public boolean setRepositoryTeams(RepositoryModel repository, List<String> repositoryTeams) {\r
+ return userService.setTeamnamesForRepositoryRole(repository.name, repositoryTeams);\r
+ }\r
+ /**\r
+ * Updates the TeamModel object for the specified name.\r
+ * \r
+ * @param teamname\r
+ * @param team\r
+ * @param isCreate\r
+ */\r
+ public void updateTeamModel(String teamname, TeamModel team, boolean isCreate) throws GitBlitException {\r
+ if (!teamname.equalsIgnoreCase(team.name)) {\r
+ if (userService.getTeamModel(team.name) != null) {\r
+ throw new GitBlitException(MessageFormat.format(\r
+ "Failed to rename ''{0}'' because ''{1}'' already exists.", teamname,\r
+ team.name));\r
+ }\r
+ }\r
+ if (!userService.updateTeamModel(teamname, team)) {\r
+ throw new GitBlitException(isCreate ? "Failed to add team!" : "Failed to update team!");\r
+ }\r
+ }\r
+ \r
+ /**\r
+ * Delete the team object with the specified teamname\r
+ * \r
+ * @see IUserService.deleteTeam(String)\r
+ * @param teamname\r
+ * @return true if successful\r
+ */\r
+ public boolean deleteTeam(String teamname) {\r
+ return userService.deleteTeam(teamname);\r
+ }\r
+\r
/**\r
* Clears all the cached data for the specified repository.\r
* \r
case PULL_REPOSITORIES:\r
return token.equals(all) || token.equals(unr) || token.equals(jur);\r
case PULL_USERS:\r
+ case PULL_TEAMS:\r
return token.equals(all) || token.equals(unr);\r
case PULL_SETTINGS:\r
return token.equals(all);\r
configService.updateUserModel(userModel);\r
}\r
}\r
- \r
+\r
// issue suggestion about switching to users.conf\r
logger.warn("Please consider using \"users.conf\" instead of the deprecated \"users.properties\" file");\r
} else if (realmFile.getName().toLowerCase().endsWith(".conf")) {\r
\r
import java.util.List;\r
\r
+import com.gitblit.models.TeamModel;\r
import com.gitblit.models.UserModel;\r
\r
/**\r
*/\r
List<String> getAllUsernames();\r
\r
+ /**\r
+ * Returns the list of all teams available to the login service.\r
+ * \r
+ * @return list of all teams\r
+ * @since 0.8.0\r
+ */ \r
+ List<String> getAllTeamNames();\r
+ \r
+ /**\r
+ * Returns the list of all users who are allowed to bypass the access\r
+ * restriction placed on the specified repository.\r
+ * \r
+ * @param role\r
+ * the repository name\r
+ * @return list of all usernames that can bypass the access restriction\r
+ */ \r
+ List<String> getTeamnamesForRepositoryRole(String role);\r
+\r
+ /**\r
+ * Sets the list of all teams who are allowed to bypass the access\r
+ * restriction placed on the specified repository.\r
+ * \r
+ * @param role\r
+ * the repository name\r
+ * @param teamnames\r
+ * @return true if successful\r
+ */ \r
+ boolean setTeamnamesForRepositoryRole(String role, List<String> teamnames);\r
+ \r
+ /**\r
+ * Retrieve the team object for the specified team name.\r
+ * \r
+ * @param teamname\r
+ * @return a team object or null\r
+ * @since 0.8.0\r
+ */ \r
+ TeamModel getTeamModel(String teamname);\r
+\r
+ /**\r
+ * Updates/writes a complete team object.\r
+ * \r
+ * @param model\r
+ * @return true if update is successful\r
+ * @since 0.8.0\r
+ */ \r
+ boolean updateTeamModel(TeamModel model);\r
+\r
+ /**\r
+ * Updates/writes and replaces a complete team object keyed by teamname.\r
+ * This method allows for renaming a team.\r
+ * \r
+ * @param teamname\r
+ * the old teamname\r
+ * @param model\r
+ * the team object to use for teamname\r
+ * @return true if update is successful\r
+ * @since 0.8.0\r
+ */\r
+ boolean updateTeamModel(String teamname, TeamModel model);\r
+\r
+ /**\r
+ * Deletes the team object from the user service.\r
+ * \r
+ * @param model\r
+ * @return true if successful\r
+ * @since 0.8.0\r
+ */\r
+ boolean deleteTeamModel(TeamModel model);\r
+\r
+ /**\r
+ * Delete the team object with the specified teamname\r
+ * \r
+ * @param teamname\r
+ * @return true if successful\r
+ * @since 0.8.0\r
+ */ \r
+ boolean deleteTeam(String teamname);\r
+\r
/**\r
* Returns the list of all users who are allowed to bypass the access\r
* restriction placed on the specified repository.\r
--- /dev/null
+/*\r
+ * Copyright 2011 gitblit.com.\r
+ *\r
+ * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ *\r
+ * http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+package com.gitblit.models;\r
+\r
+import java.io.Serializable;\r
+import java.util.Collection;\r
+import java.util.HashSet;\r
+import java.util.Set;\r
+\r
+/**\r
+ * TeamModel is a serializable model class that represents a group of users and\r
+ * a list of accessible repositories.\r
+ * \r
+ * @author James Moger\r
+ * \r
+ */\r
+public class TeamModel implements Serializable, Comparable<TeamModel> {\r
+\r
+ private static final long serialVersionUID = 1L;\r
+\r
+ // field names are reflectively mapped in EditTeam page\r
+ public String name;\r
+ public final Set<String> users = new HashSet<String>();\r
+ public final Set<String> repositories = new HashSet<String>();\r
+\r
+ public TeamModel(String name) {\r
+ this.name = name;\r
+ }\r
+\r
+ public boolean hasRepository(String name) {\r
+ return repositories.contains(name.toLowerCase());\r
+ }\r
+\r
+ public void addRepository(String name) {\r
+ repositories.add(name.toLowerCase());\r
+ }\r
+ \r
+ public void addRepositories(Collection<String> names) {\r
+ for (String name:names) {\r
+ repositories.add(name.toLowerCase());\r
+ }\r
+ } \r
+\r
+ public void removeRepository(String name) {\r
+ repositories.remove(name.toLowerCase());\r
+ }\r
+ \r
+ public boolean hasUser(String name) {\r
+ return users.contains(name.toLowerCase());\r
+ }\r
+\r
+ public void addUser(String name) {\r
+ users.add(name.toLowerCase());\r
+ }\r
+\r
+ public void addUsers(Collection<String> names) {\r
+ for (String name:names) {\r
+ users.add(name.toLowerCase());\r
+ }\r
+ }\r
+\r
+ public void removeUser(String name) {\r
+ users.remove(name.toLowerCase());\r
+ }\r
+\r
+ @Override\r
+ public String toString() {\r
+ return name;\r
+ }\r
+\r
+ @Override\r
+ public int compareTo(TeamModel o) {\r
+ return name.compareTo(o.name);\r
+ }\r
+}\r
public boolean canAdmin;\r
public boolean excludeFromFederation;\r
public final Set<String> repositories = new HashSet<String>();\r
+ public final Set<TeamModel> teams = new HashSet<TeamModel>();\r
\r
public UserModel(String username) {\r
this.username = username;\r
*/\r
@Deprecated\r
public boolean canAccessRepository(String repositoryName) {\r
- return canAdmin || repositories.contains(repositoryName.toLowerCase());\r
+ return canAdmin || repositories.contains(repositoryName.toLowerCase())\r
+ || hasTeamAccess(repositoryName);\r
}\r
\r
public boolean canAccessRepository(RepositoryModel repository) {\r
boolean isOwner = !StringUtils.isEmpty(repository.owner)\r
&& repository.owner.equals(username);\r
- return canAdmin || isOwner || repositories.contains(repository.name.toLowerCase());\r
+ return canAdmin || isOwner || repositories.contains(repository.name.toLowerCase())\r
+ || hasTeamAccess(repository.name);\r
+ }\r
+\r
+ public boolean hasTeamAccess(String repositoryName) {\r
+ for (TeamModel team : teams) {\r
+ if (team.hasRepository(repositoryName)) {\r
+ return true;\r
+ }\r
+ }\r
+ return false;\r
}\r
\r
public boolean hasRepository(String name) {\r
repositories.remove(name.toLowerCase());\r
}\r
\r
+ public boolean isTeamMember(String teamname) {\r
+ for (TeamModel team : teams) {\r
+ if (team.name.equalsIgnoreCase(teamname)) {\r
+ return true;\r
+ }\r
+ }\r
+ return false;\r
+ }\r
+\r
@Override\r
public String getName() {\r
return username;\r
--- /dev/null
+/*\r
+ * Copyright 2011 gitblit.com.\r
+ *\r
+ * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ *\r
+ * http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+package com.gitblit.utils;\r
+\r
+import java.io.ByteArrayInputStream;\r
+import java.io.ByteArrayOutputStream;\r
+import java.io.IOException;\r
+import java.io.InputStream;\r
+import java.io.ObjectInputStream;\r
+import java.io.ObjectOutputStream;\r
+import java.io.PipedInputStream;\r
+import java.io.PipedOutputStream;\r
+\r
+public class DeepCopier {\r
+\r
+ /**\r
+ * Produce a deep copy of the given object. Serializes the entire object to\r
+ * a byte array in memory. Recommended for relatively small objects.\r
+ */\r
+ @SuppressWarnings("unchecked")\r
+ public static <T> T copy(T original) {\r
+ T o = null;\r
+ try {\r
+ ByteArrayOutputStream byteOut = new ByteArrayOutputStream();\r
+ ObjectOutputStream oos = new ObjectOutputStream(byteOut);\r
+ oos.writeObject(original);\r
+ ByteArrayInputStream byteIn = new ByteArrayInputStream(byteOut.toByteArray());\r
+ ObjectInputStream ois = new ObjectInputStream(byteIn);\r
+ try {\r
+ o = (T) ois.readObject();\r
+ } catch (ClassNotFoundException cex) {\r
+ // actually can not happen in this instance\r
+ }\r
+ } catch (IOException iox) {\r
+ // doesn't seem likely to happen as these streams are in memory\r
+ throw new RuntimeException(iox);\r
+ }\r
+ return o;\r
+ }\r
+\r
+ /**\r
+ * This conserves heap memory!!!!! Produce a deep copy of the given object.\r
+ * Serializes the object through a pipe between two threads. Recommended for\r
+ * very large objects. The current thread is used for serializing the\r
+ * original object in order to respect any synchronization the caller may\r
+ * have around it, and a new thread is used for deserializing the copy.\r
+ * \r
+ */\r
+ public static <T> T copyParallel(T original) {\r
+ try {\r
+ PipedOutputStream outputStream = new PipedOutputStream();\r
+ PipedInputStream inputStream = new PipedInputStream(outputStream);\r
+ ObjectOutputStream ois = new ObjectOutputStream(outputStream);\r
+ Receiver<T> receiver = new Receiver<T>(inputStream);\r
+ try {\r
+ ois.writeObject(original);\r
+ } finally {\r
+ ois.close();\r
+ }\r
+ return receiver.getResult();\r
+ } catch (IOException iox) {\r
+ // doesn't seem likely to happen as these streams are in memory\r
+ throw new RuntimeException(iox);\r
+ }\r
+ }\r
+\r
+ private static class Receiver<T> extends Thread {\r
+\r
+ private final InputStream inputStream;\r
+ private volatile T result;\r
+ private volatile Throwable throwable;\r
+\r
+ public Receiver(InputStream inputStream) {\r
+ this.inputStream = inputStream;\r
+ start();\r
+ }\r
+\r
+ @SuppressWarnings("unchecked")\r
+ public void run() {\r
+\r
+ try {\r
+ ObjectInputStream ois = new ObjectInputStream(inputStream);\r
+ try {\r
+ result = (T) ois.readObject();\r
+ try {\r
+ // Some serializers may write more than they actually\r
+ // need to deserialize the object, but if we don't\r
+ // read it all the PipedOutputStream will choke.\r
+ while (inputStream.read() != -1) {\r
+ }\r
+ } catch (IOException e) {\r
+ // The object has been successfully deserialized, so\r
+ // ignore problems at this point (for example, the\r
+ // serializer may have explicitly closed the inputStream\r
+ // itself, causing this read to fail).\r
+ }\r
+ } finally {\r
+ ois.close();\r
+ }\r
+ } catch (Throwable t) {\r
+ throwable = t;\r
+ }\r
+ }\r
+\r
+ public T getResult() throws IOException {\r
+ try {\r
+ join();\r
+ } catch (InterruptedException e) {\r
+ throw new RuntimeException("Unexpected InterruptedException", e);\r
+ }\r
+ // join() guarantees that all shared memory is synchronized between\r
+ // the two threads\r
+ if (throwable != null) {\r
+ if (throwable instanceof ClassNotFoundException) {\r
+ // actually can not happen in this instance\r
+ }\r
+ throw new RuntimeException(throwable);\r
+ }\r
+ return result;\r
+ }\r
+ }\r
+}
\ No newline at end of file
gb.dailyActivity = daily activity\r
gb.activeRepositories = active repositories\r
gb.activeAuthors = active authors\r
-gb.commits = commits
\ No newline at end of file
+gb.commits = commits\r
+gb.teams = teams\r
+gb.teamName = team name\r
+gb.teamMembers = team members\r
+gb.teamMemberships = team memberships\r
+gb.newTeam = new team\r
+gb.permittedTeams = permitted teams
\ No newline at end of file
return new PageParameters("user=" + username);\r
}\r
\r
+ public static PageParameters newTeamnameParameter(String teamname) {\r
+ return new PageParameters("team=" + teamname);\r
+ }\r
+\r
public static PageParameters newRepositoryParameter(String repositoryName) {\r
return new PageParameters("r=" + repositoryName);\r
}\r
return params.getString("user", "");\r
}\r
\r
+ public static String getTeamname(PageParameters params) {\r
+ return params.getString("team", "");\r
+ }\r
+\r
public static String getToken(PageParameters params) {\r
return params.getString("t", "");\r
}\r
<wicket:extend>\r
<body onload="document.getElementById('name').focus();">\r
<!-- Repository Table -->\r
- <form wicket:id="editForm">\r
+ <form style="padding-top:5px;" wicket:id="editForm">\r
<table class="plain">\r
<tbody>\r
<tr><th><wicket:message key="gb.name"></wicket:message></th><td class="edit"><input class="span6" type="text" wicket:id="name" id="name" size="40" tabindex="1" /> <i><wicket:message key="gb.nameDescription"></wicket:message></i></td></tr>\r
<tr><td colspan="2"><hr></hr></td></tr>\r
<tr><th><wicket:message key="gb.accessRestriction"></wicket:message></th><td class="edit"><select class="span6" wicket:id="accessRestriction" tabindex="12" /></td></tr> \r
<tr><th style="vertical-align: top;"><wicket:message key="gb.permittedUsers"></wicket:message></th><td style="padding:2px;"><span wicket:id="users"></span></td></tr>\r
+ <tr><th style="vertical-align: top;"><wicket:message key="gb.permittedTeams"></wicket:message></th><td style="padding:2px;"><span wicket:id="teams"></span></td></tr>\r
<tr><td colspan="2"><hr></hr></td></tr> \r
<tr><th><wicket:message key="gb.federationStrategy"></wicket:message></th><td class="edit"><select class="span6" wicket:id="federationStrategy" tabindex="13" /></td></tr>\r
<tr><th style="vertical-align: top;"><wicket:message key="gb.federationSets"></wicket:message></th><td style="padding:2px;"><span wicket:id="federationSets"></span></td></tr>\r
\r
List<String> federationSets = new ArrayList<String>();\r
List<String> repositoryUsers = new ArrayList<String>();\r
+ List<String> repositoryTeams = new ArrayList<String>();\r
if (isCreate) {\r
super.setupPage(getString("gb.newRepository"), "");\r
} else {\r
super.setupPage(getString("gb.edit"), repositoryModel.name);\r
if (repositoryModel.accessRestriction.exceeds(AccessRestrictionType.NONE)) {\r
repositoryUsers.addAll(GitBlit.self().getRepositoryUsers(repositoryModel));\r
+ repositoryTeams.addAll(GitBlit.self().getRepositoryTeams(repositoryModel));\r
Collections.sort(repositoryUsers);\r
}\r
federationSets.addAll(repositoryModel.federationSets);\r
repositoryUsers), new CollectionModel<String>(GitBlit.self().getAllUsernames()),\r
new ChoiceRenderer<String>("", ""), 10, false);\r
\r
+ // teams palette\r
+ final Palette<String> teamsPalette = new Palette<String>("teams", new ListModel<String>(\r
+ repositoryTeams), new CollectionModel<String>(GitBlit.self().getAllTeamnames()),\r
+ new ChoiceRenderer<String>("", ""), 10, false);\r
+\r
// federation sets palette\r
List<String> sets = GitBlit.getStrings(Keys.federation.sets);\r
final Palette<String> federationSetsPalette = new Palette<String>("federationSets",\r
// save the repository\r
GitBlit.self().updateRepositoryModel(oldName, repositoryModel, isCreate);\r
\r
- // save the repository access list\r
+ // repository access\r
if (repositoryModel.accessRestriction.exceeds(AccessRestrictionType.NONE)) {\r
+ // save the user access list\r
Iterator<String> users = usersPalette.getSelectedChoices();\r
List<String> repositoryUsers = new ArrayList<String>();\r
while (users.hasNext()) {\r
repositoryUsers.add(repositoryModel.owner);\r
}\r
GitBlit.self().setRepositoryUsers(repositoryModel, repositoryUsers);\r
+ \r
+ // save the team access list\r
+ Iterator<String> teams = teamsPalette.getSelectedChoices();\r
+ List<String> repositoryTeams = new ArrayList<String>();\r
+ while (teams.hasNext()) {\r
+ repositoryTeams.add(teams.next());\r
+ }\r
+ GitBlit.self().setRepositoryTeams(repositoryModel, repositoryTeams);\r
}\r
} catch (GitBlitException e) {\r
error(e.getMessage());\r
form.add(new CheckBox("skipSizeCalculation"));\r
form.add(new CheckBox("skipSummaryMetrics"));\r
form.add(usersPalette);\r
+ form.add(teamsPalette);\r
form.add(federationSetsPalette);\r
\r
form.add(new Button("save"));\r
--- /dev/null
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\r
+<html xmlns="http://www.w3.org/1999/xhtml" \r
+ xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd" \r
+ xml:lang="en" \r
+ lang="en"> \r
+\r
+<wicket:extend>\r
+<body onload="document.getElementById('name').focus();">\r
+ <!-- User Table -->\r
+ <form style="padding-top:5px;" wicket:id="editForm">\r
+ <table class="plain">\r
+ <tbody>\r
+ <tr><th><wicket:message key="gb.teamName"></wicket:message></th><td class="edit"><input type="text" wicket:id="name" id="name" size="30" tabindex="1" /></td></tr>\r
+ <tr><td colspan="2"><hr></hr></td></tr>\r
+ <tr><th style="vertical-align: top;"><wicket:message key="gb.teamMembers"></wicket:message></th><td style="padding:2px;"><span wicket:id="users"></span></td></tr>\r
+ <tr><td colspan="2"><hr></hr></td></tr>\r
+ <tr><th style="vertical-align: top;"><wicket:message key="gb.restrictedRepositories"></wicket:message></th><td style="padding:2px;"><span wicket:id="repositories"></span></td></tr>\r
+ <tr><th></th><td class="editButton"><input class="btn" type="submit" value="Cancel" wicket:message="value:gb.cancel" wicket:id="cancel" tabindex="3" /> <input class="btn primary" type="submit" value="Save" wicket:message="value:gb.save" wicket:id="save" tabindex="4" /></td></tr>\r
+ </tbody>\r
+ </table>\r
+ </form> \r
+</body>\r
+</wicket:extend>\r
+</html>
\ No newline at end of file
--- /dev/null
+/*\r
+ * Copyright 2011 gitblit.com.\r
+ *\r
+ * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ *\r
+ * http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+package com.gitblit.wicket.pages;\r
+\r
+import java.text.MessageFormat;\r
+import java.util.ArrayList;\r
+import java.util.Collections;\r
+import java.util.Iterator;\r
+import java.util.List;\r
+\r
+import org.apache.wicket.PageParameters;\r
+import org.apache.wicket.extensions.markup.html.form.palette.Palette;\r
+import org.apache.wicket.markup.html.form.Button;\r
+import org.apache.wicket.markup.html.form.ChoiceRenderer;\r
+import org.apache.wicket.markup.html.form.Form;\r
+import org.apache.wicket.markup.html.form.TextField;\r
+import org.apache.wicket.model.CompoundPropertyModel;\r
+import org.apache.wicket.model.util.CollectionModel;\r
+import org.apache.wicket.model.util.ListModel;\r
+\r
+import com.gitblit.Constants.AccessRestrictionType;\r
+import com.gitblit.GitBlit;\r
+import com.gitblit.GitBlitException;\r
+import com.gitblit.models.RepositoryModel;\r
+import com.gitblit.models.TeamModel;\r
+import com.gitblit.utils.StringUtils;\r
+import com.gitblit.wicket.RequiresAdminRole;\r
+import com.gitblit.wicket.WicketUtils;\r
+\r
+@RequiresAdminRole\r
+public class EditTeamPage extends RootSubPage {\r
+\r
+ private final boolean isCreate;\r
+\r
+ public EditTeamPage() {\r
+ // create constructor\r
+ super();\r
+ isCreate = true;\r
+ setupPage(new TeamModel(""));\r
+ }\r
+\r
+ public EditTeamPage(PageParameters params) {\r
+ // edit constructor\r
+ super(params);\r
+ isCreate = false;\r
+ String name = WicketUtils.getTeamname(params);\r
+ TeamModel model = GitBlit.self().getTeamModel(name);\r
+ setupPage(model);\r
+ }\r
+\r
+ protected void setupPage(final TeamModel teamModel) {\r
+ if (isCreate) {\r
+ super.setupPage(getString("gb.newTeam"), "");\r
+ } else {\r
+ super.setupPage(getString("gb.edit"), teamModel.name);\r
+ }\r
+ \r
+ CompoundPropertyModel<TeamModel> model = new CompoundPropertyModel<TeamModel>(teamModel);\r
+\r
+ List<String> repos = new ArrayList<String>();\r
+ for (String repo : GitBlit.self().getRepositoryList()) {\r
+ RepositoryModel repositoryModel = GitBlit.self().getRepositoryModel(repo);\r
+ if (repositoryModel.accessRestriction.exceeds(AccessRestrictionType.NONE)) {\r
+ repos.add(repo);\r
+ }\r
+ }\r
+ StringUtils.sortRepositorynames(repos);\r
+ \r
+ List<String> teamUsers = new ArrayList<String>(teamModel.users);\r
+ Collections.sort(teamUsers);\r
+ \r
+ final String oldName = teamModel.name;\r
+ final Palette<String> repositories = new Palette<String>("repositories",\r
+ new ListModel<String>(new ArrayList<String>(teamModel.repositories)),\r
+ new CollectionModel<String>(repos), new ChoiceRenderer<String>("", ""), 10, false);\r
+ final Palette<String> users = new Palette<String>("users", new ListModel<String>(\r
+ new ArrayList<String>(teamUsers)), new CollectionModel<String>(GitBlit.self()\r
+ .getAllUsernames()), new ChoiceRenderer<String>("", ""), 10, false);\r
+ Form<TeamModel> form = new Form<TeamModel>("editForm", model) {\r
+\r
+ private static final long serialVersionUID = 1L;\r
+\r
+ /*\r
+ * (non-Javadoc)\r
+ * \r
+ * @see org.apache.wicket.markup.html.form.Form#onSubmit()\r
+ */\r
+ @Override\r
+ protected void onSubmit() {\r
+ String teamname = teamModel.name;\r
+ if (StringUtils.isEmpty(teamname)) {\r
+ error("Please enter a teamname!");\r
+ return;\r
+ }\r
+ if (isCreate) {\r
+ TeamModel model = GitBlit.self().getTeamModel(teamname);\r
+ if (model != null) {\r
+ error(MessageFormat.format("Team name ''{0}'' is unavailable.", teamname));\r
+ return;\r
+ }\r
+ }\r
+ Iterator<String> selectedRepositories = repositories.getSelectedChoices();\r
+ List<String> repos = new ArrayList<String>();\r
+ while (selectedRepositories.hasNext()) {\r
+ repos.add(selectedRepositories.next().toLowerCase());\r
+ }\r
+ teamModel.repositories.clear();\r
+ teamModel.repositories.addAll(repos);\r
+\r
+ Iterator<String> selectedUsers = users.getSelectedChoices();\r
+ List<String> members = new ArrayList<String>();\r
+ while (selectedUsers.hasNext()) {\r
+ members.add(selectedUsers.next().toLowerCase());\r
+ }\r
+ teamModel.users.clear();\r
+ teamModel.users.addAll(members);\r
+\r
+ try {\r
+ GitBlit.self().updateTeamModel(oldName, teamModel, isCreate);\r
+ } catch (GitBlitException e) {\r
+ error(e.getMessage());\r
+ return;\r
+ }\r
+ setRedirect(false);\r
+ if (isCreate) {\r
+ // create another team\r
+ info(MessageFormat.format("New team ''{0}'' successfully created.",\r
+ teamModel.name));\r
+ setResponsePage(EditTeamPage.class);\r
+ } else {\r
+ // back to users page\r
+ setResponsePage(UsersPage.class);\r
+ }\r
+ }\r
+ };\r
+\r
+ // field names reflective match TeamModel fields\r
+ form.add(new TextField<String>("name"));\r
+ form.add(repositories);\r
+ form.add(users);\r
+\r
+ form.add(new Button("save"));\r
+ Button cancel = new Button("cancel") {\r
+ private static final long serialVersionUID = 1L;\r
+\r
+ @Override\r
+ public void onSubmit() {\r
+ setResponsePage(UsersPage.class);\r
+ }\r
+ };\r
+ cancel.setDefaultFormProcessing(false);\r
+ form.add(cancel);\r
+\r
+ add(form);\r
+ }\r
+}\r
<wicket:extend>\r
<body onload="document.getElementById('username').focus();">\r
<!-- User Table -->\r
- <form wicket:id="editForm">\r
+ <form style="padding-top:5px;" wicket:id="editForm">\r
<table class="plain">\r
<tbody>\r
<tr><th><wicket:message key="gb.username"></wicket:message></th><td class="edit"><input type="text" wicket:id="username" id="username" size="30" tabindex="1" /></td></tr>\r
<tr><th><wicket:message key="gb.confirmPassword"></wicket:message></th><td class="edit"><input type="password" wicket:id="confirmPassword" size="30" tabindex="3" /></td></tr>\r
<tr><th><wicket:message key="gb.canAdmin"></wicket:message></th><td class="edit"><input type="checkbox" wicket:id="canAdmin" tabindex="6" /> <i><wicket:message key="gb.canAdminDescription"></wicket:message></i></td></tr> \r
<tr><th><wicket:message key="gb.excludeFromFederation"></wicket:message></th><td class="edit"><input type="checkbox" wicket:id="excludeFromFederation" tabindex="7" /> <i><wicket:message key="gb.excludeFromFederationDescription"></wicket:message></i></td></tr> \r
+ <tr><td colspan="2"><hr></hr></td></tr>\r
+ <tr><th style="vertical-align: top;"><wicket:message key="gb.teamMemberships"></wicket:message></th><td style="padding:2px;"><span wicket:id="teams"></span></td></tr>\r
+ <tr><td colspan="2"><hr></hr></td></tr>\r
<tr><th style="vertical-align: top;"><wicket:message key="gb.restrictedRepositories"></wicket:message></th><td style="padding:2px;"><span wicket:id="repositories"></span></td></tr>\r
<tr><th></th><td class="editButton"><input class="btn" type="submit" value="Cancel" wicket:message="value:gb.cancel" wicket:id="cancel" tabindex="8" /> <input class="btn primary" type="submit" value="Save" wicket:message="value:gb.save" wicket:id="save" tabindex="9" /></td></tr>\r
</tbody>\r
\r
import java.text.MessageFormat;\r
import java.util.ArrayList;\r
+import java.util.Collections;\r
import java.util.Iterator;\r
import java.util.List;\r
\r
import com.gitblit.GitBlitException;\r
import com.gitblit.Keys;\r
import com.gitblit.models.RepositoryModel;\r
+import com.gitblit.models.TeamModel;\r
import com.gitblit.models.UserModel;\r
import com.gitblit.utils.StringUtils;\r
import com.gitblit.wicket.RequiresAdminRole;\r
repos.add(repo);\r
}\r
}\r
+ List<String> userTeams = new ArrayList<String>();\r
+ for (TeamModel team : userModel.teams) {\r
+ userTeams.add(team.name);\r
+ }\r
+ Collections.sort(userTeams);\r
+ \r
final String oldName = userModel.username;\r
final Palette<String> repositories = new Palette<String>("repositories",\r
new ListModel<String>(new ArrayList<String>(userModel.repositories)),\r
new CollectionModel<String>(repos), new ChoiceRenderer<String>("", ""), 10, false);\r
+ final Palette<String> teams = new Palette<String>("teams", new ListModel<String>(\r
+ new ArrayList<String>(userTeams)), new CollectionModel<String>(GitBlit.self()\r
+ .getAllTeamnames()), new ChoiceRenderer<String>("", ""), 10, false);\r
Form<UserModel> form = new Form<UserModel>("editForm", model) {\r
\r
private static final long serialVersionUID = 1L;\r
return;\r
}\r
}\r
- boolean rename = !StringUtils.isEmpty(oldName) && !oldName.equalsIgnoreCase(username);\r
+ boolean rename = !StringUtils.isEmpty(oldName)\r
+ && !oldName.equalsIgnoreCase(username);\r
if (!userModel.password.equals(confirmPassword.getObject())) {\r
error("Passwords do not match!");\r
return;\r
}\r
userModel.repositories.clear();\r
userModel.repositories.addAll(repos);\r
+\r
+ Iterator<String> selectedTeams = teams.getSelectedChoices();\r
+ userModel.teams.clear();\r
+ while (selectedTeams.hasNext()) {\r
+ TeamModel team = GitBlit.self().getTeamModel(selectedTeams.next());\r
+ if (team == null) {\r
+ continue;\r
+ }\r
+ userModel.teams.add(team);\r
+ }\r
+\r
try {\r
GitBlit.self().updateUserModel(oldName, userModel, isCreate);\r
} catch (GitBlitException e) {\r
form.add(new CheckBox("canAdmin"));\r
form.add(new CheckBox("excludeFromFederation"));\r
form.add(repositories);\r
+ form.add(teams);\r
\r
form.add(new Button("save"));\r
Button cancel = new Button("cancel") {\r
lang="en"> \r
<body>\r
<wicket:extend>\r
+ <div wicket:id="teamsPanel">[teams panel]</div>\r
+\r
<div wicket:id="usersPanel">[users panel]</div>\r
</wicket:extend>\r
</body>\r
package com.gitblit.wicket.pages;\r
\r
import com.gitblit.wicket.RequiresAdminRole;\r
+import com.gitblit.wicket.panels.TeamsPanel;\r
import com.gitblit.wicket.panels.UsersPanel;\r
\r
@RequiresAdminRole\r
super(); \r
setupPage("", "");\r
\r
+ add(new TeamsPanel("teamsPanel", showAdmin).setVisible(showAdmin));\r
+ \r
add(new UsersPanel("usersPanel", showAdmin).setVisible(showAdmin));\r
}\r
}\r
--- /dev/null
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\r
+<html xmlns="http://www.w3.org/1999/xhtml" \r
+ xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd" \r
+ xml:lang="en" \r
+ lang="en"> \r
+\r
+<body>\r
+<wicket:panel>\r
+\r
+ <div wicket:id="adminPanel">[admin links]</div>\r
+ \r
+ <table class="repositories">\r
+ <tr>\r
+ <th class="left">\r
+ <img style="vertical-align: middle; border: 1px solid #888; background-color: white;" src="users_16x16.png"/>\r
+ <wicket:message key="gb.teams">[teams]</wicket:message>\r
+ </th>\r
+ <th class="right"></th>\r
+ </tr>\r
+ <tbody> \r
+ <tr wicket:id="teamRow">\r
+ <td class="left" ><div class="list" wicket:id="teamname">[teamname]</div></td>\r
+ <td class="rightAlign"><span wicket:id="teamLinks"></span></td> \r
+ </tr>\r
+ </tbody>\r
+ </table>\r
+ \r
+ <wicket:fragment wicket:id="adminLinks">\r
+ <!-- page nav links --> \r
+ <div class="admin_nav">\r
+ <img style="vertical-align: middle;" src="add_16x16.png"/>\r
+ <a wicket:id="newTeam">\r
+ <wicket:message key="gb.newTeam"></wicket:message>\r
+ </a>\r
+ </div> \r
+ </wicket:fragment>\r
+ \r
+ <wicket:fragment wicket:id="teamAdminLinks">\r
+ <span class="link"><a wicket:id="editTeam"><wicket:message key="gb.edit">[edit]</wicket:message></a> | <a wicket:id="deleteTeam"><wicket:message key="gb.delete">[delete]</wicket:message></a></span>\r
+ </wicket:fragment>\r
+ \r
+</wicket:panel>\r
+</body>\r
+</html>
\ No newline at end of file
--- /dev/null
+/*\r
+ * Copyright 2011 gitblit.com.\r
+ *\r
+ * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ *\r
+ * http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+package com.gitblit.wicket.panels;\r
+\r
+import java.text.MessageFormat;\r
+import java.util.List;\r
+\r
+import org.apache.wicket.markup.html.link.BookmarkablePageLink;\r
+import org.apache.wicket.markup.html.link.Link;\r
+import org.apache.wicket.markup.html.panel.Fragment;\r
+import org.apache.wicket.markup.repeater.Item;\r
+import org.apache.wicket.markup.repeater.data.DataView;\r
+import org.apache.wicket.markup.repeater.data.ListDataProvider;\r
+\r
+import com.gitblit.GitBlit;\r
+import com.gitblit.wicket.WicketUtils;\r
+import com.gitblit.wicket.pages.EditTeamPage;\r
+\r
+public class TeamsPanel extends BasePanel {\r
+\r
+ private static final long serialVersionUID = 1L;\r
+\r
+ public TeamsPanel(String wicketId, final boolean showAdmin) {\r
+ super(wicketId);\r
+\r
+ Fragment adminLinks = new Fragment("adminPanel", "adminLinks", this);\r
+ adminLinks.add(new BookmarkablePageLink<Void>("newTeam", EditTeamPage.class));\r
+ add(adminLinks.setVisible(showAdmin));\r
+\r
+ final List<String> teamnames = GitBlit.self().getAllTeamnames();\r
+ DataView<String> teamsView = new DataView<String>("teamRow", new ListDataProvider<String>(\r
+ teamnames)) {\r
+ private static final long serialVersionUID = 1L;\r
+ private int counter;\r
+\r
+ @Override\r
+ protected void onBeforeRender() {\r
+ super.onBeforeRender();\r
+ counter = 0;\r
+ }\r
+\r
+ public void populateItem(final Item<String> item) {\r
+ final String entry = item.getModelObject();\r
+ LinkPanel editLink = new LinkPanel("teamname", "list", entry, EditTeamPage.class,\r
+ WicketUtils.newTeamnameParameter(entry));\r
+ WicketUtils.setHtmlTooltip(editLink, getString("gb.edit") + " " + entry);\r
+ item.add(editLink);\r
+ Fragment teamLinks = new Fragment("teamLinks", "teamAdminLinks", this);\r
+ teamLinks.add(new BookmarkablePageLink<Void>("editTeam", EditTeamPage.class,\r
+ WicketUtils.newTeamnameParameter(entry)));\r
+ Link<Void> deleteLink = new Link<Void>("deleteTeam") {\r
+\r
+ private static final long serialVersionUID = 1L;\r
+\r
+ @Override\r
+ public void onClick() {\r
+ if (GitBlit.self().deleteTeam(entry)) {\r
+ teamnames.remove(entry);\r
+ info(MessageFormat.format("Team ''{0}'' deleted.", entry));\r
+ } else {\r
+ error(MessageFormat.format("Failed to delete team ''{0}''!", entry));\r
+ }\r
+ }\r
+ };\r
+ deleteLink.add(new JavascriptEventConfirmation("onclick", MessageFormat.format(\r
+ "Delete team \"{0}\"?", entry)));\r
+ teamLinks.add(deleteLink);\r
+ item.add(teamLinks);\r
+\r
+ WicketUtils.setAlternatingBackground(item, counter);\r
+ counter++;\r
+ }\r
+ };\r
+ add(teamsView.setVisible(showAdmin));\r
+ }\r
+}\r
<tr>\r
<th class="left">\r
<img style="vertical-align: middle; border: 1px solid #888; background-color: white;" src="user_16x16.png"/>\r
- <wicket:message key="gb.username">[username]</wicket:message>\r
+ <wicket:message key="gb.users">[users]</wicket:message>\r
</th>\r
<th class="right"></th>\r
</tr>\r
file.delete();\r
IUserService service = new FileUserService(file);\r
testUsers(service);\r
+ testTeams(service);\r
file.delete();\r
}\r
\r
file.delete();\r
IUserService service = new ConfigUserService(file);\r
testUsers(service);\r
+ testTeams(service);\r
file.delete();\r
}\r
\r
testUser = service.getUserModel("test");\r
assertTrue(testUser.hasRepository("newrepo1"));\r
}\r
+\r
+ protected void testTeams(IUserService service) {\r
+\r
+ // confirm we have no teams\r
+ assertEquals(0, service.getAllTeamNames().size());\r
+\r
+ // remove newrepo1 from test user\r
+ // now test user has no repositories\r
+ UserModel user = service.getUserModel("test");\r
+ user.repositories.clear();\r
+ service.updateUserModel(user);\r
+ user = service.getUserModel("test");\r
+ assertEquals(0, user.repositories.size());\r
+ assertFalse(user.canAccessRepository("newrepo1"));\r
+ assertFalse(user.canAccessRepository("NEWREPO1"));\r
+\r
+ // create test team and add test user and newrepo1\r
+ TeamModel team = new TeamModel("testteam");\r
+ team.addUser("test");\r
+ team.addRepository("newrepo1");\r
+ service.updateTeamModel(team);\r
+\r
+ // confirm 1 user and 1 repo\r
+ team = service.getTeamModel("testteam");\r
+ assertEquals(1, team.repositories.size());\r
+ assertEquals(1, team.users.size());\r
+\r
+ // confirm team membership\r
+ user = service.getUserModel("test");\r
+ assertEquals(0, user.repositories.size());\r
+ assertEquals(1, user.teams.size());\r
+\r
+ // confirm team access\r
+ assertTrue(team.hasRepository("newrepo1"));\r
+ assertTrue(user.hasTeamAccess("newrepo1"));\r
+ assertTrue(team.hasRepository("NEWREPO1"));\r
+ assertTrue(user.hasTeamAccess("NEWREPO1"));\r
+\r
+ // rename the team and add new repository\r
+ team.addRepository("newrepo2");\r
+ team.name = "testteam2";\r
+ service.updateTeamModel("testteam", team);\r
+\r
+ team = service.getTeamModel("testteam2");\r
+ user = service.getUserModel("test");\r
+\r
+ // confirm user and team can access newrepo2\r
+ assertEquals(2, team.repositories.size());\r
+ assertTrue(team.hasRepository("newrepo2"));\r
+ assertTrue(user.hasTeamAccess("newrepo2"));\r
+ assertTrue(team.hasRepository("NEWREPO2"));\r
+ assertTrue(user.hasTeamAccess("NEWREPO2"));\r
+\r
+ // delete testteam2\r
+ service.deleteTeam("testteam2");\r
+ team = service.getTeamModel("testteam2");\r
+ user = service.getUserModel("test");\r
+\r
+ // confirm team does not exist and user can not access newrepo1 and 2\r
+ assertEquals(null, team);\r
+ assertFalse(user.canAccessRepository("newrepo1"));\r
+ assertFalse(user.canAccessRepository("newrepo2"));\r
+\r
+ // create new team and add it to user\r
+ // this tests the inverse team creation/team addition\r
+ team = new TeamModel("testteam");\r
+ team.addRepository("NEWREPO1");\r
+ team.addRepository("NEWREPO2");\r
+ user.teams.add(team);\r
+ service.updateUserModel(user);\r
+\r
+ // confirm the inverted team addition\r
+ user = service.getUserModel("test");\r
+ team = service.getTeamModel("testteam");\r
+ assertTrue(user.hasTeamAccess("newrepo1"));\r
+ assertTrue(user.hasTeamAccess("newrepo2"));\r
+ assertTrue(team.hasUser("test"));\r
+\r
+ // drop testteam from user and add nextteam to user\r
+ team = new TeamModel("nextteam");\r
+ team.addRepository("NEWREPO1");\r
+ team.addRepository("NEWREPO2");\r
+ user.teams.clear();\r
+ user.teams.add(team);\r
+ service.updateUserModel(user);\r
+\r
+ // confirm implicit drop\r
+ user = service.getUserModel("test");\r
+ team = service.getTeamModel("testteam");\r
+ assertTrue(user.hasTeamAccess("newrepo1"));\r
+ assertTrue(user.hasTeamAccess("newrepo2"));\r
+ assertFalse(team.hasUser("test"));\r
+ team = service.getTeamModel("nextteam");\r
+ assertTrue(team.hasUser("test"));\r
+\r
+ // delete the user and confirm team no longer has user\r
+ service.deleteUser("test");\r
+ team = service.getTeamModel("testteam");\r
+ assertFalse(team.hasUser("test"));\r
+\r
+ // delete both teams\r
+ service.deleteTeam("testteam");\r
+ service.deleteTeam("nextteam");\r
+ assertEquals(0, service.getAllTeamNames().size());\r
+ }\r
}
\ No newline at end of file