Skip to content

Commit 7b1cd79

Browse files
authored
Merge pull request #3902 from magento-arcticfoxes/2.3-develop-pr
[2.3-develop] Sync with 2.3.1-release
2 parents ce17220 + 383988d commit 7b1cd79

File tree

12 files changed

+353
-77
lines changed

12 files changed

+353
-77
lines changed

app/code/Magento/Authorizenet/Model/Directpost.php

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -546,15 +546,16 @@ public function setResponseData(array $postData)
546546
public function validateResponse()
547547
{
548548
$response = $this->getResponse();
549-
//md5 check
550-
if (!$this->getConfigData('trans_md5')
551-
|| !$this->getConfigData('login')
552-
|| !$response->isValidHash($this->getConfigData('trans_md5'), $this->getConfigData('login'))
549+
$hashConfigKey = !empty($response->getData('x_SHA2_Hash')) ? 'signature_key' : 'trans_md5';
550+
551+
//hash check
552+
if (!$response->isValidHash($this->getConfigData($hashConfigKey), $this->getConfigData('login'))
553553
) {
554554
throw new \Magento\Framework\Exception\LocalizedException(
555555
__('The transaction was declined because the response hash validation failed.')
556556
);
557557
}
558+
558559
return true;
559560
}
560561

app/code/Magento/Authorizenet/Model/Directpost/Request.php

Lines changed: 105 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
namespace Magento\Authorizenet\Model\Directpost;
99

1010
use Magento\Authorizenet\Model\Request as AuthorizenetRequest;
11+
use Magento\Framework\App\ObjectManager;
12+
use Magento\Framework\Intl\DateTimeFactory;
1113

1214
/**
1315
* Authorize.net request model for DirectPost model
@@ -20,10 +22,35 @@ class Request extends AuthorizenetRequest
2022
*/
2123
protected $_transKey = null;
2224

25+
/**
26+
* Hexadecimal signature key.
27+
*
28+
* @var string
29+
*/
30+
private $signatureKey = '';
31+
32+
/**
33+
* @var DateTimeFactory
34+
*/
35+
private $dateTimeFactory;
36+
37+
/**
38+
* @param array $data
39+
* @param DateTimeFactory $dateTimeFactory
40+
*/
41+
public function __construct(
42+
array $data = [],
43+
DateTimeFactory $dateTimeFactory = null
44+
) {
45+
$this->dateTimeFactory = $dateTimeFactory ?? ObjectManager::getInstance()
46+
->get(DateTimeFactory::class);
47+
parent::__construct($data);
48+
}
49+
2350
/**
2451
* Return merchant transaction key.
2552
*
26-
* Needed to generate sign.
53+
* Needed to generate MD5 sign.
2754
*
2855
* @return string
2956
*/
@@ -35,7 +62,7 @@ protected function _getTransactionKey()
3562
/**
3663
* Set merchant transaction key.
3764
*
38-
* Needed to generate sign.
65+
* Needed to generate MD5 sign.
3966
*
4067
* @param string $transKey
4168
* @return $this
@@ -47,7 +74,7 @@ protected function _setTransactionKey($transKey)
4774
}
4875

4976
/**
50-
* Generates the fingerprint for request.
77+
* Generates the MD5 fingerprint for request.
5178
*
5279
* @param string $merchantApiLoginId
5380
* @param string $merchantTransactionKey
@@ -67,7 +94,7 @@ public function generateRequestSign(
6794
) {
6895
return hash_hmac(
6996
"md5",
70-
$merchantApiLoginId . "^" . $fpSequence . "^" . $fpTimestamp . "^" . $amount . "^" . $currencyCode,
97+
$merchantApiLoginId . '^' . $fpSequence . '^' . $fpTimestamp . '^' . $amount . '^' . $currencyCode,
7198
$merchantTransactionKey
7299
);
73100
}
@@ -82,7 +109,7 @@ public function setConstantData(\Magento\Authorizenet\Model\Directpost $paymentM
82109
{
83110
$this->setXVersion('3.1')->setXDelimData('FALSE')->setXRelayResponse('TRUE');
84111

85-
$this->setXTestRequest($paymentMethod->getConfigData('test') ? 'TRUE' : 'FALSE');
112+
$this->setSignatureKey($paymentMethod->getConfigData('signature_key'));
86113

87114
$this->setXLogin($paymentMethod->getConfigData('login'))
88115
->setXMethod(\Magento\Authorizenet\Model\Authorizenet::REQUEST_METHOD_CC)
@@ -173,17 +200,81 @@ public function setDataFromOrder(
173200
*/
174201
public function signRequestData()
175202
{
176-
$fpTimestamp = time();
177-
$hash = $this->generateRequestSign(
178-
$this->getXLogin(),
179-
$this->_getTransactionKey(),
180-
$this->getXAmount(),
181-
$this->getXCurrencyCode(),
182-
$this->getXFpSequence(),
183-
$fpTimestamp
184-
);
203+
$fpDate = $this->dateTimeFactory->create('now', new \DateTimeZone('UTC'));
204+
$fpTimestamp = $fpDate->getTimestamp();
205+
206+
if (!empty($this->getSignatureKey())) {
207+
$hash = $this->generateSha2RequestSign(
208+
(string)$this->getXLogin(),
209+
(string)$this->getSignatureKey(),
210+
(string)$this->getXAmount(),
211+
(string)$this->getXCurrencyCode(),
212+
(string)$this->getXFpSequence(),
213+
$fpTimestamp
214+
);
215+
} else {
216+
$hash = $this->generateRequestSign(
217+
$this->getXLogin(),
218+
$this->_getTransactionKey(),
219+
$this->getXAmount(),
220+
$this->getXCurrencyCode(),
221+
$this->getXFpSequence(),
222+
$fpTimestamp
223+
);
224+
}
225+
185226
$this->setXFpTimestamp($fpTimestamp);
186227
$this->setXFpHash($hash);
228+
187229
return $this;
188230
}
231+
232+
/**
233+
* Generates the SHA2 fingerprint for request.
234+
*
235+
* @param string $merchantApiLoginId
236+
* @param string $merchantSignatureKey
237+
* @param string $amount
238+
* @param string $currencyCode
239+
* @param string $fpSequence An invoice number or random number.
240+
* @param int $fpTimestamp
241+
* @return string The fingerprint.
242+
*/
243+
private function generateSha2RequestSign(
244+
string $merchantApiLoginId,
245+
string $merchantSignatureKey,
246+
string $amount,
247+
string $currencyCode,
248+
string $fpSequence,
249+
int $fpTimestamp
250+
): string {
251+
$message = $merchantApiLoginId . '^' . $fpSequence . '^' . $fpTimestamp . '^' . $amount . '^' . $currencyCode;
252+
253+
return strtoupper(hash_hmac('sha512', $message, pack('H*', $merchantSignatureKey)));
254+
}
255+
256+
/**
257+
* Return merchant hexadecimal signature key.
258+
*
259+
* Needed to generate SHA2 sign.
260+
*
261+
* @return string
262+
*/
263+
private function getSignatureKey(): string
264+
{
265+
return $this->signatureKey;
266+
}
267+
268+
/**
269+
* Set merchant hexadecimal signature key.
270+
*
271+
* Needed to generate SHA2 sign.
272+
*
273+
* @param string $signatureKey
274+
* @return void
275+
*/
276+
private function setSignatureKey(string $signatureKey)
277+
{
278+
$this->signatureKey = $signatureKey;
279+
}
189280
}

