Пример #1
0
    async def _handle(self, data: Dict[str, Any]) -> None:
        """Handles message from websocket."""

        server_log.debug(f"Received ws: {data}")

        op = data["op"]
        if op == Opcode.HEARTBEAT.value:
            self._last_hb = time.time()
            await self.notify(opcode=Opcode.HEARTBEAT_ACK)

        elif op == Opcode.IDENTIFY.value:
            try:
                token = Token.from_string(
                    data["d"]["token"], self._emitter._app["pg_conn"]
                )
                if not await token.verify():  # ValueError possible
                    raise ValueError
            except (ValueError, RuntimeError):
                await self.notify(opcode=Opcode.INVALIDATE_SESSION)
                await self.close(code=CloseCode.BAD_TOKEN)

                return

            self.user_id = token.user_id

            await self._emitter.add_listener(self)
Пример #2
0
async def send_message(
    targets: List[str],
    text: str,
    config: Dict[str, Any],
    loop: Optional[asyncio.AbstractEventLoop] = None,
) -> None:
    if loop is None:
        loop = asyncio.get_running_loop()

    server_log.debug(f"Sending email to {targets}")

    config = config["email-confirmation"]["smtp"]

    for i in ("host", "login", "password"):
        if config.get(i) is None:
            server_log.info(
                f"Bad configuration, unable to send the following email:\n{text}"
            )
            return

    await loop.run_in_executor(
        None,
        _send_message,
        targets,
        text,
        config["host"],
        config["login"],
        config["password"],
    )
Пример #3
0
async def match_info_validator(
    req: web.Request, handler: HandlerType
) -> web.Response:
    req["match_info"] = {}

    for key, value in req.match_info.items():
        if not key.endswith("_id"):
            continue

        if value == "@me":
            try:
                token_header = req.headers["Authorization"]
            except KeyError:
                raise web.HTTPUnauthorized(
                    reason="No access token passed, unable to decode @me"
                )

            try:
                # not doing propper token checks here, not even token structure
                if token_header.startswith("Bearer "):
                    token_header = token_header[7:]

                value = Token.decode_user_id(token_header.partition(".")[0])
            except (ValueError, binascii.Error):
                raise web.HTTPUnauthorized(reason="Bad access token passed")

        try:
            req["match_info"][key] = await converters.ID().convert(
                value, req.app
            )
        except (converters.ConvertError, converters.CheckError) as e:
            server_log.debug(f"match_validator: {value}: {e}")
            raise web.HTTPNotFound

    return await handler(req)
Пример #4
0
        async def wrapper(req: web.Request) -> web.StreamResponse:
            content_type_matches = False
            for content_type in content_types:
                if content_type.value == req.content_type:
                    content_type_matches = True
                    break

            if not content_type_matches:
                return web.json_response(
                    {
                        "message":
                        f"Bad content type. Expected: {[c.value for c in content_types]}"
                    },
                    status=400,
                )

            if content_type == ContentType.JSON:
                try:
                    query = await req.json()
                except json.JSONDecodeError as e:
                    return web.json_response(
                        {"message": f"Error parsing json from body: {e}"},
                        status=400,
                    )
            elif content_type in (
                    ContentType.URLENCODED,
                    ContentType.FORM_DATA,
            ):
                query = await req.post()
            else:
                server_log.debug(
                    f"body_params: unknown content type: {content_type}")

            if unique:
                repeating = get_repeating(query.keys())

                if repeating is not None:
                    if json_response:
                        return web.json_response(
                            {repeating: "Repeats in body"}, status=400)

                    raise web.HTTPBadRequest(
                        reason=f"{repeating}: Repeats in body")

            req["body"] = req.get("body", {})

            try:
                req["body"].update(
                    **await converters.convert_map(params, query, req.app))
            except converters.ConvertError as e:
                return e.to_bad_request(json_response)

            return await endpoint(req)
Пример #5
0
    async def notify_everyone(self, event: GlobalEvent) -> None:
        """Discpatches event for all connected users."""

        server_log.debug(f"Notifying everyone: {event}")

        to_close = []

        async with self._lock:
            for listeners in self._listeners.values():
                for listener in listeners:
                    if not await listener.event_notify(event):
                        to_close.append(listener)

        for listener in to_close:
            await listener.close()
