Integrate audit functionality across multiple controllers and services for WebDAV, WOPI, and Async Upload

- Added `TriggerAuditInterface` to `WebdavController`, `ConvertController`, and `AsyncUploadController`, with audit triggers for create, update, view, and convert actions using translatable messages.
- Enhanced `ChillWopi` service with audit triggers for file operations (view, update) and integrated French translations for audit labels.
- Updated `messages.fr.yml` to include new translatable keys for audit actions in both WOPI and WebDAV contexts.
- Registered `TriggerAuditInterface` as a dependency in modified classes to enable audit tracking functionality.
This commit is contained in:
2026-03-02 17:31:19 +01:00
parent 2313b053d4
commit 64f6fc5306
6 changed files with 93 additions and 3 deletions

View File

@@ -15,6 +15,8 @@ use Chill\DocStoreBundle\AsyncUpload\TempUrlGeneratorInterface;
use Chill\DocStoreBundle\Entity\StoredObject;
use Chill\DocStoreBundle\Entity\StoredObjectVersion;
use Chill\DocStoreBundle\Security\Authorization\StoredObjectRoleEnum;
use Chill\MainBundle\Audit\TriggerAuditInterface;
use Chill\MainBundle\Entity\AuditTrail;
use Chill\MainBundle\Entity\User;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
@@ -25,6 +27,7 @@ use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
use Symfony\Component\Serializer\SerializerInterface;
use Symfony\Component\Translation\TranslatableMessage;
final readonly class AsyncUploadController
{
@@ -33,6 +36,7 @@ final readonly class AsyncUploadController
private SerializerInterface $serializer,
private Security $security,
private LoggerInterface $chillLogger,
private TriggerAuditInterface $triggerAudit,
) {}
#[Route(path: '/api/1.0/doc-store/async-upload/temp_url/{uuid}/generate/post', name: 'chill_docstore_asyncupload_getsignedurlpost')]
@@ -56,6 +60,13 @@ final readonly class AsyncUploadController
'doc_uuid' => $storedObject->getUuid(),
]);
$this->triggerAudit->triggerAudit(
AuditTrail::AUDIT_VIEW,
$storedObject,
description: new TranslatableMessage('audit.stored_object.write_by_ui'),
metadata: ['so_write' => 'signed_url'],
);
return new JsonResponse(
$this->serializer->serialize($p, 'json', [AbstractNormalizer::GROUPS => ['read']]),
Response::HTTP_OK,
@@ -111,6 +122,13 @@ final readonly class AsyncUploadController
'user_id' => $userId,
]);
$this->triggerAudit->triggerAudit(
AuditTrail::AUDIT_VIEW,
$storedObject,
description: new TranslatableMessage('audit.stored_object.read_by_ui'),
metadata: ['so_read' => 'signed_url'],
);
return new JsonResponse(
$this->serializer->serialize($p, 'json', [AbstractNormalizer::GROUPS => ['read']]),
Response::HTTP_OK,

View File

@@ -16,6 +16,8 @@ use Chill\DocStoreBundle\Dav\Response\DavResponse;
use Chill\DocStoreBundle\Entity\StoredObject;
use Chill\DocStoreBundle\Security\Authorization\StoredObjectRoleEnum;
use Chill\DocStoreBundle\Service\StoredObjectManagerInterface;
use Chill\MainBundle\Audit\TriggerAuditInterface;
use Chill\MainBundle\Entity\AuditTrail;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
@@ -23,6 +25,7 @@ use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Translation\TranslatableMessage;
/**
* Provide endpoint for editing a document on the desktop using dav.
@@ -44,6 +47,7 @@ final readonly class WebdavController
private StoredObjectManagerInterface $storedObjectManager,
private Security $security,
private EntityManagerInterface $entityManager,
private TriggerAuditInterface $triggerAudit,
) {
$this->requestAnalyzer = new PropfindRequestAnalyzer();
}
@@ -122,6 +126,13 @@ final readonly class WebdavController
throw new AccessDeniedHttpException();
}
$this->triggerAudit->triggerAudit(
AuditTrail::AUDIT_VIEW,
$storedObject,
description: new TranslatableMessage('audit.stored_object_webdav.read_by_webdav'),
metadata: ['so_read' => 'webdav'],
);
return (new DavResponse($this->storedObjectManager->read($storedObject)))
->setEtag($this->storedObjectManager->etag($storedObject));
}
@@ -205,6 +216,13 @@ final readonly class WebdavController
$this->entityManager->flush();
$this->triggerAudit->triggerAudit(
AuditTrail::AUDIT_UPDATE,
$storedObject,
description: new TranslatableMessage('audit.stored_object_webdav.update_by_webdav'),
metadata: ['so_update' => 'webdav'],
);
return new DavResponse('', Response::HTTP_NO_CONTENT);
}

View File

@@ -133,8 +133,13 @@ audit:
stored_object:
display: Document n°{id}
display_with_title: Document n°{id} - {title}
read_by_ui: Téléchargement par l'interface web
write_by_ui: Écriture par l'interface web
generic-doc:
list_for_accompanying_period: Liste des documents d'un parcours d'accompagnement
list_for_person: Liste des documents de l'usager
accompanying_course_document:
display: Document d'un parcours d'accompagnement n°{id}
stored_object_webdav:
read_by_webdav: Lecture par WebDAV
update_by_webdav: Mise à jour par WebDAV

View File

@@ -14,12 +14,15 @@ namespace Chill\WopiBundle\Controller;
use Chill\DocStoreBundle\Entity\StoredObject;
use Chill\DocStoreBundle\Security\Authorization\StoredObjectRoleEnum;
use Chill\DocStoreBundle\Service\StoredObjectManagerInterface;
use Chill\MainBundle\Audit\TriggerAuditInterface;
use Chill\MainBundle\Entity\AuditTrail;
use Chill\WopiBundle\Service\WopiConverter;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Translation\TranslatableMessage;
class ConvertController
{
@@ -30,6 +33,7 @@ class ConvertController
private readonly StoredObjectManagerInterface $storedObjectManager,
private readonly WopiConverter $wopiConverter,
private readonly LoggerInterface $logger,
private readonly TriggerAuditInterface $triggerAudit,
) {}
public function __invoke(StoredObject $storedObject, Request $request): Response
@@ -51,6 +55,12 @@ class ConvertController
$lang = $request->getLocale();
try {
$this->triggerAudit->triggerAudit(
AuditTrail::AUDIT_VIEW,
$storedObject,
description: new TranslatableMessage('audit.stored_object_wopi.convert_to_pdf')
);
return new Response($this->wopiConverter->convert($lang, $content, $storedObject->getType()), Response::HTTP_OK, [
'Content-Type' => 'application/pdf',
]);

View File

@@ -12,21 +12,41 @@ declare(strict_types=1);
namespace Chill\WopiBundle\Service\Wopi;
use ChampsLibres\WopiLib\Contract\Service\WopiInterface;
use Chill\MainBundle\Audit\TriggerAuditInterface;
use Chill\MainBundle\Entity\AuditTrail;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Symfony\Component\Translation\TranslatableMessage;
final readonly class ChillWopi implements WopiInterface
{
public function __construct(private WopiInterface $wopi) {}
public function __construct(
private WopiInterface $wopi,
private TriggerAuditInterface $triggerAudit,
private ChillDocumentManager $documentManager,
) {}
public function checkFileInfo(
string $fileId,
?string $accessToken,
RequestInterface $request,
): ResponseInterface {
return $this->wopi->checkFileInfo($fileId, $accessToken, $request, [
$response = $this->wopi->checkFileInfo($fileId, $accessToken, $request, [
'SupportsRename' => false,
]);
$document = $this->documentManager->findByDocumentId($fileId);
$this->triggerAudit->triggerAudit(
AuditTrail::AUDIT_VIEW,
$document,
description: new TranslatableMessage(
'audit.stored_object_wopi.view_through_wopi'
),
metadata: ['so_read' => 'wopi'],
);
return $response;
}
public function deleteFile(string $fileId, ?string $accessToken, RequestInterface $request): ResponseInterface
@@ -76,7 +96,20 @@ final readonly class ChillWopi implements WopiInterface
string $xWopiEditors,
RequestInterface $request,
): ResponseInterface {
return $this->wopi->putFile($fileId, $accessToken, $xWopiLock, $xWopiEditors, $request);
$response = $this->wopi->putFile($fileId, $accessToken, $xWopiLock, $xWopiEditors, $request);
$document = $this->documentManager->findByDocumentId($fileId);
$this->triggerAudit->triggerAudit(
AuditTrail::AUDIT_UPDATE,
$document,
description: new TranslatableMessage(
'audit.stored_object_wopi.update_through_wopi'
),
metadata: ['so_update' => 'wopi']
);
return $response;
}
public function putRelativeFile(string $fileId, string $accessToken, ?string $suggestedTarget, ?string $relativeTarget, bool $overwriteRelativeTarget, int $size, RequestInterface $request): ResponseInterface

View File

@@ -1,2 +1,8 @@
wopi_editor:
document unsupported for edition: Ce format de document n'est pas éditable
audit:
stored_object_wopi:
convert_to_pdf: 'Conversion du document en PDF'
update_through_wopi: 'Mise à jour du document par WOPI'
view_through_wopi: 'Visualisation du document par WOPI'