Raise ConfigEntryAuthFailed during setup or coordinator update to start reauth (#48962)

This commit is contained in:
J. Nick Koston 2021-04-09 19:41:29 -10:00 committed by GitHub
parent 7cc857a298
commit 4cd7f9bd8b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
51 changed files with 534 additions and 517 deletions

View File

@ -776,6 +776,7 @@ omit =
homeassistant/components/poolsense/__init__.py homeassistant/components/poolsense/__init__.py
homeassistant/components/poolsense/sensor.py homeassistant/components/poolsense/sensor.py
homeassistant/components/poolsense/binary_sensor.py homeassistant/components/poolsense/binary_sensor.py
homeassistant/components/powerwall/__init__.py
homeassistant/components/proliphix/climate.py homeassistant/components/proliphix/climate.py
homeassistant/components/progettihwsw/__init__.py homeassistant/components/progettihwsw/__init__.py
homeassistant/components/progettihwsw/binary_sensor.py homeassistant/components/progettihwsw/binary_sensor.py

View File

@ -9,7 +9,7 @@ import abodepy.helpers.timeline as TIMELINE
from requests.exceptions import ConnectTimeout, HTTPError from requests.exceptions import ConnectTimeout, HTTPError
import voluptuous as vol import voluptuous as vol
from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_REAUTH from homeassistant.config_entries import SOURCE_IMPORT
from homeassistant.const import ( from homeassistant.const import (
ATTR_ATTRIBUTION, ATTR_ATTRIBUTION,
ATTR_DATE, ATTR_DATE,
@ -20,7 +20,7 @@ from homeassistant.const import (
CONF_USERNAME, CONF_USERNAME,
EVENT_HOMEASSISTANT_STOP, EVENT_HOMEASSISTANT_STOP,
) )
from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from homeassistant.helpers import config_validation as cv from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.dispatcher import dispatcher_send from homeassistant.helpers.dispatcher import dispatcher_send
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
@ -124,17 +124,10 @@ async def async_setup_entry(hass, config_entry):
) )
except AbodeAuthenticationException as ex: except AbodeAuthenticationException as ex:
LOGGER.error("Invalid credentials: %s", ex) raise ConfigEntryAuthFailed(f"Invalid credentials: {ex}") from ex
await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_REAUTH},
data=config_entry.data,
)
return False
except (AbodeException, ConnectTimeout, HTTPError) as ex: except (AbodeException, ConnectTimeout, HTTPError) as ex:
LOGGER.error("Unable to connect to Abode: %s", ex) raise ConfigEntryNotReady(f"Unable to connect to Abode: {ex}") from ex
raise ConfigEntryNotReady from ex
hass.data[DOMAIN] = AbodeSystem(abode, polling) hass.data[DOMAIN] = AbodeSystem(abode, polling)

View File

@ -11,7 +11,6 @@ from pyairvisual.errors import (
NodeProError, NodeProError,
) )
from homeassistant.config_entries import SOURCE_REAUTH
from homeassistant.const import ( from homeassistant.const import (
ATTR_ATTRIBUTION, ATTR_ATTRIBUTION,
CONF_API_KEY, CONF_API_KEY,
@ -23,6 +22,7 @@ from homeassistant.const import (
CONF_STATE, CONF_STATE,
) )
from homeassistant.core import callback from homeassistant.core import callback
from homeassistant.exceptions import ConfigEntryAuthFailed
from homeassistant.helpers import aiohttp_client, config_validation as cv from homeassistant.helpers import aiohttp_client, config_validation as cv
from homeassistant.helpers.update_coordinator import ( from homeassistant.helpers.update_coordinator import (
CoordinatorEntity, CoordinatorEntity,
@ -206,27 +206,8 @@ async def async_setup_entry(hass, config_entry):
try: try:
return await api_coro return await api_coro
except (InvalidKeyError, KeyExpiredError): except (InvalidKeyError, KeyExpiredError) as ex:
matching_flows = [ raise ConfigEntryAuthFailed from ex
flow
for flow in hass.config_entries.flow.async_progress()
if flow["context"]["source"] == SOURCE_REAUTH
and flow["context"]["unique_id"] == config_entry.unique_id
]
if not matching_flows:
hass.async_create_task(
hass.config_entries.flow.async_init(
DOMAIN,
context={
"source": SOURCE_REAUTH,
"unique_id": config_entry.unique_id,
},
data=config_entry.data,
)
)
return {}
except AirVisualError as err: except AirVisualError as err:
raise UpdateFailed(f"Error while retrieving data: {err}") from err raise UpdateFailed(f"Error while retrieving data: {err}") from err

View File

@ -8,10 +8,14 @@ from yalexs.exceptions import AugustApiAIOHTTPError
from yalexs.pubnub_activity import activities_from_pubnub_message from yalexs.pubnub_activity import activities_from_pubnub_message
from yalexs.pubnub_async import AugustPubNub, async_create_pubnub from yalexs.pubnub_async import AugustPubNub, async_create_pubnub
from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_PASSWORD, HTTP_UNAUTHORIZED from homeassistant.const import CONF_PASSWORD
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import ConfigEntryNotReady, HomeAssistantError from homeassistant.exceptions import (
ConfigEntryAuthFailed,
ConfigEntryNotReady,
HomeAssistantError,
)
from .activity import ActivityStream from .activity import ActivityStream
from .const import DATA_AUGUST, DOMAIN, MIN_TIME_BETWEEN_DETAIL_UPDATES, PLATFORMS from .const import DATA_AUGUST, DOMAIN, MIN_TIME_BETWEEN_DETAIL_UPDATES, PLATFORMS
@ -43,28 +47,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
try: try:
await august_gateway.async_setup(entry.data) await august_gateway.async_setup(entry.data)
return await async_setup_august(hass, entry, august_gateway) return await async_setup_august(hass, entry, august_gateway)
except ClientResponseError as err: except (RequireValidation, InvalidAuth) as err:
if err.status == HTTP_UNAUTHORIZED: raise ConfigEntryAuthFailed from err
_async_start_reauth(hass, entry) except (ClientResponseError, CannotConnect, asyncio.TimeoutError) as err:
return False
raise ConfigEntryNotReady from err raise ConfigEntryNotReady from err
except (RequireValidation, InvalidAuth):
_async_start_reauth(hass, entry)
return False
except (CannotConnect, asyncio.TimeoutError) as err:
raise ConfigEntryNotReady from err
def _async_start_reauth(hass: HomeAssistant, entry: ConfigEntry):
hass.async_create_task(
hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_REAUTH},
data=entry.data,
)
)
_LOGGER.error("Password is no longer valid. Please reauthenticate")
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):

View File

@ -8,8 +8,8 @@ from async_timeout import timeout
from python_awair import Awair from python_awair import Awair
from python_awair.exceptions import AuthError from python_awair.exceptions import AuthError
from homeassistant.config_entries import SOURCE_REAUTH
from homeassistant.const import CONF_ACCESS_TOKEN from homeassistant.const import CONF_ACCESS_TOKEN
from homeassistant.exceptions import ConfigEntryAuthFailed
from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
@ -74,27 +74,7 @@ class AwairDataUpdateCoordinator(DataUpdateCoordinator):
) )
return {result.device.uuid: result for result in results} return {result.device.uuid: result for result in results}
except AuthError as err: except AuthError as err:
flow_context = { raise ConfigEntryAuthFailed from err
"source": SOURCE_REAUTH,
"unique_id": self._config_entry.unique_id,
}
matching_flows = [
flow
for flow in self.hass.config_entries.flow.async_progress()
if flow["context"] == flow_context
]
if not matching_flows:
self.hass.async_create_task(
self.hass.config_entries.flow.async_init(
DOMAIN,
context=flow_context,
data=self._config_entry.data,
)
)
raise UpdateFailed(err) from err
except Exception as err: except Exception as err:
raise UpdateFailed(err) from err raise UpdateFailed(err) from err

View File

@ -69,13 +69,9 @@ class AwairFlowHandler(ConfigFlow, domain=DOMAIN):
_, error = await self._check_connection(access_token) _, error = await self._check_connection(access_token)
if error is None: if error is None:
for entry in self._async_current_entries(): entry = await self.async_set_unique_id(self.unique_id)
if entry.unique_id == self.unique_id: self.hass.config_entries.async_update_entry(entry, data=user_input)
self.hass.config_entries.async_update_entry( return self.async_abort(reason="reauth_successful")
entry, data=user_input
)
return self.async_abort(reason="reauth_successful")
if error != "invalid_access_token": if error != "invalid_access_token":
return self.async_abort(reason=error) return self.async_abort(reason=error)

View File

