Skip to content

Commit

Permalink
Let's port to Synapse module API (#128)
Browse files Browse the repository at this point in the history
  • Loading branch information
Yoric authored Feb 9, 2022
1 parent f74cf8a commit 9c9bd0e
Show file tree
Hide file tree
Showing 7 changed files with 108 additions and 28 deletions.
40 changes: 21 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
12 changes: 12 additions & 0 deletions mx-tester.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
name: mjolnir

up:
before:
# Launch the reverse proxy, listening for connections *only* on the local host.
Expand All @@ -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
Expand Down
1 change: 1 addition & 0 deletions synapse_antispam/mjolnir/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
from .antispam import AntiSpam
from .antispam import Module
50 changes: 47 additions & 3 deletions synapse_antispam/mjolnir/antispam.py
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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)
Expand Down Expand Up @@ -77,7 +85,11 @@ def check_event_for_spam(self, event):
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
Expand Down Expand Up @@ -113,7 +125,9 @@ def check_username_for_spam(self, user_profile):

# 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
Expand All @@ -127,3 +141,33 @@ def user_may_publish_room(self, user_id, room_id):
@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)
28 changes: 23 additions & 5 deletions synapse_antispam/mjolnir/ban_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -52,7 +59,11 @@ def run(with_event):
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)
Expand All @@ -61,8 +72,13 @@ def run(with_event):
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:
Expand All @@ -73,4 +89,6 @@ def run(with_event):
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]
)
3 changes: 3 additions & 0 deletions synapse_antispam/mjolnir/list_rule.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,13 @@
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
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion synapse_antispam/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

setup(
name="mjolnir",
version="0.0.1",
version="0.1.0",
packages=find_packages(),
description="Mjolnir Antispam",
include_package_data=True,
Expand Down

0 comments on commit 9c9bd0e

Please sign in to comment.