From ba6150d1712d5f5986e72333831940a46316aab3 Mon Sep 17 00:00:00 2001
From: James Moger <james.moger@gitblit.com>
Date: Fri, 02 Nov 2012 16:52:41 -0400
Subject: [PATCH] Permission filtering in web ui

---
 src/com/gitblit/models/RegistrantAccessPermission.java        |   51 +++++++
 src/com/gitblit/GitBlit.java                                  |   61 +++++++-
 src/com/gitblit/wicket/GitBlitWebApp.properties               |    5 
 .gitignore                                                    |    1 
 src/com/gitblit/wicket/panels/RegistrantPermissionsPanel.java |   90 +++++++++++-
 src/com/gitblit/client/GitblitClient.java                     |   56 +++++++
 src/com/gitblit/client/RegistrantPermissionsPanel.java        |   25 +++
 src/com/gitblit/client/UsersPanel.java                        |    2 
 src/com/gitblit/wicket/pages/EditUserPage.java                |   22 ---
 src/com/gitblit/wicket/panels/RegistrantPermissionsPanel.html |   20 ++
 src/com/gitblit/client/Utils.java                             |   25 +++
 src/com/gitblit/models/UserModel.java                         |   25 +++
 12 files changed, 328 insertions(+), 55 deletions(-)

diff --git a/.gitignore b/.gitignore
index 173bd34..ffaba31 100644
--- a/.gitignore
+++ b/.gitignore
@@ -28,3 +28,4 @@
 /.gradle
 /projects.conf
 /pom.xml
+/deploy
diff --git a/src/com/gitblit/GitBlit.java b/src/com/gitblit/GitBlit.java
index 0d883ad..0d37b44 100644
--- a/src/com/gitblit/GitBlit.java
+++ b/src/com/gitblit/GitBlit.java
@@ -79,6 +79,8 @@
 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;
 import com.gitblit.models.FederationSet;
