feat: DefaultPath als Base-Path für User-Workspaces

- WorkspaceHelper.cs: Zentrale Workspace-Auflösung (relativ → absolut)
- Session-Workspace wird relativ zum User-DefaultPath gespeichert
- System-Prompt bekommt aufgelösten absoluten Pfad
- Path-Traversal-Schutz: '..' in Workspace-Pfaden blockiert
- Browse-API gibt '..' nicht mehr als Eintrag zurück
- Folder-Browser: basePath-Limit, Back-Button navigiert zum Parent
- DefaultPath nur durch Admin bearbeitbar (Profil: readonly)
- Legacy: Absolute Pfade in session.Workspace weiterhin kompatibel
This commit is contained in:
Meleki committed 2026-03-05 22:49:09 +01:00
1 parent 8e7bda50f9
commit 4e1ed81f37
12 files changed
+240 -106

No files matched your search

+43 -58
View File
@@ -4,6 +4,7 @@ using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Gott.Extensions;
using Gott.Models;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
@@ -52,22 +53,42 @@ public static class WorkspaceEndpoint
app.MapDelete("/api/workspace/agent/dir", HandleAgentRmdir);
}
private static async Task HandleTree(HttpContext ctx)
/// <summary>
/// Lädt Session + User und löst den Workspace-Pfad auf (relativ → absolut via DefaultPath).
/// Gibt null zurück und setzt StatusCode 400, wenn kein gültiger Workspace vorhanden ist.
/// </summary>
private static async Task<(SessionRecord session, string basePath)?> ResolveSessionWorkspace(HttpContext ctx, string sessionId)
{
var sessionId = ctx.Request.Query["sessionId"].FirstOrDefault();
var subPath = ctx.Request.Query["path"].FirstOrDefault() ?? "";
// Workspace aus Session holen
var db = ctx.RequestServices.GetRequiredService<Gott.Services.DatabaseService>();
var session = await db.GetSession(sessionId ?? "");
if (session == null || session.UserId != ctx.GetUserId() || string.IsNullOrEmpty(session.Workspace))
{
ctx.Response.StatusCode = 400;
await ctx.Response.WriteAsJsonAsync(new { error = "Kein Workspace konfiguriert" });
return;
return null;
}
var basePath = Path.GetFullPath(session.Workspace);
var userId = ctx.GetUserId();
var user = await db.GetUser(userId);
var resolved = Gott.Services.WorkspaceHelper.ResolveWorkspace(session, user);
if (string.IsNullOrEmpty(resolved))
{
ctx.Response.StatusCode = 400;
await ctx.Response.WriteAsJsonAsync(new { error = "Workspace kann nicht aufgelöst werden. Default-Pfad im Profil setzen." });
return null;
}
return (session, Path.GetFullPath(resolved));
}
private static async Task HandleTree(HttpContext ctx)
{
var sessionId = ctx.Request.Query["sessionId"].FirstOrDefault();
var subPath = ctx.Request.Query["path"].FirstOrDefault() ?? "";
var result = await ResolveSessionWorkspace(ctx, sessionId);
if (result == null) return;
var (session, basePath) = result.Value;
var targetPath = Path.GetFullPath(Path.Combine(basePath, subPath));
// Sicherheit: nicht aus dem Workspace ausbrechen
@@ -122,16 +143,10 @@ public static class WorkspaceEndpoint
return;
}
var db = ctx.RequestServices.GetRequiredService<Gott.Services.DatabaseService>();
var session = await db.GetSession(sessionId ?? "");
if (session == null || session.UserId != ctx.GetUserId() || string.IsNullOrEmpty(session.Workspace))
{
ctx.Response.StatusCode = 400;
await ctx.Response.WriteAsJsonAsync(new { error = "Kein Workspace konfiguriert" });
return;
}
var result = await ResolveSessionWorkspace(ctx, sessionId);
if (result == null) return;
var (session, basePath) = result.Value;
var basePath = Path.GetFullPath(session.Workspace);
var targetPath = Path.GetFullPath(Path.Combine(basePath, filePath));
if (!targetPath.StartsWith(basePath))
@@ -180,16 +195,10 @@ public static class WorkspaceEndpoint
return;
}
var db = ctx.RequestServices.GetRequiredService<Gott.Services.DatabaseService>();
var session = await db.GetSession(sessionId ?? "");
if (session == null || session.UserId != ctx.GetUserId() || string.IsNullOrEmpty(session.Workspace))
{
ctx.Response.StatusCode = 400;
await ctx.Response.WriteAsJsonAsync(new { error = "Kein Workspace konfiguriert" });
return;
}
var result = await ResolveSessionWorkspace(ctx, sessionId);
if (result == null) return;
var (session, basePath) = result.Value;
var basePath = Path.GetFullPath(session.Workspace);
var targetPath = Path.GetFullPath(Path.Combine(basePath, filePath));
if (!targetPath.StartsWith(basePath))
@@ -235,13 +244,6 @@ public static class WorkspaceEndpoint
var entries = new List<object>();
// Parent
var parent = Directory.GetParent(targetPath);
if (parent != null)
{
entries.Add(new { name = "..", path = parent.FullName.Replace('\\', '/'), type = "dir" });
}
try
{
foreach (var dir in Directory.GetDirectories(targetPath).OrderBy(d => Path.GetFileName(d), StringComparer.OrdinalIgnoreCase))
@@ -473,16 +475,10 @@ public static class WorkspaceEndpoint
return;
}
var db = ctx.RequestServices.GetRequiredService<Gott.Services.DatabaseService>();
var session = await db.GetSession(sessionId ?? "");
if (session == null || session.UserId != ctx.GetUserId() || string.IsNullOrEmpty(session.Workspace))
{
ctx.Response.StatusCode = 400;
await ctx.Response.WriteAsJsonAsync(new { error = "Kein Workspace konfiguriert" });
return;
}
var result = await ResolveSessionWorkspace(ctx, sessionId);
if (result == null) return;
var (session, basePath) = result.Value;
var basePath = Path.GetFullPath(session.Workspace);
var targetPath = Path.GetFullPath(Path.Combine(basePath, dirPath));
if (!targetPath.StartsWith(basePath))
@@ -514,16 +510,10 @@ public static class WorkspaceEndpoint
return;
}
var db = ctx.RequestServices.GetRequiredService<Gott.Services.DatabaseService>();
var session = await db.GetSession(sessionId ?? "");
if (session == null || session.UserId != ctx.GetUserId() || string.IsNullOrEmpty(session.Workspace))
{
ctx.Response.StatusCode = 400;
await ctx.Response.WriteAsJsonAsync(new { error = "Kein Workspace konfiguriert" });
return;
}
var result = await ResolveSessionWorkspace(ctx, sessionId);
if (result == null) return;
var (session, basePath) = result.Value;
var basePath = Path.GetFullPath(session.Workspace);
var targetPath = Path.GetFullPath(Path.Combine(basePath, dirPath));
if (!targetPath.StartsWith(basePath) || targetPath == basePath)
@@ -650,15 +640,10 @@ public static class WorkspaceEndpoint
return;
}
var db = ctx.RequestServices.GetRequiredService<Gott.Services.DatabaseService>();
var session = await db.GetSession(sessionId ?? "");
if (session == null || session.UserId != ctx.GetUserId() || string.IsNullOrEmpty(session.Workspace))
{
ctx.Response.StatusCode = 400;
return;
}
var result = await ResolveSessionWorkspace(ctx, sessionId);
if (result == null) return;
var basePath = result.Value.basePath;
var basePath = Path.GetFullPath(session.Workspace);
var targetPath = Path.GetFullPath(Path.Combine(basePath, filePath));
if (!targetPath.StartsWith(basePath))