Let's port to Synapse module API (#128)

This commit is contained in:
David Teller 2022-02-09 08:40:33 +01:00 committed by GitHub
parent f74cf8a6e5
commit 9c9bd0e029
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 108 additions and 28 deletions

View File

@ -84,6 +84,8 @@ set up:
## Synapse Module
**This requires Synapse 1.37.0 or higher**
Using the bot to manage your rooms is great, however if you want to use your ban lists
(or someone else's) on your server to affect all of your users then a Synapse module
is needed. Primarily meant to block invites from undesired homeservers/users, Mjolnir's
@ -99,25 +101,25 @@ pip install -e "git+https://github.com/matrix-org/mjolnir.git#egg=mjolnir&subdir
Then add the following to your `homeserver.yaml`:
```yaml
spam_checker:
module: mjolnir.AntiSpam
config:
# Prevent servers/users in the ban lists from inviting users on this
# server to rooms. Default true.
block_invites: true
# Flag messages sent by servers/users in the ban lists as spam. Currently
# this means that spammy messages will appear as empty to users. Default
# false.
block_messages: false
# Remove users from the user directory search by filtering matrix IDs and
# display names by the entries in the user ban list. Default false.
block_usernames: false
# The room IDs of the ban lists to honour. Unlike other parts of Mjolnir,
# this list cannot be room aliases or permalinks. This server is expected
# to already be joined to the room - Mjolnir will not automatically join
# these rooms.
ban_lists:
- "!roomid:example.org"
modules:
- module: mjolnir.Module
config:
# Prevent servers/users in the ban lists from inviting users on this
# server to rooms. Default true.
block_invites: true
# Flag messages sent by servers/users in the ban lists as spam. Currently
# this means that spammy messages will appear as empty to users. Default
# false.
block_messages: false
# Remove users from the user directory search by filtering matrix IDs and
# display names by the entries in the user ban list. Default false.
block_usernames: false
# The room IDs of the ban lists to honour. Unlike other parts of Mjolnir,
# this list cannot be room aliases or permalinks. This server is expected
# to already be joined to the room - Mjolnir will not automatically join
# these rooms.
ban_lists:
- "!roomid:example.org"
```
*Note*: Although this is described as a "spam checker", it does much more than fight

View File

@ -1,4 +1,5 @@
name: mjolnir
up:
before:
# Launch the reverse proxy, listening for connections *only* on the local host.
@ -10,9 +11,20 @@ up:
run:
- yarn test:integration
down:
finally:
- docker stop mjolnir-test-reverse-proxy || true
modules:
- name: mjolnir
build:
- cp -r synapse_antispam $MX_TEST_MODULE_DIR
config:
module: mjolnir.Module
config: {}
homeserver:
# Basic configuration.
server_name: localhost:9999

View File

@ -1 +1,2 @@
from .antispam import AntiSpam
from .antispam import Module

View File

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2019 The Matrix.org Foundation C.I.C.
# Copyright 2019-2022 The Matrix.org Foundation C.I.C.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@ -14,13 +14,21 @@
# limitations under the License.
import logging
from typing import Dict, Union
from .list_rule import ALL_RULE_TYPES, RECOMMENDATION_BAN
from .ban_list import BanList
from synapse.types import UserID
logger = logging.getLogger("synapse.contrib." + __name__)
class AntiSpam(object):
"""
Implements the old synapse spam-checker API, for compatibility with older configurations.
See https://github.com/matrix-org/synapse/blob/master/docs/spam_checker.md
"""
def __init__(self, config, api):
self.block_invites = config.get("block_invites", True)
self.block_messages = config.get("block_messages", False)
@ -77,7 +85,11 @@ class AntiSpam(object):
state_key = event.get("state_key", None)
# Rebuild the rules if there's an event for our ban lists
if state_key is not None and event_type in ALL_RULE_TYPES and room_id in self.list_room_ids:
if (
state_key is not None
and event_type in ALL_RULE_TYPES
and room_id in self.list_room_ids
):
logger.info("Received ban list event - updating list")
self.get_list_for_room(room_id).build(with_event=event)
return False # Ban list updates aren't spam
@ -113,7 +125,9 @@ class AntiSpam(object):
# Check whether the user ID or display name matches any of the banned
# patterns.
return self.is_user_banned(user_profile["user_id"]) or self.is_user_banned(user_profile["display_name"])
return self.is_user_banned(user_profile["user_id"]) or self.is_user_banned(
user_profile["display_name"]
)
def user_may_create_room(self, user_id):
return True # allowed
@ -127,3 +141,33 @@ class AntiSpam(object):
@staticmethod
def parse_config(config):
return config # no parsing needed
# New module API
class Module:
"""
Our main entry point. Implements the Synapse Module API.
"""
def __init__(self, config, api):
self.antispam = AntiSpam(config, api)
self.antispam.api.register_spam_checker_callbacks(
check_event_for_spam=self.check_event_for_spam,
user_may_invite=self.user_may_invite,
check_username_for_spam=self.check_username_for_spam,
)
# Callbacks for `register_spam_checker_callbacks`
# Note that these are `async`, by opposition to the APIs in `AntiSpam`.
async def check_event_for_spam(
self, event: "synapse.events.EventBase"
) -> Union[bool, str]:
return self.antispam.check_event_for_spam(event)
async def user_may_invite(
self, inviter_user_id: str, invitee_user_id: str, room_id: str
) -> bool:
return self.antispam.user_may_invite(inviter_user_id, invitee_user_id, room_id)
async def check_username_for_spam(self, user_profile: Dict[str, str]) -> bool:
return self.antispam.check_username_for_spam(user_profile)

View File

@ -14,12 +14,19 @@
# limitations under the License.
import logging
from .list_rule import ListRule, ALL_RULE_TYPES, USER_RULE_TYPES, SERVER_RULE_TYPES, ROOM_RULE_TYPES
from .list_rule import (
ListRule,
ALL_RULE_TYPES,
USER_RULE_TYPES,
SERVER_RULE_TYPES,
ROOM_RULE_TYPES,
)
from twisted.internet import defer
from synapse.metrics.background_process_metrics import run_as_background_process
logger = logging.getLogger("synapse.contrib." + __name__)
class BanList(object):
def __init__(self, api, room_id):
self.api = api
@ -52,7 +59,11 @@ class BanList(object):
w_state_key = with_event.get("state_key", "")
w_event_id = with_event.event_id
event_id = event.event_id
if w_event_type == event_type and w_state_key == state_key and w_event_id != event_id:
if (
w_event_type == event_type
and w_state_key == state_key
and w_event_id != event_id
):
continue
entity = content.get("entity", None)
@ -61,8 +72,13 @@ class BanList(object):
if entity is None or recommendation is None or reason is None:
continue # invalid event
logger.info("Adding rule %s/%s with action %s" % (event_type, entity, recommendation))
rule = ListRule(entity=entity, action=recommendation, reason=reason, kind=event_type)
logger.info(
"Adding rule %s/%s with action %s"
% (event_type, entity, recommendation)
)
rule = ListRule(
entity=entity, action=recommendation, reason=reason, kind=event_type
)
if event_type in USER_RULE_TYPES:
self.user_rules.append(rule)
elif event_type in ROOM_RULE_TYPES:
@ -73,4 +89,6 @@ class BanList(object):
run_as_background_process("mjolnir_build_ban_list", run, with_event)
def get_relevant_state_events(self):
return self.api.get_state_events_in_room(self.room_id, [(t, None) for t in ALL_RULE_TYPES])
return self.api.get_state_events_in_room(
self.room_id, [(t, None) for t in ALL_RULE_TYPES]
)

View File

@ -26,11 +26,13 @@ ROOM_RULE_TYPES = [RULE_ROOM, "m.room.rule.room", "org.matrix.mjolnir.rule.room"
SERVER_RULE_TYPES = [RULE_SERVER, "m.room.rule.server", "org.matrix.mjolnir.rule.server"]
ALL_RULE_TYPES = [*USER_RULE_TYPES, *ROOM_RULE_TYPES, *SERVER_RULE_TYPES]
def recommendation_to_stable(recommendation):
if recommendation in RECOMMENDATION_BAN_TYPES:
return RECOMMENDATION_BAN
return None
def rule_type_to_stable(rule):
if rule in USER_RULE_TYPES:
return RULE_USER
@ -40,6 +42,7 @@ def rule_type_to_stable(rule):
return RULE_SERVER
return None
class ListRule(object):
def __init__(self, entity, action, reason, kind):
self.entity = entity

View File

@ -2,7 +2,7 @@ from setuptools import setup, find_packages
setup(
name="mjolnir",
version="0.0.1",
version="0.1.0",
packages=find_packages(),
description="Mjolnir Antispam",
include_package_data=True,