summaryrefslogtreecommitdiff
path: root/synapse/events
diff options
context:
space:
mode:
authorAndrej Shadura <andrewsh@debian.org>2020-06-14 18:05:46 +0200
committerAndrej Shadura <andrewsh@debian.org>2020-06-14 18:05:46 +0200
commit08d5e062dc43f8af47c39699483652c1235ec5d2 (patch)
treefe437819faf5d7e1173b41388ac2973cc0c342d1 /synapse/events
parentda7f96aa2a3b1485dafa016f38aac1d4376b64e7 (diff)
New upstream version 1.15.0
Diffstat (limited to 'synapse/events')
-rw-r--r--synapse/events/spamcheck.py78
-rw-r--r--synapse/events/utils.py35
-rw-r--r--synapse/events/validator.py7
3 files changed, 80 insertions, 40 deletions
diff --git a/synapse/events/spamcheck.py b/synapse/events/spamcheck.py
index a23b6b7b..1ffc9525 100644
--- a/synapse/events/spamcheck.py
+++ b/synapse/events/spamcheck.py
@@ -15,7 +15,7 @@
# limitations under the License.
import inspect
-from typing import Dict
+from typing import Any, Dict, List
from synapse.spam_checker_api import SpamCheckerApi
@@ -26,24 +26,17 @@ if MYPY:
class SpamChecker(object):
def __init__(self, hs: "synapse.server.HomeServer"):
- self.spam_checker = None
+ self.spam_checkers = [] # type: List[Any]
- module = None
- config = None
- try:
- module, config = hs.config.spam_checker
- except Exception:
- pass
-
- if module is not None:
+ for module, config in hs.config.spam_checkers:
# 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:
api = SpamCheckerApi(hs)
- self.spam_checker = module(config=config, api=api)
+ self.spam_checkers.append(module(config=config, api=api))
else:
- self.spam_checker = module(config=config)
+ self.spam_checkers.append(module(config=config))
def check_event_for_spam(self, event: "synapse.events.EventBase") -> bool:
"""Checks if a given event is considered "spammy" by this server.
@@ -58,10 +51,11 @@ class SpamChecker(object):
Returns:
True if the event is spammy.
"""
- if self.spam_checker is None:
- return False
+ for spam_checker in self.spam_checkers:
+ if spam_checker.check_event_for_spam(event):
+ return True
- return self.spam_checker.check_event_for_spam(event)
+ return False
def user_may_invite(
self, inviter_userid: str, invitee_userid: str, room_id: str
@@ -78,12 +72,14 @@ class SpamChecker(object):
Returns:
True if the user may send an invite, otherwise False
"""
- if self.spam_checker is None:
- return True
+ for spam_checker in self.spam_checkers:
+ if (
+ spam_checker.user_may_invite(inviter_userid, invitee_userid, room_id)
+ is False
+ ):
+ return False
- return self.spam_checker.user_may_invite(
- inviter_userid, invitee_userid, room_id
- )
+ return True
def user_may_create_room(self, userid: str) -> bool:
"""Checks if a given user may create a room
@@ -96,10 +92,11 @@ class SpamChecker(object):
Returns:
True if the user may create a room, otherwise False
"""
- if self.spam_checker is None:
- return True
+ for spam_checker in self.spam_checkers:
+ if spam_checker.user_may_create_room(userid) is False:
+ return False
- return self.spam_checker.user_may_create_room(userid)
+ return True
def user_may_create_room_alias(self, userid: str, room_alias: str) -> bool:
"""Checks if a given user may create a room alias
@@ -113,10 +110,11 @@ class SpamChecker(object):
Returns:
True if the user may create a room alias, otherwise False
"""
- if self.spam_checker is None:
- return True
+ for spam_checker in self.spam_checkers:
+ if spam_checker.user_may_create_room_alias(userid, room_alias) is False:
+ return False
- return self.spam_checker.user_may_create_room_alias(userid, room_alias)
+ return True
def user_may_publish_room(self, userid: str, room_id: str) -> bool:
"""Checks if a given user may publish a room to the directory
@@ -130,10 +128,11 @@ class SpamChecker(object):
Returns:
True if the user may publish the room, otherwise False
"""
- if self.spam_checker is None:
- return True
+ for spam_checker in self.spam_checkers:
+ if spam_checker.user_may_publish_room(userid, room_id) is False:
+ return False
- return self.spam_checker.user_may_publish_room(userid, room_id)
+ return True
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.
@@ -150,13 +149,14 @@ class SpamChecker(object):
Returns:
True if the user is spammy.
"""
- if self.spam_checker is None:
- return False
-
- # For backwards compatibility, if the method does not exist on the spam checker, fallback to not interfering.
- checker = getattr(self.spam_checker, "check_username_for_spam", None)
- if not checker:
- return False
- # Make a copy of the user profile object to ensure the spam checker
- # cannot modify it.
- return checker(user_profile.copy())
+ 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
diff --git a/synapse/events/utils.py b/synapse/events/utils.py
index b75b097e..dd340be9 100644
--- a/synapse/events/utils.py
+++ b/synapse/events/utils.py
@@ -14,7 +14,7 @@
# limitations under the License.
import collections
import re
-from typing import Mapping, Union
+from typing import Any, Mapping, Union
from six import string_types
@@ -23,6 +23,7 @@ from frozendict import frozendict
from twisted.internet import defer
from synapse.api.constants import EventTypes, RelationTypes
+from synapse.api.errors import Codes, SynapseError
from synapse.api.room_versions import RoomVersion
from synapse.util.async_helpers import yieldable_gather_results
@@ -449,3 +450,35 @@ def copy_power_levels_contents(
raise TypeError("Invalid power_levels value for %s: %r" % (k, v))
return power_levels
+
+
+def validate_canonicaljson(value: Any):
+ """
+ Ensure that the JSON object is valid according to the rules of canonical JSON.
+
+ See the appendix section 3.1: Canonical JSON.
+
+ This rejects JSON that has:
+ * An integer outside the range of [-2 ^ 53 + 1, 2 ^ 53 - 1]
+ * Floats
+ * NaN, Infinity, -Infinity
+ """
+ if isinstance(value, int):
+ if value <= -(2 ** 53) or 2 ** 53 <= value:
+ raise SynapseError(400, "JSON integer out of range", Codes.BAD_JSON)
+
+ elif isinstance(value, float):
+ # Note that Infinity, -Infinity, and NaN are also considered floats.
+ raise SynapseError(400, "Bad JSON value: float", Codes.BAD_JSON)
+
+ elif isinstance(value, (dict, frozendict)):
+ for v in value.values():
+ validate_canonicaljson(v)
+
+ elif isinstance(value, (list, tuple)):
+ for i in value:
+ validate_canonicaljson(i)
+
+ elif not isinstance(value, (bool, str)) and value is not None:
+ # Other potential JSON values (bool, None, str) are safe.
+ raise SynapseError(400, "Unknown JSON value", Codes.BAD_JSON)
diff --git a/synapse/events/validator.py b/synapse/events/validator.py
index 9b90c9ce..b001c64b 100644
--- a/synapse/events/validator.py
+++ b/synapse/events/validator.py
@@ -18,6 +18,7 @@ from six import integer_types, string_types
from synapse.api.constants import MAX_ALIAS_LENGTH, EventTypes, Membership
from synapse.api.errors import Codes, SynapseError
from synapse.api.room_versions import EventFormatVersions
+from synapse.events.utils import validate_canonicaljson
from synapse.types import EventID, RoomID, UserID
@@ -55,6 +56,12 @@ class EventValidator(object):
if not isinstance(getattr(event, s), string_types):
raise SynapseError(400, "'%s' not a string type" % (s,))
+ # Depending on the room version, ensure the data is spec compliant JSON.
+ if event.room_version.strict_canonicaljson:
+ # Note that only the client controlled portion of the event is
+ # checked, since we trust the portions of the event we created.
+ validate_canonicaljson(event.content)
+
if event.type == EventTypes.Aliases:
if "aliases" in event.content:
for alias in event.content["aliases"]: