From dd3965e4e2b2a2f18d03c09fda7e3336d8ce8531 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 11 May 2021 23:24:42 -0500 Subject: [PATCH] Ensure zeroconf does not generate config flows when matching attributes are missing (#50208) If macaddress, name, or manufacturer were missing from the discovery info, the matcher would accept instead of reject. --- homeassistant/components/zeroconf/__init__.py | 29 ++++++++--------- tests/components/zeroconf/test_init.py | 32 +++++++++++++++++++ 2 files changed, 45 insertions(+), 16 deletions(-) diff --git a/homeassistant/components/zeroconf/__init__.py b/homeassistant/components/zeroconf/__init__.py index bf717141f11..484d4404a66 100644 --- a/homeassistant/components/zeroconf/__init__.py +++ b/homeassistant/components/zeroconf/__init__.py @@ -348,32 +348,29 @@ async def _async_start_zeroconf_browser( # Not all homekit types are currently used for discovery # so not all service type exist in zeroconf_types - for entry in zeroconf_types.get(service_type, []): - if len(entry) > 1: - if ( - uppercase_mac is not None - and "macaddress" in entry - and not fnmatch.fnmatch(uppercase_mac, entry["macaddress"]) + for matcher in zeroconf_types.get(service_type, []): + if len(matcher) > 1: + if "macaddress" in matcher and ( + uppercase_mac is None + or not fnmatch.fnmatch(uppercase_mac, matcher["macaddress"]) ): continue - if ( - lowercase_name is not None - and "name" in entry - and not fnmatch.fnmatch(lowercase_name, entry["name"]) + if "name" in matcher and ( + lowercase_name is None + or not fnmatch.fnmatch(lowercase_name, matcher["name"]) ): continue - if ( - lowercase_manufacturer is not None - and "manufacturer" in entry - and not fnmatch.fnmatch( - lowercase_manufacturer, entry["manufacturer"] + if "manufacturer" in matcher and ( + lowercase_manufacturer is None + or not fnmatch.fnmatch( + lowercase_manufacturer, matcher["manufacturer"] ) ): continue hass.add_job( hass.config_entries.flow.async_init( - entry["domain"], context={"source": DOMAIN}, data=info + matcher["domain"], context={"source": DOMAIN}, data=info ) # type: ignore ) diff --git a/tests/components/zeroconf/test_init.py b/tests/components/zeroconf/test_init.py index 0ad15c947cf..809177b6089 100644 --- a/tests/components/zeroconf/test_init.py +++ b/tests/components/zeroconf/test_init.py @@ -346,6 +346,38 @@ async def test_zeroconf_match_manufacturer(hass, mock_zeroconf): assert mock_config_flow.mock_calls[0][1][0] == "samsungtv" +async def test_zeroconf_match_manufacturer_not_present(hass, mock_zeroconf): + """Test matchers reject when a property is missing.""" + + def http_only_service_update_mock(zeroconf, services, handlers): + """Call service update handler.""" + handlers[0]( + zeroconf, + "_airplay._tcp.local.", + "s1000._airplay._tcp.local.", + ServiceStateChange.Added, + ) + + with patch.dict( + zc_gen.ZEROCONF, + {"_airplay._tcp.local.": [{"domain": "samsungtv", "manufacturer": "samsung*"}]}, + clear=True, + ), patch.object( + hass.config_entries.flow, "async_init" + ) as mock_config_flow, patch.object( + zeroconf, "HaServiceBrowser", side_effect=http_only_service_update_mock + ) as mock_service_browser: + mock_zeroconf.get_service_info.side_effect = get_zeroconf_info_mock( + "aa:bb:cc:dd:ee:ff" + ) + assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}}) + hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) + await hass.async_block_till_done() + + assert len(mock_service_browser.mock_calls) == 1 + assert len(mock_config_flow.mock_calls) == 0 + + async def test_zeroconf_no_match(hass, mock_zeroconf): """Test configured options for a device are loaded via config entry."""