directory.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375
  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, get_domain_from_id
  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.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. )