test_directory.py 16 KB

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