From f988253399ee475aa4f4e60adb95a220f8f88d21 Mon Sep 17 00:00:00 2001
From: James Moger <james.moger@gitblit.com>
Date: Thu, 12 May 2011 17:33:31 -0400
Subject: [PATCH] Moved distribution files. Revised build script. Security revisions.

---
 src/com/gitblit/ILoginService.java                   |   12 
 src/com/gitblit/wicket/pages/RepositoriesPage.java   |   15 
 .gitignore                                           |    1 
 distrib/JavaService64.exe                            |    0 
 src/com/gitblit/wicket/pages/EditUserPage.html       |    4 
 gitblit.properties                                   |    6 
 src/com/gitblit/GitBlit.java                         |   16 +
 src/com/gitblit/wicket/GitBlitWebApp.properties      |   15 
 src/com/gitblit/GitBlitServer.java                   |    2 
 distrib/JavaService.exe                              |    0 
 src/com/gitblit/wicket/pages/RepositoriesPage.html   |    2 
 distrib/users.properties                             |    2 
 src/com/gitblit/wicket/pages/EditRepositoryPage.html |    9 
 users.properties                                     |    3 
 src/com/gitblit/Constants.java                       |   20 
 src/com/gitblit/wicket/pages/SummaryPage.html        |    2 
 src/com/gitblit/wicket/pages/EditRepositoryPage.java |   84 +++++
 distrib/gitblit-stop.cmd                             |    1 
 src/com/gitblit/wicket/pages/EditUserPage.java       |   52 +++
 distrib/gitblit.properties                           |  159 +++++++++++
 src/com/gitblit/wicket/BasePage.java                 |   24 +
 distrib/gitblit.cmd                                  |    1 
 distrib/UninstallService64.bat                       |    0 
 src/com/gitblit/JettyLoginService.java               |  313 +++++++++++++++++++--
 distrib/installService64.bat                         |    0 
 build.xml                                            |   36 ++
 distrib/installService.bat                           |    0 
 src/com/gitblit/wicket/pages/SummaryPage.java        |   19 +
 distrib/UninstallService.bat                         |    0 
 distrib/makekeystore_jdk.cmd                         |    2 
 30 files changed, 703 insertions(+), 97 deletions(-)

diff --git a/.gitignore b/.gitignore
index 10d4ae8..3ad2f7e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,3 +4,4 @@
 /build
 /keystore
 /gitblit.zip
+/gitblit-0.1.0-SNAPSHOT.zip
diff --git a/build.xml b/build.xml
index caaf343..22f2336 100644
--- a/build.xml
+++ b/build.xml
@@ -3,12 +3,35 @@
 
 	<!-- Project Properties -->
 	<property name="project.jar" value="gitblit.jar" />
-	<property name="project.mainclass" value="com.gitblit.Launcher" />
-	<property name="distribution.zipfile" value="gitblit.zip" />
+	<property name="project.mainclass" value="com.gitblit.Launcher" />	
 	<property name="project.build.dir" value="${basedir}/build" />
 
 	<target name="main">
-
+			
+		<!-- extract version number from source code -->
+		<loadfile property="gb.version" srcfile="${basedir}/src/com/gitblit/Constants.java">
+			<filterchain>
+				<linecontains>
+		        	<contains value="public final static String VERSION = "/>
+		    	</linecontains>
+				<striplinebreaks/>
+				<tokenfilter>				    
+					<replacestring from="public final static String VERSION = &quot;" to=""/>
+					<replacestring from="&quot;;" to=""/>
+					<trim />
+				</tokenfilter>
+			</filterchain>
+		</loadfile>
+		<echo>Building Git:Blit ${gb.version}</echo>
+		
+		<!-- copy required distribution files to project folder -->
+		<copy todir="${basedir}" overwrite="false">
+			<fileset dir="${basedir}/distrib">
+				<include name="gitblit.properties" />
+				<include name="users.properties" />
+			</fileset>
+		</copy>
+		
 		<!-- Compile the build tool and execute it.
 			 This downloads missing compile-time dependencies from Maven. -->
 
@@ -53,16 +76,13 @@
 		<mkdir dir="${basedir}/deploy" />
 		<copy todir="${basedir}/deploy" file="${project.jar}" />
 		<copy todir="${basedir}/deploy">
-			<fileset dir="${basedir}/service">
+			<fileset dir="${basedir}/distrib">
 				<include name="**/*" />
-			</fileset>
-			<fileset dir="${basedir}">
-				<include name="*.cmd" />
-				<include name="*.properties" />
 			</fileset>
 		</copy>
 
 		<!-- Create Zip deployment -->
+		<property name="distribution.zipfile" value="gitblit-${gb.version}.zip" />
 		<zip destfile="${distribution.zipfile}">
 			<fileset dir="${basedir}/deploy">
 				<include name="**/*" />
