test_auth.py 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  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 twisted.test.proto_helpers import MemoryReactor
  17. from synapse.api.errors import AuthError, ResourceLimitError
  18. from synapse.rest import admin
  19. from synapse.server import HomeServer
  20. from synapse.util import Clock
  21. from tests import unittest
  22. from tests.test_utils import make_awaitable
  23. class AuthTestCase(unittest.HomeserverTestCase):
  24. servlets = [
  25. admin.register_servlets,
  26. ]
  27. def prepare(self, reactor: MemoryReactor, clock: Clock, hs: HomeServer) -> None:
  28. self.auth_handler = hs.get_auth_handler()
  29. self.macaroon_generator = hs.get_macaroon_generator()
  30. # MAU tests
  31. # AuthBlocking reads from the hs' config on initialization. We need to
  32. # modify its config instead of the hs'
  33. self.auth_blocking = hs.get_auth_blocking()
  34. self.auth_blocking._max_mau_value = 50
  35. self.small_number_of_users = 1
  36. self.large_number_of_users = 100
  37. self.user1 = self.register_user("a_user", "pass")
  38. def test_macaroon_caveats(self) -> None:
  39. token = self.macaroon_generator.generate_guest_access_token("a_user")
  40. macaroon = pymacaroons.Macaroon.deserialize(token)
  41. def verify_gen(caveat: str) -> bool:
  42. return caveat == "gen = 1"
  43. def verify_user(caveat: str) -> bool:
  44. return caveat == "user_id = a_user"
  45. def verify_type(caveat: str) -> bool:
  46. return caveat == "type = access"
  47. def verify_nonce(caveat: str) -> bool:
  48. return caveat.startswith("nonce =")
  49. def verify_guest(caveat: str) -> bool:
  50. return caveat == "guest = true"
  51. v = pymacaroons.Verifier()
  52. v.satisfy_general(verify_gen)
  53. v.satisfy_general(verify_user)
  54. v.satisfy_general(verify_type)
  55. v.satisfy_general(verify_nonce)
  56. v.satisfy_general(verify_guest)
  57. v.verify(macaroon, self.hs.config.key.macaroon_secret_key)
  58. def test_short_term_login_token_gives_user_id(self) -> None:
  59. token = self.macaroon_generator.generate_short_term_login_token(
  60. self.user1, "", duration_in_ms=5000
  61. )
  62. res = self.get_success(self.auth_handler.validate_short_term_login_token(token))
  63. self.assertEqual(self.user1, res.user_id)
  64. self.assertEqual("", res.auth_provider_id)
  65. # when we advance the clock, the token should be rejected
  66. self.reactor.advance(6)
  67. self.get_failure(
  68. self.auth_handler.validate_short_term_login_token(token),
  69. AuthError,
  70. )
  71. def test_short_term_login_token_gives_auth_provider(self) -> None:
  72. token = self.macaroon_generator.generate_short_term_login_token(
  73. self.user1, auth_provider_id="my_idp"
  74. )
  75. res = self.get_success(self.auth_handler.validate_short_term_login_token(token))
  76. self.assertEqual(self.user1, res.user_id)
  77. self.assertEqual("my_idp", res.auth_provider_id)
  78. def test_short_term_login_token_cannot_replace_user_id(self) -> None:
  79. token = self.macaroon_generator.generate_short_term_login_token(
  80. self.user1, "", duration_in_ms=5000
  81. )
  82. macaroon = pymacaroons.Macaroon.deserialize(token)
  83. res = self.get_success(
  84. self.auth_handler.validate_short_term_login_token(macaroon.serialize())
  85. )
  86. self.assertEqual(self.user1, res.user_id)
  87. # add another "user_id" caveat, which might allow us to override the
  88. # user_id.
  89. macaroon.add_first_party_caveat("user_id = b_user")
  90. self.get_failure(
  91. self.auth_handler.validate_short_term_login_token(macaroon.serialize()),
  92. AuthError,
  93. )
  94. def test_mau_limits_disabled(self) -> None:
  95. self.auth_blocking._limit_usage_by_mau = False
  96. # Ensure does not throw exception
  97. self.get_success(
  98. self.auth_handler.create_access_token_for_user_id(
  99. self.user1, device_id=None, valid_until_ms=None
  100. )
  101. )
  102. self.get_success(
  103. self.auth_handler.validate_short_term_login_token(
  104. self._get_macaroon().serialize()
  105. )
  106. )
  107. def test_mau_limits_exceeded_large(self) -> None:
  108. self.auth_blocking._limit_usage_by_mau = True
  109. self.hs.get_datastores().main.get_monthly_active_count = Mock(
  110. return_value=make_awaitable(self.large_number_of_users)
  111. )
  112. self.get_failure(
  113. self.auth_handler.create_access_token_for_user_id(
  114. self.user1, device_id=None, valid_until_ms=None
  115. ),
  116. ResourceLimitError,
  117. )
  118. self.hs.get_datastores().main.get_monthly_active_count = Mock(
  119. return_value=make_awaitable(self.large_number_of_users)
  120. )
  121. self.get_failure(
  122. self.auth_handler.validate_short_term_login_token(
  123. self._get_macaroon().serialize()
  124. ),
  125. ResourceLimitError,
  126. )
  127. def test_mau_limits_parity(self) -> None:
  128. # Ensure we're not at the unix epoch.
  129. self.reactor.advance(1)
  130. self.auth_blocking._limit_usage_by_mau = True
  131. # Set the server to be at the edge of too many users.
  132. self.hs.get_datastores().main.get_monthly_active_count = Mock(
  133. return_value=make_awaitable(self.auth_blocking._max_mau_value)
  134. )
  135. # If not in monthly active cohort
  136. self.get_failure(
  137. self.auth_handler.create_access_token_for_user_id(
  138. self.user1, device_id=None, valid_until_ms=None
  139. ),
  140. ResourceLimitError,
  141. )
  142. self.get_failure(
  143. self.auth_handler.validate_short_term_login_token(
  144. self._get_macaroon().serialize()
  145. ),
  146. ResourceLimitError,
  147. )
  148. # If in monthly active cohort
  149. self.hs.get_datastores().main.user_last_seen_monthly_active = Mock(
  150. return_value=make_awaitable(self.clock.time_msec())
  151. )
  152. self.get_success(
  153. self.auth_handler.create_access_token_for_user_id(
  154. self.user1, device_id=None, valid_until_ms=None
  155. )
  156. )
  157. self.get_success(
  158. self.auth_handler.validate_short_term_login_token(
  159. self._get_macaroon().serialize()
  160. )
  161. )
  162. def test_mau_limits_not_exceeded(self) -> None:
  163. self.auth_blocking._limit_usage_by_mau = True
  164. self.hs.get_datastores().main.get_monthly_active_count = Mock(
  165. return_value=make_awaitable(self.small_number_of_users)
  166. )
  167. # Ensure does not raise exception
  168. self.get_success(
  169. self.auth_handler.create_access_token_for_user_id(
  170. self.user1, device_id=None, valid_until_ms=None
  171. )
  172. )
  173. self.hs.get_datastores().main.get_monthly_active_count = Mock(
  174. return_value=make_awaitable(self.small_number_of_users)
  175. )
  176. self.get_success(
  177. self.auth_handler.validate_short_term_login_token(
  178. self._get_macaroon().serialize()
  179. )
  180. )
  181. def _get_macaroon(self) -> pymacaroons.Macaroon:
  182. token = self.macaroon_generator.generate_short_term_login_token(
  183. self.user1, "", duration_in_ms=5000
  184. )
  185. return pymacaroons.Macaroon.deserialize(token)