diff options
author | James Moger <james.moger@gitblit.com> | 2013-11-24 23:18:50 -0500 |
---|---|---|
committer | James Moger <james.moger@gitblit.com> | 2013-11-29 11:05:51 -0500 |
commit | 04a98505a4ab8f48aee22800fcac193d9367d0ae (patch) | |
tree | eb05bc77eeafda1c5b7af9d7b5b27012065f7a98 /src/main | |
parent | f8f6aa4d07cdfaaf23e24bf9eaf0a5fb9b437dda (diff) | |
download | gitblit-04a98505a4ab8f48aee22800fcac193d9367d0ae.tar.gz gitblit-04a98505a4ab8f48aee22800fcac193d9367d0ae.zip |
Refactor user services and separate authentication (issue-281)
Change-Id: I336e005e02623fc5e11a4f8b4408bea5465a43fd
Diffstat (limited to 'src/main')
44 files changed, 2031 insertions, 2829 deletions
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);
@@ -125,60 +127,6 @@ public class ConfigUserService implements IUserService { }
/**
- * Does the user service support changes to credentials?
- *
- * @return true or false
- * @since 1.0.0
- */
- @Override
- public boolean supportsCredentialChanges() {
- return true;
- }
-
- /**
- * Does the user service support changes to user display name?
- *
- * @return true or false
- * @since 1.0.0
- */
- @Override
- public boolean supportsDisplayNameChanges() {
- return true;
- }
-
- /**
- * Does the user service support changes to user email address?
- *
- * @return true or false
- * @since 1.0.0
- */
- @Override
- public boolean supportsEmailAddressChanges() {
- return true;
- }
-
- /**
- * Does the user service support changes to team memberships?
- *
- * @return true or false
- * @since 1.0.0
- */
- @Override
- public boolean supportsTeamMembershipChanges() {
- return true;
- }
-
- /**
- * Does the user service support cookie authentication?
- *
- * @return true or false
- */
- @Override
- public boolean supportsCookies() {
- return true;
- }
-
- /**
* Returns the cookie value for the specified user.
*
* @param model
@@ -197,13 +145,13 @@ 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;
@@ -223,49 +171,6 @@ public class ConfigUserService implements IUserService { }
/**
- * 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.
*
* @param 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);
@@ -505,45 +414,6 @@ public class ConfigUserService implements IUserService { }
/**
- * Sets the list of all teams who are allowed to bypass the access
- * restriction placed on the specified repository.
- *
- * @param role
- * the repository name
- * @param teamnames
- * @return true if successful
- */
- @Override
- public synchronized boolean setTeamnamesForRepositoryRole(String role, List<String> teamnames) {
- try {
- Set<String> specifiedTeams = new HashSet<String>();
- for (String teamname : teamnames) {
- specifiedTeams.add(teamname.toLowerCase());
- }
-
- read();
-
- // identify teams which require add or remove role
- for (TeamModel team : teams.values()) {
- // team has role, check against revised team list
- if (specifiedTeams.contains(team.name.toLowerCase())) {
- team.addRepositoryPermission(role);
- } else {
- // remove role from team
- team.removeRepositoryPermission(role);
- }
- }
-
- // persist changes
- write();
- return true;
- } catch (Throwable t) {
- logger.error(MessageFormat.format("Failed to set teams for role {0}!", role), t);
- }
- return false;
- }
-
- /**
* Retrieve the team object for the specified team name.
*
* @param teamname
@@ -716,46 +586,6 @@ public class ConfigUserService implements IUserService { }
/**
- * Sets the list of all uses who are allowed to bypass the access
- * restriction placed on the specified repository.
- *
- * @param role
- * the repository name
- * @param usernames
- * @return true if successful
- */
- @Override
- @Deprecated
- public synchronized boolean setUsernamesForRepositoryRole(String role, List<String> usernames) {
- try {
- Set<String> specifiedUsers = new HashSet<String>();
- for (String username : usernames) {
- specifiedUsers.add(username.toLowerCase());
- }
-
- read();
-
- // identify users which require add or remove role
- for (UserModel user : users.values()) {
- // user has role, check against revised user list
- if (specifiedUsers.contains(user.username.toLowerCase())) {
- user.addRepositoryPermission(role);
- } else {
- // remove role from user
- user.removeRepositoryPermission(role);
- }
- }
-
- // persist changes
- write();
- return true;
- } catch (Throwable t) {
- logger.error(MessageFormat.format("Failed to set usernames for role {0}!", role), t);
- }
- return false;
- }
-
- /**
* Renames a repository role.
*
* @param oldRole
@@ -846,6 +676,9 @@ 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 @@ -321,11 +325,6 @@ public class GitBlit implements IRuntimeManager, } @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<UserModel> 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<String> getAllUsernames() {
- return serviceImpl.getAllUsernames();
- }
-
- @Override
- public List<UserModel> getAllUsers() {
- List<UserModel> users = serviceImpl.getAllUsers();
- for (UserModel user : users) {
- setAccountType(user);
- }
- return users;
- }
-
- @Override
- public List<String> getAllTeamNames() {
- return serviceImpl.getAllTeamNames();
- }
-
- @Override
- public List<TeamModel> getAllTeams() {
- return serviceImpl.getAllTeams();
- }
-
- @Override
- public List<String> getTeamNamesForRepositoryRole(String role) {
- return serviceImpl.getTeamNamesForRepositoryRole(role);
- }
-
- @Override
- @Deprecated
- public boolean setTeamnamesForRepositoryRole(String role, List<String> teamnames) {
- return serviceImpl.setTeamnamesForRepositoryRole(role, teamnames);
- }
-
- @Override
- public TeamModel getTeamModel(String teamname) {
- return serviceImpl.getTeamModel(teamname);
- }
-
- @Override
- public boolean updateTeamModel(TeamModel model) {
- return serviceImpl.updateTeamModel(model);
- }
-
- @Override
- public boolean updateTeamModels(Collection<TeamModel> models) {
- return serviceImpl.updateTeamModels(models);
- }
-
- @Override
- public boolean updateTeamModel(String teamname, TeamModel model) {
- if (!supportsTeamMembershipChanges()) {
- // teams are externally controlled - copy from original model
- TeamModel existingModel = getTeamModel(teamname);
-
- model = DeepCopier.copy(model);
- model.users.clear();
- model.users.addAll(existingModel.users);
- }
- return serviceImpl.updateTeamModel(teamname, model);
- }
-
- @Override
- public boolean deleteTeamModel(TeamModel model) {
- return serviceImpl.deleteTeamModel(model);
- }
-
- @Override
- public boolean deleteTeam(String teamname) {
- return serviceImpl.deleteTeam(teamname);
- }
-
- @Override
- public List<String> getUsernamesForRepositoryRole(String role) {
- return serviceImpl.getUsernamesForRepositoryRole(role);
- }
-
- @Override
- @Deprecated
- public boolean setUsernamesForRepositoryRole(String role, List<String> usernames) {
- return serviceImpl.setUsernamesForRepositoryRole(role, usernames);
- }
-
- @Override
- public boolean renameRepositoryRole(String oldRole, String newRole) {
- return serviceImpl.renameRepositoryRole(oldRole, newRole);
- }
-
- @Override
- public boolean deleteRepositoryRole(String role) {
- return serviceImpl.deleteRepositoryRole(role);
- }
-
- 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/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;
@@ -43,45 +42,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.
*
* @param model
@@ -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.
@@ -209,19 +153,6 @@ public interface IUserService { List<String> getTeamNamesForRepositoryRole(String role);
/**
- * Sets the list of all teams who are allowed to bypass the access
- * restriction placed on the specified repository.
- *
- * @param role
- * the repository name
- * @param teamnames
- * @return true if successful
- * @since 0.8.0
- */
- @Deprecated
- boolean setTeamnamesForRepositoryRole(String role, List<String> teamnames);
-
- /**
* Retrieve the team object for the specified team name.
*
* @param teamname
@@ -291,18 +222,6 @@ public interface IUserService { List<String> getUsernamesForRepositoryRole(String role);
/**
- * Sets the list of all uses who are allowed to bypass the access
- * restriction placed on the specified repository.
- *
- * @param role
- * the repository name
- * @param usernames
- * @return true if successful
- */
- @Deprecated
- boolean setUsernamesForRepositoryRole(String role, List<String> usernames);
-
- /**
* Renames a repository role.
*
* @param oldRole
@@ -320,14 +239,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/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/HtpasswdUserService.java b/src/main/java/com/gitblit/auth/HtpasswdAuthProvider.java index ca5295c9..559a0fa0 100644 --- a/src/main/java/com/gitblit/HtpasswdUserService.java +++ b/src/main/java/com/gitblit/auth/HtpasswdAuthProvider.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.gitblit; +package com.gitblit.auth; import java.io.File; import java.io.FileInputStream; @@ -29,11 +29,11 @@ import org.apache.commons.codec.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; import com.gitblit.Constants.AccountType; -import com.gitblit.manager.IRuntimeManager; +import com.gitblit.Keys; +import com.gitblit.auth.AuthenticationProvider.UsernamePasswordAuthenticationProvider; import com.gitblit.models.UserModel; import com.gitblit.utils.ArrayUtils; import com.gitblit.utils.StringUtils; @@ -64,52 +64,25 @@ import com.gitblit.utils.StringUtils; * @author Florian Zschocke * */ -public class HtpasswdUserService extends GitblitUserService -{ - - private static final String KEY_BACKING_US = Keys.realm.htpasswd.backingUserService; - private static final String DEFAULT_BACKING_US = "${baseFolder}/users.conf"; +public class HtpasswdAuthProvider extends UsernamePasswordAuthenticationProvider { private static final String KEY_HTPASSWD_FILE = Keys.realm.htpasswd.userfile; private static final String DEFAULT_HTPASSWD_FILE = "${baseFolder}/htpasswd"; - private static final String KEY_OVERRIDE_LOCALAUTH = Keys.realm.htpasswd.overrideLocalAuthentication; - private static final boolean DEFAULT_OVERRIDE_LOCALAUTH = true; - private static final String KEY_SUPPORT_PLAINTEXT_PWD = "realm.htpasswd.supportPlaintextPasswords"; - private final boolean SUPPORT_PLAINTEXT_PWD; + private boolean supportPlainTextPwd; - private IRuntimeManager runtimeManager; - private IStoredSettings settings; private File htpasswdFile; - - private final Logger logger = LoggerFactory.getLogger(HtpasswdUserService.class); - private final Map<String, String> htUsers = new ConcurrentHashMap<String, String>(); private volatile long lastModified; - private volatile boolean forceReload; - - - - public HtpasswdUserService() - { - super(); - - String os = System.getProperty("os.name").toLowerCase(); - if (os.startsWith("windows") || os.startsWith("netware")) { - SUPPORT_PLAINTEXT_PWD = true; - } - else { - SUPPORT_PLAINTEXT_PWD = false; - } + public HtpasswdAuthProvider() { + super("htpasswd"); } - - /** * Setup the user service. * @@ -118,42 +91,40 @@ public class HtpasswdUserService extends GitblitUserService * In addition the setup tries to read and parse the htpasswd file to be used * for authentication. * - * @param runtimeManager - * @since 1.4.0 + * @param settings + * @since 0.7.0 */ @Override - public void setup(IRuntimeManager runtimeManager) - { - this.runtimeManager = runtimeManager; - this.settings = runtimeManager.getSettings(); - - // This is done in two steps in order to avoid calling GitBlit.getFileOrFolder(String, String) which will segfault for unit tests. - String file = settings.getString(KEY_BACKING_US, DEFAULT_BACKING_US); - File realmFile = runtimeManager.getFileOrFolder(file); - serviceImpl = createUserService(realmFile); - logger.info("Htpasswd User Service backed by " + serviceImpl.toString()); - + public void setup() { + String os = System.getProperty("os.name").toLowerCase(); + if (os.startsWith("windows") || os.startsWith("netware")) { + supportPlainTextPwd = true; + } else { + supportPlainTextPwd = false; + } read(); - logger.debug("Read " + htUsers.size() + " users from htpasswd file: " + this.htpasswdFile); } - - - /** - * For now, credentials are defined in the htpasswd file and can not be manipulated - * from Gitblit. - * - * @return false - * @since 1.0.0 - */ @Override - public boolean supportsCredentialChanges() - { + public boolean supportsCredentialChanges() { return false; } + @Override + public boolean supportsDisplayNameChanges() { + return true; + } + + @Override + public boolean supportsEmailAddressChanges() { + return true; + } + @Override + public boolean supportsTeamMembershipChanges() { + return true; + } /** * Authenticate a user based on a username and password. @@ -168,14 +139,7 @@ public class HtpasswdUserService extends GitblitUserService * @return a user object or null */ @Override - public UserModel authenticate(String username, char[] password) - { - if (isLocalAccount(username)) { - // local account, bypass htpasswd authentication - return super.authenticate(username, password); - } - - + public UserModel authenticate(String username, char[] password) { read(); String storedPwd = htUsers.get(username); if (storedPwd != null) { @@ -183,27 +147,27 @@ public class HtpasswdUserService extends GitblitUserService final String passwd = new String(password); // test Apache MD5 variant encrypted password - if ( storedPwd.startsWith("$apr1$") ) { - if ( storedPwd.equals(Md5Crypt.apr1Crypt(passwd, storedPwd)) ) { + if (storedPwd.startsWith("$apr1$")) { + if (storedPwd.equals(Md5Crypt.apr1Crypt(passwd, storedPwd))) { logger.debug("Apache MD5 encoded password matched for user '" + username + "'"); authenticated = true; } } // test unsalted SHA password - else if ( storedPwd.startsWith("{SHA}") ) { + else if (storedPwd.startsWith("{SHA}")) { String passwd64 = Base64.encodeBase64String(DigestUtils.sha1(passwd)); - if ( storedPwd.substring("{SHA}".length()).equals(passwd64) ) { + if (storedPwd.substring("{SHA}".length()).equals(passwd64)) { logger.debug("Unsalted SHA-1 encoded password matched for user '" + username + "'"); authenticated = true; } } // test libc crypt() encoded password - else if ( supportCryptPwd() && storedPwd.equals(Crypt.crypt(passwd, storedPwd)) ) { + else if (supportCryptPwd() && storedPwd.equals(Crypt.crypt(passwd, storedPwd))) { logger.debug("Libc crypt encoded password matched for user '" + username + "'"); authenticated = true; } // test clear text - else if ( supportPlaintextPwd() && storedPwd.equals(passwd) ){ + else if (supportPlaintextPwd() && storedPwd.equals(passwd)){ logger.debug("Clear text password matched for user '" + username + "'"); authenticated = true; } @@ -212,10 +176,13 @@ public class HtpasswdUserService extends GitblitUserService if (authenticated) { logger.debug("Htpasswd authenticated: " + username); - UserModel user = getUserModel(username); - if (user == null) { + UserModel curr = userManager.getUserModel(username); + UserModel user; + if (curr == null) { // create user object for new authenticated user user = new UserModel(username); + } else { + user = curr; } // create a user cookie @@ -228,7 +195,7 @@ public class HtpasswdUserService extends GitblitUserService user.accountType = getAccountType(); // Push the looked up values to backing file - super.updateUserModel(user); + updateUser(user); return user; } @@ -237,70 +204,29 @@ public class HtpasswdUserService extends GitblitUserService return null; } - - - /** - * Determine if the account is to be treated as a local account. - * - * This influences authentication. A local account will be authenticated - * by the backing user service while an external account will be handled - * by this user service. - * <br/> - * The decision also depends on the setting of the key - * realm.htpasswd.overrideLocalAuthentication. - * If it is set to true, then passwords will first be checked against the - * htpasswd store. If an account exists and is marked as local in the backing - * user service, that setting will be overwritten by the result. This - * means that an account that looks local to the backing user service will - * be turned into an external account upon valid login of a user that has - * an entry in the htpasswd file. - * If the key is set to false, then it is determined if the account is local - * according to the logic of the GitblitUserService. - */ - @Override - protected boolean isLocalAccount(String username) - { - if ( settings.getBoolean(KEY_OVERRIDE_LOCALAUTH, DEFAULT_OVERRIDE_LOCALAUTH) ) { - read(); - if ( htUsers.containsKey(username) ) return false; - } - return super.isLocalAccount(username); - } - - - /** * Get the account type used for this user service. * * @return AccountType.HTPASSWD */ @Override - public AccountType getAccountType() - { + public AccountType getAccountType() { return AccountType.HTPASSWD; } - - - private String htpasswdFilePath = null; /** * Reads the realm file and rebuilds the in-memory lookup tables. */ - protected synchronized void read() - { - - // This is done in two steps in order to avoid calling GitBlit.getFileOrFolder(String, String) which will segfault for unit tests. - String file = settings.getString(KEY_HTPASSWD_FILE, DEFAULT_HTPASSWD_FILE); - if ( !file.equals(htpasswdFilePath) ) { - // The htpasswd file setting changed. Rediscover the file. - this.htpasswdFilePath = file; - this.htpasswdFile = runtimeManager.getFileOrFolder(file); + protected synchronized void read() { + boolean forceReload = false; + File file = getFileOrFolder(KEY_HTPASSWD_FILE, DEFAULT_HTPASSWD_FILE); + if (!file.equals(htpasswdFile)) { + this.htpasswdFile = file; this.htUsers.clear(); - this.forceReload = true; + forceReload = true; } if (htpasswdFile.exists() && (forceReload || (htpasswdFile.lastModified() != lastModified))) { - forceReload = false; lastModified = htpasswdFile.lastModified(); htUsers.clear(); @@ -309,53 +235,42 @@ public class HtpasswdUserService extends GitblitUserService Scanner scanner = null; try { scanner = new Scanner(new FileInputStream(htpasswdFile)); - while( scanner.hasNextLine()) { + while (scanner.hasNextLine()) { String line = scanner.nextLine().trim(); - if ( !line.isEmpty() && !line.startsWith("#") ) { + if (!line.isEmpty() && !line.startsWith("#")) { Matcher m = entry.matcher(line); - if ( m.matches() ) { + if (m.matches()) { htUsers.put(m.group(1), m.group(2)); } } } } catch (Exception e) { logger.error(MessageFormat.format("Failed to read {0}", htpasswdFile), e); - } - finally { - if (scanner != null) scanner.close(); + } finally { + if (scanner != null) { + scanner.close(); + } } } } - - - private boolean supportPlaintextPwd() - { - return this.settings.getBoolean(KEY_SUPPORT_PLAINTEXT_PWD, SUPPORT_PLAINTEXT_PWD); + private boolean supportPlaintextPwd() { + return this.settings.getBoolean(KEY_SUPPORT_PLAINTEXT_PWD, supportPlainTextPwd); } - - private boolean supportCryptPwd() - { + private boolean supportCryptPwd() { return !supportPlaintextPwd(); } - - - @Override - public String toString() - { - return getClass().getSimpleName() + "(" + ((htpasswdFile != null) ? htpasswdFile.getAbsolutePath() : "null") + ")"; - } - - - - /* * Method only used for unit tests. Return number of users read from htpasswd file. */ - public int getNumberHtpasswdUsers() - { + public int getNumberHtpasswdUsers() { return this.htUsers.size(); } + + @Override + public String toString() { + return getClass().getSimpleName() + "(" + ((htpasswdFile != null) ? htpasswdFile.getAbsolutePath() : "null") + ")"; + } } diff --git a/src/main/java/com/gitblit/LdapUserService.java b/src/main/java/com/gitblit/auth/LdapAuthProvider.java index c075afca..7a6b74df 100644 --- a/src/main/java/com/gitblit/LdapUserService.java +++ b/src/main/java/com/gitblit/auth/LdapAuthProvider.java @@ -1,530 +1,508 @@ -/*
- * Copyright 2012 John Crygier
- * Copyright 2012 gitblit.com
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.gitblit;
-
-import java.io.File;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.security.GeneralSecurityException;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicLong;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.gitblit.Constants.AccountType;
-import com.gitblit.manager.IRuntimeManager;
-import com.gitblit.models.TeamModel;
-import com.gitblit.models.UserModel;
-import com.gitblit.utils.ArrayUtils;
-import com.gitblit.utils.StringUtils;
-import com.unboundid.ldap.sdk.Attribute;
-import com.unboundid.ldap.sdk.DereferencePolicy;
-import com.unboundid.ldap.sdk.ExtendedResult;
-import com.unboundid.ldap.sdk.LDAPConnection;
-import com.unboundid.ldap.sdk.LDAPException;
-import com.unboundid.ldap.sdk.LDAPSearchException;
-import com.unboundid.ldap.sdk.ResultCode;
-import com.unboundid.ldap.sdk.SearchRequest;
-import com.unboundid.ldap.sdk.SearchResult;
-import com.unboundid.ldap.sdk.SearchResultEntry;
-import com.unboundid.ldap.sdk.SearchScope;
-import com.unboundid.ldap.sdk.SimpleBindRequest;
-import com.unboundid.ldap.sdk.extensions.StartTLSExtendedRequest;
-import com.unboundid.util.ssl.SSLUtil;
-import com.unboundid.util.ssl.TrustAllTrustManager;
-
-/**
- * Implementation of an LDAP user service.
- *
- * @author John Crygier
- */
-public class LdapUserService extends GitblitUserService {
-
- public static final Logger logger = LoggerFactory.getLogger(LdapUserService.class);
-
- private IStoredSettings settings;
- private AtomicLong lastLdapUserSync = new AtomicLong(0L);
-
- public LdapUserService() {
- super();
- }
-
- private long getSynchronizationPeriod() {
- final String cacheDuration = settings.getString(Keys.realm.ldap.ldapCachePeriod, "2 MINUTES");
- try {
- final String[] s = cacheDuration.split(" ", 2);
- long duration = Long.parseLong(s[0]);
- TimeUnit timeUnit = TimeUnit.valueOf(s[1]);
- return timeUnit.toMillis(duration);
- } catch (RuntimeException ex) {
- throw new IllegalArgumentException(Keys.realm.ldap.ldapCachePeriod + " must have format '<long> <TimeUnit>' where <TimeUnit> is one of 'MILLISECONDS', 'SECONDS', 'MINUTES', 'HOURS', 'DAYS'");
- }
- }
-
- @Override
- public void setup(IRuntimeManager runtimeManager) {
- this.settings = runtimeManager.getSettings();
- String file = settings.getString(Keys.realm.ldap.backingUserService, "${baseFolder}/users.conf");
- File realmFile = runtimeManager.getFileOrFolder(file);
-
- serviceImpl = createUserService(realmFile);
- logger.info("LDAP User Service backed by " + serviceImpl.toString());
-
- synchronizeLdapUsers();
- }
-
- protected synchronized void synchronizeLdapUsers() {
- final boolean enabled = settings.getBoolean(Keys.realm.ldap.synchronizeUsers.enable, false);
- if (enabled) {
- if (System.currentTimeMillis() > (lastLdapUserSync.get() + getSynchronizationPeriod())) {
- logger.info("Synchronizing with LDAP @ " + settings.getRequiredString(Keys.realm.ldap.server));
- final boolean deleteRemovedLdapUsers = settings.getBoolean(Keys.realm.ldap.synchronizeUsers.removeDeleted, true);
- LDAPConnection ldapConnection = getLdapConnection();
- if (ldapConnection != null) {
- try {
- String accountBase = settings.getString(Keys.realm.ldap.accountBase, "");
- String uidAttribute = settings.getString(Keys.realm.ldap.uid, "uid");
- String accountPattern = settings.getString(Keys.realm.ldap.accountPattern, "(&(objectClass=person)(sAMAccountName=${username}))");
- accountPattern = StringUtils.replace(accountPattern, "${username}", "*");
-
- SearchResult result = doSearch(ldapConnection, accountBase, accountPattern);
- if (result != null && result.getEntryCount() > 0) {
- final Map<String, UserModel> ldapUsers = new HashMap<String, UserModel>();
-
- for (SearchResultEntry loggingInUser : result.getSearchEntries()) {
-
- final String username = loggingInUser.getAttribute(uidAttribute).getValue();
- logger.debug("LDAP synchronizing: " + username);
-
- UserModel user = getUserModel(username);
- if (user == null) {
- user = new UserModel(username);
- }
-
- if (!supportsTeamMembershipChanges())
- getTeamsFromLdap(ldapConnection, username, loggingInUser, user);
-
- // Get User Attributes
- setUserAttributes(user, loggingInUser);
-
- // store in map
- ldapUsers.put(username.toLowerCase(), user);
- }
-
- if (deleteRemovedLdapUsers) {
- logger.debug("detecting removed LDAP users...");
-
- for (UserModel userModel : super.getAllUsers()) {
- if (Constants.EXTERNAL_ACCOUNT.equals(userModel.password)) {
- if (! ldapUsers.containsKey(userModel.username)) {
- logger.info("deleting removed LDAP user " + userModel.username + " from backing user service");
- super.deleteUser(userModel.username);
- }
- }
- }
- }
-
- super.updateUserModels(ldapUsers.values());
-
- if (!supportsTeamMembershipChanges()) {
- final Map<String, TeamModel> userTeams = new HashMap<String, TeamModel>();
- for (UserModel user : ldapUsers.values()) {
- for (TeamModel userTeam : user.teams) {
- userTeams.put(userTeam.name, userTeam);
- }
- }
- updateTeamModels(userTeams.values());
- }
- }
- lastLdapUserSync.set(System.currentTimeMillis());
- } finally {
- ldapConnection.close();
- }
- }
- }
- }
- }
-
- private LDAPConnection getLdapConnection() {
- try {
-
- URI ldapUrl = new URI(settings.getRequiredString(Keys.realm.ldap.server));
- String ldapHost = ldapUrl.getHost();
- int ldapPort = ldapUrl.getPort();
- String bindUserName = settings.getString(Keys.realm.ldap.username, "");
- String bindPassword = settings.getString(Keys.realm.ldap.password, "");
-
-
- LDAPConnection conn;
- if (ldapUrl.getScheme().equalsIgnoreCase("ldaps")) { // SSL
- SSLUtil sslUtil = new SSLUtil(new TrustAllTrustManager());
- conn = new LDAPConnection(sslUtil.createSSLSocketFactory());
- } else if (ldapUrl.getScheme().equalsIgnoreCase("ldap") || ldapUrl.getScheme().equalsIgnoreCase("ldap+tls")) { // no encryption or StartTLS
- conn = new LDAPConnection();
- } else {
- logger.error("Unsupported LDAP URL scheme: " + ldapUrl.getScheme());
- return null;
- }
-
- conn.connect(ldapHost, ldapPort);
-
- if (ldapUrl.getScheme().equalsIgnoreCase("ldap+tls")) {
- SSLUtil sslUtil = new SSLUtil(new TrustAllTrustManager());
- ExtendedResult extendedResult = conn.processExtendedOperation(
- new StartTLSExtendedRequest(sslUtil.createSSLContext()));
- if (extendedResult.getResultCode() != ResultCode.SUCCESS) {
- throw new LDAPException(extendedResult.getResultCode());
- }
- }
-
- if ( ! StringUtils.isEmpty(bindUserName) || ! StringUtils.isEmpty(bindPassword)) {
- conn.bind(new SimpleBindRequest(bindUserName, bindPassword));
- }
-
- return conn;
-
- } catch (URISyntaxException e) {
- logger.error("Bad LDAP URL, should be in the form: ldap(s|+tls)://<server>:<port>", e);
- } catch (GeneralSecurityException e) {
- logger.error("Unable to create SSL Connection", e);
- } catch (LDAPException e) {
- logger.error("Error Connecting to LDAP", e);
- }
-
- return null;
- }
-
- /**
- * Credentials are defined in the LDAP server and can not be manipulated
- * from Gitblit.
- *
- * @return false
- * @since 1.0.0
- */
- @Override
- public boolean supportsCredentialChanges() {
- return false;
- }
-
- /**
- * If no displayName pattern is defined then Gitblit can manage the display name.
- *
- * @return true if Gitblit can manage the user display name
- * @since 1.0.0
- */
- @Override
- public boolean supportsDisplayNameChanges() {
- return StringUtils.isEmpty(settings.getString(Keys.realm.ldap.displayName, ""));
- }
-
- /**
- * If no email pattern is defined then Gitblit can manage the email address.
- *
- * @return true if Gitblit can manage the user email address
- * @since 1.0.0
- */
- @Override
- public boolean supportsEmailAddressChanges() {
- return StringUtils.isEmpty(settings.getString(Keys.realm.ldap.email, ""));
- }
-
-
- /**
- * If the LDAP server will maintain team memberships then LdapUserService
- * will not allow team membership changes. In this scenario all team
- * changes must be made on the LDAP server by the LDAP administrator.
- *
- * @return true or false
- * @since 1.0.0
- */
- @Override
- public boolean supportsTeamMembershipChanges() {
- return !settings.getBoolean(Keys.realm.ldap.maintainTeams, false);
- }
-
- @Override
- public AccountType getAccountType() {
- return AccountType.LDAP;
- }
-
- @Override
- public UserModel authenticate(String username, char[] password) {
- if (isLocalAccount(username)) {
- // local account, bypass LDAP authentication
- return super.authenticate(username, password);
- }
-
- String simpleUsername = getSimpleUsername(username);
-
- LDAPConnection ldapConnection = getLdapConnection();
- if (ldapConnection != null) {
- try {
- // Find the logging in user's DN
- String accountBase = settings.getString(Keys.realm.ldap.accountBase, "");
- String accountPattern = settings.getString(Keys.realm.ldap.accountPattern, "(&(objectClass=person)(sAMAccountName=${username}))");
- accountPattern = StringUtils.replace(accountPattern, "${username}", escapeLDAPSearchFilter(simpleUsername));
-
- SearchResult result = doSearch(ldapConnection, accountBase, accountPattern);
- if (result != null && result.getEntryCount() == 1) {
- SearchResultEntry loggingInUser = result.getSearchEntries().get(0);
- String loggingInUserDN = loggingInUser.getDN();
-
- if (isAuthenticated(ldapConnection, loggingInUserDN, new String(password))) {
- logger.debug("LDAP authenticated: " + username);
-
- UserModel user = null;
- synchronized (this) {
- user = getUserModel(simpleUsername);
- if (user == null) // create user object for new authenticated user
- user = new UserModel(simpleUsername);
-
- // create a user cookie
- if (StringUtils.isEmpty(user.cookie) && !ArrayUtils.isEmpty(password)) {
- user.cookie = StringUtils.getSHA1(user.username + new String(password));
- }
-
- if (!supportsTeamMembershipChanges())
- getTeamsFromLdap(ldapConnection, simpleUsername, loggingInUser, user);
-
- // Get User Attributes
- setUserAttributes(user, loggingInUser);
-
- // Push the ldap looked up values to backing file
- super.updateUserModel(user);
- if (!supportsTeamMembershipChanges()) {
- for (TeamModel userTeam : user.teams)
- updateTeamModel(userTeam);
- }
- }
-
- return user;
- }
- }
- } finally {
- ldapConnection.close();
- }
- }
- return null;
- }
-
- /**
- * Set the admin attribute from team memberships retrieved from LDAP.
- * If we are not storing teams in LDAP and/or we have not defined any
- * administrator teams, then do not change the admin flag.
- *
- * @param user
- */
- private void setAdminAttribute(UserModel user) {
- if (!supportsTeamMembershipChanges()) {
- List<String> admins = settings.getStrings(Keys.realm.ldap.admins);
- // if we have defined administrative teams, then set admin flag
- // otherwise leave admin flag unchanged
- if (!ArrayUtils.isEmpty(admins)) {
- user.canAdmin = false;
- for (String admin : admins) {
- if (admin.startsWith("@")) { // Team
- if (user.getTeam(admin.substring(1)) != null)
- user.canAdmin = true;
- } else
- if (user.getName().equalsIgnoreCase(admin))
- user.canAdmin = true;
- }
- }
- }
- }
-
- private void setUserAttributes(UserModel user, SearchResultEntry userEntry) {
- // Is this user an admin?
- setAdminAttribute(user);
-
- // Don't want visibility into the real password, make up a dummy
- user.password = Constants.EXTERNAL_ACCOUNT;
- user.accountType = getAccountType();
-
- // Get full name Attribute
- String displayName = settings.getString(Keys.realm.ldap.displayName, "");
- if (!StringUtils.isEmpty(displayName)) {
- // Replace embedded ${} with attributes
- if (displayName.contains("${")) {
- for (Attribute userAttribute : userEntry.getAttributes())
- displayName = StringUtils.replace(displayName, "${" + userAttribute.getName() + "}", userAttribute.getValue());
-
- user.displayName = displayName;
- } else {
- Attribute attribute = userEntry.getAttribute(displayName);
- if (attribute != null && attribute.hasValue()) {
- user.displayName = attribute.getValue();
- }
- }
- }
-
- // Get email address Attribute
- String email = settings.getString(Keys.realm.ldap.email, "");
- if (!StringUtils.isEmpty(email)) {
- if (email.contains("${")) {
- for (Attribute userAttribute : userEntry.getAttributes())
- email = StringUtils.replace(email, "${" + userAttribute.getName() + "}", userAttribute.getValue());
-
- user.emailAddress = email;
- } else {
- Attribute attribute = userEntry.getAttribute(email);
- if (attribute != null && attribute.hasValue()) {
- user.emailAddress = attribute.getValue();
- }
- }
- }
- }
-
- private void getTeamsFromLdap(LDAPConnection ldapConnection, String simpleUsername, SearchResultEntry loggingInUser, UserModel user) {
- String loggingInUserDN = loggingInUser.getDN();
-
- user.teams.clear(); // Clear the users team memberships - we're going to get them from LDAP
- String groupBase = settings.getString(Keys.realm.ldap.groupBase, "");
- String groupMemberPattern = settings.getString(Keys.realm.ldap.groupMemberPattern, "(&(objectClass=group)(member=${dn}))");
-
- groupMemberPattern = StringUtils.replace(groupMemberPattern, "${dn}", escapeLDAPSearchFilter(loggingInUserDN));
- groupMemberPattern = StringUtils.replace(groupMemberPattern, "${username}", escapeLDAPSearchFilter(simpleUsername));
-
- // Fill in attributes into groupMemberPattern
- for (Attribute userAttribute : loggingInUser.getAttributes())
- groupMemberPattern = StringUtils.replace(groupMemberPattern, "${" + userAttribute.getName() + "}", escapeLDAPSearchFilter(userAttribute.getValue()));
-
- SearchResult teamMembershipResult = doSearch(ldapConnection, groupBase, true, groupMemberPattern, Arrays.asList("cn"));
- if (teamMembershipResult != null && teamMembershipResult.getEntryCount() > 0) {
- for (int i = 0; i < teamMembershipResult.getEntryCount(); i++) {
- SearchResultEntry teamEntry = teamMembershipResult.getSearchEntries().get(i);
- String teamName = teamEntry.getAttribute("cn").getValue();
-
- TeamModel teamModel = getTeamModel(teamName);
- if (teamModel == null)
- teamModel = createTeamFromLdap(teamEntry);
-
- user.teams.add(teamModel);
- teamModel.addUser(user.getName());
- }
- }
- }
-
- private TeamModel createTeamFromLdap(SearchResultEntry teamEntry) {
- TeamModel answer = new TeamModel(teamEntry.getAttributeValue("cn"));
- // potentially retrieve other attributes here in the future
-
- return answer;
- }
-
- private SearchResult doSearch(LDAPConnection ldapConnection, String base, String filter) {
- try {
- return ldapConnection.search(base, SearchScope.SUB, filter);
- } catch (LDAPSearchException e) {
- logger.error("Problem Searching LDAP", e);
-
- return null;
- }
- }
-
- private SearchResult doSearch(LDAPConnection ldapConnection, String base, boolean dereferenceAliases, String filter, List<String> attributes) {
- try {
- SearchRequest searchRequest = new SearchRequest(base, SearchScope.SUB, filter);
- if ( dereferenceAliases ) {
- searchRequest.setDerefPolicy(DereferencePolicy.SEARCHING);
- }
- if (attributes != null) {
- searchRequest.setAttributes(attributes);
- }
- return ldapConnection.search(searchRequest);
-
- } catch (LDAPSearchException e) {
- logger.error("Problem Searching LDAP", e);
-
- return null;
- } catch (LDAPException e) {
- logger.error("Problem creating LDAP search", e);
- return null;
- }
- }
-
- private boolean isAuthenticated(LDAPConnection ldapConnection, String userDn, String password) {
- try {
- // Binding will stop any LDAP-Injection Attacks since the searched-for user needs to bind to that DN
- ldapConnection.bind(userDn, password);
- return true;
- } catch (LDAPException e) {
- logger.error("Error authenticating user", e);
- return false;
- }
- }
-
- @Override
- public List<String> getAllUsernames() {
- synchronizeLdapUsers();
- return super.getAllUsernames();
- }
-
- @Override
- public List<UserModel> getAllUsers() {
- synchronizeLdapUsers();
- return super.getAllUsers();
- }
-
- /**
- * Returns a simple username without any domain prefixes.
- *
- * @param username
- * @return a simple username
- */
- protected String getSimpleUsername(String username) {
- int lastSlash = username.lastIndexOf('\\');
- if (lastSlash > -1) {
- username = username.substring(lastSlash + 1);
- }
-
- return username;
- }
-
- // From: https://www.owasp.org/index.php/Preventing_LDAP_Injection_in_Java
- public static final String escapeLDAPSearchFilter(String filter) {
- StringBuilder sb = new StringBuilder();
- for (int i = 0; i < filter.length(); i++) {
- char curChar = filter.charAt(i);
- switch (curChar) {
- case '\\':
- sb.append("\\5c");
- break;
- case '*':
- sb.append("\\2a");
- break;
- case '(':
- sb.append("\\28");
- break;
- case ')':
- sb.append("\\29");
- break;
- case '\u0000':
- sb.append("\\00");
- break;
- default:
- sb.append(curChar);
- }
- }
- return sb.toString();
- }
-}
+/* + * Copyright 2012 John Crygier + * Copyright 2012 gitblit.com + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.gitblit.auth; + +import java.net.URI; +import java.net.URISyntaxException; +import java.security.GeneralSecurityException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; + +import com.gitblit.Constants; +import com.gitblit.Constants.AccountType; +import com.gitblit.Keys; +import com.gitblit.auth.AuthenticationProvider.UsernamePasswordAuthenticationProvider; +import com.gitblit.models.TeamModel; +import com.gitblit.models.UserModel; +import com.gitblit.utils.ArrayUtils; +import com.gitblit.utils.StringUtils; +import com.unboundid.ldap.sdk.Attribute; +import com.unboundid.ldap.sdk.DereferencePolicy; +import com.unboundid.ldap.sdk.ExtendedResult; +import com.unboundid.ldap.sdk.LDAPConnection; +import com.unboundid.ldap.sdk.LDAPException; +import com.unboundid.ldap.sdk.LDAPSearchException; +import com.unboundid.ldap.sdk.ResultCode; +import com.unboundid.ldap.sdk.SearchRequest; +import com.unboundid.ldap.sdk.SearchResult; +import com.unboundid.ldap.sdk.SearchResultEntry; +import com.unboundid.ldap.sdk.SearchScope; +import com.unboundid.ldap.sdk.SimpleBindRequest; +import com.unboundid.ldap.sdk.extensions.StartTLSExtendedRequest; +import com.unboundid.util.ssl.SSLUtil; +import com.unboundid.util.ssl.TrustAllTrustManager; + +/** + * Implementation of an LDAP user service. + * + * @author John Crygier + */ +public class LdapAuthProvider extends UsernamePasswordAuthenticationProvider { + + private AtomicLong lastLdapUserSync = new AtomicLong(0L); + + public LdapAuthProvider() { + super("ldap"); + } + + private long getSynchronizationPeriod() { + final String cacheDuration = settings.getString(Keys.realm.ldap.ldapCachePeriod, "2 MINUTES"); + try { + final String[] s = cacheDuration.split(" ", 2); + long duration = Long.parseLong(s[0]); + TimeUnit timeUnit = TimeUnit.valueOf(s[1]); + return timeUnit.toMillis(duration); + } catch (RuntimeException ex) { + throw new IllegalArgumentException(Keys.realm.ldap.ldapCachePeriod + " must have format '<long> <TimeUnit>' where <TimeUnit> is one of 'MILLISECONDS', 'SECONDS', 'MINUTES', 'HOURS', 'DAYS'"); + } + } + + @Override + public void setup() { + synchronizeLdapUsers(); + } + + protected synchronized void synchronizeLdapUsers() { + final boolean enabled = settings.getBoolean(Keys.realm.ldap.synchronizeUsers.enable, false); + if (enabled) { + if (System.currentTimeMillis() > (lastLdapUserSync.get() + getSynchronizationPeriod())) { + logger.info("Synchronizing with LDAP @ " + settings.getRequiredString(Keys.realm.ldap.server)); + final boolean deleteRemovedLdapUsers = settings.getBoolean(Keys.realm.ldap.synchronizeUsers.removeDeleted, true); + LDAPConnection ldapConnection = getLdapConnection(); + if (ldapConnection != null) { + try { + String accountBase = settings.getString(Keys.realm.ldap.accountBase, ""); + String uidAttribute = settings.getString(Keys.realm.ldap.uid, "uid"); + String accountPattern = settings.getString(Keys.realm.ldap.accountPattern, "(&(objectClass=person)(sAMAccountName=${username}))"); + accountPattern = StringUtils.replace(accountPattern, "${username}", "*"); + + SearchResult result = doSearch(ldapConnection, accountBase, accountPattern); + if (result != null && result.getEntryCount() > 0) { + final Map<String, UserModel> ldapUsers = new HashMap<String, UserModel>(); + + for (SearchResultEntry loggingInUser : result.getSearchEntries()) { + + final String username = loggingInUser.getAttribute(uidAttribute).getValue(); + logger.debug("LDAP synchronizing: " + username); + + UserModel user = userManager.getUserModel(username); + if (user == null) { + user = new UserModel(username); + } + + if (!supportsTeamMembershipChanges()) { + getTeamsFromLdap(ldapConnection, username, loggingInUser, user); + } + + // Get User Attributes + setUserAttributes(user, loggingInUser); + + // store in map + ldapUsers.put(username.toLowerCase(), user); + } + + if (deleteRemovedLdapUsers) { + logger.debug("detecting removed LDAP users..."); + + for (UserModel userModel : userManager.getAllUsers()) { + if (Constants.EXTERNAL_ACCOUNT.equals(userModel.password)) { + if (!ldapUsers.containsKey(userModel.username)) { + logger.info("deleting removed LDAP user " + userModel.username + " from user service"); + userManager.deleteUser(userModel.username); + } + } + } + } + + userManager.updateUserModels(ldapUsers.values()); + + if (!supportsTeamMembershipChanges()) { + final Map<String, TeamModel> userTeams = new HashMap<String, TeamModel>(); + for (UserModel user : ldapUsers.values()) { + for (TeamModel userTeam : user.teams) { + userTeams.put(userTeam.name, userTeam); + } + } + userManager.updateTeamModels(userTeams.values()); + } + } + lastLdapUserSync.set(System.currentTimeMillis()); + } finally { + ldapConnection.close(); + } + } + } + } + } + + private LDAPConnection getLdapConnection() { + try { + + URI ldapUrl = new URI(settings.getRequiredString(Keys.realm.ldap.server)); + String ldapHost = ldapUrl.getHost(); + int ldapPort = ldapUrl.getPort(); + String bindUserName = settings.getString(Keys.realm.ldap.username, ""); + String bindPassword = settings.getString(Keys.realm.ldap.password, ""); + + + LDAPConnection conn; + if (ldapUrl.getScheme().equalsIgnoreCase("ldaps")) { + // SSL + SSLUtil sslUtil = new SSLUtil(new TrustAllTrustManager()); + conn = new LDAPConnection(sslUtil.createSSLSocketFactory()); + } else if (ldapUrl.getScheme().equalsIgnoreCase("ldap") || ldapUrl.getScheme().equalsIgnoreCase("ldap+tls")) { + // no encryption or StartTLS + conn = new LDAPConnection(); + } else { + logger.error("Unsupported LDAP URL scheme: " + ldapUrl.getScheme()); + return null; + } + + conn.connect(ldapHost, ldapPort); + + if (ldapUrl.getScheme().equalsIgnoreCase("ldap+tls")) { + SSLUtil sslUtil = new SSLUtil(new TrustAllTrustManager()); + ExtendedResult extendedResult = conn.processExtendedOperation( + new StartTLSExtendedRequest(sslUtil.createSSLContext())); + if (extendedResult.getResultCode() != ResultCode.SUCCESS) { + throw new LDAPException(extendedResult.getResultCode()); + } + } + + if (!StringUtils.isEmpty(bindUserName) || !StringUtils.isEmpty(bindPassword)) { + conn.bind(new SimpleBindRequest(bindUserName, bindPassword)); + } + + return conn; + + } catch (URISyntaxException e) { + logger.error("Bad LDAP URL, should be in the form: ldap(s|+tls)://<server>:<port>", e); + } catch (GeneralSecurityException e) { + logger.error("Unable to create SSL Connection", e); + } catch (LDAPException e) { + logger.error("Error Connecting to LDAP", e); + } + + return null; + } + + /** + * Credentials are defined in the LDAP server and can not be manipulated + * from Gitblit. + * + * @return false + * @since 1.0.0 + */ + @Override + public boolean supportsCredentialChanges() { + return false; + } + + /** + * If no displayName pattern is defined then Gitblit can manage the display name. + * + * @return true if Gitblit can manage the user display name + * @since 1.0.0 + */ + @Override + public boolean supportsDisplayNameChanges() { + return StringUtils.isEmpty(settings.getString(Keys.realm.ldap.displayName, "")); + } + + /** + * If no email pattern is defined then Gitblit can manage the email address. + * + * @return true if Gitblit can manage the user email address + * @since 1.0.0 + */ + @Override + public boolean supportsEmailAddressChanges() { + return StringUtils.isEmpty(settings.getString(Keys.realm.ldap.email, "")); + } + + + /** + * If the LDAP server will maintain team memberships then LdapUserService + * will not allow team membership changes. In this scenario all team + * changes must be made on the LDAP server by the LDAP administrator. + * + * @return true or false + * @since 1.0.0 + */ + @Override + public boolean supportsTeamMembershipChanges() { + return !settings.getBoolean(Keys.realm.ldap.maintainTeams, false); + } + + @Override + public AccountType getAccountType() { + return AccountType.LDAP; + } + + @Override + public UserModel authenticate(String username, char[] password) { + String simpleUsername = getSimpleUsername(username); + + LDAPConnection ldapConnection = getLdapConnection(); + if (ldapConnection != null) { + try { + // Find the logging in user's DN + String accountBase = settings.getString(Keys.realm.ldap.accountBase, ""); + String accountPattern = settings.getString(Keys.realm.ldap.accountPattern, "(&(objectClass=person)(sAMAccountName=${username}))"); + accountPattern = StringUtils.replace(accountPattern, "${username}", escapeLDAPSearchFilter(simpleUsername)); + + SearchResult result = doSearch(ldapConnection, accountBase, accountPattern); + if (result != null && result.getEntryCount() == 1) { + SearchResultEntry loggingInUser = result.getSearchEntries().get(0); + String loggingInUserDN = loggingInUser.getDN(); + + if (isAuthenticated(ldapConnection, loggingInUserDN, new String(password))) { + logger.debug("LDAP authenticated: " + username); + + UserModel user = null; + synchronized (this) { + user = userManager.getUserModel(simpleUsername); + if (user == null) // create user object for new authenticated user + user = new UserModel(simpleUsername); + + // create a user cookie + if (StringUtils.isEmpty(user.cookie) && !ArrayUtils.isEmpty(password)) { + user.cookie = StringUtils.getSHA1(user.username + new String(password)); + } + + if (!supportsTeamMembershipChanges()) + getTeamsFromLdap(ldapConnection, simpleUsername, loggingInUser, user); + + // Get User Attributes + setUserAttributes(user, loggingInUser); + + // Push the ldap looked up values to backing file + updateUser(user); + + if (!supportsTeamMembershipChanges()) { + for (TeamModel userTeam : user.teams) + updateTeam(userTeam); + } + } + + return user; + } + } + } finally { + ldapConnection.close(); + } + } + return null; + } + + /** + * Set the admin attribute from team memberships retrieved from LDAP. + * If we are not storing teams in LDAP and/or we have not defined any + * administrator teams, then do not change the admin flag. + * + * @param user + */ + private void setAdminAttribute(UserModel user) { + if (!supportsTeamMembershipChanges()) { + List<String> admins = settings.getStrings(Keys.realm.ldap.admins); + // if we have defined administrative teams, then set admin flag + // otherwise leave admin flag unchanged + if (!ArrayUtils.isEmpty(admins)) { + user.canAdmin = false; + for (String admin : admins) { + if (admin.startsWith("@")) { // Team + if (user.getTeam(admin.substring(1)) != null) + user.canAdmin = true; + } else + if (user.getName().equalsIgnoreCase(admin)) + user.canAdmin = true; + } + } + } + } + + private void setUserAttributes(UserModel user, SearchResultEntry userEntry) { + // Is this user an admin? + setAdminAttribute(user); + + // Don't want visibility into the real password, make up a dummy + user.password = Constants.EXTERNAL_ACCOUNT; + user.accountType = getAccountType(); + + // Get full name Attribute + String displayName = settings.getString(Keys.realm.ldap.displayName, ""); + if (!StringUtils.isEmpty(displayName)) { + // Replace embedded ${} with attributes + if (displayName.contains("${")) { + for (Attribute userAttribute : userEntry.getAttributes()) + displayName = StringUtils.replace(displayName, "${" + userAttribute.getName() + "}", userAttribute.getValue()); + + user.displayName = displayName; + } else { + Attribute attribute = userEntry.getAttribute(displayName); + if (attribute != null && attribute.hasValue()) { + user.displayName = attribute.getValue(); + } + } + } + + // Get email address Attribute + String email = settings.getString(Keys.realm.ldap.email, ""); + if (!StringUtils.isEmpty(email)) { + if (email.contains("${")) { + for (Attribute userAttribute : userEntry.getAttributes()) + email = StringUtils.replace(email, "${" + userAttribute.getName() + "}", userAttribute.getValue()); + + user.emailAddress = email; + } else { + Attribute attribute = userEntry.getAttribute(email); + if (attribute != null && attribute.hasValue()) { + user.emailAddress = attribute.getValue(); + } + } + } + } + + private void getTeamsFromLdap(LDAPConnection ldapConnection, String simpleUsername, SearchResultEntry loggingInUser, UserModel user) { + String loggingInUserDN = loggingInUser.getDN(); + + user.teams.clear(); // Clear the users team memberships - we're going to get them from LDAP + String groupBase = settings.getString(Keys.realm.ldap.groupBase, ""); + String groupMemberPattern = settings.getString(Keys.realm.ldap.groupMemberPattern, "(&(objectClass=group)(member=${dn}))"); + + groupMemberPattern = StringUtils.replace(groupMemberPattern, "${dn}", escapeLDAPSearchFilter(loggingInUserDN)); + groupMemberPattern = StringUtils.replace(groupMemberPattern, "${username}", escapeLDAPSearchFilter(simpleUsername)); + + // Fill in attributes into groupMemberPattern + for (Attribute userAttribute : loggingInUser.getAttributes()) { + groupMemberPattern = StringUtils.replace(groupMemberPattern, "${" + userAttribute.getName() + "}", escapeLDAPSearchFilter(userAttribute.getValue())); + } + + SearchResult teamMembershipResult = doSearch(ldapConnection, groupBase, true, groupMemberPattern, Arrays.asList("cn")); + if (teamMembershipResult != null && teamMembershipResult.getEntryCount() > 0) { + for (int i = 0; i < teamMembershipResult.getEntryCount(); i++) { + SearchResultEntry teamEntry = teamMembershipResult.getSearchEntries().get(i); + String teamName = teamEntry.getAttribute("cn").getValue(); + + TeamModel teamModel = userManager.getTeamModel(teamName); + if (teamModel == null) { + teamModel = createTeamFromLdap(teamEntry); + } + + user.teams.add(teamModel); + teamModel.addUser(user.getName()); + } + } + } + + private TeamModel createTeamFromLdap(SearchResultEntry teamEntry) { + TeamModel answer = new TeamModel(teamEntry.getAttributeValue("cn")); + answer.accountType = getAccountType(); + // potentially retrieve other attributes here in the future + + return answer; + } + + private SearchResult doSearch(LDAPConnection ldapConnection, String base, String filter) { + try { + return ldapConnection.search(base, SearchScope.SUB, filter); + } catch (LDAPSearchException e) { + logger.error("Problem Searching LDAP", e); + + return null; + } + } + + private SearchResult doSearch(LDAPConnection ldapConnection, String base, boolean dereferenceAliases, String filter, List<String> attributes) { + try { + SearchRequest searchRequest = new SearchRequest(base, SearchScope.SUB, filter); + if (dereferenceAliases) { + searchRequest.setDerefPolicy(DereferencePolicy.SEARCHING); + } + if (attributes != null) { + searchRequest.setAttributes(attributes); + } + return ldapConnection.search(searchRequest); + + } catch (LDAPSearchException e) { + logger.error("Problem Searching LDAP", e); + + return null; + } catch (LDAPException e) { + logger.error("Problem creating LDAP search", e); + return null; + } + } + + private boolean isAuthenticated(LDAPConnection ldapConnection, String userDn, String password) { + try { + // Binding will stop any LDAP-Injection Attacks since the searched-for user needs to bind to that DN + ldapConnection.bind(userDn, password); + return true; + } catch (LDAPException e) { + logger.error("Error authenticating user", e); + return false; + } + } + + /** + * Returns a simple username without any domain prefixes. + * + * @param username + * @return a simple username + */ + protected String getSimpleUsername(String username) { + int lastSlash = username.lastIndexOf('\\'); + if (lastSlash > -1) { + username = username.substring(lastSlash + 1); + } + + return username; + } + + // From: https://www.owasp.org/index.php/Preventing_LDAP_Injection_in_Java + public static final String escapeLDAPSearchFilter(String filter) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < filter.length(); i++) { + char curChar = filter.charAt(i); + switch (curChar) { + case '\\': + sb.append("\\5c"); + break; + case '*': + sb.append("\\2a"); + break; + case '(': + sb.append("\\28"); + break; + case ')': + sb.append("\\29"); + break; + case '\u0000': + sb.append("\\00"); + break; + default: + sb.append(curChar); + } + } + return sb.toString(); + } +} diff --git a/src/main/java/com/gitblit/PAMUserService.java b/src/main/java/com/gitblit/auth/PAMAuthProvider.java index db569fbf..bbc82d84 100644 --- a/src/main/java/com/gitblit/PAMUserService.java +++ b/src/main/java/com/gitblit/auth/PAMAuthProvider.java @@ -1,143 +1,126 @@ -/*
- * Copyright 2013 gitblit.com.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.gitblit;
-
-import java.io.File;
-
-import org.jvnet.libpam.PAM;
-import org.jvnet.libpam.PAMException;
-import org.jvnet.libpam.impl.CLibrary;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.gitblit.Constants.AccountType;
-import com.gitblit.manager.IRuntimeManager;
-import com.gitblit.models.UserModel;
-import com.gitblit.utils.ArrayUtils;
-import com.gitblit.utils.StringUtils;
-
-/**
- * Implementation of a PAM user service for Linux/Unix/MacOSX.
- *
- * @author James Moger
- */
-public class PAMUserService extends GitblitUserService {
-
- private final Logger logger = LoggerFactory.getLogger(PAMUserService.class);
-
- private IStoredSettings settings;
-
- public PAMUserService() {
- super();
- }
-
- @Override
- public void setup(IRuntimeManager runtimeManager) {
- this.settings = runtimeManager.getSettings();
-
- String file = settings.getString(Keys.realm.pam.backingUserService, "${baseFolder}/users.conf");
- File realmFile = runtimeManager.getFileOrFolder(file);
-
- serviceImpl = createUserService(realmFile);
- logger.info("PAM User Service backed by " + serviceImpl.toString());
-
- // Try to identify the passwd database
- String [] files = { "/etc/shadow", "/etc/master.passwd" };
- File passwdFile = null;
- for (String name : files) {
- File f = new File(name);
- if (f.exists()) {
- passwdFile = f;
- break;
- }
- }
- if (passwdFile == null) {
- logger.error("PAM User Service could not find a passwd database!");
- } else if (!passwdFile.canRead()) {
- logger.error("PAM User Service can not read passwd database {}! PAM authentications may fail!", passwdFile);
- }
- }
-
- @Override
- public boolean supportsCredentialChanges() {
- return false;
- }
-
- @Override
- public boolean supportsDisplayNameChanges() {
- return true;
- }
-
- @Override
- public boolean supportsEmailAddressChanges() {
- return true;
- }
-
- @Override
- public boolean supportsTeamMembershipChanges() {
- return true;
- }
-
- @Override
- public AccountType getAccountType() {
- return AccountType.PAM;
- }
-
- @Override
- public UserModel authenticate(String username, char[] password) {
- if (isLocalAccount(username)) {
- // local account, bypass PAM authentication
- return super.authenticate(username, password);
- }
-
- if (CLibrary.libc.getpwnam(username) == null) {
- logger.warn("Can not get PAM passwd for " + username);
- return null;
- }
-
- PAM pam = null;
- try {
- String serviceName = settings.getString(Keys.realm.pam.serviceName, "system-auth");
- pam = new PAM(serviceName);
- pam.authenticate(username, new String(password));
- } catch (PAMException e) {
- logger.error(e.getMessage());
- return null;
- } finally {
- pam.dispose();
- }
-
- UserModel user = getUserModel(username);
- if (user == null) // create user object for new authenticated user
- user = new UserModel(username.toLowerCase());
-
- // create a user cookie
- if (StringUtils.isEmpty(user.cookie) && !ArrayUtils.isEmpty(password)) {
- user.cookie = StringUtils.getSHA1(user.username + new String(password));
- }
-
- // update user attributes from UnixUser
- user.accountType = getAccountType();
- user.password = Constants.EXTERNAL_ACCOUNT;
-
- // TODO consider mapping PAM groups to teams
-
- // push the changes to the backing user service
- super.updateUserModel(user);
-
- return user;
- }
-}
+/* + * Copyright 2013 gitblit.com. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.gitblit.auth; + +import java.io.File; + +import org.jvnet.libpam.PAM; +import org.jvnet.libpam.PAMException; +import org.jvnet.libpam.impl.CLibrary; + +import com.gitblit.Constants; +import com.gitblit.Constants.AccountType; +import com.gitblit.Keys; +import com.gitblit.auth.AuthenticationProvider.UsernamePasswordAuthenticationProvider; +import com.gitblit.models.UserModel; +import com.gitblit.utils.ArrayUtils; +import com.gitblit.utils.StringUtils; + +/** + * Implementation of PAM authentication for Linux/Unix/MacOSX. + * + * @author James Moger + */ +public class PAMAuthProvider extends UsernamePasswordAuthenticationProvider { + + public PAMAuthProvider() { + super("pam"); + } + + @Override + public void setup() { + // Try to identify the passwd database + String [] files = { "/etc/shadow", "/etc/master.passwd" }; + File passwdFile = null; + for (String name : files) { + File f = new File(name); + if (f.exists()) { + passwdFile = f; + break; + } + } + if (passwdFile == null) { + logger.error("PAM Authentication could not find a passwd database!"); + } else if (!passwdFile.canRead()) { + logger.error("PAM Authentication can not read passwd database {}! PAM authentications may fail!", passwdFile); + } + } + + @Override + public boolean supportsCredentialChanges() { + return false; + } + + @Override + public boolean supportsDisplayNameChanges() { + return true; + } + + @Override + public boolean supportsEmailAddressChanges() { + return true; + } + + @Override + public boolean supportsTeamMembershipChanges() { + return true; + } + + @Override + public AccountType getAccountType() { + return AccountType.PAM; + } + + @Override + public UserModel authenticate(String username, char[] password) { + if (CLibrary.libc.getpwnam(username) == null) { + logger.warn("Can not get PAM passwd for " + username); + return null; + } + + PAM pam = null; + try { + String serviceName = settings.getString(Keys.realm.pam.serviceName, "system-auth"); + pam = new PAM(serviceName); + pam.authenticate(username, new String(password)); + } catch (PAMException e) { + logger.error(e.getMessage()); + return null; + } finally { + pam.dispose(); + } + + UserModel user = userManager.getUserModel(username); + if (user == null) // create user object for new authenticated user + user = new UserModel(username.toLowerCase()); + + // create a user cookie + if (StringUtils.isEmpty(user.cookie) && !ArrayUtils.isEmpty(password)) { + user.cookie = StringUtils.getSHA1(user.username + new String(password)); + } + + // update user attributes from UnixUser + user.accountType = getAccountType(); + user.password = Constants.EXTERNAL_ACCOUNT; + + // TODO consider mapping PAM groups to teams + + // push the changes to the backing user service + updateUser(user); + + return user; + } +} diff --git a/src/main/java/com/gitblit/RedmineUserService.java b/src/main/java/com/gitblit/auth/RedmineAuthProvider.java index 7c38ef2f..176c576b 100644 --- a/src/main/java/com/gitblit/RedmineUserService.java +++ b/src/main/java/com/gitblit/auth/RedmineAuthProvider.java @@ -1,203 +1,186 @@ -/*
- * Copyright 2012 gitblit.com.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.gitblit;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.net.HttpURLConnection;
-
-import org.apache.wicket.util.io.IOUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.gitblit.Constants.AccountType;
-import com.gitblit.manager.IRuntimeManager;
-import com.gitblit.models.UserModel;
-import com.gitblit.utils.ArrayUtils;
-import com.gitblit.utils.ConnectionUtils;
-import com.gitblit.utils.StringUtils;
-import com.google.gson.Gson;
-
-/**
- * Implementation of an Redmine user service.<br>
- * you can login to gitblit with Redmine user id and api key.
- */
-public class RedmineUserService extends GitblitUserService {
-
- private final Logger logger = LoggerFactory.getLogger(RedmineUserService.class);
-
- private IStoredSettings settings;
-
- private String testingJson;
-
- private class RedmineCurrent {
- private class RedmineUser {
- public String login;
- public String firstname;
- public String lastname;
- public String mail;
- }
-
- public RedmineUser user;
- }
-
- public RedmineUserService() {
- super();
- }
-
- @Override
- public void setup(IRuntimeManager runtimeManager) {
- this.settings = runtimeManager.getSettings();
-
- String file = settings.getString(Keys.realm.redmine.backingUserService, "${baseFolder}/users.conf");
- File realmFile = runtimeManager.getFileOrFolder(file);
-
- serviceImpl = createUserService(realmFile);
- logger.info("Redmine User Service backed by " + serviceImpl.toString());
- }
-
- @Override
- public boolean supportsCredentialChanges() {
- return false;
- }
-
- @Override
- public boolean supportsDisplayNameChanges() {
- return false;
- }
-
- @Override
- public boolean supportsEmailAddressChanges() {
- return false;
- }
-
- @Override
- public boolean supportsTeamMembershipChanges() {
- return false;
- }
-
- @Override
- public AccountType getAccountType() {
- return AccountType.REDMINE;
- }
-
- @Override
- public UserModel authenticate(String username, char[] password) {
- if (isLocalAccount(username)) {
- // local account, bypass Redmine authentication
- return super.authenticate(username, password);
- }
-
- String jsonString = null;
- try {
- // first attempt by username/password
- jsonString = getCurrentUserAsJson(username, password);
- } catch (Exception e1) {
- logger.warn("Failed to authenticate via username/password against Redmine");
- try {
- // second attempt is by apikey
- jsonString = getCurrentUserAsJson(null, password);
- username = null;
- } catch (Exception e2) {
- logger.error("Failed to authenticate via apikey against Redmine", e2);
- return null;
- }
- }
-
- if (StringUtils.isEmpty(jsonString)) {
- logger.error("Received empty authentication response from Redmine");
- return null;
- }
-
- RedmineCurrent current = null;
- try {
- current = new Gson().fromJson(jsonString, RedmineCurrent.class);
- } catch (Exception e) {
- logger.error("Failed to deserialize Redmine json response: " + jsonString, e);
- return null;
- }
-
- if (StringUtils.isEmpty(username)) {
- // if the username has been reset because of apikey authentication
- // then use the email address of the user. this is the original
- // behavior as contributed by github/mallowlabs
- username = current.user.mail;
- }
-
- UserModel user = getUserModel(username);
- if (user == null) // create user object for new authenticated user
- user = new UserModel(username.toLowerCase());
-
- // create a user cookie
- if (StringUtils.isEmpty(user.cookie) && !ArrayUtils.isEmpty(password)) {
- user.cookie = StringUtils.getSHA1(user.username + new String(password));
- }
-
- // update user attributes from Redmine
- user.accountType = getAccountType();
- user.displayName = current.user.firstname + " " + current.user.lastname;
- user.emailAddress = current.user.mail;
- user.password = Constants.EXTERNAL_ACCOUNT;
- if (!StringUtils.isEmpty(current.user.login)) {
- // only admin users can get login name
- // evidently this is an undocumented behavior of Redmine
- user.canAdmin = true;
- }
-
- // TODO consider Redmine group mapping for team membership
- // http://www.redmine.org/projects/redmine/wiki/Rest_Users
-
- // push the changes to the backing user service
- super.updateUserModel(user);
-
- return user;
- }
-
- private String getCurrentUserAsJson(String username, char [] password) throws IOException {
- if (testingJson != null) { // for testing
- return testingJson;
- }
-
- String url = this.settings.getString(Keys.realm.redmine.url, "");
- if (!url.endsWith("/")) {
- url = url.concat("/");
- }
- HttpURLConnection http;
- if (username == null) {
- // apikey authentication
- String apiKey = String.valueOf(password);
- String apiUrl = url + "users/current.json?key=" + apiKey;
- http = (HttpURLConnection) ConnectionUtils.openConnection(apiUrl, null, null);
- } else {
- // username/password BASIC authentication
- String apiUrl = url + "users/current.json";
- http = (HttpURLConnection) ConnectionUtils.openConnection(apiUrl, username, password);
- }
- http.setRequestMethod("GET");
- http.connect();
- InputStreamReader reader = new InputStreamReader(http.getInputStream());
- return IOUtils.toString(reader);
- }
-
- /**
- * set json response. do NOT invoke from production code.
- * @param json json
- */
- public void setTestingCurrentUserAsJson(String json) {
- this.testingJson = json;
- }
-}
+/* + * Copyright 2012 gitblit.com. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.gitblit.auth; + +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; + +import org.apache.wicket.util.io.IOUtils; + +import com.gitblit.Constants; +import com.gitblit.Constants.AccountType; +import com.gitblit.Keys; +import com.gitblit.auth.AuthenticationProvider.UsernamePasswordAuthenticationProvider; +import com.gitblit.models.UserModel; +import com.gitblit.utils.ArrayUtils; +import com.gitblit.utils.ConnectionUtils; +import com.gitblit.utils.StringUtils; +import com.google.gson.Gson; + +/** + * Implementation of Redmine authentication.<br> + * you can login to gitblit with Redmine user id and api key. + */ +public class RedmineAuthProvider extends UsernamePasswordAuthenticationProvider { + + private String testingJson; + + private class RedmineCurrent { + private class RedmineUser { + public String login; + public String firstname; + public String lastname; + public String mail; + } + + public RedmineUser user; + } + + public RedmineAuthProvider() { + super("redmine"); + } + + @Override + public void setup() { + } + + @Override + public boolean supportsCredentialChanges() { + return false; + } + + @Override + public boolean supportsDisplayNameChanges() { + return false; + } + + @Override + public boolean supportsEmailAddressChanges() { + return false; + } + + @Override + public boolean supportsTeamMembershipChanges() { + return false; + } + + @Override + public AccountType getAccountType() { + return AccountType.REDMINE; + } + + @Override + public UserModel authenticate(String username, char[] password) { + String jsonString = null; + try { + // first attempt by username/password + jsonString = getCurrentUserAsJson(username, password); + } catch (Exception e1) { + logger.warn("Failed to authenticate via username/password against Redmine"); + try { + // second attempt is by apikey + jsonString = getCurrentUserAsJson(null, password); + username = null; + } catch (Exception e2) { + logger.error("Failed to authenticate via apikey against Redmine", e2); + return null; + } + } + + if (StringUtils.isEmpty(jsonString)) { + logger.error("Received empty authentication response from Redmine"); + return null; + } + + RedmineCurrent current = null; + try { + current = new Gson().fromJson(jsonString, RedmineCurrent.class); + } catch (Exception e) { + logger.error("Failed to deserialize Redmine json response: " + jsonString, e); + return null; + } + + if (StringUtils.isEmpty(username)) { + // if the username has been reset because of apikey authentication + // then use the email address of the user. this is the original + // behavior as contributed by github/mallowlabs + username = current.user.mail; + } + + UserModel user = userManager.getUserModel(username); + if (user == null) // create user object for new authenticated user + user = new UserModel(username.toLowerCase()); + + // create a user cookie + if (StringUtils.isEmpty(user.cookie) && !ArrayUtils.isEmpty(password)) { + user.cookie = StringUtils.getSHA1(user.username + new String(password)); + } + + // update user attributes from Redmine + user.accountType = getAccountType(); + user.displayName = current.user.firstname + " " + current.user.lastname; + user.emailAddress = current.user.mail; + user.password = Constants.EXTERNAL_ACCOUNT; + if (!StringUtils.isEmpty(current.user.login)) { + // only admin users can get login name + // evidently this is an undocumented behavior of Redmine + user.canAdmin = true; + } + + // TODO consider Redmine group mapping for team membership + // http://www.redmine.org/projects/redmine/wiki/Rest_Users + + // push the changes to the backing user service + updateUser(user); + + return user; + } + + private String getCurrentUserAsJson(String username, char [] password) throws IOException { + if (testingJson != null) { // for testing + return testingJson; + } + + String url = this.settings.getString(Keys.realm.redmine.url, ""); + if (!url.endsWith("/")) { + url = url.concat("/"); + } + HttpURLConnection http; + if (username == null) { + // apikey authentication + String apiKey = String.valueOf(password); + String apiUrl = url + "users/current.json?key=" + apiKey; + http = (HttpURLConnection) ConnectionUtils.openConnection(apiUrl, null, null); + } else { + // username/password BASIC authentication + String apiUrl = url + "users/current.json"; + http = (HttpURLConnection) ConnectionUtils.openConnection(apiUrl, username, password); + } + http.setRequestMethod("GET"); + http.connect(); + InputStreamReader reader = new InputStreamReader(http.getInputStream()); + return IOUtils.toString(reader); + } + + /** + * set json response. do NOT invoke from production code. + * @param json json + */ + public void setTestingCurrentUserAsJson(String json) { + this.testingJson = json; + } +} diff --git a/src/main/java/com/gitblit/SalesforceUserService.java b/src/main/java/com/gitblit/auth/SalesforceAuthProvider.java index 6161ba93..fdda32af 100644 --- a/src/main/java/com/gitblit/SalesforceUserService.java +++ b/src/main/java/com/gitblit/auth/SalesforceAuthProvider.java @@ -1,12 +1,9 @@ -package com.gitblit; - -import java.io.File; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +package com.gitblit.auth; +import com.gitblit.Constants; import com.gitblit.Constants.AccountType; -import com.gitblit.manager.IRuntimeManager; +import com.gitblit.Keys; +import com.gitblit.auth.AuthenticationProvider.UsernamePasswordAuthenticationProvider; import com.gitblit.models.UserModel; import com.gitblit.utils.ArrayUtils; import com.gitblit.utils.StringUtils; @@ -16,10 +13,11 @@ import com.sforce.soap.partner.PartnerConnection; import com.sforce.ws.ConnectionException; import com.sforce.ws.ConnectorConfig; -public class SalesforceUserService extends GitblitUserService { +public class SalesforceAuthProvider extends UsernamePasswordAuthenticationProvider { - public static final Logger logger = LoggerFactory.getLogger(SalesforceUserService.class); - private IStoredSettings settings; + public SalesforceAuthProvider() { + super("salesforce"); + } @Override public AccountType getAccountType() { @@ -27,26 +25,11 @@ public class SalesforceUserService extends GitblitUserService { } @Override - public void setup(IRuntimeManager runtimeManager) { - this.settings = runtimeManager.getSettings(); - String file = settings.getString( - Keys.realm.salesforce.backingUserService, - "${baseFolder}/users.conf"); - File realmFile = runtimeManager.getFileOrFolder(file); - - serviceImpl = createUserService(realmFile); - - logger.info("Salesforce User Service backed by " - + serviceImpl.toString()); + public void setup() { } @Override public UserModel authenticate(String username, char[] password) { - if (isLocalAccount(username)) { - // local account, bypass Salesforce authentication - return super.authenticate(username, password); - } - ConnectorConfig config = new ConnectorConfig(); config.setUsername(username); config.setPassword(new String(password)); @@ -78,7 +61,7 @@ public class SalesforceUserService extends GitblitUserService { UserModel user = null; synchronized (this) { - user = getUserModel(simpleUsername); + user = userManager.getUserModel(simpleUsername); if (user == null) user = new UserModel(simpleUsername); @@ -90,7 +73,7 @@ public class SalesforceUserService extends GitblitUserService { setUserAttributes(user, info); - super.updateUserModel(user); + updateUser(user); } return user; @@ -122,6 +105,7 @@ public class SalesforceUserService extends GitblitUserService { return email.split("@")[0]; } + @Override public boolean supportsCredentialChanges() { return false; @@ -136,4 +120,9 @@ public class SalesforceUserService extends GitblitUserService { public boolean supportsEmailAddressChanges() { return false; } + + @Override + public boolean supportsTeamMembershipChanges() { + return true; + } } diff --git a/src/main/java/com/gitblit/WindowsUserService.java b/src/main/java/com/gitblit/auth/WindowsAuthProvider.java index 99077c67..d455d58f 100644 --- a/src/main/java/com/gitblit/WindowsUserService.java +++ b/src/main/java/com/gitblit/auth/WindowsAuthProvider.java @@ -1,195 +1,177 @@ -/*
- * Copyright 2013 gitblit.com.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.gitblit;
-
-import java.io.File;
-import java.util.Set;
-import java.util.TreeSet;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import waffle.windows.auth.IWindowsAccount;
-import waffle.windows.auth.IWindowsAuthProvider;
-import waffle.windows.auth.IWindowsComputer;
-import waffle.windows.auth.IWindowsIdentity;
-import waffle.windows.auth.impl.WindowsAuthProviderImpl;
-
-import com.gitblit.Constants.AccountType;
-import com.gitblit.manager.IRuntimeManager;
-import com.gitblit.models.UserModel;
-import com.gitblit.utils.ArrayUtils;
-import com.gitblit.utils.StringUtils;
-import com.sun.jna.platform.win32.Win32Exception;
-
-/**
- * Implementation of a Windows user service.
- *
- * @author James Moger
- */
-public class WindowsUserService extends GitblitUserService {
-
- private final Logger logger = LoggerFactory.getLogger(WindowsUserService.class);
-
- private IStoredSettings settings;
-
- private IWindowsAuthProvider waffle;
-
- public WindowsUserService() {
- super();
- }
-
- @Override
- public void setup(IRuntimeManager runtimeManager) {
- this.settings = runtimeManager.getSettings();
-
- String file = settings.getString(Keys.realm.windows.backingUserService, "${baseFolder}/users.conf");
- File realmFile = runtimeManager.getFileOrFolder(file);
-
- serviceImpl = createUserService(realmFile);
- logger.info("Windows User Service backed by " + serviceImpl.toString());
-
- waffle = new WindowsAuthProviderImpl();
- IWindowsComputer computer = waffle.getCurrentComputer();
- logger.info(" name = " + computer.getComputerName());
- logger.info(" status = " + describeJoinStatus(computer.getJoinStatus()));
- logger.info(" memberOf = " + computer.getMemberOf());
- //logger.info(" groups = " + Arrays.asList(computer.getGroups()));
- }
-
- protected String describeJoinStatus(String value) {
- if ("NetSetupUnknownStatus".equals(value)) {
- return "unknown";
- } else if ("NetSetupUnjoined".equals(value)) {
- return "not joined";
- } else if ("NetSetupWorkgroupName".equals(value)) {
- return "joined to a workgroup";
- } else if ("NetSetupDomainName".equals(value)) {
- return "joined to a domain";
- }
- return value;
- }
-
- @Override
- public boolean supportsCredentialChanges() {
- return false;
- }
-
- @Override
- public boolean supportsDisplayNameChanges() {
- return false;
- }
-
- @Override
- public boolean supportsEmailAddressChanges() {
- return true;
- }
-
- @Override
- public boolean supportsTeamMembershipChanges() {
- return true;
- }
-
- @Override
- public AccountType getAccountType() {
- return AccountType.WINDOWS;
- }
-
- @Override
- public UserModel authenticate(String username, char[] password) {
- if (isLocalAccount(username)) {
- // local account, bypass Windows authentication
- return super.authenticate(username, password);
- }
-
- String defaultDomain = settings.getString(Keys.realm.windows.defaultDomain, null);
- if (StringUtils.isEmpty(defaultDomain)) {
- // ensure that default domain is null
- defaultDomain = null;
- }
-
- if (defaultDomain != null) {
- // sanitize username
- if (username.startsWith(defaultDomain + "\\")) {
- // strip default domain from domain\ username
- username = username.substring(defaultDomain.length() + 1);
- } else if (username.endsWith("@" + defaultDomain)) {
- // strip default domain from username@domain
- username = username.substring(0, username.lastIndexOf('@'));
- }
- }
-
- IWindowsIdentity identity = null;
- try {
- if (username.indexOf('@') > -1 || username.indexOf('\\') > -1) {
- // manually specified domain
- identity = waffle.logonUser(username, new String(password));
- } else {
- // no domain specified, use default domain
- identity = waffle.logonDomainUser(username, defaultDomain, new String(password));
- }
- } catch (Win32Exception e) {
- logger.error(e.getMessage());
- return null;
- }
-
- if (identity.isGuest() && !settings.getBoolean(Keys.realm.windows.allowGuests, false)) {
- logger.warn("Guest account access is disabled");
- identity.dispose();
- return null;
- }
-
- UserModel user = getUserModel(username);
- if (user == null) // create user object for new authenticated user
- user = new UserModel(username.toLowerCase());
-
- // create a user cookie
- if (StringUtils.isEmpty(user.cookie) && !ArrayUtils.isEmpty(password)) {
- user.cookie = StringUtils.getSHA1(user.username + new String(password));
- }
-
- // update user attributes from Windows identity
- user.accountType = getAccountType();
- String fqn = identity.getFqn();
- if (fqn.indexOf('\\') > -1) {
- user.displayName = fqn.substring(fqn.lastIndexOf('\\') + 1);
- } else {
- user.displayName = fqn;
- }
- user.password = Constants.EXTERNAL_ACCOUNT;
-
- Set<String> groupNames = new TreeSet<String>();
- for (IWindowsAccount group : identity.getGroups()) {
- groupNames.add(group.getFqn());
- }
-
- if (groupNames.contains("BUILTIN\\Administrators")) {
- // local administrator
- user.canAdmin = true;
- }
-
- // TODO consider mapping Windows groups to teams
-
- // push the changes to the backing user service
- super.updateUserModel(user);
-
-
- // cleanup resources
- identity.dispose();
-
- return user;
- }
-}
+/* + * Copyright 2013 gitblit.com. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.gitblit.auth; + +import java.util.Set; +import java.util.TreeSet; + +import waffle.windows.auth.IWindowsAccount; +import waffle.windows.auth.IWindowsAuthProvider; +import waffle.windows.auth.IWindowsComputer; +import waffle.windows.auth.IWindowsIdentity; +import waffle.windows.auth.impl.WindowsAuthProviderImpl; + +import com.gitblit.Constants; +import com.gitblit.Constants.AccountType; +import com.gitblit.Keys; +import com.gitblit.auth.AuthenticationProvider.UsernamePasswordAuthenticationProvider; +import com.gitblit.models.UserModel; +import com.gitblit.utils.ArrayUtils; +import com.gitblit.utils.StringUtils; +import com.sun.jna.platform.win32.Win32Exception; + +/** + * Implementation of a Windows authentication provider. + * + * @author James Moger + */ +public class WindowsAuthProvider extends UsernamePasswordAuthenticationProvider { + + private IWindowsAuthProvider waffle; + + public WindowsAuthProvider() { + super("windows"); + } + + @Override + public void setup() { + + waffle = new WindowsAuthProviderImpl(); + IWindowsComputer computer = waffle.getCurrentComputer(); + logger.info("Windows Authentication Provider"); + logger.info(" name = " + computer.getComputerName()); + logger.info(" status = " + describeJoinStatus(computer.getJoinStatus())); + logger.info(" memberOf = " + computer.getMemberOf()); + //logger.info(" groups = " + Arrays.asList(computer.getGroups())); + } + + protected String describeJoinStatus(String value) { + if ("NetSetupUnknownStatus".equals(value)) { + return "unknown"; + } else if ("NetSetupUnjoined".equals(value)) { + return "not joined"; + } else if ("NetSetupWorkgroupName".equals(value)) { + return "joined to a workgroup"; + } else if ("NetSetupDomainName".equals(value)) { + return "joined to a domain"; + } + return value; + } + + @Override + public boolean supportsCredentialChanges() { + return false; + } + + @Override + public boolean supportsDisplayNameChanges() { + return false; + } + + @Override + public boolean supportsEmailAddressChanges() { + return true; + } + + @Override + public boolean supportsTeamMembershipChanges() { + return true; + } + + @Override + public AccountType getAccountType() { + return AccountType.WINDOWS; + } + + @Override + public UserModel authenticate(String username, char[] password) { + String defaultDomain = settings.getString(Keys.realm.windows.defaultDomain, null); + if (StringUtils.isEmpty(defaultDomain)) { + // ensure that default domain is null + defaultDomain = null; + } + + if (defaultDomain != null) { + // sanitize username + if (username.startsWith(defaultDomain + "\\")) { + // strip default domain from domain\ username + username = username.substring(defaultDomain.length() + 1); + } else if (username.endsWith("@" + defaultDomain)) { + // strip default domain from username@domain + username = username.substring(0, username.lastIndexOf('@')); + } + } + + IWindowsIdentity identity = null; + try { + if (username.indexOf('@') > -1 || username.indexOf('\\') > -1) { + // manually specified domain + identity = waffle.logonUser(username, new String(password)); + } else { + // no domain specified, use default domain + identity = waffle.logonDomainUser(username, defaultDomain, new String(password)); + } + } catch (Win32Exception e) { + logger.error(e.getMessage()); + return null; + } + + if (identity.isGuest() && !settings.getBoolean(Keys.realm.windows.allowGuests, false)) { + logger.warn("Guest account access is disabled"); + identity.dispose(); + return null; + } + + UserModel user = userManager.getUserModel(username); + if (user == null) // create user object for new authenticated user + user = new UserModel(username.toLowerCase()); + + // create a user cookie + if (StringUtils.isEmpty(user.cookie) && !ArrayUtils.isEmpty(password)) { + user.cookie = StringUtils.getSHA1(user.username + new String(password)); + } + + // update user attributes from Windows identity + user.accountType = getAccountType(); + String fqn = identity.getFqn(); + if (fqn.indexOf('\\') > -1) { + user.displayName = fqn.substring(fqn.lastIndexOf('\\') + 1); + } else { + user.displayName = fqn; + } + user.password = Constants.EXTERNAL_ACCOUNT; + + Set<String> groupNames = new TreeSet<String>(); + for (IWindowsAccount group : identity.getGroups()) { + groupNames.add(group.getFqn()); + } + + if (groupNames.contains("BUILTIN\\Administrators")) { + // local administrator + user.canAdmin = true; + } + + // TODO consider mapping Windows groups to teams + + // push the changes to the backing user service + updateUser(user); + + // cleanup resources + identity.dispose(); + + return user; + } +} 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<String>();
- 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<TeamModel>();
- 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<X> implements UploadPackFactory<X> { - private final ISessionManager sessionManager; + private final IAuthenticationManager authenticationManager; - public GitblitUploadPackFactory(ISessionManager sessionManager) { - this.sessionManager = sessionManager; + public GitblitUploadPackFactory(IAuthenticationManager authenticationManager) { + this.authenticationManager = authenticationManager; } @Override @@ -51,7 +51,7 @@ public class GitblitUploadPackFactory<X> implements UploadPackFactory<X> { 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<AuthenticationProvider> authenticationProviders; + + private final Map<String, Class<? extends AuthenticationProvider>> providerNames; + + private final Map<String, String> legacyRedirects; + + public AuthenticationManager( + IRuntimeManager runtimeManager, + IUserManager userManager) { + + this.settings = runtimeManager.getSettings(); + this.runtimeManager = runtimeManager; + this.userManager = userManager; + this.authenticationProviders = new ArrayList<AuthenticationProvider>(); + + // map of shortcut provider names + providerNames = new HashMap<String, Class<? extends AuthenticationProvider>>(); + providerNames.put("htpasswd", HtpasswdAuthProvider.class); + providerNames.put("ldap", LdapAuthProvider.class); + providerNames.put("pam", PAMAuthProvider.class); + providerNames.put("redmine", RedmineAuthProvider.class); + providerNames.put("salesforce", SalesforceAuthProvider.class); + providerNames.put("windows", WindowsAuthProvider.class); + + // map of legacy external user services + legacyRedirects = new HashMap<String, String>(); + legacyRedirects.put("com.gitblit.HtpasswdUserService", "htpasswd"); + legacyRedirects.put("com.gitblit.LdapUserService", "ldap"); + legacyRedirects.put("com.gitblit.PAMUserService", "pam"); + legacyRedirects.put("com.gitblit.RedmineUserService", "redmine"); + legacyRedirects.put("com.gitblit.SalesforceUserService", "salesforce"); + legacyRedirects.put("com.gitblit.WindowsUserService", "windows"); + } + + @Override + public AuthenticationManager start() { + // automatically adjust legacy configurations + String realm = settings.getString(Keys.realm.userService, "${baseFolder}/users.conf"); + if (legacyRedirects.containsKey(realm)) { + logger.warn(""); + logger.warn("#################################################################"); + logger.warn(" IUserService '{}' is obsolete!", realm); + logger.warn(" Please set '{}={}'", "realm.authenticationProviders", legacyRedirects.get(realm)); + logger.warn("#################################################################"); + logger.warn(""); + + // conditionally override specified authentication providers + if (StringUtils.isEmpty(settings.getString(Keys.realm.authenticationProviders, null))) { + settings.overrideSetting(Keys.realm.authenticationProviders, legacyRedirects.get(realm)); + } + } + + // instantiate and setup specified authentication providers + List<String> providers = settings.getStrings(Keys.realm.authenticationProviders); + if (providers.isEmpty()) { + logger.info("External authentication disabled."); + } else { + for (String provider : providers) { + try { + Class<?> authClass; + if (providerNames.containsKey(provider)) { + // map the name -> class + authClass = providerNames.get(provider); + } else { + // reflective lookup + authClass = Class.forName(provider); + } + logger.info("setting up {}", authClass.getName()); + AuthenticationProvider authImpl = (AuthenticationProvider) authClass.newInstance(); + authImpl.setup(runtimeManager, userManager); + authenticationProviders.add(authImpl); + } catch (Exception e) { + logger.error("", e); + } + } + } + return this; + } + + @Override + public AuthenticationManager stop() { + return this; + } + + /** + * Authenticate a user based on HTTP request parameters. + * + * Authentication by X509Certificate is tried first and then by cookie. + * + * @param httpRequest + * @return a user object or null + */ + @Override + public UserModel authenticate(HttpServletRequest httpRequest) { + return authenticate(httpRequest, false); + } + + /** + * Authenticate a user based on HTTP request parameters. + * + * Authentication by servlet container principal, X509Certificate, cookie, + * and finally BASIC header. + * + * @param httpRequest + * @param requiresCertificate + * @return a user object or null + */ + @Override + public UserModel authenticate(HttpServletRequest httpRequest, boolean requiresCertificate) { + // try to authenticate by servlet container principal + if (!requiresCertificate) { + Principal principal = httpRequest.getUserPrincipal(); + if (principal != null) { + String username = principal.getName(); + if (!StringUtils.isEmpty(username)) { + boolean internalAccount = isInternalAccount(username); + UserModel user = userManager.getUserModel(username); + if (user != null) { + // existing user + flagWicketSession(AuthenticationType.CONTAINER); + logger.debug(MessageFormat.format("{0} authenticated by servlet container principal from {1}", + user.username, httpRequest.getRemoteAddr())); + return user; + } else if (settings.getBoolean(Keys.realm.container.autoCreateAccounts, false) + && !internalAccount) { + // auto-create user from an authenticated container principal + user = new UserModel(username.toLowerCase()); + user.displayName = username; + user.password = Constants.EXTERNAL_ACCOUNT; + user.accountType = AccountType.CONTAINER; + userManager.updateUserModel(user); + flagWicketSession(AuthenticationType.CONTAINER); + logger.debug(MessageFormat.format("{0} authenticated and created by servlet container principal from {1}", + user.username, httpRequest.getRemoteAddr())); + return user; + } else if (!internalAccount) { + logger.warn(MessageFormat.format("Failed to find UserModel for {0}, attempted servlet container authentication from {1}", + principal.getName(), httpRequest.getRemoteAddr())); + } + } + } + } + + // try to authenticate by certificate + boolean checkValidity = settings.getBoolean(Keys.git.enforceCertificateValidity, true); + String [] oids = settings.getStrings(Keys.git.certificateUsernameOIDs).toArray(new String[0]); + UserModel model = HttpUtils.getUserModelFromCertificate(httpRequest, checkValidity, oids); + if (model != null) { + // grab real user model and preserve certificate serial number + UserModel user = userManager.getUserModel(model.username); + X509Metadata metadata = HttpUtils.getCertificateMetadata(httpRequest); + if (user != null) { + flagWicketSession(AuthenticationType.CERTIFICATE); + logger.debug(MessageFormat.format("{0} authenticated by client certificate {1} from {2}", + user.username, metadata.serialNumber, httpRequest.getRemoteAddr())); + return user; + } else { + logger.warn(MessageFormat.format("Failed to find UserModel for {0}, attempted client certificate ({1}) authentication from {2}", + model.username, metadata.serialNumber, httpRequest.getRemoteAddr())); + } + } + + if (requiresCertificate) { + // caller requires client certificate authentication (e.g. git servlet) + return null; + } + + // try to authenticate by cookie + UserModel user = authenticate(httpRequest.getCookies()); + if (user != null) { + flagWicketSession(AuthenticationType.COOKIE); + logger.debug(MessageFormat.format("{0} authenticated by cookie from {1}", + user.username, httpRequest.getRemoteAddr())); + return user; + } + + // try to authenticate by BASIC + final String authorization = httpRequest.getHeader("Authorization"); + if (authorization != null && authorization.startsWith("Basic")) { + // Authorization: Basic base64credentials + String base64Credentials = authorization.substring("Basic".length()).trim(); + String credentials = new String(Base64.decode(base64Credentials), + Charset.forName("UTF-8")); + // credentials = username:password + final String[] values = credentials.split(":", 2); + + if (values.length == 2) { + String username = values[0]; + char[] password = values[1].toCharArray(); + user = authenticate(username, password); + if (user != null) { + flagWicketSession(AuthenticationType.CREDENTIALS); + logger.debug(MessageFormat.format("{0} authenticated by BASIC request header from {1}", + user.username, httpRequest.getRemoteAddr())); + return user; + } else { + logger.warn(MessageFormat.format("Failed login attempt for {0}, invalid credentials from {1}", + username, httpRequest.getRemoteAddr())); + } + } + } + return null; + } + + /** + * Authenticate a user based on their cookie. + * + * @param cookies + * @return a user object or null + */ + protected UserModel authenticate(Cookie[] cookies) { + if (settings.getBoolean(Keys.web.allowCookieAuthentication, true)) { + if (cookies != null && cookies.length > 0) { + for (Cookie cookie : cookies) { + if (cookie.getName().equals(Constants.NAME)) { + String value = cookie.getValue(); + return userManager.getUserModel(value.toCharArray()); + } + } + } + } + return null; + } + + protected void flagWicketSession(AuthenticationType authenticationType) { + RequestCycle requestCycle = RequestCycle.get(); + if (requestCycle != null) { + // flag the Wicket session, if this is a Wicket request + GitBlitWebSession session = GitBlitWebSession.get(); + session.authenticationType = authenticationType; + } + } + + /** + * Authenticate a user based on a username and password. + * + * @see IUserService.authenticate(String, char[]) + * @param username + * @param password + * @return a user object or null + */ + @Override + public UserModel authenticate(String username, char[] password) { + if (StringUtils.isEmpty(username)) { + // can not authenticate empty username + return null; + } + + String usernameDecoded = StringUtils.decodeUsername(username); + String pw = new String(password); + if (StringUtils.isEmpty(pw)) { + // can not authenticate empty password + return null; + } + // check to see if this is the federation user +// if (canFederate()) { +// if (usernameDecoded.equalsIgnoreCase(Constants.FEDERATION_USER)) { +// List<String> tokens = getFederationTokens(); +// if (tokens.contains(pw)) { +// return getFederationUser(); +// } +// } +// } + + // try local authentication + UserModel user = userManager.getUserModel(usernameDecoded); + if (user != null) { + UserModel returnedUser = null; + if (user.password.startsWith(StringUtils.MD5_TYPE)) { + // password digest + String md5 = StringUtils.MD5_TYPE + StringUtils.getMD5(new String(password)); + if (user.password.equalsIgnoreCase(md5)) { + returnedUser = user; + } + } else if (user.password.startsWith(StringUtils.COMBINED_MD5_TYPE)) { + // username+password digest + String md5 = StringUtils.COMBINED_MD5_TYPE + + StringUtils.getMD5(username.toLowerCase() + new String(password)); + if (user.password.equalsIgnoreCase(md5)) { + returnedUser = user; + } + } else if (user.password.equals(new String(password))) { + // plain-text password + returnedUser = user; + } + return returnedUser; + } + + // try registered external authentication providers + if (user == null) { + for (AuthenticationProvider provider : authenticationProviders) { + if (provider instanceof UsernamePasswordAuthenticationProvider) { + user = provider.authenticate(usernameDecoded, password); + if (user != null) { + // user authenticated + user.accountType = provider.getAccountType(); + return user; + } + } + } + } + return user; + } + + /** + * Sets a cookie for the specified user. + * + * @param response + * @param user + */ + @Override + public void setCookie(HttpServletResponse response, UserModel user) { + if (settings.getBoolean(Keys.web.allowCookieAuthentication, true)) { + GitBlitWebSession session = GitBlitWebSession.get(); + boolean standardLogin = session.authenticationType.isStandard(); + + if (standardLogin) { + Cookie userCookie; + if (user == null) { + // clear cookie for logout + userCookie = new Cookie(Constants.NAME, ""); + } else { + // set cookie for login + String cookie = userManager.getCookie(user); + if (StringUtils.isEmpty(cookie)) { + // create empty cookie + userCookie = new Cookie(Constants.NAME, ""); + } else { + // create real cookie + userCookie = new Cookie(Constants.NAME, cookie); + userCookie.setMaxAge(Integer.MAX_VALUE); + } + } + userCookie.setPath("/"); + response.addCookie(userCookie); + } + } + } + + /** + * Logout a user. + * + * @param user + */ + @Override + public void logout(HttpServletResponse response, UserModel user) { + setCookie(response, null); + } + + /** + * Returns true if the user's credentials can be changed. + * + * @param user + * @return true if the user service supports credential changes + */ + @Override + public boolean supportsCredentialChanges(UserModel user) { + return (user != null && user.isLocalAccount()) || findProvider(user).supportsCredentialChanges(); + } + + /** + * Returns true if the user's display name can be changed. + * + * @param user + * @return true if the user service supports display name changes + */ + @Override + public boolean supportsDisplayNameChanges(UserModel user) { + return (user != null && user.isLocalAccount()) || findProvider(user).supportsDisplayNameChanges(); + } + + /** + * Returns true if the user's email address can be changed. + * + * @param user + * @return true if the user service supports email address changes + */ + @Override + public boolean supportsEmailAddressChanges(UserModel user) { + return (user != null && user.isLocalAccount()) || findProvider(user).supportsEmailAddressChanges(); + } + + /** + * Returns true if the user's team memberships can be changed. + * + * @param user + * @return true if the user service supports team membership changes + */ + @Override + public boolean supportsTeamMembershipChanges(UserModel user) { + return (user != null && user.isLocalAccount()) || findProvider(user).supportsTeamMembershipChanges(); + } + + /** + * Returns true if the team memberships can be changed. + * + * @param user + * @return true if the team membership can be changed + */ + @Override + public boolean supportsTeamMembershipChanges(TeamModel team) { + return (team != null && team.isLocalTeam()) || findProvider(team).supportsTeamMembershipChanges(); + } + + protected AuthenticationProvider findProvider(UserModel user) { + for (AuthenticationProvider provider : authenticationProviders) { + if (provider.getAccountType().equals(user.accountType)) { + return provider; + } + } + return AuthenticationProvider.NULL_PROVIDER; + } + + protected AuthenticationProvider findProvider(TeamModel team) { + for (AuthenticationProvider provider : authenticationProviders) { + if (provider.getAccountType().equals(team.accountType)) { + return provider; + } + } + return AuthenticationProvider.NULL_PROVIDER; + } + + /** + * Returns true if the username represents an internal account + * + * @param username + * @return true if the specified username represents an internal account + */ + protected boolean isInternalAccount(String username) { + return !StringUtils.isEmpty(username) + && (username.equalsIgnoreCase(Constants.FEDERATION_USER) + || username.equalsIgnoreCase(UserModel.ANONYMOUS.username)); + } + +// protected UserModel getFederationUser() { +// // the federation user is an administrator +// UserModel federationUser = new UserModel(Constants.FEDERATION_USER); +// federationUser.canAdmin = true; +// return federationUser; +// } +} 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<String, SettingModel> */ private void loadSettingModels(ServerSettings settingsModel) { - // this entire "supports" concept will go away with user service refactoring - UserModel externalUser = new UserModel(Constants.EXTERNAL_ACCOUNT); - externalUser.password = Constants.EXTERNAL_ACCOUNT; - settingsModel.supportsCredentialChanges = userManager.supportsCredentialChanges(externalUser); - settingsModel.supportsDisplayNameChanges = userManager.supportsDisplayNameChanges(externalUser); - settingsModel.supportsEmailAddressChanges = userManager.supportsEmailAddressChanges(externalUser); - settingsModel.supportsTeamMembershipChanges = userManager.supportsTeamMembershipChanges(externalUser); try { // Read bundled Gitblit properties to extract setting descriptions. // This copy is pristine and only used for populating the setting diff --git a/src/main/java/com/gitblit/manager/ISessionManager.java b/src/main/java/com/gitblit/manager/IAuthenticationManager.java index 29f17096..093f44d9 100644 --- a/src/main/java/com/gitblit/manager/ISessionManager.java +++ b/src/main/java/com/gitblit/manager/IAuthenticationManager.java @@ -18,9 +18,10 @@ 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 ISessionManager extends IManager { +public interface IAuthenticationManager extends IManager { /** * Authenticate a user based on HTTP request parameters. @@ -69,4 +70,44 @@ public interface ISessionManager extends IManager { */ 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/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<UserModel> models); - - /** - * Adds/updates a user object keyed by username. This method allows for - * renaming a user. - * - * @param username - * the old username - * @param model - * the user object to use for username - * @return true if update is successful - */ - boolean updateUserModel(String username, UserModel model); - - /** - * Deletes the user object from the user service. - * - * @param model - * @return true if successful - */ - boolean deleteUserModel(UserModel model); - - /** - * Delete the user object with the specified username - * - * @param username - * @return true if successful - */ - boolean deleteUser(String username); - - /** - * Returns the list of all users available to the login service. - * - * @return list of all usernames - */ - List<String> getAllUsernames(); - - /** - * Returns the list of all users available to the login service. - * - * @return list of all users - * @since 0.8.0 - */ - List<UserModel> getAllUsers(); - - /** - * Returns the list of all teams available to the login service. - * - * @return list of all teams - * @since 0.8.0 - */ - List<String> getAllTeamNames(); - - /** - * Returns the list of all teams available to the login service. - * - * @return list of all teams - * @since 0.8.0 - */ - List<TeamModel> getAllTeams(); - - /** - * Returns the list of all users who are allowed to bypass the access - * restriction placed on the specified repository. - * - * @param role - * the repository name - * @return list of all usernames that can bypass the access restriction - * @since 0.8.0 - */ - List<String> getTeamNamesForRepositoryRole(String role); - - /** - * Retrieve the team object for the specified team name. - * - * @param teamname - * @return a team object or null - * @since 0.8.0 - */ - TeamModel getTeamModel(String teamname); - - /** - * Updates/writes a complete team object. - * - * @param model - * @return true if update is successful - * @since 0.8.0 - */ - boolean updateTeamModel(TeamModel model); - - /** - * Updates/writes all specified team objects. - * - * @param models a list of team models - * @return true if update is successful - * @since 1.2.0 - */ - boolean updateTeamModels(Collection<TeamModel> models); - - /** - * Updates/writes and replaces a complete team object keyed by teamname. - * This method allows for renaming a team. - * - * @param teamname - * the old teamname - * @param model - * the team object to use for teamname - * @return true if update is successful - * @since 0.8.0 - */ - boolean updateTeamModel(String teamname, TeamModel model); - - /** - * Deletes the team object from the user service. - * - * @param model - * @return true if successful - * @since 0.8.0 - */ - boolean deleteTeamModel(TeamModel model); - - /** - * Delete the team object with the specified teamname - * - * @param teamname - * @return true if successful - * @since 0.8.0 - */ - boolean deleteTeam(String teamname); - - /** - * Returns the list of all users who are allowed to bypass the access - * restriction placed on the specified repository. - * - * @param role - * the repository name - * @return list of all usernames that can bypass the access restriction - * @since 0.8.0 - */ - List<String> getUsernamesForRepositoryRole(String role); - - /** - * Renames a repository role. - * - * @param oldRole - * @param newRole - * @return true if successful - */ - boolean renameRepositoryRole(String oldRole, String newRole); - - /** - * Removes a repository role from all users. - * - * @param role - * @return true if successful - */ - boolean deleteRepositoryRole(String role); }
\ 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<String> 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<String> 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<String, String> legacyBackingServices; + private IUserService userService; public UserManager(IRuntimeManager runtimeManager) { this.settings = runtimeManager.getSettings(); this.runtimeManager = runtimeManager; + + // map of legacy realm backing user services + legacyBackingServices = new HashMap<String, String>(); + legacyBackingServices.put("com.gitblit.HtpasswdUserService", "realm.htpasswd.backingUserService"); + legacyBackingServices.put("com.gitblit.LdapUserService", "realm.ldap.backingUserService"); + legacyBackingServices.put("com.gitblit.PAMUserService", "realm.pam.backingUserService"); + legacyBackingServices.put("com.gitblit.RedmineUserService", "realm.redmine.backingUserService"); + legacyBackingServices.put("com.gitblit.SalesforceUserService", "realm.salesforce.backingUserService"); + legacyBackingServices.put("com.gitblit.WindowsUserService", "realm.windows.backingUserService"); } /** - * Set the user service. The user service authenticates local users and is - * responsible for persisting and retrieving users and teams. + * Set the user service. The user service authenticates *local* users and is + * responsible for persisting and retrieving all users and all teams. * * @param userService */ public void setUserService(IUserService userService) { - logger.info("UserService: " + userService.toString()); + logger.info(userService.toString()); this.userService = userService; this.userService.setup(runtimeManager); } @Override + public void setup(IRuntimeManager runtimeManager) { + // NOOP + } + + @Override public UserManager start() { if (this.userService == null) { - String realm = settings.getString(Keys.realm.userService, "${baseFolder}/users.properties"); + String realm = settings.getString(Keys.realm.userService, "${baseFolder}/users.conf"); IUserService service = null; - try { - // check to see if this "file" is a login service class - Class<?> realmClass = Class.forName(realm); - service = (IUserService) realmClass.newInstance(); - } catch (Throwable t) { - File realmFile = runtimeManager.getFileOrFolder(Keys.realm.userService, "${baseFolder}/users.conf"); + if (legacyBackingServices.containsKey(realm)) { + // create the user service from the legacy config + String realmKey = legacyBackingServices.get(realm); + logger.warn(""); + logger.warn("#################################################################"); + logger.warn(" Key '{}' is obsolete!", realmKey); + logger.warn(" Please set '{}={}'", Keys.realm.userService, settings.getString(realmKey, "${baseFolder}/users.conf")); + logger.warn("#################################################################"); + logger.warn(""); + File realmFile = runtimeManager.getFileOrFolder(realmKey, "${baseFolder}/users.conf"); service = createUserService(realmFile); + } else { + // either a file path OR a custom user service + try { + // check to see if this "file" is a custom user service class + Class<?> realmClass = Class.forName(realm); + service = (IUserService) realmClass.newInstance(); + } catch (Throwable t) { + // typical file path configuration + File realmFile = runtimeManager.getFileOrFolder(Keys.realm.userService, "${baseFolder}/users.conf"); + service = createUserService(realmFile); + } } setUserService(service); } @@ -90,7 +120,7 @@ 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,47 +160,18 @@ 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. * * @param 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<UserModel> getAllUsers() { List<UserModel> users = userService.getAllUsers(); - for (UserModel user : users) { - setAccountType(user); - } return users; } @@ -378,7 +282,8 @@ public class UserManager implements IUserManager { */ @Override public List<String> getAllTeamNames() { - return userService.getAllTeamNames(); + List<String> teams = userService.getAllTeamNames(); + return teams; } /** @@ -404,7 +309,8 @@ public class UserManager implements IUserManager { */ @Override public List<String> getTeamNamesForRepositoryRole(String role) { - return userService.getTeamNamesForRepositoryRole(role); + List<String> 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<String> pushScripts;
- public boolean supportsCredentialChanges;
-
- public boolean supportsDisplayNameChanges;
-
- public boolean supportsEmailAddressChanges;
-
- public boolean supportsTeamMembershipChanges;
-
public ServerSettings() {
settings = new TreeMap<String, SettingModel>();
}
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<TeamModel> { public boolean canAdmin;
public boolean canFork;
public boolean canCreate;
+ public AccountType accountType;
public final Set<String> users = new HashSet<String>();
// retained for backwards-compatibility with RPC clients
@Deprecated
@@ -59,6 +61,7 @@ public class TeamModel implements Serializable, Comparable<TeamModel> { public TeamModel(String name) {
this.name = name;
+ this.accountType = AccountType.LOCAL;
}
/**
@@ -358,6 +361,10 @@ public class TeamModel implements Serializable, Comparable<TeamModel> { }
}
+ 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<String>("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<String>("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<Void>("newTeam", EditTeamPage.class));
- add(adminLinks.setVisible(showAdmin && app().users().supportsTeamMembershipChanges(null)));
+ add(adminLinks.setVisible(showAdmin));
final List<TeamModel> teams = app().users().getAllTeams();
DataView<TeamModel> teamsView = new DataView<TeamModel>("teamRow",
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<Void>("newUser", EditUserPage.class)
- .setVisible(app().users().supportsAddUser()));
+ adminLinks.add(new BookmarkablePageLink<Void>("newUser", EditUserPage.class));
add(adminLinks.setVisible(showAdmin));
final List<UserModel> users = app().users().getAllUsers();
|