test_auth.py 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. # Copyright 2015, 2016 OpenMarket Ltd
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. from unittest.mock import Mock
  15. import pymacaroons
  16. from synapse.api.errors import AuthError, ResourceLimitError
  17. from tests import unittest
  18. from tests.test_utils import make_awaitable
  19. class AuthTestCase(unittest.HomeserverTestCase):
  20. def prepare(self, reactor, clock, hs):
  21. self.auth_handler = hs.get_auth_handler()
  22. self.macaroon_generator = hs.get_macaroon_generator()
  23. # MAU tests
  24. # AuthBlocking reads from the hs' config on initialization. We need to
  25. # modify its config instead of the hs'
  26. self.auth_blocking = hs.get_auth()._auth_blocking
  27. self.auth_blocking._max_mau_value = 50
  28. self.small_number_of_users = 1
  29. self.large_number_of_users = 100
  30. def test_token_is_a_macaroon(self):
  31. token = self.macaroon_generator.generate_access_token("some_user")
  32. # Check that we can parse the thing with pymacaroons
  33. macaroon = pymacaroons.Macaroon.deserialize(token)
  34. # The most basic of sanity checks
  35. if "some_user" not in macaroon.inspect():
  36. self.fail("some_user was not in %s" % macaroon.inspect())
  37. def test_macaroon_caveats(self):
  38. token = self.macaroon_generator.generate_access_token("a_user")
  39. macaroon = pymacaroons.Macaroon.deserialize(token)
  40. def verify_gen(caveat):
  41. return caveat == "gen = 1"
  42. def verify_user(caveat):
  43. return caveat == "user_id = a_user"
  44. def verify_type(caveat):
  45. return caveat == "type = access"
  46. def verify_nonce(caveat):
  47. return caveat.startswith("nonce =")
  48. v = pymacaroons.Verifier()
  49. v.satisfy_general(verify_gen)
  50. v.satisfy_general(verify_user)
  51. v.satisfy_general(verify_type)
  52. v.satisfy_general(verify_nonce)
  53. v.verify(macaroon, self.hs.config.macaroon_secret_key)
  54. def test_short_term_login_token_gives_user_id(self):
  55. token = self.macaroon_generator.generate_short_term_login_token(
  56. "a_user", "", 5000
  57. )
  58. res = self.get_success(self.auth_handler.validate_short_term_login_token(token))
  59. self.assertEqual("a_user", res.user_id)
  60. self.assertEqual("", res.auth_provider_id)
  61. # when we advance the clock, the token should be rejected
  62. self.reactor.advance(6)
  63. self.get_failure(
  64. self.auth_handler.validate_short_term_login_token(token),
  65. AuthError,
  66. )
  67. def test_short_term_login_token_gives_auth_provider(self):
  68. token = self.macaroon_generator.generate_short_term_login_token(
  69. "a_user", auth_provider_id="my_idp"
  70. )
  71. res = self.get_success(self.auth_handler.validate_short_term_login_token(token))
  72. self.assertEqual("a_user", res.user_id)
  73. self.assertEqual("my_idp", res.auth_provider_id)
  74. def test_short_term_login_token_cannot_replace_user_id(self):
  75. token = self.macaroon_generator.generate_short_term_login_token(
  76. "a_user", "", 5000
  77. )
  78. macaroon = pymacaroons.Macaroon.deserialize(token)
  79. res = self.get_success(
  80. self.auth_handler.validate_short_term_login_token(macaroon.serialize())
  81. )
  82. self.assertEqual("a_user", res.user_id)
  83. # add another "user_id" caveat, which might allow us to override the
  84. # user_id.
  85. macaroon.add_first_party_caveat("user_id = b_user")
  86. self.get_failure(
  87. self.auth_handler.validate_short_term_login_token(macaroon.serialize()),
  88. AuthError,
  89. )
  90. def test_mau_limits_disabled(self):
  91. self.auth_blocking._limit_usage_by_mau = False
  92. # Ensure does not throw exception
  93. self.get_success(
  94. self.auth_handler.get_access_token_for_user_id(
  95. "user_a", device_id=None, valid_until_ms=None
  96. )
  97. )
  98. self.get_success(
  99. self.auth_handler.validate_short_term_login_token(
  100. self._get_macaroon().serialize()
  101. )
  102. )
  103. def test_mau_limits_exceeded_large(self):
  104. self.auth_blocking._limit_usage_by_mau = True
  105. self.hs.get_datastore().get_monthly_active_count = Mock(
  106. return_value=make_awaitable(self.large_number_of_users)
  107. )
  108. self.get_failure(
  109. self.auth_handler.get_access_token_for_user_id(
  110. "user_a", device_id=None, valid_until_ms=None
  111. ),
  112. ResourceLimitError,
  113. )
  114. self.hs.get_datastore().get_monthly_active_count = Mock(
  115. return_value=make_awaitable(self.large_number_of_users)
  116. )
  117. self.get_failure(
  118. self.auth_handler.validate_short_term_login_token(
  119. self._get_macaroon().serialize()
  120. ),
  121. ResourceLimitError,
  122. )
  123. def test_mau_limits_parity(self):
  124. # Ensure we're not at the unix epoch.
  125. self.reactor.advance(1)
  126. self.auth_blocking._limit_usage_by_mau = True
  127. # Set the server to be at the edge of too many users.
  128. self.hs.get_datastore().get_monthly_active_count = Mock(
  129. return_value=make_awaitable(self.auth_blocking._max_mau_value)
  130. )
  131. # If not in monthly active cohort
  132. self.get_failure(
  133. self.auth_handler.get_access_token_for_user_id(
  134. "user_a", device_id=None, valid_until_ms=None
  135. ),
  136. ResourceLimitError,
  137. )
  138. self.get_failure(
  139. self.auth_handler.validate_short_term_login_token(
  140. self._get_macaroon().serialize()
  141. ),
  142. ResourceLimitError,
  143. )
  144. # If in monthly active cohort
  145. self.hs.get_datastore().user_last_seen_monthly_active = Mock(
  146. return_value=make_awaitable(self.clock.time_msec())
  147. )
  148. self.get_success(
  149. self.auth_handler.get_access_token_for_user_id(
  150. "user_a", device_id=None, valid_until_ms=None
  151. )
  152. )
  153. self.get_success(
  154. self.auth_handler.validate_short_term_login_token(
  155. self._get_macaroon().serialize()
  156. )
  157. )
  158. def test_mau_limits_not_exceeded(self):
  159. self.auth_blocking._limit_usage_by_mau = True
  160. self.hs.get_datastore().get_monthly_active_count = Mock(
  161. return_value=make_awaitable(self.small_number_of_users)
  162. )
  163. # Ensure does not raise exception
  164. self.get_success(
  165. self.auth_handler.get_access_token_for_user_id(
  166. "user_a", device_id=None, valid_until_ms=None
  167. )
  168. )
  169. self.hs.get_datastore().get_monthly_active_count = Mock(
  170. return_value=make_awaitable(self.small_number_of_users)
  171. )
  172. self.get_success(
  173. self.auth_handler.validate_short_term_login_token(
  174. self._get_macaroon().serialize()
  175. )
  176. )
  177. def _get_macaroon(self):
  178. token = self.macaroon_generator.generate_short_term_login_token(
  179. "user_a", "", 5000
  180. )
  181. return pymacaroons.Macaroon.deserialize(token)