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

Commit c2000ab

Browse files
authored
Add get_userinfo_by_id method to ModuleApi (#9581)
Makes it easier to fetch user details in for example spam checker modules, without needing to use api._store or figure out database interactions. Signed-off-by: Jason Robinson <[email protected]>
1 parent 72935b7 commit c2000ab

File tree

5 files changed

+80
-2
lines changed

5 files changed

+80
-2
lines changed

changelog.d/9581.feature

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add `get_userinfo_by_id` method to ModuleApi.

synapse/module_api/__init__.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
from synapse.storage.database import DatabasePool, LoggingTransaction
4646
from synapse.storage.databases.main.roommember import ProfileInfo
4747
from synapse.storage.state import StateFilter
48-
from synapse.types import JsonDict, Requester, UserID, create_requester
48+
from synapse.types import JsonDict, Requester, UserID, UserInfo, create_requester
4949
from synapse.util import Clock
5050
from synapse.util.caches.descriptors import cached
5151

@@ -174,6 +174,16 @@ def email_app_name(self) -> str:
174174
"""The application name configured in the homeserver's configuration."""
175175
return self._hs.config.email.email_app_name
176176

177+
async def get_userinfo_by_id(self, user_id: str) -> Optional[UserInfo]:
178+
"""Get user info by user_id
179+
180+
Args:
181+
user_id: Fully qualified user id.
182+
Returns:
183+
UserInfo object if a user was found, otherwise None
184+
"""
185+
return await self._store.get_userinfo_by_id(user_id)
186+
177187
async def get_user_by_req(
178188
self,
179189
req: SynapseRequest,

synapse/storage/databases/main/registration.py

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
from synapse.storage.types import Connection, Cursor
3030
from synapse.storage.util.id_generators import IdGenerator
3131
from synapse.storage.util.sequence import build_sequence_generator
32-
from synapse.types import UserID
32+
from synapse.types import UserID, UserInfo
3333
from synapse.util.caches.descriptors import cached
3434

3535
if TYPE_CHECKING:
@@ -146,6 +146,7 @@ def __init__(
146146

147147
@cached()
148148
async def get_user_by_id(self, user_id: str) -> Optional[Dict[str, Any]]:
149+
"""Deprecated: use get_userinfo_by_id instead"""
149150
return await self.db_pool.simple_select_one(
150151
table="users",
151152
keyvalues={"name": user_id},
@@ -166,6 +167,33 @@ async def get_user_by_id(self, user_id: str) -> Optional[Dict[str, Any]]:
166167
desc="get_user_by_id",
167168
)
168169

170+
async def get_userinfo_by_id(self, user_id: str) -> Optional[UserInfo]:
171+
"""Get a UserInfo object for a user by user ID.
172+
173+
Note! Currently uses the cache of `get_user_by_id`. Once that deprecated method is removed,
174+
this method should be cached.
175+
176+
Args:
177+
user_id: The user to fetch user info for.
178+
Returns:
179+
`UserInfo` object if user found, otherwise `None`.
180+
"""
181+
user_data = await self.get_user_by_id(user_id)
182+
if not user_data:
183+
return None
184+
return UserInfo(
185+
appservice_id=user_data["appservice_id"],
186+
consent_server_notice_sent=user_data["consent_server_notice_sent"],
187+
consent_version=user_data["consent_version"],
188+
creation_ts=user_data["creation_ts"],
189+
is_admin=bool(user_data["admin"]),
190+
is_deactivated=bool(user_data["deactivated"]),
191+
is_guest=bool(user_data["is_guest"]),
192+
is_shadow_banned=bool(user_data["shadow_banned"]),
193+
user_id=UserID.from_string(user_data["name"]),
194+
user_type=user_data["user_type"],
195+
)
196+
169197
async def is_trial_user(self, user_id: str) -> bool:
170198
"""Checks if user is in the "trial" period, i.e. within the first
171199
N days of registration defined by `mau_trial_days` config

synapse/types.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -751,3 +751,32 @@ def get_verify_key_from_cross_signing_key(key_info):
751751
# and return that one key
752752
for key_id, key_data in keys.items():
753753
return (key_id, decode_verify_key_bytes(key_id, decode_base64(key_data)))
754+
755+
756+
@attr.s(auto_attribs=True, frozen=True, slots=True)
757+
class UserInfo:
758+
"""Holds information about a user. Result of get_userinfo_by_id.
759+
760+
Attributes:
761+
user_id: ID of the user.
762+
appservice_id: Application service ID that created this user.
763+
consent_server_notice_sent: Version of policy documents the user has been sent.
764+
consent_version: Version of policy documents the user has consented to.
765+
creation_ts: Creation timestamp of the user.
766+
is_admin: True if the user is an admin.
767+
is_deactivated: True if the user has been deactivated.
768+
is_guest: True if the user is a guest user.
769+
is_shadow_banned: True if the user has been shadow-banned.
770+
user_type: User type (None for normal user, 'support' and 'bot' other options).
771+
"""
772+
773+
user_id: UserID
774+
appservice_id: Optional[int]
775+
consent_server_notice_sent: Optional[str]
776+
consent_version: Optional[str]
777+
user_type: Optional[str]
778+
creation_ts: int
779+
is_admin: bool
780+
is_deactivated: bool
781+
is_guest: bool
782+
is_shadow_banned: bool

tests/module_api/test_api.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,16 @@ def test_can_register_user(self):
7979
displayname = self.get_success(self.store.get_profile_displayname("bob"))
8080
self.assertEqual(displayname, "Bobberino")
8181

82+
def test_get_userinfo_by_id(self):
83+
user_id = self.register_user("alice", "1234")
84+
found_user = self.get_success(self.module_api.get_userinfo_by_id(user_id))
85+
self.assertEqual(found_user.user_id.to_string(), user_id)
86+
self.assertIdentical(found_user.is_admin, False)
87+
88+
def test_get_userinfo_by_id__no_user_found(self):
89+
found_user = self.get_success(self.module_api.get_userinfo_by_id("@alice:test"))
90+
self.assertIsNone(found_user)
91+
8292
def test_sending_events_into_room(self):
8393
"""Tests that a module can send events into a room"""
8494
# Mock out create_and_send_nonmember_event to check whether events are being sent

0 commit comments

Comments
 (0)