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.""" """The ONVIF integration."""
import asyncio import asyncio
import requests
from requests.auth import HTTPDigestAuth
from urllib3.exceptions import ReadTimeoutError
import voluptuous as vol import voluptuous as vol
from homeassistant.components.ffmpeg import CONF_EXTRA_ARGUMENTS from homeassistant.components.ffmpeg import CONF_EXTRA_ARGUMENTS
@ -12,6 +15,8 @@ from homeassistant.const import (
CONF_PORT, CONF_PORT,
CONF_USERNAME, CONF_USERNAME,
EVENT_HOMEASSISTANT_STOP, EVENT_HOMEASSISTANT_STOP,
HTTP_BASIC_AUTHENTICATION,
HTTP_DIGEST_AUTHENTICATION,
) )
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.exceptions import ConfigEntryNotReady
@ -19,6 +24,7 @@ from homeassistant.helpers import config_per_platform
from .const import ( from .const import (
CONF_RTSP_TRANSPORT, CONF_RTSP_TRANSPORT,
CONF_SNAPSHOT_AUTH,
DEFAULT_ARGUMENTS, DEFAULT_ARGUMENTS,
DEFAULT_NAME, DEFAULT_NAME,
DEFAULT_PASSWORD, DEFAULT_PASSWORD,
@ -76,6 +82,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
if not device.available: if not device.available:
raise ConfigEntryNotReady() 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 hass.data[DOMAIN][entry.unique_id] = device
platforms = ["camera"] 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): async def async_populate_options(hass, entry):
"""Populate default options for device.""" """Populate default options for device."""
options = { options = {

View File

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

View File

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

View File

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