Proper handling of authentication errors in AVM Fritz!Tools (#79434)

This commit is contained in:
Michael 2023-02-01 16:03:18 +01:00 committed by GitHub
parent afa55156d6
commit 2f51059b7f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 57 additions and 21 deletions

View file

@ -1,15 +1,19 @@
"""Support for AVM Fritz!Box functions.""" """Support for AVM Fritz!Box functions."""
import logging import logging
from fritzconnection.core.exceptions import FritzConnectionException
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT, CONF_USERNAME from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT, CONF_USERNAME
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from .common import AvmWrapper, FritzData from .common import AvmWrapper, FritzData
from .const import DATA_FRITZ, DOMAIN, FRITZ_EXCEPTIONS, PLATFORMS from .const import (
DATA_FRITZ,
DOMAIN,
FRITZ_AUTH_EXCEPTIONS,
FRITZ_EXCEPTIONS,
PLATFORMS,
)
from .services import async_setup_services, async_unload_services from .services import async_setup_services, async_unload_services
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -28,10 +32,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
try: try:
await avm_wrapper.async_setup(entry.options) await avm_wrapper.async_setup(entry.options)
except FRITZ_AUTH_EXCEPTIONS as ex:
raise ConfigEntryAuthFailed from ex
except FRITZ_EXCEPTIONS as ex: except FRITZ_EXCEPTIONS as ex:
raise ConfigEntryNotReady from ex raise ConfigEntryNotReady from ex
except FritzConnectionException as ex:
raise ConfigEntryAuthFailed from ex
if ( if (
"X_AVM-DE_UPnP1" in avm_wrapper.connection.services "X_AVM-DE_UPnP1" in avm_wrapper.connection.services

View file

@ -9,7 +9,7 @@ from typing import Any
from urllib.parse import ParseResult, urlparse from urllib.parse import ParseResult, urlparse
from fritzconnection import FritzConnection from fritzconnection import FritzConnection
from fritzconnection.core.exceptions import FritzConnectionException, FritzSecurityError from fritzconnection.core.exceptions import FritzConnectionException
import voluptuous as vol import voluptuous as vol
from homeassistant.components import ssdp from homeassistant.components import ssdp
@ -32,6 +32,7 @@ from .const import (
ERROR_CANNOT_CONNECT, ERROR_CANNOT_CONNECT,
ERROR_UNKNOWN, ERROR_UNKNOWN,
ERROR_UPNP_NOT_CONFIGURED, ERROR_UPNP_NOT_CONFIGURED,
FRITZ_AUTH_EXCEPTIONS,
) )
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -70,7 +71,7 @@ class FritzBoxToolsFlowHandler(ConfigFlow, domain=DOMAIN):
timeout=60.0, timeout=60.0,
pool_maxsize=30, pool_maxsize=30,
) )
except FritzSecurityError: except FRITZ_AUTH_EXCEPTIONS:
return ERROR_AUTH_INVALID return ERROR_AUTH_INVALID
except FritzConnectionException: except FritzConnectionException:
return ERROR_CANNOT_CONNECT return ERROR_CANNOT_CONNECT

View file

@ -5,8 +5,11 @@ from typing import Literal
from fritzconnection.core.exceptions import ( from fritzconnection.core.exceptions import (
FritzActionError, FritzActionError,
FritzActionFailedError, FritzActionFailedError,
FritzAuthorizationError,
FritzConnectionException,
FritzInternalError, FritzInternalError,
FritzLookUpError, FritzLookUpError,
FritzSecurityError,
FritzServiceError, FritzServiceError,
) )
@ -66,9 +69,12 @@ UPTIME_DEVIATION = 5
FRITZ_EXCEPTIONS = ( FRITZ_EXCEPTIONS = (
FritzActionError, FritzActionError,
FritzActionFailedError, FritzActionFailedError,
FritzConnectionException,
FritzInternalError, FritzInternalError,
FritzServiceError, FritzServiceError,
FritzLookUpError, FritzLookUpError,
) )
FRITZ_AUTH_EXCEPTIONS = (FritzAuthorizationError, FritzSecurityError)
WIFI_STANDARD = {1: "2.4Ghz", 2: "5Ghz", 3: "5Ghz", 4: "Guest"} WIFI_STANDARD = {1: "2.4Ghz", 2: "5Ghz", 3: "5Ghz", 4: "Guest"}

