Server IP : 104.21.14.48 / Your IP : 3.144.242.40 [ Web Server : Apache System : Linux b70eb322-3aee-0c53-7c82-0db91281f2c6.secureserver.net 6.1.90-1.el9.elrepo.x86_64 #1 SMP PREEMPT_DYNAMIC Thu May 2 12:09:22 EDT 2024 x86_64 User : root ( 0) PHP Version : 8.0.30.2 Disable Function : NONE Domains : 0 Domains MySQL : ON | cURL : ON | WGET : ON | Perl : OFF | Python : OFF | Sudo : OFF | Pkexec : OFF Directory : /var/www/wp-content/plugins/defender-security/src/controller/ |
Upload File : |
<?php /** * Handles audit logging functionalities . * * @package WP_Defender\Controller */ namespace WP_Defender\Controller; use DateTime; use Exception; use DateInterval; use WP_Defender\Event; use Calotes\Helper\HTTP; use WP_Defender\Traits\User; use Calotes\Component\Request; use Calotes\Component\Response; use Calotes\Helper\Array_Cache; use WP_Defender\Traits\Formats; use WP_Defender\Component\Audit; use WP_Defender\Model\Audit_Log; use WP_Defender\Behavior\WPMUDEV; use WP_Defender\Model\Notification\Audit_Report; use WP_Defender\Component\Config\Config_Hub_Helper; use WP_Defender\Model\Setting\Audit_Logging as Model_Audit_Logging; /** * Handles audit logging functionalities . */ class Audit_Logging extends Event { use User; use Formats; /** * The slug identifier for this controller. * * @var string */ public $slug = 'wdf-logging'; /** * The model for handling the data. * * @var Model_Audit_Logging */ public $model; /** * Service for handling logic. * * @var Audit|null */ public ?Audit $service; /** * Initializes the model and service, registers routes, and sets up scheduled events if the model is active. */ public function __construct() { $this->register_page( esc_html( Model_Audit_Logging::get_module_name() ), $this->slug, array( &$this, 'main_view', ), $this->parent_slug ); add_action( 'defender_enqueue_assets', array( &$this, 'enqueue_assets' ) ); $this->model = wd_di()->get( Model_Audit_Logging::class ); $this->service = new Audit(); $this->register_routes(); if ( $this->model->is_active() ) { $this->service->enqueue_event_listener(); add_action( 'shutdown', array( &$this, 'cache_audit_logs' ) ); /** * We will schedule the time for flush data into cloud. */ if ( ! wp_next_scheduled( 'audit_sync_events' ) ) { wp_schedule_event( time() + 15, 'hourly', 'audit_sync_events' ); } add_action( 'audit_sync_events', array( &$this, 'sync_events' ) ); /** * We will schedule the time to clean up old logs. */ if ( ! wp_next_scheduled( 'audit_clean_up_logs' ) ) { wp_schedule_event( time(), 'hourly', 'audit_clean_up_logs' ); } add_action( 'audit_clean_up_logs', array( &$this, 'clean_up_audit_logs' ) ); } } /** * Sync all the events into cloud, this will happen per hourly basis. * * @return void */ public function sync_events(): void { $this->service->flush(); } /** * Clean up all the old logs from the local storage, this will happen per hourly basis. * * @return void * @throws Exception When the $duration cannot be parsed as an interval. */ public function clean_up_audit_logs(): void { $this->service->audit_clean_up_logs(); } /** * Exports audit logs as a CSV file. * * @return void * @throws Exception If there is an error during export. * @defender_route */ public function export_as_csv(): void { $date_from = HTTP::get( 'date_from', wp_date( 'Y-m-d H:i:s', strtotime( '-7 days', time() ) ) ); $date_to = HTTP::get( 'date_to', wp_date( 'Y-m-d H:i:s', time() ) ); // Convert date using timezone. $timezone = wp_timezone(); $date_from = ( new DateTime( $date_from, $timezone ) )->setTime( 0, 0, 0 )->getTimestamp(); $date_to = ( new DateTime( $date_to, $timezone ) )->setTime( 23, 59, 59 )->getTimestamp(); $username = HTTP::get( 'term', '' ); $user_id = ''; $user = get_user_by( 'login', $username ); $events = HTTP::get( 'event_type', array() ); if ( is_object( $user ) ) { $user_id = $user->ID; } $handler = new Audit(); $ip_address = HTTP::get( 'ip_address', '' ); $result = $handler->fetch( $date_from, $date_to, $events, $user_id, $ip_address, false ); // WP_Filesystem class doesn’t directly provide a function for opening a stream to php://memory with the 'w' mode. $fp = fopen( 'php://memory', 'w' ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen $headers = array( esc_html__( 'Summary', 'defender-security' ), esc_html__( 'Date / Time', 'defender-security' ), esc_html__( 'Context', 'defender-security' ), esc_html__( 'Type', 'defender-security' ), esc_html__( 'IP address', 'defender-security' ), esc_html__( 'User', 'defender-security' ), ); fputcsv( $fp, $headers ); foreach ( $result as $log ) { $fields = $log->export(); $vars = array( $fields['msg'], is_array( $fields['timestamp'] ) ? $this->format_date_time( wp_date( 'Y-m-d H:i:s', $fields['timestamp'][0] ) ) : $this->format_date_time( wp_date( 'Y-m-d H:i:s', $fields['timestamp'] ) ), $fields['context'], $fields['action_type'], $fields['ip'], $this->get_user_display( $fields['user_id'] ), ); fputcsv( $fp, $vars ); } $filename = 'wdf-audit-logs-export-' . wp_date( 'ymdHis' ) . '.csv'; fseek( $fp, 0 ); header( 'Content-Type: text/csv' ); header( 'Content-Disposition: attachment; filename="' . $filename . '";' ); // Make php send the generated csv lines to the browser. fpassthru( $fp ); exit(); } /** * We'll pass all the event logs into the db handler, so it writes down to db. * Do it in shutdown runtime, so no delay time. * * @return void */ public function cache_audit_logs(): void { $audit = new Audit(); $audit->log_audit_events(); } /** * Pull the logs from db cached: * - date_from: the start of the date we will run the query, as mysql time format, * - date_to: similar to the above, * others will refer to Audit. * * @param Request $request The request object containing filter parameters. * * @return Response * @throws Exception If there is an error during log retrieval. * @defender_route */ public function pull_logs( Request $request ): Response { $data = $request->get_data( array( 'date_from' => array( 'type' => 'string', 'sanitize' => 'sanitize_text_field', ), 'date_to' => array( 'type' => 'string', 'sanitize' => 'sanitize_text_field', ), 'username' => array( 'type' => 'string', 'sanitize' => 'sanitize_text_field', ), 'events' => array( 'type' => 'array', 'sanitize' => 'sanitize_text_field', ), 'ip_address' => array( 'type' => 'string', 'sanitize' => 'sanitize_text_field', ), 'paged' => array( 'type' => 'int', 'sanitize' => 'sanitize_text_field', ), ) ); if ( empty( $data['date_from'] ) || empty( $data['date_to'] ) ) { return new Response( false, array( 'message' => esc_html__( 'Invalid data.', 'defender-security' ) ) ); } // Convert date using timezone. $timezone = wp_timezone(); $date_from = ( new DateTime( $data['date_from'], $timezone ) ) ->setTime( 0, 0, 0 ) ->getTimestamp(); $date_to = ( new DateTime( $data['date_to'], $timezone ) ) ->setTime( 23, 59, 59 ) ->getTimestamp(); $events = $data['events'] ?? array(); $ip_address = $data['ip_address'] ?? ''; $paged = $data['paged'] ?? 1; $username = $data['username'] ?? ''; $user_id = ''; if ( ! empty( $username ) ) { $user = get_user_by( 'login', $username ); if ( is_object( $user ) ) { $user_id = $user->ID; // Fetch result with the specified user. $result = $this->service->fetch( $date_from, $date_to, $events, $user_id, $ip_address, $paged ); } else { // A non-existent username. $result = array(); } } else { // Fetch result with empty user field. $result = $this->service->fetch( $date_from, $date_to, $events, $user_id, $ip_address, $paged ); } if ( is_wp_error( $result ) ) { return new Response( false, array( 'message' => $result->get_error_message() ) ); } $logs = array(); if ( ! empty( $result ) ) { foreach ( $result as $item ) { $logs[] = array_merge( $item->export(), array( 'user' => $this->get_user_display( $item->user_id ), 'user_url' => (int) $item->user_id > 0 ? get_edit_user_link( $item->user_id ) : '', 'log_date' => $this->get_date( $item->timestamp ), 'format_date' => $this->format_date_time( wp_date( 'Y-m-d H:i:s', $item->timestamp ) ), ) ); } } // @since 3.0.0 If no logs then $count = 0. if ( empty( $logs ) ) { $count = 0; } else { $count = Audit_Log::count( $date_from, $date_to, $events, $user_id, $ip_address ); } $per_page = 20; // Get the count for the submitted data. return new Response( true, array( 'logs' => $logs, 'total_items' => $count, 'total_pages' => ceil( $count / $per_page ), 'per_page' => $per_page, ) ); } /** * Generates a human-readable frequency text for audit reports. * * @param Audit_Report $audit_report The audit report object. * * @return string Returns the formatted frequency description. */ public function get_frequency_text( Audit_Report $audit_report ): string { $text = ''; switch ( $audit_report->frequency ) { case 'daily': $text = ucfirst( $audit_report->day ) . 's at ' . $audit_report->time; break; case 'weekly': case 'monthly': $text = ucfirst( $audit_report->frequency ) . ' on ' . ucfirst( $audit_report->day ) . 's at ' . $audit_report->time; break; default: break; } return $text; } /** * Enqueues scripts and styles for this page. * Only enqueues assets if the page is active. */ public function enqueue_assets() { if ( ! $this->is_page_active() ) { return; } wp_enqueue_script( 'def-moment', defender_asset_url( '/assets/js/vendor/moment/moment.min.js' ), array(), DEFENDER_VERSION, true ); wp_enqueue_script( 'def-daterangepicker', defender_asset_url( '/assets/js/vendor/daterangepicker/daterangepicker.js' ), array(), DEFENDER_VERSION, true ); wp_localize_script( 'def-audit', 'audit', $this->data_frontend() ); wp_enqueue_script( 'def-audit' ); $this->enqueue_main_assets(); } /** * Render the root element for frontend. * * @return void */ public function main_view(): void { $this->render( 'main' ); } /** * Provides a summary of audit logs. * * @return void * @throws Exception If there is an error during summary generation. * @defender_route */ public function summary(): void { $response = $this->model->is_active() ? $this->summary_data() : array(); wp_send_json_success( $response ); } /** * Returns an array with summary data for audit logging. * * @param bool $for_hub Default 'false' because it's displayed on site summary sections. * * @return array * @throws Exception Emits Exception in case of an error. */ public function summary_data( bool $for_hub = false ): array { // Monthly count. $date_from = ( new DateTime( wp_date( 'Y-m-d', strtotime( '-30 days' ) ) ) ) ->setTime( 0, 0, 0 ) ->getTimestamp(); $date_to = ( new DateTime( wp_date( 'Y-m-d' ) ) )->setTime( 23, 59, 59 )->getTimestamp(); $month_count = Audit_Log::count( $date_from, $date_to ); // Weekly count. $date_from = ( new DateTime( wp_date( 'Y-m-d', strtotime( '-7 days' ) ) ) ) ->setTime( 0, 0, 0 ) ->getTimestamp(); $week_count = Audit_Log::count( $date_from, $date_to ); // Daily count. Sync data to the Hub without timezone. $date_from = $for_hub ? new DateTime( 'now' ) : new DateTime( 'now', wp_timezone() ); $date_from = $date_from->modify( '-24 hours' )->setTime( 0, 0, 0 )->getTimestamp(); $day_count = Audit_Log::count( $date_from, $date_to ); // Get the last item. $last = Audit_Log::get_last(); if ( is_object( $last ) ) { $last = $for_hub ? $this->persistent_hub_datetime_format( $last->timestamp ) : $this->format_date_time( $last->timestamp ); } else { $last = 'n/a'; } return array( 'monthCount' => $month_count, 'weekCount' => $week_count, 'dayCount' => $day_count, 'lastEvent' => $last, ); } /** * Save settings. * * @param Request $request The request object containing new settings data. * * @return Response * @defender_route */ public function save_settings( Request $request ): Response { $data = $request->get_data_by_model( $this->model ); if ( false === $data['enabled'] && $data['enabled'] !== $this->model->is_active() ) { // Toggle off, so we need to flush everything to cloud. $this->service->flush(); } $this->model->import( $data ); if ( $this->model->validate() ) { $this->model->save(); } Config_Hub_Helper::set_clear_active_flag(); return new Response( true, array_merge( $this->data_frontend(), array( 'message' => esc_html__( 'Your settings have been updated.', 'defender-security' ), 'auto_close' => true, ) ) ); } /** * Converts the current state of the object to an array. * * @return array Returns an associative array of object properties. */ public function to_array(): array { return array_merge( array( 'enabled' => $this->model->is_active(), 'report' => true, ), $this->dump_routes_and_nonces() ); } /** * Removes settings for all submodules. */ public function remove_settings(): void { ( new Model_Audit_Logging() )->delete(); } /** * Delete all the data & the cache. */ public function remove_data(): void { Audit_Log::truncate(); // Remove cached data. Array_Cache::remove( 'sockets', 'audit' ); Array_Cache::remove( 'logs', 'audit' ); Array_Cache::remove( 'menu_updated', 'audit' ); Array_Cache::remove( 'post_updated', 'audit' ); delete_site_option( Audit::CACHE_LAST_CHECKPOINT ); } /** * Provides data for the frontend. * * @return array An array of data for the frontend. * @throws Exception If there is an error. */ public function data_frontend(): array { $logs = array(); $count = 0; $per_page = 20; $total_page = 1; if ( $this->model->is_active() ) { $timezone = wp_timezone(); $date_from = ( new DateTime() )->setTimezone( $timezone ) ->sub( new DateInterval( 'P7D' ) )->setTime( 0, 0, 0 ); $date_to = ( new DateTime() )->setTimezone( $timezone )->setTime( 23, 59, 59 ); $result = $this->service->fetch( $date_from->getTimestamp(), $date_to->getTimestamp(), array(), '', '', 1 ); if ( ! is_wp_error( $result ) ) { foreach ( $result as $item ) { $logs[] = array_merge( $item->export(), array( 'user' => $this->get_user_display( $item->user_id ), 'user_url' => (int) $item->user_id > 0 ? get_edit_user_link( $item->user_id ) : '', 'log_date' => $this->get_date( $item->timestamp ), 'format_date' => $this->format_date_time( wp_date( 'Y-m-d H:i:s', $item->timestamp ) ), ) ); } $count = Audit_Log::count( $date_from->getTimestamp(), $date_to->getTimestamp() ); $total_page = ceil( $count / $per_page ); } } return array_merge( array( 'model' => $this->model->export(), 'logs' => $logs, 'events_type' => Audit_Log::allowed_events(), 'summary' => array( 'count_7_days' => $count, 'report' => wd_di()->get( Audit_Report::class )->to_string(), ), 'paging' => array( 'paged' => 1, 'total_pages' => $total_page, 'count' => $count, ), ), $this->dump_routes_and_nonces() ); } /** * Imports data into the model. * * @param array $data Data to be imported into the model. * * @throws Exception If table is not defined. */ public function import_data( array $data ) { $model = $this->model; if ( empty( $data ) ) { $model->enabled = false; $model->storage_days = '6 months'; $model->save(); } else { $model->import( $data ); if ( $model->validate() ) { $model->save(); } } } /** * Exports strings. * * @return array An array of strings. */ public function export_strings(): array { if ( ! ( new WPMUDEV() )->is_pro() ) { return array( sprintf( /* translators: %s: Html for Pro-tag. */ esc_html__( 'Inactive %s', 'defender-security' ), '<span class="sui-tag sui-tag-pro">Pro</span>' ), ); } if ( $this->model->is_active() ) { $strings = array( esc_html__( 'Active', 'defender-security' ) ); $audit_report = new Audit_Report(); if ( 'enabled' === $audit_report->status ) { $strings[] = sprintf( /* translators: %s: Frequency value. */ esc_html__( 'Email reports sending %s', 'defender-security' ), $audit_report->frequency ); } } else { $strings = array( esc_html__( 'Inactive', 'defender-security' ) ); } return $strings; } /** * Generates configuration strings based on the provided configuration and * whether the product is a pro version. * * @param array $config Configuration data. * @param bool $is_pro Indicates if the product is a pro version. * * @return array Returns an array of configuration strings. */ public function config_strings( array $config, bool $is_pro ): array { if ( $is_pro ) { if ( $config['enabled'] ) { $strings = array( esc_html__( 'Active', 'defender-security' ) ); if ( isset( $config['report'] ) && 'enabled' === $config['report'] ) { $strings[] = sprintf( /* translators: %s: Frequency value. */ esc_html__( 'Email reports sending %s', 'defender-security' ), $config['frequency'] ); } } else { $strings = array( esc_html__( 'Inactive', 'defender-security' ) ); } } else { $strings = array( sprintf( /* translators: %s: Html for Pro-tag. */ esc_html__( 'Inactive %s', 'defender-security' ), '<span class="sui-tag sui-tag-pro">Pro</span>' ), ); } return $strings; } }