- archive_entries (append-only) + archive_access_log; Disk archive außerhalb des Web-Roots; eigener ARCHIVE_KEY (archive:key), AES-256-CBC, SHA-256 am Eintrag - EvidenceArchive: record/open/manifest/export (signiertes ZIP), setLegalHold/releaseLegalHold (Frist läuft neu), purgeDue, exportTombstones, reconcileAfterRestore - PressReleaseArchiver: image_removed (Original, Snapshot, Bestätigungen), pm_depublished (letzter veröffentlichter Stand), gdpr_change (Anonymisierung, Korrekturen), label_corrected - Hooks in Remover, depublish/archive/changeStatusFromAdmin/deleteFromAdmin, AnonymizationService, CorrectionService, ImageCorrectionService - Scheduler archive:purge 03:20 und archive:export-tombstones 03:30; archive:reconcile-after-restore im Runbook - Admin ▸ Management ▸ Beweisarchiv (Liste, Legal Hold mit Pflichtvermerk, Öffnen/Export nur mit Zweck) - Scope-Wächter: neue Befehle nutzen withoutGlobalScope(PortalScope::class) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
54 lines
1.8 KiB
PHP
54 lines
1.8 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): ohne Zweck
|
|
* zeigt die Route ein kleines Formular, mit Zweck kommt der Download und
|
|
* der Zugriff steht im Protokoll.
|
|
*/
|
|
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 = trim((string) $request->input('purpose', ''));
|
|
|
|
if ($purpose === '') {
|
|
return view('admin.archive.purpose', ['entry' => $entry, 'action' => $action]);
|
|
}
|
|
|
|
$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',
|
|
]);
|
|
}
|
|
}
|