test_auth.py 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  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. self.hs.config.max_mau_value = 50
  36. self.small_number_of_users = 1
  37. self.large_number_of_users = 100
  38. def test_token_is_a_macaroon(self):
  39. token = self.macaroon_generator.generate_access_token("some_user")
  40. # Check that we can parse the thing with pymacaroons
  41. macaroon = pymacaroons.Macaroon.deserialize(token)
  42. # The most basic of sanity checks
  43. if "some_user" not in macaroon.inspect():
  44. self.fail("some_user was not in %s" % macaroon.inspect())
  45. def test_macaroon_caveats(self):
  46. self.hs.clock.now = 5000
  47. token = self.macaroon_generator.generate_access_token("a_user")
  48. macaroon = pymacaroons.Macaroon.deserialize(token)
  49. def verify_gen(caveat):
  50. return caveat == "gen = 1"
  51. def verify_user(caveat):
  52. return caveat == "user_id = a_user"
  53. def verify_type(caveat):
  54. return caveat == "type = access"
  55. def verify_nonce(caveat):
  56. return caveat.startswith("nonce =")
  57. v = pymacaroons.Verifier()
  58. v.satisfy_general(verify_gen)
  59. v.satisfy_general(verify_user)
  60. v.satisfy_general(verify_type)
  61. v.satisfy_general(verify_nonce)
  62. v.verify(macaroon, self.hs.config.macaroon_secret_key)
  63. @defer.inlineCallbacks
  64. def test_short_term_login_token_gives_user_id(self):
  65. self.hs.clock.now = 1000
  66. token = self.macaroon_generator.generate_short_term_login_token("a_user", 5000)
  67. user_id = yield self.auth_handler.validate_short_term_login_token_and_get_user_id(
  68. token
  69. )
  70. self.assertEqual("a_user", user_id)
  71. # when we advance the clock, the token should be rejected
  72. self.hs.clock.now = 6000
  73. with self.assertRaises(synapse.api.errors.AuthError):
  74. yield self.auth_handler.validate_short_term_login_token_and_get_user_id(
  75. token
  76. )
  77. @defer.inlineCallbacks
  78. def test_short_term_login_token_cannot_replace_user_id(self):
  79. token = self.macaroon_generator.generate_short_term_login_token("a_user", 5000)
  80. macaroon = pymacaroons.Macaroon.deserialize(token)
  81. user_id = yield self.auth_handler.validate_short_term_login_token_and_get_user_id(
  82. macaroon.serialize()
  83. )
  84. self.assertEqual("a_user", user_id)
  85. # add another "user_id" caveat, which might allow us to override the
  86. # user_id.
  87. macaroon.add_first_party_caveat("user_id = b_user")
  88. with self.assertRaises(synapse.api.errors.AuthError):
  89. yield self.auth_handler.validate_short_term_login_token_and_get_user_id(
  90. macaroon.serialize()
  91. )
  92. @defer.inlineCallbacks
  93. def test_mau_limits_disabled(self):
  94. self.hs.config.limit_usage_by_mau = False
  95. # Ensure does not throw exception
  96. yield self.auth_handler.get_access_token_for_user_id(
  97. "user_a", device_id=None, valid_until_ms=None
  98. )
  99. yield self.auth_handler.validate_short_term_login_token_and_get_user_id(
  100. self._get_macaroon().serialize()
  101. )
  102. @defer.inlineCallbacks
  103. def test_mau_limits_exceeded_large(self):
  104. self.hs.config.limit_usage_by_mau = True
  105. self.hs.get_datastore().get_monthly_active_count = Mock(
  106. return_value=defer.succeed(self.large_number_of_users)
  107. )
  108. with self.assertRaises(ResourceLimitError):
  109. yield self.auth_handler.get_access_token_for_user_id(
  110. "user_a", device_id=None, valid_until_ms=None
  111. )
  112. self.hs.get_datastore().get_monthly_active_count = Mock(
  113. return_value=defer.succeed(self.large_number_of_users)
  114. )
  115. with self.assertRaises(ResourceLimitError):
  116. yield self.auth_handler.validate_short_term_login_token_and_get_user_id(
  117. self._get_macaroon().serialize()
  118. )
  119. @defer.inlineCallbacks
  120. def test_mau_limits_parity(self):
  121. self.hs.config.limit_usage_by_mau = True
  122. # If not in monthly active cohort
  123. self.hs.get_datastore().get_monthly_active_count = Mock(
  124. return_value=defer.succeed(self.hs.config.max_mau_value)
  125. )
  126. with self.assertRaises(ResourceLimitError):
  127. yield self.auth_handler.get_access_token_for_user_id(
  128. "user_a", device_id=None, valid_until_ms=None
  129. )
  130. self.hs.get_datastore().get_monthly_active_count = Mock(
  131. return_value=defer.succeed(self.hs.config.max_mau_value)
  132. )
  133. with self.assertRaises(ResourceLimitError):
  134. yield self.auth_handler.validate_short_term_login_token_and_get_user_id(
  135. self._get_macaroon().serialize()
  136. )
  137. # If in monthly active cohort
  138. self.hs.get_datastore().user_last_seen_monthly_active = Mock(
  139. return_value=defer.succeed(self.hs.get_clock().time_msec())
  140. )
  141. self.hs.get_datastore().get_monthly_active_count = Mock(
  142. return_value=defer.succeed(self.hs.config.max_mau_value)
  143. )
  144. yield self.auth_handler.get_access_token_for_user_id(
  145. "user_a", device_id=None, valid_until_ms=None
  146. )
  147. self.hs.get_datastore().user_last_seen_monthly_active = Mock(
  148. return_value=defer.succeed(self.hs.get_clock().time_msec())
  149. )
  150. self.hs.get_datastore().get_monthly_active_count = Mock(
  151. return_value=defer.succeed(self.hs.config.max_mau_value)
  152. )
  153. yield self.auth_handler.validate_short_term_login_token_and_get_user_id(
  154. self._get_macaroon().serialize()
  155. )
  156. @defer.inlineCallbacks
  157. def test_mau_limits_not_exceeded(self):
  158. self.hs.config.limit_usage_by_mau = True
  159. self.hs.get_datastore().get_monthly_active_count = Mock(
  160. return_value=defer.succeed(self.small_number_of_users)
  161. )
  162. # Ensure does not raise exception
  163. yield self.auth_handler.get_access_token_for_user_id(
  164. "user_a", device_id=None, valid_until_ms=None
  165. )
  166. self.hs.get_datastore().get_monthly_active_count = Mock(
  167. return_value=defer.succeed(self.small_number_of_users)
  168. )
  169. yield self.auth_handler.validate_short_term_login_token_and_get_user_id(
  170. self._get_macaroon().serialize()
  171. )
  172. def _get_macaroon(self):
  173. token = self.macaroon_generator.generate_short_term_login_token("user_a", 5000)
  174. return pymacaroons.Macaroon.deserialize(token)