Add timestamp to user's consent (#13741)

Co-authored-by: reivilibre <olivier@librepush.net>
This commit is contained in:
Dirk Klimpel 2022-09-08 17:41:48 +02:00 committed by GitHub
parent 906cead9ca
commit f799eac7ea
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 52 additions and 8 deletions

View file

@ -0,0 +1 @@
Document the timestamp when a user accepts the consent, if [consent tracking](https://matrix-org.github.io/synapse/latest/consent_tracking.html) is used.

View file

@ -42,6 +42,7 @@ It returns a JSON body like the following:
"appservice_id": null, "appservice_id": null,
"consent_server_notice_sent": null, "consent_server_notice_sent": null,
"consent_version": null, "consent_version": null,
"consent_ts": null,
"external_ids": [ "external_ids": [
{ {
"auth_provider": "<provider1>", "auth_provider": "<provider1>",
@ -364,6 +365,7 @@ The following actions are **NOT** performed. The list may be incomplete.
- Remove the user's creation (registration) timestamp - Remove the user's creation (registration) timestamp
- [Remove rate limit overrides](#override-ratelimiting-for-users) - [Remove rate limit overrides](#override-ratelimiting-for-users)
- Remove from monthly active users - Remove from monthly active users
- Remove user's consent information (consent version and timestamp)
## Reset password ## Reset password

View file

@ -70,6 +70,7 @@ class AdminHandler:
"appservice_id", "appservice_id",
"consent_server_notice_sent", "consent_server_notice_sent",
"consent_version", "consent_version",
"consent_ts",
"user_type", "user_type",
"is_guest", "is_guest",
} }

View file

@ -175,6 +175,7 @@ class RegistrationWorkerStore(CacheInvalidationWorkerStore):
"is_guest", "is_guest",
"admin", "admin",
"consent_version", "consent_version",
"consent_ts",
"consent_server_notice_sent", "consent_server_notice_sent",
"appservice_id", "appservice_id",
"creation_ts", "creation_ts",
@ -2227,7 +2228,10 @@ class RegistrationStore(StatsStore, RegistrationBackgroundUpdateStore):
txn, txn,
table="users", table="users",
keyvalues={"name": user_id}, keyvalues={"name": user_id},
updatevalues={"consent_version": consent_version}, updatevalues={
"consent_version": consent_version,
"consent_ts": self._clock.time_msec(),
},
) )
self._invalidate_cache_and_stream(txn, self.get_user_by_id, (user_id,)) self._invalidate_cache_and_stream(txn, self.get_user_by_id, (user_id,))

View file

@ -0,0 +1,16 @@
/* Copyright 2022 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 consent_ts bigint;

View file

@ -2580,6 +2580,7 @@ class UserRestTestCase(unittest.HomeserverTestCase):
self.assertIn("appservice_id", content) self.assertIn("appservice_id", content)
self.assertIn("consent_server_notice_sent", content) self.assertIn("consent_server_notice_sent", content)
self.assertIn("consent_version", content) self.assertIn("consent_version", content)
self.assertIn("consent_ts", content)
self.assertIn("external_ids", content) self.assertIn("external_ids", content)
# This key was removed intentionally. Ensure it is not accidentally re-included. # This key was removed intentionally. Ensure it is not accidentally re-included.

View file

@ -11,15 +11,18 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# 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.
from twisted.test.proto_helpers import MemoryReactor
from synapse.api.constants import UserTypes from synapse.api.constants import UserTypes
from synapse.api.errors import ThreepidValidationError from synapse.api.errors import ThreepidValidationError
from synapse.server import HomeServer
from synapse.util import Clock
from tests.unittest import HomeserverTestCase from tests.unittest import HomeserverTestCase
class RegistrationStoreTestCase(HomeserverTestCase): class RegistrationStoreTestCase(HomeserverTestCase):
def prepare(self, reactor, clock, hs): def prepare(self, reactor: MemoryReactor, clock: Clock, hs: HomeServer) -> None:
self.store = hs.get_datastores().main self.store = hs.get_datastores().main
self.user_id = "@my-user:test" self.user_id = "@my-user:test"
@ -27,7 +30,7 @@ class RegistrationStoreTestCase(HomeserverTestCase):
self.pwhash = "{xx1}123456789" self.pwhash = "{xx1}123456789"
self.device_id = "akgjhdjklgshg" self.device_id = "akgjhdjklgshg"
def test_register(self): def test_register(self) -> None:
self.get_success(self.store.register_user(self.user_id, self.pwhash)) self.get_success(self.store.register_user(self.user_id, self.pwhash))
self.assertEqual( self.assertEqual(
@ -38,6 +41,7 @@ class RegistrationStoreTestCase(HomeserverTestCase):
"admin": 0, "admin": 0,
"is_guest": 0, "is_guest": 0,
"consent_version": None, "consent_version": None,
"consent_ts": None,
"consent_server_notice_sent": None, "consent_server_notice_sent": None,
"appservice_id": None, "appservice_id": None,
"creation_ts": 0, "creation_ts": 0,
@ -48,7 +52,20 @@ class RegistrationStoreTestCase(HomeserverTestCase):
(self.get_success(self.store.get_user_by_id(self.user_id))), (self.get_success(self.store.get_user_by_id(self.user_id))),
) )
def test_add_tokens(self): def test_consent(self) -> None:
self.get_success(self.store.register_user(self.user_id, self.pwhash))
before_consent = self.clock.time_msec()
self.reactor.advance(5)
self.get_success(self.store.user_set_consent_version(self.user_id, "1"))
self.reactor.advance(5)
user = self.get_success(self.store.get_user_by_id(self.user_id))
assert user
self.assertEqual(user["consent_version"], "1")
self.assertGreater(user["consent_ts"], before_consent)
self.assertLess(user["consent_ts"], self.clock.time_msec())
def test_add_tokens(self) -> None:
self.get_success(self.store.register_user(self.user_id, self.pwhash)) self.get_success(self.store.register_user(self.user_id, self.pwhash))
self.get_success( self.get_success(
self.store.add_access_token_to_user( self.store.add_access_token_to_user(
@ -58,11 +75,12 @@ class RegistrationStoreTestCase(HomeserverTestCase):
result = self.get_success(self.store.get_user_by_access_token(self.tokens[1])) result = self.get_success(self.store.get_user_by_access_token(self.tokens[1]))
assert result
self.assertEqual(result.user_id, self.user_id) self.assertEqual(result.user_id, self.user_id)
self.assertEqual(result.device_id, self.device_id) self.assertEqual(result.device_id, self.device_id)
self.assertIsNotNone(result.token_id) self.assertIsNotNone(result.token_id)
def test_user_delete_access_tokens(self): def test_user_delete_access_tokens(self) -> None:
# add some tokens # add some tokens
self.get_success(self.store.register_user(self.user_id, self.pwhash)) self.get_success(self.store.register_user(self.user_id, self.pwhash))
self.get_success( self.get_success(
@ -87,6 +105,7 @@ class RegistrationStoreTestCase(HomeserverTestCase):
# check the one not associated with the device was not deleted # check the one not associated with the device was not deleted
user = self.get_success(self.store.get_user_by_access_token(self.tokens[0])) user = self.get_success(self.store.get_user_by_access_token(self.tokens[0]))
assert user
self.assertEqual(self.user_id, user.user_id) self.assertEqual(self.user_id, user.user_id)
# now delete the rest # now delete the rest
@ -95,11 +114,11 @@ class RegistrationStoreTestCase(HomeserverTestCase):
user = self.get_success(self.store.get_user_by_access_token(self.tokens[0])) user = self.get_success(self.store.get_user_by_access_token(self.tokens[0]))
self.assertIsNone(user, "access token was not deleted without device_id") self.assertIsNone(user, "access token was not deleted without device_id")
def test_is_support_user(self): def test_is_support_user(self) -> None:
TEST_USER = "@test:test" TEST_USER = "@test:test"
SUPPORT_USER = "@support:test" SUPPORT_USER = "@support:test"
res = self.get_success(self.store.is_support_user(None)) res = self.get_success(self.store.is_support_user(None)) # type: ignore[arg-type]
self.assertFalse(res) self.assertFalse(res)
self.get_success( self.get_success(
self.store.register_user(user_id=TEST_USER, password_hash=None) self.store.register_user(user_id=TEST_USER, password_hash=None)
@ -115,7 +134,7 @@ class RegistrationStoreTestCase(HomeserverTestCase):
res = self.get_success(self.store.is_support_user(SUPPORT_USER)) res = self.get_success(self.store.is_support_user(SUPPORT_USER))
self.assertTrue(res) self.assertTrue(res)
def test_3pid_inhibit_invalid_validation_session_error(self): def test_3pid_inhibit_invalid_validation_session_error(self) -> None:
"""Tests that enabling the configuration option to inhibit 3PID errors on """Tests that enabling the configuration option to inhibit 3PID errors on
/requestToken also inhibits validation errors caused by an unknown session ID. /requestToken also inhibits validation errors caused by an unknown session ID.
""" """