Joel Johnson
2015-06-12 46f61d3990813d488454ce48596620e9c1cded1a
implement an HTTP header AuthenticationProvider
1 files added
4 files modified
277 ■■■■■ changed files
src/main/distrib/data/defaults.properties 37 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/Constants.java 4 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/auth/AuthenticationProvider.java 53 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/auth/HttpHeaderAuthProvider.java 161 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/manager/AuthenticationManager.java 22 ●●●●● patch | view | raw | blame | history
src/main/distrib/data/defaults.properties
@@ -817,6 +817,7 @@
# Valid providers are:
#
#    htpasswd
#    httpheader
#    ldap
#    pam
#    redmine
@@ -1739,6 +1740,42 @@
# 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)
#
src/main/java/com/gitblit/Constants.java
@@ -574,7 +574,7 @@
    }
    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 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()) {
src/main/java/com/gitblit/auth/AuthenticationProvider.java
@@ -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;
@@ -73,6 +76,8 @@
        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 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 @@
     * 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 @@
     * 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 @@
     * 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 @@
            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 @@
        }
        @Override
        public UserModel authenticate(HttpServletRequest httpRequest) {
            return null;
        }
        @Override
        public UserModel authenticate(String username, char[] password) {
            return null;
        }
@@ -213,6 +251,11 @@
        }
        @Override
        public AuthenticationType getAuthenticationType() {
            return null;
        }
        @Override
        public boolean supportsCredentialChanges() {
            return true;
        }
src/main/java/com/gitblit/auth/HttpHeaderAuthProvider.java
New file
@@ -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;
    }
}
src/main/java/com/gitblit/manager/AuthenticationManager.java
@@ -41,6 +41,7 @@
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 @@
        // 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 @@
    }
    /**
     * 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 @@
    /**
     * 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 @@
                }
            }
        }
        // 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;
    }