From 94b96b5a98bef254128ccc4f5fc0e0c4376324e4 Mon Sep 17 00:00:00 2001
From: James Moger <james.moger@gitblit.com>
Date: Wed, 13 Apr 2011 17:45:22 -0400
Subject: [PATCH] Start of webapp authentication.

---
 src/com/gitblit/wicket/BasePage.html              |    2 
 src/com/gitblit/Build.java                        |    2 
 gitblit.properties                                |    3 
 src/com/gitblit/wicket/AuthorizationStrategy.java |   45 +++++++++
 src/com/gitblit/wicket/GitBlitWebSession.java     |   15 +++
 src/com/gitblit/wicket/LoginPage.java             |  101 ++++++++++++++++++++
 src/com/gitblit/wicket/User.java                  |   23 ++++
 src/com/gitblit/wicket/AdminPage.java             |    2 
 src/com/gitblit/wicket/GitBlitWebApp.java         |   47 ++++++++-
 src/com/gitblit/wicket/BasePage.java              |   10 +
 src/com/gitblit/wicket/LoginPage.html             |   37 +++++++
 11 files changed, 275 insertions(+), 12 deletions(-)

diff --git a/gitblit.properties b/gitblit.properties
index b06adc2..a2e9ab6 100644
--- a/gitblit.properties
+++ b/gitblit.properties
@@ -26,6 +26,9 @@
 # Require authentication for http/https push/pull access of git repositories
 authenticatePushPull = true
 
+# Require authentication to see the web ui
+authenticateWebUI = true
+
 # Simple user realm file to authenticate users for push/pull
 realmFile = users.properties
 
diff --git a/src/com/gitblit/Build.java b/src/com/gitblit/Build.java
index 08202ba..0af7390 100644
--- a/src/com/gitblit/Build.java
+++ b/src/com/gitblit/Build.java
@@ -100,7 +100,7 @@
 	 *            the byte array
 	 * @return the SHA1 checksum
 	 */
