Add Permission checking for UniFi Protect (#73765)

Co-authored-by: J. Nick Koston <nick@koston.org>
pull/73768/head
Christopher Bailey 2022-06-21 12:17:29 -04:00 committed by GitHub
parent a816348616
commit 3823edda32
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 109 additions and 13 deletions

View File

@ -18,7 +18,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DOMAIN from .const import DOMAIN
from .data import ProtectData from .data import ProtectData
from .entity import ProtectDeviceEntity, async_all_device_entities from .entity import ProtectDeviceEntity, async_all_device_entities
from .models import ProtectSetableKeysMixin, T from .models import PermRequired, ProtectSetableKeysMixin, T
@dataclass @dataclass
@ -40,6 +40,7 @@ ALL_DEVICE_BUTTONS: tuple[ProtectButtonEntityDescription, ...] = (
device_class=ButtonDeviceClass.RESTART, device_class=ButtonDeviceClass.RESTART,
name="Reboot Device", name="Reboot Device",
ufp_press="reboot", ufp_press="reboot",
ufp_perm=PermRequired.WRITE,
), ),
) )
@ -49,6 +50,7 @@ SENSOR_BUTTONS: tuple[ProtectButtonEntityDescription, ...] = (
name="Clear Tamper", name="Clear Tamper",
icon="mdi:notification-clear-all", icon="mdi:notification-clear-all",
ufp_press="clear_tamper", ufp_press="clear_tamper",
ufp_perm=PermRequired.WRITE,
), ),
) )

View File

@ -26,7 +26,7 @@ from homeassistant.helpers.entity import DeviceInfo, Entity, EntityDescription
from .const import ATTR_EVENT_SCORE, DEFAULT_ATTRIBUTION, DEFAULT_BRAND, DOMAIN from .const import ATTR_EVENT_SCORE, DEFAULT_ATTRIBUTION, DEFAULT_BRAND, DOMAIN
from .data import ProtectData from .data import ProtectData
from .models import ProtectRequiredKeysMixin from .models import PermRequired, ProtectRequiredKeysMixin
from .utils import get_nested_attr from .utils import get_nested_attr
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -46,6 +46,11 @@ def _async_device_entities(
for device in data.get_by_types({model_type}): for device in data.get_by_types({model_type}):
assert isinstance(device, (Camera, Light, Sensor, Viewer, Doorlock, Chime)) assert isinstance(device, (Camera, Light, Sensor, Viewer, Doorlock, Chime))
for description in descs: for description in descs:
if description.ufp_perm is not None:
can_write = device.can_write(data.api.bootstrap.auth_user)
if description.ufp_perm == PermRequired.WRITE and not can_write:
continue
if description.ufp_required_field: if description.ufp_required_field:
required_field = get_nested_attr(device, description.ufp_required_field) required_field = get_nested_attr(device, description.ufp_required_field)
if not required_field: if not required_field:

View File

@ -25,13 +25,10 @@ async def async_setup_entry(
) -> None: ) -> None:
"""Set up lights for UniFi Protect integration.""" """Set up lights for UniFi Protect integration."""
data: ProtectData = hass.data[DOMAIN][entry.entry_id] data: ProtectData = hass.data[DOMAIN][entry.entry_id]
entities = [ entities = []
ProtectLight( for device in data.api.bootstrap.lights.values():
data, if device.can_write(data.api.bootstrap.auth_user):
device, entities.append(ProtectLight(data, device))
)
for device in data.api.bootstrap.lights.values()
]
if not entities: if not entities:
return return

View File

