Use DetermineAuthTypeJob in HttpCredentials

* Move it to networkjobs
* Minor adjustments to its logic
* Fixes redirect handling for oauth/basic http auth check #6003
This commit is contained in:
Christian Kamm 2017-09-07 14:58:45 +02:00 committed by Roeland Jago Douma
parent ed9977a2b3
commit 1c0d80c20d
No known key found for this signature in database
GPG key ID: F941078878347C0C
13 changed files with 136 additions and 114 deletions

View file

@ -22,6 +22,7 @@
#include "creds/httpcredentialsgui.h" #include "creds/httpcredentialsgui.h"
#include "theme.h" #include "theme.h"
#include "account.h" #include "account.h"
#include "networkjobs.h"
#include <QMessageBox> #include <QMessageBox>
#include "asserts.h" #include "asserts.h"
@ -39,15 +40,11 @@ void HttpCredentialsGui::askFromUser()
void HttpCredentialsGui::askFromUserAsync() void HttpCredentialsGui::askFromUserAsync()
{ {
_password = QString(); // So our QNAM does not add any auth // First, we will check what kind of auth we need.
auto job = new DetermineAuthTypeJob(_account->sharedFromThis(), this);
// First, we will send a call to the webdav endpoint to check what kind of auth we need. job->setTimeout(30 * 1000);
auto reply = _account->sendRequest("GET", _account->davUrl()); QObject::connect(job, &DetermineAuthTypeJob::authType, this, [this](DetermineAuthTypeJob::AuthType type) {
QTimer::singleShot(30 * 1000, reply, &QNetworkReply::abort); if (type == DetermineAuthTypeJob::OAuth) {
QObject::connect(reply, &QNetworkReply::finished, this, [this, reply] {
reply->deleteLater();
if (reply->rawHeader("WWW-Authenticate").contains("Bearer ")) {
// OAuth
_asyncAuth.reset(new OAuth(_account, this)); _asyncAuth.reset(new OAuth(_account, this));
_asyncAuth->_expectedUser = _user; _asyncAuth->_expectedUser = _user;
connect(_asyncAuth.data(), &OAuth::result, connect(_asyncAuth.data(), &OAuth::result,
@ -56,15 +53,16 @@ void HttpCredentialsGui::askFromUserAsync()
this, &HttpCredentialsGui::authorisationLinkChanged); this, &HttpCredentialsGui::authorisationLinkChanged);
_asyncAuth->start(); _asyncAuth->start();
emit authorisationLinkChanged(); emit authorisationLinkChanged();
} else if (reply->error() == QNetworkReply::AuthenticationRequiredError) { } else if (type == DetermineAuthTypeJob::Basic) {
// Show the dialog // Show the dialog
// We will re-enter the event loop, so better wait the next iteration // We will re-enter the event loop, so better wait the next iteration
QMetaObject::invokeMethod(this, "showDialog", Qt::QueuedConnection); QMetaObject::invokeMethod(this, "showDialog", Qt::QueuedConnection);
} else { } else {
// Network error? // Network error? Unsupported auth type?
emit asked(); emit asked();
} }
}); });
job->start();
} }
void HttpCredentialsGui::asyncAuthResult(OAuth::Result r, const QString &user, void HttpCredentialsGui::asyncAuthResult(OAuth::Result r, const QString &user,

View file

@ -207,8 +207,8 @@ void OwncloudSetupWizard::slotOwnCloudFoundAuth(const QUrl &url, const QJsonObje
DetermineAuthTypeJob *job = new DetermineAuthTypeJob(_ocWizard->account(), this); DetermineAuthTypeJob *job = new DetermineAuthTypeJob(_ocWizard->account(), this);
job->setIgnoreCredentialFailure(true); job->setIgnoreCredentialFailure(true);
connect(job, SIGNAL(authType(WizardCommon::AuthType)), connect(job, &DetermineAuthTypeJob::authType,
_ocWizard, SLOT(setAuthType(WizardCommon::AuthType))); _ocWizard, &OwncloudWizard::setAuthType);
job->start(); job->start();
} }
@ -600,58 +600,4 @@ AccountState *OwncloudSetupWizard::applyAccountChanges()
return newState; return newState;
} }
DetermineAuthTypeJob::DetermineAuthTypeJob(AccountPtr account, QObject *parent)
: AbstractNetworkJob(account, QString(), parent)
, _redirects(0)
{
// This job implements special redirect handling to detect redirections
// to pages that are indicative of Shibboleth-using servers. Hence we
// disable the standard job redirection handling here.
_followRedirects = false;
}
void DetermineAuthTypeJob::start()
{
sendRequest("GET", account()->davUrl());
AbstractNetworkJob::start();
}
bool DetermineAuthTypeJob::finished()
{
QUrl redirection = reply()->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
qCDebug(lcWizard) << redirection.toString();
if (_redirects >= maxRedirects()) {
redirection.clear();
}
if ((reply()->error() == QNetworkReply::AuthenticationRequiredError) || redirection.isEmpty()) {
if (reply()->rawHeader("WWW-Authenticate").contains("Bearer ")) {
emit authType(WizardCommon::OAuth);
} else {
emit authType(WizardCommon::HttpCreds);
}
} else if (redirection.toString().endsWith(account()->davPath())) {
// do a new run
_redirects++;
resetTimeout();
sendRequest("GET", redirection);
return false; // don't discard
} else {
#ifndef NO_SHIBBOLETH
QRegExp shibbolethyWords("SAML|wayf");
shibbolethyWords.setCaseSensitivity(Qt::CaseInsensitive);
if (redirection.toString().contains(shibbolethyWords)) {
emit authType(WizardCommon::Shibboleth);
} else
#endif
{
// TODO: Send an error.
// eh?
emit authType(WizardCommon::HttpCreds);
}
}
return true;
}
} // namespace OCC } // namespace OCC

