Carpetas de imágenes por usuario en ASP.NET Core

De serie, la galería muestra a todo el mundo las mismas carpetas, algo adecuado para el sitio web de una empresa. En un foro o en una aplicación con varios clientes, cada persona solo debería ver sus propias imágenes. En los ejemplos de ASP.NET Core, eso es un solo método, RootFor(HttpContext), que decide dónde están los archivos de una solicitud. Esta página muestra la versión por usuario, el inicio de sesión que necesita y lo que sigue siendo público.
Un método decide
Cada ruta (listar, crear carpeta, subir) pide a RootFor una carpeta en el disco y la URL en la que se sirve. Nada más en el código sabe nada de usuarios.
// Where this request's files live. This is the only method to change for per-user folders.
// "PerUserFolders": false (default) everyone shares <content root>/uploads, served at /uploads
// "PerUserFolders": true each signed-in user gets uploads/users/<user id>
public static RteRoot RootFor(HttpContext http)
{
var uploads = UploadsFolder(http.RequestServices.GetRequiredService<IWebHostEnvironment>());
if (!http.RequestServices.GetRequiredService<IConfiguration>().GetValue<bool>("PerUserFolders"))
return new RteRoot(uploads, "/uploads");
// Use the user's id (a GUID with ASP.NET Core Identity), not the user name, which can change.
var id = http.User.FindFirstValue(ClaimTypes.NameIdentifier)
?? throw new RteError(401, "Sign in to use the image gallery.");
// The id becomes a folder name, so it must pass the same rules as any other folder name.
if (!IsValidName(id)) throw new RteError(403, "This user id cannot be used as a folder name.");
return new RteRoot(Path.Combine(uploads, "users", id), "/uploads/users/" + Uri.EscapeDataString(id));
}PerUserFolders, enappsettings.json, valefalsede forma predeterminada. Contrue, cada usuario con sesión iniciada recibeuploads/users/<id>/como raíz de su galería. Su botón New folder y sus cargas funcionan dentro de ella, y..no puede salir, porque la comprobación de carpetas se ejecuta contra esta raíz.- El id es
ClaimTypes.NameIdentifier. Con ASP.NET Core Identity, esa claim contiene el id del usuario, un GUID, que nunca cambia. Un nombre de usuario o una dirección de correo electrónico pueden cambiar, y en WindowsAliceyaliceserían la misma carpeta. - Sin usuario, no hay galería. Una solicitud anónima recibe
401 {"error":"Sign in to use the image gallery."}, y el cuadro de diálogo muestra su propio "The image gallery is unavailable right now (http-401)." en lugar del texto del servidor. - El id se comprueba como un nombre de carpeta. Un id como
../../xrecibe un 403 en lugar de una ruta.
Inicio de sesión
El ejemplo usa autenticación con cookies, el mismo esquema con el que inicia sesión ASP.NET Core Identity. El inicio de sesión propio de tu aplicación sustituye al inicio de sesión de prueba de abajo.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews();
// Your app's sign-in goes here. "PerUserFolders": true in appsettings.json gives each user a folder.
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie();
var app = builder.Build();
app.UseStaticFiles();
app.UseRteStaticFiles(); // /uploads, /richtexteditor, /rte-files.js
app.UseAuthentication();
app.UseAuthorization();
app.MapDefaultControllerRoute(); // HomeController.Index at "/", plus RteController's [Route] attributesPara probar las carpetas por usuario sin crear un inicio de sesión, el ejemplo asigna un inicio de sesión solo de prueba, y solo en el entorno Development. Permite que cualquiera inicie sesión como cualquiera, así que nunca debe llegar a un servidor real:
// TEST ONLY, and mapped only in Development: /demo/sign-in?user=alice signs anyone in as anyone,
// so per-user folders can be tried without a real login. Never ship this.
if (app.Environment.IsDevelopment())
{
app.MapGet("/demo/sign-in", async (HttpContext http, string user) =>
{
var identity = new ClaimsIdentity([new Claim(ClaimTypes.NameIdentifier, user)], CookieAuthenticationDefaults.AuthenticationScheme);
await http.SignInAsync(new ClaimsPrincipal(identity));
return Results.Redirect("/");
});
}Con un simple dotnet run, que se ejecuta como Production, /demo/sign-in respondió 404.
Lo que sigue siendo público
Tener galerías separadas no hace privados los archivos. /uploads/users/alice/harbor-fd5ff24b.jpg se sirve con la misma asignación de archivos estáticos que cualquier otra carga, y respondió 200 a una solicitud sin cookie. Eso suele ser lo correcto para las imágenes del contenido publicado: los lectores de una entrada tienen que poder cargar sus imágenes. La parte aleatoria del nombre de archivo hace que nadie pueda adivinar la URL, pero cualquiera con quien se comparta puede abrirla. Si las imágenes de un usuario deben seguir siendo privadas, sírvelas desde un endpoint que compruebe el usuario en lugar de desde UseStaticFiles. El ejemplo no incluye ninguno.
Activar las carpetas por usuario tampoco decide quién puede subir archivos. Añade [Authorize] al controlador, o .RequireAuthorization() al grupo de Minimal API, cuando la aplicación tenga inicio de sesión.
Pruébalo
Inicia el proyecto de ejemplo de esta guía en Development con las carpetas por usuario activadas, y trabaja como dos usuarios con dos archivos de cookies:
dotnet run -- --environment Development --PerUserFolders true
curl "http://localhost:5302/rte/gallery?folder="
{"error":"Sign in to use the image gallery."}
curl -s -o /dev/null -c alice.jar "http://localhost:5302/demo/sign-in?user=alice"
curl -b alice.jar -F "file=@harbor.jpg" http://localhost:5302/rte/upload
{"url":"/uploads/users/alice/harbor-fd5ff24b.jpg"}
curl -s -o /dev/null -c bob.jar "http://localhost:5302/demo/sign-in?user=bob"
curl -b bob.jar -F "file=@forest.jpg" http://localhost:5302/rte/upload
{"url":"/uploads/users/bob/forest-8b1a0f16.jpg"}
curl -b bob.jar "http://localhost:5302/rte/gallery?folder="
{"currentFolder":"","currentFolderDisplay":"/","parentFolder":null,"folders":[],"images":[{"url":"/uploads/users/bob/forest-8b1a0f16.jpg","name":"forest-8b1a0f16.jpg","folder":"","size":36337}]}La galería de Bob solo muestra su imagen. En un navegador, abre http://localhost:5302/demo/sign-in?user=alice y haz clic en Insert Gallery: solo muestra las imágenes y las carpetas de Alice. La imagen del principio de esta página es la carpeta uploads después de esto, con users/alice y users/bob junto a las carpetas compartidas que usa el ejemplo mientras PerUserFolders está desactivado.
La misma guía para otras plataformas
JavaScript · PHP · MVC 5 · Web Forms