Commit 5c1c3d57 authored by Bertrand Gauthier's avatar Bertrand Gauthier
Browse files

Sécurisation à l'aide d'un témoin d'anonymisation dans une table de metadata ; refactorisations.

parent 4de75e1f
Loading
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -10,6 +10,8 @@ use UnicaenDbfakator\Controller\IndexController;
use UnicaenDbfakator\Controller\IndexControllerFactory;
use UnicaenDbfakator\Service\DbfakatorService;
use UnicaenDbfakator\Service\DbfakatorServiceFactory;
use UnicaenDbfakator\Service\DbService;
use UnicaenDbfakator\Service\DbServiceFactory;

return [
    'router' => [
@@ -150,6 +152,7 @@ return [
    ],
    'service_manager' => [
        'factories' => [
            DbService::class => DbServiceFactory::class,
            DbfakatorService::class => DbfakatorServiceFactory::class,
        ],
    ],
+12 −0
Original line number Diff line number Diff line
--
-- Création si nécessaire de la table des métadonnées de la bdd.
--
create table if not exists _METADATA (
    key varchar(64) not null,
    value text,
    extra text,
    description varchar(128)
);
-- Création du témoin d'anonymisation
insert into _METADATA (key, value, description)
values ('BDD_ANONYMISEE', '0', 'Indique si les données de cette bdd ont été anonymisées (1) ou non (0)');
+11 −11
Original line number Diff line number Diff line
@@ -25,7 +25,7 @@ class ConsoleController extends AbstractConsoleController
        $anonymisationScriptPath = $this->dbfakatorService->getAnonymisationScriptPath();
        $restaurationScriptPath = $this->dbfakatorService->getRestaurationScriptPath();
        try {
            $gen = $this->dbfakatorService->generer($anonymisationScriptPath, $restaurationScriptPath);
            $gen = $this->dbfakatorService->generer();
            foreach ($gen as ['table' => $table, 'fields' => $fields, 'count' => $count]) {
                $this->console->writeLine(
                    sprintf("- Table %s (colonnes %s) : %d lignes", $table, implode(', ', $fields), $count)
@@ -49,9 +49,15 @@ class ConsoleController extends AbstractConsoleController
        $this->console->writeLine("Lancement du script d'anonymisation '$scriptPath'...");

        $start = microtime(true);
        $this->lancerAction($scriptPath);
        try {
            $result = $this->dbfakatorService->anonymiser();
        } catch (Exception $e) {
            throw new RuntimeException("Une erreur est survenue lors du lancement du script '$scriptPath'.", null, $e);
        }
        $end = microtime(true);

        $this->console->writeLine("> Résultat : " . ($result->isSuccess() ? 'Succès' : 'Échec'));
        $this->console->writeLine("> Log file : " . $result->getLogFilePath());
        $this->console->writeLine(sprintf("Duree : %.2f s", $end - $start));
    }

@@ -62,21 +68,15 @@ class ConsoleController extends AbstractConsoleController
        $this->console->writeLine("Lancement du script de restauration '$scriptPath'...");

        $start = microtime(true);
        $this->lancerAction($scriptPath);
        $end = microtime(true);

        $this->console->writeLine(sprintf("Duree : %.2f s", $end - $start));
    }

    protected function lancerAction(string $scriptPath)
    {
        try {
            $result = $this->dbfakatorService->lancer($scriptPath);
            $result = $this->dbfakatorService->restaurer();
        } catch (Exception $e) {
            throw new RuntimeException("Une erreur est survenue lors du lancement du script '$scriptPath'.", null, $e);
        }
        $end = microtime(true);

        $this->console->writeLine("> Résultat : " . ($result->isSuccess() ? 'Succès' : 'Échec'));
        $this->console->writeLine("> Log file : " . $result->getLogFilePath());
        $this->console->writeLine(sprintf("Duree : %.2f s", $end - $start));
    }
}
 No newline at end of file
+5 −9
Original line number Diff line number Diff line
@@ -3,12 +3,11 @@
namespace UnicaenDbfakator\Controller;

use Application\Controller\AbstractController;
use Exception;
use Laminas\View\Model\ViewModel;
use UnicaenApp\Exception\RuntimeException;
use UnicaenApp\Service\SQL\RunSQLResult;
use UnicaenDbfakator\Service\DbfakatorServiceAwareTrait;
use Exception;
use UnicaenApp\Exception\RuntimeException;
use Webmozart\Assert\InvalidArgumentException;

class IndexController extends AbstractController
{
@@ -16,9 +15,6 @@ class IndexController extends AbstractController

    public function indexAction(): array
    {
//        $action = $this->params()->fromQuery('action');
//        $this->url()->fromRoute('', [], ['query' => ['redirect' => ]], true);

        $anonymisationScriptPath = $this->dbfakatorService->getAnonymisationScriptPath();
        $restaurationScriptPath = $this->dbfakatorService->getRestaurationScriptPath();

@@ -41,7 +37,7 @@ class IndexController extends AbstractController
        $messages = [];

        try {
            $gen = $this->dbfakatorService->generer($anonymisationScriptPath, $restaurationScriptPath);
            $gen = $this->dbfakatorService->generer();
            foreach ($gen as ['table' => $table, 'fields' => $fields, 'count' => $count]) {
                $messages[] = sprintf("- Table %s (colonnes %s) : %d lignes", $table, implode(', ', $fields), $count);
            }
@@ -61,7 +57,7 @@ class IndexController extends AbstractController
        $scriptPath = $this->dbfakatorService->getAnonymisationScriptPath();

        try {
            $result = $this->dbfakatorService->lancerAnonymisation($scriptPath);
            $result = $this->dbfakatorService->anonymiser();
        } catch (Exception $e) {
            throw new RuntimeException("Une erreur est survenue lors du lancement du script '$scriptPath'.", null, $e);
        }
@@ -74,7 +70,7 @@ class IndexController extends AbstractController
        $scriptPath = $this->dbfakatorService->getRestaurationScriptPath();

        try {
            $result = $this->dbfakatorService->lancerRestauration($scriptPath);
            $result = $this->dbfakatorService->restaurer();
        } catch (Exception $e) {
            throw new RuntimeException("Une erreur est survenue lors du lancement du script '$scriptPath'.", null, $e);
        }
+187 −0
Original line number Diff line number Diff line
<?php

namespace UnicaenDbfakator\Service;

use Doctrine\DBAL\ConnectionException;
use Doctrine\DBAL\Exception;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\Mapping\ClassMetadata;
use Faker\Factory;
use Laminas\Log\Logger;
use Laminas\Log\Writer\Stream;
use Locale;
use RuntimeException;
use Throwable;
use UnicaenApp\Service\SQL\RunSQLProcess;
use UnicaenApp\Service\SQL\RunSQLResult;
use Webmozart\Assert\Assert;

class DbService
{
    const METADATA_KEY_ANONYMISEE = 'BDD_ANONYMISEE';

    /**
     * @var \Doctrine\ORM\EntityManager
     */
    protected $entityManager;

    /**
     * @var \Doctrine\DBAL\Platforms\AbstractPlatform
     */
    protected $databasePlatform;

    /**
     * @var string
     */
    protected $updateSQLTemplate = 'update %s set %s where id = %d ;';

    /**
     * @var \Faker\Generator $faker
     */
    protected $faker;


    public function __construct()
    {
        $this->faker = Factory::create(Locale::getDefault());
    }

    /**
     * @param \Doctrine\ORM\EntityManager $entityManager
     * @throws \Doctrine\DBAL\Exception
     */
    public function setEntityManager(EntityManager $entityManager): void
    {
        $this->entityManager = $entityManager;
        $this->databasePlatform = $this->entityManager->getConnection()->getDatabasePlatform();
    }

    public function isBddAnonymisee(): bool
    {
        $sqlTemplate = "select value from _METADATA where key = '%s'";

        $sql = sprintf($sqlTemplate, self::METADATA_KEY_ANONYMISEE);

        try {
            /** @var string|false $result */
            $result = $this->entityManager->getConnection()->executeQuery($sql)->fetchOne();
        } catch (Throwable $e) {
            throw new RuntimeException(
                "Erreur lors de l'interrogation du témoin d'anonymisation dans la table des metadata", null, $e);
        }

        if ($result === false) {
            throw new RuntimeException(sprintf(
                "Le témoin d'anonymisation '%s' est introuvable dans la table des metadata",
                self::METADATA_KEY_ANONYMISEE
            ));
        }

        return $result === '1';
    }

    public function setBddAnonymisee(bool $bddAnonymisee)
    {
        $sqlTemplate = "update _METADATA set value = '%d' where key = '%s'";

        $sql = sprintf($sqlTemplate, $bddAnonymisee ? '1' : '0', self::METADATA_KEY_ANONYMISEE);

        try {
            $this->entityManager->getConnection()->executeQuery($sql);
        } catch (Exception $e) {
            throw new RuntimeException(
                "Erreur lors de la modification du témoin d'anonymisation dans la table des metadata", null, $e);
        }
    }

    public function fetchEntityRecords(string $entityName, array $fields, array $except): array
    {
        $qb = $this->entityManager->createQueryBuilder()
            ->select("partial t.{id," . implode(',', $fields) . "}")
            ->from($entityName, 't');

        foreach ($except as $field => $values) {
            $qb->andWhere($qb->expr()->notIn('t.' . $field, (array)$values));
        }

        return $qb->getQuery()->getArrayResult();
    }

    public function getEntityClassMetadata(string $entityName): ClassMetadata
    {
        return $this->entityManager->getClassMetadata($entityName);
    }

    public function genUpdateForAnonymiser(array $record, array $mapping, ClassMetadata $metadata): string
    {
        $tableName = $metadata->getTableName();

        $sets = [];

        foreach ($mapping as $fieldName => $fakerConfig) {
            $fakerConfig = (array)$fakerConfig;
            $method = array_shift($fakerConfig);
            if ($method === 'null') {
                $fakeValue = 'NULL';
            } else {
                $fakeValue = $this->faker->$method(...$fakerConfig);
                $fakeValue = $this->databasePlatform->quoteStringLiteral($fakeValue);
            }
            $columnName = $metadata->getColumnName($fieldName);
            $sets[] = "$columnName = $fakeValue";
        }

        return sprintf($this->updateSQLTemplate,
            $tableName,
            implode(', ', $sets),
            $record['id']
        );
    }

    public function genUpdateForRestaurer(array $record, array $mapping, ClassMetadata $metadata): string
    {
        $tableName = $metadata->getTableName();

        $sets = [];

        foreach ($mapping as $fieldName => $fakerConfig) {
            $value = $this->databasePlatform->quoteStringLiteral($record[$fieldName]);
            $columnName = $metadata->getColumnName($fieldName);
            $sets[] = "$columnName = $value";
        }

        return sprintf($this->updateSQLTemplate,
            $tableName,
            implode(', ', $sets),
            $record['id']
        );
    }

    /**
     * @throws \Doctrine\DBAL\ConnectionException
     */
    public function lancerScript(string $scriptPath): RunSQLResult
    {
        Assert::readable($scriptPath, "Le script '$scriptPath' n'existe pas ou n'est pas lisible");

        $conn = $this->entityManager->getConnection();
        $conn->beginTransaction();

        $p = new RunSQLProcess();
        $p->setConnection($conn);
        $p->setScriptPath($scriptPath);
        $p->setQueriesSplitPattern("#;\n#m"); // point virgule puis un retour
        $p->setLogFilePath($scriptPath . '.log');
        $p->setLogger((new Logger())->addWriter(new Stream('php://stdout')));
        $result = $p->executeScript();

        try {
            $conn->commit();
        } catch (ConnectionException $e) {
            $conn->rollBack();
            throw $e;
        }

        return $result;
    }
}
 No newline at end of file
Loading