Bündel 3: Security-Helper + Demo-Migration, SW by-v1115
NEUE HELPER in auth.py:
require_moderator(user=Depends(get_current_user))
Konsequente Dependency statt inline
'if user["rolle"] not in ("admin", "moderator")'
require_breeder(user=Depends(get_current_user))
Konsequente Dependency statt inline
'if user["subscription_tier"] not in ("breeder", "breeder_test")'
require_owner(row, user, owner_field='user_id',
not_found_msg, forbidden_msg) -> row
Zentralisiert das häufigste Pattern (54 Stellen im Audit):
Statt:
row = conn.execute(...).fetchone()
if not row: raise HTTPException(404, ...)
if row['user_id'] != user['id']: raise HTTPException(403, ...)
Jetzt:
row = require_owner(conn.execute(...).fetchone(), user,
not_found_msg='Ort nicht gefunden.')
is_owner_or_admin(row, user, owner_field='user_id') -> bool
True wenn Owner ODER Admin/Moderator (Admin-Override für
Moderations-Endpoints)
DEMO-MIGRATION:
places.py PATCH /places/{id} + DELETE /places/{id} migriert auf
require_owner() — als Style-Referenz für künftige Migrationen.
KEINE Massen-Migration der 54 Stellen — bewusste Entscheidung
weil security-kritisch. Helper sind bereitgestellt, neuer Code
nutzt sie, bestehender bleibt funktional identisch.
Tests 19/19 grün.
Hinweis: Massen-Migration der Owner-Checks ist eigener Sprint mit
sehr sorgfältigem Testing — bei jeder migrierten Route muss die
404→403→Cascade durchgeprüft werden, dass Owner+Non-Owner+Admin
sich identisch zum Vorher verhalten.
This commit is contained in:
parent
297bd22f96
commit
35937ed51b
7 changed files with 68 additions and 27 deletions
|
|
@ -4,7 +4,7 @@ from fastapi import APIRouter, Depends, HTTPException
|
|||
from pydantic import BaseModel
|
||||
from typing import Optional
|
||||
from database import db
|
||||
from auth import get_current_user
|
||||
from auth import get_current_user, require_owner
|
||||
from math_utils import haversine_m
|
||||
|
||||
router = APIRouter()
|
||||
|
|
@ -121,11 +121,10 @@ async def get_place(place_id: int):
|
|||
@router.patch("/{place_id}")
|
||||
async def update_place(place_id: int, data: PlaceUpdate, user=Depends(get_current_user)):
|
||||
with db() as conn:
|
||||
row = conn.execute("SELECT * FROM places WHERE id = ?", (place_id,)).fetchone()
|
||||
if not row:
|
||||
raise HTTPException(404, "Ort nicht gefunden.")
|
||||
if row['user_id'] != user['id']:
|
||||
raise HTTPException(403, "Nicht berechtigt.")
|
||||
row = require_owner(
|
||||
conn.execute("SELECT * FROM places WHERE id = ?", (place_id,)).fetchone(),
|
||||
user, not_found_msg="Ort nicht gefunden.", forbidden_msg="Nicht berechtigt."
|
||||
)
|
||||
|
||||
updates = data.model_dump(exclude_none=True)
|
||||
if not updates:
|
||||
|
|
@ -150,9 +149,8 @@ async def update_place(place_id: int, data: PlaceUpdate, user=Depends(get_curren
|
|||
@router.delete("/{place_id}", status_code=204)
|
||||
async def delete_place(place_id: int, user=Depends(get_current_user)):
|
||||
with db() as conn:
|
||||
row = conn.execute("SELECT * FROM places WHERE id = ?", (place_id,)).fetchone()
|
||||
if not row:
|
||||
raise HTTPException(404, "Ort nicht gefunden.")
|
||||
if row['user_id'] != user['id']:
|
||||
raise HTTPException(403, "Nicht berechtigt.")
|
||||
require_owner(
|
||||
conn.execute("SELECT * FROM places WHERE id = ?", (place_id,)).fetchone(),
|
||||
user, not_found_msg="Ort nicht gefunden.", forbidden_msg="Nicht berechtigt."
|
||||
)
|
||||
conn.execute("DELETE FROM places WHERE id = ?", (place_id,))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue