Show encryption errors in qml and add request keys button

This commit is contained in:
Nicolas Werner 2021-08-07 22:51:09 +02:00
parent 9f742fe23d
commit 72bbad7485
No known key found for this signature in database
GPG Key ID: C8D75E610773F2D9
14 changed files with 220 additions and 165 deletions

View File

@ -541,32 +541,33 @@ qt5_wrap_cpp(MOC_HEADERS
src/AvatarProvider.h src/AvatarProvider.h
src/BlurhashProvider.h src/BlurhashProvider.h
src/Cache_p.h
src/CacheCryptoStructs.h src/CacheCryptoStructs.h
src/Cache_p.h
src/CallDevices.h src/CallDevices.h
src/CallManager.h src/CallManager.h
src/ChatPage.h src/ChatPage.h
src/Clipboard.h src/Clipboard.h
src/CombinedImagePackModel.h
src/CompletionProxyModel.h src/CompletionProxyModel.h
src/DeviceVerificationFlow.h src/DeviceVerificationFlow.h
src/ImagePackListModel.h
src/InviteesModel.h src/InviteesModel.h
src/LoginPage.h src/LoginPage.h
src/MainWindow.h src/MainWindow.h
src/MemberList.h src/MemberList.h
src/MxcImageProvider.h src/MxcImageProvider.h
src/ReadReceiptsModel.h src/Olm.h
src/RegisterPage.h src/RegisterPage.h
src/RoomsModel.h
src/SSOHandler.h src/SSOHandler.h
src/CombinedImagePackModel.h
src/SingleImagePackModel.h src/SingleImagePackModel.h
src/ImagePackListModel.h
src/TrayIcon.h src/TrayIcon.h
src/UserSettingsPage.h src/UserSettingsPage.h
src/UsersModel.h src/UsersModel.h
src/RoomsModel.h
src/WebRTCSession.h src/WebRTCSession.h
src/WelcomePage.h src/WelcomePage.h
) src/ReadReceiptsModel.h
)
# #
# Bundle translations. # Bundle translations.

View File

