MatrixSynapse/synapse/handlers/room.py

526 lines
16 KiB
Python
Raw Normal View History

2014-08-12 16:10:52 +02:00
# -*- coding: utf-8 -*-
2016-01-05 19:01:18 +01:00
# Copyright 2014 - 2016 OpenMarket Ltd
2018-02-06 17:40:38 +01:00
# Copyright 2018 New Vector Ltd
2014-08-12 16:10:52 +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.
2014-08-12 16:10:52 +02:00
"""Contains functions for performing events on rooms."""
from twisted.internet import defer
from ._base import BaseHandler
2016-03-31 14:08:45 +02:00
from synapse.types import UserID, RoomAlias, RoomID, RoomStreamToken
2015-07-13 17:48:06 +02:00
from synapse.api.constants import (
EventTypes, JoinRules, RoomCreationPreset
2015-07-13 17:48:06 +02:00
)
2016-03-31 14:08:45 +02:00
from synapse.api.errors import AuthError, StoreError, SynapseError
2016-04-01 15:06:00 +02:00
from synapse.util import stringutils
from synapse.visibility import filter_events_for_client
2014-08-12 16:10:52 +02:00
from collections import OrderedDict
2014-08-12 16:10:52 +02:00
import logging
2015-10-28 14:45:56 +01:00
import math
import string
2014-08-12 16:10:52 +02:00
logger = logging.getLogger(__name__)
id_server_scheme = "https://"
2014-08-12 16:10:52 +02:00
class RoomCreationHandler(BaseHandler):
2014-08-12 16:10:52 +02:00
2015-07-13 17:48:06 +02:00
PRESETS_DICT = {
2015-07-14 11:20:31 +02:00
RoomCreationPreset.PRIVATE_CHAT: {
2015-07-13 17:48:06 +02:00
"join_rules": JoinRules.INVITE,
"history_visibility": "shared",
"original_invitees_have_ops": False,
"guest_can_join": True,
2015-07-13 17:48:06 +02:00
},
RoomCreationPreset.TRUSTED_PRIVATE_CHAT: {
"join_rules": JoinRules.INVITE,
"history_visibility": "shared",
"original_invitees_have_ops": True,
"guest_can_join": True,
},
2015-07-14 11:20:31 +02:00
RoomCreationPreset.PUBLIC_CHAT: {
2015-07-13 17:48:06 +02:00
"join_rules": JoinRules.PUBLIC,
"history_visibility": "shared",
"original_invitees_have_ops": False,
"guest_can_join": False,
2015-07-13 17:48:06 +02:00
},
}
def __init__(self, hs):
super(RoomCreationHandler, self).__init__(hs)
self.spam_checker = hs.get_spam_checker()
2018-01-15 17:52:07 +01:00
self.event_creation_handler = hs.get_event_creation_handler()
2014-08-12 16:10:52 +02:00
@defer.inlineCallbacks
def create_room(self, requester, config, ratelimit=True):
2014-08-12 16:10:52 +02:00
""" Creates a new room.
Args:
2016-02-15 19:13:10 +01:00
requester (Requester): The user who requested the room creation.
2014-08-12 16:10:52 +02:00
config (dict) : A dict of configuration options.
Returns:
The new room ID.
Raises:
2016-02-15 19:13:10 +01:00
SynapseError if the room ID couldn't be stored, or something went
horribly wrong.
2014-08-12 16:10:52 +02:00
"""
2016-02-15 19:13:10 +01:00
user_id = requester.user.to_string()
if not self.spam_checker.user_may_create_room(user_id):
2017-10-04 13:44:27 +02:00
raise SynapseError(403, "You are not permitted to create rooms")
if ratelimit:
yield self.ratelimit(requester)
2014-08-12 16:10:52 +02:00
if "room_alias_name" in config:
for wchar in string.whitespace:
if wchar in config["room_alias_name"]:
raise SynapseError(400, "Invalid characters in room alias")
room_alias = RoomAlias(
2014-08-12 16:10:52 +02:00
config["room_alias_name"],
self.hs.hostname,
2014-08-12 16:10:52 +02:00
)
mapping = yield self.store.get_association_from_room_alias(
room_alias
)
if mapping:
raise SynapseError(400, "Room alias already taken")
else:
room_alias = None
invite_list = config.get("invite", [])
for i in invite_list:
try:
UserID.from_string(i)
except Exception:
raise SynapseError(400, "Invalid user_id: %s" % (i,))
2016-01-05 12:56:21 +01:00
invite_3pid_list = config.get("invite_3pid", [])
visibility = config.get("visibility", None)
is_public = visibility == "public"
2016-02-15 19:13:10 +01:00
# autogen room IDs and try to create it. We may clash, so just
# try a few times till one goes through, giving up eventually.
attempts = 0
room_id = None
while attempts < 5:
try:
random_string = stringutils.random_string(18)
gen_room_id = RoomID(
2016-02-15 19:13:10 +01:00
random_string,
self.hs.hostname,
)
yield self.store.store_room(
room_id=gen_room_id.to_string(),
room_creator_user_id=user_id,
is_public=is_public
)
room_id = gen_room_id.to_string()
break
except StoreError:
attempts += 1
if not room_id:
raise StoreError(500, "Couldn't generate a room ID.")
2014-08-12 16:10:52 +02:00
if room_alias:
directory_handler = self.hs.get_handlers().directory_handler
yield directory_handler.create_association(
user_id=user_id,
room_id=room_id,
room_alias=room_alias,
servers=[self.hs.hostname],
)
2015-07-13 17:48:06 +02:00
preset_config = config.get(
"preset",
RoomCreationPreset.PRIVATE_CHAT
if visibility == "private"
else RoomCreationPreset.PUBLIC_CHAT
2015-07-13 17:48:06 +02:00
)
raw_initial_state = config.get("initial_state", [])
initial_state = OrderedDict()
for val in raw_initial_state:
initial_state[(val["type"], val.get("state_key", ""))] = val["content"]
creation_content = config.get("creation_content", {})
2018-03-01 11:54:37 +01:00
room_member_handler = self.hs.get_room_member_handler()
2016-02-16 13:00:50 +01:00
yield self._send_events_for_new_room(
requester,
room_id,
room_member_handler,
2015-07-13 17:48:06 +02:00
preset_config=preset_config,
invite_list=invite_list,
initial_state=initial_state,
creation_content=creation_content,
room_alias=room_alias,
power_level_content_override=config.get("power_level_content_override", {})
)
if "name" in config:
name = config["name"]
2018-01-15 17:52:07 +01:00
yield self.event_creation_handler.create_and_send_nonmember_event(
requester,
{
"type": EventTypes.Name,
"room_id": room_id,
"sender": user_id,
"state_key": "",
"content": {"name": name},
},
ratelimit=False)
if "topic" in config:
topic = config["topic"]
2018-01-15 17:52:07 +01:00
yield self.event_creation_handler.create_and_send_nonmember_event(
requester,
{
"type": EventTypes.Topic,
"room_id": room_id,
"sender": user_id,
"state_key": "",
"content": {"topic": topic},
},
ratelimit=False)
for invitee in invite_list:
content = {}
is_direct = config.get("is_direct", None)
if is_direct:
content["is_direct"] = is_direct
2017-11-28 16:23:26 +01:00
yield room_member_handler.update_membership(
requester,
UserID.from_string(invitee),
room_id,
"invite",
ratelimit=False,
2016-09-12 17:34:20 +02:00
content=content,
)
2016-01-05 12:56:21 +01:00
for invite_3pid in invite_3pid_list:
id_server = invite_3pid["id_server"]
address = invite_3pid["address"]
medium = invite_3pid["medium"]
2018-03-01 11:54:37 +01:00
yield self.hs.get_room_member_handler().do_3pid_invite(
2016-01-05 12:56:21 +01:00
room_id,
2016-02-16 13:00:50 +01:00
requester.user,
2016-01-05 12:56:21 +01:00
medium,
address,
id_server,
requester,
2016-01-05 13:57:45 +01:00
txn_id=None,
2016-01-05 12:56:21 +01:00
)
2014-08-12 16:10:52 +02:00
result = {"room_id": room_id}
2014-08-12 16:10:52 +02:00
if room_alias:
result["room_alias"] = room_alias.to_string()
2014-11-20 18:26:36 +01:00
yield directory_handler.send_room_alias_update_event(
requester, user_id, room_id
2014-11-20 18:26:36 +01:00
)
2014-08-12 16:10:52 +02:00
defer.returnValue(result)
2016-02-16 13:00:50 +01:00
@defer.inlineCallbacks
def _send_events_for_new_room(
self,
creator, # A Requester object.
room_id,
room_member_handler,
preset_config,
invite_list,
initial_state,
creation_content,
room_alias,
power_level_content_override,
2016-02-16 13:00:50 +01:00
):
2014-12-08 11:16:18 +01:00
def create(etype, content, **kwargs):
e = {
"type": etype,
"content": content,
}
e.update(event_keys)
2014-12-08 11:16:18 +01:00
e.update(kwargs)
return e
2014-09-01 17:15:34 +02:00
2016-02-16 13:00:50 +01:00
@defer.inlineCallbacks
def send(etype, content, **kwargs):
event = create(etype, content, **kwargs)
2018-01-15 17:52:07 +01:00
yield self.event_creation_handler.create_and_send_nonmember_event(
creator,
event,
ratelimit=False
)
2016-02-16 13:00:50 +01:00
config = RoomCreationHandler.PRESETS_DICT[preset_config]
creator_id = creator.user.to_string()
event_keys = {
"room_id": room_id,
"sender": creator_id,
"state_key": "",
}
creation_content.update({"creator": creator_id})
yield send(
2014-12-16 12:29:05 +01:00
etype=EventTypes.Create,
content=creation_content,
)
2016-02-16 13:00:50 +01:00
yield room_member_handler.update_membership(
creator,
creator.user,
room_id,
"join",
ratelimit=False,
)
# We treat the power levels override specially as this needs to be one
# of the first events that get sent into a room.
pl_content = initial_state.pop((EventTypes.PowerLevels, ''), None)
if pl_content is not None:
yield send(
etype=EventTypes.PowerLevels,
content=pl_content,
)
else:
power_level_content = {
"users": {
2016-02-16 13:00:50 +01:00
creator_id: 100,
},
"users_default": 0,
"events": {
EventTypes.Name: 50,
EventTypes.PowerLevels: 100,
EventTypes.RoomHistoryVisibility: 100,
EventTypes.CanonicalAlias: 50,
EventTypes.RoomAvatar: 50,
},
"events_default": 0,
"state_default": 50,
"ban": 50,
"kick": 50,
"redact": 50,
"invite": 0,
}
2015-07-13 17:48:06 +02:00
if config["original_invitees_have_ops"]:
for invitee in invite_list:
power_level_content["users"][invitee] = 100
2015-07-13 17:48:06 +02:00
power_level_content.update(power_level_content_override)
2016-02-16 13:00:50 +01:00
yield send(
etype=EventTypes.PowerLevels,
content=power_level_content,
)
2015-09-30 17:46:24 +02:00
if room_alias and (EventTypes.CanonicalAlias, '') not in initial_state:
2016-02-16 13:00:50 +01:00
yield send(
2015-09-30 17:46:24 +02:00
etype=EventTypes.CanonicalAlias,
content={"alias": room_alias.to_string()},
)
if (EventTypes.JoinRules, '') not in initial_state:
2016-02-16 13:00:50 +01:00
yield send(
etype=EventTypes.JoinRules,
content={"join_rule": config["join_rules"]},
)
if (EventTypes.RoomHistoryVisibility, '') not in initial_state:
2016-02-16 13:00:50 +01:00
yield send(
etype=EventTypes.RoomHistoryVisibility,
content={"history_visibility": config["history_visibility"]}
)
if config["guest_can_join"]:
if (EventTypes.GuestAccess, '') not in initial_state:
yield send(
etype=EventTypes.GuestAccess,
content={"guest_access": "can_join"}
)
for (etype, state_key), content in initial_state.items():
2016-02-16 13:00:50 +01:00
yield send(
etype=etype,
state_key=state_key,
content=content,
2016-02-16 13:00:50 +01:00
)
2014-08-12 16:10:52 +02:00
2015-10-28 14:45:56 +01:00
class RoomContextHandler(BaseHandler):
@defer.inlineCallbacks
2017-02-20 16:02:01 +01:00
def get_event_context(self, user, room_id, event_id, limit):
2015-10-28 15:05:50 +01:00
"""Retrieves events, pagination tokens and state around a given event
in a room.
Args:
user (UserID)
room_id (str)
event_id (str)
limit (int): The maximum number of events to return in total
(excluding state).
Returns:
dict, or None if the event isn't found
2015-10-28 15:05:50 +01:00
"""
2016-02-02 18:18:50 +01:00
before_limit = math.floor(limit / 2.)
2015-10-28 14:45:56 +01:00
after_limit = limit - before_limit
now_token = yield self.hs.get_event_sources().get_current_token()
2017-02-20 15:54:50 +01:00
users = yield self.store.get_users_in_room(room_id)
is_peeking = user.to_string() not in users
def filter_evts(events):
return filter_events_for_client(
self.store,
user.to_string(),
events,
2017-02-20 15:54:50 +01:00
is_peeking=is_peeking
)
event = yield self.store.get_event(event_id, get_prev_content=True,
allow_none=True)
if not event:
defer.returnValue(None)
return
filtered = yield(filter_evts([event]))
if not filtered:
raise AuthError(
403,
"You don't have permission to access that event."
)
2015-10-28 14:45:56 +01:00
results = yield self.store.get_events_around(
room_id, event_id, before_limit, after_limit
)
results["events_before"] = yield filter_evts(results["events_before"])
results["events_after"] = yield filter_evts(results["events_after"])
results["event"] = event
2015-10-28 14:45:56 +01:00
if results["events_after"]:
last_event_id = results["events_after"][-1].event_id
else:
last_event_id = event_id
state = yield self.store.get_state_for_events(
[last_event_id], None
)
results["state"] = state[last_event_id].values()
results["start"] = now_token.copy_and_replace(
"room_key", results["start"]
).to_string()
results["end"] = now_token.copy_and_replace(
"room_key", results["end"]
).to_string()
defer.returnValue(results)
class RoomEventSource(object):
def __init__(self, hs):
self.store = hs.get_datastore()
@defer.inlineCallbacks
def get_new_events(
self,
user,
from_key,
limit,
room_ids,
is_guest,
explicit_room_id=None,
):
# We just ignore the key for now.
to_key = yield self.get_current_key()
from_token = RoomStreamToken.parse(from_key)
if from_token.topological:
logger.warn("Stream has topological part!!!! %r", from_key)
from_key = "s%s" % (from_token.stream,)
app_service = self.store.get_app_service_by_user_id(
user.to_string()
)
if app_service:
# We no longer support AS users using /sync directly.
# See https://github.com/matrix-org/matrix-doc/issues/1144
raise NotImplementedError()
else:
room_events = yield self.store.get_membership_changes_for_user(
user.to_string(), from_key, to_key
)
room_to_events = yield self.store.get_room_events_stream_for_rooms(
room_ids=room_ids,
from_key=from_key,
to_key=to_key,
limit=limit or 10,
order='ASC',
)
events = list(room_events)
events.extend(e for evs, _ in room_to_events.values() for e in evs)
2016-02-01 17:32:46 +01:00
events.sort(key=lambda e: e.internal_metadata.order)
if limit:
events[:] = events[:limit]
if events:
end_key = events[-1].internal_metadata.after
else:
end_key = to_key
defer.returnValue((events, end_key))
def get_current_key(self):
return self.store.get_room_events_max_id()
def get_current_key_for_room(self, room_id):
return self.store.get_room_events_max_id(room_id)
@defer.inlineCallbacks
def get_pagination_rows(self, user, config, key):
events, next_key = yield self.store.paginate_room_events(
room_id=key,
from_key=config.from_key,
to_key=config.to_key,
direction=config.direction,
limit=config.limit,
)
defer.returnValue((events, next_key))