Skip to content

Commit 214b4df

Browse files
authored
Merge pull request #6388 from magento-cia/cia-2.4.2-11242020
[cia] MC-38032: Captcha Improvement
2 parents 9b20426 + 84af0a8 commit 214b4df

File tree

20 files changed

+395
-171
lines changed

20 files changed

+395
-171
lines changed

app/code/Magento/Captcha/view/adminhtml/templates/default.phtml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@
99
/** @var \Magento\Framework\View\Helper\SecureHtmlRenderer $secureRenderer */
1010

1111
$captcha = $block->getCaptchaModel();
12+
/** @var bool $validationEnabled */
13+
$validationEnabled = $block->hasData('frontend_validation') ? $block->getData('frontend_validation') : true;
1214
?>
13-
<div class="admin__field _required">
15+
<div class="admin__field<?php if ($validationEnabled): ?> _required<?php endif; ?>">
1416
<label for="captcha" class="admin__field-label">
1517
<span><?= $block->escapeHtml(__('Please enter the letters and numbers from the image')) ?></span>
1618
</label>
@@ -21,7 +23,7 @@ $captcha = $block->getCaptchaModel();
2123
type="text"
2224
name="<?= $block->escapeHtmlAttr(\Magento\Captcha\Helper\Data::INPUT_NAME_FIELD_VALUE)
2325
?>[<?= $block->escapeHtml($block->getFormId()) ?>]"
24-
data-validate="{required:true}"/>
26+
<?php if ($validationEnabled): ?>data-validate="{required:true}"<?php endif; ?>/>
2527
<?php if ($captcha->isCaseSensitive()):?>
2628
<div class="admin__field-note">
2729
<span><?= $block->escapeHtml(__('<strong>Attention</strong>: Captcha is case sensitive.'), ['strong'])

app/code/Magento/Captcha/view/frontend/templates/default.phtml

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,27 +8,46 @@
88

99
/** @var \Magento\Captcha\Model\DefaultModel $captcha */
1010
$captcha = $block->getCaptchaModel();
11+
/** @var bool $validationEnabled */
12+
$validationEnabled = $block->hasData('frontend_validation') ? $block->getData('frontend_validation') : true;
13+
$inputName = $block->escapeHtmlAttr(\Magento\Captcha\Helper\Data::INPUT_NAME_FIELD_VALUE);
14+
$loaderUrl = $block->escapeUrl($block->getViewFileUrl('images/loader-2.gif'));
15+
$note = $block->escapeHtml(__('<strong>Attention</strong>: Captcha is case sensitive.'), ['strong']);
1116
?>
12-
<div class="field captcha required" role="<?= $block->escapeHtmlAttr($block->getFormId()) ?>">
13-
<label for="captcha_<?= $block->escapeHtmlAttr($block->getFormId()) ?>" class="label"><span><?= $block->escapeHtml(__('Please type the letters and numbers below')) ?></span></label>
17+
<div class="field captcha<?php if ($validationEnabled): ?> required<?php endif; ?>"
18+
role="<?= $block->escapeHtmlAttr($block->getFormId()) ?>">
19+
<label for="captcha_<?= $block->escapeHtmlAttr($block->getFormId()) ?>" class="label">
20+
<span><?= $block->escapeHtml(__('Please type the letters and numbers below')) ?></span>
21+
</label>
1422
<div class="control captcha">
15-
<input name="<?= $block->escapeHtmlAttr(\Magento\Captcha\Helper\Data::INPUT_NAME_FIELD_VALUE) ?>[<?= $block->escapeHtmlAttr($block->getFormId()) ?>]" type="text" class="input-text required-entry" data-validate="{required:true}" id="captcha_<?= $block->escapeHtmlAttr($block->getFormId()) ?>" autocomplete="off"/>
23+
<input
24+
name="<?= /* @noEscape */ $inputName ?>[<?= $block->escapeHtmlAttr($block->getFormId()) ?>]"
25+
type="text"
26+
class="input-text<?php if ($validationEnabled): ?> required-entry<?php endif; ?>"
27+
<?php if ($validationEnabled): ?>data-validate="{required:true}"<?php endif; ?>
28+
id="captcha_<?= $block->escapeHtmlAttr($block->getFormId()) ?>"
29+
autocomplete="off"/>
1630
<div class="nested">
1731
<div class="field captcha no-label"
1832
data-captcha="<?= $block->escapeHtmlAttr($block->getFormId()) ?>"
1933
id="captcha-container-<?= $block->escapeHtmlAttr($block->getFormId()) ?>"
2034
data-mage-init='{"captcha":{"url": "<?= $block->escapeUrl($block->getRefreshUrl()) ?>",
21-
"imageLoader": "<?= $block->escapeUrl($block->getViewFileUrl('images/loader-2.gif')) ?>",
35+
"imageLoader": "<?= /* @noEscape */ $loaderUrl ?>",
2236
"type": "<?= $block->escapeHtmlAttr($block->getFormId()) ?>"}}'>
2337
<div class="control captcha-image">
24-
<img alt="<?= $block->escapeHtmlAttr(__('Please type the letters and numbers below')) ?>" class="captcha-img" height="<?= /* @noEscape */ (float) $block->getImgHeight() ?>" src="<?= $block->escapeUrl($captcha->getImgSrc()) ?>"/>
25-
<button type="button" class="action reload captcha-reload" title="<?= $block->escapeHtmlAttr(__('Reload captcha')) ?>"><span><?= $block->escapeHtml(__('Reload captcha')) ?></span></button>
38+
<img alt="<?= $block->escapeHtmlAttr(__('Please type the letters and numbers below')) ?>"
39+
class="captcha-img"
40+
height="<?= /* @noEscape */ (float) $block->getImgHeight() ?>"
41+
src="<?= $block->escapeUrl($captcha->getImgSrc()) ?>"/>
42+
<button type="button"
43+
class="action reload captcha-reload"
44+
title="<?= $block->escapeHtmlAttr(__('Reload captcha')) ?>">
45+
<span><?= $block->escapeHtml(__('Reload captcha')) ?></span>
46+
</button>
2647
</div>
2748
</div>
28-
<?php if ($captcha->isCaseSensitive()) :?>
29-
<div class="captcha-note note">
30-
<?= $block->escapeHtml(__('<strong>Attention</strong>: Captcha is case sensitive.'), ['strong']) ?>
31-
</div>
49+
<?php if ($captcha->isCaseSensitive()):?>
50+
<div class="captcha-note note"><?= /* @noEscape */ $note ?></div>
3251
<?php endif; ?>
3352
</div>
3453
</div>

