Merge branch 'krille/implement-well-known' into 'master'

Krille/implement well known

Closes #51

See merge request famedly/famedlysdk!296
This commit is contained in:
Christian Pauly 2020-05-16 06:42:56 +00:00
commit 7c0d84b27f
3 changed files with 108 additions and 38 deletions

View file

@ -42,6 +42,7 @@ export 'package:famedlysdk/src/utils/states_map.dart';
export 'package:famedlysdk/src/utils/to_device_event.dart'; export 'package:famedlysdk/src/utils/to_device_event.dart';
export 'package:famedlysdk/src/utils/turn_server_credentials.dart'; export 'package:famedlysdk/src/utils/turn_server_credentials.dart';
export 'package:famedlysdk/src/utils/user_device.dart'; export 'package:famedlysdk/src/utils/user_device.dart';
export 'package:famedlysdk/src/utils/well_known_informations.dart';
export 'package:famedlysdk/src/account_data.dart'; export 'package:famedlysdk/src/account_data.dart';
export 'package:famedlysdk/src/client.dart'; export 'package:famedlysdk/src/client.dart';
export 'package:famedlysdk/src/event.dart'; export 'package:famedlysdk/src/event.dart';

View file

@ -55,6 +55,7 @@ import 'utils/matrix_exception.dart';
import 'utils/profile.dart'; import 'utils/profile.dart';
import 'database/database.dart' show Database; import 'database/database.dart' show Database;
import 'utils/pusher.dart'; import 'utils/pusher.dart';
import 'utils/well_known_informations.dart';
typedef RoomSorter = int Function(Room a, Room b); typedef RoomSorter = int Function(Room a, Room b);
@ -202,6 +203,16 @@ class Client {
return null; return null;
} }
/// Gets discovery information about the domain. The file may include additional keys.
Future<WellKnownInformations> getWellKnownInformationsByUserId(
String MatrixIdOrDomain,
) async {
final response = await http
.get('https://${MatrixIdOrDomain.domain}/.well-known/matrix/client');
final rawJson = json.decode(response.body);
return WellKnownInformations.fromJson(rawJson);
}
/// Checks the supported versions of the Matrix protocol and the supported /// Checks the supported versions of the Matrix protocol and the supported
/// login types. Returns false if the server is not compatible with the /// login types. Returns false if the server is not compatible with the
/// client. /// client.
@ -724,13 +735,25 @@ class Client {
if (database != null) { if (database != null) {
if (id != null) { if (id != null) {
await database.updateClient( await database.updateClient(
_homeserver, _accessToken, _userID, _deviceID, _homeserver,
_deviceName, prevBatch, pickledOlmAccount, id, _accessToken,
_userID,
_deviceID,
_deviceName,
prevBatch,
pickledOlmAccount,
id,
); );
} else { } else {
_id = await database.insertClient( _id = await database.insertClient(
clientName, _homeserver, _accessToken, _userID, _deviceID, clientName,
_deviceName, prevBatch, pickledOlmAccount, _homeserver,
_accessToken,
_userID,
_deviceID,
_deviceName,
prevBatch,
pickledOlmAccount,
); );
} }
_userDeviceKeys = await database.getUserDeviceKeys(id); _userDeviceKeys = await database.getUserDeviceKeys(id);
@ -763,8 +786,8 @@ class Client {
}); });
_olmAccount?.free(); _olmAccount?.free();
database?.clear(id); database?.clear(id);
_id = _accessToken = _homeserver = _id = _accessToken =
_userID = _deviceID = _deviceName = prevBatch = null; _homeserver = _userID = _deviceID = _deviceName = prevBatch = null;
_rooms = []; _rooms = [];
onLoginStateChanged.add(LoginState.loggedOut); onLoginStateChanged.add(LoginState.loggedOut);
} }
@ -979,7 +1002,8 @@ class Client {
} }
if (sync['account_data'] is Map<String, dynamic> && if (sync['account_data'] is Map<String, dynamic> &&
sync['account_data']['events'] is List<dynamic>) { sync['account_data']['events'] is List<dynamic>) {
_handleGlobalEvents(sync['account_data']['events'], 'account_data', dbActions); _handleGlobalEvents(
sync['account_data']['events'], 'account_data', dbActions);
} }
if (sync['device_lists'] is Map<String, dynamic>) { if (sync['device_lists'] is Map<String, dynamic>) {
_handleDeviceListsEvents(sync['device_lists'], dbActions); _handleDeviceListsEvents(sync['device_lists'], dbActions);
@ -1011,13 +1035,15 @@ class Client {
} }
} }
void _handleDeviceListsEvents(Map<String, dynamic> deviceLists, List<Future<dynamic> Function()> dbActions) { void _handleDeviceListsEvents(Map<String, dynamic> deviceLists,
List<Future<dynamic> Function()> dbActions) {
if (deviceLists['changed'] is List) { if (deviceLists['changed'] is List) {
for (final userId in deviceLists['changed']) { for (final userId in deviceLists['changed']) {
if (_userDeviceKeys.containsKey(userId)) { if (_userDeviceKeys.containsKey(userId)) {
_userDeviceKeys[userId].outdated = true; _userDeviceKeys[userId].outdated = true;
if (database != null) { if (database != null) {
dbActions.add(() => database.storeUserDeviceKeysInfo(id, userId, true)); dbActions
.add(() => database.storeUserDeviceKeysInfo(id, userId, true));
} }
} }
} }
@ -1056,7 +1082,8 @@ class Client {
} }
} }
void _handleRooms(Map<String, dynamic> rooms, Membership membership, List<Future<dynamic> Function()> dbActions) { void _handleRooms(Map<String, dynamic> rooms, Membership membership,
List<Future<dynamic> Function()> dbActions) {
rooms.forEach((String id, dynamic room) { rooms.forEach((String id, dynamic room) {
// calculate the notification counts, the limitedTimeline and prevbatch // calculate the notification counts, the limitedTimeline and prevbatch
num highlight_count = 0; num highlight_count = 0;
@ -1104,11 +1131,13 @@ class Client {
roomObj.resetSortOrder(); roomObj.resetSortOrder();
} }
if (database != null) { if (database != null) {
dbActions.add(() => database.storeRoomUpdate(this.id, update, getRoomById(id))); dbActions.add(
() => database.storeRoomUpdate(this.id, update, getRoomById(id)));
} }
onRoomUpdate.add(update); onRoomUpdate.add(update);
var handledEvents = false; var handledEvents = false;
/// Handle now all room events and save them in the database /// Handle now all room events and save them in the database
if (room['state'] is Map<String, dynamic> && if (room['state'] is Map<String, dynamic> &&
room['state']['events'] is List<dynamic> && room['state']['events'] is List<dynamic> &&
@ -1119,13 +1148,15 @@ class Client {
if (room['invite_state'] is Map<String, dynamic> && if (room['invite_state'] is Map<String, dynamic> &&
room['invite_state']['events'] is List<dynamic>) { room['invite_state']['events'] is List<dynamic>) {
_handleRoomEvents(id, room['invite_state']['events'], 'invite_state', dbActions); _handleRoomEvents(
id, room['invite_state']['events'], 'invite_state', dbActions);
} }
if (room['timeline'] is Map<String, dynamic> && if (room['timeline'] is Map<String, dynamic> &&
room['timeline']['events'] is List<dynamic> && room['timeline']['events'] is List<dynamic> &&
room['timeline']['events'].isNotEmpty) { room['timeline']['events'].isNotEmpty) {
_handleRoomEvents(id, room['timeline']['events'], 'timeline', dbActions); _handleRoomEvents(
id, room['timeline']['events'], 'timeline', dbActions);
handledEvents = true; handledEvents = true;
} }
@ -1136,7 +1167,8 @@ class Client {
if (room['account_data'] is Map<String, dynamic> && if (room['account_data'] is Map<String, dynamic> &&
room['account_data']['events'] is List<dynamic>) { room['account_data']['events'] is List<dynamic>) {
_handleRoomEvents(id, room['account_data']['events'], 'account_data', dbActions); _handleRoomEvents(
id, room['account_data']['events'], 'account_data', dbActions);
} }
if (handledEvents && database != null && roomObj != null) { if (handledEvents && database != null && roomObj != null) {
@ -1145,7 +1177,8 @@ class Client {
}); });
} }
void _handleEphemerals(String id, List<dynamic> events, List<Future<dynamic> Function()> dbActions) { void _handleEphemerals(String id, List<dynamic> events,
List<Future<dynamic> Function()> dbActions) {
for (num i = 0; i < events.length; i++) { for (num i = 0; i < events.length; i++) {
_handleEvent(events[i], id, 'ephemeral', dbActions); _handleEvent(events[i], id, 'ephemeral', dbActions);
@ -1167,11 +1200,10 @@ class Client {
final mxid = userTimestampMapEntry.key; final mxid = userTimestampMapEntry.key;
// Remove previous receipt event from this user // Remove previous receipt event from this user
if ( if (receiptStateContent[eventID] is Map<String, dynamic> &&
receiptStateContent[eventID] is Map<String, dynamic> && receiptStateContent[eventID]['m.read']
receiptStateContent[eventID]['m.read'] is Map<String, dynamic> && is Map<String, dynamic> &&
receiptStateContent[eventID]['m.read'].containsKey(mxid) receiptStateContent[eventID]['m.read'].containsKey(mxid)) {
) {
receiptStateContent[eventID]['m.read'].remove(mxid); receiptStateContent[eventID]['m.read'].remove(mxid);
} }
if (userTimestampMap[mxid] is Map<String, dynamic> && if (userTimestampMap[mxid] is Map<String, dynamic> &&
@ -1190,13 +1222,15 @@ class Client {
} }
} }
void _handleRoomEvents(String chat_id, List<dynamic> events, String type, List<Future<dynamic> Function()> dbActions) { void _handleRoomEvents(String chat_id, List<dynamic> events, String type,
List<Future<dynamic> Function()> dbActions) {
for (num i = 0; i < events.length; i++) { for (num i = 0; i < events.length; i++) {
_handleEvent(events[i], chat_id, type, dbActions); _handleEvent(events[i], chat_id, type, dbActions);
} }
} }
void _handleGlobalEvents(List<dynamic> events, String type, List<Future<dynamic> Function()> dbActions) { void _handleGlobalEvents(List<dynamic> events, String type,
List<Future<dynamic> Function()> dbActions) {
for (var i = 0; i < events.length; i++) { for (var i = 0; i < events.length; i++) {
if (events[i]['type'] is String && if (events[i]['type'] is String &&
events[i]['content'] is Map<String, dynamic>) { events[i]['content'] is Map<String, dynamic>) {
@ -1213,13 +1247,14 @@ class Client {
} }
} }
void _handleEvent(Map<String, dynamic> event, String roomID, String type, List<Future<dynamic> Function()> dbActions) { void _handleEvent(Map<String, dynamic> event, String roomID, String type,
List<Future<dynamic> Function()> dbActions) {
if (event['type'] is String && event['content'] is Map<String, dynamic>) { if (event['type'] is String && event['content'] is Map<String, dynamic>) {
// The client must ignore any new m.room.encryption event to prevent // The client must ignore any new m.room.encryption event to prevent
// man-in-the-middle attacks! // man-in-the-middle attacks!
final room = getRoomById(roomID); final room = getRoomById(roomID);
if (room == null || (event['type'] == 'm.room.encryption' && if (room == null ||
room.encrypted)) { (event['type'] == 'm.room.encryption' && room.encrypted)) {
return; return;
} }
@ -1328,7 +1363,8 @@ class Client {
if (eventUpdate.type == 'timeline' || if (eventUpdate.type == 'timeline' ||
eventUpdate.type == 'state' || eventUpdate.type == 'state' ||
eventUpdate.type == 'invite_state') { eventUpdate.type == 'invite_state') {
var stateEvent = Event.fromJson(eventUpdate.content, rooms[j], eventUpdate.sortOrder); var stateEvent =
Event.fromJson(eventUpdate.content, rooms[j], eventUpdate.sortOrder);
if (stateEvent.type == EventTypes.Redaction) { if (stateEvent.type == EventTypes.Redaction) {
final String redacts = eventUpdate.content['redacts']; final String redacts = eventUpdate.content['redacts'];
rooms[j].states.states.forEach( rooms[j].states.states.forEach(
@ -1517,7 +1553,7 @@ class Client {
final String deviceId = rawDeviceKeyEntry.key; final String deviceId = rawDeviceKeyEntry.key;
// Set the new device key for this device // Set the new device key for this device
if (!oldKeys.containsKey(deviceId)) { if (!oldKeys.containsKey(deviceId)) {
_userDeviceKeys[userId].deviceKeys[deviceId] = _userDeviceKeys[userId].deviceKeys[deviceId] =
DeviceKeys.fromJson(rawDeviceKeyEntry.value); DeviceKeys.fromJson(rawDeviceKeyEntry.value);
@ -1531,11 +1567,15 @@ class Client {
_userDeviceKeys[userId].deviceKeys[deviceId] = oldKeys[deviceId]; _userDeviceKeys[userId].deviceKeys[deviceId] = oldKeys[deviceId];
} }
if (database != null) { if (database != null) {
dbActions.add(() => database.storeUserDeviceKey(id, userId, deviceId, dbActions.add(() => database.storeUserDeviceKey(
json.encode(_userDeviceKeys[userId].deviceKeys[deviceId].toJson()), id,
_userDeviceKeys[userId].deviceKeys[deviceId].verified, userId,
_userDeviceKeys[userId].deviceKeys[deviceId].blocked, deviceId,
)); json.encode(
_userDeviceKeys[userId].deviceKeys[deviceId].toJson()),
_userDeviceKeys[userId].deviceKeys[deviceId].verified,
_userDeviceKeys[userId].deviceKeys[deviceId].blocked,
));
} }
} }
if (database != null) { if (database != null) {
@ -1543,13 +1583,15 @@ class Client {
final deviceId = oldDeviceKeyEntry.key; final deviceId = oldDeviceKeyEntry.key;
if (!_userDeviceKeys[userId].deviceKeys.containsKey(deviceId)) { if (!_userDeviceKeys[userId].deviceKeys.containsKey(deviceId)) {
// we need to remove an old key // we need to remove an old key
dbActions.add(() => database.removeUserDeviceKey(id, userId, deviceId)); dbActions.add(
() => database.removeUserDeviceKey(id, userId, deviceId));
} }
} }
} }
_userDeviceKeys[userId].outdated = false; _userDeviceKeys[userId].outdated = false;
if (database != null) { if (database != null) {
dbActions.add(() => database.storeUserDeviceKeysInfo(id, userId, false)); dbActions
.add(() => database.storeUserDeviceKeysInfo(id, userId, false));
} }
} }
} }
@ -1762,9 +1804,8 @@ class Client {
_olmSessions[curve25519IdentityKey] = []; _olmSessions[curve25519IdentityKey] = [];
} }
final ix = _olmSessions[curve25519IdentityKey] final ix = _olmSessions[curve25519IdentityKey]
.indexWhere((s) => s.session_id() == session.session_id()); .indexWhere((s) => s.session_id() == session.session_id());
if (ix == if (ix == -1) {
-1) {
// add a new session // add a new session
_olmSessions[curve25519IdentityKey].add(session); _olmSessions[curve25519IdentityKey].add(session);
} else { } else {
@ -1772,7 +1813,8 @@ class Client {
_olmSessions[curve25519IdentityKey][ix] = session; _olmSessions[curve25519IdentityKey][ix] = session;
} }
final pickle = session.pickle(userID); final pickle = session.pickle(userID);
database?.storeOlmSession(id, curve25519IdentityKey, session.session_id(), pickle); database?.storeOlmSession(
id, curve25519IdentityKey, session.session_id(), pickle);
} }
/// Sends an encrypted [message] of this [type] to these [deviceKeys]. To send /// Sends an encrypted [message] of this [type] to these [deviceKeys]. To send

View file

@ -0,0 +1,27 @@
class WellKnownInformations {
MHomeserver mHomeserver;
MHomeserver mIdentityServer;
Map<String, dynamic> content;
WellKnownInformations({this.mHomeserver, this.mIdentityServer});
WellKnownInformations.fromJson(Map<String, dynamic> json) {
content = json;
mHomeserver = json['m.homeserver'] != null
? MHomeserver.fromJson(json['m.homeserver'])
: null;
mIdentityServer = json['m.identity_server'] != null
? MHomeserver.fromJson(json['m.identity_server'])
: null;
}
}
class MHomeserver {
String baseUrl;
MHomeserver({this.baseUrl});
MHomeserver.fromJson(Map<String, dynamic> json) {
baseUrl = json['base_url'];
}
}