directory.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376
  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. import logging
  16. import string
  17. from twisted.internet import defer
  18. from synapse.api.constants import EventTypes
  19. from synapse.api.errors import AuthError, CodeMessageException, Codes, SynapseError
  20. from synapse.types import RoomAlias, UserID, get_domain_from_id
  21. from ._base import BaseHandler
  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.event_creation_handler = hs.get_event_creation_handler()
  29. self.federation = hs.get_federation_client()
  30. hs.get_federation_registry().register_query_handler(
  31. "directory", self.on_directory_query
  32. )
  33. self.spam_checker = hs.get_spam_checker()
  34. @defer.inlineCallbacks
  35. def _create_association(self, room_alias, room_id, servers=None, creator=None):
  36. # general association creation for both human users and app services
  37. for wchar in string.whitespace:
  38. if wchar in room_alias.localpart:
  39. raise SynapseError(400, "Invalid characters in room alias")
  40. if not self.hs.is_mine(room_alias):
  41. raise SynapseError(400, "Room alias must be local")
  42. # TODO(erikj): Change this.
  43. # TODO(erikj): Add transactions.
  44. # TODO(erikj): Check if there is a current association.
  45. if not servers:
  46. users = yield self.state.get_current_user_in_room(room_id)
  47. servers = set(get_domain_from_id(u) for u in users)
  48. if not servers:
  49. raise SynapseError(400, "Failed to get server list")
  50. yield self.store.create_room_alias_association(
  51. room_alias,
  52. room_id,
  53. servers,
  54. creator=creator,
  55. )
  56. @defer.inlineCallbacks
  57. def create_association(self, user_id, room_alias, room_id, servers=None):
  58. # association creation for human users
  59. # TODO(erikj): Do user auth.
  60. if not self.spam_checker.user_may_create_room_alias(user_id, room_alias):
  61. raise SynapseError(
  62. 403, "This user is not permitted to create this alias",
  63. )
  64. can_create = yield self.can_modify_alias(
  65. room_alias,
  66. user_id=user_id
  67. )
  68. if not can_create:
  69. raise SynapseError(
  70. 400, "This alias is reserved by an application service.",
  71. errcode=Codes.EXCLUSIVE
  72. )
  73. yield self._create_association(room_alias, room_id, servers, creator=user_id)
  74. @defer.inlineCallbacks
  75. def create_appservice_association(self, service, room_alias, room_id,
  76. servers=None):
  77. if not service.is_interested_in_alias(room_alias.to_string()):
  78. raise SynapseError(
  79. 400, "This application service has not reserved"
  80. " this kind of alias.", errcode=Codes.EXCLUSIVE
  81. )
  82. # association creation for app services
  83. yield self._create_association(room_alias, room_id, servers)
  84. @defer.inlineCallbacks
  85. def delete_association(self, requester, user_id, room_alias):
  86. # association deletion for human users
  87. can_delete = yield self._user_can_delete_alias(room_alias, user_id)
  88. if not can_delete:
  89. raise AuthError(
  90. 403, "You don't have permission to delete the alias.",
  91. )
  92. can_delete = yield self.can_modify_alias(
  93. room_alias,
  94. user_id=user_id
  95. )
  96. if not can_delete:
  97. raise SynapseError(
  98. 400, "This alias is reserved by an application service.",
  99. errcode=Codes.EXCLUSIVE
  100. )
  101. room_id = yield self._delete_association(room_alias)
  102. try:
  103. yield self.send_room_alias_update_event(
  104. requester,
  105. requester.user.to_string(),
  106. room_id
  107. )
  108. yield self._update_canonical_alias(
  109. requester,
  110. requester.user.to_string(),
  111. room_id,
  112. room_alias,
  113. )
  114. except AuthError as e:
  115. logger.info("Failed to update alias events: %s", e)
  116. defer.returnValue(room_id)
  117. @defer.inlineCallbacks
  118. def delete_appservice_association(self, service, room_alias):
  119. if not service.is_interested_in_alias(room_alias.to_string()):
  120. raise SynapseError(
  121. 400,
  122. "This application service has not reserved this kind of alias",
  123. errcode=Codes.EXCLUSIVE
  124. )
  125. yield self._delete_association(room_alias)
  126. @defer.inlineCallbacks
  127. def _delete_association(self, room_alias):
  128. if not self.hs.is_mine(room_alias):
  129. raise SynapseError(400, "Room alias must be local")
  130. room_id = yield self.store.delete_room_alias(room_alias)
  131. defer.returnValue(room_id)
  132. @defer.inlineCallbacks
  133. def get_association(self, room_alias):
  134. room_id = None
  135. if self.hs.is_mine(room_alias):
  136. result = yield self.get_association_from_room_alias(
  137. room_alias
  138. )
  139. if result:
  140. room_id = result.room_id
  141. servers = result.servers
  142. else:
  143. try:
  144. result = yield self.federation.make_query(
  145. destination=room_alias.domain,
  146. query_type="directory",
  147. args={
  148. "room_alias": room_alias.to_string(),
  149. },
  150. retry_on_dns_fail=False,
  151. ignore_backoff=True,
  152. )
  153. except CodeMessageException as e:
  154. logging.warn("Error retrieving alias")
  155. if e.code == 404:
  156. result = None
  157. else:
  158. raise
  159. if result and "room_id" in result and "servers" in result:
  160. room_id = result["room_id"]
  161. servers = result["servers"]
  162. if not room_id:
  163. raise SynapseError(
  164. 404,
  165. "Room alias %s not found" % (room_alias.to_string(),),
  166. Codes.NOT_FOUND
  167. )
  168. users = yield self.state.get_current_user_in_room(room_id)
  169. extra_servers = set(get_domain_from_id(u) for u in users)
  170. servers = set(extra_servers) | set(servers)
  171. # If this server is in the list of servers, return it first.
  172. if self.server_name in servers:
  173. servers = (
  174. [self.server_name] +
  175. [s for s in servers if s != self.server_name]
  176. )
  177. else:
  178. servers = list(servers)
  179. defer.returnValue({
  180. "room_id": room_id,
  181. "servers": servers,
  182. })
  183. return
  184. @defer.inlineCallbacks
  185. def on_directory_query(self, args):
  186. room_alias = RoomAlias.from_string(args["room_alias"])
  187. if not self.hs.is_mine(room_alias):
  188. raise SynapseError(
  189. 400, "Room Alias is not hosted on this Home Server"
  190. )
  191. result = yield self.get_association_from_room_alias(
  192. room_alias
  193. )
  194. if result is not None:
  195. defer.returnValue({
  196. "room_id": result.room_id,
  197. "servers": result.servers,
  198. })
  199. else:
  200. raise SynapseError(
  201. 404,
  202. "Room alias %r not found" % (room_alias.to_string(),),
  203. Codes.NOT_FOUND
  204. )
  205. @defer.inlineCallbacks
  206. def send_room_alias_update_event(self, requester, user_id, room_id):
  207. aliases = yield self.store.get_aliases_for_room(room_id)
  208. yield self.event_creation_handler.create_and_send_nonmember_event(
  209. requester,
  210. {
  211. "type": EventTypes.Aliases,
  212. "state_key": self.hs.hostname,
  213. "room_id": room_id,
  214. "sender": user_id,
  215. "content": {"aliases": aliases},
  216. },
  217. ratelimit=False
  218. )
  219. @defer.inlineCallbacks
  220. def _update_canonical_alias(self, requester, user_id, room_id, room_alias):
  221. alias_event = yield self.state.get_current_state(
  222. room_id, EventTypes.CanonicalAlias, ""
  223. )
  224. alias_str = room_alias.to_string()
  225. if not alias_event or alias_event.content.get("alias", "") != alias_str:
  226. return
  227. yield self.event_creation_handler.create_and_send_nonmember_event(
  228. requester,
  229. {
  230. "type": EventTypes.CanonicalAlias,
  231. "state_key": "",
  232. "room_id": room_id,
  233. "sender": user_id,
  234. "content": {},
  235. },
  236. ratelimit=False
  237. )
  238. @defer.inlineCallbacks
  239. def get_association_from_room_alias(self, room_alias):
  240. result = yield self.store.get_association_from_room_alias(
  241. room_alias
  242. )
  243. if not result:
  244. # Query AS to see if it exists
  245. as_handler = self.appservice_handler
  246. result = yield as_handler.query_room_alias_exists(room_alias)
  247. defer.returnValue(result)
  248. def can_modify_alias(self, alias, user_id=None):
  249. # Any application service "interested" in an alias they are regexing on
  250. # can modify the alias.
  251. # Users can only modify the alias if ALL the interested services have
  252. # non-exclusive locks on the alias (or there are no interested services)
  253. services = self.store.get_app_services()
  254. interested_services = [
  255. s for s in services if s.is_interested_in_alias(alias.to_string())
  256. ]
  257. for service in interested_services:
  258. if user_id == service.sender:
  259. # this user IS the app service so they can do whatever they like
  260. return defer.succeed(True)
  261. elif service.is_exclusive_alias(alias.to_string()):
  262. # another service has an exclusive lock on this alias.
  263. return defer.succeed(False)
  264. # either no interested services, or no service with an exclusive lock
  265. return defer.succeed(True)
  266. @defer.inlineCallbacks
  267. def _user_can_delete_alias(self, alias, user_id):
  268. creator = yield self.store.get_room_alias_creator(alias.to_string())
  269. if creator and creator == user_id:
  270. defer.returnValue(True)
  271. is_admin = yield self.auth.is_server_admin(UserID.from_string(user_id))
  272. defer.returnValue(is_admin)
  273. @defer.inlineCallbacks
  274. def edit_published_room_list(self, requester, room_id, visibility):
  275. """Edit the entry of the room in the published room list.
  276. requester
  277. room_id (str)
  278. visibility (str): "public" or "private"
  279. """
  280. if not self.spam_checker.user_may_publish_room(
  281. requester.user.to_string(), room_id
  282. ):
  283. raise AuthError(
  284. 403,
  285. "This user is not permitted to publish rooms to the room list"
  286. )
  287. if requester.is_guest:
  288. raise AuthError(403, "Guests cannot edit the published room list")
  289. if visibility not in ["public", "private"]:
  290. raise SynapseError(400, "Invalid visibility setting")
  291. room = yield self.store.get_room(room_id)
  292. if room is None:
  293. raise SynapseError(400, "Unknown room")
  294. yield self.auth.check_can_change_room_list(room_id, requester.user)
  295. yield self.store.set_room_is_public(room_id, visibility == "public")
  296. @defer.inlineCallbacks
  297. def edit_published_appservice_room_list(self, appservice_id, network_id,
  298. room_id, visibility):
  299. """Add or remove a room from the appservice/network specific public
  300. room list.
  301. Args:
  302. appservice_id (str): ID of the appservice that owns the list
  303. network_id (str): The ID of the network the list is associated with
  304. room_id (str)
  305. visibility (str): either "public" or "private"
  306. """
  307. if visibility not in ["public", "private"]:
  308. raise SynapseError(400, "Invalid visibility setting")
  309. yield self.store.set_room_is_public_appservice(
  310. room_id, appservice_id, network_id, visibility == "public"
  311. )