View file

@ -2,7 +2,7 @@
"domain": "fritz", "domain": "fritz",
"name": "AVM FRITZ!Box Tools", "name": "AVM FRITZ!Box Tools",
"documentation": "https://www.home-assistant.io/integrations/fritz", "documentation": "https://www.home-assistant.io/integrations/fritz",
"requirements": ["fritzconnection==1.10.3", "xmltodict==0.13.0"], "requirements": ["fritzconnection==1.11.0", "xmltodict==0.13.0"],
"dependencies": ["network"], "dependencies": ["network"],
"codeowners": ["@mammuth", "@AaronDavidSchneider", "@chemelli74", "@mib1185"], "codeowners": ["@mammuth", "@AaronDavidSchneider", "@chemelli74", "@mib1185"],
"config_flow": true, "config_flow": true,

View file

@ -4,7 +4,7 @@
"integration_type": "device", "integration_type": "device",
"config_flow": true, "config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/fritzbox_callmonitor", "documentation": "https://www.home-assistant.io/integrations/fritzbox_callmonitor",
"requirements": ["fritzconnection==1.10.3"], "requirements": ["fritzconnection==1.11.0"],
"codeowners": ["@cdce8p"], "codeowners": ["@cdce8p"],
"iot_class": "local_polling", "iot_class": "local_polling",
"loggers": ["fritzconnection"] "loggers": ["fritzconnection"]

View file

@ -748,7 +748,7 @@ freesms==0.2.0
# homeassistant.components.fritz # homeassistant.components.fritz
# homeassistant.components.fritzbox_callmonitor # homeassistant.components.fritzbox_callmonitor
fritzconnection==1.10.3 fritzconnection==1.11.0
# homeassistant.components.google_translate # homeassistant.components.google_translate
gTTS==2.2.4 gTTS==2.2.4

View file

@ -567,7 +567,7 @@ freebox-api==1.0.1
# homeassistant.components.fritz # homeassistant.components.fritz
# homeassistant.components.fritzbox_callmonitor # homeassistant.components.fritzbox_callmonitor
fritzconnection==1.10.3 fritzconnection==1.11.0
# homeassistant.components.google_translate # homeassistant.components.google_translate
gTTS==2.2.4 gTTS==2.2.4

View file

