matrix-dart-sdk/test_driver/matrixsdk_test.dart

534 lines
21 KiB
Dart

/*
* Famedly Matrix SDK
* Copyright (C) 2019, 2020, 2021 Famedly GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import 'dart:io';
import 'package:hive/hive.dart';
import 'package:olm/olm.dart' as olm;
import 'package:test/test.dart';
import 'package:matrix/matrix.dart';
import '../test/fake_database.dart';
import 'test_config.dart';
const String testMessage = 'Hello world';
const String testMessage2 = 'Hello moon';
const String testMessage3 = 'Hello sun';
const String testMessage4 = 'Hello star';
const String testMessage5 = 'Hello earth';
const String testMessage6 = 'Hello mars';
void main() => group('Integration tests', () {
test('E2EE', () async {
Client? testClientA, testClientB;
try {
Hive.init(null);
await olm.init();
olm.Account();
Logs().i('[LibOlm] Enabled');
final homeserverUri = Uri.parse(homeserver);
Logs().i('++++ Using homeserver $homeserverUri ++++');
Logs().i('++++ Login Alice at ++++');
testClientA = Client('TestClientA', databaseBuilder: getDatabase);
await testClientA.checkHomeserver(homeserverUri);
await testClientA.login(
LoginType.mLoginPassword,
identifier: AuthenticationUserIdentifier(user: Users.user1.name),
password: Users.user1.password,
);
expect(testClientA.encryptionEnabled, true);
Logs().i('++++ Login Bob ++++');
testClientB = Client('TestClientB', databaseBuilder: getDatabase);
await testClientB.checkHomeserver(homeserverUri);
await testClientB.login(
LoginType.mLoginPassword,
identifier: AuthenticationUserIdentifier(user: Users.user2.name),
password: Users.user2.password,
);
expect(testClientB.encryptionEnabled, true);
Logs().i('++++ (Alice) Leave all rooms ++++');
while (testClientA.rooms.isNotEmpty) {
final room = testClientA.rooms.first;
if (room.canonicalAlias.isNotEmpty) {
break;
}
try {
await room.leave();
await room.forget();
} catch (_) {}
}
Logs().i('++++ (Bob) Leave all rooms ++++');
for (var i = 0; i < 3; i++) {
if (testClientB.rooms.isNotEmpty) {
final room = testClientB.rooms.first;
try {
await room.leave();
await room.forget();
} catch (_) {}
}
}
Logs().i('++++ Check if own olm device is verified by default ++++');
expect(testClientA.userDeviceKeys, contains(testClientA.userID));
expect(testClientA.userDeviceKeys[testClientA.userID]!.deviceKeys,
contains(testClientA.deviceID));
expect(
testClientA.userDeviceKeys[testClientA.userID]!
.deviceKeys[testClientA.deviceID!]!.verified,
isTrue);
expect(
!testClientA.userDeviceKeys[testClientA.userID]!
.deviceKeys[testClientA.deviceID!]!.blocked,
isTrue);
expect(testClientB.userDeviceKeys, contains(testClientB.userID));
expect(testClientB.userDeviceKeys[testClientB.userID]!.deviceKeys,
contains(testClientB.deviceID));
expect(
testClientB.userDeviceKeys[testClientB.userID]!
.deviceKeys[testClientB.deviceID!]!.verified,
isTrue);
expect(
!testClientB.userDeviceKeys[testClientB.userID]!
.deviceKeys[testClientB.deviceID!]!.blocked,
isTrue);
Logs().i('++++ (Alice) Create room and invite Bob ++++');
await testClientA.startDirectChat(
testClientB.userID!,
enableEncryption: false,
);
await Future.delayed(Duration(seconds: 1));
final room = testClientA.rooms.first;
final roomId = room.id;
Logs().i('++++ (Bob) Join room ++++');
final inviteRoom = testClientB.getRoomById(roomId)!;
await inviteRoom.join();
await Future.delayed(Duration(seconds: 1));
expect(inviteRoom.membership, Membership.join);
Logs().i('++++ (Alice) Enable encryption ++++');
expect(room.encrypted, false);
await room.enableEncryption();
var waitSeconds = 0;
while (!room.encrypted) {
await Future.delayed(Duration(seconds: 1));
waitSeconds++;
if (waitSeconds >= 60) {
throw Exception('Unable to enable encryption');
}
}
expect(room.encrypted, isTrue);
expect(
room.client.encryption!.keyManager
.getOutboundGroupSession(room.id),
isNull);
Logs().i('++++ (Alice) Check known olm devices ++++');
expect(testClientA.userDeviceKeys, contains(testClientB.userID));
expect(testClientA.userDeviceKeys[testClientB.userID]!.deviceKeys,
contains(testClientB.deviceID));
expect(
testClientA.userDeviceKeys[testClientB.userID]!
.deviceKeys[testClientB.deviceID!]!.verified,
isFalse);
expect(
testClientA.userDeviceKeys[testClientB.userID]!
.deviceKeys[testClientB.deviceID!]!.blocked,
isFalse);
expect(testClientB.userDeviceKeys, contains(testClientA.userID));
expect(testClientB.userDeviceKeys[testClientA.userID]!.deviceKeys,
contains(testClientA.deviceID));
expect(
testClientB.userDeviceKeys[testClientA.userID]!
.deviceKeys[testClientA.deviceID!]!.verified,
isFalse);
expect(
testClientB.userDeviceKeys[testClientA.userID]!
.deviceKeys[testClientA.deviceID!]!.blocked,
isFalse);
await Future.wait([
testClientA.updateUserDeviceKeys(),
testClientB.updateUserDeviceKeys(),
]);
await testClientA.userDeviceKeys[testClientB.userID]!
.deviceKeys[testClientB.deviceID!]!
.setVerified(true);
Logs().i('++++ Check if own olm device is verified by default ++++');
expect(testClientA.userDeviceKeys, contains(testClientA.userID));
expect(testClientA.userDeviceKeys[testClientA.userID]!.deviceKeys,
contains(testClientA.deviceID));
expect(
testClientA.userDeviceKeys[testClientA.userID]!
.deviceKeys[testClientA.deviceID!]!.verified,
isTrue);
expect(testClientB.userDeviceKeys, contains(testClientB.userID));
expect(testClientB.userDeviceKeys[testClientB.userID]!.deviceKeys,
contains(testClientB.deviceID));
expect(
testClientB.userDeviceKeys[testClientB.userID]!
.deviceKeys[testClientB.deviceID!]!.verified,
isTrue);
Logs().i("++++ (Alice) Send encrypted message: '$testMessage' ++++");
await room.sendTextEvent(testMessage);
await Future.delayed(Duration(seconds: 5));
expect(
room.client.encryption!.keyManager
.getOutboundGroupSession(room.id),
isNotNull);
var currentSessionIdA = room.client.encryption!.keyManager
.getOutboundGroupSession(room.id)!
.outboundGroupSession!
.session_id();
/*expect(room.client.encryption.keyManager
.getInboundGroupSession(room.id, currentSessionIdA, '') !=
null);*/
expect(
testClientA.encryption!.olmManager
.olmSessions[testClientB.identityKey]!.length,
olmLengthMatcher,
);
expect(
testClientB.encryption!.olmManager
.olmSessions[testClientA.identityKey]!.length,
olmLengthMatcher,
);
expect(
testClientA.encryption!.olmManager
.olmSessions[testClientB.identityKey]!.first.sessionId,
testClientB.encryption!.olmManager
.olmSessions[testClientA.identityKey]!.first.sessionId);
/*expect(inviteRoom.client.encryption.keyManager
.getInboundGroupSession(inviteRoom.id, currentSessionIdA, '') !=
null);*/
expect(room.lastEvent!.body, testMessage);
expect(inviteRoom.lastEvent!.body, testMessage);
Logs().i(
"++++ (Bob) Received decrypted message: '${inviteRoom.lastEvent!.body}' ++++");
Logs().i(
"++++ (Alice) Send again encrypted message: '$testMessage2' ++++");
await room.sendTextEvent(testMessage2);
await Future.delayed(Duration(seconds: 5));
expect(
testClientA.encryption!.olmManager
.olmSessions[testClientB.identityKey]!.length,
olmLengthMatcher,
);
expect(
testClientB.encryption!.olmManager
.olmSessions[testClientA.identityKey]!.length,
olmLengthMatcher,
);
expect(
testClientA.encryption!.olmManager
.olmSessions[testClientB.identityKey]!.first.sessionId,
testClientB.encryption!.olmManager
.olmSessions[testClientA.identityKey]!.first.sessionId);
expect(
room.client.encryption!.keyManager
.getOutboundGroupSession(room.id)!
.outboundGroupSession!
.session_id(),
currentSessionIdA);
/*expect(room.client.encryption.keyManager
.getInboundGroupSession(room.id, currentSessionIdA, '') !=
null);*/
expect(room.lastEvent!.body, testMessage2);
expect(inviteRoom.lastEvent!.body, testMessage2);
Logs().i(
"++++ (Bob) Received decrypted message: '${inviteRoom.lastEvent!.body}' ++++");
Logs().i(
"++++ (Bob) Send again encrypted message: '$testMessage3' ++++");
await inviteRoom.sendTextEvent(testMessage3);
await Future.delayed(Duration(seconds: 5));
expect(
testClientA.encryption!.olmManager
.olmSessions[testClientB.identityKey]!.length,
olmLengthMatcher,
);
expect(
testClientB.encryption!.olmManager
.olmSessions[testClientA.identityKey]!.length,
olmLengthMatcher);
expect(
room.client.encryption!.keyManager
.getOutboundGroupSession(room.id)!
.outboundGroupSession!
.session_id(),
currentSessionIdA);
final inviteRoomOutboundGroupSession = inviteRoom
.client.encryption!.keyManager
.getOutboundGroupSession(inviteRoom.id)!;
expect(inviteRoomOutboundGroupSession.isValid, isTrue);
/*expect(inviteRoom.client.encryption.keyManager.getInboundGroupSession(
inviteRoom.id,
inviteRoomOutboundGroupSession.outboundGroupSession.session_id(),
'') !=
null);
expect(room.client.encryption.keyManager.getInboundGroupSession(
room.id,
inviteRoomOutboundGroupSession.outboundGroupSession.session_id(),
'') !=
null);*/
expect(inviteRoom.lastEvent!.body, testMessage3);
expect(room.lastEvent!.body, testMessage3);
Logs().i(
"++++ (Alice) Received decrypted message: '${room.lastEvent!.body}' ++++");
Logs().i('++++ Login Bob in another client ++++');
final testClientC =
Client('TestClientC', databaseBuilder: getDatabase);
await testClientC.checkHomeserver(homeserverUri);
// We can't sign in using the displayname, since that breaks e2ee on dendrite: https://github.com/matrix-org/dendrite/issues/2914
await testClientC.login(
LoginType.mLoginPassword,
identifier: AuthenticationUserIdentifier(user: Users.user2.name),
password: Users.user2.password,
);
await Future.delayed(Duration(seconds: 3));
Logs().i(
"++++ (Alice) Send again encrypted message: '$testMessage4' ++++");
await room.sendTextEvent(testMessage4);
await Future.delayed(Duration(seconds: 7));
expect(
testClientA.encryption!.olmManager
.olmSessions[testClientB.identityKey]!.length,
olmLengthMatcher,
);
expect(
testClientB.encryption!.olmManager
.olmSessions[testClientA.identityKey]!.length,
olmLengthMatcher,
);
expect(
testClientA.encryption!.olmManager
.olmSessions[testClientB.identityKey]!.first.sessionId,
testClientB.encryption!.olmManager
.olmSessions[testClientA.identityKey]!.first.sessionId);
expect(
testClientA.encryption!.olmManager
.olmSessions[testClientC.identityKey]!.length,
olmLengthMatcher);
expect(
testClientC.encryption!.olmManager
.olmSessions[testClientA.identityKey]!.length,
olmLengthMatcher);
expect(
testClientA.encryption!.olmManager
.olmSessions[testClientC.identityKey]!.first.sessionId,
testClientC.encryption!.olmManager
.olmSessions[testClientA.identityKey]!.first.sessionId);
expect(
room.client.encryption!.keyManager
.getOutboundGroupSession(room.id)!
.outboundGroupSession!
.session_id(),
currentSessionIdA);
/*expect(inviteRoom.client.encryption.keyManager
.getInboundGroupSession(inviteRoom.id, currentSessionIdA, '') !=
null);*/
expect(room.lastEvent!.body, testMessage4);
expect(inviteRoom.lastEvent!.body, testMessage4);
Logs().i(
"++++ (Bob) Received decrypted message: '${inviteRoom.lastEvent!.body}' ++++");
Logs()
.i('++++ Logout Bob another client ${testClientC.deviceID} ++++');
await testClientC.dispose(closeDatabase: false);
await testClientC.logout();
await Future.delayed(Duration(seconds: 5));
Logs().i(
"++++ (Alice) Send again encrypted message: '$testMessage6' ++++");
await room.sendTextEvent(testMessage6);
await Future.delayed(Duration(seconds: 5));
expect(
testClientA.encryption!.olmManager
.olmSessions[testClientB.identityKey]!.length,
olmLengthMatcher,
);
expect(
testClientB.encryption!.olmManager
.olmSessions[testClientA.identityKey]!.length,
olmLengthMatcher,
);
expect(
testClientA.encryption!.olmManager
.olmSessions[testClientB.identityKey]!.first.sessionId,
testClientB.encryption!.olmManager
.olmSessions[testClientA.identityKey]!.first.sessionId);
// This does not work on conduit because of a server bug: https://gitlab.com/famedly/conduit/-/issues/325
if (Platform.environment['HOMESERVER_IMPLEMENTATION'] != 'conduit') {
expect(
room.client.encryption!.keyManager
.getOutboundGroupSession(room.id)!
.outboundGroupSession!
.session_id(),
isNot(currentSessionIdA));
}
currentSessionIdA = room.client.encryption!.keyManager
.getOutboundGroupSession(room.id)!
.outboundGroupSession!
.session_id();
/*expect(inviteRoom.client.encryption.keyManager
.getInboundGroupSession(inviteRoom.id, currentSessionIdA, '') !=
null);*/
expect(room.lastEvent!.body, testMessage6);
expect(inviteRoom.lastEvent!.body, testMessage6);
Logs().i(
"++++ (Bob) Received decrypted message: '${inviteRoom.lastEvent!.body}' ++++");
await room.leave();
await room.forget();
await inviteRoom.leave();
await inviteRoom.forget();
await Future.delayed(Duration(seconds: 1));
} catch (e, s) {
Logs().e('Test failed', e, s);
rethrow;
} finally {
Logs().i('++++ Logout Alice and Bob ++++');
if (testClientA?.isLogged() ?? false) await testClientA!.logoutAll();
if (testClientA?.isLogged() ?? false) await testClientB!.logoutAll();
await testClientA?.dispose(closeDatabase: false);
await testClientB?.dispose(closeDatabase: false);
testClientA = null;
testClientB = null;
}
return;
});
test('dm creation', () async {
Client? testClientA, testClientB;
try {
Hive.init(null);
await olm.init();
olm.Account();
Logs().i('[LibOlm] Enabled');
final homeserverUri = Uri.parse(homeserver);
Logs().i('++++ Using homeserver $homeserverUri ++++');
Logs().i('++++ Login Alice at ++++');
testClientA = Client('TestClientA', databaseBuilder: getDatabase);
await testClientA.checkHomeserver(homeserverUri);
await testClientA.login(
LoginType.mLoginPassword,
identifier: AuthenticationUserIdentifier(user: Users.user1.name),
password: Users.user1.password,
);
expect(testClientA.encryptionEnabled, true);
Logs().i('++++ Login Bob ++++');
testClientB = Client('TestClientB', databaseBuilder: getDatabase);
await testClientB.checkHomeserver(homeserverUri);
await testClientB.login(
LoginType.mLoginPassword,
identifier: AuthenticationUserIdentifier(user: Users.user2.name),
password: Users.user2.password,
);
expect(testClientB.encryptionEnabled, true);
Logs().i('++++ (Alice) Leave all rooms ++++');
while (testClientA.rooms.isNotEmpty) {
final room = testClientA.rooms.first;
if (room.canonicalAlias.isNotEmpty) {
break;
}
try {
await room.leave();
await room.forget();
} catch (_) {}
}
Logs().i('++++ (Bob) Leave all rooms ++++');
for (var i = 0; i < 3; i++) {
if (testClientB.rooms.isNotEmpty) {
final room = testClientB.rooms.first;
try {
await room.leave();
await room.forget();
} catch (_) {}
}
}
Logs().i('++++ (Alice) Create DM ++++');
final dmRoom = await testClientA.startDirectChat(testClientB.userID!);
if (testClientB.getRoomById(dmRoom) == null) {
await testClientB.waitForRoomInSync(dmRoom, invite: true);
}
// Wait at least for one additional sync to make sure the invite landed
// correctly. Workaround for synapse CI job failing.
await testClientB.onSync.stream.first;
Logs().i('++++ (Bob) Create DM ++++');
final dmRoomFromB =
await testClientB.startDirectChat(testClientA.userID!);
expect(dmRoom, dmRoomFromB,
reason:
"Bob should join alice's DM room instead of creating a new one");
expect(testClientB.getRoomById(dmRoom)?.membership, Membership.join,
reason: 'Room should actually be in the join state now.');
expect(testClientA.getRoomById(dmRoom)?.membership, Membership.join,
reason: 'Room should actually be in the join state now.');
} catch (e, s) {
Logs().e('Test failed', e, s);
rethrow;
} finally {
Logs().i('++++ Logout Alice and Bob ++++');
if (testClientA?.isLogged() ?? false) await testClientA!.logoutAll();
if (testClientA?.isLogged() ?? false) await testClientB!.logoutAll();
await testClientA?.dispose(closeDatabase: false);
await testClientB?.dispose(closeDatabase: false);
testClientA = null;
testClientB = null;
}
return;
});
}, timeout: Timeout(Duration(minutes: 6)));
Object get olmLengthMatcher {
return
// workarounding weird Dendrite bug
Platform.environment['HOMESERVER_IMPLEMENTATION'] != 'dendrite'
? 1
: predicate(
[1, 2].contains,
'is either 1 or two',
);
}