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

Allow modules to create and send events into rooms #8479

Merged
merged 16 commits into from
Oct 9, 2020
Merged
Show file tree
Hide file tree
Changes from 5 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
52 changes: 15 additions & 37 deletions synapse/handlers/message.py
Original file line number Diff line number Diff line change
Expand Up @@ -394,7 +394,9 @@ def __init__(self, hs: "HomeServer"):
self.action_generator = hs.get_action_generator()

self.spam_checker = hs.get_spam_checker()
self.third_party_event_rules = None # type: Optional[ThirdPartyEventRules]
self.third_party_event_rules = (
self.hs.get_third_party_event_rules()
) # type: ThirdPartyEventRules

self._block_events_without_consent_error = (
self.config.block_events_without_consent_error
Expand Down Expand Up @@ -672,8 +674,6 @@ async def create_and_send_nonmember_event(
ratelimit: bool = True,
txn_id: Optional[str] = None,
ignore_shadow_ban: bool = False,
ignore_spam_check: bool = False,
ignore_third_party_event_rules: bool = False,
) -> Tuple[EventBase, int]:
"""
Creates an event, then sends it.
Expand All @@ -687,18 +687,13 @@ async def create_and_send_nonmember_event(
txn_id: The transaction ID.
ignore_shadow_ban: True if shadow-banned users should be allowed to
send this event.
ignore_spam_check: True to bypass spam-checking of the event.
ignore_third_party_event_rules: True to bypass checking of the event via
ThirdPartyEventRules.

Returns:
The event, and its stream ordering (if state event deduplication happened,
the previous, duplicate event).

Raises:
ShadowBanError if the requester has been shadow-banned.
SynapseError if ignore_spam_check is False and the event is considered spam, or is
not allowed for some other reason.
"""

if event_dict["type"] == EventTypes.Member:
Expand All @@ -725,20 +720,18 @@ async def create_and_send_nonmember_event(
event.sender,
)

if not ignore_spam_check:
spam_error = self.spam_checker.check_event_for_spam(event)
if spam_error:
if not isinstance(spam_error, str):
spam_error = "Spam is not permitted here"
raise SynapseError(403, spam_error, Codes.FORBIDDEN)
spam_error = self.spam_checker.check_event_for_spam(event)
if spam_error:
if not isinstance(spam_error, str):
spam_error = "Spam is not permitted here"
raise SynapseError(403, spam_error, Codes.FORBIDDEN)

ev = await self.handle_new_client_event(
requester=requester,
event=event,
context=context,
ratelimit=ratelimit,
ignore_shadow_ban=ignore_shadow_ban,
ignore_third_party_event_rules=ignore_third_party_event_rules,
)

# we know it was persisted, so must have a stream ordering
Expand Down Expand Up @@ -818,7 +811,6 @@ async def handle_new_client_event(
ratelimit: bool = True,
extra_users: List[UserID] = [],
ignore_shadow_ban: bool = False,
ignore_third_party_event_rules: bool = False,
) -> EventBase:
"""Processes a new event.

Expand All @@ -834,8 +826,6 @@ async def handle_new_client_event(
context
ratelimit
extra_users: Any extra users to notify about event
ignore_third_party_event_rules: If True, bypass checking the event against
ThirdPartyEventRules.

ignore_shadow_ban: True if shadow-banned users should be allowed to
send this event.
Expand Down Expand Up @@ -878,20 +868,13 @@ async def handle_new_client_event(
else:
room_version = await self.store.get_room_version_id(event.room_id)

if not ignore_third_party_event_rules:
if not self.third_party_event_rules:
# Only initialise this if necessary. It's possible for ThirdPartyEventRules to call
# this function. However, it should not reach this line. If it does, a cyclic
# dependency will be created.
self.third_party_event_rules = self.hs.get_third_party_event_rules()

event_allowed = await self.third_party_event_rules.check_event_allowed(
event, context
event_allowed = await 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
)
if not event_allowed:
raise SynapseError(
403, "This event is not allowed in this context", Codes.FORBIDDEN
)

if event.internal_metadata.is_out_of_band_membership():
# the only sort of out-of-band-membership events we expect to see here
Expand Down Expand Up @@ -1249,12 +1232,7 @@ async def _send_dummy_event_for_room(self, room_id: str) -> bool:
# Since this is a dummy-event it is OK if it is sent by a
# shadow-banned user.
await self.handle_new_client_event(
requester,
event,
context,
ratelimit=False,
ignore_shadow_ban=True,
ignore_third_party_event_rules=True,
requester, event, context, ratelimit=False, ignore_shadow_ban=True,
)
return True
except ConsentNotGivenError:
Expand Down
11 changes: 3 additions & 8 deletions synapse/module_api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

from synapse.api.constants import EventTypes
from synapse.events import EventBase
from synapse.handlers.message import EventCreationHandler
from synapse.http.client import SimpleHttpClient
from synapse.http.site import SynapseRequest
from synapse.logging.context import make_deferred_yieldable, run_in_background
Expand Down Expand Up @@ -348,8 +349,7 @@ async def create_and_send_event_into_room(
"""
if event_type == EventTypes.Member:
raise Exception(
"ThirdPartyEventRules modules are not currently able to send "
"m.room.member events"
"Synapse modules are not currently able to send m.room.member events"
)

# Build event dictionary
Expand All @@ -369,12 +369,7 @@ async def create_and_send_event_into_room(

# Create and send the event
event, _ = await self._event_creation_handler.create_and_send_nonmember_event(
requester,
event_dict,
ratelimit=False,
ignore_shadow_ban=True,
ignore_spam_check=True,
ignore_third_party_event_rules=True,
requester, event_dict, ratelimit=False, ignore_shadow_ban=True,
)

return event
Expand Down
6 changes: 1 addition & 5 deletions tests/module_api/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
from mock import Mock

from synapse.events import EventBase
from synapse.module_api import ModuleApi
from synapse.rest import admin
from synapse.rest.client.v1 import login, room
from synapse.types import create_requester
Expand All @@ -33,6 +32,7 @@ class ModuleApiTestCase(HomeserverTestCase):
def prepare(self, reactor, clock, homeserver):
self.store = homeserver.get_datastore()
self.module_api = homeserver.get_module_api()
self.event_creation_handler = homeserver.get_event_creation_handler()

def test_can_register_user(self):
"""Tests that an external module can register a user"""
Expand Down Expand Up @@ -101,8 +101,6 @@ def test_sending_events_into_room(self):
},
ratelimit=False,
ignore_shadow_ban=True,
ignore_spam_check=True,
ignore_third_party_event_rules=True,
)