@ -2,7 +2,12 @@
import dataclasses import dataclasses
from unittest.mock import patch from unittest.mock import patch
from fritzconnection.core.exceptions import FritzConnectionException, FritzSecurityError from fritzconnection.core.exceptions import (
FritzAuthorizationError,
FritzConnectionException,
FritzSecurityError,
)
import pytest
from homeassistant.components.device_tracker import ( from homeassistant.components.device_tracker import (
CONF_CONSIDER_HOME, CONF_CONSIDER_HOME,
@ -13,6 +18,7 @@ from homeassistant.components.fritz.const import (
ERROR_AUTH_INVALID, ERROR_AUTH_INVALID,
ERROR_CANNOT_CONNECT, ERROR_CANNOT_CONNECT,
ERROR_UNKNOWN, ERROR_UNKNOWN,
FRITZ_AUTH_EXCEPTIONS,
) )
from homeassistant.components.ssdp import ATTR_UPNP_UDN from homeassistant.components.ssdp import ATTR_UPNP_UDN
from homeassistant.config_entries import SOURCE_REAUTH, SOURCE_SSDP, SOURCE_USER from homeassistant.config_entries import SOURCE_REAUTH, SOURCE_SSDP, SOURCE_USER
@ -120,7 +126,11 @@ async def test_user_already_configured(
assert result["errors"]["base"] == "already_configured" assert result["errors"]["base"] == "already_configured"
async def test_exception_security(hass: HomeAssistant, mock_get_source_ip): @pytest.mark.parametrize(
"error",
FRITZ_AUTH_EXCEPTIONS,
)
async def test_exception_security(hass: HomeAssistant, mock_get_source_ip, error):
"""Test starting a flow by user with invalid credentials.""" """Test starting a flow by user with invalid credentials."""
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
@ -131,7 +141,7 @@ async def test_exception_security(hass: HomeAssistant, mock_get_source_ip):
with patch( with patch(
"homeassistant.components.fritz.config_flow.FritzConnection", "homeassistant.components.fritz.config_flow.FritzConnection",
side_effect=FritzSecurityError, side_effect=error,
): ):
result = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
@ -239,8 +249,16 @@ async def test_reauth_successful(
assert mock_setup_entry.called assert mock_setup_entry.called
@pytest.mark.parametrize(
"side_effect,error",
[
(FritzAuthorizationError, ERROR_AUTH_INVALID),
(FritzConnectionException, ERROR_CANNOT_CONNECT),
(FritzSecurityError, ERROR_AUTH_INVALID),
],
)
async def test_reauth_not_successful( async def test_reauth_not_successful(
hass: HomeAssistant, fc_class_mock, mock_get_source_ip hass: HomeAssistant, fc_class_mock, mock_get_source_ip, side_effect, error
): ):
"""Test starting a reauthentication flow but no connection found.""" """Test starting a reauthentication flow but no connection found."""
@ -249,7 +267,7 @@ async def test_reauth_not_successful(
with patch( with patch(
"homeassistant.components.fritz.config_flow.FritzConnection", "homeassistant.components.fritz.config_flow.FritzConnection",
side_effect=FritzConnectionException, side_effect=side_effect,
): ):
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
@ -271,7 +289,7 @@ async def test_reauth_not_successful(
assert result["type"] == FlowResultType.FORM assert result["type"] == FlowResultType.FORM
assert result["step_id"] == "reauth_confirm" assert result["step_id"] == "reauth_confirm"
assert result["errors"]["base"] == "cannot_connect" assert result["errors"]["base"] == error
async def test_ssdp_already_configured( async def test_ssdp_already_configured(

View file

@ -1,14 +1,17 @@
"""Tests for Fritz!Tools.""" """Tests for Fritz!Tools."""
from unittest.mock import patch from unittest.mock import patch
from fritzconnection.core.exceptions import FritzSecurityError
import pytest import pytest
from homeassistant.components.device_tracker import ( from homeassistant.components.device_tracker import (
CONF_CONSIDER_HOME, CONF_CONSIDER_HOME,
DEFAULT_CONSIDER_HOME, DEFAULT_CONSIDER_HOME,
) )
from homeassistant.components.fritz.const import DOMAIN, FRITZ_EXCEPTIONS from homeassistant.components.fritz.const import (
DOMAIN,
FRITZ_AUTH_EXCEPTIONS,
FRITZ_EXCEPTIONS,
)
from homeassistant.config_entries import ConfigEntryState from homeassistant.config_entries import ConfigEntryState
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.setup import async_setup_component from homeassistant.setup import async_setup_component
@ -60,7 +63,11 @@ async def test_options_reload(hass: HomeAssistant, fc_class_mock, fh_class_mock)
mock_reload.assert_called_once() mock_reload.assert_called_once()
async def test_setup_auth_fail(hass: HomeAssistant): @pytest.mark.parametrize(
"error",
FRITZ_AUTH_EXCEPTIONS,
)
async def test_setup_auth_fail(hass: HomeAssistant, error):
"""Test starting a flow by user with an already configured device.""" """Test starting a flow by user with an already configured device."""
entry = MockConfigEntry(domain=DOMAIN, data=MOCK_USER_DATA) entry = MockConfigEntry(domain=DOMAIN, data=MOCK_USER_DATA)
@ -68,7 +75,7 @@ async def test_setup_auth_fail(hass: HomeAssistant):
with patch( with patch(
"homeassistant.components.fritz.common.FritzConnection", "homeassistant.components.fritz.common.FritzConnection",
side_effect=FritzSecurityError, side_effect=error,
): ):
assert await async_setup_component(hass, DOMAIN, {}) assert await async_setup_component(hass, DOMAIN, {})
await hass.async_block_till_done() await hass.async_block_till_done()