PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / 6.2.0
Defender Security – Malware Scanner, Login Security & Firewall v6.2.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-application-password-audit.php

class-application-password-audit.php in Defender Security – Malware Scanner, Login Security & Firewall 6.2.0, at src/component/audit/class-application-password-audit.php

218 lines 6.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Application password audit logging.
4 *
5 * @package WP_Defender\Component\Audit
6 */
7
8 namespace WP_Defender\Component\Audit;
9
10 use WP_Application_Passwords;
11 use WP_Defender\Model\Audit_Log;
12
13 /**
14 * Extends User_Audit to add application password tracking.
15 *
16 * @since 5.8.0
17 */
18 class Application_Password_Audit extends Audit_Event {
19
20 /**
21 * Track if we've already logged a delete all event for this request.
22 *
23 * @var array
24 */
25 private static array $deletion_count = array();
26
27 /**
28 * Track which delete all events have been logged to prevent duplicates.
29 *
30 * @var array
31 */
32 private static array $logged_delete_all = array();
33
34 /**
35 * Returns hooks including application password tracking.
36 *
37 * @return array
38 */
39 public function get_hooks(): array {
40
41 return array(
42 'wp_create_application_password' => array(
43 'args' => array( 'user_id', 'new_item', 'new_password', 'args' ),
44 'event_type' => Audit_Log::EVENT_TYPE_USER,
45 'context' => User_Audit::CONTEXT_PROFILE,
46 'action_type' => self::ACTION_CREATED,
47 'callback' => array( $this, 'create_application_password_callback' ),
48 ),
49 'wp_delete_application_password' => array(
50 'args' => array( 'user_id', 'item' ),
51 'event_type' => Audit_Log::EVENT_TYPE_USER,
52 'context' => User_Audit::CONTEXT_PROFILE,
53 'action_type' => self::ACTION_DELETED,
54 'callback' => array( $this, 'delete_application_password_callback' ),
55 ),
56 );
57 }
58
59 /**
60 * Callback for logging application password creation.
61 * Detects if someone is creating a password for another user.
62 *
63 * @param string $hook_name The name of the hook.
64 * @param array $params The parameters passed to the hook.
65 *
66 * @return bool|array
67 */
68 public function create_application_password_callback( string $hook_name, array $params ): bool|array {
69 $user_id = $params['user_id'];
70 $current_user_id = get_current_user_id();
71 $blog_name = is_multisite() ? '[' . get_bloginfo( 'name' ) . ']' : '';
72 $user = get_user_by( 'id', $user_id );
73
74 if ( ! $user ) {
75 return false;
76 }
77
78 $username = $user->user_login;
79 $password_name = $params['new_item']['name'] ?? '';
80
81 // Check if someone is creating a password for another user.
82 if ( $current_user_id && $current_user_id !== $user_id ) {
83 $current_user_display = $this->get_user_display( $current_user_id );
84
85 return array(
86 sprintf(
87 /* translators: 1: Blog name, 2: Current user's display name, 3: Affected username, 4: Application password name. */
88 esc_html__( '%1$s %2$s created an application password %4$s for %3$s', 'defender-security' ),
89 $blog_name,
90 $current_user_display,
91 $username,
92 $password_name
93 ),
94 self::ACTION_CREATED,
95 );
96 }
97
98 // User is creating their own password.
99 return array(
100 sprintf(
101 /* translators: 1: Blog name, 2: Username, 3: Application password name. */
102 esc_html__( '%1$s %2$s created an application password %3$s', 'defender-security' ),
103 $blog_name,
104 $username,
105 $password_name
106 ),
107 self::ACTION_CREATED,
108 );
109 }
110
111 /**
112 * Callback for logging application password deletion.
113 * Detects if all passwords are being deleted at once and logs accordingly.
114 *
115 * @param string $hook_name The name of the hook.
116 * @param array $params The parameters passed to the hook.
117 *
118 * @return bool|array
119 */
120 public function delete_application_password_callback( string $hook_name, array $params ): bool|array {
121 $user_id = $params['user_id'];
122 $current_user_id = get_current_user_id();
123 $blog_name = is_multisite() ? '[' . get_bloginfo( 'name' ) . ']' : '';
124 $user = get_user_by( 'id', $user_id );
125
126 if ( ! $user ) {
127 return false;
128 }
129
130 $username = $user->user_login;
131 $password_name = $params['item']['name'] ?? '';
132
133 // Check if this is a "delete all" operation by checking remaining passwords.
134 $remaining_passwords = WP_Application_Passwords::get_user_application_passwords( $user_id );
135 $is_delete_all = is_array( $remaining_passwords ) && 0 === count( $remaining_passwords );
136
137 // Generate a unique key for this user and request.
138 $log_key = $user_id . '_' . ( defender_get_data_from_request( 'REQUEST_TIME_FLOAT', 's' ) ?? microtime( true ) );
139
140 if ( ! isset( self::$deletion_count[ $log_key ] ) ) {
141 self::$deletion_count[ $log_key ] = 0;
142 }
143 ++ self::$deletion_count[ $log_key ];
144
145 $remaining_passwords = WP_Application_Passwords::get_user_application_passwords( $user_id );
146 // If this is a delete all operation, and we haven't logged it yet.
147 if ( array() === $remaining_passwords && self::$deletion_count[ $log_key ] >= 1 ) {
148 if ( isset( self::$logged_delete_all[ $log_key ] ) ) {
149 return false;
150 }
151
152 self::$logged_delete_all[ $log_key ] = 1;
153
154 // Check if someone is revoking passwords for another user.
155 if ( $current_user_id && $current_user_id !== $user_id ) {
156 $current_user_display = $this->get_user_display( $current_user_id );
157
158 return array(
159 sprintf(
160 /* translators: 1: Blog name, 2: Current user's display name, 3: Affected username. */
161 esc_html__( '%1$s %2$s revoked all application passwords for %3$s', 'defender-security' ),
162 $blog_name,
163 $current_user_display,
164 $username
165 ),
166 self::ACTION_DELETED,
167 );
168 }
169
170 // User is revoking their own passwords.
171 return array(
172 sprintf(
173 /* translators: 1: Blog name, 2: Username. */
174 esc_html__( '%1$s %2$s revoked all application passwords', 'defender-security' ),
175 $blog_name,
176 $username
177 ),
178 self::ACTION_DELETED,
179 );
180 }
181
182 // If we already logged "delete all" for this request, suppress further logs.
183 if ( isset( self::$logged_delete_all[ $log_key ] ) ) {
184 return false;
185 }
186
187 // Otherwise, log single password deletion.
188 // Check if someone is revoking a password for another user.
189 if ( $current_user_id && $current_user_id !== $user_id ) {
190 $current_user_display = $this->get_user_display( $current_user_id );
191
192 return array(
193 sprintf(
194 /* translators: 1: Blog name, 2: Current user's display name, 3: Affected username, 4: Application password name. */
195 esc_html__( '%1$s %2$s revoked an application password %4$s for %3$s', 'defender-security' ),
196 $blog_name,
197 $current_user_display,
198 $username,
199 $password_name
200 ),
201 self::ACTION_DELETED,
202 );
203 }
204
205 // User is revoking their own single password.
206 return array(
207 sprintf(
208 /* translators: 1: Blog name, 2: Username, 3: Application password name. */
209 esc_html__( '%1$s %2$s revoked an application password %3$s', 'defender-security' ),
210 $blog_name,
211 $username,
212 $password_name
213 ),
214 self::ACTION_DELETED,
215 );
216 }
217 }
218