Пример #6
0
    async def notify_channel(self, event: LocalEvent) -> None:
        """Dispatches event for all users in channel of event."""

        server_log.debug(f"Notifying channel {event.channel_id}: {event}")

        to_close = []

        async with self._lock:
            for user_id in self._channels.get(event.channel_id, ()):
                for listener in self._listeners[user_id]:
                    if not await listener.event_notify(event):
                        to_close.append(listener)

        for listener in to_close:
            await listener.close()
Пример #7
0
    async def notify(
        self, *, opcode: Opcode, data: Optional[Any] = None
    ) -> bool:
        """Sends data to websocket."""

        try:
            if data is None:
                await self.ws.send_json({"op": opcode.value})
            else:
                await self.ws.send_json({"op": opcode.value, "d": data})
        except RuntimeError:  # ws closed (dirty)
            server_log.debug("WS closed by user (dirty)")

            return False

        return True
Пример #8
0
    async def event_notify(self, event: Event) -> bool:
        """Sends dispatch message to websocket with event payload."""

        try:
            await self.ws.send_json(
                {
                    "op": Opcode.DISPATCH.value,
                    "d": event.payload,
                    "t": event.name,
                }
            )
        except RuntimeError:  # ws closed (dirty)
            server_log.debug("WS closed by user (dirty)")

            return False

        return True
Пример #9
0
    async def notify_channels(self, event: OuterEvent) -> None:
        """
        Dispatches event for all users sharing channel with user of event.
        """

        server_log.debug(f"Notifying user {event.user_id} channels: {event}")

        to_close = []

        async with self._lock:
            for channel_id in self._users.get(event.user_id, ()):
                for user_id in self._channels[channel_id]:
                    for listener in self._listeners[user_id]:
                        if not await listener.event_notify(event):
                            to_close.append(listener)

        for listener in to_close:
            await listener.close()
Пример #10
0
    async def _check_hb(self) -> None:
        """
        A task that checks for user heartbeat responses every
        HEARTBEAT_INTERVAL milliseconds with one tenth precision.
        """

        interval = HEARTBEAT_INTERVAL / 1000
        response_error_treshold = interval / 10

        while not self.ws.closed:
            await asyncio.sleep(interval)

            response_error = (time.time() - self._last_hb) - interval

            if response_error > response_error_treshold:
                server_log.debug(f"Heartbeat: expired for user {self.user_id}")

                break

        server_log.debug(f"Heartbeat: closing listener {self}")

        await self.close()
Пример #11
0
async def convert_map(
    converters: typing.Dict[str, Converter],
    query: typing.Mapping[str, InputType],
    app: aiohttp.web.Application,
    location: str = "body",
) -> typing.Dict[str, typing.Any]:
    """
    Converts mapping of elements generating new map with the same keys.
    Supports recursive maps/lists conversion.

    Propperly indicates problematic value in case of error with ConvertError.

    Known issues:
        Allows type implicit type casts, e.g: 1 can be treated as string
    """

    result = {}

    if not isinstance(query, typing.Mapping):
        raise ConvertError(f"Expected dict, got {type(query).__name__}", "")

    for name, converter in converters.items():
        if name not in query:
            try:
                # not converting default value to type, be careful
                result[name] = converter.get_default()
                continue
            except KeyError:  # no default value
                server_log.debug(f"Parameter {name}: missing")

                raise ConvertError(f"Missing from {location}", name)
        try:
            result[name] = await converter.convert(query[name], app)
        except ConvertError as e:
            e.update_parameter(name)

            if type(e) is BadArgument:
                server_log.debug(
                    f"Bad argument for parameter {e.parameter}: {e}")

                raise ConvertError(
                    f"Should be of type {converter} in {location}",
                    e.parameter)
            elif type(e) is CheckError:
                server_log.debug(f"Parameter {name}: check failed: {e}")

                raise ConvertError(f"Check failed in {location}: {e}",
                                   e.parameter)
            else:
                raise ConvertError(str(e), e.parameter)

    return result