From 334d158ad313aaf56e33d55981f969fa05d8547e Mon Sep 17 00:00:00 2001 From: mschaefers <mschaefers@scoop-gmbh.de> Date: Tue, 04 Dec 2012 12:55:17 -0500 Subject: [PATCH] FIX: setting line separator back to Windows style --- src/com/gitblit/ConfigUserService.java | 2148 +++++++++++++++++++++++++++++----------------------------- 1 files changed, 1,074 insertions(+), 1,074 deletions(-) diff --git a/src/com/gitblit/ConfigUserService.java b/src/com/gitblit/ConfigUserService.java index 478d6b3..e3cdbe6 100644 --- a/src/com/gitblit/ConfigUserService.java +++ b/src/com/gitblit/ConfigUserService.java @@ -1,1074 +1,1074 @@ -/* - * Copyright 2011 gitblit.com. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.gitblit; - -import java.io.File; -import java.io.IOException; -import java.text.MessageFormat; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.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() + ")"; - } -} +/* + * 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() + ")"; + } +} -- Gitblit v1.9.1