]> source.dussan.org Git - gitblit.git/commitdiff
significantly reduce write operations to backing user system.
authormschaefers <mschaefers@scoop-gmbh.de>
Fri, 30 Nov 2012 09:29:51 +0000 (10:29 +0100)
committermschaefers <mschaefers@scoop-gmbh.de>
Fri, 30 Nov 2012 09:29:51 +0000 (10:29 +0100)
src/com/gitblit/ConfigUserService.java
src/com/gitblit/FileUserService.java
src/com/gitblit/GitblitUserService.java
src/com/gitblit/IUserService.java
src/com/gitblit/LdapUserService.java

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