-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #5 from carnage/migration
Migration
- Loading branch information
Showing
21 changed files
with
549 additions
and
13 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
<?php | ||
namespace Carnage\EncryptedColumn\Dbal; | ||
|
||
use Carnage\EncryptedColumn\Service\EncryptionService; | ||
use Carnage\EncryptedColumn\ValueObject\ValueHolder; | ||
use Doctrine\DBAL\Platforms\AbstractPlatform; | ||
use Doctrine\DBAL\Types\ConversionException; | ||
use Doctrine\DBAL\Types\Type; | ||
use Carnage\EncryptedColumn\ValueObject\EncryptedColumn as EncryptedColumnVO; | ||
|
||
/** | ||
* Object type for reading from legacy encrypted column extensions and converting to a better format | ||
* | ||
* This type is a drop in replacement for the EncryptedColumn class but drops some strictness to allow for | ||
* reading data that is not in an expected format. You should only use this if you have existing data in | ||
* your database you wish to convert | ||
* | ||
* Class EncryptedColumn | ||
*/ | ||
class EncryptedColumnLegacySupport extends Type | ||
{ | ||
const ENCRYPTED = 'encrypted'; | ||
|
||
/** | ||
* @var EncryptionService | ||
*/ | ||
private $encryptionService; | ||
|
||
public static function create(EncryptionService $encryptionService) | ||
{ | ||
Type::addType(EncryptedColumnLegacySupport::ENCRYPTED, EncryptedColumnLegacySupport::class); | ||
/** @var EncryptedColumnLegacySupport $instance */ | ||
$instance = Type::getType(EncryptedColumnLegacySupport::ENCRYPTED); | ||
$instance->encryptionService = $encryptionService; | ||
} | ||
|
||
public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform) | ||
{ | ||
return $platform->getClobTypeDeclarationSQL($fieldDeclaration); | ||
} | ||
|
||
public function requiresSQLCommentHint(AbstractPlatform $platform) | ||
{ | ||
return true; | ||
} | ||
|
||
public function getName() | ||
{ | ||
return self::ENCRYPTED; | ||
} | ||
|
||
public function convertToPHPValue($value, AbstractPlatform $platform) | ||
{ | ||
if ($value === null) { | ||
return null; | ||
} | ||
|
||
try { | ||
$decoded = $this->decodeJson($value); | ||
} catch (ConversionException $e) { | ||
//The data wasn't in the format we expected, assume it is legacy data which needs converting | ||
//Drop in some defaults to allow the library to handle it. | ||
$decoded = [ | ||
'data' => $value, | ||
'classname' => ValueHolder::class, | ||
'serializer' => 'legacy', | ||
'encryptor' => 'legacy' | ||
]; | ||
} | ||
|
||
return $this->encryptionService->decryptField(EncryptedColumnVO::fromArray($decoded)); | ||
} | ||
|
||
public function convertToDatabaseValue($value, AbstractPlatform $platform) | ||
{ | ||
if ($value === null) { | ||
return null; | ||
} | ||
|
||
return json_encode($this->encryptionService->encryptField($value)); | ||
} | ||
|
||
/** | ||
* Based on: https://github.com/schmittjoh/serializer/blob/master/src/JMS/Serializer/JsonDeserializationVisitor.php | ||
* | ||
* @param $value | ||
* @return mixed | ||
* @throws ConversionException | ||
*/ | ||
private function decodeJson($value) | ||
{ | ||
$decoded = json_decode($value, true); | ||
|
||
switch (json_last_error()) { | ||
case JSON_ERROR_NONE: | ||
if (!is_array($decoded)) { | ||
throw ConversionException::conversionFailed($value, 'Json was not an array'); | ||
} | ||
return $decoded; | ||
case JSON_ERROR_DEPTH: | ||
throw ConversionException::conversionFailed($value, 'Could not decode JSON, maximum stack depth exceeded.'); | ||
case JSON_ERROR_STATE_MISMATCH: | ||
throw ConversionException::conversionFailed($value, 'Could not decode JSON, underflow or the nodes mismatch.'); | ||
case JSON_ERROR_CTRL_CHAR: | ||
throw ConversionException::conversionFailed($value, 'Could not decode JSON, unexpected control character found.'); | ||
case JSON_ERROR_SYNTAX: | ||
throw ConversionException::conversionFailed($value, 'Could not decode JSON, syntax error - malformed JSON.'); | ||
case JSON_ERROR_UTF8: | ||
throw ConversionException::conversionFailed($value, 'Could not decode JSON, malformed UTF-8 characters (incorrectly encoded?)'); | ||
default: | ||
throw ConversionException::conversionFailed($value, 'Could not decode Json'); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
<?php | ||
|
||
namespace Carnage\EncryptedColumn\Encryptor; | ||
|
||
use Carnage\EncryptedColumn\Exception\PopArtPenguinException; | ||
use Carnage\EncryptedColumn\ValueObject\EncryptorIdentity; | ||
use Carnage\EncryptedColumn\ValueObject\IdentityInterface; | ||
use phpseclib\Crypt\Base; | ||
use phpseclib\Crypt\Rijndael; | ||
|
||
class LegacyEncryptor implements EncryptorInterface | ||
{ | ||
const IDENTITY = 'legacy'; | ||
/** | ||
* @var string | ||
*/ | ||
private $secret; | ||
|
||
public function __construct($secret) | ||
{ | ||
$this->secret = $secret; | ||
} | ||
|
||
public function encrypt($data) | ||
{ | ||
throw new PopArtPenguinException(); | ||
} | ||
|
||
public function decrypt($data) | ||
{ | ||
$cipher = new Rijndael(Base::MODE_ECB); | ||
$cipher->setBlockLength(256); | ||
$cipher->setKey($this->secret); | ||
$cipher->padding = false; | ||
|
||
return trim($cipher->decrypt(base64_decode($data))); | ||
} | ||
|
||
public function getIdentifier(): IdentityInterface | ||
{ | ||
return new EncryptorIdentity(self::IDENTITY); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
<?php | ||
|
||
namespace Carnage\EncryptedColumn\Exception; | ||
|
||
class PopArtPenguinException extends \BadMethodCallException | ||
{ | ||
public function __construct() | ||
{ | ||
parent::__construct( | ||
'The encryption class you attempted to use is not considered secure and is only suitable for creating pop art penguins https://blog.filippo.io/the-ecb-penguin/' | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
<?php | ||
|
||
namespace Carnage\EncryptedColumn\Serializer; | ||
|
||
use Carnage\EncryptedColumn\ValueObject\IdentityInterface; | ||
use Carnage\EncryptedColumn\ValueObject\SerializerIdentity; | ||
use Carnage\EncryptedColumn\ValueObject\ValueHolder; | ||
|
||
class LegacySerializer implements SerializerInterface | ||
{ | ||
const IDENTITY = 'legacy'; | ||
|
||
public function serialize($data) | ||
{ | ||
throw new \Exception('This class is for read only access to legacy data'); | ||
} | ||
|
||
public function unserialize($data) | ||
{ | ||
return new ValueHolder($data); | ||
} | ||
|
||
public function getIdentifier(): IdentityInterface | ||
{ | ||
return new SerializerIdentity(self::IDENTITY); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
<?php | ||
|
||
namespace Carnage\EncryptedColumn; | ||
|
||
use Carnage\EncryptedColumn\Container\VersionedContainer; | ||
use Carnage\EncryptedColumn\Dbal\EncryptedColumn; | ||
use Carnage\EncryptedColumn\Dbal\EncryptedColumnLegacySupport; | ||
use Carnage\EncryptedColumn\Encryptor\HaliteEncryptor; | ||
use Carnage\EncryptedColumn\Encryptor\LegacyEncryptor; | ||
use Carnage\EncryptedColumn\Serializer\LegacySerializer; | ||
use Carnage\EncryptedColumn\Serializer\PhpSerializer; | ||
use Carnage\EncryptedColumn\Service\EncryptionService; | ||
use Doctrine\ORM\EntityManagerInterface; | ||
|
||
final class Setup | ||
{ | ||
private $keyPath; | ||
private $enableLegacy = false; | ||
private $legacyKey; | ||
|
||
public function register(EntityManagerInterface $em) | ||
{ | ||
if ($this->enableLegacy) { | ||
$this->doRegisterLegacy($em); | ||
} else { | ||
$this->doRegister($em); | ||
} | ||
} | ||
|
||
public function enableLegacy(string $legacyKey) | ||
{ | ||
$this->enableLegacy = true; | ||
$this->legacyKey = $legacyKey; | ||
return $this; | ||
} | ||
|
||
public function withKeyPath(string $keypath) | ||
{ | ||
$this->keyPath = $keypath; | ||
return $this; | ||
} | ||
|
||
private function buildEncryptionService(): EncryptionService | ||
{ | ||
$encryptors = self::buildEncryptorsContainer(); | ||
$serializers = self::buildSerilaizerContainer(); | ||
return new EncryptionService( | ||
$encryptors->get(HaliteEncryptor::IDENTITY), | ||
$serializers->get(PhpSerializer::IDENTITY), | ||
$encryptors, | ||
$serializers | ||
); | ||
} | ||
|
||
private function buildEncryptorsContainer(): VersionedContainer | ||
{ | ||
$services = [new HaliteEncryptor($this->keyPath)]; | ||
if ($this->enableLegacy) { | ||
$services[] = new LegacyEncryptor($this->legacyKey); | ||
} | ||
//@TODO add legacy encryptor, throw exceptions if required keys aren't specified | ||
return new VersionedContainer(...$services); | ||
} | ||
|
||
private function buildSerilaizerContainer(): VersionedContainer | ||
{ | ||
$services = [new PhpSerializer()]; | ||
if ($this->enableLegacy) { | ||
$services[] = new LegacySerializer(); | ||
} | ||
return new VersionedContainer(...$services); | ||
} | ||
|
||
/** | ||
* @param EntityManagerInterface $em | ||
*/ | ||
private function doRegister(EntityManagerInterface $em) | ||
{ | ||
EncryptedColumn::create($this->buildEncryptionService()); | ||
$conn = $em->getConnection(); | ||
$conn->getDatabasePlatform()->registerDoctrineTypeMapping( | ||
EncryptedColumn::ENCRYPTED, | ||
EncryptedColumn::ENCRYPTED | ||
); | ||
} | ||
|
||
/** | ||
* @param EntityManagerInterface $em | ||
*/ | ||
private function doRegisterLegacy(EntityManagerInterface $em) | ||
{ | ||
EncryptedColumnLegacySupport::create($this->buildEncryptionService()); | ||
$conn = $em->getConnection(); | ||
$conn->getDatabasePlatform()->registerDoctrineTypeMapping( | ||
EncryptedColumnLegacySupport::ENCRYPTED, | ||
EncryptedColumnLegacySupport::ENCRYPTED | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
<?php | ||
|
||
namespace Carnage\EncryptedColumn\ValueObject; | ||
|
||
class ValueHolder | ||
{ | ||
private $value; | ||
|
||
public function __construct($value) | ||
{ | ||
$this->value = $value; | ||
} | ||
|
||
/** | ||
* @return mixed | ||
*/ | ||
public function getValue() | ||
{ | ||
return $this->value; | ||
} | ||
} |
Oops, something went wrong.