267 lines
8.5 KiB
C++
267 lines
8.5 KiB
C++
// SPDX-FileCopyrightText: 2023 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 "accountmanager.h"
|
|
|
|
#include <QCoreApplication>
|
|
#include <QTimer>
|
|
|
|
#include <KLocalizedString>
|
|
|
|
#include <Quotient/settings.h>
|
|
|
|
#include "neochatroom.h"
|
|
|
|
#include "general_logging.h"
|
|
|
|
using namespace Qt::StringLiterals;
|
|
|
|
AccountManager::AccountManager(bool testMode, QObject *parent)
|
|
: QObject(parent)
|
|
, m_accountRegistry(new Quotient::AccountRegistry(this))
|
|
{
|
|
if (!testMode) {
|
|
QTimer::singleShot(0, this, [this] {
|
|
loadAccountsFromCache();
|
|
});
|
|
} else {
|
|
auto connection = new NeoChatConnection(QUrl(u"https://localhost:1234"_s), this);
|
|
connection->assumeIdentity(u"@user:localhost:1234"_s, u"device_1234"_s, u"token_1234"_s);
|
|
m_accountRegistry->add(connection);
|
|
connection->syncLoop();
|
|
}
|
|
}
|
|
|
|
Quotient::AccountRegistry *AccountManager::accounts()
|
|
{
|
|
return m_accountRegistry;
|
|
}
|
|
|
|
void AccountManager::loadAccountsFromCache()
|
|
{
|
|
for (const auto &accountId : Quotient::SettingsGroup("Accounts"_L1).childGroups()) {
|
|
Quotient::AccountSettings account{accountId};
|
|
m_accountsLoading += accountId;
|
|
Q_EMIT accountsLoadingChanged();
|
|
if (account.homeserver().isEmpty()) {
|
|
continue;
|
|
}
|
|
auto accessTokenLoadingJob = loadAccessTokenFromKeyChain(account.userId());
|
|
connect(accessTokenLoadingJob, &QKeychain::Job::finished, this, [accountId, this, accessTokenLoadingJob](QKeychain::Job *) {
|
|
if (accessTokenLoadingJob->error() != QKeychain::Error::NoError) {
|
|
return;
|
|
}
|
|
|
|
Quotient::AccountSettings account{accountId};
|
|
auto connection = new NeoChatConnection(account.homeserver());
|
|
m_connectionsLoading[accountId] = connection;
|
|
connect(connection, &NeoChatConnection::connected, this, [this, connection, accountId] {
|
|
connection->loadState();
|
|
if (connection->allRooms().size() == 0 || connection->allRooms()[0]->currentState().get<Quotient::RoomCreateEvent>()) {
|
|
addConnection(connection);
|
|
m_accountsLoading.removeAll(connection->userId());
|
|
m_connectionsLoading.remove(accountId);
|
|
Q_EMIT accountsLoadingChanged();
|
|
} else {
|
|
connect(
|
|
connection->allRooms()[0],
|
|
&NeoChatRoom::baseStateLoaded,
|
|
this,
|
|
[this, connection, accountId]() {
|
|
addConnection(connection);
|
|
m_accountsLoading.removeAll(connection->userId());
|
|
m_connectionsLoading.remove(accountId);
|
|
Q_EMIT accountsLoadingChanged();
|
|
},
|
|
Qt::SingleShotConnection);
|
|
}
|
|
});
|
|
connection->assumeIdentity(account.userId(), account.deviceId(), QString::fromLatin1(accessTokenLoadingJob->binaryData()));
|
|
});
|
|
}
|
|
}
|
|
|
|
QStringList AccountManager::accountsLoading() const
|
|
{
|
|
return m_accountsLoading;
|
|
}
|
|
|
|
void AccountManager::saveAccessTokenToKeyChain(NeoChatConnection *connection)
|
|
{
|
|
if (!connection) {
|
|
return;
|
|
}
|
|
const auto userId = connection->userId();
|
|
|
|
qCDebug(GENERAL) << "Save the access token to the keychain for " << userId;
|
|
auto job = new QKeychain::WritePasswordJob(qAppName());
|
|
job->setAutoDelete(true);
|
|
job->setKey(userId);
|
|
job->setBinaryData(connection->accessToken());
|
|
connect(job, &QKeychain::WritePasswordJob::finished, this, [job]() {
|
|
if (job->error()) {
|
|
qWarning() << "Could not save access token to the keychain: " << qPrintable(job->errorString());
|
|
}
|
|
});
|
|
job->start();
|
|
}
|
|
|
|
QKeychain::ReadPasswordJob *AccountManager::loadAccessTokenFromKeyChain(const QString &userId)
|
|
{
|
|
qCDebug(GENERAL) << "Reading access token from the keychain for" << userId;
|
|
auto job = new QKeychain::ReadPasswordJob(qAppName(), this);
|
|
job->setKey(userId);
|
|
|
|
// Handling of errors
|
|
connect(job, &QKeychain::Job::finished, this, [this, job]() {
|
|
if (job->error() == QKeychain::Error::NoError) {
|
|
return;
|
|
}
|
|
|
|
switch (job->error()) {
|
|
case QKeychain::EntryNotFound:
|
|
Q_EMIT errorOccured(i18n("Access token wasn't found: Maybe it was deleted?"));
|
|
break;
|
|
case QKeychain::AccessDeniedByUser:
|
|
case QKeychain::AccessDenied:
|
|
Q_EMIT errorOccured(i18n("Access to keychain was denied: Please allow NeoChat to read the access token"));
|
|
break;
|
|
case QKeychain::NoBackendAvailable:
|
|
Q_EMIT errorOccured(i18n("No keychain available: Please install a keychain, e.g. KWallet or GNOME keyring on Linux"));
|
|
break;
|
|
case QKeychain::OtherError:
|
|
Q_EMIT errorOccured(i18n("Unable to read access token: %1", job->errorString()));
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
});
|
|
job->start();
|
|
|
|
return job;
|
|
}
|
|
|
|
NeoChatConnection *AccountManager::activeConnection() const
|
|
{
|
|
return m_activeConnection;
|
|
}
|
|
|
|
void AccountManager::setActiveConnection(NeoChatConnection *connection)
|
|
{
|
|
if (connection == m_activeConnection) {
|
|
return;
|
|
}
|
|
|
|
const auto oldConnection = m_activeConnection;
|
|
m_activeConnection = connection;
|
|
Q_EMIT activeConnectionChanged(oldConnection, m_activeConnection);
|
|
}
|
|
|
|
void AccountManager::addConnection(NeoChatConnection *connection)
|
|
{
|
|
Q_ASSERT_X(connection, __FUNCTION__, "Attempt to add a null connection");
|
|
|
|
saveAccessTokenToKeyChain(connection);
|
|
m_accountRegistry->add(connection);
|
|
|
|
connection->setLazyLoading(true);
|
|
|
|
connect(connection, &NeoChatConnection::syncDone, this, [connection] {
|
|
connection->sync(30000);
|
|
connection->saveState();
|
|
});
|
|
connect(connection, &NeoChatConnection::loggedOut, this, [this, connection] {
|
|
// Only set the connection if the account being logged out is currently active
|
|
if (m_accountRegistry->accounts().count() == 1 && connection == activeConnection()) {
|
|
setActiveConnection(dynamic_cast<NeoChatConnection *>(m_accountRegistry->accounts()[0]));
|
|
} else {
|
|
setActiveConnection(nullptr);
|
|
}
|
|
|
|
dropConnection(connection);
|
|
});
|
|
|
|
connection->sync();
|
|
|
|
Q_EMIT connectionAdded(connection);
|
|
}
|
|
|
|
void AccountManager::dropConnection(const QString &userId)
|
|
{
|
|
if (userId.isEmpty()) {
|
|
return;
|
|
}
|
|
|
|
// There are 3 possible states:
|
|
// - in m_accountsLoading trying to loadAccessTokenFromKeyChain()
|
|
// - in m_connectionsLoading
|
|
// - in the AccountRegistry
|
|
// Check all locations.
|
|
|
|
if (dropAccountLoading(userId)) {
|
|
return;
|
|
}
|
|
if (dropConnectionLoading(m_connectionsLoading.value(userId, nullptr))) {
|
|
return;
|
|
}
|
|
if (const auto connection = dynamic_cast<NeoChatConnection *>(m_accountRegistry->get(userId))) {
|
|
dropRegistry(connection);
|
|
}
|
|
}
|
|
|
|
void AccountManager::dropConnection(NeoChatConnection *connection)
|
|
{
|
|
if (!connection) {
|
|
return;
|
|
}
|
|
|
|
// There are 3 possible states:
|
|
// - in m_accountsLoading trying to loadAccessTokenFromKeyChain()
|
|
// - in m_connectionsLoading
|
|
// - in the AccountRegistry
|
|
// Check all locations.
|
|
|
|
if (dropAccountLoading(connection->userId())) {
|
|
return;
|
|
}
|
|
if (dropConnectionLoading(connection)) {
|
|
return;
|
|
}
|
|
dropRegistry(connection);
|
|
}
|
|
|
|
bool AccountManager::dropAccountLoading(const QString &userId)
|
|
{
|
|
if (!m_accountsLoading.contains(userId)) {
|
|
return false;
|
|
}
|
|
|
|
m_accountsLoading.removeAll(userId);
|
|
Q_EMIT accountsLoadingChanged();
|
|
return true;
|
|
}
|
|
|
|
bool AccountManager::dropConnectionLoading(NeoChatConnection *connection)
|
|
{
|
|
if (!connection || (m_connectionsLoading.contains(connection->userId()) && m_connectionsLoading.value(connection->userId(), nullptr) == connection)) {
|
|
return false;
|
|
}
|
|
|
|
m_connectionsLoading.remove(connection->userId());
|
|
Quotient::SettingsGroup("Accounts"_L1).remove(connection->userId());
|
|
Q_EMIT connectionLoadingChanged();
|
|
return true;
|
|
}
|
|
|
|
bool AccountManager::dropRegistry(NeoChatConnection *connection)
|
|
{
|
|
if (!m_accountRegistry->isLoggedIn(connection->userId())) {
|
|
return false;
|
|
}
|
|
|
|
connection->disconnect(this);
|
|
m_accountRegistry->drop(connection);
|
|
Q_EMIT connectionDropped(connection);
|
|
return true;
|
|
}
|