@ -349,6 +349,7 @@ ScrollView {
required property string callType required property string callType
required property var reactions required property var reactions
required property int trustlevel required property int trustlevel
required property int encryptionError
required property var timestamp required property var timestamp
required property int status required property int status
required property int index required property int index
@ -456,6 +457,7 @@ ScrollView {
callType: wrapper.callType callType: wrapper.callType
reactions: wrapper.reactions reactions: wrapper.reactions
trustlevel: wrapper.trustlevel trustlevel: wrapper.trustlevel
encryptionError: wrapper.encryptionError
timestamp: wrapper.timestamp timestamp: wrapper.timestamp
status: wrapper.status status: wrapper.status
relatedEventCacheBuster: wrapper.relatedEventCacheBuster relatedEventCacheBuster: wrapper.relatedEventCacheBuster

View File

@ -38,6 +38,7 @@ Item {
required property string callType required property string callType
required property var reactions required property var reactions
required property int trustlevel required property int trustlevel
required property int encryptionError
required property var timestamp required property var timestamp
required property int status required property int status
required property int relatedEventCacheBuster required property int relatedEventCacheBuster
@ -110,6 +111,7 @@ Item {
roomTopic: r.relatedEventCacheBuster, fromModel(Room.RoomTopic) ?? "" roomTopic: r.relatedEventCacheBuster, fromModel(Room.RoomTopic) ?? ""
roomName: r.relatedEventCacheBuster, fromModel(Room.RoomName) ?? "" roomName: r.relatedEventCacheBuster, fromModel(Room.RoomName) ?? ""
callType: r.relatedEventCacheBuster, fromModel(Room.CallType) ?? "" callType: r.relatedEventCacheBuster, fromModel(Room.CallType) ?? ""
encryptionError: r.relatedEventCacheBuster, fromModel(Room.EncryptionError) ?? ""
relatedEventCacheBuster: r.relatedEventCacheBuster, fromModel(Room.RelatedEventCacheBuster) ?? 0 relatedEventCacheBuster: r.relatedEventCacheBuster, fromModel(Room.RelatedEventCacheBuster) ?? 0
} }
@ -136,6 +138,7 @@ Item {
roomTopic: r.roomTopic roomTopic: r.roomTopic
roomName: r.roomName roomName: r.roomName
callType: r.callType callType: r.callType
encryptionError: r.encryptionError
relatedEventCacheBuster: r.relatedEventCacheBuster relatedEventCacheBuster: r.relatedEventCacheBuster
isReply: false isReply: false
} }

View File

@ -0,0 +1,48 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later
import ".."
import QtQuick.Controls 2.1
import QtQuick.Layouts 1.2
import im.nheko 1.0
ColumnLayout {
id: r
required property int encryptionError
required property string eventId
width: parent ? parent.width : undefined
MatrixText {
text: {
switch (encryptionError) {
case Olm.MissingSession:
return qsTr("There is no key to unlock this message. We requested the key automatically, but you can try requesting it again if you are impatient.");
case Olm.MissingSessionIndex:
return qsTr("This message couldn't be decrypted, because we only have a key for newer messages. You can try requesting access to this message.");
case Olm.DbError:
return qsTr("There was an internal error reading the decryption key from the database.");
case Olm.DecryptionFailed:
return qsTr("There was an error decrypting this message.");
case Olm.ParsingFailed:
return qsTr("The message couldn't be parsed.");
case Olm.ReplayAttack:
return qsTr("The encryption key was reused! Someone is possibly trying to insert false messages into this chat!");
default:
return qsTr("Unknown decryption error");
}
}
color: Nheko.colors.buttonText
width: r ? r.width : undefined
}
Button {
palette: Nheko.colors
visible: encryptionError == Olm.MissingSession || encryptionError == Olm.MissingSessionIndex
text: qsTr("Request key")
onClicked: room.requestKeyForEvent(eventId)
}
}

View File

@ -29,6 +29,7 @@ Item {
required property string roomTopic required property string roomTopic
required property string roomName required property string roomName
required property string callType required property string callType
required property int encryptionError
required property int relatedEventCacheBuster required property int relatedEventCacheBuster
height: chooser.childrenRect.height height: chooser.childrenRect.height
@ -189,6 +190,16 @@ Item {
} }
DelegateChoice {
roleValue: MtxEvent.Encrypted
Encrypted {
encryptionError: d.encryptionError
eventId: d.eventId
}
}
DelegateChoice { DelegateChoice {
roleValue: MtxEvent.Name roleValue: MtxEvent.Name

View File

@ -30,6 +30,7 @@ Item {
property string roomTopic property string roomTopic
property string roomName property string roomName
property string callType property string callType
property int encryptionError
property int relatedEventCacheBuster property int relatedEventCacheBuster
width: parent.width width: parent.width
@ -97,6 +98,7 @@ Item {
roomName: r.roomName roomName: r.roomName
callType: r.callType callType: r.callType
relatedEventCacheBuster: r.relatedEventCacheBuster relatedEventCacheBuster: r.relatedEventCacheBuster
encryptionError: r.encryptionError
enabled: false enabled: false
width: parent.width width: parent.width
isReply: true isReply: true

View File

@ -143,14 +143,15 @@
<file>qml/emoji/StickerPicker.qml</file> <file>qml/emoji/StickerPicker.qml</file>
<file>qml/UserProfile.qml</file> <file>qml/UserProfile.qml</file>
<file>qml/delegates/MessageDelegate.qml</file> <file>qml/delegates/MessageDelegate.qml</file>
<file>qml/delegates/TextMessage.qml</file> <file>qml/delegates/Encrypted.qml</file>
<file>qml/delegates/NoticeMessage.qml</file>
<file>qml/delegates/ImageMessage.qml</file>
<file>qml/delegates/PlayableMediaMessage.qml</file>
<file>qml/delegates/FileMessage.qml</file> <file>qml/delegates/FileMessage.qml</file>
<file>qml/delegates/ImageMessage.qml</file>
<file>qml/delegates/NoticeMessage.qml</file>
<file>qml/delegates/Pill.qml</file> <file>qml/delegates/Pill.qml</file>
<file>qml/delegates/Placeholder.qml</file> <file>qml/delegates/Placeholder.qml</file>
<file>qml/delegates/PlayableMediaMessage.qml</file>
<file>qml/delegates/Reply.qml</file> <file>qml/delegates/Reply.qml</file>
<file>qml/delegates/TextMessage.qml</file>
<file>qml/device-verification/Waiting.qml</file> <file>qml/device-verification/Waiting.qml</file>
<file>qml/device-verification/DeviceVerification.qml</file> <file>qml/device-verification/DeviceVerification.qml</file>
<file>qml/device-verification/DigitVerification.qml</file> <file>qml/device-verification/DigitVerification.qml</file>

View File

@ -1069,7 +1069,7 @@ decryptEvent(const MegolmSessionIndex &index,
mtx::events::collections::TimelineEvent te; mtx::events::collections::TimelineEvent te;
mtx::events::collections::from_json(body, te); mtx::events::collections::from_json(body, te);
return {std::nullopt, std::nullopt, std::move(te.data)}; return {DecryptionErrorCode::NoError, std::nullopt, std::move(te.data)};
} catch (std::exception &e) { } catch (std::exception &e) {
return {DecryptionErrorCode::ParsingFailed, e.what(), std::nullopt}; return {DecryptionErrorCode::ParsingFailed, e.what(), std::nullopt};
} }

View File

@ -14,9 +14,11 @@
constexpr auto OLM_ALGO = "m.olm.v1.curve25519-aes-sha2"; constexpr auto OLM_ALGO = "m.olm.v1.curve25519-aes-sha2";
namespace olm { namespace olm {
Q_NAMESPACE
enum class DecryptionErrorCode enum DecryptionErrorCode
{ {
NoError,
MissingSession, // Session was not found, retrieve from backup or request from other devices MissingSession, // Session was not found, retrieve from backup or request from other devices
// and try again // and try again
MissingSessionIndex, // Session was found, but it does not reach back enough to this index, MissingSessionIndex, // Session was found, but it does not reach back enough to this index,
@ -25,14 +27,13 @@ enum class DecryptionErrorCode
DecryptionFailed, // libolm error DecryptionFailed, // libolm error
ParsingFailed, // Failed to parse the actual event ParsingFailed, // Failed to parse the actual event
ReplayAttack, // Megolm index reused ReplayAttack, // Megolm index reused
UnknownFingerprint, // Unknown device Fingerprint
}; };
Q_ENUM_NS(DecryptionErrorCode)
struct DecryptionResult struct DecryptionResult
{ {
std::optional<DecryptionErrorCode> error; DecryptionErrorCode error;
std::optional<std::string> error_message; std::optional<std::string> error_message;
std::optional<mtx::events::collections::TimelineEvents> event; std::optional<mtx::events::collections::TimelineEvents> event;
}; };

View File

@ -20,8 +20,7 @@
Q_DECLARE_METATYPE(Reaction) Q_DECLARE_METATYPE(Reaction)
QCache<EventStore::IdIndex, mtx::events::collections::TimelineEvents> EventStore::decryptedEvents_{ QCache<EventStore::IdIndex, olm::DecryptionResult> EventStore::decryptedEvents_{1000};
1000};
QCache<EventStore::IdIndex, mtx::events::collections::TimelineEvents> EventStore::events_by_id_{ QCache<EventStore::IdIndex, mtx::events::collections::TimelineEvents> EventStore::events_by_id_{
1000}; 1000};
QCache<EventStore::Index, mtx::events::collections::TimelineEvents> EventStore::events_{1000}; QCache<EventStore::Index, mtx::events::collections::TimelineEvents> EventStore::events_{1000};
@ -144,12 +143,16 @@ EventStore::EventStore(std::string room_id, QObject *)
mtx::events::msg::Encrypted>) { mtx::events::msg::Encrypted>) {
auto event = auto event =
decryptEvent({room_id_, e.event_id}, e); decryptEvent({room_id_, e.event_id}, e);
if (auto dec = if (event->event) {
std::get_if<mtx::events::RoomEvent< if (auto dec = std::get_if<
mtx::events::msg:: mtx::events::RoomEvent<
KeyVerificationRequest>>(event)) { mtx::events::msg::
emit updateFlowEventId( KeyVerificationRequest>>(
event_id.event_id.to_string()); &event->event.value())) {
emit updateFlowEventId(
event_id.event_id
.to_string());
}
} }
} }
}); });
@ -393,12 +396,12 @@ EventStore::handleSync(const mtx::responses::Timeline &events)
if (auto encrypted = if (auto encrypted =
std::get_if<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>( std::get_if<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(
&event)) { &event)) {
mtx::events::collections::TimelineEvents *d_event = auto d_event = decryptEvent({room_id_, encrypted->event_id}, *encrypted);
decryptEvent({room_id_, encrypted->event_id}, *encrypted); if (d_event->event &&
if (std::visit( std::visit(
[](auto e) { return (e.sender != utils::localUser().toStdString()); }, [](auto e) { return (e.sender != utils::localUser().toStdString()); },
*d_event)) { *d_event->event)) {
handle_room_verification(*d_event); handle_room_verification(*d_event->event);
} }
} }
} }
@ -599,11 +602,15 @@ EventStore::get(int idx, bool decrypt)
events_.insert(index, event_ptr); events_.insert(index, event_ptr);
} }
if (decrypt) if (decrypt) {
if (auto encrypted = if (auto encrypted =
std::get_if<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>( std::get_if<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(
event_ptr)) event_ptr)) {
return decryptEvent({room_id_, encrypted->event_id}, *encrypted); auto decrypted = decryptEvent({room_id_, encrypted->event_id}, *encrypted);
if (decrypted->event)
return &*decrypted->event;
}
}
return event_ptr; return event_ptr;
} }
@ -629,7 +636,7 @@ EventStore::indexToId(int idx) const
return cache::client()->getTimelineEventId(room_id_, toInternalIdx(idx)); return cache::client()->getTimelineEventId(room_id_, toInternalIdx(idx));
} }
mtx::events::collections::TimelineEvents * olm::DecryptionResult *
EventStore::decryptEvent(const IdIndex &idx, EventStore::decryptEvent(const IdIndex &idx,
const mtx::events::EncryptedEvent<mtx::events::msg::Encrypted> &e) const mtx::events::EncryptedEvent<mtx::events::msg::Encrypted> &e)
{ {
@ -641,57 +648,24 @@ EventStore::decryptEvent(const IdIndex &idx,
index.session_id = e.content.session_id; index.session_id = e.content.session_id;
index.sender_key = e.content.sender_key; index.sender_key = e.content.sender_key;
auto asCacheEntry = [&idx](mtx::events::collections::TimelineEvents &&event) { auto asCacheEntry = [&idx](olm::DecryptionResult &&event) {
auto event_ptr = new mtx::events::collections::TimelineEvents(std::move(event)); auto event_ptr = new olm::DecryptionResult(std::move(event));
decryptedEvents_.insert(idx, event_ptr); decryptedEvents_.insert(idx, event_ptr);
return event_ptr; return event_ptr;
}; };
auto decryptionResult = olm::decryptEvent(index, e); auto decryptionResult = olm::decryptEvent(index, e);
mtx::events::RoomEvent<mtx::events::msg::Notice> dummy;
dummy.origin_server_ts = e.origin_server_ts;
dummy.event_id = e.event_id;
dummy.sender = e.sender;
if (decryptionResult.error) { if (decryptionResult.error) {
switch (*decryptionResult.error) { switch (decryptionResult.error) {
case olm::DecryptionErrorCode::MissingSession: case olm::DecryptionErrorCode::MissingSession:
case olm::DecryptionErrorCode::MissingSessionIndex: { case olm::DecryptionErrorCode::MissingSessionIndex: {
if (decryptionResult.error == olm::DecryptionErrorCode::MissingSession)
dummy.content.body =
tr("-- Encrypted Event (No keys found for decryption) --",
"Placeholder, when the message was not decrypted yet or can't "
"be "
"decrypted.")
.toStdString();
else
dummy.content.body =
tr("-- Encrypted Event (Key not valid for this index) --",
"Placeholder, when the message can't be decrypted with this "
"key since it is not valid for this index ")
.toStdString();
nhlog::crypto()->info("Could not find inbound megolm session ({}, {}, {})", nhlog::crypto()->info("Could not find inbound megolm session ({}, {}, {})",
index.room_id, index.room_id,
index.session_id, index.session_id,
e.sender); e.sender);
// we may not want to request keys during initial sync and such
if (suppressKeyRequests) requestSession(e, false);
break;
// TODO: Check if this actually works and look in key backup
auto copy = e;
copy.room_id = room_id_;
if (pending_key_requests.count(e.content.session_id)) {
pending_key_requests.at(e.content.session_id)
.events.push_back(copy);
} else {
PendingKeyRequests request;
request.request_id =
"key_request." + http::client()->generate_txn_id();
request.events.push_back(copy);
olm::send_key_request_for(copy, request.request_id);
pending_key_requests[e.content.session_id] = request;
}
break; break;
} }
case olm::DecryptionErrorCode::DbError: case olm::DecryptionErrorCode::DbError:
@ -701,12 +675,6 @@ EventStore::decryptEvent(const IdIndex &idx,
index.session_id, index.session_id,
index.sender_key, index.sender_key,
decryptionResult.error_message.value_or("")); decryptionResult.error_message.value_or(""));
dummy.content.body =
tr("-- Decryption Error (failed to retrieve megolm keys from db) --",
"Placeholder, when the message can't be decrypted, because the DB "
"access "
"failed.")
.toStdString();
break; break;
case olm::DecryptionErrorCode::DecryptionFailed: case olm::DecryptionErrorCode::DecryptionFailed:
nhlog::crypto()->critical( nhlog::crypto()->critical(
@ -715,22 +683,8 @@ EventStore::decryptEvent(const IdIndex &idx,
index.session_id, index.session_id,
index.sender_key, index.sender_key,
decryptionResult.error_message.value_or("")); decryptionResult.error_message.value_or(""));
dummy.content.body =
tr("-- Decryption Error (%1) --",
"Placeholder, when the message can't be decrypted. In this case, the "
"Olm "
"decrytion returned an error, which is passed as %1.")
.arg(
QString::fromStdString(decryptionResult.error_message.value_or("")))
.toStdString();
break; break;
case olm::DecryptionErrorCode::ParsingFailed: case olm::DecryptionErrorCode::ParsingFailed:
dummy.content.body =
tr("-- Encrypted Event (Unknown event type) --",
"Placeholder, when the message was decrypted, but we couldn't parse "
"it, because "
"Nheko/mtxclient don't support that event type yet.")
.toStdString();
break; break;
case olm::DecryptionErrorCode::ReplayAttack: case olm::DecryptionErrorCode::ReplayAttack:
nhlog::crypto()->critical( nhlog::crypto()->critical(
@ -738,85 +692,50 @@ EventStore::decryptEvent(const IdIndex &idx,
e.event_id, e.event_id,
room_id_, room_id_,
index.sender_key); index.sender_key);
dummy.content.body =
tr("-- Replay attack! This message index was reused! --").toStdString();
break; break;
case olm::DecryptionErrorCode::UnknownFingerprint: case olm::DecryptionErrorCode::NoError:
// TODO: don't fail, just show in UI. // unreachable
nhlog::crypto()->critical("Message by unverified fingerprint {}",
index.sender_key);
dummy.content.body =
tr("-- Message by unverified device! --").toStdString();
break; break;
} }
return asCacheEntry(std::move(dummy)); return asCacheEntry(std::move(decryptionResult));
}
std::string msg_str;
try {
auto session = cache::client()->getInboundMegolmSession(index);
auto res =
olm::client()->decrypt_group_message(session.get(), e.content.ciphertext);
msg_str = std::string((char *)res.data.data(), res.data.size());
} catch (const lmdb::error &e) {
nhlog::db()->critical("failed to retrieve megolm session with index ({}, {}, {})",
index.room_id,
index.session_id,
index.sender_key,
e.what());
dummy.content.body =
tr("-- Decryption Error (failed to retrieve megolm keys from db) --",
"Placeholder, when the message can't be decrypted, because the DB "
"access "
"failed.")
.toStdString();
return asCacheEntry(std::move(dummy));
} catch (const mtx::crypto::olm_exception &e) {
nhlog::crypto()->critical("failed to decrypt message with index ({}, {}, {}): {}",
index.room_id,
index.session_id,
index.sender_key,
e.what());
dummy.content.body =
tr("-- Decryption Error (%1) --",
"Placeholder, when the message can't be decrypted. In this case, the "
"Olm "
"decrytion returned an error, which is passed as %1.")
.arg(e.what())
.toStdString();
return asCacheEntry(std::move(dummy));
}
// Add missing fields for the event.
json body = json::parse(msg_str);
body["event_id"] = e.event_id;
body["sender"] = e.sender;
body["origin_server_ts"] = e.origin_server_ts;
body["unsigned"] = e.unsigned_data;
// relations are unencrypted in content...
mtx::common::add_relations(body["content"], e.content.relations);
json event_array = json::array();
event_array.push_back(body);
std::vector<mtx::events::collections::TimelineEvents> temp_events;
mtx::responses::utils::parse_timeline_events(event_array, temp_events);
if (temp_events.size() == 1) {
auto encInfo = mtx::accessors::file(temp_events[0]);
if (encInfo)
emit newEncryptedImage(encInfo.value());
return asCacheEntry(std::move(temp_events[0]));
} }
auto encInfo = mtx::accessors::file(decryptionResult.event.value()); auto encInfo = mtx::accessors::file(decryptionResult.event.value());
if (encInfo) if (encInfo)
emit newEncryptedImage(encInfo.value()); emit newEncryptedImage(encInfo.value());
return asCacheEntry(std::move(decryptionResult.event.value())); return asCacheEntry(std::move(decryptionResult));
}
void
EventStore::requestSession(const mtx::events::EncryptedEvent<mtx::events::msg::Encrypted> &ev,
bool manual)
{
// we may not want to request keys during initial sync and such
if (suppressKeyRequests)
return;
// TODO: Look in key backup
auto copy = ev;
copy.room_id = room_id_;
if (pending_key_requests.count(ev.content.session_id)) {
auto &r = pending_key_requests.at(ev.content.session_id);
r.events.push_back(copy);
// automatically request once every 10 min, manually every 1 min
qint64 delay = manual ? 60 : (60 * 10);
if (r.requested_at + delay < QDateTime::currentSecsSinceEpoch()) {
r.requested_at = QDateTime::currentSecsSinceEpoch();
olm::send_key_request_for(copy, r.request_id);
}
} else {
PendingKeyRequests request;
request.request_id = "key_request." + http::client()->generate_txn_id();
request.requested_at = QDateTime::currentSecsSinceEpoch();
request.events.push_back(copy);
olm::send_key_request_for(copy, request.request_id);
pending_key_requests[ev.content.session_id] = request;
}
} }
void void
@ -877,15 +796,56 @@ EventStore::get(std::string id, std::string_view related_to, bool decrypt, bool
events_by_id_.insert(index, event_ptr); events_by_id_.insert(index, event_ptr);
} }
if (decrypt) if (decrypt) {
if (auto encrypted = if (auto encrypted =
std::get_if<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>( std::get_if<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(
event_ptr)) event_ptr)) {
return decryptEvent(index, *encrypted); auto decrypted = decryptEvent(index, *encrypted);
if (decrypted->event)
return &*decrypted->event;
}
}
return event_ptr; return event_ptr;
} }
olm::DecryptionErrorCode
EventStore::decryptionError(std::string id)
{
if (this->thread() != QThread::currentThread())
nhlog::db()->warn("{} called from a different thread!", __func__);
if (id.empty())
return olm::DecryptionErrorCode::NoError;
IdIndex index{room_id_, std::move(id)};
auto edits_ = edits(index.id);
if (!edits_.empty()) {
index.id = mtx::accessors::event_id(edits_.back());
auto event_ptr =
new mtx::events::collections::TimelineEvents(std::move(edits_.back()));
events_by_id_.insert(index, event_ptr);
}
auto event_ptr = events_by_id_.object(index);
if (!event_ptr) {
auto event = cache::client()->getEvent(room_id_, index.id);
if (!event) {
return olm::DecryptionErrorCode::NoError;
}
event_ptr = new mtx::events::collections::TimelineEvents(std::move(event->data));
events_by_id_.insert(index, event_ptr);
}
if (auto encrypted =
std::get_if<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(event_ptr)) {
auto decrypted = decryptEvent(index, *encrypted);
return decrypted->error;
}
return olm::DecryptionErrorCode::NoError;
}
void void
EventStore::fetchMore() EventStore::fetchMore()
{ {

View File

@ -15,6 +15,7 @@
#include <mtx/responses/messages.hpp> #include <mtx/responses/messages.hpp>
#include <mtx/responses/sync.hpp> #include <mtx/responses/sync.hpp>
#include "Olm.h"
#include "Reaction.h" #include "Reaction.h"
class EventStore : public QObject class EventStore : public QObject
@ -78,6 +79,9 @@ public:
mtx::events::collections::TimelineEvents *get(int idx, bool decrypt = true); mtx::events::collections::TimelineEvents *get(int idx, bool decrypt = true);
QVariantList reactions(const std::string &event_id); QVariantList reactions(const std::string &event_id);
olm::DecryptionErrorCode decryptionError(std::string id);
void requestSession(const mtx::events::EncryptedEvent<mtx::events::msg::Encrypted> &ev,
bool manual);
int size() const int size() const
{ {
@ -119,7 +123,7 @@ public slots:
private: private:
std::vector<mtx::events::collections::TimelineEvents> edits(const std::string &event_id); std::vector<mtx::events::collections::TimelineEvents> edits(const std::string &event_id);
mtx::events::collections::TimelineEvents *decryptEvent( olm::DecryptionResult *decryptEvent(
const IdIndex &idx, const IdIndex &idx,
const mtx::events::EncryptedEvent<mtx::events::msg::Encrypted> &e); const mtx::events::EncryptedEvent<mtx::events::msg::Encrypted> &e);
void handle_room_verification(mtx::events::collections::TimelineEvents event); void handle_room_verification(mtx::events::collections::TimelineEvents event);
@ -129,7 +133,7 @@ private:
uint64_t first = std::numeric_limits<uint64_t>::max(), uint64_t first = std::numeric_limits<uint64_t>::max(),
last = std::numeric_limits<uint64_t>::max(); last = std::numeric_limits<uint64_t>::max();
static QCache<IdIndex, mtx::events::collections::TimelineEvents> decryptedEvents_; static QCache<IdIndex, olm::DecryptionResult> decryptedEvents_;
static QCache<Index, mtx::events::collections::TimelineEvents> events_; static QCache<Index, mtx::events::collections::TimelineEvents> events_;
static QCache<IdIndex, mtx::events::collections::TimelineEvents> events_by_id_; static QCache<IdIndex, mtx::events::collections::TimelineEvents> events_by_id_;
@ -137,6 +141,7 @@ private:
{ {
std::string request_id; std::string request_id;
std::vector<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>> events; std::vector<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>> events;
qint64 requested_at;
}; };
std::map<std::string, PendingKeyRequests> pending_key_requests; std::map<std::string, PendingKeyRequests> pending_key_requests;

View File

@ -452,6 +452,7 @@ TimelineModel::roleNames() const
{IsEditable, "isEditable"}, {IsEditable, "isEditable"},
{IsEncrypted, "isEncrypted"}, {IsEncrypted, "isEncrypted"},
{Trustlevel, "trustlevel"}, {Trustlevel, "trustlevel"},
{EncryptionError, "encryptionError"},
{ReplyTo, "replyTo"}, {ReplyTo, "replyTo"},
{Reactions, "reactions"}, {Reactions, "reactions"},
{RoomId, "roomId"}, {RoomId, "roomId"},
@ -639,6 +640,9 @@ TimelineModel::data(const mtx::events::collections::TimelineEvents &event, int r
return crypto::Trust::Unverified; return crypto::Trust::Unverified;
} }
case EncryptionError:
return events.decryptionError(event_id(event));
case ReplyTo: case ReplyTo:
return QVariant(QString::fromStdString(relations(event).reply_to().value_or(""))); return QVariant(QString::fromStdString(relations(event).reply_to().value_or("")));
case Reactions: { case Reactions: {
@ -690,6 +694,7 @@ TimelineModel::data(const mtx::events::collections::TimelineEvents &event, int r
m.insert(names[RoomName], data(event, static_cast<int>(RoomName))); m.insert(names[RoomName], data(event, static_cast<int>(RoomName)));
m.insert(names[RoomTopic], data(event, static_cast<int>(RoomTopic))); m.insert(names[RoomTopic], data(event, static_cast<int>(RoomTopic)));
m.insert(names[CallType], data(event, static_cast<int>(CallType))); m.insert(names[CallType], data(event, static_cast<int>(CallType)));
m.insert(names[EncryptionError], data(event, static_cast<int>(EncryptionError)));
return QVariant(m); return QVariant(m);
} }
@ -1551,6 +1556,17 @@ TimelineModel::scrollTimerEvent()
} }
} }
void
TimelineModel::requestKeyForEvent(QString id)
{
auto encrypted_event = events.get(id.toStdString(), "", false);
if (encrypted_event) {
if (auto ev = std::get_if<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(
encrypted_event))
events.requestSession(*ev, true);
}
}
void void
TimelineModel::copyLinkToEvent(QString eventId) const TimelineModel::copyLinkToEvent(QString eventId) const
{ {

View File

@ -212,6 +212,7 @@ public:
IsEditable, IsEditable,
IsEncrypted, IsEncrypted,
Trustlevel, Trustlevel,
EncryptionError,
ReplyTo, ReplyTo,
Reactions, Reactions,
RoomId, RoomId,
@ -264,6 +265,8 @@ public:
endResetModel(); endResetModel();
} }
Q_INVOKABLE void requestKeyForEvent(QString id);
std::vector<::Reaction> reactions(const std::string &event_id) std::vector<::Reaction> reactions(const std::string &event_id)
{ {
auto list = events.reactions(event_id); auto list = events.reactions(event_id);

View File

@ -157,6 +157,8 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par
0, 0,
"MtxEvent", "MtxEvent",
"Can't instantiate enum!"); "Can't instantiate enum!");
qmlRegisterUncreatableMetaObject(
olm::staticMetaObject, "im.nheko", 1, 0, "Olm", "Can't instantiate enum!");
qmlRegisterUncreatableMetaObject( qmlRegisterUncreatableMetaObject(
crypto::staticMetaObject, "im.nheko", 1, 0, "Crypto", "Can't instantiate enum!"); crypto::staticMetaObject, "im.nheko", 1, 0, "Crypto", "Can't instantiate enum!");
qmlRegisterUncreatableMetaObject(verification::staticMetaObject, qmlRegisterUncreatableMetaObject(verification::staticMetaObject,