diff --git a/service/JavaService.exe b/distrib/JavaService.exe
similarity index 100%
rename from service/JavaService.exe
rename to distrib/JavaService.exe
Binary files differ
diff --git a/service/JavaService64.exe b/distrib/JavaService64.exe
similarity index 100%
rename from service/JavaService64.exe
rename to distrib/JavaService64.exe
Binary files differ
diff --git a/service/UninstallService.bat b/distrib/UninstallService.bat
similarity index 100%
rename from service/UninstallService.bat
rename to distrib/UninstallService.bat
diff --git a/service/UninstallService64.bat b/distrib/UninstallService64.bat
similarity index 100%
rename from service/UninstallService64.bat
rename to distrib/UninstallService64.bat
diff --git a/distrib/gitblit-stop.cmd b/distrib/gitblit-stop.cmd
new file mode 100644
index 0000000..c139d57
--- /dev/null
+++ b/distrib/gitblit-stop.cmd
@@ -0,0 +1 @@
+@java -jar gitblit.jar --stop
diff --git a/distrib/gitblit.cmd b/distrib/gitblit.cmd
new file mode 100644
index 0000000..ce96a79
--- /dev/null
+++ b/distrib/gitblit.cmd
@@ -0,0 +1 @@
+@java -jar gitblit.jar
diff --git a/distrib/gitblit.properties b/distrib/gitblit.properties
new file mode 100644
index 0000000..6bf33f1
--- /dev/null
+++ b/distrib/gitblit.properties
@@ -0,0 +1,159 @@
+#
+# Git Servlet Settings
+#
+
+# Allow push/pull over http/https with JGit servlet
+git.enableGitServlet = true
+
+# Base folder for repositories
+# Use forward slashes even on Windows!!
+git.repositoriesFolder = c:/git
+
+# Export all repositories
+# if false, each exported repository must have a .git/git-daemon-export-ok file
+git.exportAll = true
+
+# Search repositories folder for nested repositories
+# e.g. /libraries/mylibrary.git
+git.nestedRepositories = true
+
+# The root clone url
+git.cloneUrl = https://localhost/git/
+
+#
+# Authentication Settings
+#
+
+# Require authentication to see everything but the admin pages
+web.authenticateViewPages = false
+
+# Require admin authentication for the admin functions and pages
+web.authenticateAdminPages = true
+
+# Simple user realm file to authenticate users
+realm.realmFile = users.properties
+
+# How to store passwords.
+# Valid values are plain, md5 or crypt (unix style).  Default is md5. 
+realm.passwordStorage = md5
+
+#
+# Git:Blit Web Settings
+#
+# If blank Git:Blit is displayed.
+web.siteName =
+
+# If web.authenticate=true, users with "admin" role can create repositories,
+# create users, and edit repository metadata (owner, description, etc)
+#
+# If web.authenticate=false, any user can execute the aforementioned functions.  
+web.allowAdministration = true
+
+# This is the message display above the repositories table.
+# This can point to a file with Markdown content.
+# Specifying "gitblit" uses the internal welcome message.
+web.repositoriesMessage = gitblit
+
+# Use the client timezone when formatting dates.
+# This uses AJAX to determine the browser's timezone.
+web.useClientTimezone = false
+
+# Date and Time formats
+web.datestampShortFormat = yyyy-MM-dd
+web.datetimestampLongFormat = EEEE, MMMM d, yyyy h:mm a z
+
+# Choose the diff presentation style: gitblt, gitweb, or plain
+web.diffStyle = gitblit
+
+# Control if email addresses are shown in web ui
+web.showEmailAddresses = true
+
+# Shows a combobox in the page links header with commit, committer, and author
+# search selection.  Default search is commit.
+web.showSearchTypeSelection = false
+
+# Generates a line graph of repository activity over time on the Summary page.
+# This is a real-time graph so generation may be expensive. 
+web.generateActivityGraph = true
+
+# The number of commits to display on the summary page
+# Value must exceed 0 else default of 20 is used
+web.summaryCommitCount = 16
+
+# The number of tags/heads to display on the summary page
+# Value must exceed 0 else default of 5 is used
+web.summaryRefsCount = 5
+
+# The number of items to show on a page before showing the first, prev, next
+# pagination links.  A default if 50 is used for any invalid value.
+web.itemsPerPage = 50
+
+# Registered extensions for google-code-prettify
+web.prettyPrintExtensions = c cpp cs css htm html java js php pl prefs properties py rb sh sql xml vb
+
+# Registered extensions for markdown transformation
+web.markdownExtensions = md mkd markdown
+
+# Image extensions
+web.imageExtensions = bmp jpg gif png 
+
+# Registered extensions for binary blobs
+web.binaryExtensions = jar pdf tar.gz zip
+
+# Aggressive heap management will run the garbage collector on every generated
+# page.  This slows down page generation but improves heap consumption. 
+web.aggressiveHeapManagement = true
+
+# Run the webapp in debug mode
+web.debugMode = false
+
+# Enable/disable global regex substitutions (i.e. shared across repositories)
+regex.global = true
+
+# Example global regex substitutions
+# Use !!! to separate the search pattern and the replace pattern
+# searchpattern!!!replacepattern
+#regex.global.bug = \\b(Bug:)(\\s*[#]?|-){0,1}(\\d+)\\b!!!<a href="http://somehost/bug/$3">Bug-Id: $3</a>
+#regex.global.changeid = \\b(Change-Id:\\s*)([A-Za-z0-9]*)\\b!!!<a href="http://somehost/changeid/$2">Change-Id: $2</a>
+
+# Example per-repository regex substitutions overrides global
+#regex.myrepository.bug = \\b(Bug:)(\\s*[#]?|-){0,1}(\\d+)\\b!!!<a href="http://elsewhere/bug/$3">Bug-Id: $3</a>
+
+#
+# Server Settings
+#
+server.tempFolder = temp
+server.log4jPattern = %-5p %d{MM-dd HH:mm:ss.SSS}  %-20.20c{1}  %m%n
+server.log4jPattern.windows = %-5p %m%n
+server.log4jPattern.linux =
+
+
+#
+# Jetty Settings
+#
+
+# Use Jetty NIO connectors.  If false, Jetty Socket connectors will be used.
+server.useNio = true
+
+# Standard http port to serve.  <= 0 disables this connector.
+server.httpPort = 0
+
+# Secure/SSL https port to serve. <= 0 disables this connector.
+server.httpsPort = 443
+
+# Specify the interface for Jetty to bind the standard connector.
+# You may specify an ip or an empty value to bind to all interfaces. 
+server.httpBindInterface = localhost
+
+# Specify the interface for Jetty to bind the secure connector.
+# You may specify an ip or an empty value to bind to all interfaces.
+server.httpsBindInterface = localhost
+
+# Password for SSL keystore.
+# Keystore password and certificate password must match.
+# This is provided for convenience, its probably more secure to set this value
+# using the --storePassword command line parameter.
+server.storePassword = dosomegit
+
+# Port for shutdown monitor to listen on.
+server.shutdownPort = 8081
diff --git a/service/installService.bat b/distrib/installService.bat
similarity index 100%
rename from service/installService.bat
rename to distrib/installService.bat
diff --git a/service/installService64.bat b/distrib/installService64.bat
similarity index 100%
rename from service/installService64.bat
rename to distrib/installService64.bat
diff --git a/distrib/makekeystore_jdk.cmd b/distrib/makekeystore_jdk.cmd
new file mode 100644
index 0000000..34a11b2
--- /dev/null
+++ b/distrib/makekeystore_jdk.cmd
@@ -0,0 +1,2 @@
+@del keystore
+@keytool -keystore keystore -alias localhost -genkey -keyalg RSA -dname "CN=localhost, OU=Git:Blit, O=Git:Blit, L=Some Town, ST=Some State, C=US"
\ No newline at end of file
diff --git a/distrib/users.properties b/distrib/users.properties
new file mode 100644
index 0000000..920f323
--- /dev/null
+++ b/distrib/users.properties
@@ -0,0 +1,2 @@
+# Git:Blit realm file format: username=password,\#permission,repository1,repository2...
+admin=admin,\#admin
diff --git a/gitblit.properties b/gitblit.properties
index 2bdcf2c..b9aae7c 100644
--- a/gitblit.properties
+++ b/gitblit.properties
@@ -31,7 +31,11 @@
 web.authenticateAdminPages = true
 
 # Simple user realm file to authenticate users
-server.realmFile = users.properties
+realm.realmFile = users.properties
+
+# How to store passwords.
+# Valid values are plain, md5 or crypt (unix style).  Default is md5. 
+realm.passwordStorage = md5
 
 #
 # Git:Blit Web Settings
diff --git a/src/com/gitblit/Constants.java b/src/com/gitblit/Constants.java
index 7d1758d..1b4a518 100644
--- a/src/com/gitblit/Constants.java
+++ b/src/com/gitblit/Constants.java
@@ -4,6 +4,8 @@
 
 	public final static String NAME = "Git:Blit";
 
+	// The build script extracts this exact line so be careful editing it
+	// and only use A-Z a-z 0-9 .-_ in the string. 
 	public final static String VERSION = "0.1.0-SNAPSHOT";
 
 	public final static String ADMIN_ROLE = "#admin";
@@ -21,23 +23,17 @@
 			}
 			return NONE;
 		}
