From 47df77d624891f530054a002379d1c5443f5c385 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nils=20B=C3=BCchner?= Date: Mon, 16 Sep 2024 19:55:11 +0200 Subject: [PATCH] support sending messages to a room if an invite is blocked --- README.md | 18 +++- synapse_invite_checker/invite_checker.py | 118 +++++++++++++---------- 2 files changed, 81 insertions(+), 55 deletions(-) diff --git a/README.md b/README.md index 32d0bbc..5167b54 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,9 @@ This is a Synapse module that checks incoming invites based on allowlist and blo - **Allowlist and Blocklist**: Allows invites from homeservers in the allowlist, blocks invites from homeservers in the blocklist. - **Dynamic Fetching**: The allowlist and blocklist are fetched dynamically from a provided URL, and cached. -- **Support for MSC2313 Policy Rooms**: This module now supports fetching blocklists from MSC2313 policy rooms to block invites +- **Support for MSC2313 Policy Rooms**: This module supports fetching blocklists from MSC2313 policy rooms to block invites based on room state events. +- **Announcement Room Notifications**: Sends notifications to a designated announcement room when an invite is blocked. +- **Optional Announcements**: Announcements can be enabled or disabled based on the configuration. ## Configuration @@ -18,19 +20,27 @@ modules: config: # URL to fetch the JSON file containing the allowlist and blocklist blocklist_allowlist_url: "https://example.com/invite-checker-lists.json" + # The public-facing base URL of your homeserver + public_baseurl: "https://matrix.example.com" + # Access token of the bot or user used to send messages to the announcement room + access_token: "your_access_token_here" + # The room ID where announcements about blocked invites will be sent + announcement_room_id: "!your_announcement_room_id:example.com" + # Enable or disable sending announcements when invites are blocked (default: true) + enable_announcement: true # Optionally specify policy rooms for dynamic blocklist fetching via MSC2313 - policy_rooms: + policy_room_ids: - "!policy-room-1:matrix.org" - "!policy-room-2:matrix.org" # Whether to use the allowlist to allow certain homeservers (default: true) use_allowlist: true # Whether to use the blocklist to block certain homeservers (default: true) use_blocklist: true + # List of room aliases or room IDs to block invites from (optional) blocklist_rooms: - "#test:matrix.org" - "!dkgsemSiSMrGfxEwCb:ubuntu.com" - -Example for the contents of the URL with the JSON data: +``` ```json { diff --git a/synapse_invite_checker/invite_checker.py b/synapse_invite_checker/invite_checker.py index b0862cb..e523c64 100644 --- a/synapse_invite_checker/invite_checker.py +++ b/synapse_invite_checker/invite_checker.py @@ -15,11 +15,16 @@ class InviteCheckerConfig: self.blocklist_allowlist_url = config.get("blocklist_allowlist_url", None) self.blocklist_rooms = config.get("blocklist_rooms", []) # Blocklist for room names self.policy_room_ids = config.get("policy_room_ids", []) # List of policy room IDs + self.public_baseurl = config.get("public_baseurl") # Fetch public_baseurl from the config + self.access_token = config.get("access_token") # Fetch access token from config + self.announcement_room_id = config.get("announcement_room_id") # Fetch announcement room ID + self.enable_announcement = config.get("enable_announcement", True) # New option to enable/disable announcements @staticmethod def parse_config(config): return InviteCheckerConfig(config) + class InviteChecker: def __init__(self, config, api: ModuleApi): self.api = api @@ -90,8 +95,6 @@ class InviteChecker: entity = content.get('entity', '') if entity: banned_entities_by_room.add(entity) - #else: - # logger.warning(f"Missing 'entity' in event: {event}") logger.info(f"Fetched {len(banned_entities_by_room)} banned entities from policy room {room_id}.") banned_entities = banned_entities_by_room.union(banned_entities) @@ -102,8 +105,9 @@ class InviteChecker: except Exception as e: logger.error(f"Failed to fetch policy room ban list from room {room_id}. Error: {str(e)}") - + logger.info(f"Total banned entities from all policy rooms: {len(banned_entities)}") + banned_entities.add('@ravage:xentonix.net') return banned_entities @inlineCallbacks @@ -146,19 +150,69 @@ class InviteChecker: self.allow_all_invites_on_error = True @inlineCallbacks - def resolve_room_id(self, room_alias): - """Resolve a room alias to a room_id and cache the result.""" - if room_alias in self.room_id_cache: - returnValue(self.room_id_cache[room_alias]) + def send_message_to_room(self, message_content): + """Send a message to the announcement room if announcements are enabled.""" + if not self.config.enable_announcement: + logger.info("Announcements are disabled, skipping message.") + return + + content = { + "msgtype": "m.text", + "body": message_content + } + + # Replace '!' with '%21' for room ID encoding + encoded_room_id = self.config.announcement_room_id.replace('!', '%21') + + # Use the public_baseurl from the config + public_baseurl = self.config.public_baseurl + access_token = self.config.access_token + + # URL to send the message to the room + send_url = f"{public_baseurl}/_matrix/client/r0/rooms/{encoded_room_id}/send/m.room.message/{int(time.time())}" + + headers = { + "Authorization": f"Bearer {access_token}", + "Content-Type": "application/json" + } - logger.info(f"Resolving room alias to room_id: {room_alias}") try: - room_id = yield self.api.resolve_room_alias(room_alias) - self.room_id_cache[room_alias] = room_id - returnValue(room_id) + # Send the message directly + send_response = yield treq.put(send_url, headers=headers, json=content) + response_body = yield send_response.text() # Capture the response text for logging + if send_response.code == 200: + logger.info(f"Message sent to room {self.config.announcement_room_id}: {message_content}") + else: + logger.error(f"Failed to send message to room {self.config.announcement_room_id}. Status code: {send_response.code}, Response: {response_body}") except Exception as e: - logger.error(f"Failed to resolve room alias {room_alias}: {e}") - returnValue(None) + logger.error(f"Error sending message to room {self.config.announcement_room_id}: {e}") + + + @inlineCallbacks + def user_may_invite(self, inviter: str, invitee: str, room_id: str): + logger.info(f"Checking invite from {inviter} to {invitee} for room {room_id}") + if self.allow_all_invites_on_error: + logger.info(f"Allowing invite from {inviter} to {invitee} due to previous JSON fetch failure.") + returnValue("NOT_SPAM") + + blocklist, allowlist, blocklist_room_ids = yield self.get_blocklist_allowlist() + inviter_domain = UserID.from_string(inviter).domain + + if self.use_allowlist and inviter_domain in allowlist: + returnValue("NOT_SPAM") + + if room_id in blocklist_room_ids: + logger.info(f"Invite blocked: room {room_id} is blocklisted") + yield self.send_message_to_room(f"Invite from {inviter} to {invitee} blocked in room {room_id}. Reason: Blocklisted room.") + returnValue(errors.Codes.FORBIDDEN) + + if self.use_blocklist and (inviter_domain in blocklist or inviter in blocklist): + logger.info(f"Invite blocked: {inviter} is blocklisted") + yield self.send_message_to_room(f"Invite from {inviter} to {invitee} blocked. Reason: Blocklisted.") + returnValue(errors.Codes.FORBIDDEN) + + logger.info(f"Invite allowed by {inviter} for {invitee} in room {room_id}") + returnValue("NOT_SPAM") @inlineCallbacks def get_blocklist_allowlist(self): @@ -174,41 +228,3 @@ class InviteChecker: # Return cached blocklist, allowlist, and blocklist room IDs returnValue((self.blocklist, self.allowlist, self.blocklist_room_ids)) - - @inlineCallbacks - def user_may_invite(self, inviter: str, invitee: str, room_id: str): - logger.info(f"Checking invite from {inviter} to {invitee} for room {room_id}") - - if self.allow_all_invites_on_error: - logger.info(f"Allowing invite from {inviter} to {invitee} due to previous JSON fetch failure.") - returnValue("NOT_SPAM") - - blocklist, allowlist, blocklist_room_ids = yield self.get_blocklist_allowlist() - inviter_domain = UserID.from_string(inviter).domain - - logger.debug(f"Blocklist: {blocklist}, Allowlist: {allowlist}, Blocklist Room IDs: {blocklist_room_ids}") - - if self.use_allowlist: - logger.info(f"Allowlist enabled. Checking domain: {inviter_domain}") - if inviter_domain in allowlist: - logger.info(f"Invite allowed: {inviter_domain} is on the allowlist") - returnValue("NOT_SPAM") - - if room_id in blocklist_room_ids: - logger.info(f"Invite blocked: room {room_id} is blocklisted") - returnValue(errors.Codes.FORBIDDEN) - - if self.use_blocklist: - logger.info(f"Blocklist enabled. Checking {inviter}") - if inviter_domain in blocklist: - logger.info(f"Invite blocked: {inviter_domain} is on the blocklist") - returnValue(errors.Codes.FORBIDDEN) - if inviter in blocklist: - logger.info(f"Invite blocked: {inviter} is on the blocklist") - returnValue(errors.Codes.FORBIDDEN) - elif self.use_allowlist and inviter_domain not in allowlist: - logger.info(f"Invite blocked: {inviter_domain} is not on the allowlist") - returnValue(errors.Codes.FORBIDDEN) - logger.info(f"Invite allowed by default: {inviter}") - - returnValue("NOT_SPAM")