mirror of
https://github.com/home-assistant/core.git
synced 2026-08-24 02:24:51 -05:00
Do not probe Pi-hole v6 API version with a wrong password (#178143)
This commit is contained in:
@@ -146,7 +146,11 @@ async def determine_api_version(
|
||||
debugging.
|
||||
"""
|
||||
|
||||
hole_v6 = api_by_version(hass, entry, 6, password="wrong_password")
|
||||
# Pi-hole v6 rate-limits login attempts, so probing with a deliberately
|
||||
# wrong password makes the real login that follows shortly after fail as
|
||||
# well. Probe with the configured password instead: a 401 identifies v6
|
||||
# just as well as a success does.
|
||||
hole_v6 = api_by_version(hass, entry, 6)
|
||||
try:
|
||||
await hole_v6.authenticate()
|
||||
except HoleConnectionError as err:
|
||||
@@ -160,7 +164,8 @@ async def determine_api_version(
|
||||
except HoleError as ex_v6:
|
||||
if str(ex_v6) == "Authentication failed: Invalid password":
|
||||
_LOGGER.debug(
|
||||
"Success connecting to Pi-hole at %s without auth, API version is : %s",
|
||||
"Pi-hole at %s answered the v6 auth endpoint (password"
|
||||
" rejected), API version is : %s",
|
||||
hole_v6.base_url,
|
||||
6,
|
||||
)
|
||||
@@ -169,13 +174,19 @@ async def determine_api_version(
|
||||
"Connection to %s failed: %s, trying API version 5", hole_v6.base_url, ex_v6
|
||||
)
|
||||
else:
|
||||
# It seems that occasionally the auth can succeed
|
||||
# unexpectedly when there is a valid session
|
||||
_LOGGER.warning(
|
||||
"Authenticated with %s through v6 API, but"
|
||||
" succeeded with an incorrect password."
|
||||
" This is a known bug",
|
||||
# Release the probe session again: the operational client
|
||||
# authenticates separately and Pi-hole allows only a limited
|
||||
# number of concurrent sessions.
|
||||
try:
|
||||
await hole_v6.logout()
|
||||
except HoleError as err:
|
||||
_LOGGER.debug(
|
||||
"Could not release the probe session at %s: %s", hole_v6.base_url, err
|
||||
)
|
||||
_LOGGER.debug(
|
||||
"Authenticated with %s through v6 API, API version is : %s",
|
||||
hole_v6.base_url,
|
||||
6,
|
||||
)
|
||||
return 6
|
||||
hole_v5 = api_by_version(hass, entry, 5, password="wrong_token")
|
||||
|
||||
@@ -202,6 +202,7 @@ def _create_mocked_hole(
|
||||
incorrect_app_password: bool = False,
|
||||
wrong_host: bool = False,
|
||||
ftl_error: bool = False,
|
||||
logout_error: bool = False,
|
||||
) -> MagicMock:
|
||||
"""Return a mocked Hole API object with side effects based on constructor args."""
|
||||
|
||||
@@ -250,6 +251,9 @@ def _create_mocked_hole(
|
||||
mocked_hole.data = FTL_ERROR
|
||||
|
||||
mocked_hole.authenticate = AsyncMock(side_effect=authenticate_side_effect)
|
||||
mocked_hole.logout = AsyncMock(
|
||||
side_effect=HoleError("Logout failed") if logout_error else None
|
||||
)
|
||||
mocked_hole.get_data = AsyncMock(side_effect=get_data_side_effect)
|
||||
|
||||
if ftl_error:
|
||||
|
||||
@@ -62,6 +62,41 @@ async def test_setup_api_v6(
|
||||
)
|
||||
|
||||
|
||||
async def test_version_probe_uses_configured_password(hass: HomeAssistant) -> None:
|
||||
"""Test the v6 version probe never authenticates with a sentinel password.
|
||||
|
||||
Pi-hole v6 rate-limits login attempts, so a deliberately failed probe makes
|
||||
the real login that follows shortly after fail as well, and the config entry
|
||||
then never recovers on its own.
|
||||
"""
|
||||
mocked_hole = _create_mocked_hole(api_version=6)
|
||||
entry = MockConfigEntry(domain=pi_hole.DOMAIN, data={**CONFIG_DATA_DEFAULTS})
|
||||
entry.add_to_hass(hass)
|
||||
with _patch_init_hole(mocked_hole):
|
||||
assert await hass.config_entries.async_setup(entry.entry_id)
|
||||
|
||||
assert mocked_hole.instances
|
||||
assert all(instance.password == API_KEY for instance in mocked_hole.instances)
|
||||
# The probe releases its session again so it does not use up one of the
|
||||
# limited number of Pi-hole sessions.
|
||||
mocked_hole.instances[0].logout.assert_awaited_once()
|
||||
|
||||
|
||||
async def test_version_probe_survives_failing_logout(hass: HomeAssistant) -> None:
|
||||
"""Test setup still succeeds when releasing the probe session fails.
|
||||
|
||||
Releasing the session is a courtesy, not a requirement, so a failure there
|
||||
must not turn a working configuration into a failed setup.
|
||||
"""
|
||||
mocked_hole = _create_mocked_hole(api_version=6, logout_error=True)
|
||||
entry = MockConfigEntry(domain=pi_hole.DOMAIN, data={**CONFIG_DATA_DEFAULTS})
|
||||
entry.add_to_hass(hass)
|
||||
with _patch_init_hole(mocked_hole):
|
||||
assert await hass.config_entries.async_setup(entry.entry_id)
|
||||
|
||||
assert entry.state is ConfigEntryState.LOADED
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("config_entry_data", "expected_api_token"),
|
||||
[({**CONFIG_DATA_DEFAULTS}, API_KEY)],
|
||||
|
||||
Reference in New Issue
Block a user