-		
+
+		public boolean exceeds(AccessRestrictionType type) {
+			return this.ordinal() > type.ordinal();
+		}
+
 		public boolean atLeast(AccessRestrictionType type) {
 			return this.ordinal() >= type.ordinal();
 		}
 
 		public String toString() {
-			switch (this) {
-			case NONE:
-				return "Anonymous View, Clone, & Push";
-			case PUSH:
-				return "Anonymous View & Clone, Authenticated Push";
-			case CLONE:
-				return "Anonymous View, Authenticated Clone & Push";
-			case VIEW:
-				return "Authenticated View, Clone, & Push";
-			}
-			return "none";
+			return name();
 		}
 	}
 
diff --git a/src/com/gitblit/GitBlit.java b/src/com/gitblit/GitBlit.java
index 40cb388..62ff55e 100644
--- a/src/com/gitblit/GitBlit.java
+++ b/src/com/gitblit/GitBlit.java
@@ -95,10 +95,22 @@
 		userCookie.setPath("/");
 		response.addCookie(userCookie);
 	}
+	
+	public List<String> getAllUsernames() {
+		return loginService.getAllUsernames();
+	}
 
-	public UserModel getUser(String username) {
+	public UserModel getUserModel(String username) {
 		UserModel user = loginService.getUserModel(username);
 		return user;
+	}
+	
+	public List<String> getRepositoryUsers(RepositoryModel repository) {
+		return loginService.getUsernamesForRole(repository.name);
+	}
+	
+	public boolean setRepositoryUsers(RepositoryModel repository, List<String> repositoryUsers) {
+		return loginService.setUsernamesForRole(repository.name, repositoryUsers);
 	}
 
 	public void editUserModel(UserModel user, boolean isCreate) throws GitBlitException {
@@ -206,7 +218,7 @@
 	}
 
 	public void configureContext(IStoredSettings settings) {
-		logger.info("Configure GitBlit from " + settings.toString());
+		logger.info("Using configuration from " + settings.toString());
 		this.storedSettings = settings;
 		repositoriesFolder = new File(settings.getString(Keys.git.repositoriesFolder, "repos"));
 		exportAll = settings.getBoolean(Keys.git.exportAll, true);
diff --git a/src/com/gitblit/GitBlitServer.java b/src/com/gitblit/GitBlitServer.java
index f5ed91a..17b9e7a 100644
--- a/src/com/gitblit/GitBlitServer.java
+++ b/src/com/gitblit/GitBlitServer.java
@@ -458,7 +458,7 @@
 		 * Authentication Parameters
 		 */
 		@Parameter(names = { "--realmFile" }, description = "Users Realm Hash File")
-		public String realmFile = fileSettings.getString(Keys.server.realmFile, "users.properties");
+		public String realmFile = fileSettings.getString(Keys.realm.realmFile, "users.properties");
 
 		/*
 		 * JETTY Parameters
diff --git a/src/com/gitblit/ILoginService.java b/src/com/gitblit/ILoginService.java
index d0c5d13..242ff80 100644
--- a/src/com/gitblit/ILoginService.java
+++ b/src/com/gitblit/ILoginService.java
@@ -1,5 +1,7 @@
 package com.gitblit;
 
+import java.util.List;
+
 import com.gitblit.wicket.models.UserModel;
 
 public interface ILoginService {
@@ -14,4 +16,14 @@
 	
 	boolean deleteUserModel(UserModel model);
 	
+	List<String> getAllUsernames();
+	
+	List<String> getUsernamesForRole(String role);
+	
+	boolean setUsernamesForRole(String role, List<String> usernames);
+	
+	boolean renameRole(String oldRole, String newRole);
+	
+	boolean deleteRole(String role);
+	
 }
diff --git a/src/com/gitblit/JettyLoginService.java b/src/com/gitblit/JettyLoginService.java
index 4b43964..fb510ee 100644
--- a/src/com/gitblit/JettyLoginService.java
+++ b/src/com/gitblit/JettyLoginService.java
@@ -5,9 +5,13 @@
 import java.io.FileWriter;
 import java.io.IOException;
 import java.security.Principal;
+import java.text.MessageFormat;
 import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Properties;
+import java.util.Set;
 
 import javax.security.auth.Subject;
 
@@ -16,11 +20,15 @@
 import org.eclipse.jetty.security.MappedLoginService;
 import org.eclipse.jetty.server.UserIdentity;
 import org.eclipse.jetty.util.log.Log;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import com.gitblit.utils.StringUtils;
 import com.gitblit.wicket.models.UserModel;
 
 public class JettyLoginService extends MappedLoginService implements ILoginService {
+
+	private final Logger logger = LoggerFactory.getLogger(JettyLoginService.class);
 
 	private final File realmFile;
 
@@ -44,8 +52,9 @@
 		for (Principal principal : identity.getSubject().getPrincipals()) {
 			if (principal instanceof RolePrincipal) {
 				RolePrincipal role = (RolePrincipal) principal;
-				if (role.getName().charAt(0) != '#') {
-					user.addRepository(role.getName().substring(1));
+				String roleName = role.getName();
+				if (roleName.charAt(0) != '#') {
+					user.addRepository(roleName);
 				}
 			}
 		}
@@ -75,9 +84,20 @@
 					}
 					break;
 				default:
-					model.addRepository(name.substring(1));
+					model.addRepository(name);
 				}
 			}
+		}
+		// Retrieve the password from the realm file.
+		// Stupid, I know, but the password is buried within protected inner
+		// classes in private variables. Too much work to reflectively retrieve.
+		try {
+			Properties allUsers = readRealmFile();
+			String value = allUsers.getProperty(username);
+			String password = value.split(",")[0];
+			model.setPassword(password);
+		} catch (Throwable t) {
+			logger.error(MessageFormat.format("Failed to read password for user {0}!", username), t);
 		}
 		return model;
 	}
@@ -85,15 +105,8 @@
 	@Override
 	public boolean updateUserModel(UserModel model) {
 		try {
-			Properties properties = new Properties();
-			FileReader reader = new FileReader(realmFile);
-			properties.load(reader);
-			reader.close();
-
-			ArrayList<String> roles = new ArrayList<String>();
-
-			// Repositories
-			roles.addAll(model.getRepositories());
+			Properties allUsers = readRealmFile();
+			ArrayList<String> roles = new ArrayList<String>(model.getRepositories());
 
 			// Permissions
 			if (model.canAdmin()) {
@@ -109,21 +122,15 @@
 			}
 			// trim trailing comma
 			sb.setLength(sb.length() - 1);
+			allUsers.put(model.getUsername(), sb.toString());
 
-			// Update realm file
-			File realmFileCopy = new File(realmFile.getAbsolutePath() + ".tmp");
-			FileWriter writer = new FileWriter(realmFileCopy);
-			properties.put(model.getUsername(), sb.toString());
-			properties.store(writer, null);
-			writer.close();
-			realmFile.delete();
-			realmFileCopy.renameTo(realmFile);
+			writeRealmFile(allUsers);
 
 			// Update login service
 			putUser(model.getUsername(), Credential.getCredential(model.getPassword()), roles.toArray(new String[0]));
 			return true;
 		} catch (Throwable t) {
-			t.printStackTrace();
+			logger.error(MessageFormat.format("Failed to update user model {0}!", model.getUsername()), t);
 		}
 		return false;
 	}
@@ -132,27 +139,256 @@
 	public boolean deleteUserModel(UserModel model) {
 		try {
 			// Read realm file
-			Properties properties = new Properties();
-			FileReader reader = new FileReader(realmFile);
-			properties.load(reader);
-			reader.close();
-			properties.remove(model.getUsername());
-
-			// Update realm file
-			File realmFileCopy = new File(realmFile.getAbsolutePath() + ".tmp");
-			FileWriter writer = new FileWriter(realmFileCopy);
-			properties.store(writer, null);
-			writer.close();
-			realmFile.delete();
-			realmFileCopy.renameTo(realmFile);
+			Properties allUsers = readRealmFile();
+			allUsers.remove(model.getUsername());
+			writeRealmFile(allUsers);
 
 			// Drop user from map
 			_users.remove(model.getUsername());
 			return true;
 		} catch (Throwable t) {
-			t.printStackTrace();
+			logger.error(MessageFormat.format("Failed to delete user model {0}!", model.getUsername()), t);
 		}
 		return false;
+	}
+
+	@Override
+	public List<String> getAllUsernames() {
+		List<String> list = new ArrayList<String>();
+		list.addAll(_users.keySet());
+		return list;
+	}
+
+	@Override
+	public List<String> getUsernamesForRole(String role) {
+		List<String> list = new ArrayList<String>();
+		try {
+			Properties allUsers = readRealmFile();
+			for (String username : allUsers.stringPropertyNames()) {
+				String value = allUsers.getProperty(username);
+				String[] values = value.split(",");
+				// skip first value (password)
+				for (int i = 1; i < values.length; i++) {
+					String r = values[i];
+					if (r.equalsIgnoreCase(role)) {
+						list.add(username);
+						break;
+					}
+				}
+			}
+		} catch (Throwable t) {
+			logger.error(MessageFormat.format("Failed to get usernames for role {0}!", role), t);
+		}
+		return list;
+	}
+
+	@Override
+	public boolean setUsernamesForRole(String role, List<String> usernames) {
+		try {
+			Set<String> specifiedUsers = new HashSet<String>(usernames);
+			Set<String> needsAddRole = new HashSet<String>(specifiedUsers);
+			Set<String> needsRemoveRole = new HashSet<String>();
+
+			// identify users which require add and remove role
+			Properties allUsers = readRealmFile();
+			for (String username : allUsers.stringPropertyNames()) {
+				String value = allUsers.getProperty(username);
+				String[] values = value.split(",");
+				// skip first value (password)
+				for (int i = 1; i < values.length; i++) {
+					String r = values[i];
+					if (r.equalsIgnoreCase(role)) {
+						// user has role, check against revised user list
+						if (specifiedUsers.contains(username)) {
+							needsAddRole.remove(username);
+						} else {
+							// remove role from user
+							needsRemoveRole.add(username);
+						}
+						break;
+					}
+				}
+			}
+
+			// add roles to users
+			for (String user : needsAddRole) {
+				String userValues = allUsers.getProperty(user);
+				userValues += ("," + role);
+				allUsers.put(user, userValues);
+				String[] values = userValues.split(",");
+				String password = values[0];
+				String[] roles = new String[values.length - 1];
+				System.arraycopy(values, 1, roles, 0, values.length - 1);
+				putUser(user, Credential.getCredential(password), roles);
+			}
+
+			// remove role from user
+			for (String user : needsRemoveRole) {
+				String[] values = allUsers.getProperty(user).split(",");
+				String password = values[0];
+				StringBuilder sb = new StringBuilder();
+				sb.append(password);
+				sb.append(',');
+				List<String> revisedRoles = new ArrayList<String>();
+				// skip first value (password)
+				for (int i = 1; i < values.length; i++) {
+					String value = values[i];
+					if (!value.equalsIgnoreCase(role)) {
+						revisedRoles.add(value);
+						sb.append(value);
+						sb.append(',');
+					}
+				}
+				sb.setLength(sb.length() - 1);
+
+				// update properties
+				allUsers.put(user, sb.toString());
+
+				// update memory
+				putUser(user, Credential.getCredential(password), revisedRoles.toArray(new String[0]));
+			}
+
+			// persist changes
+			writeRealmFile(allUsers);
+			return true;
+		} catch (Throwable t) {
+			logger.error(MessageFormat.format("Failed to set usernames for role {0}!", role), t);
+		}
+		return false;
+	}
+
+	@Override
+	public boolean renameRole(String oldRole, String newRole) {
+		try {
+			Properties allUsers = readRealmFile();
+			Set<String> needsRenameRole = new HashSet<String>();
+
+			// identify users which require role rename
+			for (String username : allUsers.stringPropertyNames()) {
+				String value = allUsers.getProperty(username);
+				String[] roles = value.split(",");
+				// skip first value (password)
+				for (int i = 1; i < roles.length; i++) {
+					String r = roles[i];
+					if (r.equalsIgnoreCase(oldRole)) {
+						needsRenameRole.remove(username);
+						break;
+					}
+				}
+			}
+
+			// rename role for identified users
+			for (String user : needsRenameRole) {
+				String userValues = allUsers.getProperty(user);
+				String[] values = userValues.split(",");
+				String password = values[0];
+				StringBuilder sb = new StringBuilder();
+				sb.append(password);
+				sb.append(',');
+				List<String> revisedRoles = new ArrayList<String>();
+				revisedRoles.add(newRole);
+				// skip first value (password)
+				for (int i = 1; i < values.length; i++) {
+					String value = values[i];
+					if (!value.equalsIgnoreCase(oldRole)) {
+						revisedRoles.add(value);
+						sb.append(value);
+						sb.append(',');
+					}
+				}
+				sb.setLength(sb.length() - 1);
+
+				// update properties
+				allUsers.put(user, sb.toString());
+
+				// update memory
+				putUser(user, Credential.getCredential(password), revisedRoles.toArray(new String[0]));
+			}
+
+			// persist changes
+			writeRealmFile(allUsers);
+			return true;
+		} catch (Throwable t) {
+			logger.error(MessageFormat.format("Failed to rename role {0} to {1}!", oldRole, newRole), t);
+		}
+		return false;
+	}
+
+	@Override
+	public boolean deleteRole(String role) {
+		try {
+			Properties allUsers = readRealmFile();
+			Set<String> needsDeleteRole = new HashSet<String>();
+
+			// identify users which require role rename
+			for (String username : allUsers.stringPropertyNames()) {
+				String value = allUsers.getProperty(username);
+				String[] roles = value.split(",");
+				// skip first value (password)
+				for (int i = 1; i < roles.length; i++) {
+					String r = roles[i];
+					if (r.equalsIgnoreCase(role)) {
+						needsDeleteRole.remove(username);
+						break;
+					}
+				}
+			}
+
+			// delete role for identified users
+			for (String user : needsDeleteRole) {
+				String userValues = allUsers.getProperty(user);
+				String[] values = userValues.split(",");
+				String password = values[0];
+				StringBuilder sb = new StringBuilder();
+				sb.append(password);
+				sb.append(',');
+				List<String> revisedRoles = new ArrayList<String>();
+				// skip first value (password)
+				for (int i = 1; i < values.length; i++) {
+					String value = values[i];
+					if (!value.equalsIgnoreCase(role)) {
+						revisedRoles.add(value);
+						sb.append(value);
+						sb.append(',');
+					}
+				}
+				sb.setLength(sb.length() - 1);
+
+				// update properties
+				allUsers.put(user, sb.toString());
+
+				// update memory
+				putUser(user, Credential.getCredential(password), revisedRoles.toArray(new String[0]));
+			}
+
+			// persist changes
+			writeRealmFile(allUsers);
+		} catch (Throwable t) {
+			logger.error(MessageFormat.format("Failed to delete role {0}!", role), t);
+		}
+		return false;
+	}
+
+	private Properties readRealmFile() throws IOException {
+		Properties allUsers = new Properties();
+		FileReader reader = new FileReader(realmFile);
+		allUsers.load(reader);
+		reader.close();
+		return allUsers;
+	}
+
+	private void writeRealmFile(Properties properties) throws IOException {
+		// Update realm file
+		File realmFileCopy = new File(realmFile.getAbsolutePath() + ".tmp");
+		FileWriter writer = new FileWriter(realmFileCopy);
+		properties.store(writer, "# Git:Blit realm file format: username=password,\\#permission,repository1,repository2...");
+		writer.close();
+		if (realmFileCopy.exists() && realmFileCopy.length() > 0) {
+			realmFile.delete();
+			realmFileCopy.renameTo(realmFile);
+		} else {
+			throw new IOException("Failed to save realmfile!");
+		}
 	}
 
 	/* ------------------------------------------------------------ */
@@ -163,13 +399,10 @@
 
 		if (Log.isDebugEnabled())
 			Log.debug("Load " + this + " from " + realmFile);
-		Properties properties = new Properties();
-		FileReader reader = new FileReader(realmFile);
-		properties.load(reader);
-		reader.close();
+		Properties allUsers = readRealmFile();
 
 		// Map Users
-		for (Map.Entry<Object, Object> entry : properties.entrySet()) {
+		for (Map.Entry<Object, Object> entry : allUsers.entrySet()) {
 			String username = ((String) entry.getKey()).trim();
 			String credentials = ((String) entry.getValue()).trim();
 			String roles = null;
diff --git a/src/com/gitblit/wicket/BasePage.java b/src/com/gitblit/wicket/BasePage.java
index 6125f2a..733f4f7 100644
--- a/src/com/gitblit/wicket/BasePage.java
+++ b/src/com/gitblit/wicket/BasePage.java
@@ -1,5 +1,7 @@
 package com.gitblit.wicket;
 
+import java.util.LinkedHashMap;
+import java.util.Map;
 import java.util.TimeZone;
 
 import javax.servlet.http.HttpServletRequest;
@@ -14,6 +16,7 @@
 import org.slf4j.LoggerFactory;
 
 import com.gitblit.Constants;
+import com.gitblit.Constants.AccessRestrictionType;
 import com.gitblit.GitBlit;
 import com.gitblit.Keys;
 import com.gitblit.wicket.pages.SummaryPage;
@@ -68,6 +71,27 @@
 		}
 	}
 
+	protected Map<AccessRestrictionType, String> getAccessRestrictions() {
+		Map<AccessRestrictionType, String> map = new LinkedHashMap<AccessRestrictionType, String>();
+		for (AccessRestrictionType type : AccessRestrictionType.values()) {
+			switch (type) {
+			case NONE:
+				map.put(type, getString("gb.notRestricted"));
+				break;
+			case PUSH:
+				map.put(type, getString("gb.pushRestricted"));
+				break;
+			case CLONE:
+				map.put(type, getString("gb.cloneRestricted"));
+				break;
+			case VIEW:
+				map.put(type, getString("gb.viewRestricted"));
+				break;
+			}
+		}
+		return map;
+	}
+
 	protected TimeZone getTimeZone() {
 		return GitBlit.self().settings().getBoolean(Keys.web.useClientTimezone, false) ? GitBlitWebSession.get().getTimezone() : TimeZone.getDefault();
 	}
diff --git a/src/com/gitblit/wicket/GitBlitWebApp.properties b/src/com/gitblit/wicket/GitBlitWebApp.properties
index f2fe232..3fe24d0 100644
--- a/src/com/gitblit/wicket/GitBlitWebApp.properties
+++ b/src/com/gitblit/wicket/GitBlitWebApp.properties
@@ -80,9 +80,14 @@
 gb.editUsers = edit users
 gb.password = password
 gb.confirmPassword = confirm password
-gb.repositories = repositories
+gb.restrictedRepositories = restricted repositories
 gb.canAdmin can admin
-gb.notRestricted = open repository
-gb.cloneRestricted = clone-restricted repository
-gb.pushRestricted = push-restricted repository
-gb.viewRestricted = view-restricted repository
\ No newline at end of file
+gb.notRestricted = anonymous view, clone, & push
+gb.pushRestricted = authenticated push
+gb.cloneRestricted = authenticated clone & push
+gb.viewRestricted = authenticated view, clone, & push
+gb.useTicketsDescription = distributed Ticgit issues
+gb.useDocsDescription = enumerates Markdown documentation in repository
+gb.showRemoteBranchesDescription = show remote branches
+gb.canAdminDescription = can administer Git:Blit server
+gb.permittedUsers = permitted users
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/pages/EditRepositoryPage.html b/src/com/gitblit/wicket/pages/EditRepositoryPage.html
index 5872347..db5ab22 100644
--- a/src/com/gitblit/wicket/pages/EditRepositoryPage.html
+++ b/src/com/gitblit/wicket/pages/EditRepositoryPage.html
@@ -18,10 +18,11 @@
 				<tr><th><wicket:message key="gb.name"></wicket:message></th><td class="edit"><input type="text" wicket:id="name" id="name" size="40" tabindex="1" /></td></tr>
 				<tr><th><wicket:message key="gb.description"></wicket:message></th><td class="edit"><input type="text" wicket:id="description" size="40" tabindex="2" /></td></tr>
 				<tr><th><wicket:message key="gb.owner"></wicket:message></th><td class="edit"><input type="text" wicket:id="owner" size="40" tabindex="3" /></td></tr>
-				<tr><th><wicket:message key="gb.accessRestriction"></wicket:message></th><td class="edit"><select wicket:id="accessRestriction" tabindex="4" /></td></tr>				
-				<tr><th><wicket:message key="gb.enableTickets"></wicket:message></th><td class="edit"><input type="checkbox" wicket:id="useTickets" tabindex="5" /> &nbsp;<i>distributed Ticgit issues</i></td></tr>
-				<tr><th><wicket:message key="gb.enableDocs"></wicket:message></th><td class="edit"><input type="checkbox" wicket:id="useDocs" tabindex="6" /> &nbsp;<i>enumerates Markdown documentation in repository</i></td></tr>
-				<tr><th><wicket:message key="gb.showRemoteBranches"></wicket:message></th><td class="edit"><input type="checkbox" wicket:id="showRemoteBranches" tabindex="7" /> &nbsp;<i>show remote branches</i></td></tr>
+				<tr><th><wicket:message key="gb.enableTickets"></wicket:message></th><td class="edit"><input type="checkbox" wicket:id="useTickets" tabindex="4" /> &nbsp;<i><wicket:message key="gb.useTicketsDescription"></wicket:message></i></td></tr>
+				<tr><th><wicket:message key="gb.enableDocs"></wicket:message></th><td class="edit"><input type="checkbox" wicket:id="useDocs" tabindex="5" /> &nbsp;<i><wicket:message key="gb.useDocsDescription"></wicket:message></i></td></tr>
+				<tr><th><wicket:message key="gb.showRemoteBranches"></wicket:message></th><td class="edit"><input type="checkbox" wicket:id="showRemoteBranches" tabindex="6" /> &nbsp;<i><wicket:message key="gb.showRemoteBranchesDescription"></wicket:message></i></td></tr>
+				<tr><th><wicket:message key="gb.accessRestriction"></wicket:message></th><td class="edit"><select wicket:id="accessRestriction" tabindex="7" /></td></tr>				
+				<tr><th style="vertical-align: top;"><wicket:message key="gb.permittedUsers"></wicket:message></th><td style="padding:2px;"><span wicket:id="users"></span></td></tr>				
 				<tr><th></th><td class="editButton"><input type="submit" value="Save" wicket:message="value:gb.save" tabindex="8" /></td></tr>
 			</tbody>
 		</table>
diff --git a/src/com/gitblit/wicket/pages/EditRepositoryPage.java b/src/com/gitblit/wicket/pages/EditRepositoryPage.java
index 2d2b0ae..8eed005 100644
--- a/src/com/gitblit/wicket/pages/EditRepositoryPage.java
+++ b/src/com/gitblit/wicket/pages/EditRepositoryPage.java
@@ -1,18 +1,29 @@
 package com.gitblit.wicket.pages;
 
+import java.text.MessageFormat;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Date;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
 
 import org.apache.wicket.PageParameters;
+import org.apache.wicket.extensions.markup.html.form.palette.Palette;
 import org.apache.wicket.markup.html.form.CheckBox;
+import org.apache.wicket.markup.html.form.ChoiceRenderer;
 import org.apache.wicket.markup.html.form.DropDownChoice;
 import org.apache.wicket.markup.html.form.Form;
+import org.apache.wicket.markup.html.form.IChoiceRenderer;
 import org.apache.wicket.markup.html.form.TextField;
 import org.apache.wicket.model.CompoundPropertyModel;
+import org.apache.wicket.model.util.CollectionModel;
+import org.apache.wicket.model.util.ListModel;
 
 import com.gitblit.Constants.AccessRestrictionType;
 import com.gitblit.GitBlit;
 import com.gitblit.GitBlitException;
+import com.gitblit.utils.StringUtils;
 import com.gitblit.wicket.AdminPage;
 import com.gitblit.wicket.BasePage;
 import com.gitblit.wicket.WicketUtils;
@@ -40,11 +51,17 @@
 	}
 
 	protected void setupPage(final RepositoryModel repositoryModel) {
+		List<String> repositoryUsers = new ArrayList<String>();
 		if (isCreate) {
 			super.setupPage("", getString("gb.newRepository"));
 		} else {
-			super.setupPage("", getString("gb.edit") + " " + repositoryModel.name);
+			super.setupPage("", getString("gb.edit"));
+			if (repositoryModel.accessRestriction.exceeds(AccessRestrictionType.NONE)) {
+				repositoryUsers.addAll(GitBlit.self().getRepositoryUsers(repositoryModel));
+			}
 		}
+
+		final Palette<String> usersPalette = new Palette<String>("users", new ListModel<String>(repositoryUsers), new CollectionModel<String>(GitBlit.self().getAllUsernames()), new ChoiceRenderer<String>("", ""), 10, false);
 		CompoundPropertyModel<RepositoryModel> model = new CompoundPropertyModel<RepositoryModel>(repositoryModel);
 		Form<RepositoryModel> form = new Form<RepositoryModel>("editForm", model) {
 
@@ -53,7 +70,48 @@
 			@Override
 			protected void onSubmit() {
 				try {
+					// confirm a repository name was entered
+					if (StringUtils.isEmpty(repositoryModel.name)) {
+						error("Please set repository name!");
+						return;
+					}
+
+					// automatically convert backslashes to forward slashes
+					repositoryModel.name = repositoryModel.name.replace('\\', '/');
+
+					// confirm valid characters in repository name
+					char[] validChars = { '/', '.', '_', '-' };
+					for (char c : repositoryModel.name.toCharArray()) {
+						if (!Character.isLetterOrDigit(c)) {
+							boolean ok = false;
+							for (char vc : validChars) {
+								ok |= c == vc;
+							}
+							if (!ok) {
+								error(MessageFormat.format("Illegal character '{0}' in repository name!", c));
+								return;
+							}
+						}
+					}
+
+					// confirm access restriction selection
+					if (repositoryModel.accessRestriction == null) {
+						error("Please select access restriction!");
+						return;
+					}
+					
+					// save the repository
 					GitBlit.self().editRepositoryModel(repositoryModel, isCreate);
+					
+					// save the repository access list
+					if (repositoryModel.accessRestriction.exceeds(AccessRestrictionType.NONE)) {
+						Iterator<String> users = usersPalette.getSelectedChoices();
+						List<String> repositoryUsers = new ArrayList<String>();
+						while (users.hasNext()) {
+							repositoryUsers.add(users.next());
+						}
+						GitBlit.self().setRepositoryUsers(repositoryModel, repositoryUsers);
+					}
 				} catch (GitBlitException e) {
 					error(e.getMessage());
 					return;
@@ -67,11 +125,33 @@
 		form.add(new TextField<String>("name").setEnabled(isCreate));
 		form.add(new TextField<String>("description"));
 		form.add(new TextField<String>("owner"));
-		form.add(new DropDownChoice<AccessRestrictionType>("accessRestriction", Arrays.asList(AccessRestrictionType.values())));
+		form.add(new DropDownChoice<AccessRestrictionType>("accessRestriction", Arrays.asList(AccessRestrictionType.values()), new AccessRestrictionRenderer()));
 		form.add(new CheckBox("useTickets"));
 		form.add(new CheckBox("useDocs"));
 		form.add(new CheckBox("showRemoteBranches"));
+		form.add(usersPalette);
 
 		add(form);
 	}
+
+	private class AccessRestrictionRenderer implements IChoiceRenderer<AccessRestrictionType> {
+
+		private static final long serialVersionUID = 1L;
+
+		private final Map<AccessRestrictionType, String> map;
+
+		public AccessRestrictionRenderer() {
+			map = getAccessRestrictions();
+		}
+
+		@Override
+		public String getDisplayValue(AccessRestrictionType type) {
+			return map.get(type);
+		}
+
+		@Override
+		public String getIdValue(AccessRestrictionType type, int index) {
+			return Integer.toString(index);
+		}
+	}
 }
diff --git a/src/com/gitblit/wicket/pages/EditUserPage.html b/src/com/gitblit/wicket/pages/EditUserPage.html
index 57407d2..c50bdba 100644
--- a/src/com/gitblit/wicket/pages/EditUserPage.html
+++ b/src/com/gitblit/wicket/pages/EditUserPage.html
@@ -18,8 +18,8 @@
 				<tr><th><wicket:message key="gb.name"></wicket:message></th><td class="edit"><input type="text" wicket:id="username" id="username" size="30" tabindex="1" /></td></tr>
 				<tr><th><wicket:message key="gb.password"></wicket:message></th><td class="edit"><input type="password" wicket:id="password" size="30" tabindex="2" /></td></tr>
 				<tr><th><wicket:message key="gb.confirmPassword"></wicket:message></th><td class="edit"><input type="password" wicket:id="confirmPassword" size="30" tabindex="3" /></td></tr>
-				<tr><th><wicket:message key="gb.canAdmin"></wicket:message></th><td class="edit"><input type="checkbox" wicket:id="canAdmin" tabindex="6" /> &nbsp;<i>can administer Git:Blit server</i></td></tr>				
-				<tr><th style="vertical-align: top;"><wicket:message key="gb.repositories"></wicket:message></th><td style="padding:2px;"><span wicket:id="repositories"></span></td></tr>
+				<tr><th><wicket:message key="gb.canAdmin"></wicket:message></th><td class="edit"><input type="checkbox" wicket:id="canAdmin" tabindex="6" /> &nbsp;<i><wicket:message key="gb.canAdminDescription"></wicket:message></i></td></tr>				
+				<tr><th style="vertical-align: top;"><wicket:message key="gb.restrictedRepositories"></wicket:message></th><td style="padding:2px;"><span wicket:id="repositories"></span></td></tr>
 				<tr><th></th><td class="editButton"><input type="submit" value="Save" wicket:message="value:gb.save" tabindex="7" /></td></tr>
 			</tbody>
 		</table>
diff --git a/src/com/gitblit/wicket/pages/EditUserPage.java b/src/com/gitblit/wicket/pages/EditUserPage.java
index 250d1fd..d1faa78 100644
--- a/src/com/gitblit/wicket/pages/EditUserPage.java
+++ b/src/com/gitblit/wicket/pages/EditUserPage.java
@@ -15,13 +15,18 @@
 import org.apache.wicket.model.Model;
 import org.apache.wicket.model.util.CollectionModel;
 import org.apache.wicket.model.util.ListModel;
+import org.eclipse.jetty.http.security.Credential.Crypt;
 import org.eclipse.jetty.http.security.Credential.MD5;
 
+import com.gitblit.Constants.AccessRestrictionType;
 import com.gitblit.GitBlit;
 import com.gitblit.GitBlitException;
+import com.gitblit.Keys;
+import com.gitblit.utils.StringUtils;
 import com.gitblit.wicket.AdminPage;
 import com.gitblit.wicket.BasePage;
 import com.gitblit.wicket.WicketUtils;
+import com.gitblit.wicket.models.RepositoryModel;
 import com.gitblit.wicket.models.UserModel;
 
 @AdminPage
@@ -41,7 +46,7 @@
 		super(params);
 		isCreate = false;
 		String name = WicketUtils.getUsername(params);
-		UserModel model = GitBlit.self().getUser(name);
+		UserModel model = GitBlit.self().getUserModel(name);
 		setupPage(model);
 	}
 
@@ -51,12 +56,17 @@
 		} else {
 			super.setupPage("", getString("gb.edit"));
 		}
-		final Model<String> confirmPassword = new Model<String>();
+		final Model<String> confirmPassword = new Model<String>(StringUtils.isEmpty(userModel.getPassword()) ? "" : userModel.getPassword());
 		CompoundPropertyModel<UserModel> model = new CompoundPropertyModel<UserModel>(userModel);
 
-		List<String> repos = GitBlit.self().getRepositoryList();
-		repos.add(0, "*"); // all repositories wildcard
-		final Palette<String> repositories = new Palette<String>("repositories", new ListModel<String>(userModel.getRepositories()), new CollectionModel<String>(repos), new ChoiceRenderer<String>("", ""), 10, false);		
+		List<String> repos = new ArrayList<String>();
+		for (String repo : GitBlit.self().getRepositoryList()) {
+			RepositoryModel repositoryModel = GitBlit.self().getRepositoryModel(repo);
+			if (repositoryModel.accessRestriction.exceeds(AccessRestrictionType.NONE)) {
+				repos.add(repo);
+			}
+		}
+		final Palette<String> repositories = new Palette<String>("repositories", new ListModel<String>(userModel.getRepositories()), new CollectionModel<String>(repos), new ChoiceRenderer<String>("", ""), 10, false);
 		Form<UserModel> form = new Form<UserModel>("editForm", model) {
 
 			private static final long serialVersionUID = 1L;
@@ -67,8 +77,20 @@
 					error("Passwords do not match!");
 					return;
 				}
-				userModel.setPassword(MD5.digest(userModel.getPassword()));
-				
+				String password = userModel.getPassword();
+				if (!password.toUpperCase().startsWith(Crypt.__TYPE) && !password.toUpperCase().startsWith(MD5.__TYPE)) {
+					// This is a plain text password.
+					// Optionally encrypt/obfuscate the password.
+					String type = GitBlit.self().settings().getString(Keys.realm.passwordStorage, "md5");
+					if (type.equalsIgnoreCase("md5")) {
+						// store MD5 checksum of password
+						userModel.setPassword(MD5.digest(userModel.getPassword()));
+					} else if (type.equalsIgnoreCase("crypt")) {
+						// simple unix encryption
+						userModel.setPassword(Crypt.crypt(userModel.getUsername(), userModel.getPassword()));
+					}
+				}
+
 				Iterator<String> selectedRepositories = repositories.getSelectedChoices();
 				List<String> repos = new ArrayList<String>();
 				while (selectedRepositories.hasNext()) {
@@ -82,14 +104,24 @@
 					return;
 				}
 				setRedirect(true);
-				setResponsePage(EditUserPage.class);
+				if (isCreate) {
+					// create another user
+					setResponsePage(EditUserPage.class);
+				} else {
+					// back to home
+					setResponsePage(RepositoriesPage.class);
+				}
 			}
 		};
 
 		// field names reflective match UserModel fields
 		form.add(new TextField<String>("username").setEnabled(isCreate));