@ -13,7 +13,6 @@ from axis.streammanager import SIGNAL_PLAYING, STATE_STOPPED
from homeassistant.components import mqtt from homeassistant.components import mqtt
from homeassistant.components.mqtt import DOMAIN as MQTT_DOMAIN from homeassistant.components.mqtt import DOMAIN as MQTT_DOMAIN
from homeassistant.components.mqtt.models import Message from homeassistant.components.mqtt.models import Message
from homeassistant.config_entries import SOURCE_REAUTH
from homeassistant.const import ( from homeassistant.const import (
CONF_HOST, CONF_HOST,
CONF_NAME, CONF_NAME,
@ -23,7 +22,7 @@ from homeassistant.const import (
CONF_USERNAME, CONF_USERNAME,
) )
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC
from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.helpers.httpx_client import get_async_client from homeassistant.helpers.httpx_client import get_async_client
@ -221,15 +220,8 @@ class AxisNetworkDevice:
except CannotConnect as err: except CannotConnect as err:
raise ConfigEntryNotReady from err raise ConfigEntryNotReady from err
except AuthenticationRequired: except AuthenticationRequired as err:
self.hass.async_create_task( raise ConfigEntryAuthFailed from err
self.hass.config_entries.flow.async_init(
AXIS_DOMAIN,
context={"source": SOURCE_REAUTH},
data=self.config_entry.data,
)
)
return False
self.fw_version = self.api.vapix.firmware_version self.fw_version = self.api.vapix.firmware_version
self.product_type = self.api.vapix.product_type self.product_type = self.api.vapix.product_type

View File

@ -14,8 +14,8 @@ from homeassistant.components.azure_devops.const import (
DATA_AZURE_DEVOPS_CLIENT, DATA_AZURE_DEVOPS_CLIENT,
DOMAIN, DOMAIN,
) )
from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
from homeassistant.helpers.typing import ConfigType, HomeAssistantType from homeassistant.helpers.typing import ConfigType, HomeAssistantType
@ -30,17 +30,9 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool
if entry.data[CONF_PAT] is not None: if entry.data[CONF_PAT] is not None:
await client.authorize(entry.data[CONF_PAT], entry.data[CONF_ORG]) await client.authorize(entry.data[CONF_PAT], entry.data[CONF_ORG])
if not client.authorized: if not client.authorized:
_LOGGER.warning( raise ConfigEntryAuthFailed(
"Could not authorize with Azure DevOps. You may need to update your token" "Could not authorize with Azure DevOps. You may need to update your token"
) )
hass.async_create_task(
hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_REAUTH},
data=entry.data,
)
)
return False
await client.get_project(entry.data[CONF_ORG], entry.data[CONF_PROJECT]) await client.get_project(entry.data[CONF_ORG], entry.data[CONF_PROJECT])
except aiohttp.ClientError as exception: except aiohttp.ClientError as exception:
_LOGGER.warning(exception) _LOGGER.warning(exception)

View File

@ -105,17 +105,16 @@ class AzureDevOpsFlowHandler(ConfigFlow, domain=DOMAIN):
if errors is not None: if errors is not None:
return await self._show_reauth_form(errors) return await self._show_reauth_form(errors)
for entry in self._async_current_entries(): entry = await self.async_set_unique_id(self.unique_id)
if entry.unique_id == self.unique_id: self.hass.config_entries.async_update_entry(
self.hass.config_entries.async_update_entry( entry,
entry, data={
data={ CONF_ORG: self._organization,
CONF_ORG: self._organization, CONF_PROJECT: self._project,
CONF_PROJECT: self._project, CONF_PAT: self._pat,
CONF_PAT: self._pat, },
}, )
) return self.async_abort(reason="reauth_successful")
return self.async_abort(reason="reauth_successful")
def _async_create_entry(self): def _async_create_entry(self):
"""Handle create entry.""" """Handle create entry."""

View File

@ -4,10 +4,9 @@ import asyncio
import async_timeout import async_timeout
from pydeconz import DeconzSession, errors 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.const import CONF_API_KEY, CONF_HOST, CONF_PORT
from homeassistant.core import callback from homeassistant.core import callback
from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from homeassistant.helpers import aiohttp_client from homeassistant.helpers import aiohttp_client
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC
from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.dispatcher import async_dispatcher_send
@ -174,15 +173,8 @@ class DeconzGateway:
except CannotConnect as err: except CannotConnect as err:
raise ConfigEntryNotReady from err raise ConfigEntryNotReady from err
except AuthenticationRequired: except AuthenticationRequired as err:
self.hass.async_create_task( raise ConfigEntryAuthFailed from err
self.hass.config_entries.flow.async_init(
DECONZ_DOMAIN,
context={"source": SOURCE_REAUTH},
data=self.config_entry.data,
)
)
return False
for platform in PLATFORMS: for platform in PLATFORMS:
self.hass.async_create_task( self.hass.async_create_task(

View File

@ -14,9 +14,10 @@ from pyfireservicerota import (
from homeassistant.components.binary_sensor import DOMAIN as BINARYSENSOR_DOMAIN from homeassistant.components.binary_sensor import DOMAIN as BINARYSENSOR_DOMAIN
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_TOKEN, CONF_URL, CONF_USERNAME from homeassistant.const import CONF_TOKEN, CONF_URL, CONF_USERNAME
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed
from homeassistant.helpers.dispatcher import dispatcher_send from homeassistant.helpers.dispatcher import dispatcher_send
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
@ -109,19 +110,10 @@ class FireServiceRotaOauth:
self._fsr.refresh_tokens self._fsr.refresh_tokens
) )
except (InvalidAuthError, InvalidTokenError): except (InvalidAuthError, InvalidTokenError) as err:
_LOGGER.error("Error refreshing tokens, triggered reauth workflow") raise ConfigEntryAuthFailed(
self._hass.async_create_task( "Error refreshing tokens, triggered reauth workflow"
self._hass.config_entries.flow.async_init( ) from err
DOMAIN,
context={"source": SOURCE_REAUTH},
data={
**self._entry.data,
},
)
)
return False
_LOGGER.debug("Saving new tokens in config entry") _LOGGER.debug("Saving new tokens in config entry")
self._hass.config_entries.async_update_entry( self._hass.config_entries.async_update_entry(

View File

@ -82,11 +82,10 @@ class FireServiceRotaFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
if step_id == "user": if step_id == "user":
return self.async_create_entry(title=self._username, data=data) return self.async_create_entry(title=self._username, data=data)
for entry in self.hass.config_entries.async_entries(DOMAIN): entry = await self.async_set_unique_id(self.unique_id)
if entry.unique_id == self.unique_id: self.hass.config_entries.async_update_entry(entry, data=data)
self.hass.config_entries.async_update_entry(entry, data=data) await self.hass.config_entries.async_reload(entry.entry_id)
await self.hass.config_entries.async_reload(entry.entry_id) return self.async_abort(reason="reauth_successful")
return self.async_abort(reason="reauth_successful")
def _show_setup_form(self, user_input=None, errors=None, step_id="user"): def _show_setup_form(self, user_input=None, errors=None, step_id="user"):
"""Show the setup form to the user.""" """Show the setup form to the user."""

View File

@ -5,7 +5,7 @@ import socket
from pyfritzhome import Fritzhome, LoginError from pyfritzhome import Fritzhome, LoginError
import voluptuous as vol import voluptuous as vol
from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_REAUTH from homeassistant.config_entries import SOURCE_IMPORT
from homeassistant.const import ( from homeassistant.const import (
CONF_DEVICES, CONF_DEVICES,
CONF_HOST, CONF_HOST,
@ -13,6 +13,7 @@ from homeassistant.const import (
CONF_USERNAME, CONF_USERNAME,
EVENT_HOMEASSISTANT_STOP, EVENT_HOMEASSISTANT_STOP,
) )
from homeassistant.exceptions import ConfigEntryAuthFailed
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from .const import CONF_CONNECTIONS, DEFAULT_HOST, DEFAULT_USERNAME, DOMAIN, PLATFORMS from .const import CONF_CONNECTIONS, DEFAULT_HOST, DEFAULT_USERNAME, DOMAIN, PLATFORMS
@ -80,15 +81,8 @@ async def async_setup_entry(hass, entry):
try: try:
await hass.async_add_executor_job(fritz.login) await hass.async_add_executor_job(fritz.login)
except LoginError: except LoginError as err:
hass.async_create_task( raise ConfigEntryAuthFailed from err
hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_REAUTH},
data=entry,
)
)
return False
hass.data.setdefault(DOMAIN, {CONF_CONNECTIONS: {}, CONF_DEVICES: set()}) hass.data.setdefault(DOMAIN, {CONF_CONNECTIONS: {}, CONF_DEVICES: set()})
hass.data[DOMAIN][CONF_CONNECTIONS][entry.entry_id] = fritz hass.data[DOMAIN][CONF_CONNECTIONS][entry.entry_id] = fritz

View File

@ -170,12 +170,12 @@ class FritzboxConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
errors=errors, errors=errors,
) )
async def async_step_reauth(self, entry): async def async_step_reauth(self, data):
"""Trigger a reauthentication flow.""" """Trigger a reauthentication flow."""
self._entry = entry self._entry = self.hass.config_entries.async_get_entry(self.context["entry_id"])
self._host = entry.data[CONF_HOST] self._host = data[CONF_HOST]
self._name = entry.data[CONF_HOST] self._name = data[CONF_HOST]
self._username = entry.data[CONF_USERNAME] self._username = data[CONF_USERNAME]
return await self.async_step_reauth_confirm() return await self.async_step_reauth_confirm()

View File