@@ -658,18 +660,48 @@
 	 * @return a user object or null
 	 */
 	public UserModel getUserModel(String username) {
-		UserModel user = userService.getUserModel(username);
-		if (user != null) {
-			// TODO reconsider ownership as a user property
-			// manually specify personal repository ownerships
-			String folder = "~" + username;
-			for (String repository : getRepositoryList()) {
-				if (repository.toLowerCase().startsWith(folder)) {
-					user.setRepositoryPermission(repository, AccessPermission.REWIND);
+		UserModel user = userService.getUserModel(username);		
+		return user;
+	}
+	
+	/**
+	 * Returns the effective list of permissions for this user, taking into account
+	 * team memberships, ownerships.
+	 * 
+	 * @param user
+	 * @return the effective list of permissions for the user
+	 */
+	public List<RegistrantAccessPermission> getUserAccessPermissions(UserModel user) {
+		Set<RegistrantAccessPermission> set = new LinkedHashSet<RegistrantAccessPermission>();
+		set.addAll(user.getRepositoryPermissions());
+		// Flag missing repositories
+		for (RegistrantAccessPermission permission : set) {
+			if (permission.mutable && PermissionType.EXPLICIT.equals(permission.permissionType)) {
+				RepositoryModel rm = GitBlit.self().getRepositoryModel(permission.registrant);
+				if (rm == null) {
+					permission.permissionType = PermissionType.MISSING;
+					permission.mutable = false;
+					continue;
 				}
 			}
 		}
-		return user;
+
+		// TODO reconsider ownership as a user property
+		// manually specify personal repository ownerships
+		for (RepositoryModel rm : repositoryListCache.values()) {
+			if (rm.isUsersPersonalRepository(user.username) || rm.isOwner(user.username)) {
+				RegistrantAccessPermission rp = new RegistrantAccessPermission(rm.name, AccessPermission.REWIND,
+						PermissionType.OWNER, RegistrantType.REPOSITORY, null, false);
+				// user may be owner of a repository to which they've inherited
+				// a team permission, replace any existing perm with owner perm
+				set.remove(rp);
+				set.add(rp);
+			}
+		}
+		
+		List<RegistrantAccessPermission> list = new ArrayList<RegistrantAccessPermission>(set);
+		Collections.sort(list);
+		return list;
 	}
 
 	/**
@@ -681,7 +713,16 @@
 	 * @return a list of RegistrantAccessPermissions
 	 */
 	public List<RegistrantAccessPermission> getUserAccessPermissions(RepositoryModel repository) {
-		List<RegistrantAccessPermission> list = new ArrayList<RegistrantAccessPermission>();		
+		List<RegistrantAccessPermission> list = new ArrayList<RegistrantAccessPermission>();
+		if (AccessRestrictionType.NONE.equals(repository.accessRestriction)) {
+			// no permissions needed, REWIND for everyone!
+			return list;
+		}
+		if (AuthorizationControl.AUTHENTICATED.equals(repository.authorizationControl)) {
+			// no permissions needed, REWIND for authenticated!
+			return list;
+		}
+		// NAMED users and teams
 		for (UserModel user : userService.getAllUsers()) {
 			RegistrantAccessPermission ap = user.getRepositoryPermission(repository);
 			if (ap.permission.exceeds(AccessPermission.NONE)) {
diff --git a/src/com/gitblit/client/GitblitClient.java b/src/com/gitblit/client/GitblitClient.java
index 56078fc..1101cd6 100644
--- a/src/com/gitblit/client/GitblitClient.java
+++ b/src/com/gitblit/client/GitblitClient.java
@@ -31,6 +31,8 @@
 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;
 import com.gitblit.GitBlitException.UnauthorizedException;
@@ -505,15 +507,63 @@
 		return usernames;
 	}
 	
+	/**
+	 * Returns the effective list of permissions for this user, taking into account
+	 * team memberships, ownerships.
+	 * 
+	 * @param user
+	 * @return the effective list of permissions for the user
+	 */
+	public List<RegistrantAccessPermission> getUserAccessPermissions(UserModel user) {
+		Set<RegistrantAccessPermission> set = new LinkedHashSet<RegistrantAccessPermission>();
+		set.addAll(user.getRepositoryPermissions());
+		// Flag missing repositories
+		for (RegistrantAccessPermission permission : set) {
+			if (permission.mutable && PermissionType.EXPLICIT.equals(permission.permissionType)) {
+				RepositoryModel rm = getRepository(permission.registrant);
+				if (rm == null) {
+					permission.permissionType = PermissionType.MISSING;
+					permission.mutable = false;
+					continue;
+				}
+			}
+		}
+
+		// TODO reconsider ownership as a user property
+		// manually specify personal repository ownerships
+		for (RepositoryModel rm : allRepositories) {
+			if (rm.isUsersPersonalRepository(user.username) || rm.isOwner(user.username)) {
+				RegistrantAccessPermission rp = new RegistrantAccessPermission(rm.name, AccessPermission.REWIND,
+						PermissionType.OWNER, RegistrantType.REPOSITORY, null, false);
+				// user may be owner of a repository to which they've inherited
+				// a team permission, replace any existing perm with owner perm
+				set.remove(rp);
+				set.add(rp);
+			}
+		}
+		
+		List<RegistrantAccessPermission> list = new ArrayList<RegistrantAccessPermission>(set);
+		Collections.sort(list);
+		return list;
+	}
+	
 	public List<RegistrantAccessPermission> getUserAccessPermissions(RepositoryModel repository) {
-		List<RegistrantAccessPermission> list = new ArrayList<RegistrantAccessPermission>();		
-		for (UserModel user : getUsers()) {
+		List<RegistrantAccessPermission> list = new ArrayList<RegistrantAccessPermission>();
+		if (AccessRestrictionType.NONE.equals(repository.accessRestriction)) {
+			// no permissions needed, REWIND for everyone!
+			return list;
+		}
+		if (AuthorizationControl.AUTHENTICATED.equals(repository.authorizationControl)) {
+			// no permissions needed, REWIND for authenticated!
+			return list;
+		}
+		// NAMED users and teams
+		for (UserModel user : allUsers) {
 			RegistrantAccessPermission ap = user.getRepositoryPermission(repository);
 			if (ap.permission.exceeds(AccessPermission.NONE)) {
 				list.add(ap);
 			}
 		}
-		Collections.sort(list);
 		return list;
 	}
 
diff --git a/src/com/gitblit/client/RegistrantPermissionsPanel.java b/src/com/gitblit/client/RegistrantPermissionsPanel.java
index ef04a87..98dbfb7 100644
--- a/src/com/gitblit/client/RegistrantPermissionsPanel.java
+++ b/src/com/gitblit/client/RegistrantPermissionsPanel.java
@@ -16,11 +16,14 @@
 package com.gitblit.client;
 
 import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Component;
 import java.awt.Dimension;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
 import java.text.MessageFormat;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 
 import javax.swing.DefaultCellEditor;
@@ -36,6 +39,7 @@
 import com.gitblit.Constants.AccessPermission;
 import com.gitblit.Constants.PermissionType;
 import com.gitblit.Constants.RegistrantType;
+import com.gitblit.client.Utils.RowRenderer;
 import com.gitblit.models.RegistrantAccessPermission;
 import com.gitblit.utils.StringUtils;
 
@@ -60,7 +64,23 @@
 	public RegistrantPermissionsPanel(final RegistrantType registrantType) {
 		super(new BorderLayout(5, 5));
 		tableModel = new RegistrantPermissionsTableModel();
-		permissionsTable = Utils.newTable(tableModel, Utils.DATE_FORMAT);
+		permissionsTable = Utils.newTable(tableModel, Utils.DATE_FORMAT, new RowRenderer() {
+			Color clear = new Color(0, 0, 0, 0);
+			Color iceGray = new Color(0xf0, 0xf0, 0xf0);
+			
+			@Override
+			public void prepareRow(Component c, boolean isSelected, int row, int column) {
+				if (isSelected) {
+					c.setBackground(permissionsTable.getSelectionBackground());
+				} else {
+					if (tableModel.permissions.get(row).mutable) {
+						c.setBackground(clear);
+					} else {
+						c.setBackground(iceGray);
+					}
+				}
+			}
+		});
 		permissionsTable.setModel(tableModel);
 		permissionsTable.setPreferredScrollableViewportSize(new Dimension(400, 150));
 		JScrollPane jsp = new JScrollPane(permissionsTable);
@@ -91,11 +111,14 @@
 				rp.permission = (AccessPermission) permissionSelector.getSelectedItem();
 				if (StringUtils.findInvalidCharacter(rp.registrant) != null) {
 					rp.permissionType = PermissionType.REGEX;
+					rp.source = rp.registrant;
 				} else {
 					rp.permissionType = PermissionType.EXPLICIT;
 				}
 
 				tableModel.permissions.add(rp);
+				// resort permissions after insert to convey idea of eval order
+				Collections.sort(tableModel.permissions);
 				
 				registrantModel.removeElement(rp.registrant);
 				registrantSelector.setSelectedIndex(-1);
diff --git a/src/com/gitblit/client/UsersPanel.java b/src/com/gitblit/client/UsersPanel.java
index 1810f8c..e14c001 100644
--- a/src/com/gitblit/client/UsersPanel.java
+++ b/src/com/gitblit/client/UsersPanel.java
@@ -308,7 +308,7 @@
 				gitblit.getSettings());
 		dialog.setLocationRelativeTo(UsersPanel.this);
 		dialog.setUsers(gitblit.getUsers());
-		dialog.setRepositories(gitblit.getRepositories(), user.getRepositoryPermissions());
+		dialog.setRepositories(gitblit.getRepositories(), gitblit.getUserAccessPermissions(user));
 		dialog.setTeams(gitblit.getTeams(), user.teams == null ? null : new ArrayList<TeamModel>(
 				user.teams));
 		dialog.setVisible(true);
diff --git a/src/com/gitblit/client/Utils.java b/src/com/gitblit/client/Utils.java
index b24c6d8..1e6ab2b 100644
--- a/src/com/gitblit/client/Utils.java
+++ b/src/com/gitblit/client/Utils.java
@@ -49,7 +49,25 @@
 	public final static String DATE_FORMAT = "yyyy-MM-dd";
 
 	public static JTable newTable(TableModel model, String datePattern) {
-		JTable table = new JTable(model);
+		return newTable(model, datePattern, null);
+	}
+	
+	public static JTable newTable(TableModel model, String datePattern, final RowRenderer rowRenderer) {
+		JTable table;
+		if (rowRenderer == null) {
+			table = new JTable(model);
+		} else {
+			table = new JTable(model) {
+				
+				@Override
+				public Component prepareRenderer(TableCellRenderer renderer, int row, int column) {
+					Component c = super.prepareRenderer(renderer, row, column);
+					boolean isSelected = isCellSelected(row, column);
+					rowRenderer.prepareRow(c, isSelected, row, column);
+					return c;
+				}
+			};
+		}
 		table.setRowHeight(table.getFont().getSize() + 8);
 		table.setCellSelectionEnabled(false);
 		table.setRowSelectionAllowed(true);
@@ -148,5 +166,8 @@
 			showException(null, x);
 		}
 	}
-
+	
+	public static abstract class RowRenderer {
+		public abstract void prepareRow(Component c, boolean isSelected, int row, int column);
+	}
 }
diff --git a/src/com/gitblit/models/RegistrantAccessPermission.java b/src/com/gitblit/models/RegistrantAccessPermission.java
index 4bdc2da..8f4049a 100644
--- a/src/com/gitblit/models/RegistrantAccessPermission.java
+++ b/src/com/gitblit/models/RegistrantAccessPermission.java
@@ -63,18 +63,67 @@
 	public boolean isOwner() {
 		return PermissionType.OWNER.equals(permissionType);
 	}
+	
+	public boolean isExplicit() {
+		return PermissionType.EXPLICIT.equals(permissionType);
+	}
+
+	public boolean isRegex() {
+		return PermissionType.REGEX.equals(permissionType);
+	}
+
+	public boolean isTeam() {
+		return PermissionType.TEAM.equals(permissionType);
+	}
 
 	public boolean isMissing() {
 		return PermissionType.MISSING.equals(permissionType);
+	}
+	
+	public int getScore() {
+		switch (registrantType) {
+		case REPOSITORY:
+			if (isAdmin()) {
+				return 0;
+			}
+			if (isOwner()) {
+				return 1;
+			}
+			if (isExplicit()) {
+				return 2;
+			}
+			if (isRegex()) {
+				return 3;
+			}
+			if (isTeam()) {
+				return 4;
+			}
+		default:
+			return 0;
+		}
 	}
 	
 	@Override
 	public int compareTo(RegistrantAccessPermission p) {
 		switch (registrantType) {
 		case REPOSITORY:
+			// repository permissions are sorted in score order
+			// to convey the order in which permissions are tested
+			int score1 = getScore();
+			int score2 = p.getScore();
+			if (score1 <= 2 && score2 <= 2) {
+				// group admin, owner, and explicit together
+				return StringUtils.compareRepositoryNames(registrant, p.registrant);	
+			}
+			if (score1 < score2) {
+				return -1;
+			} else if (score2 < score1) {
+				return 1;
+			}
 			return StringUtils.compareRepositoryNames(registrant, p.registrant);
 		default:
-			return registrant.toLowerCase().compareTo(p.registrant.toLowerCase());		
+			// user and team permissions are string sorted
+			return registrant.toLowerCase().compareTo(p.registrant.toLowerCase());
 		}
 	}
 	
diff --git a/src/com/gitblit/models/UserModel.java b/src/com/gitblit/models/UserModel.java
index 23322c2..1159905 100644
--- a/src/com/gitblit/models/UserModel.java
+++ b/src/com/gitblit/models/UserModel.java
@@ -21,6 +21,7 @@
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -160,7 +161,20 @@
 			list.add(new RegistrantAccessPermission(registrant, ap, pType, RegistrantType.REPOSITORY, source, mutable));
 		}
 		Collections.sort(list);
-		return list;
+		
+		// include immutable team permissions, being careful to preserve order
+		Set<RegistrantAccessPermission> set = new LinkedHashSet<RegistrantAccessPermission>(list);
+		for (TeamModel team : teams) {
+			for (RegistrantAccessPermission teamPermission : team.getRepositoryPermissions()) {
+				// we can not change an inherited team permission, though we can override
+				teamPermission.registrantType = RegistrantType.REPOSITORY;
+				teamPermission.permissionType = PermissionType.TEAM;
+				teamPermission.source = team.name;
+				teamPermission.mutable = false;
+				set.add(teamPermission);
+			}
+		}
+		return new ArrayList<RegistrantAccessPermission>(set);
 	}
 	
 	/**
@@ -253,6 +267,13 @@
 		ap.permission = AccessPermission.NONE;
 		ap.mutable = false;
 
+		if (AccessRestrictionType.NONE.equals(repository.accessRestriction)) {
+			// anonymous rewind
+			ap.permissionType = PermissionType.ADMINISTRATOR;
+			ap.permission = AccessPermission.REWIND;
+			return ap;
+		}
+
 		// administrator
 		if (canAdmin()) {
 			ap.permissionType = PermissionType.ADMINISTRATOR;
@@ -277,7 +298,7 @@
 		}
 		
 		if (AuthorizationControl.AUTHENTICATED.equals(repository.authorizationControl) && isAuthenticated) {
-			// AUTHENTICATED is a shortcut for authorizing all logged-in users RW access
+			// AUTHENTICATED is a shortcut for authorizing all logged-in users RW+ access
 			ap.permission = AccessPermission.REWIND;
 			return ap;
 		}
diff --git a/src/com/gitblit/wicket/GitBlitWebApp.properties b/src/com/gitblit/wicket/GitBlitWebApp.properties
index 4303b13..22ae92f 100644
--- a/src/com/gitblit/wicket/GitBlitWebApp.properties
+++ b/src/com/gitblit/wicket/GitBlitWebApp.properties
@@ -370,4 +370,7 @@
 gb.team = team
 gb.teamPermission = permission set by \"{0}\" team membership
 gb.missing = missing!
-gb.missingPermission = the repository for this permission is missing!
\ No newline at end of file
+gb.missingPermission = the repository for this permission is missing!
+gb.mutable = mutable
+gb.specified = specified
+gb.effective = effective
diff --git a/src/com/gitblit/wicket/pages/EditUserPage.java b/src/com/gitblit/wicket/pages/EditUserPage.java
index ea92293..80f09db 100644
--- a/src/com/gitblit/wicket/pages/EditUserPage.java
+++ b/src/com/gitblit/wicket/pages/EditUserPage.java
@@ -34,13 +34,11 @@
 import org.apache.wicket.model.util.CollectionModel;
 import org.apache.wicket.model.util.ListModel;
 
-import com.gitblit.Constants.PermissionType;
 import com.gitblit.Constants.RegistrantType;
 import com.gitblit.GitBlit;
 import com.gitblit.GitBlitException;
 import com.gitblit.Keys;
 import com.gitblit.models.RegistrantAccessPermission;
-import com.gitblit.models.RepositoryModel;
 import com.gitblit.models.TeamModel;
 import com.gitblit.models.UserModel;
 import com.gitblit.utils.StringUtils;
@@ -104,25 +102,7 @@
 		Collections.sort(userTeams);
 		
 		final String oldName = userModel.username;
-		final List<RegistrantAccessPermission> permissions = userModel.getRepositoryPermissions();
-		for (RegistrantAccessPermission permission : permissions) {
-			if (permission.mutable && 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.
-				RepositoryModel rm = GitBlit.self().getRepositoryModel(permission.registrant);
-				if (rm == null) {
-					permission.permissionType = PermissionType.MISSING;
-					permission.mutable = false;
-					continue;
-				}
-				boolean isOwner = rm.isOwner(oldName);
-				if (isOwner) {
-					permission.permissionType = PermissionType.OWNER;
-					permission.mutable = false;
-				}
-			}
-		}
+		final List<RegistrantAccessPermission> permissions = GitBlit.self().getUserAccessPermissions(userModel);
 
 		final Palette<String> teams = new Palette<String>("teams", new ListModel<String>(
 				new ArrayList<String>(userTeams)), new CollectionModel<String>(GitBlit.self()
diff --git a/src/com/gitblit/wicket/panels/RegistrantPermissionsPanel.html b/src/com/gitblit/wicket/panels/RegistrantPermissionsPanel.html
index ec8d43d..eb82245 100644
--- a/src/com/gitblit/wicket/panels/RegistrantPermissionsPanel.html
+++ b/src/com/gitblit/wicket/panels/RegistrantPermissionsPanel.html
@@ -7,20 +7,32 @@
 <body>
 <wicket:panel>
 
-	<div wicket:id="permissionRow">
+	<form class="form-inline" wicket:id="permissionToggleForm">
+		<div style="padding-bottom:10px" class="btn-group pull-right" data-toggle="buttons-radio">
+			<a class="btn btn-info" wicket:id="showMutable"><wicket:message key="gb.mutable"></wicket:message></a>
+			<a class="btn btn-info" wicket:id="showSpecified"><wicket:message key="gb.specified"></wicket:message></a>
+			<a class="btn btn-info" wicket:id="showEffective"><wicket:message key="gb.effective"></wicket:message></a>
+		</div>
+	</form>
+	
+	<div style="clear:both;" 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="span3"><span class="label" wicket:id="pType">[permission type]</span></div> <select class="input-medium" wicket:id="permission"></select>
 		</div>
 	</div>
 
-	<div style="padding-top:15px;" class="row-fluid">
+	<div style="clear:both; padding-top:15px;" class="row-fluid">
 		<form style="padding: 20px 40px;" class="well form-inline" wicket:id="addPermissionForm">
 			<select class="input-xlarge" wicket:id="registrant"></select> <select class="input-large" wicket:id="permission"></select> <input class="btn btn-success" type="submit" value="Add" wicket:message="value:gb.add" wicket:id="addPermissionButton"/>
 		</form>
 	</div>
-	
+
+	<wicket:fragment wicket:id="repositoryRegistrant">
+		<b><span class="repositorySwatch" wicket:id="repositorySwatch"></span></b> <span wicket:id="repositoryName"></span>
+	</wicket:fragment>
+
 	<wicket:fragment wicket:id="userRegistrant">
-		<span wicket:id="userAvatar"></span> <span style="font-weight: bold;" wicket:id="userName"></span>
+		<span wicket:id="userAvatar"></span> <span wicket:id="userName"></span>
 	</wicket:fragment>
 
 	<wicket:fragment wicket:id="teamRegistrant">
diff --git a/src/com/gitblit/wicket/panels/RegistrantPermissionsPanel.java b/src/com/gitblit/wicket/panels/RegistrantPermissionsPanel.java
index 9431df8..689ee57 100644
--- a/src/com/gitblit/wicket/panels/RegistrantPermissionsPanel.java
+++ b/src/com/gitblit/wicket/panels/RegistrantPermissionsPanel.java
@@ -18,10 +18,12 @@
 import java.text.MessageFormat;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 
+import org.apache.wicket.Component;
 import org.apache.wicket.ajax.AjaxRequestTarget;
 import org.apache.wicket.ajax.form.AjaxFormComponentUpdatingBehavior;
 import org.apache.wicket.ajax.markup.html.form.AjaxButton;
@@ -57,12 +59,42 @@
 public class RegistrantPermissionsPanel extends BasePanel {
 
 	private static final long serialVersionUID = 1L;
-
+	
+	public enum Show {
+		specified, mutable, effective;
+		
+		public boolean show(RegistrantAccessPermission ap) {
+			switch (this) {
+			case specified:
+				return ap.mutable || ap.isOwner();
+			case mutable:
+				return ap.mutable;
+			case effective:
+				return true;
+			default:
+				return true;
+			}
+		}
+	}
+	
+	private Show activeState = Show.mutable;
+	
 	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
+
+		/*
+		 * Permission view toggle buttons
+		 */
+		Form<Void> permissionToggleForm = new Form<Void>("permissionToggleForm");
+		permissionToggleForm.add(new ShowStateButton("showSpecified", Show.specified));
+		permissionToggleForm.add(new ShowStateButton("showMutable", Show.mutable));
+		permissionToggleForm.add(new ShowStateButton("showEffective", Show.effective));
+		add(permissionToggleForm);
+
+		/*
+		 * Permission repeating display
+		 */
 		RefreshingView<RegistrantAccessPermission> dataView = new RefreshingView<RegistrantAccessPermission>("permissionRow") {
 			private static final long serialVersionUID = 1L;
 		
@@ -91,16 +123,19 @@
 					String repoName = StringUtils.stripDotGit(entry.registrant);
 					if (!entry.isMissing() && 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);
+						Fragment repositoryFragment = new Fragment("registrant", "repositoryRegistrant", RegistrantPermissionsPanel.this);
+						Component swatch = new Label("repositorySwatch", "&nbsp;").setEscapeModelStrings(false);
+						WicketUtils.setCssBackground(swatch, entry.toString());
+						repositoryFragment.add(swatch);
+						Label registrant = new Label("repositoryName", repoName);
+						repositoryFragment.add(registrant);
+						item.add(repositoryFragment);
 					} else {
 						// regex or missing
 						Label label = new Label("registrant", entry.registrant);
 						WicketUtils.setCssStyle(label, "font-weight: bold;");
 						item.add(label);
-					}
+					}					
 				} else if (RegistrantType.USER.equals(entry.registrantType)) {
 					// user
 					PersonIdent ident = new PersonIdent(entry.registrant, null);
@@ -160,6 +195,8 @@
 					break;
 				}
 
