Make sure we can set up OAuth based integrations via discovery (#145144)

This commit is contained in:
Joost Lekkerkerker 2025-05-26 20:48:07 +02:00 committed by GitHub
parent a2b02537a6
commit e2a916ff9d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 130 additions and 7 deletions

View File

@ -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": {

View File

@ -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": {

View File

@ -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": {

View File

@ -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()

View File

@ -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": {

View File

@ -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": {

View File

@ -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

View File

@ -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,
{

View File

@ -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,
{

View File

@ -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"

View File

@ -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,
{

View File

@ -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"