Merge branch 'soru/modularize-e2ee' into soru/cross-signing

This commit is contained in:
Sorunome 2020-06-05 18:59:58 +02:00
commit d29fb9abfe
No known key found for this signature in database
GPG Key ID: B19471D07FC9BE9C
116 changed files with 12418 additions and 4844 deletions

View File

@ -5,5 +5,7 @@ linter:
- camel_case_types
analyzer:
errors:
todo: ignore
# exclude:
# - path/to/excluded/files/**

23
lib/encryption.dart Normal file
View File

@ -0,0 +1,23 @@
/*
* Famedly Matrix SDK
* Copyright (C) 2020 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/>.
*/
library encryption;
export './encryption/encryption.dart';
export './encryption/key_manager.dart';
export './encryption/utils/key_verification.dart';

View File

@ -0,0 +1,283 @@
/*
* Famedly Matrix SDK
* Copyright (C) 2019, 2020 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:convert';
import 'package:famedlysdk/famedlysdk.dart';
import 'package:famedlysdk/matrix_api.dart';
import 'package:pedantic/pedantic.dart';
import 'key_manager.dart';
import 'olm_manager.dart';
import 'key_verification_manager.dart';
class Encryption {
final Client client;
final bool debug;
final bool enableE2eeRecovery;
bool get enabled => olmManager.enabled;
/// Returns the base64 encoded keys to store them in a store.
/// This String should **never** leave the device!
String get pickledOlmAccount => olmManager.pickledOlmAccount;
String get fingerprintKey => olmManager.fingerprintKey;
String get identityKey => olmManager.identityKey;
KeyManager keyManager;
OlmManager olmManager;
KeyVerificationManager keyVerificationManager;
Encryption({
this.client,
this.debug,
this.enableE2eeRecovery,
}) {
keyManager = KeyManager(this);
olmManager = OlmManager(this);
keyVerificationManager = KeyVerificationManager(this);
}
Future<void> init(String olmAccount) async {
await olmManager.init(olmAccount);
}
void handleDeviceOneTimeKeysCount(Map<String, int> countJson) {
olmManager.handleDeviceOneTimeKeysCount(countJson);
}
void onSync() {
keyVerificationManager.cleanup();
}
Future<void> handleToDeviceEvent(ToDeviceEvent event) async {
if (['m.room_key', 'm.room_key_request', 'm.forwarded_room_key']
.contains(event.type)) {
// a new room key or thelike. We need to handle this asap, before other
// events in /sync are handled
await keyManager.handleToDeviceEvent(event);
}
if (event.type.startsWith('m.key.verification.')) {
// some key verification event. No need to handle it now, we can easily
// do this in the background
unawaited(keyVerificationManager.handleToDeviceEvent(event));
}
}
Future<ToDeviceEvent> decryptToDeviceEvent(ToDeviceEvent event) async {
return await olmManager.decryptToDeviceEvent(event);
}
Event decryptRoomEventSync(String roomId, Event event) {
if (event.type != EventTypes.Encrypted ||
event.content['ciphertext'] == null) return event;
Map<String, dynamic> decryptedPayload;
try {
if (event.content['algorithm'] != 'm.megolm.v1.aes-sha2') {
throw (DecryptError.UNKNOWN_ALGORITHM);
}
final String sessionId = event.content['session_id'];
final String senderKey = event.content['sender_key'];
final inboundGroupSession =
keyManager.getInboundGroupSession(roomId, sessionId, senderKey);
if (inboundGroupSession == null) {
throw (DecryptError.UNKNOWN_SESSION);
}
final decryptResult = inboundGroupSession.inboundGroupSession
.decrypt(event.content['ciphertext']);
final messageIndexKey = event.eventId +
event.originServerTs.millisecondsSinceEpoch.toString();
var haveIndex = inboundGroupSession.indexes.containsKey(messageIndexKey);
if (haveIndex &&
inboundGroupSession.indexes[messageIndexKey] !=
decryptResult.message_index) {
// TODO: maybe clear outbound session, if it is ours
throw (DecryptError.CHANNEL_CORRUPTED);
}
inboundGroupSession.indexes[messageIndexKey] =
decryptResult.message_index;
if (!haveIndex) {
// now we persist the udpated indexes into the database.
// the entry should always exist. In the case it doesn't, the following
// line *could* throw an error. As that is a future, though, and we call
// it un-awaited here, nothing happens, which is exactly the result we want
client.database?.updateInboundGroupSessionIndexes(
json.encode(inboundGroupSession.indexes),
client.id,
roomId,
sessionId);
}
decryptedPayload = json.decode(decryptResult.plaintext);
} catch (exception) {
// alright, if this was actually by our own outbound group session, we might as well clear it
if (client.enableE2eeRecovery &&
(keyManager
.getOutboundGroupSession(roomId)
?.outboundGroupSession
?.session_id() ??
'') ==
event.content['session_id']) {
keyManager.clearOutboundGroupSession(roomId, wipe: true);
}
if (exception.toString() == DecryptError.UNKNOWN_SESSION) {
decryptedPayload = {
'content': event.content,
'type': EventTypes.Encrypted,
};
decryptedPayload['content']['body'] = exception.toString();
decryptedPayload['content']['msgtype'] = 'm.bad.encrypted';
} else {
decryptedPayload = {
'content': <String, dynamic>{
'msgtype': 'm.bad.encrypted',
'body': exception.toString(),
},
'type': EventTypes.Encrypted,
};
}
}
if (event.content['m.relates_to'] != null) {
decryptedPayload['content']['m.relates_to'] =
event.content['m.relates_to'];
}
return Event(
content: decryptedPayload['content'],
type: decryptedPayload['type'],
senderId: event.senderId,
eventId: event.eventId,
roomId: event.roomId,
room: event.room,
originServerTs: event.originServerTs,
unsigned: event.unsigned,
stateKey: event.stateKey,
prevContent: event.prevContent,
status: event.status,
sortOrder: event.sortOrder,
);
}
Future<Event> decryptRoomEvent(String roomId, Event event,
{bool store = false, String updateType = 'timeline'}) async {
final doStore = () async {
await client.database?.storeEventUpdate(
client.id,
EventUpdate(
eventType: event.type,
content: event.toJson(),
roomID: event.roomId,
type: updateType,
sortOrder: event.sortOrder,
),
);
if (updateType != 'history') {
event.room?.setState(event);
}
};
if (event.type != EventTypes.Encrypted) {
return event;
}
event = decryptRoomEventSync(roomId, event);
if (event.type != EventTypes.Encrypted) {
if (store) {
await doStore();
}
return event;
}
if (client.database == null) {
return event;
}
await keyManager.loadInboundGroupSession(
roomId, event.content['session_id'], event.content['sender_key']);
event = decryptRoomEventSync(roomId, event);
if (event.type != EventTypes.Encrypted && store) {
await doStore();
}
return event;
}
/// Encrypts the given json payload and creates a send-ready m.room.encrypted
/// payload. This will create a new outgoingGroupSession if necessary.
Future<Map<String, dynamic>> encryptGroupMessagePayload(
String roomId, Map<String, dynamic> payload,
{String type = EventTypes.Message}) async {
final room = client.getRoomById(roomId);
if (room == null || !room.encrypted || !enabled) {
return payload;
}
if (room.encryptionAlgorithm != 'm.megolm.v1.aes-sha2') {
throw ('Unknown encryption algorithm');
}
if (keyManager.getOutboundGroupSession(roomId) == null) {
await keyManager.loadOutboundGroupSession(roomId);
}
await keyManager.clearOutboundGroupSession(roomId);
if (keyManager.getOutboundGroupSession(roomId) == null) {
await keyManager.createOutboundGroupSession(roomId);
}
final sess = keyManager.getOutboundGroupSession(roomId);
if (sess == null) {
throw ('Unable to create new outbound group session');
}
final Map<String, dynamic> mRelatesTo = payload.remove('m.relates_to');
final payloadContent = {
'content': payload,
'type': type,
'room_id': roomId,
};
var encryptedPayload = <String, dynamic>{
'algorithm': 'm.megolm.v1.aes-sha2',
'ciphertext':
sess.outboundGroupSession.encrypt(json.encode(payloadContent)),
'device_id': client.deviceID,
'sender_key': identityKey,
'session_id': sess.outboundGroupSession.session_id(),
if (mRelatesTo != null) 'm.relates_to': mRelatesTo,
};
sess.sentMessages++;
await keyManager.storeOutboundGroupSession(roomId, sess);
return encryptedPayload;
}
Future<Map<String, dynamic>> encryptToDeviceMessagePayload(
DeviceKeys device, String type, Map<String, dynamic> payload) async {
return await olmManager.encryptToDeviceMessagePayload(
device, type, payload);
}
Future<Map<String, dynamic>> encryptToDeviceMessage(
List<DeviceKeys> deviceKeys,
String type,
Map<String, dynamic> payload) async {
return await olmManager.encryptToDeviceMessage(deviceKeys, type, payload);
}
void dispose() {
keyManager.dispose();
olmManager.dispose();
keyVerificationManager.dispose();
}
}
abstract class DecryptError {
static const String NOT_ENABLED = 'Encryption is not enabled in your client.';
static const String UNKNOWN_ALGORITHM = 'Unknown encryption algorithm.';
static const String UNKNOWN_SESSION =
'The sender has not sent us the session key.';
static const String CHANNEL_CORRUPTED =
'The secure channel with the sender was corrupted.';
}

View File

@ -0,0 +1,517 @@
/*
* Famedly Matrix SDK
* Copyright (C) 2019, 2020 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:convert';
import 'package:pedantic/pedantic.dart';
import 'package:famedlysdk/famedlysdk.dart';
import 'package:famedlysdk/matrix_api.dart';
import 'package:olm/olm.dart' as olm;
import './encryption.dart';
import './utils/session_key.dart';
import './utils/outbound_group_session.dart';
class KeyManager {
final Encryption encryption;
Client get client => encryption.client;
final outgoingShareRequests = <String, KeyManagerKeyShareRequest>{};
final incomingShareRequests = <String, KeyManagerKeyShareRequest>{};
final _inboundGroupSessions = <String, Map<String, SessionKey>>{};
final _outboundGroupSessions = <String, OutboundGroupSession>{};
final Set<String> _loadedOutboundGroupSessions = <String>{};
final Set<String> _requestedSessionIds = <String>{};
KeyManager(this.encryption);
/// clear all cached inbound group sessions. useful for testing
void clearInboundGroupSessions() {
_inboundGroupSessions.clear();
}
void setInboundGroupSession(String roomId, String sessionId, String senderKey,
Map<String, dynamic> content,
{bool forwarded = false}) {
final oldSession =
getInboundGroupSession(roomId, sessionId, senderKey, otherRooms: false);
if (oldSession != null) {
return;
}
if (content['algorithm'] != 'm.megolm.v1.aes-sha2') {
return;
}
olm.InboundGroupSession inboundGroupSession;
try {
inboundGroupSession = olm.InboundGroupSession();
if (forwarded) {
inboundGroupSession.import_session(content['session_key']);
} else {
inboundGroupSession.create(content['session_key']);
}
} catch (e) {
inboundGroupSession.free();
print(
'[LibOlm] Could not create new InboundGroupSession: ' + e.toString());
return;
}
if (!_inboundGroupSessions.containsKey(roomId)) {
_inboundGroupSessions[roomId] = <String, SessionKey>{};
}
_inboundGroupSessions[roomId][sessionId] = SessionKey(
content: content,
inboundGroupSession: inboundGroupSession,
indexes: {},
key: client.userID,
);
client.database?.storeInboundGroupSession(
client.id,
roomId,
sessionId,
inboundGroupSession.pickle(client.userID),
json.encode(content),
json.encode({}),
);
// TODO: somehow try to decrypt last message again
final room = client.getRoomById(roomId);
if (room != null) {
room.onSessionKeyReceived.add(sessionId);
}
}
SessionKey getInboundGroupSession(
String roomId, String sessionId, String senderKey,
{bool otherRooms = true}) {
if (_inboundGroupSessions.containsKey(roomId) &&
_inboundGroupSessions[roomId].containsKey(sessionId)) {
return _inboundGroupSessions[roomId][sessionId];
}
if (!otherRooms) {
return null;
}
// search if this session id is *somehow* found in another room
for (final val in _inboundGroupSessions.values) {
if (val.containsKey(sessionId)) {
return val[sessionId];
}
}
return null;
}
/// Loads an inbound group session
Future<SessionKey> loadInboundGroupSession(
String roomId, String sessionId, String senderKey) async {
if (roomId == null || sessionId == null || senderKey == null) {
return null;
}
if (_inboundGroupSessions.containsKey(roomId) &&
_inboundGroupSessions[roomId].containsKey(sessionId)) {
return _inboundGroupSessions[roomId][sessionId]; // nothing to do
}
final session = await client.database
?.getDbInboundGroupSession(client.id, roomId, sessionId);
if (session == null) {
final room = client.getRoomById(roomId);
final requestIdent = '$roomId|$sessionId|$senderKey';
if (client.enableE2eeRecovery &&
room != null &&
!_requestedSessionIds.contains(requestIdent)) {
// do e2ee recovery
_requestedSessionIds.add(requestIdent);
unawaited(request(room, sessionId, senderKey));
}
return null;
}
if (!_inboundGroupSessions.containsKey(roomId)) {
_inboundGroupSessions[roomId] = <String, SessionKey>{};
}
final sess = SessionKey.fromDb(session, client.userID);
if (!sess.isValid) {
return null;
}
_inboundGroupSessions[roomId][sessionId] = sess;
return sess;
}
/// clear all cached inbound group sessions. useful for testing
void clearOutboundGroupSessions() {
_outboundGroupSessions.clear();
}
/// Clears the existing outboundGroupSession but first checks if the participating
/// devices have been changed. Returns false if the session has not been cleared because
/// it wasn't necessary.
Future<bool> clearOutboundGroupSession(String roomId,
{bool wipe = false}) async {
final room = client.getRoomById(roomId);
final sess = getOutboundGroupSession(roomId);
if (room == null || sess == null) {
return true;
}
if (!wipe) {
// first check if the devices in the room changed
final deviceKeys = await room.getUserDeviceKeys();
deviceKeys.removeWhere((k) => k.blocked);
final deviceKeyIds = deviceKeys.map((k) => k.deviceId).toList();
deviceKeyIds.sort();
if (deviceKeyIds.toString() != sess.devices.toString()) {
wipe = true;
}
// next check if it needs to be rotated
final encryptionContent = room.getState(EventTypes.Encryption)?.content;
final maxMessages = encryptionContent != null &&
encryptionContent['rotation_period_msgs'] is int
? encryptionContent['rotation_period_msgs']
: 100;
final maxAge = encryptionContent != null &&
encryptionContent['rotation_period_ms'] is int
? encryptionContent['rotation_period_ms']
: 604800000; // default of one week
if (sess.sentMessages >= maxMessages ||
sess.creationTime
.add(Duration(milliseconds: maxAge))
.isBefore(DateTime.now())) {
wipe = true;
}
if (!wipe) {
return false;
}
}
sess.dispose();
_outboundGroupSessions.remove(roomId);
await client.database?.removeOutboundGroupSession(client.id, roomId);
return true;
}
Future<void> storeOutboundGroupSession(
String roomId, OutboundGroupSession sess) async {
if (sess == null) {
return;
}
await client.database?.storeOutboundGroupSession(
client.id,
roomId,
sess.outboundGroupSession.pickle(client.userID),
json.encode(sess.devices),
sess.creationTime,
sess.sentMessages);
}
Future<OutboundGroupSession> createOutboundGroupSession(String roomId) async {
await clearOutboundGroupSession(roomId, wipe: true);
final room = client.getRoomById(roomId);
if (room == null) {
return null;
}
final deviceKeys = await room.getUserDeviceKeys();
deviceKeys.removeWhere((k) => k.blocked);
final deviceKeyIds = deviceKeys.map((k) => k.deviceId).toList();
deviceKeyIds.sort();
final outboundGroupSession = olm.OutboundGroupSession();
try {
outboundGroupSession.create();
} catch (e) {
outboundGroupSession.free();
print('[LibOlm] Unable to create new outboundGroupSession: ' +
e.toString());
return null;
}
final rawSession = <String, dynamic>{
'algorithm': 'm.megolm.v1.aes-sha2',
'room_id': room.id,
'session_id': outboundGroupSession.session_id(),
'session_key': outboundGroupSession.session_key(),
};
setInboundGroupSession(
roomId, rawSession['session_id'], encryption.identityKey, rawSession);
final sess = OutboundGroupSession(
devices: deviceKeyIds,
creationTime: DateTime.now(),
outboundGroupSession: outboundGroupSession,
sentMessages: 0,
key: client.userID,
);
try {
await client.sendToDevice(deviceKeys, 'm.room_key', rawSession);
await storeOutboundGroupSession(roomId, sess);
_outboundGroupSessions[roomId] = sess;
} catch (e, s) {
print(
'[LibOlm] Unable to send the session key to the participating devices: ' +
e.toString());
print(s);
sess.dispose();
return null;
}
return sess;
}
OutboundGroupSession getOutboundGroupSession(String roomId) {
return _outboundGroupSessions[roomId];
}
Future<void> loadOutboundGroupSession(String roomId) async {
if (_loadedOutboundGroupSessions.contains(roomId) ||
_outboundGroupSessions.containsKey(roomId) ||
client.database == null) {
return; // nothing to do
}
_loadedOutboundGroupSessions.add(roomId);
final session =
await client.database.getDbOutboundGroupSession(client.id, roomId);
if (session == null) {
return;
}
final sess = OutboundGroupSession.fromDb(session, client.userID);
if (!sess.isValid) {
return;
}
_outboundGroupSessions[roomId] = sess;
}
/// Request a certain key from another device
Future<void> request(Room room, String sessionId, String senderKey) async {
// while we just send the to-device event to '*', we still need to save the
// devices themself to know where to send the cancel to after receiving a reply
final devices = await room.getUserDeviceKeys();
final requestId = client.generateUniqueTransactionId();
final request = KeyManagerKeyShareRequest(
requestId: requestId,
devices: devices,
room: room,
sessionId: sessionId,
senderKey: senderKey,
);
await client.sendToDevice(
[],
'm.room_key_request',
{
'action': 'request',
'body': {
'algorithm': 'm.megolm.v1.aes-sha2',
'room_id': room.id,
'sender_key': senderKey,
'session_id': sessionId,
},
'request_id': requestId,
'requesting_device_id': client.deviceID,
},
encrypted: false,
toUsers: await room.requestParticipants());
outgoingShareRequests[request.requestId] = request;
}
/// Handle an incoming to_device event that is related to key sharing
Future<void> handleToDeviceEvent(ToDeviceEvent event) async {
if (event.type == 'm.room_key_request') {
if (!event.content.containsKey('request_id')) {
return; // invalid event
}
if (event.content['action'] == 'request') {
// we are *receiving* a request
if (!event.content.containsKey('body')) {
return; // no body
}
if (!client.userDeviceKeys.containsKey(event.sender) ||
!client.userDeviceKeys[event.sender].deviceKeys
.containsKey(event.content['requesting_device_id'])) {
return; // device not found
}
final device = client.userDeviceKeys[event.sender]
.deviceKeys[event.content['requesting_device_id']];
if (device.userId == client.userID &&
device.deviceId == client.deviceID) {
return; // ignore requests by ourself
}
final room = client.getRoomById(event.content['body']['room_id']);
if (room == null) {
return; // unknown room
}
final sessionId = event.content['body']['session_id'];
final senderKey = event.content['body']['sender_key'];
// okay, let's see if we have this session at all
if ((await loadInboundGroupSession(room.id, sessionId, senderKey)) ==
null) {
return; // we don't have this session anyways
}
final request = KeyManagerKeyShareRequest(
requestId: event.content['request_id'],
devices: [device],
room: room,
sessionId: sessionId,
senderKey: senderKey,
);
if (incomingShareRequests.containsKey(request.requestId)) {
return; // we don't want to process one and the same request multiple times
}
incomingShareRequests[request.requestId] = request;
final roomKeyRequest =
RoomKeyRequest.fromToDeviceEvent(event, this, request);
if (device.userId == client.userID &&
device.verified &&
!device.blocked) {
// alright, we can forward the key
await roomKeyRequest.forwardKey();
} else {
client.onRoomKeyRequest
.add(roomKeyRequest); // let the client handle this
}
} else if (event.content['action'] == 'request_cancellation') {
// we got told to cancel an incoming request
if (!incomingShareRequests.containsKey(event.content['request_id'])) {
return; // we don't know this request anyways
}
// alright, let's just cancel this request
final request = incomingShareRequests[event.content['request_id']];
request.canceled = true;
incomingShareRequests.remove(request.requestId);
}
} else if (event.type == 'm.forwarded_room_key') {
// we *received* an incoming key request
if (event.encryptedContent == null) {
return; // event wasn't encrypted, this is a security risk
}
final request = outgoingShareRequests.values.firstWhere(
(r) =>
r.room.id == event.content['room_id'] &&
r.sessionId == event.content['session_id'] &&
r.senderKey == event.content['sender_key'],
orElse: () => null);
if (request == null || request.canceled) {
return; // no associated request found or it got canceled
}
final device = request.devices.firstWhere(
(d) =>
d.userId == event.sender &&
d.curve25519Key == event.encryptedContent['sender_key'],
orElse: () => null);
if (device == null) {
return; // someone we didn't send our request to replied....better ignore this
}
// TODO: verify that the keys work to decrypt a message
// alright, all checks out, let's go ahead and store this session
setInboundGroupSession(
request.room.id, request.sessionId, request.senderKey, event.content,
forwarded: true);
request.devices.removeWhere(
(k) => k.userId == device.userId && k.deviceId == device.deviceId);
outgoingShareRequests.remove(request.requestId);
// send cancel to all other devices
if (request.devices.isEmpty) {
return; // no need to send any cancellation
}
await client.sendToDevice(
request.devices,
'm.room_key_request',
{
'action': 'request_cancellation',
'request_id': request.requestId,
'requesting_device_id': client.deviceID,
},
encrypted: false);
} else if (event.type == 'm.room_key') {
if (event.encryptedContent == null) {
return; // the event wasn't encrypted, this is a security risk;
}
final String roomId = event.content['room_id'];
final String sessionId = event.content['session_id'];
if (client.userDeviceKeys.containsKey(event.sender) &&
client.userDeviceKeys[event.sender].deviceKeys
.containsKey(event.content['requesting_device_id'])) {
event.content['sender_claimed_ed25519_key'] = client
.userDeviceKeys[event.sender]
.deviceKeys[event.content['requesting_device_id']]
.ed25519Key;
}
setInboundGroupSession(roomId, sessionId,
event.encryptedContent['sender_key'], event.content,
forwarded: false);
}
}
void dispose() {
for (final sess in _outboundGroupSessions.values) {
sess.dispose();
}
for (final entries in _inboundGroupSessions.values) {
for (final sess in entries.values) {
sess.dispose();
}
}
}
}
class KeyManagerKeyShareRequest {
final String requestId;
final List<DeviceKeys> devices;
final Room room;
final String sessionId;
final String senderKey;
bool canceled;
KeyManagerKeyShareRequest(
{this.requestId,
this.devices,
this.room,
this.sessionId,
this.senderKey,
this.canceled = false});
}
class RoomKeyRequest extends ToDeviceEvent {
KeyManager keyManager;
KeyManagerKeyShareRequest request;
RoomKeyRequest.fromToDeviceEvent(ToDeviceEvent toDeviceEvent,
KeyManager keyManager, KeyManagerKeyShareRequest request) {
this.keyManager = keyManager;
this.request = request;
sender = toDeviceEvent.sender;
content = toDeviceEvent.content;
type = toDeviceEvent.type;
}
Room get room => request.room;
DeviceKeys get requestingDevice => request.devices.first;
Future<void> forwardKey() async {
if (request.canceled) {
keyManager.incomingShareRequests.remove(request.requestId);
return; // request is canceled, don't send anything
}
var room = this.room;
final session = await keyManager.loadInboundGroupSession(
room.id, request.sessionId, request.senderKey);
var forwardedKeys = <dynamic>[keyManager.encryption.identityKey];
for (final key in session.forwardingCurve25519KeyChain) {
forwardedKeys.add(key);
}
await requestingDevice.setVerified(true, keyManager.client);
var message = session.content;
message['forwarding_curve25519_key_chain'] = forwardedKeys;
message['session_key'] = session.inboundGroupSession
.export_session(session.inboundGroupSession.first_known_index());
// send the actual reply of the key back to the requester
await keyManager.client.sendToDevice(
[requestingDevice],
'm.forwarded_room_key',
message,
);
keyManager.incomingShareRequests.remove(request.requestId);
}
}

View File

@ -0,0 +1,83 @@
/*
* Famedly Matrix SDK
* Copyright (C) 2020 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 'package:famedlysdk/famedlysdk.dart';
import './encryption.dart';
import './utils/key_verification.dart';
class KeyVerificationManager {
final Encryption encryption;
Client get client => encryption.client;
KeyVerificationManager(this.encryption);
final Map<String, KeyVerification> _requests = {};
Future<void> cleanup() async {
for (final entry in _requests.entries) {
var dispose = entry.value.canceled ||
entry.value.state == KeyVerificationState.done ||
entry.value.state == KeyVerificationState.error;
if (!dispose) {
dispose = !(await entry.value.verifyActivity());
}
if (dispose) {
entry.value.dispose();
_requests.remove(entry.key);
}
}
}
void addRequest(KeyVerification request) {
if (request.transactionId == null) {
return;
}
_requests[request.transactionId] = request;
}
Future<void> handleToDeviceEvent(ToDeviceEvent event) async {
if (!event.type.startsWith('m.key.verification')) {
return;
}
// we have key verification going on!
final transactionId = KeyVerification.getTransactionId(event.content);
if (transactionId == null) {
return; // TODO: send cancel with unknown transaction id
}
if (_requests.containsKey(transactionId)) {
await _requests[transactionId].handlePayload(event.type, event.content);
} else {
final newKeyRequest =
KeyVerification(encryption: encryption, userId: event.sender);
await newKeyRequest.handlePayload(event.type, event.content);
if (newKeyRequest.state != KeyVerificationState.askAccept) {
// okay, something went wrong (unknown transaction id?), just dispose it
newKeyRequest.dispose();
} else {
_requests[transactionId] = newKeyRequest;
client.onKeyVerificationRequest.add(newKeyRequest);
}
}
}
void dispose() {
for (final req in _requests.values) {
req.dispose();
}
}
}

View File

@ -0,0 +1,433 @@
/*
* Famedly Matrix SDK
* Copyright (C) 2019, 2020 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:convert';
import 'package:canonical_json/canonical_json.dart';
import 'package:famedlysdk/famedlysdk.dart';
import 'package:famedlysdk/matrix_api.dart';
import 'package:olm/olm.dart' as olm;
import './encryption.dart';
class OlmManager {
final Encryption encryption;
Client get client => encryption.client;
olm.Account _olmAccount;
/// Returns the base64 encoded keys to store them in a store.
/// This String should **never** leave the device!
String get pickledOlmAccount =>
enabled ? _olmAccount.pickle(client.userID) : null;
String get fingerprintKey =>
enabled ? json.decode(_olmAccount.identity_keys())['ed25519'] : null;
String get identityKey =>
enabled ? json.decode(_olmAccount.identity_keys())['curve25519'] : null;
bool get enabled => _olmAccount != null;
OlmManager(this.encryption);
/// A map from Curve25519 identity keys to existing olm sessions.
Map<String, List<olm.Session>> get olmSessions => _olmSessions;
final Map<String, List<olm.Session>> _olmSessions = {};
Future<void> init(String olmAccount) async {
if (olmAccount == null) {
try {
await olm.init();
_olmAccount = olm.Account();
_olmAccount.create();
if (await uploadKeys(uploadDeviceKeys: true) == false) {
throw ('Upload key failed');
}
} catch (_) {
_olmAccount?.free();
_olmAccount = null;
}
} else {
try {
await olm.init();
_olmAccount = olm.Account();
_olmAccount.unpickle(client.userID, olmAccount);
} catch (_) {
_olmAccount?.free();
_olmAccount = null;
}
}
}
/// Adds a signature to this json from this olm account.
Map<String, dynamic> signJson(Map<String, dynamic> payload) {
if (!enabled) throw ('Encryption is disabled');
final Map<String, dynamic> unsigned = payload['unsigned'];
final Map<String, dynamic> signatures = payload['signatures'];
payload.remove('unsigned');
payload.remove('signatures');
final canonical = canonicalJson.encode(payload);
final signature = _olmAccount.sign(String.fromCharCodes(canonical));
if (signatures != null) {
payload['signatures'] = signatures;
} else {
payload['signatures'] = <String, dynamic>{};
}
if (!payload['signatures'].containsKey(client.userID)) {
payload['signatures'][client.userID] = <String, dynamic>{};
}
payload['signatures'][client.userID]['ed25519:${client.deviceID}'] =
signature;
if (unsigned != null) {
payload['unsigned'] = unsigned;
}
return payload;
}
/// Checks the signature of a signed json object.
bool checkJsonSignature(String key, Map<String, dynamic> signedJson,
String userId, String deviceId) {
if (!enabled) throw ('Encryption is disabled');
final Map<String, dynamic> signatures = signedJson['signatures'];
if (signatures == null || !signatures.containsKey(userId)) return false;
signedJson.remove('unsigned');
signedJson.remove('signatures');
if (!signatures[userId].containsKey('ed25519:$deviceId')) return false;
final String signature = signatures[userId]['ed25519:$deviceId'];
final canonical = canonicalJson.encode(signedJson);
final message = String.fromCharCodes(canonical);
var isValid = false;
final olmutil = olm.Utility();
try {
olmutil.ed25519_verify(key, message, signature);
isValid = true;
} catch (e) {
isValid = false;
print('[LibOlm] Signature check failed: ' + e.toString());
} finally {
olmutil.free();
}
return isValid;
}
/// Generates new one time keys, signs everything and upload it to the server.
Future<bool> uploadKeys(
{bool uploadDeviceKeys = false, int oldKeyCount = 0}) async {
if (!enabled) {
return true;
}
// generate one-time keys
// we generate 2/3rds of max, so that other keys people may still have can
// still be used
final oneTimeKeysCount =
(_olmAccount.max_number_of_one_time_keys() * 2 / 3).floor() -
oldKeyCount;
_olmAccount.generate_one_time_keys(oneTimeKeysCount);
final Map<String, dynamic> oneTimeKeys =
json.decode(_olmAccount.one_time_keys());
// now sign all the one-time keys
final signedOneTimeKeys = <String, dynamic>{};
for (final entry in oneTimeKeys['curve25519'].entries) {
final key = entry.key;
final value = entry.value;
signedOneTimeKeys['signed_curve25519:$key'] = <String, dynamic>{};
signedOneTimeKeys['signed_curve25519:$key'] = signJson({
'key': value,
});
}
// and now generate the payload to upload
final keysContent = <String, dynamic>{
if (uploadDeviceKeys)
'device_keys': {
'user_id': client.userID,
'device_id': client.deviceID,
'algorithms': [
'm.olm.v1.curve25519-aes-sha2',
'm.megolm.v1.aes-sha2'
],
'keys': <String, dynamic>{},
},
};
if (uploadDeviceKeys) {
final Map<String, dynamic> keys =
json.decode(_olmAccount.identity_keys());
for (final entry in keys.entries) {
final algorithm = entry.key;
final value = entry.value;
keysContent['device_keys']['keys']['$algorithm:${client.deviceID}'] =
value;
}
keysContent['device_keys'] =
signJson(keysContent['device_keys'] as Map<String, dynamic>);
}
final response = await client.api.uploadDeviceKeys(
deviceKeys: uploadDeviceKeys
? MatrixDeviceKeys.fromJson(keysContent['device_keys'])
: null,
oneTimeKeys: signedOneTimeKeys,
);
_olmAccount.mark_keys_as_published();
await client.database?.updateClientKeys(pickledOlmAccount, client.id);
return response['signed_curve25519'] == oneTimeKeysCount;
}
void handleDeviceOneTimeKeysCount(Map<String, int> countJson) {
if (!enabled) {
return;
}
// Check if there are at least half of max_number_of_one_time_keys left on the server
// and generate and upload more if not.
if (countJson.containsKey('signed_curve25519') &&
countJson['signed_curve25519'] <
(_olmAccount.max_number_of_one_time_keys() / 2)) {
uploadKeys(oldKeyCount: countJson['signed_curve25519']);
}
}
void storeOlmSession(String curve25519IdentityKey, olm.Session session) {
if (client.database == null) {
return;
}
if (!_olmSessions.containsKey(curve25519IdentityKey)) {
_olmSessions[curve25519IdentityKey] = [];
}
final ix = _olmSessions[curve25519IdentityKey]
.indexWhere((s) => s.session_id() == session.session_id());
if (ix == -1) {
// add a new session
_olmSessions[curve25519IdentityKey].add(session);
} else {
// update an existing session
_olmSessions[curve25519IdentityKey][ix] = session;
}
final pickle = session.pickle(client.userID);
client.database.storeOlmSession(
client.id, curve25519IdentityKey, session.session_id(), pickle);
}
ToDeviceEvent _decryptToDeviceEvent(ToDeviceEvent event) {
if (event.type != EventTypes.Encrypted) {
return event;
}
if (event.content['algorithm'] != 'm.olm.v1.curve25519-aes-sha2') {
throw ('Unknown algorithm: ${event.content}');
}
if (!event.content['ciphertext'].containsKey(identityKey)) {
throw ("The message isn't sent for this device");
}
String plaintext;
final String senderKey = event.content['sender_key'];
final String body = event.content['ciphertext'][identityKey]['body'];
final int type = event.content['ciphertext'][identityKey]['type'];
if (type != 0 && type != 1) {
throw ('Unknown message type');
}
var existingSessions = olmSessions[senderKey];
if (existingSessions != null) {
for (var session in existingSessions) {
if (type == 0 && session.matches_inbound(body) == true) {
plaintext = session.decrypt(type, body);
storeOlmSession(senderKey, session);
break;
} else if (type == 1) {
try {
plaintext = session.decrypt(type, body);
storeOlmSession(senderKey, session);
break;
} catch (_) {
plaintext = null;
}
}
}
}
if (plaintext == null && type != 0) {
return event;
}
if (plaintext == null) {
var newSession = olm.Session();
try {
newSession.create_inbound_from(_olmAccount, senderKey, body);
_olmAccount.remove_one_time_keys(newSession);
client.database?.updateClientKeys(pickledOlmAccount, client.id);
plaintext = newSession.decrypt(type, body);
storeOlmSession(senderKey, newSession);
} catch (_) {
newSession?.free();
rethrow;
}
}
final Map<String, dynamic> plainContent = json.decode(plaintext);
if (plainContent.containsKey('sender') &&
plainContent['sender'] != event.sender) {
throw ("Message was decrypted but sender doesn't match");
}
if (plainContent.containsKey('recipient') &&
plainContent['recipient'] != client.userID) {
throw ("Message was decrypted but recipient doesn't match");
}
if (plainContent['recipient_keys'] is Map &&
plainContent['recipient_keys']['ed25519'] is String &&
plainContent['recipient_keys']['ed25519'] != fingerprintKey) {
throw ("Message was decrypted but own fingerprint Key doesn't match");
}
return ToDeviceEvent(
content: plainContent['content'],
encryptedContent: event.content,
type: plainContent['type'],
sender: event.sender,
);
}
Future<ToDeviceEvent> decryptToDeviceEvent(ToDeviceEvent event) async {
if (event.type != EventTypes.Encrypted) {
return event;
}
final senderKey = event.content['sender_key'];
final loadFromDb = () async {
if (client.database == null) {
return false;
}
final sessions = await client.database
.getSingleOlmSessions(client.id, senderKey, client.userID);
if (sessions.isEmpty) {
return false; // okay, can't do anything
}
_olmSessions[senderKey] = sessions;
return true;
};
if (!_olmSessions.containsKey(senderKey)) {
await loadFromDb();
}
event = _decryptToDeviceEvent(event);
if (event.type != EventTypes.Encrypted || !(await loadFromDb())) {
return event;
}
// retry to decrypt!
return _decryptToDeviceEvent(event);
}
Future<void> startOutgoingOlmSessions(List<DeviceKeys> deviceKeys) async {
var requestingKeysFrom = <String, Map<String, String>>{};
for (var device in deviceKeys) {
if (requestingKeysFrom[device.userId] == null) {
requestingKeysFrom[device.userId] = {};
}
requestingKeysFrom[device.userId][device.deviceId] = 'signed_curve25519';
}
final response =
await client.api.requestOneTimeKeys(requestingKeysFrom, timeout: 10000);
for (var userKeysEntry in response.oneTimeKeys.entries) {
final userId = userKeysEntry.key;
for (var deviceKeysEntry in userKeysEntry.value.entries) {
final deviceId = deviceKeysEntry.key;
final fingerprintKey =
client.userDeviceKeys[userId].deviceKeys[deviceId].ed25519Key;
final identityKey =
client.userDeviceKeys[userId].deviceKeys[deviceId].curve25519Key;
for (Map<String, dynamic> deviceKey in deviceKeysEntry.value.values) {
if (!checkJsonSignature(
fingerprintKey, deviceKey, userId, deviceId)) {
continue;
}
try {
var session = olm.Session();
session.create_outbound(_olmAccount, identityKey, deviceKey['key']);
await storeOlmSession(identityKey, session);
} catch (e) {
print('[LibOlm] Could not create new outbound olm session: ' +
e.toString());
}
}
}
}
}
Future<Map<String, dynamic>> encryptToDeviceMessagePayload(
DeviceKeys device, String type, Map<String, dynamic> payload) async {
var sess = olmSessions[device.curve25519Key];
if (sess == null || sess.isEmpty) {
final sessions = await client.database
.getSingleOlmSessions(client.id, device.curve25519Key, client.userID);
if (sessions.isEmpty) {
throw ('No olm session found');
}
sess = _olmSessions[device.curve25519Key] = sessions;
}
sess.sort((a, b) => a.session_id().compareTo(b.session_id()));
final fullPayload = {
'type': type,
'content': payload,
'sender': client.userID,
'keys': {'ed25519': fingerprintKey},
'recipient': device.userId,
'recipient_keys': {'ed25519': device.ed25519Key},
};
final encryptResult = sess.first.encrypt(json.encode(fullPayload));
storeOlmSession(device.curve25519Key, sess.first);
final encryptedBody = <String, dynamic>{
'algorithm': 'm.olm.v1.curve25519-aes-sha2',
'sender_key': identityKey,
'ciphertext': <String, dynamic>{},
};
encryptedBody['ciphertext'][device.curve25519Key] = {
'type': encryptResult.type,
'body': encryptResult.body,
};
return encryptedBody;
}
Future<Map<String, dynamic>> encryptToDeviceMessage(
List<DeviceKeys> deviceKeys,
String type,
Map<String, dynamic> payload) async {
var data = <String, Map<String, Map<String, dynamic>>>{};
final deviceKeysWithoutSession = List<DeviceKeys>.from(deviceKeys);
deviceKeysWithoutSession.removeWhere((DeviceKeys deviceKeys) =>
olmSessions.containsKey(deviceKeys.curve25519Key));
if (deviceKeysWithoutSession.isNotEmpty) {
await startOutgoingOlmSessions(deviceKeysWithoutSession);
}
for (final device in deviceKeys) {
if (!data.containsKey(device.userId)) {
data[device.userId] = {};
}
try {
data[device.userId][device.deviceId] =
await encryptToDeviceMessagePayload(device, type, payload);
} catch (e) {
print('[LibOlm] Error encrypting to-device event: ' + e.toString());
continue;
}
}
return data;
}
void dispose() {
for (final sessions in olmSessions.values) {
for (final sess in sessions) {
sess.free();
}
}
_olmAccount?.free();
_olmAccount = null;
}
}

View File

@ -1,10 +1,29 @@
/*
* Famedly Matrix SDK
* Copyright (C) 2020 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:typed_data';
import 'package:canonical_json/canonical_json.dart';
import 'package:pedantic/pedantic.dart';
import 'package:olm/olm.dart' as olm;
import 'device_keys_list.dart';
import '../client.dart';
import '../room.dart';
import 'package:famedlysdk/famedlysdk.dart';
import 'package:famedlysdk/matrix_api.dart';
import '../encryption.dart';
/*
+-------------+ +-----------+
@ -55,6 +74,9 @@ enum KeyVerificationState {
enum KeyVerificationMethod { emoji, numbers }
List<String> _intersect(List<String> a, List<dynamic> b) {
if (b == null || a == null) {
return [];
}
final res = <String>[];
for (final v in a) {
if (b.contains(v)) {
@ -94,7 +116,8 @@ _KeyVerificationMethod _makeVerificationMethod(
class KeyVerification {
String transactionId;
final Client client;
final Encryption encryption;
Client get client => encryption.client;
final Room room;
final String userId;
void Function() onUpdate;
@ -116,7 +139,11 @@ class KeyVerification {
String canceledReason;
KeyVerification(
{this.client, this.room, this.userId, String deviceId, this.onUpdate}) {
{this.encryption,
this.room,
this.userId,
String deviceId,
this.onUpdate}) {
lastActivity = DateTime.now();
_deviceId ??= deviceId;
print('Setting device id constructor: ' + _deviceId.toString());
@ -489,12 +516,12 @@ class KeyVerification {
payload['to'] = userId;
payload['body'] =
'Attempting verification request. (${type}) Apparently your client doesn\'t support this';
type = 'm.room.message';
type = EventTypes.Message;
}
final newTransactionId = await room.sendEvent(payload, type: type);
if (transactionId == null) {
transactionId = newTransactionId;
client.addKeyVerificationRequest(this);
encryption.keyVerificationManager.addRequest(this);
}
} else {
await client.sendToDevice(
@ -514,10 +541,9 @@ class KeyVerification {
abstract class _KeyVerificationMethod {
KeyVerification request;
Client client;
_KeyVerificationMethod({this.request}) {
client = request.client;
}
Encryption get encryption => request.encryption;
Client get client => request.client;
_KeyVerificationMethod({this.request});
Future<void> handlePayload(String type, Map<String, dynamic> payload);
bool validateStart(Map<String, dynamic> payload) {
@ -788,7 +814,7 @@ class _KeyVerificationMethodSas extends _KeyVerificationMethod {
// we would also add the cross signing key here
final deviceKeyId = 'ed25519:${client.deviceID}';
mac[deviceKeyId] =
_calculateMac(client.fingerprintKey, baseInfo + deviceKeyId);
_calculateMac(encryption.fingerprintKey, baseInfo + deviceKeyId);
keyList.add(deviceKeyId);
final masterKey = client.userDeviceKeys.containsKey(client.userID)

View File

@ -0,0 +1,58 @@
/*
* Famedly Matrix SDK
* Copyright (C) 2019, 2020 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:convert';
import 'package:olm/olm.dart' as olm;
import '../../src/database/database.dart' show DbOutboundGroupSession;
class OutboundGroupSession {
List<String> devices;
DateTime creationTime;
olm.OutboundGroupSession outboundGroupSession;
int sentMessages;
bool get isValid => outboundGroupSession != null;
final String key;
OutboundGroupSession(
{this.devices,
this.creationTime,
this.outboundGroupSession,
this.sentMessages,
this.key});
OutboundGroupSession.fromDb(DbOutboundGroupSession dbEntry, String key)
: key = key {
outboundGroupSession = olm.OutboundGroupSession();
try {
outboundGroupSession.unpickle(key, dbEntry.pickle);
devices = List<String>.from(json.decode(dbEntry.deviceIds));
creationTime = dbEntry.creationTime;
sentMessages = dbEntry.sentMessages;
} catch (e) {
dispose();
print(
'[LibOlm] Unable to unpickle outboundGroupSession: ' + e.toString());
}
}
void dispose() {
outboundGroupSession?.free();
outboundGroupSession = null;
}
}

View File

@ -0,0 +1,76 @@
/*
* Famedly Matrix SDK
* Copyright (C) 2019, 2020 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:convert';
import 'package:olm/olm.dart' as olm;
import 'package:famedlysdk/famedlysdk.dart';
import '../../src/database/database.dart' show DbInboundGroupSession;
class SessionKey {
Map<String, dynamic> content;
Map<String, int> indexes;
olm.InboundGroupSession inboundGroupSession;
final String key;
List<dynamic> get forwardingCurve25519KeyChain =>
content['forwarding_curve25519_key_chain'] ?? [];
String get senderClaimedEd25519Key =>
content['sender_claimed_ed25519_key'] ?? '';
String get senderKey => content['sender_key'] ?? '';
bool get isValid => inboundGroupSession != null;
SessionKey({this.content, this.inboundGroupSession, this.key, this.indexes});
SessionKey.fromDb(DbInboundGroupSession dbEntry, String key) : key = key {
final parsedContent = Event.getMapFromPayload(dbEntry.content);
final parsedIndexes = Event.getMapFromPayload(dbEntry.indexes);
content =
parsedContent != null ? Map<String, dynamic>.from(parsedContent) : null;
indexes = parsedIndexes != null
? Map<String, int>.from(parsedIndexes)
: <String, int>{};
inboundGroupSession = olm.InboundGroupSession();
try {
inboundGroupSession.unpickle(key, dbEntry.pickle);
} catch (e) {
dispose();
print('[LibOlm] Unable to unpickle inboundGroupSession: ' + e.toString());
}
}
Map<String, dynamic> toJson() {
final data = <String, dynamic>{};
if (content != null) {
data['content'] = content;
}
if (indexes != null) {
data['indexes'] = indexes;
}
data['inboundGroupSession'] = inboundGroupSession.pickle(key);
return data;
}
void dispose() {
inboundGroupSession?.free();
inboundGroupSession = null;
}
@override
String toString() => json.encode(toJson());
}

View File

@ -1,55 +1,37 @@
/*
* Copyright (c) 2019 Zender & Kurtz GbR.
* Famedly Matrix SDK
* Copyright (C) 2019, 2020 Famedly GmbH
*
* Authors:
* Christian Pauly <krille@famedly.com>
* Marcel Radzio <mtrnord@famedly.com>
* 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 file is part of famedlysdk.
* 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.
*
* 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 <http://www.gnu.org/licenses/>.
* 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/>.
*/
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 'matrix_api.dart';
export 'package:famedlysdk/src/utils/room_update.dart';
export 'package:famedlysdk/src/utils/event_update.dart';
export 'package:famedlysdk/src/utils/device_keys_list.dart';
export 'package:famedlysdk/src/utils/key_verification.dart';
export 'package:famedlysdk/src/utils/matrix_exception.dart';
export 'package:famedlysdk/src/utils/matrix_file.dart';
export 'package:famedlysdk/src/utils/matrix_id_string_extension.dart';
export 'package:famedlysdk/src/utils/uri_extension.dart';
export 'package:famedlysdk/src/utils/matrix_localizations.dart';
export 'package:famedlysdk/src/utils/open_id_credentials.dart';
export 'package:famedlysdk/src/utils/profile.dart';
export 'package:famedlysdk/src/utils/public_rooms_response.dart';
export 'package:famedlysdk/src/utils/push_rules.dart';
export 'package:famedlysdk/src/utils/receipt.dart';
export 'package:famedlysdk/src/utils/states_map.dart';
export 'package:famedlysdk/src/utils/to_device_event.dart';
export 'package:famedlysdk/src/utils/turn_server_credentials.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/client.dart';
export 'package:famedlysdk/src/event.dart';
export 'package:famedlysdk/src/key_manager.dart';
export 'package:famedlysdk/src/presence.dart';
export 'package:famedlysdk/src/room.dart';
export 'package:famedlysdk/src/room_account_data.dart';
export 'package:famedlysdk/src/timeline.dart';
export 'package:famedlysdk/src/user.dart';
export 'package:famedlysdk/src/database/database.dart' show Database;

