mirror of
https://github.com/home-assistant/core.git
synced 2025-07-22 20:57:21 +00:00
Make sure we can set up OAuth based integrations via discovery (#145144)
This commit is contained in:
parent
a2b02537a6
commit
e2a916ff9d
@ -11,6 +11,9 @@
|
|||||||
"reauth_confirm": {
|
"reauth_confirm": {
|
||||||
"title": "[%key:common::config_flow::title::reauth%]",
|
"title": "[%key:common::config_flow::title::reauth%]",
|
||||||
"description": "The Home Connect integration needs to re-authenticate your account"
|
"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": {
|
"abort": {
|
||||||
|
@ -7,6 +7,9 @@
|
|||||||
"reauth_confirm": {
|
"reauth_confirm": {
|
||||||
"title": "[%key:common::config_flow::title::reauth%]",
|
"title": "[%key:common::config_flow::title::reauth%]",
|
||||||
"description": "The Lyric integration needs to re-authenticate your account."
|
"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": {
|
"abort": {
|
||||||
|
@ -13,6 +13,9 @@
|
|||||||
"reauth_confirm": {
|
"reauth_confirm": {
|
||||||
"title": "[%key:common::config_flow::title::reauth%]",
|
"title": "[%key:common::config_flow::title::reauth%]",
|
||||||
"description": "The Miele integration needs to re-authenticate your account"
|
"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": {
|
"abort": {
|
||||||
|
@ -31,6 +31,7 @@ from homeassistant.helpers.selector import (
|
|||||||
SelectSelectorConfig,
|
SelectSelectorConfig,
|
||||||
SelectSelectorMode,
|
SelectSelectorMode,
|
||||||
)
|
)
|
||||||
|
from homeassistant.helpers.service_info.dhcp import DhcpServiceInfo
|
||||||
from homeassistant.util import get_random_string
|
from homeassistant.util import get_random_string
|
||||||
|
|
||||||
from . import api
|
from . import api
|
||||||
@ -440,3 +441,9 @@ class NestFlowHandler(
|
|||||||
if self._structure_config_title:
|
if self._structure_config_title:
|
||||||
title = self._structure_config_title
|
title = self._structure_config_title
|
||||||
return self.async_create_entry(title=title, data=self._data)
|
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()
|
||||||
|
@ -7,6 +7,9 @@
|
|||||||
"reauth_confirm": {
|
"reauth_confirm": {
|
||||||
"title": "[%key:common::config_flow::title::reauth%]",
|
"title": "[%key:common::config_flow::title::reauth%]",
|
||||||
"description": "The Spotify integration needs to re-authenticate with Spotify for account: {account}"
|
"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": {
|
"abort": {
|
||||||
|
@ -7,6 +7,9 @@
|
|||||||
"reauth_confirm": {
|
"reauth_confirm": {
|
||||||
"title": "[%key:common::config_flow::title::reauth%]",
|
"title": "[%key:common::config_flow::title::reauth%]",
|
||||||
"description": "The Withings integration needs to re-authenticate your account"
|
"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": {
|
"error": {
|
||||||
|
@ -22,6 +22,7 @@ import time
|
|||||||
from typing import Any, cast
|
from typing import Any, cast
|
||||||
|
|
||||||
from aiohttp import ClientError, ClientResponseError, client, web
|
from aiohttp import ClientError, ClientResponseError, client, web
|
||||||
|
from habluetooth import BluetoothServiceInfoBleak
|
||||||
import jwt
|
import jwt
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
from yarl import URL
|
from yarl import URL
|
||||||
@ -34,6 +35,9 @@ from homeassistant.util.hass_dict import HassKey
|
|||||||
from . import http
|
from . import http
|
||||||
from .aiohttp_client import async_get_clientsession
|
from .aiohttp_client import async_get_clientsession
|
||||||
from .network import NoURLAvailableError
|
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__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -493,6 +497,45 @@ class AbstractOAuth2FlowHandler(config_entries.ConfigFlow, metaclass=ABCMeta):
|
|||||||
"""Handle a flow start."""
|
"""Handle a flow start."""
|
||||||
return await self.async_step_pick_implementation(user_input)
|
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
|
@classmethod
|
||||||
def async_register_implementation(
|
def async_register_implementation(
|
||||||
cls, hass: HomeAssistant, local_impl: LocalOAuth2Implementation
|
cls, hass: HomeAssistant, local_impl: LocalOAuth2Implementation
|
||||||
|
@ -279,6 +279,15 @@ async def test_zeroconf_flow(
|
|||||||
result = await hass.config_entries.flow.async_init(
|
result = await hass.config_entries.flow.async_init(
|
||||||
DOMAIN, context={"source": config_entries.SOURCE_ZEROCONF}
|
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(
|
state = config_entry_oauth2_flow._encode_jwt(
|
||||||
hass,
|
hass,
|
||||||
{
|
{
|
||||||
@ -351,6 +360,15 @@ async def test_dhcp_flow(
|
|||||||
result = await hass.config_entries.flow.async_init(
|
result = await hass.config_entries.flow.async_init(
|
||||||
DOMAIN, context={"source": config_entries.SOURCE_DHCP}, data=dhcp_discovery
|
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(
|
state = config_entry_oauth2_flow._encode_jwt(
|
||||||
hass,
|
hass,
|
||||||
{
|
{
|
||||||
|
@ -225,6 +225,16 @@ async def test_zeroconf_flow(
|
|||||||
result = await hass.config_entries.flow.async_init(
|
result = await hass.config_entries.flow.async_init(
|
||||||
DOMAIN, context={"source": SOURCE_ZEROCONF}
|
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(
|
state = config_entry_oauth2_flow._encode_jwt(
|
||||||
hass,
|
hass,
|
||||||
{
|
{
|
||||||
|
@ -38,13 +38,6 @@ async def test_abort_if_no_configuration(hass: HomeAssistant) -> None:
|
|||||||
assert result["type"] is FlowResultType.ABORT
|
assert result["type"] is FlowResultType.ABORT
|
||||||
assert result["reason"] == "missing_credentials"
|
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:
|
async def test_zeroconf_abort_if_existing_entry(hass: HomeAssistant) -> None:
|
||||||
"""Check zeroconf flow aborts when an entry already exist."""
|
"""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["type"] is FlowResultType.ABORT
|
||||||
assert result["reason"] == "reauth_account_mismatch"
|
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"
|
||||||
|
@ -312,6 +312,15 @@ async def test_dhcp(
|
|||||||
result = await hass.config_entries.flow.async_init(
|
result = await hass.config_entries.flow.async_init(
|
||||||
DOMAIN, context={"source": SOURCE_DHCP}, data=service_info
|
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(
|
state = config_entry_oauth2_flow._encode_jwt(
|
||||||
hass,
|
hass,
|
||||||
{
|
{
|
||||||
|
@ -397,6 +397,14 @@ async def test_step_discovery(
|
|||||||
data=data_entry_flow.BaseServiceInfo(),
|
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["type"] == data_entry_flow.FlowResultType.FORM
|
||||||
assert result["step_id"] == "pick_implementation"
|
assert result["step_id"] == "pick_implementation"
|
||||||
|
|
||||||
@ -418,6 +426,11 @@ async def test_abort_discovered_multiple(
|
|||||||
data=data_entry_flow.BaseServiceInfo(),
|
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["type"] == data_entry_flow.FlowResultType.FORM
|
||||||
assert result["step_id"] == "pick_implementation"
|
assert result["step_id"] == "pick_implementation"
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user