diff options
author | James Moger <james.moger@gmail.com> | 2015-12-10 09:55:23 -0500 |
---|---|---|
committer | James Moger <james.moger@gmail.com> | 2015-12-10 09:55:23 -0500 |
commit | 7b7b0d54b606e5a7d63ea39ec8918968f612d61d (patch) | |
tree | ce2e6e349123a59cb04957eb1dcaa6d170952c51 /src | |
parent | 280c0c041ed830a4d5874b60adf1b72760326c6a (diff) | |
parent | fd0fc5fc9a13456c4038d03cbc6c6b3beed38910 (diff) | |
download | gitblit-7b7b0d54b606e5a7d63ea39ec8918968f612d61d.tar.gz gitblit-7b7b0d54b606e5a7d63ea39ec8918968f612d61d.zip |
Merge pull request #980 from mrjoel/mrjoel-httpheaders
Refactor authentication for servlet HTTP header handler
Diffstat (limited to 'src')
-rw-r--r-- | src/main/distrib/data/defaults.properties | 37 | ||||
-rw-r--r-- | src/main/java/com/gitblit/ConfigUserService.java | 3 | ||||
-rw-r--r-- | src/main/java/com/gitblit/Constants.java | 4 | ||||
-rw-r--r-- | src/main/java/com/gitblit/auth/AuthenticationProvider.java | 53 | ||||
-rw-r--r-- | src/main/java/com/gitblit/auth/HttpHeaderAuthProvider.java | 161 | ||||
-rw-r--r-- | src/main/java/com/gitblit/manager/AuthenticationManager.java | 22 | ||||
-rw-r--r-- | src/site/setup_authentication.mkd | 12 |
7 files changed, 280 insertions, 12 deletions
diff --git a/src/main/distrib/data/defaults.properties b/src/main/distrib/data/defaults.properties index ce6267a5..403b7417 100644 --- a/src/main/distrib/data/defaults.properties +++ b/src/main/distrib/data/defaults.properties @@ -817,6 +817,7 @@ realm.userService = ${baseFolder}/users.conf # Valid providers are: # # htpasswd +# httpheader # ldap # pam # redmine @@ -1739,6 +1740,42 @@ realm.pam.serviceName = system-auth # SINCE 1.3.2 realm.htpasswd.userfile = ${baseFolder}/htpasswd +# The name of the HTTP header containing the user name to trust as authenticated +# default: none +# +# WARNING: only use this mechanism if your requests are coming from a trusted +# and secure source such as a self managed reverse proxy! +# +# RESTART REQUIRED +# SINCE 1.7.2 +realm.httpheader.userheader = + +# The name of the HTTP header containing the team names of which the user is a member. +# If this is defined, then only groups from the headers will be available, whereas +# if this remains undefined, then local groups will be used. +# +# This setting requires that you have configured realm.httpheader.userheader. +# +# default: none +# +# RESTART REQUIRED +# SINCE 1.7.2 +realm.httpheader.teamheader = + +# The regular expression pattern used to separate team names in the team header value +# default: , +# +# This setting requires that you have configured realm.httpheader.teamheader +# +# RESTART REQUIRED +# SINCE 1.7.2 +realm.httpheader.teamseparator = , + +# Auto-creates user accounts when successfully authenticated based on HTTP headers. +# +# SINCE 1.7.2 +realm.httpheader.autoCreateAccounts = false + # Restrict the Salesforce user to members of this org. # default: 0 (i.e. do not check the Org ID) # diff --git a/src/main/java/com/gitblit/ConfigUserService.java b/src/main/java/com/gitblit/ConfigUserService.java index 200ec8a6..6d7230f7 100644 --- a/src/main/java/com/gitblit/ConfigUserService.java +++ b/src/main/java/com/gitblit/ConfigUserService.java @@ -890,9 +890,6 @@ public class ConfigUserService implements IUserService { 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 = AccountType.EXTERNAL;
- }
user.disabled = config.getBoolean(USER, username, DISABLED, false);
user.organizationalUnit = config.getString(USER, username, ORGANIZATIONALUNIT);
user.organization = config.getString(USER, username, ORGANIZATION);
diff --git a/src/main/java/com/gitblit/Constants.java b/src/main/java/com/gitblit/Constants.java index 4aa8c0ca..0a99953a 100644 --- a/src/main/java/com/gitblit/Constants.java +++ b/src/main/java/com/gitblit/Constants.java @@ -574,7 +574,7 @@ public class Constants { }
public static enum AuthenticationType {
- PUBLIC_KEY, CREDENTIALS, COOKIE, CERTIFICATE, CONTAINER;
+ PUBLIC_KEY, CREDENTIALS, COOKIE, CERTIFICATE, CONTAINER, HTTPHEADER;
public boolean isStandard() {
return ordinal() <= COOKIE.ordinal();
@@ -582,7 +582,7 @@ public class Constants { }
public static enum AccountType {
- LOCAL, EXTERNAL, CONTAINER, LDAP, REDMINE, SALESFORCE, WINDOWS, PAM, HTPASSWD;
+ LOCAL, CONTAINER, LDAP, REDMINE, SALESFORCE, WINDOWS, PAM, HTPASSWD, HTTPHEADER;
public static AccountType fromString(String value) {
for (AccountType type : AccountType.values()) {
diff --git a/src/main/java/com/gitblit/auth/AuthenticationProvider.java b/src/main/java/com/gitblit/auth/AuthenticationProvider.java index c56c1843..0bfe2351 100644 --- a/src/main/java/com/gitblit/auth/AuthenticationProvider.java +++ b/src/main/java/com/gitblit/auth/AuthenticationProvider.java @@ -18,11 +18,14 @@ package com.gitblit.auth; import java.io.File; import java.math.BigInteger; +import javax.servlet.http.HttpServletRequest; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.gitblit.Constants.AccountType; import com.gitblit.Constants.Role; +import com.gitblit.Constants.AuthenticationType; import com.gitblit.IStoredSettings; import com.gitblit.manager.IRuntimeManager; import com.gitblit.manager.IUserManager; @@ -73,6 +76,8 @@ public abstract class AuthenticationProvider { return serviceName; } + public abstract AuthenticationType getAuthenticationType(); + protected void setCookie(UserModel user, char [] password) { // create a user cookie if (StringUtils.isEmpty(user.cookie) && !ArrayUtils.isEmpty(password)) { @@ -116,14 +121,32 @@ public abstract class AuthenticationProvider { public abstract void stop(); + /** + * Used to handle requests for requests for pages requiring authentication. + * This allows authentication to occur based on the contents of the request + * itself. + * + * @param httpRequest + * @return + */ + public abstract UserModel authenticate(HttpServletRequest httpRequest); + + /** + * Used to authentication user/password credentials, both for login form + * and HTTP Basic authentication processing. + * + * @param username + * @param password + * @return + */ public abstract UserModel authenticate(String username, char[] password); public abstract AccountType getAccountType(); /** - * Does the user service support changes to credentials? + * Returns true if the users's credentials can be changed. * - * @return true or false + * @return true if the authentication provider supports credential changes * @since 1.0.0 */ public abstract boolean supportsCredentialChanges(); @@ -132,7 +155,7 @@ public abstract class AuthenticationProvider { * Returns true if the user's display name can be changed. * * @param user - * @return true if the user service supports display name changes + * @return true if the authentication provider supports display name changes */ public abstract boolean supportsDisplayNameChanges(); @@ -140,7 +163,7 @@ public abstract class AuthenticationProvider { * Returns true if the user's email address can be changed. * * @param user - * @return true if the user service supports email address changes + * @return true if the authentication provider supports email address changes */ public abstract boolean supportsEmailAddressChanges(); @@ -148,7 +171,7 @@ public abstract class AuthenticationProvider { * Returns true if the user's team memberships can be changed. * * @param user - * @return true if the user service supports team membership changes + * @return true if the authentication provider supports team membership changes */ public abstract boolean supportsTeamMembershipChanges(); @@ -180,6 +203,16 @@ public abstract class AuthenticationProvider { super(serviceName); } + @Override + public UserModel authenticate(HttpServletRequest httpRequest) { + return null; + } + + @Override + public AuthenticationType getAuthenticationType() { + return AuthenticationType.CREDENTIALS; + } + @Override public void stop() { @@ -203,6 +236,11 @@ public abstract class AuthenticationProvider { } @Override + public UserModel authenticate(HttpServletRequest httpRequest) { + return null; + } + + @Override public UserModel authenticate(String username, char[] password) { return null; } @@ -213,6 +251,11 @@ public abstract class AuthenticationProvider { } @Override + public AuthenticationType getAuthenticationType() { + return null; + } + + @Override public boolean supportsCredentialChanges() { return true; } diff --git a/src/main/java/com/gitblit/auth/HttpHeaderAuthProvider.java b/src/main/java/com/gitblit/auth/HttpHeaderAuthProvider.java new file mode 100644 index 00000000..3a9c5399 --- /dev/null +++ b/src/main/java/com/gitblit/auth/HttpHeaderAuthProvider.java @@ -0,0 +1,161 @@ +/* + * Copyright 2015 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.HashSet; +import java.util.Set; + +import javax.servlet.http.HttpServletRequest; + +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.Constants.Role; +import com.gitblit.Keys; +import com.gitblit.models.TeamModel; +import com.gitblit.models.UserModel; +import com.gitblit.utils.StringUtils; + +public class HttpHeaderAuthProvider extends AuthenticationProvider { + + protected final Logger logger = LoggerFactory.getLogger(getClass()); + + protected String userHeaderName; + protected String teamHeaderName; + protected String teamHeaderSeparator; + + public HttpHeaderAuthProvider() { + super("httpheader"); + } + + @Override + public void setup() { + // Load HTTP header configuration + userHeaderName = settings.getString(Keys.realm.httpheader.userheader, null); + teamHeaderName = settings.getString(Keys.realm.httpheader.teamheader, null); + teamHeaderSeparator = settings.getString(Keys.realm.httpheader.teamseparator, ","); + + if (StringUtils.isEmpty(userHeaderName)) { + logger.warn("HTTP Header authentication is enabled, but no header is not defined in " + Keys.realm.httpheader.userheader); + } + } + + @Override + public void stop() {} + + + @Override + public UserModel authenticate(HttpServletRequest httpRequest) { + // Try to authenticate using custom HTTP header if user header is defined + if (!StringUtils.isEmpty(userHeaderName)) { + String headerUserName = httpRequest.getHeader(userHeaderName); + if (!StringUtils.isEmpty(headerUserName) && !userManager.isInternalAccount(headerUserName)) { + // We have a user, try to load team names as well + Set<TeamModel> userTeams = new HashSet<>(); + if (!StringUtils.isEmpty(teamHeaderName)) { + String headerTeamValue = httpRequest.getHeader(teamHeaderName); + if (!StringUtils.isEmpty(headerTeamValue)) { + String[] headerTeamNames = headerTeamValue.split(teamHeaderSeparator); + for (String teamName : headerTeamNames) { + teamName = teamName.trim(); + if (!StringUtils.isEmpty(teamName)) { + TeamModel team = userManager.getTeamModel(teamName); + if (null == team) { + // Create teams here so they can marked with the correct AccountType + team = new TeamModel(teamName); + team.accountType = AccountType.HTTPHEADER; + updateTeam(team); + } + userTeams.add(team); + } + } + } + } + + UserModel user = userManager.getUserModel(headerUserName); + if (user != null) { + // If team header is provided in request, reset all team memberships, even if resetting to empty set + if (!StringUtils.isEmpty(teamHeaderName)) { + user.teams.clear(); + user.teams.addAll(userTeams); + } + updateUser(user); + return user; + } else if (settings.getBoolean(Keys.realm.httpheader.autoCreateAccounts, false)) { + // auto-create user from HTTP header + user = new UserModel(headerUserName.toLowerCase()); + user.displayName = headerUserName; + user.password = Constants.EXTERNAL_ACCOUNT; + user.accountType = AccountType.HTTPHEADER; + user.teams.addAll(userTeams); + updateUser(user); + return user; + } + } + } + + return null; + } + + @Override + public UserModel authenticate(String username, char[] password){ + // Username/password is not supported for HTTP header authentication + return null; + } + + @Override + public AccountType getAccountType() { + return AccountType.HTTPHEADER; + } + + @Override + public AuthenticationType getAuthenticationType() { + return AuthenticationType.HTTPHEADER; + } + + @Override + public boolean supportsCredentialChanges() { + return false; + } + + @Override + public boolean supportsDisplayNameChanges() { + return false; + } + + @Override + public boolean supportsEmailAddressChanges() { + return false; + } + + @Override + public boolean supportsTeamMembershipChanges() { + return StringUtils.isEmpty(teamHeaderName); + } + + @Override + public boolean supportsRoleChanges(UserModel user, Role role) { + return true; + } + + @Override + public boolean supportsRoleChanges(TeamModel team, Role role) { + return true; + } +}
\ No newline at end of file diff --git a/src/main/java/com/gitblit/manager/AuthenticationManager.java b/src/main/java/com/gitblit/manager/AuthenticationManager.java index 7e0b07be..f092bfed 100644 --- a/src/main/java/com/gitblit/manager/AuthenticationManager.java +++ b/src/main/java/com/gitblit/manager/AuthenticationManager.java @@ -41,6 +41,7 @@ import com.gitblit.Keys; import com.gitblit.auth.AuthenticationProvider; import com.gitblit.auth.AuthenticationProvider.UsernamePasswordAuthenticationProvider; import com.gitblit.auth.HtpasswdAuthProvider; +import com.gitblit.auth.HttpHeaderAuthProvider; import com.gitblit.auth.LdapAuthProvider; import com.gitblit.auth.PAMAuthProvider; import com.gitblit.auth.RedmineAuthProvider; @@ -92,6 +93,7 @@ public class AuthenticationManager implements IAuthenticationManager { // map of shortcut provider names providerNames = new HashMap<String, Class<? extends AuthenticationProvider>>(); providerNames.put("htpasswd", HtpasswdAuthProvider.class); + providerNames.put("httpheader", HttpHeaderAuthProvider.class); providerNames.put("ldap", LdapAuthProvider.class); providerNames.put("pam", PAMAuthProvider.class); providerNames.put("redmine", RedmineAuthProvider.class); @@ -170,7 +172,11 @@ public class AuthenticationManager implements IAuthenticationManager { } /** - * Authenticate a user based on HTTP request parameters. + * Used to handle authentication for page requests. + * + * This allows authentication to occur based on the contents of the request + * itself. If no configured @{AuthenticationProvider}s authenticate succesffully, + * a request for login will be shown. * * Authentication by X509Certificate is tried first and then by cookie. * @@ -185,7 +191,7 @@ public class AuthenticationManager implements IAuthenticationManager { /** * Authenticate a user based on HTTP request parameters. * - * Authentication by servlet container principal, X509Certificate, cookie, + * Authentication by custom HTTP header, servlet container principal, X509Certificate, cookie, * and finally BASIC header. * * @param httpRequest @@ -319,6 +325,18 @@ public class AuthenticationManager implements IAuthenticationManager { } } } + + // Check each configured AuthenticationProvider + for (AuthenticationProvider ap : authenticationProviders) { + UserModel authedUser = ap.authenticate(httpRequest); + if (null != authedUser) { + flagRequest(httpRequest, ap.getAuthenticationType(), authedUser.username); + logger.debug(MessageFormat.format("{0} authenticated by {1} from {2} for {3}", + authedUser.username, ap.getServiceName(), httpRequest.getRemoteAddr(), + httpRequest.getPathInfo())); + return validateAuthentication(authedUser, ap.getAuthenticationType()); + } + } return null; } diff --git a/src/site/setup_authentication.mkd b/src/site/setup_authentication.mkd index a3bf4451..71136675 100644 --- a/src/site/setup_authentication.mkd +++ b/src/site/setup_authentication.mkd @@ -8,6 +8,7 @@ Gitblit supports additional authentication mechanisms aside from it's internal o * Windows authentication
* PAM authentication
* Htpasswd authentication
+* HTTP header authentication
* Redmine auhentication
* Salesforce.com authentication
* Servlet container authentication
@@ -101,6 +102,17 @@ Htpasswd authentication allows you to maintain your user credentials in an Apach realm.authenticationProviders = htpasswd
realm.htpasswd.userFile = /path/to/htpasswd
+### HTTP Header Authentication
+
+HTTP header authentication allows you to use existing authentication performed by a trusted frontend, such as a reverse proxy. Ensure that when used, gitblit is ONLY availabe via the trusted frontend, otherwise it is vulnerable to a user adding the header explicitly.
+
+By default, no user or team header is defined, which results in all authentication failing this mechanism. The user header can also be defined while leaving the team header undefined, which causes users to be authenticated from the headers, but team memberships to be maintained locally.
+
+ realm.httpheader.userheader = REMOTE_USER
+ realm.httpheader.teamheader = X-GitblitExample-GroupNames
+ realm.httpheader.teamseparator = ,
+ realm.httpheader.autoCreateAccounts = false
+
### Redmine Authentication
You may authenticate your users against a Redmine installation as long as your Redmine install has properly enabled [API authentication](http://www.redmine.org/projects/redmine/wiki/Rest_Api#Authentication). This user service only supports user authentication; it does not support team creation based on Redmine groups. Redmine administrators will also be Gitblit administrators.
|