app/code/Magento/Checkout/Api/Exception/PaymentProcessingRateLimitExceededException.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
use Magento\Framework\Exception\LocalizedException;
1212

1313
/**
14-
* Thrown when too many payment processing requests have been initiated by a user.
14+
* Thrown when too many payment processing/saving requests have been initiated by a user.
1515
*/
1616
class PaymentProcessingRateLimitExceededException extends LocalizedException
1717
{
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
7+
declare(strict_types=1);
8+
9+
namespace Magento\Checkout\Api;
10+
11+
use Magento\Checkout\Api\Exception\PaymentProcessingRateLimitExceededException;
12+
13+
/**
14+
* Limits number of times a user can store payment method info.
15+
*/
16+
interface PaymentSavingRateLimiterInterface
17+
{
18+
/**
19+
* Limit an attempt.
20+
*
21+
* @return void
22+
* @throws PaymentProcessingRateLimitExceededException
23+
*/
24+
public function limit(): void;
25+
}

app/code/Magento/Checkout/Model/CaptchaPaymentProcessingRateLimiter.php

Lines changed: 21 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -12,42 +12,23 @@
1212
use Magento\Checkout\Api\Exception\PaymentProcessingRateLimitExceededException;
1313
use Magento\Checkout\Api\PaymentProcessingRateLimiterInterface;
1414
use Magento\Customer\Api\CustomerRepositoryInterface;
15-
use Magento\Captcha\Model\DefaultModel as Captcha;
1615
use Magento\Captcha\Helper\Data as CaptchaHelper;
1716
use Magento\Captcha\Observer\CaptchaStringResolver as CaptchaResolver;
17+
use Magento\Framework\App\ObjectManager;
1818
use Magento\Framework\App\RequestInterface;
19+
use Magento\Framework\Exception\LocalizedException;
1920

2021
/**
21-
* Utilize CAPTCHA as a rate-limiting mechanism.
22+
* Utilize CAPTCHA to limit payment processing requests.
2223
*/
2324
class CaptchaPaymentProcessingRateLimiter implements PaymentProcessingRateLimiterInterface
2425
{
2526
public const CAPTCHA_FORM = 'payment_processing_request';
2627

2728
/**
28-
* @var UserContextInterface
29+
* @var CaptchaRateLimiter
2930
*/
30-
private $userContext;
31-
32-
/**
33-
* @var CustomerRepositoryInterface
34-
*/
35-
private $customerRepo;
36-
37-
/**
38-
* @var CaptchaHelper
39-
*/
40-
private $captchaHelper;
41-
42-
/**
43-
* @var RequestInterface
44-
*/
45-
private $request;
46-
47-
/**
48-
* @var CaptchaResolver
49-
*/
50-
private $captchaResolver;
31+
private $limiter;
5132

5233
/**
5334
* CaptchaPaymentProcessingRateLimiter constructor.
@@ -57,67 +38,36 @@ class CaptchaPaymentProcessingRateLimiter implements PaymentProcessingRateLimite
5738
* @param CaptchaHelper $captchaHelper
5839
* @param RequestInterface $request
5940
* @param CaptchaResolver $captchaResolver
41+
* @param CaptchaRateLimiterFactory|null $limiterFactory
6042
*/
6143
public function __construct(
6244
UserContextInterface $userContext,
6345
CustomerRepositoryInterface $customerRepo,
6446
CaptchaHelper $captchaHelper,
6547
RequestInterface $request,
66-
CaptchaResolver $captchaResolver
48+
CaptchaResolver $captchaResolver,
49+
?CaptchaRateLimiterFactory $limiterFactory
6750
) {
68-
$this->userContext = $userContext;
69-
$this->customerRepo = $customerRepo;
70-
$this->captchaHelper = $captchaHelper;
71-
$this->request = $request;
72-
$this->captchaResolver = $captchaResolver;
51+
$limiterFactory = $limiterFactory ?? ObjectManager::getInstance()->get(CaptchaRateLimiterFactory::class);
52+
$this->limiter = $limiterFactory->create([
53+
'userContext' => $userContext,
54+
'customerRepo' => $customerRepo,
55+
'captchaHelper' => $captchaHelper,
56+
'captchaResolver' => $captchaResolver,
57+
'request' => $request,
58+
'captchaId' => self::CAPTCHA_FORM
59+
]);
7360
}
7461

7562
/**
7663
* @inheritDoc
7764
*/
7865
public function limit(): void
7966
{
80-
if ($this->userContext->getUserType() !== UserContextInterface::USER_TYPE_GUEST
81-
&& $this->userContext->getUserType() !== UserContextInterface::USER_TYPE_CUSTOMER
82-
&& $this->userContext->getUserType() !== null
83-
) {
84-
return;
67+
try {
68+
$this->limiter->limit();
69+
} catch (LocalizedException $exception) {
70+
throw new PaymentProcessingRateLimitExceededException(__($exception->getMessage()), $exception);
8571
}
86-
87-
$login = $this->retrieveLogin();
88-
/** @var Captcha $captcha */
89-
$captcha = $this->captchaHelper->getCaptcha(self::CAPTCHA_FORM);
90-
/** @var PaymentProcessingRateLimitExceededException|null $exception */
91-
$exception = null;
92-
if ($captcha->isRequired($login)) {
93-
$value = $this->captchaResolver->resolve($this->request, self::CAPTCHA_FORM);
94-
if ($value && !$captcha->isCorrect($value)) {
95-
$exception = new PaymentProcessingRateLimitExceededException(__('Incorrect CAPTCHA'));
96-
} elseif (!$value) {
97-
$exception = new PaymentProcessingRateLimitExceededException(
98-
__('Please provide CAPTCHA code and try again')
99-
);
100-
}
101-
}
102-
103-
$captcha->logAttempt($login);
104-
if ($exception) {
105-
throw $exception;
106-
}
107-
}
108-
109-
/**
110-
* Retrieve current user login.
111-
*
112-
* @return string|null
113-
*/
114-
private function retrieveLogin(): ?string
115-
{
116-
$login = null;
117-
if ($this->userContext->getUserId()) {
118-
$login = $this->customerRepo->getById($this->userContext->getUserId())->getEmail();
119-
}
120-
121-
return $login;
12272
}
12373
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
7+
declare(strict_types=1);
8+
9+
namespace Magento\Checkout\Model;
10+
11+
use Magento\Checkout\Api\Exception\PaymentProcessingRateLimitExceededException;
12+
use Magento\Checkout\Api\PaymentSavingRateLimiterInterface;
13+
use Magento\Framework\Exception\LocalizedException;
14+
15+
/**
16+
* Utilize CAPTCHA to limit save payment requests.
17+
*/
18+
class CaptchaPaymentSavingRateLimiter implements PaymentSavingRateLimiterInterface
19+
{
20+
public const CAPTCHA_FORM = 'payment_saving_request';
21+
22+
/**
23+
* @var CaptchaRateLimiter
24+
*/
25+
private $limiter;
26+
27+
/**
28+
* CaptchaPaymentProcessingRateLimiter constructor.
29+
*
30+
* @param CaptchaRateLimiterFactory $limiterFactory
31+
*/
32+
public function __construct(
33+
CaptchaRateLimiterFactory $limiterFactory
34+
) {
35+
$this->limiter = $limiterFactory->create(['captchaId' => self::CAPTCHA_FORM]);
36+
}
37+
38+
/**
39+
* @inheritDoc
40+
*/
41+
public function limit(): void
42+
{
43+
try {
44+
$this->limiter->limit();
45+
} catch (LocalizedException $exception) {
46+
throw new PaymentProcessingRateLimitExceededException(
47+
__(
48+
'Could not store billing/shipping information at the moment'
49+
.' but you can proceed with the checkout'
50+
),
51+
$exception
52+
);
53+
}
54+
}
55+
}

0 commit comments

Comments
 (0)