From edd8aa5c4c38acc464a5049ded7476d61268cf21 Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Tue, 4 Feb 2020 13:41:13 +0000 Subject: [PATCH] Client feature add device tracking --- lib/famedlysdk.dart | 1 + lib/src/client.dart | 116 +++++++++++++++++++++++++--- lib/src/event.dart | 4 +- lib/src/room.dart | 54 ++++++++++++- lib/src/store_api.dart | 5 ++ lib/src/utils/device_keys_list.dart | 102 ++++++++++++++++++++++++ pubspec.lock | 16 +--- pubspec.yaml | 2 - test/client_test.dart | 60 ++++++++++---- test/device_keys_list_test.dart | 78 +++++++++++++++++++ test/fake_matrix_api.dart | 42 +++++++++- test/room_test.dart | 4 +- 12 files changed, 435 insertions(+), 49 deletions(-) create mode 100644 lib/src/utils/device_keys_list.dart create mode 100644 test/device_keys_list_test.dart diff --git a/lib/famedlysdk.dart b/lib/famedlysdk.dart index 55117b8..8e90acd 100644 --- a/lib/famedlysdk.dart +++ b/lib/famedlysdk.dart @@ -26,6 +26,7 @@ library famedlysdk; export 'package:famedlysdk/src/sync/room_update.dart'; export 'package:famedlysdk/src/sync/event_update.dart'; export 'package:famedlysdk/src/sync/user_update.dart'; +export 'package:famedlysdk/src/utils/device_keys_list.dart'; export 'package:famedlysdk/src/utils/matrix_exception.dart'; export 'package:famedlysdk/src/utils/matrix_file.dart'; export 'package:famedlysdk/src/utils/mx_content.dart'; diff --git a/lib/src/client.dart b/lib/src/client.dart index 35185e1..ba8d7a5 100644 --- a/lib/src/client.dart +++ b/lib/src/client.dart @@ -29,6 +29,7 @@ import 'package:famedlysdk/src/account_data.dart'; import 'package:famedlysdk/src/presence.dart'; import 'package:famedlysdk/src/store_api.dart'; import 'package:famedlysdk/src/sync/user_update.dart'; +import 'package:famedlysdk/src/utils/device_keys_list.dart'; import 'package:famedlysdk/src/utils/matrix_file.dart'; import 'package:famedlysdk/src/utils/open_id_credentials.dart'; import 'package:famedlysdk/src/utils/turn_server_credentials.dart'; @@ -540,6 +541,12 @@ class Client { } static String syncFilters = '{"room":{"state":{"lazy_load_members":true}}}'; + static const List supportedDirectEncryptionAlgorithms = [ + "m.olm.v1.curve25519-aes-sha2" + ]; + static const List supportedGroupEncryptionAlgorithms = [ + "m.megolm.v1.aes-sha2" + ]; http.Client httpClient = http.Client(); @@ -630,15 +637,16 @@ class Client { /// ``` /// /// Sends [LoginState.logged] to [onLoginStateChanged]. - void connect( - {String newToken, - String newHomeserver, - String newUserID, - String newDeviceName, - String newDeviceID, - List newMatrixVersions, - bool newLazyLoadMembers, - String newPrevBatch}) async { + void connect({ + String newToken, + String newHomeserver, + String newUserID, + String newDeviceName, + String newDeviceID, + List newMatrixVersions, + bool newLazyLoadMembers, + String newPrevBatch, + }) async { this._accessToken = newToken; this._homeserver = newHomeserver; this._userID = newUserID; @@ -650,6 +658,7 @@ class Client { if (this.storeAPI != null) { await this.storeAPI.storeClient(); + _userDeviceKeys = await this.storeAPI.getUserDeviceKeys(); if (this.store != null) { this._rooms = await this.store.getRoomList(onlyLeft: false); this._sortRooms(); @@ -833,6 +842,7 @@ class Client { _sortRooms(); } this.prevBatch = syncResp["next_batch"]; + unawaited(_updateUserDeviceKeys()); if (hash == _syncRequest.hashCode) unawaited(_sync()); } on MatrixException catch (exception) { onError.add(exception); @@ -866,9 +876,29 @@ class Client { sync["to_device"]["events"] is List) { _handleGlobalEvents(sync["to_device"]["events"], "to_device"); } + if (sync["device_lists"] is Map) { + _handleDeviceListsEvents(sync["device_lists"]); + } onSync.add(sync); } + void _handleDeviceListsEvents(Map deviceLists) { + if (deviceLists["changed"] is List) { + for (final userId in deviceLists["changed"]) { + print("The device list of $userId has changed. Mark as outdated!"); + if (_userDeviceKeys.containsKey(userId)) { + _userDeviceKeys[userId].outdated = true; + } + } + for (final userId in deviceLists["left"]) { + print("The device list of $userId is no longer relevant! Remove it!"); + if (_userDeviceKeys.containsKey(userId)) { + _userDeviceKeys.remove(userId); + } + } + } + } + void _handleRooms(Map rooms, Membership membership) { rooms.forEach((String id, dynamic room) async { // calculate the notification counts, the limitedTimeline and prevbatch @@ -1011,6 +1041,13 @@ class Client { void _handleEvent(Map event, String roomID, String type) { if (event["type"] is String && event["content"] is Map) { + // The client must ignore any new m.room.encryption event to prevent + // man-in-the-middle attacks! + if (event["type"] == "m.room.encryption" && + getRoomById(roomID).encrypted) { + return; + } + EventUpdate update = EventUpdate( eventType: event["type"], roomID: roomID, @@ -1162,4 +1199,65 @@ class Client { ); return OpenIdCredentials.fromJson(response); } + + /// A map of known device keys per user. + Map get userDeviceKeys => _userDeviceKeys; + Map _userDeviceKeys = {}; + + Future> _getUserIdsInEncryptedRooms() async { + Set userIds = {}; + for (int i = 0; i < rooms.length; i++) { + if (rooms[i].encrypted) { + List userList = await rooms[i].requestParticipants(); + for (User user in userList) { + userIds.add(user.id); + } + } + } + return userIds; + } + + Future _updateUserDeviceKeys() async { + Set trackedUserIds = await _getUserIdsInEncryptedRooms(); + print("We are tracking the devices of these users:"); + print(trackedUserIds); + + // Remove all userIds we no longer need to track the devices of. + _userDeviceKeys + .removeWhere((String userId, v) => !trackedUserIds.contains(userId)); + + // Check if there are outdated device key lists. Add it to the set. + Map outdatedLists = {}; + for (String userId in trackedUserIds) { + if (!userDeviceKeys.containsKey(userId)) { + print("Create new device list for user $userId"); + _userDeviceKeys[userId] = DeviceKeysList(userId); + } + DeviceKeysList deviceKeysList = userDeviceKeys[userId]; + if (deviceKeysList.outdated) { + print( + "The device keys list of $userId is outdated. Add to the request"); + outdatedLists[userId] = []; + } + } + + // Request the missing device key lists. + if (outdatedLists.isNotEmpty) { + final Map response = await this.jsonRequest( + type: HTTPType.POST, + action: "/client/r0/keys/query", + data: {"timeout": 10000, "device_keys": outdatedLists}); + for (final rawDeviceKeyListEntry in response["device_keys"].entries) { + final String userId = rawDeviceKeyListEntry.key; + _userDeviceKeys[userId].deviceKeys = {}; + print("Got device key list of $userId. Store it now!"); + for (final rawDeviceKeyEntry in rawDeviceKeyListEntry.value.entries) { + _userDeviceKeys[userId].deviceKeys[rawDeviceKeyEntry.key] = + DeviceKeys.fromJson(rawDeviceKeyEntry.value); + } + _userDeviceKeys[userId].outdated = false; + } + } + await this.storeAPI?.storeUserDeviceKeys(userDeviceKeys); + } } diff --git a/lib/src/event.dart b/lib/src/event.dart index 1e36e73..0651889 100644 --- a/lib/src/event.dart +++ b/lib/src/event.dart @@ -208,9 +208,9 @@ class Event { return EventTypes.Sticker; case "m.room.message": return EventTypes.Message; - case "m.call.encrypted": + case "m.room.encrypted": return EventTypes.Encrypted; - case "m.call.encryption": + case "m.room.encryption": return EventTypes.Encryption; case "m.call.invite": return EventTypes.CallInvite; diff --git a/lib/src/room.dart b/lib/src/room.dart index de39c36..8a47c25 100644 --- a/lib/src/room.dart +++ b/lib/src/room.dart @@ -23,6 +23,7 @@ import 'dart:async'; +import 'package:famedlysdk/famedlysdk.dart'; import 'package:famedlysdk/src/client.dart'; import 'package:famedlysdk/src/event.dart'; import 'package:famedlysdk/src/room_account_data.dart'; @@ -761,6 +762,7 @@ class Room { /// Request the full list of participants from the server. The local list /// from the store is not complete if the client uses lazy loading. Future> requestParticipants() async { + if (participantListComplete) return getParticipants(); List participants = []; dynamic res = await client.jsonRequest( @@ -768,12 +770,24 @@ class Room { for (num i = 0; i < res["chunk"].length; i++) { User newUser = Event.fromJson(res["chunk"][i], this).asUser; - if (newUser.membership != Membership.leave) participants.add(newUser); + if (![Membership.leave, Membership.ban].contains(newUser.membership)) { + participants.add(newUser); + setState(newUser); + } } return participants; } + /// Checks if the local participant list of joined and invited users is complete. + bool get participantListComplete { + List knownParticipants = getParticipants(); + knownParticipants.removeWhere( + (u) => ![Membership.join, Membership.invite].contains(u.membership)); + return knownParticipants.length == + (this.mJoinedMemberCount ?? 0) + (this.mInvitedMemberCount ?? 0); + } + /// Returns the [User] object for the given [mxID] or requests it from /// the homeserver and waits for a response. Future getUserByMXID(String mxID) async { @@ -1235,4 +1249,42 @@ class Room { /// Whether the user has the permission to change the history visibility. bool get canChangeHistoryVisibility => canSendEvent("m.room.history_visibility"); + + /// Returns the encryption algorithm. Currently only `m.megolm.v1.aes-sha2` is supported. + /// Returns null if there is no encryption algorithm. + String get encryptionAlgorithm => getState("m.room.encryption") != null + ? getState("m.room.encryption").content["algorithm"].toString() + : null; + + /// Checks if this room is encrypted. + bool get encrypted => encryptionAlgorithm != null; + + Future enableEncryption({int algorithmIndex = 0}) async { + if (encrypted) throw ("Encryption is already enabled!"); + final String algorithm = + Client.supportedGroupEncryptionAlgorithms[algorithmIndex]; + await client.jsonRequest( + type: HTTPType.PUT, + action: "/client/r0/rooms/$id/state/m.room.encryption/", + data: { + "algorithm": algorithm, + }, + ); + return; + } + + Future> getUserDeviceKeys() async { + List deviceKeys = []; + List users = await requestParticipants(); + for (final userDeviceKeyEntry in client.userDeviceKeys.entries) { + if (users.indexWhere((u) => u.id == userDeviceKeyEntry.key) == -1) { + continue; + } + for (DeviceKeys deviceKeyEntry + in userDeviceKeyEntry.value.deviceKeys.values) { + deviceKeys.add(deviceKeyEntry); + } + } + return deviceKeys; + } } diff --git a/lib/src/store_api.dart b/lib/src/store_api.dart index 58ac190..7a28e87 100644 --- a/lib/src/store_api.dart +++ b/lib/src/store_api.dart @@ -25,6 +25,7 @@ import 'dart:async'; import 'dart:core'; import 'package:famedlysdk/src/account_data.dart'; import 'package:famedlysdk/src/presence.dart'; +import 'package:famedlysdk/src/utils/device_keys_list.dart'; import 'client.dart'; import 'event.dart'; import 'room.dart'; @@ -46,6 +47,10 @@ abstract class StoreAPI { /// Clears all tables from the database. Future clear(); + + Future storeUserDeviceKeys(Map userDeviceKeys); + + Future> getUserDeviceKeys(); } /// Responsible to store all data persistent and to query objects from the diff --git a/lib/src/utils/device_keys_list.dart b/lib/src/utils/device_keys_list.dart new file mode 100644 index 0000000..a963843 --- /dev/null +++ b/lib/src/utils/device_keys_list.dart @@ -0,0 +1,102 @@ +import 'dart:convert'; + +import '../client.dart'; + +class DeviceKeysList { + String userId; + bool outdated = true; + Map deviceKeys = {}; + + DeviceKeysList.fromJson(Map json) { + userId = json['user_id']; + outdated = json['outdated']; + deviceKeys = {}; + for (final rawDeviceKeyEntry in json['device_keys'].entries) { + deviceKeys[rawDeviceKeyEntry.key] = + DeviceKeys.fromJson(rawDeviceKeyEntry.value); + } + } + + Map toJson() { + final Map data = Map(); + data['user_id'] = this.userId; + data['outdated'] = this.outdated; + + Map rawDeviceKeys = {}; + for (final deviceKeyEntry in this.deviceKeys.entries) { + rawDeviceKeys[deviceKeyEntry.key] = deviceKeyEntry.value.toJson(); + } + data['device_keys'] = rawDeviceKeys; + return data; + } + + String toString() => json.encode(toJson()); + + DeviceKeysList(this.userId); +} + +class DeviceKeys { + String userId; + String deviceId; + List algorithms; + Map keys; + Map signatures; + Map unsigned; + bool verified; + bool blocked; + + Future setVerified(bool newVerified, Client client) { + verified = newVerified; + return client.storeAPI.storeUserDeviceKeys(client.userDeviceKeys); + } + + Future setBlocked(bool newBlocked, Client client) { + blocked = newBlocked; + return client.storeAPI.storeUserDeviceKeys(client.userDeviceKeys); + } + + DeviceKeys({ + this.userId, + this.deviceId, + this.algorithms, + this.keys, + this.signatures, + this.unsigned, + this.verified, + this.blocked, + }); + + DeviceKeys.fromJson(Map json) { + userId = json['user_id']; + deviceId = json['device_id']; + algorithms = json['algorithms'].cast(); + keys = json['keys'] != null ? Map.from(json['keys']) : null; + signatures = json['signatures'] != null + ? Map.from(json['signatures']) + : null; + unsigned = json['unsigned'] != null + ? Map.from(json['unsigned']) + : null; + verified = json['verified'] ?? false; + blocked = json['blocked'] ?? false; + } + + Map toJson() { + final Map data = Map(); + data['user_id'] = this.userId; + data['device_id'] = this.deviceId; + data['algorithms'] = this.algorithms; + if (this.keys != null) { + data['keys'] = this.keys; + } + if (this.signatures != null) { + data['signatures'] = this.signatures; + } + if (this.unsigned != null) { + data['unsigned'] = this.unsigned; + } + data['verified'] = this.verified; + data['blocked'] = this.blocked; + return data; + } +} diff --git a/pubspec.lock b/pubspec.lock index ac250af..1b2cbcf 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -212,19 +212,12 @@ packages: source: hosted version: "0.6.1+1" json_annotation: - dependency: "direct main" + dependency: transitive description: name: json_annotation url: "https://pub.dartlang.org" source: hosted version: "2.4.0" - json_serializable: - dependency: "direct dev" - description: - name: json_serializable - url: "https://pub.dartlang.org" - source: hosted - version: "3.0.0" kernel: dependency: transitive description: @@ -365,13 +358,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.2.3" - source_gen: - dependency: transitive - description: - name: source_gen - url: "https://pub.dartlang.org" - source: hosted - version: "0.9.4+2" source_map_stack_trace: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index e171bb4..280104f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -10,10 +10,8 @@ environment: dependencies: http: ^0.12.0+2 mime_type: ^0.2.4 - json_annotation: ^2.4.0 dev_dependencies: test: ^1.0.0 build_runner: ^1.5.2 - json_serializable: ^3.0.0 pedantic: ^1.5.0 # DO NOT UPDATE AS THIS WOULD CAUSE FLUTTER TO FAIL \ No newline at end of file diff --git a/test/client_test.dart b/test/client_test.dart index 0899686..a1828f5 100644 --- a/test/client_test.dart +++ b/test/client_test.dart @@ -138,6 +138,9 @@ void main() { expect(matrix.rooms[1].typingUsers.length, 1); expect(matrix.rooms[1].typingUsers[0].id, "@alice:example.com"); expect(matrix.rooms[1].roomAccountData.length, 3); + expect(matrix.rooms[1].encrypted, true); + expect(matrix.rooms[1].encryptionAlgorithm, + Client.supportedGroupEncryptionAlgorithms.first); expect( matrix.rooms[1].roomAccountData["m.receipt"] .content["@alice:example.com"]["ts"], @@ -151,11 +154,33 @@ void main() { "#famedlyContactDiscovery:${matrix.userID.split(":")[1]}"); final List contacts = await matrix.loadFamedlyContacts(); expect(contacts.length, 1); - expect(contacts[0].senderId, "@alice:example.org"); + expect(contacts[0].senderId, "@alice:example.com"); expect( matrix.presences["@alice:example.com"].presence, PresenceType.online); expect(presenceCounter, 1); expect(accountDataCounter, 3); + await Future.delayed(Duration(milliseconds: 50)); + expect(matrix.userDeviceKeys.length, 1); + expect(matrix.userDeviceKeys["@alice:example.com"].outdated, false); + expect(matrix.userDeviceKeys["@alice:example.com"].deviceKeys.length, 1); + expect( + matrix.userDeviceKeys["@alice:example.com"].deviceKeys["JLAFKJWSCS"] + .verified, + false); + + matrix.handleSync({ + "device_lists": { + "changed": [ + "@alice:example.com", + ], + "left": [ + "@bob:example.com", + ], + } + }); + await Future.delayed(Duration(milliseconds: 50)); + expect(matrix.userDeviceKeys.length, 1); + expect(matrix.userDeviceKeys["@alice:example.com"].outdated, true); matrix.handleSync({ "rooms": { @@ -184,6 +209,7 @@ void main() { "#famedlyContactDiscovery:${matrix.userID.split(":")[1]}"), null); final List altContacts = await matrix.loadFamedlyContacts(); + altContacts.forEach((u) => print(u.id)); expect(altContacts.length, 2); expect(altContacts[0].senderId, "@alice:example.com"); }); @@ -248,7 +274,7 @@ void main() { List eventUpdateList = await eventUpdateListFuture; - expect(eventUpdateList.length, 12); + expect(eventUpdateList.length, 13); expect(eventUpdateList[0].eventType, "m.room.member"); expect(eventUpdateList[0].roomID, "!726s6s6q:example.com"); @@ -258,41 +284,45 @@ void main() { expect(eventUpdateList[1].roomID, "!726s6s6q:example.com"); expect(eventUpdateList[1].type, "state"); - expect(eventUpdateList[2].eventType, "m.room.member"); + expect(eventUpdateList[2].eventType, "m.room.encryption"); expect(eventUpdateList[2].roomID, "!726s6s6q:example.com"); - expect(eventUpdateList[2].type, "timeline"); + expect(eventUpdateList[2].type, "state"); - expect(eventUpdateList[3].eventType, "m.room.message"); + expect(eventUpdateList[3].eventType, "m.room.member"); expect(eventUpdateList[3].roomID, "!726s6s6q:example.com"); expect(eventUpdateList[3].type, "timeline"); - expect(eventUpdateList[4].eventType, "m.typing"); + expect(eventUpdateList[4].eventType, "m.room.message"); expect(eventUpdateList[4].roomID, "!726s6s6q:example.com"); - expect(eventUpdateList[4].type, "ephemeral"); + expect(eventUpdateList[4].type, "timeline"); - expect(eventUpdateList[5].eventType, "m.receipt"); + expect(eventUpdateList[5].eventType, "m.typing"); expect(eventUpdateList[5].roomID, "!726s6s6q:example.com"); expect(eventUpdateList[5].type, "ephemeral"); expect(eventUpdateList[6].eventType, "m.receipt"); expect(eventUpdateList[6].roomID, "!726s6s6q:example.com"); - expect(eventUpdateList[6].type, "account_data"); + expect(eventUpdateList[6].type, "ephemeral"); - expect(eventUpdateList[7].eventType, "m.tag"); + expect(eventUpdateList[7].eventType, "m.receipt"); expect(eventUpdateList[7].roomID, "!726s6s6q:example.com"); expect(eventUpdateList[7].type, "account_data"); - expect(eventUpdateList[8].eventType, "org.example.custom.room.config"); + expect(eventUpdateList[8].eventType, "m.tag"); expect(eventUpdateList[8].roomID, "!726s6s6q:example.com"); expect(eventUpdateList[8].type, "account_data"); - expect(eventUpdateList[9].eventType, "m.room.name"); - expect(eventUpdateList[9].roomID, "!696r7674:example.com"); - expect(eventUpdateList[9].type, "invite_state"); + expect(eventUpdateList[9].eventType, "org.example.custom.room.config"); + expect(eventUpdateList[9].roomID, "!726s6s6q:example.com"); + expect(eventUpdateList[9].type, "account_data"); - expect(eventUpdateList[10].eventType, "m.room.member"); + expect(eventUpdateList[10].eventType, "m.room.name"); expect(eventUpdateList[10].roomID, "!696r7674:example.com"); expect(eventUpdateList[10].type, "invite_state"); + + expect(eventUpdateList[11].eventType, "m.room.member"); + expect(eventUpdateList[11].roomID, "!696r7674:example.com"); + expect(eventUpdateList[11].type, "invite_state"); }); test('User Update Test', () async { diff --git a/test/device_keys_list_test.dart b/test/device_keys_list_test.dart new file mode 100644 index 0000000..af78f14 --- /dev/null +++ b/test/device_keys_list_test.dart @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2019 Zender & Kurtz GbR. + * + * Authors: + * Christian Pauly + * Marcel Radzio + * + * This file is part of famedlysdk. + * + * famedlysdk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * famedlysdk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with famedlysdk. If not, see . + */ + +import 'dart:convert'; + +import 'package:famedlysdk/famedlysdk.dart'; +import 'package:test/test.dart'; + +void main() { + /// All Tests related to device keys + group("Device keys", () { + test("fromJson", () async { + Map rawJson = { + "user_id": "@alice:example.com", + "device_id": "JLAFKJWSCS", + "algorithms": ["m.olm.v1.curve25519-aes-sha2", "m.megolm.v1.aes-sha2"], + "keys": { + "curve25519:JLAFKJWSCS": + "3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI", + "ed25519:JLAFKJWSCS": "lEuiRJBit0IG6nUf5pUzWTUEsRVVe/HJkoKuEww9ULI" + }, + "signatures": { + "@alice:example.com": { + "ed25519:JLAFKJWSCS": + "dSO80A01XiigH3uBiDVx/EjzaoycHcjq9lfQX0uWsqxl2giMIiSPR8a4d291W1ihKJL/a+myXS367WT6NAIcBA" + } + }, + "unsigned": {"device_display_name": "Alice's mobile phone"}, + "verified": false, + "blocked": true, + }; + Map rawListJson = { + "user_id": "@alice:example.com", + "outdated": true, + "device_keys": {"JLAFKJWSCS": rawJson}, + }; + + Map userDeviceKeys = { + "@alice:example.com": DeviceKeysList.fromJson(rawListJson), + }; + Map userDeviceKeyRaw = { + "@alice:example.com": rawListJson, + }; + + expect(json.encode(DeviceKeys.fromJson(rawJson).toJson()), + json.encode(rawJson)); + expect(json.encode(DeviceKeysList.fromJson(rawListJson).toJson()), + json.encode(rawListJson)); + + Map mapFromRaw = {}; + for (final rawListEntry in userDeviceKeyRaw.entries) { + mapFromRaw[rawListEntry.key] = + DeviceKeysList.fromJson(rawListEntry.value); + } + expect(mapFromRaw.toString(), userDeviceKeys.toString()); + }); + }); +} diff --git a/test/fake_matrix_api.dart b/test/fake_matrix_api.dart index 1911b47..f6af172 100644 --- a/test/fake_matrix_api.dart +++ b/test/fake_matrix_api.dart @@ -364,7 +364,15 @@ class FakeMatrixApi extends MockClient { "state_key": "", "origin_server_ts": 1417731086796, "event_id": "66697273743032:example.com" - } + }, + { + "sender": "@alice:example.com", + "type": "m.room.encryption", + "state_key": "", + "content": {"algorithm": "m.megolm.v1.aes-sha2"}, + "origin_server_ts": 1417731086795, + "event_id": "666972737430353:example.com" + }, ] }, "timeline": { @@ -582,10 +590,10 @@ class FakeMatrixApi extends MockClient { "type": "m.room.member", "event_id": "ยง143273582443PhrSn:example.org", "room_id": "!636q39766251:example.com", - "sender": "@alice:example.org", + "sender": "@alice:example.com", "origin_server_ts": 1432735824653, "unsigned": {"age": 1234}, - "state_key": "@alice:example.org" + "state_key": "@alice:example.com" } ] }, @@ -760,6 +768,34 @@ class FakeMatrixApi extends MockClient { {"available": true}, }, "POST": { + "/client/r0/keys/query": (var req) => { + "failures": {}, + "device_keys": { + "@alice:example.com": { + "JLAFKJWSCS": { + "user_id": "@alice:example.com", + "device_id": "JLAFKJWSCS", + "algorithms": [ + "m.olm.v1.curve25519-aes-sha2", + "m.megolm.v1.aes-sha2" + ], + "keys": { + "curve25519:JLAFKJWSCS": + "3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI", + "ed25519:JLAFKJWSCS": + "lEuiRJBit0IG6nUf5pUzWTUEsRVVe/HJkoKuEww9ULI" + }, + "signatures": { + "@alice:example.com": { + "ed25519:JLAFKJWSCS": + "dSO80A01XiigH3uBiDVx/EjzaoycHcjq9lfQX0uWsqxl2giMIiSPR8a4d291W1ihKJL/a+myXS367WT6NAIcBA" + } + }, + "unsigned": {"device_display_name": "Alice's mobile phone"} + } + } + } + }, "/client/r0/register": (var req) => {"user_id": "@testuser:example.com"}, "/client/r0/register?kind=user": (var req) => {"user_id": "@testuser:example.com"}, diff --git a/test/room_test.dart b/test/room_test.dart index 0097cd3..8a62d21 100644 --- a/test/room_test.dart +++ b/test/room_test.dart @@ -298,8 +298,8 @@ void main() { content: {"displayname": "alice"}, stateKey: "@alice:test.abc")); final List userList = room.getParticipants(); - expect(userList.length, 4); - expect(userList[3].displayName, "alice"); + expect(userList.length, 5); + expect(userList[3].displayName, "Alice Margatroid"); }); test("addToDirectChat", () async {