@ -3,6 +3,7 @@ from __future__ import annotations
from collections.abc import Callable, Coroutine from collections.abc import Callable, Coroutine
from dataclasses import dataclass from dataclasses import dataclass
from enum import Enum
import logging import logging
from typing import Any, Generic, TypeVar from typing import Any, Generic, TypeVar
@ -17,6 +18,13 @@ _LOGGER = logging.getLogger(__name__)
T = TypeVar("T", bound=ProtectDeviceModel) T = TypeVar("T", bound=ProtectDeviceModel)
class PermRequired(int, Enum):
"""Type of permission level required for entity."""
NO_WRITE = 1
WRITE = 2
@dataclass @dataclass
class ProtectRequiredKeysMixin(EntityDescription, Generic[T]): class ProtectRequiredKeysMixin(EntityDescription, Generic[T]):
"""Mixin for required keys.""" """Mixin for required keys."""
@ -25,6 +33,7 @@ class ProtectRequiredKeysMixin(EntityDescription, Generic[T]):
ufp_value: str | None = None ufp_value: str | None = None
ufp_value_fn: Callable[[T], Any] | None = None ufp_value_fn: Callable[[T], Any] | None = None
ufp_enabled: str | None = None ufp_enabled: str | None = None
ufp_perm: PermRequired | None = None
def get_ufp_value(self, obj: T) -> Any: def get_ufp_value(self, obj: T) -> Any:
"""Return value from UniFi Protect device.""" """Return value from UniFi Protect device."""

View File

