From 4cf88e2be6256aeef00d22c299891713b835b893 Mon Sep 17 00:00:00 2001 From: Krille Fear Date: Fri, 5 Nov 2021 14:23:11 +0100 Subject: [PATCH 1/2] feat: More advanced create chat methods This includes a new simplified API to create new direct and group chats. It also handles enabling encryption by default. --- lib/src/client.dart | 86 ++++++++++++++++++++++++++++++++++++++++--- lib/src/user.dart | 12 +++++- test/client_test.dart | 6 +++ test/user_test.dart | 2 +- 4 files changed, 99 insertions(+), 7 deletions(-) diff --git a/lib/src/client.dart b/lib/src/client.dart index 30372237..e6da4ba7 100644 --- a/lib/src/client.dart +++ b/lib/src/client.dart @@ -556,24 +556,100 @@ class Client extends MatrixApi { } /// Returns an existing direct room ID with this user or creates a new one. - /// Returns null on error. - Future startDirectChat(String mxid) async { + /// By default encryption will be enabled if the client supports encryption + /// and the other user has uploaded any encryption keys. + Future startDirectChat( + String mxid, { + bool? enableEncryption, + List? initialState, + bool waitForSync = true, + }) async { // Try to find an existing direct chat - var roomId = getDirectChatFromUserId(mxid); - if (roomId != null) return roomId; + final directChatRoomId = getDirectChatFromUserId(mxid); + if (directChatRoomId != null) return directChatRoomId; + + enableEncryption ??= await userOwnsEncryptionKeys(mxid); + if (enableEncryption) { + initialState ??= []; + initialState.add(StateEvent( + content: { + 'algorithm': supportedGroupEncryptionAlgorithms.first, + }, + type: EventTypes.Encryption, + )); + } // Start a new direct chat - roomId = await createRoom( + final roomId = await createRoom( invite: [mxid], isDirect: true, preset: CreateRoomPreset.trustedPrivateChat, + initialState: initialState, ); + if (waitForSync) { + if (getRoomById(roomId) == null) { + // Wait for room actually appears in sync + await onSync.stream.firstWhere( + (sync) => sync.rooms?.join?.containsKey(roomId) ?? false); + } + } + await Room(id: roomId, client: this).addToDirectChat(mxid); return roomId; } + /// Simplified method to create a new group chat. By default it is a private + /// chat. The encryption is enabled if this client supports encryption and + /// the preset is not a public chat. + Future createGroupChat({ + String? groupName, + bool? enableEncryption, + List? invite, + CreateRoomPreset preset = CreateRoomPreset.privateChat, + List? initialState, + bool waitForSync = true, + }) async { + enableEncryption ??= + encryptionEnabled && preset != CreateRoomPreset.publicChat; + if (enableEncryption) { + initialState ??= []; + initialState.add(StateEvent( + content: { + 'algorithm': supportedGroupEncryptionAlgorithms.first, + }, + type: EventTypes.Encryption, + )); + } + final roomId = await createRoom( + invite: invite, + preset: preset, + name: groupName, + initialState: initialState, + ); + + if (waitForSync) { + if (getRoomById(roomId) == null) { + // Wait for room actually appears in sync + await onSync.stream.firstWhere( + (sync) => sync.rooms?.join?.containsKey(roomId) ?? false); + } + } + return roomId; + } + + /// Checks if the given user has encryption keys. May query keys from the + /// server to answer this. + Future userOwnsEncryptionKeys(String userId) async { + if (userId == userID) return encryptionEnabled; + if (_userDeviceKeys.containsKey(userId)) { + return true; + } + final keys = await queryKeys({userId: []}); + return keys.deviceKeys?.isNotEmpty ?? false; + } + /// Creates a new space and returns the Room ID. The parameters are mostly /// the same like in [createRoom()]. /// Be aware that spaces appear in the [rooms] list. You should check if a diff --git a/lib/src/user.dart b/lib/src/user.dart index e980b64b..8147a0d7 100644 --- a/lib/src/user.dart +++ b/lib/src/user.dart @@ -148,7 +148,17 @@ class User extends Event { /// Returns an existing direct chat ID with this user or creates a new one. /// Returns null on error. - Future startDirectChat() async => room.client.startDirectChat(id); + Future startDirectChat({ + bool? enableEncryption, + List? initialState, + bool waitForSync = true, + }) async => + room.client.startDirectChat( + id, + enableEncryption: enableEncryption, + initialState: initialState, + waitForSync: waitForSync, + ); /// The newest presence of this user if there is any and null if not. Presence? get presence => room.client.presences[id]; diff --git a/test/client_test.dart b/test/client_test.dart index 09a720dd..7e11af25 100644 --- a/test/client_test.dart +++ b/test/client_test.dart @@ -560,6 +560,12 @@ void main() { bunnyContent); await client.dispose(closeDatabase: true); }); + test('startDirectChat', () async { + await matrix.startDirectChat('@alice:example.com', waitForSync: false); + }); + test('createGroupChat', () async { + await matrix.createGroupChat(groupName: 'Testgroup', waitForSync: false); + }); test('Test the fake store api', () async { final database = await getDatabase(null); final client1 = Client( diff --git a/test/user_test.dart b/test/user_test.dart index 61e09978..0518811c 100644 --- a/test/user_test.dart +++ b/test/user_test.dart @@ -118,7 +118,7 @@ void main() { await user1.setPower(50); }); test('startDirectChat', () async { - await user1.startDirectChat(); + await user1.startDirectChat(waitForSync: false); }); test('getPresence', () async { await client.handleSync(SyncUpdate.fromJson({ From 9abe1ed81daf535ce7da31a18ccb68df543992a6 Mon Sep 17 00:00:00 2001 From: Krille Fear Date: Mon, 8 Nov 2021 09:39:27 +0100 Subject: [PATCH 2/2] fix: Dont enable e2ee in test verification DM room --- lib/src/client.dart | 38 ++++++++++++---------- lib/src/utils/device_keys_list.dart | 15 +++++++-- test/device_keys_list_test.dart | 2 +- test/encryption/key_verification_test.dart | 24 +++++++------- 4 files changed, 47 insertions(+), 32 deletions(-) diff --git a/lib/src/client.dart b/lib/src/client.dart index e6da4ba7..cc46ce78 100644 --- a/lib/src/client.dart +++ b/lib/src/client.dart @@ -571,12 +571,14 @@ class Client extends MatrixApi { enableEncryption ??= await userOwnsEncryptionKeys(mxid); if (enableEncryption) { initialState ??= []; - initialState.add(StateEvent( - content: { - 'algorithm': supportedGroupEncryptionAlgorithms.first, - }, - type: EventTypes.Encryption, - )); + if (!initialState.any((s) => s.type == EventTypes.Encryption)) { + initialState.add(StateEvent( + content: { + 'algorithm': supportedGroupEncryptionAlgorithms.first, + }, + type: EventTypes.Encryption, + )); + } } // Start a new direct chat @@ -587,12 +589,10 @@ class Client extends MatrixApi { initialState: initialState, ); - if (waitForSync) { - if (getRoomById(roomId) == null) { - // Wait for room actually appears in sync - await onSync.stream.firstWhere( - (sync) => sync.rooms?.join?.containsKey(roomId) ?? false); - } + if (waitForSync && getRoomById(roomId) == null) { + // Wait for room actually appears in sync + await onSync.stream + .firstWhere((sync) => sync.rooms?.join?.containsKey(roomId) ?? false); } await Room(id: roomId, client: this).addToDirectChat(mxid); @@ -615,12 +615,14 @@ class Client extends MatrixApi { encryptionEnabled && preset != CreateRoomPreset.publicChat; if (enableEncryption) { initialState ??= []; - initialState.add(StateEvent( - content: { - 'algorithm': supportedGroupEncryptionAlgorithms.first, - }, - type: EventTypes.Encryption, - )); + if (!initialState.any((s) => s.type == EventTypes.Encryption)) { + initialState.add(StateEvent( + content: { + 'algorithm': supportedGroupEncryptionAlgorithms.first, + }, + type: EventTypes.Encryption, + )); + } } final roomId = await createRoom( invite: invite, diff --git a/lib/src/utils/device_keys_list.dart b/lib/src/utils/device_keys_list.dart index 6babeb1e..a6bdb115 100644 --- a/lib/src/utils/device_keys_list.dart +++ b/lib/src/utils/device_keys_list.dart @@ -67,14 +67,25 @@ class DeviceKeysList { } } - Future startVerification() async { + /// Starts a verification with this device. This might need to create a new + /// direct chat to send the verification request over this room. For this you + /// can set parameters here. + Future startVerification({ + bool? newDirectChatEnableEncryption, + List? newDirectChatInitialState, + }) async { final encryption = client.encryption; if (encryption == null) { throw Exception('Encryption not enabled'); } if (userId != client.userID) { // in-room verification with someone else - final roomId = await client.startDirectChat(userId); + final roomId = await client.startDirectChat( + userId, + enableEncryption: newDirectChatEnableEncryption, + initialState: newDirectChatInitialState, + waitForSync: false, + ); final room = client.getRoomById(roomId) ?? Room(id: roomId, client: client); diff --git a/test/device_keys_list_test.dart b/test/device_keys_list_test.dart index 98560862..13d24f59 100644 --- a/test/device_keys_list_test.dart +++ b/test/device_keys_list_test.dart @@ -252,7 +252,7 @@ void main() { expect(req?.room != null, false); req = await client.userDeviceKeys['@alice:example.com'] - ?.startVerification(); + ?.startVerification(newDirectChatEnableEncryption: false); expect(req != null, true); expect(req?.room != null, true); }); diff --git a/test/encryption/key_verification_test.dart b/test/encryption/key_verification_test.dart index 8a622e44..770af7aa 100644 --- a/test/encryption/key_verification_test.dart +++ b/test/encryption/key_verification_test.dart @@ -119,7 +119,9 @@ void main() { client1.userDeviceKeys[client1.userID]!.masterKey! .setDirectVerified(false); final req1 = - await client1.userDeviceKeys[client2.userID]!.startVerification(); + await client1.userDeviceKeys[client2.userID]!.startVerification( + newDirectChatEnableEncryption: false, + ); var evt = getLastSentEvent(req1); expect(req1.state, KeyVerificationState.waitingAccept); @@ -221,8 +223,8 @@ void main() { client1.userDeviceKeys[client1.userID]!.masterKey! .setDirectVerified(true); await client1.encryption!.ssss.clearCache(); - final req1 = - await client1.userDeviceKeys[client2.userID]!.startVerification(); + final req1 = await client1.userDeviceKeys[client2.userID]! + .startVerification(newDirectChatEnableEncryption: false); expect(req1.state, KeyVerificationState.askSSSS); await req1.openSSSS(recoveryKey: ssssKey); await Future.delayed(Duration(seconds: 1)); @@ -241,8 +243,8 @@ void main() { // the other one has to have their master key verified to trigger asking for ssss client2.userDeviceKeys[client2.userID]!.masterKey! .setDirectVerified(true); - final req1 = - await client1.userDeviceKeys[client2.userID]!.startVerification(); + final req1 = await client1.userDeviceKeys[client2.userID]! + .startVerification(newDirectChatEnableEncryption: false); var evt = getLastSentEvent(req1); expect(req1.state, KeyVerificationState.waitingAccept); @@ -345,8 +347,8 @@ void main() { // make sure our master key is *not* verified to not triger SSSS for now client1.userDeviceKeys[client1.userID]!.masterKey! .setDirectVerified(false); - final req1 = - await client1.userDeviceKeys[client2.userID]!.startVerification(); + final req1 = await client1.userDeviceKeys[client2.userID]! + .startVerification(newDirectChatEnableEncryption: false); var evt = getLastSentEvent(req1); expect(req1.state, KeyVerificationState.waitingAccept); @@ -375,8 +377,8 @@ void main() { // make sure our master key is *not* verified to not triger SSSS for now client1.userDeviceKeys[client1.userID]!.masterKey! .setDirectVerified(false); - final req1 = - await client1.userDeviceKeys[client2.userID]!.startVerification(); + final req1 = await client1.userDeviceKeys[client2.userID]! + .startVerification(newDirectChatEnableEncryption: false); var evt = getLastSentEvent(req1); expect(req1.state, KeyVerificationState.waitingAccept); @@ -436,8 +438,8 @@ void main() { // make sure our master key is *not* verified to not triger SSSS for now client1.userDeviceKeys[client1.userID]!.masterKey! .setDirectVerified(false); - final req1 = - await client1.userDeviceKeys[client2.userID]!.startVerification(); + final req1 = await client1.userDeviceKeys[client2.userID]! + .startVerification(newDirectChatEnableEncryption: false); final evt = getLastSentEvent(req1); expect(req1.state, KeyVerificationState.waitingAccept);