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,
|
safe => 0,
|
||||||
rebuild => 0,
|
rebuild => 0,
|
||||||
},
|
},
|
||||||
|
test_receive => {
|
||||||
|
type => "internal",
|
||||||
|
default => 0,
|
||||||
|
description => "running in receive test mode",
|
||||||
|
safe => 0,
|
||||||
|
rebuild => 0,
|
||||||
|
},
|
||||||
getctime => {
|
getctime => {
|
||||||
type => "internal",
|
type => "internal",
|
||||||
default => 0,
|
default => 0,
|
||||||
|
@ -1575,6 +1582,10 @@ sub rcs_getctime ($) { #{{{
|
||||||
$hooks{rcs}{rcs_getctime}{call}->(@_);
|
$hooks{rcs}{rcs_getctime}{call}->(@_);
|
||||||
} #}}}
|
} #}}}
|
||||||
|
|
||||||
|
sub rcs_test_receive ($) { #{{{
|
||||||
|
$hooks{rcs}{rcs_test_receive}{call}->(@_);
|
||||||
|
} #}}}
|
||||||
|
|
||||||
sub globlist_to_pagespec ($) { #{{{
|
sub globlist_to_pagespec ($) { #{{{
|
||||||
my @globlist=split(' ', shift);
|
my @globlist=split(' ', shift);
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,7 @@ sub import { #{{{
|
||||||
hook(type => "rcs", id => "rcs_recentchanges", call => \&rcs_recentchanges);
|
hook(type => "rcs", id => "rcs_recentchanges", call => \&rcs_recentchanges);
|
||||||
hook(type => "rcs", id => "rcs_diff", call => \&rcs_diff);
|
hook(type => "rcs", id => "rcs_diff", call => \&rcs_diff);
|
||||||
hook(type => "rcs", id => "rcs_getctime", call => \&rcs_getctime);
|
hook(type => "rcs", id => "rcs_getctime", call => \&rcs_getctime);
|
||||||
|
hook(type => "rcs", id => "rcs_test_receive", call => \&rcs_test_receive);
|
||||||
} #}}}
|
} #}}}
|
||||||
|
|
||||||
sub checkconfig () { #{{{
|
sub checkconfig () { #{{{
|
||||||
|
@ -32,12 +33,21 @@ sub checkconfig () { #{{{
|
||||||
if (! defined $config{gitmaster_branch}) {
|
if (! defined $config{gitmaster_branch}) {
|
||||||
$config{gitmaster_branch}="master";
|
$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}}, {
|
push @{$config{wrappers}}, {
|
||||||
wrapper => $config{git_wrapper},
|
wrapper => $config{git_wrapper},
|
||||||
wrappermode => (defined $config{git_wrappermode} ? $config{git_wrappermode} : "06755"),
|
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 () { #{{{
|
sub getsetup () { #{{{
|
||||||
|
@ -60,6 +70,20 @@ sub getsetup () { #{{{
|
||||||
safe => 0,
|
safe => 0,
|
||||||
rebuild => 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 => {
|
historyurl => {
|
||||||
type => "string",
|
type => "string",
|
||||||
example => "http://git.example.com/gitweb.cgi?p=wiki.git;a=history;f=[[file]]",
|
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),
|
'file' => decode("utf8", $file),
|
||||||
'sha1_from' => $sha1_from[0],
|
'sha1_from' => $sha1_from[0],
|
||||||
'sha1_to' => $sha1_to,
|
'sha1_to' => $sha1_to,
|
||||||
|
'mode_from' => $mode_from[0],
|
||||||
|
'mode_to' => $mode_to,
|
||||||
|
'status' => $status,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
next;
|
next;
|
||||||
|
@ -331,14 +358,12 @@ sub parse_diff_tree ($@) { #{{{
|
||||||
} #}}}
|
} #}}}
|
||||||
|
|
||||||
sub git_commit_info ($;$) { #{{{
|
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.
|
# starting from the given sha1sum.
|
||||||
|
|
||||||
my ($sha1, $num) = @_;
|
my ($sha1, $num) = @_;
|
||||||
|
|
||||||
$num ||= 1;
|
my @raw_lines = run_or_die('git', 'log',
|
||||||
|
(defined $num ? "--max-count=$num" : ""),
|
||||||
my @raw_lines = run_or_die('git', 'log', "--max-count=$num",
|
|
||||||
'--pretty=raw', '--raw', '--abbrev=40', '--always', '-c',
|
'--pretty=raw', '--raw', '--abbrev=40', '--always', '-c',
|
||||||
'-r', $sha1, '--', '.');
|
'-r', $sha1, '--', '.');
|
||||||
my ($prefix) = run_or_die('git', 'rev-parse', '--show-prefix');
|
my ($prefix) = run_or_die('git', 'rev-parse', '--show-prefix');
|
||||||
|
@ -355,7 +380,6 @@ sub git_commit_info ($;$) { #{{{
|
||||||
|
|
||||||
sub git_sha1 (;$) { #{{{
|
sub git_sha1 (;$) { #{{{
|
||||||
# Return head sha1sum (of given file).
|
# Return head sha1sum (of given file).
|
||||||
|
|
||||||
my $file = shift || q{--};
|
my $file = shift || q{--};
|
||||||
|
|
||||||
# Ignore error since a non-existing file might be given.
|
# Ignore error since a non-existing file might be given.
|
||||||
|
@ -378,7 +402,6 @@ sub rcs_update () { #{{{
|
||||||
sub rcs_prepedit ($) { #{{{
|
sub rcs_prepedit ($) { #{{{
|
||||||
# Return the commit sha1sum of the file when editing begins.
|
# Return the commit sha1sum of the file when editing begins.
|
||||||
# This will be later used in rcs_commit if a merge is required.
|
# This will be later used in rcs_commit if a merge is required.
|
||||||
|
|
||||||
my ($file) = @_;
|
my ($file) = @_;
|
||||||
|
|
||||||
return git_sha1($file);
|
return git_sha1($file);
|
||||||
|
@ -475,7 +498,7 @@ sub rcs_recentchanges ($) { #{{{
|
||||||
error($@) if $@;
|
error($@) if $@;
|
||||||
|
|
||||||
my @rets;
|
my @rets;
|
||||||
foreach my $ci (git_commit_info('HEAD', $num)) {
|
foreach my $ci (git_commit_info('HEAD', $num || 1)) {
|
||||||
# Skip redundant commits.
|
# Skip redundant commits.
|
||||||
next if ($ci->{'comment'} && @{$ci->{'comment'}}[0] eq $dummy_commit_msg);
|
next if ($ci->{'comment'} && @{$ci->{'comment'}}[0] eq $dummy_commit_msg);
|
||||||
|
|
||||||
|
@ -558,11 +581,83 @@ sub rcs_getctime ($) { #{{{
|
||||||
$file =~ s/^\Q$config{srcdir}\E\/?//;
|
$file =~ s/^\Q$config{srcdir}\E\/?//;
|
||||||
|
|
||||||
my $sha1 = git_sha1($file);
|
my $sha1 = git_sha1($file);
|
||||||
my $ci = git_commit_info($sha1);
|
my $ci = git_commit_info($sha1, 1);
|
||||||
my $ctime = $ci->{'author_epoch'};
|
my $ctime = $ci->{'author_epoch'};
|
||||||
debug("ctime for '$file': ". localtime($ctime));
|
debug("ctime for '$file': ". localtime($ctime));
|
||||||
|
|
||||||
return $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
|
1
|
||||||
|
|
|
@ -820,6 +820,15 @@ it up in the history.
|
||||||
|
|
||||||
It's ok if this is not implemented, and throws an error.
|
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
|
### PageSpec plugins
|
||||||
|
|
||||||
It's also possible to write plugins that add new functions to
|
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-commit in the remote repository
|
||||||
* git-push, pushes the commit to the master repo on the server
|
* 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
|
* the master repo's post-update hook notices this update, and runs ikiwiki
|
||||||
* ikiwiki notices the modifies page source, and compiles it
|
* 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
|
group. Take care that ikiwiki uses a umask that does not cause files in
|
||||||
the srcdir to become group writable. (umask 022 will work.)
|
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
|
## Optionally using a local wiki to preview changes
|
||||||
|
|
||||||
When working on the "working clones" to add content to your wiki,
|
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()) {
|
elsif ($config{post_commit} && ! commit_hook_enabled()) {
|
||||||
# do nothing
|
# do nothing
|
||||||
}
|
}
|
||||||
|
elsif ($config{test_receive}) {
|
||||||
|
rcs_test_receive();
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
if ($config{rebuild}) {
|
if ($config{rebuild}) {
|
||||||
debug(gettext("rebuilding wiki.."));
|
debug(gettext("rebuilding wiki.."));
|
||||||
|
|
Loading…
Reference in New Issue