@ -8,7 +8,7 @@ from pyunifiprotect.data import Camera, Doorlock, Light, ProtectModelWithId
from homeassistant.components.number import NumberEntity, NumberEntityDescription from homeassistant.components.number import NumberEntity, NumberEntityDescription
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import TIME_SECONDS from homeassistant.const import PERCENTAGE, TIME_SECONDS
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity import EntityCategory
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
@ -16,7 +16,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DOMAIN from .const import DOMAIN
from .data import ProtectData from .data import ProtectData
from .entity import ProtectDeviceEntity, async_all_device_entities from .entity import ProtectDeviceEntity, async_all_device_entities
from .models import ProtectSetableKeysMixin, T from .models import PermRequired, ProtectSetableKeysMixin, T
@dataclass @dataclass
@ -63,30 +63,35 @@ CAMERA_NUMBERS: tuple[ProtectNumberEntityDescription, ...] = (
ufp_required_field="feature_flags.has_wdr", ufp_required_field="feature_flags.has_wdr",
ufp_value="isp_settings.wdr", ufp_value="isp_settings.wdr",
ufp_set_method="set_wdr_level", ufp_set_method="set_wdr_level",
ufp_perm=PermRequired.WRITE,
), ),
ProtectNumberEntityDescription( ProtectNumberEntityDescription(
key="mic_level", key="mic_level",
name="Microphone Level", name="Microphone Level",
icon="mdi:microphone", icon="mdi:microphone",
entity_category=EntityCategory.CONFIG, entity_category=EntityCategory.CONFIG,
native_unit_of_measurement=PERCENTAGE,
ufp_min=0, ufp_min=0,
ufp_max=100, ufp_max=100,
ufp_step=1, ufp_step=1,
ufp_required_field="feature_flags.has_mic", ufp_required_field="feature_flags.has_mic",
ufp_value="mic_volume", ufp_value="mic_volume",
ufp_set_method="set_mic_volume", ufp_set_method="set_mic_volume",
ufp_perm=PermRequired.WRITE,
), ),
ProtectNumberEntityDescription( ProtectNumberEntityDescription(
key="zoom_position", key="zoom_position",
name="Zoom Level", name="Zoom Level",
icon="mdi:magnify-plus-outline", icon="mdi:magnify-plus-outline",
entity_category=EntityCategory.CONFIG, entity_category=EntityCategory.CONFIG,
native_unit_of_measurement=PERCENTAGE,
ufp_min=0, ufp_min=0,
ufp_max=100, ufp_max=100,
ufp_step=1, ufp_step=1,
ufp_required_field="feature_flags.can_optical_zoom", ufp_required_field="feature_flags.can_optical_zoom",
ufp_value="isp_settings.zoom_position", ufp_value="isp_settings.zoom_position",
ufp_set_method="set_camera_zoom", ufp_set_method="set_camera_zoom",
ufp_perm=PermRequired.WRITE,
), ),
) )
@ -96,12 +101,14 @@ LIGHT_NUMBERS: tuple[ProtectNumberEntityDescription, ...] = (
name="Motion Sensitivity", name="Motion Sensitivity",
icon="mdi:walk", icon="mdi:walk",
entity_category=EntityCategory.CONFIG, entity_category=EntityCategory.CONFIG,
native_unit_of_measurement=PERCENTAGE,
ufp_min=0, ufp_min=0,
ufp_max=100, ufp_max=100,
ufp_step=1, ufp_step=1,
ufp_required_field=None, ufp_required_field=None,
ufp_value="light_device_settings.pir_sensitivity", ufp_value="light_device_settings.pir_sensitivity",
ufp_set_method="set_sensitivity", ufp_set_method="set_sensitivity",
ufp_perm=PermRequired.WRITE,
), ),
ProtectNumberEntityDescription[Light]( ProtectNumberEntityDescription[Light](
key="duration", key="duration",
@ -115,6 +122,7 @@ LIGHT_NUMBERS: tuple[ProtectNumberEntityDescription, ...] = (
ufp_required_field=None, ufp_required_field=None,
ufp_value_fn=_get_pir_duration, ufp_value_fn=_get_pir_duration,
ufp_set_method_fn=_set_pir_duration, ufp_set_method_fn=_set_pir_duration,
ufp_perm=PermRequired.WRITE,
), ),
) )
@ -124,12 +132,14 @@ SENSE_NUMBERS: tuple[ProtectNumberEntityDescription, ...] = (
name="Motion Sensitivity", name="Motion Sensitivity",
icon="mdi:walk", icon="mdi:walk",
entity_category=EntityCategory.CONFIG, entity_category=EntityCategory.CONFIG,
native_unit_of_measurement=PERCENTAGE,
ufp_min=0, ufp_min=0,
ufp_max=100, ufp_max=100,
ufp_step=1, ufp_step=1,
ufp_required_field=None, ufp_required_field=None,
ufp_value="motion_settings.sensitivity", ufp_value="motion_settings.sensitivity",
ufp_set_method="set_motion_sensitivity", ufp_set_method="set_motion_sensitivity",
ufp_perm=PermRequired.WRITE,
), ),
) )
@ -146,6 +156,7 @@ DOORLOCK_NUMBERS: tuple[ProtectNumberEntityDescription, ...] = (
ufp_required_field=None, ufp_required_field=None,
ufp_value_fn=_get_auto_close, ufp_value_fn=_get_auto_close,
ufp_set_method_fn=_set_auto_close, ufp_set_method_fn=_set_auto_close,
ufp_perm=PermRequired.WRITE,
), ),
) )
@ -155,11 +166,13 @@ CHIME_NUMBERS: tuple[ProtectNumberEntityDescription, ...] = (
name="Volume", name="Volume",
icon="mdi:speaker", icon="mdi:speaker",
entity_category=EntityCategory.CONFIG, entity_category=EntityCategory.CONFIG,
native_unit_of_measurement=PERCENTAGE,
ufp_min=0, ufp_min=0,
ufp_max=100, ufp_max=100,
ufp_step=1, ufp_step=1,
ufp_value="volume", ufp_value="volume",
ufp_set_method="set_volume", ufp_set_method="set_volume",
ufp_perm=PermRequired.WRITE,
), ),
) )

View File

