mirror of
https://github.com/home-assistant/core.git
synced 2025-07-20 19:57:07 +00:00
Add reauth to Alexa Devices config flow (#147773)
This commit is contained in:
parent
926e9261ab
commit
058f3b8b6e
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections.abc import Mapping
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from aioamazondevices.api import AmazonEchoApi
|
from aioamazondevices.api import AmazonEchoApi
|
||||||
@ -10,11 +11,36 @@ import voluptuous as vol
|
|||||||
|
|
||||||
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
|
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
|
||||||
from homeassistant.const import CONF_CODE, CONF_COUNTRY, CONF_PASSWORD, CONF_USERNAME
|
from homeassistant.const import CONF_CODE, CONF_COUNTRY, CONF_PASSWORD, CONF_USERNAME
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
from homeassistant.helpers.selector import CountrySelector
|
from homeassistant.helpers.selector import CountrySelector
|
||||||
|
|
||||||
from .const import CONF_LOGIN_DATA, DOMAIN
|
from .const import CONF_LOGIN_DATA, DOMAIN
|
||||||
|
|
||||||
|
STEP_REAUTH_DATA_SCHEMA = vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Required(CONF_PASSWORD): cv.string,
|
||||||
|
vol.Required(CONF_CODE): cv.string,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str, Any]:
|
||||||
|
"""Validate the user input allows us to connect."""
|
||||||
|
|
||||||
|
api = AmazonEchoApi(
|
||||||
|
data[CONF_COUNTRY],
|
||||||
|
data[CONF_USERNAME],
|
||||||
|
data[CONF_PASSWORD],
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
data = await api.login_mode_interactive(data[CONF_CODE])
|
||||||
|
finally:
|
||||||
|
await api.close()
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
class AmazonDevicesConfigFlow(ConfigFlow, domain=DOMAIN):
|
class AmazonDevicesConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||||
"""Handle a config flow for Alexa Devices."""
|
"""Handle a config flow for Alexa Devices."""
|
||||||
@ -25,13 +51,8 @@ class AmazonDevicesConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||||||
"""Handle the initial step."""
|
"""Handle the initial step."""
|
||||||
errors = {}
|
errors = {}
|
||||||
if user_input:
|
if user_input:
|
||||||
client = AmazonEchoApi(
|
|
||||||
user_input[CONF_COUNTRY],
|
|
||||||
user_input[CONF_USERNAME],
|
|
||||||
user_input[CONF_PASSWORD],
|
|
||||||
)
|
|
||||||
try:
|
try:
|
||||||
data = await client.login_mode_interactive(user_input[CONF_CODE])
|
data = await validate_input(self.hass, user_input)
|
||||||
except CannotConnect:
|
except CannotConnect:
|
||||||
errors["base"] = "cannot_connect"
|
errors["base"] = "cannot_connect"
|
||||||
except CannotAuthenticate:
|
except CannotAuthenticate:
|
||||||
@ -44,8 +65,6 @@ class AmazonDevicesConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||||||
title=user_input[CONF_USERNAME],
|
title=user_input[CONF_USERNAME],
|
||||||
data=user_input | {CONF_LOGIN_DATA: data},
|
data=user_input | {CONF_LOGIN_DATA: data},
|
||||||
)
|
)
|
||||||
finally:
|
|
||||||
await client.close()
|
|
||||||
|
|
||||||
return self.async_show_form(
|
return self.async_show_form(
|
||||||
step_id="user",
|
step_id="user",
|
||||||
@ -61,3 +80,43 @@ class AmazonDevicesConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||||||
}
|
}
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
async def async_step_reauth(
|
||||||
|
self, entry_data: Mapping[str, Any]
|
||||||
|
) -> ConfigFlowResult:
|
||||||
|
"""Handle reauth flow."""
|
||||||
|
self.context["title_placeholders"] = {CONF_USERNAME: entry_data[CONF_USERNAME]}
|
||||||
|
return await self.async_step_reauth_confirm()
|
||||||
|
|
||||||
|
async def async_step_reauth_confirm(
|
||||||
|
self, user_input: dict[str, Any] | None = None
|
||||||
|
) -> ConfigFlowResult:
|
||||||
|
"""Handle reauth confirm."""
|
||||||
|
errors: dict[str, str] = {}
|
||||||
|
|
||||||
|
reauth_entry = self._get_reauth_entry()
|
||||||
|
entry_data = reauth_entry.data
|
||||||
|
|
||||||
|
if user_input is not None:
|
||||||
|
try:
|
||||||
|
await validate_input(self.hass, {**reauth_entry.data, **user_input})
|
||||||
|
except CannotConnect:
|
||||||
|
errors["base"] = "cannot_connect"
|
||||||
|
except CannotAuthenticate:
|
||||||
|
errors["base"] = "invalid_auth"
|
||||||
|
else:
|
||||||
|
return self.async_update_reload_and_abort(
|
||||||
|
reauth_entry,
|
||||||
|
data={
|
||||||
|
CONF_USERNAME: entry_data[CONF_USERNAME],
|
||||||
|
CONF_PASSWORD: entry_data[CONF_PASSWORD],
|
||||||
|
CONF_CODE: user_input[CONF_CODE],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
return self.async_show_form(
|
||||||
|
step_id="reauth_confirm",
|
||||||
|
description_placeholders={CONF_USERNAME: entry_data[CONF_USERNAME]},
|
||||||
|
data_schema=STEP_REAUTH_DATA_SCHEMA,
|
||||||
|
errors=errors,
|
||||||
|
)
|
||||||
|
@ -12,10 +12,10 @@ from aioamazondevices.exceptions import (
|
|||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import CONF_COUNTRY, CONF_PASSWORD, CONF_USERNAME
|
from homeassistant.const import CONF_COUNTRY, CONF_PASSWORD, CONF_USERNAME
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.exceptions import ConfigEntryError
|
from homeassistant.exceptions import ConfigEntryAuthFailed
|
||||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||||
|
|
||||||
from .const import _LOGGER, CONF_LOGIN_DATA
|
from .const import _LOGGER, CONF_LOGIN_DATA, DOMAIN
|
||||||
|
|
||||||
SCAN_INTERVAL = 30
|
SCAN_INTERVAL = 30
|
||||||
|
|
||||||
@ -55,4 +55,8 @@ class AmazonDevicesCoordinator(DataUpdateCoordinator[dict[str, AmazonDevice]]):
|
|||||||
except (CannotConnect, CannotRetrieveData) as err:
|
except (CannotConnect, CannotRetrieveData) as err:
|
||||||
raise UpdateFailed(f"Error occurred while updating {self.name}") from err
|
raise UpdateFailed(f"Error occurred while updating {self.name}") from err
|
||||||
except CannotAuthenticate as err:
|
except CannotAuthenticate as err:
|
||||||
raise ConfigEntryError("Could not authenticate") from err
|
raise ConfigEntryAuthFailed(
|
||||||
|
translation_domain=DOMAIN,
|
||||||
|
translation_key="invalid_auth",
|
||||||
|
translation_placeholders={"error": repr(err)},
|
||||||
|
) from err
|
||||||
|
@ -34,7 +34,7 @@ rules:
|
|||||||
integration-owner: done
|
integration-owner: done
|
||||||
log-when-unavailable: done
|
log-when-unavailable: done
|
||||||
parallel-updates: done
|
parallel-updates: done
|
||||||
reauthentication-flow: todo
|
reauthentication-flow: done
|
||||||
test-coverage:
|
test-coverage:
|
||||||
status: todo
|
status: todo
|
||||||
comment: all tests missing
|
comment: all tests missing
|
||||||
|
@ -22,12 +22,23 @@
|
|||||||
"password": "[%key:component::alexa_devices::common::data_description_password%]",
|
"password": "[%key:component::alexa_devices::common::data_description_password%]",
|
||||||
"code": "[%key:component::alexa_devices::common::data_description_code%]"
|
"code": "[%key:component::alexa_devices::common::data_description_code%]"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"reauth_confirm": {
|
||||||
|
"data": {
|
||||||
|
"password": "[%key:common::config_flow::data::password%]",
|
||||||
|
"code": "[%key:component::alexa_devices::common::data_code%]"
|
||||||
|
},
|
||||||
|
"data_description": {
|
||||||
|
"password": "[%key:component::alexa_devices::common::data_description_password%]",
|
||||||
|
"code": "[%key:component::alexa_devices::common::data_description_code%]"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"abort": {
|
"abort": {
|
||||||
"already_configured": "[%key:common::config_flow::abort::already_configured_service%]",
|
"already_configured": "[%key:common::config_flow::abort::already_configured_service%]",
|
||||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
||||||
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
|
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
|
||||||
|
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]",
|
||||||
"unknown": "[%key:common::config_flow::error::unknown%]"
|
"unknown": "[%key:common::config_flow::error::unknown%]"
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
|
@ -133,3 +133,77 @@ async def test_already_configured(
|
|||||||
|
|
||||||
assert result["type"] is FlowResultType.ABORT
|
assert result["type"] is FlowResultType.ABORT
|
||||||
assert result["reason"] == "already_configured"
|
assert result["reason"] == "already_configured"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_reauth_successful(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_amazon_devices_client: AsyncMock,
|
||||||
|
mock_setup_entry: AsyncMock,
|
||||||
|
mock_config_entry: MockConfigEntry,
|
||||||
|
) -> None:
|
||||||
|
"""Test starting a reauthentication flow."""
|
||||||
|
mock_config_entry.add_to_hass(hass)
|
||||||
|
result = await mock_config_entry.start_reauth_flow(hass)
|
||||||
|
assert result["type"] is FlowResultType.FORM
|
||||||
|
assert result["step_id"] == "reauth_confirm"
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
user_input={
|
||||||
|
CONF_PASSWORD: "other_fake_password",
|
||||||
|
CONF_CODE: "000000",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] is FlowResultType.ABORT
|
||||||
|
assert result["reason"] == "reauth_successful"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("side_effect", "error"),
|
||||||
|
[
|
||||||
|
(CannotConnect, "cannot_connect"),
|
||||||
|
(CannotAuthenticate, "invalid_auth"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_reauth_not_successful(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_amazon_devices_client: AsyncMock,
|
||||||
|
mock_setup_entry: AsyncMock,
|
||||||
|
mock_config_entry: MockConfigEntry,
|
||||||
|
side_effect: Exception,
|
||||||
|
error: str,
|
||||||
|
) -> None:
|
||||||
|
"""Test starting a reauthentication flow but no connection found."""
|
||||||
|
mock_config_entry.add_to_hass(hass)
|
||||||
|
result = await mock_config_entry.start_reauth_flow(hass)
|
||||||
|
assert result["type"] is FlowResultType.FORM
|
||||||
|
assert result["step_id"] == "reauth_confirm"
|
||||||
|
|
||||||
|
mock_amazon_devices_client.login_mode_interactive.side_effect = side_effect
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
user_input={
|
||||||
|
CONF_PASSWORD: "other_fake_password",
|
||||||
|
CONF_CODE: "000000",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] is FlowResultType.FORM
|
||||||
|
assert result["step_id"] == "reauth_confirm"
|
||||||
|
assert result["errors"] == {"base": error}
|
||||||
|
|
||||||
|
mock_amazon_devices_client.login_mode_interactive.side_effect = None
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
user_input={
|
||||||
|
CONF_PASSWORD: "fake_password",
|
||||||
|
CONF_CODE: "111111",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] is FlowResultType.ABORT
|
||||||
|
assert result["reason"] == "reauth_successful"
|
||||||
|
assert mock_config_entry.data[CONF_PASSWORD] == "fake_password"
|
||||||
|
assert mock_config_entry.data[CONF_CODE] == "111111"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user