@ -10,7 +10,7 @@ import voluptuous as vol
from homeassistant import config_entries from homeassistant import config_entries
from homeassistant.const import CONF_PASSWORD, CONF_SCAN_INTERVAL, CONF_USERNAME from homeassistant.const import CONF_PASSWORD, CONF_SCAN_INTERVAL, CONF_USERNAME
from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from homeassistant.helpers import aiohttp_client, config_validation as cv from homeassistant.helpers import aiohttp_client, config_validation as cv
from homeassistant.helpers.dispatcher import ( from homeassistant.helpers.dispatcher import (
async_dispatcher_connect, async_dispatcher_connect,
@ -77,18 +77,8 @@ async def async_setup_entry(hass, entry):
except HTTPException as error: except HTTPException as error:
_LOGGER.error("Could not connect to the internet: %s", error) _LOGGER.error("Could not connect to the internet: %s", error)
raise ConfigEntryNotReady() from error raise ConfigEntryNotReady() from error
except HiveReauthRequired: except HiveReauthRequired as err:
hass.async_create_task( raise ConfigEntryAuthFailed from err
hass.config_entries.flow.async_init(
DOMAIN,
context={
"source": config_entries.SOURCE_REAUTH,
"unique_id": entry.unique_id,
},
data=entry.data,
)
)
return False
for ha_type, hive_type in PLATFORM_LOOKUP.items(): for ha_type, hive_type in PLATFORM_LOOKUP.items():
device_list = devices.get(hive_type) device_list = devices.get(hive_type)

View File

@ -11,10 +11,10 @@ from hyperion import client, const as hyperion_const
from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN
from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_HOST, CONF_PORT, CONF_SOURCE, CONF_TOKEN from homeassistant.const import CONF_HOST, CONF_PORT, CONF_TOKEN
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from homeassistant.helpers.dispatcher import ( from homeassistant.helpers.dispatcher import (
async_dispatcher_connect, async_dispatcher_connect,
async_dispatcher_send, async_dispatcher_send,
@ -109,17 +109,6 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
return True return True
async def _create_reauth_flow(
hass: HomeAssistant,
config_entry: ConfigEntry,
) -> None:
hass.async_create_task(
hass.config_entries.flow.async_init(
DOMAIN, context={CONF_SOURCE: SOURCE_REAUTH}, data=config_entry.data
)
)
@callback @callback
def listen_for_instance_updates( def listen_for_instance_updates(
hass: HomeAssistant, hass: HomeAssistant,
@ -181,14 +170,12 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
and token is None and token is None
): ):
await hyperion_client.async_client_disconnect() await hyperion_client.async_client_disconnect()
await _create_reauth_flow(hass, config_entry) raise ConfigEntryAuthFailed
return False
# Client login doesn't work? => Reauth. # Client login doesn't work? => Reauth.
if not await hyperion_client.async_client_login(): if not await hyperion_client.async_client_login():
await hyperion_client.async_client_disconnect() await hyperion_client.async_client_disconnect()
await _create_reauth_flow(hass, config_entry) raise ConfigEntryAuthFailed
return False
# Cannot switch instance or cannot load state? => Not ready. # Cannot switch instance or cannot load state? => Not ready.
if ( if (

View File

@ -154,11 +154,10 @@ class IcloudFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
if step_id == "user": if step_id == "user":
return self.async_create_entry(title=self._username, data=data) return self.async_create_entry(title=self._username, data=data)
for entry in self.hass.config_entries.async_entries(DOMAIN): entry = await self.async_set_unique_id(self.unique_id)
if entry.unique_id == self.unique_id: self.hass.config_entries.async_update_entry(entry, data=data)
self.hass.config_entries.async_update_entry(entry, data=data) await self.hass.config_entries.async_reload(entry.entry_id)
await self.hass.config_entries.async_reload(entry.entry_id) return self.async_abort(reason="reauth_successful")
return self.async_abort(reason="reauth_successful")
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."""

View File

@ -7,14 +7,9 @@ from pybotvac import Account, Neato
from pybotvac.exceptions import NeatoException from pybotvac.exceptions import NeatoException
import voluptuous as vol import voluptuous as vol
from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ( from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET, CONF_TOKEN
CONF_CLIENT_ID, from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
CONF_CLIENT_SECRET,
CONF_SOURCE,
CONF_TOKEN,
)
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers import config_entry_oauth2_flow, config_validation as cv from homeassistant.helpers import config_entry_oauth2_flow, config_validation as cv
from homeassistant.helpers.typing import ConfigType, HomeAssistantType from homeassistant.helpers.typing import ConfigType, HomeAssistantType
from homeassistant.util import Throttle from homeassistant.util import Throttle
@ -74,14 +69,7 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool:
async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool: async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool:
"""Set up config entry.""" """Set up config entry."""
if CONF_TOKEN not in entry.data: if CONF_TOKEN not in entry.data:
# Init reauth flow raise ConfigEntryAuthFailed
hass.async_create_task(
hass.config_entries.flow.async_init(
NEATO_DOMAIN,
context={CONF_SOURCE: SOURCE_REAUTH},
)
)
return False
implementation = ( implementation = (
await config_entry_oauth2_flow.async_get_config_entry_implementation( await config_entry_oauth2_flow.async_get_config_entry_implementation(

View File

@ -12,7 +12,7 @@ from google_nest_sdm.exceptions import (
from google_nest_sdm.google_nest_subscriber import GoogleNestSubscriber from google_nest_sdm.google_nest_subscriber import GoogleNestSubscriber
import voluptuous as vol import voluptuous as vol
from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ( from homeassistant.const import (
CONF_BINARY_SENSORS, CONF_BINARY_SENSORS,
CONF_CLIENT_ID, CONF_CLIENT_ID,
@ -22,7 +22,7 @@ from homeassistant.const import (
CONF_STRUCTURE, CONF_STRUCTURE,
) )
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from homeassistant.helpers import ( from homeassistant.helpers import (
aiohttp_client, aiohttp_client,
config_entry_oauth2_flow, config_entry_oauth2_flow,
@ -167,14 +167,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
await subscriber.start_async() await subscriber.start_async()
except AuthException as err: except AuthException as err:
_LOGGER.debug("Subscriber authentication error: %s", err) _LOGGER.debug("Subscriber authentication error: %s", err)
hass.async_create_task( raise ConfigEntryAuthFailed from err
hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_REAUTH},
data=entry.data,
)
)
return False
except ConfigurationException as err: except ConfigurationException as err:
_LOGGER.error("Configuration error: %s", err) _LOGGER.error("Configuration error: %s", err)
subscriber.stop_async() subscriber.stop_async()

View File

@ -10,7 +10,7 @@ from pynuki.bridge import InvalidCredentialsException
from requests.exceptions import RequestException from requests.exceptions import RequestException
from homeassistant import exceptions from homeassistant import exceptions
from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_REAUTH from homeassistant.config_entries import SOURCE_IMPORT
from homeassistant.const import CONF_HOST, CONF_PORT, CONF_TOKEN from homeassistant.const import CONF_HOST, CONF_PORT, CONF_TOKEN
from homeassistant.helpers.update_coordinator import ( from homeassistant.helpers.update_coordinator import (
CoordinatorEntity, CoordinatorEntity,
@ -85,13 +85,8 @@ async def async_setup_entry(hass, entry):
) )
locks, openers = await hass.async_add_executor_job(_get_bridge_devices, bridge) locks, openers = await hass.async_add_executor_job(_get_bridge_devices, bridge)
except InvalidCredentialsException: except InvalidCredentialsException as err:
hass.async_create_task( raise exceptions.ConfigEntryAuthFailed from err
hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_REAUTH}, data=entry.data
)
)
return False
except RequestException as err: except RequestException as err:
raise exceptions.ConfigEntryNotReady from err raise exceptions.ConfigEntryNotReady from err

View File

@ -10,9 +10,9 @@ import async_timeout
from ovoenergy import OVODailyUsage from ovoenergy import OVODailyUsage
from ovoenergy.ovoenergy import OVOEnergy from ovoenergy.ovoenergy import OVOEnergy
from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from homeassistant.helpers.typing import ConfigType, HomeAssistantType from homeassistant.helpers.typing import ConfigType, HomeAssistantType
from homeassistant.helpers.update_coordinator import ( from homeassistant.helpers.update_coordinator import (
CoordinatorEntity, CoordinatorEntity,
@ -44,12 +44,7 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool
raise ConfigEntryNotReady from exception raise ConfigEntryNotReady from exception
if not authenticated: if not authenticated:
hass.async_create_task( raise ConfigEntryAuthFailed
hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_REAUTH}, data=entry.data
)
)
return False
async def async_update_data() -> OVODailyUsage: async def async_update_data() -> OVODailyUsage:
"""Fetch data from OVO Energy.""" """Fetch data from OVO Energy."""
@ -61,12 +56,7 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool
except aiohttp.ClientError as exception: except aiohttp.ClientError as exception:
raise UpdateFailed(exception) from exception raise UpdateFailed(exception) from exception
if not authenticated: if not authenticated:
hass.async_create_task( raise ConfigEntryAuthFailed("Not authenticated with OVO Energy")
hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_REAUTH}, data=entry.data
)
)
raise UpdateFailed("Not authenticated with OVO Energy")
return await client.get_daily_usage(datetime.utcnow().strftime("%Y-%m")) return await client.get_daily_usage(datetime.utcnow().strftime("%Y-%m"))
coordinator = DataUpdateCoordinator( coordinator = DataUpdateCoordinator(

View File

@ -74,18 +74,15 @@ class OVOEnergyFlowHandler(ConfigFlow, domain=DOMAIN):
errors["base"] = "connection_error" errors["base"] = "connection_error"
else: else:
if authenticated: if authenticated:
await self.async_set_unique_id(self.username) entry = await self.async_set_unique_id(self.username)
self.hass.config_entries.async_update_entry(
for entry in self._async_current_entries(): entry,
if entry.unique_id == self.unique_id: data={
self.hass.config_entries.async_update_entry( CONF_USERNAME: self.username,
entry, CONF_PASSWORD: user_input[CONF_PASSWORD],
data={ },
CONF_USERNAME: self.username, )
CONF_PASSWORD: user_input[CONF_PASSWORD], return self.async_abort(reason="reauth_successful")
},
)
return self.async_abort(reason="reauth_successful")
errors["base"] = "authorization_error" errors["base"] = "authorization_error"

View File

@ -15,15 +15,10 @@ from plexwebsocket import (
import requests.exceptions import requests.exceptions
from homeassistant.components.media_player import DOMAIN as MP_DOMAIN from homeassistant.components.media_player import DOMAIN as MP_DOMAIN
from homeassistant.config_entries import ENTRY_STATE_SETUP_RETRY, SOURCE_REAUTH from homeassistant.config_entries import ENTRY_STATE_SETUP_RETRY
from homeassistant.const import ( from homeassistant.const import CONF_URL, CONF_VERIFY_SSL, EVENT_HOMEASSISTANT_STOP
CONF_SOURCE,
CONF_URL,
CONF_VERIFY_SSL,
EVENT_HOMEASSISTANT_STOP,
)
from homeassistant.core import callback from homeassistant.core import callback
from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from homeassistant.helpers import device_registry as dev_reg, entity_registry as ent_reg from homeassistant.helpers import device_registry as dev_reg, entity_registry as ent_reg
from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.debounce import Debouncer from homeassistant.helpers.debounce import Debouncer
@ -120,19 +115,10 @@ async def async_setup_entry(hass, entry):
error, error,
) )
raise ConfigEntryNotReady from error raise ConfigEntryNotReady from error
except plexapi.exceptions.Unauthorized: except plexapi.exceptions.Unauthorized as ex:
hass.async_create_task( raise ConfigEntryAuthFailed(
hass.config_entries.flow.async_init( f"Token not accepted, please reauthenticate Plex server '{entry.data[CONF_SERVER]}'"
PLEX_DOMAIN, ) from ex
context={CONF_SOURCE: SOURCE_REAUTH},
data=entry.data,
)
)
_LOGGER.error(
"Token not accepted, please reauthenticate Plex server '%s'",
entry.data[CONF_SERVER],
)
return False
except ( except (
plexapi.exceptions.BadRequest, plexapi.exceptions.BadRequest,
plexapi.exceptions.NotFound, plexapi.exceptions.NotFound,

View File

@ -11,10 +11,10 @@ from tesla_powerwall import (
PowerwallUnreachableError, PowerwallUnreachableError,
) )
from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_IP_ADDRESS, CONF_PASSWORD from homeassistant.const import CONF_IP_ADDRESS, CONF_PASSWORD
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from homeassistant.helpers import entity_registry from homeassistant.helpers import entity_registry
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
@ -115,8 +115,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
except AccessDeniedError as err: except AccessDeniedError as err:
_LOGGER.debug("Authentication failed", exc_info=err) _LOGGER.debug("Authentication failed", exc_info=err)
http_session.close() http_session.close()
_async_start_reauth(hass, entry) raise ConfigEntryAuthFailed from err
return False
await _migrate_old_unique_ids(hass, entry_id, powerwall_data) await _migrate_old_unique_ids(hass, entry_id, powerwall_data)
@ -130,13 +129,16 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
_LOGGER.debug("Updating data") _LOGGER.debug("Updating data")
try: try:
return await _async_update_powerwall_data(hass, entry, power_wall) return await _async_update_powerwall_data(hass, entry, power_wall)
except AccessDeniedError: except AccessDeniedError as err:
if password is None: if password is None:
raise raise ConfigEntryAuthFailed from err
# If the session expired, relogin, and try again # If the session expired, relogin, and try again
await hass.async_add_executor_job(power_wall.login, "", password) try:
return await _async_update_powerwall_data(hass, entry, power_wall) await hass.async_add_executor_job(power_wall.login, "", password)
return await _async_update_powerwall_data(hass, entry, power_wall)
except AccessDeniedError as ex:
raise ConfigEntryAuthFailed from ex
coordinator = DataUpdateCoordinator( coordinator = DataUpdateCoordinator(
hass, hass,
@ -181,17 +183,6 @@ async def _async_update_powerwall_data(
return hass.data[DOMAIN][entry.entry_id][POWERWALL_COORDINATOR].data return hass.data[DOMAIN][entry.entry_id][POWERWALL_COORDINATOR].data
def _async_start_reauth(hass: HomeAssistant, entry: ConfigEntry):
hass.async_create_task(
hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_REAUTH},
data=entry.data,
)
)
_LOGGER.error("Password is no longer valid. Please reauthenticate")
def _login_and_fetch_base_info(power_wall: Powerwall, password: str): def _login_and_fetch_base_info(power_wall: Powerwall, password: str):
"""Login to the powerwall and fetch the base info.""" """Login to the powerwall and fetch the base info."""
if password is not None: if password is not None:

View File

@ -84,13 +84,10 @@ class SharkIqConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
_, errors = await self._async_validate_input(user_input) _, errors = await self._async_validate_input(user_input)
if not errors: if not errors:
for entry in self._async_current_entries(): entry = await self.async_set_unique_id(self.unique_id)
if entry.unique_id == self.unique_id: self.hass.config_entries.async_update_entry(entry, data=user_input)
self.hass.config_entries.async_update_entry(
entry, data=user_input
)
return self.async_abort(reason="reauth_successful") return self.async_abort(reason="reauth_successful")
if errors["base"] != "invalid_auth": if errors["base"] != "invalid_auth":
return self.async_abort(reason=errors["base"]) return self.async_abort(reason=errors["base"])

View File

@ -12,8 +12,9 @@ from sharkiqpy import (
SharkIqVacuum, SharkIqVacuum,
) )
from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
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, API_TIMEOUT, DOMAIN, UPDATE_INTERVAL from .const import _LOGGER, API_TIMEOUT, DOMAIN, UPDATE_INTERVAL
@ -75,30 +76,7 @@ class SharkIqUpdateCoordinator(DataUpdateCoordinator):
SharkIqAuthExpiringError, SharkIqAuthExpiringError,
) as err: ) as err:
_LOGGER.debug("Bad auth state. Attempting re-auth", exc_info=err) _LOGGER.debug("Bad auth state. Attempting re-auth", exc_info=err)
flow_context = { raise ConfigEntryAuthFailed from err
"source": SOURCE_REAUTH,
"unique_id": self._config_entry.unique_id,
}
matching_flows = [
flow
for flow in self.hass.config_entries.flow.async_progress()
if flow["context"] == flow_context
]
if not matching_flows:
_LOGGER.debug("Re-initializing flows. Attempting re-auth")
self.hass.async_create_task(
self.hass.config_entries.flow.async_init(
DOMAIN,
context=flow_context,
data=self._config_entry.data,
)
)
else:
_LOGGER.debug("Matching flow found")
raise UpdateFailed(err) from err
except Exception as err: except Exception as err:
_LOGGER.exception("Unexpected error updating SharkIQ") _LOGGER.exception("Unexpected error updating SharkIQ")
raise UpdateFailed(err) from err raise UpdateFailed(err) from err

View File

@ -17,7 +17,6 @@ from simplipy.websocket import (
) )
import voluptuous as vol import voluptuous as vol
from homeassistant.config_entries import SOURCE_REAUTH
from homeassistant.const import ( from homeassistant.const import (
ATTR_CODE, ATTR_CODE,
CONF_CODE, CONF_CODE,
@ -26,7 +25,7 @@ from homeassistant.const import (
EVENT_HOMEASSISTANT_STOP, EVENT_HOMEASSISTANT_STOP,
) )
from homeassistant.core import CoreState, callback from homeassistant.core import CoreState, callback
from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from homeassistant.helpers import ( from homeassistant.helpers import (
aiohttp_client, aiohttp_client,
config_validation as cv, config_validation as cv,
@ -514,27 +513,9 @@ class SimpliSafe:
for result in results: for result in results:
if isinstance(result, InvalidCredentialsError): if isinstance(result, InvalidCredentialsError):
if self._emergency_refresh_token_used: if self._emergency_refresh_token_used:
matching_flows = [ raise ConfigEntryAuthFailed(
flow "Update failed with stored refresh token"
for flow in self._hass.config_entries.flow.async_progress() )
if flow["context"].get("source") == SOURCE_REAUTH
and flow["context"].get("unique_id")
== self.config_entry.unique_id
]
if not matching_flows:
self._hass.async_create_task(
self._hass.config_entries.flow.async_init(
DOMAIN,
context={
"source": SOURCE_REAUTH,
"unique_id": self.config_entry.unique_id,
},
data=self.config_entry.data,
)
)
raise UpdateFailed("Update failed with stored refresh token")
LOGGER.warning("SimpliSafe cloud error; trying stored refresh token") LOGGER.warning("SimpliSafe cloud error; trying stored refresh token")
self._emergency_refresh_token_used = True self._emergency_refresh_token_used = True

View File

@ -8,17 +8,16 @@ from typing import Any
from sonarr import Sonarr, SonarrAccessRestricted, SonarrError from sonarr import Sonarr, SonarrAccessRestricted, SonarrError
from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ( from homeassistant.const import (
ATTR_NAME, ATTR_NAME,
CONF_API_KEY, CONF_API_KEY,
CONF_HOST, CONF_HOST,
CONF_PORT, CONF_PORT,
CONF_SOURCE,
CONF_SSL, CONF_SSL,
CONF_VERIFY_SSL, CONF_VERIFY_SSL,
) )
from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
from homeassistant.helpers.typing import HomeAssistantType from homeassistant.helpers.typing import HomeAssistantType
@ -73,9 +72,10 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool
try: try:
await sonarr.update() await sonarr.update()
except SonarrAccessRestricted: except SonarrAccessRestricted as err:
_async_start_reauth(hass, entry) raise ConfigEntryAuthFailed(
return False "API Key is no longer valid. Please reauthenticate"
) from err
except SonarrError as err: except SonarrError as err:
raise ConfigEntryNotReady from err raise ConfigEntryNotReady from err
@ -113,17 +113,6 @@ async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry) -> boo
return unload_ok return unload_ok
def _async_start_reauth(hass: HomeAssistantType, entry: ConfigEntry):
hass.async_create_task(
hass.config_entries.flow.async_init(
DOMAIN,
context={CONF_SOURCE: SOURCE_REAUTH},
data={"config_entry_id": entry.entry_id, **entry.data},
)
)
_LOGGER.error("API Key is no longer valid. Please reauthenticate")
async def _async_update_listener(hass: HomeAssistantType, entry: ConfigEntry) -> None: async def _async_update_listener(hass: HomeAssistantType, entry: ConfigEntry) -> None:
"""Handle options update.""" """Handle options update."""
await hass.config_entries.async_reload(entry.entry_id) await hass.config_entries.async_reload(entry.entry_id)

View File

@ -79,7 +79,8 @@ class SonarrConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle configuration by re-auth.""" """Handle configuration by re-auth."""
self._reauth = True self._reauth = True
self._entry_data = dict(data) self._entry_data = dict(data)
self._entry_id = self._entry_data.pop("config_entry_id") entry = await self.async_set_unique_id(self.unique_id)
self._entry_id = entry.entry_id
return await self.async_step_reauth_confirm() return await self.async_step_reauth_confirm()

View File

@ -6,10 +6,10 @@ import voluptuous as vol
from homeassistant.components.media_player import DOMAIN as MEDIA_PLAYER_DOMAIN from homeassistant.components.media_player import DOMAIN as MEDIA_PLAYER_DOMAIN
from homeassistant.components.spotify import config_flow from homeassistant.components.spotify import config_flow
from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ATTR_CREDENTIALS, CONF_CLIENT_ID, CONF_CLIENT_SECRET from homeassistant.const import ATTR_CREDENTIALS, CONF_CLIENT_ID, CONF_CLIENT_SECRET
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from homeassistant.helpers import config_entry_oauth2_flow, config_validation as cv from homeassistant.helpers import config_entry_oauth2_flow, config_validation as cv
from homeassistant.helpers.config_entry_oauth2_flow import ( from homeassistant.helpers.config_entry_oauth2_flow import (
OAuth2Session, OAuth2Session,
@ -84,13 +84,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
} }
if not set(session.token["scope"].split(" ")).issuperset(SPOTIFY_SCOPES): if not set(session.token["scope"].split(" ")).issuperset(SPOTIFY_SCOPES):
hass.async_create_task( raise ConfigEntryAuthFailed
hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_REAUTH},
data=entry.data,
)
)
hass.async_create_task( hass.async_create_task(
hass.config_entries.async_forward_entry_setup(entry, MEDIA_PLAYER_DOMAIN) hass.config_entries.async_forward_entry_setup(entry, MEDIA_PLAYER_DOMAIN)

View File

@ -9,7 +9,7 @@ from teslajsonpy import Controller as TeslaAPI
from teslajsonpy.exceptions import IncompleteCredentials, TeslaException from teslajsonpy.exceptions import IncompleteCredentials, TeslaException
import voluptuous as vol import voluptuous as vol
from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_REAUTH, ConfigEntry from homeassistant.config_entries import SOURCE_IMPORT
from homeassistant.const import ( from homeassistant.const import (
ATTR_BATTERY_CHARGING, ATTR_BATTERY_CHARGING,
ATTR_BATTERY_LEVEL, ATTR_BATTERY_LEVEL,
@ -20,7 +20,8 @@ from homeassistant.const import (
CONF_USERNAME, CONF_USERNAME,
HTTP_UNAUTHORIZED, HTTP_UNAUTHORIZED,
) )
from homeassistant.core import HomeAssistant, callback from homeassistant.core import callback
from homeassistant.exceptions import ConfigEntryAuthFailed
from homeassistant.helpers import aiohttp_client, config_validation as cv from homeassistant.helpers import aiohttp_client, config_validation as cv
from homeassistant.helpers.update_coordinator import ( from homeassistant.helpers.update_coordinator import (
CoordinatorEntity, CoordinatorEntity,
@ -158,12 +159,11 @@ async def async_setup_entry(hass, config_entry):
CONF_WAKE_ON_START, DEFAULT_WAKE_ON_START CONF_WAKE_ON_START, DEFAULT_WAKE_ON_START
) )
) )
except IncompleteCredentials: except IncompleteCredentials as ex:
_async_start_reauth(hass, config_entry) raise ConfigEntryAuthFailed from ex
return False
except TeslaException as ex: except TeslaException as ex:
if ex.code == HTTP_UNAUTHORIZED: if ex.code == HTTP_UNAUTHORIZED:
_async_start_reauth(hass, config_entry) raise ConfigEntryAuthFailed from ex
_LOGGER.error("Unable to communicate with Tesla API: %s", ex.message) _LOGGER.error("Unable to communicate with Tesla API: %s", ex.message)
return False return False
_async_save_tokens(hass, config_entry, access_token, refresh_token) _async_save_tokens(hass, config_entry, access_token, refresh_token)
@ -216,17 +216,6 @@ async def async_unload_entry(hass, config_entry) -> bool:
return False return False
def _async_start_reauth(hass: HomeAssistant, entry: ConfigEntry):
hass.async_create_task(
hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_REAUTH},
data=entry.data,
)
)
_LOGGER.error("Credentials are no longer valid. Please reauthenticate")
async def update_listener(hass, config_entry): async def update_listener(hass, config_entry):
"""Update when config_entry options update.""" """Update when config_entry options update."""
controller = hass.data[DOMAIN][config_entry.entry_id]["coordinator"].controller controller = hass.data[DOMAIN][config_entry.entry_id]["coordinator"].controller

View File

@ -5,9 +5,10 @@ import logging
from total_connect_client import TotalConnectClient from total_connect_client import TotalConnectClient
import voluptuous as vol import voluptuous as vol
from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from .const import CONF_USERCODES, DOMAIN from .const import CONF_USERCODES, DOMAIN
@ -46,16 +47,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
password = conf[CONF_PASSWORD] password = conf[CONF_PASSWORD]
if CONF_USERCODES not in conf: if CONF_USERCODES not in conf:
_LOGGER.warning("No usercodes in TotalConnect configuration")
# should only happen for those who used UI before we added usercodes # should only happen for those who used UI before we added usercodes
await hass.config_entries.flow.async_init( raise ConfigEntryAuthFailed("No usercodes in TotalConnect configuration")
DOMAIN,
context={
"source": SOURCE_REAUTH,
},
data=conf,
)
return False
temp_codes = conf[CONF_USERCODES] temp_codes = conf[CONF_USERCODES]
usercodes = {} usercodes = {}
@ -67,18 +60,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
) )
if not client.is_valid_credentials(): if not client.is_valid_credentials():
_LOGGER.error("TotalConnect authentication failed") raise ConfigEntryAuthFailed("TotalConnect authentication failed")
await hass.async_create_task(
hass.config_entries.flow.async_init(
DOMAIN,
context={
"source": SOURCE_REAUTH,
},
data=conf,
)
)
return False
hass.data[DOMAIN][entry.entry_id] = client hass.data[DOMAIN][entry.entry_id] = client

View File

@ -192,8 +192,11 @@ class UnifiFlowHandler(config_entries.ConfigFlow, domain=UNIFI_DOMAIN):
errors=errors, errors=errors,
) )
async def async_step_reauth(self, config_entry: dict): async def async_step_reauth(self, data: dict):
"""Trigger a reauthentication flow.""" """Trigger a reauthentication flow."""
config_entry = self.hass.config_entries.async_get_entry(
self.context["entry_id"]
)
self.reauth_config_entry = config_entry self.reauth_config_entry = config_entry
self.context["title_placeholders"] = { self.context["title_placeholders"] = {

View File

@ -30,7 +30,6 @@ from homeassistant.components.device_tracker import DOMAIN as TRACKER_DOMAIN
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
from homeassistant.components.unifi.switch import BLOCK_SWITCH, POE_SWITCH from homeassistant.components.unifi.switch import BLOCK_SWITCH, POE_SWITCH
from homeassistant.config_entries import SOURCE_REAUTH
from homeassistant.const import ( from homeassistant.const import (
CONF_HOST, CONF_HOST,
CONF_PASSWORD, CONF_PASSWORD,
@ -39,7 +38,7 @@ from homeassistant.const import (
CONF_VERIFY_SSL, CONF_VERIFY_SSL,
) )
from homeassistant.core import callback from homeassistant.core import callback
from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from homeassistant.helpers import aiohttp_client from homeassistant.helpers import aiohttp_client
from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.helpers.entity_registry import async_entries_for_config_entry from homeassistant.helpers.entity_registry import async_entries_for_config_entry
@ -323,15 +322,8 @@ class UniFiController:
except CannotConnect as err: except CannotConnect as err:
raise ConfigEntryNotReady from err raise ConfigEntryNotReady from err
except AuthenticationRequired: except AuthenticationRequired as err:
self.hass.async_create_task( raise ConfigEntryAuthFailed from err
self.hass.config_entries.flow.async_init(
UNIFI_DOMAIN,
context={"source": SOURCE_REAUTH},
data=self.config_entry,
)
)
return False
for site in sites.values(): for site in sites.values():
if self.site == site["name"]: if self.site == site["name"]:

View File

@ -16,7 +16,7 @@ from homeassistant.components.camera import DOMAIN as CAMERA_DOMAIN
from homeassistant.components.lock import DOMAIN as LOCK_DOMAIN from homeassistant.components.lock import DOMAIN as LOCK_DOMAIN
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_REAUTH, ConfigEntry from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
from homeassistant.const import ( from homeassistant.const import (
CONF_EMAIL, CONF_EMAIL,
CONF_PASSWORD, CONF_PASSWORD,
@ -24,6 +24,7 @@ from homeassistant.const import (
EVENT_HOMEASSISTANT_STOP, EVENT_HOMEASSISTANT_STOP,
) )
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.storage import STORAGE_DIR from homeassistant.helpers.storage import STORAGE_DIR
@ -124,12 +125,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
coordinator = VerisureDataUpdateCoordinator(hass, entry=entry) coordinator = VerisureDataUpdateCoordinator(hass, entry=entry)
if not await coordinator.async_login(): if not await coordinator.async_login():
await hass.config_entries.flow.async_init( raise ConfigEntryAuthFailed
DOMAIN,
context={"source": SOURCE_REAUTH},
data={"entry": entry},
)
return False
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, coordinator.async_logout) hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, coordinator.async_logout)

View File

@ -126,7 +126,7 @@ class VerisureConfigFlowHandler(ConfigFlow, domain=DOMAIN):
async def async_step_reauth(self, data: dict[str, Any]) -> dict[str, Any]: async def async_step_reauth(self, data: dict[str, Any]) -> dict[str, Any]:
"""Handle initiation of re-authentication with Verisure.""" """Handle initiation of re-authentication with Verisure."""
self.entry = data["entry"] self.entry = self.hass.config_entries.async_get_entry(self.context["entry_id"])
return await self.async_step_reauth_confirm() return await self.async_step_reauth_confirm()
async def async_step_reauth_confirm( async def async_step_reauth_confirm(

View File

@ -14,7 +14,11 @@ import attr
from homeassistant import data_entry_flow, loader from homeassistant import data_entry_flow, loader
from homeassistant.const import EVENT_HOMEASSISTANT_STARTED from homeassistant.const import EVENT_HOMEASSISTANT_STARTED
from homeassistant.core import CALLBACK_TYPE, CoreState, HomeAssistant, callback from homeassistant.core import CALLBACK_TYPE, CoreState, HomeAssistant, callback
from homeassistant.exceptions import ConfigEntryNotReady, HomeAssistantError from homeassistant.exceptions import (
ConfigEntryAuthFailed,
ConfigEntryNotReady,
HomeAssistantError,
)
from homeassistant.helpers import device_registry, entity_registry from homeassistant.helpers import device_registry, entity_registry
from homeassistant.helpers.event import Event from homeassistant.helpers.event import Event
from homeassistant.helpers.typing import UNDEFINED, UndefinedType from homeassistant.helpers.typing import UNDEFINED, UndefinedType
@ -259,13 +263,26 @@ class ConfigEntry:
"%s.async_setup_entry did not return boolean", integration.domain "%s.async_setup_entry did not return boolean", integration.domain
) )
result = False result = False
except ConfigEntryAuthFailed as ex:
message = str(ex)
auth_base_message = "could not authenticate"
auth_message = (
f"{auth_base_message}: {message}" if message else auth_base_message
)
_LOGGER.warning(
"Config entry '%s' for %s integration %s",
self.title,
self.domain,
auth_message,
)
self._async_process_on_unload()
self.async_start_reauth(hass)
result = False
except ConfigEntryNotReady as ex: except ConfigEntryNotReady as ex:
self.state = ENTRY_STATE_SETUP_RETRY self.state = ENTRY_STATE_SETUP_RETRY
wait_time = 2 ** min(tries, 4) * 5 wait_time = 2 ** min(tries, 4) * 5
tries += 1 tries += 1
message = str(ex) message = str(ex)
if not message and ex.__cause__:
message = str(ex.__cause__)
ready_message = f"ready yet: {message}" if message else "ready yet" ready_message = f"ready yet: {message}" if message else "ready yet"
if tries == 1: if tries == 1:
_LOGGER.warning( _LOGGER.warning(
@ -494,6 +511,27 @@ class ConfigEntry:
while self._on_unload: while self._on_unload:
self._on_unload.pop()() self._on_unload.pop()()
@callback
def async_start_reauth(self, hass: HomeAssistant) -> None:
"""Start a reauth flow."""
flow_context = {
"source": SOURCE_REAUTH,
"entry_id": self.entry_id,
"unique_id": self.unique_id,
}
for flow in hass.config_entries.flow.async_progress():
if flow["context"] == flow_context:
return
hass.async_create_task(
hass.config_entries.flow.async_init(
self.domain,
context=flow_context,
data=self.data,
)
)
current_entry: ContextVar[ConfigEntry | None] = ContextVar( current_entry: ContextVar[ConfigEntry | None] = ContextVar(
"current_entry", default=None "current_entry", default=None

View File

@ -98,14 +98,26 @@ class ConditionErrorContainer(ConditionError):
yield from item.output(indent) yield from item.output(indent)
class PlatformNotReady(HomeAssistantError): class IntegrationError(HomeAssistantError):
"""Base class for platform and config entry exceptions."""
def __str__(self) -> str:
"""Return a human readable error."""
return super().__str__() or str(self.__cause__)
class PlatformNotReady(IntegrationError):
"""Error to indicate that platform is not ready.""" """Error to indicate that platform is not ready."""
class ConfigEntryNotReady(HomeAssistantError): class ConfigEntryNotReady(IntegrationError):
"""Error to indicate that config entry is not ready.""" """Error to indicate that config entry is not ready."""
class ConfigEntryAuthFailed(IntegrationError):
"""Error to indicate that config entry could not authenticate."""
class InvalidStateError(HomeAssistantError): class InvalidStateError(HomeAssistantError):
"""When an invalid state is encountered.""" """When an invalid state is encountered."""

View File

@ -228,8 +228,6 @@ class EntityPlatform:
tries += 1 tries += 1
wait_time = min(tries, 6) * PLATFORM_NOT_READY_BASE_WAIT_TIME wait_time = min(tries, 6) * PLATFORM_NOT_READY_BASE_WAIT_TIME
message = str(ex) message = str(ex)
if not message and ex.__cause__:
message = str(ex.__cause__)
ready_message = f"ready yet: {message}" if message else "ready yet" ready_message = f"ready yet: {message}" if message else "ready yet"
if tries == 1: if tries == 1:
logger.warning( logger.warning(

View File

@ -11,9 +11,10 @@ import urllib.error
import aiohttp import aiohttp
import requests import requests
from homeassistant import config_entries
from homeassistant.const import EVENT_HOMEASSISTANT_STOP from homeassistant.const import EVENT_HOMEASSISTANT_STOP
from homeassistant.core import CALLBACK_TYPE, Event, HassJob, HomeAssistant, callback from homeassistant.core import CALLBACK_TYPE, Event, HassJob, HomeAssistant, callback
from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from homeassistant.helpers import entity, event from homeassistant.helpers import entity, event
from homeassistant.util.dt import utcnow from homeassistant.util.dt import utcnow
@ -149,7 +150,7 @@ class DataUpdateCoordinator(Generic[T]):
fails. Additionally logging is handled by config entry setup fails. Additionally logging is handled by config entry setup
to ensure that multiple retries do not cause log spam. to ensure that multiple retries do not cause log spam.
""" """
await self._async_refresh(log_failures=False) await self._async_refresh(log_failures=False, raise_on_auth_failed=True)
if self.last_update_success: if self.last_update_success:
return return
ex = ConfigEntryNotReady() ex = ConfigEntryNotReady()
@ -160,7 +161,9 @@ class DataUpdateCoordinator(Generic[T]):
"""Refresh data and log errors.""" """Refresh data and log errors."""
await self._async_refresh(log_failures=True) await self._async_refresh(log_failures=True)
async def _async_refresh(self, log_failures: bool = True) -> None: async def _async_refresh(
self, log_failures: bool = True, raise_on_auth_failed: bool = False
) -> None:
"""Refresh data.""" """Refresh data."""
if self._unsub_refresh: if self._unsub_refresh:
self._unsub_refresh() self._unsub_refresh()
@ -168,6 +171,7 @@ class DataUpdateCoordinator(Generic[T]):
self._debounced_refresh.async_cancel() self._debounced_refresh.async_cancel()
start = monotonic() start = monotonic()
auth_failed = False
try: try:
self.data = await self._async_update_data() self.data = await self._async_update_data()
@ -205,6 +209,23 @@ class DataUpdateCoordinator(Generic[T]):
self.logger.error("Error fetching %s data: %s", self.name, err) self.logger.error("Error fetching %s data: %s", self.name, err)
self.last_update_success = False self.last_update_success = False
except ConfigEntryAuthFailed as err:
auth_failed = True
self.last_exception = err
if self.last_update_success:
if log_failures:
self.logger.error(
"Authentication failed while fetching %s data: %s",
self.name,
err,
)
self.last_update_success = False
if raise_on_auth_failed:
raise
config_entry = config_entries.current_entry.get()
if config_entry:
config_entry.async_start_reauth(self.hass)
except NotImplementedError as err: except NotImplementedError as err:
self.last_exception = err self.last_exception = err
raise err raise err
@ -228,7 +249,7 @@ class DataUpdateCoordinator(Generic[T]):
self.name, self.name,
monotonic() - start, monotonic() - start,
) )
if self._listeners: if not auth_failed and self._listeners:
self._schedule_refresh() self._schedule_refresh()
for update_callback in self._listeners: for update_callback in self._listeners:

View File

@ -1,8 +1,9 @@
"""Tests for the Abode module.""" """Tests for the Abode module."""
from unittest.mock import patch from unittest.mock import patch
from abodepy.exceptions import AbodeAuthenticationException from abodepy.exceptions import AbodeAuthenticationException, AbodeException
from homeassistant import data_entry_flow
from homeassistant.components.abode import ( from homeassistant.components.abode import (
DOMAIN as ABODE_DOMAIN, DOMAIN as ABODE_DOMAIN,
SERVICE_CAPTURE_IMAGE, SERVICE_CAPTURE_IMAGE,
@ -10,6 +11,7 @@ from homeassistant.components.abode import (
SERVICE_TRIGGER_AUTOMATION, SERVICE_TRIGGER_AUTOMATION,
) )
from homeassistant.components.alarm_control_panel import DOMAIN as ALARM_DOMAIN from homeassistant.components.alarm_control_panel import DOMAIN as ALARM_DOMAIN
from homeassistant.config_entries import ENTRY_STATE_SETUP_RETRY
from homeassistant.const import CONF_USERNAME, HTTP_BAD_REQUEST from homeassistant.const import CONF_USERNAME, HTTP_BAD_REQUEST
from .common import setup_platform from .common import setup_platform
@ -68,8 +70,23 @@ async def test_invalid_credentials(hass):
"homeassistant.components.abode.Abode", "homeassistant.components.abode.Abode",
side_effect=AbodeAuthenticationException((HTTP_BAD_REQUEST, "auth error")), side_effect=AbodeAuthenticationException((HTTP_BAD_REQUEST, "auth error")),
), patch( ), patch(
"homeassistant.components.abode.config_flow.AbodeFlowHandler.async_step_reauth" "homeassistant.components.abode.config_flow.AbodeFlowHandler.async_step_reauth",
return_value={"type": data_entry_flow.RESULT_TYPE_FORM},
) as mock_async_step_reauth: ) as mock_async_step_reauth:
await setup_platform(hass, ALARM_DOMAIN) await setup_platform(hass, ALARM_DOMAIN)
mock_async_step_reauth.assert_called_once() mock_async_step_reauth.assert_called_once()
async def test_raise_config_entry_not_ready_when_offline(hass):
"""Config entry state is ENTRY_STATE_SETUP_RETRY when abode is offline."""
with patch(
"homeassistant.components.abode.Abode",
side_effect=AbodeException("any"),
):
config_entry = await setup_platform(hass, ALARM_DOMAIN)
await hass.async_block_till_done()
assert config_entry.state == ENTRY_STATE_SETUP_RETRY
assert hass.config_entries.flow.async_progress() == []

View File

@ -271,6 +271,32 @@ async def test_requires_validation_state(hass):
assert hass.config_entries.flow.async_progress()[0]["context"]["source"] == "reauth" assert hass.config_entries.flow.async_progress()[0]["context"]["source"] == "reauth"
async def test_unknown_auth_http_401(hass):
"""Config entry state is ENTRY_STATE_SETUP_ERROR when august gets an http."""
config_entry = MockConfigEntry(
domain=DOMAIN,
data=_mock_get_config()[DOMAIN],
title="August august",
)
config_entry.add_to_hass(hass)
assert hass.config_entries.flow.async_progress() == []
await setup.async_setup_component(hass, "persistent_notification", {})
with patch(
"yalexs.authenticator_async.AuthenticatorAsync.async_authenticate",
return_value=_mock_august_authentication("original_token", 1234, None),
):
await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
assert config_entry.state == ENTRY_STATE_SETUP_ERROR
flows = hass.config_entries.flow.async_progress()
assert flows[0]["step_id"] == "reauth_validate"
async def test_load_unload(hass): async def test_load_unload(hass):
"""Config entry can be unloaded.""" """Config entry can be unloaded."""

View File

@ -107,3 +107,42 @@ async def test_step_user(hass):
} }
assert len(mock_setup_entry.mock_calls) == 1 assert len(mock_setup_entry.mock_calls) == 1
async def test_reauth(hass):
"""Test the start of the config flow."""
entry = MockConfigEntry(
domain=DOMAIN, data=MOCK_CONF, unique_id=MOCK_CONF[CONF_USERNAME]
)
entry.add_to_hass(hass)
with patch(
"homeassistant.components.fireservicerota.config_flow.FireServiceRota"
) as mock_fsr:
mock_fireservicerota = mock_fsr.return_value
mock_fireservicerota.request_tokens.return_value = MOCK_TOKEN_INFO
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": "reauth", "unique_id": entry.unique_id},
data=MOCK_CONF,
)
await hass.async_block_till_done()
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
with patch(
"homeassistant.components.fireservicerota.config_flow.FireServiceRota"
) as mock_fsr, patch(
"homeassistant.components.fireservicerota.async_setup_entry",
return_value=True,
):
mock_fireservicerota = mock_fsr.return_value
mock_fireservicerota.request_tokens.return_value = MOCK_TOKEN_INFO
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{CONF_PASSWORD: "any"},
)
await hass.async_block_till_done()
assert result2["type"] == data_entry_flow.RESULT_TYPE_ABORT
assert result2["reason"] == "reauth_successful"

View File

@ -103,7 +103,9 @@ async def test_reauth_success(hass: HomeAssistantType, fritz: Mock):
mock_config.add_to_hass(hass) mock_config.add_to_hass(hass)
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_REAUTH}, data=mock_config DOMAIN,
context={"source": SOURCE_REAUTH, "entry_id": mock_config.entry_id},
data=mock_config.data,
) )
assert result["type"] == RESULT_TYPE_FORM assert result["type"] == RESULT_TYPE_FORM
assert result["step_id"] == "reauth_confirm" assert result["step_id"] == "reauth_confirm"
@ -130,7 +132,9 @@ async def test_reauth_auth_failed(hass: HomeAssistantType, fritz: Mock):
mock_config.add_to_hass(hass) mock_config.add_to_hass(hass)
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_REAUTH}, data=mock_config DOMAIN,
context={"source": SOURCE_REAUTH, "entry_id": mock_config.entry_id},
data=mock_config.data,
) )
assert result["type"] == RESULT_TYPE_FORM assert result["type"] == RESULT_TYPE_FORM
assert result["step_id"] == "reauth_confirm" assert result["step_id"] == "reauth_confirm"
@ -156,7 +160,9 @@ async def test_reauth_not_successful(hass: HomeAssistantType, fritz: Mock):
mock_config.add_to_hass(hass) mock_config.add_to_hass(hass)
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_REAUTH}, data=mock_config DOMAIN,
context={"source": SOURCE_REAUTH, "entry_id": mock_config.entry_id},
data=mock_config.data,
) )
assert result["type"] == RESULT_TYPE_FORM assert result["type"] == RESULT_TYPE_FORM
assert result["step_id"] == "reauth_confirm" assert result["step_id"] == "reauth_confirm"

