Implement request for user data erasure

This adds UI for MSC4025 to the account deactivation dialog, if the
server supports it. We also switch away from our
customDeactivateAccountJob to libQuotient's.

Fixes #670.
This commit is contained in:
Joshua Goins
2024-12-24 21:45:08 -05:00
parent 2a9c75e24f
commit d7202ae0a7
6 changed files with 43 additions and 47 deletions

View File

@@ -126,8 +126,6 @@ add_library(neochat STATIC
registration.cpp registration.cpp
neochatconnection.cpp neochatconnection.cpp
neochatconnection.h neochatconnection.h
jobs/neochatdeactivateaccountjob.cpp
jobs/neochatdeactivateaccountjob.h
jobs/neochatgetcommonroomsjob.cpp jobs/neochatgetcommonroomsjob.cpp
jobs/neochatgetcommonroomsjob.h jobs/neochatgetcommonroomsjob.h
mediasizehelper.cpp mediasizehelper.cpp

View File

@@ -1,14 +0,0 @@
// SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
// SPDX-License-Identifier: GPL-2.0-or-later
#include "neochatdeactivateaccountjob.h"
using namespace Quotient;
NeoChatDeactivateAccountJob::NeoChatDeactivateAccountJob(const std::optional<QJsonObject> &auth)
: BaseJob(HttpVerb::Post, u"DisableDeviceJob"_s, "_matrix/client/v3/account/deactivate")
{
QJsonObject data;
addParam<IfNotEmpty>(data, u"auth"_s, auth);
setRequestData(data);
}

View File

@@ -1,12 +0,0 @@
// SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <Quotient/jobs/basejob.h>
class NeoChatDeactivateAccountJob : public Quotient::BaseJob
{
public:
explicit NeoChatDeactivateAccountJob(const std::optional<QJsonObject> &auth = {});
};

View File

@@ -6,7 +6,6 @@
#include <QImageReader> #include <QImageReader>
#include <QJsonDocument> #include <QJsonDocument>
#include "jobs/neochatdeactivateaccountjob.h"
#include "neochatconfig.h" #include "neochatconfig.h"
#include "neochatroom.h" #include "neochatroom.h"
#include "spacehierarchycache.h" #include "spacehierarchycache.h"
@@ -142,6 +141,8 @@ void NeoChatConnection::connectSignals()
connect(job, &GetVersionsJob::success, this, [this, job] { connect(job, &GetVersionsJob::success, this, [this, job] {
m_canCheckMutualRooms = job->unstableFeatures().contains("uk.half-shot.msc2666.query_mutual_rooms"_L1); m_canCheckMutualRooms = job->unstableFeatures().contains("uk.half-shot.msc2666.query_mutual_rooms"_L1);
Q_EMIT canCheckMutualRoomsChanged(); Q_EMIT canCheckMutualRoomsChanged();
m_canEraseData = job->unstableFeatures().contains("org.matrix.msc4025"_L1) || job->versions().count("v1.10"_L1);
Q_EMIT canEraseDataChanged();
}); });
}, },
Qt::SingleShotConnection); Qt::SingleShotConnection);
@@ -255,20 +256,20 @@ QString NeoChatConnection::label() const
return accountDataJson("org.kde.neochat.account_label"_L1)["account_label"_L1].toString(); return accountDataJson("org.kde.neochat.account_label"_L1)["account_label"_L1].toString();
} }
void NeoChatConnection::deactivateAccount(const QString &password) void NeoChatConnection::deactivateAccount(const QString &password, const bool erase)
{ {
auto job = callApi<NeoChatDeactivateAccountJob>(); auto job = callApi<DeactivateAccountJob>();
connect(job, &BaseJob::result, this, [this, job, password] { connect(job, &BaseJob::result, this, [this, job, password, erase] {
if (job->error() == 103) { if (job->error() == 103) {
QJsonObject replyData = job->jsonData(); QJsonObject replyData = job->jsonData();
QJsonObject authData; AuthenticationData authData;
authData["session"_L1] = replyData["session"_L1]; authData.session = replyData["session"_L1].toString();
authData["password"_L1] = password; authData.authInfo["password"_L1] = password;
authData["type"_L1] = "m.login.password"_L1; authData.type = "m.login.password"_L1;
authData["user"_L1] = user()->id(); authData.authInfo["user"_L1] = user()->id();
QJsonObject identifier = {{"type"_L1, "m.id.user"_L1}, {"user"_L1, user()->id()}}; QJsonObject identifier = {{"type"_L1, "m.id.user"_L1}, {"user"_L1, user()->id()}};
authData["identifier"_L1] = identifier; authData.authInfo["identifier"_L1] = identifier;
auto innerJob = callApi<NeoChatDeactivateAccountJob>(authData); auto innerJob = callApi<DeactivateAccountJob>(authData, QString{}, erase);
connect(innerJob, &BaseJob::success, this, [this]() { connect(innerJob, &BaseJob::success, this, [this]() {
logout(false); logout(false);
}); });
@@ -548,4 +549,9 @@ KeyImport::Error NeoChatConnection::exportMegolmSessions(const QString &passphra
return KeyImport::Success; return KeyImport::Success;
} }
bool NeoChatConnection::canEraseData() const
{
return m_canEraseData;
}
#include "moc_neochatconnection.cpp" #include "moc_neochatconnection.cpp"

View File

@@ -87,6 +87,11 @@ class NeoChatConnection : public Quotient::Connection
*/ */
Q_PROPERTY(bool canCheckMutualRooms READ canCheckMutualRooms NOTIFY canCheckMutualRoomsChanged) Q_PROPERTY(bool canCheckMutualRooms READ canCheckMutualRooms NOTIFY canCheckMutualRoomsChanged)
/**
* @brief Whether the server supports erasing user data when deactivating the account. This checks for MSC4025.
*/
Q_PROPERTY(bool canEraseData READ canEraseData NOTIFY canEraseDataChanged)
public: public:
/** /**
* @brief Defines the status after an attempt to change the password on an account. * @brief Defines the status after an attempt to change the password on an account.
@@ -104,6 +109,7 @@ public:
Q_INVOKABLE void logout(bool serverSideLogout); Q_INVOKABLE void logout(bool serverSideLogout);
Q_INVOKABLE QVariantList getSupportedRoomVersions() const; Q_INVOKABLE QVariantList getSupportedRoomVersions() const;
bool canCheckMutualRooms() const; bool canCheckMutualRooms() const;
bool canEraseData() const;
/** /**
* @brief Change the password for an account. * @brief Change the password for an account.
@@ -123,7 +129,7 @@ public:
[[nodiscard]] QString label() const; [[nodiscard]] QString label() const;
void setLabel(const QString &label); void setLabel(const QString &label);
Q_INVOKABLE void deactivateAccount(const QString &password); Q_INVOKABLE void deactivateAccount(const QString &password, bool erase);
ThreePIdModel *threePIdModel() const; ThreePIdModel *threePIdModel() const;
@@ -194,6 +200,7 @@ Q_SIGNALS:
void userConsentRequired(QUrl url); void userConsentRequired(QUrl url);
void badgeNotificationCountChanged(NeoChatConnection *connection, int count); void badgeNotificationCountChanged(NeoChatConnection *connection, int count);
void canCheckMutualRoomsChanged(); void canCheckMutualRoomsChanged();
void canEraseDataChanged();
/** /**
* @brief Request a message be shown to the user of the given type. * @brief Request a message be shown to the user of the given type.
@@ -223,4 +230,5 @@ private:
QCache<QUrl, LinkPreviewer> m_linkPreviewers; QCache<QUrl, LinkPreviewer> m_linkPreviewers;
bool m_canCheckMutualRooms = false; bool m_canCheckMutualRooms = false;
bool m_canEraseData = false;
}; };

View File

@@ -2,6 +2,7 @@
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
import QtQuick import QtQuick
import QtQuick.Layouts
import QtQuick.Controls as QQC2 import QtQuick.Controls as QQC2
import org.kde.kirigamiaddons.formcard as FormCard import org.kde.kirigamiaddons.formcard as FormCard
@@ -18,12 +19,21 @@ Kirigami.PromptDialog {
dialogType: Kirigami.PromptDialog.Warning dialogType: Kirigami.PromptDialog.Warning
mainItem: FormCard.FormTextFieldDelegate { mainItem: ColumnLayout {
id: passwordField FormCard.FormTextFieldDelegate {
label: i18nc("@label:textbox", "Password") id: passwordField
echoMode: TextInput.Password label: i18nc("@label:textbox", "Password")
horizontalPadding: 0 echoMode: TextInput.Password
bottomPadding: 0 horizontalPadding: 0
bottomPadding: 0
}
FormCard.FormCheckDelegate {
id: eraseDelegate
text: i18nc("@label:checkbox", "Erase Data")
description: i18nc("@info", "Request your server to delete as much user data as possible.")
visible: connection.canEraseData
}
} }
footer: QQC2.DialogButtonBox { footer: QQC2.DialogButtonBox {
@@ -34,7 +44,7 @@ Kirigami.PromptDialog {
icon.name: "emblem-warning" icon.name: "emblem-warning"
enabled: passwordField.text.length > 0 enabled: passwordField.text.length > 0
onClicked: { onClicked: {
root.connection.deactivateAccount(passwordField.text); root.connection.deactivateAccount(passwordField.text, eraseDelegate.checked);
root.closeDialog(); root.closeDialog();
} }
} }