Check for incompatible special chars in Reolink password (#122461)
This commit is contained in:
parent
156a2427ff
commit
b46b74df90
6 changed files with 48 additions and 7 deletions
|
@ -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 (
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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."""
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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}"
|
||||||
},
|
},
|
||||||
|
|
|
@ -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"],
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue