fix: don't convert archived rooms to joined rooms by accident

When decrypting the last event for archived rooms when a room key is
received, we used to send a synthetic SyncUpdate. This however put the
archived room into the join section, which converted the room to a
joined room. We need to respect what section the room was in when
sending synthetic SyncUpdates.

fixes https://github.com/famedly/matrix-dart-sdk/issues/1916
This commit is contained in:
Nicolas Werner 2024-09-17 19:14:45 +02:00
parent fec258ef12
commit 15dce00c0f
No known key found for this signature in database
GPG Key ID: B38119FF80087618
3 changed files with 104 additions and 3 deletions

View File

@ -164,6 +164,7 @@ class KeyManager {
if (!client.isLogged() || client.encryption == null) {
return;
}
final storeFuture = client.database
?.storeInboundGroupSession(
roomId,
@ -201,7 +202,18 @@ class KeyManager {
await client.handleSync(
SyncUpdate(
nextBatch: '',
rooms: RoomsUpdate(join: {room.id: JoinedRoomUpdate()})),
rooms: switch (room.membership) {
Membership.join =>
RoomsUpdate(join: {room.id: JoinedRoomUpdate()}),
Membership.ban ||
Membership.leave =>
RoomsUpdate(leave: {room.id: LeftRoomUpdate()}),
Membership.invite =>
RoomsUpdate(invite: {room.id: InvitedRoomUpdate()}),
Membership.knock =>
RoomsUpdate(knock: {room.id: KnockRoomUpdate()}),
},
),
);
});
}

View File

@ -114,11 +114,13 @@ class RoomsUpdate {
Map<String, JoinedRoomUpdate>? join;
Map<String, InvitedRoomUpdate>? invite;
Map<String, LeftRoomUpdate>? leave;
Map<String, KnockRoomUpdate>? knock;
RoomsUpdate({
this.join,
this.invite,
this.leave,
this.knock,
});
RoomsUpdate.fromJson(Map<String, Object?> json) {
@ -128,6 +130,8 @@ class RoomsUpdate {
MapEntry(k, InvitedRoomUpdate.fromJson(v as Map<String, Object?>)));
leave = json.tryGetMap<String, Object?>('leave')?.catchMap((k, v) =>
MapEntry(k, LeftRoomUpdate.fromJson(v as Map<String, Object?>)));
knock = json.tryGetMap<String, Object?>('knock')?.catchMap((k, v) =>
MapEntry(k, KnockRoomUpdate.fromJson(v as Map<String, Object?>)));
}
Map<String, Object?> toJson() {
@ -141,6 +145,9 @@ class RoomsUpdate {
if (leave != null) {
data['leave'] = leave!.map((k, v) => MapEntry(k, v.toJson()));
}
if (knock != null) {
data['knock'] = knock!.map((k, v) => MapEntry(k, v.toJson()));
}
return data;
}
}
@ -234,6 +241,28 @@ class InvitedRoomUpdate extends SyncRoomUpdate {
}
}
class KnockRoomUpdate extends SyncRoomUpdate {
List<StrippedStateEvent>? knockState;
KnockRoomUpdate({this.knockState});
KnockRoomUpdate.fromJson(Map<String, Object?> json)
: knockState = json
.tryGetMap<String, List<Object?>>('knock_state')?['events']
?.map((i) => StrippedStateEvent.fromJson(i as Map<String, Object?>))
.toList();
Map<String, Object?> toJson() {
final data = <String, Object?>{};
if (knockState != null) {
data['knock_state'] = {
'events': knockState!.map((i) => i.toJson()).toList(),
};
}
return data;
}
}
class LeftRoomUpdate extends SyncRoomUpdate {
List<MatrixEvent>? state;
TimelineUpdate? timeline;

View File

@ -23,7 +23,7 @@ import 'package:test/test.dart';
import 'package:matrix/matrix.dart';
import 'fake_client.dart';
void main() {
void main() async {
group('Timeline', () {
Logs().level = Level.error;
@ -115,6 +115,66 @@ void main() {
expect(client.getArchiveRoomFromCache('!5345234235:example.com'), null);
});
test("assert that key updates don't change membership", () async {
const roomid = '!5345234235:example.com';
// prep work to be able to set a last event that would trigger the (fixed) bug
await client.loadArchiveWithTimeline();
expect(client.getArchiveRoomFromCache(roomid) != null, true);
expect(client.getRoomById(roomid)?.membership, Membership.leave);
final outboundSession = await client.encryption?.keyManager
.createOutboundGroupSession(roomid);
final inboundSession = client.encryption!.keyManager
.getInboundGroupSession(
roomid, outboundSession!.outboundGroupSession!.session_id())!;
// ensure encryption is "enabled"
client.getRoomById(roomid)?.setState(StrippedStateEvent(
type: EventTypes.Encryption,
content: {'algorithm': AlgorithmTypes.megolmV1AesSha2},
senderId: client.userID!,
stateKey: '',
));
final encryptedEvent = await client.encryption!
.encryptGroupMessagePayload(
roomid, {'msgtype': 'm.room.text', 'body': 'empty'});
// reset client
await client.dispose().onError((e, s) {});
client = await getClient(
sendTimelineEventTimeout: const Duration(seconds: 5),
);
await client.abortSync();
insertList.clear();
// now do our tests
await client.loadArchiveWithTimeline();
expect(client.getArchiveRoomFromCache(roomid) != null, true);
expect(client.getRoomById(roomid)?.membership, Membership.leave);
// set the last event
final room = client.getRoomById(roomid)!;
room.lastEvent = Event(
type: EventTypes.Encrypted,
content: encryptedEvent,
senderId: client.userID!,
eventId: '\$archivedencr',
room: room,
originServerTs: DateTime.now());
// import the inbound session
await client.encryption!.keyManager.setInboundGroupSession(
roomid,
inboundSession.sessionId,
inboundSession.senderKey,
inboundSession.content);
expect(client.getArchiveRoomFromCache(roomid) != null, true);
expect(client.getRoomById(roomid)?.membership, Membership.leave);
}, tags: 'olm');
test('clear archive', () async {
await client.loadArchiveWithTimeline();
client.clearArchivesFromCache();