return 403 for a snapshot or thumbnail on another camera

The broad `except Exception` handlers in `event_snapshot` and `event_thumbnail` caught the `HTTPException` from `require_camera_access`, so a restricted user asking for another camera's snapshot got a 404 instead of a 403, and for an object still being tracked the snapshot was rendered before the check ran. Both endpoints now look up the event and check access in their own block, the way the other endpoints do, so a denial propagates.
This commit is contained in:
Josh Hawkins
2026-09-18 06:55:36 -05:00
parent ee35be19ac
commit 54ba07917d
+37 -15
View File
@@ -955,15 +955,23 @@ async def event_snapshot(
event_complete = False event_complete = False
jpg_bytes = None jpg_bytes = None
frame_time = 0 frame_time = 0
try: try:
event = Event.get(Event.id == event_id, Event.end_time != None) event = Event.get(Event.id == event_id, Event.end_time != None)
event_complete = True
await require_camera_access(event.camera, request=request) await require_camera_access(event.camera, request=request)
except DoesNotExist:
event = None
if event is not None:
event_complete = True
if not event.has_snapshot: if not event.has_snapshot:
return JSONResponse( return JSONResponse(
content={"success": False, "message": "Snapshot not available"}, content={"success": False, "message": "Snapshot not available"},
status_code=404, status_code=404,
) )
try:
snapshot_settings = _resolve_snapshot_settings( snapshot_settings = _resolve_snapshot_settings(
request.app.frigate_config.cameras[event.camera].snapshots, params request.app.frigate_config.cameras[event.camera].snapshots, params
) )
@@ -978,18 +986,30 @@ async def event_snapshot(
timestamp_style=request.app.frigate_config.cameras[ timestamp_style=request.app.frigate_config.cameras[
event.camera event.camera
].timestamp_style, ].timestamp_style,
colormap=request.app.frigate_config.model_for_camera(event.camera).colormap, colormap=request.app.frigate_config.model_for_camera(
event.camera
).colormap,
) )
except DoesNotExist: except Exception:
return JSONResponse(
content={"success": False, "message": "Unknown error occurred"},
status_code=404,
)
else:
# see if the object is currently being tracked # see if the object is currently being tracked
try:
camera_states: list[CameraState] = ( camera_states: list[CameraState] = (
request.app.detected_frames_processor.get_camera_states() request.app.detected_frames_processor.get_camera_states()
) )
for camera_state in camera_states: for camera_state in camera_states:
if event_id in camera_state.tracked_objects:
tracked_obj = camera_state.tracked_objects.get(event_id) tracked_obj = camera_state.tracked_objects.get(event_id)
if tracked_obj is not None:
if tracked_obj is None:
continue
await require_camera_access(camera_state.name, request=request)
try:
snapshot_settings = _resolve_snapshot_settings( snapshot_settings = _resolve_snapshot_settings(
camera_state.camera_config.snapshots, params camera_state.camera_config.snapshots, params
) )
@@ -1001,17 +1021,13 @@ async def event_snapshot(
height=snapshot_settings["height"], height=snapshot_settings["height"],
quality=snapshot_settings["quality"], quality=snapshot_settings["quality"],
) )
await require_camera_access(camera_state.name, request=request)
except Exception: except Exception:
return JSONResponse( return JSONResponse(
content={"success": False, "message": "Ongoing event not found"}, content={"success": False, "message": "Ongoing event not found"},
status_code=404, status_code=404,
) )
except Exception:
return JSONResponse( break
content={"success": False, "message": "Unknown error occurred"},
status_code=404,
)
if jpg_bytes is None: if jpg_bytes is None:
return JSONResponse( return JSONResponse(
@@ -1061,13 +1077,17 @@ async def event_thumbnail(
if not thumbnail_bytes: if not thumbnail_bytes:
# see if the object is currently being tracked # see if the object is currently being tracked
try:
camera_states = request.app.detected_frames_processor.get_camera_states() camera_states = request.app.detected_frames_processor.get_camera_states()
for camera_state in camera_states: for camera_state in camera_states:
if event_id in camera_state.tracked_objects:
tracked_obj = camera_state.tracked_objects.get(event_id) tracked_obj = camera_state.tracked_objects.get(event_id)
if tracked_obj is not None:
if tracked_obj is None:
continue
await require_camera_access(camera_state.name, request=request) await require_camera_access(camera_state.name, request=request)
try:
thumbnail_bytes = tracked_obj.get_thumbnail(extension.value) thumbnail_bytes = tracked_obj.get_thumbnail(extension.value)
except Exception: except Exception:
return JSONResponse( return JSONResponse(
@@ -1075,6 +1095,8 @@ async def event_thumbnail(
status_code=404, status_code=404,
) )
break
if not thumbnail_bytes: if not thumbnail_bytes:
return JSONResponse( return JSONResponse(
content={"success": False, "message": "Event not found"}, content={"success": False, "message": "Event not found"},