PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / 3.0.1
Defender Security – Malware Scanner, Login Security & Firewall v3.0.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 / audit-event.php

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

391 lines 10.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Author: Hoang Ngo
4 */
5
6 namespace WP_Defender\Component\Audit;
7
8 use Calotes\Helper\Array_Cache;
9 use WP_Defender\Behavior\Utils;
10 use WP_Defender\Component;
11 use WP_Defender\Model\Setting\Audit_Logging;
12 use WP_Defender\Module\Audit\Component\Audit_API;
13 use WP_Defender\Module\Audit\Model\Settings;
14 use WP_Defender\Traits\IP;
15 use WP_Defender\Traits\User;
16
17 /**
18 * Class Audit_Event
19 * @package WP_Defender\Component\Audit
20 */
21 abstract class Audit_Event extends Component {
22 use User, IP;
23
24 public const ACTION_DELETED = 'deleted', ACTION_TRASHED = 'trashed', ACTION_RESTORED = 'restored', ACTION_UPDATED = 'updated';
25
26 /**
27 * Return an array of hooks.
28 *
29 * @return mixed
30 */
31 public abstract function get_hooks();
32
33 /**
34 * @param array $args Custom args.
35 * @param array $params Whole parameters of current hook.
36 */
37 private static function get_custom_args( $args, &$params, &$leftover ) {
38 foreach ( $args as $key => $arg ) {
39 if ( is_string( $arg ) && preg_match( '/{{.*}}/', $arg ) ) {
40 $search = str_replace( array( '{{', '}}' ), '', $arg );
41 $found = self::recursive_look_array( $params, $search );
42 if ( false !== $found ) {
43 $params[ $key ] = $found;
44 }
45 } else {
46 $params[ $key ] = $arg;
47 }
48 }
49 }
50
51 /**
52 * @param $args
53 * @param $params
54 * @param $leftover
55 */
56 private static function get_program_args( $args, &$params, &$leftover ) {
57 foreach ( $args as $key => $arg ) {
58 // Loop through each.
59 $func_args = array();
60 if ( isset( $arg['params'] ) && is_array( $arg['params'] ) ) {
61 foreach ( $arg['params'] as $value ) {
62 if ( is_string( $value ) && preg_match( '/{{.*}}/', $value ) ) {
63 $search = str_replace( array( '{{', '}}' ), '', $value );
64 $found = self::recursive_look_array( $params, $search );
65 if ( false !== $found ) {
66 $func_args[] = $found;
67 }
68 } else {
69 $func_args[] = $value;
70 }
71 }
72 }
73 // Call the function.
74 if ( ! is_array( $arg['callable'] ) ) {
75 $ret = call_user_func_array( $arg['callable'], $func_args );
76 } else {
77 $reflection_method = new \ReflectionMethod( $arg['callable'][0], $arg['callable'][1] );
78 $ret = $reflection_method->invokeArgs( new $arg['callable'][0], $func_args );
79 }
80 if ( isset( $arg['result_property'] ) ) {
81 $ret = self::recursive_look( $ret, explode( '->', $arg['result_property'] ) );
82 }
83
84 $params[ $key ] = $ret;
85 }
86 }
87
88
89 /**
90 * @param $obj
91 * @param $links
92 *
93 * @return bool|mixed
94 */
95 private static function recursive_look( $obj, $links ) {
96 $look = null;
97 while ( is_array($links) || $links instanceof \Countable ? count( $links ) : 0 ) {
98 $link = array_shift( $links );
99 if ( is_array( $obj ) ) {
100 $obj = @$obj[ $link ];// phpcs:ignore
101 } elseif ( is_object( $obj ) ) {
102 $obj = $obj->$link;
103 } else {
104 $look = false;
105 break;
106 }
107 }
108 if ( false === $look ) {
109 return false;
110 }
111
112 return $obj;
113 }
114
115 /**
116 * @param $data
117 * @param $links
118 *
119 * @return bool|mixed
120 */
121 private static function recursive_look_array( $data, $links ) {
122 $links = explode( '->', $links );
123 $obj = $data[ $links[0] ];
124 unset( $links[0] );
125 $obj = self::recursive_look( $obj, $links );
126
127 return $obj;
128 }
129
130 /**
131 * In many case, text can be condition, example an array contain text 1, text 2, text 3, if 2 matched, we get the first.
132 *
133 * @param $text
134 * @param $params
135 *
136 * @return mixed
137 */
138 private function get_text( $text, $params ) {
139 if ( ! is_array( $text ) ) {
140 return $text;
141 }
142
143 $matched = array();
144 foreach ( $text as $row ) {
145 $t = $row[0];
146 // This will be inside parameters.
147 $value_placeholder = str_replace( array( '{{', '}}' ), '', $row[1] );
148 // No need to validate here, all should be passed by hardcode.
149 $value = self::recursive_look_array( $params, $value_placeholder );
150
151 $compare = $row[2];
152 if ( is_string( $compare ) && preg_match( '/{{.*}}/', $compare ) ) {
153 $search = str_replace( array( '{{', '}}' ), '', $compare );
154 $compare = self::recursive_look_array( $params, $search );
155 }
156
157 $comparison = $row[3] ?? '==';
158
159 if ( is_array( $value ) && is_array( $compare ) ) {
160 // Compare 2 arrays.
161 $map = array(
162 '==' => count( self::array_recursive_diff( $value, $compare ) ) == 0 ? true : false,
163 '!=' => count( self::array_recursive_diff( $value, $compare ) ) == 0 ? false : true,
164 );
165 } elseif ( is_array( $compare ) ) {
166 // In or not in array.
167 $map = array(
168 'in' => in_array( $value, $compare ),
169 'not_in' => in_array( $value, $compare ) == false,
170 );
171 } else {
172 $map = array(
173 '==' => $value == $compare,
174 '!=' => $value != $compare,
175 '>' => $value > $compare,
176 '>=' => $value >= $compare,
177 '<' => $value < $compare,
178 '<=' => $value <= $compare,
179 );
180 }
181
182 if ( $map[ $comparison ] ) {
183 $matched[] = $t;
184 }
185 }
186
187 return array_shift( $matched );
188 }
189
190 /**
191 * @param array $array1
192 * @param array $array2
193 *
194 * @return array
195 */
196 protected static function array_recursive_diff( $array1, $array2 ) {
197 $result = array();
198
199 foreach ( $array1 as $key => $value ) {
200 if ( array_key_exists( $key, $array2 ) ) {
201 if ( is_array( $value ) ) {
202 $recursive_diff = self::array_recursive_diff( $value, $array2[ $key ] );
203 if ( count( $recursive_diff ) ) {
204 $result[ $key ] = $recursive_diff;
205 }
206 } else {
207 if ( $value != $array2[ $key ] ) {
208 $result[ $key ] = $value;
209 }
210 }
211 } else {
212 $result[ $key ] = $value;
213 }
214 }
215
216 return $result;
217 }
218
219 /**
220 * @return bool
221 */
222 private static function check_condition( $conditions, $params ) {
223 $good = true;
224 foreach ( $conditions as $condition ) {
225 $links = $condition[0];
226 $value = self::recursive_look_array( $params, str_replace( array( '{{', '}}' ), '', $links ) );
227 $compare = $condition[1];
228 if ( is_string( $compare ) && preg_match( '/{{.*}}/', $compare ) ) {
229 $search = str_replace( array( '{{', '}}' ), '', $compare );
230 $compare = self::recursive_look_array( $params, $search );
231 }
232
233 if ( is_array( $compare ) && in_array( $value, $compare ) ) {
234 $good = false;
235 } elseif ( $value == $compare ) {
236 $good = false;
237 }
238 }
239
240 return $good;
241 }
242
243 public function build_log_data() {
244 $args = func_get_args();
245 $hook_name = $args[0];
246 $params = $args[1];
247 $user_id = get_current_user_id();
248 $hook_data = $args[2];
249 // Have to build iup params first.
250 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) ) {
251 // Return false for now.
252 return false;
253 } else {
254 if ( empty( $hook_data['args'] ) && empty( $params ) ) {
255 $params = array();
256 } else {
257 $params = array_combine( $hook_data['args'], $params );
258 }
259 }
260
261 if ( isset( $hook_data['callback'] ) && ! empty( $hook_data['callback'] ) ) {
262 // Custom callback provided, call it.
263 $reflection_method = new \ReflectionMethod( $hook_data['callback'][0], $hook_data['callback'][1] );
264 $ret = $reflection_method->invokeArgs(
265 new $hook_data['callback'][0],
266 array(
267 $hook_name,
268 $params,
269 )
270 );
271
272 if ( is_array( $ret ) && 2 === count( $ret ) ) {
273 [$text, $context] = $ret;
274 } elseif ( is_array( $ret ) && 3 === count( $ret ) ) {
275 [$text, $context, $action] = $ret;
276 } else {
277 $text = false;
278 }
279 } else {
280 /**
281 * First we need to query all parameters,
282 * we have to loop around custom args, program args to queries and maintain the params list,
283 * then we will check the left over, till all done,
284 * then we will build up the text.
285 */
286 $leftover = array();
287 if ( isset( $hook_data['custom_args'] ) ) {
288 self::get_custom_args( $hook_data['custom_args'], $params, $leftover );
289 }
290
291 if ( isset( $hook_data['program_args'] ) ) {
292 self::get_program_args( $hook_data['program_args'], $params, $leftover );
293 }
294 // Finally, default params.
295 $params = array_merge( $this->get_default_params(), $params );
296 // Still need to check if this condition okay.
297 if ( isset( $hook_data['false_when'] ) && self::check_condition(
298 $hook_data['false_when'],
299 $params
300 ) == false ) {
301 return false;
302 }
303 // Now we got all params as key=>value, just build the text.
304 $text = self::get_text( $hook_data['text'], $params );
305
306 if ( empty( $text ) ) {
307 return false;
308 }
309
310 foreach ( $params as $key => $val ) {
311 $replacer = $val;
312 if ( is_array( $replacer ) || is_object( $replacer ) ) {
313 continue;
314 }
315 $text = str_replace( '{{' . $key . '}}', $replacer, $text );
316 }
317
318 $context = '';
319 if ( isset( $hook_data['context'] ) ) {
320 if ( is_callable( $hook_data['context'] ) ) {
321 $reflection_method = new \ReflectionMethod( $hook_data['context'][0], $hook_data['context'][1] );
322 $context = $reflection_method->invokeArgs(
323 new $hook_data['context'][0],
324 array(
325 $hook_name,
326 $params,
327 )
328 );
329 } elseif ( preg_match( '/{{.*}}/', $hook_data['context'] ) ) {
330 $context = self::recursive_look_array(
331 $params,
332 str_replace(
333 array(
334 '{{',
335 '}}',
336 ),
337 '',
338 $hook_data['context']
339 )
340 );
341 } else {
342 $context = $hook_data['context'];
343 }
344 }
345 }
346
347 $anonymous_override_list = array(
348 'wp_login',
349 'wp_logout',
350 'retrieve_password',
351 'after_password_reset',
352 );
353 if ( 0 === $user_id && in_array( $hook_name, $anonymous_override_list, true ) ) {
354 // In this state, user id still 0, we have to get the id via hooks param.
355 $user = $params['user'];
356 $user_id = $user->ID;
357 }
358
359 // We got text, now build the data.
360 if ( false == $text ) {
361 return;
362 }
363 $settings = new Audit_Logging();
364 // Build data.
365 $post = array(
366 'timestamp' => time(),
367 'event_type' => $hook_data['event_type'],
368 'action_type' => $action ?? $hook_data['action_type'],
369 'site_url' => network_site_url(),
370 'user_id' => $user_id,
371 'context' => $context,
372 'ip' => $this->get_user_ip(),
373 'msg' => strip_tags( $text ),
374 'blog_id' => get_current_blog_id(),
375 'ttl' => strtotime( '+ ' . $settings->storage_days ),
376 );
377 Array_Cache::append( 'logs', $post, 'audit' );
378 }
379
380 /**
381 * @return array
382 */
383 private function get_default_params() {
384 return array(
385 'wp_user' => $this->get_source_of_action(),
386 'wp_user_id' => get_current_user_id(),
387 'blog_name' => is_multisite() ? '[' . get_bloginfo( 'name' ) . ']' : '',
388 );
389 }
390 }
391