src/com/gitblit/ConfigUserService.java | ●●●●● patch | view | raw | blame | history | |
src/com/gitblit/FileUserService.java | ●●●●● patch | view | raw | blame | history | |
src/com/gitblit/GitblitUserService.java | ●●●●● patch | view | raw | blame | history | |
src/com/gitblit/IUserService.java | ●●●●● patch | view | raw | blame | history | |
src/com/gitblit/LdapUserService.java | ●●●●● patch | view | raw | blame | history |
src/com/gitblit/ConfigUserService.java
@@ -1,1074 +1,1074 @@ /* * 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; import java.io.File; import java.io.IOException; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import org.eclipse.jgit.lib.StoredConfig; import org.eclipse.jgit.storage.file.FileBasedConfig; import org.eclipse.jgit.util.FS; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.gitblit.Constants.AccessPermission; import com.gitblit.models.TeamModel; import com.gitblit.models.UserModel; import com.gitblit.utils.ArrayUtils; import com.gitblit.utils.DeepCopier; import com.gitblit.utils.StringUtils; /** * ConfigUserService is Gitblit's default user service implementation since * version 0.8.0. * * Users and their repository memberships are stored in a git-style config file * which is cached and dynamically reloaded when modified. This file is * plain-text, human-readable, and may be edited with a text editor. * * Additionally, this format allows for expansion of the user model without * bringing in the complexity of a database. * * @author James Moger * */ 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 DISPLAYNAME = "displayName"; private static final String EMAILADDRESS = "emailAddress"; private static final String ORGANIZATIONALUNIT = "organizationalUnit"; private static final String ORGANIZATION = "organization"; private static final String LOCALITY = "locality"; private static final String STATEPROVINCE = "stateProvince"; private static final String COUNTRYCODE = "countryCode"; private static final String COOKIE = "cookie"; private static final String REPOSITORY = "repository"; private static final String ROLE = "role"; private static final String MAILINGLIST = "mailingList"; private static final String PRERECEIVE = "preReceiveScript"; private static final String POSTRECEIVE = "postReceiveScript"; private final File realmFile; private final Logger logger = LoggerFactory.getLogger(ConfigUserService.class); private final Map<String, UserModel> users = new ConcurrentHashMap<String, UserModel>(); private final Map<String, UserModel> cookies = new ConcurrentHashMap<String, UserModel>(); private final Map<String, TeamModel> teams = new ConcurrentHashMap<String, TeamModel>(); private volatile long lastModified; private volatile boolean forceReload; public ConfigUserService(File realmFile) { this.realmFile = realmFile; } /** * Setup the user service. * * @param settings * @since 0.7.0 */ @Override public void setup(IStoredSettings settings) { } /** * Does the user service support changes to credentials? * * @return true or false * @since 1.0.0 */ @Override public boolean supportsCredentialChanges() { return true; } /** * Does the user service support changes to user display name? * * @return true or false * @since 1.0.0 */ @Override public boolean supportsDisplayNameChanges() { return true; } /** * Does the user service support changes to user email address? * * @return true or false * @since 1.0.0 */ @Override public boolean supportsEmailAddressChanges() { return true; } /** * Does the user service support changes to team memberships? * * @return true or false * @since 1.0.0 */ public boolean supportsTeamMembershipChanges() { return true; } /** * Does the user service support cookie authentication? * * @return true or false */ @Override public boolean supportsCookies() { return true; } /** * Returns the cookie value for the specified user. * * @param model * @return cookie value */ @Override public String getCookie(UserModel model) { if (!StringUtils.isEmpty(model.cookie)) { return model.cookie; } read(); UserModel storedModel = users.get(model.username.toLowerCase()); return storedModel.cookie; } /** * Authenticate a user based on their cookie. * * @param cookie * @return a user object or null */ @Override public UserModel authenticate(char[] cookie) { String hash = new String(cookie); if (StringUtils.isEmpty(hash)) { return null; } read(); UserModel model = null; if (cookies.containsKey(hash)) { model = cookies.get(hash); } return model; } /** * Authenticate a user based on a username and password. * * @param username * @param password * @return a user object or null */ @Override public UserModel authenticate(String username, char[] password) { read(); UserModel returnedUser = null; UserModel user = getUserModel(username); if (user == null) { return null; } if (user.password.startsWith(StringUtils.MD5_TYPE)) { // password digest String md5 = StringUtils.MD5_TYPE + StringUtils.getMD5(new String(password)); if (user.password.equalsIgnoreCase(md5)) { returnedUser = user; } } else if (user.password.startsWith(StringUtils.COMBINED_MD5_TYPE)) { // username+password digest String md5 = StringUtils.COMBINED_MD5_TYPE + StringUtils.getMD5(username.toLowerCase() + new String(password)); if (user.password.equalsIgnoreCase(md5)) { returnedUser = user; } } else if (user.password.equals(new String(password))) { // plain-text password returnedUser = user; } return returnedUser; } /** * Logout a user. * * @param user */ @Override public void logout(UserModel user) { } /** * Retrieve the user object for the specified username. * * @param username * @return a user object or null */ @Override public UserModel getUserModel(String username) { read(); 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; } /** * Updates/writes a complete user object. * * @param model * @return true if update is successful */ @Override public boolean updateUserModel(UserModel model) { return updateUserModel(model.username, model); } /** * Updates/writes all specified user objects. * * @param models a list of user models * @return true if update is successful * @since 1.2.0 */ @Override public boolean updateUserModels(List<UserModel> models) { try { read(); for (UserModel model : models) { UserModel originalUser = users.remove(model.username.toLowerCase()); 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(model.username); teams.put(team.name.toLowerCase(), team); } else { // do not clobber existing team definition // maybe because this is a federated user t.addUser(model.username); } } // check for implicit team removal if (originalUser != null) { for (TeamModel team : originalUser.teams) { if (!model.isTeamMember(team.name)) { team.removeUser(model.username); } } } } } write(); return true; } catch (Throwable t) { logger.error(MessageFormat.format("Failed to update user {0} models!", models.size()), t); } return false; } /** * Updates/writes and replaces a complete 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 */ @Override public boolean updateUserModel(String username, UserModel model) { UserModel originalUser = null; try { read(); originalUser = users.remove(username.toLowerCase()); 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 (originalUser != null) { for (TeamModel team : originalUser.teams) { if (!model.isTeamMember(team.name)) { team.removeUser(username); } } } } write(); return true; } catch (Throwable t) { if (originalUser != null) { // restore original user users.put(originalUser.username.toLowerCase(), originalUser); } else { // drop attempted add users.remove(model.username.toLowerCase()); } logger.error(MessageFormat.format("Failed to update user model {0}!", model.username), t); } return false; } /** * Deletes the user object from the user service. * * @param model * @return true if successful */ @Override public boolean deleteUserModel(UserModel model) { return deleteUser(model.username); } /** * Delete the user object with the specified username * * @param username * @return true if successful */ @Override public boolean deleteUser(String username) { try { // Read realm file read(); 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(); return true; } catch (Throwable t) { logger.error(MessageFormat.format("Failed to delete user {0}!", username), t); } 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()); Collections.sort(list); return list; } /** * Returns the list of all teams available to the login service. * * @return list of all teams * @since 0.8.0 */ @Override public List<TeamModel> getAllTeams() { read(); List<TeamModel> list = new ArrayList<TeamModel>(teams.values()); list = DeepCopier.copy(list); Collections.sort(list); 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.hasRepositoryPermission(role)) { list.add(model.name); } } } catch (Throwable t) { logger.error(MessageFormat.format("Failed to get teamnames for role {0}!", role), t); } Collections.sort(list); 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.addRepositoryPermission(role); } else { // remove role from team team.removeRepositoryPermission(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 all specified team objects. * * @param models a list of team models * @return true if update is successful * @since 1.2.0 */ @Override public boolean updateTeamModels(List<TeamModel> models) { try { read(); for (TeamModel team : models) { teams.put(team.name.toLowerCase(), team); } write(); return true; } catch (Throwable t) { logger.error(MessageFormat.format("Failed to update team {0} models!", models.size()), t); } return false; } /** * 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) { TeamModel original = null; try { read(); original = teams.remove(teamname.toLowerCase()); teams.put(model.name.toLowerCase(), model); write(); return true; } catch (Throwable t) { if (original != null) { // restore original team teams.put(original.name.toLowerCase(), original); } else { // drop attempted add teams.remove(model.name.toLowerCase()); } 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. * * @return list of all usernames */ @Override public List<String> getAllUsernames() { read(); List<String> list = new ArrayList<String>(users.keySet()); Collections.sort(list); return list; } /** * Returns the list of all users available to the login service. * * @return list of all usernames */ @Override public List<UserModel> getAllUsers() { read(); List<UserModel> list = new ArrayList<UserModel>(users.values()); list = DeepCopier.copy(list); Collections.sort(list); 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> getUsernamesForRepositoryRole(String role) { List<String> list = new ArrayList<String>(); try { read(); for (Map.Entry<String, UserModel> entry : users.entrySet()) { UserModel model = entry.getValue(); if (model.hasRepositoryPermission(role)) { list.add(model.username); } } } catch (Throwable t) { logger.error(MessageFormat.format("Failed to get usernames for role {0}!", role), t); } Collections.sort(list); return list; } /** * 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 */ @Override @Deprecated public boolean setUsernamesForRepositoryRole(String role, List<String> usernames) { try { Set<String> specifiedUsers = new HashSet<String>(); for (String username : usernames) { specifiedUsers.add(username.toLowerCase()); } read(); // identify users which require add or remove role for (UserModel user : users.values()) { // user has role, check against revised user list if (specifiedUsers.contains(user.username.toLowerCase())) { user.addRepositoryPermission(role); } else { // remove role from user user.removeRepositoryPermission(role); } } // persist changes write(); return true; } catch (Throwable t) { logger.error(MessageFormat.format("Failed to set usernames for role {0}!", role), t); } return false; } /** * Renames a repository role. * * @param oldRole * @param newRole * @return true if successful */ @Override public boolean renameRepositoryRole(String oldRole, String newRole) { try { read(); // identify users which require role rename for (UserModel model : users.values()) { if (model.hasRepositoryPermission(oldRole)) { AccessPermission permission = model.removeRepositoryPermission(oldRole); model.setRepositoryPermission(newRole, permission); } } // identify teams which require role rename for (TeamModel model : teams.values()) { if (model.hasRepositoryPermission(oldRole)) { AccessPermission permission = model.removeRepositoryPermission(oldRole); model.setRepositoryPermission(newRole, permission); } } // persist changes write(); return true; } catch (Throwable t) { logger.error( MessageFormat.format("Failed to rename role {0} to {1}!", oldRole, newRole), t); } return false; } /** * Removes a repository role from all users. * * @param role * @return true if successful */ @Override public boolean deleteRepositoryRole(String role) { try { read(); // identify users which require role rename for (UserModel user : users.values()) { user.removeRepositoryPermission(role); } // identify teams which require role rename for (TeamModel team : teams.values()) { team.removeRepositoryPermission(role); } // persist changes write(); return true; } catch (Throwable t) { logger.error(MessageFormat.format("Failed to delete role {0}!", role), t); } return false; } /** * Writes the properties file. * * @param properties * @throws IOException */ private synchronized void write() throws IOException { // Write a temporary copy of the users file File realmFileCopy = new File(realmFile.getAbsolutePath() + ".tmp"); StoredConfig config = new FileBasedConfig(realmFileCopy, FS.detect()); // write users for (UserModel model : users.values()) { if (!StringUtils.isEmpty(model.password)) { config.setString(USER, model.username, PASSWORD, model.password); } if (!StringUtils.isEmpty(model.cookie)) { config.setString(USER, model.username, COOKIE, model.cookie); } if (!StringUtils.isEmpty(model.displayName)) { config.setString(USER, model.username, DISPLAYNAME, model.displayName); } if (!StringUtils.isEmpty(model.emailAddress)) { config.setString(USER, model.username, EMAILADDRESS, model.emailAddress); } if (!StringUtils.isEmpty(model.organizationalUnit)) { config.setString(USER, model.username, ORGANIZATIONALUNIT, model.organizationalUnit); } if (!StringUtils.isEmpty(model.organization)) { config.setString(USER, model.username, ORGANIZATION, model.organization); } if (!StringUtils.isEmpty(model.locality)) { config.setString(USER, model.username, LOCALITY, model.locality); } if (!StringUtils.isEmpty(model.stateProvince)) { config.setString(USER, model.username, STATEPROVINCE, model.stateProvince); } if (!StringUtils.isEmpty(model.countryCode)) { config.setString(USER, model.username, COUNTRYCODE, model.countryCode); } // user roles List<String> roles = new ArrayList<String>(); if (model.canAdmin) { roles.add(Constants.ADMIN_ROLE); } if (model.canFork) { roles.add(Constants.FORK_ROLE); } if (model.canCreate) { roles.add(Constants.CREATE_ROLE); } if (model.excludeFromFederation) { roles.add(Constants.NOT_FEDERATED_ROLE); } if (roles.size() == 0) { // we do this to ensure that user record with no password // is written. otherwise, StoredConfig optimizes that account // away. :( roles.add(Constants.NO_ROLE); } config.setStringList(USER, model.username, ROLE, roles); // discrete repository permissions if (model.permissions != null && !model.canAdmin) { List<String> permissions = new ArrayList<String>(); for (Map.Entry<String, AccessPermission> entry : model.permissions.entrySet()) { if (entry.getValue().exceeds(AccessPermission.NONE)) { permissions.add(entry.getValue().asRole(entry.getKey())); } } config.setStringList(USER, model.username, REPOSITORY, permissions); } } // write teams for (TeamModel model : teams.values()) { // team roles List<String> roles = new ArrayList<String>(); if (model.canAdmin) { roles.add(Constants.ADMIN_ROLE); } if (model.canFork) { roles.add(Constants.FORK_ROLE); } if (model.canCreate) { roles.add(Constants.CREATE_ROLE); } if (roles.size() == 0) { // we do this to ensure that team record is written. // Otherwise, StoredConfig might optimizes that record away. roles.add(Constants.NO_ROLE); } config.setStringList(TEAM, model.name, ROLE, roles); if (!model.canAdmin) { // write team permission for non-admin teams if (model.permissions == null) { // null check on "final" repositories because JSON-sourced TeamModel // can have a null repositories object if (!ArrayUtils.isEmpty(model.repositories)) { config.setStringList(TEAM, model.name, REPOSITORY, new ArrayList<String>( model.repositories)); } } else { // discrete repository permissions List<String> permissions = new ArrayList<String>(); for (Map.Entry<String, AccessPermission> entry : model.permissions.entrySet()) { if (entry.getValue().exceeds(AccessPermission.NONE)) { // code:repository (e.g. RW+:~james/myrepo.git permissions.add(entry.getValue().asRole(entry.getKey())); } } config.setStringList(TEAM, model.name, REPOSITORY, permissions); } } // null check on "final" users because JSON-sourced TeamModel // can have a null users object if (!ArrayUtils.isEmpty(model.users)) { config.setStringList(TEAM, model.name, USER, new ArrayList<String>(model.users)); } // null check on "final" mailing lists because JSON-sourced // TeamModel can have a null users object if (!ArrayUtils.isEmpty(model.mailingLists)) { config.setStringList(TEAM, model.name, MAILINGLIST, new ArrayList<String>( model.mailingLists)); } // null check on "final" preReceiveScripts because JSON-sourced // TeamModel can have a null preReceiveScripts object if (!ArrayUtils.isEmpty(model.preReceiveScripts)) { config.setStringList(TEAM, model.name, PRERECEIVE, model.preReceiveScripts); } // null check on "final" postReceiveScripts because JSON-sourced // TeamModel can have a null postReceiveScripts object if (!ArrayUtils.isEmpty(model.postReceiveScripts)) { config.setStringList(TEAM, model.name, POSTRECEIVE, model.postReceiveScripts); } } config.save(); // manually set the forceReload flag because not all JVMs support real // millisecond resolution of lastModified. (issue-55) forceReload = true; // If the write is successful, delete the current file and rename // the temporary copy to the original filename. if (realmFileCopy.exists() && realmFileCopy.length() > 0) { if (realmFile.exists()) { if (!realmFile.delete()) { throw new IOException(MessageFormat.format("Failed to delete {0}!", realmFile.getAbsolutePath())); } } if (!realmFileCopy.renameTo(realmFile)) { throw new IOException(MessageFormat.format("Failed to rename {0} to {1}!", realmFileCopy.getAbsolutePath(), realmFile.getAbsolutePath())); } } else { throw new IOException(MessageFormat.format("Failed to save {0}!", realmFileCopy.getAbsolutePath())); } } /** * Reads the realm file and rebuilds the in-memory lookup tables. */ protected synchronized void read() { if (realmFile.exists() && (forceReload || (realmFile.lastModified() != lastModified))) { forceReload = false; lastModified = realmFile.lastModified(); users.clear(); cookies.clear(); teams.clear(); try { StoredConfig config = new FileBasedConfig(realmFile, FS.detect()); config.load(); Set<String> usernames = config.getSubsections(USER); for (String username : usernames) { UserModel user = new UserModel(username.toLowerCase()); user.password = config.getString(USER, username, PASSWORD); user.displayName = config.getString(USER, username, DISPLAYNAME); user.emailAddress = config.getString(USER, username, EMAILADDRESS); user.organizationalUnit = config.getString(USER, username, ORGANIZATIONALUNIT); user.organization = config.getString(USER, username, ORGANIZATION); user.locality = config.getString(USER, username, LOCALITY); user.stateProvince = config.getString(USER, username, STATEPROVINCE); user.countryCode = config.getString(USER, username, COUNTRYCODE); user.cookie = config.getString(USER, username, COOKIE); if (StringUtils.isEmpty(user.cookie) && !StringUtils.isEmpty(user.password)) { user.cookie = StringUtils.getSHA1(user.username + user.password); } // user roles Set<String> roles = new HashSet<String>(Arrays.asList(config.getStringList( USER, username, ROLE))); user.canAdmin = roles.contains(Constants.ADMIN_ROLE); user.canFork = roles.contains(Constants.FORK_ROLE); user.canCreate = roles.contains(Constants.CREATE_ROLE); user.excludeFromFederation = roles.contains(Constants.NOT_FEDERATED_ROLE); // repository memberships if (!user.canAdmin) { // non-admin, read permissions Set<String> repositories = new HashSet<String>(Arrays.asList(config .getStringList(USER, username, REPOSITORY))); for (String repository : repositories) { user.addRepositoryPermission(repository); } } // update cache users.put(user.username, user); if (!StringUtils.isEmpty(user.cookie)) { cookies.put(user.cookie, user); } } // load the teams Set<String> teamnames = config.getSubsections(TEAM); for (String teamname : teamnames) { TeamModel team = new TeamModel(teamname); Set<String> roles = new HashSet<String>(Arrays.asList(config.getStringList( TEAM, teamname, ROLE))); team.canAdmin = roles.contains(Constants.ADMIN_ROLE); team.canFork = roles.contains(Constants.FORK_ROLE); team.canCreate = roles.contains(Constants.CREATE_ROLE); if (!team.canAdmin) { // non-admin team, read permissions team.addRepositoryPermissions(Arrays.asList(config.getStringList(TEAM, teamname, REPOSITORY))); } team.addUsers(Arrays.asList(config.getStringList(TEAM, teamname, USER))); team.addMailingLists(Arrays.asList(config.getStringList(TEAM, teamname, MAILINGLIST))); team.preReceiveScripts.addAll(Arrays.asList(config.getStringList(TEAM, teamname, PRERECEIVE))); team.postReceiveScripts.addAll(Arrays.asList(config.getStringList(TEAM, teamname, POSTRECEIVE))); 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) { logger.error(MessageFormat.format("Failed to read {0}", realmFile), e); } } } protected long lastModified() { return lastModified; } @Override public String toString() { return getClass().getSimpleName() + "(" + realmFile.getAbsolutePath() + ")"; } } /* * 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; import java.io.File; import java.io.IOException; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import org.eclipse.jgit.lib.StoredConfig; import org.eclipse.jgit.storage.file.FileBasedConfig; import org.eclipse.jgit.util.FS; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.gitblit.Constants.AccessPermission; import com.gitblit.models.TeamModel; import com.gitblit.models.UserModel; import com.gitblit.utils.ArrayUtils; import com.gitblit.utils.DeepCopier; import com.gitblit.utils.StringUtils; /** * ConfigUserService is Gitblit's default user service implementation since * version 0.8.0. * * Users and their repository memberships are stored in a git-style config file * which is cached and dynamically reloaded when modified. This file is * plain-text, human-readable, and may be edited with a text editor. * * Additionally, this format allows for expansion of the user model without * bringing in the complexity of a database. * * @author James Moger * */ 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 DISPLAYNAME = "displayName"; private static final String EMAILADDRESS = "emailAddress"; private static final String ORGANIZATIONALUNIT = "organizationalUnit"; private static final String ORGANIZATION = "organization"; private static final String LOCALITY = "locality"; private static final String STATEPROVINCE = "stateProvince"; private static final String COUNTRYCODE = "countryCode"; private static final String COOKIE = "cookie"; private static final String REPOSITORY = "repository"; private static final String ROLE = "role"; private static final String MAILINGLIST = "mailingList"; private static final String PRERECEIVE = "preReceiveScript"; private static final String POSTRECEIVE = "postReceiveScript"; private final File realmFile; private final Logger logger = LoggerFactory.getLogger(ConfigUserService.class); private final Map<String, UserModel> users = new ConcurrentHashMap<String, UserModel>(); private final Map<String, UserModel> cookies = new ConcurrentHashMap<String, UserModel>(); private final Map<String, TeamModel> teams = new ConcurrentHashMap<String, TeamModel>(); private volatile long lastModified; private volatile boolean forceReload; public ConfigUserService(File realmFile) { this.realmFile = realmFile; } /** * Setup the user service. * * @param settings * @since 0.7.0 */ @Override public void setup(IStoredSettings settings) { } /** * Does the user service support changes to credentials? * * @return true or false * @since 1.0.0 */ @Override public boolean supportsCredentialChanges() { return true; } /** * Does the user service support changes to user display name? * * @return true or false * @since 1.0.0 */ @Override public boolean supportsDisplayNameChanges() { return true; } /** * Does the user service support changes to user email address? * * @return true or false * @since 1.0.0 */ @Override public boolean supportsEmailAddressChanges() { return true; } /** * Does the user service support changes to team memberships? * * @return true or false * @since 1.0.0 */ public boolean supportsTeamMembershipChanges() { return true; } /** * Does the user service support cookie authentication? * * @return true or false */ @Override public boolean supportsCookies() { return true; } /** * Returns the cookie value for the specified user. * * @param model * @return cookie value */ @Override public String getCookie(UserModel model) { if (!StringUtils.isEmpty(model.cookie)) { return model.cookie; } read(); UserModel storedModel = users.get(model.username.toLowerCase()); return storedModel.cookie; } /** * Authenticate a user based on their cookie. * * @param cookie * @return a user object or null */ @Override public UserModel authenticate(char[] cookie) { String hash = new String(cookie); if (StringUtils.isEmpty(hash)) { return null; } read(); UserModel model = null; if (cookies.containsKey(hash)) { model = cookies.get(hash); } return model; } /** * Authenticate a user based on a username and password. * * @param username * @param password * @return a user object or null */ @Override public UserModel authenticate(String username, char[] password) { read(); UserModel returnedUser = null; UserModel user = getUserModel(username); if (user == null) { return null; } if (user.password.startsWith(StringUtils.MD5_TYPE)) { // password digest String md5 = StringUtils.MD5_TYPE + StringUtils.getMD5(new String(password)); if (user.password.equalsIgnoreCase(md5)) { returnedUser = user; } } else if (user.password.startsWith(StringUtils.COMBINED_MD5_TYPE)) { // username+password digest String md5 = StringUtils.COMBINED_MD5_TYPE + StringUtils.getMD5(username.toLowerCase() + new String(password)); if (user.password.equalsIgnoreCase(md5)) { returnedUser = user; } } else if (user.password.equals(new String(password))) { // plain-text password returnedUser = user; } return returnedUser; } /** * Logout a user. * * @param user */ @Override public void logout(UserModel user) { } /** * Retrieve the user object for the specified username. * * @param username * @return a user object or null */ @Override public UserModel getUserModel(String username) { read(); 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; } /** * Updates/writes a complete user object. * * @param model * @return true if update is successful */ @Override public boolean updateUserModel(UserModel model) { return updateUserModel(model.username, model); } /** * Updates/writes all specified user objects. * * @param models a list of user models * @return true if update is successful * @since 1.2.0 */ @Override public boolean updateUserModels(Collection<UserModel> models) { try { read(); for (UserModel model : models) { UserModel originalUser = users.remove(model.username.toLowerCase()); 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(model.username); teams.put(team.name.toLowerCase(), team); } else { // do not clobber existing team definition // maybe because this is a federated user t.addUser(model.username); } } // check for implicit team removal if (originalUser != null) { for (TeamModel team : originalUser.teams) { if (!model.isTeamMember(team.name)) { team.removeUser(model.username); } } } } } write(); return true; } catch (Throwable t) { logger.error(MessageFormat.format("Failed to update user {0} models!", models.size()), t); } return false; } /** * Updates/writes and replaces a complete 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 */ @Override public boolean updateUserModel(String username, UserModel model) { UserModel originalUser = null; try { read(); originalUser = users.remove(username.toLowerCase()); 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 (originalUser != null) { for (TeamModel team : originalUser.teams) { if (!model.isTeamMember(team.name)) { team.removeUser(username); } } } } write(); return true; } catch (Throwable t) { if (originalUser != null) { // restore original user users.put(originalUser.username.toLowerCase(), originalUser); } else { // drop attempted add users.remove(model.username.toLowerCase()); } logger.error(MessageFormat.format("Failed to update user model {0}!", model.username), t); } return false; } /** * Deletes the user object from the user service. * * @param model * @return true if successful */ @Override public boolean deleteUserModel(UserModel model) { return deleteUser(model.username); } /** * Delete the user object with the specified username * * @param username * @return true if successful */ @Override public boolean deleteUser(String username) { try { // Read realm file read(); 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(); return true; } catch (Throwable t) { logger.error(MessageFormat.format("Failed to delete user {0}!", username), t); } 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()); Collections.sort(list); return list; } /** * Returns the list of all teams available to the login service. * * @return list of all teams * @since 0.8.0 */ @Override public List<TeamModel> getAllTeams() { read(); List<TeamModel> list = new ArrayList<TeamModel>(teams.values()); list = DeepCopier.copy(list); Collections.sort(list); 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.hasRepositoryPermission(role)) { list.add(model.name); } } } catch (Throwable t) { logger.error(MessageFormat.format("Failed to get teamnames for role {0}!", role), t); } Collections.sort(list); 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.addRepositoryPermission(role); } else { // remove role from team team.removeRepositoryPermission(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 all specified team objects. * * @param models a list of team models * @return true if update is successful * @since 1.2.0 */ @Override public boolean updateTeamModels(Collection<TeamModel> models) { try { read(); for (TeamModel team : models) { teams.put(team.name.toLowerCase(), team); } write(); return true; } catch (Throwable t) { logger.error(MessageFormat.format("Failed to update team {0} models!", models.size()), t); } return false; } /** * 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) { TeamModel original = null; try { read(); original = teams.remove(teamname.toLowerCase()); teams.put(model.name.toLowerCase(), model); write(); return true; } catch (Throwable t) { if (original != null) { // restore original team teams.put(original.name.toLowerCase(), original); } else { // drop attempted add teams.remove(model.name.toLowerCase()); } 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. * * @return list of all usernames */ @Override public List<String> getAllUsernames() { read(); List<String> list = new ArrayList<String>(users.keySet()); Collections.sort(list); return list; } /** * Returns the list of all users available to the login service. * * @return list of all usernames */ @Override public List<UserModel> getAllUsers() { read(); List<UserModel> list = new ArrayList<UserModel>(users.values()); list = DeepCopier.copy(list); Collections.sort(list); 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> getUsernamesForRepositoryRole(String role) { List<String> list = new ArrayList<String>(); try { read(); for (Map.Entry<String, UserModel> entry : users.entrySet()) { UserModel model = entry.getValue(); if (model.hasRepositoryPermission(role)) { list.add(model.username); } } } catch (Throwable t) { logger.error(MessageFormat.format("Failed to get usernames for role {0}!", role), t); } Collections.sort(list); return list; } /** * 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 */ @Override @Deprecated public boolean setUsernamesForRepositoryRole(String role, List<String> usernames) { try { Set<String> specifiedUsers = new HashSet<String>(); for (String username : usernames) { specifiedUsers.add(username.toLowerCase()); } read(); // identify users which require add or remove role for (UserModel user : users.values()) { // user has role, check against revised user list if (specifiedUsers.contains(user.username.toLowerCase())) { user.addRepositoryPermission(role); } else { // remove role from user user.removeRepositoryPermission(role); } } // persist changes write(); return true; } catch (Throwable t) { logger.error(MessageFormat.format("Failed to set usernames for role {0}!", role), t); } return false; } /** * Renames a repository role. * * @param oldRole * @param newRole * @return true if successful */ @Override public boolean renameRepositoryRole(String oldRole, String newRole) { try { read(); // identify users which require role rename for (UserModel model : users.values()) { if (model.hasRepositoryPermission(oldRole)) { AccessPermission permission = model.removeRepositoryPermission(oldRole); model.setRepositoryPermission(newRole, permission); } } // identify teams which require role rename for (TeamModel model : teams.values()) { if (model.hasRepositoryPermission(oldRole)) { AccessPermission permission = model.removeRepositoryPermission(oldRole); model.setRepositoryPermission(newRole, permission); } } // persist changes write(); return true; } catch (Throwable t) { logger.error( MessageFormat.format("Failed to rename role {0} to {1}!", oldRole, newRole), t); } return false; } /** * Removes a repository role from all users. * * @param role * @return true if successful */ @Override public boolean deleteRepositoryRole(String role) { try { read(); // identify users which require role rename for (UserModel user : users.values()) { user.removeRepositoryPermission(role); } // identify teams which require role rename for (TeamModel team : teams.values()) { team.removeRepositoryPermission(role); } // persist changes write(); return true; } catch (Throwable t) { logger.error(MessageFormat.format("Failed to delete role {0}!", role), t); } return false; } /** * Writes the properties file. * * @throws IOException */ private synchronized void write() throws IOException { // Write a temporary copy of the users file File realmFileCopy = new File(realmFile.getAbsolutePath() + ".tmp"); StoredConfig config = new FileBasedConfig(realmFileCopy, FS.detect()); // write users for (UserModel model : users.values()) { if (!StringUtils.isEmpty(model.password)) { config.setString(USER, model.username, PASSWORD, model.password); } if (!StringUtils.isEmpty(model.cookie)) { config.setString(USER, model.username, COOKIE, model.cookie); } if (!StringUtils.isEmpty(model.displayName)) { config.setString(USER, model.username, DISPLAYNAME, model.displayName); } if (!StringUtils.isEmpty(model.emailAddress)) { config.setString(USER, model.username, EMAILADDRESS, model.emailAddress); } if (!StringUtils.isEmpty(model.organizationalUnit)) { config.setString(USER, model.username, ORGANIZATIONALUNIT, model.organizationalUnit); } if (!StringUtils.isEmpty(model.organization)) { config.setString(USER, model.username, ORGANIZATION, model.organization); } if (!StringUtils.isEmpty(model.locality)) { config.setString(USER, model.username, LOCALITY, model.locality); } if (!StringUtils.isEmpty(model.stateProvince)) { config.setString(USER, model.username, STATEPROVINCE, model.stateProvince); } if (!StringUtils.isEmpty(model.countryCode)) { config.setString(USER, model.username, COUNTRYCODE, model.countryCode); } // user roles List<String> roles = new ArrayList<String>(); if (model.canAdmin) { roles.add(Constants.ADMIN_ROLE); } if (model.canFork) { roles.add(Constants.FORK_ROLE); } if (model.canCreate) { roles.add(Constants.CREATE_ROLE); } if (model.excludeFromFederation) { roles.add(Constants.NOT_FEDERATED_ROLE); } if (roles.size() == 0) { // we do this to ensure that user record with no password // is written. otherwise, StoredConfig optimizes that account // away. :( roles.add(Constants.NO_ROLE); } config.setStringList(USER, model.username, ROLE, roles); // discrete repository permissions if (model.permissions != null && !model.canAdmin) { List<String> permissions = new ArrayList<String>(); for (Map.Entry<String, AccessPermission> entry : model.permissions.entrySet()) { if (entry.getValue().exceeds(AccessPermission.NONE)) { permissions.add(entry.getValue().asRole(entry.getKey())); } } config.setStringList(USER, model.username, REPOSITORY, permissions); } } // write teams for (TeamModel model : teams.values()) { // team roles List<String> roles = new ArrayList<String>(); if (model.canAdmin) { roles.add(Constants.ADMIN_ROLE); } if (model.canFork) { roles.add(Constants.FORK_ROLE); } if (model.canCreate) { roles.add(Constants.CREATE_ROLE); } if (roles.size() == 0) { // we do this to ensure that team record is written. // Otherwise, StoredConfig might optimizes that record away. roles.add(Constants.NO_ROLE); } config.setStringList(TEAM, model.name, ROLE, roles); if (!model.canAdmin) { // write team permission for non-admin teams if (model.permissions == null) { // null check on "final" repositories because JSON-sourced TeamModel // can have a null repositories object if (!ArrayUtils.isEmpty(model.repositories)) { config.setStringList(TEAM, model.name, REPOSITORY, new ArrayList<String>( model.repositories)); } } else { // discrete repository permissions List<String> permissions = new ArrayList<String>(); for (Map.Entry<String, AccessPermission> entry : model.permissions.entrySet()) { if (entry.getValue().exceeds(AccessPermission.NONE)) { // code:repository (e.g. RW+:~james/myrepo.git permissions.add(entry.getValue().asRole(entry.getKey())); } } config.setStringList(TEAM, model.name, REPOSITORY, permissions); } } // null check on "final" users because JSON-sourced TeamModel // can have a null users object if (!ArrayUtils.isEmpty(model.users)) { config.setStringList(TEAM, model.name, USER, new ArrayList<String>(model.users)); } // null check on "final" mailing lists because JSON-sourced // TeamModel can have a null users object if (!ArrayUtils.isEmpty(model.mailingLists)) { config.setStringList(TEAM, model.name, MAILINGLIST, new ArrayList<String>( model.mailingLists)); } // null check on "final" preReceiveScripts because JSON-sourced // TeamModel can have a null preReceiveScripts object if (!ArrayUtils.isEmpty(model.preReceiveScripts)) { config.setStringList(TEAM, model.name, PRERECEIVE, model.preReceiveScripts); } // null check on "final" postReceiveScripts because JSON-sourced // TeamModel can have a null postReceiveScripts object if (!ArrayUtils.isEmpty(model.postReceiveScripts)) { config.setStringList(TEAM, model.name, POSTRECEIVE, model.postReceiveScripts); } } config.save(); // manually set the forceReload flag because not all JVMs support real // millisecond resolution of lastModified. (issue-55) forceReload = true; // If the write is successful, delete the current file and rename // the temporary copy to the original filename. if (realmFileCopy.exists() && realmFileCopy.length() > 0) { if (realmFile.exists()) { if (!realmFile.delete()) { throw new IOException(MessageFormat.format("Failed to delete {0}!", realmFile.getAbsolutePath())); } } if (!realmFileCopy.renameTo(realmFile)) { throw new IOException(MessageFormat.format("Failed to rename {0} to {1}!", realmFileCopy.getAbsolutePath(), realmFile.getAbsolutePath())); } } else { throw new IOException(MessageFormat.format("Failed to save {0}!", realmFileCopy.getAbsolutePath())); } } /** * Reads the realm file and rebuilds the in-memory lookup tables. */ protected synchronized void read() { if (realmFile.exists() && (forceReload || (realmFile.lastModified() != lastModified))) { forceReload = false; lastModified = realmFile.lastModified(); users.clear(); cookies.clear(); teams.clear(); try { StoredConfig config = new FileBasedConfig(realmFile, FS.detect()); config.load(); Set<String> usernames = config.getSubsections(USER); for (String username : usernames) { UserModel user = new UserModel(username.toLowerCase()); user.password = config.getString(USER, username, PASSWORD); user.displayName = config.getString(USER, username, DISPLAYNAME); user.emailAddress = config.getString(USER, username, EMAILADDRESS); user.organizationalUnit = config.getString(USER, username, ORGANIZATIONALUNIT); user.organization = config.getString(USER, username, ORGANIZATION); user.locality = config.getString(USER, username, LOCALITY); user.stateProvince = config.getString(USER, username, STATEPROVINCE); user.countryCode = config.getString(USER, username, COUNTRYCODE); user.cookie = config.getString(USER, username, COOKIE); if (StringUtils.isEmpty(user.cookie) && !StringUtils.isEmpty(user.password)) { user.cookie = StringUtils.getSHA1(user.username + user.password); } // user roles Set<String> roles = new HashSet<String>(Arrays.asList(config.getStringList( USER, username, ROLE))); user.canAdmin = roles.contains(Constants.ADMIN_ROLE); user.canFork = roles.contains(Constants.FORK_ROLE); user.canCreate = roles.contains(Constants.CREATE_ROLE); user.excludeFromFederation = roles.contains(Constants.NOT_FEDERATED_ROLE); // repository memberships if (!user.canAdmin) { // non-admin, read permissions Set<String> repositories = new HashSet<String>(Arrays.asList(config .getStringList(USER, username, REPOSITORY))); for (String repository : repositories) { user.addRepositoryPermission(repository); } } // update cache users.put(user.username, user); if (!StringUtils.isEmpty(user.cookie)) { cookies.put(user.cookie, user); } } // load the teams Set<String> teamnames = config.getSubsections(TEAM); for (String teamname : teamnames) { TeamModel team = new TeamModel(teamname); Set<String> roles = new HashSet<String>(Arrays.asList(config.getStringList( TEAM, teamname, ROLE))); team.canAdmin = roles.contains(Constants.ADMIN_ROLE); team.canFork = roles.contains(Constants.FORK_ROLE); team.canCreate = roles.contains(Constants.CREATE_ROLE); if (!team.canAdmin) { // non-admin team, read permissions team.addRepositoryPermissions(Arrays.asList(config.getStringList(TEAM, teamname, REPOSITORY))); } team.addUsers(Arrays.asList(config.getStringList(TEAM, teamname, USER))); team.addMailingLists(Arrays.asList(config.getStringList(TEAM, teamname, MAILINGLIST))); team.preReceiveScripts.addAll(Arrays.asList(config.getStringList(TEAM, teamname, PRERECEIVE))); team.postReceiveScripts.addAll(Arrays.asList(config.getStringList(TEAM, teamname, POSTRECEIVE))); 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) { logger.error(MessageFormat.format("Failed to read {0}", realmFile), e); } } } protected long lastModified() { return lastModified; } @Override public String toString() { return getClass().getSimpleName() + "(" + realmFile.getAbsolutePath() + ")"; } } src/com/gitblit/FileUserService.java
@@ -1,1145 +1,1146 @@ /* * 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; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.gitblit.Constants.AccessPermission; import com.gitblit.models.TeamModel; import com.gitblit.models.UserModel; import com.gitblit.utils.ArrayUtils; import com.gitblit.utils.DeepCopier; import com.gitblit.utils.StringUtils; /** * FileUserService is Gitblit's original default user service implementation. * * Users and their repository memberships are stored in a simple properties file * which is cached and dynamically reloaded when modified. * * This class was deprecated in Gitblit 0.8.0 in favor of ConfigUserService * which is still a human-readable, editable, plain-text file but it is more * flexible for storing additional fields. * * @author James Moger * */ @Deprecated public class FileUserService extends FileSettings implements IUserService { private final Logger logger = LoggerFactory.getLogger(FileUserService.class); private final Map<String, String> cookies = new ConcurrentHashMap<String, String>(); private final Map<String, TeamModel> teams = new ConcurrentHashMap<String, TeamModel>(); public FileUserService(File realmFile) { super(realmFile.getAbsolutePath()); } /** * Setup the user service. * * @param settings * @since 0.7.0 */ @Override public void setup(IStoredSettings settings) { } /** * Does the user service support changes to credentials? * * @return true or false * @since 1.0.0 */ @Override public boolean supportsCredentialChanges() { return true; } /** * Does the user service support changes to user display name? * * @return true or false * @since 1.0.0 */ @Override public boolean supportsDisplayNameChanges() { return false; } /** * Does the user service support changes to user email address? * * @return true or false * @since 1.0.0 */ @Override public boolean supportsEmailAddressChanges() { return false; } /** * Does the user service support changes to team memberships? * * @return true or false * @since 1.0.0 */ public boolean supportsTeamMembershipChanges() { return true; } /** * Does the user service support cookie authentication? * * @return true or false */ @Override public boolean supportsCookies() { return true; } /** * Returns the cookie value for the specified user. * * @param model * @return cookie value */ @Override public String getCookie(UserModel model) { if (!StringUtils.isEmpty(model.cookie)) { return model.cookie; } Properties allUsers = super.read(); String value = allUsers.getProperty(model.username); String[] roles = value.split(","); String password = roles[0]; String cookie = StringUtils.getSHA1(model.username + password); return cookie; } /** * Authenticate a user based on their cookie. * * @param cookie * @return a user object or null */ @Override public UserModel authenticate(char[] cookie) { String hash = new String(cookie); if (StringUtils.isEmpty(hash)) { return null; } read(); UserModel model = null; if (cookies.containsKey(hash)) { String username = cookies.get(hash); model = getUserModel(username); } return model; } /** * Authenticate a user based on a username and password. * * @param username * @param password * @return a user object or null */ @Override public UserModel authenticate(String username, char[] password) { Properties allUsers = read(); String userInfo = allUsers.getProperty(username); if (StringUtils.isEmpty(userInfo)) { return null; } UserModel returnedUser = null; UserModel user = getUserModel(username); if (user.password.startsWith(StringUtils.MD5_TYPE)) { // password digest String md5 = StringUtils.MD5_TYPE + StringUtils.getMD5(new String(password)); if (user.password.equalsIgnoreCase(md5)) { returnedUser = user; } } else if (user.password.startsWith(StringUtils.COMBINED_MD5_TYPE)) { // username+password digest String md5 = StringUtils.COMBINED_MD5_TYPE + StringUtils.getMD5(username.toLowerCase() + new String(password)); if (user.password.equalsIgnoreCase(md5)) { returnedUser = user; } } else if (user.password.equals(new String(password))) { // plain-text password returnedUser = user; } return returnedUser; } /** * Logout a user. * * @param user */ @Override public void logout(UserModel user) { } /** * Retrieve the user object for the specified username. * * @param username * @return a user object or null */ @Override public UserModel getUserModel(String username) { Properties allUsers = read(); String userInfo = allUsers.getProperty(username.toLowerCase()); if (userInfo == null) { return null; } UserModel model = new UserModel(username.toLowerCase()); String[] userValues = userInfo.split(","); model.password = userValues[0]; for (int i = 1; i < userValues.length; i++) { String role = userValues[i]; switch (role.charAt(0)) { case '#': // Permissions if (role.equalsIgnoreCase(Constants.ADMIN_ROLE)) { model.canAdmin = true; } else if (role.equalsIgnoreCase(Constants.FORK_ROLE)) { model.canFork = true; } else if (role.equalsIgnoreCase(Constants.CREATE_ROLE)) { model.canCreate = true; } else if (role.equalsIgnoreCase(Constants.NOT_FEDERATED_ROLE)) { model.excludeFromFederation = true; } break; default: model.addRepositoryPermission(role); } } // set the teams for the user for (TeamModel team : teams.values()) { if (team.hasUser(username)) { model.teams.add(DeepCopier.copy(team)); } } return model; } /** * Updates/writes a complete user object. * * @param model * @return true if update is successful */ @Override public boolean updateUserModel(UserModel model) { return updateUserModel(model.username, model); } /** * Updates/writes all specified user objects. * * @param model a list of user models * @return true if update is successful * @since 1.2.0 */ @Override public boolean updateUserModels(List<UserModel> models) { try { Properties allUsers = read(); for (UserModel model : models) { updateUserCache(allUsers, model.username, model); } write(allUsers); return true; } catch (Throwable t) { logger.error(MessageFormat.format("Failed to update {0} user models!", models.size()), t); } return false; } /** * Updates/writes and replaces a complete 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 */ @Override public boolean updateUserModel(String username, UserModel model) { try { Properties allUsers = read(); updateUserCache(allUsers, username, model); write(allUsers); return true; } catch (Throwable t) { logger.error(MessageFormat.format("Failed to update user model {0}!", model.username), t); } return false; } /** * Updates/writes and replaces a complete 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 */ private boolean updateUserCache(Properties allUsers, String username, UserModel model) { try { UserModel oldUser = getUserModel(username); List<String> roles; if (model.permissions == null) { roles = new ArrayList<String>(); } else { // discrete repository permissions roles = new ArrayList<String>(); for (Map.Entry<String, AccessPermission> entry : model.permissions.entrySet()) { if (entry.getValue().exceeds(AccessPermission.NONE)) { // code:repository (e.g. RW+:~james/myrepo.git roles.add(entry.getValue().asRole(entry.getKey())); } } } // Permissions if (model.canAdmin) { roles.add(Constants.ADMIN_ROLE); } if (model.canFork) { roles.add(Constants.FORK_ROLE); } if (model.canCreate) { roles.add(Constants.CREATE_ROLE); } if (model.excludeFromFederation) { roles.add(Constants.NOT_FEDERATED_ROLE); } StringBuilder sb = new StringBuilder(); if (!StringUtils.isEmpty(model.password)) { sb.append(model.password); } sb.append(','); for (String role : roles) { sb.append(role); sb.append(','); } // trim trailing comma sb.setLength(sb.length() - 1); allUsers.remove(username.toLowerCase()); allUsers.put(model.username.toLowerCase(), 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); } } } } return true; } catch (Throwable t) { logger.error(MessageFormat.format("Failed to update user model {0}!", model.username), t); } return false; } /** * Deletes the user object from the user service. * * @param model * @return true if successful */ @Override public boolean deleteUserModel(UserModel model) { return deleteUser(model.username); } /** * Delete the user object with the specified username * * @param username * @return true if successful */ @Override public boolean deleteUser(String username) { try { // Read realm file Properties allUsers = read(); UserModel user = getUserModel(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); return true; } catch (Throwable t) { logger.error(MessageFormat.format("Failed to delete user {0}!", username), t); } return false; } /** * Returns the list of all users available to the login service. * * @return list of all usernames */ @Override public List<String> getAllUsernames() { Properties allUsers = read(); List<String> list = new ArrayList<String>(); for (String user : allUsers.stringPropertyNames()) { if (user.charAt(0) == '@') { // skip team user definitions continue; } list.add(user); } Collections.sort(list); return list; } /** * Returns the list of all users available to the login service. * * @return list of all usernames */ @Override public List<UserModel> getAllUsers() { read(); List<UserModel> list = new ArrayList<UserModel>(); for (String username : getAllUsernames()) { list.add(getUserModel(username)); } Collections.sort(list); 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> getUsernamesForRepositoryRole(String role) { List<String> list = new ArrayList<String>(); try { Properties allUsers = read(); for (String username : allUsers.stringPropertyNames()) { if (username.charAt(0) == '@') { continue; } String value = allUsers.getProperty(username); String[] values = value.split(","); // skip first value (password) for (int i = 1; i < values.length; i++) { String r = values[i]; if (r.equalsIgnoreCase(role)) { list.add(username); break; } } } } catch (Throwable t) { logger.error(MessageFormat.format("Failed to get usernames for role {0}!", role), t); } Collections.sort(list); return list; } /** * Sets the list of all users who are allowed to bypass the access * restriction placed on the specified repository. * * @param role * the repository name * @param usernames * @return true if successful */ @Override public boolean setUsernamesForRepositoryRole(String role, List<String> usernames) { try { Set<String> specifiedUsers = new HashSet<String>(usernames); Set<String> needsAddRole = new HashSet<String>(specifiedUsers); Set<String> needsRemoveRole = new HashSet<String>(); // identify users which require add and remove role Properties allUsers = read(); for (String username : allUsers.stringPropertyNames()) { String value = allUsers.getProperty(username); String[] values = value.split(","); // skip first value (password) for (int i = 1; i < values.length; i++) { String r = values[i]; if (r.equalsIgnoreCase(role)) { // user has role, check against revised user list if (specifiedUsers.contains(username)) { needsAddRole.remove(username); } else { // remove role from user needsRemoveRole.add(username); } break; } } } // add roles to users for (String user : needsAddRole) { String userValues = allUsers.getProperty(user); userValues += "," + role; allUsers.put(user, userValues); } // remove role from user for (String user : needsRemoveRole) { String[] values = allUsers.getProperty(user).split(","); String password = values[0]; StringBuilder sb = new StringBuilder(); sb.append(password); sb.append(','); // skip first value (password) for (int i = 1; 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(user, sb.toString()); } // persist changes write(allUsers); return true; } catch (Throwable t) { logger.error(MessageFormat.format("Failed to set usernames for role {0}!", role), t); } return false; } /** * Renames a repository role. * * @param oldRole * @param newRole * @return true if successful */ @Override public boolean renameRepositoryRole(String oldRole, String newRole) { try { Properties allUsers = read(); Set<String> needsRenameRole = new HashSet<String>(); // identify users which require role rename for (String username : allUsers.stringPropertyNames()) { String value = allUsers.getProperty(username); String[] roles = value.split(","); // skip first value (password) for (int i = 1; i < roles.length; i++) { String repository = AccessPermission.repositoryFromRole(roles[i]); if (repository.equalsIgnoreCase(oldRole)) { needsRenameRole.add(username); break; } } } // rename role for identified users for (String user : needsRenameRole) { String userValues = allUsers.getProperty(user); String[] values = userValues.split(","); String password = values[0]; StringBuilder sb = new StringBuilder(); sb.append(password); sb.append(','); sb.append(newRole); sb.append(','); // skip first value (password) for (int i = 1; i < values.length; i++) { String repository = AccessPermission.repositoryFromRole(values[i]); if (repository.equalsIgnoreCase(oldRole)) { AccessPermission permission = AccessPermission.permissionFromRole(values[i]); sb.append(permission.asRole(newRole)); sb.append(','); } else { sb.append(values[i]); sb.append(','); } } sb.setLength(sb.length() - 1); // update properties allUsers.put(user, sb.toString()); } // persist changes write(allUsers); return true; } catch (Throwable t) { logger.error( MessageFormat.format("Failed to rename role {0} to {1}!", oldRole, newRole), t); } return false; } /** * Removes a repository role from all users. * * @param role * @return true if successful */ @Override public boolean deleteRepositoryRole(String role) { try { Properties allUsers = read(); Set<String> needsDeleteRole = new HashSet<String>(); // identify users which require role rename for (String username : allUsers.stringPropertyNames()) { String value = allUsers.getProperty(username); String[] roles = value.split(","); // skip first value (password) for (int i = 1; i < roles.length; i++) { String repository = AccessPermission.repositoryFromRole(roles[i]); if (repository.equalsIgnoreCase(role)) { needsDeleteRole.add(username); break; } } } // delete role for identified users for (String user : needsDeleteRole) { String userValues = allUsers.getProperty(user); String[] values = userValues.split(","); String password = values[0]; StringBuilder sb = new StringBuilder(); sb.append(password); sb.append(','); // skip first value (password) for (int i = 1; i < values.length; i++) { String repository = AccessPermission.repositoryFromRole(values[i]); if (!repository.equalsIgnoreCase(role)) { sb.append(values[i]); sb.append(','); } } sb.setLength(sb.length() - 1); // update properties allUsers.put(user, sb.toString()); } // persist changes write(allUsers); return true; } catch (Throwable t) { logger.error(MessageFormat.format("Failed to delete role {0}!", role), t); } return false; } /** * Writes the properties file. * * @param properties * @throws IOException */ private void write(Properties properties) throws IOException { // Write a temporary copy of the users file File realmFileCopy = new File(propertiesFile.getAbsolutePath() + ".tmp"); FileWriter writer = new FileWriter(realmFileCopy); properties .store(writer, " Gitblit realm file format:\n username=password,\\#permission,repository1,repository2...\n @teamname=!username1,!username2,!username3,repository1,repository2..."); writer.close(); // If the write is successful, delete the current file and rename // the temporary copy to the original filename. if (realmFileCopy.exists() && realmFileCopy.length() > 0) { if (propertiesFile.exists()) { if (!propertiesFile.delete()) { throw new IOException(MessageFormat.format("Failed to delete {0}!", propertiesFile.getAbsolutePath())); } } if (!realmFileCopy.renameTo(propertiesFile)) { throw new IOException(MessageFormat.format("Failed to rename {0} to {1}!", realmFileCopy.getAbsolutePath(), propertiesFile.getAbsolutePath())); } } else { throw new IOException(MessageFormat.format("Failed to save {0}!", realmFileCopy.getAbsolutePath())); } } /** * Reads the properties file and rebuilds the in-memory cookie lookup table. */ @Override protected synchronized Properties read() { long lastRead = lastModified(); boolean reload = forceReload(); Properties allUsers = super.read(); if (reload || (lastRead != lastModified())) { // reload hash cache cookies.clear(); teams.clear(); for (String username : allUsers.stringPropertyNames()) { String value = allUsers.getProperty(username); String[] roles = value.split(","); 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>(); List<String> mailingLists = new ArrayList<String>(); List<String> preReceive = new ArrayList<String>(); List<String> postReceive = new ArrayList<String>(); for (String role : roles) { if (role.charAt(0) == '!') { users.add(role.substring(1)); } else if (role.charAt(0) == '&') { mailingLists.add(role.substring(1)); } else if (role.charAt(0) == '^') { preReceive.add(role.substring(1)); } else if (role.charAt(0) == '%') { postReceive.add(role.substring(1)); } else { switch (role.charAt(0)) { case '#': // Permissions if (role.equalsIgnoreCase(Constants.ADMIN_ROLE)) { team.canAdmin = true; } else if (role.equalsIgnoreCase(Constants.FORK_ROLE)) { team.canFork = true; } else if (role.equalsIgnoreCase(Constants.CREATE_ROLE)) { team.canCreate = true; } break; default: repositories.add(role); } repositories.add(role); } } if (!team.canAdmin) { // only read permissions for non-admin teams team.addRepositoryPermissions(repositories); } team.addUsers(users); team.addMailingLists(mailingLists); team.preReceiveScripts.addAll(preReceive); team.postReceiveScripts.addAll(postReceive); teams.put(team.name.toLowerCase(), team); } else { // user definition String password = roles[0]; cookies.put(StringUtils.getSHA1(username.toLowerCase() + password), username.toLowerCase()); } } } return allUsers; } @Override public String toString() { 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()); Collections.sort(list); return list; } /** * Returns the list of all teams available to the login service. * * @return list of all teams * @since 0.8.0 */ @Override public List<TeamModel> getAllTeams() { List<TeamModel> list = new ArrayList<TeamModel>(teams.values()); list = DeepCopier.copy(list); Collections.sort(list); 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); } Collections.sort(list); 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 all specified team objects. * * @param models a list of team models * @return true if update is successful * @since 1.2.0 */ public boolean updateTeamModels(List<TeamModel> models) { try { Properties allUsers = read(); for (TeamModel model : models) { updateTeamCache(allUsers, model.name, model); } write(allUsers); return true; } catch (Throwable t) { logger.error(MessageFormat.format("Failed to update {0} team models!", models.size()), t); } return false; } /** * 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(); List<String> roles; if (model.permissions == null) { // legacy, use repository list if (model.repositories != null) { roles = new ArrayList<String>(model.repositories); } else { roles = new ArrayList<String>(); } } else { // discrete repository permissions roles = new ArrayList<String>(); for (Map.Entry<String, AccessPermission> entry : model.permissions.entrySet()) { if (entry.getValue().exceeds(AccessPermission.NONE)) { // code:repository (e.g. RW+:~james/myrepo.git roles.add(entry.getValue().asRole(entry.getKey())); } } } // Permissions if (model.canAdmin) { roles.add(Constants.ADMIN_ROLE); } if (model.canFork) { roles.add(Constants.FORK_ROLE); } if (model.canCreate) { roles.add(Constants.CREATE_ROLE); } for (String role : roles) { sb.append(role); sb.append(','); } if (!ArrayUtils.isEmpty(model.users)) { for (String user : model.users) { sb.append('!'); sb.append(user); sb.append(','); } } if (!ArrayUtils.isEmpty(model.mailingLists)) { for (String address : model.mailingLists) { sb.append('&'); sb.append(address); sb.append(','); } } if (!ArrayUtils.isEmpty(model.preReceiveScripts)) { for (String script : model.preReceiveScripts) { sb.append('^'); sb.append(script); sb.append(','); } } if (!ArrayUtils.isEmpty(model.postReceiveScripts)) { for (String script : model.postReceiveScripts) { sb.append('%'); sb.append(script); 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; } } /* * 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; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.gitblit.Constants.AccessPermission; import com.gitblit.models.TeamModel; import com.gitblit.models.UserModel; import com.gitblit.utils.ArrayUtils; import com.gitblit.utils.DeepCopier; import com.gitblit.utils.StringUtils; /** * FileUserService is Gitblit's original default user service implementation. * * Users and their repository memberships are stored in a simple properties file * which is cached and dynamically reloaded when modified. * * This class was deprecated in Gitblit 0.8.0 in favor of ConfigUserService * which is still a human-readable, editable, plain-text file but it is more * flexible for storing additional fields. * * @author James Moger * */ @Deprecated public class FileUserService extends FileSettings implements IUserService { private final Logger logger = LoggerFactory.getLogger(FileUserService.class); private final Map<String, String> cookies = new ConcurrentHashMap<String, String>(); private final Map<String, TeamModel> teams = new ConcurrentHashMap<String, TeamModel>(); public FileUserService(File realmFile) { super(realmFile.getAbsolutePath()); } /** * Setup the user service. * * @param settings * @since 0.7.0 */ @Override public void setup(IStoredSettings settings) { } /** * Does the user service support changes to credentials? * * @return true or false * @since 1.0.0 */ @Override public boolean supportsCredentialChanges() { return true; } /** * Does the user service support changes to user display name? * * @return true or false * @since 1.0.0 */ @Override public boolean supportsDisplayNameChanges() { return false; } /** * Does the user service support changes to user email address? * * @return true or false * @since 1.0.0 */ @Override public boolean supportsEmailAddressChanges() { return false; } /** * Does the user service support changes to team memberships? * * @return true or false * @since 1.0.0 */ public boolean supportsTeamMembershipChanges() { return true; } /** * Does the user service support cookie authentication? * * @return true or false */ @Override public boolean supportsCookies() { return true; } /** * Returns the cookie value for the specified user. * * @param model * @return cookie value */ @Override public String getCookie(UserModel model) { if (!StringUtils.isEmpty(model.cookie)) { return model.cookie; } Properties allUsers = super.read(); String value = allUsers.getProperty(model.username); String[] roles = value.split(","); String password = roles[0]; String cookie = StringUtils.getSHA1(model.username + password); return cookie; } /** * Authenticate a user based on their cookie. * * @param cookie * @return a user object or null */ @Override public UserModel authenticate(char[] cookie) { String hash = new String(cookie); if (StringUtils.isEmpty(hash)) { return null; } read(); UserModel model = null; if (cookies.containsKey(hash)) { String username = cookies.get(hash); model = getUserModel(username); } return model; } /** * Authenticate a user based on a username and password. * * @param username * @param password * @return a user object or null */ @Override public UserModel authenticate(String username, char[] password) { Properties allUsers = read(); String userInfo = allUsers.getProperty(username); if (StringUtils.isEmpty(userInfo)) { return null; } UserModel returnedUser = null; UserModel user = getUserModel(username); if (user.password.startsWith(StringUtils.MD5_TYPE)) { // password digest String md5 = StringUtils.MD5_TYPE + StringUtils.getMD5(new String(password)); if (user.password.equalsIgnoreCase(md5)) { returnedUser = user; } } else if (user.password.startsWith(StringUtils.COMBINED_MD5_TYPE)) { // username+password digest String md5 = StringUtils.COMBINED_MD5_TYPE + StringUtils.getMD5(username.toLowerCase() + new String(password)); if (user.password.equalsIgnoreCase(md5)) { returnedUser = user; } } else if (user.password.equals(new String(password))) { // plain-text password returnedUser = user; } return returnedUser; } /** * Logout a user. * * @param user */ @Override public void logout(UserModel user) { } /** * Retrieve the user object for the specified username. * * @param username * @return a user object or null */ @Override public UserModel getUserModel(String username) { Properties allUsers = read(); String userInfo = allUsers.getProperty(username.toLowerCase()); if (userInfo == null) { return null; } UserModel model = new UserModel(username.toLowerCase()); String[] userValues = userInfo.split(","); model.password = userValues[0]; for (int i = 1; i < userValues.length; i++) { String role = userValues[i]; switch (role.charAt(0)) { case '#': // Permissions if (role.equalsIgnoreCase(Constants.ADMIN_ROLE)) { model.canAdmin = true; } else if (role.equalsIgnoreCase(Constants.FORK_ROLE)) { model.canFork = true; } else if (role.equalsIgnoreCase(Constants.CREATE_ROLE)) { model.canCreate = true; } else if (role.equalsIgnoreCase(Constants.NOT_FEDERATED_ROLE)) { model.excludeFromFederation = true; } break; default: model.addRepositoryPermission(role); } } // set the teams for the user for (TeamModel team : teams.values()) { if (team.hasUser(username)) { model.teams.add(DeepCopier.copy(team)); } } return model; } /** * Updates/writes a complete user object. * * @param model * @return true if update is successful */ @Override public boolean updateUserModel(UserModel model) { return updateUserModel(model.username, model); } /** * Updates/writes all specified user objects. * * @param models a list of user models * @return true if update is successful * @since 1.2.0 */ @Override public boolean updateUserModels(Collection<UserModel> models) { try { Properties allUsers = read(); for (UserModel model : models) { updateUserCache(allUsers, model.username, model); } write(allUsers); return true; } catch (Throwable t) { logger.error(MessageFormat.format("Failed to update {0} user models!", models.size()), t); } return false; } /** * Updates/writes and replaces a complete 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 */ @Override public boolean updateUserModel(String username, UserModel model) { try { Properties allUsers = read(); updateUserCache(allUsers, username, model); write(allUsers); return true; } catch (Throwable t) { logger.error(MessageFormat.format("Failed to update user model {0}!", model.username), t); } return false; } /** * Updates/writes and replaces a complete 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 */ private boolean updateUserCache(Properties allUsers, String username, UserModel model) { try { UserModel oldUser = getUserModel(username); List<String> roles; if (model.permissions == null) { roles = new ArrayList<String>(); } else { // discrete repository permissions roles = new ArrayList<String>(); for (Map.Entry<String, AccessPermission> entry : model.permissions.entrySet()) { if (entry.getValue().exceeds(AccessPermission.NONE)) { // code:repository (e.g. RW+:~james/myrepo.git roles.add(entry.getValue().asRole(entry.getKey())); } } } // Permissions if (model.canAdmin) { roles.add(Constants.ADMIN_ROLE); } if (model.canFork) { roles.add(Constants.FORK_ROLE); } if (model.canCreate) { roles.add(Constants.CREATE_ROLE); } if (model.excludeFromFederation) { roles.add(Constants.NOT_FEDERATED_ROLE); } StringBuilder sb = new StringBuilder(); if (!StringUtils.isEmpty(model.password)) { sb.append(model.password); } sb.append(','); for (String role : roles) { sb.append(role); sb.append(','); } // trim trailing comma sb.setLength(sb.length() - 1); allUsers.remove(username.toLowerCase()); allUsers.put(model.username.toLowerCase(), 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); } } } } return true; } catch (Throwable t) { logger.error(MessageFormat.format("Failed to update user model {0}!", model.username), t); } return false; } /** * Deletes the user object from the user service. * * @param model * @return true if successful */ @Override public boolean deleteUserModel(UserModel model) { return deleteUser(model.username); } /** * Delete the user object with the specified username * * @param username * @return true if successful */ @Override public boolean deleteUser(String username) { try { // Read realm file Properties allUsers = read(); UserModel user = getUserModel(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); return true; } catch (Throwable t) { logger.error(MessageFormat.format("Failed to delete user {0}!", username), t); } return false; } /** * Returns the list of all users available to the login service. * * @return list of all usernames */ @Override public List<String> getAllUsernames() { Properties allUsers = read(); List<String> list = new ArrayList<String>(); for (String user : allUsers.stringPropertyNames()) { if (user.charAt(0) == '@') { // skip team user definitions continue; } list.add(user); } Collections.sort(list); return list; } /** * Returns the list of all users available to the login service. * * @return list of all usernames */ @Override public List<UserModel> getAllUsers() { read(); List<UserModel> list = new ArrayList<UserModel>(); for (String username : getAllUsernames()) { list.add(getUserModel(username)); } Collections.sort(list); 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> getUsernamesForRepositoryRole(String role) { List<String> list = new ArrayList<String>(); try { Properties allUsers = read(); for (String username : allUsers.stringPropertyNames()) { if (username.charAt(0) == '@') { continue; } String value = allUsers.getProperty(username); String[] values = value.split(","); // skip first value (password) for (int i = 1; i < values.length; i++) { String r = values[i]; if (r.equalsIgnoreCase(role)) { list.add(username); break; } } } } catch (Throwable t) { logger.error(MessageFormat.format("Failed to get usernames for role {0}!", role), t); } Collections.sort(list); return list; } /** * Sets the list of all users who are allowed to bypass the access * restriction placed on the specified repository. * * @param role * the repository name * @param usernames * @return true if successful */ @Override public boolean setUsernamesForRepositoryRole(String role, List<String> usernames) { try { Set<String> specifiedUsers = new HashSet<String>(usernames); Set<String> needsAddRole = new HashSet<String>(specifiedUsers); Set<String> needsRemoveRole = new HashSet<String>(); // identify users which require add and remove role Properties allUsers = read(); for (String username : allUsers.stringPropertyNames()) { String value = allUsers.getProperty(username); String[] values = value.split(","); // skip first value (password) for (int i = 1; i < values.length; i++) { String r = values[i]; if (r.equalsIgnoreCase(role)) { // user has role, check against revised user list if (specifiedUsers.contains(username)) { needsAddRole.remove(username); } else { // remove role from user needsRemoveRole.add(username); } break; } } } // add roles to users for (String user : needsAddRole) { String userValues = allUsers.getProperty(user); userValues += "," + role; allUsers.put(user, userValues); } // remove role from user for (String user : needsRemoveRole) { String[] values = allUsers.getProperty(user).split(","); String password = values[0]; StringBuilder sb = new StringBuilder(); sb.append(password); sb.append(','); // skip first value (password) for (int i = 1; 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(user, sb.toString()); } // persist changes write(allUsers); return true; } catch (Throwable t) { logger.error(MessageFormat.format("Failed to set usernames for role {0}!", role), t); } return false; } /** * Renames a repository role. * * @param oldRole * @param newRole * @return true if successful */ @Override public boolean renameRepositoryRole(String oldRole, String newRole) { try { Properties allUsers = read(); Set<String> needsRenameRole = new HashSet<String>(); // identify users which require role rename for (String username : allUsers.stringPropertyNames()) { String value = allUsers.getProperty(username); String[] roles = value.split(","); // skip first value (password) for (int i = 1; i < roles.length; i++) { String repository = AccessPermission.repositoryFromRole(roles[i]); if (repository.equalsIgnoreCase(oldRole)) { needsRenameRole.add(username); break; } } } // rename role for identified users for (String user : needsRenameRole) { String userValues = allUsers.getProperty(user); String[] values = userValues.split(","); String password = values[0]; StringBuilder sb = new StringBuilder(); sb.append(password); sb.append(','); sb.append(newRole); sb.append(','); // skip first value (password) for (int i = 1; i < values.length; i++) { String repository = AccessPermission.repositoryFromRole(values[i]); if (repository.equalsIgnoreCase(oldRole)) { AccessPermission permission = AccessPermission.permissionFromRole(values[i]); sb.append(permission.asRole(newRole)); sb.append(','); } else { sb.append(values[i]); sb.append(','); } } sb.setLength(sb.length() - 1); // update properties allUsers.put(user, sb.toString()); } // persist changes write(allUsers); return true; } catch (Throwable t) { logger.error( MessageFormat.format("Failed to rename role {0} to {1}!", oldRole, newRole), t); } return false; } /** * Removes a repository role from all users. * * @param role * @return true if successful */ @Override public boolean deleteRepositoryRole(String role) { try { Properties allUsers = read(); Set<String> needsDeleteRole = new HashSet<String>(); // identify users which require role rename for (String username : allUsers.stringPropertyNames()) { String value = allUsers.getProperty(username); String[] roles = value.split(","); // skip first value (password) for (int i = 1; i < roles.length; i++) { String repository = AccessPermission.repositoryFromRole(roles[i]); if (repository.equalsIgnoreCase(role)) { needsDeleteRole.add(username); break; } } } // delete role for identified users for (String user : needsDeleteRole) { String userValues = allUsers.getProperty(user); String[] values = userValues.split(","); String password = values[0]; StringBuilder sb = new StringBuilder(); sb.append(password); sb.append(','); // skip first value (password) for (int i = 1; i < values.length; i++) { String repository = AccessPermission.repositoryFromRole(values[i]); if (!repository.equalsIgnoreCase(role)) { sb.append(values[i]); sb.append(','); } } sb.setLength(sb.length() - 1); // update properties allUsers.put(user, sb.toString()); } // persist changes write(allUsers); return true; } catch (Throwable t) { logger.error(MessageFormat.format("Failed to delete role {0}!", role), t); } return false; } /** * Writes the properties file. * * @param properties * @throws IOException */ private void write(Properties properties) throws IOException { // Write a temporary copy of the users file File realmFileCopy = new File(propertiesFile.getAbsolutePath() + ".tmp"); FileWriter writer = new FileWriter(realmFileCopy); properties .store(writer, " Gitblit realm file format:\n username=password,\\#permission,repository1,repository2...\n @teamname=!username1,!username2,!username3,repository1,repository2..."); writer.close(); // If the write is successful, delete the current file and rename // the temporary copy to the original filename. if (realmFileCopy.exists() && realmFileCopy.length() > 0) { if (propertiesFile.exists()) { if (!propertiesFile.delete()) { throw new IOException(MessageFormat.format("Failed to delete {0}!", propertiesFile.getAbsolutePath())); } } if (!realmFileCopy.renameTo(propertiesFile)) { throw new IOException(MessageFormat.format("Failed to rename {0} to {1}!", realmFileCopy.getAbsolutePath(), propertiesFile.getAbsolutePath())); } } else { throw new IOException(MessageFormat.format("Failed to save {0}!", realmFileCopy.getAbsolutePath())); } } /** * Reads the properties file and rebuilds the in-memory cookie lookup table. */ @Override protected synchronized Properties read() { long lastRead = lastModified(); boolean reload = forceReload(); Properties allUsers = super.read(); if (reload || (lastRead != lastModified())) { // reload hash cache cookies.clear(); teams.clear(); for (String username : allUsers.stringPropertyNames()) { String value = allUsers.getProperty(username); String[] roles = value.split(","); 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>(); List<String> mailingLists = new ArrayList<String>(); List<String> preReceive = new ArrayList<String>(); List<String> postReceive = new ArrayList<String>(); for (String role : roles) { if (role.charAt(0) == '!') { users.add(role.substring(1)); } else if (role.charAt(0) == '&') { mailingLists.add(role.substring(1)); } else if (role.charAt(0) == '^') { preReceive.add(role.substring(1)); } else if (role.charAt(0) == '%') { postReceive.add(role.substring(1)); } else { switch (role.charAt(0)) { case '#': // Permissions if (role.equalsIgnoreCase(Constants.ADMIN_ROLE)) { team.canAdmin = true; } else if (role.equalsIgnoreCase(Constants.FORK_ROLE)) { team.canFork = true; } else if (role.equalsIgnoreCase(Constants.CREATE_ROLE)) { team.canCreate = true; } break; default: repositories.add(role); } repositories.add(role); } } if (!team.canAdmin) { // only read permissions for non-admin teams team.addRepositoryPermissions(repositories); } team.addUsers(users); team.addMailingLists(mailingLists); team.preReceiveScripts.addAll(preReceive); team.postReceiveScripts.addAll(postReceive); teams.put(team.name.toLowerCase(), team); } else { // user definition String password = roles[0]; cookies.put(StringUtils.getSHA1(username.toLowerCase() + password), username.toLowerCase()); } } } return allUsers; } @Override public String toString() { 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()); Collections.sort(list); return list; } /** * Returns the list of all teams available to the login service. * * @return list of all teams * @since 0.8.0 */ @Override public List<TeamModel> getAllTeams() { List<TeamModel> list = new ArrayList<TeamModel>(teams.values()); list = DeepCopier.copy(list); Collections.sort(list); 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); } Collections.sort(list); 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 all specified team objects. * * @param models a list of team models * @return true if update is successful * @since 1.2.0 */ public boolean updateTeamModels(Collection<TeamModel> models) { try { Properties allUsers = read(); for (TeamModel model : models) { updateTeamCache(allUsers, model.name, model); } write(allUsers); return true; } catch (Throwable t) { logger.error(MessageFormat.format("Failed to update {0} team models!", models.size()), t); } return false; } /** * 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(); List<String> roles; if (model.permissions == null) { // legacy, use repository list if (model.repositories != null) { roles = new ArrayList<String>(model.repositories); } else { roles = new ArrayList<String>(); } } else { // discrete repository permissions roles = new ArrayList<String>(); for (Map.Entry<String, AccessPermission> entry : model.permissions.entrySet()) { if (entry.getValue().exceeds(AccessPermission.NONE)) { // code:repository (e.g. RW+:~james/myrepo.git roles.add(entry.getValue().asRole(entry.getKey())); } } } // Permissions if (model.canAdmin) { roles.add(Constants.ADMIN_ROLE); } if (model.canFork) { roles.add(Constants.FORK_ROLE); } if (model.canCreate) { roles.add(Constants.CREATE_ROLE); } for (String role : roles) { sb.append(role); sb.append(','); } if (!ArrayUtils.isEmpty(model.users)) { for (String user : model.users) { sb.append('!'); sb.append(user); sb.append(','); } } if (!ArrayUtils.isEmpty(model.mailingLists)) { for (String address : model.mailingLists) { sb.append('&'); sb.append(address); sb.append(','); } } if (!ArrayUtils.isEmpty(model.preReceiveScripts)) { for (String script : model.preReceiveScripts) { sb.append('^'); sb.append(script); sb.append(','); } } if (!ArrayUtils.isEmpty(model.postReceiveScripts)) { for (String script : model.postReceiveScripts) { sb.append('%'); sb.append(script); 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; } } src/com/gitblit/GitblitUserService.java
@@ -1,303 +1,304 @@ /* * 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; import java.io.File; import java.io.IOException; import java.text.MessageFormat; import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.gitblit.models.TeamModel; import com.gitblit.models.UserModel; import com.gitblit.utils.DeepCopier; /** * This class wraps the default user service and is recommended as the starting * point for custom user service implementations. * * This does seem a little convoluted, but the idea is to allow IUserService to * evolve with new methods and implementations without breaking custom * authentication implementations. * * The most common implementation of a custom IUserService is to only override * authentication and then delegate all other functionality to one of Gitblit's * user services. This class optimizes that use-case. * * Extending GitblitUserService allows for authentication customization without * having to keep-up-with IUSerService API changes. * * @author James Moger * */ public class GitblitUserService implements IUserService { protected IUserService serviceImpl; private final Logger logger = LoggerFactory.getLogger(GitblitUserService.class); public GitblitUserService() { } @Override public void setup(IStoredSettings settings) { File realmFile = GitBlit.getFileOrFolder(Keys.realm.userService, "users.conf"); serviceImpl = createUserService(realmFile); logger.info("GUS delegating to " + serviceImpl.toString()); } @SuppressWarnings("deprecation") protected IUserService createUserService(File realmFile) { IUserService service = null; if (realmFile.getName().toLowerCase().endsWith(".properties")) { // v0.5.0 - v0.7.0 properties-based realm file service = new FileUserService(realmFile); } else if (realmFile.getName().toLowerCase().endsWith(".conf")) { // v0.8.0+ config-based realm file service = new ConfigUserService(realmFile); } assert service != null; if (!realmFile.exists()) { // Create the Administrator account for a new realm file try { realmFile.createNewFile(); } catch (IOException x) { logger.error(MessageFormat.format("COULD NOT CREATE REALM FILE {0}!", realmFile), x); } UserModel admin = new UserModel("admin"); admin.password = "admin"; admin.canAdmin = true; admin.excludeFromFederation = true; service.updateUserModel(admin); } if (service instanceof FileUserService) { // automatically create a users.conf realm file from the original // users.properties file File usersConfig = new File(realmFile.getParentFile(), "users.conf"); if (!usersConfig.exists()) { logger.info(MessageFormat.format("Automatically creating {0} based on {1}", usersConfig.getAbsolutePath(), realmFile.getAbsolutePath())); ConfigUserService configService = new ConfigUserService(usersConfig); for (String username : service.getAllUsernames()) { UserModel userModel = service.getUserModel(username); configService.updateUserModel(userModel); } } // issue suggestion about switching to users.conf logger.warn("Please consider using \"users.conf\" instead of the deprecated \"users.properties\" file"); } return service; } @Override public String toString() { return getClass().getSimpleName(); } @Override public boolean supportsCredentialChanges() { return serviceImpl.supportsCredentialChanges(); } @Override public boolean supportsDisplayNameChanges() { return serviceImpl.supportsDisplayNameChanges(); } @Override public boolean supportsEmailAddressChanges() { return serviceImpl.supportsEmailAddressChanges(); } @Override public boolean supportsTeamMembershipChanges() { return serviceImpl.supportsTeamMembershipChanges(); } @Override public boolean supportsCookies() { return serviceImpl.supportsCookies(); } @Override public String getCookie(UserModel model) { return serviceImpl.getCookie(model); } @Override public UserModel authenticate(char[] cookie) { return serviceImpl.authenticate(cookie); } @Override public UserModel authenticate(String username, char[] password) { return serviceImpl.authenticate(username, password); } @Override public void logout(UserModel user) { serviceImpl.logout(user); } @Override public UserModel getUserModel(String username) { return serviceImpl.getUserModel(username); } @Override public boolean updateUserModel(UserModel model) { return serviceImpl.updateUserModel(model); } @Override public boolean updateUserModels(List<UserModel> models) { return serviceImpl.updateUserModels(models); } @Override public boolean updateUserModel(String username, UserModel model) { if (supportsCredentialChanges()) { if (!supportsTeamMembershipChanges()) { // teams are externally controlled - copy from original model UserModel existingModel = getUserModel(username); model = DeepCopier.copy(model); model.teams.clear(); model.teams.addAll(existingModel.teams); } return serviceImpl.updateUserModel(username, model); } if (model.username.equals(username)) { // passwords are not persisted by the backing user service model.password = null; if (!supportsTeamMembershipChanges()) { // teams are externally controlled- copy from original model UserModel existingModel = getUserModel(username); model = DeepCopier.copy(model); model.teams.clear(); model.teams.addAll(existingModel.teams); } return serviceImpl.updateUserModel(username, model); } logger.error("Users can not be renamed!"); return false; } @Override public boolean deleteUserModel(UserModel model) { return serviceImpl.deleteUserModel(model); } @Override public boolean deleteUser(String username) { return serviceImpl.deleteUser(username); } @Override public List<String> getAllUsernames() { return serviceImpl.getAllUsernames(); } @Override public List<UserModel> getAllUsers() { return serviceImpl.getAllUsers(); } @Override public List<String> getAllTeamNames() { return serviceImpl.getAllTeamNames(); } @Override public List<TeamModel> getAllTeams() { return serviceImpl.getAllTeams(); } @Override public List<String> getTeamnamesForRepositoryRole(String role) { return serviceImpl.getTeamnamesForRepositoryRole(role); } @Override @Deprecated public boolean setTeamnamesForRepositoryRole(String role, List<String> teamnames) { return serviceImpl.setTeamnamesForRepositoryRole(role, teamnames); } @Override public TeamModel getTeamModel(String teamname) { return serviceImpl.getTeamModel(teamname); } @Override public boolean updateTeamModel(TeamModel model) { return serviceImpl.updateTeamModel(model); } @Override public boolean updateTeamModels(List<TeamModel> models) { return serviceImpl.updateTeamModels(models); } @Override public boolean updateTeamModel(String teamname, TeamModel model) { if (!supportsTeamMembershipChanges()) { // teams are externally controlled - copy from original model TeamModel existingModel = getTeamModel(teamname); model = DeepCopier.copy(model); model.users.clear(); model.users.addAll(existingModel.users); } return serviceImpl.updateTeamModel(teamname, model); } @Override public boolean deleteTeamModel(TeamModel model) { return serviceImpl.deleteTeamModel(model); } @Override public boolean deleteTeam(String teamname) { return serviceImpl.deleteTeam(teamname); } @Override public List<String> getUsernamesForRepositoryRole(String role) { return serviceImpl.getUsernamesForRepositoryRole(role); } @Override @Deprecated public boolean setUsernamesForRepositoryRole(String role, List<String> usernames) { return serviceImpl.setUsernamesForRepositoryRole(role, usernames); } @Override public boolean renameRepositoryRole(String oldRole, String newRole) { return serviceImpl.renameRepositoryRole(oldRole, newRole); } @Override public boolean deleteRepositoryRole(String role) { return serviceImpl.deleteRepositoryRole(role); } } /* * 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; import java.io.File; import java.io.IOException; import java.text.MessageFormat; import java.util.Collection; import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.gitblit.models.TeamModel; import com.gitblit.models.UserModel; import com.gitblit.utils.DeepCopier; /** * This class wraps the default user service and is recommended as the starting * point for custom user service implementations. * * This does seem a little convoluted, but the idea is to allow IUserService to * evolve with new methods and implementations without breaking custom * authentication implementations. * * The most common implementation of a custom IUserService is to only override * authentication and then delegate all other functionality to one of Gitblit's * user services. This class optimizes that use-case. * * Extending GitblitUserService allows for authentication customization without * having to keep-up-with IUSerService API changes. * * @author James Moger * */ public class GitblitUserService implements IUserService { protected IUserService serviceImpl; private final Logger logger = LoggerFactory.getLogger(GitblitUserService.class); public GitblitUserService() { } @Override public void setup(IStoredSettings settings) { File realmFile = GitBlit.getFileOrFolder(Keys.realm.userService, "users.conf"); serviceImpl = createUserService(realmFile); logger.info("GUS delegating to " + serviceImpl.toString()); } @SuppressWarnings("deprecation") protected IUserService createUserService(File realmFile) { IUserService service = null; if (realmFile.getName().toLowerCase().endsWith(".properties")) { // v0.5.0 - v0.7.0 properties-based realm file service = new FileUserService(realmFile); } else if (realmFile.getName().toLowerCase().endsWith(".conf")) { // v0.8.0+ config-based realm file service = new ConfigUserService(realmFile); } assert service != null; if (!realmFile.exists()) { // Create the Administrator account for a new realm file try { realmFile.createNewFile(); } catch (IOException x) { logger.error(MessageFormat.format("COULD NOT CREATE REALM FILE {0}!", realmFile), x); } UserModel admin = new UserModel("admin"); admin.password = "admin"; admin.canAdmin = true; admin.excludeFromFederation = true; service.updateUserModel(admin); } if (service instanceof FileUserService) { // automatically create a users.conf realm file from the original // users.properties file File usersConfig = new File(realmFile.getParentFile(), "users.conf"); if (!usersConfig.exists()) { logger.info(MessageFormat.format("Automatically creating {0} based on {1}", usersConfig.getAbsolutePath(), realmFile.getAbsolutePath())); ConfigUserService configService = new ConfigUserService(usersConfig); for (String username : service.getAllUsernames()) { UserModel userModel = service.getUserModel(username); configService.updateUserModel(userModel); } } // issue suggestion about switching to users.conf logger.warn("Please consider using \"users.conf\" instead of the deprecated \"users.properties\" file"); } return service; } @Override public String toString() { return getClass().getSimpleName(); } @Override public boolean supportsCredentialChanges() { return serviceImpl.supportsCredentialChanges(); } @Override public boolean supportsDisplayNameChanges() { return serviceImpl.supportsDisplayNameChanges(); } @Override public boolean supportsEmailAddressChanges() { return serviceImpl.supportsEmailAddressChanges(); } @Override public boolean supportsTeamMembershipChanges() { return serviceImpl.supportsTeamMembershipChanges(); } @Override public boolean supportsCookies() { return serviceImpl.supportsCookies(); } @Override public String getCookie(UserModel model) { return serviceImpl.getCookie(model); } @Override public UserModel authenticate(char[] cookie) { return serviceImpl.authenticate(cookie); } @Override public UserModel authenticate(String username, char[] password) { return serviceImpl.authenticate(username, password); } @Override public void logout(UserModel user) { serviceImpl.logout(user); } @Override public UserModel getUserModel(String username) { return serviceImpl.getUserModel(username); } @Override public boolean updateUserModel(UserModel model) { return serviceImpl.updateUserModel(model); } @Override public boolean updateUserModels(Collection<UserModel> models) { return serviceImpl.updateUserModels(models); } @Override public boolean updateUserModel(String username, UserModel model) { if (supportsCredentialChanges()) { if (!supportsTeamMembershipChanges()) { // teams are externally controlled - copy from original model UserModel existingModel = getUserModel(username); model = DeepCopier.copy(model); model.teams.clear(); model.teams.addAll(existingModel.teams); } return serviceImpl.updateUserModel(username, model); } if (model.username.equals(username)) { // passwords are not persisted by the backing user service model.password = null; if (!supportsTeamMembershipChanges()) { // teams are externally controlled- copy from original model UserModel existingModel = getUserModel(username); model = DeepCopier.copy(model); model.teams.clear(); model.teams.addAll(existingModel.teams); } return serviceImpl.updateUserModel(username, model); } logger.error("Users can not be renamed!"); return false; } @Override public boolean deleteUserModel(UserModel model) { return serviceImpl.deleteUserModel(model); } @Override public boolean deleteUser(String username) { return serviceImpl.deleteUser(username); } @Override public List<String> getAllUsernames() { return serviceImpl.getAllUsernames(); } @Override public List<UserModel> getAllUsers() { return serviceImpl.getAllUsers(); } @Override public List<String> getAllTeamNames() { return serviceImpl.getAllTeamNames(); } @Override public List<TeamModel> getAllTeams() { return serviceImpl.getAllTeams(); } @Override public List<String> getTeamnamesForRepositoryRole(String role) { return serviceImpl.getTeamnamesForRepositoryRole(role); } @Override @Deprecated public boolean setTeamnamesForRepositoryRole(String role, List<String> teamnames) { return serviceImpl.setTeamnamesForRepositoryRole(role, teamnames); } @Override public TeamModel getTeamModel(String teamname) { return serviceImpl.getTeamModel(teamname); } @Override public boolean updateTeamModel(TeamModel model) { return serviceImpl.updateTeamModel(model); } @Override public boolean updateTeamModels(Collection<TeamModel> models) { return serviceImpl.updateTeamModels(models); } @Override public boolean updateTeamModel(String teamname, TeamModel model) { if (!supportsTeamMembershipChanges()) { // teams are externally controlled - copy from original model TeamModel existingModel = getTeamModel(teamname); model = DeepCopier.copy(model); model.users.clear(); model.users.addAll(existingModel.users); } return serviceImpl.updateTeamModel(teamname, model); } @Override public boolean deleteTeamModel(TeamModel model) { return serviceImpl.deleteTeamModel(model); } @Override public boolean deleteTeam(String teamname) { return serviceImpl.deleteTeam(teamname); } @Override public List<String> getUsernamesForRepositoryRole(String role) { return serviceImpl.getUsernamesForRepositoryRole(role); } @Override @Deprecated public boolean setUsernamesForRepositoryRole(String role, List<String> usernames) { return serviceImpl.setUsernamesForRepositoryRole(role, usernames); } @Override public boolean renameRepositoryRole(String oldRole, String newRole) { return serviceImpl.renameRepositoryRole(oldRole, newRole); } @Override public boolean deleteRepositoryRole(String role) { return serviceImpl.deleteRepositoryRole(role); } } src/com/gitblit/IUserService.java
@@ -1,324 +1,325 @@ /* * 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; import java.util.List; import com.gitblit.models.TeamModel; import com.gitblit.models.UserModel; /** * Implementations of IUserService control all aspects of UserModel objects and * user authentication. * * @author James Moger * */ public interface IUserService { /** * Setup the user service. This method allows custom implementations to * retrieve settings from gitblit.properties or the web.xml file without * relying on the GitBlit static singleton. * * @param settings * @since 0.7.0 */ void setup(IStoredSettings settings); /** * Does the user service support changes to credentials? * * @return true or false * @since 1.0.0 */ boolean supportsCredentialChanges(); /** * Does the user service support changes to user display name? * * @return true or false * @since 1.0.0 */ boolean supportsDisplayNameChanges(); /** * Does the user service support changes to user email address? * * @return true or false * @since 1.0.0 */ boolean supportsEmailAddressChanges(); /** * Does the user service support changes to team memberships? * * @return true or false * @since 1.0.0 */ boolean supportsTeamMembershipChanges(); /** * 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 */ String 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); /** * Logout a user. * * @param user */ void logout(UserModel user); /** * 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); /** * Updates/writes all specified user objects. * * @param models a list of user models * @return true if update is successful * @since 1.2.0 */ boolean updateUserModels(List<UserModel> models); /** * 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 available to the login service. * * @return list of all users * @since 0.8.0 */ List<UserModel> getAllUsers(); /** * 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 teams available to the login service. * * @return list of all teams * @since 0.8.0 */ List<TeamModel> getAllTeams(); /** * 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 * @since 0.8.0 */ 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 * @since 0.8.0 */ @Deprecated 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 all specified team objects. * * @param models a list of team models * @return true if update is successful * @since 1.2.0 */ boolean updateTeamModels(List<TeamModel> models); /** * 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 * restriction placed on the specified repository. * * @param role * the repository name * @return list of all usernames that can bypass the access restriction * @since 0.8.0 */ 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 */ @Deprecated 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(); } /* * 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; import java.util.Collection; import java.util.List; import com.gitblit.models.TeamModel; import com.gitblit.models.UserModel; /** * Implementations of IUserService control all aspects of UserModel objects and * user authentication. * * @author James Moger * */ public interface IUserService { /** * Setup the user service. This method allows custom implementations to * retrieve settings from gitblit.properties or the web.xml file without * relying on the GitBlit static singleton. * * @param settings * @since 0.7.0 */ void setup(IStoredSettings settings); /** * Does the user service support changes to credentials? * * @return true or false * @since 1.0.0 */ boolean supportsCredentialChanges(); /** * Does the user service support changes to user display name? * * @return true or false * @since 1.0.0 */ boolean supportsDisplayNameChanges(); /** * Does the user service support changes to user email address? * * @return true or false * @since 1.0.0 */ boolean supportsEmailAddressChanges(); /** * Does the user service support changes to team memberships? * * @return true or false * @since 1.0.0 */ boolean supportsTeamMembershipChanges(); /** * 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 */ String 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); /** * Logout a user. * * @param user */ void logout(UserModel user); /** * 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); /** * Updates/writes all specified user objects. * * @param models a list of user models * @return true if update is successful * @since 1.2.0 */ boolean updateUserModels(Collection<UserModel> models); /** * 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 available to the login service. * * @return list of all users * @since 0.8.0 */ List<UserModel> getAllUsers(); /** * 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 teams available to the login service. * * @return list of all teams * @since 0.8.0 */ List<TeamModel> getAllTeams(); /** * 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 * @since 0.8.0 */ 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 * @since 0.8.0 */ @Deprecated 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 all specified team objects. * * @param models a list of team models * @return true if update is successful * @since 1.2.0 */ boolean updateTeamModels(Collection<TeamModel> models); /** * 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 * restriction placed on the specified repository. * * @param role * the repository name * @return list of all usernames that can bypass the access restriction * @since 0.8.0 */ 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 */ @Deprecated 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(); } src/com/gitblit/LdapUserService.java
@@ -24,6 +24,9 @@ import java.util.List; import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.gitblit.models.TeamModel; import com.gitblit.models.UserModel; import com.gitblit.utils.ArrayUtils; @@ -40,8 +43,6 @@ import com.unboundid.ldap.sdk.extensions.StartTLSExtendedRequest; import com.unboundid.util.ssl.SSLUtil; import com.unboundid.util.ssl.TrustAllTrustManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Implementation of an LDAP user service. @@ -122,15 +123,17 @@ } } for (UserModel user : ldapUsers.values()) { // Push the ldap looked up values to backing file super.updateUserModel(user); if (!supportsTeamMembershipChanges()) { for (TeamModel userTeam : user.teams) updateTeamModel(userTeam); } } super.updateUserModels(ldapUsers.values()); if (!supportsTeamMembershipChanges()) { final Map<String, TeamModel> userTeams = new HashMap<String, TeamModel>(); for (UserModel user : ldapUsers.values()) { for (TeamModel userTeam : user.teams) { userTeams.put(userTeam.name, userTeam); } } updateTeamModels(userTeams.values()); } } } finally { ldapConnection.close();