feat: operation-level privacy gating instead of broad service-level
All checks were successful
CI / lint-and-test (push) Successful in 21s

- Replace broad service-level hiding with fine-grained operation-level gating
- PRIVACY_SENSITIVE_OPERATIONS config: hide specific operations across ALL services
- PRIVACY_SERVICES still works for broad service-level blocking (optional)
- Users without PRIVACY_SERVICE_ROLES:
  * Don't see sensitive operations in /api/filter-options
  * Can't query sensitive operations via /api/events or /api/ask
  * Get 403 on /api/events/{id}/explain for sensitive events
- Exchange/Teams services remain visible; only privacy ops are hidden
- Update .env.example with new operation-level config docs
This commit is contained in:
2026-04-22 08:23:46 +02:00
parent b2f4cabef4
commit 2fffe3aec2
6 changed files with 89 additions and 35 deletions

View File

@@ -13,6 +13,7 @@ from config import (
LLM_MAX_EVENTS,
LLM_MODEL,
LLM_TIMEOUT_SECONDS,
PRIVACY_SENSITIVE_OPERATIONS,
PRIVACY_SERVICES,
)
from database import events_collection
@@ -596,7 +597,9 @@ async def explain_event(event_id: str, user: dict = Depends(require_auth)):
if not event:
raise HTTPException(status_code=404, detail="Event not found")
if event.get("service") in PRIVACY_SERVICES and not user_can_access_privacy_services(user):
if (
event.get("service") in PRIVACY_SERVICES or event.get("operation") in PRIVACY_SENSITIVE_OPERATIONS
) and not user_can_access_privacy_services(user):
raise HTTPException(status_code=403, detail="Access to this event is restricted")
event.pop("_id", None)
@@ -689,7 +692,8 @@ async def ask_question(body: AskRequest, user: dict = Depends(require_auth)):
# -----------------------------------------------------------------------
# Build and run query
# -----------------------------------------------------------------------
privacy_excluded = [] if user_can_access_privacy_services(user) else list(PRIVACY_SERVICES)
privacy_excluded_services = [] if user_can_access_privacy_services(user) else list(PRIVACY_SERVICES)
privacy_excluded_ops = [] if user_can_access_privacy_services(user) else list(PRIVACY_SENSITIVE_OPERATIONS)
query = _build_event_query(
entity,
start,
@@ -701,8 +705,13 @@ async def ask_question(body: AskRequest, user: dict = Depends(require_auth)):
include_tags=body.include_tags,
exclude_tags=body.exclude_tags,
)
if privacy_excluded:
query["$and"] = query.get("$and", []) + [{"service": {"$nin": privacy_excluded}}]
extra_filters = []
if privacy_excluded_services:
extra_filters.append({"service": {"$nin": privacy_excluded_services}})
if privacy_excluded_ops:
extra_filters.append({"operation": {"$nin": privacy_excluded_ops}})
if extra_filters:
query["$and"] = query.get("$and", []) + extra_filters
try:
total = events_collection.count_documents(query)