support policy rooms

This commit is contained in:
Nils Büchner 2024-09-14 23:16:32 +02:00
parent c0d5dfcb5b
commit b7174bd16c
2 changed files with 89 additions and 21 deletions

View file

@ -4,9 +4,9 @@ This is a Synapse module that checks incoming invites based on allowlist and blo
## Features ## Features
- **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.
- **Fallback on Failure**: If the JSON file cannot be fetched (e.g., network error), the module automatically allows all invites to prevent disruptions. - **Support for MSC2313 Policy Rooms**: This module now supports fetching blocklists from MSC2313 policy rooms to block invites
## Configuration ## Configuration
@ -18,15 +18,19 @@ 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"
# Optionally specify policy rooms for dynamic blocklist fetching via MSC2313
policy_rooms:
- "!policy-room-1: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
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: Example for the contents of the URL with the JSON data:
```json ```json
{ {

View file

@ -14,6 +14,7 @@ class InviteCheckerConfig:
self.use_blocklist = config.get("use_blocklist", True) self.use_blocklist = config.get("use_blocklist", True)
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
@staticmethod @staticmethod
def parse_config(config): def parse_config(config):
@ -60,9 +61,54 @@ class InviteChecker:
logger.error(f"Error while fetching JSON: {str(e)}") logger.error(f"Error while fetching JSON: {str(e)}")
returnValue(None) returnValue(None)
@inlineCallbacks
def fetch_policy_room_banlist(self):
"""Fetches the ban lists from multiple policy rooms using Synapse API."""
if not self.config.policy_room_ids:
return set() # No policy rooms configured, return an empty set
logger.info(f"Fetching ban lists from policy rooms: {self.config.policy_room_ids}")
banned_entities = set()
banned_entities_by_room = set()
for room_id in self.config.policy_room_ids:
logger.info(f"Fetching ban list from policy room: {room_id}")
try:
# Fetch all state events from the policy room
state_events = yield self.api.get_room_state(room_id)
if isinstance(state_events, dict):
logger.info(f"Received state events in dict format from room {room_id} with {len(state_events)} entries.")
# Loop over the dictionary of state events
for key, event in state_events.items():
event_type = event.get("type", "")
content = event.get("content", {})
# Check for ban events of type 'm.policy.rule.user' and 'm.policy.rule.server'
if event_type in ["m.policy.rule.user", "m.policy.rule.server"]:
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)
banned_entities_by_room = set()
else:
logger.error(f"Unexpected response format from room {room_id}: {type(state_events)}")
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)}")
return banned_entities
@inlineCallbacks @inlineCallbacks
def update_blocklist_allowlist(self): def update_blocklist_allowlist(self):
"""Fetch and update the blocklist, allowlist, and blocklisted room IDs from the URLs.""" """Fetch and update the blocklist, allowlist, and blocklisted room IDs."""
logger.info("Updating blocklist, allowlist, and room blocklist") logger.info("Updating blocklist, allowlist, and room blocklist")
json_data = yield self.fetch_json(self.config.blocklist_allowlist_url) json_data = yield self.fetch_json(self.config.blocklist_allowlist_url)
@ -73,25 +119,43 @@ class InviteChecker:
self.use_blocklist = json_data.get('use_blocklist', True) self.use_blocklist = json_data.get('use_blocklist', True)
self.blocklist = set(json_data.get('blocklist', [])) self.blocklist = set(json_data.get('blocklist', []))
self.allowlist = set(json_data.get('allowlist', [])) self.allowlist = set(json_data.get('allowlist', []))
self.blocklist_room_ids_json = set(json_data.get('blocklist_rooms', []))
self.blocklist_room_ids = set() # Fetch and merge the policy room ban lists from multiple rooms
for room_entry in self.blocklist_room_ids_json: policy_banlist = yield self.fetch_policy_room_banlist()
self.blocklist.update(policy_banlist)
self.blocklist_room_ids = set()
for room_entry in json_data.get('blocklist_rooms', []):
if room_entry.startswith('!'): if room_entry.startswith('!'):
# If it's a room ID, add it directly to the blocklist
logger.info(f"Blocklisting room ID directly: {room_entry}") logger.info(f"Blocklisting room ID directly: {room_entry}")
self.blocklist_room_ids.add(room_entry) self.blocklist_room_ids.add(room_entry)
else: else:
try: room_id = yield self.resolve_room_id(room_entry)
# If it's a room alias, resolve it to a room ID if room_id:
room_id = yield self.resolve_room_id(room_entry) logger.info(f"Blocklisting room: {room_entry} -> {room_id}")
if room_id: self.blocklist_room_ids.add(room_id)
logger.info(f"Blocklisting room: {room_entry} -> {room_id}") else:
self.blocklist_room_ids.add(room_id) logger.error(f"Failed to blocklist room: {room_entry}")
else:
logger.error(f"Failed to blocklist room: {room_entry}")
except Exception as e:
logger.error(f"Failed to blocklist room: {room_entry}. Error: {str(e)}")
logger.info(f"Updated blocklist with {len(self.blocklist)} entries and {len(self.blocklist_room_ids)} room IDs.")
else:
logger.error("Failed to update allowlist/blocklist due to missing JSON data.")
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])
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)
except Exception as e:
logger.error(f"Failed to resolve room alias {room_alias}: {e}")
returnValue(None)
@inlineCallbacks @inlineCallbacks
def get_blocklist_allowlist(self): def get_blocklist_allowlist(self):
@ -117,7 +181,7 @@ class InviteChecker:
blocklist, allowlist, blocklist_room_ids = yield self.get_blocklist_allowlist() blocklist, allowlist, blocklist_room_ids = yield self.get_blocklist_allowlist()
inviter_domain = UserID.from_string(inviter).domain inviter_domain = UserID.from_string(inviter).domain
logger.info(f"Blocklist: {blocklist}, Allowlist: {allowlist}, Blocklist Room IDs: {blocklist_room_ids}") logger.debug(f"Blocklist: {blocklist}, Allowlist: {allowlist}, Blocklist Room IDs: {blocklist_room_ids}")
if room_id in blocklist_room_ids: if room_id in blocklist_room_ids:
logger.info(f"Invite blocked: room {room_id} is blocklisted") logger.info(f"Invite blocked: room {room_id} is blocklisted")