PluginProbe
MainWP Dashboard: Self-hosted WordPress Management for Agencies / 6.1.4
MainWP Dashboard: Self-hosted WordPress Management for Agencies v6.1.4
6.2 6.1.8 6.1.7 6.1.6 6.1.5 6.1.4 6.1.3 6.1.2 6.1.1 6.1 6.0.12 6.0.11 4.6.0.1 5.0 5.0.1 5.0.2 5.0.3 5.0.3.1 5.0.3.2 5.1 5.1.1 5.2 5.2.1 5.2.2 5.3 All 153 releases
mainwp / class / class-mainwp-keys-manager.php

class-mainwp-keys-manager.php in MainWP Dashboard: Self-hosted WordPress Management for Agencies 6.1.4, at class/class-mainwp-keys-manager.php

742 lines 27.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 *
4 * Encrypts & Decrypts API Keys.
5 *
6 * @package MainWP/MainWP_Keys_Manager
7 */
8
9 namespace MainWP\Dashboard;
10
11 use phpseclib3\Crypt\AES;
12 use phpseclib3\Crypt\Random;
13
14 // Exit if accessed directly.
15 if ( ! defined( 'ABSPATH' ) ) {
16 exit;
17 }
18
19 /**
20 * Class MainWP_Keys_Manager
21 *
22 * @package MainWP/MainWP_Keys_Manager
23 */
24 class MainWP_Keys_Manager { // phpcs:ignore Generic.Classes.OpeningBraceSameLine.ContentAfterBrace -- NOSONAR.
25
26 /**
27 * Private static variable to hold the single instance of the class.
28 *
29 * @static
30 *
31 * @var mixed Default null
32 */
33 private static $instance = null;
34
35 /**
36 * Method instance()
37 *
38 * Create a public static instance.
39 *
40 * @static
41 * @return Instance class.
42 */
43 public static function instance() {
44 if ( null === static::$instance ) {
45 static::$instance = new self();
46 }
47 static::auto_load_files(); // to fix.
48 return static::$instance;
49 }
50
51 /**
52 * Method get_class_name()
53 *
54 * Get Class Name.
55 *
56 * @return object Class name.
57 */
58 public static function get_class_name() {
59 return __CLASS__;
60 }
61
62 /**
63 * Method auto_load_files()
64 *
65 * Handle autoload files.
66 */
67 public static function auto_load_files() {
68 require_once MAINWP_PLUGIN_DIR . 'libs' . DIRECTORY_SEPARATOR . 'phpseclib' . DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . 'autoload.php'; // NOSONAR -- WP compatible.
69 }
70
71 /**
72 * Method get_keys_value()
73 *
74 * Get decrypt value.
75 *
76 * @param string $name Name of key.
77 * @param mixed $default_value Default value.
78 *
79 * @return string Decrypt value.
80 */
81 public function get_keys_value( $name, $default_value = false ) {
82 $opt = get_option( $name );
83 if ( ! empty( $opt ) && is_array( $opt ) && ! empty( $opt['file_key'] ) ) {
84 return $this->decrypt_keys_data( $opt, $default_value );
85 }
86 return $default_value;
87 }
88
89 /**
90 * Method update_key_value()
91 *
92 * Get decrypt value.
93 *
94 * @param mixed $option_name option name.
95 * @param mixed $value The option value.
96 * @param mixed $prefix The prefix value.
97 *
98 * @return string Decrypt value.
99 */
100 public function update_key_value( $option_name, $value = false, $prefix = 'dash_' ) {
101 static::init_keys_dir();
102
103 if ( false === $value || '' === $value ) {
104 $opt = get_option( $option_name );
105 if ( ! empty( $opt ) && is_array( $opt ) && ! empty( $opt['file_key'] ) ) {
106 $this->delete_key_file( $opt['file_key'] );
107 }
108 return delete_option( $option_name );
109 }
110
111 try {
112 $result = $this->encrypt_value( $value, $option_name, $prefix );
113 } catch ( \Exception $ex ) {
114 $err = $ex->getMessage();
115 if ( is_string( $err ) ) {
116 MainWP_Logger::instance()->debug( 'encrypt :: name[' . $option_name . '] :: error[' . $err . ']' );
117 }
118 return false;
119 }
120
121 if ( is_array( $result ) && ! empty( $result['encrypted_value'] ) ) {
122 $key = $result['key'];
123 $file = $result['file_key'];
124 $pw = $result['encrypted_value'];
125 if ( $this->save_key_file( $file, $key ) ) {
126 $update = array(
127 'encrypted_val' => $pw,
128 'file_key' => $file,
129 );
130 update_option( $option_name, $update );
131 return true;
132 }
133 }
134 return false;
135 }
136
137 /**
138 * Method delete_key_file()
139 *
140 * Delete key file.
141 *
142 * @param string $file_key Name of key file.
143 *
144 * @return string Deleted.
145 */
146 public function delete_key_file( $file_key ) {
147 $key_dir = static::get_keys_dir();
148 $file_path = $key_dir . $file_key;
149 MainWP_Utility::delete_file( $file_path ); // delete file content key.
150 return true;
151 }
152
153 /**
154 * Method get_decrypt_values()
155 *
156 * Get decrypt value.
157 *
158 * @param mixed $encodedValue Encoded The value to decrypt.
159 * @param mixed $key_file The value key.
160 * @param mixed $default_value The default value.
161 *
162 * @return string Decrypt value.
163 */
164 private function get_decrypt_values( $encodedValue, $key_file, $default_value = '' ) {
165 // find the key file, and get saved key.
166 $key = $this->get_key_val( $key_file );
167 if ( ! empty( $key ) ) {
168 return $this->decrypt_value( $encodedValue, $key );
169 }
170 return $default_value;
171 }
172
173 /**
174 * Method encrypt_value()
175 *
176 * Handle encrypt value.
177 *
178 * @param mixed $keypass The value to encrypt.
179 * @param string $name Option name of encrypted data.
180 * @param string $prefix using for prefix key file name.
181 *
182 * @return string Encrypted value.
183 */
184 private function encrypt_value( $keypass, $name, $prefix ) {
185
186 if ( '_' !== substr( $prefix, -1 ) ) {
187 $prefix .= '_';
188 }
189
190 $opt = get_option( $name );
191
192 if ( ! empty( $opt ) && is_array( $opt ) && ! empty( $opt['file_key'] ) ) {
193 $file_name = $opt['file_key'];
194 } else {
195 $file_name = $prefix . sha1( sha1( $prefix . $name . time() ) . 'key_files' ); // NOSONAR - safe for salt file name.
196 }
197
198 MainWP_Logger::instance()->debug( 'encrypt :: option name[' . $name . '] :: K file[' . $file_name . ']' );
199
200 $key = Random::string( 32 ); // supported key length: 16, 24, 32.
201
202 $encrypted = $this->encrypt_with_key( $keypass, $key );
203
204 return array(
205 'key' => $key,
206 'file_key' => $file_name,
207 'encrypted_value' => $encrypted,
208 );
209 }
210
211 /**
212 * Method decrypt_value()
213 *
214 * Handle decrypt value.
215 *
216 * @param mixed $encodedValue The value to decrypt.
217 * @param mixed $key Key to decrypt.
218 *
219 * @return string Decrypt value.
220 */
221 private function decrypt_value( $encodedValue, $key ) {
222 return $this->decrypt_with_key( $encodedValue, $key );
223 }
224
225 /**
226 * Method save_key_file()
227 *
228 * Handle save key passwd.
229 *
230 * @param mixed $key_file The value key.
231 * @param mixed $key_val The value.
232 *
233 * @return mixed Result.
234 */
235 public function save_key_file( $key_file, $key_val ) {
236 static::init_keys_dir();
237 $key_dir = static::get_keys_dir();
238 $file_path = $key_dir . $key_file;
239 $saved = file_put_contents( $file_path, $key_val ); //phpcs:ignore
240 return false === $saved ? false : true;
241 }
242
243 /**
244 * Method get_key_val()
245 *
246 * Get decrypt value.
247 *
248 * @param mixed $key_file The value key.
249 *
250 * @return string Decrypt value.
251 */
252 public function get_key_val( $key_file ) {
253 $key_dir = static::get_keys_dir();
254 $path = $key_dir . $key_file;
255 if ( file_exists( $path ) ) {
256 return file_get_contents( $path ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- private key files.
257 }
258 return '';
259 }
260
261
262 /**
263 * Method encrypt_with_key()
264 *
265 * Handle encrypt value.
266 *
267 * @param mixed $keypass The value to encrypt.
268 * @param mixed $key Key to encrypt.
269 *
270 * @return string Encrypted value.
271 */
272 private function encrypt_with_key( $keypass, $key ) {
273
274 // Generate a random IV (Initialization Vector).
275 $iv = Random::string( 16 );
276
277 // Create AES instance.
278 $aes = new AES( 'gcm' ); // MODE_GCM.
279 $aes->setKey( $key );
280
281 $aes->setNonce( $iv ); // Nonces are only used in GCM mode.
282 $aes->setAAD( 'authentication_data' );
283
284 // Encrypt the value.
285 $ciphertext = $aes->encrypt( $keypass );
286
287 // Get the authentication tag.
288 $tag = $aes->getTag();
289
290 // Combine IV, ciphertext, and tag.
291 $encryptedValue = $iv . $ciphertext . $tag;
292
293 // Encode the encrypted value using base64 for storage.
294 return base64_encode( $encryptedValue ); //phpcs:ignore
295 }
296
297 /**
298 * Method decrypt_with_key()
299 *
300 * Handle decrypt value.
301 *
302 * @param mixed $encodedValue The string to decrypt.
303 * @param mixed $key Key to decrypt.
304 *
305 * @return string Decrypt value.
306 */
307 private function decrypt_with_key( $encodedValue, $key ) {
308 if ( empty( $encodedValue ) ) {
309 return '';
310 }
311 try {
312 // Decode the base64 encoded value.
313 $encryptedValue = base64_decode( $encodedValue ); //phpcs:ignore
314
315 // Extract the IV, ciphertext, and tag.
316 $iv = substr( $encryptedValue, 0, 16 );
317 $ciphertext = substr( $encryptedValue, 16, -16 );
318 $tag = substr( $encryptedValue, -16 );
319
320 // Create AES instance.
321 $aes = new AES( 'gcm' ); // MODE_GCM.
322 $aes->setKey( $key );
323
324 $aes->setNonce( $iv ); // Nonces are only used in GCM mode.
325 $aes->setAAD( 'authentication_data' );
326
327 // Set the authentication tag.
328 $aes->setTag( $tag );
329
330 // Decrypt the value.
331 return $aes->decrypt( $ciphertext );
332 } catch ( \Exception $ex ) {
333 // error.
334 }
335 return '';
336 }
337
338 /**
339 * Method init_keys_dir()
340 *
341 * Check for keys directory and create it if it doesn't already exist,
342 * set the file permissions and update htaccess.
343 *
344 * @param mixed $keysDir Keys directory.
345 *
346 * @return void
347 */
348 public static function init_keys_dir( $keysDir = '' ) { //phpcs:ignore -- NOSONAR - complex.
349
350 if ( '' === $keysDir ) {
351 $keysDir = static::get_keys_dir();
352 }
353
354 if ( ! is_string( $keysDir ) || stristr( $keysDir, '..' ) ) {
355 return;
356 }
357
358 $hasWPFileSystem = MainWP_System_Utility::get_wp_file_system();
359
360 /**
361 * WordPress files system object.
362 *
363 * @global object
364 */
365 global $wp_filesystem;
366
367 if ( $hasWPFileSystem && ! empty( $wp_filesystem ) ) {
368
369 if ( ! $wp_filesystem->is_dir( $keysDir ) ) {
370 // MWP-1557: 0700 preferred (owner only), 0750 fallback for shared-hosting umask edge cases. Mirrors migrate_private_filenames().
371 if ( ! $wp_filesystem->mkdir( $keysDir, 0700 ) ) {
372 $wp_filesystem->mkdir( $keysDir, 0750 );
373 }
374 }
375
376 if ( ! file_exists( $keysDir . '.htaccess' ) ) {
377 $file_htaccess = $keysDir . '.htaccess';
378 $wp_filesystem->put_contents( $file_htaccess, 'deny from all' );
379 }
380
381 if ( ! file_exists( $keysDir . 'index.php' ) ) {
382 $file_index = $keysDir . 'index.php';
383 $wp_filesystem->touch( $file_index );
384 }
385 } else {
386
387 //phpcs:disable
388 if ( ! file_exists( $keysDir ) ) {
389 // MWP-1557: 0700 preferred (owner only), 0750 fallback for shared-hosting umask edge cases. Mirrors migrate_private_filenames().
390 if ( ! mkdir( $keysDir, 0700, true ) ) {
391 mkdir( $keysDir, 0750, true );
392 }
393 }
394
395 if ( ! file_exists( $keysDir . '.htaccess' ) ) {
396 $file_htaccess = @fopen( $keysDir . '.htaccess', 'w+' );
397 fwrite( $file_htaccess, 'deny from all' );
398 fclose( $file_htaccess );
399 }
400
401 if ( ! file_exists( $keysDir . 'index.php' ) ) {
402 $file_index = @fopen( $keysDir . 'index.php', 'w+' );
403 fclose( $file_index );
404 }
405 // phpcs:enable
406 }
407 }
408
409 /**
410 * Method get_keys_dir().
411 *
412 * Check for keys directory and create it if it doesn't already exist.
413 * set the file permissions and update htaccess.
414 *
415 * @return string Keys dir.
416 */
417 public static function get_keys_dir() {
418 $dirs = MainWP_System_Utility::get_mainwp_dir();
419 return $dirs[0] . 'pk' . DIRECTORY_SEPARATOR;
420 }
421
422 /**
423 * Method register_migration_hooks()
424 *
425 * Register the post-upgrade hook that triggers the one-time pk/ filename
426 * migration. Called from MainWP_System::activate_this_plugin() before
427 * MainWP_Install::install() runs, so the action handler is registered when
428 * `mainwp_db_after_update` fires.
429 *
430 * @return void
431 */
432 public static function register_migration_hooks() {
433 add_action( 'mainwp_db_after_update', array( static::class, 'migrate_private_filenames' ), 10, 2 );
434 add_action( 'mainwp_db_after_update', array( static::class, 'migrate_sibling_dir_perms' ), 10, 2 );
435 add_action( 'mainwp_db_after_update', array( static::class, 'fix_sibling_dir_perms_9023' ), 10, 2 );
436 }
437
438 /**
439 * Method migrate_private_filenames()
440 *
441 * One-time bulk migration for MWP-1557: rename legacy pk/ filenames
442 * (`mainwp_priv_encrypt_keys_<site_id>`) to the opaque HMAC-derived names
443 * computed by MainWP_System_Utility::get_private_filename(). Also tightens
444 * directory permissions from the legacy 0777 to 0700 (or 0750 fallback).
445 *
446 * Idempotent: only matches the legacy filename pattern, so re-running on
447 * already-migrated installs is a no-op. Lazy migration in
448 * MainWP_Encrypt_Data_Lib::get_key_file() handles any files this bulk
449 * pass might miss.
450 *
451 * @param string $from_version Pre-upgrade mainwp_db_version.
452 * @param string $to_version Post-upgrade mainwp_db_version.
453 *
454 * @return void
455 */
456 public static function migrate_private_filenames( $from_version, $to_version ) {
457 if ( ! version_compare( $from_version, '9.0.2.0', '<' ) ) {
458 return;
459 }
460 static::init_keys_dir();
461 $key_dir = static::get_keys_dir();
462 if ( ! is_dir( $key_dir ) ) {
463 return;
464 }
465
466 // Tighten directory permissions; 0700 preferred, 0750 if a shared web group needs read access.
467 if ( ! @chmod( $key_dir, 0700 ) ) { // phpcs:ignore WordPress.PHP.NoSilencedErrors,WordPress.WP.AlternativeFunctions.file_system_operations_chmod -- NOSONAR - best-effort hardening.
468 @chmod( $key_dir, 0750 ); // phpcs:ignore WordPress.PHP.NoSilencedErrors,WordPress.WP.AlternativeFunctions.file_system_operations_chmod -- NOSONAR - best-effort hardening.
469 }
470
471 // Rename legacy pk files to opaque HMAC-derived names.
472 $entries = @scandir( $key_dir ); // phpcs:ignore WordPress.PHP.NoSilencedErrors -- best-effort directory walk.
473 if ( ! is_array( $entries ) ) {
474 return;
475 }
476 foreach ( $entries as $entry ) {
477 if ( ! preg_match( '/^mainwp_priv_encrypt_keys_(\d+)$/', $entry, $m ) ) {
478 continue;
479 }
480 $site_id = (int) $m[1];
481 $new_name = MainWP_System_Utility::get_private_filename( 'pk', $site_id, 'priv_encrypt_keys' );
482 $old_path = $key_dir . $entry;
483 $new_path = $key_dir . $new_name;
484 if ( file_exists( $new_path ) ) {
485 // New file already present (rare race); legacy is stale, remove it.
486 @unlink( $old_path ); // phpcs:ignore WordPress.PHP.NoSilencedErrors -- cleanup of stale legacy.
487 continue;
488 }
489 @rename( $old_path, $new_path ); // phpcs:ignore WordPress.PHP.NoSilencedErrors -- best-effort; lazy migration in get_key_file() handles failures.
490 }
491 }
492
493 /**
494 * Method migrate_sibling_dir_perms()
495 *
496 * One-time chmod sweep for installs that created mainwp/ subdirs before
497 * MWP-1558's mkdir tightening landed in 9.0.2.0. mkdir() does not touch
498 * the mode of an existing directory, so pre-fix installs keep their
499 * legacy 0777 even after upgrading. Reported by Daan Kortenbach in the
500 * MWP-1557/1558 follow-up sweep (MWP-1566).
501 *
502 * Targets the known set of subdirs the plugin manages. Idempotent:
503 * chmodding an already-correct dir is a no-op. @chmod failures are
504 * swallowed (Windows hosts, shared-hosting suexec mismatches, dirs
505 * owned by a different system user -- all expected).
506 *
507 * @param string $from_version Pre-upgrade mainwp_db_version.
508 * @param string $to_version Post-upgrade mainwp_db_version.
509 *
510 * @return void
511 */
512 public static function migrate_sibling_dir_perms( $from_version, $to_version ) {
513 if ( ! version_compare( $from_version, '9.0.2.1', '<' ) ) {
514 return;
515 }
516 $dirs = MainWP_System_Utility::get_mainwp_dir();
517 if ( empty( $dirs[0] ) || ! is_dir( $dirs[0] ) ) {
518 return;
519 }
520 $base = rtrim( $dirs[0], '/\\' ) . DIRECTORY_SEPARATOR;
521
522 // mainwp/ root: 0755 (public-asset convention, holds index.php + subdirs).
523 @chmod( rtrim( $base, '/\\' ), 0755 ); // phpcs:ignore WordPress.PHP.NoSilencedErrors,WordPress.WP.AlternativeFunctions.file_system_operations_chmod -- NOSONAR - best-effort hardening.
524
525 // Public-asset subdirs. cost-tracker-products-icons is module-specific (constant-gated)
526 // but uses the same get_mainwp_dir(..., true) public pattern, so legacy installs that ran
527 // Cost Tracker pre-9.0.2.0 need the same chmod.
528 foreach ( array( 'icons', 'plugin-icons', 'theme-icons', 'client-images', 'site-icons', 'themes', 'cost-tracker-products-icons' ) as $sub ) {
529 $p = $base . $sub;
530 if ( is_dir( $p ) ) {
531 @chmod( $p, 0755 ); // phpcs:ignore WordPress.PHP.NoSilencedErrors,WordPress.WP.AlternativeFunctions.file_system_operations_chmod -- NOSONAR - best-effort hardening.
532 }
533 }
534
535 // Private (htaccess-protected) subdirs.
536 foreach ( array( 'cookies', 'templates', 'templates' . DIRECTORY_SEPARATOR . 'emails', 'bulk' ) as $sub ) {
537 $p = $base . $sub;
538 if ( is_dir( $p ) ) {
539 @chmod( $p, 0750 ); // phpcs:ignore WordPress.PHP.NoSilencedErrors,WordPress.WP.AlternativeFunctions.file_system_operations_chmod -- NOSONAR - best-effort hardening.
540 }
541 }
542
543 // Per-user dirs and all descendants: mainwp/<userid>/, /<userid>/bulk/, per-site
544 // <siteid>/ dirs from get_mainwp_specific_dir($website->id), and any deeper paths
545 // that backup_download_file() may have materialized via dirname($pFile) on legacy
546 // installs with custom backup filenames. All private (0750).
547
548 $public_access_dirs = array( 'favorites' );
549
550 $chmod_recursive = function ( $dir ) use ( &$chmod_recursive, $public_access_dirs ) {
551 @chmod( $dir, 0750 ); // phpcs:ignore WordPress.PHP.NoSilencedErrors,WordPress.WP.AlternativeFunctions.file_system_operations_chmod -- NOSONAR - best-effort hardening.
552 $subs = @glob( $dir . DIRECTORY_SEPARATOR . '*', GLOB_ONLYDIR ); // phpcs:ignore WordPress.PHP.NoSilencedErrors,WordPress.WP.AlternativeFunctions.file_system_operations_chmod -- NOSONAR - best-effort directory walk.
553 if ( is_array( $subs ) ) {
554 foreach ( $subs as $s ) {
555 if ( in_array( basename( $s ), $public_access_dirs, true ) ) {
556 @chmod( $s, 0755 ); // phpcs:ignore WordPress.PHP.NoSilencedErrors,WordPress.WP.AlternativeFunctions.file_system_operations_chmod -- NOSONAR - best-effort hardening.
557 continue;
558 }
559 $chmod_recursive( $s );
560 }
561 }
562 };
563
564 $userdirs = @glob( $base . '[0-9]*', GLOB_ONLYDIR ); // phpcs:ignore WordPress.PHP.NoSilencedErrors -- NOSONAR - best-effort directory walk.
565
566 if ( is_array( $userdirs ) ) {
567 foreach ( $userdirs as $udir ) {
568 @chmod( $udir, 0751 ); // phpcs:ignore WordPress.PHP.NoSilencedErrors,WordPress.WP.AlternativeFunctions.file_system_operations_chmod -- NOSONAR - best-effort hardening.
569 $subs = @glob( $udir . DIRECTORY_SEPARATOR . '*', GLOB_ONLYDIR ); // phpcs:ignore WordPress.PHP.NoSilencedErrors,WordPress.WP.AlternativeFunctions.file_system_operations_chmod -- NOSONAR - best-effort directory walk.
570 if ( is_array( $subs ) ) {
571 foreach ( $subs as $s ) {
572 if ( in_array( basename( $s ), $public_access_dirs, true ) ) {
573 @chmod( $s, 0755 ); // phpcs:ignore WordPress.PHP.NoSilencedErrors,WordPress.WP.AlternativeFunctions.file_system_operations_chmod -- NOSONAR - best-effort hardening.
574 continue;
575 }
576 $chmod_recursive( $s );
577 }
578 }
579 }
580 }
581 }
582
583 /**
584 * Method fix_sibling_dir_perms_9023()
585 *
586 * One-time chmod sweep for MWP-1566: fix a small number of installs that ran the MWP-1558/1566
587 * follow-up sweep (MWP-1566) on.
588 *
589 * @param string $from_version Pre-upgrade mainwp_db_version.
590 * @param string $to_version Post-upgrade mainwp_db_version.
591 *
592 * @return void
593 */
594 public static function fix_sibling_dir_perms_9023( $from_version, $to_version ) {
595
596 if ( empty( $from_version ) || version_compare( $from_version, '9.0.2.1', '<' ) || version_compare( $from_version, '9.0.2.3', '>=' ) ) {
597 return;
598 }
599
600 $dirs = MainWP_System_Utility::get_mainwp_dir();
601 if ( empty( $dirs[0] ) || ! is_dir( $dirs[0] ) ) {
602 return;
603 }
604 $base = rtrim( $dirs[0], '/\\' ) . DIRECTORY_SEPARATOR;
605
606 // Per-user dirs and all descendants: mainwp/<userid>/, /<userid>/bulk/, per-site
607 // <siteid>/ dirs from get_mainwp_specific_dir($website->id), and any deeper paths
608 // that backup_download_file() may have materialized via dirname($pFile) on legacy
609 // installs with custom backup filenames. All private (0750).
610
611 $public_access_dirs = array( 'favorites' );
612
613 $chmod_recursive = function ( $dir ) use ( &$chmod_recursive, $public_access_dirs ) {
614 @chmod( $dir, 0750 ); // phpcs:ignore WordPress.PHP.NoSilencedErrors,WordPress.WP.AlternativeFunctions.file_system_operations_chmod -- NOSONAR - best-effort hardening.
615 $subs = @glob( $dir . DIRECTORY_SEPARATOR . '*', GLOB_ONLYDIR ); // phpcs:ignore WordPress.PHP.NoSilencedErrors -- NOSONAR - best-effort directory walk.
616 if ( is_array( $subs ) ) {
617 foreach ( $subs as $s ) {
618 if ( in_array( basename( $s ), $public_access_dirs, true ) ) {
619 @chmod( $s, 0755 ); // phpcs:ignore WordPress.PHP.NoSilencedErrors,WordPress.WP.AlternativeFunctions.file_system_operations_chmod -- NOSONAR - best-effort hardening.
620 continue;
621 }
622 $chmod_recursive( $s );
623 }
624 }
625 };
626
627 $userdirs = @glob( $base . '[0-9]*', GLOB_ONLYDIR ); // phpcs:ignore WordPress.PHP.NoSilencedErrors -- NOSONAR - best-effort directory walk.
628
629 if ( is_array( $userdirs ) ) {
630 foreach ( $userdirs as $udir ) {
631 @chmod( $udir, 0751 ); // phpcs:ignore WordPress.PHP.NoSilencedErrors,WordPress.WP.AlternativeFunctions.file_system_operations_chmod -- NOSONAR - best-effort hardening.
632 $subs = @glob( $udir . DIRECTORY_SEPARATOR . '*', GLOB_ONLYDIR ); // phpcs:ignore WordPress.PHP.NoSilencedErrors,WordPress.WP.AlternativeFunctions.file_system_operations_chmod -- NOSONAR - best-effort directory walk.
633 if ( is_array( $subs ) ) {
634 foreach ( $subs as $s ) {
635 if ( in_array( basename( $s ), $public_access_dirs, true ) ) {
636 @chmod( $s, 0755 ); // phpcs:ignore WordPress.PHP.NoSilencedErrors,WordPress.WP.AlternativeFunctions.file_system_operations_chmod -- NOSONAR - best-effort hardening.
637 continue;
638 }
639 $chmod_recursive( $s );
640 }
641 }
642 }
643 }
644 }
645
646
647 /**
648 * Method encrypt_keys_data()
649 *
650 * Handle encrypt value.
651 *
652 * @param mixed $data The value to encrypt.
653 * @param string $prefix prefix key file name.
654 * @param string $key_file key file name.
655 *
656 * @return string Encrypted value.
657 */
658 public function encrypt_keys_data( $data, $prefix, $key_file = false ) { //phpcs:ignore -- NOSONAR - complex.
659
660 if ( empty( $data ) ) {
661 if ( ! empty( $key_file ) ) {
662 $this->delete_key_file( $key_file );
663 }
664 return $data;
665 }
666
667 if ( '_' !== substr( $prefix, -1 ) ) {
668 $prefix .= '_';
669 }
670
671 if ( ! function_exists( '\wp_rand' ) ) {
672 include_once ABSPATH . WPINC . '/pluggable.php'; // NOSONAR - WP compatible.
673 }
674
675 if ( ! empty( $key_file ) && is_string( $key_file ) ) {
676 $file_name = $key_file;
677 } elseif ( ! empty( $data ) && is_array( $data ) && ! empty( $data['file_key'] ) ) {
678 $file_name = $data['file_key'];
679 } else {
680 $ran = wp_rand( 0, 9990 ); // to fix repeat value.
681 $file_name = $prefix . sha1( sha1( $prefix . time() . $ran ) . 'key_files' ); // NOSONAR - safe for salt file name.
682 }
683
684 MainWP_Logger::instance()->debug( 'encrypt :: K file[' . $file_name . ']' );
685
686 try {
687 $key = Random::string( 32 ); // supported key length: 16, 24, 32.
688 $encrypted = $this->encrypt_with_key( $data, $key );
689 $result = array(
690 'key' => $key,
691 'file_key' => $file_name,
692 'encrypted_value' => $encrypted,
693 );
694 } catch ( \Exception $ex ) {
695 $err = $ex->getMessage();
696 if ( is_string( $err ) ) {
697 MainWP_Logger::instance()->debug( 'encrypt :: error[' . $err . ']' );
698 }
699 return false;
700 }
701
702 if ( is_array( $result ) && ! empty( $result['encrypted_value'] ) ) {
703 $key = $result['key'];
704 $file = $result['file_key'];
705 $pw = $result['encrypted_value'];
706 if ( $this->save_key_file( $file, $key ) ) {
707 return array(
708 'encrypted_val' => $pw,
709 'file_key' => $file,
710 );
711 }
712 }
713 return false;
714 }
715
716
717 /**
718 * Method decrypt_keys_data()
719 *
720 * Get decrypt value.
721 *
722 * @param string $encrypted Name of key.
723 * @param mixed $default_value Default value.
724 *
725 * @return string Decrypt value.
726 */
727 public function decrypt_keys_data( $encrypted, $default_value = false ) {
728 if ( is_array( $encrypted ) && ! empty( $encrypted['file_key'] ) && ! empty( $encrypted['encrypted_val'] ) ) {
729 try {
730 return $this->get_decrypt_values( $encrypted['encrypted_val'], $encrypted['file_key'], $default_value );
731 } catch ( \Exception $ex ) {
732 $err = $ex->getMessage();
733 if ( is_string( $err ) ) {
734 MainWP_Logger::instance()->debug( 'decrypt :: error[' . $err . ']' );
735 }
736 return false;
737 }
738 }
739 return $default_value;
740 }
741 }
742