summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--docs/sample_config.yaml5
-rw-r--r--synapse/config/registration.py8
-rw-r--r--synapse/groups/groups_server.py21
-rw-r--r--synapse/handlers/groups_local.py19
-rw-r--r--synapse/rest/client/v2_alpha/account.py4
-rw-r--r--synapse/rest/client/v2_alpha/groups.py26
-rw-r--r--synapse/rest/client/v2_alpha/register.py10
-rw-r--r--synapse/storage/databases/main/group_server.py10
-rw-r--r--synapse/util/threepids.py49
9 files changed, 145 insertions, 7 deletions
diff --git a/docs/sample_config.yaml b/docs/sample_config.yaml
index 8a3206e845..e71bd782b0 100644
--- a/docs/sample_config.yaml
+++ b/docs/sample_config.yaml
@@ -1182,6 +1182,11 @@ account_validity:
 # Mandate that users are only allowed to associate certain formats of
 # 3PIDs with accounts on this server.
 #
+# Use an Identity Server to establish which 3PIDs are allowed to register?
+# Overrides allowed_local_3pids below.
+#
+#check_is_for_allowed_local_3pids: matrix.org
+#
 #allowed_local_3pids:
 #  - medium: email
 #    pattern: '.*@matrix\.org'
diff --git a/synapse/config/registration.py b/synapse/config/registration.py
index d7e3690a32..aeae5bcaea 100644
--- a/synapse/config/registration.py
+++ b/synapse/config/registration.py
@@ -100,6 +100,9 @@ class RegistrationConfig(Config):
 
         self.registrations_require_3pid = config.get("registrations_require_3pid", [])
         self.allowed_local_3pids = config.get("allowed_local_3pids", [])
+        self.check_is_for_allowed_local_3pids = config.get(
+            "check_is_for_allowed_local_3pids", None
+        )
         self.enable_3pid_lookup = config.get("enable_3pid_lookup", True)
         self.registration_shared_secret = config.get("registration_shared_secret")
 
@@ -299,6 +302,11 @@ class RegistrationConfig(Config):
         # Mandate that users are only allowed to associate certain formats of
         # 3PIDs with accounts on this server.
         #
+        # Use an Identity Server to establish which 3PIDs are allowed to register?
+        # Overrides allowed_local_3pids below.
+        #
+        #check_is_for_allowed_local_3pids: matrix.org
+        #
         #allowed_local_3pids:
         #  - medium: email
         #    pattern: '.*@matrix\\.org'
diff --git a/synapse/groups/groups_server.py b/synapse/groups/groups_server.py
index e5f85b472d..de4c94cd3a 100644
--- a/synapse/groups/groups_server.py
+++ b/synapse/groups/groups_server.py
@@ -719,6 +719,27 @@ class GroupsServerHandler(GroupsServerWorkerHandler):
 
         raise NotImplementedError()
 
+    async def change_user_admin_in_group(
+        self, group_id, user_id, want_admin, requester_user_id, content
+    ):
+        """Promotes or demotes a user in a group.
+        """
+
+        await self.check_group_is_ours(group_id, requester_user_id, and_exists=True)
+
+        if requester_user_id == user_id:
+            raise SynapseError(400, "User cannot target themselves")
+
+        is_admin = await self.store.is_user_admin_in_group(
+            group_id, requester_user_id
+        )
+        if not is_admin:
+            raise SynapseError(403, "User is not admin in group")
+
+        await self.store.change_user_admin_in_group(group_id, user_id, want_admin)
+
+        return {}
+
     async def remove_user_from_group(
         self, group_id, user_id, requester_user_id, content
     ):
diff --git a/synapse/handlers/groups_local.py b/synapse/handlers/groups_local.py
index 9684e60fc8..489a7b885d 100644
--- a/synapse/handlers/groups_local.py
+++ b/synapse/handlers/groups_local.py
@@ -461,6 +461,25 @@ class GroupsLocalHandler(GroupsLocalWorkerHandler):
 
         return {"state": "invite", "user_profile": user_profile}
 
+    async def change_user_admin_in_group(
+        self, group_id, user_id, want_admin, requester_user_id, content
+    ):
+        """Promotes or demotes a user in a group.
+        """
+
+        if not self.is_mine_id(user_id):
+            raise SynapseError(400, "User not on this server")
+
+        # TODO: We should probably support federation, but this is fine for now
+        if not self.is_mine_id(group_id):
+            raise SynapseError(400, "Group not on this server")
+
+        res = await self.groups_server_handler.change_user_admin_in_group(
+            group_id, user_id, want_admin, requester_user_id, content
+        )
+
+        return res
+
     async def remove_user_from_group(
         self, group_id, user_id, requester_user_id, content
     ):
