From 8f75ac687f77f21cb8a04b656824a63861f8304b Mon Sep 17 00:00:00 2001 From: dklimpel <5740567+dklimpel@users.noreply.github.com> Date: Sat, 11 Apr 2020 22:00:25 +0200 Subject: [PATCH] Create user with admin API if MAU is reached --- changelog.d/7263.bugfix | 1 + synapse/handlers/register.py | 4 +- tests/handlers/test_register.py | 9 +- tests/rest/admin/test_user.py | 174 +++++++++++++++++++++++++++++++- 4 files changed, 183 insertions(+), 5 deletions(-) create mode 100644 changelog.d/7263.bugfix diff --git a/changelog.d/7263.bugfix b/changelog.d/7263.bugfix new file mode 100644 index 000000000000..a70378d3781c --- /dev/null +++ b/changelog.d/7263.bugfix @@ -0,0 +1 @@ +Create user with admin API if MAU limit is reached. \ No newline at end of file diff --git a/synapse/handlers/register.py b/synapse/handlers/register.py index 7ffc194f0c67..8e7e692dcbff 100644 --- a/synapse/handlers/register.py +++ b/synapse/handlers/register.py @@ -163,7 +163,9 @@ def register_user( """ yield self.check_registration_ratelimit(address) - yield self.auth.check_auth_blocking(threepid=threepid) + # do not check_auth_blocking if the call is coming through the Admin API + if address: + yield self.auth.check_auth_blocking(threepid=threepid) password_hash = None if password: password_hash = yield self._auth_handler.hash(password) diff --git a/tests/handlers/test_register.py b/tests/handlers/test_register.py index e7b638dbfe49..bd9b13515266 100644 --- a/tests/handlers/test_register.py +++ b/tests/handlers/test_register.py @@ -125,14 +125,17 @@ def test_register_mau_blocked(self): return_value=defer.succeed(self.lots_of_users) ) self.get_failure( - self.handler.register_user(localpart="local_part"), ResourceLimitError + # for MAU it must have an IP address used to perform the registration + self.handler.register_user(localpart="local_part", address="127.0.0.1"), + ResourceLimitError, ) self.store.get_monthly_active_count = Mock( return_value=defer.succeed(self.hs.config.max_mau_value) ) self.get_failure( - self.handler.register_user(localpart="local_part"), ResourceLimitError + self.handler.register_user(localpart="local_part", address="127.0.0.1"), + ResourceLimitError, ) def test_auto_create_auto_join_rooms(self): @@ -288,10 +291,12 @@ def get_or_create_user(self, requester, localpart, displayname, password_hash=No token = self.macaroon_generator.generate_access_token(user_id) if need_register: + # for MAU it must have an IP address used to perform the registration yield self.handler.register_with_store( user_id=user_id, password_hash=password_hash, create_profile_with_displayname=user.localpart, + address="127.0.0.1", ) else: yield self.hs.get_auth_handler().delete_access_tokens_for_user(user_id) diff --git a/tests/rest/admin/test_user.py b/tests/rest/admin/test_user.py index 6416fb5d2a3e..5287584ce27b 100644 --- a/tests/rest/admin/test_user.py +++ b/tests/rest/admin/test_user.py @@ -20,9 +20,13 @@ from mock import Mock +from twisted.internet import defer + import synapse.rest.admin from synapse.api.constants import UserTypes +from synapse.api.errors import Codes, HttpResponseException, ResourceLimitError from synapse.rest.client.v1 import login +from synapse.rest.client.v2_alpha import sync from tests import unittest @@ -320,6 +324,62 @@ def nonce(): self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"]) self.assertEqual("Invalid user type", channel.json_body["error"]) + def test_register_limit_mau(self): + """ + Register user if MAU limit is reached. + """ + handler = self.hs.get_registration_handler() + store = self.hs.get_datastore() + + self.hs.config.limit_usage_by_mau = True + self.hs.config.max_mau_value = 2 + self.hs.config.mau_trial_days = 0 + + # Set MAU limit + store.get_monthly_active_count = Mock( + return_value=defer.succeed(self.hs.config.max_mau_value + 1) + ) + self.get_failure( + # for MAU it must have an IP address used to perform the registration + handler.register_user(localpart="local_part", address="127.0.0.1"), + ResourceLimitError, + ) + + store.get_monthly_active_count = Mock( + return_value=defer.succeed(self.hs.config.max_mau_value + 1) + ) + self.get_failure( + handler.register_user(localpart="local_part", address="127.0.0.1"), + ResourceLimitError, + ) + + # Register new user with admin API + request, channel = self.make_request("GET", self.url) + self.render(request) + nonce = channel.json_body["nonce"] + + want_mac = hmac.new(key=b"shared", digestmod=hashlib.sha1) + want_mac.update( + nonce.encode("ascii") + b"\x00bob\x00abc123\x00admin\x00support" + ) + want_mac = want_mac.hexdigest() + + body = json.dumps( + { + "nonce": nonce, + "username": "bob", + "password": "abc123", + "admin": True, + "user_type": UserTypes.SUPPORT, + "mac": want_mac, + } + ) + request, channel = self.make_request("POST", self.url, body.encode("utf8")) + self.render(request) + + self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"]) + self.assertEqual("@bob:test", channel.json_body["user_id"]) + class UsersListTestCase(unittest.HomeserverTestCase): @@ -344,7 +404,7 @@ def test_no_auth(self): self.render(request) self.assertEqual(401, int(channel.result["code"]), msg=channel.result["body"]) - self.assertEqual("M_MISSING_TOKEN", channel.json_body["errcode"]) + self.assertEqual(Codes.MISSING_TOKEN, channel.json_body["errcode"]) def test_all_users(self): """ @@ -367,6 +427,7 @@ class UserRestTestCase(unittest.HomeserverTestCase): servlets = [ synapse.rest.admin.register_servlets, login.register_servlets, + sync.register_servlets, ] def prepare(self, reactor, clock, hs): @@ -418,7 +479,7 @@ def test_user_does_not_exist(self): self.render(request) self.assertEqual(404, channel.code, msg=channel.json_body) - self.assertEqual("M_NOT_FOUND", channel.json_body["errcode"]) + self.assertEqual(Codes.NOT_FOUND, channel.json_body["errcode"]) def test_create_server_admin(self): """ @@ -514,6 +575,115 @@ def test_create_user(self): self.assertEqual(False, channel.json_body["is_guest"]) self.assertEqual(False, channel.json_body["deactivated"]) + def test_create_user_limit_mau_active_admin(self): + """ + Check that a new regular user is created successfully if MAU limit is reached. + Admin user was active before creating user. + """ + self.hs.config.registration_shared_secret = None + + handler = self.hs.get_registration_handler() + + self.hs.config.limit_usage_by_mau = True + self.hs.config.max_mau_value = 2 + self.hs.config.mau_trial_days = 0 + + def _do_sync_for_user(token): + request, channel = self.make_request("GET", "/sync", access_token=token) + self.render(request) + + if channel.code != 200: + raise HttpResponseException( + channel.code, channel.result["reason"], channel.result["body"] + ).to_synapse_error() + + # Sync to set admin user to active + _do_sync_for_user(self.admin_user_tok) + + self.store.get_monthly_active_count = Mock( + return_value=defer.succeed(self.hs.config.max_mau_value + 1) + ) + + # Set MAU limit + self.get_failure( + # for MAU it must have an IP address used to perform the registration + handler.register_user(localpart="local_part", address="127.0.0.1"), + ResourceLimitError, + ) + + self.store.get_monthly_active_count = Mock( + return_value=defer.succeed(self.hs.config.max_mau_value + 1) + ) + self.get_failure( + handler.register_user(localpart="local_part", address="127.0.0.1"), + ResourceLimitError, + ) + + # Register new user with admin API + url = "/_synapse/admin/v2/users/@bob:test" + + # Create user + body = json.dumps({"password": "abc123", "admin": False}) + + request, channel = self.make_request( + "PUT", + url, + access_token=self.admin_user_tok, + content=body.encode(encoding="utf_8"), + ) + self.render(request) + + self.assertEqual(201, int(channel.result["code"]), msg=channel.result["body"]) + self.assertEqual("@bob:test", channel.json_body["name"]) + self.assertEqual(False, channel.json_body["admin"]) + + def test_create_user_limit_mau_passiv_admin(self): + """ + Check that a new regular user is created successfully if MAU limit is reached. + Admin user was not active before creating user and creation fails. + """ + self.hs.config.registration_shared_secret = None + + handler = self.hs.get_registration_handler() + + self.hs.config.limit_usage_by_mau = True + self.hs.config.max_mau_value = 2 + self.hs.config.mau_trial_days = 0 + + # Set MAU limit + self.store.get_monthly_active_count = Mock( + return_value=defer.succeed(self.hs.config.max_mau_value + 1) + ) + self.get_failure( + # for MAU it must have an IP address used to perform the registration + handler.register_user(localpart="local_part", address="127.0.0.1"), + ResourceLimitError, + ) + + self.store.get_monthly_active_count = Mock( + return_value=defer.succeed(self.hs.config.max_mau_value + 1) + ) + self.get_failure( + handler.register_user(localpart="local_part", address="127.0.0.1"), + ResourceLimitError, + ) + + # Register new user with admin API + url = "/_synapse/admin/v2/users/@bob:test" + + # Create user + body = json.dumps({"password": "abc123", "admin": False}) + + request, channel = self.make_request( + "PUT", + url, + access_token=self.admin_user_tok, + content=body.encode(encoding="utf_8"), + ) + self.render(request) + + self.assertEqual(500, int(channel.result["code"]), msg=channel.result["body"]) + def test_set_password(self): """ Test setting a new password for another user.