Skip to content

Commit

Permalink
send private key in form body as text and encrypt
Browse files Browse the repository at this point in the history
  • Loading branch information
artyom-jaksov-tl committed Nov 6, 2024
1 parent de67360 commit 38c42a0
Show file tree
Hide file tree
Showing 11 changed files with 259 additions and 206 deletions.
47 changes: 47 additions & 0 deletions Block/Adminhtml/System/Config/Field/Base64FileUpload.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php
/**
* Copyright © TrueLayer Ltd. All rights reserved.
* See COPYING.txt for license details.
*/
declare(strict_types=1);

namespace TrueLayer\Connect\Block\Adminhtml\System\Config\Field;

use Magento\Framework\Data\Form\Element\AbstractElement;
use Magento\Config\Block\System\Config\Form\Field;
use TrueLayer\Connect\Model\Config\Source\Mode;

/**
* Color picker for admin config field
*/
class Base64FileUpload extends Field
{
protected $_template = 'TrueLayer_Connect::system/config/button/base64-file-upload.phtml';

// public function
/**
* @param AbstractElement $element
* @return string
*/
protected function _getElementHtml(AbstractElement $element)
{
$htmlTextInputId = $element->getHtmlId();
$mode = $element->getData('field_config')['depends']['fields']['mode']['value'] ?? Mode::SANDBOX;
$fieldType = $element->getData('field_config')['type'] ?? 'text';
$tooltip = $element->getTooltip();
$element->setTooltip();
$displayValue = $element->getValue();
if ($displayValue && $fieldType == 'obscure') {
$displayValue = '******';
}

$this->setData([
'htmlTextInputId' => $htmlTextInputId,
'mode' => $mode,
'fieldType' => $fieldType,
'displayValue' => $displayValue,
'tooltip' => $tooltip,
]);
return $this->_toHtml();
}
}
37 changes: 13 additions & 24 deletions Controller/Adminhtml/Credentials/Check.php
Original file line number Diff line number Diff line change
Expand Up @@ -154,52 +154,41 @@ private function getCredentials(): array
$clientId = $this->getRequest()->getParam('sandbox_client_id');
$clientSecret = $this->getRequest()->getParam('sandbox_client_secret');
$keyId = $this->getRequest()->getParam('sandbox_key_id');
$privateKey = $this->getRequest()->getParam('sandbox_private_key');
} else {
$clientId = $this->getRequest()->getParam('production_client_id');
$clientSecret = $this->getRequest()->getParam('production_client_secret');
$keyId = $this->getRequest()->getParam('production_key_id');
$privateKey = $this->getRequest()->getParam('production_private_key');
}

$configCredentials = $this->configProvider->getCredentials($storeId, $mode === Mode::SANDBOX);
if ($clientSecret == '******') {
$clientSecret = $configCredentials['client_secret'];
}
if ($privateKey == '******') {
$privateKey = $configCredentials['private_key'];
} else {
if ($privateKey) {
$decoded = base64_decode($privateKey, true);
if (@base64_encode($decoded) === $privateKey) {
$privateKey = $decoded;
}
}
}

return [
'store_id' => $storeId,
'credentials' => [
'client_id' => $clientId,
'client_secret' => $clientSecret,
'private_key' => $this->getPrivateKeyPath($configCredentials),
'private_key' => $privateKey,
'key_id' => $keyId,
'cache_encryption_key' => $configCredentials['cache_encryption_key']
]
];
}

/**
* @param array $configCredentials
* @return string
* @throws FileSystemException
*/
private function getPrivateKeyPath(array $configCredentials): string
{
if ($privateKey = $this->getRequest()->getParam('private_key')) {
$path = $this->directoryList->getPath('var') . self::PEM_UPLOAD_FILE;
$fileInfo = $this->file->getPathInfo($path);

if (!$this->file->fileExists($fileInfo['dirname'])) {
$this->file->mkdir($fileInfo['dirname']);
}

$this->file->write($path, $privateKey);

return $path;
}

return $configCredentials['private_key'];
}

