Merge pull request #5039 from nextcloud/bugfix/checkTokenForEditLocallyRequests

Bugfix/check token for edit locally requests
This commit is contained in:
Matthieu Gallien 2022-10-17 11:13:03 +02:00 committed by GitHub
commit 5baa254f3d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 190 additions and 99 deletions

View file

@ -61,6 +61,7 @@
#include <QMessageBox>
#include <QDesktopServices>
#include <QGuiApplication>
#include <QUrlQuery>
class QSocket;
@ -764,8 +765,16 @@ void Application::handleEditLocally(const QUrl &url) const
// for a sample URL "nc://open/admin@nextcloud.lan:8080/Photos/lovely.jpg", QUrl::path would return "admin@nextcloud.lan:8080/Photos/lovely.jpg"
const auto accountDisplayName = pathSplit.takeFirst();
const auto fileRemotePath = pathSplit.join('/');
const auto urlQuery = QUrlQuery{url};
FolderMan::instance()->editFileLocally(accountDisplayName, fileRemotePath);
auto token = QString{};
if (urlQuery.hasQueryItem(QStringLiteral("token"))) {
token = urlQuery.queryItemValue(QStringLiteral("token"));
} else {
qCWarning(lcApplication) << "Invalid URL for file local editing: missing token";
}
FolderMan::instance()->editFileLocally(accountDisplayName, fileRemotePath, token);
}
QString substLang(const QString &lang)

View file

