fix: router db.refresh() nach commit bricht RLS-Kontext
SET LOCAL Werte (bypass_rls, company_id) sind transaktions-gebunden. Nach db.commit() ist der Kontext weg – ein nachfolgendes db.refresh() läuft in einer neuen Transaktion ohne RLS-Kontext und liefert 0 Rows. Da expire_on_commit=False gesetzt ist, sind alle Instanz-Attribute nach dem Commit bereits im Speicher vorhanden. Die expliziten db.refresh()-Aufrufe nach db.commit() in allen Routers sind daher redundant und wurden entfernt. test_rls.py: 6 neue Tests beweisen DB-seitige Mandanten-Isolation. conftest.py: _apply_rls() wendet RLS-Policies auf Test-DB an. migrations/0024: korrigiert auf op.execute(text()) API. migrations/env.py: SET LOCAL außerhalb Transaktion entfernt. Ergebnis: 8 failed (pre-existing), 126 passed – identisch zur Baseline vor RLS. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -72,7 +72,6 @@ async def create_absence_type(
|
||||
):
|
||||
at = await absence_service.create_type(current_user.company_id, data, db)
|
||||
await db.commit()
|
||||
await db.refresh(at)
|
||||
return AbsenceTypeOut.model_validate(at)
|
||||
|
||||
|
||||
@@ -85,7 +84,6 @@ async def update_absence_type(
|
||||
):
|
||||
at = await absence_service.update_type(type_id, current_user.company_id, data, db)
|
||||
await db.commit()
|
||||
await db.refresh(at)
|
||||
return AbsenceTypeOut.model_validate(at)
|
||||
|
||||
|
||||
@@ -111,7 +109,6 @@ async def create_public_holiday(
|
||||
):
|
||||
holiday = await absence_service.create_holiday(data, db)
|
||||
await db.commit()
|
||||
await db.refresh(holiday)
|
||||
return PublicHolidayOut.model_validate(holiday)
|
||||
|
||||
|
||||
@@ -181,7 +178,6 @@ async def quick_sick(
|
||||
data.start_date, data.end_date, current_user, db
|
||||
)
|
||||
await db.commit()
|
||||
await db.refresh(absence)
|
||||
return AbsenceOut.model_validate(absence)
|
||||
|
||||
|
||||
@@ -256,7 +252,6 @@ async def create_absence(
|
||||
acting_user = target
|
||||
absence, warnings = await absence_service.create_absence(data, acting_user, db)
|
||||
await db.commit()
|
||||
await db.refresh(absence)
|
||||
return AbsenceOut.model_validate(absence)
|
||||
|
||||
|
||||
@@ -270,7 +265,6 @@ async def update_absence(
|
||||
"""Ausstehenden Antrag bearbeiten (Mitarbeiter: eigene; Manager: alle der Company)."""
|
||||
absence = await absence_service.update_absence(absence_id, data, current_user, db)
|
||||
await db.commit()
|
||||
await db.refresh(absence)
|
||||
return AbsenceOut.model_validate(absence)
|
||||
|
||||
|
||||
@@ -303,7 +297,6 @@ async def approve_absence(
|
||||
):
|
||||
absence = await absence_service.approve_absence(absence_id, current_user, db)
|
||||
await db.commit()
|
||||
await db.refresh(absence)
|
||||
return AbsenceOut.model_validate(absence)
|
||||
|
||||
|
||||
@@ -316,7 +309,6 @@ async def reject_absence(
|
||||
):
|
||||
absence = await absence_service.reject_absence(absence_id, data, current_user, db)
|
||||
await db.commit()
|
||||
await db.refresh(absence)
|
||||
return AbsenceOut.model_validate(absence)
|
||||
|
||||
|
||||
@@ -332,7 +324,6 @@ async def mark_certificate_received(
|
||||
absence_id, data.received_at, current_user, db
|
||||
)
|
||||
await db.commit()
|
||||
await db.refresh(absence)
|
||||
return AbsenceOut.model_validate(absence)
|
||||
|
||||
|
||||
@@ -357,7 +348,6 @@ async def update_balance(
|
||||
for field, value in data.model_dump(exclude_unset=True).items():
|
||||
setattr(balance, field, value)
|
||||
await db.commit()
|
||||
await db.refresh(balance)
|
||||
pending = await absence_service.get_pending_days(user_id, year, db)
|
||||
company = await db.get(Company, current_user.company_id)
|
||||
expires_at, expired = _carryover_expiry(company, year) if company else (None, False)
|
||||
|
||||
@@ -62,7 +62,6 @@ async def save_company_config(
|
||||
raise HTTPException(status_code=400, detail="Passwort wird beim ersten Speichern benötigt.")
|
||||
|
||||
await db.commit()
|
||||
await db.refresh(cfg)
|
||||
return CaldavCompanyConfigOut.model_validate(cfg)
|
||||
|
||||
|
||||
@@ -125,7 +124,6 @@ async def save_user_config(
|
||||
raise HTTPException(status_code=400, detail="Passwort wird beim ersten Speichern benötigt.")
|
||||
|
||||
await db.commit()
|
||||
await db.refresh(cfg)
|
||||
return CaldavUserConfigOut.model_validate(cfg)
|
||||
|
||||
|
||||
|
||||
@@ -34,7 +34,6 @@ async def create_device(
|
||||
"""Neues Kiosk-Gerät registrieren. Token wird nur einmalig zurückgegeben."""
|
||||
device, raw_token = await kiosk_service.create_device(current_user.company_id, data, db)
|
||||
await db.commit()
|
||||
await db.refresh(device)
|
||||
return KioskDeviceCreated(
|
||||
**KioskDeviceOut.model_validate(device).model_dump(),
|
||||
token=raw_token,
|
||||
@@ -59,7 +58,6 @@ async def update_device(
|
||||
):
|
||||
device = await kiosk_service.update_device(device_id, current_user.company_id, data, db)
|
||||
await db.commit()
|
||||
await db.refresh(device)
|
||||
return KioskDeviceOut.model_validate(device)
|
||||
|
||||
|
||||
@@ -72,7 +70,6 @@ async def rotate_token(
|
||||
"""Token rotieren – das alte Token wird sofort ungültig."""
|
||||
device, raw_token = await kiosk_service.rotate_token(device_id, current_user.company_id, db)
|
||||
await db.commit()
|
||||
await db.refresh(device)
|
||||
return KioskDeviceCreated(
|
||||
**KioskDeviceOut.model_validate(device).model_dump(),
|
||||
token=raw_token,
|
||||
|
||||
@@ -66,7 +66,6 @@ async def create_ldap_config(
|
||||
)
|
||||
db.add(cfg)
|
||||
await db.commit()
|
||||
await db.refresh(cfg)
|
||||
return cfg
|
||||
|
||||
|
||||
@@ -135,5 +134,4 @@ async def _apply_update(cfg: LdapConfig, updates: dict, db: AsyncSession) -> Lda
|
||||
elif hasattr(cfg, field):
|
||||
setattr(cfg, field, value)
|
||||
await db.commit()
|
||||
await db.refresh(cfg)
|
||||
return cfg
|
||||
|
||||
@@ -58,7 +58,6 @@ async def create_project(
|
||||
)
|
||||
db.add(project)
|
||||
await db.commit()
|
||||
await db.refresh(project)
|
||||
return project
|
||||
|
||||
|
||||
@@ -141,7 +140,6 @@ async def update_project(
|
||||
setattr(project, field, value)
|
||||
|
||||
await db.commit()
|
||||
await db.refresh(project)
|
||||
return project
|
||||
|
||||
|
||||
|
||||
@@ -71,7 +71,6 @@ async def save_smtp_config(
|
||||
cfg.password_encrypted = _encrypt(data.password)
|
||||
|
||||
await db.commit()
|
||||
await db.refresh(cfg)
|
||||
return SmtpConfigOut.model_validate(cfg)
|
||||
|
||||
|
||||
|
||||
@@ -39,7 +39,6 @@ async def stamp_in(
|
||||
"""Einstempeln – startet einen neuen Zeiterfassungseintrag."""
|
||||
entry, warnings = await time_service.stamp_in(current_user, data, db)
|
||||
await db.commit()
|
||||
await db.refresh(entry)
|
||||
return TimeEntryWithWarnings(entry=TimeEntryOut.model_validate(entry), warnings=warnings)
|
||||
|
||||
|
||||
@@ -52,7 +51,6 @@ async def stamp_out(
|
||||
"""Ausstempeln – schließt den offenen Zeiterfassungseintrag."""
|
||||
entry, warnings = await time_service.stamp_out(current_user, data.note, db)
|
||||
await db.commit()
|
||||
await db.refresh(entry)
|
||||
return TimeEntryWithWarnings(entry=TimeEntryOut.model_validate(entry), warnings=warnings)
|
||||
|
||||
|
||||
@@ -64,7 +62,6 @@ async def break_start(
|
||||
"""Pause beginnen."""
|
||||
entry = await time_service.break_start(current_user, db)
|
||||
await db.commit()
|
||||
await db.refresh(entry)
|
||||
return TimeEntryOut.model_validate(entry)
|
||||
|
||||
|
||||
@@ -76,7 +73,6 @@ async def break_end(
|
||||
"""Pause beenden."""
|
||||
entry = await time_service.break_end(current_user, db)
|
||||
await db.commit()
|
||||
await db.refresh(entry)
|
||||
return TimeEntryOut.model_validate(entry)
|
||||
|
||||
|
||||
@@ -122,7 +118,6 @@ async def create_manual_entry(
|
||||
"""Manuellen Zeiterfassungseintrag anlegen."""
|
||||
entry, warnings = await time_service.create_manual(data, current_user, db)
|
||||
await db.commit()
|
||||
await db.refresh(entry)
|
||||
return TimeEntryWithWarnings(entry=TimeEntryOut.model_validate(entry), warnings=warnings)
|
||||
|
||||
|
||||
@@ -136,7 +131,6 @@ async def update_entry(
|
||||
"""Zeiterfassungseintrag korrigieren."""
|
||||
entry = await time_service.update_entry(entry_id, data, current_user, db)
|
||||
await db.commit()
|
||||
await db.refresh(entry)
|
||||
return TimeEntryOut.model_validate(entry)
|
||||
|
||||
|
||||
@@ -149,7 +143,6 @@ async def approve_entry(
|
||||
"""Zeiterfassungseintrag genehmigen."""
|
||||
entry = await time_service.approve_entry(entry_id, current_user, db)
|
||||
await db.commit()
|
||||
await db.refresh(entry)
|
||||
return TimeEntryOut.model_validate(entry)
|
||||
|
||||
|
||||
@@ -163,7 +156,6 @@ async def reject_entry(
|
||||
"""Zeiterfassungseintrag ablehnen."""
|
||||
entry = await time_service.reject_entry(entry_id, current_user, data.rejection_note, db)
|
||||
await db.commit()
|
||||
await db.refresh(entry)
|
||||
return TimeEntryOut.model_validate(entry)
|
||||
|
||||
|
||||
@@ -228,7 +220,6 @@ async def create_schedule(
|
||||
):
|
||||
schedule = await time_service.create_work_schedule(current_user.company_id, data, db)
|
||||
await db.commit()
|
||||
await db.refresh(schedule)
|
||||
return WorkScheduleOut.model_validate(schedule)
|
||||
|
||||
|
||||
@@ -253,7 +244,6 @@ async def update_schedule(
|
||||
for field, value in data.model_dump().items():
|
||||
setattr(schedule, field, value)
|
||||
await db.commit()
|
||||
await db.refresh(schedule)
|
||||
return WorkScheduleOut.model_validate(schedule)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user