initial support for git repos with untrusted committers

Still need to wire up the calls to check_* , but it's cold out here and my
hands are going numb, so enough for now.
master
Joey Hess 2008-10-22 20:52:34 -04:00
parent 9fc126ada6
commit 094af3d113
6 changed files with 158 additions and 10 deletions

View File

@ -382,6 +382,13 @@ sub getsetup () { #{{{
safe => 0,
rebuild => 0,
},
test_receive => {
type => "internal",
default => 0,
description => "running in receive test mode",
safe => 0,
rebuild => 0,
},
getctime => {
type => "internal",
default => 0,
@ -1575,6 +1582,10 @@ sub rcs_getctime ($) { #{{{
$hooks{rcs}{rcs_getctime}{call}->(@_);
} #}}}
sub rcs_test_receive ($) { #{{{
$hooks{rcs}{rcs_test_receive}{call}->(@_);
} #}}}
sub globlist_to_pagespec ($) { #{{{
my @globlist=split(' ', shift);

View File

@ -23,6 +23,7 @@ sub import { #{{{
hook(type => "rcs", id => "rcs_recentchanges", call => \&rcs_recentchanges);
hook(type => "rcs", id => "rcs_diff", call => \&rcs_diff);
hook(type => "rcs", id => "rcs_getctime", call => \&rcs_getctime);
hook(type => "rcs", id => "rcs_test_receive", call => \&rcs_test_receive);
} #}}}
sub checkconfig () { #{{{
@ -32,12 +33,21 @@ sub checkconfig () { #{{{
if (! defined $config{gitmaster_branch}) {
$config{gitmaster_branch}="master";
}
if (defined $config{git_wrapper} && length $config{git_wrapper}) {
if (defined $config{git_wrapper} &&
length $config{git_wrapper}) {
push @{$config{wrappers}}, {
wrapper => $config{git_wrapper},
wrappermode => (defined $config{git_wrappermode} ? $config{git_wrappermode} : "06755"),
};
}
if (defined $config{git_test_receive_wrapper} &&
length $config{git_test_receive_wrapper}) {
push @{$config{wrappers}}, {
test_receive => 1,
wrapper => $config{git_test_receive_wrapper},
wrappermode => "0755",
};
}
} #}}}
sub getsetup () { #{{{
@ -60,6 +70,20 @@ sub getsetup () { #{{{
safe => 0,
rebuild => 0,
},
git_test_receive_wrapper => {
type => "string",
example => "/git/wiki.git/hooks/pre-receive",
description => "git pre-receive hook to generate",
safe => 0, # file
rebuild => 0,
},
git_untrusted_committers => {
type => "string",
example => [],
description => "unix users whose commits should be checked by the pre-receive hook",
safe => 0,
rebuild => 0,
},
historyurl => {
type => "string",
example => "http://git.example.com/gitweb.cgi?p=wiki.git;a=history;f=[[file]]",
@ -320,6 +344,9 @@ sub parse_diff_tree ($@) { #{{{
'file' => decode("utf8", $file),
'sha1_from' => $sha1_from[0],
'sha1_to' => $sha1_to,
'mode_from' => $mode_from[0],
'mode_to' => $mode_to,
'status' => $status,
};
}
next;
@ -331,14 +358,12 @@ sub parse_diff_tree ($@) { #{{{
} #}}}
sub git_commit_info ($;$) { #{{{
# Return an array of commit info hashes of num commits (default: 1)
# Return an array of commit info hashes of num commits
# starting from the given sha1sum.
my ($sha1, $num) = @_;
$num ||= 1;
my @raw_lines = run_or_die('git', 'log', "--max-count=$num",
my @raw_lines = run_or_die('git', 'log',
(defined $num ? "--max-count=$num" : ""),
'--pretty=raw', '--raw', '--abbrev=40', '--always', '-c',
'-r', $sha1, '--', '.');
my ($prefix) = run_or_die('git', 'rev-parse', '--show-prefix');
@ -355,7 +380,6 @@ sub git_commit_info ($;$) { #{{{
sub git_sha1 (;$) { #{{{
# Return head sha1sum (of given file).
my $file = shift || q{--};
# Ignore error since a non-existing file might be given.
@ -378,7 +402,6 @@ sub rcs_update () { #{{{
sub rcs_prepedit ($) { #{{{
# Return the commit sha1sum of the file when editing begins.
# This will be later used in rcs_commit if a merge is required.
my ($file) = @_;
return git_sha1($file);
@ -475,7 +498,7 @@ sub rcs_recentchanges ($) { #{{{
error($@) if $@;
my @rets;
foreach my $ci (git_commit_info('HEAD', $num)) {
foreach my $ci (git_commit_info('HEAD', $num || 1)) {
# Skip redundant commits.
next if ($ci->{'comment'} && @{$ci->{'comment'}}[0] eq $dummy_commit_msg);
@ -558,11 +581,83 @@ sub rcs_getctime ($) { #{{{
$file =~ s/^\Q$config{srcdir}\E\/?//;
my $sha1 = git_sha1($file);
my $ci = git_commit_info($sha1);
my $ci = git_commit_info($sha1, 1);
my $ctime = $ci->{'author_epoch'};
debug("ctime for '$file': ". localtime($ctime));
return $ctime;
} #}}}
sub rcs_test_receive () { #{{{
# quick success if the user is trusted
my $committer=(getpwuid($<))[0];
if (! defined $committer) {
error("cannot determine username for $<");
}
exit 0 if ! ref $config{git_untrusted_committers} ||
! grep { $_ eq $committer } @{$config{git_untrusted_committers}};
# The wiki may not be the only thing in the git repo.
# Determine if it is in a subdirectory by examining the srcdir,
# and its parents, looking for the .git directory.
my $subdir="";
my $dir=$config{srcdir};
while (! -d "$dir/.git") {
$subdir=IkiWiki::basename($dir)."/".$subdir;
$dir=IkiWiki::dirname($dir);
if (! length $dir) {
error("cannot determine root of git repo");
}
}
my @errors;
while (<>) {
chomp;
my ($oldrev, $newrev, $refname) = split(' ', $_, 3);
# only allow changes to gitmaster_branch
if ($refname !~ /^refs\/heads\/\Q$config{gitmaster_branch}\E$/) {
push @errors, sprintf(gettext("you are not allowed to change %s"), $refname);
}
foreach my $ci (git_commit_info($oldrev."..".$newrev)) {
foreach my $detail (@{ $ci->{'details'} }) {
my $file = $detail->{'file'};
# check that all changed files are in the subdir
if (length $subdir &&
! ($file =~ s/^\Q$subdir\E//)) {
push @errors, sprintf(gettext("you are not allowed to change %s"), $file);
next;
}
if ($detail->{'mode_from'} ne $detail->{'mode_to'}) {
push @errors, gettext("you are not allowed to change file modes");
}
if ($detail->{'status'} =~ /^D+\d*/) {
# TODO check_canremove
}
elsif ($detail->{'status'} !~ /^[MA]+\d*$/) {
push @errors, "unknown status ".$detail->{'status'};
}
else {
# TODO check_canedit
# TODO check_canattach
}
}
}
}
if (@errors) {
# TODO clean up objects from failed push
print STDERR "$_\n" foreach @errors;
exit 1;
}
else {
exit 0;
}
} #}}}
1

View File

@ -820,6 +820,15 @@ it up in the history.
It's ok if this is not implemented, and throws an error.
#### `rcs_test_receive()`
This is used to test if changes pushed into the RCS should be accepted.
Ikiwiki will be running as a pre-receive hook (or equivilant) and should
examine the incoming changes, decide if they are allowed, and communicate
that to the RCS.
This is optional, and doesn't make sense for all RCSs.
### PageSpec plugins
It's also possible to write plugins that add new functions to

View File

@ -280,6 +280,9 @@ Here is a how a commit from a remote repository works:
* git-commit in the remote repository
* git-push, pushes the commit to the master repo on the server
* (Optionally, the master repo's pre-receive hook runs, and checks that the
update only modifies files that the pushing user is allowed to update.
If not, it aborts the receive.)
* the master repo's post-update hook notices this update, and runs ikiwiki
* ikiwiki notices the modifies page source, and compiles it

View File

@ -100,6 +100,33 @@ repository, should only be writable by the wiki's admin, and *not* by the
group. Take care that ikiwiki uses a umask that does not cause files in
the srcdir to become group writable. (umask 022 will work.)
## git repository with untrusted committers
By default, anyone who can commit to the git repository can modify any file
on the wiki however they like. A `pre-receive` hook can be set up to limit
incoming commits from untrusted users. Then the same limits that are placed
on edits via the web will be in effect for commits to git for the users.
They will not be allowed to edit locked pages, they will only be able to
delete pages that the [[plugins/remove]] configuration allows them to
remove, and they will only be allowed to add non-page attachments that the
[[plugins/attachment]] configuration allows.
To enable this, you need to set up the git repository to have multiple
committers. Trusted committers, including the user that ikiwiki runs as,
will not have their commits checked by the `pre-receive` hook. Untrusted
committers will have their commits checked. The configuration settings to
enable are `git_test_receive_wrapper`, which enables generation of a
`pre-receive` hook, and `git_untrusted_committers`, which is a list of
usernames of the untrusted committers.
Note that when the `pre-receive` hook is checking incoming changes, it
ignores the git authorship information, and uses the username of the unix
user who made the commit. Then tests including the `locked_pages` [[PageSpec]]
are checked to see if that user can edit the pages in the commit.
You can even set up an anonymous user, to allow anyone to push
changes in via git rather than using the web interface.
## Optionally using a local wiki to preview changes
When working on the "working clones" to add content to your wiki,

View File

@ -183,6 +183,9 @@ sub main () { #{{{
elsif ($config{post_commit} && ! commit_hook_enabled()) {
# do nothing
}
elsif ($config{test_receive}) {
rcs_test_receive();
}
else {
if ($config{rebuild}) {
debug(gettext("rebuilding wiki.."));