getMethod() === 'GET') { return (new NullResponse())->withStatus(400); } if (!$this->isSystemMaintainer()) { return (new NullResponse())->withStatus(403); } return $this->dispatchAction($request) ?? (new NullResponse())->withStatus(500); } protected function dispatchAction(ServerRequestInterface $request): ?ResponseInterface { $parsedBody = $request->getParsedBody(); $hmac = $parsedBody['hmac'] ?? null; $action = $parsedBody['action'] ?? null; $summaries = $parsedBody['summaries'] ?? []; $scope = Scope::tryFrom($parsedBody['scope'] ?? ''); $uuid = $parsedBody['uuid'] ?? null; if ($uuid !== null) { $uuid = Uuidv4::fromString($uuid); } if (!empty($parsedBody['suggestion'])) { $suggestion = $this->modelService->buildMutationSuggestionFromArray($parsedBody['suggestion']); } // reports if ($action === 'fetchReports') { return $this->fetchReportsAction($scope); } if ($action === 'muteReport' && is_array($summaries)) { return $this->muteReportAction(...$summaries); } if ($action === 'deleteReport' && is_array($summaries)) { return $this->deleteReportAction(...$summaries); } if ($action === 'deleteReports') { return $this->deleteReportsAction($scope); } if ($action === 'handleReport' && $uuid !== null) { return $this->handleReportAction($uuid); } if ($action === 'mutateReport' && $scope !== null && is_array($summaries) && isset($suggestion) && hash_equals($suggestion->hmac(), $hmac) ) { return $this->mutateReportAction($scope, $suggestion, ...$summaries); } return null; } protected function fetchReportsAction(?Scope $scope): ResponseInterface { $demand = ReportDemand::create(); $demand->scope = $scope; $reports = $this->reportRepository->findAllSummarized($demand); // @todo not sure whether this is a good idea performance-wise $reports = array_map( function (SummarizedReport $report): SummarizedReport { $event = $this->dispatchInvestigateMutationsEvent($report); if ($event->getMutationSuggestions() !== []) { $mutationHashes = array_map( static fn(MutationSuggestion $suggestion): string => $suggestion->hash(), $event->getMutationSuggestions() ); $report = $report->withMutationHashes(...$mutationHashes) ->withAttribute(ReportAttribute::fixable); } return $report; }, $reports ); return new JsonResponse($reports); } protected function muteReportAction(string ...$summaries): ResponseInterface { $reports = $this->reportRepository->findBySummary(...$summaries); $uuids = array_map(static fn(Report $report): UuidV4 => $report->uuid, $reports); $this->reportRepository->updateStatus(ReportStatus::Muted, ...$uuids); return new JsonResponse(['uuids' => $uuids]); } protected function deleteReportAction(string ...$summaries): ResponseInterface { $reports = $this->reportRepository->findBySummary(...$summaries); $reportUuids = $this->resolveReportUuids(...$reports); $this->reportRepository->updateStatus(ReportStatus::Deleted, ...$reportUuids); return new JsonResponse(['uuids' => $reportUuids]); } protected function deleteReportsAction(?Scope $scope): ResponseInterface { $amount = $this->reportRepository->removeAll($scope); return new JsonResponse(['amount' => $amount]); } protected function handleReportAction(UuidV4 $uuid): ResponseInterface { $report = $this->reportRepository->findByUuid($uuid); if ($report === null) { return new JsonResponse(); } $event = $this->dispatchInvestigateMutationsEvent($report); $suggestions = $event->getMutationSuggestions(); // reverse sort by priority (higher priorities take precedence) usort($suggestions, static fn(MutationSuggestion $a, MutationSuggestion $b) => $b->priority <=> $a->priority); return new JsonResponse($suggestions); } protected function mutateReportAction(Scope $scope, MutationSuggestion $suggestion, string ...$initiators): ResponseInterface { $summary = $this->generateResolutionSummary($scope, $suggestion); $resolution = $this->resolutionRepository->findBySummary($summary); $reports = $this->reportRepository->findBySummary(...$initiators); if ($resolution !== null || $reports === []) { return new JsonResponse(); } $resolution = new Resolution($summary, $scope, $suggestion->identifier, $suggestion->collection, ['initiators' => $initiators]); $this->resolutionRepository->add($resolution); $reportUuids = $this->resolveReportUuids(...$reports); $this->reportRepository->updateStatus(ReportStatus::Handled, ...$reportUuids); return new JsonResponse(['initiators' => $initiators, 'uuids' => $reportUuids]); } protected function dispatchInvestigateMutationsEvent(Report $report): InvestigateMutationsEvent { // @todo for future versions, it might be considered to distinguish `enforce` and `report` in the database $policy = $this->policyProvider->provideFor($report->scope, $report->details->resolveDisposition()); $event = new InvestigateMutationsEvent($policy, $report); $this->eventDispatcher->dispatch($event); return $event; } protected function generateResolutionSummary(Scope $scope, MutationSuggestion $suggestion): string { return $this->hashService->hmac( json_encode([ $scope, $suggestion->identifier, $suggestion->collection, ]), self::class, ); } protected function resolveReportUuids(Report ...$reports): array { return array_map(static fn(Report $report): UuidV4 => $report->uuid, $reports); } protected function isSystemMaintainer(): bool { $backendUser = $GLOBALS['BE_USER'] ?? null; return $backendUser instanceof BackendUserAuthentication && $backendUser->isSystemMaintainer(); } }