PluginProbe
User Profile Builder – Beautiful User Registration Forms, User Profiles & User Role Editor / 4.0.3
User Profile Builder – Beautiful User Registration Forms, User Profiles & User Role Editor v4.0.3
4.0.3 4.0.2 4.0.1 4.0.0 3.16.6 3.16.5 3.16.4 3.16.3 3.16.2 3.16.1 3.16.0 3.15.9 3.9.9 3.9.5 3.9.6 3.9.7 3.9.8 1.1.7 1.1.8 1.1.9 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 All 341 releases
← All changes | front-end/default-fields/turnstile/turnstile.php +144 -43 3.16.1 → 4.0.3 View file →
@@ -13,10 +13,13 @@
13 13 $response = wp_remote_post( $path, array(
14 14 'body' => $data
15 15 ) );
16 16
17 - if ( ! is_wp_error( $response ) )
18 - return $response["body"];
17 + if ( is_wp_error( $response ) ) {
18 + return '';
19 + }
20 +
21 + return isset( $response['body'] ) ? $response['body'] : '';
19 22 }
20 23
21 24 /**
22 25 * Gets the challenge HTML wrapper for Turnstile.
@@ -69,15 +72,16 @@
69 72 /* if we do not have a turnstile field do nothing */
70 73 if( empty( $field ) )
71 74 return;
72 75
73 - //do not add script if there is no shortcode
76 + global $wppb_turnstile_present;
74 77 global $wppb_shortcode_on_front;
75 - if( current_filter() == 'wp_footer' && ( !isset( $wppb_shortcode_on_front ) || $wppb_shortcode_on_front === false ) )
78 +
79 + //do not add script on regular frontend pages unless a PB shortcode or Turnstile HTML is present
80 + if( current_filter() == 'wp_footer' && ( !isset( $wppb_shortcode_on_front ) || $wppb_shortcode_on_front === false ) && ( !isset( $wppb_turnstile_present ) || $wppb_turnstile_present === false ) )
76 81 return;
77 82
78 83 //do not add script if the html for the field has not been added
79 - global $wppb_turnstile_present;
80 84 if( !isset( $wppb_turnstile_present ) || $wppb_turnstile_present === false )
81 85 return;
82 86
83 87 //we don't have jquery on the backend
@@ -183,15 +187,16 @@
183 187 'response' => $response
184 188 )
185 189 );
186 190
187 - $answers = json_decode($getResponse, true);
191 + $answers = json_decode( $getResponse, true );
188 192 $turnstileResponse = new wppb_TurnstileResponse();
189 193
190 - if (trim($answers ['success']) == true) {
194 + // Fail closed when the HTTP call fails or the body is not valid JSON.
195 + if ( ! is_array( $answers ) || empty( $answers['success'] ) ) {
196 + $turnstileResponse->is_valid = false;
197 + } else {
191 198 $turnstileResponse->is_valid = true;
192 - } else {
193 - $turnstileResponse->is_valid = false;
194 199 }
195 200
196 201 return $turnstileResponse;
197 202
@@ -198,8 +203,15 @@
198 203 }
199 204
200 205 /* the function to validate the Turnstile response with the API */
201 206 function wppb_validate_turnstile_response( $publickey, $privatekey ){
207 + /* If the Turnstile keys are not configured the widget cannot work for anyone, so do not enforce -
208 + otherwise an incomplete setup would lock every visitor out of the form. These keys are admin-side
209 + configuration, not attacker controlled, so this cannot be used to bypass a properly configured Turnstile. */
210 + if ( empty( $publickey ) || empty( $privatekey ) ) {
211 + return true;
212 + }
213 +
202 214 if (isset($_POST['cf-turnstile-response'])){
203 215 $turnstile_response_field = sanitize_textarea_field( $_POST['cf-turnstile-response'] );
204 216 } else {
205 217 $turnstile_response_field = '';
@@ -228,15 +240,16 @@
228 240 }
229 241
230 242 }
231 243
232 - // Save valid results when they are being triggered from an ajax request
233 - if( wp_doing_ajax() && isset( $_POST['action'] ) && $_POST['action'] == 'pms_validate_checkout' ){
244 + // Save valid results when they are being triggered from an ajax request that only pre-validates the
245 + // credentials, so the same single use token is still accepted on the form submission that follows it
246 + if( wppb_is_captcha_prevalidation_request() ){
234 247
235 - $saved = get_option( 'wppb_turnstile_validations', array() );
248 + $saved = wppb_prune_captcha_prevalidations( get_option( 'wppb_turnstile_validations', array() ) );
236 249
237 250 if( $already_validated === true )
238 - $saved[ $turnstile_response_field ] = true;
251 + $saved[ $turnstile_response_field ] = time();
239 252
240 253 update_option( 'wppb_turnstile_validations', $saved, false );
241 254
242 255 }
@@ -288,9 +301,12 @@
288 301 global $wppb_turnstile_response;
289 302 if (!isset($wppb_turnstile_response)){
290 303 $wppb_turnstile_response = wppb_validate_turnstile_response( trim( $field['turnstile-site-key'] ), trim( $field['turnstile-secret-key'] ) );
291 304 }
292 - if ( ( $wppb_turnstile_response == false ) && ( $field['required'] == 'Yes' ) ){
305 + /* Turnstile must fail closed: whenever it is configured to display on this form it has to be
306 + verified, regardless of the "required" toggle. A missing/empty token makes
307 + wppb_validate_turnstile_response() return false, so bots that omit cf-turnstile-response are blocked. */
308 + if ( $wppb_turnstile_response == false ){
293 309 return __('Cloudflare Turnstile could not be verified. Please try again.', 'profile-builder');
294 310 }
295 311 }
296 312 }
@@ -466,34 +482,34 @@
466 482 if ( isset( $_POST['log'] ) && !is_wp_error($user) && !isset( $_POST['pms_login'] ) ) {
467 483
468 484 $field = wppb_get_turnstile_field();
469 485 if ( !empty($field) ){
470 - global $wppb_turnstile_response;
486 + /* Work out whether Turnstile is enabled for the form that was actually submitted before verifying
487 + anything. The Cloudflare token is single use, so verifying it on a form where our widget was never
488 + displayed spends a token that belongs to whatever else protects that form, and that plugin\'s own
489 + check then fails with timeout-or-duplicate. */
490 + if ( isset($_POST['wppb_login']) && ($_POST['wppb_login'] == true) ) {
491 + // it\'s a PB login form, check if we have Turnstile on it
492 + $turnstile_enabled = ( isset($field['turnstile-pb-forms']) && (strpos($field['turnstile-pb-forms'], 'pb_login') !== false) );
493 + }
494 + else {
495 + // default WP login form
496 + $turnstile_enabled = ( isset($field['turnstile-wp-forms']) && (strpos($field['turnstile-wp-forms'], 'default_wp_login') !== false) );
497 + }
471 498
472 - if (!isset($wppb_turnstile_response)) $wppb_turnstile_response = wppb_validate_turnstile_response( trim( $field['turnstile-site-key'] ), trim( $field['turnstile-secret-key'] ) );
499 + if ( $turnstile_enabled ) {
500 + global $wppb_turnstile_response;
473 501
474 - $turnstile_error_message = __('Cloudflare Turnstile could not be verified. Please try again.','profile-builder');
502 + if (!isset($wppb_turnstile_response)) $wppb_turnstile_response = wppb_validate_turnstile_response( trim( $field['turnstile-site-key'] ), trim( $field['turnstile-secret-key'] ) );
475 503
476 - //Turnstile error for displaying on the PB login form
477 - if ( isset($_POST['wppb_login']) && ($_POST['wppb_login'] == true) ) {
504 + $turnstile_error_message = __('Cloudflare Turnstile could not be verified. Please try again.','profile-builder');
478 505
479 - // it\'s a PB login form, check if we have Turnstile on it and display error if not valid
480 - if ((isset($field['turnstile-pb-forms'])) && (strpos($field['turnstile-pb-forms'], 'pb_login') !== false) && ($wppb_turnstile_response == false)) {
506 + if ( $wppb_turnstile_response == false ) {
481 507 $user = new WP_Error('wppb_turnstile_error', $turnstile_error_message);
482 508 remove_filter( 'authenticate', 'wp_authenticate_username_password', 20, 3 );
483 509 remove_filter( 'authenticate', 'wp_authenticate_email_password', 20, 3 );
484 510 }
485 -
486 511 }
487 - else {
488 - //Turnstile error for displaying on the default WP login form
489 - if (isset($field['turnstile-wp-forms']) && (strpos($field['turnstile-wp-forms'], 'default_wp_login') !== false) && ($wppb_turnstile_response == false)) {
490 - $user = new WP_Error('wppb_turnstile_error', $turnstile_error_message);
491 - remove_filter( 'authenticate', 'wp_authenticate_username_password', 20, 3 );
492 - remove_filter( 'authenticate', 'wp_authenticate_email_password', 20, 3 );
493 - }
494 -
495 - }
496 512 }
497 513 }
498 514 return $user;
499 515 }
@@ -548,16 +564,21 @@
548 564 return;
549 565
550 566 $field = wppb_get_turnstile_field();
551 567 if ( !empty($field) ){
552 - global $wppb_turnstile_response;
553 - if (!isset($wppb_turnstile_response)) $wppb_turnstile_response = wppb_validate_turnstile_response( trim( $field['turnstile-site-key'] ), trim( $field['turnstile-secret-key'] ) );
568 + /* Only verify where Turnstile is configured for the form being submitted. The Cloudflare token is
569 + single use, so verifying it on a form our widget was never displayed on spends a token that another
570 + plugin protecting that form still needs, and its own check then fails with timeout-or-duplicate. */
571 + if ( isset( $field['turnstile-wp-forms'] ) && ( strpos( $field['turnstile-wp-forms'], 'default_wp_recover_password' ) !== false ) ) {
572 + global $wppb_turnstile_response;
573 + if (!isset($wppb_turnstile_response)) $wppb_turnstile_response = wppb_validate_turnstile_response( trim( $field['turnstile-site-key'] ), trim( $field['turnstile-secret-key'] ) );
554 574
555 - $turnstile_error_message = esc_html__('Cloudflare Turnstile could not be verified. Please try again.','profile-builder');
575 + $turnstile_error_message = esc_html__('Cloudflare Turnstile could not be verified. Please try again.','profile-builder');
556 576
557 - // If Turnstile not entered or incorrect Turnstile answer
558 - if ( isset( $_REQUEST['cf-turnstile-response'] ) && ( ( "" === $_REQUEST['cf-turnstile-response'] ) || ( $wppb_turnstile_response == false ) ) ) {
559 - wp_die( esc_html( $turnstile_error_message ) . '<br />' . esc_html__( "Click the BACK button on your browser, and try again.", 'profile-builder' ) ) ;
577 + // Fail closed: a missing token is treated as a failed verification.
578 + if ( $wppb_turnstile_response == false ) {
579 + wp_die( esc_html( $turnstile_error_message ) . '<br />' . esc_html__( "Click the BACK button on your browser, and try again.", 'profile-builder' ) ) ;
580 + }
560 581 }
561 582 }
562 583 }
563 584 add_action('lostpassword_post','wppb_verify_turnstile_default_wp_recover_password');
@@ -593,16 +614,21 @@
593 614 function wppb_verify_turnstile_default_wp_register( $errors ){
594 615
595 616 $field = wppb_get_turnstile_field();
596 617 if ( !empty($field) ){
597 - global $wppb_turnstile_response;
598 - if (!isset($wppb_turnstile_response)) $wppb_turnstile_response = wppb_validate_turnstile_response( trim( $field['turnstile-site-key'] ), trim( $field['turnstile-secret-key'] ) );
618 + /* Only verify where Turnstile is configured for the form being submitted. The Cloudflare token is
619 + single use, so verifying it on a form our widget was never displayed on spends a token that another
620 + plugin protecting that form still needs, and its own check then fails with timeout-or-duplicate. */
621 + if ( isset( $field['turnstile-wp-forms'] ) && ( strpos( $field['turnstile-wp-forms'], 'default_wp_register' ) !== false ) ) {
622 + global $wppb_turnstile_response;
623 + if (!isset($wppb_turnstile_response)) $wppb_turnstile_response = wppb_validate_turnstile_response( trim( $field['turnstile-site-key'] ), trim( $field['turnstile-secret-key'] ) );
599 624
600 - $turnstile_error_message = esc_html__('Cloudflare Turnstile could not be verified. Please try again.','profile-builder');
625 + $turnstile_error_message = esc_html__('Cloudflare Turnstile could not be verified. Please try again.','profile-builder');
601 626
602 - // If Turnstile not entered or incorrect Turnstile answer
603 - if ( isset( $_REQUEST['cf-turnstile-response'] ) && ( ( "" === $_REQUEST['cf-turnstile-response'] ) || ( $wppb_turnstile_response == false ) ) ) {
604 - $errors->add( 'wppb_turnstile_error', $turnstile_error_message );
627 + // Fail closed: a missing token is treated as a failed verification.
628 + if ( $wppb_turnstile_response == false ) {
629 + $errors->add( 'wppb_turnstile_error', $turnstile_error_message );
630 + }
605 631 }
606 632 }
607 633
608 634 return $errors;
@@ -607,8 +633,83 @@
607 633
608 634 return $errors;
609 635 }
610 636 add_filter('registration_errors','wppb_verify_turnstile_default_wp_register');
637 +
638 +/* Display Turnstile html on default WP Comments form */
639 +function wppb_display_turnstile_default_wp_comments(){
640 + $field = wppb_get_turnstile_field();
641 +
642 + if ( !empty( $field ) ) {
643 + if ( isset( $field['turnstile-wp-forms'] ) && ( strpos( $field['turnstile-wp-forms'], 'default_wp_comments' ) !== false ) ) {
644 + $publickey = trim( $field['turnstile-site-key'] );
645 + $item_title = apply_filters( 'wppb_comments_turnstile_custom_field_' . $field['id'] . '_item_title', wppb_icl_t( 'plugin profile-builder-pro', 'custom_field_' . $field['id'] . '_title_translation', $field['field-title'], true ) );
646 + $item_description = wppb_icl_t( 'plugin profile-builder-pro', 'custom_field_' . $field['id'] . '_description_translation', $field['description'], true );
647 +
648 + global $wppb_turnstile_present;
649 + $wppb_turnstile_present = true;
650 +
651 + $turnstile_output = '<label for="turnstile_response_field">' . $item_title . '</label>' . wppb_turnstile_get_html( $publickey, 'default_wp_comments' );
652 + if ( !empty( $item_description ) )
653 + $turnstile_output .= '<span class="wppb-description-delimiter">' . $item_description . '</span>';
654 +
655 + echo '<div class="wppb-form-field wppb-turnstile">' . $turnstile_output . '</div>'; /* phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped */ /* properly escaped when constructing the var */
656 + }
657 + }
658 +}
659 +add_action( 'comment_form_after_fields', 'wppb_display_turnstile_default_wp_comments' );
660 +add_action( 'comment_form_logged_in_after', 'wppb_display_turnstile_default_wp_comments' );
661 +
662 +function wppb_get_turnstile_default_wp_comments_error_message(){
663 + return __( 'Cloudflare Turnstile could not be verified. Please try again.', 'profile-builder' );
664 +}
665 +
666 +function wppb_display_turnstile_default_wp_comments_error(){
667 + if ( !isset( $_GET['wppb_comment_turnstile_error'] ) )
668 + return;
669 +
670 + $field = wppb_get_turnstile_field();
671 +
672 + if ( empty( $field ) || !isset( $field['turnstile-wp-forms'] ) || ( strpos( $field['turnstile-wp-forms'], 'default_wp_comments' ) === false ) )
673 + return;
674 +
675 + echo '<p class="wppb-error wppb-comment-captcha-error" id="wppb_comment_turnstile_error">' . esc_html( wppb_get_turnstile_default_wp_comments_error_message() ) . '</p>';
676 +}
677 +add_action( 'comment_form_top', 'wppb_display_turnstile_default_wp_comments_error' );
678 +
679 +// Verify Turnstile for default WP Comments form
680 +function wppb_verify_turnstile_default_wp_comments( $approved, $commentdata ){
681 + if ( !isset( $_POST['comment_post_ID'] ) )
682 + return $approved;
683 +
684 + $field = wppb_get_turnstile_field();
685 +
686 + if ( !empty( $field ) ) {
687 + if ( isset( $field['turnstile-wp-forms'] ) && ( strpos( $field['turnstile-wp-forms'], 'default_wp_comments' ) !== false ) ) {
688 + global $wppb_turnstile_response;
689 + if ( !isset( $wppb_turnstile_response ) )
690 + $wppb_turnstile_response = wppb_validate_turnstile_response( trim( $field['turnstile-site-key'] ), trim( $field['turnstile-secret-key'] ) );
691 +
692 + if ( $wppb_turnstile_response == false ) {
693 + $redirect_to = wp_get_referer();
694 +
695 + if ( empty( $redirect_to ) && isset( $commentdata['comment_post_ID'] ) )
696 + $redirect_to = get_permalink( absint( $commentdata['comment_post_ID'] ) );
697 +
698 + if ( !empty( $redirect_to ) && !wp_doing_ajax() ) {
699 + $redirect_to = preg_replace( '/#.*$/', '', remove_query_arg( array( 'wppb_comment_recaptcha_error', 'wppb_comment_turnstile_error' ), $redirect_to ) );
700 + wp_safe_redirect( add_query_arg( 'wppb_comment_turnstile_error', '1', $redirect_to ) . '#respond' );
701 + exit;
702 + }
703 +
704 + return new WP_Error( 'wppb_turnstile_error', wppb_get_turnstile_default_wp_comments_error_message(), 200 );
705 + }
706 + }
707 + }
708 +
709 + return $approved;
710 +}
711 +add_filter( 'pre_comment_approved', 'wppb_verify_turnstile_default_wp_comments', 10, 2 );
611 712
612 713 // set default values in case there's already an existing Turnstile field in Manage fields (when upgrading)
613 714 function wppb_turnstile_set_default_values() {
614 715 $manage_fields = get_option('wppb_manage_fields', 'not_set');