summary refs log tree commit diff
path: root/synapse
diff options
context:
space:
mode:
authorDirk Klimpel <5740567+dklimpel@users.noreply.github.com>2021-08-17 12:56:11 +0200
committerGitHub <noreply@github.com>2021-08-17 11:56:11 +0100
commit3bcd525b46678ff228c4275acad47c12974c9a33 (patch)
treeeef582d7b2f39c83757b90514f820acc5f3d2f62 /synapse
parentupdate links to schema doc (#10620) (diff)
downloadsynapse-3bcd525b46678ff228c4275acad47c12974c9a33.tar.xz
Allow to edit `external_ids` by Edit User admin API (#10598)
Signed-off-by: Dirk Klimpel dirk@klimpel.org
Diffstat (limited to 'synapse')
-rw-r--r--synapse/rest/admin/users.py139
-rw-r--r--synapse/storage/databases/main/registration.py22
2 files changed, 113 insertions, 48 deletions
diff --git a/synapse/rest/admin/users.py b/synapse/rest/admin/users.py
index 41f21ba118..c885fd77ab 100644
--- a/synapse/rest/admin/users.py
+++ b/synapse/rest/admin/users.py
@@ -196,20 +196,57 @@ class UserRestServletV2(RestServlet):
         user = await self.admin_handler.get_user(target_user)
         user_id = target_user.to_string()
 
+        # check for required parameters for each threepid
+        threepids = body.get("threepids")
+        if threepids is not None:
+            for threepid in threepids:
+                assert_params_in_dict(threepid, ["medium", "address"])
+
+        # check for required parameters for each external_id
+        external_ids = body.get("external_ids")
+        if external_ids is not None:
+            for external_id in external_ids:
+                assert_params_in_dict(external_id, ["auth_provider", "external_id"])
+
+        user_type = body.get("user_type", None)
+        if user_type is not None and user_type not in UserTypes.ALL_USER_TYPES:
+            raise SynapseError(400, "Invalid user type")
+
+        set_admin_to = body.get("admin", False)
+        if not isinstance(set_admin_to, bool):
+            raise SynapseError(
+                HTTPStatus.BAD_REQUEST,
+                "Param 'admin' must be a boolean, if given",
+                Codes.BAD_JSON,
+            )
+
+        password = body.get("password", None)
+        if password is not None:
+            if not isinstance(password, str) or len(password) > 512:
+                raise SynapseError(400, "Invalid password")
+
+        deactivate = body.get("deactivated", False)
+        if not isinstance(deactivate, bool):
+            raise SynapseError(400, "'deactivated' parameter is not of type boolean")
+
+        # convert into List[Tuple[str, str]]
+        if external_ids is not None:
+            new_external_ids = []
+            for external_id in external_ids:
+                new_external_ids.append(
+                    (external_id["auth_provider"], external_id["external_id"])
+                )
+
         if user:  # modify user
             if "displayname" in body:
                 await self.profile_handler.set_displayname(
                     target_user, requester, body["displayname"], True
                 )
 
-            if "threepids" in body:
-                # check for required parameters for each threepid
-                for threepid in body["threepids"]:
-                    assert_params_in_dict(threepid, ["medium", "address"])
-
+            if threepids is not None:
                 # remove old threepids from user
-                threepids = await self.store.user_get_threepids(user_id)
-                for threepid in threepids:
+                old_threepids = await self.store.user_get_threepids(user_id)
+                for threepid in old_threepids:
                     try:
                         await self.auth_handler.delete_threepid(
                             user_id, threepid["medium"], threepid["address"], None
@@ -220,18 +257,39 @@ class UserRestServletV2(RestServlet):
 
                 # add new threepids to user
                 current_time = self.hs.get_clock().time_msec()
-                for threepid in body["threepids"]:
+                for threepid in threepids:
                     await self.auth_handler.add_threepid(
                         user_id, threepid["medium"], threepid["address"], current_time
                     )
 
-            if "avatar_url" in body and type(body["avatar_url"]) == str:
+            if external_ids is not None:
+                # get changed external_ids (added and removed)
+                cur_external_ids = await self.store.get_external_ids_by_user(user_id)
+                add_external_ids = set(new_external_ids) - set(cur_external_ids)
+                del_external_ids = set(cur_external_ids) - set(new_external_ids)
+
+                # remove old external_ids
+                for auth_provider, external_id in del_external_ids:
+                    await self.store.remove_user_external_id(
+                        auth_provider,
+                        external_id,
+                        user_id,
+                    )
+
+                # add new external_ids
+                for auth_provider, external_id in add_external_ids:
+                    await self.store.record_user_external_id(
+                        auth_provider,
+                        external_id,
+                        user_id,
+                    )
+
+            if "avatar_url" in body and isinstance(body["avatar_url"], str):
                 await self.profile_handler.set_avatar_url(
                     target_user, requester, body["avatar_url"], True
                 )
 
             if "admin" in body:
-                set_admin_to = bool(body["admin"])
                 if set_admin_to != user["admin"]:
                     auth_user = requester.user
                     if target_user == auth_user and not set_admin_to:
@@ -239,29 +297,18 @@ class UserRestServletV2(RestServlet):
 
                     await self.store.set_server_admin(target_user, set_admin_to)
 
-            if "password" in body:
-                if not isinstance(body["password"], str) or len(body["password"]) > 512:
-                    raise SynapseError(400, "Invalid password")
-                else:
-                    new_password = body["password"]
-                    logout_devices = True
-
-                    new_password_hash = await self.auth_handler.hash(new_password)
-
-                    await self.set_password_handler.set_password(
-                        target_user.to_string(),
-                        new_password_hash,
-                        logout_devices,
-                        requester,
-                    )
+            if password is not None:
+                logout_devices = True
+                new_password_hash = await self.auth_handler.hash(password)
+
+                await self.set_password_handler.set_password(
+                    target_user.to_string(),
+                    new_password_hash,
+                    logout_devices,
+                    requester,
+                )
 
             if "deactivated" in body:
-                deactivate = body["deactivated"]
-                if not isinstance(deactivate, bool):
-                    raise SynapseError(
-                        400, "'deactivated' parameter is not of type boolean"
-                    )
-
                 if deactivate and not user["deactivated"]:
                     await self.deactivate_account_handler.deactivate_account(
                         target_user.to_string(), False, requester, by_admin=True
@@ -285,36 +332,24 @@ class UserRestServletV2(RestServlet):
             return 200, user
 
         else:  # create user
-            password = body.get("password")
+            displayname = body.get("displayname", None)
+
             password_hash = None
             if password is not None:
-                if not isinstance(password, str) or len(password) > 512:
-                    raise SynapseError(400, "Invalid password")
                 password_hash = await self.auth_handler.hash(password)
 
-            admin = body.get("admin", None)
-            user_type = body.get("user_type", None)
-            displayname = body.get("displayname", None)
-
-            if user_type is not None and user_type not in UserTypes.ALL_USER_TYPES:
-                raise SynapseError(400, "Invalid user type")
-
             user_id = await self.registration_handler.register_user(
                 localpart=target_user.localpart,
                 password_hash=password_hash,
-                admin=bool(admin),
+                admin=set_admin_to,
                 default_display_name=displayname,
                 user_type=user_type,
                 by_admin=True,
             )
 
-            if "threepids" in body:
-                # check for required parameters for each threepid
-                for threepid in body["threepids"]:
-                    assert_params_in_dict(threepid, ["medium", "address"])
-
+            if threepids is not None:
                 current_time = self.hs.get_clock().time_msec()
-                for threepid in body["threepids"]:
+                for threepid in threepids:
                     await self.auth_handler.add_threepid(
                         user_id, threepid["medium"], threepid["address"], current_time
                     )
@@ -334,6 +369,14 @@ class UserRestServletV2(RestServlet):
                             data={},
                         )
 
+            if external_ids is not None:
+                for auth_provider, external_id in new_external_ids:
+                    await self.store.record_user_external_id(
+                        auth_provider,
+                        external_id,
+                        user_id,
+                    )
+
             if "avatar_url" in body and isinstance(body["avatar_url"], str):
                 await self.profile_handler.set_avatar_url(
                     target_user, requester, body["avatar_url"], True
diff --git a/synapse/storage/databases/main/registration.py b/synapse/storage/databases/main/registration.py
index 14670c2881..c67bea81c6 100644
--- a/synapse/storage/databases/main/registration.py
+++ b/synapse/storage/databases/main/registration.py
@@ -599,6 +599,28 @@ class RegistrationWorkerStore(CacheInvalidationWorkerStore):
             desc="record_user_external_id",
         )
 
+    async def remove_user_external_id(
+        self, auth_provider: str, external_id: str, user_id: str
+    ) -> None:
+        """Remove a mapping from an external user id to a mxid
+
+        If the mapping is not found, this method does nothing.
+
+        Args:
+            auth_provider: identifier for the remote auth provider
+            external_id: id on that system
+            user_id: complete mxid that it is mapped to
+        """
+        await self.db_pool.simple_delete(
+            table="user_external_ids",
+            keyvalues={
+                "auth_provider": auth_provider,
+                "external_id": external_id,
+                "user_id": user_id,
+            },
+            desc="remove_user_external_id",
+        )
+
     async def get_user_by_external_id(
         self, auth_provider: str, external_id: str
     ) -> Optional[str]: