James Moger
2013-11-20 aa6d43e8b28ff73d69a920e9b3a7b284cfce00c3
Extract SessionManager from GitBlit singleton

Change-Id: I85c9dfc1413f858dc28d731a0bf653828626e127
1 files added
3 files modified
609 ■■■■■ changed files
src/main/java/com/gitblit/DaggerModule.java 10 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/GitBlit.java 249 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/manager/ISessionManager.java 10 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/manager/SessionManager.java 340 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/DaggerModule.java
@@ -30,6 +30,7 @@
import com.gitblit.manager.IUserManager;
import com.gitblit.manager.NotificationManager;
import com.gitblit.manager.RuntimeManager;
import com.gitblit.manager.SessionManager;
import com.gitblit.manager.UserManager;
import com.gitblit.wicket.GitBlitWebApp;
import com.gitblit.wicket.GitblitWicketFilter;
@@ -105,8 +106,13 @@
        return new UserManager(runtimeManager);
    }
    @Provides @Singleton ISessionManager provideSessionManager() {
        return gitblit;
    @Provides @Singleton ISessionManager provideSessionManager(
            IRuntimeManager runtimeManager,
            IUserManager userManager) {
        return new SessionManager(
                runtimeManager,
                userManager);
    }
    @Provides @Singleton IRepositoryManager provideRepositoryManager() {
src/main/java/com/gitblit/GitBlit.java
@@ -29,8 +29,6 @@
import java.lang.reflect.Type;
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;
@@ -61,11 +59,8 @@
import javax.naming.NamingException;
import javax.servlet.ServletContext;
import javax.servlet.annotation.WebListener;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.wicket.RequestCycle;
import org.apache.wicket.resource.ContextRelativeResource;
import org.apache.wicket.util.resource.ResourceStreamNotFoundException;
import org.eclipse.jgit.lib.Repository;
@@ -80,7 +75,6 @@
import com.gitblit.Constants.AccessPermission;
import com.gitblit.Constants.AccessRestrictionType;
import com.gitblit.Constants.AuthenticationType;
import com.gitblit.Constants.AuthorizationControl;
import com.gitblit.Constants.CommitMessageRenderer;
import com.gitblit.Constants.FederationRequest;
@@ -120,7 +114,6 @@
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.CommitCache;
import com.gitblit.utils.ContainerUtils;
@@ -136,8 +129,6 @@
import com.gitblit.utils.ObjectCache;
import com.gitblit.utils.StringUtils;
import com.gitblit.utils.TimeUtils;
import com.gitblit.utils.X509Utils.X509Metadata;
import com.gitblit.wicket.GitBlitWebSession;
import com.gitblit.wicket.GitblitWicketFilter;
import com.gitblit.wicket.WicketUtils;
import com.google.gson.Gson;
@@ -161,8 +152,7 @@
 */
@WebListener
public class GitBlit extends DaggerContextListener
                     implements ISessionManager,
                                IRepositoryManager,
                     implements IRepositoryManager,
                                IProjectManager,
                                IFederationManager,
                                IGitblitManager {
@@ -475,201 +465,6 @@
    }
    /**
     * Returns true if the username represents an internal account
     *
     * @param username
     * @return true if the specified username represents an internal account
     */
    protected boolean isInternalAccount(String username) {
        return !StringUtils.isEmpty(username)
                && (username.equalsIgnoreCase(Constants.FEDERATION_USER)
                        || username.equalsIgnoreCase(UserModel.ANONYMOUS.username));
    }
    /**
     * Authenticate a user based on a username and password.
     *
     * @see IUserService.authenticate(String, char[])
     * @param username
     * @param password
     * @return a user object or null
     */
    @Override
    public UserModel authenticate(String username, char[] password) {
        if (StringUtils.isEmpty(username)) {
            // can not authenticate empty username
            return null;
        }
        String usernameDecoded = StringUtils.decodeUsername(username);
        String pw = new String(password);
        if (StringUtils.isEmpty(pw)) {
            // can not authenticate empty password
            return null;
        }
        // check to see if this is the federation user
        if (canFederate()) {
            if (usernameDecoded.equalsIgnoreCase(Constants.FEDERATION_USER)) {
                List<String> tokens = getFederationTokens();
                if (tokens.contains(pw)) {
                    return getFederationUser();
                }
            }
        }
        // delegate authentication to the user service
        return getManager(IUserManager.class).authenticate(usernameDecoded, password);
    }
    /**
     * Authenticate a user based on their cookie.
     *
     * @param cookies
     * @return a user object or null
     */
    protected UserModel authenticate(Cookie[] cookies) {
        if (getManager(IUserManager.class).supportsCookies()) {
            if (cookies != null && cookies.length > 0) {
                for (Cookie cookie : cookies) {
                    if (cookie.getName().equals(Constants.NAME)) {
                        String value = cookie.getValue();
                        return getManager(IUserManager.class).authenticate(value.toCharArray());
                    }
                }
            }
        }
        return null;
    }
    /**
     * Authenticate a user based on HTTP request parameters.
     *
     * Authentication by X509Certificate is tried first and then by cookie.
     *
     * @param httpRequest
     * @return a user object or null
     */
    @Override
    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
     */
    @Override
    public UserModel authenticate(HttpServletRequest httpRequest, boolean requiresCertificate) {
        // try to authenticate by certificate
        boolean checkValidity = settings.getBoolean(Keys.git.enforceCertificateValidity, true);
        String [] oids = settings.getStrings(Keys.git.certificateUsernameOIDs).toArray(new String[0]);
        UserModel model = HttpUtils.getUserModelFromCertificate(httpRequest, checkValidity, oids);
        if (model != null) {
            // grab real user model and preserve certificate serial number
            UserModel user = getManager(IUserManager.class).getUserModel(model.username);
            X509Metadata metadata = HttpUtils.getCertificateMetadata(httpRequest);
            if (user != null) {
                flagWicketSession(AuthenticationType.CERTIFICATE);
                logger.debug(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) {
            String username = principal.getName();
            if (!StringUtils.isEmpty(username)) {
                boolean internalAccount = isInternalAccount(username);
                UserModel user = getManager(IUserManager.class).getUserModel(username);
                if (user != null) {
                    // existing user
                    flagWicketSession(AuthenticationType.CONTAINER);
                    logger.debug(MessageFormat.format("{0} authenticated by servlet container principal from {1}",
                            user.username, httpRequest.getRemoteAddr()));
                    return user;
                } else if (settings.getBoolean(Keys.realm.container.autoCreateAccounts, false)
                        && !internalAccount) {
                    // auto-create user from an authenticated container principal
                    user = new UserModel(username.toLowerCase());
                    user.displayName = username;
                    user.password = Constants.EXTERNAL_ACCOUNT;
                    getManager(IUserManager.class).updateUserModel(user);
                    flagWicketSession(AuthenticationType.CONTAINER);
                    logger.debug(MessageFormat.format("{0} authenticated and created by servlet container principal from {1}",
                            user.username, httpRequest.getRemoteAddr()));
                    return user;
                } else if (!internalAccount) {
                    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
        if (getManager(IUserManager.class).supportsCookies()) {
            UserModel user = authenticate(httpRequest.getCookies());
            if (user != null) {
                flagWicketSession(AuthenticationType.COOKIE);
                logger.debug(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.debug(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 from {1}",
                            username, 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;
        }
    }
    /**
     * Open a file resource using the Servlet container.
     * @param file to open
     * @return InputStream of the opened file
@@ -678,39 +473,6 @@
    public InputStream getResourceAsStream(String file) throws ResourceStreamNotFoundException {
        ContextRelativeResource res = WicketUtils.getResource(file);
        return res.getResourceStream().getInputStream();
    }
    /**
     * Sets a cookie for the specified user.
     *
     * @param response
     * @param user
     */
    @Override
    public void setCookie(HttpServletResponse response, UserModel user) {
        GitBlitWebSession session = GitBlitWebSession.get();
        boolean standardLogin = session.authenticationType.isStandard();
        if (getManager(IUserManager.class).supportsCookies() && standardLogin) {
            Cookie userCookie;
            if (user == null) {
                // clear cookie for logout
                userCookie = new Cookie(Constants.NAME, "");
            } else {
                // set cookie for login
                String cookie = getManager(IUserManager.class).getCookie(user);
                if (StringUtils.isEmpty(cookie)) {
                    // create empty cookie
                    userCookie = new Cookie(Constants.NAME, "");
                } else {
                    // create real cookie
                    userCookie = new Cookie(Constants.NAME, cookie);
                    userCookie.setMaxAge(Integer.MAX_VALUE);
                }
            }
            userCookie.setPath("/");
            response.addCookie(userCookie);
        }
    }
    @Override
@@ -3101,7 +2863,7 @@
                        getManager(IRuntimeManager.class),
                        getManager(INotificationManager.class),
                        getManager(IUserManager.class),
                        this,
                        getManager(ISessionManager.class),
                        this,
                        this,
                        this,
@@ -3210,6 +2972,7 @@
        startManager(injector, INotificationManager.class);
        startManager(injector, IUserManager.class);
        startManager(injector, ISessionManager.class);
        repositoriesFolder = getRepositoriesFolder();
@@ -3553,12 +3316,6 @@
        // add this clone to the cached model
        addToCachedRepositoryList(cloneModel);
        return cloneModel;
    }
    @Override
    public void logout(HttpServletResponse response, UserModel user) {
        setCookie(response,  null);
        getManager(IUserManager.class).logout(user);
    }
    @Override
src/main/java/com/gitblit/manager/ISessionManager.java
@@ -20,7 +20,7 @@
import com.gitblit.models.UserModel;
public interface ISessionManager {
public interface ISessionManager extends IManager {
    /**
     * Authenticate a user based on HTTP request parameters.
@@ -44,6 +44,14 @@
     */
    UserModel authenticate(HttpServletRequest httpRequest, boolean requiresCertificate);
    /**
     * Authenticate a user based on a username and password.
     *
     * @see IUserService.authenticate(String, char[])
     * @param username
     * @param password
     * @return a user object or null
     */
    UserModel authenticate(String username, char[] password);
    /**
src/main/java/com/gitblit/manager/SessionManager.java
New file
@@ -0,0 +1,340 @@
/*
 * Copyright 2013 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.manager;
import java.nio.charset.Charset;
import java.security.Principal;
import java.text.MessageFormat;
import java.util.List;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.wicket.RequestCycle;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.gitblit.Constants;
import com.gitblit.Constants.AuthenticationType;
import com.gitblit.IStoredSettings;
import com.gitblit.Keys;
import com.gitblit.models.UserModel;
import com.gitblit.utils.Base64;
import com.gitblit.utils.HttpUtils;
import com.gitblit.utils.StringUtils;
import com.gitblit.utils.X509Utils.X509Metadata;
import com.gitblit.wicket.GitBlitWebSession;
/**
 * The session manager handles user login & logout.
 *
 * @author James Moger
 *
 */
public class SessionManager implements ISessionManager {
    private final Logger logger = LoggerFactory.getLogger(getClass());
    private final IStoredSettings settings;
    private final IRuntimeManager runtimeManager;
    private final IUserManager userManager;
    public SessionManager(
            IRuntimeManager runtimeManager,
            IUserManager userManager) {
        this.settings = runtimeManager.getSettings();
        this.runtimeManager = runtimeManager;
        this.userManager = userManager;
    }
    @Override
    public IManager setup() {
        List<String> services = settings.getStrings("realm.authenticationServices");
        for (String service : services) {
            // TODO populate authentication services here
        }
        return this;
    }
    @Override
    public IManager stop() {
        return this;
    }
    /**
     * Authenticate a user based on HTTP request parameters.
     *
     * Authentication by X509Certificate is tried first and then by cookie.
     *
     * @param httpRequest
     * @return a user object or null
     */
    @Override
    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
     */
    @Override
    public UserModel authenticate(HttpServletRequest httpRequest, boolean requiresCertificate) {
        // try to authenticate by certificate
        boolean checkValidity = settings.getBoolean(Keys.git.enforceCertificateValidity, true);
        String [] oids = settings.getStrings(Keys.git.certificateUsernameOIDs).toArray(new String[0]);
        UserModel model = HttpUtils.getUserModelFromCertificate(httpRequest, checkValidity, oids);
        if (model != null) {
            // grab real user model and preserve certificate serial number
            UserModel user = userManager.getUserModel(model.username);
            X509Metadata metadata = HttpUtils.getCertificateMetadata(httpRequest);
            if (user != null) {
                flagWicketSession(AuthenticationType.CERTIFICATE);
                logger.debug(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) {
            String username = principal.getName();
            if (!StringUtils.isEmpty(username)) {
                boolean internalAccount = isInternalAccount(username);
                UserModel user = userManager.getUserModel(username);
                if (user != null) {
                    // existing user
                    flagWicketSession(AuthenticationType.CONTAINER);
                    logger.debug(MessageFormat.format("{0} authenticated by servlet container principal from {1}",
                            user.username, httpRequest.getRemoteAddr()));
                    return user;
                } else if (settings.getBoolean(Keys.realm.container.autoCreateAccounts, false)
                        && !internalAccount) {
                    // auto-create user from an authenticated container principal
                    user = new UserModel(username.toLowerCase());
                    user.displayName = username;
                    user.password = Constants.EXTERNAL_ACCOUNT;
                    userManager.updateUserModel(user);
                    flagWicketSession(AuthenticationType.CONTAINER);
                    logger.debug(MessageFormat.format("{0} authenticated and created by servlet container principal from {1}",
                            user.username, httpRequest.getRemoteAddr()));
                    return user;
                } else if (!internalAccount) {
                    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
        if (userManager.supportsCookies()) {
            UserModel user = authenticate(httpRequest.getCookies());
            if (user != null) {
                flagWicketSession(AuthenticationType.COOKIE);
                logger.debug(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.debug(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 from {1}",
                            username, httpRequest.getRemoteAddr()));
                }
            }
        }
        return null;
    }
    /**
     * Authenticate a user based on their cookie.
     *
     * @param cookies
     * @return a user object or null
     */
    protected UserModel authenticate(Cookie[] cookies) {
        if (userManager.supportsCookies()) {
            if (cookies != null && cookies.length > 0) {
                for (Cookie cookie : cookies) {
                    if (cookie.getName().equals(Constants.NAME)) {
                        String value = cookie.getValue();
                        return userManager.authenticate(value.toCharArray());
                    }
                }
            }
        }
        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;
        }
    }
    /**
     * Authenticate a user based on a username and password.
     *
     * @see IUserService.authenticate(String, char[])
     * @param username
     * @param password
     * @return a user object or null
     */
    @Override
    public UserModel authenticate(String username, char[] password) {
        if (StringUtils.isEmpty(username)) {
            // can not authenticate empty username
            return null;
        }
        String usernameDecoded = StringUtils.decodeUsername(username);
        String pw = new String(password);
        if (StringUtils.isEmpty(pw)) {
            // can not authenticate empty password
            return null;
        }
        // check to see if this is the federation user
//        if (canFederate()) {
//            if (usernameDecoded.equalsIgnoreCase(Constants.FEDERATION_USER)) {
//                List<String> tokens = getFederationTokens();
//                if (tokens.contains(pw)) {
//                    return getFederationUser();
//                }
//            }
//        }
        UserModel user = userManager.authenticate(usernameDecoded, password);
        // try registered external authentication providers
        if (user == null) {
//            for (AuthenticationService service : authenticationServices) {
//                if (service instanceof UsernamePasswordAuthenticationService) {
//                    user = service.authenticate(usernameDecoded, password);
//                    if (user != null) {
//                        // user authenticated
//                        user.accountType = service.getAccountType();
//                        return user;
//                    }
//                }
//            }
        }
        return user;
    }
    /**
     * Sets a cookie for the specified user.
     *
     * @param response
     * @param user
     */
    @Override
    public void setCookie(HttpServletResponse response, UserModel user) {
        GitBlitWebSession session = GitBlitWebSession.get();
        boolean standardLogin = session.authenticationType.isStandard();
        if (userManager.supportsCookies() && standardLogin) {
            Cookie userCookie;
            if (user == null) {
                // clear cookie for logout
                userCookie = new Cookie(Constants.NAME, "");
            } else {
                // set cookie for login
                String cookie = userManager.getCookie(user);
                if (StringUtils.isEmpty(cookie)) {
                    // create empty cookie
                    userCookie = new Cookie(Constants.NAME, "");
                } else {
                    // create real cookie
                    userCookie = new Cookie(Constants.NAME, cookie);
                    userCookie.setMaxAge(Integer.MAX_VALUE);
                }
            }
            userCookie.setPath("/");
            response.addCookie(userCookie);
        }
    }
    /**
     * Logout a user.
     *
     * @param user
     */
    @Override
    public void logout(HttpServletResponse response, UserModel user) {
        setCookie(response,  null);
        userManager.logout(user);
    }
    /**
     * Returns true if the username represents an internal account
     *
     * @param username
     * @return true if the specified username represents an internal account
     */
    protected boolean isInternalAccount(String username) {
        return !StringUtils.isEmpty(username)
                && (username.equalsIgnoreCase(Constants.FEDERATION_USER)
                        || username.equalsIgnoreCase(UserModel.ANONYMOUS.username));
    }
//    protected UserModel getFederationUser() {
//        // the federation user is an administrator
//        UserModel federationUser = new UserModel(Constants.FEDERATION_USER);
//        federationUser.canAdmin = true;
//        return federationUser;
//    }
}