+				item.setVisible(activeState.show(entry));
+
 				// use ajax to get immediate update of permission level change
 				// otherwise we can lose it if they change levels and then add
 				// a new repository permission
@@ -203,7 +240,9 @@
 			}
 		}
 
-		// add new permission form
+		/*
+		 * Add permission form
+		 */
 		IModel<RegistrantAccessPermission> addPermissionModel = new CompoundPropertyModel<RegistrantAccessPermission>(new RegistrantAccessPermission(registrantType));
 		Form<RegistrantAccessPermission> addPermissionForm = new Form<RegistrantAccessPermission>("addPermissionForm", addPermissionModel);
 		addPermissionForm.add(new DropDownChoice<String>("registrant", registrants));
@@ -223,8 +262,12 @@
 				RegistrantAccessPermission copy = DeepCopier.copy(rp);
 				if (StringUtils.findInvalidCharacter(copy.registrant) != null) {
 					copy.permissionType = PermissionType.REGEX;
+					copy.source = copy.registrant;
 				}
 				permissions.add(copy);
+				
+				// resort permissions after insert to convey idea of eval order
+				Collections.sort(permissions);
 				
 				// remove registrant from available choices
 				registrants.remove(rp.registrant);
@@ -265,4 +308,33 @@
 			return Integer.toString(index);
 		}
 	}
+	
+	private class ShowStateButton extends AjaxButton {
+		private static final long serialVersionUID = 1L;
+
+		Show buttonState;
+		
+		public ShowStateButton(String wicketId, Show state) {
+			super(wicketId);
+			this.buttonState = state;
+			setOutputMarkupId(true);
+		}
+		
+		@Override
+		protected void onBeforeRender()
+		{
+			String cssClass = "btn";
+			if (buttonState.equals(RegistrantPermissionsPanel.this.activeState)) {
+				cssClass = "btn btn-info active";
+			}
+			WicketUtils.setCssClass(this, cssClass);
+			super.onBeforeRender();
+		}
+		
+		@Override
+		protected void onSubmit(AjaxRequestTarget target, Form<?> form) {
+			RegistrantPermissionsPanel.this.activeState = buttonState;
+			target.addComponent(RegistrantPermissionsPanel.this);
+		}
+	};
 }

--
Gitblit v1.9.1