From 94df6769dce81080e4418c75db5b6d77de9b3167 Mon Sep 17 00:00:00 2001 From: alex-z Date: Fri, 23 Dec 2022 15:24:19 +0100 Subject: [PATCH 1/4] Allow setting up an account with apppasword and folder via command-line arguments. For deployment. Signed-off-by: alex-z --- src/gui/application.cpp | 216 +++++++++++++++++++++++++++++++++++++++- src/gui/application.h | 15 +++ 2 files changed, 230 insertions(+), 1 deletion(-) diff --git a/src/gui/application.cpp b/src/gui/application.cpp index b75ebb121..4106afe38 100644 --- a/src/gui/application.cpp +++ b/src/gui/application.cpp @@ -24,6 +24,7 @@ #include "accountstate.h" #include "editlocallymanager.h" #include "connectionvalidator.h" +#include "filesystem.h" #include "folder.h" #include "folderman.h" #include "logger.h" @@ -34,6 +35,7 @@ #include "clientproxy.h" #include "accountmanager.h" #include "creds/abstractcredentials.h" +#include "creds/webflowcredentials.h" #include "pushnotifications.h" #include "shellextensionsserver.h" @@ -411,6 +413,10 @@ Application::Application(int &argc, char **argv) _gui->createTray(); handleEditLocallyFromOptions(); + + if (!_appPassword.isEmpty() && !_userId.isEmpty() && !_baseDir.isEmpty() && _serverUrl.isValid()) { + handleAccountSetupFromCommandLine(); + } } Application::~Application() @@ -663,7 +669,37 @@ void Application::parseOptions(const QStringList &options) qCInfo(lcApplication) << errorParsingLocalFileEditingUrl; showHint(errorParsingLocalFileEditingUrl.toStdString()); } - } + } else if (option == QLatin1String("--apppassword")) { + if (it.hasNext() && !it.peekNext().startsWith(QLatin1String("--"))) { + _appPassword = it.next(); + } else { + showHint("apppassword not specified"); + } + } else if (option == QLatin1String("--userid")) { + if (it.hasNext() && !it.peekNext().startsWith(QLatin1String("--"))) { + _userId = it.next(); + } else { + showHint("userid not specified"); + } + } else if (option == QLatin1String("--basedir")) { + if (it.hasNext() && !it.peekNext().startsWith(QLatin1String("--"))) { + _baseDir = it.next(); + } else { + showHint("basedir not specified"); + } + } else if (option == QLatin1String("--remotedir")) { + if (it.hasNext() && !it.peekNext().startsWith(QLatin1String("--"))) { + _remoteDir = it.next(); + } else { + showHint("remotedir not specified"); + } + } else if (option == QLatin1String("--serverurl")) { + if (it.hasNext() && !it.peekNext().startsWith(QLatin1String("--"))) { + _serverUrl = it.next(); + } else { + showHint("serverurl not specified"); + } + } else { showHint("Unrecognized option '" + option.toStdString() + "'"); } @@ -849,6 +885,184 @@ void Application::slotGuiIsShowingSettings() emit isShowingSettingsDialog(); } +void Application::handleAccountSetupFromCommandLine() +{ + if (AccountManager::instance()->accountFromUserId(QStringLiteral("%1@%2").arg(_userId).arg(_serverUrl.host()))) { + printAccountSetupFromCommandLineStatusAndExit(QStringLiteral("Account %1 already exists!").arg(QDir::toNativeSeparators(_userId)), true); + return; + } + + QDir dir(_baseDir); + if (dir.exists() && !dir.isEmpty()) { + printAccountSetupFromCommandLineStatusAndExit(QStringLiteral("Local folder %1 already exists and is non-empty!").arg(QDir::toNativeSeparators(_baseDir)), true); + return; + } + + qInfo() << "Creating folder" << _baseDir; + if (!dir.exists() && !dir.mkpath(".")) { + printAccountSetupFromCommandLineStatusAndExit(QStringLiteral("Folder creation failed. Could not create local folder %1").arg(QDir::toNativeSeparators(_baseDir)), true); + return; + } + + FileSystem::setFolderMinimumPermissions(_baseDir); + Utility::setupFavLink(_baseDir); + + const auto credentials = new WebFlowCredentials(_userId, _appPassword); + _account = AccountManager::createAccount(); + _account->setCredentials(credentials); + _account->setUrl(_serverUrl); + + _userId.clear(); + _appPassword.clear(); + _serverUrl.clear(); + + fetchUserName(); +} + +void Application::checkLastModifiedWithPropfind() +{ + const auto job = new PropfindJob(_account, "/", this); + job->setIgnoreCredentialFailure(true); + // There is custom redirect handling in the error handler, + // so don't automatically follow redirects. + job->setFollowRedirects(false); + job->setProperties(QList() << "getlastmodified"); + connect(job, &PropfindJob::result, this, &Application::accountSetupFromCommandLinePropfindHandleSuccess); + connect(job, &PropfindJob::finishedWithError, this, &Application::accountSetupFromCommandLinePropfindHandleFailure); + job->start(); +} + +void Application::accountSetupFromCommandLinePropfindHandleSuccess() +{ + const auto accountManager = AccountManager::instance(); + const auto accountState = accountManager->addAccount(_account); + accountManager->save(); + + FolderDefinition definition; + definition.localPath = _baseDir; + definition.targetPath = FolderDefinition::prepareTargetPath(!_remoteDir.isEmpty() ? _remoteDir : QStringLiteral("/")); + definition.virtualFilesMode = bestAvailableVfsMode(); + + const auto folderMan = FolderMan::instance(); + + definition.ignoreHiddenFiles = folderMan->ignoreHiddenFiles(); + definition.alias = folderMan->map().size() > 0 ? QString::number(folderMan->map().size()) : QString::number(0); + + if (folderMan->navigationPaneHelper().showInExplorerNavigationPane()) { + definition.navigationPaneClsid = QUuid::createUuid(); + } + + folderMan->setSyncEnabled(false); + + if (const auto folder = folderMan->addFolder(accountState, definition)) { + if (definition.virtualFilesMode != Vfs::Off) { + folder->setRootPinState(PinState::OnlineOnly); + } + folder->journalDb()->setSelectiveSyncList(SyncJournalDb::SelectiveSyncWhiteList, QStringList() << QLatin1String("/")); + qInfo() << QStringLiteral("Folder %1 setup from command line success.").arg(definition.localPath); + printAccountSetupFromCommandLineStatusAndExit(QStringLiteral("Account %1 setup from command line success.").arg(_account->displayName()), false); + } else { + accountManager->deleteAccount(accountState); + printAccountSetupFromCommandLineStatusAndExit(QStringLiteral("Account %1 setup from command line failed, due to folder creation failure.").arg(_account->displayName()), false); + } +} + +void Application::accountSetupFromCommandLinePropfindHandleFailure() +{ + const auto job = qobject_cast(sender()); + if (!job) { + printAccountSetupFromCommandLineStatusAndExit(QStringLiteral("Cannot check for authed redirects. This slot should be invoked from PropfindJob!"), true); + return; + } + const auto reply = job->reply(); + + QString errorMsg; + + // If there were redirects on the *authed* requests, also store + // the updated server URL, similar to redirects on status.php. + QUrl redirectUrl = reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl(); + if (!redirectUrl.isEmpty()) { + qInfo() << "Authed request was redirected to" << redirectUrl.toString(); + + // strip the expected path + auto path = redirectUrl.path(); + static QString expectedPath = "/" + _account->davPath(); + if (path.endsWith(expectedPath)) { + path.chop(expectedPath.size()); + redirectUrl.setPath(path); + + qInfo() << "Setting account url to" << redirectUrl.toString(); + _account->setUrl(redirectUrl); + checkLastModifiedWithPropfind(); + } + errorMsg = tr("The authenticated request to the server was redirected to " + "\"%1\". The URL is bad, the server is misconfigured.") + .arg(Utility::escape(redirectUrl.toString())); + + // A 404 is actually a success: we were authorized to know that the folder does + // not exist. It will be created later... + } else if (reply->error() == QNetworkReply::ContentNotFoundError) { + accountSetupFromCommandLinePropfindHandleSuccess(); + } else if (reply->error() != QNetworkReply::NoError) { + if (!_account->credentials()->stillValid(reply)) { + errorMsg = tr("Access forbidden by server. To verify that you have proper access, " + "click here to access the service with your browser.") + .arg(Utility::escape(_account->url().toString())); + } else { + errorMsg = job->errorStringParsingBody(); + } + // Something else went wrong, maybe the response was 200 but with invalid data. + } else { + errorMsg = tr("There was an invalid response to an authenticated WebDAV request"); + } + printAccountSetupFromCommandLineStatusAndExit( QStringLiteral("Account %1 setup from command line failed with error: %2.").arg(_account->displayName()).arg(errorMsg), true); +} + +void Application::printAccountSetupFromCommandLineStatusAndExit(const QString &status, bool isFailure) +{ + if (isFailure) { + qWarning() << status; + } else { + qInfo() << status; + } + _userId.clear(); + _appPassword.clear(); + _serverUrl.clear(); + _baseDir.clear(); + _remoteDir.clear(); + _account.clear(); + QTimer::singleShot(0, this, [isFailure]() { + if (isFailure) { + qApp->exit(1); + } else { + // we might need another wait to quit such that we won't end up with syncjournal being locked and lock not lifted + qApp->quit(); + } + }); +} + +void Application::fetchUserName() +{ + const auto fetchUserNameJob = new JsonApiJob(_account, QStringLiteral("/ocs/v1.php/cloud/user")); + connect(fetchUserNameJob, &JsonApiJob::jsonReceived, this, [this](const QJsonDocument &json, int statusCode) { + sender()->deleteLater(); + + if (statusCode != 100) { + printAccountSetupFromCommandLineStatusAndExit("Could not fetch username.", true); + return; + } + + const auto objData = json.object().value("ocs").toObject().value("data").toObject(); + const auto userId = objData.value("id").toString(""); + const auto displayName = objData.value("display-name").toString(""); + _account->setDavUser(userId); + _account->setDavDisplayName(displayName); + + checkLastModifiedWithPropfind(); + }); + fetchUserNameJob->start(); +} + void Application::openVirtualFile(const QString &filename) { QString virtualFileExt = QStringLiteral(APPLICATION_DOTVIRTUALFILE_SUFFIX); diff --git a/src/gui/application.h b/src/gui/application.h index 5bd412077..c41b4cae4 100644 --- a/src/gui/application.h +++ b/src/gui/application.h @@ -107,6 +107,14 @@ protected slots: void slotSystemOnlineConfigurationChanged(QNetworkConfiguration); void slotGuiIsShowingSettings(); +private slots: + void handleAccountSetupFromCommandLine(); + void fetchUserName(); + void checkLastModifiedWithPropfind(); + void accountSetupFromCommandLinePropfindHandleSuccess(); + void accountSetupFromCommandLinePropfindHandleFailure(); + void printAccountSetupFromCommandLineStatusAndExit(const QString &status, bool isFailure); + private: void setHelp(); @@ -132,6 +140,11 @@ private: bool _quitInstance = false; QString _logFile; QString _logDir; + QString _appPassword; + QString _userId; + QString _baseDir; + QString _remoteDir; + QUrl _serverUrl; int _logExpire; bool _logFlush; bool _logDebug; @@ -145,6 +158,8 @@ private: QNetworkConfigurationManager _networkConfigurationManager; QTimer _checkConnectionTimer; + AccountPtr _account; + #if defined(WITH_CRASHREPORTER) QScopedPointer _crashHandler; #endif From e53d08fedb05be859c333684bb2fe6e42833cfba Mon Sep 17 00:00:00 2001 From: alex-z Date: Thu, 29 Dec 2022 12:50:17 +0100 Subject: [PATCH 2/4] Fix review comments from Claudio. Refactoring. Signed-off-by: alex-z --- src/gui/CMakeLists.txt | 2 + src/gui/accountsetupcommandlinemanager.cpp | 311 +++++++++++++++++++++ src/gui/accountsetupcommandlinemanager.h | 82 ++++++ src/gui/application.cpp | 238 ++-------------- src/gui/application.h | 15 - 5 files changed, 418 insertions(+), 230 deletions(-) create mode 100644 src/gui/accountsetupcommandlinemanager.cpp create mode 100644 src/gui/accountsetupcommandlinemanager.h diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 7588a6169..ed1820809 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -65,6 +65,8 @@ set(client_SRCS accountmanager.cpp accountsettings.h accountsettings.cpp + accountsetupcommandlinemanager.h + accountsetupcommandlinemanager.cpp application.h application.cpp invalidfilenamedialog.h diff --git a/src/gui/accountsetupcommandlinemanager.cpp b/src/gui/accountsetupcommandlinemanager.cpp new file mode 100644 index 000000000..dbc5c4604 --- /dev/null +++ b/src/gui/accountsetupcommandlinemanager.cpp @@ -0,0 +1,311 @@ +/* + * Copyright (C) by Oleksandr Zolotov + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include "accountsetupcommandlinemanager.h" + +#include "accountmanager.h" +#include "creds/webflowcredentials.h" +#include "filesystem.h" +#include "folder.h" +#include "folderman.h" +#include "networkjobs.h" + +#include +#include +#include +#include +#include +#include + +namespace OCC +{ +Q_LOGGING_CATEGORY(lcAccountSetupCommandLineManager, "nextcloud.gui.accountsetupcommandlinemanager", QtInfoMsg) + +AccountSetupFromCommandLineJob::AccountSetupFromCommandLineJob(QString appPassword, + QString userId, + QUrl serverUrl, + QString localDirPath, + bool nonVfsMode, + QString remoteDirPath, + QObject *parent) + : QObject(parent) + , _appPassword(appPassword) + , _userId(userId) + , _serverUrl(serverUrl) + , _localDirPath(localDirPath) + , _nonVfsMode(nonVfsMode) + , _remoteDirPath(remoteDirPath) +{ +} + +void AccountSetupFromCommandLineJob::handleAccountSetupFromCommandLine() +{ + if (AccountManager::instance()->accountFromUserId(QStringLiteral("%1@%2").arg(_userId).arg(_serverUrl.host()))) { + printAccountSetupFromCommandLineStatusAndExit(QStringLiteral("Account %1 already exists!").arg(QDir::toNativeSeparators(_userId)), true); + return; + } + + if (!_localDirPath.isEmpty()) { + QDir dir(_localDirPath); + if (dir.exists() && !dir.isEmpty()) { + printAccountSetupFromCommandLineStatusAndExit( + QStringLiteral("Local folder %1 already exists and is non-empty!").arg(QDir::toNativeSeparators(_localDirPath)), + true); + return; + } + + qCInfo(lcAccountSetupCommandLineManager) << "Creating folder" << _localDirPath; + if (!dir.exists() && !dir.mkpath(".")) { + printAccountSetupFromCommandLineStatusAndExit( + QStringLiteral("Folder creation failed. Could not create local folder %1").arg(QDir::toNativeSeparators(_localDirPath)), + true); + return; + } + + FileSystem::setFolderMinimumPermissions(_localDirPath); + Utility::setupFavLink(_localDirPath); + } + + const auto credentials = new WebFlowCredentials(_userId, _appPassword); + _account = AccountManager::createAccount(); + _account->setCredentials(credentials); + _account->setUrl(_serverUrl); + + fetchUserName(); +} + +void AccountSetupFromCommandLineJob::checkLastModifiedWithPropfind() +{ + const auto job = new PropfindJob(_account, "/", this); + job->setIgnoreCredentialFailure(true); + // There is custom redirect handling in the error handler, + // so don't automatically follow redirects. + job->setFollowRedirects(false); + job->setProperties(QList() << QByteArrayLiteral("getlastmodified")); + connect(job, &PropfindJob::result, this, &AccountSetupFromCommandLineJob::accountSetupFromCommandLinePropfindHandleSuccess); + connect(job, &PropfindJob::finishedWithError, this, &AccountSetupFromCommandLineJob::accountSetupFromCommandLinePropfindHandleFailure); + job->start(); +} + +void AccountSetupFromCommandLineJob::accountSetupFromCommandLinePropfindHandleSuccess() +{ + const auto accountManager = AccountManager::instance(); + const auto accountState = accountManager->addAccount(_account); + accountManager->save(); + + if (!_localDirPath.isEmpty()) { + FolderDefinition definition; + definition.localPath = _localDirPath; + definition.targetPath = FolderDefinition::prepareTargetPath(!_remoteDirPath.isEmpty() ? _remoteDirPath : QStringLiteral("/")); + definition.virtualFilesMode = _nonVfsMode ? Vfs::Off : bestAvailableVfsMode(); + + const auto folderMan = FolderMan::instance(); + + definition.ignoreHiddenFiles = folderMan->ignoreHiddenFiles(); + definition.alias = folderMan->map().size() > 0 ? QString::number(folderMan->map().size()) : QString::number(0); + + if (folderMan->navigationPaneHelper().showInExplorerNavigationPane()) { + definition.navigationPaneClsid = QUuid::createUuid(); + } + + folderMan->setSyncEnabled(false); + + if (const auto folder = folderMan->addFolder(accountState, definition)) { + if (definition.virtualFilesMode != Vfs::Off) { + folder->setRootPinState(PinState::OnlineOnly); + } + folder->journalDb()->setSelectiveSyncList(SyncJournalDb::SelectiveSyncWhiteList, QStringList() << QLatin1String("/")); + qCInfo(lcAccountSetupCommandLineManager) << QStringLiteral("Folder %1 setup from command line success.").arg(definition.localPath); + printAccountSetupFromCommandLineStatusAndExit(QStringLiteral("Account %1 setup from command line success.").arg(_account->displayName()), false); + } else { + accountManager->deleteAccount(accountState); + printAccountSetupFromCommandLineStatusAndExit( + QStringLiteral("Account %1 setup from command line failed, due to folder creation failure.").arg(_account->displayName()), + false); + } + } else { + qCInfo(lcAccountSetupCommandLineManager) << QStringLiteral("Set up a new account without a folder."); + printAccountSetupFromCommandLineStatusAndExit(QStringLiteral("Account %1 setup from command line success.").arg(_account->displayName()), false); + } +} + +void AccountSetupFromCommandLineJob::accountSetupFromCommandLinePropfindHandleFailure() +{ + const auto job = qobject_cast(sender()); + if (!job) { + printAccountSetupFromCommandLineStatusAndExit(QStringLiteral("Cannot check for authed redirects. This slot should be invoked from PropfindJob!"), true); + return; + } + const auto reply = job->reply(); + + QString errorMsg; + + // If there were redirects on the *authed* requests, also store + // the updated server URL, similar to redirects on status.php. + QUrl redirectUrl = reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl(); + if (!redirectUrl.isEmpty()) { + qCInfo(lcAccountSetupCommandLineManager) << "Authed request was redirected to" << redirectUrl.toString(); + + // strip the expected path + auto path = redirectUrl.path(); + static QString expectedPath = "/" + _account->davPath(); + if (path.endsWith(expectedPath)) { + path.chop(expectedPath.size()); + redirectUrl.setPath(path); + + qCInfo(lcAccountSetupCommandLineManager) << "Setting account url to" << redirectUrl.toString(); + _account->setUrl(redirectUrl); + checkLastModifiedWithPropfind(); + } + errorMsg = tr("The authenticated request to the server was redirected to " + "\"%1\". The URL is bad, the server is misconfigured.") + .arg(Utility::escape(redirectUrl.toString())); + + // A 404 is actually a success: we were authorized to know that the folder does + // not exist. It will be created later... + } else if (reply->error() == QNetworkReply::ContentNotFoundError) { + accountSetupFromCommandLinePropfindHandleSuccess(); + } else if (reply->error() != QNetworkReply::NoError) { + if (!_account->credentials()->stillValid(reply)) { + errorMsg = tr("Access forbidden by server. To verify that you have proper access, " + "click here to access the service with your browser.") + .arg(Utility::escape(_account->url().toString())); + } else { + errorMsg = job->errorStringParsingBody(); + } + // Something else went wrong, maybe the response was 200 but with invalid data. + } else { + errorMsg = tr("There was an invalid response to an authenticated WebDAV request"); + } + printAccountSetupFromCommandLineStatusAndExit( + QStringLiteral("Account %1 setup from command line failed with error: %2.").arg(_account->displayName()).arg(errorMsg), + true); +} + +void AccountSetupFromCommandLineJob::printAccountSetupFromCommandLineStatusAndExit(const QString &status, bool isFailure) +{ + if (isFailure) { + qCWarning(lcAccountSetupCommandLineManager) << status; + } else { + qCInfo(lcAccountSetupCommandLineManager) << status; + } + QTimer::singleShot(0, this, [this, isFailure]() { + this->deleteLater(); + if (!isFailure) { + qApp->quit(); + } else { + qApp->exit(1); + } + }); +} + +void AccountSetupFromCommandLineJob::fetchUserName() +{ + const auto fetchUserNameJob = new JsonApiJob(_account, QStringLiteral("/ocs/v1.php/cloud/user")); + connect(fetchUserNameJob, &JsonApiJob::jsonReceived, this, [this](const QJsonDocument &json, int statusCode) { + sender()->deleteLater(); + + if (statusCode != 100) { + printAccountSetupFromCommandLineStatusAndExit("Could not fetch username.", true); + return; + } + + const auto objData = json.object().value("ocs").toObject().value("data").toObject(); + const auto userId = objData.value("id").toString(""); + const auto displayName = objData.value("display-name").toString(""); + _account->setDavUser(userId); + _account->setDavDisplayName(displayName); + + checkLastModifiedWithPropfind(); + }); + fetchUserNameJob->start(); +} + +bool AccountSetupCommandLineManager::parseCommandlineOption(const QString &option, QStringListIterator &optionsIterator, QString &errorMessage) +{ + if (option == QStringLiteral("--apppassword")) { + if (optionsIterator.hasNext() && !optionsIterator.peekNext().startsWith(QLatin1String("--"))) { + _appPassword = optionsIterator.next(); + return true; + } else { + errorMessage = QStringLiteral("apppassword not specified"); + } + } else if (option == QStringLiteral("--localdirpath")) { + if (optionsIterator.hasNext() && !optionsIterator.peekNext().startsWith(QLatin1String("--"))) { + _localDirPath = optionsIterator.next(); + return true; + } else { + errorMessage = QStringLiteral("basedir not specified"); + } + } else if (option == QStringLiteral("--remotedirpath")) { + if (optionsIterator.hasNext() && !optionsIterator.peekNext().startsWith(QLatin1String("--"))) { + _remoteDirPath = optionsIterator.next(); + return true; + } else { + errorMessage = QStringLiteral("remotedir not specified"); + } + } else if (option == QStringLiteral("--serverurl")) { + if (optionsIterator.hasNext() && !optionsIterator.peekNext().startsWith(QLatin1String("--"))) { + _serverUrl = optionsIterator.next(); + return true; + } else { + errorMessage = QStringLiteral("serverurl not specified"); + } + } else if (option == QStringLiteral("--userid")) { + if (optionsIterator.hasNext() && !optionsIterator.peekNext().startsWith(QLatin1String("--"))) { + _userId = optionsIterator.next(); + return true; + } else { + errorMessage = QStringLiteral("userid not specified"); + } + } else if (option == QLatin1String("--nonvfsmode")) { + if (optionsIterator.hasNext() && !optionsIterator.peekNext().startsWith(QLatin1String("--"))) { + _nonVfsMode = optionsIterator.next().toInt() != 0; + return true; + } else { + errorMessage = QStringLiteral("nonVfsMode not specified"); + } + } + return false; +} + +bool AccountSetupCommandLineManager::isCommandLineParsed() +{ + return !_appPassword.isEmpty() && !_userId.isEmpty() && _serverUrl.isValid(); +} + +void AccountSetupCommandLineManager::setupAccountFromCommandLine(QObject *parent) +{ + if (isCommandLineParsed()) { + qCInfo(lcAccountSetupCommandLineManager) << QStringLiteral("Command line has been parsed and account setup parameters have been found. Attempting setup a new account %1...").arg(_userId); + const auto accountSetupJob = new AccountSetupFromCommandLineJob(_appPassword, _userId, _serverUrl, _localDirPath, _nonVfsMode, _remoteDirPath, parent); + accountSetupJob->handleAccountSetupFromCommandLine(); + } else { + qCInfo(lcAccountSetupCommandLineManager) << QStringLiteral("No account setup parameters have been found, or they are invalid. Proceed with normal startup..."); + } + _appPassword.clear(); + _userId.clear(); + _serverUrl.clear(); + _remoteDirPath.clear(); + _localDirPath.clear(); + _nonVfsMode = false; +} + +QString AccountSetupCommandLineManager::_appPassword; +QString AccountSetupCommandLineManager::_userId; +QUrl AccountSetupCommandLineManager::_serverUrl; +QString AccountSetupCommandLineManager::_remoteDirPath; +QString AccountSetupCommandLineManager::_localDirPath; +bool AccountSetupCommandLineManager::_nonVfsMode = false; +} diff --git a/src/gui/accountsetupcommandlinemanager.h b/src/gui/accountsetupcommandlinemanager.h new file mode 100644 index 000000000..2875febdc --- /dev/null +++ b/src/gui/accountsetupcommandlinemanager.h @@ -0,0 +1,82 @@ +/* + * Copyright (C) by Oleksandr Zolotov + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#pragma once + +#include "account.h" +#include "accountstate.h" + +#include + +namespace OCC { + +class AccountSetupFromCommandLineJob : public QObject +{ + Q_OBJECT +public: + AccountSetupFromCommandLineJob(QString appPassword, + QString userId, + QUrl serverUrl, + QString localDirPath = {}, + bool nonVfsMode = false, + QString remoteDirPath = QStringLiteral("/"), + QObject *parent = nullptr); + +public slots: + void handleAccountSetupFromCommandLine(); + +private slots: + void checkLastModifiedWithPropfind(); + + void accountSetupFromCommandLinePropfindHandleSuccess(); + + void accountSetupFromCommandLinePropfindHandleFailure(); + + void printAccountSetupFromCommandLineStatusAndExit(const QString &status, bool isFailure); + + void fetchUserName(); + +private: + QString _appPassword; + QString _userId; + QUrl _serverUrl; + QString _localDirPath; + bool _nonVfsMode = false; + QString _remoteDirPath; + + AccountPtr _account; +}; + + +class AccountSetupCommandLineManager +{ +public: + [[nodiscard]] static bool parseCommandlineOption(const QString &option, QStringListIterator &optionsIterator, QString &errorMessage); + + [[nodiscard]] static bool isCommandLineParsed(); + + static void setupAccountFromCommandLine(QObject *parent = nullptr); + +private: + explicit AccountSetupCommandLineManager() = delete; + + static QString _appPassword; + static QString _userId; + static QUrl _serverUrl; + static QString _remoteDirPath; + static QString _localDirPath; + static bool _nonVfsMode; +}; + +} diff --git a/src/gui/application.cpp b/src/gui/application.cpp index 4106afe38..54d696ab8 100644 --- a/src/gui/application.cpp +++ b/src/gui/application.cpp @@ -21,10 +21,10 @@ #include "config.h" #include "account.h" +#include "accountsetupcommandlinemanager.h" #include "accountstate.h" #include "editlocallymanager.h" #include "connectionvalidator.h" -#include "filesystem.h" #include "folder.h" #include "folderman.h" #include "logger.h" @@ -35,7 +35,6 @@ #include "clientproxy.h" #include "accountmanager.h" #include "creds/abstractcredentials.h" -#include "creds/webflowcredentials.h" #include "pushnotifications.h" #include "shellextensionsserver.h" @@ -87,7 +86,13 @@ namespace { " --logflush : flush the log file after every write.\n" " --logdebug : also output debug-level messages in the log.\n" " --confdir : Use the given configuration folder.\n" - " --background : launch the application in the background.\n"; + " --background : launch the application in the background.\n" + " --userid : userId (username as on the server) to pass when creating an account via command-line.\n" + " --apppassword : appPassword to pass when creating an account via command-line.\n" + " --localdirpath : (optional) path where to create a local sync folder when creating an account via command-line.\n" + " --nonvfsmode : whether to set up a non-VFS folder (1 for 'yes' or 0 for 'no') when creating an account via command-line.\n" + " --remotedirpath : (optional) path to a remote subfolder when creating an account via command-line.\n" + " --serverurl : a server URL to use when creating an account via command-line.\n"; QString applicationTrPath() { @@ -414,8 +419,8 @@ Application::Application(int &argc, char **argv) handleEditLocallyFromOptions(); - if (!_appPassword.isEmpty() && !_userId.isEmpty() && !_baseDir.isEmpty() && _serverUrl.isValid()) { - handleAccountSetupFromCommandLine(); + if (AccountSetupCommandLineManager::isCommandLineParsed()) { + AccountSetupCommandLineManager::setupAccountFromCommandLine(this); } } @@ -585,6 +590,10 @@ void Application::slotParseMessage(const QString &msg, QObject *) handleEditLocallyFromOptions(); + if (AccountSetupCommandLineManager::isCommandLineParsed()) { + AccountSetupCommandLineManager::setupAccountFromCommandLine(this); + } + } else if (msg.startsWith(QLatin1String("MSG_SHOWMAINDIALOG"))) { qCInfo(lcApplication) << "Running for" << _startedAt.elapsed() / 1000.0 << "sec"; if (_startedAt.elapsed() < 10 * 1000) { @@ -669,39 +678,16 @@ void Application::parseOptions(const QStringList &options) qCInfo(lcApplication) << errorParsingLocalFileEditingUrl; showHint(errorParsingLocalFileEditingUrl.toStdString()); } - } else if (option == QLatin1String("--apppassword")) { - if (it.hasNext() && !it.peekNext().startsWith(QLatin1String("--"))) { - _appPassword = it.next(); - } else { - showHint("apppassword not specified"); - } - } else if (option == QLatin1String("--userid")) { - if (it.hasNext() && !it.peekNext().startsWith(QLatin1String("--"))) { - _userId = it.next(); - } else { - showHint("userid not specified"); - } - } else if (option == QLatin1String("--basedir")) { - if (it.hasNext() && !it.peekNext().startsWith(QLatin1String("--"))) { - _baseDir = it.next(); - } else { - showHint("basedir not specified"); - } - } else if (option == QLatin1String("--remotedir")) { - if (it.hasNext() && !it.peekNext().startsWith(QLatin1String("--"))) { - _remoteDir = it.next(); - } else { - showHint("remotedir not specified"); - } - } else if (option == QLatin1String("--serverurl")) { - if (it.hasNext() && !it.peekNext().startsWith(QLatin1String("--"))) { - _serverUrl = it.next(); - } else { - showHint("serverurl not specified"); - } - } + } else { - showHint("Unrecognized option '" + option.toStdString() + "'"); + QString errorMessage; + if (!AccountSetupCommandLineManager::parseCommandlineOption(option, it, errorMessage)) { + if (!errorMessage.isEmpty()) { + showHint(errorMessage.toStdString()); + return; + } + showHint("Unrecognized option '" + option.toStdString() + "'"); + } } } } @@ -885,184 +871,6 @@ void Application::slotGuiIsShowingSettings() emit isShowingSettingsDialog(); } -void Application::handleAccountSetupFromCommandLine() -{ - if (AccountManager::instance()->accountFromUserId(QStringLiteral("%1@%2").arg(_userId).arg(_serverUrl.host()))) { - printAccountSetupFromCommandLineStatusAndExit(QStringLiteral("Account %1 already exists!").arg(QDir::toNativeSeparators(_userId)), true); - return; - } - - QDir dir(_baseDir); - if (dir.exists() && !dir.isEmpty()) { - printAccountSetupFromCommandLineStatusAndExit(QStringLiteral("Local folder %1 already exists and is non-empty!").arg(QDir::toNativeSeparators(_baseDir)), true); - return; - } - - qInfo() << "Creating folder" << _baseDir; - if (!dir.exists() && !dir.mkpath(".")) { - printAccountSetupFromCommandLineStatusAndExit(QStringLiteral("Folder creation failed. Could not create local folder %1").arg(QDir::toNativeSeparators(_baseDir)), true); - return; - } - - FileSystem::setFolderMinimumPermissions(_baseDir); - Utility::setupFavLink(_baseDir); - - const auto credentials = new WebFlowCredentials(_userId, _appPassword); - _account = AccountManager::createAccount(); - _account->setCredentials(credentials); - _account->setUrl(_serverUrl); - - _userId.clear(); - _appPassword.clear(); - _serverUrl.clear(); - - fetchUserName(); -} - -void Application::checkLastModifiedWithPropfind() -{ - const auto job = new PropfindJob(_account, "/", this); - job->setIgnoreCredentialFailure(true); - // There is custom redirect handling in the error handler, - // so don't automatically follow redirects. - job->setFollowRedirects(false); - job->setProperties(QList() << "getlastmodified"); - connect(job, &PropfindJob::result, this, &Application::accountSetupFromCommandLinePropfindHandleSuccess); - connect(job, &PropfindJob::finishedWithError, this, &Application::accountSetupFromCommandLinePropfindHandleFailure); - job->start(); -} - -void Application::accountSetupFromCommandLinePropfindHandleSuccess() -{ - const auto accountManager = AccountManager::instance(); - const auto accountState = accountManager->addAccount(_account); - accountManager->save(); - - FolderDefinition definition; - definition.localPath = _baseDir; - definition.targetPath = FolderDefinition::prepareTargetPath(!_remoteDir.isEmpty() ? _remoteDir : QStringLiteral("/")); - definition.virtualFilesMode = bestAvailableVfsMode(); - - const auto folderMan = FolderMan::instance(); - - definition.ignoreHiddenFiles = folderMan->ignoreHiddenFiles(); - definition.alias = folderMan->map().size() > 0 ? QString::number(folderMan->map().size()) : QString::number(0); - - if (folderMan->navigationPaneHelper().showInExplorerNavigationPane()) { - definition.navigationPaneClsid = QUuid::createUuid(); - } - - folderMan->setSyncEnabled(false); - - if (const auto folder = folderMan->addFolder(accountState, definition)) { - if (definition.virtualFilesMode != Vfs::Off) { - folder->setRootPinState(PinState::OnlineOnly); - } - folder->journalDb()->setSelectiveSyncList(SyncJournalDb::SelectiveSyncWhiteList, QStringList() << QLatin1String("/")); - qInfo() << QStringLiteral("Folder %1 setup from command line success.").arg(definition.localPath); - printAccountSetupFromCommandLineStatusAndExit(QStringLiteral("Account %1 setup from command line success.").arg(_account->displayName()), false); - } else { - accountManager->deleteAccount(accountState); - printAccountSetupFromCommandLineStatusAndExit(QStringLiteral("Account %1 setup from command line failed, due to folder creation failure.").arg(_account->displayName()), false); - } -} - -void Application::accountSetupFromCommandLinePropfindHandleFailure() -{ - const auto job = qobject_cast(sender()); - if (!job) { - printAccountSetupFromCommandLineStatusAndExit(QStringLiteral("Cannot check for authed redirects. This slot should be invoked from PropfindJob!"), true); - return; - } - const auto reply = job->reply(); - - QString errorMsg; - - // If there were redirects on the *authed* requests, also store - // the updated server URL, similar to redirects on status.php. - QUrl redirectUrl = reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl(); - if (!redirectUrl.isEmpty()) { - qInfo() << "Authed request was redirected to" << redirectUrl.toString(); - - // strip the expected path - auto path = redirectUrl.path(); - static QString expectedPath = "/" + _account->davPath(); - if (path.endsWith(expectedPath)) { - path.chop(expectedPath.size()); - redirectUrl.setPath(path); - - qInfo() << "Setting account url to" << redirectUrl.toString(); - _account->setUrl(redirectUrl); - checkLastModifiedWithPropfind(); - } - errorMsg = tr("The authenticated request to the server was redirected to " - "\"%1\". The URL is bad, the server is misconfigured.") - .arg(Utility::escape(redirectUrl.toString())); - - // A 404 is actually a success: we were authorized to know that the folder does - // not exist. It will be created later... - } else if (reply->error() == QNetworkReply::ContentNotFoundError) { - accountSetupFromCommandLinePropfindHandleSuccess(); - } else if (reply->error() != QNetworkReply::NoError) { - if (!_account->credentials()->stillValid(reply)) { - errorMsg = tr("Access forbidden by server. To verify that you have proper access, " - "click here to access the service with your browser.") - .arg(Utility::escape(_account->url().toString())); - } else { - errorMsg = job->errorStringParsingBody(); - } - // Something else went wrong, maybe the response was 200 but with invalid data. - } else { - errorMsg = tr("There was an invalid response to an authenticated WebDAV request"); - } - printAccountSetupFromCommandLineStatusAndExit( QStringLiteral("Account %1 setup from command line failed with error: %2.").arg(_account->displayName()).arg(errorMsg), true); -} - -void Application::printAccountSetupFromCommandLineStatusAndExit(const QString &status, bool isFailure) -{ - if (isFailure) { - qWarning() << status; - } else { - qInfo() << status; - } - _userId.clear(); - _appPassword.clear(); - _serverUrl.clear(); - _baseDir.clear(); - _remoteDir.clear(); - _account.clear(); - QTimer::singleShot(0, this, [isFailure]() { - if (isFailure) { - qApp->exit(1); - } else { - // we might need another wait to quit such that we won't end up with syncjournal being locked and lock not lifted - qApp->quit(); - } - }); -} - -void Application::fetchUserName() -{ - const auto fetchUserNameJob = new JsonApiJob(_account, QStringLiteral("/ocs/v1.php/cloud/user")); - connect(fetchUserNameJob, &JsonApiJob::jsonReceived, this, [this](const QJsonDocument &json, int statusCode) { - sender()->deleteLater(); - - if (statusCode != 100) { - printAccountSetupFromCommandLineStatusAndExit("Could not fetch username.", true); - return; - } - - const auto objData = json.object().value("ocs").toObject().value("data").toObject(); - const auto userId = objData.value("id").toString(""); - const auto displayName = objData.value("display-name").toString(""); - _account->setDavUser(userId); - _account->setDavDisplayName(displayName); - - checkLastModifiedWithPropfind(); - }); - fetchUserNameJob->start(); -} - void Application::openVirtualFile(const QString &filename) { QString virtualFileExt = QStringLiteral(APPLICATION_DOTVIRTUALFILE_SUFFIX); diff --git a/src/gui/application.h b/src/gui/application.h index c41b4cae4..5bd412077 100644 --- a/src/gui/application.h +++ b/src/gui/application.h @@ -107,14 +107,6 @@ protected slots: void slotSystemOnlineConfigurationChanged(QNetworkConfiguration); void slotGuiIsShowingSettings(); -private slots: - void handleAccountSetupFromCommandLine(); - void fetchUserName(); - void checkLastModifiedWithPropfind(); - void accountSetupFromCommandLinePropfindHandleSuccess(); - void accountSetupFromCommandLinePropfindHandleFailure(); - void printAccountSetupFromCommandLineStatusAndExit(const QString &status, bool isFailure); - private: void setHelp(); @@ -140,11 +132,6 @@ private: bool _quitInstance = false; QString _logFile; QString _logDir; - QString _appPassword; - QString _userId; - QString _baseDir; - QString _remoteDir; - QUrl _serverUrl; int _logExpire; bool _logFlush; bool _logDebug; @@ -158,8 +145,6 @@ private: QNetworkConfigurationManager _networkConfigurationManager; QTimer _checkConnectionTimer; - AccountPtr _account; - #if defined(WITH_CRASHREPORTER) QScopedPointer _crashHandler; #endif From 07be556ecabf422c902245ae9cc3902d66e4cd55 Mon Sep 17 00:00:00 2001 From: alex-z Date: Thu, 29 Dec 2022 18:28:28 +0100 Subject: [PATCH 3/4] Fix review comments from Claudio II. Signed-off-by: alex-z --- src/gui/CMakeLists.txt | 2 + src/gui/accountsetupcommandlinemanager.cpp | 227 +------------------- src/gui/accountsetupcommandlinemanager.h | 47 +--- src/gui/accountsetupfromcommandlinejob.cpp | 238 +++++++++++++++++++++ src/gui/accountsetupfromcommandlinejob.h | 66 ++++++ src/gui/application.cpp | 2 +- 6 files changed, 318 insertions(+), 264 deletions(-) create mode 100644 src/gui/accountsetupfromcommandlinejob.cpp create mode 100644 src/gui/accountsetupfromcommandlinejob.h diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index ed1820809..af02aa429 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -65,6 +65,8 @@ set(client_SRCS accountmanager.cpp accountsettings.h accountsettings.cpp + accountsetupfromcommandlinejob.h + accountsetupfromcommandlinejob.cpp accountsetupcommandlinemanager.h accountsetupcommandlinemanager.cpp application.h diff --git a/src/gui/accountsetupcommandlinemanager.cpp b/src/gui/accountsetupcommandlinemanager.cpp index dbc5c4604..39fe895ca 100644 --- a/src/gui/accountsetupcommandlinemanager.cpp +++ b/src/gui/accountsetupcommandlinemanager.cpp @@ -13,225 +13,12 @@ */ #include "accountsetupcommandlinemanager.h" - -#include "accountmanager.h" -#include "creds/webflowcredentials.h" -#include "filesystem.h" -#include "folder.h" -#include "folderman.h" -#include "networkjobs.h" - -#include -#include -#include -#include -#include -#include +#include "accountsetupfromcommandlinejob.h" namespace OCC { Q_LOGGING_CATEGORY(lcAccountSetupCommandLineManager, "nextcloud.gui.accountsetupcommandlinemanager", QtInfoMsg) -AccountSetupFromCommandLineJob::AccountSetupFromCommandLineJob(QString appPassword, - QString userId, - QUrl serverUrl, - QString localDirPath, - bool nonVfsMode, - QString remoteDirPath, - QObject *parent) - : QObject(parent) - , _appPassword(appPassword) - , _userId(userId) - , _serverUrl(serverUrl) - , _localDirPath(localDirPath) - , _nonVfsMode(nonVfsMode) - , _remoteDirPath(remoteDirPath) -{ -} - -void AccountSetupFromCommandLineJob::handleAccountSetupFromCommandLine() -{ - if (AccountManager::instance()->accountFromUserId(QStringLiteral("%1@%2").arg(_userId).arg(_serverUrl.host()))) { - printAccountSetupFromCommandLineStatusAndExit(QStringLiteral("Account %1 already exists!").arg(QDir::toNativeSeparators(_userId)), true); - return; - } - - if (!_localDirPath.isEmpty()) { - QDir dir(_localDirPath); - if (dir.exists() && !dir.isEmpty()) { - printAccountSetupFromCommandLineStatusAndExit( - QStringLiteral("Local folder %1 already exists and is non-empty!").arg(QDir::toNativeSeparators(_localDirPath)), - true); - return; - } - - qCInfo(lcAccountSetupCommandLineManager) << "Creating folder" << _localDirPath; - if (!dir.exists() && !dir.mkpath(".")) { - printAccountSetupFromCommandLineStatusAndExit( - QStringLiteral("Folder creation failed. Could not create local folder %1").arg(QDir::toNativeSeparators(_localDirPath)), - true); - return; - } - - FileSystem::setFolderMinimumPermissions(_localDirPath); - Utility::setupFavLink(_localDirPath); - } - - const auto credentials = new WebFlowCredentials(_userId, _appPassword); - _account = AccountManager::createAccount(); - _account->setCredentials(credentials); - _account->setUrl(_serverUrl); - - fetchUserName(); -} - -void AccountSetupFromCommandLineJob::checkLastModifiedWithPropfind() -{ - const auto job = new PropfindJob(_account, "/", this); - job->setIgnoreCredentialFailure(true); - // There is custom redirect handling in the error handler, - // so don't automatically follow redirects. - job->setFollowRedirects(false); - job->setProperties(QList() << QByteArrayLiteral("getlastmodified")); - connect(job, &PropfindJob::result, this, &AccountSetupFromCommandLineJob::accountSetupFromCommandLinePropfindHandleSuccess); - connect(job, &PropfindJob::finishedWithError, this, &AccountSetupFromCommandLineJob::accountSetupFromCommandLinePropfindHandleFailure); - job->start(); -} - -void AccountSetupFromCommandLineJob::accountSetupFromCommandLinePropfindHandleSuccess() -{ - const auto accountManager = AccountManager::instance(); - const auto accountState = accountManager->addAccount(_account); - accountManager->save(); - - if (!_localDirPath.isEmpty()) { - FolderDefinition definition; - definition.localPath = _localDirPath; - definition.targetPath = FolderDefinition::prepareTargetPath(!_remoteDirPath.isEmpty() ? _remoteDirPath : QStringLiteral("/")); - definition.virtualFilesMode = _nonVfsMode ? Vfs::Off : bestAvailableVfsMode(); - - const auto folderMan = FolderMan::instance(); - - definition.ignoreHiddenFiles = folderMan->ignoreHiddenFiles(); - definition.alias = folderMan->map().size() > 0 ? QString::number(folderMan->map().size()) : QString::number(0); - - if (folderMan->navigationPaneHelper().showInExplorerNavigationPane()) { - definition.navigationPaneClsid = QUuid::createUuid(); - } - - folderMan->setSyncEnabled(false); - - if (const auto folder = folderMan->addFolder(accountState, definition)) { - if (definition.virtualFilesMode != Vfs::Off) { - folder->setRootPinState(PinState::OnlineOnly); - } - folder->journalDb()->setSelectiveSyncList(SyncJournalDb::SelectiveSyncWhiteList, QStringList() << QLatin1String("/")); - qCInfo(lcAccountSetupCommandLineManager) << QStringLiteral("Folder %1 setup from command line success.").arg(definition.localPath); - printAccountSetupFromCommandLineStatusAndExit(QStringLiteral("Account %1 setup from command line success.").arg(_account->displayName()), false); - } else { - accountManager->deleteAccount(accountState); - printAccountSetupFromCommandLineStatusAndExit( - QStringLiteral("Account %1 setup from command line failed, due to folder creation failure.").arg(_account->displayName()), - false); - } - } else { - qCInfo(lcAccountSetupCommandLineManager) << QStringLiteral("Set up a new account without a folder."); - printAccountSetupFromCommandLineStatusAndExit(QStringLiteral("Account %1 setup from command line success.").arg(_account->displayName()), false); - } -} - -void AccountSetupFromCommandLineJob::accountSetupFromCommandLinePropfindHandleFailure() -{ - const auto job = qobject_cast(sender()); - if (!job) { - printAccountSetupFromCommandLineStatusAndExit(QStringLiteral("Cannot check for authed redirects. This slot should be invoked from PropfindJob!"), true); - return; - } - const auto reply = job->reply(); - - QString errorMsg; - - // If there were redirects on the *authed* requests, also store - // the updated server URL, similar to redirects on status.php. - QUrl redirectUrl = reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl(); - if (!redirectUrl.isEmpty()) { - qCInfo(lcAccountSetupCommandLineManager) << "Authed request was redirected to" << redirectUrl.toString(); - - // strip the expected path - auto path = redirectUrl.path(); - static QString expectedPath = "/" + _account->davPath(); - if (path.endsWith(expectedPath)) { - path.chop(expectedPath.size()); - redirectUrl.setPath(path); - - qCInfo(lcAccountSetupCommandLineManager) << "Setting account url to" << redirectUrl.toString(); - _account->setUrl(redirectUrl); - checkLastModifiedWithPropfind(); - } - errorMsg = tr("The authenticated request to the server was redirected to " - "\"%1\". The URL is bad, the server is misconfigured.") - .arg(Utility::escape(redirectUrl.toString())); - - // A 404 is actually a success: we were authorized to know that the folder does - // not exist. It will be created later... - } else if (reply->error() == QNetworkReply::ContentNotFoundError) { - accountSetupFromCommandLinePropfindHandleSuccess(); - } else if (reply->error() != QNetworkReply::NoError) { - if (!_account->credentials()->stillValid(reply)) { - errorMsg = tr("Access forbidden by server. To verify that you have proper access, " - "click here to access the service with your browser.") - .arg(Utility::escape(_account->url().toString())); - } else { - errorMsg = job->errorStringParsingBody(); - } - // Something else went wrong, maybe the response was 200 but with invalid data. - } else { - errorMsg = tr("There was an invalid response to an authenticated WebDAV request"); - } - printAccountSetupFromCommandLineStatusAndExit( - QStringLiteral("Account %1 setup from command line failed with error: %2.").arg(_account->displayName()).arg(errorMsg), - true); -} - -void AccountSetupFromCommandLineJob::printAccountSetupFromCommandLineStatusAndExit(const QString &status, bool isFailure) -{ - if (isFailure) { - qCWarning(lcAccountSetupCommandLineManager) << status; - } else { - qCInfo(lcAccountSetupCommandLineManager) << status; - } - QTimer::singleShot(0, this, [this, isFailure]() { - this->deleteLater(); - if (!isFailure) { - qApp->quit(); - } else { - qApp->exit(1); - } - }); -} - -void AccountSetupFromCommandLineJob::fetchUserName() -{ - const auto fetchUserNameJob = new JsonApiJob(_account, QStringLiteral("/ocs/v1.php/cloud/user")); - connect(fetchUserNameJob, &JsonApiJob::jsonReceived, this, [this](const QJsonDocument &json, int statusCode) { - sender()->deleteLater(); - - if (statusCode != 100) { - printAccountSetupFromCommandLineStatusAndExit("Could not fetch username.", true); - return; - } - - const auto objData = json.object().value("ocs").toObject().value("data").toObject(); - const auto userId = objData.value("id").toString(""); - const auto displayName = objData.value("display-name").toString(""); - _account->setDavUser(userId); - _account->setDavDisplayName(displayName); - - checkLastModifiedWithPropfind(); - }); - fetchUserNameJob->start(); -} - bool AccountSetupCommandLineManager::parseCommandlineOption(const QString &option, QStringListIterator &optionsIterator, QString &errorMessage) { if (option == QStringLiteral("--apppassword")) { @@ -269,12 +56,12 @@ bool AccountSetupCommandLineManager::parseCommandlineOption(const QString &optio } else { errorMessage = QStringLiteral("userid not specified"); } - } else if (option == QLatin1String("--nonvfsmode")) { + } else if (option == QLatin1String("--isvfsenabled")) { if (optionsIterator.hasNext() && !optionsIterator.peekNext().startsWith(QLatin1String("--"))) { - _nonVfsMode = optionsIterator.next().toInt() != 0; + _isVfsEnabled = optionsIterator.next().toInt() != 0; return true; } else { - errorMessage = QStringLiteral("nonVfsMode not specified"); + errorMessage = QStringLiteral("isvfsenabled not specified"); } } return false; @@ -289,7 +76,7 @@ void AccountSetupCommandLineManager::setupAccountFromCommandLine(QObject *parent { if (isCommandLineParsed()) { qCInfo(lcAccountSetupCommandLineManager) << QStringLiteral("Command line has been parsed and account setup parameters have been found. Attempting setup a new account %1...").arg(_userId); - const auto accountSetupJob = new AccountSetupFromCommandLineJob(_appPassword, _userId, _serverUrl, _localDirPath, _nonVfsMode, _remoteDirPath, parent); + const auto accountSetupJob = new AccountSetupFromCommandLineJob(_appPassword, _userId, _serverUrl, _localDirPath, _isVfsEnabled, _remoteDirPath, parent); accountSetupJob->handleAccountSetupFromCommandLine(); } else { qCInfo(lcAccountSetupCommandLineManager) << QStringLiteral("No account setup parameters have been found, or they are invalid. Proceed with normal startup..."); @@ -299,7 +86,7 @@ void AccountSetupCommandLineManager::setupAccountFromCommandLine(QObject *parent _serverUrl.clear(); _remoteDirPath.clear(); _localDirPath.clear(); - _nonVfsMode = false; + _isVfsEnabled = true; } QString AccountSetupCommandLineManager::_appPassword; @@ -307,5 +94,5 @@ QString AccountSetupCommandLineManager::_userId; QUrl AccountSetupCommandLineManager::_serverUrl; QString AccountSetupCommandLineManager::_remoteDirPath; QString AccountSetupCommandLineManager::_localDirPath; -bool AccountSetupCommandLineManager::_nonVfsMode = false; +bool AccountSetupCommandLineManager::_isVfsEnabled = true; } diff --git a/src/gui/accountsetupcommandlinemanager.h b/src/gui/accountsetupcommandlinemanager.h index 2875febdc..f9b1e9279 100644 --- a/src/gui/accountsetupcommandlinemanager.h +++ b/src/gui/accountsetupcommandlinemanager.h @@ -14,51 +14,12 @@ #pragma once -#include "account.h" -#include "accountstate.h" - +#include +#include #include +#include namespace OCC { - -class AccountSetupFromCommandLineJob : public QObject -{ - Q_OBJECT -public: - AccountSetupFromCommandLineJob(QString appPassword, - QString userId, - QUrl serverUrl, - QString localDirPath = {}, - bool nonVfsMode = false, - QString remoteDirPath = QStringLiteral("/"), - QObject *parent = nullptr); - -public slots: - void handleAccountSetupFromCommandLine(); - -private slots: - void checkLastModifiedWithPropfind(); - - void accountSetupFromCommandLinePropfindHandleSuccess(); - - void accountSetupFromCommandLinePropfindHandleFailure(); - - void printAccountSetupFromCommandLineStatusAndExit(const QString &status, bool isFailure); - - void fetchUserName(); - -private: - QString _appPassword; - QString _userId; - QUrl _serverUrl; - QString _localDirPath; - bool _nonVfsMode = false; - QString _remoteDirPath; - - AccountPtr _account; -}; - - class AccountSetupCommandLineManager { public: @@ -76,7 +37,7 @@ private: static QUrl _serverUrl; static QString _remoteDirPath; static QString _localDirPath; - static bool _nonVfsMode; + static bool _isVfsEnabled; }; } diff --git a/src/gui/accountsetupfromcommandlinejob.cpp b/src/gui/accountsetupfromcommandlinejob.cpp new file mode 100644 index 000000000..eeff0cfb0 --- /dev/null +++ b/src/gui/accountsetupfromcommandlinejob.cpp @@ -0,0 +1,238 @@ +/* + * Copyright (C) by Oleksandr Zolotov + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include "accountsetupfromcommandlinejob.h" + +#include "accountmanager.h" +#include "accountstate.h" +#include "creds/webflowcredentials.h" +#include "filesystem.h" +#include "folder.h" +#include "folderman.h" +#include "networkjobs.h" + +#include +#include +#include +#include + +namespace OCC +{ +Q_LOGGING_CATEGORY(lcAccountSetupCommandLineJob, "nextcloud.gui.accountsetupcommandlinejob", QtInfoMsg) + +AccountSetupFromCommandLineJob::AccountSetupFromCommandLineJob(QString appPassword, + QString userId, + QUrl serverUrl, + QString localDirPath, + bool isVfsEnabled, + QString remoteDirPath, + QObject *parent) + : QObject(parent) + , _appPassword(appPassword) + , _userId(userId) + , _serverUrl(serverUrl) + , _localDirPath(localDirPath) + , _isVfsEnabled(isVfsEnabled) + , _remoteDirPath(remoteDirPath) +{ +} + +void AccountSetupFromCommandLineJob::handleAccountSetupFromCommandLine() +{ + if (AccountManager::instance()->accountFromUserId(QStringLiteral("%1@%2").arg(_userId).arg(_serverUrl.host()))) { + printAccountSetupFromCommandLineStatusAndExit(QStringLiteral("Account %1 already exists!").arg(QDir::toNativeSeparators(_userId)), true); + return; + } + + if (!_localDirPath.isEmpty()) { + QDir dir(_localDirPath); + if (dir.exists() && !dir.isEmpty()) { + printAccountSetupFromCommandLineStatusAndExit( + QStringLiteral("Local folder %1 already exists and is non-empty!").arg(QDir::toNativeSeparators(_localDirPath)), + true); + return; + } + + qCInfo(lcAccountSetupCommandLineJob) << "Creating folder" << _localDirPath; + if (!dir.exists() && !dir.mkpath(".")) { + printAccountSetupFromCommandLineStatusAndExit( + QStringLiteral("Folder creation failed. Could not create local folder %1").arg(QDir::toNativeSeparators(_localDirPath)), + true); + return; + } + + FileSystem::setFolderMinimumPermissions(_localDirPath); + Utility::setupFavLink(_localDirPath); + } + + const auto credentials = new WebFlowCredentials(_userId, _appPassword); + _account = AccountManager::createAccount(); + _account->setCredentials(credentials); + _account->setUrl(_serverUrl); + + fetchUserName(); +} + +void AccountSetupFromCommandLineJob::checkLastModifiedWithPropfind() +{ + const auto job = new PropfindJob(_account, "/", this); + job->setIgnoreCredentialFailure(true); + // There is custom redirect handling in the error handler, + // so don't automatically follow redirects. + job->setFollowRedirects(false); + job->setProperties(QList() << QByteArrayLiteral("getlastmodified")); + connect(job, &PropfindJob::result, this, &AccountSetupFromCommandLineJob::accountSetupFromCommandLinePropfindHandleSuccess); + connect(job, &PropfindJob::finishedWithError, this, &AccountSetupFromCommandLineJob::accountSetupFromCommandLinePropfindHandleFailure); + job->start(); +} + +void AccountSetupFromCommandLineJob::accountSetupFromCommandLinePropfindHandleSuccess() +{ + const auto accountManager = AccountManager::instance(); + const auto accountState = accountManager->addAccount(_account); + accountManager->save(); + + if (!_localDirPath.isEmpty()) { + setupLocalSyncFolder(accountState); + } else { + qCInfo(lcAccountSetupCommandLineJob) << QStringLiteral("Set up a new account without a folder."); + printAccountSetupFromCommandLineStatusAndExit(QStringLiteral("Account %1 setup from command line success.").arg(_account->displayName()), false); + } +} + +void AccountSetupFromCommandLineJob::accountSetupFromCommandLinePropfindHandleFailure() +{ + const auto job = qobject_cast(sender()); + if (!job) { + printAccountSetupFromCommandLineStatusAndExit(QStringLiteral("Cannot check for authed redirects. This slot should be invoked from PropfindJob!"), true); + return; + } + const auto reply = job->reply(); + + QString errorMsg; + + // If there were redirects on the *authed* requests, also store + // the updated server URL, similar to redirects on status.php. + QUrl redirectUrl = reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl(); + if (!redirectUrl.isEmpty()) { + qCInfo(lcAccountSetupCommandLineJob) << "Authed request was redirected to" << redirectUrl.toString(); + + // strip the expected path + auto path = redirectUrl.path(); + static QString expectedPath = "/" + _account->davPath(); + if (path.endsWith(expectedPath)) { + path.chop(expectedPath.size()); + redirectUrl.setPath(path); + + qCInfo(lcAccountSetupCommandLineJob) << "Setting account url to" << redirectUrl.toString(); + _account->setUrl(redirectUrl); + checkLastModifiedWithPropfind(); + } + errorMsg = tr("The authenticated request to the server was redirected to " + "\"%1\". The URL is bad, the server is misconfigured.") + .arg(Utility::escape(redirectUrl.toString())); + + // A 404 is actually a success: we were authorized to know that the folder does + // not exist. It will be created later... + } else if (reply->error() == QNetworkReply::ContentNotFoundError) { + accountSetupFromCommandLinePropfindHandleSuccess(); + } else if (reply->error() != QNetworkReply::NoError) { + if (!_account->credentials()->stillValid(reply)) { + errorMsg = tr("Access forbidden by server. To verify that you have proper access, " + "click here to access the service with your browser.") + .arg(Utility::escape(_account->url().toString())); + } else { + errorMsg = job->errorStringParsingBody(); + } + // Something else went wrong, maybe the response was 200 but with invalid data. + } else { + errorMsg = tr("There was an invalid response to an authenticated WebDAV request"); + } + printAccountSetupFromCommandLineStatusAndExit( + QStringLiteral("Account %1 setup from command line failed with error: %2.").arg(_account->displayName()).arg(errorMsg), + true); +} + +void AccountSetupFromCommandLineJob::setupLocalSyncFolder(AccountState *accountState) +{ + FolderDefinition definition; + definition.localPath = _localDirPath; + definition.targetPath = FolderDefinition::prepareTargetPath(!_remoteDirPath.isEmpty() ? _remoteDirPath : QStringLiteral("/")); + definition.virtualFilesMode = _isVfsEnabled ? bestAvailableVfsMode() : Vfs::Off; + + const auto folderMan = FolderMan::instance(); + + definition.ignoreHiddenFiles = folderMan->ignoreHiddenFiles(); + definition.alias = folderMan->map().size() > 0 ? QString::number(folderMan->map().size()) : QString::number(0); + + if (folderMan->navigationPaneHelper().showInExplorerNavigationPane()) { + definition.navigationPaneClsid = QUuid::createUuid(); + } + + folderMan->setSyncEnabled(false); + + if (const auto folder = folderMan->addFolder(accountState, definition)) { + if (definition.virtualFilesMode != Vfs::Off) { + folder->setRootPinState(PinState::OnlineOnly); + } + folder->journalDb()->setSelectiveSyncList(SyncJournalDb::SelectiveSyncWhiteList, QStringList() << QLatin1String("/")); + qCInfo(lcAccountSetupCommandLineJob) << QStringLiteral("Folder %1 setup from command line success.").arg(definition.localPath); + printAccountSetupFromCommandLineStatusAndExit(QStringLiteral("Account %1 setup from command line success.").arg(_account->displayName()), false); + } else { + AccountManager::instance()->deleteAccount(accountState); + printAccountSetupFromCommandLineStatusAndExit( + QStringLiteral("Account %1 setup from command line failed, due to folder creation failure.").arg(_account->displayName()), + false); + } +} + +void AccountSetupFromCommandLineJob::printAccountSetupFromCommandLineStatusAndExit(const QString &status, bool isFailure) +{ + if (isFailure) { + qCWarning(lcAccountSetupCommandLineJob) << status; + } else { + qCInfo(lcAccountSetupCommandLineJob) << status; + } + QTimer::singleShot(0, this, [this, isFailure]() { + this->deleteLater(); + if (!isFailure) { + qApp->quit(); + } else { + qApp->exit(1); + } + }); +} + +void AccountSetupFromCommandLineJob::fetchUserName() +{ + const auto fetchUserNameJob = new JsonApiJob(_account, QStringLiteral("/ocs/v1.php/cloud/user")); + connect(fetchUserNameJob, &JsonApiJob::jsonReceived, this, [this](const QJsonDocument &json, int statusCode) { + sender()->deleteLater(); + + if (statusCode != 100) { + printAccountSetupFromCommandLineStatusAndExit("Could not fetch username.", true); + return; + } + + const auto objData = json.object().value("ocs").toObject().value("data").toObject(); + const auto userId = objData.value("id").toString(""); + const auto displayName = objData.value("display-name").toString(""); + _account->setDavUser(userId); + _account->setDavDisplayName(displayName); + + checkLastModifiedWithPropfind(); + }); + fetchUserNameJob->start(); +} +} diff --git a/src/gui/accountsetupfromcommandlinejob.h b/src/gui/accountsetupfromcommandlinejob.h new file mode 100644 index 000000000..8485c72a1 --- /dev/null +++ b/src/gui/accountsetupfromcommandlinejob.h @@ -0,0 +1,66 @@ +/* + * Copyright (C) by Oleksandr Zolotov + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#pragma once + +#include "account.h" + +#include +#include +#include + +namespace OCC +{ +class AccountState; + +class AccountSetupFromCommandLineJob : public QObject +{ + Q_OBJECT + +public: + AccountSetupFromCommandLineJob(QString appPassword, + QString userId, + QUrl serverUrl, + QString localDirPath = {}, + bool nonVfsMode = false, + QString remoteDirPath = QStringLiteral("/"), + QObject *parent = nullptr); + +public slots: + void handleAccountSetupFromCommandLine(); + +private slots: + void checkLastModifiedWithPropfind(); + + void accountSetupFromCommandLinePropfindHandleSuccess(); + + void accountSetupFromCommandLinePropfindHandleFailure(); + + void setupLocalSyncFolder(AccountState *accountState); + + void printAccountSetupFromCommandLineStatusAndExit(const QString &status, bool isFailure); + + void fetchUserName(); + +private: + QString _appPassword; + QString _userId; + QUrl _serverUrl; + QString _localDirPath; + bool _isVfsEnabled = true; + QString _remoteDirPath; + + AccountPtr _account; +}; +} diff --git a/src/gui/application.cpp b/src/gui/application.cpp index 54d696ab8..eeaab85da 100644 --- a/src/gui/application.cpp +++ b/src/gui/application.cpp @@ -90,7 +90,7 @@ namespace { " --userid : userId (username as on the server) to pass when creating an account via command-line.\n" " --apppassword : appPassword to pass when creating an account via command-line.\n" " --localdirpath : (optional) path where to create a local sync folder when creating an account via command-line.\n" - " --nonvfsmode : whether to set up a non-VFS folder (1 for 'yes' or 0 for 'no') when creating an account via command-line.\n" + " --isvfsenabled : whether to set a VFS or non-VFS folder (1 for 'yes' or 0 for 'no') when creating an account via command-line.\n" " --remotedirpath : (optional) path to a remote subfolder when creating an account via command-line.\n" " --serverurl : a server URL to use when creating an account via command-line.\n"; From cecd24b6c526e087c7cfd2382fea4dc9eb7cba35 Mon Sep 17 00:00:00 2001 From: alex-z Date: Fri, 30 Dec 2022 14:45:47 +0100 Subject: [PATCH 4/4] Fix review comments from Claudio III. Signed-off-by: alex-z --- src/gui/accountsetupcommandlinemanager.cpp | 36 ++++++++++++++++------ src/gui/accountsetupcommandlinemanager.h | 30 +++++++++++------- src/gui/application.cpp | 12 +++++--- 3 files changed, 52 insertions(+), 26 deletions(-) diff --git a/src/gui/accountsetupcommandlinemanager.cpp b/src/gui/accountsetupcommandlinemanager.cpp index 39fe895ca..a604cb53b 100644 --- a/src/gui/accountsetupcommandlinemanager.cpp +++ b/src/gui/accountsetupcommandlinemanager.cpp @@ -19,6 +19,29 @@ namespace OCC { Q_LOGGING_CATEGORY(lcAccountSetupCommandLineManager, "nextcloud.gui.accountsetupcommandlinemanager", QtInfoMsg) +AccountSetupCommandLineManager *AccountSetupCommandLineManager::_instance = nullptr; + +AccountSetupCommandLineManager::AccountSetupCommandLineManager(QObject *parent) + : QObject{parent} +{ +} + +AccountSetupCommandLineManager *AccountSetupCommandLineManager::instance() +{ + if (!_instance) { + _instance = new AccountSetupCommandLineManager(); + } + return _instance; +} + +void AccountSetupCommandLineManager::destroy() +{ + if (_instance) { + _instance->deleteLater(); + _instance = nullptr; + } +} + bool AccountSetupCommandLineManager::parseCommandlineOption(const QString &option, QStringListIterator &optionsIterator, QString &errorMessage) { if (option == QStringLiteral("--apppassword")) { @@ -67,16 +90,16 @@ bool AccountSetupCommandLineManager::parseCommandlineOption(const QString &optio return false; } -bool AccountSetupCommandLineManager::isCommandLineParsed() +bool AccountSetupCommandLineManager::isCommandLineParsed() const { return !_appPassword.isEmpty() && !_userId.isEmpty() && _serverUrl.isValid(); } -void AccountSetupCommandLineManager::setupAccountFromCommandLine(QObject *parent) +void AccountSetupCommandLineManager::setupAccountFromCommandLine() { if (isCommandLineParsed()) { qCInfo(lcAccountSetupCommandLineManager) << QStringLiteral("Command line has been parsed and account setup parameters have been found. Attempting setup a new account %1...").arg(_userId); - const auto accountSetupJob = new AccountSetupFromCommandLineJob(_appPassword, _userId, _serverUrl, _localDirPath, _isVfsEnabled, _remoteDirPath, parent); + const auto accountSetupJob = new AccountSetupFromCommandLineJob(_appPassword, _userId, _serverUrl, _localDirPath, _isVfsEnabled, _remoteDirPath, parent()); accountSetupJob->handleAccountSetupFromCommandLine(); } else { qCInfo(lcAccountSetupCommandLineManager) << QStringLiteral("No account setup parameters have been found, or they are invalid. Proceed with normal startup..."); @@ -88,11 +111,4 @@ void AccountSetupCommandLineManager::setupAccountFromCommandLine(QObject *parent _localDirPath.clear(); _isVfsEnabled = true; } - -QString AccountSetupCommandLineManager::_appPassword; -QString AccountSetupCommandLineManager::_userId; -QUrl AccountSetupCommandLineManager::_serverUrl; -QString AccountSetupCommandLineManager::_remoteDirPath; -QString AccountSetupCommandLineManager::_localDirPath; -bool AccountSetupCommandLineManager::_isVfsEnabled = true; } diff --git a/src/gui/accountsetupcommandlinemanager.h b/src/gui/accountsetupcommandlinemanager.h index f9b1e9279..585627e85 100644 --- a/src/gui/accountsetupcommandlinemanager.h +++ b/src/gui/accountsetupcommandlinemanager.h @@ -20,24 +20,32 @@ #include namespace OCC { -class AccountSetupCommandLineManager +class AccountSetupCommandLineManager : public QObject { + Q_OBJECT + public: - [[nodiscard]] static bool parseCommandlineOption(const QString &option, QStringListIterator &optionsIterator, QString &errorMessage); + [[nodiscard]] static AccountSetupCommandLineManager *instance(); + static void destroy(); - [[nodiscard]] static bool isCommandLineParsed(); + [[nodiscard]] bool parseCommandlineOption(const QString &option, QStringListIterator &optionsIterator, QString &errorMessage); - static void setupAccountFromCommandLine(QObject *parent = nullptr); + [[nodiscard]] bool isCommandLineParsed() const; + +public slots: + void setupAccountFromCommandLine(); private: - explicit AccountSetupCommandLineManager() = delete; + explicit AccountSetupCommandLineManager(QObject *parent = nullptr); - static QString _appPassword; - static QString _userId; - static QUrl _serverUrl; - static QString _remoteDirPath; - static QString _localDirPath; - static bool _isVfsEnabled; + static AccountSetupCommandLineManager *_instance; + + QString _appPassword; + QString _userId; + QUrl _serverUrl; + QString _remoteDirPath; + QString _localDirPath; + bool _isVfsEnabled; }; } diff --git a/src/gui/application.cpp b/src/gui/application.cpp index eeaab85da..8af99a7df 100644 --- a/src/gui/application.cpp +++ b/src/gui/application.cpp @@ -419,9 +419,10 @@ Application::Application(int &argc, char **argv) handleEditLocallyFromOptions(); - if (AccountSetupCommandLineManager::isCommandLineParsed()) { - AccountSetupCommandLineManager::setupAccountFromCommandLine(this); + if (AccountSetupCommandLineManager::instance()->isCommandLineParsed()) { + AccountSetupCommandLineManager::instance()->setupAccountFromCommandLine(); } + AccountSetupCommandLineManager::destroy(); } Application::~Application() @@ -590,9 +591,10 @@ void Application::slotParseMessage(const QString &msg, QObject *) handleEditLocallyFromOptions(); - if (AccountSetupCommandLineManager::isCommandLineParsed()) { - AccountSetupCommandLineManager::setupAccountFromCommandLine(this); + if (AccountSetupCommandLineManager::instance()->isCommandLineParsed()) { + AccountSetupCommandLineManager::instance()->setupAccountFromCommandLine(); } + AccountSetupCommandLineManager::destroy(); } else if (msg.startsWith(QLatin1String("MSG_SHOWMAINDIALOG"))) { qCInfo(lcApplication) << "Running for" << _startedAt.elapsed() / 1000.0 << "sec"; @@ -681,7 +683,7 @@ void Application::parseOptions(const QStringList &options) } else { QString errorMessage; - if (!AccountSetupCommandLineManager::parseCommandlineOption(option, it, errorMessage)) { + if (!AccountSetupCommandLineManager::instance()->parseCommandlineOption(option, it, errorMessage)) { if (!errorMessage.isEmpty()) { showHint(errorMessage.toStdString()); return;