@ -1422,7 +1422,7 @@ void FolderMan::setDirtyNetworkLimits()
}
}
void FolderMan::editFileLocally(const QString &accountDisplayName, const QString &relPath)
void FolderMan::editFileLocally(const QString &accountDisplayName, const QString &relPath, const QString &token)
{
const auto showError = [this](const OCC::AccountStatePtr accountState, const QString &errorMessage, const QString &subject) {
if (accountState && accountState->account()) {
@ -1447,6 +1447,12 @@ void FolderMan::editFileLocally(const QString &accountDisplayName, const QString
messageBox->raise();
};
if (token.isEmpty()) {
qCWarning(lcFolderMan) << "Edit locally request is missing a valid token. Impossible to open the file.";
showError({}, tr("Edit locally request is not valid. Opening the file is forbidden."), accountDisplayName);
return;
}
const auto accountFound = AccountManager::instance()->account(accountDisplayName);
if (!accountFound) {
@ -1488,23 +1494,38 @@ void FolderMan::editFileLocally(const QString &accountDisplayName, const QString
showError(accountFound, tr("Could not find a file for local editing. Make sure its path is valid and it is synced locally."), relPath);
return;
}
folderForFile->startSync();
_localFileEditingSyncFinishedConnections.insert(localFilePath, QObject::connect(folderForFile, &Folder::syncFinished, this,
[this, localFilePath](const OCC::SyncResult &result) {
Q_UNUSED(result);
const auto foundConnectionIt = _localFileEditingSyncFinishedConnections.find(localFilePath);
if (foundConnectionIt != std::end(_localFileEditingSyncFinishedConnections) && foundConnectionIt.value()) {
QObject::disconnect(foundConnectionIt.value());
_localFileEditingSyncFinishedConnections.erase(foundConnectionIt);
}
// In case the VFS mode is enabled and a file is not yet hydrated, we must call QDesktopServices::openUrl
// from a separate thread, or, there will be a freeze. To avoid searching for a specific folder and checking
// if the VFS is enabled - we just always call it from a separate thread.
QtConcurrent::run([localFilePath]() {
QDesktopServices::openUrl(QUrl::fromLocalFile(localFilePath));
const auto checkTokenForEditLocally = new SimpleApiJob(accountFound->account(), QStringLiteral("/ocs/v2.php/apps/files/api/v1/openlocaleditor/%1").arg(token));
checkTokenForEditLocally->setVerb(SimpleApiJob::Verb::Post);
checkTokenForEditLocally->setBody(QByteArray{"path=/"}.append(relPath.toUtf8()));
connect(checkTokenForEditLocally, &SimpleApiJob::resultReceived, checkTokenForEditLocally, [this, folderForFile, localFilePath, showError, accountFound, relPath] (int statusCode) {
constexpr auto HTTP_OK_CODE = 200;
if (statusCode != HTTP_OK_CODE) {
Systray::instance()->destroyEditFileLocallyLoadingDialog();
});
}));
showError(accountFound, tr("Could not validate the request to open a file from server."), relPath);
qCInfo(lcFolderMan()) << "token check result" << statusCode;
return;
}
folderForFile->startSync();
_localFileEditingSyncFinishedConnections.insert(localFilePath, QObject::connect(folderForFile, &Folder::syncFinished, this,
[this, localFilePath](const OCC::SyncResult &result) {
Q_UNUSED(result);
const auto foundConnectionIt = _localFileEditingSyncFinishedConnections.find(localFilePath);
if (foundConnectionIt != std::end(_localFileEditingSyncFinishedConnections) && foundConnectionIt.value()) {
QObject::disconnect(foundConnectionIt.value());
_localFileEditingSyncFinishedConnections.erase(foundConnectionIt);
}
// In case the VFS mode is enabled and a file is not yet hydrated, we must call QDesktopServices::openUrl
// from a separate thread, or, there will be a freeze. To avoid searching for a specific folder and checking
// if the VFS is enabled - we just always call it from a separate thread.
QtConcurrent::run([localFilePath]() {
QDesktopServices::openUrl(QUrl::fromLocalFile(localFilePath));
Systray::instance()->destroyEditFileLocallyLoadingDialog();
});
}));
});
checkTokenForEditLocally->start();
}
void FolderMan::trayOverallStatus(const QList<Folder *> &folders,

View file

@ -214,7 +214,7 @@ public:
void setDirtyNetworkLimits();
/** opens a file with default app, if the file is present **/
void editFileLocally(const QString &accountDisplayName, const QString &relPath);
void editFileLocally(const QString &accountDisplayName, const QString &relPath, const QString &token);
signals:
/**

View file

@ -54,6 +54,7 @@ Q_LOGGING_CATEGORY(lcAvatarJob, "nextcloud.sync.networkjob.avatar", QtInfoMsg)
Q_LOGGING_CATEGORY(lcMkColJob, "nextcloud.sync.networkjob.mkcol", QtInfoMsg)
Q_LOGGING_CATEGORY(lcProppatchJob, "nextcloud.sync.networkjob.proppatch", QtInfoMsg)
Q_LOGGING_CATEGORY(lcJsonApiJob, "nextcloud.sync.networkjob.jsonapi", QtInfoMsg)
Q_LOGGING_CATEGORY(lcSimpleApiJob, "nextcloud.sync.networkjob.simpleapi", QtInfoMsg)
Q_LOGGING_CATEGORY(lcDetermineAuthTypeJob, "nextcloud.sync.networkjob.determineauthtype", QtInfoMsg)
Q_LOGGING_CATEGORY(lcSimpleFileJob, "nextcloud.sync.networkjob.simplefilejob", QtInfoMsg)
const int notModifiedStatusCode = 304;
@ -822,64 +823,23 @@ bool EntityExistsJob::finished()
/*********************************************************************************************/
JsonApiJob::JsonApiJob(const AccountPtr &account, const QString &path, QObject *parent)
: AbstractNetworkJob(account, path, parent)
: SimpleApiJob(account, path, parent)
{
}
void JsonApiJob::addQueryParams(const QUrlQuery &params)
{
_additionalParams = params;
}
void JsonApiJob::addRawHeader(const QByteArray &headerName, const QByteArray &value)
{
_request.setRawHeader(headerName, value);
}
void JsonApiJob::setBody(const QJsonDocument &body)
{
_body = body.toJson();
qCDebug(lcJsonApiJob) << "Set body for request:" << _body;
if (!_body.isEmpty()) {
_request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
SimpleApiJob::setBody(body.toJson());
qCDebug(lcJsonApiJob) << "Set body for request:" << SimpleApiJob::body();
if (!SimpleApiJob::body().isEmpty()) {
request().setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
}
}
void JsonApiJob::setVerb(Verb value)
{
_verb = value;
}
QByteArray JsonApiJob::verbToString() const
{
switch (_verb) {
case Verb::Get:
return "GET";
case Verb::Post:
return "POST";
case Verb::Put:
return "PUT";
case Verb::Delete:
return "DELETE";
}
return "GET";
}
void JsonApiJob::start()
{
addRawHeader("OCS-APIREQUEST", "true");
auto query = _additionalParams;
query.addQueryItem(QLatin1String("format"), QLatin1String("json"));
QUrl url = Utility::concatUrlPath(account()->url(), path(), query);
const auto httpVerb = verbToString();
if (!_body.isEmpty()) {
sendRequest(httpVerb, url, _request, _body);
} else {
sendRequest(httpVerb, url, _request);
}
AbstractNetworkJob::start();
additionalParams().addQueryItem(QLatin1String("format"), QLatin1String("json"));
SimpleApiJob::start();
}
bool JsonApiJob::finished()
@ -1183,4 +1143,84 @@ void fetchPrivateLinkUrl(AccountPtr account, const QString &remotePath,
job->start();
}
SimpleApiJob::SimpleApiJob(const AccountPtr &account, const QString &path, QObject *parent)
: AbstractNetworkJob(account, path, parent)
{
}
void SimpleApiJob::setBody(const QByteArray &body)
{
_body = body;
qCDebug(lcSimpleApiJob) << "Set body for request:" << _body;
}
void SimpleApiJob::setVerb(Verb value)
{
_verb = value;
}
QByteArray SimpleApiJob::verbToString() const
{
switch (_verb) {
case Verb::Get:
return "GET";
case Verb::Post:
return "POST";
case Verb::Put:
return "PUT";
case Verb::Delete:
return "DELETE";
}
return "GET";
}
void SimpleApiJob::start()
{
addRawHeader("OCS-APIREQUEST", "true");
auto query = _additionalParams;
QUrl url = Utility::concatUrlPath(account()->url(), path(), query);
const auto httpVerb = verbToString();
if (!SimpleApiJob::body().isEmpty()) {
sendRequest(httpVerb, url, request(), SimpleApiJob::body());
} else {
sendRequest(httpVerb, url, request());
}
AbstractNetworkJob::start();
}
bool SimpleApiJob::finished()
{
const auto httpStatusCode = reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
qCDebug(lcSimpleApiJob) << "result: " << path() << errorString() << httpStatusCode;
emit resultReceived(httpStatusCode);
return true;
}
QNetworkRequest& SimpleApiJob::request()
{
return _request;
}
QByteArray& SimpleApiJob::body()
{
return _body;
}
QUrlQuery &SimpleApiJob::additionalParams()
{
return _additionalParams;
}
void SimpleApiJob::addQueryParams(const QUrlQuery &params)
{
_additionalParams = params;
}
void SimpleApiJob::addRawHeader(const QByteArray &headerName, const QByteArray &value)
{
request().setRawHeader(headerName, value);
}
} // namespace OCC

View file

@ -382,6 +382,58 @@ private slots:
bool finished() override;
};
class OWNCLOUDSYNC_EXPORT SimpleApiJob : public AbstractNetworkJob
{
Q_OBJECT
public:
enum class Verb {
Get,
Post,
Put,
Delete,
};
explicit SimpleApiJob(const AccountPtr &account, const QString &path, QObject *parent = nullptr);
void setBody(const QByteArray &body);
void setVerb(Verb value);
/**
* @brief addQueryParams - add more parameters to the ocs call
* @param params: list pairs of strings containing the parameter name and the value.
*
* All parameters from the passed list are appended to the query. Note
* that the format=json parameter is added automatically and does not
* need to be set this way.
*
* This function needs to be called before start() obviously.
*/
void addQueryParams(const QUrlQuery &params);
void addRawHeader(const QByteArray &headerName, const QByteArray &value);
public slots:
void start() override;
Q_SIGNALS:
void resultReceived(int statusCode);
protected:
bool finished() override;
[[nodiscard]] QNetworkRequest& request();
[[nodiscard]] QByteArray& body();
[[nodiscard]] QUrlQuery& additionalParams();
[[nodiscard]] QByteArray verbToString() const;
private:
QByteArray _body;
QUrlQuery _additionalParams;
QNetworkRequest _request;
Verb _verb = Verb::Get;
};
/**
* @brief Job to check an API that return JSON
*
@ -397,36 +449,14 @@ private slots:
*
* @ingroup libsync
*/
class OWNCLOUDSYNC_EXPORT JsonApiJob : public AbstractNetworkJob
class OWNCLOUDSYNC_EXPORT JsonApiJob : public SimpleApiJob
{
Q_OBJECT
public:
enum class Verb {
Get,
Post,
Put,
Delete,
};
explicit JsonApiJob(const AccountPtr &account, const QString &path, QObject *parent = nullptr);
/**
* @brief addQueryParams - add more parameters to the ocs call
* @param params: list pairs of strings containing the parameter name and the value.
*
* All parameters from the passed list are appended to the query. Note
* that the format=json parameter is added automatically and does not
* need to be set this way.
*
* This function needs to be called before start() obviously.
*/
void addQueryParams(const QUrlQuery &params);
void addRawHeader(const QByteArray &headerName, const QByteArray &value);
void setBody(const QJsonDocument &body);
void setVerb(Verb value);
public slots:
void start() override;
@ -448,15 +478,6 @@ signals:
* @param statusCode - the OCS status code: 100 (!) for success
*/
void etagResponseHeaderReceived(const QByteArray &value, int statusCode);
private:
QByteArray _body;
QUrlQuery _additionalParams;
QNetworkRequest _request;
Verb _verb = Verb::Get;
[[nodiscard]] QByteArray verbToString() const;
};
/**