diff --git a/app/code/Magento/Authorizenet/Model/Directpost.php b/app/code/Magento/Authorizenet/Model/Directpost.php
index 5bc9335d24439..946ec8ba01a0e 100644
--- a/app/code/Magento/Authorizenet/Model/Directpost.php
+++ b/app/code/Magento/Authorizenet/Model/Directpost.php
@@ -546,15 +546,16 @@ public function setResponseData(array $postData)
public function validateResponse()
{
$response = $this->getResponse();
- //md5 check
- if (!$this->getConfigData('trans_md5')
- || !$this->getConfigData('login')
- || !$response->isValidHash($this->getConfigData('trans_md5'), $this->getConfigData('login'))
+ $hashConfigKey = !empty($response->getData('x_SHA2_Hash')) ? 'signature_key' : 'trans_md5';
+
+ //hash check
+ if (!$response->isValidHash($this->getConfigData($hashConfigKey), $this->getConfigData('login'))
) {
throw new \Magento\Framework\Exception\LocalizedException(
__('The transaction was declined because the response hash validation failed.')
);
}
+
return true;
}
diff --git a/app/code/Magento/Authorizenet/Model/Directpost/Request.php b/app/code/Magento/Authorizenet/Model/Directpost/Request.php
index 357385e5c8c79..d518af4e04f55 100644
--- a/app/code/Magento/Authorizenet/Model/Directpost/Request.php
+++ b/app/code/Magento/Authorizenet/Model/Directpost/Request.php
@@ -8,6 +8,8 @@
namespace Magento\Authorizenet\Model\Directpost;
use Magento\Authorizenet\Model\Request as AuthorizenetRequest;
+use Magento\Framework\App\ObjectManager;
+use Magento\Framework\Intl\DateTimeFactory;
/**
* Authorize.net request model for DirectPost model
@@ -20,10 +22,35 @@ class Request extends AuthorizenetRequest
*/
protected $_transKey = null;
+ /**
+ * Hexadecimal signature key.
+ *
+ * @var string
+ */
+ private $signatureKey = '';
+
+ /**
+ * @var DateTimeFactory
+ */
+ private $dateTimeFactory;
+
+ /**
+ * @param array $data
+ * @param DateTimeFactory $dateTimeFactory
+ */
+ public function __construct(
+ array $data = [],
+ DateTimeFactory $dateTimeFactory = null
+ ) {
+ $this->dateTimeFactory = $dateTimeFactory ?? ObjectManager::getInstance()
+ ->get(DateTimeFactory::class);
+ parent::__construct($data);
+ }
+
/**
* Return merchant transaction key.
*
- * Needed to generate sign.
+ * Needed to generate MD5 sign.
*
* @return string
*/
@@ -35,7 +62,7 @@ protected function _getTransactionKey()
/**
* Set merchant transaction key.
*
- * Needed to generate sign.
+ * Needed to generate MD5 sign.
*
* @param string $transKey
* @return $this
@@ -47,7 +74,7 @@ protected function _setTransactionKey($transKey)
}
/**
- * Generates the fingerprint for request.
+ * Generates the MD5 fingerprint for request.
*
* @param string $merchantApiLoginId
* @param string $merchantTransactionKey
@@ -67,7 +94,7 @@ public function generateRequestSign(
) {
return hash_hmac(
"md5",
- $merchantApiLoginId . "^" . $fpSequence . "^" . $fpTimestamp . "^" . $amount . "^" . $currencyCode,
+ $merchantApiLoginId . '^' . $fpSequence . '^' . $fpTimestamp . '^' . $amount . '^' . $currencyCode,
$merchantTransactionKey
);
}
@@ -82,7 +109,7 @@ public function setConstantData(\Magento\Authorizenet\Model\Directpost $paymentM
{
$this->setXVersion('3.1')->setXDelimData('FALSE')->setXRelayResponse('TRUE');
- $this->setXTestRequest($paymentMethod->getConfigData('test') ? 'TRUE' : 'FALSE');
+ $this->setSignatureKey($paymentMethod->getConfigData('signature_key'));
$this->setXLogin($paymentMethod->getConfigData('login'))
->setXMethod(\Magento\Authorizenet\Model\Authorizenet::REQUEST_METHOD_CC)
@@ -173,17 +200,81 @@ public function setDataFromOrder(
*/
public function signRequestData()
{
- $fpTimestamp = time();
- $hash = $this->generateRequestSign(
- $this->getXLogin(),
- $this->_getTransactionKey(),
- $this->getXAmount(),
- $this->getXCurrencyCode(),
- $this->getXFpSequence(),
- $fpTimestamp
- );
+ $fpDate = $this->dateTimeFactory->create('now', new \DateTimeZone('UTC'));
+ $fpTimestamp = $fpDate->getTimestamp();
+
+ if (!empty($this->getSignatureKey())) {
+ $hash = $this->generateSha2RequestSign(
+ (string)$this->getXLogin(),
+ (string)$this->getSignatureKey(),
+ (string)$this->getXAmount(),
+ (string)$this->getXCurrencyCode(),
+ (string)$this->getXFpSequence(),
+ $fpTimestamp
+ );
+ } else {
+ $hash = $this->generateRequestSign(
+ $this->getXLogin(),
+ $this->_getTransactionKey(),
+ $this->getXAmount(),
+ $this->getXCurrencyCode(),
+ $this->getXFpSequence(),
+ $fpTimestamp
+ );
+ }
+
$this->setXFpTimestamp($fpTimestamp);
$this->setXFpHash($hash);
+
return $this;
}
+
+ /**
+ * Generates the SHA2 fingerprint for request.
+ *
+ * @param string $merchantApiLoginId
+ * @param string $merchantSignatureKey
+ * @param string $amount
+ * @param string $currencyCode
+ * @param string $fpSequence An invoice number or random number.
+ * @param int $fpTimestamp
+ * @return string The fingerprint.
+ */
+ private function generateSha2RequestSign(
+ string $merchantApiLoginId,
+ string $merchantSignatureKey,
+ string $amount,
+ string $currencyCode,
+ string $fpSequence,
+ int $fpTimestamp
+ ): string {
+ $message = $merchantApiLoginId . '^' . $fpSequence . '^' . $fpTimestamp . '^' . $amount . '^' . $currencyCode;
+
+ return strtoupper(hash_hmac('sha512', $message, pack('H*', $merchantSignatureKey)));
+ }
+
+ /**
+ * Return merchant hexadecimal signature key.
+ *
+ * Needed to generate SHA2 sign.
+ *
+ * @return string
+ */
+ private function getSignatureKey(): string
+ {
+ return $this->signatureKey;
+ }
+
+ /**
+ * Set merchant hexadecimal signature key.
+ *
+ * Needed to generate SHA2 sign.
+ *
+ * @param string $signatureKey
+ * @return void
+ */
+ private function setSignatureKey(string $signatureKey)
+ {
+ $this->signatureKey = $signatureKey;
+ }
}
diff --git a/app/code/Magento/Authorizenet/Model/Directpost/Response.php b/app/code/Magento/Authorizenet/Model/Directpost/Response.php
index 1c713a159c3ad..b5604a78cb9cd 100644
--- a/app/code/Magento/Authorizenet/Model/Directpost/Response.php
+++ b/app/code/Magento/Authorizenet/Model/Directpost/Response.php
@@ -27,25 +27,31 @@ class Response extends AuthorizenetResponse
*/
public function generateHash($merchantMd5, $merchantApiLogin, $amount, $transactionId)
{
- if (!$amount) {
- $amount = '0.00';
- }
-
return strtoupper(md5($merchantMd5 . $merchantApiLogin . $transactionId . $amount));
}
/**
* Return if is valid order id.
*
- * @param string $merchantMd5
+ * @param string $storedHash
* @param string $merchantApiLogin
* @return bool
*/
- public function isValidHash($merchantMd5, $merchantApiLogin)
+ public function isValidHash($storedHash, $merchantApiLogin)
{
- $hash = $this->generateHash($merchantMd5, $merchantApiLogin, $this->getXAmount(), $this->getXTransId());
+ if (empty($this->getData('x_amount'))) {
+ $this->setData('x_amount', '0.00');
+ }
- return Security::compareStrings($hash, $this->getData('x_MD5_Hash'));
+ if (!empty($this->getData('x_SHA2_Hash'))) {
+ $hash = $this->generateSha2Hash($storedHash);
+ return Security::compareStrings($hash, $this->getData('x_SHA2_Hash'));
+ } elseif (!empty($this->getData('x_MD5_Hash'))) {
+ $hash = $this->generateHash($storedHash, $merchantApiLogin, $this->getXAmount(), $this->getXTransId());
+ return Security::compareStrings($hash, $this->getData('x_MD5_Hash'));
+ }
+
+ return false;
}
/**
@@ -57,4 +63,54 @@ public function isApproved()
{
return $this->getXResponseCode() == \Magento\Authorizenet\Model\Directpost::RESPONSE_CODE_APPROVED;
}
+
+ /**
+ * Generates an SHA2 hash to compare against AuthNet's.
+ *
+ * @param string $signatureKey
+ * @return string
+ * @see https://support.authorize.net/s/article/MD5-Hash-End-of-Life-Signature-Key-Replacement
+ */
+ private function generateSha2Hash(string $signatureKey): string
+ {
+ $hashFields = [
+ 'x_trans_id',
+ 'x_test_request',
+ 'x_response_code',
+ 'x_auth_code',
+ 'x_cvv2_resp_code',
+ 'x_cavv_response',
+ 'x_avs_code',
+ 'x_method',
+ 'x_account_number',
+ 'x_amount',
+ 'x_company',
+ 'x_first_name',
+ 'x_last_name',
+ 'x_address',
+ 'x_city',
+ 'x_state',
+ 'x_zip',
+ 'x_country',
+ 'x_phone',
+ 'x_fax',
+ 'x_email',
+ 'x_ship_to_company',
+ 'x_ship_to_first_name',
+ 'x_ship_to_last_name',
+ 'x_ship_to_address',
+ 'x_ship_to_city',
+ 'x_ship_to_state',
+ 'x_ship_to_zip',
+ 'x_ship_to_country',
+ 'x_invoice_num',
+ ];
+
+ $message = '^';
+ foreach ($hashFields as $field) {
+ $message .= ($this->getData($field) ?? '') . '^';
+ }
+
+ return strtoupper(hash_hmac('sha512', $message, pack('H*', $signatureKey)));
+ }
}
diff --git a/app/code/Magento/Authorizenet/Test/Unit/Model/Directpost/RequestTest.php b/app/code/Magento/Authorizenet/Test/Unit/Model/Directpost/RequestTest.php
new file mode 100644
index 0000000000000..94d8f3a0d27a7
--- /dev/null
+++ b/app/code/Magento/Authorizenet/Test/Unit/Model/Directpost/RequestTest.php
@@ -0,0 +1,80 @@
+dateTimeFactory = $this->getMockBuilder(DateTimeFactory::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $dateTime = new \DateTime('2016-07-05 00:00:00', new \DateTimeZone('UTC'));
+ $this->dateTimeFactory->method('create')
+ ->willReturn($dateTime);
+
+ $this->requestModel = new Request([], $this->dateTimeFactory);
+ }
+
+ /**
+ * @param string $signatureKey
+ * @param string $expectedHash
+ * @dataProvider signRequestDataProvider
+ */
+ public function testSignRequestData(string $signatureKey, string $expectedHash)
+ {
+ /** @var \Magento\Authorizenet\Model\Directpost $paymentMethod */
+ $paymentMethod = $this->createMock(\Magento\Authorizenet\Model\Directpost::class);
+ $paymentMethod->method('getConfigData')
+ ->willReturnMap(
+ [
+ ['test', null, true],
+ ['login', null, 'login'],
+ ['trans_key', null, 'trans_key'],
+ ['signature_key', null, $signatureKey],
+ ]
+ );
+
+ $this->requestModel->setConstantData($paymentMethod);
+ $this->requestModel->signRequestData();
+ $signHash = $this->requestModel->getXFpHash();
+
+ $this->assertEquals($expectedHash, $signHash);
+ }
+
+ /**
+ * @return array
+ */
+ public function signRequestDataProvider()
+ {
+ return [
+ [
+ 'signatureKey' => '3EAFCE5697C1B4B9748385C1FCD29D86F3B9B41C7EED85A3A01DFF65' .
+ '70C8C29373C2A153355C3313CDF4AF723C0036DBF244A0821713A910024EE85547CEF37F',
+ 'expectedHash' => '719ED94DF5CF3510CB5531E8115462C8F12CBCC8E917BD809E8D40B4FF06' .
+ '1E14953554403DD9813CCCE0F31B184EB4DEF558E9C0747505A0C25420372DB00BE1'
+ ],
+ [
+ 'signatureKey' => '',
+ 'expectedHash' => '3656211f2c41d1e4c083606f326c0460'
+ ],
+ ];
+ }
+}
diff --git a/app/code/Magento/Authorizenet/Test/Unit/Model/Directpost/ResponseTest.php b/app/code/Magento/Authorizenet/Test/Unit/Model/Directpost/ResponseTest.php
index 15c7eecb09a69..ff4aa8b5ee361 100644
--- a/app/code/Magento/Authorizenet/Test/Unit/Model/Directpost/ResponseTest.php
+++ b/app/code/Magento/Authorizenet/Test/Unit/Model/Directpost/ResponseTest.php
@@ -13,53 +13,16 @@ class ResponseTest extends \PHPUnit\Framework\TestCase
/**
* @var \Magento\Authorizenet\Model\Directpost\Response
*/
- protected $responseModel;
+ private $responseModel;
protected function setUp()
{
$objectManager = new ObjectManager($this);
- $this->responseModel = $objectManager->getObject(\Magento\Authorizenet\Model\Directpost\Response::class);
- }
-
- /**
- * @param string $merchantMd5
- * @param string $merchantApiLogin
- * @param float|null $amount
- * @param float|string $amountTestFunc
- * @param string $transactionId
- * @dataProvider generateHashDataProvider
- */
- public function testGenerateHash($merchantMd5, $merchantApiLogin, $amount, $amountTestFunc, $transactionId)
- {
- $this->assertEquals(
- $this->generateHash($merchantMd5, $merchantApiLogin, $amountTestFunc, $transactionId),
- $this->responseModel->generateHash($merchantMd5, $merchantApiLogin, $amount, $transactionId)
+ $this->responseModel = $objectManager->getObject(
+ \Magento\Authorizenet\Model\Directpost\Response::class
);
}
- /**
- * @return array
- */
- public function generateHashDataProvider()
- {
- return [
- [
- 'merchantMd5' => 'FCD7F001E9274FDEFB14BFF91C799306',
- 'merchantApiLogin' => 'Magento',
- 'amount' => null,
- 'amountTestFunc' => '0.00',
- 'transactionId' => '1'
- ],
- [
- 'merchantMd5' => '8AEF4E508261A287C3E2F544720FCA3A',
- 'merchantApiLogin' => 'Magento2',
- 'amount' => 100.50,
- 'amountTestFunc' => 100.50,
- 'transactionId' => '2'
- ]
- ];
- }
-
/**
* @param $merchantMd5
* @param $merchantApiLogin
@@ -73,7 +36,8 @@ protected function generateHash($merchantMd5, $merchantApiLogin, $amount, $trans
}
/**
- * @param string $merchantMd5
+ * @param string $storedHash
+ * @param string $hashKey
* @param string $merchantApiLogin
* @param float|null $amount
* @param string $transactionId
@@ -81,12 +45,21 @@ protected function generateHash($merchantMd5, $merchantApiLogin, $amount, $trans
* @param bool $expectedValue
* @dataProvider isValidHashDataProvider
*/
- public function testIsValidHash($merchantMd5, $merchantApiLogin, $amount, $transactionId, $hash, $expectedValue)
- {
+ public function testIsValidHash(
+ string $storedHash,
+ string $hashKey,
+ string $merchantApiLogin,
+ $amount,
+ string $transactionId,
+ string $hash,
+ bool $expectedValue
+ ) {
$this->responseModel->setXAmount($amount);
$this->responseModel->setXTransId($transactionId);
- $this->responseModel->setData('x_MD5_Hash', $hash);
- $this->assertEquals($expectedValue, $this->responseModel->isValidHash($merchantMd5, $merchantApiLogin));
+ $this->responseModel->setData($hashKey, $hash);
+ $result = $this->responseModel->isValidHash($storedHash, $merchantApiLogin);
+
+ $this->assertEquals($expectedValue, $result);
}
/**
@@ -94,9 +67,14 @@ public function testIsValidHash($merchantMd5, $merchantApiLogin, $amount, $trans
*/
public function isValidHashDataProvider()
{
+ $signatureKey = '3EAFCE5697C1B4B9748385C1FCD29D86F3B9B41C7EED85A3A01DFF6570C8C' .
+ '29373C2A153355C3313CDF4AF723C0036DBF244A0821713A910024EE85547CEF37F';
+ $expectedSha2Hash = '368D48E0CD1274BF41C059138DA69985594021A4AD5B4C5526AE88C8F' .
+ '7C5769B13C5E1E4358900F3E51076FB69D14B0A797904C22E8A11A52AA49CDE5FBB703C';
return [
[
'merchantMd5' => 'FCD7F001E9274FDEFB14BFF91C799306',
+ 'hashKey' => 'x_MD5_Hash',
'merchantApiLogin' => 'Magento',
'amount' => null,
'transactionId' => '1',
@@ -105,11 +83,21 @@ public function isValidHashDataProvider()
],
[
'merchantMd5' => '8AEF4E508261A287C3E2F544720FCA3A',
+ 'hashKey' => 'x_MD5_Hash',
'merchantApiLogin' => 'Magento2',
'amount' => 100.50,
'transactionId' => '2',
'hash' => '1F24A4EC9A169B2B2A072A5F168E16DC',
'expectedValue' => false
+ ],
+ [
+ 'signatureKey' => $signatureKey,
+ 'hashKey' => 'x_SHA2_Hash',
+ 'merchantApiLogin' => 'Magento2',
+ 'amount' => 100.50,
+ 'transactionId' => '2',
+ 'hash' => $expectedSha2Hash,
+ 'expectedValue' => true
]
];
}
diff --git a/app/code/Magento/Authorizenet/etc/adminhtml/system.xml b/app/code/Magento/Authorizenet/etc/adminhtml/system.xml
index 28bf6945c8b81..fc86c0d2dc68d 100644
--- a/app/code/Magento/Authorizenet/etc/adminhtml/system.xml
+++ b/app/code/Magento/Authorizenet/etc/adminhtml/system.xml
@@ -29,6 +29,10 @@
Magento\Config\Model\Config\Backend\Encrypted
+
+
+ Magento\Config\Model\Config\Backend\Encrypted
+
Magento\Config\Model\Config\Backend\Encrypted
diff --git a/app/code/Magento/Authorizenet/etc/config.xml b/app/code/Magento/Authorizenet/etc/config.xml
index 02dca74023e22..60356460f553f 100644
--- a/app/code/Magento/Authorizenet/etc/config.xml
+++ b/app/code/Magento/Authorizenet/etc/config.xml
@@ -22,6 +22,7 @@
Credit Card Direct Post (Authorize.Net)
+
0
USD
1
diff --git a/app/code/Magento/Braintree/Controller/Paypal/Review.php b/app/code/Magento/Braintree/Controller/Paypal/Review.php
index 14ec829d98024..eb2de7c7b6e39 100644
--- a/app/code/Magento/Braintree/Controller/Paypal/Review.php
+++ b/app/code/Magento/Braintree/Controller/Paypal/Review.php
@@ -13,11 +13,12 @@
use Magento\Braintree\Model\Paypal\Helper\QuoteUpdater;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\App\Action\HttpPostActionInterface;
+use Magento\Framework\App\Action\HttpGetActionInterface;
/**
* Class Review
*/
-class Review extends AbstractAction implements HttpPostActionInterface
+class Review extends AbstractAction implements HttpPostActionInterface, HttpGetActionInterface
{
/**
* @var QuoteUpdater
diff --git a/app/code/Magento/Customer/Model/Visitor.php b/app/code/Magento/Customer/Model/Visitor.php
index 9caa2988c5a94..4f129f05aa82c 100644
--- a/app/code/Magento/Customer/Model/Visitor.php
+++ b/app/code/Magento/Customer/Model/Visitor.php
@@ -14,6 +14,7 @@
*
* @package Magento\Customer\Model
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
+ * @SuppressWarnings(PHPMD.CookieAndSessionMisuse)
*/
class Visitor extends \Magento\Framework\Model\AbstractModel
{
@@ -168,10 +169,6 @@ public function initByRequest($observer)
$this->setLastVisitAt((new \DateTime())->format(\Magento\Framework\Stdlib\DateTime::DATETIME_PHP_FORMAT));
- // prevent saving Visitor for safe methods, e.g. GET request
- if ($this->requestSafety->isSafeMethod()) {
- return $this;
- }
if (!$this->getId()) {
$this->setSessionId($this->session->getSessionId());
$this->save();
diff --git a/dev/tests/integration/testsuite/Magento/Braintree/Controller/Paypal/ReviewTest.php b/dev/tests/integration/testsuite/Magento/Braintree/Controller/Paypal/ReviewTest.php
new file mode 100644
index 0000000000000..fc79048f15f45
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/Braintree/Controller/Paypal/ReviewTest.php
@@ -0,0 +1,43 @@
+controller = $this->_objectManager->create(Review::class);
+ }
+
+ /**
+ * Test controller implements correct interfaces
+ *
+ */
+ public function testInterfaceImplementation()
+ {
+ $this->assertInstanceOf(HttpGetActionInterface::class, $this->controller);
+ $this->assertInstanceOf(HttpPostActionInterface::class, $this->controller);
+ }
+}
diff --git a/dev/tests/integration/testsuite/Magento/Bundle/Model/Product/FixedBundlePriceCalculatorWithDimensionTest.php b/dev/tests/integration/testsuite/Magento/Bundle/Model/Product/FixedBundlePriceCalculatorWithDimensionTest.php
index b97bd9f822666..e9cb2f2d6c9d4 100644
--- a/dev/tests/integration/testsuite/Magento/Bundle/Model/Product/FixedBundlePriceCalculatorWithDimensionTest.php
+++ b/dev/tests/integration/testsuite/Magento/Bundle/Model/Product/FixedBundlePriceCalculatorWithDimensionTest.php
@@ -13,7 +13,6 @@
* @magentoDbIsolation disabled
* @magentoIndexerDimensionMode catalog_product_price website_and_customer_group
* @group indexer_dimension
- * @magentoAppArea frontend
*/
class FixedBundlePriceCalculatorWithDimensionTest extends BundlePriceAbstract
{
diff --git a/dev/tests/integration/testsuite/Magento/Customer/Controller/AccountTest.php b/dev/tests/integration/testsuite/Magento/Customer/Controller/AccountTest.php
index ea7a7710acbc3..10b632c002475 100644
--- a/dev/tests/integration/testsuite/Magento/Customer/Controller/AccountTest.php
+++ b/dev/tests/integration/testsuite/Magento/Customer/Controller/AccountTest.php
@@ -751,6 +751,21 @@ public function loginPostRedirectDataProvider()
];
}
+ /**
+ * @magentoDataFixture Magento/Customer/_files/customer.php
+ * @magentoDataFixture Magento/Customer/_files/customer_address.php
+ * @magentoAppArea frontend
+ */
+ public function testCheckVisitorModel()
+ {
+ /** @var \Magento\Customer\Model\Visitor $visitor */
+ $visitor = $this->_objectManager->get(\Magento\Customer\Model\Visitor::class);
+ $this->login(1);
+ $this->assertNull($visitor->getId());
+ $this->dispatch('customer/account/index');
+ $this->assertNotNull($visitor->getId());
+ }
+
/**
* @param string $email
* @return void