Skip to content

Commit

Permalink
Merge pull request #980 from mrjoel/mrjoel-httpheaders
Browse files Browse the repository at this point in the history
Refactor authentication for servlet HTTP header handler
  • Loading branch information
gitblit committed Dec 10, 2015
2 parents 280c0c0 + fd0fc5f commit 7b7b0d5
Show file tree
Hide file tree
Showing 7 changed files with 280 additions and 12 deletions.
37 changes: 37 additions & 0 deletions src/main/distrib/data/defaults.properties
Expand Up @@ -817,6 +817,7 @@ realm.userService = ${baseFolder}/users.conf
# Valid providers are:
#
# htpasswd
# httpheader
# ldap
# pam
# redmine
Expand Down Expand Up @@ -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)
#
Expand Down
3 changes: 0 additions & 3 deletions src/main/java/com/gitblit/ConfigUserService.java
Expand Up @@ -890,9 +890,6 @@ protected synchronized void read() {
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);
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/com/gitblit/Constants.java
Expand Up @@ -574,15 +574,15 @@ public boolean exceeds(GCStatus s) {
}

public static enum AuthenticationType {
PUBLIC_KEY, CREDENTIALS, COOKIE, CERTIFICATE, CONTAINER;
PUBLIC_KEY, CREDENTIALS, COOKIE, CERTIFICATE, CONTAINER, HTTPHEADER;

public boolean isStandard() {
return ordinal() <= COOKIE.ordinal();
}
}

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()) {
Expand Down
53 changes: 48 additions & 5 deletions src/main/java/com/gitblit/auth/AuthenticationProvider.java
Expand Up @@ -18,11 +18,14 @@
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;
Expand Down Expand Up @@ -73,6 +76,8 @@ public String getServiceName() {
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)) {
Expand Down Expand Up @@ -116,14 +121,32 @@ protected void updateTeam(TeamModel teamModel) {

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();
Expand All @@ -132,23 +155,23 @@ protected void updateTeam(TeamModel teamModel) {
* 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();

/**
* 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();

/**
* 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();

Expand Down Expand Up @@ -180,6 +203,16 @@ protected UsernamePasswordAuthenticationProvider(String serviceName) {
super(serviceName);
}

@Override
public UserModel authenticate(HttpServletRequest httpRequest) {
return null;
}

@Override
public AuthenticationType getAuthenticationType() {
return AuthenticationType.CREDENTIALS;
}

@Override
public void stop() {

Expand All @@ -202,6 +235,11 @@ public void stop() {

}

@Override
public UserModel authenticate(HttpServletRequest httpRequest) {
return null;
}

@Override
public UserModel authenticate(String username, char[] password) {
return null;
Expand All @@ -212,6 +250,11 @@ public AccountType getAccountType() {
return AccountType.LOCAL;
}

@Override
public AuthenticationType getAuthenticationType() {
return null;
}

@Override
public boolean supportsCredentialChanges() {
return true;
Expand Down
161 changes: 161 additions & 0 deletions 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;
}
}

0 comments on commit 7b7b0d5

Please sign in to comment.