View File

@ -1,9 +1,15 @@
"""Tests for the AVM Fritz!Box integration.""" """Tests for the AVM Fritz!Box integration."""
from unittest.mock import Mock, call from unittest.mock import Mock, call, patch
from pyfritzhome import LoginError
from homeassistant.components.fritzbox.const import DOMAIN as FB_DOMAIN from homeassistant.components.fritzbox.const import DOMAIN as FB_DOMAIN
from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
from homeassistant.config_entries import ENTRY_STATE_LOADED, ENTRY_STATE_NOT_LOADED from homeassistant.config_entries import (
ENTRY_STATE_LOADED,
ENTRY_STATE_NOT_LOADED,
ENTRY_STATE_SETUP_ERROR,
)
from homeassistant.const import ( from homeassistant.const import (
CONF_DEVICES, CONF_DEVICES,
CONF_HOST, CONF_HOST,
@ -88,3 +94,23 @@ async def test_unload_remove(hass: HomeAssistantType, fritz: Mock):
assert entry.state == ENTRY_STATE_NOT_LOADED assert entry.state == ENTRY_STATE_NOT_LOADED
state = hass.states.get(entity_id) state = hass.states.get(entity_id)
assert state is None assert state is None
async def test_raise_config_entry_not_ready_when_offline(hass):
"""Config entry state is ENTRY_STATE_SETUP_RETRY when fritzbox is offline."""
entry = MockConfigEntry(
domain=FB_DOMAIN,
data={CONF_HOST: "any", **MOCK_CONFIG[FB_DOMAIN][CONF_DEVICES][0]},
unique_id="any",
)
entry.add_to_hass(hass)
with patch(
"homeassistant.components.fritzbox.Fritzhome.login",
side_effect=LoginError("user"),
):
await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
entries = hass.config_entries.async_entries()
config_entry = entries[0]
assert config_entry.state == ENTRY_STATE_SETUP_ERROR

View File

@ -761,7 +761,11 @@ async def test_setup_entry_no_token_reauth(hass: HomeAssistantType) -> None:
assert client.async_client_disconnect.called assert client.async_client_disconnect.called
mock_flow_init.assert_called_once_with( mock_flow_init.assert_called_once_with(
DOMAIN, DOMAIN,
context={CONF_SOURCE: SOURCE_REAUTH}, context={
CONF_SOURCE: SOURCE_REAUTH,
"entry_id": config_entry.entry_id,
"unique_id": config_entry.unique_id,
},
data=config_entry.data, data=config_entry.data,
) )
assert config_entry.state == ENTRY_STATE_SETUP_ERROR assert config_entry.state == ENTRY_STATE_SETUP_ERROR
@ -785,7 +789,11 @@ async def test_setup_entry_bad_token_reauth(hass: HomeAssistantType) -> None:
assert client.async_client_disconnect.called assert client.async_client_disconnect.called
mock_flow_init.assert_called_once_with( mock_flow_init.assert_called_once_with(
DOMAIN, DOMAIN,
context={CONF_SOURCE: SOURCE_REAUTH}, context={
CONF_SOURCE: SOURCE_REAUTH,
"entry_id": config_entry.entry_id,
"unique_id": config_entry.unique_id,
},
data=config_entry.data, data=config_entry.data,
) )
assert config_entry.state == ENTRY_STATE_SETUP_ERROR assert config_entry.state == ENTRY_STATE_SETUP_ERROR

View File

@ -101,13 +101,15 @@ async def test_full_reauth_flow_implementation(
hass: HomeAssistantType, aioclient_mock: AiohttpClientMocker hass: HomeAssistantType, aioclient_mock: AiohttpClientMocker
) -> None: ) -> None:
"""Test the manual reauth flow from start to finish.""" """Test the manual reauth flow from start to finish."""
entry = await setup_integration(hass, aioclient_mock, skip_entry_setup=True) entry = await setup_integration(
hass, aioclient_mock, skip_entry_setup=True, unique_id="any"
)
assert entry assert entry
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, DOMAIN,
context={CONF_SOURCE: SOURCE_REAUTH}, context={CONF_SOURCE: SOURCE_REAUTH, "unique_id": entry.unique_id},
data={"config_entry_id": entry.entry_id, **entry.data}, data=entry.data,
) )
assert result["type"] == RESULT_TYPE_FORM assert result["type"] == RESULT_TYPE_FORM

