From c746bf529d38974603cea05ca31e49b94d416599 Mon Sep 17 00:00:00 2001 From: Robert Ristroph Date: Wed, 12 Feb 2025 23:18:58 +0000 Subject: [PATCH] WS-503: Upgraded to ReCAPTCHA v3 on back end. --- composer.json | 1 + composer.lock | 56 ++++++++++++++++++- config/default/captcha.settings.yml | 2 +- config/default/core.extension.yml | 1 + .../entity_clone.cloneable_entities.yml | 1 + config/default/recaptcha.settings.yml | 4 +- ...ecaptcha_v3.recaptcha_v3_action.submit.yml | 8 +++ config/default/recaptcha_v3.settings.yml | 10 ++++ .../resource/WebformSubmissionResource.php | 56 ++++++++++++++++++- 9 files changed, 133 insertions(+), 6 deletions(-) create mode 100644 config/default/recaptcha_v3.recaptcha_v3_action.submit.yml create mode 100644 config/default/recaptcha_v3.settings.yml diff --git a/composer.json b/composer.json index 4a96a4948..d544bfab0 100644 --- a/composer.json +++ b/composer.json @@ -89,6 +89,7 @@ "drupal/quickedit": "^1.0", "drupal/rdf": "^2.0", "drupal/recaptcha": "^3.4", + "drupal/recaptcha_v3": "^2.0", "drupal/restui": "^1.21.0", "drupal/roleassign": "^2.0.0", "drupal/rules": "^3.0@alpha", diff --git a/composer.lock b/composer.lock index bc7f25168..7c70de750 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": "8c7f4f09ec5659dc7fc111891a74e7de", + "content-hash": "9d6ad21b58f92a81c04411963fcd0ce1", "packages": [ { "name": "acquia/blt", @@ -8557,6 +8557,60 @@ "issues": "https://www.drupal.org/project/issues/recaptcha" } }, + { + "name": "drupal/recaptcha_v3", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://git.drupalcode.org/project/recaptcha_v3.git", + "reference": "2.0.3" + }, + "dist": { + "type": "zip", + "url": "https://ftp.drupal.org/files/projects/recaptcha_v3-2.0.3.zip", + "reference": "2.0.3", + "shasum": "51bdbfd4602550fb4822ba8725e8899e23d4568a" + }, + "require": { + "drupal/captcha": "^2.0", + "drupal/core": "^10 || ^11", + "google/recaptcha": "^1.3" + }, + "type": "drupal-module", + "extra": { + "drupal": { + "version": "2.0.3", + "datestamp": "1723447296", + "security-coverage": { + "status": "covered", + "message": "Covered by Drupal's security advisory policy" + } + } + }, + "notification-url": "https://packages.drupal.org/8/downloads", + "license": [ + "GPL-2.0-or-later" + ], + "authors": [ + { + "name": "b-prod", + "homepage": "https://www.drupal.org/user/407852" + }, + { + "name": "dench0", + "homepage": "https://www.drupal.org/user/896504" + }, + { + "name": "majid.ali", + "homepage": "https://www.drupal.org/user/1271330" + } + ], + "description": "The reCaptcha V3 module provides integration with Google reCaptcha V3 and CAPTCHA module.", + "homepage": "https://www.drupal.org/project/recaptcha_v3", + "support": { + "source": "https://git.drupalcode.org/project/recaptcha_v3" + } + }, { "name": "drupal/restui", "version": "1.21.0", diff --git a/config/default/captcha.settings.yml b/config/default/captcha.settings.yml index 1b43bfb45..2e35f3fd4 100644 --- a/config/default/captcha.settings.yml +++ b/config/default/captcha.settings.yml @@ -2,7 +2,7 @@ _core: default_config_hash: QDFjOXYIYVwCPQYHY4wAx4DUqOEkNaZokIx6DGApR9I enable_globally: 0 enable_globally_on_admin_routes: false -default_challenge: recaptcha/reCAPTCHA +default_challenge: recaptcha_v3/submit description: 'This question is for testing whether or not you are a human visitor and to prevent automated spam submissions.' title: CAPTCHA administration_mode: true diff --git a/config/default/core.extension.yml b/config/default/core.extension.yml index 122db4fc1..5e30cbdb5 100644 --- a/config/default/core.extension.yml +++ b/config/default/core.extension.yml @@ -139,6 +139,7 @@ module: quickedit: 0 rdf: 0 recaptcha: 0 + recaptcha_v3: 0 rest: 0 restui: 0 roleassign: 0 diff --git a/config/default/entity_clone.cloneable_entities.yml b/config/default/entity_clone.cloneable_entities.yml index 45070952c..bd79e5dc9 100644 --- a/config/default/entity_clone.cloneable_entities.yml +++ b/config/default/entity_clone.cloneable_entities.yml @@ -71,3 +71,4 @@ cloneable_entities: - mailer_policy - config_split - captcha_point + - recaptcha_v3_action diff --git a/config/default/recaptcha.settings.yml b/config/default/recaptcha.settings.yml index 7db674329..f16a7f271 100644 --- a/config/default/recaptcha.settings.yml +++ b/config/default/recaptcha.settings.yml @@ -1,7 +1,7 @@ _core: default_config_hash: ByOVf1cU9r5NkZ4ieBJ7k9sUid6jb03ojMN1gjJ0-OU -site_key: 6Le3tV0qAAAAAJB8fxsSPxs3v46Zo69t2IaRFU5C -secret_key: 6Le3tV0qAAAAAB4omG0eYGfM4AaP7pfEuFGD12gE +site_key: '' +secret_key: '' verify_hostname: false use_globally: false widget: diff --git a/config/default/recaptcha_v3.recaptcha_v3_action.submit.yml b/config/default/recaptcha_v3.recaptcha_v3_action.submit.yml new file mode 100644 index 000000000..4d0f1b506 --- /dev/null +++ b/config/default/recaptcha_v3.recaptcha_v3_action.submit.yml @@ -0,0 +1,8 @@ +uuid: 07cf3e20-e94b-4432-ad26-e748d2c078a7 +langcode: en +status: true +dependencies: { } +id: submit +label: submit +threshold: 0.5 +challenge: recaptcha/reCAPTCHA diff --git a/config/default/recaptcha_v3.settings.yml b/config/default/recaptcha_v3.settings.yml new file mode 100644 index 000000000..405f3e2fc --- /dev/null +++ b/config/default/recaptcha_v3.settings.yml @@ -0,0 +1,10 @@ +_core: + default_config_hash: 6xvuojdlZ8-8Q7yJyRldPaE0UK_YijOlDo6HvURmW_E +site_key: 6Le3tV0qAAAAAJB8fxsSPxs3v46Zo69t2IaRFU5C +secret_key: 6Le3tV0qAAAAAB4omG0eYGfM4AaP7pfEuFGD12gE +hide_badge: false +verify_hostname: true +default_challenge: '' +error_message: 'Anti-bot verification failed.' +cacheable: false +library_use_recaptcha_net: true diff --git a/docroot/modules/custom/foia_api/src/Plugin/rest/resource/WebformSubmissionResource.php b/docroot/modules/custom/foia_api/src/Plugin/rest/resource/WebformSubmissionResource.php index c6dea95ae..927cc1f47 100644 --- a/docroot/modules/custom/foia_api/src/Plugin/rest/resource/WebformSubmissionResource.php +++ b/docroot/modules/custom/foia_api/src/Plugin/rest/resource/WebformSubmissionResource.php @@ -198,7 +198,7 @@ public function post($data) { // Validate recaptcha. $captcha_errors = []; $captcha = $values['data']['captcha']; - $captcha_errors = $this->validateCaptcha($captcha); + $captcha_errors = $this->validateCaptchaV3($captcha); if (!empty($captcha_errors)) { // The react front end will know that 401 means the captcha failed and @@ -270,7 +270,7 @@ public function post($data) { } /** - * Validates that the submitted reCAPTCHA ( google ) is correct. + * Validates that the submitted reCAPTCHA_v2 ( google ) is correct. * * @param string $captcha * The submitted captcha value. @@ -364,6 +364,58 @@ protected function validateCaptcha(string $captcha) { return $errors; } + /** + * CAPTCHA Callback; Validates the reCAPTCHA v3 code. + * + * Copied and modified from recaptcha_v3.module. + */ + protected function validateCaptchaV3($captcha_response) { + + // This is hardwired on the reactjs side. + $captcha_type_challenge = 'submit'; + /** @var \Drupal\recaptcha_v3\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); + } + + if ($errors) { + $errors_string = implode(' ', $errors); + \Drupal::logger('recaptcha_v3')->error( + 'Google reCAPTCHA v3 validation failed: @error', + ['@error' => $errors_string] + ); + } + } + + $error_message = \Drupal::config('recaptcha_v3.settings')->get('error_message'); + } + + return (bool) $verification_response['success']; + } + /** * Logs a submission with HTTP status code, message, and optional component. *