nextcloud-desktop/src/gui/accountmanager.cpp

461 lines
16 KiB
C++
Raw Normal View History

/*
* Copyright (C) by Olivier Goffart <ogoffart@woboq.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
#include "accountmanager.h"
#include "configfile.h"
2015-04-17 18:56:17 +03:00
#include "sslerrordialog.h"
#include "proxyauthhandler.h"
#include "common/asserts.h"
#include <theme.h>
#include <creds/credentialsfactory.h>
#include <creds/abstractcredentials.h>
#include <cookiejar.h>
#include <QSettings>
#include <QDir>
#include <QNetworkAccessManager>
#include <QMessageBox>
#include "clientsideencryption.h"
#include "ui_mnemonicdialog.h"
namespace {
static const char urlC[] = "url";
static const char authTypeC[] = "authType";
static const char userC[] = "user";
static const char httpUserC[] = "http_user";
static const char davUserC[] = "dav_user";
static const char caCertsKeyC[] = "CaCertificates";
2015-04-24 11:18:33 +03:00
static const char accountsC[] = "Accounts";
static const char versionC[] = "version";
static const char serverVersionC[] = "serverVersion";
// The maximum versions that this client can read
static const int maxAccountsVersion = 2;
static const int maxAccountVersion = 1;
}
namespace OCC {
Q_LOGGING_CATEGORY(lcAccountManager, "nextcloud.gui.account.manager", QtInfoMsg)
AccountManager *AccountManager::instance()
{
static AccountManager instance;
return &instance;
}
bool AccountManager::restore()
2015-04-23 16:42:18 +03:00
{
QStringList skipSettingsKeys;
backwardMigrationSettingsKeys(&skipSettingsKeys, &skipSettingsKeys);
auto settings = ConfigFile::settingsWithGroup(QLatin1String(accountsC));
if (settings->status() != QSettings::NoError || !settings->isWritable()) {
qCWarning(lcAccountManager) << "Could not read settings from" << settings->fileName()
2017-05-17 11:55:42 +03:00
<< settings->status();
return false;
}
2015-04-23 16:42:18 +03:00
if (skipSettingsKeys.contains(settings->group())) {
// Should not happen: bad container keys should have been deleted
qCWarning(lcAccountManager) << "Accounts structure is too new, ignoring";
return true;
}
2015-04-23 16:42:18 +03:00
// If there are no accounts, check the old format.
if (settings->childGroups().isEmpty()
2017-05-17 11:55:42 +03:00
&& !settings->contains(QLatin1String(versionC))) {
restoreFromLegacySettings();
return true;
2015-04-23 16:42:18 +03:00
}
for (const auto &accountId : settings->childGroups()) {
2015-04-23 16:42:18 +03:00
settings->beginGroup(accountId);
if (!skipSettingsKeys.contains(settings->group())) {
if (auto acc = loadAccountHelper(*settings)) {
acc->_id = accountId;
if (auto accState = AccountState::loadFromSettings(acc, *settings)) {
auto jar = qobject_cast<CookieJar*>(acc->_am->cookieJar());
ASSERT(jar);
if (jar)
jar->restore(acc->cookieJarPath());
addAccountState(accState);
}
}
} else {
qCInfo(lcAccountManager) << "Account" << accountId << "is too new, ignoring";
_additionalBlockedAccountIds.insert(accountId);
2015-04-23 16:42:18 +03:00
}
settings->endGroup();
}
return true;
}
void AccountManager::backwardMigrationSettingsKeys(QStringList *deleteKeys, QStringList *ignoreKeys)
{
auto settings = ConfigFile::settingsWithGroup(QLatin1String(accountsC));
const int accountsVersion = settings->value(QLatin1String(versionC)).toInt();
if (accountsVersion <= maxAccountsVersion) {
foreach (const auto &accountId, settings->childGroups()) {
settings->beginGroup(accountId);
const int accountVersion = settings->value(QLatin1String(versionC), 1).toInt();
if (accountVersion > maxAccountVersion) {
ignoreKeys->append(settings->group());
}
settings->endGroup();
}
} else {
deleteKeys->append(settings->group());
}
}
2015-04-23 16:42:18 +03:00
bool AccountManager::restoreFromLegacySettings()
{
qCInfo(lcAccountManager) << "Migrate: restoreFromLegacySettings, checking settings group"
2017-05-17 11:55:42 +03:00
<< Theme::instance()->appName();
// try to open the correctly themed settings
auto settings = ConfigFile::settingsWithGroup(Theme::instance()->appName());
// if the settings file could not be opened, the childKeys list is empty
2015-04-23 16:42:18 +03:00
// then try to load settings from a very old place
2017-05-17 11:55:42 +03:00
if (settings->childKeys().isEmpty()) {
// Now try to open the original ownCloud settings to see if they exist.
2017-05-17 11:55:42 +03:00
QString oCCfgFile = QDir::fromNativeSeparators(settings->fileName());
// replace the last two segments with ownCloud/owncloud.cfg
2017-05-17 11:55:42 +03:00
oCCfgFile = oCCfgFile.left(oCCfgFile.lastIndexOf('/'));
oCCfgFile = oCCfgFile.left(oCCfgFile.lastIndexOf('/'));
oCCfgFile += QLatin1String("/ownCloud/owncloud.cfg");
qCInfo(lcAccountManager) << "Migrate: checking old config " << oCCfgFile;
2017-05-17 11:55:42 +03:00
QFileInfo fi(oCCfgFile);
if (fi.isReadable()) {
std::unique_ptr<QSettings> oCSettings(new QSettings(oCCfgFile, QSettings::IniFormat));
oCSettings->beginGroup(QLatin1String("ownCloud"));
// Check the theme url to see if it is the same url that the oC config was for
QString overrideUrl = Theme::instance()->overrideServerUrl();
2017-05-17 11:55:42 +03:00
if (!overrideUrl.isEmpty()) {
if (overrideUrl.endsWith('/')) {
overrideUrl.chop(1);
}
QString oCUrl = oCSettings->value(QLatin1String(urlC)).toString();
2017-05-17 11:55:42 +03:00
if (oCUrl.endsWith('/')) {
oCUrl.chop(1);
}
// in case the urls are equal reset the settings object to read from
// the ownCloud settings object
qCInfo(lcAccountManager) << "Migrate oC config if " << oCUrl << " == " << overrideUrl << ":"
2017-05-17 11:55:42 +03:00
<< (oCUrl == overrideUrl ? "Yes" : "No");
if (oCUrl == overrideUrl) {
settings = std::move(oCSettings);
}
}
}
}
2015-04-23 16:42:18 +03:00
// Try to load the single account.
if (!settings->childKeys().isEmpty()) {
if (auto acc = loadAccountHelper(*settings)) {
2015-04-23 16:42:18 +03:00
addAccount(acc);
return true;
}
}
return false;
}
void AccountManager::save(bool saveCredentials)
{
auto settings = ConfigFile::settingsWithGroup(QLatin1String(accountsC));
settings->setValue(QLatin1String(versionC), maxAccountsVersion);
for (const auto &acc : qAsConst(_accounts)) {
2015-04-23 16:42:18 +03:00
settings->beginGroup(acc->account()->id());
saveAccountHelper(acc->account().data(), *settings, saveCredentials);
acc->writeToSettings(*settings);
2015-04-23 16:42:18 +03:00
settings->endGroup();
2015-04-17 18:56:17 +03:00
}
2015-09-17 14:48:05 +03:00
settings->sync();
qCInfo(lcAccountManager) << "Saved all account settings, status:" << settings->status();
2015-04-17 18:56:17 +03:00
}
2017-05-17 11:55:42 +03:00
void AccountManager::saveAccount(Account *a)
{
qCDebug(lcAccountManager) << "Saving account" << a->url().toString();
auto settings = ConfigFile::settingsWithGroup(QLatin1String(accountsC));
settings->beginGroup(a->id());
saveAccountHelper(a, *settings, false); // don't save credentials they might not have been loaded yet
settings->endGroup();
2015-09-17 14:48:05 +03:00
settings->sync();
qCDebug(lcAccountManager) << "Saved account settings, status:" << settings->status();
}
2017-05-17 11:55:42 +03:00
void AccountManager::saveAccountState(AccountState *a)
{
qCDebug(lcAccountManager) << "Saving account state" << a->account()->url().toString();
auto settings = ConfigFile::settingsWithGroup(QLatin1String(accountsC));
settings->beginGroup(a->account()->id());
a->writeToSettings(*settings);
settings->endGroup();
settings->sync();
qCDebug(lcAccountManager) << "Saved account state settings, status:" << settings->status();
}
2017-05-17 11:55:42 +03:00
void AccountManager::saveAccountHelper(Account *acc, QSettings &settings, bool saveCredentials)
2015-04-17 18:56:17 +03:00
{
settings.setValue(QLatin1String(versionC), maxAccountVersion);
2015-04-24 11:18:33 +03:00
settings.setValue(QLatin1String(urlC), acc->_url.toString());
settings.setValue(QLatin1String(davUserC), acc->_davUser);
settings.setValue(QLatin1String(serverVersionC), acc->_serverVersion);
if (acc->_credentials) {
if (saveCredentials) {
// Only persist the credentials if the parameter is set, on migration from 1.8.x
// we want to save the accounts but not overwrite the credentials
// (This is easier than asynchronously fetching the credentials from keychain and then
// re-persisting them)
acc->_credentials->persist();
}
for (const auto &key : acc->_settingsMap.keys()) {
2015-04-24 11:18:33 +03:00
settings.setValue(key, acc->_settingsMap.value(key));
}
2015-04-24 11:18:33 +03:00
settings.setValue(QLatin1String(authTypeC), acc->_credentials->authType());
// HACK: Save http_user also as user
if (acc->_settingsMap.contains(httpUserC))
2015-04-24 11:18:33 +03:00
settings.setValue(userC, acc->_settingsMap.value(httpUserC));
}
// Save accepted certificates.
2015-04-24 11:18:33 +03:00
settings.beginGroup(QLatin1String("General"));
qCInfo(lcAccountManager) << "Saving " << acc->approvedCerts().count() << " unknown certs.";
QByteArray certs;
for (const auto &cert : acc->approvedCerts()) {
certs += cert.toPem() + '\n';
}
if (!certs.isEmpty()) {
2017-05-17 11:55:42 +03:00
settings.setValue(QLatin1String(caCertsKeyC), certs);
}
2015-04-24 11:18:33 +03:00
settings.endGroup();
// Save cookies.
if (acc->_am) {
auto *jar = qobject_cast<CookieJar *>(acc->_am->cookieJar());
if (jar) {
qCInfo(lcAccountManager) << "Saving cookies." << acc->cookieJarPath();
2020-01-24 19:57:34 +03:00
if (!jar->save(acc->cookieJarPath()))
{
qCWarning(lcAccountManager) << "Failed to save cookies to" << acc->cookieJarPath();
}
}
}
}
2017-05-17 11:55:42 +03:00
AccountPtr AccountManager::loadAccountHelper(QSettings &settings)
2015-04-23 16:42:18 +03:00
{
auto urlConfig = settings.value(QLatin1String(urlC));
if (!urlConfig.isValid()) {
// No URL probably means a corrupted entry in the account settings
qCWarning(lcAccountManager) << "No URL for account " << settings.group();
return AccountPtr();
}
auto acc = createAccount();
2015-04-23 16:42:18 +03:00
QString authType = settings.value(QLatin1String(authTypeC)).toString();
2017-01-03 12:37:42 +03:00
// There was an account-type saving bug when 'skip folder config' was used
// See #5408. This attempts to fix up the "dummy" authType
if (authType == QLatin1String("dummy")) {
if (settings.contains(QLatin1String("http_user"))) {
authType = "http";
} else if (settings.contains(QLatin1String("shibboleth_shib_user"))) {
authType = "shibboleth";
}
}
QString overrideUrl = Theme::instance()->overrideServerUrl();
QString forceAuth = Theme::instance()->forceConfigAuthType();
2017-05-17 11:55:42 +03:00
if (!forceAuth.isEmpty() && !overrideUrl.isEmpty()) {
// If forceAuth is set, this might also mean the overrideURL has changed.
// See enterprise issues #1126
acc->setUrl(overrideUrl);
authType = forceAuth;
} else {
acc->setUrl(urlConfig.toUrl());
}
// Migrate to webflow
if (authType == QLatin1String("http")) {
authType = "webflow";
settings.setValue(QLatin1String(authTypeC), authType);
for (const QString &key : settings.childKeys()) {
if (!key.startsWith("http_"))
continue;
auto newkey = QString::fromLatin1("webflow_").append(key.mid(5));
settings.setValue(newkey, settings.value((key)));
settings.remove(key);
}
}
qCInfo(lcAccountManager) << "Account for" << acc->url() << "using auth type" << authType;
acc->_serverVersion = settings.value(QLatin1String(serverVersionC)).toString();
acc->_davUser = settings.value(QLatin1String(davUserC)).toString();
2015-04-23 16:42:18 +03:00
// We want to only restore settings for that auth type and the user value
2015-04-24 11:18:33 +03:00
acc->_settingsMap.insert(QLatin1String(userC), settings.value(userC));
QString authTypePrefix = authType + "_";
for (const auto &key : settings.childKeys()) {
2015-04-23 16:42:18 +03:00
if (!key.startsWith(authTypePrefix))
continue;
2015-04-24 11:18:33 +03:00
acc->_settingsMap.insert(key, settings.value(key));
2015-04-23 16:42:18 +03:00
}
acc->setCredentials(CredentialsFactory::create(authType));
2015-04-23 16:42:18 +03:00
// now the server cert, it is in the general group
2015-04-24 11:18:33 +03:00
settings.beginGroup(QLatin1String("General"));
const auto certs = QSslCertificate::fromData(settings.value(caCertsKeyC).toByteArray());
qCInfo(lcAccountManager) << "Restored: " << certs.count() << " unknown certs.";
acc->setApprovedCerts(certs);
2015-04-24 11:18:33 +03:00
settings.endGroup();
2015-04-23 16:42:18 +03:00
return acc;
}
2017-05-17 11:55:42 +03:00
AccountStatePtr AccountManager::account(const QString &name)
{
const auto it = std::find_if(_accounts.cbegin(), _accounts.cend(), [name](const auto &acc) {
return acc->account()->displayName() == name;
});
return it != _accounts.cend() ? *it : AccountStatePtr();
}
2017-05-17 11:55:42 +03:00
AccountState *AccountManager::addAccount(const AccountPtr &newAccount)
2015-04-17 18:56:17 +03:00
{
2015-04-24 08:02:51 +03:00
auto id = newAccount->id();
if (id.isEmpty() || !isAccountIdAvailable(id)) {
id = generateFreeAccountId();
}
newAccount->_id = id;
auto newAccountState = new AccountState(newAccount);
addAccountState(newAccountState);
return newAccountState;
2015-04-17 18:56:17 +03:00
}
2017-05-17 11:55:42 +03:00
void AccountManager::deleteAccount(AccountState *account)
{
auto it = std::find(_accounts.begin(), _accounts.end(), account);
2017-05-17 11:55:42 +03:00
if (it == _accounts.end()) {
return;
}
auto copy = *it; // keep a reference to the shared pointer so it does not delete it just yet
_accounts.erase(it);
// Forget account credentials, cookies
account->account()->credentials()->forgetSensitiveData();
QFile::remove(account->account()->cookieJarPath());
auto settings = ConfigFile::settingsWithGroup(QLatin1String(accountsC));
settings->remove(account->account()->id());
// Forget E2E keys
account->account()->e2e()->forgetSensitiveData(account->account());
emit accountRemoved(account);
}
AccountPtr AccountManager::createAccount()
{
AccountPtr acc = Account::create();
acc->setSslErrorHandler(new SslDialogErrorHandler);
connect(acc.data(), &Account::proxyAuthenticationRequired,
ProxyAuthHandler::instance(), &ProxyAuthHandler::handleProxyAuthenticationRequired);
return acc;
}
void AccountManager::displayMnemonic(const QString& mnemonic)
{
auto *widget = new QDialog;
Ui_Dialog ui;
ui.setupUi(widget);
widget->setWindowTitle(tr("End to end encryption mnemonic"));
ui.label->setText(tr("To protect your Cryptographic Identity, we encrypt it with a mnemonic of 12 dictionary words. "
"Please note these down and keep them safe. "
"They will be needed to add other devices to your account (like your mobile phone or laptop)."));
ui.textEdit->setText(mnemonic);
ui.textEdit->focusWidget();
ui.textEdit->selectAll();
ui.textEdit->setAlignment(Qt::AlignCenter);
widget->exec();
widget->resize(widget->sizeHint());
}
2015-04-17 18:56:17 +03:00
void AccountManager::shutdown()
{
const auto accountsCopy = _accounts;
2015-04-17 18:56:17 +03:00
_accounts.clear();
for (const auto &acc : accountsCopy) {
2015-04-17 18:56:17 +03:00
emit accountRemoved(acc.data());
emit removeAccountFolders(acc.data());
2015-04-17 18:56:17 +03:00
}
}
QList<AccountStatePtr> AccountManager::accounts() const
{
return _accounts;
}
2017-05-17 11:55:42 +03:00
bool AccountManager::isAccountIdAvailable(const QString &id) const
2015-04-24 08:02:51 +03:00
{
if (_additionalBlockedAccountIds.contains(id))
return false;
return std::none_of(_accounts.cbegin(), _accounts.cend(), [id](const auto &acc) {
return acc->account()->id() == id;
});
2015-04-24 08:02:51 +03:00
}
QString AccountManager::generateFreeAccountId() const
{
int i = 0;
forever {
QString id = QString::number(i);
if (isAccountIdAvailable(id)) {
return id;
}
++i;
}
}
2017-05-17 11:55:42 +03:00
void AccountManager::addAccountState(AccountState *accountState)
{
QObject::connect(accountState->account().data(),
&Account::wantsAccountSaved,
this, &AccountManager::saveAccount);
AccountStatePtr ptr(accountState);
_accounts << ptr;
emit accountAdded(accountState);
}
}