Beispiel #1
0
    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",
        )
Beispiel #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,
            )
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
Beispiel #4
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
Beispiel #5
0
    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)
Beispiel #8
0
 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)
Beispiel #9
0
    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)
Beispiel #11
0
    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)
Beispiel #12
0
    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)
Beispiel #13
0
    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)
Beispiel #14
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
Beispiel #15
0
 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)
Beispiel #17
0
    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})
Beispiel #18
0
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)
Beispiel #19
0
    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)
Beispiel #20
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
    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)
Beispiel #22
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
Beispiel #23
0
    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
Beispiel #24
0
 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])