mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-03-23 08:38:22 +03:00
Fix go2rtc stream alias authorization and live audio gating for main/sub stream names
This commit is contained in:
parent
4d51f7a1bb
commit
8e35a6cd92
@ -986,15 +986,23 @@ async def require_camera_access(
|
|||||||
|
|
||||||
current_user = await get_current_user(request)
|
current_user = await get_current_user(request)
|
||||||
if isinstance(current_user, JSONResponse):
|
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"]
|
role = current_user["role"]
|
||||||
all_camera_names = set(request.app.frigate_config.cameras.keys())
|
all_camera_names = set(request.app.frigate_config.cameras.keys())
|
||||||
roles_dict = request.app.frigate_config.auth.roles
|
roles_dict = request.app.frigate_config.auth.roles
|
||||||
allowed_cameras = User.get_allowed_cameras(role, roles_dict, all_camera_names)
|
allowed_cameras = User.get_allowed_cameras(role, roles_dict, all_camera_names)
|
||||||
|
|
||||||
# Admin or full access bypasses
|
if role == "admin":
|
||||||
if role == "admin" or not roles_dict.get(role):
|
|
||||||
return
|
return
|
||||||
|
|
||||||
if camera_name not in allowed_cameras:
|
if camera_name not in allowed_cameras:
|
||||||
@ -1004,6 +1012,60 @@ 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)
|
||||||
|
|
||||||
|
if role == "admin":
|
||||||
|
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):
|
async def get_allowed_cameras_for_filter(request: Request):
|
||||||
"""Dependency to get allowed_cameras for filtering lists."""
|
"""Dependency to get allowed_cameras for filtering lists."""
|
||||||
current_user = await get_current_user(request)
|
current_user = await get_current_user(request)
|
||||||
|
|||||||
@ -17,7 +17,7 @@ from zeep.transports import AsyncTransport
|
|||||||
|
|
||||||
from frigate.api.auth import (
|
from frigate.api.auth import (
|
||||||
allow_any_authenticated,
|
allow_any_authenticated,
|
||||||
require_camera_access,
|
require_go2rtc_stream_access,
|
||||||
require_role,
|
require_role,
|
||||||
)
|
)
|
||||||
from frigate.api.defs.tags import Tags
|
from frigate.api.defs.tags import Tags
|
||||||
@ -71,14 +71,27 @@ def go2rtc_streams():
|
|||||||
|
|
||||||
|
|
||||||
@router.get(
|
@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(
|
r = requests.get(
|
||||||
f"http://127.0.0.1:1984/api/streams?src={camera_name}&video=all&audio=allµphone"
|
"http://127.0.0.1:1984/api/streams",
|
||||||
|
params={
|
||||||
|
"src": stream_name,
|
||||||
|
"video": "all",
|
||||||
|
"audio": "all",
|
||||||
|
"microphone": "",
|
||||||
|
},
|
||||||
)
|
)
|
||||||
if not r.ok:
|
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:
|
if camera_config and camera_config.enabled:
|
||||||
logger.error("Failed to fetch streams from go2rtc")
|
logger.error("Failed to fetch streams from go2rtc")
|
||||||
|
|||||||
@ -18,18 +18,25 @@ export default function useCameraLiveMode(
|
|||||||
|
|
||||||
const streamNames = new Set<string>();
|
const streamNames = new Set<string>();
|
||||||
cameras.forEach((camera) => {
|
cameras.forEach((camera) => {
|
||||||
const isRestreamed = Object.keys(config.go2rtc.streams || {}).includes(
|
if (activeStreams && activeStreams[camera.name]) {
|
||||||
Object.values(camera.live.streams)[0],
|
const selectedStreamName = activeStreams[camera.name];
|
||||||
);
|
const isRestreamed = Object.keys(config.go2rtc.streams || {}).includes(
|
||||||
|
selectedStreamName,
|
||||||
|
);
|
||||||
|
|
||||||
if (isRestreamed) {
|
if (isRestreamed) {
|
||||||
if (activeStreams && activeStreams[camera.name]) {
|
streamNames.add(selectedStreamName);
|
||||||
streamNames.add(activeStreams[camera.name]);
|
|
||||||
} else {
|
|
||||||
Object.values(camera.live.streams).forEach((streamName) => {
|
|
||||||
streamNames.add(streamName);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
Object.values(camera.live.streams).forEach((streamName) => {
|
||||||
|
const isRestreamed = Object.keys(
|
||||||
|
config.go2rtc.streams || {},
|
||||||
|
).includes(streamName);
|
||||||
|
|
||||||
|
if (isRestreamed) {
|
||||||
|
streamNames.add(streamName);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -66,11 +73,11 @@ export default function useCameraLiveMode(
|
|||||||
} = {};
|
} = {};
|
||||||
|
|
||||||
cameras.forEach((camera) => {
|
cameras.forEach((camera) => {
|
||||||
|
const selectedStreamName =
|
||||||
|
activeStreams?.[camera.name] ?? Object.values(camera.live.streams)[0];
|
||||||
const isRestreamed =
|
const isRestreamed =
|
||||||
config &&
|
config &&
|
||||||
Object.keys(config.go2rtc.streams || {}).includes(
|
Object.keys(config.go2rtc.streams || {}).includes(selectedStreamName);
|
||||||
Object.values(camera.live.streams)[0],
|
|
||||||
);
|
|
||||||
|
|
||||||
newIsRestreamedStates[camera.name] = isRestreamed ?? false;
|
newIsRestreamedStates[camera.name] = isRestreamed ?? false;
|
||||||
|
|
||||||
@ -101,14 +108,21 @@ export default function useCameraLiveMode(
|
|||||||
setPreferredLiveModes(newPreferredLiveModes);
|
setPreferredLiveModes(newPreferredLiveModes);
|
||||||
setIsRestreamedStates(newIsRestreamedStates);
|
setIsRestreamedStates(newIsRestreamedStates);
|
||||||
setSupportsAudioOutputStates(newSupportsAudioOutputStates);
|
setSupportsAudioOutputStates(newSupportsAudioOutputStates);
|
||||||
}, [cameras, config, windowVisible, streamMetadata]);
|
}, [activeStreams, cameras, config, windowVisible, streamMetadata]);
|
||||||
|
|
||||||
const resetPreferredLiveMode = useCallback(
|
const resetPreferredLiveMode = useCallback(
|
||||||
(cameraName: string) => {
|
(cameraName: string) => {
|
||||||
const mseSupported =
|
const mseSupported =
|
||||||
"MediaSource" in window || "ManagedMediaSource" in window;
|
"MediaSource" in window || "ManagedMediaSource" in window;
|
||||||
|
const cameraConfig = cameras.find((camera) => camera.name === cameraName);
|
||||||
|
const selectedStreamName =
|
||||||
|
activeStreams?.[cameraName] ??
|
||||||
|
(cameraConfig
|
||||||
|
? Object.values(cameraConfig.live.streams)[0]
|
||||||
|
: cameraName);
|
||||||
const isRestreamed =
|
const isRestreamed =
|
||||||
config && Object.keys(config.go2rtc.streams || {}).includes(cameraName);
|
config &&
|
||||||
|
Object.keys(config.go2rtc.streams || {}).includes(selectedStreamName);
|
||||||
|
|
||||||
setPreferredLiveModes((prevModes) => {
|
setPreferredLiveModes((prevModes) => {
|
||||||
const newModes = { ...prevModes };
|
const newModes = { ...prevModes };
|
||||||
@ -122,7 +136,7 @@ export default function useCameraLiveMode(
|
|||||||
return newModes;
|
return newModes;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[config],
|
[activeStreams, cameras, config],
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user