test_utils.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459
  1. # -*- coding: utf-8 -*-
  2. # Copyright 2015, 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 synapse.api.room_versions import RoomVersions
  16. from synapse.events import make_event_from_dict
  17. from synapse.events.utils import (
  18. copy_power_levels_contents,
  19. prune_event,
  20. serialize_event,
  21. )
  22. from synapse.util.frozenutils import freeze
  23. from tests import unittest
  24. def MockEvent(**kwargs):
  25. if "event_id" not in kwargs:
  26. kwargs["event_id"] = "fake_event_id"
  27. if "type" not in kwargs:
  28. kwargs["type"] = "fake_type"
  29. return make_event_from_dict(kwargs)
  30. class PruneEventTestCase(unittest.TestCase):
  31. def run_test(self, evdict, matchdict, **kwargs):
  32. """
  33. Asserts that a new event constructed with `evdict` will look like
  34. `matchdict` when it is redacted.
  35. Args:
  36. evdict: The dictionary to build the event from.
  37. matchdict: The expected resulting dictionary.
  38. kwargs: Additional keyword arguments used to create the event.
  39. """
  40. self.assertEqual(
  41. prune_event(make_event_from_dict(evdict, **kwargs)).get_dict(), matchdict
  42. )
  43. def test_minimal(self):
  44. self.run_test(
  45. {"type": "A", "event_id": "$test:domain"},
  46. {
  47. "type": "A",
  48. "event_id": "$test:domain",
  49. "content": {},
  50. "signatures": {},
  51. "unsigned": {},
  52. },
  53. )
  54. def test_basic_keys(self):
  55. """Ensure that the keys that should be untouched are kept."""
  56. # Note that some of the values below don't really make sense, but the
  57. # pruning of events doesn't worry about the values of any fields (with
  58. # the exception of the content field).
  59. self.run_test(
  60. {
  61. "event_id": "$3:domain",
  62. "type": "A",
  63. "room_id": "!1:domain",
  64. "sender": "@2:domain",
  65. "state_key": "B",
  66. "content": {"other_key": "foo"},
  67. "hashes": "hashes",
  68. "signatures": {"domain": {"algo:1": "sigs"}},
  69. "depth": 4,
  70. "prev_events": "prev_events",
  71. "prev_state": "prev_state",
  72. "auth_events": "auth_events",
  73. "origin": "domain",
  74. "origin_server_ts": 1234,
  75. "membership": "join",
  76. # Also include a key that should be removed.
  77. "other_key": "foo",
  78. },
  79. {
  80. "event_id": "$3:domain",
  81. "type": "A",
  82. "room_id": "!1:domain",
  83. "sender": "@2:domain",
  84. "state_key": "B",
  85. "hashes": "hashes",
  86. "depth": 4,
  87. "prev_events": "prev_events",
  88. "prev_state": "prev_state",
  89. "auth_events": "auth_events",
  90. "origin": "domain",
  91. "origin_server_ts": 1234,
  92. "membership": "join",
  93. "content": {},
  94. "signatures": {"domain": {"algo:1": "sigs"}},
  95. "unsigned": {},
  96. },
  97. )
  98. # As of MSC2176 we now redact the membership and prev_states keys.
  99. self.run_test(
  100. {"type": "A", "prev_state": "prev_state", "membership": "join"},
  101. {"type": "A", "content": {}, "signatures": {}, "unsigned": {}},
  102. room_version=RoomVersions.MSC2176,
  103. )
  104. def test_unsigned(self):
  105. """Ensure that unsigned properties get stripped (except age_ts and replaces_state)."""
  106. self.run_test(
  107. {
  108. "type": "B",
  109. "event_id": "$test:domain",
  110. "unsigned": {
  111. "age_ts": 20,
  112. "replaces_state": "$test2:domain",
  113. "other_key": "foo",
  114. },
  115. },
  116. {
  117. "type": "B",
  118. "event_id": "$test:domain",
  119. "content": {},
  120. "signatures": {},
  121. "unsigned": {"age_ts": 20, "replaces_state": "$test2:domain"},
  122. },
  123. )
  124. def test_content(self):
  125. """The content dictionary should be stripped in most cases."""
  126. self.run_test(
  127. {"type": "C", "event_id": "$test:domain", "content": {"things": "here"}},
  128. {
  129. "type": "C",
  130. "event_id": "$test:domain",
  131. "content": {},
  132. "signatures": {},
  133. "unsigned": {},
  134. },
  135. )
  136. # Some events keep a single content key/value.
  137. EVENT_KEEP_CONTENT_KEYS = [
  138. ("member", "membership", "join"),
  139. ("join_rules", "join_rule", "invite"),
  140. ("history_visibility", "history_visibility", "shared"),
  141. ]
  142. for event_type, key, value in EVENT_KEEP_CONTENT_KEYS:
  143. self.run_test(
  144. {
  145. "type": "m.room." + event_type,
  146. "event_id": "$test:domain",
  147. "content": {key: value, "other_key": "foo"},
  148. },
  149. {
  150. "type": "m.room." + event_type,
  151. "event_id": "$test:domain",
  152. "content": {key: value},
  153. "signatures": {},
  154. "unsigned": {},
  155. },
  156. )
  157. def test_create(self):
  158. """Create events are partially redacted until MSC2176."""
  159. self.run_test(
  160. {
  161. "type": "m.room.create",
  162. "event_id": "$test:domain",
  163. "content": {"creator": "@2:domain", "other_key": "foo"},
  164. },
  165. {
  166. "type": "m.room.create",
  167. "event_id": "$test:domain",
  168. "content": {"creator": "@2:domain"},
  169. "signatures": {},
  170. "unsigned": {},
  171. },
  172. )
  173. # After MSC2176, create events get nothing redacted.
  174. self.run_test(
  175. {"type": "m.room.create", "content": {"not_a_real_key": True}},
  176. {
  177. "type": "m.room.create",
  178. "content": {"not_a_real_key": True},
  179. "signatures": {},
  180. "unsigned": {},
  181. },
  182. room_version=RoomVersions.MSC2176,
  183. )
  184. def test_power_levels(self):
  185. """Power level events keep a variety of content keys."""
  186. self.run_test(
  187. {
  188. "type": "m.room.power_levels",
  189. "event_id": "$test:domain",
  190. "content": {
  191. "ban": 1,
  192. "events": {"m.room.name": 100},
  193. "events_default": 2,
  194. "invite": 3,
  195. "kick": 4,
  196. "redact": 5,
  197. "state_default": 6,
  198. "users": {"@admin:domain": 100},
  199. "users_default": 7,
  200. "other_key": 8,
  201. },
  202. },
  203. {
  204. "type": "m.room.power_levels",
  205. "event_id": "$test:domain",
  206. "content": {
  207. "ban": 1,
  208. "events": {"m.room.name": 100},
  209. "events_default": 2,
  210. # Note that invite is not here.
  211. "kick": 4,
  212. "redact": 5,
  213. "state_default": 6,
  214. "users": {"@admin:domain": 100},
  215. "users_default": 7,
  216. },
  217. "signatures": {},
  218. "unsigned": {},
  219. },
  220. )
  221. # After MSC2176, power levels events keep the invite key.
  222. self.run_test(
  223. {"type": "m.room.power_levels", "content": {"invite": 75}},
  224. {
  225. "type": "m.room.power_levels",
  226. "content": {"invite": 75},
  227. "signatures": {},
  228. "unsigned": {},
  229. },
  230. room_version=RoomVersions.MSC2176,
  231. )
  232. def test_alias_event(self):
  233. """Alias events have special behavior up through room version 6."""
  234. self.run_test(
  235. {
  236. "type": "m.room.aliases",
  237. "event_id": "$test:domain",
  238. "content": {"aliases": ["test"]},
  239. },
  240. {
  241. "type": "m.room.aliases",
  242. "event_id": "$test:domain",
  243. "content": {"aliases": ["test"]},
  244. "signatures": {},
  245. "unsigned": {},
  246. },
  247. )
  248. # After MSC2432, alias events have no special behavior.
  249. self.run_test(
  250. {"type": "m.room.aliases", "content": {"aliases": ["test"]}},
  251. {
  252. "type": "m.room.aliases",
  253. "content": {},
  254. "signatures": {},
  255. "unsigned": {},
  256. },
  257. room_version=RoomVersions.V6,
  258. )
  259. def test_redacts(self):
  260. """Redaction events have no special behaviour until MSC2174/MSC2176."""
  261. self.run_test(
  262. {"type": "m.room.redaction", "content": {"redacts": "$test2:domain"}},
  263. {
  264. "type": "m.room.redaction",
  265. "content": {},
  266. "signatures": {},
  267. "unsigned": {},
  268. },
  269. room_version=RoomVersions.V6,
  270. )
  271. # After MSC2174, redaction events keep the redacts content key.
  272. self.run_test(
  273. {"type": "m.room.redaction", "content": {"redacts": "$test2:domain"}},
  274. {
  275. "type": "m.room.redaction",
  276. "content": {"redacts": "$test2:domain"},
  277. "signatures": {},
  278. "unsigned": {},
  279. },
  280. room_version=RoomVersions.MSC2176,
  281. )
  282. class SerializeEventTestCase(unittest.TestCase):
  283. def serialize(self, ev, fields):
  284. return serialize_event(ev, 1479807801915, only_event_fields=fields)
  285. def test_event_fields_works_with_keys(self):
  286. self.assertEquals(
  287. self.serialize(
  288. MockEvent(sender="@alice:localhost", room_id="!foo:bar"), ["room_id"]
  289. ),
  290. {"room_id": "!foo:bar"},
  291. )
  292. def test_event_fields_works_with_nested_keys(self):
  293. self.assertEquals(
  294. self.serialize(
  295. MockEvent(
  296. sender="@alice:localhost",
  297. room_id="!foo:bar",
  298. content={"body": "A message"},
  299. ),
  300. ["content.body"],
  301. ),
  302. {"content": {"body": "A message"}},
  303. )
  304. def test_event_fields_works_with_dot_keys(self):
  305. self.assertEquals(
  306. self.serialize(
  307. MockEvent(
  308. sender="@alice:localhost",
  309. room_id="!foo:bar",
  310. content={"key.with.dots": {}},
  311. ),
  312. [r"content.key\.with\.dots"],
  313. ),
  314. {"content": {"key.with.dots": {}}},
  315. )
  316. def test_event_fields_works_with_nested_dot_keys(self):
  317. self.assertEquals(
  318. self.serialize(
  319. MockEvent(
  320. sender="@alice:localhost",
  321. room_id="!foo:bar",
  322. content={
  323. "not_me": 1,
  324. "nested.dot.key": {"leaf.key": 42, "not_me_either": 1},
  325. },
  326. ),
  327. [r"content.nested\.dot\.key.leaf\.key"],
  328. ),
  329. {"content": {"nested.dot.key": {"leaf.key": 42}}},
  330. )
  331. def test_event_fields_nops_with_unknown_keys(self):
  332. self.assertEquals(
  333. self.serialize(
  334. MockEvent(
  335. sender="@alice:localhost",
  336. room_id="!foo:bar",
  337. content={"foo": "bar"},
  338. ),
  339. ["content.foo", "content.notexists"],
  340. ),
  341. {"content": {"foo": "bar"}},
  342. )
  343. def test_event_fields_nops_with_non_dict_keys(self):
  344. self.assertEquals(
  345. self.serialize(
  346. MockEvent(
  347. sender="@alice:localhost",
  348. room_id="!foo:bar",
  349. content={"foo": ["I", "am", "an", "array"]},
  350. ),
  351. ["content.foo.am"],
  352. ),
  353. {},
  354. )
  355. def test_event_fields_nops_with_array_keys(self):
  356. self.assertEquals(
  357. self.serialize(
  358. MockEvent(
  359. sender="@alice:localhost",
  360. room_id="!foo:bar",
  361. content={"foo": ["I", "am", "an", "array"]},
  362. ),
  363. ["content.foo.1"],
  364. ),
  365. {},
  366. )
  367. def test_event_fields_all_fields_if_empty(self):
  368. self.assertEquals(
  369. self.serialize(
  370. MockEvent(
  371. type="foo",
  372. event_id="test",
  373. room_id="!foo:bar",
  374. content={"foo": "bar"},
  375. ),
  376. [],
  377. ),
  378. {
  379. "type": "foo",
  380. "event_id": "test",
  381. "room_id": "!foo:bar",
  382. "content": {"foo": "bar"},
  383. "unsigned": {},
  384. },
  385. )
  386. def test_event_fields_fail_if_fields_not_str(self):
  387. with self.assertRaises(TypeError):
  388. self.serialize(
  389. MockEvent(room_id="!foo:bar", content={"foo": "bar"}), ["room_id", 4]
  390. )
  391. class CopyPowerLevelsContentTestCase(unittest.TestCase):
  392. def setUp(self) -> None:
  393. self.test_content = {
  394. "ban": 50,
  395. "events": {"m.room.name": 100, "m.room.power_levels": 100},
  396. "events_default": 0,
  397. "invite": 50,
  398. "kick": 50,
  399. "notifications": {"room": 20},
  400. "redact": 50,
  401. "state_default": 50,
  402. "users": {"@example:localhost": 100},
  403. "users_default": 0,
  404. }
  405. def _test(self, input):
  406. a = copy_power_levels_contents(input)
  407. self.assertEqual(a["ban"], 50)
  408. self.assertEqual(a["events"]["m.room.name"], 100)
  409. # make sure that changing the copy changes the copy and not the orig
  410. a["ban"] = 10
  411. a["events"]["m.room.power_levels"] = 20
  412. self.assertEqual(input["ban"], 50)
  413. self.assertEqual(input["events"]["m.room.power_levels"], 100)
  414. def test_unfrozen(self):
  415. self._test(self.test_content)
  416. def test_frozen(self):
  417. input = freeze(self.test_content)
  418. self._test(input)