Skip to content

Pre-commit hook to protect ref paths from non-FF updates or deletes #7

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Feb 16, 2012
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 108 additions & 0 deletions groovy/protect-refs.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/*
* Copyright 2012 Philip L. McMahon.
*
* Derived from blockpush.groovy, copyright 2011 gitblit.com.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import com.gitblit.GitBlit
import com.gitblit.models.RepositoryModel
import com.gitblit.models.UserModel

import org.eclipse.jgit.transport.ReceiveCommand
import org.eclipse.jgit.transport.ReceiveCommand.Result
import org.slf4j.Logger

/**
* Sample Gitblit Pre-Receive Hook: protect-refs
*
* This script provides basic authorization of receive command types for a list
* of known ref patterns. Command types and unmatched ref patterns will be
* ignored, meaning this script has an "allow by default" policy.
*
* This script works best when a repository requires authentication on push, but
* can be used to enforce fast-forward commits or prohibit ref deletion by
* setting the *authorizedTeams* variable to an empty list and adding a ".+"
* entry to the *protectedRefs* list.
*
* The Pre-Receive hook is executed after an incoming push has been parsed,
* validated, and objects have been written but BEFORE the refs are updated.
* This is the appropriate point to block a push for some reason.
*
* This script is only executed when pushing to *Gitblit*, not to other Git
* tooling you may be using.
*
* If this script is specified in *groovy.preReceiveScripts* of gitblit.properties
* or web.xml then it will be executed by any repository when it receives a
* push. If you choose to share your script then you may have to consider
* tailoring control-flow based on repository access restrictions.
*
* Scripts may also be specified per-repository in the repository settings page.
* Shared scripts will be excluded from this list of available scripts.
*
* This script is dynamically reloaded and it is executed within it's own
* exception handler so it will not crash another script nor crash Gitblit.
*
* This script may reject one or more commands, but will never return false.
* Subsequent scripts, if any, will always be invoked.
*
* Bound Variables:
* gitblit Gitblit Server com.gitblit.GitBlit
* repository Gitblit Repository com.gitblit.models.RepositoryModel
* user Gitblit User com.gitblit.models.UserModel
* commands JGit commands Collection<org.eclipse.jgit.transport.ReceiveCommand>
* url Base url for Gitblit String
* logger Logger instance org.slf4j.Logger
*
*/

// map of protected command types to returned results type
// commands not included will skip authz check
def protectedCmds = [
UPDATE_NONFASTFORWARD: Result.REJECTED_NONFASTFORWARD,
DELETE: Result.REJECTED_NODELETE
]

// list of regex patterns for protected refs
def protectedRefs = [
"refs/heads/master",
"refs/tags/.+"
]

// teams which are authorized to perform protected commands on protected refs
def authorizedTeams = [ "admins" ]

for (ReceiveCommand command : commands) {
def updateType = command.type
def updatedRef = command.refName

// find first regex which matches updated ref, if any
def refPattern = protectedRefs.find { updatedRef.matches ~it }

// find rejection result for update type, if any
def result = protectedCmds[updateType.name()]

// command requires authz if ref is protected and has a mapped rejection result
if (refPattern && result) {

// verify user is a member of any authorized team
def team = authorizedTeams.find { user.isTeamMember it }
if (team) {
// don't adjust command result
logger.info "${user.username} authorized for ${updateType} of protected ref ${repository.name}:${updatedRef} (${command.oldId.name} -> ${command.newId.name})"
} else {
// mark command result as rejected
command.setResult(result, "${user.username} cannot ${updateType} protected ref ${repository.name}:${updatedRef} matching pattern ${refPattern}")
}
}
}
91 changes: 91 additions & 0 deletions tests/com/gitblit/tests/GroovyScriptTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,97 @@ public void testSendMail() throws Exception {
assertTrue(m.message.contains("BIT"));
}

