From 91109ca845b24a910cdbef6200cf3a1021d67861 Mon Sep 17 00:00:00 2001 From: James Graham Date: Sat, 8 Jun 2024 08:42:34 +0000 Subject: [PATCH] Get 3PID binds on startup Get binds on startup and update the staus properly after bind/unbinding 3PIDs --- src/models/threepidmodel.cpp | 82 +++++++++++++++++++++++++++++++++++ src/models/threepidmodel.h | 5 +++ src/settings/ThreePIdCard.qml | 11 ++++- src/threepidbindhelper.cpp | 2 + 4 files changed, 98 insertions(+), 2 deletions(-) diff --git a/src/models/threepidmodel.cpp b/src/models/threepidmodel.cpp index 71086f7f6..90f00cf55 100644 --- a/src/models/threepidmodel.cpp +++ b/src/models/threepidmodel.cpp @@ -3,6 +3,12 @@ #include "threepidmodel.h" +#include +#include + +#include +#include + #include "neochatconnection.h" ThreePIdModel::ThreePIdModel(NeoChatConnection *connection) @@ -30,6 +36,9 @@ QVariant ThreePIdModel::data(const QModelIndex &index, int role) const if (role == MediumRole) { return m_threePIds.at(index.row()).medium; } + if (role == IsBoundRole) { + return m_bindings.contains(m_threePIds.at(index.row()).address); + } return {}; } @@ -45,6 +54,7 @@ QHash ThreePIdModel::roleNames() const return { {AddressRole, QByteArrayLiteral("address")}, {MediumRole, QByteArrayLiteral("medium")}, + {IsBoundRole, QByteArrayLiteral("isBound")}, }; } @@ -57,8 +67,80 @@ void ThreePIdModel::refreshModel() beginResetModel(); m_threePIds = threePIdJob->threepids(); endResetModel(); + + refreshBindStatus(); }); } } +void ThreePIdModel::refreshBindStatus() +{ + const auto connection = dynamic_cast(this->parent()); + if (connection == nullptr || !connection->hasIdentityServer()) { + return; + } + + const auto openIdJob = connection->callApi(connection->userId()); + connect(openIdJob, &Quotient::BaseJob::success, this, [this, connection, openIdJob]() { + const auto requestUrl = QUrl(connection->identityServer().toString() + QStringLiteral("/_matrix/identity/v2/account/register")); + if (!(requestUrl.scheme() == QStringLiteral("https") || requestUrl.scheme() == QStringLiteral("http"))) { + return; + } + + QNetworkRequest request(requestUrl); + auto newRequest = Quotient::NetworkAccessManager::instance()->post(request, QJsonDocument(openIdJob->jsonData()).toJson()); + connect(newRequest, &QNetworkReply::finished, this, [this, connection, newRequest]() { + QJsonObject replyJson = QJsonDocument::fromJson(newRequest->readAll()).object(); + const auto identityServerToken = replyJson[QLatin1String("token")].toString(); + + const auto requestUrl = QUrl(connection->identityServer().toString() + QStringLiteral("/_matrix/identity/v2/hash_details")); + if (!(requestUrl.scheme() == QStringLiteral("https") || requestUrl.scheme() == QStringLiteral("http"))) { + return; + } + + QNetworkRequest hashRequest(requestUrl); + hashRequest.setRawHeader("Authorization", "Bearer " + identityServerToken.toLatin1()); + + auto hashReply = Quotient::NetworkAccessManager::instance()->get(hashRequest); + connect(hashReply, &QNetworkReply::finished, this, [this, connection, identityServerToken, hashReply]() { + QJsonObject replyJson = QJsonDocument::fromJson(hashReply->readAll()).object(); + const auto lookupPepper = replyJson[QLatin1String("lookup_pepper")].toString(); + + const auto requestUrl = QUrl(connection->identityServer().toString() + QStringLiteral("/_matrix/identity/v2/lookup")); + if (!(requestUrl.scheme() == QStringLiteral("https") || requestUrl.scheme() == QStringLiteral("http"))) { + return; + } + + QNetworkRequest lookupRequest(requestUrl); + lookupRequest.setRawHeader("Authorization", "Bearer " + identityServerToken.toLatin1()); + + QJsonObject requestData = { + {QLatin1String("algorithm"), QLatin1String("none")}, + {QLatin1String("pepper"), lookupPepper}, + }; + QJsonArray idLookups; + for (const auto &id : m_threePIds) { + idLookups += QStringLiteral("%1 %2").arg(id.address, id.medium); + } + requestData[QLatin1String("addresses")] = idLookups; + + auto lookupReply = Quotient::NetworkAccessManager::instance()->post(lookupRequest, QJsonDocument(requestData).toJson(QJsonDocument::Compact)); + connect(lookupReply, &QNetworkReply::finished, this, [this, connection, lookupReply]() { + beginResetModel(); + m_bindings.clear(); + + QJsonObject mappings = QJsonDocument::fromJson(lookupReply->readAll()).object()[QLatin1String("mappings")].toObject(); + for (const auto &id : mappings.keys()) { + if (mappings[id] == connection->userId()) { + m_bindings += id.section(u' ', 0, 0); + } + } + + endResetModel(); + }); + }); + }); + }); +} + #include "moc_threepidmodel.cpp" diff --git a/src/models/threepidmodel.h b/src/models/threepidmodel.h index 59662acab..beb0ff1cf 100644 --- a/src/models/threepidmodel.h +++ b/src/models/threepidmodel.h @@ -28,6 +28,7 @@ public: enum EventRoles { AddressRole = Qt::DisplayRole, /**< The third-party identifier address. */ MediumRole, /**< The medium of the third-party identifier. One of: [email, msisdn]. */ + IsBoundRole, /**< Whether the 3PID is bound to the current identity server. */ }; explicit ThreePIdModel(NeoChatConnection *parent); @@ -57,4 +58,8 @@ public: private: QVector m_threePIds; + + QList m_bindings; + + void refreshBindStatus(); }; diff --git a/src/settings/ThreePIdCard.qml b/src/settings/ThreePIdCard.qml index e5650cef6..ccfbe8e6d 100644 --- a/src/settings/ThreePIdCard.qml +++ b/src/settings/ThreePIdCard.qml @@ -37,6 +37,7 @@ ColumnLayout { id: threePIdDelegate required property string address required property string medium + required property bool isBound contentItem: ColumnLayout { RowLayout { @@ -50,7 +51,13 @@ ColumnLayout { color: Kirigami.Theme.textColor } QQC2.ToolButton { - visible: threePIdBindHelper.bindStatus === ThreePIdBindHelper.Ready && root.connection.hasIdentityServer + visible: root.connection.hasIdentityServer && threePIdDelegate.isBound + text: i18nc("@action:button", "Hide") + icon.name: "hide_table_row" + onClicked: threePIdBindHelper.unbind3PId(threePIdDelegate.address, threePIdDelegate.medium) + } + QQC2.ToolButton { + visible: threePIdBindHelper.bindStatus === ThreePIdBindHelper.Ready && root.connection.hasIdentityServer && !threePIdDelegate.isBound text: i18nc("@action:button", "Share") icon.name: "send-to-symbolic" onClicked: threePIdBindHelper.bindStatus === ThreePIdBindHelper.Verification ? threePIdBindHelper.finalizeNewIdBind() : threePIdBindHelper.initiateNewIdBind() @@ -70,7 +77,7 @@ ColumnLayout { type: threePIdBindHelper.statusType } RowLayout { - visible: threePIdBindHelper.bindStatus !== ThreePIdBindHelper.Ready + visible: threePIdBindHelper.bindStatus !== ThreePIdBindHelper.Ready && threePIdBindHelper.bindStatus !== ThreePIdBindHelper.Success Item { Layout.fillWidth: true } diff --git a/src/threepidbindhelper.cpp b/src/threepidbindhelper.cpp index a893e34f6..6f6ba3f0e 100644 --- a/src/threepidbindhelper.cpp +++ b/src/threepidbindhelper.cpp @@ -192,6 +192,7 @@ void ThreePIdBindHelper::finalizeNewIdBind() connect(job, &Quotient::BaseJob::success, this, [this] { m_bindStatus = Success; Q_EMIT bindStatusChanged(); + m_connection->threePIdModel()->refreshModel(); }); connect(job, &Quotient::BaseJob::failure, this, [this, job]() { if (job->jsonData()[QLatin1String("errcode")] == QLatin1String("M_SESSION_NOT_VALIDATED")) { @@ -208,6 +209,7 @@ void ThreePIdBindHelper::unbind3PId(const QString &threePId, const QString &type { const auto job = m_connection->callApi(type, threePId); connect(job, &Quotient::BaseJob::success, this, [this]() { + cancel(); m_connection->threePIdModel()->refreshModel(); }); }