diff --git a/README.md b/README.md index 4aa32e7..30e9000 100644 --- a/README.md +++ b/README.md @@ -174,9 +174,9 @@ Use integration master branch instead of latest release to keep up with RouterOS ## Setup integration 1. Create user for homeassistant on your mikrotik router with following permissions: - * read, write, api, test, policy + * read, write, api, reboot, policy 2. If you want to be able to execute scripts on your mikrotik router from HA, script needs to have only following policies: - * read, write, test + * read, write or check "Don't Require Permissions" option 3. Setup this integration for your Mikrotik device in Home Assistant via `Configuration -> Integrations -> Add -> Mikrotik Router`. You can add this integration several times for different devices. diff --git a/custom_components/mikrotik_router/mikrotik_controller.py b/custom_components/mikrotik_router/mikrotik_controller.py index f9a61f3..dd6754f 100644 --- a/custom_components/mikrotik_router/mikrotik_controller.py +++ b/custom_components/mikrotik_router/mikrotik_controller.py @@ -106,6 +106,7 @@ class MikrotikControllerData: self.host = config_entry.data[CONF_HOST] self.data = { + "access": {}, "routerboard": {}, "resource": {}, "health": {}, @@ -181,7 +182,7 @@ class MikrotikControllerData: self.major_fw_version = 0 self.async_mac_lookup = AsyncMacLookup() - # self.async_mac_lookup.update_vendors() + self.accessrights_reported = False async def async_init(self): self.listeners.append( @@ -491,7 +492,10 @@ class MikrotikControllerData: except Exception: return - await self.hass.async_add_executor_job(self.get_firmware_update) + await self.hass.async_add_executor_job(self.get_access) + + if self.api.connected(): + await self.hass.async_add_executor_job(self.get_firmware_update) if self.api.connected(): await self.hass.async_add_executor_job(self.get_system_resource) @@ -685,6 +689,48 @@ class MikrotikControllerData: async_dispatcher_send(self.hass, self.signal_update) self.lock.release() + # --------------------------- + # get_access + # --------------------------- + def get_access(self): + """Get access rights from Mikrotik""" + tmp_user = parse_api( + data={}, + source=self.api.query("/user"), + key="name", + vals=[ + {"name": "name"}, + {"name": "group"}, + ], + ) + + tmp_group = parse_api( + data={}, + source=self.api.query("/user/group"), + key="name", + vals=[ + {"name": "name"}, + {"name": "policy"}, + ], + ) + + self.data["access"] = tmp_group[ + tmp_user[self.config_entry.data[CONF_USERNAME]]["group"] + ]["policy"].split(",") + + if not self.accessrights_reported: + self.accessrights_reported = True + if ( + "write" not in self.data["access"] + or "policy" not in self.data["access"] + or "reboot" not in self.data["access"] + ): + _LOGGER.warning( + "Mikrotik %s user %s does not have sufficient access rights. Integration functionality will be limited.", + self.host, + self.config_entry.data[CONF_USERNAME], + ) + # --------------------------- # get_interface # --------------------------- @@ -1330,11 +1376,26 @@ class MikrotikControllerData: ], ) + if ( + "write" not in self.data["access"] + or "policy" not in self.data["access"] + or "reboot" not in self.data["access"] + ): + self.data["routerboard"].pop("current-firmware") + self.data["routerboard"].pop("upgrade-firmware") + # --------------------------- # get_system_health # --------------------------- def get_system_health(self): """Get routerboard data from Mikrotik""" + if ( + "write" not in self.data["access"] + or "policy" not in self.data["access"] + or "reboot" not in self.data["access"] + ): + return + if 0 < self.major_fw_version < 7: self.data["health"] = parse_api( data=self.data["health"], @@ -1467,6 +1528,9 @@ class MikrotikControllerData: # --------------------------- def get_firmware_update(self): """Check for firmware update on Mikrotik""" + if "write" not in self.data["access"] or "policy" not in self.data["access"]: + return + self.execute("/system/package/update", "check-for-updates", None, None) self.data["fw-update"] = parse_api( data=self.data["fw-update"], diff --git a/custom_components/mikrotik_router/switch.py b/custom_components/mikrotik_router/switch.py index a9c72be..e799551 100644 --- a/custom_components/mikrotik_router/switch.py +++ b/custom_components/mikrotik_router/switch.py @@ -70,6 +70,9 @@ class MikrotikSwitch(MikrotikEntity, SwitchEntity, RestoreEntity): async def async_turn_on(self) -> None: """Turn on the switch.""" + if "write" not in self._ctrl.data["access"]: + return + path = self.entity_description.data_switch_path param = self.entity_description.data_reference value = self._data[self.entity_description.data_reference] @@ -79,6 +82,9 @@ class MikrotikSwitch(MikrotikEntity, SwitchEntity, RestoreEntity): async def async_turn_off(self) -> None: """Turn off the switch.""" + if "write" not in self._ctrl.data["access"]: + return + path = self.entity_description.data_switch_path param = self.entity_description.data_reference value = self._data[self.entity_description.data_reference] @@ -125,6 +131,9 @@ class MikrotikPortSwitch(MikrotikSwitch): async def async_turn_on(self) -> Optional[str]: """Turn on the switch.""" + if "write" not in self._ctrl.data["access"]: + return + path = self.entity_description.data_switch_path param = self.entity_description.data_reference if self._data["about"] == "managed by CAPsMAN": @@ -144,6 +153,9 @@ class MikrotikPortSwitch(MikrotikSwitch): async def async_turn_off(self) -> Optional[str]: """Turn off the switch.""" + if "write" not in self._ctrl.data["access"]: + return + path = self.entity_description.data_switch_path param = self.entity_description.data_reference if self._data["about"] == "managed by CAPsMAN": @@ -170,6 +182,9 @@ class MikrotikNATSwitch(MikrotikSwitch): async def async_turn_on(self) -> None: """Turn on the switch.""" + if "write" not in self._ctrl.data["access"]: + return + path = self.entity_description.data_switch_path param = ".id" value = None @@ -187,6 +202,9 @@ class MikrotikNATSwitch(MikrotikSwitch): async def async_turn_off(self) -> None: """Turn off the switch.""" + if "write" not in self._ctrl.data["access"]: + return + path = self.entity_description.data_switch_path param = ".id" value = None @@ -211,6 +229,9 @@ class MikrotikMangleSwitch(MikrotikSwitch): async def async_turn_on(self) -> None: """Turn on the switch.""" + if "write" not in self._ctrl.data["access"]: + return + path = self.entity_description.data_switch_path param = ".id" value = None @@ -229,6 +250,9 @@ class MikrotikMangleSwitch(MikrotikSwitch): async def async_turn_off(self) -> None: """Turn off the switch.""" + if "write" not in self._ctrl.data["access"]: + return + path = self.entity_description.data_switch_path param = ".id" value = None @@ -254,6 +278,9 @@ class MikrotikFilterSwitch(MikrotikSwitch): async def async_turn_on(self) -> None: """Turn on the switch.""" + if "write" not in self._ctrl.data["access"]: + return + path = self.entity_description.data_switch_path param = ".id" value = None @@ -271,6 +298,9 @@ class MikrotikFilterSwitch(MikrotikSwitch): async def async_turn_off(self) -> None: """Turn off the switch.""" + if "write" not in self._ctrl.data["access"]: + return + path = self.entity_description.data_switch_path param = ".id" value = None @@ -295,6 +325,9 @@ class MikrotikQueueSwitch(MikrotikSwitch): async def async_turn_on(self) -> None: """Turn on the switch.""" + if "write" not in self._ctrl.data["access"]: + return + path = self.entity_description.data_switch_path param = ".id" value = None @@ -308,6 +341,9 @@ class MikrotikQueueSwitch(MikrotikSwitch): async def async_turn_off(self) -> None: """Turn off the switch.""" + if "write" not in self._ctrl.data["access"]: + return + path = self.entity_description.data_switch_path param = ".id" value = None @@ -328,6 +364,9 @@ class MikrotikKidcontrolPauseSwitch(MikrotikSwitch): async def async_turn_on(self) -> None: """Turn on the switch.""" + if "write" not in self._ctrl.data["access"]: + return + path = self.entity_description.data_switch_path param = self.entity_description.data_reference value = self._data[self.entity_description.data_reference] @@ -337,6 +376,9 @@ class MikrotikKidcontrolPauseSwitch(MikrotikSwitch): async def async_turn_off(self) -> None: """Turn off the switch.""" + if "write" not in self._ctrl.data["access"]: + return + path = self.entity_description.data_switch_path param = self.entity_description.data_reference value = self._data[self.entity_description.data_reference]