James Moger
2013-11-20 8f1c9fd7e0f7ea3d7d0b87788eb92ba2f0f09d59
Extract UserManager from GitBlit singleton

Change-Id: I4885255ed63aa6c4e000c3e5501675440dca3958
1 files added
15 files modified
1039 ■■■■■ changed files
src/main/java/com/gitblit/ConfigUserService.java 8 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/DaggerModule.java 5 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/GitBlit.java 417 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/Gitblit.java 4 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/GitblitUserService.java 13 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/HtpasswdUserService.java 2 ●●● patch | view | raw | blame | history
src/main/java/com/gitblit/IUserService.java 11 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/LdapUserService.java 2 ●●● patch | view | raw | blame | history
src/main/java/com/gitblit/PAMUserService.java 2 ●●● patch | view | raw | blame | history
src/main/java/com/gitblit/RedmineUserService.java 2 ●●● patch | view | raw | blame | history
src/main/java/com/gitblit/SalesforceUserService.java 2 ●●● patch | view | raw | blame | history
src/main/java/com/gitblit/WindowsUserService.java 2 ●●● patch | view | raw | blame | history
src/main/java/com/gitblit/manager/IUserManager.java 4 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/manager/UserManager.java 542 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/utils/StringUtils.java 21 ●●●●● patch | view | raw | blame | history
src/test/java/de/akquinet/devops/GitBlit4UITests.java 2 ●●● patch | view | raw | blame | history
src/main/java/com/gitblit/ConfigUserService.java
@@ -35,6 +35,7 @@
import org.slf4j.LoggerFactory;
import com.gitblit.Constants.AccessPermission;
import com.gitblit.Constants.AccountType;
import com.gitblit.manager.IRuntimeManager;
import com.gitblit.models.TeamModel;
import com.gitblit.models.UserModel;
@@ -486,7 +487,7 @@
     * @return list of all usernames that can bypass the access restriction
     */
    @Override
    public synchronized List<String> getTeamnamesForRepositoryRole(String role) {
    public synchronized List<String> getTeamNamesForRepositoryRole(String role) {
        List<String> list = new ArrayList<String>();
        try {
            read();
@@ -1111,4 +1112,9 @@
    public String toString() {
        return getClass().getSimpleName() + "(" + realmFile.getAbsolutePath() + ")";
    }
    @Override
    public AccountType getAccountType() {
        return AccountType.LOCAL;
    }
}
src/main/java/com/gitblit/DaggerModule.java
@@ -30,6 +30,7 @@
import com.gitblit.manager.IUserManager;
import com.gitblit.manager.NotificationManager;
import com.gitblit.manager.RuntimeManager;
import com.gitblit.manager.UserManager;
import com.gitblit.wicket.GitBlitWebApp;
import com.gitblit.wicket.GitblitWicketFilter;
@@ -100,8 +101,8 @@
        return new NotificationManager(settings);
    }
    @Provides @Singleton IUserManager provideUserManager() {
        return gitblit;
    @Provides @Singleton IUserManager provideUserManager(IRuntimeManager runtimeManager) {
        return new UserManager(runtimeManager);
    }
    @Provides @Singleton ISessionManager provideSessionManager() {
src/main/java/com/gitblit/GitBlit.java
@@ -80,7 +80,6 @@
import com.gitblit.Constants.AccessPermission;
import com.gitblit.Constants.AccessRestrictionType;
import com.gitblit.Constants.AccountType;
import com.gitblit.Constants.AuthenticationType;
import com.gitblit.Constants.AuthorizationControl;
import com.gitblit.Constants.CommitMessageRenderer;
@@ -162,8 +161,7 @@
 */
@WebListener
public class GitBlit extends DaggerContextListener
                     implements IUserManager,
                                ISessionManager,
                     implements ISessionManager,
                                IRepositoryManager,
                                IProjectManager,
                                IFederationManager,
@@ -202,8 +200,6 @@
    private File repositoriesFolder;
    private IUserService userService;
    private IStoredSettings settings;
    private LuceneExecutor luceneExecutor;
@@ -221,13 +217,6 @@
    public GitBlit() {
        this.goSettings = null;
        this.goBaseFolder = null;
    }
    protected GitBlit(final IUserService userService) {
        this.goSettings = null;
        this.goBaseFolder = null;
        this.userService = userService;
        gitblit = this;
    }
    public GitBlit(IStoredSettings settings, File baseFolder) {
@@ -335,7 +324,7 @@
        if (user == null) {
            user = UserModel.ANONYMOUS;
        }
        String username = encodeUsername(UserModel.ANONYMOUS.equals(user) ? "" : user.username);
        String username = StringUtils.encodeUsername(UserModel.ANONYMOUS.equals(user) ? "" : user.username);
        List<RepositoryUrl> list = new ArrayList<RepositoryUrl>();
        // http/https url
@@ -486,75 +475,6 @@
    }
    /**
     * Set the user service. The user service authenticates all users and is
     * responsible for managing user permissions.
     *
     * @param userService
     */
    public void setUserService(IUserService userService) {
        logger.info("Setting up user service " + userService.toString());
        this.userService = userService;
        this.userService.setup(getManager(IRuntimeManager.class));
    }
    @Override
    public boolean supportsAddUser() {
        return supportsCredentialChanges(new UserModel(""));
    }
    /**
     * Returns true if the user's credentials can be changed.
     *
     * @param user
     * @return true if the user service supports credential changes
     */
    @Override
    public boolean supportsCredentialChanges(UserModel user) {
        if (user == null) {
            return false;
        } else if (AccountType.LOCAL.equals(user.accountType)) {
            // local account, we can change credentials
            return true;
        } else {
            // external account, ask user service
            return userService.supportsCredentialChanges();
        }
    }
    /**
     * Returns true if the user's display name can be changed.
     *
     * @param user
     * @return true if the user service supports display name changes
     */
    @Override
    public boolean supportsDisplayNameChanges(UserModel user) {
        return (user != null && user.isLocalAccount()) || userService.supportsDisplayNameChanges();
    }
    /**
     * Returns true if the user's email address can be changed.
     *
     * @param user
     * @return true if the user service supports email address changes
     */
    @Override
    public boolean supportsEmailAddressChanges(UserModel user) {
        return (user != null && user.isLocalAccount()) || userService.supportsEmailAddressChanges();
    }
    /**
     * Returns true if the user's team memberships can be changed.
     *
     * @param user
     * @return true if the user service supports team membership changes
     */
    @Override
    public boolean supportsTeamMembershipChanges(UserModel user) {
        return (user != null && user.isLocalAccount()) || userService.supportsTeamMembershipChanges();
    }
    /**
     * Returns true if the username represents an internal account
     *
     * @param username
@@ -580,7 +500,7 @@
            // can not authenticate empty username
            return null;
        }
        String usernameDecoded = decodeUsername(username);
        String usernameDecoded = StringUtils.decodeUsername(username);
        String pw = new String(password);
        if (StringUtils.isEmpty(pw)) {
            // can not authenticate empty password
@@ -598,10 +518,7 @@
        }
        // delegate authentication to the user service
        if (userService == null) {
            return null;
        }
        return userService.authenticate(usernameDecoded, password);
        return getManager(IUserManager.class).authenticate(usernameDecoded, password);
    }
    /**
@@ -611,15 +528,12 @@
     * @return a user object or null
     */
    protected UserModel authenticate(Cookie[] cookies) {
        if (userService == null) {
            return null;
        }
        if (userService.supportsCookies()) {
        if (getManager(IUserManager.class).supportsCookies()) {
            if (cookies != null && cookies.length > 0) {
                for (Cookie cookie : cookies) {
                    if (cookie.getName().equals(Constants.NAME)) {
                        String value = cookie.getValue();
                        return userService.authenticate(value.toCharArray());
                        return getManager(IUserManager.class).authenticate(value.toCharArray());
                    }
                }
            }
@@ -658,7 +572,7 @@
        UserModel model = HttpUtils.getUserModelFromCertificate(httpRequest, checkValidity, oids);
        if (model != null) {
            // grab real user model and preserve certificate serial number
            UserModel user = getUserModel(model.username);
            UserModel user = getManager(IUserManager.class).getUserModel(model.username);
            X509Metadata metadata = HttpUtils.getCertificateMetadata(httpRequest);
            if (user != null) {
                flagWicketSession(AuthenticationType.CERTIFICATE);
@@ -682,7 +596,7 @@
            String username = principal.getName();
            if (!StringUtils.isEmpty(username)) {
                boolean internalAccount = isInternalAccount(username);
                UserModel user = getUserModel(username);
                UserModel user = getManager(IUserManager.class).getUserModel(username);
                if (user != null) {
                    // existing user
                    flagWicketSession(AuthenticationType.CONTAINER);
@@ -695,7 +609,7 @@
                    user = new UserModel(username.toLowerCase());
                    user.displayName = username;
                    user.password = Constants.EXTERNAL_ACCOUNT;
                    userService.updateUserModel(user);
                    getManager(IUserManager.class).updateUserModel(user);
                    flagWicketSession(AuthenticationType.CONTAINER);
                    logger.debug(MessageFormat.format("{0} authenticated and created by servlet container principal from {1}",
                            user.username, httpRequest.getRemoteAddr()));
@@ -708,7 +622,7 @@
        }
        // try to authenticate by cookie
        if (supportsCookies()) {
        if (getManager(IUserManager.class).supportsCookies()) {
            UserModel user = authenticate(httpRequest.getCookies());
            if (user != null) {
                flagWicketSession(AuthenticationType.COOKIE);
@@ -726,7 +640,7 @@
            String credentials = new String(Base64.decode(base64Credentials),
                    Charset.forName("UTF-8"));
            // credentials = username:password
            final String[] values = credentials.split(":",2);
            final String[] values = credentials.split(":", 2);
            if (values.length == 2) {
                String username = values[0];
@@ -774,20 +688,17 @@
     */
    @Override
    public void setCookie(HttpServletResponse response, UserModel user) {
        if (userService == null) {
            return;
        }
        GitBlitWebSession session = GitBlitWebSession.get();
        boolean standardLogin = session.authenticationType.isStandard();
        if (userService.supportsCookies() && standardLogin) {
        if (getManager(IUserManager.class).supportsCookies() && standardLogin) {
            Cookie userCookie;
            if (user == null) {
                // clear cookie for logout
                userCookie = new Cookie(Constants.NAME, "");
            } else {
                // set cookie for login
                String cookie = userService.getCookie(user);
                String cookie = getManager(IUserManager.class).getCookie(user);
                if (StringUtils.isEmpty(cookie)) {
                    // create empty cookie
                    userCookie = new Cookie(Constants.NAME, "");
@@ -802,102 +713,12 @@
        }
    }
    /**
     * Logout a user.
     *
     * @param user
     */
    @Override
    public void logout(UserModel user) {
        if (userService == null) {
            return;
        }
        userService.logout(user);
    }
    /**
     * Encode the username for user in an url.
     *
     * @param name
     * @return the encoded name
     */
    protected String encodeUsername(String name) {
        return name.replace("@", "%40").replace(" ", "%20").replace("\\", "%5C");
    }
    /**
     * Decode a username from an encoded url.
     *
     * @param name
     * @return the decoded name
     */
    protected String decodeUsername(String name) {
        return name.replace("%40", "@").replace("%20", " ").replace("%5C", "\\");
    }
    /**
     * Returns the list of all users available to the login service.
     *
     * @see IUserService.getAllUsernames()
     * @return list of all usernames
     */
    @Override
    public List<String> getAllUsernames() {
        List<String> names = new ArrayList<String>(userService.getAllUsernames());
        return names;
    }
    /**
     * Returns the list of all users available to the login service.
     *
     * @see IUserService.getAllUsernames()
     * @return list of all usernames
     */
    @Override
    public List<UserModel> getAllUsers() {
        List<UserModel> users = userService.getAllUsers();
        return users;
    }
    /**
     * Delete the user object with the specified username
     *
     * @see IUserService.deleteUser(String)
     * @param username
     * @return true if successful
     */
    @Override
    public boolean deleteUser(String username) {
        if (StringUtils.isEmpty(username)) {
            return false;
        }
        String usernameDecoded = decodeUsername(username);
        return userService.deleteUser(usernameDecoded);
    }
    @Override
    public UserModel getFederationUser() {
        // the federation user is an administrator
        UserModel federationUser = new UserModel(Constants.FEDERATION_USER);
        federationUser.canAdmin = true;
        return federationUser;
    }
    /**
     * Retrieve the user object for the specified username.
     *
     * @see IUserService.getUserModel(String)
     * @param username
     * @return a user object or null
     */
    @Override
    public UserModel getUserModel(String username) {
        if (StringUtils.isEmpty(username)) {
            return null;
        }
        String usernameDecoded = decodeUsername(username);
        UserModel user = userService.getUserModel(usernameDecoded);
        return user;
    }
    /**
@@ -965,7 +786,7 @@
            return list;
        }
        // NAMED users and teams
        for (UserModel user : userService.getAllUsers()) {
        for (UserModel user : getManager(IUserManager.class).getAllUsers()) {
            RegistrantAccessPermission ap = user.getRepositoryPermission(repository);
            if (ap.permission.exceeds(AccessPermission.NONE)) {
                list.add(ap);
@@ -987,12 +808,12 @@
        for (RegistrantAccessPermission up : permissions) {
            if (up.mutable) {
                // only set editable defined permissions
                UserModel user = userService.getUserModel(up.registrant);
                UserModel user = getManager(IUserManager.class).getUserModel(up.registrant);
                user.setRepositoryPermission(repository.name, up.permission);
                users.add(user);
            }
        }
        return userService.updateUserModels(users);
        return getManager(IUserManager.class).updateUserModels(users);
    }
    /**
@@ -1005,7 +826,7 @@
     */
    @Override
    public List<String> getRepositoryUsers(RepositoryModel repository) {
        return userService.getUsernamesForRepositoryRole(repository.name);
        return getManager(IUserManager.class).getUsernamesForRepositoryRole(repository.name);
    }
    /**
@@ -1038,7 +859,7 @@
    public void updateUserModel(String username, UserModel user, boolean isCreate)
            throws GitBlitException {
        if (!username.equalsIgnoreCase(user.username)) {
            if (userService.getUserModel(user.username) != null) {
            if (getManager(IUserManager.class).getUserModel(user.username) != null) {
                throw new GitBlitException(MessageFormat.format(
                        "Failed to rename ''{0}'' because ''{1}'' already exists.", username,
                        user.username));
@@ -1060,43 +881,9 @@
                }
            }
        }
        if (!userService.updateUserModel(username, user)) {
        if (!getManager(IUserManager.class).updateUserModel(username, user)) {
            throw new GitBlitException(isCreate ? "Failed to add user!" : "Failed to update user!");
        }
    }
    /**
     * Returns the list of available teams that a user or repository may be
     * assigned to.
     *
     * @return the list of teams
     */
    public List<String> getAllTeamnames() {
        List<String> teams = new ArrayList<String>(userService.getAllTeamNames());
        return teams;
    }
    /**
     * Returns the list of available teams that a user or repository may be
     * assigned to.
     *
     * @return the list of teams
     */
    @Override
    public List<TeamModel> getAllTeams() {
        List<TeamModel> teams = userService.getAllTeams();
        return teams;
    }
    /**
     * Returns the TeamModel object for the specified name.
     *
     * @param teamname
     * @return a TeamModel object or null
     */
    @Override
    public TeamModel getTeamModel(String teamname) {
        return userService.getTeamModel(teamname);
    }
    /**
@@ -1110,7 +897,7 @@
    @Override
    public List<RegistrantAccessPermission> getTeamAccessPermissions(RepositoryModel repository) {
        List<RegistrantAccessPermission> list = new ArrayList<RegistrantAccessPermission>();
        for (TeamModel team : userService.getAllTeams()) {
        for (TeamModel team : getManager(IUserManager.class).getAllTeams()) {
            RegistrantAccessPermission ap = team.getRepositoryPermission(repository);
            if (ap.permission.exceeds(AccessPermission.NONE)) {
                list.add(ap);
@@ -1133,12 +920,12 @@
        for (RegistrantAccessPermission tp : permissions) {
            if (tp.mutable) {
                // only set explicitly defined access permissions
                TeamModel team = userService.getTeamModel(tp.registrant);
                TeamModel team = getManager(IUserManager.class).getTeamModel(tp.registrant);
                team.setRepositoryPermission(repository.name, tp.permission);
                teams.add(team);
            }
        }
        return userService.updateTeamModels(teams);
        return getManager(IUserManager.class).updateTeamModels(teams);
    }
    /**
@@ -1151,7 +938,7 @@
     */
    @Override
    public List<String> getRepositoryTeams(RepositoryModel repository) {
        return userService.getTeamnamesForRepositoryRole(repository.name);
        return getManager(IUserManager.class).getTeamNamesForRepositoryRole(repository.name);
    }
    /**
@@ -1181,27 +968,15 @@
    public void updateTeamModel(String teamname, TeamModel team, boolean isCreate)
            throws GitBlitException {
        if (!teamname.equalsIgnoreCase(team.name)) {
            if (userService.getTeamModel(team.name) != null) {
            if (getManager(IUserManager.class).getTeamModel(team.name) != null) {
                throw new GitBlitException(MessageFormat.format(
                        "Failed to rename ''{0}'' because ''{1}'' already exists.", teamname,
                        team.name));
            }
        }
        if (!userService.updateTeamModel(teamname, team)) {
        if (!getManager(IUserManager.class).updateTeamModel(teamname, team)) {
            throw new GitBlitException(isCreate ? "Failed to add team!" : "Failed to update team!");
        }
    }
    /**
     * Delete the team object with the specified teamname
     *
     * @see IUserService.deleteTeam(String)
     * @param teamname
     * @return true if successful
     */
    @Override
    public boolean deleteTeam(String teamname) {
        return userService.deleteTeam(teamname);
    }
    /**
@@ -1519,7 +1294,7 @@
    @Override
    public long getStarCount(RepositoryModel repository) {
        long count = 0;
        for (UserModel user : getAllUsers()) {
        for (UserModel user : getManager(IUserManager.class).getAllUsers()) {
            if (user.getPreferences().isStarredRepository(repository.name)) {
                count++;
            }
@@ -1680,7 +1455,7 @@
        if (project == null) {
            project = new ProjectModel(name);
            if (ModelUtils.isPersonalRepository(name)) {
                UserModel user = getUserModel(ModelUtils.getUserNameFromRepoPath(name));
                UserModel user = getManager(IUserManager.class).getUserModel(ModelUtils.getUserNameFromRepoPath(name));
                if (user != null) {
                    project.title = user.getDisplayName();
                    project.description = "personal repositories";
@@ -2297,7 +2072,7 @@
                            repository.name));
                }
                // rename the roles
                if (!userService.renameRepositoryRole(repositoryName, repository.name)) {
                if (!getManager(IUserManager.class).renameRepositoryRole(repositoryName, repository.name)) {
                    throw new GitBlitException(MessageFormat.format(
                            "Failed to rename repository permissions ''{0}'' to ''{1}''.",
                            repositoryName, repository.name));
@@ -2514,7 +2289,7 @@
            File folder = new File(repositoriesFolder, repositoryName);
            if (folder.exists() && folder.isDirectory()) {
                FileUtils.delete(folder, FileUtils.RECURSIVE | FileUtils.RETRY);
                if (userService.deleteRepositoryRole(repositoryName)) {
                if (getManager(IUserManager.class).deleteRepositoryRole(repositoryName)) {
                    logger.info(MessageFormat.format("Repository \"{0}\" deleted", repositoryName));
                    return true;
                }
@@ -3046,8 +2821,8 @@
        // Team Scripts
        if (repository != null) {
            for (String teamname : userService.getTeamnamesForRepositoryRole(repository.name)) {
                TeamModel team = userService.getTeamModel(teamname);
            for (String teamname : getManager(IUserManager.class).getTeamNamesForRepositoryRole(repository.name)) {
                TeamModel team = getManager(IUserManager.class).getTeamModel(teamname);
                if (!ArrayUtils.isEmpty(team.preReceiveScripts)) {
                    scripts.addAll(team.preReceiveScripts);
                }
@@ -3100,8 +2875,8 @@
        }
        // Team Scripts
        if (repository != null) {
            for (String teamname : userService.getTeamnamesForRepositoryRole(repository.name)) {
                TeamModel team = userService.getTeamModel(teamname);
            for (String teamname : getManager(IUserManager.class).getTeamNamesForRepositoryRole(repository.name)) {
                TeamModel team = getManager(IUserManager.class).getTeamModel(teamname);
                if (!ArrayUtils.isEmpty(team.postReceiveScripts)) {
                    scripts.addAll(team.postReceiveScripts);
                }
@@ -3156,10 +2931,14 @@
     * @return Map<String, SettingModel>
     */
    private ServerSettings loadSettingModels(ServerSettings settingsModel) {
        settingsModel.supportsCredentialChanges = userService.supportsCredentialChanges();
        settingsModel.supportsDisplayNameChanges = userService.supportsDisplayNameChanges();
        settingsModel.supportsEmailAddressChanges = userService.supportsEmailAddressChanges();
        settingsModel.supportsTeamMembershipChanges = userService.supportsTeamMembershipChanges();
        // this entire "supports" concept will go away with user service refactoring
        UserModel externalUser = new UserModel(Constants.EXTERNAL_ACCOUNT);
        externalUser.password = Constants.EXTERNAL_ACCOUNT;
        IUserManager userManager = getManager(IUserManager.class);
        settingsModel.supportsCredentialChanges = userManager.supportsCredentialChanges(externalUser);
        settingsModel.supportsDisplayNameChanges = userManager.supportsDisplayNameChanges(externalUser);
        settingsModel.supportsEmailAddressChanges = userManager.supportsEmailAddressChanges(externalUser);
        settingsModel.supportsTeamMembershipChanges = userManager.supportsTeamMembershipChanges(externalUser);
        try {
            // Read bundled Gitblit properties to extract setting descriptions.
            // This copy is pristine and only used for populating the setting
@@ -3321,7 +3100,7 @@
                Gitblit gitblit = new Gitblit(
                        getManager(IRuntimeManager.class),
                        getManager(INotificationManager.class),
                        this,
                        getManager(IUserManager.class),
                        this,
                        this,
                        this,
@@ -3430,6 +3209,7 @@
        runtime.getStatus().servletContainer = context.getServerInfo();
        startManager(injector, INotificationManager.class);
        startManager(injector, IUserManager.class);
        repositoriesFolder = getRepositoriesFolder();
@@ -3452,19 +3232,6 @@
        if (runtimeSettings.getBoolean(Keys.git.cacheRepositoryList,  true)) {
            logger.info("Identifying available repositories...");
            getRepositoryList();
        }
        if (this.userService == null) {
            String realm = runtimeSettings.getString(Keys.realm.userService, "${baseFolder}/users.properties");
            IUserService loginService = null;
            try {
                // check to see if this "file" is a login service class
                Class<?> realmClass = Class.forName(realm);
                loginService = (IUserService) realmClass.newInstance();
            } catch (Throwable t) {
                loginService = new GitblitUserService();
            }
            setUserService(loginService);
        }
        loadSettingModels(runtime.getSettingsModel());
@@ -3747,7 +3514,7 @@
        // add the owner of the source repository to the clone's access list
        if (!ArrayUtils.isEmpty(repository.owners)) {
            for (String owner : repository.owners) {
                UserModel originOwner = getUserModel(owner);
                UserModel originOwner = getManager(IUserManager.class).getUserModel(owner);
                if (originOwner != null) {
                    originOwner.setRepositoryPermission(cloneName, AccessPermission.CLONE);
                    updateUserModel(originOwner.username, originOwner, false);
@@ -3760,7 +3527,7 @@
        List<UserModel> cloneUsers = new ArrayList<UserModel>();
        for (String name : users) {
            if (!name.equalsIgnoreCase(user.username)) {
                UserModel cloneUser = getUserModel(name);
                UserModel cloneUser = getManager(IUserManager.class).getUserModel(name);
                if (cloneUser.canClone(repository)) {
                    // origin user can clone origin, grant clone access to fork
                    cloneUser.setRepositoryPermission(cloneName, AccessPermission.CLONE);
@@ -3768,116 +3535,30 @@
                cloneUsers.add(cloneUser);
            }
        }
        userService.updateUserModels(cloneUsers);
        getManager(IUserManager.class).updateUserModels(cloneUsers);
        // grant origin's team list clone permission to fork
        List<String> teams = getRepositoryTeams(repository);
        List<TeamModel> cloneTeams = new ArrayList<TeamModel>();
        for (String name : teams) {
            TeamModel cloneTeam = getTeamModel(name);
            TeamModel cloneTeam = getManager(IUserManager.class).getTeamModel(name);
            if (cloneTeam.canClone(repository)) {
                // origin team can clone origin, grant clone access to fork
                cloneTeam.setRepositoryPermission(cloneName, AccessPermission.CLONE);
            }
            cloneTeams.add(cloneTeam);
        }
        userService.updateTeamModels(cloneTeams);
        getManager(IUserManager.class).updateTeamModels(cloneTeams);
        // add this clone to the cached model
        addToCachedRepositoryList(cloneModel);
        return cloneModel;
    }
    /**
     * Allow to understand if GitBlit supports and is configured to allow
     * cookie-based authentication.
     *
     * @return status of Cookie authentication enablement.
     */
    @Override
    public boolean supportsCookies() {
        return settings.getBoolean(Keys.web.allowCookieAuthentication, true) && userService.supportsCookies();
    }
    @Override
    public String getCookie(UserModel model) {
        return userService.getCookie(model);
    }
    @Override
    public UserModel authenticate(char[] cookie) {
        return userService.authenticate(cookie);
    }
    @Override
    public boolean updateUserModel(UserModel model) {
        return userService.updateUserModel(model);
    }
    @Override
    public boolean updateUserModels(Collection<UserModel> models) {
        return userService.updateUserModels(models);
    }
    @Override
    public boolean updateUserModel(String username, UserModel model) {
        return userService.updateUserModel(username, model);
    }
    @Override
    public boolean deleteUserModel(UserModel model) {
        return userService.deleteUserModel(model);
    }
    @Override
    public List<String> getAllTeamNames() {
        return userService.getAllTeamNames();
    }
    @Override
    public List<String> getTeamnamesForRepositoryRole(String role) {
        return userService.getTeamnamesForRepositoryRole(role);
    }
    @Override
    public boolean updateTeamModel(TeamModel model) {
        return userService.updateTeamModel(model);
    }
    @Override
    public boolean updateTeamModels(Collection<TeamModel> models) {
        return userService.updateTeamModels(models);
    }
    @Override
    public boolean updateTeamModel(String teamname, TeamModel model) {
        return userService.updateTeamModel(teamname, model);
    }
    @Override
    public boolean deleteTeamModel(TeamModel model) {
        return userService.deleteTeamModel(model);
    }
    @Override
    public List<String> getUsernamesForRepositoryRole(String role) {
        return userService.getUsernamesForRepositoryRole(role);
    }
    @Override
    public boolean renameRepositoryRole(String oldRole, String newRole) {
        return userService.renameRepositoryRole(oldRole, newRole);
    }
    @Override
    public boolean deleteRepositoryRole(String role) {
        return userService.deleteRepositoryRole(role);
    }
    @Override
    public void logout(HttpServletResponse response, UserModel user) {
        setCookie(response,  null);
        userService.logout(user);
        getManager(IUserManager.class).logout(user);
    }
    @Override
src/main/java/com/gitblit/Gitblit.java
@@ -348,8 +348,8 @@
    }
    @Override
    public List<String> getTeamnamesForRepositoryRole(String role) {
        return userManager.getTeamnamesForRepositoryRole(role);
    public List<String> getTeamNamesForRepositoryRole(String role) {
        return userManager.getTeamNamesForRepositoryRole(role);
    }
    @Override
src/main/java/com/gitblit/GitblitUserService.java
@@ -126,6 +126,12 @@
        return serviceImpl.getCookie(model);
    }
    /**
     * Authenticate a user based on their cookie.
     *
     * @param cookie
     * @return a user object or null
     */
    @Override
    public UserModel authenticate(char[] cookie) {
        UserModel user = serviceImpl.authenticate(cookie);
@@ -226,8 +232,8 @@
    }
    @Override
    public List<String> getTeamnamesForRepositoryRole(String role) {
        return serviceImpl.getTeamnamesForRepositoryRole(role);
    public List<String> getTeamNamesForRepositoryRole(String role) {
        return serviceImpl.getTeamNamesForRepositoryRole(role);
    }
    @Override
@@ -312,7 +318,8 @@
        }
    }
    protected AccountType getAccountType() {
    @Override
    public AccountType getAccountType() {
        return AccountType.LOCAL;
    }
}
src/main/java/com/gitblit/HtpasswdUserService.java
@@ -275,7 +275,7 @@
     * @return AccountType.HTPASSWD
     */
    @Override
    protected AccountType getAccountType()
    public AccountType getAccountType()
    {
        return AccountType.HTPASSWD;
    }
src/main/java/com/gitblit/IUserService.java
@@ -18,6 +18,7 @@
import java.util.Collection;
import java.util.List;
import com.gitblit.Constants.AccountType;
import com.gitblit.manager.IRuntimeManager;
import com.gitblit.models.TeamModel;
import com.gitblit.models.UserModel;
@@ -205,7 +206,7 @@
     * @return list of all usernames that can bypass the access restriction
     * @since 0.8.0
     */
    List<String> getTeamnamesForRepositoryRole(String role);
    List<String> getTeamNamesForRepositoryRole(String role);
    /**
     * Sets the list of all teams who are allowed to bypass the access
@@ -319,6 +320,14 @@
    boolean deleteRepositoryRole(String role);
    /**
     * Returns the account type for the user models.
     *
     * @return the account type
     * @since 1.4.0
     */
    AccountType getAccountType();
    /**
     * @See java.lang.Object.toString();
     * @return string representation of the login service
     */
src/main/java/com/gitblit/LdapUserService.java
@@ -262,7 +262,7 @@
    }
    @Override
    protected AccountType getAccountType() {
    public AccountType getAccountType() {
         return AccountType.LDAP;
    }
src/main/java/com/gitblit/PAMUserService.java
@@ -92,7 +92,7 @@
    }
     @Override
    protected AccountType getAccountType() {
    public AccountType getAccountType() {
        return AccountType.PAM;
    }
src/main/java/com/gitblit/RedmineUserService.java
@@ -91,7 +91,7 @@
    }
     @Override
    protected AccountType getAccountType() {
    public AccountType getAccountType() {
        return AccountType.REDMINE;
    }
src/main/java/com/gitblit/SalesforceUserService.java
@@ -22,7 +22,7 @@
    private IStoredSettings settings;
    @Override
    protected AccountType getAccountType() {
    public AccountType getAccountType() {
        return AccountType.SALESFORCE;
    }
src/main/java/com/gitblit/WindowsUserService.java
@@ -104,7 +104,7 @@
    }
     @Override
    protected AccountType getAccountType() {
    public AccountType getAccountType() {
        return AccountType.WINDOWS;
    }
src/main/java/com/gitblit/manager/IUserManager.java
@@ -21,7 +21,7 @@
import com.gitblit.models.TeamModel;
import com.gitblit.models.UserModel;
public interface IUserManager {
public interface IUserManager extends IManager {
    boolean supportsAddUser();
@@ -189,7 +189,7 @@
     * @return list of all usernames that can bypass the access restriction
     * @since 0.8.0
     */
    List<String> getTeamnamesForRepositoryRole(String role);
    List<String> getTeamNamesForRepositoryRole(String role);
    /**
     * Retrieve the team object for the specified team name.
src/main/java/com/gitblit/manager/UserManager.java
New file
@@ -0,0 +1,542 @@
/*
 * Copyright 2013 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.manager;
import java.io.File;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.gitblit.ConfigUserService;
import com.gitblit.Constants;
import com.gitblit.Constants.AccountType;
import com.gitblit.IStoredSettings;
import com.gitblit.IUserService;
import com.gitblit.Keys;
import com.gitblit.models.TeamModel;
import com.gitblit.models.UserModel;
import com.gitblit.utils.DeepCopier;
import com.gitblit.utils.StringUtils;
/**
 * The user manager manages persistence and retrieval of users and teams.
 *
 * @author James Moger
 *
 */
public class UserManager implements IUserManager {
    private final Logger logger = LoggerFactory.getLogger(getClass());
    private final IStoredSettings settings;
    private final IRuntimeManager runtimeManager;
    private IUserService userService;
    public UserManager(IRuntimeManager runtimeManager) {
        this.settings = runtimeManager.getSettings();
        this.runtimeManager = runtimeManager;
    }
    /**
     * Set the user service. The user service authenticates local users and is
     * responsible for persisting and retrieving users and teams.
     *
     * @param userService
     */
    public void setUserService(IUserService userService) {
        logger.info("Setting up user service " + userService.toString());
        this.userService = userService;
        this.userService.setup(runtimeManager);
    }
    @Override
    public IManager setup() {
        if (this.userService == null) {
            String realm = settings.getString(Keys.realm.userService, "${baseFolder}/users.properties");
            IUserService service = null;
            try {
                // check to see if this "file" is a login service class
                Class<?> realmClass = Class.forName(realm);
                service = (IUserService) realmClass.newInstance();
            } catch (Throwable t) {
                File realmFile = runtimeManager.getFileOrFolder(Keys.realm.userService, "${baseFolder}/users.conf");
                service = createUserService(realmFile);
            }
            setUserService(service);
        }
        return this;
    }
    protected IUserService createUserService(File realmFile) {
        IUserService service = null;
        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);
        }
        return service;
    }
    @Override
    public IManager stop() {
        return this;
    }
    @Override
    public boolean supportsAddUser() {
        return supportsCredentialChanges(new UserModel(""));
    }
    /**
     * Returns true if the user's credentials can be changed.
     *
     * @param user
     * @return true if the user service supports credential changes
     */
    @Override
    public boolean supportsCredentialChanges(UserModel user) {
        if (user == null) {
            return false;
        } else if (AccountType.LOCAL.equals(user.accountType)) {
            // local account, we can change credentials
            return true;
        } else {
            // external account, ask user service
            return userService.supportsCredentialChanges();
        }
    }
    /**
     * Returns true if the user's display name can be changed.
     *
     * @param user
     * @return true if the user service supports display name changes
     */
    @Override
    public boolean supportsDisplayNameChanges(UserModel user) {
        return (user != null && user.isLocalAccount()) || userService.supportsDisplayNameChanges();
    }
    /**
     * Returns true if the user's email address can be changed.
     *
     * @param user
     * @return true if the user service supports email address changes
     */
    @Override
    public boolean supportsEmailAddressChanges(UserModel user) {
        return (user != null && user.isLocalAccount()) || userService.supportsEmailAddressChanges();
    }
    /**
     * Returns true if the user's team memberships can be changed.
     *
     * @param user
     * @return true if the user service supports team membership changes
     */
    @Override
    public boolean supportsTeamMembershipChanges(UserModel user) {
        return (user != null && user.isLocalAccount()) || userService.supportsTeamMembershipChanges();
    }
    /**
     * Allow to understand if GitBlit supports and is configured to allow
     * cookie-based authentication.
     *
     * @return status of Cookie authentication enablement.
     */
    @Override
    public boolean supportsCookies() {
        return settings.getBoolean(Keys.web.allowCookieAuthentication, true) && userService.supportsCookies();
    }
    /**
     * Returns the cookie value for the specified user.
     *
     * @param model
     * @return cookie value
     */
    @Override
    public String getCookie(UserModel model) {
        return userService.getCookie(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) {
        UserModel user = userService.authenticate(username, password);
        setAccountType(user);
        return user;
    }
    /**
     * Authenticate a user based on their cookie.
     *
     * @param cookie
     * @return a user object or null
     */
    @Override
    public UserModel authenticate(char[] cookie) {
        UserModel user = userService.authenticate(cookie);
        setAccountType(user);
        return user;
    }
    /**
     * Logout a user.
     *
     * @param user
     */
    @Override
    public void logout(UserModel user) {
        if (userService == null) {
            return;
        }
        userService.logout(user);
    }
    /**
     * Retrieve the user object for the specified username.
     *
     * @param username
     * @return a user object or null
     */
    @Override
    public UserModel getUserModel(String username) {
        if (StringUtils.isEmpty(username)) {
            return null;
        }
        String usernameDecoded = StringUtils.decodeUsername(username);
        UserModel user = userService.getUserModel(usernameDecoded);
        setAccountType(user);
        return user;
    }
    /**
     * Updates/writes a complete user object.
     *
     * @param model
     * @return true if update is successful
     */
    @Override
    public boolean updateUserModel(UserModel model) {
        return userService.updateUserModel(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) {
        return userService.updateUserModels(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
     */
    @Override
    public boolean updateUserModel(String username, UserModel model) {
        if (model.isLocalAccount() || userService.supportsCredentialChanges()) {
            if (!model.isLocalAccount() && !userService.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 userService.updateUserModel(username, model);
        }
        if (model.username.equals(username)) {
            // passwords are not persisted by the backing user service
            model.password = null;
            if (!model.isLocalAccount() && !userService.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 userService.updateUserModel(username, model);
        }
        logger.error("Users can not be renamed!");
        return false;
    }
    /**
     * Deletes the user object from the user service.
     *
     * @param model
     * @return true if successful
     */
    @Override
    public boolean deleteUserModel(UserModel model) {
        return userService.deleteUserModel(model);
    }
    /**
     * Delete the user object with the specified username
     *
     * @param username
     * @return true if successful
     */
    @Override
    public boolean deleteUser(String username) {
        if (StringUtils.isEmpty(username)) {
            return false;
        }
        String usernameDecoded = StringUtils.decodeUsername(username);
        return userService.deleteUser(usernameDecoded);
    }
    /**
     * Returns the list of all users available to the login service.
     *
     * @return list of all usernames
     */
    @Override
    public List<String> getAllUsernames() {
        List<String> names = new ArrayList<String>(userService.getAllUsernames());
        return names;
    }
    /**
     * Returns the list of all users available to the login service.
     *
     * @return list of all users
     * @since 0.8.0
     */
    @Override
    public List<UserModel> getAllUsers() {
        List<UserModel> users = userService.getAllUsers();
        for (UserModel user : users) {
            setAccountType(user);
        }
        return users;
    }
    /**
     * 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() {
        return userService.getAllTeamNames();
    }
    /**
     * 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> teams = userService.getAllTeams();
        return teams;
    }
    /**
     * 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 teams that can bypass the access restriction
     * @since 0.8.0
     */
    @Override
    public List<String> getTeamNamesForRepositoryRole(String role) {
        return userService.getTeamNamesForRepositoryRole(role);
    }
    /**
     * 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) {
        return userService.getTeamModel(teamname);
    }
    /**
     * 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 userService.updateTeamModel(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) {
        return userService.updateTeamModels(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
     */
    @Override
    public boolean updateTeamModel(String teamname, TeamModel model) {
        if (!userService.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 userService.updateTeamModel(teamname, 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 userService.deleteTeamModel(model);
    }
    /**
     * Delete the team object with the specified teamname
     *
     * @param teamname
     * @return true if successful
     * @since 0.8.0
     */
    @Override
    public boolean deleteTeam(String teamname) {
        return userService.deleteTeam(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
     */
    @Override
    public List<String> getUsernamesForRepositoryRole(String role) {
        return userService.getUsernamesForRepositoryRole(role);
    }
    /**
     * Renames a repository role.
     *
     * @param oldRole
     * @param newRole
     * @return true if successful
     */
    @Override
    public boolean renameRepositoryRole(String oldRole, String newRole) {
        return userService.renameRepositoryRole(oldRole, newRole);
    }
    /**
     * Removes a repository role from all users.
     *
     * @param role
     * @return true if successful
     */
    @Override
    public boolean deleteRepositoryRole(String role) {
        return userService.deleteRepositoryRole(role);
    }
    protected void setAccountType(UserModel user) {
        if (user != null) {
            if (!StringUtils.isEmpty(user.password)
                    && !Constants.EXTERNAL_ACCOUNT.equalsIgnoreCase(user.password)
                    && !"StoredInLDAP".equalsIgnoreCase(user.password)) {
                user.accountType = AccountType.LOCAL;
            } else {
                user.accountType = userService.getAccountType();
            }
        }
    }
}
src/main/java/com/gitblit/utils/StringUtils.java
@@ -747,4 +747,25 @@
        }
        return input.replace('\n',' ').replace('\r',  ' ').trim();
    }
    /**
     * Encode the username for user in an url.
     *
     * @param name
     * @return the encoded name
     */
    public static String encodeUsername(String name) {
        return name.replace("@", "%40").replace(" ", "%20").replace("\\", "%5C");
    }
    /**
     * Decode a username from an encoded url.
     *
     * @param name
     * @return the decoded name
     */
    public static String decodeUsername(String name) {
        return name.replace("%40", "@").replace("%20", " ").replace("%5C", "\\");
    }
}
src/test/java/de/akquinet/devops/GitBlit4UITests.java
@@ -9,7 +9,7 @@
    private boolean luceneIndexingEnabled;
    public GitBlit4UITests(boolean luceneIndexingEnabled) {
        super(null);
        super();
        this.luceneIndexingEnabled = luceneIndexingEnabled;
    }