diff --git a/synapse/rest/client/v2_alpha/account.py b/synapse/rest/client/v2_alpha/account.py
index ab5815e7f7..86d3d86fad 100644
--- a/synapse/rest/client/v2_alpha/account.py
+++ b/synapse/rest/client/v2_alpha/account.py
@@ -366,7 +366,7 @@ class EmailThreepidRequestTokenRestServlet(RestServlet):
         send_attempt = body["send_attempt"]
         next_link = body.get("next_link")  # Optional param
 
-        if not check_3pid_allowed(self.hs, "email", email):
+        if not await check_3pid_allowed(self.hs, "email", email):
             raise SynapseError(
                 403,
                 "Your email domain is not authorized on this server",
@@ -441,7 +441,7 @@ class MsisdnThreepidRequestTokenRestServlet(RestServlet):
 
         msisdn = phone_number_to_msisdn(country, phone_number)
 
-        if not check_3pid_allowed(self.hs, "msisdn", msisdn):
+        if not await check_3pid_allowed(self.hs, "msisdn", msisdn):
             raise SynapseError(
                 403,
                 "Account phone numbers are not authorized on this server",
diff --git a/synapse/rest/client/v2_alpha/groups.py b/synapse/rest/client/v2_alpha/groups.py
index a3bb095c2d..75215a3779 100644
--- a/synapse/rest/client/v2_alpha/groups.py
+++ b/synapse/rest/client/v2_alpha/groups.py
@@ -552,6 +552,31 @@ class GroupAdminUsersKickServlet(RestServlet):
 
         return 200, result
 
+class GroupAdminChangeAdminServlet(RestServlet):
+    """Promote or demote a user in the group
+    """
+
+    PATTERNS = client_patterns(
+        "/groups/(?P<group_id>[^/]*)/admin/users/admins/(?P<user_id>[^/]*)$"
+    )
+
+    def __init__(self, hs):
+        super(GroupAdminChangeAdminServlet, self).__init__()
+        self.auth = hs.get_auth()
+        self.clock = hs.get_clock()
+        self.groups_handler = hs.get_groups_local_handler()
+
+    async def on_POST(self, request, group_id, user_id):
+        requester = await self.auth.get_user_by_req(request)
+        requester_user_id = requester.user.to_string()
+
+        content = parse_json_object_from_request(request)
+        want_admin = content["is_admin"]
+        result = await self.groups_handler.change_user_admin_in_group(
+            group_id, user_id, want_admin, requester_user_id, content
+        )
+
+        return 200, result
 
 class GroupSelfLeaveServlet(RestServlet):
     """Leave a joined group
@@ -726,6 +751,7 @@ def register_servlets(hs, http_server):
     GroupAdminRoomsConfigServlet(hs).register(http_server)
     GroupAdminUsersInviteServlet(hs).register(http_server)
     GroupAdminUsersKickServlet(hs).register(http_server)
+    GroupAdminChangeAdminServlet(hs).register(http_server)
     GroupSelfLeaveServlet(hs).register(http_server)
     GroupSelfJoinServlet(hs).register(http_server)
     GroupSelfAcceptInviteServlet(hs).register(http_server)
diff --git a/synapse/rest/client/v2_alpha/register.py b/synapse/rest/client/v2_alpha/register.py
index ffa2dfce42..ec8ef9bf88 100644
--- a/synapse/rest/client/v2_alpha/register.py
+++ b/synapse/rest/client/v2_alpha/register.py
@@ -117,10 +117,10 @@ class EmailRegisterRequestTokenRestServlet(RestServlet):
         send_attempt = body["send_attempt"]
         next_link = body.get("next_link")  # Optional param
 
-        if not check_3pid_allowed(self.hs, "email", email):
+        if not await check_3pid_allowed(self.hs, "email", email, during_registration=True):
             raise SynapseError(
                 403,
-                "Your email domain is not authorized to register on this server",
+                "You currently can't create an account with this email address",
                 Codes.THREEPID_DENIED,
             )
 
@@ -192,7 +192,7 @@ class MsisdnRegisterRequestTokenRestServlet(RestServlet):
 
         msisdn = phone_number_to_msisdn(country, phone_number)
 
-        if not check_3pid_allowed(self.hs, "msisdn", msisdn):
+        if not await check_3pid_allowed(self.hs, "msisdn", msisdn, during_registration=True):
             raise SynapseError(
                 403,
                 "Phone numbers are not authorized to register on this server",
@@ -538,7 +538,9 @@ class RegisterRestServlet(RestServlet):
                     medium = auth_result[login_type]["medium"]
                     address = auth_result[login_type]["address"]
 
-                    if not check_3pid_allowed(self.hs, medium, address):
+                    if not await check_3pid_allowed(
+                        self.hs, medium, address, during_registration=True
+                    ):
                         raise SynapseError(
                             403,
                             "Third party identifiers (email/phone numbers)"
diff --git a/synapse/storage/databases/main/group_server.py b/synapse/storage/databases/main/group_server.py
index 7218191965..f724f45494 100644
--- a/synapse/storage/databases/main/group_server.py
+++ b/synapse/storage/databases/main/group_server.py
@@ -1116,6 +1116,16 @@ class GroupServerStore(GroupServerWorkerStore):
             "remove_user_from_group", _remove_user_from_group_txn
         )
 
+    async def change_user_admin_in_group(
+        self, group_id: str, user_id: str, is_admin: bool
+    ) -> int:
+        return await self.db_pool.simple_update(
+            table="group_users",
+            keyvalues={"group_id": group_id, "user_id": user_id},
+            updatevalues={"is_admin": is_admin},
+            desc="change_user_admin_in_group"
+        )
+
     async def add_room_to_group(
         self, group_id: str, room_id: str, is_public: bool
     ) -> None:
diff --git a/synapse/util/threepids.py b/synapse/util/threepids.py
index 43c2e0ac23..527d873935 100644
--- a/synapse/util/threepids.py
+++ b/synapse/util/threepids.py
@@ -19,7 +19,7 @@ import re
 logger = logging.getLogger(__name__)
 
 
-def check_3pid_allowed(hs, medium, address):
+async def check_3pid_allowed(hs, medium, address, during_registration: bool = False):
     """Checks whether a given format of 3PID is allowed to be used on this HS
 
     Args:
@@ -27,10 +27,57 @@ def check_3pid_allowed(hs, medium, address):
         medium (str): 3pid medium - e.g. email, msisdn
         address (str): address within that medium (e.g. "wotan@matrix.org")
             msisdns need to first have been canonicalised
+        during_registration: Whether this request has been made while registering a new
+            user.
     Returns:
         bool: whether the 3PID medium/address is allowed to be added to this HS
     """
 
+    if hs.config.check_is_for_allowed_local_3pids and during_registration:
+        # If this 3pid is being approved as part of registering a new user,
+        # we'll want to make sure the 3pid has been invited by someone already.
+        #
+        # We condition on registration so that user 3pids do not require an invite while
+        # doing tasks other than registration, such as resetting their password or adding a
+        # second email to their account.
+        data = await hs.get_simple_http_client().get_json(
+            "https://%s%s" % (
+                hs.config.check_is_for_allowed_local_3pids,
+                "/_matrix/identity/api/v1/internal-info"
+            ),
+            {'medium': medium, 'address': address}
+        )
+        logger.info(
+            "Received internal-info data for medium '%s', address '%s': %s",
+            medium, address, data,
+        )
+
+        # Check for invalid response
+        if 'hs' not in data and 'shadow_hs' not in data:
+            return False
+
+        # Check if this user is intended to register for this homeserver
+        if (
+            data.get('hs') != hs.config.server_name
+            and data.get('shadow_hs') != hs.config.server_name
+        ):
+            logger.info(
+                "%s did not match %s or %s did not match %s",
+                data.get("hs"), hs.config.server_name,
+                data.get("shadow_hs"), hs.config.server_name,
+            )
+            return False
+
+        if data.get('requires_invite', False) and not data.get('invited', False):
+            # Requires an invite but hasn't been invited
+            logger.info(
+                "3PID check failed due to 'required_invite' = '%s' and 'invited' = '%s'",
+                data.get('required_invite'), data.get("invited"),
+            )
+            return False
+
+        return True
+
     if hs.config.allowed_local_3pids:
         for constraint in hs.config.allowed_local_3pids:
             logger.debug(