From b98f2a6edf949b476a1bb8b48f63f07fd1c296f3 Mon Sep 17 00:00:00 2001 From: Nikita Sinelnikov Date: Thu, 11 Aug 2022 21:49:19 +0300 Subject: [PATCH] - fixed issue with visible ID. Changed it to the user_login. It's WordPress native logic for reset password links and form; --- includes/core/class-password.php | 129 ++++++++++++--------------- includes/core/class-user.php | 12 ++- includes/core/um-actions-account.php | 8 +- templates/password-change.php | 6 +- templates/password-reset.php | 10 ++- 5 files changed, 84 insertions(+), 81 deletions(-) diff --git a/includes/core/class-password.php b/includes/core/class-password.php index dbb168d2..9b18caa7 100644 --- a/includes/core/class-password.php +++ b/includes/core/class-password.php @@ -42,11 +42,19 @@ if ( ! class_exists( 'um\core\Password' ) ) { delete_option( "um_cache_userdata_{$user_id}" ); - //new reset password key via WP native field + //new reset password key via WordPress native field. It maybe already exists here but generated twice to make sure that emailed with a proper and fresh hash $user_data = get_userdata( $user_id ); - $key = UM()->user()->maybe_generate_password_reset_key( $user_data ); + $key = UM()->user()->maybe_generate_password_reset_key( $user_data ); - $url = add_query_arg( array( 'act' => 'reset_password', 'hash' => $key, 'user_id' => $user_id ), um_get_core_page( 'password-reset' ) ); + // this link looks like WordPress native link e.g. wp-login.php?action=rp&key={hash}&login={user_login} + $url = add_query_arg( + array( + 'act' => 'reset_password', + 'hash' => $key, + 'login' => $user_data->user_login, + ), + um_get_core_page( 'password-reset' ) + ); return $url; } @@ -149,14 +157,14 @@ if ( ! class_exists( 'um\core\Password' ) ) { $args = apply_filters( 'um_reset_password_shortcode_args_filter', $args ); if ( isset( $this->change_password ) ) { + // then COOKIE are valid then get data from them and populate hidden fields for the password reset form $args['template'] = 'password-change'; - $args['rp_key'] = ''; + $args['rp_key'] = ''; $rp_cookie = 'wp-resetpass-' . COOKIEHASH; if ( isset( $_COOKIE[ $rp_cookie ] ) && 0 < strpos( $_COOKIE[ $rp_cookie ], ':' ) ) { list( $rp_login, $rp_key ) = explode( ':', wp_unslash( $_COOKIE[ $rp_cookie ] ), 2 ); - $user = get_user_by( 'login', $rp_login ); - $args['user_id'] = $user->ID; + $args['login'] = $rp_login; $args['rp_key'] = $rp_key; } } @@ -258,6 +266,7 @@ if ( ! class_exists( 'um\core\Password' ) ) { /** * Check if a legitimate password change request is in action * + * works both for the Account > Password form and the Reset Password shortcode form * * @return bool */ @@ -280,56 +289,47 @@ if ( ! class_exists( 'um\core\Password' ) ) { UM()->fields()->set_mode = 'password'; } + // validate $rp_cookie and hash via check_password_reset_key if ( um_is_core_page( 'password-reset' ) && isset( $_REQUEST['act'] ) && 'reset_password' === sanitize_key( $_REQUEST['act'] ) ) { wp_fix_server_vars(); $rp_cookie = 'wp-resetpass-' . COOKIEHASH; - if ( isset( $_GET['hash'] ) ) { - $userdata = get_userdata( wp_unslash( absint( $_GET['user_id'] ) ) ); - if ( ! $userdata || is_wp_error( $userdata ) ) { - wp_redirect( add_query_arg( array( 'act' => 'reset_password', 'error' => 'invalidkey' ), get_permalink() ) ); - exit; - } - $rp_login = $userdata->user_login; - $rp_key = wp_unslash( sanitize_text_field( $_GET['hash'] ) ); - - $user = check_password_reset_key( $rp_key, $rp_login ); - - if ( is_wp_error( $user ) ) { - $this->setcookie( $rp_cookie, false ); - wp_redirect( add_query_arg( array( 'updated' => 'invalidkey' ), get_permalink() ) ); - } else { - $value = sprintf( '%s:%s', $rp_login, wp_unslash( sanitize_text_field( $_GET['hash'] ) ) ); - $this->setcookie( $rp_cookie, $value ); - wp_safe_redirect( remove_query_arg( array( 'hash', 'user_id' ) ) ); - } + if ( isset( $_GET['hash'] ) && isset( $_GET['login'] ) ) { + $value = sprintf( '%s:%s', wp_unslash( $_GET['login'] ), wp_unslash( $_GET['hash'] ) ); + $this->setcookie( $rp_cookie, $value ); + wp_safe_redirect( remove_query_arg( array( 'hash', 'login' ) ) ); exit; } if ( isset( $_COOKIE[ $rp_cookie ] ) && 0 < strpos( $_COOKIE[ $rp_cookie ], ':' ) ) { list( $rp_login, $rp_key ) = explode( ':', wp_unslash( $_COOKIE[ $rp_cookie ] ), 2 ); + $user = check_password_reset_key( $rp_key, $rp_login ); + + if ( isset( $_POST['user_password'] ) && ! hash_equals( $rp_key, $_POST['rp_key'] ) ) { + $user = false; + } } else { $user = false; } - if ( ( ! $user || is_wp_error( $user ) ) && ! isset( $_GET['updated'] ) ) { + if ( ! $user || is_wp_error( $user ) ) { $this->setcookie( $rp_cookie, false ); - if ( $user && $user->get_error_code() === 'expired_key' ) { - wp_redirect( add_query_arg( array( 'updated' => 'expiredkey' ), get_permalink() ) ); + if ( $user && 'expired_key' === $user->get_error_code() ) { + wp_redirect( add_query_arg( array( 'updated' => 'expiredkey' ), um_get_core_page( 'password-reset' ) ) ); } else { - wp_redirect( add_query_arg( array( 'updated' => 'invalidkey' ), get_permalink() ) ); + wp_redirect( add_query_arg( array( 'updated' => 'invalidkey' ), um_get_core_page( 'password-reset' ) ) ); } exit; } + // this variable is used for populating the reset password form via the hash and login $this->change_password = true; } if ( $this->is_reset_request() ) { - UM()->form()->post_form = $_POST; if ( empty( UM()->form()->post_form['mode'] ) ) { @@ -358,7 +358,6 @@ if ( ! class_exists( 'um\core\Password' ) ) { do_action( 'um_reset_password_errors_hook', UM()->form()->post_form ); if ( ! isset( UM()->form()->errors ) ) { - /** * UM hook * @@ -379,7 +378,6 @@ if ( ! class_exists( 'um\core\Password' ) ) { * ?> */ do_action( 'um_reset_password_process_hook', UM()->form()->post_form ); - } } @@ -408,7 +406,6 @@ if ( ! class_exists( 'um\core\Password' ) ) { do_action( 'um_change_password_errors_hook', UM()->form()->post_form ); if ( ! isset( UM()->form()->errors ) ) { - /** * UM hook * @@ -429,7 +426,6 @@ if ( ! class_exists( 'um\core\Password' ) ) { * ?> */ do_action( 'um_change_password_process_hook', UM()->form()->post_form ); - } } } @@ -446,7 +442,6 @@ if ( ! class_exists( 'um\core\Password' ) ) { } $user = ''; - foreach ( $args as $key => $val ) { if ( strstr( $key, 'username_b' ) ) { $user = trim( sanitize_text_field( $val ) ); @@ -468,7 +463,6 @@ if ( ! class_exists( 'um\core\Password' ) ) { $is_admin = user_can( absint( $user_id ), 'manage_options' ); if ( UM()->options()->get( 'enable_reset_password_limit' ) ) { // if reset password limit is set - if ( ! ( UM()->options()->get( 'disable_admin_reset_password_limit' ) && $is_admin ) ) { // Doesn't trigger this when a user has admin capabilities and when reset password limit is disabled for admins $limit = UM()->options()->get( 'reset_password_limit_number' ); @@ -480,7 +474,6 @@ if ( ! class_exists( 'um\core\Password' ) ) { } } } - } @@ -509,13 +502,16 @@ if ( ! class_exists( 'um\core\Password' ) ) { UM()->user()->password_reset(); } - exit( wp_redirect( um_get_core_page('password-reset', 'checkemail' ) ) ); + wp_redirect( um_get_core_page('password-reset', 'checkemail' ) ); + exit; } /** * Error handler: changing password * + * It works both for the Reset Password Shortcode and Account > Change password form + * * @param $args */ public function um_change_password_errors_hook( $args ) { @@ -523,9 +519,11 @@ if ( ! class_exists( 'um\core\Password' ) ) { wp_die( esc_html__( 'Hello, spam bot!', 'ultimate-member' ) ); } - if ( ! is_user_logged_in() && isset( $args ) && ! um_is_core_page( 'password-reset' ) || - is_user_logged_in() && isset( $args['user_id'] ) && absint( $args['user_id'] ) !== get_current_user_id() ) { - wp_die( esc_html__( 'This is not possible for security reasons.', 'ultimate-member' ) ); + if ( isset( $args['_um_account'] ) == 1 && isset( $args['_um_account_tab'] ) && 'password' === sanitize_key( $args['_um_account_tab'] ) ) { + // validate for security on the account change password page + if ( ! is_user_logged_in() ) { + wp_die( esc_html__( 'This is not possible for security reasons.', 'ultimate-member' ) ); + } } if ( isset( $args['user_password'] ) && empty( $args['user_password'] ) ) { @@ -535,6 +533,7 @@ if ( ! class_exists( 'um\core\Password' ) ) { if ( isset( $args['user_password'] ) ) { $args['user_password'] = trim( $args['user_password'] ); } + if ( isset( $args['confirm_user_password'] ) ) { $args['confirm_user_password'] = trim( $args['confirm_user_password'] ); } @@ -545,7 +544,6 @@ if ( ! class_exists( 'um\core\Password' ) ) { } if ( UM()->options()->get( 'require_strongpass' ) ) { - $min_length = UM()->options()->get( 'password_min_chars' ); $min_length = ! empty( $min_length ) ? $min_length : 8; $max_length = UM()->options()->get( 'password_max_chars' ); @@ -571,7 +569,6 @@ if ( ! class_exists( 'um\core\Password' ) ) { if ( isset( $args['user_password'] ) && isset( $args['confirm_user_password'] ) && $args['user_password'] !== $args['confirm_user_password'] ) { UM()->form()->add_error( 'confirm_user_password', __( 'Your passwords do not match', 'ultimate-member' ) ); } - } @@ -582,20 +579,16 @@ if ( ! class_exists( 'um\core\Password' ) ) { */ public function um_change_password_process_hook( $args ) { if ( isset( $args['_um_password_change'] ) && $args['_um_password_change'] == 1 ) { - + // it only works on the Password Reset Shortcode form $rp_cookie = 'wp-resetpass-' . COOKIEHASH; - $user = get_userdata( absint( $args['user_id'] ) ); if ( isset( $_COOKIE[ $rp_cookie ] ) && 0 < strpos( $_COOKIE[ $rp_cookie ], ':' ) ) { list( $rp_login, $rp_key ) = explode( ':', wp_unslash( $_COOKIE[ $rp_cookie ] ), 2 ); - if ( $user->user_login != $rp_login ) { + $user = check_password_reset_key( $rp_key, $rp_login ); + + if ( isset( $args['user_password'] ) && ! hash_equals( $rp_key, $args['rp_key'] ) ) { $user = false; - } else { - $user = check_password_reset_key( $rp_key, $rp_login ); - if ( isset( $args['user_password'] ) && ! hash_equals( $rp_key, $args['rp_key'] ) ) { - $user = false; - } } } else { $user = false; @@ -603,31 +596,24 @@ if ( ! class_exists( 'um\core\Password' ) ) { if ( ! $user || is_wp_error( $user ) ) { $this->setcookie( $rp_cookie, false ); - if ( $user && $user->get_error_code() === 'expired_key' ) { - wp_redirect( add_query_arg( array( 'updated' => 'expiredkey' ), get_permalink() ) ); + if ( $user && 'expired_key' === $user->get_error_code() ) { + wp_redirect( add_query_arg( array( 'updated' => 'expiredkey' ), um_get_core_page( 'password-reset' ) ) ); } else { - wp_redirect( add_query_arg( array( 'updated' => 'invalidkey' ), get_permalink() ) ); + wp_redirect( add_query_arg( array( 'updated' => 'invalidkey' ), um_get_core_page( 'password-reset' ) ) ); } exit; } - $errors = new \WP_Error(); - /** - * Fires before the password reset procedure is validated. - * - * @since 3.5.0 - * - * @param object $errors WP Error object. - * @param \WP_User|\WP_Error $user WP_User object if the login and reset key match. WP_Error object otherwise. - */ + + /** This action is documented in wp-login.php */ do_action( 'validate_password_reset', $errors, $user ); if ( ( ! $errors->get_error_code() ) ) { reset_password( $user, trim( $args['user_password'] ) ); // send the Password Changed Email - UM()->user()->password_changed(); + UM()->user()->password_changed( $user->ID ); // clear temporary data $attempts = (int) get_user_meta( $user->ID, 'password_rst_attempts', true ); @@ -636,11 +622,6 @@ if ( ! class_exists( 'um\core\Password' ) ) { } $this->setcookie( $rp_cookie, false ); - // logout - if ( is_user_logged_in() ) { - wp_logout(); - } - /** * UM hook * @@ -660,9 +641,15 @@ if ( ! class_exists( 'um\core\Password' ) ) { * } * ?> */ - do_action( 'um_after_changing_user_password', absint( $args['user_id'] ) ); + do_action( 'um_after_changing_user_password', $user->ID ); - exit( wp_redirect( um_get_core_page( 'login', 'password_changed' ) ) ); + if ( ! is_user_logged_in() ) { + $url = um_get_core_page( 'login', 'password_changed' ); + } else { + $url = um_get_core_page( 'password-reset', 'password_changed' ); + } + wp_redirect( $url ); + exit; } } } diff --git a/includes/core/class-user.php b/includes/core/class-user.php index 069be147..46e89d90 100644 --- a/includes/core/class-user.php +++ b/includes/core/class-user.php @@ -1409,9 +1409,19 @@ if ( ! class_exists( 'um\core\User' ) ) { /** * Password changed email + * + * @param null|int $user_id */ - function password_changed() { + function password_changed( $user_id = null ) { + if ( ! empty( $user_id ) ) { + um_fetch_user( $user_id ); + } + UM()->mail()->send( um_user( 'user_email' ), 'changedpw_email' ); + + if ( ! empty( $user_id ) ) { + um_reset_user(); + } } diff --git a/includes/core/um-actions-account.php b/includes/core/um-actions-account.php index c4324e74..723663a5 100644 --- a/includes/core/um-actions-account.php +++ b/includes/core/um-actions-account.php @@ -209,20 +209,18 @@ function um_submit_account_details( $args ) { //change password account's tab if ( 'password' === $current_tab && $args['user_password'] && $args['confirm_user_password'] ) { - $changes['user_pass'] = trim( $args['user_password'] ); - - $args['user_id'] = $user_id; + $args['user_id'] = get_current_user_id(); UM()->user()->password_changed(); add_filter( 'send_password_change_email', '__return_false' ); //clear all sessions with old passwords - $user = WP_Session_Tokens::get_instance( $user_id ); + $user = WP_Session_Tokens::get_instance( $args['user_id'] ); $user->destroy_all(); - wp_set_password( $changes['user_pass'], $user_id ); + wp_set_password( $changes['user_pass'], $args['user_id'] ); do_action( 'um_before_signon_after_account_changes', $args ); diff --git a/templates/password-change.php b/templates/password-change.php index 7b9101e2..414cb6e0 100644 --- a/templates/password-change.php +++ b/templates/password-change.php @@ -6,7 +6,7 @@
- +
- +
@@ -96,4 +96,4 @@ do_action( 'um_after_form_fields', $args ); ?>
- \ No newline at end of file + diff --git a/templates/password-reset.php b/templates/password-reset.php index 384da0be..9b706317 100644 --- a/templates/password-reset.php +++ b/templates/password-reset.php @@ -13,6 +13,14 @@ + +
+
+
+ +
+
+
@@ -93,7 +101,7 @@
- +