From 0c63f03db46265427ea3c3c40c09e28852f721f0 Mon Sep 17 00:00:00 2001 From: mrgoldy Date: Sat, 14 Mar 2020 14:42:08 +0100 Subject: [PATCH 1/6] [ticket/15937] Google reCAPTCHA v3 Plugin PHPBB3-15937 --- phpBB/adm/style/captcha_recaptcha_v3.html | 15 + phpBB/adm/style/captcha_recaptcha_v3_acp.html | 111 ++++++++ phpBB/assets/javascript/core.js | 103 ++++--- .../default/container/services_captcha.yml | 8 + phpBB/language/en/captcha_recaptcha.php | 29 +- phpBB/phpbb/captcha/plugins/recaptcha_v3.php | 263 ++++++++++++++++++ .../prosilver/template/captcha_recaptcha.html | 1 + .../template/captcha_recaptcha_v3.html | 13 + 8 files changed, 507 insertions(+), 36 deletions(-) create mode 100644 phpBB/adm/style/captcha_recaptcha_v3.html create mode 100644 phpBB/adm/style/captcha_recaptcha_v3_acp.html create mode 100644 phpBB/phpbb/captcha/plugins/recaptcha_v3.php create mode 100644 phpBB/styles/prosilver/template/captcha_recaptcha_v3.html diff --git a/phpBB/adm/style/captcha_recaptcha_v3.html b/phpBB/adm/style/captcha_recaptcha_v3.html new file mode 100644 index 0000000000..0f2d279fe9 --- /dev/null +++ b/phpBB/adm/style/captcha_recaptcha_v3.html @@ -0,0 +1,15 @@ +{% if S_RECAPTCHA_AVAILABLE %} +
+
 
+
+ + + {{ lang('RECAPTCHA_INVISIBLE') }} + +
+
+{% else %} + {{ lang('RECAPTCHA_NOT_AVAILABLE') }} +{% endif %} diff --git a/phpBB/adm/style/captcha_recaptcha_v3_acp.html b/phpBB/adm/style/captcha_recaptcha_v3_acp.html new file mode 100644 index 0000000000..8a1e0a7bce --- /dev/null +++ b/phpBB/adm/style/captcha_recaptcha_v3_acp.html @@ -0,0 +1,111 @@ +{% include 'overall_header.html' %} + + + +

{{ lang('ACP_VC_SETTINGS') }}

+

{{ lang('ACP_VC_SETTINGS_EXPLAIN') }}

+ +
+
+ {{ lang('GENERAL_OPTIONS') }} + +
+
+ +
{{ lang('RECAPTCHA_V3_PUBLIC_EXPLAIN') }} +
+
+ +
+
+
+
+ +
{{ lang('RECAPTCHA_V3_PRIVATE_EXPLAIN') }} +
+
+ +
+
+ +
+
+ +
{{ lang('RECAPTCHA_V3_DOMAIN_EXPLAIN') }} +
+
+ {% for domain in RECAPTCHA_V3_DOMAINS %} + + {% endfor %} +
+
+
+
+ +
{{ lang('RECAPTCHA_V3_METHOD_EXPLAIN') }} +
+
+ {% for method, constant in RECAPTCHA_V3_METHODS %} + + {% endfor %} +
+
+
+ +
+ {{ lang('RECAPTCHA_V3_THRESHOLDS') }} +

{{ lang('RECAPTCHA_V3_THRESHOLDS_EXPLAIN') }}

+ +
+
+ +
{{ lang('RECAPTCHA_V3_THRESHOLD_EXPLAIN') }} +
+
+
+ + {% for threshold in thresholds %} +
+
+
+
+ {% endfor %} +
+ +
+ {{ lang('PREVIEW') }} + + {% if PREVIEW %} +
+

{{ lang('WARNING') }}

+

{{ lang('CAPTCHA_PREVIEW_MSG') }}

+
+ {% endif %} + + {% include CAPTCHA_PREVIEW %} +
+ +
+ {{ lang('ACP_SUBMIT_CHANGES') }} + +

+ + + + + + {{ S_FORM_TOKEN }} +

