test_directory.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475
  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 mock import Mock
  16. from twisted.internet import defer
  17. import synapse
  18. import synapse.api.errors
  19. from synapse.api.constants import EventTypes
  20. from synapse.config.room_directory import RoomDirectoryConfig
  21. from synapse.rest.client.v1 import directory, login, room
  22. from synapse.types import RoomAlias, create_requester
  23. from tests import unittest
  24. class DirectoryTestCase(unittest.HomeserverTestCase):
  25. """ Tests the directory service. """
  26. def make_homeserver(self, reactor, clock):
  27. self.mock_federation = Mock()
  28. self.mock_registry = Mock()
  29. self.query_handlers = {}
  30. def register_query_handler(query_type, handler):
  31. self.query_handlers[query_type] = handler
  32. self.mock_registry.register_query_handler = register_query_handler
  33. hs = self.setup_test_homeserver(
  34. http_client=None,
  35. resource_for_federation=Mock(),
  36. federation_client=self.mock_federation,
  37. federation_registry=self.mock_registry,
  38. )
  39. self.handler = hs.get_handlers().directory_handler
  40. self.store = hs.get_datastore()
  41. self.my_room = RoomAlias.from_string("#my-room:test")
  42. self.your_room = RoomAlias.from_string("#your-room:test")
  43. self.remote_room = RoomAlias.from_string("#another:remote")
  44. return hs
  45. def test_get_local_association(self):
  46. self.get_success(
  47. self.store.create_room_alias_association(
  48. self.my_room, "!8765qwer:test", ["test"]
  49. )
  50. )
  51. result = self.get_success(self.handler.get_association(self.my_room))
  52. self.assertEquals({"room_id": "!8765qwer:test", "servers": ["test"]}, result)
  53. def test_get_remote_association(self):
  54. self.mock_federation.make_query.return_value = defer.succeed(
  55. {"room_id": "!8765qwer:test", "servers": ["test", "remote"]}
  56. )
  57. result = self.get_success(self.handler.get_association(self.remote_room))
  58. self.assertEquals(
  59. {"room_id": "!8765qwer:test", "servers": ["test", "remote"]}, result
  60. )
  61. self.mock_federation.make_query.assert_called_with(
  62. destination="remote",
  63. query_type="directory",
  64. args={"room_alias": "#another:remote"},
  65. retry_on_dns_fail=False,
  66. ignore_backoff=True,
  67. )
  68. def test_incoming_fed_query(self):
  69. self.get_success(
  70. self.store.create_room_alias_association(
  71. self.your_room, "!8765asdf:test", ["test"]
  72. )
  73. )
  74. response = self.get_success(
  75. self.handler.on_directory_query({"room_alias": "#your-room:test"})
  76. )
  77. self.assertEquals({"room_id": "!8765asdf:test", "servers": ["test"]}, response)
  78. class TestCreateAlias(unittest.HomeserverTestCase):
  79. servlets = [
  80. synapse.rest.admin.register_servlets,
  81. login.register_servlets,
  82. room.register_servlets,
  83. directory.register_servlets,
  84. ]
  85. def prepare(self, reactor, clock, hs):
  86. self.handler = hs.get_handlers().directory_handler
  87. # Create user
  88. self.admin_user = self.register_user("admin", "pass", admin=True)
  89. self.admin_user_tok = self.login("admin", "pass")
  90. # Create a test room
  91. self.room_id = self.helper.create_room_as(
  92. self.admin_user, tok=self.admin_user_tok
  93. )
  94. self.test_alias = "#test:test"
  95. self.room_alias = RoomAlias.from_string(self.test_alias)
  96. # Create a test user.
  97. self.test_user = self.register_user("user", "pass", admin=False)
  98. self.test_user_tok = self.login("user", "pass")
  99. self.helper.join(room=self.room_id, user=self.test_user, tok=self.test_user_tok)
  100. def test_create_alias_joined_room(self):
  101. """A user can create an alias for a room they're in."""
  102. self.get_success(
  103. self.handler.create_association(
  104. create_requester(self.test_user), self.room_alias, self.room_id,
  105. )
  106. )
  107. def test_create_alias_other_room(self):
  108. """A user cannot create an alias for a room they're NOT in."""
  109. other_room_id = self.helper.create_room_as(
  110. self.admin_user, tok=self.admin_user_tok
  111. )
  112. self.get_failure(
  113. self.handler.create_association(
  114. create_requester(self.test_user), self.room_alias, other_room_id,
  115. ),
  116. synapse.api.errors.SynapseError,
  117. )
  118. def test_create_alias_admin(self):
  119. """An admin can create an alias for a room they're NOT in."""
  120. other_room_id = self.helper.create_room_as(
  121. self.test_user, tok=self.test_user_tok
  122. )
  123. self.get_success(
  124. self.handler.create_association(
  125. create_requester(self.admin_user), self.room_alias, other_room_id,
  126. )
  127. )
  128. class TestDeleteAlias(unittest.HomeserverTestCase):
  129. servlets = [
  130. synapse.rest.admin.register_servlets,
  131. login.register_servlets,
  132. room.register_servlets,
  133. directory.register_servlets,
  134. ]
  135. def prepare(self, reactor, clock, hs):
  136. self.store = hs.get_datastore()
  137. self.handler = hs.get_handlers().directory_handler
  138. self.state_handler = hs.get_state_handler()
  139. # Create user
  140. self.admin_user = self.register_user("admin", "pass", admin=True)
  141. self.admin_user_tok = self.login("admin", "pass")
  142. # Create a test room
  143. self.room_id = self.helper.create_room_as(
  144. self.admin_user, tok=self.admin_user_tok
  145. )
  146. self.test_alias = "#test:test"
  147. self.room_alias = RoomAlias.from_string(self.test_alias)
  148. # Create a test user.
  149. self.test_user = self.register_user("user", "pass", admin=False)
  150. self.test_user_tok = self.login("user", "pass")
  151. self.helper.join(room=self.room_id, user=self.test_user, tok=self.test_user_tok)
  152. def _create_alias(self, user):
  153. # Create a new alias to this room.
  154. self.get_success(
  155. self.store.create_room_alias_association(
  156. self.room_alias, self.room_id, ["test"], user
  157. )
  158. )
  159. def test_delete_alias_not_allowed(self):
  160. """A user that doesn't meet the expected guidelines cannot delete an alias."""
  161. self._create_alias(self.admin_user)
  162. self.get_failure(
  163. self.handler.delete_association(
  164. create_requester(self.test_user), self.room_alias
  165. ),
  166. synapse.api.errors.AuthError,
  167. )
  168. def test_delete_alias_creator(self):
  169. """An alias creator can delete their own alias."""
  170. # Create an alias from a different user.
  171. self._create_alias(self.test_user)
  172. # Delete the user's alias.
  173. result = self.get_success(
  174. self.handler.delete_association(
  175. create_requester(self.test_user), self.room_alias
  176. )
  177. )
  178. self.assertEquals(self.room_id, result)
  179. # Confirm the alias is gone.
  180. self.get_failure(
  181. self.handler.get_association(self.room_alias),
  182. synapse.api.errors.SynapseError,
  183. )
  184. def test_delete_alias_admin(self):
  185. """A server admin can delete an alias created by another user."""
  186. # Create an alias from a different user.
  187. self._create_alias(self.test_user)
  188. # Delete the user's alias as the admin.
  189. result = self.get_success(
  190. self.handler.delete_association(
  191. create_requester(self.admin_user), self.room_alias
  192. )
  193. )
  194. self.assertEquals(self.room_id, result)
  195. # Confirm the alias is gone.
  196. self.get_failure(
  197. self.handler.get_association(self.room_alias),
  198. synapse.api.errors.SynapseError,
  199. )
  200. def test_delete_alias_sufficient_power(self):
  201. """A user with a sufficient power level should be able to delete an alias."""
  202. self._create_alias(self.admin_user)
  203. # Increase the user's power level.
  204. self.helper.send_state(
  205. self.room_id,
  206. "m.room.power_levels",
  207. {"users": {self.test_user: 100}},
  208. tok=self.admin_user_tok,
  209. )
  210. # They can now delete the alias.
  211. result = self.get_success(
  212. self.handler.delete_association(
  213. create_requester(self.test_user), self.room_alias
  214. )
  215. )
  216. self.assertEquals(self.room_id, result)
  217. # Confirm the alias is gone.
  218. self.get_failure(
  219. self.handler.get_association(self.room_alias),
  220. synapse.api.errors.SynapseError,
  221. )
  222. class CanonicalAliasTestCase(unittest.HomeserverTestCase):
  223. """Test modifications of the canonical alias when delete aliases.
  224. """
  225. servlets = [
  226. synapse.rest.admin.register_servlets,
  227. login.register_servlets,
  228. room.register_servlets,
  229. directory.register_servlets,
  230. ]
  231. def prepare(self, reactor, clock, hs):
  232. self.store = hs.get_datastore()
  233. self.handler = hs.get_handlers().directory_handler
  234. self.state_handler = hs.get_state_handler()
  235. # Create user
  236. self.admin_user = self.register_user("admin", "pass", admin=True)
  237. self.admin_user_tok = self.login("admin", "pass")
  238. # Create a test room
  239. self.room_id = self.helper.create_room_as(
  240. self.admin_user, tok=self.admin_user_tok
  241. )
  242. self.test_alias = "#test:test"
  243. self.room_alias = self._add_alias(self.test_alias)
  244. def _add_alias(self, alias: str) -> RoomAlias:
  245. """Add an alias to the test room."""
  246. room_alias = RoomAlias.from_string(alias)
  247. # Create a new alias to this room.
  248. self.get_success(
  249. self.store.create_room_alias_association(
  250. room_alias, self.room_id, ["test"], self.admin_user
  251. )
  252. )
  253. return room_alias
  254. def _set_canonical_alias(self, content):
  255. """Configure the canonical alias state on the room."""
  256. self.helper.send_state(
  257. self.room_id, "m.room.canonical_alias", content, tok=self.admin_user_tok,
  258. )
  259. def _get_canonical_alias(self):
  260. """Get the canonical alias state of the room."""
  261. return self.get_success(
  262. self.state_handler.get_current_state(
  263. self.room_id, EventTypes.CanonicalAlias, ""
  264. )
  265. )
  266. def test_remove_alias(self):
  267. """Removing an alias that is the canonical alias should remove it there too."""
  268. # Set this new alias as the canonical alias for this room
  269. self._set_canonical_alias(
  270. {"alias": self.test_alias, "alt_aliases": [self.test_alias]}
  271. )
  272. data = self._get_canonical_alias()
  273. self.assertEqual(data["content"]["alias"], self.test_alias)
  274. self.assertEqual(data["content"]["alt_aliases"], [self.test_alias])
  275. # Finally, delete the alias.
  276. self.get_success(
  277. self.handler.delete_association(
  278. create_requester(self.admin_user), self.room_alias
  279. )
  280. )
  281. data = self._get_canonical_alias()
  282. self.assertNotIn("alias", data["content"])
  283. self.assertNotIn("alt_aliases", data["content"])
  284. def test_remove_other_alias(self):
  285. """Removing an alias listed as in alt_aliases should remove it there too."""
  286. # Create a second alias.
  287. other_test_alias = "#test2:test"
  288. other_room_alias = self._add_alias(other_test_alias)
  289. # Set the alias as the canonical alias for this room.
  290. self._set_canonical_alias(
  291. {
  292. "alias": self.test_alias,
  293. "alt_aliases": [self.test_alias, other_test_alias],
  294. }
  295. )
  296. data = self._get_canonical_alias()
  297. self.assertEqual(data["content"]["alias"], self.test_alias)
  298. self.assertEqual(
  299. data["content"]["alt_aliases"], [self.test_alias, other_test_alias]
  300. )
  301. # Delete the second alias.
  302. self.get_success(
  303. self.handler.delete_association(
  304. create_requester(self.admin_user), other_room_alias
  305. )
  306. )
  307. data = self._get_canonical_alias()
  308. self.assertEqual(data["content"]["alias"], self.test_alias)
  309. self.assertEqual(data["content"]["alt_aliases"], [self.test_alias])
  310. class TestCreateAliasACL(unittest.HomeserverTestCase):
  311. user_id = "@test:test"
  312. servlets = [directory.register_servlets, room.register_servlets]
  313. def prepare(self, reactor, clock, hs):
  314. # We cheekily override the config to add custom alias creation rules
  315. config = {}
  316. config["alias_creation_rules"] = [
  317. {"user_id": "*", "alias": "#unofficial_*", "action": "allow"}
  318. ]
  319. config["room_list_publication_rules"] = []
  320. rd_config = RoomDirectoryConfig()
  321. rd_config.read_config(config)
  322. self.hs.config.is_alias_creation_allowed = rd_config.is_alias_creation_allowed
  323. return hs
  324. def test_denied(self):
  325. room_id = self.helper.create_room_as(self.user_id)
  326. request, channel = self.make_request(
  327. "PUT",
  328. b"directory/room/%23test%3Atest",
  329. ('{"room_id":"%s"}' % (room_id,)).encode("ascii"),
  330. )
  331. self.render(request)
  332. self.assertEquals(403, channel.code, channel.result)
  333. def test_allowed(self):
  334. room_id = self.helper.create_room_as(self.user_id)
  335. request, channel = self.make_request(
  336. "PUT",
  337. b"directory/room/%23unofficial_test%3Atest",
  338. ('{"room_id":"%s"}' % (room_id,)).encode("ascii"),
  339. )
  340. self.render(request)
  341. self.assertEquals(200, channel.code, channel.result)
  342. class TestRoomListSearchDisabled(unittest.HomeserverTestCase):
  343. user_id = "@test:test"
  344. servlets = [directory.register_servlets, room.register_servlets]
  345. def prepare(self, reactor, clock, hs):
  346. room_id = self.helper.create_room_as(self.user_id)
  347. request, channel = self.make_request(
  348. "PUT", b"directory/list/room/%s" % (room_id.encode("ascii"),), b"{}"
  349. )
  350. self.render(request)
  351. self.assertEquals(200, channel.code, channel.result)
  352. self.room_list_handler = hs.get_room_list_handler()
  353. self.directory_handler = hs.get_handlers().directory_handler
  354. return hs
  355. def test_disabling_room_list(self):
  356. self.room_list_handler.enable_room_list_search = True
  357. self.directory_handler.enable_room_list_search = True
  358. # Room list is enabled so we should get some results
  359. request, channel = self.make_request("GET", b"publicRooms")
  360. self.render(request)
  361. self.assertEquals(200, channel.code, channel.result)
  362. self.assertTrue(len(channel.json_body["chunk"]) > 0)
  363. self.room_list_handler.enable_room_list_search = False
  364. self.directory_handler.enable_room_list_search = False
  365. # Room list disabled so we should get no results
  366. request, channel = self.make_request("GET", b"publicRooms")
  367. self.render(request)
  368. self.assertEquals(200, channel.code, channel.result)
  369. self.assertTrue(len(channel.json_body["chunk"]) == 0)
  370. # Room list disabled so we shouldn't be allowed to publish rooms
  371. room_id = self.helper.create_room_as(self.user_id)
  372. request, channel = self.make_request(
  373. "PUT", b"directory/list/room/%s" % (room_id.encode("ascii"),), b"{}"
  374. )
  375. self.render(request)
  376. self.assertEquals(403, channel.code, channel.result)