Skip to content

Commit

Permalink
Support password less PI-Hole installations (#86183)
Browse files Browse the repository at this point in the history
  • Loading branch information
mib1185 committed Jan 22, 2023
1 parent aa7e051 commit e0d0dc0
Show file tree
Hide file tree
Showing 7 changed files with 95 additions and 45 deletions.
4 changes: 0 additions & 4 deletions homeassistant/components/pi_hole/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,10 +117,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
entry_data.pop(CONF_STATISTICS_ONLY)
hass.config_entries.async_update_entry(entry, data=entry_data)

# start reauth to force api key is present
if CONF_API_KEY not in entry.data:
raise ConfigEntryAuthFailed

_LOGGER.debug("Setting up %s integration with host %s", DOMAIN, host)

session = async_get_clientsession(hass, verify_tls)
Expand Down
26 changes: 23 additions & 3 deletions homeassistant/components/pi_hole/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ async def async_step_user(
CONF_LOCATION: user_input[CONF_LOCATION],
CONF_SSL: user_input[CONF_SSL],
CONF_VERIFY_SSL: user_input[CONF_VERIFY_SSL],
CONF_API_KEY: user_input[CONF_API_KEY],
}

self._async_abort_entries_match(
Expand All @@ -71,6 +70,9 @@ async def async_step_user(
title=user_input[CONF_NAME], data=self._config
)

if CONF_API_KEY in errors:
return await self.async_step_api_key()

user_input = user_input or {}
return self.async_show_form(
step_id="user",
Expand All @@ -80,7 +82,6 @@ async def async_step_user(
vol.Required(
CONF_PORT, default=user_input.get(CONF_PORT, 80)
): vol.Coerce(int),
vol.Required(CONF_API_KEY): str,
vol.Required(
CONF_NAME, default=user_input.get(CONF_NAME, DEFAULT_NAME)
): str,
Expand All @@ -101,6 +102,25 @@ async def async_step_user(
errors=errors,
)

async def async_step_api_key(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Handle step to setup API key."""
errors = {}
if user_input is not None:
self._config[CONF_API_KEY] = user_input[CONF_API_KEY]
if not (errors := await self._async_try_connect()):
return self.async_create_entry(
title=self._config[CONF_NAME],
data=self._config,
)

return self.async_show_form(
step_id="api_key",
data_schema=vol.Schema({vol.Required(CONF_API_KEY): str}),
errors=errors,
)

async def async_step_import(self, user_input: dict[str, Any]) -> FlowResult:
"""Handle a flow initiated by import."""

Expand Down Expand Up @@ -178,7 +198,7 @@ async def _async_try_connect(self) -> dict[str, str]:
session,
location=self._config[CONF_LOCATION],
tls=self._config[CONF_SSL],
api_token=self._config[CONF_API_KEY],
api_token=self._config.get(CONF_API_KEY),
)
try:
await pi_hole.get_data()
Expand Down
6 changes: 5 additions & 1 deletion homeassistant/components/pi_hole/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,15 @@
"port": "[%key:common::config_flow::data::port%]",
"name": "[%key:common::config_flow::data::name%]",
"location": "[%key:common::config_flow::data::location%]",
"api_key": "[%key:common::config_flow::data::api_key%]",
"ssl": "[%key:common::config_flow::data::ssl%]",
"verify_ssl": "[%key:common::config_flow::data::verify_ssl%]"
}
},
"api_key": {
"data": {
"api_key": "[%key:common::config_flow::data::api_key%]"
}
},
"reauth_confirm": {
"title": "PI-Hole [%key:common::config_flow::title::reauth%]",
"description": "Please enter a new api key for PI-Hole at {host}/{location}",
Expand Down
6 changes: 5 additions & 1 deletion homeassistant/components/pi_hole/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@
"invalid_auth": "Invalid authentication"
},
"step": {
"api_key": {
"data": {
"api_key": "API Key"
}
},
"reauth_confirm": {
"data": {
"api_key": "API Key"
Expand All @@ -18,7 +23,6 @@
},
"user": {
"data": {
"api_key": "API Key",
"host": "Host",
"location": "Location",
"name": "Name",
Expand Down
19 changes: 16 additions & 3 deletions tests/components/pi_hole/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@
CONFIG_FLOW_USER = {
CONF_HOST: HOST,
CONF_PORT: PORT,
CONF_API_KEY: API_KEY,
CONF_LOCATION: LOCATION,
CONF_NAME: NAME,
CONF_SSL: SSL,
Expand All @@ -85,7 +84,7 @@
CONF_API_KEY: API_KEY,
}

CONFIG_ENTRY = {
CONFIG_ENTRY_WITH_API_KEY = {
CONF_HOST: f"{HOST}:{PORT}",
CONF_LOCATION: LOCATION,
CONF_NAME: NAME,
Expand All @@ -94,7 +93,15 @@
CONF_VERIFY_SSL: VERIFY_SSL,
}

CONFIG_ENTRY_IMPORTED = {**CONFIG_ENTRY, CONF_STATISTICS_ONLY: False}
CONFIG_ENTRY_WITHOUT_API_KEY = {
CONF_HOST: f"{HOST}:{PORT}",
CONF_LOCATION: LOCATION,
CONF_NAME: NAME,
CONF_SSL: SSL,
CONF_VERIFY_SSL: VERIFY_SSL,
}

CONFIG_ENTRY_IMPORTED = {**CONFIG_ENTRY_WITH_API_KEY, CONF_STATISTICS_ONLY: False}

SWITCH_ENTITY_ID = "switch.pi_hole"

Expand Down Expand Up @@ -128,3 +135,9 @@ def _patch_config_flow_hole(mocked_hole):
return patch(
"homeassistant.components.pi_hole.config_flow.Hole", return_value=mocked_hole
)


def _patch_setup_hole():
return patch(
"homeassistant.components.pi_hole.async_setup_entry", return_value=True
)
64 changes: 45 additions & 19 deletions tests/components/pi_hole/test_config_flow.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
"""Test pi_hole config flow."""
import logging
from unittest.mock import patch

from homeassistant.components.pi_hole.const import DOMAIN
from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_USER
Expand All @@ -11,30 +10,26 @@
from . import (
CONFIG_DATA,
CONFIG_DATA_DEFAULTS,
CONFIG_ENTRY,
CONFIG_ENTRY_IMPORTED,
CONFIG_ENTRY_WITH_API_KEY,
CONFIG_ENTRY_WITHOUT_API_KEY,
CONFIG_FLOW_API_KEY,
CONFIG_FLOW_USER,
NAME,
ZERO_DATA,
_create_mocked_hole,
_patch_config_flow_hole,
_patch_init_hole,
_patch_setup_hole,
)

from tests.common import MockConfigEntry


def _patch_setup():
return patch(
"homeassistant.components.pi_hole.async_setup_entry",
return_value=True,
)


async def test_flow_import(hass: HomeAssistant, caplog):
"""Test import flow."""
mocked_hole = _create_mocked_hole()
with _patch_config_flow_hole(mocked_hole), _patch_setup():
with _patch_config_flow_hole(mocked_hole), _patch_setup_hole():
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_IMPORT}, data=CONFIG_DATA
)
Expand All @@ -53,7 +48,7 @@ async def test_flow_import(hass: HomeAssistant, caplog):
async def test_flow_import_invalid(hass: HomeAssistant, caplog):
"""Test import flow with invalid server."""
mocked_hole = _create_mocked_hole(True)
with _patch_config_flow_hole(mocked_hole), _patch_setup():
with _patch_config_flow_hole(mocked_hole), _patch_setup_hole():
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_IMPORT}, data=CONFIG_DATA
)
Expand All @@ -62,10 +57,10 @@ async def test_flow_import_invalid(hass: HomeAssistant, caplog):
assert len([x for x in caplog.records if x.levelno == logging.ERROR]) == 1


async def test_flow_user(hass: HomeAssistant):
"""Test user initialized flow."""
async def test_flow_user_with_api_key(hass: HomeAssistant):
"""Test user initialized flow with api key needed."""
mocked_hole = _create_mocked_hole(has_data=False)
with _patch_config_flow_hole(mocked_hole), _patch_init_hole(mocked_hole):
with _patch_config_flow_hole(mocked_hole), _patch_setup_hole() as mock_setup:
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_USER},
Expand All @@ -79,17 +74,26 @@ async def test_flow_user(hass: HomeAssistant):
user_input=CONFIG_FLOW_USER,
)
assert result["type"] == FlowResultType.FORM
assert result["step_id"] == "user"
assert result["step_id"] == "api_key"
assert result["errors"] == {}

result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input={CONF_API_KEY: "some_key"},
)
assert result["type"] == FlowResultType.FORM
assert result["step_id"] == "api_key"
assert result["errors"] == {CONF_API_KEY: "invalid_auth"}

mocked_hole.data = ZERO_DATA
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input=CONFIG_FLOW_USER,
user_input=CONFIG_FLOW_API_KEY,
)
assert result["type"] == FlowResultType.CREATE_ENTRY
assert result["title"] == NAME
assert result["data"] == CONFIG_ENTRY
assert result["data"] == CONFIG_ENTRY_WITH_API_KEY
mock_setup.assert_called_once()

# duplicated server
result = await hass.config_entries.flow.async_init(
Expand All @@ -101,10 +105,32 @@ async def test_flow_user(hass: HomeAssistant):
assert result["reason"] == "already_configured"


async def test_flow_user_invalid(hass):
async def test_flow_user_without_api_key(hass: HomeAssistant):
"""Test user initialized flow without api key needed."""
mocked_hole = _create_mocked_hole()
with _patch_config_flow_hole(mocked_hole), _patch_setup_hole() as mock_setup:
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_USER},
)
assert result["type"] == FlowResultType.FORM
assert result["step_id"] == "user"
assert result["errors"] == {}

result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input=CONFIG_FLOW_USER,
)
assert result["type"] == FlowResultType.CREATE_ENTRY
assert result["title"] == NAME
assert result["data"] == CONFIG_ENTRY_WITHOUT_API_KEY
mock_setup.assert_called_once()


async def test_flow_user_invalid(hass: HomeAssistant):
"""Test user initialized flow with invalid server."""
mocked_hole = _create_mocked_hole(True)
with _patch_config_flow_hole(mocked_hole), _patch_setup():
with _patch_config_flow_hole(mocked_hole):
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}, data=CONFIG_FLOW_USER
)
Expand Down
15 changes: 1 addition & 14 deletions tests/components/pi_hole/test_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@
SERVICE_DISABLE,
SERVICE_DISABLE_ATTR_DURATION,
)
from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import ATTR_ENTITY_ID, CONF_API_KEY, CONF_HOST
from homeassistant.const import ATTR_ENTITY_ID, CONF_HOST
from homeassistant.core import HomeAssistant
from homeassistant.setup import async_setup_component

Expand Down Expand Up @@ -200,15 +199,3 @@ async def test_remove_obsolete(hass: HomeAssistant):
with _patch_init_hole(mocked_hole):
assert await hass.config_entries.async_setup(entry.entry_id)
assert CONF_STATISTICS_ONLY not in entry.data


async def test_missing_api_key(hass: HomeAssistant):
"""Tests start reauth flow if api key is missing."""
mocked_hole = _create_mocked_hole()
data = CONFIG_DATA_DEFAULTS.copy()
data.pop(CONF_API_KEY)
entry = MockConfigEntry(domain=pi_hole.DOMAIN, data=data)
entry.add_to_hass(hass)
with _patch_init_hole(mocked_hole):
assert not await hass.config_entries.async_setup(entry.entry_id)
assert entry.state == ConfigEntryState.SETUP_ERROR

0 comments on commit e0d0dc0

Please sign in to comment.