154 lines
3.8 KiB
PHP
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() );
|
|
}
|
|
}
|