@ -38,7 +38,7 @@ from homeassistant.util.dt import utcnow
from .const import ATTR_DURATION, ATTR_MESSAGE, DOMAIN, TYPE_EMPTY_VALUE from .const import ATTR_DURATION, ATTR_MESSAGE, DOMAIN, TYPE_EMPTY_VALUE
from .data import ProtectData from .data import ProtectData
from .entity import ProtectDeviceEntity, async_all_device_entities from .entity import ProtectDeviceEntity, async_all_device_entities
from .models import ProtectSetableKeysMixin, T from .models import PermRequired, ProtectSetableKeysMixin, T
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
_KEY_LIGHT_MOTION = "light_motion" _KEY_LIGHT_MOTION = "light_motion"
@ -208,6 +208,7 @@ CAMERA_SELECTS: tuple[ProtectSelectEntityDescription, ...] = (
ufp_enum_type=RecordingMode, ufp_enum_type=RecordingMode,
ufp_value="recording_settings.mode", ufp_value="recording_settings.mode",
ufp_set_method="set_recording_mode", ufp_set_method="set_recording_mode",
ufp_perm=PermRequired.WRITE,
), ),
ProtectSelectEntityDescription( ProtectSelectEntityDescription(
key="infrared", key="infrared",
@ -219,6 +220,7 @@ CAMERA_SELECTS: tuple[ProtectSelectEntityDescription, ...] = (
ufp_enum_type=IRLEDMode, ufp_enum_type=IRLEDMode,
ufp_value="isp_settings.ir_led_mode", ufp_value="isp_settings.ir_led_mode",
ufp_set_method="set_ir_led_model", ufp_set_method="set_ir_led_model",
ufp_perm=PermRequired.WRITE,
), ),
ProtectSelectEntityDescription[Camera]( ProtectSelectEntityDescription[Camera](
key="doorbell_text", key="doorbell_text",
@ -230,6 +232,7 @@ CAMERA_SELECTS: tuple[ProtectSelectEntityDescription, ...] = (
ufp_value_fn=_get_doorbell_current, ufp_value_fn=_get_doorbell_current,
ufp_options_fn=_get_doorbell_options, ufp_options_fn=_get_doorbell_options,
ufp_set_method_fn=_set_doorbell_message, ufp_set_method_fn=_set_doorbell_message,
ufp_perm=PermRequired.WRITE,
), ),
ProtectSelectEntityDescription( ProtectSelectEntityDescription(
key="chime_type", key="chime_type",
@ -241,6 +244,7 @@ CAMERA_SELECTS: tuple[ProtectSelectEntityDescription, ...] = (
ufp_enum_type=ChimeType, ufp_enum_type=ChimeType,
ufp_value="chime_type", ufp_value="chime_type",
ufp_set_method="set_chime_type", ufp_set_method="set_chime_type",
ufp_perm=PermRequired.WRITE,
), ),
) )
@ -253,6 +257,7 @@ LIGHT_SELECTS: tuple[ProtectSelectEntityDescription, ...] = (
ufp_options=MOTION_MODE_TO_LIGHT_MODE, ufp_options=MOTION_MODE_TO_LIGHT_MODE,
ufp_value_fn=_get_light_motion_current, ufp_value_fn=_get_light_motion_current,
ufp_set_method_fn=_set_light_mode, ufp_set_method_fn=_set_light_mode,
ufp_perm=PermRequired.WRITE,
), ),
ProtectSelectEntityDescription[Light]( ProtectSelectEntityDescription[Light](
key="paired_camera", key="paired_camera",
@ -262,6 +267,7 @@ LIGHT_SELECTS: tuple[ProtectSelectEntityDescription, ...] = (
ufp_value="camera_id", ufp_value="camera_id",
ufp_options_fn=_get_paired_camera_options, ufp_options_fn=_get_paired_camera_options,
ufp_set_method_fn=_set_paired_camera, ufp_set_method_fn=_set_paired_camera,
ufp_perm=PermRequired.WRITE,
), ),
) )
@ -275,6 +281,7 @@ SENSE_SELECTS: tuple[ProtectSelectEntityDescription, ...] = (
ufp_enum_type=MountType, ufp_enum_type=MountType,
ufp_value="mount_type", ufp_value="mount_type",
ufp_set_method="set_mount_type", ufp_set_method="set_mount_type",
ufp_perm=PermRequired.WRITE,
), ),
ProtectSelectEntityDescription[Sensor]( ProtectSelectEntityDescription[Sensor](
key="paired_camera", key="paired_camera",
@ -284,6 +291,7 @@ SENSE_SELECTS: tuple[ProtectSelectEntityDescription, ...] = (
ufp_value="camera_id", ufp_value="camera_id",
ufp_options_fn=_get_paired_camera_options, ufp_options_fn=_get_paired_camera_options,
ufp_set_method_fn=_set_paired_camera, ufp_set_method_fn=_set_paired_camera,
ufp_perm=PermRequired.WRITE,
), ),
) )
@ -296,6 +304,7 @@ DOORLOCK_SELECTS: tuple[ProtectSelectEntityDescription, ...] = (
ufp_value="camera_id", ufp_value="camera_id",
ufp_options_fn=_get_paired_camera_options, ufp_options_fn=_get_paired_camera_options,
ufp_set_method_fn=_set_paired_camera, ufp_set_method_fn=_set_paired_camera,
ufp_perm=PermRequired.WRITE,
), ),
) )
@ -308,6 +317,7 @@ VIEWER_SELECTS: tuple[ProtectSelectEntityDescription, ...] = (
ufp_options_fn=_get_viewer_options, ufp_options_fn=_get_viewer_options,
ufp_value_fn=_get_viewer_current, ufp_value_fn=_get_viewer_current,
ufp_set_method_fn=_set_liveview, ufp_set_method_fn=_set_liveview,
ufp_perm=PermRequired.WRITE,
), ),
) )

