PluginProbe
Search Atlas SEO – OTTO AI SEO Automation for WordPress / trunk
Search Atlas SEO – OTTO AI SEO Automation for WordPress vtrunk
2.6.26 2.6.25 2.6.24 2.6.23 2.6.22 2.6.21 2.6.20 2.6.19 2.6.18 2.6.17 2.6.16 2.6.15 2.6.14 2.6.13 2.6.12 2.6.11 2.6.10 2.6.9 2.6.8 2.6.7 2.6.6 2.6.5 2.6.4 2.6.3 2.5.23 All 138 releases
← All changes | admin/class-metasync-dev-panel.php +56 -16 2.6.13trunk View file →
@@ -40,8 +40,9 @@
40 40 /**
41 41 * Option name for storing the developer panel password.
42 42 */
43 43 const PASSWORD_OPTION = 'metasync_dev_panel_password';
44 + const PASSWORD_HASH_PREFIX = 'metasync-hash:';
44 45
45 46 /**
46 47 * Authentication manager instance.
47 48 *
@@ -72,8 +73,47 @@
72 73 add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
73 74 }
74 75
75 76 /**
77 + * Hash and store the developer tools password.
78 + *
79 + * @param string $password Plaintext password.
80 + * @return bool Whether the option was updated.
81 + */
82 + private function store_hashed_password( $password ) {
83 + $stored = self::PASSWORD_HASH_PREFIX . wp_hash_password( $password );
84 +
85 + return update_option( self::PASSWORD_OPTION, $stored );
86 + }
87 +
88 + /**
89 + * Verify the stored password and migrate legacy plaintext values on success.
90 + *
91 + * @param string $submitted Submitted plaintext password.
92 + * @return bool Whether the password is valid.
93 + */
94 + private function verify_dev_password( $submitted ) {
95 + $stored = get_option( self::PASSWORD_OPTION, '' );
96 +
97 + if ( ! is_string( $stored ) || $stored === '' ) {
98 + return false;
99 + }
100 +
101 + if ( strpos( $stored, self::PASSWORD_HASH_PREFIX ) === 0 ) {
102 + $hash = substr( $stored, strlen( self::PASSWORD_HASH_PREFIX ) );
103 +
104 + return wp_check_password( $submitted, $hash );
105 + }
106 +
107 + if ( hash_equals( $stored, $submitted ) ) {
108 + $this->store_hashed_password( $submitted );
109 + return true;
110 + }
111 +
112 + return false;
113 + }
114 +
115 + /**
76 116 * Register the developer panel menu (hidden from menu, accessible via URL).
77 117 */
78 118 public function add_dev_panel_menu() {
79 119 // Add hidden submenu page (no parent = hidden from menu)
@@ -171,10 +211,10 @@
171 211
172 212 // Handle password setup submission
173 213 if ( isset( $_POST['dev_panel_setup_submit'] ) ) {
174 214 if ( wp_verify_nonce( $_POST['dev_panel_setup_nonce'], 'metasync_dev_panel_setup' ) ) {
175 - $new_password = sanitize_text_field( $_POST['dev_panel_new_password'] );
176 - $confirm_password = sanitize_text_field( $_POST['dev_panel_confirm_password'] );
215 + $new_password = isset( $_POST['dev_panel_new_password'] ) ? (string) wp_unslash( $_POST['dev_panel_new_password'] ) : '';
216 + $confirm_password = isset( $_POST['dev_panel_confirm_password'] ) ? (string) wp_unslash( $_POST['dev_panel_confirm_password'] ) : '';
177 217
178 218 if ( empty( $new_password ) ) {
179 219 $setup_error = 'Password cannot be empty.';
180 220 } elseif ( $new_password !== $confirm_password ) {
@@ -181,9 +221,9 @@
181 221 $setup_error = 'Passwords do not match.';
182 222 } elseif ( strlen( $new_password ) < 6 ) {
183 223 $setup_error = 'Password must be at least 6 characters long.';
184 224 } else {
185 - $result = update_option( self::PASSWORD_OPTION, $new_password );
225 + $result = $this->store_hashed_password( $new_password );
186 226 if ( $result ) {
187 227 // Grant access immediately
188 228 if ( $this->auth ) {
189 229 $this->auth->grant_transient_access();
@@ -277,12 +317,12 @@
277 317
278 318 // Handle password submission
279 319 if ( isset( $_POST['dev_panel_password_submit'] ) ) {
280 320 if ( wp_verify_nonce( $_POST['dev_panel_nonce'], 'metasync_dev_panel_nonce' ) ) {
281 - $submitted_password = sanitize_text_field( $_POST['dev_panel_password'] );
282 - $saved_password = get_option( self::PASSWORD_OPTION, '' );
321 + $submitted_password = isset( $_POST['dev_panel_password'] ) ? (string) wp_unslash( $_POST['dev_panel_password'] ) : '';
283 322
284 - if ( $this->auth && $this->auth->verify_and_grant( $submitted_password, $saved_password, false ) ) {
323 + if ( $this->auth && $this->verify_dev_password( $submitted_password ) ) {
324 + $this->auth->grant_transient_access();
285 325 // Refresh page to show authenticated state - add timestamp to prevent caching
286 326 wp_safe_redirect( add_query_arg( 'login', 'success', $_SERVER['REQUEST_URI'] ) );
287 327 exit;
288 328 } else {
@@ -376,9 +416,9 @@
376 416
377 417 // Handle password update
378 418 if ( isset( $_POST['update_password_submit'] ) ) {
379 419 if ( wp_verify_nonce( $_POST['update_password_nonce'], 'metasync_update_dev_password' ) ) {
380 - $new_password = sanitize_text_field( $_POST['dev_panel_new_password'] );
420 + $new_password = isset( $_POST['dev_panel_new_password'] ) ? (string) wp_unslash( $_POST['dev_panel_new_password'] ) : '';
381 421
382 422 if ( empty( $new_password ) ) {
383 423 $message = 'Password cannot be empty.';
384 424 $message_type = 'error';
@@ -385,9 +425,9 @@
385 425 } elseif ( strlen( $new_password ) < 6 ) {
386 426 $message = 'Password must be at least 6 characters long.';
387 427 $message_type = 'error';
388 428 } else {
389 - $result = update_option( self::PASSWORD_OPTION, $new_password );
429 + $result = $this->store_hashed_password( $new_password );
390 430 if ( $result ) {
391 431 $message = 'Password updated successfully!';
392 432 $message_type = 'success';
393 433 } else {
@@ -533,11 +573,11 @@
533 573 public function ajax_switch_endpoints() {
534 574 // Verify nonce
535 575 check_ajax_referer( 'metasync_switch_endpoints', 'nonce' );
536 576
537 - // Verify permissions (matches main plugin's access control)
538 - if ( ! Metasync::current_user_has_plugin_access() ) {
539 - wp_send_json_error( array( 'message' => 'Insufficient permissions' ) );
577 + // Developer tools are administrator-only.
578 + if ( ! current_user_can( 'manage_options' ) ) {
579 + wp_send_json_error( array( 'message' => 'Insufficient permissions' ), 403 );
540 580 return;
541 581 }
542 582
543 583 // Verify authentication
@@ -575,11 +615,11 @@
575 615 public function ajax_update_dev_password() {
576 616 // Verify nonce
577 617 check_ajax_referer( 'metasync_update_dev_password', 'nonce' );
578 618
579 - // Verify permissions (matches main plugin's access control)
580 - if ( ! Metasync::current_user_has_plugin_access() ) {
581 - wp_send_json_error( array( 'message' => 'Insufficient permissions' ) );
619 + // Developer tools are administrator-only.
620 + if ( ! current_user_can( 'manage_options' ) ) {
621 + wp_send_json_error( array( 'message' => 'Insufficient permissions' ), 403 );
582 622 return;
583 623 }
584 624
585 625 // Verify authentication
@@ -588,9 +628,9 @@
588 628 return;
589 629 }
590 630
591 631 // Get new password
592 - $new_password = isset( $_POST['dev_panel_password'] ) ? sanitize_text_field( $_POST['dev_panel_password'] ) : '';
632 + $new_password = isset( $_POST['dev_panel_password'] ) ? (string) wp_unslash( $_POST['dev_panel_password'] ) : '';
593 633
594 634 if ( empty( $new_password ) ) {
595 635 wp_send_json_error( array( 'message' => 'Password cannot be empty' ) );
596 636 return;
@@ -600,9 +640,9 @@
600 640 wp_send_json_error( array( 'message' => 'Password must be at least 6 characters long' ) );
601 641 return;
602 642 }
603 643
604 - $result = update_option( self::PASSWORD_OPTION, $new_password );
644 + $result = $this->store_hashed_password( $new_password );
605 645
606 646 if ( $result ) {
607 647 wp_send_json_success( array( 'message' => 'Password updated successfully' ) );
608 648 } else {