test_auth.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389
  1. # -*- coding: utf-8 -*-
  2. # Copyright 2015 - 2016 OpenMarket Ltd
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License");
  5. # you may not use this file except in compliance with the License.
  6. # You may obtain a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS,
  12. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. # See the License for the specific language governing permissions and
  14. # limitations under the License.
  15. from mock import Mock
  16. import pymacaroons
  17. from twisted.internet import defer
  18. import synapse.handlers.auth
  19. from synapse.api.auth import Auth
  20. from synapse.api.errors import (
  21. AuthError,
  22. Codes,
  23. InvalidClientCredentialsError,
  24. InvalidClientTokenError,
  25. MissingClientTokenError,
  26. ResourceLimitError,
  27. )
  28. from synapse.types import UserID
  29. from tests import unittest
  30. from tests.utils import mock_getRawHeaders, setup_test_homeserver
  31. class TestHandlers(object):
  32. def __init__(self, hs):
  33. self.auth_handler = synapse.handlers.auth.AuthHandler(hs)
  34. class AuthTestCase(unittest.TestCase):
  35. @defer.inlineCallbacks
  36. def setUp(self):
  37. self.state_handler = Mock()
  38. self.store = Mock()
  39. self.hs = yield setup_test_homeserver(self.addCleanup, handlers=None)
  40. self.hs.get_datastore = Mock(return_value=self.store)
  41. self.hs.handlers = TestHandlers(self.hs)
  42. self.auth = Auth(self.hs)
  43. self.test_user = "@foo:bar"
  44. self.test_token = b"_test_token_"
  45. # this is overridden for the appservice tests
  46. self.store.get_app_service_by_token = Mock(return_value=None)
  47. self.store.is_support_user = Mock(return_value=defer.succeed(False))
  48. @defer.inlineCallbacks
  49. def test_get_user_by_req_user_valid_token(self):
  50. user_info = {"name": self.test_user, "token_id": "ditto", "device_id": "device"}
  51. self.store.get_user_by_access_token = Mock(return_value=user_info)
  52. request = Mock(args={})
  53. request.args[b"access_token"] = [self.test_token]
  54. request.requestHeaders.getRawHeaders = mock_getRawHeaders()
  55. requester = yield self.auth.get_user_by_req(request)
  56. self.assertEquals(requester.user.to_string(), self.test_user)
  57. def test_get_user_by_req_user_bad_token(self):
  58. self.store.get_user_by_access_token = Mock(return_value=None)
  59. request = Mock(args={})
  60. request.args[b"access_token"] = [self.test_token]
  61. request.requestHeaders.getRawHeaders = mock_getRawHeaders()
  62. d = self.auth.get_user_by_req(request)
  63. f = self.failureResultOf(d, InvalidClientTokenError).value
  64. self.assertEqual(f.code, 401)
  65. self.assertEqual(f.errcode, "M_UNKNOWN_TOKEN")
  66. def test_get_user_by_req_user_missing_token(self):
  67. user_info = {"name": self.test_user, "token_id": "ditto"}
  68. self.store.get_user_by_access_token = Mock(return_value=user_info)
  69. request = Mock(args={})
  70. request.requestHeaders.getRawHeaders = mock_getRawHeaders()
  71. d = self.auth.get_user_by_req(request)
  72. f = self.failureResultOf(d, MissingClientTokenError).value
  73. self.assertEqual(f.code, 401)
  74. self.assertEqual(f.errcode, "M_MISSING_TOKEN")
  75. @defer.inlineCallbacks
  76. def test_get_user_by_req_appservice_valid_token(self):
  77. app_service = Mock(
  78. token="foobar", url="a_url", sender=self.test_user, ip_range_whitelist=None
  79. )
  80. self.store.get_app_service_by_token = Mock(return_value=app_service)
  81. self.store.get_user_by_access_token = Mock(return_value=None)
  82. request = Mock(args={})
  83. request.getClientIP.return_value = "127.0.0.1"
  84. request.args[b"access_token"] = [self.test_token]
  85. request.requestHeaders.getRawHeaders = mock_getRawHeaders()
  86. requester = yield self.auth.get_user_by_req(request)
  87. self.assertEquals(requester.user.to_string(), self.test_user)
  88. @defer.inlineCallbacks
  89. def test_get_user_by_req_appservice_valid_token_good_ip(self):
  90. from netaddr import IPSet
  91. app_service = Mock(
  92. token="foobar",
  93. url="a_url",
  94. sender=self.test_user,
  95. ip_range_whitelist=IPSet(["192.168/16"]),
  96. )
  97. self.store.get_app_service_by_token = Mock(return_value=app_service)
  98. self.store.get_user_by_access_token = Mock(return_value=None)
  99. request = Mock(args={})
  100. request.getClientIP.return_value = "192.168.10.10"
  101. request.args[b"access_token"] = [self.test_token]
  102. request.requestHeaders.getRawHeaders = mock_getRawHeaders()
  103. requester = yield self.auth.get_user_by_req(request)
  104. self.assertEquals(requester.user.to_string(), self.test_user)
  105. def test_get_user_by_req_appservice_valid_token_bad_ip(self):
  106. from netaddr import IPSet
  107. app_service = Mock(
  108. token="foobar",
  109. url="a_url",
  110. sender=self.test_user,
  111. ip_range_whitelist=IPSet(["192.168/16"]),
  112. )
  113. self.store.get_app_service_by_token = Mock(return_value=app_service)
  114. self.store.get_user_by_access_token = Mock(return_value=None)
  115. request = Mock(args={})
  116. request.getClientIP.return_value = "131.111.8.42"
  117. request.args[b"access_token"] = [self.test_token]
  118. request.requestHeaders.getRawHeaders = mock_getRawHeaders()
  119. d = self.auth.get_user_by_req(request)
  120. f = self.failureResultOf(d, InvalidClientTokenError).value
  121. self.assertEqual(f.code, 401)
  122. self.assertEqual(f.errcode, "M_UNKNOWN_TOKEN")
  123. def test_get_user_by_req_appservice_bad_token(self):
  124. self.store.get_app_service_by_token = Mock(return_value=None)
  125. self.store.get_user_by_access_token = Mock(return_value=None)
  126. request = Mock(args={})
  127. request.args[b"access_token"] = [self.test_token]
  128. request.requestHeaders.getRawHeaders = mock_getRawHeaders()
  129. d = self.auth.get_user_by_req(request)
  130. f = self.failureResultOf(d, InvalidClientTokenError).value
  131. self.assertEqual(f.code, 401)
  132. self.assertEqual(f.errcode, "M_UNKNOWN_TOKEN")
  133. def test_get_user_by_req_appservice_missing_token(self):
  134. app_service = Mock(token="foobar", url="a_url", sender=self.test_user)
  135. self.store.get_app_service_by_token = Mock(return_value=app_service)
  136. self.store.get_user_by_access_token = Mock(return_value=None)
  137. request = Mock(args={})
  138. request.requestHeaders.getRawHeaders = mock_getRawHeaders()
  139. d = self.auth.get_user_by_req(request)
  140. f = self.failureResultOf(d, MissingClientTokenError).value
  141. self.assertEqual(f.code, 401)
  142. self.assertEqual(f.errcode, "M_MISSING_TOKEN")
  143. @defer.inlineCallbacks
  144. def test_get_user_by_req_appservice_valid_token_valid_user_id(self):
  145. masquerading_user_id = b"@doppelganger:matrix.org"
  146. app_service = Mock(
  147. token="foobar", url="a_url", sender=self.test_user, ip_range_whitelist=None
  148. )
  149. app_service.is_interested_in_user = Mock(return_value=True)
  150. self.store.get_app_service_by_token = Mock(return_value=app_service)
  151. self.store.get_user_by_access_token = Mock(return_value=None)
  152. request = Mock(args={})
  153. request.getClientIP.return_value = "127.0.0.1"
  154. request.args[b"access_token"] = [self.test_token]
  155. request.args[b"user_id"] = [masquerading_user_id]
  156. request.requestHeaders.getRawHeaders = mock_getRawHeaders()
  157. requester = yield self.auth.get_user_by_req(request)
  158. self.assertEquals(
  159. requester.user.to_string(), masquerading_user_id.decode("utf8")
  160. )
  161. def test_get_user_by_req_appservice_valid_token_bad_user_id(self):
  162. masquerading_user_id = b"@doppelganger:matrix.org"
  163. app_service = Mock(
  164. token="foobar", url="a_url", sender=self.test_user, ip_range_whitelist=None
  165. )
  166. app_service.is_interested_in_user = Mock(return_value=False)
  167. self.store.get_app_service_by_token = Mock(return_value=app_service)
  168. self.store.get_user_by_access_token = Mock(return_value=None)
  169. request = Mock(args={})
  170. request.getClientIP.return_value = "127.0.0.1"
  171. request.args[b"access_token"] = [self.test_token]
  172. request.args[b"user_id"] = [masquerading_user_id]
  173. request.requestHeaders.getRawHeaders = mock_getRawHeaders()
  174. d = self.auth.get_user_by_req(request)
  175. self.failureResultOf(d, AuthError)
  176. @defer.inlineCallbacks
  177. def test_get_user_from_macaroon(self):
  178. self.store.get_user_by_access_token = Mock(
  179. return_value={"name": "@baldrick:matrix.org", "device_id": "device"}
  180. )
  181. user_id = "@baldrick:matrix.org"
  182. macaroon = pymacaroons.Macaroon(
  183. location=self.hs.config.server_name,
  184. identifier="key",
  185. key=self.hs.config.macaroon_secret_key,
  186. )
  187. macaroon.add_first_party_caveat("gen = 1")
  188. macaroon.add_first_party_caveat("type = access")
  189. macaroon.add_first_party_caveat("user_id = %s" % (user_id,))
  190. user_info = yield self.auth.get_user_by_access_token(macaroon.serialize())
  191. user = user_info["user"]
  192. self.assertEqual(UserID.from_string(user_id), user)
  193. # TODO: device_id should come from the macaroon, but currently comes
  194. # from the db.
  195. self.assertEqual(user_info["device_id"], "device")
  196. @defer.inlineCallbacks
  197. def test_get_guest_user_from_macaroon(self):
  198. self.store.get_user_by_id = Mock(return_value={"is_guest": True})
  199. self.store.get_user_by_access_token = Mock(return_value=None)
  200. user_id = "@baldrick:matrix.org"
  201. macaroon = pymacaroons.Macaroon(
  202. location=self.hs.config.server_name,
  203. identifier="key",
  204. key=self.hs.config.macaroon_secret_key,
  205. )
  206. macaroon.add_first_party_caveat("gen = 1")
  207. macaroon.add_first_party_caveat("type = access")
  208. macaroon.add_first_party_caveat("user_id = %s" % (user_id,))
  209. macaroon.add_first_party_caveat("guest = true")
  210. serialized = macaroon.serialize()
  211. user_info = yield self.auth.get_user_by_access_token(serialized)
  212. user = user_info["user"]
  213. is_guest = user_info["is_guest"]
  214. self.assertEqual(UserID.from_string(user_id), user)
  215. self.assertTrue(is_guest)
  216. self.store.get_user_by_id.assert_called_with(user_id)
  217. @defer.inlineCallbacks
  218. def test_cannot_use_regular_token_as_guest(self):
  219. USER_ID = "@percy:matrix.org"
  220. self.store.add_access_token_to_user = Mock()
  221. token = yield self.hs.handlers.auth_handler.get_access_token_for_user_id(
  222. USER_ID, "DEVICE", valid_until_ms=None
  223. )
  224. self.store.add_access_token_to_user.assert_called_with(
  225. USER_ID, token, "DEVICE", None
  226. )
  227. def get_user(tok):
  228. if token != tok:
  229. return None
  230. return {
  231. "name": USER_ID,
  232. "is_guest": False,
  233. "token_id": 1234,
  234. "device_id": "DEVICE",
  235. }
  236. self.store.get_user_by_access_token = get_user
  237. self.store.get_user_by_id = Mock(return_value={"is_guest": False})
  238. # check the token works
  239. request = Mock(args={})
  240. request.args[b"access_token"] = [token.encode("ascii")]
  241. request.requestHeaders.getRawHeaders = mock_getRawHeaders()
  242. requester = yield self.auth.get_user_by_req(request, allow_guest=True)
  243. self.assertEqual(UserID.from_string(USER_ID), requester.user)
  244. self.assertFalse(requester.is_guest)
  245. # add an is_guest caveat
  246. mac = pymacaroons.Macaroon.deserialize(token)
  247. mac.add_first_party_caveat("guest = true")
  248. guest_tok = mac.serialize()
  249. # the token should *not* work now
  250. request = Mock(args={})
  251. request.args[b"access_token"] = [guest_tok.encode("ascii")]
  252. request.requestHeaders.getRawHeaders = mock_getRawHeaders()
  253. with self.assertRaises(InvalidClientCredentialsError) as cm:
  254. yield self.auth.get_user_by_req(request, allow_guest=True)
  255. self.assertEqual(401, cm.exception.code)
  256. self.assertEqual("Guest access token used for regular user", cm.exception.msg)
  257. self.store.get_user_by_id.assert_called_with(USER_ID)
  258. @defer.inlineCallbacks
  259. def test_blocking_mau(self):
  260. self.hs.config.limit_usage_by_mau = False
  261. self.hs.config.max_mau_value = 50
  262. lots_of_users = 100
  263. small_number_of_users = 1
  264. # Ensure no error thrown
  265. yield self.auth.check_auth_blocking()
  266. self.hs.config.limit_usage_by_mau = True
  267. self.store.get_monthly_active_count = Mock(
  268. return_value=defer.succeed(lots_of_users)
  269. )
  270. with self.assertRaises(ResourceLimitError) as e:
  271. yield self.auth.check_auth_blocking()
  272. self.assertEquals(e.exception.admin_contact, self.hs.config.admin_contact)
  273. self.assertEquals(e.exception.errcode, Codes.RESOURCE_LIMIT_EXCEEDED)
  274. self.assertEquals(e.exception.code, 403)
  275. # Ensure does not throw an error
  276. self.store.get_monthly_active_count = Mock(
  277. return_value=defer.succeed(small_number_of_users)
  278. )
  279. yield self.auth.check_auth_blocking()
  280. @defer.inlineCallbacks
  281. def test_reserved_threepid(self):
  282. self.hs.config.limit_usage_by_mau = True
  283. self.hs.config.max_mau_value = 1
  284. self.store.get_monthly_active_count = lambda: defer.succeed(2)
  285. threepid = {"medium": "email", "address": "reserved@server.com"}
  286. unknown_threepid = {"medium": "email", "address": "unreserved@server.com"}
  287. self.hs.config.mau_limits_reserved_threepids = [threepid]
  288. yield self.store.register_user(user_id="user1", password_hash=None)
  289. with self.assertRaises(ResourceLimitError):
  290. yield self.auth.check_auth_blocking()
  291. with self.assertRaises(ResourceLimitError):
  292. yield self.auth.check_auth_blocking(threepid=unknown_threepid)
  293. yield self.auth.check_auth_blocking(threepid=threepid)
  294. @defer.inlineCallbacks
  295. def test_hs_disabled(self):
  296. self.hs.config.hs_disabled = True
  297. self.hs.config.hs_disabled_message = "Reason for being disabled"
  298. with self.assertRaises(ResourceLimitError) as e:
  299. yield self.auth.check_auth_blocking()
  300. self.assertEquals(e.exception.admin_contact, self.hs.config.admin_contact)
  301. self.assertEquals(e.exception.errcode, Codes.RESOURCE_LIMIT_EXCEEDED)
  302. self.assertEquals(e.exception.code, 403)
  303. @defer.inlineCallbacks
  304. def test_hs_disabled_no_server_notices_user(self):
  305. """Check that 'hs_disabled_message' works correctly when there is no
  306. server_notices user.
  307. """
  308. # this should be the default, but we had a bug where the test was doing the wrong
  309. # thing, so let's make it explicit
  310. self.hs.config.server_notices_mxid = None
  311. self.hs.config.hs_disabled = True
  312. self.hs.config.hs_disabled_message = "Reason for being disabled"
  313. with self.assertRaises(ResourceLimitError) as e:
  314. yield self.auth.check_auth_blocking()
  315. self.assertEquals(e.exception.admin_contact, self.hs.config.admin_contact)
  316. self.assertEquals(e.exception.errcode, Codes.RESOURCE_LIMIT_EXCEEDED)
  317. self.assertEquals(e.exception.code, 403)
  318. @defer.inlineCallbacks
  319. def test_server_notices_mxid_special_cased(self):
  320. self.hs.config.hs_disabled = True
  321. user = "@user:server"
  322. self.hs.config.server_notices_mxid = user
  323. self.hs.config.hs_disabled_message = "Reason for being disabled"
  324. yield self.auth.check_auth_blocking(user)