PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / 5.8.0
Defender Security – Malware Scanner, Login Security & Firewall v5.8.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
defender-security / src / component / audit / class-user-audit.php

class-user-audit.php in Defender Security – Malware Scanner, Login Security & Firewall 5.8.0, at src/component/audit/class-user-audit.php

589 lines 18.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Handles user-related audit events.
4 *
5 * @package WP_Defender\Component\Audit
6 */
7
8 namespace WP_Defender\Component\Audit;
9
10 use WP_User;
11 use WP_Defender\Traits\User;
12 use WP_Defender\Model\Audit_Log;
13
14 /**
15 * Handle user-related audit events such as login, logout, registration, and profile updates.
16 */
17 class User_Audit extends Audit_Event {
18
19 use User;
20
21 public const ACTION_LOGIN = 'login', ACTION_LOGOUT = 'logout', ACTION_REGISTERED = 'registered',
22 ACTION_LOST_PASS = 'lost_password', ACTION_RESET_PASS = 'reset_password',
23 ACTION_CREATED = 'created', ACTION_ADDED = 'added',
24 ACTION_GRANTED = 'granted', ACTION_REVOKED = 'revoked';
25
26 public const CONTEXT_SESSION = 'session', CONTEXT_USERS = 'users', CONTEXT_PROFILE = 'profile',
27 CONTEXT_MULTISITE_USERS = 'multisite_users', CONTEXT_SUPER_ADMIN = 'super-admin';
28
29 /**
30 * Returns an array of hooks associated with various user actions to capture and log them.
31 *
32 * @return array
33 */
34 public function get_hooks(): array {
35
36 $hooks = array(
37 'wp_login_failed' => array(
38 'args' => array( 'username' ),
39 'text' => sprintf(
40 /* translators: 1: Blog name, 2: Username */
41 esc_html__( '%1$s User login fail. Username: %2$s', 'defender-security' ),
42 '{{blog_name}}',
43 '{{username}}'
44 ),
45 'event_type' => Audit_Log::EVENT_TYPE_USER,
46 'context' => self::CONTEXT_SESSION,
47 'action_type' => self::ACTION_LOGIN,
48 ),
49 'wp_login' => array(
50 'args' => array( 'userlogin', 'user' ),
51 'text' => sprintf(
52 /* translators: 1: Blog name, 2: Username */
53 esc_html__( '%1$s User login success: %2$s', 'defender-security' ),
54 '{{blog_name}}',
55 '{{userlogin}}'
56 ),
57 'event_type' => Audit_Log::EVENT_TYPE_USER,
58 'context' => self::CONTEXT_SESSION,
59 'action_type' => self::ACTION_LOGIN,
60 ),
61 'wpmu_2fa_login' => array(
62 'args' => array( 'user_id', '2fa_slug' ),
63 'text' => sprintf(
64 /* translators: 1: Blog name, 2: 2fa method slug, 3: Username. */
65 esc_html__( '%1$s 2fa with %2$s method login success for user: %3$s', 'defender-security' ),
66 '{{blog_name}}',
67 '{{2fa_slug}}',
68 '{{username}}'
69 ),
70 'event_type' => Audit_Log::EVENT_TYPE_USER,
71 'context' => self::CONTEXT_SESSION,
72 'action_type' => self::ACTION_LOGIN,
73 'program_args' => array(
74 'username' => array(
75 'callable' => 'get_user_by',
76 'params' => array(
77 'id',
78 '{{user_id}}',
79 ),
80 'result_property' => 'user_login',
81 ),
82 ),
83 ),
84 'wp_logout' => array(
85 'args' => array( 'user_id' ),
86 'text' => sprintf(
87 /* translators: 1: Blog name, 2: Username */
88 esc_html__( '%1$s User logout success: %2$s', 'defender-security' ),
89 '{{blog_name}}',
90 '{{username}}'
91 ),
92 'event_type' => Audit_Log::EVENT_TYPE_USER,
93 'action_type' => self::ACTION_LOGOUT,
94 'context' => self::CONTEXT_SESSION,
95 'program_args' => array(
96 'username' => array(
97 'callable' => 'get_user_by',
98 'params' => array(
99 'id',
100 '{{user_id}}',
101 ),
102 'result_property' => 'user_login',
103 ),
104 ),
105 ),
106 '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 ),
143 ),
144 'delete_user' => array(
145 'args' => array( 'user_id' ),
146 'text' => sprintf(
147 /* translators: 1: Blog name, 2: Source of action. For e.g. Hub or a logged-in user, 3: User ID, 4: Username */
148 esc_html__( '%1$s %2$s deleted a user: ID: %3$s, username: %4$s', 'defender-security' ),
149 '{{blog_name}}',
150 '{{wp_user}}',
151 '{{user_id}}',
152 '{{username}}'
153 ),
154 'context' => self::CONTEXT_USERS,
155 'action_type' => self::ACTION_DELETED,
156 'event_type' => Audit_Log::EVENT_TYPE_USER,
157 'program_args' => array(
158 'username' => array(
159 'callable' => 'get_user_by',
160 'params' => array(
161 'id',
162 '{{user_id}}',
163 ),
164 'result_property' => 'user_login',
165 ),
166 ),
167 ),
168 'remove_user_from_blog' => array(
169 'args' => array( 'user_id', 'blog_id' ),
170 'context' => self::CONTEXT_USERS,
171 'action_type' => self::ACTION_DELETED,
172 'event_type' => Audit_Log::EVENT_TYPE_USER,
173 'callback' => array( self::class, 'remove_user_from_blog_callback' ),
174 ),
175 'wpmu_delete_user' => array(
176 'args' => array( 'user_id' ),
177 'text' => sprintf(
178 /* translators: 1: Blog name, 2: Source of action. For e.g. Hub or a logged-in user, 3: User ID, 4: Username */
179 esc_html__( '%1$s %2$s deleted a user: ID: %3$s, username: %4$s', 'defender-security' ),
180 '{{blog_name}}',
181 '{{wp_user}}',
182 '{{user_id}}',
183 '{{username}}'
184 ),
185 'context' => self::CONTEXT_USERS,
186 'action_type' => self::ACTION_DELETED,
187 'event_type' => Audit_Log::EVENT_TYPE_USER,
188 'program_args' => array(
189 'username' => array(
190 'callable' => 'get_user_by',
191 'params' => array(
192 'id',
193 '{{user_id}}',
194 ),
195 'result_property' => 'user_login',
196 ),
197 ),
198 ),
199 'profile_update' => array(
200 'args' => array( 'user_id', 'old_user_data' ),
201 'action_type' => self::ACTION_UPDATED,
202 'event_type' => Audit_Log::EVENT_TYPE_USER,
203 'context' => self::CONTEXT_PROFILE,
204 'callback' => array( self::class, 'profile_update_callback' ),
205 ),
206 'retrieve_password' => array(
207 'args' => array( 'username' ),
208 'text' => sprintf(
209 /* translators: 1: Blog name, 2: Username */
210 esc_html__( '%1$s Password requested to reset for user: %2$s', 'defender-security' ),
211 '{{blog_name}}',
212 '{{username}}'
213 ),
214 'action_type' => self::ACTION_LOST_PASS,
215 'event_type' => Audit_Log::EVENT_TYPE_USER,
216 'context' => self::CONTEXT_PROFILE,
217 'program_args' => array(
218 'user' => array(
219 'callable' => 'get_user_by',
220 'params' => array(
221 'login',
222 '{{username}}',
223 ),
224 ),
225 ),
226 ),
227 'after_password_reset' => array(
228 'args' => array( 'user' ),
229 'text' => sprintf(
230 /* translators: 1: Blog name, 2: Username. */
231 esc_html__( '%1$s Password reset for user: %2$s', 'defender-security' ),
232 '{{blog_name}}',
233 '{{user_login}}'
234 ),
235 'event_type' => Audit_Log::EVENT_TYPE_USER,
236 'action_type' => self::ACTION_RESET_PASS,
237 'context' => self::CONTEXT_PROFILE,
238 'custom_args' => array(
239 'user_login' => '{{user->user_login}}',
240 ),
241 ),
242 'set_user_role' => array(
243 'args' => array( 'user_ID', 'new_role', 'old_role' ),
244 'text' => sprintf(
245 /* translators: 1: Blog name, 2: Source of action. For e.g. Hub or a logged-in user, 3: Username, 4: Old user role, 5: New user role */
246 esc_html__( "%1\$s %2\$s changed user %3\$s's role from %4\$s to %5\$s", 'defender-security' ),
247 '{{blog_name}}',
248 '{{wp_user}}',
249 '{{username}}',
250 '{{from_role}}',
251 '{{new_role}}'
252 ),
253 'action_type' => self::ACTION_UPDATED,
254 'event_type' => Audit_Log::EVENT_TYPE_USER,
255 'context' => self::CONTEXT_PROFILE,
256 'custom_args' => array(
257 'from_role' => '{{old_role->0}}',
258 ),
259 'program_args' => array(
260 'username' => array(
261 'callable' => 'get_user_by',
262 'params' => array(
263 'id',
264 '{{user_ID}}',
265 ),
266 'result_property' => 'user_login',
267 ),
268 ),
269 'false_when' => array(
270 array(
271 '{{old_role}}',
272 array(),
273 '==',
274 ),
275 ),
276 ),
277 'wpdef_session_lock' => array(
278 'args' => array( 'user_id', 'session_lock_type' ),
279 'text' => sprintf(
280 /* translators: 1: Blog name, 2: Username, 3: Session lock type. */
281 esc_html__( '%1$s User session ended for %2$s due to a change in %3$s', 'defender-security' ),
282 '{{blog_name}}',
283 '{{username}}',
284 '{{session_lock_type}}'
285 ),
286 'event_type' => Audit_Log::EVENT_TYPE_USER,
287 'context' => self::CONTEXT_SESSION,
288 'action_type' => self::ACTION_LOGIN,
289 'program_args' => array(
290 'username' => array(
291 'callable' => 'get_user_by',
292 'params' => array(
293 'id',
294 '{{user_id}}',
295 ),
296 'result_property' => 'user_login',
297 ),
298 ),
299 ),
300 'wpdef_session_timeout' => array(
301 'args' => array( 'user_id' ),
302 'text' => sprintf(
303 /* translators: 1: Blog name, 2: Username. */
304 esc_html__( '%1$s User session ended for %2$s due to an idle session', 'defender-security' ),
305 '{{blog_name}}',
306 '{{username}}'
307 ),
308 'event_type' => Audit_Log::EVENT_TYPE_USER,
309 'context' => self::CONTEXT_SESSION,
310 'action_type' => self::ACTION_LOGIN,
311 'program_args' => array(
312 'username' => array(
313 'callable' => 'get_user_by',
314 'params' => array(
315 'id',
316 '{{user_id}}',
317 ),
318 'result_property' => 'user_login',
319 ),
320 ),
321 ),
322 // Since 5.6.0.
323 'wpmudev_sso_set_current_user' => array(
324 'args' => array( 'user_id' ),
325 'text' => sprintf(
326 /* translators: 1: Blog name, 2: Username. */
327 esc_html__( '%1$s Hub SSO login success: %2$s', 'defender-security' ),
328 '{{blog_name}}',
329 '{{username}}'
330 ),
331 'event_type' => Audit_Log::EVENT_TYPE_USER,
332 'context' => self::CONTEXT_SESSION,
333 'action_type' => self::ACTION_LOGIN,
334 'program_args' => array(
335 'username' => array(
336 'callable' => 'get_user_by',
337 'params' => array(
338 'id',
339 '{{user_id}}',
340 ),
341 'result_property' => 'user_login',
342 ),
343 ),
344 ),
345 );
346
347 if ( is_multisite() ) {
348 $hooks['grant_super_admin'] = array(
349 'args' => array( 'user_id' ),
350 'text' => sprintf(
351 /* translators: 1: Blog name. 2: Actor username. 3: Affected username. */
352 esc_html__( '%1$s %2$s granted Super Admin privileges to %3$s', 'defender-security' ),
353 '{{blog_name}}',
354 '{{wp_user}}',
355 '{{username}}'
356 ),
357 'event_type' => Audit_Log::EVENT_TYPE_USER,
358 'context' => self::CONTEXT_SUPER_ADMIN,
359 'action_type' => self::ACTION_GRANTED,
360 'program_args' => array(
361 'username' => array(
362 'callable' => 'get_user_by',
363 'params' => array( 'id', '{{user_id}}' ),
364 'result_property' => 'user_login',
365 ),
366 ),
367 );
368 $hooks['revoke_super_admin'] = array(
369 'args' => array( 'user_id' ),
370 'text' => sprintf(
371 /* translators: 1: Blog name. 2: Actor username. 3: Affected username. */
372 esc_html__( '%1$s %2$s revoked Super Admin privileges from %3$s', 'defender-security' ),
373 '{{blog_name}}',
374 '{{wp_user}}',
375 '{{username}}'
376 ),
377 'event_type' => Audit_Log::EVENT_TYPE_USER,
378 'context' => self::CONTEXT_SUPER_ADMIN,
379 'action_type' => self::ACTION_REVOKED,
380 'program_args' => array(
381 'username' => array(
382 'callable' => 'get_user_by',
383 'params' => array( 'id', '{{user_id}}' ),
384 'result_property' => 'user_login',
385 ),
386 ),
387 );
388 $hooks['wpmu_new_user'] = array(
389 'args' => array( 'user_id' ),
390 'event_type' => Audit_Log::EVENT_TYPE_USER,
391 'context' => self::CONTEXT_MULTISITE_USERS,
392 'action_type' => self::ACTION_CREATED,
393 'callback' => array( $this, 'created_new_user_callback' ),
394 );
395 $hooks['add_user_to_blog'] = array(
396 'args' => array( 'user_id', 'role', 'blog_id' ),
397 'text' => sprintf(
398 /* translators: 1: Blog name, 2: Username, 3: User role. */
399 esc_html__( 'User added to %1$s, Username: %2$s, Role: %3$s', 'defender-security' ),
400 '{{site_name}}',
401 '{{username}}',
402 '{{role}}'
403 ),
404 'event_type' => Audit_Log::EVENT_TYPE_USER,
405 'context' => self::CONTEXT_MULTISITE_USERS,
406 'action_type' => self::ACTION_ADDED,
407 'program_args' => array(
408 'site_name' => array(
409 'callable' => 'get_blog_details',
410 'params' => array( '{{blog_id}}' ),
411 'result_property' => 'blogname',
412 ),
413 'username' => array(
414 'callable' => 'get_user_by',
415 'params' => array( 'id', '{{user_id}}' ),
416 'result_property' => 'user_login',
417 ),
418 ),
419 );
420 }
421
422 return $hooks;
423 }
424
425 /**
426 * Log when user is created on MU.
427 *
428 * @param string $hook_name The name of the hook.
429 * @param array $params The parameters passed to the hook.
430 *
431 * @return bool|array
432 */
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 ) {
439
440 $role = $this->get_first_user_role( $user );
441 if ( '' === $role ) {
442 return false;
443 }
444 } else {
445 return false;
446 }
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 }
459
460 /**
461 * Log when user is removed from a blog.
462 *
463 * @return bool|array
464 */
465 public function remove_user_from_blog_callback() {
466 if ( self::is_create_user_action() ) {
467 return false;
468 }
469
470 $args = func_get_args();
471 $user_id = $args[1]['user_id'];
472 $blog_id = $args[1]['blog_id'];
473 $user = get_user_by( 'id', $user_id );
474 $username = $user->user_login ?? '';
475 $current_user_display = $this->get_user_display( get_current_user_id() );
476 $blog_name = is_multisite() ? '[' . get_bloginfo( 'name' ) . ']' : '';
477
478 return array(
479 sprintf(
480 /* translators: 1: Blog name, 2: User's display name, 3: User ID, 4: Username, 5: Blog ID */
481 esc_html__( '%1$s %2$s removed a user: ID: %3$s, username: %4$s from blog %5$s', 'defender-security' ),
482 $blog_name,
483 $current_user_display,
484 $user_id,
485 $username,
486 $blog_id
487 ),
488 self::ACTION_DELETED,
489 );
490 }
491
492 /**
493 * Callback for logging when a user's profile is updated. It distinguishes between self-updates and updates made by
494 * other users.
495 *
496 * @return bool|array
497 */
498 public function profile_update_callback() {
499 if ( self::is_create_user_action() ) {
500 return false;
501 }
502
503 $args = func_get_args();
504 $user_id = $args[1]['user_id'];
505 $current_user = get_user_by( 'id', $user_id );
506 $blog_name = is_multisite() ? '[' . get_bloginfo( 'name' ) . ']' : '';
507 $current_user_id = get_current_user_id();
508
509 if ( $current_user_id === $user_id ) {
510
511 return array(
512 sprintf(
513 /* translators: 1: Blog name, 2: User's nicename */
514 esc_html__( '%1$s User %2$s updated his/her profile', 'defender-security' ),
515 $blog_name,
516 $current_user->user_nicename
517 ),
518 self::ACTION_UPDATED,
519 );
520 } elseif ( 0 !== $current_user_id ) {
521
522 return array(
523 sprintf(
524 /* translators: 1: Blog name, 2: User's display name, 3: User's nicename */
525 esc_html__( "%1\$s %2\$s updated user %3\$s's profile information", 'defender-security' ),
526 $blog_name,
527 $this->get_user_display( $current_user_id ),
528 $current_user->user_nicename
529 ),
530 self::ACTION_UPDATED,
531 );
532 }
533
534 return false;
535 }
536
537 /**
538 * Provides a dictionary for translating action constants into human-readable strings.
539 *
540 * @return array
541 */
542 public function dictionary(): array {
543 return array(
544 self::ACTION_LOST_PASS => esc_html__( 'lost password', 'defender-security' ),
545 self::ACTION_REGISTERED => esc_html__( 'registered', 'defender-security' ),
546 self::ACTION_LOGIN => esc_html__( 'login', 'defender-security' ),
547 self::ACTION_LOGOUT => esc_html__( 'logout', 'defender-security' ),
548 self::ACTION_RESET_PASS => esc_html__( 'password reset', 'defender-security' ),
549 self::ACTION_CREATED => esc_html__( 'created', 'defender-security' ),
550 self::ACTION_ADDED => esc_html__( 'added', 'defender-security' ),
551 self::ACTION_GRANTED => esc_html__( 'granted', 'defender-security' ),
552 self::ACTION_REVOKED => esc_html__( 'revoked', 'defender-security' ),
553 );
554 }
555
556 /**
557 * Static method to retrieve the role of a user by their ID
558 *
559 * @param int $user_id The ID of the user whose role is to be fetched.
560 *
561 * @return string
562 */
563 public static function get_user_role( $user_id ): string {
564 $user = get_user_by( 'id', $user_id );
565 if ( $user instanceof WP_User ) {
566 $_this = new self();
567
568 return $_this->get_first_user_role( $user );
569 } else {
570 return '';
571 }
572 }
573
574 /**
575 * Check if it is a create new user request.
576 *
577 * @since 2.8.0
578 * @return bool
579 */
580 public static function is_create_user_action(): bool {
581 $action = defender_get_data_from_request( 'action', 'p' );
582 if ( 'createuser' === $action ) {
583 return true;
584 }
585
586 return false;
587 }
588 }
589