Skip to content

Commit b605ff2

Browse files
committed
Merge remote-tracking branch 'origin/MC-15012' into 2.2.8-develop-pr81
2 parents 7c26834 + 4322e6a commit b605ff2

File tree

16 files changed

+334
-91
lines changed

16 files changed

+334
-91
lines changed

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

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -543,15 +543,16 @@ public function setResponseData(array $postData)
543543
public function validateResponse()
544544
{
545545
$response = $this->getResponse();
546-
//md5 check
547-
if (!$this->getConfigData('trans_md5')
548-
|| !$this->getConfigData('login')
549-
|| !$response->isValidHash($this->getConfigData('trans_md5'), $this->getConfigData('login'))
546+
$hashConfigKey = !empty($response->getData('x_SHA2_Hash')) ? 'signature_key' : 'trans_md5';
547+
548+
//hash check
549+
if (!$response->isValidHash($this->getConfigData($hashConfigKey), $this->getConfigData('login'))
550550
) {
551551
throw new \Magento\Framework\Exception\LocalizedException(
552552
__('The transaction was declined because the response hash validation failed.')
553553
);
554554
}
555+
555556
return true;
556557
}
557558

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

Lines changed: 103 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
namespace Magento\Authorizenet\Model\Directpost;
88

99
use Magento\Authorizenet\Model\Request as AuthorizenetRequest;
10+
use Magento\Framework\Intl\DateTimeFactory;
1011

1112
/**
1213
* Authorize.net request model for DirectPost model
@@ -18,9 +19,33 @@ class Request extends AuthorizenetRequest
1819
*/
1920
protected $_transKey = null;
2021

22+
/**
23+
* Hexadecimal signature key.
24+
*
25+
* @var string
26+
*/
27+
private $signatureKey = '';
28+
29+
/**
30+
* @var DateTimeFactory
31+
*/
32+
private $dateTimeFactory;
33+
34+
/**
35+
* @param DateTimeFactory $dateTimeFactory
36+
* @param array $data
37+
*/
38+
public function __construct(
39+
DateTimeFactory $dateTimeFactory,
40+
array $data = []
41+
) {
42+
$this->dateTimeFactory = $dateTimeFactory;
43+
parent::__construct($data);
44+
}
45+
2146
/**
2247
* Return merchant transaction key.
23-
* Needed to generate sign.
48+
* Needed to generate MD5 sign.
2449
*
2550
* @return string
2651
*/
@@ -31,7 +56,7 @@ protected function _getTransactionKey()
3156

3257
/**
3358
* Set merchant transaction key.
34-
* Needed to generate sign.
59+
* Needed to generate MD5 sign.
3560
*
3661
* @param string $transKey
3762
* @return $this
@@ -43,7 +68,7 @@ protected function _setTransactionKey($transKey)
4368
}
4469

4570
/**
46-
* Generates the fingerprint for request.
71+
* Generates the MD5 fingerprint for request.
4772
*
4873
* @param string $merchantApiLoginId
4974
* @param string $merchantTransactionKey
@@ -63,7 +88,7 @@ public function generateRequestSign(
6388
) {
6489
return hash_hmac(
6590
"md5",
66-
$merchantApiLoginId . "^" . $fpSequence . "^" . $fpTimestamp . "^" . $amount . "^" . $currencyCode,
91+
$merchantApiLoginId . '^' . $fpSequence . '^' . $fpTimestamp . '^' . $amount . '^' . $currencyCode,
6792
$merchantTransactionKey
6893
);
6994
}
@@ -85,6 +110,7 @@ public function setConstantData(\Magento\Authorizenet\Model\Directpost $paymentM
85110
->setXRelayUrl($paymentMethod->getRelayUrl());
86111

87112
$this->_setTransactionKey($paymentMethod->getConfigData('trans_key'));
113+
$this->setSignatureKey($paymentMethod->getConfigData('signature_key'));
88114
return $this;
89115
}
90116

@@ -168,17 +194,81 @@ public function setDataFromOrder(
168194
*/
169195
public function signRequestData()
170196
{
171-
$fpTimestamp = time();
172-
$hash = $this->generateRequestSign(
173-
$this->getXLogin(),
174-
$this->_getTransactionKey(),
175-
$this->getXAmount(),
176-
$this->getXCurrencyCode(),
177-
$this->getXFpSequence(),
178-
$fpTimestamp
179-
);
197+
$fpDate = $this->dateTimeFactory->create('now', new \DateTimeZone('UTC'));
198+
$fpTimestamp = $fpDate->getTimestamp();
199+
200+
if (!empty($this->getSignatureKey())) {
201+
$hash = $this->generateSha2RequestSign(
202+
$this->getXLogin(),
203+
$this->getSignatureKey(),
204+
$this->getXAmount(),
205+
$this->getXCurrencyCode(),
206+
$this->getXFpSequence(),
207+
$fpTimestamp
208+
);
209+
} else {
210+
$hash = $this->generateRequestSign(
211+
$this->getXLogin(),
212+
$this->_getTransactionKey(),
213+
$this->getXAmount(),
214+
$this->getXCurrencyCode(),
215+
$this->getXFpSequence(),
216+
$fpTimestamp
217+
);
218+
}
219+
180220
$this->setXFpTimestamp($fpTimestamp);
181221
$this->setXFpHash($hash);
222+
182223
return $this;
183224
}
225+
226+
/**
227+
* Generates the SHA2 fingerprint for request.
228+
*
229+
* @param string $merchantApiLoginId
230+
* @param string $merchantSignatureKey
231+
* @param string $amount
232+
* @param string $currencyCode
233+
* @param string $fpSequence An invoice number or random number.
234+
* @param string $fpTimestamp
235+
* @return string The fingerprint.
236+
*/
237+
private function generateSha2RequestSign(
238+
$merchantApiLoginId,
239+
$merchantSignatureKey,
240+
$amount,
241+
$currencyCode,
242+
$fpSequence,
243+
$fpTimestamp
244+
): string {
245+
$message = $merchantApiLoginId . '^' . $fpSequence . '^' . $fpTimestamp . '^' . $amount . '^' . $currencyCode;
246+
247+
return strtoupper(hash_hmac('sha512', $message, pack('H*', $merchantSignatureKey)));
248+
}
249+
250+
/**
251+
* Return merchant hexadecimal signature key.
252+
*
253+
* Needed to generate SHA2 sign.
254+
*
255+
* @return string
256+
*/
257+
private function getSignatureKey(): string
258+
{
259+
return $this->signatureKey;
260+
}
261+
262+
/**
263+
* Set merchant hexadecimal signature key.
264+
*
265+
* Needed to generate SHA2 sign.
266+
*
267+
* @param string $signatureKey
268+
* @return void
269+
*/
270+
private function setSignatureKey(string $signatureKey)
271+
{
272+
$this->signatureKey = $signatureKey;
273+
}
184274
}

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

