WordPress.org

Make WordPress Core

Changeset 42964


Ignore:
Timestamp:
04/06/2018 07:09:53 PM (2 years ago)
Author:
azaozz
Message:

Privacy: fixes and updates for the method to confirm user requests by email.

  • Improve function and variable names.
  • Allow extra data to be passed with the request.
  • Make the option/user meta names more consistent.
  • Adds an inline comment explaining use of hash.

Props mikejolley.
See #43443.

Location:
trunk/src
Files:
2 edited

Legend:

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

    r42827 r42964  
    28162816 * @since 5.0.0
    28172817 *
    2818  * @param string $action_name        Name of the action that is being confirmed.
    2819  * @param string $action_description User facing description of the action they will be confirming.
    2820  * @param string $email              User email address. This can be the address of a registered or non-registered user. Defaults to logged in user email address.
    2821  *
    2822  * @return WP_ERROR|bool Will return true/false based on the success of sending the email, or a WP_Error object.
    2823  */
    2824 function send_confirm_account_action_email( $action_name, $action_description = '', $email = '' ) {
     2818 * @param string $email              User email address. This can be the address of a registered or non-registered user. Defaults to logged in user email address.
     2819 * @param string $action_name        Name of the action that is being confirmed. Defaults to 'confirm_email'.
     2820 * @param string $action_description User facing description of the action they will be confirming. Defaults to "confirm your email address".
     2821 * @param array  $request_data       Misc data you want to send with the verification request and pass to the actions once the request is confirmed.
     2822 * @return WP_Error|bool Will return true/false based on the success of sending the email, or a WP_Error object.
     2823 */
     2824function wp_send_account_verification_key( $email = '', $action_name = '', $action_description = '', $request_data = array() ) {
     2825    if ( ! function_exists( 'wp_get_current_user' ) ) {
     2826        return new WP_Error( 'invalid', __( 'This function cannot be used before init.' ) );
     2827    }
     2828
    28252829    $action_name        = sanitize_key( $action_name );
    28262830    $action_description = wp_kses_post( $action_description );
    28272831
    28282832    if ( empty( $action_name ) ) {
    2829         return new WP_Error( 'invalid_action', __( 'Invalid action' ) );
     2833        $action_name = 'confirm_email';
     2834    }
     2835
     2836    if ( empty( $action_description ) ) {
     2837        $action_description = __( 'Confirm your email address.' );
    28302838    }
    28312839
     
    28472855    }
    28482856
     2857    $confirm_key = wp_get_account_verification_key( $email, $action_name, $request_data );
     2858
     2859    if ( is_wp_error( $confirm_key ) ) {
     2860        return $confirm_key;
     2861    }
     2862
    28492863    // We could be dealing with a registered user account, or a visitor.
    28502864    $is_registered_user = $user && ! is_wp_error( $user );
    2851     $uid                = $is_registered_user ? $user->ID : hash( 'sha256', $email );
    2852     $confirm_key        = get_confirm_account_action_key( $action_name, $email );
    2853 
    2854     if ( is_wp_error( $confirm_key ) ) {
    2855         return $confirm_key;
    2856     }
    2857 
    2858     // Prepare the email content.
    2859     if ( ! $action_description ) {
    2860         $action_description = $action_name;
     2865
     2866    if ( $is_registered_user ) {
     2867        $uid = $user->ID;
     2868    } else {
     2869        // Generate a UID for this email address so we don't send the actual email in the query string. Hash is not supported on all systems.
     2870        $uid = function_exists( 'hash' ) ? hash( 'sha256', $email ) : sha1( $email );
    28612871    }
    28622872
     
    28652875        'Howdy,
    28662876
    2867 An account linked to your email address has requested to perform
    2868 the following action:
     2877A request has been made to perform the following action on your account:
    28692878
    28702879     ###DESCRIPTION###
    28712880
    2872 To confirm this action, please click on the following link:
     2881To confirm this, please click on the following link:
    28732882###CONFIRM_URL###
    28742883
     
    28882897        'description' => $action_description,
    28892898        'confirm_url' => add_query_arg( array(
    2890             'action'         => 'emailconfirm',
     2899            'action'         => 'verifyaccount',
    28912900            'confirm_action' => $action_name,
    28922901            'uid'            => $uid,
     
    29082917     * ###SITEURL###            The URL to the site.
    29092918     *
     2919     * @since 5.0.0
     2920     *
    29102921     * @param string $email_text     Text in the email.
    29112922     * @param array  $email_data {
     
    29202931     * }
    29212932     */
    2922     $content = apply_filters( 'confirm_account_action_email_content', $email_text, $email_data );
     2933    $content = apply_filters( 'account_verification_email_content', $email_text, $email_data );
    29232934
    29242935    $content = str_replace( '###DESCRIPTION###', $email_data['description'], $content );
     
    29292940
    29302941    /* translators: %s Site name. */
    2931     return wp_mail( $email_data['email'], sprintf( __( '[%s] Confirm Account Action' ), wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES ) ), $content );
     2942    return wp_mail( $email_data['email'], sprintf( __( '[%s] Confirm Action' ), wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES ) ), $content );
    29322943}
    29332944
     
    29372948 * @since 5.0.0
    29382949 *
    2939  * @param string $action_name Name of the action this key is being generated for.
    2940  * @param string $email       User email address. This can be the address of a registered or non-registered user.
    2941  *
     2950 * @param string $email        User email address. This can be the address of a registered or non-registered user.
     2951 * @param string $action_name  Name of the action this key is being generated for.
     2952 * @param array  $request_data Misc data you want to send with the verification request and pass to the actions once the request is confirmed.
    29422953 * @return string|WP_Error Confirmation key on success. WP_Error on error.
    29432954 */
    2944 function get_confirm_account_action_key( $action_name, $email ) {
     2955function wp_get_account_verification_key( $email, $action_name, $request_data = array() ) {
    29452956    global $wp_hasher;
    29462957
     
    29682979
    29692980    $hashed_key = $wp_hasher->HashPassword( $key );
     2981    $value      = array(
     2982        'action'       => $action_name,
     2983        'time'         => time(),
     2984        'hash'         => $hashed_key,
     2985        'email'        => $email,
     2986        'request_data' => $request_data,
     2987    );
    29702988
    29712989    if ( $is_registered_user ) {
    2972         $key_saved = (bool) update_user_meta( $user->ID, '_account_action_' . $action_name, implode( ':', array( time(), $hashed_key ) ) );
     2990        $key_saved = (bool) update_user_meta( $user->ID, '_verify_action_' . $action_name, wp_json_encode( $value ) );
    29732991    } else {
    2974         $key_saved = (bool) update_site_option( '_account_action_' . hash( 'sha256', $email ) . '_' . $action_name, implode( ':', array( time(), $hashed_key, $email ) ) );
     2992        $uid       = function_exists( 'hash' ) ? hash( 'sha256', $email ) : sha1( $email );
     2993        $key_saved = (bool) update_site_option( '_verify_action_' . $action_name . '_' . $uid, wp_json_encode( $value ) );
    29752994    }
    29762995
    29772996    if ( false === $key_saved ) {
    2978         return new WP_Error( 'no_confirm_account_action_key_update', __( 'Could not save confirm account action key to database.' ) );
     2997        return new WP_Error( 'no_account_verification_key_update', __( 'Could not save confirm account action key to database.' ) );
    29792998    }
    29802999
     
    29873006 * @since 5.0.0
    29883007 *
    2989  * @param string $action_name Name of the action this key is being generated for.
    29903008 * @param string $key         Key to confirm.
    29913009 * @param string $uid         Email hash or user ID.
    2992  *
     3010 * @param string $action_name Name of the action this key is being generated for.
    29933011 * @return array|WP_Error WP_Error on failure, action name and user email address on success.
    29943012 */
    2995 function check_confirm_account_action_key( $action_name, $key, $uid ) {
     3013function wp_check_account_verification_key( $key, $uid, $action_name ) {
    29963014    global $wp_hasher;
    29973015
    2998     if ( ! empty( $action_name ) && ! empty( $key ) && ! empty( $uid ) ) {
    2999         $user = false;
    3000 
    3001         if ( is_numeric( $uid ) ) {
    3002             $user = get_user_by( 'id', absint( $uid ) );
    3003         }
    3004 
    3005         // We could be dealing with a registered user account, or a visitor.
    3006         $is_registered_user = $user && ! is_wp_error( $user );
    3007         $key_request_time = '';
    3008         $saved_key        = '';
    3009         $email            = '';
    3010 
    3011         if ( empty( $wp_hasher ) ) {
    3012             require_once ABSPATH . WPINC . '/class-phpass.php';
    3013             $wp_hasher = new PasswordHash( 8, true );
    3014         }
    3015 
    3016         // Get the saved key from the database.
    3017         if ( $is_registered_user ) {
    3018             $confirm_action_data = get_user_meta( $user->ID, '_account_action_' . $action_name, true );
    3019             $email               = $user->user_email;
    3020 
    3021             if ( false !== strpos( $confirm_action_data, ':' ) ) {
    3022                 list( $key_request_time, $saved_key ) = explode( ':', $confirm_action_data, 2 );
    3023             }
    3024         } else {
    3025             $confirm_action_data = get_site_option( '_account_action_' . $uid . '_' . $action_name, '' );
    3026 
    3027             if ( false !== strpos( $confirm_action_data, ':' ) ) {
    3028                 list( $key_request_time, $saved_key, $email ) = explode( ':', $confirm_action_data, 3 );
    3029             }
    3030         }
    3031 
    3032         if ( ! $saved_key ) {
    3033             return new WP_Error( 'invalid_key', __( 'Invalid key' ) );
    3034         }
    3035 
    3036         /**
    3037          * Filters the expiration time of confirm keys.
    3038          *
    3039          * @param int $expiration The expiration time in seconds.
    3040          */
    3041         $expiration_duration = apply_filters( 'account_action_expiration', DAY_IN_SECONDS );
    3042         $expiration_time     = $key_request_time + $expiration_duration;
    3043 
    3044         if ( $wp_hasher->CheckPassword( $key, $saved_key ) ) {
    3045             if ( $expiration_time && time() < $expiration_time ) {
    3046                 $return = array(
    3047                     'action' => $action_name,
    3048                     'email'  => $email,
    3049                 );
    3050             } else {
    3051                 $return = new WP_Error( 'expired_key', __( 'The confirmation email has expired.' ) );
    3052             }
    3053 
    3054             // Clean up stored keys.
    3055             if ( $is_registered_user ) {
    3056                 delete_user_meta( $user->ID, '_account_action_' . $action_name );
    3057             } else {
    3058                 delete_site_option( '_account_action_' . $uid . '_' . $action_name );
    3059             }
    3060 
    3061             return $return;
    3062         }
    3063     }
    3064 
    3065     return new WP_Error( 'invalid_key', __( 'Invalid key' ) );
    3066 }
     3016    if ( empty( $action_name ) || empty( $key ) || empty( $uid ) ) {
     3017        return new WP_Error( 'invalid_key', __( 'Invalid key' ) );
     3018    }
     3019
     3020    $user = false;
     3021
     3022    if ( is_numeric( $uid ) ) {
     3023        $user = get_user_by( 'id', absint( $uid ) );
     3024    }
     3025
     3026    // We could be dealing with a registered user account, or a visitor.
     3027    $is_registered_user = ( $user && ! is_wp_error( $user ) );
     3028    $key_request_time   = '';
     3029    $saved_key          = '';
     3030    $email              = '';
     3031
     3032    if ( empty( $wp_hasher ) ) {
     3033        require_once ABSPATH . WPINC . '/class-phpass.php';
     3034        $wp_hasher = new PasswordHash( 8, true );
     3035    }
     3036
     3037    // Get the saved key from the database.
     3038    if ( $is_registered_user ) {
     3039        $raw_data = get_user_meta( $user->ID, '_verify_action_' . $action_name, true );
     3040        $email    = $user->user_email;
     3041
     3042        if ( false !== strpos( $confirm_action_data, ':' ) ) {
     3043            list( $key_request_time, $saved_key ) = explode( ':', $confirm_action_data, 2 );
     3044        }
     3045    } else {
     3046        $raw_data = get_site_option( '_verify_action_' . $action_name . '_' . $uid, '' );
     3047
     3048        if ( false !== strpos( $confirm_action_data, ':' ) ) {
     3049            list( $key_request_time, $saved_key, $email ) = explode( ':', $confirm_action_data, 3 );
     3050        }
     3051    }
     3052
     3053    $data             = json_decode( $raw_data, true );
     3054    $key_request_time = (int) isset( $data['time'] ) ? $data['time'] : 0;
     3055    $saved_key        = isset( $data['hash'] ) ? $data['hash'] : '';
     3056    $email            = sanitize_email( isset( $data['email'] ) ? $data['email'] : '' );
     3057    $request_data     = isset( $data['request_data'] ) ? $data['request_data'] : array();
     3058
     3059    if ( ! $saved_key ) {
     3060        return new WP_Error( 'invalid_key', __( 'Invalid key' ) );
     3061    }
     3062
     3063    if ( ! $key_request_time || ! $email ) {
     3064        return new WP_Error( 'invalid_key', __( 'Invalid action' ) );
     3065    }
     3066
     3067    /**
     3068     * Filters the expiration time of confirm keys.
     3069     *
     3070     * @since 5.0.0
     3071     *
     3072     * @param int $expiration The expiration time in seconds.
     3073     */
     3074    $expiration_duration = apply_filters( 'account_verification_expiration', DAY_IN_SECONDS );
     3075    $expiration_time     = $key_request_time + $expiration_duration;
     3076
     3077    if ( ! $wp_hasher->CheckPassword( $key, $saved_key ) ) {
     3078        return new WP_Error( 'invalid_key', __( 'Invalid key' ) );
     3079    }
     3080
     3081    if ( $expiration_time && time() < $expiration_time ) {
     3082        $return = array(
     3083            'action'       => $action_name,
     3084            'email'        => $email,
     3085            'request_data' => $request_data,
     3086        );
     3087    } else {
     3088        $return = new WP_Error( 'expired_key', __( 'The confirmation email has expired.' ) );
     3089    }
     3090
     3091    // Clean up stored keys.
     3092    if ( $is_registered_user ) {
     3093        delete_user_meta( $user->ID, '_verify_action_' . $action_name );
     3094    } else {
     3095        delete_site_option( '_verify_action_' . $action_name . '_' . $uid );
     3096    }
     3097
     3098    return $return;
     3099}
  • trunk/src/wp-login.php

    r42892 r42964  
    428428
    429429// validate action so as to default to the login screen
    430 if ( ! in_array( $action, array( 'postpass', 'logout', 'lostpassword', 'retrievepassword', 'resetpass', 'rp', 'register', 'login', 'emailconfirm' ), true ) && false === has_filter( 'login_form_' . $action ) ) {
     430if ( ! in_array( $action, array( 'postpass', 'logout', 'lostpassword', 'retrievepassword', 'resetpass', 'rp', 'register', 'login', 'verifyaccount' ), true ) && false === has_filter( 'login_form_' . $action ) ) {
    431431    $action = 'login';
    432432}
     
    859859        break;
    860860
    861     case 'emailconfirm' :
     861    case 'verifyaccount' :
    862862        if ( isset( $_GET['confirm_action'], $_GET['confirm_key'], $_GET['uid'] ) ) {
    863             $action_name = sanitize_key( wp_unslash( $_GET['confirm_action'] ) );
    864863            $key         = sanitize_text_field( wp_unslash( $_GET['confirm_key'] ) );
    865864            $uid         = sanitize_text_field( wp_unslash( $_GET['uid'] ) );
    866             $result      = check_confirm_account_action_key( $action_name, $key, $uid );
     865            $action_name = sanitize_key( wp_unslash( $_GET['confirm_action'] ) );
     866            $result      = wp_check_account_verification_key( $key, $uid, $action_name );
    867867        } else {
    868868            $result = new WP_Error( 'invalid_key', __( 'Invalid key' ) );
Note: See TracChangeset for help on using the changeset viewer.