PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / 5.8.1
Defender Security – Malware Scanner, Login Security & Firewall v5.8.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 5.8.1, at src/controller/class-ua-lockout.php

438 lines 12.3 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\Setting\User_Agent_Lockout;
16 use WP_Defender\Component\Config\Config_Hub_Helper;
17 use WP_Defender\Component\User_Agent as User_Agent_Service;
18
19 /**
20 * Handles user agent lockout.
21 *
22 * @since 2.6.0
23 */
24 class UA_Lockout extends Event {
25
26 use Setting;
27
28 /**
29 * The slug identifier for this controller.
30 *
31 * @var string
32 */
33 public $slug = 'wdf-ip-lockout';
34
35 /**
36 * The model for handling the data.
37 *
38 * @var User_Agent_Lockout
39 */
40 protected $model;
41
42 /**
43 * Service for handling logic.
44 *
45 * @var User_Agent_Service
46 */
47 protected $service;
48
49 /**
50 * Initializes the model and service, registers routes, and sets up scheduled events if the model is active.
51 */
52 public function __construct() {
53 $this->register_routes();
54 $this->model = $this->get_model();
55 $this->service = wd_di()->get( User_Agent_Service::class );
56 add_action( 'defender_enqueue_assets', array( $this, 'enqueue_assets' ) );
57 }
58
59 /**
60 * Returns an instance of the User_Agent_Lockout model class.
61 *
62 * @return User_Agent_Lockout The User_Agent_Lockout model class.
63 */
64 private function get_model() {
65 if ( is_object( $this->model ) ) {
66 return $this->model;
67 }
68
69 return new User_Agent_Lockout();
70 }
71
72 /**
73 * Enqueues scripts and styles for this page.
74 * Only enqueues assets if the page is active.
75 */
76 public function enqueue_assets() {
77 if ( ! $this->is_page_active() ) {
78 return;
79 }
80 wp_localize_script( 'def-iplockout', 'ua_lockout', $this->data_frontend() );
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 = (bool) $this->model->enabled;
95 $old_malicious_bot_enabled = (bool) $this->model->malicious_bot_enabled;
96 $old_fake_bots_enabled = (bool) $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
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 if ( $this->is_feature_state_changed( $prev_data, $data ) ) {
162 $track_data = array(
163 'Action' => $data['enabled'] ? 'Enabled' : 'Disabled',
164 'No of Bots in the Whitelist' => count( $this->model->get_lockout_list( 'allowlist', false ) ),
165 'No of Bots in the Blocklist' => count( $this->model->get_lockout_list( 'blocklist', false ) ),
166 );
167 $this->track_feature( 'def_user_agent_banning', $track_data );
168 }
169 // New one for 'Blocklist Presets'.
170 if (
171 ( $prev_data['blocklist_presets'] !== $data['blocklist_presets'] ) ||
172 ( count( $prev_data['blocklist_preset_values'] ) !==
173 count( array_intersect( $prev_data['blocklist_preset_values'], $data['blocklist_preset_values'] ) )
174 )
175 ) {
176 $track_data = array(
177 'Action' => $data['blocklist_presets'] ? 'Enabled' : 'Disabled',
178 'List of Activated Bots' => implode( ', ', $data['blocklist_preset_values'] ),
179 );
180 $this->track_feature( 'def_ua_blocklist_preset', $track_data );
181 }
182 // New one for 'Script Presets'.
183 if (
184 ( $prev_data['script_presets'] !== $data['script_presets'] ) ||
185 ( count( $prev_data['script_preset_values'] ) !==
186 count( array_intersect( $prev_data['script_preset_values'], $data['script_preset_values'] ) )
187 )
188 ) {
189 $track_data = array(
190 'Action' => $data['script_presets'] ? 'Enabled' : 'Disabled',
191 'List of Activated Scripts' => implode( ', ', $data['script_preset_values'] ),
192 );
193 $this->track_feature( 'def_ua_scripts_preset', $track_data );
194 }
195 // Track "Fake Bots Detection".
196 if ( $prev_data['fake_bots_enabled'] !== $data['fake_bots_enabled'] ||
197 $prev_data['fake_bots_lockout_type'] !== $data['fake_bots_lockout_type']
198 ) {
199 $data = array(
200 'Action' => $data['fake_bots_enabled'] ? 'Enabled' : 'Disabled',
201 'Blocking type' => 'temporary' === $data['fake_bots_lockout_type'] ? 'Temporary' : 'Permanent',
202 );
203 $this->track_feature( 'def_fake_crawler', $data );
204 }
205 }
206
207 return new Response(
208 true,
209 array_merge(
210 array(
211 'message' => $this->get_update_message(
212 $data,
213 $old_enabled,
214 User_Agent_Lockout::get_module_name()
215 ),
216 'auto_close' => true,
217 ),
218 $this->data_frontend()
219 )
220 );
221 }
222
223 return new Response(
224 false,
225 array( 'message' => $this->model->get_formatted_errors() )
226 );
227 }
228
229 /**
230 * Removes settings for all submodules.
231 */
232 public function remove_settings() {
233 }
234
235 /**
236 * Delete all the data & the cache.
237 */
238 public function remove_data() {
239 }
240
241 /**
242 * Converts the current object state to an array.
243 *
244 * @return array The array representation of the object.
245 */
246 public function to_array(): array {
247 return array();
248 }
249
250 /**
251 * Provides data for the frontend.
252 *
253 * @return array An array of data for the frontend.
254 */
255 public function data_frontend(): array {
256 $arr_model = $this->model->export();
257
258 // Check for empty Disallow line only if Bot Trap is enabled.
259 $has_empty_disallow = false;
260 if ( $this->model->enabled && $this->model->malicious_bot_enabled ) {
261 $malicious_bot_service = wd_di()->get( \WP_Defender\Component\Malicious_Bot::class );
262 $has_empty_disallow = $malicious_bot_service->has_empty_disallow_line();
263 }
264
265 $misc = array(
266 'no_ua' => '' === $arr_model['blacklist'] && '' === $arr_model['whitelist'],
267 'module_name' => User_Agent_Lockout::get_module_name(),
268 'blocklist_presets' => User_Agent_Service::get_blocklist_presets(),
269 'script_presets' => User_Agent_Service::get_script_presets(),
270 'has_empty_disallow_line' => $has_empty_disallow,
271 );
272
273 return array_merge(
274 array(
275 'model' => $arr_model,
276 'misc' => $misc,
277 ),
278 $this->dump_routes_and_nonces()
279 );
280 }
281
282 /**
283 * Adapt the given data array by adding additional fields if necessary.
284 *
285 * @param array $data The data array to adapt.
286 *
287 * @return array The adapted data array.
288 */
289 private function adapt_data( array $data ): array {
290 $adapted_data = array();
291 if ( isset( $data['ua_banning_enabled'] ) ) {
292 $adapted_data['enabled'] = (bool) $data['ua_banning_enabled'];
293 }
294 if ( isset( $data['ua_banning_message'] ) ) {
295 $adapted_data['message'] = $data['ua_banning_message'];
296 }
297 if ( isset( $data['ua_banning_blacklist'] ) ) {
298 $adapted_data['blacklist'] = $data['ua_banning_blacklist'];
299 }
300 if ( isset( $data['ua_banning_whitelist'] ) ) {
301 $adapted_data['whitelist'] = $data['ua_banning_whitelist'];
302 }
303 if ( isset( $data['ua_banning_empty_headers'] ) ) {
304 $adapted_data['empty_headers'] = (bool) $data['ua_banning_empty_headers'];
305 }
306
307 return array_merge( $data, $adapted_data );
308 }
309
310 /**
311 * Imports data into the model.
312 *
313 * @param array $data Data to be imported into the model.
314 *
315 * @throws Exception If table is not defined.
316 */
317 public function import_data( array $data ): void {
318 $model = $this->get_model();
319 if ( array() !== $data ) {
320 $data = $this->adapt_data( $data );
321 $model->import( $data );
322 if ( $model->validate() ) {
323 $model->save();
324 }
325 } else {
326 $default_ua_values = $model->get_default_values();
327 $model->enabled = false;
328 $model->message = $default_ua_values['message'];
329 $model->blacklist = $default_ua_values['blacklist'];
330 $model->whitelist = $default_ua_values['whitelist'];
331 $model->empty_headers = false;
332 $model->save();
333 }
334 }
335
336 /**
337 * Exports User Agents to a CSV file.
338 *
339 * @return void
340 * @defender_route
341 * @since 2.6.0
342 */
343 public function export_ua(): void {
344 $data = array();
345
346 foreach ( $this->model->get_lockout_list( 'blocklist', false ) as $ua ) {
347 $data[] = array(
348 'ua' => $ua,
349 'type' => 'blocklist',
350 );
351 }
352 foreach ( $this->model->get_lockout_list( 'allowlist', false ) as $ua ) {
353 $data[] = array(
354 'ua' => $ua,
355 'type' => 'allowlist',
356 );
357 }
358 // WP_Filesystem class doesn’t directly provide a function for opening a stream to php://memory with the 'w' mode.
359 $fp = fopen( 'php://memory', 'w' ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen
360 foreach ( $data as $fields ) {
361 fputcsv( $fp, $fields, ',', '"', '\\' );
362 }
363 $filename = 'wdf-ua-export-' . wp_date( 'ymdHis' ) . '.csv';
364 fseek( $fp, 0 );
365 header( 'Content-Type: text/csv' );
366 header( 'Content-Disposition: attachment; filename="' . $filename . '";' );
367 // Make php send the generated csv lines to the browser.
368 fpassthru( $fp );
369 exit();
370 }
371
372 /**
373 * Importing UAs from exporter.
374 *
375 * @param Request $request The request object.
376 *
377 * @defender_route
378 * @return Response
379 */
380 public function import_ua( Request $request ) {
381 $data = $request->get_data(
382 array(
383 'id' => array(
384 'type' => 'int',
385 ),
386 )
387 );
388
389 $attached_id = $data['id'];
390 if ( ! is_object( get_post( $attached_id ) ) ) {
391 return new Response(
392 false,
393 array( 'message' => esc_html__( 'Your file is invalid!', 'defender-security' ) )
394 );
395 }
396
397 $file = get_attached_file( $attached_id );
398 if ( ! is_file( $file ) ) {
399 return new Response(
400 false,
401 array( 'message' => esc_html__( 'Your file is invalid!', 'defender-security' ) )
402 );
403 }
404
405 $data = $this->service->verify_import_file( $file );
406 if ( ! $data ) {
407 return new Response(
408 false,
409 array(
410 'message' => esc_html__( 'Your file content is invalid! Please use a CSV file format and try again.', 'defender-security' ),
411 )
412 );
413 }
414
415 // All good, start to import.
416 foreach ( $data as $line ) {
417 $this->model->add_to_list( $line[0], $line[1] );
418 }
419
420 return new Response(
421 true,
422 array(
423 'message' => esc_html__( 'Your blocklist and allowlist have been successfully imported.', 'defender-security' ),
424 'interval' => 1,
425 )
426 );
427 }
428
429 /**
430 * Exports strings.
431 *
432 * @return array An array of strings.
433 */
434 public function export_strings(): array {
435 return array();
436 }
437 }
438