diff --git a/.phpunit.result.cache b/.phpunit.result.cache new file mode 100644 index 00000000..2ea5639a --- /dev/null +++ b/.phpunit.result.cache @@ -0,0 +1 @@ +{"version":1,"defects":{"Ambta\\DoctrineEncryptBundle\\Tests\\Functional\\BasicQueryTest\\BasicQueryDefuseTest::testPersistEntity":4,"Ambta\\DoctrineEncryptBundle\\Tests\\Functional\\BasicQueryTest\\BasicQueryDefuseTest::testNoUpdateOnReadEncrypted":4,"Ambta\\DoctrineEncryptBundle\\Tests\\Functional\\BasicQueryTest\\BasicQueryDefuseTest::testStoredDataIsEncrypted":4,"Ambta\\DoctrineEncryptBundle\\Tests\\Functional\\BasicQueryTest\\BasicQueryHaliteTest::testPersistEntity":1,"Ambta\\DoctrineEncryptBundle\\Tests\\Functional\\BasicQueryTest\\BasicQueryHaliteTest::testNoUpdateOnReadEncrypted":1,"Ambta\\DoctrineEncryptBundle\\Tests\\Functional\\BasicQueryTest\\BasicQueryHaliteTest::testStoredDataIsEncrypted":1,"Ambta\\DoctrineEncryptBundle\\Tests\\Unit\\DependencyInjection\\DoctrineEncryptExtensionTest::testConfigLoadCustom":1,"Ambta\\DoctrineEncryptBundle\\Tests\\Unit\\Encryptors\\HaliteEncryptorTest::testEncryptWithoutExtensionThrowsException":1,"Ambta\\DoctrineEncryptBundle\\Tests\\Unit\\Encryptors\\DefuseEncryptorTest::testEncrypt":4,"Ambta\\DoctrineEncryptBundle\\Tests\\Unit\\Encryptors\\DefuseEncryptorTest::testGenerateKey":4,"Ambta\\DoctrineEncryptBundle\\Tests\\Unit\\Encryptors\\HaliteEncryptorTest::testEncryptExtension":1,"Ambta\\DoctrineEncryptBundle\\Tests\\Unit\\Encryptors\\HaliteEncryptorTest::testGenerateKey":1,"Ambta\\DoctrineEncryptBundle\\Tests\\Unit\\Subscribers\\DoctrineEncryptSubscriberTest::testProcessFieldsNoEncryptor":4},"times":{"Ambta\\DoctrineEncryptBundle\\Tests\\Functional\\BasicQueryTest\\BasicQueryDefuseTest::testPersistEntity":0.456,"Ambta\\DoctrineEncryptBundle\\Tests\\Functional\\BasicQueryTest\\BasicQueryDefuseTest::testNoUpdateOnReadEncrypted":1.046,"Ambta\\DoctrineEncryptBundle\\Tests\\Functional\\BasicQueryTest\\BasicQueryDefuseTest::testStoredDataIsEncrypted":0.654,"Ambta\\DoctrineEncryptBundle\\Tests\\Functional\\BasicQueryTest\\BasicQueryHaliteTest::testPersistEntity":0.106,"Ambta\\DoctrineEncryptBundle\\Tests\\Functional\\BasicQueryTest\\BasicQueryHaliteTest::testNoUpdateOnReadEncrypted":0.142,"Ambta\\DoctrineEncryptBundle\\Tests\\Functional\\BasicQueryTest\\BasicQueryHaliteTest::testStoredDataIsEncrypted":0.126,"Ambta\\DoctrineEncryptBundle\\Tests\\Unit\\DependencyInjection\\DoctrineEncryptExtensionTest::testConfigLoadHalite":0.031,"Ambta\\DoctrineEncryptBundle\\Tests\\Unit\\DependencyInjection\\DoctrineEncryptExtensionTest::testConfigLoadDefuse":0.012,"Ambta\\DoctrineEncryptBundle\\Tests\\Unit\\DependencyInjection\\DoctrineEncryptExtensionTest::testConfigLoadCustom":0.013,"Ambta\\DoctrineEncryptBundle\\Tests\\Unit\\Encryptors\\DefuseEncryptorTest::testEncrypt":0.252,"Ambta\\DoctrineEncryptBundle\\Tests\\Unit\\Encryptors\\DefuseEncryptorTest::testGenerateKey":0.132,"Ambta\\DoctrineEncryptBundle\\Tests\\Unit\\Encryptors\\HaliteEncryptorTest::testEncryptExtension":0.005,"Ambta\\DoctrineEncryptBundle\\Tests\\Unit\\Encryptors\\HaliteEncryptorTest::testGenerateKey":0.008,"Ambta\\DoctrineEncryptBundle\\Tests\\Unit\\Encryptors\\HaliteEncryptorTest::testEncryptWithoutExtensionThrowsException":0.001,"Ambta\\DoctrineEncryptBundle\\Tests\\Unit\\Subscribers\\DoctrineEncryptSubscriberTest::testSetRestorEncryptor":0.01,"Ambta\\DoctrineEncryptBundle\\Tests\\Unit\\Subscribers\\DoctrineEncryptSubscriberTest::testProcessFieldsEncrypt":0.003,"Ambta\\DoctrineEncryptBundle\\Tests\\Unit\\Subscribers\\DoctrineEncryptSubscriberTest::testProcessFieldsEncryptExtend":0.003,"Ambta\\DoctrineEncryptBundle\\Tests\\Unit\\Subscribers\\DoctrineEncryptSubscriberTest::testProcessFieldsEncryptEmbedded":0.004,"Ambta\\DoctrineEncryptBundle\\Tests\\Unit\\Subscribers\\DoctrineEncryptSubscriberTest::testProcessFieldsEncryptNull":0.002,"Ambta\\DoctrineEncryptBundle\\Tests\\Unit\\Subscribers\\DoctrineEncryptSubscriberTest::testProcessFieldsNoEncryptor":0.001,"Ambta\\DoctrineEncryptBundle\\Tests\\Unit\\Subscribers\\DoctrineEncryptSubscriberTest::testProcessFieldsDecrypt":0.002,"Ambta\\DoctrineEncryptBundle\\Tests\\Unit\\Subscribers\\DoctrineEncryptSubscriberTest::testProcessFieldsDecryptExtended":0.003,"Ambta\\DoctrineEncryptBundle\\Tests\\Unit\\Subscribers\\DoctrineEncryptSubscriberTest::testProcessFieldsDecryptEmbedded":0.003,"Ambta\\DoctrineEncryptBundle\\Tests\\Unit\\Subscribers\\DoctrineEncryptSubscriberTest::testProcessFieldsDecryptNull":0.002,"Ambta\\DoctrineEncryptBundle\\Tests\\Unit\\Subscribers\\DoctrineEncryptSubscriberTest::testProcessFieldsDecryptNonEncrypted":0.002,"Ambta\\DoctrineEncryptBundle\\Tests\\Unit\\Subscribers\\DoctrineEncryptSubscriberTest::testOnFlush":0.052,"Ambta\\DoctrineEncryptBundle\\Tests\\Unit\\Subscribers\\DoctrineEncryptSubscriberTest::testPostFlush":0.006}} \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 8ce365be..feddc2d9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,6 +26,12 @@ matrix: - php: 7.3 env: COVERAGE=true PHPUNIT_FLAGS="-v --coverage-text" + - php: 7.4 + env: COVERAGE=true PHPUNIT_FLAGS="-v --coverage-text" + + - php: 8.0 + env: COVERAGE=true PHPUNIT_FLAGS="-v --coverage-text" + # Latest commit to master - php: 7.3 env: STABILITY="dev" diff --git a/Tests/Functional/AbstractFunctionalTestCase.php b/Tests/Functional/AbstractFunctionalTestCase.php index 58552630..97fc9384 100644 --- a/Tests/Functional/AbstractFunctionalTestCase.php +++ b/Tests/Functional/AbstractFunctionalTestCase.php @@ -32,7 +32,7 @@ abstract class AbstractFunctionalTestCase extends TestCase abstract protected function getEncryptor(): EncryptorInterface; - public function setUp() + public function setUp(): void { // Create a simple "default" Doctrine ORM configuration for Annotations $isDevMode = true; @@ -73,8 +73,9 @@ public function setUp() error_reporting(E_ALL); } - public function tearDown() + public function tearDown(): void { + $this->entityManager->getConnection()->close(); unlink($this->dbFile); } @@ -132,4 +133,4 @@ public function assertStringDoesNotContain($needle, $string, $ignoreCase = false static::assertThat($string, $constraint, $message); } -} \ No newline at end of file +} diff --git a/Tests/Functional/BasicQueryTest/AbstractBasicQueryTestCase.php b/Tests/Functional/BasicQueryTest/AbstractBasicQueryTestCase.php index 442b6f66..0065311c 100644 --- a/Tests/Functional/BasicQueryTest/AbstractBasicQueryTestCase.php +++ b/Tests/Functional/BasicQueryTest/AbstractBasicQueryTestCase.php @@ -8,7 +8,7 @@ abstract class AbstractBasicQueryTestCase extends AbstractFunctionalTestCase { - public function testPersistEntity() + public function testPersistEntity(): void { $user = new CascadeTarget(); $user->setNotSecret('My public information'); @@ -21,7 +21,7 @@ public function testPersistEntity() $this->assertEquals(3,$this->getCurrentQueryCount()); } - public function testNoUpdateOnReadEncrypted() + public function testNoUpdateOnReadEncrypted(): void { $this->entityManager->beginTransaction(); $this->assertEquals(1,$this->getCurrentQueryCount()); @@ -57,7 +57,7 @@ public function testNoUpdateOnReadEncrypted() $this->assertEquals(4,$this->getCurrentQueryCount()); } - public function testStoredDataIsEncrypted() + public function testStoredDataIsEncrypted(): void { $user = new CascadeTarget(); $user->setNotSecret('My public information'); diff --git a/Tests/Functional/DoctrineEncryptSubscriber/AbstractDoctrineEncryptSubscriberTestCase.php b/Tests/Functional/DoctrineEncryptSubscriber/AbstractDoctrineEncryptSubscriberTestCase.php index 7b113de6..2c11b33f 100644 --- a/Tests/Functional/DoctrineEncryptSubscriber/AbstractDoctrineEncryptSubscriberTestCase.php +++ b/Tests/Functional/DoctrineEncryptSubscriber/AbstractDoctrineEncryptSubscriberTestCase.php @@ -11,7 +11,7 @@ abstract class AbstractDoctrineEncryptSubscriberTestCase extends AbstractFunctionalTestCase { - public function testEncryptionHappensOnOnlyAnnotatedFields() + public function testEncryptionHappensOnOnlyAnnotatedFields(): void { $secret = "It's a secret"; $notSecret = "You're all welcome to know this."; @@ -44,7 +44,7 @@ public function testEncryptionHappensOnOnlyAnnotatedFields() $this->assertEquals($secret, $decrypted); } - public function testEncryptionCascades() + public function testEncryptionCascades(): void { $secret = "It's a secret"; $notSecret = "You're all welcome to know this."; @@ -87,7 +87,7 @@ public function testEncryptionCascades() * @throws \Doctrine\DBAL\DBALException * @throws \Doctrine\ORM\OptimisticLockException */ - public function testEncryptionDoesNotHappenWhenThereIsNoChange() + public function testEncryptionDoesNotHappenWhenThereIsNoChange(): void { $secret = "It's a secret"; $notSecret = "You're all welcome to know this."; @@ -155,7 +155,7 @@ public function testEncryptionDoesNotHappenWhenThereIsNoChange() } - public function testEncryptionDoesHappenWhenASecretIsChanged() + public function testEncryptionDoesHappenWhenASecretIsChanged(): void { $secret = "It's a secret"; $notSecret = "You're all welcome to know this."; @@ -198,4 +198,4 @@ public function testEncryptionDoesHappenWhenASecretIsChanged() $this->assertStringEndsWith('', $shouldBeDifferentFromBefore); // is encrypted $this->assertNotEquals($originalEncryption, $shouldBeDifferentFromBefore); } -} \ No newline at end of file +} diff --git a/Tests/Unit/DependencyInjection/DoctrineEncryptExtensionTest.php b/Tests/Unit/DependencyInjection/DoctrineEncryptExtensionTest.php index 42b422c8..4aced3d8 100644 --- a/Tests/Unit/DependencyInjection/DoctrineEncryptExtensionTest.php +++ b/Tests/Unit/DependencyInjection/DoctrineEncryptExtensionTest.php @@ -16,12 +16,12 @@ class DoctrineEncryptExtensionTest extends TestCase */ private $extension; - protected function setUp() + protected function setUp(): void { $this->extension = new DoctrineEncryptExtension(); } - public function testConfigLoadHalite() + public function testConfigLoadHalite(): void { $container = $this->createContainer(); $this->extension->load([[]], $container); @@ -29,7 +29,7 @@ public function testConfigLoadHalite() $this->assertSame(HaliteEncryptor::class, $container->getParameter('ambta_doctrine_encrypt.encryptor_class_name')); } - public function testConfigLoadDefuse() + public function testConfigLoadDefuse(): void { $container = $this->createContainer(); @@ -41,7 +41,7 @@ public function testConfigLoadDefuse() $this->assertSame(DefuseEncryptor::class, $container->getParameter('ambta_doctrine_encrypt.encryptor_class_name')); } - public function testConfigLoadCustom() + public function testConfigLoadCustom(): void { $container = $this->createContainer(); $config = [ @@ -54,7 +54,7 @@ public function testConfigLoadCustom() $this->assertSame(self::class, $container->getParameter('ambta_doctrine_encrypt.encryptor_class_name')); } - private function createContainer() + private function createContainer(): ContainerBuilder { $container = new ContainerBuilder( new ParameterBag(['kernel.debug' => false]) diff --git a/Tests/Unit/Encryptors/DefuseEncryptorTest.php b/Tests/Unit/Encryptors/DefuseEncryptorTest.php index c57e9813..cf3e0d8e 100644 --- a/Tests/Unit/Encryptors/DefuseEncryptorTest.php +++ b/Tests/Unit/Encryptors/DefuseEncryptorTest.php @@ -9,7 +9,7 @@ class DefuseEncryptorTest extends TestCase { private const DATA = 'foobar'; - public function testEncrypt() + public function testEncrypt(): void { $keyfile = __DIR__.'/fixtures/defuse.key'; $key = file_get_contents($keyfile); @@ -24,7 +24,7 @@ public function testEncrypt() $this->assertSame($key, $newkey, 'The key must not be modified'); } - public function testGenerateKey() + public function testGenerateKey(): void { $keyfile = sys_get_temp_dir().'/defuse-'.md5(time()); if (file_exists($keyfile)) { diff --git a/Tests/Unit/Encryptors/HaliteEncryptorTest.php b/Tests/Unit/Encryptors/HaliteEncryptorTest.php index 96c064b5..794b5a2f 100644 --- a/Tests/Unit/Encryptors/HaliteEncryptorTest.php +++ b/Tests/Unit/Encryptors/HaliteEncryptorTest.php @@ -9,7 +9,7 @@ class HaliteEncryptorTest extends TestCase { private const DATA = 'foobar'; - public function testEncryptExtension() + public function testEncryptExtension(): void { if (! extension_loaded('sodium')) { $this->markTestSkipped('This test only runs when the sodium extension is enabled.'); @@ -27,7 +27,7 @@ public function testEncryptExtension() $this->assertSame($key, $newkey, 'The key must not be modified'); } - public function testGenerateKey() + public function testGenerateKey(): void { if (! extension_loaded('sodium')) { $this->markTestSkipped('This test only runs when the sodium extension is enabled.'); @@ -46,7 +46,7 @@ public function testGenerateKey() } - public function testEncryptWithoutExtensionThrowsException() + public function testEncryptWithoutExtensionThrowsException(): void { if (extension_loaded('sodium')) { $this->markTestSkipped('This only runs when the sodium extension is disabled.'); diff --git a/Tests/Unit/Subscribers/DoctrineEncryptSubscriberTest.php b/Tests/Unit/Subscribers/DoctrineEncryptSubscriberTest.php index 59b880cb..0a407cc3 100644 --- a/Tests/Unit/Subscribers/DoctrineEncryptSubscriberTest.php +++ b/Tests/Unit/Subscribers/DoctrineEncryptSubscriberTest.php @@ -35,7 +35,7 @@ class DoctrineEncryptSubscriberTest extends TestCase */ private $reader; - protected function setUp() + protected function setUp(): void { $this->encryptor = $this->createMock(EncryptorInterface::class); $this->encryptor @@ -71,7 +71,7 @@ protected function setUp() $this->subscriber = new DoctrineEncryptSubscriber($this->reader, $this->encryptor); } - public function testSetRestorEncryptor() + public function testSetRestorEncryptor(): void { $replaceEncryptor = $this->createMock(EncryptorInterface::class); @@ -82,7 +82,7 @@ public function testSetRestorEncryptor() $this->assertSame($this->encryptor, $this->subscriber->getEncryptor()); } - public function testProcessFieldsEncrypt() + public function testProcessFieldsEncrypt(): void { $user = new User('David', 'Switzerland'); @@ -92,7 +92,7 @@ public function testProcessFieldsEncrypt() $this->assertStringStartsWith('encrypted-', $user->getAddress()); } - public function testProcessFieldsEncryptExtend() + public function testProcessFieldsEncryptExtend(): void { $user = new ExtendedUser('David', 'Switzerland', 'extra'); @@ -103,7 +103,7 @@ public function testProcessFieldsEncryptExtend() $this->assertStringStartsWith('encrypted-', $user->extra); } - public function testProcessFieldsEncryptEmbedded() + public function testProcessFieldsEncryptEmbedded(): void { $withUser = new WithUser('Thing', 'foo', new User('David', 'Switzerland')); @@ -115,7 +115,7 @@ public function testProcessFieldsEncryptEmbedded() $this->assertStringStartsWith('encrypted-', $withUser->user->getAddress()); } - public function testProcessFieldsEncryptNull() + public function testProcessFieldsEncryptNull(): void { $user = new User('David', null); @@ -125,7 +125,7 @@ public function testProcessFieldsEncryptNull() $this->assertNull($user->getAddress()); } - public function testProcessFieldsNoEncryptor() + public function testProcessFieldsNoEncryptor(): void { $user = new User('David', 'Switzerland'); @@ -136,7 +136,7 @@ public function testProcessFieldsNoEncryptor() $this->assertSame('Switzerland', $user->getAddress()); } - public function testProcessFieldsDecrypt() + public function testProcessFieldsDecrypt(): void { $user = new User('encrypted-David', 'encrypted-Switzerland'); @@ -146,7 +146,7 @@ public function testProcessFieldsDecrypt() $this->assertSame('Switzerland', $user->getAddress()); } - public function testProcessFieldsDecryptExtended() + public function testProcessFieldsDecryptExtended(): void { $user = new ExtendedUser('encrypted-David', 'encrypted-Switzerland', 'encrypted-extra'); @@ -157,7 +157,7 @@ public function testProcessFieldsDecryptExtended() $this->assertSame('extra', $user->extra); } - public function testProcessFieldsDecryptEmbedded() + public function testProcessFieldsDecryptEmbedded(): void { $withUser = new WithUser('encrypted-Thing', 'foo', new User('encrypted-David', 'encrypted-Switzerland')); @@ -169,7 +169,7 @@ public function testProcessFieldsDecryptEmbedded() $this->assertSame('Switzerland', $withUser->user->getAddress()); } - public function testProcessFieldsDecryptNull() + public function testProcessFieldsDecryptNull(): void { $user = new User('encrypted-David', null); @@ -179,7 +179,7 @@ public function testProcessFieldsDecryptNull() $this->assertNull($user->getAddress()); } - public function testProcessFieldsDecryptNonEncrypted() + public function testProcessFieldsDecryptNonEncrypted(): void { // no trailing but somethint that our mock decrypt would change if called $user = new User('encrypted-David', 'encrypted-Switzerland'); @@ -193,7 +193,7 @@ public function testProcessFieldsDecryptNonEncrypted() /** * Test that fields are encrypted before flushing. */ - public function testOnFlush() + public function testOnFlush(): void { $user = new User('David', 'Switzerland'); @@ -222,7 +222,7 @@ public function testOnFlush() /** * Test that fields are decrypted again after flushing */ - public function testPostFlush() + public function testPostFlush(): void { $user = new User('encrypted-David', 'encrypted-Switzerland'); diff --git a/composer.json b/composer.json index c463c326..fc24f0c4 100644 --- a/composer.json +++ b/composer.json @@ -1,38 +1,40 @@ { - "name": "michaeldegroot/doctrine-encrypt-bundle", - "type": "library", - "keywords": ["doctrine", "symfony", "halite", "defuse", "encrypt", "decrypt"], - "license": "MIT", - "description": "Encrypted symfony entity's by verified and standardized libraries", - "require": { - "php": "^7.2", - "paragonie/halite": "^4.6", - "paragonie/sodium_compat": "^1.5", - "doctrine/orm": "^2.5", - "symfony/property-access": "^4.1|^5.0", - "symfony/dependency-injection": "^4.1|^5.0", - "symfony/yaml": "^4.1|^5.0", - "symfony/http-kernel": "^4.1|^5.0", - "symfony/config": "^4.1|^5.0" - }, - "require-dev": { - "phpunit/phpunit": "^6.5", - "defuse/php-encryption": "^2.1" - }, - "suggest": { - "defuse/php-encryption": "Alternative for halite for use with older php-versions", - "ext-sodium": "Required to use halite encryption library." - }, - "autoload": { - "psr-4": { "Ambta\\DoctrineEncryptBundle\\": "src/" } - }, - "autoload-dev": { - "psr-4": { "Ambta\\DoctrineEncryptBundle\\Tests\\": "Tests/" } - }, - "authors": [ - { - "name": "GiveMeAllYourCats", - "email": "specamps@gmail.com" - } - ] + "name": "absolute-quantum/doctrine-encrypt-bundle", + "type": "library", + "keywords": ["doctrine", "symfony", "halite", "defuse", "encrypt", "decrypt"], + "license": "MIT", + "description": "Encrypted symfony entity's by verified and standardized libraries", + "require": { + "php": "^8.0", + "paragonie/halite": "^4.6", + "doctrine/orm": "^2.5", + "symfony/property-access": "^4.1|^5.0|^6.0", + "symfony/dependency-injection": "^4.1|^5.0|^6.0", + "symfony/yaml": "^4.1|^5.0|^6.0", + "symfony/http-kernel": "^4.1|^5.0|^6.0", + "symfony/config": "^4.1|^5.0|^6.0" + }, + "require-dev": { + "phpunit/phpunit": "^8.0|^9.0", + "defuse/php-encryption": "^2.1" + }, + "suggest": { + "defuse/php-encryption": "Alternative for halite for use with older php-versions", + "ext-sodium": "Required to use halite encryption library." + }, + "autoload": { + "psr-4": { + "Ambta\\DoctrineEncryptBundle\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "Ambta\\DoctrineEncryptBundle\\Tests\\": "Tests/" + } + }, + "config": { + "allow-plugins": { + "composer/package-versions-deprecated": true + } + } } diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 3d09a6ff..80037be4 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -4,8 +4,7 @@ colors="true" convertErrorsToExceptions="true" convertNoticesToExceptions="true" - convertWarningsToExceptions="true" - syntaxCheck="true"> + convertWarningsToExceptions="true"> @@ -16,15 +15,15 @@ - - + + ./Command ./Configuration ./DependencyInjection ./Encryptors ./Subscribers - - + + diff --git a/src/AmbtaDoctrineEncryptBundle.php b/src/AmbtaDoctrineEncryptBundle.php index 9b033601..11b24614 100644 --- a/src/AmbtaDoctrineEncryptBundle.php +++ b/src/AmbtaDoctrineEncryptBundle.php @@ -2,12 +2,15 @@ namespace Ambta\DoctrineEncryptBundle; +use JetBrains\PhpStorm\Pure; +use Symfony\Component\DependencyInjection\Extension\ExtensionInterface; use Symfony\Component\HttpKernel\Bundle\Bundle; use Ambta\DoctrineEncryptBundle\DependencyInjection\DoctrineEncryptExtension; class AmbtaDoctrineEncryptBundle extends Bundle { - public function getContainerExtension() + #[Pure] + public function getContainerExtension(): ?ExtensionInterface { return new DoctrineEncryptExtension(); } diff --git a/src/Command/AbstractCommand.php b/src/Command/AbstractCommand.php index b6953667..489875a3 100644 --- a/src/Command/AbstractCommand.php +++ b/src/Command/AbstractCommand.php @@ -18,17 +18,17 @@ abstract class AbstractCommand extends Command /** * @var EntityManagerInterface */ - protected $entityManager; + protected EntityManagerInterface|EntityManager $entityManager; /** * @var DoctrineEncryptSubscriber */ - protected $subscriber; + protected DoctrineEncryptSubscriber $subscriber; /** * @var Reader */ - protected $annotationReader; + protected Reader $annotationReader; /** * AbstractCommand constructor. @@ -52,14 +52,13 @@ public function __construct( * Get an result iterator over the whole table of an entity. * * @param string $entityName - * - * @return \Doctrine\ORM\Internal\Hydration\IterableResult + * @return iterable|array */ - protected function getEntityIterator($entityName) + protected function getEntityIterator(string $entityName): iterable { $query = $this->entityManager->createQuery(sprintf('SELECT o FROM %s o', $entityName)); - return $query->iterate(); + return $query->toIterable(); } /** @@ -69,7 +68,7 @@ protected function getEntityIterator($entityName) * * @return int */ - protected function getTableCount($entityName) + protected function getTableCount(string $entityName): int { $query = $this->entityManager->createQuery(sprintf('SELECT COUNT(o) FROM %s o', $entityName)); @@ -82,7 +81,7 @@ protected function getTableCount($entityName) * * @return array */ - protected function getEncryptionableEntityMetaData() + protected function getEncryptionableEntityMetaData(): array { $validMetaData = []; $metaDataArray = $this->entityManager->getMetadataFactory()->getAllMetadata(); @@ -109,7 +108,7 @@ protected function getEncryptionableEntityMetaData() * * @return array */ - protected function getEncryptionableProperties($entityMetaData) + protected function getEncryptionableProperties($entityMetaData): array { //Create reflectionClass for each meta data object $reflectionClass = new \ReflectionClass($entityMetaData->name); diff --git a/src/Command/DoctrineDecryptDatabaseCommand.php b/src/Command/DoctrineDecryptDatabaseCommand.php index c677111d..52fd45dd 100644 --- a/src/Command/DoctrineDecryptDatabaseCommand.php +++ b/src/Command/DoctrineDecryptDatabaseCommand.php @@ -33,7 +33,7 @@ protected function configure() /** * {@inheritdoc} */ - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { // Get entity manager, question helper, subscriber service and annotation reader $question = $this->getHelper('question'); @@ -146,5 +146,6 @@ protected function execute(InputInterface $input, OutputInterface $output) } $output->writeln('' . PHP_EOL . 'Decryption finished values found: ' . $valueCounter . ', decrypted: ' . $this->subscriber->decryptCounter . '.' . PHP_EOL . 'All values are now decrypted.'); + return 1; } } diff --git a/src/Command/DoctrineEncryptDatabaseCommand.php b/src/Command/DoctrineEncryptDatabaseCommand.php index f5d371c8..7bb03502 100644 --- a/src/Command/DoctrineEncryptDatabaseCommand.php +++ b/src/Command/DoctrineEncryptDatabaseCommand.php @@ -32,7 +32,7 @@ protected function configure() /** * {@inheritdoc} */ - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { // Get entity manager, question helper, subscriber service and annotation reader $question = $this->getHelper('question'); @@ -84,7 +84,7 @@ protected function execute(InputInterface $input, OutputInterface $output) $output->writeln(sprintf('Processing %s', $metaData->name)); $progressBar = new ProgressBar($output, $totalCount); foreach ($iterator as $row) { - $this->subscriber->processFields($row[0]); + $this->subscriber->processFields((is_array($row) ? $row[0] : $row)); if (($i % $batchSize) === 0) { $this->entityManager->flush(); @@ -101,6 +101,7 @@ protected function execute(InputInterface $input, OutputInterface $output) // Say it is finished $output->writeln('Encryption finished. Values encrypted: ' . $this->subscriber->encryptCounter . ' values.' . PHP_EOL . 'All values are now encrypted.'); + return 1; } diff --git a/src/Command/DoctrineEncryptStatusCommand.php b/src/Command/DoctrineEncryptStatusCommand.php index 37b826ac..4338c5bf 100644 --- a/src/Command/DoctrineEncryptStatusCommand.php +++ b/src/Command/DoctrineEncryptStatusCommand.php @@ -27,7 +27,7 @@ protected function configure() /** * {@inheritdoc} */ - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { $metaDataArray = $this->entityManager->getMetadataFactory()->getAllMetadata(); @@ -53,5 +53,6 @@ protected function execute(InputInterface $input, OutputInterface $output) $output->writeln(''); $output->writeln(sprintf('%d entities found which are containing %d encrypted properties.', count($metaDataArray), $totalCount)); + return 1; } } diff --git a/src/Configuration/Annotation.php b/src/Configuration/Annotation.php new file mode 100644 index 00000000..0896617d --- /dev/null +++ b/src/Configuration/Annotation.php @@ -0,0 +1,12 @@ + * @Annotation + * @Target("PROPERTY") */ -class Encrypted +#[Attribute(Attribute::TARGET_PROPERTY)] +class Encrypted implements Annotation { // Placeholder } diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index db9708b0..bf46810b 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -18,7 +18,7 @@ class Configuration implements ConfigurationInterface /** * {@inheritDoc} */ - public function getConfigTreeBuilder() + public function getConfigTreeBuilder(): TreeBuilder { // Create tree builder $treeBuilder = new TreeBuilder('ambta_doctrine_encrypt'); diff --git a/src/DependencyInjection/DoctrineEncryptExtension.php b/src/DependencyInjection/DoctrineEncryptExtension.php index ebe6df46..4914ea40 100644 --- a/src/DependencyInjection/DoctrineEncryptExtension.php +++ b/src/DependencyInjection/DoctrineEncryptExtension.php @@ -51,7 +51,7 @@ public function load(array $configs, ContainerBuilder $container) * * @return string */ - public function getAlias() + public function getAlias(): string { return 'ambta_doctrine_encrypt'; } diff --git a/src/Encryptors/DefuseEncryptor.php b/src/Encryptors/DefuseEncryptor.php index c7004a51..0079aba8 100644 --- a/src/Encryptors/DefuseEncryptor.php +++ b/src/Encryptors/DefuseEncryptor.php @@ -12,9 +12,9 @@ class DefuseEncryptor implements EncryptorInterface { - private $fs; - private $encryptionKey; - private $keyFile; + private Filesystem $fs; + private ?string $encryptionKey = null; + private string $keyFile; /** * {@inheritdoc} @@ -28,7 +28,7 @@ public function __construct(string $keyFile) /** * {@inheritdoc} */ - public function encrypt($data) + public function encrypt(string $data): string { return \Defuse\Crypto\Crypto::encryptWithPassword($data, $this->getKey()); } @@ -36,12 +36,12 @@ public function encrypt($data) /** * {@inheritdoc} */ - public function decrypt($data) + public function decrypt(string $data): string { return \Defuse\Crypto\Crypto::decryptWithPassword($data, $this->getKey()); } - private function getKey() + private function getKey(): string { if ($this->encryptionKey === null) { if ($this->fs->exists($this->keyFile)) { diff --git a/src/Encryptors/EncryptorInterface.php b/src/Encryptors/EncryptorInterface.php index 84b9dca7..fdb1847c 100644 --- a/src/Encryptors/EncryptorInterface.php +++ b/src/Encryptors/EncryptorInterface.php @@ -13,11 +13,11 @@ interface EncryptorInterface * @param string $data Plain text to encrypt * @return string Encrypted text */ - public function encrypt($data); + public function encrypt(string $data): string; /** * @param string $data Encrypted text * @return string Plain text */ - public function decrypt($data); + public function decrypt(string $data): string; } diff --git a/src/Encryptors/HaliteEncryptor.php b/src/Encryptors/HaliteEncryptor.php index 3ef64cf7..07653093 100644 --- a/src/Encryptors/HaliteEncryptor.php +++ b/src/Encryptors/HaliteEncryptor.php @@ -3,6 +3,7 @@ namespace Ambta\DoctrineEncryptBundle\Encryptors; use ParagonIE\Halite\Alerts\CannotPerformOperation; +use ParagonIE\Halite\Symmetric\EncryptionKey; use ParagonIE\HiddenString\HiddenString; use ParagonIE\Halite\KeyFactory; use ParagonIE\Halite\Symmetric\Crypto; @@ -15,8 +16,8 @@ class HaliteEncryptor implements EncryptorInterface { - private $encryptionKey; - private $keyFile; + private ?EncryptionKey $encryptionKey = null; + private string $keyFile; /** * {@inheritdoc} @@ -29,7 +30,7 @@ public function __construct(string $keyFile) /** * {@inheritdoc} */ - public function encrypt($data) + public function encrypt(string $data): string { return Crypto::encrypt(new HiddenString($data), $this->getKey()); } @@ -37,7 +38,7 @@ public function encrypt($data) /** * {@inheritdoc} */ - public function decrypt($data) + public function decrypt(string $data): string { $data = Crypto::decrypt($data, $this->getKey()); @@ -49,7 +50,7 @@ public function decrypt($data) return $data; } - private function getKey() + private function getKey(): EncryptionKey { if ($this->encryptionKey === null) { try { diff --git a/src/Mapping/AttributeAnnotationReader.php b/src/Mapping/AttributeAnnotationReader.php new file mode 100644 index 00000000..86309e45 --- /dev/null +++ b/src/Mapping/AttributeAnnotationReader.php @@ -0,0 +1,111 @@ + + * + * @internal + */ +final class AttributeAnnotationReader implements Reader +{ + /** + * @var Reader + */ + private Reader $annotationReader; + + /** + * @var AttributeReader + */ + private AttributeReader $attributeReader; + + public function __construct(AttributeReader $attributeReader, Reader $annotationReader) + { + $this->attributeReader = $attributeReader; + $this->annotationReader = $annotationReader; + } + + /** + * @return Annotation[] + */ + public function getClassAnnotations(ReflectionClass $class): array + { + $annotations = $this->attributeReader->getClassAnnotations($class); + + if ([] !== $annotations) { + return $annotations; + } + + return $this->annotationReader->getClassAnnotations($class); + } + + /** + * @param class-string $annotationName the name of the annotation + * + * @return T|null the Annotation or NULL, if the requested annotation does not exist + * + * @template T + */ + public function getClassAnnotation(ReflectionClass $class, $annotationName) + { + $annotation = $this->attributeReader->getClassAnnotation($class, $annotationName); + + if (null !== $annotation) { + return $annotation; + } + + return $this->annotationReader->getClassAnnotation($class, $annotationName); + } + + /** + * @return Annotation[] + */ + public function getPropertyAnnotations(\ReflectionProperty $property): array + { + $propertyAnnotations = $this->attributeReader->getPropertyAnnotations($property); + + if ([] !== $propertyAnnotations) { + return $propertyAnnotations; + } + + return $this->annotationReader->getPropertyAnnotations($property); + } + + /** + * @param class-string $annotationName the name of the annotation + * + * @return T|null the Annotation or NULL, if the requested annotation does not exist + * + * @template T + */ + public function getPropertyAnnotation(\ReflectionProperty $property, $annotationName) + { + $annotation = $this->attributeReader->getPropertyAnnotation($property, $annotationName); + + if (null !== $annotation) { + return $annotation; + } + + return $this->annotationReader->getPropertyAnnotation($property, $annotationName); + } + + public function getMethodAnnotations(ReflectionMethod $method): array + { + throw new \BadMethodCallException('Not implemented'); + } + + /** + * @param ReflectionMethod $method + * @param $annotationName + * @return mixed + */ + public function getMethodAnnotation(ReflectionMethod $method, $annotationName): mixed + { + throw new \BadMethodCallException('Not implemented'); + } +} diff --git a/src/Mapping/AttributeReader.php b/src/Mapping/AttributeReader.php new file mode 100644 index 00000000..d8d36470 --- /dev/null +++ b/src/Mapping/AttributeReader.php @@ -0,0 +1,102 @@ + + * + * @internal + */ +final class AttributeReader +{ + /** @var array */ + private array $isRepeatableAttribute = []; + + /** + * @param ReflectionClass $class + * @return Annotation[] + */ + public function getClassAnnotations(ReflectionClass $class): array + { + return $this->convertToAttributeInstances($class->getAttributes()); + } + + /** + * @phpstan-param class-string $annotationName + * + * @return Annotation|Annotation[]|null + */ + public function getClassAnnotation(ReflectionClass $class, string $annotationName): array|Annotation|null + { + return $this->getClassAnnotations($class)[$annotationName] ?? null; + } + + /** + * @param \ReflectionProperty $property + * @return Annotation[] + */ + public function getPropertyAnnotations(\ReflectionProperty $property): array + { + return $this->convertToAttributeInstances($property->getAttributes()); + } + + /** + * @phpstan-param class-string $annotationName + * + * @return Annotation|Annotation[]|null + */ + public function getPropertyAnnotation(\ReflectionProperty $property, string $annotationName): array|Annotation|null + { + return $this->getPropertyAnnotations($property)[$annotationName] ?? null; + } + + /** + * @param array<\ReflectionAttribute> $attributes + * + * @return array + */ + private function convertToAttributeInstances(array $attributes): array + { + $instances = []; + + foreach ($attributes as $attribute) { + $attributeName = $attribute->getName(); + assert(is_string($attributeName)); + // Make sure we only get Gedmo Annotations + if (!is_subclass_of($attributeName, Annotation::class)) { + continue; + } + + $instance = $attribute->newInstance(); + assert($instance instanceof Annotation); + + if ($this->isRepeatable($attributeName)) { + if (!isset($instances[$attributeName])) { + $instances[$attributeName] = []; + } + + $instances[$attributeName][] = $instance; + } else { + $instances[$attributeName] = $instance; + } + } + + return $instances; + } + + private function isRepeatable(string $attributeClassName): bool + { + if (isset($this->isRepeatableAttribute[$attributeClassName])) { + return $this->isRepeatableAttribute[$attributeClassName]; + } + + $reflectionClass = new ReflectionClass($attributeClassName); + $attribute = $reflectionClass->getAttributes()[0]->newInstance(); + + return $this->isRepeatableAttribute[$attributeClassName] = ($attribute->flags & Attribute::IS_REPEATABLE) > 0; + } +} diff --git a/src/Resources/config/services.yml b/src/Resources/config/services.yml index b3a54e7c..a9356243 100644 --- a/src/Resources/config/services.yml +++ b/src/Resources/config/services.yml @@ -1,7 +1,14 @@ services: + ambta_doctrine_attribute_reader: + class: Ambta\DoctrineEncryptBundle\Mapping\AttributeReader + + ambta_doctrine_annotation_reader: + class: Ambta\DoctrineEncryptBundle\Mapping\AttributeAnnotationReader + arguments: ["@ambta_doctrine_attribute_reader", "@annotations.reader"] + ambta_doctrine_encrypt.orm_subscriber: class: Ambta\DoctrineEncryptBundle\Subscribers\DoctrineEncryptSubscriber - arguments: ["@annotation_reader", "@ambta_doctrine_encrypt.encryptor"] + arguments: ["@ambta_doctrine_annotation_reader", "@ambta_doctrine_encrypt.encryptor"] tags: - { name: doctrine.event_subscriber } @@ -18,7 +25,7 @@ services: tags: ['console.command'] arguments: - "@doctrine.orm.entity_manager" - - "@annotation_reader" + - "@ambta_doctrine_annotation_reader" - "@ambta_doctrine_encrypt.subscriber" ambta_doctrine_encrypt.command.encrypt.database: @@ -26,7 +33,7 @@ services: tags: ['console.command'] arguments: - "@doctrine.orm.entity_manager" - - "@annotation_reader" + - "@ambta_doctrine_annotation_reader" - "@ambta_doctrine_encrypt.subscriber" ambta_doctrine_encrypt.command.encrypt.status: @@ -34,5 +41,5 @@ services: tags: ['console.command'] arguments: - "@doctrine.orm.entity_manager" - - "@annotation_reader" - - "@ambta_doctrine_encrypt.subscriber" + - "@ambta_doctrine_annotation_reader" + - "@ambta_doctrine_encrypt.subscriber" \ No newline at end of file diff --git a/src/Resources/doc/installation.md b/src/Resources/doc/installation.md index 0a80a193..d6703712 100644 --- a/src/Resources/doc/installation.md +++ b/src/Resources/doc/installation.md @@ -6,7 +6,7 @@ ### Requirements - - PHP >=7.0 + - PHP >=8.0 - Comes with package: [paragonie/sodium_compat](https://github.com/paragonie/sodium_compat) ^1.5 - Comes with package: [Halite](https://github.com/paragonie/halite) ^3.0 - [doctrine/orm](https://packagist.org/packages/doctrine/orm) >= 2.0 diff --git a/src/Subscribers/DoctrineEncryptSubscriber.php b/src/Subscribers/DoctrineEncryptSubscriber.php index 85207851..06f05df4 100644 --- a/src/Subscribers/DoctrineEncryptSubscriber.php +++ b/src/Subscribers/DoctrineEncryptSubscriber.php @@ -38,42 +38,42 @@ class DoctrineEncryptSubscriber implements EventSubscriber /** * Encryptor - * @var EncryptorInterface + * @var EncryptorInterface|null */ - private $encryptor; + private ?EncryptorInterface $encryptor; /** * Annotation reader - * @var \Doctrine\Common\Annotations\Reader + * @var Reader */ - private $annReader; + private Reader $annReader; /** * Used for restoring the encryptor after changing it - * @var string + * @var EncryptorInterface|string */ - private $restoreEncryptor; + private EncryptorInterface|string $restoreEncryptor; /** * Count amount of decrypted values in this service * @var integer */ - public $decryptCounter = 0; + public int $decryptCounter = 0; /** * Count amount of encrypted values in this service * @var integer */ - public $encryptCounter = 0; + public int $encryptCounter = 0; /** @var array */ - private $cachedDecryptions = []; + private array $cachedDecryptions = []; /** * Initialization of subscriber * * @param Reader $annReader - * @param EncryptorInterface|NULL $encryptor (Optional) An EncryptorInterface. + * @param EncryptorInterface $encryptor (Optional) An EncryptorInterface. */ public function __construct(Reader $annReader, EncryptorInterface $encryptor) { @@ -85,9 +85,9 @@ public function __construct(Reader $annReader, EncryptorInterface $encryptor) /** * Change the encryptor * - * @param EncryptorInterface $encryptor + * @param EncryptorInterface|null $encryptor */ - public function setEncryptor(EncryptorInterface $encryptor = null) + public function setEncryptor(?EncryptorInterface $encryptor = null) { $this->encryptor = $encryptor; } @@ -95,9 +95,9 @@ public function setEncryptor(EncryptorInterface $encryptor = null) /** * Get the current encryptor * - * @return EncryptorInterface returns the encryptor class or null + * @return EncryptorInterface|null returns the encryptor class or null */ - public function getEncryptor() + public function getEncryptor(): ?EncryptorInterface { return $this->encryptor; } @@ -208,7 +208,7 @@ public function postFlush(PostFlushEventArgs $postFlushEventArgs) * * @return array Return all events which this subscriber is listening */ - public function getSubscribedEvents() + public function getSubscribedEvents(): array { return array( Events::postUpdate, @@ -226,11 +226,11 @@ public function getSubscribedEvents() * @param Object $entity doctrine entity * @param Boolean $isEncryptOperation If true - encrypt, false - decrypt entity * - * @throws \RuntimeException - * * @return object|null + *@throws \RuntimeException + * */ - public function processFields($entity, $isEncryptOperation = true) + public function processFields(object $entity, bool $isEncryptOperation = true): ?object { if (!empty($this->encryptor)) { // Check which operation to be used @@ -304,7 +304,7 @@ private function handleEmbeddedAnnotation($entity, ReflectionProperty $embeddedP * * @return array */ - private function getClassProperties($className) + private function getClassProperties(string $className): array { $reflectionClass = new ReflectionClass($className); $properties = $reflectionClass->getProperties();