account.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575
  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 "cookiejar.h"
  16. #include "networkjobs.h"
  17. #include "configfile.h"
  18. #include "accessmanager.h"
  19. #include "creds/abstractcredentials.h"
  20. #include "capabilities.h"
  21. #include "theme.h"
  22. #include "common/asserts.h"
  23. #include "clientsideencryption.h"
  24. #include <QLoggingCategory>
  25. #include <QNetworkReply>
  26. #include <QNetworkAccessManager>
  27. #include <QSslSocket>
  28. #include <QNetworkCookieJar>
  29. #include <QFileInfo>
  30. #include <QDir>
  31. #include <QSslKey>
  32. #include <QAuthenticator>
  33. #include <QStandardPaths>
  34. #include <keychain.h>
  35. #include "creds/abstractcredentials.h"
  36. using namespace QKeychain;
  37. namespace OCC {
  38. Q_LOGGING_CATEGORY(lcAccount, "nextcloud.sync.account", QtInfoMsg)
  39. const char app_password[] = "_app-password";
  40. Account::Account(QObject *parent)
  41. : QObject(parent)
  42. , _capabilities(QVariantMap())
  43. , _davPath(Theme::instance()->webDavPath())
  44. {
  45. qRegisterMetaType<AccountPtr>("AccountPtr");
  46. }
  47. AccountPtr Account::create()
  48. {
  49. AccountPtr acc = AccountPtr(new Account);
  50. acc->setSharedThis(acc);
  51. //TODO: This probably needs to have a better
  52. // coupling, but it should work for now.
  53. acc->e2e()->setAccount(acc);
  54. return acc;
  55. }
  56. ClientSideEncryption* Account::e2e()
  57. {
  58. // Qt expects everything in the connect to be a pointer, so return a pointer.
  59. return &_e2e;
  60. }
  61. Account::~Account()
  62. {
  63. }
  64. QString Account::davPath() const
  65. {
  66. if (capabilities().chunkingNg()) {
  67. // The chunking-ng means the server prefer to use the new webdav URL
  68. return QLatin1String("/remote.php/dav/files/") + davUser() + QLatin1Char('/');
  69. }
  70. // make sure to have a trailing slash
  71. if (!_davPath.endsWith('/')) {
  72. QString dp(_davPath);
  73. dp.append('/');
  74. return dp;
  75. }
  76. return _davPath;
  77. }
  78. void Account::setSharedThis(AccountPtr sharedThis)
  79. {
  80. _sharedThis = sharedThis.toWeakRef();
  81. }
  82. AccountPtr Account::sharedFromThis()
  83. {
  84. return _sharedThis.toStrongRef();
  85. }
  86. QString Account::davUser() const
  87. {
  88. return _davUser.isEmpty() ? _credentials->user() : _davUser;
  89. }
  90. void Account::setDavUser(const QString &newDavUser)
  91. {
  92. _davUser = newDavUser;
  93. }
  94. #ifndef TOKEN_AUTH_ONLY
  95. QImage Account::avatar() const
  96. {
  97. return _avatarImg;
  98. }
  99. void Account::setAvatar(const QImage &img)
  100. {
  101. _avatarImg = img;
  102. emit accountChangedAvatar();
  103. }
  104. #endif
  105. QString Account::displayName() const
  106. {
  107. QString dn = QString("%1@%2").arg(credentials()->user(), _url.host());
  108. int port = url().port();
  109. if (port > 0 && port != 80 && port != 443) {
  110. dn.append(QLatin1Char(':'));
  111. dn.append(QString::number(port));
  112. }
  113. return dn;
  114. }
  115. QString Account::davDisplayName() const
  116. {
  117. return _displayName;
  118. }
  119. void Account::setDavDisplayName(const QString &newDisplayName)
  120. {
  121. _displayName = newDisplayName;
  122. emit accountChangedDisplayName();
  123. }
  124. QString Account::id() const
  125. {
  126. return _id;
  127. }
  128. AbstractCredentials *Account::credentials() const
  129. {
  130. return _credentials.data();
  131. }
  132. void Account::setCredentials(AbstractCredentials *cred)
  133. {
  134. // set active credential manager
  135. QNetworkCookieJar *jar = nullptr;
  136. if (_am) {
  137. jar = _am->cookieJar();
  138. jar->setParent(nullptr);
  139. _am = QSharedPointer<QNetworkAccessManager>();
  140. }
  141. // The order for these two is important! Reading the credential's
  142. // settings accesses the account as well as account->_credentials,
  143. _credentials.reset(cred);
  144. cred->setAccount(this);
  145. // Note: This way the QNAM can outlive the Account and Credentials.
  146. // This is necessary to avoid issues with the QNAM being deleted while
  147. // processing slotHandleSslErrors().
  148. _am = QSharedPointer<QNetworkAccessManager>(_credentials->createQNAM(), &QObject::deleteLater);
  149. if (jar) {
  150. _am->setCookieJar(jar);
  151. }
  152. connect(_am.data(), SIGNAL(sslErrors(QNetworkReply *, QList<QSslError>)),
  153. SLOT(slotHandleSslErrors(QNetworkReply *, QList<QSslError>)));
  154. connect(_am.data(), &QNetworkAccessManager::proxyAuthenticationRequired,
  155. this, &Account::proxyAuthenticationRequired);
  156. connect(_credentials.data(), &AbstractCredentials::fetched,
  157. this, &Account::slotCredentialsFetched);
  158. connect(_credentials.data(), &AbstractCredentials::asked,
  159. this, &Account::slotCredentialsAsked);
  160. }
  161. QUrl Account::davUrl() const
  162. {
  163. return Utility::concatUrlPath(url(), davPath());
  164. }
  165. QUrl Account::deprecatedPrivateLinkUrl(const QByteArray &numericFileId) const
  166. {
  167. return Utility::concatUrlPath(_userVisibleUrl,
  168. QLatin1String("/index.php/f/") + QUrl::toPercentEncoding(QString::fromLatin1(numericFileId)));
  169. }
  170. /**
  171. * clear all cookies. (Session cookies or not)
  172. */
  173. void Account::clearCookieJar()
  174. {
  175. auto jar = qobject_cast<CookieJar *>(_am->cookieJar());
  176. ASSERT(jar);
  177. jar->setAllCookies(QList<QNetworkCookie>());
  178. emit wantsAccountSaved(this);
  179. }
  180. /*! This shares our official cookie jar (containing all the tasty
  181. authentication cookies) with another QNAM while making sure
  182. of not losing its ownership. */
  183. void Account::lendCookieJarTo(QNetworkAccessManager *guest)
  184. {
  185. auto jar = _am->cookieJar();
  186. auto oldParent = jar->parent();
  187. guest->setCookieJar(jar); // takes ownership of our precious cookie jar
  188. jar->setParent(oldParent); // takes it back
  189. }
  190. QString Account::cookieJarPath()
  191. {
  192. return QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation) + "/cookies" + id() + ".db";
  193. }
  194. void Account::resetNetworkAccessManager()
  195. {
  196. if (!_credentials || !_am) {
  197. return;
  198. }
  199. qCDebug(lcAccount) << "Resetting QNAM";
  200. QNetworkCookieJar *jar = _am->cookieJar();
  201. // Use a QSharedPointer to allow locking the life of the QNAM on the stack.
  202. // Make it call deleteLater to make sure that we can return to any QNAM stack frames safely.
  203. _am = QSharedPointer<QNetworkAccessManager>(_credentials->createQNAM(), &QObject::deleteLater);
  204. _am->setCookieJar(jar); // takes ownership of the old cookie jar
  205. connect(_am.data(), SIGNAL(sslErrors(QNetworkReply *, QList<QSslError>)),
  206. SLOT(slotHandleSslErrors(QNetworkReply *, QList<QSslError>)));
  207. connect(_am.data(), &QNetworkAccessManager::proxyAuthenticationRequired,
  208. this, &Account::proxyAuthenticationRequired);
  209. }
  210. QNetworkAccessManager *Account::networkAccessManager()
  211. {
  212. return _am.data();
  213. }
  214. QSharedPointer<QNetworkAccessManager> Account::sharedNetworkAccessManager()
  215. {
  216. return _am;
  217. }
  218. QNetworkReply *Account::sendRawRequest(const QByteArray &verb, const QUrl &url, QNetworkRequest req, QIODevice *data)
  219. {
  220. req.setUrl(url);
  221. req.setSslConfiguration(this->getOrCreateSslConfig());
  222. if (verb == "HEAD" && !data) {
  223. return _am->head(req);
  224. } else if (verb == "GET" && !data) {
  225. return _am->get(req);
  226. } else if (verb == "POST") {
  227. return _am->post(req, data);
  228. } else if (verb == "PUT") {
  229. return _am->put(req, data);
  230. } else if (verb == "DELETE" && !data) {
  231. return _am->deleteResource(req);
  232. }
  233. return _am->sendCustomRequest(req, verb, data);
  234. }
  235. SimpleNetworkJob *Account::sendRequest(const QByteArray &verb, const QUrl &url, QNetworkRequest req, QIODevice *data)
  236. {
  237. auto job = new SimpleNetworkJob(sharedFromThis(), this);
  238. job->startRequest(verb, url, req, data);
  239. return job;
  240. }
  241. void Account::setSslConfiguration(const QSslConfiguration &config)
  242. {
  243. _sslConfiguration = config;
  244. }
  245. QSslConfiguration Account::getOrCreateSslConfig()
  246. {
  247. if (!_sslConfiguration.isNull()) {
  248. // Will be set by CheckServerJob::finished()
  249. // We need to use a central shared config to get SSL session tickets
  250. return _sslConfiguration;
  251. }
  252. // if setting the client certificate fails, you will probably get an error similar to this:
  253. // "An internal error number 1060 happened. SSL handshake failed, client certificate was requested: SSL error: sslv3 alert handshake failure"
  254. QSslConfiguration sslConfig = QSslConfiguration::defaultConfiguration();
  255. // Try hard to re-use session for different requests
  256. sslConfig.setSslOption(QSsl::SslOptionDisableSessionTickets, false);
  257. sslConfig.setSslOption(QSsl::SslOptionDisableSessionSharing, false);
  258. sslConfig.setSslOption(QSsl::SslOptionDisableSessionPersistence, false);
  259. return sslConfig;
  260. }
  261. void Account::setApprovedCerts(const QList<QSslCertificate> certs)
  262. {
  263. _approvedCerts = certs;
  264. }
  265. void Account::addApprovedCerts(const QList<QSslCertificate> certs)
  266. {
  267. _approvedCerts += certs;
  268. }
  269. void Account::resetRejectedCertificates()
  270. {
  271. _rejectedCertificates.clear();
  272. }
  273. void Account::setSslErrorHandler(AbstractSslErrorHandler *handler)
  274. {
  275. _sslErrorHandler.reset(handler);
  276. }
  277. void Account::setUrl(const QUrl &url)
  278. {
  279. _url = url;
  280. _userVisibleUrl = url;
  281. }
  282. void Account::setUserVisibleHost(const QString &host)
  283. {
  284. _userVisibleUrl.setHost(host);
  285. }
  286. QVariant Account::credentialSetting(const QString &key) const
  287. {
  288. if (_credentials) {
  289. QString prefix = _credentials->authType();
  290. QString value = _settingsMap.value(prefix + "_" + key).toString();
  291. if (value.isEmpty()) {
  292. value = _settingsMap.value(key).toString();
  293. }
  294. return value;
  295. }
  296. return QVariant();
  297. }
  298. void Account::setCredentialSetting(const QString &key, const QVariant &value)
  299. {
  300. if (_credentials) {
  301. QString prefix = _credentials->authType();
  302. _settingsMap.insert(prefix + "_" + key, value);
  303. }
  304. }
  305. void Account::slotHandleSslErrors(QNetworkReply *reply, QList<QSslError> errors)
  306. {
  307. NetworkJobTimeoutPauser pauser(reply);
  308. QString out;
  309. QDebug(&out) << "SSL-Errors happened for url " << reply->url().toString();
  310. foreach (const QSslError &error, errors) {
  311. QDebug(&out) << "\tError in " << error.certificate() << ":"
  312. << error.errorString() << "(" << error.error() << ")"
  313. << "\n";
  314. }
  315. bool allPreviouslyRejected = true;
  316. foreach (const QSslError &error, errors) {
  317. if (!_rejectedCertificates.contains(error.certificate())) {
  318. allPreviouslyRejected = false;
  319. }
  320. }
  321. // If all certs have previously been rejected by the user, don't ask again.
  322. if (allPreviouslyRejected) {
  323. qCInfo(lcAccount) << out << "Certs not trusted by user decision, returning.";
  324. return;
  325. }
  326. QList<QSslCertificate> approvedCerts;
  327. if (_sslErrorHandler.isNull()) {
  328. qCWarning(lcAccount) << out << "called without valid SSL error handler for account" << url();
  329. return;
  330. }
  331. // SslDialogErrorHandler::handleErrors will run an event loop that might execute
  332. // the deleteLater() of the QNAM before we have the chance of unwinding our stack.
  333. // Keep a ref here on our stackframe to make sure that it doesn't get deleted before
  334. // handleErrors returns.
  335. QSharedPointer<QNetworkAccessManager> qnamLock = _am;
  336. QPointer<QObject> guard = reply;
  337. if (_sslErrorHandler->handleErrors(errors, reply->sslConfiguration(), &approvedCerts, sharedFromThis())) {
  338. if (!guard)
  339. return;
  340. QSslSocket::addDefaultCaCertificates(approvedCerts);
  341. addApprovedCerts(approvedCerts);
  342. emit wantsAccountSaved(this);
  343. // all ssl certs are known and accepted. We can ignore the problems right away.
  344. qCInfo(lcAccount) << out << "Certs are known and trusted! This is not an actual error.";
  345. // Warning: Do *not* use ignoreSslErrors() (without args) here:
  346. // it permanently ignores all SSL errors for this host, even
  347. // certificate changes.
  348. reply->ignoreSslErrors(errors);
  349. } else {
  350. if (!guard)
  351. return;
  352. // Mark all involved certificates as rejected, so we don't ask the user again.
  353. foreach (const QSslError &error, errors) {
  354. if (!_rejectedCertificates.contains(error.certificate())) {
  355. _rejectedCertificates.append(error.certificate());
  356. }
  357. }
  358. // Not calling ignoreSslErrors will make the SSL handshake fail.
  359. return;
  360. }
  361. }
  362. void Account::slotCredentialsFetched()
  363. {
  364. emit credentialsFetched(_credentials.data());
  365. }
  366. void Account::slotCredentialsAsked()
  367. {
  368. emit credentialsAsked(_credentials.data());
  369. }
  370. void Account::handleInvalidCredentials()
  371. {
  372. // Retrieving password will trigger remote wipe check job
  373. retrieveAppPassword();
  374. emit invalidCredentials();
  375. }
  376. void Account::clearQNAMCache()
  377. {
  378. _am->clearAccessCache();
  379. }
  380. const Capabilities &Account::capabilities() const
  381. {
  382. return _capabilities;
  383. }
  384. void Account::setCapabilities(const QVariantMap &caps)
  385. {
  386. _capabilities = Capabilities(caps);
  387. }
  388. QString Account::serverVersion() const
  389. {
  390. return _serverVersion;
  391. }
  392. int Account::serverVersionInt() const
  393. {
  394. // FIXME: Use Qt 5.5 QVersionNumber
  395. auto components = serverVersion().split('.');
  396. return makeServerVersion(components.value(0).toInt(),
  397. components.value(1).toInt(),
  398. components.value(2).toInt());
  399. }
  400. int Account::makeServerVersion(int majorVersion, int minorVersion, int patchVersion)
  401. {
  402. return (majorVersion << 16) + (minorVersion << 8) + patchVersion;
  403. }
  404. bool Account::serverVersionUnsupported() const
  405. {
  406. if (serverVersionInt() == 0) {
  407. // not detected yet, assume it is fine.
  408. return false;
  409. }
  410. return serverVersionInt() < makeServerVersion(9, 1, 0);
  411. }
  412. void Account::setServerVersion(const QString &version)
  413. {
  414. if (version == _serverVersion) {
  415. return;
  416. }
  417. auto oldServerVersion = _serverVersion;
  418. _serverVersion = version;
  419. emit serverVersionChanged(this, oldServerVersion, version);
  420. }
  421. bool Account::rootEtagChangesNotOnlySubFolderEtags()
  422. {
  423. return (serverVersionInt() >= makeServerVersion(8, 1, 0));
  424. }
  425. void Account::setNonShib(bool nonShib)
  426. {
  427. if (nonShib) {
  428. _davPath = Theme::instance()->webDavPathNonShib();
  429. } else {
  430. _davPath = Theme::instance()->webDavPath();
  431. }
  432. }
  433. void Account::setAppPassword(QString appPassword){
  434. const QString kck = AbstractCredentials::keychainKey(
  435. url().toString(),
  436. davUser() + app_password,
  437. id()
  438. );
  439. WritePasswordJob *job = new WritePasswordJob(Theme::instance()->appName());
  440. job->setInsecureFallback(false);
  441. job->setKey(kck);
  442. job->setBinaryData(appPassword.toLatin1());
  443. connect(job, &WritePasswordJob::finished, [](Job *) {
  444. qCInfo(lcAccount) << "appPassword stored in keychain";
  445. });
  446. job->start();
  447. }
  448. void Account::retrieveAppPassword(){
  449. const QString kck = AbstractCredentials::keychainKey(
  450. url().toString(),
  451. credentials()->user() + app_password,
  452. id()
  453. );
  454. ReadPasswordJob *job = new ReadPasswordJob(Theme::instance()->appName());
  455. job->setInsecureFallback(false);
  456. job->setKey(kck);
  457. connect(job, &WritePasswordJob::finished, [this](Job *incoming) {
  458. ReadPasswordJob *readJob = static_cast<ReadPasswordJob *>(incoming);
  459. QString pwd("");
  460. // Error or no valid public key error out
  461. if (readJob->error() == NoError &&
  462. readJob->binaryData().length() > 0) {
  463. pwd = readJob->binaryData();
  464. }
  465. emit appPasswordRetrieved(pwd);
  466. });
  467. job->start();
  468. }
  469. void Account::deleteAppPassword(){
  470. const QString kck = AbstractCredentials::keychainKey(
  471. url().toString(),
  472. credentials()->user() + app_password,
  473. id()
  474. );
  475. if (kck.isEmpty()) {
  476. qCDebug(lcAccount) << "appPassword is empty";
  477. return;
  478. }
  479. DeletePasswordJob *job = new DeletePasswordJob(Theme::instance()->appName());
  480. job->setInsecureFallback(false);
  481. job->setKey(kck);
  482. job->start();
  483. }
  484. } // namespace OCC