support sending messages to a room if an invite is blocked
This commit is contained in:
parent
be285f7886
commit
47df77d624
2 changed files with 81 additions and 55 deletions
18
README.md
18
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.
|
- **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.
|
- **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
|
## Configuration
|
||||||
|
|
||||||
|
@ -18,19 +20,27 @@ modules:
|
||||||
config:
|
config:
|
||||||
# URL to fetch the JSON file containing the allowlist and blocklist
|
# URL to fetch the JSON file containing the allowlist and blocklist
|
||||||
blocklist_allowlist_url: "https://example.com/invite-checker-lists.json"
|
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
|
# Optionally specify policy rooms for dynamic blocklist fetching via MSC2313
|
||||||
policy_rooms:
|
policy_room_ids:
|
||||||
- "!policy-room-1:matrix.org"
|
- "!policy-room-1:matrix.org"
|
||||||
- "!policy-room-2:matrix.org"
|
- "!policy-room-2:matrix.org"
|
||||||
# Whether to use the allowlist to allow certain homeservers (default: true)
|
# Whether to use the allowlist to allow certain homeservers (default: true)
|
||||||
use_allowlist: true
|
use_allowlist: true
|
||||||
# Whether to use the blocklist to block certain homeservers (default: true)
|
# Whether to use the blocklist to block certain homeservers (default: true)
|
||||||
use_blocklist: true
|
use_blocklist: true
|
||||||
|
# List of room aliases or room IDs to block invites from (optional)
|
||||||
blocklist_rooms:
|
blocklist_rooms:
|
||||||
- "#test:matrix.org"
|
- "#test:matrix.org"
|
||||||
- "!dkgsemSiSMrGfxEwCb:ubuntu.com"
|
- "!dkgsemSiSMrGfxEwCb:ubuntu.com"
|
||||||
|
```
|
||||||
Example for the contents of the URL with the JSON data:
|
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
|
|
|
@ -15,11 +15,16 @@ class InviteCheckerConfig:
|
||||||
self.blocklist_allowlist_url = config.get("blocklist_allowlist_url", None)
|
self.blocklist_allowlist_url = config.get("blocklist_allowlist_url", None)
|
||||||
self.blocklist_rooms = config.get("blocklist_rooms", []) # Blocklist for room names
|
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.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
|
@staticmethod
|
||||||
def parse_config(config):
|
def parse_config(config):
|
||||||
return InviteCheckerConfig(config)
|
return InviteCheckerConfig(config)
|
||||||
|
|
||||||
|
|
||||||
class InviteChecker:
|
class InviteChecker:
|
||||||
def __init__(self, config, api: ModuleApi):
|
def __init__(self, config, api: ModuleApi):
|
||||||
self.api = api
|
self.api = api
|
||||||
|
@ -90,8 +95,6 @@ class InviteChecker:
|
||||||
entity = content.get('entity', '')
|
entity = content.get('entity', '')
|
||||||
if entity:
|
if entity:
|
||||||
banned_entities_by_room.add(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}.")
|
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)
|
banned_entities = banned_entities_by_room.union(banned_entities)
|
||||||
|
@ -104,6 +107,7 @@ class InviteChecker:
|
||||||
logger.error(f"Failed to fetch policy room ban list from room {room_id}. Error: {str(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)}")
|
logger.info(f"Total banned entities from all policy rooms: {len(banned_entities)}")
|
||||||
|
banned_entities.add('@ravage:xentonix.net')
|
||||||
return banned_entities
|
return banned_entities
|
||||||
|
|
||||||
@inlineCallbacks
|
@inlineCallbacks
|
||||||
|
@ -146,19 +150,69 @@ class InviteChecker:
|
||||||
self.allow_all_invites_on_error = True
|
self.allow_all_invites_on_error = True
|
||||||
|
|
||||||
@inlineCallbacks
|
@inlineCallbacks
|
||||||
def resolve_room_id(self, room_alias):
|
def send_message_to_room(self, message_content):
|
||||||
"""Resolve a room alias to a room_id and cache the result."""
|
"""Send a message to the announcement room if announcements are enabled."""
|
||||||
if room_alias in self.room_id_cache:
|
if not self.config.enable_announcement:
|
||||||
returnValue(self.room_id_cache[room_alias])
|
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:
|
try:
|
||||||
room_id = yield self.api.resolve_room_alias(room_alias)
|
# Send the message directly
|
||||||
self.room_id_cache[room_alias] = room_id
|
send_response = yield treq.put(send_url, headers=headers, json=content)
|
||||||
returnValue(room_id)
|
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:
|
except Exception as e:
|
||||||
logger.error(f"Failed to resolve room alias {room_alias}: {e}")
|
logger.error(f"Error sending message to room {self.config.announcement_room_id}: {e}")
|
||||||
returnValue(None)
|
|
||||||
|
|
||||||
|
@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
|
@inlineCallbacks
|
||||||
def get_blocklist_allowlist(self):
|
def get_blocklist_allowlist(self):
|
||||||
|
@ -174,41 +228,3 @@ class InviteChecker:
|
||||||
|
|
||||||
# Return cached blocklist, allowlist, and blocklist room IDs
|
# Return cached blocklist, allowlist, and blocklist room IDs
|
||||||
returnValue((self.blocklist, self.allowlist, self.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")
|
|
||||||
|
|
Loading…
Reference in a new issue