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

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

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