-		form.add(new PasswordTextField("password"));
-		form.add(new PasswordTextField("confirmPassword", confirmPassword));
+		PasswordTextField passwordField = new PasswordTextField("password");
+		passwordField.setResetPassword(false);
+		form.add(passwordField);
+		PasswordTextField confirmPasswordField = new PasswordTextField("confirmPassword", confirmPassword);
+		confirmPasswordField.setResetPassword(false);
+		form.add(confirmPasswordField);
 		form.add(new CheckBox("canAdmin"));
 		form.add(repositories);
 		add(form);
diff --git a/src/com/gitblit/wicket/pages/RepositoriesPage.html b/src/com/gitblit/wicket/pages/RepositoriesPage.html
index 3016f64..c33e530 100644
--- a/src/com/gitblit/wicket/pages/RepositoriesPage.html
+++ b/src/com/gitblit/wicket/pages/RepositoriesPage.html
@@ -31,7 +31,7 @@
          		<td><div class="list" wicket:id="repositoryName">[repository name]</div></td>
          		<td><div class="list" wicket:id="repositoryDescription">[repository description]</div></td>
          		<td class="author"><span wicket:id="repositoryOwner">[repository owner]</span></td>
-         		<td class="icon"><img wicket:id="ticketsIcon" /><img wicket:id="docsIcon" /><img wicket:id="restrictedAccessIcon" /></td>
+         		<td class="icon"><img wicket:id="ticketsIcon" /><img wicket:id="docsIcon" /><img wicket:id="accessRestrictionIcon" /></td>
          		<td><span wicket:id="repositoryLastChange">[last change]</span></td>
          		<td class="rightAlign"><span wicket:id="repositoryLinks"></span></td>
        		</tr>
