Remove SSDP discovery from Hue (#85506)

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
Co-authored-by: Franck Nijhof <git@frenck.dev>
This commit is contained in:
Marcel van der Veldt 2023-01-16 19:56:46 +01:00 committed by GitHub
parent 1afb4897a8
commit fe583b7c4a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 3 additions and 276 deletions

View file

@ -4,7 +4,6 @@ from __future__ import annotations
import asyncio import asyncio
import logging import logging
from typing import Any from typing import Any
from urllib.parse import urlparse
import aiohttp import aiohttp
from aiohue import LinkButtonNotPressed, create_app_key from aiohue import LinkButtonNotPressed, create_app_key
@ -15,7 +14,7 @@ import slugify as unicode_slug
import voluptuous as vol import voluptuous as vol
from homeassistant import config_entries from homeassistant import config_entries
from homeassistant.components import ssdp, zeroconf from homeassistant.components import zeroconf
from homeassistant.const import CONF_API_KEY, CONF_HOST from homeassistant.const import CONF_API_KEY, CONF_HOST
from homeassistant.core import callback from homeassistant.core import callback
from homeassistant.data_entry_flow import FlowResult from homeassistant.data_entry_flow import FlowResult
@ -201,53 +200,6 @@ class HueFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
}, },
) )
async def async_step_ssdp(self, discovery_info: ssdp.SsdpServiceInfo) -> FlowResult:
"""Handle a discovered Hue bridge.
This flow is triggered by the SSDP component. It will check if the
host is already configured and delegate to the import step if not.
"""
# Filter out non-Hue bridges #1
if (
discovery_info.upnp.get(ssdp.ATTR_UPNP_MANUFACTURER_URL)
not in HUE_MANUFACTURERURL
):
return self.async_abort(reason="not_hue_bridge")
# Filter out non-Hue bridges #2
if any(
name in discovery_info.upnp.get(ssdp.ATTR_UPNP_FRIENDLY_NAME, "")
for name in HUE_IGNORED_BRIDGE_NAMES
):
return self.async_abort(reason="not_hue_bridge")
if (
not discovery_info.ssdp_location
or ssdp.ATTR_UPNP_SERIAL not in discovery_info.upnp
):
return self.async_abort(reason="not_hue_bridge")
url = urlparse(discovery_info.ssdp_location)
if not url.hostname:
return self.async_abort(reason="not_hue_bridge")
# Ignore if host is IPv6
if is_ipv6_address(url.hostname):
return self.async_abort(reason="invalid_host")
# abort if we already have exactly this bridge id/host
# reload the integration if the host got updated
bridge_id = normalize_bridge_id(discovery_info.upnp[ssdp.ATTR_UPNP_SERIAL])
await self.async_set_unique_id(bridge_id)
self._abort_if_unique_id_configured(
updates={CONF_HOST: url.hostname}, reload_on_update=True
)
self.bridge = await self._get_bridge(
url.hostname, discovery_info.upnp[ssdp.ATTR_UPNP_SERIAL]
)
return await self.async_step_link()
async def async_step_zeroconf( async def async_step_zeroconf(
self, discovery_info: zeroconf.ZeroconfServiceInfo self, discovery_info: zeroconf.ZeroconfServiceInfo
) -> FlowResult: ) -> FlowResult:

View file

@ -4,20 +4,6 @@
"config_flow": true, "config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/hue", "documentation": "https://www.home-assistant.io/integrations/hue",
"requirements": ["aiohue==4.6.1"], "requirements": ["aiohue==4.6.1"],
"ssdp": [
{
"manufacturer": "Royal Philips Electronics",
"modelName": "Philips hue bridge 2012"
},
{
"manufacturer": "Royal Philips Electronics",
"modelName": "Philips hue bridge 2015"
},
{
"manufacturer": "Signify",
"modelName": "Philips hue bridge 2015"
}
],
"homekit": { "homekit": {
"models": ["BSB002"] "models": ["BSB002"]
}, },

View file

