Exemplo n.º 1
0
def test_base_modify_config():
    user = User()
    user.modify_config("invalid", 80, "invalid", "http://", False)
    with pytest.raises(ConnectError):
        user.lists()
    assert isinstance(User().lists(), Contents)
    user.modify_config("invalid", 80, "invalid", "http://", True)
    with pytest.raises(ConnectError):
        user.lists()
        User().lists()
    assert user.modify_config("localhost", 8008, admin_access_token, "http://")
    assert isinstance(user.lists(), Contents)
    assert isinstance(User().lists(), Contents)
class Management(Admin):
    """
    Wapper class for admin API for server management

    Reference:
        https://github.com/matrix-org/synapse/blob/develop/docs/admin_api/server_notices.md
        https://github.com/matrix-org/synapse/blob/develop/docs/admin_api/version_api.md
        https://github.com/matrix-org/synapse/blob/develop/docs/admin_api/register_api.md
        https://github.com/matrix-org/synapse/blob/develop/docs/admin_api/purge_history_api.md
        https://github.com/matrix-org/synapse/blob/develop/docs/admin_api/delete_group.md
        https://github.com/matrix-org/synapse/blob/develop/docs/admin_api/event_reports.md
    """
    class SynapseVersion(NamedTuple):
        server: str
        python: str

    def __init__(self,
                 server_addr=None,
                 server_port=443,
                 access_token=None,
                 server_protocol=None,
                 suppress_exception=False):
        super().__init__(server_addr, server_port, access_token,
                         server_protocol, suppress_exception)
        if server_addr is not None and access_token is not None:
            self.user = User(server_addr, server_port, access_token,
                             server_protocol, suppress_exception)
            self.client = ClientAPI(server_addr, server_port, access_token,
                                    server_protocol, suppress_exception)
        else:
            self.user = User()
            self.client = ClientAPI()
        self._create_alias()

    def _create_alias(self) -> None:
        """Create alias for some methods"""
        self.event_report = self.specific_event_report

    def _prepare_attachment(
            self, attachment: Union[str, bytes]) -> Tuple[str, str, str]:
        """Upload a media and return its information

        Args:
            attachment (Union[str, bytes]): the media, either in str or bytes

        Returns:
            Tuple[str, str, str]: media id, message type defined in matrix, file name  # noqa: E501
        """
        mediaid, mime = self.client.client_upload_attachment(attachment)
        if isinstance(attachment, bytes):
            filename = "unknown"
        else:
            filename = os.path.basename(attachment)
        if "image/" in mime:
            msgtype = "m.image"
        elif "video/" in mime:
            msgtype = "m.video"
        elif "audio/" in mime:
            msgtype = "m.audio"
        else:
            msgtype = "m.file"

        return mediaid, msgtype, filename

    def announce(self,
                 userid: Union[str, bool],
                 announcement: str = None,
                 attachment: Union[str, bytes] = None) -> Union[str, list]:
        """Send an announcement to a user or a batch of users

        Args:
            userid (Union[str, bool]): user you want to send them annoucement or set True to send the announcement to all users  # noqa: E501
            announcement (str, optional): a text-based announcement. Defaults to None.
            attachment (Union[str, bytes], optional): the media you want to send or to attach. Either provide a path to the file or the stream. Defaults to None.

        Returns:
            Union[str, list]: if either announcement or attachment is specified, return the event id
                if both announcement and attachment are specified, return a list which contains the event id for the attachment and the text
        """
        if isinstance(userid, str):
            invoking_method = self._announce
        elif isinstance(userid, bool) and userid:
            invoking_method = self.announce_all
            raise ValueError("Argument must be a non-empty str or True")
        if announcement is None and attachment is None:
            raise ValueError("You must at least specify"
                             "announcement or attachment")
        userid = self.validate_username(userid)
        if attachment is not None:
            mediaid, msgtype, filename = self._prepare_attachment(attachment)
            data = {
                "user_id": userid,
                "content": {
                    "body": filename,
                    "msgtype": msgtype,
                    "url": mediaid
                }
            }
            if announcement is not None:
                event_ids = []
                event_ids.append(invoking_method(userid, "", data))
                event_ids.append(invoking_method(userid, announcement))
                return event_ids
            else:
                announcement = ""
        elif announcement is not None:
            data = None
        return invoking_method(userid, announcement, data)

    def _announce(self,
                  userid: str,
                  announcement: str,
                  data: dict = None) -> str:
        """Send an announcement to a specific user

        https://github.com/matrix-org/synapse/blob/develop/docs/admin_api/server_notices.md#server-notices

        Args:
            userid (str): the user that the announcement should be delivered to
            announcement (str): the announcement

        Returns:
            str: event id of the announcement
        """
        userid = self.validate_username(userid)
        if data is None:
            data = {
                "user_id": userid,
                "content": {
                    "msgtype": "m.text",
                    "body": announcement
                }
            }

        resp = self.connection.request("POST",
                                       self.admin_patterns(
                                           "/send_server_notice", 1),
                                       json=data)
        data = resp.json()
        if resp.status_code == 200:
            return data["event_id"]
        else:
            if self.suppress_exception:
                return False, data["errcode"], data["error"]
            else:
                raise SynapseException(data["errcode"], data["error"])

    def announce_all(self, announcement: str, data: dict = None) -> dict:
        """Send an announcement to all local users

        Args:
            announcement (str): the announcement

        Returns:
            dict: a dict with user id as key and the event id as value
        """
        events = {}
        for user in self.user.lists():
            events[user["name"]] = self._announce(user["name"], announcement,
                                                  data)
        return events

    def version(self) -> SynapseVersion:
        """Get the server and python version

        https://github.com/matrix-org/synapse/blob/develop/docs/admin_api/version_api.md#version-api

        Returns:
            SynapseVersion: server: server version, python: python version  # noqa: E501
        """
        resp = self.connection.request(
            "GET", self.admin_patterns("/server_version", 1))
        data = resp.json()
        return Management.SynapseVersion(data["server_version"],
                                         data["python_version"])

    def purge_history(self,
                      roomid: str,
                      event_id_ts: Union[str, int],
                      include_local_event: bool = False) -> str:
        """Purge old events in a room from database

        https://github.com/matrix-org/synapse/blob/develop/docs/admin_api/purge_history_api.md#purge-history-api

        Args:
            roomid (str): the room you want to perform the purging
            event_id_ts (Union[str, int]): purge up to an event id or timestamp
            include_local_event (bool, optional): whether to purge local events. Defaults to False. # noqa: E501

        Returns:
            str: purge id
        """
        roomid = self.validate_room(roomid)
        data = {"delete_local_events": include_local_event}
        if isinstance(event_id_ts, str):
            data["purge_up_to_event_id"] = event_id_ts
        elif isinstance(event_id_ts, int):
            data["purge_up_to_ts"] = event_id_ts
        resp = self.connection.request("POST",
                                       self.admin_patterns(
                                           f"/purge_history/{roomid}", 1),
                                       json=data)
        data = resp.json()
        if resp.status_code == 200:
            return data["purge_id"]
        else:
            if self.suppress_exception:
                return False, data["errcode"], data["error"]
            else:
                raise SynapseException(data["errcode"], data["error"])

    def purge_history_status(self, purge_id: str) -> str:
        """Query the purge job status

        https://github.com/matrix-org/synapse/blob/develop/docs/admin_api/purge_history_api.md#purge-status-query

        Args:
            purge_id (str): the purge id you want to query

        Returns:
            str: the status of the purge job
        """
        resp = self.connection.request(
            "GET", self.admin_patterns(f"/purge_history_status/{purge_id}", 1))
        data = resp.json()
        if resp.status_code == 200:
            return data["status"]
        else:
            if self.suppress_exception:
                return False, data["errcode"], data["error"]
            else:
                raise SynapseException(data["errcode"], data["error"])

    def event_reports(self,
                      limit: int = 100,
                      _from: int = 0,
                      recent_first: bool = True,
                      userid: str = None,
                      roomid: str = None) -> Contents:
        """Query all reported events

        https://github.com/matrix-org/synapse/blob/develop/docs/admin_api/event_reports.md#show-reported-events

        Args:
            limit (int, optional): equivalent to "limit". Defaults to 100.
            _from (int, optional): equivalent to "from". Defaults to 0.
            recent_first (bool, optional): equivalent to "dir". True as "b" False as "f" Defaults to True. # noqa: E501
            userid (str, optional): equivalent to "user_id". Defaults to None.
            roomid (str, optional): equivalent to "room_id". Defaults to None.

        Returns:
            Contents: list of reported events
        """
        if recent_first:
            recent_first = "b"
        else:
            recent_first = "f"
        optional_str = ""
        if userid is not None:
            optional_str += f"&user_id={userid}"
        if roomid is not None:
            roomid = self.validate_room(roomid)
            optional_str += f"&room_id={roomid}"

        resp = self.connection.request(
            "GET",
            self.admin_patterns(
                f"/event_reports?from={_from}"
                f"&limit={limit}&dir={recent_first}"
                f"{optional_str}", 1))
        data = resp.json()
        if data["total"] == 0:
            return None
        return Contents(data["event_reports"], data["total"],
                        data.get("next_token", None))

    def specific_event_report(self, reportid: int) -> dict:
        """Query specific event report

        https://github.com/matrix-org/synapse/blob/develop/docs/admin_api/event_reports.md#show-details-of-a-specific-event-report

        Args:
            reportid (int): the report id

        Returns:
            dict: a dict with all details of the report
        """
        resp = self.connection.request(
            "GET", self.admin_patterns(f"/event_reports/{reportid}", 1))
        data = resp.json()
        if resp.status_code == 200:
            return data
        else:
            if self.suppress_exception:
                return False, data["errcode"], data["error"]
            else:
                raise SynapseException(data["errcode"], data["error"])

    def delete_group(self, groupid: str) -> bool:
        """Delete a local group

        https://github.com/matrix-org/synapse/blob/develop/docs/admin_api/delete_group.md#delete-a-local-group

        Args:
            groupid (str): the group id you want to delete

        Returns:
            bool: the deletion is successful or not
        """
        groupid = self.validate_group(groupid)
        resp = self.connection.request(
            "POST", self.admin_patterns(f"/delete_group/{groupid}", 1))
        data = resp.json()
        if resp.status_code == 200:
            return data == {}
        else:
            if self.suppress_exception:
                return False, data["errcode"], data["error"]
            else:
                raise SynapseException(data["errcode"], data["error"])

    def background_updates_get(self) -> Tuple[bool, dict]:
        """Get the current status of background updates

        https://github.com/matrix-org/synapse/blob/develop/docs/usage/administration/admin_api/background_updates.md#status

        Returns:
            Tuple[bool, dict]: whether background updates is enabled, details of current updates  # noqa: E501
        """
        resp = self.connection.request(
            "GET", self.admin_patterns("/background_updates/status", 1))
        data = resp.json()
        if resp.status_code == 200:
            return data["enabled"], data["current_updates"]
        else:
            if self.suppress_exception:
                return False, data["errcode"], data["error"]
            else:
                raise SynapseException(data["errcode"], data["error"])

    def background_updates_set(self, enabled: bool) -> bool:
        """Pause or resume background updates

        https://github.com/matrix-org/synapse/blob/develop/docs/usage/administration/admin_api/background_updates.md#enabled

        Args:
            enabled (bool, optional): True to enable, False to disable background updates.  # noqa: E501

        Returns:
            bool: whether the background updates are now enabled or disabled. True means enabled, False means disabled.
        """
        resp = self.connection.request("POST",
                                       self.admin_patterns(
                                           "/background_updates/enabled", 1),
                                       json={"enabled": enabled})
        data = resp.json()
        if resp.status_code == 200:
            return data["enabled"]
        else:
            if self.suppress_exception:
                return False, data["errcode"], data["error"]
            else:
                raise SynapseException(data["errcode"], data["error"])

    def background_updates_run(self, job_name: str):
        """Run a background update

        https://github.com/matrix-org/synapse/blob/develop/docs/usage/administration/admin_api/background_updates.md#run

        Args:
            job_name (str): job name of the background update.  # noqa: E501
        """
        jobs = {"populate_stats_process_rooms", "regenerate_directory"}
        if job_name not in jobs:
            raise ValueError(
                "Value of job_name can only be either"
                "populate_stats_process_rooms or regenerate_directory")
        resp = self.connection.request("POST",
                                       self.admin_patterns(
                                           "/background_updates/start_job", 1),
                                       json={"job_name": job_name})
        data = resp.json()
        if resp.status_code == 200:
            return data
        else:
            if self.suppress_exception:
                return False, data["errcode"], data["error"]
            else:
                raise SynapseException(data["errcode"], data["error"])

    def federation_list(self,
                        _from: int = 0,
                        limit: int = 100,
                        orderby: str = None,
                        _dir: str = "f",
                        destination: str = None) -> Contents:
        """List infomation of retrying timing for all remote servers

        https://github.com/matrix-org/synapse/blob/develop/docs/usage/administration/admin_api/federation.md#federation-api
        https://github.com/matrix-org/synapse/blob/develop/docs/usage/administration/admin_api/federation.md#list-of-destinations
        https://github.com/matrix-org/synapse/blob/develop/docs/usage/administration/admin_api/federation.md#destination-details-api

        Args:
            _from (int, optional): equivalent to "from". Defaults to 0.
            limit (int, optional): equivalent to "limit". Defaults to 100.
            order_by (int, optional): equivalent to "order_by". Defaults to None. # noqa: E501
            _dir (str, optional): equivalent to "dir". Defaults to "f".
            destination (str, optional): show only the specified remote server. Defaults to None (no specification).

        Returns:
            Contents: list of timing information of current destination
        """
        if isinstance(destination, str):
            return self._federation_list(destination)

        params = {"from": _from, "limit": limit, "dir": _dir}
        if orderby is not None:
            params["order_by"] = orderby
        resp = self.connection.request("GET",
                                       self.admin_patterns(
                                           "/federation/destinations", 1),
                                       params=params)
        data = resp.json()
        if resp.status_code == 200:
            return Contents(data["destinations"], data["total"],
                            data.get("next_token", None))
        else:
            if self.suppress_exception:
                return False, data["errcode"], data["error"]
            else:
                raise SynapseException(data["errcode"], data["error"])

    def _federation_list(self, destination: str) -> dict:
        """Query the retry timing details of a specific remote server

        https://github.com/matrix-org/synapse/blob/develop/docs/usage/administration/admin_api/federation.md#destination-details-api

        Args:
            destination (str): show only the specified remote server.

        Returns:
            dict: details of retry timing
        """
        resp = self.connection.request(
            "GET",
            self.admin_patterns(f"/federation/destinations/{destination}", 1))
        data = resp.json()
        if resp.status_code == 200:
            return data
        else:
            if self.suppress_exception:
                return False, data["errcode"], data["error"]
            else:
                raise SynapseException(data["errcode"], data["error"])

    def reset_connection(self, destination: str) -> bool:
        """Reset the connection timeout for a specific destination

        https://github.com/matrix-org/synapse/blob/develop/docs/usage/administration/admin_api/federation.md#reset-connection-timeout

        Args:
            destination (str): the remote destination

        Returns:
            bool: whether or not the connection reset successfully
        """
        resp = self.connection.request(
            "POST",
            self.admin_patterns(
                f"/federation/destinations/{destination}/reset_connection", 1),
            json={})
        if resp.status_code == 200:
            return True
        else:
            data = resp.json()
            if self.suppress_exception:
                return False, data["errcode"], data["error"]
            else:
                raise SynapseException(data["errcode"], data["error"])

    def federation_room(self,
                        destination: str,
                        _from: int = 0,
                        limit: int = 100,
                        _dir: str = "f") -> Contents:
        """Fetch roms federated with remote destination

        https://github.com/matrix-org/synapse/blob/develop/docs/usage/administration/admin_api/federation.md#destination-rooms

        Args:
            destination (str): the remote destination
            _from (int, optional): equivalent to "from". Defaults to 0.
            limit (int, optional): equivalent to "limit". Defaults to 100.
            _dir (str, optional): equivalent to "dir". Defaults to "f".

        Returns:
            Contents: A list of federated room
        """
        resp = self.connection.request(
            "GET",
            self.admin_patterns(
                f"/federation/destinations/{destination}/rooms", 1),
            params={
                "from": _from,
                "limit": limit,
                "dir": _dir
            })
        data = resp.json()
        if resp.status_code == 200:
            return Contents(data["rooms"], data["total"],
                            data.get("next_token", None))
        else:
            if self.suppress_exception:
                return False, data["errcode"], data["error"]
            else:
                raise SynapseException(data["errcode"], data["error"])