Skip to content
This repository has been archived by the owner on Apr 26, 2024. It is now read-only.

Allow server admins to define implementations of extra rules for allowing or denying incoming events #5440

Merged
merged 1 commit into from
Jun 14, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/5440.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Allow server admins to define implementations of extra rules for allowing or denying incoming events.
13 changes: 13 additions & 0 deletions docs/sample_config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1351,3 +1351,16 @@ password_config:
# alias: "*"
# room_id: "*"
# action: allow


# Server admins can define a Python module that implements extra rules for
# allowing or denying incoming events. In order to work, this module needs to
# override the methods defined in synapse/events/third_party_rules.py.
#
babolivier marked this conversation as resolved.
Show resolved Hide resolved
# This feature is designed to be used in closed federations only, where each
# participating server enforces the same rules.
#
#third_party_event_rules:
# module: "my_custom_project.SuperRulesSet"
# config:
# example_option: 'things'
2 changes: 2 additions & 0 deletions synapse/config/homeserver.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
from .server_notices_config import ServerNoticesConfig
from .spam_checker import SpamCheckerConfig
from .stats import StatsConfig
from .third_party_event_rules import ThirdPartyRulesConfig
from .tls import TlsConfig
from .user_directory import UserDirectoryConfig
from .voip import VoipConfig
Expand Down Expand Up @@ -73,5 +74,6 @@ class HomeServerConfig(
StatsConfig,
ServerNoticesConfig,
RoomDirectoryConfig,
ThirdPartyRulesConfig,
):
pass
42 changes: 42 additions & 0 deletions synapse/config/third_party_event_rules.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# -*- coding: utf-8 -*-
# Copyright 2019 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.
# 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.

from synapse.util.module_loader import load_module

from ._base import Config


class ThirdPartyRulesConfig(Config):
def read_config(self, config):
self.third_party_event_rules = None

provider = config.get("third_party_event_rules", None)
if provider is not None:
self.third_party_event_rules = load_module(provider)

def default_config(self, **kwargs):
return """\
# Server admins can define a Python module that implements extra rules for
# allowing or denying incoming events. In order to work, this module needs to
# override the methods defined in synapse/events/third_party_rules.py.
#
# This feature is designed to be used in closed federations only, where each
# participating server enforces the same rules.
#
#third_party_event_rules:
# module: "my_custom_project.SuperRulesSet"
# config:
# example_option: 'things'
"""
62 changes: 62 additions & 0 deletions synapse/events/third_party_rules.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# -*- coding: utf-8 -*-
# Copyright 2019 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.
# 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.

from twisted.internet import defer


class ThirdPartyEventRules(object):
babolivier marked this conversation as resolved.
Show resolved Hide resolved
"""Allows server admins to provide a Python module implementing an extra set of rules
to apply when processing events.

This is designed to help admins of closed federations with enforcing custom
behaviours.
"""

def __init__(self, hs):
self.third_party_rules = None

self.store = hs.get_datastore()

module = None
config = None
if hs.config.third_party_event_rules:
module, config = hs.config.third_party_event_rules

if module is not None:
self.third_party_rules = module(config=config)

@defer.inlineCallbacks
def check_event_allowed(self, event, context):
"""Check if a provided event should be allowed in the given context.

Args:
event (synapse.events.EventBase): The event to be checked.
context (synapse.events.snapshot.EventContext): The context of the event.

Returns:
defer.Deferred(bool), True if the event should be allowed, False if not.
"""
if self.third_party_rules is None:
defer.returnValue(True)

prev_state_ids = yield context.get_prev_state_ids(self.store)

# Retrieve the state events from the database.
state_events = {}
for key, event_id in prev_state_ids.items():
state_events[key] = yield self.store.get_event(event_id, allow_none=True)