@ -155,20 +155,6 @@ SSDP = {
"manufacturer": "SOYEA TECHNOLOGY CO., LTD.", "manufacturer": "SOYEA TECHNOLOGY CO., LTD.",
}, },
], ],
"hue": [
{
"manufacturer": "Royal Philips Electronics",
"modelName": "Philips hue bridge 2012",
},
{
"manufacturer": "Royal Philips Electronics",
"modelName": "Philips hue bridge 2015",
},
{
"manufacturer": "Signify",
"modelName": "Philips hue bridge 2015",
},
],
"hyperion": [ "hyperion": [
{ {
"manufacturer": "Hyperion Open Source Ambient Lighting", "manufacturer": "Hyperion Open Source Ambient Lighting",

View file

@ -8,7 +8,7 @@ import pytest
import voluptuous as vol import voluptuous as vol
from homeassistant import config_entries from homeassistant import config_entries
from homeassistant.components import ssdp, zeroconf from homeassistant.components import zeroconf
from homeassistant.components.hue import config_flow, const from homeassistant.components.hue import config_flow, const
from homeassistant.components.hue.errors import CannotConnect from homeassistant.components.hue.errors import CannotConnect
from homeassistant.helpers import device_registry as dr from homeassistant.helpers import device_registry as dr
@ -327,176 +327,6 @@ async def test_flow_link_cannot_connect(hass):
assert result["reason"] == "cannot_connect" assert result["reason"] == "cannot_connect"
@pytest.mark.parametrize("mf_url", config_flow.HUE_MANUFACTURERURL)
async def test_bridge_ssdp(hass, mf_url, aioclient_mock):
"""Test a bridge being discovered."""
create_mock_api_discovery(aioclient_mock, [("0.0.0.0", "1234")])
result = await hass.config_entries.flow.async_init(
const.DOMAIN,
context={"source": config_entries.SOURCE_SSDP},
data=ssdp.SsdpServiceInfo(
ssdp_usn="mock_usn",
ssdp_st="mock_st",
ssdp_location="http://0.0.0.0/",
upnp={
ssdp.ATTR_UPNP_MANUFACTURER_URL: mf_url,
ssdp.ATTR_UPNP_SERIAL: "1234",
},
),
)
assert result["type"] == "form"
assert result["step_id"] == "link"
async def test_bridge_ssdp_discover_other_bridge(hass):
"""Test that discovery ignores other bridges."""
result = await hass.config_entries.flow.async_init(
const.DOMAIN,
context={"source": config_entries.SOURCE_SSDP},
data=ssdp.SsdpServiceInfo(
ssdp_usn="mock_usn",
ssdp_st="mock_st",
upnp={ssdp.ATTR_UPNP_MANUFACTURER_URL: "http://www.notphilips.com"},
),
)
assert result["type"] == "abort"
assert result["reason"] == "not_hue_bridge"
async def test_bridge_ssdp_emulated_hue(hass):
"""Test if discovery info is from an emulated hue instance."""
result = await hass.config_entries.flow.async_init(
const.DOMAIN,
context={"source": config_entries.SOURCE_SSDP},
data=ssdp.SsdpServiceInfo(
ssdp_usn="mock_usn",
ssdp_st="mock_st",
ssdp_location="http://0.0.0.0/",
upnp={
ssdp.ATTR_UPNP_FRIENDLY_NAME: "Home Assistant Bridge",
ssdp.ATTR_UPNP_MANUFACTURER_URL: config_flow.HUE_MANUFACTURERURL[0],
ssdp.ATTR_UPNP_SERIAL: "1234",
},
),
)
assert result["type"] == "abort"
assert result["reason"] == "not_hue_bridge"
async def test_bridge_ssdp_missing_location(hass):
"""Test if discovery info is missing a location attribute."""
result = await hass.config_entries.flow.async_init(
const.DOMAIN,
context={"source": config_entries.SOURCE_SSDP},
data=ssdp.SsdpServiceInfo(
ssdp_usn="mock_usn",
ssdp_st="mock_st",
upnp={
ssdp.ATTR_UPNP_MANUFACTURER_URL: config_flow.HUE_MANUFACTURERURL[0],
ssdp.ATTR_UPNP_SERIAL: "1234",
},
),
)
assert result["type"] == "abort"
assert result["reason"] == "not_hue_bridge"
async def test_bridge_ssdp_missing_serial(hass):
"""Test if discovery info is a serial attribute."""
result = await hass.config_entries.flow.async_init(
const.DOMAIN,
context={"source": config_entries.SOURCE_SSDP},
data=ssdp.SsdpServiceInfo(
ssdp_usn="mock_usn",
ssdp_st="mock_st",
ssdp_location="http://0.0.0.0/",
upnp={
ssdp.ATTR_UPNP_MANUFACTURER_URL: config_flow.HUE_MANUFACTURERURL[0],
},
),
)
assert result["type"] == "abort"
assert result["reason"] == "not_hue_bridge"
@pytest.mark.parametrize(
"location,reason",
(
("http:///", "not_hue_bridge"),
("http://[fd00::eeb5:faff:fe84:b17d]/description.xml", "invalid_host"),
),
)
async def test_bridge_ssdp_invalid_location(hass, location, reason):
"""Test if discovery info is a serial attribute."""
result = await hass.config_entries.flow.async_init(
const.DOMAIN,
context={"source": config_entries.SOURCE_SSDP},
data=ssdp.SsdpServiceInfo(
ssdp_usn="mock_usn",
ssdp_st="mock_st",
ssdp_location=location,
upnp={
ssdp.ATTR_UPNP_MANUFACTURER_URL: config_flow.HUE_MANUFACTURERURL[0],
ssdp.ATTR_UPNP_SERIAL: "1234",
},
),
)
assert result["type"] == "abort"
assert result["reason"] == reason
async def test_bridge_ssdp_espalexa(hass):
"""Test if discovery info is from an Espalexa based device."""
result = await hass.config_entries.flow.async_init(
const.DOMAIN,
context={"source": config_entries.SOURCE_SSDP},
data=ssdp.SsdpServiceInfo(
ssdp_usn="mock_usn",
ssdp_st="mock_st",
ssdp_location="http://0.0.0.0/",
upnp={
ssdp.ATTR_UPNP_FRIENDLY_NAME: "Espalexa (0.0.0.0)",
ssdp.ATTR_UPNP_MANUFACTURER_URL: config_flow.HUE_MANUFACTURERURL[0],
ssdp.ATTR_UPNP_SERIAL: "1234",
},
),
)
assert result["type"] == "abort"
assert result["reason"] == "not_hue_bridge"
async def test_bridge_ssdp_already_configured(hass, aioclient_mock):
"""Test if a discovered bridge has already been configured."""
create_mock_api_discovery(aioclient_mock, [("0.0.0.0", "1234")])
MockConfigEntry(
domain="hue", unique_id="1234", data={"host": "0.0.0.0"}
).add_to_hass(hass)
result = await hass.config_entries.flow.async_init(
const.DOMAIN,
context={"source": config_entries.SOURCE_SSDP},
data=ssdp.SsdpServiceInfo(
ssdp_usn="mock_usn",
ssdp_st="mock_st",
ssdp_location="http://0.0.0.0/",
upnp={
ssdp.ATTR_UPNP_MANUFACTURER_URL: config_flow.HUE_MANUFACTURERURL[0],
ssdp.ATTR_UPNP_SERIAL: "1234",
},
),
)
assert result["type"] == "abort"
assert result["reason"] == "already_configured"
async def test_import_with_no_config(hass, aioclient_mock): async def test_import_with_no_config(hass, aioclient_mock):
"""Test importing a host without an existing config file.""" """Test importing a host without an existing config file."""
create_mock_api_discovery(aioclient_mock, [("0.0.0.0", "1234")]) create_mock_api_discovery(aioclient_mock, [("0.0.0.0", "1234")])
@ -634,33 +464,6 @@ async def test_bridge_homekit_already_configured(hass, aioclient_mock):
assert result["reason"] == "already_configured" assert result["reason"] == "already_configured"
async def test_ssdp_discovery_update_configuration(hass, aioclient_mock):
"""Test if a discovered bridge is configured and updated with new host."""
create_mock_api_discovery(aioclient_mock, [("1.1.1.1", "aabbccddeeff")])
entry = MockConfigEntry(
domain="hue", unique_id="aabbccddeeff", data={"host": "0.0.0.0"}
)
entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init(
const.DOMAIN,
context={"source": config_entries.SOURCE_SSDP},
data=ssdp.SsdpServiceInfo(
ssdp_usn="mock_usn",
ssdp_st="mock_st",
ssdp_location="http://1.1.1.1/",
upnp={
ssdp.ATTR_UPNP_MANUFACTURER_URL: config_flow.HUE_MANUFACTURERURL[0],
ssdp.ATTR_UPNP_SERIAL: "aabbccddeeff",
},
),
)
assert result["type"] == "abort"
assert result["reason"] == "already_configured"
assert entry.data["host"] == "1.1.1.1"
async def test_options_flow_v1(hass): async def test_options_flow_v1(hass):
"""Test options config flow for a V1 bridge.""" """Test options config flow for a V1 bridge."""
entry = MockConfigEntry( entry = MockConfigEntry(
@ -772,7 +575,7 @@ async def test_bridge_zeroconf_already_exists(hass, aioclient_mock):
) )
entry = MockConfigEntry( entry = MockConfigEntry(
domain="hue", domain="hue",
source=config_entries.SOURCE_SSDP, source=config_entries.SOURCE_HOMEKIT,
data={"host": "0.0.0.0"}, data={"host": "0.0.0.0"},
unique_id="ecb5faabcabc", unique_id="ecb5faabcabc",
) )