From ea2b5fb29cfe511bff8d5a7ae6e0bfa4c55738ea Mon Sep 17 00:00:00 2001 From: Daniel Molkentin Date: Fri, 22 Nov 2013 14:01:11 +0100 Subject: [PATCH] Query credentials when needed. Put the account offline if user aborts. This is only implemented for HTTP auth. Shibboleth still does its own thing. --- src/creds/abstractcredentials.h | 3 + src/creds/dummycredentials.cpp | 12 ++++ src/creds/dummycredentials.h | 2 + src/creds/httpcredentials.cpp | 39 +++++++++++ src/creds/httpcredentials.h | 3 + src/creds/shibboleth/shibbolethrefresher.cpp | 7 +- src/creds/shibboleth/shibbolethrefresher.h | 4 +- src/creds/shibbolethcredentials.cpp | 21 ++++-- src/creds/shibbolethcredentials.h | 4 +- src/mirall/connectionvalidator.cpp | 12 ++-- src/mirall/networkjobs.cpp | 71 ++++++++++++++------ src/mirall/networkjobs.h | 25 ++++--- src/mirall/owncloudsetupwizard.cpp | 8 +-- src/mirall/owncloudsetupwizard.h | 4 +- 14 files changed, 165 insertions(+), 50 deletions(-) diff --git a/src/creds/abstractcredentials.h b/src/creds/abstractcredentials.h index 871ec0b3a7..4abae96705 100644 --- a/src/creds/abstractcredentials.h +++ b/src/creds/abstractcredentials.h @@ -19,6 +19,7 @@ #include class QNetworkAccessManager; +class QNetworkReply; namespace Mirall { class Account; @@ -37,6 +38,8 @@ public: virtual QNetworkAccessManager* getQNAM() const = 0; virtual bool ready() const = 0; virtual void fetch(Account *account) = 0; + virtual bool stillValid(QNetworkReply *reply) = 0; + virtual bool fetchFromUser(Account *account) = 0; virtual void persist(Account *account) = 0; diff --git a/src/creds/dummycredentials.cpp b/src/creds/dummycredentials.cpp index b92bb58385..478020fe56 100644 --- a/src/creds/dummycredentials.cpp +++ b/src/creds/dummycredentials.cpp @@ -50,6 +50,18 @@ bool DummyCredentials::ready() const return true; } +bool DummyCredentials::stillValid(QNetworkReply *reply) +{ + Q_UNUSED(reply) + return true; +} + +bool DummyCredentials::fetchFromUser(Account *account) +{ + Q_UNUSED(account) + return false; +} + void DummyCredentials::fetch(Account*) { Q_EMIT(fetched()); diff --git a/src/creds/dummycredentials.h b/src/creds/dummycredentials.h index b06f053c50..59df8615d7 100644 --- a/src/creds/dummycredentials.h +++ b/src/creds/dummycredentials.h @@ -31,6 +31,8 @@ public: QString user() const; QNetworkAccessManager* getQNAM() const; bool ready() const; + bool stillValid(QNetworkReply *reply); + bool fetchFromUser(Account *account); void fetch(Account*); void persist(Account*); }; diff --git a/src/creds/httpcredentials.cpp b/src/creds/httpcredentials.cpp index 98892fedca..12208bbb70 100644 --- a/src/creds/httpcredentials.cpp +++ b/src/creds/httpcredentials.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include @@ -83,6 +84,7 @@ protected: QByteArray credHash = QByteArray(_cred->user().toUtf8()+":"+_cred->password().toUtf8()).toBase64(); QNetworkRequest req(request); req.setRawHeader(QByteArray("Authorization"), QByteArray("Basic ") + credHash); + qDebug() << "Request for " << req.url() << "with authorization" << QByteArray::fromBase64(credHash); return MirallAccessManager::createRequest(op, req, outgoingData); } private: @@ -183,6 +185,22 @@ void HttpCredentials::fetch(Account *account) job->start(); } } +bool HttpCredentials::stillValid(QNetworkReply *reply) +{ + return (reply->error() != QNetworkReply::AuthenticationRequiredError); +} + +bool HttpCredentials::fetchFromUser(Account *account) +{ + bool ok = false; + QString password = queryPassword(&ok); + if (ok) { + _password = password; + _ready = true; + persist(account); + } + return ok; +} void HttpCredentials::slotReadJobDone(QKeychain::Job *job) { @@ -196,9 +214,30 @@ void HttpCredentials::slotReadJobDone(QKeychain::Job *job) Q_EMIT fetched(); break; default: + if (!_user.isEmpty()) { + bool ok; + QString pwd = queryPassword(&ok); + if (ok) { + _password = pwd; + _ready = true; + Q_EMIT fetched(); + break; + } + } qDebug() << "Error while reading password" << job->errorString(); } +} +QString HttpCredentials::queryPassword(bool *ok) +{ + if (ok) { + return QInputDialog::getText(0, tr("Enter Password"), + tr("Please enter %1 password for user '%2':") + .arg(Theme::instance()->appNameGUI(), _user), + QLineEdit::Password, QString(), ok); + } else { + return QString(); + } } void HttpCredentials::persist(Account *account) diff --git a/src/creds/httpcredentials.h b/src/creds/httpcredentials.h index 99ccb65b0d..4d06b47337 100644 --- a/src/creds/httpcredentials.h +++ b/src/creds/httpcredentials.h @@ -45,9 +45,12 @@ public: QNetworkAccessManager* getQNAM() const; bool ready() const; void fetch(Account *account); + bool stillValid(QNetworkReply *reply); + bool fetchFromUser(Account *account); void persist(Account *account); QString user() const; QString password() const; + QString queryPassword(bool *ok); private Q_SLOTS: void slotAuthentication(QNetworkReply*, QAuthenticator*); diff --git a/src/creds/shibboleth/shibbolethrefresher.cpp b/src/creds/shibboleth/shibbolethrefresher.cpp index 3f5e00466f..a301330e82 100644 --- a/src/creds/shibboleth/shibbolethrefresher.cpp +++ b/src/creds/shibboleth/shibbolethrefresher.cpp @@ -13,14 +13,16 @@ #include +#include "mirall/account.h" #include "creds/shibboleth/shibbolethrefresher.h" #include "creds/shibbolethcredentials.h" namespace Mirall { -ShibbolethRefresher::ShibbolethRefresher(ShibbolethCredentials* creds, CSYNC* csync_ctx, QObject* parent) +ShibbolethRefresher::ShibbolethRefresher(Account *account, ShibbolethCredentials* creds, CSYNC* csync_ctx, QObject* parent) : QObject(parent), + _account(account), _creds(creds), _csync_ctx(csync_ctx) {} @@ -33,7 +35,8 @@ void ShibbolethRefresher::refresh() this, SLOT(onInvalidatedAndFetched(QByteArray))); connect(_creds, SIGNAL(invalidatedAndFetched(QByteArray)), &loop, SLOT(quit())); - QMetaObject::invokeMethod(_creds, "invalidateAndFetch", Qt::QueuedConnection); + QMetaObject::invokeMethod(_creds, "invalidateAndFetch",Qt::QueuedConnection, + Q_ARG(Account*, _account)); loop.exec(); disconnect(_creds, SIGNAL(invalidatedAndFetched(QByteArray)), &loop, SLOT(quit())); diff --git a/src/creds/shibboleth/shibbolethrefresher.h b/src/creds/shibboleth/shibbolethrefresher.h index 39d2ef55c1..242a5229a8 100644 --- a/src/creds/shibboleth/shibbolethrefresher.h +++ b/src/creds/shibboleth/shibbolethrefresher.h @@ -23,6 +23,7 @@ class QByteArray; namespace Mirall { +class Account; class ShibbolethCredentials; class ShibbolethRefresher : public QObject @@ -30,7 +31,7 @@ class ShibbolethRefresher : public QObject Q_OBJECT public: - ShibbolethRefresher(ShibbolethCredentials* creds, CSYNC* csync_ctx, QObject* parent = 0); + ShibbolethRefresher(Account *account, ShibbolethCredentials* creds, CSYNC* csync_ctx, QObject* parent = 0); void refresh(); @@ -38,6 +39,7 @@ private Q_SLOTS: void onInvalidatedAndFetched(const QByteArray& cookieData); private: + Account* _account; ShibbolethCredentials* _creds; CSYNC* _csync_ctx; }; diff --git a/src/creds/shibbolethcredentials.cpp b/src/creds/shibbolethcredentials.cpp index f7140d9058..975d1489b2 100644 --- a/src/creds/shibbolethcredentials.cpp +++ b/src/creds/shibbolethcredentials.cpp @@ -46,7 +46,8 @@ int shibboleth_redirect_callback(CSYNC* csync_ctx, QMutex mutex; QMutexLocker locker(&mutex); - ShibbolethCredentials* creds = qobject_cast(AccountManager::instance()->account()->credentials()); + Account *account = AccountManager::instance()->account(); + ShibbolethCredentials* creds = qobject_cast(account->credentials()); if (!creds) { @@ -54,7 +55,7 @@ int shibboleth_redirect_callback(CSYNC* csync_ctx, return 1; } - ShibbolethRefresher refresher(creds, csync_ctx); + ShibbolethRefresher refresher(account, creds, csync_ctx); // blocks refresher.refresh(); @@ -191,6 +192,18 @@ void ShibbolethCredentials::fetch(Account *account) } } +bool ShibbolethCredentials::stillValid(QNetworkReply *reply) +{ + Q_UNUSED(reply) + return true; +} + +bool ShibbolethCredentials::fetchFromUser(Account *account) +{ + Q_UNUSED(account) + return false; +} + void ShibbolethCredentials::persist(Account* /*account*/) { ShibbolethConfigFile cfg; @@ -226,7 +239,7 @@ void ShibbolethCredentials::slotBrowserHidden() Q_EMIT fetched(); } -void ShibbolethCredentials::invalidateAndFetch() +void ShibbolethCredentials::invalidateAndFetch(Account* account) { _ready = false; connect (this, SIGNAL(fetched()), @@ -234,7 +247,7 @@ void ShibbolethCredentials::invalidateAndFetch() // small hack to support the ShibbolethRefresher hack // we already rand fetch() with a valid account object, // and hence know the url on refresh - fetch(0); + fetch(account); } void ShibbolethCredentials::onFetched() diff --git a/src/creds/shibbolethcredentials.h b/src/creds/shibbolethcredentials.h index e77891f788..e7f9a4033d 100644 --- a/src/creds/shibbolethcredentials.h +++ b/src/creds/shibbolethcredentials.h @@ -42,12 +42,14 @@ public: QNetworkAccessManager* getQNAM() const; bool ready() const; void fetch(Account *account); + bool stillValid(QNetworkReply *reply); + virtual bool fetchFromUser(Account *account); void persist(Account *account); QNetworkCookie cookie() const; public Q_SLOTS: - void invalidateAndFetch(); + void invalidateAndFetch(Account *account); private Q_SLOTS: void onShibbolethCookieReceived(const QNetworkCookie& cookie); diff --git a/src/mirall/connectionvalidator.cpp b/src/mirall/connectionvalidator.cpp index 5bc06f16f1..6d6533d4a1 100644 --- a/src/mirall/connectionvalidator.cpp +++ b/src/mirall/connectionvalidator.cpp @@ -82,6 +82,7 @@ void ConnectionValidator::checkConnection() { if( _account ) { CheckServerJob *checkJob = new CheckServerJob(_account, false, this); + checkJob->setIgnoreCredentialFailure(true); connect(checkJob, SIGNAL(instanceFound(QUrl,QVariantMap)), SLOT(slotStatusFound(QUrl,QVariantMap))); connect(checkJob, SIGNAL(networkError(QNetworkReply*)), SLOT(slotNoStatusFound(QNetworkReply*))); checkJob->start(); @@ -127,11 +128,12 @@ void ConnectionValidator::slotCheckAuthentication() { // simply GET the webdav root, will fail if credentials are wrong. // continue in slotAuthCheck here :-) - PropfindJob *propFind = new PropfindJob(_account, "/", this); - propFind->setProperties(QList() << "getlastmodified"); - connect(propFind, SIGNAL(result(QVariantMap)), SLOT(slotAuthSuccess())); - connect(propFind, SIGNAL(networkError(QNetworkReply*)), SLOT(slotAuthFailed(QNetworkReply*))); - propFind->start(); + PropfindJob *job = new PropfindJob(_account, "/", this); + job->setIgnoreCredentialFailure(true); + job->setProperties(QList() << "getlastmodified"); + connect(job, SIGNAL(result(QVariantMap)), SLOT(slotAuthSuccess())); + connect(job, SIGNAL(networkError(QNetworkReply*)), SLOT(slotAuthFailed(QNetworkReply*))); + job->start(); qDebug() << "# checking for authentication settings."; } diff --git a/src/mirall/networkjobs.cpp b/src/mirall/networkjobs.cpp index ce80caefa2..2a29dfcbfd 100644 --- a/src/mirall/networkjobs.cpp +++ b/src/mirall/networkjobs.cpp @@ -23,7 +23,7 @@ #include #include #include - +#include #include #include "json.h" @@ -32,11 +32,13 @@ #include "mirall/account.h" #include "creds/credentialsfactory.h" +#include "creds/abstractcredentials.h" namespace Mirall { AbstractNetworkJob::AbstractNetworkJob(Account *account, const QString &path, QObject *parent) : QObject(parent) + , _ignoreCredentialFailure(false) , _reply(0) , _account(account) , _path(path) @@ -70,6 +72,11 @@ void AbstractNetworkJob::resetTimeout() _timer->start(interval); } +void AbstractNetworkJob::setIgnoreCredentialFailure(bool ignore) +{ + _ignoreCredentialFailure = ignore; +} + void AbstractNetworkJob::setAccount(Account *account) { _account = account; @@ -80,14 +87,10 @@ void AbstractNetworkJob::setPath(const QString &path) _path = path; } -void AbstractNetworkJob::slotError(QNetworkReply::NetworkError error) +void AbstractNetworkJob::slotError(QNetworkReply::NetworkError) { - if (error == QNetworkReply::ContentAccessDenied) { - // ### ask for password, retry job, needs refactoring to use start() - } qDebug() << metaObject()->className() << "Error:" << _reply->errorString(); emit networkError(_reply); - deleteLater(); } void AbstractNetworkJob::setupConnections(QNetworkReply *reply) @@ -128,10 +131,37 @@ QNetworkReply *AbstractNetworkJob::headRequest(const QUrl &url) return _account->headRequest(url); } +void AbstractNetworkJob::slotFinished() +{ + static QMutex mutex; + AbstractCredentials *creds = _account->credentials(); + qDebug() << creds->stillValid(_reply) << _ignoreCredentialFailure << _reply->errorString(); + if (creds->stillValid(_reply) || _ignoreCredentialFailure) { + finished(); + } else { + // If other jobs that still were created from before + // the account was put offline by the code below, + // we do want them to fail silently while we + // query the user + if (mutex.tryLock()) { + Account *a = account(); + a->setOnline(false); + a->setOnline(creds->fetchFromUser(a)); + mutex.unlock(); + } + } + deleteLater(); +} + AbstractNetworkJob::~AbstractNetworkJob() { _reply->deleteLater(); } +void AbstractNetworkJob::start() +{ + qDebug() << "!!!" << metaObject()->className() << "created for" << account()->url() << "querying" << path(); +} + /*********************************************************************************************/ RequestEtagJob::RequestEtagJob(Account *account, const QString &path, QObject *parent) @@ -167,9 +197,10 @@ void RequestEtagJob::start() if( reply()->error() != QNetworkReply::NoError ) { qDebug() << "getting etag: request network error: " << reply()->errorString(); } + AbstractNetworkJob::start(); } -void RequestEtagJob::slotFinished() +void RequestEtagJob::finished() { if (reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute) == 207) { // Parse DAV response @@ -188,7 +219,6 @@ void RequestEtagJob::slotFinished() } emit etagRetreived(etag); } - deleteLater(); } /*********************************************************************************************/ @@ -204,12 +234,12 @@ void MkColJob::start() QNetworkReply *reply = davRequest("MKCOL", path()); setReply(reply); setupConnections(reply); + AbstractNetworkJob::start(); } -void MkColJob::slotFinished() +void MkColJob::finished() { emit finished(reply()->error()); - deleteLater(); } /*********************************************************************************************/ @@ -236,9 +266,10 @@ void LsColJob::start() buf->setParent(reply); setReply(reply); setupConnections(reply); + AbstractNetworkJob::start(); } -void LsColJob::slotFinished() +void LsColJob::finished() { if (reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute) == 207) { // Parse DAV response @@ -264,8 +295,6 @@ void LsColJob::slotFinished() } emit directoryListing(folders); } - - deleteLater(); } /*********************************************************************************************/ @@ -281,6 +310,7 @@ void CheckServerJob::start() { setReply(getRequest(path())); setupConnections(reply()); + AbstractNetworkJob::start(); } void CheckServerJob::slotTimeout() @@ -305,7 +335,7 @@ bool CheckServerJob::installed(const QVariantMap &info) return info.value(QLatin1String("installed")).toBool(); } -void CheckServerJob::slotFinished() +void CheckServerJob::finished() { account()->setCertificateChain(reply()->sslConfiguration().peerCertificateChain()); @@ -343,7 +373,6 @@ void CheckServerJob::slotFinished() } else { qDebug() << "No proper answer on " << requestedUrl; } - deleteLater(); } /*********************************************************************************************/ @@ -380,6 +409,7 @@ void PropfindJob::start() setReply(davRequest("PROPFIND", path(), req, buf)); buf->setParent(reply()); setupConnections(reply()); + AbstractNetworkJob::start(); } void PropfindJob::setProperties(QList properties) @@ -392,7 +422,7 @@ QList PropfindJob::properties() const return _properties; } -void PropfindJob::slotFinished() +void PropfindJob::finished() { int http_result_code = reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); @@ -426,8 +456,6 @@ void PropfindJob::slotFinished() } else { qDebug() << "Quota request *not* successful, http result code is " << http_result_code; } - - deleteLater(); } /*********************************************************************************************/ @@ -441,9 +469,10 @@ void EntityExistsJob::start() { setReply(headRequest(path())); setupConnections(reply()); + AbstractNetworkJob::start(); } -void EntityExistsJob::slotFinished() +void EntityExistsJob::finished() { emit exists(reply()); } @@ -473,9 +502,10 @@ void CheckQuotaJob::start() setReply(davRequest("PROPFIND", path(), req, buf)); buf->setParent(reply()); setupConnections(reply()); + AbstractNetworkJob::start(); } -void CheckQuotaJob::slotFinished() +void CheckQuotaJob::finished() { if (reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute) == 207) { // Parse DAV response @@ -498,7 +528,6 @@ void CheckQuotaJob::slotFinished() qint64 total = quotaUsedBytes + quotaAvailableBytes; emit quotaRetrieved(total, quotaUsedBytes); } - deleteLater(); } } // namespace Mirall diff --git a/src/mirall/networkjobs.h b/src/mirall/networkjobs.h index 4b1475fbd0..5617f6f30f 100644 --- a/src/mirall/networkjobs.h +++ b/src/mirall/networkjobs.h @@ -37,7 +37,7 @@ public: explicit AbstractNetworkJob(Account *account, const QString &path, QObject* parent = 0); virtual ~AbstractNetworkJob(); - virtual void start() = 0; + virtual void start(); void setAccount(Account *account); Account* account() const { return _account; } @@ -50,6 +50,9 @@ public: void setTimeout(qint64 msec); void resetTimeout(); + void setIgnoreCredentialFailure(bool ignore); + bool ignoreCredentialFailure() const { return _ignoreCredentialFailure; } + signals: void networkError(QNetworkReply *reply); protected: @@ -66,13 +69,15 @@ protected: QNetworkReply* headRequest(const QUrl &url); int maxRedirects() const { return 10; } + virtual void finished() = 0; private slots: - virtual void slotFinished() = 0; + void slotFinished(); void slotError(QNetworkReply::NetworkError); virtual void slotTimeout() {} private: + bool _ignoreCredentialFailure; QNetworkReply *_reply; Account *_account; QString _path; @@ -92,7 +97,7 @@ signals: void exists(QNetworkReply*); private slots: - virtual void slotFinished(); + virtual void finished(); }; /** @@ -108,7 +113,7 @@ signals: void directoryListing(const QStringList &items); private slots: - virtual void slotFinished(); + virtual void finished(); }; /** @@ -126,7 +131,7 @@ signals: void result(const QVariantMap &values); private slots: - virtual void slotFinished(); + virtual void finished(); private: QList _properties; @@ -145,11 +150,11 @@ signals: void finished(QNetworkReply::NetworkError); private slots: - virtual void slotFinished(); + virtual void finished(); }; /** - * @brief The CheckOwncloudJob class + * @brief The CheckServerJob class */ class CheckServerJob : public AbstractNetworkJob { Q_OBJECT @@ -166,7 +171,7 @@ signals: void timeout(const QUrl&url); private slots: - virtual void slotFinished(); + virtual void finished(); virtual void slotTimeout(); private: @@ -188,7 +193,7 @@ signals: void etagRetreived(const QString &etag); private slots: - virtual void slotFinished(); + virtual void finished(); }; /** @@ -204,7 +209,7 @@ signals: void quotaRetrieved(qint64 totalBytes, qint64 availableBytes); private slots: - virtual void slotFinished(); + virtual void finished(); }; } // namespace Mirall diff --git a/src/mirall/owncloudsetupwizard.cpp b/src/mirall/owncloudsetupwizard.cpp index 1e613ccd63..808aa3c805 100644 --- a/src/mirall/owncloudsetupwizard.cpp +++ b/src/mirall/owncloudsetupwizard.cpp @@ -430,9 +430,10 @@ void DetermineAuthTypeJob::start() QNetworkReply *reply = getRequest(Account::davPath()); setReply(reply); setupConnections(reply); + AbstractNetworkJob::start(); } -void DetermineAuthTypeJob::slotFinished() +void DetermineAuthTypeJob::finished() { QUrl redirection = reply()->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl(); qDebug() << redirection.toString(); @@ -458,7 +459,6 @@ void DetermineAuthTypeJob::slotFinished() emit authType(WizardCommon::HttpCreds); } } - deleteLater(); } ValidateDavAuthJob::ValidateDavAuthJob(Account *account, QObject *parent) @@ -471,12 +471,12 @@ void ValidateDavAuthJob::start() QNetworkReply *reply = getRequest(Account::davPath()); setReply(reply); setupConnections(reply); + AbstractNetworkJob::start(); } -void ValidateDavAuthJob::slotFinished() +void ValidateDavAuthJob::finished() { emit authResult(reply()); - deleteLater(); } } // ns Mirall diff --git a/src/mirall/owncloudsetupwizard.h b/src/mirall/owncloudsetupwizard.h index d5cfd87c15..ee9f329bf9 100644 --- a/src/mirall/owncloudsetupwizard.h +++ b/src/mirall/owncloudsetupwizard.h @@ -39,7 +39,7 @@ public: signals: void authResult(QNetworkReply*); private slots: - void slotFinished(); + void finished(); }; class DetermineAuthTypeJob : public AbstractNetworkJob { @@ -50,7 +50,7 @@ public: signals: void authType(WizardCommon::AuthType); private slots: - void slotFinished(); + void finished(); private: int _redirects; };