/**
* @return void
* @throws FileSystemException
Expand Down
14 changes: 5 additions & 9 deletions Model/Config/System/ConnectionRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public function getCredentials(?int $storeId = null, ?bool $forceSandbox = null)
return [
"client_id" => $this->getClientId($storeId, $isSandBox),
"client_secret" => $this->getClientSecret($storeId, $isSandBox),
"private_key" => $this->getPathToPrivateKey($storeId, $isSandBox),
"private_key" => $this->getPrivateKey($storeId, $isSandBox),
"key_id" => $this->getKeyId($storeId, $isSandBox),
"cache_encryption_key" => $this->getCacheEncryptionKey($storeId)
];
Expand All @@ -52,18 +52,14 @@ public function getCredentials(?int $storeId = null, ?bool $forceSandbox = null)
* @param bool $isSandBox
* @return string
*/
private function getPathToPrivateKey(?int $storeId = null, bool $isSandBox = false): string
private function getPrivateKey(?int $storeId = null, bool $isSandBox = false): string
{
$path = $isSandBox ? self::XML_PATH_SANDBOX_PRIVATE_KEY : self::XML_PATH_PRODUCTION_PRIVATE_KEY;
if (!$savedPrivateKey = $this->getStoreValue($path, $storeId)) {
return '';
if ($value = $this->getStoreValue($path, $storeId)) {
return $this->encryptor->decrypt($value);
}

try {
return $this->directoryList->getPath('var') . '/truelayer/' . $savedPrivateKey;
} catch (\Exception $exception) {
return '';
}
return '';
}

/**
Expand Down
180 changes: 18 additions & 162 deletions Model/System/Config/Backend/PrivateKey.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,178 +7,34 @@

namespace TrueLayer\Connect\Model\System\Config\Backend;

use Magento\Framework\App\Cache\TypeListInterface;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Framework\App\Config\Value;
use Magento\Framework\Data\Collection\AbstractDb;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Filesystem;
use Magento\Framework\Filesystem\Directory\ReadInterface;
use Magento\Framework\Filesystem\Io\File;
use Magento\Framework\Model\Context;
use Magento\Framework\Model\ResourceModel\AbstractResource;
use Magento\Framework\Registry;
use TrueLayer\Connect\Api\Config\System\ConnectionInterface;
use Magento\Config\Model\Config\Backend\Encrypted;

/**
* Backend model for saving certificate file
* Backend model for saving certificate
*/
class PrivateKey extends Value
class PrivateKey extends Encrypted
{
public const FILENAME = 'private-key.pem';
/**
* @var File
*/
private $file;
/**
* @var ReadInterface
*/
private $tmpDirectory;
/**
* @var ReadInterface
*/
private $varDirectory;

/**
* @param Context $context
* @param Registry $registry
* @param ScopeConfigInterface $config
* @param TypeListInterface $cacheTypeList
* @param Filesystem $filesystem
* @param File $file
* @param AbstractResource|null $resource
* @param AbstractDb|null $resourceCollection
* @param array $data
*/
public function __construct(
Context $context,
Registry $registry,
ScopeConfigInterface $config,
TypeListInterface $cacheTypeList,
Filesystem $filesystem,
File $file,
AbstractResource $resource = null,
AbstractDb $resourceCollection = null,
array $data = []
) {
$this->file = $file;
$this->tmpDirectory = $filesystem->getDirectoryRead('sys_tmp');
$this->varDirectory = $filesystem->getDirectoryRead('var');
parent::__construct($context, $registry, $config, $cacheTypeList, $resource, $resourceCollection, $data);
}

/**
* Process additional data before save config.
* Decode and encrypt value before saving
*
* @return $this
* @throws LocalizedException
*/
public function beforeSave(): self
{
$value = (array)$this->getValue();
$sandbox = $this->getPath() === ConnectionInterface::XML_PATH_SANDBOX_PRIVATE_KEY;
$directory = $this->getDirectory($sandbox);

if (!empty($value['delete'])) {
$this->deleteCertificateAndReset($this->isObjectNew() ? '' : $this->getOldValue());
return $this;
}

$tmpName = $this->getTmpName($sandbox);
$isUploading = (is_string($tmpName) && !empty($tmpName) && $this->tmpDirectory->isExist($tmpName));

if (!$isUploading) {
$this->setValue($this->isObjectNew() ? '' : $this->getOldValue());
return $this;
}

if ($isUploading) {
$tmpPath = $this->tmpDirectory->getAbsolutePath($tmpName);
if (!$this->tmpDirectory->stat($tmpPath)['size']) {
throw new LocalizedException(__('The TrueLayer certificate file is empty.'));
}

$destinationPath = $this->varDirectory->getAbsolutePath('truelayer/' . $directory);

$filePath = $directory . self::FILENAME;
$this->file->checkAndCreateFolder($destinationPath);
$this->file->mv(
$tmpPath,
$this->varDirectory->getAbsolutePath('truelayer/' . $filePath)
);
$this->setValue($filePath);
}

return $this;
}

/**
* Delete the cert file from disk when deleting the setting.
*
* @return $this
*/
public function beforeDelete()
{
$returnValue = parent::beforeDelete();
$filePath = $this->isObjectNew() ? '' : $this->getOldValue();
if ($filePath) {
$absolutePath = $this->varDirectory->getAbsolutePath('truelayer/' . $filePath);
if ($this->file->fileExists($absolutePath)) {
$this->file->rm($absolutePath);
}
}
return $returnValue;
}

/**
* Delete the cert file and unset the config value.
*
* @param string $filePath
* @return void
*/
private function deleteCertificateAndReset(string $filePath): void
public function beforeSave()
{
if (!empty($filePath)) {
$absolutePath = $this->varDirectory->getAbsolutePath('truelayer/' . $filePath);
if ($this->file->fileExists($absolutePath)) {
$this->file->rm($absolutePath);
$this->_dataSaveAllowed = false;
$value = (string)$this->getValue();
// don't save value, if an obscured value was received. This indicates that data was not changed.
if (!preg_match('/^\*+$/', $value) && !empty($value)) {
$this->_dataSaveAllowed = true;
$decoded = base64_decode($value, true);
if (!$decoded || @base64_encode($decoded) !== $value) {
$decoded = '';
}
}

$this->setValue('');
}

/**
* Returns the directory based on set scope.
*
* @param bool $sandbox
* @return string
*/
private function getDirectory(bool $sandbox): string
{
$mode = $sandbox ? 'sandbox' : 'production';
return $this->getScope() !== 'default'
? sprintf('%s/%s/%s/', $mode, $this->getScope(), $this->getScopeId())
: sprintf('%s/default/', $mode);
}

/**
* Returns the path to the uploaded tmp_file based on set scope.
*
* @param bool $sandbox
* @return string
*/
private function getTmpName(bool $sandbox): ?string
{
$files = $_FILES;
if (empty($files)) {
return null;
}
try {
$tmpName = $files['groups']['tmp_name']['general']['fields'][$sandbox ? 'sandbox_private_key' : 'production_private_key']['value'];
return empty($tmpName) ? null : $tmpName;
} catch (\Exception $e) {
return null;
$encrypted = $decoded ? $this->_encryptor->encrypt($decoded) : null;
$this->setValue($encrypted);
} elseif (empty($value)) {
$this->setValue(null);
$this->_dataSaveAllowed = true;
}
}
}
2 changes: 1 addition & 1 deletion Service/Client/ClientFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ private function createClient(array $credentials, ?bool $forceSandbox = null): ?
$clientFactory->clientId($credentials['client_id'])
->clientSecret($credentials['client_secret'])
->keyId($credentials['key_id'])
->pemFile($credentials['private_key'])
->pem($credentials['private_key'])
->useProduction(is_null($forceSandbox) ? !$this->configProvider->isSandbox() : !$forceSandbox);

if ($cacheEncryptionKey) {
Expand Down
Loading

0 comments on commit 38c42a0

Please sign in to comment.