63
lib/matrix_api.dart Normal file
View File

@ -0,0 +1,63 @@
/*
* Famedly Matrix SDK
* Copyright (C) 2019, 2020 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/>.
*/
library matrix_api;
export 'package:famedlysdk/matrix_api/matrix_api.dart';
export 'package:famedlysdk/matrix_api/model/basic_event_with_sender.dart';
export 'package:famedlysdk/matrix_api/model/basic_event.dart';
export 'package:famedlysdk/matrix_api/model/device.dart';
export 'package:famedlysdk/matrix_api/model/basic_room_event.dart';
export 'package:famedlysdk/matrix_api/model/event_context.dart';
export 'package:famedlysdk/matrix_api/model/matrix_event.dart';
export 'package:famedlysdk/matrix_api/model/event_types.dart';
export 'package:famedlysdk/matrix_api/model/events_sync_update.dart';
export 'package:famedlysdk/matrix_api/model/filter.dart';
export 'package:famedlysdk/matrix_api/model/keys_query_response.dart';
export 'package:famedlysdk/matrix_api/model/login_response.dart';
export 'package:famedlysdk/matrix_api/model/login_types.dart';
export 'package:famedlysdk/matrix_api/model/matrix_device_keys.dart';
export 'package:famedlysdk/matrix_api/model/matrix_exception.dart';
export 'package:famedlysdk/matrix_api/model/message_types.dart';
export 'package:famedlysdk/matrix_api/model/presence_content.dart';
export 'package:famedlysdk/matrix_api/model/notifications_query_response.dart';
export 'package:famedlysdk/matrix_api/model/one_time_keys_claim_response.dart';
export 'package:famedlysdk/matrix_api/model/open_graph_data.dart';
export 'package:famedlysdk/matrix_api/model/open_id_credentials.dart';
export 'package:famedlysdk/matrix_api/model/presence.dart';
export 'package:famedlysdk/matrix_api/model/profile.dart';
export 'package:famedlysdk/matrix_api/model/public_rooms_response.dart';
export 'package:famedlysdk/matrix_api/model/push_rule_set.dart';
export 'package:famedlysdk/matrix_api/model/pusher.dart';
export 'package:famedlysdk/matrix_api/model/request_token_response.dart';
export 'package:famedlysdk/matrix_api/model/room_alias_informations.dart';
export 'package:famedlysdk/matrix_api/model/room_summary.dart';
export 'package:famedlysdk/matrix_api/model/server_capabilities.dart';
export 'package:famedlysdk/matrix_api/model/stripped_state_event.dart';
export 'package:famedlysdk/matrix_api/model/supported_protocol.dart';
export 'package:famedlysdk/matrix_api/model/supported_versions.dart';
export 'package:famedlysdk/matrix_api/model/sync_update.dart';
export 'package:famedlysdk/matrix_api/model/tag.dart';
export 'package:famedlysdk/matrix_api/model/third_party_identifier.dart';
export 'package:famedlysdk/matrix_api/model/third_party_location.dart';
export 'package:famedlysdk/matrix_api/model/third_party_user.dart';
export 'package:famedlysdk/matrix_api/model/timeline_history_response.dart';
export 'package:famedlysdk/matrix_api/model/turn_server_credentials.dart';
export 'package:famedlysdk/matrix_api/model/user_search_result.dart';
export 'package:famedlysdk/matrix_api/model/well_known_informations.dart';
export 'package:famedlysdk/matrix_api/model/who_is_info.dart';

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,38 @@
/*
* Famedly Matrix SDK
* Copyright (C) 2019, 2020 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/>.
*/
class BasicEvent {
String type;
Map<String, dynamic> content;
BasicEvent({
this.type,
this.content,
});
BasicEvent.fromJson(Map<String, dynamic> json) {
type = json['type'];
content = Map<String, dynamic>.from(json['content']);
}
Map<String, dynamic> toJson() {
final data = <String, dynamic>{};
data['type'] = type;
data['content'] = content;
return data;
}
}

View File

@ -0,0 +1,39 @@
/*
* Famedly Matrix SDK
* Copyright (C) 2019, 2020 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 'basic_event.dart';
class BasicEventWithSender extends BasicEvent {
String senderId;
BasicEventWithSender();
BasicEventWithSender.fromJson(Map<String, dynamic> json) {
final basicEvent = BasicEvent.fromJson(json);
type = basicEvent.type;
content = basicEvent.content;
senderId = json['sender'];
}
@override
Map<String, dynamic> toJson() {
final data = super.toJson();
data['sender'] = senderId;
return data;
}
}

View File

@ -0,0 +1,46 @@
/*
* Famedly Matrix SDK
* Copyright (C) 2019, 2020 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 'package:famedlysdk/matrix_api/model/basic_event.dart';
class BasicRoomEvent extends BasicEvent {
String roomId;
BasicRoomEvent({
this.roomId,
Map<String, dynamic> content,
String type,
}) : super(
content: content,
type: type,
);
BasicRoomEvent.fromJson(Map<String, dynamic> json) {
final basicEvent = BasicEvent.fromJson(json);
content = basicEvent.content;
type = basicEvent.type;
roomId = json['room_id'];
}
@override
Map<String, dynamic> toJson() {
final data = super.toJson();
if (roomId != null) data['room_id'] = roomId;
return data;
}
}

View File

@ -0,0 +1,46 @@
/*
* Famedly Matrix SDK
* Copyright (C) 2019, 2020 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/>.
*/
class Device {
String deviceId;
String displayName;
String lastSeenIp;
DateTime lastSeenTs;
Device.fromJson(Map<String, dynamic> json) {
deviceId = json['device_id'];
displayName = json['display_name'];
lastSeenIp = json['last_seen_ip'];
lastSeenTs = DateTime.fromMillisecondsSinceEpoch(json['last_seen_ts'] ?? 0);
}
Map<String, dynamic> toJson() {
final data = <String, dynamic>{};
data['device_id'] = deviceId;
if (displayName != null) {
data['display_name'] = displayName;
}
if (lastSeenIp != null) {
data['last_seen_ip'] = lastSeenIp;
}
if (lastSeenTs != null) {
data['last_seen_ts'] = lastSeenTs.millisecondsSinceEpoch;
}
return data;
}
}

View File

@ -0,0 +1,73 @@
/*
* Famedly Matrix SDK
* Copyright (C) 2019, 2020 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 'matrix_event.dart';
class EventContext {
String end;
List<MatrixEvent> eventsAfter;
MatrixEvent event;
List<MatrixEvent> eventsBefore;
String start;
List<MatrixEvent> state;
EventContext.fromJson(Map<String, dynamic> json) {
end = json['end'];
if (json['events_after'] != null) {
eventsAfter = <MatrixEvent>[];
json['events_after'].forEach((v) {
eventsAfter.add(MatrixEvent.fromJson(v));
});
}
event = json['event'] != null ? MatrixEvent.fromJson(json['event']) : null;
if (json['events_before'] != null) {
eventsBefore = <MatrixEvent>[];
json['events_before'].forEach((v) {
eventsBefore.add(MatrixEvent.fromJson(v));
});
}
start = json['start'];
if (json['state'] != null) {
state = <MatrixEvent>[];
json['state'].forEach((v) {
state.add(MatrixEvent.fromJson(v));
});
}
}
Map<String, dynamic> toJson() {
final data = <String, dynamic>{};
if (end != null) {
data['end'] = end;
}
if (eventsAfter != null) {
data['events_after'] = eventsAfter.map((v) => v.toJson()).toList();
}
if (event != null) {
data['event'] = event.toJson();
}
if (eventsBefore != null) {
data['events_before'] = eventsBefore.map((v) => v.toJson()).toList();
}
data['start'] = start;
if (state != null) {
data['state'] = state.map((v) => v.toJson()).toList();
}
return data;
}
}

View File

@ -0,0 +1,42 @@
/*
* Famedly Matrix SDK
* Copyright (C) 2019, 2020 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/>.
*/
abstract class EventTypes {
static const String Message = 'm.room.message';
static const String Sticker = 'm.sticker';
static const String Redaction = 'm.room.redaction';
static const String RoomAliases = 'm.room.aliases';
static const String RoomCanonicalAlias = 'm.room.canonical_alias';
static const String RoomCreate = 'm.room.create';
static const String RoomJoinRules = 'm.room.join_rules';
static const String RoomMember = 'm.room.member';
static const String RoomPowerLevels = 'm.room.power_levels';
static const String RoomName = 'm.room.name';
static const String RoomTopic = 'm.room.topic';
static const String RoomAvatar = 'm.room.avatar';
static const String RoomTombstone = 'm.room.tombsone';
static const String GuestAccess = 'm.room.guest_access';
static const String HistoryVisibility = 'm.room.history_visibility';
static const String Encryption = 'm.room.encryption';
static const String Encrypted = 'm.room.encrypted';
static const String CallInvite = 'm.room.call.invite';
static const String CallAnswer = 'm.room.call.answer';
static const String CallCandidates = 'm.room.call.candidates';
static const String CallHangup = 'm.room.call.hangup';
static const String Unknown = 'm.unknown';
}

View File

@ -0,0 +1,47 @@
/*
* Famedly Matrix SDK
* Copyright (C) 2019, 2020 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 'matrix_event.dart';
class EventsSyncUpdate {
String start;
String end;
List<MatrixEvent> chunk;
EventsSyncUpdate.fromJson(Map<String, dynamic> json) {
start = json['start'];
end = json['end'];
chunk = json['chunk'] != null
? (json['chunk'] as List).map((i) => MatrixEvent.fromJson(i)).toList()
: null;
}
Map<String, dynamic> toJson() {
final data = <String, dynamic>{};
if (start != null) {
data['start'] = start;
}
if (end != null) {
data['end'] = end;
}
if (chunk != null) {
data['chunk'] = chunk.map((i) => i.toJson()).toList();
}
return data;
}
}

View File

@ -0,0 +1,222 @@
/*
* Famedly Matrix SDK
* Copyright (C) 2019, 2020 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/>.
*/
enum EventFormat { client, federation }
class Filter {
RoomFilter room;
EventFilter presence;
EventFilter accountData;
EventFormat eventFormat;
List<String> eventFields;
Filter({
this.room,
this.presence,
this.accountData,
this.eventFormat,
this.eventFields,
});
Filter.fromJson(Map<String, dynamic> json) {
room = json['room'] != null ? RoomFilter.fromJson(json['room']) : null;
presence = json['presence'] != null
? EventFilter.fromJson(json['presence'])
: null;
accountData = json['account_data'] != null
? EventFilter.fromJson(json['account_data'])
: null;
eventFormat = json['event_format'] != null
? EventFormat.values.firstWhere(
(e) => e.toString().split('.').last == json['event_format'])
: null;
eventFields = json['event_fields'] != null
? json['event_fields'].cast<String>()
: null;
}
Map<String, dynamic> toJson() {
final data = <String, dynamic>{};
if (room != null) {
data['room'] = room.toJson();
}
if (presence != null) {
data['presence'] = presence.toJson();
}
if (eventFormat != null) {
data['event_format'] = eventFormat.toString().split('.').last;
}
if (eventFields != null) {
data['event_fields'] = eventFields;
}
if (accountData != null) {
data['account_data'] = accountData.toJson();
}
return data;
}
}
class RoomFilter {
List<String> notRooms;
List<String> rooms;
StateFilter ephemeral;
bool includeLeave;
StateFilter state;
StateFilter timeline;
StateFilter accountData;
RoomFilter({
this.notRooms,
this.rooms,
this.ephemeral,
this.includeLeave,
this.state,
this.timeline,
this.accountData,
});
RoomFilter.fromJson(Map<String, dynamic> json) {
notRooms = json['not_rooms']?.cast<String>();
rooms = json['rooms']?.cast<String>();
state = json['state'] != null ? StateFilter.fromJson(json['state']) : null;
includeLeave = json['include_leave'];
timeline = json['timeline'] != null
? StateFilter.fromJson(json['timeline'])
: null;
ephemeral = json['ephemeral'] != null
? StateFilter.fromJson(json['ephemeral'])
: null;
accountData = json['account_data'] != null
? StateFilter.fromJson(json['account_data'])
: null;
}
Map<String, dynamic> toJson() {
final data = <String, dynamic>{};
if (notRooms != null) {
data['not_rooms'] = notRooms;
}
if (rooms != null) {
data['rooms'] = rooms;
}
if (ephemeral != null) {
data['ephemeral'] = ephemeral.toJson();
}
if (includeLeave != null) {
data['include_leave'] = includeLeave;
}
if (state != null) {
data['state'] = state.toJson();
}
if (timeline != null) {
data['timeline'] = timeline.toJson();
}
if (accountData != null) {
data['account_data'] = accountData.toJson();
}
return data;
}
}
class EventFilter {
int limit;
List<String> senders;
List<String> types;
List<String> notRooms;
List<String> notSenders;
EventFilter(
{this.limit, this.senders, this.types, this.notRooms, this.notSenders});
EventFilter.fromJson(Map<String, dynamic> json) {
limit = json['limit'];
types = json['senders']?.cast<String>();
types = json['types']?.cast<String>();
notRooms = json['not_rooms']?.cast<String>();
notSenders = json['not_senders']?.cast<String>();
}
Map<String, dynamic> toJson() {
final data = <String, dynamic>{};
if (limit != null) data['limit'] = limit;
if (types != null) data['types'] = types;
if (notRooms != null) data['not_rooms'] = notRooms;
if (notSenders != null) data['not_senders'] = notSenders;
return data;
}
}
class StateFilter extends EventFilter {
List<String> notTypes;
bool lazyLoadMembers;
bool includeRedundantMembers;
bool containsUrl;
StateFilter({
this.notTypes,
this.lazyLoadMembers,
this.includeRedundantMembers,
this.containsUrl,
int limit,
List<String> senders,
List<String> types,
List<String> notRooms,
List<String> notSenders,
}) : super(
limit: limit,
senders: senders,
types: types,
notRooms: notRooms,
notSenders: notSenders,
);
StateFilter.fromJson(Map<String, dynamic> json) {
final eventFilter = EventFilter.fromJson(json);
limit = eventFilter.limit;
senders = eventFilter.senders;
types = eventFilter.types;
notRooms = eventFilter.notRooms;
notSenders = eventFilter.notSenders;
notTypes = json['not_types']?.cast<String>();
lazyLoadMembers = json['lazy_load_members'];
includeRedundantMembers = json['include_redundant_members'];
containsUrl = json['contains_url'];
}
@override
Map<String, dynamic> toJson() {
final data = super.toJson();
if (limit != null) {
data['limit'] = limit;
}
if (notTypes != null) {
data['not_types'] = notTypes;
}
if (lazyLoadMembers != null) {
data['lazy_load_members'] = notTypes;
}
if (includeRedundantMembers != null) {
data['include_redundant_members'] = notTypes;
}
if (containsUrl != null) {
data['contains_url'] = notTypes;
}
return data;
}
}

