mirror of
https://github.com/nextcloud/desktop.git
synced 2024-10-27 23:17:13 +03:00
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:
parent
ed9977a2b3
commit
1c0d80c20d
13 changed files with 136 additions and 114 deletions
|
@ -22,6 +22,7 @@
|
|||
#include "creds/httpcredentialsgui.h"
|
||||
#include "theme.h"
|
||||
#include "account.h"
|
||||
#include "networkjobs.h"
|
||||
#include <QMessageBox>
|
||||
#include "asserts.h"
|
||||
|
||||
|
@ -39,15 +40,11 @@ void HttpCredentialsGui::askFromUser()
|
|||
|
||||
void HttpCredentialsGui::askFromUserAsync()
|
||||
{
|
||||
_password = QString(); // So our QNAM does not add any auth
|
||||
|
||||
// First, we will send a call to the webdav endpoint to check what kind of auth we need.
|
||||
auto reply = _account->sendRequest("GET", _account->davUrl());
|
||||
QTimer::singleShot(30 * 1000, reply, &QNetworkReply::abort);
|
||||
QObject::connect(reply, &QNetworkReply::finished, this, [this, reply] {
|
||||
reply->deleteLater();
|
||||
if (reply->rawHeader("WWW-Authenticate").contains("Bearer ")) {
|
||||
// OAuth
|
||||
// First, we will check what kind of auth we need.
|
||||
auto job = new DetermineAuthTypeJob(_account->sharedFromThis(), this);
|
||||
job->setTimeout(30 * 1000);
|
||||
QObject::connect(job, &DetermineAuthTypeJob::authType, this, [this](DetermineAuthTypeJob::AuthType type) {
|
||||
if (type == DetermineAuthTypeJob::OAuth) {
|
||||
_asyncAuth.reset(new OAuth(_account, this));
|
||||
_asyncAuth->_expectedUser = _user;
|
||||
connect(_asyncAuth.data(), &OAuth::result,
|
||||
|
@ -56,15 +53,16 @@ void HttpCredentialsGui::askFromUserAsync()
|
|||
this, &HttpCredentialsGui::authorisationLinkChanged);
|
||||
_asyncAuth->start();
|
||||
emit authorisationLinkChanged();
|
||||
} else if (reply->error() == QNetworkReply::AuthenticationRequiredError) {
|
||||
} else if (type == DetermineAuthTypeJob::Basic) {
|
||||
// Show the dialog
|
||||
// We will re-enter the event loop, so better wait the next iteration
|
||||
QMetaObject::invokeMethod(this, "showDialog", Qt::QueuedConnection);
|
||||
} else {
|
||||
// Network error?
|
||||
// Network error? Unsupported auth type?
|
||||
emit asked();
|
||||
}
|
||||
});
|
||||
job->start();
|
||||
}
|
||||
|
||||
void HttpCredentialsGui::asyncAuthResult(OAuth::Result r, const QString &user,
|
||||
|
|
|
@ -207,8 +207,8 @@ void OwncloudSetupWizard::slotOwnCloudFoundAuth(const QUrl &url, const QJsonObje
|
|||
|
||||
DetermineAuthTypeJob *job = new DetermineAuthTypeJob(_ocWizard->account(), this);
|
||||
job->setIgnoreCredentialFailure(true);
|
||||
connect(job, SIGNAL(authType(WizardCommon::AuthType)),
|
||||
_ocWizard, SLOT(setAuthType(WizardCommon::AuthType)));
|
||||
connect(job, &DetermineAuthTypeJob::authType,
|
||||
_ocWizard, &OwncloudWizard::setAuthType);
|
||||
job->start();
|
||||
}
|
||||
|
||||
|
@ -600,58 +600,4 @@ AccountState *OwncloudSetupWizard::applyAccountChanges()
|
|||
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
|
||||
|
|
|
@ -33,25 +33,6 @@ class AccountState;
|
|||
|
||||
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
|
||||
* @ingroup gui
|
||||
|
|
|
@ -76,7 +76,7 @@ void OwncloudOAuthCredsPage::asyncAuthResult(OAuth::Result r, const QString &use
|
|||
/* OAuth not supported (can't open browser), fallback to HTTP credentials */
|
||||
OwncloudWizard *ocWizard = qobject_cast<OwncloudWizard *>(wizard());
|
||||
ocWizard->back();
|
||||
ocWizard->setAuthType(WizardCommon::HttpCreds);
|
||||
ocWizard->setAuthType(DetermineAuthTypeJob::Basic);
|
||||
break;
|
||||
}
|
||||
case OAuth::Error:
|
||||
|
|
|
@ -40,7 +40,7 @@ OwncloudSetupPage::OwncloudSetupPage(QWidget *parent)
|
|||
, _ocUser()
|
||||
, _authTypeKnown(false)
|
||||
, _checking(false)
|
||||
, _authType(WizardCommon::HttpCreds)
|
||||
, _authType(DetermineAuthTypeJob::Basic)
|
||||
, _progressIndi(new QProgressIndicator(this))
|
||||
{
|
||||
_ui.setupUi(this);
|
||||
|
@ -201,9 +201,9 @@ bool OwncloudSetupPage::urlHasChanged()
|
|||
|
||||
int OwncloudSetupPage::nextId() const
|
||||
{
|
||||
if (_authType == WizardCommon::HttpCreds) {
|
||||
if (_authType == DetermineAuthTypeJob::Basic) {
|
||||
return WizardCommon::Page_HttpCreds;
|
||||
} else if (_authType == WizardCommon::OAuth) {
|
||||
} else if (_authType == DetermineAuthTypeJob::OAuth) {
|
||||
return WizardCommon::Page_OAuthCreds;
|
||||
} else {
|
||||
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;
|
||||
_authType = type;
|
||||
|
|
|
@ -53,7 +53,7 @@ public:
|
|||
QString localFolder() const;
|
||||
void setRemoteFolder(const QString &remoteFolder);
|
||||
void setMultipleFoldersExist(bool exist);
|
||||
void setAuthType(WizardCommon::AuthType type);
|
||||
void setAuthType(DetermineAuthTypeJob::AuthType type);
|
||||
|
||||
public slots:
|
||||
void setErrorString(const QString &, bool retryHTTPonly);
|
||||
|
@ -80,7 +80,7 @@ private:
|
|||
bool _authTypeKnown;
|
||||
bool _checking;
|
||||
bool _multipleFoldersExist;
|
||||
WizardCommon::AuthType _authType;
|
||||
DetermineAuthTypeJob::AuthType _authType;
|
||||
|
||||
QProgressIndicator *_progressIndi;
|
||||
QButtonGroup *_selectiveSyncButtons;
|
||||
|
|
|
@ -167,17 +167,17 @@ void OwncloudWizard::successfulStep()
|
|||
next();
|
||||
}
|
||||
|
||||
void OwncloudWizard::setAuthType(WizardCommon::AuthType type)
|
||||
void OwncloudWizard::setAuthType(DetermineAuthTypeJob::AuthType type)
|
||||
{
|
||||
_setupPage->setAuthType(type);
|
||||
#ifndef NO_SHIBBOLETH
|
||||
if (type == WizardCommon::Shibboleth) {
|
||||
if (type == DetermineAuthTypeJob::Shibboleth) {
|
||||
_credentialsPage = _shibbolethCredsPage;
|
||||
} else
|
||||
#endif
|
||||
if (type == WizardCommon::OAuth) {
|
||||
if (type == DetermineAuthTypeJob::OAuth) {
|
||||
_credentialsPage = _browserCredsPage;
|
||||
} else {
|
||||
} else { // try Basic auth even for "Unknown"
|
||||
_credentialsPage = _httpCredsPage;
|
||||
}
|
||||
next();
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
#include <QSslKey>
|
||||
#include <QSslCertificate>
|
||||
|
||||
#include "networkjobs.h"
|
||||
#include "wizard/owncloudwizardcommon.h"
|
||||
#include "accountfwd.h"
|
||||
|
||||
|
@ -75,7 +76,7 @@ public:
|
|||
QSslCertificate _clientSslCertificate;
|
||||
|
||||
public slots:
|
||||
void setAuthType(WizardCommon::AuthType type);
|
||||
void setAuthType(DetermineAuthTypeJob::AuthType type);
|
||||
void setRemoteFolder(const QString &);
|
||||
void appendToConfigurationLog(const QString &msg, LogType type = LogParagraph);
|
||||
void slotCurrentPageChanged(int);
|
||||
|
|
|
@ -28,12 +28,6 @@ namespace WizardCommon {
|
|||
QString subTitleTemplate();
|
||||
void initErrorLabel(QLabel *errorLabel);
|
||||
|
||||
enum AuthType {
|
||||
HttpCreds,
|
||||
Shibboleth,
|
||||
OAuth
|
||||
};
|
||||
|
||||
enum SyncMode {
|
||||
SelectiveMode,
|
||||
BoxMode
|
||||
|
|
|
@ -58,18 +58,20 @@ protected:
|
|||
QNetworkReply *createRequest(Operation op, const QNetworkRequest &request, QIODevice *outgoingData) Q_DECL_OVERRIDE
|
||||
{
|
||||
QNetworkRequest req(request);
|
||||
if (_cred && !_cred->password().isEmpty()) {
|
||||
if (_cred->isUsingOAuth()) {
|
||||
req.setRawHeader("Authorization", "Bearer " + _cred->password().toUtf8());
|
||||
} else {
|
||||
QByteArray credHash = QByteArray(_cred->user().toUtf8() + ":" + _cred->password().toUtf8()).toBase64();
|
||||
if (!req.attribute(HttpCredentials::DontAddCredentialsAttribute).toBool()) {
|
||||
if (_cred && !_cred->password().isEmpty()) {
|
||||
if (_cred->isUsingOAuth()) {
|
||||
req.setRawHeader("Authorization", "Bearer " + _cred->password().toUtf8());
|
||||
} 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);
|
||||
}
|
||||
} 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()) {
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
#include <QMap>
|
||||
#include <QSslCertificate>
|
||||
#include <QSslKey>
|
||||
#include <QNetworkRequest>
|
||||
#include "creds/abstractcredentials.h"
|
||||
|
||||
class QNetworkReply;
|
||||
|
@ -75,6 +76,9 @@ class OWNCLOUDSYNC_EXPORT HttpCredentials : public AbstractCredentials
|
|||
friend class HttpCredentialsAccessManager;
|
||||
|
||||
public:
|
||||
/// Don't add credentials if this is set on a QNetworkRequest
|
||||
static constexpr QNetworkRequest::Attribute DontAddCredentialsAttribute = QNetworkRequest::User;
|
||||
|
||||
explicit HttpCredentials();
|
||||
HttpCredentials(const QString &user, const QString &password, const QSslCertificate &certificate = QSslCertificate(), const QSslKey &key = QSslKey());
|
||||
|
||||
|
|
|
@ -36,6 +36,7 @@
|
|||
#include "owncloudpropagator.h"
|
||||
|
||||
#include "creds/abstractcredentials.h"
|
||||
#include "creds/httpcredentials.h"
|
||||
|
||||
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(lcProppatchJob, "sync.networkjob.proppatch", 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)
|
||||
: AbstractNetworkJob(account, path, parent)
|
||||
|
@ -798,4 +800,72 @@ bool JsonApiJob::finished()
|
|||
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
|
||||
|
|
|
@ -330,6 +330,32 @@ private:
|
|||
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
|
||||
|
||||
#endif // NETWORKJOBS_H
|
||||
|
|
Loading…
Reference in a new issue