diff --git a/src/com/gitblit/wicket/pages/RepositoriesPage.java b/src/com/gitblit/wicket/pages/RepositoriesPage.java
index 4505829..acdc02f 100644
--- a/src/com/gitblit/wicket/pages/RepositoriesPage.java
+++ b/src/com/gitblit/wicket/pages/RepositoriesPage.java
@@ -8,6 +8,7 @@
 import java.util.Comparator;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
 
 import org.apache.wicket.Component;
 import org.apache.wicket.PageParameters;
@@ -23,6 +24,7 @@
 import org.apache.wicket.model.Model;
 import org.apache.wicket.resource.ContextRelativeResource;
 
+import com.gitblit.Constants.AccessRestrictionType;
 import com.gitblit.GitBlit;
 import com.gitblit.Keys;
 import com.gitblit.utils.MarkdownUtils;
@@ -99,6 +101,7 @@
 		}
 		add(repositoriesMessage);
 
+		final Map<AccessRestrictionType, String> accessRestrictionTranslations = getAccessRestrictions();
 		UserModel user = GitBlitWebSession.get().getUser();
 		List<RepositoryModel> rows = GitBlit.self().getRepositoryModels(user);
 		DataProvider dp = new DataProvider(rows);
@@ -130,22 +133,22 @@
 				} else {
 					item.add(WicketUtils.newBlankImage("docsIcon"));
 				}
