| 1 |
<?php |
| 2 |
/* |
| 3 |
* Plugin Name: WP Admin Audit |
| 4 |
* Plugin URI: https://wpadminaudit.com/ |
| 5 |
* Description: Monitor the security relevant activities on your site and get notified when something out of the usual is happening. Browse the event log to find out who did what at which time. |
| 6 |
* Version: 1.2.5 |
| 7 |
* Author: brandtoss |
| 8 |
* Author URI: https://wpadminaudit.com/ |
| 9 |
* License: GPL2 |
| 10 |
* License URI: http://www.gnu.org/licenses/gpl-2.0.txt |
| 11 |
* Requires at least: 5.5 |
| 12 |
* Requires PHP: 5.6 |
| 13 |
* |
| 14 |
* Text Domain: wp-admin-audit |
| 15 |
* Domain Path: /languages |
| 16 |
* |
| 17 |
*/ |
| 18 |
|
| 19 |
|
| 20 |
/* |
| 21 |
WP Admin Audit is free software; you can redistribute it and/or modify |
| 22 |
it under the terms of the GNU General Public License, version 2, as |
| 23 |
published by the Free Software Foundation. |
| 24 |
|
| 25 |
WP Admin Audit is distributed in the hope that it will be useful, |
| 26 |
but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 27 |
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 28 |
GNU General Public License for more details. |
| 29 |
|
| 30 |
You should have received a copy of the GNU General Public License |
| 31 |
along with WP Admin Audit. If not, see <http://www.gnu.org/licenses/>. |
| 32 |
*/ |
| 33 |
|
| 34 |
|
| 35 |
if ( ! class_exists( 'WpAdminAudit' ) ) { |
| 36 |
class WpAdminAudit |
| 37 |
{ |
| 38 |
public $version = '1.2.5'; |
| 39 |
protected $eventListener; |
| 40 |
|
| 41 |
public function __construct() |
| 42 |
{ |
| 43 |
self::errorLog('construct'); |
| 44 |
if (self::onFrontend()) { |
| 45 |
add_action('wp_loaded', array($this, 'setupWADA'), 0); |
| 46 |
}else{ |
| 47 |
add_action('plugins_loaded', array($this, 'setupWADA'), 8); |
| 48 |
} |
| 49 |
|
| 50 |
register_activation_hook(__FILE__, array($this, 'installWADA')); |
| 51 |
register_deactivation_hook(__FILE__, array($this, 'deactivateWADA')); |
| 52 |
add_filter('cron_schedules', array($this, 'addCronSchedules')); |
| 53 |
} |
| 54 |
|
| 55 |
protected static function errorLog($msg){ |
| 56 |
if(self::isHeartbeatRequest()) return; |
| 57 |
//$requestId = sprintf("%08x", abs(crc32($_SERVER['REMOTE_ADDR'] . $_SERVER['REQUEST_TIME'] . $_SERVER['REMOTE_PORT']))); |
| 58 |
//error_log($requestId."\t".$msg); |
| 59 |
} |
| 60 |
|
| 61 |
public static function getInstance() { |
| 62 |
static $instance = null; |
| 63 |
if (!$instance) { |
| 64 |
$instance = new self(); |
| 65 |
} |
| 66 |
return $instance; |
| 67 |
} |
| 68 |
|
| 69 |
public function setupWADA(){ |
| 70 |
self::errorLog('setupWADA'); |
| 71 |
if(self::isHeartbeatRequest()){ |
| 72 |
return; // do not set up in that scenario |
| 73 |
} |
| 74 |
|
| 75 |
// Find out what kind of fun WP was having |
| 76 |
//add_action ( 'shutdown', function() { |
| 77 |
// self::errorLog('WP->shutdown actions: '.print_r($GLOBALS['wp_actions'], true)); |
| 78 |
//} ); |
| 79 |
|
| 80 |
if(!self::onFrontend() || (self::loadOnFrontend()) || (is_user_logged_in())){ |
| 81 |
self::errorLog('Go ahead'); |
| 82 |
$this->loadPluginDependencies(); |
| 83 |
$this->setupHooksAndFilters(); |
| 84 |
$this->completePendingUpdates(); |
| 85 |
|
| 86 |
if(is_admin()){ |
| 87 |
$lastActivityWidget = new WADA_Widget_LastActivities(); |
| 88 |
$loginAttemptsWidget = new WADA_Widget_LoginAttempts(); |
| 89 |
} |
| 90 |
|
| 91 |
if (did_action('init')){ |
| 92 |
$this->initWADA(); |
| 93 |
} |
| 94 |
}else{ |
| 95 |
self::errorLog('Skip WADA setup because Frontend: '.(self::onFrontend() ? 'y':'n').', Logged in: '.(is_user_logged_in() ? 'y':'n').', Rest API: '.(self::isRequestForRestAPI()?'y':'n')); |
| 96 |
} |
| 97 |
} |
| 98 |
|
| 99 |
public function loadOnFrontend(){ // the frontend activities that are (potentially) relevant for our sensors |
| 100 |
if(array_key_exists('login', $_REQUEST)) return true; |
| 101 |
if(array_key_exists('register', $_REQUEST)) return true; |
| 102 |
if(array_key_exists('reset_key', $_REQUEST)) return true; |
| 103 |
if(array_key_exists('reset_login', $_REQUEST)) return true; |
| 104 |
if(array_key_exists('wc_reset_password', $_REQUEST)) return true; |
| 105 |
return false; |
| 106 |
} |
| 107 |
|
| 108 |
public function loadPluginDependencies(){ |
| 109 |
require_once 'classes/Constants.php'; |
| 110 |
require_once 'classes/Setup.php'; |
| 111 |
require_once 'classes/Application/BackendSum.php'; |
| 112 |
require_once 'classes/Application/BackendWoosl.php'; |
| 113 |
require_once 'classes/Application/Database.php'; |
| 114 |
require_once 'classes/Application/EventListener.php'; |
| 115 |
require_once 'classes/Application/Extensions.php'; |
| 116 |
require_once 'classes/Application/Log.php'; |
| 117 |
require_once 'classes/Application/Maintenance.php'; |
| 118 |
require_once 'classes/Application/Router.php'; |
| 119 |
require_once 'classes/Application/Settings.php'; |
| 120 |
require_once 'classes/Application/Updater.php'; |
| 121 |
require_once 'classes/Application/Version.php'; |
| 122 |
require_once 'classes/Utils/CommentUtils.php'; |
| 123 |
require_once 'classes/Utils/CompUtils.php'; |
| 124 |
require_once 'classes/Utils/DateUtils.php'; |
| 125 |
require_once 'classes/Utils/FileUtils.php'; |
| 126 |
require_once 'classes/Utils/PHPUtils.php'; |
| 127 |
require_once 'classes/Utils/PluginUtils.php'; |
| 128 |
require_once 'classes/Utils/PostUtils.php'; |
| 129 |
require_once 'classes/Utils/ScriptUtils.php'; |
| 130 |
require_once 'classes/Utils/TermUtils.php'; |
| 131 |
require_once 'classes/Utils/UserUtils.php'; |
| 132 |
require_once 'classes/Utils/UpgraderUtils.php'; |
| 133 |
if(is_admin()){ |
| 134 |
require_once 'classes/Application/Menu.php'; |
| 135 |
require_once 'classes/Utils/HtmlUtils.php'; |
| 136 |
} |
| 137 |
} |
| 138 |
|
| 139 |
public function setupHooksAndFilters(){ |
| 140 |
if(count($_POST)>0) { |
| 141 |
WADA_Log::debug('WADA Setup POST: ' . print_r($_POST, true)); |
| 142 |
} |
| 143 |
add_action( 'init', array( $this, 'initWADA' ), 5 ); |
| 144 |
add_action( 'admin_menu', array( 'WADA_Menu', 'adminMenu' ) ); |
| 145 |
add_action( 'admin_enqueue_scripts', array( 'WADA_Menu', 'adminAssets' ) ); |
| 146 |
|
| 147 |
// Schedules |
| 148 |
add_action( 'wp_admin_audit_maintenance', array( 'WADA_Maintenance', 'scheduledRun' ) ); |
| 149 |
add_action( 'wp_admin_audit_queue_work', array( 'WADA_Notification_Queue', 'workOnQueue' ), 10 ); |
| 150 |
add_action( 'wp_admin_audit_queue_work', array( 'WADA_Replicator_Worker', 'workOnPendingReplications' ), 9 ); // same hook, higher priority for replications |
| 151 |
|
| 152 |
// Internal hooks |
| 153 |
add_action( 'wp_admin_audit_new_event', array( 'WADA_Notification_Queue', 'matchAndQueueEvent' ), 10, 2 ); |
| 154 |
|
| 155 |
WADA_BackendSum::autoResetExtensionCacheOnPluginLifecycleActivities(); |
| 156 |
add_filter('plugins_api', array('WADA_BackendSum', 'injectPluginInfos'), 25, 3); |
| 157 |
|
| 158 |
// Ajax |
| 159 |
WADA_Router::setupAjaxHooks(); |
| 160 |
} |
| 161 |
|
| 162 |
public function completePendingUpdates(){ |
| 163 |
require_once 'classes/Setup.php'; |
| 164 |
$setup = new WADA_Setup(); |
| 165 |
if($setup->isDatabaseUpdateNeeded()){ |
| 166 |
WADA_Log::info('completePendingUpdates'); |
| 167 |
$setup->installOrUpdate(); |
| 168 |
} |
| 169 |
} |
| 170 |
|
| 171 |
public function addCronSchedules($schedules){ |
| 172 |
$schedules['15min'] = array( |
| 173 |
'interval' => 900, |
| 174 |
'display' => __('Every 15 minutes', 'wp-admin-audit'), |
| 175 |
); |
| 176 |
$schedules['5min'] = array( |
| 177 |
'interval' => 300, |
| 178 |
'display' => __('Every 5 minutes', 'wp-admin-audit'), |
| 179 |
); |
| 180 |
$schedules['1min'] = array( |
| 181 |
'interval' => 60, |
| 182 |
'display' => __('Every minute', 'wp-admin-audit'), |
| 183 |
); |
| 184 |
return $schedules; |
| 185 |
} |
| 186 |
|
| 187 |
function autoload($className){ |
| 188 |
$inclPath = null; |
| 189 |
if(strpos($className , 'WADA_Layout_' ) === 0){ |
| 190 |
$namePart = substr($className, strlen('WADA_Layout_')); |
| 191 |
$inclPath = 'classes/Views/Layouts/'.$namePart.'.php'; |
| 192 |
}elseif(strpos($className , 'WADA_Widget_' ) === 0) { |
| 193 |
$namePart = substr($className, strlen('WADA_Widget_')); |
| 194 |
$inclPath = 'classes/Views/Widgets/' . $namePart . '.php'; |
| 195 |
}elseif($className === 'WADA_HtmlUtils') { |
| 196 |
$inclPath = 'classes/Utils/HtmlUtils.php'; |
| 197 |
}else{ |
| 198 |
$folders = array('Model', 'Sensor', 'View', 'Notification', 'Replicator'); |
| 199 |
if(strpos($className, 'WADA_') === 0) { |
| 200 |
foreach($folders as $folder) { |
| 201 |
if(strpos($className, 'WADA_' . $folder . '_') === 0) { |
| 202 |
$namePart = substr($className, strlen('WADA_' . $folder . '_')); |
| 203 |
$inclPath = 'classes/' . $folder . 's/' . $namePart . '.php'; |
| 204 |
//self::errorLog('WADA->autoload ' . $folder . ' ' . $namePart . ' via ' . $inclPath); |
| 205 |
break; // no need to look further |
| 206 |
} |
| 207 |
} |
| 208 |
} |
| 209 |
} |
| 210 |
if($inclPath){ |
| 211 |
//error_log('WADA autoload '.$className .' at '.$inclPath); |
| 212 |
if(is_file(__DIR__.'/'.$inclPath)){ |
| 213 |
require_once __DIR__.'/'.$inclPath; |
| 214 |
}else{ |
| 215 |
// error_log('WADA autoload '.$className .' NOT EXISTING where we thought ('.(__DIR__.'/'.$inclPath).')'); |
| 216 |
} |
| 217 |
} |
| 218 |
} |
| 219 |
|
| 220 |
public function initWADA(){ |
| 221 |
self::errorLog('initWADA'); |
| 222 |
do_action( 'wp_admin_audit_loaded_pre_sensors', $this ); |
| 223 |
//WADA_Log::info('initWADA hey hey'); |
| 224 |
if(class_exists('WADA_EventListener')) { |
| 225 |
$eventListener = new WADA_EventListener(); |
| 226 |
$this->eventListener = $eventListener; |
| 227 |
$this->eventListener->startListening(); |
| 228 |
} |
| 229 |
|
| 230 |
// Done initializing, tell whoever is interested |
| 231 |
do_action( 'wp_admin_audit_loaded_post_sensors', $this ); |
| 232 |
} |
| 233 |
|
| 234 |
public function installWADA(){ |
| 235 |
require_once 'classes/Setup.php'; |
| 236 |
$setup = new WADA_Setup(); |
| 237 |
$setup->installOrUpdate(); |
| 238 |
} |
| 239 |
|
| 240 |
public function deactivateWADA(){ // we do some cleanup or notifications if needed |
| 241 |
$setup = new WADA_Setup(); |
| 242 |
$setup->unscheduleEvents(); |
| 243 |
// TODO ONCE WE HAVE REPLICATION MAKE SURE THE PLUGIN DEACTIVATION EVENT GETS SENT |
| 244 |
} |
| 245 |
|
| 246 |
public static function onFrontend(){ |
| 247 |
$onLoginScreen = parse_url(site_url('wp-login.php'), PHP_URL_PATH) === parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH); |
| 248 |
$isAdmin = is_admin(); |
| 249 |
$restApi = self::isRequestForRestAPI(); |
| 250 |
$isCron = wp_doing_cron(); |
| 251 |
$isWPCli = (defined('WP_CLI') && WP_CLI); |
| 252 |
if($isAdmin || $restApi || $onLoginScreen || $isCron || $isWPCli){ |
| 253 |
return false; |
| 254 |
} |
| 255 |
return true; |
| 256 |
} |
| 257 |
|
| 258 |
public static function isRequestForRestAPI(){ |
| 259 |
$isWpJsonV2 = false; |
| 260 |
$isRestUrlPath = false; |
| 261 |
$isRestRoute = isset($_GET['rest_route']); |
| 262 |
if (!empty($_SERVER['REQUEST_URI'])) { |
| 263 |
$isWpJsonV2 = (array_key_exists('REQUEST_URI', $_SERVER) && (strpos($_SERVER['REQUEST_URI'], 'wp-json/wp/v2') !== false)); |
| 264 |
$restUrlPath = trim(parse_url(home_url('/wp-json/'), PHP_URL_PATH), '/'); |
| 265 |
$requestUrl = trim($_SERVER['REQUEST_URI'], '/'); |
| 266 |
$isRestUrlPath = (strpos($requestUrl, $restUrlPath) === 0); |
| 267 |
} |
| 268 |
return ($isWpJsonV2 || $isRestUrlPath || $isRestRoute); |
| 269 |
} |
| 270 |
|
| 271 |
public static function isHeartbeatRequest(){ |
| 272 |
if(array_key_exists('action', $_POST) && $_POST['action'] === 'heartbeat'){ |
| 273 |
return true; |
| 274 |
} |
| 275 |
|
| 276 |
/* */ |
| 277 |
|
| 278 |
return false; |
| 279 |
} |
| 280 |
|
| 281 |
} // class end |
| 282 |
|
| 283 |
$adminAudit = WpAdminAudit::getInstance(); // init and run |
| 284 |
|
| 285 |
spl_autoload_register(array($adminAudit, 'autoload')); // register autoloader |
| 286 |
|
| 287 |
/* */ |
| 288 |
|
| 289 |
} |