From b451dd6fd5ba3652ad4b73ba927f725ed5ab1e75 Mon Sep 17 00:00:00 2001 From: Sean Mancini Date: Fri, 27 Feb 2026 19:59:20 -0500 Subject: [PATCH 01/12] rename duplicated files --- .../ISSUE_TEMPLATE/copilot-instructions.md | 80 ------------------- .github/ISSUE_TEMPLATE/opilot-instructions.md | 80 ------------------- .../copilot-instructions.md | 10 ++- 3 files changed, 6 insertions(+), 164 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/copilot-instructions.md delete mode 100644 .github/ISSUE_TEMPLATE/opilot-instructions.md rename .github/{ => instructions}/copilot-instructions.md (88%) diff --git a/.github/ISSUE_TEMPLATE/copilot-instructions.md b/.github/ISSUE_TEMPLATE/copilot-instructions.md deleted file mode 100644 index c658193..0000000 --- a/.github/ISSUE_TEMPLATE/copilot-instructions.md +++ /dev/null @@ -1,80 +0,0 @@ -# Cacti Audit Plugin Development Instructions - -## Project Overview -This is a Cacti plugin designed to audit user activities, including configuration changes and CLI commands. It hooks into Cacti's core to capture `$_POST` data and logs it to the `audit_log` table. - -## Architecture & Core Components -- **Plugin Entry (`setup.php`)**: Registers hooks, realms, and handles installation/upgrades. -- **Audit Logic (`audit_functions.php`)**: Contains `audit_process_page_data` which resolves object IDs to human-readable names for specific Cacti pages. -- **UI (`audit.php`)**: The main interface for viewing audit logs. -- **Database**: Uses the `audit_log` table. Schema updates are handled in `audit_check_upgrade()` within `setup.php`. - -## Key Development Patterns - -### 1. Extending Audit Coverage -To add detailed auditing for a new Cacti page (e.g., resolving an ID to a name), modify `audit_process_page_data` in `audit_functions.php`. - -**Pattern:** -```php -case 'your_page.php': - foreach ($selected_items as $item) { - // Fetch descriptive data for the item ID - $objects[] = db_fetch_assoc_prepared('SELECT name FROM your_table WHERE id = ?', array($item)); - } - break; -``` - -### 2. Database Interaction -Always use Cacti's database wrapper functions. **Never** use raw PHP MySQL functions. -- `db_fetch_assoc_prepared($sql, $params)`: For fetching multiple rows. -- `db_fetch_row_prepared($sql, $params)`: For fetching a single row. -- `db_fetch_cell_prepared($sql, $params)`: For fetching a single value. -- `db_execute_prepared($sql, $params)`: For INSERT/UPDATE/DELETE. - -### 3. Input Handling & Security -- Use `get_request_var('var_name')` or `get_filter_request_var('var_name')` to retrieve `$_GET`/`$_POST` data. -- Ensure all user-facing strings are localized using `__('String', 'audit')`. - -### 4. Plugin Hooks -Hooks are registered in `plugin_audit_install()` in `setup.php`. -- `config_insert`: The primary hook used to capture data changes. -- `is_console_page`: Determines if the plugin page is part of the console. - -### 5. SIEM / File Logging -The plugin supports writing audit logs to an external file (JSON format) for SIEM ingestion. -- **Configuration**: Controlled by `audit_log_external` (on/off) and `audit_log_external_path` settings in `setup.php`. -- **Implementation**: Logic resides in `audit_functions.php`. It writes JSON-encoded events to the specified file. -- **Permissions**: Ensure the web server user has write permissions to the target directory. - -## Common Workflows - -### Installation/Upgrade -- The plugin resides in `plugins/audit/`. -- Version changes in `INFO` trigger `audit_check_upgrade()` in `setup.php`. -- Always increment the version in `INFO` and `setup.php` when making schema changes. - -### Localization -- Run `locales/build_gettext.sh` to regenerate `.pot` and `.mo` files after adding new translatable strings. -- Domain must always be `'audit'`. - -## Clean as you Code & Refactoring Opportunities -When touching existing code, look for opportunities to improve quality: - -1. **N+1 Query Optimization**: - - **Issue**: `audit_process_page_data` often loops through `$selected_items` and executes a SQL query for *each* item. - - **Refactor**: Aggregate IDs and use `WHERE id IN (?, ?, ...)` to fetch all data in a single query. - -2. **Modern File Operations**: - - **Issue**: Usage of `fopen`/`fwrite`/`fclose`. - - **Refactor**: Use `file_put_contents()` with `FILE_APPEND` and `LOCK_EX` flags for atomic, cleaner file writing. - -3. **Switch Statement Complexity**: - - **Issue**: `audit_process_page_data` contains a massive switch statement. - - **Refactor**: Consider extracting case logic into separate handler functions or a map-based strategy to improve readability. - -## Directory Structure -- `setup.php`: Plugin registration and hooks. -- `audit.php`: Main UI file. -- `audit_functions.php`: Helper functions and logic. -- `locales/`: Translation files. -- `INFO`: Plugin metadata (version, author, etc.). diff --git a/.github/ISSUE_TEMPLATE/opilot-instructions.md b/.github/ISSUE_TEMPLATE/opilot-instructions.md deleted file mode 100644 index c658193..0000000 --- a/.github/ISSUE_TEMPLATE/opilot-instructions.md +++ /dev/null @@ -1,80 +0,0 @@ -# Cacti Audit Plugin Development Instructions - -## Project Overview -This is a Cacti plugin designed to audit user activities, including configuration changes and CLI commands. It hooks into Cacti's core to capture `$_POST` data and logs it to the `audit_log` table. - -## Architecture & Core Components -- **Plugin Entry (`setup.php`)**: Registers hooks, realms, and handles installation/upgrades. -- **Audit Logic (`audit_functions.php`)**: Contains `audit_process_page_data` which resolves object IDs to human-readable names for specific Cacti pages. -- **UI (`audit.php`)**: The main interface for viewing audit logs. -- **Database**: Uses the `audit_log` table. Schema updates are handled in `audit_check_upgrade()` within `setup.php`. - -## Key Development Patterns - -### 1. Extending Audit Coverage -To add detailed auditing for a new Cacti page (e.g., resolving an ID to a name), modify `audit_process_page_data` in `audit_functions.php`. - -**Pattern:** -```php -case 'your_page.php': - foreach ($selected_items as $item) { - // Fetch descriptive data for the item ID - $objects[] = db_fetch_assoc_prepared('SELECT name FROM your_table WHERE id = ?', array($item)); - } - break; -``` - -### 2. Database Interaction -Always use Cacti's database wrapper functions. **Never** use raw PHP MySQL functions. -- `db_fetch_assoc_prepared($sql, $params)`: For fetching multiple rows. -- `db_fetch_row_prepared($sql, $params)`: For fetching a single row. -- `db_fetch_cell_prepared($sql, $params)`: For fetching a single value. -- `db_execute_prepared($sql, $params)`: For INSERT/UPDATE/DELETE. - -### 3. Input Handling & Security -- Use `get_request_var('var_name')` or `get_filter_request_var('var_name')` to retrieve `$_GET`/`$_POST` data. -- Ensure all user-facing strings are localized using `__('String', 'audit')`. - -### 4. Plugin Hooks -Hooks are registered in `plugin_audit_install()` in `setup.php`. -- `config_insert`: The primary hook used to capture data changes. -- `is_console_page`: Determines if the plugin page is part of the console. - -### 5. SIEM / File Logging -The plugin supports writing audit logs to an external file (JSON format) for SIEM ingestion. -- **Configuration**: Controlled by `audit_log_external` (on/off) and `audit_log_external_path` settings in `setup.php`. -- **Implementation**: Logic resides in `audit_functions.php`. It writes JSON-encoded events to the specified file. -- **Permissions**: Ensure the web server user has write permissions to the target directory. - -## Common Workflows - -### Installation/Upgrade -- The plugin resides in `plugins/audit/`. -- Version changes in `INFO` trigger `audit_check_upgrade()` in `setup.php`. -- Always increment the version in `INFO` and `setup.php` when making schema changes. - -### Localization -- Run `locales/build_gettext.sh` to regenerate `.pot` and `.mo` files after adding new translatable strings. -- Domain must always be `'audit'`. - -## Clean as you Code & Refactoring Opportunities -When touching existing code, look for opportunities to improve quality: - -1. **N+1 Query Optimization**: - - **Issue**: `audit_process_page_data` often loops through `$selected_items` and executes a SQL query for *each* item. - - **Refactor**: Aggregate IDs and use `WHERE id IN (?, ?, ...)` to fetch all data in a single query. - -2. **Modern File Operations**: - - **Issue**: Usage of `fopen`/`fwrite`/`fclose`. - - **Refactor**: Use `file_put_contents()` with `FILE_APPEND` and `LOCK_EX` flags for atomic, cleaner file writing. - -3. **Switch Statement Complexity**: - - **Issue**: `audit_process_page_data` contains a massive switch statement. - - **Refactor**: Consider extracting case logic into separate handler functions or a map-based strategy to improve readability. - -## Directory Structure -- `setup.php`: Plugin registration and hooks. -- `audit.php`: Main UI file. -- `audit_functions.php`: Helper functions and logic. -- `locales/`: Translation files. -- `INFO`: Plugin metadata (version, author, etc.). diff --git a/.github/copilot-instructions.md b/.github/instructions/copilot-instructions.md similarity index 88% rename from .github/copilot-instructions.md rename to .github/instructions/copilot-instructions.md index d06768b..c930819 100644 --- a/.github/copilot-instructions.md +++ b/.github/instructions/copilot-instructions.md @@ -6,13 +6,15 @@ This is a Cacti plugin that logs GUI and CLI activities to an audit trail. The p **Core Components:** - [`setup.php`](../setup.php): Plugin lifecycle (install/uninstall/upgrade) and hook registration via `api_plugin_register_hook()` - [`audit.php`](../audit.php): Web UI for viewing/exporting/purging audit logs; handles actions via `switch(get_request_var('action'))` -- [`audit_functions.php`](../audit_functions.php): Core logging logic in `audit_config_insert()` and page-specific data extraction in `audit_process_page_data()` + - [`audit_functions.php`](../audit_functions.php): Core logging logic in `auditConfigInsert()` and page-specific data extraction in `auditProcessPageData()` - Database: Single `audit_log` table with columns: `page`, `user_id`, `action`, `ip_address`, `user_agent`, `event_time`, `post` (JSON), `object_data` (JSON) +**Standards:** This project enforces PSR-12 coding standards (use camelCase for function and method names) and requires PHP 8.1 or newer. + **Data Flow:** -1. Cacti triggers `config_insert` hook on POST requests → `audit_config_insert()` executes +1. Cacti triggers `config_insert` hook on POST requests → `auditConfigInsert()` executes 2. Function validates event via `audit_log_valid_event()`, sanitizes `$_POST`, removes passwords -3. If `selected_items` present, `audit_process_page_data()` extracts object details from DB +3. If `selected_items` present, `auditProcessPageData()` extracts object details from DB 4. Event logged to `audit_log` table + optional external JSON file ## Critical Conventions @@ -95,6 +97,6 @@ Hooks registered in `plugin_audit_install()`: ## Key Files Reference - [`setup.php`](../setup.php) - Hook registration, table schema, upgrade logic - [`audit.php`](../audit.php) - UI controller with export/purge/getdata actions -- [`audit_functions.php`](../audit_functions.php) - `audit_config_insert()` (main logger), `audit_process_page_data()` (extract object details) +- [`audit_functions.php`](../audit_functions.php) - `auditConfigInsert()` (main logger), `auditProcessPageData()` (extract object details) - [`locales/build_gettext.sh`](../locales/build_gettext.sh) - Translation builder - [`.github/workflows/plugin-ci-workflow.yml`](../.github/workflows/plugin-ci-workflow.yml) - Integration tests From 29a72326acdea76c64e51d571f48b1561a3465fc Mon Sep 17 00:00:00 2001 From: Sean Mancini Date: Fri, 27 Feb 2026 19:59:42 -0500 Subject: [PATCH 02/12] rename functions to PSR12 standards --- audit_functions.php | 9 +++++---- setup.php | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/audit_functions.php b/audit_functions.php index 6a93705..075a770 100644 --- a/audit_functions.php +++ b/audit_functions.php @@ -1,6 +1,6 @@ Date: Fri, 27 Feb 2026 22:55:37 -0500 Subject: [PATCH 03/12] rename functions for PSR12 compatability --- audit.php | 22 +++++++++--------- audit_functions.php | 2 +- setup.php | 56 ++++++++++++++++++++++----------------------- 3 files changed, 40 insertions(+), 40 deletions(-) diff --git a/audit.php b/audit.php index dffed41..b98f86c 100644 --- a/audit.php +++ b/audit.php @@ -29,14 +29,14 @@ switch(get_request_var('action')) { case 'export': - audit_export_rows(); + auditExportRows(); break; case 'purge': - audit_purge(); + auditPurge(); top_header(); - audit_log(); + auditLog(); bottom_footer(); break; @@ -133,11 +133,11 @@ break; default: top_header(); - audit_log(); + auditLog(); bottom_footer(); } -function audit_purge() { +function auditPurge() { db_execute('TRUNCATE TABLE audit_log'); $_SESSION['audit_message'] = __('Audit Log Purged by %s', get_username($_SESSION['sess_user_id']), 'audit'); @@ -147,8 +147,8 @@ function audit_purge() { raise_message('audit_message'); } -function audit_export_rows() { - process_request_vars(); +function auditExportRows() { + processRequestVars(); /* form the 'where' clause for our main sql query */ if (get_request_var('filter') != '') { @@ -202,13 +202,13 @@ function audit_export_rows() { } } -function audit_csv_escape($string) { +function auditCsvEscape($string) { $string = str_replace('"', '', $string); $string = str_replace(',', '|', $string); return $string; } -function process_request_vars() { +function processRequestVars() { /* ================= input validation and session storage ================= */ $filters = array( 'rows' => array( @@ -252,10 +252,10 @@ function process_request_vars() { /* ================= input validation ================= */ } -function audit_log() { +function auditLog() { global $item_rows; - process_request_vars(); + processRequestVars(); if (get_request_var('rows') == '-1') { $rows = read_config_option('num_rows_table'); diff --git a/audit_functions.php b/audit_functions.php index 075a770..3611ec6 100644 --- a/audit_functions.php +++ b/audit_functions.php @@ -126,7 +126,7 @@ function auditProcessPageData($page, $drop_action, $selected_items) { function auditConfigInsert() { global $action, $config; - if (audit_log_valid_event()) { + if (auditLogValidEvent()) { /* prepare post */ $post = $_REQUEST; diff --git a/setup.php b/setup.php index a62b83d..91ed8df 100644 --- a/setup.php +++ b/setup.php @@ -24,29 +24,29 @@ include_once('audit_functions.php'); -function plugin_audit_install() { - api_plugin_register_hook('audit', 'config_arrays', 'audit_config_arrays', 'setup.php'); - api_plugin_register_hook('audit', 'config_settings', 'audit_config_settings', 'setup.php'); +function pluginAuditInstall() { + api_plugin_register_hook('audit', 'config_arrays', 'auditConfigArrays', 'setup.php'); + api_plugin_register_hook('audit', 'config_settings', 'auditConfigSettings', 'setup.php'); api_plugin_register_hook('audit', 'config_insert', 'auditConfigInsert', 'setup.php'); - api_plugin_register_hook('audit', 'poller_bottom', 'audit_poller_bottom', 'setup.php'); - api_plugin_register_hook('audit', 'draw_navigation_text', 'audit_draw_navigation_text', 'setup.php'); - api_plugin_register_hook('audit', 'utilities_array', 'audit_utilities_array', 'setup.php'); - api_plugin_register_hook('audit', 'is_console_page', 'audit_is_console_page', 'setup.php'); + api_plugin_register_hook('audit', 'poller_bottom', 'auditPollerBottom', 'setup.php'); + api_plugin_register_hook('audit', 'draw_navigation_text', 'auditDrawNavigationText', 'setup.php'); + api_plugin_register_hook('audit', 'utilities_array', 'auditUtilitiesArray', 'setup.php'); + api_plugin_register_hook('audit', 'is_console_page', 'auditIsConsolePage', 'setup.php'); /* hook for table replication */ - api_plugin_register_hook('audit', 'replicate_out', 'audit_replicate_out', 'setup.php'); + api_plugin_register_hook('audit', 'replicate_out', 'auditReplicateOut', 'setup.php'); api_plugin_register_realm('audit', 'audit.php', __('View Cacti Audit Log', 'audit'), 1); - audit_setup_table(); + auditSetupTable(); } -function plugin_audit_uninstall() { +function pluginAuditUninstall() { db_execute('DROP TABLE IF EXISTS audit_log'); return true; } -function audit_is_console_page($url) { +function auditIsConsolePage($url) { if (strpos($url, 'audit.php') !== false) { return true; } @@ -54,15 +54,15 @@ function audit_is_console_page($url) { return false; } -function plugin_audit_check_config() { +function pluginAuditCheckConfig() { return true; } -function plugin_audit_upgrade() { +function pluginAuditUpgrade() { return true; } -function audit_check_upgrade() { +function auditCheckUpgrade() { global $config, $database_default; include_once($config['library_path'] . '/database.php'); include_once($config['library_path'] . '/functions.php'); @@ -72,7 +72,7 @@ function audit_check_upgrade() { return; } - $info = plugin_audit_version(); + $info = pluginAuditVersion(); $current = $info['version']; $old = db_fetch_cell("SELECT version FROM plugin_config WHERE directory='audit'"); if ($current != $old) { @@ -95,12 +95,12 @@ function audit_check_upgrade() { WHERE directory='" . $info['name'] . "' "); /* hook for table replication */ - api_plugin_register_hook('audit', 'replicate_out', 'audit_replicate_out', 'setup.php', '1'); - api_plugin_register_hook('audit', 'is_console_page', 'audit_is_console_page', 'setup.php', 1); + api_plugin_register_hook('audit', 'replicate_out', 'auditReplicateOut', 'setup.php', '1'); + api_plugin_register_hook('audit', 'is_console_page', 'auditIsConsolePage', 'setup.php', 1); } } -function audit_check_dependencies($data) { +function auditCheckDependencies($data) { $remote_poller_id = $data['remote_poller_id']; $rcnn_id = $data['rcnn_id']; $class = $data['class']; @@ -116,7 +116,7 @@ function audit_check_dependencies($data) { return $data; } -function audit_replicate_out($data) { +function auditReplicateOut($data) { $remote_poller_id = $data['remote_poller_id']; $rcnn_id = $data['rcnn_id']; $class = $data['class']; @@ -145,7 +145,7 @@ function audit_replicate_out($data) { return $data; } -function audit_poller_bottom() { +function auditPollerBottom() { $last_check = read_config_option('audit_last_check'); $now = date('d'); @@ -163,7 +163,7 @@ function audit_poller_bottom() { set_config_option('audit_last_check', $now); } -function audit_setup_table() { +function auditSetupTable() { global $config, $database_default; include_once($config['library_path'] . '/database.php'); @@ -189,13 +189,13 @@ function audit_setup_table() { return true; } -function plugin_audit_version() { +function pluginAuditVersion() { global $config; $info = parse_ini_file($config['base_path'] . '/plugins/audit/INFO', true); return $info['info']; } -function audit_log_valid_event() { +function auditLogValidEvent() { global $action; $valid = false; @@ -228,7 +228,7 @@ function audit_log_valid_event() { return $valid; } -function audit_utilities_array() { +function auditUtilitiesArray() { global $utilities; if (version_compare(CACTI_VERSION, '1.3.0', '<')) { @@ -246,7 +246,7 @@ function audit_utilities_array() { } } -function audit_config_arrays() { +function auditConfigArrays() { global $menu, $messages, $audit_retentions, $utilities; if (isset($_SESSION['audit_message']) && $_SESSION['audit_message'] != '') { @@ -272,10 +272,10 @@ function audit_config_arrays() { auth_augment_roles(__('System Administration'), array('audit.php')); } - audit_check_upgrade(); + auditCheckUpgrade(); } -function audit_config_settings() { +function auditConfigSettings() { global $tabs, $settings, $item_rows, $audit_retentions; $temp = array( @@ -320,7 +320,7 @@ function audit_config_settings() { } } -function audit_draw_navigation_text($nav) { +function auditDrawNavigationText($nav) { $nav['audit.php:'] = array( 'title' => __('Audit Event Log', 'audit'), 'mapping' => 'index.php:', From c194803bdd82045d16b5f7638f2879511ff20341 Mon Sep 17 00:00:00 2001 From: Sean Mancini Date: Fri, 27 Feb 2026 23:03:37 -0500 Subject: [PATCH 04/12] Breakdown complex functions Reduce cognetive complexity following PSR12 and Sonarqube --- audit.php | 335 ++++++++++++++++----------------- audit_functions.php | 439 ++++++++++++++++++++------------------------ 2 files changed, 368 insertions(+), 406 deletions(-) diff --git a/audit.php b/audit.php index b98f86c..587ef5e 100644 --- a/audit.php +++ b/audit.php @@ -147,124 +147,46 @@ function auditPurge() { raise_message('audit_message'); } -function auditExportRows() { - processRequestVars(); +function auditBuildSqlWhereClause() { + $sql_where = ''; - /* form the 'where' clause for our main sql query */ if (get_request_var('filter') != '') { $sql_where = 'WHERE ( page LIKE ' . db_qstr('%' . get_request_var('filter') . '%') . ' OR post LIKE ' . db_qstr('%' . get_request_var('filter') . '%') . ')'; - } else { - $sql_where = ''; } if (get_request_var('event_page') != '-1') { - $sql_where .= ($sql_where != '' ? ' AND ':'WHERE ') . ' page = ' . db_qstr(get_request_var('event_page')); + $sql_where .= ($sql_where != '' ? ' AND ' : 'WHERE ') . ' page = ' . db_qstr(get_request_var('event_page')); } if (!isempty_request_var('user_id') && get_request_var('user_id') > '-1') { - $sql_where .= ($sql_where != '' ? ' AND ':'WHERE ') . ' user_id = ' . get_request_var('user_id'); + $sql_where .= ($sql_where != '' ? ' AND ' : 'WHERE ') . ' user_id = ' . get_request_var('user_id'); } - $events = db_fetch_assoc("SELECT audit_log.*, user_auth.username - FROM audit_log - LEFT JOIN user_auth - ON audit_log.user_id=user_auth.id - $sql_where"); - - if (cacti_sizeof($events)) { - header('Content-Disposition: attachment; filename=audit_export.csv'); + return $sql_where; +} - print __x('Column Header used for CSV log export. Ensure that you do NOT(!) remove one of the commas. The output needs to be CSV compliant.','page, user_id, username, action, ip_address, user_agent, event_time, post', 'audit') . "\n"; +function auditBuildPosterString($post_payload) { + $post = json_decode($post_payload); + $poster = ''; - foreach($events as $event) { - $post = json_decode($event['post']); - $poster = ''; - foreach($post as $var => $value) { - if (is_array($value)) { - $poster .= ($poster != '' ? '|':'') . $var . ':' . implode('%', $value); - } else { - $poster .= ($poster != '' ? '|':'') . $var . ':' . $value; - } - } + if (!is_object($post)) { + return $poster; + } - print - $event['page'] . ', ' . - $event['user_id'] . ', ' . - get_username($event['user_id']) . ', ' . - $event['action'] . ', ' . - $event['ip_address'] . ', ' . - $event['user_agent'] . ', ' . - $event['event_time'] . ', ' . - $poster . "\n"; + foreach ($post as $var => $value) { + if (is_array($value)) { + $poster .= ($poster != '' ? '|' : '') . $var . ':' . implode('%', $value); + } else { + $poster .= ($poster != '' ? '|' : '') . $var . ':' . $value; } } -} - -function auditCsvEscape($string) { - $string = str_replace('"', '', $string); - $string = str_replace(',', '|', $string); - return $string; -} - -function processRequestVars() { - /* ================= input validation and session storage ================= */ - $filters = array( - 'rows' => array( - 'filter' => FILTER_VALIDATE_INT, - 'pageset' => true, - 'default' => '-1' - ), - 'page' => array( - 'filter' => FILTER_VALIDATE_INT, - 'default' => '1' - ), - 'filter' => array( - 'filter' => FILTER_DEFAULT, - 'pageset' => true, - 'default' => '' - ), - 'sort_column' => array( - 'filter' => FILTER_CALLBACK, - 'default' => 'event_time', - 'options' => array('options' => 'sanitize_search_string') - ), - 'sort_direction' => array( - 'filter' => FILTER_CALLBACK, - 'default' => 'DESC', - 'options' => array('options' => 'sanitize_search_string') - ), - 'user_id' => array( - 'filter' => FILTER_VALIDATE_INT, - 'pageset' => true, - 'default' => '-1' - ), - 'event_page' => array( - 'filter' => FILTER_CALLBACK, - 'pageset' => true, - 'default' => '-1', - 'options' => array('options' => 'sanitize_search_string') - ) - ); - validate_store_request_vars($filters, 'sess_audit'); - /* ================= input validation ================= */ + return $poster; } -function auditLog() { - global $item_rows; - - processRequestVars(); - - if (get_request_var('rows') == '-1') { - $rows = read_config_option('num_rows_table'); - } else { - $rows = get_request_var('rows'); - } - - html_start_box(__('Audit Log', 'audit'), '100%', '', '3', 'center', ''); - +function auditRenderFilterForm($item_rows) { ?> @@ -341,51 +263,39 @@ function auditLog() { " . __('No Audit Log Events Found', 'audit') . "\n"; + return; } - if (!isempty_request_var('user_id') && get_request_var('user_id') > '-1') { - $sql_where .= ($sql_where != '' ? ' AND ':'WHERE ') . ' user_id = ' . get_request_var('user_id'); + foreach ($events as $e) { + if ($e['action'] == 'cli') { + form_alternate_row('line' . $e['id'], false); + form_selectable_cell($e['page'], $e['id']); + form_selectable_cell($e['user_agent'], $e['id']); + form_selectable_cell('' . ucfirst($e['action']) . '', $e['id']); + form_selectable_cell(__('N/A', 'audit'), $e['id']); + form_selectable_cell($e['ip_address'], $e['id'], '', 'right'); + form_selectable_cell($e['event_time'], $e['id'], '', 'right'); + form_end_row(); + } else { + form_alternate_row('line' . $e['id'], false); + form_selectable_cell(filter_value($e['page'], get_request_var('filter')), $e['id']); + form_selectable_cell($e['username'], $e['id']); + form_selectable_cell('' . ucfirst($e['action']) . '', $e['id']); + form_selectable_cell($e['user_agent'], $e['id']); + form_selectable_cell($e['ip_address'], $e['id'], '', 'right'); + form_selectable_cell($e['event_time'], $e['id'], '', 'right'); + form_end_row(); + } } +} - $total_rows = db_fetch_cell("SELECT - COUNT(*) - FROM audit_log - LEFT JOIN user_auth - ON audit_log.user_id=user_auth.id - $sql_where"); - - $sql_order = get_order_string(); - $sql_limit = ' LIMIT ' . ($rows*(get_request_var('page')-1)) . ',' . $rows; - - $events = db_fetch_assoc("SELECT audit_log.*, user_auth.username - FROM audit_log - LEFT JOIN user_auth - ON audit_log.user_id=user_auth.id - $sql_where - $sql_order - $sql_limit"); - - $nav = html_nav_bar('audit.php?filter=' . get_request_var('filter'), MAX_DISPLAY_PAGES, get_request_var('page'), $rows, $total_rows, 5, __('Audit Events', 'audit'), 'page', 'main'); - - print $nav; - - html_start_box('', '100%', '', '3', 'center', ''); - - $display_text = array( +function auditGetDisplayText() { + return array( 'page' => array( 'display' => __('Page Name', 'audit'), 'align' => 'left', @@ -404,54 +314,150 @@ function auditLog() { 'sort' => 'ASC', 'tip' => __('The Cacti Action requested. Hover over action to see $_POST data.', 'audit') ), - 'user_agent' => array( + 'user_agent' => array( 'display' => __('User Agent', 'audit'), 'align' => 'left', 'sort' => 'ASC', 'tip' => __('The browser type of the requester.', 'audit') ), - 'ip_address' => array( + 'ip_address' => array( 'display' => __('IP Address', 'audit'), 'align' => 'right', 'sort' => 'ASC', 'tip' => __('The IP Address of the requester.', 'audit') ), - 'event_time' => array( + 'event_time' => array( 'display' => __('Event Time', 'audit'), 'align' => 'right', 'sort' => 'DESC', 'tip' => __('The time the Event took place.', 'audit') ) ); +} - html_header_sort($display_text, get_request_var('sort_column'), get_request_var('sort_direction'), false); +function auditExportRows() { + processRequestVars(); + $sql_where = auditBuildSqlWhereClause(); + + $events = db_fetch_assoc("SELECT audit_log.*, user_auth.username + FROM audit_log + LEFT JOIN user_auth + ON audit_log.user_id=user_auth.id + $sql_where"); - $i = 0; if (cacti_sizeof($events)) { - foreach ($events as $e) { - if ($e['action'] == 'cli') { - form_alternate_row('line' . $e['id'], false); - form_selectable_cell($e['page'], $e['id']); - form_selectable_cell($e['user_agent'], $e['id']); - form_selectable_cell('' . ucfirst($e['action']) . '', $e['id']); - form_selectable_cell(__('N/A', 'audit'), $e['id']); - form_selectable_cell($e['ip_address'], $e['id'], '', 'right'); - form_selectable_cell($e['event_time'], $e['id'], '', 'right'); - form_end_row(); - } else { - form_alternate_row('line' . $e['id'], false); - form_selectable_cell(filter_value($e['page'], get_request_var('filter')), $e['id']); - form_selectable_cell($e['username'], $e['id']); - form_selectable_cell('' . ucfirst($e['action']) . '', $e['id']); - form_selectable_cell($e['user_agent'], $e['id']); - form_selectable_cell($e['ip_address'], $e['id'], '', 'right'); - form_selectable_cell($e['event_time'], $e['id'], '', 'right'); - form_end_row(); - } + header('Content-Disposition: attachment; filename=audit_export.csv'); + + print __x('Column Header used for CSV log export. Ensure that you do NOT(!) remove one of the commas. The output needs to be CSV compliant.','page, user_id, username, action, ip_address, user_agent, event_time, post', 'audit') . "\n"; + + foreach($events as $event) { + $poster = auditBuildPosterString($event['post']); + + print + $event['page'] . ', ' . + $event['user_id'] . ', ' . + get_username($event['user_id']) . ', ' . + $event['action'] . ', ' . + $event['ip_address'] . ', ' . + $event['user_agent'] . ', ' . + $event['event_time'] . ', ' . + $poster . "\n"; } - } else { - print "" . __('No Audit Log Events Found', 'audit') . "\n"; } +} + +function auditCsvEscape($string) { + $string = str_replace('"', '', $string); + $string = str_replace(',', '|', $string); + return $string; +} + +function processRequestVars() { + /* ================= input validation and session storage ================= */ + $filters = array( + 'rows' => array( + 'filter' => FILTER_VALIDATE_INT, + 'pageset' => true, + 'default' => '-1' + ), + 'page' => array( + 'filter' => FILTER_VALIDATE_INT, + 'default' => '1' + ), + 'filter' => array( + 'filter' => FILTER_DEFAULT, + 'pageset' => true, + 'default' => '' + ), + 'sort_column' => array( + 'filter' => FILTER_CALLBACK, + 'default' => 'event_time', + 'options' => array('options' => 'sanitize_search_string') + ), + 'sort_direction' => array( + 'filter' => FILTER_CALLBACK, + 'default' => 'DESC', + 'options' => array('options' => 'sanitize_search_string') + ), + 'user_id' => array( + 'filter' => FILTER_VALIDATE_INT, + 'pageset' => true, + 'default' => '-1' + ), + 'event_page' => array( + 'filter' => FILTER_CALLBACK, + 'pageset' => true, + 'default' => '-1', + 'options' => array('options' => 'sanitize_search_string') + ) + ); + + validate_store_request_vars($filters, 'sess_audit'); + /* ================= input validation ================= */ +} + +function auditLog() { + global $item_rows; + + processRequestVars(); + + $rows = (get_request_var('rows') == '-1') ? read_config_option('num_rows_table') : get_request_var('rows'); + + html_start_box(__('Audit Log', 'audit'), '100%', '', '3', 'center', ''); + auditRenderFilterForm($item_rows); + + html_end_box(); + + $sql_where = auditBuildSqlWhereClause(); + + $total_rows = db_fetch_cell("SELECT + COUNT(*) + FROM audit_log + LEFT JOIN user_auth + ON audit_log.user_id=user_auth.id + $sql_where"); + + $sql_order = get_order_string(); + $sql_limit = ' LIMIT ' . ($rows*(get_request_var('page')-1)) . ',' . $rows; + + $events = db_fetch_assoc("SELECT audit_log.*, user_auth.username + FROM audit_log + LEFT JOIN user_auth + ON audit_log.user_id=user_auth.id + $sql_where + $sql_order + $sql_limit"); + + $nav = html_nav_bar('audit.php?filter=' . get_request_var('filter'), MAX_DISPLAY_PAGES, get_request_var('page'), $rows, $total_rows, 5, __('Audit Events', 'audit'), 'page', 'main'); + + print $nav; + + html_start_box('', '100%', '', '3', 'center', ''); + + $display_text = auditGetDisplayText(); + + html_header_sort($display_text, get_request_var('sort_column'), get_request_var('sort_direction'), false); + auditRenderEventsRows($events); html_end_box(false); @@ -463,4 +469,3 @@ function auditLog() { 'SELECT id AS host_id,site_id,description,hostname,status,status_fail_date AS last_failed_date,status_rec_date AS last_recovered_date FROM host WHERE id IN (?)', + 'host_templates.php' => 'SELECT name FROM host_template WHERE id IN (?)', + 'templates_export.php' => 'SELECT name FROM graph_templates WHERE id IN (?)', + 'automation_devices.php' => 'SELECT id, network_id,hostname,ip,sysName,syslocation,snmp,up FROM automation_devices WHERE id IN (?)', + 'graph_templates.php' => 'SELECT name FROM graph_templates WHERE id IN (?)', + 'thold.php' => 'SELECT id,name_cache AS THOLD_NAME,data_source_name AS Data_Source FROM thold_data WHERE id IN (?)', + 'data_sources.php' => 'SELECT name_cache AS Data_Source_Name,active FROM data_template_data WHERE local_data_id IN (?)', + 'data_templates.php' => 'SELECT name FROM data_template WHERE id IN (?)', + 'aggregate_templates.php' => 'SELECT name FROM aggregate_graph_template WHERE id IN (?)', + 'thold_templates.php' => 'SELECT name FROM thold_template WHERE id IN (?)', + 'user_admin.php' => 'SELECT username FROM user_auth WHERE id IN (?)', + 'user_group_admin.php' => 'SELECT name FROM user_auth_group WHERE id IN (?)' + ); +} + +function auditTransformAutomationDevices($result) { + foreach ($result as &$row) { + $row['snmp'] = ($row['snmp'] == 1) ? 'UP' : 'Down'; + $row['up'] = ($row['up'] == 1) ? 'Yes' : 'No'; + } + + return $result; +} + function auditProcessPageData($page, $drop_action, $selected_items) { + if ($drop_action === false) { + return json_encode(array()); + } + + $query_map = auditBuildPageQueryMap(); + if (!isset($query_map[$page])) { + return json_encode(array()); + } + $objects = array(); - if ($drop_action !== false) { - switch ($page) { - case 'host.php': - //loop over array and perform query for each item - foreach ($selected_items as $item) { - $objects[] = db_fetch_assoc_prepared('SELECT id AS host_id,site_id,description,hostname,status,status_fail_date AS last_failed_date,status_rec_date AS last_recovered_date - FROM host - WHERE id IN (?)', - array($item)); - } - break; - case 'host_templates.php': - foreach ($selected_items as $item) { - $objects[] = db_fetch_assoc_prepared('SELECT name - FROM host_template - WHERE id IN (?)', - array($item)); - } - break; - - case 'templates_export.php': - foreach ($selected_items as $item) { - $objects[] = db_fetch_assoc_prepared('SELECT name FROM graph_templates - WHERE id IN (?)', - array($item)); - } - break; - - - case 'automation_devices.php': - foreach ($selected_items as $item) { - $result = db_fetch_assoc_prepared('SELECT id, network_id,hostname,ip,sysName,syslocation,snmp,up - FROM automation_devices - WHERE id IN (?)', - array($item)); - - foreach ($result as &$row) { - $row['snmp'] = ($row['snmp'] == 1) ? 'UP' : 'Down'; - $row['up'] = ($row['up'] == 1) ? 'Yes' : 'No'; - } - - $objects[] = $result; - } - break; - - - case 'graph_templates.php': - foreach ($selected_items as $item) { - $objects[] = db_fetch_assoc_prepared('SELECT name - FROM graph_templates - WHERE id IN (?)', - array($item)); - } - break; - - case 'thold.php': - foreach ($selected_items as $item) { - $objects[] = db_fetch_assoc_prepared('SELECT id,name_cache AS THOLD_NAME,data_source_name AS Data_Source - FROM thold_data - WHERE id IN (?)', - array($item)); - } - break; - case 'data_sources.php': - foreach ($selected_items as $item) { - $objects[] = db_fetch_assoc_prepared('select name_cache AS Data_Source_Name,active from data_template_data - WHERE local_data_id IN (?)', - array($item)); - } - break; - - case 'data_templates.php': - foreach ($selected_items as $item) { - $objects[] = db_fetch_assoc_prepared('SELECT name - FROM data_template - WHERE id IN (?)', - array($item)); - } - break; - - case 'aggregate_templates.php': - foreach ($selected_items as $item) { - $objects[] = db_fetch_assoc_prepared('SELECT name - FROM aggregate_graph_template - WHERE id IN (?)', - array($item)); - } - break; - - case 'thold_templates.php': - foreach ($selected_items as $item) { - $objects[] = db_fetch_assoc_prepared('SELECT name - FROM thold_template - WHERE id IN (?)', - array($item)); - } - break; - case 'user_admin.php': - foreach ($selected_items as $item) { - $objects[] = db_fetch_assoc_prepared('SELECT username - FROM user_auth - WHERE id IN (?)', - array($item)); - } - break; - case 'user_group_admin.php': - foreach ($selected_items as $item) { - $objects[] = db_fetch_assoc_prepared('SELECT name - FROM user_auth_group - WHERE id IN (?)', - array($item)); - } - break; + foreach ($selected_items as $item) { + $result = db_fetch_assoc_prepared($query_map[$page], array($item)); + if ($page == 'automation_devices.php') { + $result = auditTransformAutomationDevices($result); } + + $objects[] = $result; } return json_encode($objects); } +function auditPrepareRequestPost(&$action) { + $post = $_REQUEST; + unset($post['__csrf_magic']); + unset($post['header']); + foreach ($post as $key => $value) { + if (preg_match('/pass|phrase/i', $key)) { + unset($post[$key]); + } + } -function auditConfigInsert() { - global $action, $config; + if (isset($post['drp_action']) && $post['drp_action'] == 1) { + $action = 'delete'; + } elseif (isset($post['drp_action']) && $post['drp_action'] == 4) { + $action = 'disable'; + } - if (auditLogValidEvent()) { - /* prepare post */ - $post = $_REQUEST; - - /* remove unsafe variables */ - unset($post['__csrf_magic']); - unset($post['header']); - foreach ($post as $key => $value) { - if (preg_match('/pass|phrase/i', $key)) { - unset($post[$key]); - } - } + return $post; +} - /* check if drp_action is present and update action accordingly */ - if (isset($post['drp_action']) && $post['drp_action'] == 1) { - $action = 'delete'; - } else if (isset($post['drp_action']) && $post['drp_action'] == 4) { - $action = 'disable'; - } +function auditGetSelectedItemsData($post) { + if (!isset($post['selected_items'])) { + return array(array(), false); + } - /* sanitize and serialize selected items */ - if (isset($post['selected_items'])) { - $selected_items = unserialize(stripslashes($post['selected_items']), array('allowed_classes' => false)); - $drop_action = $post['drp_action']; - } else { - $selected_items = array(); - $drop_action = false; - } + $selected_items = unserialize(stripslashes($post['selected_items']), array('allowed_classes' => false)); + $drop_action = isset($post['drp_action']) ? $post['drp_action'] : false; - $post = json_encode($post); - $page = basename($_SERVER['SCRIPT_NAME']); - $user_id = (isset($_SESSION['sess_user_id']) ? $_SESSION['sess_user_id'] : 0); - $event_time = date('Y-m-d H:i:s'); + return array($selected_items, $drop_action); +} - /* Retrieve IP address */ - $ip_address = get_client_addr(); +function auditGetBasePath($config) { + if (defined('CACTI_PATH_BASE')) { + return CACTI_PATH_BASE; + } - /* Get the User Agent */ - $user_agent = $_SERVER['HTTP_USER_AGENT']; + return $config['base_path']; +} - if (empty($action) && isset_request_var('action')) { - $action = get_nfilter_request_var('action'); - } elseif (empty($action)) { - $action = 'none'; - } +function auditResolveAction($page, $drop_action, $action) { + $action_map = array( + 'automation_devices.php' => array( + 2 => 'Delete Device', + 1 => 'Create Device' + ), + 'host.php' => array( + 2 => 'Host Enabled', + 3 => 'Host Disabled' + ) + ); + + if (isset($action_map[$page][$drop_action])) { + return $action_map[$page][$drop_action]; + } - $object_data = auditProcessPageData($page, $drop_action, $selected_items); - - switch ($page) { - case 'automation_devices.php': - switch ($drop_action) { - case 2: - $action = 'Delete Device'; - break; - case 1: - $action = 'Create Device'; - break; - } - - break; - case 'host.php': - switch ($drop_action) { - case 2: - $action = 'Host Enabled'; - break; - case 3: - $action = 'Host Disabled'; - break; - } - - break; - } + return $action; +} - $audit_log = read_config_option('audit_log_external_path'); +function auditBuildGuiEventData($config, &$action) { + $post = auditPrepareRequestPost($action); + list($selected_items, $drop_action) = auditGetSelectedItemsData($post); - if (!defined('CACTI_PATH_BASE')) { - $base = $config['base_path']; - } else { - $base = CACTI_PATH_BASE; - } + if (empty($action) && isset_request_var('action')) { + $action = get_nfilter_request_var('action'); + } elseif (empty($action)) { + $action = 'none'; + } - db_execute_prepared('INSERT INTO audit_log (page, user_id, action, ip_address, user_agent, event_time, post, object_data) - VALUES (?, ?, ?, ?, ?, ?, ?, ?)', - array($page, $user_id, $action, $ip_address, $user_agent, $event_time, $post, $object_data)); + $page = basename($_SERVER['SCRIPT_NAME']); + $action = auditResolveAction($page, $drop_action, $action); + + return array( + 'page' => $page, + 'user_id' => isset($_SESSION['sess_user_id']) ? $_SESSION['sess_user_id'] : 0, + 'action' => $action, + 'ip_address' => get_client_addr(), + 'user_agent' => $_SERVER['HTTP_USER_AGENT'], + 'event_time' => date('Y-m-d H:i:s'), + 'post' => json_encode($post), + 'object_data' => auditProcessPageData($page, $drop_action, $selected_items), + 'base_path' => auditGetBasePath($config) + ); +} - if ($audit_log == '') { - set_config_option('audit_log_external_path', $base . '/log/audit.log'); - $audit_log = $base . '/log/audit.log'; - } +function auditInsertGuiEvent($event) { + db_execute_prepared('INSERT INTO audit_log (page, user_id, action, ip_address, user_agent, event_time, post, object_data) + VALUES (?, ?, ?, ?, ?, ?, ?, ?)', + array($event['page'], $event['user_id'], $event['action'], $event['ip_address'], $event['user_agent'], $event['event_time'], $event['post'], $event['object_data'])); +} - if ($audit_log != '' && !file_exists($audit_log)) { - if (is_writable(dirname($audit_log))) { - cacti_log(sprintf('NOTE: The Audit Log file \'%s\' does not exist. Creating it.', $audit_log), false, 'AUDIT'); - touch($audit_log); - } else { - cacti_log(sprintf('ERROR: Audit Log file path \'%s\' does not exist and the path is not writeable.', $audit_log), false, 'AUDIT'); - } - } +function auditGetExternalLogPath($base_path) { + $audit_log = read_config_option('audit_log_external_path'); + if ($audit_log == '') { + $audit_log = $base_path . '/log/audit.log'; + set_config_option('audit_log_external_path', $audit_log); + } - if (read_config_option('audit_log_external') == 'on' && $audit_log != '' && file_exists($audit_log)) { - $log_data = array( - 'page' => $page, - 'user_id' => $user_id, - 'action' => $action, - 'ip_address' => $ip_address, - 'user_agent' => $user_agent, - 'event_time' => $event_time, - 'post' => $post, - 'object_data' => $object_data - ); - - $log_msg = json_encode($log_data) . "\n"; - $file = fopen($audit_log, 'a'); - - if ($file) { - fwrite($file, $log_msg); - fclose($file); - } - } - } elseif (isset($_SERVER['argv'])) { - $page = basename($_SERVER['argv'][0]); - $user_id = 0; - $action = 'cli'; - $ip_address = getHostByName(php_uname('n')); - $user_agent = get_current_user(); - $event_time = date('Y-m-d H:i:s'); - $post = implode(' ', $_SERVER['argv']); - - /* don't insert poller records */ - if (strpos($_SERVER['argv'][0], 'poller') === false && - strpos($_SERVER['argv'][0], 'cmd.php') === false && - strpos($_SERVER['argv'][0], '/scripts/') === false && - strpos($_SERVER['argv'][0], 'script_server.php') === false && - strpos($_SERVER['argv'][0], '_process.php') === false) { - - db_execute_prepared('INSERT INTO audit_log (page, user_id, action, ip_address, user_agent, event_time, post) - VALUES (?, ?, ?, ?, ?, ?, ?)', - array($page, $user_id, $action, $ip_address, $user_agent, $event_time, $post)); - } + return $audit_log; +} + +function auditEnsureExternalLogFile($audit_log) { + if ($audit_log == '' || file_exists($audit_log)) { + return; + } + + if (is_writable(dirname($audit_log))) { + cacti_log(sprintf('NOTE: The Audit Log file \'%s\' does not exist. Creating it.', $audit_log), false, 'AUDIT'); + touch($audit_log); + } else { + cacti_log(sprintf('ERROR: Audit Log file path \'%s\' does not exist and the path is not writeable.', $audit_log), false, 'AUDIT'); + } +} + +function auditWriteExternalLog($audit_log, $event) { + if (read_config_option('audit_log_external') != 'on' || $audit_log == '' || !file_exists($audit_log)) { + return; } + + $log_data = array( + 'page' => $event['page'], + 'user_id' => $event['user_id'], + 'action' => $event['action'], + 'ip_address' => $event['ip_address'], + 'user_agent' => $event['user_agent'], + 'event_time' => $event['event_time'], + 'post' => $event['post'], + 'object_data' => $event['object_data'] + ); + + $log_msg = json_encode($log_data) . "\n"; + $file = fopen($audit_log, 'a'); + if ($file) { + fwrite($file, $log_msg); + fclose($file); + } +} + +function auditInsertCliEvent() { + $page = basename($_SERVER['argv'][0]); + $user_id = 0; + $action = 'cli'; + $ip_address = getHostByName(php_uname('n')); + $user_agent = get_current_user(); + $event_time = date('Y-m-d H:i:s'); + $post = implode(' ', $_SERVER['argv']); + + if (strpos($_SERVER['argv'][0], 'poller') !== false || + strpos($_SERVER['argv'][0], 'cmd.php') !== false || + strpos($_SERVER['argv'][0], '/scripts/') !== false || + strpos($_SERVER['argv'][0], 'script_server.php') !== false || + strpos($_SERVER['argv'][0], '_process.php') !== false) { + return; + } + + db_execute_prepared('INSERT INTO audit_log (page, user_id, action, ip_address, user_agent, event_time, post) + VALUES (?, ?, ?, ?, ?, ?, ?)', + array($page, $user_id, $action, $ip_address, $user_agent, $event_time, $post)); } +function auditConfigInsert() { + global $action, $config; + + if (auditLogValidEvent()) { + $event = auditBuildGuiEventData($config, $action); + $audit_log = auditGetExternalLogPath($event['base_path']); + + auditInsertGuiEvent($event); + auditEnsureExternalLogFile($audit_log); + auditWriteExternalLog($audit_log, $event); + return; + } + + if (isset($_SERVER['argv'])) { + auditInsertCliEvent(); + } +} From 47a0767917aa617d7b65ada043cb4693a9aa61e3 Mon Sep 17 00:00:00 2001 From: Sean Mancini Date: Fri, 27 Feb 2026 23:07:41 -0500 Subject: [PATCH 05/12] Change from spaces to tabs per PSR12 --- audit.php | 764 ++++++++++++++++++++++---------------------- audit_functions.php | 352 ++++++++++---------- setup.php | 498 ++++++++++++++--------------- 3 files changed, 807 insertions(+), 807 deletions(-) diff --git a/audit.php b/audit.php index 587ef5e..ee200f2 100644 --- a/audit.php +++ b/audit.php @@ -23,449 +23,449 @@ */ chdir('../../'); -include_once('./include/auth.php'); +include_once './include/auth.php'; set_default_action(); switch(get_request_var('action')) { case 'export': - auditExportRows(); + auditExportRows(); - break; + break; case 'purge': - auditPurge(); - - top_header(); - auditLog(); - bottom_footer(); - - break; - case 'getdata': - $data = db_fetch_row_prepared('SELECT * - FROM audit_log - WHERE id = ?', - array(get_filter_request_var('id'))); - - $output = ''; - - if ($data['action'] == 'cli') { - $width = 'wide'; - $output .= '
'; - $output .= '' . __('Page:', 'audit') . ' ' . $data['page'] . ''; - $output .= '
' . __('User:', 'audit') . ' ' . $data['user_agent'] . ''; - $output .= '
' . __('IP Address:', 'audit') . ' ' . $data['ip_address'] . ''; - $output .= '
' . __('Date:', 'audit') . ' ' . $data['event_time'] . ''; - $output .= '
' . __('Action:', 'audit') . ' ' . $data['action'] . ''; - $output .= '
'; - $output .= '' . __('Script:', 'audit') . ' ' . $data['post'] . ''; - } elseif (cacti_sizeof($data)) { - $attribs = json_decode($data['post']); - - $nattribs = array(); - foreach($attribs as $field => $content) { - $nattribs[$field] = $content; - } - ksort($nattribs); - - if (cacti_sizeof($nattribs) > 16) { - $width = 'wide'; - } else { - $width = 'narrow'; - } - - $output .= ''; - $output .= ''; - - foreach ($recordData as $record) { - $output .= ''; - } - } else { - $output .= '
'; - $output .= '' . __('Page:', 'audit') . ' ' . $data['page'] . ''; - $output .= '
' . __('User:', 'audit') . ' ' . get_username($data['user_id']) . ''; - $output .= '
' . __('IP Address:', 'audit') . ' ' . $data['ip_address'] . ''; - $output .= '
' . __('Date:', 'audit') . ' ' . $data['event_time'] . ''; - $output .= '
' . __('Action:', 'audit') . ' ' . $data['action'] . ''; - $output .= '
'; - $output .= ''; - - if (cacti_sizeof($nattribs) > 16) { - $columns = 2; - $output .= ''; - } else { - $columns = 1; - $output .= ''; - } - - $i = 0; - if (cacti_sizeof($nattribs)) { - foreach($nattribs as $field => $content) { - if ($i % $columns == 0) { - $output .= ($output != '' ? '':'') . ''; - } - - if (is_array($content)) { - $output .= '' . implode(',', $content) . ''; - } else { - $output .= ''; - } - - $i++; - } - - if ($i % $columns > 0) { - $output . ''; - } - } - - // Display the Record Data under selected_items if it is not empty - $recordData = json_decode($data['object_data']); - if (!empty($recordData)) { - $output .= '
' . __('Attrib', 'audit') . '' . __('Value', 'audit') . '' . __('Attrib', 'audit') . '' . __('Value', 'audit') . '
' . __('Attrib', 'audit') . '' . __('Value', 'audit') . '
' . $field . '' . $field . '' . $content . '
'; - $output .= '