-
+				
 				switch (entry.accessRestriction) {
 				case NONE:
-					item.add(WicketUtils.newBlankImage("restrictedAccessIcon"));
+					item.add(WicketUtils.newBlankImage("accessRestrictionIcon"));
 					break;
 				case PUSH:
-					item.add(WicketUtils.newImage("restrictedAccessIcon", "lock_go_16x16.png", getString("gb.pushRestricted")));
+					item.add(WicketUtils.newImage("accessRestrictionIcon", "lock_go_16x16.png", accessRestrictionTranslations.get(entry.accessRestriction)));
 					break;
 				case CLONE:
-					item.add(WicketUtils.newImage("restrictedAccessIcon", "lock_pull_16x16.png", getString("gb.cloneRestricted")));
+					item.add(WicketUtils.newImage("accessRestrictionIcon", "lock_pull_16x16.png", accessRestrictionTranslations.get(entry.accessRestriction)));
 					break;
 				case VIEW:
-					item.add(WicketUtils.newImage("restrictedAccessIcon", "shield_16x16.png", getString("gb.viewRestricted")));
+					item.add(WicketUtils.newImage("accessRestrictionIcon", "shield_16x16.png", accessRestrictionTranslations.get(entry.accessRestriction)));
 					break;
 				default:
-					item.add(WicketUtils.newBlankImage("restrictedAccessIcon"));
+					item.add(WicketUtils.newBlankImage("accessRestrictionIcon"));
 				}
 
 				item.add(new Label("repositoryOwner", entry.owner));
