123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525 |
- # -*- coding: utf-8 -*-
- # Copyright 2014 - 2016 OpenMarket Ltd
- # Copyright 2018 New Vector Ltd
- #
- # 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.
- """Contains functions for performing events on rooms."""
- from twisted.internet import defer
- from ._base import BaseHandler
- from synapse.types import UserID, RoomAlias, RoomID, RoomStreamToken
- from synapse.api.constants import (
- EventTypes, JoinRules, RoomCreationPreset
- )
- from synapse.api.errors import AuthError, StoreError, SynapseError
- from synapse.util import stringutils
- from synapse.visibility import filter_events_for_client
- from collections import OrderedDict
- import logging
- import math
- import string
- logger = logging.getLogger(__name__)
- id_server_scheme = "https://"
- class RoomCreationHandler(BaseHandler):
- PRESETS_DICT = {
- RoomCreationPreset.PRIVATE_CHAT: {
- "join_rules": JoinRules.INVITE,
- "history_visibility": "shared",
- "original_invitees_have_ops": False,
- "guest_can_join": True,
- },
- RoomCreationPreset.TRUSTED_PRIVATE_CHAT: {
- "join_rules": JoinRules.INVITE,
- "history_visibility": "shared",
- "original_invitees_have_ops": True,
- "guest_can_join": True,
- },
- RoomCreationPreset.PUBLIC_CHAT: {
- "join_rules": JoinRules.PUBLIC,
- "history_visibility": "shared",
- "original_invitees_have_ops": False,
- "guest_can_join": False,
- },
- }
- def __init__(self, hs):
- super(RoomCreationHandler, self).__init__(hs)
- self.spam_checker = hs.get_spam_checker()
- self.event_creation_handler = hs.get_event_creation_handler()
- @defer.inlineCallbacks
- def create_room(self, requester, config, ratelimit=True):
- """ Creates a new room.
- Args:
- requester (Requester): The user who requested the room creation.
- config (dict) : A dict of configuration options.
- Returns:
- The new room ID.
- Raises:
- SynapseError if the room ID couldn't be stored, or something went
- horribly wrong.
- """
- user_id = requester.user.to_string()
- if not self.spam_checker.user_may_create_room(user_id):
- raise SynapseError(403, "You are not permitted to create rooms")
- if ratelimit:
- yield self.ratelimit(requester)
- 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(
- config["room_alias_name"],
- self.hs.hostname,
- )
- 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,))
- invite_3pid_list = config.get("invite_3pid", [])
- visibility = config.get("visibility", None)
- is_public = visibility == "public"
- # 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(
- 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.")
- 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],
- )
- preset_config = config.get(
- "preset",
- RoomCreationPreset.PRIVATE_CHAT
- if visibility == "private"
- else RoomCreationPreset.PUBLIC_CHAT
- )
- 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", {})
- room_member_handler = self.hs.get_room_member_handler()
- yield self._send_events_for_new_room(
- requester,
- room_id,
- room_member_handler,
- 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"]
- 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"]
- 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
- yield room_member_handler.update_membership(
- requester,
- UserID.from_string(invitee),
- room_id,
- "invite",
- ratelimit=False,
- content=content,
- )
- for invite_3pid in invite_3pid_list:
- id_server = invite_3pid["id_server"]
- address = invite_3pid["address"]
- medium = invite_3pid["medium"]
- yield self.hs.get_room_member_handler().do_3pid_invite(
- room_id,
- requester.user,
- medium,
- address,
- id_server,
- requester,
- txn_id=None,
- )
- result = {"room_id": room_id}
- if room_alias:
- result["room_alias"] = room_alias.to_string()
- yield directory_handler.send_room_alias_update_event(
- requester, user_id, room_id
- )
- defer.returnValue(result)
- @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,
- ):
- def create(etype, content, **kwargs):
- e = {
- "type": etype,
- "content": content,
- }
- e.update(event_keys)
- e.update(kwargs)
- return e
- @defer.inlineCallbacks
- def send(etype, content, **kwargs):
- event = create(etype, content, **kwargs)
- yield self.event_creation_handler.create_and_send_nonmember_event(
- creator,
- event,
- ratelimit=False
- )
- 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(
- etype=EventTypes.Create,
- content=creation_content,
- )
- 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": {
- 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,
- }
- if config["original_invitees_have_ops"]:
- for invitee in invite_list:
- power_level_content["users"][invitee] = 100
- power_level_content.update(power_level_content_override)
- yield send(
- etype=EventTypes.PowerLevels,
- content=power_level_content,
- )
- if room_alias and (EventTypes.CanonicalAlias, '') not in initial_state:
- yield send(
- etype=EventTypes.CanonicalAlias,
- content={"alias": room_alias.to_string()},
- )
- if (EventTypes.JoinRules, '') not in initial_state:
- yield send(
- etype=EventTypes.JoinRules,
- content={"join_rule": config["join_rules"]},
- )
- if (EventTypes.RoomHistoryVisibility, '') not in initial_state:
- 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():
- yield send(
- etype=etype,
- state_key=state_key,
- content=content,
- )
- class RoomContextHandler(BaseHandler):
- @defer.inlineCallbacks
- def get_event_context(self, user, room_id, event_id, limit):
- """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
- """
- before_limit = math.floor(limit / 2.)
- after_limit = limit - before_limit
- now_token = yield self.hs.get_event_sources().get_current_token()
- 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,
- 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."
- )
- 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
- 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)
- 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))
|