PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / 5.3.1
Defender Security – Malware Scanner, Login Security & Firewall v5.3.1
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-audit-event.php

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

447 lines 13.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Abstract class used for handling audit events in the WP_Defender plugin.
4 *
5 * @package WP_Defender\Component\Audit
6 */
7
8 namespace WP_Defender\Component\Audit;
9
10 use Countable;
11 use ReflectionMethod;
12 use ReflectionException;
13 use WP_Defender\Component;
14 use WP_Defender\Traits\IP;
15 use WP_Defender\Traits\User;
16 use Calotes\Helper\Array_Cache;
17 use WP_Defender\Model\Setting\Audit_Logging;
18
19 /**
20 * Base class for all audit events.
21 */
22 abstract class Audit_Event extends Component {
23
24 use User;
25 use IP;
26
27 public const ACTION_DELETED = 'deleted', ACTION_TRASHED = 'trashed', ACTION_RESTORED = 'restored', ACTION_UPDATED = 'updated';
28
29 /**
30 * Return an array of hooks.
31 *
32 * @return mixed
33 */
34 abstract public function get_hooks();
35
36 /**
37 * Loop through the arguments array and process each argument based on its type.
38 *
39 * @param array $args The array of arguments to process.
40 * @param array $params The parameters array to update with processed arguments.
41 * @param mixed $leftover Additional leftover parameter.
42 */
43 private static function get_custom_args( $args, &$params, &$leftover ) {
44 foreach ( $args as $key => $arg ) {
45 if ( is_string( $arg ) && preg_match( '/{{.*}}/', $arg ) ) {
46 $search = str_replace( array( '{{', '}}' ), '', $arg );
47 $found = self::recursive_look_array( $params, $search );
48 if ( false !== $found ) {
49 $params[ $key ] = $found;
50 }
51 } else {
52 $params[ $key ] = $arg;
53 }
54 }
55 }
56
57 /**
58 * Loop through the arguments array and process each argument based on its type.
59 *
60 * @param mixed $args The array of arguments to process.
61 * @param array $params The parameters array to update with processed arguments.
62 * @param mixed $leftover Additional leftover parameter.
63 *
64 * @throws ReflectionException If the class or method does not exist.
65 */
66 private static function get_program_args( $args, &$params, &$leftover ) {
67 foreach ( $args as $key => $arg ) {
68 // Loop through each.
69 $func_args = array();
70 if ( isset( $arg['params'] ) && is_array( $arg['params'] ) ) {
71 foreach ( $arg['params'] as $value ) {
72 if ( is_string( $value ) && preg_match( '/{{.*}}/', $value ) ) {
73 $search = str_replace( array( '{{', '}}' ), '', $value );
74 $found = self::recursive_look_array( $params, $search );
75 if ( false !== $found ) {
76 $func_args[] = $found;
77 }
78 } else {
79 $func_args[] = $value;
80 }
81 }
82 }
83 // Call the function.
84 if ( ! is_array( $arg['callable'] ) ) {
85 $ret = call_user_func_array( $arg['callable'], $func_args );
86 } else {
87 $reflection_method = new ReflectionMethod( $arg['callable'][0], $arg['callable'][1] );
88 $ret = $reflection_method->invokeArgs( new $arg['callable'][0](), $func_args );
89 }
90 if ( isset( $arg['result_property'] ) ) {
91 $ret = self::recursive_look( $ret, explode( '->', $arg['result_property'] ) );
92 }
93
94 $params[ $key ] = $ret;
95 }
96 }
97
98 /**
99 * Recursively looks up a value within a nested array or object based on provided links.
100 *
101 * This method takes an object or an array and an array of links. It iterates over the links
102 * and uses each link to access a nested property or array element of the $obj. For example,
103 * if the $obj is an array ['a' => ['b' => 'c']], and the links are ['a', 'b'], the method will
104 * return 'c'. If the link does not exist in the object or array, the method will return false.
105 *
106 * @param mixed $obj The object or array to access nested properties or array elements.
107 * @param array $links The array of links to access nested properties or array elements of the $obj.
108 *
109 * @return mixed The final value of $obj if the iteration is successful, false otherwise.
110 */
111 private static function recursive_look( $obj, array $links ) {
112 // We will iterate over the links and use each link to access a nested property or array element of the $obj.
113 foreach ( $links as $link ) {
114 if ( is_array( $obj ) && array_key_exists( $link, $obj ) ) {
115 $obj = $obj[ $link ];
116 } elseif ( is_object( $obj ) && isset( $obj->$link ) ) {
117 $obj = $obj->$link;
118 } else {
119 return false;
120 }
121 }
122 // If the iteration is successful, we return the final value of $obj.
123 return $obj;
124 }
125
126 /**
127 * Performs a recursive lookup in an array based on a given set of links.
128 *
129 * @param array $data The array to perform the lookup on.
130 * @param string $links The links to follow in the array, separated by '->'.
131 *
132 * @return mixed The value found at the end of the links, or null if not found.
133 */
134 private static function recursive_look_array( $data, $links ) {
135 $links_array = explode( '->', $links );
136
137 // Check if the first link exists and is valid.
138 if ( empty( $links_array ) || ! isset( $data[ $links_array[0] ] ) ) {
139 return false;
140 }
141
142 // If the first link exists and is valid, retrieve the value of the first link from the data array.
143 $obj = $data[ $links_array[0] ];
144
145 // If there are more links to process, pass them to recursive_look.
146 if ( count( $links_array ) > 1 ) {
147 // Pass the remaining links starting from index 1 to the recursive_look method.
148 $remaining_links = array_slice( $links_array, 1 );
149 $obj = self::recursive_look( $obj, $remaining_links );
150 }
151
152 // Finally, return the final value found at the end of the links.
153 return $obj;
154 }
155
156 /**
157 * This is a private method that is used to get the text based on the provided parameters.
158 * In many case, text can be condition, example an array contain text 1, text 2, text 3,
159 * if 2 matched, we get the first.
160 *
161 * @param mixed $text The text to be processed. It can be either a string or an array.
162 * @param array $params The parameters used to replace placeholders in the text.
163 *
164 * @return mixed The processed text or false if no match is found.
165 */
166 private function get_text( $text, $params ) {
167 if ( ! is_array( $text ) ) {
168 return $text;
169 }
170
171 $matched = array();
172 foreach ( $text as $row ) {
173 $t = $row[0];
174 // This will be inside parameters.
175 $value_placeholder = str_replace( array( '{{', '}}' ), '', $row[1] );
176 // No need to validate here, all should be passed by hardcode.
177 $value = self::recursive_look_array( $params, $value_placeholder );
178
179 $compare = $row[2];
180 if ( is_string( $compare ) && preg_match( '/{{.*}}/', $compare ) ) {
181 $search = str_replace( array( '{{', '}}' ), '', $compare );
182 $compare = self::recursive_look_array( $params, $search );
183 }
184
185 $comparison = $row[3] ?? '==';
186
187 if ( is_array( $value ) && is_array( $compare ) ) {
188 // Compare 2 arrays.
189 $map = array(
190 '==' => count( self::array_recursive_diff( $value, $compare ) ) === 0,
191 '!=' => count( self::array_recursive_diff( $value, $compare ) ) !== 0,
192 );
193 } elseif ( is_array( $compare ) ) {
194 // In or not in array.
195 $map = array(
196 'in' => in_array( $value, $compare, true ),
197 'not_in' => in_array( $value, $compare, true ) === false,
198 );
199 } else {
200 $map = array(
201 '===' => $value === $compare,
202 '!==' => $value !== $compare,
203 '>' => $value > $compare,
204 '>=' => $value >= $compare,
205 '<' => $value < $compare,
206 '<=' => $value <= $compare,
207 '==' => $value === $compare,
208 '!=' => $value !== $compare,
209 );
210 }
211
212 if ( $map[ $comparison ] ) {
213 $matched[] = $t;
214 }
215 }
216
217 return array_shift( $matched );
218 }
219
220 /**
221 * Recursively finds the difference between two arrays.
222 *
223 * @param array $array1 The first array to compare.
224 * @param array $array2 The second array to compare against.
225 *
226 * @return array Difference between the two arrays.
227 */
228 protected static function array_recursive_diff( $array1, $array2 ): array {
229 $result = array();
230
231 foreach ( $array1 as $key => $value ) {
232 if ( array_key_exists( $key, $array2 ) ) {
233 if ( is_array( $value ) ) {
234 $recursive_diff = self::array_recursive_diff( $value, $array2[ $key ] );
235 if ( count( $recursive_diff ) ) {
236 $result[ $key ] = $recursive_diff;
237 }
238 } elseif ( $value !== $array2[ $key ] ) {
239 $result[ $key ] = $value;
240 }
241 } else {
242 $result[ $key ] = $value;
243 }
244 }
245
246 return $result;
247 }
248
249 /**
250 * Check conditions against parameters and return a boolean result.
251 *
252 * @param mixed $conditions The conditions to check against.
253 * @param array $params The parameters to use for comparison.
254 *
255 * @return bool The boolean result of the condition check.
256 */
257 private static function check_condition( $conditions, $params ): bool {
258 $good = true;
259 foreach ( $conditions as $condition ) {
260 $links = $condition[0];
261 $value = self::recursive_look_array( $params, str_replace( array( '{{', '}}' ), '', $links ) );
262 $compare = $condition[1];
263 if ( is_string( $compare ) && preg_match( '/{{.*}}/', $compare ) ) {
264 $search = str_replace( array( '{{', '}}' ), '', $compare );
265 $compare = self::recursive_look_array( $params, $search );
266 }
267
268 if ( is_array( $compare ) && in_array( $value, $compare, true ) ) {
269 $good = false;
270 } elseif ( $value === $compare ) {
271 $good = false;
272 }
273 }
274
275 return $good;
276 }
277
278 /**
279 * Build log data based on the provided hook data and parameters.
280 *
281 * @return mixed Returns false if the log data cannot be built, otherwise an array of log data.
282 * @throws ReflectionException If the class or method does not exist.
283 */
284 public function build_log_data() {
285 $args = func_get_args();
286 $hook_name = $args[0];
287 $params = $args[1];
288 $user_id = get_current_user_id();
289 $hook_data = $args[2];
290 // Have to build iup params first.
291 if ( ( is_array( $hook_data['args'] ) || $hook_data['args'] instanceof Countable ? count( $hook_data['args'] ) : 0 ) !== ( is_array( $params ) || $params instanceof Countable ? count( $params ) : 0 ) ) {
292 return false;
293 } elseif ( empty( $hook_data['args'] ) && empty( $params ) ) {
294 $params = array();
295 } else {
296 $params = array_combine( $hook_data['args'], $params );
297 }
298
299 if ( isset( $hook_data['callback'] ) && ! empty( $hook_data['callback'] ) ) {
300 // Custom callback provided, call it.
301 $reflection_method = new ReflectionMethod( $hook_data['callback'][0], $hook_data['callback'][1] );
302 $ret = $reflection_method->invokeArgs(
303 new $hook_data['callback'][0](),
304 array(
305 $hook_name,
306 $params,
307 )
308 );
309
310 if ( is_array( $ret ) && 2 === count( $ret ) ) {
311 [ $text, $context ] = $ret;
312 } elseif ( is_array( $ret ) && 3 === count( $ret ) ) {
313 [ $text, $context, $action ] = $ret;
314 } else {
315 $text = false;
316 }
317 } else {
318 /**
319 * First we need to query all parameters,
320 * we have to loop around custom args, program args to queries and maintain the params list,
321 * then we will check the left over, till all done,
322 * then we will build up the text.
323 */
324 $leftover = array();
325 if ( isset( $hook_data['custom_args'] ) ) {
326 self::get_custom_args( $hook_data['custom_args'], $params, $leftover );
327 }
328
329 if ( isset( $hook_data['program_args'] ) ) {
330 self::get_program_args( $hook_data['program_args'], $params, $leftover );
331 }
332 $params = array_merge( $this->get_default_params(), $params );
333 // Still need to check if this condition okay.
334 if ( isset( $hook_data['false_when'] ) && self::check_condition(
335 $hook_data['false_when'],
336 $params
337 ) === false ) {
338 return false;
339 }
340 // Now we got all params as key=>value, just build the text.
341 $text = self::get_text( $hook_data['text'], $params );
342
343 if ( empty( $text ) ) {
344 return false;
345 }
346
347 foreach ( $params as $key => $val ) {
348 $replacer = $val;
349 if ( is_array( $replacer ) || is_object( $replacer ) ) {
350 continue;
351 }
352 $text = str_replace( '{{' . $key . '}}', $replacer, $text );
353 }
354
355 $context = '';
356 if ( isset( $hook_data['context'] ) ) {
357 if ( is_array( $hook_data['context'] ) && is_callable( $hook_data['context'] ) ) {
358 $reflection_method = new ReflectionMethod( $hook_data['context'][0], $hook_data['context'][1] );
359 $context = $reflection_method->invokeArgs(
360 new $hook_data['context'][0](),
361 array(
362 $hook_name,
363 $params,
364 )
365 );
366 } elseif ( preg_match( '/{{.*}}/', $hook_data['context'] ) ) {
367 $context = self::recursive_look_array(
368 $params,
369 str_replace(
370 array(
371 '{{',
372 '}}',
373 ),
374 '',
375 $hook_data['context']
376 )
377 );
378 } else {
379 $context = $hook_data['context'];
380 }
381 }
382 }
383
384 $anonymous_override_list = array(
385 'wp_login',
386 'wp_logout',
387 'retrieve_password',
388 'after_password_reset',
389 );
390 if ( 0 === $user_id && in_array( $hook_name, $anonymous_override_list, true ) ) {
391 // In this state, user id still 0, we have to get the id via hooks param.
392 if ( isset( $params['user'] ) ) {
393 $user = $params['user'];
394 $user_id = $user->ID;
395 } elseif ( isset( $params['user_id'] ) ) {
396 $user_id = $params['user_id'];
397 } else {
398 // Incorrect data. Return.
399 return false;
400 }
401 }
402
403 // We don't get text. Return.
404 if ( ! $text ) {
405 return false;
406 }
407
408 // Build data.
409 $settings = new Audit_Logging();
410 $time = time();
411 $event_type = $hook_data['event_type'];
412 $action_type = $action ?? $hook_data['action_type'];
413 $site_url = network_site_url();
414 $msg = wp_strip_all_tags( $text );
415 $blog_id = get_current_blog_id();
416 $ttl = strtotime( '+ ' . $settings->storage_days );
417 foreach ( $this->get_user_ip() as $ip ) {
418 $post = array(
419 'timestamp' => $time,
420 'event_type' => $event_type,
421 'action_type' => $action_type,
422 'site_url' => $site_url,
423 'user_id' => $user_id,
424 'context' => $context,
425 'ip' => $ip,
426 'msg' => $msg,
427 'blog_id' => $blog_id,
428 'ttl' => $ttl,
429 );
430 Array_Cache::append( 'logs', $post, 'audit' );
431 }
432 }
433
434 /**
435 * Retrieves default parameters for building log data.
436 *
437 * @return array default parameters.
438 */
439 private function get_default_params(): array {
440 return array(
441 'wp_user' => $this->get_source_of_action(),
442 'wp_user_id' => get_current_user_id(),
443 'blog_name' => is_multisite() ? '[' . get_bloginfo( 'name' ) . ']' : '',
444 );
445 }
446 }
447