James Moger
2012-12-03 37fa664c58df034607edf2485a1414b3417b2755
Consolidate authentication techniques and support container principals (issue-68)
4 files modified
154 ■■■■■ changed files
docs/04_releases.mkd 6 ●●●●● patch | view | raw | blame | history
src/com/gitblit/AuthenticationFilter.java 43 ●●●●● patch | view | raw | blame | history
src/com/gitblit/Constants.java 2 ●●● patch | view | raw | blame | history
src/com/gitblit/GitBlit.java 103 ●●●● patch | view | raw | blame | history
docs/04_releases.mkd
@@ -72,7 +72,7 @@
#### changes
- Access restricted servlets (e.g. DownloadZip, RSS, etc) will try to authenticate any Gitblit cookie found in the request before resorting to BASIC authentication.
- All access restricted servlets (e.g. DownloadZip, RSS, etc) will try to authenticate using X509 certificates, container principals, cookies, and BASIC headers, in that order.
- Added *groovy* and *scala* to *web.prettyPrintExtensions*
- Added short commit id column to log and history tables (issue 168)
- Teams can now specify the *admin*, *create*, and *fork* roles to simplify user administration
@@ -83,15 +83,17 @@
- Emit a warning in the log file if running on a Tomcat-based servlet container which is unfriendly to %2F forward-slash url encoding AND Gitblit is configured to mount parameters with %2F forward-slash url encoding (Github/jpyeron, issue 126)
- LDAP admin attribute setting is now consistent with LDAP teams setting and admin teams list.  
If *realm.ldap.maintainTeams==true* **AND** *realm.ldap.admins* is not empty, then User.canAdmin() is controlled by LDAP administrative team membership.  Otherwise, User.canAdmin() is controlled by Gitblit.
- Support servlet container authentication for existing UserModels (issue 68)
#### dependency changes
- updated to Jetty 7.6.7
- updated to Jetty 7.6.8
- updated to JGit 2.1.0.201209190230-r
- updated to Groovy 1.8.8
- updated to Wicket 1.4.21
- updated to Lucene 3.6.1
- updated to BouncyCastle 1.47
- updated to MarkdownPapers 1.3.2
- added JCalendar 1.3.2
- added Commons-Compress 1.4.1
- added XZ for Java 1.0
src/com/gitblit/AuthenticationFilter.java
@@ -16,9 +16,7 @@
package com.gitblit;
import java.io.IOException;
import java.nio.charset.Charset;
import java.security.Principal;
import java.text.MessageFormat;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
@@ -37,7 +35,6 @@
import org.slf4j.LoggerFactory;
import com.gitblit.models.UserModel;
import com.gitblit.utils.Base64;
import com.gitblit.utils.StringUtils;
/**
@@ -51,9 +48,7 @@
 */
public abstract class AuthenticationFilter implements Filter {
    protected static final String BASIC = "Basic";
    protected static final String CHALLENGE = BASIC + " realm=\"" + Constants.NAME + "\"";
    protected static final String CHALLENGE = "Basic realm=\"" + Constants.NAME + "\"";
    protected static final String SESSION_SECURED = "com.gitblit.secured";
@@ -103,40 +98,8 @@
     * @return user
     */
    protected UserModel getUser(HttpServletRequest httpRequest) {
        UserModel user = null;
        // try request authentication
        user = GitBlit.self().authenticate(httpRequest);
        if (user != null) {
            return user;
        } else if (requiresClientCertificate()) {
            // http request does not have a valid certificate
            // and the filter requires one
            return null;
        }
        // look for client authorization credentials in header
        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 = GitBlit.self().authenticate(username, password);
                if (user != null) {
                    return user;
                }
            }
            if (GitBlit.isDebugMode()) {
                logger.info(MessageFormat.format("AUTH: invalid credentials ({0})", credentials));
            }
        }
        return null;
        UserModel user = GitBlit.self().authenticate(httpRequest, requiresClientCertificate());
        return user;
    }
    /**
src/com/gitblit/Constants.java
@@ -399,7 +399,7 @@
    }
    public static enum AuthenticationType {
        CREDENTIALS, COOKIE, CERTIFICATE;
        CREDENTIALS, COOKIE, CERTIFICATE, CONTAINER;
        
        public boolean isStandard() {
            return ordinal() <= COOKIE.ordinal();
src/com/gitblit/GitBlit.java
@@ -24,6 +24,8 @@
import java.lang.reflect.Field;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.Charset;
import java.security.Principal;
import java.text.MessageFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
@@ -98,6 +100,7 @@
import com.gitblit.models.TeamModel;
import com.gitblit.models.UserModel;
import com.gitblit.utils.ArrayUtils;
import com.gitblit.utils.Base64;
import com.gitblit.utils.ByteFormat;
import com.gitblit.utils.ContainerUtils;
import com.gitblit.utils.DeepCopier;
@@ -567,6 +570,20 @@
     * @return a user object or null
     */
    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
     */
    public UserModel authenticate(HttpServletRequest httpRequest, boolean requiresCertificate) {
        // try to authenticate by certificate
        boolean checkValidity = settings.getBoolean(Keys.git.enforceCertificateValidity, true);
        String [] oids = getStrings(Keys.git.certificateUsernameOIDs).toArray(new String[0]);
@@ -574,38 +591,84 @@
        if (model != null) {
            // grab real user model and preserve certificate serial number
            UserModel user = getUserModel(model.username);
            X509Metadata metadata = HttpUtils.getCertificateMetadata(httpRequest);
            if (user != null) {
                RequestCycle requestCycle = RequestCycle.get();
                if (requestCycle != null) {
                    // flag the Wicket session, if this is a Wicket request
                    GitBlitWebSession session = GitBlitWebSession.get();
                    session.authenticationType = AuthenticationType.CERTIFICATE;
                }
                X509Metadata metadata = HttpUtils.getCertificateMetadata(httpRequest);
                flagWicketSession(AuthenticationType.CERTIFICATE);
                logger.info(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) {
            UserModel user = getUserModel(principal.getName());
            if (user != null) {
                flagWicketSession(AuthenticationType.CONTAINER);
                logger.info(MessageFormat.format("{0} authenticated by servlet container principal from {1}",
                        user.username, httpRequest.getRemoteAddr()));
                return user;
            } else {
                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
        Cookie[] cookies = httpRequest.getCookies();
        if (allowCookieAuthentication() && cookies != null && cookies.length > 0) {
            // Grab cookie from Browser Session
            UserModel user = authenticate(cookies);
        if (allowCookieAuthentication()) {
            UserModel user = authenticate(httpRequest.getCookies());
            if (user != null) {
                RequestCycle requestCycle = RequestCycle.get();
                if (requestCycle != null) {
                    // flag the Wicket session, if this is a Wicket request
                    GitBlitWebSession session = GitBlitWebSession.get();
                    session.authenticationType = AuthenticationType.COOKIE;
                }
                flagWicketSession(AuthenticationType.COOKIE);
                logger.info(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.info(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 ({1}) from {2}",
                            username, credentials, httpRequest.getRemoteAddr()));
                }
            }
        }
        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;
        }
    }
    /**
@@ -693,6 +756,9 @@
     * @return true if successful
     */
    public boolean deleteUser(String username) {
        if (StringUtils.isEmpty(username)) {
            return false;
        }
        return userService.deleteUser(username);
    }
@@ -704,6 +770,9 @@
     * @return a user object or null
     */
    public UserModel getUserModel(String username) {
        if (StringUtils.isEmpty(username)) {
            return null;
        }
        UserModel user = userService.getUserModel(username);        
        return user;
    }