Compare commits
8 Commits
v1.140.0rc
...
dmr/user-d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
18df1c142a | ||
|
|
3303dda8a8 | ||
|
|
cd8ecee5e3 | ||
|
|
79059f4e1e | ||
|
|
cf0f93860c | ||
|
|
0391b5ca15 | ||
|
|
5e99901ab4 | ||
|
|
aa4f0fe96a |
@@ -196,63 +196,12 @@ class UserDirectoryHandler(StateDeltasHandler):
|
||||
room_id, prev_event_id, event_id, typ
|
||||
)
|
||||
elif typ == EventTypes.Member:
|
||||
change = await self._get_key_change(
|
||||
await self._handle_room_membership_event(
|
||||
room_id,
|
||||
prev_event_id,
|
||||
event_id,
|
||||
key_name="membership",
|
||||
public_value=Membership.JOIN,
|
||||
state_key,
|
||||
)
|
||||
|
||||
is_remote = not self.is_mine_id(state_key)
|
||||
if change is MatchChange.now_false:
|
||||
# Need to check if the server left the room entirely, if so
|
||||
# we might need to remove all the users in that room
|
||||
is_in_room = await self.store.is_host_joined(
|
||||
room_id, self.server_name
|
||||
)
|
||||
if not is_in_room:
|
||||
logger.debug("Server left room: %r", room_id)
|
||||
# Fetch all the users that we marked as being in user
|
||||
# directory due to being in the room and then check if
|
||||
# need to remove those users or not
|
||||
user_ids = await self.store.get_users_in_dir_due_to_room(
|
||||
room_id
|
||||
)
|
||||
|
||||
for user_id in user_ids:
|
||||
await self._handle_remove_user(room_id, user_id)
|
||||
continue
|
||||
else:
|
||||
logger.debug("Server is still in room: %r", room_id)
|
||||
|
||||
include_in_dir = (
|
||||
is_remote
|
||||
or await self.store.should_include_local_user_in_dir(state_key)
|
||||
)
|
||||
if include_in_dir:
|
||||
if change is MatchChange.no_change:
|
||||
# Handle any profile changes for remote users.
|
||||
# (For local users we are not forced to scan membership
|
||||
# events; instead the rest of the application calls
|
||||
# `handle_local_profile_change`.)
|
||||
if is_remote:
|
||||
await self._handle_profile_change(
|
||||
state_key, room_id, prev_event_id, event_id
|
||||
)
|
||||
continue
|
||||
|
||||
if change is MatchChange.now_true: # The user joined
|
||||
# This may be the first time we've seen a remote user. If
|
||||
# so, ensure we have a directory entry for them. (We don't
|
||||
# need to do this for local users: their directory entry
|
||||
# is created at the point of registration.
|
||||
if is_remote:
|
||||
await self._upsert_directory_entry_for_remote_user(
|
||||
state_key, event_id
|
||||
)
|
||||
await self._track_user_joined_room(room_id, state_key)
|
||||
else: # The user left
|
||||
await self._handle_remove_user(room_id, state_key)
|
||||
else:
|
||||
logger.debug("Ignoring irrelevant type: %r", typ)
|
||||
|
||||
@@ -326,6 +275,68 @@ class UserDirectoryHandler(StateDeltasHandler):
|
||||
for user_id in users_in_room:
|
||||
await self._track_user_joined_room(room_id, user_id)
|
||||
|
||||
async def _handle_room_membership_event(
|
||||
self,
|
||||
room_id: str,
|
||||
prev_event_id: str,
|
||||
event_id: str,
|
||||
state_key: str,
|
||||
) -> None:
|
||||
joined = await self._get_key_change(
|
||||
prev_event_id,
|
||||
event_id,
|
||||
key_name="membership",
|
||||
public_value=Membership.JOIN,
|
||||
)
|
||||
# We have to do two things:
|
||||
# 1. Update the room-sharing tables.
|
||||
# This applies to remote users and non-excluded local users.
|
||||
# 2. Update the user_directory and user_directory_search tables.
|
||||
# This applies to remote users only, because we only become aware of
|
||||
# the (and any profile changes) by listening to these events.
|
||||
# The rest of the application knows exactly when local users are
|
||||
# created or their profile changed---it will directly call methods
|
||||
# on this class.
|
||||
|
||||
# Both cases ignore excluded local users, so start by discarding them.
|
||||
is_remote = not self.is_mine_id(state_key)
|
||||
if not is_remote and not await self.store.should_include_local_user_in_dir(
|
||||
state_key
|
||||
):
|
||||
return
|
||||
|
||||
if joined is MatchChange.now_false:
|
||||
# Need to check if the server left the room entirely, if so
|
||||
# we might need to remove all the users in that room
|
||||
is_in_room = await self.store.is_host_joined(room_id, self.server_name)
|
||||
if not is_in_room:
|
||||
logger.debug("Server left room: %r", room_id)
|
||||
# Fetch all the users that we marked as being in user
|
||||
# directory due to being in the room and then check if
|
||||
# need to remove those users or not
|
||||
user_ids = await self.store.get_users_in_dir_due_to_room(room_id)
|
||||
|
||||
for user_id in user_ids:
|
||||
await self._handle_remove_user(room_id, user_id)
|
||||
else:
|
||||
logger.debug("Server is still in room: %r", room_id)
|
||||
await self._handle_remove_user(room_id, state_key)
|
||||
elif joined is MatchChange.no_change:
|
||||
# Handle any profile changes for remote users.
|
||||
# (For local users the rest of the application calls
|
||||
# `handle_local_profile_change`.)
|
||||
if is_remote:
|
||||
await self._handle_possible_remote_profile_change(
|
||||
state_key, room_id, prev_event_id, event_id
|
||||
)
|
||||
elif joined is MatchChange.now_true: # The user joined
|
||||
# This may be the first time we've seen a remote user. If
|
||||
# so, ensure we have a directory entry for them. (For local users,
|
||||
# the rest of the application calls `handle_local_profile_change`.)
|
||||
if is_remote:
|
||||
await self._upsert_directory_entry_for_remote_user(state_key, event_id)
|
||||
await self._track_user_joined_room(room_id, state_key)
|
||||
|
||||
async def _upsert_directory_entry_for_remote_user(
|
||||
self, user_id: str, event_id: str
|
||||
) -> None:
|
||||
@@ -386,7 +397,12 @@ class UserDirectoryHandler(StateDeltasHandler):
|
||||
await self.store.add_users_who_share_private_room(room_id, to_insert)
|
||||
|
||||
async def _handle_remove_user(self, room_id: str, user_id: str) -> None:
|
||||
"""Called when we might need to remove user from directory
|
||||
"""Called when when someone leaves a room. The user may be local or remote.
|
||||
|
||||
(If the person who left was the last local user in this room, the server
|
||||
is no longer in the room. We call this function to forget that the remaining
|
||||
remote users are in the room, even though they haven't left. So the name is
|
||||
a little misleading!)
|
||||
|
||||
Args:
|
||||
room_id: The room ID that user left or stopped being public that
|
||||
@@ -397,13 +413,15 @@ class UserDirectoryHandler(StateDeltasHandler):
|
||||
# Remove user from sharing tables
|
||||
await self.store.remove_user_who_share_room(user_id, room_id)
|
||||
|
||||
# Are they still in any rooms? If not, remove them entirely.
|
||||
rooms_user_is_in = await self.store.get_user_dir_rooms_user_is_in(user_id)
|
||||
# If they're a remote user and not in any rooms we can see,
|
||||
# remove their user_directory entry.
|
||||
if not self.is_mine_id(user_id):
|
||||
rooms_user_is_in = await self.store.get_user_dir_rooms_user_is_in(user_id)
|
||||
|
||||
if len(rooms_user_is_in) == 0:
|
||||
await self.store.remove_from_user_dir(user_id)
|
||||
if len(rooms_user_is_in) == 0:
|
||||
await self.store.remove_from_user_dir(user_id)
|
||||
|
||||
async def _handle_profile_change(
|
||||
async def _handle_possible_remote_profile_change(
|
||||
self,
|
||||
user_id: str,
|
||||
room_id: str,
|
||||
@@ -411,7 +429,8 @@ class UserDirectoryHandler(StateDeltasHandler):
|
||||
event_id: Optional[str],
|
||||
) -> None:
|
||||
"""Check member event changes for any profile changes and update the
|
||||
database if there are.
|
||||
database if there are. This is intended for remote users only. The caller
|
||||
is responsible for checking that the given user is remote.
|
||||
"""
|
||||
if not prev_event_id or not event_id:
|
||||
return
|
||||
|
||||
@@ -503,6 +503,35 @@ class UserDirectoryTestCase(unittest.HomeserverTestCase):
|
||||
0,
|
||||
)
|
||||
|
||||
def test_local_user_remains_in_directory_after_leaving_all_rooms(self) -> None:
|
||||
"""We should preserve the invariant that local, non-excluded users are
|
||||
always in the user_directory table.
|
||||
|
||||
This is a choice to simplify the implementation, and also ensure that
|
||||
the config option to search for all users works in this case."""
|
||||
alice = self.register_user("alice", "pass")
|
||||
alice_token = self.login(alice, "pass")
|
||||
|
||||
# Alice should have a user directory entry created at registration.
|
||||
users = self.get_success(self.user_dir_helper.get_profiles_in_user_directory())
|
||||
self.assertEqual(
|
||||
users, {alice: ProfileInfo(display_name="alice", avatar_url=None)}
|
||||
)
|
||||
|
||||
# Alice makes a room for herself.
|
||||
room = self.helper.create_room_as(alice, tok=alice_token)
|
||||
|
||||
# Alice leaves that room.
|
||||
self.helper.leave(room, alice, tok=alice_token)
|
||||
|
||||
# Wait for background updates to ensure that the user directory handler
|
||||
# handler has processed all events. Alice should remain in the directory.
|
||||
self.wait_for_background_updates()
|
||||
users = self.get_success(self.user_dir_helper.get_profiles_in_user_directory())
|
||||
self.assertEqual(
|
||||
users, {alice: ProfileInfo(display_name="alice", avatar_url=None)}
|
||||
)
|
||||
|
||||
def test_private_room(self) -> None:
|
||||
"""
|
||||
A user can be searched for only by people that are either in a public
|
||||
|
||||
Reference in New Issue
Block a user