Fix go2rtc stream alias auth (#22097)

* Fix go2rtc stream alias authorization and live audio gating for main/sub stream names

* revert

* add tests
This commit is contained in:
Josh Hawkins
2026-02-27 21:02:19 -06:00
committed by GitHub
parent 96c70eee4c
commit e064024a31
4 changed files with 317 additions and 22 deletions

View File

@@ -986,7 +986,16 @@ async def require_camera_access(
current_user = await get_current_user(request)
if isinstance(current_user, JSONResponse):
return current_user
detail = "Authentication required"
try:
error_payload = json.loads(current_user.body)
detail = (
error_payload.get("message") or error_payload.get("detail") or detail
)
except Exception:
pass
raise HTTPException(status_code=current_user.status_code, detail=detail)
role = current_user["role"]
all_camera_names = set(request.app.frigate_config.cameras.keys())
@@ -1004,6 +1013,61 @@ async def require_camera_access(
)
def _get_stream_owner_cameras(request: Request, stream_name: str) -> set[str]:
owner_cameras: set[str] = set()
for camera_name, camera in request.app.frigate_config.cameras.items():
if stream_name == camera_name:
owner_cameras.add(camera_name)
continue
if stream_name in camera.live.streams.values():
owner_cameras.add(camera_name)
return owner_cameras
async def require_go2rtc_stream_access(
stream_name: Optional[str] = None,
request: Request = None,
):
"""Dependency to enforce go2rtc stream access based on owning camera access."""
if stream_name is None:
return
current_user = await get_current_user(request)
if isinstance(current_user, JSONResponse):
detail = "Authentication required"
try:
error_payload = json.loads(current_user.body)
detail = (
error_payload.get("message") or error_payload.get("detail") or detail
)
except Exception:
pass
raise HTTPException(status_code=current_user.status_code, detail=detail)
role = current_user["role"]
all_camera_names = set(request.app.frigate_config.cameras.keys())
roles_dict = request.app.frigate_config.auth.roles
allowed_cameras = User.get_allowed_cameras(role, roles_dict, all_camera_names)
# Admin or full access bypasses
if role == "admin" or not roles_dict.get(role):
return
owner_cameras = _get_stream_owner_cameras(request, stream_name)
if owner_cameras & set(allowed_cameras):
return
raise HTTPException(
status_code=403,
detail=f"Access denied to camera '{stream_name}'. Allowed: {allowed_cameras}",
)
async def get_allowed_cameras_for_filter(request: Request):
"""Dependency to get allowed_cameras for filtering lists."""
current_user = await get_current_user(request)

View File

@@ -17,7 +17,7 @@ from zeep.transports import AsyncTransport
from frigate.api.auth import (
allow_any_authenticated,
require_camera_access,
require_go2rtc_stream_access,
require_role,
)
from frigate.api.defs.tags import Tags
@@ -71,14 +71,27 @@ def go2rtc_streams():
@router.get(
"/go2rtc/streams/{camera_name}", dependencies=[Depends(require_camera_access)]
"/go2rtc/streams/{stream_name}",
dependencies=[Depends(require_go2rtc_stream_access)],
)
def go2rtc_camera_stream(request: Request, camera_name: str):
def go2rtc_camera_stream(request: Request, stream_name: str):
r = requests.get(
f"http://127.0.0.1:1984/api/streams?src={camera_name}&video=all&audio=all&microphone"
"http://127.0.0.1:1984/api/streams",
params={
"src": stream_name,
"video": "all",
"audio": "all",
"microphone": "",
},
)
if not r.ok:
camera_config = request.app.frigate_config.cameras.get(camera_name)
camera_config = request.app.frigate_config.cameras.get(stream_name)
if camera_config is None:
for camera_name, camera in request.app.frigate_config.cameras.items():
if stream_name in camera.live.streams.values():
camera_config = request.app.frigate_config.cameras.get(camera_name)
break
if camera_config and camera_config.enabled:
logger.error("Failed to fetch streams from go2rtc")