diff --git a/Model/Config.php b/Model/Config.php new file mode 100644 index 0000000..47d3ce0 --- /dev/null +++ b/Model/Config.php @@ -0,0 +1,50 @@ + + * @copyright Copyright © 2021. All rights reserved. + */ +declare(strict_types=1); + +namespace PrestonChoate\CardingPrevention\Model; + +use Magento\Framework\App\Config\ScopeConfigInterface; + +class Config +{ + const CARDING_PREVENTION_ENABLED_CONFIG_PATH = 'carding_prevention/general/enabled'; + const CARDING_PREVENTION_THRESHOLD_CONFIG_PATH = 'carding_prevention/general/threshold'; + + /** @var ScopeConfigInterface */ + protected $scopeConfig; + + /** + * Config constructor. + * @param ScopeConfigInterface $scopeConfig + */ + public function __construct( + ScopeConfigInterface $scopeConfig + ) { + $this->scopeConfig = $scopeConfig; + } + + /** + * @param string $scopeType + * @param null $scopeCode + * @return bool + */ + public function getEnabled($scopeType = ScopeConfigInterface::SCOPE_TYPE_DEFAULT, $scopeCode = null): bool + { + return $this->scopeConfig->getValue(self::CARDING_PREVENTION_ENABLED_CONFIG_PATH, $scopeType, $scopeCode) ? true : false; + } + + /** + * @param string $scopeType + * @param null $scopeCode + * @return string + */ + public function getThreshold($scopeType = ScopeConfigInterface::SCOPE_TYPE_DEFAULT, $scopeCode = null): string + { + return $this->scopeConfig->getValue(self::CARDING_PREVENTION_THRESHOLD_CONFIG_PATH, $scopeType, $scopeCode); + } +} diff --git a/Plugin/Magento/Checkout/Model/CardingPreventionPlugin.php b/Plugin/Magento/Checkout/Model/CardingPreventionPlugin.php new file mode 100644 index 0000000..e0da380 --- /dev/null +++ b/Plugin/Magento/Checkout/Model/CardingPreventionPlugin.php @@ -0,0 +1,70 @@ + + * @copyright Copyright © 2021. All rights reserved. + */ +declare(strict_types=1); + +namespace PrestonChoate\CardingPrevention\Plugin\Magento\Checkout\Model; +use PrestonChoate\CardingPrevention\Model\Config; +use Magento\Checkout\Model\GuestPaymentInformationManagement; +use Magento\Framework\App\CacheInterface; +use Magento\Framework\Exception\AuthorizationException; +use Magento\Quote\Api\Data\AddressInterface; +use Magento\Quote\Api\Data\PaymentInterface; + +class CardingPreventionPlugin +{ + /** @var CacheInterface */ + protected $cache; + + /** @var Config */ + protected $config; + + /** + * CardingPreventionPlugin constructor. + * @param CacheInterface $cache + * @param Config $config + */ + public function __construct( + CacheInterface $cache, + Config $config + ) { + $this->cache = $cache; + $this->config = $config; + } + + /** + * @param GuestPaymentInformationManagement $subject + * @param $cartId + * @param $email + * @param PaymentInterface $paymentMethod + * @param AddressInterface|null $billingAddress + * @throws AuthorizationException + */ + public function beforeSavePaymentInformationAndPlaceOrder( + GuestPaymentInformationManagement $subject, + $cartId, + $email, + PaymentInterface $paymentMethod, + AddressInterface $billingAddress = null) + { + $lifetime = $this->config->getThreshold(); + $lifetime = ctype_digit($lifetime) ? intval($lifetime) : 0; + if ($this->config->getEnabled() && $lifetime > 0) { + $data = 'cart id already used'; + $tags = ['carding_prevention']; + + // Check if cartId has been used to post a transaction in the last $threshold seconds + // then save this instance of that cartId being used and if a value was previously found + // then throw an exception to drop the connection + $value = $this->cache->load($cartId); + $this->cache->save($data, $cartId, $tags, $lifetime); + if ($value) { + throw new AuthorizationException(__('Too many requests')); + } + + } + } +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..626db8f --- /dev/null +++ b/README.md @@ -0,0 +1,46 @@ +# PrestonChoate CardingPrevention + +```#bash +PrestonChoate/module-carding-prevention +``` + +- [Main Functionalities](#markdown-header-main-functionalities) +- [Installation](#markdown-header-installation) +- [Configuration](#markdown-header-configuration) +- [Specifications](#markdown-header-specifications) + +## Main Functionalities + +This module adds a plugin before `\Magento\Checkout\Model\GuestPaymentInformationManagement::savePaymentInformationAndPlaceOrder` that will log the used cart ID to the configured caching service (most likely redis). If that cart ID is used again within the configured threshold the transaction will be canceled. + +## Installation + +To install via Composer you need to add this repository to your Magneto project. + +`composer config repositories.prestonchoate '{"type": "vcs","url": "git@github.com:prestonchoate/CardingModule.git"}'` + +Then require the module with: + +`composer require PrestonChoate/module-carding-prevention` + +Finally run the following commands to fully install the module: + +```#bash +bin/magento module:enable PrestonChoate_CardingModule +bin/magento setup:upgrade +bin/magento cache:flush +``` + +## Configuration + +There are two configurations for this module. They both are located at Stores -> Configuration -> Security -> Carding Prevention + +- General + - Enabled - This controls enabling and disabling the module as a whole +- Threshold + - This will set the number of seconds between requests that a user must wait to post another transaction with the same Cart ID + +## Specifications + +A model is supplied for retrieving configurations at `\PrestonChoate/CardingPrevention/Model/Config.php`. +The main plugin exists at `\PrestonChoate\CardingPrevention\Plugin\Magento\Checkout\Model\CardingPreventionPlugin` diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..e952ae9 --- /dev/null +++ b/composer.json @@ -0,0 +1,13 @@ +{ + "name": "prestonchoate/module-carding-prevention", + "description": "Module to prevent carding attacks at M2 checkout", + "minimum-stability": "stable", + "license": "GPL-3.0-or-later", + "require": {}, + "autoload": { + "files": ["registration.php"], + "psr-4": { + "PrestonChoate\\CardingPrevention\\": "" + } + } +} diff --git a/etc/acl.xml b/etc/acl.xml new file mode 100644 index 0000000..c53967c --- /dev/null +++ b/etc/acl.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + diff --git a/etc/adminhtml/system.xml b/etc/adminhtml/system.xml new file mode 100644 index 0000000..466507a --- /dev/null +++ b/etc/adminhtml/system.xml @@ -0,0 +1,32 @@ + + + + +
+ + security + PrestonChoate_CardingPrevention::config_prestonchoate_cardingprevention + + + + + Magento\Config\Model\Config\Source\Yesno + + + + Number of seconds to hold onto a previously used transaction ID. If another transaction occurs with this same ID the transaction will be denied. + validate-number validate-digits validate-not-negative-number + + 1 + + + +
+
+
diff --git a/etc/config.xml b/etc/config.xml new file mode 100644 index 0000000..5db2efc --- /dev/null +++ b/etc/config.xml @@ -0,0 +1,18 @@ + + + + + + + 0 + 60 + + + + diff --git a/etc/di.xml b/etc/di.xml new file mode 100644 index 0000000..732f104 --- /dev/null +++ b/etc/di.xml @@ -0,0 +1,17 @@ + + + + + + + diff --git a/etc/module.xml b/etc/module.xml new file mode 100644 index 0000000..cccd1d6 --- /dev/null +++ b/etc/module.xml @@ -0,0 +1,16 @@ + + + + + + + + + diff --git a/registration.php b/registration.php new file mode 100644 index 0000000..99867f7 --- /dev/null +++ b/registration.php @@ -0,0 +1,14 @@ + + * @copyright Copyright © 2021. All rights reserved. + */ +declare(strict_types=1); +use Magento\Framework\Component\ComponentRegistrar; + +ComponentRegistrar::register( + ComponentRegistrar::MODULE, + 'PrestonChoate_CardingPrevention', + __DIR__ +);