Make WordPress Core

Changeset 43070


Ignore:
Timestamp:
05/01/2018 11:36:37 PM (8 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.