mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 05:07:41 +00:00
Add reauth config flow to devolo Home Control (#49697)
This commit is contained in:
parent
7a4f3fe7b8
commit
3027b848c1
@ -10,7 +10,7 @@ from homeassistant.components import zeroconf
|
|||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, EVENT_HOMEASSISTANT_STOP
|
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, EVENT_HOMEASSISTANT_STOP
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.exceptions import ConfigEntryNotReady
|
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
CONF_MYDEVOLO,
|
CONF_MYDEVOLO,
|
||||||
@ -30,7 +30,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
credentials_valid = await hass.async_add_executor_job(mydevolo.credentials_valid)
|
credentials_valid = await hass.async_add_executor_job(mydevolo.credentials_valid)
|
||||||
|
|
||||||
if not credentials_valid:
|
if not credentials_valid:
|
||||||
return False
|
raise ConfigEntryAuthFailed
|
||||||
|
|
||||||
if await hass.async_add_executor_job(mydevolo.maintenance):
|
if await hass.async_add_executor_job(mydevolo.maintenance):
|
||||||
raise ConfigEntryNotReady
|
raise ConfigEntryNotReady
|
||||||
|
@ -8,7 +8,7 @@ from homeassistant.helpers.typing import DiscoveryInfoType
|
|||||||
|
|
||||||
from . import configure_mydevolo
|
from . import configure_mydevolo
|
||||||
from .const import CONF_MYDEVOLO, DEFAULT_MYDEVOLO, DOMAIN, SUPPORTED_MODEL_TYPES
|
from .const import CONF_MYDEVOLO, DEFAULT_MYDEVOLO, DOMAIN, SUPPORTED_MODEL_TYPES
|
||||||
from .exceptions import CredentialsInvalid
|
from .exceptions import CredentialsInvalid, UuidChanged
|
||||||
|
|
||||||
|
|
||||||
class DevoloHomeControlFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
class DevoloHomeControlFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
@ -22,13 +22,13 @@ class DevoloHomeControlFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
vol.Required(CONF_USERNAME): str,
|
vol.Required(CONF_USERNAME): str,
|
||||||
vol.Required(CONF_PASSWORD): str,
|
vol.Required(CONF_PASSWORD): str,
|
||||||
}
|
}
|
||||||
|
self._reauth_entry = None
|
||||||
|
self._url = DEFAULT_MYDEVOLO
|
||||||
|
|
||||||
async def async_step_user(self, user_input=None):
|
async def async_step_user(self, user_input=None):
|
||||||
"""Handle a flow initiated by the user."""
|
"""Handle a flow initiated by the user."""
|
||||||
if self.show_advanced_options:
|
if self.show_advanced_options:
|
||||||
self.data_schema[
|
self.data_schema[vol.Required(CONF_MYDEVOLO, default=self._url)] = str
|
||||||
vol.Required(CONF_MYDEVOLO, default=DEFAULT_MYDEVOLO)
|
|
||||||
] = str
|
|
||||||
if user_input is None:
|
if user_input is None:
|
||||||
return self._show_form(step_id="user")
|
return self._show_form(step_id="user")
|
||||||
try:
|
try:
|
||||||
@ -55,8 +55,36 @@ class DevoloHomeControlFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
step_id="zeroconf_confirm", errors={"base": "invalid_auth"}
|
step_id="zeroconf_confirm", errors={"base": "invalid_auth"}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
async def async_step_reauth(self, user_input):
|
||||||
|
"""Handle reauthentication."""
|
||||||
|
self._reauth_entry = self.hass.config_entries.async_get_entry(
|
||||||
|
self.context["entry_id"]
|
||||||
|
)
|
||||||
|
self._url = user_input[CONF_MYDEVOLO]
|
||||||
|
self.data_schema = {
|
||||||
|
vol.Required(CONF_USERNAME, default=user_input[CONF_USERNAME]): str,
|
||||||
|
vol.Required(CONF_PASSWORD): str,
|
||||||
|
}
|
||||||
|
return await self.async_step_reauth_confirm()
|
||||||
|
|
||||||
|
async def async_step_reauth_confirm(self, user_input=None):
|
||||||
|
"""Handle a flow initiated by reauthentication."""
|
||||||
|
if user_input is None:
|
||||||
|
return self._show_form(step_id="reauth_confirm")
|
||||||
|
try:
|
||||||
|
return await self._connect_mydevolo(user_input)
|
||||||
|
except CredentialsInvalid:
|
||||||
|
return self._show_form(
|
||||||
|
step_id="reauth_confirm", errors={"base": "invalid_auth"}
|
||||||
|
)
|
||||||
|
except UuidChanged:
|
||||||
|
return self._show_form(
|
||||||
|
step_id="reauth_confirm", errors={"base": "reauth_failed"}
|
||||||
|
)
|
||||||
|
|
||||||
async def _connect_mydevolo(self, user_input):
|
async def _connect_mydevolo(self, user_input):
|
||||||
"""Connect to mydevolo."""
|
"""Connect to mydevolo."""
|
||||||
|
user_input[CONF_MYDEVOLO] = user_input.get(CONF_MYDEVOLO, self._url)
|
||||||
mydevolo = configure_mydevolo(conf=user_input)
|
mydevolo = configure_mydevolo(conf=user_input)
|
||||||
credentials_valid = await self.hass.async_add_executor_job(
|
credentials_valid = await self.hass.async_add_executor_job(
|
||||||
mydevolo.credentials_valid
|
mydevolo.credentials_valid
|
||||||
@ -64,17 +92,30 @@ class DevoloHomeControlFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
if not credentials_valid:
|
if not credentials_valid:
|
||||||
raise CredentialsInvalid
|
raise CredentialsInvalid
|
||||||
uuid = await self.hass.async_add_executor_job(mydevolo.uuid)
|
uuid = await self.hass.async_add_executor_job(mydevolo.uuid)
|
||||||
await self.async_set_unique_id(uuid)
|
|
||||||
self._abort_if_unique_id_configured()
|
|
||||||
|
|
||||||
return self.async_create_entry(
|
if not self._reauth_entry:
|
||||||
title="devolo Home Control",
|
await self.async_set_unique_id(uuid)
|
||||||
data={
|
self._abort_if_unique_id_configured()
|
||||||
CONF_PASSWORD: mydevolo.password,
|
return self.async_create_entry(
|
||||||
CONF_USERNAME: mydevolo.user,
|
title="devolo Home Control",
|
||||||
CONF_MYDEVOLO: mydevolo.url,
|
data={
|
||||||
},
|
CONF_PASSWORD: mydevolo.password,
|
||||||
|
CONF_USERNAME: mydevolo.user,
|
||||||
|
CONF_MYDEVOLO: mydevolo.url,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
if self._reauth_entry.unique_id != uuid:
|
||||||
|
# The old user and the new user are not the same. This could mess-up everything as all unique IDs might change.
|
||||||
|
raise UuidChanged
|
||||||
|
|
||||||
|
self.hass.config_entries.async_update_entry(
|
||||||
|
self._reauth_entry, data=user_input, unique_id=uuid
|
||||||
)
|
)
|
||||||
|
self.hass.async_create_task(
|
||||||
|
self.hass.config_entries.async_reload(self._reauth_entry.entry_id)
|
||||||
|
)
|
||||||
|
return self.async_abort(reason="reauth_successful")
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _show_form(self, step_id, errors=None):
|
def _show_form(self, step_id, errors=None):
|
||||||
|
@ -4,3 +4,7 @@ from homeassistant.exceptions import HomeAssistantError
|
|||||||
|
|
||||||
class CredentialsInvalid(HomeAssistantError):
|
class CredentialsInvalid(HomeAssistantError):
|
||||||
"""Given credentials are invalid."""
|
"""Given credentials are invalid."""
|
||||||
|
|
||||||
|
|
||||||
|
class UuidChanged(HomeAssistantError):
|
||||||
|
"""UUID of the user changed."""
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
{
|
{
|
||||||
"config": {
|
"config": {
|
||||||
"abort": {
|
"abort": {
|
||||||
"already_configured": "[%key:common::config_flow::abort::already_configured_account%]"
|
"already_configured": "[%key:common::config_flow::abort::already_configured_account%]",
|
||||||
|
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]"
|
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
|
||||||
|
"reauth_failed": "Please use the same mydevolo user as before."
|
||||||
},
|
},
|
||||||
"step": {
|
"step": {
|
||||||
"user": {
|
"user": {
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
{
|
{
|
||||||
"config": {
|
"config": {
|
||||||
"abort": {
|
"abort": {
|
||||||
"already_configured": "Account is already configured"
|
"already_configured": "Account is already configured",
|
||||||
|
"reauth_successful": "Re-authentication was successful"
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"invalid_auth": "Invalid authentication"
|
"invalid_auth": "Invalid authentication",
|
||||||
|
"reauth_failed": "Please use the same mydevolo user as before."
|
||||||
},
|
},
|
||||||
"step": {
|
"step": {
|
||||||
"user": {
|
"user": {
|
||||||
|
@ -5,7 +5,6 @@ import pytest
|
|||||||
|
|
||||||
from homeassistant import config_entries, data_entry_flow, setup
|
from homeassistant import config_entries, data_entry_flow, setup
|
||||||
from homeassistant.components.devolo_home_control.const import DEFAULT_MYDEVOLO, DOMAIN
|
from homeassistant.components.devolo_home_control.const import DEFAULT_MYDEVOLO, DOMAIN
|
||||||
from homeassistant.config_entries import SOURCE_USER
|
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
DISCOVERY_INFO,
|
DISCOVERY_INFO,
|
||||||
@ -57,7 +56,7 @@ async def test_form_already_configured(hass):
|
|||||||
MockConfigEntry(domain=DOMAIN, unique_id="123456", data={}).add_to_hass(hass)
|
MockConfigEntry(domain=DOMAIN, unique_id="123456", data={}).add_to_hass(hass)
|
||||||
result = await hass.config_entries.flow.async_init(
|
result = await hass.config_entries.flow.async_init(
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
context={"source": SOURCE_USER},
|
context={"source": config_entries.SOURCE_USER},
|
||||||
data={"username": "test-username", "password": "test-password"},
|
data={"username": "test-username", "password": "test-password"},
|
||||||
)
|
)
|
||||||
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
|
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
|
||||||
@ -70,7 +69,7 @@ async def test_form_advanced_options(hass):
|
|||||||
DOMAIN,
|
DOMAIN,
|
||||||
context={"source": config_entries.SOURCE_USER, "show_advanced_options": True},
|
context={"source": config_entries.SOURCE_USER, "show_advanced_options": True},
|
||||||
)
|
)
|
||||||
assert result["type"] == "form"
|
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||||
assert result["errors"] == {}
|
assert result["errors"] == {}
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
@ -157,6 +156,106 @@ async def test_zeroconf_wrong_device(hass):
|
|||||||
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
|
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
|
||||||
|
|
||||||
|
|
||||||
|
async def test_form_reauth(hass):
|
||||||
|
"""Test that the reauth confirmation form is served."""
|
||||||
|
mock_config = MockConfigEntry(domain=DOMAIN, unique_id="123456", data={})
|
||||||
|
mock_config.add_to_hass(hass)
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN,
|
||||||
|
context={
|
||||||
|
"source": config_entries.SOURCE_REAUTH,
|
||||||
|
"entry_id": mock_config.entry_id,
|
||||||
|
},
|
||||||
|
data={
|
||||||
|
"username": "test-username",
|
||||||
|
"password": "test-password",
|
||||||
|
"mydevolo_url": "https://test_mydevolo_url.test",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["step_id"] == "reauth_confirm"
|
||||||
|
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.devolo_home_control.async_setup_entry",
|
||||||
|
return_value=True,
|
||||||
|
) as mock_setup_entry, patch(
|
||||||
|
"homeassistant.components.devolo_home_control.Mydevolo.uuid",
|
||||||
|
return_value="123456",
|
||||||
|
):
|
||||||
|
result2 = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
{"username": "test-username-new", "password": "test-password-new"},
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert result2["type"] == data_entry_flow.RESULT_TYPE_ABORT
|
||||||
|
assert len(mock_setup_entry.mock_calls) == 1
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.credentials_invalid
|
||||||
|
async def test_form_invalid_credentials_reauth(hass):
|
||||||
|
"""Test if we get the error message on invalid credentials."""
|
||||||
|
mock_config = MockConfigEntry(domain=DOMAIN, unique_id="123456", data={})
|
||||||
|
mock_config.add_to_hass(hass)
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN,
|
||||||
|
context={
|
||||||
|
"source": config_entries.SOURCE_REAUTH,
|
||||||
|
"entry_id": mock_config.entry_id,
|
||||||
|
},
|
||||||
|
data={
|
||||||
|
"username": "test-username",
|
||||||
|
"password": "test-password",
|
||||||
|
"mydevolo_url": "https://test_mydevolo_url.test",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
{"username": "test-username", "password": "test-password"},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["errors"] == {"base": "invalid_auth"}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_form_uuid_change_reauth(hass):
|
||||||
|
"""Test that the reauth confirmation form is served."""
|
||||||
|
mock_config = MockConfigEntry(domain=DOMAIN, unique_id="123456", data={})
|
||||||
|
mock_config.add_to_hass(hass)
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN,
|
||||||
|
context={
|
||||||
|
"source": config_entries.SOURCE_REAUTH,
|
||||||
|
"entry_id": mock_config.entry_id,
|
||||||
|
},
|
||||||
|
data={
|
||||||
|
"username": "test-username",
|
||||||
|
"password": "test-password",
|
||||||
|
"mydevolo_url": "https://test_mydevolo_url.test",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["step_id"] == "reauth_confirm"
|
||||||
|
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.devolo_home_control.async_setup_entry",
|
||||||
|
return_value=True,
|
||||||
|
), patch(
|
||||||
|
"homeassistant.components.devolo_home_control.Mydevolo.uuid",
|
||||||
|
return_value="789123",
|
||||||
|
):
|
||||||
|
result2 = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
{"username": "test-username-new", "password": "test-password-new"},
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||||
|
assert result2["errors"] == {"base": "reauth_failed"}
|
||||||
|
|
||||||
|
|
||||||
async def _setup(hass, result):
|
async def _setup(hass, result):
|
||||||
"""Finish configuration steps."""
|
"""Finish configuration steps."""
|
||||||
with patch(
|
with patch(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user