View File

@ -0,0 +1,62 @@
/*
* Famedly Matrix SDK
* Copyright (C) 2019, 2020 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 'matrix_device_keys.dart';
class KeysQueryResponse {
Map<String, dynamic> failures;
Map<String, Map<String, MatrixDeviceKeys>> deviceKeys;
KeysQueryResponse.fromJson(Map<String, dynamic> json) {
failures = Map<String, dynamic>.from(json['failures']);
deviceKeys = json['device_keys'] != null
? (json['device_keys'] as Map).map(
(k, v) => MapEntry(
k,
(v as Map).map(
(k, v) => MapEntry(
k,
MatrixDeviceKeys.fromJson(v),
),
),
),
)
: null;
}
Map<String, dynamic> toJson() {
final data = <String, dynamic>{};
if (failures != null) {
data['failures'] = failures;
}
if (deviceKeys != null) {
data['device_keys'] = deviceKeys.map(
(k, v) => MapEntry(
k,
v.map(
(k, v) => MapEntry(
k,
v.toJson(),
),
),
),
);
}
return data;
}
}

View File

@ -0,0 +1,47 @@
/*
* Famedly Matrix SDK
* Copyright (C) 2019, 2020 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 'well_known_informations.dart';
class LoginResponse {
String userId;
String accessToken;
String deviceId;
WellKnownInformations wellKnownInformations;
LoginResponse.fromJson(Map<String, dynamic> json) {
userId = json['user_id'];
accessToken = json['access_token'];
deviceId = json['device_id'];
if (json.containsKey('well_known')) {
wellKnownInformations =
WellKnownInformations.fromJson(json['well_known']);
}
}
Map<String, dynamic> toJson() {
final data = <String, dynamic>{};
if (userId != null) data['user_id'] = userId;
if (accessToken != null) data['access_token'] = accessToken;
if (deviceId != null) data['device_id'] = deviceId;
if (wellKnownInformations != null) {
data['well_known'] = wellKnownInformations.toJson();
}
return data;
}
}

View File

@ -0,0 +1,52 @@
/*
* Famedly Matrix SDK
* Copyright (C) 2019, 2020 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/>.
*/
class LoginTypes {
List<Flows> flows;
LoginTypes.fromJson(Map<String, dynamic> json) {
if (json['flows'] != null) {
flows = <Flows>[];
json['flows'].forEach((v) {
flows.add(Flows.fromJson(v));
});
}
}
Map<String, dynamic> toJson() {
final data = <String, dynamic>{};
if (flows != null) {
data['flows'] = flows.map((v) => v.toJson()).toList();
}
return data;
}
}
class Flows {
String type;
Flows.fromJson(Map<String, dynamic> json) {
type = json['type'];
}
Map<String, dynamic> toJson() {
final data = <String, dynamic>{};
data['type'] = type;
return data;
}
}

View File

@ -0,0 +1,70 @@
/*
* Famedly Matrix SDK
* Copyright (C) 2019, 2020 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/>.
*/
class MatrixDeviceKeys {
String userId;
String deviceId;
List<String> algorithms;
Map<String, String> keys;
Map<String, Map<String, String>> signatures;
Map<String, dynamic> unsigned;
String get deviceDisplayName =>
unsigned != null ? unsigned['device_display_name'] : null;
// This object is used for signing so we need the raw json too
Map<String, dynamic> _json;
MatrixDeviceKeys(
this.userId,
this.deviceId,
this.algorithms,
this.keys,
this.signatures, {
this.unsigned,
});
MatrixDeviceKeys.fromJson(Map<String, dynamic> json) {
_json = json;
userId = json['user_id'];
deviceId = json['device_id'];
algorithms = json['algorithms'].cast<String>();
keys = Map<String, String>.from(json['keys']);
signatures = Map<String, Map<String, String>>.from(
(json['signatures'] as Map)
.map((k, v) => MapEntry(k, Map<String, String>.from(v))));
unsigned = json['unsigned'] != null
? Map<String, dynamic>.from(json['unsigned'])
: null;
}
Map<String, dynamic> toJson() {
final data = _json ?? <String, dynamic>{};
data['user_id'] = userId;
data['device_id'] = deviceId;
data['algorithms'] = algorithms;
data['keys'] = keys;
if (signatures != null) {
data['signatures'] = signatures;
}
if (unsigned != null) {
data['unsigned'] = unsigned;
}
return data;
}
}

View File

@ -0,0 +1,72 @@
/*
* Famedly Matrix SDK
* Copyright (C) 2019, 2020 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 'package:famedlysdk/matrix_api/model/stripped_state_event.dart';
class MatrixEvent extends StrippedStateEvent {
String eventId;
String roomId;
DateTime originServerTs;
Map<String, dynamic> unsigned;
Map<String, dynamic> prevContent;
String redacts;
MatrixEvent();
MatrixEvent.fromJson(Map<String, dynamic> json) {
final strippedStateEvent = StrippedStateEvent.fromJson(json);
content = strippedStateEvent.content;
type = strippedStateEvent.type;
senderId = strippedStateEvent.senderId;
stateKey = strippedStateEvent.stateKey;
eventId = json['event_id'];
roomId = json['room_id'];
originServerTs =
DateTime.fromMillisecondsSinceEpoch(json['origin_server_ts']);
unsigned = json['unsigned'] != null
? Map<String, dynamic>.from(json['unsigned'])
: null;
prevContent = json['prev_content'] != null
? Map<String, dynamic>.from(json['prev_content'])
: null;
redacts = json['redacts'];
}
@override
Map<String, dynamic> toJson() {
final data = super.toJson();
data['event_id'] = eventId;
data['origin_server_ts'] = originServerTs.millisecondsSinceEpoch;
if (unsigned != null) {
data['unsigned'] = unsigned;
}
if (prevContent != null) {
data['prev_content'] = prevContent;
}
if (roomId != null) {
data['room_id'] = roomId;
}
if (data['state_key'] == null) {
data.remove('state_key');
}
if (redacts != null) {
data['redacts'] = redacts;
}
return data;
}
}

View File

@ -1,24 +1,19 @@
/*
* Copyright (c) 2019 Zender & Kurtz GbR.
* Famedly Matrix SDK
* Copyright (C) 2019, 2020 Famedly GmbH
*
* Authors:
* Christian Pauly <krille@famedly.com>
* Marcel Radzio <mtrnord@famedly.com>
* 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 file is part of famedlysdk.
* 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.
*
* 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 <http://www.gnu.org/licenses/>.
* 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:convert';
@ -62,6 +57,7 @@ class MatrixException implements Exception {
http.Response response;
MatrixException(this.response) : raw = json.decode(response.body);
MatrixException.fromJson(Map<String, dynamic> content) : raw = content;
@override
String toString() => '$errcode: $errorMessage';
@ -78,7 +74,9 @@ class MatrixException implements Exception {
String get session => raw['session'];
/// Returns true if the server requires additional authentication.
bool get requireAdditionalAuthentication => response.statusCode == 401;
bool get requireAdditionalAuthentication => response != null
? response.statusCode == 401
: authenticationFlows != null;
/// For each endpoint, a server offers one or more 'flows' that the client can use
/// to authenticate itself. Each flow comprises a series of stages. If this request

View File

@ -0,0 +1,32 @@
/*
* Famedly Matrix SDK
* Copyright (C) 2019, 2020 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/>.
*/
abstract class MessageTypes {
static const String Text = 'm.text';
static const String Emote = 'm.emote';
static const String Notice = 'm.notice';
static const String Image = 'm.image';
static const String Video = 'm.video';
static const String Audio = 'm.audio';
static const String File = 'm.file';
static const String Location = 'm.location';
static const String Reply = 'm.relates_to';
static const String Sticker = 'm.sticker';
static const String BadEncrypted = 'm.bad.encrypted';
static const String None = 'm.none';
}

View File

@ -0,0 +1,72 @@
/*
* Famedly Matrix SDK
* Copyright (C) 2019, 2020 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 'matrix_event.dart';
class NotificationsQueryResponse {
String nextToken;
List<Notification> notifications;
NotificationsQueryResponse.fromJson(Map<String, dynamic> json) {
nextToken = json['next_token'];
notifications = <Notification>[];
json['notifications'].forEach((v) {
notifications.add(Notification.fromJson(v));
});
}
Map<String, dynamic> toJson() {
final data = <String, dynamic>{};
if (nextToken != null) {
data['next_token'] = nextToken;
}
data['notifications'] = notifications.map((v) => v.toJson()).toList();
return data;
}
}
class Notification {
List<String> actions;
String profileTag;
bool read;
String roomId;
int ts;
MatrixEvent event;
Notification.fromJson(Map<String, dynamic> json) {
actions = json['actions'].cast<String>();
profileTag = json['profile_tag'];
read = json['read'];
roomId = json['room_id'];
ts = json['ts'];
event = MatrixEvent.fromJson(json['event']);
}
Map<String, dynamic> toJson() {
final data = <String, dynamic>{};
data['actions'] = actions;
if (profileTag != null) {
data['profile_tag'] = profileTag;
}
data['read'] = read;
data['room_id'] = roomId;
data['ts'] = ts;
data['event'] = event.toJson();
return data;
}
}

View File

@ -0,0 +1,38 @@
/*
* Famedly Matrix SDK
* Copyright (C) 2019, 2020 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/>.
*/
class OneTimeKeysClaimResponse {
Map<String, dynamic> failures;
Map<String, Map<String, dynamic>> oneTimeKeys;
OneTimeKeysClaimResponse.fromJson(Map<String, dynamic> json) {
failures = Map<String, dynamic>.from(json['failures']);
oneTimeKeys = Map<String, Map<String, dynamic>>.from(json['one_time_keys']);
}
Map<String, dynamic> toJson() {
final data = <String, dynamic>{};
if (failures != null) {
data['failures'] = failures;
}
if (oneTimeKeys != null) {
data['one_time_keys'] = oneTimeKeys;
}
return data;
}
}

View File

@ -0,0 +1,63 @@
/*
* Famedly Matrix SDK
* Copyright (C) 2019, 2020 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/>.
*/
class OpenGraphData {
String ogTitle;
String ogDescription;
String ogImage;
String ogImageType;
int ogImageHeight;
int ogImageWidth;
int matrixImageSize;
OpenGraphData.fromJson(Map<String, dynamic> json) {
ogTitle = json['og:title'];
ogDescription = json['og:description'];
ogImage = json['og:image'];
ogImageType = json['og:image:type'];
ogImageHeight = json['og:image:height'];
ogImageWidth = json['og:image:width'];
matrixImageSize = json['matrix:image:size'];
}
Map<String, dynamic> toJson() {
final data = <String, dynamic>{};
if (ogTitle != null) {
data['og:title'] = ogTitle;
}
if (ogDescription != null) {
data['og:description'] = ogDescription;
}
if (ogImage != null) {
data['og:image'] = ogImage;
}
if (ogImageType != null) {
data['og:image:type'] = ogImageType;
}
if (ogImageHeight != null) {
data['og:image:height'] = ogImageHeight;
}
if (ogImageWidth != null) {
data['og:image:width'] = ogImageWidth;
}
if (matrixImageSize != null) {
data['matrix:image:size'] = matrixImageSize;
}
return data;
}
}

View File

@ -0,0 +1,40 @@
/*
* Famedly Matrix SDK
* Copyright (C) 2019, 2020 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/>.
*/
class OpenIdCredentials {
String accessToken;
String tokenType;
String matrixServerName;
int expiresIn;
OpenIdCredentials.fromJson(Map<String, dynamic> json) {
accessToken = json['access_token'];
tokenType = json['token_type'];
matrixServerName = json['matrix_server_name'];
expiresIn = json['expires_in'];
}
Map<String, dynamic> toJson() {
final data = <String, dynamic>{};
data['access_token'] = accessToken;
data['token_type'] = tokenType;
data['matrix_server_name'] = matrixServerName;
data['expires_in'] = expiresIn;
return data;
}
}

View File

@ -0,0 +1,32 @@
/*
* Famedly Matrix SDK
* Copyright (C) 2019, 2020 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 'basic_event_with_sender.dart';
import 'presence_content.dart';
class Presence extends BasicEventWithSender {
PresenceContent presence;
Presence.fromJson(Map<String, dynamic> json) {
final basicEvent = BasicEventWithSender.fromJson(json);
type = basicEvent.type;
content = basicEvent.content;
senderId = basicEvent.senderId;
presence = PresenceContent.fromJson(content);
}
}

View File

@ -0,0 +1,49 @@
/*
* Famedly Matrix SDK
* Copyright (C) 2019, 2020 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/>.
*/
enum PresenceType { online, offline, unavailable }
class PresenceContent {
PresenceType presence;
int lastActiveAgo;
String statusMsg;
bool currentlyActive;
PresenceContent.fromJson(Map<String, dynamic> json) {
presence = PresenceType.values
.firstWhere((p) => p.toString().split('.').last == json['presence']);
lastActiveAgo = json['last_active_ago'];
statusMsg = json['status_msg'];
currentlyActive = json['currently_active'];
}
Map<String, dynamic> toJson() {
final data = <String, dynamic>{};
data['presence'] = presence.toString().split('.').last;
if (lastActiveAgo != null) {
data['last_active_ago'] = lastActiveAgo;
}
if (statusMsg != null) {
data['status_msg'] = statusMsg;
}
if (currentlyActive != null) {
data['currently_active'] = currentlyActive;
}
return data;
}
}

View File

@ -0,0 +1,44 @@
/*
* Famedly Matrix SDK
* Copyright (C) 2019, 2020 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/>.
*/
class Profile {
/// The user's avatar URL if they have set one, otherwise null.
Uri avatarUrl;
/// The user's display name if they have set one, otherwise null.
String displayname;
/// The matrix ID of this user. May be omitted.
String userId;
Map<String, dynamic> additionalContent;
Profile(this.displayname, this.avatarUrl,
{this.additionalContent = const {}});
Profile.fromJson(Map<String, dynamic> json)
: avatarUrl =
json['avatar_url'] != null ? Uri.parse(json['avatar_url']) : null,
displayname = json['display_name'] ?? json['displayname'],
userId = json['user_id'],
additionalContent = json;
Map<String, dynamic> toJson() {
return additionalContent;
}
}

View File

@ -0,0 +1,97 @@
/*
* Famedly Matrix SDK
* Copyright (C) 2019, 2020 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/>.
*/
class PublicRoomsResponse {
List<PublicRoom> chunk;
String nextBatch;
String prevBatch;
int totalRoomCountEstimate;
PublicRoomsResponse.fromJson(Map<String, dynamic> json) {
chunk = <PublicRoom>[];
json['chunk'].forEach((v) {
chunk.add(PublicRoom.fromJson(v));
});
nextBatch = json['next_batch'];
prevBatch = json['prev_batch'];
totalRoomCountEstimate = json['total_room_count_estimate'];
}
Map<String, dynamic> toJson() {
final data = <String, dynamic>{};
data['chunk'] = chunk.map((v) => v.toJson()).toList();
if (nextBatch != null) {
data['next_batch'] = nextBatch;
}
if (prevBatch != null) {
data['prev_batch'] = prevBatch;
}
if (totalRoomCountEstimate != null) {
data['total_room_count_estimate'] = totalRoomCountEstimate;
}
return data;
}
}
class PublicRoom {
List<String> aliases;
String avatarUrl;
bool guestCanJoin;
String name;
int numJoinedMembers;
String roomId;
String topic;
bool worldReadable;
String canonicalAlias;
PublicRoom.fromJson(Map<String, dynamic> json) {
aliases = json['aliases']?.cast<String>();
avatarUrl = json['avatar_url'];
guestCanJoin = json['guest_can_join'];
canonicalAlias = json['canonical_alias'];
name = json['name'];
numJoinedMembers = json['num_joined_members'];
roomId = json['room_id'];
topic = json['topic'];
worldReadable = json['world_readable'];
}
Map<String, dynamic> toJson() {
final data = <String, dynamic>{};
if (aliases != null) {
data['aliases'] = aliases;
}
if (canonicalAlias != null) {
data['canonical_alias'] = canonicalAlias;
}
if (avatarUrl != null) {
data['avatar_url'] = avatarUrl;
}
data['guest_can_join'] = guestCanJoin;
if (name != null) {
data['name'] = name;
}
data['num_joined_members'] = numJoinedMembers;
data['room_id'] = roomId;
if (topic != null) {
data['topic'] = topic;
}
data['world_readable'] = worldReadable;
return data;
}
}

View File

@ -0,0 +1,143 @@
/*
* Famedly Matrix SDK
* Copyright (C) 2019, 2020 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/>.
*/
enum PushRuleKind { content, override, room, sender, underride }
enum PushRuleAction { notify, dont_notify, coalesce, set_tweak }
class PushRuleSet {
List<PushRule> content;
List<PushRule> override;
List<PushRule> room;
List<PushRule> sender;
List<PushRule> underride;
PushRuleSet.fromJson(Map<String, dynamic> json) {
if (json['content'] != null) {
content =
(json['content'] as List).map((i) => PushRule.fromJson(i)).toList();
}
if (json['override'] != null) {
override =
(json['override'] as List).map((i) => PushRule.fromJson(i)).toList();
}
if (json['room'] != null) {
room = (json['room'] as List).map((i) => PushRule.fromJson(i)).toList();
}
if (json['sender'] != null) {
sender =
(json['sender'] as List).map((i) => PushRule.fromJson(i)).toList();
}
if (json['underride'] != null) {
underride =
(json['underride'] as List).map((i) => PushRule.fromJson(i)).toList();
}
}
Map<String, dynamic> toJson() {
final data = <String, dynamic>{};
if (content != null) {
data['content'] = content.map((v) => v.toJson()).toList();
}
if (override != null) {
data['override'] = override.map((v) => v.toJson()).toList();
}
if (room != null) {
data['room'] = room.map((v) => v.toJson()).toList();
}
if (sender != null) {
data['sender'] = sender.map((v) => v.toJson()).toList();
}
if (underride != null) {
data['underride'] = underride.map((v) => v.toJson()).toList();
}
return data;
}
}
class PushRule {
List<dynamic> actions;
List<PushConditions> conditions;
bool isDefault;
bool enabled;
String pattern;
String ruleId;
PushRule.fromJson(Map<String, dynamic> json) {
actions = json['actions'];
isDefault = json['default'];
enabled = json['enabled'];
pattern = json['pattern'];
ruleId = json['rule_id'];
conditions = json['conditions'] != null
? (json['conditions'] as List)
.map((i) => PushConditions.fromJson(i))
.toList()
: null;
}
Map<String, dynamic> toJson() {
final data = <String, dynamic>{};
data['actions'] = actions;
data['default'] = isDefault;
data['enabled'] = enabled;
if (pattern != null) {
data['pattern'] = pattern;
}
if (conditions != null) {
data['conditions'] = conditions.map((i) => i.toJson()).toList();
}
data['rule_id'] = ruleId;
return data;
}
}
class PushConditions {
String key;
String kind;
String pattern;
String isOperator;
PushConditions(
this.kind, {
this.key,
this.pattern,
this.isOperator,
});
PushConditions.fromJson(Map<String, dynamic> json) {
key = json['key'];
kind = json['kind'];
pattern = json['pattern'];
isOperator = json['is'];
}
Map<String, dynamic> toJson() {
final data = <String, dynamic>{};
if (key != null) {
data['key'] = key;
}
data['kind'] = kind;
if (pattern != null) {
data['pattern'] = pattern;
}
if (isOperator != null) {
data['is'] = isOperator;
}
return data;
}
}

View File

@ -0,0 +1,91 @@
/*
* Famedly Matrix SDK
* Copyright (C) 2019, 2020 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/>.
*/
class Pusher {
String pushkey;
String kind;
String appId;
String appDisplayName;
String deviceDisplayName;
String profileTag;
String lang;
PusherData data;
Pusher(
this.pushkey,
this.appId,
this.appDisplayName,
this.deviceDisplayName,
this.lang,
this.data, {
this.profileTag,
this.kind,
});
Pusher.fromJson(Map<String, dynamic> json) {
pushkey = json['pushkey'];
kind = json['kind'];
appId = json['app_id'];
appDisplayName = json['app_display_name'];
deviceDisplayName = json['device_display_name'];
profileTag = json['profile_tag'];
lang = json['lang'];
data = PusherData.fromJson(json['data']);
}
Map<String, dynamic> toJson() {
final data = <String, dynamic>{};
data['pushkey'] = pushkey;
data['kind'] = kind;
data['app_id'] = appId;
data['app_display_name'] = appDisplayName;
data['device_display_name'] = deviceDisplayName;
if (profileTag != null) {
data['profile_tag'] = profileTag;
}
data['lang'] = lang;
data['data'] = this.data.toJson();
return data;
}
}
class PusherData {
Uri url;
String format;
PusherData({
this.url,
this.format,
});
PusherData.fromJson(Map<String, dynamic> json) {
url = Uri.parse(json['url']);
format = json['format'];
}
Map<String, dynamic> toJson() {
final data = <String, dynamic>{};
if (url != null) {
data['url'] = url.toString();
}
if (format != null) {
data['format'] = format;
}
return data;
}
}

View File

@ -0,0 +1,34 @@
/*
* Famedly Matrix SDK
* Copyright (C) 2019, 2020 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/>.
*/
class RequestTokenResponse {
String sid;
String submitUrl;
RequestTokenResponse.fromJson(Map<String, dynamic> json) {
sid = json['sid'];
submitUrl = json['submit_url'];
}
Map<String, dynamic> toJson() {
final data = <String, dynamic>{};
data['sid'] = sid;
data['submit_url'] = submitUrl;
return data;
}
}

View File

@ -0,0 +1,34 @@
/*
* Famedly Matrix SDK
* Copyright (C) 2019, 2020 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/>.
*/
class RoomAliasInformations {
String roomId;
List<String> servers;
RoomAliasInformations.fromJson(Map<String, dynamic> json) {
roomId = json['room_id'];
servers = json['servers'].cast<String>();
}
Map<String, dynamic> toJson() {
final data = <String, dynamic>{};
data['room_id'] = roomId;
data['servers'] = servers;
return data;
}
}

View File

@ -0,0 +1,42 @@
/*
* Famedly Matrix SDK
* Copyright (C) 2019, 2020 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/>.
*/
class RoomSummary {
List<String> mHeroes;
int mJoinedMemberCount;
int mInvitedMemberCount;
RoomSummary.fromJson(Map<String, dynamic> json) {
mHeroes =
json['m.heroes'] != null ? List<String>.from(json['m.heroes']) : null;
mJoinedMemberCount = json['m.joined_member_count'];
mInvitedMemberCount = json['m.invited_member_count'];
}
Map<String, dynamic> toJson() {
final data = <String, dynamic>{};
if (mHeroes != null) {
data['m.heroes'] = mHeroes;
}
if (mJoinedMemberCount != null) {
data['m.joined_member_count'] = mJoinedMemberCount;
}
if (mInvitedMemberCount != null) {
data['m.invited_member_count'] = mInvitedMemberCount;
}
return data;
}
}

View File

@ -0,0 +1,89 @@
/*
* Famedly Matrix SDK
* Copyright (C) 2019, 2020 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/>.
*/
enum RoomVersionStability { stable, unstable }
class ServerCapabilities {
MChangePassword mChangePassword;
MRoomVersions mRoomVersions;
Map<String, dynamic> customCapabilities;
ServerCapabilities.fromJson(Map<String, dynamic> json) {
mChangePassword = json['m.change_password'] != null
? MChangePassword.fromJson(json['m.change_password'])
: null;
mRoomVersions = json['m.room_versions'] != null
? MRoomVersions.fromJson(json['m.room_versions'])
: null;
customCapabilities = Map<String, dynamic>.from(json);
customCapabilities.remove('m.change_password');
customCapabilities.remove('m.room_versions');
}
Map<String, dynamic> toJson() {
final data = <String, dynamic>{};
if (mChangePassword != null) {
data['m.change_password'] = mChangePassword.toJson();
}
if (mRoomVersions != null) {
data['m.room_versions'] = mRoomVersions.toJson();
}
for (final entry in customCapabilities.entries) {
data[entry.key] = entry.value;
}
return data;
}
}
class MChangePassword {
bool enabled;
MChangePassword.fromJson(Map<String, dynamic> json) {
enabled = json['enabled'];
}
Map<String, dynamic> toJson() {
final data = <String, dynamic>{};
data['enabled'] = enabled;
return data;
}
}
class MRoomVersions {
String defaultVersion;
Map<String, RoomVersionStability> available;
MRoomVersions.fromJson(Map<String, dynamic> json) {
defaultVersion = json['default'];
available = (json['available'] as Map).map<String, RoomVersionStability>(
(k, v) => MapEntry(
k,
RoomVersionStability.values
.firstWhere((r) => r.toString().split('.').last == v),
),
);
}
Map<String, dynamic> toJson() {
final data = <String, dynamic>{};
data['default'] = defaultVersion;
data['available'] = available.map<String, dynamic>(
(k, v) => MapEntry(k, v.toString().split('.').last));
return data;
}
}

View File

@ -0,0 +1,39 @@
/*
* Famedly Matrix SDK
* Copyright (C) 2019, 2020 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 'package:famedlysdk/matrix_api/model/basic_event_with_sender.dart';
class StrippedStateEvent extends BasicEventWithSender {
String stateKey;
StrippedStateEvent();
StrippedStateEvent.fromJson(Map<String, dynamic> json) {
final basicEvent = BasicEventWithSender.fromJson(json);
content = basicEvent.content;
type = basicEvent.type;
senderId = basicEvent.senderId;
stateKey = json['state_key'];
}
@override
Map<String, dynamic> toJson() {
final data = super.toJson();
data['state_key'] = stateKey;
return data;
}
}

View File

@ -0,0 +1,92 @@
/*
* Famedly Matrix SDK
* Copyright (C) 2019, 2020 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/>.
*/
class SupportedProtocol {
List<String> userFields;
List<String> locationFields;
String icon;
Map<String, ProtocolFieldType> fieldTypes;
List<ProtocolInstance> instances;
SupportedProtocol.fromJson(Map<String, dynamic> json) {
userFields = json['user_fields'].cast<String>();
locationFields = json['location_fields'].cast<String>();
icon = json['icon'];
fieldTypes = (json['field_types'] as Map)
.map((k, v) => MapEntry(k, ProtocolFieldType.fromJson(v)));
instances = <ProtocolInstance>[];
json['instances'].forEach((v) {
instances.add(ProtocolInstance.fromJson(v));
});
}
Map<String, dynamic> toJson() {
final data = <String, dynamic>{};
data['user_fields'] = userFields;
data['location_fields'] = locationFields;
data['icon'] = icon;
data['field_types'] = fieldTypes.map((k, v) => MapEntry(k, v.toJson()));
data['instances'] = instances.map((v) => v.toJson()).toList();
return data;
}
}
class ProtocolFieldType {
String regexp;
String placeholder;
ProtocolFieldType.fromJson(Map<String, dynamic> json) {
regexp = json['regexp'];
placeholder = json['placeholder'];
}
Map<String, dynamic> toJson() {
final data = <String, dynamic>{};
data['regexp'] = regexp;
data['placeholder'] = placeholder;
return data;
}
}
class ProtocolInstance {
String networkId;
String desc;
String icon;
dynamic fields;
ProtocolInstance.fromJson(Map<String, dynamic> json) {
networkId = json['network_id'];
desc = json['desc'];
icon = json['icon'];
fields = json['fields'];
}
Map<String, dynamic> toJson() {
final data = <String, dynamic>{};
data['network_id'] = networkId;
data['desc'] = desc;
if (icon != null) {
data['icon'] = icon;
}
data['fields'] = fields;
return data;
}
}

View File

@ -0,0 +1,36 @@
/*
* Famedly Matrix SDK
* Copyright (C) 2019, 2020 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/>.
*/
class SupportedVersions {
List<String> versions;
Map<String, bool> unstableFeatures;
SupportedVersions.fromJson(Map<String, dynamic> json) {
versions = json['versions'].cast<String>();
unstableFeatures = Map<String, bool>.from(json['unstable_features']);
}
Map<String, dynamic> toJson() {
final data = <String, dynamic>{};
data['versions'] = versions;
if (unstableFeatures != null) {
data['unstable_features'] = unstableFeatures;
}
return data;
}
}

View File

