test_auth.py 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  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
  19. import synapse.api.errors
  20. from synapse.api.errors import ResourceLimitError
  21. from synapse.handlers.auth import AuthHandler
  22. from tests import unittest
  23. from tests.utils import setup_test_homeserver
  24. class AuthHandlers(object):
  25. def __init__(self, hs):
  26. self.auth_handler = AuthHandler(hs)
  27. class AuthTestCase(unittest.TestCase):
  28. @defer.inlineCallbacks
  29. def setUp(self):
  30. self.hs = yield setup_test_homeserver(self.addCleanup, handlers=None)
  31. self.hs.handlers = AuthHandlers(self.hs)
  32. self.auth_handler = self.hs.handlers.auth_handler
  33. self.macaroon_generator = self.hs.get_macaroon_generator()
  34. # MAU tests
  35. # AuthBlocking reads from the hs' config on initialization. We need to
  36. # modify its config instead of the hs'
  37. self.auth_blocking = self.hs.get_auth()._auth_blocking
  38. self.auth_blocking._max_mau_value = 50
  39. self.small_number_of_users = 1
  40. self.large_number_of_users = 100
  41. def test_token_is_a_macaroon(self):
  42. token = self.macaroon_generator.generate_access_token("some_user")
  43. # Check that we can parse the thing with pymacaroons
  44. macaroon = pymacaroons.Macaroon.deserialize(token)
  45. # The most basic of sanity checks
  46. if "some_user" not in macaroon.inspect():
  47. self.fail("some_user was not in %s" % macaroon.inspect())
  48. def test_macaroon_caveats(self):
  49. self.hs.clock.now = 5000
  50. token = self.macaroon_generator.generate_access_token("a_user")
  51. macaroon = pymacaroons.Macaroon.deserialize(token)
  52. def verify_gen(caveat):
  53. return caveat == "gen = 1"
  54. def verify_user(caveat):
  55. return caveat == "user_id = a_user"
  56. def verify_type(caveat):
  57. return caveat == "type = access"
  58. def verify_nonce(caveat):
  59. return caveat.startswith("nonce =")
  60. v = pymacaroons.Verifier()
  61. v.satisfy_general(verify_gen)
  62. v.satisfy_general(verify_user)
  63. v.satisfy_general(verify_type)
  64. v.satisfy_general(verify_nonce)
  65. v.verify(macaroon, self.hs.config.macaroon_secret_key)
  66. @defer.inlineCallbacks
  67. def test_short_term_login_token_gives_user_id(self):
  68. self.hs.clock.now = 1000
  69. token = self.macaroon_generator.generate_short_term_login_token("a_user", 5000)
  70. user_id = yield defer.ensureDeferred(
  71. self.auth_handler.validate_short_term_login_token_and_get_user_id(token)
  72. )
  73. self.assertEqual("a_user", user_id)
  74. # when we advance the clock, the token should be rejected
  75. self.hs.clock.now = 6000
  76. with self.assertRaises(synapse.api.errors.AuthError):
  77. yield defer.ensureDeferred(
  78. self.auth_handler.validate_short_term_login_token_and_get_user_id(token)
  79. )
  80. @defer.inlineCallbacks
  81. def test_short_term_login_token_cannot_replace_user_id(self):
  82. token = self.macaroon_generator.generate_short_term_login_token("a_user", 5000)
  83. macaroon = pymacaroons.Macaroon.deserialize(token)
  84. user_id = yield defer.ensureDeferred(
  85. self.auth_handler.validate_short_term_login_token_and_get_user_id(
  86. macaroon.serialize()
  87. )
  88. )
  89. self.assertEqual("a_user", user_id)
  90. # add another "user_id" caveat, which might allow us to override the
  91. # user_id.
  92. macaroon.add_first_party_caveat("user_id = b_user")
  93. with self.assertRaises(synapse.api.errors.AuthError):
  94. yield defer.ensureDeferred(
  95. self.auth_handler.validate_short_term_login_token_and_get_user_id(
  96. macaroon.serialize()
  97. )
  98. )
  99. @defer.inlineCallbacks
  100. def test_mau_limits_disabled(self):
  101. self.auth_blocking._limit_usage_by_mau = False
  102. # Ensure does not throw exception
  103. yield defer.ensureDeferred(
  104. self.auth_handler.get_access_token_for_user_id(
  105. "user_a", device_id=None, valid_until_ms=None
  106. )
  107. )
  108. yield defer.ensureDeferred(
  109. self.auth_handler.validate_short_term_login_token_and_get_user_id(
  110. self._get_macaroon().serialize()
  111. )
  112. )
  113. @defer.inlineCallbacks
  114. def test_mau_limits_exceeded_large(self):
  115. self.auth_blocking._limit_usage_by_mau = True
  116. self.hs.get_datastore().get_monthly_active_count = Mock(
  117. return_value=defer.succeed(self.large_number_of_users)
  118. )
  119. with self.assertRaises(ResourceLimitError):
  120. yield defer.ensureDeferred(
  121. self.auth_handler.get_access_token_for_user_id(
  122. "user_a", device_id=None, valid_until_ms=None
  123. )
  124. )
  125. self.hs.get_datastore().get_monthly_active_count = Mock(
  126. return_value=defer.succeed(self.large_number_of_users)
  127. )
  128. with self.assertRaises(ResourceLimitError):
  129. yield defer.ensureDeferred(
  130. self.auth_handler.validate_short_term_login_token_and_get_user_id(
  131. self._get_macaroon().serialize()
  132. )
  133. )
  134. @defer.inlineCallbacks
  135. def test_mau_limits_parity(self):
  136. self.auth_blocking._limit_usage_by_mau = True
  137. # If not in monthly active cohort
  138. self.hs.get_datastore().get_monthly_active_count = Mock(
  139. return_value=defer.succeed(self.auth_blocking._max_mau_value)
  140. )
  141. with self.assertRaises(ResourceLimitError):
  142. yield defer.ensureDeferred(
  143. self.auth_handler.get_access_token_for_user_id(
  144. "user_a", device_id=None, valid_until_ms=None
  145. )
  146. )
  147. self.hs.get_datastore().get_monthly_active_count = Mock(
  148. return_value=defer.succeed(self.auth_blocking._max_mau_value)
  149. )
  150. with self.assertRaises(ResourceLimitError):
  151. yield defer.ensureDeferred(
  152. self.auth_handler.validate_short_term_login_token_and_get_user_id(
  153. self._get_macaroon().serialize()
  154. )
  155. )
  156. # If in monthly active cohort
  157. self.hs.get_datastore().user_last_seen_monthly_active = Mock(
  158. return_value=defer.succeed(self.hs.get_clock().time_msec())
  159. )
  160. self.hs.get_datastore().get_monthly_active_count = Mock(
  161. return_value=defer.succeed(self.auth_blocking._max_mau_value)
  162. )
  163. yield defer.ensureDeferred(
  164. self.auth_handler.get_access_token_for_user_id(
  165. "user_a", device_id=None, valid_until_ms=None
  166. )
  167. )
  168. self.hs.get_datastore().user_last_seen_monthly_active = Mock(
  169. return_value=defer.succeed(self.hs.get_clock().time_msec())
  170. )
  171. self.hs.get_datastore().get_monthly_active_count = Mock(
  172. return_value=defer.succeed(self.auth_blocking._max_mau_value)
  173. )
  174. yield defer.ensureDeferred(
  175. self.auth_handler.validate_short_term_login_token_and_get_user_id(
  176. self._get_macaroon().serialize()
  177. )
  178. )
  179. @defer.inlineCallbacks
  180. def test_mau_limits_not_exceeded(self):
  181. self.auth_blocking._limit_usage_by_mau = True
  182. self.hs.get_datastore().get_monthly_active_count = Mock(
  183. return_value=defer.succeed(self.small_number_of_users)
  184. )
  185. # Ensure does not raise exception
  186. yield defer.ensureDeferred(
  187. self.auth_handler.get_access_token_for_user_id(
  188. "user_a", device_id=None, valid_until_ms=None
  189. )
  190. )
  191. self.hs.get_datastore().get_monthly_active_count = Mock(
  192. return_value=defer.succeed(self.small_number_of_users)
  193. )
  194. yield defer.ensureDeferred(
  195. self.auth_handler.validate_short_term_login_token_and_get_user_id(
  196. self._get_macaroon().serialize()
  197. )
  198. )
  199. def _get_macaroon(self):
  200. token = self.macaroon_generator.generate_short_term_login_token("user_a", 5000)
  201. return pymacaroons.Macaroon.deserialize(token)