From 092f0a62302e87f44403ba24fc519c65534dbfff Mon Sep 17 00:00:00 2001
From: James Moger <james.moger@gitblit.com>
Date: Mon, 29 Oct 2012 23:22:54 -0400
Subject: [PATCH] Stabilizing and polishing permissions ui.  Still in-progress.

---
 src/com/gitblit/models/RegistrantAccessPermission.java        |   36 ++
 src/com/gitblit/wicket/pages/EditRepositoryPage.java          |  125 +++++++++-
 src/com/gitblit/wicket/panels/RegistrantPermissionsPanel.java |   69 ++++-
 src/com/gitblit/client/GitblitClient.java                     |   68 +++++
 src/com/gitblit/client/EditTeamDialog.java                    |   10 
 src/com/gitblit/client/RegistrantPermissionsPanel.java        |   56 +++-
 src/com/gitblit/wicket/pages/EditUserPage.java                |   20 +
 src/com/gitblit/client/EditUserDialog.java                    |   18 +
 src/com/gitblit/models/UserModel.java                         |   18 +
 src/com/gitblit/GitBlit.java                                  |   49 +++
 src/com/gitblit/wicket/GitBlitWebApp.properties               |    5 
 src/com/gitblit/wicket/pages/EditTeamPage.java                |    8 
 src/com/gitblit/wicket/pages/BasePage.java                    |   16 +
 tests/com/gitblit/tests/RpcTests.java                         |    5 
 src/com/gitblit/client/UsersPanel.java                        |   17 +
 src/com/gitblit/client/EditRepositoryDialog.java              |   63 +++++
 src/com/gitblit/models/TeamModel.java                         |   10 
 src/com/gitblit/wicket/panels/RegistrantPermissionsPanel.html |    2 
 src/com/gitblit/wicket/pages/EditRepositoryPage.html          |    7 
 resources/gitblit.css                                         |    6 
 src/com/gitblit/client/RegistrantPermissionsTableModel.java   |    4 
 src/com/gitblit/wicket/pages/RootSubPage.java                 |   19 +
 src/com/gitblit/Constants.java                                |    4 
 23 files changed, 520 insertions(+), 115 deletions(-)

diff --git a/resources/gitblit.css b/resources/gitblit.css
index 5c2d92a..e5363c8 100644
--- a/resources/gitblit.css
+++ b/resources/gitblit.css
@@ -188,6 +188,12 @@
 	vertical-align: middle;
 }
 
+span.authorizationControl label {
+	display: inline;
+	color: #777;
+	padding:5px 0px 5px 10px;	
+}
+
 div.page_footer {
 	clear: both;
 	height: 17px;
diff --git a/src/com/gitblit/Constants.java b/src/com/gitblit/Constants.java
index f74317e..e7812ee 100644
--- a/src/com/gitblit/Constants.java
+++ b/src/com/gitblit/Constants.java
@@ -386,6 +386,10 @@
 		REPOSITORY, USER, TEAM;
 	}
 	
