Automatically configure HTTP auth type in ONVIF snapshots (#38729)

* Allow selection of HTTP auth type in ONVIF snapshots

* Auto populate snapshot auth

* Fix no auth case

* Add missing return
This commit is contained in:
On Freund 2020-09-03 23:41:24 +03:00 committed by GitHub
parent c1b8497aaa
commit a87fedc0af
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 58 additions and 6 deletions

View File

@ -1,6 +1,9 @@
"""The ONVIF integration."""
import asyncio
import requests
from requests.auth import HTTPDigestAuth
from urllib3.exceptions import ReadTimeoutError
import voluptuous as vol
from homeassistant.components.ffmpeg import CONF_EXTRA_ARGUMENTS
@ -12,6 +15,8 @@ from homeassistant.const import (
CONF_PORT,
CONF_USERNAME,
EVENT_HOMEASSISTANT_STOP,
HTTP_BASIC_AUTHENTICATION,
HTTP_DIGEST_AUTHENTICATION,
)
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
@ -19,6 +24,7 @@ from homeassistant.helpers import config_per_platform
from .const import (
CONF_RTSP_TRANSPORT,
CONF_SNAPSHOT_AUTH,
DEFAULT_ARGUMENTS,
DEFAULT_NAME,
DEFAULT_PASSWORD,
@ -76,6 +82,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
if not device.available:
raise ConfigEntryNotReady()
if not entry.data.get(CONF_SNAPSHOT_AUTH):
await async_populate_snapshot_auth(hass, device, entry)
hass.data[DOMAIN][entry.unique_id] = device
platforms = ["camera"]
@ -113,6 +122,39 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
)
async def _get_snapshot_auth(hass, device, entry):
if not (device.username and device.password):
return HTTP_DIGEST_AUTHENTICATION
snapshot_uri = await device.async_get_snapshot_uri(device.profiles[0])
auth = HTTPDigestAuth(device.username, device.password)
def _get():
# so we can handle keyword arguments
return requests.get(snapshot_uri, timeout=1, auth=auth)
try:
response = await hass.async_add_executor_job(_get)
if response.status_code == 401:
return HTTP_BASIC_AUTHENTICATION
return HTTP_DIGEST_AUTHENTICATION
except requests.exceptions.Timeout:
return HTTP_BASIC_AUTHENTICATION
except requests.exceptions.ConnectionError as error:
if isinstance(error.args[0], ReadTimeoutError):
return HTTP_BASIC_AUTHENTICATION
return HTTP_DIGEST_AUTHENTICATION
async def async_populate_snapshot_auth(hass, device, entry):
"""Check if digest auth for snapshots is possible."""
auth = await _get_snapshot_auth(hass, device, entry)
new_data = {**entry.data, CONF_SNAPSHOT_AUTH: auth}
hass.config_entries.async_update_entry(entry, data=new_data)
async def async_populate_options(hass, entry):
"""Populate default options for device."""
options = {

View File

@ -4,11 +4,12 @@ import asyncio
from haffmpeg.camera import CameraMjpeg
from haffmpeg.tools import IMAGE_JPEG, ImageFrame
import requests
from requests.auth import HTTPDigestAuth
from requests.auth import HTTPBasicAuth, HTTPDigestAuth
import voluptuous as vol
from homeassistant.components.camera import SUPPORT_STREAM, Camera
from homeassistant.components.ffmpeg import CONF_EXTRA_ARGUMENTS, DATA_FFMPEG
from homeassistant.const import HTTP_BASIC_AUTHENTICATION
from homeassistant.helpers import config_validation as cv, entity_platform
from homeassistant.helpers.aiohttp_client import async_aiohttp_proxy_stream
@ -24,6 +25,7 @@ from .const import (
ATTR_TILT,
ATTR_ZOOM,
CONF_RTSP_TRANSPORT,
CONF_SNAPSHOT_AUTH,
CONTINUOUS_MOVE,
DIR_DOWN,
DIR_LEFT,
@ -79,6 +81,10 @@ class ONVIFCameraEntity(ONVIFBaseEntity, Camera):
self.stream_options[CONF_RTSP_TRANSPORT] = device.config_entry.options.get(
CONF_RTSP_TRANSPORT
)
self._basic_auth = (
device.config_entry.data.get(CONF_SNAPSHOT_AUTH)
== HTTP_BASIC_AUTHENTICATION
)
self._stream_uri = None
self._snapshot_uri = None
@ -115,6 +121,9 @@ class ONVIFCameraEntity(ONVIFBaseEntity, Camera):
if self.device.capabilities.snapshot:
auth = None
if self.device.username and self.device.password:
if self._basic_auth:
auth = HTTPBasicAuth(self.device.username, self.device.password)
else:
auth = HTTPDigestAuth(self.device.username, self.device.password)
def fetch():

View File

@ -13,6 +13,7 @@ DEFAULT_ARGUMENTS = "-pred 1"
CONF_DEVICE_ID = "deviceid"
CONF_RTSP_TRANSPORT = "rtsp_transport"
CONF_SNAPSHOT_AUTH = "snapshot_auth"
RTSP_TRANS_PROTOCOLS = ["tcp", "udp", "udp_multicast", "http"]

View File

@ -13,8 +13,8 @@
"step": {
"auth": {
"data": {
"password": "Password",
"username": "Username"
"password": "[%key:common::config_flow::data::password%]",
"username": "[%key:common::config_flow::data::username%]"
},
"title": "Configure authentication"
},
@ -33,9 +33,9 @@
},
"manual_input": {
"data": {
"host": "Host",
"host": "[%key:common::config_flow::data::host%]",
"name": "Name",
"port": "Port"
"port": "[%key:common::config_flow::data::port%]"
},
"title": "Configure ONVIF device"
},