diff --git a/src/com/gitblit/wicket/pages/SummaryPage.html b/src/com/gitblit/wicket/pages/SummaryPage.html
index 6dbcfa2..35331f5 100644
--- a/src/com/gitblit/wicket/pages/SummaryPage.html
+++ b/src/com/gitblit/wicket/pages/SummaryPage.html
@@ -20,7 +20,7 @@
 				<tr><th><wicket:message key="gb.owner">[owner]</wicket:message></th><td><span wicket:id="repositoryOwner">[repository owner]</span></td></tr>
 				<tr><th><wicket:message key="gb.lastChange">[last change]</wicket:message></th><td><span wicket:id="repositoryLastChange">[repository last change]</span></td></tr>
 				<tr><th><wicket:message key="gb.stats">[stats]</wicket:message></th><td><span wicket:id="repositoryStats">[repository stats]</span></td></tr>
-				<tr><th><wicket:message key="gb.url">[URL]</wicket:message></th><td><span wicket:id="repositoryCloneUrl">[repository clone url]</span></td></tr>
+				<tr><th><wicket:message key="gb.url">[URL]</wicket:message></th><td><img style="vertical-align: top; padding-right:5px;" wicket:id="accessRestrictionIcon" /><span wicket:id="repositoryCloneUrl">[repository clone url]</span></td></tr>
 			</table>
 		</div>
 	</div>
