Newer
Older
Andrew Morgan
committed
# Add a user to the room.
builder = self.event_builder_factory.for_room_version(
room_version,
{
"type": "m.room.member",
"sender": user_id,
"state_key": user_id,
"room_id": room_id,
"content": {"membership": "join"},
},
)
Shay
committed
event, unpersisted_context = self.get_success(
Andrew Morgan
committed
self.event_creation_handler.create_new_client_event(builder)
)
Shay
committed
context = self.get_success(unpersisted_context.persist(event))
persistence = self.hs.get_storage_controllers().persistence
assert persistence is not None
self.get_success(persistence.persist_event(event, context))
Andrew Morgan
committed
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
def test_local_user_leaving_room_remains_in_user_directory(self) -> None:
"""We've chosen to simplify the user directory's implementation by
always including local users. Ensure this invariant is maintained when
a local user
- leaves a room, and
- leaves the last room they're in which is visible to this server.
This is user-visible if the "search_all_users" config option is on: the
local user who left a room would no longer be searchable if this test fails!
"""
alice = self.register_user("alice", "pass")
alice_token = self.login(alice, "pass")
bob = self.register_user("bob", "pass")
bob_token = self.login(bob, "pass")
# Alice makes two public rooms, which Bob joins.
room1 = self.helper.create_room_as(alice, is_public=True, tok=alice_token)
room2 = self.helper.create_room_as(alice, is_public=True, tok=alice_token)
self.helper.join(room1, bob, tok=bob_token)
self.helper.join(room2, bob, tok=bob_token)
# The user directory tables are updated.
users, in_public, in_private = self.get_success(
self.user_dir_helper.get_tables()
)
self.assertEqual(users, {alice, bob})
self.assertEqual(
in_public, {(alice, room1), (alice, room2), (bob, room1), (bob, room2)}
)
self.assertEqual(in_private, set())
# Alice leaves one room. She should still be in the directory.
self.helper.leave(room1, alice, tok=alice_token)
users, in_public, in_private = self.get_success(
self.user_dir_helper.get_tables()
)
self.assertEqual(users, {alice, bob})
self.assertEqual(in_public, {(alice, room2), (bob, room1), (bob, room2)})
self.assertEqual(in_private, set())
# Alice leaves the other. She should still be in the directory.
self.helper.leave(room2, alice, tok=alice_token)
self.wait_for_background_updates()
users, in_public, in_private = self.get_success(
self.user_dir_helper.get_tables()
)
self.assertEqual(users, {alice, bob})
self.assertEqual(in_public, {(bob, room1), (bob, room2)})
self.assertEqual(in_private, set())
David Robertson
committed
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
def test_ignore_display_names_with_null_codepoints(self) -> None:
MXC_DUMMY = "mxc://dummy"
# Alice creates a public room.
alice = self.register_user("alice", "pass")
# Alice has a user directory entry to start with.
self.assertIn(
alice,
self.get_success(self.user_dir_helper.get_profiles_in_user_directory()),
)
# Alice changes her name to include a null codepoint.
self.get_success(
self.hs.get_user_directory_handler().handle_local_profile_change(
alice,
ProfileInfo(
display_name="abcd\u0000efgh",
avatar_url=MXC_DUMMY,
),
)
)
# Alice's profile should be updated with the new avatar, but no display name.
self.assertEqual(
self.get_success(self.user_dir_helper.get_profiles_in_user_directory()),
{alice: ProfileInfo(display_name=None, avatar_url=MXC_DUMMY)},
)
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
def test_search_punctuation(self) -> None:
"""Test that you can search for a user that includes punctuation"""
searching_user = self.register_user("searcher", "password")
searching_user_tok = self.login("searcher", "password")
room_id = self.helper.create_room_as(
searching_user,
room_version=RoomVersions.V1.identifier,
tok=searching_user_tok,
)
# We want to test searching for users of the form e.g. "user-1", with
# various punctuation. We also test both where the prefix is numeric and
# alphanumeric, as e.g. postgres tokenises "user-1" as "user" and "-1".
i = 1
for char in ["-", ".", "_"]:
for use_numeric in [False, True]:
if use_numeric:
prefix1 = f"{i}"
prefix2 = f"{i+1}"
else:
prefix1 = f"a{i}"
prefix2 = f"a{i+1}"
local_user_1 = self.register_user(f"user{char}{prefix1}", "password")
local_user_2 = self.register_user(f"user{char}{prefix2}", "password")
self._add_user_to_room(room_id, RoomVersions.V1, local_user_1)
self._add_user_to_room(room_id, RoomVersions.V1, local_user_2)
results = self.get_success(
self.handler.search_users(searching_user, local_user_1, 20)
)["results"]
received_user_id_ordering = [result["user_id"] for result in results]
self.assertSequenceEqual(received_user_id_ordering[:1], [local_user_1])
i += 2
class TestUserDirSearchDisabled(unittest.HomeserverTestCase):
servlets = [
user_directory.register_servlets,
room.register_servlets,
login.register_servlets,
synapse.rest.admin.register_servlets_for_client_rest_resource,
def make_homeserver(self, reactor: MemoryReactor, clock: Clock) -> HomeServer:
# Re-enables updating the user directory, as that function is needed below. It
# will be force disabled later
config["update_user_directory_from_worker"] = None
hs = self.setup_test_homeserver(config=config)
self.config = hs.config
return hs
def test_disabling_room_list(self) -> None:
self.config.userdirectory.user_directory_search_enabled = True
# Create two users and put them in the same room.
u1 = self.register_user("user1", "pass")
u1_token = self.login(u1, "pass")
u2_token = self.login(u2, "pass")
room = self.helper.create_room_as(u1, tok=u1_token)
self.helper.join(room, user=u2, tok=u2_token)
# Each should see the other when searching the user directory.
channel = self.make_request(
"POST",
b"user_directory/search",
b'{"search_term":"user2"}',
access_token=u1_token,
self.assertEqual(200, channel.code, channel.result)
self.assertTrue(len(channel.json_body["results"]) > 0)
# Disable user directory and check search returns nothing
self.config.userdirectory.user_directory_search_enabled = False
channel = self.make_request(
"POST",
b"user_directory/search",
b'{"search_term":"user2"}',
access_token=u1_token,
self.assertEqual(200, channel.code, channel.result)
reivilibre
committed
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
class UserDirectoryRemoteProfileTestCase(unittest.HomeserverTestCase):
servlets = [
login.register_servlets,
synapse.rest.admin.register_servlets,
register.register_servlets,
room.register_servlets,
]
def default_config(self) -> JsonDict:
config = super().default_config()
# Re-enables updating the user directory, as that functionality is needed below.
config["update_user_directory_from_worker"] = None
return config
def prepare(self, reactor: MemoryReactor, clock: Clock, hs: HomeServer) -> None:
self.store = hs.get_datastores().main
self.alice = self.register_user("alice", "alice123")
self.alice_tok = self.login("alice", "alice123")
self.user_dir_helper = GetUserDirectoryTables(self.store)
self.user_dir_handler = hs.get_user_directory_handler()
self.profile_handler = hs.get_profile_handler()
# Cancel the startup call: in the steady-state case we can't rely on it anyway.
assert self.user_dir_handler._refresh_remote_profiles_call_later is not None
self.user_dir_handler._refresh_remote_profiles_call_later.cancel()
def test_public_rooms_have_profiles_collected(self) -> None:
"""
In a public room, member state events are treated as reflecting the user's
real profile and they are accepted.
(The main motivation for accepting this is to prevent having to query
*every* single profile change over federation.)
"""
room_id = self.helper.create_room_as(
self.alice, is_public=True, tok=self.alice_tok
)
self.get_success(
event_injection.inject_member_event(
self.hs,
room_id,
"@bruce:remote",
"join",
"@bruce:remote",
extra_content={
"displayname": "Bruce!",
"avatar_url": "mxc://remote/123",
},
)
)
# Sending this event makes the streams move forward after the injection...
self.helper.send(room_id, "Test", tok=self.alice_tok)
self.pump(0.1)
profiles = self.get_success(
self.user_dir_helper.get_profiles_in_user_directory()
)
self.assertEqual(
profiles.get("@bruce:remote"),
ProfileInfo(display_name="Bruce!", avatar_url="mxc://remote/123"),
)
def test_private_rooms_do_not_have_profiles_collected(self) -> None:
"""
In a private room, member state events are not pulled out and used to populate
the user directory.
"""
room_id = self.helper.create_room_as(
self.alice, is_public=False, tok=self.alice_tok
)
self.get_success(
event_injection.inject_member_event(
self.hs,
room_id,
"@bruce:remote",
"join",
"@bruce:remote",
extra_content={
"displayname": "super-duper bruce",
"avatar_url": "mxc://remote/456",
},
)
)
# Sending this event makes the streams move forward after the injection...
self.helper.send(room_id, "Test", tok=self.alice_tok)
self.pump(0.1)
profiles = self.get_success(
self.user_dir_helper.get_profiles_in_user_directory()
)
self.assertNotIn("@bruce:remote", profiles)
def test_private_rooms_have_profiles_requested(self) -> None:
"""
When a name changes in a private room, the homeserver instead requests
the user's global profile over federation.
"""
async def get_remote_profile(
user_id: str, ignore_backoff: bool = True
) -> JsonDict:
if user_id == "@bruce:remote":
return {
"displayname": "Sir Bruce Bruceson",
"avatar_url": "mxc://remote/789",
}
else:
raise ValueError(f"unable to fetch {user_id}")
with patch.object(self.profile_handler, "get_profile", get_remote_profile):
# Continue from the earlier test...
self.test_private_rooms_do_not_have_profiles_collected()
# Advance by a minute
self.reactor.advance(61.0)
profiles = self.get_success(
self.user_dir_helper.get_profiles_in_user_directory()
)
self.assertEqual(
profiles.get("@bruce:remote"),
ProfileInfo(
display_name="Sir Bruce Bruceson", avatar_url="mxc://remote/789"
),
)
def test_profile_requests_are_retried(self) -> None:
"""
When we fail to fetch the user's profile over federation,
we try again later.
"""
has_failed_once = False
async def get_remote_profile(
user_id: str, ignore_backoff: bool = True
) -> JsonDict:
nonlocal has_failed_once
if user_id == "@bruce:remote":
if not has_failed_once:
has_failed_once = True
raise SynapseError(502, "temporary network problem")
return {
"displayname": "Sir Bruce Bruceson",
"avatar_url": "mxc://remote/789",
}
else:
raise ValueError(f"unable to fetch {user_id}")
with patch.object(self.profile_handler, "get_profile", get_remote_profile):
# Continue from the earlier test...
self.test_private_rooms_do_not_have_profiles_collected()
# Advance by a minute
self.reactor.advance(61.0)
# The request has already failed once
self.assertTrue(has_failed_once)
# The profile has yet to be updated.
profiles = self.get_success(
self.user_dir_helper.get_profiles_in_user_directory()
)
self.assertNotIn(
"@bruce:remote",
profiles,
)
# Advance by five minutes, after the backoff has finished
self.reactor.advance(301.0)
# The profile should have been updated now
profiles = self.get_success(
self.user_dir_helper.get_profiles_in_user_directory()
)
self.assertEqual(
profiles.get("@bruce:remote"),
ProfileInfo(
display_name="Sir Bruce Bruceson", avatar_url="mxc://remote/789"
),
)