From a0cf9f7a180ab6351d01722c92d3bcb232e9aca3 Mon Sep 17 00:00:00 2001 From: Cyrill Gsell Date: Tue, 22 Aug 2023 17:34:13 +0200 Subject: [PATCH 1/8] Allowing custom request class for recaptcha verification --- Classes/FormElements/Recaptcha.php | 43 ++++++++++++++++++++---------- 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/Classes/FormElements/Recaptcha.php b/Classes/FormElements/Recaptcha.php index a6e8fd0..80e3f46 100644 --- a/Classes/FormElements/Recaptcha.php +++ b/Classes/FormElements/Recaptcha.php @@ -20,6 +20,8 @@ use Neos\Error\Messages\Error; use Neos\Form\Core\Model\AbstractFormElement; use Neos\Form\Core\Runtime\FormRuntime; +use ReCaptcha\RequestMethod; +use ReCaptcha\RequestMethod\CurlPost; /** * This is the implementation class of the Recaptcha. @@ -71,36 +73,39 @@ public function onSubmit(FormRuntime $formRuntime, &$elementValue) return; } - $requestMethodString = strtolower($this->settings['requestMethod']); - if ($requestMethodString === 'curl') { - $requestMethod = new \ReCaptcha\RequestMethod\CurlPost(); - } elseif ($requestMethodString === 'socket') { - $requestMethod = new \ReCaptcha\RequestMethod\SocketPost(); + $requestMethodString = $this->settings['requestMethod']; + + if (self::isReCaptchaRequestClass($requestMethodString)) { + $requestMethod = new $requestMethodString(); } else { - $requestMethod = new \ReCaptcha\RequestMethod\Post(); + $requestMethod = match (strtolower($requestMethodString)) { + 'curl' => new \ReCaptcha\RequestMethod\CurlPost(), + 'socket' => new \ReCaptcha\RequestMethod\SocketPost(), + default => new \ReCaptcha\RequestMethod\Post(), + }; } $properties = $this->getProperties(); - $recaptcha = new \ReCaptcha\ReCaptcha($properties['secretKey'], $requestMethod); + $recaptcha = new \ReCaptcha\ReCaptcha($properties['secretKey'], $requestMethod); if (!empty($properties['expectedHostname'])) { $recaptcha->setExpectedHostname($properties['expectedHostname']); } - /** + /** * If one of the following three is set, it is the V3 Captcha. - * Action and Threshold can't be empty due to validators, we still + * Action and Threshold can't be empty due to validators, we still * need to look if they are set because it could be the V2 Captcha. */ - if(isset($properties['action'])) { + if (isset($properties['action'])) { $recaptcha->setExpectedAction($properties['action']); } - if(isset($properties['threshold'])) { + if (isset($properties['threshold'])) { $recaptcha->setScoreThreshold($properties['threshold']); } /** * Optional */ - if(isset($properties['timeout'])) { + if (isset($properties['timeout'])) { $recaptcha->setChallengeTimeout($properties['timeout']); } @@ -108,7 +113,7 @@ public function onSubmit(FormRuntime $formRuntime, &$elementValue) if ($resp->isSuccess() === false) { - $processingRule = + $processingRule = $this ->getRootForm() ->getProcessingRule($this->getIdentifier()); @@ -118,7 +123,7 @@ public function onSubmit(FormRuntime $formRuntime, &$elementValue) * The Error 'Please check the box "I am not a robot" and try again.' * Is not suitable for the V3 Captcha. */ - if(isset($properties['action'])) { + if (isset($properties['action'])) { $processingRule ->getProcessingMessages() ->addError( @@ -139,4 +144,14 @@ public function onSubmit(FormRuntime $formRuntime, &$elementValue) } } } + + /** + * @param string $className + * + * @return bool + */ + protected static function isReCaptchaRequestClass(string $className) + { + return class_exists($className) && in_array('ReCaptcha\RequestMethod', class_implements($className), true); + } } From 59f64adf04a4bb59e000cbd7deb932da248a6530 Mon Sep 17 00:00:00 2001 From: Cyrill Gsell Date: Tue, 22 Aug 2023 17:34:45 +0200 Subject: [PATCH 2/8] Adding curl request class respecting server http_proxy options --- Classes/RequestMethod/CurlPostWithProxy.php | 80 +++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 Classes/RequestMethod/CurlPostWithProxy.php diff --git a/Classes/RequestMethod/CurlPostWithProxy.php b/Classes/RequestMethod/CurlPostWithProxy.php new file mode 100644 index 0000000..5ab199b --- /dev/null +++ b/Classes/RequestMethod/CurlPostWithProxy.php @@ -0,0 +1,80 @@ +curl = (is_null($curl)) ? new Curl() : $curl; + $this->siteVerifyUrl = (is_null($siteVerifyUrl)) ? ReCaptcha::SITE_VERIFY_URL : $siteVerifyUrl; + } + + /** + * Submit the cURL request with the specified parameters. + * + * @param RequestParameters $params Request parameters + * @return string Body of the reCAPTCHA response + */ + public function submit(RequestParameters $params) + { + $handle = $this->curl->init($this->siteVerifyUrl); + + $options = array( + CURLOPT_POST => true, + CURLOPT_POSTFIELDS => $params->toQueryString(), + CURLOPT_HTTPHEADER => array( + 'Content-Type: application/x-www-form-urlencoded' + ), + CURLINFO_HEADER_OUT => false, + CURLOPT_HEADER => false, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_SSL_VERIFYPEER => true, + ); + + if (isset($_SERVER["http_proxy"])) { + $proxy = trim( + preg_replace('/^http\:\/\//i', '', $_SERVER["http_proxy"]), + '/' + ); + $proxy = explode(':', $proxy); + $options[CURLOPT_RETURNTRANSFER] = 1; + $options[CURLOPT_PROXY] = $proxy[0]; + $options[CURLOPT_PROXYPORT] = $proxy[1]; + } + + $this->curl->setoptArray($handle, $options); + + $response = $this->curl->exec($handle); + $this->curl->close($handle); + + if ($response !== false) { + return $response; + } + + return '{"success": false, "error-codes": ["'.ReCaptcha::E_CONNECTION_FAILED.'"]}'; + } +} From d482f1121f942cf2815c615c08c5ab436f17d158 Mon Sep 17 00:00:00 2001 From: Cyrill Gsell Date: Tue, 22 Aug 2023 23:34:45 +0200 Subject: [PATCH 3/8] Adding slot to CurlPostWithProxy request method to allow modification of httpProxy settings --- Classes/RequestMethod/CurlPostWithProxy.php | 79 +++++++++++++++------ Configuration/Settings.yaml | 4 ++ 2 files changed, 62 insertions(+), 21 deletions(-) diff --git a/Classes/RequestMethod/CurlPostWithProxy.php b/Classes/RequestMethod/CurlPostWithProxy.php index 5ab199b..314952f 100644 --- a/Classes/RequestMethod/CurlPostWithProxy.php +++ b/Classes/RequestMethod/CurlPostWithProxy.php @@ -6,30 +6,57 @@ use ReCaptcha\RequestMethod; use ReCaptcha\RequestMethod\Curl; use ReCaptcha\RequestParameters; +use Neos\Flow\Annotations as Flow; +/** + * Copy of \ReCaptcha\RequestMethod\CurlPost() + * adding functionality for curl to work with a http proxy + */ class CurlPostWithProxy implements RequestMethod { /** * Curl connection to the reCAPTCHA service + * * @var Curl */ private $curl; /** * URL for reCAPTCHA siteverify API + * * @var string */ private $siteVerifyUrl; + /** + * Recaptcha settings + * + * @var array + */ + protected $settings; + + /** + * Inject the settings + * + * @param array $settings The settings to inject. + * + * @return void + */ + public function injectSettings(array $settings) + { + $this->settings = $settings['proxy'] ?? []; + } + + /** * Only needed if you want to override the defaults * - * @param Curl $curl Curl resource + * @param Curl $curl Curl resource * @param string $siteVerifyUrl URL for reCAPTCHA siteverify API */ public function __construct(Curl $curl = null, $siteVerifyUrl = null) { - $this->curl = (is_null($curl)) ? new Curl() : $curl; + $this->curl = (is_null($curl)) ? new Curl() : $curl; $this->siteVerifyUrl = (is_null($siteVerifyUrl)) ? ReCaptcha::SITE_VERIFY_URL : $siteVerifyUrl; } @@ -37,33 +64,33 @@ public function __construct(Curl $curl = null, $siteVerifyUrl = null) * Submit the cURL request with the specified parameters. * * @param RequestParameters $params Request parameters + * * @return string Body of the reCAPTCHA response */ public function submit(RequestParameters $params) { $handle = $this->curl->init($this->siteVerifyUrl); - $options = array( - CURLOPT_POST => true, - CURLOPT_POSTFIELDS => $params->toQueryString(), - CURLOPT_HTTPHEADER => array( - 'Content-Type: application/x-www-form-urlencoded' - ), - CURLINFO_HEADER_OUT => false, - CURLOPT_HEADER => false, + $options = [ + CURLOPT_POST => true, + CURLOPT_POSTFIELDS => $params->toQueryString(), + CURLOPT_HTTPHEADER => [ + 'Content-Type: application/x-www-form-urlencoded', + ], + CURLINFO_HEADER_OUT => false, + CURLOPT_HEADER => false, CURLOPT_RETURNTRANSFER => true, CURLOPT_SSL_VERIFYPEER => true, - ); - - if (isset($_SERVER["http_proxy"])) { - $proxy = trim( - preg_replace('/^http\:\/\//i', '', $_SERVER["http_proxy"]), - '/' - ); - $proxy = explode(':', $proxy); + ]; + + if (isset($this->settings['httpProxy']) && !empty($this->settings['httpProxy'])) { + $httpProxy = $this->settings['httpProxy']; + $this->emitHttpProxyRetrieved($httpProxy); + + $proxy = explode(':', $httpProxy); $options[CURLOPT_RETURNTRANSFER] = 1; - $options[CURLOPT_PROXY] = $proxy[0]; - $options[CURLOPT_PROXYPORT] = $proxy[1]; + $options[CURLOPT_PROXY] = $proxy[0]; + $options[CURLOPT_PROXYPORT] = $proxy[1]; } $this->curl->setoptArray($handle, $options); @@ -75,6 +102,16 @@ public function submit(RequestParameters $params) return $response; } - return '{"success": false, "error-codes": ["'.ReCaptcha::E_CONNECTION_FAILED.'"]}'; + return '{"success": false, "error-codes": ["' . ReCaptcha::E_CONNECTION_FAILED . '"]}'; + } + + /** + * @param string $httpProxy + * + * @return void + * @Flow\Signal + */ + protected function emitHttpProxyRetrieved(string &$httpProxy) + { } } diff --git a/Configuration/Settings.yaml b/Configuration/Settings.yaml index 6fe60d4..6cbc232 100644 --- a/Configuration/Settings.yaml +++ b/Configuration/Settings.yaml @@ -39,3 +39,7 @@ Wegmeister: # Available request methods: file_get_contents, curl, socket. # If an invalid request method is applied, file_get_contents will be used. requestMethod: 'file_get_contents' + + proxy: + # in the format of : + httpProxy: '' From c9b83197f5077a9bf1c9c5cbe7cc2d2bb87d7972 Mon Sep 17 00:00:00 2001 From: Cyrill Gsell Date: Wed, 23 Aug 2023 09:27:22 +0200 Subject: [PATCH 4/8] Remove obsolete level in proxy settings & adding error handling if settings are missing --- Classes/RequestMethod/CurlPostWithProxy.php | 19 ++++++++++--------- Configuration/Settings.yaml | 6 ++---- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/Classes/RequestMethod/CurlPostWithProxy.php b/Classes/RequestMethod/CurlPostWithProxy.php index 314952f..b60a694 100644 --- a/Classes/RequestMethod/CurlPostWithProxy.php +++ b/Classes/RequestMethod/CurlPostWithProxy.php @@ -44,7 +44,10 @@ class CurlPostWithProxy implements RequestMethod */ public function injectSettings(array $settings) { - $this->settings = $settings['proxy'] ?? []; + if (empty($this->settings['httpProxy'])) { + throw new \Exception("Missing configuration, please add the following settings: 'Wegmeister.Recaptcha.httpProxy'"); + } + $this->settings = $settings; } @@ -83,15 +86,13 @@ public function submit(RequestParameters $params) CURLOPT_SSL_VERIFYPEER => true, ]; - if (isset($this->settings['httpProxy']) && !empty($this->settings['httpProxy'])) { - $httpProxy = $this->settings['httpProxy']; - $this->emitHttpProxyRetrieved($httpProxy); + $httpProxy = $this->settings['httpProxy']; + $this->emitHttpProxyRetrieved($httpProxy); - $proxy = explode(':', $httpProxy); - $options[CURLOPT_RETURNTRANSFER] = 1; - $options[CURLOPT_PROXY] = $proxy[0]; - $options[CURLOPT_PROXYPORT] = $proxy[1]; - } + $proxy = explode(':', $httpProxy); + $options[CURLOPT_RETURNTRANSFER] = 1; + $options[CURLOPT_PROXY] = $proxy[0]; + $options[CURLOPT_PROXYPORT] = $proxy[1]; $this->curl->setoptArray($handle, $options); diff --git a/Configuration/Settings.yaml b/Configuration/Settings.yaml index 6cbc232..13df82e 100644 --- a/Configuration/Settings.yaml +++ b/Configuration/Settings.yaml @@ -39,7 +39,5 @@ Wegmeister: # Available request methods: file_get_contents, curl, socket. # If an invalid request method is applied, file_get_contents will be used. requestMethod: 'file_get_contents' - - proxy: - # in the format of : - httpProxy: '' + # in the format of : + httpProxy: '' From d89326c765d1c5a54a98a4d659967a3a08a7d3cc Mon Sep 17 00:00:00 2001 From: Cyrill Gsell Date: Wed, 23 Aug 2023 10:46:19 +0200 Subject: [PATCH 5/8] Fixing error handling for missing settings in CurlPostWithProxy class --- Classes/RequestMethod/CurlPostWithProxy.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Classes/RequestMethod/CurlPostWithProxy.php b/Classes/RequestMethod/CurlPostWithProxy.php index b60a694..5082ba8 100644 --- a/Classes/RequestMethod/CurlPostWithProxy.php +++ b/Classes/RequestMethod/CurlPostWithProxy.php @@ -44,7 +44,7 @@ class CurlPostWithProxy implements RequestMethod */ public function injectSettings(array $settings) { - if (empty($this->settings['httpProxy'])) { + if (empty($settings['httpProxy'])) { throw new \Exception("Missing configuration, please add the following settings: 'Wegmeister.Recaptcha.httpProxy'"); } $this->settings = $settings; From 2d9f38efcabddef0dc712bf426a69cd80148170d Mon Sep 17 00:00:00 2001 From: Cyrill Gsell Date: Mon, 4 Dec 2023 18:07:51 +0100 Subject: [PATCH 6/8] [TASK] Adding php 8 as a composer requirement --- composer.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 61e6290..90525cd 100644 --- a/composer.json +++ b/composer.json @@ -5,7 +5,8 @@ "description": "Form Element for the Flow Form Framework integrating Google's Recaptcha", "require": { "google/recaptcha": "^1.2", - "neos/form": "^4.0 || ^5.0.9" + "neos/form": "^4.0 || ^5.0.9", + "php": "^8.0" }, "autoload": { "psr-4": { From a46ea009b4474bb224452b72ff09c0c9087b8ca5 Mon Sep 17 00:00:00 2001 From: Cyrill Gsell Date: Mon, 4 Dec 2023 18:11:48 +0100 Subject: [PATCH 7/8] [TASK] Adding enhanced error handling and code cleanup --- Classes/FormElements/Recaptcha.php | 2 -- Classes/RequestMethod/CurlPostWithProxy.php | 26 ++++++++++++++++++--- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/Classes/FormElements/Recaptcha.php b/Classes/FormElements/Recaptcha.php index 80e3f46..3fd9b28 100644 --- a/Classes/FormElements/Recaptcha.php +++ b/Classes/FormElements/Recaptcha.php @@ -20,8 +20,6 @@ use Neos\Error\Messages\Error; use Neos\Form\Core\Model\AbstractFormElement; use Neos\Form\Core\Runtime\FormRuntime; -use ReCaptcha\RequestMethod; -use ReCaptcha\RequestMethod\CurlPost; /** * This is the implementation class of the Recaptcha. diff --git a/Classes/RequestMethod/CurlPostWithProxy.php b/Classes/RequestMethod/CurlPostWithProxy.php index 5082ba8..7ca7f44 100644 --- a/Classes/RequestMethod/CurlPostWithProxy.php +++ b/Classes/RequestMethod/CurlPostWithProxy.php @@ -35,18 +35,39 @@ class CurlPostWithProxy implements RequestMethod */ protected $settings; + /** + * @var string + */ + protected $proxyHost; + + /** + * @var int + */ + protected $proxyPort; + /** * Inject the settings * * @param array $settings The settings to inject. * * @return void + * @throws \Exception */ public function injectSettings(array $settings) { if (empty($settings['httpProxy'])) { throw new \Exception("Missing configuration, please add the following settings: 'Wegmeister.Recaptcha.httpProxy'"); } + + $httpProxyParts = explode(':', $settings['httpProxy']); + // check if the settings is a string & has exactly two parts & the port only contains numbers + if(!is_string($settings['httpProxy']) || count($httpProxyParts) !== 2 || (int)$httpProxyParts[1] === 0){ + throw new \Exception("Invalid configuration, the Wegmeister.Recaptcha.httpProxy option should have the following format: 'http://yourproxy.com:1234'"); + } + + $this->proxyHost = $httpProxyParts[0]; + $this->proxyPort = $httpProxyParts[1]; + $this->settings = $settings; } @@ -89,10 +110,9 @@ public function submit(RequestParameters $params) $httpProxy = $this->settings['httpProxy']; $this->emitHttpProxyRetrieved($httpProxy); - $proxy = explode(':', $httpProxy); $options[CURLOPT_RETURNTRANSFER] = 1; - $options[CURLOPT_PROXY] = $proxy[0]; - $options[CURLOPT_PROXYPORT] = $proxy[1]; + $options[CURLOPT_PROXY] = $this->proxyHost; + $options[CURLOPT_PROXYPORT] = $this->proxyPort; $this->curl->setoptArray($handle, $options); From bb3d1e1429d56d93cb9a6e5fb095d59c9b73bdaf Mon Sep 17 00:00:00 2001 From: Cyrill Gsell Date: Mon, 4 Dec 2023 18:12:21 +0100 Subject: [PATCH 8/8] [TASK] Split error handling for settings --- Classes/RequestMethod/CurlPostWithProxy.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Classes/RequestMethod/CurlPostWithProxy.php b/Classes/RequestMethod/CurlPostWithProxy.php index 7ca7f44..5b0dbb2 100644 --- a/Classes/RequestMethod/CurlPostWithProxy.php +++ b/Classes/RequestMethod/CurlPostWithProxy.php @@ -59,9 +59,12 @@ public function injectSettings(array $settings) throw new \Exception("Missing configuration, please add the following settings: 'Wegmeister.Recaptcha.httpProxy'"); } + if(!is_string($settings['httpProxy'])){ + throw new \Exception("Invalid configuration, the Wegmeister.Recaptcha.httpProxy option has to be a string."); + } + $httpProxyParts = explode(':', $settings['httpProxy']); - // check if the settings is a string & has exactly two parts & the port only contains numbers - if(!is_string($settings['httpProxy']) || count($httpProxyParts) !== 2 || (int)$httpProxyParts[1] === 0){ + if(count($httpProxyParts) !== 2 || (int)$httpProxyParts[1] === 0){ throw new \Exception("Invalid configuration, the Wegmeister.Recaptcha.httpProxy option should have the following format: 'http://yourproxy.com:1234'"); }