mirror of
https://mau.dev/maunium/synapse.git
synced 2024-10-01 01:36:05 -04:00
Track deactivated accounts in the database (#5378)
This commit is contained in:
parent
ad566df746
commit
57bd5cfc9a
1
changelog.d/5378.misc
Normal file
1
changelog.d/5378.misc
Normal file
@ -0,0 +1 @@
|
|||||||
|
Track deactivated accounts in the database.
|
@ -1,5 +1,6 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2017, 2018 New Vector Ltd
|
# Copyright 2017, 2018 New Vector Ltd
|
||||||
|
# Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
@ -118,6 +119,9 @@ class DeactivateAccountHandler(BaseHandler):
|
|||||||
# parts users from rooms (if it isn't already running)
|
# parts users from rooms (if it isn't already running)
|
||||||
self._start_user_parting()
|
self._start_user_parting()
|
||||||
|
|
||||||
|
# Mark the user as deactivated.
|
||||||
|
yield self.store.set_user_deactivated_status(user_id, True)
|
||||||
|
|
||||||
defer.returnValue(identity_server_supports_unbinding)
|
defer.returnValue(identity_server_supports_unbinding)
|
||||||
|
|
||||||
def _start_user_parting(self):
|
def _start_user_parting(self):
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
|
import logging
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from six import iterkeys
|
from six import iterkeys
|
||||||
@ -31,6 +32,8 @@ from synapse.util.caches.descriptors import cached, cachedInlineCallbacks
|
|||||||
|
|
||||||
THIRTY_MINUTES_IN_MS = 30 * 60 * 1000
|
THIRTY_MINUTES_IN_MS = 30 * 60 * 1000
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class RegistrationWorkerStore(SQLBaseStore):
|
class RegistrationWorkerStore(SQLBaseStore):
|
||||||
def __init__(self, db_conn, hs):
|
def __init__(self, db_conn, hs):
|
||||||
@ -598,11 +601,75 @@ class RegistrationStore(
|
|||||||
"user_threepids_grandfather", self._bg_user_threepids_grandfather,
|
"user_threepids_grandfather", self._bg_user_threepids_grandfather,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self.register_background_update_handler(
|
||||||
|
"users_set_deactivated_flag", self._backgroud_update_set_deactivated_flag,
|
||||||
|
)
|
||||||
|
|
||||||
# Create a background job for culling expired 3PID validity tokens
|
# Create a background job for culling expired 3PID validity tokens
|
||||||
hs.get_clock().looping_call(
|
hs.get_clock().looping_call(
|
||||||
self.cull_expired_threepid_validation_tokens, THIRTY_MINUTES_IN_MS,
|
self.cull_expired_threepid_validation_tokens, THIRTY_MINUTES_IN_MS,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def _backgroud_update_set_deactivated_flag(self, progress, batch_size):
|
||||||
|
"""Retrieves a list of all deactivated users and sets the 'deactivated' flag to 1
|
||||||
|
for each of them.
|
||||||
|
"""
|
||||||
|
|
||||||
|
last_user = progress.get("user_id", "")
|
||||||
|
|
||||||
|
def _backgroud_update_set_deactivated_flag_txn(txn):
|
||||||
|
txn.execute(
|
||||||
|
"""
|
||||||
|
SELECT
|
||||||
|
users.name,
|
||||||
|
COUNT(access_tokens.token) AS count_tokens,
|
||||||
|
COUNT(user_threepids.address) AS count_threepids
|
||||||
|
FROM users
|
||||||
|
LEFT JOIN access_tokens ON (access_tokens.user_id = users.name)
|
||||||
|
LEFT JOIN user_threepids ON (user_threepids.user_id = users.name)
|
||||||
|
WHERE password_hash IS NULL OR password_hash = ''
|
||||||
|
AND users.name > ?
|
||||||
|
GROUP BY users.name
|
||||||
|
ORDER BY users.name ASC
|
||||||
|
LIMIT ?;
|
||||||
|
""",
|
||||||
|
(last_user, batch_size),
|
||||||
|
)
|
||||||
|
|
||||||
|
rows = self.cursor_to_dict(txn)
|
||||||
|
|
||||||
|
if not rows:
|
||||||
|
return True
|
||||||
|
|
||||||
|
rows_processed_nb = 0
|
||||||
|
|
||||||
|
for user in rows:
|
||||||
|
if not user["count_tokens"] and not user["count_threepids"]:
|
||||||
|
self.set_user_deactivated_status_txn(txn, user["user_id"], True)
|
||||||
|
rows_processed_nb += 1
|
||||||
|
|
||||||
|
logger.info("Marked %d rows as deactivated", rows_processed_nb)
|
||||||
|
|
||||||
|
self._background_update_progress_txn(
|
||||||
|
txn, "users_set_deactivated_flag", {"user_id": rows[-1]["user_id"]}
|
||||||
|
)
|
||||||
|
|
||||||
|
if batch_size > len(rows):
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
end = yield self.runInteraction(
|
||||||
|
"users_set_deactivated_flag",
|
||||||
|
_backgroud_update_set_deactivated_flag_txn,
|
||||||
|
)
|
||||||
|
|
||||||
|
if end:
|
||||||
|
yield self._end_background_update("users_set_deactivated_flag")
|
||||||
|
|
||||||
|
defer.returnValue(batch_size)
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def add_access_token_to_user(self, user_id, token, device_id=None):
|
def add_access_token_to_user(self, user_id, token, device_id=None):
|
||||||
"""Adds an access token for the given user.
|
"""Adds an access token for the given user.
|
||||||
@ -1268,3 +1335,50 @@ class RegistrationStore(
|
|||||||
"delete_threepid_session",
|
"delete_threepid_session",
|
||||||
delete_threepid_session_txn,
|
delete_threepid_session_txn,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def set_user_deactivated_status_txn(self, txn, user_id, deactivated):
|
||||||
|
self._simple_update_one_txn(
|
||||||
|
txn=txn,
|
||||||
|
table="users",
|
||||||
|
keyvalues={"name": user_id},
|
||||||
|
updatevalues={"deactivated": 1 if deactivated else 0},
|
||||||
|
)
|
||||||
|
self._invalidate_cache_and_stream(
|
||||||
|
txn, self.get_user_deactivated_status, (user_id,),
|
||||||
|
)
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def set_user_deactivated_status(self, user_id, deactivated):
|
||||||
|
"""Set the `deactivated` property for the provided user to the provided value.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
user_id (str): The ID of the user to set the status for.
|
||||||
|
deactivated (bool): The value to set for `deactivated`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
yield self.runInteraction(
|
||||||
|
"set_user_deactivated_status",
|
||||||
|
self.set_user_deactivated_status_txn,
|
||||||
|
user_id, deactivated,
|
||||||
|
)
|
||||||
|
|
||||||
|
@cachedInlineCallbacks()
|
||||||
|
def get_user_deactivated_status(self, user_id):
|
||||||
|
"""Retrieve the value for the `deactivated` property for the provided user.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
user_id (str): The ID of the user to retrieve the status for.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
defer.Deferred(bool): The requested value.
|
||||||
|
"""
|
||||||
|
|
||||||
|
res = yield self._simple_select_one_onecol(
|
||||||
|
table="users",
|
||||||
|
keyvalues={"name": user_id},
|
||||||
|
retcol="deactivated",
|
||||||
|
desc="get_user_deactivated_status",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Convert the integer into a boolean.
|
||||||
|
defer.returnValue(res == 1)
|
||||||
|
19
synapse/storage/schema/delta/55/users_alter_deactivated.sql
Normal file
19
synapse/storage/schema/delta/55/users_alter_deactivated.sql
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
/* Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
ALTER TABLE users ADD deactivated SMALLINT DEFAULT 0 NOT NULL;
|
||||||
|
|
||||||
|
INSERT INTO background_updates (update_name, progress_json) VALUES
|
||||||
|
('users_set_deactivated_flag', '{}');
|
@ -15,6 +15,7 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
|
import json
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
from email.parser import Parser
|
from email.parser import Parser
|
||||||
@ -239,3 +240,47 @@ class PasswordResetTestCase(unittest.HomeserverTestCase):
|
|||||||
)
|
)
|
||||||
self.render(request)
|
self.render(request)
|
||||||
self.assertEquals(expected_code, channel.code, channel.result)
|
self.assertEquals(expected_code, channel.code, channel.result)
|
||||||
|
|
||||||
|
|
||||||
|
class DeactivateTestCase(unittest.HomeserverTestCase):
|
||||||
|
|
||||||
|
servlets = [
|
||||||
|
synapse.rest.admin.register_servlets_for_client_rest_resource,
|
||||||
|
login.register_servlets,
|
||||||
|
account.register_servlets,
|
||||||
|
]
|
||||||
|
|
||||||
|
def make_homeserver(self, reactor, clock):
|
||||||
|
hs = self.setup_test_homeserver()
|
||||||
|
return hs
|
||||||
|
|
||||||
|
def test_deactivate_account(self):
|
||||||
|
user_id = self.register_user("kermit", "test")
|
||||||
|
tok = self.login("kermit", "test")
|
||||||
|
|
||||||
|
request_data = json.dumps({
|
||||||
|
"auth": {
|
||||||
|
"type": "m.login.password",
|
||||||
|
"user": user_id,
|
||||||
|
"password": "test",
|
||||||
|
},
|
||||||
|
"erase": False,
|
||||||
|
})
|
||||||
|
request, channel = self.make_request(
|
||||||
|
"POST",
|
||||||
|
"account/deactivate",
|
||||||
|
request_data,
|
||||||
|
access_token=tok,
|
||||||
|
)
|
||||||
|
self.render(request)
|
||||||
|
self.assertEqual(request.code, 200)
|
||||||
|
|
||||||
|
store = self.hs.get_datastore()
|
||||||
|
|
||||||
|
# Check that the user has been marked as deactivated.
|
||||||
|
self.assertTrue(self.get_success(store.get_user_deactivated_status(user_id)))
|
||||||
|
|
||||||
|
# Check that this access token has been invalidated.
|
||||||
|
request, channel = self.make_request("GET", "account/whoami")
|
||||||
|
self.render(request)
|
||||||
|
self.assertEqual(request.code, 401)
|
||||||
|
Loading…
Reference in New Issue
Block a user