Skip to content

Commit e0ab677

Browse files
committed
Merge remote-tracking branch 'mainline/2.4.1-develop' into 28124-graphql-order-email
2 parents 98ec18d + 8e105c7 commit e0ab677

File tree

49 files changed

+1723
-176
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+1723
-176
lines changed

app/code/Magento/Authorization/Model/CompositeUserContext.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,15 +56,15 @@ protected function add(UserContextInterface $userContext)
5656
}
5757

5858
/**
59-
* {@inheritdoc}
59+
* @inheritDoc
6060
*/
6161
public function getUserId()
6262
{
6363
return $this->getUserContext() ? $this->getUserContext()->getUserId() : null;
6464
}
6565

6666
/**
67-
* {@inheritdoc}
67+
* @inheritDoc
6868
*/
6969
public function getUserType()
7070
{
@@ -78,7 +78,7 @@ public function getUserType()
7878
*/
7979
protected function getUserContext()
8080
{
81-
if ($this->chosenUserContext === null) {
81+
if (!$this->chosenUserContext) {
8282
/** @var UserContextInterface $userContext */
8383
foreach ($this->userContexts as $userContext) {
8484
if ($userContext->getUserType() && $userContext->getUserId() !== null) {

app/code/Magento/Captcha/Model/DefaultModel.php

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77

88
namespace Magento\Captcha\Model;
99

10+
use Magento\Authorization\Model\UserContextInterface;
1011
use Magento\Captcha\Helper\Data;
12+
use Magento\Framework\App\ObjectManager;
1113
use Magento\Framework\Math\Random;
1214

1315
/**
@@ -93,27 +95,35 @@ class DefaultModel extends \Laminas\Captcha\Image implements \Magento\Captcha\Mo
9395
*/
9496
private $randomMath;
9597

98+
/**
99+
* @var UserContextInterface
100+
*/
101+
private $userContext;
102+
96103
/**
97104
* @param \Magento\Framework\Session\SessionManagerInterface $session
98105
* @param \Magento\Captcha\Helper\Data $captchaData
99106
* @param ResourceModel\LogFactory $resLogFactory
100107
* @param string $formId
101108
* @param Random $randomMath
109+
* @param UserContextInterface|null $userContext
102110
* @throws \Laminas\Captcha\Exception\ExtensionNotLoadedException
103111
*/
104112
public function __construct(
105113
\Magento\Framework\Session\SessionManagerInterface $session,
106114
\Magento\Captcha\Helper\Data $captchaData,
107115
\Magento\Captcha\Model\ResourceModel\LogFactory $resLogFactory,
108116
$formId,
109-
Random $randomMath = null
117+
Random $randomMath = null,
118+
?UserContextInterface $userContext = null
110119
) {
111120
parent::__construct();
112121
$this->session = $session;
113122
$this->captchaData = $captchaData;
114123
$this->resLogFactory = $resLogFactory;
115124
$this->formId = $formId;
116-
$this->randomMath = $randomMath ?? \Magento\Framework\App\ObjectManager::getInstance()->get(Random::class);
125+
$this->randomMath = $randomMath ?? ObjectManager::getInstance()->get(Random::class);
126+
$this->userContext = $userContext ?? ObjectManager::getInstance()->get(UserContextInterface::class);
117127
}
118128

119129
/**
@@ -152,6 +162,7 @@ public function isRequired($login = null)
152162
$this->formId,
153163
$this->getTargetForms()
154164
)
165+
|| $this->userContext->getUserType() === UserContextInterface::USER_TYPE_INTEGRATION
155166
) {
156167
return false;
157168
}
@@ -241,7 +252,7 @@ private function isOverLimitLoginAttempts($login)
241252
*/
242253
private function isUserAuth()
243254
{
244-
return $this->session->isLoggedIn();
255+
return $this->session->isLoggedIn() || $this->userContext->getUserId();
245256
}
246257

247258
/**
@@ -427,7 +438,7 @@ public function getWordLen()
427438
$to = self::DEFAULT_WORD_LENGTH_TO;
428439
}
429440

430-
return \Magento\Framework\Math\Random::getRandomNumber($from, $to);
441+
return Random::getRandomNumber($from, $to);
431442
}
432443

433444
/**
@@ -549,7 +560,7 @@ private function clearWord()
549560
*/
550561
protected function randomSize()
551562
{
552-
return \Magento\Framework\Math\Random::getRandomNumber(280, 300) / 100;
563+
return Random::getRandomNumber(280, 300) / 100;
553564
}
554565

555566
/**

app/code/Magento/Captcha/Observer/CaptchaStringResolver.php

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,14 @@
33
* Copyright © Magento, Inc. All rights reserved.
44
* See COPYING.txt for license details.
55
*/
6+
7+
declare(strict_types=1);
8+
69
namespace Magento\Captcha\Observer;
710

811
use Magento\Framework\App\RequestInterface;
912
use Magento\Framework\App\Request\Http as HttpRequest;
13+
use Magento\Captcha\Helper\Data as CaptchaHelper;
1014

1115
/**
1216
* Extract given captcha word.
@@ -22,12 +26,13 @@ class CaptchaStringResolver
2226
*/
2327
public function resolve(RequestInterface $request, $formId)
2428
{
25-
$captchaParams = $request->getPost(\Magento\Captcha\Helper\Data::INPUT_NAME_FIELD_VALUE);
29+
$value = '';
30+
$captchaParams = $request->getPost(CaptchaHelper::INPUT_NAME_FIELD_VALUE);
2631
if (!empty($captchaParams) && !empty($captchaParams[$formId])) {
2732
$value = $captchaParams[$formId];
28-
} else {
29-
//For Web APIs
30-
$value = $request->getHeader('X-Captcha');
33+
} elseif ($headerValue = $request->getHeader('X-Captcha')) {
34+
//CAPTCHA was provided via header for this XHR/web API request.
35+
$value = $headerValue;
3136
}
3237

3338
return $value;

app/code/Magento/Captcha/Test/Unit/Model/DefaultTest.php

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
namespace Magento\Captcha\Test\Unit\Model;
99

10+
use Magento\Authorization\Model\UserContextInterface;
1011
use Magento\Captcha\Block\Captcha\DefaultCaptcha;
1112
use Magento\Captcha\Helper\Data;
1213
use Magento\Captcha\Model\DefaultModel;
@@ -93,10 +94,15 @@ class DefaultTest extends TestCase
9394
protected $session;
9495

9596
/**
96-
* @var MockObject
97+
* @var MockObject|LogFactory
9798
*/
9899
protected $_resLogFactory;
99100

101+
/**
102+
* @var UserContextInterface|MockObject
103+
*/
104+
private $userContextMock;
105+
100106
/**
101107
* Sets up the fixture, for example, opens a network connection.
102108
* This method is called before a test is executed.
@@ -139,11 +145,18 @@ protected function setUp(): void
139145
$this->_getResourceModelStub()
140146
);
141147

148+
$randomMock = $this->createMock(Random::class);
149+
$randomMock->method('getRandomString')->willReturn('random-string');
150+
151+
$this->userContextMock = $this->getMockForAbstractClass(UserContextInterface::class);
152+
142153
$this->_object = new DefaultModel(
143154
$this->session,
144155
$this->_getHelperStub(),
145156
$this->_resLogFactory,
146-
'user_create'
157+
'user_create',
158+
$randomMock,
159+
$this->userContextMock
147160
);
148161
}
149162

@@ -163,6 +176,19 @@ public function testIsRequired()
163176
$this->assertTrue($this->_object->isRequired());
164177
}
165178

179+
/**
180+
* Validate that CAPTCHA is disabled for integrations.
181+
*
182+
* @return void
183+
*/
184+
public function testIsRequiredForIntegration(): void
185+
{
186+
$this->userContextMock->method('getUserType')->willReturn(UserContextInterface::USER_TYPE_INTEGRATION);
187+
$this->userContextMock->method('getUserId')->willReturn(1);
188+
189+
$this->assertFalse($this->_object->isRequired());
190+
}
191+
166192
/**
167193
* @covers \Magento\Captcha\Model\DefaultModel::isCaseSensitive
168194
*/

app/code/Magento/Captcha/composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
"magento/module-checkout": "*",
1212
"magento/module-customer": "*",
1313
"magento/module-store": "*",
14+
"magento/module-authorization": "*",
1415
"laminas/laminas-captcha": "^2.7.1",
1516
"laminas/laminas-db": "^2.8.2",
1617
"laminas/laminas-session": "^2.7.3"

app/code/Magento/Captcha/i18n/en_US.csv

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ Always,Always
99
"Reload captcha","Reload captcha"
1010
"Please type the letters and numbers below","Please type the letters and numbers below"
1111
"Attention: Captcha is case sensitive.","Attention: Captcha is case sensitive."
12+
"Please provide CAPTCHA code and try again","Please provide CAPTCHA code and try again"
1213
CAPTCHA,CAPTCHA
1314
"Enable CAPTCHA in Admin","Enable CAPTCHA in Admin"
1415
Font,Font
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
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\Exception;
10+
11+
use Magento\Framework\Exception\LocalizedException;
12+
13+
/**
14+
* Thrown when too many payment processing requests have been initiated by a user.
15+
*/
16+
class PaymentProcessingRateLimitExceededException extends LocalizedException
17+
{
18+
19+
}
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 initiate payment processing.
15+
*/
16+
interface PaymentProcessingRateLimiterInterface
17+
{
18+
/**
19+
* Limit an attempt to initiate a new payment processing.
20+
*
21+
* @return void
22+
* @throws PaymentProcessingRateLimitExceededException
23+
*/
24+
public function limit(): void;
25+
}
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
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\Authorization\Model\UserContextInterface;
12+
use Magento\Checkout\Api\Exception\PaymentProcessingRateLimitExceededException;
13+
use Magento\Checkout\Api\PaymentProcessingRateLimiterInterface;
14+
use Magento\Customer\Api\CustomerRepositoryInterface;
15+
use Magento\Captcha\Model\DefaultModel as Captcha;
16+
use Magento\Captcha\Helper\Data as CaptchaHelper;
17+
use Magento\Captcha\Observer\CaptchaStringResolver as CaptchaResolver;
18+
use Magento\Framework\App\RequestInterface;
19+
20+
/**
21+
* Utilize CAPTCHA as a rate-limiting mechanism.
22+
*/
23+
class CaptchaPaymentProcessingRateLimiter implements PaymentProcessingRateLimiterInterface
24+
{
25+
public const CAPTCHA_FORM = 'payment_processing_request';
26+
27+
/**
28+
* @var UserContextInterface
29+
*/
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;
51+
52+
/**
53+
* CaptchaPaymentProcessingRateLimiter constructor.
54+
*
55+
* @param UserContextInterface $userContext
56+
* @param CustomerRepositoryInterface $customerRepo
57+
* @param CaptchaHelper $captchaHelper
58+
* @param RequestInterface $request
59+
* @param CaptchaResolver $captchaResolver
60+
*/
61+
public function __construct(
62+
UserContextInterface $userContext,
63+
CustomerRepositoryInterface $customerRepo,
64+
CaptchaHelper $captchaHelper,
65+
RequestInterface $request,
66+
CaptchaResolver $captchaResolver
67+
) {
68+
$this->userContext = $userContext;
69+
$this->customerRepo = $customerRepo;
70+
$this->captchaHelper = $captchaHelper;
71+
$this->request = $request;
72+
$this->captchaResolver = $captchaResolver;
73+
}
74+
75+
/**
76+
* @inheritDoc
77+
*/
78+
public function limit(): void
79+
{
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;
85+
}
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;
122+
}
123+
}

0 commit comments

Comments
 (0)