Browse Source

Teams support.

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.
tags/v0.8.0
James Moger 12 years ago
parent
commit
fe24a0be91

+ 1
- 0
docs/01_features.mkd View File

- Gitweb inspired web UI - Gitweb inspired web UI
- Administrators may create, edit, rename, or delete repositories through the web UI or RPC interface - Administrators may create, edit, rename, or delete repositories through the web UI or RPC interface
- Administrators may create, edit, rename, or delete users through the web UI or RPC interface - Administrators may create, edit, rename, or delete users through the web UI or RPC interface
- Administrators may create, edit, rename, or delete teams through the web UI or RPC interface
- Repository Owners may edit repositories through the web UI - Repository Owners may edit repositories through the web UI
- Git-notes display support - Git-notes display support
- Branch metrics (uses Google Charts) - Branch metrics (uses Google Charts)

+ 32
- 147
docs/01_setup.mkd View File

#### Repository Owner #### Repository Owner
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. 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.
### Administering Users (Gitblit v0.8.0+)
All users are stored in the `users.conf` file or in the file you specified in `gitblit.properties`.<br/>
### Teams
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.
### Administering Users (users.conf, Gitblit v0.8.0+)
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.
The `users.conf` file uses a Git-style configuration format: The `users.conf` file uses a Git-style configuration format:
[user "admin"] [user "admin"]
role = "#notfederated" role = "#notfederated"
repository = repo1.git repository = repo1.git
repository = repo2.git repository = repo2.git
[user "hannibal"]
password = bossman
[user "faceman"]
password = vanity
[user "murdock"]
password = crazy
[user "babaracus"]
password = grrrr
[team "ateam"]
user = hannibal
user = faceman
user = murdock
user = babaracus
repository = topsecret.git
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. 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.
### Administering Users (Gitblit v0.5.0 - v0.7.0)
All users are stored in the `users.properties` file or in the file you specified in `gitblit.properties`.<br/>
### Administering Users (users.properties, Gitblit v0.5.0 - v0.7.0)
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.
The format of `users.properties` follows Jetty's convention for HashRealms: The format of `users.properties` follows Jetty's convention for HashRealms:
username,password,role1,role2,role3... username,password,role1,role2,role3...
@teamname,!username1,!username2,!username3,repository1,repository2,repository3...
#### Usernames #### Usernames
Usernames must be unique and are case-insensitive. Usernames must be unique and are case-insensitive.
You may use your own custom *com.gitblit.IUserService* implementation by specifying its fully qualified classname in the *realm.userService* setting. You may use your own custom *com.gitblit.IUserService* implementation by specifying its fully qualified classname in the *realm.userService* setting.
Your user service class must be on Gitblit's classpath and must have a public default constructor.
%BEGINCODE%
public interface IUserService {
/**
* Setup the user service.
*
* @param settings
* @since 0.7.0
*/
@Override
public void setup(IStoredSettings settings) {
}
/**
* Does the user service support cookie authentication?
*
* @return true or false
*/
boolean supportsCookies();
/**
* Returns the cookie value for the specified user.
*
* @param model
* @return cookie value
*/
char[] getCookie(UserModel model);
/**
* Authenticate a user based on their cookie.
*
* @param cookie
* @return a user object or null
*/
UserModel authenticate(char[] cookie);
/**
* Authenticate a user based on a username and password.
*
* @param username
* @param password
* @return a user object or null
*/
UserModel authenticate(String username, char[] password);
/**
* Retrieve the user object for the specified username.
*
* @param username
* @return a user object or null
*/
UserModel getUserModel(String username);
/**
* Updates/writes a complete user object.
*
* @param model
* @return true if update is successful
*/
boolean updateUserModel(UserModel model);
/**
* Adds/updates a user object keyed by username. This method allows for
* renaming a user.
*
* @param username
* the old username
* @param model
* the user object to use for username
* @return true if update is successful
*/
boolean updateUserModel(String username, UserModel model);
/**
* Deletes the user object from the user service.
*
* @param model
* @return true if successful
*/
boolean deleteUserModel(UserModel model);
/**
* Delete the user object with the specified username
*
* @param username
* @return true if successful
*/
boolean deleteUser(String username);
/**
* Returns the list of all users available to the login service.
*
* @return list of all usernames
*/
List<String> getAllUsernames();
/**
* Returns the list of all users who are allowed to bypass the access
* restriction placed on the specified repository.
*
* @param role
* the repository name
* @return list of all usernames that can bypass the access restriction
*/
List<String> getUsernamesForRepositoryRole(String role);
/**
* Sets the list of all uses who are allowed to bypass the access
* restriction placed on the specified repository.
*
* @param role
* the repository name
* @param usernames
* @return true if successful
*/
boolean setUsernamesForRepositoryRole(String role, List<String> usernames);
/**
* Renames a repository role.
*
* @param oldRole
* @param newRole
* @return true if successful
*/
boolean renameRepositoryRole(String oldRole, String newRole);
/**
* Removes a repository role from all users.
*
* @param role
* @return true if successful
*/
boolean deleteRepositoryRole(String role);
/**
* @See java.lang.Object.toString();
* @return string representation of the login service
*/
String toString();
}
%ENDCODE%
Your user service class must be on Gitblit's classpath and must have a public default constructor.
Please see the following interface definition [com.gitblit.IUserService](https://github.com/gitblit/gitblit/blob/master/src/com/gitblit/IUserService.java).
## Client Setup and Configuration ## Client Setup and Configuration
### Https with Self-Signed Certificates ### Https with Self-Signed Certificates

+ 1
- 0
docs/04_releases.mkd View File

- added: new default user service implementation: com.gitblit.ConfigUserService (users.conf) - added: new default user service implementation: com.gitblit.ConfigUserService (users.conf)
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. 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.
**New:** *realm.userService = users.conf* **New:** *realm.userService = users.conf*
- added: Teams for specifying user-repository access
- added: Gitblit Express bundle to get started running Gitblit on RedHat's OpenShift cloud - added: Gitblit Express bundle to get started running Gitblit on RedHat's OpenShift cloud
- added: optional Gravatar integration - added: optional Gravatar integration
**New:** *web.allowGravatar = true* **New:** *web.allowGravatar = true*

BIN
resources/users_16x16.png View File


+ 289
- 18
src/com/gitblit/ConfigUserService.java View File

import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import com.gitblit.models.TeamModel;
import com.gitblit.models.UserModel; import com.gitblit.models.UserModel;
import com.gitblit.utils.DeepCopier;
import com.gitblit.utils.StringUtils; import com.gitblit.utils.StringUtils;
/** /**
*/ */
public class ConfigUserService implements IUserService { public class ConfigUserService implements IUserService {
private static final String TEAM = "team";
private static final String USER = "user";
private static final String PASSWORD = "password";
private static final String REPOSITORY = "repository";
private static final String ROLE = "role";
private final File realmFile; private final File realmFile;
private final Logger logger = LoggerFactory.getLogger(ConfigUserService.class); private final Logger logger = LoggerFactory.getLogger(ConfigUserService.class);
private final Map<String, UserModel> cookies = new ConcurrentHashMap<String, UserModel>(); private final Map<String, UserModel> cookies = new ConcurrentHashMap<String, UserModel>();
private final String userSection = "user";
private final String passwordField = "password";
private final String repositoryField = "repository";
private final String roleField = "role";
private final Map<String, TeamModel> teams = new ConcurrentHashMap<String, TeamModel>();
private volatile long lastModified; private volatile long lastModified;
* Setup the user service. * Setup the user service.
* *
* @param settings * @param settings
* @since 0.6.1
* @since 0.7.0
*/ */
@Override @Override
public void setup(IStoredSettings settings) { public void setup(IStoredSettings settings) {
public UserModel getUserModel(String username) { public UserModel getUserModel(String username) {
read(); read();
UserModel model = users.get(username.toLowerCase()); UserModel model = users.get(username.toLowerCase());
if (model != null) {
// clone the model, otherwise all changes to this object are
// live and unpersisted
model = DeepCopier.copy(model);
}
return model; return model;
} }
public boolean updateUserModel(String username, UserModel model) { public boolean updateUserModel(String username, UserModel model) {
try { try {
read(); read();
users.remove(username.toLowerCase());
UserModel oldUser = users.remove(username.toLowerCase());
users.put(model.username.toLowerCase(), model); users.put(model.username.toLowerCase(), model);
// null check on "final" teams because JSON-sourced UserModel
// can have a null teams object
if (model.teams != null) {
for (TeamModel team : model.teams) {
TeamModel t = teams.get(team.name.toLowerCase());
if (t == null) {
// new team
team.addUser(username);
teams.put(team.name.toLowerCase(), team);
} else {
// do not clobber existing team definition
// maybe because this is a federated user
t.removeUser(username);
t.addUser(model.username);
}
}
// check for implicit team removal
if (oldUser != null) {
for (TeamModel team : oldUser.teams) {
if (!model.isTeamMember(team.name)) {
team.removeUser(username);
}
}
}
}
write(); write();
return true; return true;
} catch (Throwable t) { } catch (Throwable t) {
try { try {
// Read realm file // Read realm file
read(); read();
users.remove(username.toLowerCase());
UserModel model = users.remove(username.toLowerCase());
// remove user from team
for (TeamModel team : model.teams) {
TeamModel t = teams.get(team.name);
if (t == null) {
// new team
team.removeUser(username);
teams.put(team.name.toLowerCase(), team);
} else {
// existing team
t.removeUser(username);
}
}
write(); write();
return true; return true;
} catch (Throwable t) { } catch (Throwable t) {
return false; return false;
} }
/**
* Returns the list of all teams available to the login service.
*
* @return list of all teams
* @since 0.8.0
*/
@Override
public List<String> getAllTeamNames() {
read();
List<String> list = new ArrayList<String>(teams.keySet());
return list;
}
/**
* Returns the list of all users who are allowed to bypass the access
* restriction placed on the specified repository.
*
* @param role
* the repository name
* @return list of all usernames that can bypass the access restriction
*/
@Override
public List<String> getTeamnamesForRepositoryRole(String role) {
List<String> list = new ArrayList<String>();
try {
read();
for (Map.Entry<String, TeamModel> entry : teams.entrySet()) {
TeamModel model = entry.getValue();
if (model.hasRepository(role)) {
list.add(model.name);
}
}
} catch (Throwable t) {
logger.error(MessageFormat.format("Failed to get teamnames for role {0}!", role), t);
}
return list;
}
/**
* Sets the list of all teams who are allowed to bypass the access
* restriction placed on the specified repository.
*
* @param role
* the repository name
* @param teamnames
* @return true if successful
*/
@Override
public boolean setTeamnamesForRepositoryRole(String role, List<String> teamnames) {
try {
Set<String> specifiedTeams = new HashSet<String>();
for (String teamname : teamnames) {
specifiedTeams.add(teamname.toLowerCase());
}
read();
// identify teams which require add or remove role
for (TeamModel team : teams.values()) {
// team has role, check against revised team list
if (specifiedTeams.contains(team.name.toLowerCase())) {
team.addRepository(role);
} else {
// remove role from team
team.removeRepository(role);
}
}
// persist changes
write();
return true;
} catch (Throwable t) {
logger.error(MessageFormat.format("Failed to set teams for role {0}!", role), t);
}
return false;
}
/**
* Retrieve the team object for the specified team name.
*
* @param teamname
* @return a team object or null
* @since 0.8.0
*/
@Override
public TeamModel getTeamModel(String teamname) {
read();
TeamModel model = teams.get(teamname.toLowerCase());
if (model != null) {
// clone the model, otherwise all changes to this object are
// live and unpersisted
model = DeepCopier.copy(model);
}
return model;
}
/**
* Updates/writes a complete team object.
*
* @param model
* @return true if update is successful
* @since 0.8.0
*/
@Override
public boolean updateTeamModel(TeamModel model) {
return updateTeamModel(model.name, model);
}
/**
* Updates/writes and replaces a complete team object keyed by teamname.
* This method allows for renaming a team.
*
* @param teamname
* the old teamname
* @param model
* the team object to use for teamname
* @return true if update is successful
* @since 0.8.0
*/
@Override
public boolean updateTeamModel(String teamname, TeamModel model) {
try {
read();
teams.remove(teamname.toLowerCase());
teams.put(model.name.toLowerCase(), model);
write();
return true;
} catch (Throwable t) {
logger.error(MessageFormat.format("Failed to update team model {0}!", model.name), t);
}
return false;
}
/**
* Deletes the team object from the user service.
*
* @param model
* @return true if successful
* @since 0.8.0
*/
@Override
public boolean deleteTeamModel(TeamModel model) {
return deleteTeam(model.name);
}
/**
* Delete the team object with the specified teamname
*
* @param teamname
* @return true if successful
* @since 0.8.0
*/
@Override
public boolean deleteTeam(String teamname) {
try {
// Read realm file
read();
teams.remove(teamname.toLowerCase());
write();
return true;
} catch (Throwable t) {
logger.error(MessageFormat.format("Failed to delete team {0}!", teamname), t);
}
return false;
}
/** /**
* Returns the list of all users available to the login service. * Returns the list of all users available to the login service.
* *
} }
} }
// identify teams which require role rename
for (TeamModel model : teams.values()) {
if (model.hasRepository(oldRole)) {
model.removeRepository(oldRole);
model.addRepository(newRole);
}
}
// persist changes // persist changes
write(); write();
return true; return true;
user.removeRepository(role); user.removeRepository(role);
} }
// identify teams which require role rename
for (TeamModel team : teams.values()) {
team.removeRepository(role);
}
// persist changes // persist changes
write(); write();
return true; return true;
File realmFileCopy = new File(realmFile.getAbsolutePath() + ".tmp"); File realmFileCopy = new File(realmFile.getAbsolutePath() + ".tmp");
StoredConfig config = new FileBasedConfig(realmFileCopy, FS.detect()); StoredConfig config = new FileBasedConfig(realmFileCopy, FS.detect());
// write users
for (UserModel model : users.values()) { for (UserModel model : users.values()) {
config.setString(userSection, model.username, passwordField, model.password);
config.setString(USER, model.username, PASSWORD, model.password);
// user roles // user roles
List<String> roles = new ArrayList<String>(); List<String> roles = new ArrayList<String>();
if (model.excludeFromFederation) { if (model.excludeFromFederation) {
roles.add(Constants.NOT_FEDERATED_ROLE); roles.add(Constants.NOT_FEDERATED_ROLE);
} }
config.setStringList(userSection, model.username, roleField, roles);
config.setStringList(USER, model.username, ROLE, roles);
// repository memberships // repository memberships
config.setStringList(userSection, model.username, repositoryField,
new ArrayList<String>(model.repositories));
// null check on "final" repositories because JSON-sourced UserModel
// can have a null repositories object
if (model.repositories != null) {
config.setStringList(USER, model.username, REPOSITORY, new ArrayList<String>(
model.repositories));
}
}
// write teams
for (TeamModel model : teams.values()) {
// null check on "final" repositories because JSON-sourced TeamModel
// can have a null repositories object
if (model.repositories != null) {
config.setStringList(TEAM, model.name, REPOSITORY, new ArrayList<String>(
model.repositories));
}
// null check on "final" users because JSON-sourced TeamModel
// can have a null users object
if (model.users != null) {
config.setStringList(TEAM, model.name, USER, new ArrayList<String>(model.users));
}
} }
config.save(); config.save();
// If the write is successful, delete the current file and rename // If the write is successful, delete the current file and rename
lastModified = realmFile.lastModified(); lastModified = realmFile.lastModified();
users.clear(); users.clear();
cookies.clear(); cookies.clear();
teams.clear();
try { try {
StoredConfig config = new FileBasedConfig(realmFile, FS.detect()); StoredConfig config = new FileBasedConfig(realmFile, FS.detect());
config.load(); config.load();
Set<String> usernames = config.getSubsections(userSection);
Set<String> usernames = config.getSubsections(USER);
for (String username : usernames) { for (String username : usernames) {
UserModel user = new UserModel(username); UserModel user = new UserModel(username);
user.password = config.getString(userSection, username, passwordField);
user.password = config.getString(USER, username, PASSWORD);
// user roles // user roles
Set<String> roles = new HashSet<String>(Arrays.asList(config.getStringList( Set<String> roles = new HashSet<String>(Arrays.asList(config.getStringList(
userSection, username, roleField)));
USER, username, ROLE)));
user.canAdmin = roles.contains(Constants.ADMIN_ROLE); user.canAdmin = roles.contains(Constants.ADMIN_ROLE);
user.excludeFromFederation = roles.contains(Constants.NOT_FEDERATED_ROLE); user.excludeFromFederation = roles.contains(Constants.NOT_FEDERATED_ROLE);
// repository memberships // repository memberships
Set<String> repositories = new HashSet<String>(Arrays.asList(config Set<String> repositories = new HashSet<String>(Arrays.asList(config
.getStringList(userSection, username, repositoryField)));
.getStringList(USER, username, REPOSITORY)));
for (String repository : repositories) { for (String repository : repositories) {
user.addRepository(repository); user.addRepository(repository);
} }
users.put(username, user); users.put(username, user);
cookies.put(StringUtils.getSHA1(username + user.password), user); cookies.put(StringUtils.getSHA1(username + user.password), user);
} }
// load the teams
Set<String> teamnames = config.getSubsections(TEAM);
for (String teamname : teamnames) {
TeamModel team = new TeamModel(teamname);
team.addRepositories(Arrays.asList(config.getStringList(TEAM, teamname,
REPOSITORY)));
team.addUsers(Arrays.asList(config.getStringList(TEAM, teamname, USER)));
teams.put(team.name.toLowerCase(), team);
// set the teams on the users
for (String user : team.users) {
UserModel model = users.get(user);
if (model != null) {
model.teams.add(team);
}
}
}
} catch (Exception e) { } catch (Exception e) {
logger.error(MessageFormat.format("Failed to read {0}", realmFile), e); logger.error(MessageFormat.format("Failed to read {0}", realmFile), e);
} }

+ 316
- 7
src/com/gitblit/FileUserService.java View File

import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import com.gitblit.models.TeamModel;
import com.gitblit.models.UserModel; import com.gitblit.models.UserModel;
import com.gitblit.utils.DeepCopier;
import com.gitblit.utils.StringUtils; import com.gitblit.utils.StringUtils;
/** /**
private final Map<String, String> cookies = new ConcurrentHashMap<String, String>(); private final Map<String, String> cookies = new ConcurrentHashMap<String, String>();
private final Map<String, TeamModel> teams = new ConcurrentHashMap<String, TeamModel>();
public FileUserService(File realmFile) { public FileUserService(File realmFile) {
super(realmFile.getAbsolutePath()); super(realmFile.getAbsolutePath());
} }
* Setup the user service. * Setup the user service.
* *
* @param settings * @param settings
* @since 0.6.1
* @since 0.7.0
*/ */
@Override @Override
public void setup(IStoredSettings settings) { public void setup(IStoredSettings settings) {
model.addRepository(role); model.addRepository(role);
} }
} }
// set the teams for the user
for (TeamModel team : teams.values()) {
if (team.hasUser(username)) {
model.teams.add(DeepCopier.copy(team));
}
}
return model; return model;
} }
public boolean updateUserModel(String username, UserModel model) { public boolean updateUserModel(String username, UserModel model) {
try { try {
Properties allUsers = read(); Properties allUsers = read();
UserModel oldUser = getUserModel(username);
ArrayList<String> roles = new ArrayList<String>(model.repositories); ArrayList<String> roles = new ArrayList<String>(model.repositories);
// Permissions // Permissions
allUsers.remove(username); allUsers.remove(username);
allUsers.put(model.username, sb.toString()); allUsers.put(model.username, sb.toString());
// null check on "final" teams because JSON-sourced UserModel
// can have a null teams object
if (model.teams != null) {
// update team cache
for (TeamModel team : model.teams) {
TeamModel t = getTeamModel(team.name);
if (t == null) {
// new team
t = team;
}
t.removeUser(username);
t.addUser(model.username);
updateTeamCache(allUsers, t.name, t);
}
// check for implicit team removal
if (oldUser != null) {
for (TeamModel team : oldUser.teams) {
if (!model.isTeamMember(team.name)) {
team.removeUser(username);
updateTeamCache(allUsers, team.name, team);
}
}
}
}
write(allUsers); write(allUsers);
return true; return true;
} catch (Throwable t) { } catch (Throwable t) {
try { try {
// Read realm file // Read realm file
Properties allUsers = read(); Properties allUsers = read();
UserModel user = getUserModel(username);
allUsers.remove(username); allUsers.remove(username);
for (TeamModel team : user.teams) {
TeamModel t = getTeamModel(team.name);
if (t == null) {
// new team
t = team;
}
t.removeUser(username);
updateTeamCache(allUsers, t.name, t);
}
write(allUsers); write(allUsers);
return true; return true;
} catch (Throwable t) { } catch (Throwable t) {
@Override @Override
public List<String> getAllUsernames() { public List<String> getAllUsernames() {
Properties allUsers = read(); Properties allUsers = read();
List<String> list = new ArrayList<String>(allUsers.stringPropertyNames());
List<String> list = new ArrayList<String>();
for (String user : allUsers.stringPropertyNames()) {
if (user.charAt(0) == '@') {
// skip team user definitions
continue;
}
list.add(user);
}
return list; return list;
} }
try { try {
Properties allUsers = read(); Properties allUsers = read();
for (String username : allUsers.stringPropertyNames()) { for (String username : allUsers.stringPropertyNames()) {
if (username.charAt(0) == '@') {
continue;
}
String value = allUsers.getProperty(username); String value = allUsers.getProperty(username);
String[] values = value.split(","); String[] values = value.split(",");
// skip first value (password) // skip first value (password)
} }
/** /**
* Sets the list of all uses who are allowed to bypass the access
* Sets the list of all users who are allowed to bypass the access
* restriction placed on the specified repository. * restriction placed on the specified repository.
* *
* @param role * @param role
sb.append(','); sb.append(',');
sb.append(newRole); sb.append(newRole);
sb.append(','); sb.append(',');
// skip first value (password) // skip first value (password)
for (int i = 1; i < values.length; i++) { for (int i = 1; i < values.length; i++) {
String value = values[i]; String value = values[i];
FileWriter writer = new FileWriter(realmFileCopy); FileWriter writer = new FileWriter(realmFileCopy);
properties properties
.store(writer, .store(writer,
"# Gitblit realm file format: username=password,\\#permission,repository1,repository2...");
" Gitblit realm file format:\n username=password,\\#permission,repository1,repository2...\n @teamname=!username1,!username2,!username3,repository1,repository2...");
writer.close(); writer.close();
// If the write is successful, delete the current file and rename // If the write is successful, delete the current file and rename
// the temporary copy to the original filename. // the temporary copy to the original filename.
if (lastRead != lastModified()) { if (lastRead != lastModified()) {
// reload hash cache // reload hash cache
cookies.clear(); cookies.clear();
teams.clear();
for (String username : allUsers.stringPropertyNames()) { for (String username : allUsers.stringPropertyNames()) {
String value = allUsers.getProperty(username); String value = allUsers.getProperty(username);
String[] roles = value.split(","); String[] roles = value.split(",");
String password = roles[0];
cookies.put(StringUtils.getSHA1(username + password), username);
if (username.charAt(0) == '@') {
// team definition
TeamModel team = new TeamModel(username.substring(1));
List<String> repositories = new ArrayList<String>();
List<String> users = new ArrayList<String>();
for (String role : roles) {
if (role.charAt(0) == '!') {
users.add(role.substring(1));
} else {
repositories.add(role);
}
}
team.addRepositories(repositories);
team.addUsers(users);
teams.put(team.name.toLowerCase(), team);
} else {
// user definition
String password = roles[0];
cookies.put(StringUtils.getSHA1(username + password), username);
}
} }
} }
return allUsers; return allUsers;
public String toString() { public String toString() {
return getClass().getSimpleName() + "(" + propertiesFile.getAbsolutePath() + ")"; return getClass().getSimpleName() + "(" + propertiesFile.getAbsolutePath() + ")";
} }
/**
* Returns the list of all teams available to the login service.
*
* @return list of all teams
* @since 0.8.0
*/
@Override
public List<String> getAllTeamNames() {
List<String> list = new ArrayList<String>(teams.keySet());
return list;
}
/**
* Returns the list of all teams who are allowed to bypass the access
* restriction placed on the specified repository.
*
* @param role
* the repository name
* @return list of all teamnames that can bypass the access restriction
*/
@Override
public List<String> getTeamnamesForRepositoryRole(String role) {
List<String> list = new ArrayList<String>();
try {
Properties allUsers = read();
for (String team : allUsers.stringPropertyNames()) {
if (team.charAt(0) != '@') {
// skip users
continue;
}
String value = allUsers.getProperty(team);
String[] values = value.split(",");
for (int i = 0; i < values.length; i++) {
String r = values[i];
if (r.equalsIgnoreCase(role)) {
// strip leading @
list.add(team.substring(1));
break;
}
}
}
} catch (Throwable t) {
logger.error(MessageFormat.format("Failed to get teamnames for role {0}!", role), t);
}
return list;
}
/**
* Sets the list of all teams who are allowed to bypass the access
* restriction placed on the specified repository.
*
* @param role
* the repository name
* @param teamnames
* @return true if successful
*/
@Override
public boolean setTeamnamesForRepositoryRole(String role, List<String> teamnames) {
try {
Set<String> specifiedTeams = new HashSet<String>(teamnames);
Set<String> needsAddRole = new HashSet<String>(specifiedTeams);
Set<String> needsRemoveRole = new HashSet<String>();
// identify teams which require add and remove role
Properties allUsers = read();
for (String team : allUsers.stringPropertyNames()) {
if (team.charAt(0) != '@') {
// skip users
continue;
}
String name = team.substring(1);
String value = allUsers.getProperty(team);
String[] values = value.split(",");
for (int i = 0; i < values.length; i++) {
String r = values[i];
if (r.equalsIgnoreCase(role)) {
// team has role, check against revised team list
if (specifiedTeams.contains(name)) {
needsAddRole.remove(name);
} else {
// remove role from team
needsRemoveRole.add(name);
}
break;
}
}
}
// add roles to teams
for (String name : needsAddRole) {
String team = "@" + name;
String teamValues = allUsers.getProperty(team);
teamValues += "," + role;
allUsers.put(team, teamValues);
}
// remove role from team
for (String name : needsRemoveRole) {
String team = "@" + name;
String[] values = allUsers.getProperty(team).split(",");
StringBuilder sb = new StringBuilder();
for (int i = 0; i < values.length; i++) {
String value = values[i];
if (!value.equalsIgnoreCase(role)) {
sb.append(value);
sb.append(',');
}
}
sb.setLength(sb.length() - 1);
// update properties
allUsers.put(team, sb.toString());
}
// persist changes
write(allUsers);
return true;
} catch (Throwable t) {
logger.error(MessageFormat.format("Failed to set teamnames for role {0}!", role), t);
}
return false;
}
/**
* Retrieve the team object for the specified team name.
*
* @param teamname
* @return a team object or null
* @since 0.8.0
*/
@Override
public TeamModel getTeamModel(String teamname) {
read();
TeamModel team = teams.get(teamname.toLowerCase());
if (team != null) {
// clone the model, otherwise all changes to this object are
// live and unpersisted
team = DeepCopier.copy(team);
}
return team;
}
/**
* Updates/writes a complete team object.
*
* @param model
* @return true if update is successful
* @since 0.8.0
*/
@Override
public boolean updateTeamModel(TeamModel model) {
return updateTeamModel(model.name, model);
}
/**
* Updates/writes and replaces a complete team object keyed by teamname.
* This method allows for renaming a team.
*
* @param teamname
* the old teamname
* @param model
* the team object to use for teamname
* @return true if update is successful
* @since 0.8.0
*/
@Override
public boolean updateTeamModel(String teamname, TeamModel model) {
try {
Properties allUsers = read();
updateTeamCache(allUsers, teamname, model);
write(allUsers);
return true;
} catch (Throwable t) {
logger.error(MessageFormat.format("Failed to update team model {0}!", model.name), t);
}
return false;
}
private void updateTeamCache(Properties allUsers, String teamname, TeamModel model) {
StringBuilder sb = new StringBuilder();
for (String repository : model.repositories) {
sb.append(repository);
sb.append(',');
}
for (String user : model.users) {
sb.append('!');
sb.append(user);
sb.append(',');
}
// trim trailing comma
sb.setLength(sb.length() - 1);
allUsers.remove("@" + teamname);
allUsers.put("@" + model.name, sb.toString());
// update team cache
teams.remove(teamname.toLowerCase());
teams.put(model.name.toLowerCase(), model);
}
/**
* Deletes the team object from the user service.
*
* @param model
* @return true if successful
* @since 0.8.0
*/
@Override
public boolean deleteTeamModel(TeamModel model) {
return deleteTeam(model.name);
}
/**
* Delete the team object with the specified teamname
*
* @param teamname
* @return true if successful
* @since 0.8.0
*/
@Override
public boolean deleteTeam(String teamname) {
Properties allUsers = read();
teams.remove(teamname.toLowerCase());
allUsers.remove("@" + teamname);
try {
write(allUsers);
return true;
} catch (Throwable t) {
logger.error(MessageFormat.format("Failed to delete team {0}!", teamname), t);
}
return false;
}
} }

+ 80
- 1
src/com/gitblit/GitBlit.java View File

import com.gitblit.models.ServerSettings; import com.gitblit.models.ServerSettings;
import com.gitblit.models.ServerStatus; import com.gitblit.models.ServerStatus;
import com.gitblit.models.SettingModel; import com.gitblit.models.SettingModel;
import com.gitblit.models.TeamModel;
import com.gitblit.models.UserModel; import com.gitblit.models.UserModel;
import com.gitblit.utils.ByteFormat; import com.gitblit.utils.ByteFormat;
import com.gitblit.utils.FederationUtils; import com.gitblit.utils.FederationUtils;
} }
} }
/**
* Returns the list of available teams that a user or repository may be
* assigned to.
*
* @return the list of teams
*/
public List<String> getAllTeamnames() {
List<String> teams = new ArrayList<String>(userService.getAllTeamNames());
Collections.sort(teams);
return teams;
}
/**
* Returns the TeamModel object for the specified name.
*
* @param teamname
* @return a TeamModel object or null
*/
public TeamModel getTeamModel(String teamname) {
return userService.getTeamModel(teamname);
}
/**
* Returns the list of all teams who are allowed to bypass the access
* restriction placed on the specified repository.
*
* @see IUserService.getTeamnamesForRepositoryRole(String)
* @param repository
* @return list of all teamnames that can bypass the access restriction
*/
public List<String> getRepositoryTeams(RepositoryModel repository) {
return userService.getTeamnamesForRepositoryRole(repository.name);
}
/**
* Sets the list of all uses who are allowed to bypass the access
* restriction placed on the specified repository.
*
* @see IUserService.setTeamnamesForRepositoryRole(String, List<String>)
* @param repository
* @param teamnames
* @return true if successful
*/
public boolean setRepositoryTeams(RepositoryModel repository, List<String> repositoryTeams) {
return userService.setTeamnamesForRepositoryRole(repository.name, repositoryTeams);
}
/**
* Updates the TeamModel object for the specified name.
*
* @param teamname
* @param team
* @param isCreate
*/
public void updateTeamModel(String teamname, TeamModel team, boolean isCreate) throws GitBlitException {
if (!teamname.equalsIgnoreCase(team.name)) {
if (userService.getTeamModel(team.name) != null) {
throw new GitBlitException(MessageFormat.format(
"Failed to rename ''{0}'' because ''{1}'' already exists.", teamname,
team.name));
}
}
if (!userService.updateTeamModel(teamname, team)) {
throw new GitBlitException(isCreate ? "Failed to add team!" : "Failed to update team!");
}
}
/**
* Delete the team object with the specified teamname
*
* @see IUserService.deleteTeam(String)
* @param teamname
* @return true if successful
*/
public boolean deleteTeam(String teamname) {
return userService.deleteTeam(teamname);
}
/** /**
* Clears all the cached data for the specified repository. * Clears all the cached data for the specified repository.
* *
case PULL_REPOSITORIES: case PULL_REPOSITORIES:
return token.equals(all) || token.equals(unr) || token.equals(jur); return token.equals(all) || token.equals(unr) || token.equals(jur);
case PULL_USERS: case PULL_USERS:
case PULL_TEAMS:
return token.equals(all) || token.equals(unr); return token.equals(all) || token.equals(unr);
case PULL_SETTINGS: case PULL_SETTINGS:
return token.equals(all); return token.equals(all);
configService.updateUserModel(userModel); configService.updateUserModel(userModel);
} }
} }
// issue suggestion about switching to users.conf // issue suggestion about switching to users.conf
logger.warn("Please consider using \"users.conf\" instead of the deprecated \"users.properties\" file"); logger.warn("Please consider using \"users.conf\" instead of the deprecated \"users.properties\" file");
} else if (realmFile.getName().toLowerCase().endsWith(".conf")) { } else if (realmFile.getName().toLowerCase().endsWith(".conf")) {

+ 79
- 0
src/com/gitblit/IUserService.java View File

import java.util.List; import java.util.List;
import com.gitblit.models.TeamModel;
import com.gitblit.models.UserModel; import com.gitblit.models.UserModel;
/** /**
*/ */
List<String> getAllUsernames(); List<String> getAllUsernames();
/**
* Returns the list of all teams available to the login service.
*
* @return list of all teams
* @since 0.8.0
*/
List<String> getAllTeamNames();
/**
* Returns the list of all users who are allowed to bypass the access
* restriction placed on the specified repository.
*
* @param role
* the repository name
* @return list of all usernames that can bypass the access restriction
*/
List<String> getTeamnamesForRepositoryRole(String role);
/**
* Sets the list of all teams who are allowed to bypass the access
* restriction placed on the specified repository.
*
* @param role
* the repository name
* @param teamnames
* @return true if successful
*/
boolean setTeamnamesForRepositoryRole(String role, List<String> teamnames);
/**
* Retrieve the team object for the specified team name.
*
* @param teamname
* @return a team object or null
* @since 0.8.0
*/
TeamModel getTeamModel(String teamname);
/**
* Updates/writes a complete team object.
*
* @param model
* @return true if update is successful
* @since 0.8.0
*/
boolean updateTeamModel(TeamModel model);
/**
* Updates/writes and replaces a complete team object keyed by teamname.
* This method allows for renaming a team.
*
* @param teamname
* the old teamname
* @param model
* the team object to use for teamname
* @return true if update is successful
* @since 0.8.0
*/
boolean updateTeamModel(String teamname, TeamModel model);
/**
* Deletes the team object from the user service.
*
* @param model
* @return true if successful
* @since 0.8.0
*/
boolean deleteTeamModel(TeamModel model);
/**
* Delete the team object with the specified teamname
*
* @param teamname
* @return true if successful
* @since 0.8.0
*/
boolean deleteTeam(String teamname);
/** /**
* Returns the list of all users who are allowed to bypass the access * Returns the list of all users who are allowed to bypass the access
* restriction placed on the specified repository. * restriction placed on the specified repository.

+ 88
- 0
src/com/gitblit/models/TeamModel.java View File

/*
* Copyright 2011 gitblit.com.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.gitblit.models;
import java.io.Serializable;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
/**
* TeamModel is a serializable model class that represents a group of users and
* a list of accessible repositories.
*
* @author James Moger
*
*/
public class TeamModel implements Serializable, Comparable<TeamModel> {
private static final long serialVersionUID = 1L;
// field names are reflectively mapped in EditTeam page
public String name;
public final Set<String> users = new HashSet<String>();
public final Set<String> repositories = new HashSet<String>();
public TeamModel(String name) {
this.name = name;
}
public boolean hasRepository(String name) {
return repositories.contains(name.toLowerCase());
}
public void addRepository(String name) {
repositories.add(name.toLowerCase());
}
public void addRepositories(Collection<String> names) {
for (String name:names) {
repositories.add(name.toLowerCase());
}
}
public void removeRepository(String name) {
repositories.remove(name.toLowerCase());
}
public boolean hasUser(String name) {
return users.contains(name.toLowerCase());
}
public void addUser(String name) {
users.add(name.toLowerCase());
}
public void addUsers(Collection<String> names) {
for (String name:names) {
users.add(name.toLowerCase());
}
}
public void removeUser(String name) {
users.remove(name.toLowerCase());
}
@Override
public String toString() {
return name;
}
@Override
public int compareTo(TeamModel o) {
return name.compareTo(o.name);
}
}

+ 23
- 2
src/com/gitblit/models/UserModel.java View File

public boolean canAdmin; public boolean canAdmin;
public boolean excludeFromFederation; public boolean excludeFromFederation;
public final Set<String> repositories = new HashSet<String>(); public final Set<String> repositories = new HashSet<String>();
public final Set<TeamModel> teams = new HashSet<TeamModel>();
public UserModel(String username) { public UserModel(String username) {
this.username = username; this.username = username;
*/ */
@Deprecated @Deprecated
public boolean canAccessRepository(String repositoryName) { public boolean canAccessRepository(String repositoryName) {
return canAdmin || repositories.contains(repositoryName.toLowerCase());
return canAdmin || repositories.contains(repositoryName.toLowerCase())
|| hasTeamAccess(repositoryName);
} }
public boolean canAccessRepository(RepositoryModel repository) { public boolean canAccessRepository(RepositoryModel repository) {
boolean isOwner = !StringUtils.isEmpty(repository.owner) boolean isOwner = !StringUtils.isEmpty(repository.owner)
&& repository.owner.equals(username); && repository.owner.equals(username);
return canAdmin || isOwner || repositories.contains(repository.name.toLowerCase());
return canAdmin || isOwner || repositories.contains(repository.name.toLowerCase())
|| hasTeamAccess(repository.name);
}
public boolean hasTeamAccess(String repositoryName) {
for (TeamModel team : teams) {
if (team.hasRepository(repositoryName)) {
return true;
}
}
return false;
} }
public boolean hasRepository(String name) { public boolean hasRepository(String name) {
repositories.remove(name.toLowerCase()); repositories.remove(name.toLowerCase());
} }
public boolean isTeamMember(String teamname) {
for (TeamModel team : teams) {
if (team.name.equalsIgnoreCase(teamname)) {
return true;
}
}
return false;
}
@Override @Override
public String getName() { public String getName() {
return username; return username;

+ 135
- 0
src/com/gitblit/utils/DeepCopier.java View File

/*
* Copyright 2011 gitblit.com.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.gitblit.utils;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
public class DeepCopier {
/**
* Produce a deep copy of the given object. Serializes the entire object to
* a byte array in memory. Recommended for relatively small objects.
*/
@SuppressWarnings("unchecked")
public static <T> T copy(T original) {
T o = null;
try {
ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(byteOut);
oos.writeObject(original);
ByteArrayInputStream byteIn = new ByteArrayInputStream(byteOut.toByteArray());
ObjectInputStream ois = new ObjectInputStream(byteIn);
try {
o = (T) ois.readObject();
} catch (ClassNotFoundException cex) {
// actually can not happen in this instance
}
} catch (IOException iox) {
// doesn't seem likely to happen as these streams are in memory
throw new RuntimeException(iox);
}
return o;
}
/**
* This conserves heap memory!!!!! Produce a deep copy of the given object.
* Serializes the object through a pipe between two threads. Recommended for
* very large objects. The current thread is used for serializing the
* original object in order to respect any synchronization the caller may
* have around it, and a new thread is used for deserializing the copy.
*
*/
public static <T> T copyParallel(T original) {
try {
PipedOutputStream outputStream = new PipedOutputStream();
PipedInputStream inputStream = new PipedInputStream(outputStream);
ObjectOutputStream ois = new ObjectOutputStream(outputStream);
Receiver<T> receiver = new Receiver<T>(inputStream);
try {
ois.writeObject(original);
} finally {
ois.close();
}
return receiver.getResult();
} catch (IOException iox) {
// doesn't seem likely to happen as these streams are in memory
throw new RuntimeException(iox);
}
}
private static class Receiver<T> extends Thread {
private final InputStream inputStream;
private volatile T result;
private volatile Throwable throwable;
public Receiver(InputStream inputStream) {
this.inputStream = inputStream;
start();
}
@SuppressWarnings("unchecked")
public void run() {
try {
ObjectInputStream ois = new ObjectInputStream(inputStream);
try {
result = (T) ois.readObject();
try {
// Some serializers may write more than they actually
// need to deserialize the object, but if we don't
// read it all the PipedOutputStream will choke.
while (inputStream.read() != -1) {
}
} catch (IOException e) {
// The object has been successfully deserialized, so
// ignore problems at this point (for example, the
// serializer may have explicitly closed the inputStream
// itself, causing this read to fail).
}
} finally {
ois.close();
}
} catch (Throwable t) {
throwable = t;
}
}
public T getResult() throws IOException {
try {
join();
} catch (InterruptedException e) {
throw new RuntimeException("Unexpected InterruptedException", e);
}
// join() guarantees that all shared memory is synchronized between
// the two threads
if (throwable != null) {
if (throwable instanceof ClassNotFoundException) {
// actually can not happen in this instance
}
throw new RuntimeException(throwable);
}
return result;
}
}
}

+ 7
- 1
src/com/gitblit/wicket/GitBlitWebApp.properties View File

gb.dailyActivity = daily activity gb.dailyActivity = daily activity
gb.activeRepositories = active repositories gb.activeRepositories = active repositories
gb.activeAuthors = active authors gb.activeAuthors = active authors
gb.commits = commits
gb.commits = commits
gb.teams = teams
gb.teamName = team name
gb.teamMembers = team members
gb.teamMemberships = team memberships
gb.newTeam = new team
gb.permittedTeams = permitted teams

+ 8
- 0
src/com/gitblit/wicket/WicketUtils.java View File

return new PageParameters("user=" + username); return new PageParameters("user=" + username);
} }
public static PageParameters newTeamnameParameter(String teamname) {
return new PageParameters("team=" + teamname);
}
public static PageParameters newRepositoryParameter(String repositoryName) { public static PageParameters newRepositoryParameter(String repositoryName) {
return new PageParameters("r=" + repositoryName); return new PageParameters("r=" + repositoryName);
} }
return params.getString("user", ""); return params.getString("user", "");
} }
public static String getTeamname(PageParameters params) {
return params.getString("team", "");
}
public static String getToken(PageParameters params) { public static String getToken(PageParameters params) {
return params.getString("t", ""); return params.getString("t", "");
} }

+ 2
- 1
src/com/gitblit/wicket/pages/EditRepositoryPage.html View File

<wicket:extend> <wicket:extend>
<body onload="document.getElementById('name').focus();"> <body onload="document.getElementById('name').focus();">
<!-- Repository Table --> <!-- Repository Table -->
<form wicket:id="editForm">
<form style="padding-top:5px;" wicket:id="editForm">
<table class="plain"> <table class="plain">
<tbody> <tbody>
<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" /> &nbsp;<i><wicket:message key="gb.nameDescription"></wicket:message></i></td></tr> <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" /> &nbsp;<i><wicket:message key="gb.nameDescription"></wicket:message></i></td></tr>
<tr><td colspan="2"><hr></hr></td></tr> <tr><td colspan="2"><hr></hr></td></tr>
<tr><th><wicket:message key="gb.accessRestriction"></wicket:message></th><td class="edit"><select class="span6" wicket:id="accessRestriction" tabindex="12" /></td></tr> <tr><th><wicket:message key="gb.accessRestriction"></wicket:message></th><td class="edit"><select class="span6" wicket:id="accessRestriction" tabindex="12" /></td></tr>
<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> <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>
<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>
<tr><td colspan="2"><hr></hr></td></tr> <tr><td colspan="2"><hr></hr></td></tr>
<tr><th><wicket:message key="gb.federationStrategy"></wicket:message></th><td class="edit"><select class="span6" wicket:id="federationStrategy" tabindex="13" /></td></tr> <tr><th><wicket:message key="gb.federationStrategy"></wicket:message></th><td class="edit"><select class="span6" wicket:id="federationStrategy" tabindex="13" /></td></tr>
<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> <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>

+ 18
- 1
src/com/gitblit/wicket/pages/EditRepositoryPage.java View File

List<String> federationSets = new ArrayList<String>(); List<String> federationSets = new ArrayList<String>();
List<String> repositoryUsers = new ArrayList<String>(); List<String> repositoryUsers = new ArrayList<String>();
List<String> repositoryTeams = new ArrayList<String>();
if (isCreate) { if (isCreate) {
super.setupPage(getString("gb.newRepository"), ""); super.setupPage(getString("gb.newRepository"), "");
} else { } else {
super.setupPage(getString("gb.edit"), repositoryModel.name); super.setupPage(getString("gb.edit"), repositoryModel.name);
if (repositoryModel.accessRestriction.exceeds(AccessRestrictionType.NONE)) { if (repositoryModel.accessRestriction.exceeds(AccessRestrictionType.NONE)) {
repositoryUsers.addAll(GitBlit.self().getRepositoryUsers(repositoryModel)); repositoryUsers.addAll(GitBlit.self().getRepositoryUsers(repositoryModel));
repositoryTeams.addAll(GitBlit.self().getRepositoryTeams(repositoryModel));
Collections.sort(repositoryUsers); Collections.sort(repositoryUsers);
} }
federationSets.addAll(repositoryModel.federationSets); federationSets.addAll(repositoryModel.federationSets);
repositoryUsers), new CollectionModel<String>(GitBlit.self().getAllUsernames()), repositoryUsers), new CollectionModel<String>(GitBlit.self().getAllUsernames()),
new ChoiceRenderer<String>("", ""), 10, false); new ChoiceRenderer<String>("", ""), 10, false);
// teams palette
final Palette<String> teamsPalette = new Palette<String>("teams", new ListModel<String>(
repositoryTeams), new CollectionModel<String>(GitBlit.self().getAllTeamnames()),
new ChoiceRenderer<String>("", ""), 10, false);
// federation sets palette // federation sets palette
List<String> sets = GitBlit.getStrings(Keys.federation.sets); List<String> sets = GitBlit.getStrings(Keys.federation.sets);
final Palette<String> federationSetsPalette = new Palette<String>("federationSets", final Palette<String> federationSetsPalette = new Palette<String>("federationSets",
// save the repository // save the repository
GitBlit.self().updateRepositoryModel(oldName, repositoryModel, isCreate); GitBlit.self().updateRepositoryModel(oldName, repositoryModel, isCreate);
// save the repository access list
// repository access
if (repositoryModel.accessRestriction.exceeds(AccessRestrictionType.NONE)) { if (repositoryModel.accessRestriction.exceeds(AccessRestrictionType.NONE)) {
// save the user access list
Iterator<String> users = usersPalette.getSelectedChoices(); Iterator<String> users = usersPalette.getSelectedChoices();
List<String> repositoryUsers = new ArrayList<String>(); List<String> repositoryUsers = new ArrayList<String>();
while (users.hasNext()) { while (users.hasNext()) {
repositoryUsers.add(repositoryModel.owner); repositoryUsers.add(repositoryModel.owner);
} }
GitBlit.self().setRepositoryUsers(repositoryModel, repositoryUsers); GitBlit.self().setRepositoryUsers(repositoryModel, repositoryUsers);
// save the team access list
Iterator<String> teams = teamsPalette.getSelectedChoices();
List<String> repositoryTeams = new ArrayList<String>();
while (teams.hasNext()) {
repositoryTeams.add(teams.next());
}
GitBlit.self().setRepositoryTeams(repositoryModel, repositoryTeams);
} }
} catch (GitBlitException e) { } catch (GitBlitException e) {
error(e.getMessage()); error(e.getMessage());
form.add(new CheckBox("skipSizeCalculation")); form.add(new CheckBox("skipSizeCalculation"));
form.add(new CheckBox("skipSummaryMetrics")); form.add(new CheckBox("skipSummaryMetrics"));
form.add(usersPalette); form.add(usersPalette);
form.add(teamsPalette);
form.add(federationSetsPalette); form.add(federationSetsPalette);
form.add(new Button("save")); form.add(new Button("save"));

+ 24
- 0
src/com/gitblit/wicket/pages/EditTeamPage.html View File

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"
xml:lang="en"
lang="en">
<wicket:extend>
<body onload="document.getElementById('name').focus();">
<!-- User Table -->
<form style="padding-top:5px;" wicket:id="editForm">
<table class="plain">
<tbody>
<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>
<tr><td colspan="2"><hr></hr></td></tr>
<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>
<tr><td colspan="2"><hr></hr></td></tr>
<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>
<tr><th></th><td class="editButton"><input class="btn" type="submit" value="Cancel" wicket:message="value:gb.cancel" wicket:id="cancel" tabindex="3" /> &nbsp; <input class="btn primary" type="submit" value="Save" wicket:message="value:gb.save" wicket:id="save" tabindex="4" /></td></tr>
</tbody>
</table>
</form>
</body>
</wicket:extend>
</html>

+ 169
- 0
src/com/gitblit/wicket/pages/EditTeamPage.java View File

/*
* Copyright 2011 gitblit.com.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.gitblit.wicket.pages;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import org.apache.wicket.PageParameters;
import org.apache.wicket.extensions.markup.html.form.palette.Palette;
import org.apache.wicket.markup.html.form.Button;
import org.apache.wicket.markup.html.form.ChoiceRenderer;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.form.TextField;
import org.apache.wicket.model.CompoundPropertyModel;
import org.apache.wicket.model.util.CollectionModel;
import org.apache.wicket.model.util.ListModel;
import com.gitblit.Constants.AccessRestrictionType;
import com.gitblit.GitBlit;
import com.gitblit.GitBlitException;
import com.gitblit.models.RepositoryModel;
import com.gitblit.models.TeamModel;
import com.gitblit.utils.StringUtils;
import com.gitblit.wicket.RequiresAdminRole;
import com.gitblit.wicket.WicketUtils;
@RequiresAdminRole
public class EditTeamPage extends RootSubPage {
private final boolean isCreate;
public EditTeamPage() {
// create constructor
super();
isCreate = true;
setupPage(new TeamModel(""));
}
public EditTeamPage(PageParameters params) {
// edit constructor
super(params);
isCreate = false;
String name = WicketUtils.getTeamname(params);
TeamModel model = GitBlit.self().getTeamModel(name);
setupPage(model);
}
protected void setupPage(final TeamModel teamModel) {
if (isCreate) {
super.setupPage(getString("gb.newTeam"), "");
} else {
super.setupPage(getString("gb.edit"), teamModel.name);
}
CompoundPropertyModel<TeamModel> model = new CompoundPropertyModel<TeamModel>(teamModel);
List<String> repos = new ArrayList<String>();
for (String repo : GitBlit.self().getRepositoryList()) {
RepositoryModel repositoryModel = GitBlit.self().getRepositoryModel(repo);
if (repositoryModel.accessRestriction.exceeds(AccessRestrictionType.NONE)) {
repos.add(repo);
}
}
StringUtils.sortRepositorynames(repos);
List<String> teamUsers = new ArrayList<String>(teamModel.users);
Collections.sort(teamUsers);
final String oldName = teamModel.name;
final Palette<String> repositories = new Palette<String>("repositories",
new ListModel<String>(new ArrayList<String>(teamModel.repositories)),
new CollectionModel<String>(repos), new ChoiceRenderer<String>("", ""), 10, false);
final Palette<String> users = new Palette<String>("users", new ListModel<String>(
new ArrayList<String>(teamUsers)), new CollectionModel<String>(GitBlit.self()
.getAllUsernames()), new ChoiceRenderer<String>("", ""), 10, false);
Form<TeamModel> form = new Form<TeamModel>("editForm", model) {
private static final long serialVersionUID = 1L;
/*
* (non-Javadoc)
*
* @see org.apache.wicket.markup.html.form.Form#onSubmit()
*/
@Override
protected void onSubmit() {
String teamname = teamModel.name;
if (StringUtils.isEmpty(teamname)) {
error("Please enter a teamname!");
return;
}
if (isCreate) {
TeamModel model = GitBlit.self().getTeamModel(teamname);
if (model != null) {
error(MessageFormat.format("Team name ''{0}'' is unavailable.", teamname));
return;
}
}
Iterator<String> selectedRepositories = repositories.getSelectedChoices();
List<String> repos = new ArrayList<String>();
while (selectedRepositories.hasNext()) {
repos.add(selectedRepositories.next().toLowerCase());
}
teamModel.repositories.clear();
teamModel.repositories.addAll(repos);
Iterator<String> selectedUsers = users.getSelectedChoices();
List<String> members = new ArrayList<String>();
while (selectedUsers.hasNext()) {
members.add(selectedUsers.next().toLowerCase());
}
teamModel.users.clear();
teamModel.users.addAll(members);
try {
GitBlit.self().updateTeamModel(oldName, teamModel, isCreate);
} catch (GitBlitException e) {
error(e.getMessage());
return;
}
setRedirect(false);
if (isCreate) {
// create another team
info(MessageFormat.format("New team ''{0}'' successfully created.",
teamModel.name));
setResponsePage(EditTeamPage.class);
} else {
// back to users page
setResponsePage(UsersPage.class);
}
}
};
// field names reflective match TeamModel fields
form.add(new TextField<String>("name"));
form.add(repositories);
form.add(users);
form.add(new Button("save"));
Button cancel = new Button("cancel") {
private static final long serialVersionUID = 1L;
@Override
public void onSubmit() {
setResponsePage(UsersPage.class);
}
};
cancel.setDefaultFormProcessing(false);
form.add(cancel);
add(form);
}
}

+ 4
- 1
src/com/gitblit/wicket/pages/EditUserPage.html View File

<wicket:extend> <wicket:extend>
<body onload="document.getElementById('username').focus();"> <body onload="document.getElementById('username').focus();">
<!-- User Table --> <!-- User Table -->
<form wicket:id="editForm">
<form style="padding-top:5px;" wicket:id="editForm">
<table class="plain"> <table class="plain">
<tbody> <tbody>
<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> <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>
<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> <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>
<tr><th><wicket:message key="gb.canAdmin"></wicket:message></th><td class="edit"><input type="checkbox" wicket:id="canAdmin" tabindex="6" /> &nbsp;<i><wicket:message key="gb.canAdminDescription"></wicket:message></i></td></tr> <tr><th><wicket:message key="gb.canAdmin"></wicket:message></th><td class="edit"><input type="checkbox" wicket:id="canAdmin" tabindex="6" /> &nbsp;<i><wicket:message key="gb.canAdminDescription"></wicket:message></i></td></tr>
<tr><th><wicket:message key="gb.excludeFromFederation"></wicket:message></th><td class="edit"><input type="checkbox" wicket:id="excludeFromFederation" tabindex="7" /> &nbsp;<i><wicket:message key="gb.excludeFromFederationDescription"></wicket:message></i></td></tr> <tr><th><wicket:message key="gb.excludeFromFederation"></wicket:message></th><td class="edit"><input type="checkbox" wicket:id="excludeFromFederation" tabindex="7" /> &nbsp;<i><wicket:message key="gb.excludeFromFederationDescription"></wicket:message></i></td></tr>
<tr><td colspan="2"><hr></hr></td></tr>
<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>
<tr><td colspan="2"><hr></hr></td></tr>
<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> <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>
<tr><th></th><td class="editButton"><input class="btn" type="submit" value="Cancel" wicket:message="value:gb.cancel" wicket:id="cancel" tabindex="8" /> &nbsp; <input class="btn primary" type="submit" value="Save" wicket:message="value:gb.save" wicket:id="save" tabindex="9" /></td></tr> <tr><th></th><td class="editButton"><input class="btn" type="submit" value="Cancel" wicket:message="value:gb.cancel" wicket:id="cancel" tabindex="8" /> &nbsp; <input class="btn primary" type="submit" value="Save" wicket:message="value:gb.save" wicket:id="save" tabindex="9" /></td></tr>
</tbody> </tbody>

+ 25
- 1
src/com/gitblit/wicket/pages/EditUserPage.java View File

import java.text.MessageFormat; import java.text.MessageFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import com.gitblit.GitBlitException; import com.gitblit.GitBlitException;
import com.gitblit.Keys; import com.gitblit.Keys;
import com.gitblit.models.RepositoryModel; import com.gitblit.models.RepositoryModel;
import com.gitblit.models.TeamModel;
import com.gitblit.models.UserModel; import com.gitblit.models.UserModel;
import com.gitblit.utils.StringUtils; import com.gitblit.utils.StringUtils;
import com.gitblit.wicket.RequiresAdminRole; import com.gitblit.wicket.RequiresAdminRole;
repos.add(repo); repos.add(repo);
} }
} }
List<String> userTeams = new ArrayList<String>();
for (TeamModel team : userModel.teams) {
userTeams.add(team.name);
}
Collections.sort(userTeams);
final String oldName = userModel.username; final String oldName = userModel.username;
final Palette<String> repositories = new Palette<String>("repositories", final Palette<String> repositories = new Palette<String>("repositories",
new ListModel<String>(new ArrayList<String>(userModel.repositories)), new ListModel<String>(new ArrayList<String>(userModel.repositories)),
new CollectionModel<String>(repos), new ChoiceRenderer<String>("", ""), 10, false); new CollectionModel<String>(repos), new ChoiceRenderer<String>("", ""), 10, false);
final Palette<String> teams = new Palette<String>("teams", new ListModel<String>(
new ArrayList<String>(userTeams)), new CollectionModel<String>(GitBlit.self()
.getAllTeamnames()), new ChoiceRenderer<String>("", ""), 10, false);
Form<UserModel> form = new Form<UserModel>("editForm", model) { Form<UserModel> form = new Form<UserModel>("editForm", model) {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
return; return;
} }
} }
boolean rename = !StringUtils.isEmpty(oldName) && !oldName.equalsIgnoreCase(username);
boolean rename = !StringUtils.isEmpty(oldName)
&& !oldName.equalsIgnoreCase(username);
if (!userModel.password.equals(confirmPassword.getObject())) { if (!userModel.password.equals(confirmPassword.getObject())) {
error("Passwords do not match!"); error("Passwords do not match!");
return; return;
} }
userModel.repositories.clear(); userModel.repositories.clear();
userModel.repositories.addAll(repos); userModel.repositories.addAll(repos);
Iterator<String> selectedTeams = teams.getSelectedChoices();
userModel.teams.clear();
while (selectedTeams.hasNext()) {
TeamModel team = GitBlit.self().getTeamModel(selectedTeams.next());
if (team == null) {
continue;
}
userModel.teams.add(team);
}
try { try {
GitBlit.self().updateUserModel(oldName, userModel, isCreate); GitBlit.self().updateUserModel(oldName, userModel, isCreate);
} catch (GitBlitException e) { } catch (GitBlitException e) {
form.add(new CheckBox("canAdmin")); form.add(new CheckBox("canAdmin"));
form.add(new CheckBox("excludeFromFederation")); form.add(new CheckBox("excludeFromFederation"));
form.add(repositories); form.add(repositories);
form.add(teams);
form.add(new Button("save")); form.add(new Button("save"));
Button cancel = new Button("cancel") { Button cancel = new Button("cancel") {

+ 2
- 0
src/com/gitblit/wicket/pages/UsersPage.html View File

lang="en"> lang="en">
<body> <body>
<wicket:extend> <wicket:extend>
<div wicket:id="teamsPanel">[teams panel]</div>
<div wicket:id="usersPanel">[users panel]</div> <div wicket:id="usersPanel">[users panel]</div>
</wicket:extend> </wicket:extend>
</body> </body>

+ 3
- 0
src/com/gitblit/wicket/pages/UsersPage.java View File

package com.gitblit.wicket.pages; package com.gitblit.wicket.pages;
import com.gitblit.wicket.RequiresAdminRole; import com.gitblit.wicket.RequiresAdminRole;
import com.gitblit.wicket.panels.TeamsPanel;
import com.gitblit.wicket.panels.UsersPanel; import com.gitblit.wicket.panels.UsersPanel;
@RequiresAdminRole @RequiresAdminRole
super(); super();
setupPage("", ""); setupPage("", "");
add(new TeamsPanel("teamsPanel", showAdmin).setVisible(showAdmin));
add(new UsersPanel("usersPanel", showAdmin).setVisible(showAdmin)); add(new UsersPanel("usersPanel", showAdmin).setVisible(showAdmin));
} }
} }

