Files
nolimit-discounts/includes/class-audit-log.php
T

154 lines
3.8 KiB
PHP

<?php
/**
* Audit log for REST API activity.
*
* Records every write to the wc-edm REST API and every rejected request,
* so unauthorized access attempts are visible after the fact.
*
* @package WC_Email_Discount_Manager
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Writes and maintains the audit log.
*/
class WC_EDM_Audit_Log {
/**
* Cron hook used for pruning old entries.
*/
const CRON_HOOK = 'wc_edm_prune_audit_log';
/**
* Hook the daily prune job.
*/
public function __construct() {
add_action( self::CRON_HOOK, array( __CLASS__, 'prune' ) );
add_action( 'init', array( __CLASS__, 'schedule_prune' ) );
}
/**
* Make sure the daily prune job is scheduled.
*/
public static function schedule_prune() {
if ( ! wp_next_scheduled( self::CRON_HOOK ) ) {
wp_schedule_event( time() + HOUR_IN_SECONDS, 'daily', self::CRON_HOOK );
}
}
/**
* Remove the scheduled prune job (used on deactivation).
*/
public static function unschedule_prune() {
$timestamp = wp_next_scheduled( self::CRON_HOOK );
if ( $timestamp ) {
wp_unschedule_event( $timestamp, self::CRON_HOOK );
}
}
/**
* Whether audit logging is enabled.
*
* @return bool
*/
public static function is_enabled() {
/**
* Filter whether REST activity is written to the audit log.
*
* @param bool $enabled Default true.
*/
return (bool) apply_filters( 'wc_edm_audit_log_enabled', true );
}
/**
* How many days entries are kept.
*
* @return int
*/
public static function retention_days() {
/**
* Filter the audit log retention period in days.
*
* @param int $days Default 30.
*/
return max( 1, (int) apply_filters( 'wc_edm_audit_log_retention_days', 30 ) );
}
/**
* Resolve the client IP address.
*
* The shop may sit behind a reverse proxy (e.g. Cloudflare), where
* REMOTE_ADDR is the proxy rather than the visitor. Proxy headers are
* only trustworthy when the proxy is guaranteed to set them, so this is
* filterable for setups that need different handling.
*
* @return string
*/
public static function get_client_ip() {
$ip = '';
if ( ! empty( $_SERVER['HTTP_CF_CONNECTING_IP'] ) ) {
$ip = sanitize_text_field( wp_unslash( $_SERVER['HTTP_CF_CONNECTING_IP'] ) );
} elseif ( ! empty( $_SERVER['REMOTE_ADDR'] ) ) {
$ip = sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) );
}
$ip = filter_var( $ip, FILTER_VALIDATE_IP ) ? $ip : '';
/**
* Filter the resolved client IP used for logging and rate limiting.
*
* @param string $ip Resolved IP address.
*/
return (string) apply_filters( 'wc_edm_client_ip', $ip );
}
/**
* Record an entry.
*
* @param string $outcome One of allowed|denied|rate_limited.
* @param string $method HTTP method.
* @param string $route REST route.
* @param int $status HTTP status code.
* @param array $details Optional extra context.
* @return void
*/
public static function record( $outcome, $method, $route, $status = 0, $details = array() ) {
if ( ! self::is_enabled() ) {
return;
}
if ( ! in_array( $outcome, array( 'allowed', 'denied', 'rate_limited' ), true ) ) {
$outcome = 'allowed';
}
$user = wp_get_current_user();
WC_EDM_DB::insert_audit_entry(
array(
'user_id' => $user ? (int) $user->ID : 0,
'user_login' => $user && $user->user_login ? $user->user_login : '',
'ip' => self::get_client_ip(),
'method' => strtoupper( substr( (string) $method, 0, 10 ) ),
'route' => substr( (string) $route, 0, 191 ),
'status' => (int) $status,
'outcome' => $outcome,
'details' => ! empty( $details ) ? wp_json_encode( $details ) : null,
)
);
}
/**
* Delete entries older than the retention period.
*
* @return void
*/
public static function prune() {
WC_EDM_DB::prune_audit_log( self::retention_days() );
}
}