app/code/Magento/Authorizenet/Model/Directpost/Response.php

Lines changed: 64 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,25 +27,31 @@ class Response extends AuthorizenetResponse
2727
*/
2828
public function generateHash($merchantMd5, $merchantApiLogin, $amount, $transactionId)
2929
{
30-
if (!$amount) {
31-
$amount = '0.00';
32-
}
33-
3430
return strtoupper(md5($merchantMd5 . $merchantApiLogin . $transactionId . $amount));
3531
}
3632

3733
/**
3834
* Return if is valid order id.
3935
*
40-
* @param string $merchantMd5
36+
* @param string $storedHash
4137
* @param string $merchantApiLogin
4238
* @return bool
4339
*/
44-
public function isValidHash($merchantMd5, $merchantApiLogin)
40+
public function isValidHash($storedHash, $merchantApiLogin)
4541
{
46-
$hash = $this->generateHash($merchantMd5, $merchantApiLogin, $this->getXAmount(), $this->getXTransId());
42+
if (empty($this->getData('x_amount'))) {
43+
$this->setData('x_amount', '0.00');
44+
}
4745

48-
return Security::compareStrings($hash, $this->getData('x_MD5_Hash'));
46+
if (!empty($this->getData('x_SHA2_Hash'))) {
47+
$hash = $this->generateSha2Hash($storedHash);
48+
return Security::compareStrings($hash, $this->getData('x_SHA2_Hash'));
49+
} elseif (!empty($this->getData('x_MD5_Hash'))) {
50+
$hash = $this->generateHash($storedHash, $merchantApiLogin, $this->getXAmount(), $this->getXTransId());
51+
return Security::compareStrings($hash, $this->getData('x_MD5_Hash'));
52+
}
53+
54+
return false;
4955
}
5056

