James Moger
2013-11-24 04a98505a4ab8f48aee22800fcac193d9367d0ae
Refactor user services and separate authentication (issue-281)

Change-Id: I336e005e02623fc5e11a4f8b4408bea5465a43fd
5 files added
12 files renamed
40 files modified
4 files deleted
6117 ■■■■■ changed files
releases.moxie 4 ●●● patch | view | raw | blame | history
src/main/distrib/data/gitblit.properties 98 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/ConfigUserService.java 202 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/Constants.java 11 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/DaggerModule.java 18 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/GitBlit.java 73 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/GitblitUserService.java 325 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/IUserService.java 93 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/auth/AuthenticationProvider.java 182 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/auth/HtpasswdAuthProvider.java 219 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/auth/LdapAuthProvider.java 1038 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/auth/PAMAuthProvider.java 269 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/auth/RedmineAuthProvider.java 389 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/auth/SalesforceAuthProvider.java 45 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/auth/WindowsAuthProvider.java 372 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/client/EditTeamDialog.java 1 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/client/EditUserDialog.java 16 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/git/GitblitUploadPackFactory.java 10 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/manager/AuthenticationManager.java 511 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/manager/GitblitManager.java 7 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/manager/IAuthenticationManager.java 43 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/manager/IUserManager.java 261 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/manager/SessionManager.java 340 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/manager/UserManager.java 221 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/models/ServerSettings.java 8 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/models/TeamModel.java 7 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/servlet/AccessRestrictionFilter.java 6 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/servlet/AuthenticationFilter.java 10 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/servlet/DownloadZipFilter.java 6 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/servlet/EnforceAuthenticationFilter.java 10 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/servlet/GitFilter.java 6 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/servlet/GitblitContext.java 4 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/servlet/PagesFilter.java 6 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/servlet/RpcFilter.java 6 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/servlet/SparkleShareInviteServlet.java 10 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/servlet/SyndicationFilter.java 6 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/GitBlitWebApp.java 12 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/ChangePasswordPage.java 4 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/EditTeamPage.java 2 ●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/EditUserPage.java 14 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/LogoutPage.java 2 ●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/RootPage.java 6 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/SessionPage.java 4 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/panels/TeamsPanel.java 2 ●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/panels/UsersPanel.java 3 ●●●● patch | view | raw | blame | history
src/site/setup_authentication.mkd 23 ●●●●● patch | view | raw | blame | history
src/test/config/test-users.conf 6 ●●●●● patch | view | raw | blame | history
src/test/java/com/gitblit/tests/GitBlitSuite.java 4 ●●●● patch | view | raw | blame | history
src/test/java/com/gitblit/tests/GitBlitTest.java 2 ●●● patch | view | raw | blame | history
src/test/java/com/gitblit/tests/GitblitUnitTest.java 6 ●●●● patch | view | raw | blame | history
src/test/java/com/gitblit/tests/HtpasswdAuthenticationTest.java 365 ●●●●● patch | view | raw | blame | history
src/test/java/com/gitblit/tests/HtpasswdUserServiceTest.java 569 ●●●●● patch | view | raw | blame | history
src/test/java/com/gitblit/tests/LdapAuthenticationTest.java 69 ●●●● patch | view | raw | blame | history
src/test/java/com/gitblit/tests/RedmineAuthenticationTest.java 65 ●●●●● patch | view | raw | blame | history
src/test/java/com/gitblit/tests/RedmineUserServiceTest.java 66 ●●●●● patch | view | raw | blame | history
src/test/java/com/gitblit/tests/UserServiceTest.java 7 ●●●● patch | view | raw | blame | history
src/test/resources/htpasswd/htpasswd-user.in patch | view | raw | blame | history
src/test/resources/htpasswd/htpasswd.in patch | view | raw | blame | history
src/test/resources/htpasswd/users.conf.in patch | view | raw | blame | history
src/test/resources/ldap/sampledata.ldif patch | view | raw | blame | history
src/test/resources/ldap/users.conf 53 ●●●●● patch | view | raw | blame | history
releases.moxie
@@ -20,6 +20,7 @@
    changes:
    - Gitblit now rejects pushes to mirror repositories (issue-5)
    - Personal repository prefix (~) is now configurable (issue-265)
    - Refactored user services and separated authentication into providers (issue-281)
    - Reversed line links in blob view (issue-309)
    - Dashboard and Activity pages now obey the web.generateActivityGraph setting (issue-310)
    - Do not log passwords on failed authentication attempts (issue-316)
@@ -58,11 +59,12 @@
    - { name: 'git.enableMirroring', defaultValue: 'false' }
    - { name: 'git.defaultAccessRestriction', defaultValue: 'PUSH' }
    - { name: 'git.mirrorPeriod', defaultValue: '30 mins' }
    - { name: 'realm.authenticationProviders', defaultValue: ' ' }
    - { name: 'web.commitMessageRenderer', defaultValue: 'plain' }
    - { name: 'web.documents', defaultValue: 'readme home index changelog contributing submitting_patches copying license notice authors' }
    - { name: 'web.showBranchGraph', defaultValue: 'true' }
    - { name: 'web.summaryShowReadme', defaultValue: 'false' }
    - { name: 'server.redirectToHttpsPort', defaultValue: 'true' }
    - { name: 'server.redirectToHttpsPort', defaultValue: 'false' }
    contributors:
    - James Moger
    - Robin Rosenberg
src/main/distrib/data/gitblit.properties
@@ -562,16 +562,7 @@
web.projectsFile = ${baseFolder}/projects.conf
# Either the full path to a user config file (users.conf)
# OR the full path to a simple user properties file (users.properties)
# OR a fully qualified class name that implements the IUserService interface.
#
# Alternative user services:
#    com.gitblit.LdapUserService
#    com.gitblit.RedmineUserService
#    com.gitblit.SalesforceUserService
#    com.gitblit.WindowsUserService
#    com.gitblit.PAMUserService
#    com.gitblit.HtpasswdUserService
#
# Any custom user service implementation must have a public default constructor.
#
@@ -579,6 +570,25 @@
# RESTART REQUIRED
# BASEFOLDER
realm.userService = ${baseFolder}/users.conf
# Ordered list of external authentication providers which will be used if
# authentication against the local user service fails.
#
# Valid providers are:
#
#    htpasswd
#    ldap
#    pam
#    redmine
#    salesforce
#    windows
# e.g. realm.authenticationProviders = htpasswd windows
#
# SINCE 1.4.0
# RESTART REQUIRED
# SPACE-DELIMITED
realm.authenticationProviders =
# How to store passwords.
# Valid values are plain, md5, or combined-md5.  md5 is the hash of password.
@@ -1331,15 +1341,6 @@
# SINCE 1.3.0
realm.container.autoCreateAccounts = false
# The WindowsUserService must be backed by another user service for standard user
# and team management.
# default: users.conf
#
# RESTART REQUIRED
# BASEFOLDER
# SINCE 1.3.0
realm.windows.backingUserService = ${baseFolder}/users.conf
# Allow or prohibit Windows guest account logins
#
# SINCE 1.3.0
@@ -1357,29 +1358,11 @@
# SINCE 1.3.0
realm.windows.defaultDomain =
# The PAMUserService must be backed by another user service for standard user
# and team management.
# default: users.conf
#
# RESTART REQUIRED
# BASEFOLDER
# SINCE 1.3.1
realm.pam.backingUserService = ${baseFolder}/users.conf
# The PAM service name for authentication.
# default: system-auth
#
# SINCE 1.3.1
realm.pam.serviceName = system-auth
# The HtpasswdUserService must be backed by another user service for standard user
# and team management and attributes. This can be one of the local Gitblit user services.
# default: users.conf
#
# RESTART REQUIRED
# BASEFOLDER
# SINCE 1.3.2
realm.htpasswd.backingUserService = ${baseFolder}/users.conf
# The Apache htpasswd file that contains the users and passwords.
# default: ${baseFolder}/htpasswd
@@ -1388,30 +1371,6 @@
# BASEFOLDER
# SINCE 1.3.2
realm.htpasswd.userfile = ${baseFolder}/htpasswd
#  Determines how accounts are looked up upon login.
#
# If set to false, then authentication for local accounts is done against
# the backing user service.
# If set to true, then authentication will first be checked against the
# htpasswd store, even if the account appears as a local account in the
# backing user service. If the user is found in the htpasswd store, then
# an already existing local account will be turned into an external account.
# In this case an initial local password is never used and gets overwritten
# by the externally stored password upon login.
# default: false
#
# SINCE 1.3.2
realm.htpasswd.overrideLocalAuthentication = false
# The SalesforceUserService must be backed by another user service for standard user
# and team management.
# default: users.conf
#
# RESTART REQUIRED
# BASEFOLDER
# SINCE 1.3.0
realm.salesforce.backingUserService = ${baseFolder}/users.conf
# Restrict the Salesforce user to members of this org.
# default: 0 (i.e. do not check the Org ID)
@@ -1438,15 +1397,6 @@
#
# SINCE 1.0.0
realm.ldap.password = password
# The LdapUserService must be backed by another user service for standard user
# and team management.
# default: users.conf
#
# SINCE 1.0.0
# RESTART REQUIRED
# BASEFOLDER
realm.ldap.backingUserService = ${baseFolder}/users.conf
# Delegate team membership control to LDAP.
#
@@ -1565,14 +1515,6 @@
# For MS Active Directory this may be sAMAccountName
realm.ldap.uid = uid
# The RedmineUserService must be backed by another user service for standard user
# and team management.
# default: users.conf
#
# RESTART REQUIRED
# BASEFOLDER
realm.redmine.backingUserService = ${baseFolder}/users.conf
# URL of the Redmine.
realm.redmine.url = http://example.com/redmine
@@ -1638,7 +1580,7 @@
#
# SINCE 1.4.0
# RESTART REQUIRED
server.redirectToHttpsPort = true
server.redirectToHttpsPort = false
# Specify the interface for Jetty to bind the standard connector.
# You may specify an ip or an empty value to bind to all interfaces.
src/main/java/com/gitblit/ConfigUserService.java
@@ -96,6 +96,8 @@
    private static final String LOCALE = "locale";
    private static final String ACCOUNTTYPE = "accountType";
    private final File realmFile;
    private final Logger logger = LoggerFactory.getLogger(ConfigUserService.class);
@@ -125,60 +127,6 @@
    }
    /**
     * 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
     */
    @Override
    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
