From 5b0c826a897bcfb49bab92ce9bfc65f3d18b49d7 Mon Sep 17 00:00:00 2001 From: Thomas Vincent Date: Sat, 24 Oct 2026 04:25:56 -0600 Subject: [PATCH] refactor: share the device scope and filter across the remaining panels Refs #384 Signed-off-by: Thomas Vincent --- includes/functions.php | 22 ++ panellib/analyze.php | 29 +-- panellib/graphs.php | 94 +------- panellib/system.php | 14 +- panellib/top5.php | 112 +-------- tests/Unit/PanelDeviceScopeTest.php | 354 ++++++++++++++++++++++++++++ tests/bootstrap-unit.php | 8 +- 7 files changed, 409 insertions(+), 224 deletions(-) create mode 100644 tests/Unit/PanelDeviceScopeTest.php diff --git a/includes/functions.php b/includes/functions.php index b5fc45e..0ed78cd 100644 --- a/includes/functions.php +++ b/includes/functions.php @@ -90,6 +90,28 @@ function intropage_device_scope($user_id): array { return ['simple' => false, 'allowed' => intropage_get_allowed_devices($user_id)]; } +/** + * Resolve a user's device scope together with the host condition a panel + * appends to its queries. + * + * Builds on intropage_device_scope(). 'cond' is the " IN (...)" text + * for a restricted user and an empty string when the user sees every device or + * has no visible devices (callers check 'allowed' before running the query). + * + * @param int|string $user_id Cacti user id whose permissions to resolve. + * @param string $column Column expression with any leading keyword, + * for example "AND host.id" or "WHERE id". + * + * @return array{simple: bool, allowed: string|false, cond: string} + */ +function intropage_device_filter($user_id, string $column): array { + $scope = intropage_device_scope($user_id); + + $scope['cond'] = $scope['simple'] ? '' : $column . ' IN (' . $scope['allowed'] . ')'; + + return $scope; +} + /** * Top-level request dispatcher for this page: sets the default action, * then routes to the appropriate handler based on which request diff --git a/panellib/analyze.php b/panellib/analyze.php index 36dd377..fc1d104 100644 --- a/panellib/analyze.php +++ b/panellib/analyze.php @@ -626,14 +626,11 @@ function analyse_tree_host_graph($panel, $user_id): void { $total_errors = 0; $data = []; - $simple_perms = get_simple_device_perms($user_id); + ['simple' => $simple_perms, 'allowed' => $allowed_devices] = intropage_device_scope($user_id); + $q_host_cond = ''; if (!$simple_perms) { - $allowed_devices = intropage_get_allowed_devices($user_id); - $host_cond = ' IN (' . $allowed_devices . ')'; - } else { - $allowed_devices = false; - $q_host_cond = ''; + $host_cond = ' IN (' . $allowed_devices . ')'; } if ($allowed_devices !== false || $simple_perms) { @@ -1565,25 +1562,19 @@ function analyse_tree_host_graph_detail(): array { global $config, $console_access; if (isset($_SESSION['sess_user_id'])) { - $simple_perms = get_simple_device_perms($_SESSION['sess_user_id']); + ['simple' => $simple_perms, 'allowed' => $allowed_devices] = intropage_device_scope($_SESSION['sess_user_id']); + $q_host_cond = ''; if (!$simple_perms) { - $allowed_devices = intropage_get_allowed_devices($_SESSION['sess_user_id']); - $host_cond = ' IN (' . $allowed_devices . ')'; - } else { - $allowed_devices = false; - $q_host_cond = ''; + $host_cond = ' IN (' . $allowed_devices . ')'; } } else { - $admin_user = read_config_option('admin_user'); - $simple_perms = get_simple_device_perms($admin_user); + $admin_user = read_config_option('admin_user'); + ['simple' => $simple_perms, 'allowed' => $allowed_devices] = intropage_device_scope($admin_user); + $q_host_cond = ''; if (!$simple_perms) { - $allowed_devices = intropage_get_allowed_devices($admin_user); - $host_cond = ' IN (' . $allowed_devices . ')'; - } else { - $allowed_devices = false; - $q_host_cond = ''; + $host_cond = ' IN (' . $allowed_devices . ')'; } } diff --git a/panellib/graphs.php b/panellib/graphs.php index 15c9f63..fe5287d 100644 --- a/panellib/graphs.php +++ b/panellib/graphs.php @@ -121,15 +121,7 @@ function register_graphs(): array { function graph_data_source($panel, $user_id): void { global $config, $input_types, $run_from_poller; - $simple_perms = get_simple_device_perms($user_id); - - if (!$simple_perms) { - $allowed_devices = intropage_get_allowed_devices($user_id); - $host_cond = 'IN (' . $allowed_devices . ')'; - } else { - $allowed_devices = false; - $q_host_cond = ''; - } + ['simple' => $simple_perms, 'allowed' => $allowed_devices, 'cond' => $q_host_cond] = intropage_device_filter($user_id, 'AND data_local.host_id'); if ($allowed_devices !== false || $simple_perms) { $graph = [ @@ -140,10 +132,6 @@ function graph_data_source($panel, $user_id): void { ], ]; - if (!$simple_perms) { - $q_host_cond = 'AND data_local.host_id ' . $host_cond; - } - $sql_ds = db_fetch_assoc("SELECT data_input.type_id, COUNT(data_input.type_id) AS total FROM data_local INNER JOIN data_template_data @@ -195,15 +183,7 @@ function graph_data_source($panel, $user_id): void { function graph_host_template($panel, $user_id): void { global $config; - $simple_perms = get_simple_device_perms($user_id); - - if (!$simple_perms) { - $allowed_devices = intropage_get_allowed_devices($user_id); - $host_cond = 'IN (' . $allowed_devices . ')'; - } else { - $allowed_devices = false; - $q_host_cond = ''; - } + ['simple' => $simple_perms, 'allowed' => $allowed_devices, 'cond' => $q_host_cond] = intropage_device_filter($user_id, 'WHERE host.id'); if (defined('CACTI_VERSION')) { $cv = CACTI_VERSION; @@ -212,10 +192,6 @@ function graph_host_template($panel, $user_id): void { } if ($allowed_devices !== false || $simple_perms) { - if (!$simple_perms) { - $q_host_cond = 'WHERE host.id ' . $host_cond; - } - // 1.2.24+ has billboard.js with treemap if (cacti_version_compare($cv, '1.2.24', '>=')) { $graph = [ @@ -304,15 +280,7 @@ function graph_host($panel, $user_id, $timespan = 0): void { $panel['alarm'] = 'green'; - $simple_perms = get_simple_device_perms($user_id); - - if (!$simple_perms) { - $allowed_devices = intropage_get_allowed_devices($user_id); - $host_cond = 'IN (' . $allowed_devices . ')'; - } else { - $allowed_devices = false; - $q_host_cond = ''; - } + ['simple' => $simple_perms, 'allowed' => $allowed_devices] = intropage_device_scope($user_id); if ($allowed_devices !== false || $simple_perms) { $graph = [ @@ -453,19 +421,7 @@ function graph_data_source_detail(): array { 'detail' => '' ]; - $simple_perms = get_simple_device_perms($_SESSION['sess_user_id']); - - if (!$simple_perms) { - $allowed_devices = intropage_get_allowed_devices($_SESSION['sess_user_id']); - $host_cond = 'IN (' . $allowed_devices . ')'; - } else { - $allowed_devices = false; - $q_host_cond = ''; - } - - if (!$simple_perms) { - $q_host_cond = 'AND dl.host_id ' . $host_cond; - } + ['simple' => $simple_perms, 'allowed' => $allowed_devices, 'cond' => $q_host_cond] = intropage_device_filter($_SESSION['sess_user_id'], 'AND dl.host_id'); $sql_ds = db_fetch_assoc("SELECT di.type_id, COUNT(di.type_id) AS total FROM data_local AS dl @@ -532,19 +488,7 @@ function graph_host_detail(): array { 'detail' => '', ]; - $simple_perms = get_simple_device_perms($_SESSION['sess_user_id']); - - if (!$simple_perms) { - $allowed_devices = intropage_get_allowed_devices($_SESSION['sess_user_id']); - $host_cond = 'IN (' . $allowed_devices . ')'; - } else { - $allowed_devices = false; - $q_host_cond = ''; - } - - if (!$simple_perms) { - $q_host_cond = 'AND id ' . $host_cond; - } + ['simple' => $simple_perms, 'allowed' => $allowed_devices, 'cond' => $q_host_cond] = intropage_device_filter($_SESSION['sess_user_id'], 'AND id'); $h_all = db_fetch_cell("SELECT COUNT(id) FROM host WHERE $q_host_cond"); @@ -689,19 +633,7 @@ function graph_host_template_detail(): array { 'detail' => '', ]; - $simple_perms = get_simple_device_perms($_SESSION['sess_user_id']); - - if (!$simple_perms) { - $allowed_devices = intropage_get_allowed_devices($_SESSION['sess_user_id']); - $host_cond = 'IN (' . $allowed_devices . ')'; - } else { - $allowed_devices = false; - $q_host_cond = ''; - } - - if (!$simple_perms) { - $q_host_cond = 'WHERE host.id ' . $host_cond; - } + ['simple' => $simple_perms, 'allowed' => $allowed_devices, 'cond' => $q_host_cond] = intropage_device_filter($_SESSION['sess_user_id'], 'WHERE host.id'); $rows = db_fetch_assoc("SELECT host_template.id as id, name, COUNT(host.host_template_id) AS total FROM host_template @@ -760,19 +692,7 @@ function host_collect(): void { $users = get_user_list(); foreach ($users as $user) { - $simple_perms = get_simple_device_perms($user['id']); - - if (!$simple_perms) { - $allowed_devices = intropage_get_allowed_devices($user['id']); - $host_cond = 'IN (' . $allowed_devices . ')'; - } else { - $allowed_devices = false; - $q_host_cond = ''; - } - - if (!$simple_perms) { - $q_host_cond = 'AND id ' . $host_cond; - } + ['simple' => $simple_perms, 'allowed' => $allowed_devices, 'cond' => $q_host_cond] = intropage_device_filter($user['id'], 'AND id'); if ($allowed_devices !== false || $simple_perms) { db_execute_prepared("INSERT INTO plugin_intropage_trends diff --git a/panellib/system.php b/panellib/system.php index 08cda14..4b33fa8 100644 --- a/panellib/system.php +++ b/panellib/system.php @@ -662,19 +662,7 @@ function extrem_trend(): void { foreach ($users as $user) { if (is_panel_allowed('extrem', $user['id'])) { - $simple_perms = get_simple_device_perms($user['id']); - - if (!$simple_perms) { - $allowed_devices = intropage_get_allowed_devices($user['id']); - $host_cond = 'IN (' . $allowed_devices . ')'; - } else { - $allowed_devices = false; - $q_host_cond = ''; - } - - if (!$simple_perms) { - $q_host_cond = 'WHERE id ' . $host_cond; - } + ['simple' => $simple_perms, 'allowed' => $allowed_devices, 'cond' => $q_host_cond] = intropage_device_filter($user['id'], 'WHERE id'); if ($allowed_devices !== false || $simple_perms) { $count = db_fetch_cell("SELECT SUM(failed_polls) diff --git a/panellib/top5.php b/panellib/top5.php index ac52265..e4776e7 100644 --- a/panellib/top5.php +++ b/panellib/top5.php @@ -144,19 +144,7 @@ function top5_ping($panel, $user_id): void { $panel['alarm'] = 'green'; - $simple_perms = get_simple_device_perms($user_id); - - if (!$simple_perms) { - $allowed_devices = intropage_get_allowed_devices($user_id); - $host_cond = 'IN (' . $allowed_devices . ')'; - } else { - $allowed_devices = false; - $q_host_cond = ''; - } - - if (!$simple_perms) { - $q_host_cond = 'AND id ' . $host_cond; - } + ['simple' => $simple_perms, 'allowed' => $allowed_devices, 'cond' => $q_host_cond] = intropage_device_filter($user_id, 'AND id'); if ($allowed_devices !== false || $simple_perms) { $console_access = get_console_access($user_id); @@ -241,19 +229,7 @@ function top5_availability($panel, $user_id): void { $panel['alarm'] = 'green'; - $simple_perms = get_simple_device_perms($user_id); - - if (!$simple_perms) { - $allowed_devices = intropage_get_allowed_devices($user_id); - $host_cond = 'IN (' . $allowed_devices . ')'; - } else { - $allowed_devices = false; - $q_host_cond = ''; - } - - if (!$simple_perms) { - $q_host_cond = 'AND id ' . $host_cond; - } + ['simple' => $simple_perms, 'allowed' => $allowed_devices, 'cond' => $q_host_cond] = intropage_device_filter($user_id, 'AND id'); if ($allowed_devices !== false || $simple_perms) { $console_access = get_console_access($user_id); @@ -335,19 +311,7 @@ function top5_polltime($panel, $user_id): void { $panel['alarm'] = 'green'; - $simple_perms = get_simple_device_perms($user_id); - - if (!$simple_perms) { - $allowed_devices = intropage_get_allowed_devices($user_id); - $host_cond = 'IN (' . $allowed_devices . ')'; - } else { - $allowed_devices = false; - $q_host_cond = ''; - } - - if (!$simple_perms) { - $q_host_cond = 'AND id ' . $host_cond; - } + ['simple' => $simple_perms, 'allowed' => $allowed_devices, 'cond' => $q_host_cond] = intropage_device_filter($user_id, 'AND id'); if ($allowed_devices !== false || $simple_perms) { $console_access = get_console_access($user_id); @@ -430,19 +394,7 @@ function top5_pollratio($panel, $user_id): void { $panel['alarm'] = 'green'; - $simple_perms = get_simple_device_perms($user_id); - - if (!$simple_perms) { - $allowed_devices = intropage_get_allowed_devices($user_id); - $host_cond = 'IN (' . $allowed_devices . ')'; - } else { - $allowed_devices = false; - $q_host_cond = ''; - } - - if (!$simple_perms) { - $q_host_cond = 'AND id ' . $host_cond; - } + ['simple' => $simple_perms, 'allowed' => $allowed_devices, 'cond' => $q_host_cond] = intropage_device_filter($user_id, 'AND id'); if ($allowed_devices !== false || $simple_perms) { $console_access = get_console_access($user_id); @@ -527,19 +479,7 @@ function top5_ping_detail(): array { 'detail' => '', ]; - $simple_perms = get_simple_device_perms($_SESSION['sess_user_id']); - - if (!$simple_perms) { - $allowed_devices = intropage_get_allowed_devices($_SESSION['sess_user_id']); - $host_cond = 'IN (' . $allowed_devices . ')'; - } else { - $allowed_devices = false; - $q_host_cond = ''; - } - - if (!$simple_perms) { - $q_host_cond = 'AND id ' . $host_cond; - } + ['simple' => $simple_perms, 'allowed' => $allowed_devices, 'cond' => $q_host_cond] = intropage_device_filter($_SESSION['sess_user_id'], 'AND id'); if ($allowed_devices !== false || $simple_perms) { $sql_worst_host = db_fetch_assoc("SELECT description, id, avg_time, cur_time @@ -619,19 +559,7 @@ function top5_availability_detail(): array { 'detail' => '', ]; - $simple_perms = get_simple_device_perms($_SESSION['sess_user_id']); - - if (!$simple_perms) { - $allowed_devices = intropage_get_allowed_devices($_SESSION['sess_user_id']); - $host_cond = 'IN (' . $allowed_devices . ')'; - } else { - $allowed_devices = false; - $q_host_cond = ''; - } - - if (!$simple_perms) { - $q_host_cond = 'AND id ' . $host_cond; - } + ['simple' => $simple_perms, 'allowed' => $allowed_devices, 'cond' => $q_host_cond] = intropage_device_filter($_SESSION['sess_user_id'], 'AND id'); if ($allowed_devices !== false || $simple_perms) { $sql_worst_host = db_fetch_assoc("SELECT description, id, availability @@ -709,19 +637,7 @@ function top5_polltime_detail(): array { 'detail' => '', ]; - $simple_perms = get_simple_device_perms($_SESSION['sess_user_id']); - - if (!$simple_perms) { - $allowed_devices = intropage_get_allowed_devices($_SESSION['sess_user_id']); - $host_cond = 'IN (' . $allowed_devices . ')'; - } else { - $allowed_devices = false; - $q_host_cond = ''; - } - - if (!$simple_perms) { - $q_host_cond = 'AND id ' . $host_cond; - } + ['simple' => $simple_perms, 'allowed' => $allowed_devices, 'cond' => $q_host_cond] = intropage_device_filter($_SESSION['sess_user_id'], 'AND id'); if ($allowed_devices !== false || $simple_perms) { $sql_worst_host = db_fetch_assoc("SELECT id, description, polling_time @@ -799,19 +715,7 @@ function top5_pollratio_detail(): array { 'detail' => '', ]; - $simple_perms = get_simple_device_perms($_SESSION['sess_user_id']); - - if (!$simple_perms) { - $allowed_devices = intropage_get_allowed_devices($_SESSION['sess_user_id']); - $host_cond = 'IN (' . $allowed_devices . ')'; - } else { - $allowed_devices = false; - $q_host_cond = ''; - } - - if (!$simple_perms) { - $q_host_cond = 'AND id ' . $host_cond; - } + ['simple' => $simple_perms, 'allowed' => $allowed_devices, 'cond' => $q_host_cond] = intropage_device_filter($_SESSION['sess_user_id'], 'AND id'); if ($allowed_devices !== false || $simple_perms) { $sql_worst_host = db_fetch_assoc("SELECT id, description, failed_polls, diff --git a/tests/Unit/PanelDeviceScopeTest.php b/tests/Unit/PanelDeviceScopeTest.php new file mode 100644 index 0000000..6cfc4b2 --- /dev/null +++ b/tests/Unit/PanelDeviceScopeTest.php @@ -0,0 +1,354 @@ + 2, 'threads' => 2]; + $_SESSION['sess_user_id'] = 2; + }); + + afterEach(function () { + unset( + $GLOBALS['__test_simple_perms'], + $GLOBALS['__test_allowed_devices'], + $GLOBALS['__test_db_calls'], + $GLOBALS['__test_fetch_cell'], + $GLOBALS['__test_fetch_row'], + $_SESSION['sess_user_id'] + ); + }); + + function panel_scope_sql(): string { + $rows = array_filter($GLOBALS['__test_db_calls'], fn ($c) => strpos($c['fn'], 'db_fetch') === 0); + + return implode("\n", array_column($rows, 'sql')); + } + + function panel_scope_run(string $fn, bool $detail): void { + $panel = ['height' => 'normal', 'id' => 1, 'data' => '', 'alarm' => 'grey']; + + if ($detail) { + $fn(); + } else { + $fn($panel, 2); + } + } + + $panels = [ + 'top5_ping' => ['top5_ping', false, 'AND id IN (3,7,9)'], + 'top5_availability' => ['top5_availability', false, 'AND id IN (3,7,9)'], + 'top5_polltime' => ['top5_polltime', false, 'AND id IN (3,7,9)'], + 'top5_pollratio' => ['top5_pollratio', false, 'AND id IN (3,7,9)'], + 'top5_ping_detail' => ['top5_ping_detail', true, 'AND id IN (3,7,9)'], + 'top5_availability_detail' => ['top5_availability_detail', true, 'AND id IN (3,7,9)'], + 'top5_polltime_detail' => ['top5_polltime_detail', true, 'AND id IN (3,7,9)'], + 'top5_pollratio_detail' => ['top5_pollratio_detail', true, 'AND id IN (3,7,9)'], + 'graph_data_source' => ['graph_data_source', false, 'AND data_local.host_id IN (3,7,9)'], + 'graph_host_template' => ['graph_host_template', false, 'WHERE host.id IN (3,7,9)'], + 'graph_data_source_detail' => ['graph_data_source_detail', true, 'AND dl.host_id IN (3,7,9)'], + 'graph_host_template_detail' => ['graph_host_template_detail', true, 'WHERE host.id IN (3,7,9)'], + 'graph_host_detail' => ['graph_host_detail', true, 'AND id IN (3,7,9)'], + 'analyse_tree_host_graph' => ['analyse_tree_host_graph', false, 'IN (3,7,9)'], + 'analyse_tree_host_graph_detail' => ['analyse_tree_host_graph_detail', true, 'IN (3,7,9)'], + ]; + + it('filters the query to the allowed hosts for a restricted user', function (string $fn, bool $detail, string $clause) { + $GLOBALS['__test_simple_perms'] = false; + $GLOBALS['__test_allowed_devices'] = [['id' => 3], ['id' => 7], ['id' => 9]]; + + panel_scope_run($fn, $detail); + + expect(panel_scope_sql())->toContain($clause); + })->with($panels); + + it('applies no host filter for a simple-permission user', function (string $fn, bool $detail, string $clause) { + $GLOBALS['__test_simple_perms'] = true; + + panel_scope_run($fn, $detail); + + expect(panel_scope_sql())->not->toContain('IN (3,7,9)'); + expect(panel_scope_sql())->not->toMatch('/\bIN \(\s*\)/'); + })->with($panels); +}); + +/* + * The detail view runs outside a session when the poller or a CLI caller + * renders it, and then scopes to the configured admin user. + */ +describe('analyse_tree_host_graph_detail without a session', function () { + beforeEach(function () { + $GLOBALS['__test_db_calls'] = []; + $GLOBALS['__test_config'] = ['admin_user' => 1]; + $GLOBALS['__test_fetch_cell'] = 0; + $GLOBALS['__test_fetch_row'] = ['processes' => 2, 'threads' => 2]; + unset($_SESSION['sess_user_id']); + }); + + afterEach(function () { + unset( + $GLOBALS['__test_simple_perms'], + $GLOBALS['__test_allowed_devices'], + $GLOBALS['__test_db_calls'], + $GLOBALS['__test_fetch_cell'], + $GLOBALS['__test_fetch_row'] + ); + }); + + it('scopes to the allowed hosts of the admin user', function () { + $GLOBALS['__test_simple_perms'] = false; + $GLOBALS['__test_allowed_devices'] = [['id' => 3], ['id' => 7], ['id' => 9]]; + + analyse_tree_host_graph_detail(); + + expect(panel_scope_sql())->toContain('IN (3,7,9)'); + }); + + it('applies no host filter when the admin user sees every device', function () { + $GLOBALS['__test_simple_perms'] = true; + + analyse_tree_host_graph_detail(); + + expect(panel_scope_sql())->not->toContain('IN (3,7,9)'); + }); +}); + +/* + * host_collect() and extrem_trend() write one trend row set per user. A + * restricted user's counts must be limited to the devices that user can see, + * and a restricted user with no visible devices must get zero rows instead of + * an unfiltered count. + */ +describe('trend collectors device scope', function () { + beforeEach(function () { + $GLOBALS['__test_db_calls'] = []; + $GLOBALS['__test_cell_calls'] = []; + $GLOBALS['__test_config'] = []; + $GLOBALS['__test_fetch_cell'] = 4; + $GLOBALS['__test_fetch_cell_prepared'] = '{"extrem":"on"}'; + $GLOBALS['config']['is_web'] = true; + $_SESSION['sess_user_id'] = 2; + }); + + afterEach(function () { + unset( + $GLOBALS['__test_simple_perms'], + $GLOBALS['__test_allowed_devices'], + $GLOBALS['__test_db_calls'], + $GLOBALS['__test_cell_calls'], + $GLOBALS['__test_fetch_cell'], + $GLOBALS['__test_fetch_cell_prepared'], + $GLOBALS['config']['is_web'], + $_SESSION['sess_user_id'] + ); + }); + + function trend_sql(string $fn): string { + if ($fn === 'db_fetch_cell') { + return implode("\n", $GLOBALS['__test_cell_calls'] ?? []); + } + + $rows = array_filter($GLOBALS['__test_db_calls'], fn ($c) => $c['fn'] === $fn); + + return implode("\n", array_column($rows, 'sql')); + } + + it('limits host_collect counts to the allowed hosts', function () { + $GLOBALS['__test_simple_perms'] = false; + $GLOBALS['__test_allowed_devices'] = [['id' => 3], ['id' => 7], ['id' => 9]]; + + host_collect(); + + expect(trend_sql('db_execute_prepared'))->toContain('AND id IN (3,7,9)'); + }); + + it('does not filter host_collect counts for a simple-permission user', function () { + $GLOBALS['__test_simple_perms'] = true; + + host_collect(); + + expect(trend_sql('db_execute_prepared'))->toContain("status='1'"); + expect(trend_sql('db_execute_prepared'))->not->toContain('IN (3,7,9)'); + }); + + it('writes zero rows from host_collect for a restricted user with no devices', function () { + $GLOBALS['__test_simple_perms'] = false; + $GLOBALS['__test_allowed_devices'] = []; + + host_collect(); + + expect(trend_sql('db_execute_prepared'))->toContain("VALUES ('host_down', 0, ?)"); + expect(trend_sql('db_execute_prepared'))->not->toContain('FROM host'); + }); + + it('limits the extrem_trend failed poll sum to the allowed hosts', function () { + $GLOBALS['__test_simple_perms'] = false; + $GLOBALS['__test_allowed_devices'] = [['id' => 3], ['id' => 7], ['id' => 9]]; + + extrem_trend(); + + expect(trend_sql('db_fetch_cell'))->toContain('WHERE id IN (3,7,9)'); + }); + + it('does not filter the extrem_trend failed poll sum for a simple-permission user', function () { + $GLOBALS['__test_simple_perms'] = true; + + extrem_trend(); + + expect(trend_sql('db_fetch_cell'))->toContain('SUM(failed_polls)'); + expect(trend_sql('db_fetch_cell'))->not->toContain('IN (3,7,9)'); + }); + + it('skips the extrem_trend failed poll sum for a restricted user with no devices', function () { + $GLOBALS['__test_simple_perms'] = false; + $GLOBALS['__test_allowed_devices'] = []; + + extrem_trend(); + + expect(trend_sql('db_fetch_cell'))->not->toContain('SUM(failed_polls)'); + }); +}); + +/* + * graph_host() reads stored trend rows rather than filtering host queries, so + * the scope only decides between drawing the graph and refusing the panel. + */ +describe('graph_host device scope', function () { + beforeEach(function () { + $GLOBALS['__test_db_calls'] = []; + $GLOBALS['__test_config'] = ['intropage_timespan' => 3600]; + }); + + afterEach(function () { + unset( + $GLOBALS['__test_simple_perms'], + $GLOBALS['__test_allowed_devices'], + $GLOBALS['__test_db_calls'] + ); + }); + + function graph_host_saved_text(): string { + $saves = array_filter($GLOBALS['__test_db_calls'], fn ($c) => strpos($c['sql'], 'UPDATE plugin_intropage_panel_data') !== false); + + return (string) end($saves)['params'][0]; + } + + it('refuses the panel for a restricted user with no visible devices', function () { + $GLOBALS['__test_simple_perms'] = false; + $GLOBALS['__test_allowed_devices'] = []; + + graph_host(['id' => 1, 'alarm' => 'grey', 'data' => '', 'refresh' => 60, 'refresh_interval' => 60], 2); + + expect(graph_host_saved_text())->toContain('permissions to any hosts'); + }); + + it('shows the panel for a restricted user with visible devices', function () { + $GLOBALS['__test_simple_perms'] = false; + $GLOBALS['__test_allowed_devices'] = [['id' => 3]]; + + graph_host(['id' => 1, 'alarm' => 'grey', 'data' => '', 'refresh' => 60, 'refresh_interval' => 60], 2); + + expect(graph_host_saved_text())->not->toContain('permissions to any hosts'); + }); + + it('shows the panel for a simple-permission user', function () { + $GLOBALS['__test_simple_perms'] = true; + + graph_host(['id' => 1, 'alarm' => 'grey', 'data' => '', 'refresh' => 60, 'refresh_interval' => 60], 2); + + expect(graph_host_saved_text())->not->toContain('permissions to any hosts'); + }); +}); + +describe('intropage_device_filter', function () { + afterEach(function () { + unset($GLOBALS['__test_simple_perms'], $GLOBALS['__test_allowed_devices']); + }); + + it('builds the condition from the allowed host ids for a restricted user', function () { + $GLOBALS['__test_simple_perms'] = false; + $GLOBALS['__test_allowed_devices'] = [['id' => 3], ['id' => 7], ['id' => 9]]; + + $filter = intropage_device_filter(2, 'AND host.id'); + + expect($filter['simple'])->toBeFalse(); + expect($filter['allowed'])->toBe('3,7,9'); + expect($filter['cond'])->toBe('AND host.id IN (3,7,9)'); + }); + + it('returns no condition for a simple-permission user', function () { + $GLOBALS['__test_simple_perms'] = true; + + $filter = intropage_device_filter(1, 'WHERE id'); + + expect($filter['simple'])->toBeTrue(); + expect($filter['allowed'])->toBeFalse(); + expect($filter['cond'])->toBe(''); + }); + + it('leaves allowed false for a restricted user with no visible devices', function () { + $GLOBALS['__test_simple_perms'] = false; + $GLOBALS['__test_allowed_devices'] = []; + + $filter = intropage_device_filter(2, 'AND id'); + + expect($filter['simple'])->toBeFalse(); + expect($filter['allowed'])->toBeFalse(); + }); +}); diff --git a/tests/bootstrap-unit.php b/tests/bootstrap-unit.php index 1bd63cb..4bb6ae5 100644 --- a/tests/bootstrap-unit.php +++ b/tests/bootstrap-unit.php @@ -123,12 +123,18 @@ function db_fetch_row_prepared($sql, $p = array()) { } if (!function_exists('db_fetch_cell')) { function db_fetch_cell($sql) { + $GLOBALS['__test_cell_calls'][] = $sql; return $GLOBALS['__test_fetch_cell'] ?? ''; } } if (!function_exists('db_fetch_cell_prepared')) { function db_fetch_cell_prepared($sql, $p = array()) { - return ''; + return $GLOBALS['__test_fetch_cell_prepared'] ?? ''; + } +} +if (!function_exists('db_table_exists')) { + function db_table_exists($t) { + return false; } } if (!function_exists('db_index_exists')) {