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

Add timestamp to user's consent #13741

Merged
merged 4 commits into from
Sep 8, 2022
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/13741.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Documents the timestamp when a user accepts the consent, if [consent tracking](https://matrix-org.github.io/synapse/latest/consent_tracking.html) is used.
reivilibre marked this conversation as resolved.
Show resolved Hide resolved
2 changes: 2 additions & 0 deletions docs/admin_api/user_admin_api.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ It returns a JSON body like the following:
"appservice_id": null,
"consent_server_notice_sent": null,
"consent_version": null,
"consent_ts": null,
"external_ids": [
{
"auth_provider": "<provider1>",
Expand Down Expand Up @@ -364,6 +365,7 @@ The following actions are **NOT** performed. The list may be incomplete.
- Remove the user's creation (registration) timestamp
- [Remove rate limit overrides](#override-ratelimiting-for-users)
- Remove from monthly active users
- Remove user's consent information (consent version and timestamp)

## Reset password

Expand Down
1 change: 1 addition & 0 deletions synapse/handlers/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ async def get_user(self, user: UserID) -> Optional[JsonDict]:
"appservice_id",
"consent_server_notice_sent",
"consent_version",
"consent_ts",
"user_type",
"is_guest",
}
Expand Down
6 changes: 5 additions & 1 deletion synapse/storage/databases/main/registration.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ async def get_user_by_id(self, user_id: str) -> Optional[Dict[str, Any]]:
"is_guest",
"admin",
"consent_version",
"consent_ts",
"consent_server_notice_sent",
"appservice_id",
"creation_ts",
Expand Down Expand Up @@ -2227,7 +2228,10 @@ def f(txn: LoggingTransaction) -> None:
txn,
table="users",
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,))

Expand Down
Original file line number Diff line number Diff line change
@@ -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;
1 change: 1 addition & 0 deletions tests/rest/admin/test_user.py
Original file line number Diff line number Diff line change
Expand Up @@ -2580,6 +2580,7 @@ def _check_fields(self, content: JsonDict) -> None:
self.assertIn("appservice_id", content)
self.assertIn("consent_server_notice_sent", content)
self.assertIn("consent_version", content)
self.assertIn("consent_ts", content)
self.assertIn("external_ids", content)

# This key was removed intentionally. Ensure it is not accidentally re-included.
Expand Down
33 changes: 26 additions & 7 deletions tests/storage/test_registration.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,26 @@
# 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.
from twisted.test.proto_helpers import MemoryReactor

from synapse.api.constants import UserTypes
from synapse.api.errors import ThreepidValidationError
from synapse.server import HomeServer
from synapse.util import Clock

from tests.unittest import 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.user_id = "@my-user:test"
self.tokens = ["AbCdEfGhIjKlMnOpQrStUvWxYz", "BcDeFgHiJkLmNoPqRsTuVwXyZa"]
self.pwhash = "{xx1}123456789"
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.assertEqual(
Expand All @@ -38,6 +41,7 @@ def test_register(self):
"admin": 0,
"is_guest": 0,
"consent_version": None,
"consent_ts": None,
"consent_server_notice_sent": None,
"appservice_id": None,
"creation_ts": 0,
Expand All @@ -48,7 +52,20 @@ def test_register(self):
(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.add_access_token_to_user(
Expand All @@ -58,11 +75,12 @@ def test_add_tokens(self):

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.device_id, self.device_id)
self.assertIsNotNone(result.token_id)

def test_user_delete_access_tokens(self):
def test_user_delete_access_tokens(self) -> None:
# add some tokens
self.get_success(self.store.register_user(self.user_id, self.pwhash))
self.get_success(
Expand All @@ -87,6 +105,7 @@ def test_user_delete_access_tokens(self):

# 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]))
assert user
self.assertEqual(self.user_id, user.user_id)

# now delete the rest
Expand All @@ -95,11 +114,11 @@ def test_user_delete_access_tokens(self):
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")

def test_is_support_user(self):
def test_is_support_user(self) -> None:
TEST_USER = "@test: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.get_success(
self.store.register_user(user_id=TEST_USER, password_hash=None)
Expand All @@ -115,7 +134,7 @@ def test_is_support_user(self):
res = self.get_success(self.store.is_support_user(SUPPORT_USER))
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
/requestToken also inhibits validation errors caused by an unknown session ID.
"""
Expand Down