From 522badaa2e7183c95b1a222a766740fc87948c22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20D?= Date: Sat, 9 Mar 2024 13:43:27 +0100 Subject: [PATCH] chore: Refactor encryptable models MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rubén D --- lib/SP/DataModel/EncryptedModel.php | 74 ++++++++++++------- .../Domain/Common/Attributes/Encryptable.php | 48 ++++++++++++ lib/SP/Domain/Plugin/Services/PluginData.php | 42 +++-------- .../Plugin/Repositories/PluginDataModel.php | 2 + 4 files changed, 106 insertions(+), 60 deletions(-) create mode 100644 lib/SP/Domain/Common/Attributes/Encryptable.php diff --git a/lib/SP/DataModel/EncryptedModel.php b/lib/SP/DataModel/EncryptedModel.php index 92e14483..3df0d768 100644 --- a/lib/SP/DataModel/EncryptedModel.php +++ b/lib/SP/DataModel/EncryptedModel.php @@ -4,7 +4,7 @@ * * @author nuxsmin * @link https://syspass.org - * @copyright 2012-2023, Rubén Domínguez nuxsmin@$syspass.org + * @copyright 2012-2024, Rubén Domínguez nuxsmin@$syspass.org * * This file is part of sysPass. * @@ -24,9 +24,10 @@ namespace SP\DataModel; +use ReflectionClass; +use SP\Domain\Common\Attributes\Encryptable; use SP\Domain\Core\Crypt\CryptInterface; use SP\Domain\Core\Exceptions\CryptException; -use SP\Domain\Core\Exceptions\NoSuchPropertyException; /** * Trait EncryptedModel @@ -36,53 +37,70 @@ trait EncryptedModel protected ?string $key = null; /** - * @param string $key + * Encrypt the encryptable property and returns a new object with the encrypted property and key + * + * @param string $password * @param CryptInterface $crypt - * @param string $property * * @return EncryptedModel * @throws CryptException - * @throws NoSuchPropertyException */ - public function encrypt(string $key, CryptInterface $crypt, string $property = 'data'): static + public function encrypt(string $password, CryptInterface $crypt): static { - if (property_exists($this, $property)) { - if ($this->{$property} === null) { - return $this; + $reflectionClass = new ReflectionClass($this); + + foreach ($reflectionClass->getAttributes(Encryptable::class) as $attribute) { + /** @var Encryptable $instance */ + $instance = $attribute->newInstance(); + + $data = $this->{$instance->getDataProperty()}; + + if ($data !== null) { + return $this->mutate([ + $instance->getKeyProperty() => $crypt->makeSecuredKey($password), + $instance->getDataProperty() => $crypt->encrypt( + $data, + $this->{$instance->getKeyProperty()}, + $password + ) + ]); } - - $this->key = $crypt->makeSecuredKey($key); - - $this->{$property} = $crypt->encrypt($this->{$property}, $this->key, $key); - - return $this; } - throw new NoSuchPropertyException($property); + return $this; } /** - * @param string $key + * Decrypt the encryptable property and returns a new object with the decryped property and key + * + * @param string $password * @param CryptInterface $crypt - * @param string $property * * @return EncryptedModel * @throws CryptException - * @throws NoSuchPropertyException */ - public function decrypt(string $key, CryptInterface $crypt, string $property = 'data'): static + public function decrypt(string $password, CryptInterface $crypt): static { - if (property_exists($this, $property) && !empty($this->key)) { - if ($this->{$property} === null) { - return $this; + $reflectionClass = new ReflectionClass($this); + + foreach ($reflectionClass->getAttributes(Encryptable::class) as $attribute) { + /** @var Encryptable $instance */ + $instance = $attribute->newInstance(); + + $data = $this->{$instance->getDataProperty()}; + + if ($data !== null) { + return $this->mutate([ + $instance->getDataProperty() => $crypt->decrypt( + $data, + $this->{$instance->getKeyProperty()}, + $password + ) + ]); } - - $this->{$property} = $crypt->decrypt($this->{$property}, $this->key, $key); - - return $this; } - throw new NoSuchPropertyException($property); + return $this; } public function getKey(): ?string diff --git a/lib/SP/Domain/Common/Attributes/Encryptable.php b/lib/SP/Domain/Common/Attributes/Encryptable.php new file mode 100644 index 00000000..448bc4e6 --- /dev/null +++ b/lib/SP/Domain/Common/Attributes/Encryptable.php @@ -0,0 +1,48 @@ +. + */ + +namespace SP\Domain\Common\Attributes; + +use Attribute; + +/** + * Class Encryptable + */ +#[Attribute(Attribute::TARGET_CLASS)] +final class Encryptable +{ + public function __construct(private readonly string $dataProperty, private readonly string $keyProperty) + { + } + + public function getDataProperty(): string + { + return $this->dataProperty; + } + + public function getKeyProperty(): string + { + return $this->keyProperty; + } +} diff --git a/lib/SP/Domain/Plugin/Services/PluginData.php b/lib/SP/Domain/Plugin/Services/PluginData.php index 50bf2e5e..f52c5fa2 100644 --- a/lib/SP/Domain/Plugin/Services/PluginData.php +++ b/lib/SP/Domain/Plugin/Services/PluginData.php @@ -4,7 +4,7 @@ * * @author nuxsmin * @link https://syspass.org - * @copyright 2012-2023, Rubén Domínguez nuxsmin@$syspass.org + * @copyright 2012-2024, Rubén Domínguez nuxsmin@$syspass.org * * This file is part of sysPass. * @@ -30,7 +30,6 @@ use SP\Domain\Common\Services\ServiceException; use SP\Domain\Core\Crypt\CryptInterface; use SP\Domain\Core\Exceptions\ConstraintException; use SP\Domain\Core\Exceptions\CryptException; -use SP\Domain\Core\Exceptions\NoSuchPropertyException; use SP\Domain\Core\Exceptions\QueryException; use SP\Domain\Core\Exceptions\SPException; use SP\Domain\Plugin\Ports\PluginDataInterface; @@ -61,10 +60,9 @@ final class PluginData extends Service implements PluginDataInterface * @param PluginDataModel $itemData * @return QueryResult * @throws ConstraintException - * @throws NoSuchPropertyException + * @throws CryptException * @throws QueryException * @throws ServiceException - * @throws CryptException */ public function create(PluginDataModel $itemData): QueryResult { @@ -78,7 +76,6 @@ final class PluginData extends Service implements PluginDataInterface * @return int * @throws ConstraintException * @throws CryptException - * @throws NoSuchPropertyException * @throws QueryException * @throws ServiceException */ @@ -96,9 +93,7 @@ final class PluginData extends Service implements PluginDataInterface * @throws ConstraintException * @throws CryptException * @throws NoSuchItemException - * @throws NoSuchPropertyException * @throws QueryException - * @throws SPException * @throws ServiceException */ public function getByItemId(string $name, int $id): PluginDataModel @@ -109,10 +104,9 @@ final class PluginData extends Service implements PluginDataInterface throw new NoSuchItemException(__u('Plugin\'s data not found'), SPException::INFO); } - /** @var PluginDataModel $itemData */ - $itemData = $result->getData(); - return $itemData->decrypt($this->getMasterKeyFromContext(), $this->crypt); + return $result->getData(PluginDataModel::class) + ->decrypt($this->getMasterKeyFromContext(), $this->crypt); } /** @@ -123,9 +117,7 @@ final class PluginData extends Service implements PluginDataInterface * @throws ConstraintException * @throws CryptException * @throws NoSuchItemException - * @throws NoSuchPropertyException * @throws QueryException - * @throws SPException * @throws ServiceException */ public function getById(string $id): array @@ -136,16 +128,10 @@ final class PluginData extends Service implements PluginDataInterface throw new NoSuchItemException(__u('Plugin\'s data not found'), SPException::INFO); } - $data = $result->getDataAsArray(); - - array_walk( - $data, - function (PluginDataModel $itemData) { - $itemData->decrypt($this->getMasterKeyFromContext(), $this->crypt); - } + return array_map( + fn(PluginDataModel $itemData) => $itemData->decrypt($this->getMasterKeyFromContext(), $this->crypt), + $result->getDataAsArray() ); - - return $data; } /** @@ -154,24 +140,16 @@ final class PluginData extends Service implements PluginDataInterface * @return PluginDataModel[] * @throws ConstraintException * @throws CryptException - * @throws NoSuchPropertyException * @throws QueryException * @throws SPException * @throws ServiceException */ public function getAll(): array { - $data = $this->pluginDataRepository->getAll()->getDataAsArray(); - - array_walk( - $data, - function ($itemData) { - /** @var PluginDataModel $itemData */ - $itemData->decrypt($this->getMasterKeyFromContext(), $this->crypt); - } + return array_map( + fn(PluginDataModel $itemData) => $itemData->decrypt($this->getMasterKeyFromContext(), $this->crypt), + $this->pluginDataRepository->getAll()->getDataAsArray() ); - - return $data; } /** diff --git a/lib/SP/Infrastructure/Plugin/Repositories/PluginDataModel.php b/lib/SP/Infrastructure/Plugin/Repositories/PluginDataModel.php index e0bf25f7..acb90b6f 100644 --- a/lib/SP/Infrastructure/Plugin/Repositories/PluginDataModel.php +++ b/lib/SP/Infrastructure/Plugin/Repositories/PluginDataModel.php @@ -25,6 +25,7 @@ namespace SP\Infrastructure\Plugin\Repositories; use SP\DataModel\EncryptedModel; +use SP\Domain\Common\Attributes\Encryptable; use SP\Domain\Common\Models\HydratableModel; use SP\Domain\Common\Models\Model; use SP\Domain\Common\Models\SerializedModel; @@ -32,6 +33,7 @@ use SP\Domain\Common\Models\SerializedModel; /** * Class PluginDataModel */ +#[Encryptable('data', 'key')] final class PluginDataModel extends Model implements HydratableModel { use SerializedModel;