示例#1
0
    async def async_handle_update_service(call):
        """Service handler for updating an entity."""
        if call.context.user_id:
            user = await opp.auth.async_get_user(call.context.user_id)

            if user is None:
                raise UnknownUser(
                    context=call.context,
                    permission=POLICY_CONTROL,
                    user_id=call.context.user_id,
                )

            for entity in call.data[ATTR_ENTITY_ID]:
                if not user.permissions.check_entity(entity, POLICY_CONTROL):
                    raise Unauthorized(
                        context=call.context,
                        permission=POLICY_CONTROL,
                        user_id=call.context.user_id,
                        perm_category=CAT_ENTITIES,
                    )

        tasks = [
            opp.helpers.entity_component.async_update_entity(entity)
            for entity in call.data[ATTR_ENTITY_ID]
        ]

        if tasks:
            await asyncio.wait(tasks)
示例#2
0
        async def check_permissions(call):
            """Check user permission and raise before call if unauthorized."""
            if not call.context.user_id:
                return await service_handler(call)

            user = await opp.auth.async_get_user(call.context.user_id)

            if user is None:
                raise UnknownUser(
                    context=call.context,
                    permission=POLICY_CONTROL,
                    user_id=call.context.user_id,
                )

            reg = await opp.helpers.entity_registry.async_get_registry()

            for entity in reg.entities.values():
                if entity.platform != domain:
                    continue

                if user.permissions.check_entity(entity.entity_id,
                                                 POLICY_CONTROL):
                    return await service_handler(call)

            raise Unauthorized(
                context=call.context,
                permission=POLICY_CONTROL,
                user_id=call.context.user_id,
                perm_category=CAT_ENTITIES,
            )
示例#3
0
    async def async_extract_from_service(call):
        if call.context.user_id:
            user = await opp.auth.async_get_user(call.context.user_id)
            if user is None:
                raise UnknownUser(context=call.context)
        else:
            user = None

        if call.data.get(ATTR_ENTITY_ID) == ENTITY_MATCH_ALL:
            # Return all entity_ids user has permission to control.
            return [
                entity_id for entity_id in opp.data[DATA_AMCREST][CAMERAS]
                if have_permission(user, entity_id)
            ]

        if call.data.get(ATTR_ENTITY_ID) == ENTITY_MATCH_NONE:
            return []

        call_ids = await async_extract_entity_ids(opp, call)
        entity_ids = []
        for entity_id in opp.data[DATA_AMCREST][CAMERAS]:
            if entity_id not in call_ids:
                continue
            if not have_permission(user, entity_id):
                raise Unauthorized(context=call.context,
                                   entity_id=entity_id,
                                   permission=POLICY_CONTROL)
            entity_ids.append(entity_id)
        return entity_ids
示例#4
0
    async def admin_handler(call: ServiceCall) -> None:
        if call.context.user_id:
            user = await opp.auth.async_get_user(call.context.user_id)
            if user is None:
                raise UnknownUser(context=call.context)
            if not user.is_admin:
                raise Unauthorized(context=call.context)

        result = opp.async_run_job(service_func, call)
        if result is not None:
            await result
示例#5
0
async def entity_service_call(
    opp: OpenPeerPower,
    platforms: Iterable[EntityPlatform],
    func: str | Callable[..., Any],
    call: ServiceCall,
    required_features: Iterable[int] | None = None,
) -> None:
    """Handle an entity service call.

    Calls all platforms simultaneously.
    """
    if call.context.user_id:
        user = await opp.auth.async_get_user(call.context.user_id)
        if user is None:
            raise UnknownUser(context=call.context)
        entity_perms: None | (Callable[[str, str],
                                       bool]) = user.permissions.check_entity
    else:
        entity_perms = None

    target_all_entities = call.data.get(ATTR_ENTITY_ID) == ENTITY_MATCH_ALL

    if target_all_entities:
        referenced: SelectedEntities | None = None
        all_referenced: set[str] | None = None
    else:
        # A set of entities we're trying to target.
        referenced = await async_extract_referenced_entity_ids(opp, call, True)
        all_referenced = referenced.referenced | referenced.indirectly_referenced

    # If the service function is a string, we'll pass it the service call data
    if isinstance(func, str):
        data: dict | ServiceCall = {
            key: val
            for key, val in call.data.items()
            if key not in cv.ENTITY_SERVICE_FIELDS
        }
    # If the service function is not a string, we pass the service call
    else:
        data = call

    # Check the permissions

    # A list with entities to call the service on.
    entity_candidates: list[Entity] = []

    if entity_perms is None:
        for platform in platforms:
            if target_all_entities:
                entity_candidates.extend(platform.entities.values())
            else:
                assert all_referenced is not None
                entity_candidates.extend([
                    entity for entity in platform.entities.values()
                    if entity.entity_id in all_referenced
                ])

    elif target_all_entities:
        # If we target all entities, we will select all entities the user
        # is allowed to control.
        for platform in platforms:
            entity_candidates.extend([
                entity for entity in platform.entities.values()
                if entity_perms(entity.entity_id, POLICY_CONTROL)
            ])

    else:
        assert all_referenced is not None

        for platform in platforms:
            platform_entities = []
            for entity in platform.entities.values():

                if entity.entity_id not in all_referenced:
                    continue

                if not entity_perms(entity.entity_id, POLICY_CONTROL):
                    raise Unauthorized(
                        context=call.context,
                        entity_id=entity.entity_id,
                        permission=POLICY_CONTROL,
                    )

                platform_entities.append(entity)

            entity_candidates.extend(platform_entities)

    if not target_all_entities:
        assert referenced is not None

        # Only report on explicit referenced entities
        missing = set(referenced.referenced)

        for entity in entity_candidates:
            missing.discard(entity.entity_id)

        referenced.log_missing(missing)

    entities = []

    for entity in entity_candidates:
        if not entity.available:
            continue

        # Skip entities that don't have the required feature.
        if required_features is not None and (
                entity.supported_features is None or
                not any(entity.supported_features & feature_set == feature_set
                        for feature_set in required_features)):
            continue

        entities.append(entity)

    if not entities:
        return

    done, pending = await asyncio.wait([
        asyncio.create_task(
            entity.async_request_call(
                _handle_entity_call(opp, entity, func, data, call.context)))
        for entity in entities
    ])
    assert not pending
    for future in done:
        future.result()  # pop exception if have

    tasks = []

    for entity in entities:
        if not entity.should_poll:
            continue

        # Context expires if the turn on commands took a long time.
        # Set context again so it's there when we update
        entity.async_set_context(call.context)
        tasks.append(asyncio.create_task(entity.async_update_op_state(True)))

    if tasks:
        done, pending = await asyncio.wait(tasks)
        assert not pending
        for future in done:
            future.result()  # pop exception if have
