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

Commit

Permalink
Track deactivated accounts in the database (#5378)
Browse files Browse the repository at this point in the history
  • Loading branch information
babolivier authored Jun 14, 2019
1 parent f03f8b7 commit d053038
Show file tree
Hide file tree
Showing 5 changed files with 183 additions and 0 deletions.
1 change: 1 addition & 0 deletions changelog.d/5378.misc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Track deactivated accounts in the database.
4 changes: 4 additions & 0 deletions synapse/handlers/deactivate_account.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
# 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");
# you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -114,6 +115,9 @@ def deactivate_account(self, user_id, erase_data, id_server=None):
# parts users from rooms (if it isn't already running)
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)

def _start_user_parting(self):
Expand Down
114 changes: 114 additions & 0 deletions synapse/storage/registration.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import logging
import re

from six import iterkeys
Expand All @@ -31,6 +32,8 @@

THIRTY_MINUTES_IN_MS = 30 * 60 * 1000

logger = logging.getLogger(__name__)


class RegistrationWorkerStore(SQLBaseStore):
def __init__(self, db_conn, hs):
Expand Down Expand Up @@ -598,11 +601,75 @@ def __init__(self, db_conn, hs):
"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
hs.get_clock().looping_call(
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
def add_access_token_to_user(self, user_id, token, device_id=None):
"""Adds an access token for the given user.
Expand Down Expand Up @@ -1268,3 +1335,50 @@ def delete_threepid_session_txn(txn):
"delete_threepid_session",
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 changes: 19 additions & 0 deletions synapse/storage/schema/delta/55/users_alter_deactivated.sql
Original file line number Diff line number Diff line change
@@ -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', '{}');
45 changes: 45 additions & 0 deletions tests/rest/client/v2_alpha/test_account.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import json
import os
import re
from email.parser import Parser
Expand Down Expand Up @@ -239,3 +240,47 @@ def _reset_password(
)
self.render(request)
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)

0 comments on commit d053038

Please sign in to comment.