Query credentials when needed. Put the account offline if user aborts.

This is only implemented for HTTP auth. Shibboleth still does its own thing.
This commit is contained in:
Daniel Molkentin 2013-11-22 14:01:11 +01:00
parent 0a2861a731
commit ea2b5fb29c
14 changed files with 165 additions and 50 deletions

View File

@ -19,6 +19,7 @@
#include <csync.h>
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;

View File

@ -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());

View File

@ -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*);
};

View File

@ -17,6 +17,7 @@
#include <QDebug>
#include <QNetworkReply>
#include <QSettings>
#include <QInputDialog>
#include <qtkeychain/keychain.h>
@ -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)

View File

@ -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*);

View File

@ -13,14 +13,16 @@
#include <QEventLoop>
#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()));

View File

@ -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;
};

View File

@ -46,7 +46,8 @@ int shibboleth_redirect_callback(CSYNC* csync_ctx,
QMutex mutex;
QMutexLocker locker(&mutex);
ShibbolethCredentials* creds = qobject_cast<ShibbolethCredentials*>(AccountManager::instance()->account()->credentials());
Account *account = AccountManager::instance()->account();
ShibbolethCredentials* creds = qobject_cast<ShibbolethCredentials*>(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()

View File

@ -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);

View File

@ -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<QByteArray>() << "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<QByteArray>() << "getlastmodified");
connect(job, SIGNAL(result(QVariantMap)), SLOT(slotAuthSuccess()));
connect(job, SIGNAL(networkError(QNetworkReply*)), SLOT(slotAuthFailed(QNetworkReply*)));
job->start();
qDebug() << "# checking for authentication settings.";
}

View File

@ -23,7 +23,7 @@
#include <QStringList>
#include <QStack>
#include <QTimer>
#include <QMutex>
#include <QDebug>
#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<QByteArray> properties)
@ -392,7 +422,7 @@ QList<QByteArray> 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

View File

@ -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<QByteArray> _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

View File

@ -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

View File

@ -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;
};