@ -0,0 +1,321 @@
/*
* Famedly Matrix SDK
* Copyright (C) 2019, 2020 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 'basic_event_with_sender.dart';
import 'basic_room_event.dart';
import 'stripped_state_event.dart';
import 'matrix_event.dart';
import 'basic_event.dart';
import 'presence.dart';
import 'room_summary.dart';
class SyncUpdate {
String nextBatch;
RoomsUpdate rooms;
List<Presence> presence;
List<BasicEvent> accountData;
List<BasicEventWithSender> toDevice;
DeviceListsUpdate deviceLists;
Map<String, int> deviceOneTimeKeysCount;
SyncUpdate.fromJson(Map<String, dynamic> json) {
nextBatch = json['next_batch'];
rooms = json['rooms'] != null ? RoomsUpdate.fromJson(json['rooms']) : null;
presence = (json['presence'] != null && json['presence']['events'] != null)
? (json['presence']['events'] as List)
.map((i) => Presence.fromJson(i))
.toList()
: null;
accountData =
(json['account_data'] != null && json['account_data']['events'] != null)
? (json['account_data']['events'] as List)
.map((i) => BasicEvent.fromJson(i))
.toList()
: null;
toDevice =
(json['to_device'] != null && json['to_device']['events'] != null)
? (json['to_device']['events'] as List)
.map((i) => BasicEventWithSender.fromJson(i))
.toList()
: null;
deviceLists = json['device_lists'] != null
? DeviceListsUpdate.fromJson(json['device_lists'])
: null;
deviceOneTimeKeysCount = json['device_one_time_keys_count'] != null
? Map<String, int>.from(json['device_one_time_keys_count'])
: null;
}
Map<String, dynamic> toJson() {
final data = <String, dynamic>{};
data['next_batch'] = nextBatch;
if (rooms != null) {
data['rooms'] = rooms.toJson();
}
if (presence != null) {
data['presence'] = {
'events': presence.map((i) => i.toJson()).toList(),
};
}
if (accountData != null) {
data['account_data'] = {
'events': accountData.map((i) => i.toJson()).toList(),
};
}
if (toDevice != null) {
data['to_device'] = {
'events': toDevice.map((i) => i.toJson()).toList(),
};
}
if (deviceLists != null) {
data['device_lists'] = deviceLists.toJson();
}
if (deviceOneTimeKeysCount != null) {
data['device_one_time_keys_count'] = deviceOneTimeKeysCount;
}
return data;
}
}
class RoomsUpdate {
Map<String, JoinedRoomUpdate> join;
Map<String, InvitedRoomUpdate> invite;
Map<String, LeftRoomUpdate> leave;
RoomsUpdate.fromJson(Map<String, dynamic> json) {
join = json['join'] != null
? (json['join'] as Map)
.map((k, v) => MapEntry(k, JoinedRoomUpdate.fromJson(v)))
: null;
invite = json['invite'] != null
? (json['invite'] as Map)
.map((k, v) => MapEntry(k, InvitedRoomUpdate.fromJson(v)))
: null;
leave = json['leave'] != null
? (json['leave'] as Map)
.map((k, v) => MapEntry(k, LeftRoomUpdate.fromJson(v)))
: null;
}
Map<String, dynamic> toJson() {
final data = <String, dynamic>{};
if (join != null) {
data['join'] = join.map((k, v) => MapEntry(k, v.toJson()));
}
if (invite != null) {
data['invite'] = invite.map((k, v) => MapEntry(k, v.toJson()));
}
if (leave != null) {
data['leave'] = leave.map((k, v) => MapEntry(k, v.toJson()));
}
return data;
}
}
abstract class SyncRoomUpdate {}
class JoinedRoomUpdate extends SyncRoomUpdate {
RoomSummary summary;
List<MatrixEvent> state;
TimelineUpdate timeline;
List<BasicRoomEvent> ephemeral;
List<BasicRoomEvent> accountData;
UnreadNotificationCounts unreadNotifications;
JoinedRoomUpdate.fromJson(Map<String, dynamic> json) {
summary =
json['summary'] != null ? RoomSummary.fromJson(json['summary']) : null;
state = (json['state'] != null && json['state']['events'] != null)
? (json['state']['events'] as List)
.map((i) => MatrixEvent.fromJson(i))
.toList()
: null;
timeline = json['timeline'] != null
? TimelineUpdate.fromJson(json['timeline'])
: null;
ephemeral =
(json['ephemeral'] != null && json['ephemeral']['events'] != null)
? (json['ephemeral']['events'] as List)
.map((i) => BasicRoomEvent.fromJson(i))
.toList()
: null;
accountData =
(json['account_data'] != null && json['account_data']['events'] != null)
? (json['account_data']['events'] as List)
.map((i) => BasicRoomEvent.fromJson(i))
.toList()
: null;
unreadNotifications = json['unread_notifications'] != null
? UnreadNotificationCounts.fromJson(json['unread_notifications'])
: null;
}
Map<String, dynamic> toJson() {
final data = <String, dynamic>{};
if (summary != null) {
data['summary'] = summary.toJson();
}
if (state != null) {
data['state'] = {
'events': state.map((i) => i.toJson()).toList(),
};
}
if (timeline != null) {
data['timeline'] = timeline.toJson();
}
if (ephemeral != null) {
data['ephemeral'] = {
'events': ephemeral.map((i) => i.toJson()).toList(),
};
}
if (accountData != null) {
data['account_data'] = {
'events': accountData.map((i) => i.toJson()).toList(),
};
}
if (unreadNotifications != null) {
data['unread_notifications'] = unreadNotifications.toJson();
}
return data;
}
}
class InvitedRoomUpdate extends SyncRoomUpdate {
List<StrippedStateEvent> inviteState;
InvitedRoomUpdate.fromJson(Map<String, dynamic> json) {
inviteState =
(json['invite_state'] != null && json['invite_state']['events'] != null)
? (json['invite_state']['events'] as List)
.map((i) => StrippedStateEvent.fromJson(i))
.toList()
: null;
}
Map<String, dynamic> toJson() {
final data = <String, dynamic>{};
if (inviteState != null) {
data['invite_state'] = {
'events': inviteState.map((i) => i.toJson()).toList(),
};
}
return data;
}
}
class LeftRoomUpdate extends SyncRoomUpdate {
List<MatrixEvent> state;
TimelineUpdate timeline;
List<BasicRoomEvent> accountData;
LeftRoomUpdate.fromJson(Map<String, dynamic> json) {
state = (json['state'] != null && json['state']['events'] != null)
? (json['state']['events'] as List)
.map((i) => MatrixEvent.fromJson(i))
.toList()
: null;
timeline = json['timeline'] != null
? TimelineUpdate.fromJson(json['timeline'])
: null;
accountData =
(json['account_data'] != null && json['account_data']['events'] != null)
? (json['account_data']['events'] as List)
.map((i) => BasicRoomEvent.fromJson(i))
.toList()
: null;
}
Map<String, dynamic> toJson() {
final data = <String, dynamic>{};
if (state != null) {
data['state'] = {
'events': state.map((i) => i.toJson()).toList(),
};
}
if (timeline != null) {
data['timeline'] = timeline.toJson();
}
if (accountData != null) {
data['account_data'] = {
'events': accountData.map((i) => i.toJson()).toList(),
};
}
return data;
}
}
class TimelineUpdate {
List<MatrixEvent> events;
bool limited;
String prevBatch;
TimelineUpdate.fromJson(Map<String, dynamic> json) {
events = json['events'] != null
? (json['events'] as List).map((i) => MatrixEvent.fromJson(i)).toList()
: null;
limited = json['limited'];
prevBatch = json['prev_batch'];
}
Map<String, dynamic> toJson() {
final data = <String, dynamic>{};
if (events != null) {
data['events'] = events.map((i) => i.toJson()).toList();
}
if (limited != null) {
data['limited'] = limited;
}
if (prevBatch != null) {
data['prev_batch'] = prevBatch;
}
return data;
}
}
class UnreadNotificationCounts {
int highlightCount;
int notificationCount;
UnreadNotificationCounts.fromJson(Map<String, dynamic> json) {
highlightCount = json['highlight_count'];
notificationCount = json['notification_count'];
}
Map<String, dynamic> toJson() {
final data = <String, dynamic>{};
if (highlightCount != null) {
data['highlight_count'] = highlightCount;
}
if (notificationCount != null) {
data['notification_count'] = notificationCount;
}
return data;
}
}
class DeviceListsUpdate {
List<String> changed;
List<String> left;
DeviceListsUpdate.fromJson(Map<String, dynamic> json) {
changed = List<String>.from(json['changed']);
left = List<String>.from(json['left']);
}
Map<String, dynamic> toJson() {
final data = <String, dynamic>{};
if (changed != null) {
data['changed'] = changed;
}
if (left != null) {
data['left'] = left;
}
return data;
}
}

View File

@ -0,0 +1,33 @@
/*
* Famedly Matrix SDK
* Copyright (C) 2019, 2020 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/>.
*/
class Tag {
double order;
Tag.fromJson(Map<String, dynamic> json) {
order = json['order'];
}
Map<String, dynamic> toJson() {
final data = <String, dynamic>{};
if (order != null) {
data['order'] = order;
}
return data;
}
}

View File

@ -0,0 +1,40 @@
/*
* Famedly Matrix SDK
* Copyright (C) 2019, 2020 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/>.
*/
class ThirdPartyIdentifier {
String medium;
String address;
int validatedAt;
int addedAt;
ThirdPartyIdentifier.fromJson(Map<String, dynamic> json) {
medium = json['medium'];
address = json['address'];
validatedAt = json['validated_at'];
addedAt = json['added_at'];
}
Map<String, dynamic> toJson() {
final data = <String, dynamic>{};
data['medium'] = medium;
data['address'] = address;
data['validated_at'] = validatedAt;
data['added_at'] = addedAt;
return data;
}
}

View File

@ -0,0 +1,37 @@
/*
* Famedly Matrix SDK
* Copyright (C) 2019, 2020 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/>.
*/
class ThirdPartyLocation {
String alias;
String protocol;
Map<String, dynamic> fields;
ThirdPartyLocation.fromJson(Map<String, dynamic> json) {
alias = json['alias'];
protocol = json['protocol'];
fields = Map<String, dynamic>.from(json['fields']);
}
Map<String, dynamic> toJson() {
final data = <String, dynamic>{};
data['alias'] = alias;
data['protocol'] = protocol;
data['fields'] = fields;
return data;
}
}

View File

@ -0,0 +1,37 @@
/*
* Famedly Matrix SDK
* Copyright (C) 2019, 2020 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/>.
*/
class ThirdPartyUser {
String userId;
String protocol;
Map<String, dynamic> fields;
ThirdPartyUser.fromJson(Map<String, dynamic> json) {
userId = json['userid'];
protocol = json['protocol'];
fields = Map<String, dynamic>.from(json['fields']);
}
Map<String, dynamic> toJson() {
final data = <String, dynamic>{};
data['userid'] = userId;
data['protocol'] = protocol;
data['fields'] = fields;
return data;
}
}

View File

@ -0,0 +1,46 @@
/*
* Famedly Matrix SDK
* Copyright (C) 2019, 2020 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 'matrix_event.dart';
class TimelineHistoryResponse {
String start;
String end;
List<MatrixEvent> chunk;
List<MatrixEvent> state;
TimelineHistoryResponse.fromJson(Map<String, dynamic> json) {
start = json['start'];
end = json['end'];
chunk = json['chunk'] != null
? (json['chunk'] as List).map((i) => MatrixEvent.fromJson(i)).toList()
: null;
state = json['state'] != null
? (json['state'] as List).map((i) => MatrixEvent.fromJson(i)).toList()
: null;
}
Map<String, dynamic> toJson() {
final data = <String, dynamic>{};
if (start != null) data['start'] = start;
if (end != null) data['end'] = end;
if (chunk != null) data['chunk'] = chunk.map((i) => i.toJson());
if (state != null) data['state'] = state.map((i) => i.toJson());
return data;
}
}

View File

@ -0,0 +1,40 @@
/*
* Famedly Matrix SDK
* Copyright (C) 2019, 2020 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/>.
*/
class TurnServerCredentials {
String username;
String password;
List<String> uris;
int ttl;
TurnServerCredentials.fromJson(Map<String, dynamic> json) {
username = json['username'];
password = json['password'];
uris = json['uris'].cast<String>();
ttl = json['ttl'];
}
Map<String, dynamic> toJson() {
final data = <String, dynamic>{};
data['username'] = username;
data['password'] = password;
data['uris'] = uris;
data['ttl'] = ttl;
return data;
}
}

View File

@ -0,0 +1,41 @@
/*
* Famedly Matrix SDK
* Copyright (C) 2019, 2020 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 'profile.dart';
class UserSearchResult {
List<Profile> results;
bool limited;
UserSearchResult.fromJson(Map<String, dynamic> json) {
results = <Profile>[];
json['results'].forEach((v) {
results.add(Profile.fromJson(v));
});
limited = json['limited'];
}
Map<String, dynamic> toJson() {
final data = <String, dynamic>{};
data['results'] = results.map((v) => v.toJson()).toList();
data['limited'] = limited;
return data;
}
}

View File

@ -0,0 +1,54 @@
/*
* Famedly Matrix SDK
* Copyright (C) 2019, 2020 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/>.
*/
class WellKnownInformations {
MHomeserver mHomeserver;
MHomeserver mIdentityServer;
Map<String, dynamic> content;
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;
}
Map<String, dynamic> toJson() {
final data = content;
data['m.homeserver'] = mHomeserver.toJson();
data['m.identity_server'] = mIdentityServer.toJson();
return data;
}
}
class MHomeserver {
String baseUrl;
MHomeserver.fromJson(Map<String, dynamic> json) {
baseUrl = json['base_url'];
}
Map<String, dynamic> toJson() {
final data = <String, dynamic>{};
data['base_url'] = baseUrl;
return data;
}
}

View File

@ -0,0 +1,107 @@
/*
* Famedly Matrix SDK
* Copyright (C) 2019, 2020 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/>.
*/
class WhoIsInfo {
String userId;
Map<String, DeviceInfo> devices;
WhoIsInfo.fromJson(Map<String, dynamic> json) {
userId = json['user_id'];
devices = json['devices'] != null
? (json['devices'] as Map)
.map((k, v) => MapEntry(k, DeviceInfo.fromJson(v)))
: null;
}
Map<String, dynamic> toJson() {
final data = <String, dynamic>{};
data['user_id'] = userId;
if (devices != null) {
data['devices'] = devices.map((k, v) => MapEntry(k, v.toJson()));
}
return data;
}
}
class DeviceInfo {
List<Sessions> sessions;
DeviceInfo.fromJson(Map<String, dynamic> json) {
if (json['sessions'] != null) {
sessions = <Sessions>[];
json['sessions'].forEach((v) {
sessions.add(Sessions.fromJson(v));
});
}
}
Map<String, dynamic> toJson() {
final data = <String, dynamic>{};
if (sessions != null) {
data['sessions'] = sessions.map((v) => v.toJson()).toList();
}
return data;
}
}
class Sessions {
List<Connections> connections;
Sessions.fromJson(Map<String, dynamic> json) {
if (json['connections'] != null) {
connections = <Connections>[];
json['connections'].forEach((v) {
connections.add(Connections.fromJson(v));
});
}
}
Map<String, dynamic> toJson() {
final data = <String, dynamic>{};
if (connections != null) {
data['connections'] = connections.map((v) => v.toJson()).toList();
}
return data;
}
}
class Connections {
String ip;
int lastSeen;
String userAgent;
Connections.fromJson(Map<String, dynamic> json) {
ip = json['ip'];
lastSeen = json['last_seen'];
userAgent = json['user_agent'];
}
Map<String, dynamic> toJson() {
final data = <String, dynamic>{};
if (ip != null) {
data['ip'] = ip;
}
if (lastSeen != null) {
data['last_seen'] = lastSeen;
}
if (userAgent != null) {
data['user_agent'] = userAgent;
}
return data;
}
}

View File

