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
← All changes | src/component/security-tweaks/class-security-key.php +266 -217 5.6.2 → 6.3.0 View file →
@@ -7,16 +7,16 @@
7 7
8 8 namespace WP_Defender\Component\Security_Tweaks;
9 9
10 10 use WP_Error;
11 -use Exception;
12 11 use Throwable;
13 12 use Calotes\Component\Response;
13 +use WP_Defender\Traits\File_Operations;
14 14 use WP_Defender\Model\Setting\Mask_Login;
15 15 use WP_Defender\Model\Setting\Security_Tweaks;
16 16 use WP_Defender\Traits\Security_Tweaks_Option;
17 +use WP_Defender\Component\Network_Cron_Manager;
17 18 use WP_Defender\Component\Security_Tweak;
18 -use WP_Filesystem_Base;
19 19
20 20 /**
21 21 * Class Security_Key
22 22 */
@@ -21,8 +21,9 @@
21 21 * Class Security_Key
22 22 */
23 23 class Security_Key extends Abstract_Security_Tweaks implements Security_Key_Const_Interface {
24 24
25 + use File_Operations;
25 26 use Security_Tweaks_Option;
26 27
27 28 public const REGENERATE_SALT_NEXT_RUN_OPTION = 'wpdef_regereate_salt_next_run';
28 29
@@ -36,33 +37,33 @@
36 37 * Default reminder duration for regenerating security keys.
37 38 *
38 39 * @var string
39 40 */
40 - public $default_days = '60 days';
41 + public string $default_days = '60 days';
41 42 /**
42 43 * Duration after which a reminder for regeneration is triggered.
43 44 *
44 - * @var null
45 + * @var string
45 46 */
46 - public $reminder_duration = null;
47 + public string $reminder_duration = '60 days';
47 48 /**
48 49 * Date of the last reminder.
49 50 *
50 - * @var null
51 + * @var int
51 52 */
52 - public $reminder_date = null;
53 + public int $reminder_date = 0;
53 54 /**
54 55 * Timestamp of the last modification of security keys.
55 56 *
56 - * @var null
57 + * @var int
57 58 */
58 - public $last_modified = null;
59 + public int $last_modified = 0;
59 60 /**
60 61 * Path to the wp-config.php file.
61 62 *
62 63 * @var string
63 64 */
64 - public $file;
65 + public string $file;
65 66
66 67 /**
67 68 * Flag to automate the security key/salt generation.
68 69 *
@@ -67,16 +68,22 @@
67 68 * Flag to automate the security key/salt generation.
68 69 *
69 70 * @var bool
70 71 */
71 - private $is_autogenerate_keys = true;
72 + private bool $is_autogenerate_keys = true;
72 73
73 74 /**
75 + * Flag set when the user explicitly reverts this tweak via the Edit button.
76 + *
77 + * @var bool
78 + */
79 + private bool $is_reverted = false;
80 +
81 + /**
74 82 * Constructor for Security_Key.
75 83 */
76 84 public function __construct() {
77 85 $this->file = defender_wp_config_path();
78 - $this->add_hooks();
79 86 $this->get_options();
80 87 $this->cron_schedule();
81 88 }
82 89
@@ -85,8 +92,12 @@
85 92 *
86 93 * @return bool|void
87 94 */
88 95 public function check() {
96 + if ( $this->is_reverted ) {
97 + return false;
98 + }
99 +
89 100 if ( ! $this->is_salts_exist() ) {
90 101 return false;
91 102 }
92 103
@@ -92,9 +103,9 @@
92 103
93 104 if ( $this->last_modified ) {
94 105 $reminder_date = strtotime( '+' . $this->reminder_duration, $this->last_modified );
95 106
96 - return $reminder_date > time();
107 + return $reminder_date > defender_get_current_time();
97 108 }
98 109 }
99 110
100 111 /**
@@ -103,22 +114,21 @@
103 114 * @return void
104 115 */
105 116 private function get_options(): void {
106 117 $options = get_site_option( 'defender_security_tweaks_' . $this->slug );
107 - $this->reminder_date = ! empty( $options['reminder_date'] ) ? $options['reminder_date'] : null;
108 - $this->reminder_duration = ! empty( $options['reminder_duration'] ) ? $options['reminder_duration'] : $this->default_days;
118 + $this->reminder_date = $options['reminder_date'] ?? 0;
119 + $this->reminder_duration = isset( $options['reminder_duration'] ) && '' !== $options['reminder_duration'] ? $options['reminder_duration'] : $this->default_days;
120 + $this->is_reverted = isset( $options['is_reverted'] ) && true === (bool) $options['is_reverted'];
109 121
110 122 $last_modified = $this->get_wp_config_last_modified_time();
111 123 if ( false === $last_modified ) {
112 124 $last_modified = $options['last_modified'] ?? null;
113 - } elseif ( ! empty( $options['last_modified'] ) && $options['last_modified'] < $last_modified ) {
125 + } elseif ( isset( $options['last_modified'] ) && is_int( $options['last_modified'] ) && 0 < $options['last_modified'] && $options['last_modified'] < $last_modified ) {
114 126 $last_modified = $options['last_modified'];
115 127 }
116 128 $this->last_modified = $last_modified;
117 129
118 - $this->is_autogenerate_keys = ! empty( $options['is_autogenerate_keys'] )
119 - ? (bool) $options['is_autogenerate_keys']
120 - : true;
130 + $this->is_autogenerate_keys = isset( $options['is_autogenerate_keys'] );
121 131 }
122 132
123 133 /**
124 134 * Here is the code for processing. If the return is true or Response, we add it to resolve list. WP_Error if any error.
@@ -125,67 +135,15 @@
125 135 *
126 136 * @return bool|WP_Error|Response
127 137 */
128 138 public function process() {
129 - global $wp_filesystem;
130 - // Initialize the WP filesystem, no more using 'file-put-contents' function.
131 - if ( ! $wp_filesystem instanceof WP_Filesystem_Base ) {
132 - require_once ABSPATH . '/wp-admin/includes/file.php';
133 - WP_Filesystem();
134 - }
135 - if ( ! $wp_filesystem->is_writable( $this->file ) ) {
136 - return new WP_Error(
137 - 'defender_file_not_writable',
138 - /* translators: %s: file path */
139 - sprintf( esc_html__( 'The file %s is not writable', 'defender-security' ), $this->file )
140 - );
141 - }
139 + $is_done = $this->update_keys();
142 140
143 - $constants = $this->get_constants();
144 - $salts = $this->get_salts();
145 -
146 - if ( is_wp_error( $salts ) ) {
147 - return $salts;
141 + if ( is_wp_error( $is_done ) ) {
142 + return $is_done;
148 143 }
149 144
150 - $contents = $wp_filesystem->get_contents( $this->file );
151 - $new_salts = '';
152 -
153 - foreach ( $constants as $key => $const ) {
154 - if ( defined( $const ) ) {
155 - $pattern = "/^define\(\s*['|\"]{$const}['|\"],(.*)\)\s*;/m";
156 - $replacement = $salts[ $key ];
157 - $contents = preg_replace_callback(
158 - $pattern,
159 - function () use ( $replacement ) {
160 - return $replacement;
161 - },
162 - $contents
163 - );
164 - } else {
165 - $new_salts .= $salts[ $key ] . PHP_EOL;
166 - }
167 - }
168 -
169 - if ( ! empty( $new_salts ) ) {
170 - $new_salts = PHP_EOL .
171 - '/* DEFENDER GENERATED SALTS */' .
172 - PHP_EOL .
173 - $new_salts .
174 - PHP_EOL;
175 -
176 - $contents = $this->append_salts( $contents, $new_salts );
177 - }
178 -
179 - $is_done = (bool) file_put_contents( $this->file, $contents, LOCK_EX ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents
180 -
181 145 if ( $is_done ) {
182 - $values = get_site_option( 'defender_security_tweaks_' . $this->slug, array() );
183 - $this->last_modified = time();
184 - $values['last_modified'] = time();
185 - update_site_option( 'defender_security_tweaks_' . $this->slug, $values );
186 - $this->log( 'Security keys are updated.', Security_Tweak::LOG_FILE_NAME );
187 -
188 146 $mask_login = wd_di()->get( Mask_Login::class );
189 147 $url = $mask_login->is_active()
190 148 ? $mask_login->get_new_login_url()
191 149 : wp_login_url( network_admin_url( 'admin.php?page=wdf-hardener' ) );
@@ -190,12 +148,8 @@
190 148 ? $mask_login->get_new_login_url()
191 149 : wp_login_url( network_admin_url( 'admin.php?page=wdf-hardener' ) );
192 150
193 151 $interval = 3;
194 - if ( is_multisite() ) {
195 - // Delete cookies forced.
196 - wp_clear_auth_cookie();
197 - }
198 152
199 153 return new Response(
200 154 true,
201 155 array(
@@ -223,8 +177,13 @@
223 177 *
224 178 * @return bool
225 179 */
226 180 public function revert(): bool {
181 + $values = get_site_option( 'defender_security_tweaks_' . $this->slug, array() );
182 + $values['is_reverted'] = true;
183 + update_site_option( 'defender_security_tweaks_' . $this->slug, $values );
184 + $this->is_reverted = true;
185 +
227 186 return true;
228 187 }
229 188
230 189 /**
@@ -236,23 +195,83 @@
236 195 return true;
237 196 }
238 197
239 198 /**
199 + * Parse and validate salt definitions string.
200 + *
201 + * @param string $body API response body.
202 + * @param array $constants Expected constant keys.
203 + *
204 + * @return array|false
205 + */
206 + private function parse_and_validate_salts( string $body, array $constants ): array|false {
207 + $raw_salts = explode( "\n", $body );
208 + $salts = array();
209 + $values = array();
210 + $index = 0;
211 +
212 + foreach ( $raw_salts as $salt ) {
213 + $salt = trim( $salt );
214 + if ( '' === $salt ) {
215 + continue;
216 + }
217 +
218 + $salt = stripslashes( $salt );
219 + $expected_const = $constants[ $index ] ?? null;
220 +
221 + if (
222 + null === $expected_const ||
223 + 0 !== strpos( $salt, 'define(' ) ||
224 + 1 !== preg_match( '/^define\(\s*[\'"]' . preg_quote( $expected_const, '/' ) . '[\'"]\s*,\s*[\'"](.*?)[\'"]\s*\)\s*;$/s', $salt, $matches )
225 + ) {
226 + return false;
227 + }
228 +
229 + $val = $matches[1];
230 + if ( 'put your unique phrase here' === $val || strlen( $val ) < 64 || isset( $values[ $val ] ) ) {
231 + return false;
232 + }
233 +
234 + $values[ $val ] = true;
235 + $salts[] = $salt;
236 + ++$index;
237 + }
238 +
239 + if ( count( $salts ) !== count( $constants ) ) {
240 + return false;
241 + }
242 +
243 + return $salts;
244 + }
245 +
246 + /**
240 247 * Get salts to be placed in wp-config.php.
241 248 *
242 249 * @return array|WP_Error
243 250 */
244 - private function get_salts() {
245 - $response = wp_safe_remote_get( 'https://api.wordpress.org/secret-key/1.1/salt/' );
251 + private function get_salts(): WP_Error|array {
252 + $constants = $this->get_constants();
253 + $max_attempts = 3;
254 + $attempt = 0;
246 255
247 - if ( is_wp_error( $response ) ) {
248 - return new WP_Error(
249 - 'defender_salts_not_found',
250 - esc_html__( 'Unable to generate salts. Please try again.', 'defender-security' )
251 - );
256 + while ( $attempt < $max_attempts ) {
257 + ++$attempt;
258 + $response = wp_safe_remote_get( 'https://api.wordpress.org/secret-key/1.1/salt/' );
259 +
260 + if ( is_wp_error( $response ) || 200 !== (int) wp_remote_retrieve_response_code( $response ) ) {
261 + continue;
262 + }
263 +
264 + $salts = $this->parse_and_validate_salts( wp_remote_retrieve_body( $response ), $constants );
265 + if ( false !== $salts ) {
266 + return $salts;
267 + }
252 268 }
253 269
254 - return array_filter( explode( "\n", wp_remote_retrieve_body( $response ) ) );
270 + return new WP_Error(
271 + 'defender_salts_not_found',
272 + esc_html__( 'Unable to generate salts. Please try again.', 'defender-security' )
273 + );
255 274 }
256 275
257 276 /**
258 277 * Get how long the wp-config file is last updated.
@@ -258,14 +277,12 @@
258 277 * Get how long the wp-config file is last updated.
259 278 *
260 279 * @return int|string
261 280 */
262 - private function get_last_modified_days() {
263 - $current_time = time();
281 + private function get_last_modified_days(): int|string {
282 + $current_time = defender_get_current_time();
264 283 $days_ago = ( $current_time - $this->last_modified ) / DAY_IN_SECONDS;
265 - $days_ago = $days_ago ? (int) round( $days_ago ) : 'unknown';
266 -
267 - return $days_ago ?? 1;
284 + return $days_ago ? (int) round( $days_ago ) : 'unknown';
268 285 }
269 286
270 287 /**
271 288 * Get all the constants.
@@ -290,9 +307,9 @@
290 307 *
291 308 * @return string
292 309 */
293 310 public function get_label(): string {
294 - return esc_html__( 'Update old security keys', 'defender-security' );
311 + return esc_html__( 'Update security keys', 'defender-security' );
295 312 }
296 313
297 314 /**
298 315 * Get the error reason.
@@ -299,30 +316,40 @@
299 316 *
300 317 * @return string
301 318 */
302 319 public function get_error_reason(): string {
303 - $get_last_modified_days = $this->get_last_modified_days();
304 -
305 - if ( 'unknown' === $get_last_modified_days ) {
306 - $error_message = esc_html__(
307 - 'We can\'t tell how old your security keys are, perhaps it\'s time to update them?',
308 - 'defender-security'
320 + if ( ! $this->is_salts_exist() ) {
321 + return wp_kses(
322 + __( '<strong>Your security keys are outdated</strong> <br /> and one or more security salts are missing in wp-config.php.', 'defender-security' ),
323 + array(
324 + 'strong' => array(),
325 + 'br' => array(),
326 + )
309 327 );
310 328 }
311 - if ( ! $this->is_salts_exist() ) {
312 - $error_message = esc_html__(
313 - 'One or more security salts aren\'t defined in wp-config.php. Time to regenerate them!',
314 - 'defender-security'
329 +
330 + $days = $this->get_last_modified_days();
331 + if ( 'unknown' === $days ) {
332 + return wp_kses(
333 + __( '<strong>Your security keys are outdated</strong> <br /> and we can\'t tell how old they are.', 'defender-security' ),
334 + array(
335 + 'strong' => array(),
336 + 'br' => array(),
337 + )
315 338 );
316 - } else {
317 - $error_message = sprintf(
318 - /* translators: %s: number of days */
319 - esc_html__( 'Your current security keys are %s days old. Time to update them!', 'defender-security' ),
320 - $get_last_modified_days
321 - );
322 339 }
323 340
324 - return $error_message;
341 + return wp_kses(
342 + sprintf(
343 + /* translators: %1$s: opening strong tag, %2$s: closing strong tag */
344 + __( '%1$sYour security keys are outdated.%2$s', 'defender-security' ),
345 + '<strong>',
346 + '</strong>'
347 + ),
348 + array(
349 + 'strong' => array(),
350 + )
351 + );
325 352 }
326 353
327 354 /**
328 355 * Return a summary data of this tweak.
@@ -329,25 +356,36 @@
329 356 *
330 357 * @return array
331 358 */
332 359 public function to_array(): array {
360 + $auto_regenerate = $this->get_is_autogenerate_keys();
361 + $auto_status = $auto_regenerate
362 + ? __( 'Auto-regenerate is turned on.', 'defender-security' )
363 + : __( 'Auto-regenerate is turned off.', 'defender-security' );
364 + $saved_duration = $this->get_option( 'reminder_duration' );
365 + $reminder_duration = ( is_string( $saved_duration ) && '' !== $saved_duration )
366 + ? $saved_duration
367 + : $this->default_days;
368 +
333 369 return array(
334 370 'slug' => $this->slug,
335 371 'title' => $this->get_label(),
336 372 'errorReason' => $this->get_error_reason(),
337 - 'successReason' => sprintf(
338 - /* translators: %s: number of days */
339 - esc_html__( 'Your security keys are less than %s days old, nice work.', 'defender-security' ),
340 - $this->get_last_modified_days()
373 + 'successReason' => wp_strip_all_tags(
374 + sprintf(
375 + /* translators: %1$s: reminder duration, %2$s: auto-regenerate status */
376 + __( 'Security keys regenerate every %1$s. %2$s', 'defender-security' ),
377 + $reminder_duration,
378 + $auto_status
379 + )
341 380 ),
342 381 'misc' => array(
343 - 'reminder' => $this->reminder_duration,
382 + 'show_revert_button' => true,
383 + 'reminder' => $reminder_duration,
384 + 'show_action_button' => true,
344 385 ),
345 - 'bulk_description' => esc_html__(
346 - 'Your current security keys are unknown days old. Time to update them! We will update the frequency to 60 days.',
347 - 'defender-security'
348 - ),
349 - 'bulk_title' => esc_html__( 'Security Keys', 'defender-security' ),
386 + 'bulk_description' => wp_strip_all_tags( __( 'WordPress security keys help protect user sessions and cookies. Update security keys with strong, unique values to improve encryption and reduce the risk of unauthorized access.', 'defender-security' ) ),
387 + 'bulk_title' => wp_strip_all_tags( __( 'Security Keys', 'defender-security' ) ),
350 388 );
351 389 }
352 390
353 391 /**
@@ -357,11 +395,9 @@
357 395 */
358 396 public function get_is_autogenerate_keys(): bool {
359 397 $is_autogenerate_keys = $this->get_option( 'is_autogenerate_keys' );
360 398
361 - $this->is_autogenerate_keys = is_null( $is_autogenerate_keys )
362 - ? true
363 - : (bool) $is_autogenerate_keys;
399 + $this->is_autogenerate_keys = is_null( $is_autogenerate_keys ) || (bool) $is_autogenerate_keys;
364 400
365 401 return $this->is_autogenerate_keys;
366 402 }
367 403
@@ -372,9 +408,9 @@
372 408 *
373 409 * @return bool Return true if value updated, otherwise false.
374 410 */
375 411 public function set_is_autogenrate_keys( bool $value ): bool {
376 - $this->is_autogenerate_keys = (bool) $value;
412 + $this->is_autogenerate_keys = $value;
377 413
378 414 return $this->update_option(
379 415 'is_autogenerate_keys',
380 416 $this->is_autogenerate_keys
@@ -381,43 +417,8 @@
381 417 );
382 418 }
383 419
384 420 /**
385 - * Method to initialize component hooks.
386 - *
387 - * @return void
388 - */
389 - public function add_hooks(): void {
390 - add_action( 'wpdef_sec_key_gen', array( $this, 'cron_process' ) );
391 - }
392 -
393 - /**
394 - * Get cron schedule details.
395 - *
396 - * @return array
397 - */
398 - private function get_cron_schedule_details(): array {
399 - $display_name = $this->get_option( 'reminder_duration' );
400 -
401 - if ( empty( $display_name ) ) {
402 - $display_name = $this->default_days;
403 - }
404 - $schedule_detail = $this->get_schedule_detail( $display_name );
405 -
406 - $schedule_key = key( $schedule_detail );
407 - $schedule_interval = time();
408 -
409 - if ( null !== $schedule_key && isset( $schedule_detail[ $schedule_key ]['interval'] ) ) {
410 - $schedule_interval = $schedule_interval + $schedule_detail[ $schedule_key ]['interval'];
411 - }
412 -
413 - return array(
414 - 'key' => $schedule_key,
415 - 'interval' => $schedule_interval,
416 - );
417 - }
418 -
419 - /**
420 421 * Cron schedule.
421 422 *
422 423 * @return void
423 424 */
@@ -422,18 +423,33 @@
422 423 * @return void
423 424 */
424 425 public function cron_schedule(): void {
425 426 if (
426 - true === $this->get_is_autogenerate_keys() &&
427 - ! wp_next_scheduled( 'wpdef_sec_key_gen' )
427 + true === $this->get_is_autogenerate_keys()
428 428 ) {
429 - $schedule_details = $this->get_cron_schedule_details();
429 + $display_name = $this->get_option( 'reminder_duration' );
430 430
431 - wp_schedule_event(
432 - $schedule_details['interval'],
433 - $schedule_details['key'],
434 - 'wpdef_sec_key_gen'
431 + if ( ! is_string( $display_name ) || '' === $display_name ) {
432 + $display_name = $this->default_days;
433 + }
434 +
435 + /**
436 + * Network Cron Manager instance.
437 + *
438 + * @var Network_Cron_Manager $network_cron_manager
439 + */
440 + $network_cron_manager = wd_di()->get( Network_Cron_Manager::class );
441 +
442 + $interval_seconds = strtotime( $display_name, 0 );
443 + $start_time = defender_get_current_time() + $interval_seconds;
444 +
445 + $network_cron_manager->register_callback(
446 + 'wpdef_sec_key_gen',
447 + array( $this, 'cron_process' ),
448 + $interval_seconds,
449 + $start_time,
435 450 );
451 +
436 452 }
437 453 }
438 454
439 455 /**
@@ -441,13 +457,15 @@
441 457 *
442 458 * @return void
443 459 */
444 460 public function cron_unschedule(): void {
445 - $timestamp = wp_next_scheduled( 'wpdef_sec_key_gen' );
446 -
447 - if ( $timestamp ) {
448 - wp_unschedule_event( $timestamp, 'wpdef_sec_key_gen' );
449 - }
461 + /**
462 + * Network Cron Manager instance.
463 + *
464 + * @var Network_Cron_Manager $network_cron_manager
465 + */
466 + $network_cron_manager = wd_di()->get( Network_Cron_Manager::class );
467 + $network_cron_manager->remove_callback( 'wpdef_sec_key_gen' );
450 468 }
451 469
452 470 /**
453 471 * Cron schedule security key/salt generation process.
@@ -455,62 +473,20 @@
455 473 * @return void
456 474 */
457 475 public function cron_process(): void {
458 476 try {
459 - if ( is_multisite() ) {
460 - $next_run = get_site_option( self::REGENERATE_SALT_NEXT_RUN_OPTION, 0 );
461 - if ( ! empty( $next_run ) && $next_run > time() ) {
462 - return;
463 - }
464 -
465 - $schedule_details = $this->get_cron_schedule_details();
466 - update_site_option( self::REGENERATE_SALT_NEXT_RUN_OPTION, $schedule_details['interval'] );
467 - }
468 -
469 - $is_switch_to_main_site = is_multisite() && ! is_main_site();
470 - if ( $is_switch_to_main_site ) {
471 - switch_to_blog( get_main_site_id() );
472 - }
473 -
474 477 $security_tweak_model = new Security_Tweaks();
475 478 if ( ! $security_tweak_model->is_tweak_ignore( $this->slug ) &&
476 479 true === $this->get_is_autogenerate_keys()
477 480 ) {
478 - $this->process();
481 + $this->update_keys();
479 482 }
480 -
481 - if ( $is_switch_to_main_site ) {
482 - restore_current_blog();
483 - }
484 483 } catch ( Throwable $th ) {
485 - // PHP 7+ catch.
486 484 $this->log( 'Security Key. Cron Throwable: ' . get_class( $th ) . ': ' . $th->getMessage(), Security_Tweak::LOG_FILE_NAME );
487 - } catch ( Exception $e ) {
488 - $this->log( 'Security Key. Cron Exception: ' . get_class( $e ) . ': ' . $e->getMessage(), Security_Tweak::LOG_FILE_NAME );
489 485 }
490 486 }
491 487
492 488 /**
493 - * Get scheduler detail.
494 - *
495 - * @param string $display_name The name to filter the schedules list.
496 - *
497 - * @return array Return filtered schedule detail.
498 - */
499 - private function get_schedule_detail( $display_name ) {
500 - $all_schedules = wp_get_schedules();
501 -
502 - $schedule_detail = array_filter(
503 - $all_schedules,
504 - function ( $arr ) use ( $display_name ) {
505 - return $display_name === $arr['display'];
506 - }
507 - );
508 -
509 - return $schedule_detail;
510 - }
511 -
512 - /**
513 489 * Check all salts are exists.
514 490 *
515 491 * @return bool Return true if all constants have salt else false.
516 492 */
@@ -526,14 +502,14 @@
526 502
527 503 /**
528 504 * Append salt to wp-config.php file content string.
529 505 *
530 - * @param string|false $contents Text content of wp-config.php.
531 - * @param string $new_salts Salts need to be appended.
506 + * @param bool|string $contents Text content of wp-config.php.
507 + * @param string $new_salts Salts need to be appended.
532 508 *
533 509 * @return string Text content after salts appended.
534 510 */
535 - private function append_salts( $contents, string $new_salts ): string {
511 + private function append_salts( bool|string $contents, string $new_salts ): string {
536 512 $pattern = '/(define\(\s*\'AUTH_KEY\'.*?\);)\s*' .
537 513 '(define\(\s*\'SECURE_AUTH_KEY\'.*?\);)\s*' .
538 514 '(define\(\s*\'LOGGED_IN_KEY\'.*?\);)\s*' .
539 515 '(define\(\s*\'NONCE_KEY\'.*?\);)\s*' .
@@ -569,9 +545,9 @@
569 545 *
570 546 * @return int|false
571 547 * @since 3.10.0
572 548 */
573 - private function get_wp_config_last_modified_time() {
549 + private function get_wp_config_last_modified_time(): bool|int {
574 550 $file = $this->file;
575 551 if ( ! file_exists( $file ) ) {
576 552 $file = ABSPATH . WPINC . '/general-template.php';
577 553 }
@@ -576,6 +552,79 @@
576 552 $file = ABSPATH . WPINC . '/general-template.php';
577 553 }
578 554
579 555 return filemtime( $file );
556 + }
557 +
558 + /**
559 + * Update keys.
560 + *
561 + * @return WP_Error|bool|array True if success. WP_Error if any error.
562 + */
563 + private function update_keys(): WP_Error|bool|array {
564 + $wp_filesystem = $this->get_wp_filesystem();
565 +
566 + if ( ! $wp_filesystem->is_writable( $this->file ) ) {
567 + return new WP_Error(
568 + 'defender_file_not_writable',
569 + /* translators: %s: file path */
570 + sprintf( esc_html__( 'The file %s is not writable', 'defender-security' ), $this->file )
571 + );
572 + }
573 +
574 + $constants = $this->get_constants();
575 + $salts = $this->get_salts();
576 +
577 + if ( is_wp_error( $salts ) ) {
578 + return $salts;
579 + }
580 +
581 + $contents = $wp_filesystem->get_contents( $this->file );
582 +
583 + $new_salts = '';
584 +
585 + foreach ( $constants as $key => $const ) {
586 + if ( defined( $const ) ) {
587 + $pattern = "/^define\(\s*['|\"]{$const}['|\"],(.*)\)\s*;/m";
588 + $replacement = $salts[ $key ];
589 + $contents = preg_replace_callback(
590 + $pattern,
591 + function () use ( $replacement ) {
592 + return $replacement;
593 + },
594 + $contents
595 + );
596 + } else {
597 + $new_salts .= $salts[ $key ] . PHP_EOL;
598 + }
599 + }
600 +
601 + if ( '' !== $new_salts ) {
602 + $new_salts = PHP_EOL .
603 + '/* DEFENDER GENERATED SALTS */' .
604 + PHP_EOL .
605 + $new_salts .
606 + PHP_EOL;
607 +
608 + $contents = $this->append_salts( $contents, $new_salts );
609 + }
610 +
611 + $is_done = $wp_filesystem->put_contents( $this->file, $contents, FS_CHMOD_FILE );
612 +
613 + if ( ! $is_done ) {
614 + return false;
615 + }
616 + $values = get_site_option( 'defender_security_tweaks_' . $this->slug, array() );
617 + $this->last_modified = defender_get_current_time();
618 + $values['last_modified'] = defender_get_current_time();
619 + unset( $values['is_reverted'] );
620 + $this->is_reverted = false;
621 + update_site_option( 'defender_security_tweaks_' . $this->slug, $values );
622 + $this->log( 'Security keys are updated.', Security_Tweak::LOG_FILE_NAME );
623 + if ( is_multisite() && ! headers_sent() ) {
624 + // Delete cookies forced.
625 + wp_clear_auth_cookie();
626 + }
627 +
628 + return true;
580 629 }
581 630 }