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