Lines changed: 64 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,25 +24,31 @@ class Response extends AuthorizenetResponse
2424
*/
2525
public function generateHash($merchantMd5, $merchantApiLogin, $amount, $transactionId)
2626
{
27-
if (!$amount) {
28-
$amount = '0.00';
29-
}
30-
3127
return strtoupper(md5($merchantMd5 . $merchantApiLogin . $transactionId . $amount));
3228
}
3329

3430
/**
3531
* Return if is valid order id.
3632
*
37-
* @param string $merchantMd5
33+
* @param string $storedHash
3834
* @param string $merchantApiLogin
3935
* @return bool
4036
*/
41-
public function isValidHash($merchantMd5, $merchantApiLogin)
37+
public function isValidHash($storedHash, $merchantApiLogin)
4238
{
43-
$hash = $this->generateHash($merchantMd5, $merchantApiLogin, $this->getXAmount(), $this->getXTransId());
39+
if (empty($this->getData('x_amount'))) {
40+
$this->setData('x_amount', '0.00');
41+
}
4442

45-
return Security::compareStrings($hash, $this->getData('x_MD5_Hash'));
43+
if (!empty($this->getData('x_SHA2_Hash'))) {
44+
$hash = $this->generateSha2Hash($storedHash);
45+
return Security::compareStrings($hash, $this->getData('x_SHA2_Hash'));
46+
} elseif (!empty($this->getData('x_MD5_Hash'))) {
47+
$hash = $this->generateHash($storedHash, $merchantApiLogin, $this->getXAmount(), $this->getXTransId());
48+
return Security::compareStrings($hash, $this->getData('x_MD5_Hash'));
49+
}
50+
51+
return false;
4652
}
4753

4854
/**
@@ -54,4 +60,54 @@ public function isApproved()
5460
{
5561
return $this->getXResponseCode() == \Magento\Authorizenet\Model\Directpost::RESPONSE_CODE_APPROVED;
5662
}
63+
64+
/**
65+
* Generates an SHA2 hash to compare against AuthNet's.
66+
*
67+
* @param string $signatureKey
68+
* @return string
69+
* @see https://support.authorize.net/s/article/MD5-Hash-End-of-Life-Signature-Key-Replacement
70+
*/
71+
private function generateSha2Hash(string $signatureKey): string
72+
{
73+
$hashFields = [
74+
'x_trans_id',
75+
'x_test_request',
76+
'x_response_code',
77+
'x_auth_code',
78+
'x_cvv2_resp_code',
79+
'x_cavv_response',
80+
'x_avs_code',
81+
'x_method',
82+
'x_account_number',
83+
'x_amount',
84+
'x_company',
85+
'x_first_name',
86+
'x_last_name',
87+
'x_address',
88+
'x_city',
89+
'x_state',
90+
'x_zip',
91+
'x_country',
92+
'x_phone',
93+
'x_fax',
94+
'x_email',
95+
'x_ship_to_company',
96+
'x_ship_to_first_name',
97+
'x_ship_to_last_name',
98+
'x_ship_to_address',
99+
'x_ship_to_city',
100+
'x_ship_to_state',
101+
'x_ship_to_zip',
102+
'x_ship_to_country',
103+
'x_invoice_num',
104+
];
105+
106+
$message = '^';
107+
foreach ($hashFields as $field) {
108+
$message .= ($this->getData($field) ?? '') . '^';
109+
}
110+
111+
return strtoupper(hash_hmac('sha512', $message, pack('H*', $signatureKey)));
112+
}
57113
}
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)