directory.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342
  1. # -*- coding: utf-8 -*-
  2. # Copyright 2014-2016 OpenMarket Ltd
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License");
  5. # you may not use this file except in compliance with the License.
  6. # You may obtain a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS,
  12. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. # See the License for the specific language governing permissions and
  14. # limitations under the License.
  15. from twisted.internet import defer
  16. from ._base import BaseHandler
  17. from synapse.api.errors import SynapseError, Codes, CodeMessageException, AuthError
  18. from synapse.api.constants import EventTypes
  19. from synapse.types import RoomAlias, UserID
  20. import logging
  21. import string
  22. logger = logging.getLogger(__name__)
  23. class DirectoryHandler(BaseHandler):
  24. def __init__(self, hs):
  25. super(DirectoryHandler, self).__init__(hs)
  26. self.state = hs.get_state_handler()
  27. self.appservice_handler = hs.get_application_service_handler()
  28. self.federation = hs.get_replication_layer()
  29. self.federation.register_query_handler(
  30. "directory", self.on_directory_query
  31. )
  32. @defer.inlineCallbacks
  33. def _create_association(self, room_alias, room_id, servers=None, creator=None):
  34. # general association creation for both human users and app services
  35. for wchar in string.whitespace:
  36. if wchar in room_alias.localpart:
  37. raise SynapseError(400, "Invalid characters in room alias")
  38. if not self.hs.is_mine(room_alias):
  39. raise SynapseError(400, "Room alias must be local")
  40. # TODO(erikj): Change this.
  41. # TODO(erikj): Add transactions.
  42. # TODO(erikj): Check if there is a current association.
  43. if not servers:
  44. servers = yield self.store.get_joined_hosts_for_room(room_id)
  45. if not servers:
  46. raise SynapseError(400, "Failed to get server list")
  47. yield self.store.create_room_alias_association(
  48. room_alias,
  49. room_id,
  50. servers,
  51. creator=creator,
  52. )
  53. @defer.inlineCallbacks
  54. def create_association(self, user_id, room_alias, room_id, servers=None):
  55. # association creation for human users
  56. # TODO(erikj): Do user auth.
  57. can_create = yield self.can_modify_alias(
  58. room_alias,
  59. user_id=user_id
  60. )
  61. if not can_create:
  62. raise SynapseError(
  63. 400, "This alias is reserved by an application service.",
  64. errcode=Codes.EXCLUSIVE
  65. )
  66. yield self._create_association(room_alias, room_id, servers, creator=user_id)
  67. @defer.inlineCallbacks
  68. def create_appservice_association(self, service, room_alias, room_id,
  69. servers=None):
  70. if not service.is_interested_in_alias(room_alias.to_string()):
  71. raise SynapseError(
  72. 400, "This application service has not reserved"
  73. " this kind of alias.", errcode=Codes.EXCLUSIVE
  74. )
  75. # association creation for app services
  76. yield self._create_association(room_alias, room_id, servers)
  77. @defer.inlineCallbacks
  78. def delete_association(self, requester, user_id, room_alias):
  79. # association deletion for human users
  80. can_delete = yield self._user_can_delete_alias(room_alias, user_id)
  81. if not can_delete:
  82. raise AuthError(
  83. 403, "You don't have permission to delete the alias.",
  84. )
  85. can_delete = yield self.can_modify_alias(
  86. room_alias,
  87. user_id=user_id
  88. )
  89. if not can_delete:
  90. raise SynapseError(
  91. 400, "This alias is reserved by an application service.",
  92. errcode=Codes.EXCLUSIVE
  93. )
  94. room_id = yield self._delete_association(room_alias)
  95. try:
  96. yield self.send_room_alias_update_event(
  97. requester,
  98. requester.user.to_string(),
  99. room_id
  100. )
  101. yield self._update_canonical_alias(
  102. requester,
  103. requester.user.to_string(),
  104. room_id,
  105. room_alias,
  106. )
  107. except AuthError as e:
  108. logger.info("Failed to update alias events: %s", e)
  109. defer.returnValue(room_id)
  110. @defer.inlineCallbacks
  111. def delete_appservice_association(self, service, room_alias):
  112. if not service.is_interested_in_alias(room_alias.to_string()):
  113. raise SynapseError(
  114. 400,
  115. "This application service has not reserved this kind of alias",
  116. errcode=Codes.EXCLUSIVE
  117. )
  118. yield self._delete_association(room_alias)
  119. @defer.inlineCallbacks
  120. def _delete_association(self, room_alias):
  121. if not self.hs.is_mine(room_alias):
  122. raise SynapseError(400, "Room alias must be local")
  123. room_id = yield self.store.delete_room_alias(room_alias)
  124. defer.returnValue(room_id)
  125. @defer.inlineCallbacks
  126. def get_association(self, room_alias):
  127. room_id = None
  128. if self.hs.is_mine(room_alias):
  129. result = yield self.get_association_from_room_alias(
  130. room_alias
  131. )
  132. if result:
  133. room_id = result.room_id
  134. servers = result.servers
  135. else:
  136. try:
  137. result = yield self.federation.make_query(
  138. destination=room_alias.domain,
  139. query_type="directory",
  140. args={
  141. "room_alias": room_alias.to_string(),
  142. },
  143. retry_on_dns_fail=False,
  144. )
  145. except CodeMessageException as e:
  146. logging.warn("Error retrieving alias")
  147. if e.code == 404:
  148. result = None
  149. else:
  150. raise
  151. if result and "room_id" in result and "servers" in result:
  152. room_id = result["room_id"]
  153. servers = result["servers"]
  154. if not room_id:
  155. raise SynapseError(
  156. 404,
  157. "Room alias %s not found" % (room_alias.to_string(),),
  158. Codes.NOT_FOUND
  159. )
  160. extra_servers = yield self.store.get_joined_hosts_for_room(room_id)
  161. servers = set(extra_servers) | set(servers)
  162. # If this server is in the list of servers, return it first.
  163. if self.server_name in servers:
  164. servers = (
  165. [self.server_name] +
  166. [s for s in servers if s != self.server_name]
  167. )
  168. else:
  169. servers = list(servers)
  170. defer.returnValue({
  171. "room_id": room_id,
  172. "servers": servers,
  173. })
  174. return
  175. @defer.inlineCallbacks
  176. def on_directory_query(self, args):
  177. room_alias = RoomAlias.from_string(args["room_alias"])
  178. if not self.hs.is_mine(room_alias):
  179. raise SynapseError(
  180. 400, "Room Alias is not hosted on this Home Server"
  181. )
  182. result = yield self.get_association_from_room_alias(
  183. room_alias
  184. )
  185. if result is not None:
  186. defer.returnValue({
  187. "room_id": result.room_id,
  188. "servers": result.servers,
  189. })
  190. else:
  191. raise SynapseError(
  192. 404,
  193. "Room alias %r not found" % (room_alias.to_string(),),
  194. Codes.NOT_FOUND
  195. )
  196. @defer.inlineCallbacks
  197. def send_room_alias_update_event(self, requester, user_id, room_id):
  198. aliases = yield self.store.get_aliases_for_room(room_id)
  199. msg_handler = self.hs.get_handlers().message_handler
  200. yield msg_handler.create_and_send_nonmember_event(
  201. requester,
  202. {
  203. "type": EventTypes.Aliases,
  204. "state_key": self.hs.hostname,
  205. "room_id": room_id,
  206. "sender": user_id,
  207. "content": {"aliases": aliases},
  208. },
  209. ratelimit=False
  210. )
  211. @defer.inlineCallbacks
  212. def _update_canonical_alias(self, requester, user_id, room_id, room_alias):
  213. alias_event = yield self.state.get_current_state(
  214. room_id, EventTypes.CanonicalAlias, ""
  215. )
  216. alias_str = room_alias.to_string()
  217. if not alias_event or alias_event.content.get("alias", "") != alias_str:
  218. return
  219. msg_handler = self.hs.get_handlers().message_handler
  220. yield msg_handler.create_and_send_nonmember_event(
  221. requester,
  222. {
  223. "type": EventTypes.CanonicalAlias,
  224. "state_key": "",
  225. "room_id": room_id,
  226. "sender": user_id,
  227. "content": {},
  228. },
  229. ratelimit=False
  230. )
  231. @defer.inlineCallbacks
  232. def get_association_from_room_alias(self, room_alias):
  233. result = yield self.store.get_association_from_room_alias(
  234. room_alias
  235. )
  236. if not result:
  237. # Query AS to see if it exists
  238. as_handler = self.appservice_handler
  239. result = yield as_handler.query_room_alias_exists(room_alias)
  240. defer.returnValue(result)
  241. @defer.inlineCallbacks
  242. def can_modify_alias(self, alias, user_id=None):
  243. # Any application service "interested" in an alias they are regexing on
  244. # can modify the alias.
  245. # Users can only modify the alias if ALL the interested services have
  246. # non-exclusive locks on the alias (or there are no interested services)
  247. services = yield self.store.get_app_services()
  248. interested_services = [
  249. s for s in services if s.is_interested_in_alias(alias.to_string())
  250. ]
  251. for service in interested_services:
  252. if user_id == service.sender:
  253. # this user IS the app service so they can do whatever they like
  254. defer.returnValue(True)
  255. return
  256. elif service.is_exclusive_alias(alias.to_string()):
  257. # another service has an exclusive lock on this alias.
  258. defer.returnValue(False)
  259. return
  260. # either no interested services, or no service with an exclusive lock
  261. defer.returnValue(True)
  262. @defer.inlineCallbacks
  263. def _user_can_delete_alias(self, alias, user_id):
  264. creator = yield self.store.get_room_alias_creator(alias.to_string())
  265. if creator and creator == user_id:
  266. defer.returnValue(True)
  267. is_admin = yield self.auth.is_server_admin(UserID.from_string(user_id))
  268. defer.returnValue(is_admin)
  269. @defer.inlineCallbacks
  270. def edit_published_room_list(self, requester, room_id, visibility):
  271. """Edit the entry of the room in the published room list.
  272. requester
  273. room_id (str)
  274. visibility (str): "public" or "private"
  275. """
  276. if requester.is_guest:
  277. raise AuthError(403, "Guests cannot edit the published room list")
  278. if visibility not in ["public", "private"]:
  279. raise SynapseError(400, "Invalid visibility setting")
  280. room = yield self.store.get_room(room_id)
  281. if room is None:
  282. raise SynapseError(400, "Unknown room")
  283. yield self.auth.check_can_change_room_list(room_id, requester.user)
  284. yield self.store.set_room_is_public(room_id, visibility == "public")