2013-10-21 19:42:52 +00:00
|
|
|
/*
|
|
|
|
* Copyright (C) by Daniel Molkentin <danimo@owncloud.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
|
2016-10-25 09:00:07 +00:00
|
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
|
|
* (at your option) any later version.
|
2013-10-21 19:42:52 +00:00
|
|
|
*
|
|
|
|
* 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.
|
|
|
|
*/
|
|
|
|
|
2014-07-10 22:31:24 +00:00
|
|
|
#include "account.h"
|
|
|
|
#include "cookiejar.h"
|
|
|
|
#include "networkjobs.h"
|
2014-11-09 22:25:57 +00:00
|
|
|
#include "configfile.h"
|
|
|
|
#include "accessmanager.h"
|
2013-10-21 19:42:52 +00:00
|
|
|
#include "creds/abstractcredentials.h"
|
2015-07-16 19:49:12 +00:00
|
|
|
#include "capabilities.h"
|
2015-02-18 16:49:07 +00:00
|
|
|
#include "theme.h"
|
2021-01-12 08:23:41 +00:00
|
|
|
#include "pushnotifications.h"
|
2021-03-16 11:09:49 +00:00
|
|
|
#include "version.h"
|
2017-09-11 14:52:57 +00:00
|
|
|
|
2017-09-01 16:11:43 +00:00
|
|
|
#include "common/asserts.h"
|
2017-09-11 14:52:57 +00:00
|
|
|
#include "clientsideencryption.h"
|
2013-10-21 19:42:52 +00:00
|
|
|
|
2017-05-09 12:24:11 +00:00
|
|
|
#include <QLoggingCategory>
|
2013-10-21 19:42:52 +00:00
|
|
|
#include <QNetworkReply>
|
2013-10-23 12:48:44 +00:00
|
|
|
#include <QNetworkAccessManager>
|
2013-10-23 22:29:08 +00:00
|
|
|
#include <QSslSocket>
|
2013-11-13 13:24:02 +00:00
|
|
|
#include <QNetworkCookieJar>
|
2020-06-18 19:48:08 +00:00
|
|
|
#include <QNetworkProxy>
|
2017-08-25 14:56:34 +00:00
|
|
|
|
2014-06-12 15:34:39 +00:00
|
|
|
#include <QFileInfo>
|
2014-06-25 09:11:27 +00:00
|
|
|
#include <QDir>
|
2014-11-18 15:44:14 +00:00
|
|
|
#include <QSslKey>
|
2017-09-20 15:14:33 +00:00
|
|
|
#include <QAuthenticator>
|
2017-12-06 14:45:17 +00:00
|
|
|
#include <QStandardPaths>
|
2020-01-18 14:07:51 +00:00
|
|
|
#include <QJsonDocument>
|
|
|
|
#include <QJsonObject>
|
|
|
|
#include <QJsonArray>
|
2013-11-07 09:14:25 +00:00
|
|
|
|
2020-10-29 12:24:20 +00:00
|
|
|
#include <qt5keychain/keychain.h>
|
2019-07-24 11:56:21 +00:00
|
|
|
#include "creds/abstractcredentials.h"
|
|
|
|
|
|
|
|
using namespace QKeychain;
|
|
|
|
|
2014-11-09 21:34:07 +00:00
|
|
|
namespace OCC {
|
2013-10-21 19:42:52 +00:00
|
|
|
|
2017-12-28 19:33:10 +00:00
|
|
|
Q_LOGGING_CATEGORY(lcAccount, "nextcloud.sync.account", QtInfoMsg)
|
2019-07-24 11:56:21 +00:00
|
|
|
const char app_password[] = "_app-password";
|
2015-06-26 15:07:47 +00:00
|
|
|
|
2014-12-18 11:09:48 +00:00
|
|
|
Account::Account(QObject *parent)
|
2013-10-21 19:42:52 +00:00
|
|
|
: QObject(parent)
|
2015-07-16 19:49:12 +00:00
|
|
|
, _capabilities(QVariantMap())
|
2015-02-18 16:49:07 +00:00
|
|
|
, _davPath(Theme::instance()->webDavPath())
|
2013-10-21 19:42:52 +00:00
|
|
|
{
|
2014-12-18 11:09:48 +00:00
|
|
|
qRegisterMetaType<AccountPtr>("AccountPtr");
|
2021-01-12 08:23:41 +00:00
|
|
|
qRegisterMetaType<Account *>("Account*");
|
2014-12-18 11:09:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
AccountPtr Account::create()
|
|
|
|
{
|
|
|
|
AccountPtr acc = AccountPtr(new Account);
|
|
|
|
acc->setSharedThis(acc);
|
|
|
|
return acc;
|
2013-10-21 19:42:52 +00:00
|
|
|
}
|
|
|
|
|
2017-11-23 15:54:45 +00:00
|
|
|
ClientSideEncryption* Account::e2e()
|
2017-11-13 16:22:09 +00:00
|
|
|
{
|
2019-07-24 11:56:21 +00:00
|
|
|
// Qt expects everything in the connect to be a pointer, so return a pointer.
|
|
|
|
return &_e2e;
|
2017-11-13 16:22:09 +00:00
|
|
|
}
|
|
|
|
|
2020-05-25 19:33:24 +00:00
|
|
|
Account::~Account() = default;
|
2013-11-04 15:36:23 +00:00
|
|
|
|
2015-09-10 13:38:40 +00:00
|
|
|
QString Account::davPath() const
|
|
|
|
{
|
2016-11-16 08:30:29 +00:00
|
|
|
if (capabilities().chunkingNg()) {
|
|
|
|
// The chunking-ng means the server prefer to use the new webdav URL
|
|
|
|
return QLatin1String("/remote.php/dav/files/") + davUser() + QLatin1Char('/');
|
|
|
|
}
|
|
|
|
|
2015-09-10 13:38:40 +00:00
|
|
|
// make sure to have a trailing slash
|
|
|
|
if (!_davPath.endsWith('/')) {
|
|
|
|
QString dp(_davPath);
|
|
|
|
dp.append('/');
|
|
|
|
return dp;
|
|
|
|
}
|
|
|
|
return _davPath;
|
|
|
|
}
|
|
|
|
|
2014-12-18 11:09:48 +00:00
|
|
|
void Account::setSharedThis(AccountPtr sharedThis)
|
|
|
|
{
|
|
|
|
_sharedThis = sharedThis.toWeakRef();
|
|
|
|
}
|
|
|
|
|
|
|
|
AccountPtr Account::sharedFromThis()
|
|
|
|
{
|
|
|
|
return _sharedThis.toStrongRef();
|
|
|
|
}
|
|
|
|
|
2016-11-23 16:08:17 +00:00
|
|
|
QString Account::davUser() const
|
2016-08-02 11:48:56 +00:00
|
|
|
{
|
2016-11-23 16:08:17 +00:00
|
|
|
return _davUser.isEmpty() ? _credentials->user() : _davUser;
|
2016-09-16 13:49:43 +00:00
|
|
|
}
|
|
|
|
|
2016-11-23 16:08:17 +00:00
|
|
|
void Account::setDavUser(const QString &newDavUser)
|
2016-09-16 13:49:43 +00:00
|
|
|
{
|
2018-10-05 17:45:43 +00:00
|
|
|
if (_davUser == newDavUser)
|
|
|
|
return;
|
2016-11-23 16:08:17 +00:00
|
|
|
_davUser = newDavUser;
|
2018-10-05 17:45:43 +00:00
|
|
|
emit wantsAccountSaved(this);
|
2016-08-02 11:48:56 +00:00
|
|
|
}
|
2013-10-21 19:42:52 +00:00
|
|
|
|
2017-11-23 09:11:39 +00:00
|
|
|
#ifndef TOKEN_AUTH_ONLY
|
2017-03-09 21:34:36 +00:00
|
|
|
QImage Account::avatar() const
|
2017-01-22 12:55:08 +00:00
|
|
|
{
|
2017-03-09 21:34:36 +00:00
|
|
|
return _avatarImg;
|
2017-01-22 12:55:08 +00:00
|
|
|
}
|
2017-03-09 21:34:36 +00:00
|
|
|
void Account::setAvatar(const QImage &img)
|
2017-01-22 12:55:08 +00:00
|
|
|
{
|
2017-03-09 21:34:36 +00:00
|
|
|
_avatarImg = img;
|
2017-01-22 12:55:08 +00:00
|
|
|
emit accountChangedAvatar();
|
|
|
|
}
|
2017-11-23 09:11:39 +00:00
|
|
|
#endif
|
2017-01-22 12:55:08 +00:00
|
|
|
|
2015-04-23 12:51:06 +00:00
|
|
|
QString Account::displayName() const
|
|
|
|
{
|
2019-09-30 15:56:14 +00:00
|
|
|
QString dn = QString("%1@%2").arg(credentials()->user(), _url.host());
|
2015-09-10 23:26:27 +00:00
|
|
|
int port = url().port();
|
|
|
|
if (port > 0 && port != 80 && port != 443) {
|
|
|
|
dn.append(QLatin1Char(':'));
|
|
|
|
dn.append(QString::number(port));
|
|
|
|
}
|
|
|
|
return dn;
|
2015-04-23 12:51:06 +00:00
|
|
|
}
|
|
|
|
|
2017-10-05 19:08:38 +00:00
|
|
|
QString Account::davDisplayName() const
|
|
|
|
{
|
|
|
|
return _displayName;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Account::setDavDisplayName(const QString &newDisplayName)
|
|
|
|
{
|
|
|
|
_displayName = newDisplayName;
|
|
|
|
emit accountChangedDisplayName();
|
|
|
|
}
|
|
|
|
|
2015-04-23 12:57:36 +00:00
|
|
|
QString Account::id() const
|
|
|
|
{
|
|
|
|
return _id;
|
|
|
|
}
|
|
|
|
|
2013-10-21 19:42:52 +00:00
|
|
|
AbstractCredentials *Account::credentials() const
|
|
|
|
{
|
2016-06-15 15:57:28 +00:00
|
|
|
return _credentials.data();
|
2013-10-21 19:42:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Account::setCredentials(AbstractCredentials *cred)
|
|
|
|
{
|
2013-10-23 12:48:44 +00:00
|
|
|
// set active credential manager
|
2018-11-12 17:46:39 +00:00
|
|
|
QNetworkCookieJar *jar = nullptr;
|
2020-06-18 19:48:08 +00:00
|
|
|
QNetworkProxy proxy;
|
|
|
|
|
2013-10-23 12:48:44 +00:00
|
|
|
if (_am) {
|
2014-05-14 09:11:45 +00:00
|
|
|
jar = _am->cookieJar();
|
2018-11-12 17:46:39 +00:00
|
|
|
jar->setParent(nullptr);
|
2014-05-14 09:11:45 +00:00
|
|
|
|
2020-06-18 19:48:08 +00:00
|
|
|
// Remember proxy (issue #2108)
|
|
|
|
proxy = _am->proxy();
|
|
|
|
|
2016-06-21 10:03:52 +00:00
|
|
|
_am = QSharedPointer<QNetworkAccessManager>();
|
2014-02-23 10:02:03 +00:00
|
|
|
}
|
2015-04-23 12:47:31 +00:00
|
|
|
|
|
|
|
// The order for these two is important! Reading the credential's
|
2016-06-15 15:57:28 +00:00
|
|
|
// settings accesses the account as well as account->_credentials,
|
2017-07-07 09:09:11 +00:00
|
|
|
_credentials.reset(cred);
|
2015-04-23 12:47:31 +00:00
|
|
|
cred->setAccount(this);
|
|
|
|
|
2017-07-07 09:09:11 +00:00
|
|
|
// Note: This way the QNAM can outlive the Account and Credentials.
|
|
|
|
// This is necessary to avoid issues with the QNAM being deleted while
|
|
|
|
// processing slotHandleSslErrors().
|
|
|
|
_am = QSharedPointer<QNetworkAccessManager>(_credentials->createQNAM(), &QObject::deleteLater);
|
2016-06-15 15:57:28 +00:00
|
|
|
|
2014-05-14 09:11:45 +00:00
|
|
|
if (jar) {
|
|
|
|
_am->setCookieJar(jar);
|
|
|
|
}
|
2020-06-18 19:48:08 +00:00
|
|
|
if (proxy.type() != QNetworkProxy::DefaultProxy) {
|
|
|
|
_am->setProxy(proxy);
|
|
|
|
}
|
2016-06-15 15:57:28 +00:00
|
|
|
connect(_am.data(), SIGNAL(sslErrors(QNetworkReply *, QList<QSslError>)),
|
2015-07-16 12:21:51 +00:00
|
|
|
SLOT(slotHandleSslErrors(QNetworkReply *, QList<QSslError>)));
|
2017-09-20 08:14:48 +00:00
|
|
|
connect(_am.data(), &QNetworkAccessManager::proxyAuthenticationRequired,
|
|
|
|
this, &Account::proxyAuthenticationRequired);
|
|
|
|
connect(_credentials.data(), &AbstractCredentials::fetched,
|
|
|
|
this, &Account::slotCredentialsFetched);
|
|
|
|
connect(_credentials.data(), &AbstractCredentials::asked,
|
|
|
|
this, &Account::slotCredentialsAsked);
|
2021-01-12 08:23:41 +00:00
|
|
|
|
|
|
|
trySetupPushNotifications();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Account::trySetupPushNotifications()
|
|
|
|
{
|
|
|
|
if (_capabilities.availablePushNotifications() != PushNotificationType::None) {
|
|
|
|
qCInfo(lcAccount) << "Try to setup push notifications";
|
|
|
|
|
|
|
|
if (!_pushNotifications) {
|
|
|
|
_pushNotifications = new PushNotifications(this, this);
|
|
|
|
|
|
|
|
connect(_pushNotifications, &PushNotifications::ready, this, [this]() { emit pushNotificationsReady(this); });
|
|
|
|
|
|
|
|
const auto deletePushNotifications = [this]() {
|
|
|
|
qCInfo(lcAccount) << "Delete push notifications object because authentication failed or connection lost";
|
|
|
|
_pushNotifications->deleteLater();
|
|
|
|
_pushNotifications = nullptr;
|
2021-01-26 14:57:46 +00:00
|
|
|
emit pushNotificationsDisabled(this);
|
2021-01-12 08:23:41 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
connect(_pushNotifications, &PushNotifications::connectionLost, this, deletePushNotifications);
|
|
|
|
connect(_pushNotifications, &PushNotifications::authenticationFailed, this, deletePushNotifications);
|
|
|
|
}
|
|
|
|
// If push notifications already running it is no problem to call setup again
|
|
|
|
_pushNotifications->setup();
|
|
|
|
}
|
2013-10-21 19:42:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
QUrl Account::davUrl() const
|
|
|
|
{
|
2016-10-25 10:04:22 +00:00
|
|
|
return Utility::concatUrlPath(url(), davPath());
|
2013-10-21 19:42:52 +00:00
|
|
|
}
|
|
|
|
|
2017-09-15 12:24:34 +00:00
|
|
|
QUrl Account::deprecatedPrivateLinkUrl(const QByteArray &numericFileId) const
|
2017-05-10 07:37:10 +00:00
|
|
|
{
|
2017-11-10 08:57:42 +00:00
|
|
|
return Utility::concatUrlPath(_userVisibleUrl,
|
2017-05-10 07:37:10 +00:00
|
|
|
QLatin1String("/index.php/f/") + QUrl::toPercentEncoding(QString::fromLatin1(numericFileId)));
|
|
|
|
}
|
|
|
|
|
2017-01-03 10:39:10 +00:00
|
|
|
/**
|
|
|
|
* clear all cookies. (Session cookies or not)
|
|
|
|
*/
|
2014-03-03 15:36:30 +00:00
|
|
|
void Account::clearCookieJar()
|
|
|
|
{
|
2017-02-07 12:52:15 +00:00
|
|
|
auto jar = qobject_cast<CookieJar *>(_am->cookieJar());
|
|
|
|
ASSERT(jar);
|
|
|
|
jar->setAllCookies(QList<QNetworkCookie>());
|
2017-01-03 10:39:10 +00:00
|
|
|
emit wantsAccountSaved(this);
|
2015-06-18 18:41:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*! This shares our official cookie jar (containing all the tasty
|
|
|
|
authentication cookies) with another QNAM while making sure
|
2015-10-05 03:20:09 +00:00
|
|
|
of not losing its ownership. */
|
2015-06-18 18:41:00 +00:00
|
|
|
void Account::lendCookieJarTo(QNetworkAccessManager *guest)
|
|
|
|
{
|
|
|
|
auto jar = _am->cookieJar();
|
|
|
|
auto oldParent = jar->parent();
|
|
|
|
guest->setCookieJar(jar); // takes ownership of our precious cookie jar
|
|
|
|
jar->setParent(oldParent); // takes it back
|
2014-05-14 09:11:45 +00:00
|
|
|
}
|
|
|
|
|
2017-01-26 09:54:03 +00:00
|
|
|
QString Account::cookieJarPath()
|
|
|
|
{
|
2017-12-06 14:45:17 +00:00
|
|
|
return QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation) + "/cookies" + id() + ".db";
|
2017-01-26 09:54:03 +00:00
|
|
|
}
|
|
|
|
|
2015-03-19 10:40:47 +00:00
|
|
|
void Account::resetNetworkAccessManager()
|
|
|
|
{
|
|
|
|
if (!_credentials || !_am) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-05-09 12:24:11 +00:00
|
|
|
qCDebug(lcAccount) << "Resetting QNAM";
|
2015-03-19 10:40:47 +00:00
|
|
|
QNetworkCookieJar *jar = _am->cookieJar();
|
2020-06-18 19:48:08 +00:00
|
|
|
QNetworkProxy proxy = _am->proxy();
|
2016-06-15 15:57:28 +00:00
|
|
|
|
|
|
|
// Use a QSharedPointer to allow locking the life of the QNAM on the stack.
|
|
|
|
// Make it call deleteLater to make sure that we can return to any QNAM stack frames safely.
|
2017-07-07 09:09:11 +00:00
|
|
|
_am = QSharedPointer<QNetworkAccessManager>(_credentials->createQNAM(), &QObject::deleteLater);
|
2016-06-15 15:57:28 +00:00
|
|
|
|
2015-03-19 10:40:47 +00:00
|
|
|
_am->setCookieJar(jar); // takes ownership of the old cookie jar
|
2020-06-18 19:48:08 +00:00
|
|
|
_am->setProxy(proxy); // Remember proxy (issue #2108)
|
|
|
|
|
2016-06-15 15:57:28 +00:00
|
|
|
connect(_am.data(), SIGNAL(sslErrors(QNetworkReply *, QList<QSslError>)),
|
2015-07-16 12:21:51 +00:00
|
|
|
SLOT(slotHandleSslErrors(QNetworkReply *, QList<QSslError>)));
|
2017-09-20 08:14:48 +00:00
|
|
|
connect(_am.data(), &QNetworkAccessManager::proxyAuthenticationRequired,
|
|
|
|
this, &Account::proxyAuthenticationRequired);
|
2015-03-19 10:40:47 +00:00
|
|
|
}
|
|
|
|
|
2014-05-14 09:11:45 +00:00
|
|
|
QNetworkAccessManager *Account::networkAccessManager()
|
|
|
|
{
|
2016-06-15 15:57:28 +00:00
|
|
|
return _am.data();
|
2014-03-03 15:36:30 +00:00
|
|
|
}
|
|
|
|
|
2017-04-20 07:21:33 +00:00
|
|
|
QSharedPointer<QNetworkAccessManager> Account::sharedNetworkAccessManager()
|
|
|
|
{
|
|
|
|
return _am;
|
|
|
|
}
|
|
|
|
|
2017-09-08 14:43:59 +00:00
|
|
|
QNetworkReply *Account::sendRawRequest(const QByteArray &verb, const QUrl &url, QNetworkRequest req, QIODevice *data)
|
2013-10-28 19:01:59 +00:00
|
|
|
{
|
|
|
|
req.setUrl(url);
|
2015-05-08 12:21:27 +00:00
|
|
|
req.setSslConfiguration(this->getOrCreateSslConfig());
|
2017-03-03 10:20:53 +00:00
|
|
|
if (verb == "HEAD" && !data) {
|
|
|
|
return _am->head(req);
|
|
|
|
} else if (verb == "GET" && !data) {
|
|
|
|
return _am->get(req);
|
|
|
|
} else if (verb == "POST") {
|
|
|
|
return _am->post(req, data);
|
|
|
|
} else if (verb == "PUT") {
|
|
|
|
return _am->put(req, data);
|
|
|
|
} else if (verb == "DELETE" && !data) {
|
|
|
|
return _am->deleteResource(req);
|
|
|
|
}
|
2013-10-23 12:48:44 +00:00
|
|
|
return _am->sendCustomRequest(req, verb, data);
|
2013-10-21 19:42:52 +00:00
|
|
|
}
|
|
|
|
|
2017-09-08 14:43:59 +00:00
|
|
|
SimpleNetworkJob *Account::sendRequest(const QByteArray &verb, const QUrl &url, QNetworkRequest req, QIODevice *data)
|
|
|
|
{
|
2018-11-22 07:46:33 +00:00
|
|
|
auto job = new SimpleNetworkJob(sharedFromThis());
|
2017-09-08 14:43:59 +00:00
|
|
|
job->startRequest(verb, url, req, data);
|
|
|
|
return job;
|
|
|
|
}
|
|
|
|
|
2014-01-21 00:45:02 +00:00
|
|
|
void Account::setSslConfiguration(const QSslConfiguration &config)
|
2013-10-21 19:42:52 +00:00
|
|
|
{
|
2014-01-21 00:45:02 +00:00
|
|
|
_sslConfiguration = config;
|
2013-10-21 19:42:52 +00:00
|
|
|
}
|
|
|
|
|
2015-05-08 12:21:27 +00:00
|
|
|
QSslConfiguration Account::getOrCreateSslConfig()
|
2014-11-18 15:44:14 +00:00
|
|
|
{
|
2015-05-08 12:21:27 +00:00
|
|
|
if (!_sslConfiguration.isNull()) {
|
|
|
|
// Will be set by CheckServerJob::finished()
|
|
|
|
// We need to use a central shared config to get SSL session tickets
|
|
|
|
return _sslConfiguration;
|
|
|
|
}
|
|
|
|
|
2014-11-18 15:44:14 +00:00
|
|
|
// if setting the client certificate fails, you will probably get an error similar to this:
|
|
|
|
// "An internal error number 1060 happened. SSL handshake failed, client certificate was requested: SSL error: sslv3 alert handshake failure"
|
2015-05-08 12:21:27 +00:00
|
|
|
QSslConfiguration sslConfig = QSslConfiguration::defaultConfiguration();
|
2017-04-21 16:13:32 +00:00
|
|
|
|
2015-05-08 12:21:27 +00:00
|
|
|
// Try hard to re-use session for different requests
|
|
|
|
sslConfig.setSslOption(QSsl::SslOptionDisableSessionTickets, false);
|
|
|
|
sslConfig.setSslOption(QSsl::SslOptionDisableSessionSharing, false);
|
|
|
|
sslConfig.setSslOption(QSsl::SslOptionDisableSessionPersistence, false);
|
|
|
|
|
2014-11-18 15:44:14 +00:00
|
|
|
return sslConfig;
|
|
|
|
}
|
|
|
|
|
2013-11-13 13:18:07 +00:00
|
|
|
void Account::setApprovedCerts(const QList<QSslCertificate> certs)
|
2013-10-21 19:42:52 +00:00
|
|
|
{
|
2013-11-13 13:18:07 +00:00
|
|
|
_approvedCerts = certs;
|
2020-02-10 12:08:19 +00:00
|
|
|
QSslSocket::addDefaultCaCertificates(certs);
|
2013-10-23 22:29:08 +00:00
|
|
|
}
|
|
|
|
|
2013-11-13 13:18:07 +00:00
|
|
|
void Account::addApprovedCerts(const QList<QSslCertificate> certs)
|
2013-11-07 11:04:45 +00:00
|
|
|
{
|
2013-11-13 13:18:07 +00:00
|
|
|
_approvedCerts += certs;
|
2013-11-07 11:04:45 +00:00
|
|
|
}
|
|
|
|
|
2016-05-27 10:08:42 +00:00
|
|
|
void Account::resetRejectedCertificates()
|
2015-05-08 08:17:21 +00:00
|
|
|
{
|
2016-05-27 10:08:42 +00:00
|
|
|
_rejectedCertificates.clear();
|
2015-05-08 08:17:21 +00:00
|
|
|
}
|
|
|
|
|
2013-10-23 22:29:08 +00:00
|
|
|
void Account::setSslErrorHandler(AbstractSslErrorHandler *handler)
|
|
|
|
{
|
2013-10-24 10:55:26 +00:00
|
|
|
_sslErrorHandler.reset(handler);
|
2013-10-21 19:42:52 +00:00
|
|
|
}
|
|
|
|
|
2013-10-23 22:29:08 +00:00
|
|
|
void Account::setUrl(const QUrl &url)
|
2013-10-21 19:42:52 +00:00
|
|
|
{
|
2013-10-23 22:29:08 +00:00
|
|
|
_url = url;
|
2017-11-10 08:57:42 +00:00
|
|
|
_userVisibleUrl = url;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Account::setUserVisibleHost(const QString &host)
|
|
|
|
{
|
|
|
|
_userVisibleUrl.setHost(host);
|
2013-10-21 19:42:52 +00:00
|
|
|
}
|
|
|
|
|
2013-11-04 15:36:23 +00:00
|
|
|
QVariant Account::credentialSetting(const QString &key) const
|
|
|
|
{
|
|
|
|
if (_credentials) {
|
|
|
|
QString prefix = _credentials->authType();
|
2019-02-19 10:38:46 +00:00
|
|
|
QVariant value = _settingsMap.value(prefix + "_" + key);
|
|
|
|
if (value.isNull()) {
|
|
|
|
value = _settingsMap.value(key);
|
2013-11-04 15:36:23 +00:00
|
|
|
}
|
|
|
|
return value;
|
|
|
|
}
|
|
|
|
return QVariant();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Account::setCredentialSetting(const QString &key, const QVariant &value)
|
|
|
|
{
|
|
|
|
if (_credentials) {
|
|
|
|
QString prefix = _credentials->authType();
|
|
|
|
_settingsMap.insert(prefix + "_" + key, value);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-07-16 12:21:51 +00:00
|
|
|
void Account::slotHandleSslErrors(QNetworkReply *reply, QList<QSslError> errors)
|
2013-10-23 22:29:08 +00:00
|
|
|
{
|
2014-02-05 13:16:43 +00:00
|
|
|
NetworkJobTimeoutPauser pauser(reply);
|
2014-11-18 15:44:14 +00:00
|
|
|
QString out;
|
|
|
|
QDebug(&out) << "SSL-Errors happened for url " << reply->url().toString();
|
2014-02-07 23:29:12 +00:00
|
|
|
foreach (const QSslError &error, errors) {
|
2014-11-18 15:44:14 +00:00
|
|
|
QDebug(&out) << "\tError in " << error.certificate() << ":"
|
|
|
|
<< error.errorString() << "(" << error.error() << ")"
|
|
|
|
<< "\n";
|
2014-02-07 23:29:12 +00:00
|
|
|
}
|
2013-10-23 22:29:08 +00:00
|
|
|
|
2016-05-27 10:08:42 +00:00
|
|
|
bool allPreviouslyRejected = true;
|
|
|
|
foreach (const QSslError &error, errors) {
|
|
|
|
if (!_rejectedCertificates.contains(error.certificate())) {
|
|
|
|
allPreviouslyRejected = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// If all certs have previously been rejected by the user, don't ask again.
|
|
|
|
if (allPreviouslyRejected) {
|
2017-03-30 11:46:20 +00:00
|
|
|
qCInfo(lcAccount) << out << "Certs not trusted by user decision, returning.";
|
2013-10-23 22:29:08 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-11-13 13:18:07 +00:00
|
|
|
QList<QSslCertificate> approvedCerts;
|
2013-11-07 09:14:12 +00:00
|
|
|
if (_sslErrorHandler.isNull()) {
|
2017-03-30 11:46:20 +00:00
|
|
|
qCWarning(lcAccount) << out << "called without valid SSL error handler for account" << url();
|
2014-11-18 15:44:14 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-06-15 16:05:56 +00:00
|
|
|
// SslDialogErrorHandler::handleErrors will run an event loop that might execute
|
|
|
|
// the deleteLater() of the QNAM before we have the chance of unwinding our stack.
|
|
|
|
// Keep a ref here on our stackframe to make sure that it doesn't get deleted before
|
|
|
|
// handleErrors returns.
|
|
|
|
QSharedPointer<QNetworkAccessManager> qnamLock = _am;
|
2016-11-29 10:14:11 +00:00
|
|
|
QPointer<QObject> guard = reply;
|
2016-06-15 16:05:56 +00:00
|
|
|
|
2015-04-06 19:46:03 +00:00
|
|
|
if (_sslErrorHandler->handleErrors(errors, reply->sslConfiguration(), &approvedCerts, sharedFromThis())) {
|
2016-11-29 10:14:11 +00:00
|
|
|
if (!guard)
|
|
|
|
return;
|
|
|
|
|
2020-02-10 12:08:57 +00:00
|
|
|
if (!approvedCerts.isEmpty()) {
|
|
|
|
QSslSocket::addDefaultCaCertificates(approvedCerts);
|
|
|
|
addApprovedCerts(approvedCerts);
|
|
|
|
emit wantsAccountSaved(this);
|
|
|
|
|
|
|
|
// all ssl certs are known and accepted. We can ignore the problems right away.
|
|
|
|
qCInfo(lcAccount) << out << "Certs are known and trusted! This is not an actual error.";
|
|
|
|
}
|
2015-06-03 08:46:33 +00:00
|
|
|
|
|
|
|
// Warning: Do *not* use ignoreSslErrors() (without args) here:
|
|
|
|
// it permanently ignores all SSL errors for this host, even
|
|
|
|
// certificate changes.
|
|
|
|
reply->ignoreSslErrors(errors);
|
2013-10-23 22:29:08 +00:00
|
|
|
} else {
|
2016-11-29 10:14:11 +00:00
|
|
|
if (!guard)
|
|
|
|
return;
|
|
|
|
|
2016-05-27 10:08:42 +00:00
|
|
|
// Mark all involved certificates as rejected, so we don't ask the user again.
|
|
|
|
foreach (const QSslError &error, errors) {
|
|
|
|
if (!_rejectedCertificates.contains(error.certificate())) {
|
|
|
|
_rejectedCertificates.append(error.certificate());
|
|
|
|
}
|
|
|
|
}
|
2017-04-27 08:13:55 +00:00
|
|
|
|
|
|
|
// Not calling ignoreSslErrors will make the SSL handshake fail.
|
2014-11-18 15:44:14 +00:00
|
|
|
return;
|
2013-10-23 22:29:08 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-12-17 13:09:57 +00:00
|
|
|
void Account::slotCredentialsFetched()
|
|
|
|
{
|
2016-06-15 15:57:28 +00:00
|
|
|
emit credentialsFetched(_credentials.data());
|
2014-12-17 13:09:57 +00:00
|
|
|
}
|
|
|
|
|
2015-09-05 13:39:22 +00:00
|
|
|
void Account::slotCredentialsAsked()
|
|
|
|
{
|
2016-06-15 15:57:28 +00:00
|
|
|
emit credentialsAsked(_credentials.data());
|
2015-09-05 13:39:22 +00:00
|
|
|
}
|
|
|
|
|
2014-12-17 13:09:57 +00:00
|
|
|
void Account::handleInvalidCredentials()
|
|
|
|
{
|
2019-07-24 11:56:21 +00:00
|
|
|
// Retrieving password will trigger remote wipe check job
|
|
|
|
retrieveAppPassword();
|
|
|
|
|
2014-12-17 13:09:57 +00:00
|
|
|
emit invalidCredentials();
|
|
|
|
}
|
|
|
|
|
2017-07-06 11:43:34 +00:00
|
|
|
void Account::clearQNAMCache()
|
|
|
|
{
|
|
|
|
_am->clearAccessCache();
|
|
|
|
}
|
|
|
|
|
2015-07-29 10:05:00 +00:00
|
|
|
const Capabilities &Account::capabilities() const
|
2015-02-05 14:18:38 +00:00
|
|
|
{
|
2015-07-29 10:05:00 +00:00
|
|
|
return _capabilities;
|
2015-02-05 14:18:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Account::setCapabilities(const QVariantMap &caps)
|
|
|
|
{
|
2015-07-16 19:49:12 +00:00
|
|
|
_capabilities = Capabilities(caps);
|
2021-01-12 08:23:41 +00:00
|
|
|
|
|
|
|
trySetupPushNotifications();
|
2015-02-05 14:18:38 +00:00
|
|
|
}
|
|
|
|
|
2016-03-02 10:59:36 +00:00
|
|
|
QString Account::serverVersion() const
|
2015-02-12 11:59:00 +00:00
|
|
|
{
|
|
|
|
return _serverVersion;
|
|
|
|
}
|
|
|
|
|
2016-03-02 10:59:36 +00:00
|
|
|
int Account::serverVersionInt() const
|
2015-10-16 09:52:27 +00:00
|
|
|
{
|
|
|
|
// FIXME: Use Qt 5.5 QVersionNumber
|
|
|
|
auto components = serverVersion().split('.');
|
2017-03-14 14:57:57 +00:00
|
|
|
return makeServerVersion(components.value(0).toInt(),
|
|
|
|
components.value(1).toInt(),
|
|
|
|
components.value(2).toInt());
|
|
|
|
}
|
|
|
|
|
|
|
|
int Account::makeServerVersion(int majorVersion, int minorVersion, int patchVersion)
|
|
|
|
{
|
|
|
|
return (majorVersion << 16) + (minorVersion << 8) + patchVersion;
|
2016-03-02 10:59:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
bool Account::serverVersionUnsupported() const
|
|
|
|
{
|
2016-03-09 14:57:45 +00:00
|
|
|
if (serverVersionInt() == 0) {
|
|
|
|
// not detected yet, assume it is fine.
|
|
|
|
return false;
|
|
|
|
}
|
2021-03-16 11:09:49 +00:00
|
|
|
return serverVersionInt() < makeServerVersion(NEXTCLOUD_SERVER_VERSION_MIN_SUPPORTED_MAJOR,
|
|
|
|
NEXTCLOUD_SERVER_VERSION_MIN_SUPPORTED_MINOR, NEXTCLOUD_SERVER_VERSION_MIN_SUPPORTED_PATCH);
|
2015-10-16 09:52:27 +00:00
|
|
|
}
|
|
|
|
|
2015-02-12 11:59:00 +00:00
|
|
|
void Account::setServerVersion(const QString &version)
|
|
|
|
{
|
2016-03-02 10:59:36 +00:00
|
|
|
if (version == _serverVersion) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto oldServerVersion = _serverVersion;
|
2015-02-12 11:59:00 +00:00
|
|
|
_serverVersion = version;
|
2016-03-02 10:59:36 +00:00
|
|
|
emit serverVersionChanged(this, oldServerVersion, version);
|
2015-02-12 11:59:00 +00:00
|
|
|
}
|
2015-02-05 14:18:38 +00:00
|
|
|
|
2015-11-02 21:57:17 +00:00
|
|
|
void Account::setNonShib(bool nonShib)
|
|
|
|
{
|
|
|
|
if (nonShib) {
|
|
|
|
_davPath = Theme::instance()->webDavPathNonShib();
|
|
|
|
} else {
|
|
|
|
_davPath = Theme::instance()->webDavPath();
|
2017-04-21 16:13:32 +00:00
|
|
|
}
|
2015-11-02 21:57:17 +00:00
|
|
|
}
|
|
|
|
|
2019-11-29 03:28:50 +00:00
|
|
|
void Account::writeAppPasswordOnce(QString appPassword){
|
|
|
|
if(_wroteAppPassword)
|
|
|
|
return;
|
|
|
|
|
2019-12-07 23:02:11 +00:00
|
|
|
// Fix: Password got written from Account Wizard, before finish.
|
|
|
|
// Only write the app password for a connected account, else
|
|
|
|
// there'll be a zombie keychain slot forever, never used again ;p
|
|
|
|
//
|
|
|
|
// Also don't write empty passwords (Log out -> Relaunch)
|
|
|
|
if(id().isEmpty() || appPassword.isEmpty())
|
|
|
|
return;
|
|
|
|
|
2019-07-24 11:56:21 +00:00
|
|
|
const QString kck = AbstractCredentials::keychainKey(
|
|
|
|
url().toString(),
|
|
|
|
davUser() + app_password,
|
|
|
|
id()
|
|
|
|
);
|
|
|
|
|
2020-05-18 18:54:23 +00:00
|
|
|
auto *job = new WritePasswordJob(Theme::instance()->appName());
|
2019-07-24 11:56:21 +00:00
|
|
|
job->setInsecureFallback(false);
|
|
|
|
job->setKey(kck);
|
|
|
|
job->setBinaryData(appPassword.toLatin1());
|
2019-12-07 23:02:11 +00:00
|
|
|
connect(job, &WritePasswordJob::finished, [this](Job *incoming) {
|
2020-05-18 18:54:23 +00:00
|
|
|
auto *writeJob = static_cast<WritePasswordJob *>(incoming);
|
2019-12-07 23:02:11 +00:00
|
|
|
if (writeJob->error() == NoError)
|
|
|
|
qCInfo(lcAccount) << "appPassword stored in keychain";
|
|
|
|
else
|
|
|
|
qCWarning(lcAccount) << "Unable to store appPassword in keychain" << writeJob->errorString();
|
|
|
|
|
|
|
|
// We don't try this again on error, to not raise CPU consumption
|
2019-11-29 03:28:50 +00:00
|
|
|
_wroteAppPassword = true;
|
2019-07-24 11:56:21 +00:00
|
|
|
});
|
|
|
|
job->start();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Account::retrieveAppPassword(){
|
|
|
|
const QString kck = AbstractCredentials::keychainKey(
|
|
|
|
url().toString(),
|
|
|
|
credentials()->user() + app_password,
|
|
|
|
id()
|
|
|
|
);
|
|
|
|
|
2020-05-18 18:54:23 +00:00
|
|
|
auto *job = new ReadPasswordJob(Theme::instance()->appName());
|
2019-07-24 11:56:21 +00:00
|
|
|
job->setInsecureFallback(false);
|
|
|
|
job->setKey(kck);
|
2019-11-29 03:47:40 +00:00
|
|
|
connect(job, &ReadPasswordJob::finished, [this](Job *incoming) {
|
2020-05-18 18:54:23 +00:00
|
|
|
auto *readJob = static_cast<ReadPasswordJob *>(incoming);
|
2019-07-24 11:56:21 +00:00
|
|
|
QString pwd("");
|
|
|
|
// Error or no valid public key error out
|
|
|
|
if (readJob->error() == NoError &&
|
|
|
|
readJob->binaryData().length() > 0) {
|
|
|
|
pwd = readJob->binaryData();
|
|
|
|
}
|
|
|
|
|
|
|
|
emit appPasswordRetrieved(pwd);
|
|
|
|
});
|
|
|
|
job->start();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Account::deleteAppPassword(){
|
|
|
|
const QString kck = AbstractCredentials::keychainKey(
|
|
|
|
url().toString(),
|
|
|
|
credentials()->user() + app_password,
|
|
|
|
id()
|
|
|
|
);
|
|
|
|
|
|
|
|
if (kck.isEmpty()) {
|
|
|
|
qCDebug(lcAccount) << "appPassword is empty";
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-05-18 18:54:23 +00:00
|
|
|
auto *job = new DeletePasswordJob(Theme::instance()->appName());
|
2019-07-24 11:56:21 +00:00
|
|
|
job->setInsecureFallback(false);
|
|
|
|
job->setKey(kck);
|
2019-12-07 23:02:11 +00:00
|
|
|
connect(job, &DeletePasswordJob::finished, [this](Job *incoming) {
|
2020-05-18 18:54:23 +00:00
|
|
|
auto *deleteJob = static_cast<DeletePasswordJob *>(incoming);
|
2019-12-07 23:02:11 +00:00
|
|
|
if (deleteJob->error() == NoError)
|
|
|
|
qCInfo(lcAccount) << "appPassword deleted from keychain";
|
|
|
|
else
|
|
|
|
qCWarning(lcAccount) << "Unable to delete appPassword from keychain" << deleteJob->errorString();
|
|
|
|
|
|
|
|
// Allow storing a new app password on re-login
|
|
|
|
_wroteAppPassword = false;
|
|
|
|
});
|
2019-07-24 11:56:21 +00:00
|
|
|
job->start();
|
|
|
|
}
|
|
|
|
|
2020-01-18 14:07:51 +00:00
|
|
|
void Account::fetchDirectEditors(const QUrl &directEditingURL, const QString &directEditingETag)
|
|
|
|
{
|
|
|
|
if(directEditingURL.isEmpty() || directEditingETag.isEmpty())
|
|
|
|
return;
|
|
|
|
|
|
|
|
// Check for the directEditing capability
|
|
|
|
if (!directEditingURL.isEmpty() &&
|
|
|
|
(directEditingETag.isEmpty() || directEditingETag != _lastDirectEditingETag)) {
|
|
|
|
// Fetch the available editors and their mime types
|
2018-11-22 07:46:33 +00:00
|
|
|
auto *job = new JsonApiJob(sharedFromThis(), QLatin1String("ocs/v2.php/apps/files/api/v1/directEditing"));
|
2020-01-18 14:07:51 +00:00
|
|
|
QObject::connect(job, &JsonApiJob::jsonReceived, this, &Account::slotDirectEditingRecieved);
|
|
|
|
job->start();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Account::slotDirectEditingRecieved(const QJsonDocument &json)
|
|
|
|
{
|
|
|
|
auto data = json.object().value("ocs").toObject().value("data").toObject();
|
|
|
|
auto editors = data.value("editors").toObject();
|
|
|
|
|
|
|
|
foreach (auto editorKey, editors.keys()) {
|
|
|
|
auto editor = editors.value(editorKey).toObject();
|
|
|
|
|
|
|
|
const QString id = editor.value("id").toString();
|
|
|
|
const QString name = editor.value("name").toString();
|
|
|
|
|
|
|
|
if(!id.isEmpty() && !name.isEmpty()) {
|
|
|
|
auto mimeTypes = editor.value("mimetypes").toArray();
|
|
|
|
auto optionalMimeTypes = editor.value("optionalMimetypes").toArray();
|
|
|
|
|
2020-05-18 18:54:23 +00:00
|
|
|
auto *directEditor = new DirectEditor(id, name);
|
2020-01-18 14:07:51 +00:00
|
|
|
|
|
|
|
foreach(auto mimeType, mimeTypes) {
|
|
|
|
directEditor->addMimetype(mimeType.toString().toLatin1());
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach(auto optionalMimeType, optionalMimeTypes) {
|
|
|
|
directEditor->addOptionalMimetype(optionalMimeType.toString().toLatin1());
|
|
|
|
}
|
|
|
|
|
|
|
|
_capabilities.addDirectEditor(directEditor);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-12 08:23:41 +00:00
|
|
|
PushNotifications *Account::pushNotifications() const
|
|
|
|
{
|
|
|
|
return _pushNotifications;
|
|
|
|
}
|
|
|
|
|
2014-11-09 21:34:07 +00:00
|
|
|
} // namespace OCC
|