Make WordPress Core

Changeset 43070


Ignore:
Timestamp:
05/01/2018 11:36:37 PM (6 years ago)
Author:
SergeyBiryukov
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.
Merges [42964] to the 4.9 branch.
See #43443.

Location:
branches/4.9
Files:
3 edited

Legend:

Unmodified
Added
Removed
  • branches/4.9

  • branches/4.9/src/wp-includes/user.php

    r43069 r43070  
    27382738 * @since 5.0.0
    27392739 *
    2740  * @param string $action_name        Name of the action that is being confirmed.
    2741  * @param string $action_description User facing description of the action they will be confirming.
    2742  * @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.
    2743  *
    2744  * @return WP_ERROR|bool Will return true/false based on the success of sending the email, or a WP_Error object.
    2745  */
    2746 function send_confirm_account_action_email( $action_name, $action_description = '', $email = '' ) {
     2740 * @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.
     2741 * @param string $action_name        Name of the action that is being confirmed. Defaults to 'confirm_email'.
     2742 * @param string $action_description User facing description of the action they will be confirming. Defaults to "confirm your email address".
     2743 * @param array  $request_data       Misc data you want to send with the verification request and pass to the actions once the request is confirmed.
     2744 * @return WP_Error|bool Will return true/false based on the success of sending the email, or a WP_Error object.
     2745 */
     2746function wp_send_account_verification_key( $email = '', $action_name = '', $action_description = '', $request_data = array() ) {
     2747    if ( ! function_exists( 'wp_get_current_user' ) ) {
     2748        return new WP_Error( 'invalid', __( 'This function cannot be used before init.' ) );
     2749    }
     2750
    27472751    $action_name        = sanitize_key( $action_name );
    27482752    $action_description = wp_kses_post( $action_description );
    27492753
    27502754    if ( empty( $action_name ) ) {
    2751         return new WP_Error( 'invalid_action', __( 'Invalid action' ) );
     2755        $action_name = 'confirm_email';
     2756    }
     2757
     2758    if ( empty( $action_description ) ) {
     2759        $action_description = __( 'Confirm your email address.' );
    27522760    }
    27532761
     
    27692777    }
    27702778
     2779    $confirm_key = wp_get_account_verification_key( $email, $action_name, $request_data );
     2780
     2781    if ( is_wp_error( $confirm_key ) ) {
     2782        return $confirm_key;
     2783    }
     2784
    27712785    // We could be dealing with a registered user account, or a visitor.
    27722786    $is_registered_user = $user && ! is_wp_error( $user );
    2773     $uid                = $is_registered_user ? $user->ID : hash( 'sha256', $email );
    2774     $confirm_key        = get_confirm_account_action_key( $action_name, $email );
    2775 
    2776     if ( is_wp_error( $confirm_key ) ) {
    2777         return $confirm_key;
    2778     }
    2779 
    2780     // Prepare the email content.
    2781     if ( ! $action_description ) {
    2782         $action_description = $action_name;
     2787
     2788    if ( $is_registered_user ) {
     2789        $uid = $user->ID;
     2790    } else {
     2791        // 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.
     2792        $uid = function_exists( 'hash' ) ? hash( 'sha256', $email ) : sha1( $email );
    27832793    }
    27842794
     
    27872797        'Howdy,
    27882798
    2789 An account linked to your email address has requested to perform
    2790 the following action:
     2799A request has been made to perform the following action on your account:
    27912800
    27922801     ###DESCRIPTION###
    27932802
    2794 To confirm this action, please click on the following link:
     2803To confirm this, please click on the following link:
    27952804###CONFIRM_URL###
    27962805
     
    28102819        'description' => $action_description,
    28112820        'confirm_url' => add_query_arg( array(
    2812             'action'         => 'emailconfirm',
     2821            'action'         => 'verifyaccount',
    28132822            'confirm_action' => $action_name,
    28142823            'uid'            => $uid,
     
    28302839     * ###SITEURL###            The URL to the site.
    28312840     *
     2841     * @since 5.0.0
     2842     *
    28322843     * @param string $email_text     Text in the email.
    28332844     * @param array  $email_data {
     
    28422853     * }
    28432854     */
    2844     $content = apply_filters( 'confirm_account_action_email_content', $email_text, $email_data );
     2855    $content = apply_filters( 'account_verification_email_content', $email_text, $email_data );
    28452856
    28462857    $content = str_replace( '###DESCRIPTION###', $email_data['description'], $content );
     
    28512862
    28522863    /* translators: %s Site name. */
    2853     return wp_mail( $email_data['email'], sprintf( __( '[%s] Confirm Account Action' ), wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES ) ), $content );
     2864    return wp_mail( $email_data['email'], sprintf( __( '[%s] Confirm Action' ), wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES ) ), $content );
    28542865}
    28552866
     
    28592870 * @since 5.0.0
    28602871 *
    2861  * @param string $action_name Name of the action this key is being generated for.
    2862  * @param string $email       User email address. This can be the address of a registered or non-registered user.
    2863  *
     2872 * @param string $email        User email address. This can be the address of a registered or non-registered user.
     2873 * @param string $action_name  Name of the action this key is being generated for.
     2874 * @param array  $request_data Misc data you want to send with the verification request and pass to the actions once the request is confirmed.
    28642875 * @return string|WP_Error Confirmation key on success. WP_Error on error.
    28652876 */
    2866 function get_confirm_account_action_key( $action_name, $email ) {
     2877function wp_get_account_verification_key( $email, $action_name, $request_data = array() ) {
    28672878    global $wp_hasher;
    28682879
     
    28902901
    28912902    $hashed_key = $wp_hasher->HashPassword( $key );
     2903    $value      = array(
     2904        'action'       => $action_name,
     2905        'time'         => time(),
     2906        'hash'         => $hashed_key,
     2907        'email'        => $email,
     2908        'request_data' => $request_data,
     2909    );
    28922910
    28932911    if ( $is_registered_user ) {
    2894         $key_saved = (bool) update_user_meta( $user->ID, '_account_action_' . $action_name, implode( ':', array( time(), $hashed_key ) ) );
     2912        $key_saved = (bool) update_user_meta( $user->ID, '_verify_action_' . $action_name, wp_json_encode( $value ) );
    28952913    } else {
    2896         $key_saved = (bool) update_site_option( '_account_action_' . hash( 'sha256', $email ) . '_' . $action_name, implode( ':', array( time(), $hashed_key, $email ) ) );
     2914        $uid       = function_exists( 'hash' ) ? hash( 'sha256', $email ) : sha1( $email );
     2915        $key_saved = (bool) update_site_option( '_verify_action_' . $action_name . '_' . $uid, wp_json_encode( $value ) );
    28972916    }
    28982917
    28992918    if ( false === $key_saved ) {
    2900         return new WP_Error( 'no_confirm_account_action_key_update', __( 'Could not save confirm account action key to database.' ) );
     2919        return new WP_Error( 'no_account_verification_key_update', __( 'Could not save confirm account action key to database.' ) );
    29012920    }
    29022921
     
    29092928 * @since 5.0.0
    29102929 *
    2911  * @param string $action_name Name of the action this key is being generated for.
    29122930 * @param string $key         Key to confirm.
    29132931 * @param string $uid         Email hash or user ID.
    2914  *
     2932 * @param string $action_name Name of the action this key is being generated for.
    29152933 * @return array|WP_Error WP_Error on failure, action name and user email address on success.
    29162934 */
    2917 function check_confirm_account_action_key( $action_name, $key, $uid ) {
     2935function wp_check_account_verification_key( $key, $uid, $action_name ) {
    29182936    global $wp_hasher;
    29192937
    2920     if ( ! empty( $action_name ) && ! empty( $key ) && ! empty( $uid ) ) {
    2921         $user = false;
    2922 
    2923         if ( is_numeric( $uid ) ) {
    2924             $user = get_user_by( 'id', absint( $uid ) );
    2925         }
    2926 
    2927         // We could be dealing with a registered user account, or a visitor.
    2928         $is_registered_user = $user && ! is_wp_error( $user );
    2929         $key_request_time = '';
    2930         $saved_key        = '';
    2931         $email            = '';
    2932 
    2933         if ( empty( $wp_hasher ) ) {
    2934             require_once ABSPATH . WPINC . '/class-phpass.php';
    2935             $wp_hasher = new PasswordHash( 8, true );
    2936         }
    2937 
    2938         // Get the saved key from the database.
    2939         if ( $is_registered_user ) {
    2940             $confirm_action_data = get_user_meta( $user->ID, '_account_action_' . $action_name, true );
    2941             $email               = $user->user_email;
    2942 
    2943             if ( false !== strpos( $confirm_action_data, ':' ) ) {
    2944                 list( $key_request_time, $saved_key ) = explode( ':', $confirm_action_data, 2 );
    2945             }
    2946         } else {
    2947             $confirm_action_data = get_site_option( '_account_action_' . $uid . '_' . $action_name, '' );
    2948 
    2949             if ( false !== strpos( $confirm_action_data, ':' ) ) {
    2950                 list( $key_request_time, $saved_key, $email ) = explode( ':', $confirm_action_data, 3 );
    2951             }
    2952         }
    2953 
    2954         if ( ! $saved_key ) {
    2955             return new WP_Error( 'invalid_key', __( 'Invalid key' ) );
    2956         }
    2957 
    2958         /**
    2959          * Filters the expiration time of confirm keys.
    2960          *
    2961          * @param int $expiration The expiration time in seconds.
    2962          */
    2963         $expiration_duration = apply_filters( 'account_action_expiration', DAY_IN_SECONDS );
    2964         $expiration_time     = $key_request_time + $expiration_duration;
    2965 
    2966         if ( $wp_hasher->CheckPassword( $key, $saved_key ) ) {
    2967             if ( $expiration_time && time() < $expiration_time ) {
    2968                 $return = array(
    2969                     'action' => $action_name,
    2970                     'email'  => $email,
    2971                 );
    2972             } else {
    2973                 $return = new WP_Error( 'expired_key', __( 'The confirmation email has expired.' ) );
    2974             }
    2975 
    2976             // Clean up stored keys.
    2977             if ( $is_registered_user ) {
    2978                 delete_user_meta( $user->ID, '_account_action_' . $action_name );
    2979             } else {
    2980                 delete_site_option( '_account_action_' . $uid . '_' . $action_name );
    2981             }
    2982 
    2983             return $return;
    2984         }
    2985     }
    2986 
    2987     return new WP_Error( 'invalid_key', __( 'Invalid key' ) );
    2988 }
     2938    if ( empty( $action_name ) || empty( $key ) || empty( $uid ) ) {
     2939        return new WP_Error( 'invalid_key', __( 'Invalid key' ) );
     2940    }
     2941
     2942    $user = false;
     2943
     2944    if ( is_numeric( $uid ) ) {
     2945        $user = get_user_by( 'id', absint( $uid ) );
     2946    }
     2947
     2948    // We could be dealing with a registered user account, or a visitor.
     2949    $is_registered_user = ( $user && ! is_wp_error( $user ) );
     2950    $key_request_time   = '';
     2951    $saved_key          = '';
     2952    $email              = '';
     2953
     2954    if ( empty( $wp_hasher ) ) {
     2955        require_once ABSPATH . WPINC . '/class-phpass.php';
     2956        $wp_hasher = new PasswordHash( 8, true );
     2957    }
     2958
     2959    // Get the saved key from the database.
     2960    if ( $is_registered_user ) {
     2961        $raw_data = get_user_meta( $user->ID, '_verify_action_' . $action_name, true );
     2962        $email    = $user->user_email;
     2963
     2964        if ( false !== strpos( $confirm_action_data, ':' ) ) {
     2965            list( $key_request_time, $saved_key ) = explode( ':', $confirm_action_data, 2 );
     2966        }
     2967    } else {
     2968        $raw_data = get_site_option( '_verify_action_' . $action_name . '_' . $uid, '' );
     2969
     2970        if ( false !== strpos( $confirm_action_data, ':' ) ) {
     2971            list( $key_request_time, $saved_key, $email ) = explode( ':', $confirm_action_data, 3 );
     2972        }
     2973    }
     2974
     2975    $data             = json_decode( $raw_data, true );
     2976    $key_request_time = (int) isset( $data['time'] ) ? $data['time'] : 0;
     2977    $saved_key        = isset( $data['hash'] ) ? $data['hash'] : '';
     2978    $email            = sanitize_email( isset( $data['email'] ) ? $data['email'] : '' );
     2979    $request_data     = isset( $data['request_data'] ) ? $data['request_data'] : array();
     2980
     2981    if ( ! $saved_key ) {
     2982        return new WP_Error( 'invalid_key', __( 'Invalid key' ) );
     2983    }
     2984
     2985    if ( ! $key_request_time || ! $email ) {
     2986        return new WP_Error( 'invalid_key', __( 'Invalid action' ) );
     2987    }
     2988
     2989    /**
     2990     * Filters the expiration time of confirm keys.
     2991     *
     2992     * @since 5.0.0
     2993     *
     2994     * @param int $expiration The expiration time in seconds.
     2995     */
     2996    $expiration_duration = apply_filters( 'account_verification_expiration', DAY_IN_SECONDS );
     2997    $expiration_time     = $key_request_time + $expiration_duration;
     2998
     2999    if ( ! $wp_hasher->CheckPassword( $key, $saved_key ) ) {
     3000        return new WP_Error( 'invalid_key', __( 'Invalid key' ) );
     3001    }
     3002
     3003    if ( $expiration_time && time() < $expiration_time ) {
     3004        $return = array(
     3005            'action'       => $action_name,
     3006            'email'        => $email,
     3007            'request_data' => $request_data,
     3008        );
     3009    } else {
     3010        $return = new WP_Error( 'expired_key', __( 'The confirmation email has expired.' ) );
     3011    }
     3012
     3013    // Clean up stored keys.
     3014    if ( $is_registered_user ) {
     3015        delete_user_meta( $user->ID, '_verify_action_' . $action_name );
     3016    } else {
     3017        delete_site_option( '_verify_action_' . $action_name . '_' . $uid );
     3018    }
     3019
     3020    return $return;
     3021}
  • branches/4.9/src/wp-login.php

    r43069 r43070  
    414414
    415415// validate action so as to default to the login screen
    416 if ( !in_array( $action, array( 'postpass', 'logout', 'lostpassword', 'retrievepassword', 'resetpass', 'rp', 'register', 'login', 'emailconfirm' ), true ) && false === has_filter( 'login_form_' . $action ) )
     416if ( !in_array( $action, array( 'postpass', 'logout', 'lostpassword', 'retrievepassword', 'resetpass', 'rp', 'register', 'login', 'verifyaccount' ), true ) && false === has_filter( 'login_form_' . $action ) )
    417417    $action = 'login';
    418418
     
    839839break;
    840840
    841 case 'emailconfirm' :
     841case 'verifyaccount' :
    842842    if ( isset( $_GET['confirm_action'], $_GET['confirm_key'], $_GET['uid'] ) ) {
    843         $action_name = sanitize_key( wp_unslash( $_GET['confirm_action'] ) );
    844843        $key         = sanitize_text_field( wp_unslash( $_GET['confirm_key'] ) );
    845844        $uid         = sanitize_text_field( wp_unslash( $_GET['uid'] ) );
    846         $result      = check_confirm_account_action_key( $action_name, $key, $uid );
     845        $action_name = sanitize_key( wp_unslash( $_GET['confirm_action'] ) );
     846        $result      = wp_check_account_verification_key( $key, $uid, $action_name );
    847847    } else {
    848848        $result = new WP_Error( 'invalid_key', __( 'Invalid key' ) );
Note: See TracChangeset for help on using the changeset viewer.