+
+
+ +{% include 'overall_footer.html' %} diff --git a/phpBB/assets/javascript/core.js b/phpBB/assets/javascript/core.js index bedbd23532..c116966eea 100644 --- a/phpBB/assets/javascript/core.js +++ b/phpBB/assets/javascript/core.js @@ -1745,49 +1745,81 @@ phpbb.lazyLoadAvatars = function loadAvatars() { }); }; -var recaptchaForm = $('.g-recaptcha').parents('form'); -var submitButton = null; -var programaticallySubmitted = false; +phpbb.recaptcha = { + button: null, + ready: false, -phpbb.recaptchaOnLoad = function () { - // Listen to submit buttons in order to know which one was pressed - $('input[type="submit"]').each(function () { - $(this).on('click', function () { - submitButton = this; + token: $('input[name="recaptcha_token"]'), + form: $('.g-recaptcha').parents('form'), + v3: $('[data-recaptcha-v3]'), + + load: function() { + phpbb.recaptcha.bindButton(); + phpbb.recaptcha.bindForm(); + }, + bindButton: function() { + phpbb.recaptcha.form.find('input[type="submit"]').on('click', function() { + // Listen to all the submit buttons for the form that has reCAPTCHA protection, + // and store it so we can click the exact same button later on when we are ready. + phpbb.recaptcha.button = this; }); - }); + }, + bindForm: function() { + phpbb.recaptcha.form.on('submit', function(e) { + // If ready is false, it means the user pressed a submit button. + // And the form was not submitted by us, after the token was loaded. + if (!phpbb.recaptcha.ready) { + // If version 3 is used, we need to make a different execution, + // including the action and the site key. + if (phpbb.recaptcha.v3.length) { + grecaptcha.execute( + phpbb.recaptcha.v3.data('recaptcha-v3'), + {action: phpbb.recaptcha.v3.val()} + ).then(function(token) { + // Place the token inside the form + phpbb.recaptcha.token.val(token); - recaptchaForm.on('submit', function (e) { - if (!programaticallySubmitted) { - grecaptcha.execute(); - e.preventDefault(); - } - }); -} + // And now we submit the form. + phpbb.recaptcha.submitForm(); + }); + } else { + // Regular version 2 execution + grecaptcha.execute(); + } -phpbb.recaptchaOnSubmit = function () { - programaticallySubmitted = true; - // If concrete button was clicked (e.g. preview instead of submit), - // let's trigger the same action - if (submitButton) { - submitButton.click(); - } else { - // Rename input[name="submit"] so that we can submit the form - if (typeof recaptchaForm.submit !== 'function') { - recaptchaForm.submit.name = 'submit_btn'; + // Do not submit the form + e.preventDefault(); + } + }); + }, + submitForm: function() { + // Now we are ready, so set it to true. + // so the 'submit' event doesn't run multiple times. + phpbb.recaptcha.ready = true; + + if (phpbb.recaptcha.button) { + // If there was a specific button pressed initially, trigger the same button + phpbb.recaptcha.button.click(); + } else { + if (typeof phpbb.recaptcha.form.submit !== 'function') { + // Rename input[name="submit"] so that we can submit the form + phpbb.recaptcha.form.submit.name = 'submit_btn'; + } + + phpbb.recaptcha.form.submit(); } - recaptchaForm.submit(); } -} +}; -// reCAPTCHA doesn't accept callback functions nested inside objects +// reCAPTCHA v2 doesn't accept callback functions nested inside objects // so we need to make this helper functions here window.phpbbRecaptchaOnLoad = function() { - phpbb.recaptchaOnLoad(); -} + phpbb.recaptcha.load(); +}; + window.phpbbRecaptchaOnSubmit = function() { - phpbb.recaptchaOnSubmit(); -} + phpbb.recaptcha.submitForm(); +}; $(window).on('load', phpbb.lazyLoadAvatars); @@ -1795,6 +1827,11 @@ $(window).on('load', phpbb.lazyLoadAvatars); * Apply code editor to all textarea elements with data-bbcode attribute */ $(function() { + // reCAPTCHA v3 needs to be initialized + if (phpbb.recaptcha.v3.length) { + phpbb.recaptcha.load(); + } + $('textarea[data-bbcode]').each(function() { phpbb.applyCodeEditor(this); }); diff --git a/phpBB/config/default/container/services_captcha.yml b/phpBB/config/default/container/services_captcha.yml index e462c43bb8..ba10264093 100644 --- a/phpBB/config/default/container/services_captcha.yml +++ b/phpBB/config/default/container/services_captcha.yml @@ -57,3 +57,11 @@ services: - [set_name, [core.captcha.plugins.recaptcha]] tags: - { name: captcha.plugins } + + core.captcha.plugins.recaptcha_v3: + class: phpbb\captcha\plugins\recaptcha_v3 + shared: false + calls: + - ['set_name', ['core.captcha.plugins.recaptcha_v3']] + tags: + - { name: captcha.plugins } diff --git a/phpBB/language/en/captcha_recaptcha.php b/phpBB/language/en/captcha_recaptcha.php index 68546ae73c..fcdde312c2 100644 --- a/phpBB/language/en/captcha_recaptcha.php +++ b/phpBB/language/en/captcha_recaptcha.php @@ -37,16 +37,39 @@ if (empty($lang) || !is_array($lang)) // in a url you again do not need to specify an order e.g., 'Click %sHERE%s' is fine $lang = array_merge($lang, array( - 'RECAPTCHA_LANG' => 'en-GB', // Find the language/country code on https://developers.google.com/recaptcha/docs/language - If no code exists for your language you can use "en" or leave the string empty - 'RECAPTCHA_NOT_AVAILABLE' => 'In order to use reCaptcha, you must create an account on www.google.com/recaptcha.', - 'CAPTCHA_RECAPTCHA' => 'reCaptcha', + // Find the language/country code on https://developers.google.com/recaptcha/docs/language + // If no code exists for your language you can use "en" or leave the string empty + 'RECAPTCHA_LANG' => 'en-GB', + + 'CAPTCHA_RECAPTCHA' => 'reCaptcha v2', + 'CAPTCHA_RECAPTCHA_V3' => 'reCaptcha v3', + 'RECAPTCHA_INCORRECT' => 'The solution you provided was incorrect', 'RECAPTCHA_NOSCRIPT' => 'Please enable JavaScript in your browser to load the challenge.', + 'RECAPTCHA_NOT_AVAILABLE' => 'In order to use reCaptcha, you must create an account on www.google.com/recaptcha.', 'RECAPTCHA_PUBLIC' => 'Site key', 'RECAPTCHA_PUBLIC_EXPLAIN' => 'Your site reCAPTCHA key. Keys can be obtained on www.google.com/recaptcha. Please, use reCAPTCHA v2 > Invisible reCAPTCHA badge type.', + 'RECAPTCHA_V3_PUBLIC_EXPLAIN' => 'Your site reCAPTCHA key. Keys can be obtained on www.google.com/recaptcha. Please, use reCAPTCHA v3.', 'RECAPTCHA_PRIVATE' => 'Secret key', 'RECAPTCHA_PRIVATE_EXPLAIN' => 'Your secret reCAPTCHA key. Keys can be obtained on www.google.com/recaptcha. Please, use reCAPTCHA v2 > Invisible reCAPTCHA badge type.', + 'RECAPTCHA_V3_PRIVATE_EXPLAIN' => 'Your secret reCAPTCHA key. Keys can be obtained on www.google.com/recaptcha. Please, use reCAPTCHA v3.', 'RECAPTCHA_INVISIBLE' => 'This CAPTCHA is actually invisible. To verify that it works, a small icon should appear in right bottom corner of this page.', + + 'RECAPTCHA_V3_DOMAIN' => 'Request domain', + 'RECAPTCHA_V3_DOMAIN_EXPLAIN' => 'The domain to fetch the script from and to use when verifying the request.
Use recaptcha.net when google.com is not accessible.', + 'RECAPTCHA_V3_METHOD' => 'Request method', + 'RECAPTCHA_V3_METHOD_EXPLAIN' => 'The method to use when verifying the request.
Disabled options are not available within your setup.', + 'RECAPTCHA_V3_METHOD_CURL' => 'cURL', + 'RECAPTCHA_V3_METHOD_POST' => 'POST', + 'RECAPTCHA_V3_METHOD_SOCKET' => 'Socket', + 'RECAPTCHA_V3_THRESHOLD' => 'Default threshold', + 'RECAPTCHA_V3_THRESHOLD_EXPLAIN' => 'Used when none of the other actions are applicable.', + 'RECAPTCHA_V3_THRESHOLD_LOGIN' => 'Login threshold', + 'RECAPTCHA_V3_THRESHOLD_POST' => 'Post threshold', + 'RECAPTCHA_V3_THRESHOLD_REGISTER' => 'Register threshold', + 'RECAPTCHA_V3_THRESHOLD_REPORT' => 'Report threshold', + 'RECAPTCHA_V3_THRESHOLDS' => 'Thresholds', + 'RECAPTCHA_V3_THRESHOLDS_EXPLAIN' => 'reCAPTCHA v3 returns a score (1.0 is very likely a good interaction, 0.0 is very likely a bot). Here you can set the minimum score per action.', )); diff --git a/phpBB/phpbb/captcha/plugins/recaptcha_v3.php b/phpBB/phpbb/captcha/plugins/recaptcha_v3.php new file mode 100644 index 0000000000..5c6b6d61e9 --- /dev/null +++ b/phpBB/phpbb/captcha/plugins/recaptcha_v3.php @@ -0,0 +1,263 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\captcha\plugins; + +/** + * Google reCaptcha v3 plugin. + */ +class recaptcha_v3 extends captcha_abstract +{ + const CURL = 'curl'; + const POST = 'post'; + const SOCKET = 'socket'; + + const GOOGLE = 'google.com'; + const RECAPTCHA = 'recaptcha.net'; + + static protected $action = 'default'; + static protected $actions = [ + CONFIRM_REG => 'register', + CONFIRM_LOGIN => 'login', + CONFIRM_POST => 'post', + CONFIRM_REPORT => 'report', + ]; + + public function execute() + { + } + + public function execute_demo() + { + } + + public function get_generator_class() + { + throw new \Exception('No generator class given.'); + } + + public function get_name() + { + return 'CAPTCHA_RECAPTCHA_V3'; + } + + public function has_config() + { + return true; + } + + public function init($type) + { + /** + * @var \phpbb\language\language $language Language object + */ + global $language; + + $language->add_lang('captcha_recaptcha'); + + parent::init($type); + } + + public function is_available() + { + /** + * @var \phpbb\config\config $config Config object + * @var \phpbb\language\language $language Language object + */ + global $config, $language; + + $language->add_lang('captcha_recaptcha'); + + return ($config->offsetGet('recaptcha_v3_key') ?? false) + && ($config->offsetGet('recaptcha_v3_secret') ?? false); + } + + public function acp_page($id, $module) + { + /** + * @var \phpbb\config\config $config Config object + * @var \phpbb\language\language $language Language object + * @var \phpbb\log\log $phpbb_log Log object + * @var \phpbb\request\request $request Request object + * @var \phpbb\template\template $template Template object + * @var \phpbb\user $user User object + */ + global $config, $language, $phpbb_log, $request, $template, $user; + + $module->tpl_name = 'captcha_recaptcha_v3_acp'; + $module->page_title = 'ACP_VC_SETTINGS'; + + $form_key = 'acp_captcha'; + add_form_key($form_key); + + if ($request->is_set_post('submit')) + { + if (!check_form_key($form_key)) + { + trigger_error($language->lang('FORM_INVALID') . adm_back_link($module->u_action), E_USER_WARNING); + } + + $config->set('recaptcha_v3_key', $request->variable('recaptcha_v3_key', '', true)); + $config->set('recaptcha_v3_secret', $request->variable('recaptcha_v3_secret', '', true)); + $config->set('recaptcha_v3_domain', $request->variable('recaptcha_v3_domain', '', true)); + $config->set('recaptcha_v3_method', $request->variable('recaptcha_v3_method', '', true)); + $config->set('recaptcha_v3_threshold', $request->variable('recaptcha_v3_threshold', 0.50)); + + foreach (self::$actions as $action) + { + $config->set("recaptcha_v3_threshold_{$action}", $request->variable("recaptcha_v3_threshold_{$action}", 0.50)); + } + + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_CONFIG_VISUAL'); + + trigger_error($language->lang('CONFIG_UPDATED') . adm_back_link($module->u_action)); + } + + foreach (self::$actions as $action) + { + $template->assign_block_vars('thresholds', [ + 'key' => "recaptcha_v3_threshold_{$action}", + 'value' => $config["recaptcha_v3_threshold_{$action}"] ?? 0.5, + ]); + } + + $template->assign_vars([ + 'CAPTCHA_NAME' => $this->get_service_name(), + 'CAPTCHA_PREVIEW' => $this->get_demo_template($id), + + 'RECAPTCHA_V3_KEY' => $config['recaptcha_v3_key'] ?? '', + 'RECAPTCHA_V3_SECRET' => $config['recaptcha_v3_secret'] ?? '', + 'RECAPTCHA_V3_THRESHOLD' => $config['recaptcha_v3_threshold'] ?? 0.5, + + 'RECAPTCHA_V3_DOMAIN' => $config['recaptcha_v3_domain'] ?? self::GOOGLE, + 'RECAPTCHA_V3_DOMAINS' => [self::GOOGLE, self::RECAPTCHA], + + 'RECAPTCHA_V3_METHOD' => $config['recaptcha_v3_method'] ?? self::POST, + 'RECAPTCHA_V3_METHODS' => [ + 'POST' => self::POST, + 'CURL' => self::CURL, + 'SOCKET' => self::SOCKET, + ], + + 'S_RECAPTCHA_V3_CURL' => extension_loaded('curl') && function_exists('curl_init'), + 'S_RECAPTCHA_V3_POST' => ini_get('allow_url_fopen') && function_exists('file_get_contents'), + 'S_RECAPTCHA_V3_SOCKET' => function_exists('fsockopen'), + + 'U_ACTION' => $module->u_action, + ]); + } + + public function get_demo_template($id) + { + return $this->get_template(); + } + + public function get_template() + { + /** + * @var \phpbb\config\config $config Config object + * @var \phpbb\language\language $language Language object + * @var \phpbb\template\template $template Template object + * @var string $phpbb_root_path phpBB root path + * @var string $phpEx php File extensions + */ + global $config, $language, $template, $phpbb_root_path, $phpEx; + + if ($this->is_solved()) + { + return false; + } + + $contact = phpbb_get_board_contact_link($config, $phpbb_root_path, $phpEx); + $explain = $this->type !== CONFIRM_POST ? 'CONFIRM_EXPLAIN' : 'POST_CONFIRM_EXPLAIN'; + + $domain = $config['recaptcha_v3_domain'] ?? self::GOOGLE; + $render = $config['recaptcha_v3_key'] ?? ''; + + $template->assign_vars([ + 'CONFIRM_EXPLAIN' => $language->lang($explain, '', ''), + + 'RECAPTCHA_ACTION' => self::$actions[$this->type] ?? self::$action, + 'RECAPTCHA_KEY' => $config['recaptcha_v3_key'] ?? '', + 'U_RECAPTCHA_SCRIPT' => sprintf('//%s/recaptcha/api.js?render=%s', $domain, $render), + + 'S_CONFIRM_CODE' => true, + 'S_RECAPTCHA_AVAILABLE' => $this->is_available(), + 'S_TYPE' => $this->type, + ]); + + return 'captcha_recaptcha_v3.html'; + } + + function validate() + { + if (!parent::validate()) + { + return false; + } + + return $this->recaptcha_check_answer(); + } + + function recaptcha_check_answer() + { + /** + * @var \phpbb\config\config $config Config object + * @var \phpbb\language\language $language Language object + * @var \phpbb\request\request $request Request object + * @var \phpbb\user $user User object + */ + global $config, $language, $request, $user; + + $action = $request->variable('recaptcha_action', self::$action, true); + $token = $request->variable('recaptcha_token', '', true); + $threshold = (double) $config["recaptcha_v3_threshold_{$action}"] ?? $config['recaptcha_v3_threshold'] ?? 0.5; + + // Discard spam submissions + if (empty($token)) + { + return $language->lang('RECAPTCHA_INCORRECT'); + } + + switch ($config['recaptcha_v3_method'] ?? '') + { + case self::CURL: + $method = new \ReCaptcha\RequestMethod\CurlPost(); + break; + + case self::SOCKET: + $method = new \ReCaptcha\RequestMethod\SocketPost(); + break; + + case self::POST: + default: + $method = new \ReCaptcha\RequestMethod\Post(); + break; + } + + $recaptcha = new \ReCaptcha\ReCaptcha($config['recaptcha_v3_secret'], $method); + + $result = $recaptcha->setExpectedAction($action) + ->setScoreThreshold($threshold) + ->verify($token, $user->ip); + + if ($result->isSuccess()) + { + $this->solved = true; + + return false; + } + + return $language->lang('RECAPTCHA_INCORRECT'); + } +} diff --git a/phpBB/styles/prosilver/template/captcha_recaptcha.html b/phpBB/styles/prosilver/template/captcha_recaptcha.html index 8fc7faa50f..a62ea3c297 100644 --- a/phpBB/styles/prosilver/template/captcha_recaptcha.html +++ b/phpBB/styles/prosilver/template/captcha_recaptcha.html @@ -3,6 +3,7 @@
{L_RECAPTCHA_NOSCRIPT}
{% INCLUDEJS RECAPTCHA_SERVER ~ '.js?onload=phpbbRecaptchaOnLoad&hl=' ~ lang('RECAPTCHA_LANG') %} + {# The g-recaptcha class is used in JavaScript #}
{L_RECAPTCHA_NOT_AVAILABLE} diff --git a/phpBB/styles/prosilver/template/captcha_recaptcha_v3.html b/phpBB/styles/prosilver/template/captcha_recaptcha_v3.html new file mode 100644 index 0000000000..76e5ef56cc --- /dev/null +++ b/phpBB/styles/prosilver/template/captcha_recaptcha_v3.html @@ -0,0 +1,13 @@ +{% if S_RECAPTCHA_AVAILABLE %} + + + + + {# The g-recaptcha class is used in JavaScript #} + + +{% else %} + {{ lang('RECAPTCHA_NOT_AVAILABLE') }} +{% endif %} From 01b966c6646ef7dcb1ab3efcf845c66454d24753 Mon Sep 17 00:00:00 2001 From: mrgoldy Date: Sat, 14 Mar 2020 15:01:49 +0100 Subject: [PATCH 2/6] [ticket/15937] Google reCAPTCHA v3 Plugin comments PHPBB3-15937 --- phpBB/phpbb/captcha/plugins/recaptcha_v3.php | 103 +++++++++++++++++-- 1 file changed, 93 insertions(+), 10 deletions(-) diff --git a/phpBB/phpbb/captcha/plugins/recaptcha_v3.php b/phpBB/phpbb/captcha/plugins/recaptcha_v3.php index 5c6b6d61e9..f04d44a275 100644 --- a/phpBB/phpbb/captcha/plugins/recaptcha_v3.php +++ b/phpBB/phpbb/captcha/plugins/recaptcha_v3.php @@ -14,18 +14,27 @@ namespace phpbb\captcha\plugins; /** - * Google reCaptcha v3 plugin. + * Google reCAPTCHA v3 plugin. */ class recaptcha_v3 extends captcha_abstract { - const CURL = 'curl'; - const POST = 'post'; - const SOCKET = 'socket'; + /** + * Possible request methods to verify the token. + */ + const CURL = 'curl'; + const POST = 'post'; + const SOCKET = 'socket'; - const GOOGLE = 'google.com'; - const RECAPTCHA = 'recaptcha.net'; + /** + * Possible domain names to load the script and verify the token. + */ + const GOOGLE = 'google.com'; + const RECAPTCHA = 'recaptcha.net'; + /** @var string Default action when no other applies */ static protected $action = 'default'; + + /** @var array CAPTCHA types mapped to their action with threshold */ static protected $actions = [ CONFIRM_REG => 'register', CONFIRM_LOGIN => 'login', @@ -33,29 +42,67 @@ class recaptcha_v3 extends captcha_abstract CONFIRM_REPORT => 'report', ]; + /** + * Execute. + * + * Not needed by this CAPTCHA plugin. + * + * @return void + */ public function execute() { } + /** + * Execute demo. + * + * Not needed by this CAPTCHA plugin. + * + * @return void + */ public function execute_demo() { } + /** + * Get generator class. + * + * Not needed by this CAPTCHA plugin. + * + * @throws \Exception + * @return void + */ public function get_generator_class() { throw new \Exception('No generator class given.'); } + /** + * Get CAPTCHA plugin name. + * + * @return string + */ public function get_name() { return 'CAPTCHA_RECAPTCHA_V3'; } + /** + * Indicator that this CAPTCHA plugin requires configuration. + * + * @return bool + */ public function has_config() { return true; } + /** + * Initialize this CAPTCHA plugin. + * + * @param int $type The CAPTCHA type + * @return void + */ public function init($type) { /** @@ -68,6 +115,11 @@ class recaptcha_v3 extends captcha_abstract parent::init($type); } + /** + * Whether or not this CAPTCHA plugin is available. + * + * @return bool + */ public function is_available() { /** @@ -82,6 +134,13 @@ class recaptcha_v3 extends captcha_abstract && ($config->offsetGet('recaptcha_v3_secret') ?? false); } + /** + * Create the ACP page for configuring this CAPTCHA plugin. + * + * @param string $id The ACP module identifier + * @param \acp_captcha $module The ACP module basename + * @return void + */ public function acp_page($id, $module) { /** @@ -157,11 +216,22 @@ class recaptcha_v3 extends captcha_abstract ]); } + /** + * Create the ACP page for previewing this CAPTCHA plugin. + * + * @param string $id The module identifier + * @return bool|string + */ public function get_demo_template($id) { return $this->get_template(); } + /** + * Get the template for this CAPTCHA plugin. + * + * @return bool|string False if CAPTCHA is already solved, template file name otherwise + */ public function get_template() { /** @@ -199,17 +269,27 @@ class recaptcha_v3 extends captcha_abstract return 'captcha_recaptcha_v3.html'; } - function validate() + /** + * Validate the user's input. + * + * @return bool|string + */ + public function validate() { if (!parent::validate()) { return false; } - return $this->recaptcha_check_answer(); + return $this->recaptcha_verify_token(); } - function recaptcha_check_answer() + /** + * Validate the token returned by Google reCAPTCHA v3. + * + * @return bool|string False on success, string containing the error otherwise + */ + protected function recaptcha_verify_token() { /** * @var \phpbb\config\config $config Config object @@ -223,12 +303,13 @@ class recaptcha_v3 extends captcha_abstract $token = $request->variable('recaptcha_token', '', true); $threshold = (double) $config["recaptcha_v3_threshold_{$action}"] ?? $config['recaptcha_v3_threshold'] ?? 0.5; - // Discard spam submissions + // No token was provided, discard spam submissions if (empty($token)) { return $language->lang('RECAPTCHA_INCORRECT'); } + // Create the request method that should be used switch ($config['recaptcha_v3_method'] ?? '') { case self::CURL: @@ -245,8 +326,10 @@ class recaptcha_v3 extends captcha_abstract break; } + // Create the recaptcha instance $recaptcha = new \ReCaptcha\ReCaptcha($config['recaptcha_v3_secret'], $method); + // Set the expected action and threshold, and verify the token $result = $recaptcha->setExpectedAction($action) ->setScoreThreshold($threshold) ->verify($token, $user->ip); From c8e5c36c1d24034132942f3b93c030aed718c780 Mon Sep 17 00:00:00 2001 From: mrgoldy Date: Sat, 14 Mar 2020 16:03:02 +0100 Subject: [PATCH 3/6] [ticket/15937] Google reCAPTCHA v3 Plugin migration and clean up PHPBB3-15937 --- phpBB/adm/style/captcha_recaptcha_v3_acp.html | 25 ++++++------ phpBB/language/en/captcha_recaptcha.php | 21 +++++----- phpBB/phpbb/captcha/plugins/recaptcha_v3.php | 38 ++++++++++--------- 3 files changed, 43 insertions(+), 41 deletions(-) diff --git a/phpBB/adm/style/captcha_recaptcha_v3_acp.html b/phpBB/adm/style/captcha_recaptcha_v3_acp.html index 8a1e0a7bce..ef9896a742 100644 --- a/phpBB/adm/style/captcha_recaptcha_v3_acp.html +++ b/phpBB/adm/style/captcha_recaptcha_v3_acp.html @@ -48,13 +48,13 @@
{{ lang('RECAPTCHA_V3_METHOD_EXPLAIN') }}
- {% for method, constant in RECAPTCHA_V3_METHODS %} + {% for method, available in RECAPTCHA_V3_METHODS %} {% endfor %}
@@ -65,17 +65,14 @@ {{ lang('RECAPTCHA_V3_THRESHOLDS') }}

{{ lang('RECAPTCHA_V3_THRESHOLDS_EXPLAIN') }}

-
-
- -
{{ lang('RECAPTCHA_V3_THRESHOLD_EXPLAIN') }} -
-
-
- {% for threshold in thresholds %}
-
+
+ + {% if lang_defined(threshold.key|upper ~ '_EXPLAIN') %} +
{{ lang(threshold.key|upper ~ '_EXPLAIN') }} + {% endif %} +
{% endfor %} diff --git a/phpBB/language/en/captcha_recaptcha.php b/phpBB/language/en/captcha_recaptcha.php index fcdde312c2..10eace67df 100644 --- a/phpBB/language/en/captcha_recaptcha.php +++ b/phpBB/language/en/captcha_recaptcha.php @@ -47,6 +47,7 @@ $lang = array_merge($lang, array( 'RECAPTCHA_INCORRECT' => 'The solution you provided was incorrect', 'RECAPTCHA_NOSCRIPT' => 'Please enable JavaScript in your browser to load the challenge.', 'RECAPTCHA_NOT_AVAILABLE' => 'In order to use reCaptcha, you must create an account on www.google.com/recaptcha.', + 'RECAPTCHA_INVISIBLE' => 'This CAPTCHA is actually invisible. To verify that it works, a small icon should appear in right bottom corner of this page.', 'RECAPTCHA_PUBLIC' => 'Site key', 'RECAPTCHA_PUBLIC_EXPLAIN' => 'Your site reCAPTCHA key. Keys can be obtained on www.google.com/recaptcha. Please, use reCAPTCHA v2 > Invisible reCAPTCHA badge type.', @@ -55,21 +56,21 @@ $lang = array_merge($lang, array( 'RECAPTCHA_PRIVATE_EXPLAIN' => 'Your secret reCAPTCHA key. Keys can be obtained on www.google.com/recaptcha. Please, use reCAPTCHA v2 > Invisible reCAPTCHA badge type.', 'RECAPTCHA_V3_PRIVATE_EXPLAIN' => 'Your secret reCAPTCHA key. Keys can be obtained on www.google.com/recaptcha. Please, use reCAPTCHA v3.', - 'RECAPTCHA_INVISIBLE' => 'This CAPTCHA is actually invisible. To verify that it works, a small icon should appear in right bottom corner of this page.', - 'RECAPTCHA_V3_DOMAIN' => 'Request domain', 'RECAPTCHA_V3_DOMAIN_EXPLAIN' => 'The domain to fetch the script from and to use when verifying the request.
Use recaptcha.net when google.com is not accessible.', + 'RECAPTCHA_V3_METHOD' => 'Request method', 'RECAPTCHA_V3_METHOD_EXPLAIN' => 'The method to use when verifying the request.
Disabled options are not available within your setup.', 'RECAPTCHA_V3_METHOD_CURL' => 'cURL', 'RECAPTCHA_V3_METHOD_POST' => 'POST', 'RECAPTCHA_V3_METHOD_SOCKET' => 'Socket', - 'RECAPTCHA_V3_THRESHOLD' => 'Default threshold', - 'RECAPTCHA_V3_THRESHOLD_EXPLAIN' => 'Used when none of the other actions are applicable.', - 'RECAPTCHA_V3_THRESHOLD_LOGIN' => 'Login threshold', - 'RECAPTCHA_V3_THRESHOLD_POST' => 'Post threshold', - 'RECAPTCHA_V3_THRESHOLD_REGISTER' => 'Register threshold', - 'RECAPTCHA_V3_THRESHOLD_REPORT' => 'Report threshold', - 'RECAPTCHA_V3_THRESHOLDS' => 'Thresholds', - 'RECAPTCHA_V3_THRESHOLDS_EXPLAIN' => 'reCAPTCHA v3 returns a score (1.0 is very likely a good interaction, 0.0 is very likely a bot). Here you can set the minimum score per action.', + + 'RECAPTCHA_V3_THRESHOLD_DEFAULT' => 'Default threshold', + 'RECAPTCHA_V3_THRESHOLD_DEFAULT_EXPLAIN' => 'Used when none of the other actions are applicable.', + 'RECAPTCHA_V3_THRESHOLD_LOGIN' => 'Login threshold', + 'RECAPTCHA_V3_THRESHOLD_POST' => 'Post threshold', + 'RECAPTCHA_V3_THRESHOLD_REGISTER' => 'Register threshold', + 'RECAPTCHA_V3_THRESHOLD_REPORT' => 'Report threshold', + 'RECAPTCHA_V3_THRESHOLDS' => 'Thresholds', + 'RECAPTCHA_V3_THRESHOLDS_EXPLAIN' => 'reCAPTCHA v3 returns a score (1.0 is very likely a good interaction, 0.0 is very likely a bot). Here you can set the minimum score per action.', )); diff --git a/phpBB/phpbb/captcha/plugins/recaptcha_v3.php b/phpBB/phpbb/captcha/plugins/recaptcha_v3.php index f04d44a275..ceea796d96 100644 --- a/phpBB/phpbb/captcha/plugins/recaptcha_v3.php +++ b/phpBB/phpbb/captcha/plugins/recaptcha_v3.php @@ -31,17 +31,26 @@ class recaptcha_v3 extends captcha_abstract const GOOGLE = 'google.com'; const RECAPTCHA = 'recaptcha.net'; - /** @var string Default action when no other applies */ - static protected $action = 'default'; - - /** @var array CAPTCHA types mapped to their action with threshold */ + /** @var array CAPTCHA types mapped to their action */ static protected $actions = [ + 0 => 'default', CONFIRM_REG => 'register', CONFIRM_LOGIN => 'login', CONFIRM_POST => 'post', CONFIRM_REPORT => 'report', ]; + /** + * Get CAPTCHA types mapped to their action. + * + * @static + * @return array + */ + static public function get_actions() + { + return self::$actions; + } + /** * Execute. * @@ -116,7 +125,7 @@ class recaptcha_v3 extends captcha_abstract } /** - * Whether or not this CAPTCHA plugin is available. + * Whether or not this CAPTCHA plugin is available and setup. * * @return bool */ @@ -170,7 +179,6 @@ class recaptcha_v3 extends captcha_abstract $config->set('recaptcha_v3_secret', $request->variable('recaptcha_v3_secret', '', true)); $config->set('recaptcha_v3_domain', $request->variable('recaptcha_v3_domain', '', true)); $config->set('recaptcha_v3_method', $request->variable('recaptcha_v3_method', '', true)); - $config->set('recaptcha_v3_threshold', $request->variable('recaptcha_v3_threshold', 0.50)); foreach (self::$actions as $action) { @@ -196,22 +204,17 @@ class recaptcha_v3 extends captcha_abstract 'RECAPTCHA_V3_KEY' => $config['recaptcha_v3_key'] ?? '', 'RECAPTCHA_V3_SECRET' => $config['recaptcha_v3_secret'] ?? '', - 'RECAPTCHA_V3_THRESHOLD' => $config['recaptcha_v3_threshold'] ?? 0.5, 'RECAPTCHA_V3_DOMAIN' => $config['recaptcha_v3_domain'] ?? self::GOOGLE, 'RECAPTCHA_V3_DOMAINS' => [self::GOOGLE, self::RECAPTCHA], 'RECAPTCHA_V3_METHOD' => $config['recaptcha_v3_method'] ?? self::POST, 'RECAPTCHA_V3_METHODS' => [ - 'POST' => self::POST, - 'CURL' => self::CURL, - 'SOCKET' => self::SOCKET, + self::POST => ini_get('allow_url_fopen') && function_exists('file_get_contents'), + self::CURL => extension_loaded('curl') && function_exists('curl_init'), + self::SOCKET => function_exists('fsockopen'), ], - 'S_RECAPTCHA_V3_CURL' => extension_loaded('curl') && function_exists('curl_init'), - 'S_RECAPTCHA_V3_POST' => ini_get('allow_url_fopen') && function_exists('file_get_contents'), - 'S_RECAPTCHA_V3_SOCKET' => function_exists('fsockopen'), - 'U_ACTION' => $module->u_action, ]); } @@ -257,7 +260,7 @@ class recaptcha_v3 extends captcha_abstract $template->assign_vars([ 'CONFIRM_EXPLAIN' => $language->lang($explain, '', ''), - 'RECAPTCHA_ACTION' => self::$actions[$this->type] ?? self::$action, + 'RECAPTCHA_ACTION' => self::$actions[$this->type] ?? reset(self::$actions), 'RECAPTCHA_KEY' => $config['recaptcha_v3_key'] ?? '', 'U_RECAPTCHA_SCRIPT' => sprintf('//%s/recaptcha/api.js?render=%s', $domain, $render), @@ -299,9 +302,10 @@ class recaptcha_v3 extends captcha_abstract */ global $config, $language, $request, $user; - $action = $request->variable('recaptcha_action', self::$action, true); $token = $request->variable('recaptcha_token', '', true); - $threshold = (double) $config["recaptcha_v3_threshold_{$action}"] ?? $config['recaptcha_v3_threshold'] ?? 0.5; + $action = $request->variable('recaptcha_action', '', true); + $action = in_array($action, self::$actions) ? $action : reset(self::$actions); + $threshold = (double) $config["recaptcha_v3_threshold_{$action}"] ?? 0.5; // No token was provided, discard spam submissions if (empty($token)) From d35b3b47926eff95bf50cd2b700f5f95262393b7 Mon Sep 17 00:00:00 2001 From: mrgoldy Date: Sat, 14 Mar 2020 16:04:48 +0100 Subject: [PATCH 4/6] [ticket/15937] Google reCAPTCHA v3 Plugin migration PHPBB3-15937 --- .../data/v330/google_recaptcha_v3.php | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 phpBB/phpbb/db/migration/data/v330/google_recaptcha_v3.php diff --git a/phpBB/phpbb/db/migration/data/v330/google_recaptcha_v3.php b/phpBB/phpbb/db/migration/data/v330/google_recaptcha_v3.php new file mode 100644 index 0000000000..42d3804876 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/v330/google_recaptcha_v3.php @@ -0,0 +1,63 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\db\migration\data\v330; + +class google_recaptcha_v3 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return $this->config->offsetExists('recaptcha_v3_key'); + } + + public static function depends_on() + { + return [ + '\phpbb\db\migration\data\v330\v330', + ]; + } + + public function update_data() + { + $data = [ + ['config.add', ['recaptcha_v3_key', '']], + ['config.add', ['recaptcha_v3_secret', '']], + ['config.add', ['recaptcha_v3_domain', \phpbb\captcha\plugins\recaptcha_v3::GOOGLE]], + ['config.add', ['recaptcha_v3_method', \phpbb\captcha\plugins\recaptcha_v3::POST]], + ]; + + foreach (\phpbb\captcha\plugins\recaptcha_v3::get_actions() as $action) + { + $data[] = ['config.add', "recaptcha_v3_threshold_{$action}", 0.5]; + } + + return $data; + } + + public function revert_data() + { + $data = [ + ['config.remove', ['recaptcha_v3_key']], + ['config.remove', ['recaptcha_v3_secret']], + ['config.remove', ['recaptcha_v3_domain']], + ['config.remove', ['recaptcha_v3_method']], + ]; + + foreach (\phpbb\captcha\plugins\recaptcha_v3::get_actions() as $action) + { + $data[] = ['config.remove', "recaptcha_v3_threshold_{$action}"]; + } + + return $data; + } +} From feb94b446acd808095e9f9085899047df523a545 Mon Sep 17 00:00:00 2001 From: mrgoldy Date: Mon, 16 Mar 2020 12:05:33 +0100 Subject: [PATCH 5/6] [ticket/15937] Amend Google reCAPTCHA v3 Migrations PHPBB3-15937 --- .../data/v330/google_recaptcha_v3.php | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/phpBB/phpbb/db/migration/data/v330/google_recaptcha_v3.php b/phpBB/phpbb/db/migration/data/v330/google_recaptcha_v3.php index 42d3804876..3c7e03ad30 100644 --- a/phpBB/phpbb/db/migration/data/v330/google_recaptcha_v3.php +++ b/phpBB/phpbb/db/migration/data/v330/google_recaptcha_v3.php @@ -38,24 +38,7 @@ class google_recaptcha_v3 extends \phpbb\db\migration\migration foreach (\phpbb\captcha\plugins\recaptcha_v3::get_actions() as $action) { - $data[] = ['config.add', "recaptcha_v3_threshold_{$action}", 0.5]; - } - - return $data; - } - - public function revert_data() - { - $data = [ - ['config.remove', ['recaptcha_v3_key']], - ['config.remove', ['recaptcha_v3_secret']], - ['config.remove', ['recaptcha_v3_domain']], - ['config.remove', ['recaptcha_v3_method']], - ]; - - foreach (\phpbb\captcha\plugins\recaptcha_v3::get_actions() as $action) - { - $data[] = ['config.remove', "recaptcha_v3_threshold_{$action}"]; + $data[] = ['config.add', ["recaptcha_v3_threshold_{$action}", 0.5]]; } return $data; From f671d7559aee3c863b30a733331409a10db2c4dc Mon Sep 17 00:00:00 2001 From: mrgoldy Date: Wed, 22 Apr 2020 20:20:23 +0200 Subject: [PATCH 6/6] [ticket/15937] ReCaptcha language fixes (sprintf and https links) PHPBB3-15937 --- phpBB/language/en/captcha_recaptcha.php | 10 +++++----- phpBB/phpbb/captcha/plugins/recaptcha_v3.php | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/phpBB/language/en/captcha_recaptcha.php b/phpBB/language/en/captcha_recaptcha.php index 10eace67df..60d61cc2c8 100644 --- a/phpBB/language/en/captcha_recaptcha.php +++ b/phpBB/language/en/captcha_recaptcha.php @@ -46,15 +46,15 @@ $lang = array_merge($lang, array( 'RECAPTCHA_INCORRECT' => 'The solution you provided was incorrect', 'RECAPTCHA_NOSCRIPT' => 'Please enable JavaScript in your browser to load the challenge.', - 'RECAPTCHA_NOT_AVAILABLE' => 'In order to use reCaptcha, you must create an account on www.google.com/recaptcha.', + 'RECAPTCHA_NOT_AVAILABLE' => 'In order to use reCaptcha, you must create an account on www.google.com/recaptcha.', 'RECAPTCHA_INVISIBLE' => 'This CAPTCHA is actually invisible. To verify that it works, a small icon should appear in right bottom corner of this page.', 'RECAPTCHA_PUBLIC' => 'Site key', - 'RECAPTCHA_PUBLIC_EXPLAIN' => 'Your site reCAPTCHA key. Keys can be obtained on www.google.com/recaptcha. Please, use reCAPTCHA v2 > Invisible reCAPTCHA badge type.', - 'RECAPTCHA_V3_PUBLIC_EXPLAIN' => 'Your site reCAPTCHA key. Keys can be obtained on www.google.com/recaptcha. Please, use reCAPTCHA v3.', + 'RECAPTCHA_PUBLIC_EXPLAIN' => 'Your site reCAPTCHA key. Keys can be obtained on www.google.com/recaptcha. Please, use reCAPTCHA v2 > Invisible reCAPTCHA badge type.', + 'RECAPTCHA_V3_PUBLIC_EXPLAIN' => 'Your site reCAPTCHA key. Keys can be obtained on www.google.com/recaptcha. Please, use reCAPTCHA v3.', 'RECAPTCHA_PRIVATE' => 'Secret key', - 'RECAPTCHA_PRIVATE_EXPLAIN' => 'Your secret reCAPTCHA key. Keys can be obtained on www.google.com/recaptcha. Please, use reCAPTCHA v2 > Invisible reCAPTCHA badge type.', - 'RECAPTCHA_V3_PRIVATE_EXPLAIN' => 'Your secret reCAPTCHA key. Keys can be obtained on www.google.com/recaptcha. Please, use reCAPTCHA v3.', + 'RECAPTCHA_PRIVATE_EXPLAIN' => 'Your secret reCAPTCHA key. Keys can be obtained on www.google.com/recaptcha. Please, use reCAPTCHA v2 > Invisible reCAPTCHA badge type.', + 'RECAPTCHA_V3_PRIVATE_EXPLAIN' => 'Your secret reCAPTCHA key. Keys can be obtained on www.google.com/recaptcha. Please, use reCAPTCHA v3.', 'RECAPTCHA_V3_DOMAIN' => 'Request domain', 'RECAPTCHA_V3_DOMAIN_EXPLAIN' => 'The domain to fetch the script from and to use when verifying the request.
Use recaptcha.net when google.com is not accessible.', diff --git a/phpBB/phpbb/captcha/plugins/recaptcha_v3.php b/phpBB/phpbb/captcha/plugins/recaptcha_v3.php index ceea796d96..7505419a31 100644 --- a/phpBB/phpbb/captcha/plugins/recaptcha_v3.php +++ b/phpBB/phpbb/captcha/plugins/recaptcha_v3.php @@ -262,7 +262,7 @@ class recaptcha_v3 extends captcha_abstract 'RECAPTCHA_ACTION' => self::$actions[$this->type] ?? reset(self::$actions), 'RECAPTCHA_KEY' => $config['recaptcha_v3_key'] ?? '', - 'U_RECAPTCHA_SCRIPT' => sprintf('//%s/recaptcha/api.js?render=%s', $domain, $render), + 'U_RECAPTCHA_SCRIPT' => sprintf('//%1$s/recaptcha/api.js?render=%2$s', $domain, $render), 'S_CONFIRM_CODE' => true, 'S_RECAPTCHA_AVAILABLE' => $this->is_available(),