diff --git a/homeassistant/components/ring/config_flow.py b/homeassistant/components/ring/config_flow.py index 8b933e8580db39..aa78164eb6d714 100644 --- a/homeassistant/components/ring/config_flow.py +++ b/homeassistant/components/ring/config_flow.py @@ -7,11 +7,13 @@ from ring_doorbell import Auth, AuthenticationError, Requires2FAError import voluptuous as vol +from homeassistant.components import dhcp from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_PASSWORD, CONF_TOKEN, CONF_USERNAME from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.aiohttp_client import async_get_clientsession +import homeassistant.helpers.device_registry as dr from . import get_auth_agent_id from .const import CONF_2FA, DOMAIN @@ -23,6 +25,8 @@ ) STEP_REAUTH_DATA_SCHEMA = vol.Schema({vol.Required(CONF_PASSWORD): str}) +UNKNOWN_RING_ACCOUNT = "unknown_ring_account" + async def validate_input(hass: HomeAssistant, data: dict[str, str]) -> dict[str, Any]: """Validate the user input allows us to connect.""" @@ -56,6 +60,25 @@ class RingConfigFlow(ConfigFlow, domain=DOMAIN): user_pass: dict[str, Any] = {} reauth_entry: ConfigEntry | None = None + async def async_step_dhcp( + self, discovery_info: dhcp.DhcpServiceInfo + ) -> ConfigFlowResult: + """Handle discovery via dhcp.""" + # Ring has a single config entry per cloud username rather than per device + # so we check whether that device is already configured. + # If the device is not configured there's either no ring config entry + # yet or the device is registered to a different account + await self.async_set_unique_id(UNKNOWN_RING_ACCOUNT) + self._abort_if_unique_id_configured() + if self.hass.config_entries.async_has_entries(DOMAIN): + device_registry = dr.async_get(self.hass) + if device_registry.async_get_device( + identifiers={(DOMAIN, discovery_info.macaddress)} + ): + return self.async_abort(reason="already_configured") + + return await self.async_step_user() + async def async_step_user( self, user_input: dict[str, Any] | None = None ) -> ConfigFlowResult: diff --git a/homeassistant/components/ring/manifest.json b/homeassistant/components/ring/manifest.json index 35a1fb84caa3b2..0d8add5a6321a4 100644 --- a/homeassistant/components/ring/manifest.json +++ b/homeassistant/components/ring/manifest.json @@ -8,6 +8,22 @@ { "hostname": "ring*", "macaddress": "0CAE7D*" + }, + { + "hostname": "ring*", + "macaddress": "2CAB33*" + }, + { + "hostname": "ring*", + "macaddress": "94E36D*" + }, + { + "hostname": "ring*", + "macaddress": "9C7613*" + }, + { + "hostname": "ring*", + "macaddress": "341513*" } ], "documentation": "https://www.home-assistant.io/integrations/ring", diff --git a/homeassistant/generated/dhcp.py b/homeassistant/generated/dhcp.py index 757c43c96a7838..f521f2937e991a 100644 --- a/homeassistant/generated/dhcp.py +++ b/homeassistant/generated/dhcp.py @@ -427,6 +427,26 @@ "hostname": "ring*", "macaddress": "0CAE7D*", }, + { + "domain": "ring", + "hostname": "ring*", + "macaddress": "2CAB33*", + }, + { + "domain": "ring", + "hostname": "ring*", + "macaddress": "94E36D*", + }, + { + "domain": "ring", + "hostname": "ring*", + "macaddress": "9C7613*", + }, + { + "domain": "ring", + "hostname": "ring*", + "macaddress": "341513*", + }, { "domain": "roomba", "hostname": "irobot-*", diff --git a/tests/components/ring/test_config_flow.py b/tests/components/ring/test_config_flow.py index d27c4878aea5ed..f947a968cf31e4 100644 --- a/tests/components/ring/test_config_flow.py +++ b/tests/components/ring/test_config_flow.py @@ -6,10 +6,12 @@ import ring_doorbell from homeassistant import config_entries +from homeassistant.components import dhcp from homeassistant.components.ring import DOMAIN from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.core import HomeAssistant from homeassistant.data_entry_flow import FlowResultType +from homeassistant.helpers import device_registry as dr from tests.common import MockConfigEntry @@ -242,3 +244,57 @@ async def test_account_configured( assert result2["type"] is FlowResultType.ABORT assert result2["reason"] == "already_configured" + + +async def test_dhcp_discovery( + hass: HomeAssistant, + mock_setup_entry: AsyncMock, + mock_ring_client: Mock, + device_registry: dr.DeviceRegistry, +) -> None: + """Test discovery by dhcp.""" + mac_address = "1234567890abcd" + hostname = "Ring-90abcd" + ip_address = "127.0.0.1" + username = "hello@home-assistant.io" + + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_DHCP}, + data=dhcp.DhcpServiceInfo( + ip=ip_address, macaddress=mac_address, hostname=hostname + ), + ) + assert result["type"] is FlowResultType.FORM + assert result["errors"] == {} + assert result["step_id"] == "user" + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + {"username": username, "password": "test-password"}, + ) + assert result["type"] is FlowResultType.CREATE_ENTRY + assert result["title"] == "hello@home-assistant.io" + assert result["data"] == { + "username": username, + "token": {"access_token": "mock-token"}, + } + + config_entry = hass.config_entries.async_entry_for_domain_unique_id( + DOMAIN, username + ) + assert config_entry + + # Create a device entry under the config entry just created + device_registry.async_get_or_create( + config_entry_id=config_entry.entry_id, + identifiers={(DOMAIN, mac_address)}, + ) + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_DHCP}, + data=dhcp.DhcpServiceInfo( + ip=ip_address, macaddress=mac_address, hostname=hostname + ), + ) + assert result["type"] is FlowResultType.ABORT + assert result["reason"] == "already_configured"