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

Commit 9549217

Browse files
authored
Refactor get_user_by_id (#16316)
1 parent 032cf84 commit 9549217

File tree

14 files changed

+108
-123
lines changed

14 files changed

+108
-123
lines changed

changelog.d/16316.misc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Refactor `get_user_by_id`.

synapse/api/auth/internal.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,7 @@ async def get_user_by_access_token(
268268
stored_user = await self.store.get_user_by_id(user_id)
269269
if not stored_user:
270270
raise InvalidClientTokenError("Unknown user_id %s" % user_id)
271-
if not stored_user["is_guest"]:
271+
if not stored_user.is_guest:
272272
raise InvalidClientTokenError(
273273
"Guest access token used for regular user"
274274
)

synapse/api/auth/msc3861_delegated.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -300,7 +300,7 @@ async def get_user_by_access_token(
300300
user_id = UserID(username, self._hostname)
301301

302302
# First try to find a user from the username claim
303-
user_info = await self.store.get_userinfo_by_id(user_id=user_id.to_string())
303+
user_info = await self.store.get_user_by_id(user_id=user_id.to_string())
304304
if user_info is None:
305305
# If the user does not exist, we should create it on the fly
306306
# TODO: we could use SCIM to provision users ahead of time and listen

synapse/handlers/account.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ async def _get_local_account_status(self, user_id: UserID) -> JsonDict:
102102
"""
103103
status = {"exists": False}
104104

105-
userinfo = await self._main_store.get_userinfo_by_id(user_id.to_string())
105+
userinfo = await self._main_store.get_user_by_id(user_id.to_string())
106106

107107
if userinfo is not None:
108108
status = {

synapse/handlers/admin.py

Lines changed: 22 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

1919
from synapse.api.constants import Direction, Membership
2020
from synapse.events import EventBase
21-
from synapse.types import JsonDict, RoomStreamToken, StateMap, UserID
21+
from synapse.types import JsonDict, RoomStreamToken, StateMap, UserID, UserInfo
2222
from synapse.visibility import filter_events_for_client
2323

2424
if TYPE_CHECKING:
@@ -57,38 +57,30 @@ async def get_whois(self, user: UserID) -> JsonDict:
5757

5858
async def get_user(self, user: UserID) -> Optional[JsonDict]:
5959
"""Function to get user details"""
60-
user_info_dict = await self._store.get_user_by_id(user.to_string())
61-
if user_info_dict is None:
60+
user_info: Optional[UserInfo] = await self._store.get_user_by_id(
61+
user.to_string()
62+
)
63+
if user_info is None:
6264
return None
6365

64-
# Restrict returned information to a known set of fields. This prevents additional
65-
# fields added to get_user_by_id from modifying Synapse's external API surface.
66-
user_info_to_return = {
67-
"name",
68-
"admin",
69-
"deactivated",
70-
"locked",
71-
"shadow_banned",
72-
"creation_ts",
73-
"appservice_id",
74-
"consent_server_notice_sent",
75-
"consent_version",
76-
"consent_ts",
77-
"user_type",
78-
"is_guest",
79-
"last_seen_ts",
66+
user_info_dict = {
67+
"name": user.to_string(),
68+
"admin": user_info.is_admin,
69+
"deactivated": user_info.is_deactivated,
70+
"locked": user_info.locked,
71+
"shadow_banned": user_info.is_shadow_banned,
72+
"creation_ts": user_info.creation_ts,
73+
"appservice_id": user_info.appservice_id,
74+
"consent_server_notice_sent": user_info.consent_server_notice_sent,
75+
"consent_version": user_info.consent_version,
76+
"consent_ts": user_info.consent_ts,
77+
"user_type": user_info.user_type,
78+
"is_guest": user_info.is_guest,
8079
}
8180

8281
if self._msc3866_enabled:
8382
# Only include the approved flag if support for MSC3866 is enabled.
84-
user_info_to_return.add("approved")
85-
86-
# Restrict returned keys to a known set.
87-
user_info_dict = {
88-
key: value
89-
for key, value in user_info_dict.items()
90-
if key in user_info_to_return
91-
}
83+
user_info_dict["approved"] = user_info.approved
9284

9385
# Add additional user metadata
9486
profile = await self._store.get_profileinfo(user)
@@ -105,6 +97,9 @@ async def get_user(self, user: UserID) -> Optional[JsonDict]:
10597
user_info_dict["external_ids"] = external_ids
10698
user_info_dict["erased"] = await self._store.is_user_erased(user.to_string())
10799

100+
last_seen_ts = await self._store.get_last_seen_for_user_id(user.to_string())
101+
user_info_dict["last_seen_ts"] = last_seen_ts
102+
108103
return user_info_dict
109104

110105
async def export_user_data(self, user_id: str, writer: "ExfiltrationWriter") -> Any:

synapse/handlers/message.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -828,13 +828,13 @@ async def assert_accepted_privacy_policy(self, requester: Requester) -> None:
828828

829829
u = await self.store.get_user_by_id(user_id)
830830
assert u is not None
831-
if u["user_type"] in (UserTypes.SUPPORT, UserTypes.BOT):
831+
if u.user_type in (UserTypes.SUPPORT, UserTypes.BOT):
832832
# support and bot users are not required to consent
833833
return
834-
if u["appservice_id"] is not None:
834+
if u.appservice_id is not None:
835835
# users registered by an appservice are exempt
836836
return
837-
if u["consent_version"] == self.config.consent.user_consent_version:
837+
if u.consent_version == self.config.consent.user_consent_version:
838838
return
839839

840840
consent_uri = self._consent_uri_builder.build_user_consent_uri(user.localpart)

synapse/module_api/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -572,7 +572,7 @@ async def get_userinfo_by_id(self, user_id: str) -> Optional[UserInfo]:
572572
Returns:
573573
UserInfo object if a user was found, otherwise None
574574
"""
575-
return await self._store.get_userinfo_by_id(user_id)
575+
return await self._store.get_user_by_id(user_id)
576576

577577
async def get_user_by_req(
578578
self,
@@ -1878,7 +1878,7 @@ async def put_global(
18781878
raise TypeError(f"new_data must be a dict; got {type(new_data).__name__}")
18791879

18801880
# Ensure the user exists, so we don't just write to users that aren't there.
1881-
if await self._store.get_userinfo_by_id(user_id) is None:
1881+
if await self._store.get_user_by_id(user_id) is None:
18821882
raise ValueError(f"User {user_id} does not exist on this server.")
18831883

18841884
await self._handler.add_account_data_for_user(user_id, data_type, new_data)

synapse/rest/consent/consent_resource.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ async def _async_render_GET(self, request: Request) -> None:
129129
if u is None:
130130
raise NotFoundError("Unknown user")
131131

132-
has_consented = u["consent_version"] == version
132+
has_consented = u.consent_version == version
133133
userhmac = userhmac_bytes.decode("ascii")
134134

135135
try:

synapse/server_notices/consent_server_notices.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,15 +79,15 @@ async def maybe_send_server_notice_to_user(self, user_id: str) -> None:
7979
if u is None:
8080
return
8181

82-
if u["is_guest"] and not self._send_to_guests:
82+
if u.is_guest and not self._send_to_guests:
8383
# don't send to guests
8484
return
8585

86-
if u["consent_version"] == self._current_consent_version:
86+
if u.consent_version == self._current_consent_version:
8787
# user has already consented
8888
return
8989

90-
if u["consent_server_notice_sent"] == self._current_consent_version:
90+
if u.consent_server_notice_sent == self._current_consent_version:
9191
# we've already sent a notice to the user
9292
return
9393

synapse/storage/databases/main/client_ips.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -764,3 +764,14 @@ async def get_user_ip_and_agents(
764764
}
765765

766766
return list(results.values())
767+
768+
async def get_last_seen_for_user_id(self, user_id: str) -> Optional[int]:
769+
"""Get the last seen timestamp for a user, if we have it."""
770+
771+
return await self.db_pool.simple_select_one_onecol(
772+
table="user_ips",
773+
keyvalues={"user_id": user_id},
774+
retcol="MAX(last_seen)",
775+
allow_none=True,
776+
desc="get_last_seen_for_user_id",
777+
)

synapse/storage/databases/main/registration.py

Lines changed: 22 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
import logging
1717
import random
1818
import re
19-
from typing import TYPE_CHECKING, Any, Dict, List, Mapping, Optional, Tuple, Union, cast
19+
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Union, cast
2020

2121
import attr
2222

@@ -192,8 +192,8 @@ def __init__(
192192
)
193193

194194
@cached()
195-
async def get_user_by_id(self, user_id: str) -> Optional[Mapping[str, Any]]:
196-
"""Deprecated: use get_userinfo_by_id instead"""
195+
async def get_user_by_id(self, user_id: str) -> Optional[UserInfo]:
196+
"""Returns info about the user account, if it exists."""
197197

198198
def get_user_by_id_txn(txn: LoggingTransaction) -> Optional[Dict[str, Any]]:
199199
# We could technically use simple_select_one here, but it would not perform
@@ -202,16 +202,12 @@ def get_user_by_id_txn(txn: LoggingTransaction) -> Optional[Dict[str, Any]]:
202202
txn.execute(
203203
"""
204204
SELECT
205-
name, password_hash, is_guest, admin, consent_version, consent_ts,
205+
name, is_guest, admin, consent_version, consent_ts,
206206
consent_server_notice_sent, appservice_id, creation_ts, user_type,
207207
deactivated, COALESCE(shadow_banned, FALSE) AS shadow_banned,
208208
COALESCE(approved, TRUE) AS approved,
209-
COALESCE(locked, FALSE) AS locked, last_seen_ts
209+
COALESCE(locked, FALSE) AS locked
210210
FROM users
211-
LEFT JOIN (
212-
SELECT user_id, MAX(last_seen) AS last_seen_ts
213-
FROM user_ips GROUP BY user_id
214-
) ls ON users.name = ls.user_id
215211
WHERE name = ?
216212
""",
217213
(user_id,),
@@ -228,51 +224,23 @@ def get_user_by_id_txn(txn: LoggingTransaction) -> Optional[Dict[str, Any]]:
228224
desc="get_user_by_id",
229225
func=get_user_by_id_txn,
230226
)
231-
232-
if row is not None:
233-
# If we're using SQLite our boolean values will be integers. Because we
234-
# present some of this data as is to e.g. server admins via REST APIs, we
235-
# want to make sure we're returning the right type of data.
236-
# Note: when adding a column name to this list, be wary of NULLable columns,
237-
# since NULL values will be turned into False.
238-
boolean_columns = [
239-
"admin",
240-
"deactivated",
241-
"shadow_banned",
242-
"approved",
243-
"locked",
244-
]
245-
for column in boolean_columns:
246-
row[column] = bool(row[column])
247-
248-
return row
249-
250-
async def get_userinfo_by_id(self, user_id: str) -> Optional[UserInfo]:
251-
"""Get a UserInfo object for a user by user ID.
252-
253-
Note! Currently uses the cache of `get_user_by_id`. Once that deprecated method is removed,
254-
this method should be cached.
255-
256-
Args:
257-
user_id: The user to fetch user info for.
258-
Returns:
259-
`UserInfo` object if user found, otherwise `None`.
260-
"""
261-
user_data = await self.get_user_by_id(user_id)
262-
if not user_data:
227+
if row is None:
263228
return None
229+
264230
return UserInfo(
265-
appservice_id=user_data["appservice_id"],
266-
consent_server_notice_sent=user_data["consent_server_notice_sent"],
267-
consent_version=user_data["consent_version"],
268-
creation_ts=user_data["creation_ts"],
269-
is_admin=bool(user_data["admin"]),
270-
is_deactivated=bool(user_data["deactivated"]),
271-
is_guest=bool(user_data["is_guest"]),
272-
is_shadow_banned=bool(user_data["shadow_banned"]),
273-
user_id=UserID.from_string(user_data["name"]),
274-
user_type=user_data["user_type"],
275-
last_seen_ts=user_data["last_seen_ts"],
231+
appservice_id=row["appservice_id"],
232+
consent_server_notice_sent=row["consent_server_notice_sent"],
233+
consent_version=row["consent_version"],
234+
consent_ts=row["consent_ts"],
235+
creation_ts=row["creation_ts"],
236+
is_admin=bool(row["admin"]),
237+
is_deactivated=bool(row["deactivated"]),
238+
is_guest=bool(row["is_guest"]),
239+
is_shadow_banned=bool(row["shadow_banned"]),
240+
user_id=UserID.from_string(row["name"]),
241+
user_type=row["user_type"],
242+
approved=bool(row["approved"]),
243+
locked=bool(row["locked"]),
276244
)
277245

278246
async def is_trial_user(self, user_id: str) -> bool:
@@ -290,10 +258,10 @@ async def is_trial_user(self, user_id: str) -> bool:
290258

291259
now = self._clock.time_msec()
292260
days = self.config.server.mau_appservice_trial_days.get(
293-
info["appservice_id"], self.config.server.mau_trial_days
261+
info.appservice_id, self.config.server.mau_trial_days
294262
)
295263
trial_duration_ms = days * 24 * 60 * 60 * 1000
296-
is_trial = (now - info["creation_ts"] * 1000) < trial_duration_ms
264+
is_trial = (now - info.creation_ts * 1000) < trial_duration_ms
297265
return is_trial
298266

299267
@cached()

synapse/types/__init__.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -933,33 +933,37 @@ def get_verify_key_from_cross_signing_key(
933933

934934
@attr.s(auto_attribs=True, frozen=True, slots=True)
935935
class UserInfo:
936-
"""Holds information about a user. Result of get_userinfo_by_id.
936+
"""Holds information about a user. Result of get_user_by_id.
937937
938938
Attributes:
939939
user_id: ID of the user.
940940
appservice_id: Application service ID that created this user.
941941
consent_server_notice_sent: Version of policy documents the user has been sent.
942942
consent_version: Version of policy documents the user has consented to.
943+
consent_ts: Time the user consented
943944
creation_ts: Creation timestamp of the user.
944945
is_admin: True if the user is an admin.
945946
is_deactivated: True if the user has been deactivated.
946947
is_guest: True if the user is a guest user.
947948
is_shadow_banned: True if the user has been shadow-banned.
948949
user_type: User type (None for normal user, 'support' and 'bot' other options).
949-
last_seen_ts: Last activity timestamp of the user.
950+
approved: If the user has been "approved" to register on the server.
951+
locked: Whether the user's account has been locked
950952
"""
951953

952954
user_id: UserID
953955
appservice_id: Optional[int]
954956
consent_server_notice_sent: Optional[str]
955957
consent_version: Optional[str]
958+
consent_ts: Optional[int]
956959
user_type: Optional[str]
957960
creation_ts: int
958961
is_admin: bool
959962
is_deactivated: bool
960963
is_guest: bool
961964
is_shadow_banned: bool
962-
last_seen_ts: Optional[int]
965+
approved: bool
966+
locked: bool
963967

964968

965969
class UserProfile(TypedDict):

tests/api/test_auth.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -188,8 +188,11 @@ def test_get_user_by_req_appservice_valid_token_valid_user_id(self) -> None:
188188
)
189189
app_service.is_interested_in_user = Mock(return_value=True)
190190
self.store.get_app_service_by_token = Mock(return_value=app_service)
191-
# This just needs to return a truth-y value.
192-
self.store.get_user_by_id = AsyncMock(return_value={"is_guest": False})
191+
192+
class FakeUserInfo:
193+
is_guest = False
194+
195+
self.store.get_user_by_id = AsyncMock(return_value=FakeUserInfo())
193196
self.store.get_user_by_access_token = AsyncMock(return_value=None)
194197

195198
request = Mock(args={})
@@ -341,7 +344,10 @@ def test_get_user_from_macaroon(self) -> None:
341344
)
342345

343346
def test_get_guest_user_from_macaroon(self) -> None:
344-
self.store.get_user_by_id = AsyncMock(return_value={"is_guest": True})
347+
class FakeUserInfo:
348+
is_guest = True
349+
350+
self.store.get_user_by_id = AsyncMock(return_value=FakeUserInfo())
345351
self.store.get_user_by_access_token = AsyncMock(return_value=None)
346352

347353
user_id = "@baldrick:matrix.org"

0 commit comments

Comments
 (0)