mirror of
https://github.com/webmin/webmin.git
synced 2026-11-02 00:20:50 +00:00
Update tests
This commit is contained in:
@@ -1650,6 +1650,72 @@ subtest 'save_group.cgi name validation' => sub {
|
||||
ok(!$g, 'group with colon-bearing description not created');
|
||||
};
|
||||
|
||||
# Group module limits must reject requests before any membership is changed.
|
||||
subtest 'save_group.cgi module limits' => sub {
|
||||
_reset_fixture();
|
||||
create_user({ name => 'admin', pass => 'x', modules => ['acl', 'proc'] });
|
||||
create_group({ name => 'parent', members => [], modules => ['proc'] });
|
||||
_seed_user_acl('admin', { mode => 2, mods => 'proc', groups => 1,
|
||||
gassign => '*' });
|
||||
|
||||
my $r = run_cgi('save_group.cgi', {
|
||||
name => 'child', desc => 'd', group => 'parent', mod => ['proc', 'cron'] });
|
||||
like($r->{out}, qr/You cannot grant access to module 'cron'/,
|
||||
'explicit module limit rejects an unauthorized grant');
|
||||
_clear_caches();
|
||||
ok(!get_group('child'), 'rejected group is not created');
|
||||
is_deeply(get_group('parent')->{members}, [],
|
||||
'rejected save does not change parent membership');
|
||||
|
||||
$r = run_cgi('save_group.cgi', {
|
||||
name => 'child', desc => 'd', group => 'parent', mod => 'proc' });
|
||||
is($r->{status}, 0, 'allowed group creation exits successfully');
|
||||
_clear_caches();
|
||||
is_deeply(get_group('child')->{modules}, ['proc'], 'allowed module is saved');
|
||||
is_deeply(get_group('parent')->{members}, ['@child'],
|
||||
'successful save updates parent membership');
|
||||
|
||||
$r = run_cgi('save_group.cgi', {
|
||||
old => 'child', name => 'child', desc => 'changed', group => '', mod => 'cron' });
|
||||
like($r->{out}, qr/You cannot grant access to module 'cron'/,
|
||||
'editing an existing group cannot grant an unauthorized module');
|
||||
_clear_caches();
|
||||
is_deeply(get_group('parent')->{members}, ['@child'],
|
||||
'rejected edit does not detach the existing group');
|
||||
|
||||
my $payload = '<script>alert(1)</script>';
|
||||
$r = run_cgi('save_group.cgi', { name => 'badmod', mod => $payload });
|
||||
like($r->{out}, qr/<script>/, 'rejected module name is escaped');
|
||||
unlike($r->{out}, qr/\Q$payload\E/, 'module error cannot inject HTML');
|
||||
|
||||
# Mode 1 uses the effective user's modules, including mapped logins.
|
||||
_seed_user_acl('admin', { mode => 1, groups => 1 });
|
||||
$r = run_cgi('save_group.cgi', { name => 'ownmods', mod => 'proc' },
|
||||
user => 'mapped-user', env => { BASE_REMOTE_USER => 'admin' });
|
||||
_clear_caches();
|
||||
is_deeply(get_group('ownmods')->{modules}, ['proc'],
|
||||
'mapped login uses the base user module list');
|
||||
$r = run_cgi('save_group.cgi', { name => 'denied', mod => 'cron' });
|
||||
like($r->{out}, qr/You cannot grant access to module 'cron'/,
|
||||
'mode 1 rejects modules the base user does not hold');
|
||||
|
||||
delete_user('admin');
|
||||
_seed_user_acl('admin', { mode => 1, groups => 1 });
|
||||
$r = run_cgi('save_group.cgi', { name => 'missing', mod => 'proc' });
|
||||
like($r->{out}, qr/\Q$text{'edit_egone'}\E/, 'missing user returns a normal error');
|
||||
unlike($r->{err}, qr/undefined value|compilation errors/,
|
||||
'missing user does not cause a Perl exception');
|
||||
_clear_caches();
|
||||
ok(!get_group('missing'), 'missing user cannot create a group');
|
||||
|
||||
# Unrestricted mode does not require the mode-1 user record.
|
||||
_seed_user_acl('admin', { mode => 0, groups => 1 });
|
||||
$r = run_cgi('save_group.cgi', { name => 'unrestricted', mod => 'cron' });
|
||||
_clear_caches();
|
||||
is_deeply(get_group('unrestricted')->{modules}, ['cron'],
|
||||
'unrestricted module grants remain supported');
|
||||
};
|
||||
|
||||
subtest 'save_user.cgi self-lockout guard' => sub {
|
||||
# Editing yourself out of the acl module is refused — otherwise an
|
||||
# admin could lock themselves out of Webmin entirely.
|
||||
|
||||
99
t/cert-proc-escaping.t
Normal file
99
t/cert-proc-escaping.t
Normal file
@@ -0,0 +1,99 @@
|
||||
#!/usr/bin/perl
|
||||
# Exercise CGI escaping without issuing certificates or signaling processes.
|
||||
use strict;
|
||||
use warnings;
|
||||
no warnings qw(once redefine uninitialized);
|
||||
use Test::More;
|
||||
use FindBin;
|
||||
|
||||
my $root = "$FindBin::Bin/..";
|
||||
require "$root/web-lib-funcs.pl";
|
||||
require "$root/ui-lib.pl";
|
||||
$INC{'./acl-lib.pl'} = $INC{'./proc-lib.pl'} = 1;
|
||||
*main::ReadParse = *main::switch_acl_uid = *main::webmin_log = sub { };
|
||||
*main::error_setup = *main::get_miniserv_config = sub { };
|
||||
*main::error = sub { die $_[0]."\n" };
|
||||
*main::get_module_variable = sub { return \%main::text };
|
||||
*main::ui_print_unbuffered_header = sub { };
|
||||
*main::load_theme_library = sub { };
|
||||
*main::get_module_name = sub { 'proc' };
|
||||
*main::get_module_info = sub { return () };
|
||||
*main::get_webprefix = sub { '' };
|
||||
for my $file ('lang/en', 'acl/lang/en', 'proc/lang/en') {
|
||||
open(my $fh, '<', "$root/$file") or die $!;
|
||||
while (<$fh>) {
|
||||
$main::text{$1} = $2 if /^(\S+)=(.*)$/;
|
||||
}
|
||||
}
|
||||
|
||||
# render_cgi(file, inputs) captures the real CGI and UI helpers.
|
||||
sub render_cgi
|
||||
{
|
||||
my ($file, $inputs) = @_;
|
||||
local %main::in = %$inputs;
|
||||
my $html = '';
|
||||
open(my $out, '>', \$html) or die $!;
|
||||
local *STDOUT = $out;
|
||||
do "$root/$file";
|
||||
return ($html, $@);
|
||||
}
|
||||
|
||||
subtest 'certificate success and error output' => sub {
|
||||
# Stub only certificate I/O and account writes; preserve submitted values.
|
||||
my ($saved, $openssl_error);
|
||||
local *main::transname = local *main::tempname = sub { '/fixture/cert' };
|
||||
local *main::open_tempfile = local *main::print_tempfile =
|
||||
local *main::close_tempfile = local *main::unlink_file = sub { };
|
||||
local *main::get_ssleay = sub { 'openssl' };
|
||||
local *main::backquote_logged = sub {
|
||||
$? = $openssl_error ? 256 : 0;
|
||||
return $openssl_error || '';
|
||||
};
|
||||
local *main::list_users = sub { return {name => 'fixture'} };
|
||||
# Stop after the tested account write, before the CGI's delay and restart.
|
||||
local *main::modify_user = sub { $saved = $_[1]->{cert}; die "fixture done\n" };
|
||||
local $main::base_remote_user = 'fixture';
|
||||
my $payload = '<script>alert(1)</script>';
|
||||
for my $name ('Review User', q{O'Reilly & Partners}, $payload) {
|
||||
my ($html, $error) = render_cgi('acl/cert_issue.cgi',
|
||||
{ key => 'fixture-key', commonName => $name });
|
||||
is($error, "fixture done\n", 'successful issuance reaches account update');
|
||||
like($html, qr/\Q@{[main::html_escape($name)]}\E/,
|
||||
'common name appears as text');
|
||||
unlike($html, qr/\Q$payload\E/, 'common name cannot introduce a script');
|
||||
like($saved, qr/\Q\/CN=$name\/Email=\E/, 'stored DN retains the original name');
|
||||
}
|
||||
$openssl_error = "Invalid subject $payload\n";
|
||||
my ($html, $error) = render_cgi('acl/cert_issue.cgi',
|
||||
{ key => 'fixture-key', commonName => 'Review User' });
|
||||
like($error, qr/<pre>Invalid subject <script>/,
|
||||
'OpenSSL diagnostics retain preformatted markup and escape input');
|
||||
unlike($error, qr/\Q$payload\E/, 'OpenSSL errors cannot introduce a script');
|
||||
};
|
||||
|
||||
subtest 'process output and return link' => sub {
|
||||
my @signals;
|
||||
local *main::list_processes = sub { return map { +{user => 'fixture'} } @_ };
|
||||
local *main::can_edit_process = sub { 1 };
|
||||
local *main::kill_logged = sub { push @signals, [@_]; return 1 };
|
||||
my $query = 'search=worker%20name&match=1';
|
||||
my ($html, $error) = render_cgi('proc/kill_proc_list.cgi',
|
||||
{pidlist => '12345 12346', signal => 'TERM', args => $query});
|
||||
is($error, '', 'ordinary request renders successfully');
|
||||
is_deeply(\@signals, [['TERM', '12345'], ['TERM', '12346']],
|
||||
'signal and PID arguments are unchanged');
|
||||
$html =~ /href="(index_search\.cgi\?[^"]+)"/;
|
||||
is(main::html_unescape($1), "index_search.cgi?$query",
|
||||
'HTML escaping preserves the return query');
|
||||
|
||||
my $payload = '<script>alert(1)</script>';
|
||||
($html, $error) = render_cgi('proc/kill_proc_list.cgi',
|
||||
{pidlist => $payload, signal => $payload, args => '">'.$payload});
|
||||
is($error, '', 'hostile values render successfully');
|
||||
unlike($html, qr/\Q$payload\E/, 'PID, signal and return link cannot inject scripts');
|
||||
like($html, qr/SIG<script>/, 'signal output is escaped');
|
||||
like($html, qr/href="index_search.cgi\?"><script>/,
|
||||
'return link cannot escape its quoted attribute');
|
||||
};
|
||||
|
||||
done_testing();
|
||||
Reference in New Issue
Block a user