Introduce reauth flow to deCONZ (#45443)

This commit is contained in:
Robert Svensson 2021-01-22 23:37:16 +01:00 committed by GitHub
parent 18c7ae9a8b
commit 57fa7f926a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 95 additions and 23 deletions

View File

@ -174,6 +174,18 @@ class DeconzFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
return self.async_create_entry(title=self.bridge_id, data=self.deconz_config)
async def async_step_reauth(self, config: dict):
"""Trigger a reauthentication flow."""
# pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167
self.context["title_placeholders"] = {CONF_HOST: config[CONF_HOST]}
self.deconz_config = {
CONF_HOST: config[CONF_HOST],
CONF_PORT: config[CONF_PORT],
}
return await self.async_step_link()
async def async_step_ssdp(self, discovery_info):
"""Handle a discovered deCONZ bridge."""
if (

View File

@ -4,6 +4,7 @@ import asyncio
import async_timeout
from pydeconz import DeconzSession, errors
from homeassistant.config_entries import SOURCE_REAUTH
from homeassistant.const import CONF_API_KEY, CONF_HOST, CONF_PORT
from homeassistant.core import callback
from homeassistant.exceptions import ConfigEntryNotReady
@ -19,7 +20,7 @@ from .const import (
DEFAULT_ALLOW_CLIP_SENSOR,
DEFAULT_ALLOW_DECONZ_GROUPS,
DEFAULT_ALLOW_NEW_DEVICES,
DOMAIN,
DOMAIN as DECONZ_DOMAIN,
LOGGER,
NEW_GROUP,
NEW_LIGHT,
@ -34,7 +35,7 @@ from .errors import AuthenticationRequired, CannotConnect
@callback
def get_gateway_from_config_entry(hass, config_entry):
"""Return gateway with a matching bridge id."""
return hass.data[DOMAIN][config_entry.unique_id]
return hass.data[DECONZ_DOMAIN][config_entry.unique_id]
class DeconzGateway:
@ -152,7 +153,7 @@ class DeconzGateway:
# Gateway service
device_registry.async_get_or_create(
config_entry_id=self.config_entry.entry_id,
identifiers={(DOMAIN, self.api.config.bridgeid)},
identifiers={(DECONZ_DOMAIN, self.api.config.bridgeid)},
manufacturer="Dresden Elektronik",
model=self.api.config.modelid,
name=self.api.config.name,
@ -173,8 +174,14 @@ class DeconzGateway:
except CannotConnect as err:
raise ConfigEntryNotReady from err
except Exception as err: # pylint: disable=broad-except
LOGGER.error("Error connecting with deCONZ gateway: %s", err, exc_info=True)
except AuthenticationRequired:
self.hass.async_create_task(
self.hass.config_entries.flow.async_init(
DECONZ_DOMAIN,
context={"source": SOURCE_REAUTH},
data=self.config_entry.data,
)
)
return False
for component in SUPPORTED_PLATFORMS:

View File

@ -15,14 +15,19 @@ from homeassistant.components.deconz.const import (
CONF_ALLOW_DECONZ_GROUPS,
CONF_ALLOW_NEW_DEVICES,
CONF_MASTER_GATEWAY,
DOMAIN,
DOMAIN as DECONZ_DOMAIN,
)
from homeassistant.components.ssdp import (
ATTR_SSDP_LOCATION,
ATTR_UPNP_MANUFACTURER_URL,
ATTR_UPNP_SERIAL,
)
from homeassistant.config_entries import SOURCE_HASSIO, SOURCE_SSDP, SOURCE_USER
from homeassistant.config_entries import (
SOURCE_HASSIO,
SOURCE_REAUTH,
SOURCE_SSDP,
SOURCE_USER,
)
from homeassistant.const import CONF_API_KEY, CONF_HOST, CONF_PORT, CONTENT_TYPE_JSON
from homeassistant.data_entry_flow import (
RESULT_TYPE_ABORT,
@ -47,7 +52,7 @@ async def test_flow_discovered_bridges(hass, aioclient_mock):
)
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}
DECONZ_DOMAIN, context={"source": SOURCE_USER}
)
assert result["type"] == RESULT_TYPE_FORM
@ -88,7 +93,7 @@ async def test_flow_manual_configuration_decision(hass, aioclient_mock):
)
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}
DECONZ_DOMAIN, context={"source": SOURCE_USER}
)
result = await hass.config_entries.flow.async_configure(
@ -140,7 +145,7 @@ async def test_flow_manual_configuration(hass, aioclient_mock):
)
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}
DECONZ_DOMAIN, context={"source": SOURCE_USER}
)
assert result["type"] == RESULT_TYPE_FORM
@ -184,7 +189,7 @@ async def test_manual_configuration_after_discovery_timeout(hass, aioclient_mock
aioclient_mock.get(pydeconz.utils.URL_DISCOVER, exc=asyncio.TimeoutError)
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}
DECONZ_DOMAIN, context={"source": SOURCE_USER}
)
assert result["type"] == RESULT_TYPE_FORM
@ -197,7 +202,7 @@ async def test_manual_configuration_after_discovery_ResponseError(hass, aioclien
aioclient_mock.get(pydeconz.utils.URL_DISCOVER, exc=pydeconz.errors.ResponseError)
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}
DECONZ_DOMAIN, context={"source": SOURCE_USER}
)
assert result["type"] == RESULT_TYPE_FORM
@ -216,7 +221,7 @@ async def test_manual_configuration_update_configuration(hass, aioclient_mock):
)
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}
DECONZ_DOMAIN, context={"source": SOURCE_USER}
)
assert result["type"] == RESULT_TYPE_FORM
@ -262,7 +267,7 @@ async def test_manual_configuration_dont_update_configuration(hass, aioclient_mo
)
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}
DECONZ_DOMAIN, context={"source": SOURCE_USER}
)
assert result["type"] == RESULT_TYPE_FORM
@ -305,7 +310,7 @@ async def test_manual_configuration_timeout_get_bridge(hass, aioclient_mock):
)
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}
DECONZ_DOMAIN, context={"source": SOURCE_USER}
)
assert result["type"] == RESULT_TYPE_FORM
@ -346,7 +351,7 @@ async def test_link_get_api_key_ResponseError(hass, aioclient_mock):
)
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}
DECONZ_DOMAIN, context={"source": SOURCE_USER}
)
result = await hass.config_entries.flow.async_configure(
@ -367,10 +372,46 @@ async def test_link_get_api_key_ResponseError(hass, aioclient_mock):
assert result["errors"] == {"base": "no_key"}
async def test_reauth_flow_update_configuration(hass, aioclient_mock):
"""Verify reauth flow can update gateway API key."""
config_entry = await setup_deconz_integration(hass)
result = await hass.config_entries.flow.async_init(
DECONZ_DOMAIN,
data=config_entry.data,
context={"source": SOURCE_REAUTH},
)
assert result["type"] == RESULT_TYPE_FORM
assert result["step_id"] == "link"
new_api_key = "new_key"
aioclient_mock.post(
"http://1.2.3.4:80/api",
json=[{"success": {"username": new_api_key}}],
headers={"content-type": CONTENT_TYPE_JSON},
)
aioclient_mock.get(
f"http://1.2.3.4:80/api/{new_api_key}/config",
json={"bridgeid": BRIDGEID},
headers={"content-type": CONTENT_TYPE_JSON},
)
result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input={}
)
assert result["type"] == RESULT_TYPE_ABORT
assert result["reason"] == "already_configured"
assert config_entry.data[CONF_API_KEY] == new_api_key
async def test_flow_ssdp_discovery(hass, aioclient_mock):
"""Test that config flow for one discovered bridge works."""
result = await hass.config_entries.flow.async_init(
DOMAIN,
DECONZ_DOMAIN,
data={
ATTR_SSDP_LOCATION: "http://1.2.3.4:80/",
ATTR_UPNP_MANUFACTURER_URL: DECONZ_MANUFACTURERURL,
@ -410,7 +451,7 @@ async def test_ssdp_discovery_update_configuration(hass):
return_value=True,
) as mock_setup_entry:
result = await hass.config_entries.flow.async_init(
DOMAIN,
DECONZ_DOMAIN,
data={
ATTR_SSDP_LOCATION: "http://2.3.4.5:80/",
ATTR_UPNP_MANUFACTURER_URL: DECONZ_MANUFACTURERURL,
@ -431,7 +472,7 @@ async def test_ssdp_discovery_dont_update_configuration(hass):
config_entry = await setup_deconz_integration(hass)
result = await hass.config_entries.flow.async_init(
DOMAIN,
DECONZ_DOMAIN,
data={
ATTR_SSDP_LOCATION: "http://1.2.3.4:80/",
ATTR_UPNP_MANUFACTURER_URL: DECONZ_MANUFACTURERURL,
@ -450,7 +491,7 @@ async def test_ssdp_discovery_dont_update_existing_hassio_configuration(hass):
config_entry = await setup_deconz_integration(hass, source=SOURCE_HASSIO)
result = await hass.config_entries.flow.async_init(
DOMAIN,
DECONZ_DOMAIN,
data={
ATTR_SSDP_LOCATION: "http://1.2.3.4:80/",
ATTR_UPNP_MANUFACTURER_URL: DECONZ_MANUFACTURERURL,
@ -467,7 +508,7 @@ async def test_ssdp_discovery_dont_update_existing_hassio_configuration(hass):
async def test_flow_hassio_discovery(hass):
"""Test hassio discovery flow works."""
result = await hass.config_entries.flow.async_init(
DOMAIN,
DECONZ_DOMAIN,
data={
"addon": "Mock Addon",
CONF_HOST: "mock-deconz",
@ -511,7 +552,7 @@ async def test_hassio_discovery_update_configuration(hass):
return_value=True,
) as mock_setup_entry:
result = await hass.config_entries.flow.async_init(
DOMAIN,
DECONZ_DOMAIN,
data={
CONF_HOST: "2.3.4.5",
CONF_PORT: 8080,
@ -535,7 +576,7 @@ async def test_hassio_discovery_dont_update_configuration(hass):
await setup_deconz_integration(hass)
result = await hass.config_entries.flow.async_init(
DOMAIN,
DECONZ_DOMAIN,
data={
CONF_HOST: "1.2.3.4",
CONF_PORT: 80,

View File

@ -181,6 +181,18 @@ async def test_update_address(hass):
assert len(mock_setup_entry.mock_calls) == 1
async def test_gateway_trigger_reauth_flow(hass):
"""Failed authentication trigger a reauthentication flow."""
with patch(
"homeassistant.components.deconz.gateway.get_gateway",
side_effect=AuthenticationRequired,
), patch.object(hass.config_entries.flow, "async_init") as mock_flow_init:
await setup_deconz_integration(hass)
mock_flow_init.assert_called_once()
assert hass.data[DECONZ_DOMAIN] == {}
async def test_reset_after_successful_setup(hass):
"""Make sure that connection status triggers a dispatcher send."""
config_entry = await setup_deconz_integration(hass)