@ -1,48 +0,0 @@
/*
* Copyright (c) 2019 Zender & Kurtz GbR.
*
* Authors:
* Christian Pauly <krille@famedly.com>
* Marcel Radzio <mtrnord@famedly.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
import 'package:famedlysdk/famedlysdk.dart';
import './database/database.dart' show DbAccountData;
/// The global private data created by this user.
class AccountData {
/// The json payload of the content. The content highly depends on the type.
final Map<String, dynamic> content;
/// The type String of this event. For example 'm.room.message'.
final String typeKey;
AccountData({this.content, this.typeKey});
/// Get a State event from a table row or from the event stream.
factory AccountData.fromJson(Map<String, dynamic> jsonPayload) {
final content = Event.getMapFromPayload(jsonPayload['content']);
return AccountData(content: content, typeKey: jsonPayload['type']);
}
/// Get account data from DbAccountData
factory AccountData.fromDb(DbAccountData dbEntry) {
final content = Event.getMapFromPayload(dbEntry.content);
return AccountData(content: content, typeKey: dbEntry.type);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -2,8 +2,11 @@ import 'package:moor/moor.dart';
import 'dart:convert';
import 'package:famedlysdk/famedlysdk.dart' as sdk;
import 'package:famedlysdk/matrix_api.dart' as api;
import 'package:olm/olm.dart' as olm;
import '../../matrix_api.dart';
part 'database.g.dart';
@UseMoor(
@ -102,6 +105,22 @@ class Database extends _$Database {
return res;
}
Future<List<olm.Session>> getSingleOlmSessions(
int clientId, String identityKey, String userId) async {
final rows = await dbGetOlmSessions(clientId, identityKey).get();
final res = <olm.Session>[];
for (final row in rows) {
try {
var session = olm.Session();
session.unpickle(userId, row.pickle);
res.add(session);
} catch (e) {
print('[LibOlm] Could not unpickle olm session: ' + e.toString());
}
}
return res;
}
Future<DbOutboundGroupSession> getDbOutboundGroupSession(
int clientId, String roomId) async {
final res = await dbGetOutboundGroupSession(clientId, roomId).get();
@ -156,20 +175,31 @@ class Database extends _$Database {
return roomList;
}
Future<Map<String, sdk.AccountData>> getAccountData(int clientId) async {
final newAccountData = <String, sdk.AccountData>{};
Future<Map<String, api.BasicEvent>> getAccountData(int clientId) async {
final newAccountData = <String, api.BasicEvent>{};
final rawAccountData = await getAllAccountData(clientId).get();
for (final d in rawAccountData) {
newAccountData[d.type] = sdk.AccountData.fromDb(d);
final content = sdk.Event.getMapFromPayload(d.content);
newAccountData[d.type] = api.BasicEvent(
content: content,
type: d.type,
);
}
return newAccountData;
}
Future<Map<String, sdk.Presence>> getPresences(int clientId) async {
final newPresences = <String, sdk.Presence>{};
Future<Map<String, api.Presence>> getPresences(int clientId) async {
final newPresences = <String, api.Presence>{};
final rawPresences = await getAllPresences(clientId).get();
for (final d in rawPresences) {
newPresences[d.sender] = sdk.Presence.fromDb(d);
// TODO: Why is this not working?
try {
final content = sdk.Event.getMapFromPayload(d.content);
var presence = api.Presence.fromJson(content);
presence.senderId = d.sender;
presence.type = d.type;
newPresences[d.sender] = api.Presence.fromJson(content);
} catch (_) {}
}
return newPresences;
}
@ -180,7 +210,7 @@ class Database extends _$Database {
Future<void> storeRoomUpdate(int clientId, sdk.RoomUpdate roomUpdate,
[sdk.Room oldRoom]) async {
final setKey = '${clientId};${roomUpdate.id}';
if (roomUpdate.membership != sdk.Membership.leave) {
if (roomUpdate.membership != api.Membership.leave) {
if (!_ensuredRooms.contains(setKey)) {
await ensureRoomExists(clientId, roomUpdate.id,
roomUpdate.membership.toString().split('.').last);
@ -241,16 +271,17 @@ class Database extends _$Database {
/// Stores an UserUpdate object in the database. Must be called inside of
/// [transaction].
Future<void> storeUserEventUpdate(
int clientId, sdk.UserUpdate userUpdate) async {
if (userUpdate.type == 'account_data') {
await storeAccountData(clientId, userUpdate.eventType,
json.encode(userUpdate.content['content']));
} else if (userUpdate.type == 'presence') {
await storePresence(
clientId,
userUpdate.eventType,
userUpdate.content['sender'],
json.encode(userUpdate.content['content']));
int clientId,
String type,
String eventType,
Map<String, dynamic> content,
) async {
if (type == 'account_data') {
await storeAccountData(
clientId, eventType, json.encode(content['content']));
} else if (type == 'presence') {
await storePresence(clientId, eventType, content['sender'],
json.encode(content['content']));
}
}
@ -269,7 +300,7 @@ class Database extends _$Database {
stateKey = eventContent['state_key'];
}
if (eventUpdate.eventType == 'm.room.redaction') {
if (eventUpdate.eventType == EventTypes.Redaction) {
await redactMessage(clientId, eventUpdate);
}

View File

@ -5482,6 +5482,19 @@ abstract class _$Database extends GeneratedDatabase {
readsFrom: {olmSessions}).map(_rowToDbOlmSessions);
}
Selectable<DbOlmSessions> dbGetOlmSessions(
int client_id, String identity_key) {
return customSelect(
'SELECT * FROM olm_sessions WHERE client_id = :client_id AND identity_key = :identity_key',
variables: [
Variable.withInt(client_id),
Variable.withString(identity_key)
],
readsFrom: {
olmSessions
}).map(_rowToDbOlmSessions);
}
Future<int> storeOlmSession(
int client_id, String identitiy_key, String session_id, String pickle) {
return customInsert(

View File

@ -175,6 +175,7 @@ getAllUserDeviceKeys: SELECT * FROM user_device_keys WHERE client_id = :client_i
getAllUserDeviceKeysKeys: SELECT * FROM user_device_keys_key WHERE client_id = :client_id;
getAllUserCrossSigningKeys: SELECT * FROM user_cross_signing_keys WHERE client_id = :client_id;
getAllOlmSessions: SELECT * FROM olm_sessions WHERE client_id = :client_id;
dbGetOlmSessions: SELECT * FROM olm_sessions WHERE client_id = :client_id AND identity_key = :identity_key;
storeOlmSession: INSERT OR REPLACE INTO olm_sessions (client_id, identity_key, session_id, pickle) VALUES (:client_id, :identitiy_key, :session_id, :pickle);
getAllOutboundGroupSessions: SELECT * FROM outbound_group_sessions WHERE client_id = :client_id;
dbGetOutboundGroupSession: SELECT * FROM outbound_group_sessions WHERE client_id = :client_id AND room_id = :room_id;

View File

@ -1,75 +1,46 @@
/*
* Copyright (c) 2019 Zender & Kurtz GbR.
* Famedly Matrix SDK
* Copyright (C) 2019, 2020 Famedly GmbH
*
* Authors:
* Christian Pauly <krille@famedly.com>
* Marcel Radzio <mtrnord@famedly.com>
* 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 file is part of famedlysdk.
* 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.
*
* 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 <http://www.gnu.org/licenses/>.
* 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:convert';
import 'dart:typed_data';
import 'package:famedlysdk/famedlysdk.dart';
import 'package:famedlysdk/encryption.dart';
import 'package:famedlysdk/src/utils/receipt.dart';
import 'package:http/http.dart' as http;
import 'package:matrix_file_e2ee/matrix_file_e2ee.dart';
import '../matrix_api.dart';
import './room.dart';
import 'utils/matrix_localizations.dart';
import './database/database.dart' show DbRoomState, DbEvent;
/// All data exchanged over Matrix is expressed as an "event". Typically each client action (e.g. sending a message) correlates with exactly one event.
class Event {
/// The Matrix ID for this event in the format '$localpart:server.abc'. Please not
/// that account data, presence and other events may not have an eventId.
final String eventId;
/// The json payload of the content. The content highly depends on the type.
Map<String, dynamic> content;
/// The type String of this event. For example 'm.room.message'.
final String typeKey;
/// The ID of the room this event belongs to.
final String roomId;
/// The user who has sent this event if it is not a global account data event.
final String senderId;
class Event extends MatrixEvent {
User get sender => room.getUserByMXIDSync(senderId ?? '@unknown');
/// The time this event has received at the server. May be null for events like
/// account data.
final DateTime time;
@Deprecated('Use [originServerTs] instead')
DateTime get time => originServerTs;
/// Optional additional content for this event.
Map<String, dynamic> unsigned;
@Deprecated('Use [type] instead')
String get typeKey => type;
/// The room this event belongs to. May be null.
final Room room;
/// Optional. The previous content for this state.
/// This will be present only for state events appearing in the timeline.
/// If this is not a state event, or there is no previous content, this key will be null.
Map<String, dynamic> prevContent;
/// Optional. This key will only be present for state events. A unique key which defines
/// the overwriting semantics for this piece of room state.
final String stateKey;
/// The status of this event.
/// -1=ERROR
/// 0=SENDING
@ -101,17 +72,27 @@ class Event {
Event(
{this.status = defaultStatus,
this.content,
this.typeKey,
this.eventId,
this.roomId,
this.senderId,
this.time,
this.unsigned,
this.prevContent,
this.stateKey,
Map<String, dynamic> content,
String type,
String eventId,
String roomId,
String senderId,
DateTime originServerTs,
Map<String, dynamic> unsigned,
Map<String, dynamic> prevContent,
String stateKey,
this.room,
this.sortOrder = 0.0});
this.sortOrder = 0.0}) {
this.content = content;
this.type = type;
this.eventId = eventId;
this.roomId = roomId ?? room?.id;
this.senderId = senderId;
this.unsigned = unsigned;
this.prevContent = prevContent;
this.stateKey = stateKey;
this.originServerTs = originServerTs;
}
static Map<String, dynamic> getMapFromPayload(dynamic payload) {
if (payload is String) {
@ -125,6 +106,27 @@ class Event {
return {};
}
factory Event.fromMatrixEvent(
MatrixEvent matrixEvent,
Room room, {
double sortOrder,
int status,
}) =>
Event(
status: status,
content: matrixEvent.content,
type: matrixEvent.type,
eventId: matrixEvent.eventId,
roomId: room.id,
senderId: matrixEvent.senderId,
originServerTs: matrixEvent.originServerTs,
unsigned: matrixEvent.unsigned,
prevContent: matrixEvent.prevContent,
stateKey: matrixEvent.stateKey,
room: room,
sortOrder: sortOrder,
);
/// Get a State event from a table row or from the event stream.
factory Event.fromJson(Map<String, dynamic> jsonPayload, Room room,
[double sortOrder]) {
@ -136,11 +138,11 @@ class Event {
stateKey: jsonPayload['state_key'],
prevContent: prevContent,
content: content,
typeKey: jsonPayload['type'],
type: jsonPayload['type'],
eventId: jsonPayload['event_id'],
roomId: jsonPayload['room_id'],
senderId: jsonPayload['sender'],
time: jsonPayload.containsKey('origin_server_ts')
originServerTs: jsonPayload.containsKey('origin_server_ts')
? DateTime.fromMillisecondsSinceEpoch(jsonPayload['origin_server_ts'])
: DateTime.now(),
unsigned: unsigned,
@ -162,17 +164,18 @@ class Event {
stateKey: dbEntry.stateKey,
prevContent: prevContent,
content: content,
typeKey: dbEntry.type,
type: dbEntry.type,
eventId: dbEntry.eventId,
roomId: dbEntry.roomId,
senderId: dbEntry.sender,
time: dbEntry.originServerTs ?? DateTime.now(),
originServerTs: dbEntry.originServerTs ?? DateTime.now(),
unsigned: unsigned,
room: room,
sortOrder: dbEntry.sortOrder ?? 0.0,
);
}
@override
Map<String, dynamic> toJson() {
final data = <String, dynamic>{};
if (stateKey != null) data['state_key'] = stateKey;
@ -180,116 +183,33 @@ class Event {
data['prev_content'] = prevContent;
}
data['content'] = content;
data['type'] = typeKey;
data['type'] = type;
data['event_id'] = eventId;
data['room_id'] = roomId;
data['sender'] = senderId;
data['origin_server_ts'] = time.millisecondsSinceEpoch;
data['origin_server_ts'] = originServerTs.millisecondsSinceEpoch;
if (unsigned != null && unsigned.isNotEmpty) {
data['unsigned'] = unsigned;
}
return data;
}
/// The unique key of this event. For events with a [stateKey], it will be the
/// stateKey. Otherwise it will be the [type] as a string.
@deprecated
String get key => stateKey == null || stateKey.isEmpty ? typeKey : stateKey;
User get asUser => User.fromState(
stateKey: stateKey,
prevContent: prevContent,
content: content,
typeKey: typeKey,
typeKey: type,
eventId: eventId,
roomId: roomId,
senderId: senderId,
time: time,
originServerTs: originServerTs,
unsigned: unsigned,
room: room);
/// Get the real type.
EventTypes get type {
switch (typeKey) {
case 'm.room.avatar':
return EventTypes.RoomAvatar;
case 'm.room.name':
return EventTypes.RoomName;
case 'm.room.topic':
return EventTypes.RoomTopic;
case 'm.room.aliases':
return EventTypes.RoomAliases;
case 'm.room.canonical_alias':
return EventTypes.RoomCanonicalAlias;
case 'm.room.create':
return EventTypes.RoomCreate;
case 'm.room.redaction':
return EventTypes.Redaction;
case 'm.room.join_rules':
return EventTypes.RoomJoinRules;
case 'm.room.member':
return EventTypes.RoomMember;
case 'm.room.power_levels':
return EventTypes.RoomPowerLevels;
case 'm.room.guest_access':
return EventTypes.GuestAccess;
case 'm.room.history_visibility':
return EventTypes.HistoryVisibility;
case 'm.sticker':
return EventTypes.Sticker;
case 'm.room.message':
return EventTypes.Message;
case 'm.room.encrypted':
return EventTypes.Encrypted;
case 'm.room.encryption':
return EventTypes.Encryption;
case 'm.room.tombsone':
return EventTypes.RoomTombstone;
case 'm.call.invite':
return EventTypes.CallInvite;
case 'm.call.answer':
return EventTypes.CallAnswer;
case 'm.call.candidates':
return EventTypes.CallCandidates;
case 'm.call.hangup':
return EventTypes.CallHangup;
}
return EventTypes.Unknown;
}
///
MessageTypes get messageType {
switch (content['msgtype'] ?? 'm.text') {
case 'm.text':
if (content.containsKey('m.relates_to')) {
return MessageTypes.Reply;
}
return MessageTypes.Text;
case 'm.notice':
return MessageTypes.Notice;
case 'm.emote':
return MessageTypes.Emote;
case 'm.image':
return MessageTypes.Image;
case 'm.video':
return MessageTypes.Video;
case 'm.audio':
return MessageTypes.Audio;
case 'm.file':
return MessageTypes.File;
case 'm.sticker':
return MessageTypes.Sticker;
case 'm.location':
return MessageTypes.Location;
case 'm.bad.encrypted':
return MessageTypes.BadEncrypted;
default:
if (type == EventTypes.Message) {
return MessageTypes.Text;
}
return MessageTypes.None;
}
}
String get messageType => (content.containsKey('m.relates_to') &&
content['m.relates_to']['m.in_reply_to'] != null)
? MessageTypes.Reply
: content['msgtype'] ?? MessageTypes.Text;
void setRedactionEvent(Event redactedBecause) {
unsigned = {
@ -341,9 +261,6 @@ class Event {
/// Returns the formatted boy of this event if it has a formatted body.
String get formattedText => content['formatted_body'] ?? '';
@Deprecated('Use [body] instead.')
String getBody() => body;
/// Use this to get the body.
String get body {
if (redacted) return 'Redacted';
@ -374,7 +291,7 @@ class Event {
room.client.onEvent.add(EventUpdate(
roomID: room.id,
type: 'timeline',
eventType: typeKey,
eventType: type,
content: {
'event_id': eventId,
'status': -2,
@ -417,36 +334,6 @@ class Event {
return await timeline.getEventById(replyEventId);
}
Future<void> loadSession() {
return room.loadInboundGroupSessionKeyForEvent(this);
}
/// Trys to decrypt this event. Returns a m.bad.encrypted event
/// if it fails and does nothing if the event was not encrypted.
Event get decrypted => room.decryptGroupMessage(this);
/// Trys to decrypt this event and persists it in the database afterwards
Future<Event> decryptAndStore([String updateType = 'timeline']) async {
final newEvent = decrypted;
if (newEvent.type == EventTypes.Encrypted) {
return newEvent; // decryption failed
}
await room.client.database?.storeEventUpdate(
room.client.id,
EventUpdate(
eventType: newEvent.typeKey,
content: newEvent.toJson(),
roomID: newEvent.roomId,
type: updateType,
sortOrder: newEvent.sortOrder,
),
);
if (updateType != 'history') {
room.setState(newEvent);
}
return newEvent;
}
/// If this event is encrypted and the decryption was not successful because
/// the session is unknown, this requests the session key from other devices
/// in the room. If the event is not encrypted or the decryption failed because
@ -473,7 +360,7 @@ class Event {
Future<MatrixFile> downloadAndDecryptAttachment(
{bool getThumbnail = false}) async {
if (![EventTypes.Message, EventTypes.Sticker].contains(type)) {
throw ("This event has the type '$typeKey' and so it can't contain an attachment.");
throw ("This event has the type '$type' and so it can't contain an attachment.");
}
if (!getThumbnail &&
!content.containsKey('url') &&
@ -741,7 +628,7 @@ class Event {
}
break;
default:
localizedBody = i18n.unknownEvent(typeKey);
localizedBody = i18n.unknownEvent(type);
}
// Hide reply fallback
@ -762,7 +649,7 @@ class Event {
return localizedBody;
}
static const Set<MessageTypes> textOnlyMessageTypes = {
static const Set<String> textOnlyMessageTypes = {
MessageTypes.Text,
MessageTypes.Reply,
MessageTypes.Notice,
@ -770,43 +657,3 @@ class Event {
MessageTypes.None,
};
}
enum MessageTypes {
Text,
Emote,
Notice,
Image,
Video,
Audio,
File,
Location,
Reply,
Sticker,
BadEncrypted,
None,
}
enum EventTypes {
Message,
Sticker,
Redaction,
RoomAliases,
RoomCanonicalAlias,
RoomCreate,
RoomJoinRules,
RoomMember,
RoomPowerLevels,
RoomName,
RoomTopic,
RoomAvatar,
RoomTombstone,
GuestAccess,
HistoryVisibility,
Encryption,
Encrypted,
CallInvite,
CallAnswer,
CallCandidates,
CallHangup,
Unknown,
}

View File

@ -1,91 +0,0 @@
/*
* Copyright (c) 2019 Zender & Kurtz GbR.
*
* Authors:
* Christian Pauly <krille@famedly.com>
* Marcel Radzio <mtrnord@famedly.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
import 'package:famedlysdk/famedlysdk.dart';
import './database/database.dart' show DbPresence;
enum PresenceType { online, offline, unavailable }
/// Informs the client of a user's presence state change.
class Presence {
/// The user who sent this presence.
final String sender;
/// The current display name for this user, if any.
final String displayname;
/// The current avatar URL for this user, if any.
final Uri avatarUrl;
final bool currentlyActive;
final int lastActiveAgo;
final PresenceType presence;
final String statusMsg;
final DateTime time;
Presence(
{this.sender,
this.displayname,
this.avatarUrl,
this.currentlyActive,
this.lastActiveAgo,
this.presence,
this.statusMsg,
this.time});
Presence.fromJson(Map<String, dynamic> json)
: sender = json['sender'],
displayname = json['content']['displayname'],
avatarUrl = json['content']['avatar_url'] != null
? Uri.parse(json['content']['avatar_url'])
: null,
currentlyActive = json['content']['currently_active'],
lastActiveAgo = json['content']['last_active_ago'],
time = DateTime.fromMillisecondsSinceEpoch(
DateTime.now().millisecondsSinceEpoch -
(json['content']['last_active_ago'] ?? 0)),
presence = PresenceType.values.firstWhere(
(e) =>
e.toString() == "PresenceType.${json['content']['presence']}",
orElse: () => null),
statusMsg = json['content']['status_msg'];
factory Presence.fromDb(DbPresence dbEntry) {
final content = Event.getMapFromPayload(dbEntry.content);
return Presence(
sender: dbEntry.sender,
displayname: content['displayname'],
avatarUrl: content['avatar_url'] != null
? Uri.parse(content['avatar_url'])
: null,
currentlyActive: content['currently_active'],
lastActiveAgo: content['last_active_ago'],
time: DateTime.fromMillisecondsSinceEpoch(
DateTime.now().millisecondsSinceEpoch -
(content['last_active_ago'] ?? 0)),
presence: PresenceType.values.firstWhere(
(e) => e.toString() == "PresenceType.${content['presence']}",
orElse: () => null),
statusMsg: content['status_msg'],
);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,60 +0,0 @@
/*
* Copyright (c) 2019 Zender & Kurtz GbR.
*
* Authors:
* Christian Pauly <krille@famedly.com>
* Marcel Radzio <mtrnord@famedly.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
import 'package:famedlysdk/famedlysdk.dart';
import 'package:famedlysdk/src/account_data.dart';
import 'package:famedlysdk/src/event.dart';
import './database/database.dart' show DbRoomAccountData;
/// Stripped down events for account data and ephemrals of a room.
class RoomAccountData extends AccountData {
/// The user who has sent this event if it is not a global account data event.
final String roomId;
final Room room;
RoomAccountData(
{this.roomId, this.room, Map<String, dynamic> content, String typeKey})
: super(content: content, typeKey: typeKey);
/// Get a State event from a table row or from the event stream.
factory RoomAccountData.fromJson(
Map<String, dynamic> jsonPayload, Room room) {
final content = Event.getMapFromPayload(jsonPayload['content']);
return RoomAccountData(
content: content,
typeKey: jsonPayload['type'],
roomId: jsonPayload['room_id'],
room: room);
}
/// get room account data from DbRoomAccountData
factory RoomAccountData.fromDb(DbRoomAccountData dbEntry, Room room) {
final content = Event.getMapFromPayload(dbEntry.content);
return RoomAccountData(
content: content,
typeKey: dbEntry.type,
roomId: dbEntry.roomId,
room: room);
}
}

View File

@ -1,68 +0,0 @@
/*
* Copyright (c) 2019 Zender & Kurtz GbR.
*
* Authors:
* Christian Pauly <krille@famedly.com>
* Marcel Radzio <mtrnord@famedly.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
import '../../famedlysdk.dart';
/// Represents a new event (e.g. a message in a room) or an update for an
/// already known event.
class EventUpdate {
/// Usually 'timeline', 'state' or whatever.
final String type;
/// Most events belong to a room. If not, this equals to eventType.
final String roomID;
/// See (Matrix Room Events)[https://matrix.org/docs/spec/client_server/r0.4.0.html#room-events]
/// and (Matrix Events)[https://matrix.org/docs/spec/client_server/r0.4.0.html#id89] for more
/// informations.
final String eventType;
// The json payload of the content of this event.
final Map<String, dynamic> content;
// the order where to stort this event
final double sortOrder;
EventUpdate(
{this.eventType, this.roomID, this.type, this.content, this.sortOrder});
EventUpdate decrypt(Room room) {
if (eventType != 'm.room.encrypted') {
return this;
}
try {
var decrpytedEvent =
room.decryptGroupMessage(Event.fromJson(content, room, sortOrder));
return EventUpdate(
eventType: decrpytedEvent.typeKey,
roomID: roomID,
type: type,
content: decrpytedEvent.toJson(),
sortOrder: sortOrder,
);
} catch (e) {
print('[LibOlm] Could not decrypt megolm event: ' + e.toString());
return this;
}
}
}

View File

@ -1,75 +0,0 @@
/*
* Copyright (c) 2019 Zender & Kurtz GbR.
*
* Authors:
* Christian Pauly <krille@famedly.com>
* Marcel Radzio <mtrnord@famedly.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
import '../user.dart';
/// Represents a new room or an update for an
/// already known room.
class RoomUpdate {
/// All rooms have an idea in the format: !uniqueid:server.abc
final String id;
/// The current membership state of the user in this room.
final Membership membership;
/// Represents the number of unead notifications. This probably doesn't fit the number
/// of unread messages.
final num notification_count;
// The number of unread highlighted notifications.
final num highlight_count;
/// If there are too much new messages, the [homeserver] will only send the
/// last X (default is 10) messages and set the [limitedTimelinbe] flag to true.
final bool limitedTimeline;
/// Represents the current position of the client in the room history.
final String prev_batch;
final RoomSummary summary;
RoomUpdate({
this.id,
this.membership,
this.notification_count,
this.highlight_count,
this.limitedTimeline,
this.prev_batch,
this.summary,
});
}
class RoomSummary {
List<String> mHeroes;
int mJoinedMemberCount;
int mInvitedMemberCount;
RoomSummary(
{this.mHeroes, this.mJoinedMemberCount, this.mInvitedMemberCount});
RoomSummary.fromJson(Map<String, dynamic> json) {
mHeroes = json['m.heroes']?.cast<String>();
mJoinedMemberCount = json['m.joined_member_count'];
mInvitedMemberCount = json['m.invited_member_count'];
}
}

View File

@ -1,37 +0,0 @@
/*
* Copyright (c) 2019 Zender & Kurtz GbR.
*
* Authors:
* Christian Pauly <krille@famedly.com>
* Marcel Radzio <mtrnord@famedly.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
/// Represents a new global event like presence or account_data.
class UserUpdate {
/// Usually 'presence', 'account_data' or whatever.
final String eventType;
/// See (Matrix Events)[https://matrix.org/docs/spec/client_server/r0.4.0.html]
/// for more informations.
final String type;
// The json payload of the content of this event.
final dynamic content;
UserUpdate({this.eventType, this.type, this.content});
}

View File

@ -1,32 +1,30 @@
/*
* Copyright (c) 2019 Zender & Kurtz GbR.
* Famedly Matrix SDK
* Copyright (C) 2019, 2020 Famedly GmbH
*
* Authors:
* Christian Pauly <krille@famedly.com>
* Marcel Radzio <mtrnord@famedly.com>
* 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 file is part of famedlysdk.
* 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.
*
* 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 <http://www.gnu.org/licenses/>.
* 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:async';
import 'package:famedlysdk/matrix_api.dart';
import 'package:famedlysdk/encryption.dart';
import 'event.dart';
import 'room.dart';
import 'sync/event_update.dart';
import 'sync/room_update.dart';
import 'utils/event_update.dart';
import 'utils/room_update.dart';
typedef onTimelineUpdateCallback = void Function();
typedef onTimelineInsertCallback = void Function(int insertID);
@ -100,12 +98,16 @@ class Timeline {
void _sessionKeyReceived(String sessionId) async {
var decryptAtLeastOneEvent = false;
final decryptFn = () async {
if (!room.client.encryptionEnabled) {
return;
}
for (var i = 0; i < events.length; i++) {
if (events[i].type == EventTypes.Encrypted &&
events[i].messageType == MessageTypes.BadEncrypted &&
events[i].content['body'] == DecryptError.UNKNOWN_SESSION &&
events[i].content['session_id'] == sessionId) {
events[i] = await events[i].decryptAndStore();
events[i] = await room.client.encryption
.decryptRoomEvent(room.id, events[i], store: true);
if (events[i].type != EventTypes.Encrypted) {
decryptAtLeastOneEvent = true;
}
@ -135,7 +137,7 @@ class Timeline {
if (eventUpdate.type == 'timeline' || eventUpdate.type == 'history') {
// Redaction events are handled as modification for existing events.
if (eventUpdate.eventType == 'm.room.redaction') {
if (eventUpdate.eventType == EventTypes.Redaction) {
final eventId = _findEvent(event_id: eventUpdate.content['redacts']);
if (eventId != null) {
events[eventId].setRedactionEvent(Event.fromJson(
@ -161,7 +163,8 @@ class Timeline {
} else {
Event newEvent;
var senderUser = room
.getState('m.room.member', eventUpdate.content['sender'])
.getState(
EventTypes.RoomMember, eventUpdate.content['sender'])
?.asUser ??
await room.client.database?.getUser(
room.client.id, eventUpdate.content['sender'], room);

View File

@ -1,32 +1,26 @@
/*
* Copyright (c) 2019 Zender & Kurtz GbR.
* Famedly Matrix SDK
* Copyright (C) 2019, 2020 Famedly GmbH
*
* Authors:
* Christian Pauly <krille@famedly.com>
* Marcel Radzio <mtrnord@famedly.com>
* 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 file is part of famedlysdk.
* 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.
*
* 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 <http://www.gnu.org/licenses/>.
* 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 'package:famedlysdk/famedlysdk.dart';
import 'package:famedlysdk/matrix_api.dart';
import 'package:famedlysdk/src/room.dart';
import 'package:famedlysdk/src/event.dart';
enum Membership { join, invite, leave, ban }
/// Represents a Matrix User which may be a participant in a Matrix Room.
class User extends Event {
factory User(
@ -43,10 +37,10 @@ class User extends Event {
return User.fromState(
stateKey: id,
content: content,
typeKey: 'm.room.member',
typeKey: EventTypes.RoomMember,
roomId: room?.id,
room: room,
time: DateTime.now(),
originServerTs: DateTime.now(),
);
}
@ -58,18 +52,18 @@ class User extends Event {
String eventId,
String roomId,
String senderId,
DateTime time,
DateTime originServerTs,
dynamic unsigned,
Room room})
: super(
stateKey: stateKey,
prevContent: prevContent,
content: content,
typeKey: typeKey,
type: typeKey,
eventId: eventId,
roomId: roomId,
senderId: senderId,
time: time,
originServerTs: originServerTs,
unsigned: unsigned,
room: room);
@ -142,16 +136,11 @@ class User extends Event {
if (roomID != null) return roomID;
// Start a new direct chat
final dynamic resp = await room.client.jsonRequest(
type: HTTPType.POST,
action: '/client/r0/createRoom',
data: {
'invite': [id],
'is_direct': true,
'preset': 'trusted_private_chat'
});
final String newRoomID = resp['room_id'];
final newRoomID = await room.client.api.createRoom(
invite: [id],
isDirect: true,
preset: CreateRoomPreset.trusted_private_chat,
);
if (newRoomID == null) return newRoomID;

View File

@ -2,13 +2,15 @@ import 'dart:convert';
import 'package:canonical_json/canonical_json.dart';
import 'package:olm/olm.dart' as olm;
import 'package:famedlysdk/matrix_api.dart';
import 'package:famedlysdk/encryption.dart';
import '../client.dart';
import '../user.dart';
import '../room.dart';
import '../database/database.dart'
show DbUserDeviceKey, DbUserDeviceKeysKey, DbUserCrossSigningKey;
import '../event.dart';
import 'key_verification.dart';
enum UserVerifiedStatus { verified, unknown, unknownDevice }
@ -351,12 +353,6 @@ class DeviceKeys extends SignedKey {
@override
Future<void> setBlocked(bool newBlocked) {
blocked = newBlocked;
for (var room in client.rooms) {
if (!room.encrypted) continue;
if (room.getParticipants().indexWhere((u) => u.id == userId) != -1) {
room.clearOutboundGroupSession();
}
}
return client.database
?.setBlockedUserDeviceKey(newBlocked, client.id, userId, deviceId);
}
@ -369,10 +365,45 @@ class DeviceKeys extends SignedKey {
identifier = dbEntry.deviceId;
algorithms = content['algorithms'].cast<String>();
keys = content['keys'] != null
}) : super(userId, deviceId, algorithms, keys, signatures,
unsigned: unsigned);
DeviceKeys({
String userId,
String deviceId,
List<String> algorithms,
Map<String, String> keys,
Map<String, Map<String, String>> signatures,
Map<String, dynamic> unsigned,
this.verified,
this.blocked,
}) : super(userId, deviceId, algorithms, keys, signatures,
unsigned: unsigned);
factory DeviceKeys.fromMatrixDeviceKeys(MatrixDeviceKeys matrixDeviceKeys) =>
DeviceKeys(
userId: matrixDeviceKeys.userId,
deviceId: matrixDeviceKeys.deviceId,
algorithms: matrixDeviceKeys.algorithms,
keys: matrixDeviceKeys.keys,
signatures: matrixDeviceKeys.signatures,
unsigned: matrixDeviceKeys.unsigned,
verified: false,
blocked: false,
);
static DeviceKeys fromDb(DbUserDeviceKeysKey dbEntry) {
var deviceKeys = DeviceKeys();
final content = Event.getMapFromPayload(dbEntry.content);
deviceKeys.userId = dbEntry.userId;
deviceKeys.deviceId = dbEntry.deviceId;
deviceKeys.algorithms = content['algorithms'].cast<String>();
deviceKeys.keys = content['keys'] != null
? Map<String, String>.from(content['keys'])
: null;
signatures = content['signatures'] != null
? Map<String, dynamic>.from(content['signatures'])
deviceKeys.signatures = content['signatures'] != null
? Map<String, Map<String, String>>.from((content['signatures'] as Map)
.map((k, v) => MapEntry(k, Map<String, String>.from(v))))
: null;
unsigned = json['unsigned'] != null
? Map<String, dynamic>.from(json['unsigned'])
@ -401,8 +432,9 @@ class DeviceKeys extends SignedKey {
KeyVerification startVerification() {
final request =
KeyVerification(client: client, userId: userId, deviceId: deviceId);
request.start();
client.addKeyVerificationRequest(request);
client.encryption.keyVerificationManager.addRequest(request);
return request;
}
}

View File

@ -0,0 +1,65 @@
/*
* Famedly Matrix SDK
* Copyright (C) 2019, 2020 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 '../../famedlysdk.dart';
import '../../matrix_api.dart';
/// Represents a new event (e.g. a message in a room) or an update for an
/// already known event.
class EventUpdate {
/// Usually 'timeline', 'state' or whatever.
final String type;
/// Most events belong to a room. If not, this equals to eventType.
final String roomID;
/// See (Matrix Room Events)[https://matrix.org/docs/spec/client_server/r0.4.0.html#room-events]
/// and (Matrix Events)[https://matrix.org/docs/spec/client_server/r0.4.0.html#id89] for more
/// informations.
final String eventType;
// The json payload of the content of this event.
final Map<String, dynamic> content;
// the order where to stort this event
final double sortOrder;
EventUpdate(
{this.eventType, this.roomID, this.type, this.content, this.sortOrder});
Future<EventUpdate> decrypt(Room room, {bool store = false}) async {
if (eventType != EventTypes.Encrypted || !room.client.encryptionEnabled) {
return this;
}
try {
var decrpytedEvent = await room.client.encryption.decryptRoomEvent(
room.id, Event.fromJson(content, room, sortOrder),
store: store, updateType: type);
return EventUpdate(
eventType: decrpytedEvent.type,
roomID: roomID,
type: type,
content: decrpytedEvent.toJson(),
sortOrder: sortOrder,
);
} catch (e) {
print('[LibOlm] Could not decrypt megolm event: ' + e.toString());
return this;
}
}
}

View File

@ -1,23 +0,0 @@
class OpenIdCredentials {
String accessToken;
String tokenType;
String matrixServerName;
num expiresIn;
OpenIdCredentials.fromJson(Map<String, dynamic> json) {
accessToken = json['access_token'];
tokenType = json['token_type'];
matrixServerName = json['matrix_server_name'];
expiresIn = json['expires_in'];
}
Map<String, dynamic> toJson() {
var map = <String, dynamic>{};
final data = map;
data['access_token'] = accessToken;
data['token_type'] = tokenType;
data['matrix_server_name'] = matrixServerName;
data['expires_in'] = expiresIn;
return data;
}
}

View File

@ -1,25 +0,0 @@
/// Represents a user profile returned by a /profile request.
class Profile {
/// The user's avatar URL if they have set one, otherwise null.
final Uri avatarUrl;
/// The user's display name if they have set one, otherwise null.
final String displayname;
/// This API may return keys which are not limited to displayname or avatar_url.
final Map<String, dynamic> content;
const Profile(this.displayname, this.avatarUrl, {this.content = const {}});
Profile.fromJson(Map<String, dynamic> json)
: avatarUrl =
json['avatar_url'] != null ? Uri.parse(json['avatar_url']) : null,
displayname = json['displayname'],
content = json;
@override
bool operator ==(dynamic other) =>
(other is Profile) &&
avatarUrl == other.avatarUrl &&
displayname == other.displayname;
}

View File

@ -1,48 +0,0 @@
import '../client.dart';
class PublicRoomsResponse {
List<PublicRoomEntry> publicRooms;
final String nextBatch;
final String prevBatch;
final int totalRoomCountEstimate;
Client client;
PublicRoomsResponse.fromJson(Map<String, dynamic> json, Client client)
: nextBatch = json['next_batch'],
prevBatch = json['prev_batch'],
client = client,
totalRoomCountEstimate = json['total_room_count_estimate'] {
if (json['chunk'] != null) {
publicRooms = <PublicRoomEntry>[];
json['chunk'].forEach((v) {
publicRooms.add(PublicRoomEntry.fromJson(v, client));
});
}
}
}
class PublicRoomEntry {
final List<String> aliases;
final String avatarUrl;
final bool guestCanJoin;
final String name;
final int numJoinedMembers;
final String roomId;
final String topic;
final bool worldReadable;
Client client;
Future<void> join() => client.joinRoomById(roomId);
PublicRoomEntry.fromJson(Map<String, dynamic> json, Client client)
: aliases =
json.containsKey('aliases') ? json['aliases'].cast<String>() : [],
avatarUrl = json['avatar_url'],
guestCanJoin = json['guest_can_join'],
name = json['name'],
numJoinedMembers = json['num_joined_members'],
roomId = json['room_id'],
topic = json['topic'],
worldReadable = json['world_readable'],
client = client;
}

View File

@ -1,83 +0,0 @@
/// The global ruleset.
class PushRules {
final GlobalPushRules global;
PushRules.fromJson(Map<String, dynamic> json)
: global = GlobalPushRules.fromJson(json['global']);
}
/// The global ruleset.
class GlobalPushRules {
final List<PushRule> content;
final List<PushRule> override;
final List<PushRule> room;
final List<PushRule> sender;
final List<PushRule> underride;
GlobalPushRules.fromJson(Map<String, dynamic> json)
: content = json.containsKey('content')
? PushRule.fromJsonList(json['content'])
: null,
override = json.containsKey('override')
? PushRule.fromJsonList(json['content'])
: null,
room = json.containsKey('room')
? PushRule.fromJsonList(json['room'])
: null,
sender = json.containsKey('sender')
? PushRule.fromJsonList(json['sender'])
: null,
underride = json.containsKey('underride')
? PushRule.fromJsonList(json['underride'])
: null;
}
/// A single pushrule.
class PushRule {
final List actions;
final bool isDefault;
final bool enabled;
final String ruleId;
final List<PushRuleConditions> conditions;
final String pattern;
static List<PushRule> fromJsonList(List<dynamic> list) {
var objList = <PushRule>[];
list.forEach((json) {
objList.add(PushRule.fromJson(json));
});
return objList;
}
PushRule.fromJson(Map<String, dynamic> json)
: actions = json['actions'],
isDefault = json['default'],
enabled = json['enabled'],
ruleId = json['rule_id'],
conditions = json.containsKey('conditions')
? PushRuleConditions.fromJsonList(json['conditions'])
: null,
pattern = json['pattern'];
}
/// Conditions when this pushrule should be active.
class PushRuleConditions {
final String kind;
final String key;
final String pattern;
final String is_;
static List<PushRuleConditions> fromJsonList(List<dynamic> list) {
var objList = <PushRuleConditions>[];
list.forEach((json) {
objList.add(PushRuleConditions.fromJson(json));
});
return objList;
}
PushRuleConditions.fromJson(Map<String, dynamic> json)
: kind = json['kind'],
key = json['key'],
pattern = json['pattern'],
is_ = json['is'];
}

View File

@ -1,53 +0,0 @@
class Pusher {
String pushkey;
String kind;
String appId;
String appDisplayName;
String deviceDisplayName;
String profileTag;
String lang;
PusherData data;
Pusher.fromJson(Map<String, dynamic> json) {
pushkey = json['pushkey'];
kind = json['kind'];
appId = json['app_id'];
appDisplayName = json['app_display_name'];
deviceDisplayName = json['device_display_name'];
profileTag = json['profile_tag'];
lang = json['lang'];
data = json['data'] != null ? PusherData.fromJson(json['data']) : null;
}
Map<String, dynamic> toJson() {
final data = <String, dynamic>{};
data['pushkey'] = pushkey;
data['kind'] = kind;
data['app_id'] = appId;
data['app_display_name'] = appDisplayName;
data['device_display_name'] = deviceDisplayName;
data['profile_tag'] = profileTag;
data['lang'] = lang;
if (this.data != null) {
data['data'] = this.data.toJson();
}
return data;
}
}
class PusherData {
String url;
String format;
PusherData.fromJson(Map<String, dynamic> json) {
url = json['url'];
format = json['format'];
}
Map<String, dynamic> toJson() {
final data = <String, dynamic>{};
if (url != null) data['url'] = url;
if (format != null) data['format'] = format;
return data;
}
}

View File

@ -0,0 +1,92 @@
/*
* Famedly Matrix SDK
* Copyright (C) 2019, 2020 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 'package:famedlysdk/matrix_api.dart';
/// Represents a new room or an update for an
/// already known room.
class RoomUpdate {
/// All rooms have an idea in the format: !uniqueid:server.abc
final String id;
/// The current membership state of the user in this room.
final Membership membership;
/// Represents the number of unead notifications. This probably doesn't fit the number
/// of unread messages.
final num notification_count;
// The number of unread highlighted notifications.
final num highlight_count;
/// If there are too much new messages, the [homeserver] will only send the
/// last X (default is 10) messages and set the [limitedTimelinbe] flag to true.
final bool limitedTimeline;
/// Represents the current position of the client in the room history.
final String prev_batch;
final RoomSummary summary;
RoomUpdate({
this.id,
this.membership,
this.notification_count,
this.highlight_count,
this.limitedTimeline,
this.prev_batch,
this.summary,
});
factory RoomUpdate.fromSyncRoomUpdate(
SyncRoomUpdate update,
String roomId,
) =>
update is JoinedRoomUpdate
? RoomUpdate(
id: roomId,
membership: Membership.join,
notification_count:
update.unreadNotifications?.notificationCount ?? 0,
highlight_count: update.unreadNotifications?.highlightCount ?? 0,
limitedTimeline: update.timeline?.limited ?? false,
prev_batch: update.timeline?.prevBatch ?? '',
summary: update.summary,
)
: update is InvitedRoomUpdate
? RoomUpdate(
id: roomId,
membership: Membership.invite,
notification_count: 0,
highlight_count: 0,
limitedTimeline: false,
prev_batch: '',
summary: null,
)
: update is LeftRoomUpdate
? RoomUpdate(
id: roomId,
membership: Membership.leave,
notification_count: 0,
highlight_count: 0,
limitedTimeline: update.timeline?.limited ?? false,
prev_batch: update.timeline?.prevBatch ?? '',
summary: null,
)
: null;
}

View File

@ -1,59 +0,0 @@
import 'dart:convert';
import 'package:olm/olm.dart';
import '../database/database.dart' show DbInboundGroupSession;
import '../event.dart';
class SessionKey {
Map<String, dynamic> content;
Map<String, int> indexes;
InboundGroupSession inboundGroupSession;
final String key;
List<dynamic> get forwardingCurve25519KeyChain =>
content['forwarding_curve25519_key_chain'] ?? [];
String get senderClaimedEd25519Key =>
content['sender_claimed_ed25519_key'] ?? '';
SessionKey({this.content, this.inboundGroupSession, this.key, this.indexes});
SessionKey.fromDb(DbInboundGroupSession dbEntry, String key) : key = key {
final parsedContent = Event.getMapFromPayload(dbEntry.content);
final parsedIndexes = Event.getMapFromPayload(dbEntry.indexes);
content =
parsedContent != null ? Map<String, dynamic>.from(parsedContent) : null;
indexes = parsedIndexes != null
? Map<String, int>.from(parsedIndexes)
: <String, int>{};
var newInboundGroupSession = InboundGroupSession();
newInboundGroupSession.unpickle(key, dbEntry.pickle);
inboundGroupSession = newInboundGroupSession;
}
SessionKey.fromJson(Map<String, dynamic> json, String key) : key = key {
content = json['content'] != null
? Map<String, dynamic>.from(json['content'])
: null;
indexes = json['indexes'] != null
? Map<String, int>.from(json['indexes'])
: <String, int>{};
var newInboundGroupSession = InboundGroupSession();
newInboundGroupSession.unpickle(key, json['inboundGroupSession']);
inboundGroupSession = newInboundGroupSession;
}
Map<String, dynamic> toJson() {
final data = <String, dynamic>{};
if (content != null) {
data['content'] = content;
}
if (indexes != null) {
data['indexes'] = indexes;
}
data['inboundGroupSession'] = inboundGroupSession.pickle(key);
return data;
}
@override
String toString() => json.encode(toJson());
}

View File

@ -1,5 +1,7 @@
import 'package:famedlysdk/famedlysdk.dart';
import '../../matrix_api.dart';
/// Matrix room states are addressed by a tuple of the [type] and an
/// optional [stateKey].
class StatesMap {
@ -11,8 +13,10 @@ class StatesMap {
//print("[Warning] This method will be depracated in the future!");
if (key == null) return null;
if (key.startsWith('@') && key.contains(':')) {
if (!states.containsKey('m.room.member')) states['m.room.member'] = {};
return states['m.room.member'][key];
if (!states.containsKey(EventTypes.RoomMember)) {
states[EventTypes.RoomMember] = {};
}
return states[EventTypes.RoomMember][key];
}
if (!states.containsKey(key)) states[key] = {};
if (states[key][''] is Event) {
@ -27,8 +31,10 @@ class StatesMap {
void operator []=(String key, Event val) {
//print("[Warning] This method will be depracated in the future!");
if (key.startsWith('@') && key.contains(':')) {
if (!states.containsKey('m.room.member')) states['m.room.member'] = {};
states['m.room.member'][key] = val;
if (!states.containsKey(EventTypes.RoomMember)) {
states[EventTypes.RoomMember] = {};
}
states[EventTypes.RoomMember][key] = val;
}
if (!states.containsKey(key)) states[key] = {};
states[key][val.stateKey ?? ''] = val;

View File

@ -1,28 +1,27 @@
class ToDeviceEvent {
String sender;
String type;
Map<String, dynamic> content;
import 'package:famedlysdk/matrix_api.dart';
class ToDeviceEvent extends BasicEventWithSender {
Map<String, dynamic> encryptedContent;
ToDeviceEvent({this.sender, this.type, this.content, this.encryptedContent});
String get sender => senderId;
set sender(String sender) => senderId = sender;
ToDeviceEvent.fromJson(Map<String, dynamic> json) {
sender = json['sender'];
type = json['type'];
content = json['content'] != null
? Map<String, dynamic>.from(json['content'])
: null;
ToDeviceEvent({
String sender,
String type,
Map<String, dynamic> content,
this.encryptedContent,
}) {
senderId = sender;
this.type = type;
this.content = content;
}
Map<String, dynamic> toJson() {
var map = <String, dynamic>{};
final data = map;
data['sender'] = sender;
data['type'] = type;
if (content != null) {
data['content'] = content;
}
return data;
ToDeviceEvent.fromJson(Map<String, dynamic> json) {
final event = BasicEventWithSender.fromJson(json);
senderId = event.senderId;
type = event.type;
content = event.content;
}
}
@ -34,7 +33,7 @@ class ToDeviceEventDecryptionError extends ToDeviceEvent {
this.exception,
this.stackTrace,
}) : super(
sender: toDeviceEvent.sender,
sender: toDeviceEvent.senderId,
content: toDeviceEvent.content,
type: toDeviceEvent.type,
);

View File

@ -1,23 +0,0 @@
/// Credentials for the client to use when initiating calls.
class TurnServerCredentials {
/// The username to use.
final String username;
/// The password to use.
final String password;
/// A list of TURN URIs
final List<String> uris;
/// The time-to-live in seconds
final double ttl;
const TurnServerCredentials(
this.username, this.password, this.uris, this.ttl);
TurnServerCredentials.fromJson(Map<String, dynamic> json)
: username = json['username'],
password = json['password'],
uris = json['uris'].cast<String>(),
ttl = json['ttl'];
}

View File

@ -1,24 +1,19 @@
/*
* Copyright (c) 2019 Zender & Kurtz GbR.
* Famedly Matrix SDK
* Copyright (C) 2019, 2020 Famedly GmbH
*
* Authors:
* Christian Pauly <krille@famedly.com>
* Marcel Radzio <mtrnord@famedly.com>
* 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 file is part of famedlysdk.
* 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.
*
* 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 <http://www.gnu.org/licenses/>.
* 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 'package:famedlysdk/src/client.dart';
@ -27,8 +22,8 @@ import 'dart:core';
extension MxcUriExtension on Uri {
/// Returns a download Link to this content.
String getDownloadLink(Client matrix) => isScheme('mxc')
? matrix.homeserver != null
? '${matrix.homeserver}/_matrix/media/r0/download/$host$path'
? matrix.api.homeserver != null
? '${matrix.api.homeserver.toString()}/_matrix/media/r0/download/$host$path'
: ''
: toString();
@ -41,8 +36,8 @@ extension MxcUriExtension on Uri {
final methodStr = method.toString().split('.').last;
width = width.round();
height = height.round();
return matrix.homeserver != null
? '${matrix.homeserver}/_matrix/media/r0/thumbnail/$host$path?width=$width&height=$height&method=$methodStr'
return matrix.api.homeserver != null
? '${matrix.api.homeserver.toString()}/_matrix/media/r0/thumbnail/$host$path?width=$width&height=$height&method=$methodStr'
: '';
}
}

View File

@ -1,54 +0,0 @@
import '../client.dart';
/// Registered device for this user.
class UserDevice {
/// Identifier of this device.
final String deviceId;
/// Display name set by the user for this device. Absent if no name has been set.
final String displayName;
/// The IP address where this device was last seen. (May be a few minutes out of date, for efficiency reasons).
final String lastSeenIp;
/// The time when this devices was last seen. (May be a few minutes out of date, for efficiency reasons).
final DateTime lastSeenTs;
final Client _client;
/// Updates the metadata on the given device.
Future<void> updateMetaData(String newName) async {
await _client.jsonRequest(
type: HTTPType.PUT,
action: '/client/r0/devices/$deviceId',
data: {'display_name': newName},
);
return;
}
/// Deletes the given device, and invalidates any access token associated with it.
Future<void> deleteDevice(Map<String, dynamic> auth) async {
await _client.jsonRequest(
type: HTTPType.DELETE,
action: '/client/r0/devices/$deviceId',
data: auth != null ? {'auth': auth} : null,
);
return;
}
UserDevice(
this._client, {
this.deviceId,
this.displayName,
this.lastSeenIp,
this.lastSeenTs,
});
UserDevice.fromJson(Map<String, dynamic> json, Client client)
: deviceId = json['device_id'],
displayName = json['display_name'],
lastSeenIp = json['last_seen_ip'],
lastSeenTs =
DateTime.fromMillisecondsSinceEpoch(json['last_seen_ts'] ?? 0),
_client = client;
}

View File

@ -1,23 +0,0 @@
class WellKnownInformations {
MHomeserver mHomeserver;
MHomeserver mIdentityServer;
Map<String, dynamic> content;
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.fromJson(Map<String, dynamic> json) {
baseUrl = json['base_url'];
}
}

View File

@ -1,24 +1,19 @@
/*
* Copyright (c) 2019 Zender & Kurtz GbR.
* Ansible inventory script used at Famedly GmbH for managing many hosts
* Copyright (C) 2019, 2020 Famedly GmbH
*
* Authors:
* Christian Pauly <krille@famedly.com>
* Marcel Radzio <mtrnord@famedly.com>
* 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 file is part of famedlysdk.
* 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.
*
* 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 <http://www.gnu.org/licenses/>.
* 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 'package:canonical_json/canonical_json.dart';

View File

@ -1,39 +1,29 @@
/*
* Copyright (c) 2019 Zender & Kurtz GbR.
* Ansible inventory script used at Famedly GmbH for managing many hosts
* Copyright (C) 2019, 2020 Famedly GmbH
*
* Authors:
* Christian Pauly <krille@famedly.com>
* Marcel Radzio <mtrnord@famedly.com>
* 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 file is part of famedlysdk.
* 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.
*
* 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 <http://www.gnu.org/licenses/>.
* 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:async';
import 'dart:convert';
import 'dart:typed_data';
import 'package:famedlysdk/famedlysdk.dart';
import 'package:famedlysdk/src/account_data.dart';
import 'package:famedlysdk/matrix_api.dart';
import 'package:famedlysdk/src/client.dart';
import 'package:famedlysdk/src/presence.dart';
import 'package:famedlysdk/src/user.dart';
import 'package:famedlysdk/src/sync/event_update.dart';
import 'package:famedlysdk/src/sync/room_update.dart';
import 'package:famedlysdk/src/sync/user_update.dart';
import 'package:famedlysdk/src/utils/matrix_exception.dart';
import 'package:famedlysdk/src/utils/event_update.dart';
import 'package:famedlysdk/src/utils/room_update.dart';
import 'package:famedlysdk/src/utils/matrix_file.dart';
import 'package:olm/olm.dart' as olm;
import 'package:test/test.dart';
@ -46,11 +36,11 @@ void main() {
Future<List<RoomUpdate>> roomUpdateListFuture;
Future<List<EventUpdate>> eventUpdateListFuture;
Future<List<UserUpdate>> userUpdateListFuture;
Future<List<ToDeviceEvent>> toDeviceUpdateListFuture;
// key @test:fakeServer.notExisting
const pickledOlmAccount =
'N2v1MkIFGcl0mQpo2OCwSopxPQJ0wnl7oe7PKiT4141AijfdTIhRu+ceXzXKy3Kr00nLqXtRv7kid6hU4a+V0rfJWLL0Y51+3Rp/ORDVnQy+SSeo6Fn4FHcXrxifJEJ0djla5u98fBcJ8BSkhIDmtXRPi5/oJAvpiYn+8zMjFHobOeZUAxYR0VfQ9JzSYBsSovoQ7uFkNks1M4EDUvHtuweStA+EKZvvHZO0SnwRp0Hw7sv8UMYvXw';
'N2v1MkIFGcl0mQpo2OCwSopxPQJ0wnl7oe7PKiT4141AijfdTIhRu+ceXzXKy3Kr00nLqXtRv7kid6hU4a+V0rfJWLL0Y51+3Rp/ORDVnQy+SSeo6Fn4FHcXrxifJEJ0djla5u98fBcJ8BSkhIDmtXRPi5/oJAvpiYn+8zMjFHobOeZUAxYR0VfQ9JzSYBsSovoQ7uFkNks1M4EDUvHtuyg3RxViwdNxs3718fyAqQ/VSwbXsY0Nl+qQbF+nlVGHenGqk5SuNl1P6e1PzZxcR0IfXA94Xij1Ob5gDv5YH4UCn9wRMG0abZsQP0YzpDM0FLaHSCyo9i5JD/vMlhH+nZWrgAzPPCTNGYewNV8/h3c+VyJh8ZTx/fVi6Yq46Fv+27Ga2ETRZ3Qn+Oyx6dLBjnBZ9iUvIhqpe2XqaGA1PopOz8iDnaZitw';
const identityKey = '7rvl3jORJkBiK4XX1e5TnGnqz068XfYJ0W++Ml63rgk';
const fingerprintKey = 'gjL//fyaFHADt9KBADGag8g7F8Up78B/K1zXeiEPLJo';
@ -58,12 +48,10 @@ void main() {
group('FluffyMatrix', () {
/// Check if all Elements get created
matrix = Client('testclient', debug: true);
matrix.httpClient = FakeMatrixApi();
matrix = Client('testclient', debug: true, httpClient: FakeMatrixApi());
roomUpdateListFuture = matrix.onRoomUpdate.stream.toList();
eventUpdateListFuture = matrix.onEvent.stream.toList();
userUpdateListFuture = matrix.onUserEvent.stream.toList();
toDeviceUpdateListFuture = matrix.onToDeviceEvent.stream.toList();
var olmEnabled = true;
try {
@ -81,11 +69,11 @@ void main() {
matrix.onPresence.stream.listen((Presence data) {
presenceCounter++;
});
matrix.onAccountData.stream.listen((AccountData data) {
matrix.onAccountData.stream.listen((BasicEvent data) {
accountDataCounter++;
});
expect(matrix.homeserver, null);
expect(matrix.api.homeserver, null);
try {
await matrix.checkServer('https://fakeserver.wrongaddress');
@ -93,61 +81,51 @@ void main() {
expect(exception != null, true);
}
await matrix.checkServer('https://fakeserver.notexisting');
expect(matrix.homeserver, 'https://fakeserver.notexisting');
expect(
matrix.api.homeserver.toString(), 'https://fakeserver.notexisting');
final resp = await matrix
.jsonRequest(type: HTTPType.POST, action: '/client/r0/login', data: {
'type': 'm.login.password',
'user': 'test',
'password': '1234',
'initial_device_display_name': 'Fluffy Matrix Client'
});
final resp = await matrix.api.login(
type: 'm.login.password',
user: 'test',
password: '1234',
initialDeviceDisplayName: 'Fluffy Matrix Client',
);
final available = await matrix.usernameAvailable('testuser');
final available = await matrix.api.usernameAvailable('testuser');
expect(available, true);
Map registerResponse = await matrix.register(username: 'testuser');
expect(registerResponse['user_id'], '@testuser:example.com');
registerResponse =
await matrix.register(username: 'testuser', kind: 'user');
expect(registerResponse['user_id'], '@testuser:example.com');
registerResponse =
await matrix.register(username: 'testuser', kind: 'guest');
expect(registerResponse['user_id'], '@testuser:example.com');
var loginStateFuture = matrix.onLoginStateChanged.stream.first;
var firstSyncFuture = matrix.onFirstSync.stream.first;
var syncFuture = matrix.onSync.stream.first;
matrix.connect(
newToken: resp['access_token'],
newUserID: resp['user_id'],
newHomeserver: matrix.homeserver,
newToken: resp.accessToken,
newUserID: resp.userId,
newHomeserver: matrix.api.homeserver,
newDeviceName: 'Text Matrix Client',
newDeviceID: resp['device_id'],
newDeviceID: resp.deviceId,
newOlmAccount: pickledOlmAccount,
);
await Future.delayed(Duration(milliseconds: 50));
expect(matrix.accessToken == resp['access_token'], true);
expect(matrix.api.accessToken == resp.accessToken, true);
expect(matrix.deviceName == 'Text Matrix Client', true);
expect(matrix.deviceID == resp['device_id'], true);
expect(matrix.userID == resp['user_id'], true);
expect(matrix.deviceID == resp.deviceId, true);
expect(matrix.userID == resp.userId, true);
var loginState = await loginStateFuture;
var firstSync = await firstSyncFuture;
dynamic sync = await syncFuture;
var sync = await syncFuture;
expect(loginState, LoginState.logged);
expect(firstSync, true);
expect(matrix.encryptionEnabled, olmEnabled);
if (olmEnabled) {
expect(matrix.pickledOlmAccount, pickledOlmAccount);
expect(matrix.identityKey, identityKey);
expect(matrix.fingerprintKey, fingerprintKey);
}
expect(sync['next_batch'] == matrix.prevBatch, true);
expect(sync.nextBatch == matrix.prevBatch, true);
expect(matrix.accountData.length, 3);
expect(matrix.getDirectChatFromUserId('@bob:example.com'),
@ -156,24 +134,6 @@ void main() {
expect(matrix.directChats, matrix.accountData['m.direct'].content);
expect(matrix.presences.length, 1);
expect(matrix.rooms[1].ephemerals.length, 2);
expect(matrix.rooms[1].inboundGroupSessions.length, 1);
expect(
matrix
.rooms[1]
.inboundGroupSessions[
'ciM/JWTPrmiWPPZNkRLDPQYf9AW/I46bxyLSr+Bx5oU']
.content['session_key'],
'AgAAAAAQcQ6XrFJk6Prm8FikZDqfry/NbDz8Xw7T6e+/9Yf/q3YHIPEQlzv7IZMNcYb51ifkRzFejVvtphS7wwG2FaXIp4XS2obla14iKISR0X74ugB2vyb1AydIHE/zbBQ1ic5s3kgjMFlWpu/S3FQCnCrv+DPFGEt3ERGWxIl3Bl5X53IjPyVkz65oljz2TZESwz0GH/QFvyOOm8ci0q/gceaF3S7Dmafg3dwTKYwcA5xkcc+BLyrLRzB6Hn+oMAqSNSscnm4mTeT5zYibIhrzqyUTMWr32spFtI9dNR/RFSzfCw');
if (olmEnabled) {
expect(
matrix
.rooms[1]
.inboundGroupSessions[
'ciM/JWTPrmiWPPZNkRLDPQYf9AW/I46bxyLSr+Bx5oU']
.inboundGroupSession !=
null,
true);
}
expect(matrix.rooms[1].typingUsers.length, 1);
expect(matrix.rooms[1].typingUsers[0].id, '@alice:example.com');
expect(matrix.rooms[1].roomAccountData.length, 3);
@ -192,14 +152,14 @@ void main() {
expect(matrix.rooms[1].canonicalAlias,
"#famedlyContactDiscovery:${matrix.userID.split(":")[1]}");
final contacts = await matrix.loadFamedlyContacts();
expect(contacts.length, 1);
expect(contacts.length, 2);
expect(contacts[0].senderId, '@alice:example.com');
expect(
matrix.presences['@alice:example.com'].presence, PresenceType.online);
expect(matrix.presences['@alice:example.com'].presence.presence,
PresenceType.online);
expect(presenceCounter, 1);
expect(accountDataCounter, 3);
await Future.delayed(Duration(milliseconds: 50));
expect(matrix.userDeviceKeys.length, 2);
expect(matrix.userDeviceKeys.length, 4);
expect(matrix.userDeviceKeys['@alice:example.com'].outdated, false);
expect(matrix.userDeviceKeys['@alice:example.com'].deviceKeys.length, 2);
expect(
@ -207,7 +167,7 @@ void main() {
.verified,
false);
await matrix.handleSync({
await matrix.handleSync(SyncUpdate.fromJson({
'device_lists': {
'changed': [
'@alice:example.com',
@ -216,12 +176,12 @@ void main() {
'@bob:example.com',
],
}
});
}));
await Future.delayed(Duration(milliseconds: 50));
expect(matrix.userDeviceKeys.length, 2);
expect(matrix.userDeviceKeys.length, 3);
expect(matrix.userDeviceKeys['@alice:example.com'].outdated, true);
await matrix.handleSync({
await matrix.handleSync(SyncUpdate.fromJson({
'rooms': {
'join': {
'!726s6s6q:example.com': {
@ -240,7 +200,7 @@ void main() {
}
}
}
});
}));
await Future.delayed(Duration(milliseconds: 50));
expect(
@ -253,27 +213,15 @@ void main() {
expect(altContacts[0].senderId, '@alice:example.com');
});
test('Try to get ErrorResponse', () async {
MatrixException expectedException;
try {
await matrix.jsonRequest(
type: HTTPType.PUT, action: '/non/existing/path');
} on MatrixException catch (exception) {
expectedException = exception;
}
expect(expectedException.error, MatrixError.M_UNRECOGNIZED);
});
test('Logout', () async {
await matrix.jsonRequest(
type: HTTPType.POST, action: '/client/r0/logout');
await matrix.api.logout();
var loginStateFuture = matrix.onLoginStateChanged.stream.first;
matrix.clear();
expect(matrix.accessToken == null, true);
expect(matrix.homeserver == null, true);
expect(matrix.api.accessToken == null, true);
expect(matrix.api.homeserver == null, true);
expect(matrix.userID == null, true);
expect(matrix.deviceID == null, true);
expect(matrix.deviceName == null, true);
@ -288,7 +236,7 @@ void main() {
var roomUpdateList = await roomUpdateListFuture;
expect(roomUpdateList.length, 3);
expect(roomUpdateList.length, 4);
expect(roomUpdateList[0].id == '!726s6s6q:example.com', true);
expect(roomUpdateList[0].membership == Membership.join, true);
@ -361,23 +309,6 @@ void main() {
expect(eventUpdateList[11].type, 'invite_state');
});
test('User Update Test', () async {
await matrix.onUserEvent.close();
var eventUpdateList = await userUpdateListFuture;
expect(eventUpdateList.length, 4);
expect(eventUpdateList[0].eventType, 'm.presence');
expect(eventUpdateList[0].type, 'presence');
expect(eventUpdateList[1].eventType, 'm.push_rules');
expect(eventUpdateList[1].type, 'account_data');
expect(eventUpdateList[2].eventType, 'org.example.custom.config');
expect(eventUpdateList[2].type, 'account_data');
});
test('To Device Update Test', () async {
await matrix.onToDeviceEvent.close();
@ -386,16 +317,18 @@ void main() {
expect(eventUpdateList.length, 2);
expect(eventUpdateList[0].type, 'm.new_device');
expect(eventUpdateList[1].type, 'm.room_key');
if (olmEnabled) {
expect(eventUpdateList[1].type, 'm.room_key');
} else {
expect(eventUpdateList[1].type, 'm.room.encrypted');
}
});
test('Login', () async {
matrix = Client('testclient', debug: true);
matrix.httpClient = FakeMatrixApi();
matrix = Client('testclient', debug: true, httpClient: FakeMatrixApi());
roomUpdateListFuture = matrix.onRoomUpdate.stream.toList();
eventUpdateListFuture = matrix.onEvent.stream.toList();
userUpdateListFuture = matrix.onUserEvent.stream.toList();
final checkResp =
await matrix.checkServer('https://fakeServer.notExisting');
@ -405,55 +338,14 @@ void main() {
expect(loginResp, true);
});
test('createRoom', () async {
final openId = await matrix.requestOpenIdCredentials();
expect(openId.accessToken, 'SomeT0kenHere');
expect(openId.tokenType, 'Bearer');
expect(openId.matrixServerName, 'example.com');
expect(openId.expiresIn, 3600);
expect(openId.toJson(), {
'access_token': 'SomeT0kenHere',
'token_type': 'Bearer',
'matrix_server_name': 'example.com',
'expires_in': 3600
});
});
test('createRoom', () async {
final users = [
User('@alice:fakeServer.notExisting'),
User('@bob:fakeServer.notExisting')
];
final newID = await matrix.createRoom(invite: users);
expect(newID, '!1234:fakeServer.notExisting');
});
test('setAvatar', () async {
final testFile =
MatrixFile(bytes: Uint8List(0), path: 'fake/path/file.jpeg');
await matrix.setAvatar(testFile);
});
test('setPushers', () async {
await matrix.setPushers('abcdefg', 'http', 'com.famedly.famedlysdk',
'famedlySDK', 'GitLabCi', 'en', 'https://examplepushserver.com',
format: 'event_id_only');
});
test('joinRoomById', () async {
final roomID = '1234';
final Map<String, dynamic> resp = await matrix.joinRoomById(roomID);
expect(resp['room_id'], roomID);
});
test('requestUserDevices', () async {
final userDevices = await matrix.requestUserDevices();
expect(userDevices.length, 1);
expect(userDevices.first.deviceId, 'QBUAZIFURK');
expect(userDevices.first.displayName, 'android');
expect(userDevices.first.lastSeenIp, '1.2.3.4');
expect(
userDevices.first.lastSeenTs.millisecondsSinceEpoch, 1474491775024);
test('setMuteAllPushNotifications', () async {
await matrix.setMuteAllPushNotifications(false);
});
test('get archive', () async {
@ -476,123 +368,12 @@ void main() {
getFromRooms: false);
expect(profile.avatarUrl.toString(), 'mxc://test');
expect(profile.displayname, 'You got me');
expect(profile.content['avatar_url'], profile.avatarUrl.toString());
expect(profile.content['displayname'], profile.displayname);
final aliceProfile =
await matrix.getProfileFromUserId('@alice:example.com');
expect(aliceProfile.avatarUrl.toString(),
'mxc://example.org/SEsfnsuifSDFSSEF');
expect(aliceProfile.displayname, 'Alice Margatroid');
});
test('signJson', () {
if (matrix.encryptionEnabled) {
expect(matrix.fingerprintKey.isNotEmpty, true);
expect(matrix.identityKey.isNotEmpty, true);
var payload = <String, dynamic>{
'unsigned': {
'foo': 'bar',
},
'auth': {
'success': true,
'mxid': '@john.doe:example.com',
'profile': {
'display_name': 'John Doe',
'three_pids': [
{'medium': 'email', 'address': 'john.doe@example.org'},
{'medium': 'msisdn', 'address': '123456789'}
]
}
}
};
var payloadWithoutUnsigned = Map<String, dynamic>.from(payload);
payloadWithoutUnsigned.remove('unsigned');
expect(
matrix.checkJsonSignature(
matrix.fingerprintKey, payload, matrix.userID, matrix.deviceID),
false);
expect(
matrix.checkJsonSignature(matrix.fingerprintKey,
payloadWithoutUnsigned, matrix.userID, matrix.deviceID),
false);
payload = matrix.signJson(payload);
payloadWithoutUnsigned = matrix.signJson(payloadWithoutUnsigned);
expect(payload['signatures'], payloadWithoutUnsigned['signatures']);
print(payload['signatures']);
expect(
matrix.checkJsonSignature(
matrix.fingerprintKey, payload, matrix.userID, matrix.deviceID),
true);
expect(
matrix.checkJsonSignature(matrix.fingerprintKey,
payloadWithoutUnsigned, matrix.userID, matrix.deviceID),
true);
}
});
test('Track oneTimeKeys', () async {
if (matrix.encryptionEnabled) {
var last = matrix.lastTimeKeysUploaded ?? DateTime.now();
await matrix.handleSync({
'device_one_time_keys_count': {'signed_curve25519': 49}
});
await Future.delayed(Duration(milliseconds: 50));
expect(
matrix.lastTimeKeysUploaded.millisecondsSinceEpoch >
last.millisecondsSinceEpoch,
true);
}
});
test('Test invalidate outboundGroupSessions', () async {
if (matrix.encryptionEnabled) {
expect(matrix.rooms[1].outboundGroupSession == null, true);
await matrix.rooms[1].createOutboundGroupSession();
expect(matrix.rooms[1].outboundGroupSession != null, true);
await matrix.handleSync({
'device_lists': {
'changed': [
'@alice:example.com',
],
'left': [
'@bob:example.com',
],
}
});
await Future.delayed(Duration(milliseconds: 50));
expect(matrix.rooms[1].outboundGroupSession != null, true);
}
});
test('Test invalidate outboundGroupSessions', () async {
if (matrix.encryptionEnabled) {
await matrix.rooms[1].clearOutboundGroupSession(wipe: true);
expect(matrix.rooms[1].outboundGroupSession == null, true);
await matrix.rooms[1].createOutboundGroupSession();
expect(matrix.rooms[1].outboundGroupSession != null, true);
await matrix.handleSync({
'rooms': {
'join': {
'!726s6s6q:example.com': {
'state': {
'events': [
{
'content': {'membership': 'leave'},
'event_id': '143273582443PhrSn:example.org',
'origin_server_ts': 1432735824653,
'room_id': '!726s6s6q:example.com',
'sender': '@alice:example.com',
'state_key': '@alice:example.com',
'type': 'm.room.member'
}
]
}
}
}
}
});
await Future.delayed(Duration(milliseconds: 50));
expect(matrix.rooms[1].outboundGroupSession != null, true);
}
});
var deviceKeys = DeviceKeys.fromJson({
'user_id': '@alice:example.com',
'device_id': 'JLAFKJWSCS',
@ -607,16 +388,6 @@ void main() {
'dSO80A01XiigH3uBiDVx/EjzaoycHcjq9lfQX0uWsqxl2giMIiSPR8a4d291W1ihKJL/a+myXS367WT6NAIcBA'
}
}
}, matrix);
test('startOutgoingOlmSessions', () async {
expect(matrix.olmSessions.length, 0);
if (olmEnabled) {
await matrix
.startOutgoingOlmSessions([deviceKeys], checkSignature: false);
expect(matrix.olmSessions.length, 1);
expect(matrix.olmSessions.entries.first.key,
'3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI');
}
});
test('sendToDevice', () async {
await matrix.sendToDevice(
@ -627,29 +398,15 @@ void main() {
'body': 'Hello world',
});
});
test('Logout when token is unknown', () async {
var loginStateFuture = matrix.onLoginStateChanged.stream.first;
try {
await matrix.jsonRequest(
type: HTTPType.DELETE, action: '/unknown/token');
} on MatrixException catch (exception) {
expect(exception.error, MatrixError.M_UNKNOWN_TOKEN);
}
var state = await loginStateFuture;
expect(state, LoginState.loggedOut);
expect(matrix.isLogged(), false);
});
test('Test the fake store api', () async {
var client1 = Client('testclient', debug: true);
client1.httpClient = FakeMatrixApi();
var client1 =
Client('testclient', debug: true, httpClient: FakeMatrixApi());
client1.database = getDatabase();
client1.connect(
newToken: 'abc123',
newUserID: '@test:fakeServer.notExisting',
newHomeserver: 'https://fakeServer.notExisting',
newHomeserver: Uri.parse('https://fakeServer.notExisting'),
newDeviceName: 'Text Matrix Client',
newDeviceID: 'GHTYAJCE',
newOlmAccount: pickledOlmAccount,
@ -657,40 +414,37 @@ void main() {
await Future.delayed(Duration(milliseconds: 50));
String sessionKey;
if (client1.encryptionEnabled) {
await client1.rooms[1].createOutboundGroupSession();
sessionKey = client1.rooms[1].outboundGroupSession.session_key();
}
expect(client1.isLogged(), true);
expect(client1.rooms.length, 2);
var client2 = Client('testclient', debug: true);
client2.httpClient = FakeMatrixApi();
var client2 =
Client('testclient', debug: true, httpClient: FakeMatrixApi());
client2.database = client1.database;
client2.connect();
await Future.delayed(Duration(milliseconds: 100));
expect(client2.isLogged(), true);
expect(client2.accessToken, client1.accessToken);
expect(client2.api.accessToken, client1.api.accessToken);
expect(client2.userID, client1.userID);
expect(client2.homeserver, client1.homeserver);
expect(client2.api.homeserver, client1.api.homeserver);
expect(client2.deviceID, client1.deviceID);
expect(client2.deviceName, client1.deviceName);
if (client2.encryptionEnabled) {
await client2.rooms[1].restoreOutboundGroupSession();
expect(client2.pickledOlmAccount, client1.pickledOlmAccount);
expect(json.encode(client2.rooms[1].inboundGroupSessions[sessionKey]),
json.encode(client1.rooms[1].inboundGroupSessions[sessionKey]));
expect(client2.encryption.pickledOlmAccount,
client1.encryption.pickledOlmAccount);
expect(client2.rooms[1].id, client1.rooms[1].id);
expect(client2.rooms[1].outboundGroupSession.session_key(), sessionKey);
}
await client1.logout();
await client2.logout();
});
test('changePassword', () async {
await matrix.changePassword('1234', oldPassword: '123456');
});
test('dispose', () async {
await matrix.dispose(closeDatabase: true);
});
});
}

View File

@ -1,24 +1,19 @@
/*
* Copyright (c) 2019 Zender & Kurtz GbR.
* Ansible inventory script used at Famedly GmbH for managing many hosts
* Copyright (C) 2019, 2020 Famedly GmbH
*
* Authors:
* Christian Pauly <krille@famedly.com>
* Marcel Radzio <mtrnord@famedly.com>
* 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 file is part of famedlysdk.
* 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.
*
* 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 <http://www.gnu.org/licenses/>.
* 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:convert';

View File

@ -0,0 +1,99 @@
/*
* Ansible inventory script used at Famedly GmbH for managing many hosts
* Copyright (C) 2020 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 'package:famedlysdk/famedlysdk.dart';
import 'package:test/test.dart';
import 'package:olm/olm.dart' as olm;
import '../fake_client.dart';
void main() {
group('Encrypt/Decrypt room message', () {
var olmEnabled = true;
try {
olm.init();
olm.Account();
} catch (_) {
olmEnabled = false;
print('[LibOlm] Failed to load LibOlm: ' + _.toString());
}
print('[LibOlm] Enabled: $olmEnabled');
if (!olmEnabled) return;
Client client;
final roomId = '!726s6s6q:example.com';
Room room;
Map<String, dynamic> payload;
final now = DateTime.now();
test('setupClient', () async {
client = await getClient();
room = client.getRoomById(roomId);
});
test('encrypt payload', () async {
payload = await client.encryption.encryptGroupMessagePayload(roomId, {
'msgtype': 'm.text',
'text': 'Hello foxies!',
});
expect(payload['algorithm'], 'm.megolm.v1.aes-sha2');
expect(payload['ciphertext'] is String, true);
expect(payload['device_id'], client.deviceID);
expect(payload['sender_key'], client.identityKey);
expect(payload['session_id'] is String, true);
});
test('decrypt payload', () async {
final encryptedEvent = Event(
type: EventTypes.Encrypted,
content: payload,
roomId: roomId,
room: room,
originServerTs: now,
eventId: '\$event',
);
final decryptedEvent =
await client.encryption.decryptRoomEvent(roomId, encryptedEvent);
expect(decryptedEvent.type, 'm.room.message');
expect(decryptedEvent.content['msgtype'], 'm.text');
expect(decryptedEvent.content['text'], 'Hello foxies!');
});
test('decrypt payload nocache', () async {
client.encryption.keyManager.clearInboundGroupSessions();
final encryptedEvent = Event(
type: EventTypes.Encrypted,
content: payload,
roomId: roomId,
room: room,
originServerTs: now,
eventId: '\$event',
);
final decryptedEvent =
await client.encryption.decryptRoomEvent(roomId, encryptedEvent);
expect(decryptedEvent.type, 'm.room.message');
expect(decryptedEvent.content['msgtype'], 'm.text');
expect(decryptedEvent.content['text'], 'Hello foxies!');
});
test('dispose client', () async {
await client.dispose(closeDatabase: true);
});
});
}

View File

@ -0,0 +1,120 @@
/*
* Ansible inventory script used at Famedly GmbH for managing many hosts
* Copyright (C) 2020 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 'package:famedlysdk/famedlysdk.dart';
import 'package:test/test.dart';
import 'package:olm/olm.dart' as olm;
import '../fake_client.dart';
import '../fake_matrix_api.dart';
void main() {
// key @othertest:fakeServer.notExisting
const otherPickledOlmAccount =
'VWhVApbkcilKAEGppsPDf9nNVjaK8/IxT3asSR0sYg0S5KgbfE8vXEPwoiKBX2cEvwX3OessOBOkk+ZE7TTbjlrh/KEd31p8Wo+47qj0AP+Ky+pabnhi+/rTBvZy+gfzTqUfCxZrkzfXI9Op4JnP6gYmy7dVX2lMYIIs9WCO1jcmIXiXum5jnfXu1WLfc7PZtO2hH+k9CDKosOFaXRBmsu8k/BGXPSoWqUpvu6WpEG9t5STk4FeAzA';
group('Encrypt/Decrypt to-device messages', () {
var olmEnabled = true;
try {
olm.init();
olm.Account();
} catch (_) {
olmEnabled = false;
print('[LibOlm] Failed to load LibOlm: ' + _.toString());
}
print('[LibOlm] Enabled: $olmEnabled');
if (!olmEnabled) return;
Client client;
var otherClient =
Client('othertestclient', debug: true, httpClient: FakeMatrixApi());
DeviceKeys device;
Map<String, dynamic> payload;
test('setupClient', () async {
client = await getClient();
otherClient.database = client.database;
await otherClient.checkServer('https://fakeServer.notExisting');
otherClient.connect(
newToken: 'abc',
newUserID: '@othertest:fakeServer.notExisting',
newHomeserver: otherClient.api.homeserver,
newDeviceName: 'Text Matrix Client',
newDeviceID: 'FOXDEVICE',
newOlmAccount: otherPickledOlmAccount,
);
await Future.delayed(Duration(milliseconds: 10));
device = DeviceKeys(
userId: client.userID,
deviceId: client.deviceID,
algorithms: ['m.olm.v1.curve25519-aes-sha2', 'm.megolm.v1.aes-sha2'],
keys: {
'curve25519:${client.deviceID}': client.identityKey,
'ed25519:${client.deviceID}': client.fingerprintKey,
},
verified: true,
blocked: false,
);
});
test('encryptToDeviceMessage', () async {
payload = await otherClient.encryption
.encryptToDeviceMessage([device], 'm.to_device', {'hello': 'foxies'});
});
test('encryptToDeviceMessagePayload', () async {
// just a hard test if nothing errors
await otherClient.encryption.encryptToDeviceMessagePayload(
device, 'm.to_device', {'hello': 'foxies'});
});
test('decryptToDeviceEvent', () async {
final encryptedEvent = ToDeviceEvent(
sender: '@othertest:fakeServer.notExisting',
type: EventTypes.Encrypted,
content: payload[client.userID][client.deviceID],
);
final decryptedEvent =
await client.encryption.decryptToDeviceEvent(encryptedEvent);
expect(decryptedEvent.type, 'm.to_device');
expect(decryptedEvent.content['hello'], 'foxies');
});
test('decryptToDeviceEvent nocache', () async {
client.encryption.olmManager.olmSessions.clear();
payload = await otherClient.encryption.encryptToDeviceMessage(
[device], 'm.to_device', {'hello': 'superfoxies'});
final encryptedEvent = ToDeviceEvent(
sender: '@othertest:fakeServer.notExisting',
type: EventTypes.Encrypted,
content: payload[client.userID][client.deviceID],
);
final decryptedEvent =
await client.encryption.decryptToDeviceEvent(encryptedEvent);
expect(decryptedEvent.type, 'm.to_device');
expect(decryptedEvent.content['hello'], 'superfoxies');
});
test('dispose client', () async {
await client.dispose(closeDatabase: true);
await otherClient.dispose(closeDatabase: true);
});
});
}

View File

@ -0,0 +1,223 @@
/*
* Ansible inventory script used at Famedly GmbH for managing many hosts
* Copyright (C) 2020 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 'package:famedlysdk/famedlysdk.dart';
import 'package:test/test.dart';
import 'package:olm/olm.dart' as olm;
import '../fake_client.dart';
void main() {
group('Key Manager', () {
var olmEnabled = true;
try {
olm.init();
olm.Account();
} catch (_) {
olmEnabled = false;
print('[LibOlm] Failed to load LibOlm: ' + _.toString());
}
print('[LibOlm] Enabled: $olmEnabled');
if (!olmEnabled) return;
Client client;
test('setupClient', () async {
client = await getClient();
});
test('handle new m.room_key', () async {
final validSessionId = 'ciM/JWTPrmiWPPZNkRLDPQYf9AW/I46bxyLSr+Bx5oU';
final validSenderKey = 'JBG7ZaPn54OBC7TuIEiylW3BZ+7WcGQhFBPB9pogbAg';
final sessionKey =
'AgAAAAAQcQ6XrFJk6Prm8FikZDqfry/NbDz8Xw7T6e+/9Yf/q3YHIPEQlzv7IZMNcYb51ifkRzFejVvtphS7wwG2FaXIp4XS2obla14iKISR0X74ugB2vyb1AydIHE/zbBQ1ic5s3kgjMFlWpu/S3FQCnCrv+DPFGEt3ERGWxIl3Bl5X53IjPyVkz65oljz2TZESwz0GH/QFvyOOm8ci0q/gceaF3S7Dmafg3dwTKYwcA5xkcc+BLyrLRzB6Hn+oMAqSNSscnm4mTeT5zYibIhrzqyUTMWr32spFtI9dNR/RFSzfCw';
client.encryption.keyManager.clearInboundGroupSessions();
var event = ToDeviceEvent(
sender: '@alice:example.com',
type: 'm.room_key',
content: {
'algorithm': 'm.megolm.v1.aes-sha2',
'room_id': '!726s6s6q:example.com',
'session_id': validSessionId,
'session_key': sessionKey,
},
encryptedContent: {
'sender_key': validSessionId,
});
await client.encryption.keyManager.handleToDeviceEvent(event);
expect(
client.encryption.keyManager.getInboundGroupSession(
'!726s6s6q:example.com', validSessionId, validSenderKey) !=
null,
true);
// now test a few invalid scenarios
// not encrypted
client.encryption.keyManager.clearInboundGroupSessions();
event = ToDeviceEvent(
sender: '@alice:example.com',
type: 'm.room_key',
content: {
'algorithm': 'm.megolm.v1.aes-sha2',
'room_id': '!726s6s6q:example.com',
'session_id': validSessionId,
'session_key': sessionKey,
});
await client.encryption.keyManager.handleToDeviceEvent(event);
expect(
client.encryption.keyManager.getInboundGroupSession(
'!726s6s6q:example.com', validSessionId, validSenderKey) !=
null,
false);
});
test('outbound group session', () async {
final roomId = '!726s6s6q:example.com';
expect(
client.encryption.keyManager.getOutboundGroupSession(roomId) != null,
false);
var sess =
await client.encryption.keyManager.createOutboundGroupSession(roomId);
expect(
client.encryption.keyManager.getOutboundGroupSession(roomId) != null,
true);
await client.encryption.keyManager.clearOutboundGroupSession(roomId);
expect(
client.encryption.keyManager.getOutboundGroupSession(roomId) != null,
true);
expect(
client.encryption.keyManager.getInboundGroupSession(roomId,
sess.outboundGroupSession.session_id(), client.identityKey) !=
null,
true);
// rotate after too many messages
sess.sentMessages = 300;
await client.encryption.keyManager.clearOutboundGroupSession(roomId);
expect(
client.encryption.keyManager.getOutboundGroupSession(roomId) != null,
false);
// rotate if devices in room change
sess =
await client.encryption.keyManager.createOutboundGroupSession(roomId);
client.userDeviceKeys['@alice:example.com'].deviceKeys['JLAFKJWSCS']
.blocked = true;
await client.encryption.keyManager.clearOutboundGroupSession(roomId);
expect(
client.encryption.keyManager.getOutboundGroupSession(roomId) != null,
false);
client.userDeviceKeys['@alice:example.com'].deviceKeys['JLAFKJWSCS']
.blocked = false;
// rotate if too far in the past
sess =
await client.encryption.keyManager.createOutboundGroupSession(roomId);
sess.creationTime = DateTime.now().subtract(Duration(days: 30));
await client.encryption.keyManager.clearOutboundGroupSession(roomId);
expect(
client.encryption.keyManager.getOutboundGroupSession(roomId) != null,
false);
// force wipe
sess =
await client.encryption.keyManager.createOutboundGroupSession(roomId);
await client.encryption.keyManager
.clearOutboundGroupSession(roomId, wipe: true);
expect(
client.encryption.keyManager.getOutboundGroupSession(roomId) != null,
false);
// load from database
sess =
await client.encryption.keyManager.createOutboundGroupSession(roomId);
client.encryption.keyManager.clearOutboundGroupSessions();
expect(
client.encryption.keyManager.getOutboundGroupSession(roomId) != null,
false);
await client.encryption.keyManager.loadOutboundGroupSession(roomId);
expect(
client.encryption.keyManager.getOutboundGroupSession(roomId) != null,
true);
});
test('inbound group session', () async {
final roomId = '!726s6s6q:example.com';
final sessionId = 'ciM/JWTPrmiWPPZNkRLDPQYf9AW/I46bxyLSr+Bx5oU';
final senderKey = 'JBG7ZaPn54OBC7TuIEiylW3BZ+7WcGQhFBPB9pogbAg';
final sessionContent = <String, dynamic>{
'algorithm': 'm.megolm.v1.aes-sha2',
'room_id': '!726s6s6q:example.com',
'session_id': 'ciM/JWTPrmiWPPZNkRLDPQYf9AW/I46bxyLSr+Bx5oU',
'session_key':
'AgAAAAAQcQ6XrFJk6Prm8FikZDqfry/NbDz8Xw7T6e+/9Yf/q3YHIPEQlzv7IZMNcYb51ifkRzFejVvtphS7wwG2FaXIp4XS2obla14iKISR0X74ugB2vyb1AydIHE/zbBQ1ic5s3kgjMFlWpu/S3FQCnCrv+DPFGEt3ERGWxIl3Bl5X53IjPyVkz65oljz2TZESwz0GH/QFvyOOm8ci0q/gceaF3S7Dmafg3dwTKYwcA5xkcc+BLyrLRzB6Hn+oMAqSNSscnm4mTeT5zYibIhrzqyUTMWr32spFtI9dNR/RFSzfCw'
};
client.encryption.keyManager.clearInboundGroupSessions();
expect(
client.encryption.keyManager
.getInboundGroupSession(roomId, sessionId, senderKey) !=
null,
false);
client.encryption.keyManager
.setInboundGroupSession(roomId, sessionId, senderKey, sessionContent);
await Future.delayed(Duration(milliseconds: 10));
expect(
client.encryption.keyManager
.getInboundGroupSession(roomId, sessionId, senderKey) !=
null,
true);
expect(
client.encryption.keyManager
.getInboundGroupSession(roomId, sessionId, senderKey) !=
null,
true);
expect(
client.encryption.keyManager
.getInboundGroupSession('otherroom', sessionId, senderKey) !=
null,
true);
expect(
client.encryption.keyManager
.getInboundGroupSession('otherroom', 'invalid', senderKey) !=
null,
false);
client.encryption.keyManager.clearInboundGroupSessions();
expect(
client.encryption.keyManager
.getInboundGroupSession(roomId, sessionId, senderKey) !=
null,
false);
await client.encryption.keyManager
.loadInboundGroupSession(roomId, sessionId, senderKey);
expect(
client.encryption.keyManager
.getInboundGroupSession(roomId, sessionId, senderKey) !=
null,
true);
});
test('dispose client', () async {
await client.dispose(closeDatabase: true);
});
});
}

View File

@ -0,0 +1,331 @@
/*
* Ansible inventory script used at Famedly GmbH for managing many hosts
* Copyright (C) 2020 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:convert';
import 'package:famedlysdk/famedlysdk.dart';
import 'package:test/test.dart';
import 'package:olm/olm.dart' as olm;
import '../fake_client.dart';
import '../fake_matrix_api.dart';
Map<String, dynamic> jsonDecode(dynamic payload) {
if (payload is String) {
try {
return json.decode(payload);
} catch (e) {
return {};
}
}
if (payload is Map<String, dynamic>) return payload;
return {};
}
void main() {
/// All Tests related to device keys
group('Key Request', () {
var olmEnabled = true;
try {
olm.init();
olm.Account();
} catch (_) {
olmEnabled = false;
print('[LibOlm] Failed to load LibOlm: ' + _.toString());
}
print('[LibOlm] Enabled: $olmEnabled');
if (!olmEnabled) return;
final validSessionId = 'ciM/JWTPrmiWPPZNkRLDPQYf9AW/I46bxyLSr+Bx5oU';
final validSenderKey = '3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI';
test('Create Request', () async {
var matrix = await getClient();
final requestRoom = matrix.getRoomById('!726s6s6q:example.com');
await matrix.encryption.keyManager
.request(requestRoom, 'sessionId', validSenderKey);
var foundEvent = false;
for (var entry in FakeMatrixApi.calledEndpoints.entries) {
final payload = jsonDecode(entry.value.first);
if (entry.key
.startsWith('/client/r0/sendToDevice/m.room_key_request') &&
(payload['messages'] is Map) &&
(payload['messages']['@alice:example.com'] is Map) &&
(payload['messages']['@alice:example.com']['*'] is Map)) {
final content = payload['messages']['@alice:example.com']['*'];
if (content['action'] == 'request' &&
content['body']['room_id'] == '!726s6s6q:example.com' &&
content['body']['sender_key'] == validSenderKey &&
content['body']['session_id'] == 'sessionId') {
foundEvent = true;
break;
}
}
}
expect(foundEvent, true);
await matrix.dispose(closeDatabase: true);
});
test('Reply To Request', () async {
var matrix = await getClient();
matrix.setUserId('@alice:example.com'); // we need to pretend to be alice
FakeMatrixApi.calledEndpoints.clear();
await matrix
.userDeviceKeys['@alice:example.com'].deviceKeys['OTHERDEVICE']
.setBlocked(false, matrix);
await matrix
.userDeviceKeys['@alice:example.com'].deviceKeys['OTHERDEVICE']
.setVerified(true, matrix);
// test a successful share
var event = ToDeviceEvent(
sender: '@alice:example.com',
type: 'm.room_key_request',
content: {
'action': 'request',
'body': {
'algorithm': 'm.megolm.v1.aes-sha2',
'room_id': '!726s6s6q:example.com',
'sender_key': validSenderKey,
'session_id': validSessionId,
},
'request_id': 'request_1',
'requesting_device_id': 'OTHERDEVICE',
});
await matrix.encryption.keyManager.handleToDeviceEvent(event);
print(FakeMatrixApi.calledEndpoints.keys.toString());
expect(
FakeMatrixApi.calledEndpoints.keys.any(
(k) => k.startsWith('/client/r0/sendToDevice/m.room.encrypted')),
true);
// test various fail scenarios
// no body
FakeMatrixApi.calledEndpoints.clear();
event = ToDeviceEvent(
sender: '@alice:example.com',
type: 'm.room_key_request',
content: {
'action': 'request',
'request_id': 'request_2',
'requesting_device_id': 'OTHERDEVICE',
});
await matrix.encryption.keyManager.handleToDeviceEvent(event);
expect(
FakeMatrixApi.calledEndpoints.keys.any(
(k) => k.startsWith('/client/r0/sendToDevice/m.room.encrypted')),
false);
// request by ourself
FakeMatrixApi.calledEndpoints.clear();
event = ToDeviceEvent(
sender: '@alice:example.com',
type: 'm.room_key_request',
content: {
'action': 'request',
'body': {
'algorithm': 'm.megolm.v1.aes-sha2',
'room_id': '!726s6s6q:example.com',
'sender_key': validSenderKey,
'session_id': validSessionId,
},
'request_id': 'request_3',
'requesting_device_id': 'JLAFKJWSCS',
});
await matrix.encryption.keyManager.handleToDeviceEvent(event);
expect(
FakeMatrixApi.calledEndpoints.keys.any(
(k) => k.startsWith('/client/r0/sendToDevice/m.room.encrypted')),
false);
// device not found
FakeMatrixApi.calledEndpoints.clear();
event = ToDeviceEvent(
sender: '@alice:example.com',
type: 'm.room_key_request',
content: {
'action': 'request',
'body': {
'algorithm': 'm.megolm.v1.aes-sha2',
'room_id': '!726s6s6q:example.com',
'sender_key': validSenderKey,
'session_id': validSessionId,
},
'request_id': 'request_4',
'requesting_device_id': 'blubb',
});
await matrix.encryption.keyManager.handleToDeviceEvent(event);
expect(
FakeMatrixApi.calledEndpoints.keys.any(
(k) => k.startsWith('/client/r0/sendToDevice/m.room.encrypted')),
false);
// unknown room
FakeMatrixApi.calledEndpoints.clear();
event = ToDeviceEvent(
sender: '@alice:example.com',
type: 'm.room_key_request',
content: {
'action': 'request',
'body': {
'algorithm': 'm.megolm.v1.aes-sha2',
'room_id': '!invalid:example.com',
'sender_key': validSenderKey,
'session_id': validSessionId,
},
'request_id': 'request_5',
'requesting_device_id': 'OTHERDEVICE',
});
await matrix.encryption.keyManager.handleToDeviceEvent(event);
expect(
FakeMatrixApi.calledEndpoints.keys.any(
(k) => k.startsWith('/client/r0/sendToDevice/m.room.encrypted')),
false);
// unknwon session
FakeMatrixApi.calledEndpoints.clear();
event = ToDeviceEvent(
sender: '@alice:example.com',
type: 'm.room_key_request',
content: {
'action': 'request',
'body': {
'algorithm': 'm.megolm.v1.aes-sha2',
'room_id': '!726s6s6q:example.com',
'sender_key': validSenderKey,
'session_id': 'invalid',
},
'request_id': 'request_6',
'requesting_device_id': 'OTHERDEVICE',
});
await matrix.encryption.keyManager.handleToDeviceEvent(event);
expect(
FakeMatrixApi.calledEndpoints.keys.any(
(k) => k.startsWith('/client/r0/sendToDevice/m.room.encrypted')),
false);
FakeMatrixApi.calledEndpoints.clear();
await matrix.dispose(closeDatabase: true);
});
test('Receive shared keys', () async {
var matrix = await getClient();
final requestRoom = matrix.getRoomById('!726s6s6q:example.com');
await matrix.encryption.keyManager
.request(requestRoom, validSessionId, validSenderKey);
final session = await matrix.encryption.keyManager
.loadInboundGroupSession(
requestRoom.id, validSessionId, validSenderKey);
final sessionKey = session.inboundGroupSession
.export_session(session.inboundGroupSession.first_known_index());
matrix.encryption.keyManager.clearInboundGroupSessions();
var event = ToDeviceEvent(
sender: '@alice:example.com',
type: 'm.forwarded_room_key',
content: {
'algorithm': 'm.megolm.v1.aes-sha2',
'room_id': '!726s6s6q:example.com',
'session_id': validSessionId,
'session_key': sessionKey,
'sender_key': validSenderKey,
'forwarding_curve25519_key_chain': [],
},
encryptedContent: {
'sender_key': '3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI',
});
await matrix.encryption.keyManager.handleToDeviceEvent(event);
expect(
matrix.encryption.keyManager.getInboundGroupSession(
requestRoom.id, validSessionId, validSenderKey) !=
null,
true);
// now test a few invalid scenarios
// request not found
matrix.encryption.keyManager.clearInboundGroupSessions();
event = ToDeviceEvent(
sender: '@alice:example.com',
type: 'm.forwarded_room_key',
content: {
'algorithm': 'm.megolm.v1.aes-sha2',
'room_id': '!726s6s6q:example.com',
'session_id': validSessionId,
'session_key': sessionKey,
'sender_key': validSenderKey,
'forwarding_curve25519_key_chain': [],
},
encryptedContent: {
'sender_key': '3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI',
});
await matrix.encryption.keyManager.handleToDeviceEvent(event);
expect(
matrix.encryption.keyManager.getInboundGroupSession(
requestRoom.id, validSessionId, validSenderKey) !=
null,
false);
// unknown device
await matrix.encryption.keyManager
.request(requestRoom, validSessionId, validSenderKey);
matrix.encryption.keyManager.clearInboundGroupSessions();
event = ToDeviceEvent(
sender: '@alice:example.com',
type: 'm.forwarded_room_key',
content: {
'algorithm': 'm.megolm.v1.aes-sha2',
'room_id': '!726s6s6q:example.com',
'session_id': validSessionId,
'session_key': sessionKey,
'sender_key': validSenderKey,
'forwarding_curve25519_key_chain': [],
},
encryptedContent: {
'sender_key': 'invalid',
});
await matrix.encryption.keyManager.handleToDeviceEvent(event);
expect(
matrix.encryption.keyManager.getInboundGroupSession(
requestRoom.id, validSessionId, validSenderKey) !=
null,
false);
// no encrypted content
await matrix.encryption.keyManager
.request(requestRoom, validSessionId, validSenderKey);
matrix.encryption.keyManager.clearInboundGroupSessions();
event = ToDeviceEvent(
sender: '@alice:example.com',
type: 'm.forwarded_room_key',
content: {
'algorithm': 'm.megolm.v1.aes-sha2',
'room_id': '!726s6s6q:example.com',
'session_id': validSessionId,
'session_key': sessionKey,
'sender_key': validSenderKey,
'forwarding_curve25519_key_chain': [],
});
await matrix.encryption.keyManager.handleToDeviceEvent(event);
expect(
matrix.encryption.keyManager.getInboundGroupSession(
requestRoom.id, validSessionId, validSenderKey) !=
null,
false);
await matrix.dispose(closeDatabase: true);
});
});
}

View File

@ -0,0 +1,107 @@
/*
* Ansible inventory script used at Famedly GmbH for managing many hosts
* Copyright (C) 2020 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 'package:famedlysdk/famedlysdk.dart';
import 'package:famedlysdk/encryption.dart';
import 'package:test/test.dart';
import 'package:olm/olm.dart' as olm;
import '../fake_client.dart';
void main() {
/// All Tests related to the ChatTime
group('Key Verification', () {
var olmEnabled = true;
try {
olm.init();
olm.Account();
} catch (_) {
olmEnabled = false;
print('[LibOlm] Failed to load LibOlm: ' + _.toString());
}
print('[LibOlm] Enabled: $olmEnabled');
if (!olmEnabled) return;
Client client;
Room room;
var updateCounter = 0;
KeyVerification keyVerification;
test('setupClient', () async {
client = await getClient();
room = Room(id: '!localpart:server.abc', client: client);
keyVerification = KeyVerification(
encryption: client.encryption,
room: room,
userId: '@alice:example.com',
deviceId: 'ABCD',
onUpdate: () => updateCounter++,
);
});
test('acceptSas', () async {
await keyVerification.acceptSas();
});
test('acceptVerification', () async {
await keyVerification.acceptVerification();
});
test('cancel', () async {
await keyVerification.cancel('m.cancelcode');
expect(keyVerification.canceled, true);
expect(keyVerification.canceledCode, 'm.cancelcode');
expect(keyVerification.canceledReason, null);
});
test('handlePayload', () async {
await keyVerification.handlePayload('m.key.verification.request', {
'from_device': 'AliceDevice2',
'methods': ['m.sas.v1'],
'timestamp': 1559598944869,
'transaction_id': 'S0meUniqueAndOpaqueString'
});
await keyVerification.handlePayload('m.key.verification.start', {
'from_device': 'BobDevice1',
'method': 'm.sas.v1',
'transaction_id': 'S0meUniqueAndOpaqueString'
});
await keyVerification.handlePayload('m.key.verification.cancel', {
'code': 'm.user',
'reason': 'User rejected the key verification request',
'transaction_id': 'S0meUniqueAndOpaqueString'
});
});
test('rejectSas', () async {
await keyVerification.rejectSas();
});
test('rejectVerification', () async {
await keyVerification.rejectVerification();
});
test('start', () async {
await keyVerification.start();
});
test('verifyActivity', () async {
final verified = await keyVerification.verifyActivity();
expect(verified, true);
keyVerification?.dispose();
});
test('dispose client', () async {
await client.dispose(closeDatabase: true);
});
});
}

View File

@ -0,0 +1,116 @@
/*
* Ansible inventory script used at Famedly GmbH for managing many hosts
* Copyright (C) 2020 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:convert';
import 'package:famedlysdk/famedlysdk.dart';
import 'package:test/test.dart';
import 'package:olm/olm.dart' as olm;
import '../fake_client.dart';
import '../fake_matrix_api.dart';
void main() {
group('Olm Manager', () {
var olmEnabled = true;
try {
olm.init();
olm.Account();
} catch (_) {
olmEnabled = false;
print('[LibOlm] Failed to load LibOlm: ' + _.toString());
}
print('[LibOlm] Enabled: $olmEnabled');
if (!olmEnabled) return;
Client client;
test('setupClient', () async {
client = await getClient();
});
test('signatures', () async {
final payload = <String, dynamic>{
'fox': 'floof',
};
final signedPayload = client.encryption.olmManager.signJson(payload);
expect(
client.encryption.olmManager.checkJsonSignature(client.fingerprintKey,
signedPayload, client.userID, client.deviceID),
true);
expect(
client.encryption.olmManager.checkJsonSignature(
client.fingerprintKey, payload, client.userID, client.deviceID),
false);
});
test('uploadKeys', () async {
FakeMatrixApi.calledEndpoints.clear();
final res =
await client.encryption.olmManager.uploadKeys(uploadDeviceKeys: true);
expect(res, true);
var sent = json.decode(
FakeMatrixApi.calledEndpoints['/client/r0/keys/upload'].first);
expect(sent['device_keys'] != null, true);
expect(sent['one_time_keys'] != null, true);
expect(sent['one_time_keys'].keys.length, 66);
FakeMatrixApi.calledEndpoints.clear();
await client.encryption.olmManager.uploadKeys();
sent = json.decode(
FakeMatrixApi.calledEndpoints['/client/r0/keys/upload'].first);
expect(sent['device_keys'] != null, false);
FakeMatrixApi.calledEndpoints.clear();
await client.encryption.olmManager.uploadKeys(oldKeyCount: 20);
sent = json.decode(
FakeMatrixApi.calledEndpoints['/client/r0/keys/upload'].first);
expect(sent['one_time_keys'].keys.length, 46);
});
test('handleDeviceOneTimeKeysCount', () async {
FakeMatrixApi.calledEndpoints.clear();
client.encryption.olmManager
.handleDeviceOneTimeKeysCount({'signed_curve25519': 20});
await Future.delayed(Duration(milliseconds: 50));
expect(
FakeMatrixApi.calledEndpoints.containsKey('/client/r0/keys/upload'),
true);
FakeMatrixApi.calledEndpoints.clear();
client.encryption.olmManager
.handleDeviceOneTimeKeysCount({'signed_curve25519': 70});
await Future.delayed(Duration(milliseconds: 50));
expect(
FakeMatrixApi.calledEndpoints.containsKey('/client/r0/keys/upload'),
false);
});
test('startOutgoingOlmSessions', () async {
// start an olm session.....with ourself!
await client.encryption.olmManager.startOutgoingOlmSessions(
[client.userDeviceKeys[client.userID].deviceKeys[client.deviceID]]);
expect(
client.encryption.olmManager.olmSessions
.containsKey(client.identityKey),
true);
});
test('dispose client', () async {
await client.dispose(closeDatabase: true);
});
});
}

View File

@ -1,33 +1,31 @@
/*
* Copyright (c) 2019 Zender & Kurtz GbR.
* Ansible inventory script used at Famedly GmbH for managing many hosts
* Copyright (C) 2019, 2020 Famedly GmbH
*
* Authors:
* Christian Pauly <krille@famedly.com>
* Marcel Radzio <mtrnord@famedly.com>
* 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 file is part of famedlysdk.
* 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.
*
* 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 <http://www.gnu.org/licenses/>.
* 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:convert';
import 'package:famedlysdk/famedlysdk.dart';
import 'package:famedlysdk/matrix_api.dart';
import 'package:famedlysdk/encryption.dart';
import 'package:famedlysdk/src/event.dart';
import 'package:test/test.dart';
import 'fake_matrix_api.dart';
import 'fake_matrix_localizations.dart';
void main() {
/// All Tests related to the Event
@ -52,9 +50,11 @@ void main() {
'status': 2,
'content': contentJson,
};
var client = Client('testclient', debug: true, httpClient: FakeMatrixApi());
var event = Event.fromJson(
jsonObj, Room(id: '!localpart:server.abc', client: client));
test('Create from json', () async {
var event = Event.fromJson(jsonObj, null);
jsonObj.remove('status');
jsonObj['content'] = json.decode(contentJson);
expect(event.toJson(), jsonObj);
@ -124,6 +124,7 @@ void main() {
jsonObj['type'] = 'm.room.message';
jsonObj['content'] = json.decode(jsonObj['content']);
jsonObj['content'].remove('m.relates_to');
jsonObj['content']['msgtype'] = 'm.notice';
event = Event.fromJson(jsonObj, null);
expect(event.messageType, MessageTypes.Notice);
@ -163,25 +164,38 @@ void main() {
});
test('redact', () async {
final room = Room(id: '1234', client: Client('testclient', debug: true));
final redactionEventJson = {
'content': {'reason': 'Spamming'},
'event_id': '143273582443PhrSn:example.org',
'origin_server_ts': 1432735824653,
'redacts': id,
'room_id': '1234',
'sender': '@example:example.org',
'type': 'm.room.redaction',
'unsigned': {'age': 1234}
};
var redactedBecause = Event.fromJson(redactionEventJson, room);
var event = Event.fromJson(jsonObj, room);
event.setRedactionEvent(redactedBecause);
expect(event.redacted, true);
expect(event.redactedBecause.toJson(), redactedBecause.toJson());
expect(event.content.isEmpty, true);
redactionEventJson.remove('redacts');
expect(event.unsigned['redacted_because'], redactionEventJson);
final redactJsonObj = Map<String, dynamic>.from(jsonObj);
final testTypes = [
EventTypes.RoomMember,
EventTypes.RoomCreate,
EventTypes.RoomJoinRules,
EventTypes.RoomPowerLevels,
EventTypes.RoomAliases,
EventTypes.HistoryVisibility,
];
for (final testType in testTypes) {
redactJsonObj['type'] = testType;
final room =
Room(id: '1234', client: Client('testclient', debug: true));
final redactionEventJson = {
'content': {'reason': 'Spamming'},
'event_id': '143273582443PhrSn:example.org',
'origin_server_ts': 1432735824653,
'redacts': id,
'room_id': '1234',
'sender': '@example:example.org',
'type': 'm.room.redaction',
'unsigned': {'age': 1234}
};
var redactedBecause = Event.fromJson(redactionEventJson, room);
var event = Event.fromJson(redactJsonObj, room);
event.setRedactionEvent(redactedBecause);
expect(event.redacted, true);
expect(event.redactedBecause.toJson(), redactedBecause.toJson());
expect(event.content.isEmpty, true);
redactionEventJson.remove('redacts');
expect(event.unsigned['redacted_because'], redactionEventJson);
}
});
test('remove', () async {
@ -195,8 +209,8 @@ void main() {
});
test('sendAgain', () async {
var matrix = Client('testclient', debug: true);
matrix.httpClient = FakeMatrixApi();
var matrix =
Client('testclient', debug: true, httpClient: FakeMatrixApi());
await matrix.checkServer('https://fakeServer.notExisting');
await matrix.login('test', '1234');
@ -206,14 +220,14 @@ void main() {
event.status = -1;
final resp2 = await event.sendAgain(txid: '1234');
expect(resp1, null);
expect(resp2, '42');
expect(resp2.startsWith('\$event'), true);
await matrix.dispose(closeDatabase: true);
});
test('requestKey', () async {
var matrix = Client('testclient', debug: true);
matrix.httpClient = FakeMatrixApi();
var matrix =
Client('testclient', debug: true, httpClient: FakeMatrixApi());
await matrix.checkServer('https://fakeServer.notExisting');
await matrix.login('test', '1234');
@ -227,7 +241,7 @@ void main() {
}
expect(exception, 'Session key not unknown');
event = Event.fromJson({
var event2 = Event.fromJson({
'event_id': id,
'sender': senderID,
'origin_server_ts': timestamp,
@ -245,9 +259,535 @@ void main() {
}),
}, Room(id: '!1234:example.com', client: matrix));
await event.requestKey();
await event2.requestKey();
await matrix.dispose(closeDatabase: true);
});
test('requestKey', () async {
jsonObj['state_key'] = '@alice:example.com';
var event = Event.fromJson(
jsonObj, Room(id: '!localpart:server.abc', client: client));
expect(event.stateKeyUser.id, '@alice:example.com');
});
test('canRedact', () async {
expect(event.canRedact, true);
});
test('getLocalizedBody', () async {
final matrix =
Client('testclient', debug: true, httpClient: FakeMatrixApi());
final room = Room(id: '!1234:example.com', client: matrix);
var event = Event.fromJson({
'content': {
'avatar_url': 'mxc://example.org/SEsfnsuifSDFSSEF',
'displayname': 'Alice Margatroid',
'membership': 'join'
},
'event_id': '\$143273582443PhrSn:example.org',
'origin_server_ts': 1432735824653,
'room_id': '!jEsUZKDJdhlrceRyVU:example.org',
'sender': '@example:example.org',
'state_key': '@alice:example.org',
'type': 'm.room.member',
'unsigned': {
'age': 1234,
'redacted_because': {
'content': {'reason': 'Spamming'},
'event_id': '\$143273582443PhrSn:example.org',
'origin_server_ts': 1432735824653,
'redacts': '\$143273582443PhrSn:example.org',
'room_id': '!jEsUZKDJdhlrceRyVU:example.org',
'sender': '@example:example.org',
'type': 'm.room.redaction',
'unsigned': {'age': 1234}
}
}
}, room);
expect(event.getLocalizedBody(FakeMatrixLocalizations()), null);
event = Event.fromJson({
'content': {
'body': 'Landing',
'info': {
'h': 200,
'mimetype': 'image/png',
'size': 73602,
'thumbnail_info': {
'h': 200,
'mimetype': 'image/png',
'size': 73602,
'w': 140
},
'thumbnail_url': 'mxc://matrix.org/sHhqkFCvSkFwtmvtETOtKnLP',
'w': 140
},
'url': 'mxc://matrix.org/sHhqkFCvSkFwtmvtETOtKnLP'
},
'event_id': '\$143273582443PhrSn:example.org',
'origin_server_ts': 1432735824653,
'room_id': '!jEsUZKDJdhlrceRyVU:example.org',
'sender': '@example:example.org',
'type': 'm.sticker',
'unsigned': {'age': 1234}
}, room);
expect(event.getLocalizedBody(FakeMatrixLocalizations()), null);
event = Event.fromJson({
'content': {'reason': 'Spamming'},
'event_id': '\$143273582443PhrSn:example.org',
'origin_server_ts': 1432735824653,
'redacts': '\$143273582443PhrSn:example.org',
'room_id': '!jEsUZKDJdhlrceRyVU:example.org',
'sender': '@example:example.org',
'type': 'm.room.redaction',
'unsigned': {'age': 1234}
}, room);
expect(event.getLocalizedBody(FakeMatrixLocalizations()), null);
event = Event.fromJson({
'content': {
'aliases': ['#somewhere:example.org', '#another:example.org']
},
'event_id': '\$143273582443PhrSn:example.org',
'origin_server_ts': 1432735824653,
'room_id': '!jEsUZKDJdhlrceRyVU:example.org',
'sender': '@example:example.org',
'state_key': 'example.org',
'type': 'm.room.aliases',
'unsigned': {'age': 1234}
}, room);
expect(event.getLocalizedBody(FakeMatrixLocalizations()), null);
event = Event.fromJson({
'content': {
'aliases': ['#somewhere:example.org', '#another:example.org']
},
'event_id': '\$143273582443PhrSn:example.org',
'origin_server_ts': 1432735824653,
'room_id': '!jEsUZKDJdhlrceRyVU:example.org',
'sender': '@example:example.org',
'state_key': 'example.org',
'type': 'm.room.aliases',
'unsigned': {'age': 1234}
}, room);
expect(event.getLocalizedBody(FakeMatrixLocalizations()), null);
event = Event.fromJson({
'content': {'alias': '#somewhere:localhost'},
'event_id': '\$143273582443PhrSn:example.org',
'origin_server_ts': 1432735824653,
'room_id': '!jEsUZKDJdhlrceRyVU:example.org',
'sender': '@example:example.org',
'state_key': '',
'type': 'm.room.canonical_alias',
'unsigned': {'age': 1234}
}, room);
expect(event.getLocalizedBody(FakeMatrixLocalizations()), null);
event = Event.fromJson({
'content': {
'creator': '@example:example.org',
'm.federate': true,
'predecessor': {
'event_id': '\$something:example.org',
'room_id': '!oldroom:example.org'
},
'room_version': '1'
},
'event_id': '\$143273582443PhrSn:example.org',
'origin_server_ts': 1432735824653,
'room_id': '!jEsUZKDJdhlrceRyVU:example.org',
'sender': '@example:example.org',
'state_key': '',
'type': 'm.room.create',
'unsigned': {'age': 1234}
}, room);
expect(event.getLocalizedBody(FakeMatrixLocalizations()), null);
event = Event.fromJson({
'content': {
'body': 'This room has been replaced',
'replacement_room': '!newroom:example.org'
},
'event_id': '\$143273582443PhrSn:example.org',
'origin_server_ts': 1432735824653,
'room_id': '!jEsUZKDJdhlrceRyVU:example.org',
'sender': '@example:example.org',
'state_key': '',
'type': 'm.room.tombstone',
'unsigned': {'age': 1234}
}, room);
expect(event.getLocalizedBody(FakeMatrixLocalizations()), null);
event = Event.fromJson({
'content': {'join_rule': 'public'},
'event_id': '\$143273582443PhrSn:example.org',
'origin_server_ts': 1432735824653,
'room_id': '!jEsUZKDJdhlrceRyVU:example.org',
'sender': '@example:example.org',
'state_key': '',
'type': 'm.room.join_rules',
'unsigned': {'age': 1234}
}, room);
expect(event.getLocalizedBody(FakeMatrixLocalizations()), null);
event = Event.fromJson({
'content': {
'avatar_url': 'mxc://example.org/SEsfnsuifSDFSSEF',
'displayname': 'Alice Margatroid',
'membership': 'join'
},
'event_id': '\$143273582443PhrSn:example.org',
'origin_server_ts': 1432735824653,
'room_id': '!jEsUZKDJdhlrceRyVU:example.org',
'sender': '@example:example.org',
'state_key': '@alice:example.org',
'type': 'm.room.member',
'unsigned': {'age': 1234}
}, room);
expect(event.getLocalizedBody(FakeMatrixLocalizations()), null);
event = Event.fromJson({
'content': {'membership': 'invite'},
'event_id': '\$143273582443PhrSn:example.org',
'origin_server_ts': 1432735824653,
'room_id': '!jEsUZKDJdhlrceRyVU:example.org',
'sender': '@example:example.org',
'state_key': '@alice:example.org',
'type': 'm.room.member'
}, room);
expect(event.getLocalizedBody(FakeMatrixLocalizations()), null);
event = Event.fromJson({
'content': {'membership': 'leave'},
'event_id': '\$143273582443PhrSn:example.org',
'origin_server_ts': 1432735824653,
'room_id': '!jEsUZKDJdhlrceRyVU:example.org',
'sender': '@example:example.org',
'state_key': '@alice:example.org',
'type': 'm.room.member',
'unsigned': {
'prev_content': {'membership': 'join'},
}
}, room);
expect(event.getLocalizedBody(FakeMatrixLocalizations()), null);
event = Event.fromJson({
'content': {'membership': 'ban'},
'event_id': '\$143273582443PhrSn:example.org',
'origin_server_ts': 1432735824653,
'room_id': '!jEsUZKDJdhlrceRyVU:example.org',
'sender': '@example:example.org',
'state_key': '@alice:example.org',
'type': 'm.room.member',
'unsigned': {
'prev_content': {'membership': 'join'},
}
}, room);
expect(event.getLocalizedBody(FakeMatrixLocalizations()), null);
event = Event.fromJson({
'content': {'membership': 'join'},
'event_id': '\$143273582443PhrSn:example.org',
'origin_server_ts': 1432735824653,
'room_id': '!jEsUZKDJdhlrceRyVU:example.org',
'sender': '@example:example.org',
'state_key': '@alice:example.org',
'type': 'm.room.member',
'unsigned': {
'prev_content': {'membership': 'invite'},
}
}, room);
expect(event.getLocalizedBody(FakeMatrixLocalizations()), null);
event = Event.fromJson({
'content': {'membership': 'invite'},
'event_id': '\$143273582443PhrSn:example.org',
'origin_server_ts': 1432735824653,
'room_id': '!jEsUZKDJdhlrceRyVU:example.org',
'sender': '@example:example.org',
'state_key': '@alice:example.org',
'type': 'm.room.member',
'unsigned': {
'prev_content': {'membership': 'join'},
}
}, room);
expect(event.getLocalizedBody(FakeMatrixLocalizations()), null);
event = Event.fromJson({
'content': {'membership': 'leave'},
'event_id': '\$143273582443PhrSn:example.org',
'origin_server_ts': 1432735824653,
'room_id': '!jEsUZKDJdhlrceRyVU:example.org',
'sender': '@example:example.org',
'state_key': '@alice:example.org',
'type': 'm.room.member',
'unsigned': {
'prev_content': {'membership': 'invite'},
}
}, room);
expect(event.getLocalizedBody(FakeMatrixLocalizations()), null);
event = Event.fromJson({
'content': {'membership': 'leave'},
'event_id': '\$143273582443PhrSn:example.org',
'origin_server_ts': 1432735824653,
'room_id': '!jEsUZKDJdhlrceRyVU:example.org',
'sender': '@alice:example.org',
'state_key': '@alice:example.org',
'type': 'm.room.member',
'unsigned': {
'prev_content': {'membership': 'invite'},
}
}, room);
expect(event.getLocalizedBody(FakeMatrixLocalizations()), null);
event = Event.fromJson({
'content': {
'ban': 50,
'events': {'m.room.name': 100, 'm.room.power_levels': 100},
'events_default': 0,
'invite': 50,
'kick': 50,
'notifications': {'room': 20},
'redact': 50,
'state_default': 50,
'users': {'@example:localhost': 100},
'users_default': 0
},
'event_id': '\$143273582443PhrSn:example.org',
'origin_server_ts': 1432735824653,
'room_id': '!jEsUZKDJdhlrceRyVU:example.org',
'sender': '@example:example.org',
'state_key': '',
'type': 'm.room.power_levels',
'unsigned': {'age': 1234}
}, room);
expect(event.getLocalizedBody(FakeMatrixLocalizations()), null);
event = Event.fromJson({
'content': {'name': 'The room name'},
'event_id': '\$143273582443PhrSn:example.org',
'origin_server_ts': 1432735824653,
'room_id': '!jEsUZKDJdhlrceRyVU:example.org',
'sender': '@example:example.org',
'state_key': '',
'type': 'm.room.name',
'unsigned': {'age': 1234}
}, room);
expect(event.getLocalizedBody(FakeMatrixLocalizations()), null);
event = Event.fromJson({
'content': {'topic': 'A room topic'},
'event_id': '\$143273582443PhrSn:example.org',
'origin_server_ts': 1432735824653,
'room_id': '!jEsUZKDJdhlrceRyVU:example.org',
'sender': '@example:example.org',
'state_key': '',
'type': 'm.room.topic',
'unsigned': {'age': 1234}
}, room);
expect(event.getLocalizedBody(FakeMatrixLocalizations()), null);
event = Event.fromJson({
'content': {
'info': {'h': 398, 'mimetype': 'image/jpeg', 'size': 31037, 'w': 394},
'url': 'mxc://example.org/JWEIFJgwEIhweiWJE'
},
'event_id': '\$143273582443PhrSn:example.org',
'origin_server_ts': 1432735824653,
'room_id': '!jEsUZKDJdhlrceRyVU:example.org',
'sender': '@example:example.org',
'state_key': '',
'type': 'm.room.avatar',
'unsigned': {'age': 1234}
}, room);
expect(event.getLocalizedBody(FakeMatrixLocalizations()), null);
event = Event.fromJson({
'content': {'history_visibility': 'shared'},
'event_id': '\$143273582443PhrSn:example.org',
'origin_server_ts': 1432735824653,
'room_id': '!jEsUZKDJdhlrceRyVU:example.org',
'sender': '@example:example.org',
'state_key': '',
'type': 'm.room.history_visibility',
'unsigned': {'age': 1234}
}, room);
expect(event.getLocalizedBody(FakeMatrixLocalizations()), null);
event = Event.fromJson({
'content': {
'algorithm': 'm.megolm.v1.aes-sha2',
'rotation_period_ms': 604800000,
'rotation_period_msgs': 100
},
'event_id': '\$143273582443PhrSn:example.org',
'origin_server_ts': 1432735824653,
'room_id': '!jEsUZKDJdhlrceRyVU:example.org',
'sender': '@example:example.org',
'state_key': '',
'type': 'm.room.encryption',
'unsigned': {'age': 1234}
}, room);
expect(event.getLocalizedBody(FakeMatrixLocalizations()),
'Example activatedEndToEndEncryption. needPantalaimonWarning');
event = Event.fromJson({
'content': {
'body': 'This is an example text message',
'format': 'org.matrix.custom.html',
'formatted_body': '<b>This is an example text message</b>',
'msgtype': 'm.text'
},
'event_id': '\$143273582443PhrSn:example.org',
'origin_server_ts': 1432735824653,
'room_id': '!jEsUZKDJdhlrceRyVU:example.org',
'sender': '@example:example.org',
'type': 'm.room.message',
'unsigned': {'age': 1234}
}, room);
expect(event.getLocalizedBody(FakeMatrixLocalizations()),
'This is an example text message');
event = Event.fromJson({
'content': {
'body': 'thinks this is an example emote',
'format': 'org.matrix.custom.html',
'formatted_body': 'thinks <b>this</b> is an example emote',
'msgtype': 'm.emote'
},
'event_id': '\$143273582443PhrSn:example.org',
'origin_server_ts': 1432735824653,
'room_id': '!jEsUZKDJdhlrceRyVU:example.org',
'sender': '@example:example.org',
'type': 'm.room.message',
'unsigned': {'age': 1234}
}, room);
expect(event.getLocalizedBody(FakeMatrixLocalizations()),
'* thinks this is an example emote');
event = Event.fromJson({
'content': {
'body': 'This is an example notice',
'format': 'org.matrix.custom.html',
'formatted_body': 'This is an <strong>example</strong> notice',
'msgtype': 'm.notice'
},
'event_id': '\$143273582443PhrSn:example.org',
'origin_server_ts': 1432735824653,
'room_id': '!jEsUZKDJdhlrceRyVU:example.org',
'sender': '@example:example.org',
'type': 'm.room.message',
'unsigned': {'age': 1234}
}, room);
expect(event.getLocalizedBody(FakeMatrixLocalizations()),
'This is an example notice');
event = Event.fromJson({
'content': {
'body': 'filename.jpg',
'info': {'h': 398, 'mimetype': 'image/jpeg', 'size': 31037, 'w': 394},
'msgtype': 'm.image',
'url': 'mxc://example.org/JWEIFJgwEIhweiWJE'
},
'event_id': '\$143273582443PhrSn:example.org',
'origin_server_ts': 1432735824653,
'room_id': '!jEsUZKDJdhlrceRyVU:example.org',
'sender': '@example:example.org',
'type': 'm.room.message',
'unsigned': {'age': 1234}
}, room);
expect(event.getLocalizedBody(FakeMatrixLocalizations()), null);
event = Event.fromJson({
'content': {
'body': 'something-important.doc',
'filename': 'something-important.doc',
'info': {'mimetype': 'application/msword', 'size': 46144},
'msgtype': 'm.file',
'url': 'mxc://example.org/FHyPlCeYUSFFxlgbQYZmoEoe'
},
'event_id': '\$143273582443PhrSn:example.org',
'origin_server_ts': 1432735824653,
'room_id': '!jEsUZKDJdhlrceRyVU:example.org',
'sender': '@example:example.org',
'type': 'm.room.message',
'unsigned': {'age': 1234}
}, room);
expect(event.getLocalizedBody(FakeMatrixLocalizations()), null);
event = Event.fromJson({
'content': {
'body': 'Bee Gees - Stayin Alive',
'info': {
'duration': 2140786,
'mimetype': 'audio/mpeg',
'size': 1563685
},
'msgtype': 'm.audio',
'url': 'mxc://example.org/ffed755USFFxlgbQYZGtryd'
},
'event_id': '\$143273582443PhrSn:example.org',
'origin_server_ts': 1432735824653,
'room_id': '!jEsUZKDJdhlrceRyVU:example.org',
'sender': '@example:example.org',
'type': 'm.room.message',
'unsigned': {'age': 1234}
}, room);
expect(event.getLocalizedBody(FakeMatrixLocalizations()), null);
event = Event.fromJson({
'content': {
'body': 'Big Ben, London, UK',
'geo_uri': 'geo:51.5008,0.1247',
'info': {
'thumbnail_info': {
'h': 300,
'mimetype': 'image/jpeg',
'size': 46144,
'w': 300
},
'thumbnail_url': 'mxc://example.org/FHyPlCeYUSFFxlgbQYZmoEoe'
},
'msgtype': 'm.location'
},
'event_id': '\$143273582443PhrSn:example.org',
'origin_server_ts': 1432735824653,
'room_id': '!jEsUZKDJdhlrceRyVU:example.org',
'sender': '@example:example.org',
'type': 'm.room.message',
'unsigned': {'age': 1234}
}, room);
expect(event.getLocalizedBody(FakeMatrixLocalizations()), null);
event = Event.fromJson({
'content': {
'body': 'Gangnam Style',
'info': {
'duration': 2140786,
'h': 320,
'mimetype': 'video/mp4',
'size': 1563685,
'thumbnail_info': {
'h': 300,
'mimetype': 'image/jpeg',
'size': 46144,
'w': 300
},
'thumbnail_url': 'mxc://example.org/FHyPlCeYUSFFxlgbQYZmoEoe',
'w': 480
},
'msgtype': 'm.video',
'url': 'mxc://example.org/a526eYUSFFxlgbQYZmo442'
},
'event_id': '\$143273582443PhrSn:example.org',
'origin_server_ts': 1432735824653,
'room_id': '!jEsUZKDJdhlrceRyVU:example.org',
'sender': '@example:example.org',
'type': 'm.room.message',
'unsigned': {'age': 1234}
}, room);
expect(event.getLocalizedBody(FakeMatrixLocalizations()), null);
});
});
}

48
test/fake_client.dart Normal file
View File

@ -0,0 +1,48 @@
/*
* Ansible inventory script used at Famedly GmbH for managing many hosts
* Copyright (C) 2020 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 'package:famedlysdk/famedlysdk.dart';
import 'fake_matrix_api.dart';
import 'fake_database.dart';
// key @test:fakeServer.notExisting
const pickledOlmAccount =
'N2v1MkIFGcl0mQpo2OCwSopxPQJ0wnl7oe7PKiT4141AijfdTIhRu+ceXzXKy3Kr00nLqXtRv7kid6hU4a+V0rfJWLL0Y51+3Rp/ORDVnQy+SSeo6Fn4FHcXrxifJEJ0djla5u98fBcJ8BSkhIDmtXRPi5/oJAvpiYn+8zMjFHobOeZUAxYR0VfQ9JzSYBsSovoQ7uFkNks1M4EDUvHtuyg3RxViwdNxs3718fyAqQ/VSwbXsY0Nl+qQbF+nlVGHenGqk5SuNl1P6e1PzZxcR0IfXA94Xij1Ob5gDv5YH4UCn9wRMG0abZsQP0YzpDM0FLaHSCyo9i5JD/vMlhH+nZWrgAzPPCTNGYewNV8/h3c+VyJh8ZTx/fVi6Yq46Fv+27Ga2ETRZ3Qn+Oyx6dLBjnBZ9iUvIhqpe2XqaGA1PopOz8iDnaZitw';
Future<Client> getClient() async {
final client = Client('testclient', debug: true, httpClient: FakeMatrixApi());
client.database = getDatabase();
await client.checkServer('https://fakeServer.notExisting');
final resp = await client.api.login(
type: 'm.login.password',
user: 'test',
password: '1234',
initialDeviceDisplayName: 'Fluffy Matrix Client',
);
client.connect(
newToken: resp.accessToken,
newUserID: resp.userId,
newHomeserver: client.api.homeserver,
newDeviceName: 'Text Matrix Client',
newDeviceID: resp.deviceId,
newOlmAccount: pickledOlmAccount,
);
await Future.delayed(Duration(milliseconds: 10));
return client;
}

View File

@ -1,6 +1,26 @@
/*
* Ansible inventory script used at Famedly GmbH for managing many hosts
* Copyright (C) 2019, 2020 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 'package:famedlysdk/famedlysdk.dart';
import 'package:moor/moor.dart';
import 'package:moor_ffi/moor_ffi.dart' as moor;
Database getDatabase() {
moorRuntimeOptions.dontWarnAboutMultipleDatabases = true;
return Database(moor.VmDatabase.memory());
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,309 @@
/*
* Ansible inventory script used at Famedly GmbH for managing many hosts
* Copyright (C) 2019, 2020 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 'package:famedlysdk/famedlysdk.dart';
class FakeMatrixLocalizations extends MatrixLocalizations {
@override
String acceptedTheInvitation(String targetName) {
// TODO: implement acceptedTheInvitation
return null;
}
@override
String activatedEndToEndEncryption(String senderName) {
// TODO: implement activatedEndToEndEncryption
return '$senderName activatedEndToEndEncryption';
}
@override
// TODO: implement anyoneCanJoin
String get anyoneCanJoin => null;
@override
String bannedUser(String senderName, String targetName) {
// TODO: implement bannedUser
return null;
}
@override
String changedTheChatAvatar(String senderName) {
// TODO: implement changedTheChatAvatar
return null;
}
@override
String changedTheChatDescriptionTo(String senderName, String content) {
// TODO: implement changedTheChatDescriptionTo
return null;
}
@override
String changedTheChatNameTo(String senderName, String content) {
// TODO: implement changedTheChatNameTo
return null;
}
@override
String changedTheChatPermissions(String senderName) {
// TODO: implement changedTheChatPermissions
return null;
}
@override
String changedTheDisplaynameTo(String targetName, String newDisplayname) {
// TODO: implement changedTheDisplaynameTo
return null;
}
@override
String changedTheGuestAccessRules(String senderName) {
// TODO: implement changedTheGuestAccessRules
return null;
}
@override
String changedTheGuestAccessRulesTo(
String senderName, String localizedString) {
// TODO: implement changedTheGuestAccessRulesTo
return null;
}
@override
String changedTheHistoryVisibility(String senderName) {
// TODO: implement changedTheHistoryVisibility
return null;
}
@override
String changedTheHistoryVisibilityTo(
String senderName, String localizedString) {
// TODO: implement changedTheHistoryVisibilityTo
return null;
}
@override
String changedTheJoinRules(String senderName) {
// TODO: implement changedTheJoinRules
return null;
}
@override
String changedTheJoinRulesTo(String senderName, String localizedString) {
// TODO: implement changedTheJoinRulesTo
return null;
}
@override
String changedTheProfileAvatar(String targetName) {
// TODO: implement changedTheProfileAvatar
return null;
}
@override
String changedTheRoomAliases(String senderName) {
// TODO: implement changedTheRoomAliases
return null;
}
@override
String changedTheRoomInvitationLink(String senderName) {
// TODO: implement changedTheRoomInvitationLink
return null;
}
@override
// TODO: implement channelCorruptedDecryptError
String get channelCorruptedDecryptError => null;
@override
String couldNotDecryptMessage(String errorText) {
// TODO: implement couldNotDecryptMessage
return null;
}
@override
String createdTheChat(String senderName) {
// TODO: implement createdTheChat
return null;
}
@override
// TODO: implement emptyChat
String get emptyChat => null;
@override
// TODO: implement encryptionNotEnabled
String get encryptionNotEnabled => null;
@override
// TODO: implement fromJoining
String get fromJoining => null;
@override
// TODO: implement fromTheInvitation
String get fromTheInvitation => null;
@override
String groupWith(String displayname) {
// TODO: implement groupWith
return null;
}
@override
// TODO: implement guestsAreForbidden
String get guestsAreForbidden => null;
@override
// TODO: implement guestsCanJoin
String get guestsCanJoin => null;
@override
String hasWithdrawnTheInvitationFor(String senderName, String targetName) {
// TODO: implement hasWithdrawnTheInvitationFor
return null;
}
@override
String invitedUser(String senderName, String targetName) {
// TODO: implement invitedUser
return null;
}
@override
// TODO: implement invitedUsersOnly
String get invitedUsersOnly => null;
@override
String joinedTheChat(String targetName) {
// TODO: implement joinedTheChat
return null;
}
@override
String kicked(String senderName, String targetName) {
// TODO: implement kicked
return null;
}
@override
String kickedAndBanned(String senderName, String targetName) {
// TODO: implement kickedAndBanned
return null;
}
@override
// TODO: implement needPantalaimonWarning
String get needPantalaimonWarning => 'needPantalaimonWarning';
@override
// TODO: implement noPermission
String get noPermission => 'noPermission';
@override
String redactedAnEvent(String senderName) {
// TODO: implement redactedAnEvent
return null;
}
@override
String rejectedTheInvitation(String targetName) {
// TODO: implement rejectedTheInvitation
return null;
}
@override
String removedBy(String calcDisplayname) {
// TODO: implement removedBy
return null;
}
@override
// TODO: implement roomHasBeenUpgraded
String get roomHasBeenUpgraded => null;
@override
String sentAFile(String senderName) {
// TODO: implement sentAFile
return null;
}
@override
String sentAPicture(String senderName) {
// TODO: implement sentAPicture
return null;
}
@override
String sentASticker(String senderName) {
// TODO: implement sentASticker
return null;
}
@override
String sentAVideo(String senderName) {
// TODO: implement sentAVideo
return null;
}
@override
String sentAnAudio(String senderName) {
// TODO: implement sentAnAudio
return null;
}
@override
String sharedTheLocation(String senderName) {
// TODO: implement sharedTheLocation
return null;
}
@override
String unbannedUser(String senderName, String targetName) {
// TODO: implement unbannedUser
return null;
}
@override
// TODO: implement unknownEncryptionAlgorithm
String get unknownEncryptionAlgorithm => null;
@override
String unknownEvent(String typeKey) {
// TODO: implement unknownEvent
return null;
}
@override
String userLeftTheChat(String targetName) {
// TODO: implement userLeftTheChat
return null;
}
@override
// TODO: implement visibleForAllParticipants
String get visibleForAllParticipants => null;
@override
// TODO: implement visibleForEveryone
String get visibleForEveryone => null;
@override
// TODO: implement you
String get you => null;
}

View File

@ -1,3 +1,21 @@
/*
* Ansible inventory script used at Famedly GmbH for managing many hosts
* Copyright (C) 2019, 2020 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 'package:famedlysdk/src/utils/markdown.dart';
import 'package:test/test.dart';

1518
test/matrix_api_test.dart Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,24 +1,19 @@
/*
* Copyright (c) 2019 Zender & Kurtz GbR.
* Ansible inventory script used at Famedly GmbH for managing many hosts
* Copyright (C) 2019, 2020 Famedly GmbH
*
* Authors:
* Christian Pauly <krille@famedly.com>
* Marcel Radzio <mtrnord@famedly.com>
* 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 file is part of famedlysdk.
* 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.
*
* 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 <http://www.gnu.org/licenses/>.
* 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 'package:famedlysdk/famedlysdk.dart';

Some files were not shown because too many files have changed in this diff Show More