Bildordner pro Benutzer in PHP

Auf einer Website, auf der jede Person ihre eigenen Inhalte schreibt, sollte die Galerie jeder Person ihre eigenen Bilder zeigen, nicht die aller anderen. Mit dem PHP-Beispiel ist das die Änderung einer einzigen Funktion: Der Stammordner für die Speicherung wird pro Request gewählt, anhand des angemeldeten Benutzers in $_SESSION. Diese Seite zeigt diese Funktion, wie Sie sie einschalten und was sie schützt und was nicht.
Eine Funktion entscheidet, wohin die Dateien gehen
Jede Route im Beispiel (auflisten, einen Ordner anlegen, hochladen) erhält ihren Stammordner von rte_root_for_request(). Standardmäßig gibt die Funktion den gemeinsamen Ordner uploads/ zurück. Mit RTE_PER_USER=1 in der Umgebung gibt sie für den angemeldeten Benutzer uploads/users/<id>/ zurück:
// The one place that decides WHERE files live.
// Returns ['dir' => folder on disk, 'url' => public URL of that folder].
// Default: one shared folder. With RTE_PER_USER=1: uploads/users/<id>/ per signed-in user.
function rte_root_for_request(): array
{
if (getenv('RTE_PER_USER') !== '1') {
return ['dir' => __DIR__ . '/../uploads', 'url' => '/uploads'];
}
$id = rte_current_user_id();
return ['dir' => __DIR__ . '/../uploads/users/' . $id, 'url' => '/uploads/users/' . $id];
}
// In shared mode, uploads/users/ holds other people's folders: never list or open it.
function rte_is_reserved(array $segments): bool
{
return getenv('RTE_PER_USER') !== '1' && isset($segments[0]) && strcasecmp($segments[0], 'users') === 0;
}
// The signed-in user's id, from YOUR login (here: the PHP session). Never from the request.
function rte_current_user_id(): string
{
if (session_status() !== PHP_SESSION_ACTIVE) {
session_start();
}
$id = (string)($_SESSION['user_id'] ?? '');
// PHP locks the session file until the request ends; release it so the
// gallery's requests do not wait for each other.
session_write_close();
// The id becomes a folder name, so it must be a plain token.
if (!preg_match('/\A[A-Za-z0-9_-]{1,64}\z/', $id)) {
throw new RteHttpError(401, 'Sign in to use the image gallery.');
}
return $id;
}Weil die Ordnerprüfungen, die Liste und move_uploaded_file() alle innerhalb dieses Stammordners arbeiten, ändert sich sonst nichts. Der Ordner drafts eines Benutzers ist auf der Festplatte uploads/users/alice/drafts, die Galerie nennt ihn aber drafts, und die zurückgegebene URL lautet /uploads/users/alice/drafts/....
Woher die Benutzer-ID kommt
Die ID muss aus Ihrem eigenen Login stammen, niemals aus dem Request. Im Beispiel wird sie aus $_SESSION['user_id'] gelesen. Außerdem wird geprüft, dass sie ein einfaches Token ist, bevor sie zu einem Ordnernamen wird. Wenn Ihre IDs E-Mail-Adressen sind, verwenden Sie stattdessen Ihre numerische Datenbank-ID.
Der Aufruf von session_write_close() steht dort wegen der Arbeitsweise von PHP. Mit den standardmäßigen Datei-Sessions hält PHP die Session bis zum Ende des Requests gesperrt, sodass die Requests der Galerie aus demselben Browser aufeinander warten würden. Der Stammordner braucht nur die ID, deshalb wird die Session direkt nach dem Lesen freigegeben.
Ohne gültige Session antwortet jede Route mit 401 {"error":"Sign in to use the image gallery."}. Der Dialog zeigt dann beim Öffnen „The image gallery is unavailable right now (http-401).“
Ohne Login-System ausprobieren
Das Beispielprojekt enthält demo-login.php, damit Sie zwei Benutzer ausprobieren können. Es setzt denselben Session-Wert, den Ihr Login-Code setzen würde:
session_start();
session_regenerate_id(true); // new session id at every sign-in
$user = $_GET['user'] ?? '';
if (in_array($user, ['alice', 'bob'], true)) {
$_SESSION['user_id'] = $user; // what rte_current_user_id() reads
} else {
unset($_SESSION['user_id']);
}Es funktioniert nur unter dem eingebauten Server von PHP (es prüft PHP_SAPI), und die Routen der Anleitung hängen nicht davon ab. Löschen Sie es und setzen Sie $_SESSION['user_id'] in Ihrem eigenen Anmeldecode, nach der Passwortprüfung, zusammen mit session_regenerate_id(true).
Gemeinsamer Modus und Modus pro Benutzer auf einer Festplatte
Die Ordner pro Benutzer liegen unter uploads/users/. Würden Sie den gemeinsamen Modus jemals auf demselben Ordner betreiben, wären die Ordner aller Benutzer in der gemeinsamen Galerie sichtbar. Deshalb blendet das Beispiel im gemeinsamen Modus users in der Liste des Stammordners aus, antwortet auf ?folder=users/alice mit 404 und verweigert das Anlegen eines Ordners namens users. Das Bild oben zeigt beides auf einer Festplatte: die gemeinsamen Ordner blog, products und team sowie users/alice und users/bob aus dem Modus pro Benutzer.
Was das nicht schützt
Die Bilder werden weiterhin als statische Dateien aus /uploads/ ausgeliefert. Wer die URL von Bobs Bild hat, kann es öffnen, ob angemeldet oder nicht. Der zufällige Teil des gespeicherten Namens macht die URLs schwer zu erraten, aber nicht geheim: Sie landen im HTML jedes Dokuments, das das Bild anzeigt. Wenn Bilder privat sein müssen, liefern Sie sie über ein PHP-Skript aus, das die Session prüft, und legen Sie uploads/ außerhalb des Document Root ab.
Testen
Starten Sie das Beispielprojekt zu dieser Anleitung im Modus pro Benutzer. In PowerShell setzen Sie stattdessen zuerst $env:RTE_PER_USER=1.
RTE_PER_USER=1 php -d upload_max_filesize=5M -d display_errors=0 -d log_errors=1 -S localhost:5201 router.phpMelden Sie sich dann mit getrennten Cookie-Dateien als zwei Benutzer an und laden Sie je ein Bild hoch:
curl "http://localhost:5201/rte/gallery?folder="
{"error":"Sign in to use the image gallery."}
curl -c alice.txt -b alice.txt "http://localhost:5201/demo-login?user=alice"
curl -c bob.txt -b bob.txt "http://localhost:5201/demo-login?user=bob"
curl -b alice.txt -F "file=@harbor.jpg" http://localhost:5201/rte/upload
{"url":"/uploads/users/alice/harbor-a936755b.jpg"}
curl -b bob.txt -F "file=@meadow.jpg" http://localhost:5201/rte/upload
{"url":"/uploads/users/bob/meadow-94064337.jpg"}
curl -b bob.txt "http://localhost:5201/rte/gallery?folder=..%2Falice"
{"error":"Invalid folder."}Bobs Liste enthält nur meadow-94064337.jpg. Ein Ordner von Alice, den Bob per Namen anfordert (?folder=drafts), ergibt für ihn ein 404, und ein selbst erfundenes Session-Cookie erhält das 401. Öffnen Sie im Browser /demo-login?user=alice und dann Insert Gallery, um Alices Ordner zu sehen.
Dieselbe Anleitung für andere Plattformen
JavaScript · ASP.NET Core · MVC 5 · Web Forms