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
+77 -55
View File
@@ -955,64 +955,80 @@ 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,
) )
snapshot_settings = _resolve_snapshot_settings(
request.app.frigate_config.cameras[event.camera].snapshots, params
)
jpg_bytes, frame_time = get_event_snapshot_bytes(
event,
ext="jpg",
timestamp=snapshot_settings["timestamp"],
bounding_box=snapshot_settings["bounding_box"],
crop=snapshot_settings["crop"],
height=snapshot_settings["height"],
quality=snapshot_settings["quality"],
timestamp_style=request.app.frigate_config.cameras[
event.camera
].timestamp_style,
colormap=request.app.frigate_config.model_for_camera(event.camera).colormap,
)
except DoesNotExist:
# see if the object is currently being tracked
try: try:
camera_states: list[CameraState] = ( snapshot_settings = _resolve_snapshot_settings(
request.app.detected_frames_processor.get_camera_states() request.app.frigate_config.cameras[event.camera].snapshots, params
)
jpg_bytes, frame_time = get_event_snapshot_bytes(
event,
ext="jpg",
timestamp=snapshot_settings["timestamp"],
bounding_box=snapshot_settings["bounding_box"],
crop=snapshot_settings["crop"],
height=snapshot_settings["height"],
quality=snapshot_settings["quality"],
timestamp_style=request.app.frigate_config.cameras[
event.camera
].timestamp_style,
colormap=request.app.frigate_config.model_for_camera(
event.camera
).colormap,
) )
for camera_state in camera_states:
if event_id in camera_state.tracked_objects:
tracked_obj = camera_state.tracked_objects.get(event_id)
if tracked_obj is not None:
snapshot_settings = _resolve_snapshot_settings(
camera_state.camera_config.snapshots, params
)
jpg_bytes, frame_time = tracked_obj.get_img_bytes(
ext="jpg",
timestamp=snapshot_settings["timestamp"],
bounding_box=snapshot_settings["bounding_box"],
crop=snapshot_settings["crop"],
height=snapshot_settings["height"],
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": "Unknown error occurred"},
status_code=404, status_code=404,
) )
except Exception: else:
return JSONResponse( # see if the object is currently being tracked
content={"success": False, "message": "Unknown error occurred"}, camera_states: list[CameraState] = (
status_code=404, request.app.detected_frames_processor.get_camera_states()
) )
for camera_state in camera_states:
tracked_obj = camera_state.tracked_objects.get(event_id)
if tracked_obj is None:
continue
await require_camera_access(camera_state.name, request=request)
try:
snapshot_settings = _resolve_snapshot_settings(
camera_state.camera_config.snapshots, params
)
jpg_bytes, frame_time = tracked_obj.get_img_bytes(
ext="jpg",
timestamp=snapshot_settings["timestamp"],
bounding_box=snapshot_settings["bounding_box"],
crop=snapshot_settings["crop"],
height=snapshot_settings["height"],
quality=snapshot_settings["quality"],
)
except Exception:
return JSONResponse(
content={"success": False, "message": "Ongoing event not found"},
status_code=404,
)
break
if jpg_bytes is None: if jpg_bytes is None:
return JSONResponse( return JSONResponse(
content={"success": False, "message": "Live frame not available"}, content={"success": False, "message": "Live frame not available"},
@@ -1061,19 +1077,25 @@ 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:
await require_camera_access(camera_state.name, request=request) continue
thumbnail_bytes = tracked_obj.get_thumbnail(extension.value)
except Exception: await require_camera_access(camera_state.name, request=request)
return JSONResponse(
content={"success": False, "message": "Event not found"}, try:
status_code=404, thumbnail_bytes = tracked_obj.get_thumbnail(extension.value)
) except Exception:
return JSONResponse(
content={"success": False, "message": "Event not found"},
status_code=404,
)
break
if not thumbnail_bytes: if not thumbnail_bytes:
return JSONResponse( return JSONResponse(