synapse/tests/test_mau.py

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

350 lines
12 KiB
Python
Raw Normal View History

2018-08-23 17:33:04 +00:00
#
2023-11-21 20:29:58 +00:00
# This file is licensed under the Affero General Public License (AGPL) version 3.
#
# Copyright (C) 2023 New Vector, Ltd
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# See the GNU Affero General Public License for more details:
# <https://www.gnu.org/licenses/agpl-3.0.html>.
#
# Originally licensed under the Apache License, Version 2.0:
# <http://www.apache.org/licenses/LICENSE-2.0>.
#
# [This file includes modifications made by New Vector Limited]
2018-08-23 17:33:04 +00:00
#
#
"""Tests REST events for /rooms paths."""
from typing import List, Optional
from twisted.test.proto_helpers import MemoryReactor
from synapse.api.constants import APP_SERVICE_REGISTRATION_TYPE, LoginType
2018-08-23 17:33:04 +00:00
from synapse.api.errors import Codes, HttpResponseException, SynapseError
from synapse.appservice import ApplicationService
from synapse.rest.client import register, sync
from synapse.server import HomeServer
from synapse.types import JsonDict
from synapse.util import Clock
2018-08-23 17:33:04 +00:00
from tests import unittest
from tests.unittest import override_config
from tests.utils import default_config
2018-08-23 17:33:04 +00:00
class TestMauLimit(unittest.HomeserverTestCase):
servlets = [register.register_servlets, sync.register_servlets]
def default_config(self) -> JsonDict:
config = default_config("test")
config.update(
{
"registrations_require_3pid": [],
"limit_usage_by_mau": True,
"max_mau_value": 2,
"mau_trial_days": 0,
"server_notices": {
"system_mxid_localpart": "server",
"room_name": "Test Server Notice Room",
},
}
2018-08-23 17:33:04 +00:00
)
# apply any additional config which was specified via the override_config
# decorator.
if self._extra_config is not None:
config.update(self._extra_config)
return config
def prepare(
self, reactor: MemoryReactor, clock: Clock, homeserver: HomeServer
) -> None:
self.store = homeserver.get_datastores().main
2018-08-23 17:33:04 +00:00
def test_simple_deny_mau(self) -> None:
2018-08-23 17:33:04 +00:00
# Create and sync so that the MAU counts get updated
token1 = self.create_user("kermit1")
self.do_sync_for_user(token1)
token2 = self.create_user("kermit2")
self.do_sync_for_user(token2)
# check we're testing what we think we are: there should be two active users
self.assertEqual(self.get_success(self.store.get_monthly_active_count()), 2)
2018-08-23 17:33:04 +00:00
# We've created and activated two users, we shouldn't be able to
# register new users
with self.assertRaises(SynapseError) as cm:
self.create_user("kermit3")
e = cm.exception
self.assertEqual(e.code, 403)
self.assertEqual(e.errcode, Codes.RESOURCE_LIMIT_EXCEEDED)
def test_as_ignores_mau(self) -> None:
"""Test that application services can still create users when the MAU
limit has been reached. This only works when application service
user ip tracking is disabled.
"""
# Create and sync so that the MAU counts get updated
token1 = self.create_user("kermit1")
self.do_sync_for_user(token1)
token2 = self.create_user("kermit2")
self.do_sync_for_user(token2)
# check we're testing what we think we are: there should be two active users
self.assertEqual(self.get_success(self.store.get_monthly_active_count()), 2)
# We've created and activated two users, we shouldn't be able to
# register new users
with self.assertRaises(SynapseError) as cm:
self.create_user("kermit3")
e = cm.exception
self.assertEqual(e.code, 403)
self.assertEqual(e.errcode, Codes.RESOURCE_LIMIT_EXCEEDED)
# Cheekily add an application service that we use to register a new user
# with.
as_token = "foobartoken"
self.store.services_cache.append(
ApplicationService(
token=as_token,
id="SomeASID",
sender="@as_sender:test",
namespaces={"users": [{"regex": "@as_*", "exclusive": True}]},
)
)
self.create_user("as_kermit4", token=as_token, appservice=True)
def test_allowed_after_a_month_mau(self) -> None:
2018-08-23 17:33:04 +00:00
# Create and sync so that the MAU counts get updated
token1 = self.create_user("kermit1")
self.do_sync_for_user(token1)
token2 = self.create_user("kermit2")
self.do_sync_for_user(token2)
# Advance time by 31 days
self.reactor.advance(31 * 24 * 60 * 60)
self.get_success(self.store.reap_monthly_active_users())
2018-08-23 17:33:04 +00:00
self.reactor.advance(0)
# We should be able to register more users
token3 = self.create_user("kermit3")
self.do_sync_for_user(token3)
@override_config({"mau_trial_days": 1})
def test_trial_delay(self) -> None:
2018-08-23 17:33:04 +00:00
# We should be able to register more than the limit initially
token1 = self.create_user("kermit1")
self.do_sync_for_user(token1)
token2 = self.create_user("kermit2")
self.do_sync_for_user(token2)
token3 = self.create_user("kermit3")
self.do_sync_for_user(token3)
# Advance time by 2 days
self.reactor.advance(2 * 24 * 60 * 60)
# Two users should be able to sync
self.do_sync_for_user(token1)
self.do_sync_for_user(token2)
# But the third should fail
with self.assertRaises(SynapseError) as cm:
self.do_sync_for_user(token3)
e = cm.exception
self.assertEqual(e.code, 403)
self.assertEqual(e.errcode, Codes.RESOURCE_LIMIT_EXCEEDED)
# And new registrations are now denied too
with self.assertRaises(SynapseError) as cm:
self.create_user("kermit4")
e = cm.exception
self.assertEqual(e.code, 403)
self.assertEqual(e.errcode, Codes.RESOURCE_LIMIT_EXCEEDED)
@override_config({"mau_trial_days": 1})
def test_trial_users_cant_come_back(self) -> None:
self.hs.config.server.mau_trial_days = 1
2018-08-23 17:33:04 +00:00
# We should be able to register more than the limit initially
token1 = self.create_user("kermit1")
self.do_sync_for_user(token1)
token2 = self.create_user("kermit2")
self.do_sync_for_user(token2)
token3 = self.create_user("kermit3")
self.do_sync_for_user(token3)
# Advance time by 2 days
self.reactor.advance(2 * 24 * 60 * 60)
# Two users should be able to sync
self.do_sync_for_user(token1)
self.do_sync_for_user(token2)
# Advance by 2 months so everyone falls out of MAU
self.reactor.advance(60 * 24 * 60 * 60)
self.get_success(self.store.reap_monthly_active_users())
2018-08-23 17:33:04 +00:00
# We can create as many new users as we want
token4 = self.create_user("kermit4")
self.do_sync_for_user(token4)
token5 = self.create_user("kermit5")
self.do_sync_for_user(token5)
token6 = self.create_user("kermit6")
self.do_sync_for_user(token6)
# users 2 and 3 can come back to bring us back up to MAU limit
self.do_sync_for_user(token2)
self.do_sync_for_user(token3)
# New trial users can still sync
self.do_sync_for_user(token4)
self.do_sync_for_user(token5)
self.do_sync_for_user(token6)
# But old user can't
2018-08-23 17:33:04 +00:00
with self.assertRaises(SynapseError) as cm:
self.do_sync_for_user(token1)
e = cm.exception
self.assertEqual(e.code, 403)
self.assertEqual(e.errcode, Codes.RESOURCE_LIMIT_EXCEEDED)
@override_config(
# max_mau_value should not matter
{"max_mau_value": 1, "limit_usage_by_mau": False, "mau_stats_only": True}
)
def test_tracked_but_not_limited(self) -> None:
# Simply being able to create 2 users indicates that the
# limit was not reached.
token1 = self.create_user("kermit1")
self.do_sync_for_user(token1)
token2 = self.create_user("kermit2")
self.do_sync_for_user(token2)
# We do want to verify that the number of tracked users
# matches what we want though
count = self.store.get_monthly_active_count()
self.reactor.advance(100)
self.assertEqual(2, self.successResultOf(count))
@override_config(
{
"mau_trial_days": 3,
"mau_appservice_trial_days": {"SomeASID": 1, "AnotherASID": 2},
}
)
def test_as_trial_days(self) -> None:
user_tokens: List[str] = []
def advance_time_and_sync() -> None:
self.reactor.advance(24 * 60 * 61)
for token in user_tokens:
self.do_sync_for_user(token)
# Cheekily add an application service that we use to register a new user
# with.
as_token_1 = "foobartoken1"
self.store.services_cache.append(
ApplicationService(
token=as_token_1,
id="SomeASID",
sender="@as_sender_1:test",
namespaces={"users": [{"regex": "@as_1.*", "exclusive": True}]},
)
)
as_token_2 = "foobartoken2"
self.store.services_cache.append(
ApplicationService(
token=as_token_2,
id="AnotherASID",
sender="@as_sender_2:test",
namespaces={"users": [{"regex": "@as_2.*", "exclusive": True}]},
)
)
user_tokens.append(self.create_user("kermit1"))
user_tokens.append(self.create_user("kermit2"))
user_tokens.append(
self.create_user("as_1kermit3", token=as_token_1, appservice=True)
)
user_tokens.append(
self.create_user("as_2kermit4", token=as_token_2, appservice=True)
)
# Advance time by 1 day to include the first appservice
advance_time_and_sync()
self.assertEqual(
self.get_success(self.store.get_monthly_active_count_by_service()),
{"SomeASID": 1},
)
# Advance time by 1 day to include the next appservice
advance_time_and_sync()
self.assertEqual(
self.get_success(self.store.get_monthly_active_count_by_service()),
{"SomeASID": 1, "AnotherASID": 1},
)
# Advance time by 1 day to include the native users
advance_time_and_sync()
self.assertEqual(
self.get_success(self.store.get_monthly_active_count_by_service()),
{
"SomeASID": 1,
"AnotherASID": 1,
"native": 2,
},
)
def create_user(
self, localpart: str, token: Optional[str] = None, appservice: bool = False
) -> str:
request_data = {
"username": localpart,
"password": "monkey",
"auth": {"type": LoginType.DUMMY},
}
if appservice:
request_data["type"] = APP_SERVICE_REGISTRATION_TYPE
2018-08-23 17:33:04 +00:00
channel = self.make_request(
"POST",
"/register",
request_data,
access_token=token,
)
2018-08-23 17:33:04 +00:00
2018-09-06 16:58:18 +00:00
if channel.code != 200:
2018-08-23 17:33:04 +00:00
raise HttpResponseException(
2018-09-06 16:58:18 +00:00
channel.code, channel.result["reason"], channel.result["body"]
2018-08-23 17:33:04 +00:00
).to_synapse_error()
access_token = channel.json_body["access_token"]
return access_token
def do_sync_for_user(self, token: str) -> None:
channel = self.make_request("GET", "/sync", access_token=token)
2018-08-23 17:33:04 +00:00
2018-09-06 16:58:18 +00:00
if channel.code != 200:
2018-08-23 17:33:04 +00:00
raise HttpResponseException(
2018-09-06 16:58:18 +00:00
channel.code, channel.result["reason"], channel.result["body"]
2018-08-23 17:33:04 +00:00
).to_synapse_error()