From 7401bd13b272bbe9a0415c2e523f8ec0fa7f3e11 Mon Sep 17 00:00:00 2001 From: Jedi18 Date: Thu, 11 Feb 2021 19:54:09 +0530 Subject: [PATCH] added notifications and encryption for the new roomsettings --- resources/qml/RoomSettings.qml | 58 +++++- resources/qml/TimelineView.qml | 2 +- src/timeline/TimelineViewManager.cpp | 10 +- src/timeline/TimelineViewManager.h | 2 +- src/ui/RoomSettings.cpp | 257 ++++++++++++++++++++++++++- src/ui/RoomSettings.h | 29 +++ 6 files changed, 352 insertions(+), 6 deletions(-) diff --git a/resources/qml/RoomSettings.qml b/resources/qml/RoomSettings.qml index d6f3fe7b..4b03e08b 100644 --- a/resources/qml/RoomSettings.qml +++ b/resources/qml/RoomSettings.qml @@ -2,6 +2,7 @@ import QtQuick 2.9 import QtQuick.Controls 2.3 import QtQuick.Layouts 1.2 import QtQuick.Window 2.3 +import QtQuick.Dialogs 1.2 import im.nheko 1.0 ApplicationWindow { @@ -17,7 +18,8 @@ ApplicationWindow { palette: colors color: colors.window title: roomSettings.roomName - modality: Qt.Modal + modality: Qt.WindowModal + flags: Qt.WindowStaysOnTopHint Shortcut { sequence: StandardKey.Cancel @@ -75,6 +77,10 @@ ApplicationWindow { ComboBox { model: [ "Muted", "Mentions only", "All messages" ] + currentIndex: roomSettings.notifications + onActivated: { + roomSettings.changeNotifications(index) + } } } @@ -85,7 +91,12 @@ ApplicationWindow { ComboBox { Layout.fillWidth: true + enabled: roomSettings.canChangeJoinRules model: [ "Anyone and guests", "Anyone", "Invited users" ] + currentIndex: roomSettings.accessJoinRules + onActivated: { + roomSettings.changeAccessRules(index) + } } } @@ -99,10 +110,46 @@ ApplicationWindow { } Switch { + id: encryptionSwitch + + checked: roomSettings.isEncryptionEnabled + onToggled: { + if(roomSettings.isEncryptionEnabled) { + checked=true; + return; + } + + confirmEncryptionDialog.open(); + } + } + + MessageDialog { + id: confirmEncryptionDialog + title: qsTr("End-to-End Encryption") + text: qsTr("Encryption is currently experimental and things might break unexpectedly.
+ Please take note that it can't be disabled afterwards.") + modality: Qt.WindowModal + icon: StandardIcon.Question + + onAccepted: { + if(roomSettings.isEncryptionEnabled) { + return; + } + + roomSettings.enableEncryption(); + } + + onRejected: { + encryptionSwitch.checked = false + } + + standardButtons: Dialog.Ok | Dialog.Cancel } } RowLayout { + visible: roomSettings.isEncryptionEnabled + MatrixText { text: "Respond to key requests" } @@ -112,6 +159,15 @@ ApplicationWindow { } Switch { + ToolTip.text: qsTr("Whether or not the client should respond automatically with the session keys + upon request. Use with caution, this is a temporary measure to test the + E2E implementation until device verification is completed.") + + checked: roomSettings.respondsToKeyRequests + + onToggled: { + roomSettings.changeKeyRequestsPreference(checked) + } } } diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml index 6b34f2ab..0c32effd 100644 --- a/resources/qml/TimelineView.qml +++ b/resources/qml/TimelineView.qml @@ -178,7 +178,7 @@ Page { target: TimelineManager onOpenRoomSettingsDialog: { var roomSettings = roomSettingsComponent.createObject(timelineRoot, { - "roomSettings": roomSettings + "roomSettings": settings }); roomSettings.show(); } diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp index 7c1922d7..4edc3369 100644 --- a/src/timeline/TimelineViewManager.cpp +++ b/src/timeline/TimelineViewManager.cpp @@ -126,6 +126,12 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par 0, "UserProfileModel", "UserProfile needs to be instantiated on the C++ side"); + qmlRegisterUncreatableType( + "im.nheko", + 1, + 0, + "RoomSettingsModel", + "Room Settings needs to be instantiated on the C++ side"); static auto self = this; qmlRegisterSingletonType( @@ -394,8 +400,8 @@ TimelineViewManager::openRoomSettings() { MainWindow::instance()->openRoomSettings(timeline_->roomId()); - RoomSettings *roomSettings = new RoomSettings(timeline_->roomId(), this); - emit openRoomSettingsDialog(roomSettings); + RoomSettings *settings = new RoomSettings(timeline_->roomId(), this); + emit openRoomSettingsDialog(settings); } void diff --git a/src/timeline/TimelineViewManager.h b/src/timeline/TimelineViewManager.h index dca133ce..10708033 100644 --- a/src/timeline/TimelineViewManager.h +++ b/src/timeline/TimelineViewManager.h @@ -89,7 +89,7 @@ signals: void showRoomList(); void narrowViewChanged(); void focusChanged(); - void openRoomSettingsDialog(RoomSettings *roomSettings); + void openRoomSettingsDialog(RoomSettings *settings); public slots: void updateReadReceipts(const QString &room_id, const std::vector &event_ids); diff --git a/src/ui/RoomSettings.cpp b/src/ui/RoomSettings.cpp index e8317024..785452d0 100644 --- a/src/ui/RoomSettings.cpp +++ b/src/ui/RoomSettings.cpp @@ -5,12 +5,65 @@ #include "Cache.h" #include "Logging.h" +#include "MatrixClient.h" +#include "Utils.h" + +using namespace mtx::events; RoomSettings::RoomSettings(QString roomid, QObject *parent) : roomid_{std::move(roomid)} , QObject(parent) { retrieveRoomInfo(); + + // get room setting notifications + http::client()->get_pushrules( + "global", + "override", + roomid_.toStdString(), + [this](const mtx::pushrules::PushRule &rule, mtx::http::RequestErr &err) { + if (err) { + if (err->status_code == boost::beast::http::status::not_found) + http::client()->get_pushrules( + "global", + "room", + roomid_.toStdString(), + [this](const mtx::pushrules::PushRule &rule, + mtx::http::RequestErr &err) { + if (err) { + notifications_ = 2; // all messages + emit notificationsChanged(); + return; + } + + if (rule.enabled) { + notifications_ = 1; // mentions only + emit notificationsChanged(); + } + }); + return; + } + + if (rule.enabled) { + notifications_ = 0; // muted + emit notificationsChanged(); + } else { + notifications_ = 2; // all messages + emit notificationsChanged(); + } + }); + + // access rules + if (info_.join_rule == state::JoinRule::Public) { + if (info_.guest_access) { + accessRules_ = 0; + } else { + accessRules_ = 1; + } + } else { + accessRules_ = 2; + } + emit accessJoinRulesChanged(); } QString @@ -25,9 +78,211 @@ RoomSettings::retrieveRoomInfo() try { usesEncryption_ = cache::isRoomEncrypted(roomid_.toStdString()); info_ = cache::singleRoomInfo(roomid_.toStdString()); - //setAvatar(); + // setAvatar(); } catch (const lmdb::error &) { nhlog::db()->warn("failed to retrieve room info from cache: {}", roomid_.toStdString()); } +} + +int +RoomSettings::notifications() +{ + return notifications_; +} + +int +RoomSettings::accessJoinRules() +{ + return accessRules_; +} + +bool +RoomSettings::respondsToKeyRequests() +{ + return usesEncryption_ && utils::respondsToKeyRequests(roomid_); +} + +void +RoomSettings::changeKeyRequestsPreference(bool isOn) +{ + utils::setKeyRequestsPreference(roomid_, isOn); + emit keyRequestsChanged(); +} + +void +RoomSettings::enableEncryption() +{ + if (usesEncryption_) + return; + + const auto room_id = roomid_.toStdString(); + http::client()->enable_encryption( + room_id, [room_id, this](const mtx::responses::EventId &, mtx::http::RequestErr err) { + if (err) { + int status_code = static_cast(err->status_code); + nhlog::net()->warn("failed to enable encryption in room ({}): {} {}", + room_id, + err->matrix_error.error, + status_code); + //emit enableEncryptionError( + // tr("Failed to enable encryption: %1") + // .arg(QString::fromStdString(err->matrix_error.error))); + usesEncryption_ = false; + emit encryptionChanged(); + return; + } + + nhlog::net()->info("enabled encryption on room ({})", room_id); + }); + + usesEncryption_ = true; + emit encryptionChanged(); +} + +bool +RoomSettings::canChangeJoinRules() const +{ + try { + return cache::hasEnoughPowerLevel({EventType::RoomJoinRules}, + roomid_.toStdString(), + utils::localUser().toStdString()); + } catch (const lmdb::error &e) { + nhlog::db()->warn("lmdb error: {}", e.what()); + } + + return false; +} + +bool +RoomSettings::isEncryptionEnabled() const +{ + return usesEncryption_; +} + +void +RoomSettings::changeNotifications(int currentIndex) +{ + notifications_ = currentIndex; + + std::string room_id = roomid_.toStdString(); + if (notifications_ == 0) { + // mute room + // delete old rule first, then add new rule + mtx::pushrules::PushRule rule; + rule.actions = {mtx::pushrules::actions::dont_notify{}}; + mtx::pushrules::PushCondition condition; + condition.kind = "event_match"; + condition.key = "room_id"; + condition.pattern = room_id; + rule.conditions = {condition}; + + http::client()->put_pushrules( + "global", "override", room_id, rule, [room_id](mtx::http::RequestErr &err) { + if (err) + nhlog::net()->error("failed to set pushrule for room {}: {} {}", + room_id, + static_cast(err->status_code), + err->matrix_error.error); + http::client()->delete_pushrules( + "global", "room", room_id, [room_id](mtx::http::RequestErr &) {}); + }); + } else if (notifications_ == 1) { + // mentions only + // delete old rule first, then add new rule + mtx::pushrules::PushRule rule; + rule.actions = {mtx::pushrules::actions::dont_notify{}}; + http::client()->put_pushrules( + "global", "room", room_id, rule, [room_id](mtx::http::RequestErr &err) { + if (err) + nhlog::net()->error("failed to set pushrule for room {}: {} {}", + room_id, + static_cast(err->status_code), + err->matrix_error.error); + http::client()->delete_pushrules( + "global", "override", room_id, [room_id](mtx::http::RequestErr &) {}); + }); + } else { + // all messages + http::client()->delete_pushrules( + "global", "override", room_id, [room_id](mtx::http::RequestErr &) { + http::client()->delete_pushrules( + "global", "room", room_id, [room_id](mtx::http::RequestErr &) {}); + }); + } +} + +void +RoomSettings::changeAccessRules(int index) +{ + using namespace mtx::events::state; + + auto guest_access = [](int index) -> state::GuestAccess { + state::GuestAccess event; + + if (index == 0) + event.guest_access = state::AccessState::CanJoin; + else + event.guest_access = state::AccessState::Forbidden; + + return event; + }(index); + + auto join_rule = [](int index) -> state::JoinRules { + state::JoinRules event; + + switch (index) { + case 0: + case 1: + event.join_rule = state::JoinRule::Public; + break; + default: + event.join_rule = state::JoinRule::Invite; + } + + return event; + }(index); + + updateAccessRules(roomid_.toStdString(), join_rule, guest_access); +} + +void +RoomSettings::updateAccessRules(const std::string &room_id, + const mtx::events::state::JoinRules &join_rule, + const mtx::events::state::GuestAccess &guest_access) +{ + // startLoadingSpinner(); + // resetErrorLabel(); + + http::client()->send_state_event( + room_id, + join_rule, + [this, room_id, guest_access](const mtx::responses::EventId &, + mtx::http::RequestErr err) { + if (err) { + nhlog::net()->warn("failed to send m.room.join_rule: {} {}", + static_cast(err->status_code), + err->matrix_error.error); + // emit showErrorMessage(QString::fromStdString(err->matrix_error.error)); + + return; + } + + http::client()->send_state_event( + room_id, + guest_access, + [this](const mtx::responses::EventId &, mtx::http::RequestErr err) { + if (err) { + nhlog::net()->warn("failed to send m.room.guest_access: {} {}", + static_cast(err->status_code), + err->matrix_error.error); + // emit showErrorMessage( + // QString::fromStdString(err->matrix_error.error)); + + return; + } + + // emit signal that stops loading spinner and reset error label + }); + }); } \ No newline at end of file diff --git a/src/ui/RoomSettings.h b/src/ui/RoomSettings.h index 98e64b74..098e27ba 100644 --- a/src/ui/RoomSettings.h +++ b/src/ui/RoomSettings.h @@ -3,23 +3,52 @@ #include #include +#include + #include "CacheStructs.h" class RoomSettings : public QObject { Q_OBJECT Q_PROPERTY(QString roomName READ roomName CONSTANT) + Q_PROPERTY(int notifications READ notifications NOTIFY notificationsChanged) + Q_PROPERTY(int accessJoinRules READ accessJoinRules NOTIFY accessJoinRulesChanged) + Q_PROPERTY(bool canChangeJoinRules READ canChangeJoinRules CONSTANT) + Q_PROPERTY(bool isEncryptionEnabled READ isEncryptionEnabled NOTIFY encryptionChanged) + Q_PROPERTY(bool respondsToKeyRequests READ respondsToKeyRequests NOTIFY keyRequestsChanged) public: RoomSettings(QString roomid, QObject *parent = nullptr); QString roomName() const; + int notifications(); + int accessJoinRules(); + bool respondsToKeyRequests(); + //! Whether the user has enough power level to send m.room.join_rules events. + bool canChangeJoinRules() const; + bool isEncryptionEnabled() const; + + Q_INVOKABLE void changeNotifications(int currentIndex); + Q_INVOKABLE void changeAccessRules(int index); + Q_INVOKABLE void changeKeyRequestsPreference(bool isOn); + Q_INVOKABLE void enableEncryption(); + +signals: + void notificationsChanged(); + void accessJoinRulesChanged(); + void keyRequestsChanged(); + void encryptionChanged(); private: void retrieveRoomInfo(); + void updateAccessRules(const std::string &room_id, + const mtx::events::state::JoinRules &, + const mtx::events::state::GuestAccess &); private: QString roomid_; bool usesEncryption_ = false; RoomInfo info_; + int notifications_ = 0; + int accessRules_ = 0; }; \ No newline at end of file