PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / 6.3.0
Defender Security – Malware Scanner, Login Security & Firewall v6.3.0
6.3.0 6.2.3 6.2.4 6.2.0 6.2.1 6.2.2 6.1.0 5.3.1 5.4.0 5.4.1 5.5.0 5.5.1 5.6.0 5.6.1 5.6.2 5.7.0 5.7.1 5.7.2 5.8.0 5.8.1 5.9.0 6.0.0 6.0.1 3.0.1 3.1.0 All 141 releases
← All changes | src/component/audit/class-user-audit.php +120 -82 5.8.1 → 6.3.0 View file →
@@ -7,8 +7,10 @@
7 7
8 8 namespace WP_Defender\Component\Audit;
9 9
10 10 use WP_User;
11 +use WP_Error;
12 +use stdClass;
11 13 use WP_Defender\Traits\User;
12 14 use WP_Defender\Model\Audit_Log;
13 15
14 16 /**
@@ -26,8 +28,18 @@
26 28 public const CONTEXT_SESSION = 'session', CONTEXT_USERS = 'users', CONTEXT_PROFILE = 'profile',
27 29 CONTEXT_MULTISITE_USERS = 'multisite_users', CONTEXT_SUPER_ADMIN = 'super-admin';
28 30
29 31 /**
32 + * Cached old user data used for tracking changes to user profiles.
33 + *
34 + * This array stores old user data when a user's profile is updated.
35 + * The data is keyed by user ID and contains the user's metadata before the update.
36 + *
37 + * @var array
38 + */
39 + private static array $cached_old_user_data = array();
40 +
41 + /**
30 42 * Returns an array of hooks associated with various user actions to capture and log them.
31 43 *
32 44 * @return array
33 45 */
@@ -33,8 +45,12 @@
33 45 */
34 46 public function get_hooks(): array {
35 47
36 48 $hooks = array(
49 + 'user_profile_update_errors' => array(
50 + 'args' => array( 'errors', 'update', 'user' ),
51 + 'callback' => array( $this, 'cache_old_user_data' ),
52 + ),
37 53 'wp_login_failed' => array(
38 54 'args' => array( 'username' ),
39 55 'text' => sprintf(
40 56 /* translators: 1: Blog name, 2: Username */
@@ -76,9 +92,9 @@
76 92 'params' => array(
77 93 'id',
78 94 '{{user_id}}',
79 95 ),
80 - 'result_property' => 'user_login',
96 + 'result_property' => 'data->user_login',
81 97 ),
82 98 ),
83 99 ),
84 100 'wp_logout' => array(
@@ -98,49 +114,18 @@
98 114 'params' => array(
99 115 'id',
100 116 '{{user_id}}',
101 117 ),
102 - 'result_property' => 'user_login',
118 + 'result_property' => 'data->user_login',
103 119 ),
104 120 ),
105 121 ),
106 122 'user_register' => array(
107 - 'args' => array( 'user_id' ),
108 - 'text' => is_admin()
109 - ? sprintf(
110 - /* translators: 1: Blog name, 2: Source of action. For e.g. Hub or a logged-in user, 3: Username, 4: User role */
111 - esc_html__( '%1$s %2$s added a new user: Username: %3$s, Role: %4$s', 'defender-security' ),
112 - '{{blog_name}}',
113 - '{{wp_user}}',
114 - '{{username}}',
115 - '{{user_role}}'
116 - )
117 - : sprintf(
118 - /* translators: 1: Blog name, 2: Username, 3: User role */
119 - esc_html__( '%1$s A new user registered: Username: %2$s, Role: %3$s', 'defender-security' ),
120 - '{{blog_name}}',
121 - '{{username}}',
122 - '{{user_role}}'
123 - ),
124 - 'event_type' => Audit_Log::EVENT_TYPE_USER,
125 - 'context' => self::CONTEXT_USERS,
126 - 'action_type' => self::ACTION_REGISTERED,
127 - 'program_args' => array(
128 - 'username' => array(
129 - 'callable' => 'get_user_by',
130 - 'params' => array(
131 - 'id',
132 - '{{user_id}}',
133 - ),
134 - 'result_property' => 'user_login',
135 - ),
136 - 'user_role' => array(
137 - 'callable' => array( self::class, 'get_user_role' ),
138 - 'params' => array(
139 - '{{user_id}}',
140 - ),
141 - ),
142 - ),
123 + 'args' => array( 'user_id' ),
124 + 'event_type' => Audit_Log::EVENT_TYPE_USER,
125 + 'context' => self::CONTEXT_USERS,
126 + 'action_type' => self::ACTION_REGISTERED,
127 + 'callback' => array( $this, 'created_new_user_callback' ),
143 128 ),
144 129 'delete_user' => array(
145 130 'args' => array( 'user_id' ),
146 131 'text' => sprintf(
@@ -160,9 +145,9 @@
160 145 'params' => array(
161 146 'id',
162 147 '{{user_id}}',
163 148 ),
164 - 'result_property' => 'user_login',
149 + 'result_property' => 'data->user_login',
165 150 ),
166 151 ),
167 152 ),
168 153 'remove_user_from_blog' => array(
@@ -191,14 +176,14 @@
191 176 'params' => array(
192 177 'id',
193 178 '{{user_id}}',
194 179 ),
195 - 'result_property' => 'user_login',
180 + 'result_property' => 'data->user_login',
196 181 ),
197 182 ),
198 183 ),
199 184 'profile_update' => array(
200 - 'args' => array( 'user_id', 'old_user_data' ),
185 + 'args' => array( 'user_id', 'old_user_data', 'userdata' ),
201 186 'action_type' => self::ACTION_UPDATED,
202 187 'event_type' => Audit_Log::EVENT_TYPE_USER,
203 188 'context' => self::CONTEXT_PROFILE,
204 189 'callback' => array( self::class, 'profile_update_callback' ),
@@ -235,9 +220,9 @@
235 220 'event_type' => Audit_Log::EVENT_TYPE_USER,
236 221 'action_type' => self::ACTION_RESET_PASS,
237 222 'context' => self::CONTEXT_PROFILE,
238 223 'custom_args' => array(
239 - 'user_login' => '{{user->user_login}}',
224 + 'user_login' => '{{user->data->user_login}}',
240 225 ),
241 226 ),
242 227 'set_user_role' => array(
243 228 'args' => array( 'user_ID', 'new_role', 'old_role' ),
@@ -262,9 +247,9 @@
262 247 'params' => array(
263 248 'id',
264 249 '{{user_ID}}',
265 250 ),
266 - 'result_property' => 'user_login',
251 + 'result_property' => 'data->user_login',
267 252 ),
268 253 ),
269 254 'false_when' => array(
270 255 array(
@@ -292,9 +277,9 @@
292 277 'params' => array(
293 278 'id',
294 279 '{{user_id}}',
295 280 ),
296 - 'result_property' => 'user_login',
281 + 'result_property' => 'data->user_login',
297 282 ),
298 283 ),
299 284 ),
300 285 'wpdef_session_timeout' => array(
@@ -314,9 +299,9 @@
314 299 'params' => array(
315 300 'id',
316 301 '{{user_id}}',
317 302 ),
318 - 'result_property' => 'user_login',
303 + 'result_property' => 'data->user_login',
319 304 ),
320 305 ),
321 306 ),
322 307 // Since 5.6.0.
@@ -337,9 +322,9 @@
337 322 'params' => array(
338 323 'id',
339 324 '{{user_id}}',
340 325 ),
341 - 'result_property' => 'user_login',
326 + 'result_property' => 'data->user_login',
342 327 ),
343 328 ),
344 329 ),
345 330 );
@@ -344,9 +329,9 @@
344 329 ),
345 330 );
346 331
347 332 if ( is_multisite() ) {
348 - $hooks['grant_super_admin'] = array(
333 + $hooks['granted_super_admin'] = array(
349 334 'args' => array( 'user_id' ),
350 335 'text' => sprintf(
351 336 /* translators: 1: Blog name. 2: Actor username. 3: Affected username. */
352 337 esc_html__( '%1$s %2$s granted Super Admin privileges to %3$s', 'defender-security' ),
@@ -360,13 +345,13 @@
360 345 'program_args' => array(
361 346 'username' => array(
362 347 'callable' => 'get_user_by',
363 348 'params' => array( 'id', '{{user_id}}' ),
364 - 'result_property' => 'user_login',
349 + 'result_property' => 'data->user_login',
365 350 ),
366 351 ),
367 352 );
368 - $hooks['revoke_super_admin'] = array(
353 + $hooks['revoked_super_admin'] = array(
369 354 'args' => array( 'user_id' ),
370 355 'text' => sprintf(
371 356 /* translators: 1: Blog name. 2: Actor username. 3: Affected username. */
372 357 esc_html__( '%1$s %2$s revoked Super Admin privileges from %3$s', 'defender-security' ),
@@ -380,9 +365,9 @@
380 365 'program_args' => array(
381 366 'username' => array(
382 367 'callable' => 'get_user_by',
383 368 'params' => array( 'id', '{{user_id}}' ),
384 - 'result_property' => 'user_login',
369 + 'result_property' => 'data->user_login',
385 370 ),
386 371 ),
387 372 );
388 373 $hooks['wpmu_new_user'] = array(
@@ -412,9 +397,9 @@
412 397 ),
413 398 'username' => array(
414 399 'callable' => 'get_user_by',
415 400 'params' => array( 'id', '{{user_id}}' ),
416 - 'result_property' => 'user_login',
401 + 'result_property' => 'data->user_login',
417 402 ),
418 403 ),
419 404 );
420 405 }
@@ -424,38 +409,46 @@
424 409
425 410 /**
426 411 * Log when user is created on MU.
427 412 *
428 - * @param string $hook_name The name of the hook.
429 - * @param array $params The parameters passed to the hook.
413 + * @param int $user_id User ID.
430 414 *
431 415 * @return bool|array
432 416 */
433 - public function created_new_user_callback( string $hook_name, array $params ): bool|array {
434 - $user_id = $params['user_id'];
435 - $user = get_user_by( 'id', $user_id );
436 - $username = $user->user_login ?? '';
437 - $blog_name = '[' . get_bloginfo( 'name' ) . ']';
438 - if ( $user instanceof WP_User ) {
417 + public function created_new_user_callback( int $user_id ): bool|array {
418 + $user = get_user_by( 'id', $user_id );
419 + $username = $user->user_login ?? '';
420 + $blog_name = is_multisite() ? '[' . get_bloginfo( 'name' ) . ']' : 'the site';
421 + $userdata_role = defender_get_data_from_request( 'role', 'p' );
439 422
440 - $role = $this->get_first_user_role( $user );
441 - if ( '' === $role ) {
442 - return false;
443 - }
423 + if ( ! isset( $user->roles[0], $userdata_role ) || $userdata_role !== $user->roles[0] ) {
424 + return false;
425 + }
426 + $role = $this->get_first_user_role( $user );
427 + $action = defender_get_data_from_request( 'action', 'p' );
428 + if ( is_admin() ) {
429 + return array(
430 + sprintf(
431 + /* translators: 1: Blog name, 2: Username, 3: User role. */
432 + esc_html__( 'User %1$s %2$s, Username: %3$s, Role: %4$s', 'defender-security' ),
433 + 'created' === $action ? 'created on' : 'added to',
434 + $blog_name,
435 + $username,
436 + $role
437 + ),
438 + self::ACTION_CREATED,
439 + );
444 440 } else {
445 - return false;
441 + return array(
442 + sprintf(
443 + /* translators: 1: Blog name, 2: Username, 3: User role */
444 + esc_html__( '%1$s A new user registered: Username: %2$s, Role: %3$s', 'defender-security' ),
445 + $blog_name,
446 + $username,
447 + $role
448 + ),
449 + );
446 450 }
447 -
448 - return array(
449 - sprintf(
450 - /* translators: 1: Blog name, 2: Username, 3: User role. */
451 - esc_html__( 'User created on %1$s, Username: %2$s, Role: %3$s', 'defender-security' ),
452 - $blog_name,
453 - $username,
454 - $role
455 - ),
456 - self::ACTION_CREATED,
457 - );
458 451 }
459 452
460 453 /**
461 454 * Log when user is removed from a blog.
@@ -462,9 +455,10 @@
462 455 *
463 456 * @return bool|array
464 457 */
465 458 public function remove_user_from_blog_callback() {
466 - if ( self::is_create_user_action() ) {
459 + $action = defender_get_data_from_request( 'action', 'p' );
460 + if ( 'doremove' !== $action ) {
467 461 return false;
468 462 }
469 463
470 464 $args = func_get_args();
@@ -492,18 +486,28 @@
492 486 /**
493 487 * Callback for logging when a user's profile is updated. It distinguishes between self-updates and updates made by
494 488 * other users.
495 489 *
490 + * @param int $user_id User ID.
491 + * @param WP_User $old_user_data Object containing user's data prior to update.
492 + * @param array $userdata The raw array of data passed to wp_insert_user().
493 + *
496 494 * @return bool|array
497 495 */
498 - public function profile_update_callback() {
499 - if ( self::is_create_user_action() ) {
496 + public function profile_update_callback( int $user_id, WP_User $old_user_data, array $userdata ): bool|array {
497 + $from = defender_get_data_from_request( 'from', 'p' );
498 + if ( 'profile' !== $from ) {
500 499 return false;
501 500 }
502 -
503 - $args = func_get_args();
504 - $user_id = $args[1]['user_id'];
505 - $current_user = get_user_by( 'id', $user_id );
501 + $diff_assoc = array_diff_assoc( $userdata, self::$cached_old_user_data[ $user_id ] ?? array() );
502 + if ( array() === $diff_assoc ) {
503 + return false;
504 + }
505 + $current_user = get_user_by( 'id', $user_id );
506 + if ( array_key_exists( 'user_pass', $diff_assoc ) && $old_user_data->user_pass !== $userdata['user_pass'] ) {
507 + $password_audit = new Password_Audit();
508 + return $password_audit->build_log( $current_user );
509 + }
506 510 $blog_name = is_multisite() ? '[' . get_bloginfo( 'name' ) . ']' : '';
507 511 $current_user_id = get_current_user_id();
508 512
509 513 if ( $current_user_id === $user_id ) {
@@ -583,6 +587,40 @@
583 587 return true;
584 588 }
585 589
586 590 return false;
591 + }
592 +
593 + /**
594 + * Cache old user data from when a user's profile is updated.
595 + *
596 + * @param WP_Error $errors WP_Error object (passed by reference).
597 + * @param bool $update Whether this is a user update.
598 + * @param stdClass $user User object (passed by reference).
599 + *
600 + * @return WP_Error The errors that occurred during the action.
601 + */
602 + public function cache_old_user_data( WP_Error $errors, bool $update, stdClass $user ): WP_Error {
603 + if ( $update ) {
604 + $id = $user->ID;
605 + $current_user = get_userdata( $id );
606 + $user_array = $current_user->to_array();
607 + $meta = array(
608 + 'first_name' => get_user_meta( $current_user->ID, 'first_name', true ),
609 + 'last_name' => get_user_meta( $current_user->ID, 'last_name', true ),
610 + 'nickname' => get_user_meta( $current_user->ID, 'nickname', true ),
611 + 'description' => get_user_meta( $current_user->ID, 'description', true ),
612 + 'rich_editing' => get_user_meta( $current_user->ID, 'rich_editing', true ),
613 + 'syntax_highlighting' => get_user_meta( $current_user->ID, 'syntax_highlighting', true ),
614 + 'comment_shortcuts' => get_user_meta( $current_user->ID, 'comment_shortcuts', true ) === 'true' ? 'true' : '',
615 + 'locale' => get_user_meta( $current_user->ID, 'locale', true ),
616 + 'admin_color' => get_user_meta( $current_user->ID, 'admin_color', true ),
617 + 'use_ssl' => get_user_meta( $current_user->ID, 'use_ssl', true ),
618 + 'show_admin_bar_front' => get_user_meta( $current_user->ID, 'show_admin_bar_front', true ),
619 + );
620 +
621 + self::$cached_old_user_data[ $id ] = array_merge_recursive( $user_array, $meta );
622 + }
623 +
624 + return $errors;
587 625 }
588 626 }