View file

@ -33,25 +33,6 @@ class AccountState;
class OwncloudWizard; class OwncloudWizard;
/**
* @brief The DetermineAuthTypeJob class
* @ingroup gui
*/
class DetermineAuthTypeJob : public AbstractNetworkJob
{
Q_OBJECT
public:
explicit DetermineAuthTypeJob(AccountPtr account, QObject *parent = 0);
void start() Q_DECL_OVERRIDE;
signals:
void authType(WizardCommon::AuthType);
private slots:
bool finished() Q_DECL_OVERRIDE;
private:
int _redirects;
};
/** /**
* @brief The OwncloudSetupWizard class * @brief The OwncloudSetupWizard class
* @ingroup gui * @ingroup gui

View file

@ -76,7 +76,7 @@ void OwncloudOAuthCredsPage::asyncAuthResult(OAuth::Result r, const QString &use
/* OAuth not supported (can't open browser), fallback to HTTP credentials */ /* OAuth not supported (can't open browser), fallback to HTTP credentials */
OwncloudWizard *ocWizard = qobject_cast<OwncloudWizard *>(wizard()); OwncloudWizard *ocWizard = qobject_cast<OwncloudWizard *>(wizard());
ocWizard->back(); ocWizard->back();
ocWizard->setAuthType(WizardCommon::HttpCreds); ocWizard->setAuthType(DetermineAuthTypeJob::Basic);
break; break;
} }
case OAuth::Error: case OAuth::Error:

View file

