diff --git a/custom_components/mega/config_flow.py b/custom_components/mega/config_flow.py index 891263a..9429425 100644 --- a/custom_components/mega/config_flow.py +++ b/custom_components/mega/config_flow.py @@ -10,7 +10,8 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_HOST, CONF_ID, CONF_PASSWORD, CONF_SCAN_INTERVAL from homeassistant.core import callback, HomeAssistant from .const import DOMAIN, CONF_PORT_TO_SCAN, CONF_RELOAD, PLATFORMS, CONF_MQTT_INPUTS, \ - CONF_NPORTS, CONF_UPDATE_ALL, CONF_POLL_OUTS, CONF_FAKE_RESPONSE, CONF_FORCE_D # pylint:disable=unused-import + CONF_NPORTS, CONF_UPDATE_ALL, CONF_POLL_OUTS, CONF_FAKE_RESPONSE, CONF_FORCE_D, \ + CONF_ALLOW_HOSTS, CONF_PROTECTED # pylint:disable=unused-import from .hub import MegaD from . import exceptions @@ -29,6 +30,8 @@ STEP_USER_DATA_SCHEMA = vol.Schema( vol.Optional(CONF_UPDATE_ALL, default=True): bool, vol.Optional(CONF_FAKE_RESPONSE, default=True): bool, vol.Optional(CONF_FORCE_D, default=True): bool, + vol.Optional(CONF_PROTECTED, default=True): bool, + vol.Optional(CONF_ALLOW_HOSTS, default='::1;127.0.0.1'): str, }, ) @@ -109,7 +112,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow): async def async_step_init(self, user_input=None): """Manage the options.""" - + hub = await get_hub(self.hass, self.config_entry.data) if user_input is not None: reload = user_input.pop(CONF_RELOAD) cfg = dict(self.config_entry.data) @@ -142,6 +145,8 @@ class OptionsFlowHandler(config_entries.OptionsFlow): vol.Optional(CONF_UPDATE_ALL, default=e.get(CONF_UPDATE_ALL, True)): bool, vol.Optional(CONF_FAKE_RESPONSE, default=e.get(CONF_FAKE_RESPONSE, True)): bool, vol.Optional(CONF_FORCE_D, default=e.get(CONF_FORCE_D, False)): bool, + vol.Optional(CONF_PROTECTED, default=e.get(CONF_PROTECTED, True)): bool, + vol.Optional(CONF_ALLOW_HOSTS, default='::1;127.0.0.1'): str, # vol.Optional(CONF_INVERT, default=''): str, }), ) diff --git a/custom_components/mega/const.py b/custom_components/mega/const.py index 0c6b165..2212242 100644 --- a/custom_components/mega/const.py +++ b/custom_components/mega/const.py @@ -26,6 +26,7 @@ CONF_UPDATE_ALL = 'update_all' CONF_FAKE_RESPONSE = 'fake_response' CONF_GET_VALUE = 'get_value' CONF_ALLOW_HOSTS = 'allow_hosts' +CONF_PROTECTED = 'protected' CONF_CONV_TEMPLATE = 'conv_template' CONF_POLL_OUTS = 'poll_outs' CONF_FORCE_D = 'force_d' diff --git a/custom_components/mega/http.py b/custom_components/mega/http.py index 6f17fbc..6fe940b 100644 --- a/custom_components/mega/http.py +++ b/custom_components/mega/http.py @@ -24,7 +24,9 @@ class MegaView(HomeAssistantView): def __init__(self, cfg: dict): self._try = 0 + self.protected = True self.allowed_hosts = {'::1', '127.0.0.1'} + self.notified_attempts = defaultdict(lambda : False) self.callbacks = defaultdict(lambda: defaultdict(list)) self.templates: typing.Dict[str, typing.Dict[str, Template]] = { mid: { @@ -37,16 +39,30 @@ class MegaView(HomeAssistantView): async def get(self, request: Request) -> Response: - auth = False - for x in self.allowed_hosts: - if request.remote.startswith(x): - auth = True - break - if not auth: - _LOGGER.warning(f'unauthorised attempt to connect from {request.remote}') - return Response(status=401) - hass: HomeAssistant = request.app['hass'] + if self.protected: + auth = False + for x in self.allowed_hosts: + if request.remote.startswith(x): + auth = True + break + if not auth: + msg = f"Non-authorised request from {request.remote} to `/mega`. "\ + f"If you want to accept requests from this host "\ + f"please add it to allowed hosts in `mega` UI-configuration" + if not self.notified_attempts[request.remote]: + await hass.services.async_call( + 'persistent_notification', + 'create', + { + "notification_id": request.remote, + "title": "Non-authorised request", + "message": msg + } + ) + _LOGGER.warning(msg) + return Response(status=401) + hub: 'h.MegaD' = hass.data.get(DOMAIN).get(request.remote) # TODO: проверить какой remote if hub is None and request.remote == '::1': hub = hass.data.get(DOMAIN).get('__def') diff --git a/custom_components/mega/hub.py b/custom_components/mega/hub.py index ab81edc..3fbe02c 100644 --- a/custom_components/mega/hub.py +++ b/custom_components/mega/hub.py @@ -72,11 +72,12 @@ class MegaD: scan_interval=60, port_to_scan=0, nports=38, - inverted: typing.List[int] = None, - update_all=True, - poll_outs=False, - fake_response=True, - force_d=None, + update_all: bool=True, + poll_outs: bool=False, + fake_response: bool=True, + force_d: bool=None, + allow_hosts: str=None, + protected=True, **kwargs, ): """Initialize.""" @@ -133,6 +134,14 @@ class MegaD: if force_d is not None: self.customize[CONF_FORCE_D] = force_d + try: + if allow_hosts is not None: + allow_hosts = set(allow_hosts.split(';')) + hass.data[DOMAIN][CONF_HTTP].allowed_hosts |= allow_hosts + hass.data[DOMAIN][CONF_HTTP].protected = protected + + except Exception: + self.lg.exception('while setting allowed hosts') async def start(self): self.loop = asyncio.get_event_loop() diff --git a/custom_components/mega/strings.json b/custom_components/mega/strings.json index a307198..ddfbf91 100644 --- a/custom_components/mega/strings.json +++ b/custom_components/mega/strings.json @@ -17,6 +17,8 @@ "update_all": "[%key:common::config_flow::data::update_all%]", "fake_response": "[%key:common::config_flow::data::fake_response%]", "force_d": "[%key:common::config_flow::data::force_d%]", + "protected": "[%key:common::config_flow::data::protected%]", + "allow_hosts": "[%key:common::config_flow::data::allow_hosts%]", "poll_outs": "[%key:common::config_flow::data::poll_outs%]" } } diff --git a/custom_components/mega/translations/en.json b/custom_components/mega/translations/en.json index f1a77f3..1d2f31c 100644 --- a/custom_components/mega/translations/en.json +++ b/custom_components/mega/translations/en.json @@ -25,6 +25,8 @@ "mqtt_inputs": "Use MQTT (Deprecated)", "fake_response": "Fake response", "force_d": "Force 'd' response", + "protected": "Protected", + "allow_hosts": "Allowed hosts", "poll_outs": "Poll outs" } } @@ -41,6 +43,8 @@ "update_all": "Update all outs when input", "fake_response": "Fake response", "force_d": "Force 'd' response", + "protected": "Protected", + "allow_hosts": "Allowed hosts", "poll_outs": "Poll outs" } } diff --git a/custom_components/mega/translations/ru.json b/custom_components/mega/translations/ru.json index 973d875..f2a2b1d 100644 --- a/custom_components/mega/translations/ru.json +++ b/custom_components/mega/translations/ru.json @@ -24,6 +24,8 @@ "update_all": "Обновить все выходы когда срабатывает вход", "fake_response": "Имитация http-ответа", "force_d": "Ответ 'd' по умолчанию", + "protected": "Блокировать неразрешенные соединения", + "allow_hosts": "Разрешенные ip (через ;)", "poll_outs": "Обновлять выходы (регулярно)" } } @@ -42,6 +44,8 @@ "force_d": "Ответ 'd' по умолчанию", "nports": "Кол-во портов", "update_all": "Обновить все выходы когда срабатывает вход", + "protected": "Блокировать неразрешенные соединения", + "allow_hosts": "Разрешенные ip (через ;)", "poll_outs": "Обновлять выходы (регулярно)" } } diff --git a/custom_components/mega/translations/uk.json b/custom_components/mega/translations/uk.json index d30988b..32b67eb 100644 --- a/custom_components/mega/translations/uk.json +++ b/custom_components/mega/translations/uk.json @@ -24,6 +24,8 @@ "update_all": "Оновити всі виходи коли спрацьовує вхід", "fake_response": "Имитация http-ответа", "force_d": "Ответ 'd' по умолчанию", + "protected": "Блокировать неразрешенные соединения", + "allow_hosts": "Разрешенные ip (через ;)", "poll_outs": "Оновити виходи" } } @@ -42,6 +44,8 @@ "fake_response": "Имитация http-ответа", "force_d": "Ответ 'd' по умолчанию", "update_all": "Оновити всі виходи коли спрацьовує вхід", + "protected": "Блокировать неразрешенные соединения", + "allow_hosts": "Разрешенные ip (через ;)", "poll_outs": "Оновити виходи" } }