From 2aabe9dcacbef4f753761f6df840da4292561d11 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Mon, 1 Nov 2021 22:20:15 +0100 Subject: [PATCH] Prompt user when there are unverified devices --- resources/qml/RoomList.qml | 80 +++++++++++++++++++ resources/qml/SelfVerificationCheck.qml | 4 + .../NewVerificationRequest.qml | 5 +- src/Cache.cpp | 10 +-- src/Cache_p.h | 2 +- src/encryption/DeviceVerificationFlow.h | 2 + src/encryption/SelfVerificationStatus.cpp | 20 ++++- src/ui/Theme.cpp | 3 + src/ui/Theme.h | 4 +- 9 files changed, 120 insertions(+), 10 deletions(-) diff --git a/resources/qml/RoomList.qml b/resources/qml/RoomList.qml index 85087bc4..72ac49e1 100644 --- a/resources/qml/RoomList.qml +++ b/resources/qml/RoomList.qml @@ -502,6 +502,86 @@ Page { Layout.fillWidth: true } + Rectangle { + id: unverifiedStuffBubble + color: Qt.lighter(Nheko.theme.orange, verifyButtonHovered.hovered ? 1.2 : 1.0) + Layout.fillWidth: true + implicitHeight: explanation.height + Nheko.paddingMedium * 2 + visible: SelfVerificationStatus.status != SelfVerificationStatus.AllVerified + + RowLayout { + id: unverifiedStuffBubbleContainer + width: parent.width + height: explanation.height + Nheko.paddingMedium * 2 + spacing: 0 + + Label { + id: explanation + Layout.margins: Nheko.paddingMedium + Layout.rightMargin: Nheko.paddingSmall + color: Nheko.colors.buttonText + Layout.fillWidth: true + text: switch(SelfVerificationStatus.status) { + case SelfVerificationStatus.NoMasterKey: + //: Cross-signing setup has not run yet. + return qsTr("Encryption not set up"); + case SelfVerificationStatus.UnverifiedMasterKey: + //: The user just signed in with this device and hasn't verified their master key. + return qsTr("Unverified login"); + case SelfVerificationStatus.UnverifiedDevices: + //: There are unverified devices signed in to this account. + return qsTr("Please verify your other devices"); + default: + return "" + } + textFormat: Text.PlainText + wrapMode: Text.Wrap + } + + ImageButton { + id: closeUnverifiedBubble + + Layout.rightMargin: Nheko.paddingMedium + Layout.topMargin: Nheko.paddingMedium + Layout.alignment: Qt.AlignRight | Qt.AlignTop + hoverEnabled: true + width: fontMetrics.font.pixelSize + height: fontMetrics.font.pixelSize + image: ":/icons/icons/ui/remove-symbol.png" + ToolTip.visible: closeUnverifiedBubble.hovered + ToolTip.text: qsTr("Close") + onClicked: unverifiedStuffBubble.visible = false + } + + } + + HoverHandler { + id: verifyButtonHovered + enabled: !closeUnverifiedBubble.hovered + + acceptedDevices: PointerDevice.Mouse | PointerDevice.Stylus | PointerDevice.TouchPad + } + + TapHandler { + enabled: !closeUnverifiedBubble.hovered + acceptedButtons: Qt.LeftButton + onSingleTapped: { + if (SelfVerificationStatus.status == SelfVerificationStatus.UnverifiedDevices) { + SelfVerificationStatus.verifyUnverifiedDevices(); + } else { + SelfVerificationStatus.statusChanged(); + } + } + } + } + + Rectangle { + color: Nheko.theme.separator + height: 1 + Layout.fillWidth: true + visible: unverifiedStuffBubble.visible + } + } footer: ColumnLayout { diff --git a/resources/qml/SelfVerificationCheck.qml b/resources/qml/SelfVerificationCheck.qml index 26af82b3..a7502d8d 100644 --- a/resources/qml/SelfVerificationCheck.qml +++ b/resources/qml/SelfVerificationCheck.qml @@ -277,6 +277,10 @@ Item { bootstrapCrosssigning.open(); else if (SelfVerificationStatus.status == SelfVerificationStatus.UnverifiedMasterKey) verifyMasterKey.open(); + else { + bootstrapCrosssigning.close(); + verifyMasterKey.close(); + } } diff --git a/resources/qml/device-verification/NewVerificationRequest.qml b/resources/qml/device-verification/NewVerificationRequest.qml index 5ae2d25b..7e521605 100644 --- a/resources/qml/device-verification/NewVerificationRequest.qml +++ b/resources/qml/device-verification/NewVerificationRequest.qml @@ -23,7 +23,10 @@ Pane { text: { if (flow.sender) { if (flow.isSelfVerification) - return qsTr("To allow other users to see, which of your devices actually belong to you, you can verify them. This also allows key backup to work automatically. Verify %1 now?").arg(flow.deviceId); + if (flow.isMultiDeviceVerification) + return qsTr("To allow other users to see, which of your devices actually belong to you, you can verify them. This also allows key backup to work automatically. Verify an unverified device now? (Please make sure you have one of those devices available.)"); + else + return qsTr("To allow other users to see, which of your devices actually belong to you, you can verify them. This also allows key backup to work automatically. Verify %1 now?").arg(flow.deviceId); else return qsTr("To ensure that no malicious user can eavesdrop on your encrypted communications you can verify the other party."); } else { diff --git a/src/Cache.cpp b/src/Cache.cpp index 5b77a9d4..45cc642d 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -208,8 +208,7 @@ Cache::Cache(const QString &userId, QObject *parent) [this](const std::string &u) { if (u == localUserId_.toStdString()) { auto status = verificationStatus(u); - if (status.unverified_device_count || !status.user_verified) - emit selfUnverified(); + emit selfVerificationStatusChanged(); } }, Qt::QueuedConnection); @@ -4265,6 +4264,7 @@ Cache::markDeviceVerified(const std::string &user_id, const std::string &key) std::unique_lock lock(verification_storage.verification_storage_mtx); if (user_id == local_user) { std::swap(tmp, verification_storage.status); + verification_storage.status.clear(); } else { verification_storage.status.erase(user_id); } @@ -4274,9 +4274,8 @@ Cache::markDeviceVerified(const std::string &user_id, const std::string &key) (void)status; emit verificationStatusChanged(user); } - } else { - emit verificationStatusChanged(user_id); } + emit verificationStatusChanged(user_id); } void @@ -4316,9 +4315,8 @@ Cache::markDeviceUnverified(const std::string &user_id, const std::string &key) (void)status; emit verificationStatusChanged(user); } - } else { - emit verificationStatusChanged(user_id); } + emit verificationStatusChanged(user_id); } VerificationStatus diff --git a/src/Cache_p.h b/src/Cache_p.h index f7db77d4..651d73d7 100644 --- a/src/Cache_p.h +++ b/src/Cache_p.h @@ -310,7 +310,7 @@ signals: void removeNotification(const QString &room_id, const QString &event_id); void userKeysUpdate(const std::string &sync_token, const mtx::responses::QueryKeys &keyQuery); void verificationStatusChanged(const std::string &userid); - void selfUnverified(); + void selfVerificationStatusChanged(); void secretChanged(const std::string name); private: diff --git a/src/encryption/DeviceVerificationFlow.h b/src/encryption/DeviceVerificationFlow.h index 55713def..537adf31 100644 --- a/src/encryption/DeviceVerificationFlow.h +++ b/src/encryption/DeviceVerificationFlow.h @@ -69,6 +69,7 @@ class DeviceVerificationFlow : public QObject Q_PROPERTY(std::vector sasList READ getSasList CONSTANT) Q_PROPERTY(bool isDeviceVerification READ isDeviceVerification CONSTANT) Q_PROPERTY(bool isSelfVerification READ isSelfVerification CONSTANT) + Q_PROPERTY(bool isMultiDeviceVerification READ isMultiDeviceVerification CONSTANT) public: enum State @@ -139,6 +140,7 @@ public: return this->type == DeviceVerificationFlow::Type::ToDevice; } bool isSelfVerification() const; + bool isMultiDeviceVerification() const { return deviceIds.size() > 1; } void callback_fn(const UserKeyCache &res, mtx::http::RequestErr err, std::string user_id); diff --git a/src/encryption/SelfVerificationStatus.cpp b/src/encryption/SelfVerificationStatus.cpp index d4be4442..ebb6b548 100644 --- a/src/encryption/SelfVerificationStatus.cpp +++ b/src/encryption/SelfVerificationStatus.cpp @@ -20,7 +20,7 @@ SelfVerificationStatus::SelfVerificationStatus(QObject *o) { connect(MainWindow::instance(), &MainWindow::reload, this, [this] { connect(cache::client(), - &Cache::selfUnverified, + &Cache::selfVerificationStatusChanged, this, &SelfVerificationStatus::invalidate, Qt::UniqueConnection); @@ -233,6 +233,24 @@ void SelfVerificationStatus::verifyUnverifiedDevices() { nhlog::db()->info("Clicked verify unverified devices"); + const auto this_user = http::client()->user_id().to_string(); + + auto keys = cache::client()->userKeys(this_user); + auto verif = cache::client()->verificationStatus(this_user); + + if (!keys) + return; + + std::vector devices; + for (const auto &[device, keys] : keys->device_keys) { + (void)keys; + if (!verif.verified_devices.count(device)) + devices.push_back(QString::fromStdString(device)); + } + + if (!devices.empty()) + ChatPage::instance()->timelineManager()->verificationManager()->verifyOneOfDevices( + QString::fromStdString(this_user), std::move(devices)); } void diff --git a/src/ui/Theme.cpp b/src/ui/Theme.cpp index d6f0b72f..d7c92fb8 100644 --- a/src/ui/Theme.cpp +++ b/src/ui/Theme.cpp @@ -62,13 +62,16 @@ Theme::Theme(std::string_view theme) sidebarBackground_ = QColor("#233649"); alternateButton_ = QColor("#ccc"); red_ = QColor("#a82353"); + orange_ = QColor("#fcbe05"); } else if (theme == "dark") { sidebarBackground_ = QColor("#2d3139"); alternateButton_ = QColor("#414A59"); red_ = QColor("#a82353"); + orange_ = QColor("#fcc53a"); } else { sidebarBackground_ = p.window().color(); alternateButton_ = p.dark().color(); red_ = QColor("red"); + orange_ = QColor("orange"); } } diff --git a/src/ui/Theme.h b/src/ui/Theme.h index f3e7c287..4fef897d 100644 --- a/src/ui/Theme.h +++ b/src/ui/Theme.h @@ -62,6 +62,7 @@ class Theme : public QPalette Q_PROPERTY(QColor alternateButton READ alternateButton CONSTANT) Q_PROPERTY(QColor separator READ separator CONSTANT) Q_PROPERTY(QColor red READ red CONSTANT) + Q_PROPERTY(QColor orange READ orange CONSTANT) public: Theme() {} explicit Theme(std::string_view theme); @@ -71,7 +72,8 @@ public: QColor alternateButton() const { return alternateButton_; } QColor separator() const { return separator_; } QColor red() const { return red_; } + QColor orange() const { return orange_; } private: - QColor sidebarBackground_, separator_, red_, alternateButton_; + QColor sidebarBackground_, separator_, red_, orange_, alternateButton_; };