diff --git a/src/com/gitblit/wicket/pages/SummaryPage.java b/src/com/gitblit/wicket/pages/SummaryPage.java
index c0193db..d83f596 100644
--- a/src/com/gitblit/wicket/pages/SummaryPage.java
+++ b/src/com/gitblit/wicket/pages/SummaryPage.java
@@ -19,6 +19,7 @@
 import org.wicketstuff.googlecharts.MarkerType;
 import org.wicketstuff.googlecharts.ShapeMarker;
 
+import com.gitblit.Constants.AccessRestrictionType;
 import com.gitblit.GitBlit;
 import com.gitblit.Keys;
 import com.gitblit.utils.JGitUtils;
@@ -66,6 +67,24 @@
 		} else {
 			add(new Label("repositoryStats", MessageFormat.format("{0} commits and {1} tags in {2}", metricsTotal.count, metricsTotal.tag, TimeUtils.duration(metricsTotal.duration))));
 		}
+		
+		AccessRestrictionType accessRestriction = getRepositoryModel().accessRestriction;
+		switch (accessRestriction) {
+		case NONE:
+			add(WicketUtils.newClearPixel("accessRestrictionIcon").setVisible(false));
+			break;
+		case PUSH:
+			add(WicketUtils.newImage("accessRestrictionIcon", "lock_go_16x16.png", getAccessRestrictions().get(accessRestriction)));
+			break;
+		case CLONE:
+			add(WicketUtils.newImage("accessRestrictionIcon", "lock_pull_16x16.png", getAccessRestrictions().get(accessRestriction)));
+			break;
+		case VIEW:
+			add(WicketUtils.newImage("accessRestrictionIcon", "shield_16x16.png", getAccessRestrictions().get(accessRestriction)));
+			break;
+		default:
+			add(WicketUtils.newClearPixel("accessRestrictionIcon").setVisible(false));
+		}
 		add(new Label("repositoryCloneUrl", GitBlit.self().getCloneUrl(repositoryName)));
 
 		add(new LogPanel("commitsPanel", repositoryName, null, r, numberCommits, 0));
diff --git a/users.properties b/users.properties
index 0b22d82..920f323 100644
--- a/users.properties
+++ b/users.properties
@@ -1,3 +1,2 @@
-#Wed May 11 21:30:28 EDT 2011
+# Git:Blit realm file format: username=password,\#permission,repository1,repository2...
 admin=admin,\#admin
-test=test

--
Gitblit v1.9.1