WordPress.org

Make WordPress Core

Changeset 25696


Ignore:
Timestamp:
10/06/2013 11:28:42 AM (6 years ago)
Author:
nacin
Message:

Hash password reset keys in the database.

All existing, unused password reset keys are now considered "expired" and the user will be told they should try again.

Introduces a password_reset_key_expired filter to allow plugins to introduce a grace period.

fixes #24783.

Location:
trunk/src
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/user.php

    r25615 r25696  
    15881588 * Retrieves a user row based on password reset key and login
    15891589 *
     1590 * A key is considered 'expired' if it exactly matches the value of the
     1591 * user_activation_key field, rather than being matched after going through the
     1592 * hashing process. This field is now hashed; old values are no longer accepted
     1593 * but have a different WP_Error code so good user feedback can be provided.
     1594 *
    15901595 * @uses $wpdb WordPress Database object
    15911596 *
    1592  * @param string $key Hash to validate sending user's password
    1593  * @param string $login The user login
    1594  * @return object|WP_Error User's database row on success, error object for invalid keys
    1595  */
    1596 function check_password_reset_key( $key, $login ) {
    1597     global $wpdb;
    1598 
    1599     $key = preg_replace( '/[^a-z0-9]/i', '', $key );
    1600 
    1601     if ( empty( $key ) || ! is_string( $key ) )
    1602         return new WP_Error( 'invalid_key', __( 'Invalid key' ) );
    1603 
    1604     if ( empty( $login ) || ! is_string( $login ) )
    1605         return new WP_Error( 'invalid_key', __( 'Invalid key' ) );
    1606 
    1607     $user = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $wpdb->users WHERE user_activation_key = %s AND user_login = %s", $key, $login ) );
    1608 
    1609     if ( empty( $user ) )
    1610         return new WP_Error( 'invalid_key', __( 'Invalid key' ) );
    1611 
    1612     return $user;
     1597 * @param string $key       Hash to validate sending user's password.
     1598 * @param string $login     The user login.
     1599 * @return WP_User|WP_Error WP_User object on success, WP_Error object for invalid or expired keys.
     1600 */
     1601function check_password_reset_key($key, $login) {
     1602    global $wpdb, $wp_hasher;
     1603
     1604    $key = preg_replace('/[^a-z0-9]/i', '', $key);
     1605
     1606    if ( empty( $key ) || !is_string( $key ) )
     1607        return new WP_Error('invalid_key', __('Invalid key'));
     1608
     1609    if ( empty($login) || !is_string($login) )
     1610        return new WP_Error('invalid_key', __('Invalid key'));
     1611
     1612    $row = $wpdb->get_row( $wpdb->prepare( "SELECT ID, user_activation_key FROM $wpdb->users WHERE user_login = %s", $login ) );
     1613    if ( ! $row )
     1614        return new WP_Error('invalid_key', __('Invalid key'));
     1615
     1616    if ( empty( $wp_hasher ) ) {
     1617        require_once ABSPATH . 'wp-includes/class-phpass.php';
     1618        $wp_hasher = new PasswordHash( 8, true );
     1619    }
     1620
     1621    if ( $wp_hasher->CheckPassword( $key, $row->user_activation_key ) )
     1622        return get_userdata( $row->ID );
     1623
     1624    if ( $key === $row->user_activation_key ) {
     1625        $return = new WP_Error( 'expired_key', __( 'Invalid key' ) );
     1626        $user_id = $row->ID;
     1627
     1628        /**
     1629         * Filter the return value of check_password_reset_key() when an
     1630         * old-style key is used (plain-text key was stored in the database).
     1631         *
     1632         * @since 3.7.0
     1633         *
     1634         * @param WP_Error $return  A WP_Error object denoting an expired key.
     1635         *                          Return a WP_User object to validate the key.
     1636         * @param int      $user_id The matched user ID.
     1637         */
     1638        return apply_filters( 'password_reset_key_expired', $return, $user_id );
     1639    }
     1640
     1641    return new WP_Error( 'invalid_key', __( 'Invalid key' ) );
    16131642}
    16141643
  • trunk/src/wp-login.php

    r25619 r25696  
    203203 */
    204204function retrieve_password() {
    205     global $wpdb, $current_site;
     205    global $wpdb, $current_site, $wp_hasher;
    206206
    207207    $errors = new WP_Error();
     
    242242        return $allow;
    243243
    244     $key = $wpdb->get_var($wpdb->prepare("SELECT user_activation_key FROM $wpdb->users WHERE user_login = %s", $user_login));
    245     if ( empty($key) ) {
    246         // Generate something random for a key...
    247         $key = wp_generate_password(20, false);
    248         do_action('retrieve_password_key', $user_login, $key);
    249         // Now insert the new md5 key into the db
    250         $wpdb->update($wpdb->users, array('user_activation_key' => $key), array('user_login' => $user_login));
    251     }
     244    // Generate something random for a password reset key.
     245    $key = wp_generate_password( 20, false );
     246
     247    /**
     248     * Fires when a password reset key is generated.
     249     *
     250     * @since 2.5.0
     251     *
     252     * @param string $user_login The username for the user.
     253     * @param string $key        The generated password reset key.
     254     */
     255    do_action( 'retrieve_password_key', $user_login, $key );
     256
     257    // Now insert the key, hashed, into the DB.
     258    if ( empty( $wp_hasher ) ) {
     259        require_once ABSPATH . 'wp-includes/class-phpass.php';
     260        $wp_hasher = new PasswordHash( 8, true );
     261    }
     262    $hashed = $wp_hasher->HashPassword( $key );
     263    $wpdb->update( $wpdb->users, array( 'user_activation_key' => $hashed ), array( 'user_login' => $user_login ) );
     264
    252265    $message = __('Someone requested that the password be reset for the following account:') . "\r\n\r\n";
    253266    $message .= network_home_url( '/' ) . "\r\n\r\n";
     
    359372    }
    360373
    361     if ( isset($_GET['error']) && 'invalidkey' == $_GET['error'] ) $errors->add('invalidkey', __('Sorry, that key does not appear to be valid.'));
     374    if ( isset( $_GET['error'] ) ) {
     375        if ( 'invalidkey' == $_GET['error'] )
     376            $errors->add( 'invalidkey', __( 'Sorry, that key does not appear to be valid.' ) );
     377        elseif ( 'expiredkey' == $_GET['error'] )
     378            $errors->add( 'expiredkey', __( 'Sorry, that key has expired. Please try again.' ) );
     379    }
     380
    362381    $redirect_to = apply_filters( 'lostpassword_redirect', !empty( $_REQUEST['redirect_to'] ) ? $_REQUEST['redirect_to'] : '' );
    363382
     
    395414
    396415    if ( is_wp_error($user) ) {
    397         wp_redirect( site_url('wp-login.php?action=lostpassword&error=invalidkey') );
     416        if ( $user->get_error_code() === 'expired_key' )
     417            wp_redirect( site_url( 'wp-login.php?action=lostpassword&error=expiredkey' ) );
     418        else
     419            wp_redirect( site_url( 'wp-login.php?action=lostpassword&error=invalidkey' ) );
    398420        exit;
    399421    }
Note: See TracChangeset for help on using the changeset viewer.