mirror of
https://github.com/nextcloud/desktop.git
synced 2024-10-23 04:45:43 +03:00
Merge pull request #5039 from nextcloud/bugfix/checkTokenForEditLocallyRequests
Bugfix/check token for edit locally requests
This commit is contained in:
commit
5baa254f3d
5 changed files with 190 additions and 99 deletions
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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:
|
||||
/**
|
||||
|
|
|
@ -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 ¶ms)
|
||||
{
|
||||
_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 ¶ms)
|
||||
{
|
||||
_additionalParams = params;
|
||||
}
|
||||
|
||||
void SimpleApiJob::addRawHeader(const QByteArray &headerName, const QByteArray &value)
|
||||
{
|
||||
request().setRawHeader(headerName, value);
|
||||
}
|
||||
|
||||
} // namespace OCC
|
||||
|
|
|
@ -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 ¶ms);
|
||||
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 ¶ms);
|
||||
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;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in a new issue