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

Add get_userinfo_by_id method to ModuleApi #9581

Merged
merged 17 commits into from
Aug 4, 2021
Merged
Show file tree
Hide file tree
Changes from 16 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/9581.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add `get_userinfo_by_id` method to ModuleApi. `UserInfo` is a new type returned from the registration store. This also deprecates the `get_user_by_id` method on the registration store.
jaywink marked this conversation as resolved.
Show resolved Hide resolved
12 changes: 11 additions & 1 deletion synapse/module_api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
from synapse.storage.database import DatabasePool, LoggingTransaction
from synapse.storage.databases.main.roommember import ProfileInfo
from synapse.storage.state import StateFilter
from synapse.types import JsonDict, Requester, UserID, create_requester
from synapse.types import JsonDict, Requester, UserID, UserInfo, create_requester
from synapse.util import Clock
from synapse.util.caches.descriptors import cached

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

async def get_userinfo_by_id(self, user_id: str) -> Optional[UserInfo]:
"""Get user info by user_id

Args:
user_id: Fully qualified user id.
Returns:
UserInfo object if a user was found, otherwise None
"""
return await self._store.get_userinfo_by_id(user_id)

async def get_user_by_req(
self,
req: SynapseRequest,
Expand Down
30 changes: 29 additions & 1 deletion synapse/storage/databases/main/registration.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
from synapse.storage.types import Connection, Cursor
from synapse.storage.util.id_generators import IdGenerator
from synapse.storage.util.sequence import build_sequence_generator
from synapse.types import UserID
from synapse.types import UserID, UserInfo
from synapse.util.caches.descriptors import cached

if TYPE_CHECKING:
Expand Down Expand Up @@ -146,6 +146,7 @@ def __init__(

@cached()
async def get_user_by_id(self, user_id: str) -> Optional[Dict[str, Any]]:
"""Deprecated: use get_userinfo_by_id instead"""
return await self.db_pool.simple_select_one(
table="users",
keyvalues={"name": user_id},
Expand All @@ -166,6 +167,33 @@ async def get_user_by_id(self, user_id: str) -> Optional[Dict[str, Any]]:
desc="get_user_by_id",
)

async def get_userinfo_by_id(self, user_id: str) -> Optional[UserInfo]:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please can you give this a docstring?

Also, please can you stick a comment or docstring on get_user_by_id to say it's deprecated and replaced by get_userinfo_by_id?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably best deprecate _store.get_user_by_id in a follow-up pr, I'm happy to do that?

Oh I just read this. I'd roll it into this PR, since it's simple enough.

Unless you were talking about actually replacing uses of get_user_by_id with get_userinfo_by_id? Which would be amazing if you've got time, and yes a separate PR would be best.

"""Get a UserInfo object for a user by user ID.

Note! Currently uses the cache of `get_user_by_id`. Once that deprecated method is removed,
this method should be cached.

Args:
user_id: The user to fetch user info for.
Returns:
`UserInfo` object if user found, otherwise `None`.
"""
user_data = await self.get_user_by_id(user_id)
if not user_data:
return None
return UserInfo(
appservice_id=user_data["appservice_id"],
consent_server_notice_sent=user_data["consent_server_notice_sent"],
consent_version=user_data["consent_version"],
creation_ts=user_data["creation_ts"],
is_admin=bool(user_data["admin"]),
is_deactivated=bool(user_data["deactivated"]),
is_guest=bool(user_data["is_guest"]),
is_shadow_banned=bool(user_data["shadow_banned"]),
user_id=UserID.from_string(user_data["name"]),
user_type=user_data["user_type"],
)

async def is_trial_user(self, user_id: str) -> bool:
"""Checks if user is in the "trial" period, i.e. within the first
N days of registration defined by `mau_trial_days` config
Expand Down
29 changes: 29 additions & 0 deletions synapse/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -751,3 +751,32 @@ def get_verify_key_from_cross_signing_key(key_info):
# and return that one key
for key_id, key_data in keys.items():
return (key_id, decode_verify_key_bytes(key_id, decode_base64(key_data)))


@attr.s(auto_attribs=True, frozen=True, slots=True)
class UserInfo:
"""Holds information about a user. Result of get_userinfo_by_id.

Attributes:
user_id: ID of the user.
appservice_id: Application service ID that created this user.
consent_server_notice_sent: Version of policy documents the user has been sent.
consent_version: Version of policy documents the user has consented to.
creation_ts: Creation timestamp of the user.
is_admin: True if the user is an admin.
is_deactivated: True if the user has been deactivated.
is_guest: True if the user is a guest user.
is_shadow_banned: True if the user has been shadow-banned.
user_type: User type (None for normal user, 'support' and 'bot' other options).
"""

user_id: UserID
appservice_id: Optional[int]
consent_server_notice_sent: Optional[str]
consent_version: Optional[str]
user_type: Optional[str]
creation_ts: int
is_admin: bool
is_deactivated: bool
is_guest: bool
is_shadow_banned: bool
10 changes: 10 additions & 0 deletions tests/module_api/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,16 @@ def test_can_register_user(self):
displayname = self.get_success(self.store.get_profile_displayname("bob"))
self.assertEqual(displayname, "Bobberino")

def test_get_userinfo_by_id(self):
user_id = self.register_user("alice", "1234")
found_user = self.get_success(self.module_api.get_userinfo_by_id(user_id))
self.assertEqual(found_user.user_id.to_string(), user_id)
self.assertIdentical(found_user.is_admin, False)

def test_get_userinfo_by_id__no_user_found(self):
found_user = self.get_success(self.module_api.get_userinfo_by_id("@alice:test"))
self.assertIsNone(found_user)

def test_sending_events_into_room(self):
"""Tests that a module can send events into a room"""
# Mock out create_and_send_nonmember_event to check whether events are being sent
Expand Down