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
- 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) |
#### 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 |
- 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* |
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); | ||||
} | } |
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; | |||||
} | |||||
} | } |
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")) { |
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. |
/* | |||||
* 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); | |||||
} | |||||
} |
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; |
/* | |||||
* 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; | |||||
} | |||||
} | |||||
} |
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 |
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", ""); | ||||
} | } |
<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" /> <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" /> <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> |
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")); |
<!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" /> <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> |
/* | |||||
* 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); | |||||
} | |||||
} |
<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" /> <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" /> <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" /> <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" /> <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" /> <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" /> <input class="btn primary" type="submit" value="Save" wicket:message="value:gb.save" wicket:id="save" tabindex="9" /></td></tr> | ||||
</tbody> | </tbody> |
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") { |
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> |
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)); | ||||
} | } | ||||
} | } |
<!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> |
/* | |||||
* 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)); | |||||
} | |||||
} |
<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> |
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()); | |||||
} | |||||
} | } |