fix(security) : harden ROLE_CLIENT isolation + tighten cross-module contracts
Findings from the post-migration code review. The arrival of ROLE_CLIENT exposed internal endpoints still guarded only by IS_AUTHENTICATED_FULLY (or no security), reachable by a client. Verified by re-running a multi-role smoke test (client -> 403, internal roles -> 200). Security (closed real client-isolation holes): - TaskDocumentDownloadController: add ownership check (admin all / client only own clientTicket docs / user only task-linked docs) — the custom download bypassed the cloistered provider. - Share browse/download/search/status controllers: IS_AUTHENTICATED_FULLY -> ROLE_USER (SMB share is internal). - User Get/GetCollection: add security ROLE_USER (was exposing the internal directory to clients). - BookStackLink GetCollection/Post/Delete: IS_AUTHENTICATED_FULLY -> ROLE_USER. Contracts / robustness: - TaskInterface gains getProject(): ?ProjectInterface; TimeTracking export controller/service drop concrete cross-module entities for repo interfaces. - Shared MCP Serializer signatures widened to the contracts (user/projectRef/ taskRef/tags/users); project()/userFull()/etc. kept concrete (use getters outside the contracts). - RecurrenceHandler: null-guard before findMaxNumberByProjectForUpdate(). 180 tests green, cs-fixer clean, routes unchanged.
This commit is contained in:
@@ -81,8 +81,12 @@ final readonly class RecurrenceHandler
|
||||
$newTask->setCalendarEventUid($savedEventUid);
|
||||
|
||||
// Generate task number in transaction
|
||||
$this->entityManager->wrapInTransaction(function () use ($newTask): void {
|
||||
$maxNumber = $this->taskRepository->findMaxNumberByProjectForUpdate($newTask->getProject());
|
||||
$project = $newTask->getProject();
|
||||
if (null === $project) {
|
||||
return;
|
||||
}
|
||||
$this->entityManager->wrapInTransaction(function () use ($newTask, $project): void {
|
||||
$maxNumber = $this->taskRepository->findMaxNumberByProjectForUpdate($project);
|
||||
$newTask->setNumber($maxNumber + 1);
|
||||
$this->entityManager->persist($newTask);
|
||||
$this->entityManager->flush();
|
||||
|
||||
+16
@@ -10,11 +10,13 @@ use App\Module\Integration\Domain\Service\FileSource;
|
||||
use App\Module\ProjectManagement\Domain\Entity\TaskDocument;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Bundle\SecurityBundle\Security;
|
||||
use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
||||
use Symfony\Component\HttpFoundation\HeaderUtils;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
|
||||
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
||||
@@ -26,6 +28,7 @@ class TaskDocumentDownloadController extends AbstractController
|
||||
public function __construct(
|
||||
private readonly EntityManagerInterface $entityManager,
|
||||
private readonly FileSource $fileSource,
|
||||
private readonly Security $security,
|
||||
private readonly string $uploadDir,
|
||||
) {}
|
||||
|
||||
@@ -39,6 +42,19 @@ class TaskDocumentDownloadController extends AbstractController
|
||||
throw new NotFoundHttpException('Document not found.');
|
||||
}
|
||||
|
||||
$isAdmin = $this->security->isGranted('ROLE_ADMIN');
|
||||
$isClient = $this->security->isGranted('ROLE_CLIENT') && !$isAdmin;
|
||||
if (!$isAdmin) {
|
||||
if ($isClient) {
|
||||
$ticket = $document->getClientTicket();
|
||||
if (null === $ticket || $ticket->getSubmittedBy() !== $this->security->getUser()) {
|
||||
throw new AccessDeniedHttpException();
|
||||
}
|
||||
} elseif (null === $document->getTask()) {
|
||||
throw new AccessDeniedHttpException();
|
||||
}
|
||||
}
|
||||
|
||||
$mimeType = $document->getMimeType() ?? 'application/octet-stream';
|
||||
|
||||
// Inline for images (except SVG) and PDFs, attachment for everything else.
|
||||
|
||||
Reference in New Issue
Block a user