-
Notifications
You must be signed in to change notification settings - Fork 457
fix: Avoid a M_FORBIDDEN response when a user tries to erase their account and profile updates are disabled
#19398
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| Allow user requested erasure to succeed even if Synapse has disabled profile changes. Contributed by Famedly. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -33,6 +33,7 @@ | |
| from synapse.util.clock import Clock | ||
|
|
||
| from tests import unittest | ||
| from tests.unittest import override_config | ||
|
|
||
|
|
||
| class ProfileTestCase(unittest.HomeserverTestCase): | ||
|
|
@@ -113,9 +114,8 @@ def test_set_my_name(self) -> None: | |
| self.get_success(self.store.get_profile_displayname(self.frank)) | ||
| ) | ||
|
|
||
| def test_set_my_name_if_disabled(self) -> None: | ||
| self.hs.config.registration.enable_set_displayname = False | ||
|
|
||
| @override_config({"enable_set_displayname": False}) | ||
| def test_set_displayname_if_disabled(self) -> None: | ||
|
Comment on lines
-116
to
+118
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This(and the similar in this file) was a drive-by fix to not accidentally change the state of the homeserver configuration the other tests in this series may depend on. If the order of the tests in this series was reversed, at least one of the tests would not pass. No changes here were relevant for to the purpose of this work
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I can remove this if it is an obstacle |
||
| # Setting displayname for the first time is allowed | ||
| self.get_success(self.store.set_profile_displayname(self.frank, "Frank")) | ||
|
|
||
|
|
@@ -234,9 +234,8 @@ def test_set_my_avatar(self) -> None: | |
| (self.get_success(self.store.get_profile_avatar_url(self.frank))), | ||
| ) | ||
|
|
||
| def test_set_my_avatar_if_disabled(self) -> None: | ||
| self.hs.config.registration.enable_set_avatar_url = False | ||
|
|
||
| @override_config({"enable_set_avatar_url": False}) | ||
| def test_set_avatar_url_if_disabled(self) -> None: | ||
| # Setting displayname for the first time is allowed | ||
| self.get_success( | ||
| self.store.set_profile_avatar_url(self.frank, "http://my.server/me.png") | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -38,7 +38,7 @@ | |
| from synapse.rest.synapse.client.password_reset import PasswordResetSubmitTokenResource | ||
| from synapse.server import HomeServer | ||
| from synapse.storage._base import db_to_json | ||
| from synapse.types import JsonDict, UserID | ||
| from synapse.types import JsonDict, UserID, create_requester | ||
| from synapse.util.clock import Clock | ||
|
|
||
| from tests import unittest | ||
|
|
@@ -500,6 +500,97 @@ def test_deactivate_account(self) -> None: | |
| channel = self.make_request("GET", "account/whoami", access_token=tok) | ||
| self.assertEqual(channel.code, 401) | ||
|
|
||
| def test_deactivate_erase_account(self) -> None: | ||
| """ | ||
| Test that a user account can be signaled for erasure on the Matrix spec endpoint | ||
| for client access, `/account/deactivate` and that profile data is erased as part | ||
| of the process | ||
| """ | ||
| mxid = self.register_user("kermit", "test") | ||
| user_id = UserID.from_string(mxid) | ||
| tok = self.login("kermit", "test") | ||
|
|
||
| profile_handler = self.hs.get_profile_handler() | ||
|
|
||
| # Set some profile data that can be checked for after the user is erased | ||
| self.get_success( | ||
| profile_handler.set_displayname( | ||
| user_id, create_requester(user_id), "Kermit the Frog" | ||
| ) | ||
| ) | ||
| self.get_success( | ||
| profile_handler.set_avatar_url( | ||
| user_id, create_requester(user_id), "http://test/Kermit.jpg" | ||
| ) | ||
| ) | ||
|
Comment on lines
+516
to
+525
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I chose to use profile data for this erasure test as the "smoke signal" that it was functioning, since it was relevant for the fix being included. There are rather large area of other things that could be checked for, and I did not see any explicit testing for comprehensive deletion of data |
||
|
|
||
| # Request erasure of the user | ||
| self.deactivate(mxid, tok, erase=True) | ||
|
|
||
| store = self.hs.get_datastores().main | ||
|
|
||
| # Check that the user has been marked as deactivated. | ||
| self.assertTrue(self.get_success(store.get_user_deactivated_status(mxid))) | ||
|
|
||
| # On deactivation with 'erase', a displayname and avatar_url are set to an empty | ||
| # string through the handler, but are turned into `None` for the database | ||
| display_name = self.get_success(profile_handler.get_displayname(user_id)) | ||
| assert display_name is None, f"{display_name}" | ||
|
|
||
| avatar_url = self.get_success(profile_handler.get_avatar_url(user_id)) | ||
| assert avatar_url is None, f"{avatar_url}" | ||
|
|
||
| # Check that this access token has been invalidated. | ||
| channel = self.make_request("GET", "account/whoami", access_token=tok) | ||
| self.assertEqual(channel.code, 401) | ||
|
|
||
| @override_config({"enable_set_displayname": False, "enable_set_avatar_url": False}) | ||
| def test_deactivate_erase_account_with_disabled_profile_changes(self) -> None: | ||
| """ | ||
| Test the same erasure process as `test_deactivate_erase_account` above, but have | ||
| the homeserver configuration disable user ability to update profile data | ||
| """ | ||
| mxid = self.register_user("kermit", "test") | ||
| user_id = UserID.from_string(mxid) | ||
| tok = self.login("kermit", "test") | ||
|
|
||
| profile_handler = self.hs.get_profile_handler() | ||
|
|
||
| # Can not use the profile handler to set a display name when it is disabled. Use | ||
| # the database directly | ||
| store = self.hs.get_datastores().main | ||
| self.get_success(store.set_profile_displayname(user_id, "Kermit the Frog")) | ||
|
|
||
| self.assertEqual( | ||
| (self.get_success(store.get_profile_displayname(user_id))), | ||
| "Kermit the Frog", | ||
| ) | ||
| self.get_success( | ||
| store.set_profile_avatar_url(user_id, "http://test/Kermit.jpg") | ||
| ) | ||
| self.assertEqual( | ||
| (self.get_success(store.get_profile_avatar_url(user_id))), | ||
| "http://test/Kermit.jpg", | ||
| ) | ||
|
|
||
| # Request erasure of the user | ||
| self.deactivate(mxid, tok, erase=True) | ||
|
|
||
| # Check that the user has been marked as deactivated. | ||
| self.assertTrue(self.get_success(store.get_user_deactivated_status(mxid))) | ||
|
|
||
| # On deactivation with 'erase', a displayname and avatar_url are set to an empty | ||
| # string through the handler, but are turned into `None` for the database | ||
| display_name = self.get_success(profile_handler.get_displayname(user_id)) | ||
| assert display_name is None, f"{display_name}" | ||
|
|
||
| avatar_url = self.get_success(profile_handler.get_avatar_url(user_id)) | ||
| assert avatar_url is None, f"{avatar_url}" | ||
|
|
||
| # Check that this access token has been invalidated. | ||
| channel = self.make_request("GET", "account/whoami", access_token=tok) | ||
| self.assertEqual(channel.code, 401) | ||
|
|
||
| def test_pending_invites(self) -> None: | ||
| """Tests that deactivating a user rejects every pending invite for them.""" | ||
| store = self.hs.get_datastores().main | ||
|
|
@@ -698,14 +789,23 @@ def test_background_update_deletes_deactivated_users_server_side_backup_keys( | |
| ) | ||
| self.assertEqual(len(res2), 4) | ||
|
|
||
| def deactivate(self, user_id: str, tok: str) -> None: | ||
| def deactivate(self, user_id: str, tok: str, erase: bool = False) -> None: | ||
| """ | ||
| Helper to deactivate a user using the /account/deactivate endpoint, optionally | ||
| with erasure | ||
|
|
||
| Args: | ||
| user_id: the string formatted mxid(not a UserID) | ||
| tok: the user's access token | ||
| erase: bool of if this should be a full erasure request | ||
| """ | ||
| request_data = { | ||
| "auth": { | ||
| "type": "m.login.password", | ||
| "user": user_id, | ||
| "password": "test", | ||
| }, | ||
| "erase": False, | ||
| "erase": erase, | ||
| } | ||
| channel = self.make_request( | ||
| "POST", "account/deactivate", request_data, access_token=tok | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
new_displayname(and similarily below fornew_avatar_url) an explicit""is checked for, as that is what theDeactivateAccountHandler.deactivate_account()uses