-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
30 changed files
with
1,822 additions
and
0 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
/* | ||
* This file is part of the ekino/data-protection-bundle project. | ||
* | ||
* (c) Ekino | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace Ekino\DataProtectionBundle\Annotations; | ||
|
||
/** | ||
* Class AnonymizedEntity. | ||
* | ||
* @Annotation | ||
* @Target({"CLASS"}) | ||
* | ||
* @author Benoit Mazière <benoit.maziere@ekino.com> | ||
*/ | ||
final class AnonymizedEntity | ||
{ | ||
public const ACTION_ANONYMIZE = 'anonymize'; | ||
public const ACTION_TRUNCATE = 'truncate'; | ||
private const ACTION_CHOICES = [self::ACTION_ANONYMIZE, self::ACTION_TRUNCATE]; | ||
|
||
/** | ||
* @var string | ||
*/ | ||
private $action = self::ACTION_ANONYMIZE; | ||
|
||
/** | ||
* Add where sql condition on which not apply anonymization. | ||
* | ||
* @var string|null | ||
*/ | ||
private $exceptWhereClause = null; | ||
|
||
public function __construct(array $options) | ||
{ | ||
foreach ($options as $key => $value) { | ||
if (!property_exists($this, $key)) { | ||
throw new \InvalidArgumentException(sprintf('Property "%s" does not exist', $key)); | ||
} | ||
|
||
$this->$key = $value; | ||
} | ||
|
||
$this->validateAction(); | ||
} | ||
|
||
public function getAction(): string | ||
{ | ||
return $this->action; | ||
} | ||
|
||
public function getExceptWhereClause(): ?string | ||
{ | ||
return $this->exceptWhereClause; | ||
} | ||
|
||
public function isTruncateAction(): bool | ||
{ | ||
return static::ACTION_TRUNCATE === $this->action; | ||
} | ||
|
||
public function isAnonymizeAction(): bool | ||
{ | ||
return static::ACTION_ANONYMIZE === $this->action; | ||
} | ||
|
||
private function validateAction(): void | ||
{ | ||
if (!\in_array($this->action, static::ACTION_CHOICES)) { | ||
throw new \InvalidArgumentException(sprintf('Action "%s" is not allowed. Allowed actions are : %s', | ||
$this->action, implode(', ', static::ACTION_CHOICES))); | ||
} | ||
} | ||
} |
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,136 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
/* | ||
* This file is part of the ekino/data-protection-bundle project. | ||
* | ||
* (c) Ekino | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace Ekino\DataProtectionBundle\Annotations; | ||
|
||
/** | ||
* Class AnonymizedProperty. | ||
* | ||
* @Annotation | ||
* @Target({"PROPERTY"}) | ||
* | ||
* @author Benoit Mazière <benoit.maziere@ekino.com> | ||
*/ | ||
final class AnonymizedProperty | ||
{ | ||
public const TYPE_STATIC = 'static'; | ||
public const TYPE_COMPOSED = 'composed'; | ||
public const TYPE_EXPRESSION = 'expression'; | ||
private const TYPE_CHOICES = [self::TYPE_STATIC, self::TYPE_COMPOSED, self::TYPE_EXPRESSION]; | ||
|
||
/** | ||
* @var mixed|null | ||
*/ | ||
private $value = null; | ||
|
||
/** | ||
* Can be of type static (fixed value) or composed (mix of static & existing field value). | ||
* | ||
* @var string | ||
*/ | ||
private $type = self::TYPE_STATIC; | ||
|
||
/** | ||
* @var string | ||
*/ | ||
private $fieldName; | ||
|
||
/** | ||
* @var string | ||
*/ | ||
private $columnName; | ||
|
||
public function __construct(array $options) | ||
{ | ||
foreach ($options as $key => $value) { | ||
if (!property_exists($this, $key)) { | ||
throw new \InvalidArgumentException(sprintf('Property "%s" does not exist', $key)); | ||
} | ||
|
||
$this->$key = $value; | ||
} | ||
|
||
$this->validateType(); | ||
} | ||
|
||
public function getValue() | ||
{ | ||
return $this->value; | ||
} | ||
|
||
public function getType(): string | ||
{ | ||
return $this->type; | ||
} | ||
|
||
public function getFieldName(): string | ||
{ | ||
return $this->fieldName; | ||
} | ||
|
||
public function setFieldName(string $fieldName): self | ||
{ | ||
$this->fieldName = $fieldName; | ||
|
||
return $this; | ||
} | ||
|
||
public function getColumnName(): string | ||
{ | ||
return $this->columnName; | ||
} | ||
|
||
public function setColumnName(string $columnName): self | ||
{ | ||
$this->columnName = $columnName; | ||
|
||
return $this; | ||
} | ||
|
||
public function isStatic(): bool | ||
{ | ||
return static::TYPE_STATIC === $this->type; | ||
} | ||
|
||
public function isComposed(): bool | ||
{ | ||
return static::TYPE_COMPOSED === $this->type; | ||
} | ||
|
||
public function isExpression(): bool | ||
{ | ||
return static::TYPE_EXPRESSION === $this->type; | ||
} | ||
|
||
public function extractComposedFieldFromValue(): string | ||
{ | ||
preg_match('/<(\w*)>/', $this->value, $matches); | ||
|
||
return $matches[1] ?? ''; | ||
} | ||
|
||
public function explodeComposedFieldValue(): array | ||
{ | ||
preg_match('/(.*)<(\w*)>(.*)/', $this->value, $matches); | ||
|
||
return $matches ?? []; | ||
} | ||
|
||
private function validateType(): void | ||
{ | ||
if (!\in_array($this->type, static::TYPE_CHOICES)) { | ||
throw new \InvalidArgumentException(sprintf('Type "%s" is not allowed. Allowed values are : %s', | ||
$this->type, implode(', ', static::TYPE_CHOICES))); | ||
} | ||
} | ||
} |
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,113 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
/* | ||
* This file is part of the ekino/data-protection-bundle project. | ||
* | ||
* (c) Ekino | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace Ekino\DataProtectionBundle\Command; | ||
|
||
use Ekino\DataProtectionBundle\Meta\AnonymizedMetadataBuilder; | ||
use Ekino\DataProtectionBundle\Meta\AnonymizedMetadataValidator; | ||
use Ekino\DataProtectionBundle\QueryBuilder\AnonymizedQueryBuilder; | ||
use Symfony\Component\Console\Command\Command; | ||
use Symfony\Component\Console\Input\InputInterface; | ||
use Symfony\Component\Console\Input\InputOption; | ||
use Symfony\Component\Console\Output\OutputInterface; | ||
|
||
/** | ||
* Class AnonymizeDataCommand | ||
* | ||
* @author Benoit Mazière <benoit.maziere@ekino.com> | ||
*/ | ||
final class AnonymizeDataCommand extends Command | ||
{ | ||
/** | ||
* {@inheritdoc} | ||
*/ | ||
protected static $defaultName = 'ekino-data-protection:anonymize'; | ||
|
||
/** | ||
* @var AnonymizedMetadataBuilder | ||
*/ | ||
protected $anonymizedMetadataBuilder; | ||
|
||
/** | ||
* @var AnonymizedMetadataValidator | ||
*/ | ||
protected $anonymizedMetadataValidator; | ||
|
||
/** | ||
* @var AnonymizedQueryBuilder | ||
*/ | ||
protected $anonymizedQueryBuilder; | ||
|
||
public function __construct( | ||
AnonymizedMetadataBuilder $anonymizedMetadataBuilder, | ||
AnonymizedMetadataValidator $anonymizedMetadataValidator, | ||
AnonymizedQueryBuilder $anonymizedQueryBuilder | ||
) | ||
{ | ||
parent::__construct(); | ||
|
||
$this->anonymizedMetadataBuilder = $anonymizedMetadataBuilder; | ||
$this->anonymizedMetadataValidator = $anonymizedMetadataValidator; | ||
$this->anonymizedQueryBuilder = $anonymizedQueryBuilder; | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
protected function configure() | ||
{ | ||
$this->setDescription('Anonymize database based on entities annotations') | ||
->addOption('force', null, InputOption::VALUE_NONE, 'Set this parameter to execute this action') | ||
->setHelp('Usage: `bin/console ekino-data-protection:anonymize`') | ||
; | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
protected function execute(InputInterface $input, OutputInterface $output): int | ||
{ | ||
$output->writeln(sprintf('<info>Anonymization starts</info>')); | ||
|
||
$anonymizedMetadatas = $this->anonymizedMetadataBuilder->build(); | ||
$queries = []; | ||
|
||
foreach ($anonymizedMetadatas as $anonymizedMetadata) { | ||
$this->anonymizedMetadataValidator->validate($anonymizedMetadata); | ||
$queries[] = $this->anonymizedQueryBuilder->buildQuery($anonymizedMetadata); | ||
} | ||
|
||
if (!$input->getOption('force')) { | ||
$output->writeln('<error>ATTENTION:</error> This operation should not be executed in a production environment.'); | ||
$output->writeln(''); | ||
$output->writeln('<info>Would annoymize your database according to your configuration.</info>'); | ||
$output->writeln('Please run the operation with --force to execute'); | ||
$output->writeln('<error>Some data will be lost/anonymized!</error>'); | ||
|
||
// display queries | ||
$output->writeln('<error>Following queries have been built and will be executed:</error>'); | ||
|
||
foreach ($queries as $query) { | ||
$output->writeln(sprintf('<info>%s</info>', $query)); | ||
} | ||
|
||
return 0; | ||
} | ||
|
||
// display & execute queries | ||
|
||
$output->writeln(sprintf('<info>Anonymization ends</info>')); | ||
|
||
return 0; | ||
} | ||
} |
Oops, something went wrong.