async def get(self, request): """Retrieve the pairing QRCode image.""" if not request.query_string: raise Unauthorized() entry_id, secret = request.query_string.split("-") if (entry_id not in request.app["opp"].data[DOMAIN] or secret != request.app["opp"].data[DOMAIN][entry_id] [HOMEKIT_PAIRING_QR_SECRET]): raise Unauthorized() return web.Response( body=request.app["opp"].data[DOMAIN][entry_id][HOMEKIT_PAIRING_QR], content_type="image/svg+xml", )
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 websocket_admin_change_password(opp, connection, msg): """Change password of any user.""" if not connection.user.is_owner: raise Unauthorized(context=connection.context(msg)) user = await opp.auth.async_get_user(msg["user_id"]) if user is None: connection.send_error(msg["id"], "user_not_found", "User not found") return provider = auth_op.async_get_provider(opp) username = None for credential in user.credentials: if credential.auth_provider_type == provider.type: username = credential.data["username"] break if username is None: connection.send_error( msg["id"], "credentials_not_found", "Credentials not found" ) return try: await provider.async_change_password(username, msg["password"]) connection.send_result(msg["id"]) except auth_op.InvalidUser: connection.send_error( msg["id"], "credentials_not_found", "Credentials not found" ) return
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 post(self, request, event_type): """Fire events.""" if not request["opp_user"].is_admin: raise Unauthorized() body = await request.text() try: event_data = json.loads(body) if body else None except ValueError: return self.json_message("Event data should be valid JSON.", HTTP_BAD_REQUEST) if event_data is not None and not isinstance(event_data, dict): return self.json_message("Event data should be a JSON object", HTTP_BAD_REQUEST) # Special case handling for event STATE_CHANGED # We will try to convert state dicts back to State objects if event_type == op.EVENT_STATE_CHANGED and event_data: for key in ("old_state", "new_state"): state = op.State.from_dict(event_data.get(key)) if state: event_data[key] = state request.app["opp"].bus.async_fire(event_type, event_data, op.EventOrigin.remote, self.context(request)) return self.json_message(f"Event {event_type} fired.")
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 post(self, request, flow_id): """Handle a POST request.""" if not request["opp_user"].is_admin: raise Unauthorized(perm_category=CAT_CONFIG_ENTRIES, permission="edit") # pylint: disable=no-value-for-parameter return await super().post(request, flow_id)
def delete(self, request, entity_id): """Remove entity.""" if not request["opp_user"].is_admin: raise Unauthorized(entity_id=entity_id) if request.app["opp"].states.async_remove(entity_id): return self.json_message("Entity removed.") return self.json_message("Entity not found.", HTTP_NOT_FOUND)
async def post(self, request, entity_id): """Update state of entity.""" if not request["opp_user"].is_admin: raise Unauthorized(entity_id=entity_id) opp = request.app["opp"] try: data = await request.json() except ValueError: return self.json_message("Invalid JSON specified.", HTTP_BAD_REQUEST) new_state = data.get("state") if new_state is None: return self.json_message("No state specified.", HTTP_BAD_REQUEST) attributes = data.get("attributes") force_update = data.get("force_update", False) is_new_state = opp.states.get(entity_id) is None # Write state opp.states.async_set(entity_id, new_state, attributes, force_update, self.context(request)) # Read the state back for our response status_code = HTTP_CREATED if is_new_state else 200 resp = self.json(opp.states.get(entity_id), status_code) resp.headers.add("Location", URL_API_STATES_ENTITY.format(entity_id)) return resp
def with_admin(opp, connection, msg): """Check admin and call function.""" user = connection.user if user is None or not user.is_admin: raise Unauthorized() func(opp, connection, msg)
def with_admin(opp: OpenPeerPower, connection: ActiveConnection, msg: dict[str, Any]) -> None: """Check admin and call function.""" user = connection.user if user is None or not user.is_admin: raise Unauthorized() func(opp, connection, msg)
def get(self, request, entity_id): """Retrieve state of entity.""" user = request["opp_user"] if not user.permissions.check_entity(entity_id, POLICY_READ): raise Unauthorized(entity_id=entity_id) state = request.app["opp"].states.get(entity_id) if state: return self.json(state) return self.json_message("Entity not found.", HTTP_NOT_FOUND)
async def post(self, request): """Handle a POST request. handler in request is entry_id. """ if not request["opp_user"].is_admin: raise Unauthorized(perm_category=CAT_CONFIG_ENTRIES, permission=POLICY_EDIT) # pylint: disable=no-value-for-parameter return await super().post(request)
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 post(self, request): """Render a template.""" if not request["opp_user"].is_admin: raise Unauthorized() try: data = await request.json() tpl = template.Template(data["template"], request.app["opp"]) return tpl.async_render(data.get("variables")) except (ValueError, TemplateError) as ex: return self.json_message(f"Error rendering template: {ex}", HTTP_BAD_REQUEST)
async def delete(self, request, entry_id): """Delete a config entry.""" if not request["opp_user"].is_admin: raise Unauthorized(config_entry_id=entry_id, permission="remove") opp = request.app["opp"] try: result = await opp.config_entries.async_remove(entry_id) except config_entries.UnknownEntry: return self.json_message("Invalid entry specified", 404) return self.json(result)
async def post(self, request, entry_id): """Reload a config entry.""" if not request["opp_user"].is_admin: raise Unauthorized(config_entry_id=entry_id, permission="remove") opp = request.app["opp"] try: result = await opp.config_entries.async_reload(entry_id) except config_entries.OperationNotAllowed: return self.json_message("Entry cannot be reloaded", HTTP_FORBIDDEN) except config_entries.UnknownEntry: return self.json_message("Invalid entry specified", HTTP_NOT_FOUND) return self.json({"require_restart": not result})
def handle_entity_source(opp: OpenPeerPower, connection: ActiveConnection, msg: dict[str, Any]) -> None: """Handle entity source command.""" raw_sources = entity.entity_sources(opp) entity_perm = connection.user.permissions.check_entity if "entity_id" not in msg: if connection.user.permissions.access_all_entities("read"): sources = raw_sources else: sources = { entity_id: source for entity_id, source in raw_sources.items() if entity_perm(entity_id, "read") } connection.send_message(messages.result_message(msg["id"], sources)) return sources = {} for entity_id in msg["entity_id"]: if not entity_perm(entity_id, "read"): raise Unauthorized( context=connection.context(msg), permission=POLICY_READ, perm_category=CAT_ENTITIES, ) source = raw_sources.get(entity_id) if source is None: connection.send_error(msg["id"], ERR_NOT_FOUND, "Entity not found") return sources[entity_id] = source connection.send_result(msg["id"], sources)
async def post( self, request: web.Request, config_entry_id: str, node_id: str ) -> web.Response: """Handle upload.""" if not request["opp_user"].is_admin: raise Unauthorized() opp = request.app["opp"] if config_entry_id not in opp.data[DOMAIN]: raise web_exceptions.HTTPBadRequest entry = opp.config_entries.async_get_entry(config_entry_id) client = opp.data[DOMAIN][config_entry_id][DATA_CLIENT] node = client.driver.controller.nodes.get(int(node_id)) if not node: raise web_exceptions.HTTPNotFound # Increase max payload request._client_max_size = 1024 * 1024 * 10 # pylint: disable=protected-access data = await request.post() if "file" not in data or not isinstance(data["file"], web_request.FileField): raise web_exceptions.HTTPBadRequest uploaded_file: web_request.FileField = data["file"] try: await begin_firmware_update( entry.data[CONF_URL], node, uploaded_file.filename, await opp.async_add_executor_job(uploaded_file.file.read), async_get_clientsession(opp), ) except BaseZwaveJSServerError as err: raise web_exceptions.HTTPBadRequest from err return self.json(None)
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 get(self, request, flow_id): """Get the current state of a data_entry_flow.""" if not request["opp_user"].is_admin: raise Unauthorized(perm_category=CAT_CONFIG_ENTRIES, permission="edit") return await super().get(request, flow_id)
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
async def get(self, request): """Provide a streaming interface for the event bus.""" if not request["opp_user"].is_admin: raise Unauthorized() opp = request.app["opp"] stop_obj = object() to_write = asyncio.Queue() restrict = request.query.get("restrict") if restrict: restrict = restrict.split(",") + [EVENT_OPENPEERPOWER_STOP] async def forward_events(event): """Forward events to the open request.""" if event.event_type == EVENT_TIME_CHANGED: return if restrict and event.event_type not in restrict: return _LOGGER.debug("STREAM %s FORWARDING %s", id(stop_obj), event) if event.event_type == EVENT_OPENPEERPOWER_STOP: data = stop_obj else: data = json.dumps(event, cls=JSONEncoder) await to_write.put(data) response = web.StreamResponse() response.content_type = "text/event-stream" await response.prepare(request) unsub_stream = opp.bus.async_listen(MATCH_ALL, forward_events) try: _LOGGER.debug("STREAM %s ATTACHED", id(stop_obj)) # Fire off one message so browsers fire open event right away await to_write.put(STREAM_PING_PAYLOAD) while True: try: with async_timeout.timeout(STREAM_PING_INTERVAL): payload = await to_write.get() if payload is stop_obj: break msg = f"data: {payload}\n\n" _LOGGER.debug("STREAM %s WRITING %s", id(stop_obj), msg.strip()) await response.write(msg.encode("UTF-8")) except asyncio.TimeoutError: await to_write.put(STREAM_PING_PAYLOAD) except asyncio.CancelledError: _LOGGER.debug("STREAM %s ABORT", id(stop_obj)) finally: _LOGGER.debug("STREAM %s RESPONSE CLOSED", id(stop_obj)) unsub_stream() return response
async def get(self, request): """Retrieve API error log.""" if not request["opp_user"].is_admin: raise Unauthorized() return web.FileResponse(request.app["opp"].data[DATA_LOGGING])