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)
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, )
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
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
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
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