Skip to content

Commit cf63175

Browse files
authored
Abort reauth flows on config entry reload (#140931)
* Abort reauth flows on config entry reload * Don't cancel reauth when reload is triggered by a reauth flow * Revert "Don't cancel reauth when reload is triggered by a reauth flow" This reverts commit f37c756. * Don't fail in FlowManager._async_handle_step when the flow was aborted * Update tplink config flow * Add tests * Don't allow create_entry from an aborted flow * Add comment * Adjust after merge with dev
1 parent 88428fc commit cf63175

File tree

5 files changed

+62
-21
lines changed

5 files changed

+62
-21
lines changed

homeassistant/components/tplink/config_flow.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -567,7 +567,7 @@ async def async_step_pick_device(
567567
)
568568

569569
async def _async_reload_requires_auth_entries(self) -> None:
570-
"""Reload any in progress config flow that now have credentials."""
570+
"""Reload all config entries after auth update."""
571571
_config_entries = self.hass.config_entries
572572

573573
if self.source == SOURCE_REAUTH:
@@ -579,11 +579,9 @@ async def _async_reload_requires_auth_entries(self) -> None:
579579
context = flow["context"]
580580
if context.get("source") != SOURCE_REAUTH:
581581
continue
582-
entry_id: str = context["entry_id"]
582+
entry_id = context["entry_id"]
583583
if entry := _config_entries.async_get_entry(entry_id):
584584
await _config_entries.async_reload(entry.entry_id)
585-
if entry.state is ConfigEntryState.LOADED:
586-
_config_entries.flow.async_abort(flow["flow_id"])
587585

588586
@callback
589587
def _async_create_or_update_entry_from_device(

homeassistant/config_entries.py

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1521,10 +1521,9 @@ def async_flow_removed(
15211521

15221522
# Clean up issue if this is a reauth flow
15231523
if flow.context["source"] == SOURCE_REAUTH:
1524-
if (entry_id := flow.context.get("entry_id")) is not None and (
1525-
entry := self.config_entries.async_get_entry(entry_id)
1526-
) is not None:
1527-
issue_id = f"config_entry_reauth_{entry.domain}_{entry.entry_id}"
1524+
if (entry_id := flow.context.get("entry_id")) is not None:
1525+
# The config entry's domain is flow.handler
1526+
issue_id = f"config_entry_reauth_{flow.handler}_{entry_id}"
15281527
ir.async_delete_issue(self.hass, HOMEASSISTANT_DOMAIN, issue_id)
15291528

15301529
async def async_finish_flow(
@@ -2128,13 +2127,7 @@ def _async_clean_up(self, entry: ConfigEntry) -> None:
21282127
# If the configuration entry is removed during reauth, it should
21292128
# abort any reauth flow that is active for the removed entry and
21302129
# linked issues.
2131-
for progress_flow in self.hass.config_entries.flow.async_progress_by_handler(
2132-
entry.domain, match_context={"entry_id": entry_id, "source": SOURCE_REAUTH}
2133-
):
2134-
if "flow_id" in progress_flow:
2135-
self.hass.config_entries.flow.async_abort(progress_flow["flow_id"])
2136-
issue_id = f"config_entry_reauth_{entry.domain}_{entry.entry_id}"
2137-
ir.async_delete_issue(self.hass, HOMEASSISTANT_DOMAIN, issue_id)
2130+
_abort_reauth_flows(self.hass, entry.domain, entry_id)
21382131

21392132
self._async_dispatch(ConfigEntryChange.REMOVED, entry)
21402133

@@ -2266,6 +2259,9 @@ async def async_reload(self, entry_id: str) -> bool:
22662259
# attempts.
22672260
entry.async_cancel_retry_setup()
22682261

2262+
# Abort any in-progress reauth flow and linked issues
2263+
_abort_reauth_flows(self.hass, entry.domain, entry_id)
2264+
22692265
if entry.domain not in self.hass.config.components:
22702266
# If the component is not loaded, just load it as
22712267
# the config entry will be loaded as well. We need
@@ -3786,3 +3782,13 @@ async def _async_get_flow_handler(
37863782
return handler
37873783

37883784
raise data_entry_flow.UnknownHandler
3785+
3786+
3787+
@callback
3788+
def _abort_reauth_flows(hass: HomeAssistant, domain: str, entry_id: str) -> None:
3789+
"""Abort reauth flows for an entry."""
3790+
for progress_flow in hass.config_entries.flow.async_progress_by_handler(
3791+
domain, match_context={"entry_id": entry_id, "source": SOURCE_REAUTH}
3792+
):
3793+
if "flow_id" in progress_flow:
3794+
hass.config_entries.flow.async_abort(progress_flow["flow_id"])

homeassistant/data_entry_flow.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -494,8 +494,11 @@ async def _async_handle_step(
494494
)
495495

496496
if flow.flow_id not in self._progress:
497-
# The flow was removed during the step
498-
raise UnknownFlow
497+
# The flow was removed during the step, raise UnknownFlow
498+
# unless the result is an abort
499+
if result["type"] != FlowResultType.ABORT:
500+
raise UnknownFlow
501+
return result
499502

500503
# Setup the flow handler's preview if needed
501504
if result.get("preview") is not None:
@@ -547,7 +550,7 @@ def schedule_configure(_: asyncio.Task) -> None:
547550
flow.cur_step = result
548551
return result
549552

550-
# Abort and Success results both finish the flow
553+
# Abort and Success results both finish the flow.
551554
self._async_remove_flow_progress(flow.flow_id)
552555

553556
return result

tests/test_config_entries.py

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -695,7 +695,7 @@ async def test_remove_entry_cancels_reauth(
695695
manager: config_entries.ConfigEntries,
696696
issue_registry: ir.IssueRegistry,
697697
) -> None:
698-
"""Tests that removing a config entry, also aborts existing reauth flows."""
698+
"""Tests that removing a config entry also aborts existing reauth flows."""
699699
entry = MockConfigEntry(title="test_title", domain="test")
700700

701701
mock_setup_entry = AsyncMock(side_effect=ConfigEntryAuthFailed())
@@ -722,6 +722,40 @@ async def test_remove_entry_cancels_reauth(
722722
assert not issue_registry.async_get_issue(HOMEASSISTANT_DOMAIN, issue_id)
723723

724724

725+
async def test_reload_entry_cancels_reauth(
726+
hass: HomeAssistant,
727+
manager: config_entries.ConfigEntries,
728+
issue_registry: ir.IssueRegistry,
729+
) -> None:
730+
"""Tests that reloading a config entry also aborts existing reauth flows."""
731+
entry = MockConfigEntry(title="test_title", domain="test")
732+
733+
mock_setup_entry = AsyncMock(side_effect=ConfigEntryAuthFailed())
734+
mock_integration(hass, MockModule("test", async_setup_entry=mock_setup_entry))
735+
mock_platform(hass, "test.config_flow", None)
736+
737+
entry.add_to_hass(hass)
738+
await manager.async_setup(entry.entry_id)
739+
await hass.async_block_till_done()
740+
741+
flows = hass.config_entries.flow.async_progress_by_handler("test")
742+
assert len(flows) == 1
743+
assert flows[0]["context"]["entry_id"] == entry.entry_id
744+
assert flows[0]["context"]["source"] == config_entries.SOURCE_REAUTH
745+
assert entry.state is config_entries.ConfigEntryState.SETUP_ERROR
746+
747+
issue_id = f"config_entry_reauth_test_{entry.entry_id}"
748+
assert issue_registry.async_get_issue(HOMEASSISTANT_DOMAIN, issue_id)
749+
750+
mock_setup_entry.return_value = True
751+
mock_setup_entry.side_effect = None
752+
await manager.async_reload(entry.entry_id)
753+
754+
flows = hass.config_entries.flow.async_progress_by_handler("test")
755+
assert len(flows) == 0
756+
assert not issue_registry.async_get_issue(HOMEASSISTANT_DOMAIN, issue_id)
757+
758+
725759
async def test_remove_entry_handles_callback_error(
726760
hass: HomeAssistant, manager: config_entries.ConfigEntries
727761
) -> None:

tests/test_data_entry_flow.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -219,8 +219,8 @@ async def async_step_init(self, user_input=None):
219219
manager.async_abort(self.flow_id)
220220
return self.async_abort(reason="blah")
221221

222-
with pytest.raises(data_entry_flow.UnknownFlow):
223-
await manager.async_init("test")
222+
form = await manager.async_init("test")
223+
assert form["reason"] == "blah"
224224
assert len(manager.async_progress()) == 0
225225
assert len(manager.mock_created_entries) == 0
226226

0 commit comments

Comments
 (0)