Fix index page to implement view ACL better

This commit is contained in:
Ilia Ross
2026-05-28 14:27:27 +02:00
parent 440ddabef1
commit e10acfa3bb
2 changed files with 73 additions and 29 deletions

View File

@@ -13,16 +13,19 @@ our $grub2_formno = 0;
&error_setup($text{'acl_ecannot'});
my %access = &grub2_effective_acl();
&error("$text{'eacl_np'} $text{'eacl_pview'}")
if (!&grub2_can_enter_module(\%access));
if (!&can_use_index(\%access));
# Show configuration/install guidance before rendering module actions.
if (!&grub2_any_installed()) {
&ui_print_header(&grub2_version_text() || "", $text{'index_title'},
"", undef, 1, 1);
print &ui_alert($text{'index_missing'}, 'warning');
foreach my $issue (&grub2_install_issues()) {
print &ui_div(&text('index_missing_detail',
&ui_tag('tt', &html_escape($issue))));
if ($access{'view'}) {
# Install issue details include discovered paths and commands.
foreach my $issue (&grub2_install_issues()) {
print &ui_div(&text('index_missing_detail',
&ui_tag('tt', &html_escape($issue))));
}
}
print &ui_p(&ui_link("@{[&get_webprefix()]}/config.cgi?$module_name",
$text{'index_config_link'}));
@@ -41,33 +44,47 @@ if (!&grub2_any_installed()) {
&ui_print_header(&grub2_version_text() || "", $text{'index_title'}, "",
undef, 1, 1, undef, &grub2_action_links(\%access));
foreach my $warning (&grub2_status_warnings()) {
print &ui_alert($warning, 'warning');
if ($access{'view'}) {
foreach my $warning (&grub2_status_warnings()) {
print &ui_alert($warning, 'warning');
}
# Only the two entry lists are tabs; global settings live in separate pages.
my @tabs = (
[ 'entries', $text{'index_entries_tab'} ],
[ 'custom', $text{'index_custom_tab'} ],
);
my %valid = map { $_->[0] => 1 } @tabs;
my $requested = defined($in{'mode'}) ? $in{'mode'} : '';
my $mode = $requested && $valid{$requested} ? $requested : 'entries';
print &ui_tabs_start(\@tabs, "mode", $mode, 1);
print &ui_tabs_start_tab("mode", "entries");
&print_entries_tab(\%access);
print &ui_tabs_end_tab("mode", "entries");
print &ui_tabs_start_tab("mode", "custom");
&print_custom_tab(\%access);
print &ui_tabs_end_tab("mode", "custom");
print &ui_tabs_end();
}
# Only the two entry lists are tabs; global settings live in separate pages.
my @tabs = (
[ 'entries', $text{'index_entries_tab'} ],
[ 'custom', $text{'index_custom_tab'} ],
);
my %valid = map { $_->[0] => 1 } @tabs;
my $requested = defined($in{'mode'}) ? $in{'mode'} : '';
my $mode = $requested && $valid{$requested} ? $requested : 'entries';
print &ui_tabs_start(\@tabs, "mode", $mode, 1);
print &ui_tabs_start_tab("mode", "entries");
&print_entries_tab(\%access);
print &ui_tabs_end_tab("mode", "entries");
print &ui_tabs_start_tab("mode", "custom");
&print_custom_tab(\%access);
print &ui_tabs_end_tab("mode", "custom");
print &ui_tabs_end();
&print_action_buttons(\%access);
&ui_print_footer("/", $text{'index_return'});
# can_use_index(&access)
# Returns true if the index can show entry data or a global action.
sub can_use_index
{
my ($access) = @_;
return 1 if ($access->{'view'});
return 1 if ($access->{'edit'} || $access->{'security'} ||
$access->{'manual'} || $access->{'install'});
return 1 if ($access->{'apply'} && &grub2_command('mkconfig_cmd'));
return 0;
}
# print_entries_tab(&access)
# Outputs generated boot menu entries and selected-entry runtime actions.
sub print_entries_tab
@@ -78,8 +95,10 @@ my $parsed = &read_grub_defaults();
my %env = &grub2_read_env();
# Selection roles are derived from both defaults and grubenv state.
my %selection = &grub2_entry_selection_roles(\@entries, $parsed, \%env);
my $can_default = $access->{'runtime'} && &grub2_command('set_default_cmd');
my $can_once = $access->{'runtime'} && &grub2_command('reboot_once_cmd');
my $can_default = $access->{'view'} && $access->{'runtime'} &&
&grub2_command('set_default_cmd');
my $can_once = $access->{'view'} && $access->{'runtime'} &&
&grub2_command('reboot_once_cmd');
my $show_actions = $can_default || $can_once;
print &ui_div($text{'index_entries_desc'});
if (!@entries) {
@@ -230,7 +249,8 @@ if ($access->{'manual'}) {
push(@icons, "images/manual.svg");
}
return if (!@links && !$can_status && !$can_generate);
print &ui_hr();
# Without view content, the action hub should start directly with actions.
print &ui_hr() if ($access->{'view'});
if (@links) {
print &ui_subheading($text{'index_global'});
&icons_table(\@links, \@titles, \@icons, scalar(@links) > 5 ? 5 :

View File

@@ -153,6 +153,7 @@ my $save_defaults_source = slurp_test_file("$bindir/../save_defaults.cgi");
my $theme_source = slurp_test_file("$bindir/../edit_theme.cgi");
my $save_theme_source = slurp_test_file("$bindir/../save_theme.cgi");
my $edit_install_source = slurp_test_file("$bindir/../edit_install.cgi");
my $acl_source = slurp_test_file("$bindir/../acl_security.pl");
unlike($index_source, qr/\[\s*'security'\s*,/,
'index has no separate security tab');
unlike($index_source, qr/\[\s*'defaults'\s*,/,
@@ -167,9 +168,29 @@ my $status_source = slurp_test_file("$bindir/../status.cgi");
like($index_source,
qr/ui_print_header\(&grub2_version_text\(\) \|\| "".*?\$text\{'index_title'\}/s,
'index header displays GRUB version text when available');
like($index_source,
qr/sub can_use_index\b.*?\$access->\{'view'\}.*?\$access->\{'edit'\}.*?\$access->\{'security'\}.*?\$access->\{'manual'\}.*?\$access->\{'install'\}.*?\$access->\{'apply'\} && &grub2_command\('mkconfig_cmd'\)/s,
'index allows action-only ACLs without granting entry view');
like($index_source,
qr/if \(\$access\{'view'\}\) \{.*?grub2_install_issues/s,
'index hides install issue details without view ACL');
like($index_source,
qr/if \(\$access\{'view'\}\) \{.*?grub2_status_warnings.*?ui_tabs_start/s,
'index hides status warnings and entry tabs without view ACL');
like($index_source,
qr/my \$can_default = \$access->\{'view'\} && \$access->\{'runtime'\}/,
'index runtime default actions require view ACL');
like($index_source,
qr/my \$can_once = \$access->\{'view'\} && \$access->\{'runtime'\}/,
'index one-time boot actions require view ACL');
like($index_source,
qr/sub print_action_buttons\b.*?ui_buttons_row\("status\.cgi".*?ui_buttons_row\("generate\.cgi"/ms,
'index exposes status above the bottom regenerate action');
like($acl_source,
qr/ui_table_row\(\$text\{'acl_view'\},\s*&ui_yesno_radio\("view"/s,
'ACL view permission is rendered as a standalone row');
unlike($acl_source, qr/acl_section_view/,
'ACL view permission does not use a one-row section heading');
like($status_source, qr/grub2_assert_acl\('view'\)/,
'status page enforces view ACL directly');
like($status_source, qr/index_boot_mode.*boot_mode_cell/s,
@@ -357,6 +378,9 @@ like($edit_install_source, qr/install_boot_directory.*use_boot_directory/s,
like($index_source,
qr/sub print_action_buttons\b.*?&icons_table\(/ms,
'index action shortcuts use an icon table');
like($index_source,
qr/sub print_action_buttons\b.*?print &ui_hr\(\) if \(\$access->\{'view'\}\)/ms,
'index omits the top action separator for action-only ACLs');
like($index_source,
qr/sub print_action_buttons\b.*?ui_buttons_row\("generate\.cgi"/ms,
'index exposes a bottom regenerate action button');