From 37fa664c58df034607edf2485a1414b3417b2755 Mon Sep 17 00:00:00 2001 From: James Moger Date: Mon, 3 Dec 2012 16:59:17 -0500 Subject: [PATCH] Consolidate authentication techniques and support container principals (issue-68) --- docs/04_releases.mkd | 6 +- src/com/gitblit/AuthenticationFilter.java | 43 +-------- src/com/gitblit/Constants.java | 2 +- src/com/gitblit/GitBlit.java | 103 ++++++++++++++++++---- 4 files changed, 94 insertions(+), 60 deletions(-) diff --git a/docs/04_releases.mkd b/docs/04_releases.mkd index 3f03160a..bf57d118 100644 --- a/docs/04_releases.mkd +++ b/docs/04_releases.mkd @@ -72,7 +72,7 @@ This is extreme and should be considered carefully since it affects every https #### 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 @@ This is extreme and should be considered carefully since it affects every https - 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 diff --git a/src/com/gitblit/AuthenticationFilter.java b/src/com/gitblit/AuthenticationFilter.java index 64aa4411..eb6e95b7 100644 --- a/src/com/gitblit/AuthenticationFilter.java +++ b/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.Logger; import org.slf4j.LoggerFactory; import com.gitblit.models.UserModel; -import com.gitblit.utils.Base64; import com.gitblit.utils.StringUtils; /** @@ -51,9 +48,7 @@ import com.gitblit.utils.StringUtils; */ 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 @@ public abstract class AuthenticationFilter implements Filter { * @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; } /** diff --git a/src/com/gitblit/Constants.java b/src/com/gitblit/Constants.java index 4669c4c9..d152651b 100644 --- a/src/com/gitblit/Constants.java +++ b/src/com/gitblit/Constants.java @@ -399,7 +399,7 @@ public class Constants { } public static enum AuthenticationType { - CREDENTIALS, COOKIE, CERTIFICATE; + CREDENTIALS, COOKIE, CERTIFICATE, CONTAINER; public boolean isStandard() { return ordinal() <= COOKIE.ordinal(); diff --git a/src/com/gitblit/GitBlit.java b/src/com/gitblit/GitBlit.java index 69135c49..02906ee9 100644 --- a/src/com/gitblit/GitBlit.java +++ b/src/com/gitblit/GitBlit.java @@ -24,6 +24,8 @@ import java.io.InputStreamReader; 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.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.ContainerUtils; import com.gitblit.utils.DeepCopier; @@ -567,6 +570,20 @@ public class GitBlit implements ServletContextListener { * @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,39 +591,85 @@ public class GitBlit implements ServletContextListener { 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; + } + } /** * Open a file resource using the Servlet container. @@ -693,6 +756,9 @@ public class GitBlit implements ServletContextListener { * @return true if successful */ public boolean deleteUser(String username) { + if (StringUtils.isEmpty(username)) { + return false; + } return userService.deleteUser(username); } @@ -704,6 +770,9 @@ public class GitBlit implements ServletContextListener { * @return a user object or null */ public UserModel getUserModel(String username) { + if (StringUtils.isEmpty(username)) { + return null; + } UserModel user = userService.getUserModel(username); return user; } -- 2.39.5