2017-09-19 13:20:11 +02:00
|
|
|
# -*- coding: utf-8 -*-
|
2019-04-11 18:08:13 +02:00
|
|
|
# Copyright 2017 New Vector Ltd
|
2019-10-31 16:16:14 +01:00
|
|
|
# Copyright 2019 The Matrix.org Foundation C.I.C.
|
2017-09-19 13:20:11 +02:00
|
|
|
#
|
|
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
# you may not use this file except in compliance with the License.
|
|
|
|
# You may obtain a copy of the License at
|
|
|
|
#
|
|
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
#
|
|
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
# See the License for the specific language governing permissions and
|
|
|
|
# limitations under the License.
|
|
|
|
|
2019-10-31 16:16:14 +01:00
|
|
|
import inspect
|
2020-08-20 21:42:58 +02:00
|
|
|
from typing import Any, Dict, List, Optional, Tuple
|
2019-10-31 16:16:14 +01:00
|
|
|
|
2020-10-07 13:03:26 +02:00
|
|
|
from synapse.spam_checker_api import RegistrationBehaviour
|
2020-08-20 21:42:58 +02:00
|
|
|
from synapse.types import Collection
|
2019-10-31 16:16:14 +01:00
|
|
|
|
2020-02-14 18:49:40 +01:00
|
|
|
MYPY = False
|
|
|
|
if MYPY:
|
2020-10-07 13:03:26 +02:00
|
|
|
import synapse.events
|
2020-02-14 18:49:40 +01:00
|
|
|
import synapse.server
|
|
|
|
|
2017-09-27 11:26:13 +02:00
|
|
|
|
2020-09-04 12:54:56 +02:00
|
|
|
class SpamChecker:
|
2020-02-14 18:49:40 +01:00
|
|
|
def __init__(self, hs: "synapse.server.HomeServer"):
|
2020-05-08 20:25:48 +02:00
|
|
|
self.spam_checkers = [] # type: List[Any]
|
2020-10-07 13:03:26 +02:00
|
|
|
api = hs.get_module_api()
|
2017-09-19 13:20:11 +02:00
|
|
|
|
2020-05-08 20:25:48 +02:00
|
|
|
for module, config in hs.config.spam_checkers:
|
2019-10-31 16:16:14 +01:00
|
|
|
# Older spam checkers don't accept the `api` argument, so we
|
|
|
|
# try and detect support.
|
|
|
|
spam_args = inspect.getfullargspec(module)
|
|
|
|
if "api" in spam_args.args:
|
2020-05-08 20:25:48 +02:00
|
|
|
self.spam_checkers.append(module(config=config, api=api))
|
2019-10-31 16:16:14 +01:00
|
|
|
else:
|
2020-05-08 20:25:48 +02:00
|
|
|
self.spam_checkers.append(module(config=config))
|
2017-09-19 13:20:11 +02:00
|
|
|
|
2020-02-14 18:49:40 +01:00
|
|
|
def check_event_for_spam(self, event: "synapse.events.EventBase") -> bool:
|
2017-09-26 20:20:23 +02:00
|
|
|
"""Checks if a given event is considered "spammy" by this server.
|
2017-09-19 13:20:11 +02:00
|
|
|
|
2017-09-26 20:20:23 +02:00
|
|
|
If the server considers an event spammy, then it will be rejected if
|
|
|
|
sent by a local user. If it is sent by a user on another server, then
|
|
|
|
users receive a blank event.
|
2017-09-19 13:20:11 +02:00
|
|
|
|
2017-09-26 20:20:23 +02:00
|
|
|
Args:
|
2020-02-14 18:49:40 +01:00
|
|
|
event: the event to be checked
|
2017-09-19 13:20:11 +02:00
|
|
|
|
2017-09-26 20:20:23 +02:00
|
|
|
Returns:
|
2020-02-14 18:49:40 +01:00
|
|
|
True if the event is spammy.
|
2017-09-26 20:20:23 +02:00
|
|
|
"""
|
2020-05-08 20:25:48 +02:00
|
|
|
for spam_checker in self.spam_checkers:
|
|
|
|
if spam_checker.check_event_for_spam(event):
|
|
|
|
return True
|
2017-09-19 13:20:11 +02:00
|
|
|
|
2020-05-08 20:25:48 +02:00
|
|
|
return False
|
2017-10-03 15:28:12 +02:00
|
|
|
|
2020-02-14 18:49:40 +01:00
|
|
|
def user_may_invite(
|
|
|
|
self, inviter_userid: str, invitee_userid: str, room_id: str
|
|
|
|
) -> bool:
|
2017-10-03 15:28:12 +02:00
|
|
|
"""Checks if a given user may send an invite
|
|
|
|
|
|
|
|
If this method returns false, the invite will be rejected.
|
|
|
|
|
|
|
|
Args:
|
2020-02-14 18:49:40 +01:00
|
|
|
inviter_userid: The user ID of the sender of the invitation
|
|
|
|
invitee_userid: The user ID targeted in the invitation
|
|
|
|
room_id: The room ID
|
2017-10-03 15:28:12 +02:00
|
|
|
|
|
|
|
Returns:
|
2020-02-14 18:49:40 +01:00
|
|
|
True if the user may send an invite, otherwise False
|
2017-10-03 15:28:12 +02:00
|
|
|
"""
|
2020-05-08 20:25:48 +02:00
|
|
|
for spam_checker in self.spam_checkers:
|
|
|
|
if (
|
|
|
|
spam_checker.user_may_invite(inviter_userid, invitee_userid, room_id)
|
|
|
|
is False
|
|
|
|
):
|
|
|
|
return False
|
2017-10-03 15:28:12 +02:00
|
|
|
|
2020-05-08 20:25:48 +02:00
|
|
|
return True
|
2017-10-04 11:47:54 +02:00
|
|
|
|
2020-02-14 18:49:40 +01:00
|
|
|
def user_may_create_room(self, userid: str) -> bool:
|
2017-10-04 11:47:54 +02:00
|
|
|
"""Checks if a given user may create a room
|
|
|
|
|
|
|
|
If this method returns false, the creation request will be rejected.
|
|
|
|
|
|
|
|
Args:
|
2020-02-14 18:49:40 +01:00
|
|
|
userid: The ID of the user attempting to create a room
|
2017-10-04 11:47:54 +02:00
|
|
|
|
|
|
|
Returns:
|
2020-02-14 18:49:40 +01:00
|
|
|
True if the user may create a room, otherwise False
|
2017-10-04 11:47:54 +02:00
|
|
|
"""
|
2020-05-08 20:25:48 +02:00
|
|
|
for spam_checker in self.spam_checkers:
|
|
|
|
if spam_checker.user_may_create_room(userid) is False:
|
|
|
|
return False
|
2017-10-04 11:47:54 +02:00
|
|
|
|
2020-05-08 20:25:48 +02:00
|
|
|
return True
|
2017-10-04 11:47:54 +02:00
|
|
|
|
2020-02-14 18:49:40 +01:00
|
|
|
def user_may_create_room_alias(self, userid: str, room_alias: str) -> bool:
|
2017-10-04 11:47:54 +02:00
|
|
|
"""Checks if a given user may create a room alias
|
|
|
|
|
|
|
|
If this method returns false, the association request will be rejected.
|
|
|
|
|
|
|
|
Args:
|
2020-02-14 18:49:40 +01:00
|
|
|
userid: The ID of the user attempting to create a room alias
|
|
|
|
room_alias: The alias to be created
|
2017-10-04 11:47:54 +02:00
|
|
|
|
|
|
|
Returns:
|
2020-02-14 18:49:40 +01:00
|
|
|
True if the user may create a room alias, otherwise False
|
2017-10-04 11:47:54 +02:00
|
|
|
"""
|
2020-05-08 20:25:48 +02:00
|
|
|
for spam_checker in self.spam_checkers:
|
|
|
|
if spam_checker.user_may_create_room_alias(userid, room_alias) is False:
|
|
|
|
return False
|
2017-10-04 11:47:54 +02:00
|
|
|
|
2020-05-08 20:25:48 +02:00
|
|
|
return True
|
2017-10-04 15:29:33 +02:00
|
|
|
|
2020-02-14 18:49:40 +01:00
|
|
|
def user_may_publish_room(self, userid: str, room_id: str) -> bool:
|
2017-10-04 15:29:33 +02:00
|
|
|
"""Checks if a given user may publish a room to the directory
|
|
|
|
|
|
|
|
If this method returns false, the publish request will be rejected.
|
|
|
|
|
|
|
|
Args:
|
2020-02-14 18:49:40 +01:00
|
|
|
userid: The user ID attempting to publish the room
|
|
|
|
room_id: The ID of the room that would be published
|
2017-10-04 15:29:33 +02:00
|
|
|
|
|
|
|
Returns:
|
2020-02-14 18:49:40 +01:00
|
|
|
True if the user may publish the room, otherwise False
|
2017-10-04 15:29:33 +02:00
|
|
|
"""
|
2020-05-08 20:25:48 +02:00
|
|
|
for spam_checker in self.spam_checkers:
|
|
|
|
if spam_checker.user_may_publish_room(userid, room_id) is False:
|
|
|
|
return False
|
2017-10-04 15:29:33 +02:00
|
|
|
|
2020-05-08 20:25:48 +02:00
|
|
|
return True
|
2020-02-14 13:17:54 +01:00
|
|
|
|
|
|
|
def check_username_for_spam(self, user_profile: Dict[str, str]) -> bool:
|
|
|
|
"""Checks if a user ID or display name are considered "spammy" by this server.
|
|
|
|
|
|
|
|
If the server considers a username spammy, then it will not be included in
|
|
|
|
user directory results.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
user_profile: The user information to check, it contains the keys:
|
|
|
|
* user_id
|
|
|
|
* display_name
|
|
|
|
* avatar_url
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
True if the user is spammy.
|
|
|
|
"""
|
2020-05-08 20:25:48 +02:00
|
|
|
for spam_checker in self.spam_checkers:
|
|
|
|
# For backwards compatibility, only run if the method exists on the
|
|
|
|
# spam checker
|
|
|
|
checker = getattr(spam_checker, "check_username_for_spam", None)
|
|
|
|
if checker:
|
|
|
|
# Make a copy of the user profile object to ensure the spam checker
|
|
|
|
# cannot modify it.
|
|
|
|
if checker(user_profile.copy()):
|
|
|
|
return True
|
|
|
|
|
|
|
|
return False
|
2020-08-20 21:42:58 +02:00
|
|
|
|
|
|
|
def check_registration_for_spam(
|
|
|
|
self,
|
|
|
|
email_threepid: Optional[dict],
|
|
|
|
username: Optional[str],
|
|
|
|
request_info: Collection[Tuple[str, str]],
|
|
|
|
) -> RegistrationBehaviour:
|
|
|
|
"""Checks if we should allow the given registration request.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
email_threepid: The email threepid used for registering, if any
|
|
|
|
username: The request user name, if any
|
|
|
|
request_info: List of tuples of user agent and IP that
|
|
|
|
were used during the registration process.
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
Enum for how the request should be handled
|
|
|
|
"""
|
|
|
|
|
|
|
|
for spam_checker in self.spam_checkers:
|
|
|
|
# For backwards compatibility, only run if the method exists on the
|
|
|
|
# spam checker
|
|
|
|
checker = getattr(spam_checker, "check_registration_for_spam", None)
|
|
|
|
if checker:
|
|
|
|
behaviour = checker(email_threepid, username, request_info)
|
|
|
|
assert isinstance(behaviour, RegistrationBehaviour)
|
|
|
|
if behaviour != RegistrationBehaviour.ALLOW:
|
|
|
|
return behaviour
|
|
|
|
|
|
|
|
return RegistrationBehaviour.ALLOW
|