mirror of
https://github.com/nextcloud/desktop.git
synced 2025-10-26 11:17:43 +00:00
Support for openid connect
This commit is contained in:
parent
47e42441f4
commit
112e78ba94
@ -72,6 +72,8 @@ void HttpCredentialsGui::performOAuthProcess()
|
||||
this, &HttpCredentialsGui::asyncAuthResult);
|
||||
connect(_asyncAuth.data(), &OAuth::destroyed,
|
||||
this, &HttpCredentialsGui::authorisationLinkChanged);
|
||||
connect(_asyncAuth.data(), &OAuth::authorisationLinkChanged,
|
||||
this, &HttpCredentialsGui::authorisationLinkChanged);
|
||||
_asyncAuth->start();
|
||||
emit authorisationLinkChanged();
|
||||
}
|
||||
|
||||
@ -23,6 +23,7 @@
|
||||
#include "theme.h"
|
||||
#include "networkjobs.h"
|
||||
#include "creds/httpcredentials.h"
|
||||
#include <QRandomGenerator>
|
||||
|
||||
namespace OCC {
|
||||
|
||||
@ -39,7 +40,7 @@ static void httpReplyAndClose(QTcpSocket *socket, const char *code, const char *
|
||||
return; // socket can have been deleted if the browser was closed
|
||||
socket->write("HTTP/1.1 ");
|
||||
socket->write(code);
|
||||
socket->write("\r\nContent-Type: text/html\r\nConnection: close\r\nContent-Length: ");
|
||||
socket->write("\r\nContent-Type: text/html; charset=utf-8\r\nConnection: close\r\nContent-Length: ");
|
||||
socket->write(QByteArray::number(qstrlen(html)));
|
||||
if (moreHeaders) {
|
||||
socket->write("\r\n");
|
||||
@ -61,8 +62,14 @@ void OAuth::start()
|
||||
return;
|
||||
}
|
||||
|
||||
if (!openBrowser())
|
||||
return;
|
||||
quint32 buffer[24];
|
||||
QRandomGenerator::global()->fillRange(buffer);
|
||||
_pkceCodeVerifier = QByteArray(reinterpret_cast<char*>(buffer), sizeof(buffer)).toBase64(QByteArray::Base64UrlEncoding);
|
||||
Q_ASSERT(_pkceCodeVerifier.size() == 128);
|
||||
|
||||
fetchWellKnown();
|
||||
|
||||
openBrowser();
|
||||
|
||||
QObject::connect(&_server, &QTcpServer::newConnection, this, [this] {
|
||||
while (QPointer<QTcpSocket> socket = _server.nextPendingConnection()) {
|
||||
@ -71,7 +78,7 @@ void OAuth::start()
|
||||
QByteArray peek = socket->peek(qMin(socket->bytesAvailable(), 4000LL)); //The code should always be within the first 4K
|
||||
if (peek.indexOf('\n') < 0)
|
||||
return; // wait until we find a \n
|
||||
QRegExp rx("^GET /\\?code=([a-zA-Z0-9]+)[& ]"); // Match a /?code=... URL
|
||||
QRegExp rx("^GET /\\?code=([a-zA-Z0-9_-]+)[& ]"); // Match a /?code=... URL
|
||||
if (rx.indexIn(peek) != 0) {
|
||||
httpReplyAndClose(socket, "404 Not Found", "<html><head><title>404 Not Found</title></head><body><center><h1>404 Not Found</h1></center></body></html>");
|
||||
return;
|
||||
@ -79,7 +86,10 @@ void OAuth::start()
|
||||
|
||||
QString code = rx.cap(1); // The 'code' is the first capture of the regexp
|
||||
|
||||
QUrl requestToken = Utility::concatUrlPath(_account->url().toString(), QLatin1String("/index.php/apps/oauth2/api/v1/token"));
|
||||
QUrl requestToken = _tokenEndpoint.isValid()
|
||||
? _tokenEndpoint
|
||||
: Utility::concatUrlPath(_account->url(), QLatin1String("/index.php/apps/oauth2/api/v1/token"));
|
||||
|
||||
QNetworkRequest req;
|
||||
req.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
|
||||
|
||||
@ -91,10 +101,9 @@ void OAuth::start()
|
||||
|
||||
auto requestBody = new QBuffer;
|
||||
QUrlQuery arguments(QString(
|
||||
"grant_type=authorization_code&code=%1&redirect_uri=http://localhost:%2")
|
||||
.arg(code, QString::number(_server.serverPort())));
|
||||
"grant_type=authorization_code&code=%1&redirect_uri=http://localhost:%2&code_verifier=%3&scope=openid offline_access")
|
||||
.arg(code, QString::number(_server.serverPort()), _pkceCodeVerifier));
|
||||
requestBody->setData(arguments.query(QUrl::FullyEncoded).toLatin1());
|
||||
|
||||
auto job = _account->sendRequest("POST", requestToken, req, requestBody);
|
||||
job->setTimeout(qMin(30 * 1000ll, job->timeoutMsec()));
|
||||
QObject::connect(job, &SimpleNetworkJob::finishedSignal, this, [this, socket](QNetworkReply *reply) {
|
||||
@ -110,7 +119,9 @@ void OAuth::start()
|
||||
|| jsonData.isEmpty() || json.isEmpty() || refreshToken.isEmpty() || accessToken.isEmpty()
|
||||
|| json["token_type"].toString() != QLatin1String("Bearer")) {
|
||||
QString errorReason;
|
||||
QString errorFromJson = json["error"].toString();
|
||||
QString errorFromJson = json["error_description"].toString();
|
||||
if (errorFromJson.isEmpty())
|
||||
QString errorFromJson = json["error"].toString();
|
||||
if (!errorFromJson.isEmpty()) {
|
||||
errorReason = tr("Error returned from the server: <em>%1</em>")
|
||||
.arg(errorFromJson.toHtmlEscaped());
|
||||
@ -129,59 +140,130 @@ void OAuth::start()
|
||||
} else {
|
||||
errorReason = tr("The reply from the server did not contain all expected fields");
|
||||
}
|
||||
qCWarning(lcOauth) << "Error when getting the accessToken" << json << errorReason;
|
||||
qCWarning(lcOauth) << "Error when getting the accessToken" << jsonData << errorReason;
|
||||
httpReplyAndClose(socket, "500 Internal Server Error",
|
||||
tr("<h1>Login Error</h1><p>%1</p>").arg(errorReason).toUtf8().constData());
|
||||
emit result(Error);
|
||||
return;
|
||||
}
|
||||
if (!_expectedUser.isNull() && user != _expectedUser) {
|
||||
// Connected with the wrong user
|
||||
QString message = tr("<h1>Wrong user</h1>"
|
||||
"<p>You logged-in with user <em>%1</em>, but must login with user <em>%2</em>.<br>"
|
||||
"Please log out of %3 in another tab, then <a href='%4'>click here</a> "
|
||||
"and log in as user %2</p>")
|
||||
.arg(user, _expectedUser, Theme::instance()->appNameGUI(),
|
||||
authorisationLink().toString(QUrl::FullyEncoded));
|
||||
httpReplyAndClose(socket, "200 OK", message.toUtf8().constData());
|
||||
// We are still listening on the socket so we will get the new connection
|
||||
if (!user.isEmpty()) {
|
||||
finalize(socket, accessToken, refreshToken, user, messageUrl);
|
||||
return;
|
||||
}
|
||||
const char *loginSuccessfullHtml = "<h1>Login Successful</h1><p>You can close this window.</p>";
|
||||
if (messageUrl.isValid()) {
|
||||
httpReplyAndClose(socket, "303 See Other", loginSuccessfullHtml,
|
||||
QByteArray("Location: " + messageUrl.toEncoded()).constData());
|
||||
} else {
|
||||
httpReplyAndClose(socket, "200 OK", loginSuccessfullHtml);
|
||||
}
|
||||
emit result(LoggedIn, user, accessToken, refreshToken);
|
||||
// If the reply don't contains the user id, we must do another call to query it
|
||||
JsonApiJob *job = new JsonApiJob(_account->sharedFromThis(), QLatin1String("ocs/v1.php/cloud/user"), this);
|
||||
job->setTimeout(qMin(30 * 1000ll, job->timeoutMsec()));
|
||||
QNetworkRequest req;
|
||||
// We are not connected yet so we need to handle the authentication manually
|
||||
req.setRawHeader("Authorization", "Bearer " + accessToken.toUtf8());
|
||||
// We just added the Authorization header, don't let HttpCredentialsAccessManager tamper with it
|
||||
req.setAttribute(HttpCredentials::DontAddCredentialsAttribute, true);
|
||||
job->startWithRequest(req);
|
||||
QObject::connect(job, &JsonApiJob::jsonReceived, this, [=](const QJsonDocument &json) {
|
||||
QString user = json.object().value("ocs").toObject().value("data").toObject().value("id").toString();
|
||||
finalize(socket, accessToken, refreshToken, user, messageUrl);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void OAuth::finalize(QPointer<QTcpSocket> socket, const QString &accessToken,
|
||||
const QString &refreshToken, const QString &user, const QUrl &messageUrl) {
|
||||
if (!_expectedUser.isNull() && user != _expectedUser) {
|
||||
// Connected with the wrong user
|
||||
QString message = tr("<h1>Wrong user</h1>"
|
||||
"<p>You logged-in with user <em>%1</em>, but must login with user <em>%2</em>.<br>"
|
||||
"Please log out of %3 in another tab, then <a href='%4'>click here</a> "
|
||||
"and log in as user %2</p>")
|
||||
.arg(user, _expectedUser, Theme::instance()->appNameGUI(),
|
||||
authorisationLink().toString(QUrl::FullyEncoded));
|
||||
httpReplyAndClose(socket, "200 OK", message.toUtf8().constData());
|
||||
// We are still listening on the socket so we will get the new connection
|
||||
return;
|
||||
}
|
||||
const char *loginSuccessfullHtml = "<h1>Login Successful</h1><p>You can close this window.</p>";
|
||||
if (messageUrl.isValid()) {
|
||||
httpReplyAndClose(socket, "303 See Other", loginSuccessfullHtml,
|
||||
QByteArray("Location: " + messageUrl.toEncoded()).constData());
|
||||
} else {
|
||||
httpReplyAndClose(socket, "200 OK", loginSuccessfullHtml);
|
||||
}
|
||||
emit result(LoggedIn, user, accessToken, refreshToken);
|
||||
}
|
||||
|
||||
QUrl OAuth::authorisationLink() const
|
||||
{
|
||||
Q_ASSERT(_server.isListening());
|
||||
QUrlQuery query;
|
||||
QByteArray code_challenge = QCryptographicHash::hash(_pkceCodeVerifier, QCryptographicHash::Sha256)
|
||||
.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals);
|
||||
query.setQueryItems({ { QLatin1String("response_type"), QLatin1String("code") },
|
||||
{ QLatin1String("client_id"), Theme::instance()->oauthClientId() },
|
||||
{ QLatin1String("redirect_uri"), QLatin1String("http://localhost:") + QString::number(_server.serverPort()) } });
|
||||
{ QLatin1String("redirect_uri"), QLatin1String("http://localhost:") + QString::number(_server.serverPort()) },
|
||||
{ QLatin1String("code_challenge"), QString::fromLatin1(code_challenge) },
|
||||
{ QLatin1String("code_challenge_method"), QLatin1String("S256")},
|
||||
{ QLatin1String("scope"), QLatin1String("openid offline_access") },
|
||||
{ QLatin1String("prompt"), QLatin1String("consent")}
|
||||
});
|
||||
if (!_expectedUser.isNull())
|
||||
query.addQueryItem("user", _expectedUser);
|
||||
QUrl url = Utility::concatUrlPath(_account->url(), QLatin1String("/index.php/apps/oauth2/authorize"), query);
|
||||
QUrl url = _authEndpoint.isValid()
|
||||
? Utility::concatUrlPath(_authEndpoint, {}, query)
|
||||
: Utility::concatUrlPath(_account->url(), QLatin1String("/index.php/apps/oauth2/authorize"), query);
|
||||
return url;
|
||||
}
|
||||
|
||||
bool OAuth::openBrowser()
|
||||
void OAuth::authorisationLinkAsync(std::function<void (const QUrl &)> callback) const
|
||||
{
|
||||
if (!QDesktopServices::openUrl(authorisationLink())) {
|
||||
// We cannot open the browser, then we claim we don't support OAuth.
|
||||
emit result(NotSupported, QString());
|
||||
return false;
|
||||
if (_wellKnownFinished) {
|
||||
callback(authorisationLink());
|
||||
} else {
|
||||
connect(this, &OAuth::authorisationLinkChanged, callback);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void OAuth::fetchWellKnown()
|
||||
{
|
||||
QUrl wellKnownUrl = Utility::concatUrlPath(_account->url().toString(), QLatin1String("/.well-known/openid-configuration"));
|
||||
QNetworkRequest req;
|
||||
auto job = _account->sendRequest("GET", wellKnownUrl);
|
||||
job->setTimeout(qMin(30 * 1000ll, job->timeoutMsec()));
|
||||
QObject::connect(job, &SimpleNetworkJob::finishedSignal, this, [this](QNetworkReply *reply) {
|
||||
_wellKnownFinished = true;
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
// Most likely the file does not exist, default to the normal endpoint
|
||||
emit this->authorisationLinkChanged(authorisationLink());
|
||||
return;
|
||||
}
|
||||
auto jsonData = reply->readAll();
|
||||
QJsonParseError jsonParseError;
|
||||
QJsonObject json = QJsonDocument::fromJson(jsonData, &jsonParseError).object();
|
||||
|
||||
if (jsonParseError.error == QJsonParseError::NoError) {
|
||||
QString authEp = json["authorization_endpoint"].toString();
|
||||
if (!authEp.isEmpty())
|
||||
this->_authEndpoint = authEp;
|
||||
QString tokenEp = json["token_endpoint"].toString();
|
||||
if (!tokenEp.isEmpty())
|
||||
this->_tokenEndpoint = tokenEp;
|
||||
} else {
|
||||
qCWarning(lcOauth) << "Json parse error in well-known: " << jsonParseError.errorString();
|
||||
}
|
||||
|
||||
emit this->authorisationLinkChanged(authorisationLink());
|
||||
});
|
||||
}
|
||||
|
||||
void OAuth::openBrowser()
|
||||
{
|
||||
authorisationLinkAsync([this](const QUrl &link) {
|
||||
if (!QDesktopServices::openUrl(link)) {
|
||||
// We cannot open the browser, then we claim we don't support OAuth.
|
||||
emit result(NotSupported, QString());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace OCC
|
||||
|
||||
@ -27,15 +27,20 @@ namespace OCC {
|
||||
*
|
||||
* --> start()
|
||||
* |
|
||||
* +----> openBrowser() open the browser to the login page, redirects to http://localhost:xxx
|
||||
* +----> fetchWellKnown() query the ".well-known/openid-configuration" endpoint
|
||||
* |
|
||||
* +----> openBrowser() open the browser to the login page after fetchWellKnown finished.
|
||||
* | Then the browser will redirect to http://localhost:xxx
|
||||
* |
|
||||
* +----> _server starts listening on a TCP port waiting for an HTTP request with a 'code'
|
||||
* |
|
||||
* v
|
||||
* request the access_token and the refresh_token via 'apps/oauth2/api/v1/token'
|
||||
* |
|
||||
* v
|
||||
* emit result(...)
|
||||
* +-> Request the user_id is not present
|
||||
* | |
|
||||
* v v
|
||||
* finalize(...): emit result(...)
|
||||
*
|
||||
*/
|
||||
class OAuth : public QObject
|
||||
@ -54,8 +59,14 @@ public:
|
||||
Error };
|
||||
Q_ENUM(Result);
|
||||
void start();
|
||||
bool openBrowser();
|
||||
void openBrowser();
|
||||
QUrl authorisationLink() const;
|
||||
/**
|
||||
* Call the callback when the call to the well-known endpoint finishes.
|
||||
* (or immediatly if it is ready)
|
||||
* The callback will not be called if this object gets destroyed
|
||||
*/
|
||||
void authorisationLinkAsync(std::function<void(const QUrl&)> callback) const;
|
||||
|
||||
signals:
|
||||
/**
|
||||
@ -64,9 +75,23 @@ signals:
|
||||
*/
|
||||
void result(OAuth::Result result, const QString &user = QString(), const QString &token = QString(), const QString &refreshToken = QString());
|
||||
|
||||
/**
|
||||
* emitted when the call to the well-known endpoint is finished
|
||||
*/
|
||||
void authorisationLinkChanged(const QUrl &);
|
||||
|
||||
private:
|
||||
Account *_account;
|
||||
|
||||
void fetchWellKnown();
|
||||
void finalize(QPointer<QTcpSocket> socket, const QString &accessToken,
|
||||
const QString &refreshToken, const QString &userId, const QUrl &messageUrl);
|
||||
|
||||
Account* _account;
|
||||
QTcpServer _server;
|
||||
bool _wellKnownFinished = false;
|
||||
QUrl _authEndpoint;
|
||||
QUrl _tokenEndpoint;
|
||||
QByteArray _pkceCodeVerifier;
|
||||
|
||||
public:
|
||||
QString _expectedUser;
|
||||
|
||||
@ -54,8 +54,11 @@ OwncloudOAuthCredsPage::OwncloudOAuthCredsPage()
|
||||
QObject::connect(_ui.openLinkButton, &QWidget::customContextMenuRequested, [this](const QPoint &pos) {
|
||||
auto menu = new QMenu(_ui.openLinkButton);
|
||||
menu->addAction(tr("Copy link to clipboard"), this, [this] {
|
||||
if (_asyncAuth)
|
||||
QApplication::clipboard()->setText(_asyncAuth->authorisationLink().toString(QUrl::FullyEncoded));
|
||||
if (_asyncAuth) {
|
||||
_asyncAuth->authorisationLinkAsync([](const QUrl &link) {
|
||||
QApplication::clipboard()->setText(link.toString(QUrl::FullyEncoded));
|
||||
});
|
||||
}
|
||||
});
|
||||
menu->setAttribute(Qt::WA_DeleteOnClose);
|
||||
menu->popup(_ui.openLinkButton->mapToGlobal(pos));
|
||||
|
||||
@ -412,47 +412,64 @@ bool HttpCredentials::refreshAccessToken()
|
||||
if (_refreshToken.isEmpty())
|
||||
return false;
|
||||
|
||||
QUrl requestToken = Utility::concatUrlPath(_account->url(), QLatin1String("/index.php/apps/oauth2/api/v1/token"));
|
||||
QUrl wellKnownUrl = Utility::concatUrlPath(_account->url().toString(), QLatin1String("/.well-known/openid-configuration"));
|
||||
QNetworkRequest req;
|
||||
req.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
|
||||
|
||||
QString basicAuth = QString("%1:%2").arg(
|
||||
Theme::instance()->oauthClientId(), Theme::instance()->oauthClientSecret());
|
||||
req.setRawHeader("Authorization", "Basic " + basicAuth.toUtf8().toBase64());
|
||||
req.setAttribute(HttpCredentials::DontAddCredentialsAttribute, true);
|
||||
|
||||
auto requestBody = new QBuffer;
|
||||
QUrlQuery arguments(QString("grant_type=refresh_token&refresh_token=%1").arg(_refreshToken));
|
||||
requestBody->setData(arguments.query(QUrl::FullyEncoded).toLatin1());
|
||||
|
||||
auto job = _account->sendRequest("POST", requestToken, req, requestBody);
|
||||
auto job = _account->sendRequest("GET", wellKnownUrl);
|
||||
job->setTimeout(qMin(30 * 1000ll, job->timeoutMsec()));
|
||||
QObject::connect(job, &SimpleNetworkJob::finishedSignal, this, [this](QNetworkReply *reply) {
|
||||
auto jsonData = reply->readAll();
|
||||
QJsonParseError jsonParseError;
|
||||
QJsonObject json = QJsonDocument::fromJson(jsonData, &jsonParseError).object();
|
||||
QString accessToken = json["access_token"].toString();
|
||||
if (jsonParseError.error != QJsonParseError::NoError || json.isEmpty()) {
|
||||
// Invalid or empty JSON: Network error maybe?
|
||||
qCWarning(lcHttpCredentials) << "Error while refreshing the token" << reply->errorString() << jsonData << jsonParseError.errorString();
|
||||
} else if (accessToken.isEmpty()) {
|
||||
// If the json was valid, but the reply did not contain an access token, the token
|
||||
// is considered expired. (Usually the HTTP reply code is 400)
|
||||
qCDebug(lcHttpCredentials) << "Expired refresh token. Logging out";
|
||||
_refreshToken.clear();
|
||||
} else {
|
||||
_ready = true;
|
||||
_password = accessToken;
|
||||
_refreshToken = json["refresh_token"].toString();
|
||||
persist();
|
||||
QUrl requestTokenUrl = Utility::concatUrlPath(_account->url(), QLatin1String("/index.php/apps/oauth2/api/v1/token"));
|
||||
if (reply->error() == QNetworkReply::NoError) {
|
||||
auto jsonData = reply->readAll();
|
||||
QJsonParseError jsonParseError;
|
||||
QJsonObject json = QJsonDocument::fromJson(jsonData, &jsonParseError).object();
|
||||
if (jsonParseError.error == QJsonParseError::NoError) {
|
||||
QString tokenEp = json["token_endpoint"].toString();
|
||||
if (!tokenEp.isEmpty())
|
||||
requestTokenUrl = tokenEp;
|
||||
}
|
||||
}
|
||||
_isRenewingOAuthToken = false;
|
||||
for (const auto &job : _retryQueue) {
|
||||
if (job)
|
||||
job->retry();
|
||||
}
|
||||
_retryQueue.clear();
|
||||
emit fetched();
|
||||
|
||||
QNetworkRequest req;
|
||||
req.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
|
||||
|
||||
QString basicAuth = QString("%1:%2").arg(
|
||||
Theme::instance()->oauthClientId(), Theme::instance()->oauthClientSecret());
|
||||
req.setRawHeader("Authorization", "Basic " + basicAuth.toUtf8().toBase64());
|
||||
req.setAttribute(HttpCredentials::DontAddCredentialsAttribute, true);
|
||||
|
||||
auto requestBody = new QBuffer;
|
||||
QUrlQuery arguments(QString("grant_type=refresh_token&refresh_token=%1").arg(_refreshToken));
|
||||
requestBody->setData(arguments.query(QUrl::FullyEncoded).toLatin1());
|
||||
|
||||
auto job = _account->sendRequest("POST", requestTokenUrl, req, requestBody);
|
||||
job->setTimeout(qMin(30 * 1000ll, job->timeoutMsec()));
|
||||
QObject::connect(job, &SimpleNetworkJob::finishedSignal, this, [this](QNetworkReply *reply) {
|
||||
auto jsonData = reply->readAll();
|
||||
QJsonParseError jsonParseError;
|
||||
QJsonObject json = QJsonDocument::fromJson(jsonData, &jsonParseError).object();
|
||||
QString accessToken = json["access_token"].toString();
|
||||
if (jsonParseError.error != QJsonParseError::NoError || json.isEmpty()) {
|
||||
// Invalid or empty JSON: Network error maybe?
|
||||
qCWarning(lcHttpCredentials) << "Error while refreshing the token" << reply->errorString() << jsonData << jsonParseError.errorString();
|
||||
} else if (accessToken.isEmpty()) {
|
||||
// If the json was valid, but the reply did not contain an access token, the token
|
||||
// is considered expired. (Usually the HTTP reply code is 400)
|
||||
qCDebug(lcHttpCredentials) << "Expired refresh token. Logging out";
|
||||
_refreshToken.clear();
|
||||
} else {
|
||||
_ready = true;
|
||||
_password = accessToken;
|
||||
_refreshToken = json["refresh_token"].toString();
|
||||
persist();
|
||||
}
|
||||
_isRenewingOAuthToken = false;
|
||||
for (const auto &job : _retryQueue) {
|
||||
if (job)
|
||||
job->retry();
|
||||
}
|
||||
_retryQueue.clear();
|
||||
emit fetched();
|
||||
});
|
||||
});
|
||||
_isRenewingOAuthToken = true;
|
||||
return true;
|
||||
|
||||
@ -808,7 +808,11 @@ void JsonApiJob::addQueryParams(const QUrlQuery ¶ms)
|
||||
|
||||
void JsonApiJob::start()
|
||||
{
|
||||
QNetworkRequest req;
|
||||
startWithRequest(QNetworkRequest());
|
||||
}
|
||||
|
||||
void OCC::JsonApiJob::startWithRequest(QNetworkRequest req)
|
||||
{
|
||||
req.setRawHeader("OCS-APIREQUEST", "true");
|
||||
auto query = _additionalParams;
|
||||
query.addQueryItem(QLatin1String("format"), QLatin1String("json"));
|
||||
|
||||
@ -358,6 +358,10 @@ public:
|
||||
|
||||
public slots:
|
||||
void start() Q_DECL_OVERRIDE;
|
||||
/**
|
||||
* Start which allow to specify a request that might contains already headers or attributes
|
||||
*/
|
||||
void startWithRequest(QNetworkRequest request);
|
||||
|
||||
protected:
|
||||
bool finished() Q_DECL_OVERRIDE;
|
||||
|
||||
@ -132,7 +132,9 @@ public:
|
||||
account->setUrl(sOAuthTestServer);
|
||||
account->setCredentials(new FakeCredentials{fakeQnam});
|
||||
fakeQnam->setParent(this);
|
||||
fakeQnam->setOverride([this](QNetworkAccessManager::Operation op, const QNetworkRequest &req, QIODevice *device) {
|
||||
fakeQnam->setOverride([this](QNetworkAccessManager::Operation op, const QNetworkRequest &req, QIODevice *device) {
|
||||
if (req.url().path().endsWith(".well-known/openid-configuration"))
|
||||
return this->wellKnownReply(op, req);
|
||||
ASSERT(device);
|
||||
ASSERT(device->bytesAvailable()>0); // OAuth2 always sends around POST data.
|
||||
return this->tokenReply(op, req);
|
||||
@ -187,6 +189,11 @@ public:
|
||||
return new FakePostReply(op, req, std::move(payload), fakeQnam);
|
||||
}
|
||||
|
||||
virtual QNetworkReply *wellKnownReply(QNetworkAccessManager::Operation op, const QNetworkRequest &req)
|
||||
{
|
||||
return new FakeErrorReply(op, req, fakeQnam, 404);
|
||||
}
|
||||
|
||||
virtual QByteArray tokenReplyPayload() const {
|
||||
QJsonDocument jsondata(QJsonObject{
|
||||
{ "access_token", "123" },
|
||||
@ -331,6 +338,39 @@ private slots:
|
||||
} test;
|
||||
test.test();
|
||||
}
|
||||
|
||||
void testWellKnown() {
|
||||
struct Test : OAuthTestCase {
|
||||
int redirectsDone = 0;
|
||||
QNetworkReply * wellKnownReply(QNetworkAccessManager::Operation op, const QNetworkRequest & req) override {
|
||||
ASSERT(op == QNetworkAccessManager::GetOperation);
|
||||
QJsonDocument jsondata(QJsonObject{
|
||||
{ "authorization_endpoint", QJsonValue(
|
||||
"oauthtest://openidserver" + sOAuthTestServer.path() + "/index.php/apps/oauth2/authorize") },
|
||||
{ "token_endpoint" , "oauthtest://openidserver/token_endpoint" }
|
||||
});
|
||||
return new FakePayloadReply(op, req, jsondata.toJson(), fakeQnam);
|
||||
}
|
||||
|
||||
void openBrowserHook(const QUrl & url) override {
|
||||
ASSERT(url.host() == "openidserver");
|
||||
QUrl url2 = url;
|
||||
url2.setHost(sOAuthTestServer.host());
|
||||
OAuthTestCase::openBrowserHook(url2);
|
||||
}
|
||||
|
||||
QNetworkReply *tokenReply(QNetworkAccessManager::Operation op, const QNetworkRequest & request) override
|
||||
{
|
||||
ASSERT(browserReply);
|
||||
ASSERT(request.url().toString().startsWith("oauthtest://openidserver/token_endpoint"));
|
||||
auto req = request;
|
||||
req.setUrl(request.url().toString().replace("oauthtest://openidserver/token_endpoint",
|
||||
sOAuthTestServer.toString() + "/index.php/apps/oauth2/api/v1/token"));
|
||||
return OAuthTestCase::tokenReply(op, req);
|
||||
}
|
||||
} test;
|
||||
test.test();
|
||||
}
|
||||
};
|
||||
|
||||
QTEST_GUILESS_MAIN(TestOAuth)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user