diff --git a/homeassistant/components/upnp/config_flow.py b/homeassistant/components/upnp/config_flow.py index 7fa37f589bf..7d4e768e855 100644 --- a/homeassistant/components/upnp/config_flow.py +++ b/homeassistant/components/upnp/config_flow.py @@ -63,6 +63,12 @@ async def _async_mac_address_from_discovery( return await async_get_mac_address_from_host(hass, host) +def _is_igd_device(discovery_info: ssdp.SsdpServiceInfo) -> bool: + """Test if discovery is a complete IGD device.""" + root_device_info = discovery_info.upnp + return root_device_info.get(ssdp.ATTR_UPNP_DEVICE_TYPE) in {ST_IGD_V1, ST_IGD_V2} + + class UpnpFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): """Handle a UPnP/IGD config flow.""" @@ -108,6 +114,7 @@ class UpnpFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): for discovery in discoveries if ( _is_complete_discovery(discovery) + and _is_igd_device(discovery) and discovery.ssdp_usn not in current_unique_ids ) ] @@ -144,6 +151,12 @@ class UpnpFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): LOGGER.debug("Incomplete discovery, ignoring") return self.async_abort(reason="incomplete_discovery") + # Ensure device is usable. Ideally we would use IgdDevice.is_profile_device, + # but that requires constructing the device completely. + if not _is_igd_device(discovery_info): + LOGGER.debug("Non IGD device, ignoring") + return self.async_abort(reason="non_igd_device") + # Ensure not already configuring/configured. unique_id = discovery_info.ssdp_usn await self.async_set_unique_id(unique_id) diff --git a/tests/components/upnp/test_config_flow.py b/tests/components/upnp/test_config_flow.py index 80847ec2737..66d84fe0862 100644 --- a/tests/components/upnp/test_config_flow.py +++ b/tests/components/upnp/test_config_flow.py @@ -14,6 +14,7 @@ from homeassistant.components.upnp.const import ( CONFIG_ENTRY_ST, CONFIG_ENTRY_UDN, DOMAIN, + ST_IGD_V1, ) from homeassistant.core import HomeAssistant @@ -75,6 +76,7 @@ async def test_flow_ssdp_incomplete_discovery(hass: HomeAssistant): ssdp_st=TEST_ST, ssdp_location=TEST_LOCATION, upnp={ + ssdp.ATTR_UPNP_DEVICE_TYPE: ST_IGD_V1, # ssdp.ATTR_UPNP_UDN: TEST_UDN, # Not provided. }, ), @@ -83,6 +85,27 @@ async def test_flow_ssdp_incomplete_discovery(hass: HomeAssistant): assert result["reason"] == "incomplete_discovery" +@pytest.mark.usefixtures("mock_get_source_ip") +async def test_flow_ssdp_non_igd_device(hass: HomeAssistant): + """Test config flow: incomplete discovery through ssdp.""" + # Discovered via step ssdp. + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_SSDP}, + data=ssdp.SsdpServiceInfo( + ssdp_usn=TEST_USN, + ssdp_st=TEST_ST, + ssdp_location=TEST_LOCATION, + upnp={ + ssdp.ATTR_UPNP_DEVICE_TYPE: "urn:schemas-upnp-org:device:WFADevice:1", # Non-IGD + ssdp.ATTR_UPNP_UDN: TEST_UDN, + }, + ), + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "non_igd_device" + + @pytest.mark.usefixtures( "ssdp_instant_discovery", "mock_setup_entry",