PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / 6.0.1
Defender Security – Malware Scanner, Login Security & Firewall v6.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 / controller / class-ua-lockout.php

class-ua-lockout.php in Defender Security – Malware Scanner, Login Security & Firewall 6.0.1, at src/controller/class-ua-lockout.php

461 lines 13.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Handles user agent lockout.
4 *
5 * @package WP_Defender\Controller
6 */
7
8 namespace WP_Defender\Controller;
9
10 use Exception;
11 use WP_Defender\Event;
12 use Calotes\Component\Request;
13 use Calotes\Component\Response;
14 use WP_Defender\Traits\Setting;
15 use WP_Defender\Model\Lockout_Log;
16 use WP_Defender\Model\Setting\User_Agent_Lockout;
17 use WP_Defender\Component\Config\Config_Hub_Helper;
18 use WP_Defender\Component\User_Agent as User_Agent_Service;
19
20 /**
21 * Handles user agent lockout.
22 *
23 * @since 2.6.0
24 */
25 class UA_Lockout extends Event {
26
27 use Setting;
28
29 /**
30 * The slug identifier for this controller.
31 *
32 * @var string
33 */
34 public $slug = 'wdf-ip-lockout';
35
36 /**
37 * The model for handling the data.
38 *
39 * @var User_Agent_Lockout
40 */
41 protected $model;
42
43 /**
44 * Service for handling logic.
45 *
46 * @var User_Agent_Service
47 */
48 protected $service;
49
50 /**
51 * Initializes the model and service, registers routes, and sets up scheduled events if the model is active.
52 */
53 public function __construct() {
54 $this->register_routes();
55 $this->model = $this->get_model();
56 $this->service = wd_di()->get( User_Agent_Service::class );
57 add_action( 'defender_enqueue_assets', array( $this, 'enqueue_assets' ) );
58 }
59
60 /**
61 * Returns an instance of the User_Agent_Lockout model class.
62 *
63 * @return User_Agent_Lockout The User_Agent_Lockout model class.
64 */
65 private function get_model() {
66 if ( is_object( $this->model ) ) {
67 return $this->model;
68 }
69
70 return new User_Agent_Lockout();
71 }
72
73 /**
74 * Enqueues scripts and styles for this page.
75 * Only enqueues assets if the page is active.
76 */
77 public function enqueue_assets() {
78 if ( ! $this->is_page_active() ) {
79 return;
80 }
81 }
82
83 /**
84 * Save settings.
85 *
86 * @param Request $request The request object containing new settings data.
87 *
88 * @return Response
89 * @defender_route
90 * @throws Exception If the table is not defined.
91 */
92 public function save_settings( Request $request ) {
93 $data = $request->get_data_by_model( $this->model );
94 $old_enabled = $this->model->enabled;
95 $old_malicious_bot_enabled = $this->model->malicious_bot_enabled;
96 $old_fake_bots_enabled = $this->model->fake_bots_enabled;
97 $prev_data = $this->model->export();
98
99 $this->model->import( $data );
100 if ( $this->model->validate() ) {
101 $arr_blocklist = $this->model->get_lockout_list( 'blocklist' );
102 if ( is_array( $arr_blocklist ) && array() !== $arr_blocklist ) {
103 // Update 'Custom User Agents' if 'Blocklist Presets' is enabled.
104 if (
105 $data['blocklist_presets']
106 && is_array( $data['blocklist_preset_values'] )
107 && array() !== $data['blocklist_preset_values']
108 ) {
109 // Check and remove duplicates.
110 $common_result = array_intersect( $arr_blocklist, $data['blocklist_preset_values'] );
111 if ( array() !== $common_result ) {
112 $arr_blocklist = User_Agent_Service::check_and_remove_duplicates(
113 $arr_blocklist,
114 $common_result
115 );
116 $this->model->blacklist = implode( PHP_EOL, $arr_blocklist );
117 }
118 }
119 // Update 'Custom User Agents' if 'Scripts Presets' is enabled.
120 if (
121 $data['script_presets']
122 && is_array( $data['script_preset_values'] )
123 && array() !== $data['script_preset_values']
124 ) {
125 // Check and remove duplicates.
126 $common_result = array_intersect( $arr_blocklist, $data['script_preset_values'] );
127 if ( array() !== $common_result ) {
128 $arr_blocklist = User_Agent_Service::check_and_remove_duplicates(
129 $arr_blocklist,
130 $common_result
131 );
132 $this->model->blacklist = implode( PHP_EOL, $arr_blocklist );
133 }
134 }
135 }
136
137 $this->model->save();
138 Config_Hub_Helper::set_clear_active_flag();
139 // Attention: next actions after saving model changes.
140 if (
141 ( ! $this->model->enabled && $old_enabled ) ||
142 ( ! $this->model->malicious_bot_enabled && $old_malicious_bot_enabled )
143 ) {
144 wd_di()->get( Malicious_Bot::class )->remove_data();
145 } elseif (
146 ( $this->model->enabled && ! $old_enabled && $this->model->malicious_bot_enabled ) ||
147 ( $this->model->malicious_bot_enabled && ! $old_malicious_bot_enabled && $this->model->enabled )
148 ) {
149 wd_di()->get( Malicious_Bot::class )->rotate_hash();
150 }
151
152 if (
153 ( ! $this->model->enabled && $old_enabled ) ||
154 ( ! $this->model->fake_bots_enabled && $old_fake_bots_enabled )
155 ) {
156 wd_di()->get( Fake_Bot_Detection::class )->remove_data();
157 }
158
159 // Maybe track.
160 if ( ! defender_is_wp_cli() ) {
161 // Track if UA Module is activated/deactivated OR there is a change in State on Add/Remove Custom User Agents (Allowlist/Blocklist).
162 if ( ( $old_enabled !== $data['enabled'] )
163 || ( $this->is_feature_state_changed( $prev_data['blacklist'], $data['blacklist'] ) )
164 || ( $this->is_feature_state_changed( $prev_data['whitelist'], $data['whitelist'] ) )
165 ) {
166 $track_data = array(
167 'Action' => $data['enabled'] ? 'Enabled' : 'Disabled',
168 'No of Bots in the Whitelist' => count( $this->model->get_lockout_list( 'allowlist', false ) ),
169 'No of Bots in the Blocklist' => count( $this->model->get_lockout_list( 'blocklist', false ) ),
170 );
171 $this->track_feature( 'def_user_agent_banning', $track_data );
172 }
173 // New one for 'Blocklist Presets'.
174 if (
175 ( $prev_data['blocklist_presets'] !== $data['blocklist_presets'] ) ||
176 ( count( $prev_data['blocklist_preset_values'] ) !==
177 count( array_intersect( $prev_data['blocklist_preset_values'], $data['blocklist_preset_values'] ) )
178 )
179 ) {
180 $track_data = array(
181 'Action' => $data['blocklist_presets'] ? 'Enabled' : 'Disabled',
182 'List of Activated Bots' => implode( ', ', $data['blocklist_preset_values'] ),
183 );
184 $this->track_feature( 'def_ua_blocklist_preset', $track_data );
185 }
186 // New one for 'Script Presets'.
187 if (
188 ( $prev_data['script_presets'] !== $data['script_presets'] ) ||
189 ( count( $prev_data['script_preset_values'] ) !==
190 count( array_intersect( $prev_data['script_preset_values'], $data['script_preset_values'] ) )
191 )
192 ) {
193 $track_data = array(
194 'Action' => $data['script_presets'] ? 'Enabled' : 'Disabled',
195 'List of Activated Scripts' => implode( ', ', $data['script_preset_values'] ),
196 );
197 $this->track_feature( 'def_ua_scripts_preset', $track_data );
198 }
199 // Track "Fake Bots Detection".
200 if ( $prev_data['fake_bots_enabled'] !== $data['fake_bots_enabled'] ||
201 $prev_data['fake_bots_lockout_type'] !== $data['fake_bots_lockout_type']
202 ) {
203 $data = array(
204 'Action' => $data['fake_bots_enabled'] ? 'Enabled' : 'Disabled',
205 'Blocking type' => 'temporary' === $data['fake_bots_lockout_type'] ? 'Temporary' : 'Permanent',
206 );
207 $this->track_feature( 'def_fake_crawler', $data );
208 }
209 }
210
211 return new Response(
212 true,
213 array_merge(
214 array(
215 'message' => $this->get_update_message(
216 $data,
217 $old_enabled,
218 User_Agent_Lockout::get_module_name()
219 ),
220 'auto_close' => true,
221 ),
222 $this->data_frontend()
223 )
224 );
225 }
226
227 return new Response(
228 false,
229 array( 'message' => $this->model->get_formatted_errors() )
230 );
231 }
232
233 /**
234 * Removes settings for all submodules.
235 */
236 public function remove_settings() {
237 }
238
239 /**
240 * Delete all the data & the cache.
241 */
242 public function remove_data() {
243 }
244
245 /**
246 * Converts the current object state to an array.
247 *
248 * @return array The array representation of the object.
249 */
250 public function to_array(): array {
251 return array();
252 }
253
254 /**
255 * Provides data for the frontend.
256 *
257 * @return array An array of data for the frontend.
258 */
259 public function data_frontend(): array {
260 $arr_model = $this->model->export();
261
262 // Check for empty Disallow line only if Bot Trap is enabled.
263 $has_empty_disallow = false;
264 if ( $this->model->enabled && $this->model->malicious_bot_enabled ) {
265 $malicious_bot_service = wd_di()->get( \WP_Defender\Component\Malicious_Bot::class );
266 $has_empty_disallow = $malicious_bot_service->has_empty_disallow_line();
267 }
268
269 $misc = array(
270 'no_ua' => '' === $arr_model['blacklist'] && '' === $arr_model['whitelist'],
271 'module_name' => User_Agent_Lockout::get_module_name(),
272 'blocklist_presets' => User_Agent_Service::get_blocklist_presets(),
273 'script_presets' => User_Agent_Service::get_script_presets(),
274 'has_empty_disallow_line' => $has_empty_disallow,
275 'lockouts_last_24_hours' => Lockout_Log::count(
276 strtotime( '-24 hours' ),
277 time(),
278 Lockout_Log::LOCKOUT_UA
279 ),
280 );
281
282 return array_merge(
283 array(
284 'model' => $arr_model,
285 'misc' => $misc,
286 ),
287 $this->dump_routes_and_nonces()
288 );
289 }
290
291 /**
292 * Adapt the given data array by adding additional fields if necessary.
293 *
294 * @param array $data The data array to adapt.
295 *
296 * @return array The adapted data array.
297 */
298 private function adapt_data( array $data ): array {
299 $adapted_data = array();
300 if ( isset( $data['ua_banning_enabled'] ) ) {
301 $adapted_data['enabled'] = (bool) $data['ua_banning_enabled'];
302 }
303 if ( isset( $data['ua_banning_message'] ) ) {
304 $adapted_data['message'] = $data['ua_banning_message'];
305 }
306 if ( isset( $data['ua_banning_blacklist'] ) ) {
307 $adapted_data['blacklist'] = $data['ua_banning_blacklist'];
308 }
309 if ( isset( $data['ua_banning_whitelist'] ) ) {
310 $adapted_data['whitelist'] = $data['ua_banning_whitelist'];
311 }
312 if ( isset( $data['ua_banning_empty_headers'] ) ) {
313 $adapted_data['empty_headers'] = (bool) $data['ua_banning_empty_headers'];
314 }
315
316 return array_merge( $data, $adapted_data );
317 }
318
319 /**
320 * Imports data into the model.
321 *
322 * @param array $data Data to be imported into the model.
323 *
324 * @throws Exception If table is not defined.
325 */
326 public function import_data( array $data ): void {
327 $model = $this->get_model();
328 if ( array() !== $data ) {
329 $data = $this->adapt_data( $data );
330 $model->import( $data );
331 if ( $model->validate() ) {
332 $model->save();
333 }
334 } else {
335 $default_ua_values = $model->get_default_values();
336 $model->enabled = false;
337 $model->message = $default_ua_values['message'];
338 $model->blacklist = $default_ua_values['blacklist'];
339 $model->whitelist = $default_ua_values['whitelist'];
340 $model->empty_headers = false;
341 $model->save();
342 }
343 }
344
345 /**
346 * Exports User Agents to a CSV file.
347 *
348 * @return void
349 * @defender_route
350 * @since 2.6.0
351 */
352 public function export_ua(): void {
353 $data = array();
354
355 foreach ( $this->model->get_lockout_list( 'allowlist', false ) as $ua ) {
356 $data[] = array(
357 'ua' => $ua,
358 'type' => 'allowlist',
359 );
360 }
361
362 foreach ( $this->model->get_all_selected_blocklist_ua( false ) as $ua ) {
363 $data[] = array(
364 'ua' => $ua,
365 'type' => 'blocklist',
366 );
367 }
368
369 // WP_Filesystem class doesn’t directly provide a function for opening a stream to php://memory with the 'w' mode.
370 $fp = fopen( 'php://memory', 'w' ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen
371 foreach ( $data as $fields ) {
372 fputcsv( $fp, $fields, ',', '"', '\\' );
373 }
374 $filename = 'wdf-ua-export-' . wp_date( 'ymdHis' ) . '.csv';
375 fseek( $fp, 0 );
376 header( 'Content-Type: text/csv' );
377 header( 'Content-Disposition: attachment; filename="' . $filename . '";' );
378 // Make php send the generated csv lines to the browser.
379 fpassthru( $fp );
380 exit();
381 }
382
383 /**
384 * Importing UAs from exporter.
385 *
386 * @param Request $request The request object.
387 *
388 * @defender_route
389 * @return Response
390 */
391 public function import_ua( Request $request ) {
392 $data = $request->get_data(
393 array(
394 'id' => array(
395 'type' => 'int',
396 ),
397 )
398 );
399
400 $file = '';
401 $attached_id = $data['id'] ?? 0;
402 if ( 0 < $attached_id ) {
403 if ( ! is_object( get_post( $attached_id ) ) ) {
404 return new Response(
405 false,
406 array( 'message' => esc_html__( 'Your file is invalid!', 'defender-security' ) )
407 );
408 }
409
410 $file = get_attached_file( $attached_id );
411 } else {
412 $file_data = defender_get_data_from_request( 'file', 'f' );
413 if ( is_array( $file_data ) && isset( $file_data['tmp_name'] ) && is_uploaded_file( $file_data['tmp_name'] ) ) {
414 $file = $file_data['tmp_name'];
415 }
416 }
417
418 if ( ! is_string( $file ) || '' === $file || ! is_file( $file ) ) {
419 return new Response(
420 false,
421 array( 'message' => esc_html__( 'Your file is invalid!', 'defender-security' ) )
422 );
423 }
424
425 $data = $this->service->verify_import_file( $file );
426 if ( ! $data ) {
427 return new Response(
428 false,
429 array(
430 'message' => esc_html__( 'Your file content is invalid! Please use a CSV file format and try again.', 'defender-security' ),
431 )
432 );
433 }
434
435 // All good, start to import.
436 foreach ( $data as $line ) {
437 $this->model->add_to_list( $line[0], $line[1] );
438 }
439
440 return new Response(
441 true,
442 array_merge(
443 array(
444 'message' => esc_html__( 'Your blocklist and allowlist have been successfully imported.', 'defender-security' ),
445 'auto_close' => true,
446 ),
447 $this->data_frontend()
448 )
449 );
450 }
451
452 /**
453 * Exports strings.
454 *
455 * @return array An array of strings.
456 */
457 public function export_strings(): array {
458 return array();
459 }
460 }
461