@ -40,7 +40,7 @@ OwncloudSetupPage::OwncloudSetupPage(QWidget *parent)
, _ocUser() , _ocUser()
, _authTypeKnown(false) , _authTypeKnown(false)
, _checking(false) , _checking(false)
, _authType(WizardCommon::HttpCreds) , _authType(DetermineAuthTypeJob::Basic)
, _progressIndi(new QProgressIndicator(this)) , _progressIndi(new QProgressIndicator(this))
{ {
_ui.setupUi(this); _ui.setupUi(this);
@ -201,9 +201,9 @@ bool OwncloudSetupPage::urlHasChanged()
int OwncloudSetupPage::nextId() const int OwncloudSetupPage::nextId() const
{ {
if (_authType == WizardCommon::HttpCreds) { if (_authType == DetermineAuthTypeJob::Basic) {
return WizardCommon::Page_HttpCreds; return WizardCommon::Page_HttpCreds;
} else if (_authType == WizardCommon::OAuth) { } else if (_authType == DetermineAuthTypeJob::OAuth) {
return WizardCommon::Page_OAuthCreds; return WizardCommon::Page_OAuthCreds;
} else { } else {
return WizardCommon::Page_ShibbolethCreds; return WizardCommon::Page_ShibbolethCreds;
@ -235,7 +235,7 @@ bool OwncloudSetupPage::validatePage()
} }
} }
void OwncloudSetupPage::setAuthType(WizardCommon::AuthType type) void OwncloudSetupPage::setAuthType(DetermineAuthTypeJob::AuthType type)
{ {
_authTypeKnown = true; _authTypeKnown = true;
_authType = type; _authType = type;

View file

@ -53,7 +53,7 @@ public:
QString localFolder() const; QString localFolder() const;
void setRemoteFolder(const QString &remoteFolder); void setRemoteFolder(const QString &remoteFolder);
void setMultipleFoldersExist(bool exist); void setMultipleFoldersExist(bool exist);
void setAuthType(WizardCommon::AuthType type); void setAuthType(DetermineAuthTypeJob::AuthType type);
public slots: public slots:
void setErrorString(const QString &, bool retryHTTPonly); void setErrorString(const QString &, bool retryHTTPonly);
@ -80,7 +80,7 @@ private:
bool _authTypeKnown; bool _authTypeKnown;
bool _checking; bool _checking;
bool _multipleFoldersExist; bool _multipleFoldersExist;
WizardCommon::AuthType _authType; DetermineAuthTypeJob::AuthType _authType;
QProgressIndicator *_progressIndi; QProgressIndicator *_progressIndi;
QButtonGroup *_selectiveSyncButtons; QButtonGroup *_selectiveSyncButtons;

View file

@ -167,17 +167,17 @@ void OwncloudWizard::successfulStep()
next(); next();
} }
void OwncloudWizard::setAuthType(WizardCommon::AuthType type) void OwncloudWizard::setAuthType(DetermineAuthTypeJob::AuthType type)
{ {
_setupPage->setAuthType(type); _setupPage->setAuthType(type);
#ifndef NO_SHIBBOLETH #ifndef NO_SHIBBOLETH
if (type == WizardCommon::Shibboleth) { if (type == DetermineAuthTypeJob::Shibboleth) {
_credentialsPage = _shibbolethCredsPage; _credentialsPage = _shibbolethCredsPage;
} else } else
#endif #endif
if (type == WizardCommon::OAuth) { if (type == DetermineAuthTypeJob::OAuth) {
_credentialsPage = _browserCredsPage; _credentialsPage = _browserCredsPage;
} else { } else { // try Basic auth even for "Unknown"
_credentialsPage = _httpCredsPage; _credentialsPage = _httpCredsPage;
} }
next(); next();

View file

@ -21,6 +21,7 @@
#include <QSslKey> #include <QSslKey>
#include <QSslCertificate> #include <QSslCertificate>
#include "networkjobs.h"
#include "wizard/owncloudwizardcommon.h" #include "wizard/owncloudwizardcommon.h"
#include "accountfwd.h" #include "accountfwd.h"
@ -75,7 +76,7 @@ public:
QSslCertificate _clientSslCertificate; QSslCertificate _clientSslCertificate;
public slots: public slots:
void setAuthType(WizardCommon::AuthType type); void setAuthType(DetermineAuthTypeJob::AuthType type);
void setRemoteFolder(const QString &); void setRemoteFolder(const QString &);
void appendToConfigurationLog(const QString &msg, LogType type = LogParagraph); void appendToConfigurationLog(const QString &msg, LogType type = LogParagraph);
void slotCurrentPageChanged(int); void slotCurrentPageChanged(int);

View file

@ -28,12 +28,6 @@ namespace WizardCommon {
QString subTitleTemplate(); QString subTitleTemplate();
void initErrorLabel(QLabel *errorLabel); void initErrorLabel(QLabel *errorLabel);
enum AuthType {
HttpCreds,
Shibboleth,
OAuth
};
enum SyncMode { enum SyncMode {
SelectiveMode, SelectiveMode,
BoxMode BoxMode

View file

@ -58,18 +58,20 @@ protected:
QNetworkReply *createRequest(Operation op, const QNetworkRequest &request, QIODevice *outgoingData) Q_DECL_OVERRIDE QNetworkReply *createRequest(Operation op, const QNetworkRequest &request, QIODevice *outgoingData) Q_DECL_OVERRIDE
{ {
QNetworkRequest req(request); QNetworkRequest req(request);
if (_cred && !_cred->password().isEmpty()) { if (!req.attribute(HttpCredentials::DontAddCredentialsAttribute).toBool()) {
if (_cred->isUsingOAuth()) { if (_cred && !_cred->password().isEmpty()) {
req.setRawHeader("Authorization", "Bearer " + _cred->password().toUtf8()); if (_cred->isUsingOAuth()) {
} else { req.setRawHeader("Authorization", "Bearer " + _cred->password().toUtf8());
QByteArray credHash = QByteArray(_cred->user().toUtf8() + ":" + _cred->password().toUtf8()).toBase64(); } else {
QByteArray credHash = QByteArray(_cred->user().toUtf8() + ":" + _cred->password().toUtf8()).toBase64();
req.setRawHeader("Authorization", "Basic " + credHash);
}
} else if (!request.url().password().isEmpty()) {
// Typically the requests to get or refresh the OAuth access token. The client
// credentials are put in the URL from the code making the request.
QByteArray credHash = request.url().userInfo().toUtf8().toBase64();
req.setRawHeader("Authorization", "Basic " + credHash); req.setRawHeader("Authorization", "Basic " + credHash);
} }
} else if (!request.url().password().isEmpty()) {
// Typically the requests to get or refresh the OAuth access token. The client
// credentials are put in the URL from the code making the request.
QByteArray credHash = request.url().userInfo().toUtf8().toBase64();
req.setRawHeader("Authorization", "Basic " + credHash);
} }
if (_cred && !_cred->_clientSslKey.isNull() && !_cred->_clientSslCertificate.isNull()) { if (_cred && !_cred->_clientSslKey.isNull() && !_cred->_clientSslCertificate.isNull()) {

View file

@ -19,6 +19,7 @@
#include <QMap> #include <QMap>
#include <QSslCertificate> #include <QSslCertificate>
#include <QSslKey> #include <QSslKey>
#include <QNetworkRequest>
#include "creds/abstractcredentials.h" #include "creds/abstractcredentials.h"
class QNetworkReply; class QNetworkReply;
@ -75,6 +76,9 @@ class OWNCLOUDSYNC_EXPORT HttpCredentials : public AbstractCredentials
friend class HttpCredentialsAccessManager; friend class HttpCredentialsAccessManager;
public: public:
/// Don't add credentials if this is set on a QNetworkRequest
static constexpr QNetworkRequest::Attribute DontAddCredentialsAttribute = QNetworkRequest::User;
explicit HttpCredentials(); explicit HttpCredentials();
HttpCredentials(const QString &user, const QString &password, const QSslCertificate &certificate = QSslCertificate(), const QSslKey &key = QSslKey()); HttpCredentials(const QString &user, const QString &password, const QSslCertificate &certificate = QSslCertificate(), const QSslKey &key = QSslKey());

View file

@ -36,6 +36,7 @@
#include "owncloudpropagator.h" #include "owncloudpropagator.h"
#include "creds/abstractcredentials.h" #include "creds/abstractcredentials.h"
#include "creds/httpcredentials.h"
namespace OCC { namespace OCC {
@ -47,6 +48,7 @@ Q_LOGGING_CATEGORY(lcAvatarJob, "sync.networkjob.avatar", QtInfoMsg)
Q_LOGGING_CATEGORY(lcMkColJob, "sync.networkjob.mkcol", QtInfoMsg) Q_LOGGING_CATEGORY(lcMkColJob, "sync.networkjob.mkcol", QtInfoMsg)
Q_LOGGING_CATEGORY(lcProppatchJob, "sync.networkjob.proppatch", QtInfoMsg) Q_LOGGING_CATEGORY(lcProppatchJob, "sync.networkjob.proppatch", QtInfoMsg)
Q_LOGGING_CATEGORY(lcJsonApiJob, "sync.networkjob.jsonapi", QtInfoMsg) Q_LOGGING_CATEGORY(lcJsonApiJob, "sync.networkjob.jsonapi", QtInfoMsg)
Q_LOGGING_CATEGORY(lcDetermineAuthTypeJob, "sync.networkjob.determineauthtype", QtInfoMsg)
RequestEtagJob::RequestEtagJob(AccountPtr account, const QString &path, QObject *parent) RequestEtagJob::RequestEtagJob(AccountPtr account, const QString &path, QObject *parent)
: AbstractNetworkJob(account, path, parent) : AbstractNetworkJob(account, path, parent)
@ -798,4 +800,72 @@ bool JsonApiJob::finished()
return true; return true;
} }
DetermineAuthTypeJob::DetermineAuthTypeJob(AccountPtr account, QObject *parent)
: AbstractNetworkJob(account, QString(), parent)
, _redirects(0)
{
// This job implements special redirect handling to detect redirections
// to pages that are indicative of Shibboleth-using servers. Hence we
// disable the standard job redirection handling here.
_followRedirects = false;
}
void DetermineAuthTypeJob::start()
{
send(account()->davUrl());
AbstractNetworkJob::start();
}
bool DetermineAuthTypeJob::finished()
{
QUrl redirection = reply()->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
if (_redirects >= maxRedirects()) {
redirection.clear();
}
auto authChallenge = reply()->rawHeader("WWW-Authenticate").toLower();
if (redirection.isEmpty()) {
if (authChallenge.contains("bearer ")) {
emit authType(OAuth);
} else if (!authChallenge.isEmpty()) {
emit authType(Basic);
} else {
// This is also where we end up in case of network error.
emit authType(Unknown);
}
} else if (redirection.toString().endsWith(account()->davPath())) {
// do a new run
_redirects++;
resetTimeout();
send(redirection);
qCDebug(lcDetermineAuthTypeJob()) << "Redirected to:" << redirection.toString();
return false; // don't discard
} else {
#ifndef NO_SHIBBOLETH
QRegExp shibbolethyWords("SAML|wayf");
shibbolethyWords.setCaseSensitivity(Qt::CaseInsensitive);
if (redirection.toString().contains(shibbolethyWords)) {
emit authType(Shibboleth);
} else
#endif
{
// We got redirected to an address that doesn't look like shib
// and also doesn't have the davPath. Give up.
qCWarning(lcDetermineAuthTypeJob()) << account()->davUrl()
<< "was redirected to the incompatible address"
<< redirection.toString();
emit authType(Unknown);
}
}
return true;
}
void DetermineAuthTypeJob::send(const QUrl &url)
{
QNetworkRequest req;
// Prevent HttpCredentialsAccessManager from setting an Authorization header.
req.setAttribute(HttpCredentials::DontAddCredentialsAttribute, true);
sendRequest("GET", url, req);
}
} // namespace OCC } // namespace OCC

View file

@ -330,6 +330,32 @@ private:
QList<QPair<QString, QString>> _additionalParams; QList<QPair<QString, QString>> _additionalParams;
}; };
/**
* @brief Checks with auth type to use for a server
* @ingroup libsync
*/
class OWNCLOUDSYNC_EXPORT DetermineAuthTypeJob : public AbstractNetworkJob
{
Q_OBJECT
public:
enum AuthType {
Unknown,
Basic,
OAuth,
Shibboleth
};
explicit DetermineAuthTypeJob(AccountPtr account, QObject *parent = 0);
void start() Q_DECL_OVERRIDE;
signals:
void authType(AuthType);
private slots:
bool finished() Q_DECL_OVERRIDE;
private:
void send(const QUrl &url);
int _redirects;
};
} // namespace OCC } // namespace OCC
#endif // NETWORKJOBS_H #endif // NETWORKJOBS_H