account.cpp 26 KB

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