mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-03-25 17:48:38 +03:00
Fix cross-camera auth in timeline and media endpoints (#22522)
Some checks are pending
CI / AMD64 Build (push) Waiting to run
CI / ARM Build (push) Waiting to run
CI / Jetson Jetpack 6 (push) Waiting to run
CI / AMD64 Extra Build (push) Blocked by required conditions
CI / ARM Extra Build (push) Blocked by required conditions
CI / Synaptics Build (push) Blocked by required conditions
CI / Assemble and push default build (push) Blocked by required conditions
Some checks are pending
CI / AMD64 Build (push) Waiting to run
CI / ARM Build (push) Waiting to run
CI / Jetson Jetpack 6 (push) Waiting to run
CI / AMD64 Extra Build (push) Blocked by required conditions
CI / ARM Extra Build (push) Blocked by required conditions
CI / Synaptics Build (push) Blocked by required conditions
CI / Assemble and push default build (push) Blocked by required conditions
* Fix cross-camera authorization bypass in timeline and event media endpoints * formatting
This commit is contained in:
parent
e78da2758d
commit
d11c26970d
@ -732,7 +732,12 @@ def get_recognized_license_plates(
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/timeline", dependencies=[Depends(allow_any_authenticated())])
|
@router.get("/timeline", dependencies=[Depends(allow_any_authenticated())])
|
||||||
def timeline(camera: str = "all", limit: int = 100, source_id: Optional[str] = None):
|
def timeline(
|
||||||
|
camera: str = "all",
|
||||||
|
limit: int = 100,
|
||||||
|
source_id: Optional[str] = None,
|
||||||
|
allowed_cameras: List[str] = Depends(get_allowed_cameras_for_filter),
|
||||||
|
):
|
||||||
clauses = []
|
clauses = []
|
||||||
|
|
||||||
selected_columns = [
|
selected_columns = [
|
||||||
@ -754,6 +759,9 @@ def timeline(camera: str = "all", limit: int = 100, source_id: Optional[str] = N
|
|||||||
else:
|
else:
|
||||||
clauses.append((Timeline.source_id.in_(source_ids)))
|
clauses.append((Timeline.source_id.in_(source_ids)))
|
||||||
|
|
||||||
|
# Enforce per-camera access control
|
||||||
|
clauses.append((Timeline.camera << allowed_cameras))
|
||||||
|
|
||||||
if len(clauses) == 0:
|
if len(clauses) == 0:
|
||||||
clauses.append((True))
|
clauses.append((True))
|
||||||
|
|
||||||
@ -769,7 +777,10 @@ def timeline(camera: str = "all", limit: int = 100, source_id: Optional[str] = N
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/timeline/hourly", dependencies=[Depends(allow_any_authenticated())])
|
@router.get("/timeline/hourly", dependencies=[Depends(allow_any_authenticated())])
|
||||||
def hourly_timeline(params: AppTimelineHourlyQueryParameters = Depends()):
|
def hourly_timeline(
|
||||||
|
params: AppTimelineHourlyQueryParameters = Depends(),
|
||||||
|
allowed_cameras: List[str] = Depends(get_allowed_cameras_for_filter),
|
||||||
|
):
|
||||||
"""Get hourly summary for timeline."""
|
"""Get hourly summary for timeline."""
|
||||||
cameras = params.cameras
|
cameras = params.cameras
|
||||||
labels = params.labels
|
labels = params.labels
|
||||||
@ -787,6 +798,9 @@ def hourly_timeline(params: AppTimelineHourlyQueryParameters = Depends()):
|
|||||||
camera_list = cameras.split(",")
|
camera_list = cameras.split(",")
|
||||||
clauses.append((Timeline.camera << camera_list))
|
clauses.append((Timeline.camera << camera_list))
|
||||||
|
|
||||||
|
# Enforce per-camera access control
|
||||||
|
clauses.append((Timeline.camera << allowed_cameras))
|
||||||
|
|
||||||
if labels != "all":
|
if labels != "all":
|
||||||
label_list = labels.split(",")
|
label_list = labels.split(",")
|
||||||
clauses.append((Timeline.data["label"] << label_list))
|
clauses.append((Timeline.data["label"] << label_list))
|
||||||
|
|||||||
@ -1142,7 +1142,6 @@ async def event_snapshot(
|
|||||||
|
|
||||||
@router.get(
|
@router.get(
|
||||||
"/events/{event_id}/thumbnail.{extension}",
|
"/events/{event_id}/thumbnail.{extension}",
|
||||||
dependencies=[Depends(require_camera_access)],
|
|
||||||
)
|
)
|
||||||
async def event_thumbnail(
|
async def event_thumbnail(
|
||||||
request: Request,
|
request: Request,
|
||||||
@ -1344,12 +1343,12 @@ def grid_snapshot(
|
|||||||
|
|
||||||
@router.get(
|
@router.get(
|
||||||
"/events/{event_id}/snapshot-clean.webp",
|
"/events/{event_id}/snapshot-clean.webp",
|
||||||
dependencies=[Depends(require_camera_access)],
|
|
||||||
)
|
)
|
||||||
def event_snapshot_clean(request: Request, event_id: str, download: bool = False):
|
async def event_snapshot_clean(request: Request, event_id: str, download: bool = False):
|
||||||
webp_bytes = None
|
webp_bytes = None
|
||||||
try:
|
try:
|
||||||
event = Event.get(Event.id == event_id)
|
event = Event.get(Event.id == event_id)
|
||||||
|
await require_camera_access(event.camera, request=request)
|
||||||
snapshot_config = request.app.frigate_config.cameras[event.camera].snapshots
|
snapshot_config = request.app.frigate_config.cameras[event.camera].snapshots
|
||||||
if not (snapshot_config.enabled and event.has_snapshot):
|
if not (snapshot_config.enabled and event.has_snapshot):
|
||||||
return JSONResponse(
|
return JSONResponse(
|
||||||
@ -1470,7 +1469,7 @@ def event_snapshot_clean(request: Request, event_id: str, download: bool = False
|
|||||||
|
|
||||||
|
|
||||||
@router.get(
|
@router.get(
|
||||||
"/events/{event_id}/clip.mp4", dependencies=[Depends(require_camera_access)]
|
"/events/{event_id}/clip.mp4",
|
||||||
)
|
)
|
||||||
async def event_clip(
|
async def event_clip(
|
||||||
request: Request,
|
request: Request,
|
||||||
@ -1484,6 +1483,8 @@ async def event_clip(
|
|||||||
content={"success": False, "message": "Event not found"}, status_code=404
|
content={"success": False, "message": "Event not found"}, status_code=404
|
||||||
)
|
)
|
||||||
|
|
||||||
|
await require_camera_access(event.camera, request=request)
|
||||||
|
|
||||||
if not event.has_clip:
|
if not event.has_clip:
|
||||||
return JSONResponse(
|
return JSONResponse(
|
||||||
content={"success": False, "message": "Clip not available"}, status_code=404
|
content={"success": False, "message": "Clip not available"}, status_code=404
|
||||||
@ -1500,9 +1501,9 @@ async def event_clip(
|
|||||||
|
|
||||||
|
|
||||||
@router.get(
|
@router.get(
|
||||||
"/events/{event_id}/preview.gif", dependencies=[Depends(require_camera_access)]
|
"/events/{event_id}/preview.gif",
|
||||||
)
|
)
|
||||||
def event_preview(request: Request, event_id: str):
|
async def event_preview(request: Request, event_id: str):
|
||||||
try:
|
try:
|
||||||
event: Event = Event.get(Event.id == event_id)
|
event: Event = Event.get(Event.id == event_id)
|
||||||
except DoesNotExist:
|
except DoesNotExist:
|
||||||
@ -1510,6 +1511,8 @@ def event_preview(request: Request, event_id: str):
|
|||||||
content={"success": False, "message": "Event not found"}, status_code=404
|
content={"success": False, "message": "Event not found"}, status_code=404
|
||||||
)
|
)
|
||||||
|
|
||||||
|
await require_camera_access(event.camera, request=request)
|
||||||
|
|
||||||
start_ts = event.start_time
|
start_ts = event.start_time
|
||||||
end_ts = start_ts + (
|
end_ts = start_ts + (
|
||||||
min(event.end_time - event.start_time, 20) if event.end_time else 20
|
min(event.end_time - event.start_time, 20) if event.end_time else 20
|
||||||
@ -1854,8 +1857,8 @@ def preview_mp4(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/review/{event_id}/preview", dependencies=[Depends(require_camera_access)])
|
@router.get("/review/{event_id}/preview")
|
||||||
def review_preview(
|
async def review_preview(
|
||||||
request: Request,
|
request: Request,
|
||||||
event_id: str,
|
event_id: str,
|
||||||
format: str = Query(default="gif", enum=["gif", "mp4"]),
|
format: str = Query(default="gif", enum=["gif", "mp4"]),
|
||||||
@ -1868,6 +1871,8 @@ def review_preview(
|
|||||||
status_code=404,
|
status_code=404,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
await require_camera_access(review.camera, request=request)
|
||||||
|
|
||||||
padding = 8
|
padding = 8
|
||||||
start_ts = review.start_time - padding
|
start_ts = review.start_time - padding
|
||||||
end_ts = (
|
end_ts = (
|
||||||
@ -1881,10 +1886,12 @@ def review_preview(
|
|||||||
|
|
||||||
|
|
||||||
@router.get(
|
@router.get(
|
||||||
"/preview/{file_name}/thumbnail.jpg", dependencies=[Depends(require_camera_access)]
|
"/preview/{file_name}/thumbnail.jpg",
|
||||||
|
dependencies=[Depends(allow_any_authenticated())],
|
||||||
)
|
)
|
||||||
@router.get(
|
@router.get(
|
||||||
"/preview/{file_name}/thumbnail.webp", dependencies=[Depends(require_camera_access)]
|
"/preview/{file_name}/thumbnail.webp",
|
||||||
|
dependencies=[Depends(allow_any_authenticated())],
|
||||||
)
|
)
|
||||||
def preview_thumbnail(file_name: str):
|
def preview_thumbnail(file_name: str):
|
||||||
"""Get a thumbnail from the cached preview frames."""
|
"""Get a thumbnail from the cached preview frames."""
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user