Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ build*
cscope.*
tags
t1.cfg
.idea

## Ignore Visual Studio Code config & environment files

Expand Down
4 changes: 4 additions & 0 deletions src/gui/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,8 @@ set(client_SRCS
proxyauthdialog.cpp
tooltipupdater.h
tooltipupdater.cpp
urischemehandler.h
urischemehandler.cpp
notificationconfirmjob.h
notificationconfirmjob.cpp
guiutility.h
Expand Down Expand Up @@ -241,6 +243,8 @@ set(client_SRCS
wizard/accountwizardcontroller.cpp
wizard/flow2authwidget.h
wizard/flow2authwidget.cpp
wizard/providersignuppage.h
wizard/providersignuppage.cpp
wizard/linklabel.h
wizard/linklabel.cpp
integration/fileactionsmodel.h
Expand Down
70 changes: 52 additions & 18 deletions src/gui/application.cpp
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
/*
* SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
* SPDX-FileCopyrightText: 2014 ownCloud GmbH
Expand All @@ -15,13 +15,13 @@
#include "configfile.h"
#include "connectionvalidator.h"
#include "creds/abstractcredentials.h"
#include "editlocallymanager.h"
#include "folder.h"
#include "folderman.h"
#include "logger.h"
#include "pushnotifications.h"
#include "socketapi/socketapi.h"
#include "theme.h"
#include "urischemehandler.h"

#if defined(BUILD_UPDATER)
#include "updater/ocupdater.h"
Expand Down Expand Up @@ -50,7 +50,6 @@
#include <QMessageBox>
#include <QDesktopServices>
#include <QGuiApplication>
#include <QUrlQuery>
#include <QVersionNumber>
#include <QRandomGenerator>
#include <QHttp2Configuration>
Expand Down Expand Up @@ -488,7 +487,7 @@

_gui->createTray();

handleEditLocallyFromOptions();
handleUriFromOptions();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Suppress the startup wizard after login URIs

When the app is cold-started with an nc://login URI and no accounts, this call starts the login-flow wizard before the queued initial slotCheckConnection from Application::Application runs. That later check still sees an empty account list, calls slotOpenSettingsDialog(), and slotNewAccountWizard() uses forceRestart=true, so it rejects the URI login wizard and replaces it with the normal setup wizard before the Flow v2 auth can complete; skip or stop that initial empty-account check after handling a login URI.

Useful? React with 👍 / 👎.


#ifdef Q_OS_MACOS
// If any sync folder needs sandbox reapproval after upgrading to v33+,
Expand Down Expand Up @@ -666,7 +665,7 @@
qApp->quit();
}

handleEditLocallyFromOptions();
handleUriFromOptions();

if (AccountSetupCommandLineManager::instance()->isCommandLineParsed()) {
AccountSetupCommandLineManager::instance()->setupAccountFromCommandLine();
Expand Down Expand Up @@ -709,7 +708,7 @@
qCDebug(lcApplication) << "Failed to move the old config directory to its new location (" << oldDir << "to" << confDir << ")";

// Try to move the files one by one
if (QFileInfo(confDir).isDir() || QDir().mkdir(confDir)) {

Check failure on line 711 in src/gui/application.cpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove this TOCTOU race condition window when accessing files

See more on https://sonarcloud.io/project/issues?id=nextcloud_desktop&issues=AZ8jAAaB6VhQ52Y1EjB1&open=AZ8jAAaB6VhQ52Y1EjB1&pullRequest=9969
const QStringList filesList = QDir(oldDir).entryList(QDir::Files);
qCDebug(lcApplication) << "Will move the individual files" << filesList;
for (const auto &name : filesList) {
Expand All @@ -726,7 +725,7 @@
}
}

AccountManager::AccountsRestoreResult Application::restoreLegacyAccount()

Check warning on line 728 in src/gui/application.cpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

This function should be declared "const".

See more on https://sonarcloud.io/project/issues?id=nextcloud_desktop&issues=AZ8jAAaB6VhQ52Y1EjB2&open=AZ8jAAaB6VhQ52Y1EjB2&pullRequest=9969
{
ConfigFile cfg;
const auto tryMigrate = cfg.overrideServerUrl().isEmpty();
Expand Down Expand Up @@ -819,6 +818,12 @@
void Application::slotCheckConnection()
{
if (AccountManager::instance()->accounts().isEmpty()) {
if (_suppressNextEmptyAccountCheck) {
qCInfo(lcApplication) << "Suppressing empty-account check after handling login URI.";
_suppressNextEmptyAccountCheck = false;
return;
}

// let gui open the setup wizard
_gui->slotOpenSettingsDialog();

Expand Down Expand Up @@ -972,18 +977,21 @@
} else if (option.endsWith(QStringLiteral(APPLICATION_DOTVIRTUALFILE_SUFFIX))) {
// virtual file, open it after the Folder were created (if the app is not terminated)
QTimer::singleShot(0, this, [this, option] { openVirtualFile(option); });
} else if (option.startsWith(QStringLiteral(APPLICATION_URI_HANDLER_SCHEME "://open"))) {
// see the section Local file editing of the Architecture page of the user documentation
_editFileLocallyUrl = QUrl::fromUserInput(option);
if (!_editFileLocallyUrl.isValid()) {
_editFileLocallyUrl.clear();
const auto errorParsingLocalFileEditingUrl = QStringLiteral("The supplied url for local file editing '%1' is invalid!").arg(option);
qCInfo(lcApplication) << errorParsingLocalFileEditingUrl;
showHint(errorParsingLocalFileEditingUrl.toStdString());
} else if (option.startsWith(QStringLiteral(APPLICATION_URI_HANDLER_SCHEME "://"))) {
_uriSchemeUrl = QUrl{option};
qCInfo(lcApplication) << "Command line contains custom URI scheme request:"
<< "scheme=" << _uriSchemeUrl.scheme()
<< "host=" << _uriSchemeUrl.host()
<< "path=" << _uriSchemeUrl.path();
if (!_uriSchemeUrl.isValid()) {
_uriSchemeUrl.clear();
const auto errorParsingUri = QStringLiteral("The supplied url '%1' is invalid!").arg(option);
qCInfo(lcApplication) << errorParsingUri;
showHint(errorParsingUri.toStdString());
}
} else if (option == QStringLiteral("--overrideserverurl")) {
if (it.hasNext() && !it.peekNext().startsWith(QLatin1String("--"))) {
const auto overrideUrl = it.next();

Check warning on line 994 in src/gui/application.cpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Avoid this unnecessary copy by using a "const" reference.

See more on https://sonarcloud.io/project/issues?id=nextcloud_desktop&issues=AZ8jAAaB6VhQ52Y1EjB6&open=AZ8jAAaB6VhQ52Y1EjB6&pullRequest=9969
const auto isUrlValid = (overrideUrl.startsWith(QStringLiteral("http://")) || overrideUrl.startsWith(QStringLiteral("https://")))
&& QUrl::fromUserInput(overrideUrl).isValid();
if (!isUrlValid) {
Expand Down Expand Up @@ -1122,14 +1130,37 @@
_helpOnly = true;
}

void Application::handleEditLocallyFromOptions()
void Application::handleUriFromOptions()
{
if (!_editFileLocallyUrl.isValid()) {
if (!_uriSchemeUrl.isValid()) {
qCDebug(lcApplication) << "No pending custom URI scheme request from command line options.";
return;
}

EditLocallyManager::instance()->handleRequest(_editFileLocallyUrl);
_editFileLocallyUrl.clear();
handleUriSchemeRequest(_uriSchemeUrl);
_uriSchemeUrl.clear();
}

bool Application::handleUriSchemeRequest(const QUrl &url)
{
const auto parsedUri = UriSchemeHandler::parseUri(url);
const auto suppressEmptyAccountCheck = parsedUri.action == UriSchemeHandler::Action::Login
&& AccountManager::instance()->accounts().isEmpty();
const auto wasCheckConnectionTimerActive = _checkConnectionTimer.isActive();
if (suppressEmptyAccountCheck) {
_suppressNextEmptyAccountCheck = true;
_checkConnectionTimer.stop();
}

const auto handled = UriSchemeHandler::handleUri(url);
if (!handled && suppressEmptyAccountCheck) {
_suppressNextEmptyAccountCheck = false;
if (wasCheckConnectionTimerActive) {
_checkConnectionTimer.start();
}
}

return handled;
}

QString enforcedLanguage()
Expand Down Expand Up @@ -1290,8 +1321,11 @@
} else if (!openEvent->url().isEmpty() && openEvent->url().isValid()) {
// On macOS, Qt does not handle receiving a custom URI as it does on other systems (as an application argument).
// Instead, it sends out a QFileOpenEvent. We therefore need custom handling for our URI handling on macOS.
qCInfo(lcApplication) << "macOS: Opening local file for editing: " << openEvent->url();
EditLocallyManager::instance()->handleRequest(openEvent->url());
qCInfo(lcApplication) << "macOS QFileOpenEvent contains custom URI scheme request:"
<< "scheme=" << openEvent->url().scheme()
<< "host=" << openEvent->url().host()
<< "path=" << openEvent->url().path();
handleUriSchemeRequest(openEvent->url());
} else {
const auto errorParsingLocalFileEditingUrl = QStringLiteral("The supplied url for local file editing '%1' is invalid!").arg(openEvent->url().toString());
qCInfo(lcApplication) << errorParsingLocalFileEditingUrl;
Expand Down
6 changes: 4 additions & 2 deletions src/gui/application.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
* @brief The Application class
* @ingroup gui
*/
class Application : public QApplication

Check warning on line 57 in src/gui/application.h

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Class has 48 methods, which is greater than the 35 authorized. Split it into smaller classes.

See more on https://sonarcloud.io/project/issues?id=nextcloud_desktop&issues=AZ8jAAbD6VhQ52Y1EjCH&open=AZ8jAAbD6VhQ52Y1EjCH&pullRequest=9969
{
Q_OBJECT
public:
Expand All @@ -74,6 +74,7 @@
bool sendMessage(const QString &message);

void showMainDialog();
bool handleUriSchemeRequest(const QUrl &url);

[[nodiscard]] ownCloudGui *gui() const;

Expand Down Expand Up @@ -120,7 +121,7 @@
private:
void setHelp();

void handleEditLocallyFromOptions();
void handleUriFromOptions();

AccountManager::AccountsRestoreResult restoreLegacyAccount();
void setupConfigFile();
Expand Down Expand Up @@ -163,7 +164,8 @@
bool _userTriggeredConnect = false;
bool _debugMode = false;
bool _backgroundMode = false;
QUrl _editFileLocallyUrl;
bool _suppressNextEmptyAccountCheck = false;
QUrl _uriSchemeUrl;

ClientProxy _proxy;

Expand Down
19 changes: 17 additions & 2 deletions src/gui/cocoainitializer_mac.mm
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
#import <AppKit/NSApplication.h>

#include "application.h"
#include "editlocallymanager.h"

#include <QTimer>

/* In theory, we should be able to just capture QFileOpenEvents
* when we open our custom URLs in our Application class and be
Expand Down Expand Up @@ -52,7 +53,21 @@ - (void)handleURLEvent:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEv
{
NSURL* url = [NSURL URLWithString:[[event paramDescriptorForKeyword:keyDirectObject] stringValue]];
const auto qtUrl = QUrl::fromNSURL(url);
OCC::EditLocallyManager::instance()->handleRequest(qtUrl);
if (qApp) {
QTimer::singleShot(0, qApp, [qtUrl] {
qCInfo(OCC::lcApplication) << "macOS AppleEvent custom URI scheme request:"
<< "scheme=" << qtUrl.scheme()
<< "host=" << qtUrl.host()
<< "path=" << qtUrl.path();
if (auto application = qobject_cast<OCC::Application *>(qApp)) {
application->handleUriSchemeRequest(qtUrl);
} else {
qCWarning(OCC::lcApplication) << "Could not dispatch macOS AppleEvent custom URI scheme request because qApp is not an Application.";
}
});
} else {
qCWarning(OCC::lcApplication) << "Could not dispatch macOS AppleEvent custom URI scheme request because qApp is not available yet.";
}
Comment thread
Rello marked this conversation as resolved.
}

@end
Expand Down
99 changes: 99 additions & 0 deletions src/gui/owncloudsetupwizard.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

#include "owncloudsetupwizard.h"

#include "accountmanager.h"
#include "folderman.h"
#include "systray.h"
#include "theme.h"
Expand Down Expand Up @@ -41,7 +42,7 @@

static QPointer<OwncloudSetupWizard> owncloudSetupWizard = nullptr;

void OwncloudSetupWizard::runWizard(QObject *obj, const char *amember, QWidget *parent, bool forceRestart)

Check warning on line 45 in src/gui/owncloudsetupwizard.cpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Make the type of this parameter a pointer-to-const. The current type of "obj" is "class QObject *".

See more on https://sonarcloud.io/project/issues?id=nextcloud_desktop&issues=AZ8jAAYh6VhQ52Y1EjBd&open=AZ8jAAYh6VhQ52Y1EjBd&pullRequest=9969

Check warning on line 45 in src/gui/owncloudsetupwizard.cpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Unmodified variable "forceRestart" of type "_Bool" should be const-qualified.

See more on https://sonarcloud.io/project/issues?id=nextcloud_desktop&issues=AZ8jAAYh6VhQ52Y1EjBh&open=AZ8jAAYh6VhQ52Y1EjBh&pullRequest=9969

Check warning on line 45 in src/gui/owncloudsetupwizard.cpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Unmodified variable "parent" of type "class QWidget *" should be const-qualified.

See more on https://sonarcloud.io/project/issues?id=nextcloud_desktop&issues=AZ8jAAYh6VhQ52Y1EjBg&open=AZ8jAAYh6VhQ52Y1EjBg&pullRequest=9969
{
if (!owncloudSetupWizard.isNull()) {
if (forceRestart) {
Expand Down Expand Up @@ -70,6 +71,37 @@
owncloudSetupWizard.clear();
}

void OwncloudSetupWizard::runWizardForLoginFlow(QObject *obj, const char *amember, const QUrl &serverUrl, QWidget *parent)

Check warning on line 74 in src/gui/owncloudsetupwizard.cpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Unmodified variable "amember" of type "const char *" should be const-qualified.

See more on https://sonarcloud.io/project/issues?id=nextcloud_desktop&issues=AZ8jAAYh6VhQ52Y1EjBk&open=AZ8jAAYh6VhQ52Y1EjBk&pullRequest=9969
{
#if defined ENFORCE_SINGLE_ACCOUNT
if (!AccountManager::instance()->accounts().isEmpty()) {
return;
}
#endif

if (!owncloudSetupWizard.isNull()) {
qCInfo(lcWizard) << "Restarting existing setup wizard for URI login flow.";
owncloudSetupWizard->finish(QDialog::Rejected);
}

if (!owncloudSetupWizard.isNull()) {
bringWizardToFrontIfVisible();
return;
}

owncloudSetupWizard = new OwncloudSetupWizard(parent);
connect(owncloudSetupWizard, SIGNAL(ownCloudWizardDone(int)), obj, amember);

FolderMan::instance()->setSyncEnabled(false);
if (owncloudSetupWizard->startQmlWizardForLoginFlow(serverUrl)) {
return;
}

emit owncloudSetupWizard->ownCloudWizardDone(QDialog::Rejected);
owncloudSetupWizard->deleteLater();
owncloudSetupWizard.clear();
}

bool OwncloudSetupWizard::bringWizardToFrontIfVisible()
{
if (owncloudSetupWizard.isNull()
Expand Down Expand Up @@ -101,7 +133,7 @@
QQmlComponent component(engine, QStringLiteral("qrc:/qml/src/gui/wizard/qml/AccountWizardWindow.qml"));
QVariantMap initialProperties;
initialProperties.insert(QStringLiteral("controller"), QVariant::fromValue<QObject *>(_qmlController));
auto *createdObject = component.createWithInitialProperties(initialProperties);

Check warning on line 136 in src/gui/owncloudsetupwizard.cpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Unmodified variable "createdObject" of type "class QObject *" should be const-qualified.

See more on https://sonarcloud.io/project/issues?id=nextcloud_desktop&issues=AZ8jAAYh6VhQ52Y1EjBn&open=AZ8jAAYh6VhQ52Y1EjBn&pullRequest=9969

if (component.isError()) {
qCWarning(lcWizard) << "Failed to load QML account wizard:" << component.errors();
Expand Down Expand Up @@ -151,7 +183,74 @@
return true;
}

bool OwncloudSetupWizard::startQmlWizardForLoginFlow(const QUrl &serverUrl)
{
auto *engine = Systray::instance()->trayEngine();
if (!engine) {
qCWarning(lcWizard) << "Cannot start QML account wizard without a QML engine.";
return false;
}

_qmlController = new AccountWizardController(this);
if (!_qmlController->setServerUrlForLoginFlow(serverUrl)) {
qCWarning(lcWizard) << "Cannot start QML account wizard for URI login flow with an unconfigured server URL.";
_qmlController->deleteLater();
_qmlController = nullptr;
return false;
}
_qmlController->submitServerUrl();

QQmlComponent component(engine, QStringLiteral("qrc:/qml/src/gui/wizard/qml/AccountWizardWindow.qml"));
QVariantMap initialProperties;
initialProperties.insert(QStringLiteral("controller"), QVariant::fromValue<QObject *>(_qmlController));
auto *createdObject = component.createWithInitialProperties(initialProperties);

Check warning on line 206 in src/gui/owncloudsetupwizard.cpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Unmodified variable "createdObject" of type "class QObject *" should be const-qualified.

See more on https://sonarcloud.io/project/issues?id=nextcloud_desktop&issues=AZ8jAAYh6VhQ52Y1EjBq&open=AZ8jAAYh6VhQ52Y1EjBq&pullRequest=9969

if (component.isError()) {
qCWarning(lcWizard) << "Failed to load QML account wizard:" << component.errors();
}

_qmlWizardWindow = qobject_cast<QQuickWindow *>(createdObject);
if (!_qmlWizardWindow) {
if (createdObject) {
createdObject->deleteLater();
}
_qmlController->deleteLater();
_qmlController = nullptr;
return false;
}

_qmlWizardWindow->setIcon(Theme::instance()->applicationIcon());

#ifdef Q_OS_MACOS
auto *fgbg = new ForegroundBackground(this);
_qmlWizardWindow->installEventFilter(fgbg);
#endif

connect(_qmlController, &AccountWizardController::finished, this, &OwncloudSetupWizard::finish);
connect(_qmlWizardWindow, &QQuickWindow::visibleChanged, this, [this](bool visible) {
if (!visible) {
finish(QDialog::Rejected);
}
});
connect(_qmlWizardWindow, &QObject::destroyed, this, [this] {
finish(QDialog::Rejected);
});

_qmlWizardWindow->show();
_qmlWizardWindow->raise();
_qmlWizardWindow->requestActivate();

#ifdef Q_OS_MACOS
styleNativeTitleBar(_qmlWizardWindow, /*hideTitleText=*/true);
connect(_qmlWizardWindow, &QQuickWindow::colorChanged, this, [this] {
styleNativeTitleBar(_qmlWizardWindow, /*hideTitleText=*/true);
});
#endif

return true;
}

void OwncloudSetupWizard::finish(int result)

Check warning on line 253 in src/gui/owncloudsetupwizard.cpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Unmodified variable "result" of type "int" should be const-qualified.

See more on https://sonarcloud.io/project/issues?id=nextcloud_desktop&issues=AZ8jAAYh6VhQ52Y1EjBs&open=AZ8jAAYh6VhQ52Y1EjBs&pullRequest=9969
{
if (_finished) {
return;
Expand Down
3 changes: 3 additions & 0 deletions src/gui/owncloudsetupwizard.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@
#ifndef OWNCLOUDSETUPWIZARD_H
#define OWNCLOUDSETUPWIZARD_H

#include <QObject>

Check failure on line 10 in src/gui/owncloudsetupwizard.h

View workflow job for this annotation

GitHub Actions / build

src/gui/owncloudsetupwizard.h:10:10 [clang-diagnostic-error]

'QObject' file not found
#include <QWidget>
#include <QPointer>
#include <QUrl>

class QQuickWindow;

Expand All @@ -27,6 +28,7 @@
public:
/** Run the wizard */
static void runWizard(QObject *obj, const char *amember, QWidget *parent = nullptr, bool forceRestart = false);
static void runWizardForLoginFlow(QObject *obj, const char *amember, const QUrl &serverUrl, QWidget *parent = nullptr);
static bool bringWizardToFrontIfVisible();

signals:
Expand All @@ -37,6 +39,7 @@
explicit OwncloudSetupWizard(QObject *parent = nullptr);
~OwncloudSetupWizard() override;
bool startQmlWizard();
bool startQmlWizardForLoginFlow(const QUrl &serverUrl);
void finish(int result);

AccountWizardController *_qmlController = nullptr;
Expand Down
Loading
Loading