Compare commits
2 Commits
3c5bded19c
...
dac84f024a
| Author | SHA1 | Date |
|---|---|---|
|
|
dac84f024a | |
|
|
60c501e40d |
|
|
@ -54,6 +54,13 @@ final class Navigation
|
||||||
['admin.maintenance', 'alert-triangle', 'maintenance', null],
|
['admin.maintenance', 'alert-triangle', 'maintenance', null],
|
||||||
['admin.incidents', 'bell', 'incidents', null],
|
['admin.incidents', 'bell', 'incidents', null],
|
||||||
['admin.capacity', 'database', 'capacity', 'hosts.manage'],
|
['admin.capacity', 'database', 'capacity', 'hosts.manage'],
|
||||||
|
// Hostnamen und ihre Zertifikate. Unter Betrieb und nicht unter
|
||||||
|
// System: was hier steht, entscheidet, ob eine Adresse
|
||||||
|
// ANTWORTET — das ist kein Einrichten, das ist Betrieb. Ohne
|
||||||
|
// diesen Eintrag war die Seite zwar vorhanden, aber über die
|
||||||
|
// Navigation nicht erreichbar, und eine Seite, die man nur mit
|
||||||
|
// der URL findet, gibt es für den Betreiber nicht.
|
||||||
|
['admin.proxy-hosts', 'globe', 'proxy_hosts', 'site.manage'],
|
||||||
['admin.revenue', 'trending-up', 'revenue', null],
|
['admin.revenue', 'trending-up', 'revenue', null],
|
||||||
// Its own entry, not a section of Settings: what is set here
|
// Its own entry, not a section of Settings: what is set here
|
||||||
// appears on a legal document, and burying it beside the
|
// appears on a legal document, and burying it beside the
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ return [
|
||||||
],
|
],
|
||||||
|
|
||||||
'nav' => [
|
'nav' => [
|
||||||
|
'proxy_hosts' => 'Hostnamen',
|
||||||
'dpa' => 'AV-Vertrag',
|
'dpa' => 'AV-Vertrag',
|
||||||
'mail_preview' => 'E-Mail-Vorschau',
|
'mail_preview' => 'E-Mail-Vorschau',
|
||||||
'inbox' => 'Posteingang',
|
'inbox' => 'Posteingang',
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ return [
|
||||||
],
|
],
|
||||||
|
|
||||||
'nav' => [
|
'nav' => [
|
||||||
|
'proxy_hosts' => 'Hostnames',
|
||||||
'dpa' => 'Processing agreement',
|
'dpa' => 'Processing agreement',
|
||||||
'mail_preview' => 'Mail preview',
|
'mail_preview' => 'Mail preview',
|
||||||
'inbox' => 'Inbox',
|
'inbox' => 'Inbox',
|
||||||
|
|
|
||||||
|
|
@ -182,6 +182,22 @@ if ($filesHost !== '') {
|
||||||
Route::domain($filesHost)->group(function () {
|
Route::domain($filesHost)->group(function () {
|
||||||
Route::get('/bootstrap.tar.gz', BootstrapArchiveController::class)->name('bootstrap.archive');
|
Route::get('/bootstrap.tar.gz', BootstrapArchiveController::class)->name('bootstrap.archive');
|
||||||
Route::get('/{file}', PublicFileController::class)->name('files.public');
|
Route::get('/{file}', PublicFileController::class)->name('files.public');
|
||||||
|
|
||||||
|
// Und sonst NICHTS. Beide Zeilen sind nötig, weil sie zwei verschiedene
|
||||||
|
// Löcher schließen: `/` trifft der Platzhalter oben nicht (er verlangt
|
||||||
|
// ein Segment), und ein mehrteiliger Pfad ebenfalls nicht.
|
||||||
|
//
|
||||||
|
// Ohne sie fiel eine Anfrage an diesen Hostnamen auf die
|
||||||
|
// host-unabhängigen Routen durch und landete beim Portal — wer
|
||||||
|
// `files.clupilot.com` aufrief, wurde nach `app.` weitergeleitet. Das
|
||||||
|
// ist nicht nur unschön: es sagt jedem, der den Namen probiert, wo das
|
||||||
|
// Portal liegt und dass beide dieselbe Maschine sind.
|
||||||
|
//
|
||||||
|
// 404 statt Weiterleitung, dieselbe Haltung wie überall sonst in dieser
|
||||||
|
// Datei: eine Adresse, die nichts anzubieten hat, hat auch nichts zu
|
||||||
|
// erzählen.
|
||||||
|
Route::get('/', fn () => abort(404))->name('files.root');
|
||||||
|
Route::any('/{any}', fn () => abort(404))->where('any', '.*');
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
Route::get('/bootstrap.tar.gz', BootstrapArchiveController::class)->name('bootstrap.archive');
|
Route::get('/bootstrap.tar.gz', BootstrapArchiveController::class)->name('bootstrap.archive');
|
||||||
|
|
|
||||||
|
|
@ -147,3 +147,31 @@ it('serves downloads even while the site is hidden', function () {
|
||||||
// hätte die Ausnahme mehr aufgemacht als sie sollte.
|
// hätte die Ausnahme mehr aufgemacht als sie sollte.
|
||||||
$this->withServerVariables($vonAussen)->get('http://localhost/')->assertStatus(503);
|
$this->withServerVariables($vonAussen)->get('http://localhost/')->assertStatus(503);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Der Datei-Hostname bietet genau zwei Dinge an — und sonst nichts.
|
||||||
|
*
|
||||||
|
* Ohne diese Grenze fiel jede andere Anfrage auf die host-unabhängigen Routen
|
||||||
|
* durch und landete beim Portal: wer `files.…` aufrief, wurde nach `app.…`
|
||||||
|
* weitergeleitet. Das sagt jedem, der den Namen probiert, wo das Portal liegt
|
||||||
|
* und dass beide auf derselben Maschine sitzen.
|
||||||
|
*/
|
||||||
|
it('offers nothing but the archive and the public files', function () {
|
||||||
|
foreach (['/', '/dashboard', '/login', '/admin', '/tief/verschachtelt/pfad'] as $pfad) {
|
||||||
|
$antwort = $this->get('http://files.clupilot.test'.$pfad);
|
||||||
|
|
||||||
|
$antwort->assertNotFound();
|
||||||
|
// Und ausdrücklich KEINE Weiterleitung: eine 302 auf app.… wäre genau
|
||||||
|
// der Hinweis, den dieser Hostname nicht geben soll.
|
||||||
|
expect($antwort->headers->get('Location'))->toBeNull();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('still serves its two things after that boundary', function () {
|
||||||
|
file_put_contents($this->publicDir.'/av-2026-01.pdf', '%PDF-1.4');
|
||||||
|
|
||||||
|
$this->get('http://files.clupilot.test/av-2026-01.pdf')->assertOk();
|
||||||
|
$this->get('http://files.clupilot.test/bootstrap.tar.gz?code='.issuedCode())->assertOk();
|
||||||
|
|
||||||
|
unlink($this->publicDir.'/av-2026-01.pdf');
|
||||||
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue