From 04a98505a4ab8f48aee22800fcac193d9367d0ae Mon Sep 17 00:00:00 2001 From: James Moger Date: Sun, 24 Nov 2013 23:18:50 -0500 Subject: Refactor user services and separate authentication (issue-281) Change-Id: I336e005e02623fc5e11a4f8b4408bea5465a43fd --- releases.moxie | 4 +- src/main/distrib/data/gitblit.properties | 98 +--- src/main/java/com/gitblit/ConfigUserService.java | 202 +------- src/main/java/com/gitblit/Constants.java | 11 +- src/main/java/com/gitblit/DaggerModule.java | 18 +- src/main/java/com/gitblit/GitBlit.java | 55 +- src/main/java/com/gitblit/GitblitUserService.java | 325 ------------ src/main/java/com/gitblit/HtpasswdUserService.java | 361 ------------- src/main/java/com/gitblit/IUserService.java | 93 +--- src/main/java/com/gitblit/LdapUserService.java | 530 ------------------- src/main/java/com/gitblit/PAMUserService.java | 143 ------ src/main/java/com/gitblit/RedmineUserService.java | 203 -------- .../java/com/gitblit/SalesforceUserService.java | 139 ----- src/main/java/com/gitblit/WindowsUserService.java | 195 ------- .../com/gitblit/auth/AuthenticationProvider.java | 182 +++++++ .../com/gitblit/auth/HtpasswdAuthProvider.java | 276 ++++++++++ .../java/com/gitblit/auth/LdapAuthProvider.java | 508 ++++++++++++++++++ .../java/com/gitblit/auth/PAMAuthProvider.java | 126 +++++ .../java/com/gitblit/auth/RedmineAuthProvider.java | 186 +++++++ .../com/gitblit/auth/SalesforceAuthProvider.java | 128 +++++ .../java/com/gitblit/auth/WindowsAuthProvider.java | 177 +++++++ .../java/com/gitblit/client/EditTeamDialog.java | 1 - .../java/com/gitblit/client/EditUserDialog.java | 16 +- .../com/gitblit/git/GitblitUploadPackFactory.java | 10 +- .../com/gitblit/manager/AuthenticationManager.java | 511 ++++++++++++++++++ .../java/com/gitblit/manager/GitblitManager.java | 7 - .../gitblit/manager/IAuthenticationManager.java | 113 ++++ .../java/com/gitblit/manager/ISessionManager.java | 72 --- .../java/com/gitblit/manager/IUserManager.java | 261 +--------- .../java/com/gitblit/manager/SessionManager.java | 340 ------------ src/main/java/com/gitblit/manager/UserManager.java | 221 ++------ .../java/com/gitblit/models/ServerSettings.java | 8 - src/main/java/com/gitblit/models/TeamModel.java | 7 + .../gitblit/servlet/AccessRestrictionFilter.java | 6 +- .../com/gitblit/servlet/AuthenticationFilter.java | 10 +- .../com/gitblit/servlet/DownloadZipFilter.java | 6 +- .../servlet/EnforceAuthenticationFilter.java | 10 +- src/main/java/com/gitblit/servlet/GitFilter.java | 6 +- .../java/com/gitblit/servlet/GitblitContext.java | 4 +- src/main/java/com/gitblit/servlet/PagesFilter.java | 6 +- src/main/java/com/gitblit/servlet/RpcFilter.java | 6 +- .../gitblit/servlet/SparkleShareInviteServlet.java | 10 +- .../com/gitblit/servlet/SyndicationFilter.java | 6 +- .../java/com/gitblit/wicket/GitBlitWebApp.java | 12 +- .../gitblit/wicket/pages/ChangePasswordPage.java | 4 +- .../com/gitblit/wicket/pages/EditTeamPage.java | 2 +- .../com/gitblit/wicket/pages/EditUserPage.java | 14 +- .../java/com/gitblit/wicket/pages/LogoutPage.java | 2 +- .../java/com/gitblit/wicket/pages/RootPage.java | 6 +- .../java/com/gitblit/wicket/pages/SessionPage.java | 4 +- .../java/com/gitblit/wicket/panels/TeamsPanel.java | 2 +- .../java/com/gitblit/wicket/panels/UsersPanel.java | 3 +- src/site/setup_authentication.mkd | 23 +- src/test/config/test-users.conf | 6 +- src/test/java/com/gitblit/tests/GitBlitSuite.java | 4 +- src/test/java/com/gitblit/tests/GitBlitTest.java | 2 +- .../java/com/gitblit/tests/GitblitUnitTest.java | 6 +- .../gitblit/tests/HtpasswdAuthenticationTest.java | 365 +++++++++++++ .../com/gitblit/tests/HtpasswdUserServiceTest.java | 569 --------------------- .../com/gitblit/tests/LdapAuthenticationTest.java | 165 ++++++ .../com/gitblit/tests/LdapUserServiceTest.java | 170 ------ .../gitblit/tests/RedmineAuthenticationTest.java | 65 +++ .../com/gitblit/tests/RedmineUserServiceTest.java | 66 --- .../java/com/gitblit/tests/UserServiceTest.java | 7 +- .../tests/resources/ldapUserServiceSampleData.ldif | 108 ---- src/test/resources/htpasswd/htpasswd-user.in | 15 + src/test/resources/htpasswd/htpasswd.in | 31 ++ src/test/resources/htpasswd/users.conf.in | 26 + src/test/resources/htpasswdUSTest/htpasswd-user.in | 15 - src/test/resources/htpasswdUSTest/htpasswd.in | 31 -- src/test/resources/htpasswdUSTest/users.conf.in | 26 - src/test/resources/ldap/sampledata.ldif | 108 ++++ src/test/resources/ldap/users.conf | 53 ++ 73 files changed, 3271 insertions(+), 4236 deletions(-) delete mode 100644 src/main/java/com/gitblit/GitblitUserService.java delete mode 100644 src/main/java/com/gitblit/HtpasswdUserService.java delete mode 100644 src/main/java/com/gitblit/LdapUserService.java delete mode 100644 src/main/java/com/gitblit/PAMUserService.java delete mode 100644 src/main/java/com/gitblit/RedmineUserService.java delete mode 100644 src/main/java/com/gitblit/SalesforceUserService.java delete mode 100644 src/main/java/com/gitblit/WindowsUserService.java create mode 100644 src/main/java/com/gitblit/auth/AuthenticationProvider.java create mode 100644 src/main/java/com/gitblit/auth/HtpasswdAuthProvider.java create mode 100644 src/main/java/com/gitblit/auth/LdapAuthProvider.java create mode 100644 src/main/java/com/gitblit/auth/PAMAuthProvider.java create mode 100644 src/main/java/com/gitblit/auth/RedmineAuthProvider.java create mode 100644 src/main/java/com/gitblit/auth/SalesforceAuthProvider.java create mode 100644 src/main/java/com/gitblit/auth/WindowsAuthProvider.java create mode 100644 src/main/java/com/gitblit/manager/AuthenticationManager.java create mode 100644 src/main/java/com/gitblit/manager/IAuthenticationManager.java delete mode 100644 src/main/java/com/gitblit/manager/ISessionManager.java delete mode 100644 src/main/java/com/gitblit/manager/SessionManager.java create mode 100644 src/test/java/com/gitblit/tests/HtpasswdAuthenticationTest.java delete mode 100644 src/test/java/com/gitblit/tests/HtpasswdUserServiceTest.java create mode 100644 src/test/java/com/gitblit/tests/LdapAuthenticationTest.java delete mode 100644 src/test/java/com/gitblit/tests/LdapUserServiceTest.java create mode 100644 src/test/java/com/gitblit/tests/RedmineAuthenticationTest.java delete mode 100644 src/test/java/com/gitblit/tests/RedmineUserServiceTest.java delete mode 100644 src/test/java/com/gitblit/tests/resources/ldapUserServiceSampleData.ldif create mode 100644 src/test/resources/htpasswd/htpasswd-user.in create mode 100644 src/test/resources/htpasswd/htpasswd.in create mode 100644 src/test/resources/htpasswd/users.conf.in delete mode 100644 src/test/resources/htpasswdUSTest/htpasswd-user.in delete mode 100644 src/test/resources/htpasswdUSTest/htpasswd.in delete mode 100644 src/test/resources/htpasswdUSTest/users.conf.in create mode 100644 src/test/resources/ldap/sampledata.ldif create mode 100644 src/test/resources/ldap/users.conf diff --git a/releases.moxie b/releases.moxie index cc764fce..dd06c823 100644 --- a/releases.moxie +++ b/releases.moxie @@ -20,6 +20,7 @@ r20: { 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 @@ r20: { - { 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 diff --git a/src/main/distrib/data/gitblit.properties b/src/main/distrib/data/gitblit.properties index 92427e51..edfa1c4c 100644 --- a/src/main/distrib/data/gitblit.properties +++ b/src/main/distrib/data/gitblit.properties @@ -562,17 +562,8 @@ web.allowCookieAuthentication = true 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. # # SINCE 0.5.0 @@ -580,6 +571,25 @@ web.projectsFile = ${baseFolder}/projects.conf # 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. # combined-md5 is the hash of username.toLowerCase()+password. @@ -1331,15 +1341,6 @@ federation.sets = # 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,30 +1358,12 @@ realm.windows.allowGuests = false # 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 # @@ -1389,30 +1372,6 @@ realm.htpasswd.backingUserService = ${baseFolder}/users.conf # 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) # @@ -1439,15 +1398,6 @@ realm.ldap.username = cn=Directory Manager # 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. # # If true, team user memberships will be specified by LDAP groups. This will @@ -1565,14 +1515,6 @@ realm.ldap.synchronizeUsers.removeDeleted = true # 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 @@ server.ajpPort = 0 # # 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. diff --git a/src/main/java/com/gitblit/ConfigUserService.java b/src/main/java/com/gitblit/ConfigUserService.java index 39374e88..aae7c14c 100644 --- a/src/main/java/com/gitblit/ConfigUserService.java +++ b/src/main/java/com/gitblit/ConfigUserService.java @@ -96,6 +96,8 @@ public class ConfigUserService implements IUserService { private static final String LOCALE = "locale"; + private static final String ACCOUNTTYPE = "accountType"; + private final File realmFile; private final Logger logger = LoggerFactory.getLogger(ConfigUserService.class); @@ -124,60 +126,6 @@ public class ConfigUserService implements IUserService { public void setup(IRuntimeManager runtimeManager) { } - /** - * 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. * @@ -197,13 +145,13 @@ public class ConfigUserService implements IUserService { } /** - * 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; @@ -222,49 +170,6 @@ public class ConfigUserService implements IUserService { 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) { - } - /** * Retrieve the user object for the specified username. * @@ -357,6 +262,10 @@ public class ConfigUserService implements IUserService { 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); @@ -504,45 +413,6 @@ public class ConfigUserService implements IUserService { 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 teamnames) { - try { - Set specifiedTeams = new HashSet(); - for (String teamname : teamnames) { - specifiedTeams.add(teamname.toLowerCase()); - } - - read(); - - // identify teams which require add or remove role - for (TeamModel team : teams.values()) { - // team has role, check against revised team list - if (specifiedTeams.contains(team.name.toLowerCase())) { - team.addRepositoryPermission(role); - } else { - // remove role from team - team.removeRepositoryPermission(role); - } - } - - // persist changes - write(); - return true; - } catch (Throwable t) { - logger.error(MessageFormat.format("Failed to set teams for role {0}!", role), t); - } - return false; - } - /** * Retrieve the team object for the specified team name. * @@ -715,46 +585,6 @@ public class ConfigUserService implements IUserService { return list; } - /** - * Sets the list of all uses who are allowed to bypass the access - * restriction placed on the specified repository. - * - * @param role - * the repository name - * @param usernames - * @return true if successful - */ - @Override - @Deprecated - public synchronized boolean setUsernamesForRepositoryRole(String role, List usernames) { - try { - Set specifiedUsers = new HashSet(); - 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. * @@ -846,6 +676,9 @@ public class ConfigUserService implements IUserService { 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 @@ public class ConfigUserService implements IUserService { 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 @@ public class ConfigUserService implements IUserService { 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 @@ public class ConfigUserService implements IUserService { 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 @@ -1112,9 +953,4 @@ public class ConfigUserService implements IUserService { public String toString() { return getClass().getSimpleName() + "(" + realmFile.getAbsolutePath() + ")"; } - - @Override - public AccountType getAccountType() { - return AccountType.LOCAL; - } } diff --git a/src/main/java/com/gitblit/Constants.java b/src/main/java/com/gitblit/Constants.java index 43c60a39..439a944d 100644 --- a/src/main/java/com/gitblit/Constants.java +++ b/src/main/java/com/gitblit/Constants.java @@ -499,7 +499,16 @@ public class Constants { } 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; diff --git a/src/main/java/com/gitblit/DaggerModule.java b/src/main/java/com/gitblit/DaggerModule.java index 5e49a97d..cc836940 100644 --- a/src/main/java/com/gitblit/DaggerModule.java +++ b/src/main/java/com/gitblit/DaggerModule.java @@ -20,8 +20,10 @@ import javax.inject.Singleton; 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.IProjectManager; 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 @@ import dagger.Provides; IRuntimeManager.class, INotificationManager.class, IUserManager.class, - ISessionManager.class, + IAuthenticationManager.class, IRepositoryManager.class, IProjectManager.class, IGitblitManager.class, @@ -122,11 +122,11 @@ public class DaggerModule { 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 @@ public class DaggerModule { IRuntimeManager runtimeManager, INotificationManager notificationManager, IUserManager userManager, - ISessionManager sessionManager, + IAuthenticationManager authenticationManager, IRepositoryManager repositoryManager, IProjectManager projectManager, IGitblitManager gitblitManager, @@ -189,7 +189,7 @@ public class DaggerModule { runtimeManager, notificationManager, userManager, - sessionManager, + authenticationManager, repositoryManager, projectManager, gitblitManager, @@ -204,7 +204,7 @@ public class DaggerModule { IRuntimeManager runtimeManager, INotificationManager notificationManager, IUserManager userManager, - ISessionManager sessionManager, + IAuthenticationManager authenticationManager, IRepositoryManager repositoryManager, IProjectManager projectManager, IGitblitManager gitblitManager, @@ -214,7 +214,7 @@ public class DaggerModule { runtimeManager, notificationManager, userManager, - sessionManager, + authenticationManager, repositoryManager, projectManager, gitblitManager, diff --git a/src/main/java/com/gitblit/GitBlit.java b/src/main/java/com/gitblit/GitBlit.java index 0dcc765b..d4e89b02 100644 --- a/src/main/java/com/gitblit/GitBlit.java +++ b/src/main/java/com/gitblit/GitBlit.java @@ -29,13 +29,13 @@ import org.eclipse.jgit.lib.Repository; 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 @@ import com.gitblit.models.UserModel; public class GitBlit implements IRuntimeManager, INotificationManager, IUserManager, - ISessionManager, + IAuthenticationManager, IRepositoryManager, IProjectManager, IGitblitManager, @@ -77,7 +77,7 @@ public class GitBlit implements IRuntimeManager, private final IUserManager userManager; - private final ISessionManager sessionManager; + private final IAuthenticationManager authenticationManager; private final IRepositoryManager repositoryManager; @@ -91,7 +91,7 @@ public class GitBlit implements IRuntimeManager, IRuntimeManager runtimeManager, INotificationManager notificationManager, IUserManager userManager, - ISessionManager sessionManager, + IAuthenticationManager authenticationManager, IRepositoryManager repositoryManager, IProjectManager projectManager, IGitblitManager gitblitManager, @@ -100,7 +100,7 @@ public class GitBlit implements IRuntimeManager, 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,55 +239,59 @@ public class GitBlit implements IRuntimeManager, @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); - } - - /* - * USER MANAGER - */ - - @Override - public boolean supportsAddUser() { - return userManager.supportsAddUser(); + authenticationManager.logout(response, user); } @Override public boolean supportsCredentialChanges(UserModel user) { - return userManager.supportsCredentialChanges(user); + return authenticationManager.supportsCredentialChanges(user); } @Override public boolean supportsDisplayNameChanges(UserModel user) { - return userManager.supportsDisplayNameChanges(user); + return authenticationManager.supportsDisplayNameChanges(user); } @Override public boolean supportsEmailAddressChanges(UserModel user) { - return userManager.supportsEmailAddressChanges(user); + return authenticationManager.supportsEmailAddressChanges(user); } @Override public boolean supportsTeamMembershipChanges(UserModel user) { - return userManager.supportsTeamMembershipChanges(user); + return authenticationManager.supportsTeamMembershipChanges(user); + } + + @Override + public boolean supportsTeamMembershipChanges(TeamModel team) { + return authenticationManager.supportsTeamMembershipChanges(team); + } + + /* + * USER MANAGER + */ + + @Override + public void setup(IRuntimeManager runtimeManager) { } @Override @@ -320,11 +324,6 @@ public class GitBlit implements IRuntimeManager, return userManager.getTeamModel(teamname); } - @Override - public boolean supportsCookies() { - return userManager.supportsCookies(); - } - @Override public String getCookie(UserModel model) { return userManager.getCookie(model); diff --git a/src/main/java/com/gitblit/GitblitUserService.java b/src/main/java/com/gitblit/GitblitUserService.java deleted file mode 100644 index 715aed9d..00000000 --- a/src/main/java/com/gitblit/GitblitUserService.java +++ /dev/null @@ -1,325 +0,0 @@ -/* - * Copyright 2011 gitblit.com. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.gitblit; - -import java.io.File; -import java.io.IOException; -import java.text.MessageFormat; -import java.util.Collection; -import java.util.List; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.gitblit.Constants.AccountType; -import com.gitblit.manager.IRuntimeManager; -import com.gitblit.models.TeamModel; -import com.gitblit.models.UserModel; -import com.gitblit.utils.DeepCopier; -import com.gitblit.utils.StringUtils; - -/** - * This class wraps the default user service and is recommended as the starting - * point for custom user service implementations. - * - * This does seem a little convoluted, but the idea is to allow IUserService to - * evolve with new methods and implementations without breaking custom - * authentication implementations. - * - * The most common implementation of a custom IUserService is to only override - * authentication and then delegate all other functionality to one of Gitblit's - * user services. This class optimizes that use-case. - * - * Extending GitblitUserService allows for authentication customization without - * having to keep-up-with IUSerService API changes. - * - * @author James Moger - * - */ -public class GitblitUserService implements IUserService { - - protected IUserService serviceImpl; - - private final Logger logger = LoggerFactory.getLogger(GitblitUserService.class); - - public GitblitUserService() { - } - - @Override - public void setup(IRuntimeManager runtimeManager) { - File realmFile = runtimeManager.getFileOrFolder(Keys.realm.userService, "${baseFolder}/users.conf"); - serviceImpl = createUserService(realmFile); - logger.info("GUS delegating to " + serviceImpl.toString()); - } - - protected IUserService createUserService(File realmFile) { - IUserService service = null; - if (realmFile.getName().toLowerCase().endsWith(".conf")) { - // v0.8.0+ config-based realm file - service = new ConfigUserService(realmFile); - } - - assert service != null; - - if (!realmFile.exists()) { - // Create the Administrator account for a new realm file - try { - realmFile.createNewFile(); - } catch (IOException x) { - logger.error(MessageFormat.format("COULD NOT CREATE REALM FILE {0}!", realmFile), x); - } - UserModel admin = new UserModel("admin"); - admin.password = "admin"; - admin.canAdmin = true; - admin.excludeFromFederation = true; - service.updateUserModel(admin); - } - - return service; - } - - @Override - public String toString() { - return getClass().getSimpleName(); - } - - @Override - public boolean supportsCredentialChanges() { - return serviceImpl.supportsCredentialChanges(); - } - - @Override - public boolean supportsDisplayNameChanges() { - return serviceImpl.supportsDisplayNameChanges(); - } - - @Override - public boolean supportsEmailAddressChanges() { - return serviceImpl.supportsEmailAddressChanges(); - } - - @Override - public boolean supportsTeamMembershipChanges() { - return serviceImpl.supportsTeamMembershipChanges(); - } - - @Override - public boolean supportsCookies() { - return serviceImpl.supportsCookies(); - } - - @Override - public String getCookie(UserModel model) { - return serviceImpl.getCookie(model); - } - - /** - * Authenticate a user based on their cookie. - * - * @param cookie - * @return a user object or null - */ - @Override - public UserModel authenticate(char[] cookie) { - UserModel user = serviceImpl.authenticate(cookie); - setAccountType(user); - return user; - } - - @Override - public UserModel authenticate(String username, char[] password) { - UserModel user = serviceImpl.authenticate(username, password); - setAccountType(user); - return user; - } - - @Override - public void logout(UserModel user) { - serviceImpl.logout(user); - } - - @Override - public UserModel getUserModel(String username) { - UserModel user = serviceImpl.getUserModel(username); - setAccountType(user); - return user; - } - - @Override - public boolean updateUserModel(UserModel model) { - return serviceImpl.updateUserModel(model); - } - - @Override - public boolean updateUserModels(Collection models) { - return serviceImpl.updateUserModels(models); - } - - @Override - public boolean updateUserModel(String username, UserModel model) { - if (model.isLocalAccount() || supportsCredentialChanges()) { - if (!model.isLocalAccount() && !supportsTeamMembershipChanges()) { - // teams are externally controlled - copy from original model - UserModel existingModel = getUserModel(username); - - model = DeepCopier.copy(model); - model.teams.clear(); - model.teams.addAll(existingModel.teams); - } - return serviceImpl.updateUserModel(username, model); - } - if (model.username.equals(username)) { - // passwords are not persisted by the backing user service - model.password = null; - if (!model.isLocalAccount() && !supportsTeamMembershipChanges()) { - // teams are externally controlled- copy from original model - UserModel existingModel = getUserModel(username); - - model = DeepCopier.copy(model); - model.teams.clear(); - model.teams.addAll(existingModel.teams); - } - return serviceImpl.updateUserModel(username, model); - } - logger.error("Users can not be renamed!"); - return false; - } - @Override - public boolean deleteUserModel(UserModel model) { - return serviceImpl.deleteUserModel(model); - } - - @Override - public boolean deleteUser(String username) { - return serviceImpl.deleteUser(username); - } - - @Override - public List getAllUsernames() { - return serviceImpl.getAllUsernames(); - } - - @Override - public List getAllUsers() { - List users = serviceImpl.getAllUsers(); - for (UserModel user : users) { - setAccountType(user); - } - return users; - } - - @Override - public List getAllTeamNames() { - return serviceImpl.getAllTeamNames(); - } - - @Override - public List getAllTeams() { - return serviceImpl.getAllTeams(); - } - - @Override - public List getTeamNamesForRepositoryRole(String role) { - return serviceImpl.getTeamNamesForRepositoryRole(role); - } - - @Override - @Deprecated - public boolean setTeamnamesForRepositoryRole(String role, List teamnames) { - return serviceImpl.setTeamnamesForRepositoryRole(role, teamnames); - } - - @Override - public TeamModel getTeamModel(String teamname) { - return serviceImpl.getTeamModel(teamname); - } - - @Override - public boolean updateTeamModel(TeamModel model) { - return serviceImpl.updateTeamModel(model); - } - - @Override - public boolean updateTeamModels(Collection models) { - return serviceImpl.updateTeamModels(models); - } - - @Override - public boolean updateTeamModel(String teamname, TeamModel model) { - if (!supportsTeamMembershipChanges()) { - // teams are externally controlled - copy from original model - TeamModel existingModel = getTeamModel(teamname); - - model = DeepCopier.copy(model); - model.users.clear(); - model.users.addAll(existingModel.users); - } - return serviceImpl.updateTeamModel(teamname, model); - } - - @Override - public boolean deleteTeamModel(TeamModel model) { - return serviceImpl.deleteTeamModel(model); - } - - @Override - public boolean deleteTeam(String teamname) { - return serviceImpl.deleteTeam(teamname); - } - - @Override - public List getUsernamesForRepositoryRole(String role) { - return serviceImpl.getUsernamesForRepositoryRole(role); - } - - @Override - @Deprecated - public boolean setUsernamesForRepositoryRole(String role, List usernames) { - return serviceImpl.setUsernamesForRepositoryRole(role, usernames); - } - - @Override - public boolean renameRepositoryRole(String oldRole, String newRole) { - return serviceImpl.renameRepositoryRole(oldRole, newRole); - } - - @Override - public boolean deleteRepositoryRole(String role) { - return serviceImpl.deleteRepositoryRole(role); - } - - protected boolean isLocalAccount(String username) { - UserModel user = getUserModel(username); - return user != null && user.isLocalAccount(); - } - - 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 = getAccountType(); - } - } - } - - @Override - public AccountType getAccountType() { - return AccountType.LOCAL; - } -} diff --git a/src/main/java/com/gitblit/HtpasswdUserService.java b/src/main/java/com/gitblit/HtpasswdUserService.java deleted file mode 100644 index ca5295c9..00000000 --- a/src/main/java/com/gitblit/HtpasswdUserService.java +++ /dev/null @@ -1,361 +0,0 @@ -/* - * Copyright 2013 Florian Zschocke - * 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.io.FileInputStream; -import java.text.MessageFormat; -import java.util.Map; -import java.util.Scanner; -import java.util.concurrent.ConcurrentHashMap; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import org.apache.commons.codec.binary.Base64; -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.AccountType; -import com.gitblit.manager.IRuntimeManager; -import com.gitblit.models.UserModel; -import com.gitblit.utils.ArrayUtils; -import com.gitblit.utils.StringUtils; - - -/** - * Implementation of a user service using an Apache htpasswd file for authentication. - * - * This user service implement custom authentication using entries in a file created - * by the 'htpasswd' program of an Apache web server. All possible output - * options of the 'htpasswd' program version 2.2 are supported: - * plain text (only on Windows and Netware), - * glibc crypt() (not on Windows and NetWare), - * Apache MD5 (apr1), - * unsalted SHA-1. - * - * Configuration options: - * realm.htpasswd.backingUserService - Specify the backing user service that is used - * to keep the user data other than the password. - * The default is '${baseFolder}/users.conf'. - * realm.htpasswd.userfile - The text file with the htpasswd entries to be used for - * authentication. - * The default is '${baseFolder}/htpasswd'. - * realm.htpasswd.overrideLocalAuthentication - Specify if local accounts are overwritten - * when authentication matches for an - * external account. - * - * @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"; - - 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 IRuntimeManager runtimeManager; - private IStoredSettings settings; - private File htpasswdFile; - - - private final Logger logger = LoggerFactory.getLogger(HtpasswdUserService.class); - - private final Map htUsers = new ConcurrentHashMap(); - - 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; - } - } - - - - /** - * Setup the user service. - * - * The HtpasswdUserService extends the GitblitUserService and is thus - * backed by the available user services provided by the GitblitUserService. - * In addition the setup tries to read and parse the htpasswd file to be used - * for authentication. - * - * @param runtimeManager - * @since 1.4.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()); - - 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() - { - return false; - } - - - - /** - * Authenticate a user based on a username and password. - * - * If the account is determined to be a local account, authentication - * will be done against the locally stored password. - * Otherwise, the configured htpasswd file is read. All current output options - * of htpasswd are supported: clear text, crypt(), Apache MD5 and unsalted SHA-1. - * - * @param username - * @param password - * @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); - } - - - read(); - String storedPwd = htUsers.get(username); - if (storedPwd != null) { - boolean authenticated = false; - final String passwd = new String(password); - - // test Apache MD5 variant encrypted password - 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}") ) { - String passwd64 = Base64.encodeBase64String(DigestUtils.sha1(passwd)); - 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)) ) { - logger.debug("Libc crypt encoded password matched for user '" + username + "'"); - authenticated = true; - } - // test clear text - else if ( supportPlaintextPwd() && storedPwd.equals(passwd) ){ - logger.debug("Clear text password matched for user '" + username + "'"); - authenticated = true; - } - - - if (authenticated) { - logger.debug("Htpasswd authenticated: " + username); - - UserModel user = getUserModel(username); - if (user == null) { - // create user object for new authenticated user - user = new UserModel(username); - } - - // create a user cookie - if (StringUtils.isEmpty(user.cookie) && !ArrayUtils.isEmpty(password)) { - user.cookie = StringUtils.getSHA1(user.username + passwd); - } - - // Set user attributes, hide password from backing user service. - user.password = Constants.EXTERNAL_ACCOUNT; - user.accountType = getAccountType(); - - // Push the looked up values to backing file - super.updateUserModel(user); - - return user; - } - } - - 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. - *
- * 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() - { - 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); - this.htUsers.clear(); - this.forceReload = true; - } - - if (htpasswdFile.exists() && (forceReload || (htpasswdFile.lastModified() != lastModified))) { - forceReload = false; - lastModified = htpasswdFile.lastModified(); - htUsers.clear(); - - Pattern entry = Pattern.compile("^([^:]+):(.+)"); - - Scanner scanner = null; - try { - scanner = new Scanner(new FileInputStream(htpasswdFile)); - while( scanner.hasNextLine()) { - String line = scanner.nextLine().trim(); - if ( !line.isEmpty() && !line.startsWith("#") ) { - Matcher m = entry.matcher(line); - 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(); - } - } - } - - - - private boolean supportPlaintextPwd() - { - return this.settings.getBoolean(KEY_SUPPORT_PLAINTEXT_PWD, SUPPORT_PLAINTEXT_PWD); - } - - - 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() - { - return this.htUsers.size(); - } -} diff --git a/src/main/java/com/gitblit/IUserService.java b/src/main/java/com/gitblit/IUserService.java index 316e4a55..053f1790 100644 --- a/src/main/java/com/gitblit/IUserService.java +++ b/src/main/java/com/gitblit/IUserService.java @@ -18,7 +18,6 @@ package com.gitblit; 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; @@ -42,45 +41,6 @@ public interface IUserService { */ 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. * @@ -90,28 +50,12 @@ public interface IUserService { 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. @@ -208,19 +152,6 @@ public interface IUserService { */ List 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 teamnames); - /** * Retrieve the team object for the specified team name. * @@ -290,18 +221,6 @@ public interface IUserService { */ List 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 usernames); - /** * Renames a repository role. * @@ -319,14 +238,6 @@ public interface IUserService { */ boolean deleteRepositoryRole(String role); - /** - * Returns the account type for the user models. - * - * @return the account type - * @since 1.4.0 - */ - AccountType getAccountType(); - /** * @See java.lang.Object.toString(); * @return string representation of the login service diff --git a/src/main/java/com/gitblit/LdapUserService.java b/src/main/java/com/gitblit/LdapUserService.java deleted file mode 100644 index c075afca..00000000 --- a/src/main/java/com/gitblit/LdapUserService.java +++ /dev/null @@ -1,530 +0,0 @@ -/* - * 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 ' ' where 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 ldapUsers = new HashMap(); - - 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 userTeams = new HashMap(); - 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)://:", 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 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 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 getAllUsernames() { - synchronizeLdapUsers(); - return super.getAllUsernames(); - } - - @Override - public List 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(); - } -} diff --git a/src/main/java/com/gitblit/PAMUserService.java b/src/main/java/com/gitblit/PAMUserService.java deleted file mode 100644 index db569fbf..00000000 --- a/src/main/java/com/gitblit/PAMUserService.java +++ /dev/null @@ -1,143 +0,0 @@ -/* - * 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; - } -} diff --git a/src/main/java/com/gitblit/RedmineUserService.java b/src/main/java/com/gitblit/RedmineUserService.java deleted file mode 100644 index 7c38ef2f..00000000 --- a/src/main/java/com/gitblit/RedmineUserService.java +++ /dev/null @@ -1,203 +0,0 @@ -/* - * 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.
- * 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; - } -} diff --git a/src/main/java/com/gitblit/SalesforceUserService.java b/src/main/java/com/gitblit/SalesforceUserService.java deleted file mode 100644 index 6161ba93..00000000 --- a/src/main/java/com/gitblit/SalesforceUserService.java +++ /dev/null @@ -1,139 +0,0 @@ -package com.gitblit; - -import java.io.File; - -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; -import com.sforce.soap.partner.Connector; -import com.sforce.soap.partner.GetUserInfoResult; -import com.sforce.soap.partner.PartnerConnection; -import com.sforce.ws.ConnectionException; -import com.sforce.ws.ConnectorConfig; - -public class SalesforceUserService extends GitblitUserService { - - public static final Logger logger = LoggerFactory.getLogger(SalesforceUserService.class); - private IStoredSettings settings; - - @Override - public AccountType getAccountType() { - return AccountType.SALESFORCE; - } - - @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()); - } - - @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)); - - try { - PartnerConnection connection = Connector.newConnection(config); - - GetUserInfoResult info = connection.getUserInfo(); - - String org = settings.getString(Keys.realm.salesforce.orgId, "0") - .trim(); - - if (!org.equals("0")) { - if (!org.equals(info.getOrganizationId())) { - logger.warn("Access attempted by user of an invalid org: " - + info.getUserName() + ", org: " - + info.getOrganizationName() + "(" - + info.getOrganizationId() + ")"); - - return null; - } - } - - logger.info("Authenticated user " + info.getUserName() - + " using org " + info.getOrganizationName() + "(" - + info.getOrganizationId() + ")"); - - String simpleUsername = getSimpleUsername(info); - - UserModel user = null; - synchronized (this) { - user = getUserModel(simpleUsername); - if (user == null) - user = new UserModel(simpleUsername); - - if (StringUtils.isEmpty(user.cookie) - && !ArrayUtils.isEmpty(password)) { - user.cookie = StringUtils.getSHA1(user.username - + new String(password)); - } - - setUserAttributes(user, info); - - super.updateUserModel(user); - } - - return user; - } catch (ConnectionException e) { - logger.error("Failed to authenticate", e); - } - - return null; - } - - private void setUserAttributes(UserModel user, GetUserInfoResult info) { - // Don't want visibility into the real password, make up a dummy - user.password = Constants.EXTERNAL_ACCOUNT; - user.accountType = getAccountType(); - - // Get full name Attribute - user.displayName = info.getUserFullName(); - - // Get email address Attribute - user.emailAddress = info.getUserEmail(); - } - - /** - * Simple user name is the first part of the email address. - */ - private String getSimpleUsername(GetUserInfoResult info) { - String email = info.getUserEmail(); - - return email.split("@")[0]; - } - - @Override - public boolean supportsCredentialChanges() { - return false; - } - - @Override - public boolean supportsDisplayNameChanges() { - return false; - } - - @Override - public boolean supportsEmailAddressChanges() { - return false; - } -} diff --git a/src/main/java/com/gitblit/WindowsUserService.java b/src/main/java/com/gitblit/WindowsUserService.java deleted file mode 100644 index 99077c67..00000000 --- a/src/main/java/com/gitblit/WindowsUserService.java +++ /dev/null @@ -1,195 +0,0 @@ -/* - * 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 groupNames = new TreeSet(); - 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; - } -} diff --git a/src/main/java/com/gitblit/auth/AuthenticationProvider.java b/src/main/java/com/gitblit/auth/AuthenticationProvider.java new file mode 100644 index 00000000..b8aaf079 --- /dev/null +++ b/src/main/java/com/gitblit/auth/AuthenticationProvider.java @@ -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; + } + } +} diff --git a/src/main/java/com/gitblit/auth/HtpasswdAuthProvider.java b/src/main/java/com/gitblit/auth/HtpasswdAuthProvider.java new file mode 100644 index 00000000..559a0fa0 --- /dev/null +++ b/src/main/java/com/gitblit/auth/HtpasswdAuthProvider.java @@ -0,0 +1,276 @@ +/* + * Copyright 2013 Florian Zschocke + * 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 java.io.FileInputStream; +import java.text.MessageFormat; +import java.util.Map; +import java.util.Scanner; +import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.codec.digest.Crypt; +import org.apache.commons.codec.digest.DigestUtils; +import org.apache.commons.codec.digest.Md5Crypt; + +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 a user service using an Apache htpasswd file for authentication. + * + * This user service implement custom authentication using entries in a file created + * by the 'htpasswd' program of an Apache web server. All possible output + * options of the 'htpasswd' program version 2.2 are supported: + * plain text (only on Windows and Netware), + * glibc crypt() (not on Windows and NetWare), + * Apache MD5 (apr1), + * unsalted SHA-1. + * + * Configuration options: + * realm.htpasswd.backingUserService - Specify the backing user service that is used + * to keep the user data other than the password. + * The default is '${baseFolder}/users.conf'. + * realm.htpasswd.userfile - The text file with the htpasswd entries to be used for + * authentication. + * The default is '${baseFolder}/htpasswd'. + * realm.htpasswd.overrideLocalAuthentication - Specify if local accounts are overwritten + * when authentication matches for an + * external account. + * + * @author Florian Zschocke + * + */ +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_SUPPORT_PLAINTEXT_PWD = "realm.htpasswd.supportPlaintextPasswords"; + + private boolean supportPlainTextPwd; + + private File htpasswdFile; + + private final Map htUsers = new ConcurrentHashMap(); + + private volatile long lastModified; + + public HtpasswdAuthProvider() { + super("htpasswd"); + } + + /** + * Setup the user service. + * + * The HtpasswdUserService extends the GitblitUserService and is thus + * backed by the available user services provided by the GitblitUserService. + * In addition the setup tries to read and parse the htpasswd file to be used + * for authentication. + * + * @param settings + * @since 0.7.0 + */ + @Override + 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); + } + + @Override + 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. + * + * If the account is determined to be a local account, authentication + * will be done against the locally stored password. + * Otherwise, the configured htpasswd file is read. All current output options + * of htpasswd are supported: clear text, crypt(), Apache MD5 and unsalted SHA-1. + * + * @param username + * @param password + * @return a user object or null + */ + @Override + public UserModel authenticate(String username, char[] password) { + read(); + String storedPwd = htUsers.get(username); + if (storedPwd != null) { + boolean authenticated = false; + final String passwd = new String(password); + + // test Apache MD5 variant encrypted password + 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}")) { + String passwd64 = Base64.encodeBase64String(DigestUtils.sha1(passwd)); + 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))) { + logger.debug("Libc crypt encoded password matched for user '" + username + "'"); + authenticated = true; + } + // test clear text + else if (supportPlaintextPwd() && storedPwd.equals(passwd)){ + logger.debug("Clear text password matched for user '" + username + "'"); + authenticated = true; + } + + + if (authenticated) { + logger.debug("Htpasswd authenticated: " + username); + + 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 + if (StringUtils.isEmpty(user.cookie) && !ArrayUtils.isEmpty(password)) { + user.cookie = StringUtils.getSHA1(user.username + passwd); + } + + // Set user attributes, hide password from backing user service. + user.password = Constants.EXTERNAL_ACCOUNT; + user.accountType = getAccountType(); + + // Push the looked up values to backing file + updateUser(user); + + return user; + } + } + + return null; + } + + /** + * Get the account type used for this user service. + * + * @return AccountType.HTPASSWD + */ + @Override + public AccountType getAccountType() { + return AccountType.HTPASSWD; + } + + /** + * Reads the realm file and rebuilds the in-memory lookup tables. + */ + 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(); + forceReload = true; + } + + if (htpasswdFile.exists() && (forceReload || (htpasswdFile.lastModified() != lastModified))) { + lastModified = htpasswdFile.lastModified(); + htUsers.clear(); + + Pattern entry = Pattern.compile("^([^:]+):(.+)"); + + Scanner scanner = null; + try { + scanner = new Scanner(new FileInputStream(htpasswdFile)); + while (scanner.hasNextLine()) { + String line = scanner.nextLine().trim(); + if (!line.isEmpty() && !line.startsWith("#")) { + Matcher m = entry.matcher(line); + 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(); + } + } + } + } + + private boolean supportPlaintextPwd() { + return this.settings.getBoolean(KEY_SUPPORT_PLAINTEXT_PWD, supportPlainTextPwd); + } + + private boolean supportCryptPwd() { + return !supportPlaintextPwd(); + } + + /* + * Method only used for unit tests. Return number of users read from htpasswd file. + */ + public int getNumberHtpasswdUsers() { + return this.htUsers.size(); + } + + @Override + public String toString() { + return getClass().getSimpleName() + "(" + ((htpasswdFile != null) ? htpasswdFile.getAbsolutePath() : "null") + ")"; + } +} diff --git a/src/main/java/com/gitblit/auth/LdapAuthProvider.java b/src/main/java/com/gitblit/auth/LdapAuthProvider.java new file mode 100644 index 00000000..7a6b74df --- /dev/null +++ b/src/main/java/com/gitblit/auth/LdapAuthProvider.java @@ -0,0 +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.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 ' ' where 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 ldapUsers = new HashMap(); + + 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 userTeams = new HashMap(); + 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)://:", 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 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 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(); + } +} diff --git a/src/main/java/com/gitblit/auth/PAMAuthProvider.java b/src/main/java/com/gitblit/auth/PAMAuthProvider.java new file mode 100644 index 00000000..bbc82d84 --- /dev/null +++ b/src/main/java/com/gitblit/auth/PAMAuthProvider.java @@ -0,0 +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.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; + } +} diff --git a/src/main/java/com/gitblit/auth/RedmineAuthProvider.java b/src/main/java/com/gitblit/auth/RedmineAuthProvider.java new file mode 100644 index 00000000..176c576b --- /dev/null +++ b/src/main/java/com/gitblit/auth/RedmineAuthProvider.java @@ -0,0 +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.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.
+ * 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; + } +} diff --git a/src/main/java/com/gitblit/auth/SalesforceAuthProvider.java b/src/main/java/com/gitblit/auth/SalesforceAuthProvider.java new file mode 100644 index 00000000..fdda32af --- /dev/null +++ b/src/main/java/com/gitblit/auth/SalesforceAuthProvider.java @@ -0,0 +1,128 @@ +package com.gitblit.auth; + +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.sforce.soap.partner.Connector; +import com.sforce.soap.partner.GetUserInfoResult; +import com.sforce.soap.partner.PartnerConnection; +import com.sforce.ws.ConnectionException; +import com.sforce.ws.ConnectorConfig; + +public class SalesforceAuthProvider extends UsernamePasswordAuthenticationProvider { + + public SalesforceAuthProvider() { + super("salesforce"); + } + + @Override + public AccountType getAccountType() { + return AccountType.SALESFORCE; + } + + @Override + public void setup() { + } + + @Override + public UserModel authenticate(String username, char[] password) { + ConnectorConfig config = new ConnectorConfig(); + config.setUsername(username); + config.setPassword(new String(password)); + + try { + PartnerConnection connection = Connector.newConnection(config); + + GetUserInfoResult info = connection.getUserInfo(); + + String org = settings.getString(Keys.realm.salesforce.orgId, "0") + .trim(); + + if (!org.equals("0")) { + if (!org.equals(info.getOrganizationId())) { + logger.warn("Access attempted by user of an invalid org: " + + info.getUserName() + ", org: " + + info.getOrganizationName() + "(" + + info.getOrganizationId() + ")"); + + return null; + } + } + + logger.info("Authenticated user " + info.getUserName() + + " using org " + info.getOrganizationName() + "(" + + info.getOrganizationId() + ")"); + + String simpleUsername = getSimpleUsername(info); + + UserModel user = null; + synchronized (this) { + user = userManager.getUserModel(simpleUsername); + if (user == null) + user = new UserModel(simpleUsername); + + if (StringUtils.isEmpty(user.cookie) + && !ArrayUtils.isEmpty(password)) { + user.cookie = StringUtils.getSHA1(user.username + + new String(password)); + } + + setUserAttributes(user, info); + + updateUser(user); + } + + return user; + } catch (ConnectionException e) { + logger.error("Failed to authenticate", e); + } + + return null; + } + + private void setUserAttributes(UserModel user, GetUserInfoResult info) { + // Don't want visibility into the real password, make up a dummy + user.password = Constants.EXTERNAL_ACCOUNT; + user.accountType = getAccountType(); + + // Get full name Attribute + user.displayName = info.getUserFullName(); + + // Get email address Attribute + user.emailAddress = info.getUserEmail(); + } + + /** + * Simple user name is the first part of the email address. + */ + private String getSimpleUsername(GetUserInfoResult info) { + String email = info.getUserEmail(); + + return email.split("@")[0]; + } + + + @Override + public boolean supportsCredentialChanges() { + return false; + } + + @Override + public boolean supportsDisplayNameChanges() { + return false; + } + + @Override + public boolean supportsEmailAddressChanges() { + return false; + } + + @Override + public boolean supportsTeamMembershipChanges() { + return true; + } +} diff --git a/src/main/java/com/gitblit/auth/WindowsAuthProvider.java b/src/main/java/com/gitblit/auth/WindowsAuthProvider.java new file mode 100644 index 00000000..d455d58f --- /dev/null +++ b/src/main/java/com/gitblit/auth/WindowsAuthProvider.java @@ -0,0 +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.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 groupNames = new TreeSet(); + 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; + } +} diff --git a/src/main/java/com/gitblit/client/EditTeamDialog.java b/src/main/java/com/gitblit/client/EditTeamDialog.java index 3c0b928c..0b5b3505 100644 --- a/src/main/java/com/gitblit/client/EditTeamDialog.java +++ b/src/main/java/com/gitblit/client/EditTeamDialog.java @@ -146,7 +146,6 @@ public class EditTeamDialog extends JDialog { final Insets _insets = new Insets(5, 5, 5, 5); repositoryPalette = new RegistrantPermissionsPanel(RegistrantType.REPOSITORY); userPalette = new JPalette(); - userPalette.setEnabled(settings.supportsTeamMembershipChanges); JPanel fieldsPanelTop = new JPanel(new BorderLayout()); fieldsPanelTop.add(fieldsPanel, BorderLayout.NORTH); diff --git a/src/main/java/com/gitblit/client/EditUserDialog.java b/src/main/java/com/gitblit/client/EditUserDialog.java index fd5cf79d..2936a29d 100644 --- a/src/main/java/com/gitblit/client/EditUserDialog.java +++ b/src/main/java/com/gitblit/client/EditUserDialog.java @@ -161,18 +161,9 @@ public class EditUserDialog extends JDialog { 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 @@ public class EditUserDialog extends JDialog { final Insets _insets = new Insets(5, 5, 5, 5); repositoryPalette = new RegistrantPermissionsPanel(RegistrantType.REPOSITORY); teamsPalette = new JPalette(); - teamsPalette.setEnabled(settings.supportsTeamMembershipChanges); JPanel fieldsPanelTop = new JPanel(new BorderLayout()); fieldsPanelTop.add(fieldsPanel, BorderLayout.NORTH); diff --git a/src/main/java/com/gitblit/git/GitblitUploadPackFactory.java b/src/main/java/com/gitblit/git/GitblitUploadPackFactory.java index 2afdde1d..d4e3ca15 100644 --- a/src/main/java/com/gitblit/git/GitblitUploadPackFactory.java +++ b/src/main/java/com/gitblit/git/GitblitUploadPackFactory.java @@ -23,7 +23,7 @@ import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException; 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 @@ import com.gitblit.models.UserModel; */ public class GitblitUploadPackFactory implements UploadPackFactory { - 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 @@ public class GitblitUploadPackFactory implements UploadPackFactory { 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; } diff --git a/src/main/java/com/gitblit/manager/AuthenticationManager.java b/src/main/java/com/gitblit/manager/AuthenticationManager.java new file mode 100644 index 00000000..6e541c45 --- /dev/null +++ b/src/main/java/com/gitblit/manager/AuthenticationManager.java @@ -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 authenticationProviders; + + private final Map> providerNames; + + private final Map legacyRedirects; + + public AuthenticationManager( + IRuntimeManager runtimeManager, + IUserManager userManager) { + + this.settings = runtimeManager.getSettings(); + this.runtimeManager = runtimeManager; + this.userManager = userManager; + this.authenticationProviders = new ArrayList(); + + // map of shortcut provider names + providerNames = new HashMap>(); + 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(); + 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 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 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; +// } +} diff --git a/src/main/java/com/gitblit/manager/GitblitManager.java b/src/main/java/com/gitblit/manager/GitblitManager.java index 2e6a33e3..9947382a 100644 --- a/src/main/java/com/gitblit/manager/GitblitManager.java +++ b/src/main/java/com/gitblit/manager/GitblitManager.java @@ -101,13 +101,6 @@ public class GitblitManager implements IGitblitManager { * @return Map */ 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 diff --git a/src/main/java/com/gitblit/manager/IAuthenticationManager.java b/src/main/java/com/gitblit/manager/IAuthenticationManager.java new file mode 100644 index 00000000..093f44d9 --- /dev/null +++ b/src/main/java/com/gitblit/manager/IAuthenticationManager.java @@ -0,0 +1,113 @@ +/* + * 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 javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import com.gitblit.models.TeamModel; +import com.gitblit.models.UserModel; + +public interface IAuthenticationManager extends IManager { + + /** + * 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 + */ + UserModel authenticate(HttpServletRequest httpRequest); + + /** + * Authenticate a user based on HTTP request parameters. + * + * Authentication by X509Certificate, servlet container principal, cookie, + * and BASIC header. + * + * @param httpRequest + * @param requiresCertificate + * @return a user object or null + */ + UserModel authenticate(HttpServletRequest httpRequest, boolean requiresCertificate); + + /** + * Authenticate a user based on a username and password. + * + * @see IUserService.authenticate(String, char[]) + * @param username + * @param password + * @return a user object or null + */ + UserModel authenticate(String username, char[] password); + + /** + * Sets a cookie for the specified user. + * + * @param response + * @param user + */ + void setCookie(HttpServletResponse response, UserModel user); + + /** + * Logout a user. + * + * @param user + */ + 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); + +} \ No newline at end of file diff --git a/src/main/java/com/gitblit/manager/ISessionManager.java b/src/main/java/com/gitblit/manager/ISessionManager.java deleted file mode 100644 index 29f17096..00000000 --- a/src/main/java/com/gitblit/manager/ISessionManager.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * 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 javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import com.gitblit.models.UserModel; - -public interface ISessionManager extends IManager { - - /** - * 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 - */ - UserModel authenticate(HttpServletRequest httpRequest); - - /** - * Authenticate a user based on HTTP request parameters. - * - * Authentication by X509Certificate, servlet container principal, cookie, - * and BASIC header. - * - * @param httpRequest - * @param requiresCertificate - * @return a user object or null - */ - UserModel authenticate(HttpServletRequest httpRequest, boolean requiresCertificate); - - /** - * Authenticate a user based on a username and password. - * - * @see IUserService.authenticate(String, char[]) - * @param username - * @param password - * @return a user object or null - */ - UserModel authenticate(String username, char[] password); - - /** - * Sets a cookie for the specified user. - * - * @param response - * @param user - */ - void setCookie(HttpServletResponse response, UserModel user); - - /** - * Logout a user. - * - * @param user - */ - void logout(HttpServletResponse response, UserModel user); - -} \ No newline at end of file diff --git a/src/main/java/com/gitblit/manager/IUserManager.java b/src/main/java/com/gitblit/manager/IUserManager.java index 387a7208..5815bcaf 100644 --- a/src/main/java/com/gitblit/manager/IUserManager.java +++ b/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 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 getAllUsernames(); - - /** - * Returns the list of all users available to the login service. - * - * @return list of all users - * @since 0.8.0 - */ - List getAllUsers(); - - /** - * Returns the list of all teams available to the login service. - * - * @return list of all teams - * @since 0.8.0 - */ - List getAllTeamNames(); - - /** - * Returns the list of all teams available to the login service. - * - * @return list of all teams - * @since 0.8.0 - */ - List 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 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 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 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); } \ No newline at end of file diff --git a/src/main/java/com/gitblit/manager/SessionManager.java b/src/main/java/com/gitblit/manager/SessionManager.java deleted file mode 100644 index 6a85da89..00000000 --- a/src/main/java/com/gitblit/manager/SessionManager.java +++ /dev/null @@ -1,340 +0,0 @@ -/* - * 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.List; - -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.AuthenticationType; -import com.gitblit.IStoredSettings; -import com.gitblit.Keys; -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 session manager handles user login & logout. - * - * @author James Moger - * - */ -public class SessionManager implements ISessionManager { - - private final Logger logger = LoggerFactory.getLogger(getClass()); - - private final IStoredSettings settings; - - private final IRuntimeManager runtimeManager; - - private final IUserManager userManager; - - public SessionManager( - IRuntimeManager runtimeManager, - IUserManager userManager) { - - this.settings = runtimeManager.getSettings(); - this.runtimeManager = runtimeManager; - this.userManager = userManager; - } - - @Override - public SessionManager start() { - List services = settings.getStrings("realm.authenticationServices"); - for (String service : services) { - // TODO populate authentication services here - } - return this; - } - - @Override - public SessionManager 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 X509Certificate, servlet container principal, cookie, - * and BASIC header. - * - * @param httpRequest - * @param requiresCertificate - * @return a user object or null - */ - @Override - public UserModel authenticate(HttpServletRequest httpRequest, boolean requiresCertificate) { - // 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 servlet container principal - 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; - 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 cookie - if (userManager.supportsCookies()) { - 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(); - UserModel 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 (userManager.supportsCookies()) { - if (cookies != null && cookies.length > 0) { - for (Cookie cookie : cookies) { - if (cookie.getName().equals(Constants.NAME)) { - String value = cookie.getValue(); - return userManager.authenticate(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 tokens = getFederationTokens(); -// if (tokens.contains(pw)) { -// return getFederationUser(); -// } -// } -// } - - UserModel user = userManager.authenticate(usernameDecoded, password); - - // try registered external authentication providers - if (user == null) { -// for (AuthenticationService service : authenticationServices) { -// if (service instanceof UsernamePasswordAuthenticationService) { -// user = service.authenticate(usernameDecoded, password); -// if (user != null) { -// // user authenticated -// user.accountType = service.getAccountType(); -// return user; -// } -// } -// } - } - return user; - } - - /** - * Sets a cookie for the specified user. - * - * @param response - * @param user - */ - @Override - public void setCookie(HttpServletResponse response, UserModel user) { - GitBlitWebSession session = GitBlitWebSession.get(); - boolean standardLogin = session.authenticationType.isStandard(); - - if (userManager.supportsCookies() && 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); - userManager.logout(user); - } - - /** - * 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; -// } -} diff --git a/src/main/java/com/gitblit/manager/UserManager.java b/src/main/java/com/gitblit/manager/UserManager.java index 90b9d1e2..3ca62e23 100644 --- a/src/main/java/com/gitblit/manager/UserManager.java +++ b/src/main/java/com/gitblit/manager/UserManager.java @@ -20,20 +20,19 @@ import java.io.IOException; 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 @@ public class UserManager implements IUserManager { private final IRuntimeManager runtimeManager; + private final Map 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(); + 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 @@ public class UserManager implements IUserManager { 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 @@ public class UserManager implements IUserManager { 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,46 +160,17 @@ public class UserManager implements IUserManager { } /** - * 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); - } - /** * Retrieve the user object for the specified username. * @@ -251,7 +184,6 @@ public class UserManager implements IUserManager { } String usernameDecoded = StringUtils.decodeUsername(username); UserModel user = userService.getUserModel(usernameDecoded); - setAccountType(user); return user; } @@ -290,32 +222,7 @@ public class UserManager implements IUserManager { */ @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 @@ public class UserManager implements IUserManager { @Override public List getAllUsers() { List users = userService.getAllUsers(); - for (UserModel user : users) { - setAccountType(user); - } return users; } @@ -378,7 +282,8 @@ public class UserManager implements IUserManager { */ @Override public List getAllTeamNames() { - return userService.getAllTeamNames(); + List teams = userService.getAllTeamNames(); + return teams; } /** @@ -404,7 +309,8 @@ public class UserManager implements IUserManager { */ @Override public List getTeamNamesForRepositoryRole(String role) { - return userService.getTeamNamesForRepositoryRole(role); + List teams = userService.getTeamNamesForRepositoryRole(role); + return teams; } /** @@ -416,7 +322,8 @@ public class UserManager implements IUserManager { */ @Override public TeamModel getTeamModel(String teamname) { - return userService.getTeamModel(teamname); + TeamModel team = userService.getTeamModel(teamname); + return team; } /** @@ -456,14 +363,6 @@ public class UserManager implements IUserManager { */ @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); } @@ -527,16 +426,4 @@ public class UserManager implements IUserManager { 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(); - } - } - } } diff --git a/src/main/java/com/gitblit/models/ServerSettings.java b/src/main/java/com/gitblit/models/ServerSettings.java index 92d5c311..07e703b0 100644 --- a/src/main/java/com/gitblit/models/ServerSettings.java +++ b/src/main/java/com/gitblit/models/ServerSettings.java @@ -37,14 +37,6 @@ public class ServerSettings implements Serializable { public List pushScripts; - public boolean supportsCredentialChanges; - - public boolean supportsDisplayNameChanges; - - public boolean supportsEmailAddressChanges; - - public boolean supportsTeamMembershipChanges; - public ServerSettings() { settings = new TreeMap(); } diff --git a/src/main/java/com/gitblit/models/TeamModel.java b/src/main/java/com/gitblit/models/TeamModel.java index a1928283..aaa3d54a 100644 --- a/src/main/java/com/gitblit/models/TeamModel.java +++ b/src/main/java/com/gitblit/models/TeamModel.java @@ -27,6 +27,7 @@ import java.util.Set; 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 class TeamModel implements Serializable, Comparable { public boolean canAdmin; public boolean canFork; public boolean canCreate; + public AccountType accountType; public final Set users = new HashSet(); // retained for backwards-compatibility with RPC clients @Deprecated @@ -59,6 +61,7 @@ public class TeamModel implements Serializable, Comparable { public TeamModel(String name) { this.name = name; + this.accountType = AccountType.LOCAL; } /** @@ -358,6 +361,10 @@ public class TeamModel implements Serializable, Comparable { } } + public boolean isLocalTeam() { + return accountType.isLocal(); + } + @Override public String toString() { return name; diff --git a/src/main/java/com/gitblit/servlet/AccessRestrictionFilter.java b/src/main/java/com/gitblit/servlet/AccessRestrictionFilter.java index d5ded33c..d6acdbb6 100644 --- a/src/main/java/com/gitblit/servlet/AccessRestrictionFilter.java +++ b/src/main/java/com/gitblit/servlet/AccessRestrictionFilter.java @@ -27,7 +27,7 @@ import javax.servlet.http.HttpServletResponse; 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 @@ public abstract class AccessRestrictionFilter extends AuthenticationFilter { protected AccessRestrictionFilter( IRuntimeManager runtimeManager, - ISessionManager sessionManager, + IAuthenticationManager authenticationManager, IRepositoryManager repositoryManager) { - super(sessionManager); + super(authenticationManager); this.runtimeManager = runtimeManager; this.repositoryManager = repositoryManager; } diff --git a/src/main/java/com/gitblit/servlet/AuthenticationFilter.java b/src/main/java/com/gitblit/servlet/AuthenticationFilter.java index 214f2042..54c70141 100644 --- a/src/main/java/com/gitblit/servlet/AuthenticationFilter.java +++ b/src/main/java/com/gitblit/servlet/AuthenticationFilter.java @@ -36,7 +36,7 @@ import org.slf4j.Logger; 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 @@ public abstract class AuthenticationFilter implements Filter { 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 @@ public abstract class AuthenticationFilter implements Filter { * @return user */ protected UserModel getUser(HttpServletRequest httpRequest) { - UserModel user = sessionManager.authenticate(httpRequest, requiresClientCertificate()); + UserModel user = authenticationManager.authenticate(httpRequest, requiresClientCertificate()); return user; } diff --git a/src/main/java/com/gitblit/servlet/DownloadZipFilter.java b/src/main/java/com/gitblit/servlet/DownloadZipFilter.java index f2064e3a..398121d3 100644 --- a/src/main/java/com/gitblit/servlet/DownloadZipFilter.java +++ b/src/main/java/com/gitblit/servlet/DownloadZipFilter.java @@ -21,7 +21,7 @@ import javax.inject.Singleton; 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 @@ public class DownloadZipFilter extends AccessRestrictionFilter { @Inject public DownloadZipFilter( IRuntimeManager runtimeManager, - ISessionManager sessionManager, + IAuthenticationManager authenticationManager, IRepositoryManager repositoryManager) { - super(runtimeManager, sessionManager, repositoryManager); + super(runtimeManager, authenticationManager, repositoryManager); } /** diff --git a/src/main/java/com/gitblit/servlet/EnforceAuthenticationFilter.java b/src/main/java/com/gitblit/servlet/EnforceAuthenticationFilter.java index d690fd2c..6655c6e1 100644 --- a/src/main/java/com/gitblit/servlet/EnforceAuthenticationFilter.java +++ b/src/main/java/com/gitblit/servlet/EnforceAuthenticationFilter.java @@ -36,7 +36,7 @@ import com.gitblit.IStoredSettings; 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 @@ public class EnforceAuthenticationFilter implements Filter { 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 @@ public class EnforceAuthenticationFilter implements Filter { 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: diff --git a/src/main/java/com/gitblit/servlet/GitFilter.java b/src/main/java/com/gitblit/servlet/GitFilter.java index f39d68fd..c44f7efc 100644 --- a/src/main/java/com/gitblit/servlet/GitFilter.java +++ b/src/main/java/com/gitblit/servlet/GitFilter.java @@ -29,7 +29,7 @@ import com.gitblit.Constants.AuthorizationControl; 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 @@ public class GitFilter extends AccessRestrictionFilter { @Inject public GitFilter( IRuntimeManager runtimeManager, - ISessionManager sessionManager, + IAuthenticationManager authenticationManager, IRepositoryManager repositoryManager) { - super(runtimeManager, sessionManager, repositoryManager); + super(runtimeManager, authenticationManager, repositoryManager); this.settings = runtimeManager.getSettings(); } diff --git a/src/main/java/com/gitblit/servlet/GitblitContext.java b/src/main/java/com/gitblit/servlet/GitblitContext.java index 73250121..3e95b4bb 100644 --- a/src/main/java/com/gitblit/servlet/GitblitContext.java +++ b/src/main/java/com/gitblit/servlet/GitblitContext.java @@ -49,7 +49,7 @@ import com.gitblit.manager.IProjectManager; 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 @@ public class GitblitContext extends DaggerContextListener { // 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); diff --git a/src/main/java/com/gitblit/servlet/PagesFilter.java b/src/main/java/com/gitblit/servlet/PagesFilter.java index 23e7859f..42e7de75 100644 --- a/src/main/java/com/gitblit/servlet/PagesFilter.java +++ b/src/main/java/com/gitblit/servlet/PagesFilter.java @@ -23,7 +23,7 @@ import org.eclipse.jgit.lib.Repository; 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 @@ public class PagesFilter extends AccessRestrictionFilter { @Inject public PagesFilter(IRuntimeManager runtimeManager, - ISessionManager sessionManager, + IAuthenticationManager authenticationManager, IRepositoryManager repositoryManager) { - super(runtimeManager, sessionManager, repositoryManager); + super(runtimeManager, authenticationManager, repositoryManager); } /** diff --git a/src/main/java/com/gitblit/servlet/RpcFilter.java b/src/main/java/com/gitblit/servlet/RpcFilter.java index 6163252d..f39d37d8 100644 --- a/src/main/java/com/gitblit/servlet/RpcFilter.java +++ b/src/main/java/com/gitblit/servlet/RpcFilter.java @@ -31,7 +31,7 @@ import com.gitblit.Constants.RpcRequest; 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 @@ public class RpcFilter extends AuthenticationFilter { @Inject public RpcFilter( IRuntimeManager runtimeManager, - ISessionManager sessionManager) { + IAuthenticationManager authenticationManager) { - super(sessionManager); + super(authenticationManager); this.settings = runtimeManager.getSettings(); this.runtimeManager = runtimeManager; } diff --git a/src/main/java/com/gitblit/servlet/SparkleShareInviteServlet.java b/src/main/java/com/gitblit/servlet/SparkleShareInviteServlet.java index 4b8b24f4..3bef51dd 100644 --- a/src/main/java/com/gitblit/servlet/SparkleShareInviteServlet.java +++ b/src/main/java/com/gitblit/servlet/SparkleShareInviteServlet.java @@ -31,7 +31,7 @@ import com.gitblit.Keys; 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 @@ public class SparkleShareInviteServlet extends HttpServlet { private final IUserManager userManager; - private final ISessionManager sessionManager; + private final IAuthenticationManager authenticationManager; private final IRepositoryManager repositoryManager; @@ -60,13 +60,13 @@ public class SparkleShareInviteServlet extends HttpServlet { 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 @@ public class SparkleShareInviteServlet extends HttpServlet { } UserModel user; if (StringUtils.isEmpty(username)) { - user = sessionManager.authenticate(request); + user = authenticationManager.authenticate(request); } else { user = userManager.getUserModel(username); } diff --git a/src/main/java/com/gitblit/servlet/SyndicationFilter.java b/src/main/java/com/gitblit/servlet/SyndicationFilter.java index adf9ba94..7eb8af96 100644 --- a/src/main/java/com/gitblit/servlet/SyndicationFilter.java +++ b/src/main/java/com/gitblit/servlet/SyndicationFilter.java @@ -31,7 +31,7 @@ import com.gitblit.Constants.AccessRestrictionType; 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 @@ public class SyndicationFilter extends AuthenticationFilter { @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; diff --git a/src/main/java/com/gitblit/wicket/GitBlitWebApp.java b/src/main/java/com/gitblit/wicket/GitBlitWebApp.java index ad13a042..1409e474 100644 --- a/src/main/java/com/gitblit/wicket/GitBlitWebApp.java +++ b/src/main/java/com/gitblit/wicket/GitBlitWebApp.java @@ -36,7 +36,7 @@ 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.IAuthenticationManager; import com.gitblit.manager.IUserManager; import com.gitblit.utils.StringUtils; import com.gitblit.wicket.pages.ActivityPage; @@ -89,7 +89,7 @@ public class GitBlitWebApp extends WebApplication { private final IUserManager userManager; - private final ISessionManager sessionManager; + private final IAuthenticationManager authenticationManager; private final IRepositoryManager repositoryManager; @@ -103,7 +103,7 @@ public class GitBlitWebApp extends WebApplication { IRuntimeManager runtimeManager, INotificationManager notificationManager, IUserManager userManager, - ISessionManager sessionManager, + IAuthenticationManager authenticationManager, IRepositoryManager repositoryManager, IProjectManager projectManager, IGitblitManager gitblitManager, @@ -114,7 +114,7 @@ public class GitBlitWebApp extends WebApplication { 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 @@ public class GitBlitWebApp extends WebApplication { return userManager; } - public ISessionManager session() { - return sessionManager; + public IAuthenticationManager authentication() { + return authenticationManager; } public IRepositoryManager repositories() { diff --git a/src/main/java/com/gitblit/wicket/pages/ChangePasswordPage.java b/src/main/java/com/gitblit/wicket/pages/ChangePasswordPage.java index a3c1ece5..ec5fe16e 100644 --- a/src/main/java/com/gitblit/wicket/pages/ChangePasswordPage.java +++ b/src/main/java/com/gitblit/wicket/pages/ChangePasswordPage.java @@ -51,7 +51,7 @@ public class ChangePasswordPage extends RootSubPage { } 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 @@ public class ChangePasswordPage extends RootSubPage { 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()); diff --git a/src/main/java/com/gitblit/wicket/pages/EditTeamPage.java b/src/main/java/com/gitblit/wicket/pages/EditTeamPage.java index 4f548d46..232dbe3b 100644 --- a/src/main/java/com/gitblit/wicket/pages/EditTeamPage.java +++ b/src/main/java/com/gitblit/wicket/pages/EditTeamPage.java @@ -216,7 +216,7 @@ public class EditTeamPage extends RootSubPage { 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("name")); diff --git a/src/main/java/com/gitblit/wicket/pages/EditUserPage.java b/src/main/java/com/gitblit/wicket/pages/EditUserPage.java index b2d3d3b1..4e91b45a 100644 --- a/src/main/java/com/gitblit/wicket/pages/EditUserPage.java +++ b/src/main/java/com/gitblit/wicket/pages/EditUserPage.java @@ -54,10 +54,6 @@ public class EditUserPage extends RootSubPage { 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 @@ public class EditUserPage extends RootSubPage { } 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 @@ public class EditUserPage extends RootSubPage { 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("username").setEnabled(editCredentials)); diff --git a/src/main/java/com/gitblit/wicket/pages/LogoutPage.java b/src/main/java/com/gitblit/wicket/pages/LogoutPage.java index d99c1466..27542bd0 100644 --- a/src/main/java/com/gitblit/wicket/pages/LogoutPage.java +++ b/src/main/java/com/gitblit/wicket/pages/LogoutPage.java @@ -27,7 +27,7 @@ public class LogoutPage extends BasePage { 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(); /* diff --git a/src/main/java/com/gitblit/wicket/pages/RootPage.java b/src/main/java/com/gitblit/wicket/pages/RootPage.java index 1a43bf1e..9141b4e3 100644 --- a/src/main/java/com/gitblit/wicket/pages/RootPage.java +++ b/src/main/java/com/gitblit/wicket/pages/RootPage.java @@ -252,7 +252,7 @@ public abstract class RootPage extends BasePage { // 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 @@ public abstract class RootPage extends BasePage { 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 @@ public abstract class RootPage extends BasePage { 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)) { diff --git a/src/main/java/com/gitblit/wicket/pages/SessionPage.java b/src/main/java/com/gitblit/wicket/pages/SessionPage.java index a10102f4..d2fcfa0d 100644 --- a/src/main/java/com/gitblit/wicket/pages/SessionPage.java +++ b/src/main/java/com/gitblit/wicket/pages/SessionPage.java @@ -60,7 +60,7 @@ public abstract class SessionPage extends WebPage { // 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 @@ public abstract class SessionPage extends WebPage { // Set Cookie WebResponse response = (WebResponse) getRequestCycle().getResponse(); - app().session().setCookie(response.getHttpServletResponse(), user); + app().authentication().setCookie(response.getHttpServletResponse(), user); session.continueRequest(); } diff --git a/src/main/java/com/gitblit/wicket/panels/TeamsPanel.java b/src/main/java/com/gitblit/wicket/panels/TeamsPanel.java index 79ddd02d..c1e1a43d 100644 --- a/src/main/java/com/gitblit/wicket/panels/TeamsPanel.java +++ b/src/main/java/com/gitblit/wicket/panels/TeamsPanel.java @@ -39,7 +39,7 @@ public class TeamsPanel extends BasePanel { Fragment adminLinks = new Fragment("adminPanel", "adminLinks", this); adminLinks.add(new BookmarkablePageLink("newTeam", EditTeamPage.class)); - add(adminLinks.setVisible(showAdmin && app().users().supportsTeamMembershipChanges(null))); + add(adminLinks.setVisible(showAdmin)); final List teams = app().users().getAllTeams(); DataView teamsView = new DataView("teamRow", diff --git a/src/main/java/com/gitblit/wicket/panels/UsersPanel.java b/src/main/java/com/gitblit/wicket/panels/UsersPanel.java index 9c1667ff..ed990c89 100644 --- a/src/main/java/com/gitblit/wicket/panels/UsersPanel.java +++ b/src/main/java/com/gitblit/wicket/panels/UsersPanel.java @@ -39,8 +39,7 @@ public class UsersPanel extends BasePanel { super(wicketId); Fragment adminLinks = new Fragment("adminPanel", "adminLinks", this); - adminLinks.add(new BookmarkablePageLink("newUser", EditUserPage.class) - .setVisible(app().users().supportsAddUser())); + adminLinks.add(new BookmarkablePageLink("newUser", EditUserPage.class)); add(adminLinks.setVisible(showAdmin)); final List users = app().users().getAllUsers(); diff --git a/src/site/setup_authentication.mkd b/src/site/setup_authentication.mkd index 3fb4a6c1..02d1be70 100644 --- a/src/site/setup_authentication.mkd +++ b/src/site/setup_authentication.mkd @@ -15,11 +15,11 @@ Gitblit supports additional authentication mechanisms aside from it's internal o ### 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") @@ -46,10 +46,6 @@ The following are the settings required to configure Gitblit to authenticate aga realm.ldap.passwordpassword The credentials that will log into the LDAP server - - realm.ldap.backingUserServiceusers.conf - Where to store all information that is used by Gitblit. All information will be synced here upon user login. - realm.ldap.maintainTeamstrue Are team memberships maintained in LDAP (true) or manually in Gitblit (false). @@ -82,35 +78,35 @@ You can start Gitblit GO with an in-memory LDAP server by specifying the *--ldap 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 @@ If you are using the WAR variant and deploying into your own servlet container w 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. diff --git a/src/test/config/test-users.conf b/src/test/config/test-users.conf index b665fd5b..59b6df49 100644 --- a/src/test/config/test-users.conf +++ b/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 diff --git a/src/test/java/com/gitblit/tests/GitBlitSuite.java b/src/test/java/com/gitblit/tests/GitBlitSuite.java index 7fc6fe0e..81180276 100644 --- a/src/test/java/com/gitblit/tests/GitBlitSuite.java +++ b/src/test/java/com/gitblit/tests/GitBlitSuite.java @@ -56,12 +56,12 @@ import com.gitblit.utils.JGitUtils; @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 { diff --git a/src/test/java/com/gitblit/tests/GitBlitTest.java b/src/test/java/com/gitblit/tests/GitBlitTest.java index d3913513..c01862fe 100644 --- a/src/test/java/com/gitblit/tests/GitBlitTest.java +++ b/src/test/java/com/gitblit/tests/GitBlitTest.java @@ -172,7 +172,7 @@ public class GitBlitTest extends GitblitUnitTest { @Test public void testAuthentication() throws Exception { - assertTrue(session().authenticate("admin", "admin".toCharArray()) != null); + assertTrue(authentication().authenticate("admin", "admin".toCharArray()) != null); } @Test diff --git a/src/test/java/com/gitblit/tests/GitblitUnitTest.java b/src/test/java/com/gitblit/tests/GitblitUnitTest.java index 500e9b9f..1885f127 100644 --- a/src/test/java/com/gitblit/tests/GitblitUnitTest.java +++ b/src/test/java/com/gitblit/tests/GitblitUnitTest.java @@ -22,7 +22,7 @@ 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.IAuthenticationManager; import com.gitblit.manager.IUserManager; import com.gitblit.servlet.GitblitContext; @@ -45,8 +45,8 @@ public class GitblitUnitTest extends org.junit.Assert { 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() { diff --git a/src/test/java/com/gitblit/tests/HtpasswdAuthenticationTest.java b/src/test/java/com/gitblit/tests/HtpasswdAuthenticationTest.java new file mode 100644 index 00000000..3b1d51e1 --- /dev/null +++ b/src/test/java/com/gitblit/tests/HtpasswdAuthenticationTest.java @@ -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()); + + 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); + } + +} diff --git a/src/test/java/com/gitblit/tests/HtpasswdUserServiceTest.java b/src/test/java/com/gitblit/tests/HtpasswdUserServiceTest.java deleted file mode 100644 index 282debb3..00000000 --- a/src/test/java/com/gitblit/tests/HtpasswdUserServiceTest.java +++ /dev/null @@ -1,569 +0,0 @@ -/* - * 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.HtpasswdUserService; -import com.gitblit.models.UserModel; -import com.gitblit.tests.mock.MemorySettings; -import com.gitblit.tests.mock.MockRuntimeManager; -import com.gitblit.utils.StringUtils; - -/** - * Test the Htpasswd user service. - * - */ -public class HtpasswdUserServiceTest extends GitblitUnitTest { - - private static final String RESOURCE_DIR = "src/test/resources/htpasswdUSTest/"; - 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()); - - private HtpasswdUserService htpwdUserService; - - - private MemorySettings getSettings( String userfile, String groupfile, Boolean overrideLA) - { - MS.put("realm.htpasswd.backingUserService", 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 MemorySettings getSettings(boolean overrideLA) - { - return getSettings(null, null, new Boolean(overrideLA)); - } - - - private void setupUS() - { - htpwdUserService = new HtpasswdUserService(); - htpwdUserService.setup(new MockRuntimeManager(getSettings())); - } - - private void setupUS(boolean overrideLA) - { - htpwdUserService = new HtpasswdUserService(); - htpwdUserService.setup(new MockRuntimeManager(getSettings(overrideLA))); - } - - - 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, htpwdUserService.getNumberHtpasswdUsers()); - } - - - @Test - public void testAuthenticate() - { - MS.put(KEY_SUPPORT_PLAINTEXT_PWD, "true"); - UserModel user = htpwdUserService.authenticate("user1", "pass1".toCharArray()); - assertNotNull(user); - assertEquals("user1", user.username); - - user = htpwdUserService.authenticate("user2", "pass2".toCharArray()); - assertNotNull(user); - assertEquals("user2", user.username); - - // Test different encryptions - user = htpwdUserService.authenticate("plain", "passWord".toCharArray()); - assertNotNull(user); - assertEquals("plain", user.username); - - MS.put(KEY_SUPPORT_PLAINTEXT_PWD, "false"); - user = htpwdUserService.authenticate("crypt", "password".toCharArray()); - assertNotNull(user); - assertEquals("crypt", user.username); - - user = htpwdUserService.authenticate("md5", "password".toCharArray()); - assertNotNull(user); - assertEquals("md5", user.username); - - user = htpwdUserService.authenticate("sha", "password".toCharArray()); - assertNotNull(user); - assertEquals("sha", user.username); - - - // Test leading and trailing whitespace - user = htpwdUserService.authenticate("trailing", "whitespace".toCharArray()); - assertNotNull(user); - assertEquals("trailing", user.username); - - user = htpwdUserService.authenticate("tabbed", "frontAndBack".toCharArray()); - assertNotNull(user); - assertEquals("tabbed", user.username); - - user = htpwdUserService.authenticate("leading", "whitespace".toCharArray()); - assertNotNull(user); - assertEquals("leading", user.username); - - - // Test local account - user = htpwdUserService.authenticate("admin", "admin".toCharArray()); - assertNotNull(user); - assertEquals("admin", user.username); - } - - - @Test - public void testAttributes() - { - MS.put(KEY_SUPPORT_PLAINTEXT_PWD, "true"); - UserModel user = htpwdUserService.authenticate("user1", "pass1".toCharArray()); - assertNotNull(user); - assertEquals("El Capitan", user.displayName); - assertEquals("cheffe@example.com", user.emailAddress); - assertTrue(user.canAdmin); - - user = htpwdUserService.authenticate("user2", "pass2".toCharArray()); - assertNotNull(user); - assertEquals("User Two", user.displayName); - assertTrue(user.canCreate); - assertTrue(user.canFork); - - - user = htpwdUserService.authenticate("admin", "admin".toCharArray()); - assertNotNull(user); - assertTrue(user.canAdmin); - - user = htpwdUserService.authenticate("staylocal", "localUser".toCharArray()); - assertNotNull(user); - assertEquals("Local User", user.displayName); - assertFalse(user.canCreate); - assertFalse(user.canFork); - assertFalse(user.canAdmin); - } - - - @Test - public void testAuthenticateDenied() - { - UserModel user = null; - MS.put(KEY_SUPPORT_PLAINTEXT_PWD, "true"); - user = htpwdUserService.authenticate("user1", "".toCharArray()); - assertNull("User 'user1' falsely authenticated.", user); - - user = htpwdUserService.authenticate("user1", "pass2".toCharArray()); - assertNull("User 'user1' falsely authenticated.", user); - - user = htpwdUserService.authenticate("user2", "lalala".toCharArray()); - assertNull("User 'user2' falsely authenticated.", user); - - - user = htpwdUserService.authenticate("user3", "disabled".toCharArray()); - assertNull("User 'user3' falsely authenticated.", user); - - user = htpwdUserService.authenticate("user4", "disabled".toCharArray()); - assertNull("User 'user4' falsely authenticated.", user); - - - user = htpwdUserService.authenticate("plain", "text".toCharArray()); - assertNull("User 'plain' falsely authenticated.", user); - - user = htpwdUserService.authenticate("plain", "password".toCharArray()); - assertNull("User 'plain' falsely authenticated.", user); - - - MS.put(KEY_SUPPORT_PLAINTEXT_PWD, "false"); - - user = htpwdUserService.authenticate("crypt", "".toCharArray()); - assertNull("User 'cyrpt' falsely authenticated.", user); - - user = htpwdUserService.authenticate("crypt", "passwd".toCharArray()); - assertNull("User 'crypt' falsely authenticated.", user); - - user = htpwdUserService.authenticate("md5", "".toCharArray()); - assertNull("User 'md5' falsely authenticated.", user); - - user = htpwdUserService.authenticate("md5", "pwd".toCharArray()); - assertNull("User 'md5' falsely authenticated.", user); - - user = htpwdUserService.authenticate("sha", "".toCharArray()); - assertNull("User 'sha' falsely authenticated.", user); - - user = htpwdUserService.authenticate("sha", "letmein".toCharArray()); - assertNull("User 'sha' falsely authenticated.", user); - - - user = htpwdUserService.authenticate(" tabbed", "frontAndBack".toCharArray()); - assertNull("User 'tabbed' falsely authenticated.", user); - - user = htpwdUserService.authenticate(" leading", "whitespace".toCharArray()); - assertNull("User 'leading' falsely authenticated.", user); - } - - - @Test - public void testNewLocalAccount() - { - UserModel newUser = new UserModel("newlocal"); - newUser.displayName = "Local User 2"; - newUser.password = StringUtils.MD5_TYPE + StringUtils.getMD5("localPwd2"); - assertTrue("Failed to add local account.", htpwdUserService.updateUserModel(newUser)); - - UserModel localAccount = htpwdUserService.authenticate(newUser.username, "localPwd2".toCharArray()); - assertNotNull(localAccount); - assertEquals(newUser, localAccount); - - localAccount = htpwdUserService.authenticate(newUser.username, "localPwd2".toCharArray()); - assertNotNull(localAccount); - assertEquals(newUser, localAccount); - - assertTrue("Failed to delete local account.", htpwdUserService.deleteUser(localAccount.username)); - assertNull(htpwdUserService.authenticate(newUser.username, "localPwd2".toCharArray())); - } - - - @Test - public void testCleartextIntrusion() - { - MS.put(KEY_SUPPORT_PLAINTEXT_PWD, "true"); - assertNull(htpwdUserService.authenticate("md5", "$apr1$qAGGNfli$sAn14mn.WKId/3EQS7KSX0".toCharArray())); - assertNull(htpwdUserService.authenticate("sha", "{SHA}W6ph5Mm5Pz8GgiULbPgzG37mj9g=".toCharArray())); - - assertNull(htpwdUserService.authenticate("user1", "#externalAccount".toCharArray())); - - MS.put(KEY_SUPPORT_PLAINTEXT_PWD, "false"); - assertNull(htpwdUserService.authenticate("md5", "$apr1$qAGGNfli$sAn14mn.WKId/3EQS7KSX0".toCharArray())); - assertNull(htpwdUserService.authenticate("sha", "{SHA}W6ph5Mm5Pz8GgiULbPgzG37mj9g=".toCharArray())); - - assertNull(htpwdUserService.authenticate("user1", "#externalAccount".toCharArray())); - } - - - @Test - public void testCryptVsPlaintext() - { - MS.put(KEY_SUPPORT_PLAINTEXT_PWD, "false"); - assertNull(htpwdUserService.authenticate("crypt", "6TmlbxqZ2kBIA".toCharArray())); - assertNotNull(htpwdUserService.authenticate("crypt", "password".toCharArray())); - - MS.put(KEY_SUPPORT_PLAINTEXT_PWD, "true"); - assertNotNull(htpwdUserService.authenticate("crypt", "6TmlbxqZ2kBIA".toCharArray())); - assertNull(htpwdUserService.authenticate("crypt", "password".toCharArray())); - } - - - /* - * Test case: User exists in user.conf with a local password and in htpasswd with an external password. - * If overrideLocalAuthentication is false, the local account takes precedence and is never updated. - */ - @Test - public void testPreparedAccountPreferLocal() throws IOException - { - setupUS(false); - - UserModel user = htpwdUserService.authenticate("leaderred", "localPassword".toCharArray()); - assertNotNull(user); - assertEquals("leaderred", user.getName()); - - user = htpwdUserService.authenticate("leaderred", "localPassword".toCharArray()); - assertNotNull(user); - assertEquals("leaderred", user.getName()); - - user = htpwdUserService.authenticate("leaderred", "externalPassword".toCharArray()); - assertNull(user); - - user = htpwdUserService.authenticate("staylocal", "localUser".toCharArray()); - assertNotNull(user); - assertEquals("staylocal", user.getName()); - - - deleteGeneratedFiles(); - copyInFiles(); - setupUS(false); - - user = htpwdUserService.authenticate("leaderred", "externalPassword".toCharArray()); - assertNull(user); - - user = htpwdUserService.authenticate("leaderred", "localPassword".toCharArray()); - assertNotNull(user); - assertEquals("leaderred", user.getName()); - - user = htpwdUserService.authenticate("leaderred", "localPassword".toCharArray()); - assertNotNull(user); - assertEquals("leaderred", user.getName()); - - user = htpwdUserService.authenticate("staylocal", "localUser".toCharArray()); - assertNotNull(user); - assertEquals("staylocal", user.getName()); - } - - - /* - * Test case: User exists in user.conf with a local password and in htpasswd with an external password. - * If overrideLocalAuthentication is true, the external account takes precedence, - * the initial local password is never used and discarded. - */ - @Test - public void testPreparedAccountPreferExternal() throws IOException - { - setupUS(true); - - UserModel user = htpwdUserService.authenticate("leaderred", "externalPassword".toCharArray()); - assertNotNull(user); - assertEquals("leaderred", user.getName()); - - user = htpwdUserService.authenticate("leaderred", "externalPassword".toCharArray()); - assertNotNull(user); - assertEquals("leaderred", user.getName()); - - user = htpwdUserService.authenticate("leaderred", "localPassword".toCharArray()); - assertNull(user); - - user = htpwdUserService.authenticate("staylocal", "localUser".toCharArray()); - assertNotNull(user); - assertEquals("staylocal", user.getName()); - - - deleteGeneratedFiles(); - copyInFiles(); - setupUS(true); - - - user = htpwdUserService.authenticate("leaderred", "localPassword".toCharArray()); - assertNull(user); - - user = htpwdUserService.authenticate("leaderred", "externalPassword".toCharArray()); - assertNotNull(user); - assertEquals("leaderred", user.getName()); - - user = htpwdUserService.authenticate("leaderred", "externalPassword".toCharArray()); - assertNotNull(user); - assertEquals("leaderred", user.getName()); - - user = htpwdUserService.authenticate("staylocal", "localUser".toCharArray()); - assertNotNull(user); - assertEquals("staylocal", user.getName()); - - // Make sure no authentication by using the string constant for external accounts is possible. - user = htpwdUserService.authenticate("leaderred", "#externalAccount".toCharArray()); - assertNull(user); - } - - - /* - * Test case: User exists in user.conf with a local password and in htpasswd with an external password. - * If overrideLocalAuthentication is true, the external account takes precedence, - * the initial local password is never used and discarded. - */ - @Test - public void testPreparedAccountChangeSetting() throws IOException - { - getSettings(false); - - UserModel user = htpwdUserService.authenticate("leaderred", "localPassword".toCharArray()); - assertNotNull(user); - assertEquals("leaderred", user.getName()); - - user = htpwdUserService.authenticate("leaderred", "externalPassword".toCharArray()); - assertNull(user); - - user = htpwdUserService.authenticate("staylocal", "localUser".toCharArray()); - assertNotNull(user); - assertEquals("staylocal", user.getName()); - - - getSettings(true); - - - user = htpwdUserService.authenticate("leaderred", "localPassword".toCharArray()); - assertNull(user); - - user = htpwdUserService.authenticate("leaderred", "externalPassword".toCharArray()); - assertNotNull(user); - assertEquals("leaderred", user.getName()); - - user = htpwdUserService.authenticate("staylocal", "localUser".toCharArray()); - assertNotNull(user); - assertEquals("staylocal", user.getName()); - - // Make sure no authentication by using the string constant for external accounts is possible. - user = htpwdUserService.authenticate("leaderred", "#externalAccount".toCharArray()); - assertNull(user); - - - getSettings(false); - // The preference is now back to local accounts but since the prepared account got switched - // to an external account, it will stay this way. - - user = htpwdUserService.authenticate("leaderred", "localPassword".toCharArray()); - assertNull(user); - - user = htpwdUserService.authenticate("leaderred", "externalPassword".toCharArray()); - assertNotNull(user); - assertEquals("leaderred", user.getName()); - - user = htpwdUserService.authenticate("staylocal", "localUser".toCharArray()); - assertNotNull(user); - assertEquals("staylocal", user.getName()); - - // Make sure no authentication by using the string constant for external accounts is possible. - user = htpwdUserService.authenticate("leaderred", "#externalAccount".toCharArray()); - assertNull(user); - } - - - @Test - public void testChangeHtpasswdFile() - { - UserModel user; - - // User default set up. - user = htpwdUserService.authenticate("md5", "password".toCharArray()); - assertNotNull(user); - assertEquals("md5", user.username); - - user = htpwdUserService.authenticate("sha", "password".toCharArray()); - assertNotNull(user); - assertEquals("sha", user.username); - - user = htpwdUserService.authenticate("blueone", "GoBlue!".toCharArray()); - assertNull(user); - - user = htpwdUserService.authenticate("bluetwo", "YayBlue!".toCharArray()); - assertNull(user); - - - // Switch to different htpasswd file. - getSettings(RESOURCE_DIR + "htpasswd-user", null, null); - - user = htpwdUserService.authenticate("md5", "password".toCharArray()); - assertNull(user); - - user = htpwdUserService.authenticate("sha", "password".toCharArray()); - assertNull(user); - - user = htpwdUserService.authenticate("blueone", "GoBlue!".toCharArray()); - assertNotNull(user); - assertEquals("blueone", user.username); - - user = htpwdUserService.authenticate("bluetwo", "YayBlue!".toCharArray()); - assertNotNull(user); - assertEquals("bluetwo", user.username); - } - - - @Test - public void testChangeHtpasswdFileNotExisting() - { - UserModel user; - - // User default set up. - user = htpwdUserService.authenticate("md5", "password".toCharArray()); - assertNotNull(user); - assertEquals("md5", user.username); - - user = htpwdUserService.authenticate("sha", "password".toCharArray()); - assertNotNull(user); - assertEquals("sha", user.username); - - user = htpwdUserService.authenticate("blueone", "GoBlue!".toCharArray()); - assertNull(user); - - user = htpwdUserService.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 = htpwdUserService.authenticate("md5", "password".toCharArray()); - assertNull(user); - - user = htpwdUserService.authenticate("sha", "password".toCharArray()); - assertNull(user); - - user = htpwdUserService.authenticate("blueone", "GoBlue!".toCharArray()); - assertNull(user); - - user = htpwdUserService.authenticate("bluetwo", "YayBlue!".toCharArray()); - assertNull(user); - } - -} diff --git a/src/test/java/com/gitblit/tests/LdapAuthenticationTest.java b/src/test/java/com/gitblit/tests/LdapAuthenticationTest.java new file mode 100644 index 00000000..3cd2dc72 --- /dev/null +++ b/src/test/java/com/gitblit/tests/LdapAuthenticationTest.java @@ -0,0 +1,165 @@ +/* + * 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.tests; + +import java.io.FileInputStream; +import java.util.HashMap; +import java.util.Map; + +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +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.unboundid.ldap.listener.InMemoryDirectoryServer; +import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig; +import com.unboundid.ldap.listener.InMemoryListenerConfig; +import com.unboundid.ldif.LDIFReader; + +/** + * An Integration test for LDAP that tests going against an in-memory UnboundID + * LDAP server. + * + * @author jcrygier + * + */ +public class LdapAuthenticationTest extends GitblitUnitTest { + + private static final String RESOURCE_DIR = "src/test/resources/ldap/"; + + private LdapAuthProvider ldap; + + static int ldapPort = 1389; + + @BeforeClass + public static void createInMemoryLdapServer() throws Exception { + InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig("dc=MyDomain"); + config.addAdditionalBindCredentials("cn=Directory Manager", "password"); + config.setListenerConfigs(InMemoryListenerConfig.createLDAPConfig("default", ldapPort)); + config.setSchema(null); + + InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config); + ds.importFromLDIF(true, new LDIFReader(new FileInputStream(RESOURCE_DIR + "sampledata.ldif"))); + ds.startListening(); + } + + @Before + 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 backingMap = new HashMap(); + 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"); + backingMap.put("realm.ldap.password", "password"); + backingMap.put("realm.ldap.backingUserService", "users.conf"); + backingMap.put("realm.ldap.maintainTeams", "true"); + backingMap.put("realm.ldap.accountBase", "OU=Users,OU=UserControl,OU=MyOrganization,DC=MyDomain"); + backingMap.put("realm.ldap.accountPattern", "(&(objectClass=person)(sAMAccountName=${username}))"); + backingMap.put("realm.ldap.groupBase", "OU=Groups,OU=UserControl,OU=MyOrganization,DC=MyDomain"); + backingMap.put("realm.ldap.groupPattern", "(&(objectClass=group)(member=${dn}))"); + backingMap.put("realm.ldap.admins", "UserThree @Git_Admins \"@Git Admins\""); + backingMap.put("realm.ldap.displayName", "displayName"); + backingMap.put("realm.ldap.email", "email"); + + MemorySettings ms = new MemorySettings(backingMap); + return ms; + } + + @Test + public void testAuthenticate() { + UserModel userOneModel = ldap.authenticate("UserOne", "userOnePassword".toCharArray()); + assertNotNull(userOneModel); + assertNotNull(userOneModel.getTeam("git_admins")); + assertNotNull(userOneModel.getTeam("git_users")); + assertTrue(userOneModel.canAdmin); + + UserModel userOneModelFailedAuth = ldap.authenticate("UserOne", "userTwoPassword".toCharArray()); + assertNull(userOneModelFailedAuth); + + 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 = ldap.authenticate("UserThree", "userThreePassword".toCharArray()); + assertNotNull(userThreeModel); + assertNotNull(userThreeModel.getTeam("git_users")); + assertNull(userThreeModel.getTeam("git_admins")); + assertTrue(userThreeModel.canAdmin); + } + + @Test + public void testDisplayName() { + 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}"); + ldap = newLdapAuthentication(ms); + + userOneModel = ldap.authenticate("UserOne", "userOnePassword".toCharArray()); + assertNotNull(userOneModel); + assertEquals("Mr. User One", userOneModel.displayName); + } + + @Test + public void testEmail() { + 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"); + ldap = newLdapAuthentication(ms); + + userOneModel = ldap.authenticate("UserOne", "userOnePassword".toCharArray()); + assertNotNull(userOneModel); + assertEquals("User.One@gitblit.com", userOneModel.emailAddress); + } + + @Test + public void testLdapInjection() { + // Inject so "(&(objectClass=person)(sAMAccountName=${username}))" becomes "(&(objectClass=person)(sAMAccountName=*)(userPassword=userOnePassword))" + // Thus searching by password + + UserModel userOneModel = ldap.authenticate("*)(userPassword=userOnePassword", "userOnePassword".toCharArray()); + assertNull(userOneModel); + } + +} diff --git a/src/test/java/com/gitblit/tests/LdapUserServiceTest.java b/src/test/java/com/gitblit/tests/LdapUserServiceTest.java deleted file mode 100644 index 8bd90147..00000000 --- a/src/test/java/com/gitblit/tests/LdapUserServiceTest.java +++ /dev/null @@ -1,170 +0,0 @@ -/* - * 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.tests; - -import java.util.HashMap; -import java.util.Map; - -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; - -import com.gitblit.LdapUserService; -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; -import com.unboundid.ldif.LDIFReader; - -/** - * An Integration test for LDAP that tests going against an in-memory UnboundID - * LDAP server. - * - * @author jcrygier - * - */ -public class LdapUserServiceTest extends GitblitUnitTest { - - private LdapUserService ldapUserService; - - static int ldapPort = 1389; - - @BeforeClass - public static void createInMemoryLdapServer() throws Exception { - InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig("dc=MyDomain"); - config.addAdditionalBindCredentials("cn=Directory Manager", "password"); - config.setListenerConfigs(InMemoryListenerConfig.createLDAPConfig("default", ldapPort)); - config.setSchema(null); - - InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config); - ds.importFromLDIF(true, new LDIFReader(LdapUserServiceTest.class.getResourceAsStream("resources/ldapUserServiceSampleData.ldif"))); - ds.startListening(); - } - - @Before - public void createLdapUserService() { - ldapUserService = new LdapUserService(); - ldapUserService.setup(new MockRuntimeManager(getSettings())); - } - - private MemorySettings getSettings() { - Map backingMap = new HashMap(); - backingMap.put("realm.ldap.server", "ldap://localhost:" + ldapPort); - backingMap.put("realm.ldap.domain", ""); - backingMap.put("realm.ldap.username", "cn=Directory Manager"); - backingMap.put("realm.ldap.password", "password"); - backingMap.put("realm.ldap.backingUserService", "users.conf"); - backingMap.put("realm.ldap.maintainTeams", "true"); - backingMap.put("realm.ldap.accountBase", "OU=Users,OU=UserControl,OU=MyOrganization,DC=MyDomain"); - backingMap.put("realm.ldap.accountPattern", "(&(objectClass=person)(sAMAccountName=${username}))"); - backingMap.put("realm.ldap.groupBase", "OU=Groups,OU=UserControl,OU=MyOrganization,DC=MyDomain"); - backingMap.put("realm.ldap.groupPattern", "(&(objectClass=group)(member=${dn}))"); - backingMap.put("realm.ldap.admins", "UserThree @Git_Admins \"@Git Admins\""); - backingMap.put("realm.ldap.displayName", "displayName"); - backingMap.put("realm.ldap.email", "email"); - - MemorySettings ms = new MemorySettings(backingMap); - return ms; - } - - @Test - public void testAuthenticate() { - UserModel userOneModel = ldapUserService.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()); - assertNull(userOneModelFailedAuth); - - UserModel userTwoModel = ldapUserService.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()); - assertNotNull(userThreeModel); - assertNotNull(userThreeModel.getTeam("git_users")); - assertNull(userThreeModel.getTeam("git_admins")); - assertTrue(userThreeModel.canAdmin); - } - - @Test - public void testDisplayName() { - UserModel userOneModel = ldapUserService.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)); - - userOneModel = ldapUserService.authenticate("UserOne", "userOnePassword".toCharArray()); - assertNotNull(userOneModel); - assertEquals("Mr. User One", userOneModel.displayName); - } - - @Test - public void testEmail() { - UserModel userOneModel = ldapUserService.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)); - - userOneModel = ldapUserService.authenticate("UserOne", "userOnePassword".toCharArray()); - assertNotNull(userOneModel); - assertEquals("User.One@gitblit.com", userOneModel.emailAddress); - } - - @Test - public void testLdapInjection() { - // Inject so "(&(objectClass=person)(sAMAccountName=${username}))" becomes "(&(objectClass=person)(sAMAccountName=*)(userPassword=userOnePassword))" - // Thus searching by password - - UserModel userOneModel = ldapUserService.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)); - } - -} diff --git a/src/test/java/com/gitblit/tests/RedmineAuthenticationTest.java b/src/test/java/com/gitblit/tests/RedmineAuthenticationTest.java new file mode 100644 index 00000000..1fe8459f --- /dev/null +++ b/src/test/java/com/gitblit/tests/RedmineAuthenticationTest.java @@ -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()); + } + + 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)); + } +} diff --git a/src/test/java/com/gitblit/tests/RedmineUserServiceTest.java b/src/test/java/com/gitblit/tests/RedmineUserServiceTest.java deleted file mode 100644 index 3c6769f0..00000000 --- a/src/test/java/com/gitblit/tests/RedmineUserServiceTest.java +++ /dev/null @@ -1,66 +0,0 @@ -package com.gitblit.tests; - -import static org.hamcrest.CoreMatchers.is; - -import org.junit.Test; - -import com.gitblit.RedmineUserService; -import com.gitblit.models.UserModel; -import com.gitblit.tests.mock.MockRuntimeManager; -import com.gitblit.utils.StringUtils; - -public class RedmineUserServiceTest 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\"}}"; - - @Test - public void testAuthenticate() throws Exception { - RedmineUserService redmineUserService = new RedmineUserService(); - redmineUserService.setup(new MockRuntimeManager()); - redmineUserService.setTestingCurrentUserAsJson(JSON); - UserModel userModel = redmineUserService.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 { - RedmineUserService redmineUserService = new RedmineUserService(); - redmineUserService.setup(new MockRuntimeManager()); - redmineUserService.setTestingCurrentUserAsJson(NOT_ADMIN_JSON); - UserModel userModel = redmineUserService.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)); - } - - @Test - public void testLocalAccount() { - RedmineUserService redmineUserService = new RedmineUserService(); - redmineUserService.setup(new MockRuntimeManager()); - - UserModel localAccount = new UserModel("bruce"); - localAccount.displayName = "Bruce Campbell"; - localAccount.password = StringUtils.MD5_TYPE + StringUtils.getMD5("gimmesomesugar"); - redmineUserService.deleteUser(localAccount.username); - assertTrue("Failed to add local account", - redmineUserService.updateUserModel(localAccount)); - assertEquals("Accounts are not equal!", - localAccount, - redmineUserService.authenticate(localAccount.username, "gimmesomesugar".toCharArray())); - assertTrue("Failed to delete local account!", - redmineUserService.deleteUser(localAccount.username)); - } - -} diff --git a/src/test/java/com/gitblit/tests/UserServiceTest.java b/src/test/java/com/gitblit/tests/UserServiceTest.java index 613e46df..cdb0a330 100644 --- a/src/test/java/com/gitblit/tests/UserServiceTest.java +++ b/src/test/java/com/gitblit/tests/UserServiceTest.java @@ -85,14 +85,9 @@ public class UserServiceTest extends GitblitUnitTest { 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 diff --git a/src/test/java/com/gitblit/tests/resources/ldapUserServiceSampleData.ldif b/src/test/java/com/gitblit/tests/resources/ldapUserServiceSampleData.ldif deleted file mode 100644 index df79333e..00000000 --- a/src/test/java/com/gitblit/tests/resources/ldapUserServiceSampleData.ldif +++ /dev/null @@ -1,108 +0,0 @@ -dn: DC=MyDomain -dc: MyDomain -objectClass: top -objectClass: domain - -dn: OU=MyOrganization,DC=MyDomain -objectClass: top -objectClass: organizationalUnit -ou: MyOrganization - -dn: OU=UserControl,OU=MyOrganization,DC=MyDomain -objectClass: top -objectClass: organizationalUnit -ou: UserControl - -dn: OU=Groups,OU=UserControl,OU=MyOrganization,DC=MyDomain -objectClass: top -objectClass: organizationalUnit -ou: Groups - -dn: CN=Git_Admins,OU=Groups,OU=UserControl,OU=MyOrganization,DC=MyDomain -objectClass: top -objectClass: group -cn: Git_Admins -sAMAccountName: Git_Admins -member: CN=UserOne,OU=US,OU=Users,OU=UserControl,OU=MyOrganization,DC=MyDomain - -dn: CN=Git Admins,OU=Groups,OU=UserControl,OU=MyOrganization,DC=MyDomain -objectClass: top -objectClass: group -cn: Git Admins -sAMAccountName: Git_Admins_With_Space -member: CN=UserTwo,OU=US,OU=Users,OU=UserControl,OU=MyOrganization,DC=MyDomain - -dn: CN=Git_Users,OU=Groups,OU=UserControl,OU=MyOrganization,DC=MyDomain -objectClass: top -objectClass: group -cn: Git_Users -sAMAccountName: Git_Users -member: CN=UserOne,OU=US,OU=Users,OU=UserControl,OU=MyOrganization,DC=MyDomain -member: CN=UserTwo,OU=US,OU=Users,OU=UserControl,OU=MyOrganization,DC=MyDomain -member: CN=UserThree,OU=Canada,OU=Users,OU=UserControl,OU=MyOrganization,DC=MyDomain -member: CN=UserFour,OU=Canada,OU=Users,OU=UserControl,OU=MyOrganization,DC=MyDomain - -dn: OU=Users,OU=UserControl,OU=MyOrganization,DC=MyDomain -objectClass: top -objectClass: organizationalUnit -ou: Users - -dn: OU=US,OU=Users,OU=UserControl,OU=MyOrganization,DC=MyDomain -objectClass: top -objectClass: organizationalUnit -ou: US - -dn: OU=Canada,OU=Users,OU=UserControl,OU=MyOrganization,DC=MyDomain -objectClass: top -objectClass: organizationalUnit -ou: Canada - -dn: CN=UserOne,OU=US,OU=Users,OU=UserControl,OU=MyOrganization,DC=MyDomain -objectClass: user -objectClass: person -sAMAccountName: UserOne -userPassword: userOnePassword -displayName: User One -givenName: User -surname: One -personalTitle: Mr -email: userone@gitblit.com -memberOf: CN=Git_Admins,OU=Groups,OU=UserControl,OU=MyOrganization,DC=MyDomain -memberOf: CN=Git_Users,OU=Groups,OU=UserControl,OU=MyOrganization,DC=MyDomain - -dn: CN=UserTwo,OU=US,OU=Users,OU=UserControl,OU=MyOrganization,DC=MyDomain -objectClass: user -objectClass: person -sAMAccountName: UserTwo -userPassword: userTwoPassword -displayName: User Two -givenName: User -surname: Two -personalTitle: Mr -email: usertwo@gitblit.com -memberOf: CN=Git_Users,OU=Groups,OU=UserControl,OU=MyOrganization,DC=MyDomain -memberOf: CN=Git Admins,OU=Groups,OU=UserControl,OU=MyOrganization,DC=MyDomain - -dn: CN=UserThree,OU=Canada,OU=Users,OU=UserControl,OU=MyOrganization,DC=MyDomain -objectClass: user -objectClass: person -sAMAccountName: UserThree -userPassword: userThreePassword -displayName: User Three -givenName: User -surname: Three -personalTitle: Mrs -email: userthree@gitblit.com -memberOf: CN=Git_Users,OU=Groups,OU=UserControl,OU=MyOrganization,DC=MyDomain - -dn: CN=UserFour,OU=Canada,OU=Users,OU=UserControl,OU=MyOrganization,DC=MyDomain -objectClass: user -objectClass: person -sAMAccountName: UserFour -userPassword: userFourPassword -displayName: User Four -givenName: User -surname: Four -personalTitle: Miss -email: userfour@gitblit.com -memberOf: CN=Git_Users,OU=Groups,OU=UserControl,OU=MyOrganization,DC=MyDomain \ No newline at end of file diff --git a/src/test/resources/htpasswd/htpasswd-user.in b/src/test/resources/htpasswd/htpasswd-user.in new file mode 100644 index 00000000..3ea87ede --- /dev/null +++ b/src/test/resources/htpasswd/htpasswd-user.in @@ -0,0 +1,15 @@ +# User database + +# htpasswd generated entries + +# Plaintext +redone:Yonder + +# Unix crypt() "GoRed!" +redtwo:RMghf6oG.QwAs + + # Apache MD5 "GoBlue!" +blueone:$apr1$phRTn/7N$237Owfhw5wZTdTyP9NPvC1 + +# SHA1 "YayBlue!" +bluetwo:{SHA}ITMvZI9OU5+Rx324C4jpf+MHAL8= diff --git a/src/test/resources/htpasswd/htpasswd.in b/src/test/resources/htpasswd/htpasswd.in new file mode 100644 index 00000000..f2900e70 --- /dev/null +++ b/src/test/resources/htpasswd/htpasswd.in @@ -0,0 +1,31 @@ +# User database + +user1:pass1 +user2:pass2 + +# "externalPassword" +leaderred:{SHA}2VZsTsVQYmWAMfQUjNAScpaAlJI= + +#user3:disabled + # user4:disabled + +# htpasswd generated entries + +# Plaintext +plain:passWord + +# Unix crypt() "password" +crypt:6TmlbxqZ2kBIA + + # Apache MD5 "password" +md5:$apr1$qAGGNfli$sAn14mn.WKId/3EQS7KSX0 + + +# SHA1 "password" +sha:{SHA}W6ph5Mm5Pz8GgiULbPgzG37mj9g= + + +trailing:.dAxRAQiOOlN. + + tabbed:$apr1$Is7zctsH$CMAXrGkgACQKgRYuQ5vHq. + leading:$apr1$O1nQtxjE$8gN15gMeuF3W1Nr8Yz/6J. diff --git a/src/test/resources/htpasswd/users.conf.in b/src/test/resources/htpasswd/users.conf.in new file mode 100644 index 00000000..142265a4 --- /dev/null +++ b/src/test/resources/htpasswd/users.conf.in @@ -0,0 +1,26 @@ +[user "admin"] + password = admin + cookie = dd94709528bb1c83d08f3088d4043f4742891f4f + role = "#admin" + role = "#notfederated" +[user "user1"] + password = "#externalAccount" + cookie = 6c7d13cf0aa43054d0fb620546e3a4d79e3d3e89 + displayName = El Capitan + emailAddress = cheffe@example.com + role = "#admin" +[user "user2"] + password = "#externalAccount" + cookie = d15eabb3a83c44a05ccbdaf3bf5fd1402d971e99 + displayName = User Two + role = "#create" + role = "#fork" +[user "staylocal"] + password = localUser + cookie = 0a99767e0259dc06ccae5ee6349177be289968f3 + displayName = Local User + role = "#none" +[user "leaderRed"] + password = localPassword + displayName = Red Leader + role = "#create" diff --git a/src/test/resources/htpasswdUSTest/htpasswd-user.in b/src/test/resources/htpasswdUSTest/htpasswd-user.in deleted file mode 100644 index 3ea87ede..00000000 --- a/src/test/resources/htpasswdUSTest/htpasswd-user.in +++ /dev/null @@ -1,15 +0,0 @@ -# User database - -# htpasswd generated entries - -# Plaintext -redone:Yonder - -# Unix crypt() "GoRed!" -redtwo:RMghf6oG.QwAs - - # Apache MD5 "GoBlue!" -blueone:$apr1$phRTn/7N$237Owfhw5wZTdTyP9NPvC1 - -# SHA1 "YayBlue!" -bluetwo:{SHA}ITMvZI9OU5+Rx324C4jpf+MHAL8= diff --git a/src/test/resources/htpasswdUSTest/htpasswd.in b/src/test/resources/htpasswdUSTest/htpasswd.in deleted file mode 100644 index f2900e70..00000000 --- a/src/test/resources/htpasswdUSTest/htpasswd.in +++ /dev/null @@ -1,31 +0,0 @@ -# User database - -user1:pass1 -user2:pass2 - -# "externalPassword" -leaderred:{SHA}2VZsTsVQYmWAMfQUjNAScpaAlJI= - -#user3:disabled - # user4:disabled - -# htpasswd generated entries - -# Plaintext -plain:passWord - -# Unix crypt() "password" -crypt:6TmlbxqZ2kBIA - - # Apache MD5 "password" -md5:$apr1$qAGGNfli$sAn14mn.WKId/3EQS7KSX0 - - -# SHA1 "password" -sha:{SHA}W6ph5Mm5Pz8GgiULbPgzG37mj9g= - - -trailing:.dAxRAQiOOlN. - - tabbed:$apr1$Is7zctsH$CMAXrGkgACQKgRYuQ5vHq. - leading:$apr1$O1nQtxjE$8gN15gMeuF3W1Nr8Yz/6J. diff --git a/src/test/resources/htpasswdUSTest/users.conf.in b/src/test/resources/htpasswdUSTest/users.conf.in deleted file mode 100644 index 142265a4..00000000 --- a/src/test/resources/htpasswdUSTest/users.conf.in +++ /dev/null @@ -1,26 +0,0 @@ -[user "admin"] - password = admin - cookie = dd94709528bb1c83d08f3088d4043f4742891f4f - role = "#admin" - role = "#notfederated" -[user "user1"] - password = "#externalAccount" - cookie = 6c7d13cf0aa43054d0fb620546e3a4d79e3d3e89 - displayName = El Capitan - emailAddress = cheffe@example.com - role = "#admin" -[user "user2"] - password = "#externalAccount" - cookie = d15eabb3a83c44a05ccbdaf3bf5fd1402d971e99 - displayName = User Two - role = "#create" - role = "#fork" -[user "staylocal"] - password = localUser - cookie = 0a99767e0259dc06ccae5ee6349177be289968f3 - displayName = Local User - role = "#none" -[user "leaderRed"] - password = localPassword - displayName = Red Leader - role = "#create" diff --git a/src/test/resources/ldap/sampledata.ldif b/src/test/resources/ldap/sampledata.ldif new file mode 100644 index 00000000..df79333e --- /dev/null +++ b/src/test/resources/ldap/sampledata.ldif @@ -0,0 +1,108 @@ +dn: DC=MyDomain +dc: MyDomain +objectClass: top +objectClass: domain + +dn: OU=MyOrganization,DC=MyDomain +objectClass: top +objectClass: organizationalUnit +ou: MyOrganization + +dn: OU=UserControl,OU=MyOrganization,DC=MyDomain +objectClass: top +objectClass: organizationalUnit +ou: UserControl + +dn: OU=Groups,OU=UserControl,OU=MyOrganization,DC=MyDomain +objectClass: top +objectClass: organizationalUnit +ou: Groups + +dn: CN=Git_Admins,OU=Groups,OU=UserControl,OU=MyOrganization,DC=MyDomain +objectClass: top +objectClass: group +cn: Git_Admins +sAMAccountName: Git_Admins +member: CN=UserOne,OU=US,OU=Users,OU=UserControl,OU=MyOrganization,DC=MyDomain + +dn: CN=Git Admins,OU=Groups,OU=UserControl,OU=MyOrganization,DC=MyDomain +objectClass: top +objectClass: group +cn: Git Admins +sAMAccountName: Git_Admins_With_Space +member: CN=UserTwo,OU=US,OU=Users,OU=UserControl,OU=MyOrganization,DC=MyDomain + +dn: CN=Git_Users,OU=Groups,OU=UserControl,OU=MyOrganization,DC=MyDomain +objectClass: top +objectClass: group +cn: Git_Users +sAMAccountName: Git_Users +member: CN=UserOne,OU=US,OU=Users,OU=UserControl,OU=MyOrganization,DC=MyDomain +member: CN=UserTwo,OU=US,OU=Users,OU=UserControl,OU=MyOrganization,DC=MyDomain +member: CN=UserThree,OU=Canada,OU=Users,OU=UserControl,OU=MyOrganization,DC=MyDomain +member: CN=UserFour,OU=Canada,OU=Users,OU=UserControl,OU=MyOrganization,DC=MyDomain + +dn: OU=Users,OU=UserControl,OU=MyOrganization,DC=MyDomain +objectClass: top +objectClass: organizationalUnit +ou: Users + +dn: OU=US,OU=Users,OU=UserControl,OU=MyOrganization,DC=MyDomain +objectClass: top +objectClass: organizationalUnit +ou: US + +dn: OU=Canada,OU=Users,OU=UserControl,OU=MyOrganization,DC=MyDomain +objectClass: top +objectClass: organizationalUnit +ou: Canada + +dn: CN=UserOne,OU=US,OU=Users,OU=UserControl,OU=MyOrganization,DC=MyDomain +objectClass: user +objectClass: person +sAMAccountName: UserOne +userPassword: userOnePassword +displayName: User One +givenName: User +surname: One +personalTitle: Mr +email: userone@gitblit.com +memberOf: CN=Git_Admins,OU=Groups,OU=UserControl,OU=MyOrganization,DC=MyDomain +memberOf: CN=Git_Users,OU=Groups,OU=UserControl,OU=MyOrganization,DC=MyDomain + +dn: CN=UserTwo,OU=US,OU=Users,OU=UserControl,OU=MyOrganization,DC=MyDomain +objectClass: user +objectClass: person +sAMAccountName: UserTwo +userPassword: userTwoPassword +displayName: User Two +givenName: User +surname: Two +personalTitle: Mr +email: usertwo@gitblit.com +memberOf: CN=Git_Users,OU=Groups,OU=UserControl,OU=MyOrganization,DC=MyDomain +memberOf: CN=Git Admins,OU=Groups,OU=UserControl,OU=MyOrganization,DC=MyDomain + +dn: CN=UserThree,OU=Canada,OU=Users,OU=UserControl,OU=MyOrganization,DC=MyDomain +objectClass: user +objectClass: person +sAMAccountName: UserThree +userPassword: userThreePassword +displayName: User Three +givenName: User +surname: Three +personalTitle: Mrs +email: userthree@gitblit.com +memberOf: CN=Git_Users,OU=Groups,OU=UserControl,OU=MyOrganization,DC=MyDomain + +dn: CN=UserFour,OU=Canada,OU=Users,OU=UserControl,OU=MyOrganization,DC=MyDomain +objectClass: user +objectClass: person +sAMAccountName: UserFour +userPassword: userFourPassword +displayName: User Four +givenName: User +surname: Four +personalTitle: Miss +email: userfour@gitblit.com +memberOf: CN=Git_Users,OU=Groups,OU=UserControl,OU=MyOrganization,DC=MyDomain \ No newline at end of file diff --git a/src/test/resources/ldap/users.conf b/src/test/resources/ldap/users.conf new file mode 100644 index 00000000..b4b4a6ea --- /dev/null +++ b/src/test/resources/ldap/users.conf @@ -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 -- cgit v1.2.3