- A-01/A-02/A-04: archive_pending_jobs je Vorgang (ArchivePendingQueue), Depublikationsstand am Auftrag festgehalten, Nachholer ohne Schlüssel FAILURE; archive_pending_at nur noch Sammelmarker - O07: Direktweg und Nachholer beanspruchen den Auftrag unter Zeilensperre - O08: fehlende Snapshot-Datei → Verweis auf den image_removed-Eintrag oder offener Auftrag, nie ein still unvollständiger Nachweis - A-03/A-06: Sweep verschiebt in die Quarantäne, Pause beim Restore, archive:reregister; Restore-Abgleich löscht Anwendungskopien erneut - O04/O05: Restore ordnet über den Payload-Pfad zu; unsignierte Exporte nur mit --accept-unsigned, reregister bricht bei ungültigem/fehlendem Export ab - A-05, A-07 bis A-15: Altbestand als möglicherweise veröffentlicht, Hold- Historie, Zweck per POST, Nachweise gelöschter Bilder nur mit compliance:manage, Anhänge auf ihrer Disk, Schlüssel-ID und ARCHIVE_PREVIOUS_KEYS, HMAC per HKDF, archive:verify-export - 5.1 d: Depublikation im Meldeverfahren trägt legal_request_id PressReleaseService.php enthält zusätzlich K-01 (Prüfung im Publish-Lock), N19 (archive() gibt Kopien frei) und N09 (markImagesPublished nur für auslieferbare Bilder), weil die Datei ungeteilt committet ist. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
60 lines
2.5 KiB
PHP
60 lines
2.5 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers\Admin;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use App\Models\ArchiveEntry;
|
|
use App\Services\Archive\EvidenceArchive;
|
|
use Illuminate\Contracts\View\View;
|
|
use Illuminate\Http\Request;
|
|
use Symfony\Component\HttpFoundation\Response;
|
|
|
|
/**
|
|
* Öffnen und Export von Archiveinträgen (Beweisarchiv F.2): GET zeigt ein
|
|
* kleines Formular, erst der POST mit Zweck (CSRF) liefert den Download,
|
|
* und der Zugriff steht im Protokoll. Der Zweck steht nie in der URL und
|
|
* damit nicht in Server-Logs (Umsetzungsaudit 29.09.2026, A-08).
|
|
*/
|
|
class ArchiveEntryController extends Controller
|
|
{
|
|
public function __construct(private readonly EvidenceArchive $archive) {}
|
|
|
|
public function download(Request $request, ArchiveEntry $entry): Response|View
|
|
{
|
|
return $this->serve($request, $entry, 'download');
|
|
}
|
|
|
|
public function export(Request $request, ArchiveEntry $entry): Response|View
|
|
{
|
|
return $this->serve($request, $entry, 'export');
|
|
}
|
|
|
|
private function serve(Request $request, ArchiveEntry $entry, string $action): Response|View
|
|
{
|
|
abort_if($entry->isTombstoned(), 410, 'Der Eintrag ist nach Fristablauf gelöscht (Tombstone).');
|
|
|
|
$purpose = $request->isMethod('post') ? trim((string) $request->request->get('purpose', '')) : '';
|
|
|
|
if ($purpose === '') {
|
|
return view('admin.archive.purpose', [
|
|
'formAction' => route($action === 'export' ? 'admin.archive.export' : 'admin.archive.download', ['entry' => $entry->id]),
|
|
'description' => __('Archiveintrag #:id (:event). Jeder Zugriff auf das Beweisarchiv wird mit Zweck protokolliert.', ['id' => $entry->id, 'event' => $entry->event->label()]),
|
|
'submitLabel' => $action === 'export' ? __('Export herunterladen') : __('Payload öffnen'),
|
|
'cancelUrl' => route('admin.archive.index'),
|
|
]);
|
|
}
|
|
|
|
$bytes = $action === 'export'
|
|
? $this->archive->export($entry, $request->user(), $purpose)
|
|
: $this->archive->open($entry, $request->user(), $purpose);
|
|
|
|
$name = sprintf('beweisarchiv-%d-%s.zip', $entry->getKey(), $action === 'export' ? 'export' : 'payload');
|
|
|
|
return response($bytes, 200, [
|
|
'Content-Type' => 'application/zip',
|
|
'Content-Disposition' => 'attachment; filename="'.$name.'"',
|
|
'Cache-Control' => 'private, no-store',
|
|
'X-Content-Type-Options' => 'nosniff',
|
|
]);
|
|
}
|
|
}
|