mirror of
https://git.anonymousland.org/anonymousland/synapse.git
synced 2025-11-11 15:45:13 -05:00
Merge pull request #3630 from matrix-org/neilj/mau_sign_in_log_in_limits
Initial impl of capping MAU
This commit is contained in:
commit
085435e13a
11 changed files with 275 additions and 16 deletions
|
|
@ -12,6 +12,7 @@
|
|||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
from mock import Mock
|
||||
|
||||
import pymacaroons
|
||||
|
||||
|
|
@ -19,6 +20,7 @@ from twisted.internet import defer
|
|||
|
||||
import synapse
|
||||
import synapse.api.errors
|
||||
from synapse.api.errors import AuthError
|
||||
from synapse.handlers.auth import AuthHandler
|
||||
|
||||
from tests import unittest
|
||||
|
|
@ -37,6 +39,10 @@ class AuthTestCase(unittest.TestCase):
|
|||
self.hs.handlers = AuthHandlers(self.hs)
|
||||
self.auth_handler = self.hs.handlers.auth_handler
|
||||
self.macaroon_generator = self.hs.get_macaroon_generator()
|
||||
# MAU tests
|
||||
self.hs.config.max_mau_value = 50
|
||||
self.small_number_of_users = 1
|
||||
self.large_number_of_users = 100
|
||||
|
||||
def test_token_is_a_macaroon(self):
|
||||
token = self.macaroon_generator.generate_access_token("some_user")
|
||||
|
|
@ -71,38 +77,37 @@ class AuthTestCase(unittest.TestCase):
|
|||
v.satisfy_general(verify_nonce)
|
||||
v.verify(macaroon, self.hs.config.macaroon_secret_key)
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def test_short_term_login_token_gives_user_id(self):
|
||||
self.hs.clock.now = 1000
|
||||
|
||||
token = self.macaroon_generator.generate_short_term_login_token(
|
||||
"a_user", 5000
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
"a_user",
|
||||
self.auth_handler.validate_short_term_login_token_and_get_user_id(
|
||||
token
|
||||
)
|
||||
user_id = yield self.auth_handler.validate_short_term_login_token_and_get_user_id(
|
||||
token
|
||||
)
|
||||
self.assertEqual("a_user", user_id)
|
||||
|
||||
# when we advance the clock, the token should be rejected
|
||||
self.hs.clock.now = 6000
|
||||
with self.assertRaises(synapse.api.errors.AuthError):
|
||||
self.auth_handler.validate_short_term_login_token_and_get_user_id(
|
||||
yield self.auth_handler.validate_short_term_login_token_and_get_user_id(
|
||||
token
|
||||
)
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def test_short_term_login_token_cannot_replace_user_id(self):
|
||||
token = self.macaroon_generator.generate_short_term_login_token(
|
||||
"a_user", 5000
|
||||
)
|
||||
macaroon = pymacaroons.Macaroon.deserialize(token)
|
||||
|
||||
user_id = yield self.auth_handler.validate_short_term_login_token_and_get_user_id(
|
||||
macaroon.serialize()
|
||||
)
|
||||
self.assertEqual(
|
||||
"a_user",
|
||||
self.auth_handler.validate_short_term_login_token_and_get_user_id(
|
||||
macaroon.serialize()
|
||||
)
|
||||
"a_user", user_id
|
||||
)
|
||||
|
||||
# add another "user_id" caveat, which might allow us to override the
|
||||
|
|
@ -110,6 +115,57 @@ class AuthTestCase(unittest.TestCase):
|
|||
macaroon.add_first_party_caveat("user_id = b_user")
|
||||
|
||||
with self.assertRaises(synapse.api.errors.AuthError):
|
||||
self.auth_handler.validate_short_term_login_token_and_get_user_id(
|
||||
yield self.auth_handler.validate_short_term_login_token_and_get_user_id(
|
||||
macaroon.serialize()
|
||||
)
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def test_mau_limits_disabled(self):
|
||||
self.hs.config.limit_usage_by_mau = False
|
||||
# Ensure does not throw exception
|
||||
yield self.auth_handler.get_access_token_for_user_id('user_a')
|
||||
|
||||
yield self.auth_handler.validate_short_term_login_token_and_get_user_id(
|
||||
self._get_macaroon().serialize()
|
||||
)
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def test_mau_limits_exceeded(self):
|
||||
self.hs.config.limit_usage_by_mau = True
|
||||
self.hs.get_datastore().count_monthly_users = Mock(
|
||||
return_value=defer.succeed(self.large_number_of_users)
|
||||
)
|
||||
|
||||
with self.assertRaises(AuthError):
|
||||
yield self.auth_handler.get_access_token_for_user_id('user_a')
|
||||
|
||||
self.hs.get_datastore().count_monthly_users = Mock(
|
||||
return_value=defer.succeed(self.large_number_of_users)
|
||||
)
|
||||
with self.assertRaises(AuthError):
|
||||
yield self.auth_handler.validate_short_term_login_token_and_get_user_id(
|
||||
self._get_macaroon().serialize()
|
||||
)
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def test_mau_limits_not_exceeded(self):
|
||||
self.hs.config.limit_usage_by_mau = True
|
||||
|
||||
self.hs.get_datastore().count_monthly_users = Mock(
|
||||
return_value=defer.succeed(self.small_number_of_users)
|
||||
)
|
||||
# Ensure does not raise exception
|
||||
yield self.auth_handler.get_access_token_for_user_id('user_a')
|
||||
|
||||
self.hs.get_datastore().count_monthly_users = Mock(
|
||||
return_value=defer.succeed(self.small_number_of_users)
|
||||
)
|
||||
yield self.auth_handler.validate_short_term_login_token_and_get_user_id(
|
||||
self._get_macaroon().serialize()
|
||||
)
|
||||
|
||||
def _get_macaroon(self):
|
||||
token = self.macaroon_generator.generate_short_term_login_token(
|
||||
"user_a", 5000
|
||||
)
|
||||
return pymacaroons.Macaroon.deserialize(token)
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ from mock import Mock
|
|||
|
||||
from twisted.internet import defer
|
||||
|
||||
from synapse.api.errors import RegistrationError
|
||||
from synapse.handlers.register import RegistrationHandler
|
||||
from synapse.types import UserID, create_requester
|
||||
|
||||
|
|
@ -77,3 +78,53 @@ class RegistrationTestCase(unittest.TestCase):
|
|||
requester, local_part, display_name)
|
||||
self.assertEquals(result_user_id, user_id)
|
||||
self.assertEquals(result_token, 'secret')
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def test_cannot_register_when_mau_limits_exceeded(self):
|
||||
local_part = "someone"
|
||||
display_name = "someone"
|
||||
requester = create_requester("@as:test")
|
||||
store = self.hs.get_datastore()
|
||||
self.hs.config.limit_usage_by_mau = False
|
||||
self.hs.config.max_mau_value = 50
|
||||
lots_of_users = 100
|
||||
small_number_users = 1
|
||||
|
||||
store.count_monthly_users = Mock(return_value=defer.succeed(lots_of_users))
|
||||
|
||||
# Ensure does not throw exception
|
||||
yield self.handler.get_or_create_user(requester, 'a', display_name)
|
||||
|
||||
self.hs.config.limit_usage_by_mau = True
|
||||
|
||||
with self.assertRaises(RegistrationError):
|
||||
yield self.handler.get_or_create_user(requester, 'b', display_name)
|
||||
|
||||
store.count_monthly_users = Mock(return_value=defer.succeed(small_number_users))
|
||||
|
||||
self._macaroon_mock_generator("another_secret")
|
||||
|
||||
# Ensure does not throw exception
|
||||
yield self.handler.get_or_create_user("@neil:matrix.org", 'c', "Neil")
|
||||
|
||||
self._macaroon_mock_generator("another another secret")
|
||||
store.count_monthly_users = Mock(return_value=defer.succeed(lots_of_users))
|
||||
|
||||
with self.assertRaises(RegistrationError):
|
||||
yield self.handler.register(localpart=local_part)
|
||||
|
||||
self._macaroon_mock_generator("another another secret")
|
||||
store.count_monthly_users = Mock(return_value=defer.succeed(lots_of_users))
|
||||
|
||||
with self.assertRaises(RegistrationError):
|
||||
yield self.handler.register_saml2(local_part)
|
||||
|
||||
def _macaroon_mock_generator(self, secret):
|
||||
"""
|
||||
Reset macaroon generator in the case where the test creates multiple users
|
||||
"""
|
||||
macaroon_generator = Mock(
|
||||
generate_access_token=Mock(return_value=secret))
|
||||
self.hs.get_macaroon_generator = Mock(return_value=macaroon_generator)
|
||||
self.hs.handlers = RegistrationHandlers(self.hs)
|
||||
self.handler = self.hs.get_handlers().registration_handler
|
||||
|
|
|
|||
65
tests/storage/test__init__.py
Normal file
65
tests/storage/test__init__.py
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2018 New Vector Ltd
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from twisted.internet import defer
|
||||
|
||||
import tests.utils
|
||||
|
||||
|
||||
class InitTestCase(tests.unittest.TestCase):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(InitTestCase, self).__init__(*args, **kwargs)
|
||||
self.store = None # type: synapse.storage.DataStore
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def setUp(self):
|
||||
hs = yield tests.utils.setup_test_homeserver()
|
||||
|
||||
hs.config.max_mau_value = 50
|
||||
hs.config.limit_usage_by_mau = True
|
||||
self.store = hs.get_datastore()
|
||||
self.clock = hs.get_clock()
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def test_count_monthly_users(self):
|
||||
count = yield self.store.count_monthly_users()
|
||||
self.assertEqual(0, count)
|
||||
|
||||
yield self._insert_user_ips("@user:server1")
|
||||
yield self._insert_user_ips("@user:server2")
|
||||
|
||||
count = yield self.store.count_monthly_users()
|
||||
self.assertEqual(2, count)
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def _insert_user_ips(self, user):
|
||||
"""
|
||||
Helper function to populate user_ips without using batch insertion infra
|
||||
args:
|
||||
user (str): specify username i.e. @user:server.com
|
||||
"""
|
||||
yield self.store._simple_upsert(
|
||||
table="user_ips",
|
||||
keyvalues={
|
||||
"user_id": user,
|
||||
"access_token": "access_token",
|
||||
"ip": "ip",
|
||||
"user_agent": "user_agent",
|
||||
"device_id": "device_id",
|
||||
},
|
||||
values={
|
||||
"last_seen": self.clock.time_msec(),
|
||||
}
|
||||
)
|
||||
Loading…
Add table
Add a link
Reference in a new issue