Skip to content

Commit

Permalink
sanitize suivi description #3580
Browse files Browse the repository at this point in the history
  • Loading branch information
numew committed Jan 20, 2025
1 parent 8843a4a commit 079bc4a
Show file tree
Hide file tree
Showing 10 changed files with 129 additions and 20 deletions.
26 changes: 26 additions & 0 deletions migrations/Version20250120141313.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

declare(strict_types=1);

namespace DoctrineMigrations;

use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;

final class Version20250120141313 extends AbstractMigration
{
public function getDescription(): string
{
return 'Add is_sanitized column to suivi';
}

public function up(Schema $schema): void
{
$this->addSql('ALTER TABLE suivi ADD is_sanitized TINYINT(1) NOT NULL');
}

public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE suivi DROP is_sanitized');
}
}
61 changes: 61 additions & 0 deletions src/Command/SanitizeSuivisCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php

namespace App\Command;

use App\Manager\HistoryEntryManager;
use App\Repository\SuiviRepository;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\HtmlSanitizer\HtmlSanitizerInterface;

#[AsCommand(
name: 'app:sanitize-suivis',
description: 'Sanitize suivis',
)]
class SanitizeSuivisCommand extends Command
{
private const int BATCH_SIZE = 1000;

public function __construct(
private readonly SuiviRepository $suiviRepository,
private readonly EntityManagerInterface $entityManager,
#[Autowire(service: 'html_sanitizer.sanitizer.app.message_sanitizer')]
private readonly HtmlSanitizerInterface $htmlSanitizer,
private readonly HistoryEntryManager $historyEntryManager,
) {
parent::__construct();
}

protected function execute(InputInterface $input, OutputInterface $output): int
{
$this->historyEntryManager->removeEntityListeners();
$io = new SymfonyStyle($input, $output);
$countAll = $this->suiviRepository->count(['isSanitized' => false]);
$io->info(sprintf('Found %s suivis to sanitize', $countAll));
$suivis = $this->suiviRepository->findBy(['isSanitized' => false], ['createdAt' => 'DESC'], 50000);
$i = 0;
$progressBar = new ProgressBar($output, \count($suivis));
$progressBar->start();
foreach ($suivis as $suivi) {
$suivi->setDescription($this->htmlSanitizer->sanitize($suivi->getDescription(false, false)));
$suivi->setIsSanitized(true);
++$i;
$progressBar->advance();
if (0 === $i % self::BATCH_SIZE) {
$this->entityManager->flush();
}
}
$this->entityManager->flush();
$progressBar->finish();
$io->newLine();
$io->success(sprintf('Sanitized %s/%s suivis', $i, $countAll));

return Command::SUCCESS;
}
}
21 changes: 20 additions & 1 deletion src/Entity/Suivi.php
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,14 @@ class Suivi implements EntityHistoryInterface
#[ORM\Column(nullable: true)]
private ?array $originalData = null;

#[ORM\Column]
private ?bool $isSanitized = null;

public function __construct()
{
$this->createdAt = new \DateTimeImmutable();
$this->isPublic = false;
$this->isSanitized = true;
}

public function getId(): ?int
Expand Down Expand Up @@ -142,8 +146,11 @@ public function getCreatedByLabel(): ?string
return 'OCCUPANT : '.strtoupper($this->getSignalement()->getNomOccupant()).' '.ucfirst($this->getSignalement()->getPrenomOccupant());
}

public function getDescription($transformHtml = true): ?string
public function getDescription($transformHtml = true, $originalData = false): ?string
{
if ($originalData) {
return $this->description;
}
if (null !== $this->deletedAt) {
return self::DESCRIPTION_DELETED.' '.$this->deletedAt->format('d/m/Y');
}
Expand Down Expand Up @@ -262,4 +269,16 @@ public function getHistoryRegisteredEvent(): array
{
return [HistoryEntryEvent::CREATE, HistoryEntryEvent::UPDATE, HistoryEntryEvent::DELETE];
}

public function getIsSanitized(): ?bool
{
return $this->isSanitized;
}

public function setIsSanitized(bool $isSanitized): static
{
$this->isSanitized = $isSanitized;

return $this;
}
}
6 changes: 5 additions & 1 deletion src/Manager/SuiviManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
use App\Service\Sanitizer;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\HtmlSanitizer\HtmlSanitizerInterface;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;