+	public static enum PermissionType {
+		EXPLICIT, OWNER, REGEX;
+	}
+	
 	public static enum GCStatus {
 		READY, COLLECTING;
 		
diff --git a/src/com/gitblit/GitBlit.java b/src/com/gitblit/GitBlit.java
index 402f600..6e587ca 100644
--- a/src/com/gitblit/GitBlit.java
+++ b/src/com/gitblit/GitBlit.java
@@ -79,6 +79,7 @@
 import com.gitblit.Constants.FederationRequest;
 import com.gitblit.Constants.FederationStrategy;
 import com.gitblit.Constants.FederationToken;
+import com.gitblit.Constants.PermissionType;
 import com.gitblit.Constants.RegistrantType;
 import com.gitblit.models.FederationModel;
 import com.gitblit.models.FederationProposal;
@@ -670,14 +671,35 @@
 	 * @return a list of User-AccessPermission tuples
 	 */
 	public List<RegistrantAccessPermission> getUserAccessPermissions(RepositoryModel repository) {
-		List<RegistrantAccessPermission> permissions = new ArrayList<RegistrantAccessPermission>();
+		Set<RegistrantAccessPermission> permissions = new LinkedHashSet<RegistrantAccessPermission>();
+		if (!StringUtils.isEmpty(repository.owner)) {
+			UserModel owner = userService.getUserModel(repository.owner);
+			if (owner != null) {
+				permissions.add(new RegistrantAccessPermission(owner.username, AccessPermission.REWIND, PermissionType.OWNER, RegistrantType.USER, false));
+			}
+		}
+		if (repository.isPersonalRepository()) {
+			UserModel owner = userService.getUserModel(repository.projectPath.substring(1));
+			if (owner != null) {
+				permissions.add(new RegistrantAccessPermission(owner.username, AccessPermission.REWIND, PermissionType.OWNER, RegistrantType.USER, false));
+			}
+		}
 		for (String user : userService.getUsernamesForRepositoryRole(repository.name)) {
 			UserModel model = userService.getUserModel(user);
 			AccessPermission ap = model.getRepositoryPermission(repository);
-			boolean isExplicit = model.hasExplicitRepositoryPermission(repository.name);
-			permissions.add(new RegistrantAccessPermission(user, ap, isExplicit, RegistrantType.USER));
+			PermissionType pType = PermissionType.REGEX;
+			boolean editable = false;
+			if (repository.isOwner(model.username)) {
+				pType = PermissionType.OWNER;
+			} else if (repository.isUsersPersonalRepository(model.username)) {
+				pType = PermissionType.OWNER;
+			} else if (model.hasExplicitRepositoryPermission(repository.name)) {
+				pType = PermissionType.EXPLICIT;
+				editable = true;
+			}			
+			permissions.add(new RegistrantAccessPermission(user, ap, pType, RegistrantType.USER, editable));
 		}
-		return permissions;
+		return new ArrayList<RegistrantAccessPermission>(permissions);
 	}
 	
 	/**
@@ -690,8 +712,8 @@
 	public boolean setUserAccessPermissions(RepositoryModel repository, Collection<RegistrantAccessPermission> permissions) {
 		List<UserModel> users = new ArrayList<UserModel>();
 		for (RegistrantAccessPermission up : permissions) {
-			if (up.isExplicit) {
-				// only set explicitly defined permissions
+			if (up.isEditable) {
+				// only set editable defined permissions
 				UserModel user = userService.getUserModel(up.registrant);
 				user.setRepositoryPermission(repository.name, up.permission);
 				users.add(user);
@@ -811,8 +833,13 @@
 		for (String team : userService.getTeamnamesForRepositoryRole(repository.name)) {
 			TeamModel model = userService.getTeamModel(team);
 			AccessPermission ap = model.getRepositoryPermission(repository);
-			boolean isExplicit = model.hasExplicitRepositoryPermission(repository.name);
-			permissions.add(new RegistrantAccessPermission(team, ap, isExplicit, RegistrantType.TEAM));
+			PermissionType pType = PermissionType.REGEX;
+			boolean editable = false;
+			if (model.hasExplicitRepositoryPermission(repository.name)) {
+				pType = PermissionType.EXPLICIT;
+				editable = true;
+			}
+			permissions.add(new RegistrantAccessPermission(team, ap, pType, RegistrantType.TEAM, editable));
 		}
 		return permissions;
 	}
@@ -827,7 +854,7 @@
 	public boolean setTeamAccessPermissions(RepositoryModel repository, Collection<RegistrantAccessPermission> permissions) {
 		List<TeamModel> teams = new ArrayList<TeamModel>();
 		for (RegistrantAccessPermission tp : permissions) {
-			if (tp.isExplicit) {
+			if (tp.isEditable) {
 				// only set explicitly defined access permissions
 				TeamModel team = userService.getTeamModel(tp.registrant);
 				team.setRepositoryPermission(repository.name, tp.permission);
@@ -1870,7 +1897,9 @@
 		config.setBoolean(Constants.CONFIG_GITBLIT, null, "isFederated", repository.isFederated);
 		config.setString(Constants.CONFIG_GITBLIT, null, "gcThreshold", repository.gcThreshold);
 		config.setString(Constants.CONFIG_GITBLIT, null, "gcPeriod", repository.gcPeriod);
-		config.setString(Constants.CONFIG_GITBLIT, null, "lastGC", new SimpleDateFormat(Constants.ISO8601).format(repository.lastGC));
+		if (repository.lastGC != null) {
+			config.setString(Constants.CONFIG_GITBLIT, null, "lastGC", new SimpleDateFormat(Constants.ISO8601).format(repository.lastGC));
+		}
 
 		updateList(config, "federationSets", repository.federationSets);
 		updateList(config, "preReceiveScript", repository.preReceiveScripts);
diff --git a/src/com/gitblit/client/EditRepositoryDialog.java b/src/com/gitblit/client/EditRepositoryDialog.java
index 0adf8a8..06621c2 100644
--- a/src/com/gitblit/client/EditRepositoryDialog.java
+++ b/src/com/gitblit/client/EditRepositoryDialog.java
@@ -24,6 +24,8 @@
 import java.awt.Insets;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
 import java.awt.event.KeyEvent;
 import java.text.MessageFormat;
 import java.util.ArrayList;
@@ -37,6 +39,7 @@
 import javax.swing.BoxLayout;
 import javax.swing.ButtonGroup;
 import javax.swing.DefaultComboBoxModel;
+import javax.swing.DefaultListCellRenderer;
 import javax.swing.ImageIcon;
 import javax.swing.JButton;
 import javax.swing.JCheckBox;
@@ -59,6 +62,7 @@
 import com.gitblit.Constants.AccessRestrictionType;
 import com.gitblit.Constants.AuthorizationControl;
 import com.gitblit.Constants.FederationStrategy;
+import com.gitblit.Constants.RegistrantType;
 import com.gitblit.models.RegistrantAccessPermission;
 import com.gitblit.models.RepositoryModel;
 import com.gitblit.utils.ArrayUtils;
@@ -218,13 +222,41 @@
 		accessRestriction = new JComboBox(AccessRestrictionType.values());
 		accessRestriction.setRenderer(new AccessRestrictionRenderer());
 		accessRestriction.setSelectedItem(anRepository.accessRestriction);
+		accessRestriction.addItemListener(new ItemListener() {
+			@Override
+			public void itemStateChanged(ItemEvent e) {
+				if (e.getStateChange() == ItemEvent.SELECTED) {
+					AccessRestrictionType art = (AccessRestrictionType) accessRestriction.getSelectedItem();
+					EditRepositoryDialog.this.setupAccessPermissions(art);
+				}
+			}
+		});
 		
 		boolean authenticated = anRepository.authorizationControl != null 
 				&& AuthorizationControl.AUTHENTICATED.equals(anRepository.authorizationControl);
 		allowAuthenticated = new JRadioButton(Translation.get("gb.allowAuthenticatedDescription"));
 		allowAuthenticated.setSelected(authenticated);
+		allowAuthenticated.addItemListener(new ItemListener() {
+			@Override
+			public void itemStateChanged(ItemEvent e) {
+				if (e.getStateChange() == ItemEvent.SELECTED) {
+					usersPalette.setEnabled(false);
+					teamsPalette.setEnabled(false);
+				}
+			}
+		});
+		
 		allowNamed = new JRadioButton(Translation.get("gb.allowNamedDescription"));
 		allowNamed.setSelected(!authenticated);
+		allowNamed.addItemListener(new ItemListener() {
+			@Override
+			public void itemStateChanged(ItemEvent e) {
+				if (e.getStateChange() == ItemEvent.SELECTED) {
+					usersPalette.setEnabled(true);
+					teamsPalette.setEnabled(true);
+				}
+			}
+		});
 		
 		ButtonGroup group = new ButtonGroup();
 		group.add(allowAuthenticated);
@@ -281,7 +313,7 @@
 		clonePushPanel
 		.add(newFieldPanel(Translation.get("gb.verifyCommitter"), verifyCommitter));
 
-		usersPalette = new RegistrantPermissionsPanel();
+		usersPalette = new RegistrantPermissionsPanel(RegistrantType.USER);
 		JPanel northAccessPanel = new JPanel(new BorderLayout(5, 5));
 		northAccessPanel.add(newFieldPanel(Translation.get("gb.accessRestriction"),
 				accessRestriction), BorderLayout.NORTH);
@@ -294,7 +326,7 @@
 		accessPanel.add(newFieldPanel(Translation.get("gb.userPermissions"),
 						usersPalette), BorderLayout.CENTER);
 
-		teamsPalette = new RegistrantPermissionsPanel();
+		teamsPalette = new RegistrantPermissionsPanel(RegistrantType.TEAM);
 		JPanel teamsPanel = new JPanel(new BorderLayout(5, 5));
 		teamsPanel.add(
 				newFieldPanel(Translation.get("gb.teamPermissions"),
@@ -349,6 +381,8 @@
 		panel.addTab(Translation.get("gb.customFields"), customFieldsScrollPane);
 		
 
+		setupAccessPermissions(anRepository.accessRestriction);
+
 		JButton createButton = new JButton(Translation.get("gb.save"));
 		createButton.addActionListener(new ActionListener() {
 			public void actionPerformed(ActionEvent event) {
@@ -402,6 +436,25 @@
 		panel.add(fieldLabel);
 		panel.add(comp);
 		return panel;
+	}
+	
+	private void setupAccessPermissions(AccessRestrictionType art) {
+		if (AccessRestrictionType.NONE.equals(art)) {
+			usersPalette.setEnabled(false);
+			teamsPalette.setEnabled(false);
+			
+			allowAuthenticated.setEnabled(false);
+			allowNamed.setEnabled(false);
+		} else {
+			allowAuthenticated.setEnabled(true);
+			allowNamed.setEnabled(true);
+			
+			if (allowNamed.isSelected()) {
+				usersPalette.setEnabled(true);
+				teamsPalette.setEnabled(true);
+			}
+		}
+
 	}
 
 	private boolean validateFields() {
@@ -538,6 +591,7 @@
 	
 	public void setAccessRestriction(AccessRestrictionType restriction) {
 		this.accessRestriction.setSelectedItem(restriction);
+		setupAccessPermissions(restriction);
 	}
 
 	public void setAuthorizationControl(AuthorizationControl authorization) {
@@ -659,14 +713,15 @@
 	 * restriction.
 	 * 
 	 */
-	private class AccessRestrictionRenderer extends JLabel implements
-			ListCellRenderer {
+	private class AccessRestrictionRenderer extends DefaultListCellRenderer {
 
 		private static final long serialVersionUID = 1L;
 
 		@Override
 		public Component getListCellRendererComponent(JList list, Object value,
 				int index, boolean isSelected, boolean cellHasFocus) {
+			super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
+			
 			if (value instanceof AccessRestrictionType) {
 				AccessRestrictionType restriction = (AccessRestrictionType) value;
 				switch (restriction) {
diff --git a/src/com/gitblit/client/EditTeamDialog.java b/src/com/gitblit/client/EditTeamDialog.java
index 4350310..4d7af26 100644
--- a/src/com/gitblit/client/EditTeamDialog.java
+++ b/src/com/gitblit/client/EditTeamDialog.java
@@ -45,11 +45,12 @@
 import javax.swing.KeyStroke;
 
 import com.gitblit.Constants.AccessRestrictionType;
+import com.gitblit.Constants.AuthorizationControl;
+import com.gitblit.Constants.RegistrantType;
 import com.gitblit.models.RegistrantAccessPermission;
 import com.gitblit.models.RepositoryModel;
 import com.gitblit.models.ServerSettings;
 import com.gitblit.models.TeamModel;
-import com.gitblit.utils.ArrayUtils;
 import com.gitblit.utils.StringUtils;
 
 public class EditTeamDialog extends JDialog {
@@ -140,7 +141,7 @@
 		fieldsPanel.add(newFieldPanel(Translation.get("gb.mailingLists"), mailingListsField));
 
 		final Insets _insets = new Insets(5, 5, 5, 5);
-		repositoryPalette = new RegistrantPermissionsPanel();
+		repositoryPalette = new RegistrantPermissionsPanel(RegistrantType.REPOSITORY);
 		userPalette = new JPalette<String>();
 		userPalette.setEnabled(settings.supportsTeamMembershipChanges);
 		
@@ -311,9 +312,10 @@
 	public void setRepositories(List<RepositoryModel> repositories, List<RegistrantAccessPermission> permissions) {
 		List<String> restricted = new ArrayList<String>();
 		for (RepositoryModel repo : repositories) {
-			if (repo.accessRestriction.exceeds(AccessRestrictionType.NONE)) {
+			if (repo.accessRestriction.exceeds(AccessRestrictionType.NONE)
+					&& repo.authorizationControl.equals(AuthorizationControl.NAMED)) {
 				restricted.add(repo.name);
-			}
+			}				
 		}
 		StringUtils.sortRepositorynames(restricted);
 		
diff --git a/src/com/gitblit/client/EditUserDialog.java b/src/com/gitblit/client/EditUserDialog.java
index e096693..070926d 100644
--- a/src/com/gitblit/client/EditUserDialog.java
+++ b/src/com/gitblit/client/EditUserDialog.java
@@ -46,6 +46,8 @@
 import javax.swing.KeyStroke;
 
 import com.gitblit.Constants.AccessRestrictionType;
+import com.gitblit.Constants.AuthorizationControl;
+import com.gitblit.Constants.RegistrantType;
 import com.gitblit.Keys;
 import com.gitblit.models.RegistrantAccessPermission;
 import com.gitblit.models.RepositoryModel;
@@ -158,7 +160,7 @@
 				notFederatedCheckbox));
 
 		final Insets _insets = new Insets(5, 5, 5, 5);
-		repositoryPalette = new RegistrantPermissionsPanel();
+		repositoryPalette = new RegistrantPermissionsPanel(RegistrantType.REPOSITORY);
 		teamsPalette = new JPalette<TeamModel>();
 		teamsPalette.setEnabled(settings.supportsTeamMembershipChanges);
 
@@ -343,8 +345,12 @@
 	public void setRepositories(List<RepositoryModel> repositories, List<RegistrantAccessPermission> permissions) {
 		List<String> restricted = new ArrayList<String>();
 		for (RepositoryModel repo : repositories) {
-			if (repo.accessRestriction.exceeds(AccessRestrictionType.NONE)) {
-				restricted.add(repo.name);
+			// exclude Owner or personal repositories
+			if (!repo.isOwner(username) && !repo.isUsersPersonalRepository(username)) {
+				if (repo.accessRestriction.exceeds(AccessRestrictionType.NONE)
+						&& repo.authorizationControl.equals(AuthorizationControl.NAMED)) {
+					restricted.add(repo.name);
+				}				
 			}
 		}
 		StringUtils.sortRepositorynames(restricted);
@@ -356,15 +362,15 @@
 		list.add("[^~].*");
 		String lastProject = null;
 		for (String repo : restricted) {
-			String projectPath = StringUtils.getFirstPathElement(repo);
+			String projectPath = StringUtils.getFirstPathElement(repo).toLowerCase();
 			if (lastProject == null || !lastProject.equalsIgnoreCase(projectPath)) {
 				lastProject = projectPath;
 				if (!StringUtils.isEmpty(projectPath)) {
 					// regex for all repositories within a project
 					list.add(projectPath + "/.*");
 				}
-				list.add(repo);
 			}
+			list.add(repo);
 		}
 
 		// remove repositories for which user already has a permission
@@ -372,7 +378,7 @@
 			permissions = new ArrayList<RegistrantAccessPermission>();
 		} else {
 			for (RegistrantAccessPermission rp : permissions) {
-				list.remove(rp.registrant);
+				list.remove(rp.registrant.toLowerCase());
 			}
 		}
 		repositoryPalette.setObjects(list, permissions);
diff --git a/src/com/gitblit/client/GitblitClient.java b/src/com/gitblit/client/GitblitClient.java
index 4620fef..b7047d7 100644
--- a/src/com/gitblit/client/GitblitClient.java
+++ b/src/com/gitblit/client/GitblitClient.java
@@ -31,6 +31,7 @@
 import com.gitblit.Constants.AccessPermission;
 import com.gitblit.Constants.AccessRestrictionType;
 import com.gitblit.Constants.AuthorizationControl;
+import com.gitblit.Constants.PermissionType;
 import com.gitblit.Constants.RegistrantType;
 import com.gitblit.GitBlitException.ForbiddenException;
 import com.gitblit.GitBlitException.NotAllowedException;
@@ -340,6 +341,7 @@
 		List<UserModel> users = RpcUtils.getUsers(url, account, password);
 		allUsers.clear();
 		allUsers.addAll(users);
+		Collections.sort(users);
 		return allUsers;
 	}
 
@@ -347,6 +349,7 @@
 		List<TeamModel> teams = RpcUtils.getTeams(url, account, password);
 		allTeams.clear();
 		allTeams.addAll(teams);
+		Collections.sort(teams);
 		return allTeams;
 	}
 
@@ -475,6 +478,15 @@
 	public List<UserModel> getUsers() {
 		return allUsers;
 	}
+	
+	public UserModel getUser(String username) {
+		for (UserModel user : getUsers()) {
+			if (user.username.equalsIgnoreCase(username)) {
+				return user;
+			}
+		}
+		return null;
+	}
 
 	public List<String> getUsernames() {
 		List<String> usernames = new ArrayList<String>();
@@ -496,15 +508,38 @@
 	}
 	
 	public List<RegistrantAccessPermission> getUserAccessPermissions(RepositoryModel repository) {
-		List<RegistrantAccessPermission> list = new ArrayList<RegistrantAccessPermission>();
-		for (UserModel user : allUsers) {
-			if (user.hasRepositoryPermission(repository.name)) {
-				AccessPermission ap = user.getRepositoryPermission(repository);
-				boolean isExplicit = user.hasExplicitRepositoryPermission(repository.name);
-				list.add(new RegistrantAccessPermission(user.username, ap, isExplicit, RegistrantType.USER));
+		Set<RegistrantAccessPermission> list = new LinkedHashSet<RegistrantAccessPermission>();
+		if (!StringUtils.isEmpty(repository.owner)) {
+			UserModel owner = getUser(repository.owner);
+			if (owner != null) {
+				list.add(new RegistrantAccessPermission(owner.username, AccessPermission.REWIND, PermissionType.OWNER, RegistrantType.USER, false));
 			}
 		}
-		return list;
+		if (repository.isPersonalRepository()) {
+			UserModel owner = getUser(repository.projectPath.substring(1));
+			if (owner != null) {
+				list.add(new RegistrantAccessPermission(owner.username, AccessPermission.REWIND, PermissionType.OWNER, RegistrantType.USER, false));
+			}
+		}
+		for (UserModel user : getUsers()) {
+			if (user.hasRepositoryPermission(repository.name)) {
+				AccessPermission ap = user.getRepositoryPermission(repository);
+				PermissionType pType = PermissionType.REGEX;
+				boolean editable = false;
+				if (repository.isOwner(user.username)) {
+					pType = PermissionType.OWNER;
+				} else if (repository.isUsersPersonalRepository(user.username)) {
+					pType = PermissionType.OWNER;
+				} else if (user.hasExplicitRepositoryPermission(repository.name)) {
+					pType = PermissionType.EXPLICIT;
+					editable = true;
+				}			
+				list.add(new RegistrantAccessPermission(user.username, ap, pType, RegistrantType.USER, editable));
+			}
+		}
+		List<RegistrantAccessPermission> raps = new ArrayList<RegistrantAccessPermission>(list);
+		Collections.sort(raps);
+		return raps;
 	}
 
 	public boolean setUserAccessPermissions(RepositoryModel repository, List<RegistrantAccessPermission> permissions) throws IOException {
@@ -539,10 +574,16 @@
 		for (TeamModel team : allTeams) {
 			if (team.hasRepositoryPermission(repository.name)) {
 				AccessPermission ap = team.getRepositoryPermission(repository);
-				boolean isExplicit = team.hasExplicitRepositoryPermission(repository.name);
-				list.add(new RegistrantAccessPermission(team.name, ap, isExplicit, RegistrantType.TEAM));
+				PermissionType pType = PermissionType.REGEX;
+				boolean editable = false;
+				if (team.hasExplicitRepositoryPermission(repository.name)) {
+					pType = PermissionType.EXPLICIT;
+					editable = true;
+				}
+				list.add(new RegistrantAccessPermission(team.name, ap, pType, RegistrantType.TEAM, editable));
 			}
 		}
+		Collections.sort(list);
 		return list;
 	}
 
@@ -566,6 +607,15 @@
 	public List<RepositoryModel> getRepositories() {
 		return allRepositories;
 	}
+	
+	public RepositoryModel getRepository(String name) {
+		for (RepositoryModel repository : allRepositories) {
+			if (repository.name.equalsIgnoreCase(name)) {
+				return repository;
+			}
+		}
+		return null;
+	}
 
 	public boolean createRepository(RepositoryModel repository, List<RegistrantAccessPermission> userPermissions)
 			throws IOException {
diff --git a/src/com/gitblit/client/RegistrantPermissionsPanel.java b/src/com/gitblit/client/RegistrantPermissionsPanel.java
index 4ea173f..b8ab939 100644
--- a/src/com/gitblit/client/RegistrantPermissionsPanel.java
+++ b/src/com/gitblit/client/RegistrantPermissionsPanel.java
@@ -33,7 +33,10 @@
 import javax.swing.table.DefaultTableCellRenderer;
 
 import com.gitblit.Constants.AccessPermission;
+import com.gitblit.Constants.PermissionType;
+import com.gitblit.Constants.RegistrantType;
 import com.gitblit.models.RegistrantAccessPermission;
+import com.gitblit.utils.StringUtils;
 
 public class RegistrantPermissionsPanel extends JPanel {
 
@@ -53,16 +56,19 @@
 
 	private JPanel addPanel;
 
-	public RegistrantPermissionsPanel() {
+	public RegistrantPermissionsPanel(final RegistrantType registrantType) {
 		super(new BorderLayout(5, 5));
 		tableModel = new RegistrantPermissionsTableModel();
-		permissionsTable = new JTable(tableModel);
+		permissionsTable = Utils.newTable(tableModel, Utils.DATE_FORMAT);
+		permissionsTable.setModel(tableModel);
 		permissionsTable.setPreferredScrollableViewportSize(new Dimension(400, 150));
 		JScrollPane jsp = new JScrollPane(permissionsTable);
 		add(jsp, BorderLayout.CENTER);
 		
+		permissionsTable.getColumnModel().getColumn(RegistrantPermissionsTableModel.Columns.Registrant.ordinal())
+		.setCellRenderer(new NameRenderer());
 		permissionsTable.getColumnModel().getColumn(RegistrantPermissionsTableModel.Columns.Type.ordinal())
-				.setCellRenderer(new RegexRenderer());
+				.setCellRenderer(new PermissionTypeRenderer());
 		permissionsTable.getColumnModel().getColumn(RegistrantPermissionsTableModel.Columns.Permission.ordinal())
 		.setCellEditor(new AccessPermissionEditor());
 		
@@ -79,9 +85,15 @@
 					return;
 				}
 				
-				RegistrantAccessPermission rp = new RegistrantAccessPermission();
+				RegistrantAccessPermission rp = new RegistrantAccessPermission(registrantType);
 				rp.registrant = registrantSelector.getSelectedItem().toString();
 				rp.permission = (AccessPermission) permissionSelector.getSelectedItem();
+				if (StringUtils.findInvalidCharacter(rp.registrant) != null) {
+					rp.permissionType = PermissionType.REGEX;
+				} else {
+					rp.permissionType = PermissionType.EXPLICIT;
+				}
+
 				tableModel.permissions.add(rp);
 				
 				registrantModel.removeElement(rp.registrant);
@@ -103,7 +115,10 @@
 	@Override
 	public void setEnabled(boolean enabled) {
 		super.setEnabled(enabled);
-		permissionsTable.setEnabled(false);
+		permissionsTable.setEnabled(enabled);
+		registrantSelector.setEnabled(enabled);
+		permissionSelector.setEnabled(enabled);
+		addButton.setEnabled(enabled);
 	}
 
 	public void setObjects(List<String> registrants, List<RegistrantAccessPermission> permissions) {
@@ -117,7 +132,11 @@
 			permissions = new ArrayList<RegistrantAccessPermission>();
 		}
 		for (RegistrantAccessPermission rp : permissions) {
-			filtered.remove(rp.registrant);
+			if (rp.isEditable) {
+				// only remove editable duplicates
+				// this allows for specifying an explicit permission
+				filtered.remove(rp.registrant);
+			}
 		}
 		for (String registrant : filtered) {
 			registrantModel.addElement(registrant);
@@ -138,30 +157,35 @@
 		private static final long serialVersionUID = 1L;
 
 		public AccessPermissionEditor() {
-	        super(new JComboBox(AccessPermission.values()));
+	        super(new JComboBox(AccessPermission.values()));	        
 	    }
 	}
 	
-	private class RegexRenderer extends DefaultTableCellRenderer {
+	private class PermissionTypeRenderer extends DefaultTableCellRenderer {
 
 		private static final long serialVersionUID = 1L;
 
-		public RegexRenderer() {
+		public PermissionTypeRenderer() {
 			super();
 			setHorizontalAlignment(SwingConstants.CENTER);
 		}
 
 		@Override
 		protected void setValue(Object value) {
-			boolean isExplicit = (Boolean) value;
-			if (isExplicit) {
-				// explicit permission
-				setText("");
-				setToolTipText(null);
-			} else {
-				// regex matched permission
+			PermissionType pType = (PermissionType) value;
+			switch (pType) {
+			case OWNER:
+				setText("owner");
+				setToolTipText(Translation.get("gb.ownerPermission"));
+				break;
+			case REGEX:
 				setText("regex");
 				setToolTipText(Translation.get("gb.regexPermission"));
+				break;
+			default:
+				setText("");
+				setToolTipText(null);
+				break;
 			}
 		}
 	}
diff --git a/src/com/gitblit/client/RegistrantPermissionsTableModel.java b/src/com/gitblit/client/RegistrantPermissionsTableModel.java
index fcd9c8b..9ed8db4 100644
--- a/src/com/gitblit/client/RegistrantPermissionsTableModel.java
+++ b/src/com/gitblit/client/RegistrantPermissionsTableModel.java
@@ -104,7 +104,7 @@
 			// and therefore can not be directly manipulated unless the current
 			// object is the source of the regex (i.e. a user or team with explicit
 			// regex definition)
-			return permissions.get(rowIndex).isExplicit;
+			return permissions.get(rowIndex).isEditable;
 		}
 		return false;
 	}
@@ -117,7 +117,7 @@
 		case Registrant:
 			return rp.registrant;
 		case Type:
-			return rp.isExplicit;
+			return rp.permissionType;
 		case Permission:
 			return rp.permission;
 		}
diff --git a/src/com/gitblit/client/UsersPanel.java b/src/com/gitblit/client/UsersPanel.java
index 9fcad7b..cd571b2 100644
--- a/src/com/gitblit/client/UsersPanel.java
+++ b/src/com/gitblit/client/UsersPanel.java
@@ -40,7 +40,9 @@
 import javax.swing.event.ListSelectionListener;
 import javax.swing.table.TableRowSorter;
 
+import com.gitblit.Constants.PermissionType;
 import com.gitblit.Constants.RpcRequest;
+import com.gitblit.models.RegistrantAccessPermission;
 import com.gitblit.models.TeamModel;
 import com.gitblit.models.UserModel;
 import com.gitblit.utils.StringUtils;
@@ -309,6 +311,21 @@
 				gitblit.getSettings());
 		dialog.setLocationRelativeTo(UsersPanel.this);
 		dialog.setUsers(gitblit.getUsers());
+		
+		List<RegistrantAccessPermission> permissions = user.getRepositoryPermissions();
+		for (RegistrantAccessPermission permission : permissions) {
+			if (permission.isEditable && PermissionType.EXPLICIT.equals(permission.permissionType)) {
+				// Ensure this is NOT an owner permission - which is non-editable
+				// We don't know this from within the usermodel, ownership is a
+				// property of a repository.
+				boolean isOwner = gitblit.getRepository(permission.registrant).isOwner(user.username);
+				if (isOwner) {
+					permission.permissionType = PermissionType.OWNER;
+					permission.isEditable = false;
+				}
+			}
+		}
+		
 		dialog.setRepositories(gitblit.getRepositories(), user.getRepositoryPermissions());
 		dialog.setTeams(gitblit.getTeams(), user.teams == null ? null : new ArrayList<TeamModel>(
 				user.teams));
diff --git a/src/com/gitblit/models/RegistrantAccessPermission.java b/src/com/gitblit/models/RegistrantAccessPermission.java
index 4a560d4..7346d31 100644
--- a/src/com/gitblit/models/RegistrantAccessPermission.java
+++ b/src/com/gitblit/models/RegistrantAccessPermission.java
@@ -18,6 +18,7 @@
 import java.io.Serializable;
 
 import com.gitblit.Constants.AccessPermission;
+import com.gitblit.Constants.PermissionType;
 import com.gitblit.Constants.RegistrantType;
 import com.gitblit.utils.StringUtils;
 
@@ -32,23 +33,27 @@
 
 	public String registrant;
 	public AccessPermission permission;
-	public RegistrantType type;
-	public boolean isExplicit;
+	public RegistrantType registrantType;
+	public PermissionType permissionType;
+	public boolean isEditable;
 
-	public RegistrantAccessPermission() {
-		isExplicit = true;
+	public RegistrantAccessPermission(RegistrantType registrantType) {
+		this.registrantType = registrantType;
+		this.permissionType = PermissionType.EXPLICIT;
+		this.isEditable = true;
 	}
 	
-	public RegistrantAccessPermission(String registrant, AccessPermission permission, boolean isExplicit, RegistrantType type) {
+	public RegistrantAccessPermission(String registrant, AccessPermission permission, PermissionType permissionType, RegistrantType registrantType, boolean isEditable) {
 		this.registrant = registrant;
 		this.permission = permission;
-		this.isExplicit = isExplicit;
-		this.type = type;
+		this.permissionType = permissionType;
+		this.registrantType = registrantType;
+		this.isEditable = isEditable;
 	}
 	
 	@Override
 	public int compareTo(RegistrantAccessPermission p) {
-		switch (type) {
+		switch (registrantType) {
 		case REPOSITORY:
 			return StringUtils.compareRepositoryNames(registrant, p.registrant);
 		default:
@@ -57,6 +62,21 @@
 	}
 	
 	@Override
+	public int hashCode() {
+		return registrant.hashCode();
+	}
+	
+	@Override
+	public boolean equals(Object o) {
+		if (o instanceof RegistrantAccessPermission) {
+			RegistrantAccessPermission p = (RegistrantAccessPermission) o;
+			return registrant.equals(p.registrant);
+		}
+		
+		return false;
+	}
+	
+	@Override
 	public String toString() {
 		return permission.asRole(registrant);
 	}
diff --git a/src/com/gitblit/models/TeamModel.java b/src/com/gitblit/models/TeamModel.java
index 7d557db..e5e3b09 100644
--- a/src/com/gitblit/models/TeamModel.java
+++ b/src/com/gitblit/models/TeamModel.java
@@ -27,6 +27,7 @@
 
 import com.gitblit.Constants.AccessPermission;
 import com.gitblit.Constants.AccessRestrictionType;
+import com.gitblit.Constants.PermissionType;
 import com.gitblit.Constants.RegistrantType;
 import com.gitblit.Constants.Unused;
 import com.gitblit.utils.StringUtils;
@@ -98,7 +99,14 @@
 	public List<RegistrantAccessPermission> getRepositoryPermissions() {
 		List<RegistrantAccessPermission> list = new ArrayList<RegistrantAccessPermission>();
 		for (Map.Entry<String, AccessPermission> entry : permissions.entrySet()) {
-			list.add(new RegistrantAccessPermission(entry.getKey(), entry.getValue(), true, RegistrantType.REPOSITORY));
+			String registrant = entry.getKey();
+			boolean editable = true;
+			PermissionType pType = PermissionType.EXPLICIT;
+			if (StringUtils.findInvalidCharacter(registrant) != null) {
+				// a regex will have at least 1 invalid character
+				pType = PermissionType.REGEX;
+			}
+			list.add(new RegistrantAccessPermission(registrant, entry.getValue(), pType, RegistrantType.REPOSITORY, editable));
 		}
 		Collections.sort(list);
 		return list;
diff --git a/src/com/gitblit/models/UserModel.java b/src/com/gitblit/models/UserModel.java
index d7bc293..22f250c 100644
--- a/src/com/gitblit/models/UserModel.java
+++ b/src/com/gitblit/models/UserModel.java
@@ -28,6 +28,7 @@
 import com.gitblit.Constants.AccessPermission;
 import com.gitblit.Constants.AccessRestrictionType;
 import com.gitblit.Constants.AuthorizationControl;
+import com.gitblit.Constants.PermissionType;
 import com.gitblit.Constants.RegistrantType;
 import com.gitblit.Constants.Unused;
 import com.gitblit.utils.ArrayUtils;
@@ -137,7 +138,17 @@
 	public List<RegistrantAccessPermission> getRepositoryPermissions() {
 		List<RegistrantAccessPermission> list = new ArrayList<RegistrantAccessPermission>();
 		for (Map.Entry<String, AccessPermission> entry : permissions.entrySet()) {
-			list.add(new RegistrantAccessPermission(entry.getKey(), entry.getValue(), true, RegistrantType.REPOSITORY));
+			String registrant = entry.getKey();
+			boolean editable = true;
+			PermissionType pType = PermissionType.EXPLICIT;
+			if (isMyPersonalRepository(registrant)) {
+				pType = PermissionType.OWNER;
+				editable = false;
+			} else if (StringUtils.findInvalidCharacter(registrant) != null) {
+				// a regex will have at least 1 invalid character
+				pType = PermissionType.REGEX;
+			}
+			list.add(new RegistrantAccessPermission(registrant, entry.getValue(), pType, RegistrantType.REPOSITORY, editable));
 		}
 		Collections.sort(list);
 		return list;
@@ -494,4 +505,9 @@
 		// Default UserModel doesn't implement branch-level security. Other Realms (i.e. Gerrit) may override this method.
 		return hasRepositoryPermission(repositoryName);
 	}
+	
+	public boolean isMyPersonalRepository(String repository) {
+		String projectPath = StringUtils.getFirstPathElement(repository);
+		return !StringUtils.isEmpty(projectPath) && projectPath.equalsIgnoreCase("~" + username);
+	}
 }
diff --git a/src/com/gitblit/wicket/GitBlitWebApp.properties b/src/com/gitblit/wicket/GitBlitWebApp.properties
index 62e4817..c6ceb9f 100644
--- a/src/com/gitblit/wicket/GitBlitWebApp.properties
+++ b/src/com/gitblit/wicket/GitBlitWebApp.properties
@@ -311,8 +311,8 @@
 gb.duration.oneYear = 1 year
 gb.duration.years = {0} years
 gb.authorizationControl = authorization control
-gb.allowAuthenticatedDescription = grant restricted access to all authenticated users
-gb.allowNamedDescription = grant restricted access to named users or teams
+gb.allowAuthenticatedDescription = grant RW+ permission to all authenticated users
+gb.allowNamedDescription = grant fine-grained permissions to named users or teams
 gb.markdownFailure = Failed to parse Markdown content!
 gb.clearCache = clear cache
 gb.projects = projects
@@ -364,3 +364,4 @@
 gb.gcPeriodDescription = duration between garbage collections
 gb.gcThreshold = GC threshold
 gb.gcThresholdDescription = minimum total size of loose objects to trigger early garbage collection
+gb.ownerPermission = repository owner
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/pages/BasePage.java b/src/com/gitblit/wicket/pages/BasePage.java
index dcca361..ceeb912 100644
--- a/src/com/gitblit/wicket/pages/BasePage.java
+++ b/src/com/gitblit/wicket/pages/BasePage.java
@@ -55,6 +55,7 @@
 import com.gitblit.Constants;
 import com.gitblit.Constants.AccessPermission;
 import com.gitblit.Constants.AccessRestrictionType;
+import com.gitblit.Constants.AuthorizationControl;
 import com.gitblit.Constants.FederationStrategy;
 import com.gitblit.GitBlit;
 import com.gitblit.Keys;
@@ -255,6 +256,21 @@
 		}
 		return map;
 	}
+	
+	protected Map<AuthorizationControl, String> getAuthorizationControls() {
+		Map<AuthorizationControl, String> map = new LinkedHashMap<AuthorizationControl, String>();
+		for (AuthorizationControl type : AuthorizationControl.values()) {
+			switch (type) {
+			case AUTHENTICATED:
+				map.put(type, getString("gb.allowAuthenticatedDescription"));
+				break;
+			case NAMED:
+				map.put(type, getString("gb.allowNamedDescription"));
+				break;
+			}
+		}
+		return map;
+	}
 
 	protected TimeZone getTimeZone() {
 		return GitBlit.getBoolean(Keys.web.useClientTimezone, false) ? GitBlitWebSession.get()
diff --git a/src/com/gitblit/wicket/pages/EditRepositoryPage.html b/src/com/gitblit/wicket/pages/EditRepositoryPage.html
index 7bd896c..638eae9 100644
--- a/src/com/gitblit/wicket/pages/EditRepositoryPage.html
+++ b/src/com/gitblit/wicket/pages/EditRepositoryPage.html
@@ -49,12 +49,7 @@
 				<tbody class="settings">
 					<tr><th><wicket:message key="gb.accessRestriction"></wicket:message></th><td class="edit"><select class="span4" wicket:id="accessRestriction" tabindex="15" /></td></tr>
 					<tr><th colspan="2"><hr/></th></tr>
-					<tr><th><wicket:message key="gb.authorizationControl"></wicket:message></th><td style="padding:2px;">
-						<wicket:container wicket:id="authorizationControl">
-							<label class="radio"><input type="radio" wicket:id="allowAuthenticated" tabindex="16" /> &nbsp;<span class="help-inline"><wicket:message key="gb.allowAuthenticatedDescription"></wicket:message></span></label>
-							<label class="radio"><input type="radio" wicket:id="allowNamed" tabindex="17" /> &nbsp;<span class="help-inline"><wicket:message key="gb.allowNamedDescription"></wicket:message></span></label>
-						</wicket:container>
-					</td></tr>
+					<tr><th><wicket:message key="gb.authorizationControl"></wicket:message></th><td style="padding:2px;"><span class="authorizationControl" wicket:id="authorizationControl"></span></td></tr>
 					<tr><th colspan="2"><hr/></th></tr>
 					<tr><th><wicket:message key="gb.isFrozen"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="isFrozen" tabindex="18" /> &nbsp;<span class="help-inline"><wicket:message key="gb.isFrozenDescription"></wicket:message></span></label></td></tr>
 					<tr><th><wicket:message key="gb.allowForks"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="allowForks" tabindex="19" /> &nbsp;<span class="help-inline"><wicket:message key="gb.allowForksDescription"></wicket:message></span></label></td></tr>
diff --git a/src/com/gitblit/wicket/pages/EditRepositoryPage.java b/src/com/gitblit/wicket/pages/EditRepositoryPage.java
index 1a2e63c..58fdf66 100644
--- a/src/com/gitblit/wicket/pages/EditRepositoryPage.java
+++ b/src/com/gitblit/wicket/pages/EditRepositoryPage.java
@@ -27,6 +27,9 @@
 import java.util.Set;
 
 import org.apache.wicket.PageParameters;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.ajax.form.AjaxFormChoiceComponentUpdatingBehavior;
+import org.apache.wicket.ajax.form.AjaxFormComponentUpdatingBehavior;
 import org.apache.wicket.behavior.SimpleAttributeModifier;
 import org.apache.wicket.extensions.markup.html.form.palette.Palette;
 import org.apache.wicket.markup.html.WebMarkupContainer;
@@ -36,8 +39,7 @@
 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.Radio;
-import org.apache.wicket.markup.html.form.RadioGroup;
+import org.apache.wicket.markup.html.form.RadioChoice;
 import org.apache.wicket.markup.html.form.TextField;
 import org.apache.wicket.markup.html.list.ListItem;
 import org.apache.wicket.markup.html.list.ListView;
@@ -51,6 +53,7 @@
 import com.gitblit.Constants.AccessRestrictionType;
 import com.gitblit.Constants.AuthorizationControl;
 import com.gitblit.Constants.FederationStrategy;
+import com.gitblit.Constants.RegistrantType;
 import com.gitblit.GitBlit;
 import com.gitblit.GitBlitException;
 import com.gitblit.Keys;
@@ -70,6 +73,8 @@
 	private final boolean isCreate;
 
 	private boolean isAdmin;
+	
+	RepositoryModel repositoryModel;
 
 	private IModel<String> mailingLists;
 
@@ -97,6 +102,7 @@
 		
 		setupPage(model);
 		setStatelessHint(false);
+		setOutputMarkupId(true);
 	}
 
 	public EditRepositoryPage(PageParameters params) {
@@ -107,9 +113,12 @@
 		RepositoryModel model = GitBlit.self().getRepositoryModel(name);
 		setupPage(model);
 		setStatelessHint(false);
+		setOutputMarkupId(true);
 	}
 
-	protected void setupPage(final RepositoryModel repositoryModel) {
+	protected void setupPage(RepositoryModel model) {
+		this.repositoryModel = model;
+		
 		// ensure this user can create or edit this repository
 		checkPermissions(repositoryModel);
 
@@ -145,10 +154,10 @@
 
 		final String oldName = repositoryModel.name;
 
-		RegistrantPermissionsPanel usersPalette = new RegistrantPermissionsPanel("users", 
-				GitBlit.self().getAllUsernames(), repositoryUsers, getAccessPermissions());
-		RegistrantPermissionsPanel teamsPalette = new RegistrantPermissionsPanel("teams", 
-				GitBlit.self().getAllTeamnames(), repositoryTeams, getAccessPermissions());
+		final RegistrantPermissionsPanel usersPalette = new RegistrantPermissionsPanel("users", 
+				RegistrantType.USER, GitBlit.self().getAllUsernames(), repositoryUsers, getAccessPermissions());
+		final RegistrantPermissionsPanel teamsPalette = new RegistrantPermissionsPanel("teams", 
+				RegistrantType.TEAM, GitBlit.self().getAllTeamnames(), repositoryTeams, getAccessPermissions());
 
 		// indexed local branches palette
 		List<String> allLocalBranches = new ArrayList<String>();
@@ -206,9 +215,9 @@
 		};
 		customFieldsListView.setReuseItems(true);
 
-		CompoundPropertyModel<RepositoryModel> model = new CompoundPropertyModel<RepositoryModel>(
+		CompoundPropertyModel<RepositoryModel> rModel = new CompoundPropertyModel<RepositoryModel>(
 				repositoryModel);
-		Form<RepositoryModel> form = new Form<RepositoryModel>("editForm", model) {
+		Form<RepositoryModel> form = new Form<RepositoryModel>("editForm", rModel) {
 
 			private static final long serialVersionUID = 1L;
 
@@ -366,8 +375,9 @@
 		form.add(new DropDownChoice<String>("owner", GitBlit.self().getAllUsernames())
 				.setEnabled(GitBlitWebSession.get().canAdmin()));
 		form.add(new CheckBox("allowForks"));
-		form.add(new DropDownChoice<AccessRestrictionType>("accessRestriction", Arrays
-				.asList(AccessRestrictionType.values()), new AccessRestrictionRenderer()));
+		DropDownChoice<AccessRestrictionType> accessRestriction = new DropDownChoice<AccessRestrictionType>("accessRestriction", Arrays
+				.asList(AccessRestrictionType.values()), new AccessRestrictionRenderer());
+		form.add(accessRestriction);
 		form.add(new CheckBox("isFrozen"));
 		// TODO enable origin definition
 		form.add(new TextField<String>("origin").setEnabled(false/* isCreate */));
@@ -403,12 +413,10 @@
 		form.add(new TextField<String>("mailingLists", mailingLists));
 		form.add(indexedBranchesPalette);
 		
-		RadioGroup<AuthorizationControl> group = new RadioGroup<AuthorizationControl>("authorizationControl");
-		Radio<AuthorizationControl> allowAuthenticated = new Radio<AuthorizationControl>("allowAuthenticated", new Model<AuthorizationControl>(AuthorizationControl.AUTHENTICATED));		
-		Radio<AuthorizationControl> allowNamed = new Radio<AuthorizationControl>("allowNamed", new Model<AuthorizationControl>(AuthorizationControl.NAMED));
-		group.add(allowAuthenticated);
-		group.add(allowNamed);
-		form.add(group);
+		List<AuthorizationControl> acList = Arrays.asList(AuthorizationControl.values());
+		final RadioChoice<AuthorizationControl> authorizationControl = new RadioChoice<Constants.AuthorizationControl>(
+				"authorizationControl", acList, new AuthorizationControlRenderer());
+		form.add(authorizationControl);
 				
 		form.add(new CheckBox("verifyCommitter"));
 
@@ -425,7 +433,69 @@
 		WebMarkupContainer customFieldsSection = new WebMarkupContainer("customFieldsSection");
 		customFieldsSection.add(customFieldsListView);
 		form.add(customFieldsSection.setVisible(!GitBlit.getString(Keys.groovy.customFields, "").isEmpty()));
+		
+		// initial enable/disable of permission controls
+		if (repositoryModel.accessRestriction.equals(AccessRestrictionType.NONE)) {
+			// anonymous everything, disable all controls
+			usersPalette.setEnabled(false);
+			teamsPalette.setEnabled(false);
+			authorizationControl.setEnabled(false);
+		} else {
+			// authenticated something
+			// enable authorization controls
+			authorizationControl.setEnabled(true);
+			
+			boolean allowFineGrainedControls = repositoryModel.authorizationControl.equals(AuthorizationControl.NAMED);
+			usersPalette.setEnabled(allowFineGrainedControls);
+			teamsPalette.setEnabled(allowFineGrainedControls);			
+		}
+		
+		accessRestriction.add(new AjaxFormComponentUpdatingBehavior("onchange") {
+	           
+			private static final long serialVersionUID = 1L;
 
+			protected void onUpdate(AjaxRequestTarget target) {
+				// enable/disable permissions panel based on access restriction
+				boolean allowAuthorizationControl = repositoryModel.accessRestriction.exceeds(AccessRestrictionType.NONE);
+				authorizationControl.setEnabled(allowAuthorizationControl);
+				
+				boolean allowFineGrainedControls = allowAuthorizationControl && repositoryModel.authorizationControl.equals(AuthorizationControl.NAMED);
+				usersPalette.setEnabled(allowFineGrainedControls);
+				teamsPalette.setEnabled(allowFineGrainedControls);
+				
+				if (allowFineGrainedControls) {
+					repositoryModel.authorizationControl = AuthorizationControl.NAMED;
+				}
+				
+				target.addComponent(authorizationControl);
+				target.addComponent(usersPalette);
+				target.addComponent(teamsPalette);
+			}
+		});
+		
+		authorizationControl.add(new AjaxFormChoiceComponentUpdatingBehavior() {
+	           
+			private static final long serialVersionUID = 1L;
+
+			protected void onUpdate(AjaxRequestTarget target) {
+				// enable/disable permissions panel based on access restriction
+				boolean allowAuthorizationControl = repositoryModel.accessRestriction.exceeds(AccessRestrictionType.NONE);
+				authorizationControl.setEnabled(allowAuthorizationControl);
+				
+				boolean allowFineGrainedControls = allowAuthorizationControl && repositoryModel.authorizationControl.equals(AuthorizationControl.NAMED);
+				usersPalette.setEnabled(allowFineGrainedControls);
+				teamsPalette.setEnabled(allowFineGrainedControls);
+				
+				if (allowFineGrainedControls) {
+					repositoryModel.authorizationControl = AuthorizationControl.NAMED;
+				}
+				
+				target.addComponent(authorizationControl);
+				target.addComponent(usersPalette);
+				target.addComponent(teamsPalette);
+			}
+		});
+		
 		form.add(new Button("save"));
 		Button cancel = new Button("cancel") {
 			private static final long serialVersionUID = 1L;
@@ -528,4 +598,25 @@
 			return Integer.toString(index);
 		}
 	}
+	
+	private class AuthorizationControlRenderer implements IChoiceRenderer<AuthorizationControl> {
+
+		private static final long serialVersionUID = 1L;
+
+		private final Map<AuthorizationControl, String> map;
+
+		public AuthorizationControlRenderer() {
+			map = getAuthorizationControls();
+		}
+
+		@Override
+		public String getDisplayValue(AuthorizationControl type) {
+			return map.get(type);
+		}
+
+		@Override
+		public String getIdValue(AuthorizationControl type, int index) {
+			return Integer.toString(index);
+		}
+	}
 }
diff --git a/src/com/gitblit/wicket/pages/EditTeamPage.java b/src/com/gitblit/wicket/pages/EditTeamPage.java
index 8908676..8ced03c 100644
--- a/src/com/gitblit/wicket/pages/EditTeamPage.java
+++ b/src/com/gitblit/wicket/pages/EditTeamPage.java
@@ -38,6 +38,7 @@
 
 import com.gitblit.GitBlit;
 import com.gitblit.GitBlitException;
+import com.gitblit.Constants.RegistrantType;
 import com.gitblit.models.RegistrantAccessPermission;
 import com.gitblit.models.TeamModel;
 import com.gitblit.utils.StringUtils;
@@ -60,6 +61,7 @@
 		isCreate = true;
 		setupPage(new TeamModel(""));
 		setStatelessHint(false);
+		setOutputMarkupId(true);
 	}
 
 	public EditTeamPage(PageParameters params) {
@@ -70,6 +72,7 @@
 		TeamModel model = GitBlit.self().getTeamModel(name);
 		setupPage(model);
 		setStatelessHint(false);
+		setOutputMarkupId(true);
 	}
 
 	protected void setupPage(final TeamModel teamModel) {
@@ -81,7 +84,7 @@
 
 		CompoundPropertyModel<TeamModel> model = new CompoundPropertyModel<TeamModel>(teamModel);
 
-		List<String> repos = getAccessRestrictedRepositoryList(true);
+		List<String> repos = getAccessRestrictedRepositoryList(true, null);
 
 		List<String> teamUsers = new ArrayList<String>(teamModel.users);
 		Collections.sort(teamUsers);
@@ -215,7 +218,8 @@
 				: StringUtils.flattenStrings(teamModel.mailingLists, " "));
 		form.add(new TextField<String>("mailingLists", mailingLists));
 
-		form.add(new RegistrantPermissionsPanel("repositories", repos, permissions, getAccessPermissions()));
+		form.add(new RegistrantPermissionsPanel("repositories", RegistrantType.REPOSITORY,
+				repos, permissions, getAccessPermissions()));
 		form.add(preReceivePalette);
 		form.add(new BulletListPanel("inheritedPreReceive", "inherited", GitBlit.self()
 				.getPreReceiveScriptsInherited(null)));
diff --git a/src/com/gitblit/wicket/pages/EditUserPage.java b/src/com/gitblit/wicket/pages/EditUserPage.java
index a165305..19d297b 100644
--- a/src/com/gitblit/wicket/pages/EditUserPage.java
+++ b/src/com/gitblit/wicket/pages/EditUserPage.java
@@ -37,6 +37,8 @@
 import com.gitblit.GitBlit;
 import com.gitblit.GitBlitException;
 import com.gitblit.Keys;
+import com.gitblit.Constants.PermissionType;
+import com.gitblit.Constants.RegistrantType;
 import com.gitblit.models.RegistrantAccessPermission;
 import com.gitblit.models.TeamModel;
 import com.gitblit.models.UserModel;
@@ -61,6 +63,7 @@
 		isCreate = true;
 		setupPage(new UserModel(""));
 		setStatelessHint(false);
+		setOutputMarkupId(true);
 	}
 
 	public EditUserPage(PageParameters params) {
@@ -71,6 +74,7 @@
 		UserModel model = GitBlit.self().getUserModel(name);
 		setupPage(model);
 		setStatelessHint(false);
+		setOutputMarkupId(true);
 	}
 
 	protected void setupPage(final UserModel userModel) {
@@ -85,7 +89,7 @@
 		CompoundPropertyModel<UserModel> model = new CompoundPropertyModel<UserModel>(userModel);
 
 		// build list of projects including all repositories wildcards
-		List<String> repos = getAccessRestrictedRepositoryList(true);
+		List<String> repos = getAccessRestrictedRepositoryList(true, userModel);
 		
 		List<String> userTeams = new ArrayList<String>();
 		for (TeamModel team : userModel.teams) {
@@ -95,6 +99,18 @@
 		
 		final String oldName = userModel.username;
 		final List<RegistrantAccessPermission> permissions = userModel.getRepositoryPermissions();
+		for (RegistrantAccessPermission permission : permissions) {
+			if (permission.isEditable && PermissionType.EXPLICIT.equals(permission.permissionType)) {
+				// Ensure this is NOT an owner permission - which is non-editable
+				// We don't know this from within the usermodel, ownership is a
+				// property of a repository.
+				boolean isOwner = GitBlit.self().getRepositoryModel(permission.registrant).isOwner(oldName);
+				if (isOwner) {
+					permission.permissionType = PermissionType.OWNER;
+					permission.isEditable = false;
+				}
+			}
+		}
 
 		final Palette<String> teams = new Palette<String>("teams", new ListModel<String>(
 				new ArrayList<String>(userTeams)), new CollectionModel<String>(GitBlit.self()
@@ -228,7 +244,7 @@
 		form.add(new CheckBox("canFork"));
 		form.add(new CheckBox("canCreate"));
 		form.add(new CheckBox("excludeFromFederation"));
-		form.add(new RegistrantPermissionsPanel("repositories",	repos, permissions, getAccessPermissions()));
+		form.add(new RegistrantPermissionsPanel("repositories",	RegistrantType.REPOSITORY, repos, permissions, getAccessPermissions()));
 		form.add(teams.setEnabled(editTeams));
 
 		form.add(new Button("save"));
diff --git a/src/com/gitblit/wicket/pages/RootSubPage.java b/src/com/gitblit/wicket/pages/RootSubPage.java
index 30d296e..891c892 100644
--- a/src/com/gitblit/wicket/pages/RootSubPage.java
+++ b/src/com/gitblit/wicket/pages/RootSubPage.java
@@ -21,9 +21,11 @@
 import org.apache.wicket.PageParameters;
 import org.apache.wicket.markup.html.basic.Label;
 
-import com.gitblit.GitBlit;
 import com.gitblit.Constants.AccessRestrictionType;
+import com.gitblit.Constants.AuthorizationControl;
+import com.gitblit.GitBlit;
 import com.gitblit.models.RepositoryModel;
+import com.gitblit.models.UserModel;
 import com.gitblit.utils.StringUtils;
 
 /**
@@ -52,7 +54,7 @@
 		super.setupPage("", pageName);
 	}
 	
-	protected List<String> getAccessRestrictedRepositoryList(boolean includeWildcards) {
+	protected List<String> getAccessRestrictedRepositoryList(boolean includeWildcards, UserModel user) {
 		// build list of access-restricted projects
 		String lastProject = null;
 		List<String> repos = new ArrayList<String>();
@@ -62,19 +64,26 @@
 			// all repositories excluding personal repositories
 			repos.add("[^~].*");
 		}
+		
 		for (String repo : GitBlit.self().getRepositoryList()) {
 			RepositoryModel repositoryModel = GitBlit.self().getRepositoryModel(repo);
-			if (repositoryModel.accessRestriction.exceeds(AccessRestrictionType.NONE)) {
+			if (repositoryModel.accessRestriction.exceeds(AccessRestrictionType.NONE)
+					&& repositoryModel.authorizationControl.equals(AuthorizationControl.NAMED)) {
+				if (user != null &&
+						(repositoryModel.isOwner(user.username) || repositoryModel.isUsersPersonalRepository(user.username))) {
+					// exclude Owner or personal repositories
+					continue;
+				}
 				if (includeWildcards) {
 					if (lastProject == null || !lastProject.equalsIgnoreCase(repositoryModel.projectPath)) {
-						lastProject = repositoryModel.projectPath;
+						lastProject = repositoryModel.projectPath.toLowerCase();
 						if (!StringUtils.isEmpty(repositoryModel.projectPath)) {
 							// regex for all repositories within a project
 							repos.add(repositoryModel.projectPath + "/.*");
 						}
 					}
 				}
-				repos.add(repo);
+				repos.add(repo.toLowerCase());
 			}
 		}
 		return repos;
diff --git a/src/com/gitblit/wicket/panels/RegistrantPermissionsPanel.html b/src/com/gitblit/wicket/panels/RegistrantPermissionsPanel.html
index 4c8c4ef..31f0b6b 100644
--- a/src/com/gitblit/wicket/panels/RegistrantPermissionsPanel.html
+++ b/src/com/gitblit/wicket/panels/RegistrantPermissionsPanel.html
@@ -9,7 +9,7 @@
 
 	<div wicket:id="permissionRow">
 		<div style="padding-top:10px;border-left:1px solid #ccc;border-right:1px solid #ccc;" class="row-fluid">
-			<div style="padding-top:5px;padding-left:5px;" class="span6"><span wicket:id="registrant"></span></div><div style="padding-top:5px;padding-right:5px;text-align:right;" class="span2"><span class="label label-info" wicket:id="regex">[regex]</span></div> <select class="input-medium" wicket:id="permission"></select>
+			<div style="padding-top:5px;padding-left:5px" class="span6"><span wicket:id="registrant"></span></div><div style="padding-top:5px;padding-right:5px;text-align:right;" class="span2"><span class="label" wicket:id="pType">[permission type]</span></div> <select class="input-medium" wicket:id="permission"></select>
 		</div>
 	</div>
 
diff --git a/src/com/gitblit/wicket/panels/RegistrantPermissionsPanel.java b/src/com/gitblit/wicket/panels/RegistrantPermissionsPanel.java
index b6ed890..27e48fb 100644
--- a/src/com/gitblit/wicket/panels/RegistrantPermissionsPanel.java
+++ b/src/com/gitblit/wicket/panels/RegistrantPermissionsPanel.java
@@ -36,6 +36,7 @@
 import org.apache.wicket.model.IModel;
 
 import com.gitblit.Constants.AccessPermission;
+import com.gitblit.Constants.PermissionType;
 import com.gitblit.Constants.RegistrantType;
 import com.gitblit.models.RegistrantAccessPermission;
 import com.gitblit.utils.DeepCopier;
@@ -52,8 +53,9 @@
 
 	private static final long serialVersionUID = 1L;
 
-	public RegistrantPermissionsPanel(String wicketId, List<String> allRegistrants, final List<RegistrantAccessPermission> permissions, final Map<AccessPermission, String> translations) {
+	public RegistrantPermissionsPanel(String wicketId, RegistrantType registrantType, List<String> allRegistrants, final List<RegistrantAccessPermission> permissions, final Map<AccessPermission, String> translations) {
 		super(wicketId);
+		setOutputMarkupId(true);
 		
 		// update existing permissions repeater
 		RefreshingView<RegistrantAccessPermission> dataView = new RefreshingView<RegistrantAccessPermission>("permissionRow") {
@@ -80,22 +82,40 @@
             
 			public void populateItem(final Item<RegistrantAccessPermission> item) {
 				final RegistrantAccessPermission entry = item.getModelObject();
-				if (RegistrantType.REPOSITORY.equals(entry.type)) {
-					// repository, strip .git and show swatch
+				if (RegistrantType.REPOSITORY.equals(entry.registrantType)) {
 					String repoName = StringUtils.stripDotGit(entry.registrant);
-					Label registrant = new Label("registrant", repoName);
-					WicketUtils.setCssClass(registrant, "repositorySwatch");
-					WicketUtils.setCssBackground(registrant, repoName);
-					item.add(registrant);
+					if (StringUtils.findInvalidCharacter(repoName) == null) {
+						// repository, strip .git and show swatch
+						Label registrant = new Label("registrant", repoName);
+						WicketUtils.setCssClass(registrant, "repositorySwatch");
+						WicketUtils.setCssBackground(registrant, repoName);
+						item.add(registrant);
+					} else {
+						// likely a regex
+						Label label = new Label("registrant", entry.registrant);
+						WicketUtils.setCssStyle(label, "font-weight: bold;");
+						item.add(label);
+					}
 				} else {
-					item.add(new Label("registrant", entry.registrant));
+					// user or team
+					Label label = new Label("registrant", entry.registrant);
+					WicketUtils.setCssStyle(label, "font-weight: bold;");
+					item.add(label);
 				}
-				if (entry.isExplicit) {
-					item.add(new Label("regex", "").setVisible(false));
-				} else {
-					Label regex = new Label("regex", "regex");
+				switch (entry.permissionType) {
+				case OWNER:
+					Label owner = new Label("pType", "owner");
+					WicketUtils.setHtmlTooltip(owner, getString("gb.ownerPermission"));
+					item.add(owner);
+					break;
+				case REGEX:
+					Label regex = new Label("pType", "regex");
 					WicketUtils.setHtmlTooltip(regex, getString("gb.regexPermission"));
 					item.add(regex);
+					break;
+				default:
+					item.add(new Label("pType", "").setVisible(false));
+					break;
 				}
 
 				// use ajax to get immediate update of permission level change
@@ -106,8 +126,9 @@
 				// only allow changing an explicitly defined permission
 				// this is designed to prevent changing a regex permission in
 				// a repository
-				permissionChoice.setEnabled(entry.isExplicit);
-				if (entry.isExplicit) {
+				permissionChoice.setEnabled(entry.isEditable);
+				permissionChoice.setOutputMarkupId(true);
+				if (entry.isEditable) {
 					permissionChoice.add(new AjaxFormComponentUpdatingBehavior("onchange") {
 		           
 						private static final long serialVersionUID = 1L;
@@ -127,11 +148,15 @@
 		// filter out registrants we already have permissions for
 		final List<String> registrants = new ArrayList<String>(allRegistrants);
 		for (RegistrantAccessPermission rp : permissions) {
-			registrants.remove(rp.registrant);
+			if (rp.isEditable) {
+				// only remove editable duplicates
+				// this allows for specifying an explicit permission
+				registrants.remove(rp.registrant);
+			}
 		}
 
 		// add new permission form
-		IModel<RegistrantAccessPermission> addPermissionModel = new CompoundPropertyModel<RegistrantAccessPermission>(new RegistrantAccessPermission());
+		IModel<RegistrantAccessPermission> addPermissionModel = new CompoundPropertyModel<RegistrantAccessPermission>(new RegistrantAccessPermission(registrantType));
 		Form<RegistrantAccessPermission> addPermissionForm = new Form<RegistrantAccessPermission>("addPermissionForm", addPermissionModel);
 		addPermissionForm.add(new DropDownChoice<String>("registrant", registrants));
 		addPermissionForm.add(new DropDownChoice<AccessPermission>("permission", Arrays
@@ -144,7 +169,11 @@
 			protected void onSubmit(AjaxRequestTarget target, Form<?> form) {
 				// add permission to our list
 				RegistrantAccessPermission rp = (RegistrantAccessPermission) form.getModel().getObject();
-				permissions.add(DeepCopier.copy(rp));
+				RegistrantAccessPermission copy = DeepCopier.copy(rp);
+				if (StringUtils.findInvalidCharacter(copy.registrant) != null) {
+					copy.permissionType = PermissionType.REGEX;
+				}
+				permissions.add(copy);
 				
 				// remove registrant from available choices
 				registrants.remove(rp.registrant);
@@ -159,6 +188,12 @@
 		add(addPermissionForm.setVisible(registrants.size() > 0));
 	}
 	
+	protected boolean getStatelessHint()
+	{
+		return false;
+	}
+
+	
 	private class AccessPermissionRenderer implements IChoiceRenderer<AccessPermission> {
 
 		private static final long serialVersionUID = 1L;
diff --git a/tests/com/gitblit/tests/RpcTests.java b/tests/com/gitblit/tests/RpcTests.java
index 0be2e02..3df0ff8 100644
--- a/tests/com/gitblit/tests/RpcTests.java
+++ b/tests/com/gitblit/tests/RpcTests.java
@@ -35,6 +35,7 @@
 import com.gitblit.Constants.AccessPermission;
 import com.gitblit.Constants.AccessRestrictionType;
 import com.gitblit.Constants.AuthorizationControl;
+import com.gitblit.Constants.PermissionType;
 import com.gitblit.Constants.RegistrantType;
 import com.gitblit.GitBlitException.UnauthorizedException;
 import com.gitblit.Keys;
@@ -199,7 +200,7 @@
 		List<RegistrantAccessPermission> permissions = RpcUtils.getRepositoryMemberPermissions(retrievedRepository, url, account,
 				password.toCharArray());
 		assertEquals("Membership permissions is not empty!", 0, permissions.size());
-		permissions.add(new RegistrantAccessPermission(testMember.username, AccessPermission.PUSH, true, RegistrantType.USER));
+		permissions.add(new RegistrantAccessPermission(testMember.username, AccessPermission.PUSH, PermissionType.EXPLICIT, RegistrantType.USER, true));
 		assertTrue(
 				"Failed to set member permissions!",
 				RpcUtils.setRepositoryMemberPermissions(retrievedRepository, permissions, url, account,
@@ -288,7 +289,7 @@
 		// set no teams
 		List<RegistrantAccessPermission> permissions = new ArrayList<RegistrantAccessPermission>();
 		for (String team : helloworldTeams) {
-			permissions.add(new RegistrantAccessPermission(team, AccessPermission.NONE, true, RegistrantType.TEAM));
+			permissions.add(new RegistrantAccessPermission(team, AccessPermission.NONE, PermissionType.EXPLICIT, RegistrantType.TEAM, true));
 		}
 		assertTrue(RpcUtils.setRepositoryTeamPermissions(helloworld, permissions, url, account,
 				password.toCharArray()));

--
Gitblit v1.9.1