From a190f787bd1c2b04523e76dbab006bd8dc94b38a Mon Sep 17 00:00:00 2001 From: "Chhandak.Barua" Date: Thu, 17 Nov 2022 21:44:22 +0530 Subject: [PATCH 01/28] ACP2E-1338: Google reCaptcha in Incorrect position --- ReCaptchaReview/view/frontend/layout/catalog_product_view.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ReCaptchaReview/view/frontend/layout/catalog_product_view.xml b/ReCaptchaReview/view/frontend/layout/catalog_product_view.xml index 95b33228..2892de64 100644 --- a/ReCaptchaReview/view/frontend/layout/catalog_product_view.xml +++ b/ReCaptchaReview/view/frontend/layout/catalog_product_view.xml @@ -7,7 +7,7 @@ --> - + Date: Fri, 18 Nov 2022 17:50:23 -0600 Subject: [PATCH 02/28] ACP2E-1338: Google reCaptcha in Incorrect position --- .../LayoutProcessor/Checkout/Onepage.php | 6 +-- .../frontend/layout/checkout_index_index.xml | 12 ++--- .../web/js/model/place-order-mixin.js | 21 ++++++--- .../view/frontend/web/js/reCaptchaCheckout.js | 45 +++++++++++++++++++ .../view/frontend/web/template/reCaptcha.html | 27 +++++++++++ .../LayoutProcessor/Checkout/Onepage.php | 5 +++ 6 files changed, 100 insertions(+), 16 deletions(-) create mode 100644 ReCaptchaCheckout/view/frontend/web/js/reCaptchaCheckout.js create mode 100644 ReCaptchaCheckout/view/frontend/web/template/reCaptcha.html diff --git a/ReCaptchaCheckout/Block/LayoutProcessor/Checkout/Onepage.php b/ReCaptchaCheckout/Block/LayoutProcessor/Checkout/Onepage.php index fac0c3d4..00b76086 100644 --- a/ReCaptchaCheckout/Block/LayoutProcessor/Checkout/Onepage.php +++ b/ReCaptchaCheckout/Block/LayoutProcessor/Checkout/Onepage.php @@ -75,14 +75,14 @@ public function process($jsLayout) $key = 'place_order'; if ($this->isCaptchaEnabled->isCaptchaEnabledFor($key)) { $jsLayout['components']['checkout']['children']['steps']['children']['billing-step']['children'] - ['payment']['children']['beforeMethods']['children']['place-order-recaptcha-container']['children'] + ['payment']['children']['payments-list']['children']['before-place-order']['children'] ['place-order-recaptcha']['settings'] = $this->captchaUiConfigResolver->get($key); } else { if (isset($jsLayout['components']['checkout']['children']['steps']['children']['billing-step']['children'] - ['payment']['children']['beforeMethods']['children']['place-order-recaptcha-container']['children'] + ['payment']['children']['payments-list']['children']['before-place-order']['children'] ['place-order-recaptcha'])) { unset($jsLayout['components']['checkout']['children']['steps']['children']['billing-step']['children'] - ['payment']['children']['beforeMethods']['children']['place-order-recaptcha-container'] + ['payment']['children']['payments-list']['children']['before-place-order'] ['children']['place-order-recaptcha']); } } diff --git a/ReCaptchaCheckout/view/frontend/layout/checkout_index_index.xml b/ReCaptchaCheckout/view/frontend/layout/checkout_index_index.xml index 0b2261ee..c4f64a3e 100644 --- a/ReCaptchaCheckout/view/frontend/layout/checkout_index_index.xml +++ b/ReCaptchaCheckout/view/frontend/layout/checkout_index_index.xml @@ -49,18 +49,18 @@ - + - - uiComponent - Magento_ReCaptchaCheckout/payment-recaptcha-container - beforeMethods + - Magento_ReCaptchaWebapiUi/js/webapiReCaptcha + Magento_ReCaptchaCheckout/js/reCaptchaCheckout place-order-recaptcha checkoutConfig recaptcha-checkout-place-order + + + diff --git a/ReCaptchaCheckout/view/frontend/web/js/model/place-order-mixin.js b/ReCaptchaCheckout/view/frontend/web/js/model/place-order-mixin.js index 9693ae7e..2cd102bd 100644 --- a/ReCaptchaCheckout/view/frontend/web/js/model/place-order-mixin.js +++ b/ReCaptchaCheckout/view/frontend/web/js/model/place-order-mixin.js @@ -4,22 +4,29 @@ */ /* eslint-disable max-nested-callbacks */ +/* eslint-disable max-depth */ define([ 'jquery', 'mage/utils/wrapper', - 'Magento_ReCaptchaWebapiUi/js/webapiReCaptchaRegistry' -], function ($, wrapper, recaptchaRegistry) { + 'Magento_ReCaptchaWebapiUi/js/webapiReCaptchaRegistry', + 'Magento_Checkout/js/model/quote' +], function ($, wrapper, recaptchaRegistry, quote) { 'use strict'; return function (placeOrder) { return wrapper.wrap(placeOrder, function (originalAction, serviceUrl, payload, messageContainer) { - var recaptchaDeferred; + var recaptchaDeferred, + reCaptchaId; - if (recaptchaRegistry.triggers.hasOwnProperty('recaptcha-checkout-place-order')) { + if (quote.paymentMethod()) { + reCaptchaId = 'recaptcha-checkout-place-order-' + quote.paymentMethod().method; + } + + if (reCaptchaId !== undefined && recaptchaRegistry.triggers.hasOwnProperty(reCaptchaId)) { //ReCaptcha is present for checkout recaptchaDeferred = $.Deferred(); - recaptchaRegistry.addListener('recaptcha-checkout-place-order', function (token) { + recaptchaRegistry.addListener(reCaptchaId, function (token) { //Add reCaptcha value to place-order request and resolve deferred with the API call results payload.xReCaptchaValue = token; originalAction(serviceUrl, payload, messageContainer).done(function () { @@ -29,9 +36,9 @@ define([ }); }); //Trigger ReCaptcha validation - recaptchaRegistry.triggers['recaptcha-checkout-place-order'](); + recaptchaRegistry.triggers[reCaptchaId](); //remove listener so that place order action is only triggered by the 'Place Order' button - recaptchaRegistry.removeListener('recaptcha-checkout-place-order'); + recaptchaRegistry.removeListener(reCaptchaId); return recaptchaDeferred; } diff --git a/ReCaptchaCheckout/view/frontend/web/js/reCaptchaCheckout.js b/ReCaptchaCheckout/view/frontend/web/js/reCaptchaCheckout.js new file mode 100644 index 00000000..06239d26 --- /dev/null +++ b/ReCaptchaCheckout/view/frontend/web/js/reCaptchaCheckout.js @@ -0,0 +1,45 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +// jscs:disable jsDoc + +/* global grecaptcha */ +define( + [ + 'Magento_ReCaptchaWebapiUi/js/webapiReCaptcha', + 'jquery' + ], + function (Component, $) { + 'use strict'; + + return Component.extend({ + defaults: { + template: 'Magento_ReCaptchaCheckout/reCaptcha', + skipPayments: [] + }, + + /** + * Render reCAPTCHA + */ + renderReCaptchaForPayment: function (method) { + var reCaptcha; + + if (!this.skipPayments || !this.skipPayments.hasOwnProperty(method.getCode())) { + reCaptcha = $.extend({}, this); + + reCaptcha.reCaptchaId = this.getPaymentReCaptchaId(method); + reCaptcha.renderReCaptcha(); + } + }, + + /** + * Render reCAPTCHA + */ + getPaymentReCaptchaId: function (method) { + return this.getReCaptchaId() + '-' + method.getCode(); + } + }); + } +); diff --git a/ReCaptchaCheckout/view/frontend/web/template/reCaptcha.html b/ReCaptchaCheckout/view/frontend/web/template/reCaptcha.html new file mode 100644 index 00000000..9fee8ab2 --- /dev/null +++ b/ReCaptchaCheckout/view/frontend/web/template/reCaptcha.html @@ -0,0 +1,27 @@ + + +
+
+ +
+
+ +
+
+ +
diff --git a/ReCaptchaPaypal/Block/LayoutProcessor/Checkout/Onepage.php b/ReCaptchaPaypal/Block/LayoutProcessor/Checkout/Onepage.php index bd8027af..5522f36f 100644 --- a/ReCaptchaPaypal/Block/LayoutProcessor/Checkout/Onepage.php +++ b/ReCaptchaPaypal/Block/LayoutProcessor/Checkout/Onepage.php @@ -53,6 +53,11 @@ public function process($jsLayout) $jsLayout['components']['checkout']['children']['steps']['children']['billing-step']['children'] ['payment']['children']['payments-list']['children']['paypal-captcha']['children'] ['recaptcha']['settings'] = $this->captchaUiConfigResolver->get($key); + if ($this->isCaptchaEnabled->isCaptchaEnabledFor('place_order')) { + $jsLayout['components']['checkout']['children']['steps']['children']['billing-step']['children'] + ['payment']['children']['payments-list']['children']['before-place-order']['children'] + ['place-order-recaptcha']['skipPayments']['payflowpro'] = true; + } } else { if (isset($jsLayout['components']['checkout']['children']['steps']['children']['billing-step']['children'] ['payment']['children']['payments-list']['children']['paypal-captcha']['children']['recaptcha'])) { From 0564dde26705f6b650a50dd91171239c92bc5180 Mon Sep 17 00:00:00 2001 From: soumah Date: Sat, 19 Nov 2022 10:43:06 -0600 Subject: [PATCH 03/28] ACP2E-1338: Google reCaptcha in Incorrect position --- ReCaptchaPaypal/Block/LayoutProcessor/Checkout/Onepage.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ReCaptchaPaypal/Block/LayoutProcessor/Checkout/Onepage.php b/ReCaptchaPaypal/Block/LayoutProcessor/Checkout/Onepage.php index 5522f36f..026086a9 100644 --- a/ReCaptchaPaypal/Block/LayoutProcessor/Checkout/Onepage.php +++ b/ReCaptchaPaypal/Block/LayoutProcessor/Checkout/Onepage.php @@ -40,7 +40,7 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc * * @param array $jsLayout * @return array From b53fbedce3935fa1234741621aeef9085f481fce Mon Sep 17 00:00:00 2001 From: soumah Date: Sat, 19 Nov 2022 12:37:42 -0600 Subject: [PATCH 04/28] ACP2E-1338: Google reCaptcha in Incorrect position --- .../LayoutProcessor/Checkout/Onepage.php | 8 ++- .../SkipPlaceOrderRecaptchaValidation.php | 68 +++++++++++++++++++ ReCaptchaPaypal/composer.json | 4 +- ReCaptchaPaypal/etc/webapi_rest/di.xml | 13 ++++ 4 files changed, 91 insertions(+), 2 deletions(-) create mode 100644 ReCaptchaPaypal/Plugin/SkipPlaceOrderRecaptchaValidation.php create mode 100644 ReCaptchaPaypal/etc/webapi_rest/di.xml diff --git a/ReCaptchaPaypal/Block/LayoutProcessor/Checkout/Onepage.php b/ReCaptchaPaypal/Block/LayoutProcessor/Checkout/Onepage.php index 026086a9..7c279b82 100644 --- a/ReCaptchaPaypal/Block/LayoutProcessor/Checkout/Onepage.php +++ b/ReCaptchaPaypal/Block/LayoutProcessor/Checkout/Onepage.php @@ -9,6 +9,7 @@ use Magento\Checkout\Block\Checkout\LayoutProcessorInterface; use Magento\Framework\Exception\InputException; +use Magento\Paypal\Model\Config; use Magento\ReCaptchaUi\Model\IsCaptchaEnabledInterface; use Magento\ReCaptchaUi\Model\UiConfigResolverInterface; @@ -56,7 +57,12 @@ public function process($jsLayout) if ($this->isCaptchaEnabled->isCaptchaEnabledFor('place_order')) { $jsLayout['components']['checkout']['children']['steps']['children']['billing-step']['children'] ['payment']['children']['payments-list']['children']['before-place-order']['children'] - ['place-order-recaptcha']['skipPayments']['payflowpro'] = true; + ['place-order-recaptcha']['skipPayments'] += [ + Config::METHOD_EXPRESS => true, + Config::METHOD_PAYFLOWPRO => true, + Config::METHOD_WPP_PE_EXPRESS => true, + Config::METHOD_WPP_PE_BML => true, + ]; } } else { if (isset($jsLayout['components']['checkout']['children']['steps']['children']['billing-step']['children'] diff --git a/ReCaptchaPaypal/Plugin/SkipPlaceOrderRecaptchaValidation.php b/ReCaptchaPaypal/Plugin/SkipPlaceOrderRecaptchaValidation.php new file mode 100644 index 00000000..c881214e --- /dev/null +++ b/ReCaptchaPaypal/Plugin/SkipPlaceOrderRecaptchaValidation.php @@ -0,0 +1,68 @@ +isCaptchaEnabled = $isCaptchaEnabled; + $this->request = $request; + } + + /** + * Skip captcha validation for "place order" button if captcha is enabled for payflow + * + * @param WebapiConfigProvider $subject + * @param ValidationConfigInterface $result + * @param EndpointInterface $endpoint + * @return ValidationConfigInterface + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function afterGetConfigFor( + WebapiConfigProvider $subject, + ?ValidationConfigInterface $result, + EndpointInterface $endpoint + ): ?ValidationConfigInterface { + + if ($result && $this->isCaptchaEnabled->isCaptchaEnabledFor(self::PAYPAL_PAYFLOWPRO_CAPTCHA_ID)) { + $bodyParams = $this->request->getBodyParams(); + $paymentMethod = $bodyParams['paymentMethod'] ?? $bodyParams['payment_method'] ?? []; + if (isset($paymentMethod['method']) && $paymentMethod['method'] === Config::METHOD_PAYFLOWPRO) { + return null; + } + } + + return $result; + } +} diff --git a/ReCaptchaPaypal/composer.json b/ReCaptchaPaypal/composer.json index ca2c4da5..a5f1a745 100644 --- a/ReCaptchaPaypal/composer.json +++ b/ReCaptchaPaypal/composer.json @@ -7,7 +7,9 @@ "magento/module-re-captcha-ui": "*", "magento/module-re-captcha-validation-api": "*", "magento/module-checkout": "*", - "magento/module-re-captcha-webapi-api": "*" + "magento/module-re-captcha-webapi-api": "*", + "magento/module-paypal": "*", + "magento/module-re-captcha-checkout": "*" }, "type": "magento2-module", "license": "OSL-3.0", diff --git a/ReCaptchaPaypal/etc/webapi_rest/di.xml b/ReCaptchaPaypal/etc/webapi_rest/di.xml new file mode 100644 index 00000000..d01d6fb5 --- /dev/null +++ b/ReCaptchaPaypal/etc/webapi_rest/di.xml @@ -0,0 +1,13 @@ + + + + + + + From b5afaf4953eaad7b99d221a9ec3ee449fc5cf816 Mon Sep 17 00:00:00 2001 From: soumah Date: Sat, 19 Nov 2022 15:08:49 -0600 Subject: [PATCH 05/28] ACP2E-1338: Google reCaptcha in Incorrect position --- .../view/frontend/web/js/reCaptchaCheckout.js | 30 ++++++++++++------- .../view/frontend/web/template/reCaptcha.html | 7 +++-- .../SkipPlaceOrderRecaptchaValidation.php | 2 +- 3 files changed, 25 insertions(+), 14 deletions(-) diff --git a/ReCaptchaCheckout/view/frontend/web/js/reCaptchaCheckout.js b/ReCaptchaCheckout/view/frontend/web/js/reCaptchaCheckout.js index 06239d26..46cca89a 100644 --- a/ReCaptchaCheckout/view/frontend/web/js/reCaptchaCheckout.js +++ b/ReCaptchaCheckout/view/frontend/web/js/reCaptchaCheckout.js @@ -3,9 +3,6 @@ * See COPYING.txt for license details. */ -// jscs:disable jsDoc - -/* global grecaptcha */ define( [ 'Magento_ReCaptchaWebapiUi/js/webapiReCaptcha', @@ -22,23 +19,36 @@ define( /** * Render reCAPTCHA + * + * @param {Object} method */ - renderReCaptchaForPayment: function (method) { + renderReCaptchaFor: function (method) { var reCaptcha; - if (!this.skipPayments || !this.skipPayments.hasOwnProperty(method.getCode())) { - reCaptcha = $.extend({}, this); - - reCaptcha.reCaptchaId = this.getPaymentReCaptchaId(method); + if (this.isCheckoutReCaptchaRequiredFor(method)) { + reCaptcha = $.extend(true, {}, this, {reCaptchaId: this.getReCaptchaIdFor(method)}); reCaptcha.renderReCaptcha(); } }, /** - * Render reCAPTCHA + * Get reCAPTCHA ID + * + * @param {Object} method + * @returns {String} */ - getPaymentReCaptchaId: function (method) { + getReCaptchaIdFor: function (method) { return this.getReCaptchaId() + '-' + method.getCode(); + }, + + /** + * Check whether checkout reCAPTCHA is required for payment method + * + * @param {Object} method + * @returns {Boolean} + */ + isCheckoutReCaptchaRequiredFor: function (method) { + return !this.skipPayments || !this.skipPayments.hasOwnProperty(method.getCode()); } }); } diff --git a/ReCaptchaCheckout/view/frontend/web/template/reCaptcha.html b/ReCaptchaCheckout/view/frontend/web/template/reCaptcha.html index 9fee8ab2..4b0cc558 100644 --- a/ReCaptchaCheckout/view/frontend/web/template/reCaptcha.html +++ b/ReCaptchaCheckout/view/frontend/web/template/reCaptcha.html @@ -4,12 +4,12 @@ * See COPYING.txt for license details. */ --> - +
@@ -25,3 +25,4 @@
+ diff --git a/ReCaptchaPaypal/Plugin/SkipPlaceOrderRecaptchaValidation.php b/ReCaptchaPaypal/Plugin/SkipPlaceOrderRecaptchaValidation.php index c881214e..28cd5be0 100644 --- a/ReCaptchaPaypal/Plugin/SkipPlaceOrderRecaptchaValidation.php +++ b/ReCaptchaPaypal/Plugin/SkipPlaceOrderRecaptchaValidation.php @@ -41,7 +41,7 @@ public function __construct( } /** - * Skip captcha validation for "place order" button if captcha is enabled for payflow + * Skip captcha validation for "place order" button if captcha is enabled for payflowpro * * @param WebapiConfigProvider $subject * @param ValidationConfigInterface $result From f9b8bb70b7e9cc432e20703004400313c60ddbd6 Mon Sep 17 00:00:00 2001 From: soumah Date: Sat, 19 Nov 2022 15:59:46 -0600 Subject: [PATCH 06/28] ACP2E-1338: Google reCaptcha in Incorrect position --- ReCaptchaCheckout/view/frontend/layout/checkout_index_index.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ReCaptchaCheckout/view/frontend/layout/checkout_index_index.xml b/ReCaptchaCheckout/view/frontend/layout/checkout_index_index.xml index c4f64a3e..ffc3c6ef 100644 --- a/ReCaptchaCheckout/view/frontend/layout/checkout_index_index.xml +++ b/ReCaptchaCheckout/view/frontend/layout/checkout_index_index.xml @@ -55,7 +55,7 @@ Magento_ReCaptchaCheckout/js/reCaptchaCheckout - place-order-recaptcha + before-place-order checkoutConfig recaptcha-checkout-place-order From 3a855767741a78887246bde906c97a12046f822d Mon Sep 17 00:00:00 2001 From: soumah Date: Sat, 19 Nov 2022 20:29:49 -0600 Subject: [PATCH 07/28] ACP2E-1338: Google reCaptcha in Incorrect position --- .../view/frontend/web/js/reCaptchaCheckout.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/ReCaptchaCheckout/view/frontend/web/js/reCaptchaCheckout.js b/ReCaptchaCheckout/view/frontend/web/js/reCaptchaCheckout.js index 46cca89a..4fd47be3 100644 --- a/ReCaptchaCheckout/view/frontend/web/js/reCaptchaCheckout.js +++ b/ReCaptchaCheckout/view/frontend/web/js/reCaptchaCheckout.js @@ -49,6 +49,23 @@ define( */ isCheckoutReCaptchaRequiredFor: function (method) { return !this.skipPayments || !this.skipPayments.hasOwnProperty(method.getCode()); + }, + + /** + * @inheritdoc + */ + initCaptcha: function () { + var $wrapper, $recaptchaResponseInput; + + this._super(); + // Since there will be multiple recaptcha in the payment form, + // they may override each other if the form is submitted. + // The recaptcha response will be collected in the callback: reCaptchaCallback() + $wrapper = $('#' + this.getReCaptchaId() + '-wrapper'); + $recaptchaResponseInput = $wrapper.find('[name=g-recaptcha-response]'); + if ($recaptchaResponseInput.length) { + $recaptchaResponseInput.prop('disabled', true); + } } }); } From 06ccaac76b3b3c908af76f8973a1c84fcb07a669 Mon Sep 17 00:00:00 2001 From: soumah Date: Mon, 21 Nov 2022 09:05:14 -0600 Subject: [PATCH 08/28] ACP2E-1338: Google reCaptcha in Incorrect position --- .../view/frontend/web/js/reCaptchaCheckout.js | 2 +- .../LayoutProcessor/Checkout/Onepage.php | 19 ++-- ReCaptchaPaypal/Model/ReCaptchaSession.php | 93 +++++++++++++++++++ ReCaptchaPaypal/Observer/PayPalObserver.php | 15 ++- ...> ReplayPayflowReCaptchaForPlaceOrder.php} | 20 +++- ReCaptchaPaypal/etc/frontend/di.xml | 6 ++ ReCaptchaPaypal/etc/webapi_rest/di.xml | 2 +- 7 files changed, 142 insertions(+), 15 deletions(-) create mode 100644 ReCaptchaPaypal/Model/ReCaptchaSession.php rename ReCaptchaPaypal/Plugin/{SkipPlaceOrderRecaptchaValidation.php => ReplayPayflowReCaptchaForPlaceOrder.php} (74%) diff --git a/ReCaptchaCheckout/view/frontend/web/js/reCaptchaCheckout.js b/ReCaptchaCheckout/view/frontend/web/js/reCaptchaCheckout.js index 4fd47be3..0598448a 100644 --- a/ReCaptchaCheckout/view/frontend/web/js/reCaptchaCheckout.js +++ b/ReCaptchaCheckout/view/frontend/web/js/reCaptchaCheckout.js @@ -14,7 +14,7 @@ define( return Component.extend({ defaults: { template: 'Magento_ReCaptchaCheckout/reCaptcha', - skipPayments: [] + skipPayments: [] // List of payment methods that do not require this reCaptcha }, /** diff --git a/ReCaptchaPaypal/Block/LayoutProcessor/Checkout/Onepage.php b/ReCaptchaPaypal/Block/LayoutProcessor/Checkout/Onepage.php index 7c279b82..16a6c528 100644 --- a/ReCaptchaPaypal/Block/LayoutProcessor/Checkout/Onepage.php +++ b/ReCaptchaPaypal/Block/LayoutProcessor/Checkout/Onepage.php @@ -50,19 +50,17 @@ public function __construct( public function process($jsLayout) { $key = 'paypal_payflowpro'; + $skipCheckoutRecaptchaForPayments = [ + Config::METHOD_EXPRESS => true, + Config::METHOD_WPP_PE_EXPRESS => true, + Config::METHOD_WPP_PE_BML => true, + ]; if ($this->isCaptchaEnabled->isCaptchaEnabledFor($key)) { $jsLayout['components']['checkout']['children']['steps']['children']['billing-step']['children'] ['payment']['children']['payments-list']['children']['paypal-captcha']['children'] ['recaptcha']['settings'] = $this->captchaUiConfigResolver->get($key); if ($this->isCaptchaEnabled->isCaptchaEnabledFor('place_order')) { - $jsLayout['components']['checkout']['children']['steps']['children']['billing-step']['children'] - ['payment']['children']['payments-list']['children']['before-place-order']['children'] - ['place-order-recaptcha']['skipPayments'] += [ - Config::METHOD_EXPRESS => true, - Config::METHOD_PAYFLOWPRO => true, - Config::METHOD_WPP_PE_EXPRESS => true, - Config::METHOD_WPP_PE_BML => true, - ]; + $skipCheckoutRecaptchaForPayments[Config::METHOD_PAYFLOWPRO] = true; } } else { if (isset($jsLayout['components']['checkout']['children']['steps']['children']['billing-step']['children'] @@ -71,6 +69,11 @@ public function process($jsLayout) ['payment']['children']['payments-list']['children']['paypal-captcha']['children']['recaptcha']); } } + if ($this->isCaptchaEnabled->isCaptchaEnabledFor('place_order')) { + $jsLayout['components']['checkout']['children']['steps']['children']['billing-step']['children'] + ['payment']['children']['payments-list']['children']['before-place-order']['children'] + ['place-order-recaptcha']['skipPayments'] += $skipCheckoutRecaptchaForPayments; + } return $jsLayout; } diff --git a/ReCaptchaPaypal/Model/ReCaptchaSession.php b/ReCaptchaPaypal/Model/ReCaptchaSession.php new file mode 100644 index 00000000..5fb4434d --- /dev/null +++ b/ReCaptchaPaypal/Model/ReCaptchaSession.php @@ -0,0 +1,93 @@ +timezone = $timezone; + $this->transparentSession = $transparentSession; + $this->checkoutSession = $checkoutSession; + } + + /** + * Saves quote_id and datetime the reCaptcha was verified + * + * @return bool + */ + public function save(): bool + { + $result = false; + if ($this->checkoutSession->getQuote()) { + $this->transparentSession->setData( + self::PAYPAL_PAYFLOWPRO_RECAPTCHA, + [ + 'quote_id' => $this->checkoutSession->getQuote()->getId(), + 'verified_at' => $this->timezone->date()->getTimestamp(), + ] + ); + $result = true; + } + return $result; + } + + /** + * Checks whether the reCaptcha extended time has not expired + * + * @param int $quoteId + * @return bool + */ + public function isValid(int $quoteId): bool + { + $result = false; + $data = $this->transparentSession->getData(self::PAYPAL_PAYFLOWPRO_RECAPTCHA) ?? []; + if (isset($data['quote_id']) + && (int) $data['quote_id'] === $quoteId + && ($data['verified_at'] + self::REPLAY_TIMEOUT) >= $this->timezone->date()->getTimestamp() + ) { + $this->transparentSession->unsetData(self::PAYPAL_PAYFLOWPRO_RECAPTCHA); + $result = true; + } + return $result; + } +} diff --git a/ReCaptchaPaypal/Observer/PayPalObserver.php b/ReCaptchaPaypal/Observer/PayPalObserver.php index ec047819..75956b4b 100644 --- a/ReCaptchaPaypal/Observer/PayPalObserver.php +++ b/ReCaptchaPaypal/Observer/PayPalObserver.php @@ -16,6 +16,7 @@ use Magento\Framework\Exception\InputException; use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Serialize\SerializerInterface; +use Magento\ReCaptchaPaypal\Model\ReCaptchaSession; use Magento\ReCaptchaUi\Model\CaptchaResponseResolverInterface; use Magento\ReCaptchaUi\Model\ErrorMessageConfigInterface; use Magento\ReCaptchaUi\Model\IsCaptchaEnabledInterface; @@ -76,6 +77,11 @@ class PayPalObserver implements ObserverInterface */ private $validationErrorMessagesProvider; + /** + * @var ReCaptchaSession + */ + private $reCaptchaSession; + /** * @param CaptchaResponseResolverInterface $captchaResponseResolver * @param ValidationConfigResolverInterface $validationConfigResolver @@ -86,6 +92,7 @@ class PayPalObserver implements ObserverInterface * @param LoggerInterface $logger * @param ErrorMessageConfigInterface|null $errorMessageConfig * @param ValidationErrorMessagesProvider|null $validationErrorMessagesProvider + * @param ReCaptchaSession|null $reCaptchaSession */ public function __construct( CaptchaResponseResolverInterface $captchaResponseResolver, @@ -96,7 +103,8 @@ public function __construct( IsCaptchaEnabledInterface $isCaptchaEnabled, LoggerInterface $logger, ?ErrorMessageConfigInterface $errorMessageConfig = null, - ?ValidationErrorMessagesProvider $validationErrorMessagesProvider = null + ?ValidationErrorMessagesProvider $validationErrorMessagesProvider = null, + ?ReCaptchaSession $reCaptchaSession = null ) { $this->captchaResponseResolver = $captchaResponseResolver; $this->validationConfigResolver = $validationConfigResolver; @@ -109,6 +117,8 @@ public function __construct( ?? ObjectManager::getInstance()->get(ErrorMessageConfigInterface::class); $this->validationErrorMessagesProvider = $validationErrorMessagesProvider ?? ObjectManager::getInstance()->get(ValidationErrorMessagesProvider::class); + $this->reCaptchaSession = $reCaptchaSession + ?? ObjectManager::getInstance()->get(ReCaptchaSession::class); } /** @@ -148,6 +158,9 @@ public function execute(Observer $observer): void $validationResult->getErrors(), $key ); + } elseif ($this->isCaptchaEnabled->isCaptchaEnabledFor('place_order')) { + // Extend reCaptcha verification to place order + $this->reCaptchaSession->save(); } } } diff --git a/ReCaptchaPaypal/Plugin/SkipPlaceOrderRecaptchaValidation.php b/ReCaptchaPaypal/Plugin/ReplayPayflowReCaptchaForPlaceOrder.php similarity index 74% rename from ReCaptchaPaypal/Plugin/SkipPlaceOrderRecaptchaValidation.php rename to ReCaptchaPaypal/Plugin/ReplayPayflowReCaptchaForPlaceOrder.php index 28cd5be0..400f450a 100644 --- a/ReCaptchaPaypal/Plugin/SkipPlaceOrderRecaptchaValidation.php +++ b/ReCaptchaPaypal/Plugin/ReplayPayflowReCaptchaForPlaceOrder.php @@ -10,11 +10,12 @@ use Magento\Framework\Webapi\Rest\Request; use Magento\Paypal\Model\Config; use Magento\ReCaptchaCheckout\Model\WebapiConfigProvider; +use Magento\ReCaptchaPaypal\Model\ReCaptchaSession; use Magento\ReCaptchaUi\Model\IsCaptchaEnabledInterface; use Magento\ReCaptchaValidationApi\Api\Data\ValidationConfigInterface; use Magento\ReCaptchaWebapiApi\Api\Data\EndpointInterface; -class SkipPlaceOrderRecaptchaValidation +class ReplayPayflowReCaptchaForPlaceOrder { private const PAYPAL_PAYFLOWPRO_CAPTCHA_ID = 'paypal_payflowpro'; @@ -28,20 +29,28 @@ class SkipPlaceOrderRecaptchaValidation */ private Request $request; + /** + * @var ReCaptchaSession + */ + private ReCaptchaSession $reCaptchaSession; + /** * @param IsCaptchaEnabledInterface $isCaptchaEnabled * @param Request $request + * @param ReCaptchaSession $reCaptchaSession */ public function __construct( IsCaptchaEnabledInterface $isCaptchaEnabled, - Request $request + Request $request, + ReCaptchaSession $reCaptchaSession ) { $this->isCaptchaEnabled = $isCaptchaEnabled; $this->request = $request; + $this->reCaptchaSession = $reCaptchaSession; } /** - * Skip captcha validation for "place order" button if captcha is enabled for payflowpro + * Skip reCaptcha validation for "place order" button if captcha is enabled for payflowpro * * @param WebapiConfigProvider $subject * @param ValidationConfigInterface $result @@ -58,8 +67,11 @@ public function afterGetConfigFor( if ($result && $this->isCaptchaEnabled->isCaptchaEnabledFor(self::PAYPAL_PAYFLOWPRO_CAPTCHA_ID)) { $bodyParams = $this->request->getBodyParams(); $paymentMethod = $bodyParams['paymentMethod'] ?? $bodyParams['payment_method'] ?? []; + $cartId = $bodyParams['cartId'] ?? $bodyParams['cart_id'] ?? null; if (isset($paymentMethod['method']) && $paymentMethod['method'] === Config::METHOD_PAYFLOWPRO) { - return null; + if ($cartId && $this->reCaptchaSession->isValid((int) $cartId)) { + return null; + } } } diff --git a/ReCaptchaPaypal/etc/frontend/di.xml b/ReCaptchaPaypal/etc/frontend/di.xml index d63b8637..ed60ff78 100644 --- a/ReCaptchaPaypal/etc/frontend/di.xml +++ b/ReCaptchaPaypal/etc/frontend/di.xml @@ -21,4 +21,10 @@ + + + Magento\Framework\Session\Generic\Proxy + Magento\Checkout\Model\Session\Proxy + + diff --git a/ReCaptchaPaypal/etc/webapi_rest/di.xml b/ReCaptchaPaypal/etc/webapi_rest/di.xml index d01d6fb5..67b665f2 100644 --- a/ReCaptchaPaypal/etc/webapi_rest/di.xml +++ b/ReCaptchaPaypal/etc/webapi_rest/di.xml @@ -8,6 +8,6 @@ - + From 30df77120bdbf9de55472958fc93ae829dc955e1 Mon Sep 17 00:00:00 2001 From: soumah Date: Mon, 21 Nov 2022 09:17:34 -0600 Subject: [PATCH 09/28] ACP2E-1338: Google reCaptcha in Incorrect position --- .../view/frontend/web/js/reCaptchaCheckout.js | 14 ++++++++------ ReCaptchaPaypal/Model/ReCaptchaSession.php | 8 ++++---- .../Plugin/ReplayPayflowReCaptchaForPlaceOrder.php | 10 ++++++---- 3 files changed, 18 insertions(+), 14 deletions(-) diff --git a/ReCaptchaCheckout/view/frontend/web/js/reCaptchaCheckout.js b/ReCaptchaCheckout/view/frontend/web/js/reCaptchaCheckout.js index 0598448a..1b8007cd 100644 --- a/ReCaptchaCheckout/view/frontend/web/js/reCaptchaCheckout.js +++ b/ReCaptchaCheckout/view/frontend/web/js/reCaptchaCheckout.js @@ -18,7 +18,7 @@ define( }, /** - * Render reCAPTCHA + * Render reCAPTCHA for payment method * * @param {Object} method */ @@ -32,7 +32,7 @@ define( }, /** - * Get reCAPTCHA ID + * Get reCAPTCHA ID for payment method * * @param {Object} method * @returns {String} @@ -55,12 +55,14 @@ define( * @inheritdoc */ initCaptcha: function () { - var $wrapper, $recaptchaResponseInput; + var $wrapper, + $recaptchaResponseInput; this._super(); - // Since there will be multiple recaptcha in the payment form, - // they may override each other if the form is submitted. - // The recaptcha response will be collected in the callback: reCaptchaCallback() + // Since there will be multiple reCaptcha in the payment form, + // they may override each other if the form data is serialized and submitted. + // Instead, the reCaptcha response will be collected in the callback: reCaptchaCallback() + // and sent in the request header X-ReCaptcha $wrapper = $('#' + this.getReCaptchaId() + '-wrapper'); $recaptchaResponseInput = $wrapper.find('[name=g-recaptcha-response]'); if ($recaptchaResponseInput.length) { diff --git a/ReCaptchaPaypal/Model/ReCaptchaSession.php b/ReCaptchaPaypal/Model/ReCaptchaSession.php index 5fb4434d..2e805236 100644 --- a/ReCaptchaPaypal/Model/ReCaptchaSession.php +++ b/ReCaptchaPaypal/Model/ReCaptchaSession.php @@ -18,7 +18,7 @@ class ReCaptchaSession { private const PAYPAL_PAYFLOWPRO_RECAPTCHA = 'paypal_payflowpro_recaptcha'; - private const REPLAY_TIMEOUT = 120; + private const TIMEOUT = 120; /** * @var TimezoneInterface @@ -51,7 +51,7 @@ public function __construct( } /** - * Saves quote_id and datetime the reCaptcha was verified + * Saves quote_id and datetime the reCaptcha was verified in session * * @return bool */ @@ -72,7 +72,7 @@ public function save(): bool } /** - * Checks whether the reCaptcha extended time has not expired + * Checks whether the time since reCaptcha was verified is not more than the timeout * * @param int $quoteId * @return bool @@ -83,7 +83,7 @@ public function isValid(int $quoteId): bool $data = $this->transparentSession->getData(self::PAYPAL_PAYFLOWPRO_RECAPTCHA) ?? []; if (isset($data['quote_id']) && (int) $data['quote_id'] === $quoteId - && ($data['verified_at'] + self::REPLAY_TIMEOUT) >= $this->timezone->date()->getTimestamp() + && ($data['verified_at'] + self::TIMEOUT) >= $this->timezone->date()->getTimestamp() ) { $this->transparentSession->unsetData(self::PAYPAL_PAYFLOWPRO_RECAPTCHA); $result = true; diff --git a/ReCaptchaPaypal/Plugin/ReplayPayflowReCaptchaForPlaceOrder.php b/ReCaptchaPaypal/Plugin/ReplayPayflowReCaptchaForPlaceOrder.php index 400f450a..35849d79 100644 --- a/ReCaptchaPaypal/Plugin/ReplayPayflowReCaptchaForPlaceOrder.php +++ b/ReCaptchaPaypal/Plugin/ReplayPayflowReCaptchaForPlaceOrder.php @@ -68,10 +68,12 @@ public function afterGetConfigFor( $bodyParams = $this->request->getBodyParams(); $paymentMethod = $bodyParams['paymentMethod'] ?? $bodyParams['payment_method'] ?? []; $cartId = $bodyParams['cartId'] ?? $bodyParams['cart_id'] ?? null; - if (isset($paymentMethod['method']) && $paymentMethod['method'] === Config::METHOD_PAYFLOWPRO) { - if ($cartId && $this->reCaptchaSession->isValid((int) $cartId)) { - return null; - } + if (isset($paymentMethod['method']) + && $paymentMethod['method'] === Config::METHOD_PAYFLOWPRO + && $cartId + && $this->reCaptchaSession->isValid((int) $cartId) + ) { + return null; } } From 618900f512f1e5fc9f165bc0e323f0ec5f28a05b Mon Sep 17 00:00:00 2001 From: soumah Date: Mon, 21 Nov 2022 10:05:21 -0600 Subject: [PATCH 10/28] ACP2E-1338: Google reCaptcha in Incorrect position --- ReCaptchaPaypal/Observer/PayPalObserver.php | 1 + 1 file changed, 1 insertion(+) diff --git a/ReCaptchaPaypal/Observer/PayPalObserver.php b/ReCaptchaPaypal/Observer/PayPalObserver.php index 75956b4b..ba4e0e25 100644 --- a/ReCaptchaPaypal/Observer/PayPalObserver.php +++ b/ReCaptchaPaypal/Observer/PayPalObserver.php @@ -93,6 +93,7 @@ class PayPalObserver implements ObserverInterface * @param ErrorMessageConfigInterface|null $errorMessageConfig * @param ValidationErrorMessagesProvider|null $validationErrorMessagesProvider * @param ReCaptchaSession|null $reCaptchaSession + * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( CaptchaResponseResolverInterface $captchaResponseResolver, From 9b65c30a2b417a5321d7a31697c622ea3cfcaf71 Mon Sep 17 00:00:00 2001 From: soumah Date: Mon, 21 Nov 2022 11:51:45 -0600 Subject: [PATCH 11/28] ACP2E-1338: Google reCaptcha in Incorrect position --- ReCaptchaPaypal/etc/di.xml | 6 ++++++ ReCaptchaPaypal/etc/frontend/di.xml | 6 ------ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ReCaptchaPaypal/etc/di.xml b/ReCaptchaPaypal/etc/di.xml index 44caf936..d52a6d06 100644 --- a/ReCaptchaPaypal/etc/di.xml +++ b/ReCaptchaPaypal/etc/di.xml @@ -14,4 +14,10 @@ + + + Magento\Framework\Session\Generic\Proxy + Magento\Checkout\Model\Session\Proxy + + diff --git a/ReCaptchaPaypal/etc/frontend/di.xml b/ReCaptchaPaypal/etc/frontend/di.xml index ed60ff78..d63b8637 100644 --- a/ReCaptchaPaypal/etc/frontend/di.xml +++ b/ReCaptchaPaypal/etc/frontend/di.xml @@ -21,10 +21,4 @@ - - - Magento\Framework\Session\Generic\Proxy - Magento\Checkout\Model\Session\Proxy - - From 0e537a52876fcf76e5f049f060ee94e7ff3eb314 Mon Sep 17 00:00:00 2001 From: soumah Date: Mon, 21 Nov 2022 13:12:48 -0600 Subject: [PATCH 12/28] ACP2E-1338: Google reCaptcha in Incorrect position --- .../ReplayPayflowReCaptchaForPlaceOrder.php | 20 ++++++++++++++++--- ReCaptchaPaypal/composer.json | 1 + 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/ReCaptchaPaypal/Plugin/ReplayPayflowReCaptchaForPlaceOrder.php b/ReCaptchaPaypal/Plugin/ReplayPayflowReCaptchaForPlaceOrder.php index 35849d79..d648246d 100644 --- a/ReCaptchaPaypal/Plugin/ReplayPayflowReCaptchaForPlaceOrder.php +++ b/ReCaptchaPaypal/Plugin/ReplayPayflowReCaptchaForPlaceOrder.php @@ -9,6 +9,7 @@ use Magento\Framework\Webapi\Rest\Request; use Magento\Paypal\Model\Config; +use Magento\Quote\Model\QuoteIdMaskFactory; use Magento\ReCaptchaCheckout\Model\WebapiConfigProvider; use Magento\ReCaptchaPaypal\Model\ReCaptchaSession; use Magento\ReCaptchaUi\Model\IsCaptchaEnabledInterface; @@ -34,19 +35,27 @@ class ReplayPayflowReCaptchaForPlaceOrder */ private ReCaptchaSession $reCaptchaSession; + /** + * @var QuoteIdMaskFactory + */ + private QuoteIdMaskFactory $quoteIdMaskFactory; + /** * @param IsCaptchaEnabledInterface $isCaptchaEnabled * @param Request $request * @param ReCaptchaSession $reCaptchaSession + * @param QuoteIdMaskFactory $quoteIdMaskFactory */ public function __construct( IsCaptchaEnabledInterface $isCaptchaEnabled, Request $request, - ReCaptchaSession $reCaptchaSession + ReCaptchaSession $reCaptchaSession, + QuoteIdMaskFactory $quoteIdMaskFactory ) { $this->isCaptchaEnabled = $isCaptchaEnabled; $this->request = $request; $this->reCaptchaSession = $reCaptchaSession; + $this->quoteIdMaskFactory = $quoteIdMaskFactory; } /** @@ -71,9 +80,14 @@ public function afterGetConfigFor( if (isset($paymentMethod['method']) && $paymentMethod['method'] === Config::METHOD_PAYFLOWPRO && $cartId - && $this->reCaptchaSession->isValid((int) $cartId) ) { - return null; + // check if it is guest cart, then resolve cart id by mask ID + if (!is_numeric($cartId)) { + $cartId = $this->quoteIdMaskFactory->create()->load($cartId, 'masked_id')->getQuoteId(); + } + if ($this->reCaptchaSession->isValid((int) $cartId)) { + return null; + } } } diff --git a/ReCaptchaPaypal/composer.json b/ReCaptchaPaypal/composer.json index a5f1a745..0410e45c 100644 --- a/ReCaptchaPaypal/composer.json +++ b/ReCaptchaPaypal/composer.json @@ -8,6 +8,7 @@ "magento/module-re-captcha-validation-api": "*", "magento/module-checkout": "*", "magento/module-re-captcha-webapi-api": "*", + "magento/module-quote": "*", "magento/module-paypal": "*", "magento/module-re-captcha-checkout": "*" }, From 584a4928e7ace08b3ade7369bc83ab632dd1b6b0 Mon Sep 17 00:00:00 2001 From: soumah Date: Tue, 22 Nov 2022 07:38:43 -0600 Subject: [PATCH 13/28] ACP2E-1338: Google reCaptcha in Incorrect position --- .../frontend/web/js/model/place-order-mixin.js | 15 ++++++++------- .../view/frontend/web/js/reCaptchaCheckout.js | 8 +++++++- .../view/frontend/web/template/reCaptcha.html | 2 +- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/ReCaptchaCheckout/view/frontend/web/js/model/place-order-mixin.js b/ReCaptchaCheckout/view/frontend/web/js/model/place-order-mixin.js index 2cd102bd..d78b2e3d 100644 --- a/ReCaptchaCheckout/view/frontend/web/js/model/place-order-mixin.js +++ b/ReCaptchaCheckout/view/frontend/web/js/model/place-order-mixin.js @@ -4,23 +4,24 @@ */ /* eslint-disable max-nested-callbacks */ -/* eslint-disable max-depth */ define([ 'jquery', 'mage/utils/wrapper', - 'Magento_ReCaptchaWebapiUi/js/webapiReCaptchaRegistry', - 'Magento_Checkout/js/model/quote' -], function ($, wrapper, recaptchaRegistry, quote) { + 'Magento_ReCaptchaWebapiUi/js/webapiReCaptchaRegistry' +], function ($, wrapper, recaptchaRegistry) { 'use strict'; return function (placeOrder) { return wrapper.wrap(placeOrder, function (originalAction, serviceUrl, payload, messageContainer) { var recaptchaDeferred, - reCaptchaId; + reCaptchaId, + $activeReCaptcha; - if (quote.paymentMethod()) { - reCaptchaId = 'recaptcha-checkout-place-order-' + quote.paymentMethod().method; + $activeReCaptcha = $('.recaptcha-checkout-place-order:visible .g-recaptcha'); + + if ($activeReCaptcha.length > 0) { + reCaptchaId = $activeReCaptcha.last().attr('id'); } if (reCaptchaId !== undefined && recaptchaRegistry.triggers.hasOwnProperty(reCaptchaId)) { diff --git a/ReCaptchaCheckout/view/frontend/web/js/reCaptchaCheckout.js b/ReCaptchaCheckout/view/frontend/web/js/reCaptchaCheckout.js index 1b8007cd..cbc51637 100644 --- a/ReCaptchaCheckout/view/frontend/web/js/reCaptchaCheckout.js +++ b/ReCaptchaCheckout/view/frontend/web/js/reCaptchaCheckout.js @@ -11,6 +11,9 @@ define( function (Component, $) { 'use strict'; + var reCaptchaIds = new WeakMap(), + uuid = 0; + return Component.extend({ defaults: { template: 'Magento_ReCaptchaCheckout/reCaptcha', @@ -38,7 +41,10 @@ define( * @returns {String} */ getReCaptchaIdFor: function (method) { - return this.getReCaptchaId() + '-' + method.getCode(); + if (!reCaptchaIds.has(method)) { + reCaptchaIds.set(method, this.getReCaptchaId() + '-' + uuid++); + } + return reCaptchaIds.get(method); }, /** diff --git a/ReCaptchaCheckout/view/frontend/web/template/reCaptcha.html b/ReCaptchaCheckout/view/frontend/web/template/reCaptcha.html index 4b0cc558..9ea1c08c 100644 --- a/ReCaptchaCheckout/view/frontend/web/template/reCaptcha.html +++ b/ReCaptchaCheckout/view/frontend/web/template/reCaptcha.html @@ -5,7 +5,7 @@ */ --> -
+ + + + + + + Magento\ReCaptchaContact\Model\ButtonLock\ContactUsFormSubmit + + + + + + + contact_us_form_submit + contact + + + diff --git a/ReCaptchaCustomer/Plugin/Customer/DisableCreateAccountButton.php b/ReCaptchaCustomer/Plugin/Customer/DisableCreateAccountButton.php deleted file mode 100644 index ea6e3ebc..00000000 --- a/ReCaptchaCustomer/Plugin/Customer/DisableCreateAccountButton.php +++ /dev/null @@ -1,46 +0,0 @@ -isCaptchaEnabled = $isCaptchaEnabled; - } - - /** - * Temporally disable button Create Account while captcha is loading - * - * @param CreateAccountButton $subject - * @return bool - * @throws InputException - * @SuppressWarnings(PHPMD.UnusedFormalParameter) - */ - public function afterDisabled(CreateAccountButton $subject): bool - { - $key = 'customer_create'; - return $this->isCaptchaEnabled->isCaptchaEnabledFor($key); - } -} diff --git a/ReCaptchaCustomer/Plugin/Customer/DisableForgotPasswordButton.php b/ReCaptchaCustomer/Plugin/Customer/DisableForgotPasswordButton.php deleted file mode 100644 index 6c3d7df6..00000000 --- a/ReCaptchaCustomer/Plugin/Customer/DisableForgotPasswordButton.php +++ /dev/null @@ -1,46 +0,0 @@ -isCaptchaEnabled = $isCaptchaEnabled; - } - - /** - * Temporally disable Forgot password button while captcha is loading - * - * @param ForgotPasswordButton $subject - * @return bool - * @throws InputException - * @SuppressWarnings(PHPMD.UnusedFormalParameter) - */ - public function afterDisabled(ForgotPasswordButton $subject): bool - { - $key = 'customer_forgot_password'; - return $this->isCaptchaEnabled->isCaptchaEnabledFor($key); - } -} diff --git a/ReCaptchaCustomer/Plugin/Customer/DisableLoginButton.php b/ReCaptchaCustomer/Plugin/Customer/DisableLoginButton.php deleted file mode 100644 index b0713fa3..00000000 --- a/ReCaptchaCustomer/Plugin/Customer/DisableLoginButton.php +++ /dev/null @@ -1,47 +0,0 @@ -isCaptchaEnabled = $isCaptchaEnabled; - } - - /** - * Temporally disable Login button while captcha is loading - * - * @param LoginButton $subject - * @return bool - * @throws InputException - * @SuppressWarnings(PHPMD.UnusedFormalParameter) - */ - public function afterDisabled(LoginButton $subject): bool - { - $key = 'customer_login'; - return $this->isCaptchaEnabled->isCaptchaEnabledFor($key); - } -} diff --git a/ReCaptchaCustomer/Test/Unit/Plugin/Customer/DisableCreateAccountButtonTest.php b/ReCaptchaCustomer/Test/Unit/Plugin/Customer/DisableCreateAccountButtonTest.php deleted file mode 100644 index bc4b4239..00000000 --- a/ReCaptchaCustomer/Test/Unit/Plugin/Customer/DisableCreateAccountButtonTest.php +++ /dev/null @@ -1,55 +0,0 @@ -isCaptchaEnabled = $this->getMockForAbstractClass( - IsCaptchaEnabledInterface::class - ); - $this->subject = $this->createMock(CreateAccountButton::class); - - $this->plugin = new DisableCreateAccountButton( - $this->isCaptchaEnabled - ); - } - - public function testAfterEnabled() - { - $key = 'customer_create'; - $this->isCaptchaEnabled->expects($this->once()) - ->method('isCaptchaEnabledFor')->with($key)->willReturn(true); - $this->assertEquals(true, $this->plugin->afterDisabled($this->subject)); - } -} diff --git a/ReCaptchaCustomer/Test/Unit/Plugin/Customer/DisableForgotPasswordButtonTest.php b/ReCaptchaCustomer/Test/Unit/Plugin/Customer/DisableForgotPasswordButtonTest.php deleted file mode 100644 index 489a5136..00000000 --- a/ReCaptchaCustomer/Test/Unit/Plugin/Customer/DisableForgotPasswordButtonTest.php +++ /dev/null @@ -1,55 +0,0 @@ -isCaptchaEnabled = $this->getMockForAbstractClass( - IsCaptchaEnabledInterface::class - ); - $this->subject = $this->createMock(ForgotPasswordButton::class); - - $this->plugin = new DisableForgotPasswordButton( - $this->isCaptchaEnabled - ); - } - - public function testAfterEnabled() - { - $key = 'customer_forgot_password'; - $this->isCaptchaEnabled->expects($this->once()) - ->method('isCaptchaEnabledFor')->with($key)->willReturn(true); - $this->assertEquals(true, $this->plugin->afterDisabled($this->subject)); - } -} diff --git a/ReCaptchaCustomer/Test/Unit/Plugin/Customer/DisableLoginButtonTest.php b/ReCaptchaCustomer/Test/Unit/Plugin/Customer/DisableLoginButtonTest.php deleted file mode 100644 index 3ef369b5..00000000 --- a/ReCaptchaCustomer/Test/Unit/Plugin/Customer/DisableLoginButtonTest.php +++ /dev/null @@ -1,61 +0,0 @@ -isCaptchaEnabled = $this->getMockForAbstractClass( - IsCaptchaEnabledInterface::class - ); - $this->subject = $this->createMock(LoginButton::class); - - $this->plugin = new DisableLoginButton( - $this->isCaptchaEnabled - ); - } - - public function testAfterEnabled() - { - $key = 'customer_login'; - $this->isCaptchaEnabled->expects($this->once()) - ->method('isCaptchaEnabledFor')->with($key)->willReturn(true); - $this->assertEquals(true, $this->plugin->afterDisabled($this->subject)); - } -} diff --git a/ReCaptchaCustomer/etc/frontend/di.xml b/ReCaptchaCustomer/etc/frontend/di.xml index 73640968..e32489da 100644 --- a/ReCaptchaCustomer/etc/frontend/di.xml +++ b/ReCaptchaCustomer/etc/frontend/di.xml @@ -20,16 +20,38 @@ - - - - - - - - + + + + Magento\ReCaptchaCustomer\Model\ButtonLock\CustomerCreateFormSubmit + Magento\ReCaptchaCustomer\Model\ButtonLock\CustomerEditFormSubmit + Magento\ReCaptchaCustomer\Model\ButtonLock\CustomerForgotPasswordFormSubmit + Magento\ReCaptchaCustomer\Model\ButtonLock\CustomerLoginFormSubmit + + + + + customer_create_form_submit + customer_create + + + + + customer_edit_form_submit + customer_edit + + + + + customer_forgot_password_form_submit + customer_forgot_password + + + + + customer_login_form_submit + customer_login + + diff --git a/ReCaptchaFrontendUi/view/frontend/web/js/reCaptcha.js b/ReCaptchaFrontendUi/view/frontend/web/js/reCaptcha.js index 14f2af32..43c476ec 100644 --- a/ReCaptchaFrontendUi/view/frontend/web/js/reCaptcha.js +++ b/ReCaptchaFrontendUi/view/frontend/web/js/reCaptcha.js @@ -174,7 +174,11 @@ define( } else { this.tokenField = null; } - if ($('#send2').length > 0) {$('#send2').prop('disabled', false);} + let submitButton = parentForm.find('button:not([type]), [type=submit]'); + + if (submitButton.length) { + submitButton.prop('disabled', false); + } }, /** diff --git a/ReCaptchaNewsletter/etc/frontend/di.xml b/ReCaptchaNewsletter/etc/frontend/di.xml new file mode 100644 index 00000000..4622e4c3 --- /dev/null +++ b/ReCaptchaNewsletter/etc/frontend/di.xml @@ -0,0 +1,24 @@ + + + + + + + + Magento\ReCaptchaNewsletter\Model\ButtonLock\NewsletterFormSubmit + + + + + + newsletter_form_submit + newsletter + + + diff --git a/ReCaptchaReview/etc/frontend/di.xml b/ReCaptchaReview/etc/frontend/di.xml new file mode 100644 index 00000000..509158d4 --- /dev/null +++ b/ReCaptchaReview/etc/frontend/di.xml @@ -0,0 +1,24 @@ + + + + + + + + Magento\ReCaptchaReview\Model\ButtonLock\ReviewFormSubmit + + + + + + review_form_submit + product_review + + + diff --git a/ReCaptchaSendFriend/etc/frontend/di.xml b/ReCaptchaSendFriend/etc/frontend/di.xml new file mode 100644 index 00000000..b48b3cb7 --- /dev/null +++ b/ReCaptchaSendFriend/etc/frontend/di.xml @@ -0,0 +1,24 @@ + + + + + + + + Magento\ReCaptchaSendFriend\Model\ButtonLock\SendFriendFormSubmit + + + + + + sendfriend_form_submit + sendfriend + + + diff --git a/ReCaptchaUi/Model/ButtonLock.php b/ReCaptchaUi/Model/ButtonLock.php new file mode 100644 index 00000000..8b349ee5 --- /dev/null +++ b/ReCaptchaUi/Model/ButtonLock.php @@ -0,0 +1,59 @@ +isCaptchaEnabled = $isCaptchaEnabled; + $this->reCaptchaId = $reCaptchaId; + $this->buttonCode = $buttonCode; + } + + /** + * @inheritDoc + */ + public function getCode(): string + { + return $this->buttonCode; + } + + /** + * @inheritDoc + */ + public function isDisabled(): bool + { + return $this->isCaptchaEnabled->isCaptchaEnabledFor($this->reCaptchaId); + } +} From 3798cb1c33ac4bb863b2efe234d4bdd3ace5d721 Mon Sep 17 00:00:00 2001 From: soumah Date: Thu, 8 Dec 2022 17:12:54 -0600 Subject: [PATCH 15/28] ACP2E-1338: Google reCaptcha in Incorrect position --- .../LayoutProcessor/Checkout/OnepageTest.php | 167 ++++++++++++++ .../LayoutProcessor/Checkout/OnepageTest.php | 209 ++++++++++++++++++ 2 files changed, 376 insertions(+) create mode 100644 ReCaptchaCheckout/Test/Unit/Block/LayoutProcessor/Checkout/OnepageTest.php create mode 100644 ReCaptchaPaypal/Test/Unit/Block/LayoutProcessor/Checkout/OnepageTest.php diff --git a/ReCaptchaCheckout/Test/Unit/Block/LayoutProcessor/Checkout/OnepageTest.php b/ReCaptchaCheckout/Test/Unit/Block/LayoutProcessor/Checkout/OnepageTest.php new file mode 100644 index 00000000..8cf57413 --- /dev/null +++ b/ReCaptchaCheckout/Test/Unit/Block/LayoutProcessor/Checkout/OnepageTest.php @@ -0,0 +1,167 @@ + [ + 'checkout' => [ + 'children' => [ + 'steps' => [ + 'children' => [ + 'shipping-step' => [ + 'children' => [ + 'shippingAddress' => [ + 'children' => [ + 'customer-email' => [ + 'children' => [ + 'recaptcha' => [] + ] + ] + ] + ] + ] + ], + 'billing-step' => [ + 'children' => [ + 'payment' => [ + 'children' => [ + 'customer-email' => [ + 'children' => [ + 'recaptcha' => [] + ] + ], + 'payments-list' => [ + 'children' => [ + 'before-place-order' => [ + 'children' => [ + 'place-order-recaptcha' => [] + ] + ] + ] + ] + ] + ] + ] + ] + ] + ], + 'authentication' => [ + 'children' => [ + 'recaptcha' => [] + ] + ] + ] + ] + ] + ]; + + /** + * @inheritdoc + */ + protected function setUp(): void + { + parent::setUp(); + $this->uiConfigResolver = $this->getMockForAbstractClass(UiConfigResolverInterface::class); + $this->isCaptchEnabled = $this->getMockForAbstractClass(IsCaptchaEnabledInterface::class); + $this->model = new Onepage( + $this->uiConfigResolver, + $this->isCaptchEnabled + ); + } + + /** + * @dataProvider processDataProvider + */ + public function testProcess(array $mocks, array $expected): void + { + $this->uiConfigResolver->method('get') + ->willReturnMap($mocks['uiConfigResolver']); + $this->isCaptchEnabled->method('isCaptchaEnabledFor') + ->willReturnMap($mocks['isCaptchaEnabled']); + $prefix = 'components/checkout/children/'; + $config = new DataObject($this->model->process($this->jsLayout)); + $actual = []; + foreach (array_keys($expected) as $path) { + $actual[$path] = $config->getDataByPath($prefix.$path); + } + $this->assertSame($expected, $actual); + } + + public function processDataProvider(): array + { + return [ + [ + [ + 'isCaptchaEnabled' => [ + ['customer_login', false], + ['place_order', false], + ], + 'uiConfigResolver' => [ + ['customer_login', ['type' => 'invisible']], + ['place_order', ['type' => 'robot']], + ], + ], + [ + 'steps/children/shipping-step/children/shippingAddress/children/customer-email/children' => [], + 'steps/children/billing-step/children/payment/children/customer-email/children' => [], + 'authentication/children' => [], + 'steps/children/billing-step/children/payment/children/payments-list/children/before-place-order/' . + 'children' => [], + ] + ], + [ + [ + 'isCaptchaEnabled' => [ + ['customer_login', true], + ['place_order', true], + ], + 'uiConfigResolver' => [ + ['customer_login', ['type' => 'invisible']], + ['place_order', ['type' => 'robot']], + ], + ], + [ + 'steps/children/shipping-step/children/shippingAddress/children/' . + 'customer-email/children' => ['recaptcha' => ['settings' => ['type' => 'invisible']]], + 'steps/children/billing-step/children/payment/children/' . + 'customer-email/children' => ['recaptcha' => ['settings' => ['type' => 'invisible']]], + 'authentication/children' => ['recaptcha' => ['settings' => ['type' => 'invisible']]], + 'steps/children/billing-step/children/payment/children/payments-list/children/before-place-order/' . + 'children' => ['place-order-recaptcha' => ['settings' => ['type' => 'robot']]], + ] + ] + ]; + } +} diff --git a/ReCaptchaPaypal/Test/Unit/Block/LayoutProcessor/Checkout/OnepageTest.php b/ReCaptchaPaypal/Test/Unit/Block/LayoutProcessor/Checkout/OnepageTest.php new file mode 100644 index 00000000..ac2382eb --- /dev/null +++ b/ReCaptchaPaypal/Test/Unit/Block/LayoutProcessor/Checkout/OnepageTest.php @@ -0,0 +1,209 @@ + [ + 'checkout' => [ + 'children' => [ + 'steps' => [ + 'children' => [ + 'billing-step' => [ + 'children' => [ + 'payment' => [ + 'children' => [ + 'payments-list' => [ + 'children' => [ + 'before-place-order' => [ + 'children' => [ + 'place-order-recaptcha' => [ + 'skipPayments' => [] + ] + ] + ], + 'paypal-captcha' => [ + 'children' => [ + 'recaptcha' => [] + ] + ] + ] + ] + ] + ] + ] + ] + ] + ] + ] + ] + ] + ]; + + /** + * @inheritdoc + */ + protected function setUp(): void + { + parent::setUp(); + $this->uiConfigResolver = $this->getMockForAbstractClass(UiConfigResolverInterface::class); + $this->isCaptchEnabled = $this->getMockForAbstractClass(IsCaptchaEnabledInterface::class); + $this->model = new Onepage( + $this->uiConfigResolver, + $this->isCaptchEnabled + ); + } + + /** + * @dataProvider processDataProvider + */ + public function testProcess(array $mocks, array $expected): void + { + $this->uiConfigResolver->method('get') + ->willReturnMap($mocks['uiConfigResolver']); + $this->isCaptchEnabled->method('isCaptchaEnabledFor') + ->willReturnMap($mocks['isCaptchaEnabled']); + $prefix = 'components/checkout/children/'; + $config = new DataObject($this->model->process($this->jsLayout)); + $actual = []; + foreach (array_keys($expected) as $path) { + $actual[$path] = $config->getDataByPath($prefix . $path); + } + $this->assertSame($expected, $actual); + } + + public function processDataProvider(): array + { + return [ + [ + [ + 'isCaptchaEnabled' => [ + ['paypal_payflowpro', false], + ['place_order', false], + ], + 'uiConfigResolver' => [ + ['paypal_payflowpro', ['type' => 'invisible']], + ['place_order', ['type' => 'robot']], + ], + ], + [ + 'steps/children/billing-step/children/payment/children/payments-list/children/paypal-captcha/' . + 'children' => [], + 'steps/children/billing-step/children/payment/children/payments-list/children/before-place-order/' . + 'children' => [ + 'place-order-recaptcha' => [ + 'skipPayments' => [] + ] + ], + ] + ], + [ + [ + 'isCaptchaEnabled' => [ + ['paypal_payflowpro', false], + ['place_order', true], + ], + 'uiConfigResolver' => [ + ['paypal_payflowpro', ['type' => 'invisible']], + ['place_order', ['type' => 'robot']], + ], + ], + [ + 'steps/children/billing-step/children/payment/children/payments-list/children/paypal-captcha/' . + 'children' => [], + 'steps/children/billing-step/children/payment/children/payments-list/children/before-place-order/' . + 'children' => [ + 'place-order-recaptcha' => [ + 'skipPayments' => [ + Config::METHOD_EXPRESS => true, + Config::METHOD_WPP_PE_EXPRESS => true, + Config::METHOD_WPP_PE_BML => true, + ] + ] + ], + ] + ], + [ + [ + 'isCaptchaEnabled' => [ + ['paypal_payflowpro', true], + ['place_order', false], + ], + 'uiConfigResolver' => [ + ['paypal_payflowpro', ['type' => 'invisible']], + ['place_order', ['type' => 'robot']], + ], + ], + [ + 'steps/children/billing-step/children/payment/children/payments-list/children/paypal-captcha/' . + 'children' => ['recaptcha' => ['settings' => ['type' => 'invisible']]], + 'steps/children/billing-step/children/payment/children/payments-list/children/before-place-order/' . + 'children' => [ + 'place-order-recaptcha' => [ + 'skipPayments' => [] + ] + ], + ] + ], + [ + [ + 'isCaptchaEnabled' => [ + ['paypal_payflowpro', true], + ['place_order', true], + ], + 'uiConfigResolver' => [ + ['paypal_payflowpro', ['type' => 'invisible']], + ['place_order', ['type' => 'robot']], + ], + ], + [ + 'steps/children/billing-step/children/payment/children/payments-list/children/paypal-captcha/' . + 'children' => ['recaptcha' => ['settings' => ['type' => 'invisible']]], + 'steps/children/billing-step/children/payment/children/payments-list/children/before-place-order/' . + 'children' => [ + 'place-order-recaptcha' => [ + 'skipPayments' => [ + Config::METHOD_EXPRESS => true, + Config::METHOD_WPP_PE_EXPRESS => true, + Config::METHOD_WPP_PE_BML => true, + Config::METHOD_PAYFLOWPRO => true + ] + ] + ], + ] + ] + ]; + } +} From 7b6fc3ce093a18ebd4e68f6d440e3583e60e5a33 Mon Sep 17 00:00:00 2001 From: soumah Date: Fri, 9 Dec 2022 13:48:08 -0600 Subject: [PATCH 16/28] ACP2E-1338: Google reCaptcha in Incorrect position --- .../Test/Unit/Model/ReCaptchaSessionTest.php | 132 +++++++++++ .../Test/Unit/Observer/PayPalObserverTest.php | 216 ++++++++++++++++++ 2 files changed, 348 insertions(+) create mode 100644 ReCaptchaPaypal/Test/Unit/Model/ReCaptchaSessionTest.php create mode 100644 ReCaptchaPaypal/Test/Unit/Observer/PayPalObserverTest.php diff --git a/ReCaptchaPaypal/Test/Unit/Model/ReCaptchaSessionTest.php b/ReCaptchaPaypal/Test/Unit/Model/ReCaptchaSessionTest.php new file mode 100644 index 00000000..58112e20 --- /dev/null +++ b/ReCaptchaPaypal/Test/Unit/Model/ReCaptchaSessionTest.php @@ -0,0 +1,132 @@ +timezone = $this->getMockForAbstractClass(TimezoneInterface::class); + $this->transparentSession = $this->getMockBuilder(SessionManager::class) + ->disableOriginalConstructor() + ->onlyMethods(['getData']) + ->addMethods(['setData', 'unsetData']) + ->getMock(); + $this->checkoutSession = $this->getMockBuilder(SessionManager::class) + ->disableOriginalConstructor() + ->addMethods(['getQuote']) + ->getMock(); + $this->model = new ReCaptchaSession( + $this->timezone, + $this->transparentSession, + $this->checkoutSession + ); + } + + public function testSaveIfThereIsNoActiveQuote(): void + { + $this->checkoutSession->expects($this->once()) + ->method('getQuote') + ->willReturn(null); + $this->assertFalse($this->model->save()); + } + + public function testSaveIfThereIsActiveQuote(): void + { + $quote = $this->getMockForAbstractClass(CartInterface::class); + $quote->expects($this->once()) + ->method('getId') + ->willReturn(1); + $this->checkoutSession->expects($this->exactly(2)) + ->method('getQuote') + ->willReturn($quote); + $this->timezone->expects($this->once()) + ->method('date') + ->willReturn(new \Datetime('@1670607221')); + $this->transparentSession->expects($this->once()) + ->method('setData') + ->with('paypal_payflowpro_recaptcha', ['quote_id' => 1, 'verified_at' => 1670607221]); + $this->assertTrue($this->model->save()); + } + + public function testIsInvalidIfQuoteIdIsMissing(): void + { + $this->transparentSession->expects($this->once()) + ->method('getData') + ->with('paypal_payflowpro_recaptcha') + ->willReturn(null); + $this->assertFalse($this->model->isValid(1)); + } + + public function testIsInvalidIfQuoteIdDoesNotMatch(): void + { + $this->transparentSession->expects($this->once()) + ->method('getData') + ->with('paypal_payflowpro_recaptcha') + ->willReturn(['quote_id' => 2, 'verified_at' => 1670607221]); + $this->assertFalse($this->model->isValid(1)); + } + + public function testIsInvalidIfExpired(): void + { + $this->timezone->expects($this->once()) + ->method('date') + ->willReturn(new \Datetime('@1670607342')); + $this->transparentSession->expects($this->once()) + ->method('getData') + ->with('paypal_payflowpro_recaptcha') + ->willReturn(['quote_id' => 1, 'verified_at' => 1670607221]); + $this->assertFalse($this->model->isValid(1)); + } + + public function testIsInvalidIfNotExpired(): void + { + $this->timezone->expects($this->once()) + ->method('date') + ->willReturn(new \Datetime('@1670607340')); + $this->transparentSession->expects($this->once()) + ->method('getData') + ->with('paypal_payflowpro_recaptcha') + ->willReturn(['quote_id' => 1, 'verified_at' => 1670607221]); + $this->transparentSession->expects($this->once()) + ->method('unsetData') + ->with('paypal_payflowpro_recaptcha'); + $this->assertTrue($this->model->isValid(1)); + } +} diff --git a/ReCaptchaPaypal/Test/Unit/Observer/PayPalObserverTest.php b/ReCaptchaPaypal/Test/Unit/Observer/PayPalObserverTest.php new file mode 100644 index 00000000..2f8efe8d --- /dev/null +++ b/ReCaptchaPaypal/Test/Unit/Observer/PayPalObserverTest.php @@ -0,0 +1,216 @@ +getMockForAbstractClass(CaptchaResponseResolverInterface::class); + $validationConfigResolver = $this->getMockForAbstractClass(ValidationConfigResolverInterface::class); + $this->captchaValidator = $this->getMockForAbstractClass(ValidatorInterface::class); + $actionFlag = $this->createMock(ActionFlag::class); + $serializer = $this->getMockForAbstractClass(SerializerInterface::class); + $this->isCaptchaEnabled = $this->getMockForAbstractClass(IsCaptchaEnabledInterface::class); + $logger = $this->getMockForAbstractClass(LoggerInterface::class); + $errorMessageConfig = $this->getMockForAbstractClass(ErrorMessageConfigInterface::class); + $validationErrorMessagesProvider = $this->createMock(ValidationErrorMessagesProvider::class); + $this->reCaptchaSession = $this->createMock(ReCaptchaSession::class); + $this->model = new PayPalObserver( + $captchaResponseResolver, + $validationConfigResolver, + $this->captchaValidator, + $actionFlag, + $serializer, + $this->isCaptchaEnabled, + $logger, + $errorMessageConfig, + $validationErrorMessagesProvider, + $this->reCaptchaSession + ); + $controller = $this->getMockBuilder(AbstractAction::class) + ->disableOriginalConstructor() + ->onlyMethods(['getRequest', 'getResponse']) + ->getMockForAbstractClass(); + $request = $this->getMockForAbstractClass(RequestInterface::class); + $response = $this->getMockBuilder(ResponseInterface::class) + ->disableOriginalConstructor() + ->addMethods(['representJson']) + ->getMockForAbstractClass(); + $controller->method('getRequest')->willReturn($request); + $controller->method('getResponse')->willReturn($response); + $this->observer = new Observer(['controller_action' => $controller]); + } + + /** + * @param array $mocks + * @dataProvider executeDataProvider + */ + public function testExecute(array $mocks): void + { + $validationResult = $this->createMock(ValidationResult::class); + $validationResult->expects($mocks['validationResult'][0]['expects'] ?? $this->never()) + ->method('isValid')->willReturn($mocks['validationResult'][0]['willReturn'] ?? false); + $this->captchaValidator->expects($mocks['captchaValidator'][0]['expects'] ?? $this->never()) + ->method('isValid') + ->willReturn($validationResult); + unset($mocks['validationResult'], $mocks['captchaValidator']); + $this->configureMock($mocks); + $this->model->execute($this->observer); + } + + public function executeDataProvider(): array + { + return [ + [ + [ + 'isCaptchaEnabled' => [ + [ + 'method' => 'isCaptchaEnabledFor', + 'willReturnMap' => [ + ['paypal_payflowpro', false], + ['place_order', false], + ] + ] + ], + 'reCaptchaSession' => [ + [ + 'method' => 'save', + 'expects' => $this->never(), + ] + ] + ] + ], + [ + [ + 'isCaptchaEnabled' => [ + [ + 'method' => 'isCaptchaEnabledFor', + 'willReturnMap' => [ + ['paypal_payflowpro', true], + ['place_order', false], + ] + ] + ], + 'reCaptchaSession' => [ + [ + 'method' => 'save', + 'expects' => $this->never(), + ] + ], + 'captchaValidator' => [ + [ + 'method' => 'isValid', + 'expects' => $this->once(), + ] + ], + 'validationResult' => [ + [ + 'method' => 'isValid', + 'expects' => $this->once(), + 'willReturn' => true, + ] + ] + ] + ], + [ + [ + 'isCaptchaEnabled' => [ + [ + 'method' => 'isCaptchaEnabledFor', + 'willReturnMap' => [ + ['paypal_payflowpro', true], + ['place_order', true], + ] + ] + ], + 'reCaptchaSession' => [ + [ + 'method' => 'save', + 'expects' => $this->once(), + ] + ], + 'captchaValidator' => [ + [ + 'method' => 'isValid', + 'expects' => $this->once(), + ] + ], + 'validationResult' => [ + [ + 'method' => 'isValid', + 'expects' => $this->once(), + 'willReturn' => true, + ] + ] + ] + ] + ]; + } + + private function configureMock(array $mocks): void + { + foreach ($mocks as $prop => $propMocks) { + foreach ($propMocks as $mock) { + $builder = $this->$prop->expects($mock['expects'] ?? $this->any()); + unset($mock['expects']); + foreach ($mock as $method => $args) { + $builder->$method(...[$args]); + } + } + } + } +} From 0ecdf3101779151fcef597e381139c5779779813 Mon Sep 17 00:00:00 2001 From: soumah Date: Fri, 9 Dec 2022 18:21:26 -0600 Subject: [PATCH 17/28] ACP2E-1338: Google reCaptcha in Incorrect position --- .../Test/Unit/Observer/PayPalObserverTest.php | 19 +- ...eplayPayflowReCaptchaForPlaceOrderTest.php | 277 ++++++++++++++++++ 2 files changed, 289 insertions(+), 7 deletions(-) create mode 100644 ReCaptchaPaypal/Test/Unit/Plugin/ReplayPayflowReCaptchaForPlaceOrderTest.php diff --git a/ReCaptchaPaypal/Test/Unit/Observer/PayPalObserverTest.php b/ReCaptchaPaypal/Test/Unit/Observer/PayPalObserverTest.php index 2f8efe8d..a02ae439 100644 --- a/ReCaptchaPaypal/Test/Unit/Observer/PayPalObserverTest.php +++ b/ReCaptchaPaypal/Test/Unit/Observer/PayPalObserverTest.php @@ -53,6 +53,11 @@ class PayPalObserverTest extends TestCase */ private $observer; + /** + * @var ValidationResult|MockObject + */ + private $validationResult; + /** * @inheritdoc */ @@ -93,6 +98,7 @@ protected function setUp(): void $controller->method('getRequest')->willReturn($request); $controller->method('getResponse')->willReturn($response); $this->observer = new Observer(['controller_action' => $controller]); + $this->validationResult = $this->createMock(ValidationResult::class); } /** @@ -101,13 +107,6 @@ protected function setUp(): void */ public function testExecute(array $mocks): void { - $validationResult = $this->createMock(ValidationResult::class); - $validationResult->expects($mocks['validationResult'][0]['expects'] ?? $this->never()) - ->method('isValid')->willReturn($mocks['validationResult'][0]['willReturn'] ?? false); - $this->captchaValidator->expects($mocks['captchaValidator'][0]['expects'] ?? $this->never()) - ->method('isValid') - ->willReturn($validationResult); - unset($mocks['validationResult'], $mocks['captchaValidator']); $this->configureMock($mocks); $this->model->execute($this->observer); } @@ -155,6 +154,7 @@ public function executeDataProvider(): array [ 'method' => 'isValid', 'expects' => $this->once(), + 'willReturnProperty' => 'validationResult' ] ], 'validationResult' => [ @@ -187,6 +187,7 @@ public function executeDataProvider(): array [ 'method' => 'isValid', 'expects' => $this->once(), + 'willReturnProperty' => 'validationResult' ] ], 'validationResult' => [ @@ -208,6 +209,10 @@ private function configureMock(array $mocks): void $builder = $this->$prop->expects($mock['expects'] ?? $this->any()); unset($mock['expects']); foreach ($mock as $method => $args) { + if ($method === 'willReturnProperty') { + $method = 'willReturn'; + $args = $this->$args; + } $builder->$method(...[$args]); } } diff --git a/ReCaptchaPaypal/Test/Unit/Plugin/ReplayPayflowReCaptchaForPlaceOrderTest.php b/ReCaptchaPaypal/Test/Unit/Plugin/ReplayPayflowReCaptchaForPlaceOrderTest.php new file mode 100644 index 00000000..6a6e884d --- /dev/null +++ b/ReCaptchaPaypal/Test/Unit/Plugin/ReplayPayflowReCaptchaForPlaceOrderTest.php @@ -0,0 +1,277 @@ +isCaptchaEnabled = $this->getMockForAbstractClass(IsCaptchaEnabledInterface::class); + $this->request = $this->createMock(Request::class); + $this->reCaptchaSession = $this->createMock(ReCaptchaSession::class); + $this->quoteIdMaskFactory = $this->createMock(QuoteIdMaskFactory::class); + $this->quoteIdMask = $this->getMockBuilder(QuoteIdMask::class) + ->onlyMethods(['load']) + ->addMethods(['getQuoteId']) + ->disableOriginalConstructor() + ->getMock(); + $this->model = new ReplayPayflowReCaptchaForPlaceOrder( + $this->isCaptchaEnabled, + $this->request, + $this->reCaptchaSession, + $this->quoteIdMaskFactory + ); + } + + /** + * @param array $mocks + * @param bool $isResultNull + * @param bool $isReturnNull + * @dataProvider afterGetConfigForDataProvider + */ + public function testAfterGetConfigFor(array $mocks, bool $isResultNull, bool $isReturnNull): void + { + $this->configureMock($mocks); + $subject = $this->createMock(WebapiConfigProvider::class); + $result = $this->getMockForAbstractClass(ValidationConfigInterface::class); + $endpoint = $this->getMockForAbstractClass(EndpointInterface::class); + $this->assertSame( + $isReturnNull ? null : $result, + $this->model->afterGetConfigFor($subject, $isResultNull ? null : $result, $endpoint) + ); + } + + /** + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ + public function afterGetConfigForDataProvider(): array + { + return [ + [ + [ + 'reCaptchaSession' => [ + ['method' => 'isValid', 'expects' => $this->never()] + ] + ], + true, + true + ], + [ + [ + 'isCaptchaEnabled' => [ + ['method' => 'isCaptchaEnabledFor', 'with' => 'paypal_payflowpro', 'willReturn' => false] + ], + 'reCaptchaSession' => [ + ['method' => 'isValid', 'expects' => $this->never(),] + ] + ], + false, + false + ], + [ + [ + 'isCaptchaEnabled' => [ + ['method' => 'isCaptchaEnabledFor', 'with' => 'paypal_payflowpro', 'willReturn' => true] + ], + 'request' => [ + ['method' => 'getBodyParams', 'expects' => $this->once(), 'willReturn' => []] + ], + 'reCaptchaSession' => [ + ['method' => 'isValid', 'expects' => $this->never(),] + ] + ], + false, + false + ], + [ + [ + 'isCaptchaEnabled' => [ + ['method' => 'isCaptchaEnabledFor', 'with' => 'paypal_payflowpro', 'willReturn' => true] + ], + 'request' => [ + [ + 'method' => 'getBodyParams', + 'expects' => $this->once(), + 'willReturn' => ['cartId' => 1, 'paymentMethod' => ['method' => 'checkmo']] + ] + ], + 'reCaptchaSession' => [ + ['method' => 'isValid', 'expects' => $this->never(), 'willReturn' => false] + ] + ], + false, + false + ], + [ + [ + 'isCaptchaEnabled' => [ + ['method' => 'isCaptchaEnabledFor', 'with' => 'paypal_payflowpro', 'willReturn' => true] + ], + 'request' => [ + [ + 'method' => 'getBodyParams', + 'expects' => $this->once(), + 'willReturn' => ['cartId' => 1, 'paymentMethod' => ['method' => Config::METHOD_PAYFLOWPRO]] + ] + ], + 'reCaptchaSession' => [ + ['method' => 'isValid', 'expects' => $this->once(), 'with' => 1, 'willReturn' => false] + ] + ], + false, + false + ], + [ + [ + 'isCaptchaEnabled' => [ + ['method' => 'isCaptchaEnabledFor', 'with' => 'paypal_payflowpro', 'willReturn' => true] + ], + 'request' => [ + [ + 'method' => 'getBodyParams', + 'expects' => $this->once(), + 'willReturn' => ['cartId' => 1, 'paymentMethod' => ['method' => Config::METHOD_PAYFLOWPRO]] + ] + ], + 'reCaptchaSession' => [ + ['method' => 'isValid', 'expects' => $this->once(), 'with' => 1, 'willReturn' => true] + ] + ], + false, + true + ], + [ + [ + 'isCaptchaEnabled' => [ + ['method' => 'isCaptchaEnabledFor', 'with' => 'paypal_payflowpro', 'willReturn' => true] + ], + 'request' => [ + [ + 'method' => 'getBodyParams', + 'expects' => $this->once(), + 'willReturn' => [ + 'cart_id' => 1, + 'payment_method' => ['method' => Config::METHOD_PAYFLOWPRO] + ] + ] + ], + 'reCaptchaSession' => [ + ['method' => 'isValid', 'expects' => $this->once(), 'with' => 1, 'willReturn' => true] + ] + ], + false, + true + ], + [ + [ + 'isCaptchaEnabled' => [ + ['method' => 'isCaptchaEnabledFor', 'with' => 'paypal_payflowpro', 'willReturn' => true] + ], + 'request' => [ + [ + 'method' => 'getBodyParams', + 'expects' => $this->once(), + 'willReturn' => [ + 'cartId' => '17uc43rge98nc92', + 'paymentMethod' => ['method' => Config::METHOD_PAYFLOWPRO] + ] + ] + ], + 'quoteIdMaskFactory' => [ + [ + 'method' => 'create', + 'expects' => $this->once(), + 'willReturnProperty' => 'quoteIdMask' + ] + ], + 'quoteIdMask' => [ + [ + 'method' => 'load', + 'expects' => $this->once(), + 'willReturnSelf' => null + ], + [ + 'method' => 'getQuoteId', + 'expects' => $this->once(), + 'willReturn' => 2 + ] + ], + 'reCaptchaSession' => [ + ['method' => 'isValid', 'expects' => $this->once(), 'with' => 2, 'willReturn' => true] + ] + ], + false, + true + ], + ]; + } + + private function configureMock(array $mocks): void + { + foreach ($mocks as $prop => $propMocks) { + foreach ($propMocks as $mock) { + $builder = $this->$prop->expects($mock['expects'] ?? $this->any()); + unset($mock['expects']); + foreach ($mock as $method => $args) { + if ($method === 'willReturnProperty') { + $method = 'willReturn'; + $args = $this->$args; + } + $builder->$method(...[$args]); + } + } + } + } +} From 92d9a7ec19ded09458401e6ce040b1930e0ae657 Mon Sep 17 00:00:00 2001 From: soumah Date: Mon, 12 Dec 2022 08:42:11 -0600 Subject: [PATCH 18/28] ACP2E-1338: Google reCaptcha in Incorrect position --- .../Test/Unit/Block/LayoutProcessor/Checkout/OnepageTest.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ReCaptchaPaypal/Test/Unit/Block/LayoutProcessor/Checkout/OnepageTest.php b/ReCaptchaPaypal/Test/Unit/Block/LayoutProcessor/Checkout/OnepageTest.php index ac2382eb..2a01a10b 100644 --- a/ReCaptchaPaypal/Test/Unit/Block/LayoutProcessor/Checkout/OnepageTest.php +++ b/ReCaptchaPaypal/Test/Unit/Block/LayoutProcessor/Checkout/OnepageTest.php @@ -104,6 +104,9 @@ public function testProcess(array $mocks, array $expected): void $this->assertSame($expected, $actual); } + /** + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ public function processDataProvider(): array { return [ From c4e6e1f370f0f54102b4f2aa6a4491fb9f455358 Mon Sep 17 00:00:00 2001 From: soumah Date: Mon, 12 Dec 2022 09:32:24 -0600 Subject: [PATCH 19/28] ACP2E-1338: Google reCaptcha in Incorrect position --- ReCaptchaPaypal/Test/Unit/Observer/PayPalObserverTest.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ReCaptchaPaypal/Test/Unit/Observer/PayPalObserverTest.php b/ReCaptchaPaypal/Test/Unit/Observer/PayPalObserverTest.php index a02ae439..66f4eead 100644 --- a/ReCaptchaPaypal/Test/Unit/Observer/PayPalObserverTest.php +++ b/ReCaptchaPaypal/Test/Unit/Observer/PayPalObserverTest.php @@ -26,6 +26,9 @@ use PHPUnit\Framework\TestCase; use Psr\Log\LoggerInterface; +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ class PayPalObserverTest extends TestCase { /** From 9366431d42772d073649418c10f094e8f1b02ff8 Mon Sep 17 00:00:00 2001 From: Anna Bukatar Date: Fri, 20 Jan 2023 18:21:02 -0800 Subject: [PATCH 20/28] ACP2E-1539: Google reCAPTCHA v3 on Checkout not working for Check/Money order or custom payment methods --- .../web/js/model/place-order-mixin.js | 11 ++- .../view/frontend/web/js/webapiReCaptcha.js | 10 ++- .../web/js/webapiReCaptchaRegistry.js | 5 ++ .../js/model/place-order-mixin.test.js | 90 +++++++++++++------ 4 files changed, 81 insertions(+), 35 deletions(-) diff --git a/ReCaptchaCheckout/view/frontend/web/js/model/place-order-mixin.js b/ReCaptchaCheckout/view/frontend/web/js/model/place-order-mixin.js index 9693ae7e..c376b915 100644 --- a/ReCaptchaCheckout/view/frontend/web/js/model/place-order-mixin.js +++ b/ReCaptchaCheckout/view/frontend/web/js/model/place-order-mixin.js @@ -30,8 +30,15 @@ define([ }); //Trigger ReCaptcha validation recaptchaRegistry.triggers['recaptcha-checkout-place-order'](); - //remove listener so that place order action is only triggered by the 'Place Order' button - recaptchaRegistry.removeListener('recaptcha-checkout-place-order'); + + if ( + !recaptchaRegistry._isInvisibleType.hasOwnProperty('recaptcha-checkout-place-order') || + recaptchaRegistry._isInvisibleType['recaptcha-checkout-place-order'] === false + ) { + //remove listener so that place order action is only triggered by the 'Place Order' button + recaptchaRegistry.removeListener('recaptcha-checkout-place-order'); + } + return recaptchaDeferred; } diff --git a/ReCaptchaWebapiUi/view/frontend/web/js/webapiReCaptcha.js b/ReCaptchaWebapiUi/view/frontend/web/js/webapiReCaptcha.js index c7c7c15d..312a68b5 100644 --- a/ReCaptchaWebapiUi/view/frontend/web/js/webapiReCaptcha.js +++ b/ReCaptchaWebapiUi/view/frontend/web/js/webapiReCaptcha.js @@ -43,14 +43,16 @@ define( var self = this, trigger; + trigger = function () { + self.reCaptchaCallback(grecaptcha.getResponse(widgetId)); + }; + registry._isInvisibleType[this.getReCaptchaId()] = false; + if (this.getIsInvisibleRecaptcha()) { trigger = function () { grecaptcha.execute(widgetId); }; - } else { - trigger = function () { - self.reCaptchaCallback(grecaptcha.getResponse(widgetId)); - }; + registry._isInvisibleType[this.getReCaptchaId()] = true; } if (this.autoTrigger) { diff --git a/ReCaptchaWebapiUi/view/frontend/web/js/webapiReCaptchaRegistry.js b/ReCaptchaWebapiUi/view/frontend/web/js/webapiReCaptchaRegistry.js index 633f092c..3d504c14 100644 --- a/ReCaptchaWebapiUi/view/frontend/web/js/webapiReCaptchaRegistry.js +++ b/ReCaptchaWebapiUi/view/frontend/web/js/webapiReCaptchaRegistry.js @@ -26,6 +26,11 @@ define([], function () { */ _listeners: {}, + /** + * recaptchaId: bool map + */ + _isInvisibleType: {}, + /** * Add a listener to when the ReCaptcha finishes verification * @param {String} id - ReCaptchaId diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/ReCaptchaCheckout/frontend/js/model/place-order-mixin.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/ReCaptchaCheckout/frontend/js/model/place-order-mixin.test.js index 499dca35..70229ccd 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/ReCaptchaCheckout/frontend/js/model/place-order-mixin.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/ReCaptchaCheckout/frontend/js/model/place-order-mixin.test.js @@ -41,38 +41,40 @@ define(['squire' expect(placeOrderMixins['Magento_ReCaptchaCheckout/js/model/place-order-mixin']).toBe(true); }); + }); + + describe('Magento_Checkout/js/action/redirect-on-success is called', function () { + var recaptchaId = 'recaptcha-checkout-place-order', + messageContainer = jasmine.createSpy('messageContainer'), + payload = {}, + serviceUrl = 'test', - it('Magento_Checkout/js/action/redirect-on-success is called', function () { - let recaptchaId = 'recaptcha-checkout-place-order', - messageContainer = jasmine.createSpy('messageContainer'), - payload = {}, - serviceUrl = 'test', - - /** - * Order place action mock - * - * @returns {{fail: fail, done: (function(Function): *)}} - */ - action = function () { - return { - /** - * Success result for request - * - * @param {Function} handler - * @returns {*} - */ - done: function (handler) { - handler(); - return this; - }, - - /** - * Fail result for request - */ - fail: function () {} - }; + /** + * Order place action mock + * + * @returns {{fail: fail, done: (function(Function): *)}} + */ + action = function () { + return { + /** + * Success result for request + * + * @param {Function} handler + * @returns {*} + */ + done: function (handler) { + handler(); + return this; + }, + + /** + * Fail result for request + */ + fail: function () {} }; + }; + it('Only PlaceOrder button triggers place order action', function () { /** * Triggers declared listener * @@ -93,9 +95,39 @@ define(['squire' registry.addListener = function (id, func) { registry._listeners[id] = func; }; + registry.removeListener = jasmine.createSpy(); mixin()(action, serviceUrl, payload, messageContainer); expect(registry.removeListener).toHaveBeenCalledWith(recaptchaId); }); + + it('PlaceOrder Listener is called for invisible google recaptcha', function () { + /** + * Triggers declared listener + * + * @returns {*} + */ + registry.triggers[recaptchaId] = function () { + if (registry._listeners[recaptchaId] !== undefined) { + return registry._listeners[recaptchaId]('token'); + } + }; + + /** + * Registers a listener + * + * @param id + * @param func + */ + registry.addListener = function (id, func) { + registry._listeners[id] = func; + }; + + registry._isInvisibleType[recaptchaId] = true; + registry.removeListener = jasmine.createSpy(); + mixin()(action, serviceUrl, payload, messageContainer); + + expect(registry.removeListener).not.toHaveBeenCalled(); + }); }); }); From dc3c0cca64ba77c957107c4c41d277c602476c83 Mon Sep 17 00:00:00 2001 From: Sachin Admane Date: Wed, 25 Jan 2023 15:38:38 -0600 Subject: [PATCH 21/28] AC-7715: Recaptcha exception fixes. --- ReCaptchaValidation/Model/ReCaptchaProxy.php | 58 ++++++++++++++++++++ ReCaptchaValidation/Model/Validator.php | 8 +-- 2 files changed, 62 insertions(+), 4 deletions(-) create mode 100644 ReCaptchaValidation/Model/ReCaptchaProxy.php diff --git a/ReCaptchaValidation/Model/ReCaptchaProxy.php b/ReCaptchaValidation/Model/ReCaptchaProxy.php new file mode 100644 index 00000000..7e103c11 --- /dev/null +++ b/ReCaptchaValidation/Model/ReCaptchaProxy.php @@ -0,0 +1,58 @@ +validationResultFactory = $validationResultFactory; $this->errorMessagesProvider = $errorMessagesProvider; From a18e2315ca660ed5aeebb759db32b782ab6ab919 Mon Sep 17 00:00:00 2001 From: Sachin Admane Date: Fri, 27 Jan 2023 13:05:36 -0600 Subject: [PATCH 22/28] AC-7715: Refactoring wrapper class and removing redundant functions. --- ReCaptchaValidation/Model/ReCaptcha.php | 23 ++++++++ ReCaptchaValidation/Model/ReCaptchaProxy.php | 58 -------------------- ReCaptchaValidation/Model/Validator.php | 8 +-- 3 files changed, 27 insertions(+), 62 deletions(-) create mode 100644 ReCaptchaValidation/Model/ReCaptcha.php delete mode 100644 ReCaptchaValidation/Model/ReCaptchaProxy.php diff --git a/ReCaptchaValidation/Model/ReCaptcha.php b/ReCaptchaValidation/Model/ReCaptcha.php new file mode 100644 index 00000000..b02a41de --- /dev/null +++ b/ReCaptchaValidation/Model/ReCaptcha.php @@ -0,0 +1,23 @@ +validationResultFactory = $validationResultFactory; $this->errorMessagesProvider = $errorMessagesProvider; From 222cf2e27ed39598765d587ba5bdb20089042b2c Mon Sep 17 00:00:00 2001 From: Sachin Admane Date: Fri, 27 Jan 2023 16:06:18 -0600 Subject: [PATCH 23/28] AC-7836: Google ReCaptcha not working on checkout page. --- .../web/js/model/place-order-mixin.js | 11 ++- .../view/frontend/web/js/webapiReCaptcha.js | 10 ++- .../web/js/webapiReCaptchaRegistry.js | 5 ++ .../js/model/place-order-mixin.test.js | 90 +++++++++++++------ 4 files changed, 81 insertions(+), 35 deletions(-) diff --git a/ReCaptchaCheckout/view/frontend/web/js/model/place-order-mixin.js b/ReCaptchaCheckout/view/frontend/web/js/model/place-order-mixin.js index 9693ae7e..c376b915 100644 --- a/ReCaptchaCheckout/view/frontend/web/js/model/place-order-mixin.js +++ b/ReCaptchaCheckout/view/frontend/web/js/model/place-order-mixin.js @@ -30,8 +30,15 @@ define([ }); //Trigger ReCaptcha validation recaptchaRegistry.triggers['recaptcha-checkout-place-order'](); - //remove listener so that place order action is only triggered by the 'Place Order' button - recaptchaRegistry.removeListener('recaptcha-checkout-place-order'); + + if ( + !recaptchaRegistry._isInvisibleType.hasOwnProperty('recaptcha-checkout-place-order') || + recaptchaRegistry._isInvisibleType['recaptcha-checkout-place-order'] === false + ) { + //remove listener so that place order action is only triggered by the 'Place Order' button + recaptchaRegistry.removeListener('recaptcha-checkout-place-order'); + } + return recaptchaDeferred; } diff --git a/ReCaptchaWebapiUi/view/frontend/web/js/webapiReCaptcha.js b/ReCaptchaWebapiUi/view/frontend/web/js/webapiReCaptcha.js index c7c7c15d..312a68b5 100644 --- a/ReCaptchaWebapiUi/view/frontend/web/js/webapiReCaptcha.js +++ b/ReCaptchaWebapiUi/view/frontend/web/js/webapiReCaptcha.js @@ -43,14 +43,16 @@ define( var self = this, trigger; + trigger = function () { + self.reCaptchaCallback(grecaptcha.getResponse(widgetId)); + }; + registry._isInvisibleType[this.getReCaptchaId()] = false; + if (this.getIsInvisibleRecaptcha()) { trigger = function () { grecaptcha.execute(widgetId); }; - } else { - trigger = function () { - self.reCaptchaCallback(grecaptcha.getResponse(widgetId)); - }; + registry._isInvisibleType[this.getReCaptchaId()] = true; } if (this.autoTrigger) { diff --git a/ReCaptchaWebapiUi/view/frontend/web/js/webapiReCaptchaRegistry.js b/ReCaptchaWebapiUi/view/frontend/web/js/webapiReCaptchaRegistry.js index 633f092c..3d504c14 100644 --- a/ReCaptchaWebapiUi/view/frontend/web/js/webapiReCaptchaRegistry.js +++ b/ReCaptchaWebapiUi/view/frontend/web/js/webapiReCaptchaRegistry.js @@ -26,6 +26,11 @@ define([], function () { */ _listeners: {}, + /** + * recaptchaId: bool map + */ + _isInvisibleType: {}, + /** * Add a listener to when the ReCaptcha finishes verification * @param {String} id - ReCaptchaId diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/ReCaptchaCheckout/frontend/js/model/place-order-mixin.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/ReCaptchaCheckout/frontend/js/model/place-order-mixin.test.js index 499dca35..70229ccd 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/ReCaptchaCheckout/frontend/js/model/place-order-mixin.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/ReCaptchaCheckout/frontend/js/model/place-order-mixin.test.js @@ -41,38 +41,40 @@ define(['squire' expect(placeOrderMixins['Magento_ReCaptchaCheckout/js/model/place-order-mixin']).toBe(true); }); + }); + + describe('Magento_Checkout/js/action/redirect-on-success is called', function () { + var recaptchaId = 'recaptcha-checkout-place-order', + messageContainer = jasmine.createSpy('messageContainer'), + payload = {}, + serviceUrl = 'test', - it('Magento_Checkout/js/action/redirect-on-success is called', function () { - let recaptchaId = 'recaptcha-checkout-place-order', - messageContainer = jasmine.createSpy('messageContainer'), - payload = {}, - serviceUrl = 'test', - - /** - * Order place action mock - * - * @returns {{fail: fail, done: (function(Function): *)}} - */ - action = function () { - return { - /** - * Success result for request - * - * @param {Function} handler - * @returns {*} - */ - done: function (handler) { - handler(); - return this; - }, - - /** - * Fail result for request - */ - fail: function () {} - }; + /** + * Order place action mock + * + * @returns {{fail: fail, done: (function(Function): *)}} + */ + action = function () { + return { + /** + * Success result for request + * + * @param {Function} handler + * @returns {*} + */ + done: function (handler) { + handler(); + return this; + }, + + /** + * Fail result for request + */ + fail: function () {} }; + }; + it('Only PlaceOrder button triggers place order action', function () { /** * Triggers declared listener * @@ -93,9 +95,39 @@ define(['squire' registry.addListener = function (id, func) { registry._listeners[id] = func; }; + registry.removeListener = jasmine.createSpy(); mixin()(action, serviceUrl, payload, messageContainer); expect(registry.removeListener).toHaveBeenCalledWith(recaptchaId); }); + + it('PlaceOrder Listener is called for invisible google recaptcha', function () { + /** + * Triggers declared listener + * + * @returns {*} + */ + registry.triggers[recaptchaId] = function () { + if (registry._listeners[recaptchaId] !== undefined) { + return registry._listeners[recaptchaId]('token'); + } + }; + + /** + * Registers a listener + * + * @param id + * @param func + */ + registry.addListener = function (id, func) { + registry._listeners[id] = func; + }; + + registry._isInvisibleType[recaptchaId] = true; + registry.removeListener = jasmine.createSpy(); + mixin()(action, serviceUrl, payload, messageContainer); + + expect(registry.removeListener).not.toHaveBeenCalled(); + }); }); }); From c767587b0b4ab63f77b92c25d3aa610606830bc1 Mon Sep 17 00:00:00 2001 From: Sergio Vera Date: Tue, 7 Mar 2023 14:03:53 +0100 Subject: [PATCH 24/28] LYNX-85: Ported recaptchaV3Config query from ReCaptchaGraphQlPwa --- ReCaptchaVersion3Invisible/Model/Config.php | 164 ++++++++++++++++++ ReCaptchaVersion3Invisible/etc/di.xml | 16 ++ .../Model/Resolver/ReCaptchaV3.php | 131 ++++++++++++++ .../Test/Api/ReCaptchaV3ConfigTest.php | 153 ++++++++++++++++ ReCaptchaWebapiGraphQl/composer.json | 2 + ReCaptchaWebapiGraphQl/etc/schema.graphqls | 29 ++++ 6 files changed, 495 insertions(+) create mode 100644 ReCaptchaVersion3Invisible/Model/Config.php create mode 100644 ReCaptchaWebapiGraphQl/Model/Resolver/ReCaptchaV3.php create mode 100644 ReCaptchaWebapiGraphQl/Test/Api/ReCaptchaV3ConfigTest.php create mode 100644 ReCaptchaWebapiGraphQl/etc/schema.graphqls diff --git a/ReCaptchaVersion3Invisible/Model/Config.php b/ReCaptchaVersion3Invisible/Model/Config.php new file mode 100644 index 00000000..a394f5b1 --- /dev/null +++ b/ReCaptchaVersion3Invisible/Model/Config.php @@ -0,0 +1,164 @@ +formTypes = $formTypes; + $this->uiConfigProvider = $uiConfigProvider; + $this->validationConfigProvider = $validationConfigProvider; + } + + /** + * Get website's Google API public key + * + * @return string + */ + public function getWebsiteKey(): string + { + if (!$this->websiteKey) { + $this->websiteKey = $this->getUiConfig()['rendering']['sitekey']; + } + return $this->websiteKey; + } + + /** + * Get configured minimum score value + * + * @return float + */ + public function getMinimumScore(): float + { + if (!$this->minimumScore) { + $this->minimumScore = $this->validationConfig->getExtensionAttributes()->getScoreThreshold(); + } + return $this->minimumScore; + } + + /** + * Get configured captcha's badge position + * + * @return string + */ + public function getBadgePosition(): string + { + if (!$this->badgePosition) { + $this->badgePosition = $this->getUiConfig()['rendering']['badge']; + } + return $this->badgePosition; + } + + /** + * Get code of language to send notifications + * + * @return string + */ + public function getLanguageCode(): string + { + if (!$this->languageCode) { + $this->languageCode = $this->getUiConfig()['rendering']['hl']; + } + return $this->languageCode; + } + + /** + * Get ReCaptchaV3's available form types + * + * @return array + */ + public function getFormTypes(): array + { + return $this->formTypes; + } + + /** + * Get front-end's validation configurations + * + * @return ValidationConfigInterface + */ + public function getValidationConfig(): ValidationConfigInterface + { + if (!$this->validationConfig) { + $this->validationConfig = $this->validationConfigProvider->get(); + } + return $this->validationConfig; + } + + /** + * Get front-end's UI configurations + * + * @return array + */ + private function getUiConfig(): array + { + if (empty($this->uiConfig)) { + $this->uiConfig = $this->uiConfigProvider->get(); + } + return $this->uiConfig; + } +} diff --git a/ReCaptchaVersion3Invisible/etc/di.xml b/ReCaptchaVersion3Invisible/etc/di.xml index d63385c4..7549b73e 100644 --- a/ReCaptchaVersion3Invisible/etc/di.xml +++ b/ReCaptchaVersion3Invisible/etc/di.xml @@ -84,4 +84,20 @@ + + + + place_order + contact + customer_forgot_password + customer_edit + customer_login + customer_create + newsletter + product_review + sendfriend + braintree + + + diff --git a/ReCaptchaWebapiGraphQl/Model/Resolver/ReCaptchaV3.php b/ReCaptchaWebapiGraphQl/Model/Resolver/ReCaptchaV3.php new file mode 100644 index 00000000..5abc02bc --- /dev/null +++ b/ReCaptchaWebapiGraphQl/Model/Resolver/ReCaptchaV3.php @@ -0,0 +1,131 @@ +reCaptchaV3Config = $reCaptchaV3Config; + $this->captchaTypeResolver = $captchaTypeResolver; + $this->errorMessageConfig = $errorMessageConfig; + } + + /** + * @inheritDoc + */ + public function resolve( + Field $field, + $context, + ResolveInfo $info, + array $value = null, + array $args = null + ) { + return [ + 'is_enabled' => $this->isEnabled(), + 'website_key' => $this->reCaptchaV3Config->getWebsiteKey(), + 'minimum_score' => $this->reCaptchaV3Config->getMinimumScore(), + 'badge_position' => $this->reCaptchaV3Config->getBadgePosition(), + 'language_code' => $this->reCaptchaV3Config->getLanguageCode(), + 'failure_message' => $this->getFailureMessage(), + 'forms' => $this->getEnumFormTypes() + ]; + } + + /** + * Get whether service has all the required settings set up to be enabled or not + * + * @return bool + * @throws InputException + */ + public function isEnabled(): bool + { + if ($this->isEnabled === null) { + $this->isEnabled = $this->reCaptchaV3Config->getValidationConfig()->getPrivateKey() && + !empty($this->reCaptchaV3Config->getWebsiteKey()) && + !empty($this->getEnumFormTypes()); + } + return $this->isEnabled; + } + + /** + * Get form keys that are configured to ReCaptcha V3 + * + * @return array + * @throws InputException + */ + private function getEnumFormTypes(): array + { + $forms = []; + if (empty($this->forms)) { + foreach ($this->reCaptchaV3Config->getFormTypes() as $formType) { + if ($this->captchaTypeResolver->getCaptchaTypeFor($formType) === self::RECAPTCHA_TYPE) { + $forms[] = $formType; + } + } + } + return array_map('strtoupper', $forms); + } + + /** + * Get configured message sent in case of failure + * + * @return string + */ + public function getFailureMessage(): string + { + if (!$this->failureMessage) { + $this->failureMessage = $this->errorMessageConfig->getValidationFailureMessage(); + } + return $this->failureMessage; + } +} diff --git a/ReCaptchaWebapiGraphQl/Test/Api/ReCaptchaV3ConfigTest.php b/ReCaptchaWebapiGraphQl/Test/Api/ReCaptchaV3ConfigTest.php new file mode 100644 index 00000000..c0978cae --- /dev/null +++ b/ReCaptchaWebapiGraphQl/Test/Api/ReCaptchaV3ConfigTest.php @@ -0,0 +1,153 @@ +encryptor = Bootstrap::getObjectManager()->get(EncryptorInterface::class); + $this->config = Bootstrap::getObjectManager()->get(\Magento\Config\Model\Config::class); + } + + #[ + Config('recaptcha_frontend/type_recaptcha_v3/score_threshold', 0.75), + Config('recaptcha_frontend/type_recaptcha_v3/position', 'bottomright'), + Config('recaptcha_frontend/type_recaptcha_v3/lang', 'en'), + Config('recaptcha_frontend/failure_messages/validation_failure_message', 'Test failure message'), + Config('recaptcha_frontend/type_for/customer_login', 'recaptcha_v3'), + ] + public function testQueryRecaptchaNoPublicKeyConfigured(): void + { + $this->assertEquals( + [ + 'recaptchaV3Config' => [ + 'is_enabled' => false, + 'website_key' => '', + 'minimum_score' => 0.75, + 'badge_position' => 'bottomright', + 'language_code' => 'en', + 'failure_message' => 'Test failure message', + 'forms' => [ + 'CUSTOMER_LOGIN' + ] + ] + ], + $this->graphQlQuery(self::QUERY) + ); + } + + #[ + Config('recaptcha_frontend/type_recaptcha_v3/score_threshold', 0.75), + Config('recaptcha_frontend/type_recaptcha_v3/position', 'bottomright'), + Config('recaptcha_frontend/type_recaptcha_v3/lang', 'en'), + Config('recaptcha_frontend/failure_messages/validation_failure_message', 'Test failure message'), + ] + public function testQueryRecaptchaNoFormsConfigured(): void + { + $this->config->setDataByPath( + 'recaptcha_frontend/type_recaptcha_v3/public_key', + $this->encryptor->encrypt('test_public_key') + ); + $this->config->setDataByPath( + 'recaptcha_frontend/type_recaptcha_v3/private_key', + $this->encryptor->encrypt('test_private_key') + ); + + $this->config->save(); + + $this->assertEquals( + [ + 'recaptchaV3Config' => [ + 'is_enabled' => false, + 'website_key' => 'test_public_key', + 'minimum_score' => 0.75, + 'badge_position' => 'bottomright', + 'language_code' => 'en', + 'failure_message' => 'Test failure message', + 'forms' => [] + ] + ], + $this->graphQlQuery(self::QUERY) + ); + } + + #[ + Config('recaptcha_frontend/type_recaptcha_v3/score_threshold', 0.75), + Config('recaptcha_frontend/type_recaptcha_v3/position', 'bottomright'), + Config('recaptcha_frontend/type_recaptcha_v3/lang', 'en'), + Config('recaptcha_frontend/failure_messages/validation_failure_message', 'Test failure message'), + Config('recaptcha_frontend/type_for/customer_login', 'recaptcha_v3'), + ] + public function testQueryRecaptchaConfigured(): void + { + $this->config->setDataByPath( + 'recaptcha_frontend/type_recaptcha_v3/public_key', + $this->encryptor->encrypt('test_public_key') + ); + $this->config->setDataByPath( + 'recaptcha_frontend/type_recaptcha_v3/private_key', + $this->encryptor->encrypt('test_private_key') + ); + + $this->config->save(); + + $this->assertEquals( + [ + 'recaptchaV3Config' => [ + 'is_enabled' => true, + 'website_key' => 'test_public_key', + 'minimum_score' => 0.75, + 'badge_position' => 'bottomright', + 'language_code' => 'en', + 'failure_message' => 'Test failure message', + 'forms' => [ + 'CUSTOMER_LOGIN' + ] + ] + ], + $this->graphQlQuery(self::QUERY) + ); + } + + public function tearDown(): void + { + $this->config->unsetData('recaptcha_frontend/type_recaptcha_v3/public_key'); + $this->config->unsetData('recaptcha_frontend/type_recaptcha_v3/private_key'); + $this->config->save(); + } +} diff --git a/ReCaptchaWebapiGraphQl/composer.json b/ReCaptchaWebapiGraphQl/composer.json index a93b5b80..67f04e53 100644 --- a/ReCaptchaWebapiGraphQl/composer.json +++ b/ReCaptchaWebapiGraphQl/composer.json @@ -5,6 +5,8 @@ "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-authorization": "*", + "magento/module-re-captcha-frontend-ui": "*", + "magento/module-re-captcha-version-3-invisible": "*", "magento/module-re-captcha-validation-api": "*", "magento/module-re-captcha-webapi-api": "*" }, diff --git a/ReCaptchaWebapiGraphQl/etc/schema.graphqls b/ReCaptchaWebapiGraphQl/etc/schema.graphqls new file mode 100644 index 00000000..63e152fa --- /dev/null +++ b/ReCaptchaWebapiGraphQl/etc/schema.graphqls @@ -0,0 +1,29 @@ +# Copyright © Magento, Inc. All rights reserved. +# See COPYING.txt for license details. + +type Query { + recaptchaV3Config: ReCaptchaConfigurationV3 @resolver(class: "Magento\\ReCaptchaWebapiGraphQl\\Model\\Resolver\\ReCaptchaV3") @doc(description: "Returns details about Google reCAPTCHA V3-Invisible configuration.") +} + +enum ReCaptchaFormEnum { + PLACE_ORDER + CONTACT + CUSTOMER_LOGIN + CUSTOMER_FORGOT_PASSWORD + CUSTOMER_CREATE + CUSTOMER_EDIT + NEWSLETTER + PRODUCT_REVIEW + SENDFRIEND + BRAINTREE +} + +type ReCaptchaConfigurationV3 @doc(description: "Contains reCAPTCHA V3-Invisible configuration details.") { + is_enabled: Boolean! @doc(description: "Return whether recaptcha is enabled or not") + website_key: String! @doc(description: "The website key generated when the Google reCAPTCHA account was registered.") + minimum_score: Float! @doc(description: "The minimum score that identifies a user interaction as a potential risk.") + badge_position: String! @doc(description: "The position of the invisible reCAPTCHA badge on each page.") + language_code: String @doc(description: "A two-character code that specifies the language that is used for Google reCAPTCHA text and messaging.") + failure_message: String! @doc(description: "The message that appears to the user if validation fails.") + forms: [ReCaptchaFormEnum!]! @doc(description: "A list of forms on the storefront that have been configured to use reCAPTCHA V3.") +} From 5ba2c8ef153d3579164e23cbd004943681ebb59b Mon Sep 17 00:00:00 2001 From: Dmytro Shevtsov Date: Wed, 15 Mar 2023 17:34:49 -0500 Subject: [PATCH 25/28] Fix Markdown formatting in docs --- README.md | 2 +- ReCaptchaCheckoutSalesRule/README.md | 2 +- ReCaptchaUser/README.md | 1 - Securitytxt/README.md | 9 ++++++--- _metapackage/README.md | 1 - 5 files changed, 8 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 48c6df95..1832a33c 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ Welcome to the Magento Security Package community project! ## Overview Magento security package provides a set of security-related features including two-factor authentication for admins, Google ReCAPTCHA support for various forms, and Security.txt to support vulnerability -disclosure practices. +disclosure practices. ## Documentation diff --git a/ReCaptchaCheckoutSalesRule/README.md b/ReCaptchaCheckoutSalesRule/README.md index 3898b6fd..469f5ec6 100644 --- a/ReCaptchaCheckoutSalesRule/README.md +++ b/ReCaptchaCheckoutSalesRule/README.md @@ -3,4 +3,4 @@ Google reCAPTCHA ensures that a human being, rather than a computer (or “bot This module provides the reCAPTCHA implementations related to coupon code apply action on checkout cart & payment. -For more information please visit the Magento document for reCAPTCHA. \ No newline at end of file +For more information please visit the Magento document for reCAPTCHA. diff --git a/ReCaptchaUser/README.md b/ReCaptchaUser/README.md index f3319e7b..5bf8739b 100644 --- a/ReCaptchaUser/README.md +++ b/ReCaptchaUser/README.md @@ -6,7 +6,6 @@ This module provides the reCAPTCHA implementations related to user actions. For more information please visit the [Magento document for reCAPTCHA](https://docs.magento.com/user-guide/stores/security-google-recaptcha.html). - ## Emergency commandline disable for Admin panel Login page: Can disable Google reCAPTCHA for Admin Panel Login page from command-line: diff --git a/Securitytxt/README.md b/Securitytxt/README.md index c4fd8c2d..9564eaf1 100644 --- a/Securitytxt/README.md +++ b/Securitytxt/README.md @@ -1,18 +1,20 @@ # Security.txt ### Summary +> > When security vulnerabilities are discovered by researchers, proper reporting channels are often lacking. As a result, vulnerabilities may be left unreported. This document defines a format ("security.txt") to help organizations describe their vulnerability disclosure practices to make it easier for researchers to report vulnerabilities. Source: https://tools.ietf.org/html/draft-foudil-securitytxt-09 -The Magento_Securitytxt module provides the following functionality: +The Magento_Securitytxt module provides the following functionality: + * allows to save the security configurations in the admin panel * contains a router to match application action class for requests to the `.well-known/security.txt` and `.well-known/security.txt.sig` files. * serves the content of the `.well-known/security.txt` and `.well-known/security.txt.sig` files. A valid security.txt file could look like the following example: -``` +```txt Contact: mailto:security@example.com Contact: tel:+1-201-555-0123 Encryption: https://example.com/pgp.asc @@ -20,6 +22,7 @@ Acknowledgement: https://example.com/security/hall-of-fame Policy: https://example.com/security-policy.html Signature: https://example.com/.well-known/security.txt.sig ``` + Security.txt can be accessed at below location: `https://example.com/.well-known/security.txt` @@ -29,4 +32,4 @@ To create security.txt signature (security.txt.sig) file: To verify the security.txt file's signature: -`gpg --verify security.txt.sig security.txt` \ No newline at end of file +`gpg --verify security.txt.sig security.txt` diff --git a/_metapackage/README.md b/_metapackage/README.md index 23b0bbfe..0b4fb3bd 100644 --- a/_metapackage/README.md +++ b/_metapackage/README.md @@ -1,2 +1 @@ # Magento_SecurityPackage - From 41060e9f72b9ec94ff5e051682b84a91f78b896f Mon Sep 17 00:00:00 2001 From: Andrii Dimov Date: Mon, 29 May 2023 14:23:04 -0500 Subject: [PATCH 26/28] ACPT-1344: Fix ReCaptcha WebApi GraphQL tests on Application Server --- ReCaptchaVersion3Invisible/Model/Config.php | 12 +++++++++++- .../Model/Resolver/ReCaptchaV3.php | 11 ++++++++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/ReCaptchaVersion3Invisible/Model/Config.php b/ReCaptchaVersion3Invisible/Model/Config.php index a394f5b1..07285a6b 100644 --- a/ReCaptchaVersion3Invisible/Model/Config.php +++ b/ReCaptchaVersion3Invisible/Model/Config.php @@ -8,11 +8,12 @@ namespace Magento\ReCaptchaVersion3Invisible\Model; +use Magento\Framework\ObjectManager\ResetAfterRequestInterface; use Magento\ReCaptchaValidationApi\Api\Data\ValidationConfigInterface; use Magento\ReCaptchaVersion3Invisible\Model\Frontend\UiConfigProvider; use Magento\ReCaptchaVersion3Invisible\Model\Frontend\ValidationConfigProvider; -class Config +class Config implements ResetAfterRequestInterface { /** * @var string|null @@ -161,4 +162,13 @@ private function getUiConfig(): array } return $this->uiConfig; } + + /** + * @inheritDoc + */ + public function _resetState(): void + { + $this->websiteKey = null; + $this->uiConfig = []; + } } diff --git a/ReCaptchaWebapiGraphQl/Model/Resolver/ReCaptchaV3.php b/ReCaptchaWebapiGraphQl/Model/Resolver/ReCaptchaV3.php index 5abc02bc..a014f0fc 100644 --- a/ReCaptchaWebapiGraphQl/Model/Resolver/ReCaptchaV3.php +++ b/ReCaptchaWebapiGraphQl/Model/Resolver/ReCaptchaV3.php @@ -12,11 +12,12 @@ use Magento\Framework\GraphQl\Config\Element\Field; use Magento\Framework\GraphQl\Query\ResolverInterface; use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; +use Magento\Framework\ObjectManager\ResetAfterRequestInterface; use Magento\ReCaptchaFrontendUi\Model\CaptchaTypeResolver; use Magento\ReCaptchaFrontendUi\Model\ErrorMessageConfig; use Magento\ReCaptchaVersion3Invisible\Model\Config; -class ReCaptchaV3 implements ResolverInterface +class ReCaptchaV3 implements ResolverInterface, ResetAfterRequestInterface { private const RECAPTCHA_TYPE = 'recaptcha_v3'; @@ -128,4 +129,12 @@ public function getFailureMessage(): string } return $this->failureMessage; } + + /** + * @inheritDoc + */ + public function _resetState(): void + { + $this->isEnabled = null; + } } From bd8ef8ec3162aeb5c4150177774084c5479052e7 Mon Sep 17 00:00:00 2001 From: Andrii Dimov Date: Mon, 29 May 2023 16:26:11 -0500 Subject: [PATCH 27/28] ACPT-1344: Fix ReCaptcha WebApi GraphQL tests on Application Server --- ReCaptchaVersion3Invisible/Model/Config.php | 1 + 1 file changed, 1 insertion(+) diff --git a/ReCaptchaVersion3Invisible/Model/Config.php b/ReCaptchaVersion3Invisible/Model/Config.php index 07285a6b..71b99327 100644 --- a/ReCaptchaVersion3Invisible/Model/Config.php +++ b/ReCaptchaVersion3Invisible/Model/Config.php @@ -170,5 +170,6 @@ public function _resetState(): void { $this->websiteKey = null; $this->uiConfig = []; + $this->validationConfig = null; } } From cceea8e28d7e5bc601618156a2e95856b56f36e7 Mon Sep 17 00:00:00 2001 From: Jacob Brown Date: Thu, 8 Jun 2023 15:54:14 -0500 Subject: [PATCH 28/28] ACPT-1360 Fixing resetState methods so that the object's state is the same as it was when first constructed --- ReCaptchaVersion3Invisible/Model/Config.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ReCaptchaVersion3Invisible/Model/Config.php b/ReCaptchaVersion3Invisible/Model/Config.php index 71b99327..8a517506 100644 --- a/ReCaptchaVersion3Invisible/Model/Config.php +++ b/ReCaptchaVersion3Invisible/Model/Config.php @@ -43,7 +43,7 @@ class Config implements ResetAfterRequestInterface /** * @var array */ - private array $uiConfig; + private array $uiConfig = []; /** * @var ValidationConfigProvider