WordPress.org

Make WordPress Core

Changeset 25696


Ignore:
Timestamp:
10/06/13 11:28:42 (6 months 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.