示例#6
0
async def entity_service_call(opp,
                              platforms,
                              func,
                              call,
                              required_features=None):
    """Handle an entity service call.

    Calls all platforms simultaneously.
    """
    if call.context.user_id:
        user = await opp.auth.async_get_user(call.context.user_id)
        if user is None:
            raise UnknownUser(context=call.context)
        entity_perms = user.permissions.check_entity
    else:
        entity_perms = None

    target_all_entities = call.data.get(ATTR_ENTITY_ID) == ENTITY_MATCH_ALL

    if not target_all_entities:
        # A set of entities we're trying to target.
        entity_ids = await async_extract_entity_ids(opp, call, True)

    # If the service function is a string, we'll pass it the service call data
    if isinstance(func, str):
        data = {
            key: val
            for key, val in call.data.items()
            if key not in cv.ENTITY_SERVICE_FIELDS
        }
    # If the service function is not a string, we pass the service call
    else:
        data = call

    # Check the permissions

    # A list with entities to call the service on.
    entity_candidates = []

    if entity_perms is None:
        for platform in platforms:
            if target_all_entities:
                entity_candidates.extend(platform.entities.values())
            else:
                entity_candidates.extend([
                    entity for entity in platform.entities.values()
                    if entity.entity_id in entity_ids
                ])

    elif target_all_entities:
        # If we target all entities, we will select all entities the user
        # is allowed to control.
        for platform in platforms:
            entity_candidates.extend([
                entity for entity in platform.entities.values()
                if entity_perms(entity.entity_id, POLICY_CONTROL)
            ])

    else:
        for platform in platforms:
            platform_entities = []
            for entity in platform.entities.values():

                if entity.entity_id not in entity_ids:
                    continue

                if not entity_perms(entity.entity_id, POLICY_CONTROL):
                    raise Unauthorized(
                        context=call.context,
                        entity_id=entity.entity_id,
                        permission=POLICY_CONTROL,
                    )

                platform_entities.append(entity)

            entity_candidates.extend(platform_entities)

    if not target_all_entities:
        for entity in entity_candidates:
            entity_ids.remove(entity.entity_id)

        if entity_ids:
            _LOGGER.warning("Unable to find referenced entities %s",
                            ", ".join(sorted(entity_ids)))

    entities = []

    for entity in entity_candidates:
        if not entity.available:
            continue

        # Skip entities that don't have the required feature.
        if required_features is not None and not any(
                entity.supported_features & feature_set
                for feature_set in required_features):
            continue

        entities.append(entity)

    if not entities:
        return

    done, pending = await asyncio.wait([
        entity.async_request_call(
            _handle_entity_call(opp, entity, func, data, call.context))
        for entity in entities
    ])
    assert not pending
    for future in done:
        future.result()  # pop exception if have

    tasks = []

    for entity in entities:
        if not entity.should_poll:
            continue

        # Context expires if the turn on commands took a long time.
        # Set context again so it's there when we update
        entity.async_set_context(call.context)
        tasks.append(entity.async_update_op_state(True))

    if tasks:
        done, pending = await asyncio.wait(tasks)
        assert not pending
        for future in done:
            future.result()  # pop exception if have