From 449cb62a71848fe7e44ae77fe2f0f097751232db Mon Sep 17 00:00:00 2001 From: Yegor Shytikov Date: Wed, 20 Oct 2021 09:17:05 -0700 Subject: [PATCH] Magento OS Fork commit --- Api/CaptchaConfigPostProcessorInterface.php | 22 + Block/Adminhtml/Captcha/DefaultCaptcha.php | 55 ++ Block/Captcha.php | 54 ++ Block/Captcha/DefaultCaptcha.php | 86 +++ Controller/Adminhtml/Refresh/Refresh.php | 66 ++ Controller/Refresh/Index.php | 112 ++++ Cron/DeleteExpiredImages.php | 95 +++ Cron/DeleteOldAttempts.php | 38 ++ CustomerData/Captcha.php | 81 +++ Helper/Adminhtml/Data.php | 61 ++ Helper/Data.php | 185 ++++++ LICENSE.txt | 48 ++ LICENSE_AFL.txt | 48 ++ Model/CaptchaFactory.php | 45 ++ Model/CaptchaInterface.php | 40 ++ Model/Cart/ConfigPlugin.php | 35 ++ Model/Checkout/ConfigProvider.php | 132 ++++ Model/Config/Font.php | 44 ++ Model/Config/Form/AbstractForm.php | 41 ++ Model/Config/Form/Backend.php | 20 + Model/Config/Form/Frontend.php | 20 + Model/Config/Mode.php | 31 + Model/Customer/Plugin/AjaxLogin.php | 125 ++++ Model/DefaultModel.php | 592 ++++++++++++++++++ .../CaptchaConfigPostProcessorComposite.php | 45 ++ Model/Filter/QuoteDataConfigFilter.php | 47 ++ Model/ResourceModel/Log.php | 192 ++++++ Observer/CaptchaStringResolver.php | 40 ++ Observer/CheckContactUsFormObserver.php | 99 +++ Observer/CheckForgotpasswordObserver.php | 83 +++ Observer/CheckUserCreateObserver.php | 104 +++ Observer/CheckUserEditObserver.php | 136 ++++ ...CheckUserForgotPasswordBackendObserver.php | 109 ++++ Observer/CheckUserLoginBackendObserver.php | 65 ++ Observer/CheckUserLoginObserver.php | 163 +++++ Observer/ResetAttemptForBackendObserver.php | 44 ++ ...tAttemptForFrontendAccountEditObserver.php | 55 ++ Observer/ResetAttemptForFrontendObserver.php | 48 ++ README.md | 1 + .../AdminLoginWithCaptchaActionGroup.xml | 21 + ...tVisibleOnCustomerLoginFormActionGroup.xml | 26 + ...tchaVisibleOnAdminLoginFormActionGroup.xml | 25 + ...ptchaVisibleOnContactUsFormActionGroup.xml | 25 + ...OnCustomerAccountCreatePageActionGroup.xml | 20 + ...isibleOnCustomerAccountInfoActionGroup.xml | 27 + ...aVisibleOnCustomerLoginFormActionGroup.xml | 25 + .../CaptchaFormsDisplayingActionGroup.xml | 27 + ...tomerChangeEmailWithCaptchaActionGroup.xml | 21 + ...illContactUsFormWithCaptchaActionGroup.xml | 21 + ...ountCreationFormWithCaptchaActionGroup.xml | 21 + ...ustomerLoginFormWithCaptchaActionGroup.xml | 21 + Test/Mftf/Data/CaptchaConfigData.xml | 142 +++++ Test/Mftf/Data/CaptchaData.xml | 19 + Test/Mftf/Data/CaptchaFormsDisplayingData.xml | 20 + Test/Mftf/LICENSE.txt | 48 ++ Test/Mftf/LICENSE_AFL.txt | 48 ++ Test/Mftf/README.md | 3 + Test/Mftf/Section/AdminLoginFormSection.xml | 16 + .../Section/CaptchaFormsDisplayingSection.xml | 27 + .../StorefrontContactUsCaptchaSection.xml | 16 + .../StorefrontContactUsFormSection.xml | 16 + ...frontCustomerAccountInformationSection.xml | 16 + .../StorefrontCustomerCreateFormSection.xml | 16 + .../StorefrontCustomerSignInFormSection.xml | 16 + ...orefrontCustomerSignInPopupFormSection.xml | 16 + Test/Mftf/Test/AdminLoginWithCaptchaTest.xml | 77 +++ .../Test/AdminResetUserPasswordFailedTest.xml | 19 + .../CaptchaFormsDisplayingTest.xml | 67 ++ .../CaptchaWithDisabledGuestCheckoutTest.xml | 60 ++ ...StorefrontCaptchaEditCustomerEmailTest.xml | 106 ++++ .../Test/StorefrontCaptchaOnContactUsTest.xml | 68 ++ .../StorefrontCaptchaOnCustomerLoginTest.xml | 83 +++ ...orefrontCaptchaRegisterNewCustomerTest.xml | 72 +++ ...refrontResetCustomerPasswordFailedTest.xml | 19 + Test/Unit/Controller/Refresh/IndexTest.php | 153 +++++ Test/Unit/Cron/DeleteExpiredImagesTest.php | 171 +++++ Test/Unit/CustomerData/CaptchaTest.php | 99 +++ Test/Unit/Helper/Adminhtml/DataTest.php | 64 ++ Test/Unit/Helper/DataTest.php | 230 +++++++ Test/Unit/Model/CaptchaFactoryTest.php | 71 +++ Test/Unit/Model/Cart/ConfigPluginTest.php | 54 ++ .../Model/Checkout/ConfigProviderTest.php | 132 ++++ Test/Unit/Model/Config/FontTest.php | 102 +++ Test/Unit/Model/Config/Form/BackendTest.php | 101 +++ Test/Unit/Model/Config/Form/FrontendTest.php | 101 +++ .../Model/Customer/Plugin/AjaxLoginTest.php | 244 ++++++++ Test/Unit/Model/DefaultTest.php | 460 ++++++++++++++ ...aptchaConfigPostProcessorCompositeTest.php | 87 +++ .../Filter/QuoteDataConfigFilterTest.php | 70 +++ .../Observer/CaptchaStringResolverTest.php | 71 +++ .../CheckContactUsFormObserverTest.php | 207 ++++++ .../CheckForgotpasswordObserverTest.php | 172 +++++ .../Observer/CheckUserCreateObserverTest.php | 187 ++++++ .../Observer/CheckUserEditObserverTest.php | 181 ++++++ ...kUserForgotPasswordBackendObserverTest.php | 231 +++++++ .../CheckUserLoginBackendObserverTest.php | 140 +++++ .../Observer/CheckUserLoginObserverTest.php | 187 ++++++ .../ResetAttemptForBackendObserverTest.php | 56 ++ .../ResetAttemptForFrontendObserverTest.php | 55 ++ composer.json | 36 ++ etc/adminhtml/di.xml | 31 + etc/adminhtml/events.xml | 15 + etc/adminhtml/routes.xml | 14 + etc/adminhtml/system.xml | 154 +++++ etc/config.xml | 98 +++ etc/crontab.xml | 17 + etc/crontab/di.xml | 10 + etc/db_schema.xml | 21 + etc/db_schema_whitelist.json | 13 + etc/di.xml | 58 ++ etc/events.xml | 30 + etc/frontend/di.xml | 37 ++ etc/frontend/events.xml | 15 + etc/frontend/routes.xml | 14 + etc/frontend/sections.xml | 13 + etc/module.xml | 15 + i18n/en_US.csv | 27 + registration.php | 9 + .../layout/adminhtml_auth_forgotpassword.xml | 24 + .../adminhtml/layout/adminhtml_auth_login.xml | 24 + view/adminhtml/templates/default.phtml | 67 ++ view/adminhtml/web/reload.png | Bin 0 -> 1304 bytes view/frontend/layout/checkout_index_index.xml | 59 ++ view/frontend/layout/contact_index_index.xml | 27 + .../layout/customer_account_create.xml | 27 + .../frontend/layout/customer_account_edit.xml | 27 + .../customer_account_forgotpassword.xml | 27 + .../layout/customer_account_login.xml | 27 + view/frontend/layout/default.xml | 29 + view/frontend/requirejs-config.js | 13 + view/frontend/templates/default.phtml | 54 ++ view/frontend/templates/js/components.phtml | 7 + view/frontend/web/js/action/refresh.js | 26 + view/frontend/web/js/captcha.js | 70 +++ view/frontend/web/js/model/captcha.js | 155 +++++ view/frontend/web/js/model/captchaList.js | 44 ++ .../web/js/view/checkout/defaultCaptcha.js | 168 +++++ .../web/js/view/checkout/loginCaptcha.js | 39 ++ .../web/template/checkout/captcha.html | 34 + 139 files changed, 9778 insertions(+) create mode 100644 Api/CaptchaConfigPostProcessorInterface.php create mode 100644 Block/Adminhtml/Captcha/DefaultCaptcha.php create mode 100644 Block/Captcha.php create mode 100644 Block/Captcha/DefaultCaptcha.php create mode 100644 Controller/Adminhtml/Refresh/Refresh.php create mode 100644 Controller/Refresh/Index.php create mode 100644 Cron/DeleteExpiredImages.php create mode 100644 Cron/DeleteOldAttempts.php create mode 100644 CustomerData/Captcha.php create mode 100644 Helper/Adminhtml/Data.php create mode 100644 Helper/Data.php create mode 100644 LICENSE.txt create mode 100644 LICENSE_AFL.txt create mode 100644 Model/CaptchaFactory.php create mode 100644 Model/CaptchaInterface.php create mode 100644 Model/Cart/ConfigPlugin.php create mode 100644 Model/Checkout/ConfigProvider.php create mode 100644 Model/Config/Font.php create mode 100644 Model/Config/Form/AbstractForm.php create mode 100644 Model/Config/Form/Backend.php create mode 100644 Model/Config/Form/Frontend.php create mode 100644 Model/Config/Mode.php create mode 100644 Model/Customer/Plugin/AjaxLogin.php create mode 100644 Model/DefaultModel.php create mode 100644 Model/Filter/CaptchaConfigPostProcessorComposite.php create mode 100644 Model/Filter/QuoteDataConfigFilter.php create mode 100644 Model/ResourceModel/Log.php create mode 100644 Observer/CaptchaStringResolver.php create mode 100644 Observer/CheckContactUsFormObserver.php create mode 100644 Observer/CheckForgotpasswordObserver.php create mode 100644 Observer/CheckUserCreateObserver.php create mode 100644 Observer/CheckUserEditObserver.php create mode 100644 Observer/CheckUserForgotPasswordBackendObserver.php create mode 100644 Observer/CheckUserLoginBackendObserver.php create mode 100644 Observer/CheckUserLoginObserver.php create mode 100644 Observer/ResetAttemptForBackendObserver.php create mode 100644 Observer/ResetAttemptForFrontendAccountEditObserver.php create mode 100644 Observer/ResetAttemptForFrontendObserver.php create mode 100644 README.md create mode 100644 Test/Mftf/ActionGroup/AdminLoginWithCaptchaActionGroup.xml create mode 100644 Test/Mftf/ActionGroup/AssertCaptchaNotVisibleOnCustomerLoginFormActionGroup.xml create mode 100644 Test/Mftf/ActionGroup/AssertCaptchaVisibleOnAdminLoginFormActionGroup.xml create mode 100644 Test/Mftf/ActionGroup/AssertCaptchaVisibleOnContactUsFormActionGroup.xml create mode 100644 Test/Mftf/ActionGroup/AssertCaptchaVisibleOnCustomerAccountCreatePageActionGroup.xml create mode 100644 Test/Mftf/ActionGroup/AssertCaptchaVisibleOnCustomerAccountInfoActionGroup.xml create mode 100644 Test/Mftf/ActionGroup/AssertCaptchaVisibleOnCustomerLoginFormActionGroup.xml create mode 100644 Test/Mftf/ActionGroup/CaptchaFormsDisplayingActionGroup.xml create mode 100644 Test/Mftf/ActionGroup/StorefrontCustomerChangeEmailWithCaptchaActionGroup.xml create mode 100644 Test/Mftf/ActionGroup/StorefrontFillContactUsFormWithCaptchaActionGroup.xml create mode 100644 Test/Mftf/ActionGroup/StorefrontFillCustomerAccountCreationFormWithCaptchaActionGroup.xml create mode 100644 Test/Mftf/ActionGroup/StorefrontFillCustomerLoginFormWithCaptchaActionGroup.xml create mode 100644 Test/Mftf/Data/CaptchaConfigData.xml create mode 100644 Test/Mftf/Data/CaptchaData.xml create mode 100644 Test/Mftf/Data/CaptchaFormsDisplayingData.xml create mode 100644 Test/Mftf/LICENSE.txt create mode 100644 Test/Mftf/LICENSE_AFL.txt create mode 100644 Test/Mftf/README.md create mode 100644 Test/Mftf/Section/AdminLoginFormSection.xml create mode 100644 Test/Mftf/Section/CaptchaFormsDisplayingSection.xml create mode 100644 Test/Mftf/Section/StorefrontContactUsCaptchaSection.xml create mode 100644 Test/Mftf/Section/StorefrontContactUsFormSection.xml create mode 100644 Test/Mftf/Section/StorefrontCustomerAccountInformationSection.xml create mode 100644 Test/Mftf/Section/StorefrontCustomerCreateFormSection.xml create mode 100644 Test/Mftf/Section/StorefrontCustomerSignInFormSection.xml create mode 100644 Test/Mftf/Section/StorefrontCustomerSignInPopupFormSection.xml create mode 100644 Test/Mftf/Test/AdminLoginWithCaptchaTest.xml create mode 100644 Test/Mftf/Test/AdminResetUserPasswordFailedTest.xml create mode 100644 Test/Mftf/Test/CaptchaFormsDisplayingTest/CaptchaFormsDisplayingTest.xml create mode 100644 Test/Mftf/Test/CaptchaFormsDisplayingTest/CaptchaWithDisabledGuestCheckoutTest.xml create mode 100644 Test/Mftf/Test/StorefrontCaptchaEditCustomerEmailTest.xml create mode 100644 Test/Mftf/Test/StorefrontCaptchaOnContactUsTest.xml create mode 100644 Test/Mftf/Test/StorefrontCaptchaOnCustomerLoginTest.xml create mode 100644 Test/Mftf/Test/StorefrontCaptchaRegisterNewCustomerTest.xml create mode 100644 Test/Mftf/Test/StorefrontResetCustomerPasswordFailedTest.xml create mode 100644 Test/Unit/Controller/Refresh/IndexTest.php create mode 100644 Test/Unit/Cron/DeleteExpiredImagesTest.php create mode 100644 Test/Unit/CustomerData/CaptchaTest.php create mode 100644 Test/Unit/Helper/Adminhtml/DataTest.php create mode 100644 Test/Unit/Helper/DataTest.php create mode 100644 Test/Unit/Model/CaptchaFactoryTest.php create mode 100644 Test/Unit/Model/Cart/ConfigPluginTest.php create mode 100644 Test/Unit/Model/Checkout/ConfigProviderTest.php create mode 100644 Test/Unit/Model/Config/FontTest.php create mode 100644 Test/Unit/Model/Config/Form/BackendTest.php create mode 100644 Test/Unit/Model/Config/Form/FrontendTest.php create mode 100644 Test/Unit/Model/Customer/Plugin/AjaxLoginTest.php create mode 100644 Test/Unit/Model/DefaultTest.php create mode 100644 Test/Unit/Model/Filter/CaptchaConfigPostProcessorCompositeTest.php create mode 100644 Test/Unit/Model/Filter/QuoteDataConfigFilterTest.php create mode 100644 Test/Unit/Observer/CaptchaStringResolverTest.php create mode 100644 Test/Unit/Observer/CheckContactUsFormObserverTest.php create mode 100644 Test/Unit/Observer/CheckForgotpasswordObserverTest.php create mode 100644 Test/Unit/Observer/CheckUserCreateObserverTest.php create mode 100644 Test/Unit/Observer/CheckUserEditObserverTest.php create mode 100644 Test/Unit/Observer/CheckUserForgotPasswordBackendObserverTest.php create mode 100644 Test/Unit/Observer/CheckUserLoginBackendObserverTest.php create mode 100644 Test/Unit/Observer/CheckUserLoginObserverTest.php create mode 100644 Test/Unit/Observer/ResetAttemptForBackendObserverTest.php create mode 100644 Test/Unit/Observer/ResetAttemptForFrontendObserverTest.php create mode 100644 composer.json create mode 100644 etc/adminhtml/di.xml create mode 100644 etc/adminhtml/events.xml create mode 100644 etc/adminhtml/routes.xml create mode 100644 etc/adminhtml/system.xml create mode 100644 etc/config.xml create mode 100644 etc/crontab.xml create mode 100644 etc/crontab/di.xml create mode 100644 etc/db_schema.xml create mode 100644 etc/db_schema_whitelist.json create mode 100644 etc/di.xml create mode 100644 etc/events.xml create mode 100644 etc/frontend/di.xml create mode 100644 etc/frontend/events.xml create mode 100644 etc/frontend/routes.xml create mode 100644 etc/frontend/sections.xml create mode 100644 etc/module.xml create mode 100644 i18n/en_US.csv create mode 100644 registration.php create mode 100644 view/adminhtml/layout/adminhtml_auth_forgotpassword.xml create mode 100644 view/adminhtml/layout/adminhtml_auth_login.xml create mode 100644 view/adminhtml/templates/default.phtml create mode 100644 view/adminhtml/web/reload.png create mode 100644 view/frontend/layout/checkout_index_index.xml create mode 100644 view/frontend/layout/contact_index_index.xml create mode 100644 view/frontend/layout/customer_account_create.xml create mode 100644 view/frontend/layout/customer_account_edit.xml create mode 100644 view/frontend/layout/customer_account_forgotpassword.xml create mode 100644 view/frontend/layout/customer_account_login.xml create mode 100644 view/frontend/layout/default.xml create mode 100644 view/frontend/requirejs-config.js create mode 100644 view/frontend/templates/default.phtml create mode 100644 view/frontend/templates/js/components.phtml create mode 100644 view/frontend/web/js/action/refresh.js create mode 100644 view/frontend/web/js/captcha.js create mode 100644 view/frontend/web/js/model/captcha.js create mode 100644 view/frontend/web/js/model/captchaList.js create mode 100644 view/frontend/web/js/view/checkout/defaultCaptcha.js create mode 100644 view/frontend/web/js/view/checkout/loginCaptcha.js create mode 100644 view/frontend/web/template/checkout/captcha.html diff --git a/Api/CaptchaConfigPostProcessorInterface.php b/Api/CaptchaConfigPostProcessorInterface.php new file mode 100644 index 0000000..f4d88d4 --- /dev/null +++ b/Api/CaptchaConfigPostProcessorInterface.php @@ -0,0 +1,22 @@ +_url = $url; + $this->_config = $config; + } + + /** + * Returns URL to controller action which returns new captcha image + * + * @return string + */ + public function getRefreshUrl() + { + return $this->_url->getUrl( + 'adminhtml/refresh/refresh', + ['_secure' => $this->_config->isSetFlag('web/secure/use_in_adminhtml'), '_nosecret' => true] + ); + } +} diff --git a/Block/Captcha.php b/Block/Captcha.php new file mode 100644 index 0000000..3bb7ec9 --- /dev/null +++ b/Block/Captcha.php @@ -0,0 +1,54 @@ + + */ +namespace Magento\Captcha\Block; + +/** + * @api + * @since 100.0.2 + */ +class Captcha extends \Magento\Framework\View\Element\Template +{ + /** + * Captcha data + * + * @var \Magento\Captcha\Helper\Data + */ + protected $_captchaData = null; + + /** + * @param \Magento\Framework\View\Element\Template\Context $context + * @param \Magento\Captcha\Helper\Data $captchaData + * @param array $data + */ + public function __construct( + \Magento\Framework\View\Element\Template\Context $context, + \Magento\Captcha\Helper\Data $captchaData, + array $data = [] + ) { + $this->_captchaData = $captchaData; + parent::__construct($context, $data); + $this->_isScopePrivate = true; + } + + /** + * Renders captcha HTML (if required) + * + * @return string + */ + protected function _toHtml() + { + $blockPath = $this->_captchaData->getCaptcha($this->getFormId())->getBlockName(); + $block = $this->getLayout()->createBlock($blockPath); + $block->setData($this->getData()); + return $block->toHtml(); + } +} diff --git a/Block/Captcha/DefaultCaptcha.php b/Block/Captcha/DefaultCaptcha.php new file mode 100644 index 0000000..027c9a9 --- /dev/null +++ b/Block/Captcha/DefaultCaptcha.php @@ -0,0 +1,86 @@ +_captchaData = $captchaData; + } + + /** + * Returns template path + * + * @return string + */ + public function getTemplate() + { + return $this->getIsAjax() ? '' : $this->_template; + } + + /** + * Returns URL to controller action which returns new captcha image + * + * @return string + */ + public function getRefreshUrl() + { + $store = $this->_storeManager->getStore(); + return $store->getUrl('captcha/refresh', ['_secure' => $store->isCurrentlySecure()]); + } + + /** + * Renders captcha HTML (if required) + * + * @return string + */ + protected function _toHtml() + { + if ($this->getCaptchaModel()->isRequired()) { + $this->getCaptchaModel()->generate(); + return parent::_toHtml(); + } + return ''; + } + + /** + * Returns captcha model + * + * @return \Magento\Captcha\Model\CaptchaInterface + */ + public function getCaptchaModel() + { + return $this->_captchaData->getCaptcha($this->getFormId()); + } +} diff --git a/Controller/Adminhtml/Refresh/Refresh.php b/Controller/Adminhtml/Refresh/Refresh.php new file mode 100644 index 0000000..cadcbdb --- /dev/null +++ b/Controller/Adminhtml/Refresh/Refresh.php @@ -0,0 +1,66 @@ +serializer = $serializer; + $this->captchaHelper = $captchaHelper; + } + + /** + * {@inheritdoc} + */ + public function execute() + { + $formId = $this->getRequest()->getPost('formId'); + $captchaModel = $this->captchaHelper->getCaptcha($formId); + $this->_view->getLayout()->createBlock( + $captchaModel->getBlockName() + )->setFormId( + $formId + )->setIsAjax( + true + )->toHtml(); + $this->getResponse()->representJson($this->serializer->serialize(['imgSrc' => $captchaModel->getImgSrc()])); + $this->_actionFlag->set('', self::FLAG_NO_POST_DISPATCH, true); + } + + /** + * Check if user has permissions to access this controller + * + * @return bool + */ + protected function _isAllowed() + { + return true; + } +} diff --git a/Controller/Refresh/Index.php b/Controller/Refresh/Index.php new file mode 100644 index 0000000..4c205af --- /dev/null +++ b/Controller/Refresh/Index.php @@ -0,0 +1,112 @@ +request = $request; + $this->jsonResultFactory = $jsonFactory; + $this->captchaHelper = $captchaHelper; + $this->layout = $layout; + $this->serializer = $serializer; + } + + /** + * @inheritdoc + */ + public function execute() + { + $formId = $this->getRequestFormId(); + + $captchaModel = $this->captchaHelper->getCaptcha($formId); + $captchaModel->generate(); + + $block = $this->layout->createBlock($captchaModel->getBlockName()); + $block->setFormId($formId)->setIsAjax(true)->toHtml(); + + $result = $this->jsonResultFactory->create(); + + return $result->setData(['imgSrc' => $captchaModel->getImgSrc()]); + } + + /** + * Returns requested Form ID + * + * @return string|null + */ + private function getRequestFormId(): ?string + { + $formId = $this->request->getPost('formId'); + if (null === $formId) { + $params = []; + $content = $this->request->getContent(); + if ($content) { + $params = $this->serializer->unserialize($content); + } + + $formId = $params['formId'] ?? null; + } + + return $formId !== null ? (string)$formId : null; + } +} diff --git a/Cron/DeleteExpiredImages.php b/Cron/DeleteExpiredImages.php new file mode 100644 index 0000000..ffe46aa --- /dev/null +++ b/Cron/DeleteExpiredImages.php @@ -0,0 +1,95 @@ +_helper = $helper; + $this->_adminHelper = $adminHelper; + $this->_mediaDirectory = $filesystem->getDirectoryWrite(DirectoryList::MEDIA, DriverPool::FILE); + $this->_storeManager = $storeManager; + } + + /** + * Delete Expired Captcha Images + * + * @return \Magento\Captcha\Cron\DeleteExpiredImages + */ + public function execute() + { + foreach ($this->_storeManager->getWebsites() as $website) { + $this->_deleteExpiredImagesForWebsite($this->_helper, $website, $website->getDefaultStore()); + } + $this->_deleteExpiredImagesForWebsite($this->_adminHelper); + + return $this; + } + + /** + * Delete Expired Captcha Images for specific website + * + * @param \Magento\Captcha\Helper\Data $helper + * @param \Magento\Store\Model\Website|null $website + * @param \Magento\Store\Model\Store|null $store + * @return void + */ + protected function _deleteExpiredImagesForWebsite( + \Magento\Captcha\Helper\Data $helper, + \Magento\Store\Model\Website $website = null, + \Magento\Store\Model\Store $store = null + ) { + $expire = time() - $helper->getConfig('timeout', $store) * 60; + $imageDirectory = $this->_mediaDirectory->getRelativePath($helper->getImgDir($website)); + foreach ($this->_mediaDirectory->read($imageDirectory) as $filePath) { + if ($this->_mediaDirectory->isFile($filePath) + && pathinfo($filePath, PATHINFO_EXTENSION) == 'png' + && $this->_mediaDirectory->stat($filePath)['mtime'] < $expire + ) { + $this->_mediaDirectory->delete($filePath); + } + } + } +} diff --git a/Cron/DeleteOldAttempts.php b/Cron/DeleteOldAttempts.php new file mode 100644 index 0000000..0e63512 --- /dev/null +++ b/Cron/DeleteOldAttempts.php @@ -0,0 +1,38 @@ +resLogFactory = $resLogFactory; + } + + /** + * Delete Unnecessary logged attempts + * + * @return \Magento\Captcha\Cron\DeleteOldAttempts + */ + public function execute() + { + $this->resLogFactory->create()->deleteOldAttempts(); + + return $this; + } +} diff --git a/CustomerData/Captcha.php b/CustomerData/Captcha.php new file mode 100644 index 0000000..901477c --- /dev/null +++ b/CustomerData/Captcha.php @@ -0,0 +1,81 @@ +helper = $helper; + $this->formIds = $formIds; + parent::__construct($data); + $this->customerSession = $customerSession ?? ObjectManager::getInstance()->get(CustomerSession::class); + } + + /** + * @inheritdoc + */ + public function getSectionData(): array + { + $data = []; + + foreach ($this->formIds as $formId) { + /** @var DefaultModel $captchaModel */ + $captchaModel = $this->helper->getCaptcha($formId); + $login = ''; + if ($this->customerSession->isLoggedIn()) { + $login = $this->customerSession->getCustomerData()->getEmail(); + } + $required = $captchaModel->isRequired($login); + $data[$formId] = [ + 'isRequired' => $required, + 'timestamp' => time() + ]; + } + + return $data; + } +} diff --git a/Helper/Adminhtml/Data.php b/Helper/Adminhtml/Data.php new file mode 100644 index 0000000..adf8b7d --- /dev/null +++ b/Helper/Adminhtml/Data.php @@ -0,0 +1,61 @@ + + */ +namespace Magento\Captcha\Helper\Adminhtml; + +class Data extends \Magento\Captcha\Helper\Data +{ + /** + * @var \Magento\Backend\App\ConfigInterface + */ + protected $_backendConfig; + + /** + * @param \Magento\Framework\App\Helper\Context $context + * @param \Magento\Store\Model\StoreManager $storeManager + * @param \Magento\Framework\Filesystem $filesystem + * @param \Magento\Captcha\Model\CaptchaFactory $factory + * @param \Magento\Backend\App\ConfigInterface $backendConfig + */ + public function __construct( + \Magento\Framework\App\Helper\Context $context, + \Magento\Store\Model\StoreManager $storeManager, + \Magento\Framework\Filesystem $filesystem, + \Magento\Captcha\Model\CaptchaFactory $factory, + \Magento\Backend\App\ConfigInterface $backendConfig + ) { + $this->_backendConfig = $backendConfig; + parent::__construct($context, $storeManager, $filesystem, $factory); + } + + /** + * Returns config value for admin captcha + * + * @param string $key The last part of XML_PATH_$area_CAPTCHA_ constant (case insensitive) + * @param \Magento\Store\Model\Store $store + * @return \Magento\Framework\App\Config\Element + */ + public function getConfig($key, $store = null) + { + return $this->_backendConfig->getValue('admin/captcha/' . $key); + } + + /** + * Get website code + * + * @param mixed $website + * @return string + */ + protected function _getWebsiteCode($website = null) + { + return 'admin'; + } +} diff --git a/Helper/Data.php b/Helper/Data.php new file mode 100644 index 0000000..8a9131a --- /dev/null +++ b/Helper/Data.php @@ -0,0 +1,185 @@ +_storeManager = $storeManager; + $this->_filesystem = $filesystem; + $this->_factory = $factory; + parent::__construct($context); + } + + /** + * Get Captcha + * + * @param string $formId + * @return \Magento\Captcha\Model\CaptchaInterface + */ + public function getCaptcha($formId) + { + if (!array_key_exists($formId, $this->_captcha)) { + $captchaType = ucfirst($this->getConfig('type')); + if (!$captchaType) { + $captchaType = self::DEFAULT_CAPTCHA_TYPE; + } elseif ($captchaType == 'Default') { + $captchaType = $captchaType . 'Model'; + } + + $this->_captcha[$formId] = $this->_factory->create($captchaType, $formId); + } + return $this->_captcha[$formId]; + } + + /** + * Returns config value + * + * @param string $key The last part of XML_PATH_$area_CAPTCHA_ constant (case insensitive) + * @param \Magento\Store\Model\Store $store + * @return \Magento\Framework\App\Config\Element + */ + public function getConfig($key, $store = null) + { + return $this->scopeConfig->getValue( + 'customer/captcha/' . $key, + \Magento\Store\Model\ScopeInterface::SCOPE_STORE, + $store + ); + } + + /** + * Get list of available fonts. + * + * Return format: + * [['arial'] => ['label' => 'Arial', 'path' => '/www/magento/fonts/arial.ttf']] + * + * @return array + */ + public function getFonts() + { + $fontsConfig = $this->scopeConfig->getValue(\Magento\Captcha\Helper\Data::XML_PATH_CAPTCHA_FONTS, 'default'); + $fonts = []; + if ($fontsConfig) { + $libDir = $this->_filesystem->getDirectoryRead(DirectoryList::LIB_INTERNAL); + foreach ($fontsConfig as $fontName => $fontConfig) { + $fonts[$fontName] = [ + 'label' => $fontConfig['label'], + 'path' => $libDir->getAbsolutePath($fontConfig['path']), + ]; + } + } + return $fonts; + } + + /** + * Get captcha image directory + * + * @param mixed $website + * @return string + */ + public function getImgDir($website = null) + { + // Captcha images are not re-used and should be stored only locally. + $mediaDir = $this->_filesystem->getDirectoryWrite(DirectoryList::MEDIA, Filesystem\DriverPool::FILE); + $captchaDir = '/captcha/' . $this->_getWebsiteCode($website); + $mediaDir->create($captchaDir); + return $mediaDir->getAbsolutePath($captchaDir) . '/'; + } + + /** + * Get website code + * + * @param mixed $website + * @return string + */ + protected function _getWebsiteCode($website = null) + { + return $this->_storeManager->getWebsite($website)->getCode(); + } + + /** + * Get captcha image base URL + * + * @param mixed $website + * @return string + */ + public function getImgUrl($website = null) + { + return $this->_storeManager->getStore()->getBaseUrl( + DirectoryList::MEDIA + ) . 'captcha' . '/' . $this->_getWebsiteCode( + $website + ) . '/'; + } +} diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..49525fd --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,48 @@ + +Open Software License ("OSL") v. 3.0 + +This Open Software License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following licensing notice adjacent to the copyright notice for the Original Work: + +Licensed under the Open Software License version 3.0 + + 1. Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following: + + 1. to reproduce the Original Work in copies, either alone or as part of a collective work; + + 2. to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works ("Derivative Works") based upon the Original Work; + + 3. to distribute or communicate copies of the Original Work and Derivative Works to the public, with the proviso that copies of Original Work or Derivative Works that You distribute or communicate shall be licensed under this Open Software License; + + 4. to perform the Original Work publicly; and + + 5. to display the Original Work publicly. + + 2. Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works. + + 3. Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work. + + 4. Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor's trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license. + + 5. External Deployment. The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c). + + 6. Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work. + + 7. Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer. + + 8. Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation. + + 9. Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including 'fair use' or 'fair dealing'). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c). + + 10. Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware. + + 11. Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License. + + 12. Attorneys' Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License. + + 13. Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. + + 14. Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + 15. Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You. + + 16. Modification of This License. This License is Copyright (C) 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Open Software License" or "OSL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under " or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process. \ No newline at end of file diff --git a/LICENSE_AFL.txt b/LICENSE_AFL.txt new file mode 100644 index 0000000..f39d641 --- /dev/null +++ b/LICENSE_AFL.txt @@ -0,0 +1,48 @@ + +Academic Free License ("AFL") v. 3.0 + +This Academic Free License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following licensing notice adjacent to the copyright notice for the Original Work: + +Licensed under the Academic Free License version 3.0 + + 1. Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following: + + 1. to reproduce the Original Work in copies, either alone or as part of a collective work; + + 2. to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works ("Derivative Works") based upon the Original Work; + + 3. to distribute or communicate copies of the Original Work and Derivative Works to the public, under any license of your choice that does not contradict the terms and conditions, including Licensor's reserved rights and remedies, in this Academic Free License; + + 4. to perform the Original Work publicly; and + + 5. to display the Original Work publicly. + + 2. Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works. + + 3. Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work. + + 4. Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor's trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license. + + 5. External Deployment. The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c). + + 6. Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work. + + 7. Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer. + + 8. Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation. + + 9. Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including "fair use" or "fair dealing"). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c). + + 10. Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware. + + 11. Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License. + + 12. Attorneys' Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License. + + 13. Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. + + 14. Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + 15. Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You. + + 16. Modification of This License. This License is Copyright © 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Academic Free License" or "AFL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under " or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process. diff --git a/Model/CaptchaFactory.php b/Model/CaptchaFactory.php new file mode 100644 index 0000000..c1a8342 --- /dev/null +++ b/Model/CaptchaFactory.php @@ -0,0 +1,45 @@ +_objectManager = $objectManager; + } + + /** + * Get captcha instance + * + * @param string $captchaType + * @param string $formId + * @return \Magento\Captcha\Model\CaptchaInterface + * @throws \InvalidArgumentException + */ + public function create($captchaType, $formId) + { + $className = 'Magento\Captcha\Model\\' . ucfirst($captchaType); + + $instance = $this->_objectManager->create($className, ['formId' => $formId]); + if (!$instance instanceof \Magento\Captcha\Model\CaptchaInterface) { + throw new \InvalidArgumentException( + $className . ' does not implement \Magento\Captcha\Model\CaptchaInterface' + ); + } + return $instance; + } +} diff --git a/Model/CaptchaInterface.php b/Model/CaptchaInterface.php new file mode 100644 index 0000000..3b5d5ee --- /dev/null +++ b/Model/CaptchaInterface.php @@ -0,0 +1,40 @@ +configProvider = $configProvider; + } + + /** + * @param \Magento\Checkout\Block\Cart\Sidebar $subject + * @param array $result + * @return array + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function afterGetConfig(\Magento\Checkout\Block\Cart\Sidebar $subject, array $result) + { + return array_merge_recursive($result, $this->configProvider->getConfig()); + } +} diff --git a/Model/Checkout/ConfigProvider.php b/Model/Checkout/ConfigProvider.php new file mode 100644 index 0000000..34ee620 --- /dev/null +++ b/Model/Checkout/ConfigProvider.php @@ -0,0 +1,132 @@ +storeManager = $storeManager; + $this->captchaData = $captchaData; + $this->formIds = $formIds; + } + + /** + * @inheritdoc + */ + public function getConfig() + { + $config = []; + foreach ($this->formIds as $formId) { + $config['captcha'][$formId] = [ + 'isCaseSensitive' => $this->isCaseSensitive($formId), + 'imageHeight' => $this->getImageHeight($formId), + 'imageSrc' => $this->getImageSrc($formId), + 'refreshUrl' => $this->getRefreshUrl(), + 'isRequired' => $this->isRequired($formId), + 'timestamp' => time() + ]; + } + return $config; + } + + /** + * Returns is captcha case sensitive + * + * @param string $formId + * @return bool + */ + protected function isCaseSensitive($formId) + { + return (boolean)$this->getCaptchaModel($formId)->isCaseSensitive(); + } + + /** + * Returns captcha image height + * + * @param string $formId + * @return int + */ + protected function getImageHeight($formId) + { + return $this->getCaptchaModel($formId)->getHeight(); + } + + /** + * Returns captcha image source path + * + * @param string $formId + * @return string + */ + protected function getImageSrc($formId) + { + if ($this->isRequired($formId)) { + $captcha = $this->getCaptchaModel($formId); + $captcha->generate(); + return $captcha->getImgSrc(); + } + return ''; + } + + /** + * Returns URL to controller action which returns new captcha image + * + * @return string + */ + protected function getRefreshUrl() + { + $store = $this->storeManager->getStore(); + return $store->getUrl('captcha/refresh', ['_secure' => $store->isCurrentlySecure()]); + } + + /** + * Whether captcha is required to be inserted to this form + * + * @param string $formId + * @return bool + */ + protected function isRequired($formId) + { + return (boolean)$this->getCaptchaModel($formId)->isRequired(); + } + + /** + * Return captcha model for specified form + * + * @param string $formId + * @return \Magento\Captcha\Model\CaptchaInterface + */ + protected function getCaptchaModel($formId) + { + return $this->captchaData->getCaptcha($formId); + } +} diff --git a/Model/Config/Font.php b/Model/Config/Font.php new file mode 100644 index 0000000..cce57e6 --- /dev/null +++ b/Model/Config/Font.php @@ -0,0 +1,44 @@ + + */ +namespace Magento\Captcha\Model\Config; + +class Font implements \Magento\Framework\Option\ArrayInterface +{ + /** + * Captcha data + * + * @var \Magento\Captcha\Helper\Data + */ + protected $_captchaData = null; + + /** + * @param \Magento\Captcha\Helper\Data $captchaData + */ + public function __construct(\Magento\Captcha\Helper\Data $captchaData) + { + $this->_captchaData = $captchaData; + } + + /** + * Get options for font selection field + * + * @return array + */ + public function toOptionArray() + { + $optionArray = []; + foreach ($this->_captchaData->getFonts() as $fontName => $fontData) { + $optionArray[] = ['label' => $fontData['label'], 'value' => $fontName]; + } + return $optionArray; + } +} diff --git a/Model/Config/Form/AbstractForm.php b/Model/Config/Form/AbstractForm.php new file mode 100644 index 0000000..811e455 --- /dev/null +++ b/Model/Config/Form/AbstractForm.php @@ -0,0 +1,41 @@ + + */ +namespace Magento\Captcha\Model\Config\Form; + +use Magento\Framework\App\Config\Value; + +abstract class AbstractForm extends Value implements \Magento\Framework\Option\ArrayInterface +{ + /** + * @var string + */ + protected $_configPath; + + /** + * Returns options for form multiselect + * + * @return array + */ + public function toOptionArray() + { + $optionArray = []; + $backendConfig = $this->_config->getValue($this->_configPath, 'default'); + if ($backendConfig) { + foreach ($backendConfig as $formName => $formConfig) { + if (!empty($formConfig['label'])) { + $optionArray[] = ['label' => $formConfig['label'], 'value' => $formName]; + } + } + } + return $optionArray; + } +} diff --git a/Model/Config/Form/Backend.php b/Model/Config/Form/Backend.php new file mode 100644 index 0000000..9fad494 --- /dev/null +++ b/Model/Config/Form/Backend.php @@ -0,0 +1,20 @@ + + */ +namespace Magento\Captcha\Model\Config\Form; + +class Backend extends \Magento\Captcha\Model\Config\Form\AbstractForm +{ + /** + * @var string + */ + protected $_configPath = 'captcha/backend/areas'; +} diff --git a/Model/Config/Form/Frontend.php b/Model/Config/Form/Frontend.php new file mode 100644 index 0000000..84c1a00 --- /dev/null +++ b/Model/Config/Form/Frontend.php @@ -0,0 +1,20 @@ + + */ +namespace Magento\Captcha\Model\Config\Form; + +class Frontend extends \Magento\Captcha\Model\Config\Form\AbstractForm +{ + /** + * @var string + */ + protected $_configPath = 'captcha/frontend/areas'; +} diff --git a/Model/Config/Mode.php b/Model/Config/Mode.php new file mode 100644 index 0000000..b1f0239 --- /dev/null +++ b/Model/Config/Mode.php @@ -0,0 +1,31 @@ + + */ +namespace Magento\Captcha\Model\Config; + +class Mode implements \Magento\Framework\Option\ArrayInterface +{ + /** + * Get options for captcha mode selection field + * + * @return array + */ + public function toOptionArray() + { + return [ + ['label' => __('Always'), 'value' => \Magento\Captcha\Helper\Data::MODE_ALWAYS], + [ + 'label' => __('After number of attempts to login'), + 'value' => \Magento\Captcha\Helper\Data::MODE_AFTER_FAIL + ] + ]; + } +} diff --git a/Model/Customer/Plugin/AjaxLogin.php b/Model/Customer/Plugin/AjaxLogin.php new file mode 100644 index 0000000..84ac710 --- /dev/null +++ b/Model/Customer/Plugin/AjaxLogin.php @@ -0,0 +1,125 @@ +helper = $helper; + $this->sessionManager = $sessionManager; + $this->resultJsonFactory = $resultJsonFactory; + $this->serializer = $serializer ?: \Magento\Framework\App\ObjectManager::getInstance() + ->get(\Magento\Framework\Serialize\Serializer\Json::class); + $this->formIds = $formIds; + } + + /** + * Check captcha data on login action. + * + * @param \Magento\Customer\Controller\Ajax\Login $subject + * @param \Closure $proceed + * @return $this + * @SuppressWarnings(PHPMD.NPathComplexity) + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + */ + public function aroundExecute( + \Magento\Customer\Controller\Ajax\Login $subject, + \Closure $proceed + ) { + $captchaFormIdField = 'captcha_form_id'; + $captchaInputName = 'captcha_string'; + + /** @var \Magento\Framework\App\RequestInterface $request */ + $request = $subject->getRequest(); + + $loginParams = []; + $content = $request->getContent(); + if ($content) { + $loginParams = $this->serializer->unserialize($content); + } + $username = $loginParams['username'] ?? null; + $captchaString = $loginParams[$captchaInputName] ?? null; + $loginFormId = $loginParams[$captchaFormIdField] ?? null; + + if (!in_array($loginFormId, $this->formIds) && $this->helper->getCaptcha($loginFormId)->isRequired($username)) { + return $this->returnJsonError(__('Provided form does not exist')); + } + + foreach ($this->formIds as $formId) { + if ($formId === $loginFormId) { + $captchaModel = $this->helper->getCaptcha($formId); + if ($captchaModel->isRequired($username)) { + if (!$captchaModel->isCorrect($captchaString)) { + $this->sessionManager->setUsername($username); + $captchaModel->logAttempt($username); + return $this->returnJsonError(__('Incorrect CAPTCHA')); + } + } + $captchaModel->logAttempt($username); + } + } + return $proceed(); + } + + /** + * Format JSON response. + * + * @param \Magento\Framework\Phrase $phrase + * @return \Magento\Framework\Controller\Result\Json + */ + private function returnJsonError(\Magento\Framework\Phrase $phrase): \Magento\Framework\Controller\Result\Json + { + $resultJson = $this->resultJsonFactory->create(); + return $resultJson->setData(['errors' => true, 'message' => $phrase]); + } +} diff --git a/Model/DefaultModel.php b/Model/DefaultModel.php new file mode 100644 index 0000000..cf6950f --- /dev/null +++ b/Model/DefaultModel.php @@ -0,0 +1,592 @@ +session = $session; + $this->captchaData = $captchaData; + $this->resLogFactory = $resLogFactory; + $this->formId = $formId; + $this->randomMath = $randomMath ?? ObjectManager::getInstance()->get(Random::class); + $this->userContext = $userContext ?? ObjectManager::getInstance()->get(UserContextInterface::class); + } + + /** + * Returns key with respect of current form ID + * + * @param string $key + * @return string + */ + private function getFormIdKey($key) + { + return $this->formId . '_' . $key; + } + + /** + * Get Block Name + * + * @return string + */ + public function getBlockName() + { + return \Magento\Captcha\Block\Captcha\DefaultCaptcha::class; + } + + /** + * Whether captcha is required to be inserted to this form + * + * @param null|string $login + * @return bool + */ + public function isRequired($login = null) + { + if (($this->isUserAuth() + && !$this->isShownToLoggedInUser()) + || !$this->isEnabled() + || !in_array( + $this->formId, + $this->getTargetForms() + ) + || $this->userContext->getUserType() === UserContextInterface::USER_TYPE_INTEGRATION + ) { + return false; + } + + return $this->isShowAlways() + || $this->isOverLimitAttempts($login) + || $this->session->getData($this->getFormIdKey('show_captcha')); + } + + /** + * Check if CAPTCHA has to be shown to logged in user on this form + * + * @return bool + */ + public function isShownToLoggedInUser() + { + $forms = (array)$this->captchaData->getConfig('shown_to_logged_in_user'); + foreach ($forms as $formId => $isShownToLoggedIn) { + if ($isShownToLoggedIn && $this->formId == $formId) { + return true; + } + } + return false; + } + + /** + * Check is over limit attempts + * + * @param string $login + * @return bool + */ + private function isOverLimitAttempts($login) + { + return $this->isOverLimitIpAttempt() || $this->isOverLimitLoginAttempts($login); + } + + /** + * Returns number of allowed attempts for same login + * + * @return int + */ + private function getAllowedAttemptsForSameLogin() + { + return (int)$this->captchaData->getConfig('failed_attempts_login'); + } + + /** + * Returns number of allowed attempts from same IP + * + * @return int + */ + private function getAllowedAttemptsFromSameIp() + { + return (int)$this->captchaData->getConfig('failed_attempts_ip'); + } + + /** + * Check is over limit saved attempts from one ip + * + * @return bool + */ + private function isOverLimitIpAttempt() + { + $countAttemptsByIp = $this->getResourceModel()->countAttemptsByRemoteAddress(); + return $countAttemptsByIp >= $this->getAllowedAttemptsFromSameIp(); + } + + /** + * Is Over Limit Login Attempts + * + * @param string $login + * @return bool + */ + private function isOverLimitLoginAttempts($login) + { + if ($login != false) { + $countAttemptsByLogin = $this->getResourceModel()->countAttemptsByUserLogin($login); + return $countAttemptsByLogin >= $this->getAllowedAttemptsForSameLogin(); + } + return false; + } + + /** + * Check is user auth + * + * @return bool + */ + private function isUserAuth() + { + return $this->session->isLoggedIn() || $this->userContext->getUserId(); + } + + /** + * Whether to respect case while checking the answer + * + * @return bool + */ + public function isCaseSensitive() + { + return (string)$this->captchaData->getConfig('case_sensitive'); + } + + /** + * Get font to use when generating captcha + * + * @return string + */ + public function getFont() + { + $font = (string)$this->captchaData->getConfig('font'); + $fonts = $this->captchaData->getFonts(); + + if (isset($fonts[$font])) { + $fontPath = $fonts[$font]['path']; + } else { + $fontData = array_shift($fonts); + $fontPath = $fontData['path']; + } + + return $fontPath; + } + + /** + * After this time isCorrect() is going to return FALSE even if word was guessed correctly + * + * @return int + */ + public function getExpiration() + { + if (!$this->expiration) { + /** + * as "timeout" configuration parameter specifies timeout in minutes - we multiply it on 60 to set + * expiration in seconds + */ + $this->expiration = (int)$this->captchaData->getConfig('timeout') * 60; + } + return $this->expiration; + } + + /** + * Get timeout for session token + * + * @return int + */ + public function getTimeout() + { + return $this->getExpiration(); + } + + /** + * Get captcha image directory + * + * @return string + */ + public function getImgDir() + { + return $this->captchaData->getImgDir(); + } + + /** + * Get captcha image base URL + * + * @return string + */ + public function getImgUrl() + { + return $this->captchaData->getImgUrl(); + } + + /** + * Checks whether captcha was guessed correctly by user + * + * @param string $word + * @return bool + */ + public function isCorrect($word) + { + $storedWords = $this->getWords(); + $this->clearWord(); + + if (!$word || !$storedWords) { + return false; + } + + if (!$this->isCaseSensitive()) { + $storedWords = strtolower($storedWords); + $word = strtolower($word); + } + return in_array($word, explode(',', $storedWords)); + } + + /** + * Return full URL to captcha image + * + * @return string + */ + public function getImgSrc() + { + return $this->getImgUrl() . $this->getId() . $this->getSuffix(); + } + + /** + * Log attempt + * + * @param string $login + * @return $this + */ + public function logAttempt($login) + { + if ($this->isEnabled() && in_array($this->formId, $this->getTargetForms())) { + $this->getResourceModel()->logAttempt($login); + if ($this->isOverLimitLoginAttempts($login)) { + $this->setShowCaptchaInSession(true); + } + } + return $this; + } + + /** + * Set show_captcha flag in session + * + * @param bool $value + * @return void + * @since 100.1.0 + */ + public function setShowCaptchaInSession($value = true) + { + if ($value !== true) { + $value = false; + } + + $this->session->setData($this->getFormIdKey('show_captcha'), $value); + } + + /** + * Generate word used for captcha render + * + * @return string + * @throws \Magento\Framework\Exception\LocalizedException + * @since 100.2.0 + */ + protected function generateWord() + { + $symbols = (string)$this->captchaData->getConfig('symbols'); + $wordLen = $this->getWordLen(); + return $this->randomMath->getRandomString($wordLen, $symbols); + } + + /** + * Returns length for generating captcha word. This value may be dynamic. + * + * @return int + * @throws \Magento\Framework\Exception\LocalizedException + * @since 100.2.0 + */ + public function getWordLen() + { + $from = 0; + $to = 0; + $length = (string)$this->captchaData->getConfig('length'); + if (!is_numeric($length)) { + if (preg_match('/(\d+)-(\d+)/', $length, $matches)) { + $from = (int)$matches[1]; + $to = (int)$matches[2]; + } + } else { + $from = (int)$length; + $to = (int)$length; + } + + if ($to < $from || $from < 1 || $to < 1) { + $from = self::DEFAULT_WORD_LENGTH_FROM; + $to = self::DEFAULT_WORD_LENGTH_TO; + } + + return Random::getRandomNumber($from, $to); + } + + /** + * Whether to show captcha for this form every time + * + * @return bool + */ + private function isShowAlways() + { + $captchaMode = (string)$this->captchaData->getConfig('mode'); + + if ($captchaMode === Data::MODE_ALWAYS) { + return true; + } + + if ($captchaMode === Data::MODE_AFTER_FAIL + && $this->getAllowedAttemptsForSameLogin() === 0 + ) { + return true; + } + + $alwaysFor = $this->captchaData->getConfig('always_for'); + foreach ($alwaysFor as $nodeFormId => $isAlwaysFor) { + if ($isAlwaysFor && $this->formId == $nodeFormId) { + return true; + } + } + + return false; + } + + /** + * Whether captcha is enabled at this area + * + * @return bool + */ + private function isEnabled() + { + return (string)$this->captchaData->getConfig('enable'); + } + + /** + * Retrieve list of forms where captcha must be shown + * + * For frontend this list is based on current website + * + * @return array + */ + private function getTargetForms() + { + $formsString = (string)$this->captchaData->getConfig('forms'); + return explode(',', $formsString); + } + + /** + * Get captcha word + * + * @return string|null + */ + public function getWord() + { + $sessionData = $this->session->getData($this->getFormIdKey(self::SESSION_WORD)); + return time() < $sessionData['expires'] ? $sessionData['data'] : null; + } + + /** + * Get captcha words + * + * @return string + */ + private function getWords() + { + $sessionData = $this->session->getData($this->getFormIdKey(self::SESSION_WORD)); + $words = ''; + if (isset($sessionData['expires'], $sessionData['words']) && time() < $sessionData['expires']) { + $words = $sessionData['words']; + } + + return $words; + } + + /** + * Set captcha word + * + * @param string $word + * @return $this + * @since 100.2.0 + */ + protected function setWord($word) + { + $this->words = $this->words ? $this->words . ',' . $word : $word; + $this->session->setData( + $this->getFormIdKey(self::SESSION_WORD), + ['data' => $word, 'words' => $this->words, 'expires' => time() + $this->getTimeout()] + ); + $this->word = $word; + return $this; + } + + /** + * Set captcha word + * + * @return $this + */ + private function clearWord() + { + $this->session->unsetData($this->getFormIdKey(self::SESSION_WORD)); + $this->word = null; + return $this; + } + + /** + * Override function to generate less curly captcha that will not cut off + * + * @see \Laminas\Captcha\Image::_randomSize() + * @return int + * @throws \Magento\Framework\Exception\LocalizedException + * @since 100.2.0 + */ + protected function randomSize() + { + return Random::getRandomNumber(280, 300) / 100; + } + + /** + * Overlap of the parent method + * + * @return void + * + * Now deleting old captcha images make crontab script + * @see \Magento\Captcha\Cron\DeleteExpiredImages::execute + * + * Added SuppressWarnings since this method is declared in parent class and we can not use other method name. + * @SuppressWarnings(PHPMD.ShortMethodName) + * @since 100.2.0 + */ + protected function gc() + { + return; // required for static testing to pass + } + + /** + * Get resource model + * + * @return \Magento\Captcha\Model\ResourceModel\Log + */ + private function getResourceModel() + { + return $this->resLogFactory->create(); + } +} diff --git a/Model/Filter/CaptchaConfigPostProcessorComposite.php b/Model/Filter/CaptchaConfigPostProcessorComposite.php new file mode 100644 index 0000000..182ef89 --- /dev/null +++ b/Model/Filter/CaptchaConfigPostProcessorComposite.php @@ -0,0 +1,45 @@ +processors = $processors; + } + + /** + * Loops through all leafs of the composite and calls process method + * + * @param array $config + * @return array + */ + public function process(array $config): array + { + $result = []; + foreach ($this->processors as $processor) { + $result = array_merge_recursive($result, $processor->process($config)); + } + return $result; + } +} diff --git a/Model/Filter/QuoteDataConfigFilter.php b/Model/Filter/QuoteDataConfigFilter.php new file mode 100644 index 0000000..b90497e --- /dev/null +++ b/Model/Filter/QuoteDataConfigFilter.php @@ -0,0 +1,47 @@ +filterList = $filterList; + } + + /** + * Filters the quote config with values from a filter list + * + * @param array $config + * @return array + */ + public function process(array $config): array + { + foreach ($this->filterList as $filterKey) { + /** @var string $filterKey */ + if (isset($config['quoteData']) && array_key_exists($filterKey, $config['quoteData'])) { + unset($config['quoteData'][$filterKey]); + } + } + return $config; + } +} diff --git a/Model/ResourceModel/Log.php b/Model/ResourceModel/Log.php new file mode 100644 index 0000000..83d055d --- /dev/null +++ b/Model/ResourceModel/Log.php @@ -0,0 +1,192 @@ + + */ +class Log extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb +{ + /** + * Remote Address log type + */ + const TYPE_REMOTE_ADDRESS = 1; + + /** + * Type User Login Name + */ + const TYPE_LOGIN = 2; + + /** + * Core Date + * + * @var \Magento\Framework\Stdlib\DateTime\DateTime + */ + protected $_coreDate; + + /** + * @var \Magento\Framework\HTTP\PhpEnvironment\RemoteAddress + */ + protected $_remoteAddress; + + /** + * @param \Magento\Framework\Model\ResourceModel\Db\Context $context + * @param \Magento\Framework\Stdlib\DateTime\DateTime $coreDate + * @param \Magento\Framework\HTTP\PhpEnvironment\RemoteAddress $remoteAddress + * @param string $connectionName + */ + public function __construct( + \Magento\Framework\Model\ResourceModel\Db\Context $context, + \Magento\Framework\Stdlib\DateTime\DateTime $coreDate, + \Magento\Framework\HTTP\PhpEnvironment\RemoteAddress $remoteAddress, + $connectionName = null + ) { + $this->_coreDate = $coreDate; + $this->_remoteAddress = $remoteAddress; + parent::__construct($context, $connectionName); + } + + /** + * Define main table + * + * @return void + */ + protected function _construct() + { + $this->_setMainTable('captcha_log'); + } + + /** + * Save or Update count Attempts + * + * @param string|null $login + * @return $this + * @throws \Magento\Framework\Exception\LocalizedException + */ + public function logAttempt($login) + { + if ($login != null) { + $this->getConnection()->insertOnDuplicate( + $this->getMainTable(), + [ + 'type' => self::TYPE_LOGIN, + 'value' => $login, + 'count' => 1, + 'updated_at' => $this->_coreDate->gmtDate() + ], + ['count' => new \Laminas\Db\Sql\Expression('count+1'), 'updated_at'] + ); + } + $ip = $this->_remoteAddress->getRemoteAddress(); + if ($ip != null) { + $this->getConnection()->insertOnDuplicate( + $this->getMainTable(), + [ + 'type' => self::TYPE_REMOTE_ADDRESS, + 'value' => $ip, + 'count' => 1, + 'updated_at' => $this->_coreDate->gmtDate() + ], + ['count' => new \Laminas\Db\Sql\Expression('count+1'), 'updated_at'] + ); + } + return $this; + } + + /** + * Delete User attempts by login + * + * @param string $login + * @return $this + * @throws \Magento\Framework\Exception\LocalizedException + */ + public function deleteUserAttempts($login) + { + if ($login != null) { + $this->getConnection()->delete( + $this->getMainTable(), + ['type = ?' => self::TYPE_LOGIN, 'value = ?' => $login] + ); + } + $ip = $this->_remoteAddress->getRemoteAddress(); + if ($ip != null) { + $this->getConnection()->delete( + $this->getMainTable(), + ['type = ?' => self::TYPE_REMOTE_ADDRESS, 'value = ?' => $ip] + ); + } + + return $this; + } + + /** + * Get count attempts by ip + * + * @return null|int + * @throws \Magento\Framework\Exception\LocalizedException + */ + public function countAttemptsByRemoteAddress() + { + $ip = $this->_remoteAddress->getRemoteAddress(); + if (!$ip) { + return 0; + } + $connection = $this->getConnection(); + $select = $connection->select()->from( + $this->getMainTable(), + 'count' + )->where( + 'type = ?', + self::TYPE_REMOTE_ADDRESS + )->where( + 'value = ?', + $ip + ); + return $connection->fetchOne($select); + } + + /** + * Get count attempts by user login + * + * @param string $login + * @return null|int + * @throws \Magento\Framework\Exception\LocalizedException + */ + public function countAttemptsByUserLogin($login) + { + if (!$login) { + return 0; + } + $connection = $this->getConnection(); + $select = $connection->select()->from( + $this->getMainTable(), + 'count' + )->where( + 'type = ?', + self::TYPE_LOGIN + )->where( + 'value = ?', + $login + ); + return $connection->fetchOne($select); + } + + /** + * Delete attempts with expired in update_at time + * + * @return void + * @throws \Magento\Framework\Exception\LocalizedException + */ + public function deleteOldAttempts() + { + $this->getConnection()->delete( + $this->getMainTable(), + ['updated_at < ?' => $this->_coreDate->gmtDate(null, time() - 60 * 30)] + ); + } +} diff --git a/Observer/CaptchaStringResolver.php b/Observer/CaptchaStringResolver.php new file mode 100644 index 0000000..059d395 --- /dev/null +++ b/Observer/CaptchaStringResolver.php @@ -0,0 +1,40 @@ +getPost(CaptchaHelper::INPUT_NAME_FIELD_VALUE); + if (!empty($captchaParams) && !empty($captchaParams[$formId])) { + $value = $captchaParams[$formId]; + } elseif ($headerValue = $request->getHeader('X-Captcha')) { + //CAPTCHA was provided via header for this XHR/web API request. + $value = $headerValue; + } + + return $value; + } +} diff --git a/Observer/CheckContactUsFormObserver.php b/Observer/CheckContactUsFormObserver.php new file mode 100644 index 0000000..2d0a647 --- /dev/null +++ b/Observer/CheckContactUsFormObserver.php @@ -0,0 +1,99 @@ +_helper = $helper; + $this->_actionFlag = $actionFlag; + $this->messageManager = $messageManager; + $this->redirect = $redirect; + $this->captchaStringResolver = $captchaStringResolver; + $this->dataPersistor = $dataPersistor; + } + + /** + * Check CAPTCHA on Contact Us page + * + * @param Observer $observer + * @return void + */ + public function execute(Observer $observer) + { + $formId = 'contact_us'; + $captcha = $this->_helper->getCaptcha($formId); + if ($captcha->isRequired()) { + /** @var Action $controller */ + $controller = $observer->getControllerAction(); + if (!$captcha->isCorrect($this->captchaStringResolver->resolve($controller->getRequest(), $formId))) { + $this->messageManager->addErrorMessage(__('Incorrect CAPTCHA.')); + $this->dataPersistor->set($formId, $controller->getRequest()->getPostValue()); + $this->_actionFlag->set('', Action::FLAG_NO_DISPATCH, true); + $this->redirect->redirect($controller->getResponse(), 'contact/index/index'); + } + } + } +} diff --git a/Observer/CheckForgotpasswordObserver.php b/Observer/CheckForgotpasswordObserver.php new file mode 100644 index 0000000..623d119 --- /dev/null +++ b/Observer/CheckForgotpasswordObserver.php @@ -0,0 +1,83 @@ +_helper = $helper; + $this->_actionFlag = $actionFlag; + $this->messageManager = $messageManager; + $this->redirect = $redirect; + $this->captchaStringResolver = $captchaStringResolver; + } + + /** + * Check Captcha On Forgot Password Page + * + * @param \Magento\Framework\Event\Observer $observer + * @return $this + */ + public function execute(\Magento\Framework\Event\Observer $observer) + { + $formId = 'user_forgotpassword'; + $captchaModel = $this->_helper->getCaptcha($formId); + if ($captchaModel->isRequired()) { + /** @var \Magento\Framework\App\Action\Action $controller */ + $controller = $observer->getControllerAction(); + if (!$captchaModel->isCorrect($this->captchaStringResolver->resolve($controller->getRequest(), $formId))) { + $this->messageManager->addErrorMessage(__('Incorrect CAPTCHA')); + $this->_actionFlag->set('', \Magento\Framework\App\Action\Action::FLAG_NO_DISPATCH, true); + $this->redirect->redirect($controller->getResponse(), '*/*/forgotpassword'); + } + } + + return $this; + } +} diff --git a/Observer/CheckUserCreateObserver.php b/Observer/CheckUserCreateObserver.php new file mode 100644 index 0000000..ef66116 --- /dev/null +++ b/Observer/CheckUserCreateObserver.php @@ -0,0 +1,104 @@ +_helper = $helper; + $this->_actionFlag = $actionFlag; + $this->messageManager = $messageManager; + $this->_session = $session; + $this->_urlManager = $urlManager; + $this->redirect = $redirect; + $this->captchaStringResolver = $captchaStringResolver; + } + + /** + * Check Captcha On User Login Page + * + * @param \Magento\Framework\Event\Observer $observer + * @return $this + */ + public function execute(\Magento\Framework\Event\Observer $observer) + { + $formId = 'user_create'; + $captchaModel = $this->_helper->getCaptcha($formId); + if ($captchaModel->isRequired()) { + /** @var \Magento\Framework\App\Action\Action $controller */ + $controller = $observer->getControllerAction(); + if (!$captchaModel->isCorrect($this->captchaStringResolver->resolve($controller->getRequest(), $formId))) { + $this->messageManager->addErrorMessage(__('Incorrect CAPTCHA')); + $this->_actionFlag->set('', \Magento\Framework\App\Action\Action::FLAG_NO_DISPATCH, true); + $this->_session->setCustomerFormData($controller->getRequest()->getPostValue()); + $url = $this->_urlManager->getUrl('*/*/create', ['_nosecret' => true]); + $controller->getResponse()->setRedirect($this->redirect->error($url)); + } + } + + return $this; + } +} diff --git a/Observer/CheckUserEditObserver.php b/Observer/CheckUserEditObserver.php new file mode 100644 index 0000000..872bbec --- /dev/null +++ b/Observer/CheckUserEditObserver.php @@ -0,0 +1,136 @@ +helper = $helper; + $this->actionFlag = $actionFlag; + $this->messageManager = $messageManager; + $this->redirect = $redirect; + $this->captchaStringResolver = $captchaStringResolver; + $this->authentication = $authentication; + $this->customerSession = $customerSession; + $this->scopeConfig = $scopeConfig; + } + + /** + * Check Captcha On Forgot Password Page + * + * @param \Magento\Framework\Event\Observer $observer + * @return $this|void + * @throws \Magento\Framework\Exception\SessionException + */ + public function execute(\Magento\Framework\Event\Observer $observer) + { + $captchaModel = $this->helper->getCaptcha(self::FORM_ID); + if ($captchaModel->isRequired()) { + /** @var \Magento\Framework\App\Action\Action $controller */ + $controller = $observer->getControllerAction(); + if (!$captchaModel->isCorrect( + $this->captchaStringResolver->resolve( + $controller->getRequest(), + self::FORM_ID + ) + )) { + $customerId = $this->customerSession->getCustomerId(); + $this->authentication->processAuthenticationFailure($customerId); + if ($this->authentication->isLocked($customerId)) { + $this->customerSession->logout(); + $this->customerSession->start(); + $message = __( + 'The account is locked. Please wait and try again or contact %1.', + $this->scopeConfig->getValue('contact/email/recipient_email') + ); + $this->messageManager->addErrorMessage($message); + } + $this->messageManager->addErrorMessage(__('Incorrect CAPTCHA')); + $this->actionFlag->set('', \Magento\Framework\App\Action\Action::FLAG_NO_DISPATCH, true); + $this->redirect->redirect($controller->getResponse(), '*/*/edit'); + } + } + + $customer = $this->customerSession->getCustomer(); + $login = $customer->getEmail(); + $captchaModel->logAttempt($login); + + return $this; + } +} diff --git a/Observer/CheckUserForgotPasswordBackendObserver.php b/Observer/CheckUserForgotPasswordBackendObserver.php new file mode 100644 index 0000000..21d7e2f --- /dev/null +++ b/Observer/CheckUserForgotPasswordBackendObserver.php @@ -0,0 +1,109 @@ +_helper = $helper; + $this->captchaStringResolver = $captchaStringResolver; + $this->_session = $session; + $this->_actionFlag = $actionFlag; + $this->messageManager = $messageManager; + $this->request = $request ?? ObjectManager::getInstance()->get(RequestInterface::class); + } + + /** + * Check Captcha On User Login Backend Page + * + * @param Event $observer + * @return $this + * @throws \Magento\Framework\Exception\Plugin\AuthenticationException + */ + public function execute(Event $observer) + { + $formId = 'backend_forgotpassword'; + $captchaModel = $this->_helper->getCaptcha($formId); + $controller = $observer->getControllerAction(); + $params = $this->request->getParams(); + $email = (string)$this->request->getParam('email'); + if (!empty($params) + && !empty($email) + && $captchaModel->isRequired() + && !$captchaModel->isCorrect($this->captchaStringResolver->resolve($this->request, $formId)) + ) { + $this->_session->setEmail($email); + $this->_actionFlag->set('', Action::FLAG_NO_DISPATCH, true); + $this->messageManager->addErrorMessage(__('Incorrect CAPTCHA')); + $controller->getResponse()->setRedirect( + $controller->getUrl('*/*/forgotpassword', ['_nosecret' => true]) + ); + } + + return $this; + } +} diff --git a/Observer/CheckUserLoginBackendObserver.php b/Observer/CheckUserLoginBackendObserver.php new file mode 100644 index 0000000..924514c --- /dev/null +++ b/Observer/CheckUserLoginBackendObserver.php @@ -0,0 +1,65 @@ +_helper = $helper; + $this->captchaStringResolver = $captchaStringResolver; + $this->_request = $request; + } + + /** + * Check Captcha On User Login Backend Page + * + * @param \Magento\Framework\Event\Observer $observer + * @throws \Magento\Framework\Exception\Plugin\AuthenticationException + * @return $this + */ + public function execute(\Magento\Framework\Event\Observer $observer) + { + $formId = 'backend_login'; + $captchaModel = $this->_helper->getCaptcha($formId); + $login = $observer->getEvent()->getUsername(); + if ($captchaModel->isRequired($login) + && !$captchaModel->isCorrect($this->captchaStringResolver->resolve($this->_request, $formId)) + ) { + $captchaModel->logAttempt($login); + throw new PluginAuthenticationException(__('Incorrect CAPTCHA.')); + } + $captchaModel->logAttempt($login); + + return $this; + } +} diff --git a/Observer/CheckUserLoginObserver.php b/Observer/CheckUserLoginObserver.php new file mode 100644 index 0000000..2750742 --- /dev/null +++ b/Observer/CheckUserLoginObserver.php @@ -0,0 +1,163 @@ +_helper = $helper; + $this->_actionFlag = $actionFlag; + $this->messageManager = $messageManager; + $this->_session = $customerSession; + $this->captchaStringResolver = $captchaStringResolver; + $this->_customerUrl = $customerUrl; + } + + /** + * Get customer repository + * + * @return \Magento\Customer\Api\CustomerRepositoryInterface + */ + private function getCustomerRepository() + { + + if (!($this->customerRepository instanceof \Magento\Customer\Api\CustomerRepositoryInterface)) { + return \Magento\Framework\App\ObjectManager::getInstance()->get( + \Magento\Customer\Api\CustomerRepositoryInterface::class + ); + } else { + return $this->customerRepository; + } + } + + /** + * Get authentication + * + * @return AuthenticationInterface + */ + private function getAuthentication() + { + + if (!($this->authentication instanceof AuthenticationInterface)) { + return \Magento\Framework\App\ObjectManager::getInstance()->get( + AuthenticationInterface::class + ); + } else { + return $this->authentication; + } + } + + /** + * Check captcha on user login page + * + * @param \Magento\Framework\Event\Observer $observer + * @return $this|void + */ + public function execute(\Magento\Framework\Event\Observer $observer) + { + $formId = 'user_login'; + $captchaModel = $this->_helper->getCaptcha($formId); + $controller = $observer->getControllerAction(); + $loginParams = $controller->getRequest()->getPost('login'); + $login = (is_array($loginParams) && array_key_exists('username', $loginParams)) + ? $loginParams['username'] + : null; + if ($captchaModel->isRequired($login)) { + $word = $this->captchaStringResolver->resolve($controller->getRequest(), $formId); + if (!$captchaModel->isCorrect($word)) { + try { + $customer = $this->getCustomerRepository()->get($login); + $this->getAuthentication()->processAuthenticationFailure($customer->getId()); + // phpcs:ignore Magento2.CodeAnalysis.EmptyBlock + } catch (NoSuchEntityException $e) { + //do nothing as customer existence is validated later in authenticate method + } + $this->messageManager->addErrorMessage(__('Incorrect CAPTCHA')); + $this->_actionFlag->set('', \Magento\Framework\App\Action\Action::FLAG_NO_DISPATCH, true); + $this->_session->setUsername($login); + $beforeUrl = $this->_session->getBeforeAuthUrl(); + $url = $beforeUrl ? $beforeUrl : $this->_customerUrl->getLoginUrl(); + $controller->getResponse()->setRedirect($url); + } + } + $captchaModel->logAttempt($login); + + return $this; + } +} diff --git a/Observer/ResetAttemptForBackendObserver.php b/Observer/ResetAttemptForBackendObserver.php new file mode 100644 index 0000000..165aef3 --- /dev/null +++ b/Observer/ResetAttemptForBackendObserver.php @@ -0,0 +1,44 @@ +resLogFactory = $resLogFactory; + } + + /** + * Reset Attempts For Backend + * + * @param Observer $observer + * @return Log + * @throws LocalizedException + */ + public function execute(Observer $observer) + { + return $this->resLogFactory->create()->deleteUserAttempts($observer->getUser()->getUsername()); + } +} diff --git a/Observer/ResetAttemptForFrontendAccountEditObserver.php b/Observer/ResetAttemptForFrontendAccountEditObserver.php new file mode 100644 index 0000000..8cf4f1d --- /dev/null +++ b/Observer/ResetAttemptForFrontendAccountEditObserver.php @@ -0,0 +1,55 @@ +helper = $helper; + $this->resLogFactory = $resLogFactory; + } + + /** + * Reset Attempts For Frontend + * + * @param \Magento\Framework\Event\Observer $observer + * @return \Magento\Captcha\Observer\ResetAttemptForFrontendObserver + */ + public function execute(\Magento\Framework\Event\Observer $observer) + { + $email = $observer->getEmail(); + $captchaModel = $this->helper->getCaptcha(self::FORM_ID); + $captchaModel->setShowCaptchaInSession(false); + + return $this->resLogFactory->create()->deleteUserAttempts($email); + } +} diff --git a/Observer/ResetAttemptForFrontendObserver.php b/Observer/ResetAttemptForFrontendObserver.php new file mode 100644 index 0000000..e65793c --- /dev/null +++ b/Observer/ResetAttemptForFrontendObserver.php @@ -0,0 +1,48 @@ +resLogFactory = $resLogFactory; + } + + /** + * Reset Attempts For Frontend + * + * @param Observer $observer + * @return Log + * @throws LocalizedException + */ + public function execute(Observer $observer) + { + /** @var Customer $model */ + $model = $observer->getModel(); + + return $this->resLogFactory->create()->deleteUserAttempts($model->getEmail()); + } +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..35979fb --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +The Captcha module allows applying Turing test in the process of user authentication or similar tasks. \ No newline at end of file diff --git a/Test/Mftf/ActionGroup/AdminLoginWithCaptchaActionGroup.xml b/Test/Mftf/ActionGroup/AdminLoginWithCaptchaActionGroup.xml new file mode 100644 index 0000000..8d09944 --- /dev/null +++ b/Test/Mftf/ActionGroup/AdminLoginWithCaptchaActionGroup.xml @@ -0,0 +1,21 @@ + + + + + + + EXTENDS: LoginAsAdmin. Fills in the Captcha field on the Backend Admin Login page. + + + + + + + + diff --git a/Test/Mftf/ActionGroup/AssertCaptchaNotVisibleOnCustomerLoginFormActionGroup.xml b/Test/Mftf/ActionGroup/AssertCaptchaNotVisibleOnCustomerLoginFormActionGroup.xml new file mode 100644 index 0000000..c33bcb4 --- /dev/null +++ b/Test/Mftf/ActionGroup/AssertCaptchaNotVisibleOnCustomerLoginFormActionGroup.xml @@ -0,0 +1,26 @@ + + + + + + + Validate that the Captcha is NOT present on the Backend Admin Login page. + + + + + + + + + + + + + diff --git a/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnAdminLoginFormActionGroup.xml b/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnAdminLoginFormActionGroup.xml new file mode 100644 index 0000000..135b687 --- /dev/null +++ b/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnAdminLoginFormActionGroup.xml @@ -0,0 +1,25 @@ + + + + + + + Validate that the Captcha IS present on the Backend Admin Login page. + + + + + + + + + + + + diff --git a/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnContactUsFormActionGroup.xml b/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnContactUsFormActionGroup.xml new file mode 100644 index 0000000..6f61083 --- /dev/null +++ b/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnContactUsFormActionGroup.xml @@ -0,0 +1,25 @@ + + + + + + + Validate that the Captcha IS present on the Storefront Contact Us page. + + + + + + + + + + + + diff --git a/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnCustomerAccountCreatePageActionGroup.xml b/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnCustomerAccountCreatePageActionGroup.xml new file mode 100644 index 0000000..bf2f5c2 --- /dev/null +++ b/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnCustomerAccountCreatePageActionGroup.xml @@ -0,0 +1,20 @@ + + + + + + + Validate that the Captcha IS present on the Storefront Create Customer page. + + + + + + + diff --git a/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnCustomerAccountInfoActionGroup.xml b/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnCustomerAccountInfoActionGroup.xml new file mode 100644 index 0000000..c2af71c --- /dev/null +++ b/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnCustomerAccountInfoActionGroup.xml @@ -0,0 +1,27 @@ + + + + + + + Validate that the Captcha IS present on the Storefront Customer Account Information page. + + + + + + + + + + + + + + diff --git a/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnCustomerLoginFormActionGroup.xml b/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnCustomerLoginFormActionGroup.xml new file mode 100644 index 0000000..a53f8f7 --- /dev/null +++ b/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnCustomerLoginFormActionGroup.xml @@ -0,0 +1,25 @@ + + + + + + + Validate that the Captcha IS present on the Storefront Customer Login page. + + + + + + + + + + + + diff --git a/Test/Mftf/ActionGroup/CaptchaFormsDisplayingActionGroup.xml b/Test/Mftf/ActionGroup/CaptchaFormsDisplayingActionGroup.xml new file mode 100644 index 0000000..52c4181 --- /dev/null +++ b/Test/Mftf/ActionGroup/CaptchaFormsDisplayingActionGroup.xml @@ -0,0 +1,27 @@ + + + + + + + Navigates to store configuration page through UI and expands the CAPTCHA section under Customers > Customer Configuration. + + + + + + + + + + + + + + diff --git a/Test/Mftf/ActionGroup/StorefrontCustomerChangeEmailWithCaptchaActionGroup.xml b/Test/Mftf/ActionGroup/StorefrontCustomerChangeEmailWithCaptchaActionGroup.xml new file mode 100644 index 0000000..662fdf5 --- /dev/null +++ b/Test/Mftf/ActionGroup/StorefrontCustomerChangeEmailWithCaptchaActionGroup.xml @@ -0,0 +1,21 @@ + + + + + + + EXTENDS: StorefrontCustomerChangeEmailActionGroup. Fills in the Captcha field on the Storefront Customer Information page. + + + + + + + + diff --git a/Test/Mftf/ActionGroup/StorefrontFillContactUsFormWithCaptchaActionGroup.xml b/Test/Mftf/ActionGroup/StorefrontFillContactUsFormWithCaptchaActionGroup.xml new file mode 100644 index 0000000..70de13b --- /dev/null +++ b/Test/Mftf/ActionGroup/StorefrontFillContactUsFormWithCaptchaActionGroup.xml @@ -0,0 +1,21 @@ + + + + + + + EXTENDS: StorefrontFillContactUsFormActionGroup. Fills in the Captcha field on the Storefront Contact Us page. + + + + + + + + diff --git a/Test/Mftf/ActionGroup/StorefrontFillCustomerAccountCreationFormWithCaptchaActionGroup.xml b/Test/Mftf/ActionGroup/StorefrontFillCustomerAccountCreationFormWithCaptchaActionGroup.xml new file mode 100644 index 0000000..6f45f1b --- /dev/null +++ b/Test/Mftf/ActionGroup/StorefrontFillCustomerAccountCreationFormWithCaptchaActionGroup.xml @@ -0,0 +1,21 @@ + + + + + + + EXTENDS: StorefrontFillCustomerAccountCreationFormActionGroup. Fills in the Captcha field on the Storefront Create Customer page. + + + + + + + + diff --git a/Test/Mftf/ActionGroup/StorefrontFillCustomerLoginFormWithCaptchaActionGroup.xml b/Test/Mftf/ActionGroup/StorefrontFillCustomerLoginFormWithCaptchaActionGroup.xml new file mode 100644 index 0000000..ace3132 --- /dev/null +++ b/Test/Mftf/ActionGroup/StorefrontFillCustomerLoginFormWithCaptchaActionGroup.xml @@ -0,0 +1,21 @@ + + + + + + + EXTENDS: StorefrontFillCustomerLoginFormActionGroup. Fills in the Captcha field on the Storefront Customer Login page. + + + + + + + + diff --git a/Test/Mftf/Data/CaptchaConfigData.xml b/Test/Mftf/Data/CaptchaConfigData.xml new file mode 100644 index 0000000..90f48c3 --- /dev/null +++ b/Test/Mftf/Data/CaptchaConfigData.xml @@ -0,0 +1,142 @@ + + + + + + + customer/captcha/enable + 0 + Yes + 1 + + + customer/captcha/enable + 0 + No + 0 + + + customer/captcha/forms + 0 + Create user + user_create + + + customer/captcha/forms + 0 + Contact Us + contact_us + + + + customer/captcha/forms + 0 + Login + user_login + + + customer/captcha/forms + 0 + Change password + user_edit + + + + customer/captcha/forms + 0 + Forgot password + user_forgotpassword + + + customer/captcha/mode + 0 + Always + always + + + + customer/captcha/mode + 0 + After number of attempts to login + after_fail + + + customer/captcha/length + admin + 1 + 3 + 3 + + + customer/captcha/symbols + admin + 1 + 1 + 1 + + + + customer/captcha/length + admin + 1 + 4-5 + 4-5 + + + + customer/captcha/symbols + admin + 1 + ABCDEFGHJKMnpqrstuvwxyz23456789 + ABCDEFGHJKMnpqrstuvwxyz23456789 + + + + admin/captcha/enable + 0 + Yes + 1 + + + admin/captcha/enable + 0 + No + 0 + + + admin/captcha/length + admin + 1 + 3 + 3 + + + admin/captcha/symbols + admin + 1 + 1 + 1 + + + + admin/captcha/length + admin + 1 + 4-5 + 4-5 + + + + admin/captcha/symbols + admin + 1 + ABCDEFGHJKMnpqrstuvwxyz23456789 + ABCDEFGHJKMnpqrstuvwxyz23456789 + + diff --git a/Test/Mftf/Data/CaptchaData.xml b/Test/Mftf/Data/CaptchaData.xml new file mode 100644 index 0000000..d8fb206 --- /dev/null +++ b/Test/Mftf/Data/CaptchaData.xml @@ -0,0 +1,19 @@ + + + + + + WrongCAPTCHA + + + + + 111 + + diff --git a/Test/Mftf/Data/CaptchaFormsDisplayingData.xml b/Test/Mftf/Data/CaptchaFormsDisplayingData.xml new file mode 100644 index 0000000..57a0921 --- /dev/null +++ b/Test/Mftf/Data/CaptchaFormsDisplayingData.xml @@ -0,0 +1,20 @@ + + + + + + Create user + Login + Forgot password + Contact Us + Change password + Register during Checkout + Check Out as Guest + + diff --git a/Test/Mftf/LICENSE.txt b/Test/Mftf/LICENSE.txt new file mode 100644 index 0000000..49525fd --- /dev/null +++ b/Test/Mftf/LICENSE.txt @@ -0,0 +1,48 @@ + +Open Software License ("OSL") v. 3.0 + +This Open Software License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following licensing notice adjacent to the copyright notice for the Original Work: + +Licensed under the Open Software License version 3.0 + + 1. Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following: + + 1. to reproduce the Original Work in copies, either alone or as part of a collective work; + + 2. to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works ("Derivative Works") based upon the Original Work; + + 3. to distribute or communicate copies of the Original Work and Derivative Works to the public, with the proviso that copies of Original Work or Derivative Works that You distribute or communicate shall be licensed under this Open Software License; + + 4. to perform the Original Work publicly; and + + 5. to display the Original Work publicly. + + 2. Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works. + + 3. Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work. + + 4. Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor's trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license. + + 5. External Deployment. The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c). + + 6. Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work. + + 7. Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer. + + 8. Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation. + + 9. Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including 'fair use' or 'fair dealing'). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c). + + 10. Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware. + + 11. Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License. + + 12. Attorneys' Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License. + + 13. Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. + + 14. Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + 15. Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You. + + 16. Modification of This License. This License is Copyright (C) 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Open Software License" or "OSL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under " or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process. \ No newline at end of file diff --git a/Test/Mftf/LICENSE_AFL.txt b/Test/Mftf/LICENSE_AFL.txt new file mode 100644 index 0000000..f39d641 --- /dev/null +++ b/Test/Mftf/LICENSE_AFL.txt @@ -0,0 +1,48 @@ + +Academic Free License ("AFL") v. 3.0 + +This Academic Free License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following licensing notice adjacent to the copyright notice for the Original Work: + +Licensed under the Academic Free License version 3.0 + + 1. Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following: + + 1. to reproduce the Original Work in copies, either alone or as part of a collective work; + + 2. to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works ("Derivative Works") based upon the Original Work; + + 3. to distribute or communicate copies of the Original Work and Derivative Works to the public, under any license of your choice that does not contradict the terms and conditions, including Licensor's reserved rights and remedies, in this Academic Free License; + + 4. to perform the Original Work publicly; and + + 5. to display the Original Work publicly. + + 2. Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works. + + 3. Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work. + + 4. Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor's trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license. + + 5. External Deployment. The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c). + + 6. Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work. + + 7. Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer. + + 8. Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation. + + 9. Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including "fair use" or "fair dealing"). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c). + + 10. Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware. + + 11. Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License. + + 12. Attorneys' Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License. + + 13. Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. + + 14. Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + 15. Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You. + + 16. Modification of This License. This License is Copyright © 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Academic Free License" or "AFL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under " or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process. diff --git a/Test/Mftf/README.md b/Test/Mftf/README.md new file mode 100644 index 0000000..48be768 --- /dev/null +++ b/Test/Mftf/README.md @@ -0,0 +1,3 @@ +# Captcha Functional Tests + +The Functional Test Module for **Magento Captcha** module. diff --git a/Test/Mftf/Section/AdminLoginFormSection.xml b/Test/Mftf/Section/AdminLoginFormSection.xml new file mode 100644 index 0000000..2bcc6fc --- /dev/null +++ b/Test/Mftf/Section/AdminLoginFormSection.xml @@ -0,0 +1,16 @@ + + + + +
+ + + +
+
diff --git a/Test/Mftf/Section/CaptchaFormsDisplayingSection.xml b/Test/Mftf/Section/CaptchaFormsDisplayingSection.xml new file mode 100644 index 0000000..030c9f5 --- /dev/null +++ b/Test/Mftf/Section/CaptchaFormsDisplayingSection.xml @@ -0,0 +1,27 @@ + + + + +
+ + + + + + + + + + + + + + +
+
diff --git a/Test/Mftf/Section/StorefrontContactUsCaptchaSection.xml b/Test/Mftf/Section/StorefrontContactUsCaptchaSection.xml new file mode 100644 index 0000000..f587812 --- /dev/null +++ b/Test/Mftf/Section/StorefrontContactUsCaptchaSection.xml @@ -0,0 +1,16 @@ + + + + +
+ + + +
+
diff --git a/Test/Mftf/Section/StorefrontContactUsFormSection.xml b/Test/Mftf/Section/StorefrontContactUsFormSection.xml new file mode 100644 index 0000000..60cf961 --- /dev/null +++ b/Test/Mftf/Section/StorefrontContactUsFormSection.xml @@ -0,0 +1,16 @@ + + + + +
+ + + +
+
diff --git a/Test/Mftf/Section/StorefrontCustomerAccountInformationSection.xml b/Test/Mftf/Section/StorefrontCustomerAccountInformationSection.xml new file mode 100644 index 0000000..a273c8d --- /dev/null +++ b/Test/Mftf/Section/StorefrontCustomerAccountInformationSection.xml @@ -0,0 +1,16 @@ + + + + +
+ + + +
+
diff --git a/Test/Mftf/Section/StorefrontCustomerCreateFormSection.xml b/Test/Mftf/Section/StorefrontCustomerCreateFormSection.xml new file mode 100644 index 0000000..f48e612 --- /dev/null +++ b/Test/Mftf/Section/StorefrontCustomerCreateFormSection.xml @@ -0,0 +1,16 @@ + + + + +
+ + + +
+
diff --git a/Test/Mftf/Section/StorefrontCustomerSignInFormSection.xml b/Test/Mftf/Section/StorefrontCustomerSignInFormSection.xml new file mode 100644 index 0000000..54aa36d --- /dev/null +++ b/Test/Mftf/Section/StorefrontCustomerSignInFormSection.xml @@ -0,0 +1,16 @@ + + + + +
+ + + +
+
diff --git a/Test/Mftf/Section/StorefrontCustomerSignInPopupFormSection.xml b/Test/Mftf/Section/StorefrontCustomerSignInPopupFormSection.xml new file mode 100644 index 0000000..7a0557c --- /dev/null +++ b/Test/Mftf/Section/StorefrontCustomerSignInPopupFormSection.xml @@ -0,0 +1,16 @@ + + + + +
+ + + +
+
diff --git a/Test/Mftf/Test/AdminLoginWithCaptchaTest.xml b/Test/Mftf/Test/AdminLoginWithCaptchaTest.xml new file mode 100644 index 0000000..58cfd7a --- /dev/null +++ b/Test/Mftf/Test/AdminLoginWithCaptchaTest.xml @@ -0,0 +1,77 @@ + + + + + + + + + + <description value="Test creation for admin login with captcha."/> + <testCaseId value="MC-14012" /> + <severity value="MAJOR"/> + <group value="captcha"/> + <group value="mtf_migrated"/> + </annotations> + + <before> + <magentoCLI command="config:set {{AdminCaptchaLength3ConfigData.path}} {{AdminCaptchaLength3ConfigData.value}}" stepKey="setCaptchaLength" /> + <magentoCLI command="config:set {{AdminCaptchaSymbols1ConfigData.path}} {{AdminCaptchaSymbols1ConfigData.value}}" stepKey="setCaptchaSymbols" /> + <actionGroup ref="CliCacheCleanActionGroup" stepKey="cleanInvalidatedCaches"> + <argument name="tags" value="config full_page"/> + </actionGroup> + </before> + <after> + <magentoCLI command="config:set {{AdminCaptchaDefaultLengthConfigData.path}} {{AdminCaptchaDefaultLengthConfigData.value}}" stepKey="setDefaultCaptchaLength" /> + <magentoCLI command="config:set {{AdminCaptchaDefaultSymbolsConfigData.path}} {{AdminCaptchaDefaultSymbolsConfigData.value}}" stepKey="setDefaultCaptchaSymbols" /> + <actionGroup ref="CliCacheCleanActionGroup" stepKey="cleanInvalidatedCaches"> + <argument name="tags" value="config full_page"/> + </actionGroup> + </after> + + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdminWithWrongCredentialsFirstAttempt"> + <argument name="username" value="{{AdminUserWrongCredentials.username}}"/> + <argument name="password" value="{{AdminUserWrongCredentials.password}}"/> + </actionGroup> + <actionGroup ref="AssertMessageOnAdminLoginActionGroup" stepKey="seeFirstLoginErrorMessage" /> + + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdminWithWrongCredentialsSecondAttempt"> + <argument name="username" value="{{AdminUserWrongCredentials.username}}"/> + <argument name="password" value="{{AdminUserWrongCredentials.password}}"/> + </actionGroup> + <actionGroup ref="AssertMessageOnAdminLoginActionGroup" stepKey="seeSecondLoginErrorMessage" /> + + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdminWithWrongCredentialsThirdAttempt"> + <argument name="username" value="{{AdminUserWrongCredentials.username}}"/> + <argument name="password" value="{{AdminUserWrongCredentials.password}}"/> + </actionGroup> + <actionGroup ref="AssertMessageOnAdminLoginActionGroup" stepKey="seeThirdLoginErrorMessage" /> + + <!-- Check captcha visibility on admin login page --> + <actionGroup ref="AssertCaptchaVisibleOnAdminLoginFormActionGroup" stepKey="assertCaptchaVisible" /> + + <!-- Submit form with incorrect captcha --> + <actionGroup ref="AdminLoginWithCaptchaActionGroup" stepKey="loginAsAdminWithIncorrectCaptcha"> + <argument name="username" value="{{DefaultAdminUser.username}}"/> + <argument name="password" value="{{DefaultAdminUser.password}}"/> + <argument name="captcha" value="{{WrongCaptcha.value}}"/> + </actionGroup> + <actionGroup ref="AssertMessageOnAdminLoginActionGroup" stepKey="seeIncorrectCaptchaErrorMessage"> + <argument name="message" value="Incorrect CAPTCHA."/> + </actionGroup> + <actionGroup ref="AssertCaptchaVisibleOnAdminLoginFormActionGroup" stepKey="assertCaptchaVisibleAfterIncorrectCaptcha" /> + + <actionGroup ref="AdminLoginWithCaptchaActionGroup" stepKey="loginAsAdminWithCorrectCaptcha"> + <argument name="username" value="{{DefaultAdminUser.username}}"/> + <argument name="password" value="{{DefaultAdminUser.password}}"/> + <argument name="captcha" value="{{PreconfiguredCaptcha.value}}"/> + </actionGroup> + <actionGroup ref="AssertAdminSuccessLoginActionGroup" stepKey="verifyAdminLoggedIn" /> + </test> +</tests> diff --git a/Test/Mftf/Test/AdminResetUserPasswordFailedTest.xml b/Test/Mftf/Test/AdminResetUserPasswordFailedTest.xml new file mode 100644 index 0000000..8f9c582 --- /dev/null +++ b/Test/Mftf/Test/AdminResetUserPasswordFailedTest.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminResetUserPasswordFailedTest"> + <before> + <magentoCLI command="config:set {{AdminCaptchaDisableConfigData.path}} {{AdminCaptchaDisableConfigData.value}} " stepKey="disableAdminCaptcha"/> + </before> + <after> + <magentoCLI command="config:set {{AdminCaptchaEnableConfigData.path}} {{AdminCaptchaEnableConfigData.value}} " stepKey="enableAdminCaptcha"/> + </after> + </test> +</tests> diff --git a/Test/Mftf/Test/CaptchaFormsDisplayingTest/CaptchaFormsDisplayingTest.xml b/Test/Mftf/Test/CaptchaFormsDisplayingTest/CaptchaFormsDisplayingTest.xml new file mode 100644 index 0000000..c6fa3d4 --- /dev/null +++ b/Test/Mftf/Test/CaptchaFormsDisplayingTest/CaptchaFormsDisplayingTest.xml @@ -0,0 +1,67 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="CaptchaFormsDisplayingTest"> + <annotations> + <features value="Captcha"/> + <stories value="MAGETWO-91552 - [github] CAPTCHA doesn't show when check out as guest"/> + <title value="Captcha forms displaying"/> + <description value="Captcha forms displaying"/> + <severity value="MAJOR"/> + <testCaseId value="MAGETWO-93941"/> + <group value="captcha"/> + </annotations> + + <!--Login as admin--> + <actionGroup ref="AdminLoginActionGroup" stepKey="LoginAsAdmin"/> + <!--Go to Captcha--> + <actionGroup ref="CaptchaFormsDisplayingActionGroup" stepKey="CaptchaFormsDisplayingActionGroup"/> + <waitForPageLoad stepKey="WaitForPageLoaded"/> + <!--Verify fields removed--> + <grabTextFrom selector="{{CaptchaFormsDisplayingSection.forms}}" stepKey="formItems"/> + <assertStringNotContainsString stepKey="checkoutAsGuest"> + <expectedResult type="string">{{CaptchaData.checkoutAsGuest}}</expectedResult> + <actualResult type="variable">$formItems</actualResult> + </assertStringNotContainsString> + <assertStringNotContainsString stepKey="register"> + <expectedResult type="string">{{CaptchaData.register}}</expectedResult> + <actualResult type="variable">$formItems</actualResult> + </assertStringNotContainsString> + <!--Verify fields existence--> + <grabTextFrom selector="{{CaptchaFormsDisplayingSection.createUser}}" stepKey="createUser"/> + <assertEquals stepKey="CreateUserFieldIsPresent"> + <expectedResult type="string">{{CaptchaData.createUser}}</expectedResult> + <actualResult type="variable">$createUser</actualResult> + </assertEquals> + <grabTextFrom selector="{{CaptchaFormsDisplayingSection.userLogin}}" stepKey="login"/> + <assertEquals stepKey="LoginFieldIsPresent"> + <expectedResult type="string">{{CaptchaData.login}}</expectedResult> + <actualResult type="variable">login</actualResult> + </assertEquals> + <grabTextFrom selector="{{CaptchaFormsDisplayingSection.forgotpassword}}" stepKey="forgotpassword"/> + <assertEquals stepKey="PasswordFieldIsPresent"> + <expectedResult type="string">{{CaptchaData.passwd}}</expectedResult> + <actualResult type="variable">$forgotpassword</actualResult> + </assertEquals> + <grabTextFrom selector="{{CaptchaFormsDisplayingSection.contactUs}}" stepKey="contactUs"/> + <assertEquals stepKey="contactUsFieldIsPresent"> + <expectedResult type="string">{{CaptchaData.contactUs}}</expectedResult> + <actualResult type="variable">$contactUs</actualResult> + </assertEquals> + <grabTextFrom selector="{{CaptchaFormsDisplayingSection.userEdit}}" stepKey="userEdit"/> + <assertEquals stepKey="userEditFieldIsPresent"> + <expectedResult type="string">{{CaptchaData.changePasswd}}</expectedResult> + <actualResult type="variable">$userEdit</actualResult> + </assertEquals> + + <!--Roll back configuration--> + <scrollToTopOfPage stepKey="ScrollToTop"/> + <click selector="{{CaptchaFormsDisplayingSection.captcha}}" stepKey="ClickToCloseCaptcha"/> + </test> +</tests> diff --git a/Test/Mftf/Test/CaptchaFormsDisplayingTest/CaptchaWithDisabledGuestCheckoutTest.xml b/Test/Mftf/Test/CaptchaFormsDisplayingTest/CaptchaWithDisabledGuestCheckoutTest.xml new file mode 100644 index 0000000..66183cb --- /dev/null +++ b/Test/Mftf/Test/CaptchaFormsDisplayingTest/CaptchaWithDisabledGuestCheckoutTest.xml @@ -0,0 +1,60 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="CaptchaWithDisabledGuestCheckoutTest"> + <annotations> + <features value="Captcha"/> + <stories value="MC-5602 - CAPTCHA doesn't appear in login popup after refreshing page."/> + <title value="Captcha is displaying on login form with disabled guest checkout"/> + <description value="Captcha is displaying on login form with disabled guest checkout"/> + <severity value="MAJOR"/> + <testCaseId value="MAGETWO-96691"/> + <group value="captcha"/> + </annotations> + <before> + <magentoCLI command="config:set checkout/options/guest_checkout 0" stepKey="disableGuestCheckout"/> + <magentoCLI command="config:set customer/captcha/failed_attempts_login 1" stepKey="decreaseLoginAttempt"/> + <createData entity="ApiCategory" stepKey="createCategory"/> + <createData entity="ApiSimpleProduct" stepKey="createSimpleProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <magentoCron stepKey="runCronIndex" groups="index"/> + </before> + <after> + <magentoCLI command="config:set checkout/options/guest_checkout 1" stepKey="enableGuestCheckout"/> + <magentoCLI command="config:set customer/captcha/failed_attempts_login 3" stepKey="increaseLoginAttempt"/> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <deleteData createDataKey="createSimpleProduct" stepKey="deleteSimpleProduct1"/> + </after> + <amOnPage url="{{StorefrontProductPage.url($$createSimpleProduct.sku$$)}}" stepKey="openProductPage"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + <actionGroup ref="StorefrontClickAddToCartOnProductPageActionGroup" stepKey="addToCartFromStorefrontProductPage"/> + <waitForText userInput="You added $$createSimpleProduct.name$$ to your shopping cart." stepKey="waitForText"/> + <actionGroup ref="StorefrontClickOnMiniCartActionGroup" stepKey="clickCart"/> + <click selector="{{StorefrontMinicartSection.goToCheckout}}" stepKey="goToCheckout"/> + <waitForElementVisible selector="{{StorefrontCustomerSignInPopupFormSection.email}}" stepKey="waitEmailFieldVisible"/> + <fillField selector="{{StorefrontCustomerSignInPopupFormSection.email}}" userInput="{{Simple_US_Customer.email}}" stepKey="fillCustomerEmail"/> + <fillField selector="{{StorefrontCustomerSignInPopupFormSection.password}}" userInput="incorrectPassword" stepKey="fillIncorrectCustomerPassword"/> + <click selector="{{StorefrontCustomerSignInPopupFormSection.signIn}}" stepKey="clickSignIn"/> + <waitForElementVisible selector="{{StorefrontCustomerSignInPopupFormSection.errorMessage}}" stepKey="seeErrorMessage"/> + <waitForElementVisible selector="{{StorefrontCustomerSignInPopupFormSection.captchaField}}" stepKey="seeCaptchaField"/> + <waitForElementVisible selector="{{StorefrontCustomerSignInPopupFormSection.captchaImg}}" stepKey="seeCaptchaImage"/> + <waitForElementVisible selector="{{StorefrontCustomerSignInPopupFormSection.captchaReload}}" stepKey="seeCaptchaReloadButton"/> + + <actionGroup ref="ReloadPageActionGroup" stepKey="refreshPage"/> + <comment userInput="Replacing reload action and preserve Backward Compatibility" stepKey="waitForPageLoad2" /> + + <actionGroup ref="StorefrontClickOnMiniCartActionGroup" stepKey="clickCart2"/> + <click selector="{{StorefrontMinicartSection.goToCheckout}}" stepKey="goToCheckout2"/> + <waitForElementVisible selector="{{StorefrontCustomerSignInPopupFormSection.email}}" stepKey="waitEmailFieldVisible2"/> + <waitForElementVisible selector="{{StorefrontCustomerSignInPopupFormSection.captchaField}}" stepKey="seeCaptchaField2"/> + <waitForElementVisible selector="{{StorefrontCustomerSignInPopupFormSection.captchaImg}}" stepKey="seeCaptchaImage2"/> + <waitForElementVisible selector="{{StorefrontCustomerSignInPopupFormSection.captchaReload}}" stepKey="seeCaptchaReloadButton2"/> + </test> +</tests> diff --git a/Test/Mftf/Test/StorefrontCaptchaEditCustomerEmailTest.xml b/Test/Mftf/Test/StorefrontCaptchaEditCustomerEmailTest.xml new file mode 100644 index 0000000..2736888 --- /dev/null +++ b/Test/Mftf/Test/StorefrontCaptchaEditCustomerEmailTest.xml @@ -0,0 +1,106 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontCaptchaEditCustomerEmailTest"> + <annotations> + <features value="Captcha"/> + <stories value="Customer Account Info Edit + Captcha"/> + <title value="Test for checking captcha on the customer account edit page."/> + <description value="Test for checking captcha on the customer account edit page and customer is locked."/> + <testCaseId value="MC-14013" /> + <severity value="MAJOR"/> + <group value="captcha"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <!-- Setup CAPTCHA for testing --> + <magentoCLI command="config:set {{StorefrontCaptchaOnCustomerChangePasswordConfigData.path}} {{StorefrontCaptchaOnCustomerChangePasswordConfigData.value}}" stepKey="enableUserEditCaptcha"/> + <magentoCLI command="config:set {{StorefrontCustomerCaptchaLength3ConfigData.path}} {{StorefrontCustomerCaptchaLength3ConfigData.value}}" stepKey="setCaptchaLength" /> + <magentoCLI command="config:set {{StorefrontCustomerCaptchaSymbols1ConfigData.path}} {{StorefrontCustomerCaptchaSymbols1ConfigData.value}}" stepKey="setCaptchaSymbols" /> + <actionGroup ref="CliCacheCleanActionGroup" stepKey="cleanInvalidatedCaches"> + <argument name="tags" value="config full_page"/> + </actionGroup> + + <createData entity="Simple_US_Customer" stepKey="customer"/> + <!-- Sign in as customer --> + <actionGroup ref="LoginToStorefrontActionGroup" stepKey="loginToStorefrontAccount"> + <argument name="Customer" value="$$customer$$"/> + </actionGroup> + </before> + <after> + <!-- Revert Captcha forms configurations --> + <magentoCLI command="config:set {{StorefrontCaptchaOnCustomerLoginConfigData.path}} {{StorefrontCaptchaOnCustomerLoginConfigData.value}},{{StorefrontCaptchaOnCustomerForgotPasswordConfigData.value}}" stepKey="enableCaptchaOnDefaultForms" /> + <magentoCLI command="config:set {{StorefrontCustomerCaptchaDefaultLengthConfigData.path}} {{StorefrontCustomerCaptchaDefaultLengthConfigData.value}}" stepKey="setDefaultCaptchaLength" /> + <magentoCLI command="config:set {{StorefrontCustomerCaptchaDefaultSymbolsConfigData.path}} {{StorefrontCustomerCaptchaDefaultSymbolsConfigData.value}}" stepKey="setDefaultCaptchaSymbols" /> + <actionGroup ref="CliCacheCleanActionGroup" stepKey="cleanInvalidatedCaches"> + <argument name="tags" value="config full_page"/> + </actionGroup> + + <deleteData createDataKey="customer" stepKey="deleteCustomer"/> + </after> + + <!-- Open Customer edit page --> + <actionGroup ref="StorefrontOpenCustomerAccountInfoEditPageActionGroup" stepKey="goToCustomerEditPage" /> + + <!-- Update email with incorrect password 3 times. --> + <actionGroup ref="StorefrontCustomerChangeEmailActionGroup" stepKey="changeEmailFirstAttempt"> + <argument name="email" value="$$customer.email$$" /> + <argument name="password" value="{{Colorado_US_Customer.password}}" /> + </actionGroup> + + <actionGroup ref="AssertMessageCustomerChangeAccountInfoActionGroup" stepKey="assertAccountMessageFirstAttempt"> + <argument name="message" value="The password doesn't match this account. Verify the password and try again." /> + <argument name="messageType" value="error" /> + </actionGroup> + + <actionGroup ref="StorefrontCustomerChangeEmailActionGroup" stepKey="changeEmailSecondAttempt"> + <argument name="email" value="$$customer.email$$" /> + <argument name="password" value="{{Colorado_US_Customer.password}}" /> + </actionGroup> + + <actionGroup ref="AssertMessageCustomerChangeAccountInfoActionGroup" stepKey="assertAccountMessageSecondAttempt"> + <argument name="message" value="The password doesn't match this account. Verify the password and try again." /> + <argument name="messageType" value="error" /> + </actionGroup> + + <actionGroup ref="StorefrontCustomerChangeEmailActionGroup" stepKey="changeEmailThirdAttempt"> + <argument name="email" value="$$customer.email$$" /> + <argument name="password" value="{{Colorado_US_Customer.password}}" /> + </actionGroup> + + <actionGroup ref="AssertMessageCustomerChangeAccountInfoActionGroup" stepKey="assertAccountMessageThirdAttempt"> + <argument name="message" value="The password doesn't match this account. Verify the password and try again." /> + <argument name="messageType" value="error" /> + </actionGroup> + + <!-- Check captcha visibility after incorrect password submit form --> + <actionGroup ref="AssertCaptchaVisibleOnCustomerAccountInfoActionGroup" stepKey="assertCaptchaVisible" /> + + <!-- Try to submit form with incorrect captcha --> + <actionGroup ref="StorefrontCustomerChangeEmailWithCaptchaActionGroup" stepKey="changeEmailWithIncorrectCaptcha"> + <argument name="email" value="$$customer.email$$" /> + <argument name="password" value="{{Colorado_US_Customer.password}}" /> + <argument name="captcha" value="{{WrongCaptcha.value}}" /> + </actionGroup> + + <actionGroup ref="AssertMessageCustomerChangeAccountInfoActionGroup" stepKey="assertAccountMessageAfterIncorrectCaptcha"> + <argument name="message" value="Incorrect CAPTCHA" /> + <argument name="messageType" value="error" /> + </actionGroup> + + <!-- Update customer email correct password and CAPTCHA --> + <actionGroup ref="StorefrontCustomerChangeEmailWithCaptchaActionGroup" stepKey="changeEmailCorrectAttempt"> + <argument name="email" value="$$customer.email$$" /> + <argument name="password" value="$$customer.password$$" /> + <argument name="captcha" value="{{PreconfiguredCaptcha.value}}" /> + </actionGroup> + <actionGroup ref="AssertMessageCustomerChangeAccountInfoActionGroup" stepKey="assertAccountMessageCorrectAttempt" /> + </test> +</tests> diff --git a/Test/Mftf/Test/StorefrontCaptchaOnContactUsTest.xml b/Test/Mftf/Test/StorefrontCaptchaOnContactUsTest.xml new file mode 100644 index 0000000..22f1ed1 --- /dev/null +++ b/Test/Mftf/Test/StorefrontCaptchaOnContactUsTest.xml @@ -0,0 +1,68 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontCaptchaOnContactUsTest"> + <annotations> + <features value="Captcha"/> + <stories value="Submit Contact us form + Captcha"/> + <title value="Captcha on contact us form test"/> + <description value="Test creation for send comment using the contact us form with captcha."/> + <testCaseId value="MC-14103" /> + <severity value="MAJOR"/> + <group value="captcha"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <magentoCLI command="config:set {{StorefrontCustomerCaptchaLength3ConfigData.path}} {{StorefrontCustomerCaptchaLength3ConfigData.value}}" stepKey="setCaptchaLength" /> + <magentoCLI command="config:set {{StorefrontCustomerCaptchaSymbols1ConfigData.path}} {{StorefrontCustomerCaptchaSymbols1ConfigData.value}}" stepKey="setCaptchaSymbols" /> + <magentoCLI command="config:set {{StorefrontCaptchaOnContactUsFormConfigData.path}} {{StorefrontCaptchaOnContactUsFormConfigData.value}}" stepKey="enableUserEditCaptcha"/> + <actionGroup ref="CliCacheCleanActionGroup" stepKey="cleanInvalidatedCaches"> + <argument name="tags" value="config full_page"/> + </actionGroup> + </before> + <after> + <magentoCLI command="config:set {{StorefrontCustomerCaptchaDefaultLengthConfigData.path}} {{StorefrontCustomerCaptchaDefaultLengthConfigData.value}}" stepKey="setDefaultCaptchaLength" /> + <magentoCLI command="config:set {{StorefrontCustomerCaptchaDefaultSymbolsConfigData.path}} {{StorefrontCustomerCaptchaDefaultSymbolsConfigData.value}}" stepKey="setDefaultCaptchaSymbols" /> + <magentoCLI command="config:set {{StorefrontCaptchaOnCustomerLoginConfigData.path}} {{StorefrontCaptchaOnCustomerLoginConfigData.value}},{{StorefrontCaptchaOnCustomerForgotPasswordConfigData.value}}" stepKey="enableCaptchaOnDefaultForms" /> + <actionGroup ref="CliCacheCleanActionGroup" stepKey="cleanInvalidatedCaches"> + <argument name="tags" value="config full_page"/> + </actionGroup> + </after> + + <!-- Open storefront contact us form --> + <actionGroup ref="StorefrontOpenContactUsPageActionGroup" stepKey="goToContactUsPage" /> + + <!-- Check Captcha items --> + <actionGroup ref="AssertCaptchaVisibleOnContactUsFormActionGroup" stepKey="seeCaptchaOnContactUsForm" /> + + <!-- Submit Contact Us form --> + <actionGroup ref="StorefrontFillContactUsFormWithCaptchaActionGroup" stepKey="fillContactUsFormWithWrongCaptcha"> + <argument name="customer" value="Simple_US_Customer" /> + <argument name="contactUsData" value="DefaultContactUsData" /> + <argument name="captcha" value="{{WrongCaptcha.value}}" /> + </actionGroup> + <actionGroup ref="StorefrontSubmitContactUsFormActionGroup" stepKey="submitContactUsFormWithWrongCaptcha" /> + + <!-- Check Captcha items after form reload --> + <actionGroup ref="AssertMessageContactUsFormActionGroup" stepKey="verifyErrorMessage"> + <argument name="message" value="Incorrect CAPTCHA" /> + <argument name="messageType" value="error" /> + </actionGroup> + <actionGroup ref="AssertCaptchaVisibleOnContactUsFormActionGroup" stepKey="seeCaptchaOnContactUsFormAfterWrongCaptcha" /> + + <actionGroup ref="StorefrontFillContactUsFormWithCaptchaActionGroup" stepKey="fillContactUsFormWithCorrectCaptcha"> + <argument name="customer" value="Simple_US_Customer" /> + <argument name="contactUsData" value="DefaultContactUsData" /> + <argument name="captcha" value="{{PreconfiguredCaptcha.value}}" /> + </actionGroup> + <actionGroup ref="StorefrontSubmitContactUsFormActionGroup" stepKey="submitContactUsFormWithCorrectCaptcha" /> + <actionGroup ref="AssertMessageContactUsFormActionGroup" stepKey="verifySuccessMessage" /> + </test> +</tests> diff --git a/Test/Mftf/Test/StorefrontCaptchaOnCustomerLoginTest.xml b/Test/Mftf/Test/StorefrontCaptchaOnCustomerLoginTest.xml new file mode 100644 index 0000000..332d7eb --- /dev/null +++ b/Test/Mftf/Test/StorefrontCaptchaOnCustomerLoginTest.xml @@ -0,0 +1,83 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontCaptchaOnCustomerLoginTest"> + <annotations> + <features value="Captcha"/> + <stories value="Login with Customer Account + Captcha"/> + <title value="Captcha customer login page test"/> + <description value="Check CAPTCHA on Storefront Login Page."/> + <severity value="MAJOR"/> + <testCaseId value="MC-14010" /> + <group value="captcha"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <magentoCLI command="config:set {{StorefrontCustomerCaptchaLength3ConfigData.path}} {{StorefrontCustomerCaptchaLength3ConfigData.value}}" stepKey="setCaptchaLength" /> + <magentoCLI command="config:set {{StorefrontCustomerCaptchaSymbols1ConfigData.path}} {{StorefrontCustomerCaptchaSymbols1ConfigData.value}}" stepKey="setCaptchaSymbols" /> + <actionGroup ref="CliCacheCleanActionGroup" stepKey="cleanInvalidatedCaches"> + <argument name="tags" value="config full_page"/> + </actionGroup> + <createData entity="Simple_US_Customer" stepKey="customer"/> + </before> + <after> + <magentoCLI command="config:set {{StorefrontCustomerCaptchaDefaultLengthConfigData.path}} {{StorefrontCustomerCaptchaDefaultLengthConfigData.value}}" stepKey="setDefaultCaptchaLength" /> + <magentoCLI command="config:set {{StorefrontCustomerCaptchaDefaultSymbolsConfigData.path}} {{StorefrontCustomerCaptchaDefaultSymbolsConfigData.value}}" stepKey="setDefaultCaptchaSymbols" /> + <actionGroup ref="CliCacheCleanActionGroup" stepKey="cleanInvalidatedCaches"> + <argument name="tags" value="config full_page"/> + </actionGroup> + <deleteData createDataKey="customer" stepKey="deleteCustomer"/> + </after> + + <!-- Open storefront login form --> + <actionGroup ref="StorefrontOpenCustomerLoginPageActionGroup" stepKey="goToSignInPage" /> + + <!-- Login with wrong credentials 3 times --> + <actionGroup ref="StorefrontFillCustomerLoginFormActionGroup" stepKey="fillLoginFormFirstAttempt"> + <argument name="customer" value="Colorado_US_Customer" /> + </actionGroup> + <actionGroup ref="StorefrontClickSignOnCustomerLoginFormActionGroup" stepKey="clickSignInAccountButtonFirstAttempt" /> + <actionGroup ref="AssertMessageCustomerLoginActionGroup" stepKey="seeErrorMessageAfterFirstAttempt" /> + <actionGroup ref="AssertCaptchaNotVisibleOnCustomerLoginFormActionGroup" stepKey="dontSeeCaptchaAfterFirstAttempt" /> + + <actionGroup ref="StorefrontFillCustomerLoginFormActionGroup" stepKey="fillLoginFormSecondAttempt"> + <argument name="customer" value="Colorado_US_Customer" /> + </actionGroup> + <actionGroup ref="StorefrontClickSignOnCustomerLoginFormActionGroup" stepKey="clickSignInAccountButtonSecondAttempt" /> + <actionGroup ref="AssertMessageCustomerLoginActionGroup" stepKey="seeErrorMessageAfterSecondAttempt" /> + <actionGroup ref="AssertCaptchaNotVisibleOnCustomerLoginFormActionGroup" stepKey="dontSeeCaptchaAfterSecondAttempt" /> + + <actionGroup ref="StorefrontFillCustomerLoginFormActionGroup" stepKey="fillLoginFormThirdAttempt"> + <argument name="customer" value="Colorado_US_Customer" /> + </actionGroup> + <actionGroup ref="StorefrontClickSignOnCustomerLoginFormActionGroup" stepKey="clickSignInAccountButtonThirdAttempt" /> + <actionGroup ref="AssertMessageCustomerLoginActionGroup" stepKey="seeErrorMessageAfterThirdAttempt" /> + <actionGroup ref="AssertCaptchaVisibleOnCustomerLoginFormActionGroup" stepKey="seeCaptchaAfterThirdAttempt" /> + + <!-- Submit form with incorrect captcha --> + <actionGroup ref="StorefrontFillCustomerLoginFormWithCaptchaActionGroup" stepKey="fillLoginFormCorrectAccountIncorrectCaptcha"> + <argument name="customer" value="$$customer$$" /> + <argument name="captcha" value="{{WrongCaptcha.value}}" /> + </actionGroup> + <actionGroup ref="StorefrontClickSignOnCustomerLoginFormActionGroup" stepKey="clickSignInAccountButtonCorrectAccountIncorrectCaptcha" /> + <actionGroup ref="AssertMessageCustomerLoginActionGroup" stepKey="seeErrorMessageAfterIncorrectCaptcha"> + <argument name="message" value="Incorrect CAPTCHA" /> + </actionGroup> + + <actionGroup ref="StorefrontFillCustomerLoginFormWithCaptchaActionGroup" stepKey="fillLoginFormCorrectAccountCorrectCaptcha"> + <argument name="customer" value="$$customer$$" /> + <argument name="captcha" value="{{PreconfiguredCaptcha.value}}" /> + </actionGroup> + <actionGroup ref="StorefrontClickSignOnCustomerLoginFormActionGroup" stepKey="clickSignInAccountButtonCorrectAccountCorrectCaptcha" /> + <actionGroup ref="AssertCustomerWelcomeMessageActionGroup" stepKey="assertCustomerLoggedIn"> + <argument name="customerFullName" value="$$customer.firstname$$ $$customer.lastname$$" /> + </actionGroup> + </test> +</tests> diff --git a/Test/Mftf/Test/StorefrontCaptchaRegisterNewCustomerTest.xml b/Test/Mftf/Test/StorefrontCaptchaRegisterNewCustomerTest.xml new file mode 100644 index 0000000..b7d5b60 --- /dev/null +++ b/Test/Mftf/Test/StorefrontCaptchaRegisterNewCustomerTest.xml @@ -0,0 +1,72 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontCaptchaRegisterNewCustomerTest"> + <annotations> + <features value="Captcha"/> + <stories value="Create New Customer Account + Captcha"/> + <title value="Test creation for customer register with captcha on storefront."/> + <description value="Test creation for customer register with captcha on storefront."/> + <severity value="MAJOR"/> + <testCaseId value="MC-14805" /> + <group value="captcha"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <!-- Enable captcha for customer. --> + <magentoCLI command="config:set {{StorefrontCaptchaOnCustomerCreateFormConfigData.path}} {{StorefrontCaptchaOnCustomerCreateFormConfigData.value}}" stepKey="enableUserRegistrationCaptcha" /> + <magentoCLI command="config:set {{StorefrontCustomerCaptchaModeAlwaysConfigData.path}} {{StorefrontCustomerCaptchaModeAlwaysConfigData.value}}" stepKey="alwaysEnableCaptcha" /> + <magentoCLI command="config:set {{StorefrontCustomerCaptchaLength3ConfigData.path}} {{StorefrontCustomerCaptchaLength3ConfigData.value}}" stepKey="setCaptchaLength" /> + <magentoCLI command="config:set {{StorefrontCustomerCaptchaSymbols1ConfigData.path}} {{StorefrontCustomerCaptchaSymbols1ConfigData.value}}" stepKey="setCaptchaSymbols" /> + <actionGroup ref="CliCacheCleanActionGroup" stepKey="cleanInvalidatedCaches"> + <argument name="tags" value="config full_page"/> + </actionGroup> + </before> + <after> + <!-- Set default configuration. --> + <magentoCLI command="config:set {{StorefrontCaptchaOnCustomerLoginConfigData.path}} {{StorefrontCaptchaOnCustomerLoginConfigData.value}},{{StorefrontCaptchaOnCustomerForgotPasswordConfigData.value}}" stepKey="enableCaptchaOnDefaultForms" /> + <magentoCLI command="config:set {{StorefrontCustomerCaptchaModeAfterFailConfigData.path}} {{StorefrontCustomerCaptchaModeAfterFailConfigData.value}}" stepKey="defaultCaptchaMode" /> + <magentoCLI command="config:set {{StorefrontCustomerCaptchaDefaultLengthConfigData.path}} {{StorefrontCustomerCaptchaDefaultLengthConfigData.value}}" stepKey="setDefaultCaptchaLength" /> + <magentoCLI command="config:set {{StorefrontCustomerCaptchaDefaultSymbolsConfigData.path}} {{StorefrontCustomerCaptchaDefaultSymbolsConfigData.value}}" stepKey="setDefaultCaptchaSymbols" /> + <actionGroup ref="CliCacheCleanActionGroup" stepKey="cleanInvalidatedCaches"> + <argument name="tags" value="config full_page"/> + </actionGroup> + </after> + + <!-- Open Customer registration page --> + <actionGroup ref="StorefrontOpenCustomerAccountCreatePageActionGroup" stepKey="goToCustomerAccountCreatePage" /> + + <!-- Check captcha visibility registration page load --> + <actionGroup ref="AssertCaptchaVisibleOnCustomerAccountCreatePageActionGroup" stepKey="verifyCaptchaVisible" /> + + <!-- Submit form with incorrect captcha --> + <actionGroup ref="StorefrontFillCustomerAccountCreationFormWithCaptchaActionGroup" stepKey="fillNewCustomerAccountFormWithIncorrectCaptcha"> + <argument name="customer" value="Simple_US_Customer" /> + <argument name="captcha" value="{{WrongCaptcha.value}}" /> + </actionGroup> + + <actionGroup ref="StorefrontClickCreateAnAccountCustomerAccountCreationFormActionGroup" stepKey="clickCreateAnAccountButton" /> + + <actionGroup ref="AssertMessageCustomerCreateAccountActionGroup" stepKey="assertMessage"> + <argument name="message" value="Incorrect CAPTCHA" /> + <argument name="messageType" value="error" /> + </actionGroup> + + <actionGroup ref="AssertCaptchaVisibleOnCustomerAccountCreatePageActionGroup" stepKey="verifyCaptchaVisibleAfterFail" /> + + <!-- Submit form with correct captcha --> + <actionGroup ref="StorefrontFillCustomerAccountCreationFormWithCaptchaActionGroup" stepKey="fillNewCustomerAccountFormWithCorrectCaptcha"> + <argument name="customer" value="Simple_US_Customer" /> + <argument name="captcha" value="{{PreconfiguredCaptcha.value}}" /> + </actionGroup> + <actionGroup ref="StorefrontClickCreateAnAccountCustomerAccountCreationFormActionGroup" stepKey="clickCreateAnAccountButtonAfterCorrectCaptcha" /> + <actionGroup ref="AssertMessageCustomerCreateAccountActionGroup" stepKey="assertSuccessMessage" /> + </test> +</tests> diff --git a/Test/Mftf/Test/StorefrontResetCustomerPasswordFailedTest.xml b/Test/Mftf/Test/StorefrontResetCustomerPasswordFailedTest.xml new file mode 100644 index 0000000..36d7989 --- /dev/null +++ b/Test/Mftf/Test/StorefrontResetCustomerPasswordFailedTest.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontResetCustomerPasswordFailedTest"> + <before> + <magentoCLI command="config:set {{StorefrontCustomerCaptchaDisableConfigData.path}} {{StorefrontCustomerCaptchaDisableConfigData.value}}" stepKey="disableCaptcha"/> + </before> + <after> + <magentoCLI command="config:set {{StorefrontCustomerCaptchaEnableConfigData.path}} {{StorefrontCustomerCaptchaEnableConfigData.value}}" stepKey="enableCaptcha"/> + </after> + </test> +</tests> diff --git a/Test/Unit/Controller/Refresh/IndexTest.php b/Test/Unit/Controller/Refresh/IndexTest.php new file mode 100644 index 0000000..af3e903 --- /dev/null +++ b/Test/Unit/Controller/Refresh/IndexTest.php @@ -0,0 +1,153 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Captcha\Test\Unit\Controller\Refresh; + +use Magento\Captcha\Controller\Refresh\Index; +use Magento\Captcha\Helper\Data as CaptchaHelper; +use Magento\Captcha\Model\CaptchaInterface; +use Magento\Framework\App\Action\Context; +use Magento\Framework\App\RequestInterface; +use Magento\Framework\Controller\Result\Json as ResultJson; +use Magento\Framework\Controller\Result\JsonFactory as ResultJsonFactory; +use Magento\Framework\Serialize\Serializer\Json as JsonSerializer; +use Magento\Framework\View\Element\BlockInterface; +use Magento\Framework\View\LayoutInterface; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +class IndexTest extends TestCase +{ + private const STUB_FORM_ID = 'StubFormId'; + private const STUB_CAPTCHA_SOURCE = '/stub-captcha-source.jpg'; + + /** @var MockObject|RequestInterface */ + private $requestMock; + + /** @var MockObject|ResultJsonFactory */ + private $jsonResultFactoryMock; + + /** @var MockObject|ResultJson */ + private $jsonResultMock; + + /** @var MockObject|CaptchaHelper */ + private $captchaHelperMock; + + /** @var MockObject|LayoutInterface */ + private $layoutMock; + + /** @var MockObject|BlockInterface */ + private $blockMock; + + /** @var MockObject|JsonSerializer */ + private $jsonSerializerMock; + + /** @var MockObject|Context */ + private $contextMock; + + /** @var Index */ + private $refreshAction; + + protected function setUp(): void + { + $this->requestMock = $this->getMockBuilder(RequestInterface::class) + ->setMethods(['getPost', 'getContent']) + ->getMockForAbstractClass(); + $this->layoutMock = $this->getMockBuilder(LayoutInterface::class) + ->setMethods(['createBlock']) + ->getMockForAbstractClass(); + $this->blockMock = $this->getMockBuilder(BlockInterface::class) + ->setMethods(['setFormId', 'setIsAjax', 'toHtml']) + ->getMockForAbstractClass(); + $this->jsonResultFactoryMock = $this->createMock(ResultJsonFactory::class); + $this->jsonResultMock = $this->createMock(ResultJson::class); + $this->jsonResultFactoryMock->method('create') + ->willReturn($this->jsonResultMock); + $this->jsonSerializerMock = $this->createMock(JsonSerializer::class); + $this->captchaHelperMock = $this->createMock(CaptchaHelper::class); + + $this->contextMock = $this->createMock(Context::class); + + $this->blockMock->method('setIsAjax') + ->willReturnSelf(); + + $this->layoutMock->method('createBlock') + ->willReturn($this->blockMock); + + $this->refreshAction = new Index( + $this->contextMock, + $this->requestMock, + $this->jsonResultFactoryMock, + $this->captchaHelperMock, + $this->layoutMock, + $this->jsonSerializerMock + ); + } + + public function testCaptchaGeneratedWhenPostDataContainsFormId() + { + // Given + $this->requestMock->method('getPost') + ->with('formId') + ->willReturn(self::STUB_FORM_ID); + $this->blockMock->method('setFormId') + ->willReturnSelf(); + + // Expect + $this->requestMock->expects($this->never()) + ->method('getContent'); + $this->captchaHelperMock->expects($this->once()) + ->method('getCaptcha') + ->with(self::STUB_FORM_ID) + ->willReturn( + $this->getCaptchaModelMock(self::STUB_CAPTCHA_SOURCE) + ); + + // When + $this->refreshAction->execute(); + } + + public function testCaptchaFallsBackToRequestContentIfPostMissing() + { + // Given + $this->requestMock->method('getPost') + ->with('formId') + ->willReturn(null); + $this->blockMock->method('setFormId') + ->willReturnSelf(); + + // Expect + $this->requestMock->expects(self::once()) + ->method('getContent') + ->willReturn(null); + $this->captchaHelperMock->expects($this->once()) + ->method('getCaptcha') + ->with(null) + ->willReturn( + $this->getCaptchaModelMock(self::STUB_CAPTCHA_SOURCE) + ); + + // When + $this->refreshAction->execute(); + } + + /** + * @param string $imageSource + * @return MockObject|CaptchaInterface + */ + private function getCaptchaModelMock(string $imageSource): CaptchaInterface + { + $modelMock = $this->getMockBuilder(CaptchaInterface::class) + ->setMethods(['generate', 'getBlockName', 'getImgSrc']) + ->getMockForAbstractClass(); + + $modelMock->method('getImgSrc') + ->willReturn($imageSource); + + return $modelMock; + } +} diff --git a/Test/Unit/Cron/DeleteExpiredImagesTest.php b/Test/Unit/Cron/DeleteExpiredImagesTest.php new file mode 100644 index 0000000..2e851fd --- /dev/null +++ b/Test/Unit/Cron/DeleteExpiredImagesTest.php @@ -0,0 +1,171 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Captcha\Test\Unit\Cron; + +use Magento\Captcha\Cron\DeleteExpiredImages; +use Magento\Captcha\Helper\Data; +use Magento\Framework\Filesystem; +use Magento\Framework\Filesystem\Directory\Write; +use Magento\Framework\Filesystem\Directory\WriteInterface; +use Magento\Store\Model\Store; +use Magento\Store\Model\StoreManager; +use Magento\Store\Model\Website; +use PHPUnit\Framework\Constraint\IsIdentical; +use PHPUnit\Framework\Constraint\IsNull; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +class DeleteExpiredImagesTest extends TestCase +{ + /** + * CAPTCHA helper + * + * @var Data|MockObject + */ + protected $_helper; + + /** + * CAPTCHA helper + * + * @var \Magento\Captcha\Helper\Adminhtml\Data|MockObject + */ + protected $_adminHelper; + + /** + * @var Filesystem|MockObject + */ + protected $_filesystem; + + /** + * @var StoreManager|MockObject + */ + protected $_storeManager; + + /** + * @var DeleteExpiredImages + */ + protected $_deleteExpiredImages; + + /** + * @var WriteInterface|MockObject + */ + protected $_directory; + + /** + * @var int + */ + public static $currentTime; + + /** + * Create mocks and model + */ + protected function setUp(): void + { + $this->_helper = $this->createMock(Data::class); + $this->_adminHelper = $this->createMock(\Magento\Captcha\Helper\Adminhtml\Data::class); + $this->_filesystem = $this->createMock(Filesystem::class); + $this->_directory = $this->createMock(Write::class); + $this->_storeManager = $this->createMock(StoreManager::class); + + $this->_filesystem->expects( + $this->once() + )->method( + 'getDirectoryWrite' + )->willReturn( + $this->_directory + ); + + $this->_deleteExpiredImages = new DeleteExpiredImages( + $this->_helper, + $this->_adminHelper, + $this->_filesystem, + $this->_storeManager + ); + } + + /** + * @dataProvider getExpiredImages + */ + public function testDeleteExpiredImages($website, $isFile, $filename, $mTime, $timeout) + { + $this->_storeManager->expects( + $this->once() + )->method( + 'getWebsites' + )->willReturn( + isset($website) ? [$website] : [] + ); + if (isset($website)) { + $this->_helper->expects( + $this->once() + )->method( + 'getConfig' + )->with( + 'timeout', + new IsIdentical($website->getDefaultStore()) + )->willReturn( + $timeout + ); + } else { + $this->_helper->expects($this->never())->method('getConfig'); + } + $this->_adminHelper->expects( + $this->once() + )->method( + 'getConfig' + )->with( + 'timeout', + new IsNull() + )->willReturn( + $timeout + ); + + $timesToCall = isset($website) ? 2 : 1; + $this->_directory->expects( + $this->exactly($timesToCall) + )->method( + 'read' + )->willReturn( + [$filename] + ); + $this->_directory->expects($this->exactly($timesToCall))->method('isFile')->willReturn($isFile); + $this->_directory->expects($this->any())->method('stat')->willReturn(['mtime' => $mTime]); + + $this->_deleteExpiredImages->execute(); + } + + /** + * @return array + */ + public function getExpiredImages() + { + $website = $this->createPartialMock(Website::class, ['__wakeup', 'getDefaultStore']); + $store = $this->createPartialMock(Store::class, ['__wakeup']); + $website->expects($this->any())->method('getDefaultStore')->willReturn($store); + $time = time(); + return [ + [null, true, 'test.png', 50, ($time - 60) / 60, true], + [$website, false, 'test.png', 50, ($time - 60) / 60, false], + [$website, true, 'test.jpg', 50, ($time - 60) / 60, false], + [$website, true, 'test.png', 50, ($time - 20) / 60, false] + ]; + } +} + +/** + * Fix current time + * + * @return int + */ +function time() +{ + if (!isset(DeleteExpiredImagesTest::$currentTime)) { + DeleteExpiredImagesTest::$currentTime = \time(); + } + return DeleteExpiredImagesTest::$currentTime; +} diff --git a/Test/Unit/CustomerData/CaptchaTest.php b/Test/Unit/CustomerData/CaptchaTest.php new file mode 100644 index 0000000..5563ca0 --- /dev/null +++ b/Test/Unit/CustomerData/CaptchaTest.php @@ -0,0 +1,99 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Captcha\Test\Unit\CustomerData; + +use Magento\Captcha\CustomerData\Captcha; +use Magento\Captcha\Helper\Data as CaptchaHelper; +use Magento\Captcha\Model\DefaultModel; +use Magento\Customer\Api\Data\CustomerInterface as CustomerData; +use Magento\Customer\Model\Session as CustomerSession; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +class CaptchaTest extends TestCase +{ + /** + * @var CaptchaHelper|MockObject + */ + private $helperMock; + + /** + * @var CustomerSession|MockObject + */ + private $customerSessionMock; + + /** + * @var Captcha + */ + private $model; + + /** + * @var array + */ + private $formIds; + + /** + * @var ObjectManagerHelper + */ + protected $objectManagerHelper; + + /** + * Create mocks and model + */ + protected function setUp(): void + { + $this->helperMock = $this->createMock(CaptchaHelper::class); + $this->customerSessionMock = $this->createMock(CustomerSession::class); + $this->formIds = [ + 'user_login' + ]; + $this->objectManagerHelper = new ObjectManagerHelper($this); + $this->model = $this->objectManagerHelper->getObject( + Captcha::class, + [ + 'helper' => $this->helperMock, + 'formIds' => $this->formIds, + 'customerSession' => $this->customerSessionMock + ] + ); + } + + /** + * Test getSectionData() when user is login and require captcha + */ + public function testGetSectionDataWhenLoginAndRequireCaptcha() + { + $emailLogin = 'test@localhost.com'; + + $userLoginModel = $this->createMock(DefaultModel::class); + $userLoginModel->expects($this->any())->method('isRequired')->with($emailLogin) + ->willReturn(true); + $this->helperMock->expects($this->any())->method('getCaptcha')->with('user_login')->willReturn($userLoginModel); + + $this->customerSessionMock->expects($this->any())->method('isLoggedIn') + ->willReturn(true); + + $customerDataMock = $this->createMock(CustomerData::class); + $customerDataMock->expects($this->any())->method('getEmail')->willReturn($emailLogin); + $this->customerSessionMock->expects($this->any())->method('getCustomerData') + ->willReturn($customerDataMock); + + /* Assert to test */ + $this->assertEquals( + [ + "user_login" => [ + "isRequired" => true, + "timestamp" => time() + ] + ], + $this->model->getSectionData() + ); + } +} diff --git a/Test/Unit/Helper/Adminhtml/DataTest.php b/Test/Unit/Helper/Adminhtml/DataTest.php new file mode 100644 index 0000000..0f21c0e --- /dev/null +++ b/Test/Unit/Helper/Adminhtml/DataTest.php @@ -0,0 +1,64 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Captcha\Test\Unit\Helper\Adminhtml; + +use Magento\Captcha\Helper\Adminhtml\Data; +use Magento\Framework\Filesystem\Directory\Write; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +class DataTest extends TestCase +{ + /** + * @var Data|MockObject + */ + protected $_model; + + /** + * setUp + */ + protected function setUp(): void + { + $objectManagerHelper = new ObjectManager($this); + $className = Data::class; + $arguments = $objectManagerHelper->getConstructArguments($className); + + $backendConfig = $arguments['backendConfig']; + $backendConfig->expects( + $this->any() + )->method( + 'getValue' + )->with( + 'admin/captcha/qwe' + )->willReturn( + '1' + ); + + $filesystemMock = $arguments['filesystem']; + $directoryMock = $this->createMock(Write::class); + + $filesystemMock->expects($this->any())->method('getDirectoryWrite')->willReturn($directoryMock); + $directoryMock->expects($this->any())->method('getAbsolutePath')->willReturnArgument(0); + + $this->_model = $objectManagerHelper->getObject($className, $arguments); + } + + public function testGetConfig() + { + $this->assertEquals('1', $this->_model->getConfig('qwe')); + } + + /** + * @covers \Magento\Captcha\Helper\Adminhtml\Data::_getWebsiteCode + */ + public function testGetWebsiteId() + { + $this->assertStringEndsWith('/admin/', $this->_model->getImgDir()); + } +} diff --git a/Test/Unit/Helper/DataTest.php b/Test/Unit/Helper/DataTest.php new file mode 100644 index 0000000..4b9286f --- /dev/null +++ b/Test/Unit/Helper/DataTest.php @@ -0,0 +1,230 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Captcha\Test\Unit\Helper; + +use Magento\Captcha\Helper\Data; +use Magento\Captcha\Model\CaptchaFactory; +use Magento\Captcha\Model\DefaultModel; +use Magento\Captcha\Model\ResourceModel\LogFactory; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\App\Filesystem\DirectoryList; +use Magento\Framework\App\Helper\Context; +use Magento\Framework\Filesystem\Directory\Read; +use Magento\Framework\Filesystem\Directory\Write; +use Magento\Framework\Session\SessionManager; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\Store\Model\ScopeInterface; +use Magento\Store\Model\Store; +use Magento\Store\Model\Website; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class DataTest extends TestCase +{ + /** + * @var MockObject + */ + protected $_filesystem; + + /** + * @var Data + */ + protected $helper; + + /** + * @var ScopeConfigInterface|MockObject + */ + protected $configMock; + + /** + * @var CaptchaFactory|MockObject + */ + protected $factoryMock; + + protected function setUp(): void + { + $objectManagerHelper = new ObjectManager($this); + $className = Data::class; + $arguments = $objectManagerHelper->getConstructArguments($className); + /** @var Context $context */ + $context = $arguments['context']; + $this->configMock = $context->getScopeConfig(); + $this->_filesystem = $arguments['filesystem']; + $storeManager = $arguments['storeManager']; + $storeManager->expects($this->any())->method('getWebsite')->willReturn($this->_getWebsiteStub()); + $storeManager->expects($this->any())->method('getStore')->willReturn($this->_getStoreStub()); + $this->factoryMock = $arguments['factory']; + $this->helper = $objectManagerHelper->getObject($className, $arguments); + } + + /** + * @covers \Magento\Captcha\Helper\Data::getCaptcha + */ + public function testGetCaptcha() + { + $this->configMock->expects( + $this->once() + )->method( + 'getValue' + )->with( + 'customer/captcha/type' + )->willReturn( + 'zend' + ); + + $this->factoryMock->expects( + $this->once() + )->method( + 'create' + )->with( + 'Zend' + )->willReturn( + new DefaultModel( + $this->createMock(SessionManager::class), + $this->createMock(Data::class), + $this->createPartialMock(LogFactory::class, ['create']), + 'user_create' + ) + ); + + $this->assertInstanceOf(DefaultModel::class, $this->helper->getCaptcha('user_create')); + } + + /** + * @covers \Magento\Captcha\Helper\Data::getConfig + */ + public function testGetConfigNode() + { + $this->configMock->expects( + $this->once() + )->method( + 'getValue' + )->with( + 'customer/captcha/enable', + ScopeInterface::SCOPE_STORE + )->willReturn( + '1' + ); + + $this->helper->getConfig('enable'); + } + + public function testGetFonts() + { + $fontPath = 'path/to/fixture.ttf'; + $expectedFontPath = 'lib/' . $fontPath; + + $libDirMock = $this->createMock(Read::class); + $libDirMock->expects($this->once()) + ->method('getAbsolutePath') + ->with($fontPath) + ->willReturn($expectedFontPath); + $this->_filesystem->expects($this->once()) + ->method('getDirectoryRead') + ->with(DirectoryList::LIB_INTERNAL) + ->willReturn($libDirMock); + + $configData = ['font_code' => ['label' => 'Label', 'path' => $fontPath]]; + + $this->configMock->expects( + $this->any() + )->method( + 'getValue' + )->with( + 'captcha/fonts', + 'default' + )->willReturn( + $configData + ); + + $fonts = $this->helper->getFonts(); + $this->assertArrayHasKey('font_code', $fonts); + // fixture + $this->assertArrayHasKey('label', $fonts['font_code']); + $this->assertArrayHasKey('path', $fonts['font_code']); + $this->assertEquals('Label', $fonts['font_code']['label']); + $this->assertEquals($expectedFontPath, $fonts['font_code']['path']); + } + + /** + * @covers \Magento\Captcha\Model\DefaultModel::getImgDir + * @covers \Magento\Captcha\Helper\Data::getImgDir + */ + public function testGetImgDir() + { + $dirWriteMock = $this->createPartialMock( + Write::class, + ['changePermissions', 'create', 'getAbsolutePath'] + ); + + $this->_filesystem->expects( + $this->once() + )->method( + 'getDirectoryWrite' + )->with( + DirectoryList::MEDIA + )->willReturn( + $dirWriteMock + ); + + $dirWriteMock->expects( + $this->once() + )->method( + 'getAbsolutePath' + )->with( + '/captcha/base' + )->willReturn( + TESTS_TEMP_DIR . '/captcha/base' + ); + + $this->assertFileDoesNotExist(TESTS_TEMP_DIR . '/captcha'); + $result = $this->helper->getImgDir(); + $this->assertStringStartsWith(TESTS_TEMP_DIR, $result); + $this->assertStringEndsWith('captcha/base/', $result); + } + + /** + * @covers \Magento\Captcha\Model\DefaultModel::getImgUrl + * @covers \Magento\Captcha\Helper\Data::getImgUrl + */ + public function testGetImgUrl() + { + $this->assertEquals($this->helper->getImgUrl(), 'http://localhost/media/captcha/base/'); + } + + /** + * Create Website Stub + * + * @return \Magento\Store\Model\Website + */ + protected function _getWebsiteStub() + { + $website = $this->createPartialMock(Website::class, ['getCode', '__wakeup']); + + $website->expects($this->any())->method('getCode')->willReturn('base'); + + return $website; + } + + /** + * Create store stub + * + * @return Store + */ + protected function _getStoreStub() + { + $store = $this->createMock(Store::class); + + $store->expects($this->any())->method('getBaseUrl')->willReturn('http://localhost/media/'); + + return $store; + } +} diff --git a/Test/Unit/Model/CaptchaFactoryTest.php b/Test/Unit/Model/CaptchaFactoryTest.php new file mode 100644 index 0000000..a92f03b --- /dev/null +++ b/Test/Unit/Model/CaptchaFactoryTest.php @@ -0,0 +1,71 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Captcha\Test\Unit\Model; + +use Magento\Captcha\Model\CaptchaFactory; +use Magento\Captcha\Model\DefaultModel; +use Magento\Framework\ObjectManagerInterface; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +class CaptchaFactoryTest extends TestCase +{ + /**@var MockObject */ + protected $_objectManagerMock; + + /** @var CaptchaFactory */ + protected $_model; + + protected function setUp(): void + { + $this->_objectManagerMock = $this->getMockForAbstractClass(ObjectManagerInterface::class); + $this->_model = new CaptchaFactory($this->_objectManagerMock); + } + + public function testCreatePositive() + { + $captchaType = 'default'; + + $defaultCaptchaMock = $this->createMock(DefaultModel::class); + + $this->_objectManagerMock->expects( + $this->once() + )->method( + 'create' + )->with( + $this->equalTo('Magento\Captcha\Model\\' . ucfirst($captchaType)) + )->willReturn( + $defaultCaptchaMock + ); + + $this->assertEquals($defaultCaptchaMock, $this->_model->create($captchaType, 'form_id')); + } + + public function testCreateNegative() + { + $captchaType = 'wrong_instance'; + + $defaultCaptchaMock = $this->createMock(\stdClass::class); + + $this->_objectManagerMock->expects( + $this->once() + )->method( + 'create' + )->with( + $this->equalTo('Magento\Captcha\Model\\' . ucfirst($captchaType)) + )->willReturn( + $defaultCaptchaMock + ); + + $this->expectException('InvalidArgumentException'); + $this->expectExceptionMessage('Magento\Captcha\Model\\' . ucfirst($captchaType) . + ' does not implement \Magento\Captcha\Model\CaptchaInterface'); + + $this->assertEquals($defaultCaptchaMock, $this->_model->create($captchaType, 'form_id')); + } +} diff --git a/Test/Unit/Model/Cart/ConfigPluginTest.php b/Test/Unit/Model/Cart/ConfigPluginTest.php new file mode 100644 index 0000000..aaf7693 --- /dev/null +++ b/Test/Unit/Model/Cart/ConfigPluginTest.php @@ -0,0 +1,54 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Captcha\Test\Unit\Model\Cart; + +use Magento\Captcha\Model\Cart\ConfigPlugin; +use Magento\Captcha\Model\Checkout\ConfigProvider; +use Magento\Checkout\Block\Cart\Sidebar; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +class ConfigPluginTest extends TestCase +{ + /** + * @var ConfigPlugin + */ + protected $model; + + /** + * @var MockObject + */ + protected $configProviderMock; + + protected function setUp(): void + { + $this->configProviderMock = $this->createMock(ConfigProvider::class); + $this->model = new ConfigPlugin( + $this->configProviderMock + ); + } + + public function testAfterGetConfig() + { + $resultMock = [ + 'result' => [ + 'data' => 'resultDataMock' + ] + ]; + $configMock = [ + 'config' => [ + 'data' => 'configDataMock' + ] + ]; + $expectedResult = array_merge_recursive($resultMock, $configMock); + $sidebarMock = $this->createMock(Sidebar::class); + $this->configProviderMock->expects($this->once())->method('getConfig')->willReturn($configMock); + + $this->assertEquals($expectedResult, $this->model->afterGetConfig($sidebarMock, $resultMock)); + } +} diff --git a/Test/Unit/Model/Checkout/ConfigProviderTest.php b/Test/Unit/Model/Checkout/ConfigProviderTest.php new file mode 100644 index 0000000..17a2783 --- /dev/null +++ b/Test/Unit/Model/Checkout/ConfigProviderTest.php @@ -0,0 +1,132 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Captcha\Test\Unit\Model\Checkout; + +use Magento\Captcha\Helper\Data; +use Magento\Captcha\Model\Checkout\ConfigProvider; +use Magento\Captcha\Model\DefaultModel; +use Magento\Store\Model\Store; +use Magento\Store\Model\StoreManagerInterface; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +class ConfigProviderTest extends TestCase +{ + /** + * @var MockObject + */ + protected $storeManagerMock; + + /** + * @var MockObject + */ + protected $captchaHelperMock; + + /** + * @var MockObject + */ + protected $captchaMock; + + /** + * @var MockObject + */ + protected $storeMock; + + /** + * @var integer + */ + protected $formId = 1; + + /** + * @var ConfigProvider + */ + protected $model; + + protected function setUp(): void + { + $this->storeManagerMock = $this->getMockForAbstractClass(StoreManagerInterface::class); + $this->captchaHelperMock = $this->createMock(Data::class); + $this->captchaMock = $this->createMock(DefaultModel::class); + $this->storeMock = $this->createMock(Store::class); + $formIds = [$this->formId]; + + $this->model = new ConfigProvider( + $this->storeManagerMock, + $this->captchaHelperMock, + $formIds + ); + } + + /** + * @dataProvider getConfigDataProvider + * @param bool $isRequired + * @param integer $captchaGenerations + * @param array $expectedConfig + */ + public function testGetConfig($isRequired, $captchaGenerations, $expectedConfig) + { + $this->captchaHelperMock->expects($this->any())->method('getCaptcha')->with($this->formId) + ->willReturn($this->captchaMock); + + $this->captchaMock->expects($this->any())->method('isCaseSensitive')->willReturn(1); + $this->captchaMock->expects($this->any())->method('getHeight')->willReturn('12px'); + $this->captchaMock->expects($this->any())->method('isRequired')->willReturn($isRequired); + + $this->captchaMock->expects($this->exactly($captchaGenerations))->method('generate'); + $this->captchaMock->expects($this->exactly($captchaGenerations))->method('getImgSrc') + ->willReturn('source'); + + $this->storeManagerMock->expects($this->any())->method('getStore')->willReturn($this->storeMock); + $this->storeMock->expects($this->once())->method('isCurrentlySecure')->willReturn(true); + $this->storeMock->expects($this->once())->method('getUrl')->with('captcha/refresh', ['_secure' => true]) + ->willReturn('https://magento.com/captcha'); + + $config = $this->model->getConfig(); + unset($config['captcha'][$this->formId]['timestamp']); + $this->assertEquals($config, $expectedConfig); + } + + /** + * @return array + */ + public function getConfigDataProvider() + { + return [ + [ + 'isRequired' => true, + 'captchaGenerations' => 1, + 'expectedConfig' => [ + 'captcha' => [ + $this->formId => [ + 'isCaseSensitive' => true, + 'imageHeight' => '12px', + 'imageSrc' => 'source', + 'refreshUrl' => 'https://magento.com/captcha', + 'isRequired' => true + ], + ], + ], + ], + [ + 'isRequired' => false, + 'captchaGenerations' => 0, + 'expectedConfig' => [ + 'captcha' => [ + $this->formId => [ + 'isCaseSensitive' => true, + 'imageHeight' => '12px', + 'imageSrc' => '', + 'refreshUrl' => 'https://magento.com/captcha', + 'isRequired' => false + ], + ], + ], + ], + ]; + } +} diff --git a/Test/Unit/Model/Config/FontTest.php b/Test/Unit/Model/Config/FontTest.php new file mode 100644 index 0000000..6938167 --- /dev/null +++ b/Test/Unit/Model/Config/FontTest.php @@ -0,0 +1,102 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Captcha\Test\Unit\Model\Config; + +use Magento\Captcha\Helper\Data as HelperData; +use Magento\Captcha\Model\Config\Font; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +class FontTest extends TestCase +{ + /** + * @var ObjectManagerHelper + */ + private $objectManagerHelper; + + /** + * @var Font + */ + private $model; + + /** + * @var HelperData|MockObject + */ + private $helperDataMock; + + /** + * Setup Environment For Testing + */ + protected function setUp(): void + { + $this->helperDataMock = $this->createMock(HelperData::class); + + $this->objectManagerHelper = new ObjectManagerHelper($this); + + $this->model = $this->objectManagerHelper->getObject( + Font::class, + [ + 'captchaData' => $this->helperDataMock + ] + ); + } + + /** + * Test toOptionArray() with data provider below + * + * @param array $fonts + * @param array $expectedResult + * @dataProvider toOptionArrayDataProvider + */ + public function testToOptionArray($fonts, $expectedResult) + { + $this->helperDataMock->expects($this->any())->method('getFonts') + ->willReturn($fonts); + + $this->assertEquals($expectedResult, $this->model->toOptionArray()); + } + + /** + * Data Provider for testing toOptionArray() + * + * @return array + */ + public function toOptionArrayDataProvider() + { + return [ + 'Empty get font' => [ + [], + [] + ], + 'Get font result' => [ + [ + 'arial' => [ + 'label' => 'Arial', + 'path' => '/www/magento/fonts/arial.ttf' + ], + 'verdana' => [ + 'label' => 'Verdana', + 'path' => '/www/magento/fonts/verdana.ttf' + ] + ], + [ + [ + 'label' => 'Arial', + 'value' => 'arial' + ], + [ + 'label' => 'Verdana', + 'value' => 'verdana' + ] + ] + ] + ]; + } +} diff --git a/Test/Unit/Model/Config/Form/BackendTest.php b/Test/Unit/Model/Config/Form/BackendTest.php new file mode 100644 index 0000000..9af8c21 --- /dev/null +++ b/Test/Unit/Model/Config/Form/BackendTest.php @@ -0,0 +1,101 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Captcha\Test\Unit\Model\Config\Form; + +use Magento\Captcha\Model\Config\Form\Backend; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +class BackendTest extends TestCase +{ + /** + * @var ObjectManagerHelper + */ + private $objectManagerHelper; + + /** + * @var Backend + */ + private $model; + + /** + * @var ScopeConfigInterface|MockObject + */ + private $configMock; + + /** + * Setup Environment For Testing + */ + protected function setUp(): void + { + $this->configMock = $this->getMockForAbstractClass(ScopeConfigInterface::class); + + $this->objectManagerHelper = new ObjectManagerHelper($this); + + $this->model = $this->objectManagerHelper->getObject( + Backend::class, + [ + 'config' => $this->configMock + ] + ); + } + + /** + * Test toOptionArray() with data provider below + * + * @param string|array $config + * @param array $expectedResult + * @dataProvider toOptionArrayDataProvider + */ + public function testToOptionArray($config, $expectedResult) + { + $this->configMock->expects($this->any())->method('getValue') + ->with('captcha/backend/areas', 'default') + ->willReturn($config); + + $this->assertEquals($expectedResult, $this->model->toOptionArray()); + } + + /** + * Data Provider for testing toOptionArray() + * + * @return array + */ + public function toOptionArrayDataProvider() + { + return [ + 'Empty captcha backend areas' => [ + '', + [] + ], + 'With two captcha backend area' => [ + [ + 'backend_login' => [ + 'label' => 'Admin Login' + ], + 'backend_forgotpassword' => [ + 'label' => 'Admin Forgot Password' + ] + ], + [ + [ + 'label' => 'Admin Login', + 'value' => 'backend_login' + ], + [ + 'label' => 'Admin Forgot Password', + 'value' => 'backend_forgotpassword' + ] + ] + ] + ]; + } +} diff --git a/Test/Unit/Model/Config/Form/FrontendTest.php b/Test/Unit/Model/Config/Form/FrontendTest.php new file mode 100644 index 0000000..1b2d969 --- /dev/null +++ b/Test/Unit/Model/Config/Form/FrontendTest.php @@ -0,0 +1,101 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Captcha\Test\Unit\Model\Config\Form; + +use Magento\Captcha\Model\Config\Form\Frontend; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +class FrontendTest extends TestCase +{ + /** + * @var ObjectManagerHelper + */ + private $objectManagerHelper; + + /** + * @var Frontend + */ + private $model; + + /** + * @var ScopeConfigInterface|MockObject + */ + private $configMock; + + /** + * Setup Environment For Testing + */ + protected function setUp(): void + { + $this->configMock = $this->getMockForAbstractClass(ScopeConfigInterface::class); + + $this->objectManagerHelper = new ObjectManagerHelper($this); + + $this->model = $this->objectManagerHelper->getObject( + Frontend::class, + [ + 'config' => $this->configMock + ] + ); + } + + /** + * Test toOptionArray() with data provider below + * + * @param string|array $config + * @param array $expectedResult + * @dataProvider toOptionArrayDataProvider + */ + public function testToOptionArray($config, $expectedResult) + { + $this->configMock->expects($this->any())->method('getValue') + ->with('captcha/frontend/areas', 'default') + ->willReturn($config); + + $this->assertEquals($expectedResult, $this->model->toOptionArray()); + } + + /** + * Data Provider for testing toOptionArray() + * + * @return array + */ + public function toOptionArrayDataProvider() + { + return [ + 'Empty captcha frontend areas' => [ + '', + [] + ], + 'With two captcha frontend area' => [ + [ + 'product_sendtofriend_form' => [ + 'label' => 'Send To Friend Form' + ], + 'sales_rule_coupon_request' => [ + 'label' => 'Applying coupon code' + ] + ], + [ + [ + 'label' => 'Send To Friend Form', + 'value' => 'product_sendtofriend_form' + ], + [ + 'label' => 'Applying coupon code', + 'value' => 'sales_rule_coupon_request' + ] + ] + ] + ]; + } +} diff --git a/Test/Unit/Model/Customer/Plugin/AjaxLoginTest.php b/Test/Unit/Model/Customer/Plugin/AjaxLoginTest.php new file mode 100644 index 0000000..3c5aed0 --- /dev/null +++ b/Test/Unit/Model/Customer/Plugin/AjaxLoginTest.php @@ -0,0 +1,244 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Captcha\Test\Unit\Model\Customer\Plugin; + +use Magento\Captcha\Helper\Data; +use Magento\Captcha\Model\Customer\Plugin\AjaxLogin; +use Magento\Captcha\Model\DefaultModel; +use Magento\Checkout\Model\Session; +use Magento\Customer\Controller\Ajax\Login; +use Magento\Framework\App\Request\Http; +use Magento\Framework\Controller\Result\JsonFactory; +use Magento\Framework\Serialize\Serializer\Json; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +class AjaxLoginTest extends TestCase +{ + /** + * @var MockObject|Session + */ + protected $sessionManagerMock; + + /** + * @var MockObject|Data + */ + protected $captchaHelperMock; + + /** + * @var MockObject|JsonFactory + */ + protected $jsonFactoryMock; + + /** + * @var MockObject + */ + protected $captchaMock; + + /** + * @var MockObject + */ + protected $resultJsonMock; + + /** + * @var MockObject + */ + protected $requestMock; + + /** + * @var MockObject|Login + */ + protected $loginControllerMock; + + /** + * @var MockObject|Json + */ + protected $serializerMock; + + /** + * @var array + */ + protected $formIds = ['user_login']; + + /** + * @var AjaxLogin + */ + protected $model; + + /** + * @inheritdoc + */ + protected function setUp(): void + { + $this->sessionManagerMock = $this->getMockBuilder(Session::class) + ->addMethods(['setUsername']) + ->disableOriginalConstructor() + ->getMock(); + $this->captchaHelperMock = $this->createMock(Data::class); + $this->captchaMock = $this->createMock(DefaultModel::class); + $this->jsonFactoryMock = $this->createPartialMock( + JsonFactory::class, + ['create'] + ); + $this->resultJsonMock = $this->createMock(\Magento\Framework\Controller\Result\Json::class); + $this->requestMock = $this->createMock(Http::class); + $this->loginControllerMock = $this->createMock(Login::class); + + $this->loginControllerMock->expects($this->any())->method('getRequest') + ->willReturn($this->requestMock); + + $this->captchaHelperMock + ->expects($this->exactly(1)) + ->method('getCaptcha') + ->willReturn($this->captchaMock); + + $this->serializerMock = $this->createMock(Json::class); + + $this->model = new AjaxLogin( + $this->captchaHelperMock, + $this->sessionManagerMock, + $this->jsonFactoryMock, + $this->formIds, + $this->serializerMock + ); + } + + /** + * Test aroundExecute. + */ + public function testAroundExecute() + { + $username = 'name'; + $captchaString = 'string'; + $requestData = [ + 'username' => $username, + 'captcha_string' => $captchaString, + 'captcha_form_id' => $this->formIds[0] + ]; + $requestContent = json_encode($requestData); + + $this->requestMock->expects($this->once())->method('getContent')->willReturn($requestContent); + $this->captchaMock->expects($this->once())->method('isRequired')->with($username) + ->willReturn(true); + $this->captchaMock->expects($this->once())->method('logAttempt')->with($username); + $this->captchaMock->expects($this->once())->method('isCorrect')->with($captchaString) + ->willReturn(true); + $this->serializerMock->expects($this->once())->method('unserialize')->willReturn($requestData); + + $closure = function () { + return 'result'; + }; + + $this->captchaHelperMock + ->expects($this->exactly(1)) + ->method('getCaptcha') + ->with('user_login') + ->willReturn($this->captchaMock); + + $this->assertEquals('result', $this->model->aroundExecute($this->loginControllerMock, $closure)); + } + + /** + * Test aroundExecuteIncorrectCaptcha. + */ + public function testAroundExecuteIncorrectCaptcha() + { + $username = 'name'; + $captchaString = 'string'; + $requestData = [ + 'username' => $username, + 'captcha_string' => $captchaString, + 'captcha_form_id' => $this->formIds[0] + ]; + $requestContent = json_encode($requestData); + + $this->requestMock->expects($this->once())->method('getContent')->willReturn($requestContent); + $this->captchaMock->expects($this->once())->method('isRequired')->with($username) + ->willReturn(true); + $this->captchaMock->expects($this->once())->method('logAttempt')->with($username); + $this->captchaMock->expects($this->once())->method('isCorrect') + ->with($captchaString)->willReturn(false); + $this->serializerMock->expects($this->once())->method('unserialize')->willReturn($requestData); + + $this->sessionManagerMock->expects($this->once())->method('setUsername')->with($username); + $this->jsonFactoryMock->expects($this->once())->method('create') + ->willReturn($this->resultJsonMock); + + $this->resultJsonMock + ->expects($this->once()) + ->method('setData') + ->with(['errors' => true, 'message' => __('Incorrect CAPTCHA')])->willReturnSelf(); + + $closure = function () { + }; + $this->assertEquals($this->resultJsonMock, $this->model->aroundExecute($this->loginControllerMock, $closure)); + } + + /** + * @dataProvider aroundExecuteCaptchaIsNotRequired + * @param string $username + * @param array $requestContent + */ + public function testAroundExecuteCaptchaIsNotRequired($username, $requestContent) + { + $this->requestMock->expects($this->once())->method('getContent') + ->willReturn(json_encode($requestContent)); + $this->serializerMock->expects($this->once())->method('unserialize') + ->willReturn($requestContent); + + $this->captchaMock->expects($this->once())->method('isRequired')->with($username) + ->willReturn(false); + $expectLogAttempt = $requestContent['captcha_form_id'] ?? false; + $this->captchaMock + ->expects($expectLogAttempt ? $this->once() : $this->never()) + ->method('logAttempt')->with($username); + $this->captchaMock->expects($this->never())->method('isCorrect'); + + $closure = function () { + return 'result'; + }; + $this->assertEquals('result', $this->model->aroundExecute($this->loginControllerMock, $closure)); + } + + /** + * @return array + */ + public function aroundExecuteCaptchaIsNotRequired(): array + { + return [ + [ + 'username' => 'name', + 'requestData' => ['username' => 'name', 'captcha_string' => 'string'], + ], + [ + 'username' => 'name', + 'requestData' => [ + 'username' => 'name', + 'captcha_string' => 'string', + 'captcha_form_id' => $this->formIds[0] + ], + ], + [ + 'username' => null, + 'requestData' => [ + 'username' => null, + 'captcha_string' => 'string', + 'captcha_form_id' => $this->formIds[0] + ], + ], + [ + 'username' => 'name', + 'requestData' => [ + 'username' => 'name', + 'captcha_string' => 'string', + 'captcha_form_id' => null + ], + ], + ]; + } +} diff --git a/Test/Unit/Model/DefaultTest.php b/Test/Unit/Model/DefaultTest.php new file mode 100644 index 0000000..1d222e2 --- /dev/null +++ b/Test/Unit/Model/DefaultTest.php @@ -0,0 +1,460 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Captcha\Test\Unit\Model; + +use Magento\Authorization\Model\UserContextInterface; +use Magento\Captcha\Block\Captcha\DefaultCaptcha; +use Magento\Captcha\Helper\Data; +use Magento\Captcha\Model\DefaultModel; +use Magento\Captcha\Model\ResourceModel\Log; +use Magento\Captcha\Model\ResourceModel\LogFactory; +use Magento\Customer\Model\Session; +use Magento\Framework\Math\Random; +use Magento\Framework\ObjectManagerInterface; +use Magento\Framework\Session\Storage; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\Store\Model\Store; +use Magento\Store\Model\StoreManager; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class DefaultTest extends TestCase +{ + /** + * Expiration frame + */ + const EXPIRE_FRAME = 86400; + + /** + * Captcha default config data + * @var array + */ + protected static $_defaultConfig = [ + 'type' => 'default', + 'enable' => '1', + 'font' => 'linlibertine', + 'mode' => 'after_fail', + 'forms' => 'user_forgotpassword,user_create', + 'failed_attempts_login' => '3', + 'failed_attempts_ip' => '1000', + 'timeout' => '7', + 'length' => '4-5', + 'symbols' => 'ABCDEFGHJKMnpqrstuvwxyz23456789', + 'case_sensitive' => '0', + 'shown_to_logged_in_user' => ['contact_us' => 1], + 'always_for' => [ + 'user_create', + 'user_forgotpassword', + 'contact_us', + ], + ]; + + /** + * @var MockObject + */ + protected $_dirMock; + + /** + * path to fonts + * @var array + */ + protected $_fontPath = [ + 'LinLibertine' => [ + 'label' => 'LinLibertine', + 'path' => 'lib/internal/LinLibertineFont/LinLibertine_Bd-2.8.1.ttf', + ], + ]; + + /** + * @var DefaultModel + */ + protected $_object; + + /** + * @var MockObject + */ + protected $_objectManager; + + /** + * @var MockObject + */ + protected $_storeManager; + + /** + * @var MockObject + */ + protected $session; + + /** + * @var MockObject|LogFactory + */ + protected $_resLogFactory; + + /** + * @var UserContextInterface|MockObject + */ + private $userContextMock; + + /** + * Sets up the fixture, for example, opens a network connection. + * This method is called before a test is executed. + */ + protected function setUp(): void + { + $this->session = $this->_getSessionStub(); + + $this->_storeManager = $this->createPartialMock(StoreManager::class, ['getStore']); + $this->_storeManager->expects( + $this->any() + )->method( + 'getStore' + )->willReturn( + $this->_getStoreStub() + ); + + // \Magento\Customer\Model\Session + $this->_objectManager = $this->getMockForAbstractClass(ObjectManagerInterface::class); + $this->_objectManager->expects( + $this->any() + )->method( + 'get' + )->willReturnMap( + [ + Data::class => $this->_getHelperStub(), + Session::class => $this->session, + ] + ); + + $this->_resLogFactory = $this->createPartialMock( + LogFactory::class, + ['create'] + ); + $this->_resLogFactory->expects( + $this->any() + )->method( + 'create' + )->willReturn( + $this->_getResourceModelStub() + ); + + $randomMock = $this->createMock(Random::class); + $randomMock->method('getRandomString')->willReturn('random-string'); + + $this->userContextMock = $this->getMockForAbstractClass(UserContextInterface::class); + + $this->_object = new DefaultModel( + $this->session, + $this->_getHelperStub(), + $this->_resLogFactory, + 'user_create', + $randomMock, + $this->userContextMock + ); + } + + /** + * @covers \Magento\Captcha\Model\DefaultModel::getBlockName + */ + public function testGetBlockName() + { + $this->assertEquals($this->_object->getBlockName(), DefaultCaptcha::class); + } + + /** + * @covers \Magento\Captcha\Model\DefaultModel::isRequired + */ + public function testIsRequired() + { + $this->assertTrue($this->_object->isRequired()); + } + + /** + * Validate that CAPTCHA is disabled for integrations. + * + * @return void + */ + public function testIsRequiredForIntegration(): void + { + $this->userContextMock->method('getUserType')->willReturn(UserContextInterface::USER_TYPE_INTEGRATION); + $this->userContextMock->method('getUserId')->willReturn(1); + + $this->assertFalse($this->_object->isRequired()); + } + + /** + * @covers \Magento\Captcha\Model\DefaultModel::isCaseSensitive + */ + public function testIsCaseSensitive() + { + self::$_defaultConfig['case_sensitive'] = '1'; + $this->assertEquals($this->_object->isCaseSensitive(), '1'); + self::$_defaultConfig['case_sensitive'] = '0'; + $this->assertEquals($this->_object->isCaseSensitive(), '0'); + } + + /** + * @covers \Magento\Captcha\Model\DefaultModel::getFont + */ + public function testGetFont() + { + $this->assertEquals($this->_object->getFont(), $this->_fontPath['LinLibertine']['path']); + } + + /** + * @covers \Magento\Captcha\Model\DefaultModel::getTimeout + * @covers \Magento\Captcha\Model\DefaultModel::getExpiration + */ + public function testGetTimeout() + { + $this->assertEquals($this->_object->getTimeout(), self::$_defaultConfig['timeout'] * 60); + } + + /** + * @covers \Magento\Captcha\Model\DefaultModel::isCorrect + */ + public function testIsCorrect() + { + self::$_defaultConfig['case_sensitive'] = '1'; + $this->assertFalse($this->_object->isCorrect('abcdef5')); + $sessionData = [ + 'user_create_word' => [ + 'data' => 'AbCdEf5', + 'words' => 'AbCdEf5', + 'expires' => time() + self::EXPIRE_FRAME + ] + ]; + $this->_object->getSession()->setData($sessionData); + self::$_defaultConfig['case_sensitive'] = '0'; + $this->assertTrue($this->_object->isCorrect('abcdef5')); + } + + /** + * @covers \Magento\Captcha\Model\DefaultModel::getImgSrc + */ + public function testGetImgSrc() + { + $this->assertEquals( + $this->_object->getImgSrc(), + 'http://localhost/media/captcha/base/' . $this->_object->getId() . '.png' + ); + } + + /** + * @covers \Magento\Captcha\Model\DefaultModel::logAttempt + */ + public function testLogAttempt() + { + $captcha = new DefaultModel( + $this->session, + $this->_getHelperStub(), + $this->_resLogFactory, + 'user_create' + ); + + $captcha->logAttempt('admin'); + + $this->assertEquals($captcha->getSession()->getData('user_create_show_captcha'), 1); + } + + /** + * @covers \Magento\Captcha\Model\DefaultModel::getWord + */ + public function testGetWord() + { + $this->assertEquals($this->_object->getWord(), 'AbCdEf5'); + $this->_object->getSession()->setData( + ['user_create_word' => ['data' => 'AbCdEf5', 'words' => 'AbCdEf5','expires' => time() - 360]] + ); + $this->assertNull($this->_object->getWord()); + } + + /** + * Create stub session object + * + * @return \Magento\Customer\Model\Session + */ + protected function _getSessionStub() + { + $helper = new ObjectManager($this); + $sessionArgs = $helper->getConstructArguments( + Session::class, + ['storage' => new Storage()] + ); + $session = $this->getMockBuilder(Session::class) + ->setMethods(['isLoggedIn', 'getUserCreateWord']) + ->setConstructorArgs($sessionArgs) + ->getMock(); + $session->expects($this->any())->method('isLoggedIn')->willReturn(false); + + $session->setData( + [ + 'user_create_word' => [ + 'data' => 'AbCdEf5', + 'words' => 'AbCdEf5', + 'expires' => time() + self::EXPIRE_FRAME + ] + ] + ); + return $session; + } + + /** + * Create helper stub + * @return Data + */ + protected function _getHelperStub() + { + $helper = $this->getMockBuilder( + Data::class + )->disableOriginalConstructor() + ->setMethods( + ['getConfig', 'getFonts', '_getWebsiteCode', 'getImgUrl'] + )->getMock(); + + $helper->expects( + $this->any() + )->method( + 'getConfig' + )->willReturnCallback( + 'Magento\Captcha\Test\Unit\Model\DefaultTest::getConfigNodeStub' + ); + + $helper->expects($this->any())->method('getFonts')->willReturn($this->_fontPath); + + $helper->expects($this->any())->method('_getWebsiteCode')->willReturn('base'); + + $helper->expects( + $this->any() + )->method( + 'getImgUrl' + )->willReturn( + 'http://localhost/media/captcha/base/' + ); + + return $helper; + } + + /** + * Get stub for resource model + * @return Log + */ + protected function _getResourceModelStub() + { + $resourceModel = $this->createPartialMock( + Log::class, + ['countAttemptsByRemoteAddress', 'countAttemptsByUserLogin', 'logAttempt', '__wakeup'] + ); + + $resourceModel->expects($this->any())->method('logAttempt'); + + $resourceModel->expects($this->any())->method('countAttemptsByRemoteAddress')->willReturn(0); + + $resourceModel->expects($this->any())->method('countAttemptsByUserLogin')->willReturn(3); + return $resourceModel; + } + + /** + * Mock get config method + * @static + * @return string + * @throws \InvalidArgumentException + */ + public static function getConfigNodeStub() + { + $args = func_get_args(); + $hashName = $args[0]; + + if (array_key_exists($hashName, self::$_defaultConfig)) { + return self::$_defaultConfig[$hashName]; + } + + throw new \InvalidArgumentException('Unknow id = ' . $hashName); + } + + /** + * Create store stub + * + * @return Store + */ + protected function _getStoreStub() + { + $store = $this->getMockBuilder(Store::class) + ->addMethods(['isAdmin']) + ->onlyMethods(['getBaseUrl']) + ->disableOriginalConstructor() + ->getMock(); + $store->expects($this->any())->method('getBaseUrl')->willReturn('http://localhost/media/'); + $store->expects($this->any())->method('isAdmin')->willReturn(false); + return $store; + } + + /** + * @param boolean $expectedResult + * @param string $formId + * @dataProvider isShownToLoggedInUserDataProvider + */ + public function testIsShownToLoggedInUser($expectedResult, $formId) + { + $captcha = new DefaultModel( + $this->session, + $this->_getHelperStub(), + $this->_resLogFactory, + $formId + ); + $this->assertEquals($expectedResult, $captcha->isShownToLoggedInUser()); + } + + /** + * @return array + */ + public function isShownToLoggedInUserDataProvider() + { + return [ + [true, 'contact_us'], + [false, 'user_create'], + [false, 'user_forgotpassword'] + ]; + } + + /** + * @param string $string + * @dataProvider generateWordProvider + * @throws \ReflectionException + */ + public function testGenerateWord($string) + { + $randomMock = $this->createMock(Random::class); + $randomMock->expects($this->once()) + ->method('getRandomString') + ->willReturn($string); + $captcha = new DefaultModel( + $this->session, + $this->_getHelperStub(), + $this->_resLogFactory, + 'user_create', + $randomMock + ); + $method = new \ReflectionMethod($captcha, 'generateWord'); + $method->setAccessible(true); + $this->assertEquals($string, $method->invoke($captcha)); + } + /** + * @return array + */ + public function generateWordProvider() + { + return [ + ['ABC123'], + ['1234567890'], + ['The quick brown fox jumps over the lazy dog.'] + ]; + } +} diff --git a/Test/Unit/Model/Filter/CaptchaConfigPostProcessorCompositeTest.php b/Test/Unit/Model/Filter/CaptchaConfigPostProcessorCompositeTest.php new file mode 100644 index 0000000..8928857 --- /dev/null +++ b/Test/Unit/Model/Filter/CaptchaConfigPostProcessorCompositeTest.php @@ -0,0 +1,87 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Captcha\Test\Unit\Model\Filter; + +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use PHPUnit\Framework\TestCase; +use Magento\Captcha\Api\CaptchaConfigPostProcessorInterface; +use Magento\Captcha\Model\Filter\CaptchaConfigPostProcessorComposite; +use PHPUnit\Framework\MockObject\MockObject; + +/** + * Test for Class \Magento\Captcha\Model\Filter\CaptchaConfigPostProcessorComposite + */ +class CaptchaConfigPostProcessorCompositeTest extends TestCase +{ + /** + * @var CaptchaConfigPostProcessorComposite + */ + private $model; + + /** + * @var ObjectManager + */ + private $objectManager; + + /** + * @var MockObject + */ + private $processorMock1; + + /** + * @var MockObject + */ + private $processorMock2; + + /** + * Initialize Class Dependencies + */ + protected function setUp(): void + { + $this->objectManager = new ObjectManager($this); + + $this->processorMock1 = $this->getMockBuilder(CaptchaConfigPostProcessorInterface::class) + ->disableOriginalConstructor() + ->setMethods(['process']) + ->getMock(); + $this->processorMock2 = $this->getMockBuilder(CaptchaConfigPostProcessorInterface::class) + ->disableOriginalConstructor() + ->setMethods(['process']) + ->getMock(); + + $processors = [$this->processorMock1, $this->processorMock2]; + + $this->model = $this->objectManager->getObject( + CaptchaConfigPostProcessorComposite::class, + [ + 'processors' => $processors, + ] + ); + } + + /** + * Test for Composite + * + * @return void + */ + public function testProcess(): void + { + $config = ['test1','test2', 'test3']; + + $this->processorMock1->expects($this->atLeastOnce()) + ->method('process') + ->with($config) + ->willReturn(['test1', 'test2']); + $this->processorMock2->expects($this->atLeastOnce()) + ->method('process') + ->with($config) + ->willReturn(['test3']); + + $this->assertEquals(['test1','test2', 'test3'], $this->model->process($config)); + } +} diff --git a/Test/Unit/Model/Filter/QuoteDataConfigFilterTest.php b/Test/Unit/Model/Filter/QuoteDataConfigFilterTest.php new file mode 100644 index 0000000..855a52a --- /dev/null +++ b/Test/Unit/Model/Filter/QuoteDataConfigFilterTest.php @@ -0,0 +1,70 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Captcha\Test\Unit\Model\Filter; + +use Magento\Captcha\Model\Filter\QuoteDataConfigFilter; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use PHPUnit\Framework\TestCase; + +/** + * Test for Class \Magento\Captcha\Model\Filter\QuoteDataConfigFilter + */ +class QuoteDataConfigFilterTest extends TestCase +{ + + /** + * @var QuoteDataConfigFilter + */ + private $model; + + /** + * @var ObjectManager + */ + private $objectManager; + + /** + * Initialize Class Dependencies + */ + protected function setUp(): void + { + $this->objectManager = new ObjectManager($this); + + $this->model = $this->objectManager->getObject( + QuoteDataConfigFilter::class, + [ + 'filterList' => ['test1', 'test2'], + ] + ); + } + + /** + * Test Process method + * + * @return void + */ + public function testProcess(): void + { + $config = [ + 'quoteData' => + [ + 'test1' => 1, + 'test2' => 2, + 'test3' => 3 + ] + ]; + + $expected = [ + 'quoteData' => + [ + 'test3' => 3 + ] + ]; + + $this->assertEquals($expected, $this->model->process($config)); + } +} diff --git a/Test/Unit/Observer/CaptchaStringResolverTest.php b/Test/Unit/Observer/CaptchaStringResolverTest.php new file mode 100644 index 0000000..7dda713 --- /dev/null +++ b/Test/Unit/Observer/CaptchaStringResolverTest.php @@ -0,0 +1,71 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Captcha\Test\Unit\Observer; + +use Magento\Captcha\Helper\Data as CaptchaDataHelper; +use Magento\Captcha\Observer\CaptchaStringResolver; +use Magento\Framework\App\Request\Http as HttpRequest; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +class CaptchaStringResolverTest extends TestCase +{ + /** + * @var ObjectManager + */ + private $objectManagerHelper; + + /** + * @var CaptchaStringResolver + */ + private $captchaStringResolver; + + /** + * @var HttpRequest|MockObject + */ + private $requestMock; + + protected function setUp(): void + { + $this->objectManagerHelper = new ObjectManager($this); + $this->requestMock = $this->createMock(HttpRequest::class); + $this->captchaStringResolver = $this->objectManagerHelper->getObject(CaptchaStringResolver::class); + } + + public function testResolveWithFormIdSet() + { + $formId = 'contact_us'; + $captchaValue = 'some-value'; + + $this->requestMock->expects($this->once()) + ->method('getPost') + ->with(CaptchaDataHelper::INPUT_NAME_FIELD_VALUE) + ->willReturn([$formId => $captchaValue]); + + self::assertEquals( + $this->captchaStringResolver->resolve($this->requestMock, $formId), + $captchaValue + ); + } + + public function testResolveWithNoFormIdInRequest() + { + $formId = 'contact_us'; + + $this->requestMock->expects($this->once()) + ->method('getPost') + ->with(CaptchaDataHelper::INPUT_NAME_FIELD_VALUE) + ->willReturn([]); + + self::assertEquals( + $this->captchaStringResolver->resolve($this->requestMock, $formId), + '' + ); + } +} diff --git a/Test/Unit/Observer/CheckContactUsFormObserverTest.php b/Test/Unit/Observer/CheckContactUsFormObserverTest.php new file mode 100644 index 0000000..bc7e295 --- /dev/null +++ b/Test/Unit/Observer/CheckContactUsFormObserverTest.php @@ -0,0 +1,207 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Captcha\Test\Unit\Observer; + +use Magento\Captcha\Helper\Data; +use Magento\Captcha\Model\DefaultModel; +use Magento\Captcha\Observer\CaptchaStringResolver; +use Magento\Captcha\Observer\CheckContactUsFormObserver; +use Magento\Framework\App\Action\Action; +use Magento\Framework\App\ActionFlag; +use Magento\Framework\App\Request\DataPersistorInterface; +use Magento\Framework\App\Request\Http; +use Magento\Framework\App\Response\RedirectInterface; +use Magento\Framework\Event\Observer; +use Magento\Framework\Message\ManagerInterface; +use Magento\Framework\Session\SessionManager; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +/** + * Test class for \Magento\Captcha\Observer\CheckContactUsFormObserver + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class CheckContactUsFormObserverTest extends TestCase +{ + /** + * @var ObjectManager + */ + private $objectManagerHelper; + + /** + * @var CheckContactUsFormObserver + */ + private $checkContactUsFormObserver; + + /** + * @var Data|MockObject + */ + private $helperMock; + + /** + * @var ActionFlag|MockObject + */ + private $actionFlagMock; + + /** + * @var ManagerInterface|MockObject + */ + private $messageManagerMock; + + /** + * @var RedirectInterface|MockObject + */ + private $redirectMock; + + /** + * @var CaptchaStringResolver|MockObject + */ + private $captchaStringResolverMock; + + /** + * @var DataPersistorInterface|MockObject + */ + private $dataPersistorMock; + + /** + * @var SessionManager|MockObject + */ + private $sessionMock; + + /** + * @var DefaultModel|MockObject + */ + private $captchaMock; + + protected function setUp(): void + { + $this->objectManagerHelper = new ObjectManager($this); + + $this->helperMock = $this->createMock(Data::class); + $this->actionFlagMock = $this->createMock(ActionFlag::class); + $this->messageManagerMock = $this->getMockForAbstractClass(ManagerInterface::class); + $this->redirectMock = $this->getMockForAbstractClass(RedirectInterface::class); + $this->captchaStringResolverMock = $this->createMock(CaptchaStringResolver::class); + $this->dataPersistorMock = $this->getMockBuilder(DataPersistorInterface::class) + ->getMockForAbstractClass(); + + $this->sessionMock = $this->getMockBuilder(SessionManager::class) + ->addMethods(['addErrorMessage']) + ->disableOriginalConstructor() + ->getMock(); + $this->captchaMock = $this->createMock(DefaultModel::class); + + $this->checkContactUsFormObserver = $this->objectManagerHelper->getObject( + CheckContactUsFormObserver::class, + [ + 'helper' => $this->helperMock, + 'actionFlag' => $this->actionFlagMock, + 'messageManager' => $this->messageManagerMock, + 'redirect' => $this->redirectMock, + 'captchaStringResolver' => $this->captchaStringResolverMock, + 'dataPersistor' => $this->dataPersistorMock + ] + ); + } + + public function testCheckContactUsFormWhenCaptchaIsRequiredAndValid() + { + $formId = 'contact_us'; + $captchaValue = 'some-value'; + + $controller = $this->createMock(Action::class); + $request = $this->createMock(Http::class); + $request->method('getPost') + ->with(Data::INPUT_NAME_FIELD_VALUE, null) + ->willReturn([$formId => $captchaValue]); + $controller->method('getRequest')->willReturn($request); + $this->captchaMock->method('isRequired')->willReturn(true); + $this->captchaMock->expects($this->once()) + ->method('isCorrect') + ->with($captchaValue) + ->willReturn(true); + $this->captchaStringResolverMock->expects($this->once()) + ->method('resolve') + ->with($request, $formId) + ->willReturn($captchaValue); + $this->helperMock->method('getCaptcha') + ->with($formId) + ->willReturn($this->captchaMock); + $this->sessionMock->expects($this->never())->method('addErrorMessage'); + + $this->checkContactUsFormObserver->execute( + new Observer(['controller_action' => $controller]) + ); + } + + public function testCheckContactUsFormRedirectsCustomerWithWarningMessageWhenCaptchaIsRequiredAndInvalid() + { + $formId = 'contact_us'; + $captchaValue = 'some-value'; + $warningMessage = 'Incorrect CAPTCHA.'; + $redirectRoutePath = 'contact/index/index'; + $redirectUrl = 'http://magento.com/contacts/'; + $postData = ['name' => 'Some Name']; + + $request = $this->createMock(Http::class); + $response = $this->createMock(\Magento\Framework\App\Response\Http::class); + $request->method('getPost') + ->with(Data::INPUT_NAME_FIELD_VALUE, null) + ->willReturn([$formId => $captchaValue]); + $request->expects($this->once()) + ->method('getPostValue') + ->willReturn($postData); + + $this->redirectMock->expects($this->once()) + ->method('redirect') + ->with($response, $redirectRoutePath, []) + ->willReturn($redirectUrl); + + $controller = $this->createMock(Action::class); + $controller->method('getRequest')->willReturn($request); + $controller->method('getResponse')->willReturn($response); + $this->captchaMock->method('isRequired')->willReturn(true); + $this->captchaMock->expects($this->once()) + ->method('isCorrect') + ->with($captchaValue) + ->willReturn(false); + $this->captchaStringResolverMock->expects($this->once()) + ->method('resolve') + ->with($request, $formId) + ->willReturn($captchaValue); + $this->helperMock->method('getCaptcha') + ->with($formId) + ->willReturn($this->captchaMock); + $this->messageManagerMock->expects($this->once()) + ->method('addErrorMessage') + ->with($warningMessage); + $this->actionFlagMock->expects($this->once()) + ->method('set') + ->with('', Action::FLAG_NO_DISPATCH, true); + $this->dataPersistorMock->expects($this->once()) + ->method('set') + ->with($formId, $postData); + + $this->checkContactUsFormObserver->execute( + new Observer(['controller_action' => $controller]) + ); + } + + public function testCheckContactUsFormDoesNotCheckCaptchaWhenItIsNotRequired() + { + $this->helperMock->method('getCaptcha') + ->with('contact_us') + ->willReturn($this->captchaMock); + $this->captchaMock->method('isRequired')->willReturn(false); + $this->captchaMock->expects($this->never())->method('isCorrect'); + + $this->checkContactUsFormObserver->execute(new Observer()); + } +} diff --git a/Test/Unit/Observer/CheckForgotpasswordObserverTest.php b/Test/Unit/Observer/CheckForgotpasswordObserverTest.php new file mode 100644 index 0000000..b8e2ea8 --- /dev/null +++ b/Test/Unit/Observer/CheckForgotpasswordObserverTest.php @@ -0,0 +1,172 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Captcha\Test\Unit\Observer; + +use Magento\Captcha\Helper\Data; +use Magento\Captcha\Model\DefaultModel; +use Magento\Captcha\Observer\CaptchaStringResolver; +use Magento\Captcha\Observer\CheckForgotpasswordObserver; +use Magento\Framework\App\Action\Action; +use Magento\Framework\App\ActionFlag; +use Magento\Framework\App\Request\Http; +use Magento\Framework\App\Response\RedirectInterface; +use Magento\Framework\Event\Observer; +use Magento\Framework\Message\ManagerInterface; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class CheckForgotpasswordObserverTest extends TestCase +{ + /** + * @var CheckForgotpasswordObserver + */ + protected $checkForgotpasswordObserver; + + /** + * @var MockObject + */ + protected $_helper; + + /** + * @var MockObject + */ + protected $_actionFlag; + + /** + * @var MockObject + */ + protected $_messageManager; + + /** + * @var MockObject + */ + protected $redirect; + + /** + * @var ObjectManager + */ + protected $_objectManager; + + /** + * @var CaptchaStringResolver|MockObject + */ + protected $captchaStringResolver; + + /** + * @var MockObject + */ + protected $_captcha; + + protected function setUp(): void + { + $this->_objectManager = new ObjectManager($this); + $this->_helper = $this->createMock(Data::class); + $this->_actionFlag = $this->createMock(ActionFlag::class); + $this->_messageManager = $this->getMockForAbstractClass(ManagerInterface::class); + $this->redirect = $this->getMockForAbstractClass(RedirectInterface::class); + $this->captchaStringResolver = $this->createMock(CaptchaStringResolver::class); + $this->checkForgotpasswordObserver = $this->_objectManager->getObject( + CheckForgotpasswordObserver::class, + [ + 'helper' => $this->_helper, + 'actionFlag' => $this->_actionFlag, + 'messageManager' => $this->_messageManager, + 'redirect' => $this->redirect, + 'captchaStringResolver' => $this->captchaStringResolver + ] + ); + $this->_captcha = $this->createMock(DefaultModel::class); + } + + public function testCheckForgotpasswordRedirects() + { + $formId = 'user_forgotpassword'; + $captchaValue = 'some-value'; + $warningMessage = 'Incorrect CAPTCHA'; + $redirectRoutePath = '*/*/forgotpassword'; + $redirectUrl = 'http://magento.com/customer/account/forgotpassword/'; + + $request = $this->createMock(Http::class); + $response = $this->createMock(\Magento\Framework\App\Response\Http::class); + $request->expects( + $this->any() + )->method( + 'getPost' + )->with( + Data::INPUT_NAME_FIELD_VALUE, + null + )->willReturn( + [$formId => $captchaValue] + ); + + $this->redirect->expects( + $this->once() + )->method( + 'redirect' + )->with( + $response, + $redirectRoutePath, + [] + )->willReturn( + $redirectUrl + ); + + $controller = $this->createMock(Action::class); + $controller->expects($this->any())->method('getRequest')->willReturn($request); + $controller->expects($this->any())->method('getResponse')->willReturn($response); + $this->_captcha->expects($this->any())->method('isRequired')->willReturn(true); + $this->_captcha->expects( + $this->once() + )->method( + 'isCorrect' + )->with( + $captchaValue + )->willReturn( + false + ); + + $this->captchaStringResolver->expects( + $this->once() + )->method( + 'resolve' + )->with( + $request, + $formId + )->willReturn( + $captchaValue + ); + + $this->_helper->expects( + $this->any() + )->method( + 'getCaptcha' + )->with( + $formId + )->willReturn( + $this->_captcha + ); + $this->_messageManager->expects($this->once())->method('addErrorMessage')->with($warningMessage); + $this->_actionFlag->expects( + $this->once() + )->method( + 'set' + )->with( + '', + Action::FLAG_NO_DISPATCH, + true + ); + + $this->checkForgotpasswordObserver->execute( + new Observer(['controller_action' => $controller]) + ); + } +} diff --git a/Test/Unit/Observer/CheckUserCreateObserverTest.php b/Test/Unit/Observer/CheckUserCreateObserverTest.php new file mode 100644 index 0000000..57bd0c6 --- /dev/null +++ b/Test/Unit/Observer/CheckUserCreateObserverTest.php @@ -0,0 +1,187 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Captcha\Test\Unit\Observer; + +use Magento\Captcha\Helper\Data; +use Magento\Captcha\Model\DefaultModel; +use Magento\Captcha\Observer\CaptchaStringResolver; +use Magento\Captcha\Observer\CheckUserCreateObserver; +use Magento\Framework\App\Action\Action; +use Magento\Framework\App\ActionFlag; +use Magento\Framework\App\Request\Http; +use Magento\Framework\App\Response\RedirectInterface; +use Magento\Framework\Event\Observer; +use Magento\Framework\Message\ManagerInterface; +use Magento\Framework\Session\SessionManager; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\Framework\Url; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class CheckUserCreateObserverTest extends TestCase +{ + /** + * @var CheckUserCreateObserver + */ + protected $checkUserCreateObserver; + + /** + * @var MockObject + */ + protected $_helper; + + /** + * @var MockObject + */ + protected $_actionFlag; + + /** + * @var MockObject + */ + protected $_messageManager; + + /** + * @var MockObject + */ + protected $_session; + + /** + * @var MockObject + */ + protected $_urlManager; + + /** + * @var ObjectManager + */ + protected $_objectManager; + + /** + * @var CaptchaStringResolver|MockObject + */ + protected $captchaStringResolver; + + /** + * @var MockObject + */ + protected $_captcha; + + /** + * @var MockObject + */ + protected $redirect; + + protected function setUp(): void + { + $this->_objectManager = new ObjectManager($this); + $this->_helper = $this->createMock(Data::class); + $this->_actionFlag = $this->createMock(ActionFlag::class); + $this->_messageManager = $this->getMockForAbstractClass(ManagerInterface::class); + $this->_session = $this->createMock(SessionManager::class); + $this->_urlManager = $this->createMock(Url::class); + $this->captchaStringResolver = $this->createMock(CaptchaStringResolver::class); + $this->redirect = $this->getMockForAbstractClass(RedirectInterface::class); + $this->checkUserCreateObserver = $this->_objectManager->getObject( + CheckUserCreateObserver::class, + [ + 'helper' => $this->_helper, + 'actionFlag' => $this->_actionFlag, + 'messageManager' => $this->_messageManager, + 'session' => $this->_session, + 'urlManager' => $this->_urlManager, + 'redirect' => $this->redirect, + 'captchaStringResolver' => $this->captchaStringResolver + ] + ); + $this->_captcha = $this->createMock(DefaultModel::class); + } + + public function testCheckUserCreateRedirectsError() + { + $formId = 'user_create'; + $captchaValue = 'some-value'; + $warningMessage = 'Incorrect CAPTCHA'; + $redirectRoutePath = '*/*/create'; + $redirectUrl = 'http://magento.com/customer/account/create/'; + + $request = $this->createMock(Http::class); + + $this->redirect->expects( + $this->once() + )->method( + 'error' + )->with( + $redirectUrl + )->willReturn( + $redirectUrl + ); + + $response = $this->createMock(\Magento\Framework\App\Response\Http::class); + $response->expects($this->once())->method('setRedirect')->with($redirectUrl); + + $this->_urlManager->expects( + $this->once() + )->method( + 'getUrl' + )->with( + $redirectRoutePath, + ['_nosecret' => true] + )->willReturn( + $redirectUrl + ); + + $controller = $this->createMock(Action::class); + $controller->expects($this->any())->method('getRequest')->willReturn($request); + $controller->expects($this->any())->method('getResponse')->willReturn($response); + $this->_captcha->expects($this->any())->method('isRequired')->willReturn(true); + $this->_captcha->expects( + $this->once() + )->method( + 'isCorrect' + )->with( + $captchaValue + )->willReturn( + false + ); + $this->captchaStringResolver->expects( + $this->once() + )->method( + 'resolve' + )->with( + $request, + $formId + )->willReturn( + $captchaValue + ); + $this->_helper->expects( + $this->any() + )->method( + 'getCaptcha' + )->with( + $formId + )->willReturn( + $this->_captcha + ); + $this->_messageManager->expects($this->once())->method('addErrorMessage')->with($warningMessage); + $this->_actionFlag->expects( + $this->once() + )->method( + 'set' + )->with( + '', + Action::FLAG_NO_DISPATCH, + true + ); + + $this->checkUserCreateObserver->execute( + new Observer(['controller_action' => $controller]) + ); + } +} diff --git a/Test/Unit/Observer/CheckUserEditObserverTest.php b/Test/Unit/Observer/CheckUserEditObserverTest.php new file mode 100644 index 0000000..40db663 --- /dev/null +++ b/Test/Unit/Observer/CheckUserEditObserverTest.php @@ -0,0 +1,181 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Captcha\Test\Unit\Observer; + +use Magento\Captcha\Helper\Data; +use Magento\Captcha\Model\DefaultModel; +use Magento\Captcha\Observer\CaptchaStringResolver; +use Magento\Captcha\Observer\CheckUserEditObserver; +use Magento\Customer\Model\AuthenticationInterface; +use Magento\Customer\Model\Data\Customer; +use Magento\Customer\Model\Session; +use Magento\Framework\App\Action\Action; +use Magento\Framework\App\ActionFlag; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\App\Response\Http; +use Magento\Framework\App\Response\RedirectInterface; +use Magento\Framework\Event\Observer; +use Magento\Framework\Message\ManagerInterface; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class CheckUserEditObserverTest extends TestCase +{ + /** @var Data|MockObject */ + protected $helperMock; + + /** @var ActionFlag|MockObject */ + protected $actionFlagMock; + + /* @var \Magento\Framework\Message\ManagerInterface|MockObject */ + protected $messageManagerMock; + + /** @var RedirectInterface|MockObject */ + protected $redirectMock; + + /** @var CaptchaStringResolver|MockObject */ + protected $captchaStringResolverMock; + + /** @var AuthenticationInterface|MockObject */ + protected $authenticationMock; + + /** @var Session|MockObject */ + protected $customerSessionMock; + + /** @var ScopeConfigInterface|MockObject */ + protected $scopeConfigMock; + + /** @var CheckUserEditObserver */ + protected $observer; + + /** + * Init mocks for tests + * @return void + */ + protected function setUp(): void + { + $this->helperMock = $this->createMock(Data::class); + $this->actionFlagMock = $this->createMock(ActionFlag::class); + $this->messageManagerMock = $this->getMockForAbstractClass(ManagerInterface::class); + $this->redirectMock = $this->getMockForAbstractClass(RedirectInterface::class); + $this->captchaStringResolverMock = $this->createMock(CaptchaStringResolver::class); + $this->authenticationMock = $this->getMockBuilder(AuthenticationInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + + $this->customerSessionMock = $this->createPartialMock( + Session::class, + ['getCustomerId', 'getCustomer', 'logout', 'start'] + ); + $this->scopeConfigMock = $this->getMockForAbstractClass(ScopeConfigInterface::class); + + $objectManager = new ObjectManager($this); + $this->observer = $objectManager->getObject( + CheckUserEditObserver::class, + [ + 'helper' => $this->helperMock, + 'actionFlag' => $this->actionFlagMock, + 'messageManager' => $this->messageManagerMock, + 'redirect' => $this->redirectMock, + 'captchaStringResolver' => $this->captchaStringResolverMock, + 'authentication' => $this->authenticationMock, + 'customerSession' => $this->customerSessionMock, + 'scopeConfig' => $this->scopeConfigMock, + ] + ); + } + + /** + * @return void + */ + public function testExecute() + { + $customerId = 7; + $captchaValue = 'some-value'; + $email = 'test@example.com'; + $redirectUrl = 'http://magento.com/customer/account/edit/'; + + $captcha = $this->createMock(DefaultModel::class); + $captcha->expects($this->once()) + ->method('isRequired') + ->willReturn(true); + $captcha->expects($this->once()) + ->method('isCorrect') + ->with($captchaValue) + ->willReturn(false); + + $this->helperMock->expects($this->once()) + ->method('getCaptcha') + ->with(CheckUserEditObserver::FORM_ID) + ->willReturn($captcha); + + $response = $this->createMock(Http::class); + $request = $this->createMock(\Magento\Framework\App\Request\Http::class); + $request->expects($this->any()) + ->method('getPost') + ->with(Data::INPUT_NAME_FIELD_VALUE, null) + ->willReturn([CheckUserEditObserver::FORM_ID => $captchaValue]); + + $controller = $this->createMock(Action::class); + $controller->expects($this->any())->method('getRequest')->willReturn($request); + $controller->expects($this->any())->method('getResponse')->willReturn($response); + + $this->captchaStringResolverMock->expects($this->once()) + ->method('resolve') + ->with($request, CheckUserEditObserver::FORM_ID) + ->willReturn($captchaValue); + + $customerDataMock = $this->createMock(Customer::class); + + $this->customerSessionMock->expects($this->once()) + ->method('getCustomerId') + ->willReturn($customerId); + + $this->customerSessionMock->expects($this->atLeastOnce()) + ->method('getCustomer') + ->willReturn($customerDataMock); + + $this->authenticationMock->expects($this->once()) + ->method('processAuthenticationFailure') + ->with($customerId); + $this->authenticationMock->expects($this->once()) + ->method('isLocked') + ->with($customerId) + ->willReturn(true); + + $this->customerSessionMock->expects($this->once()) + ->method('logout'); + $this->customerSessionMock->expects($this->once()) + ->method('start'); + + $this->scopeConfigMock->expects($this->once()) + ->method('getValue') + ->with('contact/email/recipient_email') + ->willReturn($email); + + $message = __('The account is locked. Please wait and try again or contact %1.', $email); + $this->messageManagerMock->expects($this->exactly(2)) + ->method('addErrorMessage') + ->withConsecutive([$message], [__('Incorrect CAPTCHA')]); + + $this->actionFlagMock->expects($this->once()) + ->method('set') + ->with('', Action::FLAG_NO_DISPATCH, true); + + $this->redirectMock->expects($this->once()) + ->method('redirect') + ->with($response, '*/*/edit') + ->willReturn($redirectUrl); + + $this->observer->execute(new Observer(['controller_action' => $controller])); + } +} diff --git a/Test/Unit/Observer/CheckUserForgotPasswordBackendObserverTest.php b/Test/Unit/Observer/CheckUserForgotPasswordBackendObserverTest.php new file mode 100644 index 0000000..7d30f9f --- /dev/null +++ b/Test/Unit/Observer/CheckUserForgotPasswordBackendObserverTest.php @@ -0,0 +1,231 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Captcha\Test\Unit\Observer; + +use Magento\Captcha\Helper\Data as DataHelper; +use Magento\Captcha\Model\CaptchaInterface; +use Magento\Captcha\Observer\CaptchaStringResolver; +use Magento\Captcha\Observer\CheckUserForgotPasswordBackendObserver; +use Magento\Framework\App\Action\Action; +use Magento\Framework\App\ActionFlag; +use Magento\Framework\App\Request\Http as HttpRequest; +use Magento\Framework\App\Response\Http as HttpResponse; +use Magento\Framework\Event\Observer; +use Magento\Framework\Message\ManagerInterface; +use Magento\Framework\Session\SessionManagerInterface; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +/** + * Unit Test for \Magento\Captcha\Observer\CheckUserForgotPasswordBackendObserver + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class CheckUserForgotPasswordBackendObserverTest extends TestCase +{ + const STUB_EMAIL = 'stub@test.mail'; + const STUB_REQUEST_PARAMS = ['STUB_PARAM']; + + /** + * @var MockObject|DataHelper + */ + private $helperMock; + + /** + * @var MockObject|CaptchaStringResolver + */ + private $captchaStringResolverMock; + + /** + * @var MockObject|SessionManagerInterface + */ + private $sessionMock; + + /** + * @var MockObject|ActionFlag + */ + private $actionFlagMock; + + /** + * @var MockObject|ManagerInterface + */ + private $messageManagerMock; + + /** + * @var CheckUserForgotPasswordBackendObserver + */ + private $observer; + + /** + * @var MockObject|CaptchaInterface + */ + private $captchaMock; + + /** + * @var MockObject|Observer + */ + private $eventObserverMock; + + /** + * @var MockObject|Action + */ + private $controllerMock; + + /** + * @var MockObject|HttpResponse + */ + private $httpResponseMock; + + /** + * @var MockObject|HttpRequest + */ + private $requestMock; + + /** + * @inheritDoc + */ + protected function setUp(): void + { + $formId = 'backend_forgotpassword'; + + $this->helperMock = $this->createMock(DataHelper::class); + $this->captchaStringResolverMock = $this->createMock(CaptchaStringResolver::class); + $this->sessionMock = $this->getMockBuilder(SessionManagerInterface::class) + ->setMethods(['setEmail']) + ->getMockForAbstractClass(); + $this->actionFlagMock = $this->createMock(ActionFlag::class); + $this->messageManagerMock = $this->getMockForAbstractClass(ManagerInterface::class); + $this->requestMock = $this->createMock(HttpRequest::class); + + $objectManager = new ObjectManagerHelper($this); + $this->observer = $objectManager->getObject( + CheckUserForgotPasswordBackendObserver::class, + [ + '_helper' => $this->helperMock, + 'captchaStringResolver' => $this->captchaStringResolverMock, + '_session' => $this->sessionMock, + '_actionFlag' => $this->actionFlagMock, + 'messageManager' => $this->messageManagerMock, + 'request' => $this->requestMock + ] + ); + + $this->captchaMock = $this->getMockBuilder(CaptchaInterface::class) + ->setMethods(['isRequired', 'isCorrect']) + ->getMockForAbstractClass(); + $this->helperMock->expects($this->once()) + ->method('getCaptcha') + ->with($formId) + ->willReturn($this->captchaMock); + + $this->httpResponseMock = $this->createMock(HttpResponse::class); + + $this->controllerMock = $this->getMockBuilder(Action::class) + ->disableOriginalConstructor() + ->setMethods(['getUrl', 'getResponse']) + ->getMockForAbstractClass(); + $this->controllerMock->expects($this->any()) + ->method('getResponse') + ->willReturn($this->httpResponseMock); + + $this->eventObserverMock = $this->getMockBuilder(Observer::class) + ->addMethods(['getControllerAction']) + ->disableOriginalConstructor() + ->getMock(); + $this->eventObserverMock->expects($this->any()) + ->method('getControllerAction') + ->willReturn($this->controllerMock); + } + + /** + * Test case when Captcha is required and was entered correctly. + */ + public function testExecuteWhenCaptchaIsCorrect() + { + $this->configureRequestMockWithStubValues(); + $this->captchaMock->expects($this->once())->method('isRequired')->willReturn(true); + $this->captchaMock->expects($this->once())->method('isCorrect')->willReturn(true); + + $this->executeOriginalMethodExpectsNoError(); + } + + /** + * Test case when Captcha is required and was entered incorrectly. + */ + public function testExecuteWhenCaptchaIsIncorrect() + { + $this->configureRequestMockWithStubValues(); + $this->captchaMock->expects($this->once())->method('isRequired')->willReturn(true); + $this->captchaMock->expects($this->once())->method('isCorrect')->willReturn(false); + + $this->sessionMock->expects($this->once())->method('setEmail'); + $this->actionFlagMock->expects($this->once())->method('set'); + $this->controllerMock->expects($this->once())->method('getUrl'); + $this->messageManagerMock->expects($this->once()) + ->method('addErrorMessage') + ->with(__('Incorrect CAPTCHA')); + $this->httpResponseMock->expects($this->once())->method('setRedirect')->willReturnSelf(); + + $this->observer->execute($this->eventObserverMock); + } + + /** + * Test case when Captcha is not required. + */ + public function testExecuteWhenCaptchaIsNotRequired() + { + $this->configureRequestMockWithStubValues(); + $this->captchaMock->expects($this->once())->method('isRequired')->willReturn(false); + + $this->executeOriginalMethodExpectsNoError(); + } + + /** + * Test case when email is not provided + */ + public function testExecuteWhenEmailParamIsNotPresent() + { + $this->requestMock->expects($this->any()) + ->method('getParam') + ->with('email') + ->willReturn(null); + $this->requestMock->expects($this->any()) + ->method('getParams') + ->willReturn(self::STUB_REQUEST_PARAMS); + $this->captchaMock->expects($this->never())->method('isRequired'); + $this->captchaMock->expects($this->never())->method('isCorrect'); + + $this->executeOriginalMethodExpectsNoError(); + } + + /** + * Stub params for Request Mock + */ + private function configureRequestMockWithStubValues() + { + $this->requestMock->expects($this->any()) + ->method('getParam') + ->with('email') + ->willReturn(self::STUB_EMAIL); + $this->requestMock->expects($this->any()) + ->method('getParams') + ->willReturn(self::STUB_REQUEST_PARAMS); + } + + /** + * Run original method, expect there is no error + */ + private function executeOriginalMethodExpectsNoError() + { + $this->messageManagerMock->expects($this->never())->method('addErrorMessage'); + $this->httpResponseMock->expects($this->never())->method('setRedirect'); + + $this->observer->execute($this->eventObserverMock); + } +} diff --git a/Test/Unit/Observer/CheckUserLoginBackendObserverTest.php b/Test/Unit/Observer/CheckUserLoginBackendObserverTest.php new file mode 100644 index 0000000..271cefe --- /dev/null +++ b/Test/Unit/Observer/CheckUserLoginBackendObserverTest.php @@ -0,0 +1,140 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Captcha\Test\Unit\Observer; + +use Magento\Captcha\Helper\Data; +use Magento\Captcha\Model\DefaultModel; +use Magento\Captcha\Observer\CaptchaStringResolver; +use Magento\Captcha\Observer\CheckUserLoginBackendObserver; +use Magento\Framework\App\RequestInterface; +use Magento\Framework\Event; +use Magento\Framework\Event\Observer; +use Magento\Framework\Message\ManagerInterface; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +class CheckUserLoginBackendObserverTest extends TestCase +{ + /** + * @var CheckUserLoginBackendObserver + */ + private $observer; + + /** + * @var ManagerInterface|MockObject + */ + private $messageManagerMock; + + /** + * @var CaptchaStringResolver|MockObject + */ + private $captchaStringResolverMock; + + /** + * @var RequestInterface|MockObject + */ + private $requestMock; + + /** + * @var Data|MockObject + */ + private $helperMock; + + /** + * Set Up + * + * @return void + */ + protected function setUp(): void + { + $this->helperMock = $this->createMock(Data::class); + $this->messageManagerMock = $this->getMockForAbstractClass(ManagerInterface::class); + $this->captchaStringResolverMock = $this->createMock(CaptchaStringResolver::class); + $this->requestMock = $this->getMockForAbstractClass(RequestInterface::class); + + $this->observer = new CheckUserLoginBackendObserver( + $this->helperMock, + $this->captchaStringResolverMock, + $this->requestMock + ); + } + + /** + * Test check user login in backend with correct captcha + * + * @dataProvider requiredCaptchaDataProvider + * @param bool $isRequired + * @return void + */ + public function testCheckOnBackendLoginWithCorrectCaptcha(bool $isRequired): void + { + $formId = 'backend_login'; + $login = 'admin'; + $captchaValue = 'captcha-value'; + + /** @var Observer|MockObject $observerMock */ + $observerMock = $this->createPartialMock(Observer::class, ['getEvent']); + $eventMock = $this->getMockBuilder(Event::class) + ->addMethods(['getUsername']) + ->disableOriginalConstructor() + ->getMock(); + $captcha = $this->createMock(DefaultModel::class); + + $eventMock->method('getUsername')->willReturn('admin'); + $observerMock->method('getEvent')->willReturn($eventMock); + $captcha->method('isRequired')->with($login)->willReturn($isRequired); + $captcha->method('isCorrect')->with($captchaValue)->willReturn(true); + $this->helperMock->method('getCaptcha')->with($formId)->willReturn($captcha); + $this->captchaStringResolverMock->method('resolve')->with($this->requestMock, $formId) + ->willReturn($captchaValue); + + $this->observer->execute($observerMock); + } + + /** + * @return array + */ + public function requiredCaptchaDataProvider(): array + { + return [ + [true], + [false] + ]; + } + + /** + * Test check user login in backend with wrong captcha + * + * @return void + */ + public function testCheckOnBackendLoginWithWrongCaptcha(): void + { + $this->expectException('Magento\Framework\Exception\Plugin\AuthenticationException'); + $formId = 'backend_login'; + $login = 'admin'; + $captchaValue = 'captcha-value'; + + /** @var Observer|MockObject $observerMock */ + $observerMock = $this->createPartialMock(Observer::class, ['getEvent']); + $eventMock = $this->getMockBuilder(Event::class) + ->addMethods(['getUsername']) + ->disableOriginalConstructor() + ->getMock(); + $captcha = $this->createMock(DefaultModel::class); + + $eventMock->method('getUsername')->willReturn($login); + $observerMock->method('getEvent')->willReturn($eventMock); + $captcha->method('isRequired')->with($login)->willReturn(true); + $captcha->method('isCorrect')->with($captchaValue)->willReturn(false); + $this->helperMock->method('getCaptcha')->with($formId)->willReturn($captcha); + $this->captchaStringResolverMock->method('resolve')->with($this->requestMock, $formId) + ->willReturn($captchaValue); + + $this->observer->execute($observerMock); + } +} diff --git a/Test/Unit/Observer/CheckUserLoginObserverTest.php b/Test/Unit/Observer/CheckUserLoginObserverTest.php new file mode 100644 index 0000000..6c0a38b --- /dev/null +++ b/Test/Unit/Observer/CheckUserLoginObserverTest.php @@ -0,0 +1,187 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Captcha\Test\Unit\Observer; + +use Magento\Captcha\Helper\Data; +use Magento\Captcha\Model\DefaultModel; +use Magento\Captcha\Observer\CaptchaStringResolver; +use Magento\Captcha\Observer\CheckUserLoginObserver; +use Magento\Customer\Api\CustomerRepositoryInterface; +use Magento\Customer\Model\AuthenticationInterface; +use Magento\Customer\Model\Data\Customer; +use Magento\Customer\Model\Session; +use Magento\Customer\Model\Url; +use Magento\Framework\App\Action\Action; +use Magento\Framework\App\ActionFlag; +use Magento\Framework\App\Response\Http; +use Magento\Framework\Event\Observer; +use Magento\Framework\Message\ManagerInterface; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class CheckUserLoginObserverTest extends TestCase +{ + /** @var Data|MockObject */ + protected $helperMock; + + /** @var ActionFlag|MockObject */ + protected $actionFlagMock; + + /* @var \Magento\Framework\Message\ManagerInterface|MockObject */ + protected $messageManagerMock; + + /** @var Session|MockObject */ + protected $customerSessionMock; + + /** @var CaptchaStringResolver|MockObject */ + protected $captchaStringResolverMock; + + /** @var Url|MockObject */ + protected $customerUrlMock; + + /** @var CustomerRepositoryInterface|MockObject */ + protected $customerRepositoryMock; + + /** @var AuthenticationInterface|MockObject */ + protected $authenticationMock; + + /** @var CheckUserLoginObserver */ + protected $observer; + + /** + * Init mocks for tests + * @return void + */ + protected function setUp(): void + { + $this->helperMock = $this->createMock(Data::class); + $this->actionFlagMock = $this->createMock(ActionFlag::class); + $this->messageManagerMock = $this->getMockForAbstractClass(ManagerInterface::class); + $this->customerSessionMock = $this->getMockBuilder(Session::class) + ->addMethods(['setUsername', 'getBeforeAuthUrl']) + ->disableOriginalConstructor() + ->getMock(); + $this->captchaStringResolverMock = $this->createMock(CaptchaStringResolver::class); + $this->customerUrlMock = $this->createMock(Url::class); + $this->customerRepositoryMock = $this->getMockForAbstractClass(CustomerRepositoryInterface::class); + $this->authenticationMock = $this->getMockForAbstractClass(AuthenticationInterface::class); + + $objectManager = new ObjectManager($this); + $this->observer = $objectManager->getObject( + CheckUserLoginObserver::class, + [ + 'helper' => $this->helperMock, + 'actionFlag' => $this->actionFlagMock, + 'messageManager' => $this->messageManagerMock, + 'customerSession' => $this->customerSessionMock, + 'captchaStringResolver' => $this->captchaStringResolverMock, + 'customerUrl' => $this->customerUrlMock, + ] + ); + + $reflection = new \ReflectionClass(get_class($this->observer)); + $reflectionProperty = $reflection->getProperty('authentication'); + $reflectionProperty->setAccessible(true); + $reflectionProperty->setValue($this->observer, $this->authenticationMock); + + $reflectionProperty2 = $reflection->getProperty('customerRepository'); + $reflectionProperty2->setAccessible(true); + $reflectionProperty2->setValue($this->observer, $this->customerRepositoryMock); + } + + /** + * @return void + */ + public function testExecute() + { + $formId = 'user_login'; + $login = 'login'; + $loginParams = ['username' => $login]; + $customerId = 7; + $redirectUrl = 'http://magento.com/customer/account/login/'; + $captchaValue = 'some-value'; + + $captcha = $this->createMock(DefaultModel::class); + $captcha->expects($this->once()) + ->method('isRequired') + ->with($login) + ->willReturn(true); + $captcha->expects($this->once()) + ->method('isCorrect') + ->with($captchaValue) + ->willReturn(false); + $captcha->expects($this->once()) + ->method('logAttempt') + ->with($login); + + $this->helperMock->expects($this->once()) + ->method('getCaptcha') + ->with($formId) + ->willReturn($captcha); + + $response = $this->createMock(Http::class); + $response->expects($this->once()) + ->method('setRedirect') + ->with($redirectUrl); + + $request = $this->createMock(\Magento\Framework\App\Request\Http::class); + $request->expects($this->any()) + ->method('getPost') + ->with('login') + ->willReturn($loginParams); + + $controller = $this->createMock(Action::class); + $controller->expects($this->any())->method('getRequest')->willReturn($request); + $controller->expects($this->any())->method('getResponse')->willReturn($response); + + $this->captchaStringResolverMock->expects($this->once()) + ->method('resolve') + ->with($request, $formId) + ->willReturn($captchaValue); + + $customerDataMock = $this->createPartialMock(Customer::class, ['getId']); + $customerDataMock->expects($this->once()) + ->method('getId') + ->willReturn($customerId); + + $this->customerRepositoryMock->expects($this->once()) + ->method('get') + ->with($login) + ->willReturn($customerDataMock); + + $this->authenticationMock->expects($this->once()) + ->method('processAuthenticationFailure') + ->with($customerId); + + $this->messageManagerMock->expects($this->once()) + ->method('addErrorMessage') + ->with(__('Incorrect CAPTCHA')); + + $this->actionFlagMock->expects($this->once()) + ->method('set') + ->with('', Action::FLAG_NO_DISPATCH, true); + + $this->customerSessionMock->expects($this->once()) + ->method('setUsername') + ->with($login); + + $this->customerSessionMock->expects($this->once()) + ->method('getBeforeAuthUrl') + ->willReturn(false); + + $this->customerUrlMock->expects($this->once()) + ->method('getLoginUrl') + ->willReturn($redirectUrl); + + $this->observer->execute(new Observer(['controller_action' => $controller])); + } +} diff --git a/Test/Unit/Observer/ResetAttemptForBackendObserverTest.php b/Test/Unit/Observer/ResetAttemptForBackendObserverTest.php new file mode 100644 index 0000000..81cefdb --- /dev/null +++ b/Test/Unit/Observer/ResetAttemptForBackendObserverTest.php @@ -0,0 +1,56 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Captcha\Test\Unit\Observer; + +use Magento\Captcha\Model\ResourceModel\Log; +use Magento\Captcha\Model\ResourceModel\LogFactory; +use Magento\Captcha\Observer\ResetAttemptForBackendObserver; +use Magento\Framework\Event; +use Magento\Framework\Event\Observer; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +/** + * Unit test for \Magento\Captcha\Observer\ResetAttemptForBackendObserver + */ +class ResetAttemptForBackendObserverTest extends TestCase +{ + /** + * Test that the method resets attempts for Backend + */ + public function testExecuteExpectsDeleteUserAttemptsCalled() + { + $logMock = $this->createMock(Log::class); + $logMock->expects($this->once())->method('deleteUserAttempts')->willReturnSelf(); + + $resLogFactoryMock = $this->createMock(LogFactory::class); + $resLogFactoryMock->expects($this->once()) + ->method('create') + ->willReturn($logMock); + + /** @var MockObject|Observer $eventObserverMock */ + $eventObserverMock = $this->getMockBuilder(Observer::class) + ->addMethods(['getUser']) + ->disableOriginalConstructor() + ->getMock(); + $eventMock = $this->createMock(Event::class); + $eventObserverMock->expects($this->once()) + ->method('getUser') + ->willReturn($eventMock); + + $objectManager = new ObjectManagerHelper($this); + /** @var ResetAttemptForBackendObserver $observer */ + $observer = $objectManager->getObject( + ResetAttemptForBackendObserver::class, + ['resLogFactory' => $resLogFactoryMock] + ); + $this->assertInstanceOf(Log::class, $observer->execute($eventObserverMock)); + } +} diff --git a/Test/Unit/Observer/ResetAttemptForFrontendObserverTest.php b/Test/Unit/Observer/ResetAttemptForFrontendObserverTest.php new file mode 100644 index 0000000..1d63f99 --- /dev/null +++ b/Test/Unit/Observer/ResetAttemptForFrontendObserverTest.php @@ -0,0 +1,55 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Captcha\Test\Unit\Observer; + +use Magento\Captcha\Model\ResourceModel\Log; +use Magento\Captcha\Model\ResourceModel\LogFactory; +use Magento\Captcha\Observer\ResetAttemptForFrontendObserver; +use Magento\Customer\Model\Customer; +use Magento\Framework\Event\Observer; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +/** + * Unit test for \Magento\Captcha\Observer\ResetAttemptForFrontendObserver + */ +class ResetAttemptForFrontendObserverTest extends TestCase +{ + /** + * Test that the method resets attempts for Frontend + */ + public function testExecuteExpectsDeleteUserAttemptsCalled() + { + $logMock = $this->createMock(Log::class); + $logMock->expects($this->once())->method('deleteUserAttempts')->willReturnSelf(); + + $resLogFactoryMock = $this->createMock(LogFactory::class); + $resLogFactoryMock->expects($this->once()) + ->method('create') + ->willReturn($logMock); + + /** @var MockObject|Observer $eventObserverMock */ + $eventObserverMock = $this->getMockBuilder(Observer::class) + ->addMethods(['getModel']) + ->disableOriginalConstructor() + ->getMock(); + $eventObserverMock->expects($this->once()) + ->method('getModel') + ->willReturn($this->createMock(Customer::class)); + + $objectManager = new ObjectManagerHelper($this); + /** @var ResetAttemptForFrontendObserver $observer */ + $observer = $objectManager->getObject( + ResetAttemptForFrontendObserver::class, + ['resLogFactory' => $resLogFactoryMock] + ); + $this->assertInstanceOf(Log::class, $observer->execute($eventObserverMock)); + } +} diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..6a3d76c --- /dev/null +++ b/composer.json @@ -0,0 +1,36 @@ +{ + "name": "magenxcommerce/module-captcha", + "description": "N/A", + "type": "magento2-module", + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "config": { + "sort-packages": true + }, + "version": "100.4.2", + "require": { + "php": "~7.3.0||~7.4.0", + "magenxcommerce/framework": "103.0.*", + "magenxcommerce/module-backend": "102.0.*", + "magenxcommerce/module-checkout": "100.4.*", + "magenxcommerce/module-customer": "103.0.*", + "magenxcommerce/module-store": "101.1.*", + "magenxcommerce/module-authorization": "100.4.*", + "laminas/laminas-captcha": "^2.7.1", + "laminas/laminas-db": "^2.8.2", + "laminas/laminas-session": "^2.7.3" + }, + "autoload": { + "files": [ + "registration.php" + ], + "psr-4": { + "Magento\\Captcha\\": "" + } + }, + "replace": { + "magento/module-captcha": "*" + } +} \ No newline at end of file diff --git a/etc/adminhtml/di.xml b/etc/adminhtml/di.xml new file mode 100644 index 0000000..139bf9e --- /dev/null +++ b/etc/adminhtml/di.xml @@ -0,0 +1,31 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> + <preference for="Magento\Captcha\Block\Captcha\DefaultCaptcha" type="Magento\Captcha\Block\Adminhtml\Captcha\DefaultCaptcha" /> + <type name="Magento\Captcha\Model\DefaultModel"> + <arguments> + <argument name="session" xsi:type="object">Magento\Backend\Model\Auth\Session</argument> + <argument name="captchaData" xsi:type="object">Magento\Captcha\Helper\Adminhtml\Data</argument> + </arguments> + </type> + <type name="Magento\Captcha\Observer\CheckUserCreateObserver"> + <arguments> + <argument name="session" xsi:type="object">Magento\Backend\Model\Session</argument> + </arguments> + </type> + <type name="Magento\Captcha\Observer\CheckUserForgotPasswordBackendObserver"> + <arguments> + <argument name="session" xsi:type="object">Magento\Backend\Model\Session</argument> + </arguments> + </type> + <type name="Magento\Captcha\Observer\CheckUserLoginObserver"> + <arguments> + <argument name="session" xsi:type="object">Magento\Backend\Model\Session</argument> + </arguments> + </type> +</config> diff --git a/etc/adminhtml/events.xml b/etc/adminhtml/events.xml new file mode 100644 index 0000000..e9de9bc --- /dev/null +++ b/etc/adminhtml/events.xml @@ -0,0 +1,15 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd"> + <event name="controller_action_predispatch_adminhtml_auth_forgotpassword"> + <observer name="captcha" instance="Magento\Captcha\Observer\CheckUserForgotPasswordBackendObserver" /> + </event> + <event name="controller_action_predispatch_customer_account_forgotpasswordpost"> + <observer name="captcha" instance="Magento\Captcha\Observer\CheckForgotpasswordObserver" /> + </event> +</config> diff --git a/etc/adminhtml/routes.xml b/etc/adminhtml/routes.xml new file mode 100644 index 0000000..4d48e63 --- /dev/null +++ b/etc/adminhtml/routes.xml @@ -0,0 +1,14 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:App/etc/routes.xsd"> + <router id="admin"> + <route id="adminhtml"> + <module name="Magento_Captcha" /> + </route> + </router> +</config> diff --git a/etc/adminhtml/system.xml b/etc/adminhtml/system.xml new file mode 100644 index 0000000..bc38749 --- /dev/null +++ b/etc/adminhtml/system.xml @@ -0,0 +1,154 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/system_file.xsd"> + <system> + <section id="admin"> + <group id="captcha" translate="label" type="text" sortOrder="50" showInDefault="1" showInWebsite="1"> + <label>CAPTCHA</label> + <field id="enable" translate="label" type="select" sortOrder="1" showInDefault="1" canRestore="1"> + <label>Enable CAPTCHA in Admin</label> + <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> + </field> + <field id="font" translate="label" type="select" sortOrder="2" showInDefault="1" canRestore="1"> + <label>Font</label> + <source_model>Magento\Captcha\Model\Config\Font</source_model> + <depends> + <field id="enable">1</field> + </depends> + </field> + <field id="forms" translate="label" type="multiselect" sortOrder="3" showInDefault="1" canRestore="1"> + <label>Forms</label> + <source_model>Magento\Captcha\Model\Config\Form\Backend</source_model> + <depends> + <field id="enable">1</field> + </depends> + </field> + <field id="mode" translate="label" type="select" sortOrder="4" showInDefault="1" canRestore="1"> + <label>Displaying Mode</label> + <source_model>Magento\Captcha\Model\Config\Mode</source_model> + <depends> + <field id="enable">1</field> + </depends> + </field> + <field id="failed_attempts_login" translate="label comment" type="text" sortOrder="5" showInDefault="1" canRestore="1"> + <label>Number of Unsuccessful Attempts to Login</label> + <comment>If 0 is specified, CAPTCHA on the Login form will be always available.</comment> + <depends> + <field id="mode">after_fail</field> + <field id="enable">1</field> + </depends> + <frontend_class>required-entry validate-digits</frontend_class> + </field> + <field id="timeout" translate="label" type="text" sortOrder="6" showInDefault="1" canRestore="1"> + <label>CAPTCHA Timeout (minutes)</label> + <depends> + <field id="enable">1</field> + </depends> + <frontend_class>required-entry validate-digits</frontend_class> + </field> + <field id="length" translate="label comment" type="text" sortOrder="7" showInDefault="1" canRestore="1"> + <label>Number of Symbols</label> + <comment>Please specify 8 symbols at the most. Range allowed (e.g. 3-5)</comment> + <depends> + <field id="enable">1</field> + </depends> + <frontend_class>required-entry validate-range range-1-8</frontend_class> + </field> + <field id="symbols" translate="label comment" type="text" sortOrder="8" showInDefault="1" canRestore="1"> + <label>Symbols Used in CAPTCHA</label> + <comment> + <![CDATA[Please use only letters (a-z or A-Z) or numbers (0-9) in this field. No spaces or other characters are allowed.<br />Similar looking characters (e.g. "i", "l", "1") decrease chance of correct recognition by customer.]]> + </comment> + <depends> + <field id="enable">1</field> + </depends> + <frontend_class>required-entry validate-alphanum</frontend_class> + </field> + <field id="case_sensitive" translate="label" type="select" sortOrder="9" showInDefault="1" canRestore="1"> + <label>Case Sensitive</label> + <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> + <depends> + <field id="enable">1</field> + </depends> + </field> + </group> + </section> + <section id="customer"> + <group id="captcha" translate="label" type="text" sortOrder="110" showInDefault="1" showInWebsite="1"> + <label>CAPTCHA</label> + <field id="enable" translate="label" type="select" sortOrder="1" showInDefault="1" showInWebsite="1" canRestore="1"> + <label>Enable CAPTCHA on Storefront</label> + <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> + </field> + <field id="font" translate="label" type="select" sortOrder="2" showInDefault="1" showInWebsite="1" canRestore="1"> + <label>Font</label> + <source_model>Magento\Captcha\Model\Config\Font</source_model> + <depends> + <field id="enable">1</field> + </depends> + </field> + <field id="forms" translate="label comment" type="multiselect" sortOrder="3" showInDefault="1" showInWebsite="1" canRestore="1"> + <label>Forms</label> + <source_model>Magento\Captcha\Model\Config\Form\Frontend</source_model> + <comment>CAPTCHA for "Create user" and "Forgot password" forms is always enabled if chosen.</comment> + <depends> + <field id="enable">1</field> + </depends> + </field> + <field id="mode" translate="label" type="select" sortOrder="4" showInDefault="1" showInWebsite="1" canRestore="1"> + <label>Displaying Mode</label> + <source_model>Magento\Captcha\Model\Config\Mode</source_model> + <depends> + <field id="enable">1</field> + </depends> + </field> + <field id="failed_attempts_login" translate="label comment" type="text" sortOrder="5" showInDefault="1" showInWebsite="1" canRestore="1"> + <label>Number of Unsuccessful Attempts to Login</label> + <comment>If 0 is specified, CAPTCHA on the Login form will be always available.</comment> + <depends> + <field id="enable">1</field> + <field id="mode">after_fail</field> + </depends> + <frontend_class>required-entry validate-digits</frontend_class> + </field> + <field id="timeout" translate="label" type="text" sortOrder="6" showInDefault="1" showInWebsite="1" canRestore="1"> + <label>CAPTCHA Timeout (minutes)</label> + <depends> + <field id="enable">1</field> + </depends> + <frontend_class>required-entry validate-digits</frontend_class> + </field> + <field id="length" translate="label comment" type="text" sortOrder="7" showInDefault="1" showInWebsite="1" canRestore="1"> + <label>Number of Symbols</label> + <comment>Please specify 8 symbols at the most. Range allowed (e.g. 3-5)</comment> + <depends> + <field id="enable">1</field> + </depends> + <frontend_class>required-entry validate-range range-1-8</frontend_class> + </field> + <field id="symbols" translate="label comment" type="text" sortOrder="8" showInDefault="1" showInWebsite="1" canRestore="1"> + <label>Symbols Used in CAPTCHA</label> + <comment> + <![CDATA[Please use only letters (a-z or A-Z) or numbers (0-9) in this field. No spaces or other characters are allowed.<br />Similar looking characters (e.g. "i", "l", "1") decrease chance of correct recognition by customer.]]> + </comment> + <depends> + <field id="enable">1</field> + </depends> + <frontend_class>required-entry validate-alphanum</frontend_class> + </field> + <field id="case_sensitive" translate="label" type="select" sortOrder="9" showInDefault="1" showInWebsite="1" canRestore="1"> + <label>Case Sensitive</label> + <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> + <depends> + <field id="enable">1</field> + </depends> + </field> + </group> + </section> + </system> +</config> diff --git a/etc/config.xml b/etc/config.xml new file mode 100644 index 0000000..dd748dd --- /dev/null +++ b/etc/config.xml @@ -0,0 +1,98 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Store:etc/config.xsd"> + <default> + <system> + <media_storage_configuration> + <allowed_resources> + <captcha_folder>captcha</captcha_folder> + </allowed_resources> + </media_storage_configuration> + </system> + <admin> + <captcha> + <type>default</type> + <enable>1</enable> + <font>linlibertine</font> + <mode>after_fail</mode> + <forms>backend_forgotpassword,backend_login</forms> + <failed_attempts_login>3</failed_attempts_login> + <failed_attempts_ip>1000</failed_attempts_ip> + <timeout>7</timeout> + <length>4-5</length> + <symbols>ABCDEFGHJKMnpqrstuvwxyz23456789</symbols> + <case_sensitive>0</case_sensitive> + <shown_to_logged_in_user /> + <always_for> + <backend_forgotpassword>1</backend_forgotpassword> + </always_for> + </captcha> + </admin> + <customer> + <captcha> + <type>default</type> + <enable>1</enable> + <font>linlibertine</font> + <mode>after_fail</mode> + <forms>user_forgotpassword,user_login</forms> + <failed_attempts_login>3</failed_attempts_login> + <failed_attempts_ip>1000</failed_attempts_ip> + <timeout>7</timeout> + <length>4-5</length> + <symbols>ABCDEFGHJKMnpqrstuvwxyz23456789</symbols> + <case_sensitive>0</case_sensitive> + <shown_to_logged_in_user> + <contact_us>1</contact_us> + <user_edit>1</user_edit> + </shown_to_logged_in_user> + <always_for> + <user_create>1</user_create> + <user_forgotpassword>1</user_forgotpassword> + <contact_us>1</contact_us> + </always_for> + </captcha> + </customer> + <captcha translate="label"> + <fonts> + <linlibertine> + <label>LinLibertine</label> + <path>LinLibertineFont/LinLibertine_Bd-2.8.1.ttf</path> + </linlibertine> + </fonts> + <frontend> + <areas> + <user_create> + <label>Create user</label> + </user_create> + <user_login> + <label>Login</label> + </user_login> + <user_forgotpassword> + <label>Forgot password</label> + </user_forgotpassword> + <contact_us> + <label>Contact Us</label> + </contact_us> + <user_edit> + <label>Change password</label> + </user_edit> + </areas> + </frontend> + <backend> + <areas> + <backend_login> + <label>Admin Login</label> + </backend_login> + <backend_forgotpassword> + <label>Admin Forgot Password</label> + </backend_forgotpassword> + </areas> + </backend> + </captcha> + </default> +</config> diff --git a/etc/crontab.xml b/etc/crontab.xml new file mode 100644 index 0000000..09909ff --- /dev/null +++ b/etc/crontab.xml @@ -0,0 +1,17 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Cron:etc/crontab.xsd"> + <group id="default"> + <job name="captcha_delete_old_attempts" instance="Magento\Captcha\Cron\DeleteOldAttempts" method="execute"> + <schedule>*/30 * * * *</schedule> + </job> + <job name="captcha_delete_expired_images" instance="Magento\Captcha\Cron\DeleteExpiredImages" method="execute"> + <schedule>*/10 * * * *</schedule> + </job> + </group> +</config> diff --git a/etc/crontab/di.xml b/etc/crontab/di.xml new file mode 100644 index 0000000..4a86160 --- /dev/null +++ b/etc/crontab/di.xml @@ -0,0 +1,10 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> + <preference for="Magento\Backend\App\ConfigInterface" type="Magento\Backend\App\Config" /> +</config> diff --git a/etc/db_schema.xml b/etc/db_schema.xml new file mode 100644 index 0000000..38e0af7 --- /dev/null +++ b/etc/db_schema.xml @@ -0,0 +1,21 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<schema xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:framework:Setup/Declaration/Schema/etc/schema.xsd"> + <table name="captcha_log" resource="default" engine="innodb" comment="Count Login Attempts"> + <column xsi:type="varchar" name="type" nullable="false" length="32" comment="Type"/> + <column xsi:type="varchar" name="value" nullable="false" length="255" comment="Value"/> + <column xsi:type="int" name="count" unsigned="true" nullable="false" identity="false" default="0" + comment="Count"/> + <column xsi:type="timestamp" name="updated_at" on_update="false" nullable="true" comment="Update Time"/> + <constraint xsi:type="primary" referenceId="PRIMARY"> + <column name="type"/> + <column name="value"/> + </constraint> + </table> +</schema> diff --git a/etc/db_schema_whitelist.json b/etc/db_schema_whitelist.json new file mode 100644 index 0000000..1f5b1b6 --- /dev/null +++ b/etc/db_schema_whitelist.json @@ -0,0 +1,13 @@ +{ + "captcha_log": { + "column": { + "type": true, + "value": true, + "count": true, + "updated_at": true + }, + "constraint": { + "PRIMARY": true + } + } +} \ No newline at end of file diff --git a/etc/di.xml b/etc/di.xml new file mode 100644 index 0000000..5162f13 --- /dev/null +++ b/etc/di.xml @@ -0,0 +1,58 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> + <preference for="Magento\Captcha\Api\CaptchaConfigPostProcessorInterface" type="Magento\Captcha\Model\Filter\CaptchaConfigPostProcessorComposite"/> + <type name="Magento\Captcha\Model\DefaultModel"> + <arguments> + <argument name="session" xsi:type="object">Magento\Customer\Model\Session</argument> + </arguments> + </type> + <type name="Magento\Captcha\Observer\CheckUserCreateObserver"> + <arguments> + <argument name="session" xsi:type="object">Magento\Customer\Model\Session</argument> + </arguments> + </type> + <type name="Magento\Captcha\Observer\CheckUserForgotPasswordBackendObserver"> + <arguments> + <argument name="session" xsi:type="object">Magento\Customer\Model\Session</argument> + </arguments> + </type> + <type name="Magento\Captcha\Observer\CheckUserLoginObserver"> + <arguments> + <argument name="session" xsi:type="object">Magento\Customer\Model\Session</argument> + </arguments> + </type> + <type name="Magento\Customer\Controller\Ajax\Login"> + <plugin name="captcha_validation" type="Magento\Captcha\Model\Customer\Plugin\AjaxLogin" sortOrder="50" /> + </type> + <type name="Magento\Captcha\Model\Customer\Plugin\AjaxLogin"> + <arguments> + <argument name="formIds" xsi:type="array"> + <item name="user_login" xsi:type="string">user_login</item> + </argument> + </arguments> + </type> + <type name="Magento\Checkout\Block\Cart\Sidebar"> + <plugin name="login_captcha" type="Magento\Captcha\Model\Cart\ConfigPlugin" sortOrder="50" /> + </type> + <type name="Magento\Captcha\Model\Filter\CaptchaConfigPostProcessorComposite"> + <arguments> + <argument name="processors" xsi:type="array"> + <item name="processor" xsi:type="object">Magento\Captcha\Model\Filter\QuoteDataConfigFilter</item> + </argument> + </arguments> + </type> + <type name="Magento\Captcha\Model\Filter\QuoteDataConfigFilter"> + <arguments> + <argument name="filterList" xsi:type="array"> + <item name="remote_ip" xsi:type="string">remote_ip</item> + <item name="x_forwarded_for" xsi:type="string">x_forwarded_for</item> + </argument> + </arguments> + </type> +</config> diff --git a/etc/events.xml b/etc/events.xml new file mode 100644 index 0000000..970c0d0 --- /dev/null +++ b/etc/events.xml @@ -0,0 +1,30 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd"> + <event name="controller_action_predispatch_customer_account_loginPost"> + <observer name="captcha" instance="Magento\Captcha\Observer\CheckUserLoginObserver" /> + </event> + <event name="controller_action_predispatch_customer_account_createpost"> + <observer name="captcha" instance="Magento\Captcha\Observer\CheckUserCreateObserver" /> + </event> + <event name="controller_action_predispatch_customer_account_forgotpasswordpost"> + <observer name="captcha" instance="Magento\Captcha\Observer\CheckForgotpasswordObserver"/> + </event> + <event name="admin_user_authenticate_before"> + <observer name="captcha" instance="Magento\Captcha\Observer\CheckUserLoginBackendObserver" /> + </event> + <event name="customer_customer_authenticated"> + <observer name="captcha_reset_attempt" instance="Magento\Captcha\Observer\ResetAttemptForFrontendObserver" /> + </event> + <event name="customer_account_edited"> + <observer name="captcha_reset_attempt" instance="Magento\Captcha\Observer\ResetAttemptForFrontendAccountEditObserver" /> + </event> + <event name="backend_auth_user_login_success"> + <observer name="captcha_reset_attempt" instance="Magento\Captcha\Observer\ResetAttemptForBackendObserver" /> + </event> +</config> diff --git a/etc/frontend/di.xml b/etc/frontend/di.xml new file mode 100644 index 0000000..490f1ea --- /dev/null +++ b/etc/frontend/di.xml @@ -0,0 +1,37 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> + <type name="Magento\Checkout\Model\CompositeConfigProvider"> + <arguments> + <argument name="configProviders" xsi:type="array"> + <item name="checkout_captcha_config_provider" xsi:type="object">Magento\Captcha\Model\Checkout\ConfigProvider</item> + </argument> + </arguments> + </type> + <type name="Magento\Captcha\Model\Checkout\ConfigProvider"> + <arguments> + <argument name="formIds" xsi:type="array"> + <item name="user_login" xsi:type="string">user_login</item> + </argument> + </arguments> + </type> + <type name="Magento\Captcha\CustomerData\Captcha"> + <arguments> + <argument name="formIds" xsi:type="array"> + <item name="user_login" xsi:type="string">user_login</item> + </argument> + </arguments> + </type> + <type name="Magento\Customer\CustomerData\SectionPoolInterface"> + <arguments> + <argument name="sectionSourceMap" xsi:type="array"> + <item name="captcha" xsi:type="string">Magento\Captcha\CustomerData\Captcha</item> + </argument> + </arguments> + </type> +</config> diff --git a/etc/frontend/events.xml b/etc/frontend/events.xml new file mode 100644 index 0000000..2f318d3 --- /dev/null +++ b/etc/frontend/events.xml @@ -0,0 +1,15 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd"> + <event name="controller_action_predispatch_contact_index_post"> + <observer name="captcha_contact_us_form" instance="Magento\Captcha\Observer\CheckContactUsFormObserver" /> + </event> + <event name="controller_action_predispatch_customer_account_editpost"> + <observer name="captcha" instance="Magento\Captcha\Observer\CheckUserEditObserver"/> + </event> +</config> diff --git a/etc/frontend/routes.xml b/etc/frontend/routes.xml new file mode 100644 index 0000000..895f18f --- /dev/null +++ b/etc/frontend/routes.xml @@ -0,0 +1,14 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:App/etc/routes.xsd"> + <router id="standard"> + <route id="captcha" frontName="captcha"> + <module name="Magento_Captcha" /> + </route> + </router> +</config> diff --git a/etc/frontend/sections.xml b/etc/frontend/sections.xml new file mode 100644 index 0000000..7f2070e --- /dev/null +++ b/etc/frontend/sections.xml @@ -0,0 +1,13 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Customer:etc/sections.xsd"> + <action name="customer/ajax/login"> + <section name="captcha"/> + </action> +</config> diff --git a/etc/module.xml b/etc/module.xml new file mode 100644 index 0000000..36a44a6 --- /dev/null +++ b/etc/module.xml @@ -0,0 +1,15 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd"> + <module name="Magento_Captcha" > + <sequence> + <module name="Magento_Customer"/> + <module name="Magento_Checkout"/> + </sequence> + </module> +</config> diff --git a/i18n/en_US.csv b/i18n/en_US.csv new file mode 100644 index 0000000..ac6a7cf --- /dev/null +++ b/i18n/en_US.csv @@ -0,0 +1,27 @@ +Always,Always +"After number of attempts to login","After number of attempts to login" +"Provided form does not exist","Provided form does not exist" +"Incorrect CAPTCHA","Incorrect CAPTCHA" +"Incorrect CAPTCHA.","Incorrect CAPTCHA." +"The account is locked. Please wait and try again or contact %1.","The account is locked. Please wait and try again or contact %1." +"Please enter the letters and numbers from the image","Please enter the letters and numbers from the image" +"<strong>Attention</strong>: Captcha is case sensitive.","<strong>Attention</strong>: Captcha is case sensitive." +"Reload captcha","Reload captcha" +"Please type the letters and numbers below","Please type the letters and numbers below" +"Attention: Captcha is case sensitive.","Attention: Captcha is case sensitive." +"Please provide CAPTCHA code and try again","Please provide CAPTCHA code and try again" +CAPTCHA,CAPTCHA +"Enable CAPTCHA in Admin","Enable CAPTCHA in Admin" +Font,Font +Forms,Forms +"Displaying Mode","Displaying Mode" +"Number of Unsuccessful Attempts to Login","Number of Unsuccessful Attempts to Login" +"If 0 is specified, CAPTCHA on the Login form will be always available.","If 0 is specified, CAPTCHA on the Login form will be always available." +"CAPTCHA Timeout (minutes)","CAPTCHA Timeout (minutes)" +"Number of Symbols","Number of Symbols" +"Please specify 8 symbols at the most. Range allowed (e.g. 3-5)","Please specify 8 symbols at the most. Range allowed (e.g. 3-5)" +"Symbols Used in CAPTCHA","Symbols Used in CAPTCHA" +"Please use only letters (a-z or A-Z) or numbers (0-9) in this field. No spaces or other characters are allowed.<br />Similar looking characters (e.g. ""i"", ""l"", ""1"") decrease chance of correct recognition by customer.","Please use only letters (a-z or A-Z) or numbers (0-9) in this field. No spaces or other characters are allowed.<br />Similar looking characters (e.g. ""i"", ""l"", ""1"") decrease chance of correct recognition by customer." +"Case Sensitive","Case Sensitive" +"Enable CAPTCHA on Storefront","Enable CAPTCHA on Storefront" +"CAPTCHA for ""Create user"" and ""Forgot password"" forms is always enabled if chosen.","CAPTCHA for ""Create user"" and ""Forgot password"" forms is always enabled if chosen." diff --git a/registration.php b/registration.php new file mode 100644 index 0000000..6721e4a --- /dev/null +++ b/registration.php @@ -0,0 +1,9 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +use Magento\Framework\Component\ComponentRegistrar; + +ComponentRegistrar::register(ComponentRegistrar::MODULE, 'Magento_Captcha', __DIR__); diff --git a/view/adminhtml/layout/adminhtml_auth_forgotpassword.xml b/view/adminhtml/layout/adminhtml_auth_forgotpassword.xml new file mode 100644 index 0000000..9d77f85 --- /dev/null +++ b/view/adminhtml/layout/adminhtml_auth_forgotpassword.xml @@ -0,0 +1,24 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> + <body> + <referenceContainer name="form.additional.info"> + <block class="Magento\Captcha\Block\Captcha" name="captcha" cacheable="false"> + <action method="setFormId"> + <argument name="formId" xsi:type="string">backend_forgotpassword</argument> + </action> + <action method="setImgWidth"> + <argument name="width" xsi:type="string">226</argument> + </action> + <action method="setImgHeight"> + <argument name="width" xsi:type="string">50</argument> + </action> + </block> + </referenceContainer> + </body> +</page> diff --git a/view/adminhtml/layout/adminhtml_auth_login.xml b/view/adminhtml/layout/adminhtml_auth_login.xml new file mode 100644 index 0000000..f9ac960 --- /dev/null +++ b/view/adminhtml/layout/adminhtml_auth_login.xml @@ -0,0 +1,24 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> + <body> + <referenceContainer name="form.additional.info"> + <block class="Magento\Captcha\Block\Captcha" name="captcha" cacheable="false"> + <action method="setFormId"> + <argument name="formId" xsi:type="string">backend_login</argument> + </action> + <action method="setImgWidth"> + <argument name="width" xsi:type="string">226</argument> + </action> + <action method="setImgHeight"> + <argument name="width" xsi:type="string">50</argument> + </action> + </block> + </referenceContainer> + </body> +</page> diff --git a/view/adminhtml/templates/default.phtml b/view/adminhtml/templates/default.phtml new file mode 100644 index 0000000..6336061 --- /dev/null +++ b/view/adminhtml/templates/default.phtml @@ -0,0 +1,67 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +/** @var \Magento\Captcha\Block\Captcha\DefaultCaptcha $block */ +/** @var \Magento\Captcha\Model\DefaultModel $captcha */ +/** @var \Magento\Framework\View\Helper\SecureHtmlRenderer $secureRenderer */ + +$captcha = $block->getCaptchaModel(); +/** @var bool $validationEnabled */ +$validationEnabled = $block->hasData('frontend_validation') ? $block->getData('frontend_validation') : true; +?> +<div class="admin__field<?php if ($validationEnabled): ?> _required<?php endif; ?>"> + <label for="captcha" class="admin__field-label"> + <span><?= $block->escapeHtml(__('Please enter the letters and numbers from the image')) ?></span> + </label> + <div class="admin__field-control"> + <input + id="captcha" + class="admin__control-text" + type="text" + name="<?= $block->escapeHtmlAttr(\Magento\Captcha\Helper\Data::INPUT_NAME_FIELD_VALUE) + ?>[<?= $block->escapeHtml($block->getFormId()) ?>]" + <?php if ($validationEnabled): ?>data-validate="{required:true}"<?php endif; ?>/> + <?php if ($captcha->isCaseSensitive()):?> + <div class="admin__field-note"> + <span><?= $block->escapeHtml(__('<strong>Attention</strong>: Captcha is case sensitive.'), ['strong']) + ?></span> + </div> + <?php endif; ?> + </div> +</div> +<div class="admin__field field-captcha"> + <img + id="captcha-reload" + class="captcha-reload" + src="<?= $block->escapeUrl($block->getViewFileUrl('Magento_Captcha::reload.png')) ?>" + alt="<?= $block->escapeHtmlAttr(__('Reload captcha')) ?>"/> + <img + id="<?= $block->escapeHtmlAttr($block->getFormId()) ?>" + width="<?= /* @noEscape */ (float) $block->getImgWidth() ?>" + height="<?= /* @noEscape */ (float) $block->getImgHeight() ?>" + src="<?= $block->escapeUrl($captcha->getImgSrc()) ?>" /> +</div> + +<?php +$url = $block->escapeJs($block->getRefreshUrl()); +$formId = $block->escapeJs($block->escapeHtml($block->getFormId())); +$scriptString = <<<script + + require(["prototype", "mage/captcha"], function(){ + +//<![CDATA[ + var captcha = new Captcha('{$url}', '{$formId}'); + + $('captcha-reload').observe('click', function () { + captcha.refresh(this); + }); + +//]]> + + }); +script; +?> +<?= /* @noEscape */ $secureRenderer->renderTag('script', [], $scriptString, false) ?> diff --git a/view/adminhtml/web/reload.png b/view/adminhtml/web/reload.png new file mode 100644 index 0000000000000000000000000000000000000000..68d4bffe4ecb80b1cad55e05263a983e50814f71 GIT binary patch literal 1304 zcmV+z1?T#SP)<h;3K|Lk000e1NJLTq000;O000;W1^@s6;CDUv000EvNkl<Zcmc&$ z1CShT5X~PuS!7dlv2EM7ZQF-9$DCvPe6ej?XM3=-J}vv7{$e}Xs#n|9)BWD-9_IL` zzp=Fo=Zu(Cw`j`R$#v-U3%W7e?1m!f4GX$s?)jLCq9+4nxqgcLSwCr>1nXWM*zLBS z%Hvz^Y0mFU&|dWA5E!dYY<<M-TD}QuLVC&aeK#>Wike)#8nbRnpz4UHQhR>4QG0%m zMRQKT`MrR^7;|_`b<|tMY@B&2ZeGj!XzH7;;}<mRLVJ!nqYSjys}H+@>WDWXO}~8- z(jQ+zU@YW(Ky`E-V2v`!(a5Zua4lg$iymJULFcLVJ<jmE=jxUFtw9;!1iHJY!N8_~ zDMJTleKrBc2A+oWhZjK=<N~Djkee~hWzfZtF;s&u?|gopU~b(oJ-=l<ai&j>1dGW? zVA3hUs7OIzEaVI%uTP67Q)Nfr2Y&lhed6K<7x2RPWbZGB73)OXw^MQ6LxA5gi;yFU zIZF!}k4_V({Lc9V7^mMo`bx-bB0|}5j|71Uz&Lcw4jm^O_`%hMms`vfYCY%H)5vce z4%(~x-;$@_4hGI+IIs@AfL+s*0AtQ=AY_K!B?9<E0i2xk=nZkx%N{_^w*t7g#KqMg za;{xKxo<WY=x8F4@!%*SVpn$ncGY{Z?*<D0)vwm2l@nyXxCrv~g8?~`t!qU-8!c`8 zZbjoHeUoi!B(ctIL4C{tM<-3H02{$^8(>#9{{Wc7Nzu$3;Ciqhk-DrdhiiH_!9?lk zWSc5-&ZRkMFL{I6kOf9Q0r<^Bfn8pgfLT`O6Od!8ngHw88}Qtrd)BcTq%N<e37I6F zOvrQom*c<Z6UAeb7s`0r+Da&B0Q{z2AouPJjBOd97f6AgFZnDO;TW32E6ALo2F+<_ z(vz~Vq!tB``|XMn7s--n8<~Z+qev6CUJxO`6WT^_OaWsf1;$#6|F2O36g!8JY4L(k zA6^95k7n<W6Ayd4qKhMnw~SjT3qcarsT%06Zy_hzz{C=0PS^tHP#(CIvJZf{H!H;x zuGl?*7(ug_M~d3L71-OWsMYh?(gVyI$|QHM30W@>k;@OEj~8yBb#;Mvp+11zwJZ@f zscEoSERgYVA7C%LhJuiJB~BsdTY-5E^XEM}wD_>p)s&9+u5BR#COrz^ZiRbc8>j+D zgK}3p0(HPhVaj!{$V~>peKaWdb|Io@^6VJVbmaWtZXRAOujIvI*<JaDI_9j<Ly#SF z4vfkeboY7%iNf9e1oTOdapxq5$B<lie!eheSqtY@+8k<AGIUkDLgjLN?zeMLv2GC# z<)5g6hnSSxt6Ma|-5~4vb}(t;g@>FLr$Ug}q)UOU&;x)q%B|%sC=j`tdg(htG#Fo0 zpB3m;J#XP7lO&T+M83JKQMI$YMSY+)=*|p=%zJi%m=}V?>F0-o+OHm94GKg#QX$wo zxp9GFUwtQF!TO=a`aN4kJ>z$jHLCZP2lavSpz*5++MsF(5+g8=H6adGQ9DM6Monl` zK=j@3;z=zERQ8!Dwti?&T}a$oCY9Y+PQ&|@&*Xh8WMZ7Wy$t=>$?6)`OlwoH2KM~W zI~ngnTp0@CM1gapb5B^yRw6LoG*&biiLnN2fA#AE-Qi_d0D<xMI{qKKEmKZL?Ga7@ O0000<MNUMnLSTYU#d8h- literal 0 HcmV?d00001 diff --git a/view/frontend/layout/checkout_index_index.xml b/view/frontend/layout/checkout_index_index.xml new file mode 100644 index 0000000..7180372 --- /dev/null +++ b/view/frontend/layout/checkout_index_index.xml @@ -0,0 +1,59 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> + <body> + <referenceBlock name="checkout.root"> + <arguments> + <argument name="jsLayout" xsi:type="array"> + <item name="components" xsi:type="array"> + <item name="checkout" xsi:type="array"> + <item name="children" xsi:type="array"> + <item name="authentication" xsi:type="array"> + <item name="children" xsi:type="array"> + <item name="captcha" xsi:type="array"> + <item name="component" xsi:type="string">Magento_Captcha/js/view/checkout/loginCaptcha</item> + <item name="displayArea" xsi:type="string">additional-login-form-fields</item> + <item name="formId" xsi:type="string">user_login</item> + <item name="configSource" xsi:type="string">checkoutConfig</item> + </item> + </item> + </item> + <item name="steps" xsi:type="array"> + <item name="children" xsi:type="array"> + <item name="shipping-step" xsi:type="array"> + <item name="children" xsi:type="array"> + <item name="shippingAddress" xsi:type="array"> + <item name="children" xsi:type="array"> + <item name="customer-email" xsi:type="array"> + <item name="children" xsi:type="array"> + <item name="additional-login-form-fields" xsi:type="array"> + <item name="children" xsi:type="array"> + <item name="captcha" xsi:type="array"> + <item name="component" xsi:type="string">Magento_Captcha/js/view/checkout/loginCaptcha</item> + <item name="displayArea" xsi:type="string">additional-login-form-fields</item> + <item name="formId" xsi:type="string">user_login</item> + <item name="configSource" xsi:type="string">checkoutConfig</item> + </item> + </item> + </item> + </item> + </item> + </item> + </item> + </item> + </item> + </item> + </item> + </item> + </item> + </item> + </argument> + </arguments> + </referenceBlock> + </body> +</page> diff --git a/view/frontend/layout/contact_index_index.xml b/view/frontend/layout/contact_index_index.xml new file mode 100644 index 0000000..df6de5b --- /dev/null +++ b/view/frontend/layout/contact_index_index.xml @@ -0,0 +1,27 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> + <body> + <referenceContainer name="form.additional.info"> + <block class="Magento\Captcha\Block\Captcha" name="captcha" after="-" cacheable="false"> + <action method="setFormId"> + <argument name="formId" xsi:type="string">contact_us</argument> + </action> + <action method="setImgWidth"> + <argument name="width" xsi:type="string">230</argument> + </action> + <action method="setImgHeight"> + <argument name="width" xsi:type="string">50</argument> + </action> + </block> + </referenceContainer> + <referenceBlock name="head.components"> + <block class="Magento\Framework\View\Element\Js\Components" name="captcha_page_head_components" template="Magento_Captcha::js/components.phtml"/> + </referenceBlock> + </body> +</page> diff --git a/view/frontend/layout/customer_account_create.xml b/view/frontend/layout/customer_account_create.xml new file mode 100644 index 0000000..1ba5deb --- /dev/null +++ b/view/frontend/layout/customer_account_create.xml @@ -0,0 +1,27 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> + <body> + <referenceContainer name="form.additional.info"> + <block class="Magento\Captcha\Block\Captcha" name="captcha" after="-" cacheable="false"> + <action method="setFormId"> + <argument name="formId" xsi:type="string">user_create</argument> + </action> + <action method="setImgWidth"> + <argument name="width" xsi:type="string">230</argument> + </action> + <action method="setImgHeight"> + <argument name="width" xsi:type="string">50</argument> + </action> + </block> + </referenceContainer> + <referenceBlock name="head.components"> + <block class="Magento\Framework\View\Element\Js\Components" name="captcha_page_head_components" template="Magento_Captcha::js/components.phtml"/> + </referenceBlock> + </body> +</page> diff --git a/view/frontend/layout/customer_account_edit.xml b/view/frontend/layout/customer_account_edit.xml new file mode 100644 index 0000000..826e288 --- /dev/null +++ b/view/frontend/layout/customer_account_edit.xml @@ -0,0 +1,27 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> + <body> + <referenceContainer name="form.additional.info"> + <block class="Magento\Captcha\Block\Captcha" name="captcha" after="-" cacheable="false"> + <action method="setFormId"> + <argument name="formId" xsi:type="string">user_edit</argument> + </action> + <action method="setImgWidth"> + <argument name="width" xsi:type="string">230</argument> + </action> + <action method="setImgHeight"> + <argument name="width" xsi:type="string">50</argument> + </action> + </block> + </referenceContainer> + <referenceBlock name="head.components"> + <block class="Magento\Framework\View\Element\Js\Components" name="captcha_page_head_components" template="Magento_Captcha::js/components.phtml"/> + </referenceBlock> + </body> +</page> diff --git a/view/frontend/layout/customer_account_forgotpassword.xml b/view/frontend/layout/customer_account_forgotpassword.xml new file mode 100644 index 0000000..cbaabf6 --- /dev/null +++ b/view/frontend/layout/customer_account_forgotpassword.xml @@ -0,0 +1,27 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> + <body> + <referenceContainer name="form.additional.info"> + <block class="Magento\Captcha\Block\Captcha" name="captcha" after="-" cacheable="false"> + <action method="setFormId"> + <argument name="formId" xsi:type="string">user_forgotpassword</argument> + </action> + <action method="setImgWidth"> + <argument name="width" xsi:type="string">230</argument> + </action> + <action method="setImgHeight"> + <argument name="width" xsi:type="string">50</argument> + </action> + </block> + </referenceContainer> + <referenceBlock name="head.components"> + <block class="Magento\Framework\View\Element\Js\Components" name="captcha_page_head_components" template="Magento_Captcha::js/components.phtml"/> + </referenceBlock> + </body> +</page> diff --git a/view/frontend/layout/customer_account_login.xml b/view/frontend/layout/customer_account_login.xml new file mode 100644 index 0000000..50d99cf --- /dev/null +++ b/view/frontend/layout/customer_account_login.xml @@ -0,0 +1,27 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> + <body> + <referenceContainer name="form.additional.info"> + <block class="Magento\Captcha\Block\Captcha" name="captcha" after="-" cacheable="false"> + <action method="setFormId"> + <argument name="formId" xsi:type="string">user_login</argument> + </action> + <action method="setImgWidth"> + <argument name="width" xsi:type="string">230</argument> + </action> + <action method="setImgHeight"> + <argument name="width" xsi:type="string">50</argument> + </action> + </block> + </referenceContainer> + <referenceBlock name="head.components"> + <block class="Magento\Framework\View\Element\Js\Components" name="captcha_page_head_components" template="Magento_Captcha::js/components.phtml"/> + </referenceBlock> + </body> +</page> diff --git a/view/frontend/layout/default.xml b/view/frontend/layout/default.xml new file mode 100644 index 0000000..5015d6b --- /dev/null +++ b/view/frontend/layout/default.xml @@ -0,0 +1,29 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> + <body> + <referenceBlock name="authentication-popup"> + <arguments> + <argument name="jsLayout" xsi:type="array"> + <item name="components" xsi:type="array"> + <item name="authenticationPopup" xsi:type="array"> + <item name="children" xsi:type="array"> + <item name="captcha" xsi:type="array"> + <item name="component" xsi:type="string">Magento_Captcha/js/view/checkout/loginCaptcha</item> + <item name="displayArea" xsi:type="string">additional-login-form-fields</item> + <item name="formId" xsi:type="string">user_login</item> + <item name="configSource" xsi:type="string">checkout</item> + </item> + </item> + </item> + </item> + </argument> + </arguments> + </referenceBlock> + </body> +</page> diff --git a/view/frontend/requirejs-config.js b/view/frontend/requirejs-config.js new file mode 100644 index 0000000..42c8063 --- /dev/null +++ b/view/frontend/requirejs-config.js @@ -0,0 +1,13 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +var config = { + map: { + '*': { + captcha: 'Magento_Captcha/js/captcha', + 'Magento_Captcha/captcha': 'Magento_Captcha/js/captcha' + } + } +}; diff --git a/view/frontend/templates/default.phtml b/view/frontend/templates/default.phtml new file mode 100644 index 0000000..0d436cc --- /dev/null +++ b/view/frontend/templates/default.phtml @@ -0,0 +1,54 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +/** @var \Magento\Captcha\Block\Captcha\DefaultCaptcha $block */ + +/** @var \Magento\Captcha\Model\DefaultModel $captcha */ +$captcha = $block->getCaptchaModel(); +/** @var bool $validationEnabled */ +$validationEnabled = $block->hasData('frontend_validation') ? $block->getData('frontend_validation') : true; +$inputName = $block->escapeHtmlAttr(\Magento\Captcha\Helper\Data::INPUT_NAME_FIELD_VALUE); +$loaderUrl = $block->escapeUrl($block->getViewFileUrl('images/loader-2.gif')); +$note = $block->escapeHtml(__('<strong>Attention</strong>: Captcha is case sensitive.'), ['strong']); +?> +<div class="field captcha<?php if ($validationEnabled): ?> required<?php endif; ?>" + role="<?= $block->escapeHtmlAttr($block->getFormId()) ?>"> + <label for="captcha_<?= $block->escapeHtmlAttr($block->getFormId()) ?>" class="label"> + <span><?= $block->escapeHtml(__('Please type the letters and numbers below')) ?></span> + </label> + <div class="control captcha"> + <input + name="<?= /* @noEscape */ $inputName ?>[<?= $block->escapeHtmlAttr($block->getFormId()) ?>]" + type="text" + class="input-text<?php if ($validationEnabled): ?> required-entry<?php endif; ?>" + <?php if ($validationEnabled): ?>data-validate="{required:true}"<?php endif; ?> + id="captcha_<?= $block->escapeHtmlAttr($block->getFormId()) ?>" + autocomplete="off"/> + <div class="nested"> + <div class="field captcha no-label" + data-captcha="<?= $block->escapeHtmlAttr($block->getFormId()) ?>" + id="captcha-container-<?= $block->escapeHtmlAttr($block->getFormId()) ?>" + data-mage-init='{"captcha":{"url": "<?= $block->escapeUrl($block->getRefreshUrl()) ?>", + "imageLoader": "<?= /* @noEscape */ $loaderUrl ?>", + "type": "<?= $block->escapeHtmlAttr($block->getFormId()) ?>"}}'> + <div class="control captcha-image"> + <img alt="<?= $block->escapeHtmlAttr(__('Please type the letters and numbers below')) ?>" + class="captcha-img" + height="<?= /* @noEscape */ (float) $block->getImgHeight() ?>" + src="<?= $block->escapeUrl($captcha->getImgSrc()) ?>"/> + <button type="button" + class="action reload captcha-reload" + title="<?= $block->escapeHtmlAttr(__('Reload captcha')) ?>"> + <span><?= $block->escapeHtml(__('Reload captcha')) ?></span> + </button> + </div> + </div> + <?php if ($captcha->isCaseSensitive()):?> + <div class="captcha-note note"><?= /* @noEscape */ $note ?></div> + <?php endif; ?> + </div> + </div> +</div> diff --git a/view/frontend/templates/js/components.phtml b/view/frontend/templates/js/components.phtml new file mode 100644 index 0000000..5902a9f --- /dev/null +++ b/view/frontend/templates/js/components.phtml @@ -0,0 +1,7 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +?> +<?= $block->getChildHtml() ?> diff --git a/view/frontend/web/js/action/refresh.js b/view/frontend/web/js/action/refresh.js new file mode 100644 index 0000000..c822782 --- /dev/null +++ b/view/frontend/web/js/action/refresh.js @@ -0,0 +1,26 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +define([ + 'mage/storage' +], function (storage) { + 'use strict'; + + return function (refreshUrl, formId, imageSource) { + return storage.post( + refreshUrl, + JSON.stringify({ + 'formId': formId + }), + false + ).done( + function (response) { + if (response.imgSrc) { + imageSource(response.imgSrc); + } + } + ); + }; +}); diff --git a/view/frontend/web/js/captcha.js b/view/frontend/web/js/captcha.js new file mode 100644 index 0000000..b5e5e6b --- /dev/null +++ b/view/frontend/web/js/captcha.js @@ -0,0 +1,70 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +define([ + 'jquery', + 'jquery-ui-modules/widget' +], function ($) { + 'use strict'; + + /** + * @api + */ + $.widget('mage.captcha', { + options: { + refreshClass: 'refreshing', + reloadSelector: '.captcha-reload', + imageSelector: '.captcha-img', + imageLoader: '' + }, + + /** + * Method binds click event to reload image + * @private + */ + _create: function () { + this.element.on('click', this.options.reloadSelector, $.proxy(this.refresh, this)); + }, + + /** + * Method triggers an AJAX request to refresh the CAPTCHA image + */ + refresh: function () { + var imageLoader = this.options.imageLoader; + + if (imageLoader) { + this.element.find(this.options.imageSelector).attr('src', imageLoader); + } + this.element.addClass(this.options.refreshClass); + + $.ajax({ + url: this.options.url, + type: 'post', + async: false, + dataType: 'json', + context: this, + data: { + 'formId': this.options.type + }, + + /** + * @param {Object} response + */ + success: function (response) { + if (response.imgSrc) { + this.element.find(this.options.imageSelector).attr('src', response.imgSrc); + } + }, + + /** Complete callback. */ + complete: function () { + this.element.removeClass(this.options.refreshClass); + } + }); + } + }); + + return $.mage.captcha; +}); diff --git a/view/frontend/web/js/model/captcha.js b/view/frontend/web/js/model/captcha.js new file mode 100644 index 0000000..e79cfb3 --- /dev/null +++ b/view/frontend/web/js/model/captcha.js @@ -0,0 +1,155 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +/*global alert*/ +define([ + 'jquery', + 'ko', + 'Magento_Captcha/js/action/refresh' +], function ($, ko, refreshAction) { + 'use strict'; + + return function (captchaData) { + return { + formId: captchaData.formId, + imageSource: ko.observable(captchaData.imageSrc), + visibility: ko.observable(false), + captchaValue: ko.observable(null), + isRequired: ko.observable(captchaData.isRequired), + isCaseSensitive: captchaData.isCaseSensitive, + imageHeight: captchaData.imageHeight, + refreshUrl: captchaData.refreshUrl, + isLoading: ko.observable(false), + timestamp: null, + + /** + * @return {String} + */ + getFormId: function () { + return this.formId; + }, + + /** + * @param {String} formId + */ + setFormId: function (formId) { + this.formId = formId; + }, + + /** + * @return {Boolean} + */ + getIsVisible: function () { + return this.visibility(); + }, + + /** + * @param {Boolean} flag + */ + setIsVisible: function (flag) { + this.visibility(flag); + }, + + /** + * @return {Boolean} + */ + getIsRequired: function () { + return this.isRequired(); + }, + + /** + * @param {Boolean} flag + */ + setIsRequired: function (flag) { + this.isRequired(flag); + }, + + /** + * @return {Boolean} + */ + getIsCaseSensitive: function () { + return this.isCaseSensitive; + }, + + /** + * @param {Boolean} flag + */ + setIsCaseSensitive: function (flag) { + this.isCaseSensitive = flag; + }, + + /** + * @return {String|Number} + */ + getImageHeight: function () { + return this.imageHeight; + }, + + /** + * @param {String|Number}height + */ + setImageHeight: function (height) { + this.imageHeight = height; + }, + + /** + * @return {String} + */ + getImageSource: function () { + return this.imageSource; + }, + + /** + * @param {String} imageSource + */ + setImageSource: function (imageSource) { + this.imageSource(imageSource); + }, + + /** + * @return {String} + */ + getRefreshUrl: function () { + return this.refreshUrl; + }, + + /** + * @param {String} url + */ + setRefreshUrl: function (url) { + this.refreshUrl = url; + }, + + /** + * @return {*} + */ + getCaptchaValue: function () { + return this.captchaValue; + }, + + /** + * @param {*} value + */ + setCaptchaValue: function (value) { + this.captchaValue(value); + }, + + /** + * Refresh captcha. + */ + refresh: function () { + var refresh, + self = this; + + this.isLoading(true); + + refresh = refreshAction(this.getRefreshUrl(), this.getFormId(), this.getImageSource()); + $.when(refresh).done(function () { + self.isLoading(false); + }); + } + }; + }; +}); diff --git a/view/frontend/web/js/model/captchaList.js b/view/frontend/web/js/model/captchaList.js new file mode 100644 index 0000000..43f29f2 --- /dev/null +++ b/view/frontend/web/js/model/captchaList.js @@ -0,0 +1,44 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +define(['jquery'], function ($) { + 'use strict'; + + var captchaList = []; + + return { + /** + * @param {Object} captcha + */ + add: function (captcha) { + captchaList.push(captcha); + }, + + /** + * @param {String} formId + * @return {Object} + */ + getCaptchaByFormId: function (formId) { + var captcha = null; + + $.each(captchaList, function (key, item) { + if (formId === item.formId) { + captcha = item; + + return false; + } + }); + + return captcha; + }, + + /** + * @return {Array} + */ + getCaptchaList: function () { + return captchaList; + } + }; +}); diff --git a/view/frontend/web/js/view/checkout/defaultCaptcha.js b/view/frontend/web/js/view/checkout/defaultCaptcha.js new file mode 100644 index 0000000..d79c42a --- /dev/null +++ b/view/frontend/web/js/view/checkout/defaultCaptcha.js @@ -0,0 +1,168 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +define([ + 'jquery', + 'uiComponent', + 'Magento_Captcha/js/model/captcha', + 'Magento_Captcha/js/model/captchaList', + 'Magento_Customer/js/customer-data', + 'underscore' +], function ($, Component, Captcha, captchaList, customerData, _) { + 'use strict'; + + var captchaConfig; + + return Component.extend({ + defaults: { + template: 'Magento_Captcha/checkout/captcha' + }, + dataScope: 'global', + currentCaptcha: null, + + /** + * @return {*} + */ + captchaValue: function () { + return this.currentCaptcha.getCaptchaValue(); + }, + + /** @inheritdoc */ + initialize: function () { + this._super(); + + if (window[this.configSource] && window[this.configSource].captcha) { + captchaConfig = window[this.configSource].captcha; + $.each(captchaConfig, function (formId, captchaData) { + var captcha; + + captchaData.formId = formId; + captcha = Captcha(captchaData); + this.checkCustomerData(formId, customerData.get('captcha')(), captcha); + this.subscribeCustomerData(formId, captcha); + captchaList.add(captcha); + }.bind(this)); + } + }, + + /** + * Check customer data for captcha configuration. + * + * @param {String} formId + * @param {Object} captchaData + * @param {Object} captcha + */ + checkCustomerData: function (formId, captchaData, captcha) { + if (!_.isEmpty(captchaData) && + !_.isEmpty(captchaData)[formId] && + captchaData[formId].timestamp > captcha.timestamp + ) { + if (!captcha.isRequired() && captchaData[formId].isRequired) { + captcha.refresh(); + } + captcha.isRequired(captchaData[formId].isRequired); + captcha.timestamp = captchaData[formId].timestamp; + } + }, + + /** + * Subscribe for customer data updates. + * + * @param {String} formId + * @param {Object} captcha + */ + subscribeCustomerData: function (formId, captcha) { + customerData.get('captcha').subscribe(function (captchaData) { + this.checkCustomerData(formId, captchaData, captcha); + }.bind(this)); + }, + + /** + * @return {Boolean} + */ + getIsLoading: function () { + return this.currentCaptcha !== null ? this.currentCaptcha.isLoading : false; + }, + + /** + * @return {null|Object} + */ + getCurrentCaptcha: function () { + return this.currentCaptcha; + }, + + /** + * @param {Object} captcha + */ + setCurrentCaptcha: function (captcha) { + this.currentCaptcha = captcha; + }, + + /** + * @return {String|null} + */ + getFormId: function () { + return this.currentCaptcha !== null ? this.currentCaptcha.getFormId() : null; + }, + + /** + * @return {Boolean} + */ + getIsVisible: function () { + return this.currentCaptcha !== null ? this.currentCaptcha.getIsVisible() : false; + }, + + /** + * @param {Boolean} flag + */ + setIsVisible: function (flag) { + this.currentCaptcha.setIsVisible(flag); + }, + + /** + * @return {Boolean} + */ + isRequired: function () { + return this.currentCaptcha !== null ? this.currentCaptcha.getIsRequired() : false; + }, + + /** + * Set isRequired on current captcha model. + * + * @param {Boolean} flag + */ + setIsRequired: function (flag) { + this.currentCaptcha.setIsRequired(flag); + }, + + /** + * @return {Boolean} + */ + isCaseSensitive: function () { + return this.currentCaptcha !== null ? this.currentCaptcha.getIsCaseSensitive() : false; + }, + + /** + * @return {String|Number|null} + */ + imageHeight: function () { + return this.currentCaptcha !== null ? this.currentCaptcha.getImageHeight() : null; + }, + + /** + * @return {String|null} + */ + getImageSource: function () { + return this.currentCaptcha !== null ? this.currentCaptcha.getImageSource() : null; + }, + + /** + * Refresh captcha. + */ + refresh: function () { + this.currentCaptcha.refresh(); + } + }); +}); diff --git a/view/frontend/web/js/view/checkout/loginCaptcha.js b/view/frontend/web/js/view/checkout/loginCaptcha.js new file mode 100644 index 0000000..49528f6 --- /dev/null +++ b/view/frontend/web/js/view/checkout/loginCaptcha.js @@ -0,0 +1,39 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +define([ + 'Magento_Captcha/js/view/checkout/defaultCaptcha', + 'Magento_Captcha/js/model/captchaList', + 'Magento_Customer/js/action/login', + 'underscore' +], +function (defaultCaptcha, captchaList, loginAction, _) { + 'use strict'; + + return defaultCaptcha.extend({ + /** @inheritdoc */ + initialize: function () { + var self = this, + currentCaptcha; + + this._super(); + currentCaptcha = captchaList.getCaptchaByFormId(this.formId); + + if (currentCaptcha != null) { + currentCaptcha.setIsVisible(true); + this.setCurrentCaptcha(currentCaptcha); + + loginAction.registerLoginCallback(function (loginData) { + if (loginData['captcha_form_id'] && + loginData['captcha_form_id'] === self.formId && + self.isRequired() + ) { + _.defer(self.refresh.bind(self)); + } + }); + } + } + }); +}); diff --git a/view/frontend/web/template/checkout/captcha.html b/view/frontend/web/template/checkout/captcha.html new file mode 100644 index 0000000..3f48ec3 --- /dev/null +++ b/view/frontend/web/template/checkout/captcha.html @@ -0,0 +1,34 @@ +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<input name="captcha_form_id" type="hidden" data-bind="value: formId, attr: {'data-scope': dataScope}" /> +<!-- ko if: (isRequired() && getIsVisible())--> +<div class="field captcha required" data-bind="blockLoader: getIsLoading()"> + <label data-bind="attr: {for: 'captcha_' + formId}" class="label"><span data-bind="i18n: 'Please type the letters and numbers below'"></span></label> + <div class="control captcha"> + <input name="captcha_string" type="text" class="input-text required-entry" data-bind="value: captchaValue(), attr: {id: 'captcha_' + formId, 'data-scope': dataScope}" autocomplete="off"/> + <div class="nested"> + <div class="field captcha no-label"> + <div class="control captcha-image"> + <img data-bind="attr: { + alt: $t('Please type the letters and numbers below'), + title: $t('Please type the letters and numbers below'), + height: imageHeight(), + src: getImageSource(), + }" + class="captcha-img"/> + <button type="button" class="action reload captcha-reload" data-bind="attr: {title: $t('Reload captcha')}, click: refresh"> + <span data-bind="i18n: 'Reload captcha'"></span> + </button> + </div> + </div> + <!-- ko if: isCaseSensitive()--> + <div class="captcha-note note" data-bind="i18n: 'Attention: Captcha is case sensitive.'"></div> + <!-- /ko --> + </div> + </div> +</div> +<!-- /ko -->