Handle AttributeError from wrong port in ONVIF config flow (#92272)

* Handle AttributeError from wrong port in ONVIF config flow

fixes
```
2023-04-29 19:17:22.289 ERROR (MainThread) [aiohttp.server] Error handling request
Traceback (most recent call last):
  File "/Users/bdraco/home-assistant/venv/lib/python3.10/site-packages/aiohttp/web_protocol.py", line 433, in _handle_request
    resp = await request_handler(request)
  File "/Users/bdraco/home-assistant/venv/lib/python3.10/site-packages/aiohttp/web_app.py", line 504, in _handle
    resp = await handler(request)
  File "/Users/bdraco/home-assistant/venv/lib/python3.10/site-packages/aiohttp/web_middlewares.py", line 117, in impl
    return await handler(request)
  File "/Users/bdraco/home-assistant/homeassistant/components/http/security_filter.py", line 85, in security_filter_middleware
    return await handler(request)
  File "/Users/bdraco/home-assistant/homeassistant/components/http/forwarded.py", line 100, in forwarded_middleware
    return await handler(request)
  File "/Users/bdraco/home-assistant/homeassistant/components/http/request_context.py", line 28, in request_context_middleware
    return await handler(request)
  File "/Users/bdraco/home-assistant/homeassistant/components/http/ban.py", line 80, in ban_middleware
    return await handler(request)
  File "/Users/bdraco/home-assistant/homeassistant/components/http/auth.py", line 235, in auth_middleware
    return await handler(request)
  File "/Users/bdraco/home-assistant/homeassistant/components/http/view.py", line 146, in handle
    result = await result
  File "/Users/bdraco/home-assistant/homeassistant/components/config/config_entries.py", line 180, in post
    return await super().post(request, flow_id)
  File "/Users/bdraco/home-assistant/homeassistant/components/http/data_validator.py", line 72, in wrapper
    result = await method(view, request, data, *args, **kwargs)
  File "/Users/bdraco/home-assistant/homeassistant/helpers/data_entry_flow.py", line 110, in post
    result = await self._flow_mgr.async_configure(flow_id, data)
  File "/Users/bdraco/home-assistant/homeassistant/data_entry_flow.py", line 271, in async_configure
    result = await self._async_handle_step(
  File "/Users/bdraco/home-assistant/homeassistant/data_entry_flow.py", line 367, in _async_handle_step
    result: FlowResult = await getattr(flow, method)(user_input)
  File "/Users/bdraco/home-assistant/homeassistant/components/onvif/config_flow.py", line 233, in async_step_configure
    errors, description_placeholders = await self.async_setup_profiles()
  File "/Users/bdraco/home-assistant/homeassistant/components/onvif/config_flow.py", line 277, in async_setup_profiles
    await device.update_xaddrs()
  File "/Users/bdraco/home-assistant/venv/lib/python3.10/site-packages/onvif/client.py", line 433, in update_xaddrs
    capabilities = await devicemgmt.GetCapabilities({"Category": "All"})
  File "/Users/bdraco/home-assistant/venv/lib/python3.10/site-packages/zeep/proxy.py", line 64, in __call__
    return await self._proxy._binding.send_async(
  File "/Users/bdraco/home-assistant/venv/lib/python3.10/site-packages/zeep/wsdl/bindings/soap.py", line 164, in send_async
    return self.process_reply(client, operation_obj, response)
  File "/Users/bdraco/home-assistant/venv/lib/python3.10/site-packages/zeep/wsdl/bindings/soap.py", line 204, in process_reply
    doc = parse_xml(content, self.transport, settings=client.settings)
  File "/Users/bdraco/home-assistant/venv/lib/python3.10/site-packages/zeep/loader.py", line 51, in parse_xml
    docinfo = elementtree.getroottree().docinfo
AttributeError: NoneType object has no attribute getroottree
```

* port

* Revert "port"

This reverts commit 4693f3f33af18af66672dbd5ce6774f35ba28316.

* misfire
This commit is contained in:
J. Nick Koston 2023-04-29 20:17:56 -05:00 committed by GitHub
parent 1a82b353e0
commit 193b2694a9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 94 additions and 1 deletions

View File

@ -316,6 +316,15 @@ class OnvifFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
# Verify there is an H264 profile
media_service = device.create_media_service()
profiles = await media_service.GetProfiles()
except AttributeError: # Likely an empty document or 404 from the wrong port
LOGGER.debug(
"%s: No ONVIF service found at %s:%s",
self.onvif_config[CONF_NAME],
self.onvif_config[CONF_HOST],
self.onvif_config[CONF_PORT],
exc_info=True,
)
return {CONF_PORT: "no_onvif_service"}, {}
except Fault as err:
stringified_error = stringify_onvif_error(err)
description_placeholders = {"error": stringified_error}

View File

@ -11,6 +11,7 @@
"error": {
"onvif_error": "Error setting up ONVIF device: {error}. Check logs for more information.",
"auth_failed": "Could not authenticate: {error}",
"no_onvif_service": "No ONVIF service found. Check that the port number is correct.",
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"
},
"step": {

View File

@ -45,12 +45,14 @@ def setup_mock_onvif_camera(
update_xaddrs_fail=False,
no_profiles=False,
auth_failure=False,
wrong_port=False,
):
"""Prepare mock onvif.ONVIFCamera."""
devicemgmt = MagicMock()
device_info = MagicMock()
device_info.SerialNumber = SERIAL_NUMBER if with_serial else None
devicemgmt.GetDeviceInformation = AsyncMock(return_value=device_info)
interface = MagicMock()
@ -82,7 +84,9 @@ def setup_mock_onvif_camera(
else:
media_service.GetProfiles = AsyncMock(return_value=[profile1, profile2])
if auth_failure:
if wrong_port:
mock_onvif_camera.update_xaddrs = AsyncMock(side_effect=AttributeError)
elif auth_failure:
mock_onvif_camera.update_xaddrs = AsyncMock(
side_effect=Fault(
"not authorized", subcodes=[MagicMock(text="NotAuthorized")]

View File

@ -829,3 +829,82 @@ async def test_flow_manual_entry_updates_existing_user_password(
assert entry.data[config_flow.CONF_USERNAME] == USERNAME
assert entry.data[config_flow.CONF_PASSWORD] == "new_password"
assert len(mock_setup_entry.mock_calls) == 1
async def test_flow_manual_entry_wrong_port(hass: HomeAssistant) -> None:
"""Test that we get a useful error with the wrong port."""
result = await hass.config_entries.flow.async_init(
config_flow.DOMAIN, context={"source": config_entries.SOURCE_USER}
)
assert result["type"] == data_entry_flow.FlowResultType.FORM
assert result["step_id"] == "user"
with patch(
"homeassistant.components.onvif.config_flow.get_device"
) as mock_onvif_camera, patch(
"homeassistant.components.onvif.config_flow.wsdiscovery"
) as mock_discovery, patch(
"homeassistant.components.onvif.ONVIFDevice"
) as mock_device:
setup_mock_onvif_camera(mock_onvif_camera, wrong_port=True)
# no discovery
mock_discovery.return_value = []
setup_mock_device(mock_device)
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input={"auto": False},
)
assert result["type"] == data_entry_flow.FlowResultType.FORM
assert result["step_id"] == "configure"
with patch(
"homeassistant.components.onvif.async_setup_entry", return_value=True
) as mock_setup_entry:
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input={
config_flow.CONF_NAME: NAME,
config_flow.CONF_HOST: HOST,
config_flow.CONF_PORT: PORT,
config_flow.CONF_USERNAME: USERNAME,
config_flow.CONF_PASSWORD: PASSWORD,
},
)
await hass.async_block_till_done()
assert len(mock_setup_entry.mock_calls) == 0
assert result["type"] == data_entry_flow.FlowResultType.FORM
assert result["step_id"] == "configure"
assert result["errors"] == {"port": "no_onvif_service"}
assert result["description_placeholders"] == {}
setup_mock_onvif_camera(mock_onvif_camera, two_profiles=True)
with patch(
"homeassistant.components.onvif.async_setup_entry", return_value=True
) as mock_setup_entry:
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input={
config_flow.CONF_NAME: NAME,
config_flow.CONF_HOST: HOST,
config_flow.CONF_PORT: PORT,
config_flow.CONF_USERNAME: USERNAME,
config_flow.CONF_PASSWORD: PASSWORD,
},
)
await hass.async_block_till_done()
assert len(mock_setup_entry.mock_calls) == 1
assert result["title"] == f"{NAME} - {MAC}"
assert result["data"] == {
config_flow.CONF_NAME: NAME,
config_flow.CONF_HOST: HOST,
config_flow.CONF_PORT: PORT,
config_flow.CONF_USERNAME: USERNAME,
config_flow.CONF_PASSWORD: PASSWORD,
}