Warning, file /plasma/plasma-desktop/attica-kde/kdeplugin/kdeplatformdependent.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
0001 /* 0002 This file is part of KDE. 0003 0004 SPDX-FileCopyrightText: 2009 Eckhart Wörner <ewoerner@kde.org> 0005 SPDX-FileCopyrightText: 2010 Frederik Gladhorn <gladhorn@kde.org> 0006 SPDX-FileCopyrightText: 2019 Dan Leinir Turthra Jensen <admin@leinir.dk> 0007 0008 SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL 0009 0010 */ 0011 0012 #include "kdeplatformdependent.h" 0013 0014 #include "attica_plugin_debug.h" 0015 0016 #include <KCMultiDialog> 0017 #include <KConfigGroup> 0018 #include <KLocalizedString> 0019 #include <QNetworkDiskCache> 0020 #include <QStorageInfo> 0021 0022 #include <Accounts/AccountService> 0023 #include <Accounts/Manager> 0024 #include <KAccounts/Core> 0025 #include <KAccounts/GetCredentialsJob> 0026 0027 using namespace Attica; 0028 0029 KdePlatformDependent::KdePlatformDependent() 0030 : m_config(KSharedConfig::openConfig(QStringLiteral("atticarc"))) 0031 , m_accessManager(nullptr) 0032 { 0033 // FIXME: Investigate how to not leak this instance without crashing. 0034 m_accessManager = new QNetworkAccessManager(nullptr); 0035 0036 const QString cacheDir = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + QStringLiteral("/attica"); 0037 QNetworkDiskCache *cache = new QNetworkDiskCache(m_accessManager); 0038 QStorageInfo storageInfo(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)); 0039 cache->setCacheDirectory(cacheDir); 0040 cache->setMaximumCacheSize(storageInfo.bytesTotal() / 1000); 0041 m_accessManager->setCache(cache); 0042 } 0043 0044 KdePlatformDependent::~KdePlatformDependent() 0045 { 0046 } 0047 0048 // TODO Cache the account (so we can call getAccount a WHOLE LOT of times without making the application super slow) 0049 // TODO Also don't just cache it forever, so reset to nullptr every so often, so we pick up potential new stuff the user's done 0050 QString KdePlatformDependent::getAccessToken(const QUrl & /*baseUrl*/) const 0051 { 0052 QString accessToken; 0053 QString idToken; 0054 Accounts::Manager *accountsManager = KAccounts::accountsManager(); 0055 if (accountsManager) { 0056 static const QString serviceType{QStringLiteral("opendesktop-rating")}; 0057 Accounts::AccountIdList accountIds = accountsManager->accountList(serviceType); 0058 // TODO Present the user with a choice in case there's more than one, but for now just pick the first successful one 0059 // loop through the accounts, and attempt to get them 0060 Accounts::Account *account{nullptr}; 0061 for (const Accounts::AccountId &accountId : accountIds) { 0062 account = accountsManager->account(accountId); 0063 if (account) { 0064 bool completed{false}; 0065 qCDebug(ATTICA_PLUGIN_LOG) << "Fetching data for" << accountId; 0066 GetCredentialsJob *job = new GetCredentialsJob(accountId, accountsManager); 0067 connect(job, &KJob::finished, [&completed, &accessToken, &idToken](KJob *kjob) { 0068 GetCredentialsJob *job = qobject_cast<GetCredentialsJob *>(kjob); 0069 const QVariantMap credentialsData = job->credentialsData(); 0070 accessToken = credentialsData[QStringLiteral("AccessToken")].toString(); 0071 idToken = credentialsData[QStringLiteral("IdToken")].toString(); 0072 // As this can be useful for more heavy duty debugging purposes, leaving this in so it doesn't have to be rewritten 0073 // if (!accessToken.isEmpty()) { 0074 // qCDebug(ATTICA_PLUGIN_LOG) << "Credentials data was retrieved"; 0075 // for (const QString& key : credentialsData.keys()) { 0076 // qCDebug(ATTICA_PLUGIN_LOG) << key << credentialsData[key]; 0077 // } 0078 // } 0079 completed = true; 0080 }); 0081 connect(job, &KJob::result, [&completed]() { 0082 completed = true; 0083 }); 0084 job->start(); 0085 while (!completed) { 0086 qApp->processEvents(); 0087 } 0088 if (!idToken.isEmpty()) { 0089 qCDebug(ATTICA_PLUGIN_LOG) << "OpenID Access token retrieved for account" << account->id(); 0090 break; 0091 } 0092 } 0093 if (idToken.isEmpty()) { 0094 // If we arrived here, we did have an opendesktop account, but without the id token, which means an old version of the signon oauth2 plugin was 0095 // used 0096 qCWarning(ATTICA_PLUGIN_LOG) << "We got an OpenDesktop account, but it seems to be lacking the id token. This means an old SignOn OAuth2 " 0097 "plugin was used for logging in. The plugin may have been upgraded in the meantime, but an account created " 0098 "using the old plugin cannot be used, and you must log out and back in again."; 0099 } 0100 } 0101 } else { 0102 qCDebug(ATTICA_PLUGIN_LOG) << "No accounts manager could be fetched, so could not ask it for account details"; 0103 } 0104 0105 return idToken; 0106 } 0107 0108 QUrl baseUrlFromRequest(const QNetworkRequest &request) 0109 { 0110 const QUrl url{request.url()}; 0111 QString baseUrl = QLatin1String("%1://%2").arg(url.scheme(), url.host()); 0112 int port = url.port(); 0113 if (port != -1) { 0114 baseUrl.append(QString::number(port)); 0115 } 0116 return url; 0117 } 0118 0119 QNetworkRequest KdePlatformDependent::addOAuthToRequest(const QNetworkRequest &request) 0120 { 0121 QNetworkRequest notConstReq = const_cast<QNetworkRequest &>(request); 0122 const QString token{getAccessToken(baseUrlFromRequest(request))}; 0123 if (!token.isEmpty()) { 0124 const QString bearer_format = QStringLiteral("Bearer %1"); 0125 const QString bearer = bearer_format.arg(token); 0126 notConstReq.setRawHeader("Authorization", bearer.toUtf8()); 0127 } 0128 notConstReq.setAttribute(QNetworkRequest::Http2AllowedAttribute, true); 0129 0130 // Add cache preference in a granular fashion (we will almost certainly want more of these, but...) 0131 static const QStringList preferCacheEndpoints{QLatin1String{"/content/categories"}}; 0132 for (const QString &endpoint : preferCacheEndpoints) { 0133 if (notConstReq.url().toString().endsWith(endpoint)) { 0134 QNetworkCacheMetaData cacheMeta{m_accessManager->cache()->metaData(notConstReq.url())}; 0135 if (cacheMeta.isValid()) { 0136 // If the expiration date is valid, but longer than 24 hours, don't trust that things 0137 // haven't changed and check first, otherwise just use the cached version to relieve 0138 // server strain and reduce network traffic. 0139 const QDateTime tomorrow{QDateTime::currentDateTime().addDays(1)}; 0140 if (cacheMeta.expirationDate().isValid() && cacheMeta.expirationDate() < tomorrow) { 0141 notConstReq.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache); 0142 } 0143 } 0144 break; 0145 } 0146 } 0147 0148 return notConstReq; 0149 } 0150 0151 QNetworkReply *KdePlatformDependent::post(const QNetworkRequest &request, const QByteArray &data) 0152 { 0153 return m_accessManager->post(addOAuthToRequest(removeAuthFromRequest(request)), data); 0154 } 0155 0156 QNetworkReply *KdePlatformDependent::post(const QNetworkRequest &request, QIODevice *data) 0157 { 0158 return m_accessManager->post(addOAuthToRequest(removeAuthFromRequest(request)), data); 0159 } 0160 0161 QNetworkReply *KdePlatformDependent::get(const QNetworkRequest &request) 0162 { 0163 return m_accessManager->get(addOAuthToRequest(removeAuthFromRequest(request))); 0164 } 0165 0166 QNetworkRequest KdePlatformDependent::removeAuthFromRequest(const QNetworkRequest &request) 0167 { 0168 const QStringList noauth = {QStringLiteral("no-auth-prompt"), QStringLiteral("true")}; 0169 QNetworkRequest notConstReq = const_cast<QNetworkRequest &>(request); 0170 notConstReq.setAttribute(QNetworkRequest::User, noauth); 0171 return notConstReq; 0172 } 0173 0174 bool KdePlatformDependent::saveCredentials(const QUrl & /*baseUrl*/, const QString & /*user*/, const QString & /*password*/) 0175 { 0176 qCDebug(ATTICA_PLUGIN_LOG) << "Launch the KAccounts control module"; 0177 // TODO KF6 This will want replacing with a call named something that suggests calling it shows accounts (and perhaps 0178 // directly requests the accounts kcm to start adding a new account if it's not there, maybe even pre-fills the fields...) 0179 0180 KCMultiDialog *dialog = new KCMultiDialog; 0181 dialog->addModule(KPluginMetaData(QStringLiteral("kcm_kaccounts"))); 0182 dialog->setAttribute(Qt::WA_DeleteOnClose); 0183 dialog->show(); 0184 0185 return true; 0186 } 0187 0188 bool KdePlatformDependent::hasCredentials(const QUrl &baseUrl) const 0189 { 0190 qCDebug(ATTICA_PLUGIN_LOG) << Q_FUNC_INFO; 0191 return !getAccessToken(baseUrl).isEmpty(); 0192 } 0193 0194 bool KdePlatformDependent::loadCredentials(const QUrl &baseUrl, QString &user, QString & /*password*/) 0195 { 0196 qCDebug(ATTICA_PLUGIN_LOG) << Q_FUNC_INFO; 0197 QString token = getAccessToken(baseUrl); 0198 if (!token.isEmpty()) { 0199 user = token; 0200 } 0201 return !token.isEmpty(); 0202 } 0203 0204 bool Attica::KdePlatformDependent::askForCredentials(const QUrl &baseUrl, QString &user, QString &password) 0205 { 0206 Q_UNUSED(baseUrl); 0207 Q_UNUSED(user); 0208 Q_UNUSED(password); 0209 0210 return false; 0211 } 0212 0213 QList<QUrl> KdePlatformDependent::getDefaultProviderFiles() const 0214 { 0215 KConfigGroup group(m_config, "General"); 0216 const QStringList pathStrings = group.readPathEntry("providerFiles", QStringList(QStringLiteral("https://autoconfig.kde.org/ocs/providers.xml"))); 0217 QList<QUrl> paths; 0218 for (const QString &pathString : pathStrings) { 0219 paths.append(QUrl(pathString)); 0220 } 0221 qCDebug(ATTICA_PLUGIN_LOG) << "Loaded paths from config:" << paths; 0222 return paths; 0223 } 0224 0225 void KdePlatformDependent::addDefaultProviderFile(const QUrl &url) 0226 { 0227 KConfigGroup group(m_config, "General"); 0228 QStringList pathStrings = group.readPathEntry("providerFiles", QStringList(QStringLiteral("https://autoconfig.kde.org/ocs/providers.xml"))); 0229 QString urlString = url.toString(); 0230 if (!pathStrings.contains(urlString)) { 0231 pathStrings.append(urlString); 0232 group.writeEntry("providerFiles", pathStrings); 0233 group.sync(); 0234 qCDebug(ATTICA_PLUGIN_LOG) << "wrote providers: " << pathStrings; 0235 } 0236 } 0237 0238 void KdePlatformDependent::removeDefaultProviderFile(const QUrl &url) 0239 { 0240 KConfigGroup group(m_config, "General"); 0241 QStringList pathStrings = group.readPathEntry("providerFiles", QStringList(QStringLiteral("https://autoconfig.kde.org/ocs/providers.xml"))); 0242 pathStrings.removeAll(url.toString()); 0243 group.writeEntry("providerFiles", pathStrings); 0244 } 0245 0246 void KdePlatformDependent::enableProvider(const QUrl &baseUrl, bool enabled) const 0247 { 0248 KConfigGroup group(m_config, "General"); 0249 QStringList pathStrings = group.readPathEntry("disabledProviders", QStringList()); 0250 if (enabled) { 0251 pathStrings.removeAll(baseUrl.toString()); 0252 } else { 0253 if (!pathStrings.contains(baseUrl.toString())) { 0254 pathStrings.append(baseUrl.toString()); 0255 } 0256 } 0257 group.writeEntry("disabledProviders", pathStrings); 0258 group.sync(); 0259 } 0260 0261 bool KdePlatformDependent::isEnabled(const QUrl &baseUrl) const 0262 { 0263 KConfigGroup group(m_config, "General"); 0264 return !group.readPathEntry("disabledProviders", QStringList()).contains(baseUrl.toString()); 0265 } 0266 0267 QNetworkAccessManager *Attica::KdePlatformDependent::nam() 0268 { 0269 return m_accessManager; 0270 }