From 20714aee0d2d2a989d93d6065e081aed8ac85fbf Mon Sep 17 00:00:00 2001
From: James Moger <james.moger@gitblit.com>
Date: Wed, 10 Oct 2012 00:05:34 -0400
Subject: [PATCH] Finer-grained repository access permissions (issue 36)
---
tests/com/gitblit/tests/GitServletTest.java | 217 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 217 insertions(+), 0 deletions(-)
diff --git a/tests/com/gitblit/tests/GitServletTest.java b/tests/com/gitblit/tests/GitServletTest.java
index bdbb2a5..09e0e5a 100644
--- a/tests/com/gitblit/tests/GitServletTest.java
+++ b/tests/com/gitblit/tests/GitServletTest.java
@@ -13,18 +13,28 @@
import org.eclipse.jgit.api.CloneCommand;
import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.ResetCommand.ResetType;
+import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.transport.CredentialsProvider;
+import org.eclipse.jgit.transport.PushResult;
+import org.eclipse.jgit.transport.RefSpec;
+import org.eclipse.jgit.transport.RemoteRefUpdate;
+import org.eclipse.jgit.transport.RemoteRefUpdate.Status;
import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
import org.eclipse.jgit.util.FileUtils;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
+import com.gitblit.Constants.AccessPermission;
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.JGitUtils;
public class GitServletTest {
@@ -233,6 +243,213 @@
}
close(git);
}
+
+ @Test
+ public void testBlockClone() throws Exception {
+ testRefChange(AccessPermission.VIEW, null, null, null);
+ }
+
+ @Test
+ public void testBlockPush() throws Exception {
+ testRefChange(AccessPermission.CLONE, null, null, null);
+ }
+
+ @Test
+ public void testBlockBranchCreation() throws Exception {
+ testRefChange(AccessPermission.PUSH, Status.REJECTED_OTHER_REASON, null, null);
+ }
+
+ @Test
+ public void testBlockBranchDeletion() throws Exception {
+ testRefChange(AccessPermission.CREATE, Status.OK, Status.REJECTED_OTHER_REASON, null);
+ }
+
+ @Test
+ public void testBlockBranchRewind() throws Exception {
+ testRefChange(AccessPermission.DELETE, Status.OK, Status.OK, Status.REJECTED_OTHER_REASON);
+ }
+
+ @Test
+ public void testBranchRewind() throws Exception {
+ testRefChange(AccessPermission.REWIND, Status.OK, Status.OK, Status.OK);
+ }
+
+ private void testRefChange(AccessPermission permission, Status expectedCreate, Status expectedDelete, Status expectedRewind) throws Exception {
+
+ UserModel user = new UserModel("james");
+ user.password = "james";
+
+ if (GitBlit.self().getUserModel(user.username) != null) {
+ GitBlit.self().deleteUser(user.username);
+ }
+
+ CredentialsProvider cp = new UsernamePasswordCredentialsProvider(user.username, user.password);
+
+ // fork from original to a temporary bare repo
+ File refChecks = new File(GitBlitSuite.REPOSITORIES, "refchecks/ticgit.git");
+ if (refChecks.exists()) {
+ FileUtils.delete(refChecks, FileUtils.RECURSIVE);
+ }
+ CloneCommand clone = Git.cloneRepository();
+ clone.setURI(MessageFormat.format("{0}/git/ticgit.git", url));
+ clone.setDirectory(refChecks);
+ clone.setBare(true);
+ clone.setCloneAllBranches(true);
+ clone.setCredentialsProvider(cp);
+ close(clone.call());
+
+ // elevate repository to clone permission
+ RepositoryModel model = GitBlit.self().getRepositoryModel("refchecks/ticgit.git");
+ switch (permission) {
+ case VIEW:
+ model.accessRestriction = AccessRestrictionType.CLONE;
+ break;
+ case CLONE:
+ model.accessRestriction = AccessRestrictionType.CLONE;
+ break;
+ default:
+ model.accessRestriction = AccessRestrictionType.PUSH;
+ }
+ model.authorizationControl = AuthorizationControl.NAMED;
+
+ // grant user specified
+ user.setRepositoryPermission(model.name, permission);
+
+ GitBlit.self().updateUserModel(user.username, user, true);
+ GitBlit.self().updateRepositoryModel(model.name, model, false);
+
+ // clone temp bare repo to working copy
+ File local = new File(GitBlitSuite.REPOSITORIES, "refchecks/ticgit-wc");
+ if (local.exists()) {
+ FileUtils.delete(local, FileUtils.RECURSIVE);
+ }
+ clone = Git.cloneRepository();
+ clone.setURI(MessageFormat.format("{0}/git/{1}", url, model.name));
+ clone.setDirectory(local);
+ clone.setBare(false);
+ clone.setCloneAllBranches(true);
+ clone.setCredentialsProvider(cp);
+
+ try {
+ close(clone.call());
+ } catch (GitAPIException e) {
+ if (permission.atLeast(AccessPermission.CLONE)) {
+ throw e;
+ } else {
+ // user does not have clone permission
+ assertTrue(e.getMessage(), e.getMessage().contains("not permitted"));
+ return;
+ }
+ }
+
+ Git git = Git.open(local);
+
+ // commit a file and push it
+ File file = new File(local, "PUSHCHK");
+ OutputStreamWriter os = new OutputStreamWriter(new FileOutputStream(file, true), Constants.CHARSET);
+ BufferedWriter w = new BufferedWriter(os);
+ w.write("// " + new Date().toString() + "\n");
+ w.close();
+ git.add().addFilepattern(file.getName()).call();
+ git.commit().setMessage("push test").call();
+ Iterable<PushResult> results = null;
+ try {
+ results = git.push().setCredentialsProvider(cp).setRemote("origin").call();
+ } catch (GitAPIException e) {
+ if (permission.atLeast(AccessPermission.PUSH)) {
+ throw e;
+ } else {
+ // user does not have push permission
+ assertTrue(e.getMessage(), e.getMessage().contains("not permitted"));
+ close(git);
+ return;
+ }
+ }
+
+ for (PushResult result : results) {
+ RemoteRefUpdate ref = result.getRemoteUpdate("refs/heads/master");
+ Status status = ref.getStatus();
+ if (permission.atLeast(AccessPermission.PUSH)) {
+ assertTrue("User failed to push commit?! " + status.name(), Status.OK.equals(status));
+ } else {
+ assertTrue("User was able to push commit! " + status.name(), Status.REJECTED_OTHER_REASON.equals(status));
+ close(git);
+ // skip delete test
+ return;
+ }
+ }
+
+ // create a local branch and push the new branch back to the origin
+ git.branchCreate().setName("protectme").call();
+ RefSpec refSpec = new RefSpec("refs/heads/protectme:refs/heads/protectme");
+ results = git.push().setCredentialsProvider(cp).setRefSpecs(refSpec).setRemote("origin").call();
+ for (PushResult result : results) {
+ RemoteRefUpdate ref = result.getRemoteUpdate("refs/heads/protectme");
+ Status status = ref.getStatus();
+ if (Status.OK.equals(expectedCreate)) {
+ assertTrue("User failed to push creation?! " + status.name(), status.equals(expectedCreate));
+ } else {
+ assertTrue("User was able to push ref creation! " + status.name(), status.equals(expectedCreate));
+ close(git);
+ // skip delete test
+ return;
+ }
+ }
+
+ // delete the branch locally
+ git.branchDelete().setBranchNames("protectme").call();
+
+ // push a delete ref command
+ refSpec = new RefSpec(":refs/heads/protectme");
+ results = git.push().setCredentialsProvider(cp).setRefSpecs(refSpec).setRemote("origin").call();
+ for (PushResult result : results) {
+ RemoteRefUpdate ref = result.getRemoteUpdate("refs/heads/protectme");
+ Status status = ref.getStatus();
+ if (Status.OK.equals(expectedDelete)) {
+ assertTrue("User failed to push ref deletion?! " + status.name(), status.equals(Status.OK));
+ } else {
+ assertTrue("User was able to push ref deletion?! " + status.name(), status.equals(expectedDelete));
+ close(git);
+ // skip rewind test
+ return;
+ }
+ }
+
+ // rewind master by two commits
+ git.reset().setRef("HEAD~2").setMode(ResetType.HARD).call();
+
+ // commit a change on this detached HEAD
+ file = new File(local, "REWINDCHK");
+ os = new OutputStreamWriter(new FileOutputStream(file, true), Constants.CHARSET);
+ w = new BufferedWriter(os);
+ w.write("// " + new Date().toString() + "\n");
+ w.close();
+ git.add().addFilepattern(file.getName()).call();
+ RevCommit commit = git.commit().setMessage("rewind master and new commit").call();
+
+ // Reset master to our new commit now we our local branch tip is no longer
+ // upstream of the remote branch tip. It is an alternate tip of the branch.
+ JGitUtils.setBranchRef(git.getRepository(), "refs/heads/master", commit.getName());
+
+ // Try pushing our new tip to the origin.
+ // This requires the server to "rewind" it's master branch and update it
+ // to point to our alternate tip. This leaves the original master tip
+ // unreferenced.
+ results = git.push().setCredentialsProvider(cp).setRemote("origin").setForce(true).call();
+ for (PushResult result : results) {
+ RemoteRefUpdate ref = result.getRemoteUpdate("refs/heads/master");
+ Status status = ref.getStatus();
+ if (Status.OK.equals(expectedRewind)) {
+ assertTrue("User failed to rewind master?! " + status.name(), status.equals(expectedRewind));
+ } else {
+ assertTrue("User was able to rewind master?! " + status.name(), status.equals(expectedRewind));
+ }
+ }
+ close(git);
+
+ GitBlit.self().deleteUser(user.username);
+ }
+
private void close(Git git) {
// really close the repository
--
Gitblit v1.9.1