Set Identity server

Part of network/neochat#565
This commit is contained in:
James Graham
2024-05-12 18:22:27 +00:00
parent 1a2272249d
commit 3de943e50f
6 changed files with 411 additions and 0 deletions

View File

@@ -179,6 +179,8 @@ add_library(neochat STATIC
threepidaddhelper.h
jobs/neochatadd3pidjob.cpp
jobs/neochatadd3pidjob.h
identityserverhelper.cpp
identityserverhelper.h
)
set_source_files_properties(qml/OsmLocationPlugin.qml PROPERTIES

View File

@@ -0,0 +1,154 @@
// SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#include "identityserverhelper.h"
#include <QNetworkReply>
#include <KLocalizedString>
#include <Quotient/networkaccessmanager.h>
#include "neochatconnection.h"
IdentityServerHelper::IdentityServerHelper(QObject *parent)
: QObject(parent)
{
}
NeoChatConnection *IdentityServerHelper::connection() const
{
return m_connection;
}
void IdentityServerHelper::setConnection(NeoChatConnection *connection)
{
if (m_connection == connection) {
return;
}
if (m_connection != nullptr) {
m_connection->disconnect(this);
}
m_connection = connection;
Q_EMIT connectionChanged();
Q_EMIT currentServerChanged();
connect(m_connection, &NeoChatConnection::accountDataChanged, this, [this](const QString &type) {
if (type == QLatin1String("m.identity_server")) {
Q_EMIT currentServerChanged();
}
});
}
QString IdentityServerHelper::currentServer() const
{
if (m_connection == nullptr) {
return {};
}
if (!m_connection->hasAccountData(QLatin1String("m.identity_server"))) {
return i18nc("@info", "No identity server configured");
}
const auto url = m_connection->accountData(QLatin1String("m.identity_server"))->contentPart<QUrl>(QLatin1String("base_url"));
if (!url.isEmpty()) {
return url.toString();
}
return i18nc("@info", "No identity server configured");
}
bool IdentityServerHelper::hasCurrentServer() const
{
if (m_connection == nullptr && !m_connection->hasAccountData(QLatin1String("m.identity_server"))) {
return false;
}
const auto url = m_connection->accountData(QLatin1String("m.identity_server"))->contentPart<QUrl>(QLatin1String("base_url"));
if (!url.isEmpty()) {
return true;
}
return false;
}
QString IdentityServerHelper::url() const
{
return m_url;
}
void IdentityServerHelper::setUrl(const QString &url)
{
if (url == m_url) {
return;
}
m_url = url;
Q_EMIT urlChanged();
checkUrl();
}
IdentityServerHelper::IdServerStatus IdentityServerHelper::status() const
{
return m_status;
}
void IdentityServerHelper::checkUrl()
{
if (m_idServerCheckRequest != nullptr) {
m_idServerCheckRequest->abort();
m_idServerCheckRequest.clear();
}
if (m_url == currentServer()) {
m_status = Match;
Q_EMIT statusChanged();
return;
}
if (m_url.isEmpty()) {
m_status = Valid;
Q_EMIT statusChanged();
return;
}
const auto requestUrl = QUrl(m_url + QStringLiteral("/_matrix/identity/v2"));
if (!(requestUrl.scheme() == QStringLiteral("https") || requestUrl.scheme() == QStringLiteral("http"))) {
m_status = Invalid;
Q_EMIT statusChanged();
return;
}
QNetworkRequest request(requestUrl);
m_idServerCheckRequest = Quotient::NetworkAccessManager::instance()->get(request);
connect(m_idServerCheckRequest, &QNetworkReply::finished, this, [this]() {
if (m_idServerCheckRequest->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 200) {
m_status = Valid;
Q_EMIT statusChanged();
} else {
m_status = Invalid;
Q_EMIT statusChanged();
}
});
}
void IdentityServerHelper::setIdentityServer()
{
if (m_url == currentServer()) {
return;
}
m_connection->setAccountData(QLatin1String("m.identity_server"), {{QLatin1String("base_url"), m_url}});
m_status = Ready;
Q_EMIT statusChanged();
}
void IdentityServerHelper::clearIdentityServer()
{
if (currentServer().isEmpty()) {
return;
}
m_connection->setAccountData(QLatin1String("m.identity_server"), {{QLatin1String("base_url"), QString()}});
m_status = Ready;
Q_EMIT statusChanged();
}

103
src/identityserverhelper.h Normal file
View File

@@ -0,0 +1,103 @@
// SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#pragma once
#include <QObject>
#include <QQmlEngine>
#include <Quotient/jobs/basejob.h>
class NeoChatConnection;
/**
* @class IdentityServerHelper
*
* This class is designed to help the process of setting an identity server for the account.
* It will manage the various stages of verification and authentication.
*/
class IdentityServerHelper : public QObject
{
Q_OBJECT
QML_ELEMENT
/**
* @brief The connection to add a 3PID to.
*/
Q_PROPERTY(NeoChatConnection *connection READ connection WRITE setConnection NOTIFY connectionChanged)
/**
* @brief The current identity server.
*/
Q_PROPERTY(QString currentServer READ currentServer NOTIFY currentServerChanged)
/**
* @brief Whether an identity server is currently configured.
*/
Q_PROPERTY(bool hasCurrentServer READ hasCurrentServer NOTIFY currentServerChanged)
/**
* @brief The URL for the desired server.
*/
Q_PROPERTY(QString url READ url WRITE setUrl NOTIFY urlChanged)
/**
* @brief The current status.
*/
Q_PROPERTY(IdServerStatus status READ status NOTIFY statusChanged)
public:
/**
* @brief The current status for adding an identity server
*/
enum IdServerStatus {
Ready, /**< The process is ready to start. I.e. there is no ongoing attempt to set a new server. */
Valid, /**< The server URL is valid. */
Invalid, /**< The server URL is invalid. */
Match, /**< The server URL is the one that is already configured. */
Other, /**< An unknown problem occurred. */
};
Q_ENUM(IdServerStatus)
explicit IdentityServerHelper(QObject *parent = nullptr);
[[nodiscard]] NeoChatConnection *connection() const;
void setConnection(NeoChatConnection *connection);
[[nodiscard]] QString currentServer() const;
[[nodiscard]] bool hasCurrentServer() const;
[[nodiscard]] QString url() const;
void setUrl(const QString &url);
[[nodiscard]] IdServerStatus status() const;
/**
* @brief Set the current URL as the user's identity server.
*
* Will do nothing if the URL isn't a valid identity server.
*/
Q_INVOKABLE void setIdentityServer();
/**
* @brief Clear the user's identity server.
*/
Q_INVOKABLE void clearIdentityServer();
Q_SIGNALS:
void connectionChanged();
void currentServerChanged();
void urlChanged();
void statusChanged();
private:
QPointer<NeoChatConnection> m_connection;
IdServerStatus m_status = Ready;
QString m_url;
QPointer<QNetworkReply> m_idServerCheckRequest;
void checkUrl();
};

View File

@@ -207,6 +207,15 @@ FormCard.FormCardPage {
title: i18n("Phone Numbers")
medium: "msisdn"
}
FormCard.FormHeader {
Layout.fillWidth: true
title: i18n("Identity Server")
}
FormCard.FormCard {
IdentityServerDelegate {
connection: root.connection
}
}
FormCard.FormHeader {
Layout.fillWidth: true
title: i18n("Server Information")

View File

@@ -29,6 +29,7 @@ qt_add_qml_module(settings
DevicesCard.qml
DeviceDelegate.qml
EmoticonFormCard.qml
IdentityServerDelegate.qml
IgnoredUsersDialog.qml
NotificationRuleItem.qml
PasswordSheet.qml

View File

@@ -0,0 +1,142 @@
// SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
import org.kde.kirigamiaddons.formcard as FormCard
import org.kde.neochat
FormCard.AbstractFormDelegate {
id: root
required property NeoChatConnection connection
property bool editServerUrl: false
text: identityServerHelper.currentServer
onClicked: editIdServerButton.toggle()
contentItem: RowLayout {
spacing: Kirigami.Units.largeSpacing
QQC2.Label {
Layout.fillWidth: true
visible: !root.editServerUrl
text: root.text
elide: Text.ElideRight
}
ColumnLayout {
Kirigami.ActionTextField {
id: editUrlField
Layout.fillWidth: true
visible: root.editServerUrl
Accessible.description: i18n("New identity server url")
rightActions: [
Kirigami.Action {
text: i18nc("@action:button", "Cancel editing identity server url")
icon.name: "edit-delete-remove"
onTriggered: editIdServerButton.toggle()
},
Kirigami.Action {
enabled: identityServerHelper.status == IdentityServerHelper.Valid
text: i18nc("@action:button", "Confirm new identity server url")
icon.name: "checkmark"
visible: editUrlField.text !== root.text
onTriggered: {
identityServerHelper.setIdentityServer();
editUrlField.text = "";
editIdServerButton.toggle();
}
}
]
onAccepted: {
identityServerHelper.setIdentityServer()
editUrlField.text = "";
editIdServerButton.toggle();
}
}
Kirigami.InlineMessage {
id: editUrlStatus
visible: root.editServerUrl && text.length > 0 && !warningTimer.running
Layout.topMargin: visible ? Kirigami.Units.smallSpacing : 0
Layout.fillWidth: true
text: switch(identityServerHelper.status) {
case IdentityServerHelper.Invalid:
return i18n("The entered url is not a valid identity server");
case IdentityServerHelper.Match:
return i18n("The entered url is already configured as your identity server");
default:
return "";
}
type: switch(identityServerHelper.status) {
case IdentityServerHelper.Invalid:
return Kirigami.MessageType.Error;
case IdentityServerHelper.Match:
return Kirigami.MessageType.Warning;
default:
return Kirigami.MessageType.Information;
}
Timer {
id: warningTimer
interval: 500
}
}
}
QQC2.ToolButton {
id: editIdServerButton
display: QQC2.AbstractButton.IconOnly
text: i18nc("@action:button", "Edit identity server url")
icon.name: "document-edit"
checkable: true
onCheckedChanged: {
root.editServerUrl = !root.editServerUrl;
if (checked) {
editUrlField.forceActiveFocus();
} else {
editUrlField.text = "";
}
}
QQC2.ToolTip {
text: editIdServerButton.text
delay: Kirigami.Units.toolTipDelay
visible: editIdServerButton.hovered
}
}
QQC2.ToolButton {
id: removeIdServerButton
visible: identityServerHelper.hasCurrentServer
display: QQC2.AbstractButton.IconOnly
text: i18nc("@action:button", "Remove identity server")
icon.name: "edit-delete-remove"
onClicked: {
identityServerHelper.clearIdentityServer();
editUrlField.text = "";
if (editIdServerButton.checked) {
editIdServerButton.toggle();
}
}
QQC2.ToolTip {
text: removeIdServerButton.text
delay: Kirigami.Units.toolTipDelay
visible: removeIdServerButton.hovered
}
}
}
IdentityServerHelper {
id: identityServerHelper
connection: root.connection
url: editUrlField.text
onUrlChanged: warningTimer.restart()
}
}