From a4208c092680740864b15bef923add5b54de2da6 Mon Sep 17 00:00:00 2001 From: hesselonline Date: Mon, 15 Nov 2021 17:25:19 +0100 Subject: [PATCH] Add Reauth flow to Wallbox integration (#58743) * Add Reauth flow to Wallbox integration * Review comments processed * Fixed tests * Added test for reauth invalid * Commit to compensate for timedrift, show changes Compensating for timedrift in my devcontainer, making a new commit with the right date/time. Requested changes were done in a previous commit. * remove reauth schema * Update homeassistant/components/wallbox/__init__.py Co-authored-by: J. Nick Koston Co-authored-by: J. Nick Koston --- homeassistant/components/wallbox/__init__.py | 29 +++---- .../components/wallbox/config_flow.py | 33 +++++++- homeassistant/components/wallbox/number.py | 10 +-- homeassistant/components/wallbox/sensor.py | 3 +- homeassistant/components/wallbox/strings.json | 12 ++- .../components/wallbox/translations/en.json | 13 ++- .../components/wallbox/translations/nl.json | 3 +- tests/components/wallbox/test_config_flow.py | 81 +++++++++++++++++++ tests/components/wallbox/test_init.py | 9 +-- 9 files changed, 149 insertions(+), 44 deletions(-) diff --git a/homeassistant/components/wallbox/__init__.py b/homeassistant/components/wallbox/__init__.py index aade0c430f6..b1604a37c6f 100644 --- a/homeassistant/components/wallbox/__init__.py +++ b/homeassistant/components/wallbox/__init__.py @@ -9,16 +9,10 @@ from wallbox import Wallbox from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.core import HomeAssistant -from homeassistant.exceptions import HomeAssistantError +from homeassistant.exceptions import ConfigEntryAuthFailed, HomeAssistantError from homeassistant.helpers.update_coordinator import DataUpdateCoordinator -from .const import ( - CONF_CONNECTIONS, - CONF_DATA_KEY, - CONF_MAX_CHARGING_CURRENT_KEY, - CONF_STATION, - DOMAIN, -) +from .const import CONF_DATA_KEY, CONF_MAX_CHARGING_CURRENT_KEY, CONF_STATION, DOMAIN _LOGGER = logging.getLogger(__name__) @@ -48,7 +42,7 @@ class WallboxCoordinator(DataUpdateCoordinator): return True except requests.exceptions.HTTPError as wallbox_connection_error: if wallbox_connection_error.response.status_code == HTTPStatus.FORBIDDEN: - raise InvalidAuth from wallbox_connection_error + raise ConfigEntryAuthFailed from wallbox_connection_error raise ConnectionError from wallbox_connection_error def _validate(self): @@ -112,18 +106,17 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: hass, ) - await wallbox_coordinator.async_validate_input() + try: + await wallbox_coordinator.async_validate_input() + + except InvalidAuth as ex: + raise ConfigEntryAuthFailed from ex await wallbox_coordinator.async_config_entry_first_refresh() - hass.data.setdefault(DOMAIN, {CONF_CONNECTIONS: {}}) - hass.data[DOMAIN][CONF_CONNECTIONS][entry.entry_id] = wallbox_coordinator + hass.data.setdefault(DOMAIN, {})[entry.entry_id] = wallbox_coordinator - for platform in PLATFORMS: - - hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, platform) - ) + hass.config_entries.async_setup_platforms(entry, PLATFORMS) return True @@ -132,7 +125,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Unload a config entry.""" unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) if unload_ok: - hass.data[DOMAIN][CONF_CONNECTIONS].pop(entry.entry_id) + hass.data[DOMAIN].pop(entry.entry_id) return unload_ok diff --git a/homeassistant/components/wallbox/config_flow.py b/homeassistant/components/wallbox/config_flow.py index f123ad0cd2d..8559c29c9aa 100644 --- a/homeassistant/components/wallbox/config_flow.py +++ b/homeassistant/components/wallbox/config_flow.py @@ -36,6 +36,18 @@ async def validate_input(hass: core.HomeAssistant, data): class ConfigFlow(config_entries.ConfigFlow, domain=COMPONENT_DOMAIN): """Handle a config flow for Wallbox.""" + def __init__(self): + """Start the Wallbox config flow.""" + self._reauth_entry = None + + async def async_step_reauth(self, user_input=None): + """Perform reauth upon an API authentication error.""" + self._reauth_entry = self.hass.config_entries.async_get_entry( + self.context["entry_id"] + ) + + return await self.async_step_user() + async def async_step_user(self, user_input=None): """Handle the initial step.""" if user_input is None: @@ -47,14 +59,27 @@ class ConfigFlow(config_entries.ConfigFlow, domain=COMPONENT_DOMAIN): errors = {} try: - info = await validate_input(self.hass, user_input) + await self.async_set_unique_id(user_input["station"]) + if not self._reauth_entry: + self._abort_if_unique_id_configured() + info = await validate_input(self.hass, user_input) + return self.async_create_entry(title=info["title"], data=user_input) + if user_input["station"] == self._reauth_entry.data[CONF_STATION]: + self.hass.config_entries.async_update_entry( + self._reauth_entry, data=user_input, unique_id=user_input["station"] + ) + self.hass.async_create_task( + self.hass.config_entries.async_reload(self._reauth_entry.entry_id) + ) + return self.async_abort(reason="reauth_successful") + errors["base"] = "reauth_invalid" except ConnectionError: errors["base"] = "cannot_connect" except InvalidAuth: errors["base"] = "invalid_auth" - else: - return self.async_create_entry(title=info["title"], data=user_input) return self.async_show_form( - step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors + step_id="user", + data_schema=STEP_USER_DATA_SCHEMA, + errors=errors, ) diff --git a/homeassistant/components/wallbox/number.py b/homeassistant/components/wallbox/number.py index 1bc561cb7c5..b94351f4353 100644 --- a/homeassistant/components/wallbox/number.py +++ b/homeassistant/components/wallbox/number.py @@ -8,12 +8,7 @@ from homeassistant.const import DEVICE_CLASS_CURRENT from homeassistant.helpers.update_coordinator import CoordinatorEntity from . import InvalidAuth -from .const import ( - CONF_CONNECTIONS, - CONF_MAX_AVAILABLE_POWER_KEY, - CONF_MAX_CHARGING_CURRENT_KEY, - DOMAIN, -) +from .const import CONF_MAX_AVAILABLE_POWER_KEY, CONF_MAX_CHARGING_CURRENT_KEY, DOMAIN @dataclass @@ -35,8 +30,7 @@ NUMBER_TYPES: dict[str, WallboxNumberEntityDescription] = { async def async_setup_entry(hass, config, async_add_entities): """Create wallbox sensor entities in HASS.""" - coordinator = hass.data[DOMAIN][CONF_CONNECTIONS][config.entry_id] - + coordinator = hass.data[DOMAIN][config.entry_id] # Check if the user is authorized to change current, if so, add number component: try: await coordinator.async_set_charging_current( diff --git a/homeassistant/components/wallbox/sensor.py b/homeassistant/components/wallbox/sensor.py index affc78d6210..4571ed08725 100644 --- a/homeassistant/components/wallbox/sensor.py +++ b/homeassistant/components/wallbox/sensor.py @@ -28,7 +28,6 @@ from .const import ( CONF_ADDED_RANGE_KEY, CONF_CHARGING_POWER_KEY, CONF_CHARGING_SPEED_KEY, - CONF_CONNECTIONS, CONF_COST_KEY, CONF_CURRENT_MODE_KEY, CONF_DEPOT_PRICE_KEY, @@ -133,7 +132,7 @@ SENSOR_TYPES: dict[str, WallboxSensorEntityDescription] = { async def async_setup_entry(hass, config, async_add_entities): """Create wallbox sensor entities in HASS.""" - coordinator = hass.data[DOMAIN][CONF_CONNECTIONS][config.entry_id] + coordinator = hass.data[DOMAIN][config.entry_id] async_add_entities( [ diff --git a/homeassistant/components/wallbox/strings.json b/homeassistant/components/wallbox/strings.json index 6824a1343fc..4cde9c6d255 100644 --- a/homeassistant/components/wallbox/strings.json +++ b/homeassistant/components/wallbox/strings.json @@ -7,15 +7,23 @@ "username": "[%key:common::config_flow::data::username%]", "password": "[%key:common::config_flow::data::password%]" } + }, + "reauth_confirm": { + "data": { + "username": "[%key:common::config_flow::data::username%]", + "password": "[%key:common::config_flow::data::password%]" + } } }, "error": { "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]", - "unknown": "[%key:common::config_flow::error::unknown%]" + "unknown": "[%key:common::config_flow::error::unknown%]", + "reauth_invalid": "Re-authentication failed; Serial Number does not match original" }, "abort": { - "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]", + "reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]" } } } diff --git a/homeassistant/components/wallbox/translations/en.json b/homeassistant/components/wallbox/translations/en.json index 52dcf8530d4..f32c7b7b481 100644 --- a/homeassistant/components/wallbox/translations/en.json +++ b/homeassistant/components/wallbox/translations/en.json @@ -1,14 +1,22 @@ { "config": { "abort": { - "already_configured": "Device is already configured" + "already_configured": "Device is already configured", + "reauth_successful": "Re-authentication was successful" }, "error": { "cannot_connect": "Failed to connect", "invalid_auth": "Invalid authentication", + "reauth_invalid": "Re-authentication failed; Serial Number does not match original", "unknown": "Unexpected error" }, "step": { + "reauth_confirm": { + "data": { + "password": "Password", + "username": "Username" + } + }, "user": { "data": { "password": "Password", @@ -17,6 +25,5 @@ } } } - }, - "title": "Wallbox" + } } \ No newline at end of file diff --git a/homeassistant/components/wallbox/translations/nl.json b/homeassistant/components/wallbox/translations/nl.json index 6ba03e7ee99..a152eb89362 100644 --- a/homeassistant/components/wallbox/translations/nl.json +++ b/homeassistant/components/wallbox/translations/nl.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "Apparaat is al geconfigureerd" + "already_configured": "Apparaat is al geconfigureerd", + "reauth_successful": "Herauthenticatie was succesvol" }, "error": { "cannot_connect": "Kan geen verbinding maken", diff --git a/tests/components/wallbox/test_config_flow.py b/tests/components/wallbox/test_config_flow.py index ca55c076fea..01993d88968 100644 --- a/tests/components/wallbox/test_config_flow.py +++ b/tests/components/wallbox/test_config_flow.py @@ -18,6 +18,7 @@ from homeassistant.components.wallbox.const import ( ) from homeassistant.core import HomeAssistant +from tests.components.wallbox import entry, setup_integration from tests.components.wallbox.const import ( CONF_ERROR, CONF_JWT, @@ -162,3 +163,83 @@ async def test_form_validate_input(hass): assert result2["title"] == "Wallbox Portal" assert result2["data"]["station"] == "12345" + + +async def test_form_reauth(hass): + """Test we handle reauth flow.""" + await setup_integration(hass) + assert entry.state == config_entries.ConfigEntryState.LOADED + + with requests_mock.Mocker() as mock_request: + mock_request.get( + "https://api.wall-box.com/auth/token/user", + text='{"jwt":"fakekeyhere","user_id":12345,"ttl":145656758,"error":false,"status":200}', + status_code=200, + ) + mock_request.get( + "https://api.wall-box.com/chargers/status/12345", + json=test_response, + status_code=200, + ) + + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={ + "source": config_entries.SOURCE_REAUTH, + "entry_id": entry.entry_id, + }, + ) + + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + "station": "12345", + "username": "test-username", + "password": "test-password", + }, + ) + + assert result2["type"] == "abort" + assert result2["reason"] == "reauth_successful" + + await hass.config_entries.async_unload(entry.entry_id) + + +async def test_form_reauth_invalid(hass): + """Test we handle reauth invalid flow.""" + await setup_integration(hass) + assert entry.state == config_entries.ConfigEntryState.LOADED + + with requests_mock.Mocker() as mock_request: + mock_request.get( + "https://api.wall-box.com/auth/token/user", + text='{"jwt":"fakekeyhere","user_id":12345,"ttl":145656758,"error":false,"status":200}', + status_code=200, + ) + mock_request.get( + "https://api.wall-box.com/chargers/status/12345", + json=test_response, + status_code=200, + ) + + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={ + "source": config_entries.SOURCE_REAUTH, + "entry_id": entry.entry_id, + }, + ) + + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + "station": "12345678", + "username": "test-username", + "password": "test-password", + }, + ) + + assert result2["type"] == "form" + assert result2["errors"] == {"base": "reauth_invalid"} + + await hass.config_entries.async_unload(entry.entry_id) diff --git a/tests/components/wallbox/test_init.py b/tests/components/wallbox/test_init.py index 10e6cab99fc..66f0701e42e 100644 --- a/tests/components/wallbox/test_init.py +++ b/tests/components/wallbox/test_init.py @@ -3,10 +3,7 @@ import json import requests_mock -from homeassistant.components.wallbox import ( - CONF_CONNECTIONS, - CONF_MAX_CHARGING_CURRENT_KEY, -) +from homeassistant.components.wallbox import CONF_MAX_CHARGING_CURRENT_KEY from homeassistant.config_entries import ConfigEntryState from homeassistant.core import HomeAssistant @@ -78,7 +75,7 @@ async def test_wallbox_refresh_failed_invalid_auth(hass: HomeAssistant): status_code=403, ) - wallbox = hass.data[DOMAIN][CONF_CONNECTIONS][entry.entry_id] + wallbox = hass.data[DOMAIN][entry.entry_id] await wallbox.async_refresh() @@ -104,7 +101,7 @@ async def test_wallbox_refresh_failed_connection_error(hass: HomeAssistant): status_code=403, ) - wallbox = hass.data[DOMAIN][CONF_CONNECTIONS][entry.entry_id] + wallbox = hass.data[DOMAIN][entry.entry_id] await wallbox.async_refresh()