Skip to content

Commit

Permalink
Auto recover mqtt config entry secret if Mosquitto add-on was re-inst…
Browse files Browse the repository at this point in the history
…alled (home-assistant#124514)

Auto recover mqtt config entry secret if Mosquitto add-on is re-installed
  • Loading branch information
jbouwh authored Aug 26, 2024
1 parent 76ebb0d commit 65216df
Show file tree
Hide file tree
Showing 2 changed files with 132 additions and 0 deletions.
30 changes: 30 additions & 0 deletions homeassistant/components/mqtt/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,36 @@ async def async_step_reauth(
) -> ConfigFlowResult:
"""Handle re-authentication with MQTT broker."""
self.entry = self.hass.config_entries.async_get_entry(self.context["entry_id"])
if is_hassio(self.hass):
# Check if entry setup matches the add-on discovery config
addon_manager = get_addon_manager(self.hass)
try:
addon_discovery_config = (
await addon_manager.async_get_addon_discovery_info()
)
except AddonError:
# Follow manual flow if we have an error
pass
else:
# Check if the addon secrets need to be renewed.
# This will repair the config entry,
# in case the official Mosquitto Broker addon was re-installed.
if (
entry_data[CONF_BROKER] == addon_discovery_config[CONF_HOST]
and entry_data[CONF_PORT] == addon_discovery_config[CONF_PORT]
and entry_data.get(CONF_USERNAME)
== (username := addon_discovery_config.get(CONF_USERNAME))
and entry_data.get(CONF_PASSWORD)
!= (password := addon_discovery_config.get(CONF_PASSWORD))
):
_LOGGER.info(
"Executing autorecovery %s add-on secrets",
addon_manager.addon_name,
)
return await self.async_step_reauth_confirm(
user_input={CONF_USERNAME: username, CONF_PASSWORD: password}
)

return await self.async_step_reauth_confirm()

async def async_step_reauth_confirm(
Expand Down
102 changes: 102 additions & 0 deletions tests/components/mqtt/test_config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -1587,6 +1587,108 @@ async def test_step_reauth(
await hass.async_block_till_done()


@pytest.mark.parametrize("discovery_info", [{"config": ADD_ON_DISCOVERY_INFO.copy()}])
@pytest.mark.usefixtures(
"mqtt_client_mock", "mock_reload_after_entry_update", "supervisor", "addon_running"
)
async def test_step_hassio_reauth(
hass: HomeAssistant, mock_try_connection: MagicMock, addon_info: AsyncMock
) -> None:
"""Test that the reauth step works in case the Mosquitto broker add-on was re-installed."""

# Set up entry data based on the discovery data, but with a stale password
entry_data = {
mqtt.CONF_BROKER: "core-mosquitto",
CONF_PORT: 1883,
CONF_USERNAME: "mock-user",
CONF_PASSWORD: "stale-secret",
}

addon_info["hostname"] = "core-mosquitto"

# Prepare the config entry
config_entry = MockConfigEntry(domain=mqtt.DOMAIN, data=entry_data)
config_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(config_entry.entry_id)

assert config_entry.data.get(CONF_PASSWORD) == "stale-secret"

# Start reauth flow
mock_try_connection.reset_mock()
mock_try_connection.return_value = True
config_entry.async_start_reauth(hass)
await hass.async_block_till_done()
flows = hass.config_entries.flow.async_progress()
assert len(flows) == 0

# Assert the entry is updated automatically
assert config_entry.data.get(CONF_PASSWORD) == "mock-pass"
mock_try_connection.assert_called_once_with(
{
"broker": "core-mosquitto",
"port": 1883,
"username": "mock-user",
"password": "mock-pass",
}
)


@pytest.mark.parametrize(
("discovery_info", "discovery_info_side_effect", "broker"),
[
({"config": ADD_ON_DISCOVERY_INFO.copy()}, AddonError, "core-mosquitto"),
({"config": ADD_ON_DISCOVERY_INFO.copy()}, None, "broker-not-addon"),
],
)
@pytest.mark.usefixtures(
"mqtt_client_mock", "mock_reload_after_entry_update", "supervisor", "addon_running"
)
async def test_step_hassio_reauth_no_discovery_info(
hass: HomeAssistant,
mock_try_connection: MagicMock,
addon_info: AsyncMock,
broker: str,
) -> None:
"""Test hassio reauth flow defaults to manual flow.
Test that the reauth step defaults to
normal reauth flow if fetching add-on discovery info failed,
or the broker is not the add-on.
"""

# Set up entry data based on the discovery data, but with a stale password
entry_data = {
mqtt.CONF_BROKER: broker,
CONF_PORT: 1883,
CONF_USERNAME: "mock-user",
CONF_PASSWORD: "wrong-pass",
}

addon_info["hostname"] = "core-mosquitto"

# Prepare the config entry
config_entry = MockConfigEntry(domain=mqtt.DOMAIN, data=entry_data)
config_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(config_entry.entry_id)

assert config_entry.data.get(CONF_PASSWORD) == "wrong-pass"

# Start reauth flow
mock_try_connection.reset_mock()
mock_try_connection.return_value = True
config_entry.async_start_reauth(hass)
await hass.async_block_till_done()
flows = hass.config_entries.flow.async_progress()
assert len(flows) == 1
result = flows[0]
assert result["step_id"] == "reauth_confirm"
assert result["context"]["source"] == "reauth"

# Assert the entry is not updated
assert config_entry.data.get(CONF_PASSWORD) == "wrong-pass"
mock_try_connection.assert_not_called()


async def test_options_user_connection_fails(
hass: HomeAssistant, mock_try_connection_time_out: MagicMock
) -> None:
Expand Down

0 comments on commit 65216df

Please sign in to comment.