Fix bsblan reauthentication (#149926)

This commit is contained in:
Willem-Jan van Rootselaar
2025-08-04 15:35:41 +02:00
committed by GitHub
parent b76f47cd9f
commit 88c9d5dbe3
2 changed files with 170 additions and 20 deletions

View File

@@ -211,16 +211,16 @@ class BSBLANFlowHandler(ConfigFlow, domain=DOMAIN):
),
)
# Use existing host and port, update auth credentials
self.host = existing_entry.data[CONF_HOST]
self.port = existing_entry.data[CONF_PORT]
self.passkey = user_input.get(CONF_PASSKEY) or existing_entry.data.get(
CONF_PASSKEY
)
self.username = user_input.get(CONF_USERNAME) or existing_entry.data.get(
CONF_USERNAME
)
self.password = user_input.get(CONF_PASSWORD)
# Combine existing data with the user's new input for validation.
# This correctly handles adding, changing, and clearing credentials.
config_data = existing_entry.data.copy()
config_data.update(user_input)
self.host = config_data[CONF_HOST]
self.port = config_data[CONF_PORT]
self.passkey = config_data.get(CONF_PASSKEY)
self.username = config_data.get(CONF_USERNAME)
self.password = config_data.get(CONF_PASSWORD)
try:
await self._get_bsblan_info(raise_on_progress=False, is_reauth=True)
@@ -267,17 +267,9 @@ class BSBLANFlowHandler(ConfigFlow, domain=DOMAIN):
errors={"base": "cannot_connect"},
)
# Update the config entry with new auth data
data_updates = {}
if self.passkey is not None:
data_updates[CONF_PASSKEY] = self.passkey
if self.username is not None:
data_updates[CONF_USERNAME] = self.username
if self.password is not None:
data_updates[CONF_PASSWORD] = self.password
# Update only the fields that were provided by the user
return self.async_update_reload_and_abort(
existing_entry, data_updates=data_updates, reason="reauth_successful"
existing_entry, data_updates=user_input, reason="reauth_successful"
)
@callback

View File

@@ -866,6 +866,164 @@ async def test_reauth_flow_partial_credentials_update(
assert mock_config_entry.data[CONF_PORT] == 80
async def test_reauth_flow_preserves_non_credential_fields(
hass: HomeAssistant,
mock_bsblan: MagicMock,
) -> None:
"""Test reauth flow preserves non-credential fields using data_updates."""
# Create a config entry with additional custom fields that should be preserved
entry = MockConfigEntry(
domain=DOMAIN,
data={
CONF_HOST: "127.0.0.1",
CONF_PORT: 80,
CONF_PASSKEY: "old_key",
CONF_USERNAME: "old_user",
CONF_PASSWORD: "old_pass",
# Add some custom fields that should be preserved
"custom_field": "should_be_preserved",
"another_field": 42,
},
unique_id="00:80:41:19:69:90",
)
entry.add_to_hass(hass)
# Start reauth flow
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={
"source": SOURCE_REAUTH,
"entry_id": entry.entry_id,
},
)
# Submit with only new credentials
result = await _configure_flow(
hass,
result["flow_id"],
{
CONF_PASSKEY: "new_key",
CONF_USERNAME: "new_user",
CONF_PASSWORD: "new_pass",
},
)
_assert_abort_result(result, "reauth_successful")
# Verify that only the provided fields were updated, others preserved
assert entry.data[CONF_PASSKEY] == "new_key" # Updated
assert entry.data[CONF_USERNAME] == "new_user" # Updated
assert entry.data[CONF_PASSWORD] == "new_pass" # Updated
# These fields should remain unchanged (preserved by data_updates)
assert entry.data[CONF_HOST] == "127.0.0.1"
assert entry.data[CONF_PORT] == 80
assert entry.data["custom_field"] == "should_be_preserved"
assert entry.data["another_field"] == 42
async def test_reauth_flow_clears_credentials_with_empty_strings(
hass: HomeAssistant,
mock_bsblan: MagicMock,
) -> None:
"""Test reauth flow can clear credentials by providing empty strings."""
# Create a config entry with existing credentials
entry = MockConfigEntry(
domain=DOMAIN,
data={
CONF_HOST: "127.0.0.1",
CONF_PORT: 80,
CONF_PASSKEY: "existing_key",
CONF_USERNAME: "existing_user",
CONF_PASSWORD: "existing_pass",
},
unique_id="00:80:41:19:69:90",
)
entry.add_to_hass(hass)
# Start reauth flow
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={
"source": SOURCE_REAUTH,
"entry_id": entry.entry_id,
},
)
# Submit with empty strings to clear credentials
result = await _configure_flow(
hass,
result["flow_id"],
{
CONF_PASSKEY: "", # Clear passkey
CONF_USERNAME: "", # Clear username
CONF_PASSWORD: "", # Clear password
},
)
_assert_abort_result(result, "reauth_successful")
# Verify that credentials were cleared (set to empty strings)
assert entry.data[CONF_PASSKEY] == ""
assert entry.data[CONF_USERNAME] == ""
assert entry.data[CONF_PASSWORD] == ""
# Host and port should remain unchanged
assert entry.data[CONF_HOST] == "127.0.0.1"
assert entry.data[CONF_PORT] == 80
async def test_reauth_flow_partial_clear_credentials(
hass: HomeAssistant,
mock_bsblan: MagicMock,
) -> None:
"""Test reauth flow can partially clear some credentials while updating others."""
# Create a config entry with existing credentials
entry = MockConfigEntry(
domain=DOMAIN,
data={
CONF_HOST: "127.0.0.1",
CONF_PORT: 80,
CONF_PASSKEY: "existing_key",
CONF_USERNAME: "existing_user",
CONF_PASSWORD: "existing_pass",
},
unique_id="00:80:41:19:69:90",
)
entry.add_to_hass(hass)
# Start reauth flow
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={
"source": SOURCE_REAUTH,
"entry_id": entry.entry_id,
},
)
# Submit with mix of clearing and updating credentials
result = await _configure_flow(
hass,
result["flow_id"],
{
CONF_PASSKEY: "", # Clear passkey
CONF_USERNAME: "new_user", # Update username
CONF_PASSWORD: "", # Clear password
},
)
_assert_abort_result(result, "reauth_successful")
# Verify mixed update: some cleared, some updated, some preserved
assert entry.data[CONF_PASSKEY] == "" # Cleared
assert entry.data[CONF_USERNAME] == "new_user" # Updated
assert entry.data[CONF_PASSWORD] == "" # Cleared
# Host and port should remain unchanged
assert entry.data[CONF_HOST] == "127.0.0.1"
assert entry.data[CONF_PORT] == 80
async def test_zeroconf_discovery_auth_error_during_confirm(
hass: HomeAssistant,
mock_bsblan: MagicMock,