-	private static String getSHA1(byte[] data) {
+	public static String getSHA1(byte[] data) {
 		MessageDigest md;
 		try {
 			md = MessageDigest.getInstance("SHA-1");
diff --git a/src/com/gitblit/wicket/SecuredPage.java b/src/com/gitblit/wicket/AdminPage.java
similarity index 88%
rename from src/com/gitblit/wicket/SecuredPage.java
rename to src/com/gitblit/wicket/AdminPage.java
index c3153a6..2f8345b 100644
--- a/src/com/gitblit/wicket/SecuredPage.java
+++ b/src/com/gitblit/wicket/AdminPage.java
@@ -7,5 +7,5 @@
 
 @Retention(RetentionPolicy.RUNTIME)
 @Target(ElementType.TYPE)
-public @interface SecuredPage {
+public @interface AdminPage {
 }
diff --git a/src/com/gitblit/wicket/AuthorizationStrategy.java b/src/com/gitblit/wicket/AuthorizationStrategy.java
new file mode 100644
index 0000000..b99ad6d
--- /dev/null
+++ b/src/com/gitblit/wicket/AuthorizationStrategy.java
@@ -0,0 +1,45 @@
+package com.gitblit.wicket;
+
+import org.apache.wicket.Component;
+import org.apache.wicket.RestartResponseAtInterceptPageException;
+import org.apache.wicket.authorization.IUnauthorizedComponentInstantiationListener;
+import org.apache.wicket.authorization.strategies.page.AbstractPageAuthorizationStrategy;
+
+import com.gitblit.wicket.pages.RepositoriesPage;
+
+public class AuthorizationStrategy extends AbstractPageAuthorizationStrategy implements IUnauthorizedComponentInstantiationListener {
+
+	public AuthorizationStrategy() {
+	}
+
+	@SuppressWarnings({ "unchecked", "rawtypes" })
+	@Override
+	protected boolean isPageAuthorized(Class pageClass) {
+		if (BasePage.class.isAssignableFrom(pageClass))
+			return isAuthorized(pageClass);
+		// Return contruction by default
+		return true;
+	}
+
+	@Override
+	public void onUnauthorizedInstantiation(Component component) {
+		if (component instanceof BasePage) {			
+			GitBlitWebSession session = GitBlitWebSession.get();			
+			if (!session.isLoggedIn())
+				throw new RestartResponseAtInterceptPageException(LoginPage.class);
+			else
+				throw new RestartResponseAtInterceptPageException(RepositoriesPage.class);
+		}
+	}
+
+	protected boolean isAuthorized(Class<? extends BasePage> pageClass) {
+		GitBlitWebSession session = GitBlitWebSession.get();
+		if (!session.isLoggedIn())
+			return false;
+		User user = session.getUser();
+		if (pageClass.isAnnotationPresent(AdminPage.class)) {
+			
+		}
+		return true;
+	}
+}
diff --git a/src/com/gitblit/wicket/BasePage.html b/src/com/gitblit/wicket/BasePage.html
index dabdb56..9ca9f13 100644
--- a/src/com/gitblit/wicket/BasePage.html
+++ b/src/com/gitblit/wicket/BasePage.html
@@ -32,7 +32,7 @@
 			<div style="float:right">
 				<a href="http://gitblit.com"><span wicket:id="gbVersion"></span></a> 
 			</div>
-			<div wicket:id="footerText">[footer text]</div>
+			<div wicket:id="userText">[user text]</div>
 		</div>
 	</body>
 </html>
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/BasePage.java b/src/com/gitblit/wicket/BasePage.java
index 3121804..8084b33 100644
--- a/src/com/gitblit/wicket/BasePage.java
+++ b/src/com/gitblit/wicket/BasePage.java
@@ -26,7 +26,7 @@
 	public BasePage(PageParameters params) {
 		super(params);
 	}
-		
+	
 	protected void setupPage(String repositoryName, String pageName) {
 		if (repositoryName != null && repositoryName.trim().length() > 0) {
 			add(new Label("title", getServerName() + " - " + repositoryName));
@@ -43,7 +43,13 @@
 		add(new Label("pageName", pageName));
 		
 		// footer
-		add(new Label("footerText", ""));
+		User user = null;
+		if (StoredSettings.getBoolean("authenticateWebUI", true)) {
+			user = GitBlitWebSession.get().getUser();
+			add(new Label("userText", "Logout " + user.toString()));
+		} else {
+			add(new Label("userText", ""));
+		}
 		add(new Label("gbVersion", "v" + Constants.VERSION));
 		if (StoredSettings.getBoolean("aggressiveHeapManagement", false)) {
 			System.gc();
diff --git a/src/com/gitblit/wicket/GitBlitWebApp.java b/src/com/gitblit/wicket/GitBlitWebApp.java
index a1de7d6..2bd3179 100644
--- a/src/com/gitblit/wicket/GitBlitWebApp.java
+++ b/src/com/gitblit/wicket/GitBlitWebApp.java
@@ -5,6 +5,7 @@
 import java.util.Date;
 import java.util.List;
 
+import javax.servlet.http.Cookie;
 import javax.servlet.http.HttpServletRequest;
 
 import org.apache.wicket.Application;
@@ -13,6 +14,7 @@
 import org.apache.wicket.Response;
 import org.apache.wicket.Session;
 import org.apache.wicket.protocol.http.WebApplication;
+import org.apache.wicket.protocol.http.WebResponse;
 import org.apache.wicket.protocol.http.request.urlcompressing.UrlCompressingWebRequestProcessor;
 import org.apache.wicket.protocol.http.servlet.ServletWebRequest;
 import org.apache.wicket.request.IRequestCycleProcessor;
@@ -24,6 +26,7 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.gitblit.Constants;
 import com.gitblit.GitBlitServer;
 import com.gitblit.StoredSettings;
 import com.gitblit.utils.JGitUtils;
@@ -31,8 +34,8 @@
 import com.gitblit.wicket.pages.BlobDiffPage;
 import com.gitblit.wicket.pages.BlobPage;
 import com.gitblit.wicket.pages.BranchesPage;
-import com.gitblit.wicket.pages.CommitPage;
 import com.gitblit.wicket.pages.CommitDiffPage;
+import com.gitblit.wicket.pages.CommitPage;
 import com.gitblit.wicket.pages.LogPage;
 import com.gitblit.wicket.pages.PatchPage;
 import com.gitblit.wicket.pages.RawPage;
@@ -44,10 +47,7 @@
 import com.gitblit.wicket.pages.TicGitTicketPage;
 import com.gitblit.wicket.pages.TreePage;
 
-
 public class GitBlitWebApp extends WebApplication {
-
-	public static int PAGING_ITEM_COUNT = 50;
 
 	Logger logger = LoggerFactory.getLogger(GitBlitWebApp.class);
 
@@ -61,8 +61,17 @@
 	public void init() {
 		super.init();
 
+		// Setup page authorization mechanism
+		if (StoredSettings.getBoolean("authenticateWebUI", false)) {
+			AuthorizationStrategy authStrategy = new AuthorizationStrategy();
+			getSecuritySettings().setAuthorizationStrategy(authStrategy);
+			getSecuritySettings().setUnauthorizedComponentInstantiationListener(authStrategy);
+		}
+
 		// Grab Browser info (like timezone, etc)
-		getRequestCycleSettings().setGatherExtendedBrowserInfo(true);
+		if (StoredSettings.getBoolean("useClientTimezone", false)) {
+			getRequestCycleSettings().setGatherExtendedBrowserInfo(true);
+		}
 
 		// setup the standard gitweb-ish urls
 		mount(new MixedParamUrlCodingStrategy("/summary", SummaryPage.class, new String[] { "r" }));
@@ -77,11 +86,13 @@
 		mount(new MixedParamUrlCodingStrategy("/blobdiff", BlobDiffPage.class, new String[] { "r", "h", "f" }));
 		mount(new MixedParamUrlCodingStrategy("/commitdiff", CommitDiffPage.class, new String[] { "r", "h" }));
 		mount(new MixedParamUrlCodingStrategy("/patch", PatchPage.class, new String[] { "r", "h", "f" }));
-		
+
 		// setup extended urls
 		mount(new MixedParamUrlCodingStrategy("/ticgit", TicGitPage.class, new String[] { "r" }));
 		mount(new MixedParamUrlCodingStrategy("/ticgittkt", TicGitTicketPage.class, new String[] { "r", "h", "f" }));
-		
+
+		mount(new MixedParamUrlCodingStrategy("/login", LoginPage.class, new String[] {}));
+
 		repositories = new File(StoredSettings.getString("repositoriesFolder", "repos"));
 		exportAll = StoredSettings.getBoolean("exportAll", true);
 		repositoryResolver = new FileResolver(repositories, exportAll);
@@ -109,6 +120,28 @@
 		return Application.DEPLOYMENT;
 	}
 
+	public User authenticate(String username, char [] password) {
+		return new User(username, password);
+	}
+
+	public User authenticate(Cookie[] cookies) {
+		if (cookies != null && cookies.length > 0) {
+			for (Cookie cookie:cookies) {
+				if (cookie.getName().equals(Constants.NAME)) {
+					String value = cookie.getValue();
+				}
+			}
+		}
+		return null;
+	}
+	
+	public void setCookie(WebResponse response, User user) {
+		Cookie userCookie = new Cookie(Constants.NAME, user.getCookie());
+		userCookie.setMaxAge(Integer.MAX_VALUE);
+		userCookie.setPath("/");
+		response.addCookie(userCookie);
+	}
+
 	public List<String> getRepositoryList() {
 		return JGitUtils.getRepositoryList(repositories, exportAll, StoredSettings.getBoolean("nestedRepositories", true));
 	}
diff --git a/src/com/gitblit/wicket/GitBlitWebSession.java b/src/com/gitblit/wicket/GitBlitWebSession.java
index 038118e..b2106e7 100644
--- a/src/com/gitblit/wicket/GitBlitWebSession.java
+++ b/src/com/gitblit/wicket/GitBlitWebSession.java
@@ -12,6 +12,8 @@
 	private static final long serialVersionUID = 1L;
 
 	protected TimeZone timezone = null;
+	
+	private User user = null;
 
 	public GitBlitWebSession(Request request) {
 		super(request);
@@ -19,6 +21,19 @@
 
 	public void invalidate() {
 		super.invalidate();
+		user = null;
+	}
+	
+	public boolean isLoggedIn() {
+		return user != null;
+	}
+	
+	public User getUser() {
+		return user;
+	}
+	
+	public void setUser(User user) {
+		this.user = user;
 	}
 
 	public TimeZone getTimezone() {
diff --git a/src/com/gitblit/wicket/LoginPage.html b/src/com/gitblit/wicket/LoginPage.html
new file mode 100644
index 0000000..adbe64f
--- /dev/null
+++ b/src/com/gitblit/wicket/LoginPage.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"  
+      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  
+      xml:lang="en"  
+      lang="en"> 
+      
+	<!-- Head with Wicket-controlled resources in this package -->
+	<wicket:head>
+   		<title wicket:id="title">[page title]</title>
+   		<wicket:link>
+   			<link rel="stylesheet" type="text/css" href="resources/gitblit.css"/>
+   			<link rel="shortcut icon" href="resources/gitblt-favicon.png" type="image/png" />
+   		</wicket:link>
+	</wicket:head>
+	
+
+	<body onload="document.getElementById('username').focus();">
+	<div>
+		<center>
+			<img wicket:id="logo" /><br/>
+			<span style="font-weight:bold;" wicket:id="name">[name]</span><br/>
+
+			<div>
+				<form style="text-align:center;" wicket:id="loginForm">
+					<p/>
+					Username <input type="text" id="username" wicket:id="username" value=""/>
+					<p/>
+					Password <input type="password"  wicket:id="password" value=""/>
+					<p/>
+					<input type="submit" value="Login" />
+					<div style="background-color:#c7c7c7" wicket:id="feedback"></div>
+				</form>
+			</div>
+		</center>
+	</div>
+	</body>
+</html>
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/LoginPage.java b/src/com/gitblit/wicket/LoginPage.java
new file mode 100644
index 0000000..39b4285
--- /dev/null
+++ b/src/com/gitblit/wicket/LoginPage.java
@@ -0,0 +1,101 @@
+package com.gitblit.wicket;
+
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.wicket.PageParameters;
+import org.apache.wicket.markup.html.WebPage;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.form.Form;
+import org.apache.wicket.markup.html.form.PasswordTextField;
+import org.apache.wicket.markup.html.form.TextField;
+import org.apache.wicket.markup.html.image.ContextImage;
+import org.apache.wicket.markup.html.panel.FeedbackPanel;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.model.Model;
+import org.apache.wicket.protocol.http.WebRequest;
+import org.apache.wicket.protocol.http.WebResponse;
+import org.apache.wicket.protocol.http.servlet.ServletWebRequest;
+
+import com.gitblit.Constants;
+
+public class LoginPage extends WebPage {
+
+	IModel<String> username = new Model<String>("");
+	IModel<String> password = new Model<String>("");
+
+	public LoginPage(PageParameters params) {
+		super(params);
+
+		tryAutomaticLogin();
+
+		add(new Label("title", getServerName()));
+		add(new ContextImage("logo", "gitblt2.png"));
+		add(new Label("name", Constants.NAME));
+
+		Form<Void> loginForm = new LoginForm("loginForm");
+		loginForm.add(new TextField<String>("username", username));
+		loginForm.add(new PasswordTextField("password", password));
+		loginForm.add(new FeedbackPanel("feedback"));
+		add(loginForm);
+	}
+	
+	protected String getServerName() {
+		ServletWebRequest servletWebRequest = (ServletWebRequest) getRequest();
+		HttpServletRequest req = servletWebRequest.getHttpServletRequest();
+		return req.getServerName();
+	}
+
+	class LoginForm extends Form<Void> {
+		private static final long serialVersionUID = 1L;
+
+		public LoginForm(String id) {
+			super(id);
+		}
+
+		@Override
+		public void onSubmit() {
+			String username = LoginPage.this.username.getObject();
+			char [] password = LoginPage.this.password.getObject().toCharArray();
+
+			User user = GitBlitWebApp.get().authenticate(username, password);
+			if (user == null)
+				error("Invalid username or password!");
+			else
+				loginUser(user);
+		}
+	}
+
+	private void tryAutomaticLogin() {
+		User user = null;
+
+		// Grab cookie from Browser Session
+		Cookie[] cookies = ((WebRequest) getRequestCycle().getRequest()).getCookies();
+		if (cookies != null && cookies.length > 0) {
+			user = GitBlitWebApp.get().authenticate(cookies);
+		}
+
+		// Login the user
+		loginUser(user);
+	}
+
+	private void loginUser(User user) {
+		if (user != null) {
+			GitBlitWebSession session = GitBlitWebSession.get();
+
+			// Set Cookie
+			WebResponse response = (WebResponse) getRequestCycle().getResponse();
+			GitBlitWebApp.get().setCookie(response, user);
+			
+			// track user object so that we do not have to continue
+			// re-authenticating on each request.
+			session.setUser(user);
+
+			// Redirect to original page OR to first available tab
+			if (!continueToOriginalDestination()) {
+				// Redirect to home page
+				setResponsePage(session.getApplication().getHomePage());
+			}
+		}
+	}
+}
diff --git a/src/com/gitblit/wicket/User.java b/src/com/gitblit/wicket/User.java
new file mode 100644
index 0000000..fb49b40
--- /dev/null
+++ b/src/com/gitblit/wicket/User.java
@@ -0,0 +1,23 @@
+package com.gitblit.wicket;
+
+import com.gitblit.Build;
+import com.gitblit.Constants;
+
+public class User {
+	
+	private String username;
+	private char [] password;
+	
+	public User(String username, char [] password) {
+		this.username = username;
+		this.password = password;
+	}
+	
+	public String getCookie() {
+		return Build.getSHA1((Constants.NAME + username + new String(password)).getBytes());
+	}
+	
+	public String toString() {
+		return username;
+	}
+}

--
Gitblit v1.9.1