Skip to content

Commit 9df608c

Browse files
author
Alexander Akimov
authored
Merge pull request #3834 from magento-tsg/2.1.17-develop-pr65
[TSG] Backporting for 2.1 (pr65) (2.1.17)
2 parents b653b2f + 0513b2f commit 9df608c

File tree

7 files changed

+302
-77
lines changed

7 files changed

+302
-77
lines changed

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -549,16 +549,16 @@ public function setResponseData(array $postData)
549549
public function validateResponse()
550550
{
551551
$response = $this->getResponse();
552-
//md5 check
553-
if (
554-
!$this->getConfigData('trans_md5')
555-
|| !$this->getConfigData('login')
556-
|| !$response->isValidHash($this->getConfigData('trans_md5'), $this->getConfigData('login'))
552+
$hashConfigKey = !empty($response->getData('x_SHA2_Hash')) ? 'signature_key' : 'trans_md5';
553+
554+
//hash check
555+
if (!$response->isValidHash($this->getConfigData($hashConfigKey), $this->getConfigData('login'))
557556
) {
558557
throw new \Magento\Framework\Exception\LocalizedException(
559558
__('The transaction was declined because the response hash validation failed.')
560559
);
561560
}
561+
562562
return true;
563563
}
564564

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+
) {
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()
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($signatureKey)
271+
{
272+
$this->signatureKey = $signatureKey;
273+
}
184274
}

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

Lines changed: 67 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,57 @@ 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($signatureKey)
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+
if (!empty($this->getData($field))) {
109+
$message .= $this->getData($field);
110+
}
111+
$message .= '^';
112+
}
113+
114+
return strtoupper(hash_hmac('sha512', $message, pack('H*', $signatureKey)));
115+
}
57116
}
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+
11+
class RequestTest extends \PHPUnit_Framework_TestCase
12+
{
13+
/**
14+
* @var DateTimeFactory|\PHPUnit_Framework_MockObject_MockObject
15+
*/
16+
private $dateTimeFactory;
17+
18+
/**
19+
* @var Request
20+
*/
21+
private $requestModel;
22+
23+
/**
24+
* @inheritdoc
25+
*/
26+
protected function setUp()
27+
{
28+
$this->dateTimeFactory = $this->getMockBuilder(DateTimeFactory::class)->disableOriginalConstructor()->getMock();
29+
$dateTime = new \DateTime('2016-07-05 00:00:00', new \DateTimeZone('UTC'));
30+
$this->dateTimeFactory->method('create')->willReturn($dateTime);
31+
32+
$this->requestModel = new Request($this->dateTimeFactory);
33+
}
34+
35+
/**
36+
* @param string $signatureKey
37+
* @param string $expectedHash
38+
* @return void
39+
* @dataProvider signRequestDataProvider
40+
*/
41+
public function testSignRequestData($signatureKey, $expectedHash)
42+
{
43+
/** @var \Magento\Authorizenet\Model\Directpost|\PHPUnit_Framework_MockObject_MockObject $paymentMethod */
44+
$paymentMethod = $this->getMock(\Magento\Authorizenet\Model\Directpost::class, [], [], '', false);
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)