View File

@ -35,8 +35,12 @@ async def test_config_entry_reauth(
mock_flow_init.assert_called_once_with( mock_flow_init.assert_called_once_with(
DOMAIN, DOMAIN,
context={CONF_SOURCE: SOURCE_REAUTH}, context={
data={"config_entry_id": entry.entry_id, **entry.data}, CONF_SOURCE: SOURCE_REAUTH,
"entry_id": entry.entry_id,
"unique_id": entry.unique_id,
},
data=entry.data,
) )

View File

@ -380,8 +380,12 @@ async def test_reauth_flow_update_configuration(hass, aioclient_mock):
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
UNIFI_DOMAIN, UNIFI_DOMAIN,
context={"source": SOURCE_REAUTH}, context={
data=config_entry, "source": SOURCE_REAUTH,
"unique_id": config_entry.unique_id,
"entry_id": config_entry.entry_id,
},
data=config_entry.data,
) )
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["type"] == data_entry_flow.RESULT_TYPE_FORM

View File

@ -204,7 +204,13 @@ async def test_reauth_flow(hass: HomeAssistant) -> None:
entry.add_to_hass(hass) entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_REAUTH}, data={"entry": entry} DOMAIN,
context={
"source": config_entries.SOURCE_REAUTH,
"unique_id": entry.unique_id,
"entry_id": entry.entry_id,
},
data=entry.data,
) )
assert result["step_id"] == "reauth_confirm" assert result["step_id"] == "reauth_confirm"
assert result["type"] == RESULT_TYPE_FORM assert result["type"] == RESULT_TYPE_FORM
@ -255,7 +261,13 @@ async def test_reauth_flow_invalid_login(hass: HomeAssistant) -> None:
entry.add_to_hass(hass) entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_REAUTH}, data={"entry": entry} DOMAIN,
context={
"source": config_entries.SOURCE_REAUTH,
"unique_id": entry.unique_id,
"entry_id": entry.entry_id,
},
data=entry.data,
) )
with patch( with patch(
@ -290,7 +302,13 @@ async def test_reauth_flow_unknown_error(hass: HomeAssistant) -> None:
entry.add_to_hass(hass) entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_REAUTH}, data={"entry": entry} DOMAIN,
context={
"source": config_entries.SOURCE_REAUTH,
"unique_id": entry.unique_id,
"entry_id": entry.entry_id,
},
data=entry.data,
) )
with patch( with patch(

View File

@ -1,16 +1,21 @@
"""Test the config manager.""" """Test the config manager."""
import asyncio import asyncio
from datetime import timedelta from datetime import timedelta
import logging
from unittest.mock import AsyncMock, Mock, patch from unittest.mock import AsyncMock, Mock, patch
import pytest import pytest
from homeassistant import config_entries, data_entry_flow, loader from homeassistant import config_entries, data_entry_flow, loader
from homeassistant.const import EVENT_HOMEASSISTANT_CLOSE, EVENT_HOMEASSISTANT_STARTED from homeassistant.const import EVENT_HOMEASSISTANT_STARTED
from homeassistant.core import CoreState, callback from homeassistant.core import CoreState, callback
from homeassistant.exceptions import ConfigEntryNotReady, HomeAssistantError from homeassistant.exceptions import (
ConfigEntryAuthFailed,
ConfigEntryNotReady,
HomeAssistantError,
)
from homeassistant.helpers import entity_registry as er from homeassistant.helpers import entity_registry as er
from homeassistant.helpers.aiohttp_client import async_create_clientsession from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from homeassistant.setup import async_setup_component from homeassistant.setup import async_setup_component
from homeassistant.util import dt from homeassistant.util import dt
@ -36,6 +41,10 @@ def mock_handlers():
VERSION = 1 VERSION = 1
async def async_step_reauth(self, data):
"""Mock Reauth."""
return self.async_show_form(step_id="reauth")
with patch.dict( with patch.dict(
config_entries.HANDLERS, {"comp": MockFlowHandler, "test": MockFlowHandler} config_entries.HANDLERS, {"comp": MockFlowHandler, "test": MockFlowHandler}
): ):
@ -2531,56 +2540,130 @@ async def test_entry_reload_calls_on_unload_listeners(hass, manager):
assert entry.state == config_entries.ENTRY_STATE_LOADED assert entry.state == config_entries.ENTRY_STATE_LOADED
async def test_entry_reload_cleans_up_aiohttp_session(hass, manager): async def test_setup_raise_auth_failed(hass, caplog):
"""Test reload cleans up aiohttp sessions their close listener created by the config entry.""" """Test a setup raising ConfigEntryAuthFailed."""
entry = MockConfigEntry(domain="comp", state=config_entries.ENTRY_STATE_LOADED) entry = MockConfigEntry(title="test_title", domain="test")
entry.add_to_hass(hass)
async_setup_calls = 0
async def async_setup_entry(hass, _): mock_setup_entry = AsyncMock(
"""Mock setup entry.""" side_effect=ConfigEntryAuthFailed("The password is no longer valid")
nonlocal async_setup_calls )
async_setup_calls += 1 mock_integration(hass, MockModule("test", async_setup_entry=mock_setup_entry))
async_create_clientsession(hass) mock_entity_platform(hass, "config_flow.test", None)
await entry.async_setup(hass)
await hass.async_block_till_done()
assert "could not authenticate: The password is no longer valid" in caplog.text
assert entry.state == config_entries.ENTRY_STATE_SETUP_ERROR
flows = hass.config_entries.flow.async_progress()
assert len(flows) == 1
assert flows[0]["context"]["entry_id"] == entry.entry_id
assert flows[0]["context"]["source"] == config_entries.SOURCE_REAUTH
caplog.clear()
entry.state = config_entries.ENTRY_STATE_NOT_LOADED
await entry.async_setup(hass)
await hass.async_block_till_done()
assert "could not authenticate: The password is no longer valid" in caplog.text
# Verify multiple ConfigEntryAuthFailed does not generate a second flow
assert entry.state == config_entries.ENTRY_STATE_SETUP_ERROR
flows = hass.config_entries.flow.async_progress()
assert len(flows) == 1
async def test_setup_raise_auth_failed_from_first_coordinator_update(hass, caplog):
"""Test async_config_entry_first_refresh raises ConfigEntryAuthFailed."""
entry = MockConfigEntry(title="test_title", domain="test")
async def async_setup_entry(hass, entry):
"""Mock setup entry with a simple coordinator."""
async def _async_update_data():
raise ConfigEntryAuthFailed("The password is no longer valid")
coordinator = DataUpdateCoordinator(
hass,
logging.getLogger(__name__),
name="any",
update_method=_async_update_data,
update_interval=timedelta(seconds=1000),
)
await coordinator.async_config_entry_first_refresh()
return True return True
async_setup = AsyncMock(return_value=True) mock_integration(hass, MockModule("test", async_setup_entry=async_setup_entry))
async_unload_entry = AsyncMock(return_value=True) mock_entity_platform(hass, "config_flow.test", None)
mock_integration( await entry.async_setup(hass)
hass, await hass.async_block_till_done()
MockModule( assert "could not authenticate: The password is no longer valid" in caplog.text
"comp",
async_setup=async_setup, assert entry.state == config_entries.ENTRY_STATE_SETUP_ERROR
async_setup_entry=async_setup_entry, flows = hass.config_entries.flow.async_progress()
async_unload_entry=async_unload_entry, assert len(flows) == 1
), assert flows[0]["context"]["entry_id"] == entry.entry_id
) assert flows[0]["context"]["source"] == config_entries.SOURCE_REAUTH
mock_entity_platform(hass, "config_flow.comp", None)
caplog.clear()
entry.state = config_entries.ENTRY_STATE_NOT_LOADED
await entry.async_setup(hass)
await hass.async_block_till_done()
assert "could not authenticate: The password is no longer valid" in caplog.text
# Verify multiple ConfigEntryAuthFailed does not generate a second flow
assert entry.state == config_entries.ENTRY_STATE_SETUP_ERROR
flows = hass.config_entries.flow.async_progress()
assert len(flows) == 1
async def test_setup_raise_auth_failed_from_future_coordinator_update(hass, caplog):
"""Test a coordinator raises ConfigEntryAuthFailed in the future."""
entry = MockConfigEntry(title="test_title", domain="test")
async def async_setup_entry(hass, entry):
"""Mock setup entry with a simple coordinator."""
async def _async_update_data():
raise ConfigEntryAuthFailed("The password is no longer valid")
coordinator = DataUpdateCoordinator(
hass,
logging.getLogger(__name__),
name="any",
update_method=_async_update_data,
update_interval=timedelta(seconds=1000),
)
await coordinator.async_refresh()
return True
mock_integration(hass, MockModule("test", async_setup_entry=async_setup_entry))
mock_entity_platform(hass, "config_flow.test", None)
await entry.async_setup(hass)
await hass.async_block_till_done()
assert "Authentication failed while fetching" in caplog.text
assert "The password is no longer valid" in caplog.text
assert await manager.async_reload(entry.entry_id)
assert len(async_unload_entry.mock_calls) == 1
assert async_setup_calls == 1
assert entry.state == config_entries.ENTRY_STATE_LOADED assert entry.state == config_entries.ENTRY_STATE_LOADED
flows = hass.config_entries.flow.async_progress()
assert len(flows) == 1
assert flows[0]["context"]["entry_id"] == entry.entry_id
assert flows[0]["context"]["source"] == config_entries.SOURCE_REAUTH
original_close_listeners = hass.bus.async_listeners()[EVENT_HOMEASSISTANT_CLOSE] caplog.clear()
entry.state = config_entries.ENTRY_STATE_NOT_LOADED
assert await manager.async_reload(entry.entry_id) await entry.async_setup(hass)
assert len(async_unload_entry.mock_calls) == 2 await hass.async_block_till_done()
assert async_setup_calls == 2 assert "Authentication failed while fetching" in caplog.text
assert "The password is no longer valid" in caplog.text
# Verify multiple ConfigEntryAuthFailed does not generate a second flow
assert entry.state == config_entries.ENTRY_STATE_LOADED assert entry.state == config_entries.ENTRY_STATE_LOADED
flows = hass.config_entries.flow.async_progress()
assert ( assert len(flows) == 1
hass.bus.async_listeners()[EVENT_HOMEASSISTANT_CLOSE]
== original_close_listeners
)
assert await manager.async_reload(entry.entry_id)
assert len(async_unload_entry.mock_calls) == 3
assert async_setup_calls == 3
assert entry.state == config_entries.ENTRY_STATE_LOADED
assert (
hass.bus.async_listeners()[EVENT_HOMEASSISTANT_CLOSE]
== original_close_listeners
)