' . __('Record Data:', 'audit') . '
' . json_encode($record, JSON_PRETTY_PRINT) . '
'; - } - } - - // Output the final result - echo $output; - - - break; + auditPurge(); + + top_header(); + auditLog(); + bottom_footer(); + + break; + case 'getdata': + $data = db_fetch_row_prepared('SELECT * + FROM audit_log + WHERE id = ?', + array(get_filter_request_var('id'))); + + $output = ''; + + if ($data['action'] == 'cli') { + $width = 'wide'; + $output .= ' - - - + + + + \n"; - return; - } - - foreach ($events as $e) { - if ($e['action'] == 'cli') { - form_alternate_row('line' . $e['id'], false); - form_selectable_cell($e['page'], $e['id']); - form_selectable_cell($e['user_agent'], $e['id']); - form_selectable_cell('' . ucfirst($e['action']) . '', $e['id']); - form_selectable_cell(__('N/A', 'audit'), $e['id']); - form_selectable_cell($e['ip_address'], $e['id'], '', 'right'); - form_selectable_cell($e['event_time'], $e['id'], '', 'right'); - form_end_row(); - } else { - form_alternate_row('line' . $e['id'], false); - form_selectable_cell(filter_value($e['page'], get_request_var('filter')), $e['id']); - form_selectable_cell($e['username'], $e['id']); - form_selectable_cell('' . ucfirst($e['action']) . '', $e['id']); - form_selectable_cell($e['user_agent'], $e['id']); - form_selectable_cell($e['ip_address'], $e['id'], '', 'right'); - form_selectable_cell($e['event_time'], $e['id'], '', 'right'); - form_end_row(); - } - } + if (!cacti_sizeof($events)) { + print "\n"; + return; + } + + foreach ($events as $e) { + if ($e['action'] == 'cli') { + form_alternate_row('line' . $e['id'], false); + form_selectable_cell($e['page'], $e['id']); + form_selectable_cell($e['user_agent'], $e['id']); + form_selectable_cell('' . ucfirst($e['action']) . '', $e['id']); + form_selectable_cell(__('N/A', 'audit'), $e['id']); + form_selectable_cell($e['ip_address'], $e['id'], '', 'right'); + form_selectable_cell($e['event_time'], $e['id'], '', 'right'); + form_end_row(); + } else { + form_alternate_row('line' . $e['id'], false); + form_selectable_cell(filter_value($e['page'], get_request_var('filter')), $e['id']); + form_selectable_cell($e['username'], $e['id']); + form_selectable_cell('' . ucfirst($e['action']) . '', $e['id']); + form_selectable_cell($e['user_agent'], $e['id']); + form_selectable_cell($e['ip_address'], $e['id'], '', 'right'); + form_selectable_cell($e['event_time'], $e['id'], '', 'right'); + form_end_row(); + } + } } function auditGetDisplayText() { - return array( - 'page' => array( - 'display' => __('Page Name', 'audit'), - 'align' => 'left', - 'sort' => 'ASC', - 'tip' => __('The page where the event was generated.', 'audit') - ), - 'username' => array( - 'display' => __('User Name', 'audit'), - 'align' => 'left', - 'sort' => 'ASC', - 'tip' => __('The user who generated the event.', 'audit') - ), - 'action' => array( - 'display' => __('Action', 'audit'), - 'align' => 'left', - 'sort' => 'ASC', - 'tip' => __('The Cacti Action requested. Hover over action to see $_POST data.', 'audit') - ), - 'user_agent' => array( - 'display' => __('User Agent', 'audit'), - 'align' => 'left', - 'sort' => 'ASC', - 'tip' => __('The browser type of the requester.', 'audit') - ), - 'ip_address' => array( - 'display' => __('IP Address', 'audit'), - 'align' => 'right', - 'sort' => 'ASC', - 'tip' => __('The IP Address of the requester.', 'audit') - ), - 'event_time' => array( - 'display' => __('Event Time', 'audit'), - 'align' => 'right', - 'sort' => 'DESC', - 'tip' => __('The time the Event took place.', 'audit') - ) - ); + return array( + 'page' => array( + 'display' => __('Page Name', 'audit'), + 'align' => 'left', + 'sort' => 'ASC', + 'tip' => __('The page where the event was generated.', 'audit') + ), + 'username' => array( + 'display' => __('User Name', 'audit'), + 'align' => 'left', + 'sort' => 'ASC', + 'tip' => __('The user who generated the event.', 'audit') + ), + 'action' => array( + 'display' => __('Action', 'audit'), + 'align' => 'left', + 'sort' => 'ASC', + 'tip' => __('The Cacti Action requested. Hover over action to see $_POST data.', 'audit') + ), + 'user_agent' => array( + 'display' => __('User Agent', 'audit'), + 'align' => 'left', + 'sort' => 'ASC', + 'tip' => __('The browser type of the requester.', 'audit') + ), + 'ip_address' => array( + 'display' => __('IP Address', 'audit'), + 'align' => 'right', + 'sort' => 'ASC', + 'tip' => __('The IP Address of the requester.', 'audit') + ), + 'event_time' => array( + 'display' => __('Event Time', 'audit'), + 'align' => 'right', + 'sort' => 'DESC', + 'tip' => __('The time the Event took place.', 'audit') + ) + ); } function auditExportRows() { - processRequestVars(); - $sql_where = auditBuildSqlWhereClause(); - - $events = db_fetch_assoc("SELECT audit_log.*, user_auth.username - FROM audit_log - LEFT JOIN user_auth - ON audit_log.user_id=user_auth.id - $sql_where"); - - if (cacti_sizeof($events)) { - header('Content-Disposition: attachment; filename=audit_export.csv'); - - print __x('Column Header used for CSV log export. Ensure that you do NOT(!) remove one of the commas. The output needs to be CSV compliant.','page, user_id, username, action, ip_address, user_agent, event_time, post', 'audit') . "\n"; - - foreach($events as $event) { - $poster = auditBuildPosterString($event['post']); - - print - $event['page'] . ', ' . - $event['user_id'] . ', ' . - get_username($event['user_id']) . ', ' . - $event['action'] . ', ' . - $event['ip_address'] . ', ' . - $event['user_agent'] . ', ' . - $event['event_time'] . ', ' . - $poster . "\n"; - } - } + processRequestVars(); + $sql_where = auditBuildSqlWhereClause(); + + $events = db_fetch_assoc("SELECT audit_log.*, user_auth.username + FROM audit_log + LEFT JOIN user_auth + ON audit_log.user_id=user_auth.id + $sql_where"); + + if (cacti_sizeof($events)) { + header('Content-Disposition: attachment; filename=audit_export.csv'); + + print __x('Column Header used for CSV log export. Ensure that you do NOT(!) remove one of the commas. The output needs to be CSV compliant.','page, user_id, username, action, ip_address, user_agent, event_time, post', 'audit') . "\n"; + + foreach($events as $event) { + $poster = auditBuildPosterString($event['post']); + + print + $event['page'] . ', ' . + $event['user_id'] . ', ' . + get_username($event['user_id']) . ', ' . + $event['action'] . ', ' . + $event['ip_address'] . ', ' . + $event['user_agent'] . ', ' . + $event['event_time'] . ', ' . + $poster . "\n"; + } + } } function auditCsvEscape($string) { - $string = str_replace('"', '', $string); - $string = str_replace(',', '|', $string); - return $string; + $string = str_replace('"', '', $string); + $string = str_replace(',', '|', $string); + return $string; } function processRequestVars() { - /* ================= input validation and session storage ================= */ - $filters = array( - 'rows' => array( - 'filter' => FILTER_VALIDATE_INT, - 'pageset' => true, - 'default' => '-1' - ), - 'page' => array( - 'filter' => FILTER_VALIDATE_INT, - 'default' => '1' - ), - 'filter' => array( - 'filter' => FILTER_DEFAULT, - 'pageset' => true, - 'default' => '' - ), - 'sort_column' => array( - 'filter' => FILTER_CALLBACK, - 'default' => 'event_time', - 'options' => array('options' => 'sanitize_search_string') - ), - 'sort_direction' => array( - 'filter' => FILTER_CALLBACK, - 'default' => 'DESC', - 'options' => array('options' => 'sanitize_search_string') - ), - 'user_id' => array( - 'filter' => FILTER_VALIDATE_INT, - 'pageset' => true, - 'default' => '-1' - ), - 'event_page' => array( - 'filter' => FILTER_CALLBACK, - 'pageset' => true, - 'default' => '-1', - 'options' => array('options' => 'sanitize_search_string') - ) - ); - - validate_store_request_vars($filters, 'sess_audit'); - /* ================= input validation ================= */ + /* ================= input validation and session storage ================= */ + $filters = array( + 'rows' => array( + 'filter' => FILTER_VALIDATE_INT, + 'pageset' => true, + 'default' => '-1' + ), + 'page' => array( + 'filter' => FILTER_VALIDATE_INT, + 'default' => '1' + ), + 'filter' => array( + 'filter' => FILTER_DEFAULT, + 'pageset' => true, + 'default' => '' + ), + 'sort_column' => array( + 'filter' => FILTER_CALLBACK, + 'default' => 'event_time', + 'options' => array('options' => 'sanitize_search_string') + ), + 'sort_direction' => array( + 'filter' => FILTER_CALLBACK, + 'default' => 'DESC', + 'options' => array('options' => 'sanitize_search_string') + ), + 'user_id' => array( + 'filter' => FILTER_VALIDATE_INT, + 'pageset' => true, + 'default' => '-1' + ), + 'event_page' => array( + 'filter' => FILTER_CALLBACK, + 'pageset' => true, + 'default' => '-1', + 'options' => array('options' => 'sanitize_search_string') + ) + ); + + validate_store_request_vars($filters, 'sess_audit'); + /* ================= input validation ================= */ } function auditLog() { - global $item_rows; + global $item_rows; - processRequestVars(); + processRequestVars(); - $rows = (get_request_var('rows') == '-1') ? read_config_option('num_rows_table') : get_request_var('rows'); + $rows = (get_request_var('rows') == '-1') ? read_config_option('num_rows_table') : get_request_var('rows'); - html_start_box(__('Audit Log', 'audit'), '100%', '', '3', 'center', ''); - auditRenderFilterForm($item_rows); + html_start_box(__('Audit Log', 'audit'), '100%', '', '3', 'center', ''); + auditRenderFilterForm($item_rows); - html_end_box(); + html_end_box(); - $sql_where = auditBuildSqlWhereClause(); + $sql_where = auditBuildSqlWhereClause(); - $total_rows = db_fetch_cell("SELECT - COUNT(*) - FROM audit_log - LEFT JOIN user_auth - ON audit_log.user_id=user_auth.id - $sql_where"); + $total_rows = db_fetch_cell("SELECT + COUNT(*) + FROM audit_log + LEFT JOIN user_auth + ON audit_log.user_id=user_auth.id + $sql_where"); - $sql_order = get_order_string(); - $sql_limit = ' LIMIT ' . ($rows*(get_request_var('page')-1)) . ',' . $rows; + $sql_order = get_order_string(); + $sql_limit = ' LIMIT ' . ($rows*(get_request_var('page')-1)) . ',' . $rows; - $events = db_fetch_assoc("SELECT audit_log.*, user_auth.username - FROM audit_log - LEFT JOIN user_auth - ON audit_log.user_id=user_auth.id - $sql_where - $sql_order - $sql_limit"); + $events = db_fetch_assoc("SELECT audit_log.*, user_auth.username + FROM audit_log + LEFT JOIN user_auth + ON audit_log.user_id=user_auth.id + $sql_where + $sql_order + $sql_limit"); $nav = html_nav_bar('audit.php?filter=' . get_request_var('filter'), MAX_DISPLAY_PAGES, get_request_var('page'), $rows, $total_rows, 5, __('Audit Events', 'audit'), 'page', 'main'); print $nav; - html_start_box('', '100%', '', '3', 'center', ''); + html_start_box('', '100%', '', '3', 'center', ''); - $display_text = auditGetDisplayText(); + $display_text = auditGetDisplayText(); - html_header_sort($display_text, get_request_var('sort_column'), get_request_var('sort_direction'), false); - auditRenderEventsRows($events); + html_header_sort($display_text, get_request_var('sort_column'), get_request_var('sort_direction'), false); + auditRenderEventsRows($events); - html_end_box(false); + html_end_box(false); - if (cacti_sizeof($events)) { - print $nav; - } + if (cacti_sizeof($events)) { + print $nav; + } - ?> - - + + 'SELECT id AS host_id,site_id,description,hostname,status,status_fail_date AS last_failed_date,status_rec_date AS last_recovered_date FROM host WHERE id IN (?)', - 'host_templates.php' => 'SELECT name FROM host_template WHERE id IN (?)', - 'templates_export.php' => 'SELECT name FROM graph_templates WHERE id IN (?)', - 'automation_devices.php' => 'SELECT id, network_id,hostname,ip,sysName,syslocation,snmp,up FROM automation_devices WHERE id IN (?)', - 'graph_templates.php' => 'SELECT name FROM graph_templates WHERE id IN (?)', - 'thold.php' => 'SELECT id,name_cache AS THOLD_NAME,data_source_name AS Data_Source FROM thold_data WHERE id IN (?)', - 'data_sources.php' => 'SELECT name_cache AS Data_Source_Name,active FROM data_template_data WHERE local_data_id IN (?)', - 'data_templates.php' => 'SELECT name FROM data_template WHERE id IN (?)', - 'aggregate_templates.php' => 'SELECT name FROM aggregate_graph_template WHERE id IN (?)', - 'thold_templates.php' => 'SELECT name FROM thold_template WHERE id IN (?)', - 'user_admin.php' => 'SELECT username FROM user_auth WHERE id IN (?)', - 'user_group_admin.php' => 'SELECT name FROM user_auth_group WHERE id IN (?)' - ); + return array( + 'host.php' => 'SELECT id AS host_id,site_id,description,hostname,status,status_fail_date AS last_failed_date,status_rec_date AS last_recovered_date FROM host WHERE id IN (?)', + 'host_templates.php' => 'SELECT name FROM host_template WHERE id IN (?)', + 'templates_export.php' => 'SELECT name FROM graph_templates WHERE id IN (?)', + 'automation_devices.php' => 'SELECT id, network_id,hostname,ip,sysName,syslocation,snmp,up FROM automation_devices WHERE id IN (?)', + 'graph_templates.php' => 'SELECT name FROM graph_templates WHERE id IN (?)', + 'thold.php' => 'SELECT id,name_cache AS THOLD_NAME,data_source_name AS Data_Source FROM thold_data WHERE id IN (?)', + 'data_sources.php' => 'SELECT name_cache AS Data_Source_Name,active FROM data_template_data WHERE local_data_id IN (?)', + 'data_templates.php' => 'SELECT name FROM data_template WHERE id IN (?)', + 'aggregate_templates.php' => 'SELECT name FROM aggregate_graph_template WHERE id IN (?)', + 'thold_templates.php' => 'SELECT name FROM thold_template WHERE id IN (?)', + 'user_admin.php' => 'SELECT username FROM user_auth WHERE id IN (?)', + 'user_group_admin.php' => 'SELECT name FROM user_auth_group WHERE id IN (?)' + ); } function auditTransformAutomationDevices($result) { - foreach ($result as &$row) { - $row['snmp'] = ($row['snmp'] == 1) ? 'UP' : 'Down'; - $row['up'] = ($row['up'] == 1) ? 'Yes' : 'No'; - } + foreach ($result as &$row) { + $row['snmp'] = ($row['snmp'] == 1) ? 'UP' : 'Down'; + $row['up'] = ($row['up'] == 1) ? 'Yes' : 'No'; + } - return $result; + return $result; } function auditProcessPageData($page, $drop_action, $selected_items) { - if ($drop_action === false) { - return json_encode(array()); - } - - $query_map = auditBuildPageQueryMap(); - if (!isset($query_map[$page])) { - return json_encode(array()); - } - - $objects = array(); - foreach ($selected_items as $item) { - $result = db_fetch_assoc_prepared($query_map[$page], array($item)); - if ($page == 'automation_devices.php') { - $result = auditTransformAutomationDevices($result); - } - - $objects[] = $result; - } - - return json_encode($objects); + if ($drop_action === false) { + return json_encode(array()); + } + + $query_map = auditBuildPageQueryMap(); + if (!isset($query_map[$page])) { + return json_encode(array()); + } + + $objects = array(); + foreach ($selected_items as $item) { + $result = db_fetch_assoc_prepared($query_map[$page], array($item)); + if ($page == 'automation_devices.php') { + $result = auditTransformAutomationDevices($result); + } + + $objects[] = $result; + } + + return json_encode($objects); } function auditPrepareRequestPost(&$action) { - $post = $_REQUEST; - unset($post['__csrf_magic']); - unset($post['header']); - - foreach ($post as $key => $value) { - if (preg_match('/pass|phrase/i', $key)) { - unset($post[$key]); - } - } - - if (isset($post['drp_action']) && $post['drp_action'] == 1) { - $action = 'delete'; - } elseif (isset($post['drp_action']) && $post['drp_action'] == 4) { - $action = 'disable'; - } - - return $post; + $post = $_REQUEST; + unset($post['__csrf_magic']); + unset($post['header']); + + foreach ($post as $key => $value) { + if (preg_match('/pass|phrase/i', $key)) { + unset($post[$key]); + } + } + + if (isset($post['drp_action']) && $post['drp_action'] == 1) { + $action = 'delete'; + } elseif (isset($post['drp_action']) && $post['drp_action'] == 4) { + $action = 'disable'; + } + + return $post; } function auditGetSelectedItemsData($post) { - if (!isset($post['selected_items'])) { - return array(array(), false); - } + if (!isset($post['selected_items'])) { + return array(array(), false); + } - $selected_items = unserialize(stripslashes($post['selected_items']), array('allowed_classes' => false)); - $drop_action = isset($post['drp_action']) ? $post['drp_action'] : false; + $selected_items = unserialize(stripslashes($post['selected_items']), array('allowed_classes' => false)); + $drop_action = isset($post['drp_action']) ? $post['drp_action'] : false; - return array($selected_items, $drop_action); + return array($selected_items, $drop_action); } function auditGetBasePath($config) { - if (defined('CACTI_PATH_BASE')) { - return CACTI_PATH_BASE; - } + if (defined('CACTI_PATH_BASE')) { + return CACTI_PATH_BASE; + } - return $config['base_path']; + return $config['base_path']; } function auditResolveAction($page, $drop_action, $action) { - $action_map = array( - 'automation_devices.php' => array( - 2 => 'Delete Device', - 1 => 'Create Device' - ), - 'host.php' => array( - 2 => 'Host Enabled', - 3 => 'Host Disabled' - ) - ); - - if (isset($action_map[$page][$drop_action])) { - return $action_map[$page][$drop_action]; - } - - return $action; + $action_map = array( + 'automation_devices.php' => array( + 2 => 'Delete Device', + 1 => 'Create Device' + ), + 'host.php' => array( + 2 => 'Host Enabled', + 3 => 'Host Disabled' + ) + ); + + if (isset($action_map[$page][$drop_action])) { + return $action_map[$page][$drop_action]; + } + + return $action; } function auditBuildGuiEventData($config, &$action) { - $post = auditPrepareRequestPost($action); - list($selected_items, $drop_action) = auditGetSelectedItemsData($post); - - if (empty($action) && isset_request_var('action')) { - $action = get_nfilter_request_var('action'); - } elseif (empty($action)) { - $action = 'none'; - } - - $page = basename($_SERVER['SCRIPT_NAME']); - $action = auditResolveAction($page, $drop_action, $action); - - return array( - 'page' => $page, - 'user_id' => isset($_SESSION['sess_user_id']) ? $_SESSION['sess_user_id'] : 0, - 'action' => $action, - 'ip_address' => get_client_addr(), - 'user_agent' => $_SERVER['HTTP_USER_AGENT'], - 'event_time' => date('Y-m-d H:i:s'), - 'post' => json_encode($post), - 'object_data' => auditProcessPageData($page, $drop_action, $selected_items), - 'base_path' => auditGetBasePath($config) - ); + $post = auditPrepareRequestPost($action); + list($selected_items, $drop_action) = auditGetSelectedItemsData($post); + + if (empty($action) && isset_request_var('action')) { + $action = get_nfilter_request_var('action'); + } elseif (empty($action)) { + $action = 'none'; + } + + $page = basename($_SERVER['SCRIPT_NAME']); + $action = auditResolveAction($page, $drop_action, $action); + + return array( + 'page' => $page, + 'user_id' => isset($_SESSION['sess_user_id']) ? $_SESSION['sess_user_id'] : 0, + 'action' => $action, + 'ip_address' => get_client_addr(), + 'user_agent' => $_SERVER['HTTP_USER_AGENT'], + 'event_time' => date('Y-m-d H:i:s'), + 'post' => json_encode($post), + 'object_data' => auditProcessPageData($page, $drop_action, $selected_items), + 'base_path' => auditGetBasePath($config) + ); } function auditInsertGuiEvent($event) { - db_execute_prepared('INSERT INTO audit_log (page, user_id, action, ip_address, user_agent, event_time, post, object_data) - VALUES (?, ?, ?, ?, ?, ?, ?, ?)', - array($event['page'], $event['user_id'], $event['action'], $event['ip_address'], $event['user_agent'], $event['event_time'], $event['post'], $event['object_data'])); + db_execute_prepared('INSERT INTO audit_log (page, user_id, action, ip_address, user_agent, event_time, post, object_data) + VALUES (?, ?, ?, ?, ?, ?, ?, ?)', + array($event['page'], $event['user_id'], $event['action'], $event['ip_address'], $event['user_agent'], $event['event_time'], $event['post'], $event['object_data'])); } function auditGetExternalLogPath($base_path) { - $audit_log = read_config_option('audit_log_external_path'); - if ($audit_log == '') { - $audit_log = $base_path . '/log/audit.log'; - set_config_option('audit_log_external_path', $audit_log); - } + $audit_log = read_config_option('audit_log_external_path'); + if ($audit_log == '') { + $audit_log = $base_path . '/log/audit.log'; + set_config_option('audit_log_external_path', $audit_log); + } - return $audit_log; + return $audit_log; } function auditEnsureExternalLogFile($audit_log) { - if ($audit_log == '' || file_exists($audit_log)) { - return; - } - - if (is_writable(dirname($audit_log))) { - cacti_log(sprintf('NOTE: The Audit Log file \'%s\' does not exist. Creating it.', $audit_log), false, 'AUDIT'); - touch($audit_log); - } else { - cacti_log(sprintf('ERROR: Audit Log file path \'%s\' does not exist and the path is not writeable.', $audit_log), false, 'AUDIT'); - } + if ($audit_log == '' || file_exists($audit_log)) { + return; + } + + if (is_writable(dirname($audit_log))) { + cacti_log(sprintf('NOTE: The Audit Log file \'%s\' does not exist. Creating it.', $audit_log), false, 'AUDIT'); + touch($audit_log); + } else { + cacti_log(sprintf('ERROR: Audit Log file path \'%s\' does not exist and the path is not writeable.', $audit_log), false, 'AUDIT'); + } } function auditWriteExternalLog($audit_log, $event) { - if (read_config_option('audit_log_external') != 'on' || $audit_log == '' || !file_exists($audit_log)) { - return; - } - - $log_data = array( - 'page' => $event['page'], - 'user_id' => $event['user_id'], - 'action' => $event['action'], - 'ip_address' => $event['ip_address'], - 'user_agent' => $event['user_agent'], - 'event_time' => $event['event_time'], - 'post' => $event['post'], - 'object_data' => $event['object_data'] - ); - - $log_msg = json_encode($log_data) . "\n"; - $file = fopen($audit_log, 'a'); - if ($file) { - fwrite($file, $log_msg); - fclose($file); - } + if (read_config_option('audit_log_external') != 'on' || $audit_log == '' || !file_exists($audit_log)) { + return; + } + + $log_data = array( + 'page' => $event['page'], + 'user_id' => $event['user_id'], + 'action' => $event['action'], + 'ip_address' => $event['ip_address'], + 'user_agent' => $event['user_agent'], + 'event_time' => $event['event_time'], + 'post' => $event['post'], + 'object_data' => $event['object_data'] + ); + + $log_msg = json_encode($log_data) . "\n"; + $file = fopen($audit_log, 'a'); + if ($file) { + fwrite($file, $log_msg); + fclose($file); + } } function auditInsertCliEvent() { - $page = basename($_SERVER['argv'][0]); - $user_id = 0; - $action = 'cli'; - $ip_address = getHostByName(php_uname('n')); - $user_agent = get_current_user(); - $event_time = date('Y-m-d H:i:s'); - $post = implode(' ', $_SERVER['argv']); - - if (strpos($_SERVER['argv'][0], 'poller') !== false || - strpos($_SERVER['argv'][0], 'cmd.php') !== false || - strpos($_SERVER['argv'][0], '/scripts/') !== false || - strpos($_SERVER['argv'][0], 'script_server.php') !== false || - strpos($_SERVER['argv'][0], '_process.php') !== false) { - return; - } - - db_execute_prepared('INSERT INTO audit_log (page, user_id, action, ip_address, user_agent, event_time, post) - VALUES (?, ?, ?, ?, ?, ?, ?)', - array($page, $user_id, $action, $ip_address, $user_agent, $event_time, $post)); + $page = basename($_SERVER['argv'][0]); + $user_id = 0; + $action = 'cli'; + $ip_address = getHostByName(php_uname('n')); + $user_agent = get_current_user(); + $event_time = date('Y-m-d H:i:s'); + $post = implode(' ', $_SERVER['argv']); + + if (strpos($_SERVER['argv'][0], 'poller') !== false || + strpos($_SERVER['argv'][0], 'cmd.php') !== false || + strpos($_SERVER['argv'][0], '/scripts/') !== false || + strpos($_SERVER['argv'][0], 'script_server.php') !== false || + strpos($_SERVER['argv'][0], '_process.php') !== false) { + return; + } + + db_execute_prepared('INSERT INTO audit_log (page, user_id, action, ip_address, user_agent, event_time, post) + VALUES (?, ?, ?, ?, ?, ?, ?)', + array($page, $user_id, $action, $ip_address, $user_agent, $event_time, $post)); } function auditConfigInsert() { - global $action, $config; + global $action, $config; - if (auditLogValidEvent()) { - $event = auditBuildGuiEventData($config, $action); - $audit_log = auditGetExternalLogPath($event['base_path']); + if (auditLogValidEvent()) { + $event = auditBuildGuiEventData($config, $action); + $audit_log = auditGetExternalLogPath($event['base_path']); - auditInsertGuiEvent($event); - auditEnsureExternalLogFile($audit_log); - auditWriteExternalLog($audit_log, $event); - return; - } + auditInsertGuiEvent($event); + auditEnsureExternalLogFile($audit_log); + auditWriteExternalLog($audit_log, $event); + return; + } - if (isset($_SERVER['argv'])) { - auditInsertCliEvent(); - } + if (isset($_SERVER['argv'])) { + auditInsertCliEvent(); + } } diff --git a/setup.php b/setup.php index 91ed8df..7ffa4e0 100644 --- a/setup.php +++ b/setup.php @@ -22,311 +22,311 @@ +-------------------------------------------------------------------------+ */ -include_once('audit_functions.php'); +include_once 'audit_functions.php'; function pluginAuditInstall() { - api_plugin_register_hook('audit', 'config_arrays', 'auditConfigArrays', 'setup.php'); - api_plugin_register_hook('audit', 'config_settings', 'auditConfigSettings', 'setup.php'); - api_plugin_register_hook('audit', 'config_insert', 'auditConfigInsert', 'setup.php'); - api_plugin_register_hook('audit', 'poller_bottom', 'auditPollerBottom', 'setup.php'); - api_plugin_register_hook('audit', 'draw_navigation_text', 'auditDrawNavigationText', 'setup.php'); - api_plugin_register_hook('audit', 'utilities_array', 'auditUtilitiesArray', 'setup.php'); - api_plugin_register_hook('audit', 'is_console_page', 'auditIsConsolePage', 'setup.php'); + api_plugin_register_hook('audit', 'config_arrays', 'auditConfigArrays', 'setup.php'); + api_plugin_register_hook('audit', 'config_settings', 'auditConfigSettings', 'setup.php'); + api_plugin_register_hook('audit', 'config_insert', 'auditConfigInsert', 'setup.php'); + api_plugin_register_hook('audit', 'poller_bottom', 'auditPollerBottom', 'setup.php'); + api_plugin_register_hook('audit', 'draw_navigation_text', 'auditDrawNavigationText', 'setup.php'); + api_plugin_register_hook('audit', 'utilities_array', 'auditUtilitiesArray', 'setup.php'); + api_plugin_register_hook('audit', 'is_console_page', 'auditIsConsolePage', 'setup.php'); - /* hook for table replication */ - api_plugin_register_hook('audit', 'replicate_out', 'auditReplicateOut', 'setup.php'); + /* hook for table replication */ + api_plugin_register_hook('audit', 'replicate_out', 'auditReplicateOut', 'setup.php'); - api_plugin_register_realm('audit', 'audit.php', __('View Cacti Audit Log', 'audit'), 1); + api_plugin_register_realm('audit', 'audit.php', __('View Cacti Audit Log', 'audit'), 1); - auditSetupTable(); + auditSetupTable(); } function pluginAuditUninstall() { - db_execute('DROP TABLE IF EXISTS audit_log'); - return true; + db_execute('DROP TABLE IF EXISTS audit_log'); + return true; } function auditIsConsolePage($url) { - if (strpos($url, 'audit.php') !== false) { - return true; - } + if (strpos($url, 'audit.php') !== false) { + return true; + } - return false; + return false; } function pluginAuditCheckConfig() { - return true; + return true; } function pluginAuditUpgrade() { - return true; + return true; } function auditCheckUpgrade() { - global $config, $database_default; - include_once($config['library_path'] . '/database.php'); - include_once($config['library_path'] . '/functions.php'); - - $files = array('plugins.php', 'audit.php'); - if (isset($_SERVER['PHP_SELF']) && !in_array(basename($_SERVER['PHP_SELF']), $files)) { - return; - } - - $info = pluginAuditVersion(); - $current = $info['version']; - $old = db_fetch_cell("SELECT version FROM plugin_config WHERE directory='audit'"); - if ($current != $old) { - if (api_plugin_is_enabled('audit')) { - # may sound ridiculous, but enables new hooks - api_plugin_enable_hooks('audit'); - - db_execute('ALTER TABLE audit_log ADD COLUMN IF NOT EXISTS object_data LONGBLOB'); - } - - db_execute("UPDATE plugin_config - SET version='$current' - WHERE directory='audit'"); - - db_execute("UPDATE plugin_config SET - version='" . $info['version'] . "', - name='" . $info['longname'] . "', - author='" . $info['author'] . "', - webpage='" . $info['homepage'] . "' - WHERE directory='" . $info['name'] . "' "); - - /* hook for table replication */ - api_plugin_register_hook('audit', 'replicate_out', 'auditReplicateOut', 'setup.php', '1'); - api_plugin_register_hook('audit', 'is_console_page', 'auditIsConsolePage', 'setup.php', 1); - } + global $config, $database_default; + include_once $config['library_path'] . '/database.php'; + include_once $config['library_path'] . '/functions.php'; + + $files = array('plugins.php', 'audit.php'); + if (isset($_SERVER['PHP_SELF']) && !in_array(basename($_SERVER['PHP_SELF']), $files)) { + return; + } + + $info = pluginAuditVersion(); + $current = $info['version']; + $old = db_fetch_cell("SELECT version FROM plugin_config WHERE directory='audit'"); + if ($current != $old) { + if (api_plugin_is_enabled('audit')) { + # may sound ridiculous, but enables new hooks + api_plugin_enable_hooks('audit'); + + db_execute('ALTER TABLE audit_log ADD COLUMN IF NOT EXISTS object_data LONGBLOB'); + } + + db_execute("UPDATE plugin_config + SET version='$current' + WHERE directory='audit'"); + + db_execute("UPDATE plugin_config SET + version='" . $info['version'] . "', + name='" . $info['longname'] . "', + author='" . $info['author'] . "', + webpage='" . $info['homepage'] . "' + WHERE directory='" . $info['name'] . "' "); + + /* hook for table replication */ + api_plugin_register_hook('audit', 'replicate_out', 'auditReplicateOut', 'setup.php', '1'); + api_plugin_register_hook('audit', 'is_console_page', 'auditIsConsolePage', 'setup.php', 1); + } } function auditCheckDependencies($data) { - $remote_poller_id = $data['remote_poller_id']; - $rcnn_id = $data['rcnn_id']; - $class = $data['class']; + $remote_poller_id = $data['remote_poller_id']; + $rcnn_id = $data['rcnn_id']; + $class = $data['class']; - if ($class == 'all') { - if (!db_table_exists('alert_log', false, $rcnn_id)) { - $create = db_fetch_cell('SHOW CREATE TABLE autid_log'); + if ($class == 'all') { + if (!db_table_exists('alert_log', false, $rcnn_id)) { + $create = db_fetch_cell('SHOW CREATE TABLE autid_log'); - db_execute($create, false, $rcnn_id); - } - } + db_execute($create, false, $rcnn_id); + } + } - return $data; + return $data; } function auditReplicateOut($data) { - $remote_poller_id = $data['remote_poller_id']; - $rcnn_id = $data['rcnn_id']; - $class = $data['class']; - - cacti_log('INFO: Replicating for the Audit Plugin', false, 'REPLICATE'); - - if ($class == 'all') { - if (!db_table_exists('audit_log', false, $rcnn_id)) { - cacti_log('INFO: Audit Log table does not exist creating', false, 'REPLICATE'); - - $table = 'audit_log'; - $create = db_fetch_row("SHOW CREATE TABLE $table"); - - if (isset($create["CREATE TABLE `$table`"]) || isset($create['Create Table'])) { - if (isset($create["CREATE TABLE `$table`"])) { - db_execute($create["CREATE TABLE `$table`"], true, $rcnn_id); - } else { - db_execute($create['Create Table'], true, $rcnn_id); - } - } - } else { - cacti_log('INFO: Audit Log table exists skipping', false, 'REPLICATE'); - } - } - - return $data; + $remote_poller_id = $data['remote_poller_id']; + $rcnn_id = $data['rcnn_id']; + $class = $data['class']; + + cacti_log('INFO: Replicating for the Audit Plugin', false, 'REPLICATE'); + + if ($class == 'all') { + if (!db_table_exists('audit_log', false, $rcnn_id)) { + cacti_log('INFO: Audit Log table does not exist creating', false, 'REPLICATE'); + + $table = 'audit_log'; + $create = db_fetch_row("SHOW CREATE TABLE $table"); + + if (isset($create["CREATE TABLE `$table`"]) || isset($create['Create Table'])) { + if (isset($create["CREATE TABLE `$table`"])) { + db_execute($create["CREATE TABLE `$table`"], true, $rcnn_id); + } else { + db_execute($create['Create Table'], true, $rcnn_id); + } + } + } else { + cacti_log('INFO: Audit Log table exists skipping', false, 'REPLICATE'); + } + } + + return $data; } function auditPollerBottom() { - $last_check = read_config_option('audit_last_check'); + $last_check = read_config_option('audit_last_check'); - $now = date('d'); + $now = date('d'); - if ($last_check != $now) { - $retention = read_config_option('audit_retention'); + if ($last_check != $now) { + $retention = read_config_option('audit_retention'); - if ($retention > 0) { - db_execute('DELETE FROM audit_log WHERE event_time < FROM_UNIXTIME(' . (time() - ($retention * 86400)) . ')'); - $rows = db_affected_rows(); - cacti_log('NOTE: Purged ' . $rows . ' Audit Log Records from Cacti', false, 'POLLER'); - } - } + if ($retention > 0) { + db_execute('DELETE FROM audit_log WHERE event_time < FROM_UNIXTIME(' . (time() - ($retention * 86400)) . ')'); + $rows = db_affected_rows(); + cacti_log('NOTE: Purged ' . $rows . ' Audit Log Records from Cacti', false, 'POLLER'); + } + } - set_config_option('audit_last_check', $now); + set_config_option('audit_last_check', $now); } function auditSetupTable() { - global $config, $database_default; - include_once($config['library_path'] . '/database.php'); - - db_execute("CREATE TABLE IF NOT EXISTS `audit_log` ( - `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, - `page` varchar(40) DEFAULT NULL, - `user_id` int(10) unsigned DEFAULT NULL, - `action` varchar(20) DEFAULT NULL, - `ip_address` varchar(40) DEFAULT NULL, - `user_agent` varchar(256) DEFAULT NULL, - `event_time` timestamp DEFAULT CURRENT_TIMESTAMP, - `post` longblob, - `object_data` longblob, - PRIMARY KEY (`id`), - KEY `user_id` (`user_id`), - KEY `page` (`page`), - KEY `ip_address` (`ip_address`), - KEY `event_time` (`event_time`), - KEY `action` (`action`)) - ENGINE=InnoDB - COMMENT='Audit Log for all GUI activities'"); - - return true; + global $config, $database_default; + include_once $config['library_path'] . '/database.php'; + + db_execute("CREATE TABLE IF NOT EXISTS `audit_log` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `page` varchar(40) DEFAULT NULL, + `user_id` int(10) unsigned DEFAULT NULL, + `action` varchar(20) DEFAULT NULL, + `ip_address` varchar(40) DEFAULT NULL, + `user_agent` varchar(256) DEFAULT NULL, + `event_time` timestamp DEFAULT CURRENT_TIMESTAMP, + `post` longblob, + `object_data` longblob, + PRIMARY KEY (`id`), + KEY `user_id` (`user_id`), + KEY `page` (`page`), + KEY `ip_address` (`ip_address`), + KEY `event_time` (`event_time`), + KEY `action` (`action`)) + ENGINE=InnoDB + COMMENT='Audit Log for all GUI activities'"); + + return true; } function pluginAuditVersion() { - global $config; - $info = parse_ini_file($config['base_path'] . '/plugins/audit/INFO', true); - return $info['info']; + global $config; + $info = parse_ini_file($config['base_path'] . '/plugins/audit/INFO', true); + return $info['info']; } function auditLogValidEvent() { - global $action; - - $valid = false; - - if (read_config_option('audit_enabled') == 'on') { - if (strpos($_SERVER['SCRIPT_NAME'], 'graph_view.php') !== false) { - $valid = false; - } elseif (strpos($_SERVER['SCRIPT_NAME'], 'user_admin.php') !== false && - isset_request_var('action') && get_nfilter_request_var('action') == 'checkpass') { - $valid = false; - } elseif (strpos($_SERVER['SCRIPT_NAME'], 'plugins.php') !== false) { - if (isset_request_var('mode')) { - $valid = true; - $action = get_nfilter_request_var('mode'); - } - } elseif (strpos($_SERVER['SCRIPT_NAME'], 'auth_profile.php') !== false) { - $valid = false; - } elseif (strpos($_SERVER['SCRIPT_NAME'], 'index.php') !== false) { - $valid = false; - } elseif (strpos($_SERVER['SCRIPT_NAME'], 'auth_changepassword.php') !== false) { - $valid = false; - } elseif (isset($_POST) && sizeof($_POST)) { - $valid = true; - } elseif (isset_request_var('purge_continue')) { - $valid = true; - $action = 'purge'; - } - } - - return $valid; + global $action; + + $valid = false; + + if (read_config_option('audit_enabled') == 'on') { + if (strpos($_SERVER['SCRIPT_NAME'], 'graph_view.php') !== false) { + $valid = false; + } elseif (strpos($_SERVER['SCRIPT_NAME'], 'user_admin.php') !== false && + isset_request_var('action') && get_nfilter_request_var('action') == 'checkpass') { + $valid = false; + } elseif (strpos($_SERVER['SCRIPT_NAME'], 'plugins.php') !== false) { + if (isset_request_var('mode')) { + $valid = true; + $action = get_nfilter_request_var('mode'); + } + } elseif (strpos($_SERVER['SCRIPT_NAME'], 'auth_profile.php') !== false) { + $valid = false; + } elseif (strpos($_SERVER['SCRIPT_NAME'], 'index.php') !== false) { + $valid = false; + } elseif (strpos($_SERVER['SCRIPT_NAME'], 'auth_changepassword.php') !== false) { + $valid = false; + } elseif (isset($_POST) && sizeof($_POST)) { + $valid = true; + } elseif (isset_request_var('purge_continue')) { + $valid = true; + $action = 'purge'; + } + } + + return $valid; } function auditUtilitiesArray() { - global $utilities; - - if (version_compare(CACTI_VERSION, '1.3.0', '<')) { - if (api_plugin_user_realm_auth('audit.php')) { - $utilities[__('Technical Support', 'audit')] = array_merge( - $utilities[__('Technical Support', 'audit')], - array( - __('View Audit Log', 'audit') => array( - 'link' => 'plugins/audit/audit.php', - 'description' => __('Allows Administrators to view change activity on the Cacti server. Administrators can also export the audit log for analysis purposes.', 'audit') - ) - ) - ); - } - } + global $utilities; + + if (version_compare(CACTI_VERSION, '1.3.0', '<')) { + if (api_plugin_user_realm_auth('audit.php')) { + $utilities[__('Technical Support', 'audit')] = array_merge( + $utilities[__('Technical Support', 'audit')], + array( + __('View Audit Log', 'audit') => array( + 'link' => 'plugins/audit/audit.php', + 'description' => __('Allows Administrators to view change activity on the Cacti server. Administrators can also export the audit log for analysis purposes.', 'audit') + ) + ) + ); + } + } } function auditConfigArrays() { - global $menu, $messages, $audit_retentions, $utilities; - - if (isset($_SESSION['audit_message']) && $_SESSION['audit_message'] != '') { - $messages['audit_message'] = array('message' => $_SESSION['audit_message'], 'type' => 'info'); - } - - $audit_retentions = array( - -1 => __('Indefinitely', 'audit'), - 14 => __('%d Weeks', 2, 'audit'), - 30 => __('%d Month', 1, 'audit'), - 60 => __('%d Months', 2, 'audit'), - 90 => __('%d Months', 3, 'audit'), - 120 => __('%d Months', 4, 'audit'), - 183 => __('%d Months', 6, 'audit'), - 365 => __('%d Year', 1, 'audit'), - 730 => __('%d Years', 2, 'audit'), - 1095 => __('%d Years', 3, 'audit') - ); - - $menu[__('Utilities')]['plugins/audit/audit.php'] = __('Audit Log', 'audit'); - - if (function_exists('auth_augment_roles')) { - auth_augment_roles(__('System Administration'), array('audit.php')); - } - - auditCheckUpgrade(); + global $menu, $messages, $audit_retentions, $utilities; + + if (isset($_SESSION['audit_message']) && $_SESSION['audit_message'] != '') { + $messages['audit_message'] = array('message' => $_SESSION['audit_message'], 'type' => 'info'); + } + + $audit_retentions = array( + -1 => __('Indefinitely', 'audit'), + 14 => __('%d Weeks', 2, 'audit'), + 30 => __('%d Month', 1, 'audit'), + 60 => __('%d Months', 2, 'audit'), + 90 => __('%d Months', 3, 'audit'), + 120 => __('%d Months', 4, 'audit'), + 183 => __('%d Months', 6, 'audit'), + 365 => __('%d Year', 1, 'audit'), + 730 => __('%d Years', 2, 'audit'), + 1095 => __('%d Years', 3, 'audit') + ); + + $menu[__('Utilities')]['plugins/audit/audit.php'] = __('Audit Log', 'audit'); + + if (function_exists('auth_augment_roles')) { + auth_augment_roles(__('System Administration'), array('audit.php')); + } + + auditCheckUpgrade(); } function auditConfigSettings() { - global $tabs, $settings, $item_rows, $audit_retentions; - - $temp = array( - 'audit_header' => array( - 'friendly_name' => __('Audit Log Settings', 'audit'), - 'method' => 'spacer', - ), - 'audit_enabled' => array( - 'friendly_name' => __('Enable Audit Log', 'audit'), - 'description' => __('Check this box, if you want the Audit Log to track GUI activities.', 'audit'), - 'method' => 'checkbox', - 'default' => 'on' - ), - 'audit_retention' => array( - 'friendly_name' => __('Audit Log Retention', 'audit'), - 'description' => __('How long do you wish Audit Log entries to be retained?', 'audit'), - 'method' => 'drop_array', - 'default' => '90', - 'array' => $audit_retentions - ), - 'audit_log_external' => array( - 'friendly_name' => __('External Audit Log', 'audit'), - 'description' => __('Check this box, if you want the Audit Log to be written to an external file.', 'audit'), - 'method' => 'checkbox', - 'default' => 'off' - ), - 'audit_log_external_path' => array( - 'friendly_name' => __('External Audit Log Log file Path', 'audit'), - 'description' => __('Enter the path to the external audit log file.', 'audit'), - 'method' => 'filepath', - 'default' => '/var/www/html/cacti/log/audit.log', - 'max_length' => '255' - ), - ); - - $tabs['audit'] = __('Audit', 'audit'); - - if (isset($settings['audit'])) { - $settings['audit'] = array_merge($settings['audit'], $temp); - } else { - $settings['audit'] = $temp; - } + global $tabs, $settings, $item_rows, $audit_retentions; + + $temp = array( + 'audit_header' => array( + 'friendly_name' => __('Audit Log Settings', 'audit'), + 'method' => 'spacer', + ), + 'audit_enabled' => array( + 'friendly_name' => __('Enable Audit Log', 'audit'), + 'description' => __('Check this box, if you want the Audit Log to track GUI activities.', 'audit'), + 'method' => 'checkbox', + 'default' => 'on' + ), + 'audit_retention' => array( + 'friendly_name' => __('Audit Log Retention', 'audit'), + 'description' => __('How long do you wish Audit Log entries to be retained?', 'audit'), + 'method' => 'drop_array', + 'default' => '90', + 'array' => $audit_retentions + ), + 'audit_log_external' => array( + 'friendly_name' => __('External Audit Log', 'audit'), + 'description' => __('Check this box, if you want the Audit Log to be written to an external file.', 'audit'), + 'method' => 'checkbox', + 'default' => 'off' + ), + 'audit_log_external_path' => array( + 'friendly_name' => __('External Audit Log Log file Path', 'audit'), + 'description' => __('Enter the path to the external audit log file.', 'audit'), + 'method' => 'filepath', + 'default' => '/var/www/html/cacti/log/audit.log', + 'max_length' => '255' + ), + ); + + $tabs['audit'] = __('Audit', 'audit'); + + if (isset($settings['audit'])) { + $settings['audit'] = array_merge($settings['audit'], $temp); + } else { + $settings['audit'] = $temp; + } } function auditDrawNavigationText($nav) { - $nav['audit.php:'] = array( - 'title' => __('Audit Event Log', 'audit'), - 'mapping' => 'index.php:', - 'url' => 'audit.php', - 'level' => '1' - ); - - return $nav; + $nav['audit.php:'] = array( + 'title' => __('Audit Event Log', 'audit'), + 'mapping' => 'index.php:', + 'url' => 'audit.php', + 'level' => '1' + ); + + return $nav; } From 4d6f719f715a041120545489d50cedf3b0ad53ff Mon Sep 17 00:00:00 2001 From: Sean Mancini Date: Fri, 27 Feb 2026 23:11:32 -0500 Subject: [PATCH 06/12] Update functions.js --- js/functions.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/js/functions.js b/js/functions.js index 693b7c3..6537a1c 100644 --- a/js/functions.js +++ b/js/functions.js @@ -31,7 +31,7 @@ * Apply filter to audit log */ function audit_applyFilter() { - strURL = 'audit.php' + + const strURL = 'audit.php' + '?filter='+$('#filter').val()+ '&rows='+$('#rows').val()+ '&page='+$('#page').val()+ @@ -45,14 +45,14 @@ function audit_applyFilter() { * Clear all filters */ function audit_clearFilter() { - strURL = 'audit.php?clear=1&header=false'; + const strURL = 'audit.php?clear=1&header=false'; loadPageNoHeader(strURL); } /** * Global variable to store audit timer */ -var auditTimer = null; +let auditTimer = null; /** * Open dialog to display audit event details @@ -60,7 +60,7 @@ var auditTimer = null; */ function audit_open_dialog(id) { $.get('audit.php?action=getdata&id='+id, function(data) { - var width; + let width; if (data.indexOf('narrow') > 0) { width = 400; } else { @@ -108,7 +108,7 @@ $(function() { }); $('#purge').click(function() { - strURL = 'audit.php?action=purge&header=false'; + const strURL = 'audit.php?action=purge&header=false'; loadPageNoHeader(strURL); }); @@ -128,7 +128,7 @@ $(function() { $('span[id^="event"]').hover(function() { audit_close_dialog(); - id = $(this).attr('id').replace('event', ''); + const id = $(this).attr('id').replace('event', ''); if (auditTimer != null) { clearTimeout(auditTimer); From 00ba5b96e9cc8ec3ad66f283eaf1da4a8bd255f8 Mon Sep 17 00:00:00 2001 From: Sean Mancini Date: Fri, 27 Feb 2026 23:16:41 -0500 Subject: [PATCH 07/12] update docblocks for functions --- audit.php | 58 +++++++++++++++++++++++++++ audit_functions.php | 98 +++++++++++++++++++++++++++++++++++++++++++++ setup.php | 88 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 244 insertions(+) diff --git a/audit.php b/audit.php index ee200f2..16b1f2b 100644 --- a/audit.php +++ b/audit.php @@ -137,6 +137,11 @@ bottom_footer(); } +/** + * Purge all audit records and raise a user-facing confirmation message. + * + * @return void + */ function auditPurge() { db_execute('TRUNCATE TABLE audit_log'); @@ -147,6 +152,11 @@ function auditPurge() { raise_message('audit_message'); } +/** + * Build SQL filter conditions based on current request filters. + * + * @return string + */ function auditBuildSqlWhereClause() { $sql_where = ''; @@ -167,6 +177,13 @@ function auditBuildSqlWhereClause() { return $sql_where; } +/** + * Convert JSON-encoded POST payload into export-friendly key/value text. + * + * @param string $post_payload JSON payload stored in audit_log.post. + * + * @return string + */ function auditBuildPosterString($post_payload) { $post = json_decode($post_payload); $poster = ''; @@ -186,6 +203,13 @@ function auditBuildPosterString($post_payload) { return $poster; } +/** + * Render the audit list filter form. + * + * @param array $item_rows Row-count options for pagination. + * + * @return void + */ function auditRenderFilterForm($item_rows) { ?> @@ -265,6 +289,13 @@ function auditRenderFilterForm($item_rows) { > $events Query result rows. + * + * @return void + */ function auditRenderEventsRows($events) { if (!cacti_sizeof($events)) { print "\n"; @@ -294,6 +325,11 @@ function auditRenderEventsRows($events) { } } +/** + * Return display metadata for sortable audit table columns. + * + * @return array> + */ function auditGetDisplayText() { return array( 'page' => array( @@ -335,6 +371,11 @@ function auditGetDisplayText() { ); } +/** + * Export filtered audit events as CSV output. + * + * @return void + */ function auditExportRows() { processRequestVars(); $sql_where = auditBuildSqlWhereClause(); @@ -366,12 +407,24 @@ function auditExportRows() { } } +/** + * Escape unsafe CSV characters in a value. + * + * @param string $string Raw value. + * + * @return string + */ function auditCsvEscape($string) { $string = str_replace('"', '', $string); $string = str_replace(',', '|', $string); return $string; } +/** + * Validate and store request filter variables in session scope. + * + * @return void + */ function processRequestVars() { /* ================= input validation and session storage ================= */ $filters = array( @@ -416,6 +469,11 @@ function processRequestVars() { /* ================= input validation ================= */ } +/** + * Render the audit log page with filters, navigation, and results. + * + * @return void + */ function auditLog() { global $item_rows; diff --git a/audit_functions.php b/audit_functions.php index 494828e..708f383 100644 --- a/audit_functions.php +++ b/audit_functions.php @@ -1,5 +1,10 @@ + */ function auditBuildPageQueryMap() { return array( 'host.php' => 'SELECT id AS host_id,site_id,description,hostname,status,status_fail_date AS last_failed_date,status_rec_date AS last_recovered_date FROM host WHERE id IN (?)', @@ -17,6 +22,13 @@ function auditBuildPageQueryMap() { ); } +/** + * Normalize automation device fields before they are written to object_data. + * + * @param array> $result Raw automation device rows. + * + * @return array> + */ function auditTransformAutomationDevices($result) { foreach ($result as &$row) { $row['snmp'] = ($row['snmp'] == 1) ? 'UP' : 'Down'; @@ -26,6 +38,15 @@ function auditTransformAutomationDevices($result) { return $result; } +/** + * Collect page-specific object snapshots for selected IDs and return JSON. + * + * @param string $page Current page filename. + * @param int|false $drop_action Bulk action value or false when not applicable. + * @param array $selected_items Selected item identifiers from request payload. + * + * @return string JSON-encoded object snapshot list. + */ function auditProcessPageData($page, $drop_action, $selected_items) { if ($drop_action === false) { return json_encode(array()); @@ -49,6 +70,13 @@ function auditProcessPageData($page, $drop_action, $selected_items) { return json_encode($objects); } +/** + * Sanitize request payload and infer action from bulk-operation request values. + * + * @param string $action Action value, updated by reference when a drop action is detected. + * + * @return array + */ function auditPrepareRequestPost(&$action) { $post = $_REQUEST; unset($post['__csrf_magic']); @@ -69,6 +97,13 @@ function auditPrepareRequestPost(&$action) { return $post; } +/** + * Extract selected item IDs and the drop action from a sanitized request payload. + * + * @param array $post Sanitized request payload. + * + * @return array{0: array, 1: int|false} + */ function auditGetSelectedItemsData($post) { if (!isset($post['selected_items'])) { return array(array(), false); @@ -80,6 +115,13 @@ function auditGetSelectedItemsData($post) { return array($selected_items, $drop_action); } +/** + * Resolve the runtime base path used by the plugin. + * + * @param array $config Global Cacti config array. + * + * @return string + */ function auditGetBasePath($config) { if (defined('CACTI_PATH_BASE')) { return CACTI_PATH_BASE; @@ -88,6 +130,15 @@ function auditGetBasePath($config) { return $config['base_path']; } +/** + * Map known bulk actions to user-friendly labels for audit output. + * + * @param string $page Current page filename. + * @param int|false $drop_action Drop action code from request payload. + * @param string $action Fallback action label. + * + * @return string + */ function auditResolveAction($page, $drop_action, $action) { $action_map = array( 'automation_devices.php' => array( @@ -107,6 +158,14 @@ function auditResolveAction($page, $drop_action, $action) { return $action; } +/** + * Build a normalized GUI audit event payload for database and file logging. + * + * @param array $config Global Cacti config array. + * @param string $action Current action value, updated by reference. + * + * @return array + */ function auditBuildGuiEventData($config, &$action) { $post = auditPrepareRequestPost($action); list($selected_items, $drop_action) = auditGetSelectedItemsData($post); @@ -133,12 +192,26 @@ function auditBuildGuiEventData($config, &$action) { ); } +/** + * Insert a GUI audit event into the audit_log table. + * + * @param array $event Normalized GUI audit event payload. + * + * @return void + */ function auditInsertGuiEvent($event) { db_execute_prepared('INSERT INTO audit_log (page, user_id, action, ip_address, user_agent, event_time, post, object_data) VALUES (?, ?, ?, ?, ?, ?, ?, ?)', array($event['page'], $event['user_id'], $event['action'], $event['ip_address'], $event['user_agent'], $event['event_time'], $event['post'], $event['object_data'])); } +/** + * Resolve and initialize the external audit log path setting. + * + * @param string $base_path Cacti base path. + * + * @return string + */ function auditGetExternalLogPath($base_path) { $audit_log = read_config_option('audit_log_external_path'); if ($audit_log == '') { @@ -149,6 +222,13 @@ function auditGetExternalLogPath($base_path) { return $audit_log; } +/** + * Create the external audit log file when configured and missing. + * + * @param string $audit_log External audit log file path. + * + * @return void + */ function auditEnsureExternalLogFile($audit_log) { if ($audit_log == '' || file_exists($audit_log)) { return; @@ -162,6 +242,14 @@ function auditEnsureExternalLogFile($audit_log) { } } +/** + * Append an event record to the configured external audit log. + * + * @param string $audit_log External audit log file path. + * @param array $event Normalized GUI audit event payload. + * + * @return void + */ function auditWriteExternalLog($audit_log, $event) { if (read_config_option('audit_log_external') != 'on' || $audit_log == '' || !file_exists($audit_log)) { return; @@ -186,6 +274,11 @@ function auditWriteExternalLog($audit_log, $event) { } } +/** + * Persist CLI audit events when the invoking script is not excluded. + * + * @return void + */ function auditInsertCliEvent() { $page = basename($_SERVER['argv'][0]); $user_id = 0; @@ -208,6 +301,11 @@ function auditInsertCliEvent() { array($page, $user_id, $action, $ip_address, $user_agent, $event_time, $post)); } +/** + * Hook callback for config_insert to capture GUI and CLI audit activity. + * + * @return void + */ function auditConfigInsert() { global $action, $config; diff --git a/setup.php b/setup.php index 7ffa4e0..7e3171e 100644 --- a/setup.php +++ b/setup.php @@ -24,6 +24,11 @@ include_once 'audit_functions.php'; +/** + * Register plugin hooks, realms, and required tables. + * + * @return void + */ function pluginAuditInstall() { api_plugin_register_hook('audit', 'config_arrays', 'auditConfigArrays', 'setup.php'); api_plugin_register_hook('audit', 'config_settings', 'auditConfigSettings', 'setup.php'); @@ -41,11 +46,23 @@ function pluginAuditInstall() { auditSetupTable(); } +/** + * Remove plugin data during uninstall. + * + * @return bool + */ function pluginAuditUninstall() { db_execute('DROP TABLE IF EXISTS audit_log'); return true; } +/** + * Determine whether a URL points to the audit plugin console page. + * + * @param string $url Request URL. + * + * @return bool + */ function auditIsConsolePage($url) { if (strpos($url, 'audit.php') !== false) { return true; @@ -54,14 +71,29 @@ function auditIsConsolePage($url) { return false; } +/** + * Report plugin configuration readiness. + * + * @return bool + */ function pluginAuditCheckConfig() { return true; } +/** + * Perform plugin upgrade checks. + * + * @return bool + */ function pluginAuditUpgrade() { return true; } +/** + * Apply schema and hook updates when the plugin version changes. + * + * @return void + */ function auditCheckUpgrade() { global $config, $database_default; include_once $config['library_path'] . '/database.php'; @@ -100,6 +132,13 @@ function auditCheckUpgrade() { } } +/** + * Replication callback used to verify remote dependencies for this plugin. + * + * @param array $data Replication context from core. + * + * @return array + */ function auditCheckDependencies($data) { $remote_poller_id = $data['remote_poller_id']; $rcnn_id = $data['rcnn_id']; @@ -116,6 +155,13 @@ function auditCheckDependencies($data) { return $data; } +/** + * Replicate audit table structures to a remote poller database. + * + * @param array $data Replication context from core. + * + * @return array + */ function auditReplicateOut($data) { $remote_poller_id = $data['remote_poller_id']; $rcnn_id = $data['rcnn_id']; @@ -145,6 +191,11 @@ function auditReplicateOut($data) { return $data; } +/** + * Poller hook to enforce retention cleanup for audit records. + * + * @return void + */ function auditPollerBottom() { $last_check = read_config_option('audit_last_check'); @@ -163,6 +214,11 @@ function auditPollerBottom() { set_config_option('audit_last_check', $now); } +/** + * Create the audit log table when it does not exist. + * + * @return bool + */ function auditSetupTable() { global $config, $database_default; include_once $config['library_path'] . '/database.php'; @@ -189,12 +245,22 @@ function auditSetupTable() { return true; } +/** + * Read plugin metadata from the INFO file. + * + * @return array + */ function pluginAuditVersion() { global $config; $info = parse_ini_file($config['base_path'] . '/plugins/audit/INFO', true); return $info['info']; } +/** + * Determine whether the current request should be logged. + * + * @return bool + */ function auditLogValidEvent() { global $action; @@ -228,6 +294,11 @@ function auditLogValidEvent() { return $valid; } +/** + * Register the plugin utility menu entry for supported versions. + * + * @return void + */ function auditUtilitiesArray() { global $utilities; @@ -246,6 +317,11 @@ function auditUtilitiesArray() { } } +/** + * Populate plugin menu entries and runtime configuration arrays. + * + * @return void + */ function auditConfigArrays() { global $menu, $messages, $audit_retentions, $utilities; @@ -275,6 +351,11 @@ function auditConfigArrays() { auditCheckUpgrade(); } +/** + * Define plugin settings fields and tabs. + * + * @return void + */ function auditConfigSettings() { global $tabs, $settings, $item_rows, $audit_retentions; @@ -320,6 +401,13 @@ function auditConfigSettings() { } } +/** + * Add audit page breadcrumb/navigation mapping metadata. + * + * @param array $nav Existing navigation map. + * + * @return array + */ function auditDrawNavigationText($nav) { $nav['audit.php:'] = array( 'title' => __('Audit Event Log', 'audit'), From 064b7772f9b8191934b7400844beb50abc11cb7d Mon Sep 17 00:00:00 2001 From: Sean Mancini Date: Fri, 27 Feb 2026 23:25:22 -0500 Subject: [PATCH 08/12] Another PSR pass Converted legacy PHP array syntax from array(...) to short arrays [...] in: --- audit.php | 82 +++++++++++++++++++++++---------------------- audit_functions.php | 42 +++++++++++------------ setup.php | 46 ++++++++++++------------- 3 files changed, 86 insertions(+), 84 deletions(-) diff --git a/audit.php b/audit.php index 16b1f2b..ef21ef2 100644 --- a/audit.php +++ b/audit.php @@ -27,7 +27,7 @@ set_default_action(); -switch(get_request_var('action')) { +switch (get_request_var('action')) { case 'export': auditExportRows(); @@ -44,7 +44,7 @@ $data = db_fetch_row_prepared('SELECT * FROM audit_log WHERE id = ?', - array(get_filter_request_var('id'))); + [get_filter_request_var('id')]); $output = ''; @@ -61,8 +61,8 @@ } elseif (cacti_sizeof($data)) { $attribs = json_decode($data['post']); - $nattribs = array(); - foreach($attribs as $field => $content) { + $nattribs = []; + foreach ($attribs as $field => $content) { $nattribs[$field] = $content; } ksort($nattribs); @@ -92,7 +92,7 @@ $i = 0; if (cacti_sizeof($nattribs)) { - foreach($nattribs as $field => $content) { + foreach ($nattribs as $field => $content) { if ($i % $columns == 0) { $output .= ($output != '' ? '':'') . ''; } @@ -250,7 +250,9 @@ function auditRenderFilterForm($item_rows) { $users = array_rekey(db_fetch_assoc('SELECT DISTINCT user_id FROM audit_log ORDER BY user_id'), 'user_id', 'user_id'); if (cacti_sizeof($users)) { foreach ($users as $user) { - if ($user == 0) continue; + if ($user == 0) { + continue; + } print "\n"; } } @@ -331,44 +333,44 @@ function auditRenderEventsRows($events) { * @return array> */ function auditGetDisplayText() { - return array( - 'page' => array( + return [ + 'page' => [ 'display' => __('Page Name', 'audit'), 'align' => 'left', 'sort' => 'ASC', 'tip' => __('The page where the event was generated.', 'audit') - ), - 'username' => array( + ], + 'username' => [ 'display' => __('User Name', 'audit'), 'align' => 'left', 'sort' => 'ASC', 'tip' => __('The user who generated the event.', 'audit') - ), - 'action' => array( + ], + 'action' => [ 'display' => __('Action', 'audit'), 'align' => 'left', 'sort' => 'ASC', 'tip' => __('The Cacti Action requested. Hover over action to see $_POST data.', 'audit') - ), - 'user_agent' => array( + ], + 'user_agent' => [ 'display' => __('User Agent', 'audit'), 'align' => 'left', 'sort' => 'ASC', 'tip' => __('The browser type of the requester.', 'audit') - ), - 'ip_address' => array( + ], + 'ip_address' => [ 'display' => __('IP Address', 'audit'), 'align' => 'right', 'sort' => 'ASC', 'tip' => __('The IP Address of the requester.', 'audit') - ), - 'event_time' => array( + ], + 'event_time' => [ 'display' => __('Event Time', 'audit'), 'align' => 'right', 'sort' => 'DESC', 'tip' => __('The time the Event took place.', 'audit') - ) - ); + ] + ]; } /** @@ -391,7 +393,7 @@ function auditExportRows() { print __x('Column Header used for CSV log export. Ensure that you do NOT(!) remove one of the commas. The output needs to be CSV compliant.','page, user_id, username, action, ip_address, user_agent, event_time, post', 'audit') . "\n"; - foreach($events as $event) { + foreach ($events as $event) { $poster = auditBuildPosterString($event['post']); print @@ -427,43 +429,43 @@ function auditCsvEscape($string) { */ function processRequestVars() { /* ================= input validation and session storage ================= */ - $filters = array( - 'rows' => array( + $filters = [ + 'rows' => [ 'filter' => FILTER_VALIDATE_INT, 'pageset' => true, 'default' => '-1' - ), - 'page' => array( + ], + 'page' => [ 'filter' => FILTER_VALIDATE_INT, 'default' => '1' - ), - 'filter' => array( + ], + 'filter' => [ 'filter' => FILTER_DEFAULT, 'pageset' => true, 'default' => '' - ), - 'sort_column' => array( + ], + 'sort_column' => [ 'filter' => FILTER_CALLBACK, 'default' => 'event_time', - 'options' => array('options' => 'sanitize_search_string') - ), - 'sort_direction' => array( + 'options' => ['options' => 'sanitize_search_string'] + ], + 'sort_direction' => [ 'filter' => FILTER_CALLBACK, 'default' => 'DESC', - 'options' => array('options' => 'sanitize_search_string') - ), - 'user_id' => array( + 'options' => ['options' => 'sanitize_search_string'] + ], + 'user_id' => [ 'filter' => FILTER_VALIDATE_INT, 'pageset' => true, 'default' => '-1' - ), - 'event_page' => array( + ], + 'event_page' => [ 'filter' => FILTER_CALLBACK, 'pageset' => true, 'default' => '-1', - 'options' => array('options' => 'sanitize_search_string') - ) - ); + 'options' => ['options' => 'sanitize_search_string'] + ] + ]; validate_store_request_vars($filters, 'sess_audit'); /* ================= input validation ================= */ diff --git a/audit_functions.php b/audit_functions.php index 708f383..dd8ebde 100644 --- a/audit_functions.php +++ b/audit_functions.php @@ -6,7 +6,7 @@ * @return array */ function auditBuildPageQueryMap() { - return array( + return [ 'host.php' => 'SELECT id AS host_id,site_id,description,hostname,status,status_fail_date AS last_failed_date,status_rec_date AS last_recovered_date FROM host WHERE id IN (?)', 'host_templates.php' => 'SELECT name FROM host_template WHERE id IN (?)', 'templates_export.php' => 'SELECT name FROM graph_templates WHERE id IN (?)', @@ -19,7 +19,7 @@ function auditBuildPageQueryMap() { 'thold_templates.php' => 'SELECT name FROM thold_template WHERE id IN (?)', 'user_admin.php' => 'SELECT username FROM user_auth WHERE id IN (?)', 'user_group_admin.php' => 'SELECT name FROM user_auth_group WHERE id IN (?)' - ); + ]; } /** @@ -49,17 +49,17 @@ function auditTransformAutomationDevices($result) { */ function auditProcessPageData($page, $drop_action, $selected_items) { if ($drop_action === false) { - return json_encode(array()); + return json_encode([]); } $query_map = auditBuildPageQueryMap(); if (!isset($query_map[$page])) { - return json_encode(array()); + return json_encode([]); } - $objects = array(); + $objects = []; foreach ($selected_items as $item) { - $result = db_fetch_assoc_prepared($query_map[$page], array($item)); + $result = db_fetch_assoc_prepared($query_map[$page], [$item]); if ($page == 'automation_devices.php') { $result = auditTransformAutomationDevices($result); } @@ -106,13 +106,13 @@ function auditPrepareRequestPost(&$action) { */ function auditGetSelectedItemsData($post) { if (!isset($post['selected_items'])) { - return array(array(), false); + return [[], false]; } - $selected_items = unserialize(stripslashes($post['selected_items']), array('allowed_classes' => false)); + $selected_items = unserialize(stripslashes($post['selected_items']), ['allowed_classes' => false]); $drop_action = isset($post['drp_action']) ? $post['drp_action'] : false; - return array($selected_items, $drop_action); + return [$selected_items, $drop_action]; } /** @@ -140,16 +140,16 @@ function auditGetBasePath($config) { * @return string */ function auditResolveAction($page, $drop_action, $action) { - $action_map = array( - 'automation_devices.php' => array( + $action_map = [ + 'automation_devices.php' => [ 2 => 'Delete Device', 1 => 'Create Device' - ), - 'host.php' => array( + ], + 'host.php' => [ 2 => 'Host Enabled', 3 => 'Host Disabled' - ) - ); + ] + ]; if (isset($action_map[$page][$drop_action])) { return $action_map[$page][$drop_action]; @@ -179,7 +179,7 @@ function auditBuildGuiEventData($config, &$action) { $page = basename($_SERVER['SCRIPT_NAME']); $action = auditResolveAction($page, $drop_action, $action); - return array( + return [ 'page' => $page, 'user_id' => isset($_SESSION['sess_user_id']) ? $_SESSION['sess_user_id'] : 0, 'action' => $action, @@ -189,7 +189,7 @@ function auditBuildGuiEventData($config, &$action) { 'post' => json_encode($post), 'object_data' => auditProcessPageData($page, $drop_action, $selected_items), 'base_path' => auditGetBasePath($config) - ); + ]; } /** @@ -202,7 +202,7 @@ function auditBuildGuiEventData($config, &$action) { function auditInsertGuiEvent($event) { db_execute_prepared('INSERT INTO audit_log (page, user_id, action, ip_address, user_agent, event_time, post, object_data) VALUES (?, ?, ?, ?, ?, ?, ?, ?)', - array($event['page'], $event['user_id'], $event['action'], $event['ip_address'], $event['user_agent'], $event['event_time'], $event['post'], $event['object_data'])); + [$event['page'], $event['user_id'], $event['action'], $event['ip_address'], $event['user_agent'], $event['event_time'], $event['post'], $event['object_data']]); } /** @@ -255,7 +255,7 @@ function auditWriteExternalLog($audit_log, $event) { return; } - $log_data = array( + $log_data = [ 'page' => $event['page'], 'user_id' => $event['user_id'], 'action' => $event['action'], @@ -264,7 +264,7 @@ function auditWriteExternalLog($audit_log, $event) { 'event_time' => $event['event_time'], 'post' => $event['post'], 'object_data' => $event['object_data'] - ); + ]; $log_msg = json_encode($log_data) . "\n"; $file = fopen($audit_log, 'a'); @@ -298,7 +298,7 @@ function auditInsertCliEvent() { db_execute_prepared('INSERT INTO audit_log (page, user_id, action, ip_address, user_agent, event_time, post) VALUES (?, ?, ?, ?, ?, ?, ?)', - array($page, $user_id, $action, $ip_address, $user_agent, $event_time, $post)); + [$page, $user_id, $action, $ip_address, $user_agent, $event_time, $post]); } /** diff --git a/setup.php b/setup.php index 7e3171e..0b2d8e8 100644 --- a/setup.php +++ b/setup.php @@ -99,7 +99,7 @@ function auditCheckUpgrade() { include_once $config['library_path'] . '/database.php'; include_once $config['library_path'] . '/functions.php'; - $files = array('plugins.php', 'audit.php'); + $files = ['plugins.php', 'audit.php']; if (isset($_SERVER['PHP_SELF']) && !in_array(basename($_SERVER['PHP_SELF']), $files)) { return; } @@ -306,12 +306,12 @@ function auditUtilitiesArray() { if (api_plugin_user_realm_auth('audit.php')) { $utilities[__('Technical Support', 'audit')] = array_merge( $utilities[__('Technical Support', 'audit')], - array( - __('View Audit Log', 'audit') => array( + [ + __('View Audit Log', 'audit') => [ 'link' => 'plugins/audit/audit.php', 'description' => __('Allows Administrators to view change activity on the Cacti server. Administrators can also export the audit log for analysis purposes.', 'audit') - ) - ) + ] + ] ); } } @@ -326,10 +326,10 @@ function auditConfigArrays() { global $menu, $messages, $audit_retentions, $utilities; if (isset($_SESSION['audit_message']) && $_SESSION['audit_message'] != '') { - $messages['audit_message'] = array('message' => $_SESSION['audit_message'], 'type' => 'info'); + $messages['audit_message'] = ['message' => $_SESSION['audit_message'], 'type' => 'info']; } - $audit_retentions = array( + $audit_retentions = [ -1 => __('Indefinitely', 'audit'), 14 => __('%d Weeks', 2, 'audit'), 30 => __('%d Month', 1, 'audit'), @@ -340,12 +340,12 @@ function auditConfigArrays() { 365 => __('%d Year', 1, 'audit'), 730 => __('%d Years', 2, 'audit'), 1095 => __('%d Years', 3, 'audit') - ); + ]; $menu[__('Utilities')]['plugins/audit/audit.php'] = __('Audit Log', 'audit'); if (function_exists('auth_augment_roles')) { - auth_augment_roles(__('System Administration'), array('audit.php')); + auth_augment_roles(__('System Administration'), ['audit.php']); } auditCheckUpgrade(); @@ -359,38 +359,38 @@ function auditConfigArrays() { function auditConfigSettings() { global $tabs, $settings, $item_rows, $audit_retentions; - $temp = array( - 'audit_header' => array( + $temp = [ + 'audit_header' => [ 'friendly_name' => __('Audit Log Settings', 'audit'), 'method' => 'spacer', - ), - 'audit_enabled' => array( + ], + 'audit_enabled' => [ 'friendly_name' => __('Enable Audit Log', 'audit'), 'description' => __('Check this box, if you want the Audit Log to track GUI activities.', 'audit'), 'method' => 'checkbox', 'default' => 'on' - ), - 'audit_retention' => array( + ], + 'audit_retention' => [ 'friendly_name' => __('Audit Log Retention', 'audit'), 'description' => __('How long do you wish Audit Log entries to be retained?', 'audit'), 'method' => 'drop_array', 'default' => '90', 'array' => $audit_retentions - ), - 'audit_log_external' => array( + ], + 'audit_log_external' => [ 'friendly_name' => __('External Audit Log', 'audit'), 'description' => __('Check this box, if you want the Audit Log to be written to an external file.', 'audit'), 'method' => 'checkbox', 'default' => 'off' - ), - 'audit_log_external_path' => array( + ], + 'audit_log_external_path' => [ 'friendly_name' => __('External Audit Log Log file Path', 'audit'), 'description' => __('Enter the path to the external audit log file.', 'audit'), 'method' => 'filepath', 'default' => '/var/www/html/cacti/log/audit.log', 'max_length' => '255' - ), - ); + ], + ]; $tabs['audit'] = __('Audit', 'audit'); @@ -409,12 +409,12 @@ function auditConfigSettings() { * @return array */ function auditDrawNavigationText($nav) { - $nav['audit.php:'] = array( + $nav['audit.php:'] = [ 'title' => __('Audit Event Log', 'audit'), 'mapping' => 'index.php:', 'url' => 'audit.php', 'level' => '1' - ); + ]; return $nav; } From c3ca75e7c621174c1b737bd53457082415a166e6 Mon Sep 17 00:00:00 2001 From: Sean Mancini Date: Fri, 27 Feb 2026 23:34:30 -0500 Subject: [PATCH 09/12] Add unit tests --- README.md | 8 +++ phpunit.xml.dist | 12 ++++ tests/AuditFunctionsTest.php | 108 +++++++++++++++++++++++++++++++++++ tests/bootstrap.php | 21 +++++++ 4 files changed, 149 insertions(+) create mode 100644 phpunit.xml.dist create mode 100644 tests/AuditFunctionsTest.php create mode 100644 tests/bootstrap.php diff --git a/README.md b/README.md index 064d75d..589e3e0 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,14 @@ data retention and turn on auditing. You can also enable file based logging for ingestion by Siem or Log analysis tools such as splunk +## Testing + +Run the unit tests with: + +```bash +phpunit -c phpunit.xml.dist +``` + ## Possible Bugs If you figure out this problem, see the Cacti forums! diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000..483e605 --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,12 @@ + + + + + tests + + + diff --git a/tests/AuditFunctionsTest.php b/tests/AuditFunctionsTest.php new file mode 100644 index 0000000..25bb523 --- /dev/null +++ b/tests/AuditFunctionsTest.php @@ -0,0 +1,108 @@ +assertArrayHasKey('host.php', $map); + $this->assertArrayHasKey('automation_devices.php', $map); + $this->assertStringContainsString('FROM host', $map['host.php']); + } + + public function testTransformsAutomationFieldsToReadableLabels(): void { + $rows = [ + ['snmp' => 1, 'up' => 0], + ['snmp' => 0, 'up' => 1], + ]; + + $out = auditTransformAutomationDevices($rows); + + $this->assertSame('UP', $out[0]['snmp']); + $this->assertSame('No', $out[0]['up']); + $this->assertSame('Down', $out[1]['snmp']); + $this->assertSame('Yes', $out[1]['up']); + } + + public function testReturnsEmptySelectionDefaultsWhenSelectedItemsMissing(): void { + [$items, $dropAction] = auditGetSelectedItemsData(['foo' => 'bar']); + + $this->assertSame([], $items); + $this->assertFalse($dropAction); + } + + public function testParsesSelectedItemsAndDropActionFromPayload(): void { + $post = [ + 'selected_items' => addslashes(serialize([101, 202])), + 'drp_action' => 4, + ]; + + [$items, $dropAction] = auditGetSelectedItemsData($post); + + $this->assertSame([101, 202], $items); + $this->assertSame(4, $dropAction); + } + + public function testResolvesBasePathFromConfigWhenCactiPathBaseIsNotDefined(): void { + $config = ['base_path' => '/tmp/cacti']; + + $this->assertSame('/tmp/cacti', auditGetBasePath($config)); + } + + public function testMapsKnownActionLabelsAndFallsBackForUnknownPages(): void { + $this->assertSame('Delete Device', auditResolveAction('automation_devices.php', 2, 'fallback')); + $this->assertSame('Host Disabled', auditResolveAction('host.php', 3, 'fallback')); + $this->assertSame('fallback', auditResolveAction('unknown.php', 1, 'fallback')); + } + + public function testSanitizesPostPayloadAndInfersDeleteAction(): void { + $_REQUEST = [ + '__csrf_magic' => 'token', + 'header' => 'header', + 'db_pass' => 'secret', + 'foo' => 'bar', + 'drp_action' => 1, + ]; + + $action = ''; + $post = auditPrepareRequestPost($action); + + $this->assertSame('delete', $action); + $this->assertArrayNotHasKey('__csrf_magic', $post); + $this->assertArrayNotHasKey('header', $post); + $this->assertArrayNotHasKey('db_pass', $post); + $this->assertSame('bar', $post['foo']); + } + + public function testBuildsGuiEventDataWithoutTouchingDatabaseWhenNoSelectedItems(): void { + $_REQUEST = [ + 'foo' => 'bar', + 'action' => 'save', + ]; + $_SERVER['SCRIPT_NAME'] = '/var/www/html/data_sources.php'; + $_SERVER['HTTP_USER_AGENT'] = 'UnitTestAgent'; + $_SESSION = []; + + $action = ''; + $config = ['base_path' => '/tmp/cacti']; + + $event = auditBuildGuiEventData($config, $action); + + $this->assertSame('data_sources.php', $event['page']); + $this->assertSame(0, $event['user_id']); + $this->assertSame('save', $event['action']); + $this->assertSame('127.0.0.1', $event['ip_address']); + $this->assertSame('UnitTestAgent', $event['user_agent']); + $this->assertSame('[]', $event['object_data']); + $this->assertSame('/tmp/cacti', $event['base_path']); + } +} diff --git a/tests/bootstrap.php b/tests/bootstrap.php new file mode 100644 index 0000000..d1d8896 --- /dev/null +++ b/tests/bootstrap.php @@ -0,0 +1,21 @@ + Date: Fri, 27 Feb 2026 23:39:23 -0500 Subject: [PATCH 10/12] update gha to run unit tests --- .github/workflows/plugin-ci-workflow.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/plugin-ci-workflow.yml b/.github/workflows/plugin-ci-workflow.yml index 87991cc..ce03d72 100644 --- a/.github/workflows/plugin-ci-workflow.yml +++ b/.github/workflows/plugin-ci-workflow.yml @@ -76,6 +76,7 @@ jobs: with: php-version: ${{ matrix.php }} extensions: intl, mysql, gd, ldap, gmp, xml, curl, json, mbstring + tools: phpunit ini-values: "post_max_size=256M, max_execution_time=60, date.timezone=America/New_York" - name: Check PHP version @@ -221,5 +222,8 @@ jobs: echo "No audit log entries found!" exit 1 fi - + - name: Run PHPUnit tests + run: | + cd ${{ github.workspace }}/cacti/plugins/audit + phpunit -c phpunit.xml.dist From 938bccb84ebac0f3404498f40a721ec8d4b483a7 Mon Sep 17 00:00:00 2001 From: Sean Mancini Date: Fri, 27 Feb 2026 23:42:00 -0500 Subject: [PATCH 11/12] apply typing --- audit.php | 20 ++++++++++---------- audit_functions.php | 28 ++++++++++++++-------------- setup.php | 32 ++++++++++++++++---------------- tests/bootstrap.php | 6 +++--- 4 files changed, 43 insertions(+), 43 deletions(-) diff --git a/audit.php b/audit.php index ef21ef2..7552349 100644 --- a/audit.php +++ b/audit.php @@ -142,7 +142,7 @@ * * @return void */ -function auditPurge() { +function auditPurge(): void { db_execute('TRUNCATE TABLE audit_log'); $_SESSION['audit_message'] = __('Audit Log Purged by %s', get_username($_SESSION['sess_user_id']), 'audit'); @@ -157,7 +157,7 @@ function auditPurge() { * * @return string */ -function auditBuildSqlWhereClause() { +function auditBuildSqlWhereClause(): string { $sql_where = ''; if (get_request_var('filter') != '') { @@ -184,7 +184,7 @@ function auditBuildSqlWhereClause() { * * @return string */ -function auditBuildPosterString($post_payload) { +function auditBuildPosterString(string $post_payload): string { $post = json_decode($post_payload); $poster = ''; @@ -210,7 +210,7 @@ function auditBuildPosterString($post_payload) { * * @return void */ -function auditRenderFilterForm($item_rows) { +function auditRenderFilterForm(array $item_rows): void { ?> \n"; return; @@ -332,7 +332,7 @@ function auditRenderEventsRows($events) { * * @return array> */ -function auditGetDisplayText() { +function auditGetDisplayText(): array { return [ 'page' => [ 'display' => __('Page Name', 'audit'), @@ -378,7 +378,7 @@ function auditGetDisplayText() { * * @return void */ -function auditExportRows() { +function auditExportRows(): void { processRequestVars(); $sql_where = auditBuildSqlWhereClause(); @@ -416,7 +416,7 @@ function auditExportRows() { * * @return string */ -function auditCsvEscape($string) { +function auditCsvEscape(string $string): string { $string = str_replace('"', '', $string); $string = str_replace(',', '|', $string); return $string; @@ -427,7 +427,7 @@ function auditCsvEscape($string) { * * @return void */ -function processRequestVars() { +function processRequestVars(): void { /* ================= input validation and session storage ================= */ $filters = [ 'rows' => [ @@ -476,7 +476,7 @@ function processRequestVars() { * * @return void */ -function auditLog() { +function auditLog(): void { global $item_rows; processRequestVars(); diff --git a/audit_functions.php b/audit_functions.php index dd8ebde..2aa03e0 100644 --- a/audit_functions.php +++ b/audit_functions.php @@ -5,7 +5,7 @@ * * @return array */ -function auditBuildPageQueryMap() { +function auditBuildPageQueryMap(): array { return [ 'host.php' => 'SELECT id AS host_id,site_id,description,hostname,status,status_fail_date AS last_failed_date,status_rec_date AS last_recovered_date FROM host WHERE id IN (?)', 'host_templates.php' => 'SELECT name FROM host_template WHERE id IN (?)', @@ -29,7 +29,7 @@ function auditBuildPageQueryMap() { * * @return array> */ -function auditTransformAutomationDevices($result) { +function auditTransformAutomationDevices(array $result): array { foreach ($result as &$row) { $row['snmp'] = ($row['snmp'] == 1) ? 'UP' : 'Down'; $row['up'] = ($row['up'] == 1) ? 'Yes' : 'No'; @@ -47,7 +47,7 @@ function auditTransformAutomationDevices($result) { * * @return string JSON-encoded object snapshot list. */ -function auditProcessPageData($page, $drop_action, $selected_items) { +function auditProcessPageData(string $page, int|false $drop_action, array $selected_items): string { if ($drop_action === false) { return json_encode([]); } @@ -77,7 +77,7 @@ function auditProcessPageData($page, $drop_action, $selected_items) { * * @return array */ -function auditPrepareRequestPost(&$action) { +function auditPrepareRequestPost(string &$action): array { $post = $_REQUEST; unset($post['__csrf_magic']); unset($post['header']); @@ -104,7 +104,7 @@ function auditPrepareRequestPost(&$action) { * * @return array{0: array, 1: int|false} */ -function auditGetSelectedItemsData($post) { +function auditGetSelectedItemsData(array $post): array { if (!isset($post['selected_items'])) { return [[], false]; } @@ -122,7 +122,7 @@ function auditGetSelectedItemsData($post) { * * @return string */ -function auditGetBasePath($config) { +function auditGetBasePath(array $config): string { if (defined('CACTI_PATH_BASE')) { return CACTI_PATH_BASE; } @@ -139,7 +139,7 @@ function auditGetBasePath($config) { * * @return string */ -function auditResolveAction($page, $drop_action, $action) { +function auditResolveAction(string $page, int|false $drop_action, string $action): string { $action_map = [ 'automation_devices.php' => [ 2 => 'Delete Device', @@ -166,7 +166,7 @@ function auditResolveAction($page, $drop_action, $action) { * * @return array */ -function auditBuildGuiEventData($config, &$action) { +function auditBuildGuiEventData(array $config, string &$action): array { $post = auditPrepareRequestPost($action); list($selected_items, $drop_action) = auditGetSelectedItemsData($post); @@ -199,7 +199,7 @@ function auditBuildGuiEventData($config, &$action) { * * @return void */ -function auditInsertGuiEvent($event) { +function auditInsertGuiEvent(array $event): void { db_execute_prepared('INSERT INTO audit_log (page, user_id, action, ip_address, user_agent, event_time, post, object_data) VALUES (?, ?, ?, ?, ?, ?, ?, ?)', [$event['page'], $event['user_id'], $event['action'], $event['ip_address'], $event['user_agent'], $event['event_time'], $event['post'], $event['object_data']]); @@ -212,7 +212,7 @@ function auditInsertGuiEvent($event) { * * @return string */ -function auditGetExternalLogPath($base_path) { +function auditGetExternalLogPath(string $base_path): string { $audit_log = read_config_option('audit_log_external_path'); if ($audit_log == '') { $audit_log = $base_path . '/log/audit.log'; @@ -229,7 +229,7 @@ function auditGetExternalLogPath($base_path) { * * @return void */ -function auditEnsureExternalLogFile($audit_log) { +function auditEnsureExternalLogFile(string $audit_log): void { if ($audit_log == '' || file_exists($audit_log)) { return; } @@ -250,7 +250,7 @@ function auditEnsureExternalLogFile($audit_log) { * * @return void */ -function auditWriteExternalLog($audit_log, $event) { +function auditWriteExternalLog(string $audit_log, array $event): void { if (read_config_option('audit_log_external') != 'on' || $audit_log == '' || !file_exists($audit_log)) { return; } @@ -279,7 +279,7 @@ function auditWriteExternalLog($audit_log, $event) { * * @return void */ -function auditInsertCliEvent() { +function auditInsertCliEvent(): void { $page = basename($_SERVER['argv'][0]); $user_id = 0; $action = 'cli'; @@ -306,7 +306,7 @@ function auditInsertCliEvent() { * * @return void */ -function auditConfigInsert() { +function auditConfigInsert(): void { global $action, $config; if (auditLogValidEvent()) { diff --git a/setup.php b/setup.php index 0b2d8e8..8f004e2 100644 --- a/setup.php +++ b/setup.php @@ -29,7 +29,7 @@ * * @return void */ -function pluginAuditInstall() { +function pluginAuditInstall(): void { api_plugin_register_hook('audit', 'config_arrays', 'auditConfigArrays', 'setup.php'); api_plugin_register_hook('audit', 'config_settings', 'auditConfigSettings', 'setup.php'); api_plugin_register_hook('audit', 'config_insert', 'auditConfigInsert', 'setup.php'); @@ -51,7 +51,7 @@ function pluginAuditInstall() { * * @return bool */ -function pluginAuditUninstall() { +function pluginAuditUninstall(): bool { db_execute('DROP TABLE IF EXISTS audit_log'); return true; } @@ -63,7 +63,7 @@ function pluginAuditUninstall() { * * @return bool */ -function auditIsConsolePage($url) { +function auditIsConsolePage(string $url): bool { if (strpos($url, 'audit.php') !== false) { return true; } @@ -76,7 +76,7 @@ function auditIsConsolePage($url) { * * @return bool */ -function pluginAuditCheckConfig() { +function pluginAuditCheckConfig(): bool { return true; } @@ -85,7 +85,7 @@ function pluginAuditCheckConfig() { * * @return bool */ -function pluginAuditUpgrade() { +function pluginAuditUpgrade(): bool { return true; } @@ -94,7 +94,7 @@ function pluginAuditUpgrade() { * * @return void */ -function auditCheckUpgrade() { +function auditCheckUpgrade(): void { global $config, $database_default; include_once $config['library_path'] . '/database.php'; include_once $config['library_path'] . '/functions.php'; @@ -139,7 +139,7 @@ function auditCheckUpgrade() { * * @return array */ -function auditCheckDependencies($data) { +function auditCheckDependencies(array $data): array { $remote_poller_id = $data['remote_poller_id']; $rcnn_id = $data['rcnn_id']; $class = $data['class']; @@ -162,7 +162,7 @@ function auditCheckDependencies($data) { * * @return array */ -function auditReplicateOut($data) { +function auditReplicateOut(array $data): array { $remote_poller_id = $data['remote_poller_id']; $rcnn_id = $data['rcnn_id']; $class = $data['class']; @@ -196,7 +196,7 @@ function auditReplicateOut($data) { * * @return void */ -function auditPollerBottom() { +function auditPollerBottom(): void { $last_check = read_config_option('audit_last_check'); $now = date('d'); @@ -219,7 +219,7 @@ function auditPollerBottom() { * * @return bool */ -function auditSetupTable() { +function auditSetupTable(): bool { global $config, $database_default; include_once $config['library_path'] . '/database.php'; @@ -250,7 +250,7 @@ function auditSetupTable() { * * @return array */ -function pluginAuditVersion() { +function pluginAuditVersion(): array { global $config; $info = parse_ini_file($config['base_path'] . '/plugins/audit/INFO', true); return $info['info']; @@ -261,7 +261,7 @@ function pluginAuditVersion() { * * @return bool */ -function auditLogValidEvent() { +function auditLogValidEvent(): bool { global $action; $valid = false; @@ -299,7 +299,7 @@ function auditLogValidEvent() { * * @return void */ -function auditUtilitiesArray() { +function auditUtilitiesArray(): void { global $utilities; if (version_compare(CACTI_VERSION, '1.3.0', '<')) { @@ -322,7 +322,7 @@ function auditUtilitiesArray() { * * @return void */ -function auditConfigArrays() { +function auditConfigArrays(): void { global $menu, $messages, $audit_retentions, $utilities; if (isset($_SESSION['audit_message']) && $_SESSION['audit_message'] != '') { @@ -356,7 +356,7 @@ function auditConfigArrays() { * * @return void */ -function auditConfigSettings() { +function auditConfigSettings(): void { global $tabs, $settings, $item_rows, $audit_retentions; $temp = [ @@ -408,7 +408,7 @@ function auditConfigSettings() { * * @return array */ -function auditDrawNavigationText($nav) { +function auditDrawNavigationText(array $nav): array { $nav['audit.php:'] = [ 'title' => __('Audit Event Log', 'audit'), 'mapping' => 'index.php:', diff --git a/tests/bootstrap.php b/tests/bootstrap.php index d1d8896..a828418 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -1,19 +1,19 @@ Date: Fri, 27 Feb 2026 23:49:12 -0500 Subject: [PATCH 12/12] Update .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 3dd84d9..c359708 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,4 @@ locales/po/*.mo +vendor/ \ No newline at end of file
'; + $output .= '' . __('Page:', 'audit') . ' ' . $data['page'] . ''; + $output .= '
' . __('User:', 'audit') . ' ' . $data['user_agent'] . ''; + $output .= '
' . __('IP Address:', 'audit') . ' ' . $data['ip_address'] . ''; + $output .= '
' . __('Date:', 'audit') . ' ' . $data['event_time'] . ''; + $output .= '
' . __('Action:', 'audit') . ' ' . $data['action'] . ''; + $output .= '
'; + $output .= '' . __('Script:', 'audit') . ' ' . $data['post'] . ''; + } elseif (cacti_sizeof($data)) { + $attribs = json_decode($data['post']); + + $nattribs = array(); + foreach($attribs as $field => $content) { + $nattribs[$field] = $content; + } + ksort($nattribs); + + if (cacti_sizeof($nattribs) > 16) { + $width = 'wide'; + } else { + $width = 'narrow'; + } + + $output .= ''; + $output .= ''; + + foreach ($recordData as $record) { + $output .= ''; + } + } else { + $output .= '
'; + $output .= '' . __('Page:', 'audit') . ' ' . $data['page'] . ''; + $output .= '
' . __('User:', 'audit') . ' ' . get_username($data['user_id']) . ''; + $output .= '
' . __('IP Address:', 'audit') . ' ' . $data['ip_address'] . ''; + $output .= '
' . __('Date:', 'audit') . ' ' . $data['event_time'] . ''; + $output .= '
' . __('Action:', 'audit') . ' ' . $data['action'] . ''; + $output .= '
'; + $output .= ''; + + if (cacti_sizeof($nattribs) > 16) { + $columns = 2; + $output .= ''; + } else { + $columns = 1; + $output .= ''; + } + + $i = 0; + if (cacti_sizeof($nattribs)) { + foreach($nattribs as $field => $content) { + if ($i % $columns == 0) { + $output .= ($output != '' ? '':'') . ''; + } + + if (is_array($content)) { + $output .= '' . implode(',', $content) . ''; + } else { + $output .= ''; + } + + $i++; + } + + if ($i % $columns > 0) { + $output . ''; + } + } + + // Display the Record Data under selected_items if it is not empty + $recordData = json_decode($data['object_data']); + if (!empty($recordData)) { + $output .= '
' . __('Attrib', 'audit') . '' . __('Value', 'audit') . '' . __('Attrib', 'audit') . '' . __('Value', 'audit') . '
' . __('Attrib', 'audit') . '' . __('Value', 'audit') . '
' . $field . '' . $field . '' . $content . '
'; + $output .= '

' . __('Record Data:', 'audit') . '
' . json_encode($record, JSON_PRETTY_PRINT) . '
'; + } + } + + // Output the final result + echo $output; + + + break; default: - top_header(); - auditLog(); - bottom_footer(); + top_header(); + auditLog(); + bottom_footer(); } function auditPurge() { - db_execute('TRUNCATE TABLE audit_log'); + db_execute('TRUNCATE TABLE audit_log'); - $_SESSION['audit_message'] = __('Audit Log Purged by %s', get_username($_SESSION['sess_user_id']), 'audit'); + $_SESSION['audit_message'] = __('Audit Log Purged by %s', get_username($_SESSION['sess_user_id']), 'audit'); - cacti_log('NOTE: Audit Log Purged by ' . get_username($_SESSION['sess_user_id']), false, 'WEBUI'); + cacti_log('NOTE: Audit Log Purged by ' . get_username($_SESSION['sess_user_id']), false, 'WEBUI'); - raise_message('audit_message'); + raise_message('audit_message'); } function auditBuildSqlWhereClause() { - $sql_where = ''; + $sql_where = ''; - if (get_request_var('filter') != '') { - $sql_where = 'WHERE ( - page LIKE ' . db_qstr('%' . get_request_var('filter') . '%') . ' - OR post LIKE ' . db_qstr('%' . get_request_var('filter') . '%') . ')'; - } + if (get_request_var('filter') != '') { + $sql_where = 'WHERE ( + page LIKE ' . db_qstr('%' . get_request_var('filter') . '%') . ' + OR post LIKE ' . db_qstr('%' . get_request_var('filter') . '%') . ')'; + } - if (get_request_var('event_page') != '-1') { - $sql_where .= ($sql_where != '' ? ' AND ' : 'WHERE ') . ' page = ' . db_qstr(get_request_var('event_page')); - } + if (get_request_var('event_page') != '-1') { + $sql_where .= ($sql_where != '' ? ' AND ' : 'WHERE ') . ' page = ' . db_qstr(get_request_var('event_page')); + } - if (!isempty_request_var('user_id') && get_request_var('user_id') > '-1') { - $sql_where .= ($sql_where != '' ? ' AND ' : 'WHERE ') . ' user_id = ' . get_request_var('user_id'); - } + if (!isempty_request_var('user_id') && get_request_var('user_id') > '-1') { + $sql_where .= ($sql_where != '' ? ' AND ' : 'WHERE ') . ' user_id = ' . get_request_var('user_id'); + } - return $sql_where; + return $sql_where; } function auditBuildPosterString($post_payload) { - $post = json_decode($post_payload); - $poster = ''; - - if (!is_object($post)) { - return $poster; - } - - foreach ($post as $var => $value) { - if (is_array($value)) { - $poster .= ($poster != '' ? '|' : '') . $var . ':' . implode('%', $value); - } else { - $poster .= ($poster != '' ? '|' : '') . $var . ':' . $value; - } - } - - return $poster; + $post = json_decode($post_payload); + $poster = ''; + + if (!is_object($post)) { + return $poster; + } + + foreach ($post as $var => $value) { + if (is_array($value)) { + $poster .= ($poster != '' ? '|' : '') . $var . ':' . implode('%', $value); + } else { + $poster .= ($poster != '' ? '|' : '') . $var . ':' . $value; + } + } + + return $poster; } function auditRenderFilterForm($item_rows) { - ?> -
-
- - - - - - - - - - - - -
- - - '> - - - - - - - - - - - - - - - - - - - -
- '> -
-
+
+ + + + + + + + + + + + +
+ + + '> + + + + + + + + + + + + + + + + + + + +
+ '> +
+
" . __('No Audit Log Events Found', 'audit') . "
" . __('No Audit Log Events Found', 'audit') . "
" . __('No Audit Log Events Found', 'audit') . "
@@ -298,7 +298,7 @@ function auditRenderFilterForm($item_rows) { * * @return void */ -function auditRenderEventsRows($events) { +function auditRenderEventsRows(array $events): void { if (!cacti_sizeof($events)) { print "
" . __('No Audit Log Events Found', 'audit') . "