feat: add auth_mode option to ONVIF config for Hikvision HTTP Digest support

Some Hikvision PTZ cameras (e.g. DS-2SE4C425MWG-E) reject WSSE wsUsername
tokens and return 401 on /onvif/Media and /onvif/PTZ endpoints. They require
HTTP Digest authentication at the transport level before processing SOAP.

Add `auth_mode` field to OnvifConfig (auto/digest/wsse, default: auto).
When auth_mode=digest, inject aiohttp.DigestAuthMiddleware into the
ONVIFCamera session so Digest challenge-response completes before SOAP.

Usage:
  cameras:
    my_hikvision_ptz:
      onvif:
        host: 192.168.31.86
        port: 80
        user: admin
        password: yourpassword
        auth_mode: digest

Fixes #22622
This commit is contained in:
Arturo Naredo 2026-03-25 05:18:29 +01:00
parent 4b42039568
commit 27c80b4944
2 changed files with 22 additions and 1 deletions

View File

@ -1,5 +1,5 @@
from enum import Enum
from typing import Optional, Union
from typing import Literal, Optional, Union
from pydantic import Field, field_validator
@ -117,6 +117,11 @@ class OnvifConfig(FrigateBaseModel):
title="Disable TLS verify",
description="Skip TLS verification and disable digest auth for ONVIF (unsafe; use in safe networks only).",
)
auth_mode: Literal["auto", "digest", "wsse"] = Field(
default="auto",
title="ONVIF authentication mode",
description="Authentication mode for ONVIF connections. 'auto' tries WSSE first (default behavior). 'digest' forces HTTP Digest auth at the transport level — required for some Hikvision cameras that reject WSSE wsUsername tokens and return 401. 'wsse' forces WSSE UsernameToken only.",
)
autotracking: PtzAutotrackConfig = Field(
default_factory=PtzAutotrackConfig,
title="Autotracking",

View File

@ -9,6 +9,7 @@ from importlib.util import find_spec
from pathlib import Path
from typing import Any
import aiohttp
import numpy
from onvif import ONVIFCamera, ONVIFError, ONVIFService
from zeep.exceptions import Fault, TransportError
@ -104,6 +105,20 @@ class OnvifController:
if password is not None and isinstance(password, bytes):
password = password.decode("utf-8")
# Build extra kwargs for digest auth mode (Hikvision and similar cameras
# that reject WSSE wsUsername tokens and require HTTP Digest at transport level).
onvif_extra: dict[str, Any] = {}
if cam.onvif.auth_mode == "digest" and user and password:
try:
onvif_extra["middlewares"] = [
aiohttp.DigestAuthMiddleware(user, password)
]
except AttributeError:
logger.warning(
f"DigestAuthMiddleware not available in installed aiohttp version "
f"for camera {cam_name}; falling back to default auth."
)
self.cams[cam_name] = {
"onvif": ONVIFCamera(
cam.onvif.host,
@ -113,6 +128,7 @@ class OnvifController:
wsdl_dir=str(Path(find_spec("onvif").origin).parent / "wsdl"),
adjust_time=cam.onvif.ignore_time_mismatch,
encrypt=not cam.onvif.tls_insecure,
**onvif_extra,
),
"init": False,
"active": False,