diff --git a/app/database.py b/app/database.py index 5d4e366..55f0ba9 100644 --- a/app/database.py +++ b/app/database.py @@ -153,9 +153,12 @@ async def add_comment(user_id: str, user_name: str, drucksache: str, async def get_comments(drucksache: str, user_id: Optional[str] = None) -> list[dict]: - """Get comments for a drucksache. Filters by visibility: - - 'all' comments are always visible - - group-scoped comments only visible if user is in that group (TODO) + """Get comments for a drucksache, server-seitig nach Sichtbarkeit gefiltert. + + - 'all': immer sichtbar + - 'authenticated': nur wenn user_id gesetzt (eingeloggt) + - 'private': nur für den Autor + - 'group:XYZ': für Gruppenmitglieder (TODO: Keycloak-Gruppen-Check) """ async with aiosqlite.connect(settings.db_path) as db: db.row_factory = aiosqlite.Row @@ -163,7 +166,22 @@ async def get_comments(drucksache: str, user_id: Optional[str] = None) -> list[d "SELECT * FROM comments WHERE drucksache=? ORDER BY created_at", (drucksache,), ) - return [dict(r) for r in await rows.fetchall()] + all_comments = [dict(r) for r in await rows.fetchall()] + + # Server-seitig filtern (Defense in Depth — Client filtert auch) + result = [] + for c in all_comments: + vis = c.get("visibility", "all") + if vis == "all": + result.append(c) + elif vis == "authenticated" and user_id: + result.append(c) + elif vis == "private" and user_id and c["user_id"] == user_id: + result.append(c) + elif vis.startswith("group:") and user_id: + # TODO: Keycloak-Gruppen-Membership prüfen + result.append(c) + return result async def delete_comment(comment_id: int, user_id: str) -> bool: diff --git a/app/templates/index.html b/app/templates/index.html index 0f07378..dcc88d4 100644 --- a/app/templates/index.html +++ b/app/templates/index.html @@ -1385,14 +1385,31 @@ if (!container) return; try { const comments = await fetch(`/api/comments?drucksache=${encodeURIComponent(drucksache)}`).then(r => r.json()); - if (comments.length === 0) { + // Client-seitig filtern nach Sichtbarkeit + const visible = comments.filter(c => { + if (c.visibility === 'all') return true; + if (!currentUser) return false; + if (c.visibility === 'authenticated') return true; + if (c.visibility === 'private') return c.user_id === currentUser.sub; + // group:XYZ — TODO: gegen Keycloak-Gruppen prüfen + if (c.visibility?.startsWith('group:')) return true; // Platzhalter + return false; + }); + if (visible.length === 0) { container.innerHTML = 'Noch keine Kommentare.'; return; } - container.innerHTML = comments.map(c => ` + const visBadge = (v) => { + if (v === 'private') return '👤'; + if (v === 'authenticated') return '🔒'; + if (v?.startsWith('group:')) return `👥 ${v.substring(6)}`; + return '🌐'; + }; + container.innerHTML = visible.map(c => `
${c.user_name || 'Anonym'} - ${new Date(c.created_at).toLocaleString('de-DE')} + ${visBadge(c.visibility)} + ${new Date(c.created_at).toLocaleString('de-DE')} ${currentUser && currentUser.sub === c.user_id ? `` : ''}
${c.text}
@@ -1401,12 +1418,15 @@ } async function addCommentUI(drucksache) { - const input = document.getElementById('comment-input-' + drucksache.replace('/', '-')); + const safeDrs = drucksache.replace('/', '-'); + const input = document.getElementById('comment-input-' + safeDrs); + const visSelect = document.getElementById('comment-visibility-' + safeDrs); if (!input || !input.value.trim()) return; + const visibility = visSelect ? visSelect.value : 'all'; await fetch('/api/comment', { method: 'POST', headers: {'Content-Type': 'application/x-www-form-urlencoded'}, - body: `drucksache=${encodeURIComponent(drucksache)}&text=${encodeURIComponent(input.value)}` + body: `drucksache=${encodeURIComponent(drucksache)}&text=${encodeURIComponent(input.value)}&visibility=${visibility}` }); input.value = ''; loadComments(drucksache); @@ -1940,11 +1960,17 @@ Lade Kommentare... ${currentUser ? ` -
+
+