5157
/**
@@ -57,4 +63,54 @@ public function isApproved()
5763
{
5864
return $this->getXResponseCode() == \Magento\Authorizenet\Model\Directpost::RESPONSE_CODE_APPROVED;
5965
}
66+
67+
/**
68+
* Generates an SHA2 hash to compare against AuthNet's.
69+
*
70+
* @param string $signatureKey
71+
* @return string
72+
* @see https://support.authorize.net/s/article/MD5-Hash-End-of-Life-Signature-Key-Replacement
73+
*/
74+
private function generateSha2Hash(string $signatureKey): string
75+
{
76+
$hashFields = [
77+
'x_trans_id',
78+
'x_test_request',
79+
'x_response_code',
80+
'x_auth_code',
81+
'x_cvv2_resp_code',
82+
'x_cavv_response',
83+
'x_avs_code',
84+
'x_method',
85+
'x_account_number',
86+
'x_amount',
87+
'x_company',
88+
'x_first_name',
89+
'x_last_name',
90+
'x_address',
91+
'x_city',
92+
'x_state',
93+
'x_zip',
94+
'x_country',
95+
'x_phone',
96+
'x_fax',
97+
'x_email',
98+
'x_ship_to_company',
99+
'x_ship_to_first_name',
100+
'x_ship_to_last_name',
101+
'x_ship_to_address',
102+
'x_ship_to_city',
103+
'x_ship_to_state',
104+
'x_ship_to_zip',
105+
'x_ship_to_country',
106+
'x_invoice_num',
107+
];
108+
109+
$message = '^';
110+
foreach ($hashFields as $field) {
111+
$message .= ($this->getData($field) ?? '') . '^';
112+
}
113+
114+
return strtoupper(hash_hmac('sha512', $message, pack('H*', $signatureKey)));
115+
}
60116
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
namespace Magento\Authorizenet\Test\Unit\Model\Directpost;
7+
8+
use Magento\Authorizenet\Model\Directpost\Request;
9+
use Magento\Framework\Intl\DateTimeFactory;
10+
use PHPUnit_Framework_MockObject_MockObject as MockObject;
11+
12+
class RequestTest extends \PHPUnit\Framework\TestCase
13+
{
14+
/**
15+
* @var DateTimeFactory|MockObject
16+
*/
17+
private $dateTimeFactory;
18+
19+
/**
20+
* @var Request
21+
*/
22+
private $requestModel;
23+
24+
protected function setUp()
25+
{
26+
$this->dateTimeFactory = $this->getMockBuilder(DateTimeFactory::class)
27+
->disableOriginalConstructor()
28+
->getMock();
29+
$dateTime = new \DateTime('2016-07-05 00:00:00', new \DateTimeZone('UTC'));
30+
$this->dateTimeFactory->method('create')
31+
->willReturn($dateTime);
32+
33+
$this->requestModel = new Request([], $this->dateTimeFactory);
34+
}
35+
36+
/**
37+
* @param string $signatureKey
38+
* @param string $expectedHash
39+
* @dataProvider signRequestDataProvider
40+
*/
41+
public function testSignRequestData(string $signatureKey, string $expectedHash)
42+
{
43+
/** @var \Magento\Authorizenet\Model\Directpost $paymentMethod */
44+
$paymentMethod = $this->createMock(\Magento\Authorizenet\Model\Directpost::class);
45+
$paymentMethod->method('getConfigData')
46+
->willReturnMap(
47+
[
48+
['test', null, true],
49+
['login', null, 'login'],
50+
['trans_key', null, 'trans_key'],
51+
['signature_key', null, $signatureKey],
52+
]
53+
);
54+
55+
$this->requestModel->setConstantData($paymentMethod);
56+
$this->requestModel->signRequestData();
57+
$signHash = $this->requestModel->getXFpHash();
58+
59+
$this->assertEquals($expectedHash, $signHash);
60+
}
61+
62+
/**
63+
* @return array
64+
*/
65+
public function signRequestDataProvider()
66+
{
67+
return [
68+
[
69+
'signatureKey' => '3EAFCE5697C1B4B9748385C1FCD29D86F3B9B41C7EED85A3A01DFF65' .
70+
'70C8C29373C2A153355C3313CDF4AF723C0036DBF244A0821713A910024EE85547CEF37F',
71+
'expectedHash' => '719ED94DF5CF3510CB5531E8115462C8F12CBCC8E917BD809E8D40B4FF06' .
72+
'1E14953554403DD9813CCCE0F31B184EB4DEF558E9C0747505A0C25420372DB00BE1'
73+
],
74+
[
75+
'signatureKey' => '',
76+
'expectedHash' => '3656211f2c41d1e4c083606f326c0460'
77+
],
78+
];
79+
}
80+
}

0 commit comments

Comments
 (0)