| @@ -9,9 +9,9 @@ | ||
| 9 | 9 | |
| 10 | 10 | /** |
| 11 | 11 | * Implementation of PHPWind Forum converter. |
| 12 | 12 | * |
| 13 | - * @since 2.5.0 bbPress (r5142) | |
| 13 | + * @since 2.5.0 bbPress (r5143) | |
| 14 | 14 | * |
| 15 | 15 | * @link Codex Docs https://codex.bbpress.org/import-forums/phpwind |
| 16 | 16 | */ |
| 17 | 17 | class PHPWind extends BBP_Converter_Base { |
| @@ -525,8 +525,126 @@ | ||
| 525 | 525 | return hash_equals( |
| 526 | 526 | $pass_array['hash'], |
| 527 | 527 | md5( md5( $password ) . $pass_array['salt'] ) |
| 528 | 528 | ); |
| 529 | + } | |
| 530 | + | |
| 531 | + /** | |
| 532 | + * Upgrade password metadata written by earlier PHPWind imports. | |
| 533 | + * | |
| 534 | + * Earlier imports stored PHPWind's random synchronization token instead of | |
| 535 | + * its login hash and salt. New imports use the parent callback and serialized | |
| 536 | + * metadata containing the login hash and salt. | |
| 537 | + * | |
| 538 | + * @param string $username WordPress user login. | |
| 539 | + * @param string $password Unslashed password for PHPWind. | |
| 540 | + * @param string|null $wp_password Optional slashed password for WordPress. | |
| 541 | + */ | |
| 542 | + public function callback_pass( $username = '', $password = '', $wp_password = null ) { | |
| 543 | + $user = get_user_by( 'login', $username ); | |
| 544 | + | |
| 545 | + if ( ! empty( $user ) && '' === $user->user_pass ) { | |
| 546 | + $stored_hash = get_user_meta( $user->ID, '_bbp_password', true ); | |
| 547 | + | |
| 548 | + if ( is_string( $stored_hash ) && preg_match( '/^[a-f0-9]{32}$/i', $stored_hash ) ) { | |
| 549 | + $serialized_pass = $this->get_source_password( $user, $stored_hash ); | |
| 550 | + | |
| 551 | + if ( is_string( $serialized_pass ) && $this->authenticate_pass( $password, $serialized_pass ) ) { | |
| 552 | + $this->upgrade_user_pass( $user->ID, is_null( $wp_password ) ? $password : $wp_password ); | |
| 553 | + } | |
| 554 | + | |
| 555 | + return; | |
| 556 | + } | |
| 557 | + } | |
| 558 | + | |
| 559 | + parent::callback_pass( $username, $password, $wp_password ); | |
| 560 | + } | |
| 561 | + | |
| 562 | + /** | |
| 563 | + * Upgrade a PHPWind hash copied directly into wp_users by an old import. | |
| 564 | + * | |
| 565 | + * Those imports stored PHPWind's random synchronization token, not its login | |
| 566 | + * hash. Match that token against the retained source row before retrieving | |
| 567 | + * the login hash and salt from WindID. | |
| 568 | + * | |
| 569 | + * @param WP_User $user WordPress user. | |
| 570 | + * @param string $password Unslashed password for PHPWind. | |
| 571 | + * @param string|null $wp_password Optional slashed password for WordPress. | |
| 572 | + */ | |
| 573 | + public function callback_user_pass( $user, $password, $wp_password = null ) { | |
| 574 | + if ( ! ( $user instanceof WP_User ) || ! is_string( $password ) || ! preg_match( '/^[a-f0-9]{32}$/i', $user->user_pass ) ) { | |
| 575 | + return; | |
| 576 | + } | |
| 577 | + | |
| 578 | + $wp_password = is_null( $wp_password ) ? $password : $wp_password; | |
| 579 | + | |
| 580 | + // Do not replace a password that WordPress or a password plugin already | |
| 581 | + // recognizes, regardless of the stored hash format. | |
| 582 | + if ( wp_check_password( $wp_password, $user->user_pass, $user->ID ) ) { | |
| 583 | + return; | |
| 584 | + } | |
| 585 | + | |
| 586 | + $serialized_pass = $this->get_source_password( $user, $user->user_pass ); | |
| 587 | + | |
| 588 | + if ( is_string( $serialized_pass ) && $this->authenticate_pass( $password, $serialized_pass ) ) { | |
| 589 | + $this->upgrade_user_pass( $user->ID, $wp_password ); | |
| 590 | + } | |
| 591 | + } | |
| 592 | + | |
| 593 | + /** | |
| 594 | + * Retrieve login metadata for a token written by an earlier import. | |
| 595 | + * | |
| 596 | + * @param WP_User $user WordPress user. | |
| 597 | + * @param string $stored_hash Random token stored by the earlier import. | |
| 598 | + * @return string|bool Serialized password metadata, or false on failure. | |
| 599 | + */ | |
| 600 | + private function get_source_password( $user, $stored_hash ) { | |
| 601 | + $old_user_id = (int) get_user_meta( $user->ID, '_bbp_old_user_id', true ); | |
| 602 | + if ( $old_user_id < 1 || get_transient( 'bbp_phpwind_source_connection_failed' ) ) { | |
| 603 | + return false; | |
| 604 | + } | |
| 605 | + | |
| 606 | + // Block concurrent login attempts while the source connection or query is pending. | |
| 607 | + // Keep the backoff when either fails, so an unavailable source cannot stall | |
| 608 | + // every login attempt for an imported username. | |
| 609 | + set_transient( 'bbp_phpwind_source_connection_failed', 1, 5 * MINUTE_IN_SECONDS ); | |
| 610 | + | |
| 611 | + if ( ! $this->opdb->db_connect( false ) ) { | |
| 612 | + return false; | |
| 613 | + } | |
| 614 | + | |
| 615 | + $legacy_table = $this->opdb->prefix . 'user'; | |
| 616 | + $windid_table = $this->opdb->prefix . 'windid_user'; | |
| 617 | + $source_user = $this->opdb->get_row( | |
| 618 | + $this->opdb->prepare( "SELECT legacy.password AS legacy_password, windid.password, windid.salt FROM {$legacy_table} AS legacy INNER JOIN {$windid_table} AS windid ON windid.uid = legacy.uid WHERE legacy.uid = %d LIMIT 1", $old_user_id ), | |
| 619 | + ARRAY_A | |
| 620 | + ); | |
| 621 | + if ( empty( $this->opdb->last_error ) ) { | |
| 622 | + delete_transient( 'bbp_phpwind_source_connection_failed' ); | |
| 623 | + } | |
| 624 | + | |
| 625 | + if ( ! is_array( $source_user ) || ! isset( $source_user['legacy_password'], $source_user['password'], $source_user['salt'] ) || ! is_string( $source_user['legacy_password'] ) || ! is_string( $source_user['password'] ) || ! is_string( $source_user['salt'] ) || ! hash_equals( $stored_hash, $source_user['legacy_password'] ) ) { | |
| 626 | + return false; | |
| 627 | + } | |
| 628 | + | |
| 629 | + return serialize( | |
| 630 | + array( | |
| 631 | + 'hash' => $source_user['password'], | |
| 632 | + 'salt' => $source_user['salt'], | |
| 633 | + ) | |
| 634 | + ); | |
| 635 | + } | |
| 636 | + | |
| 637 | + /** | |
| 638 | + * Replace a verified PHPWind hash with a WordPress password. | |
| 639 | + * | |
| 640 | + * @param int $user_id WordPress user ID. | |
| 641 | + * @param string $password Password to hash for WordPress. | |
| 642 | + */ | |
| 643 | + private function upgrade_user_pass( $user_id, $password ) { | |
| 644 | + wp_set_password( $password, $user_id ); | |
| 645 | + delete_user_meta( $user_id, '_bbp_password' ); | |
| 646 | + delete_user_meta( $user_id, '_bbp_class' ); | |
| 529 | 647 | } |
| 530 | 648 | |
| 531 | 649 | /** |
| 532 | 650 | * Translate the forum type from PHPWind v9.x Capitalised case to WordPress's non-capatilise case strings. |