mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-04-15 11:32:09 +03:00
protect review endpoints
This commit is contained in:
parent
ad21495aeb
commit
39cb3f7217
@ -4,6 +4,7 @@ import datetime
|
|||||||
import logging
|
import logging
|
||||||
from functools import reduce
|
from functools import reduce
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import List
|
||||||
|
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
from fastapi import APIRouter, Request
|
from fastapi import APIRouter, Request
|
||||||
@ -12,7 +13,12 @@ from fastapi.responses import JSONResponse
|
|||||||
from peewee import Case, DoesNotExist, IntegrityError, fn, operator
|
from peewee import Case, DoesNotExist, IntegrityError, fn, operator
|
||||||
from playhouse.shortcuts import model_to_dict
|
from playhouse.shortcuts import model_to_dict
|
||||||
|
|
||||||
from frigate.api.auth import get_current_user, require_role
|
from frigate.api.auth import (
|
||||||
|
get_allowed_cameras_for_filter,
|
||||||
|
get_current_user,
|
||||||
|
require_camera_access,
|
||||||
|
require_role,
|
||||||
|
)
|
||||||
from frigate.api.defs.query.review_query_parameters import (
|
from frigate.api.defs.query.review_query_parameters import (
|
||||||
ReviewActivityMotionQueryParams,
|
ReviewActivityMotionQueryParams,
|
||||||
ReviewQueryParams,
|
ReviewQueryParams,
|
||||||
@ -41,6 +47,7 @@ router = APIRouter(tags=[Tags.review])
|
|||||||
async def review(
|
async def review(
|
||||||
params: ReviewQueryParams = Depends(),
|
params: ReviewQueryParams = Depends(),
|
||||||
current_user: dict = Depends(get_current_user),
|
current_user: dict = Depends(get_current_user),
|
||||||
|
allowed_cameras: List[str] = Depends(get_allowed_cameras_for_filter),
|
||||||
):
|
):
|
||||||
if isinstance(current_user, JSONResponse):
|
if isinstance(current_user, JSONResponse):
|
||||||
return current_user
|
return current_user
|
||||||
@ -65,8 +72,14 @@ async def review(
|
|||||||
]
|
]
|
||||||
|
|
||||||
if cameras != "all":
|
if cameras != "all":
|
||||||
camera_list = cameras.split(",")
|
requested = set(cameras.split(","))
|
||||||
clauses.append((ReviewSegment.camera << camera_list))
|
filtered = requested.intersection(allowed_cameras)
|
||||||
|
if not filtered:
|
||||||
|
return JSONResponse(content=[])
|
||||||
|
camera_list = list(filtered)
|
||||||
|
else:
|
||||||
|
camera_list = allowed_cameras
|
||||||
|
clauses.append((ReviewSegment.camera << camera_list))
|
||||||
|
|
||||||
if labels != "all":
|
if labels != "all":
|
||||||
# use matching so segments with multiple labels
|
# use matching so segments with multiple labels
|
||||||
@ -149,6 +162,18 @@ def review_ids(ids: str):
|
|||||||
status_code=400,
|
status_code=400,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
for review_id in ids:
|
||||||
|
try:
|
||||||
|
review = ReviewSegment.get(ReviewSegment.id == review_id)
|
||||||
|
require_camera_access(review.camera)
|
||||||
|
except DoesNotExist:
|
||||||
|
return JSONResponse(
|
||||||
|
content=(
|
||||||
|
{"success": False, "message": f"Review {review_id} not found"}
|
||||||
|
),
|
||||||
|
status_code=404,
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
reviews = (
|
reviews = (
|
||||||
ReviewSegment.select().where(ReviewSegment.id << ids).dicts().iterator()
|
ReviewSegment.select().where(ReviewSegment.id << ids).dicts().iterator()
|
||||||
@ -165,6 +190,7 @@ def review_ids(ids: str):
|
|||||||
async def review_summary(
|
async def review_summary(
|
||||||
params: ReviewSummaryQueryParams = Depends(),
|
params: ReviewSummaryQueryParams = Depends(),
|
||||||
current_user: dict = Depends(get_current_user),
|
current_user: dict = Depends(get_current_user),
|
||||||
|
allowed_cameras: List[str] = Depends(get_allowed_cameras_for_filter),
|
||||||
):
|
):
|
||||||
if isinstance(current_user, JSONResponse):
|
if isinstance(current_user, JSONResponse):
|
||||||
return current_user
|
return current_user
|
||||||
@ -181,8 +207,14 @@ async def review_summary(
|
|||||||
clauses = [(ReviewSegment.start_time > day_ago)]
|
clauses = [(ReviewSegment.start_time > day_ago)]
|
||||||
|
|
||||||
if cameras != "all":
|
if cameras != "all":
|
||||||
camera_list = cameras.split(",")
|
requested = set(cameras.split(","))
|
||||||
clauses.append((ReviewSegment.camera << camera_list))
|
filtered = requested.intersection(allowed_cameras)
|
||||||
|
if not filtered:
|
||||||
|
return JSONResponse(content={})
|
||||||
|
camera_list = list(filtered)
|
||||||
|
else:
|
||||||
|
camera_list = allowed_cameras
|
||||||
|
clauses.append((ReviewSegment.camera << camera_list))
|
||||||
|
|
||||||
if labels != "all":
|
if labels != "all":
|
||||||
# use matching so segments with multiple labels
|
# use matching so segments with multiple labels
|
||||||
@ -276,8 +308,14 @@ async def review_summary(
|
|||||||
clauses = []
|
clauses = []
|
||||||
|
|
||||||
if cameras != "all":
|
if cameras != "all":
|
||||||
camera_list = cameras.split(",")
|
requested = set(cameras.split(","))
|
||||||
clauses.append((ReviewSegment.camera << camera_list))
|
filtered = requested.intersection(allowed_cameras)
|
||||||
|
if not filtered:
|
||||||
|
return JSONResponse(content={})
|
||||||
|
camera_list = list(filtered)
|
||||||
|
else:
|
||||||
|
camera_list = allowed_cameras
|
||||||
|
clauses.append((ReviewSegment.camera << camera_list))
|
||||||
|
|
||||||
if labels != "all":
|
if labels != "all":
|
||||||
# use matching so segments with multiple labels
|
# use matching so segments with multiple labels
|
||||||
@ -390,6 +428,8 @@ async def set_multiple_reviewed(
|
|||||||
|
|
||||||
for review_id in body.ids:
|
for review_id in body.ids:
|
||||||
try:
|
try:
|
||||||
|
review = ReviewSegment.get(ReviewSegment.id == review_id)
|
||||||
|
require_camera_access(review.camera)
|
||||||
review_status = UserReviewStatus.get(
|
review_status = UserReviewStatus.get(
|
||||||
UserReviewStatus.user_id == user_id,
|
UserReviewStatus.user_id == user_id,
|
||||||
UserReviewStatus.review_segment == review_id,
|
UserReviewStatus.review_segment == review_id,
|
||||||
@ -421,6 +461,18 @@ async def set_multiple_reviewed(
|
|||||||
)
|
)
|
||||||
def delete_reviews(body: ReviewModifyMultipleBody):
|
def delete_reviews(body: ReviewModifyMultipleBody):
|
||||||
list_of_ids = body.ids
|
list_of_ids = body.ids
|
||||||
|
for review_id in list_of_ids:
|
||||||
|
try:
|
||||||
|
review = ReviewSegment.get(ReviewSegment.id == review_id)
|
||||||
|
require_camera_access(review.camera)
|
||||||
|
except DoesNotExist:
|
||||||
|
return JSONResponse(
|
||||||
|
content=(
|
||||||
|
{"success": False, "message": f"Review {review_id} not found"}
|
||||||
|
),
|
||||||
|
status_code=404,
|
||||||
|
)
|
||||||
|
list_of_ids = body.ids
|
||||||
reviews = (
|
reviews = (
|
||||||
ReviewSegment.select(
|
ReviewSegment.select(
|
||||||
ReviewSegment.camera,
|
ReviewSegment.camera,
|
||||||
@ -471,7 +523,10 @@ def delete_reviews(body: ReviewModifyMultipleBody):
|
|||||||
@router.get(
|
@router.get(
|
||||||
"/review/activity/motion", response_model=list[ReviewActivityMotionResponse]
|
"/review/activity/motion", response_model=list[ReviewActivityMotionResponse]
|
||||||
)
|
)
|
||||||
def motion_activity(params: ReviewActivityMotionQueryParams = Depends()):
|
def motion_activity(
|
||||||
|
params: ReviewActivityMotionQueryParams = Depends(),
|
||||||
|
allowed_cameras: List[str] = Depends(get_allowed_cameras_for_filter),
|
||||||
|
):
|
||||||
"""Get motion and audio activity."""
|
"""Get motion and audio activity."""
|
||||||
cameras = params.cameras
|
cameras = params.cameras
|
||||||
before = params.before or datetime.datetime.now().timestamp()
|
before = params.before or datetime.datetime.now().timestamp()
|
||||||
@ -486,8 +541,14 @@ def motion_activity(params: ReviewActivityMotionQueryParams = Depends()):
|
|||||||
clauses.append((Recordings.motion > 0))
|
clauses.append((Recordings.motion > 0))
|
||||||
|
|
||||||
if cameras != "all":
|
if cameras != "all":
|
||||||
camera_list = cameras.split(",")
|
requested = set(cameras.split(","))
|
||||||
|
filtered = requested.intersection(allowed_cameras)
|
||||||
|
if not filtered:
|
||||||
|
return JSONResponse(content=[])
|
||||||
|
camera_list = list(filtered)
|
||||||
clauses.append((Recordings.camera << camera_list))
|
clauses.append((Recordings.camera << camera_list))
|
||||||
|
else:
|
||||||
|
clauses.append((Recordings.camera << allowed_cameras))
|
||||||
|
|
||||||
data: list[Recordings] = (
|
data: list[Recordings] = (
|
||||||
Recordings.select(
|
Recordings.select(
|
||||||
@ -547,13 +608,11 @@ def motion_activity(params: ReviewActivityMotionQueryParams = Depends()):
|
|||||||
@router.get("/review/event/{event_id}", response_model=ReviewSegmentResponse)
|
@router.get("/review/event/{event_id}", response_model=ReviewSegmentResponse)
|
||||||
def get_review_from_event(event_id: str):
|
def get_review_from_event(event_id: str):
|
||||||
try:
|
try:
|
||||||
return JSONResponse(
|
review = ReviewSegment.get(
|
||||||
model_to_dict(
|
ReviewSegment.data["detections"].cast("text") % f'*"{event_id}"*'
|
||||||
ReviewSegment.get(
|
|
||||||
ReviewSegment.data["detections"].cast("text") % f'*"{event_id}"*'
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
require_camera_access(review.camera)
|
||||||
|
return JSONResponse(model_to_dict(review))
|
||||||
except DoesNotExist:
|
except DoesNotExist:
|
||||||
return JSONResponse(
|
return JSONResponse(
|
||||||
content={"success": False, "message": "Review item not found"},
|
content={"success": False, "message": "Review item not found"},
|
||||||
@ -564,9 +623,9 @@ def get_review_from_event(event_id: str):
|
|||||||
@router.get("/review/{review_id}", response_model=ReviewSegmentResponse)
|
@router.get("/review/{review_id}", response_model=ReviewSegmentResponse)
|
||||||
def get_review(review_id: str):
|
def get_review(review_id: str):
|
||||||
try:
|
try:
|
||||||
return JSONResponse(
|
review = ReviewSegment.get(ReviewSegment.id == review_id)
|
||||||
content=model_to_dict(ReviewSegment.get(ReviewSegment.id == review_id))
|
require_camera_access(review.camera)
|
||||||
)
|
return JSONResponse(content=model_to_dict(review))
|
||||||
except DoesNotExist:
|
except DoesNotExist:
|
||||||
return JSONResponse(
|
return JSONResponse(
|
||||||
content={"success": False, "message": "Review item not found"},
|
content={"success": False, "message": "Review item not found"},
|
||||||
@ -613,6 +672,7 @@ async def set_not_reviewed(
|
|||||||
@router.post(
|
@router.post(
|
||||||
"/review/summarize/start/{start_ts}/end/{end_ts}",
|
"/review/summarize/start/{start_ts}/end/{end_ts}",
|
||||||
description="Use GenAI to summarize review items over a period of time.",
|
description="Use GenAI to summarize review items over a period of time.",
|
||||||
|
dependencies=[Depends(require_camera_access)],
|
||||||
)
|
)
|
||||||
def generate_review_summary(request: Request, start_ts: float, end_ts: float):
|
def generate_review_summary(request: Request, start_ts: float, end_ts: float):
|
||||||
config: FrigateConfig = request.app.frigate_config
|
config: FrigateConfig = request.app.frigate_config
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user