Changeset View
Changeset View
Standalone View
Standalone View
attica-kde/kdeplugin/kdeplatformdependent.cpp
1 | /* | 1 | /* | ||
---|---|---|---|---|---|
2 | This file is part of KDE. | 2 | This file is part of KDE. | ||
3 | 3 | | |||
4 | Copyright (c) 2009 Eckhart Wörner <ewoerner@kde.org> | 4 | Copyright (c) 2009 Eckhart Wörner <ewoerner@kde.org> | ||
5 | Copyright (c) 2010 Frederik Gladhorn <gladhorn@kde.org> | 5 | Copyright (c) 2010 Frederik Gladhorn <gladhorn@kde.org> | ||
6 | Copyright (c) 2019 Dan Leinir Turthra Jensen <admin@leinir.dk> | ||||
6 | 7 | | |||
7 | This library is free software; you can redistribute it and/or | 8 | This library is free software; you can redistribute it and/or | ||
8 | modify it under the terms of the GNU Lesser General Public | 9 | modify it under the terms of the GNU Lesser General Public | ||
9 | License as published by the Free Software Foundation; either | 10 | License as published by the Free Software Foundation; either | ||
10 | version 2.1 of the License, or (at your option) version 3, or any | 11 | version 2.1 of the License, or (at your option) version 3, or any | ||
11 | later version accepted by the membership of KDE e.V. (or its | 12 | later version accepted by the membership of KDE e.V. (or its | ||
12 | successor approved by the membership of KDE e.V.), which shall | 13 | successor approved by the membership of KDE e.V.), which shall | ||
13 | act as a proxy defined in Section 6 of version 3 of the license. | 14 | act as a proxy defined in Section 6 of version 3 of the license. | ||
14 | 15 | | |||
15 | This library is distributed in the hope that it will be useful, | 16 | This library is distributed in the hope that it will be useful, | ||
16 | but WITHOUT ANY WARRANTY; without even the implied warranty of | 17 | but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | 18 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
18 | Lesser General Public License for more details. | 19 | Lesser General Public License for more details. | ||
19 | 20 | | |||
20 | You should have received a copy of the GNU Lesser General Public | 21 | You should have received a copy of the GNU Lesser General Public | ||
21 | License along with this library. If not, see <http://www.gnu.org/licenses/>. | 22 | License along with this library. If not, see <http://www.gnu.org/licenses/>. | ||
22 | 23 | | |||
23 | */ | 24 | */ | ||
24 | 25 | | |||
25 | #include "kdeplatformdependent.h" | 26 | #include "kdeplatformdependent.h" | ||
26 | 27 | | |||
27 | #include "attica_plugin_debug.h" | 28 | #include "attica_plugin_debug.h" | ||
28 | 29 | | |||
30 | #include <KServiceTypeTrader> | ||||
29 | #include <KConfigGroup> | 31 | #include <KConfigGroup> | ||
30 | #include <KWallet/KWallet> | | |||
31 | #include <kcmultidialog.h> | 32 | #include <kcmultidialog.h> | ||
32 | #include <KLocalizedString> | 33 | #include <KLocalizedString> | ||
33 | #include <KStringHandler> | 34 | #include <KStringHandler> | ||
34 | #include <KMessageBox> | 35 | #include <KMessageBox> | ||
35 | #include <QNetworkDiskCache> | 36 | #include <QNetworkDiskCache> | ||
37 | #include <QProcess> | ||||
36 | #include <QStorageInfo> | 38 | #include <QStorageInfo> | ||
37 | 39 | | |||
40 | #include <KAccounts/Core> | ||||
41 | #include <KAccounts/getcredentialsjob.h> | ||||
42 | #include <Accounts/Manager> | ||||
43 | #include <Accounts/AccountService> | ||||
44 | | ||||
38 | using namespace Attica; | 45 | using namespace Attica; | ||
39 | 46 | | |||
40 | KdePlatformDependent::KdePlatformDependent() | 47 | KdePlatformDependent::KdePlatformDependent() | ||
41 | : m_config(KSharedConfig::openConfig(QStringLiteral("atticarc"))), m_accessManager(nullptr), m_wallet(nullptr) | 48 | : m_config(KSharedConfig::openConfig(QStringLiteral("atticarc"))), m_accessManager(nullptr) | ||
42 | { | 49 | { | ||
43 | // FIXME: Investigate how to not leak this instance without crashing. | 50 | // FIXME: Investigate how to not leak this instance without crashing. | ||
44 | m_accessManager = new QNetworkAccessManager(nullptr); | 51 | m_accessManager = new QNetworkAccessManager(nullptr); | ||
45 | 52 | | |||
46 | const QString cacheDir = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + QStringLiteral("/attica"); | 53 | const QString cacheDir = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + QStringLiteral("/attica"); | ||
47 | QNetworkDiskCache *cache = new QNetworkDiskCache(m_accessManager); | 54 | QNetworkDiskCache *cache = new QNetworkDiskCache(m_accessManager); | ||
48 | QStorageInfo storageInfo(cacheDir); | 55 | QStorageInfo storageInfo(cacheDir); | ||
49 | cache->setCacheDirectory(cacheDir); | 56 | cache->setCacheDirectory(cacheDir); | ||
50 | cache->setMaximumCacheSize(storageInfo.bytesTotal() / 1000); | 57 | cache->setMaximumCacheSize(storageInfo.bytesTotal() / 1000); | ||
51 | m_accessManager->setCache(cache); | 58 | m_accessManager->setCache(cache); | ||
52 | } | 59 | } | ||
53 | 60 | | |||
54 | KdePlatformDependent::~KdePlatformDependent() | 61 | KdePlatformDependent::~KdePlatformDependent() | ||
55 | { | 62 | { | ||
56 | delete m_wallet; | | |||
57 | } | 63 | } | ||
58 | 64 | | |||
59 | bool KdePlatformDependent::openWallet(bool force) | 65 | // TODO Cache the account (so we can call getAccount a WHOLE LOT of times without making the application super slow) | ||
60 | { | 66 | // 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 | ||
61 | if (m_wallet) { | 67 | QString KdePlatformDependent::getAccessToken(const QUrl& /*baseUrl*/) const | ||
62 | return true; | 68 | { | ||
69 | QString accessToken; | ||||
70 | QString idToken; | ||||
71 | Accounts::Manager* accountsManager = KAccounts::accountsManager(); | ||||
72 | if (accountsManager) { | ||||
73 | static const QString serviceType{"opendesktop-rating"}; | ||||
74 | Accounts::AccountIdList accountIds = accountsManager->accountList(serviceType); | ||||
75 | // TODO Present the user with a choice in case there's more than one, but for now just pick the first successful one | ||||
76 | // loop through the accounts, and attempt to get them | ||||
77 | Accounts::Account* account{nullptr}; | ||||
78 | qDebug() << accountIds; | ||||
79 | for (const Accounts::AccountId& accountId : accountIds) { | ||||
80 | account = accountsManager->account(accountId); | ||||
81 | if (account) { | ||||
82 | bool completed{false}; | ||||
83 | qCDebug(ATTICA_PLUGIN_LOG) << "Fetching data for" << accountId; | ||||
84 | GetCredentialsJob *job = new GetCredentialsJob(accountId, accountsManager); | ||||
85 | connect(job, &KJob::finished, [&completed,&accessToken,&idToken](KJob* kjob){ | ||||
86 | GetCredentialsJob *job = qobject_cast< GetCredentialsJob* >(kjob); | ||||
87 | QVariantMap credentialsData = job->credentialsData(); | ||||
88 | accessToken = credentialsData["AccessToken"].toString(); | ||||
89 | idToken = credentialsData["IdToken"].toString(); | ||||
90 | if (!accessToken.isEmpty()) { | ||||
91 | qCDebug(ATTICA_PLUGIN_LOG) << "Credentials data was gottened"; | ||||
92 | for (const QString& key : credentialsData.keys()) { | ||||
93 | qCDebug(ATTICA_PLUGIN_LOG) << key << credentialsData[key]; | ||||
94 | } | ||||
95 | } | ||||
96 | completed = true; | ||||
97 | }); | ||||
98 | connect(job, &KJob::result, [&completed](){ completed = true; }); | ||||
99 | job->start(); | ||||
100 | while(!completed) { | ||||
101 | qApp->processEvents(); | ||||
102 | } | ||||
103 | if(!idToken.isEmpty()) { | ||||
104 | qCDebug(ATTICA_PLUGIN_LOG) << "OpenID Access token retrieved for account" << account->id(); | ||||
105 | break; | ||||
106 | } | ||||
107 | } | ||||
108 | } | ||||
109 | } else { | ||||
110 | qCDebug(ATTICA_PLUGIN_LOG) << "No accounts manager could be fetched, so could not ask it for account details"; | ||||
63 | } | 111 | } | ||
64 | 112 | | |||
65 | QString networkWallet = KWallet::Wallet::NetworkWallet(); | 113 | return idToken; | ||
66 | // if not forced, or the folder doesn't exist, don't try to open the wallet | | |||
67 | if (force || (!KWallet::Wallet::folderDoesNotExist(networkWallet, QStringLiteral("Attica")))) { | | |||
68 | m_wallet = KWallet::Wallet::openWallet(networkWallet, 0); | | |||
69 | } | 114 | } | ||
70 | 115 | | |||
71 | if (m_wallet) { | 116 | QUrl baseUrlFromRequest(const QNetworkRequest& request) { | ||
72 | m_wallet->createFolder(QStringLiteral("Attica")); | 117 | const QUrl url{request.url()}; | ||
73 | m_wallet->setFolder(QStringLiteral("Attica")); | 118 | QString baseUrl = QString("%1://%2").arg(url.scheme()).arg(url.host()); | ||
74 | return true; | 119 | int port = url.port(); | ||
120 | if (port != -1) { | ||||
121 | baseUrl.append(QString::number(port)); | ||||
75 | } | 122 | } | ||
76 | return false; | 123 | return url; | ||
124 | } | ||||
125 | | ||||
126 | QNetworkRequest KdePlatformDependent::addOAuthToRequest(const QNetworkRequest& request) | ||||
127 | { | ||||
128 | QNetworkRequest notConstReq = const_cast<QNetworkRequest&>(request); | ||||
129 | const QString token{getAccessToken(baseUrlFromRequest(request))}; | ||||
130 | if (!token.isEmpty()) { | ||||
131 | const QString bearer_format = QStringLiteral("Bearer %1"); | ||||
132 | const QString bearer = bearer_format.arg(token); | ||||
133 | notConstReq.setRawHeader("Authorization", bearer.toUtf8()); | ||||
134 | } | ||||
135 | return notConstReq; | ||||
77 | } | 136 | } | ||
78 | 137 | | |||
79 | QNetworkReply* KdePlatformDependent::post(const QNetworkRequest& request, const QByteArray& data) | 138 | QNetworkReply* KdePlatformDependent::post(const QNetworkRequest& request, const QByteArray& data) | ||
80 | { | 139 | { | ||
81 | return m_accessManager->post(removeAuthFromRequest(request), data); | 140 | return m_accessManager->post(addOAuthToRequest(removeAuthFromRequest(request)), data); | ||
82 | } | 141 | } | ||
83 | 142 | | |||
84 | QNetworkReply* KdePlatformDependent::post(const QNetworkRequest& request, QIODevice* data) | 143 | QNetworkReply* KdePlatformDependent::post(const QNetworkRequest& request, QIODevice* data) | ||
85 | { | 144 | { | ||
86 | return m_accessManager->post(removeAuthFromRequest(request), data); | 145 | return m_accessManager->post(addOAuthToRequest(removeAuthFromRequest(request)), data); | ||
87 | } | 146 | } | ||
88 | 147 | | |||
89 | QNetworkReply* KdePlatformDependent::get(const QNetworkRequest& request) | 148 | QNetworkReply* KdePlatformDependent::get(const QNetworkRequest& request) | ||
90 | { | 149 | { | ||
91 | return m_accessManager->get(removeAuthFromRequest(request)); | 150 | return m_accessManager->get(addOAuthToRequest(removeAuthFromRequest(request))); | ||
92 | } | 151 | } | ||
93 | 152 | | |||
94 | QNetworkRequest KdePlatformDependent::removeAuthFromRequest(const QNetworkRequest& request) | 153 | QNetworkRequest KdePlatformDependent::removeAuthFromRequest(const QNetworkRequest& request) | ||
95 | { | 154 | { | ||
96 | const QStringList noauth = { QStringLiteral("no-auth-prompt"), QStringLiteral("true") }; | 155 | const QStringList noauth = { QStringLiteral("no-auth-prompt"), QStringLiteral("true") }; | ||
97 | QNetworkRequest notConstReq = const_cast<QNetworkRequest&>(request); | 156 | QNetworkRequest notConstReq = const_cast<QNetworkRequest&>(request); | ||
98 | notConstReq.setAttribute(QNetworkRequest::User, noauth); | 157 | notConstReq.setAttribute(QNetworkRequest::User, noauth); | ||
99 | return notConstReq; | 158 | return notConstReq; | ||
100 | } | 159 | } | ||
101 | 160 | | |||
102 | bool KdePlatformDependent::saveCredentials(const QUrl& baseUrl, const QString& user, const QString& password) | 161 | bool KdePlatformDependent::saveCredentials(const QUrl& /*baseUrl*/, const QString& /*user*/, const QString& /*password*/) | ||
103 | { | 162 | { | ||
104 | m_passwords[baseUrl.toString()] = qMakePair(user, password); | 163 | // TODO KF6 This will want replacing with a call named something that suggests calling it shows accounts (and perhaps | ||
105 | 164 | // directly requests the accounts kcm to start adding a new account if it's not there, maybe even pre-fills the fields...) | |||
106 | if (!m_wallet && !openWallet(true)) { | 165 | KService::List services = KServiceTypeTrader::self()->query(QStringLiteral("KCModule"), QStringLiteral("Library == 'kcm_kaccounts'")); | ||
nicolasfella: This sounds like a use case for KCMultiDialog, like e.g. in https://invent.kde. | |||||
leinir: That certainly could be! i'll take a look, thanks for the hint :) | |||||
107 | 166 | // If we failed to get the kcm, tell the caller we failed | |||
108 | if (KMessageBox::warningContinueCancel(nullptr, i18n("Should the password be stored in the configuration file? This is unsafe.") | 167 | if (services.count() == 0) { | ||
109 | , i18n("Social Desktop Configuration")) | | |||
110 | == KMessageBox::Cancel) { | | |||
111 | return false; | 168 | return false; | ||
112 | } | 169 | } | ||
113 | 170 | KService::Ptr service = services[0]; | |||
114 | // use kconfig | 171 | qCDebug(ATTICA_PLUGIN_LOG) << "Launch the KAccounts control module" << service->name(); | ||
115 | KConfigGroup group(m_config, baseUrl.toString()); | 172 | return QProcess::startDetached(service->exec()); | ||
116 | group.writeEntry("user", user); | | |||
117 | group.writeEntry("password", KStringHandler::obscure(password)); | | |||
118 | qCDebug(ATTICA_PLUGIN_LOG) << "Saved credentials in KConfig"; | | |||
119 | return true; | | |||
120 | } | | |||
121 | | ||||
122 | // Remove the entry when user name is empty | | |||
123 | if (user.isEmpty()) { | | |||
124 | m_wallet->removeEntry(baseUrl.toString()); | | |||
125 | return true; | | |||
126 | } | | |||
127 | | ||||
128 | const QMap<QString, QString> entries = { | | |||
129 | { QStringLiteral("user"), user }, | | |||
130 | { QStringLiteral("password"), password } | | |||
131 | }; | | |||
132 | qCDebug(ATTICA_PLUGIN_LOG) << "Saved credentials in KWallet"; | | |||
133 | | ||||
134 | return !m_wallet->writeMap(baseUrl.toString(), entries); | | |||
135 | } | 173 | } | ||
136 | 174 | | |||
137 | bool KdePlatformDependent::hasCredentials(const QUrl& baseUrl) const | 175 | bool KdePlatformDependent::hasCredentials(const QUrl& baseUrl) const | ||
138 | { | 176 | { | ||
139 | if (m_passwords.contains(baseUrl.toString())) { | 177 | qCDebug(ATTICA_PLUGIN_LOG) << Q_FUNC_INFO; | ||
140 | return true; | 178 | return !getAccessToken(baseUrl).isEmpty(); | ||
141 | } | | |||
142 | | ||||
143 | QString networkWallet = KWallet::Wallet::NetworkWallet(); | | |||
144 | if (!KWallet::Wallet::folderDoesNotExist(networkWallet, QStringLiteral("Attica")) && | | |||
145 | !KWallet::Wallet::keyDoesNotExist(networkWallet, QStringLiteral("Attica"), baseUrl.toString())) { | | |||
146 | qCDebug(ATTICA_PLUGIN_LOG) << "Found credentials in KWallet"; | | |||
147 | return true; | | |||
148 | } | | |||
149 | | ||||
150 | KConfigGroup group(m_config, baseUrl.toString()); | | |||
151 | | ||||
152 | const QString user = group.readEntry("user", QString()); | | |||
153 | qCDebug(ATTICA_PLUGIN_LOG) << "Credentials found:" << !user.isEmpty(); | | |||
154 | return !user.isEmpty(); | | |||
155 | } | 179 | } | ||
156 | 180 | | |||
157 | 181 | | |||
158 | bool KdePlatformDependent::loadCredentials(const QUrl& baseUrl, QString& user, QString& password) | 182 | bool KdePlatformDependent::loadCredentials(const QUrl& baseUrl, QString& user, QString& /*password*/) | ||
159 | { | 183 | { | ||
160 | QString networkWallet = KWallet::Wallet::NetworkWallet(); | 184 | qCDebug(ATTICA_PLUGIN_LOG) << Q_FUNC_INFO; | ||
161 | if (KWallet::Wallet::folderDoesNotExist(networkWallet, QStringLiteral("Attica")) && | 185 | QString token = getAccessToken(baseUrl); | ||
162 | KWallet::Wallet::keyDoesNotExist(networkWallet, QStringLiteral("Attica"), baseUrl.toString())) { | 186 | if (!token.isEmpty()) { | ||
163 | // use KConfig | 187 | user = token; | ||
164 | KConfigGroup group(m_config, baseUrl.toString()); | | |||
165 | user = group.readEntry("user", QString()); | | |||
166 | password = KStringHandler::obscure(group.readEntry("password", QString())); | | |||
167 | if (!user.isEmpty()) { | | |||
168 | qCDebug(ATTICA_PLUGIN_LOG) << "Successfully loaded credentials from kconfig"; | | |||
169 | m_passwords[baseUrl.toString()] = qMakePair(user, password); | | |||
170 | return true; | | |||
171 | } | 188 | } | ||
172 | return false; | 189 | return !token.isEmpty(); | ||
173 | } | | |||
174 | | ||||
175 | if (!m_wallet && !openWallet(true)) { | | |||
176 | return false; | | |||
177 | } | | |||
178 | | ||||
179 | QMap<QString, QString> entries; | | |||
180 | if (m_wallet->readMap(baseUrl.toString(), entries) != 0) { | | |||
181 | return false; | | |||
182 | } | | |||
183 | user = entries.value(QStringLiteral("user")); | | |||
184 | password = entries.value(QStringLiteral("password")); | | |||
185 | qCDebug(ATTICA_PLUGIN_LOG) << "Successfully loaded credentials."; | | |||
186 | | ||||
187 | m_passwords[baseUrl.toString()] = qMakePair(user, password); | | |||
188 | | ||||
189 | return true; | | |||
190 | } | 190 | } | ||
191 | 191 | | |||
192 | 192 | | |||
193 | bool Attica::KdePlatformDependent::askForCredentials(const QUrl& baseUrl, QString& user, QString& password) | 193 | bool Attica::KdePlatformDependent::askForCredentials(const QUrl& baseUrl, QString& user, QString& password) | ||
194 | { | 194 | { | ||
195 | Q_UNUSED(baseUrl); | 195 | Q_UNUSED(baseUrl); | ||
196 | Q_UNUSED(user); | 196 | Q_UNUSED(user); | ||
197 | Q_UNUSED(password); | 197 | Q_UNUSED(password); | ||
▲ Show 20 Lines • Show All 54 Lines • ▼ Show 20 Line(s) | 251 | { | |||
252 | KConfigGroup group(m_config, "General"); | 252 | KConfigGroup group(m_config, "General"); | ||
253 | return !group.readPathEntry("disabledProviders", QStringList()).contains(baseUrl.toString()); | 253 | return !group.readPathEntry("disabledProviders", QStringList()).contains(baseUrl.toString()); | ||
254 | } | 254 | } | ||
255 | 255 | | |||
256 | QNetworkAccessManager* Attica::KdePlatformDependent::nam() | 256 | QNetworkAccessManager* Attica::KdePlatformDependent::nam() | ||
257 | { | 257 | { | ||
258 | return m_accessManager; | 258 | return m_accessManager; | ||
259 | } | 259 | } | ||
260 | | ||||
261 | // TODO: re-enable, see https://community.kde.org/Frameworks/Porting_Notes | | |||
262 | // Q_EXPORT_PLUGIN2(attica_kde, Attica::KdePlatformDependent) | | |||
263 | |
This sounds like a use case for KCMultiDialog, like e.g. in https://invent.kde.org/kde/kdeconnect-kde/-/blob/master/settings/main.cpp