ret = yield self.third_party_rules.check_event_allowed(event, state_events)
defer.returnValue(ret)
68 changes: 66 additions & 2 deletions synapse/handlers/federation.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
# Copyright 2014-2016 OpenMarket Ltd
# Copyright 2018 New Vector Ltd
# Copyright 2017-2018 New Vector Ltd
# Copyright 2019 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 Down Expand Up @@ -33,6 +34,7 @@
from synapse.api.errors import (
AuthError,
CodeMessageException,
Codes,
FederationDeniedError,
FederationError,
RequestSendFailed,
Expand Down Expand Up @@ -127,6 +129,8 @@ def __init__(self, hs):
self.room_queues = {}
self._room_pdu_linearizer = Linearizer("fed_room_pdu")

self.third_party_event_rules = hs.get_third_party_event_rules()

@defer.inlineCallbacks
def on_receive_pdu(
self, origin, pdu, sent_to_us_directly=False,
Expand Down Expand Up @@ -1258,6 +1262,15 @@ def on_make_join_request(self, room_id, user_id):
logger.warn("Failed to create join %r because %s", event, e)
raise e

event_allowed = yield self.third_party_event_rules.check_event_allowed(
event, context,
)
if not event_allowed:
logger.info("Creation of join %s forbidden by third-party rules", event)
raise SynapseError(
403, "This event is not allowed in this context", Codes.FORBIDDEN,
)

# The remote hasn't signed it yet, obviously. We'll do the full checks
# when we get the event back in `on_send_join_request`
yield self.auth.check_from_context(
Expand Down Expand Up @@ -1300,6 +1313,15 @@ def on_send_join_request(self, origin, pdu):
origin, event
)

event_allowed = yield self.third_party_event_rules.check_event_allowed(
event, context,
)
if not event_allowed:
logger.info("Sending of join %s forbidden by third-party rules", event)
raise SynapseError(
403, "This event is not allowed in this context", Codes.FORBIDDEN,
)

logger.debug(
"on_send_join_request: After _handle_new_event: %s, sigs: %s",
event.event_id,
Expand Down Expand Up @@ -1458,6 +1480,15 @@ def on_make_leave_request(self, room_id, user_id):
builder=builder,
)

event_allowed = yield self.third_party_event_rules.check_event_allowed(
event, context,
)
if not event_allowed:
logger.warning("Creation of leave %s forbidden by third-party rules", event)
raise SynapseError(
403, "This event is not allowed in this context", Codes.FORBIDDEN,
)

try:
# The remote hasn't signed it yet, obviously. We'll do the full checks
# when we get the event back in `on_send_leave_request`
Expand All @@ -1484,10 +1515,19 @@ def on_send_leave_request(self, origin, pdu):

event.internal_metadata.outlier = False

yield self._handle_new_event(
context = yield self._handle_new_event(
origin, event
)

event_allowed = yield self.third_party_event_rules.check_event_allowed(
event, context,
)
if not event_allowed:
logger.info("Sending of leave %s forbidden by third-party rules", event)
raise SynapseError(
403, "This event is not allowed in this context", Codes.FORBIDDEN,
)

logger.debug(
"on_send_leave_request: After _handle_new_event: %s, sigs: %s",
event.event_id,
Expand Down Expand Up @@ -2550,6 +2590,18 @@ def exchange_third_party_invite(
builder=builder
)

event_allowed = yield self.third_party_event_rules.check_event_allowed(
event, context,
)
if not event_allowed:
logger.info(
"Creation of threepid invite %s forbidden by third-party rules",
event,
)
raise SynapseError(
403, "This event is not allowed in this context", Codes.FORBIDDEN,
)

event, context = yield self.add_display_name_to_third_party_invite(
room_version, event_dict, event, context
)
Expand Down Expand Up @@ -2598,6 +2650,18 @@ def on_exchange_third_party_invite_request(self, origin, room_id, event_dict):
builder=builder,
)

event_allowed = yield self.third_party_event_rules.check_event_allowed(
event, context,
)
if not event_allowed:
logger.warning(
"Exchange of threepid invite %s forbidden by third-party rules",
event,
)
raise SynapseError(
403, "This event is not allowed in this context", Codes.FORBIDDEN,
)

event, context = yield self.add_display_name_to_third_party_invite(
room_version, event_dict, event, context
)
Expand Down
14 changes: 12 additions & 2 deletions synapse/handlers/message.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
# Copyright 2014 - 2016 OpenMarket Ltd
# Copyright 2017 - 2018 New Vector Ltd
# Copyright 2014-2016 OpenMarket Ltd
# Copyright 2017-2018 New Vector Ltd
# Copyright 2019 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 Down Expand Up @@ -248,6 +249,7 @@ def __init__(self, hs):
self.action_generator = hs.get_action_generator()

self.spam_checker = hs.get_spam_checker()
self.third_party_event_rules = hs.get_third_party_event_rules()

self._block_events_without_consent_error = (
self.config.block_events_without_consent_error
Expand Down Expand Up @@ -658,6 +660,14 @@ def handle_new_client_event(
else:
room_version = yield self.store.get_room_version(event.room_id)

event_allowed = yield self.third_party_event_rules.check_event_allowed(
event, context,
)
if not event_allowed:
raise SynapseError(
403, "This event is not allowed in this context", Codes.FORBIDDEN,
)

try:
yield self.auth.check_from_context(room_version, event, context)
except AuthError as err:
Expand Down
7 changes: 7 additions & 0 deletions synapse/server.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# -*- coding: utf-8 -*-
# Copyright 2014-2016 OpenMarket Ltd
# Copyright 2017-2018 New Vector Ltd
# Copyright 2019 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 Down Expand Up @@ -35,6 +37,7 @@
from synapse.crypto.keyring import Keyring
from synapse.events.builder import EventBuilderFactory
from synapse.events.spamcheck import SpamChecker
from synapse.events.third_party_rules import ThirdPartyEventRules
from synapse.events.utils import EventClientSerializer
from synapse.federation.federation_client import FederationClient
from synapse.federation.federation_server import (
Expand Down Expand Up @@ -178,6 +181,7 @@ def build_DEPENDENCY(self)
'groups_attestation_renewer',
'secrets',
'spam_checker',
'third_party_event_rules',
'room_member_handler',
'federation_registry',
'server_notices_manager',
Expand Down Expand Up @@ -483,6 +487,9 @@ def build_stats_handler(self):
def build_spam_checker(self):
return SpamChecker(self)

def build_third_party_event_rules(self):
return ThirdPartyEventRules(self)

def build_room_member_handler(self):
if self.config.worker_app:
return RoomMemberWorkerHandler(self)
Expand Down
Loading