test_register.py 46 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228
  1. # Copyright 2014-2016 OpenMarket Ltd
  2. # Copyright 2017-2018 New Vector Ltd
  3. # Copyright 2019 The Matrix.org Foundation C.I.C.
  4. #
  5. # Licensed under the Apache License, Version 2.0 (the "License");
  6. # you may not use this file except in compliance with the License.
  7. # You may obtain a copy of the License at
  8. #
  9. # http://www.apache.org/licenses/LICENSE-2.0
  10. #
  11. # Unless required by applicable law or agreed to in writing, software
  12. # distributed under the License is distributed on an "AS IS" BASIS,
  13. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. # See the License for the specific language governing permissions and
  15. # limitations under the License.
  16. import datetime
  17. import json
  18. import os
  19. from typing import Any, Dict, List, Tuple
  20. import pkg_resources
  21. from twisted.test.proto_helpers import MemoryReactor
  22. import synapse.rest.admin
  23. from synapse.api.constants import APP_SERVICE_REGISTRATION_TYPE, LoginType
  24. from synapse.api.errors import Codes
  25. from synapse.appservice import ApplicationService
  26. from synapse.rest.client import account, account_validity, login, logout, register, sync
  27. from synapse.server import HomeServer
  28. from synapse.storage._base import db_to_json
  29. from synapse.types import JsonDict
  30. from synapse.util import Clock
  31. from tests import unittest
  32. from tests.unittest import override_config
  33. class RegisterRestServletTestCase(unittest.HomeserverTestCase):
  34. servlets = [
  35. login.register_servlets,
  36. register.register_servlets,
  37. synapse.rest.admin.register_servlets,
  38. ]
  39. url = b"/_matrix/client/r0/register"
  40. def default_config(self) -> Dict[str, Any]:
  41. config = super().default_config()
  42. config["allow_guest_access"] = True
  43. return config
  44. def test_POST_appservice_registration_valid(self) -> None:
  45. user_id = "@as_user_kermit:test"
  46. as_token = "i_am_an_app_service"
  47. appservice = ApplicationService(
  48. as_token,
  49. self.hs.config.server.server_name,
  50. id="1234",
  51. namespaces={"users": [{"regex": r"@as_user.*", "exclusive": True}]},
  52. sender="@as:test",
  53. )
  54. self.hs.get_datastores().main.services_cache.append(appservice)
  55. request_data = json.dumps(
  56. {"username": "as_user_kermit", "type": APP_SERVICE_REGISTRATION_TYPE}
  57. )
  58. channel = self.make_request(
  59. b"POST", self.url + b"?access_token=i_am_an_app_service", request_data
  60. )
  61. self.assertEqual(channel.result["code"], b"200", channel.result)
  62. det_data = {"user_id": user_id, "home_server": self.hs.hostname}
  63. self.assertDictContainsSubset(det_data, channel.json_body)
  64. def test_POST_appservice_registration_no_type(self) -> None:
  65. as_token = "i_am_an_app_service"
  66. appservice = ApplicationService(
  67. as_token,
  68. self.hs.config.server.server_name,
  69. id="1234",
  70. namespaces={"users": [{"regex": r"@as_user.*", "exclusive": True}]},
  71. sender="@as:test",
  72. )
  73. self.hs.get_datastores().main.services_cache.append(appservice)
  74. request_data = json.dumps({"username": "as_user_kermit"})
  75. channel = self.make_request(
  76. b"POST", self.url + b"?access_token=i_am_an_app_service", request_data
  77. )
  78. self.assertEqual(channel.result["code"], b"400", channel.result)
  79. def test_POST_appservice_registration_invalid(self) -> None:
  80. self.appservice = None # no application service exists
  81. request_data = json.dumps(
  82. {"username": "kermit", "type": APP_SERVICE_REGISTRATION_TYPE}
  83. )
  84. channel = self.make_request(
  85. b"POST", self.url + b"?access_token=i_am_an_app_service", request_data
  86. )
  87. self.assertEqual(channel.result["code"], b"401", channel.result)
  88. def test_POST_bad_password(self) -> None:
  89. request_data = json.dumps({"username": "kermit", "password": 666})
  90. channel = self.make_request(b"POST", self.url, request_data)
  91. self.assertEqual(channel.result["code"], b"400", channel.result)
  92. self.assertEqual(channel.json_body["error"], "Invalid password")
  93. def test_POST_bad_username(self) -> None:
  94. request_data = json.dumps({"username": 777, "password": "monkey"})
  95. channel = self.make_request(b"POST", self.url, request_data)
  96. self.assertEqual(channel.result["code"], b"400", channel.result)
  97. self.assertEqual(channel.json_body["error"], "Invalid username")
  98. def test_POST_user_valid(self) -> None:
  99. user_id = "@kermit:test"
  100. device_id = "frogfone"
  101. params = {
  102. "username": "kermit",
  103. "password": "monkey",
  104. "device_id": device_id,
  105. "auth": {"type": LoginType.DUMMY},
  106. }
  107. request_data = json.dumps(params)
  108. channel = self.make_request(b"POST", self.url, request_data)
  109. det_data = {
  110. "user_id": user_id,
  111. "home_server": self.hs.hostname,
  112. "device_id": device_id,
  113. }
  114. self.assertEqual(channel.result["code"], b"200", channel.result)
  115. self.assertDictContainsSubset(det_data, channel.json_body)
  116. @override_config({"enable_registration": False})
  117. def test_POST_disabled_registration(self) -> None:
  118. request_data = json.dumps({"username": "kermit", "password": "monkey"})
  119. self.auth_result = (None, {"username": "kermit", "password": "monkey"}, None)
  120. channel = self.make_request(b"POST", self.url, request_data)
  121. self.assertEqual(channel.result["code"], b"403", channel.result)
  122. self.assertEqual(channel.json_body["error"], "Registration has been disabled")
  123. self.assertEqual(channel.json_body["errcode"], "M_FORBIDDEN")
  124. def test_POST_guest_registration(self) -> None:
  125. self.hs.config.key.macaroon_secret_key = "test"
  126. self.hs.config.registration.allow_guest_access = True
  127. channel = self.make_request(b"POST", self.url + b"?kind=guest", b"{}")
  128. det_data = {"home_server": self.hs.hostname, "device_id": "guest_device"}
  129. self.assertEqual(channel.result["code"], b"200", channel.result)
  130. self.assertDictContainsSubset(det_data, channel.json_body)
  131. def test_POST_disabled_guest_registration(self) -> None:
  132. self.hs.config.registration.allow_guest_access = False
  133. channel = self.make_request(b"POST", self.url + b"?kind=guest", b"{}")
  134. self.assertEqual(channel.result["code"], b"403", channel.result)
  135. self.assertEqual(channel.json_body["error"], "Guest access is disabled")
  136. @override_config({"rc_registration": {"per_second": 0.17, "burst_count": 5}})
  137. def test_POST_ratelimiting_guest(self) -> None:
  138. for i in range(0, 6):
  139. url = self.url + b"?kind=guest"
  140. channel = self.make_request(b"POST", url, b"{}")
  141. if i == 5:
  142. self.assertEqual(channel.result["code"], b"429", channel.result)
  143. retry_after_ms = int(channel.json_body["retry_after_ms"])
  144. else:
  145. self.assertEqual(channel.result["code"], b"200", channel.result)
  146. self.reactor.advance(retry_after_ms / 1000.0 + 1.0)
  147. channel = self.make_request(b"POST", self.url + b"?kind=guest", b"{}")
  148. self.assertEqual(channel.result["code"], b"200", channel.result)
  149. @override_config({"rc_registration": {"per_second": 0.17, "burst_count": 5}})
  150. def test_POST_ratelimiting(self) -> None:
  151. for i in range(0, 6):
  152. params = {
  153. "username": "kermit" + str(i),
  154. "password": "monkey",
  155. "device_id": "frogfone",
  156. "auth": {"type": LoginType.DUMMY},
  157. }
  158. request_data = json.dumps(params)
  159. channel = self.make_request(b"POST", self.url, request_data)
  160. if i == 5:
  161. self.assertEqual(channel.result["code"], b"429", channel.result)
  162. retry_after_ms = int(channel.json_body["retry_after_ms"])
  163. else:
  164. self.assertEqual(channel.result["code"], b"200", channel.result)
  165. self.reactor.advance(retry_after_ms / 1000.0 + 1.0)
  166. channel = self.make_request(b"POST", self.url + b"?kind=guest", b"{}")
  167. self.assertEqual(channel.result["code"], b"200", channel.result)
  168. @override_config({"registration_requires_token": True})
  169. def test_POST_registration_requires_token(self) -> None:
  170. username = "kermit"
  171. device_id = "frogfone"
  172. token = "abcd"
  173. store = self.hs.get_datastores().main
  174. self.get_success(
  175. store.db_pool.simple_insert(
  176. "registration_tokens",
  177. {
  178. "token": token,
  179. "uses_allowed": None,
  180. "pending": 0,
  181. "completed": 0,
  182. "expiry_time": None,
  183. },
  184. )
  185. )
  186. params: JsonDict = {
  187. "username": username,
  188. "password": "monkey",
  189. "device_id": device_id,
  190. }
  191. # Request without auth to get flows and session
  192. channel = self.make_request(b"POST", self.url, json.dumps(params))
  193. self.assertEqual(channel.result["code"], b"401", channel.result)
  194. flows = channel.json_body["flows"]
  195. # Synapse adds a dummy stage to differentiate flows where otherwise one
  196. # flow would be a subset of another flow.
  197. self.assertCountEqual(
  198. [[LoginType.REGISTRATION_TOKEN, LoginType.DUMMY]],
  199. (f["stages"] for f in flows),
  200. )
  201. session = channel.json_body["session"]
  202. # Do the registration token stage and check it has completed
  203. params["auth"] = {
  204. "type": LoginType.REGISTRATION_TOKEN,
  205. "token": token,
  206. "session": session,
  207. }
  208. request_data = json.dumps(params)
  209. channel = self.make_request(b"POST", self.url, request_data)
  210. self.assertEqual(channel.result["code"], b"401", channel.result)
  211. completed = channel.json_body["completed"]
  212. self.assertCountEqual([LoginType.REGISTRATION_TOKEN], completed)
  213. # Do the m.login.dummy stage and check registration was successful
  214. params["auth"] = {
  215. "type": LoginType.DUMMY,
  216. "session": session,
  217. }
  218. request_data = json.dumps(params)
  219. channel = self.make_request(b"POST", self.url, request_data)
  220. det_data = {
  221. "user_id": f"@{username}:{self.hs.hostname}",
  222. "home_server": self.hs.hostname,
  223. "device_id": device_id,
  224. }
  225. self.assertEqual(channel.result["code"], b"200", channel.result)
  226. self.assertDictContainsSubset(det_data, channel.json_body)
  227. # Check the `completed` counter has been incremented and pending is 0
  228. res = self.get_success(
  229. store.db_pool.simple_select_one(
  230. "registration_tokens",
  231. keyvalues={"token": token},
  232. retcols=["pending", "completed"],
  233. )
  234. )
  235. self.assertEqual(res["completed"], 1)
  236. self.assertEqual(res["pending"], 0)
  237. @override_config({"registration_requires_token": True})
  238. def test_POST_registration_token_invalid(self) -> None:
  239. params: JsonDict = {
  240. "username": "kermit",
  241. "password": "monkey",
  242. }
  243. # Request without auth to get session
  244. channel = self.make_request(b"POST", self.url, json.dumps(params))
  245. session = channel.json_body["session"]
  246. # Test with token param missing (invalid)
  247. params["auth"] = {
  248. "type": LoginType.REGISTRATION_TOKEN,
  249. "session": session,
  250. }
  251. channel = self.make_request(b"POST", self.url, json.dumps(params))
  252. self.assertEqual(channel.result["code"], b"401", channel.result)
  253. self.assertEqual(channel.json_body["errcode"], Codes.MISSING_PARAM)
  254. self.assertEqual(channel.json_body["completed"], [])
  255. # Test with non-string (invalid)
  256. params["auth"]["token"] = 1234
  257. channel = self.make_request(b"POST", self.url, json.dumps(params))
  258. self.assertEqual(channel.result["code"], b"401", channel.result)
  259. self.assertEqual(channel.json_body["errcode"], Codes.INVALID_PARAM)
  260. self.assertEqual(channel.json_body["completed"], [])
  261. # Test with unknown token (invalid)
  262. params["auth"]["token"] = "1234"
  263. channel = self.make_request(b"POST", self.url, json.dumps(params))
  264. self.assertEqual(channel.result["code"], b"401", channel.result)
  265. self.assertEqual(channel.json_body["errcode"], Codes.UNAUTHORIZED)
  266. self.assertEqual(channel.json_body["completed"], [])
  267. @override_config({"registration_requires_token": True})
  268. def test_POST_registration_token_limit_uses(self) -> None:
  269. token = "abcd"
  270. store = self.hs.get_datastores().main
  271. # Create token that can be used once
  272. self.get_success(
  273. store.db_pool.simple_insert(
  274. "registration_tokens",
  275. {
  276. "token": token,
  277. "uses_allowed": 1,
  278. "pending": 0,
  279. "completed": 0,
  280. "expiry_time": None,
  281. },
  282. )
  283. )
  284. params1: JsonDict = {"username": "bert", "password": "monkey"}
  285. params2: JsonDict = {"username": "ernie", "password": "monkey"}
  286. # Do 2 requests without auth to get two session IDs
  287. channel1 = self.make_request(b"POST", self.url, json.dumps(params1))
  288. session1 = channel1.json_body["session"]
  289. channel2 = self.make_request(b"POST", self.url, json.dumps(params2))
  290. session2 = channel2.json_body["session"]
  291. # Use token with session1 and check `pending` is 1
  292. params1["auth"] = {
  293. "type": LoginType.REGISTRATION_TOKEN,
  294. "token": token,
  295. "session": session1,
  296. }
  297. self.make_request(b"POST", self.url, json.dumps(params1))
  298. # Repeat request to make sure pending isn't increased again
  299. self.make_request(b"POST", self.url, json.dumps(params1))
  300. pending = self.get_success(
  301. store.db_pool.simple_select_one_onecol(
  302. "registration_tokens",
  303. keyvalues={"token": token},
  304. retcol="pending",
  305. )
  306. )
  307. self.assertEqual(pending, 1)
  308. # Check auth fails when using token with session2
  309. params2["auth"] = {
  310. "type": LoginType.REGISTRATION_TOKEN,
  311. "token": token,
  312. "session": session2,
  313. }
  314. channel = self.make_request(b"POST", self.url, json.dumps(params2))
  315. self.assertEqual(channel.result["code"], b"401", channel.result)
  316. self.assertEqual(channel.json_body["errcode"], Codes.UNAUTHORIZED)
  317. self.assertEqual(channel.json_body["completed"], [])
  318. # Complete registration with session1
  319. params1["auth"]["type"] = LoginType.DUMMY
  320. self.make_request(b"POST", self.url, json.dumps(params1))
  321. # Check pending=0 and completed=1
  322. res = self.get_success(
  323. store.db_pool.simple_select_one(
  324. "registration_tokens",
  325. keyvalues={"token": token},
  326. retcols=["pending", "completed"],
  327. )
  328. )
  329. self.assertEqual(res["pending"], 0)
  330. self.assertEqual(res["completed"], 1)
  331. # Check auth still fails when using token with session2
  332. channel = self.make_request(b"POST", self.url, json.dumps(params2))
  333. self.assertEqual(channel.result["code"], b"401", channel.result)
  334. self.assertEqual(channel.json_body["errcode"], Codes.UNAUTHORIZED)
  335. self.assertEqual(channel.json_body["completed"], [])
  336. @override_config({"registration_requires_token": True})
  337. def test_POST_registration_token_expiry(self) -> None:
  338. token = "abcd"
  339. now = self.hs.get_clock().time_msec()
  340. store = self.hs.get_datastores().main
  341. # Create token that expired yesterday
  342. self.get_success(
  343. store.db_pool.simple_insert(
  344. "registration_tokens",
  345. {
  346. "token": token,
  347. "uses_allowed": None,
  348. "pending": 0,
  349. "completed": 0,
  350. "expiry_time": now - 24 * 60 * 60 * 1000,
  351. },
  352. )
  353. )
  354. params: JsonDict = {"username": "kermit", "password": "monkey"}
  355. # Request without auth to get session
  356. channel = self.make_request(b"POST", self.url, json.dumps(params))
  357. session = channel.json_body["session"]
  358. # Check authentication fails with expired token
  359. params["auth"] = {
  360. "type": LoginType.REGISTRATION_TOKEN,
  361. "token": token,
  362. "session": session,
  363. }
  364. channel = self.make_request(b"POST", self.url, json.dumps(params))
  365. self.assertEqual(channel.result["code"], b"401", channel.result)
  366. self.assertEqual(channel.json_body["errcode"], Codes.UNAUTHORIZED)
  367. self.assertEqual(channel.json_body["completed"], [])
  368. # Update token so it expires tomorrow
  369. self.get_success(
  370. store.db_pool.simple_update_one(
  371. "registration_tokens",
  372. keyvalues={"token": token},
  373. updatevalues={"expiry_time": now + 24 * 60 * 60 * 1000},
  374. )
  375. )
  376. # Check authentication succeeds
  377. channel = self.make_request(b"POST", self.url, json.dumps(params))
  378. completed = channel.json_body["completed"]
  379. self.assertCountEqual([LoginType.REGISTRATION_TOKEN], completed)
  380. @override_config({"registration_requires_token": True})
  381. def test_POST_registration_token_session_expiry(self) -> None:
  382. """Test `pending` is decremented when an uncompleted session expires."""
  383. token = "abcd"
  384. store = self.hs.get_datastores().main
  385. self.get_success(
  386. store.db_pool.simple_insert(
  387. "registration_tokens",
  388. {
  389. "token": token,
  390. "uses_allowed": None,
  391. "pending": 0,
  392. "completed": 0,
  393. "expiry_time": None,
  394. },
  395. )
  396. )
  397. # Do 2 requests without auth to get two session IDs
  398. params1: JsonDict = {"username": "bert", "password": "monkey"}
  399. params2: JsonDict = {"username": "ernie", "password": "monkey"}
  400. channel1 = self.make_request(b"POST", self.url, json.dumps(params1))
  401. session1 = channel1.json_body["session"]
  402. channel2 = self.make_request(b"POST", self.url, json.dumps(params2))
  403. session2 = channel2.json_body["session"]
  404. # Use token with both sessions
  405. params1["auth"] = {
  406. "type": LoginType.REGISTRATION_TOKEN,
  407. "token": token,
  408. "session": session1,
  409. }
  410. self.make_request(b"POST", self.url, json.dumps(params1))
  411. params2["auth"] = {
  412. "type": LoginType.REGISTRATION_TOKEN,
  413. "token": token,
  414. "session": session2,
  415. }
  416. self.make_request(b"POST", self.url, json.dumps(params2))
  417. # Complete registration with session1
  418. params1["auth"]["type"] = LoginType.DUMMY
  419. self.make_request(b"POST", self.url, json.dumps(params1))
  420. # Check `result` of registration token stage for session1 is `True`
  421. result1 = self.get_success(
  422. store.db_pool.simple_select_one_onecol(
  423. "ui_auth_sessions_credentials",
  424. keyvalues={
  425. "session_id": session1,
  426. "stage_type": LoginType.REGISTRATION_TOKEN,
  427. },
  428. retcol="result",
  429. )
  430. )
  431. self.assertTrue(db_to_json(result1))
  432. # Check `result` for session2 is the token used
  433. result2 = self.get_success(
  434. store.db_pool.simple_select_one_onecol(
  435. "ui_auth_sessions_credentials",
  436. keyvalues={
  437. "session_id": session2,
  438. "stage_type": LoginType.REGISTRATION_TOKEN,
  439. },
  440. retcol="result",
  441. )
  442. )
  443. self.assertEqual(db_to_json(result2), token)
  444. # Delete both sessions (mimics expiry)
  445. self.get_success(
  446. store.delete_old_ui_auth_sessions(self.hs.get_clock().time_msec())
  447. )
  448. # Check pending is now 0
  449. pending = self.get_success(
  450. store.db_pool.simple_select_one_onecol(
  451. "registration_tokens",
  452. keyvalues={"token": token},
  453. retcol="pending",
  454. )
  455. )
  456. self.assertEqual(pending, 0)
  457. @override_config({"registration_requires_token": True})
  458. def test_POST_registration_token_session_expiry_deleted_token(self) -> None:
  459. """Test session expiry doesn't break when the token is deleted.
  460. 1. Start but don't complete UIA with a registration token
  461. 2. Delete the token from the database
  462. 3. Expire the session
  463. """
  464. token = "abcd"
  465. store = self.hs.get_datastores().main
  466. self.get_success(
  467. store.db_pool.simple_insert(
  468. "registration_tokens",
  469. {
  470. "token": token,
  471. "uses_allowed": None,
  472. "pending": 0,
  473. "completed": 0,
  474. "expiry_time": None,
  475. },
  476. )
  477. )
  478. # Do request without auth to get a session ID
  479. params: JsonDict = {"username": "kermit", "password": "monkey"}
  480. channel = self.make_request(b"POST", self.url, json.dumps(params))
  481. session = channel.json_body["session"]
  482. # Use token
  483. params["auth"] = {
  484. "type": LoginType.REGISTRATION_TOKEN,
  485. "token": token,
  486. "session": session,
  487. }
  488. self.make_request(b"POST", self.url, json.dumps(params))
  489. # Delete token
  490. self.get_success(
  491. store.db_pool.simple_delete_one(
  492. "registration_tokens",
  493. keyvalues={"token": token},
  494. )
  495. )
  496. # Delete session (mimics expiry)
  497. self.get_success(
  498. store.delete_old_ui_auth_sessions(self.hs.get_clock().time_msec())
  499. )
  500. def test_advertised_flows(self) -> None:
  501. channel = self.make_request(b"POST", self.url, b"{}")
  502. self.assertEqual(channel.result["code"], b"401", channel.result)
  503. flows = channel.json_body["flows"]
  504. # with the stock config, we only expect the dummy flow
  505. self.assertCountEqual([["m.login.dummy"]], (f["stages"] for f in flows))
  506. @unittest.override_config(
  507. {
  508. "public_baseurl": "https://test_server",
  509. "enable_registration_captcha": True,
  510. "user_consent": {
  511. "version": "1",
  512. "template_dir": "/",
  513. "require_at_registration": True,
  514. },
  515. "account_threepid_delegates": {
  516. "email": "https://id_server",
  517. "msisdn": "https://id_server",
  518. },
  519. }
  520. )
  521. def test_advertised_flows_captcha_and_terms_and_3pids(self) -> None:
  522. channel = self.make_request(b"POST", self.url, b"{}")
  523. self.assertEqual(channel.result["code"], b"401", channel.result)
  524. flows = channel.json_body["flows"]
  525. self.assertCountEqual(
  526. [
  527. ["m.login.recaptcha", "m.login.terms", "m.login.dummy"],
  528. ["m.login.recaptcha", "m.login.terms", "m.login.email.identity"],
  529. ["m.login.recaptcha", "m.login.terms", "m.login.msisdn"],
  530. [
  531. "m.login.recaptcha",
  532. "m.login.terms",
  533. "m.login.msisdn",
  534. "m.login.email.identity",
  535. ],
  536. ],
  537. (f["stages"] for f in flows),
  538. )
  539. @unittest.override_config(
  540. {
  541. "public_baseurl": "https://test_server",
  542. "registrations_require_3pid": ["email"],
  543. "disable_msisdn_registration": True,
  544. "email": {
  545. "smtp_host": "mail_server",
  546. "smtp_port": 2525,
  547. "notif_from": "sender@host",
  548. },
  549. }
  550. )
  551. def test_advertised_flows_no_msisdn_email_required(self) -> None:
  552. channel = self.make_request(b"POST", self.url, b"{}")
  553. self.assertEqual(channel.result["code"], b"401", channel.result)
  554. flows = channel.json_body["flows"]
  555. # with the stock config, we expect all four combinations of 3pid
  556. self.assertCountEqual(
  557. [["m.login.email.identity"]], (f["stages"] for f in flows)
  558. )
  559. @unittest.override_config(
  560. {
  561. "request_token_inhibit_3pid_errors": True,
  562. "public_baseurl": "https://test_server",
  563. "email": {
  564. "smtp_host": "mail_server",
  565. "smtp_port": 2525,
  566. "notif_from": "sender@host",
  567. },
  568. }
  569. )
  570. def test_request_token_existing_email_inhibit_error(self) -> None:
  571. """Test that requesting a token via this endpoint doesn't leak existing
  572. associations if configured that way.
  573. """
  574. user_id = self.register_user("kermit", "monkey")
  575. self.login("kermit", "monkey")
  576. email = "test@example.com"
  577. # Add a threepid
  578. self.get_success(
  579. self.hs.get_datastores().main.user_add_threepid(
  580. user_id=user_id,
  581. medium="email",
  582. address=email,
  583. validated_at=0,
  584. added_at=0,
  585. )
  586. )
  587. channel = self.make_request(
  588. "POST",
  589. b"register/email/requestToken",
  590. {"client_secret": "foobar", "email": email, "send_attempt": 1},
  591. )
  592. self.assertEqual(200, channel.code, channel.result)
  593. self.assertIsNotNone(channel.json_body.get("sid"))
  594. @unittest.override_config(
  595. {
  596. "public_baseurl": "https://test_server",
  597. "email": {
  598. "smtp_host": "mail_server",
  599. "smtp_port": 2525,
  600. "notif_from": "sender@host",
  601. },
  602. }
  603. )
  604. def test_reject_invalid_email(self) -> None:
  605. """Check that bad emails are rejected"""
  606. # Test for email with multiple @
  607. channel = self.make_request(
  608. "POST",
  609. b"register/email/requestToken",
  610. {"client_secret": "foobar", "email": "email@@email", "send_attempt": 1},
  611. )
  612. self.assertEqual(400, channel.code, channel.result)
  613. # Check error to ensure that we're not erroring due to a bug in the test.
  614. self.assertEqual(
  615. channel.json_body,
  616. {"errcode": "M_UNKNOWN", "error": "Unable to parse email address"},
  617. )
  618. # Test for email with no @
  619. channel = self.make_request(
  620. "POST",
  621. b"register/email/requestToken",
  622. {"client_secret": "foobar", "email": "email", "send_attempt": 1},
  623. )
  624. self.assertEqual(400, channel.code, channel.result)
  625. self.assertEqual(
  626. channel.json_body,
  627. {"errcode": "M_UNKNOWN", "error": "Unable to parse email address"},
  628. )
  629. # Test for super long email
  630. email = "a@" + "a" * 1000
  631. channel = self.make_request(
  632. "POST",
  633. b"register/email/requestToken",
  634. {"client_secret": "foobar", "email": email, "send_attempt": 1},
  635. )
  636. self.assertEqual(400, channel.code, channel.result)
  637. self.assertEqual(
  638. channel.json_body,
  639. {"errcode": "M_UNKNOWN", "error": "Unable to parse email address"},
  640. )
  641. @override_config(
  642. {
  643. "inhibit_user_in_use_error": True,
  644. }
  645. )
  646. def test_inhibit_user_in_use_error(self) -> None:
  647. """Tests that the 'inhibit_user_in_use_error' configuration flag behaves
  648. correctly.
  649. """
  650. username = "arthur"
  651. # Manually register the user, so we know the test isn't passing because of a lack
  652. # of clashing.
  653. reg_handler = self.hs.get_registration_handler()
  654. self.get_success(reg_handler.register_user(username))
  655. # Check that /available correctly ignores the username provided despite the
  656. # username being already registered.
  657. channel = self.make_request("GET", "register/available?username=" + username)
  658. self.assertEqual(200, channel.code, channel.result)
  659. # Test that when starting a UIA registration flow the request doesn't fail because
  660. # of a conflicting username
  661. channel = self.make_request(
  662. "POST",
  663. "register",
  664. {"username": username, "type": "m.login.password", "password": "foo"},
  665. )
  666. self.assertEqual(channel.code, 401)
  667. self.assertIn("session", channel.json_body)
  668. # Test that finishing the registration fails because of a conflicting username.
  669. session = channel.json_body["session"]
  670. channel = self.make_request(
  671. "POST",
  672. "register",
  673. {"auth": {"session": session, "type": LoginType.DUMMY}},
  674. )
  675. self.assertEqual(channel.code, 400, channel.json_body)
  676. self.assertEqual(channel.json_body["errcode"], Codes.USER_IN_USE)
  677. class AccountValidityTestCase(unittest.HomeserverTestCase):
  678. servlets = [
  679. register.register_servlets,
  680. synapse.rest.admin.register_servlets_for_client_rest_resource,
  681. login.register_servlets,
  682. sync.register_servlets,
  683. logout.register_servlets,
  684. account_validity.register_servlets,
  685. ]
  686. def make_homeserver(self, reactor: MemoryReactor, clock: Clock) -> HomeServer:
  687. config = self.default_config()
  688. # Test for account expiring after a week.
  689. config["enable_registration"] = True
  690. config["account_validity"] = {
  691. "enabled": True,
  692. "period": 604800000, # Time in ms for 1 week
  693. }
  694. self.hs = self.setup_test_homeserver(config=config)
  695. return self.hs
  696. def test_validity_period(self) -> None:
  697. self.register_user("kermit", "monkey")
  698. tok = self.login("kermit", "monkey")
  699. # The specific endpoint doesn't matter, all we need is an authenticated
  700. # endpoint.
  701. channel = self.make_request(b"GET", "/sync", access_token=tok)
  702. self.assertEqual(channel.result["code"], b"200", channel.result)
  703. self.reactor.advance(datetime.timedelta(weeks=1).total_seconds())
  704. channel = self.make_request(b"GET", "/sync", access_token=tok)
  705. self.assertEqual(channel.result["code"], b"403", channel.result)
  706. self.assertEqual(
  707. channel.json_body["errcode"], Codes.EXPIRED_ACCOUNT, channel.result
  708. )
  709. def test_manual_renewal(self) -> None:
  710. user_id = self.register_user("kermit", "monkey")
  711. tok = self.login("kermit", "monkey")
  712. self.reactor.advance(datetime.timedelta(weeks=1).total_seconds())
  713. # If we register the admin user at the beginning of the test, it will
  714. # expire at the same time as the normal user and the renewal request
  715. # will be denied.
  716. self.register_user("admin", "adminpassword", admin=True)
  717. admin_tok = self.login("admin", "adminpassword")
  718. url = "/_synapse/admin/v1/account_validity/validity"
  719. params = {"user_id": user_id}
  720. request_data = json.dumps(params)
  721. channel = self.make_request(b"POST", url, request_data, access_token=admin_tok)
  722. self.assertEqual(channel.result["code"], b"200", channel.result)
  723. # The specific endpoint doesn't matter, all we need is an authenticated
  724. # endpoint.
  725. channel = self.make_request(b"GET", "/sync", access_token=tok)
  726. self.assertEqual(channel.result["code"], b"200", channel.result)
  727. def test_manual_expire(self) -> None:
  728. user_id = self.register_user("kermit", "monkey")
  729. tok = self.login("kermit", "monkey")
  730. self.register_user("admin", "adminpassword", admin=True)
  731. admin_tok = self.login("admin", "adminpassword")
  732. url = "/_synapse/admin/v1/account_validity/validity"
  733. params = {
  734. "user_id": user_id,
  735. "expiration_ts": 0,
  736. "enable_renewal_emails": False,
  737. }
  738. request_data = json.dumps(params)
  739. channel = self.make_request(b"POST", url, request_data, access_token=admin_tok)
  740. self.assertEqual(channel.result["code"], b"200", channel.result)
  741. # The specific endpoint doesn't matter, all we need is an authenticated
  742. # endpoint.
  743. channel = self.make_request(b"GET", "/sync", access_token=tok)
  744. self.assertEqual(channel.result["code"], b"403", channel.result)
  745. self.assertEqual(
  746. channel.json_body["errcode"], Codes.EXPIRED_ACCOUNT, channel.result
  747. )
  748. def test_logging_out_expired_user(self) -> None:
  749. user_id = self.register_user("kermit", "monkey")
  750. tok = self.login("kermit", "monkey")
  751. self.register_user("admin", "adminpassword", admin=True)
  752. admin_tok = self.login("admin", "adminpassword")
  753. url = "/_synapse/admin/v1/account_validity/validity"
  754. params = {
  755. "user_id": user_id,
  756. "expiration_ts": 0,
  757. "enable_renewal_emails": False,
  758. }
  759. request_data = json.dumps(params)
  760. channel = self.make_request(b"POST", url, request_data, access_token=admin_tok)
  761. self.assertEqual(channel.result["code"], b"200", channel.result)
  762. # Try to log the user out
  763. channel = self.make_request(b"POST", "/logout", access_token=tok)
  764. self.assertEqual(channel.result["code"], b"200", channel.result)
  765. # Log the user in again (allowed for expired accounts)
  766. tok = self.login("kermit", "monkey")
  767. # Try to log out all of the user's sessions
  768. channel = self.make_request(b"POST", "/logout/all", access_token=tok)
  769. self.assertEqual(channel.result["code"], b"200", channel.result)
  770. class AccountValidityRenewalByEmailTestCase(unittest.HomeserverTestCase):
  771. servlets = [
  772. register.register_servlets,
  773. synapse.rest.admin.register_servlets_for_client_rest_resource,
  774. login.register_servlets,
  775. sync.register_servlets,
  776. account_validity.register_servlets,
  777. account.register_servlets,
  778. ]
  779. def make_homeserver(self, reactor: MemoryReactor, clock: Clock) -> HomeServer:
  780. config = self.default_config()
  781. # Test for account expiring after a week and renewal emails being sent 2
  782. # days before expiry.
  783. config["enable_registration"] = True
  784. config["account_validity"] = {
  785. "enabled": True,
  786. "period": 604800000, # Time in ms for 1 week
  787. "renew_at": 172800000, # Time in ms for 2 days
  788. "renew_by_email_enabled": True,
  789. "renew_email_subject": "Renew your account",
  790. "account_renewed_html_path": "account_renewed.html",
  791. "invalid_token_html_path": "invalid_token.html",
  792. }
  793. # Email config.
  794. config["email"] = {
  795. "enable_notifs": True,
  796. "template_dir": os.path.abspath(
  797. pkg_resources.resource_filename("synapse", "res/templates")
  798. ),
  799. "expiry_template_html": "notice_expiry.html",
  800. "expiry_template_text": "notice_expiry.txt",
  801. "notif_template_html": "notif_mail.html",
  802. "notif_template_text": "notif_mail.txt",
  803. "smtp_host": "127.0.0.1",
  804. "smtp_port": 20,
  805. "require_transport_security": False,
  806. "smtp_user": None,
  807. "smtp_pass": None,
  808. "notif_from": "test@example.com",
  809. }
  810. self.hs = self.setup_test_homeserver(config=config)
  811. async def sendmail(*args: Any, **kwargs: Any) -> None:
  812. self.email_attempts.append((args, kwargs))
  813. self.email_attempts: List[Tuple[Any, Any]] = []
  814. self.hs.get_send_email_handler()._sendmail = sendmail
  815. self.store = self.hs.get_datastores().main
  816. return self.hs
  817. def test_renewal_email(self) -> None:
  818. self.email_attempts = []
  819. (user_id, tok) = self.create_user()
  820. # Move 5 days forward. This should trigger a renewal email to be sent.
  821. self.reactor.advance(datetime.timedelta(days=5).total_seconds())
  822. self.assertEqual(len(self.email_attempts), 1)
  823. # Retrieving the URL from the email is too much pain for now, so we
  824. # retrieve the token from the DB.
  825. renewal_token = self.get_success(self.store.get_renewal_token_for_user(user_id))
  826. url = "/_matrix/client/unstable/account_validity/renew?token=%s" % renewal_token
  827. channel = self.make_request(b"GET", url)
  828. self.assertEqual(channel.result["code"], b"200", channel.result)
  829. # Check that we're getting HTML back.
  830. content_type = channel.headers.getRawHeaders(b"Content-Type")
  831. self.assertEqual(content_type, [b"text/html; charset=utf-8"], channel.result)
  832. # Check that the HTML we're getting is the one we expect on a successful renewal.
  833. expiration_ts = self.get_success(self.store.get_expiration_ts_for_user(user_id))
  834. expected_html = self.hs.config.account_validity.account_validity_account_renewed_template.render(
  835. expiration_ts=expiration_ts
  836. )
  837. self.assertEqual(
  838. channel.result["body"], expected_html.encode("utf8"), channel.result
  839. )
  840. # Move 1 day forward. Try to renew with the same token again.
  841. url = "/_matrix/client/unstable/account_validity/renew?token=%s" % renewal_token
  842. channel = self.make_request(b"GET", url)
  843. self.assertEqual(channel.result["code"], b"200", channel.result)
  844. # Check that we're getting HTML back.
  845. content_type = channel.headers.getRawHeaders(b"Content-Type")
  846. self.assertEqual(content_type, [b"text/html; charset=utf-8"], channel.result)
  847. # Check that the HTML we're getting is the one we expect when reusing a
  848. # token. The account expiration date should not have changed.
  849. expected_html = self.hs.config.account_validity.account_validity_account_previously_renewed_template.render(
  850. expiration_ts=expiration_ts
  851. )
  852. self.assertEqual(
  853. channel.result["body"], expected_html.encode("utf8"), channel.result
  854. )
  855. # Move 3 days forward. If the renewal failed, every authed request with
  856. # our access token should be denied from now, otherwise they should
  857. # succeed.
  858. self.reactor.advance(datetime.timedelta(days=3).total_seconds())
  859. channel = self.make_request(b"GET", "/sync", access_token=tok)
  860. self.assertEqual(channel.result["code"], b"200", channel.result)
  861. def test_renewal_invalid_token(self) -> None:
  862. # Hit the renewal endpoint with an invalid token and check that it behaves as
  863. # expected, i.e. that it responds with 404 Not Found and the correct HTML.
  864. url = "/_matrix/client/unstable/account_validity/renew?token=123"
  865. channel = self.make_request(b"GET", url)
  866. self.assertEqual(channel.result["code"], b"404", channel.result)
  867. # Check that we're getting HTML back.
  868. content_type = channel.headers.getRawHeaders(b"Content-Type")
  869. self.assertEqual(content_type, [b"text/html; charset=utf-8"], channel.result)
  870. # Check that the HTML we're getting is the one we expect when using an
  871. # invalid/unknown token.
  872. expected_html = (
  873. self.hs.config.account_validity.account_validity_invalid_token_template.render()
  874. )
  875. self.assertEqual(
  876. channel.result["body"], expected_html.encode("utf8"), channel.result
  877. )
  878. def test_manual_email_send(self) -> None:
  879. self.email_attempts = []
  880. (user_id, tok) = self.create_user()
  881. channel = self.make_request(
  882. b"POST",
  883. "/_matrix/client/unstable/account_validity/send_mail",
  884. access_token=tok,
  885. )
  886. self.assertEqual(channel.result["code"], b"200", channel.result)
  887. self.assertEqual(len(self.email_attempts), 1)
  888. def test_deactivated_user(self) -> None:
  889. self.email_attempts = []
  890. (user_id, tok) = self.create_user()
  891. request_data = json.dumps(
  892. {
  893. "auth": {
  894. "type": "m.login.password",
  895. "user": user_id,
  896. "password": "monkey",
  897. },
  898. "erase": False,
  899. }
  900. )
  901. channel = self.make_request(
  902. "POST", "account/deactivate", request_data, access_token=tok
  903. )
  904. self.assertEqual(channel.code, 200)
  905. self.reactor.advance(datetime.timedelta(days=8).total_seconds())
  906. self.assertEqual(len(self.email_attempts), 0)
  907. def create_user(self) -> Tuple[str, str]:
  908. user_id = self.register_user("kermit", "monkey")
  909. tok = self.login("kermit", "monkey")
  910. # We need to manually add an email address otherwise the handler will do
  911. # nothing.
  912. now = self.hs.get_clock().time_msec()
  913. self.get_success(
  914. self.store.user_add_threepid(
  915. user_id=user_id,
  916. medium="email",
  917. address="kermit@example.com",
  918. validated_at=now,
  919. added_at=now,
  920. )
  921. )
  922. return user_id, tok
  923. def test_manual_email_send_expired_account(self) -> None:
  924. user_id = self.register_user("kermit", "monkey")
  925. tok = self.login("kermit", "monkey")
  926. # We need to manually add an email address otherwise the handler will do
  927. # nothing.
  928. now = self.hs.get_clock().time_msec()
  929. self.get_success(
  930. self.store.user_add_threepid(
  931. user_id=user_id,
  932. medium="email",
  933. address="kermit@example.com",
  934. validated_at=now,
  935. added_at=now,
  936. )
  937. )
  938. # Make the account expire.
  939. self.reactor.advance(datetime.timedelta(days=8).total_seconds())
  940. # Ignore all emails sent by the automatic background task and only focus on the
  941. # ones sent manually.
  942. self.email_attempts = []
  943. # Test that we're still able to manually trigger a mail to be sent.
  944. channel = self.make_request(
  945. b"POST",
  946. "/_matrix/client/unstable/account_validity/send_mail",
  947. access_token=tok,
  948. )
  949. self.assertEqual(channel.result["code"], b"200", channel.result)
  950. self.assertEqual(len(self.email_attempts), 1)
  951. class AccountValidityBackgroundJobTestCase(unittest.HomeserverTestCase):
  952. servlets = [synapse.rest.admin.register_servlets_for_client_rest_resource]
  953. def make_homeserver(self, reactor: MemoryReactor, clock: Clock) -> HomeServer:
  954. self.validity_period = 10
  955. self.max_delta = self.validity_period * 10.0 / 100.0
  956. config = self.default_config()
  957. config["enable_registration"] = True
  958. config["account_validity"] = {"enabled": False}
  959. self.hs = self.setup_test_homeserver(config=config)
  960. # We need to set these directly, instead of in the homeserver config dict above.
  961. # This is due to account validity-related config options not being read by
  962. # Synapse when account_validity.enabled is False.
  963. self.hs.get_datastores().main._account_validity_period = self.validity_period
  964. self.hs.get_datastores().main._account_validity_startup_job_max_delta = (
  965. self.max_delta
  966. )
  967. self.store = self.hs.get_datastores().main
  968. return self.hs
  969. def test_background_job(self) -> None:
  970. """
  971. Tests the same thing as test_background_job, except that it sets the
  972. startup_job_max_delta parameter and checks that the expiration date is within the
  973. allowed range.
  974. """
  975. user_id = self.register_user("kermit_delta", "user")
  976. self.hs.config.account_validity.startup_job_max_delta = self.max_delta
  977. now_ms = self.hs.get_clock().time_msec()
  978. self.get_success(self.store._set_expiration_date_when_missing())
  979. res = self.get_success(self.store.get_expiration_ts_for_user(user_id))
  980. self.assertGreaterEqual(res, now_ms + self.validity_period - self.max_delta)
  981. self.assertLessEqual(res, now_ms + self.validity_period)
  982. class RegistrationTokenValidityRestServletTestCase(unittest.HomeserverTestCase):
  983. servlets = [register.register_servlets]
  984. url = "/_matrix/client/v1/register/m.login.registration_token/validity"
  985. def default_config(self) -> Dict[str, Any]:
  986. config = super().default_config()
  987. config["registration_requires_token"] = True
  988. return config
  989. def test_GET_token_valid(self) -> None:
  990. token = "abcd"
  991. store = self.hs.get_datastores().main
  992. self.get_success(
  993. store.db_pool.simple_insert(
  994. "registration_tokens",
  995. {
  996. "token": token,
  997. "uses_allowed": None,
  998. "pending": 0,
  999. "completed": 0,
  1000. "expiry_time": None,
  1001. },
  1002. )
  1003. )
  1004. channel = self.make_request(
  1005. b"GET",
  1006. f"{self.url}?token={token}",
  1007. )
  1008. self.assertEqual(channel.result["code"], b"200", channel.result)
  1009. self.assertEqual(channel.json_body["valid"], True)
  1010. def test_GET_token_invalid(self) -> None:
  1011. token = "1234"
  1012. channel = self.make_request(
  1013. b"GET",
  1014. f"{self.url}?token={token}",
  1015. )
  1016. self.assertEqual(channel.result["code"], b"200", channel.result)
  1017. self.assertEqual(channel.json_body["valid"], False)
  1018. @override_config(
  1019. {"rc_registration_token_validity": {"per_second": 0.1, "burst_count": 5}}
  1020. )
  1021. def test_GET_ratelimiting(self) -> None:
  1022. token = "1234"
  1023. for i in range(0, 6):
  1024. channel = self.make_request(
  1025. b"GET",
  1026. f"{self.url}?token={token}",
  1027. )
  1028. if i == 5:
  1029. self.assertEqual(channel.result["code"], b"429", channel.result)
  1030. retry_after_ms = int(channel.json_body["retry_after_ms"])
  1031. else:
  1032. self.assertEqual(channel.result["code"], b"200", channel.result)
  1033. self.reactor.advance(retry_after_ms / 1000.0 + 1.0)
  1034. channel = self.make_request(
  1035. b"GET",
  1036. f"{self.url}?token={token}",
  1037. )
  1038. self.assertEqual(channel.result["code"], b"200", channel.result)