diff --git a/homeassistant/components/home_connect/strings.json b/homeassistant/components/home_connect/strings.json index 3fc509e79f3..37ef37a2839 100644 --- a/homeassistant/components/home_connect/strings.json +++ b/homeassistant/components/home_connect/strings.json @@ -11,6 +11,9 @@ "reauth_confirm": { "title": "[%key:common::config_flow::title::reauth%]", "description": "The Home Connect integration needs to re-authenticate your account" + }, + "oauth_discovery": { + "description": "Home Assistant has found a Home Connect device on your network. Press **Submit** to continue setting up Home Connect." } }, "abort": { diff --git a/homeassistant/components/lyric/strings.json b/homeassistant/components/lyric/strings.json index bc48a791e70..41598dfbdd0 100644 --- a/homeassistant/components/lyric/strings.json +++ b/homeassistant/components/lyric/strings.json @@ -7,6 +7,9 @@ "reauth_confirm": { "title": "[%key:common::config_flow::title::reauth%]", "description": "The Lyric integration needs to re-authenticate your account." + }, + "oauth_discovery": { + "description": "Home Assistant has found a Honeywell Lyric device on your network. Press **Submit** to continue setting up Honeywell Lyric." } }, "abort": { diff --git a/homeassistant/components/miele/strings.json b/homeassistant/components/miele/strings.json index 55d1769daf8..6774d813e44 100644 --- a/homeassistant/components/miele/strings.json +++ b/homeassistant/components/miele/strings.json @@ -13,6 +13,9 @@ "reauth_confirm": { "title": "[%key:common::config_flow::title::reauth%]", "description": "The Miele integration needs to re-authenticate your account" + }, + "oauth_discovery": { + "description": "Home Assistant has found a Miele device on your network. Press **Submit** to continue setting up Miele." } }, "abort": { diff --git a/homeassistant/components/nest/config_flow.py b/homeassistant/components/nest/config_flow.py index 0b249db7a4b..6ed43066fe3 100644 --- a/homeassistant/components/nest/config_flow.py +++ b/homeassistant/components/nest/config_flow.py @@ -31,6 +31,7 @@ from homeassistant.helpers.selector import ( SelectSelectorConfig, SelectSelectorMode, ) +from homeassistant.helpers.service_info.dhcp import DhcpServiceInfo from homeassistant.util import get_random_string from . import api @@ -440,3 +441,9 @@ class NestFlowHandler( if self._structure_config_title: title = self._structure_config_title return self.async_create_entry(title=title, data=self._data) + + async def async_step_dhcp( + self, discovery_info: DhcpServiceInfo + ) -> ConfigFlowResult: + """Handle a flow initialized by discovery.""" + return await self.async_step_user() diff --git a/homeassistant/components/spotify/strings.json b/homeassistant/components/spotify/strings.json index 66d837c503f..303942803be 100644 --- a/homeassistant/components/spotify/strings.json +++ b/homeassistant/components/spotify/strings.json @@ -7,6 +7,9 @@ "reauth_confirm": { "title": "[%key:common::config_flow::title::reauth%]", "description": "The Spotify integration needs to re-authenticate with Spotify for account: {account}" + }, + "oauth_discovery": { + "description": "Home Assistant has found Spotify on your network. Press **Submit** to continue setting up Spotify." } }, "abort": { diff --git a/homeassistant/components/withings/strings.json b/homeassistant/components/withings/strings.json index 746fa244c8e..8eb4293c637 100644 --- a/homeassistant/components/withings/strings.json +++ b/homeassistant/components/withings/strings.json @@ -7,6 +7,9 @@ "reauth_confirm": { "title": "[%key:common::config_flow::title::reauth%]", "description": "The Withings integration needs to re-authenticate your account" + }, + "oauth_discovery": { + "description": "Home Assistant has found a Withings device on your network. Press **Submit** to continue setting up Withings." } }, "error": { diff --git a/homeassistant/helpers/config_entry_oauth2_flow.py b/homeassistant/helpers/config_entry_oauth2_flow.py index 1cff90031c2..1671e8e2cc2 100644 --- a/homeassistant/helpers/config_entry_oauth2_flow.py +++ b/homeassistant/helpers/config_entry_oauth2_flow.py @@ -22,6 +22,7 @@ import time from typing import Any, cast from aiohttp import ClientError, ClientResponseError, client, web +from habluetooth import BluetoothServiceInfoBleak import jwt import voluptuous as vol from yarl import URL @@ -34,6 +35,9 @@ from homeassistant.util.hass_dict import HassKey from . import http from .aiohttp_client import async_get_clientsession from .network import NoURLAvailableError +from .service_info.dhcp import DhcpServiceInfo +from .service_info.ssdp import SsdpServiceInfo +from .service_info.zeroconf import ZeroconfServiceInfo _LOGGER = logging.getLogger(__name__) @@ -493,6 +497,45 @@ class AbstractOAuth2FlowHandler(config_entries.ConfigFlow, metaclass=ABCMeta): """Handle a flow start.""" return await self.async_step_pick_implementation(user_input) + async def async_step_bluetooth( + self, discovery_info: BluetoothServiceInfoBleak + ) -> config_entries.ConfigFlowResult: + """Handle a flow initialized by Bluetooth discovery.""" + return await self.async_step_oauth_discovery() + + async def async_step_dhcp( + self, discovery_info: DhcpServiceInfo + ) -> config_entries.ConfigFlowResult: + """Handle a flow initialized by DHCP discovery.""" + return await self.async_step_oauth_discovery() + + async def async_step_homekit( + self, discovery_info: ZeroconfServiceInfo + ) -> config_entries.ConfigFlowResult: + """Handle a flow initialized by Homekit discovery.""" + return await self.async_step_oauth_discovery() + + async def async_step_ssdp( + self, discovery_info: SsdpServiceInfo + ) -> config_entries.ConfigFlowResult: + """Handle a flow initialized by SSDP discovery.""" + return await self.async_step_oauth_discovery() + + async def async_step_zeroconf( + self, discovery_info: ZeroconfServiceInfo + ) -> config_entries.ConfigFlowResult: + """Handle a flow initialized by Zeroconf discovery.""" + return await self.async_step_oauth_discovery() + + async def async_step_oauth_discovery( + self, user_input: dict[str, Any] | None = None + ) -> config_entries.ConfigFlowResult: + """Handle a flow initialized by a discovery method.""" + if user_input is not None: + return await self.async_step_user() + await self._async_handle_discovery_without_unique_id() + return self.async_show_form(step_id="oauth_discovery") + @classmethod def async_register_implementation( cls, hass: HomeAssistant, local_impl: LocalOAuth2Implementation diff --git a/tests/components/home_connect/test_config_flow.py b/tests/components/home_connect/test_config_flow.py index 3d239d63bd0..ad35f890528 100644 --- a/tests/components/home_connect/test_config_flow.py +++ b/tests/components/home_connect/test_config_flow.py @@ -279,6 +279,15 @@ async def test_zeroconf_flow( result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_ZEROCONF} ) + + assert result["type"] is FlowResultType.FORM + assert result["step_id"] == "oauth_discovery" + assert not result["errors"] + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + {}, + ) state = config_entry_oauth2_flow._encode_jwt( hass, { @@ -351,6 +360,15 @@ async def test_dhcp_flow( result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_DHCP}, data=dhcp_discovery ) + + assert result["type"] is FlowResultType.FORM + assert result["step_id"] == "oauth_discovery" + assert not result["errors"] + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + {}, + ) state = config_entry_oauth2_flow._encode_jwt( hass, { diff --git a/tests/components/miele/test_config_flow.py b/tests/components/miele/test_config_flow.py index 78478bc0e9d..bbe5844c1cd 100644 --- a/tests/components/miele/test_config_flow.py +++ b/tests/components/miele/test_config_flow.py @@ -225,6 +225,16 @@ async def test_zeroconf_flow( result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_ZEROCONF} ) + + assert result["type"] is FlowResultType.FORM + assert result["step_id"] == "oauth_discovery" + assert not result["errors"] + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + {}, + ) + state = config_entry_oauth2_flow._encode_jwt( hass, { diff --git a/tests/components/spotify/test_config_flow.py b/tests/components/spotify/test_config_flow.py index 24c0e1d41d9..0f48002e5db 100644 --- a/tests/components/spotify/test_config_flow.py +++ b/tests/components/spotify/test_config_flow.py @@ -38,13 +38,6 @@ async def test_abort_if_no_configuration(hass: HomeAssistant) -> None: assert result["type"] is FlowResultType.ABORT assert result["reason"] == "missing_credentials" - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": SOURCE_ZEROCONF}, data=BLANK_ZEROCONF_INFO - ) - - assert result["type"] is FlowResultType.ABORT - assert result["reason"] == "missing_credentials" - async def test_zeroconf_abort_if_existing_entry(hass: HomeAssistant) -> None: """Check zeroconf flow aborts when an entry already exist.""" @@ -265,3 +258,18 @@ async def test_reauth_account_mismatch( assert result["type"] is FlowResultType.ABORT assert result["reason"] == "reauth_account_mismatch" + + +async def test_zeroconf(hass: HomeAssistant) -> None: + """Check zeroconf flow aborts when an entry already exist.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_ZEROCONF}, data=BLANK_ZEROCONF_INFO + ) + + assert result["type"] is FlowResultType.FORM + assert result["step_id"] == "oauth_discovery" + + result = await hass.config_entries.flow.async_configure(result["flow_id"], {}) + + assert result["type"] is FlowResultType.ABORT + assert result["reason"] == "missing_credentials" diff --git a/tests/components/withings/test_config_flow.py b/tests/components/withings/test_config_flow.py index b61a54150e4..4c9e2bef0d6 100644 --- a/tests/components/withings/test_config_flow.py +++ b/tests/components/withings/test_config_flow.py @@ -312,6 +312,15 @@ async def test_dhcp( result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_DHCP}, data=service_info ) + + assert result["type"] is FlowResultType.FORM + assert result["step_id"] == "oauth_discovery" + assert not result["errors"] + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + {}, + ) state = config_entry_oauth2_flow._encode_jwt( hass, { diff --git a/tests/helpers/test_config_entry_oauth2_flow.py b/tests/helpers/test_config_entry_oauth2_flow.py index 5d16a9a62fd..f250f97cfd4 100644 --- a/tests/helpers/test_config_entry_oauth2_flow.py +++ b/tests/helpers/test_config_entry_oauth2_flow.py @@ -397,6 +397,14 @@ async def test_step_discovery( data=data_entry_flow.BaseServiceInfo(), ) + assert result["type"] == data_entry_flow.FlowResultType.FORM + assert result["step_id"] == "oauth_discovery" + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={}, + ) + assert result["type"] == data_entry_flow.FlowResultType.FORM assert result["step_id"] == "pick_implementation" @@ -418,6 +426,11 @@ async def test_abort_discovered_multiple( data=data_entry_flow.BaseServiceInfo(), ) + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={}, + ) + assert result["type"] == data_entry_flow.FlowResultType.FORM assert result["step_id"] == "pick_implementation"