Skip to content

Commit

Permalink
PLUG-127: Add Client cache (#25)
Browse files Browse the repository at this point in the history
* create psr 16 compliant cache bridge
* add cache encryption key to config

---------

Co-authored-by: Alexandru Lighezan <[email protected]>
  • Loading branch information
artyom-jaksov-tl and lighe authored Sep 19, 2024
1 parent 454d2ee commit c049613
Show file tree
Hide file tree
Showing 9 changed files with 262 additions and 7 deletions.
1 change: 1 addition & 0 deletions Api/Config/System/ConnectionInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,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';
public const XML_PATH_PRODUCTION_RELEASE_CHANNEL = 'payment/truelayer/production_release_channel';

/**
Expand Down
3 changes: 2 additions & 1 deletion Controller/Adminhtml/Credentials/Check.php
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,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']
]
];
}
Expand Down
29 changes: 29 additions & 0 deletions Model/Cache/CacheType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

namespace TrueLayer\Connect\Model\Cache;

use Magento\Framework\App\Cache\Type\FrontendPool;
use Magento\Framework\Cache\Frontend\Decorator\TagScope;

class CacheType extends TagScope
{
/**
* Cache type code unique among all cache types
* @var string
*/
public const TYPE_IDENTIFIER = 'truelayer';

/**
* The tag name that limits the cache cleaning scope within a particular tag
* @var string
*/
public const CACHE_TAG = 'TRUELAYER';

public function __construct(FrontendPool $cacheFrontendPool)
{
parent::__construct(
$cacheFrontendPool->get(self::TYPE_IDENTIFIER),
self::CACHE_TAG
);
}
}
22 changes: 21 additions & 1 deletion Model/Config/System/ConnectionRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,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)
];
}

Expand Down Expand Up @@ -114,4 +115,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;
}
}
12 changes: 12 additions & 0 deletions Service/Cache/InvalidArgumentException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php


namespace Truelayer\Connect\Service\Cache;

class InvalidArgumentException extends \InvalidArgumentException implements \Psr\Cache\InvalidArgumentException
{
public function __construct(string $message)
{
parent::__construct($message);
}
}
127 changes: 127 additions & 0 deletions Service/Cache/Psr16CacheAdapter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
<?php

namespace TrueLayer\Connect\Service\Cache;

use Psr\SimpleCache\CacheInterface;
use Truelayer\Connect\Model\Cache\CacheType;
use Truelayer\Connect\Service\Cache\InvalidArgumentException;

class Psr16CacheAdapter implements CacheInterface
{
private CacheType $cacheFrontend;

public function __construct(CacheType $cacheFrontend)
{
$this->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<int|string, mixed> $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)));
}

$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);
}
}
24 changes: 19 additions & 5 deletions Service/Client/ClientFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
use TrueLayer\Client;
use TrueLayer\Connect\Api\Config\RepositoryInterface as ConfigRepository;
use TrueLayer\Connect\Api\Log\LogServiceInterface;
use TrueLayer\Connect\Service\Cache\Psr16CacheAdapter;
use TrueLayer\Exceptions\InvalidArgumentException;
use TrueLayer\Exceptions\SignerException;
use TrueLayer\Interfaces\Client\ClientInterface;
use TrueLayer\Settings;
Expand All @@ -20,17 +22,20 @@ class ClientFactory
{
private ConfigRepository $configProvider;
private LogServiceInterface $logger;
private Psr16CacheAdapter $cacheAdapter;

/**
* @param ConfigRepository $configProvider
* @param LogServiceInterface $logger
*/
public function __construct(
ConfigRepository $configProvider,
LogServiceInterface $logger
LogServiceInterface $logger,
Psr16CacheAdapter $cacheAdapter,
) {
$this->configProvider = $configProvider;
$this->logger = $logger;
$this->cacheAdapter = $cacheAdapter;
}

/**
Expand All @@ -54,18 +59,27 @@ public function create(int $storeId = 0, ?array $data = []): ?ClientInterface

/**
* @param array $credentials
* @param bool|null $forceSandbox
* @return ClientInterface|null
* @throws InvalidArgumentException
* @throws SignerException
*/
private function createClient(array $credentials, ?bool $forceSandbox = null): ?ClientInterface
{
$cacheEncryptionKey = $credentials['cache_encryption_key'];
Settings::tlAgent('truelayer-magento/' . $this->configProvider->getExtensionVersion());
return Client::configure()
->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(is_null($forceSandbox) ? !$this->configProvider->isSandbox() : !$forceSandbox)
->create();
->useProduction(is_null($forceSandbox) ? !$this->configProvider->isSandbox() : !$forceSandbox);

if ($cacheEncryptionKey) {
$clientFactory->cache($this->cacheAdapter, $cacheEncryptionKey);
}

return $clientFactory->create();
}
}
44 changes: 44 additions & 0 deletions Setup/Recurring.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

namespace TrueLayer\Connect\Setup;

use Magento\Framework\Setup\InstallSchemaInterface;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\SchemaSetupInterface;
use Magento\Config\Model\ResourceModel\Config;
use TrueLayer\Connect\Api\Config\RepositoryInterface;
use TrueLayer\Connect\Api\Config\System\ConnectionInterface;
use Magento\Framework\Encryption\EncryptorInterface;

class Recurring implements InstallSchemaInterface
{
public function __construct(private RepositoryInterface $configRepository, private EncryptorInterface $encryptor, private Config $resourceConfig)
{

}
public function install(SchemaSetupInterface $setup, ModuleContextInterface $context)
{
$setup->startSetup();

$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));
$value = $this->encryptor->encrypt($value);
$this->resourceConfig->saveConfig($path, $value, 'default', 0);
}

$setup->endSetup();
}
}
7 changes: 7 additions & 0 deletions etc/cache.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Cache/etc/cache.xsd">
<type name="truelayer" translate="label,description" instance="TrueLayer\Connect\Model\Cache\CacheType">
<label>Truelayer</label>
<description>Cache used by TrueLayer Plugin</description>
</type>
</config>

0 comments on commit c049613

Please sign in to comment.