Skip to content
This repository has been archived by the owner on Apr 26, 2024. It is now read-only.

Fix deactivate a user if he does not have a profile #10252

Merged
merged 5 commits into from
Jul 6, 2021
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/10252.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Deactivate and mark a user as erased also if he has no profile.
dklimpel marked this conversation as resolved.
Show resolved Hide resolved
19 changes: 14 additions & 5 deletions synapse/handlers/deactivate_account.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
import logging
from typing import TYPE_CHECKING, Optional

from synapse.api.errors import SynapseError
from synapse.api.errors import StoreError, SynapseError
from synapse.metrics.background_process_metrics import run_as_background_process
from synapse.types import Requester, UserID, create_requester

Expand Down Expand Up @@ -136,10 +136,19 @@ async def deactivate_account(
# Mark the user as erased, if they asked for that
if erase_data:
user = UserID.from_string(user_id)
# Remove avatar URL from this user
await self._profile_handler.set_avatar_url(user, requester, "", by_admin)
# Remove displayname from this user
await self._profile_handler.set_displayname(user, requester, "", by_admin)
try:
# Remove avatar URL from this user
await self._profile_handler.set_avatar_url(
user, requester, "", by_admin
)
# Remove displayname from this user
await self._profile_handler.set_displayname(
user, requester, "", by_admin
)
except StoreError as e:
# bypass only 404 if user does not have a profile
if e.code != 404:
raise e
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the reason a StoreError is raised here is due to set_profile_avatar_url performing an update on profiles rather than an upsert:

async def set_profile_avatar_url(
self, user_localpart: str, new_avatar_url: Optional[str]
) -> None:
await self.db_pool.simple_update_one(
table="profiles",
keyvalues={"user_id": user_localpart},
updatevalues={"avatar_url": new_avatar_url},
desc="set_profile_avatar_url",
)

I think a more fundamental fix for this whole problem would actually just be to convert set_profile_avatar_url to use self.db_pool.simple_upsert, like so:

        await self.db_pool.simple_upsert(
            table="profiles",
            keyvalues={"user_id": user_localpart},
            values={},
            insertion_values={"avatar_url": new_avatar_url},
            desc="set_profile_avatar_url",
        )

it'd probably be a good idea to do the same for set_profile_displayname as well, just to avoid the same issue in the future.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok. I will make an update.


logger.info("Marking %s as erased", user_id)
await self.store.mark_user_erased(user_id)
Expand Down
59 changes: 57 additions & 2 deletions tests/rest/admin/test_user.py
Original file line number Diff line number Diff line change
Expand Up @@ -1006,7 +1006,7 @@ def test_user_is_not_local(self):

def test_deactivate_user_erase_true(self):
"""
Test deactivating an user and set `erase` to `true`
Test deactivating a user and set `erase` to `true`
"""

# Get user
Expand Down Expand Up @@ -1053,7 +1053,7 @@ def test_deactivate_user_erase_true(self):

def test_deactivate_user_erase_false(self):
"""
Test deactivating an user and set `erase` to `false`
Test deactivating a user and set `erase` to `false`
"""

# Get user
Expand Down Expand Up @@ -1098,6 +1098,61 @@ def test_deactivate_user_erase_false(self):

self._is_erased("@user:test", False)

def test_deactivate_user_erase_true_no_profile(self):
"""
Test deactivating a user and set `erase` to `true`
if user has no profile information in database table `profile`
dklimpel marked this conversation as resolved.
Show resolved Hide resolved
"""

# Delete profile information
richvdh marked this conversation as resolved.
Show resolved Hide resolved
self.get_success(
self.store.db_pool.simple_delete_one(
table="profiles", keyvalues={"user_id": "user"}
)
)

# Get user
channel = self.make_request(
"GET",
self.url_other_user,
access_token=self.admin_user_tok,
)

self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
self.assertEqual("@user:test", channel.json_body["name"])
self.assertEqual(False, channel.json_body["deactivated"])
self.assertEqual("foo@bar.com", channel.json_body["threepids"][0]["address"])
self.assertIsNone(channel.json_body["avatar_url"])
self.assertIsNone(channel.json_body["displayname"])

# Deactivate user
dklimpel marked this conversation as resolved.
Show resolved Hide resolved
body = json.dumps({"erase": True})

channel = self.make_request(
"POST",
self.url,
access_token=self.admin_user_tok,
content=body.encode(encoding="utf_8"),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can actually just pass the Python dict directory to make_request these days.

)

self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
dklimpel marked this conversation as resolved.
Show resolved Hide resolved

# Get user
channel = self.make_request(
"GET",
self.url_other_user,
access_token=self.admin_user_tok,
)

self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
dklimpel marked this conversation as resolved.
Show resolved Hide resolved
self.assertEqual("@user:test", channel.json_body["name"])
self.assertEqual(True, channel.json_body["deactivated"])
self.assertEqual(0, len(channel.json_body["threepids"]))
self.assertIsNone(channel.json_body["avatar_url"])
self.assertIsNone(channel.json_body["displayname"])

self._is_erased("@user:test", True)

def _is_erased(self, user_id: str, expect: bool) -> None:
"""Assert that the user is erased or not"""
d = self.store.is_user_erased(user_id)
Expand Down