class SuiviManager extends Manager
Expand All @@ -24,6 +26,8 @@ public function __construct(
private readonly SignalementUpdatedListener $signalementUpdatedListener,
private readonly Security $security,
private readonly DesordreCritereRepository $desordreCritereRepository,
#[Autowire(service: 'html_sanitizer.sanitizer.app.message_sanitizer')]
private readonly HtmlSanitizerInterface $htmlSanitizer,
string $entityName = Suivi::class,
) {
parent::__construct($managerRegistry, $entityName);
Expand All @@ -42,7 +46,7 @@ public function createSuivi(
$suivi = (new Suivi())
->setCreatedBy($user)
->setSignalement($signalement)
->setDescription($description)
->setDescription($this->htmlSanitizer->sanitize($description))
->setType($type)
->setIsPublic($isPublic)
->setContext($context)
Expand Down
8 changes: 4 additions & 4 deletions templates/back/notifications/index.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,10 @@
</td>
<td>{{ notification.suivi.createdAt|format_datetime(locale='fr', timezone=territory_timezone, pattern='d MMMM yyyy à HH:mm:ss') }}</td>
<td class="word-wrap-anywhere">{{ notification.suivi.description
|replace({'&t=___TOKEN___':'/'~notification.signalement.uuid})
|replace({'?t=___TOKEN___':'/'~notification.signalement.uuid})
|replace({'?folder=_up':'/'~notification.signalement.uuid~'?variant=resize'})
|sanitize_html('app.message_sanitizer') }}
|replace({'&amp;t&#61;___TOKEN___':'/'~notification.signalement.uuid})
|replace({'?t&#61;___TOKEN___':'/'~notification.signalement.uuid})
|replace({'?folder&#61;_up':'/'~notification.signalement.uuid~'?variant=resize'})
|raw }}
</td>
<td>{{ notification.suivi.createdBy ? notification.suivi.createdBy.nomComplet : notification.signalement.nomOccupant|upper~' '~notification.signalement.prenomOccupant|capitalize }}</td>
<td class="fr-text--right fr-ws-nowrap">
Expand Down
14 changes: 4 additions & 10 deletions templates/back/signalement/view/suivis.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -61,17 +61,11 @@
{% else %}
<div class="fr-col-7 bloc-suivi-content-row fr-pl-3v">
{% endif %}
{#{{ dump(suivi.description
|replace({'&t=___TOKEN___':'/'~signalement.uuid})
|replace({'?t=___TOKEN___':'/'~signalement.uuid})
|replace({'?folder=_up':'/'~signalement.uuid~'?variant=resize'})
|sanitize_html('app.message_sanitizer'))
}}#}
{{ suivi.description
|replace({'&t=___TOKEN___':'/'~signalement.uuid})
|replace({'?t=___TOKEN___':'/'~signalement.uuid})
|replace({'?folder=_up':'/'~signalement.uuid~'?variant=resize'})
|sanitize_html('app.message_sanitizer')
|replace({'&amp;t&#61;___TOKEN___':'/'~signalement.uuid})
|replace({'?t&#61;___TOKEN___':'/'~signalement.uuid})
|replace({'?foldert&#61_up':'/'~signalement.uuid~'?variant=resize'})
|raw
}}
</div>
<div class="fr-col-2">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
</strong>
</div>
<div class="message-box-message">
{{ suivi.description|replace({'___TOKEN___':csrf_token('suivi_signalement_ext_file_view')})|sanitize_html('app.message_sanitizer') }}
{{ suivi.description|replace({'___TOKEN___':csrf_token('suivi_signalement_ext_file_view')})|raw }}
</div>
</div>
{% endfor %}
Expand Down
2 changes: 1 addition & 1 deletion templates/pdf/signalement.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -625,7 +625,7 @@
{% endif %}
<small>{{ suivi.createdAt|date('d/m/Y') }}</small>
</td>
<td style="padding:.5rem;width:85%"> {{ suivi.description|sanitize_html('app.message_sanitizer') }}</td>
<td style="padding:.5rem;width:85%"> {{ suivi.description|raw }}</td>
</tr>
{% endfor %}
</table>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ public function testDeleteSuivi(): void
$route = $this->router->generate('back_signalement_delete_suivi', ['uuid' => $signalement->getUuid()]);
$this->client->request('GET', $route);

$description = 'Un petit message de rappel afin d\'y revenir plus tard';
$description = 'Un petit message de rappel afin d&#039;y revenir plus tard';
$suivi = $this->suiviRepository->findOneBy(['description' => $description]);

$this->client->request(
Expand Down
7 changes: 6 additions & 1 deletion tests/Functional/Manager/SuiviManagerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\HtmlSanitizer\HtmlSanitizerInterface;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Core\User\UserInterface;

Expand All @@ -24,6 +25,7 @@ class SuiviManagerTest extends KernelTestCase
private Security $security;
private UrlGeneratorInterface $urlGenerator;
private DesordreCritereRepository $desordreCritereRepository;
private HtmlSanitizerInterface $htmlSanitizerInterface;

protected function setUp(): void
{
Expand All @@ -33,6 +35,7 @@ protected function setUp(): void
$this->security = static::getContainer()->get(Security::class);
$this->urlGenerator = static::getContainer()->get(UrlGeneratorInterface::class);
$this->desordreCritereRepository = static::getContainer()->get(DesordreCritereRepository::class);
$this->htmlSanitizerInterface = static::getContainer()->get(HtmlSanitizerInterface::class);
}

public function testCreateSuivi(): void
Expand All @@ -43,6 +46,7 @@ public function testCreateSuivi(): void
$this->signalementUpdatedListener,
$this->security,
$this->desordreCritereRepository,
$this->htmlSanitizerInterface,
Suivi::class,
);

Expand Down Expand Up @@ -74,9 +78,10 @@ public function testCreateSuivi(): void
$this->assertEquals(Suivi::TYPE_PARTNER, $suivi->getType());
$this->assertNotEquals($countSuivisBeforeCreate, $countSuivisAfterCreate);
$this->assertInstanceOf(Suivi::class, $suivi);
$desc = 'Le signalement a été cloturé pour test avec le motif suivant <br><strong>Non décence</strong><br><strong>Desc. : </strong>Lorem ipsum suivi sit amet, consectetur adipiscing elit.';
$desc = 'Le signalement a été cloturé pour test avec le motif suivant <br /><strong>Non décence</strong><br /><strong>Desc. : </strong>Lorem ipsum suivi sit amet, consectetur adipiscing elit.';
$this->assertEquals($desc, $suivi->getDescription());
$this->assertTrue($suivi->getIsPublic());
$this->assertTrue($suivi->getIsSanitized());
$this->assertInstanceOf(UserInterface::class, $suivi->getCreatedBy());
}
}

0 comments on commit 079bc4a

Please sign in to comment.