View File

@ -21,7 +21,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DOMAIN from .const import DOMAIN
from .data import ProtectData from .data import ProtectData
from .entity import ProtectDeviceEntity, async_all_device_entities from .entity import ProtectDeviceEntity, async_all_device_entities
from .models import ProtectSetableKeysMixin, T from .models import PermRequired, ProtectSetableKeysMixin, T
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -56,6 +56,7 @@ CAMERA_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
entity_category=EntityCategory.CONFIG, entity_category=EntityCategory.CONFIG,
ufp_value="is_ssh_enabled", ufp_value="is_ssh_enabled",
ufp_set_method="set_ssh", ufp_set_method="set_ssh",
ufp_perm=PermRequired.WRITE,
), ),
ProtectSwitchEntityDescription( ProtectSwitchEntityDescription(
key="status_light", key="status_light",
@ -65,6 +66,7 @@ CAMERA_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
ufp_required_field="feature_flags.has_led_status", ufp_required_field="feature_flags.has_led_status",
ufp_value="led_settings.is_enabled", ufp_value="led_settings.is_enabled",
ufp_set_method="set_status_light", ufp_set_method="set_status_light",
ufp_perm=PermRequired.WRITE,
), ),
ProtectSwitchEntityDescription( ProtectSwitchEntityDescription(
key="hdr_mode", key="hdr_mode",
@ -74,6 +76,7 @@ CAMERA_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
ufp_required_field="feature_flags.has_hdr", ufp_required_field="feature_flags.has_hdr",
ufp_value="hdr_mode", ufp_value="hdr_mode",
ufp_set_method="set_hdr", ufp_set_method="set_hdr",
ufp_perm=PermRequired.WRITE,
), ),
ProtectSwitchEntityDescription[Camera]( ProtectSwitchEntityDescription[Camera](
key="high_fps", key="high_fps",
@ -83,6 +86,7 @@ CAMERA_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
ufp_required_field="feature_flags.has_highfps", ufp_required_field="feature_flags.has_highfps",
ufp_value_fn=_get_is_highfps, ufp_value_fn=_get_is_highfps,
ufp_set_method_fn=_set_highfps, ufp_set_method_fn=_set_highfps,
ufp_perm=PermRequired.WRITE,
), ),
ProtectSwitchEntityDescription( ProtectSwitchEntityDescription(
key=_KEY_PRIVACY_MODE, key=_KEY_PRIVACY_MODE,
@ -91,6 +95,7 @@ CAMERA_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
entity_category=EntityCategory.CONFIG, entity_category=EntityCategory.CONFIG,
ufp_required_field="feature_flags.has_privacy_mask", ufp_required_field="feature_flags.has_privacy_mask",
ufp_value="is_privacy_on", ufp_value="is_privacy_on",
ufp_perm=PermRequired.WRITE,
), ),
ProtectSwitchEntityDescription( ProtectSwitchEntityDescription(
key="system_sounds", key="system_sounds",
@ -100,6 +105,7 @@ CAMERA_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
ufp_required_field="feature_flags.has_speaker", ufp_required_field="feature_flags.has_speaker",
ufp_value="speaker_settings.are_system_sounds_enabled", ufp_value="speaker_settings.are_system_sounds_enabled",
ufp_set_method="set_system_sounds", ufp_set_method="set_system_sounds",
ufp_perm=PermRequired.WRITE,
), ),
ProtectSwitchEntityDescription( ProtectSwitchEntityDescription(
key="osd_name", key="osd_name",
@ -108,6 +114,7 @@ CAMERA_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
entity_category=EntityCategory.CONFIG, entity_category=EntityCategory.CONFIG,
ufp_value="osd_settings.is_name_enabled", ufp_value="osd_settings.is_name_enabled",
ufp_set_method="set_osd_name", ufp_set_method="set_osd_name",
ufp_perm=PermRequired.WRITE,
), ),
ProtectSwitchEntityDescription( ProtectSwitchEntityDescription(
key="osd_date", key="osd_date",
@ -116,6 +123,7 @@ CAMERA_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
entity_category=EntityCategory.CONFIG, entity_category=EntityCategory.CONFIG,
ufp_value="osd_settings.is_date_enabled", ufp_value="osd_settings.is_date_enabled",
ufp_set_method="set_osd_date", ufp_set_method="set_osd_date",
ufp_perm=PermRequired.WRITE,
), ),
ProtectSwitchEntityDescription( ProtectSwitchEntityDescription(
key="osd_logo", key="osd_logo",
@ -124,6 +132,7 @@ CAMERA_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
entity_category=EntityCategory.CONFIG, entity_category=EntityCategory.CONFIG,
ufp_value="osd_settings.is_logo_enabled", ufp_value="osd_settings.is_logo_enabled",
ufp_set_method="set_osd_logo", ufp_set_method="set_osd_logo",
ufp_perm=PermRequired.WRITE,
), ),
ProtectSwitchEntityDescription( ProtectSwitchEntityDescription(
key="osd_bitrate", key="osd_bitrate",
@ -132,6 +141,7 @@ CAMERA_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
entity_category=EntityCategory.CONFIG, entity_category=EntityCategory.CONFIG,
ufp_value="osd_settings.is_debug_enabled", ufp_value="osd_settings.is_debug_enabled",
ufp_set_method="set_osd_bitrate", ufp_set_method="set_osd_bitrate",
ufp_perm=PermRequired.WRITE,
), ),
ProtectSwitchEntityDescription( ProtectSwitchEntityDescription(
key="motion", key="motion",
@ -140,6 +150,7 @@ CAMERA_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
entity_category=EntityCategory.CONFIG, entity_category=EntityCategory.CONFIG,
ufp_value="recording_settings.enable_motion_detection", ufp_value="recording_settings.enable_motion_detection",
ufp_set_method="set_motion_detection", ufp_set_method="set_motion_detection",
ufp_perm=PermRequired.WRITE,
), ),
ProtectSwitchEntityDescription( ProtectSwitchEntityDescription(
key="smart_person", key="smart_person",
@ -149,6 +160,7 @@ CAMERA_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
ufp_required_field="can_detect_person", ufp_required_field="can_detect_person",
ufp_value="is_person_detection_on", ufp_value="is_person_detection_on",
ufp_set_method="set_person_detection", ufp_set_method="set_person_detection",
ufp_perm=PermRequired.WRITE,
), ),
ProtectSwitchEntityDescription( ProtectSwitchEntityDescription(
key="smart_vehicle", key="smart_vehicle",
@ -158,6 +170,7 @@ CAMERA_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
ufp_required_field="can_detect_vehicle", ufp_required_field="can_detect_vehicle",
ufp_value="is_vehicle_detection_on", ufp_value="is_vehicle_detection_on",
ufp_set_method="set_vehicle_detection", ufp_set_method="set_vehicle_detection",
ufp_perm=PermRequired.WRITE,
), ),
ProtectSwitchEntityDescription( ProtectSwitchEntityDescription(
key="smart_face", key="smart_face",
@ -167,6 +180,7 @@ CAMERA_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
ufp_required_field="can_detect_face", ufp_required_field="can_detect_face",
ufp_value="is_face_detection_on", ufp_value="is_face_detection_on",
ufp_set_method="set_face_detection", ufp_set_method="set_face_detection",
ufp_perm=PermRequired.WRITE,
), ),
ProtectSwitchEntityDescription( ProtectSwitchEntityDescription(
key="smart_package", key="smart_package",
@ -176,6 +190,7 @@ CAMERA_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
ufp_required_field="can_detect_package", ufp_required_field="can_detect_package",
ufp_value="is_package_detection_on", ufp_value="is_package_detection_on",
ufp_set_method="set_package_detection", ufp_set_method="set_package_detection",
ufp_perm=PermRequired.WRITE,
), ),
) )
@ -187,6 +202,7 @@ SENSE_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
entity_category=EntityCategory.CONFIG, entity_category=EntityCategory.CONFIG,
ufp_value="led_settings.is_enabled", ufp_value="led_settings.is_enabled",
ufp_set_method="set_status_light", ufp_set_method="set_status_light",
ufp_perm=PermRequired.WRITE,
), ),
ProtectSwitchEntityDescription( ProtectSwitchEntityDescription(
key="motion", key="motion",
@ -195,6 +211,7 @@ SENSE_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
entity_category=EntityCategory.CONFIG, entity_category=EntityCategory.CONFIG,
ufp_value="motion_settings.is_enabled", ufp_value="motion_settings.is_enabled",
ufp_set_method="set_motion_status", ufp_set_method="set_motion_status",
ufp_perm=PermRequired.WRITE,
), ),
ProtectSwitchEntityDescription( ProtectSwitchEntityDescription(
key="temperature", key="temperature",
@ -203,6 +220,7 @@ SENSE_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
entity_category=EntityCategory.CONFIG, entity_category=EntityCategory.CONFIG,
ufp_value="temperature_settings.is_enabled", ufp_value="temperature_settings.is_enabled",
ufp_set_method="set_temperature_status", ufp_set_method="set_temperature_status",
ufp_perm=PermRequired.WRITE,
), ),
ProtectSwitchEntityDescription( ProtectSwitchEntityDescription(
key="humidity", key="humidity",
@ -211,6 +229,7 @@ SENSE_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
entity_category=EntityCategory.CONFIG, entity_category=EntityCategory.CONFIG,
ufp_value="humidity_settings.is_enabled", ufp_value="humidity_settings.is_enabled",
ufp_set_method="set_humidity_status", ufp_set_method="set_humidity_status",
ufp_perm=PermRequired.WRITE,
), ),
ProtectSwitchEntityDescription( ProtectSwitchEntityDescription(
key="light", key="light",
@ -219,6 +238,7 @@ SENSE_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
entity_category=EntityCategory.CONFIG, entity_category=EntityCategory.CONFIG,
ufp_value="light_settings.is_enabled", ufp_value="light_settings.is_enabled",
ufp_set_method="set_light_status", ufp_set_method="set_light_status",
ufp_perm=PermRequired.WRITE,
), ),
ProtectSwitchEntityDescription( ProtectSwitchEntityDescription(
key="alarm", key="alarm",
@ -226,6 +246,7 @@ SENSE_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
entity_category=EntityCategory.CONFIG, entity_category=EntityCategory.CONFIG,
ufp_value="alarm_settings.is_enabled", ufp_value="alarm_settings.is_enabled",
ufp_set_method="set_alarm_status", ufp_set_method="set_alarm_status",
ufp_perm=PermRequired.WRITE,
), ),
) )
@ -239,6 +260,7 @@ LIGHT_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
entity_category=EntityCategory.CONFIG, entity_category=EntityCategory.CONFIG,
ufp_value="is_ssh_enabled", ufp_value="is_ssh_enabled",
ufp_set_method="set_ssh", ufp_set_method="set_ssh",
ufp_perm=PermRequired.WRITE,
), ),
ProtectSwitchEntityDescription( ProtectSwitchEntityDescription(
key="status_light", key="status_light",
@ -247,6 +269,7 @@ LIGHT_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
entity_category=EntityCategory.CONFIG, entity_category=EntityCategory.CONFIG,
ufp_value="light_device_settings.is_indicator_enabled", ufp_value="light_device_settings.is_indicator_enabled",
ufp_set_method="set_status_light", ufp_set_method="set_status_light",
ufp_perm=PermRequired.WRITE,
), ),
) )
@ -258,6 +281,7 @@ DOORLOCK_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
entity_category=EntityCategory.CONFIG, entity_category=EntityCategory.CONFIG,
ufp_value="led_settings.is_enabled", ufp_value="led_settings.is_enabled",
ufp_set_method="set_status_light", ufp_set_method="set_status_light",
ufp_perm=PermRequired.WRITE,
), ),
) )
@ -270,6 +294,7 @@ VIEWER_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
entity_category=EntityCategory.CONFIG, entity_category=EntityCategory.CONFIG,
ufp_value="is_ssh_enabled", ufp_value="is_ssh_enabled",
ufp_set_method="set_ssh", ufp_set_method="set_ssh",
ufp_perm=PermRequired.WRITE,
), ),
) )

