From 9f7f5b2509395047fbc2f3c8af1ae78530cf3fce Mon Sep 17 00:00:00 2001 From: Artyom Jaksov Date: Thu, 1 Aug 2024 17:12:54 +0200 Subject: [PATCH 1/4] create psr 16 compliant cache bridge --- Model/Cache/CacheType.php | 29 +++++ Service/Cache/Adapter.php | 131 +++++++++++++++++++++ Service/Cache/InvalidArgumentException.php | 12 ++ Service/Client/ClientFactory.php | 8 +- etc/cache.xml | 7 ++ 5 files changed, 186 insertions(+), 1 deletion(-) create mode 100644 Model/Cache/CacheType.php create mode 100644 Service/Cache/Adapter.php create mode 100644 Service/Cache/InvalidArgumentException.php create mode 100644 etc/cache.xml diff --git a/Model/Cache/CacheType.php b/Model/Cache/CacheType.php new file mode 100644 index 0000000..13b46a0 --- /dev/null +++ b/Model/Cache/CacheType.php @@ -0,0 +1,29 @@ +get(self::TYPE_IDENTIFIER), + self::CACHE_TAG + ); + } +} diff --git a/Service/Cache/Adapter.php b/Service/Cache/Adapter.php new file mode 100644 index 0000000..065bf72 --- /dev/null +++ b/Service/Cache/Adapter.php @@ -0,0 +1,131 @@ +cacheFrontend = $cacheFrontend; + } + + /** + * {@inheritdoc} + */ + public function get(string $key, mixed $default = null): mixed + { + $item = $this->cacheFrontend->load($key); + + if ($item !== false) { + $item = unserialize($item); + } else { + $item = $default; + } + + return $item; + } + + /** + * {@inheritdoc} + */ + public function set(string $key, $value, $ttl = null): bool + { + $value = serialize($value); + return $this->cacheFrontend->save( + $value, + $key, + [CacheType::CACHE_TAG], + $ttl + ); + } + + /** + * {@inheritdoc} + */ + public function delete($key): bool + { + return $this->cacheFrontend->remove($key); + } + + /** + * {@inheritdoc} + */ + public function clear(): bool + { + return $this->cacheFrontend->clean(); + } + + /** + * {@inheritdoc} + */ + public function getMultiple(iterable $keys, mixed $default = null): iterable + { + if ($keys instanceof \Traversable) { + $keys = iterator_to_array($keys, false); + } + $values = []; + foreach ($keys as $key) { + $values[$key] = $this->get($key, $default); + } + return $values; + } + + /** + * {@inheritdoc} + * @param iterable $values + */ + public function setMultiple(iterable $values, null|int|\DateInterval $ttl = null): bool + { + $stringKeyedValues = []; + foreach ($values as $key => $value) { + if (is_int($key)) { + $key = (string) $key; + } + + if (!is_string($key)) { + throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given', gettype($key))); + } + + if (preg_match('|[\{\}\(\)/\\\@\:]|', $key)) { + throw new InvalidArgumentException(sprintf('Invalid key: "%s". The key contains one or more characters reserved for future extension: {}()/\@:', $key)); + } + + $stringKeyedValues[$key] = $value; + } + $success = true; + foreach ($stringKeyedValues as $key => $value) { + $success = $this->set($key, $value, $ttl) && $success; + } + + return $success; + } + + /** + * {@inheritdoc} + */ + public function deleteMultiple(iterable $keys): bool + { + if ($keys instanceof \Traversable) { + $keys = iterator_to_array($keys, false); + } + $success = true; + foreach ($keys as $key) { + $success = $this->delete($key) && $success; + } + return $success; + } + + /** + * {@inheritdoc} + */ + public function has(string $key): bool + { + return $this->cacheFrontend->test($key); + } +} diff --git a/Service/Cache/InvalidArgumentException.php b/Service/Cache/InvalidArgumentException.php new file mode 100644 index 0000000..c25f12f --- /dev/null +++ b/Service/Cache/InvalidArgumentException.php @@ -0,0 +1,12 @@ +configProvider = $configProvider; $this->logger = $logger; + $this->cacheAdapter = $cacheAdapter; } /** @@ -58,10 +62,12 @@ public function create(int $storeId = 0, ?array $data = []): ?ClientInterface */ private function createClient(array $credentials): ?ClientInterface { + $encryptionKey = 'c604441012bcc628fdc6b4674f1e134cdf5a7810039e873b140d2f4aae5765a7'; Settings::tlAgent('truelayer-magento/' . $this->configProvider->getExtensionVersion()); return Client::configure() ->clientId($credentials['client_id']) ->clientSecret($credentials['client_secret']) + ->cache($this->cacheAdapter, $encryptionKey) ->keyId($credentials['key_id']) ->pemFile($credentials['private_key']) ->useProduction(!$this->configProvider->isSandbox()) diff --git a/etc/cache.xml b/etc/cache.xml new file mode 100644 index 0000000..0237bc0 --- /dev/null +++ b/etc/cache.xml @@ -0,0 +1,7 @@ + + + + + Cache used by TrueLayer Plugin + + From a7472f470386ce43e9fd5b14a4e3d79d76ea96ac Mon Sep 17 00:00:00 2001 From: Artyom Jaksov Date: Fri, 23 Aug 2024 13:04:30 +0200 Subject: [PATCH 2/4] add cache encryption key to config --- Api/Config/System/ConnectionInterface.php | 1 + Model/Config/System/ConnectionRepository.php | 22 ++++++++++++- Service/Client/ClientFactory.php | 14 ++++---- Setup/Recurring.php | 34 ++++++++++++++++++++ etc/adminhtml/system/general.xml | 9 ++++++ 5 files changed, 73 insertions(+), 7 deletions(-) create mode 100644 Setup/Recurring.php diff --git a/Api/Config/System/ConnectionInterface.php b/Api/Config/System/ConnectionInterface.php index 0877cef..7d46987 100644 --- a/Api/Config/System/ConnectionInterface.php +++ b/Api/Config/System/ConnectionInterface.php @@ -25,6 +25,7 @@ interface ConnectionInterface extends DebugInterface public const XML_PATH_PRODUCTION_CLIENT_SECRET = 'payment/truelayer/production_client_secret'; public const XML_PATH_PRODUCTION_PRIVATE_KEY = 'payment/truelayer/production_private_key'; public const XML_PATH_PRODUCTION_KEY_ID = 'payment/truelayer/production_key_id'; + public const XML_PATH_CACHE_ENCRYPTION_KEY = 'payment/truelayer/cache_encryption_key'; /** * Get Merchant Account Name diff --git a/Model/Config/System/ConnectionRepository.php b/Model/Config/System/ConnectionRepository.php index b39fc7c..34b83f3 100644 --- a/Model/Config/System/ConnectionRepository.php +++ b/Model/Config/System/ConnectionRepository.php @@ -42,7 +42,8 @@ public function getCredentials(?int $storeId = null, ?bool $forceSandbox = null) "client_id" => $this->getClientId($storeId, $isSandBox), "client_secret" => $this->getClientSecret($storeId, $isSandBox), "private_key" => $this->getPathToPrivateKey($storeId, $isSandBox), - "key_id" => $this->getKeyId($storeId, $isSandBox) + "key_id" => $this->getKeyId($storeId, $isSandBox), + "cache_encryption_key" => $this->getCacheEncryptionKey($storeId) ]; } @@ -103,4 +104,23 @@ private function getClientSecret(?int $storeId = null, bool $isSandBox = false): return ''; } + + /** + * @param int|null $storeId + * + * @return string + */ + private function getCacheEncryptionKey(?int $storeId = null): ?string + { + $path = self::XML_PATH_CACHE_ENCRYPTION_KEY; + $value = $this->getStoreValue($path, $storeId); + if (is_string($value)) { + if ($value) { + return $this->encryptor->decrypt($value); + } + return ''; + } + + return null; + } } diff --git a/Service/Client/ClientFactory.php b/Service/Client/ClientFactory.php index 7dd5844..d874923 100644 --- a/Service/Client/ClientFactory.php +++ b/Service/Client/ClientFactory.php @@ -62,15 +62,17 @@ public function create(int $storeId = 0, ?array $data = []): ?ClientInterface */ private function createClient(array $credentials): ?ClientInterface { - $encryptionKey = 'c604441012bcc628fdc6b4674f1e134cdf5a7810039e873b140d2f4aae5765a7'; + $cacheEncryptionKey = $this->configProvider->getCacheEncryptionKey(); Settings::tlAgent('truelayer-magento/' . $this->configProvider->getExtensionVersion()); - return Client::configure() - ->clientId($credentials['client_id']) + $configurator = Client::configure(); + $configurator->clientId($credentials['client_id']) ->clientSecret($credentials['client_secret']) - ->cache($this->cacheAdapter, $encryptionKey) ->keyId($credentials['key_id']) ->pemFile($credentials['private_key']) - ->useProduction(!$this->configProvider->isSandbox()) - ->create(); + ->useProduction(!$this->configProvider->isSandbox()); + if ($cacheEncryptionKey) { + $configurator->cache($this->cacheAdapter, $cacheEncryptionKey); + } + return $configurator->create(); } } diff --git a/Setup/Recurring.php b/Setup/Recurring.php new file mode 100644 index 0000000..9759942 --- /dev/null +++ b/Setup/Recurring.php @@ -0,0 +1,34 @@ +startSetup(); + + $credentials = $this->configRepository->getCredentials(); + $cacheEncryptionKey = $credentials['cache_encryption_key'] ?? null; + if (is_null($cacheEncryptionKey)) { + $path = ConnectionInterface::XML_PATH_CACHE_ENCRYPTION_KEY; + $value = bin2hex(openssl_random_pseudo_bytes(32)); + $value = $this->encryptor->encrypt($value); + $this->resourceConfig->saveConfig($path, $value, 'default', 0); + } + + $setup->endSetup(); + } +} \ No newline at end of file diff --git a/etc/adminhtml/system/general.xml b/etc/adminhtml/system/general.xml index eb110f8..7355796 100644 --- a/etc/adminhtml/system/general.xml +++ b/etc/adminhtml/system/general.xml @@ -33,6 +33,15 @@ 1 + + + Magento\Config\Model\Config\Backend\Encrypted + payment/truelayer/cache_encryption_key + + 1 + + Clear the field to disable cache]]> + TrueLayer\Connect\Model\Config\Source\Mode From f8ba05ef5eb381eb54608e582799ed2002c4b208 Mon Sep 17 00:00:00 2001 From: Artyom Jaksov Date: Fri, 23 Aug 2024 13:32:00 +0200 Subject: [PATCH 3/4] remove cache encryption key from admin panel --- Controller/Adminhtml/Credentials/Check.php | 3 ++- Service/Client/ClientFactory.php | 2 +- Setup/Recurring.php | 10 ++++++++++ etc/adminhtml/system/general.xml | 9 --------- 4 files changed, 13 insertions(+), 11 deletions(-) diff --git a/Controller/Adminhtml/Credentials/Check.php b/Controller/Adminhtml/Credentials/Check.php index 4b53bbf..9beaf99 100644 --- a/Controller/Adminhtml/Credentials/Check.php +++ b/Controller/Adminhtml/Credentials/Check.php @@ -133,7 +133,8 @@ private function getCredentials(): array 'client_id' => $clientId, 'client_secret' => $clientSecret, 'private_key' => $this->getPrivateKeyPath($configCredentials), - 'key_id' => $keyId + 'key_id' => $keyId, + 'cache_encryption_key' => $configCredentials['cache_encryption_key'] ] ]; } diff --git a/Service/Client/ClientFactory.php b/Service/Client/ClientFactory.php index d874923..afe96d3 100644 --- a/Service/Client/ClientFactory.php +++ b/Service/Client/ClientFactory.php @@ -62,7 +62,7 @@ public function create(int $storeId = 0, ?array $data = []): ?ClientInterface */ private function createClient(array $credentials): ?ClientInterface { - $cacheEncryptionKey = $this->configProvider->getCacheEncryptionKey(); + $cacheEncryptionKey = $credentials['cache_encryption_key']; Settings::tlAgent('truelayer-magento/' . $this->configProvider->getExtensionVersion()); $configurator = Client::configure(); $configurator->clientId($credentials['client_id']) diff --git a/Setup/Recurring.php b/Setup/Recurring.php index 9759942..ed546d4 100644 --- a/Setup/Recurring.php +++ b/Setup/Recurring.php @@ -22,6 +22,16 @@ public function install(SchemaSetupInterface $setup, ModuleContextInterface $con $credentials = $this->configRepository->getCredentials(); $cacheEncryptionKey = $credentials['cache_encryption_key'] ?? null; + if ($cacheEncryptionKey) { + try { + $binEncryptionKey = \hex2bin($cacheEncryptionKey); + if (strlen($binEncryptionKey) !== 32) { + $cacheEncryptionKey = null; + } + } catch (\Exception $e) { + $cacheEncryptionKey = null; + } + } if (is_null($cacheEncryptionKey)) { $path = ConnectionInterface::XML_PATH_CACHE_ENCRYPTION_KEY; $value = bin2hex(openssl_random_pseudo_bytes(32)); diff --git a/etc/adminhtml/system/general.xml b/etc/adminhtml/system/general.xml index 7355796..eb110f8 100644 --- a/etc/adminhtml/system/general.xml +++ b/etc/adminhtml/system/general.xml @@ -33,15 +33,6 @@ 1 - - - Magento\Config\Model\Config\Backend\Encrypted - payment/truelayer/cache_encryption_key - - 1 - - Clear the field to disable cache]]> - TrueLayer\Connect\Model\Config\Source\Mode From 0e959ebd3adf1e903e970e0aeb0b0d0e079d7a0c Mon Sep 17 00:00:00 2001 From: Artyom Jaksov Date: Wed, 18 Sep 2024 16:28:35 +0200 Subject: [PATCH 4/4] remove pointless regex and add better naming --- .../Cache/{Adapter.php => Psr16CacheAdapter.php} | 6 +----- Service/Client/ClientFactory.php | 14 +++++++------- 2 files changed, 8 insertions(+), 12 deletions(-) rename Service/Cache/{Adapter.php => Psr16CacheAdapter.php} (91%) diff --git a/Service/Cache/Adapter.php b/Service/Cache/Psr16CacheAdapter.php similarity index 91% rename from Service/Cache/Adapter.php rename to Service/Cache/Psr16CacheAdapter.php index 065bf72..769e8f9 100644 --- a/Service/Cache/Adapter.php +++ b/Service/Cache/Psr16CacheAdapter.php @@ -6,7 +6,7 @@ use Truelayer\Connect\Model\Cache\CacheType; use Truelayer\Connect\Service\Cache\InvalidArgumentException; -class Adapter implements CacheInterface +class Psr16CacheAdapter implements CacheInterface { private CacheType $cacheFrontend; @@ -92,10 +92,6 @@ public function setMultiple(iterable $values, null|int|\DateInterval $ttl = null throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given', gettype($key))); } - if (preg_match('|[\{\}\(\)/\\\@\:]|', $key)) { - throw new InvalidArgumentException(sprintf('Invalid key: "%s". The key contains one or more characters reserved for future extension: {}()/\@:', $key)); - } - $stringKeyedValues[$key] = $value; } $success = true; diff --git a/Service/Client/ClientFactory.php b/Service/Client/ClientFactory.php index afe96d3..98fc538 100644 --- a/Service/Client/ClientFactory.php +++ b/Service/Client/ClientFactory.php @@ -12,7 +12,7 @@ use TrueLayer\Client; use TrueLayer\Connect\Api\Config\RepositoryInterface as ConfigRepository; use TrueLayer\Connect\Api\Log\LogServiceInterface; -use TrueLayer\Connect\Service\Cache\Adapter; +use TrueLayer\Connect\Service\Cache\Psr16CacheAdapter; use TrueLayer\Exceptions\SignerException; use TrueLayer\Interfaces\Client\ClientInterface; use TrueLayer\Settings; @@ -21,7 +21,7 @@ class ClientFactory { private ConfigRepository $configProvider; private LogServiceInterface $logger; - private Adapter $cacheAdapter; + private Psr16CacheAdapter $cacheAdapter; /** * @param ConfigRepository $configProvider @@ -30,7 +30,7 @@ class ClientFactory public function __construct( ConfigRepository $configProvider, LogServiceInterface $logger, - Adapter $cacheAdapter, + Psr16CacheAdapter $cacheAdapter, ) { $this->configProvider = $configProvider; $this->logger = $logger; @@ -64,15 +64,15 @@ private function createClient(array $credentials): ?ClientInterface { $cacheEncryptionKey = $credentials['cache_encryption_key']; Settings::tlAgent('truelayer-magento/' . $this->configProvider->getExtensionVersion()); - $configurator = Client::configure(); - $configurator->clientId($credentials['client_id']) + $clientFactory = Client::configure(); + $clientFactory->clientId($credentials['client_id']) ->clientSecret($credentials['client_secret']) ->keyId($credentials['key_id']) ->pemFile($credentials['private_key']) ->useProduction(!$this->configProvider->isSandbox()); if ($cacheEncryptionKey) { - $configurator->cache($this->cacheAdapter, $cacheEncryptionKey); + $clientFactory->cache($this->cacheAdapter, $cacheEncryptionKey); } - return $configurator->create(); + return $clientFactory->create(); } }