@@ -197,13 +145,13 @@
    }
    /**
     * Authenticate a user based on their cookie.
     * Gets the user object for the specified cookie.
     *
     * @param cookie
     * @return a user object or null
     */
    @Override
    public synchronized UserModel authenticate(char[] cookie) {
    public synchronized UserModel getUserModel(char[] cookie) {
        String hash = new String(cookie);
        if (StringUtils.isEmpty(hash)) {
            return null;
@@ -220,49 +168,6 @@
            model = DeepCopier.copy(model);
        }
        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) {
        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) {
    }
    /**
@@ -357,6 +262,10 @@
    public synchronized boolean updateUserModel(String username, UserModel model) {
        UserModel originalUser = null;
        try {
            if (!model.isLocalAccount()) {
                // do not persist password
                model.password = Constants.EXTERNAL_ACCOUNT;
            }
            read();
            originalUser = users.remove(username.toLowerCase());
            users.put(model.username.toLowerCase(), model);
@@ -502,45 +411,6 @@
        }
        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 synchronized 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;
    }
    /**
@@ -716,46 +586,6 @@
    }
    /**
     * 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 synchronized 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
@@ -846,6 +676,9 @@
            if (!StringUtils.isEmpty(model.emailAddress)) {
                config.setString(USER, model.username, EMAILADDRESS, model.emailAddress);
            }
            if (model.accountType != null) {
                config.setString(USER, model.username, ACCOUNTTYPE, model.accountType.name());
            }
            if (!StringUtils.isEmpty(model.organizationalUnit)) {
                config.setString(USER, model.username, ORGANIZATIONALUNIT, model.organizationalUnit);
            }
@@ -928,6 +761,9 @@
                roles.add(Constants.NO_ROLE);
            }
            config.setStringList(TEAM, model.name, ROLE, roles);
            if (model.accountType != null) {
                config.setString(TEAM, model.name, ACCOUNTTYPE, model.accountType.name());
            }
            if (!model.canAdmin) {
                // write team permission for non-admin teams
@@ -1021,6 +857,10 @@
                    user.password = config.getString(USER, username, PASSWORD);
                    user.displayName = config.getString(USER, username, DISPLAYNAME);
                    user.emailAddress = config.getString(USER, username, EMAILADDRESS);
                    user.accountType = AccountType.fromString(config.getString(USER, username, ACCOUNTTYPE));
                    if (Constants.EXTERNAL_ACCOUNT.equals(user.password) && user.accountType.isLocal()) {
                        user.accountType = null;
                    }
                    user.organizationalUnit = config.getString(USER, username, ORGANIZATIONALUNIT);
                    user.organization = config.getString(USER, username, ORGANIZATION);
                    user.locality = config.getString(USER, username, LOCALITY);
@@ -1074,6 +914,7 @@
                    team.canAdmin = roles.contains(Constants.ADMIN_ROLE);
                    team.canFork = roles.contains(Constants.FORK_ROLE);
                    team.canCreate = roles.contains(Constants.CREATE_ROLE);
                    team.accountType = AccountType.fromString(config.getString(TEAM, teamname, ACCOUNTTYPE));
                    if (!team.canAdmin) {
                        // non-admin team, read permissions
@@ -1111,10 +952,5 @@
    @Override
    public String toString() {
        return getClass().getSimpleName() + "(" + realmFile.getAbsolutePath() + ")";
    }
    @Override
    public AccountType getAccountType() {
        return AccountType.LOCAL;
    }
}
src/main/java/com/gitblit/Constants.java
@@ -499,7 +499,16 @@
    }
    public static enum AccountType {
        LOCAL, EXTERNAL, LDAP, REDMINE, SALESFORCE, WINDOWS, PAM, HTPASSWD;
        LOCAL, EXTERNAL, CONTAINER, LDAP, REDMINE, SALESFORCE, WINDOWS, PAM, HTPASSWD;
        public static AccountType fromString(String value) {
            for (AccountType type : AccountType.values()) {
                if (type.name().equalsIgnoreCase(value)) {
                    return type;
                }
            }
            return AccountType.LOCAL;
        }
        public boolean isLocal() {
            return this == LOCAL;
src/main/java/com/gitblit/DaggerModule.java
@@ -20,8 +20,10 @@
import org.apache.wicket.protocol.http.WebApplication;
import com.gitblit.git.GitServlet;
import com.gitblit.manager.AuthenticationManager;
import com.gitblit.manager.FederationManager;
import com.gitblit.manager.GitblitManager;
import com.gitblit.manager.IAuthenticationManager;
import com.gitblit.manager.IFederationManager;
import com.gitblit.manager.IGitblitManager;
import com.gitblit.manager.INotificationManager;
@@ -29,14 +31,12 @@
import com.gitblit.manager.IRepositoryManager;
import com.gitblit.manager.IRuntimeManager;
import com.gitblit.manager.IServicesManager;
import com.gitblit.manager.ISessionManager;
import com.gitblit.manager.IUserManager;
import com.gitblit.manager.NotificationManager;
import com.gitblit.manager.ProjectManager;
import com.gitblit.manager.RepositoryManager;
import com.gitblit.manager.RuntimeManager;
import com.gitblit.manager.ServicesManager;
import com.gitblit.manager.SessionManager;
import com.gitblit.manager.UserManager;
import com.gitblit.servlet.BranchGraphServlet;
import com.gitblit.servlet.DownloadZipFilter;
@@ -74,7 +74,7 @@
            IRuntimeManager.class,
            INotificationManager.class,
            IUserManager.class,
            ISessionManager.class,
            IAuthenticationManager.class,
            IRepositoryManager.class,
            IProjectManager.class,
            IGitblitManager.class,
@@ -122,11 +122,11 @@
        return new UserManager(runtimeManager);
    }
    @Provides @Singleton ISessionManager provideSessionManager(
    @Provides @Singleton IAuthenticationManager provideAuthenticationManager(
            IRuntimeManager runtimeManager,
            IUserManager userManager) {
        return new SessionManager(
        return new AuthenticationManager(
                runtimeManager,
                userManager);
    }
@@ -179,7 +179,7 @@
            IRuntimeManager runtimeManager,
            INotificationManager notificationManager,
            IUserManager userManager,
            ISessionManager sessionManager,
            IAuthenticationManager authenticationManager,
            IRepositoryManager repositoryManager,
            IProjectManager projectManager,
            IGitblitManager gitblitManager,
@@ -189,7 +189,7 @@
                runtimeManager,
                notificationManager,
                userManager,
                sessionManager,
                authenticationManager,
                repositoryManager,
                projectManager,
                gitblitManager,
@@ -204,7 +204,7 @@
            IRuntimeManager runtimeManager,
            INotificationManager notificationManager,
            IUserManager userManager,
            ISessionManager sessionManager,
            IAuthenticationManager authenticationManager,
            IRepositoryManager repositoryManager,
            IProjectManager projectManager,
            IGitblitManager gitblitManager,
@@ -214,7 +214,7 @@
                runtimeManager,
                notificationManager,
                userManager,
                sessionManager,
                authenticationManager,
                repositoryManager,
                projectManager,
                gitblitManager,
src/main/java/com/gitblit/GitBlit.java
@@ -29,13 +29,13 @@
import com.gitblit.Constants.FederationRequest;
import com.gitblit.Constants.FederationToken;
import com.gitblit.manager.IAuthenticationManager;
import com.gitblit.manager.IFederationManager;
import com.gitblit.manager.IGitblitManager;
import com.gitblit.manager.INotificationManager;
import com.gitblit.manager.IProjectManager;
import com.gitblit.manager.IRepositoryManager;
import com.gitblit.manager.IRuntimeManager;
import com.gitblit.manager.ISessionManager;
import com.gitblit.manager.IUserManager;
import com.gitblit.models.FederationModel;
import com.gitblit.models.FederationProposal;
@@ -65,7 +65,7 @@
public class GitBlit implements IRuntimeManager,
                                INotificationManager,
                                IUserManager,
                                ISessionManager,
                                IAuthenticationManager,
                                IRepositoryManager,
                                IProjectManager,
                                IGitblitManager,
@@ -77,7 +77,7 @@
    private final IUserManager userManager;
    private final ISessionManager sessionManager;
    private final IAuthenticationManager authenticationManager;
    private final IRepositoryManager repositoryManager;
@@ -91,7 +91,7 @@
            IRuntimeManager runtimeManager,
            INotificationManager notificationManager,
            IUserManager userManager,
            ISessionManager sessionManager,
            IAuthenticationManager authenticationManager,
            IRepositoryManager repositoryManager,
            IProjectManager projectManager,
            IGitblitManager gitblitManager,
@@ -100,7 +100,7 @@
        this.runtimeManager = runtimeManager;
        this.notificationManager = notificationManager;
        this.userManager = userManager;
        this.sessionManager = sessionManager;
        this.authenticationManager = authenticationManager;
        this.repositoryManager = repositoryManager;
        this.projectManager = projectManager;
        this.gitblitManager = gitblitManager;
@@ -239,26 +239,51 @@
    @Override
    public UserModel authenticate(String username, char[] password) {
        return sessionManager.authenticate(username, password);
        return authenticationManager.authenticate(username, password);
    }
    @Override
    public UserModel authenticate(HttpServletRequest httpRequest) {
        return sessionManager.authenticate(httpRequest, false);
        return authenticationManager.authenticate(httpRequest, false);
    }
    @Override
    public UserModel authenticate(HttpServletRequest httpRequest, boolean requiresCertificate) {
        return sessionManager.authenticate(httpRequest, requiresCertificate);
        return authenticationManager.authenticate(httpRequest, requiresCertificate);
    }
    @Override
    public void setCookie(HttpServletResponse response, UserModel user) {
        sessionManager.setCookie(response, user);
        authenticationManager.setCookie(response, user);
    }
    @Override
    public void logout(HttpServletResponse response, UserModel user) {
        sessionManager.logout(response, user);
        authenticationManager.logout(response, user);
    }
    @Override
    public boolean supportsCredentialChanges(UserModel user) {
        return authenticationManager.supportsCredentialChanges(user);
    }
    @Override
    public boolean supportsDisplayNameChanges(UserModel user) {
        return authenticationManager.supportsDisplayNameChanges(user);
    }
    @Override
    public boolean supportsEmailAddressChanges(UserModel user) {
        return authenticationManager.supportsEmailAddressChanges(user);
    }
    @Override
    public boolean supportsTeamMembershipChanges(UserModel user) {
        return authenticationManager.supportsTeamMembershipChanges(user);
    }
    @Override
    public boolean supportsTeamMembershipChanges(TeamModel team) {
        return authenticationManager.supportsTeamMembershipChanges(team);
    }
    /*
@@ -266,28 +291,7 @@
     */
    @Override
    public boolean supportsAddUser() {
        return userManager.supportsAddUser();
    }
    @Override
    public boolean supportsCredentialChanges(UserModel user) {
        return userManager.supportsCredentialChanges(user);
    }
    @Override
    public boolean supportsDisplayNameChanges(UserModel user) {
        return userManager.supportsDisplayNameChanges(user);
    }
    @Override
    public boolean supportsEmailAddressChanges(UserModel user) {
        return userManager.supportsEmailAddressChanges(user);
    }
    @Override
    public boolean supportsTeamMembershipChanges(UserModel user) {
        return userManager.supportsTeamMembershipChanges(user);
    public void setup(IRuntimeManager runtimeManager) {
    }
    @Override
@@ -318,11 +322,6 @@
    @Override
    public TeamModel getTeamModel(String teamname) {
        return userManager.getTeamModel(teamname);
    }
    @Override
    public boolean supportsCookies() {
        return userManager.supportsCookies();
    }
    @Override
src/main/java/com/gitblit/GitblitUserService.java
File was deleted
src/main/java/com/gitblit/IUserService.java
@@ -18,7 +18,6 @@
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;
@@ -43,45 +42,6 @@
    void setup(IRuntimeManager runtimeManager);
    /**
     * Does the user service support changes to credentials?
     *
     * @return true or false
     * @since 1.0.0
     */
    boolean supportsCredentialChanges();
    /**
     * Does the user service support changes to user display name?
     *
     * @return true or false
     * @since 1.0.0
     */
    boolean supportsDisplayNameChanges();
    /**
     * Does the user service support changes to user email address?
     *
     * @return true or false
     * @since 1.0.0
     */
    boolean supportsEmailAddressChanges();
    /**
     * Does the user service support changes to team memberships?
     *
     * @return true or false
     * @since 1.0.0
     */
    boolean supportsTeamMembershipChanges();
    /**
     * Does the user service support cookie authentication?
     *
     * @return true or false
     */
    boolean supportsCookies();
    /**
     * Returns the cookie value for the specified user.
     *
     * @param model
@@ -90,28 +50,12 @@
    String getCookie(UserModel model);
    /**
     * Authenticate a user based on their cookie.
     * Retrieve a user object for the specified cookie.
     *
     * @param cookie
     * @return a user object or null
     */
    UserModel authenticate(char[] cookie);
    /**
     * Authenticate a user based on a username and password.
     *
     * @param username
     * @param password
     * @return a user object or null
     */
    UserModel authenticate(String username, char[] password);
    /**
     * Logout a user.
     *
     * @param user
     */
    void logout(UserModel user);
    UserModel getUserModel(char[] cookie);
    /**
     * Retrieve the user object for the specified username.
@@ -209,19 +153,6 @@
    List<String> getTeamNamesForRepositoryRole(String role);
    /**
     * Sets the list of all teams who are allowed to bypass the access
     * restriction placed on the specified repository.
     *
     * @param role
     *            the repository name
     * @param teamnames
     * @return true if successful
     * @since 0.8.0
     */
    @Deprecated
    boolean setTeamnamesForRepositoryRole(String role, List<String> teamnames);
    /**
     * Retrieve the team object for the specified team name.
     *
     * @param teamname
@@ -291,18 +222,6 @@
    List<String> getUsernamesForRepositoryRole(String role);
    /**
     * Sets the list of all uses who are allowed to bypass the access
     * restriction placed on the specified repository.
     *
     * @param role
     *            the repository name
     * @param usernames
     * @return true if successful
     */
    @Deprecated
    boolean setUsernamesForRepositoryRole(String role, List<String> usernames);
    /**
     * Renames a repository role.
     *
     * @param oldRole
@@ -318,14 +237,6 @@
     * @return true if successful
     */
    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();
src/main/java/com/gitblit/auth/AuthenticationProvider.java
New file
@@ -0,0 +1,182 @@
/*
 * 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.auth;
import java.io.File;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.gitblit.Constants.AccountType;
import com.gitblit.IStoredSettings;
import com.gitblit.manager.IRuntimeManager;
import com.gitblit.manager.IUserManager;
import com.gitblit.models.TeamModel;
import com.gitblit.models.UserModel;
public abstract class AuthenticationProvider {
    public static NullProvider NULL_PROVIDER = new NullProvider();
    protected final Logger logger = LoggerFactory.getLogger(getClass());
    protected final String serviceName;
    protected File baseFolder;
    protected IStoredSettings settings;
    protected IRuntimeManager runtimeManager;
    protected IUserManager userManager;
    protected AuthenticationProvider(String serviceName) {
        this.serviceName = serviceName;
    }
    /**
     * Returns the file object for the specified configuration key.
     *
     * @return the file
     */
    public File getFileOrFolder(String key, String defaultFileOrFolder) {
        return runtimeManager.getFileOrFolder(key, defaultFileOrFolder);
    }
    public final void setup(IRuntimeManager runtimeManager, IUserManager userManager) {
        this.baseFolder = runtimeManager.getBaseFolder();
        this.settings = runtimeManager.getSettings();
        this.runtimeManager = runtimeManager;
        this.userManager = userManager;
        setup();
    }
    public String getServiceName() {
        return serviceName;
    }
    protected void updateUser(UserModel userModel) {
        // TODO implement user model change detection
        // account for new user and revised user
        // username
        // displayname
        // email address
        // cookie
        userManager.updateUserModel(userModel);
    }
    protected void updateTeam(TeamModel teamModel) {
        // TODO implement team model change detection
        // account for new team and revised team
        // memberships
        userManager.updateTeamModel(teamModel);
    }
    public abstract void setup();
    public abstract UserModel authenticate(String username, char[] password);
    public abstract AccountType getAccountType();
    /**
     * Does the user service support changes to credentials?
     *
     * @return true or false
     * @since 1.0.0
     */
    public abstract boolean supportsCredentialChanges();
    /**
     * Returns true if the user's display name can be changed.
     *
     * @param user
     * @return true if the user service supports display name changes
     */
    public abstract boolean supportsDisplayNameChanges();
    /**
     * Returns true if the user's email address can be changed.
     *
     * @param user
     * @return true if the user service supports email address changes
     */
    public abstract boolean supportsEmailAddressChanges();
    /**
     * Returns true if the user's team memberships can be changed.
     *
     * @param user
     * @return true if the user service supports team membership changes
     */
    public abstract boolean supportsTeamMembershipChanges();
    @Override
    public String toString() {
        return getServiceName() + " (" + getClass().getName() + ")";
    }
    public abstract static class UsernamePasswordAuthenticationProvider extends AuthenticationProvider {
        protected UsernamePasswordAuthenticationProvider(String serviceName) {
            super(serviceName);
        }
    }
    public static class NullProvider extends AuthenticationProvider {
        protected NullProvider() {
            super("NULL");
        }
        @Override
        public void setup() {
        }
        @Override
        public UserModel authenticate(String username, char[] password) {
            return null;
        }
        @Override
        public AccountType getAccountType() {
            return AccountType.LOCAL;
        }
        @Override
        public boolean supportsCredentialChanges() {
            return false;
        }
        @Override
        public boolean supportsDisplayNameChanges() {
            return false;
        }
        @Override
        public boolean supportsEmailAddressChanges() {
            return false;
        }
        @Override
        public boolean supportsTeamMembershipChanges() {
            return false;
        }
    }
}
src/main/java/com/gitblit/auth/HtpasswdAuthProvider.java
File was renamed from src/main/java/com/gitblit/HtpasswdUserService.java
@@ -14,7 +14,7 @@
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.gitblit;
package com.gitblit.auth;
import java.io.File;
import java.io.FileInputStream;
@@ -29,11 +29,11 @@
import org.apache.commons.codec.digest.Crypt;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.codec.digest.Md5Crypt;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.gitblit.Constants;
import com.gitblit.Constants.AccountType;
import com.gitblit.manager.IRuntimeManager;
import com.gitblit.Keys;
import com.gitblit.auth.AuthenticationProvider.UsernamePasswordAuthenticationProvider;
import com.gitblit.models.UserModel;
import com.gitblit.utils.ArrayUtils;
import com.gitblit.utils.StringUtils;
@@ -64,51 +64,24 @@
 * @author Florian Zschocke
 *
 */
public class HtpasswdUserService extends GitblitUserService
{
    private static final String KEY_BACKING_US = Keys.realm.htpasswd.backingUserService;
    private static final String DEFAULT_BACKING_US = "${baseFolder}/users.conf";
public class HtpasswdAuthProvider extends UsernamePasswordAuthenticationProvider {
    private static final String KEY_HTPASSWD_FILE = Keys.realm.htpasswd.userfile;
    private static final String DEFAULT_HTPASSWD_FILE = "${baseFolder}/htpasswd";
    private static final String KEY_OVERRIDE_LOCALAUTH = Keys.realm.htpasswd.overrideLocalAuthentication;
    private static final boolean DEFAULT_OVERRIDE_LOCALAUTH = true;
    private static final String KEY_SUPPORT_PLAINTEXT_PWD = "realm.htpasswd.supportPlaintextPasswords";
    private final boolean SUPPORT_PLAINTEXT_PWD;
    private boolean supportPlainTextPwd;
    private IRuntimeManager runtimeManager;
    private IStoredSettings settings;
    private File htpasswdFile;
    private final Logger logger = LoggerFactory.getLogger(HtpasswdUserService.class);
    private final Map<String, String> htUsers = new ConcurrentHashMap<String, String>();
    private volatile long lastModified;
    private volatile boolean forceReload;
    public HtpasswdUserService()
    {
        super();
        String os = System.getProperty("os.name").toLowerCase();
        if (os.startsWith("windows") || os.startsWith("netware")) {
            SUPPORT_PLAINTEXT_PWD = true;
        }
        else {
            SUPPORT_PLAINTEXT_PWD = false;
        }
    public HtpasswdAuthProvider() {
        super("htpasswd");
    }
    /**
     * Setup the user service.
@@ -118,42 +91,40 @@
     * In addition the setup tries to read and parse the htpasswd file to be used
     * for authentication.
     *
     * @param runtimeManager
     * @since 1.4.0
     * @param settings
     * @since 0.7.0
     */
    @Override
    public void setup(IRuntimeManager runtimeManager)
    {
        this.runtimeManager = runtimeManager;
        this.settings = runtimeManager.getSettings();
        // This is done in two steps in order to avoid calling GitBlit.getFileOrFolder(String, String) which will segfault for unit tests.
        String file = settings.getString(KEY_BACKING_US, DEFAULT_BACKING_US);
        File realmFile = runtimeManager.getFileOrFolder(file);
        serviceImpl = createUserService(realmFile);
        logger.info("Htpasswd User Service backed by " + serviceImpl.toString());
    public void setup() {
        String os = System.getProperty("os.name").toLowerCase();
        if (os.startsWith("windows") || os.startsWith("netware")) {
            supportPlainTextPwd = true;
        } else {
            supportPlainTextPwd = false;
        }
        read();
        logger.debug("Read " + htUsers.size() + " users from htpasswd file: " + this.htpasswdFile);
    }
    /**
     * For now, credentials are defined in the htpasswd file and can not be manipulated
     * from Gitblit.
     *
     * @return false
     * @since 1.0.0
     */
    @Override
    public boolean supportsCredentialChanges()
    {
    public boolean supportsCredentialChanges() {
        return false;
    }
    @Override
    public boolean supportsDisplayNameChanges() {
        return true;
    }
    @Override
    public boolean supportsEmailAddressChanges() {
        return true;
    }
    @Override
    public boolean supportsTeamMembershipChanges() {
        return true;
    }
    /**
     * Authenticate a user based on a username and password.
@@ -168,14 +139,7 @@
     * @return a user object or null
     */
    @Override
    public UserModel authenticate(String username, char[] password)
    {
        if (isLocalAccount(username)) {
            // local account, bypass htpasswd authentication
            return super.authenticate(username, password);
        }
    public UserModel authenticate(String username, char[] password) {
        read();
        String storedPwd = htUsers.get(username);
        if (storedPwd != null) {
@@ -183,27 +147,27 @@
            final String passwd = new String(password);
            // test Apache MD5 variant encrypted password
            if ( storedPwd.startsWith("$apr1$") ) {
                if ( storedPwd.equals(Md5Crypt.apr1Crypt(passwd, storedPwd)) ) {
            if (storedPwd.startsWith("$apr1$")) {
                if (storedPwd.equals(Md5Crypt.apr1Crypt(passwd, storedPwd))) {
                    logger.debug("Apache MD5 encoded password matched for user '" + username + "'");
                    authenticated = true;
                }
            }
            // test unsalted SHA password
            else if ( storedPwd.startsWith("{SHA}") ) {
            else if (storedPwd.startsWith("{SHA}")) {
                String passwd64 = Base64.encodeBase64String(DigestUtils.sha1(passwd));
                if ( storedPwd.substring("{SHA}".length()).equals(passwd64) ) {
                if (storedPwd.substring("{SHA}".length()).equals(passwd64)) {
                    logger.debug("Unsalted SHA-1 encoded password matched for user '" + username + "'");
                    authenticated = true;
                }
            }
            // test libc crypt() encoded password
            else if ( supportCryptPwd() && storedPwd.equals(Crypt.crypt(passwd, storedPwd)) ) {
            else if (supportCryptPwd() && storedPwd.equals(Crypt.crypt(passwd, storedPwd))) {
                logger.debug("Libc crypt encoded password matched for user '" + username + "'");
                authenticated = true;
            }
            // test clear text
            else if ( supportPlaintextPwd() && storedPwd.equals(passwd) ){
            else if (supportPlaintextPwd() && storedPwd.equals(passwd)){
                logger.debug("Clear text password matched for user '" + username + "'");
                authenticated = true;
            }
@@ -212,10 +176,13 @@
            if (authenticated) {
                logger.debug("Htpasswd authenticated: " + username);
                UserModel user = getUserModel(username);
                if (user == null) {
                UserModel curr = userManager.getUserModel(username);
                UserModel user;
                if (curr == null) {
                    // create user object for new authenticated user
                    user = new UserModel(username);
                } else {
                    user = curr;
                }
                // create a user cookie
@@ -228,7 +195,7 @@
                user.accountType = getAccountType();
                // Push the looked up values to backing file
                super.updateUserModel(user);
                   updateUser(user);
                return user;
            }
@@ -237,70 +204,29 @@
        return null;
    }
    /**
     * Determine if the account is to be treated as a local account.
     *
     * This influences authentication. A local account will be authenticated
     * by the backing user service while an external account will be handled
     * by this user service.
     * <br/>
     * The decision also depends on the setting of the key
     * realm.htpasswd.overrideLocalAuthentication.
     * If it is set to true, then passwords will first be checked against the
     * htpasswd store. If an account exists and is marked as local in the backing
     * user service, that setting will be overwritten by the result. This
     * means that an account that looks local to the backing user service will
     * be turned into an external account upon valid login of a user that has
     * an entry in the htpasswd file.
     * If the key is set to false, then it is determined if the account is local
     * according to the logic of the GitblitUserService.
     */
    @Override
    protected boolean isLocalAccount(String username)
    {
        if ( settings.getBoolean(KEY_OVERRIDE_LOCALAUTH, DEFAULT_OVERRIDE_LOCALAUTH) ) {
            read();
            if ( htUsers.containsKey(username) ) return false;
        }
        return super.isLocalAccount(username);
    }
    /**
     * Get the account type used for this user service.
     *
     * @return AccountType.HTPASSWD
     */
    @Override
    public AccountType getAccountType()
    {
    public AccountType getAccountType() {
        return AccountType.HTPASSWD;
    }
    private String htpasswdFilePath = null;
    /**
     * Reads the realm file and rebuilds the in-memory lookup tables.
     */
    protected synchronized void read()
    {
        // This is done in two steps in order to avoid calling GitBlit.getFileOrFolder(String, String) which will segfault for unit tests.
        String file = settings.getString(KEY_HTPASSWD_FILE, DEFAULT_HTPASSWD_FILE);
        if ( !file.equals(htpasswdFilePath) ) {
            // The htpasswd file setting changed. Rediscover the file.
            this.htpasswdFilePath = file;
            this.htpasswdFile = runtimeManager.getFileOrFolder(file);
    protected synchronized void read() {
        boolean forceReload = false;
        File file = getFileOrFolder(KEY_HTPASSWD_FILE, DEFAULT_HTPASSWD_FILE);
        if (!file.equals(htpasswdFile)) {
            this.htpasswdFile = file;
            this.htUsers.clear();
            this.forceReload = true;
            forceReload = true;
        }
        if (htpasswdFile.exists() && (forceReload || (htpasswdFile.lastModified() != lastModified))) {
            forceReload = false;
            lastModified = htpasswdFile.lastModified();
            htUsers.clear();
@@ -309,53 +235,42 @@
            Scanner scanner = null;
            try {
                scanner = new Scanner(new FileInputStream(htpasswdFile));
                while( scanner.hasNextLine()) {
                while (scanner.hasNextLine()) {
                    String line = scanner.nextLine().trim();
                    if ( !line.isEmpty() &&  !line.startsWith("#") ) {
                    if (!line.isEmpty() &&  !line.startsWith("#")) {
                        Matcher m = entry.matcher(line);
                        if ( m.matches() ) {
                        if (m.matches()) {
                            htUsers.put(m.group(1), m.group(2));
                        }
                    }
                }
            } catch (Exception e) {
                logger.error(MessageFormat.format("Failed to read {0}", htpasswdFile), e);
            }
            finally {
                if (scanner != null) scanner.close();
            } finally {
                if (scanner != null) {
                    scanner.close();
                }
            }
        }
    }
    private boolean supportPlaintextPwd()
    {
        return this.settings.getBoolean(KEY_SUPPORT_PLAINTEXT_PWD, SUPPORT_PLAINTEXT_PWD);
    private boolean supportPlaintextPwd() {
        return this.settings.getBoolean(KEY_SUPPORT_PLAINTEXT_PWD, supportPlainTextPwd);
    }
    private boolean supportCryptPwd()
    {
    private boolean supportCryptPwd() {
        return !supportPlaintextPwd();
    }
    @Override
    public String toString()
    {
        return getClass().getSimpleName() + "(" + ((htpasswdFile != null) ? htpasswdFile.getAbsolutePath() : "null") + ")";
    }
    /*
     * Method only used for unit tests. Return number of users read from htpasswd file.
     */
    public int getNumberHtpasswdUsers()
    {
    public int getNumberHtpasswdUsers() {
        return this.htUsers.size();
    }
    @Override
    public String toString() {
        return getClass().getSimpleName() + "(" + ((htpasswdFile != null) ? htpasswdFile.getAbsolutePath() : "null") + ")";
    }
}
src/main/java/com/gitblit/auth/LdapAuthProvider.java
File was renamed from src/main/java/com/gitblit/LdapUserService.java
@@ -1,530 +1,508 @@
/*
 * Copyright 2012 John Crygier
 * Copyright 2012 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.net.URI;
import java.net.URISyntaxException;
import java.security.GeneralSecurityException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.gitblit.Constants.AccountType;
import com.gitblit.manager.IRuntimeManager;
import com.gitblit.models.TeamModel;
import com.gitblit.models.UserModel;
import com.gitblit.utils.ArrayUtils;
import com.gitblit.utils.StringUtils;
import com.unboundid.ldap.sdk.Attribute;
import com.unboundid.ldap.sdk.DereferencePolicy;
import com.unboundid.ldap.sdk.ExtendedResult;
import com.unboundid.ldap.sdk.LDAPConnection;
import com.unboundid.ldap.sdk.LDAPException;
import com.unboundid.ldap.sdk.LDAPSearchException;
import com.unboundid.ldap.sdk.ResultCode;
import com.unboundid.ldap.sdk.SearchRequest;
import com.unboundid.ldap.sdk.SearchResult;
import com.unboundid.ldap.sdk.SearchResultEntry;
import com.unboundid.ldap.sdk.SearchScope;
import com.unboundid.ldap.sdk.SimpleBindRequest;
import com.unboundid.ldap.sdk.extensions.StartTLSExtendedRequest;
import com.unboundid.util.ssl.SSLUtil;
import com.unboundid.util.ssl.TrustAllTrustManager;
/**
 * Implementation of an LDAP user service.
 *
 * @author John Crygier
 */
public class LdapUserService extends GitblitUserService {
    public static final Logger logger = LoggerFactory.getLogger(LdapUserService.class);
    private IStoredSettings settings;
    private AtomicLong lastLdapUserSync = new AtomicLong(0L);
    public LdapUserService() {
        super();
    }
     private long getSynchronizationPeriod() {
        final String cacheDuration = settings.getString(Keys.realm.ldap.ldapCachePeriod, "2 MINUTES");
        try {
            final String[] s = cacheDuration.split(" ", 2);
            long duration = Long.parseLong(s[0]);
            TimeUnit timeUnit = TimeUnit.valueOf(s[1]);
            return timeUnit.toMillis(duration);
        } catch (RuntimeException ex) {
            throw new IllegalArgumentException(Keys.realm.ldap.ldapCachePeriod + " must have format '<long> <TimeUnit>' where <TimeUnit> is one of 'MILLISECONDS', 'SECONDS', 'MINUTES', 'HOURS', 'DAYS'");
        }
    }
    @Override
    public void setup(IRuntimeManager runtimeManager) {
        this.settings = runtimeManager.getSettings();
        String file = settings.getString(Keys.realm.ldap.backingUserService, "${baseFolder}/users.conf");
        File realmFile = runtimeManager.getFileOrFolder(file);
        serviceImpl = createUserService(realmFile);
        logger.info("LDAP User Service backed by " + serviceImpl.toString());
        synchronizeLdapUsers();
    }
    protected synchronized void synchronizeLdapUsers() {
        final boolean enabled = settings.getBoolean(Keys.realm.ldap.synchronizeUsers.enable, false);
        if (enabled) {
            if (System.currentTimeMillis() > (lastLdapUserSync.get() + getSynchronizationPeriod())) {
                logger.info("Synchronizing with LDAP @ " + settings.getRequiredString(Keys.realm.ldap.server));
                final boolean deleteRemovedLdapUsers = settings.getBoolean(Keys.realm.ldap.synchronizeUsers.removeDeleted, true);
                LDAPConnection ldapConnection = getLdapConnection();
                if (ldapConnection != null) {
                    try {
                        String accountBase = settings.getString(Keys.realm.ldap.accountBase, "");
                        String uidAttribute = settings.getString(Keys.realm.ldap.uid, "uid");
                        String accountPattern = settings.getString(Keys.realm.ldap.accountPattern, "(&(objectClass=person)(sAMAccountName=${username}))");
                        accountPattern = StringUtils.replace(accountPattern, "${username}", "*");
                        SearchResult result = doSearch(ldapConnection, accountBase, accountPattern);
                        if (result != null && result.getEntryCount() > 0) {
                            final Map<String, UserModel> ldapUsers = new HashMap<String, UserModel>();
                            for (SearchResultEntry loggingInUser : result.getSearchEntries()) {
                                final String username = loggingInUser.getAttribute(uidAttribute).getValue();
                                logger.debug("LDAP synchronizing: " + username);
                                UserModel user = getUserModel(username);
                                if (user == null) {
                                    user = new UserModel(username);
                                }
                                if (!supportsTeamMembershipChanges())
                                    getTeamsFromLdap(ldapConnection, username, loggingInUser, user);
                                // Get User Attributes
                                setUserAttributes(user, loggingInUser);
                                // store in map
                                ldapUsers.put(username.toLowerCase(), user);
                            }
                            if (deleteRemovedLdapUsers) {
                                logger.debug("detecting removed LDAP users...");
                                for (UserModel userModel : super.getAllUsers()) {
                                    if (Constants.EXTERNAL_ACCOUNT.equals(userModel.password)) {
                                        if (! ldapUsers.containsKey(userModel.username)) {
                                            logger.info("deleting removed LDAP user " + userModel.username + " from backing user service");
                                            super.deleteUser(userModel.username);
                                        }
                                    }
                                }
                            }
                            super.updateUserModels(ldapUsers.values());
                            if (!supportsTeamMembershipChanges()) {
                                final Map<String, TeamModel> userTeams = new HashMap<String, TeamModel>();
                                for (UserModel user : ldapUsers.values()) {
                                    for (TeamModel userTeam : user.teams) {
                                        userTeams.put(userTeam.name, userTeam);
                                    }
                                }
                                updateTeamModels(userTeams.values());
                            }
                        }
                        lastLdapUserSync.set(System.currentTimeMillis());
                    } finally {
                        ldapConnection.close();
                    }
                }
            }
        }
    }
    private LDAPConnection getLdapConnection() {
        try {
            URI ldapUrl = new URI(settings.getRequiredString(Keys.realm.ldap.server));
            String ldapHost = ldapUrl.getHost();
            int ldapPort = ldapUrl.getPort();
            String bindUserName = settings.getString(Keys.realm.ldap.username, "");
            String bindPassword = settings.getString(Keys.realm.ldap.password, "");
            LDAPConnection conn;
            if (ldapUrl.getScheme().equalsIgnoreCase("ldaps")) {    // SSL
                SSLUtil sslUtil = new SSLUtil(new TrustAllTrustManager());
                conn = new LDAPConnection(sslUtil.createSSLSocketFactory());
            } else if (ldapUrl.getScheme().equalsIgnoreCase("ldap") || ldapUrl.getScheme().equalsIgnoreCase("ldap+tls")) {    // no encryption or StartTLS
                conn = new LDAPConnection();
            } else {
                logger.error("Unsupported LDAP URL scheme: " + ldapUrl.getScheme());
                return null;
            }
            conn.connect(ldapHost, ldapPort);
            if (ldapUrl.getScheme().equalsIgnoreCase("ldap+tls")) {
                SSLUtil sslUtil = new SSLUtil(new TrustAllTrustManager());
                ExtendedResult extendedResult = conn.processExtendedOperation(
                        new StartTLSExtendedRequest(sslUtil.createSSLContext()));
                if (extendedResult.getResultCode() != ResultCode.SUCCESS) {
                    throw new LDAPException(extendedResult.getResultCode());
                }
            }
            if ( ! StringUtils.isEmpty(bindUserName) || ! StringUtils.isEmpty(bindPassword)) {
                conn.bind(new SimpleBindRequest(bindUserName, bindPassword));
            }
            return conn;
        } catch (URISyntaxException e) {
            logger.error("Bad LDAP URL, should be in the form: ldap(s|+tls)://<server>:<port>", e);
        } catch (GeneralSecurityException e) {
            logger.error("Unable to create SSL Connection", e);
        } catch (LDAPException e) {
            logger.error("Error Connecting to LDAP", e);
        }
        return null;
    }
    /**
     * Credentials are defined in the LDAP server and can not be manipulated
     * from Gitblit.
     *
     * @return false
     * @since 1.0.0
     */
    @Override
    public boolean supportsCredentialChanges() {
        return false;
    }
    /**
     * If no displayName pattern is defined then Gitblit can manage the display name.
     *
     * @return true if Gitblit can manage the user display name
     * @since 1.0.0
     */
    @Override
    public boolean supportsDisplayNameChanges() {
        return StringUtils.isEmpty(settings.getString(Keys.realm.ldap.displayName, ""));
    }
    /**
     * If no email pattern is defined then Gitblit can manage the email address.
     *
     * @return true if Gitblit can manage the user email address
     * @since 1.0.0
     */
    @Override
    public boolean supportsEmailAddressChanges() {
        return StringUtils.isEmpty(settings.getString(Keys.realm.ldap.email, ""));
    }
    /**
     * If the LDAP server will maintain team memberships then LdapUserService
     * will not allow team membership changes.  In this scenario all team
     * changes must be made on the LDAP server by the LDAP administrator.
     *
     * @return true or false
     * @since 1.0.0
     */
    @Override
    public boolean supportsTeamMembershipChanges() {
        return !settings.getBoolean(Keys.realm.ldap.maintainTeams, false);
    }
    @Override
    public AccountType getAccountType() {
         return AccountType.LDAP;
    }
    @Override
    public UserModel authenticate(String username, char[] password) {
        if (isLocalAccount(username)) {
            // local account, bypass LDAP authentication
            return super.authenticate(username, password);
        }
        String simpleUsername = getSimpleUsername(username);
        LDAPConnection ldapConnection = getLdapConnection();
        if (ldapConnection != null) {
            try {
                // Find the logging in user's DN
                String accountBase = settings.getString(Keys.realm.ldap.accountBase, "");
                String accountPattern = settings.getString(Keys.realm.ldap.accountPattern, "(&(objectClass=person)(sAMAccountName=${username}))");
                accountPattern = StringUtils.replace(accountPattern, "${username}", escapeLDAPSearchFilter(simpleUsername));
                SearchResult result = doSearch(ldapConnection, accountBase, accountPattern);
                if (result != null && result.getEntryCount() == 1) {
                    SearchResultEntry loggingInUser = result.getSearchEntries().get(0);
                    String loggingInUserDN = loggingInUser.getDN();
                    if (isAuthenticated(ldapConnection, loggingInUserDN, new String(password))) {
                        logger.debug("LDAP authenticated: " + username);
                        UserModel user = null;
                        synchronized (this) {
                            user = getUserModel(simpleUsername);
                            if (user == null)    // create user object for new authenticated user
                                user = new UserModel(simpleUsername);
                            // create a user cookie
                            if (StringUtils.isEmpty(user.cookie) && !ArrayUtils.isEmpty(password)) {
                                user.cookie = StringUtils.getSHA1(user.username + new String(password));
                            }
                            if (!supportsTeamMembershipChanges())
                                getTeamsFromLdap(ldapConnection, simpleUsername, loggingInUser, user);
                            // Get User Attributes
                            setUserAttributes(user, loggingInUser);
                            // Push the ldap looked up values to backing file
                            super.updateUserModel(user);
                            if (!supportsTeamMembershipChanges()) {
                                for (TeamModel userTeam : user.teams)
                                    updateTeamModel(userTeam);
                            }
                        }
                        return user;
                    }
                }
            } finally {
                ldapConnection.close();
            }
        }
        return null;
    }
    /**
     * Set the admin attribute from team memberships retrieved from LDAP.
     * If we are not storing teams in LDAP and/or we have not defined any
     * administrator teams, then do not change the admin flag.
     *
     * @param user
     */
    private void setAdminAttribute(UserModel user) {
        if (!supportsTeamMembershipChanges()) {
            List<String> admins = settings.getStrings(Keys.realm.ldap.admins);
            // if we have defined administrative teams, then set admin flag
            // otherwise leave admin flag unchanged
            if (!ArrayUtils.isEmpty(admins)) {
                user.canAdmin = false;
                for (String admin : admins) {
                    if (admin.startsWith("@")) { // Team
                        if (user.getTeam(admin.substring(1)) != null)
                            user.canAdmin = true;
                    } else
                        if (user.getName().equalsIgnoreCase(admin))
                            user.canAdmin = true;
                }
            }
        }
    }
    private void setUserAttributes(UserModel user, SearchResultEntry userEntry) {
        // Is this user an admin?
        setAdminAttribute(user);
        // Don't want visibility into the real password, make up a dummy
        user.password = Constants.EXTERNAL_ACCOUNT;
        user.accountType = getAccountType();
        // Get full name Attribute
        String displayName = settings.getString(Keys.realm.ldap.displayName, "");
        if (!StringUtils.isEmpty(displayName)) {
            // Replace embedded ${} with attributes
            if (displayName.contains("${")) {
                for (Attribute userAttribute : userEntry.getAttributes())
                    displayName = StringUtils.replace(displayName, "${" + userAttribute.getName() + "}", userAttribute.getValue());
                user.displayName = displayName;
            } else {
                Attribute attribute = userEntry.getAttribute(displayName);
                if (attribute != null && attribute.hasValue()) {
                    user.displayName = attribute.getValue();
                }
            }
        }
        // Get email address Attribute
        String email = settings.getString(Keys.realm.ldap.email, "");
        if (!StringUtils.isEmpty(email)) {
            if (email.contains("${")) {
                for (Attribute userAttribute : userEntry.getAttributes())
                    email = StringUtils.replace(email, "${" + userAttribute.getName() + "}", userAttribute.getValue());
                user.emailAddress = email;
            } else {
                Attribute attribute = userEntry.getAttribute(email);
                if (attribute != null && attribute.hasValue()) {
                    user.emailAddress = attribute.getValue();
                }
            }
        }
    }
    private void getTeamsFromLdap(LDAPConnection ldapConnection, String simpleUsername, SearchResultEntry loggingInUser, UserModel user) {
        String loggingInUserDN = loggingInUser.getDN();
        user.teams.clear();        // Clear the users team memberships - we're going to get them from LDAP
        String groupBase = settings.getString(Keys.realm.ldap.groupBase, "");
        String groupMemberPattern = settings.getString(Keys.realm.ldap.groupMemberPattern, "(&(objectClass=group)(member=${dn}))");
        groupMemberPattern = StringUtils.replace(groupMemberPattern, "${dn}", escapeLDAPSearchFilter(loggingInUserDN));
        groupMemberPattern = StringUtils.replace(groupMemberPattern, "${username}", escapeLDAPSearchFilter(simpleUsername));
        // Fill in attributes into groupMemberPattern
        for (Attribute userAttribute : loggingInUser.getAttributes())
            groupMemberPattern = StringUtils.replace(groupMemberPattern, "${" + userAttribute.getName() + "}", escapeLDAPSearchFilter(userAttribute.getValue()));
        SearchResult teamMembershipResult = doSearch(ldapConnection, groupBase, true, groupMemberPattern, Arrays.asList("cn"));
        if (teamMembershipResult != null && teamMembershipResult.getEntryCount() > 0) {
            for (int i = 0; i < teamMembershipResult.getEntryCount(); i++) {
                SearchResultEntry teamEntry = teamMembershipResult.getSearchEntries().get(i);
                String teamName = teamEntry.getAttribute("cn").getValue();
                TeamModel teamModel = getTeamModel(teamName);
                if (teamModel == null)
                    teamModel = createTeamFromLdap(teamEntry);
                user.teams.add(teamModel);
                teamModel.addUser(user.getName());
            }
        }
    }
    private TeamModel createTeamFromLdap(SearchResultEntry teamEntry) {
        TeamModel answer = new TeamModel(teamEntry.getAttributeValue("cn"));
        // potentially retrieve other attributes here in the future
        return answer;
    }
    private SearchResult doSearch(LDAPConnection ldapConnection, String base, String filter) {
        try {
            return ldapConnection.search(base, SearchScope.SUB, filter);
        } catch (LDAPSearchException e) {
            logger.error("Problem Searching LDAP", e);
            return null;
        }
    }
    private SearchResult doSearch(LDAPConnection ldapConnection, String base, boolean dereferenceAliases, String filter, List<String> attributes) {
        try {
            SearchRequest searchRequest = new SearchRequest(base, SearchScope.SUB, filter);
            if ( dereferenceAliases ) {
                searchRequest.setDerefPolicy(DereferencePolicy.SEARCHING);
            }
            if (attributes != null) {
                searchRequest.setAttributes(attributes);
            }
            return ldapConnection.search(searchRequest);
        } catch (LDAPSearchException e) {
            logger.error("Problem Searching LDAP", e);
            return null;
        } catch (LDAPException e) {
            logger.error("Problem creating LDAP search", e);
            return null;
        }
    }
    private boolean isAuthenticated(LDAPConnection ldapConnection, String userDn, String password) {
        try {
            // Binding will stop any LDAP-Injection Attacks since the searched-for user needs to bind to that DN
            ldapConnection.bind(userDn, password);
            return true;
        } catch (LDAPException e) {
            logger.error("Error authenticating user", e);
            return false;
        }
    }
    @Override
    public List<String> getAllUsernames() {
        synchronizeLdapUsers();
        return super.getAllUsernames();
    }
    @Override
    public List<UserModel> getAllUsers() {
        synchronizeLdapUsers();
        return super.getAllUsers();
    }
    /**
     * Returns a simple username without any domain prefixes.
     *
     * @param username
     * @return a simple username
     */
    protected String getSimpleUsername(String username) {
        int lastSlash = username.lastIndexOf('\\');
        if (lastSlash > -1) {
            username = username.substring(lastSlash + 1);
        }
        return username;
    }
    // From: https://www.owasp.org/index.php/Preventing_LDAP_Injection_in_Java
    public static final String escapeLDAPSearchFilter(String filter) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < filter.length(); i++) {
            char curChar = filter.charAt(i);
            switch (curChar) {
            case '\\':
                sb.append("\\5c");
                break;
            case '*':
                sb.append("\\2a");
                break;
            case '(':
                sb.append("\\28");
                break;
            case ')':
                sb.append("\\29");
                break;
            case '\u0000':
                sb.append("\\00");
                break;
            default:
                sb.append(curChar);
            }
        }
        return sb.toString();
    }
}
/*
 * Copyright 2012 John Crygier
 * Copyright 2012 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.auth;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.GeneralSecurityException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import com.gitblit.Constants;
import com.gitblit.Constants.AccountType;
import com.gitblit.Keys;
import com.gitblit.auth.AuthenticationProvider.UsernamePasswordAuthenticationProvider;
import com.gitblit.models.TeamModel;
import com.gitblit.models.UserModel;
import com.gitblit.utils.ArrayUtils;
import com.gitblit.utils.StringUtils;
import com.unboundid.ldap.sdk.Attribute;
import com.unboundid.ldap.sdk.DereferencePolicy;
import com.unboundid.ldap.sdk.ExtendedResult;
import com.unboundid.ldap.sdk.LDAPConnection;
import com.unboundid.ldap.sdk.LDAPException;
import com.unboundid.ldap.sdk.LDAPSearchException;
import com.unboundid.ldap.sdk.ResultCode;
import com.unboundid.ldap.sdk.SearchRequest;
import com.unboundid.ldap.sdk.SearchResult;
import com.unboundid.ldap.sdk.SearchResultEntry;
import com.unboundid.ldap.sdk.SearchScope;
import com.unboundid.ldap.sdk.SimpleBindRequest;
import com.unboundid.ldap.sdk.extensions.StartTLSExtendedRequest;
import com.unboundid.util.ssl.SSLUtil;
import com.unboundid.util.ssl.TrustAllTrustManager;
/**
 * Implementation of an LDAP user service.
 *
 * @author John Crygier
 */
public class LdapAuthProvider extends UsernamePasswordAuthenticationProvider {
    private AtomicLong lastLdapUserSync = new AtomicLong(0L);
    public LdapAuthProvider() {
        super("ldap");
    }
     private long getSynchronizationPeriod() {
        final String cacheDuration = settings.getString(Keys.realm.ldap.ldapCachePeriod, "2 MINUTES");
        try {
            final String[] s = cacheDuration.split(" ", 2);
            long duration = Long.parseLong(s[0]);
            TimeUnit timeUnit = TimeUnit.valueOf(s[1]);
            return timeUnit.toMillis(duration);
        } catch (RuntimeException ex) {
            throw new IllegalArgumentException(Keys.realm.ldap.ldapCachePeriod + " must have format '<long> <TimeUnit>' where <TimeUnit> is one of 'MILLISECONDS', 'SECONDS', 'MINUTES', 'HOURS', 'DAYS'");
        }
    }
    @Override
    public void setup() {
        synchronizeLdapUsers();
    }
    protected synchronized void synchronizeLdapUsers() {
        final boolean enabled = settings.getBoolean(Keys.realm.ldap.synchronizeUsers.enable, false);
        if (enabled) {
            if (System.currentTimeMillis() > (lastLdapUserSync.get() + getSynchronizationPeriod())) {
                logger.info("Synchronizing with LDAP @ " + settings.getRequiredString(Keys.realm.ldap.server));
                final boolean deleteRemovedLdapUsers = settings.getBoolean(Keys.realm.ldap.synchronizeUsers.removeDeleted, true);
                LDAPConnection ldapConnection = getLdapConnection();
                if (ldapConnection != null) {
                    try {
                        String accountBase = settings.getString(Keys.realm.ldap.accountBase, "");
                        String uidAttribute = settings.getString(Keys.realm.ldap.uid, "uid");
                        String accountPattern = settings.getString(Keys.realm.ldap.accountPattern, "(&(objectClass=person)(sAMAccountName=${username}))");
                        accountPattern = StringUtils.replace(accountPattern, "${username}", "*");
                        SearchResult result = doSearch(ldapConnection, accountBase, accountPattern);
                        if (result != null && result.getEntryCount() > 0) {
                            final Map<String, UserModel> ldapUsers = new HashMap<String, UserModel>();
                            for (SearchResultEntry loggingInUser : result.getSearchEntries()) {
                                final String username = loggingInUser.getAttribute(uidAttribute).getValue();
                                logger.debug("LDAP synchronizing: " + username);
                                UserModel user = userManager.getUserModel(username);
                                if (user == null) {
                                    user = new UserModel(username);
                                }
                                if (!supportsTeamMembershipChanges()) {
                                    getTeamsFromLdap(ldapConnection, username, loggingInUser, user);
                                }
                                // Get User Attributes
                                setUserAttributes(user, loggingInUser);
                                // store in map
                                ldapUsers.put(username.toLowerCase(), user);
                            }
                            if (deleteRemovedLdapUsers) {
                                logger.debug("detecting removed LDAP users...");
                                for (UserModel userModel : userManager.getAllUsers()) {
                                    if (Constants.EXTERNAL_ACCOUNT.equals(userModel.password)) {
                                        if (!ldapUsers.containsKey(userModel.username)) {
                                            logger.info("deleting removed LDAP user " + userModel.username + " from user service");
                                            userManager.deleteUser(userModel.username);
                                        }
                                    }
                                }
                            }
                            userManager.updateUserModels(ldapUsers.values());
                            if (!supportsTeamMembershipChanges()) {
                                final Map<String, TeamModel> userTeams = new HashMap<String, TeamModel>();
                                for (UserModel user : ldapUsers.values()) {
                                    for (TeamModel userTeam : user.teams) {
                                        userTeams.put(userTeam.name, userTeam);
                                    }
                                }
                                userManager.updateTeamModels(userTeams.values());
                            }
                        }
                        lastLdapUserSync.set(System.currentTimeMillis());
                    } finally {
                        ldapConnection.close();
                    }
                }
            }
        }
    }
    private LDAPConnection getLdapConnection() {
        try {
            URI ldapUrl = new URI(settings.getRequiredString(Keys.realm.ldap.server));
            String ldapHost = ldapUrl.getHost();
            int ldapPort = ldapUrl.getPort();
            String bindUserName = settings.getString(Keys.realm.ldap.username, "");
            String bindPassword = settings.getString(Keys.realm.ldap.password, "");
            LDAPConnection conn;
            if (ldapUrl.getScheme().equalsIgnoreCase("ldaps")) {
                // SSL
                SSLUtil sslUtil = new SSLUtil(new TrustAllTrustManager());
                conn = new LDAPConnection(sslUtil.createSSLSocketFactory());
            } else if (ldapUrl.getScheme().equalsIgnoreCase("ldap") || ldapUrl.getScheme().equalsIgnoreCase("ldap+tls")) {
                // no encryption or StartTLS
                conn = new LDAPConnection();
            } else {
                logger.error("Unsupported LDAP URL scheme: " + ldapUrl.getScheme());
                return null;
            }
            conn.connect(ldapHost, ldapPort);
            if (ldapUrl.getScheme().equalsIgnoreCase("ldap+tls")) {
                SSLUtil sslUtil = new SSLUtil(new TrustAllTrustManager());
                ExtendedResult extendedResult = conn.processExtendedOperation(
                        new StartTLSExtendedRequest(sslUtil.createSSLContext()));
                if (extendedResult.getResultCode() != ResultCode.SUCCESS) {
                    throw new LDAPException(extendedResult.getResultCode());
                }
            }
            if (!StringUtils.isEmpty(bindUserName) || !StringUtils.isEmpty(bindPassword)) {
                conn.bind(new SimpleBindRequest(bindUserName, bindPassword));
            }
            return conn;
        } catch (URISyntaxException e) {
            logger.error("Bad LDAP URL, should be in the form: ldap(s|+tls)://<server>:<port>", e);
        } catch (GeneralSecurityException e) {
            logger.error("Unable to create SSL Connection", e);
        } catch (LDAPException e) {
            logger.error("Error Connecting to LDAP", e);
        }
        return null;
    }
    /**
     * Credentials are defined in the LDAP server and can not be manipulated
     * from Gitblit.
     *
     * @return false
     * @since 1.0.0
     */
    @Override
    public boolean supportsCredentialChanges() {
        return false;
    }
    /**
     * If no displayName pattern is defined then Gitblit can manage the display name.
     *
     * @return true if Gitblit can manage the user display name
     * @since 1.0.0
     */
    @Override
    public boolean supportsDisplayNameChanges() {
        return StringUtils.isEmpty(settings.getString(Keys.realm.ldap.displayName, ""));
    }
    /**
     * If no email pattern is defined then Gitblit can manage the email address.
     *
     * @return true if Gitblit can manage the user email address
     * @since 1.0.0
     */
    @Override
    public boolean supportsEmailAddressChanges() {
        return StringUtils.isEmpty(settings.getString(Keys.realm.ldap.email, ""));
    }
    /**
     * If the LDAP server will maintain team memberships then LdapUserService
     * will not allow team membership changes.  In this scenario all team
     * changes must be made on the LDAP server by the LDAP administrator.
     *
     * @return true or false
     * @since 1.0.0
     */
    @Override
    public boolean supportsTeamMembershipChanges() {
        return !settings.getBoolean(Keys.realm.ldap.maintainTeams, false);
    }
    @Override
    public AccountType getAccountType() {
         return AccountType.LDAP;
    }
    @Override
    public UserModel authenticate(String username, char[] password) {
        String simpleUsername = getSimpleUsername(username);
        LDAPConnection ldapConnection = getLdapConnection();
        if (ldapConnection != null) {
            try {
                // Find the logging in user's DN
                String accountBase = settings.getString(Keys.realm.ldap.accountBase, "");
                String accountPattern = settings.getString(Keys.realm.ldap.accountPattern, "(&(objectClass=person)(sAMAccountName=${username}))");
                accountPattern = StringUtils.replace(accountPattern, "${username}", escapeLDAPSearchFilter(simpleUsername));
                SearchResult result = doSearch(ldapConnection, accountBase, accountPattern);
                if (result != null && result.getEntryCount() == 1) {
                    SearchResultEntry loggingInUser = result.getSearchEntries().get(0);
                    String loggingInUserDN = loggingInUser.getDN();
                    if (isAuthenticated(ldapConnection, loggingInUserDN, new String(password))) {
                        logger.debug("LDAP authenticated: " + username);
                        UserModel user = null;
                        synchronized (this) {
                            user = userManager.getUserModel(simpleUsername);
                            if (user == null)    // create user object for new authenticated user
                                user = new UserModel(simpleUsername);
                            // create a user cookie
                            if (StringUtils.isEmpty(user.cookie) && !ArrayUtils.isEmpty(password)) {
                                user.cookie = StringUtils.getSHA1(user.username + new String(password));
                            }
                            if (!supportsTeamMembershipChanges())
                                getTeamsFromLdap(ldapConnection, simpleUsername, loggingInUser, user);
                            // Get User Attributes
                            setUserAttributes(user, loggingInUser);
                            // Push the ldap looked up values to backing file
                            updateUser(user);
                            if (!supportsTeamMembershipChanges()) {
                                for (TeamModel userTeam : user.teams)
                                    updateTeam(userTeam);
                            }
                        }
                        return user;
                    }
                }
            } finally {
                ldapConnection.close();
            }
        }
        return null;
    }
    /**
     * Set the admin attribute from team memberships retrieved from LDAP.
     * If we are not storing teams in LDAP and/or we have not defined any
     * administrator teams, then do not change the admin flag.
     *
     * @param user
     */
    private void setAdminAttribute(UserModel user) {
        if (!supportsTeamMembershipChanges()) {
            List<String> admins = settings.getStrings(Keys.realm.ldap.admins);
            // if we have defined administrative teams, then set admin flag
            // otherwise leave admin flag unchanged
            if (!ArrayUtils.isEmpty(admins)) {
                user.canAdmin = false;
                for (String admin : admins) {
                    if (admin.startsWith("@")) { // Team
                        if (user.getTeam(admin.substring(1)) != null)
                            user.canAdmin = true;
                    } else
                        if (user.getName().equalsIgnoreCase(admin))
                            user.canAdmin = true;
                }
            }
        }
    }
    private void setUserAttributes(UserModel user, SearchResultEntry userEntry) {
        // Is this user an admin?
        setAdminAttribute(user);
        // Don't want visibility into the real password, make up a dummy
        user.password = Constants.EXTERNAL_ACCOUNT;
        user.accountType = getAccountType();
        // Get full name Attribute
        String displayName = settings.getString(Keys.realm.ldap.displayName, "");
        if (!StringUtils.isEmpty(displayName)) {
            // Replace embedded ${} with attributes
            if (displayName.contains("${")) {
                for (Attribute userAttribute : userEntry.getAttributes())
                    displayName = StringUtils.replace(displayName, "${" + userAttribute.getName() + "}", userAttribute.getValue());
                user.displayName = displayName;
            } else {
                Attribute attribute = userEntry.getAttribute(displayName);
                if (attribute != null && attribute.hasValue()) {
                    user.displayName = attribute.getValue();
                }
            }
        }
        // Get email address Attribute
        String email = settings.getString(Keys.realm.ldap.email, "");
        if (!StringUtils.isEmpty(email)) {
            if (email.contains("${")) {
                for (Attribute userAttribute : userEntry.getAttributes())
                    email = StringUtils.replace(email, "${" + userAttribute.getName() + "}", userAttribute.getValue());
                user.emailAddress = email;
            } else {
                Attribute attribute = userEntry.getAttribute(email);
                if (attribute != null && attribute.hasValue()) {
                    user.emailAddress = attribute.getValue();
                }
            }
        }
    }
    private void getTeamsFromLdap(LDAPConnection ldapConnection, String simpleUsername, SearchResultEntry loggingInUser, UserModel user) {
        String loggingInUserDN = loggingInUser.getDN();
        user.teams.clear();        // Clear the users team memberships - we're going to get them from LDAP
        String groupBase = settings.getString(Keys.realm.ldap.groupBase, "");
        String groupMemberPattern = settings.getString(Keys.realm.ldap.groupMemberPattern, "(&(objectClass=group)(member=${dn}))");
        groupMemberPattern = StringUtils.replace(groupMemberPattern, "${dn}", escapeLDAPSearchFilter(loggingInUserDN));
        groupMemberPattern = StringUtils.replace(groupMemberPattern, "${username}", escapeLDAPSearchFilter(simpleUsername));
        // Fill in attributes into groupMemberPattern
        for (Attribute userAttribute : loggingInUser.getAttributes()) {
            groupMemberPattern = StringUtils.replace(groupMemberPattern, "${" + userAttribute.getName() + "}", escapeLDAPSearchFilter(userAttribute.getValue()));
        }
        SearchResult teamMembershipResult = doSearch(ldapConnection, groupBase, true, groupMemberPattern, Arrays.asList("cn"));
        if (teamMembershipResult != null && teamMembershipResult.getEntryCount() > 0) {
            for (int i = 0; i < teamMembershipResult.getEntryCount(); i++) {
                SearchResultEntry teamEntry = teamMembershipResult.getSearchEntries().get(i);
                String teamName = teamEntry.getAttribute("cn").getValue();
                TeamModel teamModel = userManager.getTeamModel(teamName);
                if (teamModel == null) {
                    teamModel = createTeamFromLdap(teamEntry);
                }
                user.teams.add(teamModel);
                teamModel.addUser(user.getName());
            }
        }
    }
    private TeamModel createTeamFromLdap(SearchResultEntry teamEntry) {
        TeamModel answer = new TeamModel(teamEntry.getAttributeValue("cn"));
        answer.accountType = getAccountType();
        // potentially retrieve other attributes here in the future
        return answer;
    }
    private SearchResult doSearch(LDAPConnection ldapConnection, String base, String filter) {
        try {
            return ldapConnection.search(base, SearchScope.SUB, filter);
        } catch (LDAPSearchException e) {
            logger.error("Problem Searching LDAP", e);
            return null;
        }
    }
    private SearchResult doSearch(LDAPConnection ldapConnection, String base, boolean dereferenceAliases, String filter, List<String> attributes) {
        try {
            SearchRequest searchRequest = new SearchRequest(base, SearchScope.SUB, filter);
            if (dereferenceAliases) {
                searchRequest.setDerefPolicy(DereferencePolicy.SEARCHING);
            }
            if (attributes != null) {
                searchRequest.setAttributes(attributes);
            }
            return ldapConnection.search(searchRequest);
        } catch (LDAPSearchException e) {
            logger.error("Problem Searching LDAP", e);
            return null;
        } catch (LDAPException e) {
            logger.error("Problem creating LDAP search", e);
            return null;
        }
    }
    private boolean isAuthenticated(LDAPConnection ldapConnection, String userDn, String password) {
        try {
            // Binding will stop any LDAP-Injection Attacks since the searched-for user needs to bind to that DN
            ldapConnection.bind(userDn, password);
            return true;
        } catch (LDAPException e) {
            logger.error("Error authenticating user", e);
            return false;
        }
    }
    /**
     * Returns a simple username without any domain prefixes.
     *
     * @param username
     * @return a simple username
     */
    protected String getSimpleUsername(String username) {
        int lastSlash = username.lastIndexOf('\\');
        if (lastSlash > -1) {
            username = username.substring(lastSlash + 1);
        }
        return username;
    }
    // From: https://www.owasp.org/index.php/Preventing_LDAP_Injection_in_Java
    public static final String escapeLDAPSearchFilter(String filter) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < filter.length(); i++) {
            char curChar = filter.charAt(i);
            switch (curChar) {
            case '\\':
                sb.append("\\5c");
                break;
            case '*':
                sb.append("\\2a");
                break;
            case '(':
                sb.append("\\28");
                break;
            case ')':
                sb.append("\\29");
                break;
            case '\u0000':
                sb.append("\\00");
                break;
            default:
                sb.append(curChar);
            }
        }
        return sb.toString();
    }
}
src/main/java/com/gitblit/auth/PAMAuthProvider.java
File was renamed from src/main/java/com/gitblit/PAMUserService.java
@@ -1,143 +1,126 @@
/*
 * 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;
import java.io.File;
import org.jvnet.libpam.PAM;
import org.jvnet.libpam.PAMException;
import org.jvnet.libpam.impl.CLibrary;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.gitblit.Constants.AccountType;
import com.gitblit.manager.IRuntimeManager;
import com.gitblit.models.UserModel;
import com.gitblit.utils.ArrayUtils;
import com.gitblit.utils.StringUtils;
/**
 * Implementation of a PAM user service for Linux/Unix/MacOSX.
 *
 * @author James Moger
 */
public class PAMUserService extends GitblitUserService {
    private final Logger logger = LoggerFactory.getLogger(PAMUserService.class);
    private IStoredSettings settings;
    public PAMUserService() {
        super();
    }
    @Override
    public void setup(IRuntimeManager runtimeManager) {
        this.settings = runtimeManager.getSettings();
        String file = settings.getString(Keys.realm.pam.backingUserService, "${baseFolder}/users.conf");
        File realmFile = runtimeManager.getFileOrFolder(file);
        serviceImpl = createUserService(realmFile);
        logger.info("PAM User Service backed by " + serviceImpl.toString());
        // Try to identify the passwd database
        String [] files = { "/etc/shadow", "/etc/master.passwd" };
        File passwdFile = null;
        for (String name : files) {
            File f = new File(name);
            if (f.exists()) {
                passwdFile = f;
                break;
            }
        }
        if (passwdFile == null) {
            logger.error("PAM User Service could not find a passwd database!");
        } else if (!passwdFile.canRead()) {
            logger.error("PAM User Service can not read passwd database {}! PAM authentications may fail!", passwdFile);
        }
    }
    @Override
    public boolean supportsCredentialChanges() {
        return false;
    }
    @Override
    public boolean supportsDisplayNameChanges() {
        return true;
    }
    @Override
    public boolean supportsEmailAddressChanges() {
        return true;
    }
    @Override
    public boolean supportsTeamMembershipChanges() {
        return true;
    }
     @Override
    public AccountType getAccountType() {
        return AccountType.PAM;
    }
    @Override
    public UserModel authenticate(String username, char[] password) {
        if (isLocalAccount(username)) {
            // local account, bypass PAM authentication
            return super.authenticate(username, password);
        }
        if (CLibrary.libc.getpwnam(username) == null) {
            logger.warn("Can not get PAM passwd for " + username);
            return null;
        }
        PAM pam = null;
        try {
            String serviceName = settings.getString(Keys.realm.pam.serviceName, "system-auth");
            pam = new PAM(serviceName);
            pam.authenticate(username, new String(password));
        } catch (PAMException e) {
            logger.error(e.getMessage());
            return null;
        } finally {
            pam.dispose();
        }
        UserModel user = getUserModel(username);
        if (user == null)    // create user object for new authenticated user
            user = new UserModel(username.toLowerCase());
        // create a user cookie
        if (StringUtils.isEmpty(user.cookie) && !ArrayUtils.isEmpty(password)) {
            user.cookie = StringUtils.getSHA1(user.username + new String(password));
        }
        // update user attributes from UnixUser
        user.accountType = getAccountType();
        user.password = Constants.EXTERNAL_ACCOUNT;
        // TODO consider mapping PAM groups to teams
        // push the changes to the backing user service
        super.updateUserModel(user);
        return user;
    }
}
/*
 * 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.auth;
import java.io.File;
import org.jvnet.libpam.PAM;
import org.jvnet.libpam.PAMException;
import org.jvnet.libpam.impl.CLibrary;
import com.gitblit.Constants;
import com.gitblit.Constants.AccountType;
import com.gitblit.Keys;
import com.gitblit.auth.AuthenticationProvider.UsernamePasswordAuthenticationProvider;
import com.gitblit.models.UserModel;
import com.gitblit.utils.ArrayUtils;
import com.gitblit.utils.StringUtils;
/**
 * Implementation of PAM authentication for Linux/Unix/MacOSX.
 *
 * @author James Moger
 */
public class PAMAuthProvider extends UsernamePasswordAuthenticationProvider {
    public PAMAuthProvider() {
        super("pam");
    }
    @Override
    public void setup() {
        // Try to identify the passwd database
        String [] files = { "/etc/shadow", "/etc/master.passwd" };
        File passwdFile = null;
        for (String name : files) {
            File f = new File(name);
            if (f.exists()) {
                passwdFile = f;
                break;
            }
        }
        if (passwdFile == null) {
            logger.error("PAM Authentication could not find a passwd database!");
        } else if (!passwdFile.canRead()) {
            logger.error("PAM Authentication can not read passwd database {}! PAM authentications may fail!", passwdFile);
        }
    }
    @Override
    public boolean supportsCredentialChanges() {
        return false;
    }
    @Override
    public boolean supportsDisplayNameChanges() {
        return true;
    }
    @Override
    public boolean supportsEmailAddressChanges() {
        return true;
    }
    @Override
    public boolean supportsTeamMembershipChanges() {
        return true;
    }
     @Override
    public AccountType getAccountType() {
        return AccountType.PAM;
    }
    @Override
    public UserModel authenticate(String username, char[] password) {
        if (CLibrary.libc.getpwnam(username) == null) {
            logger.warn("Can not get PAM passwd for " + username);
            return null;
        }
        PAM pam = null;
        try {
            String serviceName = settings.getString(Keys.realm.pam.serviceName, "system-auth");
            pam = new PAM(serviceName);
            pam.authenticate(username, new String(password));
        } catch (PAMException e) {
            logger.error(e.getMessage());
            return null;
        } finally {
            pam.dispose();
        }
        UserModel user = userManager.getUserModel(username);
        if (user == null)    // create user object for new authenticated user
            user = new UserModel(username.toLowerCase());
        // create a user cookie
        if (StringUtils.isEmpty(user.cookie) && !ArrayUtils.isEmpty(password)) {
            user.cookie = StringUtils.getSHA1(user.username + new String(password));
        }
        // update user attributes from UnixUser
        user.accountType = getAccountType();
        user.password = Constants.EXTERNAL_ACCOUNT;
        // TODO consider mapping PAM groups to teams
        // push the changes to the backing user service
        updateUser(user);
        return user;
    }
}
src/main/java/com/gitblit/auth/RedmineAuthProvider.java
File was renamed from src/main/java/com/gitblit/RedmineUserService.java
@@ -1,203 +1,186 @@
/*
 * Copyright 2012 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.io.InputStreamReader;
import java.net.HttpURLConnection;
import org.apache.wicket.util.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.gitblit.Constants.AccountType;
import com.gitblit.manager.IRuntimeManager;
import com.gitblit.models.UserModel;
import com.gitblit.utils.ArrayUtils;
import com.gitblit.utils.ConnectionUtils;
import com.gitblit.utils.StringUtils;
import com.google.gson.Gson;
/**
 * Implementation of an Redmine user service.<br>
 * you can login to gitblit with Redmine user id and api key.
 */
public class RedmineUserService extends GitblitUserService {
    private final Logger logger = LoggerFactory.getLogger(RedmineUserService.class);
    private IStoredSettings settings;
    private String testingJson;
    private class RedmineCurrent {
        private class RedmineUser {
            public String login;
            public String firstname;
            public String lastname;
            public String mail;
        }
        public RedmineUser user;
    }
    public RedmineUserService() {
        super();
    }
    @Override
    public void setup(IRuntimeManager runtimeManager) {
        this.settings = runtimeManager.getSettings();
        String file = settings.getString(Keys.realm.redmine.backingUserService, "${baseFolder}/users.conf");
        File realmFile = runtimeManager.getFileOrFolder(file);
        serviceImpl = createUserService(realmFile);
        logger.info("Redmine User Service backed by " + serviceImpl.toString());
    }
    @Override
    public boolean supportsCredentialChanges() {
        return false;
    }
    @Override
    public boolean supportsDisplayNameChanges() {
        return false;
    }
    @Override
    public boolean supportsEmailAddressChanges() {
        return false;
    }
    @Override
    public boolean supportsTeamMembershipChanges() {
        return false;
    }
     @Override
    public AccountType getAccountType() {
        return AccountType.REDMINE;
    }
    @Override
    public UserModel authenticate(String username, char[] password) {
        if (isLocalAccount(username)) {
            // local account, bypass Redmine authentication
            return super.authenticate(username, password);
        }
        String jsonString = null;
        try {
            // first attempt by username/password
            jsonString = getCurrentUserAsJson(username, password);
        } catch (Exception e1) {
            logger.warn("Failed to authenticate via username/password against Redmine");
            try {
                // second attempt is by apikey
                jsonString = getCurrentUserAsJson(null, password);
                username = null;
            } catch (Exception e2) {
                logger.error("Failed to authenticate via apikey against Redmine", e2);
                return null;
            }
        }
        if (StringUtils.isEmpty(jsonString)) {
            logger.error("Received empty authentication response from Redmine");
            return null;
        }
        RedmineCurrent current = null;
        try {
            current = new Gson().fromJson(jsonString, RedmineCurrent.class);
        } catch (Exception e) {
            logger.error("Failed to deserialize Redmine json response: " + jsonString, e);
            return null;
        }
        if (StringUtils.isEmpty(username)) {
            // if the username has been reset because of apikey authentication
            // then use the email address of the user. this is the original
            // behavior as contributed by github/mallowlabs
            username = current.user.mail;
        }
        UserModel user = getUserModel(username);
        if (user == null)    // create user object for new authenticated user
            user = new UserModel(username.toLowerCase());
        // create a user cookie
        if (StringUtils.isEmpty(user.cookie) && !ArrayUtils.isEmpty(password)) {
            user.cookie = StringUtils.getSHA1(user.username + new String(password));
        }
        // update user attributes from Redmine
        user.accountType = getAccountType();
        user.displayName = current.user.firstname + " " + current.user.lastname;
        user.emailAddress = current.user.mail;
        user.password = Constants.EXTERNAL_ACCOUNT;
        if (!StringUtils.isEmpty(current.user.login)) {
            // only admin users can get login name
            // evidently this is an undocumented behavior of Redmine
            user.canAdmin = true;
        }
        // TODO consider Redmine group mapping for team membership
        // http://www.redmine.org/projects/redmine/wiki/Rest_Users
        // push the changes to the backing user service
        super.updateUserModel(user);
        return user;
    }
    private String getCurrentUserAsJson(String username, char [] password) throws IOException {
        if (testingJson != null) { // for testing
            return testingJson;
        }
        String url = this.settings.getString(Keys.realm.redmine.url, "");
        if (!url.endsWith("/")) {
            url = url.concat("/");
        }
        HttpURLConnection http;
        if (username == null) {
            // apikey authentication
            String apiKey = String.valueOf(password);
            String apiUrl = url + "users/current.json?key=" + apiKey;
            http = (HttpURLConnection) ConnectionUtils.openConnection(apiUrl, null, null);
        } else {
            // username/password BASIC authentication
            String apiUrl = url + "users/current.json";
            http = (HttpURLConnection) ConnectionUtils.openConnection(apiUrl, username, password);
        }
        http.setRequestMethod("GET");
        http.connect();
        InputStreamReader reader = new InputStreamReader(http.getInputStream());
        return IOUtils.toString(reader);
    }
    /**
     * set json response. do NOT invoke from production code.
     * @param json json
     */
    public void setTestingCurrentUserAsJson(String json) {
        this.testingJson = json;
    }
}
/*
 * Copyright 2012 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.auth;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import org.apache.wicket.util.io.IOUtils;
import com.gitblit.Constants;
import com.gitblit.Constants.AccountType;
import com.gitblit.Keys;
import com.gitblit.auth.AuthenticationProvider.UsernamePasswordAuthenticationProvider;
import com.gitblit.models.UserModel;
import com.gitblit.utils.ArrayUtils;
import com.gitblit.utils.ConnectionUtils;
import com.gitblit.utils.StringUtils;
import com.google.gson.Gson;
/**
 * Implementation of Redmine authentication.<br>
 * you can login to gitblit with Redmine user id and api key.
 */
public class RedmineAuthProvider extends UsernamePasswordAuthenticationProvider {
    private String testingJson;
    private class RedmineCurrent {
        private class RedmineUser {
            public String login;
            public String firstname;
            public String lastname;
            public String mail;
        }
        public RedmineUser user;
    }
    public RedmineAuthProvider() {
        super("redmine");
    }
    @Override
    public void setup() {
    }
    @Override
    public boolean supportsCredentialChanges() {
        return false;
    }
    @Override
    public boolean supportsDisplayNameChanges() {
        return false;
    }
    @Override
    public boolean supportsEmailAddressChanges() {
        return false;
    }
    @Override
    public boolean supportsTeamMembershipChanges() {
        return false;
    }
     @Override
    public AccountType getAccountType() {
        return AccountType.REDMINE;
    }
    @Override
    public UserModel authenticate(String username, char[] password) {
        String jsonString = null;
        try {
            // first attempt by username/password
            jsonString = getCurrentUserAsJson(username, password);
        } catch (Exception e1) {
            logger.warn("Failed to authenticate via username/password against Redmine");
            try {
                // second attempt is by apikey
                jsonString = getCurrentUserAsJson(null, password);
                username = null;
            } catch (Exception e2) {
                logger.error("Failed to authenticate via apikey against Redmine", e2);
                return null;
            }
        }
        if (StringUtils.isEmpty(jsonString)) {
            logger.error("Received empty authentication response from Redmine");
            return null;
        }
        RedmineCurrent current = null;
        try {
            current = new Gson().fromJson(jsonString, RedmineCurrent.class);
        } catch (Exception e) {
            logger.error("Failed to deserialize Redmine json response: " + jsonString, e);
            return null;
        }
        if (StringUtils.isEmpty(username)) {
            // if the username has been reset because of apikey authentication
            // then use the email address of the user. this is the original
            // behavior as contributed by github/mallowlabs
            username = current.user.mail;
        }
        UserModel user = userManager.getUserModel(username);
        if (user == null)    // create user object for new authenticated user
            user = new UserModel(username.toLowerCase());
        // create a user cookie
        if (StringUtils.isEmpty(user.cookie) && !ArrayUtils.isEmpty(password)) {
            user.cookie = StringUtils.getSHA1(user.username + new String(password));
        }
        // update user attributes from Redmine
        user.accountType = getAccountType();
        user.displayName = current.user.firstname + " " + current.user.lastname;
        user.emailAddress = current.user.mail;
        user.password = Constants.EXTERNAL_ACCOUNT;
        if (!StringUtils.isEmpty(current.user.login)) {
            // only admin users can get login name
            // evidently this is an undocumented behavior of Redmine
            user.canAdmin = true;
        }
        // TODO consider Redmine group mapping for team membership
        // http://www.redmine.org/projects/redmine/wiki/Rest_Users
        // push the changes to the backing user service
        updateUser(user);
        return user;
    }
    private String getCurrentUserAsJson(String username, char [] password) throws IOException {
        if (testingJson != null) { // for testing
            return testingJson;
        }
        String url = this.settings.getString(Keys.realm.redmine.url, "");
        if (!url.endsWith("/")) {
            url = url.concat("/");
        }
        HttpURLConnection http;
        if (username == null) {
            // apikey authentication
            String apiKey = String.valueOf(password);
            String apiUrl = url + "users/current.json?key=" + apiKey;
            http = (HttpURLConnection) ConnectionUtils.openConnection(apiUrl, null, null);
        } else {
            // username/password BASIC authentication
            String apiUrl = url + "users/current.json";
            http = (HttpURLConnection) ConnectionUtils.openConnection(apiUrl, username, password);
        }
        http.setRequestMethod("GET");
        http.connect();
        InputStreamReader reader = new InputStreamReader(http.getInputStream());
        return IOUtils.toString(reader);
    }
    /**
     * set json response. do NOT invoke from production code.
     * @param json json
     */
    public void setTestingCurrentUserAsJson(String json) {
        this.testingJson = json;
    }
}
src/main/java/com/gitblit/auth/SalesforceAuthProvider.java
File was renamed from src/main/java/com/gitblit/SalesforceUserService.java
@@ -1,12 +1,9 @@
package com.gitblit;
package com.gitblit.auth;
import java.io.File;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.gitblit.Constants;
import com.gitblit.Constants.AccountType;
import com.gitblit.manager.IRuntimeManager;
import com.gitblit.Keys;
import com.gitblit.auth.AuthenticationProvider.UsernamePasswordAuthenticationProvider;
import com.gitblit.models.UserModel;
import com.gitblit.utils.ArrayUtils;
import com.gitblit.utils.StringUtils;
@@ -16,10 +13,11 @@
import com.sforce.ws.ConnectionException;
import com.sforce.ws.ConnectorConfig;
public class SalesforceUserService extends GitblitUserService {
public class SalesforceAuthProvider extends UsernamePasswordAuthenticationProvider {
    public static final Logger logger = LoggerFactory.getLogger(SalesforceUserService.class);
    private IStoredSettings settings;
    public SalesforceAuthProvider() {
        super("salesforce");
    }
    @Override
    public AccountType getAccountType() {
@@ -27,26 +25,11 @@
    }
    @Override
    public void setup(IRuntimeManager runtimeManager) {
        this.settings = runtimeManager.getSettings();
        String file = settings.getString(
                Keys.realm.salesforce.backingUserService,
                "${baseFolder}/users.conf");
        File realmFile = runtimeManager.getFileOrFolder(file);
        serviceImpl = createUserService(realmFile);
        logger.info("Salesforce User Service backed by "
                + serviceImpl.toString());
    public void setup() {
    }
    @Override
    public UserModel authenticate(String username, char[] password) {
        if (isLocalAccount(username)) {
            // local account, bypass Salesforce authentication
            return super.authenticate(username, password);
        }
        ConnectorConfig config = new ConnectorConfig();
        config.setUsername(username);
        config.setPassword(new String(password));
@@ -78,7 +61,7 @@
            UserModel user = null;
            synchronized (this) {
                user = getUserModel(simpleUsername);
                user = userManager.getUserModel(simpleUsername);
                if (user == null)
                    user = new UserModel(simpleUsername);
@@ -90,7 +73,7 @@
                setUserAttributes(user, info);
                super.updateUserModel(user);
                updateUser(user);
            }
            return user;
@@ -122,6 +105,7 @@
        return email.split("@")[0];
    }
    @Override
    public boolean supportsCredentialChanges() {
        return false;
@@ -136,4 +120,9 @@
    public boolean supportsEmailAddressChanges() {
        return false;
    }
    @Override
    public boolean supportsTeamMembershipChanges() {
        return true;
    }
}
src/main/java/com/gitblit/auth/WindowsAuthProvider.java
File was renamed from src/main/java/com/gitblit/WindowsUserService.java
@@ -1,195 +1,177 @@
/*
 * 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;
import java.io.File;
import java.util.Set;
import java.util.TreeSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import waffle.windows.auth.IWindowsAccount;
import waffle.windows.auth.IWindowsAuthProvider;
import waffle.windows.auth.IWindowsComputer;
import waffle.windows.auth.IWindowsIdentity;
import waffle.windows.auth.impl.WindowsAuthProviderImpl;
import com.gitblit.Constants.AccountType;
import com.gitblit.manager.IRuntimeManager;
import com.gitblit.models.UserModel;
import com.gitblit.utils.ArrayUtils;
import com.gitblit.utils.StringUtils;
import com.sun.jna.platform.win32.Win32Exception;
/**
 * Implementation of a Windows user service.
 *
 * @author James Moger
 */
public class WindowsUserService extends GitblitUserService {
    private final Logger logger = LoggerFactory.getLogger(WindowsUserService.class);
    private IStoredSettings settings;
    private IWindowsAuthProvider waffle;
    public WindowsUserService() {
        super();
    }
    @Override
    public void setup(IRuntimeManager runtimeManager) {
        this.settings = runtimeManager.getSettings();
        String file = settings.getString(Keys.realm.windows.backingUserService, "${baseFolder}/users.conf");
        File realmFile = runtimeManager.getFileOrFolder(file);
        serviceImpl = createUserService(realmFile);
        logger.info("Windows User Service backed by " + serviceImpl.toString());
        waffle = new WindowsAuthProviderImpl();
        IWindowsComputer computer = waffle.getCurrentComputer();
        logger.info("      name = " + computer.getComputerName());
        logger.info("    status = " + describeJoinStatus(computer.getJoinStatus()));
        logger.info("  memberOf = " + computer.getMemberOf());
        //logger.info("  groups     = " + Arrays.asList(computer.getGroups()));
    }
    protected String describeJoinStatus(String value) {
        if ("NetSetupUnknownStatus".equals(value)) {
            return "unknown";
        } else if ("NetSetupUnjoined".equals(value)) {
            return "not joined";
        } else if ("NetSetupWorkgroupName".equals(value)) {
            return "joined to a workgroup";
        } else if ("NetSetupDomainName".equals(value)) {
            return "joined to a domain";
        }
        return value;
    }
    @Override
    public boolean supportsCredentialChanges() {
        return false;
    }
    @Override
    public boolean supportsDisplayNameChanges() {
        return false;
    }
    @Override
    public boolean supportsEmailAddressChanges() {
        return true;
    }
    @Override
    public boolean supportsTeamMembershipChanges() {
        return true;
    }
     @Override
    public AccountType getAccountType() {
        return AccountType.WINDOWS;
    }
    @Override
    public UserModel authenticate(String username, char[] password) {
        if (isLocalAccount(username)) {
            // local account, bypass Windows authentication
            return super.authenticate(username, password);
        }
        String defaultDomain = settings.getString(Keys.realm.windows.defaultDomain, null);
        if (StringUtils.isEmpty(defaultDomain)) {
            // ensure that default domain is null
            defaultDomain = null;
        }
        if (defaultDomain != null) {
            // sanitize username
            if (username.startsWith(defaultDomain + "\\")) {
                // strip default domain from domain\ username
                username = username.substring(defaultDomain.length() + 1);
            } else if (username.endsWith("@" + defaultDomain)) {
                // strip default domain from username@domain
                username = username.substring(0, username.lastIndexOf('@'));
            }
        }
        IWindowsIdentity identity = null;
        try {
            if (username.indexOf('@') > -1 || username.indexOf('\\') > -1) {
                // manually specified domain
                identity = waffle.logonUser(username, new String(password));
            } else {
                // no domain specified, use default domain
                identity = waffle.logonDomainUser(username, defaultDomain, new String(password));
            }
        } catch (Win32Exception e) {
            logger.error(e.getMessage());
            return null;
        }
        if (identity.isGuest() && !settings.getBoolean(Keys.realm.windows.allowGuests, false)) {
            logger.warn("Guest account access is disabled");
            identity.dispose();
            return null;
        }
        UserModel user = getUserModel(username);
        if (user == null)    // create user object for new authenticated user
            user = new UserModel(username.toLowerCase());
        // create a user cookie
        if (StringUtils.isEmpty(user.cookie) && !ArrayUtils.isEmpty(password)) {
            user.cookie = StringUtils.getSHA1(user.username + new String(password));
        }
        // update user attributes from Windows identity
        user.accountType = getAccountType();
        String fqn = identity.getFqn();
        if (fqn.indexOf('\\') > -1) {
            user.displayName = fqn.substring(fqn.lastIndexOf('\\') + 1);
        } else {
            user.displayName = fqn;
        }
        user.password = Constants.EXTERNAL_ACCOUNT;
        Set<String> groupNames = new TreeSet<String>();
           for (IWindowsAccount group : identity.getGroups()) {
               groupNames.add(group.getFqn());
        }
        if (groupNames.contains("BUILTIN\\Administrators")) {
            // local administrator
            user.canAdmin = true;
        }
        // TODO consider mapping Windows groups to teams
        // push the changes to the backing user service
        super.updateUserModel(user);
        // cleanup resources
        identity.dispose();
        return user;
    }
}
/*
 * 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.auth;
import java.util.Set;
import java.util.TreeSet;
import waffle.windows.auth.IWindowsAccount;
import waffle.windows.auth.IWindowsAuthProvider;
import waffle.windows.auth.IWindowsComputer;
import waffle.windows.auth.IWindowsIdentity;
import waffle.windows.auth.impl.WindowsAuthProviderImpl;
import com.gitblit.Constants;
import com.gitblit.Constants.AccountType;
import com.gitblit.Keys;
import com.gitblit.auth.AuthenticationProvider.UsernamePasswordAuthenticationProvider;
import com.gitblit.models.UserModel;
import com.gitblit.utils.ArrayUtils;
import com.gitblit.utils.StringUtils;
import com.sun.jna.platform.win32.Win32Exception;
/**
 * Implementation of a Windows authentication provider.
 *
 * @author James Moger
 */
public class WindowsAuthProvider extends UsernamePasswordAuthenticationProvider {
    private IWindowsAuthProvider waffle;
    public WindowsAuthProvider() {
        super("windows");
    }
    @Override
    public void setup() {
        waffle = new WindowsAuthProviderImpl();
        IWindowsComputer computer = waffle.getCurrentComputer();
        logger.info("Windows Authentication Provider");
        logger.info("      name = " + computer.getComputerName());
        logger.info("    status = " + describeJoinStatus(computer.getJoinStatus()));
        logger.info("  memberOf = " + computer.getMemberOf());
        //logger.info("  groups     = " + Arrays.asList(computer.getGroups()));
    }
    protected String describeJoinStatus(String value) {
        if ("NetSetupUnknownStatus".equals(value)) {
            return "unknown";
        } else if ("NetSetupUnjoined".equals(value)) {
            return "not joined";
        } else if ("NetSetupWorkgroupName".equals(value)) {
            return "joined to a workgroup";
        } else if ("NetSetupDomainName".equals(value)) {
            return "joined to a domain";
        }
        return value;
    }
    @Override
    public boolean supportsCredentialChanges() {
        return false;
    }
    @Override
    public boolean supportsDisplayNameChanges() {
        return false;
    }
    @Override
    public boolean supportsEmailAddressChanges() {
        return true;
    }
    @Override
    public boolean supportsTeamMembershipChanges() {
        return true;
    }
     @Override
    public AccountType getAccountType() {
        return AccountType.WINDOWS;
    }
    @Override
    public UserModel authenticate(String username, char[] password) {
        String defaultDomain = settings.getString(Keys.realm.windows.defaultDomain, null);
        if (StringUtils.isEmpty(defaultDomain)) {
            // ensure that default domain is null
            defaultDomain = null;
        }
        if (defaultDomain != null) {
            // sanitize username
            if (username.startsWith(defaultDomain + "\\")) {
                // strip default domain from domain\ username
                username = username.substring(defaultDomain.length() + 1);
            } else if (username.endsWith("@" + defaultDomain)) {
                // strip default domain from username@domain
                username = username.substring(0, username.lastIndexOf('@'));
            }
        }
        IWindowsIdentity identity = null;
        try {
            if (username.indexOf('@') > -1 || username.indexOf('\\') > -1) {
                // manually specified domain
                identity = waffle.logonUser(username, new String(password));
            } else {
                // no domain specified, use default domain
                identity = waffle.logonDomainUser(username, defaultDomain, new String(password));
            }
        } catch (Win32Exception e) {
            logger.error(e.getMessage());
            return null;
        }
        if (identity.isGuest() && !settings.getBoolean(Keys.realm.windows.allowGuests, false)) {
            logger.warn("Guest account access is disabled");
            identity.dispose();
            return null;
        }
        UserModel user = userManager.getUserModel(username);
        if (user == null)    // create user object for new authenticated user
            user = new UserModel(username.toLowerCase());
        // create a user cookie
        if (StringUtils.isEmpty(user.cookie) && !ArrayUtils.isEmpty(password)) {
            user.cookie = StringUtils.getSHA1(user.username + new String(password));
        }
        // update user attributes from Windows identity
        user.accountType = getAccountType();
        String fqn = identity.getFqn();
        if (fqn.indexOf('\\') > -1) {
            user.displayName = fqn.substring(fqn.lastIndexOf('\\') + 1);
        } else {
            user.displayName = fqn;
        }
        user.password = Constants.EXTERNAL_ACCOUNT;
        Set<String> groupNames = new TreeSet<String>();
           for (IWindowsAccount group : identity.getGroups()) {
               groupNames.add(group.getFqn());
        }
        if (groupNames.contains("BUILTIN\\Administrators")) {
            // local administrator
            user.canAdmin = true;
        }
        // TODO consider mapping Windows groups to teams
        // push the changes to the backing user service
        updateUser(user);
        // cleanup resources
        identity.dispose();
        return user;
    }
}
src/main/java/com/gitblit/client/EditTeamDialog.java
@@ -146,7 +146,6 @@
        final Insets _insets = new Insets(5, 5, 5, 5);
        repositoryPalette = new RegistrantPermissionsPanel(RegistrantType.REPOSITORY);
        userPalette = new JPalette<String>();
        userPalette.setEnabled(settings.supportsTeamMembershipChanges);
        JPanel fieldsPanelTop = new JPanel(new BorderLayout());
        fieldsPanelTop.add(fieldsPanel, BorderLayout.NORTH);
src/main/java/com/gitblit/client/EditUserDialog.java
@@ -161,18 +161,9 @@
        countryCodeField = new JTextField(anUser.countryCode == null ? "" : anUser.countryCode, 15);
        // credentials are optionally controlled by 3rd-party authentication
        usernameField.setEnabled(settings.supportsCredentialChanges);
        passwordField.setEnabled(settings.supportsCredentialChanges);
        confirmPasswordField.setEnabled(settings.supportsCredentialChanges);
        displayNameField.setEnabled(settings.supportsDisplayNameChanges);
        emailAddressField.setEnabled(settings.supportsEmailAddressChanges);
        organizationalUnitField.setEnabled(settings.supportsDisplayNameChanges);
        organizationField.setEnabled(settings.supportsDisplayNameChanges);
        localityField.setEnabled(settings.supportsDisplayNameChanges);
        stateProvinceField.setEnabled(settings.supportsDisplayNameChanges);
        countryCodeField.setEnabled(settings.supportsDisplayNameChanges);
        usernameField.setEnabled(anUser.isLocalAccount());
        passwordField.setEnabled(anUser.isLocalAccount());
        confirmPasswordField.setEnabled(anUser.isLocalAccount());
        JPanel fieldsPanel = new JPanel(new GridLayout(0, 1));
        fieldsPanel.add(newFieldPanel(Translation.get("gb.username"), usernameField));
@@ -196,7 +187,6 @@
        final Insets _insets = new Insets(5, 5, 5, 5);
        repositoryPalette = new RegistrantPermissionsPanel(RegistrantType.REPOSITORY);
        teamsPalette = new JPalette<TeamModel>();
        teamsPalette.setEnabled(settings.supportsTeamMembershipChanges);
        JPanel fieldsPanelTop = new JPanel(new BorderLayout());
        fieldsPanelTop.add(fieldsPanel, BorderLayout.NORTH);
src/main/java/com/gitblit/git/GitblitUploadPackFactory.java
@@ -23,7 +23,7 @@
import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException;
import org.eclipse.jgit.transport.resolver.UploadPackFactory;
import com.gitblit.manager.ISessionManager;
import com.gitblit.manager.IAuthenticationManager;
import com.gitblit.models.UserModel;
/**
@@ -36,10 +36,10 @@
 */
public class GitblitUploadPackFactory<X> implements UploadPackFactory<X> {
    private final ISessionManager sessionManager;
    private final IAuthenticationManager authenticationManager;
    public GitblitUploadPackFactory(ISessionManager sessionManager) {
        this.sessionManager = sessionManager;
    public GitblitUploadPackFactory(IAuthenticationManager authenticationManager) {
        this.authenticationManager = authenticationManager;
    }
    @Override
@@ -51,7 +51,7 @@
        if (req instanceof HttpServletRequest) {
            // http/https request may or may not be authenticated
            user = sessionManager.authenticate((HttpServletRequest) req);
            user = authenticationManager.authenticate((HttpServletRequest) req);
            if (user == null) {
                user = UserModel.ANONYMOUS;
            }
src/main/java/com/gitblit/manager/AuthenticationManager.java
New file
@@ -0,0 +1,511 @@
/*
 * 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.nio.charset.Charset;
import java.security.Principal;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.wicket.RequestCycle;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.gitblit.Constants;
import com.gitblit.Constants.AccountType;
import com.gitblit.Constants.AuthenticationType;
import com.gitblit.IStoredSettings;
import com.gitblit.Keys;
import com.gitblit.auth.AuthenticationProvider;
import com.gitblit.auth.AuthenticationProvider.UsernamePasswordAuthenticationProvider;
import com.gitblit.auth.HtpasswdAuthProvider;
import com.gitblit.auth.LdapAuthProvider;
import com.gitblit.auth.PAMAuthProvider;
import com.gitblit.auth.RedmineAuthProvider;
import com.gitblit.auth.SalesforceAuthProvider;
import com.gitblit.auth.WindowsAuthProvider;
import com.gitblit.models.TeamModel;
import com.gitblit.models.UserModel;
import com.gitblit.utils.Base64;
import com.gitblit.utils.HttpUtils;
import com.gitblit.utils.StringUtils;
import com.gitblit.utils.X509Utils.X509Metadata;
import com.gitblit.wicket.GitBlitWebSession;
/**
 * The authentication manager handles user login & logout.
 *
 * @author James Moger
 *
 */
public class AuthenticationManager implements IAuthenticationManager {
    private final Logger logger = LoggerFactory.getLogger(getClass());
    private final IStoredSettings settings;
    private final IRuntimeManager runtimeManager;
    private final IUserManager userManager;
    private final List<AuthenticationProvider> authenticationProviders;
    private final Map<String, Class<? extends AuthenticationProvider>> providerNames;
    private final Map<String, String> legacyRedirects;
    public AuthenticationManager(
            IRuntimeManager runtimeManager,
            IUserManager userManager) {
        this.settings = runtimeManager.getSettings();
        this.runtimeManager = runtimeManager;
        this.userManager = userManager;
        this.authenticationProviders = new ArrayList<AuthenticationProvider>();
        // map of shortcut provider names
        providerNames = new HashMap<String, Class<? extends AuthenticationProvider>>();
        providerNames.put("htpasswd", HtpasswdAuthProvider.class);
        providerNames.put("ldap", LdapAuthProvider.class);
        providerNames.put("pam", PAMAuthProvider.class);
        providerNames.put("redmine", RedmineAuthProvider.class);
        providerNames.put("salesforce", SalesforceAuthProvider.class);
        providerNames.put("windows", WindowsAuthProvider.class);
        // map of legacy external user services
        legacyRedirects = new HashMap<String, String>();
        legacyRedirects.put("com.gitblit.HtpasswdUserService", "htpasswd");
        legacyRedirects.put("com.gitblit.LdapUserService", "ldap");
        legacyRedirects.put("com.gitblit.PAMUserService", "pam");
        legacyRedirects.put("com.gitblit.RedmineUserService", "redmine");
        legacyRedirects.put("com.gitblit.SalesforceUserService", "salesforce");
        legacyRedirects.put("com.gitblit.WindowsUserService", "windows");
    }
    @Override
    public AuthenticationManager start() {
        // automatically adjust legacy configurations
        String realm = settings.getString(Keys.realm.userService, "${baseFolder}/users.conf");
        if (legacyRedirects.containsKey(realm)) {
            logger.warn("");
            logger.warn("#################################################################");
            logger.warn(" IUserService '{}' is obsolete!", realm);
            logger.warn(" Please set '{}={}'", "realm.authenticationProviders", legacyRedirects.get(realm));
            logger.warn("#################################################################");
            logger.warn("");
            // conditionally override specified authentication providers
            if (StringUtils.isEmpty(settings.getString(Keys.realm.authenticationProviders, null))) {
                settings.overrideSetting(Keys.realm.authenticationProviders, legacyRedirects.get(realm));
            }
        }
        // instantiate and setup specified authentication providers
        List<String> providers = settings.getStrings(Keys.realm.authenticationProviders);
        if (providers.isEmpty()) {
            logger.info("External authentication disabled.");
        } else {
            for (String provider : providers) {
                try {
                    Class<?> authClass;
                    if (providerNames.containsKey(provider)) {
                        // map the name -> class
                        authClass = providerNames.get(provider);
                    } else {
                        // reflective lookup
                        authClass = Class.forName(provider);
                    }
                    logger.info("setting up {}", authClass.getName());
                    AuthenticationProvider authImpl = (AuthenticationProvider) authClass.newInstance();
                    authImpl.setup(runtimeManager, userManager);
                    authenticationProviders.add(authImpl);
                } catch (Exception e) {
                    logger.error("", e);
                }
            }
        }
        return this;
    }
    @Override
    public AuthenticationManager stop() {
        return this;
    }
    /**
     * Authenticate a user based on HTTP request parameters.
     *
     * Authentication by X509Certificate is tried first and then by cookie.
     *
     * @param httpRequest
     * @return a user object or null
     */
    @Override
    public UserModel authenticate(HttpServletRequest httpRequest) {
        return authenticate(httpRequest, false);
    }
    /**
     * Authenticate a user based on HTTP request parameters.
     *
     * Authentication by servlet container principal, X509Certificate, cookie,
     * and finally BASIC header.
     *
     * @param httpRequest
     * @param requiresCertificate
     * @return a user object or null
     */
    @Override
    public UserModel authenticate(HttpServletRequest httpRequest, boolean requiresCertificate) {
        // try to authenticate by servlet container principal
        if (!requiresCertificate) {
            Principal principal = httpRequest.getUserPrincipal();
            if (principal != null) {
                String username = principal.getName();
                if (!StringUtils.isEmpty(username)) {
                    boolean internalAccount = isInternalAccount(username);
                    UserModel user = userManager.getUserModel(username);
                    if (user != null) {
                        // existing user
                        flagWicketSession(AuthenticationType.CONTAINER);
                        logger.debug(MessageFormat.format("{0} authenticated by servlet container principal from {1}",
                                user.username, httpRequest.getRemoteAddr()));
                        return user;
                    } else if (settings.getBoolean(Keys.realm.container.autoCreateAccounts, false)
                            && !internalAccount) {
                        // auto-create user from an authenticated container principal
                        user = new UserModel(username.toLowerCase());
                        user.displayName = username;
                        user.password = Constants.EXTERNAL_ACCOUNT;
                        user.accountType = AccountType.CONTAINER;
                        userManager.updateUserModel(user);
                        flagWicketSession(AuthenticationType.CONTAINER);
                        logger.debug(MessageFormat.format("{0} authenticated and created by servlet container principal from {1}",
                                user.username, httpRequest.getRemoteAddr()));
                        return user;
                    } else if (!internalAccount) {
                        logger.warn(MessageFormat.format("Failed to find UserModel for {0}, attempted servlet container authentication from {1}",
                                principal.getName(), httpRequest.getRemoteAddr()));
                    }
                }
            }
        }
        // try to authenticate by certificate
        boolean checkValidity = settings.getBoolean(Keys.git.enforceCertificateValidity, true);
        String [] oids = settings.getStrings(Keys.git.certificateUsernameOIDs).toArray(new String[0]);
        UserModel model = HttpUtils.getUserModelFromCertificate(httpRequest, checkValidity, oids);
        if (model != null) {
            // grab real user model and preserve certificate serial number
            UserModel user = userManager.getUserModel(model.username);
            X509Metadata metadata = HttpUtils.getCertificateMetadata(httpRequest);
            if (user != null) {
                flagWicketSession(AuthenticationType.CERTIFICATE);
                logger.debug(MessageFormat.format("{0} authenticated by client certificate {1} from {2}",
                        user.username, metadata.serialNumber, httpRequest.getRemoteAddr()));
                return user;
            } else {
                logger.warn(MessageFormat.format("Failed to find UserModel for {0}, attempted client certificate ({1}) authentication from {2}",
                        model.username, metadata.serialNumber, httpRequest.getRemoteAddr()));
            }
        }
        if (requiresCertificate) {
            // caller requires client certificate authentication (e.g. git servlet)
            return null;
        }
        // try to authenticate by cookie
        UserModel user = authenticate(httpRequest.getCookies());
        if (user != null) {
            flagWicketSession(AuthenticationType.COOKIE);
            logger.debug(MessageFormat.format("{0} authenticated by cookie from {1}",
                    user.username, httpRequest.getRemoteAddr()));
            return user;
        }
        // try to authenticate by BASIC
        final String authorization = httpRequest.getHeader("Authorization");
        if (authorization != null && authorization.startsWith("Basic")) {
            // Authorization: Basic base64credentials
            String base64Credentials = authorization.substring("Basic".length()).trim();
            String credentials = new String(Base64.decode(base64Credentials),
                    Charset.forName("UTF-8"));
            // credentials = username:password
            final String[] values = credentials.split(":", 2);
            if (values.length == 2) {
                String username = values[0];
                char[] password = values[1].toCharArray();
                user = authenticate(username, password);
                if (user != null) {
                    flagWicketSession(AuthenticationType.CREDENTIALS);
                    logger.debug(MessageFormat.format("{0} authenticated by BASIC request header from {1}",
                            user.username, httpRequest.getRemoteAddr()));
                    return user;
                } else {
                    logger.warn(MessageFormat.format("Failed login attempt for {0}, invalid credentials from {1}",
                            username, httpRequest.getRemoteAddr()));
                }
            }
        }
        return null;
    }
    /**
     * Authenticate a user based on their cookie.
     *
     * @param cookies
     * @return a user object or null
     */
    protected UserModel authenticate(Cookie[] cookies) {
        if (settings.getBoolean(Keys.web.allowCookieAuthentication, true)) {
            if (cookies != null && cookies.length > 0) {
                for (Cookie cookie : cookies) {
                    if (cookie.getName().equals(Constants.NAME)) {
                        String value = cookie.getValue();
                        return userManager.getUserModel(value.toCharArray());
                    }
                }
            }
        }
        return null;
    }
    protected void flagWicketSession(AuthenticationType authenticationType) {
        RequestCycle requestCycle = RequestCycle.get();
        if (requestCycle != null) {
            // flag the Wicket session, if this is a Wicket request
            GitBlitWebSession session = GitBlitWebSession.get();
            session.authenticationType = authenticationType;
        }
    }
    /**
     * Authenticate a user based on a username and password.
     *
     * @see IUserService.authenticate(String, char[])
     * @param username
     * @param password
     * @return a user object or null
     */
    @Override
    public UserModel authenticate(String username, char[] password) {
        if (StringUtils.isEmpty(username)) {
            // can not authenticate empty username
            return null;
        }
        String usernameDecoded = StringUtils.decodeUsername(username);
        String pw = new String(password);
        if (StringUtils.isEmpty(pw)) {
            // can not authenticate empty password
            return null;
        }
        // check to see if this is the federation user
//        if (canFederate()) {
//            if (usernameDecoded.equalsIgnoreCase(Constants.FEDERATION_USER)) {
//                List<String> tokens = getFederationTokens();
//                if (tokens.contains(pw)) {
//                    return getFederationUser();
//                }
//            }
//        }
        // try local authentication
        UserModel user = userManager.getUserModel(usernameDecoded);
        if (user != null) {
            UserModel returnedUser = 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;
        }
        // try registered external authentication providers
        if (user == null) {
            for (AuthenticationProvider provider : authenticationProviders) {
                if (provider instanceof UsernamePasswordAuthenticationProvider) {
                    user = provider.authenticate(usernameDecoded, password);
                    if (user != null) {
                        // user authenticated
                        user.accountType = provider.getAccountType();
                        return user;
                    }
                }
            }
        }
        return user;
    }
    /**
     * Sets a cookie for the specified user.
     *
     * @param response
     * @param user
     */
    @Override
    public void setCookie(HttpServletResponse response, UserModel user) {
        if (settings.getBoolean(Keys.web.allowCookieAuthentication, true)) {
            GitBlitWebSession session = GitBlitWebSession.get();
            boolean standardLogin = session.authenticationType.isStandard();
            if (standardLogin) {
                Cookie userCookie;
                if (user == null) {
                    // clear cookie for logout
                    userCookie = new Cookie(Constants.NAME, "");
                } else {
                    // set cookie for login
                    String cookie = userManager.getCookie(user);
                    if (StringUtils.isEmpty(cookie)) {
                        // create empty cookie
                        userCookie = new Cookie(Constants.NAME, "");
                    } else {
                        // create real cookie
                        userCookie = new Cookie(Constants.NAME, cookie);
                        userCookie.setMaxAge(Integer.MAX_VALUE);
                    }
                }
                userCookie.setPath("/");
                response.addCookie(userCookie);
            }
        }
    }
    /**
     * Logout a user.
     *
     * @param user
     */
    @Override
    public void logout(HttpServletResponse response, UserModel user) {
        setCookie(response,  null);
    }
    /**
     * 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) {
        return (user != null && user.isLocalAccount()) || findProvider(user).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()) || findProvider(user).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()) || findProvider(user).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()) || findProvider(user).supportsTeamMembershipChanges();
    }
    /**
     * Returns true if the team memberships can be changed.
     *
     * @param user
     * @return true if the team membership can be changed
     */
    @Override
    public boolean supportsTeamMembershipChanges(TeamModel team) {
        return (team != null && team.isLocalTeam()) || findProvider(team).supportsTeamMembershipChanges();
    }
    protected AuthenticationProvider findProvider(UserModel user) {
        for (AuthenticationProvider provider : authenticationProviders) {
            if (provider.getAccountType().equals(user.accountType)) {
                return provider;
            }
        }
        return AuthenticationProvider.NULL_PROVIDER;
    }
    protected AuthenticationProvider findProvider(TeamModel team) {
        for (AuthenticationProvider provider : authenticationProviders) {
            if (provider.getAccountType().equals(team.accountType)) {
                return provider;
            }
        }
        return AuthenticationProvider.NULL_PROVIDER;
    }
    /**
     * Returns true if the username represents an internal account
     *
     * @param username
     * @return true if the specified username represents an internal account
     */
    protected boolean isInternalAccount(String username) {
        return !StringUtils.isEmpty(username)
                && (username.equalsIgnoreCase(Constants.FEDERATION_USER)
                        || username.equalsIgnoreCase(UserModel.ANONYMOUS.username));
    }
//    protected UserModel getFederationUser() {
//        // the federation user is an administrator
//        UserModel federationUser = new UserModel(Constants.FEDERATION_USER);
//        federationUser.canAdmin = true;
//        return federationUser;
//    }
}
src/main/java/com/gitblit/manager/GitblitManager.java
@@ -101,13 +101,6 @@
     * @return Map<String, SettingModel>
     */
    private void loadSettingModels(ServerSettings settingsModel) {
        // this entire "supports" concept will go away with user service refactoring
        UserModel externalUser = new UserModel(Constants.EXTERNAL_ACCOUNT);
        externalUser.password = Constants.EXTERNAL_ACCOUNT;
        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
src/main/java/com/gitblit/manager/IAuthenticationManager.java
File was renamed from src/main/java/com/gitblit/manager/ISessionManager.java
@@ -18,9 +18,10 @@
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.gitblit.models.TeamModel;
import com.gitblit.models.UserModel;
public interface ISessionManager extends IManager {
public interface IAuthenticationManager extends IManager {
    /**
     * Authenticate a user based on HTTP request parameters.
@@ -69,4 +70,44 @@
     */
    void logout(HttpServletResponse response, UserModel user);
    /**
     * Does the user service support changes to credentials?
     *
     * @return true or false
     * @since 1.0.0
     */
    boolean supportsCredentialChanges(UserModel user);
    /**
     * Returns true if the user's display name can be changed.
     *
     * @param user
     * @return true if the user service supports display name changes
     */
    boolean supportsDisplayNameChanges(UserModel user);
    /**
     * Returns true if the user's email address can be changed.
     *
     * @param user
     * @return true if the user service supports email address changes
     */
    boolean supportsEmailAddressChanges(UserModel user);
    /**
     * Returns true if the user's team memberships can be changed.
     *
     * @param user
     * @return true if the user service supports team membership changes
     */
    boolean supportsTeamMembershipChanges(UserModel user);
    /**
     * Returns true if the team memberships can be changed.
     *
     * @param user
     * @return true if the team memberships can be changed
     */
    boolean supportsTeamMembershipChanges(TeamModel team);
}
src/main/java/com/gitblit/manager/IUserManager.java
@@ -15,266 +15,9 @@
 */
package com.gitblit.manager;
import java.util.Collection;
import java.util.List;
import com.gitblit.IUserService;
import com.gitblit.models.TeamModel;
import com.gitblit.models.UserModel;
public interface IUserManager extends IManager, IUserService {
public interface IUserManager extends IManager {
    boolean supportsAddUser();
    /**
     * Does the user service support changes to credentials?
     *
     * @return true or false
     * @since 1.0.0
     */
    boolean supportsCredentialChanges(UserModel user);
    /**
     * Returns true if the user's display name can be changed.
     *
     * @param user
     * @return true if the user service supports display name changes
     */
    boolean supportsDisplayNameChanges(UserModel user);
    /**
     * Returns true if the user's email address can be changed.
     *
     * @param user
     * @return true if the user service supports email address changes
     */
    boolean supportsEmailAddressChanges(UserModel user);
    /**
     * Returns true if the user's team memberships can be changed.
     *
     * @param user
     * @return true if the user service supports team membership changes
     */
    boolean supportsTeamMembershipChanges(UserModel user);
    /**
     * Does the user service support cookie authentication?
     *
     * @return true or false
     */
    boolean supportsCookies();
    /**
     * Returns the cookie value for the specified user.
     *
     * @param model
     * @return cookie value
     */
    String getCookie(UserModel model);
    /**
     * Authenticate a user based on their cookie.
     *
     * @param cookie
     * @return a user object or null
     */
    UserModel authenticate(char[] cookie);
    /**
     * Authenticate a user based on a username and password.
     *
     * @param username
     * @param password
     * @return a user object or null
     */
    UserModel authenticate(String username, char[] password);
    /**
     * Logout a user.
     *
     * @param user
     */
    void logout(UserModel user);
    /**
     * Retrieve the user object for the specified username.
     *
     * @param username
     * @return a user object or null
     */
    UserModel getUserModel(String username);
    /**
     * Updates/writes a complete user object.
     *
     * @param model
     * @return true if update is successful
     */
    boolean updateUserModel(UserModel model);
    /**
     * Updates/writes all specified user objects.
     *
     * @param models a list of user models
     * @return true if update is successful
     * @since 1.2.0
     */
    boolean updateUserModels(Collection<UserModel> models);
    /**
     * Adds/updates a user object keyed by username. This method allows for
     * renaming a user.
     *
     * @param username
     *            the old username
     * @param model
     *            the user object to use for username
     * @return true if update is successful
     */
    boolean updateUserModel(String username, UserModel model);
    /**
     * Deletes the user object from the user service.
     *
     * @param model
     * @return true if successful
     */
    boolean deleteUserModel(UserModel model);
    /**
     * Delete the user object with the specified username
     *
     * @param username
     * @return true if successful
     */
    boolean deleteUser(String username);
    /**
     * Returns the list of all users available to the login service.
     *
     * @return list of all usernames
     */
    List<String> getAllUsernames();
    /**
     * Returns the list of all users available to the login service.
     *
     * @return list of all users
     * @since 0.8.0
     */
    List<UserModel> getAllUsers();
    /**
     * Returns the list of all teams available to the login service.
     *
     * @return list of all teams
     * @since 0.8.0
     */
    List<String> getAllTeamNames();
    /**
     * Returns the list of all teams available to the login service.
     *
     * @return list of all teams
     * @since 0.8.0
     */
    List<TeamModel> getAllTeams();
    /**
     * Returns the list of all users who are allowed to bypass the access
     * restriction placed on the specified repository.
     *
     * @param role
     *            the repository name
     * @return list of all usernames that can bypass the access restriction
     * @since 0.8.0
     */
    List<String> getTeamNamesForRepositoryRole(String role);
    /**
     * Retrieve the team object for the specified team name.
     *
     * @param teamname
     * @return a team object or null
     * @since 0.8.0
     */
    TeamModel getTeamModel(String teamname);
    /**
     * Updates/writes a complete team object.
     *
     * @param model
     * @return true if update is successful
     * @since 0.8.0
     */
    boolean updateTeamModel(TeamModel model);
    /**
     * Updates/writes all specified team objects.
     *
     * @param models a list of team models
     * @return true if update is successful
     * @since 1.2.0
     */
    boolean updateTeamModels(Collection<TeamModel> models);
    /**
     * Updates/writes and replaces a complete team object keyed by teamname.
     * This method allows for renaming a team.
     *
     * @param teamname
     *            the old teamname
     * @param model
     *            the team object to use for teamname
     * @return true if update is successful
     * @since 0.8.0
     */
    boolean updateTeamModel(String teamname, TeamModel model);
    /**
     * Deletes the team object from the user service.
     *
     * @param model
     * @return true if successful
     * @since 0.8.0
     */
    boolean deleteTeamModel(TeamModel model);
    /**
     * Delete the team object with the specified teamname
     *
     * @param teamname
     * @return true if successful
     * @since 0.8.0
     */
    boolean deleteTeam(String teamname);
    /**
     * Returns the list of all users who are allowed to bypass the access
     * restriction placed on the specified repository.
     *
     * @param role
     *            the repository name
     * @return list of all usernames that can bypass the access restriction
     * @since 0.8.0
     */
    List<String> getUsernamesForRepositoryRole(String role);
    /**
     * Renames a repository role.
     *
     * @param oldRole
     * @param newRole
     * @return true if successful
     */
    boolean renameRepositoryRole(String oldRole, String newRole);
    /**
     * Removes a repository role from all users.
     *
     * @param role
     * @return true if successful
     */
    boolean deleteRepositoryRole(String role);
}
src/main/java/com/gitblit/manager/SessionManager.java
File was deleted
src/main/java/com/gitblit/manager/UserManager.java
@@ -20,20 +20,19 @@
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
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;
/**
@@ -50,37 +49,68 @@
    private final IRuntimeManager runtimeManager;
    private final Map<String, String> legacyBackingServices;
    private IUserService userService;
    public UserManager(IRuntimeManager runtimeManager) {
        this.settings = runtimeManager.getSettings();
        this.runtimeManager = runtimeManager;
        // map of legacy realm backing user services
        legacyBackingServices = new HashMap<String, String>();
        legacyBackingServices.put("com.gitblit.HtpasswdUserService", "realm.htpasswd.backingUserService");
        legacyBackingServices.put("com.gitblit.LdapUserService", "realm.ldap.backingUserService");
        legacyBackingServices.put("com.gitblit.PAMUserService", "realm.pam.backingUserService");
        legacyBackingServices.put("com.gitblit.RedmineUserService", "realm.redmine.backingUserService");
        legacyBackingServices.put("com.gitblit.SalesforceUserService", "realm.salesforce.backingUserService");
        legacyBackingServices.put("com.gitblit.WindowsUserService", "realm.windows.backingUserService");
    }
    /**
     * Set the user service. The user service authenticates local users and is
     * responsible for persisting and retrieving users and teams.
     * Set the user service. The user service authenticates *local* users and is
     * responsible for persisting and retrieving all users and all teams.
     *
     * @param userService
     */
    public void setUserService(IUserService userService) {
        logger.info("UserService: " + userService.toString());
        logger.info(userService.toString());
        this.userService = userService;
        this.userService.setup(runtimeManager);
    }
    @Override
    public void setup(IRuntimeManager runtimeManager) {
        // NOOP
    }
    @Override
    public UserManager start() {
        if (this.userService == null) {
            String realm = settings.getString(Keys.realm.userService, "${baseFolder}/users.properties");
            String realm = settings.getString(Keys.realm.userService, "${baseFolder}/users.conf");
            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");
            if (legacyBackingServices.containsKey(realm)) {
                // create the user service from the legacy config
                String realmKey = legacyBackingServices.get(realm);
                logger.warn("");
                logger.warn("#################################################################");
                logger.warn(" Key '{}' is obsolete!", realmKey);
                logger.warn(" Please set '{}={}'", Keys.realm.userService, settings.getString(realmKey, "${baseFolder}/users.conf"));
                logger.warn("#################################################################");
                logger.warn("");
                File realmFile = runtimeManager.getFileOrFolder(realmKey, "${baseFolder}/users.conf");
                service = createUserService(realmFile);
            } else {
                // either a file path OR a custom user service
                try {
                    // check to see if this "file" is a custom user service class
                    Class<?> realmClass = Class.forName(realm);
                    service = (IUserService) realmClass.newInstance();
                } catch (Throwable t) {
                    // typical file path configuration
                    File realmFile = runtimeManager.getFileOrFolder(Keys.realm.userService, "${baseFolder}/users.conf");
                    service = createUserService(realmFile);
                }
            }
            setUserService(service);
        }
@@ -90,7 +120,7 @@
    protected IUserService createUserService(File realmFile) {
        IUserService service = null;
        if (realmFile.getName().toLowerCase().endsWith(".conf")) {
            // v0.8.0+ config-based realm file
            // config-based realm file
            service = new ConfigUserService(realmFile);
        }
@@ -118,74 +148,6 @@
        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.
     *
@@ -198,44 +160,15 @@
    }
    /**
     * 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.
     * Retrieve the user object for the specified cookie.
     *
     * @param cookie
     * @return a user object or null
     */
    @Override
    public UserModel authenticate(char[] cookie) {
        UserModel user = userService.authenticate(cookie);
        setAccountType(user);
    public UserModel getUserModel(char[] cookie) {
        UserModel user = userService.getUserModel(cookie);
        return user;
    }
    /**
     * Logout a user.
     *
     * @param user
     */
    @Override
    public void logout(UserModel user) {
        if (userService == null) {
            return;
        }
        userService.logout(user);
    }
    /**
@@ -251,7 +184,6 @@
        }
        String usernameDecoded = StringUtils.decodeUsername(username);
        UserModel user = userService.getUserModel(usernameDecoded);
        setAccountType(user);
        return user;
    }
@@ -290,32 +222,7 @@
     */
    @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;
        return userService.updateUserModel(username, model);
    }
    /**
@@ -364,9 +271,6 @@
    @Override
    public List<UserModel> getAllUsers() {
        List<UserModel> users = userService.getAllUsers();
        for (UserModel user : users) {
            setAccountType(user);
        }
        return users;
    }
@@ -378,7 +282,8 @@
     */
    @Override
    public List<String> getAllTeamNames() {
        return userService.getAllTeamNames();
        List<String> teams = userService.getAllTeamNames();
        return teams;
    }
    /**
@@ -404,7 +309,8 @@
     */
    @Override
    public List<String> getTeamNamesForRepositoryRole(String role) {
        return userService.getTeamNamesForRepositoryRole(role);
        List<String> teams = userService.getTeamNamesForRepositoryRole(role);
        return teams;
    }
    /**
@@ -416,7 +322,8 @@
     */
    @Override
    public TeamModel getTeamModel(String teamname) {
        return userService.getTeamModel(teamname);
        TeamModel team = userService.getTeamModel(teamname);
        return team;
    }
    /**
@@ -456,14 +363,6 @@
     */
    @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);
    }
@@ -526,17 +425,5 @@
    @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/models/ServerSettings.java
@@ -37,14 +37,6 @@
    public List<String> pushScripts;
    public boolean supportsCredentialChanges;
    public boolean supportsDisplayNameChanges;
    public boolean supportsEmailAddressChanges;
    public boolean supportsTeamMembershipChanges;
    public ServerSettings() {
        settings = new TreeMap<String, SettingModel>();
    }
src/main/java/com/gitblit/models/TeamModel.java
@@ -27,6 +27,7 @@
import com.gitblit.Constants.AccessPermission;
import com.gitblit.Constants.AccessRestrictionType;
import com.gitblit.Constants.AccountType;
import com.gitblit.Constants.PermissionType;
import com.gitblit.Constants.RegistrantType;
import com.gitblit.Constants.Unused;
@@ -48,6 +49,7 @@
    public boolean canAdmin;
    public boolean canFork;
    public boolean canCreate;
    public AccountType accountType;
    public final Set<String> users = new HashSet<String>();
    // retained for backwards-compatibility with RPC clients
    @Deprecated
@@ -59,6 +61,7 @@
    public TeamModel(String name) {
        this.name = name;
        this.accountType = AccountType.LOCAL;
    }
    /**
@@ -358,6 +361,10 @@
        }
    }
    public boolean isLocalTeam() {
        return accountType.isLocal();
    }
    @Override
    public String toString() {
        return name;
src/main/java/com/gitblit/servlet/AccessRestrictionFilter.java
@@ -27,7 +27,7 @@
import com.gitblit.manager.IRepositoryManager;
import com.gitblit.manager.IRuntimeManager;
import com.gitblit.manager.ISessionManager;
import com.gitblit.manager.IAuthenticationManager;
import com.gitblit.models.RepositoryModel;
import com.gitblit.models.UserModel;
import com.gitblit.utils.StringUtils;
@@ -54,9 +54,9 @@
    protected AccessRestrictionFilter(
            IRuntimeManager runtimeManager,
            ISessionManager sessionManager,
            IAuthenticationManager authenticationManager,
            IRepositoryManager repositoryManager) {
        super(sessionManager);
        super(authenticationManager);
        this.runtimeManager = runtimeManager;
        this.repositoryManager = repositoryManager;
    }
src/main/java/com/gitblit/servlet/AuthenticationFilter.java
@@ -36,7 +36,7 @@
import org.slf4j.LoggerFactory;
import com.gitblit.Constants;
import com.gitblit.manager.ISessionManager;
import com.gitblit.manager.IAuthenticationManager;
import com.gitblit.models.UserModel;
import com.gitblit.utils.DeepCopier;
import com.gitblit.utils.StringUtils;
@@ -58,10 +58,10 @@
    protected transient Logger logger = LoggerFactory.getLogger(getClass());
    protected final ISessionManager sessionManager;
    protected final IAuthenticationManager authenticationManager;
    protected AuthenticationFilter(ISessionManager sessionManager) {
        this.sessionManager = sessionManager;
    protected AuthenticationFilter(IAuthenticationManager authenticationManager) {
        this.authenticationManager = authenticationManager;
    }
    /**
@@ -108,7 +108,7 @@
     * @return user
     */
    protected UserModel getUser(HttpServletRequest httpRequest) {
        UserModel user = sessionManager.authenticate(httpRequest, requiresClientCertificate());
        UserModel user = authenticationManager.authenticate(httpRequest, requiresClientCertificate());
        return user;
    }
src/main/java/com/gitblit/servlet/DownloadZipFilter.java
@@ -21,7 +21,7 @@
import com.gitblit.Constants.AccessRestrictionType;
import com.gitblit.manager.IRepositoryManager;
import com.gitblit.manager.IRuntimeManager;
import com.gitblit.manager.ISessionManager;
import com.gitblit.manager.IAuthenticationManager;
import com.gitblit.models.RepositoryModel;
import com.gitblit.models.UserModel;
@@ -39,10 +39,10 @@
    @Inject
    public DownloadZipFilter(
            IRuntimeManager runtimeManager,
            ISessionManager sessionManager,
            IAuthenticationManager authenticationManager,
            IRepositoryManager repositoryManager) {
        super(runtimeManager, sessionManager, repositoryManager);
        super(runtimeManager, authenticationManager, repositoryManager);
    }
    /**
src/main/java/com/gitblit/servlet/EnforceAuthenticationFilter.java
@@ -36,7 +36,7 @@
import com.gitblit.Keys;
import com.gitblit.Keys.web;
import com.gitblit.manager.IRuntimeManager;
import com.gitblit.manager.ISessionManager;
import com.gitblit.manager.IAuthenticationManager;
import com.gitblit.models.UserModel;
/**
@@ -54,16 +54,16 @@
    private final IStoredSettings settings;
    private final ISessionManager sessionManager;
    private final IAuthenticationManager authenticationManager;
    @Inject
    public EnforceAuthenticationFilter(
            IRuntimeManager runtimeManager,
            ISessionManager sessionManager) {
            IAuthenticationManager authenticationManager) {
        super();
        this.settings = runtimeManager.getSettings();
        this.sessionManager = sessionManager;
        this.authenticationManager = authenticationManager;
    }
    /*
@@ -86,7 +86,7 @@
        HttpServletRequest  httpRequest  = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        UserModel user = sessionManager.authenticate(httpRequest);
        UserModel user = authenticationManager.authenticate(httpRequest);
        if (mustForceAuth && (user == null)) {
            // not authenticated, enforce now:
src/main/java/com/gitblit/servlet/GitFilter.java
@@ -29,7 +29,7 @@
import com.gitblit.Keys.git;
import com.gitblit.manager.IRepositoryManager;
import com.gitblit.manager.IRuntimeManager;
import com.gitblit.manager.ISessionManager;
import com.gitblit.manager.IAuthenticationManager;
import com.gitblit.models.RepositoryModel;
import com.gitblit.models.UserModel;
import com.gitblit.utils.StringUtils;
@@ -57,10 +57,10 @@
    @Inject
    public GitFilter(
            IRuntimeManager runtimeManager,
            ISessionManager sessionManager,
            IAuthenticationManager authenticationManager,
            IRepositoryManager repositoryManager) {
        super(runtimeManager, sessionManager, repositoryManager);
        super(runtimeManager, authenticationManager, repositoryManager);
        this.settings = runtimeManager.getSettings();
    }
src/main/java/com/gitblit/servlet/GitblitContext.java
@@ -49,7 +49,7 @@
import com.gitblit.manager.IRepositoryManager;
import com.gitblit.manager.IRuntimeManager;
import com.gitblit.manager.IServicesManager;
import com.gitblit.manager.ISessionManager;
import com.gitblit.manager.IAuthenticationManager;
import com.gitblit.manager.IUserManager;
import com.gitblit.utils.ContainerUtils;
import com.gitblit.utils.StringUtils;
@@ -170,7 +170,7 @@
        // start all other managers
        startManager(injector, INotificationManager.class);
        startManager(injector, IUserManager.class);
        startManager(injector, ISessionManager.class);
        startManager(injector, IAuthenticationManager.class);
        startManager(injector, IRepositoryManager.class);
        startManager(injector, IProjectManager.class);
        startManager(injector, IGitblitManager.class);
src/main/java/com/gitblit/servlet/PagesFilter.java
@@ -23,7 +23,7 @@
import com.gitblit.Constants.AccessRestrictionType;
import com.gitblit.manager.IRepositoryManager;
import com.gitblit.manager.IRuntimeManager;
import com.gitblit.manager.ISessionManager;
import com.gitblit.manager.IAuthenticationManager;
import com.gitblit.models.RepositoryModel;
import com.gitblit.models.UserModel;
@@ -39,10 +39,10 @@
    @Inject
    public PagesFilter(IRuntimeManager runtimeManager,
            ISessionManager sessionManager,
            IAuthenticationManager authenticationManager,
            IRepositoryManager repositoryManager) {
        super(runtimeManager, sessionManager, repositoryManager);
        super(runtimeManager, authenticationManager, repositoryManager);
    }
    /**
src/main/java/com/gitblit/servlet/RpcFilter.java
@@ -31,7 +31,7 @@
import com.gitblit.IStoredSettings;
import com.gitblit.Keys;
import com.gitblit.manager.IRuntimeManager;
import com.gitblit.manager.ISessionManager;
import com.gitblit.manager.IAuthenticationManager;
import com.gitblit.models.UserModel;
/**
@@ -57,9 +57,9 @@
    @Inject
    public RpcFilter(
            IRuntimeManager runtimeManager,
            ISessionManager sessionManager) {
            IAuthenticationManager authenticationManager) {
        super(sessionManager);
        super(authenticationManager);
        this.settings = runtimeManager.getSettings();
        this.runtimeManager = runtimeManager;
    }
src/main/java/com/gitblit/servlet/SparkleShareInviteServlet.java
@@ -31,7 +31,7 @@
import com.gitblit.Keys.fanout;
import com.gitblit.manager.IRepositoryManager;
import com.gitblit.manager.IRuntimeManager;
import com.gitblit.manager.ISessionManager;
import com.gitblit.manager.IAuthenticationManager;
import com.gitblit.manager.IUserManager;
import com.gitblit.models.RepositoryModel;
import com.gitblit.models.UserModel;
@@ -52,7 +52,7 @@
    private final IUserManager userManager;
    private final ISessionManager sessionManager;
    private final IAuthenticationManager authenticationManager;
    private final IRepositoryManager repositoryManager;
@@ -60,13 +60,13 @@
    public SparkleShareInviteServlet(
            IRuntimeManager runtimeManager,
            IUserManager userManager,
            ISessionManager sessionManager,
            IAuthenticationManager authenticationManager,
            IRepositoryManager repositoryManager) {
        super();
        this.settings = runtimeManager.getSettings();
        this.userManager = userManager;
        this.sessionManager = sessionManager;
        this.authenticationManager = authenticationManager;
        this.repositoryManager = repositoryManager;
    }
@@ -106,7 +106,7 @@
        }
        UserModel user;
        if (StringUtils.isEmpty(username)) {
            user = sessionManager.authenticate(request);
            user = authenticationManager.authenticate(request);
        } else {
            user = userManager.getUserModel(username);
        }
src/main/java/com/gitblit/servlet/SyndicationFilter.java
@@ -31,7 +31,7 @@
import com.gitblit.manager.IProjectManager;
import com.gitblit.manager.IRepositoryManager;
import com.gitblit.manager.IRuntimeManager;
import com.gitblit.manager.ISessionManager;
import com.gitblit.manager.IAuthenticationManager;
import com.gitblit.models.ProjectModel;
import com.gitblit.models.RepositoryModel;
import com.gitblit.models.UserModel;
@@ -54,11 +54,11 @@
    @Inject
    public SyndicationFilter(
            IRuntimeManager runtimeManager,
            ISessionManager sessionManager,
            IAuthenticationManager authenticationManager,
            IRepositoryManager repositoryManager,
            IProjectManager projectManager) {
        super(sessionManager);
        super(authenticationManager);
        this.runtimeManager = runtimeManager;
        this.repositoryManager = repositoryManager;
        this.projectManager = projectManager;
src/main/java/com/gitblit/wicket/GitBlitWebApp.java
@@ -36,7 +36,7 @@
import com.gitblit.manager.IProjectManager;
import com.gitblit.manager.IRepositoryManager;
import com.gitblit.manager.IRuntimeManager;
import com.gitblit.manager.ISessionManager;
import com.gitblit.manager.IAuthenticationManager;
import com.gitblit.manager.IUserManager;
import com.gitblit.utils.StringUtils;
import com.gitblit.wicket.pages.ActivityPage;
@@ -89,7 +89,7 @@
    private final IUserManager userManager;
    private final ISessionManager sessionManager;
    private final IAuthenticationManager authenticationManager;
    private final IRepositoryManager repositoryManager;
@@ -103,7 +103,7 @@
            IRuntimeManager runtimeManager,
            INotificationManager notificationManager,
            IUserManager userManager,
            ISessionManager sessionManager,
            IAuthenticationManager authenticationManager,
            IRepositoryManager repositoryManager,
            IProjectManager projectManager,
            IGitblitManager gitblitManager,
@@ -114,7 +114,7 @@
        this.runtimeManager = runtimeManager;
        this.notificationManager = notificationManager;
        this.userManager = userManager;
        this.sessionManager = sessionManager;
        this.authenticationManager = authenticationManager;
        this.repositoryManager = repositoryManager;
        this.projectManager = projectManager;
        this.gitblitManager = gitblitManager;
@@ -267,8 +267,8 @@
        return userManager;
    }
    public ISessionManager session() {
        return sessionManager;
    public IAuthenticationManager authentication() {
        return authenticationManager;
    }
    public IRepositoryManager repositories() {
src/main/java/com/gitblit/wicket/pages/ChangePasswordPage.java
@@ -51,7 +51,7 @@
        }
        UserModel user = GitBlitWebSession.get().getUser();
        if (!app().users().supportsCredentialChanges(user)) {
        if (!app().authentication().supportsCredentialChanges(user)) {
            error(MessageFormat.format(getString("gb.userServiceDoesNotPermitPasswordChanges"),
                    app().settings().getString(Keys.realm.userService, "${baseFolder}/users.conf")), true);
        }
@@ -100,7 +100,7 @@
                    app().gitblit().updateUserModel(user.username, user, false);
                    if (app().settings().getBoolean(Keys.web.allowCookieAuthentication, false)) {
                        WebResponse response = (WebResponse) getRequestCycle().getResponse();
                        app().session().setCookie(response.getHttpServletResponse(), user);
                        app().authentication().setCookie(response.getHttpServletResponse(), user);
                    }
                } catch (GitBlitException e) {
                    error(e.getMessage());
src/main/java/com/gitblit/wicket/pages/EditTeamPage.java
@@ -216,7 +216,7 @@
        form.add(new SimpleAttributeModifier("autocomplete", "off"));
        // not all user services support manipulating team memberships
        boolean editMemberships = app().users().supportsTeamMembershipChanges(null);
        boolean editMemberships = app().authentication().supportsTeamMembershipChanges(teamModel);
        // field names reflective match TeamModel fields
        form.add(new TextField<String>("name"));
src/main/java/com/gitblit/wicket/pages/EditUserPage.java
@@ -54,10 +54,6 @@
    public EditUserPage() {
        // create constructor
        super();
        if (!app().users().supportsAddUser()) {
            error(MessageFormat.format(getString("gb.userServiceDoesNotPermitAddUser"),
                    app().settings().getString(Keys.realm.userService, "${baseFolder}/users.conf")), true);
        }
        isCreate = true;
        setupPage(new UserModel(""));
        setStatelessHint(false);
@@ -138,7 +134,7 @@
                }
                boolean rename = !StringUtils.isEmpty(oldName)
                        && !oldName.equalsIgnoreCase(username);
                if (app().users().supportsCredentialChanges(userModel)) {
                if (app().authentication().supportsCredentialChanges(userModel)) {
                    if (!userModel.password.equals(confirmPassword.getObject())) {
                        error(getString("gb.passwordsDoNotMatch"));
                        return;
@@ -214,16 +210,16 @@
        form.add(new SimpleAttributeModifier("autocomplete", "off"));
        // not all user services support manipulating username and password
        boolean editCredentials = app().users().supportsCredentialChanges(userModel);
        boolean editCredentials = app().authentication().supportsCredentialChanges(userModel);
        // not all user services support manipulating display name
        boolean editDisplayName = app().users().supportsDisplayNameChanges(userModel);
        boolean editDisplayName = app().authentication().supportsDisplayNameChanges(userModel);
        // not all user services support manipulating email address
        boolean editEmailAddress = app().users().supportsEmailAddressChanges(userModel);
        boolean editEmailAddress = app().authentication().supportsEmailAddressChanges(userModel);
        // not all user services support manipulating team memberships
        boolean editTeams = app().users().supportsTeamMembershipChanges(userModel);
        boolean editTeams = app().authentication().supportsTeamMembershipChanges(userModel);
        // field names reflective match UserModel fields
        form.add(new TextField<String>("username").setEnabled(editCredentials));
src/main/java/com/gitblit/wicket/pages/LogoutPage.java
@@ -27,7 +27,7 @@
        super();
        GitBlitWebSession session = GitBlitWebSession.get();
        UserModel user = session.getUser();
        app().session().logout(((WebResponse) getResponse()).getHttpServletResponse(), user);
        app().authentication().logout(((WebResponse) getResponse()).getHttpServletResponse(), user);
        session.invalidate();
        /*
src/main/java/com/gitblit/wicket/pages/RootPage.java
@@ -252,7 +252,7 @@
            // Set Cookie
            if (app().settings().getBoolean(Keys.web.allowCookieAuthentication, false)) {
                WebResponse response = (WebResponse) getRequestCycle().getResponse();
                app().session().setCookie(response.getHttpServletResponse(), user);
                app().authentication().setCookie(response.getHttpServletResponse(), user);
            }
            if (!session.continueRequest()) {
@@ -536,7 +536,7 @@
                    String username = RootPage.this.username.getObject();
                    char[] password = RootPage.this.password.getObject().toCharArray();
                    UserModel user = app().session().authenticate(username, password);
                    UserModel user = app().authentication().authenticate(username, password);
                    if (user == null) {
                        error(getString("gb.invalidUsernameOrPassword"));
                    } else if (user.username.equals(Constants.FEDERATION_USER)) {
@@ -572,7 +572,7 @@
            GitBlitWebSession session = GitBlitWebSession.get();
            UserModel user = session.getUser();
            boolean editCredentials = app().users().supportsCredentialChanges(user);
            boolean editCredentials = app().authentication().supportsCredentialChanges(user);
            boolean standardLogin = session.authenticationType.isStandard();
            if (app().settings().getBoolean(Keys.web.allowGravatar, true)) {
src/main/java/com/gitblit/wicket/pages/SessionPage.java
@@ -60,7 +60,7 @@
        // try to authenticate by servlet request
        HttpServletRequest httpRequest = ((WebRequest) getRequestCycle().getRequest())
                .getHttpServletRequest();
        UserModel user = app().session().authenticate(httpRequest);
        UserModel user = app().authentication().authenticate(httpRequest);
        // Login the user
        if (user != null) {
@@ -70,7 +70,7 @@
            // Set Cookie
            WebResponse response = (WebResponse) getRequestCycle().getResponse();
            app().session().setCookie(response.getHttpServletResponse(), user);
            app().authentication().setCookie(response.getHttpServletResponse(), user);
            session.continueRequest();
        }
src/main/java/com/gitblit/wicket/panels/TeamsPanel.java
@@ -39,7 +39,7 @@
        Fragment adminLinks = new Fragment("adminPanel", "adminLinks", this);
        adminLinks.add(new BookmarkablePageLink<Void>("newTeam", EditTeamPage.class));
        add(adminLinks.setVisible(showAdmin && app().users().supportsTeamMembershipChanges(null)));
        add(adminLinks.setVisible(showAdmin));
        final List<TeamModel> teams = app().users().getAllTeams();
        DataView<TeamModel> teamsView = new DataView<TeamModel>("teamRow",
src/main/java/com/gitblit/wicket/panels/UsersPanel.java
@@ -39,8 +39,7 @@
        super(wicketId);
        Fragment adminLinks = new Fragment("adminPanel", "adminLinks", this);
        adminLinks.add(new BookmarkablePageLink<Void>("newUser", EditUserPage.class)
                .setVisible(app().users().supportsAddUser()));
        adminLinks.add(new BookmarkablePageLink<Void>("newUser", EditUserPage.class));
        add(adminLinks.setVisible(showAdmin));
        final List<UserModel> users = app().users().getAllUsers();
src/site/setup_authentication.mkd
@@ -15,11 +15,11 @@
### LDAP Authentication
*SINCE 1.0.0*
LDAP can be used to authenticate Users and optionally control Team memberships.  When properly configured, Gitblit will delegate authentication to your LDAP server and will cache some user information in the usual users file (.conf or .properties).
LDAP can be used to authenticate Users and optionally control Team memberships.  When properly configured, Gitblit will delegate authentication to your LDAP server and will cache some user information in the usual users.conf file.
When using the LDAP User Service, new user accounts can not be manually created from Gitblit.  Gitblit user accounts are automatically created for new users on their first succesful authentication through Gitblit against the LDAP server.  It is also important to note that the LDAP User Service does not retrieve or store user passwords nor does it implement any LDAP-write functionality.
To use the *LdapUserService* set *realm.userService=com.gitblit.LdapUserService* in your `gitblit.properties` file or your `web.xml` file and then configure the *realm.ldap* settings appropriately for your LDAP environment.
To use the *LdapUserService* set *realm.authenticationProviders=ldap* in your `gitblit.properties` file and then configure the *realm.ldap* settings appropriately for your LDAP environment.
#### Example LDAP Layout
![block diagram](ldapSample.png "LDAP Sample")
@@ -45,10 +45,6 @@
<tr>
  <th>realm.ldap.password</th><td>password</td>
  <td>The credentials that will log into the LDAP server</td>
</tr>
<tr>
  <th>realm.ldap.backingUserService</th><td>users.conf</td>
  <td>Where to store all information that is used by Gitblit.  All information will be synced here upon user login.</td>
</tr>
<tr>
  <th>realm.ldap.maintainTeams</th><td>true</td>
@@ -82,35 +78,35 @@
Windows authentication is based on the use of Waffle and JNA.  It is known to work properly for authenticating against the local Windows machine, but it is unclear if it works properly with a domain controller and Active Directory.  To use this service, your Gitblit server must be installed on a Windows machine.
    realm.userService = com.gitblit.WindowsUserService
    realm.authenticationProviders = windows
    realm.windows.defaultDomain =
### PAM Authentication
PAM authentication is based on the use of libpam4j and JNA.  To use this service, your Gitblit server must be installed on a Linux/Unix/MacOSX machine and the user that Gitblit runs-as must have root permissions.
    realm.userService = com.gitblit.PAMUserService
    realm.authenticationProviders = pam
    realm.pam.serviceName = system-auth
### Htpasswd Authentication
Htpasswd authentication allows you to maintain your user credentials in an Apache htpasswd file thay may be shared with other htpasswd-capable servers.
    realm.userService = com.gitblit.HtpasswdUserService
    realm.authenticationProviders = htpasswd
    realm.htpasswd.userFile = /path/to/htpasswd
### Redmine Authentication
You may authenticate your users against a Redmine installation as long as your Redmine install has properly enabled [API authentication](http://www.redmine.org/projects/redmine/wiki/Rest_Api#Authentication).  This user service only supports user authentication; it does not support team creation based on Redmine groups.  Redmine administrators will also be Gitblit administrators.
    realm.userService = com.gitblit.RedmineUserService
    realm.authenticationProviders = redmine
    realm.redmine.url = http://example.com/redmine
### Salesforce.com Authentication
You may authenticate your users against Salesforce.com.  You can require that user's belong to a particular organization by specifying a non-zero organization id.
    realm.userService = com.gitblit.SalesforceUserService
    realm.authenticationProviders = salesforce
    realm.salesforce.orgId = 0
### Container Authentication
@@ -123,10 +119,9 @@
This is the simplest choice where you implement custom authentication and delegate all other standard user and team operations to one of Gitblit's user service implementations.  This choice insulates your customization from changes in User and Team model classes and additional API that may be added to IUserService.
Please subclass [com.gitblit.GitblitUserService](https://github.com/gitblit/gitblit/blob/master/src/main/java/com/gitblit/GitblitUserService.java) and override the *setup()* and *authenticate()* methods.
Make sure to set the *serviceImpl* field in your *setup()* method.
Please subclass [com.gitblit.auth.AuthenticationProvider](https://github.com/gitblit/gitblit/blob/master/src/main/java/com/gitblit/auth/AuthenticationProvider.java).
You may use your subclass by specifying its fully qualified classname in the *realm.userService* setting.
You may use your subclass by specifying its fully qualified classname in the *realm.authenticationProviders* setting.
Your subclass must be on Gitblit's classpath and must have a public default constructor.  
src/test/config/test-users.conf
@@ -1,12 +1,10 @@
[user "admin"]
    password = admin
    cookie = dd94709528bb1c83d08f3088d4043f4742891f4f
    accountType = LOCAL
    role = "#admin"
    role = "#notfederated"
[user "sampleuser"]
    password = sampleuser
    cookie = 6e07ed42149fc166206319faffdfba2e2ec82e43
    role = "#none"
[team "admins"]
    role = "#none"
    accountType = LOCAL
    user = admin
src/test/java/com/gitblit/tests/GitBlitSuite.java
@@ -56,12 +56,12 @@
@RunWith(Suite.class)
@SuiteClasses({ ArrayUtilsTest.class, FileUtilsTest.class, TimeUtilsTest.class,
        StringUtilsTest.class, Base64Test.class, JsonUtilsTest.class, ByteFormatTest.class,
        ObjectCacheTest.class, PermissionsTest.class, UserServiceTest.class, LdapUserServiceTest.class,
        ObjectCacheTest.class, PermissionsTest.class, UserServiceTest.class, LdapAuthenticationTest.class,
        MarkdownUtilsTest.class, JGitUtilsTest.class, SyndicationUtilsTest.class,
        DiffUtilsTest.class, MetricUtilsTest.class, X509UtilsTest.class,
        GitBlitTest.class, FederationTests.class, RpcTests.class, GitServletTest.class, GitDaemonTest.class,
        GroovyScriptTest.class, LuceneExecutorTest.class, RepositoryModelTest.class,
        FanoutServiceTest.class, Issue0259Test.class, Issue0271Test.class, HtpasswdUserServiceTest.class,
        FanoutServiceTest.class, Issue0259Test.class, Issue0271Test.class, HtpasswdAuthenticationTest.class,
        ModelUtilsTest.class, JnaUtilsTest.class })
public class GitBlitSuite {
src/test/java/com/gitblit/tests/GitBlitTest.java
@@ -172,7 +172,7 @@
    @Test
    public void testAuthentication() throws Exception {
        assertTrue(session().authenticate("admin", "admin".toCharArray()) != null);
        assertTrue(authentication().authenticate("admin", "admin".toCharArray()) != null);
    }
    @Test
src/test/java/com/gitblit/tests/GitblitUnitTest.java
@@ -22,7 +22,7 @@
import com.gitblit.manager.IProjectManager;
import com.gitblit.manager.IRepositoryManager;
import com.gitblit.manager.IRuntimeManager;
import com.gitblit.manager.ISessionManager;
import com.gitblit.manager.IAuthenticationManager;
import com.gitblit.manager.IUserManager;
import com.gitblit.servlet.GitblitContext;
@@ -45,8 +45,8 @@
        return GitblitContext.getManager(IUserManager.class);
    }
    public static ISessionManager session() {
        return GitblitContext.getManager(ISessionManager.class);
    public static IAuthenticationManager authentication() {
        return GitblitContext.getManager(IAuthenticationManager.class);
    }
    public static IRepositoryManager repositories() {
src/test/java/com/gitblit/tests/HtpasswdAuthenticationTest.java
New file
@@ -0,0 +1,365 @@
/*
 * 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.tests;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.HashMap;
import org.apache.commons.io.FileUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import com.gitblit.IStoredSettings;
import com.gitblit.auth.HtpasswdAuthProvider;
import com.gitblit.manager.RuntimeManager;
import com.gitblit.manager.UserManager;
import com.gitblit.models.UserModel;
import com.gitblit.tests.mock.MemorySettings;
/**
 * Test the Htpasswd user service.
 *
 */
public class HtpasswdAuthenticationTest extends GitblitUnitTest {
    private static final String RESOURCE_DIR = "src/test/resources/htpasswd/";
    private static final String KEY_SUPPORT_PLAINTEXT_PWD = "realm.htpasswd.supportPlaintextPasswords";
    private static final int NUM_USERS_HTPASSWD = 10;
    private static final MemorySettings MS = new MemorySettings(new HashMap<String, Object>());
    private HtpasswdAuthProvider htpasswd;
    private MemorySettings getSettings(String userfile, String groupfile, Boolean overrideLA)
    {
        MS.put("realm.userService", RESOURCE_DIR + "users.conf");
        MS.put("realm.htpasswd.userfile", (userfile == null) ? (RESOURCE_DIR + "htpasswd") : userfile);
        MS.put("realm.htpasswd.groupfile", (groupfile == null) ? (RESOURCE_DIR + "htgroup") : groupfile);
        MS.put("realm.htpasswd.overrideLocalAuthentication", (overrideLA == null) ? "false" : overrideLA.toString());
        // Default to keep test the same on all platforms.
        MS.put(KEY_SUPPORT_PLAINTEXT_PWD, "false");
        return MS;
    }
    private MemorySettings getSettings()
    {
        return getSettings(null, null, null);
    }
    private void setupUS()
    {
        htpasswd = newHtpasswdAuthentication(getSettings());
    }
    private HtpasswdAuthProvider newHtpasswdAuthentication(IStoredSettings settings) {
        RuntimeManager runtime = new RuntimeManager(settings, GitBlitSuite.BASEFOLDER).start();
        UserManager users = new UserManager(runtime).start();
        HtpasswdAuthProvider htpasswd = new HtpasswdAuthProvider();
        htpasswd.setup(runtime, users);
        return htpasswd;
    }
    private void copyInFiles() throws IOException
    {
        File dir = new File(RESOURCE_DIR);
        FilenameFilter filter = new FilenameFilter() {
            @Override
            public boolean accept(File dir, String file) {
                return file.endsWith(".in");
                }
            };
        for (File inf : dir.listFiles(filter)) {
            File dest = new File(inf.getParent(), inf.getName().substring(0, inf.getName().length() - 3));
            FileUtils.copyFile(inf, dest);
        }
    }
    private void deleteGeneratedFiles()
    {
        File dir = new File(RESOURCE_DIR);
        FilenameFilter filter = new FilenameFilter() {
            @Override
            public boolean accept(File dir, String file) {
                return !(file.endsWith(".in"));
                }
            };
        for (File file : dir.listFiles(filter)) {
            file.delete();
        }
    }
    @Before
    public void setup() throws IOException
    {
        copyInFiles();
        setupUS();
    }
    @After
    public void tearDown()
    {
        deleteGeneratedFiles();
    }
    @Test
    public void testSetup() throws IOException
    {
        assertEquals(NUM_USERS_HTPASSWD, htpasswd.getNumberHtpasswdUsers());
    }
    @Test
    public void testAuthenticate()
    {
        MS.put(KEY_SUPPORT_PLAINTEXT_PWD, "true");
        UserModel user = htpasswd.authenticate("user1", "pass1".toCharArray());
        assertNotNull(user);
        assertEquals("user1", user.username);
        user = htpasswd.authenticate("user2", "pass2".toCharArray());
        assertNotNull(user);
        assertEquals("user2", user.username);
        // Test different encryptions
        user = htpasswd.authenticate("plain", "passWord".toCharArray());
        assertNotNull(user);
        assertEquals("plain", user.username);
        MS.put(KEY_SUPPORT_PLAINTEXT_PWD, "false");
        user = htpasswd.authenticate("crypt", "password".toCharArray());
        assertNotNull(user);
        assertEquals("crypt", user.username);
        user = htpasswd.authenticate("md5", "password".toCharArray());
        assertNotNull(user);
        assertEquals("md5", user.username);
        user = htpasswd.authenticate("sha", "password".toCharArray());
        assertNotNull(user);
        assertEquals("sha", user.username);
        // Test leading and trailing whitespace
        user = htpasswd.authenticate("trailing", "whitespace".toCharArray());
        assertNotNull(user);
        assertEquals("trailing", user.username);
        user = htpasswd.authenticate("tabbed", "frontAndBack".toCharArray());
        assertNotNull(user);
        assertEquals("tabbed", user.username);
        user = htpasswd.authenticate("leading", "whitespace".toCharArray());
        assertNotNull(user);
        assertEquals("leading", user.username);
    }
    @Test
    public void testAttributes()
    {
        MS.put(KEY_SUPPORT_PLAINTEXT_PWD, "true");
        UserModel user = htpasswd.authenticate("user1", "pass1".toCharArray());
        assertNotNull(user);
        assertEquals("El Capitan", user.displayName);
        assertEquals("cheffe@example.com", user.emailAddress);
        assertTrue(user.canAdmin);
        user = htpasswd.authenticate("user2", "pass2".toCharArray());
        assertNotNull(user);
        assertEquals("User Two", user.displayName);
        assertTrue(user.canCreate);
        assertTrue(user.canFork);
    }
    @Test
    public void testAuthenticateDenied()
    {
        UserModel user = null;
        MS.put(KEY_SUPPORT_PLAINTEXT_PWD, "true");
        user = htpasswd.authenticate("user1", "".toCharArray());
        assertNull("User 'user1' falsely authenticated.", user);
        user = htpasswd.authenticate("user1", "pass2".toCharArray());
        assertNull("User 'user1' falsely authenticated.", user);
        user = htpasswd.authenticate("user2", "lalala".toCharArray());
        assertNull("User 'user2' falsely authenticated.", user);
        user = htpasswd.authenticate("user3", "disabled".toCharArray());
        assertNull("User 'user3' falsely authenticated.", user);
        user = htpasswd.authenticate("user4", "disabled".toCharArray());
        assertNull("User 'user4' falsely authenticated.", user);
        user = htpasswd.authenticate("plain", "text".toCharArray());
        assertNull("User 'plain' falsely authenticated.", user);
        user = htpasswd.authenticate("plain", "password".toCharArray());
        assertNull("User 'plain' falsely authenticated.", user);
        MS.put(KEY_SUPPORT_PLAINTEXT_PWD, "false");
        user = htpasswd.authenticate("crypt", "".toCharArray());
        assertNull("User 'cyrpt' falsely authenticated.", user);
        user = htpasswd.authenticate("crypt", "passwd".toCharArray());
        assertNull("User 'crypt' falsely authenticated.", user);
        user = htpasswd.authenticate("md5", "".toCharArray());
        assertNull("User 'md5' falsely authenticated.", user);
        user = htpasswd.authenticate("md5", "pwd".toCharArray());
        assertNull("User 'md5' falsely authenticated.", user);
        user = htpasswd.authenticate("sha", "".toCharArray());
        assertNull("User 'sha' falsely authenticated.", user);
        user = htpasswd.authenticate("sha", "letmein".toCharArray());
        assertNull("User 'sha' falsely authenticated.", user);
        user = htpasswd.authenticate("  tabbed", "frontAndBack".toCharArray());
        assertNull("User 'tabbed' falsely authenticated.", user);
        user = htpasswd.authenticate("    leading", "whitespace".toCharArray());
        assertNull("User 'leading' falsely authenticated.", user);
    }
    @Test
    public void testCleartextIntrusion()
    {
        MS.put(KEY_SUPPORT_PLAINTEXT_PWD, "true");
        assertNull(htpasswd.authenticate("md5", "$apr1$qAGGNfli$sAn14mn.WKId/3EQS7KSX0".toCharArray()));
        assertNull(htpasswd.authenticate("sha", "{SHA}W6ph5Mm5Pz8GgiULbPgzG37mj9g=".toCharArray()));
        assertNull(htpasswd.authenticate("user1", "#externalAccount".toCharArray()));
        MS.put(KEY_SUPPORT_PLAINTEXT_PWD, "false");
        assertNull(htpasswd.authenticate("md5", "$apr1$qAGGNfli$sAn14mn.WKId/3EQS7KSX0".toCharArray()));
        assertNull(htpasswd.authenticate("sha", "{SHA}W6ph5Mm5Pz8GgiULbPgzG37mj9g=".toCharArray()));
        assertNull(htpasswd.authenticate("user1", "#externalAccount".toCharArray()));
    }
    @Test
    public void testCryptVsPlaintext()
    {
        MS.put(KEY_SUPPORT_PLAINTEXT_PWD, "false");
        assertNull(htpasswd.authenticate("crypt", "6TmlbxqZ2kBIA".toCharArray()));
        assertNotNull(htpasswd.authenticate("crypt", "password".toCharArray()));
        MS.put(KEY_SUPPORT_PLAINTEXT_PWD, "true");
        assertNotNull(htpasswd.authenticate("crypt", "6TmlbxqZ2kBIA".toCharArray()));
        assertNull(htpasswd.authenticate("crypt", "password".toCharArray()));
    }
    @Test
    public void testChangeHtpasswdFile()
    {
        UserModel user;
        // User default set up.
        user = htpasswd.authenticate("md5", "password".toCharArray());
        assertNotNull(user);
        assertEquals("md5", user.username);
        user = htpasswd.authenticate("sha", "password".toCharArray());
        assertNotNull(user);
        assertEquals("sha", user.username);
        user = htpasswd.authenticate("blueone", "GoBlue!".toCharArray());
        assertNull(user);
        user = htpasswd.authenticate("bluetwo", "YayBlue!".toCharArray());
        assertNull(user);
        // Switch to different htpasswd file.
        getSettings(RESOURCE_DIR + "htpasswd-user", null, null);
        user = htpasswd.authenticate("md5", "password".toCharArray());
        assertNull(user);
        user = htpasswd.authenticate("sha", "password".toCharArray());
        assertNull(user);
        user = htpasswd.authenticate("blueone", "GoBlue!".toCharArray());
        assertNotNull(user);
        assertEquals("blueone", user.username);
        user = htpasswd.authenticate("bluetwo", "YayBlue!".toCharArray());
        assertNotNull(user);
        assertEquals("bluetwo", user.username);
    }
    @Test
    public void testChangeHtpasswdFileNotExisting()
    {
        UserModel user;
        // User default set up.
        user = htpasswd.authenticate("md5", "password".toCharArray());
        assertNotNull(user);
        assertEquals("md5", user.username);
        user = htpasswd.authenticate("sha", "password".toCharArray());
        assertNotNull(user);
        assertEquals("sha", user.username);
        user = htpasswd.authenticate("blueone", "GoBlue!".toCharArray());
        assertNull(user);
        user = htpasswd.authenticate("bluetwo", "YayBlue!".toCharArray());
        assertNull(user);
        // Switch to different htpasswd file that doesn't exist.
        // Currently we stop working with old users upon this change.
        getSettings(RESOURCE_DIR + "no-such-file", null, null);
        user = htpasswd.authenticate("md5", "password".toCharArray());
        assertNull(user);
        user = htpasswd.authenticate("sha", "password".toCharArray());
        assertNull(user);
        user = htpasswd.authenticate("blueone", "GoBlue!".toCharArray());
        assertNull(user);
        user = htpasswd.authenticate("bluetwo", "YayBlue!".toCharArray());
        assertNull(user);
    }
}
src/test/java/com/gitblit/tests/HtpasswdUserServiceTest.java
File was deleted
src/test/java/com/gitblit/tests/LdapAuthenticationTest.java
File was renamed from src/test/java/com/gitblit/tests/LdapUserServiceTest.java
@@ -16,6 +16,7 @@
 */
package com.gitblit.tests;
import java.io.FileInputStream;
import java.util.HashMap;
import java.util.Map;
@@ -23,11 +24,12 @@
import org.junit.BeforeClass;
import org.junit.Test;
import com.gitblit.LdapUserService;
import com.gitblit.IStoredSettings;
import com.gitblit.auth.LdapAuthProvider;
import com.gitblit.manager.RuntimeManager;
import com.gitblit.manager.UserManager;
import com.gitblit.models.UserModel;
import com.gitblit.tests.mock.MemorySettings;
import com.gitblit.tests.mock.MockRuntimeManager;
import com.gitblit.utils.StringUtils;
import com.unboundid.ldap.listener.InMemoryDirectoryServer;
import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig;
import com.unboundid.ldap.listener.InMemoryListenerConfig;
@@ -40,9 +42,11 @@
 * @author jcrygier
 *
 */
public class LdapUserServiceTest extends GitblitUnitTest {
public class LdapAuthenticationTest extends GitblitUnitTest {
    private LdapUserService ldapUserService;
    private static final String RESOURCE_DIR = "src/test/resources/ldap/";
    private LdapAuthProvider ldap;
    static int ldapPort = 1389;
@@ -54,18 +58,26 @@
        config.setSchema(null);
        InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config);
        ds.importFromLDIF(true, new LDIFReader(LdapUserServiceTest.class.getResourceAsStream("resources/ldapUserServiceSampleData.ldif")));
        ds.importFromLDIF(true, new LDIFReader(new FileInputStream(RESOURCE_DIR + "sampledata.ldif")));
        ds.startListening();
    }
    @Before
    public void createLdapUserService() {
        ldapUserService = new LdapUserService();
        ldapUserService.setup(new MockRuntimeManager(getSettings()));
    public void newLdapAuthentication() {
        ldap = newLdapAuthentication(getSettings());
    }
    public LdapAuthProvider newLdapAuthentication(IStoredSettings settings) {
        RuntimeManager runtime = new RuntimeManager(settings, GitBlitSuite.BASEFOLDER).start();
        UserManager users = new UserManager(runtime).start();
        LdapAuthProvider ldap = new LdapAuthProvider();
        ldap.setup(runtime, users);
        return ldap;
    }
    private MemorySettings getSettings() {
        Map<String, Object> backingMap = new HashMap<String, Object>();
        backingMap.put("realm.userService", RESOURCE_DIR + "users.conf");
        backingMap.put("realm.ldap.server", "ldap://localhost:" + ldapPort);
        backingMap.put("realm.ldap.domain", "");
        backingMap.put("realm.ldap.username", "cn=Directory Manager");
@@ -86,23 +98,23 @@
    @Test
    public void testAuthenticate() {
        UserModel userOneModel = ldapUserService.authenticate("UserOne", "userOnePassword".toCharArray());
        UserModel userOneModel = ldap.authenticate("UserOne", "userOnePassword".toCharArray());
        assertNotNull(userOneModel);
        assertNotNull(userOneModel.getTeam("git_admins"));
        assertNotNull(userOneModel.getTeam("git_users"));
        assertTrue(userOneModel.canAdmin);
        UserModel userOneModelFailedAuth = ldapUserService.authenticate("UserOne", "userTwoPassword".toCharArray());
        UserModel userOneModelFailedAuth = ldap.authenticate("UserOne", "userTwoPassword".toCharArray());
        assertNull(userOneModelFailedAuth);
        UserModel userTwoModel = ldapUserService.authenticate("UserTwo", "userTwoPassword".toCharArray());
        UserModel userTwoModel = ldap.authenticate("UserTwo", "userTwoPassword".toCharArray());
        assertNotNull(userTwoModel);
        assertNotNull(userTwoModel.getTeam("git_users"));
        assertNull(userTwoModel.getTeam("git_admins"));
        assertNotNull(userTwoModel.getTeam("git admins"));
        assertTrue(userTwoModel.canAdmin);
        UserModel userThreeModel = ldapUserService.authenticate("UserThree", "userThreePassword".toCharArray());
        UserModel userThreeModel = ldap.authenticate("UserThree", "userThreePassword".toCharArray());
        assertNotNull(userThreeModel);
        assertNotNull(userThreeModel.getTeam("git_users"));
        assertNull(userThreeModel.getTeam("git_admins"));
@@ -111,34 +123,32 @@
    @Test
    public void testDisplayName() {
        UserModel userOneModel = ldapUserService.authenticate("UserOne", "userOnePassword".toCharArray());
        UserModel userOneModel = ldap.authenticate("UserOne", "userOnePassword".toCharArray());
        assertNotNull(userOneModel);
        assertEquals("User One", userOneModel.displayName);
        // Test more complicated scenarios - concat
        MemorySettings ms = getSettings();
        ms.put("realm.ldap.displayName", "${personalTitle}. ${givenName} ${surname}");
        ldapUserService = new LdapUserService();
        ldapUserService.setup(new MockRuntimeManager(ms));
        ldap = newLdapAuthentication(ms);
        userOneModel = ldapUserService.authenticate("UserOne", "userOnePassword".toCharArray());
        userOneModel = ldap.authenticate("UserOne", "userOnePassword".toCharArray());
        assertNotNull(userOneModel);
        assertEquals("Mr. User One", userOneModel.displayName);
    }
    @Test
    public void testEmail() {
        UserModel userOneModel = ldapUserService.authenticate("UserOne", "userOnePassword".toCharArray());
        UserModel userOneModel = ldap.authenticate("UserOne", "userOnePassword".toCharArray());
        assertNotNull(userOneModel);
        assertEquals("userone@gitblit.com", userOneModel.emailAddress);
        // Test more complicated scenarios - concat
        MemorySettings ms = getSettings();
        ms.put("realm.ldap.email", "${givenName}.${surname}@gitblit.com");
        ldapUserService = new LdapUserService();
        ldapUserService.setup(new MockRuntimeManager(ms));
        ldap = newLdapAuthentication(ms);
        userOneModel = ldapUserService.authenticate("UserOne", "userOnePassword".toCharArray());
        userOneModel = ldap.authenticate("UserOne", "userOnePassword".toCharArray());
        assertNotNull(userOneModel);
        assertEquals("User.One@gitblit.com", userOneModel.emailAddress);
    }
@@ -148,23 +158,8 @@
        // Inject so "(&(objectClass=person)(sAMAccountName=${username}))" becomes "(&(objectClass=person)(sAMAccountName=*)(userPassword=userOnePassword))"
        // Thus searching by password
        UserModel userOneModel = ldapUserService.authenticate("*)(userPassword=userOnePassword", "userOnePassword".toCharArray());
        UserModel userOneModel = ldap.authenticate("*)(userPassword=userOnePassword", "userOnePassword".toCharArray());
        assertNull(userOneModel);
    }
    @Test
    public void testLocalAccount() {
        UserModel localAccount = new UserModel("bruce");
        localAccount.displayName = "Bruce Campbell";
        localAccount.password = StringUtils.MD5_TYPE + StringUtils.getMD5("gimmesomesugar");
        ldapUserService.deleteUser(localAccount.username);
        assertTrue("Failed to add local account",
                ldapUserService.updateUserModel(localAccount));
        assertEquals("Accounts are not equal!",
                localAccount,
                ldapUserService.authenticate(localAccount.username, "gimmesomesugar".toCharArray()));
        assertTrue("Failed to delete local account!",
                ldapUserService.deleteUser(localAccount.username));
    }
}
src/test/java/com/gitblit/tests/RedmineAuthenticationTest.java
New file
@@ -0,0 +1,65 @@
package com.gitblit.tests;
import static org.hamcrest.CoreMatchers.is;
import java.util.HashMap;
import org.junit.Test;
import com.gitblit.IStoredSettings;
import com.gitblit.auth.RedmineAuthProvider;
import com.gitblit.manager.RuntimeManager;
import com.gitblit.manager.UserManager;
import com.gitblit.models.UserModel;
import com.gitblit.tests.mock.MemorySettings;
public class RedmineAuthenticationTest extends GitblitUnitTest {
    private static final String JSON = "{\"user\":{\"created_on\":\"2011-03-28T00:41:29Z\",\"lastname\":\"foo\","
        + "\"last_login_on\":\"2012-09-06T23:59:26Z\",\"firstname\":\"baz\","
        + "\"id\":4,\"login\":\"RedmineUserId\",\"mail\":\"baz@example.com\"}}";
    private static final String NOT_ADMIN_JSON = "{\"user\":{\"lastname\":\"foo\","
        + "\"last_login_on\":\"2012-09-08T13:59:01Z\",\"created_on\":\"2009-03-17T14:25:50Z\","
        + "\"mail\":\"baz@example.com\",\"id\":5,\"firstname\":\"baz\"}}";
    MemorySettings getSettings() {
        return new MemorySettings(new HashMap<String, Object>());
    }
    RedmineAuthProvider newRedmineAuthentication(IStoredSettings settings) {
        RuntimeManager runtime = new RuntimeManager(settings, GitBlitSuite.BASEFOLDER).start();
        UserManager users = new UserManager(runtime).start();
        RedmineAuthProvider redmine = new RedmineAuthProvider();
        redmine.setup(runtime, users);
        return redmine;
    }
    RedmineAuthProvider newRedmineAuthentication() {
        return newRedmineAuthentication(getSettings());
    }
    @Test
    public void testAuthenticate() throws Exception {
        RedmineAuthProvider redmine = newRedmineAuthentication();
        redmine.setTestingCurrentUserAsJson(JSON);
        UserModel userModel = redmine.authenticate("RedmineAdminId", "RedmineAPIKey".toCharArray());
        assertThat(userModel.getName(), is("redmineadminid"));
        assertThat(userModel.getDisplayName(), is("baz foo"));
        assertThat(userModel.emailAddress, is("baz@example.com"));
        assertNotNull(userModel.cookie);
        assertThat(userModel.canAdmin, is(true));
    }
    @Test
    public void testAuthenticateNotAdminUser() throws Exception {
        RedmineAuthProvider redmine = newRedmineAuthentication();
        redmine.setTestingCurrentUserAsJson(NOT_ADMIN_JSON);
        UserModel userModel = redmine.authenticate("RedmineUserId", "RedmineAPIKey".toCharArray());
        assertThat(userModel.getName(), is("redmineuserid"));
        assertThat(userModel.getDisplayName(), is("baz foo"));
        assertThat(userModel.emailAddress, is("baz@example.com"));
        assertNotNull(userModel.cookie);
        assertThat(userModel.canAdmin, is(false));
    }
}
src/test/java/com/gitblit/tests/RedmineUserServiceTest.java
File was deleted
src/test/java/com/gitblit/tests/UserServiceTest.java
@@ -85,14 +85,9 @@
        assertTrue(newUser.hasRepositoryPermission("repo2"));
        assertTrue(newUser.hasRepositoryPermission("sub/repo3"));
        // confirm authentication of test user
        UserModel testUser = service.authenticate("test", "testPassword".toCharArray());
        assertEquals("test", testUser.username);
        assertEquals("testPassword", testUser.password);
        // delete a repository role and confirm role removal from test user
        service.deleteRepositoryRole("repo2");
        testUser = service.getUserModel("test");
        UserModel testUser = service.getUserModel("test");
        assertEquals(2, testUser.permissions.size());
        // delete garbage user and confirm user count
src/test/resources/htpasswd/htpasswd-user.in
src/test/resources/htpasswd/htpasswd.in
src/test/resources/htpasswd/users.conf.in
src/test/resources/ldap/sampledata.ldif
src/test/resources/ldap/users.conf
New file
@@ -0,0 +1,53 @@
[user "admin"]
    password = admin
    cookie = dd94709528bb1c83d08f3088d4043f4742891f4f
    accountType = LOCAL
    role = "#admin"
    role = "#notfederated"
[user "userthree"]
    password = "#externalAccount"
    cookie = d7d3894fc517612aa6c595555b6e1ab8e147e597
    displayName = User Three
    emailAddress = userthree@gitblit.com
    accountType = LDAP
    role = "#admin"
[user "userone"]
    password = "#externalAccount"
    cookie = c97cd38e50858cd0b389ec61b18fb9a89b4da54c
    displayName = Mr. User One
    emailAddress = userone@gitblit.com
    accountType = LDAP
    role = "#admin"
[user "usertwo"]
    password = "#externalAccount"
    cookie = 498ca9bd2841d39050fa45d1d737b9f9f767858d
    displayName = User Two
    emailAddress = usertwo@gitblit.com
    accountType = LDAP
    role = "#admin"
[user "basic"]
    password = MD5:f17aaabc20bfe045075927934fed52d2
    cookie = dd94709528bb1c83d08f3088d4043f4742891f4f
    accountType = LOCAL
    role = "#fork"
    repository = RW:~repocreator/shb.git
    repository = V:test/gitective.git
[user "repocreator"]
    password = MD5:b77e53bb561c47368d133b22e285f60b
    cookie = dd94709528bb1c83d08f3088d4043f4742891f4f
    accountType = LOCAL
    role = "#create"
[team "Git_Admins"]
    role = "#none"
    accountType = LOCAL
    user = userone
[team "Git_Users"]
    role = "#none"
    accountType = LOCAL
    user = userone
    user = usertwo
    user = userthree
[team "Git Admins"]
    role = "#none"
    accountType = LOCAL
    user = usertwo