Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@
## Next

* [feature] 🌟 String pattern anonymizer, build complex strings by fetching values from other anonymizers.
* [fix] Some minor PHP 8.4 deprecations.
* [bc] Salt in `AbstractAnonymizer::$option->get('salt')` in now in `AbstractAnonymizer::$context->salt` (#235).
* [bc] `AbstractAnonymizer::__construct()` now expects an additional `$context` parameter (#235).
* [bc] `Anonymizator::__construct()` `$salt` parameter was removed (#235).
* [bc] Officially drop support for MySQL 5.7 - code is still there and working but automated unit testing has been disabled.
* [internal] introduce anonymizer context for carrying environment configuration to anonymizers (#235).
* [internal] Add automated testing for MariaDB 12.
* [internal] Rewrote `dev.sh` local unit testing script to be simpler, reorganized the local unit testing Docker stack.

Expand Down
21 changes: 10 additions & 11 deletions src/Anonymization/Anonymizator.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use MakinaCorpus\DbToolsBundle\Anonymization\Anonymizer\AbstractAnonymizer;
use MakinaCorpus\DbToolsBundle\Anonymization\Anonymizer\AnonymizerRegistry;
use MakinaCorpus\DbToolsBundle\Anonymization\Anonymizer\Context;
use MakinaCorpus\DbToolsBundle\Anonymization\Config\AnonymizationConfig;
use MakinaCorpus\DbToolsBundle\Anonymization\Config\AnonymizerConfig;
use MakinaCorpus\DbToolsBundle\Helper\Format;
Expand Down Expand Up @@ -42,15 +43,16 @@ class Anonymizator implements LoggerAwareInterface
];

private OutputInterface $output;
private readonly Context $defaultContext;

public function __construct(
private DatabaseSession $databaseSession,
private AnonymizerRegistry $anonymizerRegistry,
private AnonymizationConfig $anonymizationConfig,
private ?string $salt = null,
) {
$this->logger = new NullLogger();
$this->output = new NullOutput();
$this->defaultContext = new Context();
}

/**
Expand All @@ -71,25 +73,21 @@ public function setOutput(OutputInterface $output): self
return $this;
}

#[\Deprecated(message: "Will be removed in 3.0, use Context::generateRandomSalt() instead.", since: "2.1.0")]
public static function generateRandomSalt(): string
{
return \base64_encode(\random_bytes(12));
}

protected function getSalt(): string
{
return $this->salt ??= self::generateRandomSalt();
return Context::generateRandomSalt();
}

/**
* Create anonymizer instance.
*/
protected function createAnonymizer(AnonymizerConfig $config): AbstractAnonymizer
protected function createAnonymizer(AnonymizerConfig $config, Context $context): AbstractAnonymizer
{
return $this->anonymizerRegistry->createAnonymizer(
$config->anonymizer,
$config,
$config->options->with(['salt' => $this->getSalt()]),
$context,
$this->databaseSession
);
}
Expand Down Expand Up @@ -127,6 +125,7 @@ public function anonymize(
}

$plan = [];
$context = clone $this->defaultContext;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given taht $context is readonly in AbstractAnonymizer, why do you need to clone it here ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because the anonymization run will change its internals (at least it will later if we implement anonymization sample table sharing). So if we run multiple anonymizations following each other, the context might wrongly keep data from one run to another.


if ($onlyTargets) {
foreach ($onlyTargets as $targetString) {
Expand Down Expand Up @@ -160,7 +159,7 @@ public function anonymize(
foreach ($plan as $table => $targets) {
$anonymizers[$table] = [];
foreach ($this->anonymizationConfig->getTableConfig($table, $targets) as $target => $config) {
$anonymizers[$table][] = $this->createAnonymizer($config);
$anonymizers[$table][] = $this->createAnonymizer($config, $context);
}
}

Expand Down Expand Up @@ -910,7 +909,7 @@ public function checkAnonymizationConfig(): array
foreach ($this->anonymizationConfig->all() as $table => $tableConfig) {
foreach ($tableConfig as $config) {
try {
$this->createAnonymizer($config);
$this->createAnonymizer($config, $this->defaultContext);
} catch (\Exception $e) {
if (!\key_exists($table, $errors)) {
$errors[$table] = [];
Expand Down
12 changes: 2 additions & 10 deletions src/Anonymization/Anonymizer/AbstractAnonymizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

namespace MakinaCorpus\DbToolsBundle\Anonymization\Anonymizer;

use MakinaCorpus\DbToolsBundle\Anonymization\Anonymizator;
use MakinaCorpus\QueryBuilder\DatabaseSession;
use MakinaCorpus\QueryBuilder\Expression;
use MakinaCorpus\QueryBuilder\ExpressionFactory;
Expand All @@ -22,7 +21,8 @@ final public function __construct(
protected string $tableName,
protected string $columnName,
protected DatabaseSession $databaseSession,
protected Options $options,
protected readonly Context $context,
protected readonly Options $options,
) {
$this->validateOptions();
}
Expand Down Expand Up @@ -74,14 +74,6 @@ protected function getJoinColumn(): Expression
return ExpressionFactory::column($this->getJoinId(), self::JOIN_TABLE);
}

/**
* Get a random, global salt for anonymizing hashed values.
*/
protected function getSalt(): string
{
return $this->options->get('salt') ?? Anonymizator::generateRandomSalt();
}

/**
* Validate options given to the Anonymizer.
*
Expand Down
4 changes: 2 additions & 2 deletions src/Anonymization/Anonymizer/AnonymizerRegistry.php
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,12 @@ public function getAllAnonymizerMetadata(): array
public function createAnonymizer(
string $name,
AnonymizerConfig $config,
Options $options,
Context $context,
DatabaseSession $databaseSession,
): AbstractAnonymizer {
$className = $this->getAnonymizerClass($name);

$ret = new $className($config->table, $config->targetName, $databaseSession, $options);
$ret = new $className($config->table, $config->targetName, $databaseSession, $context, $config->options);
\assert($ret instanceof AbstractAnonymizer);

if ($ret instanceof WithAnonymizerRegistry) {
Expand Down
21 changes: 21 additions & 0 deletions src/Anonymization/Anonymizer/Context.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

declare(strict_types=1);

namespace MakinaCorpus\DbToolsBundle\Anonymization\Anonymizer;

class Context
{
public readonly string $salt;

public function __construct(
?string $salt = null,
) {
$this->salt = $salt ?? self::generateRandomSalt();
}

public static function generateRandomSalt(): string
{
return \base64_encode(\random_bytes(12));
}
}
2 changes: 1 addition & 1 deletion src/Anonymization/Anonymizer/Core/EmailAnonymizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public function createAnonymizeExpression(Update $update): Expression
$userExpr = $expr->column($this->columnName, $this->tableName);

if ($this->options->getBool('use_salt', true)) {
$userExpr = $expr->concat($userExpr, $expr->value($this->getSalt()));
$userExpr = $expr->concat($userExpr, $expr->value($this->context->salt));
}

$emailHashExpr = $expr->md5($userExpr);
Expand Down
2 changes: 1 addition & 1 deletion src/Anonymization/Anonymizer/Core/Md5Anonymizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public function createAnonymizeExpression(Update $update): Expression
$columnExpr = $expr->column($this->columnName, $this->tableName);

if ($this->options->get('use_salt', true)) {
$columnExpr = $expr->concat($columnExpr, $expr->value($this->getSalt()));
$columnExpr = $expr->concat($columnExpr, $expr->value($this->context->salt));

// Work around some RDBMS not seeing the NULL value anymore
// once we added the string concat.
Expand Down
4 changes: 2 additions & 2 deletions src/Anonymization/Anonymizer/Core/StringPatternAnonymizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -194,14 +194,14 @@ private function getAnonymizer(string $anonymizer, ?Options $options = null, int
return $ret;
}

$config = new AnonymizerConfig($this->tableName, $this->columnName, $anonymizer, new Options());
$config = new AnonymizerConfig($this->tableName, $this->columnName, $anonymizer, $options ?? new Options());

return $this->childAnonymizers[$key] = $this
->getAnonymizerRegistry()
->createAnonymizer(
$anonymizer,
$config,
$options ?? new Options(),
$this->context,
$this->databaseSession
)
;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace MakinaCorpus\DbToolsBundle\Tests\Unit\Anonymization\Anonymizer;

use MakinaCorpus\DbToolsBundle\Anonymization\Anonymizer\AbstractMultipleColumnAnonymizer;
use MakinaCorpus\DbToolsBundle\Anonymization\Anonymizer\Context;
use MakinaCorpus\DbToolsBundle\Anonymization\Anonymizer\Options;
use MakinaCorpus\DbToolsBundle\Attribute\AsAnonymizer;
use MakinaCorpus\DbToolsBundle\Test\UnitTestCase;
Expand All @@ -17,6 +18,7 @@ public function testValidateOptionsOkWithAllColumnOption(): void
'some_table',
'some_column',
$this->getDatabaseSession(),
new Context(),
new Options([
'column_1' => 'actual_column_1',
'column_2' => 'actual_column_2',
Expand All @@ -32,6 +34,7 @@ public function testValidateOptionsOkWithSomeColumnOption(): void
'some_table',
'some_column',
$this->getDatabaseSession(),
new Context(),
new Options([
'column_2' => 'actual_column_2',
]),
Expand All @@ -48,7 +51,8 @@ public function testValidateOptionsKoWithNoOption(): void
'some_table',
'some_column',
$this->getDatabaseSession(),
new Options([]),
new Context(),
new Options(),
);
}

Expand All @@ -60,6 +64,7 @@ public function testValidateOptionsKoWithColumnMapedTwice(): void
'some_table',
'some_column',
$this->getDatabaseSession(),
new Context(),
new Options([
'column_1' => 'actual_column_1',
'column_2' => 'actual_column_1',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace MakinaCorpus\DbToolsBundle\Tests\Unit\Anonymization\Anonymizer\Core;

use MakinaCorpus\DbToolsBundle\Anonymization\Anonymizer\Context;
use MakinaCorpus\DbToolsBundle\Anonymization\Anonymizer\Core\ConstantAnonymizer;
use MakinaCorpus\DbToolsBundle\Anonymization\Anonymizer\Options;
use MakinaCorpus\DbToolsBundle\Test\UnitTestCase;
Expand All @@ -16,6 +17,7 @@ public function testValidateOptionsOkWithValueOption(): void
'some_table',
'some_column',
$this->getDatabaseSession(),
new Context(),
new Options([
'value' => 'test',
]),
Expand All @@ -32,6 +34,7 @@ public function testValidateOptionsKoWithValueOption(): void
'some_table',
'some_column',
$this->getDatabaseSession(),
new Context(),
new Options([]),
);
}
Expand Down
22 changes: 16 additions & 6 deletions tests/Unit/Anonymization/Anonymizer/Core/EmailAnonymizerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@

namespace MakinaCorpus\DbToolsBundle\Tests\Unit\Anonymization\Anonymizer\Core;

use MakinaCorpus\DbToolsBundle\Anonymization\Anonymizer\Options;
use MakinaCorpus\DbToolsBundle\Anonymization\Anonymizer\Context;
use MakinaCorpus\DbToolsBundle\Anonymization\Anonymizer\Core\EmailAnonymizer;
use MakinaCorpus\DbToolsBundle\Anonymization\Anonymizer\Options;
use MakinaCorpus\DbToolsBundle\Test\UnitTestCase;

class EmailAnonymizerTest extends UnitTestCase
Expand All @@ -16,7 +17,8 @@ public function testValidateOptionsOkWithNoOption(): void
'some_table',
'email',
$this->getDatabaseSession(),
new Options([]),
new Context(),
new Options(),
);

self::expectNotToPerformAssertions();
Expand All @@ -28,6 +30,7 @@ public function testValidateOptionsOkWithDomainOptionAsString(): void
'some_table',
'email',
$this->getDatabaseSession(),
new Context(),
new Options([
'domain' => 'makina-corpus.com',
]),
Expand All @@ -44,6 +47,7 @@ public function testValidateOptionsKoWithDomainNotStringable(): void
'some_table',
'email',
$this->getDatabaseSession(),
new Context(),
new Options([
'domain' => ['ttt', 'ttt'],
]),
Expand All @@ -56,6 +60,7 @@ public function testValidateOptionsOkWithUseSaltOptionAsBool(): void
'some_table',
'email',
$this->getDatabaseSession(),
new Context(),
new Options([
'use_salt' => true,
]),
Expand All @@ -72,6 +77,7 @@ public function testValidateOptionsKoWithUseSaltOptionAsNoneBool(): void
'some_table',
'email',
$this->getDatabaseSession(),
new Context(),
new Options([
'use_salt' => ['true'],
]),
Expand All @@ -86,9 +92,10 @@ public function testAnonymizeWithDefaultDomain(): void
'some_table',
'email',
$this->getDatabaseSession(),
new Options([
'salt' => 'my_salt',
])
new Context(
salt: 'my_salt',
),
new Options(),
);

$instance->anonymize($update);
Expand Down Expand Up @@ -121,9 +128,11 @@ public function testAnonymize(): void
'some_table',
'email',
$this->getDatabaseSession(),
new Context(
salt: 'my_salt',
),
new Options([
'domain' => 'makina-corpus.com',
'salt' => 'my_salt',
]),
);

Expand Down Expand Up @@ -157,6 +166,7 @@ public function testAnonymizeWithoutSalt(): void
'some_table',
'email',
$this->getDatabaseSession(),
new Context(),
new Options([
'use_salt' => false,
]),
Expand Down
Loading