account.cpp 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946
  1. /*
  2. * Copyright (C) by Daniel Molkentin <danimo@owncloud.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. #include "account.h"
  15. #include "accountfwd.h"
  16. #include "clientsideencryptionjobs.h"
  17. #include "cookiejar.h"
  18. #include "networkjobs.h"
  19. #include "configfile.h"
  20. #include "accessmanager.h"
  21. #include "creds/abstractcredentials.h"
  22. #include "capabilities.h"
  23. #include "theme.h"
  24. #include "pushnotifications.h"
  25. #include "version.h"
  26. #include "deletejob.h"
  27. #include "lockfilejobs.h"
  28. #include "common/syncjournaldb.h"
  29. #include "common/asserts.h"
  30. #include "clientsideencryption.h"
  31. #include "ocsuserstatusconnector.h"
  32. #include <QLoggingCategory>
  33. #include <QNetworkReply>
  34. #include <QNetworkAccessManager>
  35. #include <QSslSocket>
  36. #include <QNetworkCookieJar>
  37. #include <QNetworkProxy>
  38. #include <QFileInfo>
  39. #include <QDir>
  40. #include <QSslKey>
  41. #include <QAuthenticator>
  42. #include <QStandardPaths>
  43. #include <QJsonDocument>
  44. #include <QJsonObject>
  45. #include <QJsonArray>
  46. #include <QLoggingCategory>
  47. #include <QHttpMultiPart>
  48. #include <qsslconfiguration.h>
  49. #include <qt5keychain/keychain.h>
  50. #include "creds/abstractcredentials.h"
  51. using namespace QKeychain;
  52. namespace {
  53. constexpr int pushNotificationsReconnectInterval = 1000 * 60 * 2;
  54. constexpr int usernamePrefillServerVersionMinSupportedMajor = 24;
  55. constexpr int checksumRecalculateRequestServerVersionMinSupportedMajor = 24;
  56. }
  57. namespace OCC {
  58. Q_LOGGING_CATEGORY(lcAccount, "nextcloud.sync.account", QtInfoMsg)
  59. const char app_password[] = "_app-password";
  60. Account::Account(QObject *parent)
  61. : QObject(parent)
  62. , _capabilities(QVariantMap())
  63. {
  64. qRegisterMetaType<AccountPtr>("AccountPtr");
  65. qRegisterMetaType<Account *>("Account*");
  66. _pushNotificationsReconnectTimer.setInterval(pushNotificationsReconnectInterval);
  67. connect(&_pushNotificationsReconnectTimer, &QTimer::timeout, this, &Account::trySetupPushNotifications);
  68. }
  69. AccountPtr Account::create()
  70. {
  71. AccountPtr acc = AccountPtr(new Account);
  72. acc->setSharedThis(acc);
  73. return acc;
  74. }
  75. ClientSideEncryption* Account::e2e()
  76. {
  77. // Qt expects everything in the connect to be a pointer, so return a pointer.
  78. return &_e2e;
  79. }
  80. Account::~Account() = default;
  81. QString Account::davPath() const
  82. {
  83. return davPathBase() + QLatin1Char('/') + davUser() + QLatin1Char('/');
  84. }
  85. void Account::setSharedThis(AccountPtr sharedThis)
  86. {
  87. _sharedThis = sharedThis.toWeakRef();
  88. setupUserStatusConnector();
  89. }
  90. QString Account::davPathBase()
  91. {
  92. return QStringLiteral("/remote.php/dav/files");
  93. }
  94. AccountPtr Account::sharedFromThis()
  95. {
  96. return _sharedThis.toStrongRef();
  97. }
  98. AccountPtr Account::sharedFromThis() const
  99. {
  100. return _sharedThis.toStrongRef();
  101. }
  102. QString Account::davUser() const
  103. {
  104. return _davUser.isEmpty() && _credentials ? _credentials->user() : _davUser;
  105. }
  106. void Account::setDavUser(const QString &newDavUser)
  107. {
  108. if (_davUser == newDavUser)
  109. return;
  110. _davUser = newDavUser;
  111. emit wantsAccountSaved(this);
  112. emit prettyNameChanged();
  113. }
  114. #ifndef TOKEN_AUTH_ONLY
  115. QImage Account::avatar() const
  116. {
  117. return _avatarImg;
  118. }
  119. void Account::setAvatar(const QImage &img)
  120. {
  121. _avatarImg = img;
  122. emit accountChangedAvatar();
  123. }
  124. #endif
  125. QString Account::displayName() const
  126. {
  127. QString dn = QString("%1@%2").arg(credentials()->user(), _url.host());
  128. int port = url().port();
  129. if (port > 0 && port != 80 && port != 443) {
  130. dn.append(QLatin1Char(':'));
  131. dn.append(QString::number(port));
  132. }
  133. return dn;
  134. }
  135. QString Account::davDisplayName() const
  136. {
  137. return _displayName;
  138. }
  139. void Account::setDavDisplayName(const QString &newDisplayName)
  140. {
  141. _displayName = newDisplayName;
  142. emit accountChangedDisplayName();
  143. emit prettyNameChanged();
  144. }
  145. QString Account::prettyName() const
  146. {
  147. // If davDisplayName is empty (can be several reasons, simplest is missing login at startup), fall back to username
  148. auto name = davDisplayName();
  149. if (name.isEmpty()) {
  150. name = davUser();
  151. }
  152. return name;
  153. }
  154. QColor Account::headerColor() const
  155. {
  156. const auto serverColor = capabilities().serverColor();
  157. return serverColor.isValid() ? serverColor : Theme::defaultColor();
  158. }
  159. QColor Account::headerTextColor() const
  160. {
  161. const auto headerTextColor = capabilities().serverTextColor();
  162. return headerTextColor.isValid() ? headerTextColor : QColor(255,255,255);
  163. }
  164. QColor Account::accentColor() const
  165. {
  166. // This will need adjusting when dark theme is a thing
  167. auto serverColor = capabilities().serverColor();
  168. if(!serverColor.isValid()) {
  169. serverColor = Theme::defaultColor();
  170. }
  171. const auto effectMultiplier = 8;
  172. auto darknessAdjustment = static_cast<int>((1 - Theme::getColorDarkness(serverColor)) * effectMultiplier);
  173. darknessAdjustment *= darknessAdjustment; // Square the value to pronounce the darkness more in lighter colours
  174. const auto baseAdjustment = 125;
  175. const auto adjusted = Theme::isDarkColor(serverColor) ? serverColor : serverColor.darker(baseAdjustment + darknessAdjustment);
  176. return adjusted;
  177. }
  178. QString Account::id() const
  179. {
  180. return _id;
  181. }
  182. AbstractCredentials *Account::credentials() const
  183. {
  184. return _credentials.data();
  185. }
  186. void Account::setCredentials(AbstractCredentials *cred)
  187. {
  188. // set active credential manager
  189. QNetworkCookieJar *jar = nullptr;
  190. QNetworkProxy proxy;
  191. if (_am) {
  192. jar = _am->cookieJar();
  193. jar->setParent(nullptr);
  194. // Remember proxy (issue #2108)
  195. proxy = _am->proxy();
  196. _am = QSharedPointer<QNetworkAccessManager>();
  197. }
  198. // The order for these two is important! Reading the credential's
  199. // settings accesses the account as well as account->_credentials,
  200. _credentials.reset(cred);
  201. cred->setAccount(this);
  202. // Note: This way the QNAM can outlive the Account and Credentials.
  203. // This is necessary to avoid issues with the QNAM being deleted while
  204. // processing slotHandleSslErrors().
  205. _am = QSharedPointer<QNetworkAccessManager>(_credentials->createQNAM(), &QObject::deleteLater);
  206. if (jar) {
  207. _am->setCookieJar(jar);
  208. }
  209. if (proxy.type() != QNetworkProxy::DefaultProxy) {
  210. _am->setProxy(proxy);
  211. }
  212. connect(_am.data(), SIGNAL(sslErrors(QNetworkReply *, QList<QSslError>)),
  213. SLOT(slotHandleSslErrors(QNetworkReply *, QList<QSslError>)));
  214. connect(_am.data(), &QNetworkAccessManager::proxyAuthenticationRequired,
  215. this, &Account::proxyAuthenticationRequired);
  216. connect(_credentials.data(), &AbstractCredentials::fetched,
  217. this, &Account::slotCredentialsFetched);
  218. connect(_credentials.data(), &AbstractCredentials::asked,
  219. this, &Account::slotCredentialsAsked);
  220. trySetupPushNotifications();
  221. }
  222. void Account::setPushNotificationsReconnectInterval(int interval)
  223. {
  224. _pushNotificationsReconnectTimer.setInterval(interval);
  225. }
  226. void Account::trySetupPushNotifications()
  227. {
  228. // Stop the timer to prevent parallel setup attempts
  229. _pushNotificationsReconnectTimer.stop();
  230. if (_capabilities.availablePushNotifications() != PushNotificationType::None) {
  231. qCInfo(lcAccount) << "Try to setup push notifications";
  232. if (!_pushNotifications) {
  233. _pushNotifications = new PushNotifications(this, this);
  234. connect(_pushNotifications, &PushNotifications::ready, this, [this]() {
  235. _pushNotificationsReconnectTimer.stop();
  236. emit pushNotificationsReady(this);
  237. });
  238. const auto disablePushNotifications = [this]() {
  239. qCInfo(lcAccount) << "Disable push notifications object because authentication failed or connection lost";
  240. if (!_pushNotifications) {
  241. return;
  242. }
  243. if (!_pushNotifications->isReady()) {
  244. emit pushNotificationsDisabled(this);
  245. }
  246. if (!_pushNotificationsReconnectTimer.isActive()) {
  247. _pushNotificationsReconnectTimer.start();
  248. }
  249. };
  250. connect(_pushNotifications, &PushNotifications::connectionLost, this, disablePushNotifications);
  251. connect(_pushNotifications, &PushNotifications::authenticationFailed, this, disablePushNotifications);
  252. }
  253. // If push notifications already running it is no problem to call setup again
  254. _pushNotifications->setup();
  255. }
  256. }
  257. QUrl Account::davUrl() const
  258. {
  259. return Utility::concatUrlPath(url(), davPath());
  260. }
  261. QUrl Account::deprecatedPrivateLinkUrl(const QByteArray &numericFileId) const
  262. {
  263. return Utility::concatUrlPath(_userVisibleUrl,
  264. QLatin1String("/index.php/f/") + QUrl::toPercentEncoding(QString::fromLatin1(numericFileId)));
  265. }
  266. /**
  267. * clear all cookies. (Session cookies or not)
  268. */
  269. void Account::clearCookieJar()
  270. {
  271. auto jar = qobject_cast<CookieJar *>(_am->cookieJar());
  272. ASSERT(jar);
  273. jar->setAllCookies(QList<QNetworkCookie>());
  274. emit wantsAccountSaved(this);
  275. }
  276. /*! This shares our official cookie jar (containing all the tasty
  277. authentication cookies) with another QNAM while making sure
  278. of not losing its ownership. */
  279. void Account::lendCookieJarTo(QNetworkAccessManager *guest)
  280. {
  281. auto jar = _am->cookieJar();
  282. auto oldParent = jar->parent();
  283. guest->setCookieJar(jar); // takes ownership of our precious cookie jar
  284. jar->setParent(oldParent); // takes it back
  285. }
  286. QString Account::cookieJarPath()
  287. {
  288. return QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation) + "/cookies" + id() + ".db";
  289. }
  290. void Account::resetNetworkAccessManager()
  291. {
  292. if (!_credentials || !_am) {
  293. return;
  294. }
  295. qCDebug(lcAccount) << "Resetting QNAM";
  296. QNetworkCookieJar *jar = _am->cookieJar();
  297. QNetworkProxy proxy = _am->proxy();
  298. // Use a QSharedPointer to allow locking the life of the QNAM on the stack.
  299. // Make it call deleteLater to make sure that we can return to any QNAM stack frames safely.
  300. _am = QSharedPointer<QNetworkAccessManager>(_credentials->createQNAM(), &QObject::deleteLater);
  301. _am->setCookieJar(jar); // takes ownership of the old cookie jar
  302. _am->setProxy(proxy); // Remember proxy (issue #2108)
  303. connect(_am.data(), SIGNAL(sslErrors(QNetworkReply *, QList<QSslError>)),
  304. SLOT(slotHandleSslErrors(QNetworkReply *, QList<QSslError>)));
  305. connect(_am.data(), &QNetworkAccessManager::proxyAuthenticationRequired,
  306. this, &Account::proxyAuthenticationRequired);
  307. }
  308. QNetworkAccessManager *Account::networkAccessManager()
  309. {
  310. return _am.data();
  311. }
  312. QSharedPointer<QNetworkAccessManager> Account::sharedNetworkAccessManager()
  313. {
  314. return _am;
  315. }
  316. QNetworkReply *Account::sendRawRequest(const QByteArray &verb, const QUrl &url, QNetworkRequest req, QIODevice *data)
  317. {
  318. req.setUrl(url);
  319. req.setSslConfiguration(this->getOrCreateSslConfig());
  320. if (verb == "HEAD" && !data) {
  321. return _am->head(req);
  322. } else if (verb == "GET" && !data) {
  323. return _am->get(req);
  324. } else if (verb == "POST") {
  325. return _am->post(req, data);
  326. } else if (verb == "PUT") {
  327. return _am->put(req, data);
  328. } else if (verb == "DELETE" && !data) {
  329. return _am->deleteResource(req);
  330. }
  331. return _am->sendCustomRequest(req, verb, data);
  332. }
  333. QNetworkReply *Account::sendRawRequest(const QByteArray &verb, const QUrl &url, QNetworkRequest req, const QByteArray &data)
  334. {
  335. req.setUrl(url);
  336. req.setSslConfiguration(this->getOrCreateSslConfig());
  337. if (verb == "HEAD" && data.isEmpty()) {
  338. return _am->head(req);
  339. } else if (verb == "GET" && data.isEmpty()) {
  340. return _am->get(req);
  341. } else if (verb == "POST") {
  342. return _am->post(req, data);
  343. } else if (verb == "PUT") {
  344. return _am->put(req, data);
  345. } else if (verb == "DELETE" && data.isEmpty()) {
  346. return _am->deleteResource(req);
  347. }
  348. return _am->sendCustomRequest(req, verb, data);
  349. }
  350. QNetworkReply *Account::sendRawRequest(const QByteArray &verb, const QUrl &url, QNetworkRequest req, QHttpMultiPart *data)
  351. {
  352. req.setUrl(url);
  353. req.setSslConfiguration(this->getOrCreateSslConfig());
  354. if (verb == "PUT") {
  355. return _am->put(req, data);
  356. } else if (verb == "POST") {
  357. return _am->post(req, data);
  358. }
  359. return _am->sendCustomRequest(req, verb, data);
  360. }
  361. SimpleNetworkJob *Account::sendRequest(const QByteArray &verb, const QUrl &url, QNetworkRequest req, QIODevice *data)
  362. {
  363. auto job = new SimpleNetworkJob(sharedFromThis());
  364. job->startRequest(verb, url, req, data);
  365. return job;
  366. }
  367. void Account::setSslConfiguration(const QSslConfiguration &config)
  368. {
  369. _sslConfiguration = config;
  370. }
  371. QSslConfiguration Account::getOrCreateSslConfig()
  372. {
  373. if (!_sslConfiguration.isNull()) {
  374. // Will be set by CheckServerJob::finished()
  375. // We need to use a central shared config to get SSL session tickets
  376. return _sslConfiguration;
  377. }
  378. // if setting the client certificate fails, you will probably get an error similar to this:
  379. // "An internal error number 1060 happened. SSL handshake failed, client certificate was requested: SSL error: sslv3 alert handshake failure"
  380. QSslConfiguration sslConfig = QSslConfiguration::defaultConfiguration();
  381. // Try hard to re-use session for different requests
  382. sslConfig.setSslOption(QSsl::SslOptionDisableSessionTickets, false);
  383. sslConfig.setSslOption(QSsl::SslOptionDisableSessionSharing, false);
  384. sslConfig.setSslOption(QSsl::SslOptionDisableSessionPersistence, false);
  385. sslConfig.setOcspStaplingEnabled(Theme::instance()->enableStaplingOCSP());
  386. return sslConfig;
  387. }
  388. void Account::setApprovedCerts(const QList<QSslCertificate> certs)
  389. {
  390. _approvedCerts = certs;
  391. QSslConfiguration::defaultConfiguration().addCaCertificates(certs);
  392. }
  393. void Account::addApprovedCerts(const QList<QSslCertificate> certs)
  394. {
  395. _approvedCerts += certs;
  396. }
  397. void Account::resetRejectedCertificates()
  398. {
  399. _rejectedCertificates.clear();
  400. }
  401. void Account::setSslErrorHandler(AbstractSslErrorHandler *handler)
  402. {
  403. _sslErrorHandler.reset(handler);
  404. }
  405. void Account::setUrl(const QUrl &url)
  406. {
  407. _url = url;
  408. _userVisibleUrl = url;
  409. }
  410. void Account::setUserVisibleHost(const QString &host)
  411. {
  412. _userVisibleUrl.setHost(host);
  413. }
  414. QVariant Account::credentialSetting(const QString &key) const
  415. {
  416. if (_credentials) {
  417. QString prefix = _credentials->authType();
  418. QVariant value = _settingsMap.value(prefix + "_" + key);
  419. if (value.isNull()) {
  420. value = _settingsMap.value(key);
  421. }
  422. return value;
  423. }
  424. return QVariant();
  425. }
  426. void Account::setCredentialSetting(const QString &key, const QVariant &value)
  427. {
  428. if (_credentials) {
  429. QString prefix = _credentials->authType();
  430. _settingsMap.insert(prefix + "_" + key, value);
  431. }
  432. }
  433. void Account::slotHandleSslErrors(QNetworkReply *reply, QList<QSslError> errors)
  434. {
  435. NetworkJobTimeoutPauser pauser(reply);
  436. QString out;
  437. QDebug(&out) << "SSL-Errors happened for url " << reply->url().toString();
  438. foreach (const QSslError &error, errors) {
  439. QDebug(&out) << "\tError in " << error.certificate() << ":"
  440. << error.errorString() << "(" << error.error() << ")"
  441. << "\n";
  442. }
  443. qCInfo(lcAccount()) << "ssl errors" << out;
  444. qCInfo(lcAccount()) << reply->sslConfiguration().peerCertificateChain();
  445. bool allPreviouslyRejected = true;
  446. foreach (const QSslError &error, errors) {
  447. if (!_rejectedCertificates.contains(error.certificate())) {
  448. allPreviouslyRejected = false;
  449. }
  450. }
  451. // If all certs have previously been rejected by the user, don't ask again.
  452. if (allPreviouslyRejected) {
  453. qCInfo(lcAccount) << out << "Certs not trusted by user decision, returning.";
  454. return;
  455. }
  456. QList<QSslCertificate> approvedCerts;
  457. if (_sslErrorHandler.isNull()) {
  458. qCWarning(lcAccount) << out << "called without valid SSL error handler for account" << url();
  459. return;
  460. }
  461. // SslDialogErrorHandler::handleErrors will run an event loop that might execute
  462. // the deleteLater() of the QNAM before we have the chance of unwinding our stack.
  463. // Keep a ref here on our stackframe to make sure that it doesn't get deleted before
  464. // handleErrors returns.
  465. QSharedPointer<QNetworkAccessManager> qnamLock = _am;
  466. QPointer<QObject> guard = reply;
  467. if (_sslErrorHandler->handleErrors(errors, reply->sslConfiguration(), &approvedCerts, sharedFromThis())) {
  468. if (!guard)
  469. return;
  470. if (!approvedCerts.isEmpty()) {
  471. QSslConfiguration::defaultConfiguration().addCaCertificates(approvedCerts);
  472. addApprovedCerts(approvedCerts);
  473. emit wantsAccountSaved(this);
  474. // all ssl certs are known and accepted. We can ignore the problems right away.
  475. qCInfo(lcAccount) << out << "Certs are known and trusted! This is not an actual error.";
  476. }
  477. // Warning: Do *not* use ignoreSslErrors() (without args) here:
  478. // it permanently ignores all SSL errors for this host, even
  479. // certificate changes.
  480. reply->ignoreSslErrors(errors);
  481. } else {
  482. if (!guard)
  483. return;
  484. // Mark all involved certificates as rejected, so we don't ask the user again.
  485. foreach (const QSslError &error, errors) {
  486. if (!_rejectedCertificates.contains(error.certificate())) {
  487. _rejectedCertificates.append(error.certificate());
  488. }
  489. }
  490. // Not calling ignoreSslErrors will make the SSL handshake fail.
  491. return;
  492. }
  493. }
  494. void Account::slotCredentialsFetched()
  495. {
  496. if (_davUser.isEmpty()) {
  497. qCDebug(lcAccount) << "User id not set. Fetch it.";
  498. const auto fetchUserNameJob = new JsonApiJob(sharedFromThis(), QStringLiteral("/ocs/v1.php/cloud/user"));
  499. connect(fetchUserNameJob, &JsonApiJob::jsonReceived, this, [this, fetchUserNameJob](const QJsonDocument &json, int statusCode) {
  500. fetchUserNameJob->deleteLater();
  501. if (statusCode != 100) {
  502. qCWarning(lcAccount) << "Could not fetch user id. Login will probably not work.";
  503. emit credentialsFetched(_credentials.data());
  504. return;
  505. }
  506. const auto objData = json.object().value("ocs").toObject().value("data").toObject();
  507. const auto userId = objData.value("id").toString("");
  508. setDavUser(userId);
  509. emit credentialsFetched(_credentials.data());
  510. });
  511. fetchUserNameJob->start();
  512. } else {
  513. qCDebug(lcAccount) << "User id already fetched.";
  514. emit credentialsFetched(_credentials.data());
  515. }
  516. }
  517. void Account::slotCredentialsAsked()
  518. {
  519. emit credentialsAsked(_credentials.data());
  520. }
  521. void Account::handleInvalidCredentials()
  522. {
  523. // Retrieving password will trigger remote wipe check job
  524. retrieveAppPassword();
  525. emit invalidCredentials();
  526. }
  527. void Account::clearQNAMCache()
  528. {
  529. _am->clearAccessCache();
  530. }
  531. const Capabilities &Account::capabilities() const
  532. {
  533. return _capabilities;
  534. }
  535. void Account::setCapabilities(const QVariantMap &caps)
  536. {
  537. _capabilities = Capabilities(caps);
  538. emit capabilitiesChanged();
  539. setupUserStatusConnector();
  540. trySetupPushNotifications();
  541. }
  542. void Account::setupUserStatusConnector()
  543. {
  544. _userStatusConnector = std::make_shared<OcsUserStatusConnector>(sharedFromThis());
  545. connect(_userStatusConnector.get(), &UserStatusConnector::userStatusFetched, this, [this](const UserStatus &) {
  546. emit userStatusChanged();
  547. });
  548. connect(_userStatusConnector.get(), &UserStatusConnector::serverUserStatusChanged, this, &Account::serverUserStatusChanged);
  549. connect(_userStatusConnector.get(), &UserStatusConnector::messageCleared, this, [this] {
  550. emit userStatusChanged();
  551. });
  552. _userStatusConnector->fetchUserStatus();
  553. }
  554. QString Account::serverVersion() const
  555. {
  556. return _serverVersion;
  557. }
  558. int Account::serverVersionInt() const
  559. {
  560. // FIXME: Use Qt 5.5 QVersionNumber
  561. auto components = serverVersion().split('.');
  562. return makeServerVersion(components.value(0).toInt(),
  563. components.value(1).toInt(),
  564. components.value(2).toInt());
  565. }
  566. bool Account::serverVersionUnsupported() const
  567. {
  568. if (serverVersionInt() == 0) {
  569. // not detected yet, assume it is fine.
  570. return false;
  571. }
  572. return serverVersionInt() < makeServerVersion(NEXTCLOUD_SERVER_VERSION_MIN_SUPPORTED_MAJOR,
  573. NEXTCLOUD_SERVER_VERSION_MIN_SUPPORTED_MINOR, NEXTCLOUD_SERVER_VERSION_MIN_SUPPORTED_PATCH);
  574. }
  575. bool Account::isUsernamePrefillSupported() const
  576. {
  577. return serverVersionInt() >= makeServerVersion(usernamePrefillServerVersionMinSupportedMajor, 0, 0);
  578. }
  579. bool Account::isChecksumRecalculateRequestSupported() const
  580. {
  581. return serverVersionInt() >= makeServerVersion(checksumRecalculateRequestServerVersionMinSupportedMajor, 0, 0);
  582. }
  583. int Account::checksumRecalculateServerVersionMinSupportedMajor() const
  584. {
  585. return checksumRecalculateRequestServerVersionMinSupportedMajor;
  586. }
  587. void Account::setServerVersion(const QString &version)
  588. {
  589. if (version == _serverVersion) {
  590. return;
  591. }
  592. auto oldServerVersion = _serverVersion;
  593. _serverVersion = version;
  594. emit serverVersionChanged(this, oldServerVersion, version);
  595. }
  596. void Account::writeAppPasswordOnce(QString appPassword){
  597. if(_wroteAppPassword)
  598. return;
  599. // Fix: Password got written from Account Wizard, before finish.
  600. // Only write the app password for a connected account, else
  601. // there'll be a zombie keychain slot forever, never used again ;p
  602. //
  603. // Also don't write empty passwords (Log out -> Relaunch)
  604. if(id().isEmpty() || appPassword.isEmpty())
  605. return;
  606. const QString kck = AbstractCredentials::keychainKey(
  607. url().toString(),
  608. davUser() + app_password,
  609. id()
  610. );
  611. auto *job = new WritePasswordJob(Theme::instance()->appName());
  612. job->setInsecureFallback(false);
  613. job->setKey(kck);
  614. job->setBinaryData(appPassword.toLatin1());
  615. connect(job, &WritePasswordJob::finished, [this](Job *incoming) {
  616. auto *writeJob = dynamic_cast<WritePasswordJob *>(incoming);
  617. if (writeJob->error() == NoError)
  618. qCInfo(lcAccount) << "appPassword stored in keychain";
  619. else
  620. qCWarning(lcAccount) << "Unable to store appPassword in keychain" << writeJob->errorString();
  621. // We don't try this again on error, to not raise CPU consumption
  622. _wroteAppPassword = true;
  623. });
  624. job->start();
  625. }
  626. void Account::retrieveAppPassword(){
  627. const QString kck = AbstractCredentials::keychainKey(
  628. url().toString(),
  629. credentials()->user() + app_password,
  630. id()
  631. );
  632. auto *job = new ReadPasswordJob(Theme::instance()->appName());
  633. job->setInsecureFallback(false);
  634. job->setKey(kck);
  635. connect(job, &ReadPasswordJob::finished, [this](Job *incoming) {
  636. auto *readJob = dynamic_cast<ReadPasswordJob *>(incoming);
  637. QString pwd("");
  638. // Error or no valid public key error out
  639. if (readJob->error() == NoError &&
  640. readJob->binaryData().length() > 0) {
  641. pwd = readJob->binaryData();
  642. }
  643. emit appPasswordRetrieved(pwd);
  644. });
  645. job->start();
  646. }
  647. void Account::deleteAppPassword()
  648. {
  649. const QString kck = AbstractCredentials::keychainKey(
  650. url().toString(),
  651. credentials()->user() + app_password,
  652. id()
  653. );
  654. if (kck.isEmpty()) {
  655. qCDebug(lcAccount) << "appPassword is empty";
  656. return;
  657. }
  658. auto *job = new DeletePasswordJob(Theme::instance()->appName());
  659. job->setInsecureFallback(false);
  660. job->setKey(kck);
  661. connect(job, &DeletePasswordJob::finished, [this](Job *incoming) {
  662. auto *deleteJob = dynamic_cast<DeletePasswordJob *>(incoming);
  663. if (deleteJob->error() == NoError)
  664. qCInfo(lcAccount) << "appPassword deleted from keychain";
  665. else
  666. qCWarning(lcAccount) << "Unable to delete appPassword from keychain" << deleteJob->errorString();
  667. // Allow storing a new app password on re-login
  668. _wroteAppPassword = false;
  669. });
  670. job->start();
  671. }
  672. void Account::deleteAppToken()
  673. {
  674. const auto deleteAppTokenJob = new DeleteJob(sharedFromThis(), QStringLiteral("/ocs/v2.php/core/apppassword"));
  675. connect(deleteAppTokenJob, &DeleteJob::finishedSignal, this, [this]() {
  676. if (const auto deleteJob = qobject_cast<DeleteJob *>(QObject::sender())) {
  677. const auto httpCode = deleteJob->reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
  678. if (httpCode != 200) {
  679. qCWarning(lcAccount) << "AppToken remove failed for user: " << displayName() << " with code: " << httpCode;
  680. } else {
  681. qCInfo(lcAccount) << "AppToken for user: " << displayName() << " has been removed.";
  682. }
  683. } else {
  684. Q_ASSERT(false);
  685. qCWarning(lcAccount) << "The sender is not a DeleteJob instance.";
  686. }
  687. });
  688. deleteAppTokenJob->start();
  689. }
  690. void Account::fetchDirectEditors(const QUrl &directEditingURL, const QString &directEditingETag)
  691. {
  692. if(directEditingURL.isEmpty() || directEditingETag.isEmpty())
  693. return;
  694. // Check for the directEditing capability
  695. if (!directEditingURL.isEmpty() &&
  696. (directEditingETag.isEmpty() || directEditingETag != _lastDirectEditingETag)) {
  697. // Fetch the available editors and their mime types
  698. auto *job = new JsonApiJob(sharedFromThis(), QLatin1String("ocs/v2.php/apps/files/api/v1/directEditing"));
  699. QObject::connect(job, &JsonApiJob::jsonReceived, this, &Account::slotDirectEditingRecieved);
  700. job->start();
  701. }
  702. }
  703. void Account::slotDirectEditingRecieved(const QJsonDocument &json)
  704. {
  705. auto data = json.object().value("ocs").toObject().value("data").toObject();
  706. auto editors = data.value("editors").toObject();
  707. foreach (auto editorKey, editors.keys()) {
  708. auto editor = editors.value(editorKey).toObject();
  709. const QString id = editor.value("id").toString();
  710. const QString name = editor.value("name").toString();
  711. if(!id.isEmpty() && !name.isEmpty()) {
  712. auto mimeTypes = editor.value("mimetypes").toArray();
  713. auto optionalMimeTypes = editor.value("optionalMimetypes").toArray();
  714. auto *directEditor = new DirectEditor(id, name);
  715. foreach(auto mimeType, mimeTypes) {
  716. directEditor->addMimetype(mimeType.toString().toLatin1());
  717. }
  718. foreach(auto optionalMimeType, optionalMimeTypes) {
  719. directEditor->addOptionalMimetype(optionalMimeType.toString().toLatin1());
  720. }
  721. _capabilities.addDirectEditor(directEditor);
  722. }
  723. }
  724. }
  725. PushNotifications *Account::pushNotifications() const
  726. {
  727. return _pushNotifications;
  728. }
  729. std::shared_ptr<UserStatusConnector> Account::userStatusConnector() const
  730. {
  731. return _userStatusConnector;
  732. }
  733. void Account::setLockFileState(const QString &serverRelativePath,
  734. SyncJournalDb * const journal,
  735. const SyncFileItem::LockStatus lockStatus)
  736. {
  737. auto job = std::make_unique<LockFileJob>(sharedFromThis(), journal, serverRelativePath, lockStatus);
  738. connect(job.get(), &LockFileJob::finishedWithoutError, this, [this]() {
  739. Q_EMIT lockFileSuccess();
  740. });
  741. connect(job.get(), &LockFileJob::finishedWithError, this, [lockStatus, serverRelativePath, this](const int httpErrorCode, const QString &errorString, const QString &lockOwnerName) {
  742. auto errorMessage = QString{};
  743. const auto filePath = serverRelativePath.mid(1);
  744. if (httpErrorCode == LockFileJob::LOCKED_HTTP_ERROR_CODE) {
  745. errorMessage = tr("File %1 is already locked by %2.").arg(filePath, lockOwnerName);
  746. } else if (lockStatus == SyncFileItem::LockStatus::LockedItem) {
  747. errorMessage = tr("Lock operation on %1 failed with error %2").arg(filePath, errorString);
  748. } else if (lockStatus == SyncFileItem::LockStatus::UnlockedItem) {
  749. errorMessage = tr("Unlock operation on %1 failed with error %2").arg(filePath, errorString);
  750. }
  751. Q_EMIT lockFileError(errorMessage);
  752. });
  753. job->start();
  754. static_cast<void>(job.release());
  755. }
  756. SyncFileItem::LockStatus Account::fileLockStatus(SyncJournalDb * const journal,
  757. const QString &folderRelativePath) const
  758. {
  759. SyncJournalFileRecord record;
  760. if (journal->getFileRecord(folderRelativePath, &record)) {
  761. return record._lockstate._locked ? SyncFileItem::LockStatus::LockedItem : SyncFileItem::LockStatus::UnlockedItem;
  762. }
  763. return SyncFileItem::LockStatus::UnlockedItem;
  764. }
  765. bool Account::fileCanBeUnlocked(SyncJournalDb * const journal,
  766. const QString &folderRelativePath) const
  767. {
  768. SyncJournalFileRecord record;
  769. if (journal->getFileRecord(folderRelativePath, &record)) {
  770. if (record._lockstate._lockOwnerType != static_cast<int>(SyncFileItem::LockOwnerType::UserLock)) {
  771. return false;
  772. }
  773. if (record._lockstate._lockOwnerId != sharedFromThis()->davUser()) {
  774. return false;
  775. }
  776. return true;
  777. }
  778. return false;
  779. }
  780. void Account::setTrustCertificates(bool trustCertificates)
  781. {
  782. _trustCertificates = trustCertificates;
  783. }
  784. bool Account::trustCertificates() const
  785. {
  786. return _trustCertificates;
  787. }
  788. void Account::setE2eEncryptionKeysGenerationAllowed(bool allowed)
  789. {
  790. _e2eEncryptionKeysGenerationAllowed = allowed;
  791. }
  792. [[nodiscard]] bool Account::e2eEncryptionKeysGenerationAllowed() const
  793. {
  794. return _e2eEncryptionKeysGenerationAllowed;
  795. }
  796. } // namespace OCC