diff --git a/frigate/api/app.py b/frigate/api/app.py index 5c56ffadea..51527f5b78 100644 --- a/frigate/api/app.py +++ b/frigate/api/app.py @@ -835,7 +835,7 @@ def nvinfo(): @router.get( "/logs/{service}", tags=[Tags.logs], - dependencies=[Depends(allow_any_authenticated())], + dependencies=[Depends(require_role(["admin"]))], ) async def logs( service: str = Path(enum=["frigate", "nginx", "go2rtc"]), @@ -1040,12 +1040,27 @@ def get_media_sync_status(job_id: str): @router.get("/labels", dependencies=[Depends(allow_any_authenticated())]) -def get_labels(camera: str = ""): +def get_labels( + camera: str = "", + allowed_cameras: List[str] = Depends(get_allowed_cameras_for_filter), +): try: if camera: + if camera not in allowed_cameras: + return JSONResponse( + content={ + "success": False, + "message": f"Access denied to camera '{camera}'", + }, + status_code=403, + ) events = Event.select(Event.label).where(Event.camera == camera).distinct() else: - events = Event.select(Event.label).distinct() + events = ( + Event.select(Event.label) + .where(Event.camera << allowed_cameras) + .distinct() + ) except Exception as e: logger.error(e) return JSONResponse( @@ -1058,9 +1073,16 @@ def get_labels(camera: str = ""): @router.get("/sub_labels", dependencies=[Depends(allow_any_authenticated())]) -def get_sub_labels(split_joined: Optional[int] = None): +def get_sub_labels( + split_joined: Optional[int] = None, + allowed_cameras: List[str] = Depends(get_allowed_cameras_for_filter), +): try: - events = Event.select(Event.sub_label).distinct() + events = ( + Event.select(Event.sub_label) + .where(Event.camera << allowed_cameras) + .distinct() + ) except Exception: return JSONResponse( content=({"success": False, "message": "Failed to get sub_labels"}), diff --git a/frigate/api/camera.py b/frigate/api/camera.py index 7a3b19439e..54d508cbd2 100644 --- a/frigate/api/camera.py +++ b/frigate/api/camera.py @@ -19,7 +19,9 @@ from zeep.exceptions import Fault, TransportError from zeep.transports import AsyncTransport from frigate.api.auth import ( + _get_stream_owner_cameras, allow_any_authenticated, + get_current_user, require_go2rtc_stream_access, require_role, ) @@ -31,6 +33,7 @@ from frigate.config.camera.updater import ( CameraConfigUpdateTopic, ) from frigate.config.env import substitute_frigate_vars +from frigate.models import User from frigate.util.builtin import clean_camera_user_pass from frigate.util.camera_cleanup import cleanup_camera_db, cleanup_camera_files from frigate.util.config import find_config_file @@ -66,7 +69,7 @@ def _is_valid_host(host: str) -> bool: @router.get("/go2rtc/streams", dependencies=[Depends(allow_any_authenticated())]) -def go2rtc_streams(): +async def go2rtc_streams(request: Request): r = requests.get("http://127.0.0.1:1984/api/streams") if not r.ok: logger.error("Failed to fetch streams from go2rtc") @@ -75,6 +78,24 @@ def go2rtc_streams(): status_code=500, ) stream_data = r.json() + + # Roles with an explicit camera list see only streams owned by an allowed + # camera. Admin and full-access roles (no list / empty list) see all streams. + current_user = await get_current_user(request) + if not isinstance(current_user, JSONResponse): + role = current_user["role"] + roles_dict = request.app.frigate_config.auth.roles + if role != "admin" and roles_dict.get(role): + all_camera_names = set(request.app.frigate_config.cameras.keys()) + allowed_cameras = set( + User.get_allowed_cameras(role, roles_dict, all_camera_names) + ) + stream_data = { + name: data + for name, data in stream_data.items() + if _get_stream_owner_cameras(request, name) & allowed_cameras + } + for data in stream_data.values(): for producer in data.get("producers") or []: producer["url"] = clean_camera_user_pass(producer.get("url", "")) @@ -966,7 +987,6 @@ async def onvif_probe( probe = ffprobe_stream( request.app.frigate_config.ffmpeg, test_uri, detailed=False ) - print(probe) ok = probe is not None and getattr(probe, "returncode", 1) == 0 tested_candidates.append( {