Allow setting up an account with apppasword and folder via command-line arguments...
authoralex-z <blackslayer4@gmail.com>
Fri, 23 Dec 2022 14:24:19 +0000 (15:24 +0100)
committerallexzander <allexzander@users.noreply.github.com>
Fri, 30 Dec 2022 13:46:06 +0000 (14:46 +0100)
Signed-off-by: alex-z <blackslayer4@gmail.com>
src/gui/application.cpp
src/gui/application.h

index b75ebb12154abebae0df1ffcaec2add2ca05af04..4106afe38e3b6810e355952cdb4290f6b3f7432a 100644 (file)
@@ -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<QByteArray>() << "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<PropfindJob *>(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, "
+                          "<a href=\"%1\">click here</a> 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);
index 5bd412077eda40c9fd80a94cbc3472cbd51008a1..c41b4cae4d8b6384d505759928beccc748fdc950 100644 (file)
@@ -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<CrashReporter::Handler> _crashHandler;
 #endif