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

402 lines 10.9 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_bot_trap_enabled = (bool) $this->model->bot_trap_enabled;
96 $prev_data = $this->model->export();
97
98 $this->model->import( $data );
99 if ( $this->model->validate() ) {
100 $arr_blocklist = $this->model->get_lockout_list( 'blocklist' );
101 if ( ! empty( $arr_blocklist ) ) {
102 // Update 'Custom User Agents' if 'Blocklist Presets' is enabled.
103 if ( $data['blocklist_presets'] && ! empty( $data['blocklist_preset_values'] ) ) {
104 // Check and remove duplicates.
105 $common_result = array_intersect( $arr_blocklist, $data['blocklist_preset_values'] );
106 if ( ! empty( $common_result ) ) {
107 $arr_blocklist = User_Agent_Service::check_and_remove_duplicates(
108 $arr_blocklist,
109 $common_result
110 );
111 $this->model->blacklist = implode( PHP_EOL, $arr_blocklist );
112 }
113 }
114 // Update 'Custom User Agents' if 'Scripts Presets' is enabled.
115 if ( $data['script_presets'] && ! empty( $data['script_preset_values'] ) ) {
116 // Check and remove duplicates.
117 $common_result = array_intersect( $arr_blocklist, $data['script_preset_values'] );
118 if ( ! empty( $common_result ) ) {
119 $arr_blocklist = User_Agent_Service::check_and_remove_duplicates(
120 $arr_blocklist,
121 $common_result
122 );
123 $this->model->blacklist = implode( PHP_EOL, $arr_blocklist );
124 }
125 }
126 }
127
128 $this->model->save();
129 Config_Hub_Helper::set_clear_active_flag();
130
131 if (
132 ( ! $this->model->enabled && $old_enabled ) ||
133 ( ! $this->model->bot_trap_enabled && $old_bot_trap_enabled )
134 ) {
135 wd_di()->get( Bot_Trap::class )->remove_data();
136 } elseif (
137 ( $this->model->enabled && ! $old_enabled && $this->model->bot_trap_enabled ) ||
138 ( $this->model->bot_trap_enabled && ! $old_bot_trap_enabled && $this->model->enabled )
139 ) {
140 wd_di()->get( Bot_Trap::class )->rotate_hash();
141 }
142
143 // Maybe track.
144 if ( ! defender_is_wp_cli() ) {
145 if ( $this->is_feature_state_changed( $prev_data, $data ) ) {
146 $track_data = array(
147 'Action' => $data['enabled'] ? 'Enabled' : 'Disabled',
148 'No of Bots in the Whitelist' => count( $this->model->get_lockout_list( 'allowlist', false ) ),
149 'No of Bots in the Blocklist' => count( $this->model->get_lockout_list( 'blocklist', false ) ),
150 );
151 $this->track_feature( 'def_user_agent_banning', $track_data );
152 }
153 // New one for 'Blocklist Presets'.
154 if (
155 ( $prev_data['blocklist_presets'] !== $data['blocklist_presets'] ) ||
156 ( count( $prev_data['blocklist_preset_values'] ) !==
157 count( array_intersect( $prev_data['blocklist_preset_values'], $data['blocklist_preset_values'] ) )
158 )
159 ) {
160 $track_data = array(
161 'Action' => $data['blocklist_presets'] ? 'Enabled' : 'Disabled',
162 'List of Activated Bots' => implode( ', ', $data['blocklist_preset_values'] ),
163 );
164 $this->track_feature( 'def_ua_blocklist_preset', $track_data );
165 }
166 // New one for 'Script Presets'.
167 if (
168 ( $prev_data['script_presets'] !== $data['script_presets'] ) ||
169 ( count( $prev_data['script_preset_values'] ) !==
170 count( array_intersect( $prev_data['script_preset_values'], $data['script_preset_values'] ) )
171 )
172 ) {
173 $track_data = array(
174 'Action' => $data['script_presets'] ? 'Enabled' : 'Disabled',
175 'List of Activated Scripts' => implode( ', ', $data['script_preset_values'] ),
176 );
177 $this->track_feature( 'def_ua_scripts_preset', $track_data );
178 }
179 }
180
181 return new Response(
182 true,
183 array_merge(
184 array(
185 'message' => $this->get_update_message(
186 $data,
187 $old_enabled,
188 User_Agent_Lockout::get_module_name()
189 ),
190 'auto_close' => true,
191 ),
192 $this->data_frontend()
193 )
194 );
195 }
196
197 return new Response(
198 false,
199 array( 'message' => $this->model->get_formatted_errors() )
200 );
201 }
202
203 /**
204 * Removes settings for all submodules.
205 */
206 public function remove_settings() {
207 }
208
209 /**
210 * Delete all the data & the cache.
211 */
212 public function remove_data() {
213 }
214
215 /**
216 * Converts the current object state to an array.
217 *
218 * @return array The array representation of the object.
219 */
220 public function to_array(): array {
221 return array();
222 }
223
224 /**
225 * Provides data for the frontend.
226 *
227 * @return array An array of data for the frontend.
228 */
229 public function data_frontend(): array {
230 $arr_model = $this->model->export();
231
232 return array_merge(
233 array(
234 'model' => $arr_model,
235 'misc' => array(
236 'no_ua' => '' === $arr_model['blacklist'] && '' === $arr_model['whitelist'],
237 'module_name' => User_Agent_Lockout::get_module_name(),
238 'blocklist_presets' => User_Agent_Service::get_blocklist_presets(),
239 'script_presets' => User_Agent_Service::get_script_presets(),
240 ),
241 ),
242 $this->dump_routes_and_nonces()
243 );
244 }
245
246 /**
247 * Adapt the given data array by adding additional fields if necessary.
248 *
249 * @param array $data The data array to adapt.
250 *
251 * @return array The adapted data array.
252 */
253 private function adapt_data( array $data ): array {
254 $adapted_data = array();
255 if ( isset( $data['ua_banning_enabled'] ) ) {
256 $adapted_data['enabled'] = (bool) $data['ua_banning_enabled'];
257 }
258 if ( isset( $data['ua_banning_message'] ) ) {
259 $adapted_data['message'] = $data['ua_banning_message'];
260 }
261 if ( isset( $data['ua_banning_blacklist'] ) ) {
262 $adapted_data['blacklist'] = $data['ua_banning_blacklist'];
263 }
264 if ( isset( $data['ua_banning_whitelist'] ) ) {
265 $adapted_data['whitelist'] = $data['ua_banning_whitelist'];
266 }
267 if ( isset( $data['ua_banning_empty_headers'] ) ) {
268 $adapted_data['empty_headers'] = (bool) $data['ua_banning_empty_headers'];
269 }
270
271 return array_merge( $data, $adapted_data );
272 }
273
274 /**
275 * Imports data into the model.
276 *
277 * @param array $data Data to be imported into the model.
278 *
279 * @throws Exception If table is not defined.
280 */
281 public function import_data( array $data ): void {
282 $model = $this->get_model();
283 if ( ! empty( $data ) ) {
284 $data = $this->adapt_data( $data );
285 $model->import( $data );
286 if ( $model->validate() ) {
287 $model->save();
288 }
289 } else {
290 $default_ua_values = $model->get_default_values();
291 $model->enabled = false;
292 $model->message = $default_ua_values['message'];
293 $model->blacklist = $default_ua_values['blacklist'];
294 $model->whitelist = $default_ua_values['whitelist'];
295 $model->empty_headers = false;
296 $model->save();
297 }
298 }
299
300 /**
301 * Exports User Agents to a CSV file.
302 *
303 * @return void
304 * @defender_route
305 * @since 2.6.0
306 */
307 public function export_ua(): void {
308 $data = array();
309
310 foreach ( $this->model->get_lockout_list( 'blocklist', false ) as $ua ) {
311 $data[] = array(
312 'ua' => $ua,
313 'type' => 'blocklist',
314 );
315 }
316 foreach ( $this->model->get_lockout_list( 'allowlist', false ) as $ua ) {
317 $data[] = array(
318 'ua' => $ua,
319 'type' => 'allowlist',
320 );
321 }
322 // WP_Filesystem class doesn’t directly provide a function for opening a stream to php://memory with the 'w' mode.
323 $fp = fopen( 'php://memory', 'w' ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen
324 foreach ( $data as $fields ) {
325 fputcsv( $fp, $fields, ',', '"', '\\' );
326 }
327 $filename = 'wdf-ua-export-' . wp_date( 'ymdHis' ) . '.csv';
328 fseek( $fp, 0 );
329 header( 'Content-Type: text/csv' );
330 header( 'Content-Disposition: attachment; filename="' . $filename . '";' );
331 // Make php send the generated csv lines to the browser.
332 fpassthru( $fp );
333 exit();
334 }
335
336 /**
337 * Importing UAs from exporter.
338 *
339 * @param Request $request The request object.
340 *
341 * @defender_route
342 * @return Response
343 */
344 public function import_ua( Request $request ) {
345 $data = $request->get_data(
346 array(
347 'id' => array(
348 'type' => 'int',
349 ),
350 )
351 );
352
353 $attached_id = $data['id'];
354 if ( ! is_object( get_post( $attached_id ) ) ) {
355 return new Response(
356 false,
357 array( 'message' => esc_html__( 'Your file is invalid!', 'defender-security' ) )
358 );
359 }
360
361 $file = get_attached_file( $attached_id );
362 if ( ! is_file( $file ) ) {
363 return new Response(
364 false,
365 array( 'message' => esc_html__( 'Your file is invalid!', 'defender-security' ) )
366 );
367 }
368
369 $data = $this->service->verify_import_file( $file );
370 if ( ! $data ) {
371 return new Response(
372 false,
373 array(
374 'message' => esc_html__( 'Your file content is invalid! Please use a CSV file format and try again.', 'defender-security' ),
375 )
376 );
377 }
378
379 // All good, start to import.
380 foreach ( $data as $line ) {
381 $this->model->add_to_list( $line[0], $line[1] );
382 }
383
384 return new Response(
385 true,
386 array(
387 'message' => esc_html__( 'Your blocklist and allowlist have been successfully imported.', 'defender-security' ),
388 'interval' => 1,
389 )
390 );
391 }
392
393 /**
394 * Exports strings.
395 *
396 * @return array An array of strings.
397 */
398 public function export_strings(): array {
399 return array();
400 }
401 }
402