Merge pull request #5204 from matrix-org/babolivier/account_validity_expiration_date

Add startup background job for account validity
pull/5227/head
Brendan Abolivier 2019-05-21 14:55:15 +01:00 committed by GitHub
commit 6a5a70edf0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 134 additions and 12 deletions

1
changelog.d/5204.feature Normal file
View File

@ -0,0 +1 @@
Stick an expiration date to any registered user missing one at startup if account validity is enabled.

View File

@ -747,6 +747,14 @@ uploads_path: "DATADIR/uploads"
# link. ``%(app)s`` can be used as a placeholder for the ``app_name`` parameter # link. ``%(app)s`` can be used as a placeholder for the ``app_name`` parameter
# from the ``email`` section. # from the ``email`` section.
# #
# Once this feature is enabled, Synapse will look for registered users without an
# expiration date at startup and will add one to every account it found using the
# current settings at that time.
# This means that, if a validity period is set, and Synapse is restarted (it will
# then derive an expiration date from the current validity period), and some time
# after that the validity period changes and Synapse is restarted, the users'
# expiration dates won't be updated unless their account is manually renewed.
#
#account_validity: #account_validity:
# enabled: True # enabled: True
# period: 6w # period: 6w

View File

@ -123,6 +123,14 @@ class RegistrationConfig(Config):
# link. ``%%(app)s`` can be used as a placeholder for the ``app_name`` parameter # link. ``%%(app)s`` can be used as a placeholder for the ``app_name`` parameter
# from the ``email`` section. # from the ``email`` section.
# #
# Once this feature is enabled, Synapse will look for registered users without an
# expiration date at startup and will add one to every account it found using the
# current settings at that time.
# This means that, if a validity period is set, and Synapse is restarted (it will
# then derive an expiration date from the current validity period), and some time
# after that the validity period changes and Synapse is restarted, the users'
# expiration dates won't be updated unless their account is manually renewed.
#
#account_validity: #account_validity:
# enabled: True # enabled: True
# period: 6w # period: 6w

View File

@ -1,5 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright 2014-2016 OpenMarket Ltd # Copyright 2014-2016 OpenMarket 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.
@ -227,6 +229,8 @@ class SQLBaseStore(object):
# A set of tables that are not safe to use native upserts in. # A set of tables that are not safe to use native upserts in.
self._unsafe_to_upsert_tables = set(UNIQUE_INDEX_BACKGROUND_UPDATES.keys()) self._unsafe_to_upsert_tables = set(UNIQUE_INDEX_BACKGROUND_UPDATES.keys())
self._account_validity = self.hs.config.account_validity
# We add the user_directory_search table to the blacklist on SQLite # We add the user_directory_search table to the blacklist on SQLite
# because the existing search table does not have an index, making it # because the existing search table does not have an index, making it
# unsafe to use native upserts. # unsafe to use native upserts.
@ -243,6 +247,14 @@ class SQLBaseStore(object):
self._check_safe_to_upsert, self._check_safe_to_upsert,
) )
if self._account_validity.enabled:
self._clock.call_later(
0.0,
run_as_background_process,
"account_validity_set_expiration_dates",
self._set_expiration_date_when_missing,
)
@defer.inlineCallbacks @defer.inlineCallbacks
def _check_safe_to_upsert(self): def _check_safe_to_upsert(self):
""" """
@ -275,6 +287,52 @@ class SQLBaseStore(object):
self._check_safe_to_upsert, self._check_safe_to_upsert,
) )
@defer.inlineCallbacks
def _set_expiration_date_when_missing(self):
"""
Retrieves the list of registered users that don't have an expiration date, and
adds an expiration date for each of them.
"""
def select_users_with_no_expiration_date_txn(txn):
"""Retrieves the list of registered users with no expiration date from the
database.
"""
sql = (
"SELECT users.name FROM users"
" LEFT JOIN account_validity ON (users.name = account_validity.user_id)"
" WHERE account_validity.user_id is NULL;"
)
txn.execute(sql, [])
res = self.cursor_to_dict(txn)
if res:
for user in res:
self.set_expiration_date_for_user_txn(txn, user["name"])
yield self.runInteraction(
"get_users_with_no_expiration_date",
select_users_with_no_expiration_date_txn,
)
def set_expiration_date_for_user_txn(self, txn, user_id):
"""Sets an expiration date to the account with the given user ID.
Args:
user_id (str): User ID to set an expiration date for.
"""
now_ms = self._clock.time_msec()
expiration_ts = now_ms + self._account_validity.period
self._simple_insert_txn(
txn,
"account_validity",
values={
"user_id": user_id,
"expiration_ts_ms": expiration_ts,
"email_sent": False,
},
)
def start_profiling(self): def start_profiling(self):
self._previous_loop_ts = self._clock.time_msec() self._previous_loop_ts = self._clock.time_msec()

View File

@ -1,5 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright 2014 - 2016 OpenMarket Ltd # Copyright 2014-2016 OpenMarket 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.
@ -725,17 +727,7 @@ class RegistrationStore(
raise StoreError(400, "User ID already taken.", errcode=Codes.USER_IN_USE) raise StoreError(400, "User ID already taken.", errcode=Codes.USER_IN_USE)
if self._account_validity.enabled: if self._account_validity.enabled:
now_ms = self.clock.time_msec() self.set_expiration_date_for_user_txn(txn, user_id)
expiration_ts = now_ms + self._account_validity.period
self._simple_insert_txn(
txn,
"account_validity",
values={
"user_id": user_id,
"expiration_ts_ms": expiration_ts,
"email_sent": False,
}
)
if token: if token:
# it's possible for this to get a conflict, but only for a single user # it's possible for this to get a conflict, but only for a single user

View File

@ -1,3 +1,20 @@
# -*- coding: utf-8 -*-
# Copyright 2014-2016 OpenMarket 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");
# 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.
import datetime import datetime
import json import json
import os import os
@ -409,3 +426,41 @@ class AccountValidityRenewalByEmailTestCase(unittest.HomeserverTestCase):
self.assertEquals(channel.result["code"], b"200", channel.result) self.assertEquals(channel.result["code"], b"200", channel.result)
self.assertEqual(len(self.email_attempts), 1) self.assertEqual(len(self.email_attempts), 1)
class AccountValidityBackgroundJobTestCase(unittest.HomeserverTestCase):
servlets = [
synapse.rest.admin.register_servlets_for_client_rest_resource,
]
def make_homeserver(self, reactor, clock):
self.validity_period = 10
config = self.default_config()
config["enable_registration"] = True
config["account_validity"] = {
"enabled": False,
}
self.hs = self.setup_test_homeserver(config=config)
self.hs.config.account_validity.period = self.validity_period
self.store = self.hs.get_datastore()
return self.hs
def test_background_job(self):
"""
Tests whether the account validity startup background job does the right thing,
which is sticking an expiration date to every account that doesn't already have
one.
"""
user_id = self.register_user("kermit", "user")
now_ms = self.hs.clock.time_msec()
self.get_success(self.store._set_expiration_date_when_missing())
res = self.get_success(self.store.get_expiration_ts_for_user(user_id))
self.assertEqual(res, now_ms + self.validity_period)