From 2d48e28bf1068b20129b2e3d5b96ecaff48f9f2f Mon Sep 17 00:00:00 2001
From: James Moger <james.moger@gitblit.com>
Date: Tue, 23 Oct 2012 22:27:56 -0400
Subject: [PATCH] Implemented exclusion (X) permission

---
 docs/01_setup.mkd                               |   21 ++++++++++++++++++++-
 src/com/gitblit/wicket/GitBlitWebApp.properties |    3 ++-
 src/com/gitblit/wicket/pages/BasePage.java      |    3 +++
 tests/com/gitblit/tests/PermissionsTest.java    |   26 ++++++++++++++++++++++++++
 src/com/gitblit/Constants.java                  |    4 ++--
 5 files changed, 53 insertions(+), 4 deletions(-)

diff --git a/docs/01_setup.mkd b/docs/01_setup.mkd
index 6d015a3..c19f7fb 100644
--- a/docs/01_setup.mkd
+++ b/docs/01_setup.mkd
@@ -266,7 +266,26 @@
 
 Gitblit also supports *case-insensitive* regex matching for repository permissions.  The following permission grants push privileges to all repositories in the *mygroup* folder.
 
-    RW:mygroup/[a-z0-9-~_\\./]+
+    RW:mygroup/.*
+
+##### Exclusions
+
+When using regex matching it may also be useful to exclude specific repositories or to exclude regex repository matches.  You may specify the **X** permission for exclusion.  The following example grants clone permission to all repositories except the repositories in mygroup.  The user/team will have no access whatsoever to these repositories.
+
+    X:mygroup/.*
+    R:.*
+
+##### Order is Important
+
+The preceding example should suggest that order of permissions is important with regex matching.  Here are the rules for determining the permission that is applied to a repository request:
+
+1. If the user is an admin or repository owner, then RW+
+2. Else if user has an explicit permission, use that
+3. Else check for the first regex match in user permissions
+4. Else check for the HIGHEST permission from team memberships
+    1. If the team is an admin team, then RW+
+    2. Else if a team has an explicit permission, use that
+    3. Else check for the first regex match in team permissions
 
 #### No-So-Discrete Permissions (Gitblit <= v1.1.0)
 
diff --git a/src/com/gitblit/Constants.java b/src/com/gitblit/Constants.java
index 970c3db..33cf287 100644
--- a/src/com/gitblit/Constants.java
+++ b/src/com/gitblit/Constants.java
@@ -319,9 +319,9 @@
 	 * The access permissions available for a repository. 
 	 */
 	public static enum AccessPermission {
-		NONE("N"), VIEW("V"), CLONE("R"), PUSH("RW"), CREATE("RWC"), DELETE("RWD"), REWIND("RW+");
+		NONE("N"), EXCLUDE("X"), VIEW("V"), CLONE("R"), PUSH("RW"), CREATE("RWC"), DELETE("RWD"), REWIND("RW+");
 		
-		public static final AccessPermission [] NEWPERMISSIONS = { VIEW, CLONE, PUSH, CREATE, DELETE, REWIND };
+		public static final AccessPermission [] NEWPERMISSIONS = { EXCLUDE, VIEW, CLONE, PUSH, CREATE, DELETE, REWIND };
 		
 		public static AccessPermission LEGACY = REWIND;
 		
diff --git a/src/com/gitblit/wicket/GitBlitWebApp.properties b/src/com/gitblit/wicket/GitBlitWebApp.properties
index 41cbdd4..09ee929 100644
--- a/src/com/gitblit/wicket/GitBlitWebApp.properties
+++ b/src/com/gitblit/wicket/GitBlitWebApp.properties
@@ -348,7 +348,8 @@
 gb.userPermissions = user permissions
 gb.teamPermissions = team permissions
 gb.add = add
-gb.noPermission = NO ACCESS
+gb.noPermission = DELETE PERMISSION
+gb.excludePermission = {0} (exclude)
 gb.viewPermission = {0} (view)
 gb.clonePermission = {0} (clone)
 gb.pushPermission = {0} (push)
diff --git a/src/com/gitblit/wicket/pages/BasePage.java b/src/com/gitblit/wicket/pages/BasePage.java
index 48a872a..dcca361 100644
--- a/src/com/gitblit/wicket/pages/BasePage.java
+++ b/src/com/gitblit/wicket/pages/BasePage.java
@@ -212,6 +212,9 @@
 			case NONE:
 				map.put(type, MessageFormat.format(getString("gb.noPermission"), type.code));
 				break;
+			case EXCLUDE:
+				map.put(type, MessageFormat.format(getString("gb.excludePermission"), type.code));
+				break;
 			case VIEW:
 				map.put(type, MessageFormat.format(getString("gb.viewPermission"), type.code));
 				break;
diff --git a/tests/com/gitblit/tests/PermissionsTest.java b/tests/com/gitblit/tests/PermissionsTest.java
index befd360..b6ffa62 100644
--- a/tests/com/gitblit/tests/PermissionsTest.java
+++ b/tests/com/gitblit/tests/PermissionsTest.java
@@ -2533,6 +2533,32 @@
 		assertFalse("user CAN delete!", user.canDelete(personal));
 		assertFalse("user CAN edit!", user.canEdit(personal));
 	}
+	
+	@Test
+	public void testExclusion() throws Exception {
+		RepositoryModel personal = new RepositoryModel("~ubercool/_my-r/e~po.git", null, null, new Date());
+		personal.authorizationControl = AuthorizationControl.NAMED;
+		personal.accessRestriction = AccessRestrictionType.VIEW;
+
+		UserModel user = new UserModel("test");
+		user.setRepositoryPermission("~ubercool/.*", AccessPermission.EXCLUDE);
+		user.setRepositoryPermission(".*", AccessPermission.PUSH);
+		
+		// has EXCLUDE access because first match is EXCLUDE permission
+		assertTrue("user DOES NOT HAVE a repository permission!", user.hasRepositoryPermission(personal.name));
+		assertFalse("user CAN NOT view!", user.canView(personal));
+		assertFalse("user CAN NOT clone!", user.canClone(personal));
+		assertFalse("user CAN push!", user.canPush(personal));
+				
+		assertFalse("user CAN create ref!", user.canCreateRef(personal));
+		assertFalse("user CAN delete ref!", user.canDeleteRef(personal));
+		assertFalse("user CAN rewind ref!", user.canRewindRef(personal));
+
+		assertFalse("user CAN fork!", user.canFork(personal));
+				
+		assertFalse("user CAN delete!", user.canDelete(personal));
+		assertFalse("user CAN edit!", user.canEdit(personal));
+	}
 
 	@Test
 	public void testAdminTeamInheritance() throws Exception {

--
Gitblit v1.9.1