123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569 |
- # -*- coding: utf-8 -*-
- # Copyright 2014 - 2016 OpenMarket 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.util.async import concurrently_execute
- from synapse.util.caches.response_cache import ResponseCache
- 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,
- },
- RoomCreationPreset.TRUSTED_PRIVATE_CHAT: {
- "join_rules": JoinRules.INVITE,
- "history_visibility": "shared",
- "original_invitees_have_ops": True,
- },
- RoomCreationPreset.PUBLIC_CHAT: {
- "join_rules": JoinRules.PUBLIC,
- "history_visibility": "shared",
- "original_invitees_have_ops": False,
- },
- }
- @defer.inlineCallbacks
- def create_room(self, requester, config):
- """ 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()
- 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.create(
- 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:
- 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.create(
- 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", {})
- msg_handler = self.hs.get_handlers().message_handler
- room_member_handler = self.hs.get_handlers().room_member_handler
- yield self._send_events_for_new_room(
- requester,
- room_id,
- msg_handler,
- room_member_handler,
- preset_config=preset_config,
- invite_list=invite_list,
- initial_state=initial_state,
- creation_content=creation_content,
- room_alias=room_alias,
- )
- if "name" in config:
- name = config["name"]
- yield msg_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 msg_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:
- yield room_member_handler.update_membership(
- requester,
- UserID.from_string(invitee),
- room_id,
- "invite",
- ratelimit=False,
- )
- 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_handlers().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,
- msg_handler,
- room_member_handler,
- preset_config,
- invite_list,
- initial_state,
- creation_content,
- room_alias
- ):
- 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 msg_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,
- )
- if (EventTypes.PowerLevels, '') not in initial_state:
- 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
- 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"]}
- )
- for (etype, state_key), content in initial_state.items():
- yield send(
- etype=etype,
- state_key=state_key,
- content=content,
- )
- class RoomListHandler(BaseHandler):
- def __init__(self, hs):
- super(RoomListHandler, self).__init__(hs)
- self.response_cache = ResponseCache()
- def get_public_room_list(self):
- result = self.response_cache.get(())
- if not result:
- result = self.response_cache.set((), self._get_public_room_list())
- return result
- @defer.inlineCallbacks
- def _get_public_room_list(self):
- room_ids = yield self.store.get_public_room_ids()
- results = []
- @defer.inlineCallbacks
- def handle_room(room_id):
- aliases = yield self.store.get_aliases_for_room(room_id)
- # We pull each bit of state out indvidually to avoid pulling the
- # full state into memory. Due to how the caching works this should
- # be fairly quick, even if not originally in the cache.
- def get_state(etype, state_key):
- return self.state_handler.get_current_state(room_id, etype, state_key)
- # Double check that this is actually a public room.
- join_rules_event = yield get_state(EventTypes.JoinRules, "")
- if join_rules_event:
- join_rule = join_rules_event.content.get("join_rule", None)
- if join_rule and join_rule != JoinRules.PUBLIC:
- defer.returnValue(None)
- result = {"room_id": room_id}
- if aliases:
- result["aliases"] = aliases
- name_event = yield get_state(EventTypes.Name, "")
- if name_event:
- name = name_event.content.get("name", None)
- if name:
- result["name"] = name
- topic_event = yield get_state(EventTypes.Topic, "")
- if topic_event:
- topic = topic_event.content.get("topic", None)
- if topic:
- result["topic"] = topic
- canonical_event = yield get_state(EventTypes.CanonicalAlias, "")
- if canonical_event:
- canonical_alias = canonical_event.content.get("alias", None)
- if canonical_alias:
- result["canonical_alias"] = canonical_alias
- visibility_event = yield get_state(EventTypes.RoomHistoryVisibility, "")
- visibility = None
- if visibility_event:
- visibility = visibility_event.content.get("history_visibility", None)
- result["world_readable"] = visibility == "world_readable"
- guest_event = yield get_state(EventTypes.GuestAccess, "")
- guest = None
- if guest_event:
- guest = guest_event.content.get("guest_access", None)
- result["guest_can_join"] = guest == "can_join"
- avatar_event = yield get_state("m.room.avatar", "")
- if avatar_event:
- avatar_url = avatar_event.content.get("url", None)
- if avatar_url:
- result["avatar_url"] = avatar_url
- joined_users = yield self.store.get_users_in_room(room_id)
- result["num_joined_members"] = len(joined_users)
- results.append(result)
- yield concurrently_execute(handle_room, room_ids, 10)
- # FIXME (erikj): START is no longer a valid value
- defer.returnValue({"start": "START", "end": "END", "chunk": results})
- class RoomContextHandler(BaseHandler):
- @defer.inlineCallbacks
- def get_event_context(self, user, room_id, event_id, limit, is_guest):
- """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()
- def filter_evts(events):
- return self._filter_events_for_client(
- user.to_string(),
- events,
- is_peeking=is_guest)
- 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,
- ):
- # 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 = yield self.store.get_app_service_by_user_id(
- user.to_string()
- )
- if app_service:
- events, end_key = yield self.store.get_appservice_room_stream(
- service=app_service,
- from_key=from_key,
- to_key=to_key,
- limit=limit,
- )
- 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, direction='f'):
- return self.store.get_room_events_max_id(direction)
- @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))
|