def test_user_device_lists(): for _ in range(3): ClientAPI.admin_login("http://", "localhost", 8008, "test2", "12345678", no_admin=True) devices = user_handler.devices.lists("test2") assert len(devices) == 4 assert devices[0]["display_name"] == "matrix-synapse-admin" with pytest.raises(SynapseException): user_handler.devices.lists("invalid")
def test_user_reset_password(): assert user_handler.reset_password("test2", "12345678") with pytest.raises(SynapseException): assert ClientAPI.admin_login("http://", "localhost", 8008, "test2", "123456789123456789", no_admin=True) assert ClientAPI.admin_login("http://", "localhost", 8008, "test2", "12345678", no_admin=True)
def test_user_reactivate(): assert user_handler.reactivate("test2", "123456789123456789") assert ClientAPI.admin_login("http://", "localhost", 8008, "test2", "123456789123456789", no_admin=True)
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_config(self, protocol: str = None, host: str = None, port: int = None, access_token: str = None, save_to_file: int = False) -> bool: """Create configuration (interactively) Args: protocol (str, optional): "http://" or "https://". Defaults to None. # noqa: E501 host (str, optional): homeserver address. Defaults to None. port (int, optional): homeserver listening port. Defaults to None. access_token (str, optional): access token that has admin privilege. Defaults to None. # noqa: E501 save_to_file (int, optional): whether or not save the configuration to a file. Defaults to False. # noqa: E501 Returns: bool: configuration saved """ if (protocol is None or host is None or port is None or access_token is None): while True: url = input("Enter the homeserver URL with port " "(e.g. https://example.com:443): ") try: protocol, host, port = self._parse_homeserver_url(url) except ValueError as e: print(e) continue else: break while True: access_token = input("Enter the access token (leave blank to" "get the access token by logging in): ") if access_token == "": from synapse_admin.client import ClientAPI access_token = ClientAPI.admin_login( protocol, host, port, suppress_exception=True) if not access_token: print( "The account you logged in is not a server admin " "or you entered an invalid username/password.") continue else: print("Token retrieved successfully") break while save_to_file not in {"y", "n", ""}: save_to_file = input("Save to a config file? (Y/n) ").lower() self.server_protocol = protocol self.server_addr = host self.server_port = int(port) self.access_token = access_token if (save_to_file == "n" or isinstance(save_to_file, bool) and not save_to_file): return True return self._save_config(protocol, host, port, access_token)
def test_user_deactivate(): assert user_handler.deactivate("test2") with pytest.raises(SynapseException): user_handler.deactivate("invalid") assert ClientAPI.admin_login("http://", "localhost", 8008, "test2", "12345678", no_admin=True)
def test_client_admin_login(monkeypatch): original = "getpass.getpass.__code__" replacement = (lambda _: "0123456789").__code__ monkeypatch.setattr(original, replacement) token = ClientAPI.admin_login( "http://", "localhost", 8008, "admin1", "0123456789", ) assert isinstance(token, str) and token[:4] == "syt_" token = ClientAPI.admin_login( "http://", "localhost", 8008, "test1", "123456789", no_admin=True ) assert isinstance(token, str) and token[:4] == "syt_" token = ClientAPI.admin_login( "http://", "localhost", 8008, "admin1" ) assert isinstance(token, str) and token[:4] == "syt_" with pytest.raises(SynapseException): ClientAPI.admin_login( "http://", "localhost", 8008, "test1", "123456789", no_admin=False ) ClientAPI.admin_login( "http://", "localhost", 8008, "admin1", "invalid" )
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"])
class Room(Admin): """ Wapper class for admin API for room management Reference: https://github.com/matrix-org/synapse/blob/develop/docs/admin_api/rooms.md https://github.com/matrix-org/synapse/blob/master/docs/admin_api/shutdown_room.md https://github.com/matrix-org/synapse/blob/master/docs/admin_api/purge_room.md """ order = { "alphabetical", "size", "name", "canonical_alias", "joined_members", "joined_local_members", "version", "creator", "encryption", "federatable", "public", "join_rules", "guest_access", "history_visibility", "state_events" } class RoomInformation(NamedTuple): roomid: str joined: list def __init__(self, server_addr: str = None, server_port: int = 443, access_token: str = None, server_protocol: str = None, suppress_exception: bool = 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_api = ClientAPI(server_addr, server_port, access_token, server_protocol, suppress_exception) else: self.user = User() self.client_api = ClientAPI() self._create_alias() def _create_alias(self) -> None: """Create alias for some methods""" self.members = self.list_members def lists(self, _from: int = None, limit: int = None, orderby: str = None, recent_first: bool = True, search: str = None) -> Contents: """List all local rooms https://github.com/matrix-org/synapse/blob/develop/docs/admin_api/rooms.md#list-room-api Args: _from (int, optional): equivalent to "from". Defaults to None. limit (int, optional): equivalent to "limit". Defaults to None. orderby (str, optional): equivalent to "order_by". Defaults to None. recent_first (bool, optional): equivalent to "dir", True to f False to b. Defaults to True. # noqa: E501 search (str, optional): equivalent to "search_term". Defaults to None. Returns: Contents: list of room """ if recent_first: optional_str = "dir=b" else: optional_str = "dir=f" if _from is not None: optional_str += f"&from={_from}" if limit is not None: optional_str += f"&limit={limit}" if orderby is not None: if not isinstance(orderby, str): raise TypeError("Argument 'orderby' should be a " f"str but not {type(orderby)}") elif orderby not in Room.order: raise ValueError( "Argument 'orderby' must be included in Room.order, " "for details please read documentation.") optional_str += f"&orderby={orderby}" if search: optional_str += f"&search_term={search}" resp = self.connection.request( "GET", self.admin_patterns(f"/rooms?{optional_str}", 1), ) data = resp.json() if resp.status_code == 200: return Contents(data["rooms"], data["total_rooms"], data.get("next_batch", None)) else: if self.suppress_exception: return False, data else: raise SynapseException(data["errcode"], data["error"]) def details(self, roomid: str) -> dict: """Query a room https://github.com/matrix-org/synapse/blob/develop/docs/admin_api/rooms.md#room-details-api Args: roomid (str): the room you want to query Returns: dict: a dict containing the room's details """ roomid = self.validate_room(roomid) resp = self.connection.request( "GET", self.admin_patterns(f"/rooms/{roomid}", 1), ) data = resp.json() if resp.status_code == 200: return data else: if self.suppress_exception: return False, data else: raise SynapseException(data["errcode"], data["error"]) def list_members(self, roomid: str) -> Contents: """List all members in the room https://github.com/matrix-org/synapse/blob/develop/docs/admin_api/rooms.md#room-members-api Args: roomid (str): the room you want to query Returns: Contents: a list of members """ roomid = self.validate_room(roomid) resp = self.connection.request( "GET", self.admin_patterns(f"/rooms/{roomid}/members", 1), ) data = resp.json() if resp.status_code == 200: return Contents(data["members"], data["total"]) else: if self.suppress_exception: return False, data else: raise SynapseException(data["errcode"], data["error"]) def create(self, public: bool = False, *, alias: str = None, name: str = None, members: list = None, federation: bool = True, leave: bool = False, encrypted: bool = True) -> RoomInformation: """Create a room and force users to be a member Args: public (bool, optional): is the room public? Defaults to False. alias (str, optional): the alias of the room. Defaults to None. name (str, optional): the name of the room. Defaults to None. members (list, optional): a list of user that should be the members of the room. Defaults to None. # noqa: E501 federation (bool, optional): can the room be federated. Defaults to True. leave (bool, optional): whether to leave the room yourself after the creation. Defaults to False. Returns: RoomInformation: roomid: room id, joined: a list of joined users """ if members is None and leave: raise ValueError("You cannot create a room and leave" " the room immediately since you" " are the only member of the room") roomid = self.client_api.client_create(public, alias, name, federation=federation, encrypted=encrypted) joined = [] if members is not None: for member in members: userid = self.validate_username(member) if self.user.join_room(userid, roomid): joined.append(userid) if leave: self.client_api.client_leave(roomid) return Room.RoomInformation(roomid, joined) def delete(self, roomid: str, new_room_userid: str = None, room_name: str = None, message: str = None, block: bool = False, purge: bool = True) -> dict: """Delete a room https://github.com/matrix-org/synapse/blob/develop/docs/admin_api/rooms.md#version-1-old-version Args: roomid (str): the room you want to delete new_room_userid (str, optional): equivalent to "new_room_user_id". Defaults to None. # noqa: E501 room_name (str, optional): equivalent to "room_name". Defaults to None. message (str, optional): equivalent to "message". Defaults to None. block (bool, optional): equivalent to "block". Defaults to False. purge (bool, optional): whether or not to purge all information of the rooom from the database. Defaults to True. Returns: dict: a dict containing kicked_users, failed_tokick_users, local_aliases, new_room_id """ roomid = self.validate_room(roomid) data = {"block": block, "purge": purge} if new_room_userid is not None: new_room_userid = self.validate_username(new_room_userid) data["new_room_user_id"] = new_room_userid if room_name is not None: data["room_name"] = room_name if message is not None: data["message"] = message resp = self.connection.request( "DELETE", self.admin_patterns(f"/rooms/{roomid}", 1), json=data, ) data = resp.json() if resp.status_code == 200: return data else: if self.suppress_exception: return False, data else: raise SynapseException(data["errcode"], data["error"]) def delete_old(self, roomid: str, new_room_userid: str = None, new_room_name: str = None, message: str = None, block: bool = False, purge: bool = True): """Old room deletion method. Use the new one.""" roomid = self.validate_room(roomid) data = {"block": block, "purge": purge} if new_room_userid is not None: new_room_userid = self.validate_username(new_room_userid) data["new_room_user_id"] = new_room_userid if new_room_name is not None: data["room_name"] = new_room_name if message is not None: data["message"] = message resp = self.connection.request("POST", self.admin_patterns( f"/rooms/{roomid}/delete", 1), json=data) data = resp.json() if resp.status_code == 200: return data else: if self.suppress_exception: return False, data else: raise SynapseException(data["errcode"], data["error"]) def set_admin(self, roomid: str, userid: str = None) -> bool: """Set a member to be the admin in the room https://github.com/matrix-org/synapse/blob/develop/docs/admin_api/rooms.md#make-room-admin-api Args: roomid (str): the room you want to grant admin privilege to the user # noqa: E501 userid (str, optional): the user you wanted them as an admin in the room. Defaults to None (self). Returns: bool: The modification is successful or not """ roomid = self.validate_room(roomid) if userid is not None: userid = self.validate_username(userid) body = {"user_id": userid} else: body = {} resp = self.connection.request("POST", self.admin_patterns( f"/rooms/{roomid}/make_room_admin", 1), json=body) data = resp.json() if resp.status_code == 200: return True else: if self.suppress_exception: return False, data else: raise SynapseException(data["errcode"], data["error"]) def purge_room(self, roomid): """Purge a room. Removed in Synapse 1.42.0 https://github.com/matrix-org/synapse/blob/3bcd525b46678ff228c4275acad47c12974c9a33/docs/admin_api/purge_room.md """ roomid = self.validate_room(roomid) resp = self.connection.request("POST", self.admin_patterns("/purge_room", 1), json={"room_id": roomid}) data = resp.json() if "errcode" in data and data["errcode"] == "M_UNRECOGNIZED": raise NotImplementedError( "This admin API has been removed in your homeserver") return data def shutdown_room(self, roomid, new_room_userid, new_room_name=None, message=None): """Shut down a room. Removed in Synapse 1.42.0 https://github.com/matrix-org/synapse/blob/3bcd525b46678ff228c4275acad47c12974c9a33/docs/admin_api/shutdown_room.md """ roomid = self.validate_room(roomid) new_room_userid = self.validate_username(new_room_userid) data = {"new_room_user_id": new_room_userid} if new_room_name is not None: data["room_name"] = new_room_name if message is not None: data["message"] = message resp = self.connection.request( "POST", self.admin_patterns(f"/shutdown_room/{roomid}", 1), json=data, ) data = resp.json() if "errcode" in data and data["errcode"] == "M_UNRECOGNIZED": raise NotImplementedError( "This admin API has been removed in your homeserver") return data def forward_extremities_check(self, roomid: str) -> Contents: """Query forward extremities in a room https://github.com/matrix-org/synapse/blob/develop/docs/admin_api/rooms.md#check-for-forward-extremities Args: roomid (str): the room you want to query Returns: Contents: a list of forward extremities """ roomid = self.validate_room(roomid) resp = self.connection.request( "GET", self.admin_patterns(f"/rooms/{roomid}/forward_extremities", 1)) data = resp.json() return Contents(data["results"], data["count"]) def forward_extremities_delete(self, roomid: str) -> int: """Delete forward extremities in a room (Do not use this method when writing automated script) https://github.com/matrix-org/synapse/blob/develop/docs/admin_api/rooms.md#deleting-forward-extremities Args: roomid (str): the room you want the forward extremities to be deleted # noqa: E501 Returns: int: number of forward extremities deleted """ roomid = self.validate_room(roomid) resp = self.connection.request( "DELETE", self.admin_patterns(f"/rooms/{roomid}/forward_extremities", 1)) data = resp.json() if resp.status_code == 200: return data["deleted"] else: # Synapse bug: Internal server error # raise if the room does not exist if self.suppress_exception: return False, data else: raise SynapseException(data["errcode"], data["error"]) def get_state(self, roomid: str) -> list: """Query the room state https://github.com/matrix-org/synapse/blob/develop/docs/admin_api/rooms.md#room-state-api Args: roomid (str): the room you want to query Returns: list: a list of state """ roomid = self.validate_room(roomid) resp = self.connection.request( "GET", self.admin_patterns(f"/rooms/{roomid}/state", 1), ) data = resp.json() if resp.status_code == 200: return data["state"] else: if self.suppress_exception: return False, data else: raise SynapseException(data["errcode"], data["error"]) def event_context(self, roomid: str, event_id: str) -> dict: """Query the context of an event https://github.com/matrix-org/synapse/blob/develop/docs/admin_api/rooms.md#event-context-api Args: roomid (str): the room where the event exist event_id (str): the event you want to query Returns: dict: a dict with event context """ roomid = self.validate_room(roomid) resp = self.connection.request( "GET", self.admin_patterns(f"/rooms/{roomid}/context/{event_id}", 1), ) data = resp.json() if resp.status_code == 200: return data else: if self.suppress_exception: return False, data else: raise SynapseException(data["errcode"], data["error"]) def delete_async(self, roomid: str, new_room_userid: str = None, room_name: str = None, message: str = None, block: bool = False, purge: bool = True, force_purge: bool = None) -> str: """Delete a room asynchronously https://github.com/matrix-org/synapse/blob/develop/docs/admin_api/rooms.md#version-2-new-version Args: roomid (str): the room you want to delete new_room_userid (str, optional): equivalent to "new_room_user_id". Defaults to None. # noqa: E501 room_name (str, optional): equivalent to "room_name". Defaults to None. message (str, optional): equivalent to "message". Defaults to None. block (bool, optional): equivalent to "block". Defaults to False. purge (bool, optional): whether or not to purge all information of the rooom from the database. Defaults to True. force_purge (bool, optional): equivalent to "force_purge". Defaults to None. Returns: dict: a dict containing kicked_users, failed_tokick_users, local_aliases, new_room_id """ roomid = self.validate_room(roomid) data = {"block": block, "purge": purge} if new_room_userid is not None: new_room_userid = self.validate_username(new_room_userid) data["new_room_user_id"] = new_room_userid if room_name is not None: data["room_name"] = room_name if message is not None: data["message"] = message if force_purge is not None: data["force_purge"] = force_purge resp = self.connection.request( "DELETE", self.admin_patterns(f"/rooms/{roomid}", 2), json=data, ) data = resp.json() if resp.status_code == 200: return data["delete_id"] else: if self.suppress_exception: return False, data else: raise SynapseException(data["errcode"], data["error"]) def delete_status(self, *, roomid: str = None, deleteid: str = None) -> dict: """Query the deletion of room(s) https://github.com/matrix-org/synapse/blob/develop/docs/admin_api/rooms.md#status-of-deleting-rooms Args: roomid (str): the room to be queried deleteid (str): the delete id to be queried Returns: dict: a dict with room deletion status """ if roomid is not None and deleteid is not None: raise ValueError("roomid and deleteid cannot " "be presented at the same time") if roomid is not None: return self.delete_status_room(roomid) if deleteid is not None: return self.delete_status_id(deleteid) raise ValueError("Either roomid or deleteid should be specified") def delete_status_room(self, roomid: str) -> dict: """Query the deletion of room by room id https://github.com/matrix-org/synapse/blob/develop/docs/admin_api/rooms.md#query-by-room_id Args: roomid (str): the room to be queried Returns: dict: a dict with room deletion status """ roomid = self.validate_room(roomid) resp = self.connection.request( "GET", self.admin_patterns(f"/rooms/{roomid}/delete_status", 2), ) data = resp.json() if resp.status_code == 200: return data["results"] else: if self.suppress_exception: return False, data else: raise SynapseException(data["errcode"], data["error"]) def delete_status_id(self, deleteid: str) -> dict: """Query the deletion of room by id https://github.com/matrix-org/synapse/blob/develop/docs/admin_api/rooms.md#query-by-delete_id Args: deleteid (str): the delete id to be queried Returns: dict: a dict with room deletion status """ resp = self.connection.request( "GET", self.admin_patterns(f"/rooms/delete_status/{deleteid}", 2), ) data = resp.json() if resp.status_code == 200: return data else: if self.suppress_exception: return False, data else: raise SynapseException(data["errcode"], data["error"]) def block(self, roomid: str, blocked: bool = True) -> bool: """Block or unblock a room https://github.com/matrix-org/synapse/blob/develop/docs/admin_api/rooms.md#block-or-unblock-a-room Args: roomid (str): the room to block or unblock blocked (bool, optional): whether the room should be blocked or unblocked, True to blocked, False to unblocked. Defaults to True. # noqa: E501 Returns: bool: whether the room is blocked or not """ roomid = self.validate_room(roomid) resp = self.connection.request("PUT", self.admin_patterns( f"/rooms/{roomid}/block", 1), json={"block": blocked}) data = resp.json() if resp.status_code == 200: return data["block"] else: if self.suppress_exception: return False, data else: raise SynapseException(data["errcode"], data["error"]) def block_status(self, roomid: str) -> bool: """Check if a room is blocked https://github.com/matrix-org/synapse/blob/develop/docs/admin_api/rooms.md#get-block-status Args: roomid (str): the room to be queried Returns: bool: whether the room is blocked or not """ roomid = self.validate_room(roomid) resp = self.connection.request( "GET", self.admin_patterns(f"/rooms/{roomid}/block", 1)) data = resp.json() if resp.status_code == 200: # TBD: whether return the user_id in the response. return data["block"] else: if self.suppress_exception: return False, data else: raise SynapseException(data["errcode"], data["error"])
SOFTWARE.""" import pytest from synapse_admin.base import SynapseException from synapse_admin.client import ClientAPI with open("synapse_test/admin.token", "r") as f: admin_access_token = f.read().replace("\n", "") with open("synapse_test/user.token", "r") as f: user_access_token = f.read().replace("\n", "") conn = ("localhost", 8008, admin_access_token, "http://") client_handler = ClientAPI(*conn) def test_client_admin_login(monkeypatch): original = "getpass.getpass.__code__" replacement = (lambda _: "0123456789").__code__ monkeypatch.setattr(original, replacement) token = ClientAPI.admin_login( "http://", "localhost", 8008, "admin1", "0123456789", ) assert isinstance(token, str) and token[:4] == "syt_"