- System > This installation > Address has a field and a link to the other spelling (with or without www), like the WordPress address. It writes SITE_ORIGIN to .env through Environment::write and takes effect on the next request. With SITE_ORIGIN from the server environment there is no field, and a POST is refused. - Before saving, OriginCheck requests /.well-known/kantsites-origin under the new address; it has to return an HMAC of APP_SECRET that only this installation knows. Switching to www without DNS or certificate would otherwise send every page, the administration included, nowhere. "Save anyway" appears only after a refusal. The probe also answers during maintenance. - The message says when the administration changes host and needs a new sign-in, since the session cookie belongs to one spelling. Tests: 584 PHPUnit on PHP 8.2 and 8.4, 35 node, test:editor and test:browser green (test:browser reports only the known editor colour contrast on /grundlagen). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
50 lines
1.9 KiB
PHP
50 lines
1.9 KiB
PHP
<?php
|
|
|
|
namespace App\Ops;
|
|
|
|
/**
|
|
* Prüft, ob eine neue Adresse der Website wirklich bei dieser Installation ankommt.
|
|
*
|
|
* Die Adresse entscheidet, wohin die Schreibweise mit oder ohne www umgeleitet wird - für die
|
|
* Website und für die Verwaltung. Stellt jemand auf www um, und www hat keinen DNS-Eintrag oder
|
|
* kein Zertifikat, leitet danach jede Seite ins Leere, und in die Verwaltung kommt man nur noch
|
|
* über FTP. Deshalb wird vor dem Speichern angefragt: Unter der neuen Adresse muss eine Kennung
|
|
* zurückkommen, die nur diese Installation kennt - abgeleitet aus APP_SECRET, ohne es zu verraten.
|
|
*/
|
|
class OriginCheck
|
|
{
|
|
public const PATH = '/.well-known/kantsites-origin';
|
|
|
|
private Download $download;
|
|
|
|
public function __construct(private string $secret, ?Download $download = null)
|
|
{
|
|
// Kurz: die Anfrage geht an den eigenen Server und hält die Verwaltung so lange auf.
|
|
$this->download = $download ?? new Download(5, 10);
|
|
}
|
|
|
|
public function token(): string
|
|
{
|
|
return hash_hmac('sha256', 'site-origin', $this->secret);
|
|
}
|
|
|
|
/** Null, wenn die Adresse hier ankommt - sonst der Grund in einem Satz. */
|
|
public function problem(string $origin): ?string
|
|
{
|
|
if (!str_starts_with($origin, 'https://')) {
|
|
return 'Geprüft werden nur HTTPS-Adressen. Eine Adresse mit http:// lässt sich nur mit „Trotzdem übernehmen“ speichern.';
|
|
}
|
|
$antwort = $this->download->get($origin.self::PATH);
|
|
if (null === $antwort) {
|
|
$grund = $this->download->reason();
|
|
|
|
return 'Unter '.$origin.' antwortet diese Website nicht'.('' !== $grund ? ': '.rtrim($grund, '.').'.' : '.').' DNS-Eintrag und Zertifikat für diese Domain prüfen.';
|
|
}
|
|
if (!hash_equals($this->token(), trim($antwort))) {
|
|
return 'Unter '.$origin.' antwortet eine andere Website. Zeigt die Domain auf diesen Server?';
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|