Redirect the other www spelling to SITE_ORIGIN, hold roles to the role help and fix the audit findings
SITE_ORIGIN alone decides whether the site is www or not: CanonicalHostSubscriber sends the other spelling there with 301, cms:site:origin writes the setting, the system screen shows it. The roles now match the role help (editor: drafts, publisher: live content, admin: design, settings, users, system). The installer no longer reopens on an unreachable database, the session cookie follows ADMIN_PATH, percent-encoded credentials no longer break the container, consent only accepts same-site decisions, JSON-LD is HTML-safe, own CSS stays in its block, plus the editor, updater, download and public-output findings from the full review (ADR 099, ADR 100). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
45040b2ca6
commit
7eb462ac7b
53 files changed
+1023
-122
No files matched your search
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
namespace App\EventSubscriber;
|
||||
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpKernel\Event\RequestEvent;
|
||||
use Symfony\Component\HttpKernel\KernelEvents;
|
||||
|
||||
/**
|
||||
* Eine Website, eine Adresse: mit oder ohne www entscheidet SITE_ORIGIN, nicht der Webserver.
|
||||
*
|
||||
* Bisher stand die Umleitung auskommentiert in public/.htaccess und musste von Hand zu SITE_ORIGIN
|
||||
* passen. Zwei Stellen fuer eine Entscheidung gehen auseinander, und dann zeigt jede Seite in
|
||||
* Canonical, Sitemap und Feed auf eine Adresse, die weiterleitet. Jetzt liest die Anwendung die
|
||||
* konfigurierte Adresse und schickt die andere Schreibweise dorthin - dauerhaft (301), mit Pfad
|
||||
* und Abfrage.
|
||||
*
|
||||
* Was sie bewusst nicht tut: einen fremden Host umleiten. Eine Vorschauadresse des Hosters, eine
|
||||
* IP-Adresse oder localhost bleiben erreichbar, auch wenn SITE_ORIGIN etwas anderes nennt - sonst
|
||||
* sperrt eine falsch eingetragene Adresse jeden aus, samt der Verwaltung. Umgeleitet wird nur das
|
||||
* Paar aus `example.de` und `www.example.de`. Und sie erzwingt kein HTTPS: hinter einem Proxy
|
||||
* sieht PHP oft nur http, und eine Schleife waere schlimmer als fehlendes HTTPS. Die drei Zeilen
|
||||
* dafuer stehen weiter in .htaccess, wo der Webserver die Verbindung wirklich kennt.
|
||||
*/
|
||||
final class CanonicalHostSubscriber implements EventSubscriberInterface
|
||||
{
|
||||
public function __construct(private string $siteOrigin)
|
||||
{
|
||||
}
|
||||
|
||||
public static function getSubscribedEvents(): array
|
||||
{
|
||||
// Vor Wartung (40) und Installer (36): auf dem falschen Host gibt es genau eine Antwort,
|
||||
// und fuer die braucht niemand die Datenbank.
|
||||
return [KernelEvents::REQUEST => ['onRequest', 48]];
|
||||
}
|
||||
|
||||
public function onRequest(RequestEvent $event): void
|
||||
{
|
||||
$request = $event->getRequest();
|
||||
// Nur lesende Anfragen: ein 301 macht aus einem POST ein GET und verwirft die Eingabe.
|
||||
// Ein Formular auf der anderen Schreibweise darf noch ankommen; die Seite dahinter
|
||||
// fuehrt dann ohnehin auf die richtige Adresse.
|
||||
if (!$event->isMainRequest() || !\in_array($request->getMethod(), ['GET', 'HEAD'], true)) {
|
||||
return;
|
||||
}
|
||||
$target = self::target($this->siteOrigin, $request);
|
||||
if (null !== $target) {
|
||||
$event->setResponse(new RedirectResponse($target, 301, ['Cache-Control' => 'public, max-age=3600']));
|
||||
}
|
||||
}
|
||||
|
||||
/** Die Adresse, auf die diese Anfrage gehoert - oder null, wenn sie schon dort ist. */
|
||||
public static function target(string $origin, Request $request): ?string
|
||||
{
|
||||
$parts = parse_url(trim($origin));
|
||||
$wanted = strtolower((string) ($parts['host'] ?? ''));
|
||||
$scheme = strtolower((string) ($parts['scheme'] ?? ''));
|
||||
if ('' === $wanted || !\in_array($scheme, ['http', 'https'], true)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
$host = strtolower($request->getHost());
|
||||
} catch (\UnexpectedValueException) {
|
||||
// Ein Host, den Symfony nicht annimmt, bekommt seine 400 vom Kernel - nicht von hier.
|
||||
return null;
|
||||
}
|
||||
if ($host === $wanted || !self::wwwTwins($host, $wanted)) {
|
||||
return null;
|
||||
}
|
||||
$port = isset($parts['port']) ? ':'.$parts['port'] : '';
|
||||
|
||||
return $scheme.'://'.$wanted.$port.$request->getRequestUri();
|
||||
}
|
||||
|
||||
/** `example.de` und `www.example.de` - und sonst nichts. */
|
||||
private static function wwwTwins(string $a, string $b): bool
|
||||
{
|
||||
return 'www.'.$a === $b || $a === 'www.'.$b;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user