diff --git a/README.md b/README.md index ca5d606..f3edbec 100644 --- a/README.md +++ b/README.md @@ -61,16 +61,35 @@ key which can prove to be tricky, especially if users already have accounts and If each user encrypts it's own data however, you can just use the automatic encryption key generation in your config.yml: -``` +```yaml sidus_encryption: encryption_key: auto_generate: false + throw_exceptions: true # Do not throw an exception when an error occurred when decrypting a value ``` This will tell the system to automatically generate a new encryption key if the user doesn't have any. In case of password recovery, the user won't be able to retrieve any of the encrypted data because he would be the only one able to decrypt the cipher key. +Disable Encryption +------------------ +The encryption can be temporary disabled on an encrypted type (in a command for example). This can be achieved using +the `Sidus\EncryptionBundle\Encryption\Enabler\EncryptionEnablerInterface` service: +```php + +// ... +$encryptionEnabler->disableEncryption(); + +// Starting from here, data will not be decrypted +$encryptionManager->decryptString($value); // The value will not be decrypted +$encryptionManager->encryptString($value); // The value will not be encrypted and store as is + +$encryptionEnabler->enableEncryption(); +// Now the encryption is re-enabled and works normally + +``` + Apache License -------------- @todo diff --git a/src/Doctrine/Connection/Factory/ConnectionFactory.php b/src/Doctrine/Connection/Factory/ConnectionFactory.php index 5676b9e..cf1da1f 100644 --- a/src/Doctrine/Connection/Factory/ConnectionFactory.php +++ b/src/Doctrine/Connection/Factory/ConnectionFactory.php @@ -11,6 +11,7 @@ use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Types\Type; use Sidus\EncryptionBundle\Doctrine\Type\EncryptTypeInterface; +use Sidus\EncryptionBundle\Encryption\Enabler\EncryptionEnablerInterface; use Sidus\EncryptionBundle\Registry\EncryptionManagerRegistry; /** @@ -22,13 +23,18 @@ class ConnectionFactory private bool $initialized = false; private EncryptionManagerRegistry $encryptionManager; - - public function __construct(array $typesConfig, EncryptionManagerRegistry $encryptionManager) - { + private EncryptionEnablerInterface $encryptionEnabler; + + public function __construct( + array $typesConfig, + EncryptionManagerRegistry $encryptionManager, + EncryptionEnablerInterface $encryptionEnabler + ) { $this->typesConfig = $typesConfig; $this->encryptionManager = $encryptionManager; + $this->encryptionEnabler = $encryptionEnabler; } - + /** * Create a connection by name. * @@ -36,6 +42,8 @@ public function __construct(array $typesConfig, EncryptionManagerRegistry $encry * @param string[]|Type[] $mappingTypes * * @return Connection + * + * @throws \Doctrine\DBAL\Exception */ public function createConnection(array $params, Configuration $config = null, EventManager $eventManager = null, array $mappingTypes = []): Connection { @@ -129,6 +137,7 @@ private function initializeTypes() : void if ($type instanceof EncryptTypeInterface) { $type->setEncryptionManager($this->encryptionManager->getDefaultEncryptionManager()); + $type->setEncryptionEnabler($this->encryptionEnabler); } } $this->initialized = true; diff --git a/src/Doctrine/Type/Behavior/EncryptType.php b/src/Doctrine/Type/Behavior/EncryptType.php new file mode 100644 index 0000000..0a267df --- /dev/null +++ b/src/Doctrine/Type/Behavior/EncryptType.php @@ -0,0 +1,44 @@ +encryptionEnabler->isEncryptionEnabled()) { + return $value; + } + + return $this->encryptionManager->decryptString(base64_decode($value)); + } + + public function convertToDatabaseValue($value, AbstractPlatform $platform) + { + // Allow to do not decrypt the value for the current request + if (!$this->encryptionEnabler->isEncryptionEnabled()) { + return $value; + } + $value = $this->encryptionManager->encryptString($value); + + return base64_encode($value); + } + + public function setEncryptionManager(EncryptionManagerInterface $encryptionManager): void + { + $this->encryptionManager = $encryptionManager; + } + + public function setEncryptionEnabler(EncryptionEnablerInterface $encryptionEnabler): void + { + $this->encryptionEnabler = $encryptionEnabler; + } +} diff --git a/src/Doctrine/Type/EncryptStringType.php b/src/Doctrine/Type/EncryptStringType.php index c9f99e9..6b8e162 100644 --- a/src/Doctrine/Type/EncryptStringType.php +++ b/src/Doctrine/Type/EncryptStringType.php @@ -2,34 +2,14 @@ namespace Sidus\EncryptionBundle\Doctrine\Type; -use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Types\StringType; -use Sidus\EncryptionBundle\Manager\EncryptionManagerInterface; +use Sidus\EncryptionBundle\Doctrine\Type\Behavior\EncryptType; class EncryptStringType extends StringType implements EncryptTypeInterface { - private EncryptionManagerInterface $encryptionManager; + use EncryptType; - public function convertToPHPValue($value, AbstractPlatform $platform) - { - $value = base64_decode($value); - - return $this->encryptionManager->decryptString($value); - } - - public function convertToDatabaseValue($value, AbstractPlatform $platform) - { - $value = $this->encryptionManager->encryptString($value); - - return base64_encode($value); - } - - public function setEncryptionManager(EncryptionManagerInterface $encryptionManager): void - { - $this->encryptionManager = $encryptionManager; - } - - public function getName() + public function getName(): string { return 'encrypt_string'; } diff --git a/src/Doctrine/Type/EncryptTextType.php b/src/Doctrine/Type/EncryptTextType.php index a299526..3dfde9b 100644 --- a/src/Doctrine/Type/EncryptTextType.php +++ b/src/Doctrine/Type/EncryptTextType.php @@ -2,32 +2,12 @@ namespace Sidus\EncryptionBundle\Doctrine\Type; -use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Types\TextType; -use Sidus\EncryptionBundle\Manager\EncryptionManagerInterface; +use Sidus\EncryptionBundle\Doctrine\Type\Behavior\EncryptType; class EncryptTextType extends TextType implements EncryptTypeInterface { - private EncryptionManagerInterface $encryptionManager; - - public function convertToPHPValue($value, AbstractPlatform $platform) - { - $value = base64_decode($value); - - return $this->encryptionManager->decryptString($value); - } - - public function convertToDatabaseValue($value, AbstractPlatform $platform) - { - $value = $this->encryptionManager->encryptString($value); - - return base64_encode($value); - } - - public function setEncryptionManager(EncryptionManagerInterface $encryptionManager): void - { - $this->encryptionManager = $encryptionManager; - } + use EncryptType; public function getName() { diff --git a/src/Doctrine/Type/EncryptTypeInterface.php b/src/Doctrine/Type/EncryptTypeInterface.php index 8a711ce..c80b0cd 100644 --- a/src/Doctrine/Type/EncryptTypeInterface.php +++ b/src/Doctrine/Type/EncryptTypeInterface.php @@ -2,9 +2,12 @@ namespace Sidus\EncryptionBundle\Doctrine\Type; +use Sidus\EncryptionBundle\Encryption\Enabler\EncryptionEnablerInterface; use Sidus\EncryptionBundle\Manager\EncryptionManagerInterface; interface EncryptTypeInterface { public function setEncryptionManager(EncryptionManagerInterface $encryptionManager): void; + + public function setEncryptionEnabler(EncryptionEnablerInterface $encryptionEnabler): void; } diff --git a/src/Encryption/Enabler/EncryptionEnabler.php b/src/Encryption/Enabler/EncryptionEnabler.php new file mode 100644 index 0000000..f9f4ce7 --- /dev/null +++ b/src/Encryption/Enabler/EncryptionEnabler.php @@ -0,0 +1,28 @@ +enabled = true; + } + + public function disableEncryption(): void + { + $this->enabled = false; + } + + public function isEncryptionEnabled(): bool + { + return $this->enabled; + } +} diff --git a/src/Encryption/Enabler/EncryptionEnablerInterface.php b/src/Encryption/Enabler/EncryptionEnablerInterface.php new file mode 100644 index 0000000..68cb3cb --- /dev/null +++ b/src/Encryption/Enabler/EncryptionEnablerInterface.php @@ -0,0 +1,23 @@ +createType(); + [$type, $encryptionManager, $encryptionEnabler] = $this->createType(); $encryptedString = '\X666'; $platform = $this->createMock(MySqlPlatform::class); + $encryptionEnabler + ->expects($this->once()) + ->method('isEncryptionEnabled') + ->willReturn(true) + ; + // The type SHOULD decrypt the encrypted string $encryptionManager ->expects($this->once()) @@ -28,12 +35,40 @@ public function testConvertToPHPValue(): void $this->assertEquals('my_decrypted_string', $value); } + public function testConvertToPHPValueWithEncryptionDisabled(): void + { + [$type, $encryptionManager, $encryptionEnabler] = $this->createType(); + $encryptedString = '\X666'; + $platform = $this->createMock(MySqlPlatform::class); + + $encryptionEnabler + ->expects($this->once()) + ->method('isEncryptionEnabled') + ->willReturn(false) + ; + + // The type SHOULD not encrypt the encrypted string if the encryption is disabled + $encryptionManager + ->expects($this->never()) + ->method('decryptString') + ; + + $value = $type->convertToPHPValue($encryptedString, $platform); + $this->assertEquals('\X666', $value); + } + public function testConvertToDatabaseValue(): void { - [$type, $encryptionManager] = $this->createType(); + [$type, $encryptionManager, $encryptionEnabler] = $this->createType(); $string = 'my_string'; $platform = $this->createMock(MySqlPlatform::class); + $encryptionEnabler + ->expects($this->once()) + ->method('isEncryptionEnabled') + ->willReturn(true) + ; + // The type SHOULD decrypt the encrypted string $encryptionManager ->expects($this->once()) @@ -46,15 +81,47 @@ public function testConvertToDatabaseValue(): void $this->assertEquals(base64_encode('my_encrypted_string'), $value); } + public function testConvertToDatabaseValueWithEncryptionDisabled(): void + { + [$type, $encryptionManager, $encryptionEnabler] = $this->createType(); + $string = 'my_string'; + $platform = $this->createMock(MySqlPlatform::class); + + $encryptionEnabler + ->expects($this->once()) + ->method('isEncryptionEnabled') + ->willReturn(false) + ; + + // The type SHOULD not decrypt the encrypted string if the encryption is disabled + $encryptionManager + ->expects($this->never()) + ->method('encryptString') + ; + + $value = $type->convertToDatabaseValue($string, $platform); + $this->assertEquals('my_string', $value); + } + + public function testGetName(): void + { + [$type] = $this->createType(); + + $this->assertEquals('encrypt_string', $type->getName()); + } + /** * @return EncryptStringType[]|MockObject[] */ private function createType(): array { $encryptionManager = $this->createMock(EncryptionManagerInterface::class); + $encryptionEnabler = $this->createMock(EncryptionEnablerInterface::class); + $type = new EncryptStringType(); $type->setEncryptionManager($encryptionManager); + $type->setEncryptionEnabler($encryptionEnabler); - return [$type, $encryptionManager]; + return [$type, $encryptionManager, $encryptionEnabler]; } } diff --git a/tests/PHPUnit/Doctrine/Type/EncryptTextTypeTest.php b/tests/PHPUnit/Doctrine/Type/EncryptTextTypeTest.php index 0612675..8359014 100644 --- a/tests/PHPUnit/Doctrine/Type/EncryptTextTypeTest.php +++ b/tests/PHPUnit/Doctrine/Type/EncryptTextTypeTest.php @@ -7,16 +7,23 @@ use PHPUnit\Framework\TestCase; use Sidus\EncryptionBundle\Doctrine\Type\EncryptStringType; use Sidus\EncryptionBundle\Doctrine\Type\EncryptTextType; +use Sidus\EncryptionBundle\Encryption\Enabler\EncryptionEnablerInterface; use Sidus\EncryptionBundle\Manager\EncryptionManagerInterface; class EncryptTextTypeTest extends TestCase { public function testConvertToPHPValue(): void { - [$type, $encryptionManager] = $this->createType(); + [$type, $encryptionManager, $encryptionEnabler] = $this->createType(); $encryptedString = '\X666'; $platform = $this->createMock(MySqlPlatform::class); - + + $encryptionEnabler + ->expects($this->once()) + ->method('isEncryptionEnabled') + ->willReturn(true) + ; + // The type SHOULD decrypt the encrypted string $encryptionManager ->expects($this->once()) @@ -29,12 +36,40 @@ public function testConvertToPHPValue(): void $this->assertEquals('my_decrypted_string', $value); } + public function testConvertToPHPValueWithEncryptionDisabled(): void + { + [$type, $encryptionManager, $encryptionEnabler] = $this->createType(); + $encryptedString = '\X666'; + $platform = $this->createMock(MySqlPlatform::class); + + $encryptionEnabler + ->expects($this->once()) + ->method('isEncryptionEnabled') + ->willReturn(false) + ; + + // The type SHOULD not encrypt the encrypted string if the encryption is disabled + $encryptionManager + ->expects($this->never()) + ->method('decryptString') + ; + + $value = $type->convertToPHPValue($encryptedString, $platform); + $this->assertEquals('\X666', $value); + } + public function testConvertToDatabaseValue(): void { - [$type, $encryptionManager] = $this->createType(); + [$type, $encryptionManager, $encryptionEnabler] = $this->createType(); $string = 'my_string'; $platform = $this->createMock(MySqlPlatform::class); - + + $encryptionEnabler + ->expects($this->once()) + ->method('isEncryptionEnabled') + ->willReturn(true) + ; + // The type SHOULD decrypt the encrypted string $encryptionManager ->expects($this->once()) @@ -42,20 +77,52 @@ public function testConvertToDatabaseValue(): void ->with($string) ->willReturn('my_encrypted_string') ; - + $value = $type->convertToDatabaseValue($string, $platform); $this->assertEquals(base64_encode('my_encrypted_string'), $value); } + public function testConvertToDatabaseValueWithEncryptionDisabled(): void + { + [$type, $encryptionManager, $encryptionEnabler] = $this->createType(); + $string = 'my_string'; + $platform = $this->createMock(MySqlPlatform::class); + + $encryptionEnabler + ->expects($this->once()) + ->method('isEncryptionEnabled') + ->willReturn(false) + ; + + // The type SHOULD not decrypt the encrypted string if the encryption is disabled + $encryptionManager + ->expects($this->never()) + ->method('encryptString') + ; + + $value = $type->convertToDatabaseValue($string, $platform); + $this->assertEquals('my_string', $value); + } + + public function testGetName(): void + { + [$type] = $this->createType(); + + $this->assertEquals('encrypt_text', $type->getName()); + } + /** * @return EncryptStringType[]|MockObject[] */ private function createType(): array { $encryptionManager = $this->createMock(EncryptionManagerInterface::class); + $encryptionEnabler = $this->createMock(EncryptionEnablerInterface::class); + $type = new EncryptTextType(); $type->setEncryptionManager($encryptionManager); + $type->setEncryptionEnabler($encryptionEnabler); - return [$type, $encryptionManager]; + return [$type, $encryptionManager, $encryptionEnabler]; } }