| @@ -8,8 +8,10 @@ | ||
| 8 | 8 | * @license https://opensource.org/licenses/gpl-license GNU Public License |
| 9 | 9 | * @since 1.0 |
| 10 | 10 | */ |
| 11 | 11 | |
| 12 | +use Give\Helpers\Form\Utils as FormUtils; | |
| 13 | +use Give\Helpers\Frontend\Shortcode as ShortcodeUtils; | |
| 12 | 14 | use Give\Helpers\Utils; |
| 13 | 15 | |
| 14 | 16 | // Exit if accessed directly. |
| 15 | 17 | if ( ! defined( 'ABSPATH' ) ) { |
| @@ -21,9 +23,11 @@ | ||
| 21 | 23 | * |
| 22 | 24 | * Handles the donation form process. |
| 23 | 25 | * |
| 24 | 26 | * @access private |
| 25 | - * @since 3.16.1 Use give_maybe_safe_unserialize() on $user_info data | |
| 27 | + * @since 4.16.7.2 Reject serialized data in name fields before storing donation data. | |
| 28 | + * @since 4.16.6 Bail early when the form ID is not a give_forms post or is a Visual Form Builder (v3) form. | |
| 29 | + * @since 3.16.1 Use give_maybe_safe_unserialize() on $user_info data | |
| 26 | 30 | * @since 1.0 |
| 27 | 31 | * |
| 28 | 32 | * @throws ReflectionException Exception Handling. |
| 29 | 33 | * |
| @@ -51,8 +55,48 @@ | ||
| 51 | 55 | give_send_back_to_checkout(); |
| 52 | 56 | } |
| 53 | 57 | } |
| 54 | 58 | |
| 59 | + $form_id = isset( $post_data['give-form-id'] ) ? absint( $post_data['give-form-id'] ) : 0; | |
| 60 | + | |
| 61 | + if ( ! ShortcodeUtils::isValidForm( $form_id ) ) { | |
| 62 | + give_set_error( | |
| 63 | + 'give_invalid_donation_form', | |
| 64 | + __( 'The donation form ID is invalid. Please reload the page and try again.', 'give' ) | |
| 65 | + ); | |
| 66 | + | |
| 67 | + if ( $is_ajax ) { | |
| 68 | + /** This action is documented in this file (see give_ajax_donation_errors above). */ | |
| 69 | + do_action( 'give_ajax_donation_errors' ); | |
| 70 | + give_die(); | |
| 71 | + return; | |
| 72 | + } | |
| 73 | + | |
| 74 | + give_send_back_to_checkout(); | |
| 75 | + | |
| 76 | + return false; | |
| 77 | + } | |
| 78 | + | |
| 79 | + // Visual Form Builder (v3) forms are processed through the givewp-donate route, | |
| 80 | + // so bail out when the legacy donation processor receives one. | |
| 81 | + if ( FormUtils::isV3Form( $form_id ) ) { | |
| 82 | + give_set_error( | |
| 83 | + 'give_unsupported_form_version', | |
| 84 | + __( 'This donation form cannot be processed through this endpoint. Please reload the page and try again.', 'give' ) | |
| 85 | + ); | |
| 86 | + | |
| 87 | + if ( $is_ajax ) { | |
| 88 | + /** This action is documented in this file (see give_ajax_donation_errors above). */ | |
| 89 | + do_action( 'give_ajax_donation_errors' ); | |
| 90 | + give_die(); | |
| 91 | + return; | |
| 92 | + } | |
| 93 | + | |
| 94 | + give_send_back_to_checkout(); | |
| 95 | + | |
| 96 | + return false; | |
| 97 | + } | |
| 98 | + | |
| 55 | 99 | /** |
| 56 | 100 | * Fires before processing the donation form. |
| 57 | 101 | * |
| 58 | 102 | * @since 1.0 |
| @@ -119,8 +163,21 @@ | ||
| 119 | 163 | 'last_name' => $user['user_last'], |
| 120 | 164 | 'address' => $user['address'], |
| 121 | 165 | ]; |
| 122 | 166 | |
| 167 | + // Reject serialized data in name fields. | |
| 168 | + $serialized_keys = array_filter( | |
| 169 | + $user_info, | |
| 170 | + static function ( $value ) { | |
| 171 | + return is_string( $value ) && \Give\Helpers\Utils::isSerialized( $value ); | |
| 172 | + } | |
| 173 | + ); | |
| 174 | + | |
| 175 | + if ( ! empty( $serialized_keys ) ) { | |
| 176 | + give_set_error( 'give_serialized_user_info', esc_html__( 'Name fields cannot contain serialized data.', 'give' ) ); | |
| 177 | + return; | |
| 178 | + } | |
| 179 | + | |
| 123 | 180 | $auth_key = defined( 'AUTH_KEY' ) ? AUTH_KEY : ''; |
| 124 | 181 | |
| 125 | 182 | // Donation form ID. |
| 126 | 183 | $form_id = isset( $post_data['give-form-id'] ) ? absint( $post_data['give-form-id'] ) : 0; |
| @@ -153,9 +210,9 @@ | ||
| 153 | 210 | $purchase_key |
| 154 | 211 | ); |
| 155 | 212 | |
| 156 | 213 | // Setup donation information. |
| 157 | - $user_info = array_map('\Give\Helpers\Utils::maybeSafeUnserialize', stripslashes_deep( $user_info )); | |
| 214 | + $user_info = stripslashes_deep( $user_info ); | |
| 158 | 215 | $donation_data = [ |
| 159 | 216 | 'price' => $price, |
| 160 | 217 | 'purchase_key' => $purchase_key, |
| 161 | 218 | 'user_email' => $user['user_email'], |
| @@ -280,8 +337,9 @@ | ||
| 280 | 337 | /** |
| 281 | 338 | * Process the checkout login form |
| 282 | 339 | * |
| 283 | 340 | * @access private |
| 341 | + * @since 4.16.7 Require a valid nonce before processing the login form. | |
| 284 | 342 | * @since 1.0 |
| 285 | 343 | * |
| 286 | 344 | * @return void |
| 287 | 345 | */ |
| @@ -286,12 +344,23 @@ | ||
| 286 | 344 | * @return void |
| 287 | 345 | */ |
| 288 | 346 | function give_process_form_login() { |
| 289 | 347 | |
| 290 | - $is_ajax = ! empty( $_POST['give_ajax'] ) ? give_clean( $_POST['give_ajax'] ) : 0; // WPCS: input var ok, sanitization ok, CSRF ok. | |
| 291 | - $referrer = wp_get_referer(); | |
| 292 | - $user_data = give_donation_form_validate_user_login(); | |
| 348 | + $is_ajax = ! empty( $_POST['give_ajax'] ) ? give_clean( $_POST['give_ajax'] ) : 0; // WPCS: input var ok, sanitization ok, CSRF ok. | |
| 349 | + $referrer = wp_get_referer(); | |
| 293 | 350 | |
| 351 | + // Default to no user until the login form is validated. | |
| 352 | + $user_data = [ | |
| 353 | + 'user_id' => - 1, | |
| 354 | + ]; | |
| 355 | + | |
| 356 | + // Require a valid nonce before processing the login form. | |
| 357 | + if ( empty( $_POST['give_login_nonce'] ) || ! wp_verify_nonce( $_POST['give_login_nonce'], 'give-login-nonce' ) ) { | |
| 358 | + give_set_error( 'invalid_nonce', __( 'Your session has expired. Please reload the page and try again.', 'give' ) ); | |
| 359 | + } else { | |
| 360 | + $user_data = give_donation_form_validate_user_login(); | |
| 361 | + } | |
| 362 | + | |
| 294 | 363 | if ( give_get_errors() || $user_data['user_id'] < 1 ) { |
| 295 | 364 | if ( $is_ajax ) { |
| 296 | 365 | /** |
| 297 | 366 | * Fires when AJAX sends back errors from the donation form. |
| @@ -302,8 +371,9 @@ | ||
| 302 | 371 | do_action( 'give_ajax_donation_errors' ); |
| 303 | 372 | $message = ob_get_contents(); |
| 304 | 373 | ob_end_clean(); |
| 305 | 374 | wp_send_json_error( $message ); |
| 375 | + return; | |
| 306 | 376 | } else { |
| 307 | 377 | wp_safe_redirect( $referrer ); |
| 308 | 378 | exit; |
| 309 | 379 | } |
| @@ -817,8 +887,9 @@ | ||
| 817 | 887 | /** |
| 818 | 888 | * Donation Form Validate Logged In User. |
| 819 | 889 | * |
| 820 | 890 | * @access private |
| 891 | + * @since 4.16.7.2 Sanitize first and last name values when falling back to stored user data. | |
| 821 | 892 | * @since 1.0 |
| 822 | 893 | * |
| 823 | 894 | * @return array |
| 824 | 895 | */ |
| @@ -852,13 +923,13 @@ | ||
| 852 | 923 | 'user_email' => ! empty( $post_data['give_email'] ) |
| 853 | 924 | ? sanitize_email( $post_data['give_email'] ) |
| 854 | 925 | : $user_data->user_email, |
| 855 | 926 | 'user_first' => ! empty( $post_data['give_first'] ) |
| 856 | - ? $post_data['give_first'] | |
| 857 | - : $user_data->first_name, | |
| 927 | + ? give_clean( $post_data['give_first'] ) | |
| 928 | + : give_clean( $user_data->first_name ), | |
| 858 | 929 | 'user_last' => ! empty( $post_data['give_last'] ) |
| 859 | - ? $post_data['give_last'] | |
| 860 | - : $user_data->last_name, | |
| 930 | + ? give_clean( $post_data['give_last'] ) | |
| 931 | + : give_clean( $user_data->last_name ), | |
| 861 | 932 | ]; |
| 862 | 933 | |
| 863 | 934 | // Validate essential form fields. |
| 864 | 935 | give_donation_form_validate_name_fields( $post_data ); |
| @@ -882,8 +953,9 @@ | ||
| 882 | 953 | /** |
| 883 | 954 | * Donate Form Validate New User |
| 884 | 955 | * |
| 885 | 956 | * @access private |
| 957 | + * @since 4.16.6 Flag data as coming from the checkout registration flow. | |
| 886 | 958 | * @since 1.0 |
| 887 | 959 | * |
| 888 | 960 | * @return array |
| 889 | 961 | */ |
| @@ -943,8 +1015,11 @@ | ||
| 943 | 1015 | if ( give_validate_user_email( $user_data['give_email'], $registering_new_user ) ) { |
| 944 | 1016 | $valid_user_data['user_email'] = $user_data['give_email']; |
| 945 | 1017 | } |
| 946 | 1018 | |
| 1019 | + // Mark this data as coming from the nonce-verified checkout flow. | |
| 1020 | + $valid_user_data['give_donation_checkout_registration'] = true; | |
| 1021 | + | |
| 947 | 1022 | return $valid_user_data; |
| 948 | 1023 | } |
| 949 | 1024 | |
| 950 | 1025 | /** |
| @@ -950,8 +1025,9 @@ | ||
| 950 | 1025 | /** |
| 951 | 1026 | * Donation Form Validate User Login |
| 952 | 1027 | * |
| 953 | 1028 | * @access private |
| 1029 | + * @since 4.16.7 Authenticate via wp_authenticate() and return a single generic error. | |
| 954 | 1030 | * @since 1.0 |
| 955 | 1031 | * |
| 956 | 1032 | * @return array |
| 957 | 1033 | */ |
| @@ -973,61 +1049,55 @@ | ||
| 973 | 1049 | return $valid_user_data; |
| 974 | 1050 | } |
| 975 | 1051 | |
| 976 | 1052 | $give_user_login = strip_tags( $post_data['give_user_login'] ); |
| 977 | - if ( is_email( $give_user_login ) ) { | |
| 978 | - // Get the user data by email. | |
| 979 | - $user_data = get_user_by( 'email', $give_user_login ); | |
| 980 | - } else { | |
| 981 | - // Get the user data by login. | |
| 982 | - $user_data = get_user_by( 'login', $give_user_login ); | |
| 1053 | + | |
| 1054 | + // Bailout, if Password is empty. | |
| 1055 | + if ( empty( $post_data['give_user_pass'] ) ) { | |
| 1056 | + give_set_error( 'password_empty', __( 'Enter a password.', 'give' ) ); | |
| 1057 | + return $valid_user_data; | |
| 983 | 1058 | } |
| 984 | 1059 | |
| 985 | - // Check if user exists. | |
| 986 | - if ( $user_data ) { | |
| 1060 | + // Authenticate through WordPress's login machinery so its authentication | |
| 1061 | + // hooks, password checks, and failed-login actions all apply. | |
| 1062 | + $user_data = wp_authenticate( $give_user_login, $post_data['give_user_pass'] ); | |
| 987 | 1063 | |
| 988 | - // Get password. | |
| 989 | - $user_pass = ! empty( $post_data['give_user_pass'] ) ? $post_data['give_user_pass'] : false; | |
| 1064 | + if ( is_wp_error( $user_data ) ) { | |
| 990 | 1065 | |
| 991 | - // Check user_pass. | |
| 992 | - if ( $user_pass ) { | |
| 1066 | + $core_auth_error_codes = [ | |
| 1067 | + 'incorrect_password', | |
| 1068 | + 'invalid_username', | |
| 1069 | + 'invalid_email', | |
| 1070 | + 'empty_username', | |
| 1071 | + 'empty_password', | |
| 1072 | + ]; | |
| 993 | 1073 | |
| 994 | - // Check if password is valid. | |
| 995 | - if ( ! wp_check_password( $user_pass, $user_data->user_pass, $user_data->ID ) ) { | |
| 1074 | + if ( in_array( $user_data->get_error_code(), $core_auth_error_codes, true ) ) { | |
| 1075 | + // A single generic message for an unknown login and a wrong password. | |
| 1076 | + $error_message = __( 'The login/password does not match or is incorrect.', 'give' ); | |
| 1077 | + } else { | |
| 1078 | + // Any other error comes from an authentication hook; surface its message. | |
| 1079 | + $error_message = wp_strip_all_tags( $user_data->get_error_message() ); | |
| 996 | 1080 | |
| 997 | - $current_page_url = site_url() . '/' . get_page_uri(); | |
| 1081 | + if ( '' === $error_message ) { | |
| 1082 | + $error_message = __( 'The login/password does not match or is incorrect.', 'give' ); | |
| 1083 | + } | |
| 1084 | + } | |
| 998 | 1085 | |
| 999 | - // Incorrect password. | |
| 1000 | - give_set_error( | |
| 1001 | - 'password_incorrect', | |
| 1002 | - sprintf( | |
| 1003 | - '%1$s <a href="%2$s">%3$s</a>', | |
| 1004 | - __( 'The password you entered is incorrect.', 'give' ), | |
| 1005 | - wp_lostpassword_url( $current_page_url ), | |
| 1006 | - __( 'Reset Password', 'give' ) | |
| 1007 | - ) | |
| 1008 | - ); | |
| 1086 | + give_set_error( 'invalid_credentials', $error_message ); | |
| 1009 | 1087 | |
| 1010 | - } else { | |
| 1088 | + return $valid_user_data; | |
| 1089 | + } | |
| 1011 | 1090 | |
| 1012 | - // Repopulate the valid user data array. | |
| 1013 | - $valid_user_data = [ | |
| 1014 | - 'user_id' => $user_data->ID, | |
| 1015 | - 'user_login' => $user_data->user_login, | |
| 1016 | - 'user_email' => $user_data->user_email, | |
| 1017 | - 'user_first' => $user_data->first_name, | |
| 1018 | - 'user_last' => $user_data->last_name, | |
| 1019 | - 'user_pass' => $user_pass, | |
| 1020 | - ]; | |
| 1021 | - } | |
| 1022 | - } else { | |
| 1023 | - // Empty password. | |
| 1024 | - give_set_error( 'password_empty', __( 'Enter a password.', 'give' ) ); | |
| 1025 | - } | |
| 1026 | - } else { | |
| 1027 | - // No username. | |
| 1028 | - give_set_error( 'username_incorrect', __( 'The username you entered does not exist.', 'give' ) ); | |
| 1029 | - } // End if(). | |
| 1091 | + // Repopulate the valid user data array. | |
| 1092 | + $valid_user_data = [ | |
| 1093 | + 'user_id' => $user_data->ID, | |
| 1094 | + 'user_login' => $user_data->user_login, | |
| 1095 | + 'user_email' => $user_data->user_email, | |
| 1096 | + 'user_first' => $user_data->first_name, | |
| 1097 | + 'user_last' => $user_data->last_name, | |
| 1098 | + 'user_pass' => $post_data['give_user_pass'], | |
| 1099 | + ]; | |
| 1030 | 1100 | |
| 1031 | 1101 | return $valid_user_data; |
| 1032 | 1102 | } |
| 1033 | 1103 | |
| @@ -1618,11 +1688,12 @@ | ||
| 1618 | 1688 | * Validates and checks if name fields are valid or not. |
| 1619 | 1689 | * |
| 1620 | 1690 | * @param array $post_data List of post data. |
| 1621 | 1691 | * |
| 1622 | - * @since 3.16.5 Check if "give_title" is set to prevent PHP warnings | |
| 1623 | - * @since 3.16.4 Add additional validation for company name field | |
| 1624 | - * @since 3.16.3 Add additional validations for name title prefix field | |
| 1692 | + * @since 4.16.7.2 Validate last name field even when omitted. | |
| 1693 | + * @since 3.16.5 Check if "give_title" is set to prevent PHP warnings | |
| 1694 | + * @since 3.16.4 Add additional validation for company name field | |
| 1695 | + * @since 3.16.3 Add additional validations for name title prefix field | |
| 1625 | 1696 | * @since 2.1 |
| 1626 | 1697 | * |
| 1627 | 1698 | * @return void |
| 1628 | 1699 | */ |
| @@ -1642,11 +1713,18 @@ | ||
| 1642 | 1713 | give_set_error( 'invalid_name_title', esc_html__( 'The name title prefix field is not valid.', 'give' ) ); |
| 1643 | 1714 | } |
| 1644 | 1715 | |
| 1645 | 1716 | $is_alpha_first_name = ( ! is_email( $post_data['give_first'] ) && ! preg_match( '~[0-9]~', $post_data['give_first'] ) ); |
| 1646 | - $is_alpha_last_name = ( ! is_email( $post_data['give_last'] ) && ! preg_match( '~[0-9]~', $post_data['give_last'] ) ); | |
| 1717 | + | |
| 1718 | + $lastName = isset( $post_data['give_last'] ) ? $post_data['give_last'] : ''; | |
| 1719 | + $is_alpha_last_name = ( ! is_email( $lastName ) && ! preg_match( '~[0-9]~', $lastName ) ); | |
| 1720 | + | |
| 1647 | 1721 | $is_alpha_title = ( isset($post_data['give_title']) && ! is_email( $post_data['give_title'] ) && ! preg_match( '~[0-9]~', $post_data['give_title'] ) ); |
| 1648 | 1722 | |
| 1649 | - if (!$is_alpha_first_name || ( ! empty( $post_data['give_last'] ) && ! $is_alpha_last_name) || ( ! empty( $post_data['give_title'] ) && ! $is_alpha_title) ) { | |
| 1723 | + if ( ! $is_alpha_first_name || ( ! empty( $lastName ) && ! $is_alpha_last_name ) || ( ! empty( $post_data['give_title'] ) && ! $is_alpha_title ) ) { | |
| 1650 | 1724 | give_set_error( 'invalid_name', esc_html__( 'The First Name and Last Name fields cannot contain an email address or numbers.', 'give' ) ); |
| 1725 | + } | |
| 1726 | + | |
| 1727 | + if ( give_is_last_name_required( $formId ) && empty( $lastName ) ) { | |
| 1728 | + give_set_error( 'invalid_last_name', esc_html__( 'Please enter your last name.', 'give' ) ); | |
| 1651 | 1729 | } |
| 1652 | 1730 | } |