From aa6d43e8b28ff73d69a920e9b3a7b284cfce00c3 Mon Sep 17 00:00:00 2001 From: James Moger Date: Wed, 20 Nov 2013 21:19:34 -0500 Subject: [PATCH] Extract SessionManager from GitBlit singleton Change-Id: I85c9dfc1413f858dc28d731a0bf653828626e127 --- src/main/java/com/gitblit/DaggerModule.java | 10 +- src/main/java/com/gitblit/GitBlit.java | 249 +------------ .../com/gitblit/manager/ISessionManager.java | 10 +- .../com/gitblit/manager/SessionManager.java | 340 ++++++++++++++++++ 4 files changed, 360 insertions(+), 249 deletions(-) create mode 100644 src/main/java/com/gitblit/manager/SessionManager.java diff --git a/src/main/java/com/gitblit/DaggerModule.java b/src/main/java/com/gitblit/DaggerModule.java index 7f12fca3..0fb8bbf4 100644 --- a/src/main/java/com/gitblit/DaggerModule.java +++ b/src/main/java/com/gitblit/DaggerModule.java @@ -30,6 +30,7 @@ import com.gitblit.manager.ISessionManager; 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 @@ public class DaggerModule { 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() { diff --git a/src/main/java/com/gitblit/GitBlit.java b/src/main/java/com/gitblit/GitBlit.java index f7e76273..e012aece 100644 --- a/src/main/java/com/gitblit/GitBlit.java +++ b/src/main/java/com/gitblit/GitBlit.java @@ -29,8 +29,6 @@ import java.lang.reflect.Field; 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.InitialContext; 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 org.slf4j.Logger; 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.SettingModel; 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.ModelUtils; 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 @@ import dagger.ObjectGraph; */ @WebListener public class GitBlit extends DaggerContextListener - implements ISessionManager, - IRepositoryManager, + implements IRepositoryManager, IProjectManager, IFederationManager, IGitblitManager { @@ -474,201 +464,6 @@ public class GitBlit extends DaggerContextListener return null; } - /** - * 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 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 @@ -680,39 +475,6 @@ public class GitBlit extends DaggerContextListener 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 public UserModel getFederationUser() { // the federation user is an administrator @@ -3101,7 +2863,7 @@ public class GitBlit extends DaggerContextListener getManager(IRuntimeManager.class), getManager(INotificationManager.class), getManager(IUserManager.class), - this, + getManager(ISessionManager.class), this, this, this, @@ -3210,6 +2972,7 @@ public class GitBlit extends DaggerContextListener startManager(injector, INotificationManager.class); startManager(injector, IUserManager.class); + startManager(injector, ISessionManager.class); repositoriesFolder = getRepositoriesFolder(); @@ -3555,12 +3318,6 @@ public class GitBlit extends DaggerContextListener return cloneModel; } - @Override - public void logout(HttpServletResponse response, UserModel user) { - setCookie(response, null); - getManager(IUserManager.class).logout(user); - } - @Override protected Object [] getModules() { return new Object [] { new DaggerModule(this) }; diff --git a/src/main/java/com/gitblit/manager/ISessionManager.java b/src/main/java/com/gitblit/manager/ISessionManager.java index 09e306ac..29f17096 100644 --- a/src/main/java/com/gitblit/manager/ISessionManager.java +++ b/src/main/java/com/gitblit/manager/ISessionManager.java @@ -20,7 +20,7 @@ import javax.servlet.http.HttpServletResponse; 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 @@ public interface ISessionManager { */ 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); /** diff --git a/src/main/java/com/gitblit/manager/SessionManager.java b/src/main/java/com/gitblit/manager/SessionManager.java new file mode 100644 index 00000000..e6c3a2d3 --- /dev/null +++ b/src/main/java/com/gitblit/manager/SessionManager.java @@ -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 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 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; +// } +} -- 2.39.5