restrict viewer access to logs, labels, and go2rtc stream list

This commit is contained in:
Josh Hawkins 2026-05-13 08:00:56 -05:00
parent 19ec6fa245
commit fc4910f817
2 changed files with 49 additions and 7 deletions

View File

@ -835,7 +835,7 @@ def nvinfo():
@router.get( @router.get(
"/logs/{service}", "/logs/{service}",
tags=[Tags.logs], tags=[Tags.logs],
dependencies=[Depends(allow_any_authenticated())], dependencies=[Depends(require_role(["admin"]))],
) )
async def logs( async def logs(
service: str = Path(enum=["frigate", "nginx", "go2rtc"]), 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())]) @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: try:
if camera: 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() events = Event.select(Event.label).where(Event.camera == camera).distinct()
else: else:
events = Event.select(Event.label).distinct() events = (
Event.select(Event.label)
.where(Event.camera << allowed_cameras)
.distinct()
)
except Exception as e: except Exception as e:
logger.error(e) logger.error(e)
return JSONResponse( return JSONResponse(
@ -1058,9 +1073,16 @@ def get_labels(camera: str = ""):
@router.get("/sub_labels", dependencies=[Depends(allow_any_authenticated())]) @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: try:
events = Event.select(Event.sub_label).distinct() events = (
Event.select(Event.sub_label)
.where(Event.camera << allowed_cameras)
.distinct()
)
except Exception: except Exception:
return JSONResponse( return JSONResponse(
content=({"success": False, "message": "Failed to get sub_labels"}), content=({"success": False, "message": "Failed to get sub_labels"}),

View File

@ -19,7 +19,9 @@ from zeep.exceptions import Fault, TransportError
from zeep.transports import AsyncTransport from zeep.transports import AsyncTransport
from frigate.api.auth import ( from frigate.api.auth import (
_get_stream_owner_cameras,
allow_any_authenticated, allow_any_authenticated,
get_current_user,
require_go2rtc_stream_access, require_go2rtc_stream_access,
require_role, require_role,
) )
@ -31,6 +33,7 @@ from frigate.config.camera.updater import (
CameraConfigUpdateTopic, CameraConfigUpdateTopic,
) )
from frigate.config.env import substitute_frigate_vars 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.builtin import clean_camera_user_pass
from frigate.util.camera_cleanup import cleanup_camera_db, cleanup_camera_files from frigate.util.camera_cleanup import cleanup_camera_db, cleanup_camera_files
from frigate.util.config import find_config_file 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())]) @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") r = requests.get("http://127.0.0.1:1984/api/streams")
if not r.ok: if not r.ok:
logger.error("Failed to fetch streams from go2rtc") logger.error("Failed to fetch streams from go2rtc")
@ -75,6 +78,24 @@ def go2rtc_streams():
status_code=500, status_code=500,
) )
stream_data = r.json() 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 data in stream_data.values():
for producer in data.get("producers") or []: for producer in data.get("producers") or []:
producer["url"] = clean_camera_user_pass(producer.get("url", "")) producer["url"] = clean_camera_user_pass(producer.get("url", ""))
@ -966,7 +987,6 @@ async def onvif_probe(
probe = ffprobe_stream( probe = ffprobe_stream(
request.app.frigate_config.ffmpeg, test_uri, detailed=False request.app.frigate_config.ffmpeg, test_uri, detailed=False
) )
print(probe)
ok = probe is not None and getattr(probe, "returncode", 1) == 0 ok = probe is not None and getattr(probe, "returncode", 1) == 0
tested_candidates.append( tested_candidates.append(
{ {