Synology SSL fix & Error handling (#4325)

* Synology SSL fix & Error handling

* change handling for cookies/ssl

* fix use not deprecated functions

* fix lint

* change verify

* fix connector close to coro

* fix force close

* not needed since websession close connector too

* fix params

* fix lint
This commit is contained in:
Pascal Vizeli 2016-11-11 06:04:47 +01:00 committed by Paulus Schoutsen
parent e005ebe989
commit 844799a1f7

View File

@ -9,13 +9,14 @@ import logging
import voluptuous as vol import voluptuous as vol
import aiohttp
from aiohttp import web from aiohttp import web
from aiohttp.web_exceptions import HTTPGatewayTimeout from aiohttp.web_exceptions import HTTPGatewayTimeout
import async_timeout import async_timeout
from homeassistant.const import ( from homeassistant.const import (
CONF_NAME, CONF_USERNAME, CONF_PASSWORD, CONF_NAME, CONF_USERNAME, CONF_PASSWORD,
CONF_URL, CONF_WHITELIST, CONF_VERIFY_SSL) CONF_URL, CONF_WHITELIST, CONF_VERIFY_SSL, EVENT_HOMEASSISTANT_STOP)
from homeassistant.components.camera import ( from homeassistant.components.camera import (
Camera, PLATFORM_SCHEMA) Camera, PLATFORM_SCHEMA)
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
@ -58,8 +59,14 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
def async_setup_platform(hass, config, async_add_devices, discovery_info=None): def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Setup a Synology IP Camera.""" """Setup a Synology IP Camera."""
if not config.get(CONF_VERIFY_SSL): if not config.get(CONF_VERIFY_SSL):
_LOGGER.warning('SSL verification currently cannot be disabled. ' connector = aiohttp.TCPConnector(verify_ssl=False)
'See https://goo.gl/1h1119') else:
connector = None
websession_init = aiohttp.ClientSession(
loop=hass.loop,
connector=connector
)
# Determine API to use for authentication # Determine API to use for authentication
syno_api_url = SYNO_API_URL.format( syno_api_url = SYNO_API_URL.format(
@ -73,12 +80,12 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
} }
try: try:
with async_timeout.timeout(TIMEOUT, loop=hass.loop): with async_timeout.timeout(TIMEOUT, loop=hass.loop):
query_req = yield from hass.websession.get( query_req = yield from websession_init.get(
syno_api_url, syno_api_url,
params=query_payload, params=query_payload
) )
except asyncio.TimeoutError: except (asyncio.TimeoutError, aiohttp.errors.ClientError):
_LOGGER.error("Timeout on %s", syno_api_url) _LOGGER.exception("Error on %s", syno_api_url)
return False return False
query_resp = yield from query_req.json() query_resp = yield from query_req.json()
@ -96,12 +103,26 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
session_id = yield from get_session_id( session_id = yield from get_session_id(
hass, hass,
websession_init,
config.get(CONF_USERNAME), config.get(CONF_USERNAME),
config.get(CONF_PASSWORD), config.get(CONF_PASSWORD),
syno_auth_url, syno_auth_url
config.get(CONF_VERIFY_SSL)
) )
websession_init.detach()
# init websession
websession = aiohttp.ClientSession(
loop=hass.loop, connector=connector, cookies={'id': session_id})
@asyncio.coroutine
def _async_close_websession(event):
"""Close webssesion on shutdown."""
yield from websession.close()
hass.bus.async_listen_once(
EVENT_HOMEASSISTANT_STOP, _async_close_websession)
# Use SessionID to get cameras in system # Use SessionID to get cameras in system
syno_camera_url = SYNO_API_URL.format( syno_camera_url = SYNO_API_URL.format(
config.get(CONF_URL), WEBAPI_PATH, camera_api) config.get(CONF_URL), WEBAPI_PATH, camera_api)
@ -113,13 +134,12 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
} }
try: try:
with async_timeout.timeout(TIMEOUT, loop=hass.loop): with async_timeout.timeout(TIMEOUT, loop=hass.loop):
camera_req = yield from hass.websession.get( camera_req = yield from websession.get(
syno_camera_url, syno_camera_url,
params=camera_payload, params=camera_payload
cookies={'id': session_id}
) )
except asyncio.TimeoutError: except (asyncio.TimeoutError, aiohttp.errors.ClientError):
_LOGGER.error("Timeout on %s", syno_camera_url) _LOGGER.exception("Error on %s", syno_camera_url)
return False return False
camera_resp = yield from camera_req.json() camera_resp = yield from camera_req.json()
@ -128,13 +148,14 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
# add cameras # add cameras
devices = [] devices = []
tasks = []
for camera in cameras: for camera in cameras:
if not config.get(CONF_WHITELIST): if not config.get(CONF_WHITELIST):
camera_id = camera['id'] camera_id = camera['id']
snapshot_path = camera['snapshot_path'] snapshot_path = camera['snapshot_path']
device = SynologyCamera( device = SynologyCamera(
hass,
websession,
config, config,
camera_id, camera_id,
camera['name'], camera['name'],
@ -143,15 +164,13 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
camera_path, camera_path,
auth_path auth_path
) )
tasks.append(device.async_read_sid())
devices.append(device) devices.append(device)
yield from asyncio.wait(tasks, loop=hass.loop)
yield from async_add_devices(devices) yield from async_add_devices(devices)
@asyncio.coroutine @asyncio.coroutine
def get_session_id(hass, username, password, login_url, valid_cert): def get_session_id(hass, websession, username, password, login_url):
"""Get a session id.""" """Get a session id."""
auth_payload = { auth_payload = {
'api': AUTH_API, 'api': AUTH_API,
@ -164,12 +183,12 @@ def get_session_id(hass, username, password, login_url, valid_cert):
} }
try: try:
with async_timeout.timeout(TIMEOUT, loop=hass.loop): with async_timeout.timeout(TIMEOUT, loop=hass.loop):
auth_req = yield from hass.websession.get( auth_req = yield from websession.get(
login_url, login_url,
params=auth_payload, params=auth_payload
) )
except asyncio.TimeoutError: except (asyncio.TimeoutError, aiohttp.errors.ClientError):
_LOGGER.error("Timeout on %s", login_url) _LOGGER.exception("Error on %s", login_url)
return False return False
auth_resp = yield from auth_req.json() auth_resp = yield from auth_req.json()
@ -181,36 +200,22 @@ def get_session_id(hass, username, password, login_url, valid_cert):
class SynologyCamera(Camera): class SynologyCamera(Camera):
"""An implementation of a Synology NAS based IP camera.""" """An implementation of a Synology NAS based IP camera."""
def __init__(self, config, camera_id, camera_name, def __init__(self, hass, websession, config, camera_id,
snapshot_path, streaming_path, camera_path, auth_path): camera_name, snapshot_path, streaming_path, camera_path,
auth_path):
"""Initialize a Synology Surveillance Station camera.""" """Initialize a Synology Surveillance Station camera."""
super().__init__() super().__init__()
self.hass = hass
self._websession = websession
self._name = camera_name self._name = camera_name
self._username = config.get(CONF_USERNAME)
self._password = config.get(CONF_PASSWORD)
self._synology_url = config.get(CONF_URL) self._synology_url = config.get(CONF_URL)
self._api_url = config.get(CONF_URL) + 'webapi/'
self._login_url = config.get(CONF_URL) + '/webapi/' + 'auth.cgi'
self._camera_name = config.get(CONF_CAMERA_NAME) self._camera_name = config.get(CONF_CAMERA_NAME)
self._stream_id = config.get(CONF_STREAM_ID) self._stream_id = config.get(CONF_STREAM_ID)
self._valid_cert = config.get(CONF_VERIFY_SSL)
self._camera_id = camera_id self._camera_id = camera_id
self._snapshot_path = snapshot_path self._snapshot_path = snapshot_path
self._streaming_path = streaming_path self._streaming_path = streaming_path
self._camera_path = camera_path self._camera_path = camera_path
self._auth_path = auth_path self._auth_path = auth_path
self._session_id = None
@asyncio.coroutine
def async_read_sid(self):
"""Get a session id."""
self._session_id = yield from get_session_id(
self.hass,
self._username,
self._password,
self._login_url,
self._valid_cert
)
def camera_image(self): def camera_image(self):
"""Return bytes of camera image.""" """Return bytes of camera image."""
@ -231,13 +236,12 @@ class SynologyCamera(Camera):
} }
try: try:
with async_timeout.timeout(TIMEOUT, loop=self.hass.loop): with async_timeout.timeout(TIMEOUT, loop=self.hass.loop):
response = yield from self.hass.websession.get( response = yield from self._websession.get(
image_url, image_url,
params=image_payload, params=image_payload
cookies={'id': self._session_id}
) )
except asyncio.TimeoutError: except (asyncio.TimeoutError, aiohttp.errors.ClientError):
_LOGGER.error("Timeout on %s", image_url) _LOGGER.exception("Error on %s", image_url)
return None return None
image = yield from response.read() image = yield from response.read()
@ -260,12 +264,12 @@ class SynologyCamera(Camera):
} }
try: try:
with async_timeout.timeout(TIMEOUT, loop=self.hass.loop): with async_timeout.timeout(TIMEOUT, loop=self.hass.loop):
stream = yield from self.hass.websession.get( stream = yield from self._websession.get(
streaming_url, streaming_url,
payload=streaming_payload, params=streaming_payload
cookies={'id': self._session_id}
) )
except asyncio.TimeoutError: except (asyncio.TimeoutError, aiohttp.errors.ClientError):
_LOGGER.exception("Error on %s", streaming_url)
raise HTTPGatewayTimeout() raise HTTPGatewayTimeout()
response = web.StreamResponse() response = web.StreamResponse()