"""Config flow for flume integration.""" from __future__ import annotations from collections.abc import Mapping import logging import os from typing import Any from pyflume import FlumeAuth, FlumeDeviceList from requests.exceptions import RequestException import voluptuous as vol from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import ( CONF_CLIENT_ID, CONF_CLIENT_SECRET, CONF_PASSWORD, CONF_USERNAME, ) from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError from .const import BASE_TOKEN_FILENAME, DOMAIN _LOGGER = logging.getLogger(__name__) # If flume ever implements a login page for oauth # we can use the oauth2 support built into Home Assistant. # # Currently they only implement the token endpoint # DATA_SCHEMA = vol.Schema( { vol.Required(CONF_USERNAME): str, vol.Required(CONF_PASSWORD): str, vol.Required(CONF_CLIENT_ID): str, vol.Required(CONF_CLIENT_SECRET): str, } ) def _validate_input( hass: HomeAssistant, data: dict[str, Any], clear_token_file: bool ) -> FlumeDeviceList: """Validate in the executor.""" flume_token_full_path = hass.config.path( f"{BASE_TOKEN_FILENAME}-{data[CONF_USERNAME]}" ) if clear_token_file and os.path.exists(flume_token_full_path): os.unlink(flume_token_full_path) return FlumeDeviceList( FlumeAuth( data[CONF_USERNAME], data[CONF_PASSWORD], data[CONF_CLIENT_ID], data[CONF_CLIENT_SECRET], flume_token_file=flume_token_full_path, ) ) async def validate_input( hass: HomeAssistant, data: dict[str, Any], clear_token_file: bool = False ) -> dict[str, Any]: """Validate the user input allows us to connect. Data has the keys from DATA_SCHEMA with values provided by the user. """ try: flume_devices = await hass.async_add_executor_job( _validate_input, hass, data, clear_token_file ) except RequestException as err: raise CannotConnect from err except Exception as err: _LOGGER.exception("Auth exception") raise InvalidAuth from err if not flume_devices or not flume_devices.device_list: raise CannotConnect # Return info that you want to store in the config entry. return {"title": data[CONF_USERNAME]} class FlumeConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for flume.""" VERSION = 1 def __init__(self) -> None: """Init flume config flow.""" self._reauth_unique_id: str | None = None async def async_step_user( self, user_input: dict[str, Any] | None = None ) -> ConfigFlowResult: """Handle the initial step.""" errors: dict[str, str] = {} if user_input is not None: await self.async_set_unique_id(user_input[CONF_USERNAME]) self._abort_if_unique_id_configured() try: info = await validate_input(self.hass, user_input) return self.async_create_entry(title=info["title"], data=user_input) except CannotConnect: errors["base"] = "cannot_connect" except InvalidAuth: errors[CONF_PASSWORD] = "invalid_auth" return self.async_show_form( step_id="user", data_schema=DATA_SCHEMA, errors=errors ) async def async_step_reauth( self, entry_data: Mapping[str, Any] ) -> ConfigFlowResult: """Handle reauth.""" self._reauth_unique_id = self.context["unique_id"] return await self.async_step_reauth_confirm() async def async_step_reauth_confirm( self, user_input: dict[str, Any] | None = None ) -> ConfigFlowResult: """Handle reauth input.""" errors: dict[str, str] = {} existing_entry = await self.async_set_unique_id(self._reauth_unique_id) assert existing_entry if user_input is not None: new_data = {**existing_entry.data, CONF_PASSWORD: user_input[CONF_PASSWORD]} try: await validate_input(self.hass, new_data, clear_token_file=True) except CannotConnect: errors["base"] = "cannot_connect" except InvalidAuth: errors[CONF_PASSWORD] = "invalid_auth" else: self.hass.config_entries.async_update_entry( existing_entry, data=new_data ) await self.hass.config_entries.async_reload(existing_entry.entry_id) return self.async_abort(reason="reauth_successful") return self.async_show_form( description_placeholders={ CONF_USERNAME: existing_entry.data[CONF_USERNAME] }, step_id="reauth_confirm", data_schema=vol.Schema( { vol.Required(CONF_PASSWORD): str, } ), errors=errors, ) class CannotConnect(HomeAssistantError): """Error to indicate we cannot connect.""" class InvalidAuth(HomeAssistantError): """Error to indicate there is invalid auth."""