@@ -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) | |||
# |
@@ -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, EXTERNAL, CONTAINER, LDAP, REDMINE, SALESFORCE, WINDOWS, PAM, HTPASSWD, HTTPHEADER; | |||
public static AccountType fromString(String value) { | |||
for (AccountType type : AccountType.values()) { |
@@ -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() { | |||
@@ -202,6 +235,11 @@ public abstract class AuthenticationProvider { | |||
} | |||
@Override | |||
public UserModel authenticate(HttpServletRequest httpRequest) { | |||
return null; | |||
} | |||
@Override | |||
public UserModel authenticate(String username, char[] password) { | |||
return null; | |||
@@ -212,6 +250,11 @@ public abstract class AuthenticationProvider { | |||
return AccountType.LOCAL; | |||
} | |||
@Override | |||
public AuthenticationType getAuthenticationType() { | |||
return null; | |||
} | |||
@Override | |||
public boolean supportsCredentialChanges() { | |||
return true; |
@@ -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; | |||
} | |||
} |
@@ -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; | |||
} | |||