121 lines
4.4 KiB
PHP
121 lines
4.4 KiB
PHP
<?php
|
|
|
|
use App\Models\Host;
|
|
use App\Support\HostEnrolment;
|
|
use App\Support\HostTakeoverCommand;
|
|
|
|
beforeEach(function () {
|
|
fakeServices();
|
|
// Hostname kommt aus phpunit.xml — Routen werden beim Booten gebunden.
|
|
|
|
$this->publicDir = storage_path('app/files/public');
|
|
if (! is_dir($this->publicDir)) {
|
|
mkdir($this->publicDir, 0755, true);
|
|
}
|
|
});
|
|
|
|
function issuedCode(): string
|
|
{
|
|
return HostEnrolment::issue(Host::factory()->create());
|
|
}
|
|
|
|
/**
|
|
* Der private Teil: ohne Ausweis gibt es das Archiv nicht — und zwar als 404,
|
|
* nicht als 403. Dieselbe Regel wie bei der Konsole: ein Fremder soll nicht
|
|
* erfahren, dass es hier etwas gibt.
|
|
*/
|
|
it('refuses the installer without a code', function () {
|
|
$this->get('http://files.clupilot.test/bootstrap.tar.gz')->assertNotFound();
|
|
$this->get('http://files.clupilot.test/bootstrap.tar.gz?code=erfunden')->assertNotFound();
|
|
});
|
|
|
|
it('serves the installer to a valid code', function () {
|
|
$response = $this->get('http://files.clupilot.test/bootstrap.tar.gz?code='.issuedCode());
|
|
|
|
$response->assertOk();
|
|
expect($response->headers->get('Content-Type'))->toBe('application/gzip')
|
|
->and($response->headers->get('X-Robots-Tag'))->toContain('noindex');
|
|
});
|
|
|
|
/**
|
|
* Der Code wird beim Herunterladen NICHT verbraucht. Er wird danach für jede
|
|
* Fortschrittsmeldung und zuletzt für die Registrierung gebraucht — ein
|
|
* Download, der ihn aufbraucht, macht die Übernahme unmöglich, und zwar
|
|
* nachdem der Server schon läuft.
|
|
*/
|
|
it('does not spend the code on the download', function () {
|
|
$code = issuedCode();
|
|
|
|
$this->get('http://files.clupilot.test/bootstrap.tar.gz?code='.$code)->assertOk();
|
|
$this->get('http://files.clupilot.test/bootstrap.tar.gz?code='.$code)->assertOk();
|
|
|
|
expect(HostEnrolment::claim($code))->not->toBeNull();
|
|
});
|
|
|
|
/**
|
|
* Der öffentliche Teil: AGB, AV, TOM. Anonym, ohne Ausweis, ohne Konto.
|
|
*/
|
|
it('serves public documents to anyone', function () {
|
|
file_put_contents($this->publicDir.'/agb-2026-01.pdf', '%PDF-1.4 Testfassung');
|
|
|
|
$response = $this->get('http://files.clupilot.test/agb-2026-01.pdf');
|
|
|
|
$response->assertOk();
|
|
|
|
// `response()->file()` streamt, der Rumpf steht also nicht im Ergebnis.
|
|
// Geprüft wird deshalb die ausgelieferte Datei selbst — und dass sie im
|
|
// Browser ANGEZEIGT wird: eine AGB, die heruntergeladen wird, wird
|
|
// weggeklickt.
|
|
expect($response->baseResponse->getFile()->getPathname())
|
|
->toBe($this->publicDir.'/agb-2026-01.pdf')
|
|
->and($response->headers->get('Content-Disposition'))->toStartWith('inline');
|
|
|
|
unlink($this->publicDir.'/agb-2026-01.pdf');
|
|
});
|
|
|
|
it('does not invent documents that are not there', function () {
|
|
$this->get('http://files.clupilot.test/gibt-es-nicht.pdf')->assertNotFound();
|
|
});
|
|
|
|
/**
|
|
* Der teuerste denkbare Fehlgriff an dieser Stelle: ein Pfad, der aus dem
|
|
* Verzeichnis herausführt. `.env` liegt zwei Ebenen darüber.
|
|
*/
|
|
it('cannot be walked out of its directory', function () {
|
|
foreach (['..%2F..%2F..%2F.env', '....//....//.env', '%2e%2e%2f.env'] as $attempt) {
|
|
$this->get('http://files.clupilot.test/'.$attempt)->assertNotFound();
|
|
}
|
|
|
|
// Und eine Punktdatei, die tatsächlich dort läge, ebenfalls nicht.
|
|
file_put_contents($this->publicDir.'/.geheim', 'nicht ausliefern');
|
|
$this->get('http://files.clupilot.test/.geheim')->assertNotFound();
|
|
unlink($this->publicDir.'/.geheim');
|
|
});
|
|
|
|
/**
|
|
* Die kopierte Zeile holt von diesem Hostnamen und legt den Code bei.
|
|
*/
|
|
it('points the command line at the files host, with the code', function () {
|
|
$host = Host::factory()->create(['dns_name' => 'fsn-01']);
|
|
$enrolment = HostEnrolment::issueWithKeys($host);
|
|
|
|
$command = HostTakeoverCommand::for($host, $enrolment);
|
|
|
|
expect($command)
|
|
->toContain('https://files.clupilot.test/bootstrap.tar.gz?code='.$enrolment['code'])
|
|
->toContain('--code '.$enrolment['code']);
|
|
});
|
|
|
|
/**
|
|
* Ohne FILES_HOST bleibt alles, wo es war. Sonst bräche eine schon ausgegebene
|
|
* Befehlszeile in dem Moment, in dem jemand die Einstellung setzt, aber der
|
|
* DNS-Eintrag noch nicht steht.
|
|
*/
|
|
it('falls back to the portal host until the files host exists', function () {
|
|
config()->set('admin_access.files_host', '');
|
|
config()->set('admin_access.app_host', 'app.clupilot.com');
|
|
|
|
expect(HostTakeoverCommand::archiveUrl())
|
|
->toBe('https://app.clupilot.com/bootstrap.tar.gz');
|
|
});
|