# Create and send a state event
Expand Down Expand Up @@ -140,8 +138,6 @@ def test_sending_events_into_room(self):
},
ratelimit=False,
ignore_shadow_ban=True,
ignore_spam_check=True,
ignore_third_party_event_rules=True,
)

# Check that we can't send membership events
Expand Down
22 changes: 21 additions & 1 deletion tests/rest/client/test_third_party_rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import threading
from typing import Dict

from mock import Mock

from synapse.events import EventBase
from synapse.module_api import ModuleApi
from synapse.rest import admin
from synapse.rest.client.v1 import login, room
from synapse.types import Requester, StateMap
Expand All @@ -27,10 +29,11 @@


class ThirdPartyRulesTestModule:
def __init__(self, config, module_api):
def __init__(self, config: Dict, module_api: ModuleApi):
# keep a record of the "current" rules module, so that the test can patch
# it if desired.
thread_local.rules_module = self
self.module_api = module_api

async def on_create_room(
self, requester: Requester, config: dict, is_requester_admin: bool
Expand Down Expand Up @@ -142,3 +145,20 @@ async def check(ev: EventBase, state):
self.assertEqual(channel.result["code"], b"200", channel.result)
ev = channel.json_body
self.assertEqual(ev["content"]["x"], "y")

def test_send_event(self):
"""Tests that the module can send an event into a room via the module api"""
content = {
"msgtype": "m.text",
"body": "Hello!",
}
event = self.get_success(
current_rules_module().module_api.create_and_send_event_into_room(
self.user_id, self.room_id, "m.room.message", content,
)
) # type: EventBase

self.assertEquals(event.sender, self.user_id)
self.assertEquals(event.room_id, self.room_id)
self.assertEquals(event.type, "m.room.message")
self.assertEquals(event.content, content)