From 644bdd5a59a5ed5fbf93a0765f92608b0530c16a Mon Sep 17 00:00:00 2001
From: James Moger <james.moger@gitblit.com>
Date: Wed, 31 Oct 2012 23:16:24 -0400
Subject: [PATCH] Improve transparency of permissions by indicating permission source

---
 src/com/gitblit/models/RegistrantAccessPermission.java        |   15 ++
 src/com/gitblit/GitBlit.java                                  |   64 +++-------
 src/com/gitblit/wicket/GitBlitWebApp.properties               |    8 +
 src/com/gitblit/wicket/panels/RegistrantPermissionsPanel.java |   31 ++++
 src/com/gitblit/client/GitblitClient.java                     |   49 +------
 src/com/gitblit/client/RegistrantPermissionsPanel.java        |   23 +++
 tests/com/gitblit/tests/RpcTests.java                         |    4 
 src/com/gitblit/models/TeamModel.java                         |   36 ++++-
 src/com/gitblit/wicket/panels/RegistrantPermissionsPanel.html |    4 
 src/com/gitblit/client/RegistrantPermissionsTableModel.java   |    4 
 src/com/gitblit/models/UserModel.java                         |  102 +++++++++++++---
 src/com/gitblit/Constants.java                                |    2 
 12 files changed, 211 insertions(+), 131 deletions(-)

diff --git a/src/com/gitblit/Constants.java b/src/com/gitblit/Constants.java
index e7812ee..426d2df 100644
--- a/src/com/gitblit/Constants.java
+++ b/src/com/gitblit/Constants.java
@@ -387,7 +387,7 @@
 	}
 	
 	public static enum PermissionType {
-		EXPLICIT, OWNER, REGEX;
+		EXPLICIT, OWNER, ADMINISTRATOR, TEAM, REGEX;
 	}
 	
 	public static enum GCStatus {
diff --git a/src/com/gitblit/GitBlit.java b/src/com/gitblit/GitBlit.java
index 35451f6..a5d8b9e 100644
--- a/src/com/gitblit/GitBlit.java
+++ b/src/com/gitblit/GitBlit.java
@@ -80,7 +80,6 @@
 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;
@@ -665,41 +664,22 @@
 	}
 
 	/**
-	 * Returns the list of users and their access permissions for the specified repository.
+	 * Returns the list of users and their access permissions for the specified
+	 * repository including permission source information such as the team or
+	 * regular expression which sets the permission.
 	 * 
 	 * @param repository
-	 * @return a list of User-AccessPermission tuples
+	 * @return a list of RegistrantAccessPermissions
 	 */
 	public List<RegistrantAccessPermission> getUserAccessPermissions(RepositoryModel repository) {
-		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));
+		List<RegistrantAccessPermission> list = new ArrayList<RegistrantAccessPermission>();		
+		for (UserModel user : userService.getAllUsers()) {
+			RegistrantAccessPermission ap = user.getRepositoryPermission(repository);
+			if (ap.permission.exceeds(AccessPermission.NONE)) {
+				list.add(ap);
 			}
 		}