@Test
public void testProtectRefsCreateBranch() throws Exception {
MockGitblit gitblit = new MockGitblit();
MockLogger logger = new MockLogger();
List<ReceiveCommand> commands = new ArrayList<ReceiveCommand>();
commands.add(new ReceiveCommand(ObjectId.zeroId(), ObjectId
.fromString("3fa7c46d11b11d61f1cbadc6888be5d0eae21969"), "refs/heads/master"));

RepositoryModel repository = new RepositoryModel("ex@mple.git", "", "admin", new Date());

test("protect-refs.groovy", gitblit, logger, commands, repository);
}

@Test
public void testProtectRefsCreateTag() throws Exception {
MockGitblit gitblit = new MockGitblit();
MockLogger logger = new MockLogger();
List<ReceiveCommand> commands = new ArrayList<ReceiveCommand>();
commands.add(new ReceiveCommand(ObjectId.zeroId(), ObjectId
.fromString("3fa7c46d11b11d61f1cbadc6888be5d0eae21969"), "refs/tags/v1.0"));

RepositoryModel repository = new RepositoryModel("ex@mple.git", "", "admin", new Date());

test("protect-refs.groovy", gitblit, logger, commands, repository);
assertEquals(0, logger.messages.size());
}

@Test
public void testProtectRefsFastForward() throws Exception {
MockGitblit gitblit = new MockGitblit();
MockLogger logger = new MockLogger();
List<ReceiveCommand> commands = new ArrayList<ReceiveCommand>();
commands.add(new ReceiveCommand(ObjectId
.fromString("c18877690322dfc6ae3e37bb7f7085a24e94e887"), ObjectId
.fromString("3fa7c46d11b11d61f1cbadc6888be5d0eae21969"), "refs/heads/master"));

RepositoryModel repository = new RepositoryModel("ex@mple.git", "", "admin", new Date());

test("protect-refs.groovy", gitblit, logger, commands, repository);
assertEquals(0, logger.messages.size());
}

@Test
public void testProtectRefsDeleteMasterBranch() throws Exception {
MockGitblit gitblit = new MockGitblit();
MockLogger logger = new MockLogger();
List<ReceiveCommand> commands = new ArrayList<ReceiveCommand>();
ReceiveCommand command = new ReceiveCommand(ObjectId
.fromString("3fa7c46d11b11d61f1cbadc6888be5d0eae21969"), ObjectId.zeroId(),
"refs/heads/master");
commands.add(command);

RepositoryModel repository = new RepositoryModel("ex@mple.git", "", "admin", new Date());

test("protect-refs.groovy", gitblit, logger, commands, repository);
assertEquals(ReceiveCommand.Result.REJECTED_NODELETE, command.getResult());
assertEquals(0, logger.messages.size());
}

@Test
public void testProtectRefsDeleteOtherBranch() throws Exception {
MockGitblit gitblit = new MockGitblit();
MockLogger logger = new MockLogger();
List<ReceiveCommand> commands = new ArrayList<ReceiveCommand>();
commands.add(new ReceiveCommand(ObjectId
.fromString("3fa7c46d11b11d61f1cbadc6888be5d0eae21969"), ObjectId.zeroId(),
"refs/heads/other"));

RepositoryModel repository = new RepositoryModel("ex@mple.git", "", "admin", new Date());

test("protect-refs.groovy", gitblit, logger, commands, repository);
assertEquals(0, logger.messages.size());
}

@Test
public void testProtectRefsDeleteTag() throws Exception {
MockGitblit gitblit = new MockGitblit();
MockLogger logger = new MockLogger();
List<ReceiveCommand> commands = new ArrayList<ReceiveCommand>();
ReceiveCommand command = new ReceiveCommand(ObjectId
.fromString("3fa7c46d11b11d61f1cbadc6888be5d0eae21969"), ObjectId.zeroId(),
"refs/tags/v1.0");
commands.add(command);

RepositoryModel repository = new RepositoryModel("ex@mple.git", "", "admin", new Date());

test("protect-refs.groovy", gitblit, logger, commands, repository);
assertEquals(ReceiveCommand.Result.REJECTED_NODELETE, command.getResult());
assertEquals(0, logger.messages.size());
}

@Test
public void testBlockPush() throws Exception {
MockGitblit gitblit = new MockGitblit();
Expand Down