+ 44
- 0
src/com/gitblit/wicket/panels/TeamsPanel.html View File

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"
xml:lang="en"
lang="en">
<body>
<wicket:panel>
<div wicket:id="adminPanel">[admin links]</div>
<table class="repositories">
<tr>
<th class="left">
<img style="vertical-align: middle; border: 1px solid #888; background-color: white;" src="users_16x16.png"/>
<wicket:message key="gb.teams">[teams]</wicket:message>
</th>
<th class="right"></th>
</tr>
<tbody>
<tr wicket:id="teamRow">
<td class="left" ><div class="list" wicket:id="teamname">[teamname]</div></td>
<td class="rightAlign"><span wicket:id="teamLinks"></span></td>
</tr>
</tbody>
</table>
<wicket:fragment wicket:id="adminLinks">
<!-- page nav links -->
<div class="admin_nav">
<img style="vertical-align: middle;" src="add_16x16.png"/>
<a wicket:id="newTeam">
<wicket:message key="gb.newTeam"></wicket:message>
</a>
</div>
</wicket:fragment>
<wicket:fragment wicket:id="teamAdminLinks">
<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>
</wicket:fragment>
</wicket:panel>
</body>
</html>

+ 89
- 0
src/com/gitblit/wicket/panels/TeamsPanel.java View File

