From 37fa664c58df034607edf2485a1414b3417b2755 Mon Sep 17 00:00:00 2001
From: James Moger <james.moger@gitblit.com>
Date: Mon, 03 Dec 2012 16:59:17 -0500
Subject: [PATCH] Consolidate authentication techniques and support container principals (issue-68)

---
 src/com/gitblit/GitBlit.java              |  103 ++++++++++++++++++++++++++++-----
 docs/04_releases.mkd                      |    6 +
 src/com/gitblit/AuthenticationFilter.java |   43 +-------------
 src/com/gitblit/Constants.java            |    2 
 4 files changed, 94 insertions(+), 60 deletions(-)

diff --git a/docs/04_releases.mkd b/docs/04_releases.mkd
index 3f03160..bf57d11 100644
--- a/docs/04_releases.mkd
+++ b/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
diff --git a/src/com/gitblit/AuthenticationFilter.java b/src/com/gitblit/AuthenticationFilter.java
index 64aa441..eb6e95b 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.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;
 	}
 
 	/**
diff --git a/src/com/gitblit/Constants.java b/src/com/gitblit/Constants.java
index 4669c4c..d152651 100644
--- a/src/com/gitblit/Constants.java
+++ b/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();
diff --git a/src/com/gitblit/GitBlit.java b/src/com/gitblit/GitBlit.java
index 69135c4..02906ee 100644
--- a/src/com/gitblit/GitBlit.java
+++ b/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;
 	}

--
Gitblit v1.9.1