File indexing completed on 2024-05-12 16:28:04
0001 // SPDX-FileCopyrightText: 2021 kaniini <https://git.pleroma.social/kaniini> 0002 // SPDX-FileCopyrightText: 2021 Carl Schwan <carl@carlschwan.eu> 0003 // SPDX-License-Identifier: GPL-3.0-only 0004 0005 #include "accountmanager.h" 0006 #include "account.h" 0007 #include "accountconfig.h" 0008 #include "config.h" 0009 #include "network/networkaccessmanagerfactory.h" 0010 #include "tokodon_debug.h" 0011 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0012 #include <qt5keychain/keychain.h> 0013 #else 0014 #include <qt6keychain/keychain.h> 0015 #endif 0016 0017 AccountManager::AccountManager(QObject *parent) 0018 : QAbstractListModel(parent) 0019 , m_selected_account(nullptr) 0020 , m_qnam(NetworkAccessManagerFactory().create(this)) 0021 { 0022 migrateSettings(); 0023 loadFromSettings(); 0024 0025 connect(this, &AccountManager::accountSelected, this, [=](AbstractAccount *account) { 0026 if (account != nullptr) { 0027 account->checkForFollowRequests(); 0028 } 0029 }); 0030 } 0031 0032 AccountManager::~AccountManager() 0033 { 0034 for (auto a : std::as_const(m_accounts)) { 0035 delete a; 0036 } 0037 0038 m_accounts.clear(); 0039 } 0040 0041 QVariant AccountManager::data(const QModelIndex &index, int role) const 0042 { 0043 if (!index.isValid()) { 0044 return {}; 0045 } 0046 0047 auto account = m_accounts.at(index.row()); 0048 if (account->identity() == nullptr) { 0049 return {}; 0050 } 0051 switch (role) { 0052 case Qt::DisplayRole: 0053 case DisplayNameRole: 0054 return account->identity()->displayNameHtml(); 0055 case DescriptionRole: 0056 return account->identity()->account(); 0057 case InstanceRole: 0058 return account->instanceName(); 0059 case AccountRole: 0060 return QVariant::fromValue(m_accounts[index.row()]); 0061 } 0062 return {}; 0063 } 0064 0065 int AccountManager::rowCount(const QModelIndex &index) const 0066 { 0067 Q_UNUSED(index) 0068 return m_accounts.size(); 0069 } 0070 0071 QHash<int, QByteArray> AccountManager::roleNames() const 0072 { 0073 return { 0074 {Qt::DisplayRole, QByteArrayLiteral("display")}, 0075 {DisplayNameRole, QByteArrayLiteral("displayName")}, 0076 {AccountRole, QByteArrayLiteral("account")}, 0077 {DescriptionRole, QByteArrayLiteral("description")}, 0078 {InstanceRole, QByteArrayLiteral("instance")}, 0079 }; 0080 } 0081 0082 AccountManager &AccountManager::instance() 0083 { 0084 static AccountManager accountManager; 0085 return accountManager; 0086 } 0087 0088 AbstractAccount *AccountManager::createNewAccount(const QString &instanceUri, bool ignoreSslErrors) 0089 { 0090 return new Account(instanceUri, m_qnam, ignoreSslErrors, this); 0091 } 0092 0093 bool AccountManager::hasAccounts() const 0094 { 0095 return !m_accounts.empty(); 0096 } 0097 0098 bool AccountManager::hasAnyAccounts() const 0099 { 0100 return m_hasAnyAccounts; 0101 } 0102 0103 void AccountManager::addAccount(AbstractAccount *account) 0104 { 0105 beginInsertRows(QModelIndex(), m_accounts.size(), m_accounts.size()); 0106 m_accounts.append(account); 0107 endInsertRows(); 0108 0109 Q_EMIT accountAdded(account); 0110 Q_EMIT accountsChanged(); 0111 connect(account, &Account::identityChanged, this, [this, account]() { 0112 childIdentityChanged(account); 0113 account->writeToSettings(); 0114 }); 0115 connect(account, &Account::authenticated, this, [this]() { 0116 Q_EMIT dataChanged(index(0, 0), index(m_accounts.size() - 1, 0)); 0117 }); 0118 0119 connect(account, &Account::fetchedTimeline, this, [this, account](QString original_name, QList<Post *> posts) { 0120 Q_EMIT fetchedTimeline(account, original_name, posts); 0121 }); 0122 connect(account, &Account::invalidated, this, [this, account]() { 0123 Q_EMIT invalidated(account); 0124 }); 0125 connect(account, &Account::fetchedInstanceMetadata, this, [this, account]() { 0126 Q_EMIT fetchedInstanceMetadata(account); 0127 Q_EMIT dataChanged(index(0, 0), index(m_accounts.size() - 1, 0)); 0128 }); 0129 connect(account, &Account::invalidatedPost, this, [this, account](Post *p) { 0130 Q_EMIT invalidatedPost(account, p); 0131 }); 0132 connect(account, &Account::notification, this, [this, account](std::shared_ptr<Notification> n) { 0133 Q_EMIT notification(account, n); 0134 }); 0135 0136 if (m_selected_account == nullptr) { 0137 m_selected_account = account; 0138 Q_EMIT accountSelected(m_selected_account); 0139 } 0140 } 0141 0142 void AccountManager::childIdentityChanged(AbstractAccount *account) 0143 { 0144 Q_EMIT identityChanged(account); 0145 0146 const auto idx = m_accounts.indexOf(account); 0147 Q_EMIT dataChanged(index(idx, 0), index(idx, 0)); 0148 } 0149 0150 void AccountManager::removeAccount(AbstractAccount *account) 0151 { 0152 // remove from settings 0153 auto config = KSharedConfig::openStateConfig(); 0154 config->deleteGroup(account->settingsGroupName()); 0155 config->sync(); 0156 0157 auto accessTokenJob = new QKeychain::DeletePasswordJob{"Tokodon"}; 0158 accessTokenJob->setKey(account->accessTokenKey()); 0159 accessTokenJob->start(); 0160 0161 auto clientSecretJob = new QKeychain::DeletePasswordJob{"Tokodon"}; 0162 clientSecretJob->setKey(account->clientSecretKey()); 0163 clientSecretJob->start(); 0164 0165 const auto index = m_accounts.indexOf(account); 0166 beginRemoveRows(QModelIndex(), index, index); 0167 m_accounts.removeOne(account); 0168 endRemoveRows(); 0169 0170 if (hasAccounts()) { 0171 m_selected_account = m_accounts[0]; 0172 } else { 0173 m_selected_account = nullptr; 0174 } 0175 Q_EMIT accountSelected(m_selected_account); 0176 0177 Q_EMIT accountRemoved(account); 0178 Q_EMIT accountsChanged(); 0179 } 0180 0181 void AccountManager::reloadAccounts() 0182 { 0183 for (auto account : std::as_const(m_accounts)) { 0184 account->validateToken(); 0185 } 0186 0187 Q_EMIT accountsReloaded(); 0188 } 0189 0190 void AccountManager::selectAccount(AbstractAccount *account, bool explicitUserAction) 0191 { 0192 if (!m_accounts.contains(account)) { 0193 qDebug() << "WTF: attempt to select unmanaged account" << account; 0194 return; 0195 } 0196 0197 m_selected_account = account; 0198 0199 if (explicitUserAction) { 0200 auto config = Config::self(); 0201 config->setLastUsedAccount(account->settingsGroupName()); 0202 config->save(); 0203 } 0204 0205 Q_EMIT accountSelected(account); 0206 } 0207 0208 AbstractAccount *AccountManager::selectedAccount() const 0209 { 0210 return m_selected_account; 0211 } 0212 0213 QString AccountManager::selectedAccountId() const 0214 { 0215 return m_selected_account->identity()->id(); 0216 } 0217 0218 int AccountManager::selectedIndex() const 0219 { 0220 for (int i = 0; i < m_accounts.length(); i++) { 0221 if (m_selected_account == m_accounts[i]) { 0222 return i; 0223 } 0224 } 0225 return -1; 0226 } 0227 0228 void AccountManager::loadFromSettings() 0229 { 0230 qCDebug(TOKODON_LOG) << "Loading accounts from settings."; 0231 0232 auto config = KSharedConfig::openStateConfig(); 0233 for (const auto &id : config->groupList()) { 0234 if (id.contains('@')) { 0235 auto accountConfig = AccountConfig{id}; 0236 0237 int index = m_accountStatus.size(); 0238 m_accountStatus.push_back(AccountStatus::NotLoaded); 0239 0240 auto account = new Account(accountConfig, m_qnam); 0241 connect(account, &Account::authenticated, this, [=](bool successful) { 0242 if (successful && account->haveToken() && account->hasName() && account->hasInstanceUrl()) { 0243 m_accountStatus[index] = AccountStatus::Loaded; 0244 0245 addAccount(account); 0246 } else { 0247 m_accountStatus[index] = AccountStatus::InvalidCredentials; 0248 0249 delete account; 0250 } 0251 0252 checkIfLoadingFinished(); 0253 }); 0254 } 0255 } 0256 0257 checkIfLoadingFinished(); 0258 } 0259 0260 KAboutData AccountManager::aboutData() const 0261 { 0262 return m_aboutData; 0263 } 0264 0265 void AccountManager::setAboutData(const KAboutData &aboutData) 0266 { 0267 m_aboutData = aboutData; 0268 Q_EMIT aboutDataChanged(); 0269 } 0270 0271 void AccountManager::checkIfLoadingFinished() 0272 { 0273 // no accounts at all 0274 if (m_accountStatus.empty()) { 0275 m_ready = true; 0276 Q_EMIT accountsReady(); 0277 return; 0278 } 0279 0280 bool finished = true; 0281 for (auto status : m_accountStatus) { 0282 if (status == AccountStatus::NotLoaded) 0283 finished = false; 0284 } 0285 0286 if (!finished) { 0287 return; 0288 } 0289 0290 qCDebug(TOKODON_LOG) << "Accounts have finished loading."; 0291 0292 auto config = Config::self(); 0293 0294 for (auto account : m_accounts) { 0295 // old LastUsedAccount values used to be only username 0296 const bool isOldVersion = !config->lastUsedAccount().contains(QLatin1Char('@')); 0297 const bool isEmpty = config->lastUsedAccount().isEmpty() || config->lastUsedAccount() == '@'; 0298 const bool matchesNewFormat = account->settingsGroupName() == config->lastUsedAccount(); 0299 const bool matchesOldFormat = account->username() == config->lastUsedAccount(); 0300 0301 const bool isValid = isEmpty || (isOldVersion ? matchesOldFormat : matchesNewFormat); 0302 0303 if (isValid) { 0304 selectAccount(account, false); 0305 break; 0306 } 0307 } 0308 0309 m_ready = true; 0310 Q_EMIT accountsReady(); 0311 } 0312 0313 bool AccountManager::isReady() const 0314 { 0315 return m_ready; 0316 } 0317 0318 QString AccountManager::settingsGroupName(const QString &name, const QString &instanceUri) 0319 { 0320 return name + QLatin1Char('@') + QUrl(instanceUri).host(); 0321 } 0322 0323 QString AccountManager::clientSecretKey(const QString &name) 0324 { 0325 #ifdef TOKODON_FLATPAK 0326 return QStringLiteral("%1-flatpak-client-secret").arg(name); 0327 #else 0328 return QStringLiteral("%1-client-secret").arg(name); 0329 #endif 0330 } 0331 0332 QString AccountManager::accessTokenKey(const QString &name) 0333 { 0334 #ifdef TOKODON_FLATPAK 0335 return QStringLiteral("%1-flatpak- client-secret").arg(name); 0336 #else 0337 return QStringLiteral("%1-access-token").arg(name); 0338 #endif 0339 } 0340 0341 void AccountManager::migrateSettings() 0342 { 0343 QSettings settings; 0344 0345 const auto version = settings.value("settingsVersion", -1).toInt(); 0346 if (version == 0) { 0347 qCDebug(TOKODON_LOG) << "Migrating v0 settings to v1"; 0348 settings.beginGroup("accounts"); 0349 const auto childGroups = settings.childGroups(); 0350 // we are just going to re-index 0351 qCDebug(TOKODON_LOG) << "Account list is" << childGroups; 0352 for (int i = 0; i < childGroups.size(); i++) { 0353 // we're going to move all of this into an array instead 0354 const auto child = childGroups[i]; 0355 settings.beginGroup(child); 0356 const auto keysInChild = settings.childKeys(); 0357 const auto childName = settings.value("name").toString(); 0358 const auto childInstance = QUrl(settings.value("instance_uri").toString()).host(); 0359 const QString newName = childName + QLatin1Char('@') + childInstance; 0360 qCDebug(TOKODON_LOG) << "Rewriting key from" << child << "to" << newName; 0361 settings.endGroup(); 0362 for (const auto &key : keysInChild) { 0363 settings.beginGroup(child); 0364 const auto value = settings.value(key); 0365 settings.endGroup(); // child 0366 settings.beginGroup(newName); 0367 settings.setValue(key, value); 0368 settings.endGroup(); 0369 } 0370 // after porting over the settings, remove it 0371 settings.remove(child); 0372 } 0373 settings.endGroup(); 0374 settings.setValue("settingsVersion", 1); 0375 0376 // we need to migrate to kconfig 0377 migrateSettings(); 0378 } else if (version == 1) { 0379 qCDebug(TOKODON_LOG) << "Migrating v1 settings to kconfig"; 0380 0381 settings.beginGroup("accounts"); 0382 const auto childGroups = settings.childGroups(); 0383 for (int i = 0; i < childGroups.size(); i++) { 0384 const auto child = childGroups[i]; 0385 settings.beginGroup(child); 0386 0387 const auto childName = settings.value("name").toString(); 0388 const auto childInstance = QUrl(settings.value("instance_uri").toString()).host(); 0389 0390 const QString settingsGroupName = childName + QLatin1Char('@') + childInstance; 0391 0392 AccountConfig config(settingsGroupName); 0393 config.setClientId(settings.value("client_id").toString()); 0394 config.setInstanceUri(settings.value("instance_uri").toString()); 0395 config.setName(settings.value("name").toString()); 0396 config.setIgnoreSslErrors(settings.value("ignoreSslErrors").toBool()); 0397 0398 config.save(); 0399 0400 auto accessTokenJob = new QKeychain::WritePasswordJob{"Tokodon"}; 0401 accessTokenJob->setKey(AccountManager::accessTokenKey(settingsGroupName)); 0402 accessTokenJob->setTextData(settings.value("token").toString()); 0403 accessTokenJob->start(); 0404 0405 auto clientSecretJob = new QKeychain::WritePasswordJob{"Tokodon"}; 0406 clientSecretJob->setKey(AccountManager::clientSecretKey(settingsGroupName)); 0407 clientSecretJob->setTextData(settings.value("client_secret").toString()); 0408 clientSecretJob->start(); 0409 0410 settings.endGroup(); 0411 } 0412 0413 settings.endGroup(); 0414 0415 // wipe file 0416 settings.clear(); 0417 } 0418 }