/*
* Copyright 2011 gitblit.com.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.gitblit.wicket.panels;
import java.text.MessageFormat;
import java.util.List;
import org.apache.wicket.markup.html.link.BookmarkablePageLink;
import org.apache.wicket.markup.html.link.Link;
import org.apache.wicket.markup.html.panel.Fragment;
import org.apache.wicket.markup.repeater.Item;
import org.apache.wicket.markup.repeater.data.DataView;
import org.apache.wicket.markup.repeater.data.ListDataProvider;
import com.gitblit.GitBlit;
import com.gitblit.wicket.WicketUtils;
import com.gitblit.wicket.pages.EditTeamPage;
public class TeamsPanel extends BasePanel {
private static final long serialVersionUID = 1L;
public TeamsPanel(String wicketId, final boolean showAdmin) {
super(wicketId);
Fragment adminLinks = new Fragment("adminPanel", "adminLinks", this);
adminLinks.add(new BookmarkablePageLink<Void>("newTeam", EditTeamPage.class));
add(adminLinks.setVisible(showAdmin));
final List<String> teamnames = GitBlit.self().getAllTeamnames();
DataView<String> teamsView = new DataView<String>("teamRow", new ListDataProvider<String>(
teamnames)) {
private static final long serialVersionUID = 1L;
private int counter;
@Override
protected void onBeforeRender() {
super.onBeforeRender();
counter = 0;
}
public void populateItem(final Item<String> item) {
final String entry = item.getModelObject();
LinkPanel editLink = new LinkPanel("teamname", "list", entry, EditTeamPage.class,
WicketUtils.newTeamnameParameter(entry));
WicketUtils.setHtmlTooltip(editLink, getString("gb.edit") + " " + entry);
item.add(editLink);
Fragment teamLinks = new Fragment("teamLinks", "teamAdminLinks", this);
teamLinks.add(new BookmarkablePageLink<Void>("editTeam", EditTeamPage.class,
WicketUtils.newTeamnameParameter(entry)));
Link<Void> deleteLink = new Link<Void>("deleteTeam") {
private static final long serialVersionUID = 1L;
@Override
public void onClick() {
if (GitBlit.self().deleteTeam(entry)) {
teamnames.remove(entry);
info(MessageFormat.format("Team ''{0}'' deleted.", entry));
} else {
error(MessageFormat.format("Failed to delete team ''{0}''!", entry));
}
}
};
deleteLink.add(new JavascriptEventConfirmation("onclick", MessageFormat.format(
"Delete team \"{0}\"?", entry)));
teamLinks.add(deleteLink);
item.add(teamLinks);
WicketUtils.setAlternatingBackground(item, counter);
counter++;
}
};
add(teamsView.setVisible(showAdmin));
}
}

+ 1
- 1
src/com/gitblit/wicket/panels/UsersPanel.html View File

<tr> <tr>
<th class="left"> <th class="left">
<img style="vertical-align: middle; border: 1px solid #888; background-color: white;" src="user_16x16.png"/> <img style="vertical-align: middle; border: 1px solid #888; background-color: white;" src="user_16x16.png"/>
<wicket:message key="gb.username">[username]</wicket:message>
<wicket:message key="gb.users">[users]</wicket:message>
</th> </th>
<th class="right"></th> <th class="right"></th>
</tr> </tr>

+ 107
- 0
tests/com/gitblit/tests/UserServiceTest.java View File

file.delete(); file.delete();
IUserService service = new FileUserService(file); IUserService service = new FileUserService(file);
testUsers(service); testUsers(service);
testTeams(service);
file.delete(); file.delete();
} }
file.delete(); file.delete();
IUserService service = new ConfigUserService(file); IUserService service = new ConfigUserService(file);
testUsers(service); testUsers(service);
testTeams(service);
file.delete(); file.delete();
} }
testUser = service.getUserModel("test"); testUser = service.getUserModel("test");
assertTrue(testUser.hasRepository("newrepo1")); assertTrue(testUser.hasRepository("newrepo1"));
} }
protected void testTeams(IUserService service) {
// confirm we have no teams
assertEquals(0, service.getAllTeamNames().size());
// remove newrepo1 from test user
// now test user has no repositories
UserModel user = service.getUserModel("test");
user.repositories.clear();
service.updateUserModel(user);
user = service.getUserModel("test");
assertEquals(0, user.repositories.size());
assertFalse(user.canAccessRepository("newrepo1"));
assertFalse(user.canAccessRepository("NEWREPO1"));
// create test team and add test user and newrepo1
TeamModel team = new TeamModel("testteam");
team.addUser("test");
team.addRepository("newrepo1");
service.updateTeamModel(team);
// confirm 1 user and 1 repo
team = service.getTeamModel("testteam");
assertEquals(1, team.repositories.size());
assertEquals(1, team.users.size());
// confirm team membership
user = service.getUserModel("test");
assertEquals(0, user.repositories.size());
assertEquals(1, user.teams.size());
// confirm team access
assertTrue(team.hasRepository("newrepo1"));
assertTrue(user.hasTeamAccess("newrepo1"));
assertTrue(team.hasRepository("NEWREPO1"));
assertTrue(user.hasTeamAccess("NEWREPO1"));
// rename the team and add new repository
team.addRepository("newrepo2");
team.name = "testteam2";
service.updateTeamModel("testteam", team);
team = service.getTeamModel("testteam2");
user = service.getUserModel("test");
// confirm user and team can access newrepo2
assertEquals(2, team.repositories.size());
assertTrue(team.hasRepository("newrepo2"));
assertTrue(user.hasTeamAccess("newrepo2"));
assertTrue(team.hasRepository("NEWREPO2"));
assertTrue(user.hasTeamAccess("NEWREPO2"));
// delete testteam2
service.deleteTeam("testteam2");
team = service.getTeamModel("testteam2");
user = service.getUserModel("test");
// confirm team does not exist and user can not access newrepo1 and 2
assertEquals(null, team);
assertFalse(user.canAccessRepository("newrepo1"));
assertFalse(user.canAccessRepository("newrepo2"));
// create new team and add it to user
// this tests the inverse team creation/team addition
team = new TeamModel("testteam");
team.addRepository("NEWREPO1");
team.addRepository("NEWREPO2");
user.teams.add(team);
service.updateUserModel(user);
// confirm the inverted team addition
user = service.getUserModel("test");
team = service.getTeamModel("testteam");
assertTrue(user.hasTeamAccess("newrepo1"));
assertTrue(user.hasTeamAccess("newrepo2"));
assertTrue(team.hasUser("test"));
// drop testteam from user and add nextteam to user
team = new TeamModel("nextteam");
team.addRepository("NEWREPO1");
team.addRepository("NEWREPO2");
user.teams.clear();
user.teams.add(team);
service.updateUserModel(user);
// confirm implicit drop
user = service.getUserModel("test");
team = service.getTeamModel("testteam");
assertTrue(user.hasTeamAccess("newrepo1"));
assertTrue(user.hasTeamAccess("newrepo2"));
assertFalse(team.hasUser("test"));
team = service.getTeamModel("nextteam");
assertTrue(team.hasUser("test"));
// delete the user and confirm team no longer has user
service.deleteUser("test");
team = service.getTeamModel("testteam");
assertFalse(team.hasUser("test"));
// delete both teams
service.deleteTeam("testteam");
service.deleteTeam("nextteam");
assertEquals(0, service.getAllTeamNames().size());
}
} }

Loading…
Cancel
Save