test_utils.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546
  1. # Copyright 2015, 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 synapse.api.constants import EventContentFields
  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. def test_join_rules(self):
  283. """Join rules events have changed behavior starting with MSC3083."""
  284. self.run_test(
  285. {
  286. "type": "m.room.join_rules",
  287. "event_id": "$test:domain",
  288. "content": {
  289. "join_rule": "invite",
  290. "allow": [],
  291. "other_key": "stripped",
  292. },
  293. },
  294. {
  295. "type": "m.room.join_rules",
  296. "event_id": "$test:domain",
  297. "content": {"join_rule": "invite"},
  298. "signatures": {},
  299. "unsigned": {},
  300. },
  301. )
  302. # After MSC3083, the allow key is protected from redaction.
  303. self.run_test(
  304. {
  305. "type": "m.room.join_rules",
  306. "content": {
  307. "join_rule": "invite",
  308. "allow": [],
  309. "other_key": "stripped",
  310. },
  311. },
  312. {
  313. "type": "m.room.join_rules",
  314. "content": {
  315. "join_rule": "invite",
  316. "allow": [],
  317. },
  318. "signatures": {},
  319. "unsigned": {},
  320. },
  321. room_version=RoomVersions.V8,
  322. )
  323. def test_member(self):
  324. """Member events have changed behavior starting with MSC3375."""
  325. self.run_test(
  326. {
  327. "type": "m.room.member",
  328. "event_id": "$test:domain",
  329. "content": {
  330. "membership": "join",
  331. EventContentFields.AUTHORISING_USER: "@user:domain",
  332. "other_key": "stripped",
  333. },
  334. },
  335. {
  336. "type": "m.room.member",
  337. "event_id": "$test:domain",
  338. "content": {"membership": "join"},
  339. "signatures": {},
  340. "unsigned": {},
  341. },
  342. )
  343. # After MSC3375, the join_authorised_via_users_server key is protected
  344. # from redaction.
  345. self.run_test(
  346. {
  347. "type": "m.room.member",
  348. "content": {
  349. "membership": "join",
  350. EventContentFields.AUTHORISING_USER: "@user:domain",
  351. "other_key": "stripped",
  352. },
  353. },
  354. {
  355. "type": "m.room.member",
  356. "content": {
  357. "membership": "join",
  358. EventContentFields.AUTHORISING_USER: "@user:domain",
  359. },
  360. "signatures": {},
  361. "unsigned": {},
  362. },
  363. room_version=RoomVersions.V9,
  364. )
  365. class SerializeEventTestCase(unittest.TestCase):
  366. def serialize(self, ev, fields):
  367. return serialize_event(ev, 1479807801915, only_event_fields=fields)
  368. def test_event_fields_works_with_keys(self):
  369. self.assertEquals(
  370. self.serialize(
  371. MockEvent(sender="@alice:localhost", room_id="!foo:bar"), ["room_id"]
  372. ),
  373. {"room_id": "!foo:bar"},
  374. )
  375. def test_event_fields_works_with_nested_keys(self):
  376. self.assertEquals(
  377. self.serialize(
  378. MockEvent(
  379. sender="@alice:localhost",
  380. room_id="!foo:bar",
  381. content={"body": "A message"},
  382. ),
  383. ["content.body"],
  384. ),
  385. {"content": {"body": "A message"}},
  386. )
  387. def test_event_fields_works_with_dot_keys(self):
  388. self.assertEquals(
  389. self.serialize(
  390. MockEvent(
  391. sender="@alice:localhost",
  392. room_id="!foo:bar",
  393. content={"key.with.dots": {}},
  394. ),
  395. [r"content.key\.with\.dots"],
  396. ),
  397. {"content": {"key.with.dots": {}}},
  398. )
  399. def test_event_fields_works_with_nested_dot_keys(self):
  400. self.assertEquals(
  401. self.serialize(
  402. MockEvent(
  403. sender="@alice:localhost",
  404. room_id="!foo:bar",
  405. content={
  406. "not_me": 1,
  407. "nested.dot.key": {"leaf.key": 42, "not_me_either": 1},
  408. },
  409. ),
  410. [r"content.nested\.dot\.key.leaf\.key"],
  411. ),
  412. {"content": {"nested.dot.key": {"leaf.key": 42}}},
  413. )
  414. def test_event_fields_nops_with_unknown_keys(self):
  415. self.assertEquals(
  416. self.serialize(
  417. MockEvent(
  418. sender="@alice:localhost",
  419. room_id="!foo:bar",
  420. content={"foo": "bar"},
  421. ),
  422. ["content.foo", "content.notexists"],
  423. ),
  424. {"content": {"foo": "bar"}},
  425. )
  426. def test_event_fields_nops_with_non_dict_keys(self):
  427. self.assertEquals(
  428. self.serialize(
  429. MockEvent(
  430. sender="@alice:localhost",
  431. room_id="!foo:bar",
  432. content={"foo": ["I", "am", "an", "array"]},
  433. ),
  434. ["content.foo.am"],
  435. ),
  436. {},
  437. )
  438. def test_event_fields_nops_with_array_keys(self):
  439. self.assertEquals(
  440. self.serialize(
  441. MockEvent(
  442. sender="@alice:localhost",
  443. room_id="!foo:bar",
  444. content={"foo": ["I", "am", "an", "array"]},
  445. ),
  446. ["content.foo.1"],
  447. ),
  448. {},
  449. )
  450. def test_event_fields_all_fields_if_empty(self):
  451. self.assertEquals(
  452. self.serialize(
  453. MockEvent(
  454. type="foo",
  455. event_id="test",
  456. room_id="!foo:bar",
  457. content={"foo": "bar"},
  458. ),
  459. [],
  460. ),
  461. {
  462. "type": "foo",
  463. "event_id": "test",
  464. "room_id": "!foo:bar",
  465. "content": {"foo": "bar"},
  466. "unsigned": {},
  467. },
  468. )
  469. def test_event_fields_fail_if_fields_not_str(self):
  470. with self.assertRaises(TypeError):
  471. self.serialize(
  472. MockEvent(room_id="!foo:bar", content={"foo": "bar"}), ["room_id", 4]
  473. )
  474. class CopyPowerLevelsContentTestCase(unittest.TestCase):
  475. def setUp(self) -> None:
  476. self.test_content = {
  477. "ban": 50,
  478. "events": {"m.room.name": 100, "m.room.power_levels": 100},
  479. "events_default": 0,
  480. "invite": 50,
  481. "kick": 50,
  482. "notifications": {"room": 20},
  483. "redact": 50,
  484. "state_default": 50,
  485. "users": {"@example:localhost": 100},
  486. "users_default": 0,
  487. }
  488. def _test(self, input):
  489. a = copy_power_levels_contents(input)
  490. self.assertEqual(a["ban"], 50)
  491. self.assertEqual(a["events"]["m.room.name"], 100)
  492. # make sure that changing the copy changes the copy and not the orig
  493. a["ban"] = 10
  494. a["events"]["m.room.power_levels"] = 20
  495. self.assertEqual(input["ban"], 50)
  496. self.assertEqual(input["events"]["m.room.power_levels"], 100)
  497. def test_unfrozen(self):
  498. self._test(self.test_content)
  499. def test_frozen(self):
  500. input = freeze(self.test_content)
  501. self._test(input)