View File

@ -8,6 +8,7 @@ import pytest
from pyunifiprotect.data import ( from pyunifiprotect.data import (
Camera, Camera,
Light, Light,
Permission,
RecordingMode, RecordingMode,
SmartDetectObjectType, SmartDetectObjectType,
VideoMode, VideoMode,
@ -214,6 +215,40 @@ async def camera_privacy_fixture(
Camera.__config__.validate_assignment = True Camera.__config__.validate_assignment = True
async def test_switch_setup_no_perm(
hass: HomeAssistant,
mock_entry: MockEntityFixture,
mock_light: Light,
mock_camera: Camera,
):
"""Test switch entity setup for light devices."""
light_obj = mock_light.copy()
light_obj._api = mock_entry.api
camera_obj = mock_camera.copy()
camera_obj._api = mock_entry.api
camera_obj.channels[0]._api = mock_entry.api
camera_obj.channels[1]._api = mock_entry.api
camera_obj.channels[2]._api = mock_entry.api
reset_objects(mock_entry.api.bootstrap)
mock_entry.api.bootstrap.lights = {
light_obj.id: light_obj,
}
mock_entry.api.bootstrap.cameras = {
camera_obj.id: camera_obj,
}
mock_entry.api.bootstrap.auth_user.all_permissions = [
Permission.unifi_dict_to_dict({"rawPermission": "light:read:*"})
]
await hass.config_entries.async_setup(mock_entry.entry.entry_id)
await hass.async_block_till_done()
assert_entity_counts(hass, Platform.SWITCH, 0, 0)
async def test_switch_setup_light( async def test_switch_setup_light(
hass: HomeAssistant, hass: HomeAssistant,
mock_entry: MockEntityFixture, mock_entry: MockEntityFixture,