-		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);
-			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 new ArrayList<RegistrantAccessPermission>(permissions);
+		return list;
 	}
 	
 	/**
@@ -823,25 +803,23 @@
 	}
 	
 	/**
-	 * Returns the list of teams and their access permissions for the specified repository.
+	 * Returns the list of teams and their access permissions for the specified
+	 * repository including the source of the permission such as the admin flag
+	 * or a regular expression.
 	 * 
 	 * @param repository
-	 * @return a list of Team-AccessPermission tuples
+	 * @return a list of RegistrantAccessPermissions
 	 */
 	public List<RegistrantAccessPermission> getTeamAccessPermissions(RepositoryModel repository) {
-		List<RegistrantAccessPermission> permissions = new ArrayList<RegistrantAccessPermission>();
-		for (String team : userService.getTeamnamesForRepositoryRole(repository.name)) {
-			TeamModel model = userService.getTeamModel(team);
-			AccessPermission ap = model.getRepositoryPermission(repository);
-			PermissionType pType = PermissionType.REGEX;
-			boolean editable = false;
-			if (model.hasExplicitRepositoryPermission(repository.name)) {
-				pType = PermissionType.EXPLICIT;
-				editable = true;
+		List<RegistrantAccessPermission> list = new ArrayList<RegistrantAccessPermission>();
+		for (TeamModel team : userService.getAllTeams()) {
+			RegistrantAccessPermission ap = team.getRepositoryPermission(repository);
+			if (ap.permission.exceeds(AccessPermission.NONE)) {
+				list.add(ap);
 			}
-			permissions.add(new RegistrantAccessPermission(team, ap, pType, RegistrantType.TEAM, editable));
 		}
-		return permissions;
+		Collections.sort(list);
+		return list;
 	}
 	
 	/**
diff --git a/src/com/gitblit/client/GitblitClient.java b/src/com/gitblit/client/GitblitClient.java
index b7047d7..56078fc 100644
--- a/src/com/gitblit/client/GitblitClient.java
+++ b/src/com/gitblit/client/GitblitClient.java
@@ -31,8 +31,6 @@
 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;
@@ -508,38 +506,15 @@
 	}
 	
 	public List<RegistrantAccessPermission> getUserAccessPermissions(RepositoryModel repository) {
-		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));
-			}
-		}
-		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));
-			}
-		}
+		List<RegistrantAccessPermission> list = new ArrayList<RegistrantAccessPermission>();		
 		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));
+			RegistrantAccessPermission ap = user.getRepositoryPermission(repository);
+			if (ap.permission.exceeds(AccessPermission.NONE)) {
+				list.add(ap);
 			}
 		}
-		List<RegistrantAccessPermission> raps = new ArrayList<RegistrantAccessPermission>(list);
-		Collections.sort(raps);
-		return raps;
+		Collections.sort(list);
+		return list;
 	}
 
 	public boolean setUserAccessPermissions(RepositoryModel repository, List<RegistrantAccessPermission> permissions) throws IOException {
@@ -572,15 +547,9 @@
 	public List<RegistrantAccessPermission> getTeamAccessPermissions(RepositoryModel repository) {
 		List<RegistrantAccessPermission> list = new ArrayList<RegistrantAccessPermission>();
 		for (TeamModel team : allTeams) {
-			if (team.hasRepositoryPermission(repository.name)) {
-				AccessPermission ap = team.getRepositoryPermission(repository);
-				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));
+			RegistrantAccessPermission ap = team.getRepositoryPermission(repository);
+			if (ap.permission.exceeds(AccessPermission.NONE)) {
+				list.add(ap);
 			}
 		}
 		Collections.sort(list);
diff --git a/src/com/gitblit/client/RegistrantPermissionsPanel.java b/src/com/gitblit/client/RegistrantPermissionsPanel.java
index b8ab939..8ae23e9 100644
--- a/src/com/gitblit/client/RegistrantPermissionsPanel.java
+++ b/src/com/gitblit/client/RegistrantPermissionsPanel.java
@@ -19,6 +19,7 @@
 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.List;
 
@@ -136,6 +137,12 @@
 				// only remove editable duplicates
 				// this allows for specifying an explicit permission
 				filtered.remove(rp.registrant);
+			} else if (rp.isAdmin()) {
+				// administrators can not have their permission changed
+				filtered.remove(rp.registrant);
+			} else if (rp.isOwner()) {
+				// owners can not have their permission changed
+				filtered.remove(rp.registrant);
 			}
 		}
 		for (String registrant : filtered) {
@@ -172,15 +179,23 @@
 
 		@Override
 		protected void setValue(Object value) {
-			PermissionType pType = (PermissionType) value;
-			switch (pType) {
+			RegistrantAccessPermission ap = (RegistrantAccessPermission) value;
+			switch (ap.permissionType) {
+			case ADMINISTRATOR:
+				setText(ap.source == null ? Translation.get("gb.administrator") : ap.source);
+				setToolTipText(Translation.get("gb.administratorPermission"));
+				break;
 			case OWNER:
-				setText("owner");
+				setText(Translation.get("gb.owner"));
 				setToolTipText(Translation.get("gb.ownerPermission"));
+				break;
+			case TEAM:
+				setText(ap.source == null ? Translation.get("gb.team") : ap.source);
+				setToolTipText(MessageFormat.format(Translation.get("gb.teamPermission"), ap.source));
 				break;
 			case REGEX:
 				setText("regex");
-				setToolTipText(Translation.get("gb.regexPermission"));
+				setToolTipText(MessageFormat.format(Translation.get("gb.regexPermission"), ap.source));
 				break;
 			default:
 				setText("");
diff --git a/src/com/gitblit/client/RegistrantPermissionsTableModel.java b/src/com/gitblit/client/RegistrantPermissionsTableModel.java
index 9ed8db4..1df6bbc 100644
--- a/src/com/gitblit/client/RegistrantPermissionsTableModel.java
+++ b/src/com/gitblit/client/RegistrantPermissionsTableModel.java
@@ -91,7 +91,7 @@
 		if (columnIndex == Columns.Permission.ordinal()) {
 			return AccessPermission.class;
 		} else if (columnIndex == Columns.Type.ordinal()) {
-			return Boolean.class;
+			return RegistrantAccessPermission.class;
 		}
 		return String.class;
 	}
@@ -117,7 +117,7 @@
 		case Registrant:
 			return rp.registrant;
 		case Type:
-			return rp.permissionType;
+			return rp;
 		case Permission:
 			return rp.permission;
 		}
diff --git a/src/com/gitblit/models/RegistrantAccessPermission.java b/src/com/gitblit/models/RegistrantAccessPermission.java
index 7346d31..2d0b90b 100644
--- a/src/com/gitblit/models/RegistrantAccessPermission.java
+++ b/src/com/gitblit/models/RegistrantAccessPermission.java
@@ -36,6 +36,10 @@
 	public RegistrantType registrantType;
 	public PermissionType permissionType;
 	public boolean isEditable;
+	public String source;
+	
+	public RegistrantAccessPermission() {
+	}
 
 	public RegistrantAccessPermission(RegistrantType registrantType) {
 		this.registrantType = registrantType;
@@ -43,14 +47,23 @@
 		this.isEditable = true;
 	}
 	
-	public RegistrantAccessPermission(String registrant, AccessPermission permission, PermissionType permissionType, RegistrantType registrantType, boolean isEditable) {
+	public RegistrantAccessPermission(String registrant, AccessPermission permission, PermissionType permissionType, RegistrantType registrantType, String source, boolean isEditable) {
 		this.registrant = registrant;
 		this.permission = permission;
 		this.permissionType = permissionType;
 		this.registrantType = registrantType;
+		this.source = source;
 		this.isEditable = isEditable;
 	}
 	
+	public boolean isAdmin() {
+		return PermissionType.ADMINISTRATOR.equals(permissionType);
+	}
+
+	public boolean isOwner() {
+		return PermissionType.OWNER.equals(permissionType);
+	}
+
 	@Override
 	public int compareTo(RegistrantAccessPermission p) {
 		switch (registrantType) {
diff --git a/src/com/gitblit/models/TeamModel.java b/src/com/gitblit/models/TeamModel.java
index e5e3b09..acdb075 100644
--- a/src/com/gitblit/models/TeamModel.java
+++ b/src/com/gitblit/models/TeamModel.java
@@ -100,13 +100,15 @@
 		List<RegistrantAccessPermission> list = new ArrayList<RegistrantAccessPermission>();
 		for (Map.Entry<String, AccessPermission> entry : permissions.entrySet()) {
 			String registrant = entry.getKey();
+			String source = null;
 			boolean editable = true;
 			PermissionType pType = PermissionType.EXPLICIT;
 			if (StringUtils.findInvalidCharacter(registrant) != null) {
 				// a regex will have at least 1 invalid character
 				pType = PermissionType.REGEX;
+				source = registrant;
 			}
-			list.add(new RegistrantAccessPermission(registrant, entry.getValue(), pType, RegistrantType.REPOSITORY, editable));
+			list.add(new RegistrantAccessPermission(registrant, entry.getValue(), pType, RegistrantType.REPOSITORY, source, editable));
 		}
 		Collections.sort(list);
 		return list;
@@ -184,13 +186,27 @@
 		repositories.add(repository.toLowerCase());
 	}
 	
-	public AccessPermission getRepositoryPermission(RepositoryModel repository) {
-		AccessPermission permission = AccessPermission.NONE;
+	public RegistrantAccessPermission getRepositoryPermission(RepositoryModel repository) {
+		RegistrantAccessPermission ap = new RegistrantAccessPermission();
+		ap.registrant = name;
+		ap.registrantType = RegistrantType.TEAM;
+		ap.permission = AccessPermission.NONE;
+		ap.isEditable = false;
+		
+		if (canAdmin) {
+			ap.permissionType = PermissionType.ADMINISTRATOR;
+			ap.permission = AccessPermission.REWIND;
+			return ap;
+		}
+		
 		if (permissions.containsKey(repository.name.toLowerCase())) {
 			// exact repository permission specified
 			AccessPermission p = permissions.get(repository.name.toLowerCase());
 			if (p != null) {
-				permission = p;
+				ap.permissionType = PermissionType.EXPLICIT;
+				ap.permission = p;
+				ap.isEditable = true;
+				return ap;
 			}
 		} else {
 			// search for case-insensitive regex permission match
@@ -198,20 +214,22 @@
 				if (StringUtils.matchesIgnoreCase(repository.name, key)) {
 					AccessPermission p = permissions.get(key);
 					if (p != null) {
-						permission = p;
 						// take first match
-						break;
+						ap.permissionType = PermissionType.REGEX;
+						ap.permission = p;
+						ap.source = key;
+						return ap;
 					}
 				}
 			}
 		}
-		return permission;
+		return ap;
 	}
 	
 	protected boolean canAccess(RepositoryModel repository, AccessRestrictionType ifRestriction, AccessPermission requirePermission) {
 		if (repository.accessRestriction.atLeast(ifRestriction)) {
-			AccessPermission permission = getRepositoryPermission(repository);
-			return permission.atLeast(requirePermission);
+			RegistrantAccessPermission ap = getRepositoryPermission(repository);
+			return ap.permission.atLeast(requirePermission);
 		}
 		return true;
 	}
diff --git a/src/com/gitblit/models/UserModel.java b/src/com/gitblit/models/UserModel.java
index d7e8f57..e81d7ca 100644
--- a/src/com/gitblit/models/UserModel.java
+++ b/src/com/gitblit/models/UserModel.java
@@ -140,16 +140,25 @@
 		List<RegistrantAccessPermission> list = new ArrayList<RegistrantAccessPermission>();
 		for (Map.Entry<String, AccessPermission> entry : permissions.entrySet()) {
 			String registrant = entry.getKey();
+			String source = null;
 			boolean editable = true;
 			PermissionType pType = PermissionType.EXPLICIT;
-			if (isMyPersonalRepository(registrant)) {
+			if (canAdmin()) {
+				pType = PermissionType.ADMINISTRATOR;
+				editable = false;
+			} else 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;
+				source = registrant;
 			}
-			list.add(new RegistrantAccessPermission(registrant, entry.getValue(), pType, RegistrantType.REPOSITORY, editable));
+			if (AccessPermission.MISSING.equals(entry.getValue())) {
+				// repository can not be found, permission is not editable
+				editable = false;
+			}
+			list.add(new RegistrantAccessPermission(registrant, entry.getValue(), pType, RegistrantType.REPOSITORY, source, editable));
 		}
 		Collections.sort(list);
 		return list;
@@ -194,6 +203,24 @@
 	}
 	
 	/**
+	 * Returns true if the user's team memberships specify an access permission for
+	 * this repository.
+	 * 
+	 * @param name
+	 * @return if the user's team memberships specifi an access permission
+	 */
+	public boolean hasTeamRepositoryPermission(String name) {
+		if (teams != null) {
+			for (TeamModel team : teams) {
+				if (team.hasRepositoryPermission(name)) {
+					return true;
+				}
+			}
+		}
+		return false;
+	}
+	
+	/**
 	 * Adds a repository permission to the team.
 	 * <p>
 	 * Role may be formatted as:
@@ -220,23 +247,52 @@
 		permissions.put(repository.toLowerCase(), permission);
 	}
 
-	public AccessPermission getRepositoryPermission(RepositoryModel repository) {
-		if (canAdmin() || repository.isOwner(username) || repository.isUsersPersonalRepository(username)) {
-			return AccessPermission.REWIND;
+	public RegistrantAccessPermission getRepositoryPermission(RepositoryModel repository) {
+		RegistrantAccessPermission ap = new RegistrantAccessPermission();
+		ap.registrant = username;
+		ap.registrantType = RegistrantType.USER;
+		ap.permission = AccessPermission.NONE;
+		ap.isEditable = false;
+
+		// administrator
+		if (canAdmin()) {
+			ap.permissionType = PermissionType.ADMINISTRATOR;
+			ap.permission = AccessPermission.REWIND;
+			if (!canAdmin) {
+				// administator permission from team membership
+				for (TeamModel team : teams) {
+					if (team.canAdmin) {
+						ap.source = team.name;
+						break;
+					}
+				}
+			}
+			return ap;
 		}
+		
+		// repository owner - either specified owner or personal repository
+		if (repository.isOwner(username) || repository.isUsersPersonalRepository(username)) {
+			ap.permissionType = PermissionType.OWNER;
+			ap.permission = AccessPermission.REWIND;
+			return ap;
+		}
+		
 		if (AuthorizationControl.AUTHENTICATED.equals(repository.authorizationControl) && isAuthenticated) {
 			// AUTHENTICATED is a shortcut for authorizing all logged-in users RW access
-			return AccessPermission.REWIND;
+			ap.permission = AccessPermission.REWIND;
+			return ap;
 		}
 		
 		// explicit user permission OR user regex match is used
 		// if that fails, then the best team permission is used
-		AccessPermission permission = AccessPermission.NONE;
 		if (permissions.containsKey(repository.name.toLowerCase())) {
 			// exact repository permission specified, use it
 			AccessPermission p = permissions.get(repository.name.toLowerCase());
 			if (p != null) {
-				return p;
+				ap.permissionType = PermissionType.EXPLICIT;
+				ap.permission = p;
+				ap.isEditable = true;
+				return ap;
 			}
 		} else {
 			// search for case-insensitive regex permission match
@@ -245,29 +301,33 @@
 					AccessPermission p = permissions.get(key);
 					if (p != null) {
 						// take first match
-						permission = p;
-						break;
+						ap.permissionType = PermissionType.REGEX;
+						ap.permission = p;
+						ap.source = key;
+						return ap;
 					}
 				}
 			}
 		}
 		
-		if (AccessPermission.NONE.equals(permission)) {
-			for (TeamModel team : teams) {
-				AccessPermission p = team.getRepositoryPermission(repository);
-				if (p.exceeds(permission)) {
-					// use highest team permission
-					permission = p;
-				}
+		// try to find a team match
+		for (TeamModel team : teams) {
+			RegistrantAccessPermission p = team.getRepositoryPermission(repository);
+			if (p.permission.exceeds(ap.permission)) {
+				// use highest team permission
+				ap.permission = p.permission;
+				ap.source = team.name;
+				ap.permissionType = PermissionType.TEAM;
 			}
-		}
-		return permission;
+		}		
+		
+		return ap;
 	}
 	
 	protected boolean canAccess(RepositoryModel repository, AccessRestrictionType ifRestriction, AccessPermission requirePermission) {
 		if (repository.accessRestriction.atLeast(ifRestriction)) {
-			AccessPermission permission = getRepositoryPermission(repository);
-			return permission.atLeast(requirePermission);
+			RegistrantAccessPermission ap = getRepositoryPermission(repository);
+			return ap.permission.atLeast(requirePermission);
 		}
 		return true;
 	}
diff --git a/src/com/gitblit/wicket/GitBlitWebApp.properties b/src/com/gitblit/wicket/GitBlitWebApp.properties
index 94d9b53..1f33826 100644
--- a/src/com/gitblit/wicket/GitBlitWebApp.properties
+++ b/src/com/gitblit/wicket/GitBlitWebApp.properties
@@ -357,11 +357,15 @@
 gb.deletePermission = {0} (push, ref creation+deletion)
 gb.rewindPermission = {0} (push, ref creation+deletion+rewind)
 gb.permission = permission
-gb.regexPermission = this permission is set from a regular expression
+gb.regexPermission = this permission is set from regular expression \"{0}\"
 gb.accessDenied = access denied
 gb.busyCollectingGarbage = sorry, Gitblit is busy collecting garbage in {0}
 gb.gcPeriod = GC period
 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
+gb.ownerPermission = repository owner
+gb.administrator = admin
+gb.administratorPermission = Gitblit administrator
+gb.team = team
+gb.teamPermission = permission set by \"{0}\" team membership
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/panels/RegistrantPermissionsPanel.html b/src/com/gitblit/wicket/panels/RegistrantPermissionsPanel.html
index 8f85816..ec8d43d 100644
--- a/src/com/gitblit/wicket/panels/RegistrantPermissionsPanel.html
+++ b/src/com/gitblit/wicket/panels/RegistrantPermissionsPanel.html
@@ -9,13 +9,13 @@
 
 	<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" wicket:id="pType">[permission type]</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="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">
 		<form style="padding: 20px 40px;" class="well form-inline" wicket:id="addPermissionForm">
-			<select class="input-large" wicket:id="registrant"></select> <select class="input-medium" wicket:id="permission"></select> <input class="btn btn-success" type="submit" value="Add" wicket:message="value:gb.add" wicket:id="addPermissionButton"/>
+			<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>
 	
diff --git a/src/com/gitblit/wicket/panels/RegistrantPermissionsPanel.java b/src/com/gitblit/wicket/panels/RegistrantPermissionsPanel.java
index 805db9d..d6bd565 100644
--- a/src/com/gitblit/wicket/panels/RegistrantPermissionsPanel.java
+++ b/src/com/gitblit/wicket/panels/RegistrantPermissionsPanel.java
@@ -15,6 +15,7 @@
  */
 package com.gitblit.wicket.panels;
 
+import java.text.MessageFormat;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Iterator;
@@ -109,7 +110,7 @@
 					}
 
 					Fragment userFragment = new Fragment("registrant", "userRegistrant", RegistrantPermissionsPanel.this);
-					userFragment.add(new GravatarImage("userAvatar", ident, 16, false));
+					userFragment.add(new GravatarImage("userAvatar", ident, 20, false));
 					userFragment.add(new Label("userName", entry.registrant));					
 					item.add(userFragment);					
 				} else {
@@ -119,14 +120,30 @@
 					item.add(teamFragment);
 				}
 				switch (entry.permissionType) {
+				case ADMINISTRATOR:
+					Label administrator = new Label("pType", entry.source == null ? getString("gb.administrator") : entry.source);
+					WicketUtils.setHtmlTooltip(administrator, getString("gb.administratorPermission"));
+					WicketUtils.setCssClass(administrator, "label label-inverse");
+					item.add(administrator);
+					break;
 				case OWNER:
-					Label owner = new Label("pType", "owner");
+					Label owner = new Label("pType", getString("gb.owner"));
 					WicketUtils.setHtmlTooltip(owner, getString("gb.ownerPermission"));
+					WicketUtils.setCssClass(owner, "label label-info");
 					item.add(owner);
+					break;
+				case TEAM:
+					Label team = new Label("pType", entry.source == null ? getString("gb.team") : entry.source);
+					WicketUtils.setHtmlTooltip(team, MessageFormat.format(getString("gb.teamPermission"), entry.source));
+					WicketUtils.setCssClass(team, "label label-success");
+					item.add(team);
 					break;
 				case REGEX:
 					Label regex = new Label("pType", "regex");
-					WicketUtils.setHtmlTooltip(regex, getString("gb.regexPermission"));
+					if (!StringUtils.isEmpty(entry.source)) {
+						WicketUtils.setHtmlTooltip(regex, MessageFormat.format(getString("gb.regexPermission"), entry.source));
+					}
+					WicketUtils.setCssClass(regex, "label");
 					item.add(regex);
 					break;
 				default:
@@ -165,9 +182,15 @@
 		final List<String> registrants = new ArrayList<String>(allRegistrants);
 		for (RegistrantAccessPermission rp : permissions) {
 			if (rp.isEditable) {
-				// only remove editable duplicates
+				// remove editable duplicates
 				// this allows for specifying an explicit permission
 				registrants.remove(rp.registrant);
+			} else if (rp.isAdmin()) {
+				// administrators can not have their permission changed
+				registrants.remove(rp.registrant);
+			} else if (rp.isOwner()) {
+				// owners can not have their permission changed
+				registrants.remove(rp.registrant);
 			}
 		}
 
diff --git a/tests/com/gitblit/tests/RpcTests.java b/tests/com/gitblit/tests/RpcTests.java
index 3df0ff8..62d87bf 100644
--- a/tests/com/gitblit/tests/RpcTests.java
+++ b/tests/com/gitblit/tests/RpcTests.java
@@ -200,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, PermissionType.EXPLICIT, RegistrantType.USER, true));
+		permissions.add(new RegistrantAccessPermission(testMember.username, AccessPermission.PUSH, PermissionType.EXPLICIT, RegistrantType.USER, null, true));
 		assertTrue(
 				"Failed to set member permissions!",
 				RpcUtils.setRepositoryMemberPermissions(retrievedRepository, permissions, url, account,
@@ -289,7 +289,7 @@
 		// set no teams
 		List<RegistrantAccessPermission> permissions = new ArrayList<RegistrantAccessPermission>();
 		for (String team : helloworldTeams) {
-			permissions.add(new RegistrantAccessPermission(team, AccessPermission.NONE, PermissionType.EXPLICIT, RegistrantType.TEAM, true));
+			permissions.add(new RegistrantAccessPermission(team, AccessPermission.NONE, PermissionType.EXPLICIT, RegistrantType.TEAM, null, true));
 		}
 		assertTrue(RpcUtils.setRepositoryTeamPermissions(helloworld, permissions, url, account,
 				password.toCharArray()));

--
Gitblit v1.9.1