| @@ -8,8 +8,12 @@ | ||
| 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; | |
| 14 | +use Give\Helpers\Utils; | |
| 15 | + | |
| 12 | 16 | // Exit if accessed directly. |
| 13 | 17 | if ( ! defined( 'ABSPATH' ) ) { |
| 14 | 18 | exit; |
| 15 | 19 | } |
| @@ -19,8 +23,11 @@ | ||
| 19 | 23 | * |
| 20 | 24 | * Handles the donation form process. |
| 21 | 25 | * |
| 22 | 26 | * @access private |
| 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 | |
| 23 | 30 | * @since 1.0 |
| 24 | 31 | * |
| 25 | 32 | * @throws ReflectionException Exception Handling. |
| 26 | 33 | * |
| @@ -48,8 +55,48 @@ | ||
| 48 | 55 | give_send_back_to_checkout(); |
| 49 | 56 | } |
| 50 | 57 | } |
| 51 | 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 | + | |
| 52 | 99 | /** |
| 53 | 100 | * Fires before processing the donation form. |
| 54 | 101 | * |
| 55 | 102 | * @since 1.0 |
| @@ -116,8 +163,21 @@ | ||
| 116 | 163 | 'last_name' => $user['user_last'], |
| 117 | 164 | 'address' => $user['address'], |
| 118 | 165 | ]; |
| 119 | 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 | + | |
| 120 | 180 | $auth_key = defined( 'AUTH_KEY' ) ? AUTH_KEY : ''; |
| 121 | 181 | |
| 122 | 182 | // Donation form ID. |
| 123 | 183 | $form_id = isset( $post_data['give-form-id'] ) ? absint( $post_data['give-form-id'] ) : 0; |
| @@ -150,14 +210,15 @@ | ||
| 150 | 210 | $purchase_key |
| 151 | 211 | ); |
| 152 | 212 | |
| 153 | 213 | // Setup donation information. |
| 214 | + $user_info = stripslashes_deep( $user_info ); | |
| 154 | 215 | $donation_data = [ |
| 155 | 216 | 'price' => $price, |
| 156 | 217 | 'purchase_key' => $purchase_key, |
| 157 | 218 | 'user_email' => $user['user_email'], |
| 158 | 219 | 'date' => date( 'Y-m-d H:i:s', current_time( 'timestamp' ) ), |
| 159 | - 'user_info' => stripslashes_deep( $user_info ), | |
| 220 | + 'user_info' => $user_info, | |
| 160 | 221 | 'post_data' => $post_data, |
| 161 | 222 | 'gateway' => $valid_data['gateway'], |
| 162 | 223 | 'card_info' => $valid_data['cc_info'], |
| 163 | 224 | ]; |
| @@ -276,8 +337,9 @@ | ||
| 276 | 337 | /** |
| 277 | 338 | * Process the checkout login form |
| 278 | 339 | * |
| 279 | 340 | * @access private |
| 341 | + * @since 4.16.7 Require a valid nonce before processing the login form. | |
| 280 | 342 | * @since 1.0 |
| 281 | 343 | * |
| 282 | 344 | * @return void |
| 283 | 345 | */ |
| @@ -282,12 +344,23 @@ | ||
| 282 | 344 | * @return void |
| 283 | 345 | */ |
| 284 | 346 | function give_process_form_login() { |
| 285 | 347 | |
| 286 | - $is_ajax = ! empty( $_POST['give_ajax'] ) ? give_clean( $_POST['give_ajax'] ) : 0; // WPCS: input var ok, sanitization ok, CSRF ok. | |
| 287 | - $referrer = wp_get_referer(); | |
| 288 | - $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(); | |
| 289 | 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 | + | |
| 290 | 363 | if ( give_get_errors() || $user_data['user_id'] < 1 ) { |
| 291 | 364 | if ( $is_ajax ) { |
| 292 | 365 | /** |
| 293 | 366 | * Fires when AJAX sends back errors from the donation form. |
| @@ -298,8 +371,9 @@ | ||
| 298 | 371 | do_action( 'give_ajax_donation_errors' ); |
| 299 | 372 | $message = ob_get_contents(); |
| 300 | 373 | ob_end_clean(); |
| 301 | 374 | wp_send_json_error( $message ); |
| 375 | + return; | |
| 302 | 376 | } else { |
| 303 | 377 | wp_safe_redirect( $referrer ); |
| 304 | 378 | exit; |
| 305 | 379 | } |
| @@ -330,8 +404,9 @@ | ||
| 330 | 404 | /** |
| 331 | 405 | * Donation Form Validate Fields. |
| 332 | 406 | * |
| 333 | 407 | * @access private |
| 408 | + * @since 3.5.0 validate serialized fields | |
| 334 | 409 | * @since 1.0 |
| 335 | 410 | * |
| 336 | 411 | * @return bool|array |
| 337 | 412 | */ |
| @@ -337,8 +412,9 @@ | ||
| 337 | 412 | */ |
| 338 | 413 | function give_donation_form_validate_fields() { |
| 339 | 414 | |
| 340 | 415 | $post_data = give_clean( $_POST ); // WPCS: input var ok, sanitization ok, CSRF ok. |
| 416 | + give_donation_form_validate_name_fields($post_data); | |
| 341 | 417 | |
| 342 | 418 | // Validate Honeypot First. |
| 343 | 419 | if ( ! empty( $post_data['give-honeypot'] ) ) { |
| 344 | 420 | give_set_error( 'invalid_honeypot', esc_html__( 'Honeypot field detected. Go away bad bot!', 'give' ) ); |
| @@ -343,8 +419,13 @@ | ||
| 343 | 419 | if ( ! empty( $post_data['give-honeypot'] ) ) { |
| 344 | 420 | give_set_error( 'invalid_honeypot', esc_html__( 'Honeypot field detected. Go away bad bot!', 'give' ) ); |
| 345 | 421 | } |
| 346 | 422 | |
| 423 | + // Validate serialized fields. | |
| 424 | + if (give_donation_form_has_serialized_fields($post_data)) { | |
| 425 | + give_set_error('invalid_serialized_fields', esc_html__('Serialized fields detected. Go away!', 'give')); | |
| 426 | + } | |
| 427 | + | |
| 347 | 428 | // Check spam detect. |
| 348 | 429 | if ( |
| 349 | 430 | isset( $post_data['action'] ) |
| 350 | 431 | && give_is_spam_donation() |
| @@ -406,8 +487,30 @@ | ||
| 406 | 487 | return $valid_data; |
| 407 | 488 | } |
| 408 | 489 | |
| 409 | 490 | /** |
| 491 | + * Detect serialized fields. | |
| 492 | + * | |
| 493 | + * @since 3.17.2 Use Utils::isSerialized() method which add supports to find hidden serialized data in the middle of a string | |
| 494 | + * @since 3.16.5 Make sure only string parameters are used with the ltrim() method to prevent PHP 8+ fatal errors | |
| 495 | + * @since 3.16.4 updated to check all values for serialized fields | |
| 496 | + * @since 3.16.2 added additional check for stripslashes_deep | |
| 497 | + * @since 3.14.2 add give-form-title, give_title | |
| 498 | + * @since 3.5.0 | |
| 499 | + */ | |
| 500 | +function give_donation_form_has_serialized_fields(array $post_data): bool | |
| 501 | +{ | |
| 502 | + foreach ($post_data as $value) { | |
| 503 | + | |
| 504 | + if (Utils::isSerialized($value)) { | |
| 505 | + return true; | |
| 506 | + } | |
| 507 | + } | |
| 508 | + | |
| 509 | + return false; | |
| 510 | +} | |
| 511 | + | |
| 512 | +/** | |
| 410 | 513 | * Detect spam donation. |
| 411 | 514 | * |
| 412 | 515 | * @since 1.8.14 |
| 413 | 516 | * |
| @@ -784,8 +887,9 @@ | ||
| 784 | 887 | /** |
| 785 | 888 | * Donation Form Validate Logged In User. |
| 786 | 889 | * |
| 787 | 890 | * @access private |
| 891 | + * @since 4.16.7.2 Sanitize first and last name values when falling back to stored user data. | |
| 788 | 892 | * @since 1.0 |
| 789 | 893 | * |
| 790 | 894 | * @return array |
| 791 | 895 | */ |
| @@ -819,13 +923,13 @@ | ||
| 819 | 923 | 'user_email' => ! empty( $post_data['give_email'] ) |
| 820 | 924 | ? sanitize_email( $post_data['give_email'] ) |
| 821 | 925 | : $user_data->user_email, |
| 822 | 926 | 'user_first' => ! empty( $post_data['give_first'] ) |
| 823 | - ? $post_data['give_first'] | |
| 824 | - : $user_data->first_name, | |
| 927 | + ? give_clean( $post_data['give_first'] ) | |
| 928 | + : give_clean( $user_data->first_name ), | |
| 825 | 929 | 'user_last' => ! empty( $post_data['give_last'] ) |
| 826 | - ? $post_data['give_last'] | |
| 827 | - : $user_data->last_name, | |
| 930 | + ? give_clean( $post_data['give_last'] ) | |
| 931 | + : give_clean( $user_data->last_name ), | |
| 828 | 932 | ]; |
| 829 | 933 | |
| 830 | 934 | // Validate essential form fields. |
| 831 | 935 | give_donation_form_validate_name_fields( $post_data ); |
| @@ -849,8 +953,9 @@ | ||
| 849 | 953 | /** |
| 850 | 954 | * Donate Form Validate New User |
| 851 | 955 | * |
| 852 | 956 | * @access private |
| 957 | + * @since 4.16.6 Flag data as coming from the checkout registration flow. | |
| 853 | 958 | * @since 1.0 |
| 854 | 959 | * |
| 855 | 960 | * @return array |
| 856 | 961 | */ |
| @@ -910,8 +1015,11 @@ | ||
| 910 | 1015 | if ( give_validate_user_email( $user_data['give_email'], $registering_new_user ) ) { |
| 911 | 1016 | $valid_user_data['user_email'] = $user_data['give_email']; |
| 912 | 1017 | } |
| 913 | 1018 | |
| 1019 | + // Mark this data as coming from the nonce-verified checkout flow. | |
| 1020 | + $valid_user_data['give_donation_checkout_registration'] = true; | |
| 1021 | + | |
| 914 | 1022 | return $valid_user_data; |
| 915 | 1023 | } |
| 916 | 1024 | |
| 917 | 1025 | /** |
| @@ -917,8 +1025,9 @@ | ||
| 917 | 1025 | /** |
| 918 | 1026 | * Donation Form Validate User Login |
| 919 | 1027 | * |
| 920 | 1028 | * @access private |
| 1029 | + * @since 4.16.7 Authenticate via wp_authenticate() and return a single generic error. | |
| 921 | 1030 | * @since 1.0 |
| 922 | 1031 | * |
| 923 | 1032 | * @return array |
| 924 | 1033 | */ |
| @@ -940,61 +1049,55 @@ | ||
| 940 | 1049 | return $valid_user_data; |
| 941 | 1050 | } |
| 942 | 1051 | |
| 943 | 1052 | $give_user_login = strip_tags( $post_data['give_user_login'] ); |
| 944 | - if ( is_email( $give_user_login ) ) { | |
| 945 | - // Get the user data by email. | |
| 946 | - $user_data = get_user_by( 'email', $give_user_login ); | |
| 947 | - } else { | |
| 948 | - // Get the user data by login. | |
| 949 | - $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; | |
| 950 | 1058 | } |
| 951 | 1059 | |
| 952 | - // Check if user exists. | |
| 953 | - 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'] ); | |
| 954 | 1063 | |
| 955 | - // Get password. | |
| 956 | - $user_pass = ! empty( $post_data['give_user_pass'] ) ? $post_data['give_user_pass'] : false; | |
| 1064 | + if ( is_wp_error( $user_data ) ) { | |
| 957 | 1065 | |
| 958 | - // Check user_pass. | |
| 959 | - if ( $user_pass ) { | |
| 1066 | + $core_auth_error_codes = [ | |
| 1067 | + 'incorrect_password', | |
| 1068 | + 'invalid_username', | |
| 1069 | + 'invalid_email', | |
| 1070 | + 'empty_username', | |
| 1071 | + 'empty_password', | |
| 1072 | + ]; | |
| 960 | 1073 | |
| 961 | - // Check if password is valid. | |
| 962 | - 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() ); | |
| 963 | 1080 | |
| 964 | - $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 | + } | |
| 965 | 1085 | |
| 966 | - // Incorrect password. | |
| 967 | - give_set_error( | |
| 968 | - 'password_incorrect', | |
| 969 | - sprintf( | |
| 970 | - '%1$s <a href="%2$s">%3$s</a>', | |
| 971 | - __( 'The password you entered is incorrect.', 'give' ), | |
| 972 | - wp_lostpassword_url( $current_page_url ), | |
| 973 | - __( 'Reset Password', 'give' ) | |
| 974 | - ) | |
| 975 | - ); | |
| 1086 | + give_set_error( 'invalid_credentials', $error_message ); | |
| 976 | 1087 | |
| 977 | - } else { | |
| 1088 | + return $valid_user_data; | |
| 1089 | + } | |
| 978 | 1090 | |
| 979 | - // Repopulate the valid user data array. | |
| 980 | - $valid_user_data = [ | |
| 981 | - 'user_id' => $user_data->ID, | |
| 982 | - 'user_login' => $user_data->user_login, | |
| 983 | - 'user_email' => $user_data->user_email, | |
| 984 | - 'user_first' => $user_data->first_name, | |
| 985 | - 'user_last' => $user_data->last_name, | |
| 986 | - 'user_pass' => $user_pass, | |
| 987 | - ]; | |
| 988 | - } | |
| 989 | - } else { | |
| 990 | - // Empty password. | |
| 991 | - give_set_error( 'password_empty', __( 'Enter a password.', 'give' ) ); | |
| 992 | - } | |
| 993 | - } else { | |
| 994 | - // No username. | |
| 995 | - give_set_error( 'username_incorrect', __( 'The username you entered does not exist.', 'give' ) ); | |
| 996 | - } // 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 | + ]; | |
| 997 | 1100 | |
| 998 | 1101 | return $valid_user_data; |
| 999 | 1102 | } |
| 1000 | 1103 | |
| @@ -1585,8 +1688,12 @@ | ||
| 1585 | 1688 | * Validates and checks if name fields are valid or not. |
| 1586 | 1689 | * |
| 1587 | 1690 | * @param array $post_data List of post data. |
| 1588 | 1691 | * |
| 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 | |
| 1589 | 1696 | * @since 2.1 |
| 1590 | 1697 | * |
| 1591 | 1698 | * @return void |
| 1592 | 1699 | */ |
| @@ -1591,11 +1698,33 @@ | ||
| 1591 | 1698 | * @return void |
| 1592 | 1699 | */ |
| 1593 | 1700 | function give_donation_form_validate_name_fields( $post_data ) { |
| 1594 | 1701 | |
| 1595 | - $is_alpha_first_name = ( ! is_email( $post_data['give_first'] ) && ! preg_match( '~[0-9]~', $post_data['give_first'] ) ); | |
| 1596 | - $is_alpha_last_name = ( ! is_email( $post_data['give_last'] ) && ! preg_match( '~[0-9]~', $post_data['give_last'] ) ); | |
| 1702 | + $formId = absint( $post_data['give-form-id'] ); | |
| 1597 | 1703 | |
| 1598 | - if ( ! $is_alpha_first_name || ( ! empty( $post_data['give_last'] ) && ! $is_alpha_last_name ) ) { | |
| 1599 | - give_set_error( 'invalid_name', esc_html__( 'The First Name and Last Name fields cannot contain an email address or numbers.', 'give' ) ); | |
| 1600 | - } | |
| 1704 | + if (!give_is_name_title_prefix_enabled($formId) && isset($post_data['give_title'])) { | |
| 1705 | + give_set_error( 'disabled_name_title', esc_html__( 'The name title prefix field is not enabled.', 'give' ) ); | |
| 1706 | + } | |
| 1707 | + | |
| 1708 | + if (!give_is_company_field_enabled($formId) && isset($post_data['give_company_name'])) { | |
| 1709 | + give_set_error( 'disabled_company', esc_html__( 'The company field is not enabled.', 'give' ) ); | |
| 1710 | + } | |
| 1711 | + | |
| 1712 | + if (give_is_name_title_prefix_enabled($formId) && isset($post_data['give_title']) && !in_array($post_data['give_title'], array_values(give_get_name_title_prefixes($formId)))) { | |
| 1713 | + give_set_error( 'invalid_name_title', esc_html__( 'The name title prefix field is not valid.', 'give' ) ); | |
| 1714 | + } | |
| 1715 | + | |
| 1716 | + $is_alpha_first_name = ( ! is_email( $post_data['give_first'] ) && ! preg_match( '~[0-9]~', $post_data['give_first'] ) ); | |
| 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 | + | |
| 1721 | + $is_alpha_title = ( isset($post_data['give_title']) && ! is_email( $post_data['give_title'] ) && ! preg_match( '~[0-9]~', $post_data['give_title'] ) ); | |
| 1722 | + | |
| 1723 | + if ( ! $is_alpha_first_name || ( ! empty( $lastName ) && ! $is_alpha_last_name ) || ( ! empty( $post_data['give_title'] ) && ! $is_alpha_title ) ) { | |
| 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' ) ); | |
| 1729 | + } | |
| 1601 | 1730 | } |