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