Check for incompatible special chars in Reolink password (#122461)

This commit is contained in:
starkillerOG 2024-07-23 15:22:23 +02:00 committed by GitHub
parent 156a2427ff
commit b46b74df90
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 48 additions and 7 deletions

View file

@ -19,7 +19,7 @@ from homeassistant.helpers.device_registry import format_mac
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from .const import DOMAIN from .const import DOMAIN
from .exceptions import ReolinkException, UserNotAdmin from .exceptions import PasswordIncompatible, ReolinkException, UserNotAdmin
from .host import ReolinkHost from .host import ReolinkHost
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -56,7 +56,7 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
try: try:
await host.async_init() await host.async_init()
except (UserNotAdmin, CredentialsInvalidError) as err: except (UserNotAdmin, CredentialsInvalidError, PasswordIncompatible) as err:
await host.stop() await host.stop()
raise ConfigEntryAuthFailed(err) from err raise ConfigEntryAuthFailed(err) from err
except ( except (

View file

@ -6,6 +6,7 @@ from collections.abc import Mapping
import logging import logging
from typing import Any from typing import Any
from reolink_aio.api import ALLOWED_SPECIAL_CHARS
from reolink_aio.exceptions import ApiError, CredentialsInvalidError, ReolinkError from reolink_aio.exceptions import ApiError, CredentialsInvalidError, ReolinkError
import voluptuous as vol import voluptuous as vol
@ -29,7 +30,12 @@ from homeassistant.helpers import config_validation as cv, selector
from homeassistant.helpers.device_registry import format_mac from homeassistant.helpers.device_registry import format_mac
from .const import CONF_USE_HTTPS, DOMAIN from .const import CONF_USE_HTTPS, DOMAIN
from .exceptions import ReolinkException, ReolinkWebhookException, UserNotAdmin from .exceptions import (
PasswordIncompatible,
ReolinkException,
ReolinkWebhookException,
UserNotAdmin,
)
from .host import ReolinkHost from .host import ReolinkHost
from .util import is_connected from .util import is_connected
@ -206,8 +212,11 @@ class ReolinkFlowHandler(ConfigFlow, domain=DOMAIN):
errors[CONF_USERNAME] = "not_admin" errors[CONF_USERNAME] = "not_admin"
placeholders["username"] = host.api.username placeholders["username"] = host.api.username
placeholders["userlevel"] = host.api.user_level placeholders["userlevel"] = host.api.user_level
except PasswordIncompatible:
errors[CONF_PASSWORD] = "password_incompatible"
placeholders["special_chars"] = ALLOWED_SPECIAL_CHARS
except CredentialsInvalidError: except CredentialsInvalidError:
errors[CONF_HOST] = "invalid_auth" errors[CONF_PASSWORD] = "invalid_auth"
except ApiError as err: except ApiError as err:
placeholders["error"] = str(err) placeholders["error"] = str(err)
errors[CONF_HOST] = "api_error" errors[CONF_HOST] = "api_error"

View file

@ -17,3 +17,7 @@ class ReolinkWebhookException(ReolinkException):
class UserNotAdmin(ReolinkException): class UserNotAdmin(ReolinkException):
"""Raised when user is not admin.""" """Raised when user is not admin."""
class PasswordIncompatible(ReolinkException):
"""Raised when the password contains special chars that are incompatible."""

View file

@ -11,7 +11,7 @@ from typing import Any, Literal
import aiohttp import aiohttp
from aiohttp.web import Request from aiohttp.web import Request
from reolink_aio.api import Host from reolink_aio.api import ALLOWED_SPECIAL_CHARS, Host
from reolink_aio.enums import SubType from reolink_aio.enums import SubType
from reolink_aio.exceptions import NotSupportedError, ReolinkError, SubscriptionError from reolink_aio.exceptions import NotSupportedError, ReolinkError, SubscriptionError
@ -31,7 +31,12 @@ from homeassistant.helpers.event import async_call_later
from homeassistant.helpers.network import NoURLAvailableError, get_url from homeassistant.helpers.network import NoURLAvailableError, get_url
from .const import CONF_USE_HTTPS, DOMAIN from .const import CONF_USE_HTTPS, DOMAIN
from .exceptions import ReolinkSetupException, ReolinkWebhookException, UserNotAdmin from .exceptions import (
PasswordIncompatible,
ReolinkSetupException,
ReolinkWebhookException,
UserNotAdmin,
)
DEFAULT_TIMEOUT = 30 DEFAULT_TIMEOUT = 30
FIRST_ONVIF_TIMEOUT = 10 FIRST_ONVIF_TIMEOUT = 10
@ -123,6 +128,13 @@ class ReolinkHost:
async def async_init(self) -> None: async def async_init(self) -> None:
"""Connect to Reolink host.""" """Connect to Reolink host."""
if not self._api.valid_password():
raise PasswordIncompatible(
"Reolink password contains incompatible special character, "
"please change the password to only contain characters: "
f"a-z, A-Z, 0-9 or {ALLOWED_SPECIAL_CHARS}"
)
await self._api.get_host_data() await self._api.get_host_data()
if self._api.mac_address is None: if self._api.mac_address is None:

View file

@ -29,6 +29,7 @@
"cannot_connect": "Failed to connect, check the IP address of the camera", "cannot_connect": "Failed to connect, check the IP address of the camera",
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]", "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
"not_admin": "User needs to be admin, user \"{username}\" has authorisation level \"{userlevel}\"", "not_admin": "User needs to be admin, user \"{username}\" has authorisation level \"{userlevel}\"",
"password_incompatible": "Password contains incompatible special character, only these characters are allowed: a-z, A-Z, 0-9 or {special_chars}",
"unknown": "[%key:common::config_flow::error::unknown%]", "unknown": "[%key:common::config_flow::error::unknown%]",
"webhook_exception": "Home Assistant URL is not available, go to Settings > System > Network > Home Assistant URL and correct the URLs, see {more_info}" "webhook_exception": "Home Assistant URL is not available, go to Settings > System > Network > Home Assistant URL and correct the URLs, see {more_info}"
}, },

View file

@ -166,8 +166,23 @@ async def test_config_flow_errors(
assert result["type"] is FlowResultType.FORM assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "user" assert result["step_id"] == "user"
assert result["errors"] == {CONF_HOST: "invalid_auth"} assert result["errors"] == {CONF_PASSWORD: "invalid_auth"}
reolink_connect.valid_password.return_value = False
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
CONF_USERNAME: TEST_USERNAME,
CONF_PASSWORD: TEST_PASSWORD,
CONF_HOST: TEST_HOST,
},
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "user"
assert result["errors"] == {CONF_PASSWORD: "password_incompatible"}
reolink_connect.valid_password.return_value = True
reolink_connect.get_host_data.side_effect = ApiError("Test error") reolink_connect.get_host_data.side_effect = ApiError("Test error")
result = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],