test_oauth_delegation.py 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664
  1. # Copyright 2022 Matrix.org Foundation C.I.C.
  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 http import HTTPStatus
  15. from typing import Any, Dict, Union
  16. from unittest.mock import ANY, Mock
  17. from urllib.parse import parse_qs
  18. from signedjson.key import (
  19. encode_verify_key_base64,
  20. generate_signing_key,
  21. get_verify_key,
  22. )
  23. from signedjson.sign import sign_json
  24. from twisted.test.proto_helpers import MemoryReactor
  25. from synapse.api.errors import (
  26. AuthError,
  27. Codes,
  28. InvalidClientTokenError,
  29. OAuthInsufficientScopeError,
  30. SynapseError,
  31. )
  32. from synapse.rest import admin
  33. from synapse.rest.client import account, devices, keys, login, logout, register
  34. from synapse.server import HomeServer
  35. from synapse.types import JsonDict
  36. from synapse.util import Clock
  37. from tests.test_utils import FakeResponse, get_awaitable_result, simple_async_mock
  38. from tests.unittest import HomeserverTestCase, skip_unless
  39. from tests.utils import mock_getRawHeaders
  40. try:
  41. import authlib # noqa: F401
  42. HAS_AUTHLIB = True
  43. except ImportError:
  44. HAS_AUTHLIB = False
  45. # These are a few constants that are used as config parameters in the tests.
  46. SERVER_NAME = "test"
  47. ISSUER = "https://issuer/"
  48. CLIENT_ID = "test-client-id"
  49. CLIENT_SECRET = "test-client-secret"
  50. BASE_URL = "https://synapse/"
  51. SCOPES = ["openid"]
  52. AUTHORIZATION_ENDPOINT = ISSUER + "authorize"
  53. TOKEN_ENDPOINT = ISSUER + "token"
  54. USERINFO_ENDPOINT = ISSUER + "userinfo"
  55. WELL_KNOWN = ISSUER + ".well-known/openid-configuration"
  56. JWKS_URI = ISSUER + ".well-known/jwks.json"
  57. INTROSPECTION_ENDPOINT = ISSUER + "introspect"
  58. SYNAPSE_ADMIN_SCOPE = "urn:synapse:admin:*"
  59. MATRIX_USER_SCOPE = "urn:matrix:org.matrix.msc2967.client:api:*"
  60. MATRIX_GUEST_SCOPE = "urn:matrix:org.matrix.msc2967.client:api:guest"
  61. MATRIX_DEVICE_SCOPE_PREFIX = "urn:matrix:org.matrix.msc2967.client:device:"
  62. DEVICE = "AABBCCDD"
  63. MATRIX_DEVICE_SCOPE = MATRIX_DEVICE_SCOPE_PREFIX + DEVICE
  64. SUBJECT = "abc-def-ghi"
  65. USERNAME = "test-user"
  66. USER_ID = "@" + USERNAME + ":" + SERVER_NAME
  67. async def get_json(url: str) -> JsonDict:
  68. # Mock get_json calls to handle jwks & oidc discovery endpoints
  69. if url == WELL_KNOWN:
  70. # Minimal discovery document, as defined in OpenID.Discovery
  71. # https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata
  72. return {
  73. "issuer": ISSUER,
  74. "authorization_endpoint": AUTHORIZATION_ENDPOINT,
  75. "token_endpoint": TOKEN_ENDPOINT,
  76. "jwks_uri": JWKS_URI,
  77. "userinfo_endpoint": USERINFO_ENDPOINT,
  78. "introspection_endpoint": INTROSPECTION_ENDPOINT,
  79. "response_types_supported": ["code"],
  80. "subject_types_supported": ["public"],
  81. "id_token_signing_alg_values_supported": ["RS256"],
  82. }
  83. elif url == JWKS_URI:
  84. return {"keys": []}
  85. return {}
  86. @skip_unless(HAS_AUTHLIB, "requires authlib")
  87. class MSC3861OAuthDelegation(HomeserverTestCase):
  88. servlets = [
  89. account.register_servlets,
  90. devices.register_servlets,
  91. keys.register_servlets,
  92. register.register_servlets,
  93. login.register_servlets,
  94. logout.register_servlets,
  95. admin.register_servlets,
  96. ]
  97. def default_config(self) -> Dict[str, Any]:
  98. config = super().default_config()
  99. config["public_baseurl"] = BASE_URL
  100. config["disable_registration"] = True
  101. config["experimental_features"] = {
  102. "msc3861": {
  103. "enabled": True,
  104. "issuer": ISSUER,
  105. "client_id": CLIENT_ID,
  106. "client_auth_method": "client_secret_post",
  107. "client_secret": CLIENT_SECRET,
  108. }
  109. }
  110. return config
  111. def make_homeserver(self, reactor: MemoryReactor, clock: Clock) -> HomeServer:
  112. self.http_client = Mock(spec=["get_json"])
  113. self.http_client.get_json.side_effect = get_json
  114. self.http_client.user_agent = b"Synapse Test"
  115. hs = self.setup_test_homeserver(proxied_http_client=self.http_client)
  116. self.auth = hs.get_auth()
  117. return hs
  118. def _assertParams(self) -> None:
  119. """Assert that the request parameters are correct."""
  120. params = parse_qs(self.http_client.request.call_args[1]["data"].decode("utf-8"))
  121. self.assertEqual(params["token"], ["mockAccessToken"])
  122. self.assertEqual(params["client_id"], [CLIENT_ID])
  123. self.assertEqual(params["client_secret"], [CLIENT_SECRET])
  124. def test_inactive_token(self) -> None:
  125. """The handler should return a 403 where the token is inactive."""
  126. self.http_client.request = simple_async_mock(
  127. return_value=FakeResponse.json(
  128. code=200,
  129. payload={"active": False},
  130. )
  131. )
  132. request = Mock(args={})
  133. request.args[b"access_token"] = [b"mockAccessToken"]
  134. request.requestHeaders.getRawHeaders = mock_getRawHeaders()
  135. self.get_failure(self.auth.get_user_by_req(request), InvalidClientTokenError)
  136. self.http_client.get_json.assert_called_once_with(WELL_KNOWN)
  137. self.http_client.request.assert_called_once_with(
  138. method="POST", uri=INTROSPECTION_ENDPOINT, data=ANY, headers=ANY
  139. )
  140. self._assertParams()
  141. def test_active_no_scope(self) -> None:
  142. """The handler should return a 403 where no scope is given."""
  143. self.http_client.request = simple_async_mock(
  144. return_value=FakeResponse.json(
  145. code=200,
  146. payload={"active": True},
  147. )
  148. )
  149. request = Mock(args={})
  150. request.args[b"access_token"] = [b"mockAccessToken"]
  151. request.requestHeaders.getRawHeaders = mock_getRawHeaders()
  152. self.get_failure(self.auth.get_user_by_req(request), InvalidClientTokenError)
  153. self.http_client.get_json.assert_called_once_with(WELL_KNOWN)
  154. self.http_client.request.assert_called_once_with(
  155. method="POST", uri=INTROSPECTION_ENDPOINT, data=ANY, headers=ANY
  156. )
  157. self._assertParams()
  158. def test_active_user_no_subject(self) -> None:
  159. """The handler should return a 500 when no subject is present."""
  160. self.http_client.request = simple_async_mock(
  161. return_value=FakeResponse.json(
  162. code=200,
  163. payload={"active": True, "scope": " ".join([MATRIX_USER_SCOPE])},
  164. )
  165. )
  166. request = Mock(args={})
  167. request.args[b"access_token"] = [b"mockAccessToken"]
  168. request.requestHeaders.getRawHeaders = mock_getRawHeaders()
  169. self.get_failure(self.auth.get_user_by_req(request), InvalidClientTokenError)
  170. self.http_client.get_json.assert_called_once_with(WELL_KNOWN)
  171. self.http_client.request.assert_called_once_with(
  172. method="POST", uri=INTROSPECTION_ENDPOINT, data=ANY, headers=ANY
  173. )
  174. self._assertParams()
  175. def test_active_no_user_scope(self) -> None:
  176. """The handler should return a 500 when no subject is present."""
  177. self.http_client.request = simple_async_mock(
  178. return_value=FakeResponse.json(
  179. code=200,
  180. payload={
  181. "active": True,
  182. "sub": SUBJECT,
  183. "scope": " ".join([MATRIX_DEVICE_SCOPE]),
  184. },
  185. )
  186. )
  187. request = Mock(args={})
  188. request.args[b"access_token"] = [b"mockAccessToken"]
  189. request.requestHeaders.getRawHeaders = mock_getRawHeaders()
  190. self.get_failure(self.auth.get_user_by_req(request), InvalidClientTokenError)
  191. self.http_client.get_json.assert_called_once_with(WELL_KNOWN)
  192. self.http_client.request.assert_called_once_with(
  193. method="POST", uri=INTROSPECTION_ENDPOINT, data=ANY, headers=ANY
  194. )
  195. self._assertParams()
  196. def test_active_admin_not_user(self) -> None:
  197. """The handler should raise when the scope has admin right but not user."""
  198. self.http_client.request = simple_async_mock(
  199. return_value=FakeResponse.json(
  200. code=200,
  201. payload={
  202. "active": True,
  203. "sub": SUBJECT,
  204. "scope": " ".join([SYNAPSE_ADMIN_SCOPE]),
  205. "username": USERNAME,
  206. },
  207. )
  208. )
  209. request = Mock(args={})
  210. request.args[b"access_token"] = [b"mockAccessToken"]
  211. request.requestHeaders.getRawHeaders = mock_getRawHeaders()
  212. self.get_failure(self.auth.get_user_by_req(request), InvalidClientTokenError)
  213. self.http_client.get_json.assert_called_once_with(WELL_KNOWN)
  214. self.http_client.request.assert_called_once_with(
  215. method="POST", uri=INTROSPECTION_ENDPOINT, data=ANY, headers=ANY
  216. )
  217. self._assertParams()
  218. def test_active_admin(self) -> None:
  219. """The handler should return a requester with admin rights."""
  220. self.http_client.request = simple_async_mock(
  221. return_value=FakeResponse.json(
  222. code=200,
  223. payload={
  224. "active": True,
  225. "sub": SUBJECT,
  226. "scope": " ".join([SYNAPSE_ADMIN_SCOPE, MATRIX_USER_SCOPE]),
  227. "username": USERNAME,
  228. },
  229. )
  230. )
  231. request = Mock(args={})
  232. request.args[b"access_token"] = [b"mockAccessToken"]
  233. request.requestHeaders.getRawHeaders = mock_getRawHeaders()
  234. requester = self.get_success(self.auth.get_user_by_req(request))
  235. self.http_client.get_json.assert_called_once_with(WELL_KNOWN)
  236. self.http_client.request.assert_called_once_with(
  237. method="POST", uri=INTROSPECTION_ENDPOINT, data=ANY, headers=ANY
  238. )
  239. self._assertParams()
  240. self.assertEqual(requester.user.to_string(), "@%s:%s" % (USERNAME, SERVER_NAME))
  241. self.assertEqual(requester.is_guest, False)
  242. self.assertEqual(requester.device_id, None)
  243. self.assertEqual(
  244. get_awaitable_result(self.auth.is_server_admin(requester)), True
  245. )
  246. def test_active_admin_highest_privilege(self) -> None:
  247. """The handler should resolve to the most permissive scope."""
  248. self.http_client.request = simple_async_mock(
  249. return_value=FakeResponse.json(
  250. code=200,
  251. payload={
  252. "active": True,
  253. "sub": SUBJECT,
  254. "scope": " ".join(
  255. [SYNAPSE_ADMIN_SCOPE, MATRIX_USER_SCOPE, MATRIX_GUEST_SCOPE]
  256. ),
  257. "username": USERNAME,
  258. },
  259. )
  260. )
  261. request = Mock(args={})
  262. request.args[b"access_token"] = [b"mockAccessToken"]
  263. request.requestHeaders.getRawHeaders = mock_getRawHeaders()
  264. requester = self.get_success(self.auth.get_user_by_req(request))
  265. self.http_client.get_json.assert_called_once_with(WELL_KNOWN)
  266. self.http_client.request.assert_called_once_with(
  267. method="POST", uri=INTROSPECTION_ENDPOINT, data=ANY, headers=ANY
  268. )
  269. self._assertParams()
  270. self.assertEqual(requester.user.to_string(), "@%s:%s" % (USERNAME, SERVER_NAME))
  271. self.assertEqual(requester.is_guest, False)
  272. self.assertEqual(requester.device_id, None)
  273. self.assertEqual(
  274. get_awaitable_result(self.auth.is_server_admin(requester)), True
  275. )
  276. def test_active_user(self) -> None:
  277. """The handler should return a requester with normal user rights."""
  278. self.http_client.request = simple_async_mock(
  279. return_value=FakeResponse.json(
  280. code=200,
  281. payload={
  282. "active": True,
  283. "sub": SUBJECT,
  284. "scope": " ".join([MATRIX_USER_SCOPE]),
  285. "username": USERNAME,
  286. },
  287. )
  288. )
  289. request = Mock(args={})
  290. request.args[b"access_token"] = [b"mockAccessToken"]
  291. request.requestHeaders.getRawHeaders = mock_getRawHeaders()
  292. requester = self.get_success(self.auth.get_user_by_req(request))
  293. self.http_client.get_json.assert_called_once_with(WELL_KNOWN)
  294. self.http_client.request.assert_called_once_with(
  295. method="POST", uri=INTROSPECTION_ENDPOINT, data=ANY, headers=ANY
  296. )
  297. self._assertParams()
  298. self.assertEqual(requester.user.to_string(), "@%s:%s" % (USERNAME, SERVER_NAME))
  299. self.assertEqual(requester.is_guest, False)
  300. self.assertEqual(requester.device_id, None)
  301. self.assertEqual(
  302. get_awaitable_result(self.auth.is_server_admin(requester)), False
  303. )
  304. def test_active_user_with_device(self) -> None:
  305. """The handler should return a requester with normal user rights and a device ID."""
  306. self.http_client.request = simple_async_mock(
  307. return_value=FakeResponse.json(
  308. code=200,
  309. payload={
  310. "active": True,
  311. "sub": SUBJECT,
  312. "scope": " ".join([MATRIX_USER_SCOPE, MATRIX_DEVICE_SCOPE]),
  313. "username": USERNAME,
  314. },
  315. )
  316. )
  317. request = Mock(args={})
  318. request.args[b"access_token"] = [b"mockAccessToken"]
  319. request.requestHeaders.getRawHeaders = mock_getRawHeaders()
  320. requester = self.get_success(self.auth.get_user_by_req(request))
  321. self.http_client.get_json.assert_called_once_with(WELL_KNOWN)
  322. self.http_client.request.assert_called_once_with(
  323. method="POST", uri=INTROSPECTION_ENDPOINT, data=ANY, headers=ANY
  324. )
  325. self._assertParams()
  326. self.assertEqual(requester.user.to_string(), "@%s:%s" % (USERNAME, SERVER_NAME))
  327. self.assertEqual(requester.is_guest, False)
  328. self.assertEqual(
  329. get_awaitable_result(self.auth.is_server_admin(requester)), False
  330. )
  331. self.assertEqual(requester.device_id, DEVICE)
  332. def test_multiple_devices(self) -> None:
  333. """The handler should raise an error if multiple devices are found in the scope."""
  334. self.http_client.request = simple_async_mock(
  335. return_value=FakeResponse.json(
  336. code=200,
  337. payload={
  338. "active": True,
  339. "sub": SUBJECT,
  340. "scope": " ".join(
  341. [
  342. MATRIX_USER_SCOPE,
  343. f"{MATRIX_DEVICE_SCOPE_PREFIX}AABBCC",
  344. f"{MATRIX_DEVICE_SCOPE_PREFIX}DDEEFF",
  345. ]
  346. ),
  347. "username": USERNAME,
  348. },
  349. )
  350. )
  351. request = Mock(args={})
  352. request.args[b"access_token"] = [b"mockAccessToken"]
  353. request.requestHeaders.getRawHeaders = mock_getRawHeaders()
  354. self.get_failure(self.auth.get_user_by_req(request), AuthError)
  355. def test_active_guest_not_allowed(self) -> None:
  356. """The handler should return an insufficient scope error."""
  357. self.http_client.request = simple_async_mock(
  358. return_value=FakeResponse.json(
  359. code=200,
  360. payload={
  361. "active": True,
  362. "sub": SUBJECT,
  363. "scope": " ".join([MATRIX_GUEST_SCOPE, MATRIX_DEVICE_SCOPE]),
  364. "username": USERNAME,
  365. },
  366. )
  367. )
  368. request = Mock(args={})
  369. request.args[b"access_token"] = [b"mockAccessToken"]
  370. request.requestHeaders.getRawHeaders = mock_getRawHeaders()
  371. error = self.get_failure(
  372. self.auth.get_user_by_req(request), OAuthInsufficientScopeError
  373. )
  374. self.http_client.get_json.assert_called_once_with(WELL_KNOWN)
  375. self.http_client.request.assert_called_once_with(
  376. method="POST", uri=INTROSPECTION_ENDPOINT, data=ANY, headers=ANY
  377. )
  378. self._assertParams()
  379. self.assertEqual(
  380. getattr(error.value, "headers", {})["WWW-Authenticate"],
  381. 'Bearer error="insufficient_scope", scope="urn:matrix:org.matrix.msc2967.client:api:*"',
  382. )
  383. def test_active_guest_allowed(self) -> None:
  384. """The handler should return a requester with guest user rights and a device ID."""
  385. self.http_client.request = simple_async_mock(
  386. return_value=FakeResponse.json(
  387. code=200,
  388. payload={
  389. "active": True,
  390. "sub": SUBJECT,
  391. "scope": " ".join([MATRIX_GUEST_SCOPE, MATRIX_DEVICE_SCOPE]),
  392. "username": USERNAME,
  393. },
  394. )
  395. )
  396. request = Mock(args={})
  397. request.args[b"access_token"] = [b"mockAccessToken"]
  398. request.requestHeaders.getRawHeaders = mock_getRawHeaders()
  399. requester = self.get_success(
  400. self.auth.get_user_by_req(request, allow_guest=True)
  401. )
  402. self.http_client.get_json.assert_called_once_with(WELL_KNOWN)
  403. self.http_client.request.assert_called_once_with(
  404. method="POST", uri=INTROSPECTION_ENDPOINT, data=ANY, headers=ANY
  405. )
  406. self._assertParams()
  407. self.assertEqual(requester.user.to_string(), "@%s:%s" % (USERNAME, SERVER_NAME))
  408. self.assertEqual(requester.is_guest, True)
  409. self.assertEqual(
  410. get_awaitable_result(self.auth.is_server_admin(requester)), False
  411. )
  412. self.assertEqual(requester.device_id, DEVICE)
  413. def test_unavailable_introspection_endpoint(self) -> None:
  414. """The handler should return an internal server error."""
  415. request = Mock(args={})
  416. request.args[b"access_token"] = [b"mockAccessToken"]
  417. request.requestHeaders.getRawHeaders = mock_getRawHeaders()
  418. # The introspection endpoint is returning an error.
  419. self.http_client.request = simple_async_mock(
  420. return_value=FakeResponse(code=500, body=b"Internal Server Error")
  421. )
  422. error = self.get_failure(self.auth.get_user_by_req(request), SynapseError)
  423. self.assertEqual(error.value.code, 503)
  424. # The introspection endpoint request fails.
  425. self.http_client.request = simple_async_mock(raises=Exception())
  426. error = self.get_failure(self.auth.get_user_by_req(request), SynapseError)
  427. self.assertEqual(error.value.code, 503)
  428. # The introspection endpoint does not return a JSON object.
  429. self.http_client.request = simple_async_mock(
  430. return_value=FakeResponse.json(
  431. code=200, payload=["this is an array", "not an object"]
  432. )
  433. )
  434. error = self.get_failure(self.auth.get_user_by_req(request), SynapseError)
  435. self.assertEqual(error.value.code, 503)
  436. # The introspection endpoint does not return valid JSON.
  437. self.http_client.request = simple_async_mock(
  438. return_value=FakeResponse(code=200, body=b"this is not valid JSON")
  439. )
  440. error = self.get_failure(self.auth.get_user_by_req(request), SynapseError)
  441. self.assertEqual(error.value.code, 503)
  442. def make_device_keys(self, user_id: str, device_id: str) -> JsonDict:
  443. # We only generate a master key to simplify the test.
  444. master_signing_key = generate_signing_key(device_id)
  445. master_verify_key = encode_verify_key_base64(get_verify_key(master_signing_key))
  446. return {
  447. "master_key": sign_json(
  448. {
  449. "user_id": user_id,
  450. "usage": ["master"],
  451. "keys": {"ed25519:" + master_verify_key: master_verify_key},
  452. },
  453. user_id,
  454. master_signing_key,
  455. ),
  456. }
  457. def test_cross_signing(self) -> None:
  458. """Try uploading device keys with OAuth delegation enabled."""
  459. self.http_client.request = simple_async_mock(
  460. return_value=FakeResponse.json(
  461. code=200,
  462. payload={
  463. "active": True,
  464. "sub": SUBJECT,
  465. "scope": " ".join([MATRIX_USER_SCOPE, MATRIX_DEVICE_SCOPE]),
  466. "username": USERNAME,
  467. },
  468. )
  469. )
  470. keys_upload_body = self.make_device_keys(USER_ID, DEVICE)
  471. channel = self.make_request(
  472. "POST",
  473. "/_matrix/client/v3/keys/device_signing/upload",
  474. keys_upload_body,
  475. access_token="mockAccessToken",
  476. )
  477. self.assertEqual(channel.code, 200, channel.json_body)
  478. channel = self.make_request(
  479. "POST",
  480. "/_matrix/client/v3/keys/device_signing/upload",
  481. keys_upload_body,
  482. access_token="mockAccessToken",
  483. )
  484. self.assertEqual(channel.code, HTTPStatus.NOT_IMPLEMENTED, channel.json_body)
  485. def expect_unauthorized(
  486. self, method: str, path: str, content: Union[bytes, str, JsonDict] = ""
  487. ) -> None:
  488. channel = self.make_request(method, path, content, shorthand=False)
  489. self.assertEqual(channel.code, 401, channel.json_body)
  490. def expect_unrecognized(
  491. self, method: str, path: str, content: Union[bytes, str, JsonDict] = ""
  492. ) -> None:
  493. channel = self.make_request(method, path, content)
  494. self.assertEqual(channel.code, 404, channel.json_body)
  495. self.assertEqual(
  496. channel.json_body["errcode"], Codes.UNRECOGNIZED, channel.json_body
  497. )
  498. def test_uia_endpoints(self) -> None:
  499. """Test that endpoints that were removed in MSC2964 are no longer available."""
  500. # This is just an endpoint that should remain visible (but requires auth):
  501. self.expect_unauthorized("GET", "/_matrix/client/v3/devices")
  502. # This remains usable, but will require a uia scope:
  503. self.expect_unauthorized(
  504. "POST", "/_matrix/client/v3/keys/device_signing/upload"
  505. )
  506. def test_3pid_endpoints(self) -> None:
  507. """Test that 3pid account management endpoints that were removed in MSC2964 are no longer available."""
  508. # Remains and requires auth:
  509. self.expect_unauthorized("GET", "/_matrix/client/v3/account/3pid")
  510. self.expect_unauthorized(
  511. "POST",
  512. "/_matrix/client/v3/account/3pid/bind",
  513. {
  514. "client_secret": "foo",
  515. "id_access_token": "bar",
  516. "id_server": "foo",
  517. "sid": "bar",
  518. },
  519. )
  520. self.expect_unauthorized("POST", "/_matrix/client/v3/account/3pid/unbind", {})
  521. # These are gone:
  522. self.expect_unrecognized(
  523. "POST", "/_matrix/client/v3/account/3pid"
  524. ) # deprecated
  525. self.expect_unrecognized("POST", "/_matrix/client/v3/account/3pid/add")
  526. self.expect_unrecognized("POST", "/_matrix/client/v3/account/3pid/delete")
  527. self.expect_unrecognized(
  528. "POST", "/_matrix/client/v3/account/3pid/email/requestToken"
  529. )
  530. self.expect_unrecognized(
  531. "POST", "/_matrix/client/v3/account/3pid/msisdn/requestToken"
  532. )
  533. def test_account_management_endpoints_removed(self) -> None:
  534. """Test that account management endpoints that were removed in MSC2964 are no longer available."""
  535. self.expect_unrecognized("POST", "/_matrix/client/v3/account/deactivate")
  536. self.expect_unrecognized("POST", "/_matrix/client/v3/account/password")
  537. self.expect_unrecognized(
  538. "POST", "/_matrix/client/v3/account/password/email/requestToken"
  539. )
  540. self.expect_unrecognized(
  541. "POST", "/_matrix/client/v3/account/password/msisdn/requestToken"
  542. )
  543. def test_registration_endpoints_removed(self) -> None:
  544. """Test that registration endpoints that were removed in MSC2964 are no longer available."""
  545. self.expect_unrecognized(
  546. "GET", "/_matrix/client/v1/register/m.login.registration_token/validity"
  547. )
  548. # This is still available for AS registrations
  549. # self.expect_unrecognized("POST", "/_matrix/client/v3/register")
  550. self.expect_unrecognized("GET", "/_matrix/client/v3/register/available")
  551. self.expect_unrecognized(
  552. "POST", "/_matrix/client/v3/register/email/requestToken"
  553. )
  554. self.expect_unrecognized(
  555. "POST", "/_matrix/client/v3/register/msisdn/requestToken"
  556. )
  557. def test_session_management_endpoints_removed(self) -> None:
  558. """Test that session management endpoints that were removed in MSC2964 are no longer available."""
  559. self.expect_unrecognized("GET", "/_matrix/client/v3/login")
  560. self.expect_unrecognized("POST", "/_matrix/client/v3/login")
  561. self.expect_unrecognized("GET", "/_matrix/client/v3/login/sso/redirect")
  562. self.expect_unrecognized("POST", "/_matrix/client/v3/logout")
  563. self.expect_unrecognized("POST", "/_matrix/client/v3/logout/all")
  564. self.expect_unrecognized("POST", "/_matrix/client/v3/refresh")
  565. self.expect_unrecognized("GET", "/_matrix/static/client/login")
  566. def test_device_management_endpoints_removed(self) -> None:
  567. """Test that device management endpoints that were removed in MSC2964 are no longer available."""
  568. self.expect_unrecognized("POST", "/_matrix/client/v3/delete_devices")
  569. self.expect_unrecognized("DELETE", "/_matrix/client/v3/devices/{DEVICE}")
  570. def test_openid_endpoints_removed(self) -> None:
  571. """Test that OpenID id_token endpoints that were removed in MSC2964 are no longer available."""
  572. self.expect_unrecognized(
  573. "POST", "/_matrix/client/v3/user/{USERNAME}/openid/request_token"
  574. )
  575. def test_admin_api_endpoints_removed(self) -> None:
  576. """Test that admin API endpoints that were removed in MSC2964 are no longer available."""
  577. self.expect_unrecognized("GET", "/_synapse/admin/v1/registration_tokens")
  578. self.expect_unrecognized("POST", "/_synapse/admin/v1/registration_tokens/new")
  579. self.expect_unrecognized("GET", "/_synapse/admin/v1/registration_tokens/abcd")
  580. self.expect_unrecognized("PUT", "/_synapse/admin/v1/registration_tokens/abcd")
  581. self.expect_unrecognized(
  582. "DELETE", "/_synapse/admin/v1/registration_tokens/abcd"
  583. )
  584. self.expect_unrecognized("POST", "/_synapse/admin/v1/reset_password/foo")
  585. self.expect_unrecognized("POST", "/_synapse/admin/v1/users/foo/login")
  586. self.expect_unrecognized("GET", "/_synapse/admin/v1/register")
  587. self.expect_unrecognized("POST", "/_synapse/admin/v1/register")
  588. self.expect_unrecognized("GET", "/_synapse/admin/v1/users/foo/admin")
  589. self.expect_unrecognized("PUT", "/_synapse/admin/v1/users/foo/admin")
  590. self.expect_unrecognized("POST", "/_synapse/admin/v1/account_validity/validity")