fix: mask contact fields for non-admin users, require disclosure for visibility

- Add strasse, plz, ort, email, telefonnummer, mobiltelefon, ansprechpartner
  to SENSITIVE_FIELDS in backend (nullified without disclosure)
- Add visibleTo: 'admin' to all Kontakt fields in frontend fieldConfig
- Consolidate _utcnow_naive() usage across all disclosure service functions
  for consistent naive datetime handling with MySQL

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
CCS Admin 2026-02-27 11:13:32 +00:00
parent d900d7864b
commit 5f957ee8ed
3 changed files with 19 additions and 16 deletions

View file

@ -128,7 +128,10 @@ class CodingUpdate(BaseModel):
ta_uebertherapie: bool = False ta_uebertherapie: bool = False
SENSITIVE_FIELDS = ("nachname", "vorname", "geburtsdatum", "anrede") SENSITIVE_FIELDS = (
"nachname", "vorname", "geburtsdatum", "anrede",
"strasse", "plz", "ort", "email", "telefonnummer", "mobiltelefon", "ansprechpartner",
)
def mask_case_for_mitarbeiter(case_dict: dict, disclosure_granted: bool = False) -> dict: def mask_case_for_mitarbeiter(case_dict: dict, disclosure_granted: bool = False) -> dict:

View file

@ -10,6 +10,11 @@ from app.models.case import Case
from app.models.user import User from app.models.user import User
def _utcnow_naive() -> datetime:
"""Return current UTC time as a naive datetime, matching MySQL DATETIME columns."""
return datetime.now(timezone.utc).replace(tzinfo=None)
def has_active_disclosure(db: Session, case_id: int, user_id: int) -> tuple[bool, Optional[datetime]]: def has_active_disclosure(db: Session, case_id: int, user_id: int) -> tuple[bool, Optional[datetime]]:
"""Check if user has an active (approved, non-expired) disclosure for a case. """Check if user has an active (approved, non-expired) disclosure for a case.
@ -21,7 +26,7 @@ def has_active_disclosure(db: Session, case_id: int, user_id: int) -> tuple[bool
DisclosureRequest.case_id == case_id, DisclosureRequest.case_id == case_id,
DisclosureRequest.requester_id == user_id, DisclosureRequest.requester_id == user_id,
DisclosureRequest.status == "approved", DisclosureRequest.status == "approved",
DisclosureRequest.expires_at > datetime.now(timezone.utc), DisclosureRequest.expires_at > _utcnow_naive(),
) )
.first() .first()
) )
@ -89,10 +94,10 @@ def review_disclosure_request(
dr.status = new_status dr.status = new_status
dr.reviewed_by = admin_id dr.reviewed_by = admin_id
dr.reviewed_at = datetime.now(timezone.utc) dr.reviewed_at = _utcnow_naive()
if new_status == "approved": if new_status == "approved":
dr.expires_at = datetime.now(timezone.utc) + timedelta(hours=24) dr.expires_at = _utcnow_naive() + timedelta(hours=24)
# Notify requester # Notify requester
case = db.query(Case).filter(Case.id == dr.case_id).first() case = db.query(Case).filter(Case.id == dr.case_id).first()
@ -113,11 +118,6 @@ def review_disclosure_request(
return dr return dr
def _utcnow_naive() -> datetime:
"""Return current UTC time as a naive datetime, matching MySQL DATETIME columns."""
return datetime.now(timezone.utc).replace(tzinfo=None)
def revoke_disclosure(db: Session, request_id: int, user_id: int, *, admin: bool = False) -> DisclosureRequest: def revoke_disclosure(db: Session, request_id: int, user_id: int, *, admin: bool = False) -> DisclosureRequest:
"""Revoke an active disclosure by setting expires_at to now. """Revoke an active disclosure by setting expires_at to now.

View file

@ -41,13 +41,13 @@ export const CASE_SECTIONS: SectionConfig[] = [
{ {
title: 'Kontakt', title: 'Kontakt',
fields: [ fields: [
{ key: 'strasse', label: 'Straße', type: 'text', editableBy: 'admin', placeholder: 'Straße + Nr.' }, { key: 'strasse', label: 'Straße', type: 'text', editableBy: 'admin', visibleTo: 'admin', placeholder: 'Straße + Nr.' },
{ key: 'plz', label: 'PLZ', type: 'text', editableBy: 'admin', placeholder: 'PLZ' }, { key: 'plz', label: 'PLZ', type: 'text', editableBy: 'admin', visibleTo: 'admin', placeholder: 'PLZ' },
{ key: 'ort', label: 'Ort', type: 'text', editableBy: 'admin', placeholder: 'Ort' }, { key: 'ort', label: 'Ort', type: 'text', editableBy: 'admin', visibleTo: 'admin', placeholder: 'Ort' },
{ key: 'email', label: 'E-Mail', type: 'text', editableBy: 'admin', placeholder: 'E-Mail' }, { key: 'email', label: 'E-Mail', type: 'text', editableBy: 'admin', visibleTo: 'admin', placeholder: 'E-Mail' },
{ key: 'telefonnummer', label: 'Telefon', type: 'text', editableBy: 'admin', placeholder: 'Telefonnummer' }, { key: 'telefonnummer', label: 'Telefon', type: 'text', editableBy: 'admin', visibleTo: 'admin', placeholder: 'Telefonnummer' },
{ key: 'mobiltelefon', label: 'Mobil', type: 'text', editableBy: 'admin', placeholder: 'Mobilnummer' }, { key: 'mobiltelefon', label: 'Mobil', type: 'text', editableBy: 'admin', visibleTo: 'admin', placeholder: 'Mobilnummer' },
{ key: 'ansprechpartner', label: 'Ansprechpartner', type: 'text', editableBy: 'admin', colSpan: 2, placeholder: 'Ansprechpartner' }, { key: 'ansprechpartner', label: 'Ansprechpartner', type: 'text', editableBy: 'admin', visibleTo: 'admin', colSpan: 2, placeholder: 'Ansprechpartner' },
], ],
}, },
{ {