PluginProbe
bbPress / 2.6.19
bbPress v2.6.19
2.6.19 2.6.18 2.6.17 trunk 2.0 2.0-beta-1 2.0-beta-2b 2.0-beta-3 2.0-beta-3b 2.0-rc-2 2.0-rc-3 2.0-rc-4 2.0-rc-5 2.0.1 2.0.2 2.0.3 2.1 2.1-beta-1 2.1-rc1 2.1-rc2 2.1-rc3 2.1-rc4 2.1.1 2.1.2 2.1.3 All 74 releases
← All changes | includes/admin/converters/Drupal7.php +197 -34 2.6.17 → 2.6.19 View file →
@@ -493,28 +493,20 @@
493 493 'to_type' => 'user',
494 494 'to_fieldname' => '_bbp_old_user_id'
495 495 );
496 496
497 - // Store old user password (Stored in usermeta serialized with salt)
497 + // Store old user password (Stored in usermeta as a serialized array)
498 498 $this->field_map[] = array(
499 499 'from_tablename' => 'users',
500 500 'from_fieldname' => 'pass',
501 501 'to_type' => 'user',
502 - 'to_fieldname' => '_bbp_password'
503 - // 'callback_method' => 'callback_savepass'
502 + 'to_fieldname' => '_bbp_password',
503 + 'callback_method' => 'callback_savepass'
504 504 );
505 505
506 - // Store old user salt (This is only used for the SELECT row info for the above password save)
507 - $this->field_map[] = array(
508 - 'from_tablename' => 'users',
509 - 'from_fieldname' => 'pass',
510 - 'to_type' => 'user',
511 - 'to_fieldname' => ''
512 - );
513 -
514 506 // User password verify class (Stored in usermeta for verifying password)
515 507 $this->field_map[] = array(
516 - 'to_type' => 'users',
508 + 'to_type' => 'user',
517 509 'to_fieldname' => '_bbp_class',
518 510 'default' => 'Drupal7'
519 511 );
520 512
@@ -569,22 +561,66 @@
569 561 return '';
570 562 }
571 563
572 564 /**
573 - * This method is to save the salt and password together. That
574 - * way when we authenticate it we can get it out of the database
575 - * as one value. Array values are auto sanitized by WordPress.
565 + * Store the Drupal password hash in a serialized array.
566 + *
567 + * Array values are automatically serialized by WordPress.
576 568 */
577 569 public function callback_savepass( $field, $row ) {
578 - $pass_array = array(
579 - 'hash' => $field,
580 - 'salt' => $row['salt']
581 - );
570 + return array( 'hash' => $field );
571 + }
582 572
583 - return $pass_array;
573 + /**
574 + * Upgrade password metadata written by earlier Drupal 7 imports.
575 + *
576 + * Earlier imports stored the hash without serializing it. New imports use
577 + * the parent callback and the serialized format from callback_savepass().
578 + *
579 + * @param string $username WordPress user login.
580 + * @param string $password Unslashed password for Drupal.
581 + * @param string|null $wp_password Optional slashed password for WordPress.
582 + */
583 + public function callback_pass( $username = '', $password = '', $wp_password = null ) {
584 + $user = get_user_by( 'login', $username );
585 +
586 + if ( ! empty( $user ) && '' === $user->user_pass ) {
587 + $stored_hash = get_user_meta( $user->ID, '_bbp_password', true );
588 +
589 + if ( is_string( $stored_hash ) && $this->is_drupal_hash( $stored_hash ) ) {
590 + if ( $this->authenticate_pass( $password, serialize( array( 'hash' => $stored_hash ) ) ) ) {
591 + $this->upgrade_user_pass( $user->ID, is_null( $wp_password ) ? $password : $wp_password );
592 + }
593 +
594 + return;
595 + }
596 + }
597 +
598 + parent::callback_pass( $username, $password, $wp_password );
584 599 }
585 600
586 601 /**
602 + * Upgrade a Drupal hash copied directly into wp_users by an old import.
603 + *
604 + * @param WP_User $user WordPress user.
605 + * @param string $password Unslashed password for Drupal.
606 + * @param string|null $wp_password Optional slashed password for WordPress.
607 + */
608 + public function callback_user_pass( $user, $password, $wp_password = null ) {
609 + $wp_password = is_null( $wp_password ) ? $password : $wp_password;
610 +
611 + // Do not replace a password that WordPress or a password plugin already
612 + // recognizes, regardless of the stored hash prefix.
613 + if ( ! ( $user instanceof WP_User ) || wp_check_password( $wp_password, $user->user_pass, $user->ID ) || ! $this->is_drupal_hash( $user->user_pass, false ) ) {
614 + return;
615 + }
616 +
617 + if ( $this->authenticate_pass( $password, serialize( array( 'hash' => $user->user_pass ) ) ) ) {
618 + $this->upgrade_user_pass( $user->ID, $wp_password );
619 + }
620 + }
621 +
622 + /**
587 623 * This method is to take the pass out of the database and compare
588 624 * to a pass the user has typed in.
589 625 */
590 626 public function authenticate_pass( $password, $serialized_pass ) {
@@ -589,26 +625,153 @@
589 625 */
590 626 public function authenticate_pass( $password, $serialized_pass ) {
591 627
592 628 // Unserialize the password, with safeguards
593 - $pass_array = unserialize(
594 - $serialized_pass,
595 - array(
596 - 'allowed_classes' => false,
597 - 'max_depth' => 1
598 - )
599 - );
629 + $pass_array = $this->unserialize_pass( $serialized_pass );
600 630
601 - // Bail if missing values
602 - if ( ! is_array( $pass_array ) || ! isset( $pass_array['hash'], $pass_array['salt'] ) ) {
631 + // Bail if missing or invalid values
632 + if ( ! is_string( $password ) || ! is_array( $pass_array ) || ! isset( $pass_array['hash'] ) || ! is_string( $pass_array['hash'] ) ) {
603 633 return false;
604 634 }
605 635
606 - // Return comparison
607 - return hash_equals(
608 - $pass_array['hash'],
609 - md5( md5( $password ) . $pass_array['salt'] )
610 - );
636 + $stored_hash = $pass_array['hash'];
637 +
638 + // Drupal 6 passwords upgraded to Drupal 7 use an MD5 pre-hash
639 + if ( 0 === strpos( $stored_hash, 'U$' ) ) {
640 + $stored_hash = substr( $stored_hash, 1 );
641 + $password = md5( $password );
642 + }
643 +
644 + switch ( substr( $stored_hash, 0, 3 ) ) {
645 + case '$S$' :
646 + $hash = $this->hash_password( 'sha512', $password, $stored_hash );
647 + break;
648 +
649 + case '$H$' :
650 + case '$P$' :
651 + $hash = $this->hash_password( 'md5', $password, $stored_hash );
652 + break;
653 +
654 + default :
655 + return false;
656 + }
657 +
658 + return is_string( $hash ) && hash_equals( $stored_hash, $hash );
659 + }
660 +
661 + /**
662 + * Hash a password using Drupal 7's portable password algorithm.
663 + *
664 + * @param string $algorithm Hash algorithm.
665 + * @param string $password Plain-text password.
666 + * @param string $setting Stored hash or hash setting.
667 + * @return string|bool Password hash on success, false on failure.
668 + */
669 + private function hash_password( $algorithm, $password, $setting ) {
670 + if ( strlen( $password ) > 512 ) {
671 + return false;
672 + }
673 +
674 + $setting = substr( $setting, 0, 12 );
675 + if ( 12 !== strlen( $setting ) || '$' !== $setting[0] || '$' !== $setting[2] ) {
676 + return false;
677 + }
678 +
679 + $characters = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
680 + $count_log2 = strpos( $characters, $setting[3] );
681 + $salt = substr( $setting, 4, 8 );
682 +
683 + if ( false === $count_log2 || $count_log2 < 7 || $count_log2 > 30 || 8 !== strlen( $salt ) ) {
684 + return false;
685 + }
686 +
687 + $count = 1 << $count_log2;
688 + $hash = hash( $algorithm, $salt . $password, true );
689 +
690 + do {
691 + $hash = hash( $algorithm, $hash . $password, true );
692 + } while ( --$count );
693 +
694 + $output = $setting . $this->base64_encode_password( $hash, strlen( $hash ) );
695 + $expected = 12 + ceil( ( 8 * strlen( $hash ) ) / 6 );
696 +
697 + return ( strlen( $output ) === (int) $expected )
698 + ? substr( $output, 0, 55 )
699 + : false;
700 + }
701 +
702 + /**
703 + * Encode bytes using Drupal 7's password base64 alphabet.
704 + *
705 + * @param string $input Bytes to encode.
706 + * @param int $count Number of bytes to encode.
707 + * @return string Encoded bytes.
708 + */
709 + private function base64_encode_password( $input, $count ) {
710 + $output = '';
711 + $index = 0;
712 + $characters = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
713 +
714 + do {
715 + $value = ord( $input[ $index++ ] );
716 + $output .= $characters[ $value & 0x3f ];
717 + if ( $index < $count ) {
718 + $value |= ord( $input[ $index ] ) << 8;
719 + }
720 + $output .= $characters[ ( $value >> 6 ) & 0x3f ];
721 + if ( $index++ >= $count ) {
722 + break;
723 + }
724 + if ( $index < $count ) {
725 + $value |= ord( $input[ $index ] ) << 16;
726 + }
727 + $output .= $characters[ ( $value >> 12 ) & 0x3f ];
728 + if ( $index++ >= $count ) {
729 + break;
730 + }
731 + $output .= $characters[ ( $value >> 18 ) & 0x3f ];
732 + } while ( $index < $count );
733 +
734 + return $output;
735 + }
736 +
737 + /**
738 + * Determine whether a stored value is a Drupal password hash.
739 + *
740 + * @param string $hash Stored password hash.
741 + * @param bool $allow_phpass Whether to include WordPress-compatible $P$.
742 + * @return bool True when the value has a supported Drupal hash prefix.
743 + */
744 + private function is_drupal_hash( $hash, $allow_phpass = true ) {
745 + if ( ! is_string( $hash ) ) {
746 + return false;
747 + }
748 +
749 + // A $P$ hash in metadata is unambiguously from Drupal, while one copied
750 + // into user_pass is also a WordPress-compatible password hash.
751 + $prefixes = $allow_phpass
752 + ? array( '$S$', '$H$', '$P$', 'U$' )
753 + : array( '$S$', '$H$', 'U$' );
754 +
755 + foreach ( $prefixes as $prefix ) {
756 + if ( 0 === strpos( $hash, $prefix ) ) {
757 + return true;
758 + }
759 + }
760 +
761 + return false;
762 + }
763 +
764 + /**
765 + * Replace a verified Drupal hash with a WordPress password.
766 + *
767 + * @param int $user_id WordPress user ID.
768 + * @param string $password Password to hash for WordPress.
769 + */
770 + private function upgrade_user_pass( $user_id, $password ) {
771 + wp_set_password( $password, $user_id );
772 + delete_user_meta( $user_id, '_bbp_password' );
773 + delete_user_meta( $user_id, '_bbp_class' );
611 774 }
612 775
613 776 /**
614 777 * Translate the post status from Drupal v7.x numerics to WordPress's