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

Fix exception thrown when attempting to add an appservice sender to user_directory #11026

Closed
1 change: 1 addition & 0 deletions changelog.d/11026.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Exclude application service sender membership from user directory to fix a bug stopping the user directory from being populated.
14 changes: 13 additions & 1 deletion synapse/storage/databases/main/user_directory.py
Original file line number Diff line number Diff line change
Expand Up @@ -383,7 +383,19 @@ async def should_include_local_user_in_dir(self, user: str) -> bool:
"""Certain classes of local user are omitted from the user directory.
Is this user one of them?
"""
# App service users aren't usually contactable, so exclude them.
# We're opting to exclude the appservice sender (user defined by the
# `sender_localpart` in the appservice registration) even though
# technically it could be DM-able. In the future, this could potentially
# be configurable per-appservice whether the appservice sender can be
# contacted.
if self.get_app_service_by_user_id(user) is not None:
return False
DMRobertson marked this conversation as resolved.
Show resolved Hide resolved

DMRobertson marked this conversation as resolved.
Show resolved Hide resolved
# We're opting to exclude appservice users (anyone matching the user
# namespace regex in the appservice registration) even though technically
# they could be DM-able. In the future, this could potentially
# be configurable per-appservice whether the appservice users can be
# contacted.
if self.get_if_app_services_interested_in_user(user):
# TODO we might want to make this configurable for each app service
return False
Expand Down
44 changes: 38 additions & 6 deletions tests/handlers/test_user_directory.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,9 @@ def make_homeserver(self, reactor: MemoryReactor, clock: Clock) -> HomeServer:
hostname="test",
id="1234",
namespaces={"users": [{"regex": r"@as_user.*", "exclusive": True}]},
sender="@as:test",
# Note: this user does not match the regex above, so that tests
# can distinguish the sender from the AS user.
sender="@as_main:test",
)

mock_load_appservices = Mock(return_value=[self.appservice])
Expand Down Expand Up @@ -179,6 +181,15 @@ def test_excludes_appservices_user(self) -> None:
)
self._check_only_one_user_in_directory(user, public)

def test_excludes_appservice_sender(self) -> None:
user = self.register_user("user", "pass")
token = self.login(user, "pass")
room = self.helper.create_room_as(
self.appservice.sender, is_public=True, tok=self.appservice.token
)
self.helper.join(room, user, tok=token)
self._check_only_one_user_in_directory(user, room)

def _create_rooms_and_inject_memberships(
self, creator: str, token: str, joiner: str
) -> Tuple[str, str]:
Expand Down Expand Up @@ -230,7 +241,7 @@ def test_handle_local_profile_change_with_support_user(self) -> None:
)
)
profile = self.get_success(self.store.get_user_in_directory(support_user_id))
self.assertTrue(profile is None)
self.assertIsNone(profile)
display_name = "display_name"

profile_info = ProfileInfo(avatar_url="avatar_url", display_name=display_name)
Expand Down Expand Up @@ -264,7 +275,7 @@ def test_handle_local_profile_change_with_deactivated_user(self) -> None:

# profile is not in directory
profile = self.get_success(self.store.get_user_in_directory(r_user_id))
self.assertTrue(profile is None)
self.assertIsNone(profile)

# update profile after deactivation
self.get_success(
Expand All @@ -273,7 +284,7 @@ def test_handle_local_profile_change_with_deactivated_user(self) -> None:

# profile is furthermore not in directory
profile = self.get_success(self.store.get_user_in_directory(r_user_id))
self.assertTrue(profile is None)
self.assertIsNone(profile)

def test_handle_local_profile_change_with_appservice_user(self) -> None:
# create user
Expand All @@ -283,7 +294,7 @@ def test_handle_local_profile_change_with_appservice_user(self) -> None:

# profile is not in directory
profile = self.get_success(self.store.get_user_in_directory(as_user_id))
self.assertTrue(profile is None)
self.assertIsNone(profile)

# update profile
profile_info = ProfileInfo(avatar_url="avatar_url", display_name="4L1c3")
Expand All @@ -293,7 +304,28 @@ def test_handle_local_profile_change_with_appservice_user(self) -> None:

# profile is still not in directory
profile = self.get_success(self.store.get_user_in_directory(as_user_id))
self.assertTrue(profile is None)
self.assertIsNone(profile)

def test_handle_local_profile_change_with_appservice_sender(self) -> None:
# profile is not in directory
profile = self.get_success(
self.store.get_user_in_directory(self.appservice.sender)
)
self.assertIsNone(profile)

# update profile
profile_info = ProfileInfo(avatar_url="avatar_url", display_name="4L1c3")
self.get_success(
self.handler.handle_local_profile_change(
self.appservice.sender, profile_info
)
)

# profile is still not in directory
profile = self.get_success(
self.store.get_user_in_directory(self.appservice.sender)
)
self.assertIsNone(profile)

def test_handle_user_deactivated_support_user(self) -> None:
s_user_id = "@support:test"
Expand Down
15 changes: 15 additions & 0 deletions tests/storage/test_user_directory.py
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,21 @@ def test_population_excludes_appservice_user(self) -> None:
# Check the AS user is not in the directory.
self._check_room_sharing_tables(user, public, private)

def test_population_excludes_appservice_sender(self) -> None:
user = self.register_user("user", "pass")
token = self.login(user, "pass")

# Join the AS sender to rooms owned by the normal user.
public, private = self._create_rooms_and_inject_memberships(
user, token, self.appservice.sender
)

# Rebuild the directory.
self._purge_and_rebuild_user_dir()

# Check the AS sender is not in the directory.
self._check_room_sharing_tables(user, public, private)

def test_population_conceals_private_nickname(self) -> None:
# Make a private room, and set a nickname within
user = self.register_user("aaaa", "pass")
Expand Down