From 79f245986b3ef2109118710edb572547e6ba8671 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sun, 8 Aug 2021 01:17:31 +0200 Subject: [PATCH 01/42] Try github forms for bug reports --- .github/ISSUE_TEMPLATE/bug_report.md | 61 ---------- .github/ISSUE_TEMPLATE/bug_report.yaml | 151 +++++++++++++++++++++++++ 2 files changed, 151 insertions(+), 61 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yaml diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index 5419532b..00000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,61 +0,0 @@ ---- -name: Bug report -about: Create a report to help us improve -title: '' -labels: bug -assignees: '' - ---- - -### Describe the bug -A clear and concise description of what the bug is. - -### To Reproduce -Steps to reproduce the behavior: -1. Go to '...' -2. Click on '....' -3. Scroll down to '....' -4. See error - -### Expected behavior -A clear and concise description of what you expected to happen. - -### Screenshots -If applicable, add screenshots to help explain your problem. - -### System: - -- Nheko version: -- Profile used: -- Installation method: -- Operating System: -- Qt version: -- C++ compiler: -- Desktop Environment: - -### Logs - - - - -### Debugger backtrace - diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml new file mode 100644 index 00000000..70094f9e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -0,0 +1,151 @@ +name: Bug Report +description: Create a report to help us improve +#title: "[Bug]: " +labels: [bug] +assignees: +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to fill out this bug report! + Please try to fill out all fields to the best of your ability. + - type: textarea + id: description + attributes: + label: Describe the bug + description: A clear and concise description of what the bug is. + placeholder: Enter your description here. + validations: + required: true + - type: textarea + id: reproduction-steps + attributes: + label: To Reproduce + description: Steps to reproduce the behavior: + placeholder: | + 1. Go to '...' + 2. Click on '....' + 3. Scroll down to '....' + 4. See error + value: | + 1. Go to '...' + 2. Click on '....' + 3. Scroll down to '....' + 4. See error + validations: + required: true + - type: textarea + id: behaviour + attributes: + label: What happened? + description: A clear and concise description of what actually happened. + validations: + required: false + - type: textarea + id: expected-behaviour + attributes: + label: Expected behavior + description: A clear and concise description of what you expected to happen. + validations: + required: false + - type: textarea + id: screenshots + attributes: + label: Screenshots + description: If applicable, add screenshots to help explain your problem. + placeholder: Upload your screenshots here. You can paste them or click on "Attach files". + validations: + required: false + - type: input + id: version + attributes: + label: Version + description: Get the version from the settings menu (bottom left corner) + placeholder: 0.0.1-deafbeef + validations: + required: true + - type: dropdown + id: os + attributes: + label: Operating system + multiple: true + options: + - Linux + - macOS + - Windows + - BSD + - Haiku + - Other + - type: dropdown + id: os + attributes: + label: Installation method + multiple: true + options: + - Flathub + - Flatpak nightly repo or download + - AppImage + - Windows download + - macOS DMG file + - Some repository (AUR, homebrew, distribution repository, PPA, etc) + - Local build + - type: input + id: qt-version + attributes: + label: Qt version + description: What version of Qt does your system use? (If you compiled Nheko yourself.) + placeholder: 5.15.2. + validations: + required: false + - type: input + id: compiler + attributes: + label: C++ compiler + description: What compiler (and version) did you use (if you compiled Nheko yourself)? + placeholder: gcc-9000 + validations: + required: false + - type: input + id: de + attributes: + label: Desktop Environment + description: If you are on Linux, describe your desktop environment. + placeholder: KDE with i3 as the window manager + validations: + required: false + - type: checkboxes + id: profiles + attributes: + label: Did you use profiles? + description: Usually by passing the --profile command line parameter. If you don't know, answer 'no'. + options: + - label: Profiles used? + required: false + - type: textarea + id: logs + attributes: + label: Relevant log output + description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks. + placeholder: | + The log file is located in + Linux: ~/.cache/nheko/ + macOS: ~/Library/Caches/nheko or /Library/Caches/nheko + Windows: C:/Users//AppData/Local/nheko/cache + render: shell + - type: textarea + id: backtrace + attributes: + label: Backtrace + description: If the program crashed send a backtrace. + placeholder: | + You can retrieve a backtrace by building nheko with -DCMAKE_BUILD_TYPE=Debug and running it through gdb or lldb. + + gdb ./build/nheko + + >> run + + ... Make the program crash + + >> bt + render: shell + From 42fdbec43aeba10711ff224fca2ac18c59f8d9c4 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sun, 8 Aug 2021 01:17:58 +0200 Subject: [PATCH 02/42] Fix decryption errors in reply popups --- resources/qml/ForwardCompleter.qml | 1 + resources/qml/ReplyPopup.qml | 1 + 2 files changed, 2 insertions(+) diff --git a/resources/qml/ForwardCompleter.qml b/resources/qml/ForwardCompleter.qml index 525477cd..daf73cec 100644 --- a/resources/qml/ForwardCompleter.qml +++ b/resources/qml/ForwardCompleter.qml @@ -68,6 +68,7 @@ Popup { isOnlyEmoji: modelData.isOnlyEmoji ?? false userId: modelData.userId ?? "" userName: modelData.userName ?? "" + encryptionError: modelData.encryptionError ?? "" } MatrixTextField { diff --git a/resources/qml/ReplyPopup.qml b/resources/qml/ReplyPopup.qml index 54b4f20c..e15b022f 100644 --- a/resources/qml/ReplyPopup.qml +++ b/resources/qml/ReplyPopup.qml @@ -45,6 +45,7 @@ Rectangle { isOnlyEmoji: modelData.isOnlyEmoji ?? false userId: modelData.userId ?? "" userName: modelData.userName ?? "" + encryptionError: modelData.encryptionError ?? "" } ImageButton { From d5c002c737379636c44e5e84145a08eb2e8ab100 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sun, 8 Aug 2021 01:20:10 +0200 Subject: [PATCH 03/42] tabs -> spaces --- .github/ISSUE_TEMPLATE/bug_report.yaml | 58 +++++++++++++------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml index 70094f9e..24822e2e 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yaml +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -8,7 +8,7 @@ body: attributes: value: | Thanks for taking the time to fill out this bug report! - Please try to fill out all fields to the best of your ability. + Please try to fill out all fields to the best of your ability. - type: textarea id: description attributes: @@ -23,15 +23,15 @@ body: label: To Reproduce description: Steps to reproduce the behavior: placeholder: | - 1. Go to '...' - 2. Click on '....' - 3. Scroll down to '....' - 4. See error + 1. Go to '...' + 2. Click on '....' + 3. Scroll down to '....' + 4. See error value: | - 1. Go to '...' - 2. Click on '....' - 3. Scroll down to '....' - 4. See error + 1. Go to '...' + 2. Click on '....' + 3. Scroll down to '....' + 4. See error validations: required: true - type: textarea @@ -53,7 +53,7 @@ body: attributes: label: Screenshots description: If applicable, add screenshots to help explain your problem. - placeholder: Upload your screenshots here. You can paste them or click on "Attach files". + placeholder: Upload your screenshots here. You can paste them or click on "Attach files". validations: required: false - type: input @@ -61,7 +61,7 @@ body: attributes: label: Version description: Get the version from the settings menu (bottom left corner) - placeholder: 0.0.1-deafbeef + placeholder: 0.0.1-deafbeef validations: required: true - type: dropdown @@ -74,8 +74,8 @@ body: - macOS - Windows - BSD - - Haiku - - Other + - Haiku + - Other - type: dropdown id: os attributes: @@ -87,14 +87,14 @@ body: - AppImage - Windows download - macOS DMG file - - Some repository (AUR, homebrew, distribution repository, PPA, etc) - - Local build + - Some repository (AUR, homebrew, distribution repository, PPA, etc) + - Local build - type: input id: qt-version attributes: label: Qt version description: What version of Qt does your system use? (If you compiled Nheko yourself.) - placeholder: 5.15.2. + placeholder: 5.15.2. validations: required: false - type: input @@ -102,7 +102,7 @@ body: attributes: label: C++ compiler description: What compiler (and version) did you use (if you compiled Nheko yourself)? - placeholder: gcc-9000 + placeholder: gcc-9000 validations: required: false - type: input @@ -110,7 +110,7 @@ body: attributes: label: Desktop Environment description: If you are on Linux, describe your desktop environment. - placeholder: KDE with i3 as the window manager + placeholder: KDE with i3 as the window manager validations: required: false - type: checkboxes @@ -126,26 +126,26 @@ body: attributes: label: Relevant log output description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks. - placeholder: | - The log file is located in - Linux: ~/.cache/nheko/ - macOS: ~/Library/Caches/nheko or /Library/Caches/nheko - Windows: C:/Users//AppData/Local/nheko/cache + placeholder: | + The log file is located in + Linux: ~/.cache/nheko/ + macOS: ~/Library/Caches/nheko or /Library/Caches/nheko + Windows: C:/Users//AppData/Local/nheko/cache render: shell - type: textarea id: backtrace attributes: label: Backtrace description: If the program crashed send a backtrace. - placeholder: | - You can retrieve a backtrace by building nheko with -DCMAKE_BUILD_TYPE=Debug and running it through gdb or lldb. + placeholder: | + You can retrieve a backtrace by building nheko with -DCMAKE_BUILD_TYPE=Debug and running it through gdb or lldb. - gdb ./build/nheko + gdb ./build/nheko - >> run + >> run - ... Make the program crash + ... Make the program crash - >> bt + >> bt render: shell From bfa257c4913cb35e64334439d9c3129d534a3f34 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sun, 8 Aug 2021 01:21:29 +0200 Subject: [PATCH 04/42] Don't end line with : --- .github/ISSUE_TEMPLATE/bug_report.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml index 24822e2e..8c22dfc7 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yaml +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -21,7 +21,7 @@ body: id: reproduction-steps attributes: label: To Reproduce - description: Steps to reproduce the behavior: + description: Steps to reproduce the behavior. placeholder: | 1. Go to '...' 2. Click on '....' From fa40b2db0b073feceded4e1d37ad9936f973cd4d Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sun, 8 Aug 2021 01:23:27 +0200 Subject: [PATCH 05/42] issue template must have unique ids --- .github/ISSUE_TEMPLATE/bug_report.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml index 8c22dfc7..871189e7 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yaml +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -2,7 +2,6 @@ name: Bug Report description: Create a report to help us improve #title: "[Bug]: " labels: [bug] -assignees: body: - type: markdown attributes: @@ -77,7 +76,7 @@ body: - Haiku - Other - type: dropdown - id: os + id: install-method attributes: label: Installation method multiple: true From e3b8998635c1fad4fc6fbcb8f03616e433a924d5 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sun, 8 Aug 2021 01:39:44 +0200 Subject: [PATCH 06/42] Use a form for feature requests --- .github/ISSUE_TEMPLATE/feature_request.md | 20 --------- .github/ISSUE_TEMPLATE/feature_request.yaml | 50 +++++++++++++++++++++ 2 files changed, 50 insertions(+), 20 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.yaml diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index 11fc491e..00000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -name: Feature request -about: Suggest an idea for this project -title: '' -labels: enhancement -assignees: '' - ---- - -**Is your feature request related to a problem? Please describe.** -A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] - -**Describe the solution you'd like** -A clear and concise description of what you want to happen. - -**Describe alternatives you've considered** -A clear and concise description of any alternative solutions or features you've considered. - -**Additional context** -Add any other context or screenshots about the feature request here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.yaml b/.github/ISSUE_TEMPLATE/feature_request.yaml new file mode 100644 index 00000000..1a6c103b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yaml @@ -0,0 +1,50 @@ +name: Feature request +description: Suggest an idea for this project +title: "" +labels: [enhancement] +body: + - type: markdown + attributes: + value: | + Please verify that there is no feature request for this already! + - type: textarea + id: problem + attributes: + label: The Problem + description: Is your feature request related to a problem? Please describe. + placeholder: "A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]" + validations: + required: true + - type: textarea + id: solution + attributes: + label: The Solution + description: Describe the solution you'd like + placeholder: A clear and concise description of what you want to happen. + validations: + required: true + - type: textarea + id: alternatives + attributes: + label: Alternatives + description: Describe alternatives you've considered. + placeholder: A clear and concise description of any alternative solutions or features you've considered. + validations: + required: false + - type: textarea + id: context + attributes: + label: Additional context + description: Describe alternatives you've considered. + placeholder: Add any other context or screenshots about the feature request here. + validations: + required: false + - type: checkboxes + id: version-check + attributes: + label: Happens in the latest version + description: Please verified, that this is still missing in the latest version. + options: + - label: Yes, this feature is still missing. + required: true + From d739ee01dedba7afc448ee37fcf0b0c0972d6a6b Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sun, 8 Aug 2021 01:41:07 +0200 Subject: [PATCH 07/42] Don't set a title in the template --- .github/ISSUE_TEMPLATE/feature_request.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/feature_request.yaml b/.github/ISSUE_TEMPLATE/feature_request.yaml index 1a6c103b..5b6fe7a1 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yaml +++ b/.github/ISSUE_TEMPLATE/feature_request.yaml @@ -1,6 +1,5 @@ name: Feature request description: Suggest an idea for this project -title: "" labels: [enhancement] body: - type: markdown From 35b36e24c18bde404d713e88170d93a0901a4476 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sun, 8 Aug 2021 01:41:59 +0200 Subject: [PATCH 08/42] Fix typo in template --- .github/ISSUE_TEMPLATE/feature_request.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/feature_request.yaml b/.github/ISSUE_TEMPLATE/feature_request.yaml index 5b6fe7a1..a07eff86 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yaml +++ b/.github/ISSUE_TEMPLATE/feature_request.yaml @@ -42,7 +42,7 @@ body: id: version-check attributes: label: Happens in the latest version - description: Please verified, that this is still missing in the latest version. + description: Please verify that this is still missing in the latest version. options: - label: Yes, this feature is still missing. required: true From d0947fd7c461391d88c3d3a9d274c6b8a7aca27b Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sun, 8 Aug 2021 11:34:15 +0200 Subject: [PATCH 09/42] increase settings window size --- resources/qml/RoomSettings.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/qml/RoomSettings.qml b/resources/qml/RoomSettings.qml index 69cf427c..491a336f 100644 --- a/resources/qml/RoomSettings.qml +++ b/resources/qml/RoomSettings.qml @@ -15,8 +15,8 @@ ApplicationWindow { property var roomSettings - minimumWidth: 420 - minimumHeight: 650 + minimumWidth: 450 + minimumHeight: 680 palette: Nheko.colors color: Nheko.colors.window modality: Qt.NonModal From 8784156da5abf82c890a4a53c5e03940b14617d3 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sun, 8 Aug 2021 11:35:09 +0200 Subject: [PATCH 10/42] Fix trying to delete unsupported events --- src/Cache_p.h | 37 +++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/src/Cache_p.h b/src/Cache_p.h index 30c365a6..51fe9978 100644 --- a/src/Cache_p.h +++ b/src/Cache_p.h @@ -414,24 +414,25 @@ private: if constexpr (isStateEvent_) { eventsDb.put(txn, e.event_id, json(e).dump()); - if (std::is_same_v< - std::remove_cv_t>, - StateEvent>) { - if (e.type == EventType::RoomMember) - membersdb.del(txn, e.state_key, ""); - else if (e.state_key.empty()) - statesdb.del(txn, to_string(e.type)); - else - stateskeydb.del( - txn, - to_string(e.type), - json::object({ - {"key", e.state_key}, - {"id", e.event_id}, - }) - .dump()); - } else if (e.type != EventType::Unsupported) { - if (e.state_key.empty()) + if (e.type != EventType::Unsupported) { + if (std::is_same_v< + std::remove_cv_t< + std::remove_reference_t>, + StateEvent>) { + if (e.type == EventType::RoomMember) + membersdb.del(txn, e.state_key, ""); + else if (e.state_key.empty()) + statesdb.del(txn, to_string(e.type)); + else + stateskeydb.del( + txn, + to_string(e.type), + json::object({ + {"key", e.state_key}, + {"id", e.event_id}, + }) + .dump()); + } else if (e.state_key.empty()) statesdb.put( txn, to_string(e.type), json(e).dump()); else From ad57a336dc5885ad11965fd72869474bd7f30496 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sun, 8 Aug 2021 18:37:40 +0200 Subject: [PATCH 11/42] Breaking: Change secret names and fix bug when storing secrets --- src/Cache.cpp | 56 +++++++++++++++++++++++++++++++++------------------ src/Olm.cpp | 2 ++ 2 files changed, 38 insertions(+), 20 deletions(-) diff --git a/src/Cache.cpp b/src/Cache.cpp index ee991dc2..7996c017 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -720,20 +720,34 @@ Cache::storeSecret(const std::string name, const std::string secret) { auto settings = UserSettings::instance(); auto job = new QKeychain::WritePasswordJob(QCoreApplication::applicationName()); + job->setAutoDelete(true); job->setInsecureFallback(true); - job->setKey("matrix." + - QString(QCryptographicHash::hash(settings->profile().toUtf8(), - QCryptographicHash::Sha256)) + - "." + name.c_str()); + + // job->setSettings(new QSettings(job)); + job->setKey( + "matrix." + + QString(QCryptographicHash::hash(settings->profile().toUtf8(), QCryptographicHash::Sha256) + .toBase64()) + + "." + QString::fromStdString(name)); + job->setTextData(QString::fromStdString(secret)); - QObject::connect(job, &QKeychain::Job::finished, job, [name, this](QKeychain::Job *job) { - if (job->error()) { - nhlog::db()->warn( - "Storing secret '{}' failed: {}", name, job->errorString().toStdString()); - } else { - emit secretChanged(name); - } - }); + QObject::connect( + job, + &QKeychain::WritePasswordJob::finished, + this, + [name, this](QKeychain::Job *job) { + if (job->error()) { + nhlog::db()->warn("Storing secret '{}' failed: {}", + name, + job->errorString().toStdString()); + } else { + // if we emit the signal directly, qtkeychain breaks and won't execute new + // jobs. You can't start a job from the finish signal of a job. + QTimer::singleShot(100, [this, name] { emit secretChanged(name); }); + nhlog::db()->info("Storing secret '{}' successful", name); + } + }, + Qt::ConnectionType::DirectConnection); job->start(); } @@ -744,10 +758,11 @@ Cache::deleteSecret(const std::string name) QKeychain::DeletePasswordJob job(QCoreApplication::applicationName()); job.setAutoDelete(false); job.setInsecureFallback(true); - job.setKey("matrix." + - QString(QCryptographicHash::hash(settings->profile().toUtf8(), - QCryptographicHash::Sha256)) + - "." + name.c_str()); + job.setKey( + "matrix." + + QString(QCryptographicHash::hash(settings->profile().toUtf8(), QCryptographicHash::Sha256) + .toBase64()) + + "." + QString::fromStdString(name)); // FIXME(Nico): Nested event loops are dangerous. Some other slots may resume in the mean // time! QEventLoop loop; @@ -765,10 +780,11 @@ Cache::secret(const std::string name) QKeychain::ReadPasswordJob job(QCoreApplication::applicationName()); job.setAutoDelete(false); job.setInsecureFallback(true); - job.setKey("matrix." + - QString(QCryptographicHash::hash(settings->profile().toUtf8(), - QCryptographicHash::Sha256)) + - "." + name.c_str()); + job.setKey( + "matrix." + + QString(QCryptographicHash::hash(settings->profile().toUtf8(), QCryptographicHash::Sha256) + .toBase64()) + + "." + QString::fromStdString(name)); // FIXME(Nico): Nested event loops are dangerous. Some other slots may resume in the mean // time! QEventLoop loop; diff --git a/src/Olm.cpp b/src/Olm.cpp index e4ab0aa1..40c87f9f 100644 --- a/src/Olm.cpp +++ b/src/Olm.cpp @@ -425,6 +425,8 @@ handle_olm_message(const OlmMessage &msg, const UserKeyCache &otherUserDeviceKey } }); + nhlog::crypto()->info("Storing secret {}", + secret_name->second); cache::client()->storeSecret(secret_name->second, e->content.secret); From 71290e208d2decab6042ce40b372e9aabfba7cef Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sun, 8 Aug 2021 18:57:38 +0200 Subject: [PATCH 12/42] Enable insecure fallback for secret storage --- src/Cache.cpp | 9 ++++++++- src/UserSettingsPage.cpp | 3 --- src/UserSettingsPage.h | 5 +++++ 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/Cache.cpp b/src/Cache.cpp index 7996c017..eb9fb028 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -722,8 +722,8 @@ Cache::storeSecret(const std::string name, const std::string secret) auto job = new QKeychain::WritePasswordJob(QCoreApplication::applicationName()); job->setAutoDelete(true); job->setInsecureFallback(true); + job->setSettings(UserSettings::instance()->qsettings()); - // job->setSettings(new QSettings(job)); job->setKey( "matrix." + QString(QCryptographicHash::hash(settings->profile().toUtf8(), QCryptographicHash::Sha256) @@ -731,6 +731,7 @@ Cache::storeSecret(const std::string name, const std::string secret) "." + QString::fromStdString(name)); job->setTextData(QString::fromStdString(secret)); + QObject::connect( job, &QKeychain::WritePasswordJob::finished, @@ -758,11 +759,14 @@ Cache::deleteSecret(const std::string name) QKeychain::DeletePasswordJob job(QCoreApplication::applicationName()); job.setAutoDelete(false); job.setInsecureFallback(true); + job.setSettings(UserSettings::instance()->qsettings()); + job.setKey( "matrix." + QString(QCryptographicHash::hash(settings->profile().toUtf8(), QCryptographicHash::Sha256) .toBase64()) + "." + QString::fromStdString(name)); + // FIXME(Nico): Nested event loops are dangerous. Some other slots may resume in the mean // time! QEventLoop loop; @@ -780,11 +784,14 @@ Cache::secret(const std::string name) QKeychain::ReadPasswordJob job(QCoreApplication::applicationName()); job.setAutoDelete(false); job.setInsecureFallback(true); + job.setSettings(UserSettings::instance()->qsettings()); + job.setKey( "matrix." + QString(QCryptographicHash::hash(settings->profile().toUtf8(), QCryptographicHash::Sha256) .toBase64()) + "." + QString::fromStdString(name)); + // FIXME(Nico): Nested event loops are dangerous. Some other slots may resume in the mean // time! QEventLoop loop; diff --git a/src/UserSettingsPage.cpp b/src/UserSettingsPage.cpp index ab6ac492..f67c5e2d 100644 --- a/src/UserSettingsPage.cpp +++ b/src/UserSettingsPage.cpp @@ -19,7 +19,6 @@ #include #include #include -#include #include #include #include @@ -63,7 +62,6 @@ UserSettings::initialize(std::optional profile) void UserSettings::load(std::optional profile) { - QSettings settings; tray_ = settings.value("user/window/tray", false).toBool(); startInTray_ = settings.value("user/window/start_in_tray", false).toBool(); @@ -601,7 +599,6 @@ UserSettings::applyTheme() void UserSettings::save() { - QSettings settings; settings.beginGroup("user"); settings.beginGroup("window"); diff --git a/src/UserSettingsPage.h b/src/UserSettingsPage.h index 096aab81..84940e47 100644 --- a/src/UserSettingsPage.h +++ b/src/UserSettingsPage.h @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -107,6 +108,8 @@ public: static QSharedPointer instance(); static void initialize(std::optional profile); + QSettings *qsettings() { return &settings; } + enum class Presence { AutomaticPresence, @@ -316,6 +319,8 @@ private: QString homeserver_; QStringList hiddenTags_; + QSettings settings; + static QSharedPointer instance_; }; From 89840b9e0bc010613a4b8f1a346be8e6a9355630 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sun, 8 Aug 2021 21:18:05 +0200 Subject: [PATCH 13/42] AttrIbution --- resources/qml/dialogs/ImagePackEditorDialog.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/qml/dialogs/ImagePackEditorDialog.qml b/resources/qml/dialogs/ImagePackEditorDialog.qml index b839c9e3..b0f431f6 100644 --- a/resources/qml/dialogs/ImagePackEditorDialog.qml +++ b/resources/qml/dialogs/ImagePackEditorDialog.qml @@ -171,7 +171,7 @@ ApplicationWindow { } MatrixText { - text: qsTr("Attrbution") + text: qsTr("Attribution") } MatrixTextField { From 7f633a02981c9c593b0c6218b95b3b2e9d9531d2 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Mon, 9 Aug 2021 20:52:54 +0200 Subject: [PATCH 14/42] Add rate limiting to unknown device list path --- src/Olm.cpp | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/src/Olm.cpp b/src/Olm.cpp index 40c87f9f..2c9ac5a3 100644 --- a/src/Olm.cpp +++ b/src/Olm.cpp @@ -1112,6 +1112,8 @@ send_encrypted_to_device_messages(const std::map, qint64> rateLimit; + nlohmann::json ev_json = std::visit([](const auto &e) { return json(e); }, event); std::map> keysToQuery; @@ -1164,7 +1166,6 @@ send_encrypted_to_device_messages(const std::map, qint64> rateLimit; auto currentTime = QDateTime::currentSecsSinceEpoch(); if (rateLimit.value(QPair(user, device)) + 60 * 60 * 10 < currentTime) { @@ -1320,7 +1321,8 @@ send_encrypted_to_device_messages(const std::mapclaim_keys(claims, BindPks(pks)); + if (!claims.one_time_keys.empty()) + http::client()->claim_keys(claims, BindPks(pks)); if (!keysToQuery.empty()) { mtx::requests::QueryKeys req; @@ -1397,9 +1399,25 @@ send_encrypted_to_device_messages(const std::mapwarn( + "Not creating new session with {}:{} " + "because of rate limit", + user.first, + device_id.get()); + continue; + } nhlog::net()->info("{}", device_id.get()); nhlog::net()->info(" curve25519 {}", pks.curve25519); @@ -1407,7 +1425,8 @@ send_encrypted_to_device_messages(const std::mapclaim_keys(claim_keys, BindPks(deviceKeys)); + if (!claim_keys.one_time_keys.empty()) + http::client()->claim_keys(claim_keys, BindPks(deviceKeys)); }); } } From db4a32e6bdd8655451e4694bdb8cc4d71cc7985a Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Mon, 9 Aug 2021 20:58:35 +0200 Subject: [PATCH 15/42] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d442818d..23d34446 100644 --- a/README.md +++ b/README.md @@ -213,7 +213,7 @@ sudo emerge -a ">=dev-qt/qtgui-5.10.0" media-libs/fontconfig dev-libs/qtkeychain ```bash # Build requirements + qml modules needed at runtime (you may not need all of them, but the following seem to work according to reports): -sudo apt install g++ cmake zlib1g-dev libssl-dev qt{base,declarative,tools,multimedia,quickcontrols2-}5-dev libqt5svg5-dev libboost-system-dev libboost-thread-dev libboost-iostreams-dev libolm-dev liblmdb++-dev libcmark-dev nlohmann-json3-dev libspdlog-dev libgtest-dev qml-module-qt{gstreamer,multimedia,quick-extras,-labs-settings,-labs-platform,graphicaleffects,quick-controls2} qt5keychain-dev +sudo apt install g++ cmake zlib1g-dev libssl-dev qt{base,declarative,tools,multimedia,quickcontrols2-}5-dev libqt5svg5-dev libboost-system-dev libboost-thread-dev libboost-iostreams-dev libolm-dev liblmdb++-dev libcmark-dev nlohmann-json3-dev libspdlog-dev libgtest-dev qml-module-qt{gstreamer,multimedia,quick-extras,-labs-settings,-labs-platform,graphicaleffects,quick-controls2} qt5keychain-dev libevent-dev ``` This will install all dependencies, except for tweeny (use bundled tweeny) and mtxclient (needs to be build separately). From a309a4bce32ff36029ab93f745bfcaca94f34362 Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Mon, 9 Aug 2021 21:12:29 +0200 Subject: [PATCH 16/42] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 23d34446..1cf5d705 100644 --- a/README.md +++ b/README.md @@ -213,7 +213,7 @@ sudo emerge -a ">=dev-qt/qtgui-5.10.0" media-libs/fontconfig dev-libs/qtkeychain ```bash # Build requirements + qml modules needed at runtime (you may not need all of them, but the following seem to work according to reports): -sudo apt install g++ cmake zlib1g-dev libssl-dev qt{base,declarative,tools,multimedia,quickcontrols2-}5-dev libqt5svg5-dev libboost-system-dev libboost-thread-dev libboost-iostreams-dev libolm-dev liblmdb++-dev libcmark-dev nlohmann-json3-dev libspdlog-dev libgtest-dev qml-module-qt{gstreamer,multimedia,quick-extras,-labs-settings,-labs-platform,graphicaleffects,quick-controls2} qt5keychain-dev libevent-dev +sudo apt install g++ cmake zlib1g-dev libssl-dev qt{base,declarative,tools,multimedia,quickcontrols2-}5-dev libqt5svg5-dev libboost-system-dev libboost-thread-dev libboost-iostreams-dev libolm-dev liblmdb++-dev libcmark-dev nlohmann-json3-dev libspdlog-dev libgtest-dev qml-module-qt{gstreamer,multimedia,quick-extras,-labs-settings,-labs-platform,graphicaleffects,quick-controls2} qt5keychain-dev libevent-dev libcurl-dev ``` This will install all dependencies, except for tweeny (use bundled tweeny) and mtxclient (needs to be build separately). From cc6693af8aa97c113b1abbc05fe5a18b09759bac Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Mon, 9 Aug 2021 19:24:46 -0400 Subject: [PATCH 17/42] Close currently open room when it is tapped again --- resources/qml/RoomList.qml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/resources/qml/RoomList.qml b/resources/qml/RoomList.qml index 98532606..cee4b30e 100644 --- a/resources/qml/RoomList.qml +++ b/resources/qml/RoomList.qml @@ -190,7 +190,12 @@ Page { TapHandler { margin: -Nheko.paddingSmall - onSingleTapped: Rooms.setCurrentRoom(roomId) + onSingleTapped: { + if (!Rooms.currentRoom || Rooms.currentRoom.roomId !== roomId) + Rooms.setCurrentRoom(roomId); + else + Rooms.resetCurrentRoom(); + } onLongPressed: { if (!isInvite) roomContextMenu.show(roomId, tags); From dbea031a8601dae57a635f28bceffb3b4134ea53 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Wed, 11 Aug 2021 00:21:02 +0200 Subject: [PATCH 18/42] Fix potential crash when trying to read room info too early --- src/Cache.cpp | 38 ++++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/src/Cache.cpp b/src/Cache.cpp index eb9fb028..b79a3b47 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -1586,26 +1586,32 @@ Cache::roomsWithStateUpdates(const mtx::responses::Sync &res) RoomInfo Cache::singleRoomInfo(const std::string &room_id) { - auto txn = ro_txn(env_); - auto statesdb = getStatesDb(txn, room_id); + auto txn = ro_txn(env_); - std::string_view data; + try { + auto statesdb = getStatesDb(txn, room_id); - // Check if the room is joined. - if (roomsDb_.get(txn, room_id, data)) { - try { - RoomInfo tmp = json::parse(data); - tmp.member_count = getMembersDb(txn, room_id).size(txn); - tmp.join_rule = getRoomJoinRule(txn, statesdb); - tmp.guest_access = getRoomGuestAccess(txn, statesdb); + std::string_view data; - return tmp; - } catch (const json::exception &e) { - nhlog::db()->warn("failed to parse room info: room_id ({}), {}: {}", - room_id, - std::string(data.data(), data.size()), - e.what()); + // Check if the room is joined. + if (roomsDb_.get(txn, room_id, data)) { + try { + RoomInfo tmp = json::parse(data); + tmp.member_count = getMembersDb(txn, room_id).size(txn); + tmp.join_rule = getRoomJoinRule(txn, statesdb); + tmp.guest_access = getRoomGuestAccess(txn, statesdb); + + return tmp; + } catch (const json::exception &e) { + nhlog::db()->warn("failed to parse room info: room_id ({}), {}: {}", + room_id, + std::string(data.data(), data.size()), + e.what()); + } } + } catch (const lmdb::error &e) { + nhlog::db()->warn( + "failed to read room info from db: room_id ({}), {}", room_id, e.what()); } return RoomInfo(); From 5e2526dac4e264914b0c2d999f99b9ffc971d5b7 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Wed, 11 Aug 2021 00:39:01 +0200 Subject: [PATCH 19/42] fix sticker packs being uneditable in some rooms --- src/timeline/TimelineModel.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp index 99e00a67..513f08bc 100644 --- a/src/timeline/TimelineModel.cpp +++ b/src/timeline/TimelineModel.cpp @@ -310,7 +310,7 @@ qml_mtx_events::fromRoomEventType(qml_mtx_events::EventType t) return mtx::events::EventType::RoomMessage; //! m.image_pack, currently im.ponies.room_emotes case qml_mtx_events::ImagePackInRoom: - return mtx::events::EventType::ImagePackRooms; + return mtx::events::EventType::ImagePackInRoom; //! m.image_pack, currently im.ponies.user_emotes case qml_mtx_events::ImagePackInAccountData: return mtx::events::EventType::ImagePackInAccountData; From a88c68c0a85ea7e4807d464543c720ee31f3013b Mon Sep 17 00:00:00 2001 From: kirp Date: Wed, 11 Aug 2021 01:39:27 +0300 Subject: [PATCH 20/42] add shortcut Escape button for closing images issues 672 --- src/dialogs/ImageOverlay.cpp | 2 ++ src/dialogs/ImageOverlay.h | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/dialogs/ImageOverlay.cpp b/src/dialogs/ImageOverlay.cpp index f38b29f5..0a3777f0 100644 --- a/src/dialogs/ImageOverlay.cpp +++ b/src/dialogs/ImageOverlay.cpp @@ -31,6 +31,8 @@ ImageOverlay::ImageOverlay(QPixmap image, QWidget *parent) connect(this, SIGNAL(closing()), this, SLOT(close())); + close_shortcut_ = new QShortcut(QKeySequence(Qt::Key_Escape), this, SLOT(close())); + raise(); } diff --git a/src/dialogs/ImageOverlay.h b/src/dialogs/ImageOverlay.h index 93b6afdc..9d4187bf 100644 --- a/src/dialogs/ImageOverlay.h +++ b/src/dialogs/ImageOverlay.h @@ -8,6 +8,7 @@ #include #include #include +#include namespace dialogs { @@ -32,5 +33,6 @@ private: QRect content_; QRect close_button_; QRect save_button_; + QShortcut *close_shortcut_; }; } // dialogs From c7295d7fd1a4732592c8fc9d2ffd7fd6ebba40eb Mon Sep 17 00:00:00 2001 From: kirp Date: Wed, 11 Aug 2021 02:26:26 +0300 Subject: [PATCH 21/42] change slot close, to signal closing --- src/dialogs/ImageOverlay.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/dialogs/ImageOverlay.cpp b/src/dialogs/ImageOverlay.cpp index 0a3777f0..50cc5564 100644 --- a/src/dialogs/ImageOverlay.cpp +++ b/src/dialogs/ImageOverlay.cpp @@ -28,10 +28,11 @@ ImageOverlay::ImageOverlay(QPixmap image, QWidget *parent) setAttribute(Qt::WA_TranslucentBackground, true); setAttribute(Qt::WA_DeleteOnClose, true); setWindowState(Qt::WindowFullScreen); + close_shortcut_ = new QShortcut(QKeySequence(Qt::Key_Escape), this); - connect(this, SIGNAL(closing()), this, SLOT(close())); + connect(close_shortcut_, &QShortcut::activated, this, &ImageOverlay::closing); + connect(this, &ImageOverlay::closing, this, &ImageOverlay::close); - close_shortcut_ = new QShortcut(QKeySequence(Qt::Key_Escape), this, SLOT(close())); raise(); } From 5335e043e76e13b8b101eee7be1f39e444548543 Mon Sep 17 00:00:00 2001 From: kirp Date: Wed, 11 Aug 2021 02:29:36 +0300 Subject: [PATCH 22/42] linting --- src/dialogs/ImageOverlay.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/dialogs/ImageOverlay.cpp b/src/dialogs/ImageOverlay.cpp index 50cc5564..12813d57 100644 --- a/src/dialogs/ImageOverlay.cpp +++ b/src/dialogs/ImageOverlay.cpp @@ -30,10 +30,9 @@ ImageOverlay::ImageOverlay(QPixmap image, QWidget *parent) setWindowState(Qt::WindowFullScreen); close_shortcut_ = new QShortcut(QKeySequence(Qt::Key_Escape), this); - connect(close_shortcut_, &QShortcut::activated, this, &ImageOverlay::closing); + connect(close_shortcut_, &QShortcut::activated, this, &ImageOverlay::closing); connect(this, &ImageOverlay::closing, this, &ImageOverlay::close); - raise(); } From 02b217d009726c68d67637df89494fa7521afae9 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Wed, 11 Aug 2021 01:30:55 +0200 Subject: [PATCH 23/42] Remove some unused headers --- src/ChatPage.cpp | 2 -- src/ChatPage.h | 2 -- 2 files changed, 4 deletions(-) diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp index 42e3bc7b..8a0e891b 100644 --- a/src/ChatPage.cpp +++ b/src/ChatPage.cpp @@ -4,11 +4,9 @@ // SPDX-License-Identifier: GPL-3.0-or-later #include -#include #include #include #include -#include #include diff --git a/src/ChatPage.h b/src/ChatPage.h index 751e7074..c90b87f5 100644 --- a/src/ChatPage.h +++ b/src/ChatPage.h @@ -17,10 +17,8 @@ #include #include -#include #include #include -#include #include #include #include From 308207c28927bf69246dccff540e353cacb41d17 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Tue, 10 Aug 2021 22:16:22 -0400 Subject: [PATCH 24/42] Remove unnecessary debugging log --- resources/qml/RoomList.qml | 1 - 1 file changed, 1 deletion(-) diff --git a/resources/qml/RoomList.qml b/resources/qml/RoomList.qml index cee4b30e..66cbac5a 100644 --- a/resources/qml/RoomList.qml +++ b/resources/qml/RoomList.qml @@ -34,7 +34,6 @@ Page { Connections { function onCurrentRoomChanged() { roomlist.positionViewAtIndex(Rooms.roomidToIndex(Rooms.currentRoom.roomId), ListView.Contain); - console.log("Test" + Rooms.currentRoom.roomId + " " + Rooms.roomidToIndex(Rooms.currentRoom.roomId)); } target: Rooms From 9a0c1c27edb2bcd0ab8e6fb4b59039c66218b935 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Tue, 10 Aug 2021 22:16:27 -0400 Subject: [PATCH 25/42] Remove warnings on closing room --- resources/qml/RoomList.qml | 3 ++- resources/qml/TimelineView.qml | 8 ++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/resources/qml/RoomList.qml b/resources/qml/RoomList.qml index 66cbac5a..576383e2 100644 --- a/resources/qml/RoomList.qml +++ b/resources/qml/RoomList.qml @@ -33,7 +33,8 @@ Page { Connections { function onCurrentRoomChanged() { - roomlist.positionViewAtIndex(Rooms.roomidToIndex(Rooms.currentRoom.roomId), ListView.Contain); + if (Rooms.currentRoom) + roomlist.positionViewAtIndex(Rooms.roomidToIndex(Rooms.currentRoom.roomId), ListView.Contain); } target: Rooms diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml index 6fc9d51b..5e99ee5c 100644 --- a/resources/qml/TimelineView.qml +++ b/resources/qml/TimelineView.qml @@ -85,9 +85,13 @@ Item { target: timelineView } - MessageView { + Loader { + active: room || roomPreview Layout.fillWidth: true - implicitHeight: msgView.height - typingIndicator.height + sourceComponent: MessageView { + implicitHeight: msgView.height - typingIndicator.height + } + } Loader { From b56a1be0bd3085680fd572bb31f17007bce53494 Mon Sep 17 00:00:00 2001 From: Callum Brown Date: Wed, 11 Aug 2021 15:07:06 +0100 Subject: [PATCH 26/42] Support token authenticated registration Using a dialog after username and password have been provided. --- CMakeLists.txt | 2 + src/RegisterPage.cpp | 18 +++++++++ src/dialogs/TokenRegistration.cpp | 62 +++++++++++++++++++++++++++++++ src/dialogs/TokenRegistration.h | 31 ++++++++++++++++ 4 files changed, 113 insertions(+) create mode 100644 src/dialogs/TokenRegistration.cpp create mode 100644 src/dialogs/TokenRegistration.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 049ed8a3..a146931e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -286,6 +286,7 @@ set(SRC_FILES src/dialogs/Logout.cpp src/dialogs/PreviewUploadOverlay.cpp src/dialogs/ReCaptcha.cpp + src/dialogs/TokenRegistration.cpp # Emoji src/emoji/EmojiModel.cpp @@ -497,6 +498,7 @@ qt5_wrap_cpp(MOC_HEADERS src/dialogs/Logout.h src/dialogs/PreviewUploadOverlay.h src/dialogs/ReCaptcha.h + src/dialogs/TokenRegistration.h # Emoji src/emoji/EmojiModel.h diff --git a/src/RegisterPage.cpp b/src/RegisterPage.cpp index bae24df0..1eac85fd 100644 --- a/src/RegisterPage.cpp +++ b/src/RegisterPage.cpp @@ -25,6 +25,7 @@ #include "dialogs/FallbackAuth.h" #include "dialogs/ReCaptcha.h" +#include "dialogs/TokenRegistration.h" Q_DECLARE_METATYPE(mtx::user_interactive::Unauthorized) Q_DECLARE_METATYPE(mtx::user_interactive::Auth) @@ -481,6 +482,23 @@ RegisterPage::doUIA(const mtx::user_interactive::Unauthorized &unauthorized) doRegistrationWithAuth( mtx::user_interactive::Auth{session, mtx::user_interactive::auth::Dummy{}}); + } else if (current_stage == mtx::user_interactive::auth_types::registration_token) { + auto dialog = new dialogs::TokenRegistration(this); + + connect(dialog, + &dialogs::TokenRegistration::confirmation, + this, + [this, session, dialog](std::string token) { + dialog->close(); + dialog->deleteLater(); + emit registrationWithAuth(mtx::user_interactive::Auth{ + session, mtx::user_interactive::auth::RegistrationToken{token}}); + }); + + connect( + dialog, &dialogs::TokenRegistration::cancel, this, &RegisterPage::errorOccurred); + + dialog->show(); } else { // use fallback auto dialog = new dialogs::FallbackAuth( diff --git a/src/dialogs/TokenRegistration.cpp b/src/dialogs/TokenRegistration.cpp new file mode 100644 index 00000000..2333dcb1 --- /dev/null +++ b/src/dialogs/TokenRegistration.cpp @@ -0,0 +1,62 @@ +// SPDX-FileCopyrightText: 2021 Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include +#include +#include + +#include "dialogs/TokenRegistration.h" + +#include "Config.h" +#include "MatrixClient.h" +#include "ui/TextField.h" + +using namespace dialogs; + +TokenRegistration::TokenRegistration(QWidget *parent) + : QWidget(parent) +{ + setAutoFillBackground(true); + setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint); + setWindowModality(Qt::WindowModal); + setAttribute(Qt::WA_DeleteOnClose, true); + + auto layout = new QVBoxLayout(this); + layout->setSpacing(conf::modals::WIDGET_SPACING); + layout->setMargin(conf::modals::WIDGET_MARGIN); + + auto buttonLayout = new QHBoxLayout(); + buttonLayout->setSpacing(8); + buttonLayout->setMargin(0); + + cancelBtn_ = new QPushButton(tr("Cancel"), this); + confirmBtn_ = new QPushButton(tr("Confirm"), this); + confirmBtn_->setDefault(true); + + buttonLayout->addStretch(1); + buttonLayout->addWidget(cancelBtn_); + buttonLayout->addWidget(confirmBtn_); + + tokenInput_ = new TextField(this); + tokenInput_->setLabel(tr("Registration token")); + + QFont font; + font.setPointSizeF(font.pointSizeF() * conf::modals::LABEL_MEDIUM_SIZE_RATIO); + + auto label = new QLabel(tr("Please enter a valid registration token."), this); + label->setFont(font); + + layout->addWidget(label); + layout->addWidget(tokenInput_); + layout->addLayout(buttonLayout); + + connect(confirmBtn_, &QPushButton::clicked, this, [this]() { + emit confirmation(tokenInput_->text().toStdString()); + emit close(); + }); + connect(cancelBtn_, &QPushButton::clicked, this, [this]() { + emit cancel(); + emit close(); + }); +} diff --git a/src/dialogs/TokenRegistration.h b/src/dialogs/TokenRegistration.h new file mode 100644 index 00000000..562dcb7b --- /dev/null +++ b/src/dialogs/TokenRegistration.h @@ -0,0 +1,31 @@ +// SPDX-FileCopyrightText: 2021 Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include + +class QPushButton; +class QLabel; +class TextField; + +namespace dialogs { + +class TokenRegistration : public QWidget +{ + Q_OBJECT + +public: + TokenRegistration(QWidget *parent = nullptr); + +signals: + void confirmation(std::string token); + void cancel(); + +private: + QPushButton *confirmBtn_; + QPushButton *cancelBtn_; + TextField *tokenInput_; +}; +} // dialogs From 6b445c33b6a17e74dd6bd0848f478c9adad25370 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Wed, 11 Aug 2021 22:09:00 +0200 Subject: [PATCH 27/42] Fix long tcp timeouts relates to #658 --- CMakeLists.txt | 4 ++-- io.github.NhekoReborn.Nheko.yaml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 049ed8a3..79aad50d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -381,7 +381,7 @@ if(USE_BUNDLED_MTXCLIENT) FetchContent_Declare( MatrixClient GIT_REPOSITORY https://github.com/Nheko-Reborn/mtxclient.git - GIT_TAG bcf363cb5e6c423f40c96123e227bc8c5f6d6f80 + GIT_TAG 2e6b834f3fbd12b23ddb5219a5a4eacb7e11e0a3 ) set(BUILD_LIB_EXAMPLES OFF CACHE INTERNAL "") set(BUILD_LIB_TESTS OFF CACHE INTERNAL "") @@ -676,7 +676,7 @@ if(USE_BUNDLED_COEURL) FetchContent_Declare( coeurl GIT_REPOSITORY https://nheko.im/Nheko-Reborn/coeurl.git - GIT_TAG e9010d1ce14e7163d1cb5407ed27b23303781796 + GIT_TAG e741ced9b2bcf14069e17d466aaef58b277b481d ) FetchContent_MakeAvailable(coeurl) target_link_libraries(nheko PUBLIC coeurl::coeurl) diff --git a/io.github.NhekoReborn.Nheko.yaml b/io.github.NhekoReborn.Nheko.yaml index a0e57b09..17db66d2 100644 --- a/io.github.NhekoReborn.Nheko.yaml +++ b/io.github.NhekoReborn.Nheko.yaml @@ -152,7 +152,7 @@ modules: - -Ddefault_library=static name: coeurl sources: - - commit: 417821a07cfe4429b08a2efed5e480a498087afd + - commit: e741ced9b2bcf14069e17d466aaef58b277b481d type: git url: https://nheko.im/nheko-reborn/coeurl.git - config-opts: @@ -163,7 +163,7 @@ modules: buildsystem: cmake-ninja name: mtxclient sources: - - commit: bcf363cb5e6c423f40c96123e227bc8c5f6d6f80 + - commit: 2e6b834f3fbd12b23ddb5219a5a4eacb7e11e0a3 type: git url: https://github.com/Nheko-Reborn/mtxclient.git - config-opts: From 3794e13745030b59d92f3b988c1ac3acd0d9f553 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Wed, 11 Aug 2021 23:51:10 +0200 Subject: [PATCH 28/42] Update coeurl --- CMakeLists.txt | 2 +- io.github.NhekoReborn.Nheko.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 79aad50d..5c4b7d56 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -676,7 +676,7 @@ if(USE_BUNDLED_COEURL) FetchContent_Declare( coeurl GIT_REPOSITORY https://nheko.im/Nheko-Reborn/coeurl.git - GIT_TAG e741ced9b2bcf14069e17d466aaef58b277b481d + GIT_TAG ab7e5b31cb4945ad2c7f276d1f93ce07773011ba ) FetchContent_MakeAvailable(coeurl) target_link_libraries(nheko PUBLIC coeurl::coeurl) diff --git a/io.github.NhekoReborn.Nheko.yaml b/io.github.NhekoReborn.Nheko.yaml index 17db66d2..a216cfa9 100644 --- a/io.github.NhekoReborn.Nheko.yaml +++ b/io.github.NhekoReborn.Nheko.yaml @@ -152,7 +152,7 @@ modules: - -Ddefault_library=static name: coeurl sources: - - commit: e741ced9b2bcf14069e17d466aaef58b277b481d + - commit: ab7e5b31cb4945ad2c7f276d1f93ce07773011ba type: git url: https://nheko.im/nheko-reborn/coeurl.git - config-opts: From 58039f7f3e58ee43bc318774cf9b1dc0d883dd4b Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Thu, 12 Aug 2021 00:07:08 +0200 Subject: [PATCH 29/42] Fix typo in coeurl --- CMakeLists.txt | 4 ++-- io.github.NhekoReborn.Nheko.yaml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5c4b7d56..3734c5c6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -381,7 +381,7 @@ if(USE_BUNDLED_MTXCLIENT) FetchContent_Declare( MatrixClient GIT_REPOSITORY https://github.com/Nheko-Reborn/mtxclient.git - GIT_TAG 2e6b834f3fbd12b23ddb5219a5a4eacb7e11e0a3 + GIT_TAG c5e12e9000fcb889a2746a774a039e0cee7d27ff ) set(BUILD_LIB_EXAMPLES OFF CACHE INTERNAL "") set(BUILD_LIB_TESTS OFF CACHE INTERNAL "") @@ -676,7 +676,7 @@ if(USE_BUNDLED_COEURL) FetchContent_Declare( coeurl GIT_REPOSITORY https://nheko.im/Nheko-Reborn/coeurl.git - GIT_TAG ab7e5b31cb4945ad2c7f276d1f93ce07773011ba + GIT_TAG 18470f1c3570f652f79de8e2fbe3eef4238c4171 ) FetchContent_MakeAvailable(coeurl) target_link_libraries(nheko PUBLIC coeurl::coeurl) diff --git a/io.github.NhekoReborn.Nheko.yaml b/io.github.NhekoReborn.Nheko.yaml index a216cfa9..7f58ffda 100644 --- a/io.github.NhekoReborn.Nheko.yaml +++ b/io.github.NhekoReborn.Nheko.yaml @@ -152,7 +152,7 @@ modules: - -Ddefault_library=static name: coeurl sources: - - commit: ab7e5b31cb4945ad2c7f276d1f93ce07773011ba + - commit: 18470f1c3570f652f79de8e2fbe3eef4238c4171 type: git url: https://nheko.im/nheko-reborn/coeurl.git - config-opts: @@ -163,7 +163,7 @@ modules: buildsystem: cmake-ninja name: mtxclient sources: - - commit: 2e6b834f3fbd12b23ddb5219a5a4eacb7e11e0a3 + - commit: c5e12e9000fcb889a2746a774a039e0cee7d27ff type: git url: https://github.com/Nheko-Reborn/mtxclient.git - config-opts: From 796e5fcd39b8b2681e173aa1d8f4c40a9ba28bab Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Thu, 12 Aug 2021 01:29:43 +0200 Subject: [PATCH 30/42] Fix lambda to function pointer in coeurl on Linux --- CMakeLists.txt | 4 ++-- io.github.NhekoReborn.Nheko.yaml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3734c5c6..8ef4470c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -381,7 +381,7 @@ if(USE_BUNDLED_MTXCLIENT) FetchContent_Declare( MatrixClient GIT_REPOSITORY https://github.com/Nheko-Reborn/mtxclient.git - GIT_TAG c5e12e9000fcb889a2746a774a039e0cee7d27ff + GIT_TAG deb51ef1d6df870098069312f0a1999550e1eb85 ) set(BUILD_LIB_EXAMPLES OFF CACHE INTERNAL "") set(BUILD_LIB_TESTS OFF CACHE INTERNAL "") @@ -676,7 +676,7 @@ if(USE_BUNDLED_COEURL) FetchContent_Declare( coeurl GIT_REPOSITORY https://nheko.im/Nheko-Reborn/coeurl.git - GIT_TAG 18470f1c3570f652f79de8e2fbe3eef4238c4171 + GIT_TAG 3901507db25cf3f9364b58cd8c7880640900c992 ) FetchContent_MakeAvailable(coeurl) target_link_libraries(nheko PUBLIC coeurl::coeurl) diff --git a/io.github.NhekoReborn.Nheko.yaml b/io.github.NhekoReborn.Nheko.yaml index 7f58ffda..c9caddc8 100644 --- a/io.github.NhekoReborn.Nheko.yaml +++ b/io.github.NhekoReborn.Nheko.yaml @@ -152,7 +152,7 @@ modules: - -Ddefault_library=static name: coeurl sources: - - commit: 18470f1c3570f652f79de8e2fbe3eef4238c4171 + - commit: 3901507db25cf3f9364b58cd8c7880640900c992 type: git url: https://nheko.im/nheko-reborn/coeurl.git - config-opts: @@ -163,7 +163,7 @@ modules: buildsystem: cmake-ninja name: mtxclient sources: - - commit: c5e12e9000fcb889a2746a774a039e0cee7d27ff + - commit: deb51ef1d6df870098069312f0a1999550e1eb85 type: git url: https://github.com/Nheko-Reborn/mtxclient.git - config-opts: From 7d484a8228886f942f62ca9cf23920b7fab2c602 Mon Sep 17 00:00:00 2001 From: Callum Brown Date: Thu, 12 Aug 2021 16:41:29 +0100 Subject: [PATCH 31/42] Use QInputDialog to get registration token --- CMakeLists.txt | 2 - src/RegisterPage.cpp | 22 +++-------- src/dialogs/TokenRegistration.cpp | 62 ------------------------------- src/dialogs/TokenRegistration.h | 31 ---------------- 4 files changed, 5 insertions(+), 112 deletions(-) delete mode 100644 src/dialogs/TokenRegistration.cpp delete mode 100644 src/dialogs/TokenRegistration.h diff --git a/CMakeLists.txt b/CMakeLists.txt index a146931e..049ed8a3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -286,7 +286,6 @@ set(SRC_FILES src/dialogs/Logout.cpp src/dialogs/PreviewUploadOverlay.cpp src/dialogs/ReCaptcha.cpp - src/dialogs/TokenRegistration.cpp # Emoji src/emoji/EmojiModel.cpp @@ -498,7 +497,6 @@ qt5_wrap_cpp(MOC_HEADERS src/dialogs/Logout.h src/dialogs/PreviewUploadOverlay.h src/dialogs/ReCaptcha.h - src/dialogs/TokenRegistration.h # Emoji src/emoji/EmojiModel.h diff --git a/src/RegisterPage.cpp b/src/RegisterPage.cpp index 1eac85fd..acee6f78 100644 --- a/src/RegisterPage.cpp +++ b/src/RegisterPage.cpp @@ -3,6 +3,7 @@ // // SPDX-License-Identifier: GPL-3.0-or-later +#include #include #include #include @@ -25,7 +26,6 @@ #include "dialogs/FallbackAuth.h" #include "dialogs/ReCaptcha.h" -#include "dialogs/TokenRegistration.h" Q_DECLARE_METATYPE(mtx::user_interactive::Unauthorized) Q_DECLARE_METATYPE(mtx::user_interactive::Auth) @@ -483,22 +483,10 @@ RegisterPage::doUIA(const mtx::user_interactive::Unauthorized &unauthorized) mtx::user_interactive::Auth{session, mtx::user_interactive::auth::Dummy{}}); } else if (current_stage == mtx::user_interactive::auth_types::registration_token) { - auto dialog = new dialogs::TokenRegistration(this); - - connect(dialog, - &dialogs::TokenRegistration::confirmation, - this, - [this, session, dialog](std::string token) { - dialog->close(); - dialog->deleteLater(); - emit registrationWithAuth(mtx::user_interactive::Auth{ - session, mtx::user_interactive::auth::RegistrationToken{token}}); - }); - - connect( - dialog, &dialogs::TokenRegistration::cancel, this, &RegisterPage::errorOccurred); - - dialog->show(); + QString token = QInputDialog::getText( + this, tr("Registration token"), tr("Please enter a valid registration token.")); + emit registrationWithAuth(mtx::user_interactive::Auth{ + session, mtx::user_interactive::auth::RegistrationToken{token.toStdString()}}); } else { // use fallback auto dialog = new dialogs::FallbackAuth( diff --git a/src/dialogs/TokenRegistration.cpp b/src/dialogs/TokenRegistration.cpp deleted file mode 100644 index 2333dcb1..00000000 --- a/src/dialogs/TokenRegistration.cpp +++ /dev/null @@ -1,62 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Nheko Contributors -// -// SPDX-License-Identifier: GPL-3.0-or-later - -#include -#include -#include - -#include "dialogs/TokenRegistration.h" - -#include "Config.h" -#include "MatrixClient.h" -#include "ui/TextField.h" - -using namespace dialogs; - -TokenRegistration::TokenRegistration(QWidget *parent) - : QWidget(parent) -{ - setAutoFillBackground(true); - setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint); - setWindowModality(Qt::WindowModal); - setAttribute(Qt::WA_DeleteOnClose, true); - - auto layout = new QVBoxLayout(this); - layout->setSpacing(conf::modals::WIDGET_SPACING); - layout->setMargin(conf::modals::WIDGET_MARGIN); - - auto buttonLayout = new QHBoxLayout(); - buttonLayout->setSpacing(8); - buttonLayout->setMargin(0); - - cancelBtn_ = new QPushButton(tr("Cancel"), this); - confirmBtn_ = new QPushButton(tr("Confirm"), this); - confirmBtn_->setDefault(true); - - buttonLayout->addStretch(1); - buttonLayout->addWidget(cancelBtn_); - buttonLayout->addWidget(confirmBtn_); - - tokenInput_ = new TextField(this); - tokenInput_->setLabel(tr("Registration token")); - - QFont font; - font.setPointSizeF(font.pointSizeF() * conf::modals::LABEL_MEDIUM_SIZE_RATIO); - - auto label = new QLabel(tr("Please enter a valid registration token."), this); - label->setFont(font); - - layout->addWidget(label); - layout->addWidget(tokenInput_); - layout->addLayout(buttonLayout); - - connect(confirmBtn_, &QPushButton::clicked, this, [this]() { - emit confirmation(tokenInput_->text().toStdString()); - emit close(); - }); - connect(cancelBtn_, &QPushButton::clicked, this, [this]() { - emit cancel(); - emit close(); - }); -} diff --git a/src/dialogs/TokenRegistration.h b/src/dialogs/TokenRegistration.h deleted file mode 100644 index 562dcb7b..00000000 --- a/src/dialogs/TokenRegistration.h +++ /dev/null @@ -1,31 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Nheko Contributors -// -// SPDX-License-Identifier: GPL-3.0-or-later - -#pragma once - -#include - -class QPushButton; -class QLabel; -class TextField; - -namespace dialogs { - -class TokenRegistration : public QWidget -{ - Q_OBJECT - -public: - TokenRegistration(QWidget *parent = nullptr); - -signals: - void confirmation(std::string token); - void cancel(); - -private: - QPushButton *confirmBtn_; - QPushButton *cancelBtn_; - TextField *tokenInput_; -}; -} // dialogs From 4fa644f2b7d0e95f8f917dc6d4d030153c0da73e Mon Sep 17 00:00:00 2001 From: Callum Brown Date: Thu, 12 Aug 2021 17:57:07 +0100 Subject: [PATCH 32/42] Fix cancel button on registration token dialog --- src/RegisterPage.cpp | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/RegisterPage.cpp b/src/RegisterPage.cpp index acee6f78..ddd4d47d 100644 --- a/src/RegisterPage.cpp +++ b/src/RegisterPage.cpp @@ -483,10 +483,21 @@ RegisterPage::doUIA(const mtx::user_interactive::Unauthorized &unauthorized) mtx::user_interactive::Auth{session, mtx::user_interactive::auth::Dummy{}}); } else if (current_stage == mtx::user_interactive::auth_types::registration_token) { - QString token = QInputDialog::getText( - this, tr("Registration token"), tr("Please enter a valid registration token.")); - emit registrationWithAuth(mtx::user_interactive::Auth{ - session, mtx::user_interactive::auth::RegistrationToken{token.toStdString()}}); + bool ok; + QString token = + QInputDialog::getText(this, + tr("Registration token"), + tr("Please enter a valid registration token."), + QLineEdit::Normal, + QString(), + &ok); + + if (ok) { + emit registrationWithAuth(mtx::user_interactive::Auth{ + session, mtx::user_interactive::auth::RegistrationToken{token.toStdString()}}); + } else { + emit errorOccurred(); + } } else { // use fallback auto dialog = new dialogs::FallbackAuth( From 18ea01e198d112de00ac70e1e1c357424706d10a Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Fri, 13 Aug 2021 23:13:09 +0200 Subject: [PATCH 33/42] Show if there are unverified devices in a room Also fixes some issues where nested transactions will poison the verification cache. --- resources/qml/RoomList.qml | 1 + resources/qml/TimelineView.qml | 1 + resources/qml/TopBar.qml | 26 +++- src/Cache.cpp | 226 +++++++++++++++++++++++---------- src/CacheCryptoStructs.h | 8 +- src/Cache_p.h | 8 +- src/timeline/TimelineModel.cpp | 17 +++ src/timeline/TimelineModel.h | 3 + 8 files changed, 219 insertions(+), 71 deletions(-) diff --git a/resources/qml/RoomList.qml b/resources/qml/RoomList.qml index 576383e2..8fbfce91 100644 --- a/resources/qml/RoomList.qml +++ b/resources/qml/RoomList.qml @@ -35,6 +35,7 @@ Page { function onCurrentRoomChanged() { if (Rooms.currentRoom) roomlist.positionViewAtIndex(Rooms.roomidToIndex(Rooms.currentRoom.roomId), ListView.Contain); + } target: Rooms diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml index 5e99ee5c..104da160 100644 --- a/resources/qml/TimelineView.qml +++ b/resources/qml/TimelineView.qml @@ -88,6 +88,7 @@ Item { Loader { active: room || roomPreview Layout.fillWidth: true + sourceComponent: MessageView { implicitHeight: msgView.height - typingIndicator.height } diff --git a/resources/qml/TopBar.qml b/resources/qml/TopBar.qml index 8543d02a..0faaea9c 100644 --- a/resources/qml/TopBar.qml +++ b/resources/qml/TopBar.qml @@ -15,6 +15,8 @@ Rectangle { property string roomName: room ? room.roomName : qsTr("No room selected") property string avatarUrl: room ? room.roomAvatarUrl : "" property string roomTopic: room ? room.roomTopic : "" + property bool isEncrypted: room ? room.isEncrypted : false + property int trustlevel: room ? room.trustlevel : Crypto.Unverified Layout.fillWidth: true implicitHeight: topLayout.height + Nheko.paddingMedium * 2 @@ -92,11 +94,33 @@ Rectangle { text: roomTopic } + EncryptionIndicator { + Layout.column: 3 + Layout.row: 0 + Layout.rowSpan: 2 + visible: isEncrypted + encrypted: isEncrypted + trust: trustlevel + ToolTip.text: { + if (!encrypted) + return qsTr("This room is not encrypted!"); + + switch (trust) { + case Crypto.Verified: + return qsTr("This room contains only verified devices."); + case Crypto.TOFU: + return qsTr("This rooms contain verified devices and devices which have never changed their master key."); + default: + return qsTr("This room contains unverified devices!"); + } + } + } + ImageButton { id: roomOptionsButton visible: !!room - Layout.column: 3 + Layout.column: 4 Layout.row: 0 Layout.rowSpan: 2 Layout.alignment: Qt.AlignVCenter diff --git a/src/Cache.cpp b/src/Cache.cpp index b79a3b47..5edfaf09 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -114,7 +114,13 @@ ro_txn(lmdb::env &env) txn = lmdb::txn::begin(env, nullptr, MDB_RDONLY); reuse_counter = 0; } else if (reuse_counter > 0) { - txn.renew(); + try { + txn.renew(); + } catch (...) { + txn.abort(); + txn = lmdb::txn::begin(env, nullptr, MDB_RDONLY); + reuse_counter = 0; + } } reuse_counter++; @@ -289,7 +295,9 @@ Cache::setup() megolmSessionDataDb_ = lmdb::dbi::open(txn, MEGOLM_SESSIONS_DATA_DB, MDB_CREATE); // What rooms are encrypted - encryptedRooms_ = lmdb::dbi::open(txn, ENCRYPTED_ROOMS_DB, MDB_CREATE); + encryptedRooms_ = lmdb::dbi::open(txn, ENCRYPTED_ROOMS_DB, MDB_CREATE); + [[maybe_unused]] auto verificationDb = getVerificationDb(txn); + [[maybe_unused]] auto userKeysDb = getUserKeysDb(txn); txn.commit(); @@ -861,6 +869,9 @@ Cache::setNextBatchToken(lmdb::txn &txn, const QString &token) bool Cache::isInitialized() { + if (!env_.handle()) + return false; + auto txn = ro_txn(env_); std::string_view token; @@ -3570,6 +3581,37 @@ Cache::roomMembers(const std::string &room_id) return members; } +crypto::Trust +Cache::roomVerificationStatus(const std::string &room_id) +{ + std::string_view keys; + + crypto::Trust trust = crypto::Verified; + + try { + auto txn = ro_txn(env_); + + auto db = getMembersDb(txn, room_id); + auto keysDb = getUserKeysDb(txn); + + std::string_view user_id, unused; + auto cursor = lmdb::cursor::open(txn, db); + while (cursor.get(user_id, unused, MDB_NEXT)) { + auto verif = verificationStatus_(std::string(user_id), txn); + if (verif.unverified_device_count) + return crypto::Unverified; + else if (verif.user_verified == crypto::TOFU) + trust = crypto::TOFU; + } + } catch (std::exception &e) { + nhlog::db()->error( + "Failed to calculate verification status for {}: {}", room_id, e.what()); + return crypto::Unverified; + } + + return trust; +} + std::map> Cache::getMembersWithKeys(const std::string &room_id, bool verified_only) { @@ -3751,11 +3793,17 @@ from_json(const json &j, UserKeyCache &info) std::optional Cache::userKeys(const std::string &user_id) +{ + auto txn = ro_txn(env_); + return userKeys_(user_id, txn); +} + +std::optional +Cache::userKeys_(const std::string &user_id, lmdb::txn &txn) { std::string_view keys; try { - auto txn = ro_txn(env_); auto db = getUserKeysDb(txn); auto res = db.get(txn, user_id, keys); @@ -3764,7 +3812,8 @@ Cache::userKeys(const std::string &user_id) } else { return {}; } - } catch (std::exception &) { + } catch (std::exception &e) { + nhlog::db()->error("Failed to retrieve user keys for {}: {}", user_id, e.what()); return {}; } } @@ -3799,8 +3848,14 @@ Cache::updateUserKeys(const std::string &sync_token, const mtx::responses::Query auto last_changed = updateToWrite.last_changed; // skip if we are tracking this and expect it to be up to date with the last // sync token - if (!last_changed.empty() && last_changed != sync_token) + if (!last_changed.empty() && last_changed != sync_token) { + nhlog::db()->debug("Not storing update for user {}, because " + "last_changed {}, but we fetched update for {}", + user, + last_changed, + sync_token); continue; + } if (!updateToWrite.master_keys.keys.empty() && update.master_keys.keys != updateToWrite.master_keys.keys) { @@ -3859,6 +3914,7 @@ Cache::updateUserKeys(const std::string &sync_token, const mtx::responses::Query updateToWrite.seen_device_ids.insert(device_id); } } + updateToWrite.updated_at = sync_token; db.put(txn, user, json(updateToWrite).dump()); } @@ -3944,35 +4000,46 @@ void Cache::query_keys(const std::string &user_id, std::function cb) { - auto cache_ = cache::userKeys(user_id); + mtx::requests::QueryKeys req; + std::string last_changed; + { + auto txn = ro_txn(env_); + auto cache_ = userKeys_(user_id, txn); - if (cache_.has_value()) { - if (!cache_->updated_at.empty() && cache_->updated_at == cache_->last_changed) { - cb(cache_.value(), {}); - return; - } - } + if (cache_.has_value()) { + if (cache_->updated_at == cache_->last_changed) { + cb(cache_.value(), {}); + return; + } else + nhlog::db()->info("Keys outdated for {}: {} vs {}", + user_id, + cache_->updated_at, + cache_->last_changed); + } else + nhlog::db()->info("No keys found for {}", user_id); - mtx::requests::QueryKeys req; - req.device_keys[user_id] = {}; + req.device_keys[user_id] = {}; - std::string last_changed; - if (cache_) - last_changed = cache_->last_changed; - req.token = last_changed; + if (cache_) + last_changed = cache_->last_changed; + req.token = last_changed; + } // use context object so that we can disconnect again QObject *context{new QObject(this)}; - QObject::connect(this, - &Cache::verificationStatusChanged, - context, - [cb, user_id, context_ = context](std::string updated_user) mutable { - if (user_id == updated_user) { - context_->deleteLater(); - auto keys = cache::userKeys(user_id); - cb(keys.value_or(UserKeyCache{}), {}); - } - }); + QObject::connect( + this, + &Cache::verificationStatusChanged, + context, + [cb, user_id, context_ = context, this](std::string updated_user) mutable { + if (user_id == updated_user) { + context_->deleteLater(); + auto txn = ro_txn(env_); + auto keys = this->userKeys_(user_id, txn); + cb(keys.value_or(UserKeyCache{}), {}); + } + }, + Qt::QueuedConnection); http::client()->query_keys( req, @@ -4000,17 +4067,16 @@ to_json(json &j, const VerificationCache &info) void from_json(const json &j, VerificationCache &info) { - info.device_verified = j.at("device_verified").get>(); - info.device_blocked = j.at("device_blocked").get>(); + info.device_verified = j.at("device_verified").get>(); + info.device_blocked = j.at("device_blocked").get>(); } std::optional -Cache::verificationCache(const std::string &user_id) +Cache::verificationCache(const std::string &user_id, lmdb::txn &txn) { std::string_view verifiedVal; - auto txn = lmdb::txn::begin(env_); - auto db = getVerificationDb(txn); + auto db = getVerificationDb(txn); try { VerificationCache verified_state; @@ -4029,26 +4095,28 @@ Cache::verificationCache(const std::string &user_id) void Cache::markDeviceVerified(const std::string &user_id, const std::string &key) { - std::string_view val; + { + std::string_view val; - auto txn = lmdb::txn::begin(env_); - auto db = getVerificationDb(txn); + auto txn = lmdb::txn::begin(env_); + auto db = getVerificationDb(txn); - try { - VerificationCache verified_state; - auto res = db.get(txn, user_id, val); - if (res) { - verified_state = json::parse(val); - } + try { + VerificationCache verified_state; + auto res = db.get(txn, user_id, val); + if (res) { + verified_state = json::parse(val); + } - for (const auto &device : verified_state.device_verified) - if (device == key) - return; + for (const auto &device : verified_state.device_verified) + if (device == key) + return; - verified_state.device_verified.push_back(key); - db.put(txn, user_id, json(verified_state).dump()); - txn.commit(); - } catch (std::exception &) { + verified_state.device_verified.insert(key); + db.put(txn, user_id, json(verified_state).dump()); + txn.commit(); + } catch (std::exception &) { + } } const auto local_user = utils::localUser().toStdString(); @@ -4086,11 +4154,7 @@ Cache::markDeviceUnverified(const std::string &user_id, const std::string &key) verified_state = json::parse(val); } - verified_state.device_verified.erase( - std::remove(verified_state.device_verified.begin(), - verified_state.device_verified.end(), - key), - verified_state.device_verified.end()); + verified_state.device_verified.erase(key); db.put(txn, user_id, json(verified_state).dump()); txn.commit(); @@ -4119,6 +4183,13 @@ Cache::markDeviceUnverified(const std::string &user_id, const std::string &key) VerificationStatus Cache::verificationStatus(const std::string &user_id) +{ + auto txn = ro_txn(env_); + return verificationStatus_(user_id, txn); +} + +VerificationStatus +Cache::verificationStatus_(const std::string &user_id, lmdb::txn &txn) { std::unique_lock lock(verification_storage.verification_storage_mtx); if (verification_storage.status.count(user_id)) @@ -4126,7 +4197,11 @@ Cache::verificationStatus(const std::string &user_id) VerificationStatus status; - if (auto verifCache = verificationCache(user_id)) { + // assume there is at least one unverified device until we have checked we have the device + // list for that user. + status.unverified_device_count = 1; + + if (auto verifCache = verificationCache(user_id, txn)) { status.verified_devices = verifCache->device_verified; } @@ -4134,12 +4209,10 @@ Cache::verificationStatus(const std::string &user_id) crypto::Trust trustlevel = crypto::Trust::Unverified; if (user_id == local_user) { - status.verified_devices.push_back(http::client()->device_id()); + status.verified_devices.insert(http::client()->device_id()); trustlevel = crypto::Trust::Verified; } - verification_storage.status[user_id] = status; - auto verifyAtLeastOneSig = [](const auto &toVerif, const std::map &keys, const std::string &keyOwner) { @@ -4157,6 +4230,16 @@ Cache::verificationStatus(const std::string &user_id) return false; }; + auto updateUnverifiedDevices = [&status](auto &theirDeviceKeys) { + int currentVerifiedDevices = 0; + for (auto device_id : status.verified_devices) { + if (theirDeviceKeys.count(device_id)) + currentVerifiedDevices++; + } + status.unverified_device_count = + static_cast(theirDeviceKeys.size()) - currentVerifiedDevices; + }; + try { // for local user verify this device_key -> our master_key -> our self_signing_key // -> our device_keys @@ -4166,17 +4249,24 @@ Cache::verificationStatus(const std::string &user_id) // // This means verifying the other user adds 2 extra steps,verifying our user_signing // key and their master key - auto ourKeys = userKeys(local_user); - auto theirKeys = userKeys(user_id); - if (!ourKeys || !theirKeys) + auto ourKeys = userKeys_(local_user, txn); + auto theirKeys = userKeys_(user_id, txn); + if (!ourKeys || !theirKeys) { + verification_storage.status[user_id] = status; return status; + } + + // Update verified devices count to count without cross-signing + updateUnverifiedDevices(theirKeys->device_keys); if (!mtx::crypto::ed25519_verify_signature( olm::client()->identity_keys().ed25519, json(ourKeys->master_keys), ourKeys->master_keys.signatures.at(local_user) - .at("ed25519:" + http::client()->device_id()))) + .at("ed25519:" + http::client()->device_id()))) { + verification_storage.status[user_id] = status; return status; + } auto master_keys = ourKeys->master_keys.keys; @@ -4191,14 +4281,17 @@ Cache::verificationStatus(const std::string &user_id) trustlevel = crypto::Trust::Verified; else if (!theirKeys->master_key_changed) trustlevel = crypto::Trust::TOFU; - else + else { + verification_storage.status[user_id] = status; return status; + } master_keys = theirKeys->master_keys.keys; } status.user_verified = trustlevel; + verification_storage.status[user_id] = status; if (!verifyAtLeastOneSig(theirKeys->self_signing_keys, master_keys, user_id)) return status; @@ -4209,16 +4302,19 @@ Cache::verificationStatus(const std::string &user_id) device_key.keys.at("curve25519:" + device_key.device_id); if (verifyAtLeastOneSig( device_key, theirKeys->self_signing_keys.keys, user_id)) { - status.verified_devices.push_back(device_key.device_id); + status.verified_devices.insert(device_key.device_id); status.verified_device_keys[identkey] = trustlevel; } } catch (...) { } } + updateUnverifiedDevices(theirKeys->device_keys); verification_storage.status[user_id] = status; return status; - } catch (std::exception &) { + } catch (std::exception &e) { + nhlog::db()->error( + "Failed to calculate verification status of {}: {}", user_id, e.what()); return status; } } diff --git a/src/CacheCryptoStructs.h b/src/CacheCryptoStructs.h index 69d64885..a992fe79 100644 --- a/src/CacheCryptoStructs.h +++ b/src/CacheCryptoStructs.h @@ -112,9 +112,11 @@ struct VerificationStatus //! True, if the users master key is verified crypto::Trust user_verified = crypto::Trust::Unverified; //! List of all devices marked as verified - std::vector verified_devices; + std::set verified_devices; //! Map from sender key/curve25519 to trust status std::map verified_device_keys; + //! Count of unverified devices + int unverified_device_count = 0; }; //! In memory cache of verification status @@ -154,9 +156,9 @@ from_json(const nlohmann::json &j, UserKeyCache &info); struct VerificationCache { //! list of verified device_ids with device-verification - std::vector device_verified; + std::set device_verified; //! list of devices the user blocks - std::vector device_blocked; + std::set device_blocked; }; void diff --git a/src/Cache_p.h b/src/Cache_p.h index 51fe9978..748404d1 100644 --- a/src/Cache_p.h +++ b/src/Cache_p.h @@ -46,7 +46,6 @@ public: std::string statusMessage(const std::string &user_id); // user cache stores user keys - std::optional userKeys(const std::string &user_id); std::map> getMembersWithKeys( const std::string &room_id, bool verified_only); @@ -63,9 +62,11 @@ public: std::function cb); // device & user verification cache + std::optional userKeys(const std::string &user_id); VerificationStatus verificationStatus(const std::string &user_id); void markDeviceVerified(const std::string &user_id, const std::string &device); void markDeviceUnverified(const std::string &user_id, const std::string &device); + crypto::Trust roomVerificationStatus(const std::string &room_id); std::vector joinedRooms(); @@ -681,7 +682,10 @@ private: return QString::fromStdString(event.state_key); } - std::optional verificationCache(const std::string &user_id); + std::optional verificationCache(const std::string &user_id, + lmdb::txn &txn); + VerificationStatus verificationStatus_(const std::string &user_id, lmdb::txn &txn); + std::optional userKeys_(const std::string &user_id, lmdb::txn &txn); void setNextBatchToken(lmdb::txn &txn, const std::string &token); void setNextBatchToken(lmdb::txn &txn, const QString &token); diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp index 513f08bc..79c28edf 100644 --- a/src/timeline/TimelineModel.cpp +++ b/src/timeline/TimelineModel.cpp @@ -418,6 +418,14 @@ TimelineModel::TimelineModel(TimelineViewManager *manager, QString room_id, QObj &events, &EventStore::enableKeyRequests); + connect(this, &TimelineModel::encryptionChanged, this, &TimelineModel::trustlevelChanged); + connect( + this, &TimelineModel::roomMemberCountChanged, this, &TimelineModel::trustlevelChanged); + connect(cache::client(), + &Cache::verificationStatusChanged, + this, + &TimelineModel::trustlevelChanged); + showEventTimer.callOnTimeout(this, &TimelineModel::scrollTimerEvent); } @@ -1993,6 +2001,15 @@ TimelineModel::roomTopic() const QString::fromStdString(info[room_id_].topic).toHtmlEscaped())); } +crypto::Trust +TimelineModel::trustlevel() const +{ + if (!isEncrypted_) + return crypto::Trust::Unverified; + + return cache::client()->roomVerificationStatus(room_id_.toStdString()); +} + int TimelineModel::roomMemberCount() const { diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h index ad7cfbbb..aa07fe01 100644 --- a/src/timeline/TimelineModel.h +++ b/src/timeline/TimelineModel.h @@ -175,6 +175,7 @@ class TimelineModel : public QAbstractListModel Q_PROPERTY(int roomMemberCount READ roomMemberCount NOTIFY roomMemberCountChanged) Q_PROPERTY(bool isEncrypted READ isEncrypted NOTIFY encryptionChanged) Q_PROPERTY(bool isSpace READ isSpace CONSTANT) + Q_PROPERTY(int trustlevel READ trustlevel NOTIFY trustlevelChanged) Q_PROPERTY(InputBar *input READ input CONSTANT) Q_PROPERTY(Permissions *permissions READ permissions NOTIFY permissionsChanged) @@ -287,6 +288,7 @@ public: DescInfo lastMessage() const { return lastMessage_; } bool isSpace() const { return isSpace_; } bool isEncrypted() const { return isEncrypted_; } + crypto::Trust trustlevel() const; int roomMemberCount() const; public slots: @@ -372,6 +374,7 @@ signals: void updateFlowEventId(std::string event_id); void encryptionChanged(); + void trustlevelChanged(); void roomNameChanged(); void plainRoomNameChanged(); void roomTopicChanged(); From 9bad584931a03717f71e96900202acac43d2a62f Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Fri, 13 Aug 2021 23:58:26 +0200 Subject: [PATCH 34/42] Show verification status in memberlist --- resources/qml/RoomMembers.qml | 42 ++++++++++++++++++++++++---- resources/qml/Root.qml | 4 +-- resources/qml/TopBar.qml | 2 +- src/Cache.cpp | 2 -- src/MemberList.cpp | 12 ++++++++ src/MemberList.h | 1 + src/timeline/TimelineViewManager.cpp | 8 ++++-- src/timeline/TimelineViewManager.h | 4 +-- 8 files changed, 59 insertions(+), 16 deletions(-) diff --git a/resources/qml/RoomMembers.qml b/resources/qml/RoomMembers.qml index 447e6fd1..8e44855c 100644 --- a/resources/qml/RoomMembers.qml +++ b/resources/qml/RoomMembers.qml @@ -13,6 +13,7 @@ ApplicationWindow { id: roomMembersRoot property MemberList members + property Room room title: qsTr("Members of %1").arg(members.roomName) height: 650 @@ -83,9 +84,14 @@ ApplicationWindow { } delegate: RowLayout { + id: del + + width: ListView.view.width spacing: Nheko.paddingMedium Avatar { + id: avatar + width: Nheko.avatarSize height: Nheko.avatarSize userid: model.mxid @@ -97,16 +103,18 @@ ApplicationWindow { ColumnLayout { spacing: Nheko.paddingSmall - Label { - text: model.displayName + ElidedLabel { + fullText: model.displayName color: TimelineManager.userColor(model ? model.mxid : "", Nheko.colors.window) - font.pointSize: fontMetrics.font.pointSize + font.pixelSize: fontMetrics.font.pixelSize + elideWidth: del.width - Nheko.paddingMedium * 2 - avatar.width - encryptInd.width } - Label { - text: model.mxid + ElidedLabel { + fullText: model.mxid color: Nheko.colors.buttonText - font.pointSize: fontMetrics.font.pointSize * 0.9 + font.pixelSize: Math.ceil(fontMetrics.font.pixelSize * 0.9) + elideWidth: del.width - Nheko.paddingMedium * 2 - avatar.width - encryptInd.width } Item { @@ -116,6 +124,28 @@ ApplicationWindow { } + EncryptionIndicator { + id: encryptInd + + Layout.alignment: Qt.AlignRight + visible: room.isEncrypted + encrypted: room.isEncrypted + trust: encrypted ? model.trustlevel : Crypto.Unverified + ToolTip.text: { + if (!encrypted) + return qsTr("This room is not encrypted!"); + + switch (trust) { + case Crypto.Verified: + return qsTr("This user is verified."); + case Crypto.TOFU: + return qsTr("This user isn't verified, but is still using the same master key from the first time you met."); + default: + return qsTr("This user has unverified devices!"); + } + } + } + } footer: Item { diff --git a/resources/qml/Root.qml b/resources/qml/Root.qml index b229acda..79f12bbf 100644 --- a/resources/qml/Root.qml +++ b/resources/qml/Root.qml @@ -153,10 +153,10 @@ Page { packSet.show(); } - function onOpenRoomMembersDialog(members) { + function onOpenRoomMembersDialog(members, room) { var membersDialog = roomMembersComponent.createObject(timelineRoot, { "members": members, - "roomName": Rooms.currentRoom.roomName + "room": room }); membersDialog.show(); } diff --git a/resources/qml/TopBar.qml b/resources/qml/TopBar.qml index 0faaea9c..7f67c028 100644 --- a/resources/qml/TopBar.qml +++ b/resources/qml/TopBar.qml @@ -140,7 +140,7 @@ Rectangle { Platform.MenuItem { text: qsTr("Members") - onTriggered: TimelineManager.openRoomMembers(room.roomId) + onTriggered: TimelineManager.openRoomMembers(room) } Platform.MenuItem { diff --git a/src/Cache.cpp b/src/Cache.cpp index 5edfaf09..ea034dd0 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -3584,8 +3584,6 @@ Cache::roomMembers(const std::string &room_id) crypto::Trust Cache::roomVerificationStatus(const std::string &room_id) { - std::string_view keys; - crypto::Trust trust = crypto::Verified; try { diff --git a/src/MemberList.cpp b/src/MemberList.cpp index 196647fe..0c0f0cdd 100644 --- a/src/MemberList.cpp +++ b/src/MemberList.cpp @@ -53,6 +53,7 @@ MemberList::roleNames() const {Mxid, "mxid"}, {DisplayName, "displayName"}, {AvatarUrl, "avatarUrl"}, + {Trustlevel, "trustlevel"}, }; } @@ -69,6 +70,17 @@ MemberList::data(const QModelIndex &index, int role) const return m_memberList[index.row()].first.display_name; case AvatarUrl: return m_memberList[index.row()].second; + case Trustlevel: { + auto stat = + cache::verificationStatus(m_memberList[index.row()].first.user_id.toStdString()); + + if (!stat) + return crypto::Unverified; + if (stat->unverified_device_count) + return crypto::Unverified; + else + return stat->user_verified; + } default: return {}; } diff --git a/src/MemberList.h b/src/MemberList.h index e6522694..cffcd83d 100644 --- a/src/MemberList.h +++ b/src/MemberList.h @@ -25,6 +25,7 @@ public: Mxid, DisplayName, AvatarUrl, + Trustlevel, }; MemberList(const QString &room_id, QObject *parent = nullptr); diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp index b23ed278..906e328f 100644 --- a/src/timeline/TimelineViewManager.cpp +++ b/src/timeline/TimelineViewManager.cpp @@ -375,10 +375,12 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par } void -TimelineViewManager::openRoomMembers(QString room_id) +TimelineViewManager::openRoomMembers(TimelineModel *room) { - MemberList *memberList = new MemberList(room_id, this); - emit openRoomMembersDialog(memberList); + if (!room) + return; + MemberList *memberList = new MemberList(room->roomId(), this); + emit openRoomMembersDialog(memberList, room); } void diff --git a/src/timeline/TimelineViewManager.h b/src/timeline/TimelineViewManager.h index 54e3a935..4dd5e996 100644 --- a/src/timeline/TimelineViewManager.h +++ b/src/timeline/TimelineViewManager.h @@ -66,7 +66,7 @@ public: Q_INVOKABLE QString userPresence(QString id) const; Q_INVOKABLE QString userStatus(QString id) const; - Q_INVOKABLE void openRoomMembers(QString room_id); + Q_INVOKABLE void openRoomMembers(TimelineModel *room); Q_INVOKABLE void openRoomSettings(QString room_id); Q_INVOKABLE void openInviteUsers(QString roomId); Q_INVOKABLE void openGlobalUserProfile(QString userId); @@ -92,7 +92,7 @@ signals: void focusChanged(); void focusInput(); void openImageOverlayInternalCb(QString eventId, QImage img); - void openRoomMembersDialog(MemberList *members); + void openRoomMembersDialog(MemberList *members, TimelineModel *room); void openRoomSettingsDialog(RoomSettings *settings); void openInviteUsersDialog(InviteesModel *invitees); void openProfile(UserProfile *profile); From 13633c7644d1a89bc09033ed1df9e4919afb3e25 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sat, 14 Aug 2021 02:06:48 +0200 Subject: [PATCH 35/42] Ensure device signatures always get verified on device update --- src/Cache.cpp | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/src/Cache.cpp b/src/Cache.cpp index ea034dd0..00602acf 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -3901,8 +3901,43 @@ Cache::updateUserKeys(const std::string &sync_token, const mtx::responses::Query } } - if (!keyReused && !oldDeviceKeys.count(device_id)) + if (!keyReused && !oldDeviceKeys.count(device_id)) { + // ensure the key has a valid signature from itself + std::string device_signing_key = + "ed25519:" + device_keys.device_id; + if (device_id != device_keys.device_id) { + nhlog::crypto()->warn( + "device {}:{} has a different device id " + "in the body: {}", + user, + device_id, + device_keys.device_id); + continue; + } + if (!device_keys.signatures.count(user) || + !device_keys.signatures.at(user).count( + device_signing_key)) { + nhlog::crypto()->warn( + "device {}:{} has no signature", + user, + device_id); + continue; + } + + if (!mtx::crypto::ed25519_verify_signature( + device_keys.keys.at(device_signing_key), + json(device_keys), + device_keys.signatures.at(user).at( + device_signing_key))) { + nhlog::crypto()->warn( + "device {}:{} has an invalid signature", + user, + device_id); + continue; + } + updateToWrite.device_keys[device_id] = device_keys; + } } for (const auto &[key_id, key] : device_keys.keys) { From 110fef5c68d8a65ecc0faaee3db26c3b7d9c5821 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sat, 14 Aug 2021 02:41:34 +0200 Subject: [PATCH 36/42] Request keys when opening a room for the first time --- src/Cache.cpp | 37 ++++++++++++++++++++++++++----------- src/CacheCryptoStructs.h | 2 ++ 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/src/Cache.cpp b/src/Cache.cpp index 00602acf..fccf1c53 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -3587,24 +3587,33 @@ Cache::roomVerificationStatus(const std::string &room_id) crypto::Trust trust = crypto::Verified; try { - auto txn = ro_txn(env_); + auto txn = lmdb::txn::begin(env_); auto db = getMembersDb(txn, room_id); auto keysDb = getUserKeysDb(txn); + std::vector keysToRequest; std::string_view user_id, unused; auto cursor = lmdb::cursor::open(txn, db); while (cursor.get(user_id, unused, MDB_NEXT)) { auto verif = verificationStatus_(std::string(user_id), txn); - if (verif.unverified_device_count) - return crypto::Unverified; - else if (verif.user_verified == crypto::TOFU) + if (verif.unverified_device_count) { + trust = crypto::Unverified; + if (verif.verified_devices.empty() && verif.no_keys) { + // we probably don't have the keys yet, so query them + keysToRequest.push_back(std::string(user_id)); + } + } else if (verif.user_verified == crypto::TOFU && trust == crypto::Verified) trust = crypto::TOFU; } + + if (!keysToRequest.empty()) + markUserKeysOutOfDate(txn, keysDb, keysToRequest, ""); + } catch (std::exception &e) { nhlog::db()->error( "Failed to calculate verification status for {}: {}", room_id, e.what()); - return crypto::Unverified; + trust = crypto::Unverified; } return trust; @@ -4000,14 +4009,16 @@ Cache::markUserKeysOutOfDate(lmdb::txn &txn, nhlog::db()->debug("Marking user keys out of date: {}", user); std::string_view oldKeys; - auto res = db.get(txn, user, oldKeys); - - if (!res) - continue; - auto cacheEntry = - json::parse(std::string_view(oldKeys.data(), oldKeys.size())).get(); + UserKeyCache cacheEntry; + auto res = db.get(txn, user, oldKeys); + if (res) { + auto cacheEntry = + json::parse(std::string_view(oldKeys.data(), oldKeys.size())) + .get(); + } cacheEntry.last_changed = sync_token; + db.put(txn, user, json(cacheEntry).dump()); query.device_keys[user] = {}; @@ -4233,6 +4244,7 @@ Cache::verificationStatus_(const std::string &user_id, lmdb::txn &txn) // assume there is at least one unverified device until we have checked we have the device // list for that user. status.unverified_device_count = 1; + status.no_keys = true; if (auto verifCache = verificationCache(user_id, txn)) { status.verified_devices = verifCache->device_verified; @@ -4284,6 +4296,9 @@ Cache::verificationStatus_(const std::string &user_id, lmdb::txn &txn) // key and their master key auto ourKeys = userKeys_(local_user, txn); auto theirKeys = userKeys_(user_id, txn); + if (theirKeys) + status.no_keys = false; + if (!ourKeys || !theirKeys) { verification_storage.status[user_id] = status; return status; diff --git a/src/CacheCryptoStructs.h b/src/CacheCryptoStructs.h index a992fe79..6c402674 100644 --- a/src/CacheCryptoStructs.h +++ b/src/CacheCryptoStructs.h @@ -117,6 +117,8 @@ struct VerificationStatus std::map verified_device_keys; //! Count of unverified devices int unverified_device_count = 0; + // if the keys are not in cache + bool no_keys = false; }; //! In memory cache of verification status From 69e65cef2f3aff2eed7e333e8962776d3057c475 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sat, 14 Aug 2021 02:52:43 +0200 Subject: [PATCH 37/42] Fix shadowing --- src/Cache.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Cache.cpp b/src/Cache.cpp index fccf1c53..8b8b2985 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -4013,9 +4013,8 @@ Cache::markUserKeysOutOfDate(lmdb::txn &txn, UserKeyCache cacheEntry; auto res = db.get(txn, user, oldKeys); if (res) { - auto cacheEntry = - json::parse(std::string_view(oldKeys.data(), oldKeys.size())) - .get(); + cacheEntry = json::parse(std::string_view(oldKeys.data(), oldKeys.size())) + .get(); } cacheEntry.last_changed = sync_token; From 24366b7520e051a1f3dd2827c27a0f2f57c9577d Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sat, 14 Aug 2021 12:19:15 +0200 Subject: [PATCH 38/42] Tab to move down completer --- resources/qml/ForwardCompleter.qml | 3 +++ resources/qml/QuickSwitcher.qml | 3 +++ 2 files changed, 6 insertions(+) diff --git a/resources/qml/ForwardCompleter.qml b/resources/qml/ForwardCompleter.qml index daf73cec..26752f92 100644 --- a/resources/qml/ForwardCompleter.qml +++ b/resources/qml/ForwardCompleter.qml @@ -86,6 +86,9 @@ Popup { } else if (event.key == Qt.Key_Down && completerPopup.opened) { event.accepted = true; completerPopup.down(); + } else if (event.key == Qt.Key_Tab && completerPopup.opened) { + event.accepted = true; + completerPopup.down(); } else if (event.matches(StandardKey.InsertParagraphSeparator)) { completerPopup.finishCompletion(); event.accepted = true; diff --git a/resources/qml/QuickSwitcher.qml b/resources/qml/QuickSwitcher.qml index 61155acf..defcc611 100644 --- a/resources/qml/QuickSwitcher.qml +++ b/resources/qml/QuickSwitcher.qml @@ -45,6 +45,9 @@ Popup { } else if (event.key == Qt.Key_Down && completerPopup.opened) { event.accepted = true; completerPopup.down(); + } else if (event.key == Qt.Key_Tab && completerPopup.opened) { + event.accepted = true; + completerPopup.down(); } else if (event.matches(StandardKey.InsertParagraphSeparator)) { completerPopup.finishCompletion(); event.accepted = true; From 42d2b10d5d53ecc92531491fdf2e27399da08d84 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sat, 14 Aug 2021 17:17:50 +0200 Subject: [PATCH 39/42] Round images in the image provider --- resources/qml/Avatar.qml | 16 +----- resources/qml/MessageView.qml | 1 - resources/qml/Root.qml | 1 - resources/qml/TimelineView.qml | 1 - src/MxcImageProvider.cpp | 96 ++++++++++++++++++++++++++++------ src/MxcImageProvider.h | 7 ++- 6 files changed, 87 insertions(+), 35 deletions(-) diff --git a/resources/qml/Avatar.qml b/resources/qml/Avatar.qml index 9685dde1..c3e8acdb 100644 --- a/resources/qml/Avatar.qml +++ b/resources/qml/Avatar.qml @@ -3,7 +3,6 @@ // SPDX-License-Identifier: GPL-3.0-or-later import "./ui" -import QtGraphicalEffects 1.0 import QtQuick 2.6 import QtQuick.Controls 2.3 import im.nheko 1.0 @@ -50,8 +49,7 @@ Rectangle { smooth: true sourceSize.width: avatar.width sourceSize.height: avatar.height - layer.enabled: true - source: avatar.url + ((avatar.crop || !avatar.url) ? "" : "?scale") + source: avatar.url ? (avatar.url + "?radius=" + radius + ((avatar.crop) ? "" : "&scale")) : "" MouseArea { id: mouseArea @@ -65,18 +63,6 @@ Rectangle { } - layer.effect: OpacityMask { - cached: true - - maskSource: Rectangle { - anchors.fill: parent - width: avatar.width - height: avatar.height - radius: Settings.avatarCircles ? height / 2 : 3 - } - - } - } Rectangle { diff --git a/resources/qml/MessageView.qml b/resources/qml/MessageView.qml index 79cbd700..e5c6b4ec 100644 --- a/resources/qml/MessageView.qml +++ b/resources/qml/MessageView.qml @@ -6,7 +6,6 @@ import "./delegates" import "./emoji" import "./ui" import Qt.labs.platform 1.1 as Platform -import QtGraphicalEffects 1.0 import QtQuick 2.15 import QtQuick.Controls 2.15 import QtQuick.Layouts 1.2 diff --git a/resources/qml/Root.qml b/resources/qml/Root.qml index 79f12bbf..cc7d32ea 100644 --- a/resources/qml/Root.qml +++ b/resources/qml/Root.qml @@ -8,7 +8,6 @@ import "./dialogs" import "./emoji" import "./voip" import Qt.labs.platform 1.1 as Platform -import QtGraphicalEffects 1.0 import QtQuick 2.15 import QtQuick.Controls 2.15 import QtQuick.Layouts 1.3 diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml index 104da160..c8ac6bc7 100644 --- a/resources/qml/TimelineView.qml +++ b/resources/qml/TimelineView.qml @@ -9,7 +9,6 @@ import "./emoji" import "./ui" import "./voip" import Qt.labs.platform 1.1 as Platform -import QtGraphicalEffects 1.0 import QtQuick 2.9 import QtQuick.Controls 2.5 import QtQuick.Layouts 1.3 diff --git a/src/MxcImageProvider.cpp b/src/MxcImageProvider.cpp index b8648269..58078a3b 100644 --- a/src/MxcImageProvider.cpp +++ b/src/MxcImageProvider.cpp @@ -11,6 +11,8 @@ #include #include #include +#include +#include #include #include "Logging.h" @@ -22,14 +24,26 @@ QHash infos; QQuickImageResponse * MxcImageProvider::requestImageResponse(const QString &id, const QSize &requestedSize) { - auto id_ = id; - bool crop = true; - if (id.endsWith("?scale")) { - crop = false; - id_.remove("?scale"); + auto id_ = id; + bool crop = true; + double radius = 0; + + auto queryStart = id.lastIndexOf('?'); + if (queryStart != -1) { + id_ = id.left(queryStart); + auto query = id.midRef(queryStart + 1); + auto queryBits = query.split('&'); + + for (auto b : queryBits) { + if (b == "scale") { + crop = false; + } else if (b.startsWith("radius=")) { + radius = b.mid(7).toDouble(); + } + } } - MxcImageResponse *response = new MxcImageResponse(id_, crop, requestedSize); + MxcImageResponse *response = new MxcImageResponse(id_, crop, radius, requestedSize); pool.start(response); return response; } @@ -53,14 +67,35 @@ MxcImageResponse::run() } emit finished(); }, - m_crop); + m_crop, + m_radius); +} + +static QImage +clipRadius(QImage img, double radius) +{ + QImage out(img.size(), QImage::Format_ARGB32_Premultiplied); + out.fill(Qt::transparent); + + QPainter painter(&out); + painter.setRenderHint(QPainter::Antialiasing, true); + painter.setRenderHint(QPainter::SmoothPixmapTransform, true); + + QPainterPath ppath; + ppath.addRoundedRect(img.rect(), radius, radius); + + painter.setClipPath(ppath); + painter.drawImage(img.rect(), img); + + return out; } void MxcImageProvider::download(const QString &id, const QSize &requestedSize, std::function then, - bool crop) + bool crop, + double radius) { std::optional encryptionInfo; auto temp = infos.find("mxc://" + id); @@ -69,12 +104,13 @@ MxcImageProvider::download(const QString &id, if (requestedSize.isValid() && !encryptionInfo) { QString fileName = - QString("%1_%2x%3_%4") + QString("%1_%2x%3_%4_radius%5") .arg(QString::fromUtf8(id.toUtf8().toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals))) .arg(requestedSize.width()) .arg(requestedSize.height()) - .arg(crop ? "crop" : "scale"); + .arg(crop ? "crop" : "scale") + .arg(radius); QFileInfo fileInfo(QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/media_cache", fileName); @@ -86,6 +122,10 @@ MxcImageProvider::download(const QString &id, image = image.scaled( requestedSize, Qt::KeepAspectRatio, Qt::SmoothTransformation); + if (radius != 0) { + image = clipRadius(std::move(image), radius); + } + if (!image.isNull()) { then(id, requestedSize, image, fileInfo.absoluteFilePath()); return; @@ -100,8 +140,8 @@ MxcImageProvider::download(const QString &id, opts.method = crop ? "crop" : "scale"; http::client()->get_thumbnail( opts, - [fileInfo, requestedSize, then, id](const std::string &res, - mtx::http::RequestErr err) { + [fileInfo, requestedSize, radius, then, id](const std::string &res, + mtx::http::RequestErr err) { if (err || res.empty()) { then(id, QSize(), {}, ""); @@ -113,6 +153,10 @@ MxcImageProvider::download(const QString &id, if (!image.isNull()) { image = image.scaled( requestedSize, Qt::KeepAspectRatio, Qt::SmoothTransformation); + + if (radius != 0) { + image = clipRadius(std::move(image), radius); + } } image.setText("mxc url", "mxc://" + id); if (image.save(fileInfo.absoluteFilePath(), "png")) @@ -126,8 +170,12 @@ MxcImageProvider::download(const QString &id, }); } else { try { - QString fileName = QString::fromUtf8(id.toUtf8().toBase64( - QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals)); + QString fileName = + QString("%1_radius%2") + .arg(QString::fromUtf8(id.toUtf8().toBase64( + QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals))) + .arg(radius); + QFileInfo fileInfo( QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/media_cache", @@ -148,6 +196,11 @@ MxcImageProvider::download(const QString &id, QImage image = utils::readImage(data); image.setText("mxc url", "mxc://" + id); if (!image.isNull()) { + if (radius != 0) { + image = + clipRadius(std::move(image), radius); + } + then(id, requestedSize, image, @@ -158,6 +211,11 @@ MxcImageProvider::download(const QString &id, QImage image = utils::readImageFromFile(fileInfo.absoluteFilePath()); if (!image.isNull()) { + if (radius != 0) { + image = + clipRadius(std::move(image), radius); + } + then(id, requestedSize, image, @@ -169,7 +227,7 @@ MxcImageProvider::download(const QString &id, http::client()->download( "mxc://" + id.toStdString(), - [fileInfo, requestedSize, then, id, encryptionInfo]( + [fileInfo, requestedSize, then, id, radius, encryptionInfo]( const std::string &res, const std::string &, const std::string &originalFilename, @@ -195,6 +253,10 @@ MxcImageProvider::download(const QString &id, auto data = QByteArray(tempData.data(), (int)tempData.size()); QImage image = utils::readImage(data); + if (radius != 0) { + image = clipRadius(std::move(image), radius); + } + image.setText("original filename", QString::fromStdString(originalFilename)); image.setText("mxc url", "mxc://" + id); @@ -205,6 +267,10 @@ MxcImageProvider::download(const QString &id, QImage image = utils::readImageFromFile(fileInfo.absoluteFilePath()); + if (radius != 0) { + image = clipRadius(std::move(image), radius); + } + image.setText("original filename", QString::fromStdString(originalFilename)); image.setText("mxc url", "mxc://" + id); diff --git a/src/MxcImageProvider.h b/src/MxcImageProvider.h index 61d82852..6de83c0e 100644 --- a/src/MxcImageProvider.h +++ b/src/MxcImageProvider.h @@ -19,10 +19,11 @@ class MxcImageResponse , public QRunnable { public: - MxcImageResponse(const QString &id, bool crop, const QSize &requestedSize) + MxcImageResponse(const QString &id, bool crop, double radius, const QSize &requestedSize) : m_id(id) , m_requestedSize(requestedSize) , m_crop(crop) + , m_radius(radius) { setAutoDelete(false); } @@ -39,6 +40,7 @@ public: QSize m_requestedSize; QImage m_image; bool m_crop; + double m_radius; }; class MxcImageProvider @@ -54,7 +56,8 @@ public slots: static void download(const QString &id, const QSize &requestedSize, std::function then, - bool crop = true); + bool crop = true, + double radius = 0); private: QThreadPool pool; From 0da58c476c05c3ec52fe69609b741d06938c9521 Mon Sep 17 00:00:00 2001 From: Callum Brown Date: Sun, 15 Aug 2021 15:51:10 +0100 Subject: [PATCH 40/42] Run linter --- src/RegisterPage.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/RegisterPage.cpp b/src/RegisterPage.cpp index ddd4d47d..fb6a1b97 100644 --- a/src/RegisterPage.cpp +++ b/src/RegisterPage.cpp @@ -492,12 +492,13 @@ RegisterPage::doUIA(const mtx::user_interactive::Unauthorized &unauthorized) QString(), &ok); - if (ok) { - emit registrationWithAuth(mtx::user_interactive::Auth{ - session, mtx::user_interactive::auth::RegistrationToken{token.toStdString()}}); - } else { - emit errorOccurred(); - } + if (ok) { + emit registrationWithAuth(mtx::user_interactive::Auth{ + session, + mtx::user_interactive::auth::RegistrationToken{token.toStdString()}}); + } else { + emit errorOccurred(); + } } else { // use fallback auto dialog = new dialogs::FallbackAuth( From 1e7756c508401cea6f840e46b799f80baf949a7b Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Mon, 16 Aug 2021 12:47:44 +0200 Subject: [PATCH 41/42] Fix dpi scaling of avatars --- resources/qml/Avatar.qml | 6 +++--- src/MxcImageProvider.cpp | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/resources/qml/Avatar.qml b/resources/qml/Avatar.qml index c3e8acdb..4a9a565c 100644 --- a/resources/qml/Avatar.qml +++ b/resources/qml/Avatar.qml @@ -20,7 +20,7 @@ Rectangle { width: 48 height: 48 - radius: Settings.avatarCircles ? height / 2 : 3 + radius: Settings.avatarCircles ? height / 2 : height / 8 color: Nheko.colors.alternateBase Component.onCompleted: { mouseArea.clicked.connect(clicked); @@ -49,7 +49,7 @@ Rectangle { smooth: true sourceSize.width: avatar.width sourceSize.height: avatar.height - source: avatar.url ? (avatar.url + "?radius=" + radius + ((avatar.crop) ? "" : "&scale")) : "" + source: avatar.url ? (avatar.url + "?radius=" + (Settings.avatarCircles ? 100.0 : 25.0) + ((avatar.crop) ? "" : "&scale")) : "" MouseArea { id: mouseArea @@ -71,7 +71,7 @@ Rectangle { visible: !!userid height: avatar.height / 6 width: height - radius: Settings.avatarCircles ? height / 2 : height / 4 + radius: Settings.avatarCircles ? height / 2 : height / 8 color: { switch (TimelineManager.userPresence(userid)) { case "online": diff --git a/src/MxcImageProvider.cpp b/src/MxcImageProvider.cpp index 58078a3b..056374a9 100644 --- a/src/MxcImageProvider.cpp +++ b/src/MxcImageProvider.cpp @@ -82,7 +82,7 @@ clipRadius(QImage img, double radius) painter.setRenderHint(QPainter::SmoothPixmapTransform, true); QPainterPath ppath; - ppath.addRoundedRect(img.rect(), radius, radius); + ppath.addRoundedRect(img.rect(), radius, radius, Qt::SizeMode::RelativeSize); painter.setClipPath(ppath); painter.drawImage(img.rect(), img); From db2a6111f64bafee28daf96b338a1cfa47f4cc7c Mon Sep 17 00:00:00 2001 From: Joseph Donofry Date: Mon, 16 Aug 2021 17:11:16 -0400 Subject: [PATCH 42/42] Update qt5 path in macos deploy.sh script --- .ci/macos/deploy.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci/macos/deploy.sh b/.ci/macos/deploy.sh index 7439a06d..56a1f23a 100755 --- a/.ci/macos/deploy.sh +++ b/.ci/macos/deploy.sh @@ -6,7 +6,7 @@ set -eux #TAG=$(git tag -l --points-at HEAD) # Add Qt binaries to path -PATH=/usr/local/opt/qt/bin/:${PATH} +PATH=/usr/local/opt/qt@5/bin/:${PATH} ( cd build # macdeployqt does not copy symlinks over.