From cb2564e239e61287f28c5adda23187acf639ea41 Mon Sep 17 00:00:00 2001 From: Brian Canini <canini.16@osu.edu> Date: Wed, 27 Jan 2021 16:12:39 -0500 Subject: [PATCH] Updating drupal/recaptcha_v3 (1.3.0 => 1.4.0) --- composer.json | 2 +- composer.lock | 18 ++-- vendor/composer/installed.json | 18 ++-- web/modules/recaptcha_v3/composer.json | 2 +- web/modules/recaptcha_v3/js/recaptcha_v3.js | 72 ++++++++++---- .../recaptcha_v3/recaptcha_v3.info.yml | 6 +- web/modules/recaptcha_v3/recaptcha_v3.module | 94 ++++++++++++------- .../src/Form/ReCaptchaV3ActionForm.php | 2 + .../src/Form/ReCaptchaV3SettingsForm.php | 1 + 9 files changed, 142 insertions(+), 73 deletions(-) diff --git a/composer.json b/composer.json index 6c97470805..2bd2726fe1 100644 --- a/composer.json +++ b/composer.json @@ -157,7 +157,7 @@ "drupal/realname": "1.0.0-rc2", "drupal/rebuild_cache_access": "1.7", "drupal/recaptcha": "2.5", - "drupal/recaptcha_v3": "^1.3", + "drupal/recaptcha_v3": "^1.4", "drupal/redirect": "1.6", "drupal/redis": "1.0", "drupal/roleassign": "1.0.0-beta1", diff --git a/composer.lock b/composer.lock index 3cfc95e144..ded05a579f 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "483958ad644d5bd3351af88c32212e2b", + "content-hash": "74fb2280bfa0733282c54f4704298ed2", "packages": [ { "name": "alchemy/zippy", @@ -6993,17 +6993,17 @@ }, { "name": "drupal/recaptcha_v3", - "version": "1.3.0", + "version": "1.4.0", "source": { "type": "git", "url": "https://git.drupalcode.org/project/recaptcha_v3.git", - "reference": "8.x-1.3" + "reference": "8.x-1.4" }, "dist": { "type": "zip", - "url": "https://ftp.drupal.org/files/projects/recaptcha_v3-8.x-1.3.zip", - "reference": "8.x-1.3", - "shasum": "1a228dafb57317889c18bbc80eafa40696673125" + "url": "https://ftp.drupal.org/files/projects/recaptcha_v3-8.x-1.4.zip", + "reference": "8.x-1.4", + "shasum": "2f4624076ebb94ad0f49a957d0b4b77094a2bf69" }, "require": { "drupal/captcha": "^1.0.0-beta4", @@ -7013,8 +7013,8 @@ "type": "drupal-module", "extra": { "drupal": { - "version": "8.x-1.3", - "datestamp": "1590393627", + "version": "8.x-1.4", + "datestamp": "1610128897", "security-coverage": { "status": "covered", "message": "Covered by Drupal's security advisory policy" @@ -7023,7 +7023,7 @@ }, "notification-url": "https://packages.drupal.org/8/downloads", "license": [ - "GPL-2.0+" + "GPL-2.0-or-later" ], "authors": [ { diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json index e0aade52f2..a8bb79c42a 100644 --- a/vendor/composer/installed.json +++ b/vendor/composer/installed.json @@ -7207,18 +7207,18 @@ }, { "name": "drupal/recaptcha_v3", - "version": "1.3.0", - "version_normalized": "1.3.0.0", + "version": "1.4.0", + "version_normalized": "1.4.0.0", "source": { "type": "git", "url": "https://git.drupalcode.org/project/recaptcha_v3.git", - "reference": "8.x-1.3" + "reference": "8.x-1.4" }, "dist": { "type": "zip", - "url": "https://ftp.drupal.org/files/projects/recaptcha_v3-8.x-1.3.zip", - "reference": "8.x-1.3", - "shasum": "1a228dafb57317889c18bbc80eafa40696673125" + "url": "https://ftp.drupal.org/files/projects/recaptcha_v3-8.x-1.4.zip", + "reference": "8.x-1.4", + "shasum": "2f4624076ebb94ad0f49a957d0b4b77094a2bf69" }, "require": { "drupal/captcha": "^1.0.0-beta4", @@ -7228,8 +7228,8 @@ "type": "drupal-module", "extra": { "drupal": { - "version": "8.x-1.3", - "datestamp": "1590393627", + "version": "8.x-1.4", + "datestamp": "1610128897", "security-coverage": { "status": "covered", "message": "Covered by Drupal's security advisory policy" @@ -7239,7 +7239,7 @@ "installation-source": "dist", "notification-url": "https://packages.drupal.org/8/downloads", "license": [ - "GPL-2.0+" + "GPL-2.0-or-later" ], "authors": [ { diff --git a/web/modules/recaptcha_v3/composer.json b/web/modules/recaptcha_v3/composer.json index ae49e61439..c6a2ed8790 100644 --- a/web/modules/recaptcha_v3/composer.json +++ b/web/modules/recaptcha_v3/composer.json @@ -2,7 +2,7 @@ "name": "drupal/recaptcha_v3", "description": "The reCaptcha V3 module provides integration with Google reCaptcha V3 and CAPTCHA module.", "type": "drupal-module", - "license": "GPL-2.0+", + "license": "GPL-2.0-or-later", "minimum-stability": "dev", "require": { "google/recaptcha": "^1.2", diff --git a/web/modules/recaptcha_v3/js/recaptcha_v3.js b/web/modules/recaptcha_v3/js/recaptcha_v3.js index 4ce96882ff..4d49bdda49 100644 --- a/web/modules/recaptcha_v3/js/recaptcha_v3.js +++ b/web/modules/recaptcha_v3/js/recaptcha_v3.js @@ -6,6 +6,44 @@ (function ($, Drupal) { 'use strict'; + function updateTokenElement(element) { + // Wait for grecaptcha to be loaded. + if (typeof grecaptcha === 'undefined') { + var timer = setInterval(function() { + if (typeof grecaptcha !== 'undefined' || !element) { + clearInterval(timer); + + if (element) { + doUpdateTokenElement(element); + } + } + }, 500); + } + else { + doUpdateTokenElement(element); + } + } + + function doUpdateTokenElement(element) { + grecaptcha.ready(function () { + if (!element) { + return; + } + + var $element = $(element); + + grecaptcha.execute( + $element.data('recaptchaV3SiteKey'), + { + action: $element.data('recaptchaV3Action') + } + ).then(function (token) { + $element.val(token); + $element.trigger('change'); + }); + }); + } + /** * Attach recaptcha response token from google with form. * @@ -14,23 +52,25 @@ Drupal.behaviors.reCaptchaV3 = { attach: function (context) { $('.recaptcha-v3-token', context).once('recaptcha-v3-token').each(function () { - var $token_element = $(this); - var timer = setInterval(function() { - if (grecaptcha !== undefined) { - grecaptcha.ready(function () { - grecaptcha.execute( - $token_element.data('recaptchaV3SiteKey'), - { - action: $token_element.data('recaptchaV3Action') - } - ).then(function (token) { - $token_element.val(token); - $token_element.trigger('change'); - }); - }); - clearInterval(timer) + var element = this; + + updateTokenElement(element); + + // Update the recaptcha tokens every 90 seconds. + // This seems to be the most robust way to always have valid recaptcha + // tokens when you don't have control over how the forms are being + // submitted. For example normal form submits are synchronous while + // Google Recaptcha v3 is asynchonous. + // A recaptcha token has a maximum lifetime of 120 seconds. + // https://developers.google.com/recaptcha/docs/v3 + var interval = setInterval(function () { + if (!element) { + clearInterval(interval); + } + else { + updateTokenElement(element); } - }, 500); + }, 90000); }); } }; diff --git a/web/modules/recaptcha_v3/recaptcha_v3.info.yml b/web/modules/recaptcha_v3/recaptcha_v3.info.yml index 68f5329c75..f1792d08d8 100644 --- a/web/modules/recaptcha_v3/recaptcha_v3.info.yml +++ b/web/modules/recaptcha_v3/recaptcha_v3.info.yml @@ -8,7 +8,7 @@ configure: recaptcha_v3.settings dependencies: - captcha:captcha -# Information added by Drupal.org packaging script on 2020-05-25 -version: '8.x-1.3' +# Information added by Drupal.org packaging script on 2021-01-08 +version: '8.x-1.4' project: 'recaptcha_v3' -datestamp: 1590393629 +datestamp: 1610128899 diff --git a/web/modules/recaptcha_v3/recaptcha_v3.module b/web/modules/recaptcha_v3/recaptcha_v3.module index ffb42e8c83..982c5ba2b4 100644 --- a/web/modules/recaptcha_v3/recaptcha_v3.module +++ b/web/modules/recaptcha_v3/recaptcha_v3.module @@ -13,6 +13,7 @@ use Drupal\Core\Routing\RouteMatchInterface; use Drupal\recaptcha_v3\ReCaptchaV3ActionInterface; use ReCaptcha\ReCaptcha; +use ReCaptcha\RequestMethod\CurlPost; /** * Implements hook_help(). @@ -84,9 +85,9 @@ function recaptcha_v3_element_info_alter(array &$info) { function recaptcha_v3_pre_captcha_element_process(array &$element, FormStateInterface $form_state, array &$complete_form) { // If form is processed input then recaptcha v3 response should be in // form values and need replace reCAPTCHA v3 element by fallback - // challenge before captcha module lement process callback, because otherwise - // in case of error form will not rebuild and recaptcha v3 element will - // return again. + // challenge before captcha module element process callback, because, + // otherwise, in case of error, form will not rebuild + // and recaptcha v3 element will return again. if ($form_state->isProcessingInput()) { \Drupal::moduleHandler()->loadInclude('captcha', 'inc', 'captcha'); @@ -126,6 +127,7 @@ function recaptcha_v3_captcha($op, $captcha_type = '', $captcha_sid = NULL) { $captcha['form']['captcha_response'] = [ '#type' => 'hidden', '#default_value' => '', + '#recaptcha_v3' => TRUE, '#attributes' => [ // Need add id, because element should have id or // 'selector' property should exist in #ajax array @@ -145,6 +147,11 @@ function recaptcha_v3_captcha($op, $captcha_type = '', $captcha_sid = NULL) { 'event' => 'change', ], ]; + // Flag that indicates that current captcha element is recaptcha_v3. + $captcha['form']['is_recaptcha_v3'] = [ + '#type' => 'hidden', + '#value' => 1, + ]; $captcha['solution'] = TRUE; $captcha['captcha_validate'] = 'recaptcha_v3_validate'; $captcha['cacheable'] = (bool) $config->get('cacheable'); @@ -169,8 +176,18 @@ function recaptcha_v3_captcha($op, $captcha_type = '', $captcha_sid = NULL) { * Set recaptcha v3 challenge if not already validated. */ function recaptcha_v3_post_captcha_element_process(array &$element, FormStateInterface $form_state, array &$complete_form) { - if ($form_state->getTemporaryValue('recaptcha_v3_action_name') && !$form_state->has('recaptcha_v3')) { - $element['#captcha_validate'] = 'recaptcha_v3_validate'; + // If value exist, then form was submitted. + if ($form_state->getTemporaryValue('recaptcha_v3_action_name')) { + $user_input = $form_state->getUserInput(); + // If value is empty, then fallback widget already used and no need to + // run recaptcha v3 validation. + if (!empty($user_input['is_recaptcha_v3'])) { + $element['#captcha_validate'] = 'recaptcha_v3_validate'; + } + } + // Hide description for the recaptcha v3 captcha element. + if (!empty($element['captcha_widgets']['captcha_response']['#recaptcha_v3'])) { + unset($element['#description']); } return $element; } @@ -179,18 +196,14 @@ function recaptcha_v3_post_captcha_element_process(array &$element, FormStateInt * CAPTCHA Callback; Validates the reCAPTCHA v3 code. */ function recaptcha_v3_validate($solution, $captcha_response, $element, FormStateInterface $form_state) { - // reCAPTCHA v3 was verified in one of the previous request. - if ($form_state->has('recaptcha_v3')) { - return (bool) $form_state->get(['recaptcha_v3', 'success']); - } - // Using user input instead of $captcha_response variable, because stupid - // recaptcha module wrongly use 'captcha_response' form element - - // instead of '#default_value' it using '#value' form api key: + // Using user input instead of $captcha_response variable, because + // recaptcha using '#value' form api key for the 'captcha_response' form + // element: // $captcha['form']['captcha_response'] = [ // '#type' => 'hidden', // '#value' => 'Google no captcha', // Problem is here // ]; - // so if using recaptcha as fallback challenge, $captcha_response is always + // So if using recaptcha as fallback challenge, $captcha_response is always // have 'Google no captcha' value. $user_input = $form_state->getUserInput(); if (!empty($user_input['captcha_response'])) { @@ -198,38 +211,56 @@ function recaptcha_v3_validate($solution, $captcha_response, $element, FormState } $captcha_type_challenge = $form_state->getTemporaryValue('recaptcha_v3_action_name'); - // Verify submitted reCAPTCHA v3 token. + /** @var ReCaptchaV3ActionInterface $recaptcha_v3 */ $recaptcha_v3 = ReCaptchaV3Action::load($captcha_type_challenge) ?? ReCaptchaV3Action::create([ 'id' => '', 'label' => '', 'threshold' => 1, 'challenge' => 'default', ]); + // Verify submitted reCAPTCHA v3 token. $verification_response = _recaptcha_v3_verify_captcha_response($recaptcha_v3, $captcha_response); if (!$verification_response['success']) { // If we here, then token verification failed. if ($verification_response['error-codes']) { $errors = []; + + $challenge = $recaptcha_v3->getChallenge(); + if ($challenge === 'default') { + $challenge = \Drupal::config('recaptcha_v3.settings') + ->get('default_challenge'); + } + foreach ($verification_response['error-codes'] as $code) { + // If we have fallback challenge then do not log the threshold errors. + if ($challenge && $code === 'score-threshold-not-met') { + continue; + } $errors[] = recaptcha_v3_error_by_code($code); } - $errors_string = implode(' ', $errors); - \Drupal::logger('recaptcha_v3')->error('Google reCAPTCHA v3 validation failed: @error', ['@error' => $errors_string]); + + if ($errors) { + $errors_string = implode(' ', $errors); + \Drupal::logger('recaptcha_v3')->error( + 'Google reCAPTCHA v3 validation failed: @error', + ['@error' => $errors_string] + ); + } } - $form_state->clearErrors(); + $error_message = \Drupal::config('recaptcha_v3.settings') ->get('error_message'); - if ($error_message) { - $form_state->setErrorByName('captcha_response', $error_message); - } } - else { + + // If captcha validated, then need to remove error related to the + // captcha_response element. Otherwise, for example, if fallback is + // captcha Math we will get error about exceeding input value length due to + // recaptcha v3 response is much longer than allowed for Math captcha. + // In another case if we have custom error message, then need to clear + // all current 'captcha_response' element error messages either. + if ($verification_response['success'] || !empty($error_message)) { $errors = $form_state->getErrors(); - // If captcha validated, then need to remove error related to the - // captcha_response element. Otherwise, for example, if fallback is - // captcha Math we will get error about exceeding input value length due to - // recaptcha v3 response is much longer than allowed for Math captcha. if (isset($errors['captcha_response'])) { $form_state->clearErrors(); foreach ($errors as $name => $error) { @@ -239,15 +270,10 @@ function recaptcha_v3_validate($solution, $captcha_response, $element, FormState } } } - // Save captcha response in $form_state storage to prevent - // further validation requests. - $form_state->set('recaptcha_v3', $verification_response); - // Need to cache form, because previous line does not make sense - // because form will not cached otherwise. Probably this is wrong because - // if form set for rebuilding it will not cached anyway. - // @todo need to check if need to move next line to the element preprocess callback. - $form_state->setCached(); + if(!empty($error_message)) { + $form_state->setErrorByName('captcha_response', $error_message); + } return (bool) $verification_response['success']; } @@ -284,7 +310,7 @@ function recaptcha_v3_ajax_callback(array $form, FormStateInterface $form_state) function _recaptcha_v3_verify_captcha_response(ReCaptchaV3ActionInterface $recaptcha_v_3_action, $captcha_response) { $request = Drupal::request(); $config = Drupal::config('recaptcha_v3.settings'); - $recaptcha = new ReCaptcha($config->get('secret_key')); + $recaptcha = new ReCaptcha($config->get('secret_key'), function_exists('curl_version') ? new CurlPost() : null); if ($config->get('verify_hostname')) { $recaptcha->setExpectedHostname($request->getHost()); diff --git a/web/modules/recaptcha_v3/src/Form/ReCaptchaV3ActionForm.php b/web/modules/recaptcha_v3/src/Form/ReCaptchaV3ActionForm.php index 5bba600e2b..e8f648baf6 100644 --- a/web/modules/recaptcha_v3/src/Form/ReCaptchaV3ActionForm.php +++ b/web/modules/recaptcha_v3/src/Form/ReCaptchaV3ActionForm.php @@ -57,6 +57,7 @@ public function form(array $form, FormStateInterface $form_state) { $form['id'] = [ '#type' => 'machine_name', '#default_value' => $recaptcha_v3_action->id(), + '#required' => TRUE, '#machine_name' => [ 'exists' => [ReCaptchaV3Action::class, 'load'], ], @@ -69,6 +70,7 @@ public function form(array $form, FormStateInterface $form_state) { '#min' => 0, '#max' => 1, '#step' => 0.1, + '#required' => TRUE, '#default_value' => $recaptcha_v3_action->getThreshold(), ]; diff --git a/web/modules/recaptcha_v3/src/Form/ReCaptchaV3SettingsForm.php b/web/modules/recaptcha_v3/src/Form/ReCaptchaV3SettingsForm.php index 450e4df4c9..da459e1c00 100644 --- a/web/modules/recaptcha_v3/src/Form/ReCaptchaV3SettingsForm.php +++ b/web/modules/recaptcha_v3/src/Form/ReCaptchaV3SettingsForm.php @@ -165,6 +165,7 @@ public function submitForm(array &$form, FormStateInterface $form_state) { ->set('verify_hostname', $values['verify_hostname']) ->set('default_challenge', $values['default_challenge']) ->set('error_message', $values['error_message']) + ->set('cacheable', $values['cacheable']) ->save(); parent::submitForm($form, $form_state); -- GitLab