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
parent
9fc126ada6
commit
094af3d113
11
IkiWiki.pm
11
IkiWiki.pm
|
@ -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);
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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.."));
|
||||
|
|
Loading…
Reference in New Issue