You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

243 lines
8.1 KiB

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