diff --git a/Tests/Functional/DoctrineEncryptSubscriber/AbstractDoctrineEncryptSubscriberTestCase.php b/Tests/Functional/DoctrineEncryptSubscriber/AbstractDoctrineEncryptSubscriberTestCase.php index 7b113de6..ff2f9074 100644 --- a/Tests/Functional/DoctrineEncryptSubscriber/AbstractDoctrineEncryptSubscriberTestCase.php +++ b/Tests/Functional/DoctrineEncryptSubscriber/AbstractDoctrineEncryptSubscriberTestCase.php @@ -5,6 +5,8 @@ use Ambta\DoctrineEncryptBundle\Tests\Functional\AbstractFunctionalTestCase; use Ambta\DoctrineEncryptBundle\Tests\Functional\fixtures\Entity\CascadeTarget; +use Ambta\DoctrineEncryptBundle\Tests\Functional\fixtures\Entity\ClassTableInheritanceBase; +use Ambta\DoctrineEncryptBundle\Tests\Functional\fixtures\Entity\ClassTableInheritanceChild; use Ambta\DoctrineEncryptBundle\Tests\Functional\fixtures\Entity\Owner; use Doctrine\DBAL\Logging\DebugStack; @@ -46,10 +48,10 @@ public function testEncryptionHappensOnOnlyAnnotatedFields() public function testEncryptionCascades() { - $secret = "It's a secret"; - $notSecret = "You're all welcome to know this."; - $em = $this->entityManager; - $owner = new Owner(); + $secret = "It's a secret"; + $notSecret = "You're all welcome to know this."; + $em = $this->entityManager; + $owner = new Owner(); $em->persist($owner); // persist cascades $em->flush(); @@ -82,6 +84,60 @@ public function testEncryptionCascades() $this->assertEquals($secret, $decrypted); } + public function testEncryptionClassTableInheritance() + { + $secretBase = "It's a secret. On the base class."; + $notSecretBase = "You're all welcome to know this. On the base class."; + $secretChild = "It's a secret. On the child class."; + $notSecretChild = "You're all welcome to know this. On the child class."; + $em = $this->entityManager; + $child = new ClassTableInheritanceChild(); + $child->setSecretBase($secretBase); + $child->setNotSecretBase($notSecretBase); + $child->setSecretChild($secretChild); + $child->setNotSecretChild($notSecretChild); + $em->persist($child); + $em->flush(); + $em->clear(); + unset($child); + + $connection = $em->getConnection(); + $stmtBase = $connection->prepare('SELECT * from classTableInheritanceBase WHERE id = ?'); + $stmtChild = $connection->prepare('SELECT * from classTableInheritanceChild WHERE id = ?'); + $childs = $em->getRepository(ClassTableInheritanceBase::class)->findAll(); + self::assertCount(1, $childs); + /** @var ClassTableInheritanceChild $child */ + $child = $childs[0]; + self::assertEquals($secretBase, $child->getSecretBase()); + self::assertEquals($notSecretBase, $child->getNotSecretBase()); + self::assertEquals($secretChild, $child->getSecretChild()); + self::assertEquals($notSecretChild, $child->getNotSecretChild()); + + // Now check that the fields are encrypted in the database. First the base table. + $stmtBase->bindValue(1, $child->getId()); + $stmtBase->execute(); + $results = $stmtBase->fetchAll(); + self::assertCount(1, $results); + $result = $results[0]; + self::assertEquals($notSecretBase, $result['notSecretBase']); + self::assertNotEquals($secretBase, $result['secretBase']); + self::assertStringEndsWith('', $result['secretBase']); + $decrypted = $this->encryptor->decrypt(str_replace('', '', $result['secretBase'])); + self::assertEquals($secretBase, $decrypted); + + // and then the child table. + $stmtChild->bindValue(1, $child->getId()); + $stmtChild->execute(); + $results = $stmtChild->fetchAll(); + self::assertCount(1, $results); + $result = $results[0]; + self::assertEquals($notSecretChild, $result['notSecretChild']); + self::assertNotEquals($secretChild, $result['secretChild']); + self::assertStringEndsWith('', $result['secretChild']); + $decrypted = $this->encryptor->decrypt(str_replace('', '', $result['secretChild'])); + self::assertEquals($secretChild, $decrypted); + } + /** * @throws \Doctrine\DBAL\DBALException @@ -92,11 +148,11 @@ public function testEncryptionDoesNotHappenWhenThereIsNoChange() $secret = "It's a secret"; $notSecret = "You're all welcome to know this."; $em = $this->entityManager; - $owner1 = new Owner(); + $owner1 = new Owner(); $owner1->setSecret($secret); $owner1->setNotSecret($notSecret); $em->persist($owner1); - $owner2 = new Owner(); + $owner2 = new Owner(); $owner2->setSecret($secret); $owner2->setNotSecret($notSecret); $em->persist($owner2); @@ -114,7 +170,7 @@ public function testEncryptionDoesNotHappenWhenThereIsNoChange() $stmt->execute(); $results = $stmt->fetchAll(); $this->assertCount(1, $results); - $result = $results[0]; + $result = $results[0]; $originalEncryption = $result['secret']; $this->assertStringEndsWith('', $originalEncryption); // is encrypted @@ -133,7 +189,7 @@ public function testEncryptionDoesNotHappenWhenThereIsNoChange() // No encryption should have happened because we didn't change anything. $this->assertEquals($beforeFlush, $afterFlush); // No queries happened because we didn't change anything. - $this->assertCount(0, $stack->queries, "Unexpected queries:\n".var_export($stack->queries, true)); + $this->assertCount(0, $stack->queries, "Unexpected queries:\n" . var_export($stack->queries, true)); // flush again $beforeFlush = $this->subscriber->encryptCounter; @@ -142,17 +198,98 @@ public function testEncryptionDoesNotHappenWhenThereIsNoChange() // No encryption should have happened because we didn't change anything. $this->assertEquals($beforeFlush, $afterFlush); // No queries happened because we didn't change anything. - $this->assertCount(0, $stack->queries, "Unexpected queries:\n".var_export($stack->queries, true)); + $this->assertCount(0, $stack->queries, "Unexpected queries:\n" . var_export($stack->queries, true)); $stmt->bindValue(1, $owner1Id); $stmt->execute(); $results = $stmt->fetchAll(); $this->assertCount(1, $results); - $result = $results[0]; + $result = $results[0]; $shouldBeTheSameAsBefore = $result['secret']; $this->assertStringEndsWith('', $shouldBeTheSameAsBefore); // is encrypted $this->assertEquals($originalEncryption, $shouldBeTheSameAsBefore); + } + + /** + * @throws \Doctrine\DBAL\DBALException + * @throws \Doctrine\ORM\OptimisticLockException + */ + public function testEncryptionDoesNotHappenWhenThereIsNoChangeClassInheritance() + { + $secretBase = "It's a secret. On the base class."; + $notSecretBase = "You're all welcome to know this. On the base class."; + $secretChild = "It's a secret. On the child class."; + $notSecretChild = "You're all welcome to know this. On the child class."; + $em = $this->entityManager; + $child = new ClassTableInheritanceChild(); + $child->setSecretBase($secretBase); + $child->setNotSecretBase($notSecretBase); + $child->setSecretChild($secretChild); + $child->setNotSecretChild($notSecretChild); + $em->persist($child); + $em->flush(); + $em->clear(); + $childId = $child->getId(); + unset($child); + // test that it was encrypted correctly + $connection = $em->getConnection(); + $stmtBase = $connection->prepare('SELECT * from classTableInheritanceBase WHERE id = ?'); + $stmtBase->bindValue(1, $childId); + $stmtBase->execute(); + $result = $stmtBase->fetch(); + $originalEncryptionBase = $result['secretBase']; + self::assertStringEndsWith('', $originalEncryptionBase); // is encrypted + + // do the same for the child. + $connection = $em->getConnection(); + $stmtChild = $connection->prepare('SELECT * from classTableInheritanceChild WHERE id = ?'); + $stmtChild->bindValue(1, $childId); + $stmtChild->execute(); + $result = $stmtChild->fetch(); + $originalEncryptionChild = $result['secretChild']; + self::assertStringEndsWith('', $originalEncryptionChild); // is encrypted + + $childs = $em->getRepository(ClassTableInheritanceChild::class)->findAll(); + $child = $childs[0]; + self::assertEquals($secretBase, $child->getSecretBase()); + self::assertEquals($notSecretBase, $child->getNotSecretBase()); + self::assertEquals($secretChild, $child->getSecretChild()); + self::assertEquals($notSecretChild, $child->getNotSecretChild()); + + $stack = new DebugStack(); + $connection->getConfiguration()->setSQLLogger($stack); + self::assertCount(0, $stack->queries); + $beforeFlush = $this->subscriber->encryptCounter; + $em->flush(); + $afterFlush = $this->subscriber->encryptCounter; + // No encryption should have happened because we didn't change anything. + self::assertEquals($beforeFlush, $afterFlush); + // No queries happened because we didn't change anything. + self::assertCount(0, $stack->queries, "Unexpected queries:\n" . var_export($stack->queries, true)); + + // flush again + $beforeFlush = $this->subscriber->encryptCounter; + $em->flush(); + $afterFlush = $this->subscriber->encryptCounter; + // No encryption should have happened because we didn't change anything. + self::assertEquals($beforeFlush, $afterFlush); + // No queries happened because we didn't change anything. + self::assertCount(0, $stack->queries, "Unexpected queries:\n" . var_export($stack->queries, true)); + + $stmtBase->bindValue(1, $childId); + $stmtBase->execute(); + $result = $stmtBase->fetch(); + $shouldBeTheSameAsBeforeBase = $result['secretBase']; + self::assertStringEndsWith('', $shouldBeTheSameAsBeforeBase); // is encrypted + self::assertEquals($originalEncryptionBase, $shouldBeTheSameAsBeforeBase); + + $stmtChild->bindValue(1, $childId); + $stmtChild->execute(); + $result = $stmtChild->fetch(); + $shouldBeTheSameAsBeforeChild = $result['secretChild']; + self::assertStringEndsWith('', $shouldBeTheSameAsBeforeChild); // is encrypted + self::assertEquals($originalEncryptionChild, $shouldBeTheSameAsBeforeChild); } public function testEncryptionDoesHappenWhenASecretIsChanged() @@ -176,7 +313,7 @@ public function testEncryptionDoesHappenWhenASecretIsChanged() $stmt->execute(); $results = $stmt->fetchAll(); $this->assertCount(1, $results); - $result = $results[0]; + $result = $results[0]; $originalEncryption = $result['secret']; $this->assertStringEndsWith('', $originalEncryption); // is encrypted @@ -193,7 +330,7 @@ public function testEncryptionDoesHappenWhenASecretIsChanged() $stmt->execute(); $results = $stmt->fetchAll(); $this->assertCount(1, $results); - $result = $results[0]; + $result = $results[0]; $shouldBeDifferentFromBefore = $result['secret']; $this->assertStringEndsWith('', $shouldBeDifferentFromBefore); // is encrypted $this->assertNotEquals($originalEncryption, $shouldBeDifferentFromBefore); diff --git a/Tests/Functional/fixtures/Entity/ClassTableInheritanceBase.php b/Tests/Functional/fixtures/Entity/ClassTableInheritanceBase.php new file mode 100644 index 00000000..799a2e4d --- /dev/null +++ b/Tests/Functional/fixtures/Entity/ClassTableInheritanceBase.php @@ -0,0 +1,66 @@ +id; + } + + public function getSecretBase() + { + return $this->secretBase; + } + + public function setSecretBase($secretBase) + { + $this->secretBase = $secretBase; + } + + /** + * @return mixed + */ + public function getNotSecretBase() + { + return $this->notSecretBase; + } + + /** + * @param mixed $notSecretBase + */ + public function setNotSecretBase($notSecretBase) + { + $this->notSecretBase = $notSecretBase; + } + +} \ No newline at end of file diff --git a/Tests/Functional/fixtures/Entity/ClassTableInheritanceChild.php b/Tests/Functional/fixtures/Entity/ClassTableInheritanceChild.php new file mode 100644 index 00000000..d8ab1fc5 --- /dev/null +++ b/Tests/Functional/fixtures/Entity/ClassTableInheritanceChild.php @@ -0,0 +1,49 @@ +secretChild; + } + + public function setSecretChild($secretChild) + { + $this->secretChild = $secretChild; + } + + /** + * @return mixed + */ + public function getNotSecretChild() + { + return $this->notSecretChild; + } + + /** + * @param mixed $notSecretChild + */ + public function setNotSecretChild($notSecretChild) + { + $this->notSecretChild = $notSecretChild; + } + +} \ No newline at end of file diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 3d09a6ff..a0f718be 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -12,7 +12,7 @@ ./Tests/Unit - ./Tests/Functional + ./Tests/Functional diff --git a/src/Subscribers/DoctrineEncryptSubscriber.php b/src/Subscribers/DoctrineEncryptSubscriber.php index 85207851..c52fa57d 100644 --- a/src/Subscribers/DoctrineEncryptSubscriber.php +++ b/src/Subscribers/DoctrineEncryptSubscriber.php @@ -158,11 +158,9 @@ public function postLoad(LifecycleEventArgs $args) public function preFlush(PreFlushEventArgs $preFlushEventArgs) { $unitOfWOrk = $preFlushEventArgs->getEntityManager()->getUnitOfWork(); - foreach ($unitOfWOrk->getIdentityMap() as $entityName => $entityArray) { - if (isset($this->cachedDecryptions[$entityName])) { - foreach ($entityArray as $entityId => $instance) { - $this->processFields($instance); - } + foreach ($unitOfWOrk->getIdentityMap() as $entityArray) { + foreach ($entityArray as $entityId => $instance) { + $this->processFields($instance); } } $this->cachedDecryptions = []; @@ -260,13 +258,13 @@ public function processFields($entity, $isEncryptOperation = true) $this->decryptCounter++; $currentPropValue = $this->encryptor->decrypt(substr($value, 0, -5)); $pac->setValue($entity, $refProperty->getName(), $currentPropValue); - $this->cachedDecryptions[get_class($entity)][spl_object_id($entity)][$refProperty->getName()][$currentPropValue] = $value; + $this->cachedDecryptions[$realClass][spl_object_id($entity)][$refProperty->getName()][$currentPropValue] = $value; } } } else { if (!is_null($value) and !empty($value)) { - if (isset($this->cachedDecryptions[get_class($entity)][spl_object_id($entity)][$refProperty->getName()][$value])) { - $pac->setValue($entity, $refProperty->getName(), $this->cachedDecryptions[get_class($entity)][spl_object_id($entity)][$refProperty->getName()][$value]); + if (isset($this->cachedDecryptions[$realClass][spl_object_id($entity)][$refProperty->getName()][$value])) { + $pac->setValue($entity, $refProperty->getName(), $this->cachedDecryptions[$realClass][spl_object_id($entity)][$refProperty->getName()][$value]); } elseif (substr($value, -strlen(self::ENCRYPTION_MARKER)) != self::ENCRYPTION_MARKER) { $this->encryptCounter++; $currentPropValue = $this->encryptor->encrypt($value).self::ENCRYPTION_MARKER;