1 | /* |
2 | * Copyright (C) by Olivier Goffart <ogoffart@woboq.com> |
3 | * |
4 | * This program is free software; you can redistribute it and/or modify |
5 | * it under the terms of the GNU General Public License as published by |
6 | * the Free Software Foundation; either version 2 of the License, or |
7 | * (at your option) any later version. |
8 | * |
9 | * This program is distributed in the hope that it will be useful, but |
10 | * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY |
11 | * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
12 | * for more details. |
13 | */ |
14 | |
15 | #include "accountmanager.h" |
16 | #include "configfile.h" |
17 | #include "sslerrordialog.h" |
18 | #include "proxyauthhandler.h" |
19 | #include <theme.h> |
20 | #include <creds/credentialsfactory.h> |
21 | #include <creds/abstractcredentials.h> |
22 | #include <cookiejar.h> |
23 | #include <QSettings> |
24 | #include <QDir> |
25 | #include <QNetworkAccessManager> |
26 | |
27 | namespace { |
28 | static const char urlC[] = "url" ; |
29 | static const char authTypeC[] = "authType" ; |
30 | static const char userC[] = "user" ; |
31 | static const char httpUserC[] = "http_user" ; |
32 | static const char caCertsKeyC[] = "CaCertificates" ; |
33 | static const char accountsC[] = "Accounts" ; |
34 | static const char versionC[] = "version" ; |
35 | static const char serverVersionC[] = "serverVersion" ; |
36 | |
37 | // The maximum versions that this client can read |
38 | static const int maxAccountsVersion = 2; |
39 | static const int maxAccountVersion = 1; |
40 | } |
41 | |
42 | |
43 | namespace OCC { |
44 | |
45 | Q_LOGGING_CATEGORY(lcAccountManager, "gui.account.manager" , QtInfoMsg) |
46 | |
47 | AccountManager *AccountManager::instance() |
48 | { |
49 | static AccountManager instance; |
50 | return &instance; |
51 | } |
52 | |
53 | bool AccountManager::restore() |
54 | { |
55 | QStringList skipSettingsKeys; |
56 | backwardMigrationSettingsKeys(&skipSettingsKeys, &skipSettingsKeys); |
57 | |
58 | auto settings = ConfigFile::settingsWithGroup(QLatin1String(accountsC)); |
59 | if (settings->status() != QSettings::NoError) { |
60 | qCWarning(lcAccountManager) << "Could not read settings from" << settings->fileName() |
61 | << settings->status(); |
62 | return false; |
63 | } |
64 | |
65 | if (skipSettingsKeys.contains(settings->group())) { |
66 | // Should not happen: bad container keys should have been deleted |
67 | qCWarning(lcAccountManager) << "Accounts structure is too new, ignoring" ; |
68 | return true; |
69 | } |
70 | |
71 | // If there are no accounts, check the old format. |
72 | if (settings->childGroups().isEmpty() |
73 | && !settings->contains(QLatin1String(versionC))) { |
74 | restoreFromLegacySettings(); |
75 | return true; |
76 | } |
77 | |
78 | foreach (const auto &accountId, settings->childGroups()) { |
79 | settings->beginGroup(accountId); |
80 | if (!skipSettingsKeys.contains(settings->group())) { |
81 | if (auto acc = loadAccountHelper(*settings)) { |
82 | acc->_id = accountId; |
83 | if (auto accState = AccountState::loadFromSettings(acc, *settings)) { |
84 | addAccountState(accState); |
85 | } |
86 | } |
87 | } else { |
88 | qCInfo(lcAccountManager) << "Account" << accountId << "is too new, ignoring" ; |
89 | _additionalBlockedAccountIds.insert(accountId); |
90 | } |
91 | settings->endGroup(); |
92 | } |
93 | |
94 | return true; |
95 | } |
96 | |
97 | void AccountManager::backwardMigrationSettingsKeys(QStringList *deleteKeys, QStringList *ignoreKeys) |
98 | { |
99 | auto settings = ConfigFile::settingsWithGroup(QLatin1String(accountsC)); |
100 | const int accountsVersion = settings->value(QLatin1String(versionC)).toInt(); |
101 | if (accountsVersion <= maxAccountsVersion) { |
102 | foreach (const auto &accountId, settings->childGroups()) { |
103 | settings->beginGroup(accountId); |
104 | const int accountVersion = settings->value(QLatin1String(versionC), 1).toInt(); |
105 | if (accountVersion > maxAccountVersion) { |
106 | ignoreKeys->append(settings->group()); |
107 | } |
108 | settings->endGroup(); |
109 | } |
110 | } else { |
111 | deleteKeys->append(settings->group()); |
112 | } |
113 | } |
114 | |
115 | bool AccountManager::restoreFromLegacySettings() |
116 | { |
117 | qCInfo(lcAccountManager) << "Migrate: restoreFromLegacySettings, checking settings group" |
118 | << Theme::instance()->appName(); |
119 | |
120 | // try to open the correctly themed settings |
121 | auto settings = ConfigFile::settingsWithGroup(Theme::instance()->appName()); |
122 | |
123 | // if the settings file could not be opened, the childKeys list is empty |
124 | // then try to load settings from a very old place |
125 | if (settings->childKeys().isEmpty()) { |
126 | // Now try to open the original ownCloud settings to see if they exist. |
127 | QString oCCfgFile = QDir::fromNativeSeparators(settings->fileName()); |
128 | // replace the last two segments with ownCloud/owncloud.cfg |
129 | oCCfgFile = oCCfgFile.left(oCCfgFile.lastIndexOf('/')); |
130 | oCCfgFile = oCCfgFile.left(oCCfgFile.lastIndexOf('/')); |
131 | oCCfgFile += QLatin1String("/ownCloud/owncloud.cfg" ); |
132 | |
133 | qCInfo(lcAccountManager) << "Migrate: checking old config " << oCCfgFile; |
134 | |
135 | QFileInfo fi(oCCfgFile); |
136 | if (fi.isReadable()) { |
137 | std::unique_ptr<QSettings> oCSettings(new QSettings(oCCfgFile, QSettings::IniFormat)); |
138 | oCSettings->beginGroup(QLatin1String("ownCloud" )); |
139 | |
140 | // Check the theme url to see if it is the same url that the oC config was for |
141 | QString overrideUrl = Theme::instance()->overrideServerUrl(); |
142 | if (!overrideUrl.isEmpty()) { |
143 | if (overrideUrl.endsWith('/')) { |
144 | overrideUrl.chop(1); |
145 | } |
146 | QString oCUrl = oCSettings->value(QLatin1String(urlC)).toString(); |
147 | if (oCUrl.endsWith('/')) { |
148 | oCUrl.chop(1); |
149 | } |
150 | |
151 | // in case the urls are equal reset the settings object to read from |
152 | // the ownCloud settings object |
153 | qCInfo(lcAccountManager) << "Migrate oC config if " << oCUrl << " == " << overrideUrl << ":" |
154 | << (oCUrl == overrideUrl ? "Yes" : "No" ); |
155 | if (oCUrl == overrideUrl) { |
156 | settings = std::move(oCSettings); |
157 | } |
158 | } |
159 | } |
160 | } |
161 | |
162 | // Try to load the single account. |
163 | if (!settings->childKeys().isEmpty()) { |
164 | if (auto acc = loadAccountHelper(*settings)) { |
165 | addAccount(acc); |
166 | return true; |
167 | } |
168 | } |
169 | return false; |
170 | } |
171 | |
172 | void AccountManager::save(bool saveCredentials) |
173 | { |
174 | auto settings = ConfigFile::settingsWithGroup(QLatin1String(accountsC)); |
175 | settings->setValue(QLatin1String(versionC), maxAccountsVersion); |
176 | foreach (const auto &acc, _accounts) { |
177 | settings->beginGroup(acc->account()->id()); |
178 | saveAccountHelper(acc->account().data(), *settings, saveCredentials); |
179 | acc->writeToSettings(*settings); |
180 | settings->endGroup(); |
181 | } |
182 | |
183 | settings->sync(); |
184 | qCInfo(lcAccountManager) << "Saved all account settings, status:" << settings->status(); |
185 | } |
186 | |
187 | void AccountManager::saveAccount(Account *a) |
188 | { |
189 | qCInfo(lcAccountManager) << "Saving account" << a->url().toString(); |
190 | auto settings = ConfigFile::settingsWithGroup(QLatin1String(accountsC)); |
191 | settings->beginGroup(a->id()); |
192 | saveAccountHelper(a, *settings, false); // don't save credentials they might not have been loaded yet |
193 | settings->endGroup(); |
194 | |
195 | settings->sync(); |
196 | qCInfo(lcAccountManager) << "Saved account settings, status:" << settings->status(); |
197 | } |
198 | |
199 | void AccountManager::saveAccountState(AccountState *a) |
200 | { |
201 | qCInfo(lcAccountManager) << "Saving account state" << a->account()->url().toString(); |
202 | auto settings = ConfigFile::settingsWithGroup(QLatin1String(accountsC)); |
203 | settings->beginGroup(a->account()->id()); |
204 | a->writeToSettings(*settings); |
205 | settings->endGroup(); |
206 | |
207 | settings->sync(); |
208 | qCInfo(lcAccountManager) << "Saved account state settings, status:" << settings->status(); |
209 | } |
210 | |
211 | void AccountManager::saveAccountHelper(Account *acc, QSettings &settings, bool saveCredentials) |
212 | { |
213 | settings.setValue(QLatin1String(versionC), maxAccountVersion); |
214 | settings.setValue(QLatin1String(urlC), acc->_url.toString()); |
215 | settings.setValue(QLatin1String(serverVersionC), acc->_serverVersion); |
216 | if (acc->_credentials) { |
217 | if (saveCredentials) { |
218 | // Only persist the credentials if the parameter is set, on migration from 1.8.x |
219 | // we want to save the accounts but not overwrite the credentials |
220 | // (This is easier than asynchronously fetching the credentials from keychain and then |
221 | // re-persisting them) |
222 | acc->_credentials->persist(); |
223 | } |
224 | Q_FOREACH (QString key, acc->_settingsMap.keys()) { |
225 | settings.setValue(key, acc->_settingsMap.value(key)); |
226 | } |
227 | settings.setValue(QLatin1String(authTypeC), acc->_credentials->authType()); |
228 | |
229 | // HACK: Save http_user also as user |
230 | if (acc->_settingsMap.contains(httpUserC)) |
231 | settings.setValue(userC, acc->_settingsMap.value(httpUserC)); |
232 | } |
233 | |
234 | // Save accepted certificates. |
235 | settings.beginGroup(QLatin1String("General" )); |
236 | qCInfo(lcAccountManager) << "Saving " << acc->approvedCerts().count() << " unknown certs." ; |
237 | QByteArray certs; |
238 | Q_FOREACH (const QSslCertificate &cert, acc->approvedCerts()) { |
239 | certs += cert.toPem() + '\n'; |
240 | } |
241 | if (!certs.isEmpty()) { |
242 | settings.setValue(QLatin1String(caCertsKeyC), certs); |
243 | } |
244 | settings.endGroup(); |
245 | |
246 | // Save cookies. |
247 | if (acc->_am) { |
248 | CookieJar *jar = qobject_cast<CookieJar *>(acc->_am->cookieJar()); |
249 | if (jar) { |
250 | qCInfo(lcAccountManager) << "Saving cookies." << acc->cookieJarPath(); |
251 | jar->save(acc->cookieJarPath()); |
252 | } |
253 | } |
254 | } |
255 | |
256 | AccountPtr AccountManager::loadAccountHelper(QSettings &settings) |
257 | { |
258 | auto urlConfig = settings.value(QLatin1String(urlC)); |
259 | if (!urlConfig.isValid()) { |
260 | // No URL probably means a corrupted entry in the account settings |
261 | qCWarning(lcAccountManager) << "No URL for account " << settings.group(); |
262 | return AccountPtr(); |
263 | } |
264 | |
265 | auto acc = createAccount(); |
266 | |
267 | QString authType = settings.value(QLatin1String(authTypeC)).toString(); |
268 | |
269 | // There was an account-type saving bug when 'skip folder config' was used |
270 | // See #5408. This attempts to fix up the "dummy" authType |
271 | if (authType == QLatin1String("dummy" )) { |
272 | if (settings.contains(QLatin1String("http_user" ))) { |
273 | authType = "http" ; |
274 | } else if (settings.contains(QLatin1String("shibboleth_shib_user" ))) { |
275 | authType = "shibboleth" ; |
276 | } |
277 | } |
278 | |
279 | QString overrideUrl = Theme::instance()->overrideServerUrl(); |
280 | QString forceAuth = Theme::instance()->forceConfigAuthType(); |
281 | if (!forceAuth.isEmpty() && !overrideUrl.isEmpty()) { |
282 | // If forceAuth is set, this might also mean the overrideURL has changed. |
283 | // See enterprise issues #1126 |
284 | acc->setUrl(overrideUrl); |
285 | authType = forceAuth; |
286 | } else { |
287 | acc->setUrl(urlConfig.toUrl()); |
288 | } |
289 | |
290 | qCInfo(lcAccountManager) << "Account for" << acc->url() << "using auth type" << authType; |
291 | |
292 | acc->_serverVersion = settings.value(QLatin1String(serverVersionC)).toString(); |
293 | |
294 | // We want to only restore settings for that auth type and the user value |
295 | acc->_settingsMap.insert(QLatin1String(userC), settings.value(userC)); |
296 | QString authTypePrefix = authType + "_" ; |
297 | Q_FOREACH (QString key, settings.childKeys()) { |
298 | if (!key.startsWith(authTypePrefix)) |
299 | continue; |
300 | acc->_settingsMap.insert(key, settings.value(key)); |
301 | } |
302 | |
303 | acc->setCredentials(CredentialsFactory::create(authType)); |
304 | |
305 | // now the server cert, it is in the general group |
306 | settings.beginGroup(QLatin1String("General" )); |
307 | acc->setApprovedCerts(QSslCertificate::fromData(settings.value(caCertsKeyC).toByteArray())); |
308 | settings.endGroup(); |
309 | |
310 | return acc; |
311 | } |
312 | |
313 | AccountStatePtr AccountManager::account(const QString &name) |
314 | { |
315 | foreach (const auto &acc, _accounts) { |
316 | if (acc->account()->displayName() == name) { |
317 | return acc; |
318 | } |
319 | } |
320 | return AccountStatePtr(); |
321 | } |
322 | |
323 | AccountState *AccountManager::addAccount(const AccountPtr &newAccount) |
324 | { |
325 | auto id = newAccount->id(); |
326 | if (id.isEmpty() || !isAccountIdAvailable(id)) { |
327 | id = generateFreeAccountId(); |
328 | } |
329 | newAccount->_id = id; |
330 | |
331 | auto newAccountState = new AccountState(newAccount); |
332 | addAccountState(newAccountState); |
333 | return newAccountState; |
334 | } |
335 | |
336 | void AccountManager::deleteAccount(AccountState *account) |
337 | { |
338 | auto it = std::find(_accounts.begin(), _accounts.end(), account); |
339 | if (it == _accounts.end()) { |
340 | return; |
341 | } |
342 | auto copy = *it; // keep a reference to the shared pointer so it does not delete it just yet |
343 | _accounts.erase(it); |
344 | |
345 | // Forget account credentials, cookies |
346 | account->account()->credentials()->forgetSensitiveData(); |
347 | QFile::remove(account->account()->cookieJarPath()); |
348 | |
349 | auto settings = ConfigFile::settingsWithGroup(QLatin1String(accountsC)); |
350 | settings->remove(account->account()->id()); |
351 | |
352 | emit accountRemoved(account); |
353 | } |
354 | |
355 | AccountPtr AccountManager::createAccount() |
356 | { |
357 | AccountPtr acc = Account::create(); |
358 | acc->setSslErrorHandler(new SslDialogErrorHandler); |
359 | connect(acc.data(), &Account::proxyAuthenticationRequired, |
360 | ProxyAuthHandler::instance(), &ProxyAuthHandler::handleProxyAuthenticationRequired); |
361 | return acc; |
362 | } |
363 | |
364 | void AccountManager::shutdown() |
365 | { |
366 | auto accountsCopy = _accounts; |
367 | _accounts.clear(); |
368 | foreach (const auto &acc, accountsCopy) { |
369 | emit accountRemoved(acc.data()); |
370 | } |
371 | } |
372 | |
373 | bool AccountManager::isAccountIdAvailable(const QString &id) const |
374 | { |
375 | foreach (const auto &acc, _accounts) { |
376 | if (acc->account()->id() == id) { |
377 | return false; |
378 | } |
379 | } |
380 | if (_additionalBlockedAccountIds.contains(id)) |
381 | return false; |
382 | return true; |
383 | } |
384 | |
385 | QString AccountManager::generateFreeAccountId() const |
386 | { |
387 | int i = 0; |
388 | forever { |
389 | QString id = QString::number(i); |
390 | if (isAccountIdAvailable(id)) { |
391 | return id; |
392 | } |
393 | ++i; |
394 | } |
395 | } |
396 | |
397 | void AccountManager::addAccountState(AccountState *accountState) |
398 | { |
399 | QObject::connect(accountState->account().data(), |
400 | &Account::wantsAccountSaved, |
401 | this, &AccountManager::saveAccount); |
402 | |
403 | AccountStatePtr ptr(accountState); |
404 | _accounts << ptr; |
405 | emit accountAdded(accountState); |
406 | } |
407 | } |
408 | |