From 76fe7432fbc2a445092d54309d8298e9e6f81906 Mon Sep 17 00:00:00 2001 From: Simon McVittie Date: Sun, 1 Oct 2017 13:17:53 +0100 Subject: t/git-untrusted.t: New test case for untrusted pushes This also exercises the typical centralized git repository workflow, where changes flow from a non-bare clone (for example on a laptop) to a centralized bare repository, then from the centralized bare repository to a non-bare clone that is ikiwiki's srcdir. Signed-off-by: Simon McVittie --- t/git-untrusted.t | 211 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 211 insertions(+) create mode 100755 t/git-untrusted.t (limited to 't') diff --git a/t/git-untrusted.t b/t/git-untrusted.t new file mode 100755 index 000000000..285a242e8 --- /dev/null +++ b/t/git-untrusted.t @@ -0,0 +1,211 @@ +#!/usr/bin/perl +use warnings; +use strict; + +use File::Temp; +use Test::More; + +BEGIN { + my $git = `which git`; + chomp $git; + plan(skip_all => 'git not available') unless -x $git; + + plan(skip_all => "IPC::Run not available") + unless eval q{ + use IPC::Run qw(run start); + 1; + }; + + use_ok('IkiWiki'); + use_ok('YAML::XS'); +} + +# We check for English error messages +$ENV{LC_ALL} = 'C'; + +use Cwd qw(getcwd); +use Errno qw(ENOENT); + +my $installed = $ENV{INSTALLED_TESTS}; +my $tmp = File::Temp->newdir(CLEANUP => 0); + +my @command; +if ($installed) { + @command = qw(ikiwiki); +} +else { + ok(! system("make -s ikiwiki.out")); + @command = ("perl", "-I".getcwd."/blib/lib", './ikiwiki.out', + '--underlaydir='.getcwd.'/underlays/basewiki', + '--set', 'underlaydirbase='.getcwd.'/underlays', + '--templatedir='.getcwd.'/templates'); +} + +sub write_old_file { + my $name = shift; + my $dir = shift; + my $content = shift; + writefile($name, $dir, $content); + ok(utime(333333333, 333333333, "$dir/$name")); +} + +sub write_setup_file { + my %params = @_; + my %setup = ( + wikiname => 'this is the name of my wiki', + srcdir => "$tmp/srcdir", + destdir => "$tmp/out", + url => 'http://example.com', + cgiurl => 'http://example.com/cgi-bin/ikiwiki.cgi', + cgi_wrapper => "$tmp/ikiwiki.cgi", + cgi_wrappermode => '0755', + add_plugins => [qw(anonok attachment lockedit recentchanges)], + disable_plugins => [qw(emailauth openid passwordauth)], + anonok_pagespec => 'writable/*', + locked_pages => '!writable/*', + rcs => 'git', + git_wrapper => "$tmp/repo.git/hooks/post-update", + git_wrappermode => '0755', + gitorigin_branch => 'test-repo', + gitmaster_branch => 'master', + untrusted_committers => [$params{trustme} ? 'nobody' : scalar getpwuid($<)], + git_test_receive_wrapper => "$tmp/repo.git/hooks/pre-receive", + ENV => { LC_ALL => 'C' }, + verbose => 1, + ); + unless ($installed) { + $setup{ENV}{'PERL5LIB'} = getcwd.'/blib/lib'; + } + writefile("test.setup", "$tmp", + "# IkiWiki::Setup::Yaml - YAML formatted setup file\n" . + Dump(\%setup)); +} + +sub thoroughly_rebuild { + ok(unlink("$tmp/ikiwiki.cgi") || $!{ENOENT}); + ok(unlink("$tmp/repo.git/hooks/post-update") || $!{ENOENT}); + ok(unlink("$tmp/repo.git/hooks/pre-receive") || $!{ENOENT}); + ok(! system(@command, qw(--setup), "$tmp/test.setup", qw(--rebuild --wrappers))); +} + +sub try_run_git { + my $args = shift; + my %params = @_; + my $git_dir = $params{chdir} || "$tmp/srcdir"; + my ($in, $out, $err); + my @redirections = ('>', \$in); + if ($params{capture_stdout}) { + push @redirections, '>', \$out; + } + else { + push @redirections, '>', \*STDERR; + } + push @redirections, '2>', \$err if $params{capture_stderr}; + my $h = start(['git', @$args], @redirections, init => sub { + chdir $git_dir or die $!; + my $name = 'The IkiWiki Tests'; + my $email = 'nobody@ikiwiki-tests.invalid'; + if ($args->[0] eq 'commit') { + $ENV{GIT_AUTHOR_NAME} = $ENV{GIT_COMMITTER_NAME} = $name; + $ENV{GIT_AUTHOR_EMAIL} = $ENV{GIT_COMMITTER_EMAIL} = $email; + } + }); + while ($h->pump) {}; + $h->finish; + return $h, $out, $err; +} + +sub run_git { + my (undef, $filename, $line) = caller; + my $args = shift; + my %params = @_; + my $git_dir = $params{chdir} || "$tmp/srcdir"; + my $desc = $params{desc} || join(' ', 'git', @$args); + my ($h, $out, $err) = try_run_git($args, capture_stdout => 1, %params); + is($h->full_result(0), 0, "'$desc' in $git_dir at $filename:$line"); + return $out; +} + +sub test { + my ($h, $out, $err); + my $sha1; + + write_old_file('.gitignore', "$tmp/srcdir", "/.ikiwiki/\n"); + write_old_file('writable/one.mdwn', "$tmp/srcdir", 'This is the first test page'); + write_old_file('writable/two.bin', "$tmp/srcdir", 'An attachment'); + + unless ($installed) { + ok(! system(qw(cp -pRL doc/wikiicons), "$tmp/srcdir/")); + ok(! system(qw(cp -pRL doc/recentchanges.mdwn), "$tmp/srcdir/")); + } + + ok(mkdir "$tmp/repo.git"); + run_git(['init', '--bare'], chdir => "$tmp/repo.git"); + + run_git(['init']); + run_git(['add', '.']); + run_git(['commit', '-m', 'Initial commit']); + my $initial_sha1 = run_git(['rev-list', '--max-count=1', 'HEAD']); + run_git(['remote', 'add', 'test-repo', "$tmp/repo.git"]); + run_git(['push', 'test-repo', 'master:master']); + run_git(['branch', '--set-upstream-to=test-repo/master']); + + run_git(['clone', '-otest-repo', "$tmp/repo.git", "$tmp/clone"], chdir => $tmp); + writefile('writable/untrusted_user_says_hi.mdwn', "$tmp/clone", 'Hi!'); + run_git(['add', 'writable'], chdir => "$tmp/clone"); + run_git(['commit', '-m', 'Hi'], chdir => "$tmp/clone"); + my $allowed_sha1 = run_git(['rev-list', '--max-count=1', 'HEAD'], + chdir => "$tmp/clone"); + + diag 'Pushing unrestricted change as untrusted user'; + write_setup_file(trustme => 0); + thoroughly_rebuild(); + + $out = run_git([ + 'push', 'test-repo', 'master:master', + ], chdir => "$tmp/clone"); + $sha1 = run_git(['rev-list', '--max-count=1', 'HEAD'], chdir => "$tmp/repo.git"); + is($sha1, $allowed_sha1, 'allowed commit was pushed'); + $sha1 = run_git(['rev-list', '--max-count=1', 'HEAD']); + is($sha1, $allowed_sha1, 'allowed commit was pushed'); + ok(-e "$tmp/srcdir/writable/untrusted_user_says_hi.mdwn"); + ok(-e "$tmp/out/writable/untrusted_user_says_hi/index.html"); + + diag 'Pushing restricted change as untrusted user'; + writefile('staff_only.mdwn', "$tmp/clone", 'Hi!'); + run_git(['add', 'staff_only.mdwn'], chdir => "$tmp/clone"); + run_git(['commit', '-m', 'Hi'], chdir => "$tmp/clone"); + my $proposed_sha1 = run_git(['rev-list', '--max-count=1', 'HEAD'], + chdir => "$tmp/clone"); + + ($h, $out, $err) = try_run_git([ + 'push', 'test-repo', 'master:master', + ], chdir => "$tmp/clone", capture_stdout => 1, capture_stderr => 1); + isnt($h->full_result(0), 0); + is($out, ''); + like($err, qr{remote: <.*>staff only is locked and cannot be edited}); + my $sha1 = run_git(['rev-list', '--max-count=1', 'HEAD'], chdir => "$tmp/repo.git"); + is($sha1, $allowed_sha1, 'proposed commit was not pushed'); + $sha1 = run_git(['rev-list', '--max-count=1', 'HEAD']); + is($sha1, $allowed_sha1, 'proposed commit was not pushed'); + ok(! -e "$tmp/srcdir/staff_only.mdwn"); + ok(! -e "$tmp/out/staff_only/index.html"); + + diag 'Pushing restricted change as trusted user'; + write_setup_file(trustme => 1); + thoroughly_rebuild(); + + $out = run_git([ + 'push', 'test-repo', 'master:master', + ], chdir => "$tmp/clone"); + $sha1 = run_git(['rev-list', '--max-count=1', 'HEAD'], chdir => "$tmp/repo.git"); + is($sha1, $proposed_sha1, 'proposed commit was pushed'); + $sha1 = run_git(['rev-list', '--max-count=1', 'HEAD']); + is($sha1, $proposed_sha1, 'proposed commit was pushed'); + ok(-e "$tmp/srcdir/staff_only.mdwn"); + ok(-e "$tmp/out/staff_only/index.html"); +} + +test(); + +done_testing(); -- cgit v1.2.3