From 8183ceb24f74607ddb1e6fa9512e976918b8ddf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= <1005065+DeepDiver1975@users.noreply.github.com> Date: Thu, 9 Oct 2025 17:26:08 +0200 Subject: [PATCH 01/17] feat: allow account setup configuration via system based configuration mechanisms --- .../newaccountwizard/urlpagecontroller.cpp | 27 ++++++++++++++++--- src/gui/newaccountwizard/urlpagecontroller.h | 6 ++--- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/src/gui/newaccountwizard/urlpagecontroller.cpp b/src/gui/newaccountwizard/urlpagecontroller.cpp index e1acdce40fd..9fb58e350c9 100644 --- a/src/gui/newaccountwizard/urlpagecontroller.cpp +++ b/src/gui/newaccountwizard/urlpagecontroller.cpp @@ -14,6 +14,7 @@ #include "urlpagecontroller.h" #include "accessmanager.h" +#include "configfile.h" #include "networkadapters/determineauthtypeadapter.h" #include "networkadapters/discoverwebfingerserviceadapter.h" #include "networkadapters/resolveurladapter.h" @@ -34,10 +35,28 @@ UrlPageController::UrlPageController(QWizardPage *page, AccessManager *accessMan { buildPage(); - QString themeUrl = Theme::instance()->overrideServerUrlV2(); - if (_urlField && !themeUrl.isEmpty()) { - setUrl(themeUrl); - // The theme provides the url, don't let the user change it! + if (_urlField == nullptr) { + return; + } + + // a theme can provide a hardcoded url which is not subject of change by definition + bool allowServerUrlChange = true; + QString serverUrl = Theme::instance()->overrideServerUrlV2(); + if (serverUrl.isEmpty()) { + // respect global pre-configuration + // TODO: move to own class or make it part of ConfigFile? + const QSettings settings(QSettings::SystemScope, this); + allowServerUrlChange = settings.value("Setup/AllowServerUrlChange", true).toBool(); + serverUrl = settings.value("Setup/ServerUrl", QString()).toString(); + } + + // no server url was given by any means, so the user has to provide one + if (serverUrl.isEmpty()) { + return; + } + setUrl(serverUrl); + // The system admin provides the url, don't let the user change it! + if (!allowServerUrlChange) { _urlField->setEnabled(false); _instructionLabel->setText(tr("Your web browser will be opened to complete sign in.")); } diff --git a/src/gui/newaccountwizard/urlpagecontroller.h b/src/gui/newaccountwizard/urlpagecontroller.h index d944881056e..71e0cc60266 100644 --- a/src/gui/newaccountwizard/urlpagecontroller.h +++ b/src/gui/newaccountwizard/urlpagecontroller.h @@ -95,9 +95,9 @@ class UrlPageController : public QObject, public WizardPageValidator QPointer _page; QPointer _accessManager; - QLabel *_instructionLabel; - QLineEdit *_urlField; - QLabel *_errorField; + QLabel *_instructionLabel = nullptr; + QLineEdit *_urlField = nullptr; + QLabel *_errorField = nullptr; UrlPageResults _results; bool _urlValidated = false; From c3b3e93573eada2188b9a48b9018d73eebdc680d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= <1005065+DeepDiver1975@users.noreply.github.com> Date: Fri, 10 Oct 2025 15:01:12 +0200 Subject: [PATCH 02/17] feat: introduce class SystemConfig --- .../newaccountwizard/urlpagecontroller.cpp | 7 ++- src/libsync/CMakeLists.txt | 1 + src/libsync/systemconfig.cpp | 43 +++++++++++++++++++ src/libsync/systemconfig.h | 30 +++++++++++++ test/CMakeLists.txt | 1 + test/testsystemconfig.cpp | 25 +++++++++++ 6 files changed, 103 insertions(+), 4 deletions(-) create mode 100644 src/libsync/systemconfig.cpp create mode 100644 src/libsync/systemconfig.h create mode 100644 test/testsystemconfig.cpp diff --git a/src/gui/newaccountwizard/urlpagecontroller.cpp b/src/gui/newaccountwizard/urlpagecontroller.cpp index 9fb58e350c9..ce668b216f7 100644 --- a/src/gui/newaccountwizard/urlpagecontroller.cpp +++ b/src/gui/newaccountwizard/urlpagecontroller.cpp @@ -18,6 +18,7 @@ #include "networkadapters/determineauthtypeadapter.h" #include "networkadapters/discoverwebfingerserviceadapter.h" #include "networkadapters/resolveurladapter.h" +#include "systemconfig.h" #include "theme.h" #include @@ -44,10 +45,8 @@ UrlPageController::UrlPageController(QWizardPage *page, AccessManager *accessMan QString serverUrl = Theme::instance()->overrideServerUrlV2(); if (serverUrl.isEmpty()) { // respect global pre-configuration - // TODO: move to own class or make it part of ConfigFile? - const QSettings settings(QSettings::SystemScope, this); - allowServerUrlChange = settings.value("Setup/AllowServerUrlChange", true).toBool(); - serverUrl = settings.value("Setup/ServerUrl", QString()).toString(); + allowServerUrlChange = SystemConfig::allowServerUrlChange(); + serverUrl = SystemConfig::serverUrl(); } // no server url was given by any means, so the user has to provide one diff --git a/src/libsync/CMakeLists.txt b/src/libsync/CMakeLists.txt index 531f77e4b88..42b22ded10e 100644 --- a/src/libsync/CMakeLists.txt +++ b/src/libsync/CMakeLists.txt @@ -52,6 +52,7 @@ set(libsync_SRCS abstractcorejob.cpp appprovider.cpp + systemconfig.cpp ) if(WIN32) diff --git a/src/libsync/systemconfig.cpp b/src/libsync/systemconfig.cpp new file mode 100644 index 00000000000..ab407fbc92a --- /dev/null +++ b/src/libsync/systemconfig.cpp @@ -0,0 +1,43 @@ + +#include "common/asserts.h" +#include "common/utility.h" +#include "systemconfig.h" +#include "theme.h" + +#include +#include +#include + +namespace OCC { + +namespace chrono = std::chrono; + +QVariant SystemConfig::value(QAnyStringView key, const QVariant &defaultValue) +{ + auto format = Utility::isWindows() ? QSettings::NativeFormat : QSettings::IniFormat; + QSettings system(configPath(QOperatingSystemVersion::currentType(), *Theme::instance()), format); + + return system.value(key, defaultValue); +} +QString SystemConfig::configPath(const QOperatingSystemVersion::OSType& os, const Theme& theme) +{ + if (os == QOperatingSystemVersion::Windows) { + return QString("HKEY_LOCAL_MACHINE\\Software\\%1\\%2").arg(theme.vendor(), theme.appNameGUI()); + } + if (os == QOperatingSystemVersion::MacOS) { + return QString("/Library/Preferences/%1/%2.ini").arg(theme.orgDomainName(), theme.appName()); + } + + return QString("/etc/%1/%1.ini").arg(theme.appName()); +} + +bool SystemConfig::allowServerUrlChange() +{ + return value("Setup/AllowServerUrlChange", true).toBool(); +} + +QString SystemConfig::serverUrl() +{ + return value("Setup/ServerUrl", QString()).toString(); +} +} diff --git a/src/libsync/systemconfig.h b/src/libsync/systemconfig.h new file mode 100644 index 00000000000..1db4c473f48 --- /dev/null +++ b/src/libsync/systemconfig.h @@ -0,0 +1,30 @@ + +#pragma once + +#include "owncloudlib.h" +#include "theme.h" + +#include +#include +#include + + +namespace OCC { + +/** + * @brief The SystemConfig class + * @ingroup libsync + */ +class OWNCLOUDSYNC_EXPORT SystemConfig +{ +public: + // Access system configuration + static bool allowServerUrlChange(); + static QString serverUrl(); + + // General purpose function + static QVariant value(QAnyStringView key, const QVariant &defaultValue); + static QString configPath(const QOperatingSystemVersion::OSType& os, const Theme& theme); + +}; +} diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 685efd9d7f7..3bbbe5a571f 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -22,6 +22,7 @@ endif() owncloud_add_test(ExcludedFiles) owncloud_add_test(Utility) +owncloud_add_test(SystemConfig ../src/libsync/owncloudtheme.cpp) owncloud_add_test(SyncEngine) owncloud_add_test(SyncMove) diff --git a/test/testsystemconfig.cpp b/test/testsystemconfig.cpp new file mode 100644 index 00000000000..0eff2b1d7d0 --- /dev/null +++ b/test/testsystemconfig.cpp @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include + +#include "testutils/testutils.h" + +#include "libsync/owncloudtheme.h" +#include "libsync/systemconfig.h" + +class TestSystemConfig : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void testConfigPath() + { + auto t = OCC::ownCloudTheme(); + QCOMPARE(OCC::SystemConfig::configPath(QOperatingSystemVersion::Windows, t), QString("HKEY_LOCAL_MACHINE\\Software\\ownCloud\\ownCloud")); + QCOMPARE(OCC::SystemConfig::configPath(QOperatingSystemVersion::MacOS, t), QString("/Library/Preferences/com.owncloud.desktopclient/ownCloud.ini")); + QCOMPARE(OCC::SystemConfig::configPath(QOperatingSystemVersion::Unknown, t), QString("/etc/ownCloud/ownCloud.ini")); + } +}; + +QTEST_GUILESS_MAIN(TestSystemConfig) +#include "testsystemconfig.moc" From e40cda67b23c182de70b4168a3152cddb94af3af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= <1005065+DeepDiver1975@users.noreply.github.com> Date: Fri, 16 Jan 2026 15:07:35 +0100 Subject: [PATCH 03/17] feat: add ConfigResolver as unique access point for Theme and SystemConfig + add OpenIdConnect configuration --- src/gui/creds/oauth.cpp | 36 ++++++++-------- src/gui/creds/oauth.h | 7 ++- .../newaccountwizard/oauthpagecontroller.cpp | 3 +- .../newaccountwizard/urlpagecontroller.cpp | 15 +++---- src/libsync/CMakeLists.txt | 4 +- src/libsync/config/configresolver.cpp | 43 +++++++++++++++++++ src/libsync/config/configresolver.h | 29 +++++++++++++ src/libsync/config/openidconfig.cpp | 38 ++++++++++++++++ src/libsync/config/openidconfig.h | 27 ++++++++++++ src/libsync/{ => config}/systemconfig.cpp | 30 +++++++++++-- src/libsync/{ => config}/systemconfig.h | 8 +++- src/libsync/configfile.cpp | 2 + 12 files changed, 202 insertions(+), 40 deletions(-) create mode 100644 src/libsync/config/configresolver.cpp create mode 100644 src/libsync/config/configresolver.h create mode 100644 src/libsync/config/openidconfig.cpp create mode 100644 src/libsync/config/openidconfig.h rename src/libsync/{ => config}/systemconfig.cpp (57%) rename src/libsync/{ => config}/systemconfig.h (79%) diff --git a/src/gui/creds/oauth.cpp b/src/gui/creds/oauth.cpp index 4f09d228eff..04c77b3f851 100644 --- a/src/gui/creds/oauth.cpp +++ b/src/gui/creds/oauth.cpp @@ -15,6 +15,7 @@ #include "oauth.h" #include "accessmanager.h" +#include "config/configresolver.h" #include "creds/credentialssupport.h" #include "gui/networkadapters/userinfoadapter.h" #include "libsync/creds/credentialmanager.h" @@ -42,12 +43,13 @@ namespace { const QString wellKnownPathC = QStringLiteral("/.well-known/openid-configuration"); -auto defaultOauthPromptValue() +auto defaultOauthPromptValue(const OpenIdConfig& config) { - static const auto promptValue = [] { + static const auto promptValue = [config] { + auto prompt = config.prompt(); OAuth::PromptValuesSupportedFlags out = OAuth::PromptValuesSupported::none; // convert the legacy openIdConnectPrompt() to QFlags - for (const auto &x : Theme::instance()->openIdConnectPrompt().split(QLatin1Char(' '))) { + for (const auto &x : prompt.split(QLatin1Char(' '))) { out |= Utility::stringToEnum(x); } return out; @@ -77,15 +79,14 @@ QVariant getRequiredField(const QVariantMap &json, const QString &s, QString *er } } -OAuth::OAuth(const QUrl &serverUrl, const QString &davUser, QNetworkAccessManager *networkAccessManager, QObject *parent) +OAuth::OAuth(const QUrl &serverUrl, const QString &davUser, const OpenIdConfig& openIdConfig, QNetworkAccessManager *networkAccessManager, QObject *parent) : QObject(parent) , _serverUrl(serverUrl) , _davUser(davUser) + , _openIdConfig(openIdConfig) , _networkAccessManager(networkAccessManager) - , _clientId(Theme::instance()->oauthClientId()) - , _clientSecret(Theme::instance()->oauthClientSecret()) , _redirectUrl(QString("http://localhost")) - , _supportedPromptValues(defaultOauthPromptValue()) + , _supportedPromptValues(defaultOauthPromptValue(openIdConfig)) { } @@ -96,8 +97,7 @@ void OAuth::startAuthentication() qCDebug(lcOauth) << "starting authentication"; // Listen on the socket to get a port which will be used in the redirect_uri - - QList ports = Theme::instance()->oauthPorts(); + QList ports = _openIdConfig.ports(); for (const auto port : std::as_const(ports)) { if (_server.listen(QHostAddress::LocalHost, port)) { break; @@ -326,17 +326,17 @@ QNetworkReply *OAuth::postTokenRequest(QUrlQuery &&queryItems) req.setTransferTimeout(defaultTimeoutMs()); switch (_endpointAuthMethod) { case TokenEndpointAuthMethods::client_secret_basic: - req.setRawHeader("Authorization", "Basic " + QStringLiteral("%1:%2").arg(_clientId, _clientSecret).toUtf8().toBase64()); + req.setRawHeader("Authorization", "Basic " + QStringLiteral("%1:%2").arg(_openIdConfig.clientId(), _openIdConfig.clientSecret()).toUtf8().toBase64()); break; case TokenEndpointAuthMethods::client_secret_post: - queryItems.addQueryItem(QStringLiteral("client_id"), _clientId); - queryItems.addQueryItem(QStringLiteral("client_secret"), _clientSecret); + queryItems.addQueryItem(QStringLiteral("client_id"), _openIdConfig.clientId()); + queryItems.addQueryItem(QStringLiteral("client_secret"), _openIdConfig.clientSecret()); break; } req.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/x-www-form-urlencoded; charset=UTF-8")); req.setAttribute(DontAddCredentialsAttribute, true); - queryItems.addQueryItem(QStringLiteral("scope"), QString::fromUtf8(QUrl::toPercentEncoding(Theme::instance()->openIdConnectScopes()))); + queryItems.addQueryItem(QStringLiteral("scope"), QString::fromUtf8(QUrl::toPercentEncoding(_openIdConfig.scopes()))); req.setUrl(requestTokenUrl); return _networkAccessManager->post(req, queryItems.toString(QUrl::FullyEncoded).toUtf8()); } @@ -360,10 +360,10 @@ QUrl OAuth::authorisationLink() const const QByteArray code_challenge = QCryptographicHash::hash(_pkceCodeVerifier, QCryptographicHash::Sha256).toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); - QUrlQuery query{{QStringLiteral("response_type"), QStringLiteral("code")}, {QStringLiteral("client_id"), _clientId}, + QUrlQuery query{{QStringLiteral("response_type"), QStringLiteral("code")}, {QStringLiteral("client_id"), _openIdConfig.clientId()}, {QStringLiteral("redirect_uri"), QStringLiteral("%1:%2").arg(_redirectUrl, QString::number(_server.serverPort()))}, {QStringLiteral("code_challenge"), QString::fromLatin1(code_challenge)}, {QStringLiteral("code_challenge_method"), QStringLiteral("S256")}, - {QStringLiteral("scope"), QString::fromUtf8(QUrl::toPercentEncoding(Theme::instance()->openIdConnectScopes()))}, + {QStringLiteral("scope"), QString::fromUtf8(QUrl::toPercentEncoding(_openIdConfig.scopes()))}, {QStringLiteral("prompt"), QString::fromUtf8(QUrl::toPercentEncoding(toString(_supportedPromptValues)))}, {QStringLiteral("state"), QString::fromUtf8(_state)}}; @@ -416,8 +416,8 @@ void OAuth::fetchWellKnown() _supportedPromptValues = PromptValuesSupported::none; for (const auto &x : promptValuesSupported) { const auto flag = Utility::stringToEnum(x.toString()); - // only use flags present in Theme::instance()->openIdConnectPrompt() - if (flag & defaultOauthPromptValue()) + // only use flags present in _openIdConfig->openIdConnectPrompt() + if (flag & defaultOauthPromptValue(_openIdConfig)) _supportedPromptValues |= flag; } } @@ -479,7 +479,7 @@ void OAuth::openBrowser() // to seed the OAuth ctr, and really, I'm not sure this should be a subclass of oauth in the first place. Instead it could simply use an // oauth instance to complete the tasks it can't do itself -> this could possibly be a "has a" not an "is a" impl AccountBasedOAuth::AccountBasedOAuth(Account *account, QObject *parent) - : OAuth(account->url(), account->davUser(), account->accessManager(), parent) + : OAuth(account->url(), account->davUser(), ConfigResolver::openIdConfig(), account->accessManager(), parent) , _account(account) { } diff --git a/src/gui/creds/oauth.h b/src/gui/creds/oauth.h index b6d089c4260..7ae0e153cb9 100644 --- a/src/gui/creds/oauth.h +++ b/src/gui/creds/oauth.h @@ -17,6 +17,7 @@ #include "owncloudlib.h" #include "account.h" +#include "config/openidconfig.h" #include #include @@ -65,7 +66,7 @@ class OAuth : public QObject Q_ENUM(PromptValuesSupported) Q_DECLARE_FLAGS(PromptValuesSupportedFlags, PromptValuesSupported) - OAuth(const QUrl &serverUrl, const QString &davUser, QNetworkAccessManager *networkAccessManager, QObject *parent); + OAuth(const QUrl &serverUrl, const QString &davUser, const OpenIdConfig& openIdConfig, QNetworkAccessManager *networkAccessManager, QObject *parent); ~OAuth() override; virtual void startAuthentication(); @@ -94,9 +95,7 @@ class OAuth : public QObject QNetworkAccessManager *_networkAccessManager; bool _isRefreshingToken = false; - QString _clientId; - QString _clientSecret; - + OpenIdConfig _openIdConfig; virtual void fetchWellKnown(); diff --git a/src/gui/newaccountwizard/oauthpagecontroller.cpp b/src/gui/newaccountwizard/oauthpagecontroller.cpp index e6d6c9e7c9c..2692522ac35 100644 --- a/src/gui/newaccountwizard/oauthpagecontroller.cpp +++ b/src/gui/newaccountwizard/oauthpagecontroller.cpp @@ -15,6 +15,7 @@ #include "accessmanager.h" #include "accountmanager.h" +#include "config/configresolver.h" #include "networkadapters/fetchcapabilitiesadapter.h" #include "networkadapters/userinfoadapter.h" #include "networkadapters/webfingerlookupadapter.h" @@ -208,7 +209,7 @@ bool OAuthPageController::validate() _authEndpoint.clear(); _urlField->clear(); - _oauth = new OAuth(_authUrl, {}, _accessManager.get(), this); + _oauth = new OAuth(_authUrl, {}, ConfigResolver::openIdConfig(), _accessManager.get(), this); // if we ever need to split out the auth link calculation, it's coming from fetchWellKnown which is a subset of // the "full" authentication routine in the oauth impl connect(_oauth, &OAuth::authorisationLinkChanged, this, &OAuthPageController::authUrlReady); diff --git a/src/gui/newaccountwizard/urlpagecontroller.cpp b/src/gui/newaccountwizard/urlpagecontroller.cpp index ce668b216f7..63405c8fe66 100644 --- a/src/gui/newaccountwizard/urlpagecontroller.cpp +++ b/src/gui/newaccountwizard/urlpagecontroller.cpp @@ -13,12 +13,13 @@ */ #include "urlpagecontroller.h" +#include "../../libsync/config/configresolver.h" +#include "../../libsync/config/systemconfig.h" #include "accessmanager.h" #include "configfile.h" #include "networkadapters/determineauthtypeadapter.h" #include "networkadapters/discoverwebfingerserviceadapter.h" #include "networkadapters/resolveurladapter.h" -#include "systemconfig.h" #include "theme.h" #include @@ -40,21 +41,15 @@ UrlPageController::UrlPageController(QWizardPage *page, AccessManager *accessMan return; } - // a theme can provide a hardcoded url which is not subject of change by definition - bool allowServerUrlChange = true; - QString serverUrl = Theme::instance()->overrideServerUrlV2(); - if (serverUrl.isEmpty()) { - // respect global pre-configuration - allowServerUrlChange = SystemConfig::allowServerUrlChange(); - serverUrl = SystemConfig::serverUrl(); - } - + QString serverUrl = ConfigResolver::serverUrl(); // no server url was given by any means, so the user has to provide one if (serverUrl.isEmpty()) { return; } setUrl(serverUrl); + // The system admin provides the url, don't let the user change it! + bool allowServerUrlChange = ConfigResolver::allowServerUrlChange(); if (!allowServerUrlChange) { _urlField->setEnabled(false); _instructionLabel->setText(tr("Your web browser will be opened to complete sign in.")); diff --git a/src/libsync/CMakeLists.txt b/src/libsync/CMakeLists.txt index 42b22ded10e..737391a2db0 100644 --- a/src/libsync/CMakeLists.txt +++ b/src/libsync/CMakeLists.txt @@ -52,7 +52,9 @@ set(libsync_SRCS abstractcorejob.cpp appprovider.cpp - systemconfig.cpp + config/systemconfig.cpp + config/configresolver.cpp + config/openidconfig.cpp ) if(WIN32) diff --git a/src/libsync/config/configresolver.cpp b/src/libsync/config/configresolver.cpp new file mode 100644 index 00000000000..e928334b8b0 --- /dev/null +++ b/src/libsync/config/configresolver.cpp @@ -0,0 +1,43 @@ +#include "configresolver.h" +#include "../theme.h" +#include "systemconfig.h" + +namespace OCC { + +bool ConfigResolver::allowServerUrlChange() +{ + return SystemConfig::allowServerUrlChange(); +} + +QString ConfigResolver::serverUrl() +{ + // a theme can provide a hardcoded url which is not subject of change by definition + auto serverUrl = Theme::instance()->overrideServerUrlV2(); + if (!serverUrl.isEmpty()) { + return serverUrl; + } + + return SystemConfig::serverUrl();; +} + +OpenIdConfig ConfigResolver::openIdConfig() +{ + // TODO: shall we fill values in from the Theme in case not set within SystemConfig? + // not reasonable for clientId/secret, but maybe for ports/scopes/prompt? + // system config has precedence here + auto cfg = SystemConfig::openIdConfig(); + if (!cfg.clientId().isEmpty()) { + return cfg; + } + + // load config from theme + QString clientId = Theme::instance()->oauthClientId(); + QString clientSecret = Theme::instance()->oauthClientSecret(); + + const auto ports = Theme::instance()->oauthPorts(); + QString scopes = Theme::instance()->openIdConnectScopes(); + QString prompt = Theme::instance()->openIdConnectPrompt(); + + return OpenIdConfig(clientId, clientSecret, ports, scopes, prompt); +} +} diff --git a/src/libsync/config/configresolver.h b/src/libsync/config/configresolver.h new file mode 100644 index 00000000000..d53e453273b --- /dev/null +++ b/src/libsync/config/configresolver.h @@ -0,0 +1,29 @@ + +#pragma once + +#include "../theme.h" +#include "openidconfig.h" +#include "owncloudlib.h" + +#include +#include +#include + + +namespace OCC { + +/** + * @brief The ConfigResolver class + * @ingroup libsync + */ +class OWNCLOUDSYNC_EXPORT ConfigResolver +{ +public: + // account setup + static bool allowServerUrlChange(); + static QString serverUrl(); + + // OAuth/OpenID Connect + static OpenIdConfig openIdConfig(); +}; +} diff --git a/src/libsync/config/openidconfig.cpp b/src/libsync/config/openidconfig.cpp new file mode 100644 index 00000000000..5cf26d12a9f --- /dev/null +++ b/src/libsync/config/openidconfig.cpp @@ -0,0 +1,38 @@ +// +// Created by deepdiver on 16/01/2026. +// + +#include "openidconfig.h" + +namespace OCC { + +OpenIdConfig::OpenIdConfig(const QString &clientId, const QString &clientSecret, const QList &ports, const QString &scopes, const QString &prompt) + : _clientId(clientId) + , _clientSecret(clientSecret) + , _ports(ports) + , _scopes(scopes) + , _prompt(prompt) +{ +} + +QString OpenIdConfig::clientId() const { + return _clientId; +} + +QString OpenIdConfig::clientSecret() const { + return _clientSecret; +} + +QList OpenIdConfig::ports() const { + return _ports; +} + +QString OpenIdConfig::scopes() const { + return _scopes; +} + +QString OpenIdConfig::prompt() const { + return _prompt; +} + +} diff --git a/src/libsync/config/openidconfig.h b/src/libsync/config/openidconfig.h new file mode 100644 index 00000000000..dee9d704917 --- /dev/null +++ b/src/libsync/config/openidconfig.h @@ -0,0 +1,27 @@ +#pragma once +#include "owncloudlib.h" + +#include +#include +#include + +namespace OCC { +class OWNCLOUDSYNC_EXPORT OpenIdConfig +{ +public: + explicit OpenIdConfig(const QString &clientId, const QString &clientSecret, const QList &ports, const QString &scopes, const QString &prompt); + + QString clientId() const; + QString clientSecret() const; + QList ports() const; + QString scopes() const; + QString prompt() const; + +private: + QString _clientId; + QString _clientSecret; + QList _ports; + QString _scopes; + QString _prompt; +}; +} \ No newline at end of file diff --git a/src/libsync/systemconfig.cpp b/src/libsync/config/systemconfig.cpp similarity index 57% rename from src/libsync/systemconfig.cpp rename to src/libsync/config/systemconfig.cpp index ab407fbc92a..e784547f5c5 100644 --- a/src/libsync/systemconfig.cpp +++ b/src/libsync/config/systemconfig.cpp @@ -1,12 +1,12 @@ +#include "systemconfig.h" -#include "common/asserts.h" +#include "../theme.h" #include "common/utility.h" -#include "systemconfig.h" -#include "theme.h" +#include "openidconfig.h" #include -#include #include +#include namespace OCC { @@ -40,4 +40,26 @@ QString SystemConfig::serverUrl() { return value("Setup/ServerUrl", QString()).toString(); } + +OpenIdConfig SystemConfig::openIdConfig() +{ + auto clientId = value("OpenIDConnect/ClientId", QString()).toString(); + auto clientSecret = value("OpenIDConnect/ClientSecret", QString()).toString(); + + QVariant portsVar = value("OpenIDConnect/Ports", "0").toString(); + QList ports; + const auto parts = portsVar.toString().split(QLatin1Char(','), Qt::SkipEmptyParts); + for (const QString &p : parts) { + bool ok = false; + const quint16 val = static_cast(p.trimmed().toUInt(&ok)); + if (ok) { + ports.append(val); + } + } + + QString scopes = value("OpenIDConnect/Scopes", QString()).toString(); + QString prompt = value("OpenIDConnect/Prompt", QString()).toString(); + + return OpenIdConfig(clientId, clientSecret, ports, scopes, prompt); +} } diff --git a/src/libsync/systemconfig.h b/src/libsync/config/systemconfig.h similarity index 79% rename from src/libsync/systemconfig.h rename to src/libsync/config/systemconfig.h index 1db4c473f48..251c1421b88 100644 --- a/src/libsync/systemconfig.h +++ b/src/libsync/config/systemconfig.h @@ -1,8 +1,9 @@ #pragma once +#include "../theme.h" #include "owncloudlib.h" -#include "theme.h" +#include "openidconfig.h" #include #include @@ -19,12 +20,15 @@ class OWNCLOUDSYNC_EXPORT SystemConfig { public: // Access system configuration + // section: account setup static bool allowServerUrlChange(); static QString serverUrl(); + // section: OpenID Connect + static OpenIdConfig openIdConfig(); + // General purpose function static QVariant value(QAnyStringView key, const QVariant &defaultValue); static QString configPath(const QOperatingSystemVersion::OSType& os, const Theme& theme); - }; } diff --git a/src/libsync/configfile.cpp b/src/libsync/configfile.cpp index dda98be048a..cfd019fafc3 100644 --- a/src/libsync/configfile.cpp +++ b/src/libsync/configfile.cpp @@ -211,6 +211,7 @@ bool ConfigFile::restoreGeometryHeader(QHeaderView *header) return false; } +// TODO: remove this in favor of SystemConfig QVariant ConfigFile::getPolicySetting(const QString &setting, const QVariant &defaultValue) const { if (Utility::isWindows()) { @@ -538,6 +539,7 @@ void ConfigFile::setProxyType(QNetworkProxy::ProxyType proxyType, const QString settings.sync(); } +// TODO: remove this in favor of SystemConfig QVariant ConfigFile::getValue(const QString ¶m, const QString &group, const QVariant &defaultValue) const { From bbdc9f1be4c59610a0721eedc2b2168d38ceb94f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= <1005065+DeepDiver1975@users.noreply.github.com> Date: Fri, 16 Jan 2026 16:26:36 +0100 Subject: [PATCH 04/17] fix: include path --- test/testsystemconfig.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/testsystemconfig.cpp b/test/testsystemconfig.cpp index 0eff2b1d7d0..6298f40039b 100644 --- a/test/testsystemconfig.cpp +++ b/test/testsystemconfig.cpp @@ -5,7 +5,7 @@ #include "testutils/testutils.h" #include "libsync/owncloudtheme.h" -#include "libsync/systemconfig.h" +#include "libsync/config/systemconfig.h" class TestSystemConfig : public QObject { From ef47893fe6dad855c93366779576f821dbc4c607 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= <1005065+DeepDiver1975@users.noreply.github.com> Date: Fri, 16 Jan 2026 17:04:48 +0100 Subject: [PATCH 05/17] feat: remove Windows specific getPolicySetting and the attempt to read settings from a system location. skipUpdateCheck is using SystemConfig already. Proxy settings will follow --- src/libsync/config/configresolver.cpp | 6 +++ src/libsync/config/configresolver.h | 4 +- src/libsync/config/systemconfig.cpp | 21 ++++++++- src/libsync/config/systemconfig.h | 7 ++- src/libsync/configfile.cpp | 67 ++++++--------------------- src/libsync/configfile.h | 3 +- 6 files changed, 51 insertions(+), 57 deletions(-) diff --git a/src/libsync/config/configresolver.cpp b/src/libsync/config/configresolver.cpp index e928334b8b0..8d086255116 100644 --- a/src/libsync/config/configresolver.cpp +++ b/src/libsync/config/configresolver.cpp @@ -20,6 +20,12 @@ QString ConfigResolver::serverUrl() return SystemConfig::serverUrl();; } +bool ConfigResolver::skipUpdateCheck() +{ + // only in SystemConfig - this is not a Theme option + return SystemConfig::skipUpdateCheck(); +} + OpenIdConfig ConfigResolver::openIdConfig() { // TODO: shall we fill values in from the Theme in case not set within SystemConfig? diff --git a/src/libsync/config/configresolver.h b/src/libsync/config/configresolver.h index d53e453273b..1379ffb91a3 100644 --- a/src/libsync/config/configresolver.h +++ b/src/libsync/config/configresolver.h @@ -1,4 +1,3 @@ - #pragma once #include "../theme.h" @@ -23,6 +22,9 @@ class OWNCLOUDSYNC_EXPORT ConfigResolver static bool allowServerUrlChange(); static QString serverUrl(); + // Return true if update checks should be skipped (e.g., via env or system config) + static bool skipUpdateCheck(); + // OAuth/OpenID Connect static OpenIdConfig openIdConfig(); }; diff --git a/src/libsync/config/systemconfig.cpp b/src/libsync/config/systemconfig.cpp index e784547f5c5..3ce65b22253 100644 --- a/src/libsync/config/systemconfig.cpp +++ b/src/libsync/config/systemconfig.cpp @@ -22,9 +22,12 @@ QVariant SystemConfig::value(QAnyStringView key, const QVariant &defaultValue) QString SystemConfig::configPath(const QOperatingSystemVersion::OSType& os, const Theme& theme) { if (os == QOperatingSystemVersion::Windows) { - return QString("HKEY_LOCAL_MACHINE\\Software\\%1\\%2").arg(theme.vendor(), theme.appNameGUI()); + // we use HKEY_LOCAL_MACHINE\Software\Policies since this is the location whe GPO operates + return QString("HKEY_LOCAL_MACHINE\\Software\\Policies\\%1\\%2").arg(theme.vendor(), theme.appNameGUI()); } if (os == QOperatingSystemVersion::MacOS) { + // we use a subfolder to have one common location where in the future more files can be stored (like icons, images and such) + // ini is used on macOS in contrary to plist because they are easier to maintain return QString("/Library/Preferences/%1/%2.ini").arg(theme.orgDomainName(), theme.appName()); } @@ -41,6 +44,22 @@ QString SystemConfig::serverUrl() return value("Setup/ServerUrl", QString()).toString(); } +bool SystemConfig::skipUpdateCheck() +{ + return value("Updater/SkipUpdateCheck", false).toBool(); +} + +bool SystemConfig::moveToTrash() +{ + // check settings first; if not present, fall back to the Theme default + QVariant v = value("Setup/MoveToTrash", QVariant()); + if (v.isValid()) { + return v.toBool(); + } + + return Theme::instance()->moveToTrashDefaultValue(); +} + OpenIdConfig SystemConfig::openIdConfig() { auto clientId = value("OpenIDConnect/ClientId", QString()).toString(); diff --git a/src/libsync/config/systemconfig.h b/src/libsync/config/systemconfig.h index 251c1421b88..d9621da7b50 100644 --- a/src/libsync/config/systemconfig.h +++ b/src/libsync/config/systemconfig.h @@ -1,4 +1,3 @@ - #pragma once #include "../theme.h" @@ -27,6 +26,12 @@ class OWNCLOUDSYNC_EXPORT SystemConfig // section: OpenID Connect static OpenIdConfig openIdConfig(); + // section: updater + static bool skipUpdateCheck(); + + // section: trash handling + static bool moveToTrash(); + // General purpose function static QVariant value(QAnyStringView key, const QVariant &defaultValue); static QString configPath(const QOperatingSystemVersion::OSType& os, const Theme& theme); diff --git a/src/libsync/configfile.cpp b/src/libsync/configfile.cpp index cfd019fafc3..2cd8bf7480e 100644 --- a/src/libsync/configfile.cpp +++ b/src/libsync/configfile.cpp @@ -15,6 +15,9 @@ #include "common/asserts.h" #include "common/utility.h" #include "common/version.h" + +#include "config/configresolver.h" +#include "config/systemconfig.h" #ifdef Q_OS_WIN #include "common/utility_win.h" #endif @@ -211,27 +214,6 @@ bool ConfigFile::restoreGeometryHeader(QHeaderView *header) return false; } -// TODO: remove this in favor of SystemConfig -QVariant ConfigFile::getPolicySetting(const QString &setting, const QVariant &defaultValue) const -{ - if (Utility::isWindows()) { - // check for policies first and return immediately if a value is found. - QSettings userPolicy(QStringLiteral("HKEY_CURRENT_USER\\Software\\Policies\\%1\\%2").arg(Theme::instance()->vendor(), Theme::instance()->appNameGUI()), - QSettings::NativeFormat); - if (userPolicy.contains(setting)) { - return userPolicy.value(setting); - } - - QSettings machinePolicy( - QStringLiteral("HKEY_LOCAL_MACHINE\\Software\\Policies\\%1\\%2").arg(Theme::instance()->vendor(), Theme::instance()->appNameGUI()), - QSettings::NativeFormat); - if (machinePolicy.contains(setting)) { - return machinePolicy.value(setting); - } - } - return defaultValue; -} - QString ConfigFile::configPath() { if (_confDir.isEmpty()) { @@ -464,16 +446,15 @@ chrono::milliseconds ConfigFile::updateCheckInterval(const QString &connection) return interval; } -bool ConfigFile::skipUpdateCheck(const QString &connection) const +bool ConfigFile::skipUpdateCheck() const { - QString con(connection); - if (connection.isEmpty()) - con = defaultConnection(); + if (ConfigResolver::skipUpdateCheck()) { + return true; + } + auto con = defaultConnection(); QVariant fallback = getValue(skipUpdateCheckC(), con, false); - fallback = getValue(skipUpdateCheckC(), QString(), fallback); - - QVariant value = getPolicySetting(skipUpdateCheckC(), fallback); + QVariant value = getValue(skipUpdateCheckC(), QString(), fallback); return value.toBool(); } @@ -539,37 +520,14 @@ void ConfigFile::setProxyType(QNetworkProxy::ProxyType proxyType, const QString settings.sync(); } -// TODO: remove this in favor of SystemConfig QVariant ConfigFile::getValue(const QString ¶m, const QString &group, const QVariant &defaultValue) const { - QVariant systemSetting; - if (Utility::isMac()) { - QSettings systemSettings(QStringLiteral("/Library/Preferences/%1.plist").arg(Theme::instance()->orgDomainName()), QSettings::NativeFormat); - if (!group.isEmpty()) { - systemSettings.beginGroup(group); - } - systemSetting = systemSettings.value(param, defaultValue); - } else if (Utility::isUnix()) { - QSettings systemSettings(QStringLiteral(SYSCONFDIR "/%1/%1.conf").arg(Theme::instance()->appName()), QSettings::NativeFormat); - if (!group.isEmpty()) { - systemSettings.beginGroup(group); - } - systemSetting = systemSettings.value(param, defaultValue); - } else { // Windows - QSettings systemSettings( - QStringLiteral("HKEY_LOCAL_MACHINE\\Software\\%1\\%2").arg(Theme::instance()->vendor(), Theme::instance()->appNameGUI()), QSettings::NativeFormat); - if (!group.isEmpty()) { - systemSettings.beginGroup(group); - } - systemSetting = systemSettings.value(param, defaultValue); - } - auto settings = makeQSettings(); if (!group.isEmpty()) settings.beginGroup(group); - return settings.value(param, systemSetting); + return settings.value(param, defaultValue); } void ConfigFile::setValue(const QString &key, const QVariant &value) @@ -619,6 +577,11 @@ void ConfigFile::setPauseSyncWhenMetered(bool isChecked) bool ConfigFile::moveToTrash() const { + // system config has precedence + if (SystemConfig::moveToTrash()) { + return true; + } + auto defaultValue = Theme::instance()->moveToTrashDefaultValue(); return getValue(moveToTrashC(), QString(), defaultValue).toBool(); } diff --git a/src/libsync/configfile.h b/src/libsync/configfile.h index 1c50cd4b95d..e9f108e9a10 100644 --- a/src/libsync/configfile.h +++ b/src/libsync/configfile.h @@ -144,7 +144,7 @@ class OWNCLOUDSYNC_EXPORT ConfigFile // how often the check about new versions runs std::chrono::milliseconds updateCheckInterval(const QString &connection = QString()) const; - bool skipUpdateCheck(const QString &connection = QString()) const; + bool skipUpdateCheck() const; void setSkipUpdateCheck(bool, const QString &); QString updateChannel() const; @@ -168,7 +168,6 @@ class OWNCLOUDSYNC_EXPORT ConfigFile static void setupDefaultExcludeFilePaths(ExcludedFiles &excludedFiles); protected: - QVariant getPolicySetting(const QString &policy, const QVariant &defaultValue = QVariant()) const; void storeData(const QString &group, const QString &key, const QVariant &value); void removeData(const QString &group, const QString &key); bool dataExists(const QString &group, const QString &key) const; From b189bee9b930a42fcb0f6581a3815de7afed9033 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= <1005065+DeepDiver1975@users.noreply.github.com> Date: Fri, 16 Jan 2026 17:54:46 +0100 Subject: [PATCH 06/17] fix: TestSystemConfig --- test/testsystemconfig.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/testsystemconfig.cpp b/test/testsystemconfig.cpp index 6298f40039b..faf40abd6b4 100644 --- a/test/testsystemconfig.cpp +++ b/test/testsystemconfig.cpp @@ -15,7 +15,7 @@ private Q_SLOTS: void testConfigPath() { auto t = OCC::ownCloudTheme(); - QCOMPARE(OCC::SystemConfig::configPath(QOperatingSystemVersion::Windows, t), QString("HKEY_LOCAL_MACHINE\\Software\\ownCloud\\ownCloud")); + QCOMPARE(OCC::SystemConfig::configPath(QOperatingSystemVersion::Windows, t), QString("HKEY_LOCAL_MACHINE\\Software\\Policies\\ownCloud\\ownCloud")); QCOMPARE(OCC::SystemConfig::configPath(QOperatingSystemVersion::MacOS, t), QString("/Library/Preferences/com.owncloud.desktopclient/ownCloud.ini")); QCOMPARE(OCC::SystemConfig::configPath(QOperatingSystemVersion::Unknown, t), QString("/etc/ownCloud/ownCloud.ini")); } From 988297e00822dbb520358a88d42e77117904ed23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= <1005065+DeepDiver1975@users.noreply.github.com> Date: Wed, 21 Jan 2026 14:49:01 +0100 Subject: [PATCH 07/17] docs: comment new config classes --- src/gui/creds/oauth.h | 3 +- src/libsync/config/configresolver.cpp | 3 + src/libsync/config/configresolver.h | 32 +++++++-- src/libsync/config/openidconfig.cpp | 5 +- src/libsync/config/openidconfig.h | 9 +++ src/libsync/config/systemconfig.cpp | 5 +- src/libsync/config/systemconfig.h | 93 +++++++++++++++++++++++++-- 7 files changed, 133 insertions(+), 17 deletions(-) diff --git a/src/gui/creds/oauth.h b/src/gui/creds/oauth.h index 7ae0e153cb9..cd6cdab4e81 100644 --- a/src/gui/creds/oauth.h +++ b/src/gui/creds/oauth.h @@ -92,11 +92,10 @@ class OAuth : public QObject QUrl _serverUrl; QString _davUser; + OpenIdConfig _openIdConfig; QNetworkAccessManager *_networkAccessManager; bool _isRefreshingToken = false; - OpenIdConfig _openIdConfig; - virtual void fetchWellKnown(); QNetworkReply *postTokenRequest(QUrlQuery &&queryItems); diff --git a/src/libsync/config/configresolver.cpp b/src/libsync/config/configresolver.cpp index 8d086255116..546e86a3b98 100644 --- a/src/libsync/config/configresolver.cpp +++ b/src/libsync/config/configresolver.cpp @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-FileCopyrightText: 2026 Thomas Müller + #include "configresolver.h" #include "../theme.h" #include "systemconfig.h" diff --git a/src/libsync/config/configresolver.h b/src/libsync/config/configresolver.h index 1379ffb91a3..ead17942e25 100644 --- a/src/libsync/config/configresolver.h +++ b/src/libsync/config/configresolver.h @@ -1,12 +1,12 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-FileCopyrightText: 2026 Thomas Müller + #pragma once -#include "../theme.h" #include "openidconfig.h" #include "owncloudlib.h" -#include #include -#include namespace OCC { @@ -14,18 +14,38 @@ namespace OCC { /** * @brief The ConfigResolver class * @ingroup libsync + * @note This class provides methods to resolve configuration settings + * from various sources such as system configuration and themes. */ class OWNCLOUDSYNC_EXPORT ConfigResolver { public: - // account setup + /** + * Determine if changing the server URL is allowed. + * This checks the system configuration for the relevant setting. + * @return True if changing the server URL is allowed, false otherwise. + */ static bool allowServerUrlChange(); + + /** + * Retrieve the server URL. + * This checks the system configuration and theme overrides for the server URL. + * @return The server URL as a QString. If not set, returns an empty string. + */ static QString serverUrl(); - // Return true if update checks should be skipped (e.g., via env or system config) + /** + * Determine if update checks should be skipped. + * This checks the system configuration for the relevant setting. + * @return True if update checks should be skipped, false otherwise. + */ static bool skipUpdateCheck(); - // OAuth/OpenID Connect + /** + * Retrieve the OpenID Connect configuration. + * This checks the system configuration and theme settings for OpenID Connect parameters. + * @return An OpenIdConfig object containing the OpenID Connect settings. + */ static OpenIdConfig openIdConfig(); }; } diff --git a/src/libsync/config/openidconfig.cpp b/src/libsync/config/openidconfig.cpp index 5cf26d12a9f..9765da105b4 100644 --- a/src/libsync/config/openidconfig.cpp +++ b/src/libsync/config/openidconfig.cpp @@ -1,6 +1,5 @@ -// -// Created by deepdiver on 16/01/2026. -// +// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-FileCopyrightText: 2026 Thomas Müller #include "openidconfig.h" diff --git a/src/libsync/config/openidconfig.h b/src/libsync/config/openidconfig.h index dee9d704917..41dae8a6372 100644 --- a/src/libsync/config/openidconfig.h +++ b/src/libsync/config/openidconfig.h @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-FileCopyrightText: 2026 Thomas Müller + #pragma once #include "owncloudlib.h" @@ -6,6 +9,12 @@ #include namespace OCC { + +/** + * @brief The OpenIdConfig class + * @ingroup libsync + * @note This class encapsulates the configuration settings required for OpenID Connect authentication. + */ class OWNCLOUDSYNC_EXPORT OpenIdConfig { public: diff --git a/src/libsync/config/systemconfig.cpp b/src/libsync/config/systemconfig.cpp index 3ce65b22253..432aa78ec4f 100644 --- a/src/libsync/config/systemconfig.cpp +++ b/src/libsync/config/systemconfig.cpp @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-FileCopyrightText: 2026 Thomas Müller + #include "systemconfig.h" #include "../theme.h" @@ -57,7 +60,7 @@ bool SystemConfig::moveToTrash() return v.toBool(); } - return Theme::instance()->moveToTrashDefaultValue(); + return false; } OpenIdConfig SystemConfig::openIdConfig() diff --git a/src/libsync/config/systemconfig.h b/src/libsync/config/systemconfig.h index d9621da7b50..4e81c87df12 100644 --- a/src/libsync/config/systemconfig.h +++ b/src/libsync/config/systemconfig.h @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-FileCopyrightText: 2026 Thomas Müller + #pragma once #include "../theme.h" @@ -14,26 +17,106 @@ namespace OCC { /** * @brief The SystemConfig class * @ingroup libsync + * @note This class provides access to system-wide configuration settings. + * These settings are typically read-only and affect the behavior of the application globally. + * On Windows, settings are read from the registry path: + * HKEY_LOCAL_MACHINE\Software\Policies\\ + * On macOS, settings are read from the file: + * /Library/Preferences//.ini + * On Linux and other Unix-like systems, settings are read from the file: + * /etc//.ini + * + * @example owncloud.ini + * [Setup] + * ServerUrl=https://cloud.example.com + * AllowServerUrlChange=false + * + * [Updater] + * SkipUpdateCheck=true + * + * [OpenIDConnect] + * ClientId=your-client-id + * ClientSecret=your-client-secret + * Ports=8080,8443 + * Scopes=openid,email,profile + * Prompt=consent + * + * [Behavior] + * MoveToTrash=true + * + * @example owncloud.reg (Windows Registry) + * Windows Registry Editor Version 5.00 + * + * [HKEY_LOCAL_MACHINE\Software\Policies\ownCloud\ownCloud] + * + * [HKEY_LOCAL_MACHINE\Software\Policies\ownCloud\ownCloud\Setup] + * "ServerUrl"="https://cloud.example.com" + * "AllowServerUrlChange"=dword:00000000 + * + * [HKEY_LOCAL_MACHINE\Software\Policies\ownCloud\ownCloud\Updater] + * "SkipUpdateCheck"=dword:00000001 + * + * [HKEY_LOCAL_MACHINE\Software\Policies\ownCloud\ownCloud\OpenIDConnect] + * "ClientId"="your-client-id" + * "ClientSecret"="your-client-secret" + * "Ports"="8080,8443" + * "Scopes"="openid,email,profile" + * "Prompt"="consent" + * + * [HKEY_LOCAL_MACHINE\Software\Policies\ownCloud\ownCloud\Behavior] + * "MoveToTrash"=dword:00000001 + * */ class OWNCLOUDSYNC_EXPORT SystemConfig { public: - // Access system configuration - // section: account setup + /** + * Determine if changing the server URL is allowed based on system configuration. + * This value is only relevant if SystemConfig::serverUrl() returns a non-empty string. + * @return True if changing the server URL is allowed, false otherwise. + */ + static bool allowServerUrlChange(); + /** + * Retrieve the server URL from the system configuration. + * @return The server URL as a QString. If not set, returns an empty string. + */ static QString serverUrl(); - // section: OpenID Connect + /** + * Retrieve the OpenID Connect configuration from the system configuration. + * The configuration includes client ID, client secret, ports, scopes, and prompt settings. + * The returned OpenIdConfig object may have empty values if not set in the system configuration. + * @return An OpenIdConfig object containing the OpenID Connect settings. + */ static OpenIdConfig openIdConfig(); - // section: updater + /** + * Determine if update checks should be skipped based on system configuration. + * @return True if update checks should be skipped, false otherwise. + */ static bool skipUpdateCheck(); - // section: trash handling + /** + * Determine if files should be moved to trash based on system configuration. + * @return True if files should be moved to trash, false otherwise. + */ static bool moveToTrash(); // General purpose function + /** + * Retrieve a configuration value from the system configuration. + * @param key The key of the configuration value to retrieve. + * @param defaultValue The default value to return if the key is not found. + * @return The configuration value associated with the key, or the default value if the key is not found. + */ static QVariant value(QAnyStringView key, const QVariant &defaultValue); + /** + * Get the path to the system configuration file or registry path based on the operating system and theme. + * @param os operating system type + * @param theme the theme instance + * @return the path to the system configuration file or registry path + */ static QString configPath(const QOperatingSystemVersion::OSType& os, const Theme& theme); }; } From 1ba891913f1f592d8fad0591b8c6fc869062d6fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= <1005065+DeepDiver1975@users.noreply.github.com> Date: Wed, 21 Jan 2026 14:58:18 +0100 Subject: [PATCH 08/17] fix: extract config keys as constants --- src/libsync/config/systemconfig.cpp | 33 +++++++++++++++++++++-------- src/libsync/config/systemconfig.h | 8 ++----- 2 files changed, 26 insertions(+), 15 deletions(-) diff --git a/src/libsync/config/systemconfig.cpp b/src/libsync/config/systemconfig.cpp index 432aa78ec4f..ce15dcfc36a 100644 --- a/src/libsync/config/systemconfig.cpp +++ b/src/libsync/config/systemconfig.cpp @@ -15,6 +15,21 @@ namespace OCC { namespace chrono = std::chrono; +namespace { + // Setup related keys + static constexpr auto KEY_SETUP_ALLOW_SERVER_URL_CHANGE = "Setup/AllowServerUrlChange"; + static constexpr auto KEY_SETUP_SERVER_URL = "Setup/ServerUrl"; + static constexpr auto KEY_SETUP_MOVE_TO_TRASH = "Setup/MoveToTrash"; + // Updater related keys + static constexpr auto KEY_UPDATER_SKIP_UPDATE_CHECK = "Updater/SkipUpdateCheck"; + // OpenID Connect related keys + static constexpr auto KEY_OIDC_CLIENT_ID = "OpenIDConnect/ClientId"; + static constexpr auto KEY_OIDC_CLIENT_SECRET = "OpenIDConnect/ClientSecret"; + static constexpr auto KEY_OIDC_PORTS = "OpenIDConnect/Ports"; + static constexpr auto KEY_OIDC_SCOPES = "OpenIDConnect/Scopes"; + static constexpr auto KEY_OIDC_PROMPT = "OpenIDConnect/Prompt"; +} // anonymous namespace + QVariant SystemConfig::value(QAnyStringView key, const QVariant &defaultValue) { auto format = Utility::isWindows() ? QSettings::NativeFormat : QSettings::IniFormat; @@ -39,23 +54,23 @@ QString SystemConfig::configPath(const QOperatingSystemVersion::OSType& os, cons bool SystemConfig::allowServerUrlChange() { - return value("Setup/AllowServerUrlChange", true).toBool(); + return value(KEY_SETUP_ALLOW_SERVER_URL_CHANGE, true).toBool(); } QString SystemConfig::serverUrl() { - return value("Setup/ServerUrl", QString()).toString(); + return value(KEY_SETUP_SERVER_URL, QString()).toString(); } bool SystemConfig::skipUpdateCheck() { - return value("Updater/SkipUpdateCheck", false).toBool(); + return value(KEY_UPDATER_SKIP_UPDATE_CHECK, false).toBool(); } bool SystemConfig::moveToTrash() { // check settings first; if not present, fall back to the Theme default - QVariant v = value("Setup/MoveToTrash", QVariant()); + QVariant v = value(KEY_SETUP_MOVE_TO_TRASH, QVariant()); if (v.isValid()) { return v.toBool(); } @@ -65,10 +80,10 @@ bool SystemConfig::moveToTrash() OpenIdConfig SystemConfig::openIdConfig() { - auto clientId = value("OpenIDConnect/ClientId", QString()).toString(); - auto clientSecret = value("OpenIDConnect/ClientSecret", QString()).toString(); + auto clientId = value(KEY_OIDC_CLIENT_ID, QString()).toString(); + auto clientSecret = value(KEY_OIDC_CLIENT_SECRET, QString()).toString(); - QVariant portsVar = value("OpenIDConnect/Ports", "0").toString(); + QVariant portsVar = value(KEY_OIDC_PORTS, "0").toString(); QList ports; const auto parts = portsVar.toString().split(QLatin1Char(','), Qt::SkipEmptyParts); for (const QString &p : parts) { @@ -79,8 +94,8 @@ OpenIdConfig SystemConfig::openIdConfig() } } - QString scopes = value("OpenIDConnect/Scopes", QString()).toString(); - QString prompt = value("OpenIDConnect/Prompt", QString()).toString(); + QString scopes = value(KEY_OIDC_SCOPES, QString()).toString(); + QString prompt = value(KEY_OIDC_PROMPT, QString()).toString(); return OpenIdConfig(clientId, clientSecret, ports, scopes, prompt); } diff --git a/src/libsync/config/systemconfig.h b/src/libsync/config/systemconfig.h index 4e81c87df12..5165db0d586 100644 --- a/src/libsync/config/systemconfig.h +++ b/src/libsync/config/systemconfig.h @@ -30,6 +30,7 @@ namespace OCC { * [Setup] * ServerUrl=https://cloud.example.com * AllowServerUrlChange=false + * MoveToTrash=true * * [Updater] * SkipUpdateCheck=true @@ -41,9 +42,6 @@ namespace OCC { * Scopes=openid,email,profile * Prompt=consent * - * [Behavior] - * MoveToTrash=true - * * @example owncloud.reg (Windows Registry) * Windows Registry Editor Version 5.00 * @@ -52,6 +50,7 @@ namespace OCC { * [HKEY_LOCAL_MACHINE\Software\Policies\ownCloud\ownCloud\Setup] * "ServerUrl"="https://cloud.example.com" * "AllowServerUrlChange"=dword:00000000 + * "MoveToTrash"=dword:00000001 * * [HKEY_LOCAL_MACHINE\Software\Policies\ownCloud\ownCloud\Updater] * "SkipUpdateCheck"=dword:00000001 @@ -63,9 +62,6 @@ namespace OCC { * "Scopes"="openid,email,profile" * "Prompt"="consent" * - * [HKEY_LOCAL_MACHINE\Software\Policies\ownCloud\ownCloud\Behavior] - * "MoveToTrash"=dword:00000001 - * */ class OWNCLOUDSYNC_EXPORT SystemConfig { From 54fdf0769a72cb1ad769720f7630748cb8a9a9da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= <1005065+DeepDiver1975@users.noreply.github.com> Date: Thu, 22 Jan 2026 14:52:25 +0100 Subject: [PATCH 09/17] fix: OpenID Connect scopes and prompt example config --- src/libsync/config/systemconfig.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libsync/config/systemconfig.h b/src/libsync/config/systemconfig.h index 5165db0d586..8c5ba29bcec 100644 --- a/src/libsync/config/systemconfig.h +++ b/src/libsync/config/systemconfig.h @@ -39,8 +39,8 @@ namespace OCC { * ClientId=your-client-id * ClientSecret=your-client-secret * Ports=8080,8443 - * Scopes=openid,email,profile - * Prompt=consent + * Scopes=openid offline_access email profile + * Prompt=select_account consent * * @example owncloud.reg (Windows Registry) * Windows Registry Editor Version 5.00 @@ -59,8 +59,8 @@ namespace OCC { * "ClientId"="your-client-id" * "ClientSecret"="your-client-secret" * "Ports"="8080,8443" - * "Scopes"="openid,email,profile" - * "Prompt"="consent" + * "Scopes"="openid offline_access email profile" + * "Prompt"="select_account consent" * */ class OWNCLOUDSYNC_EXPORT SystemConfig From 2fe4be68e5f6571ed8c05381b3163c13665c5d49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= <1005065+DeepDiver1975@users.noreply.github.com> Date: Mon, 26 Jan 2026 17:06:21 +0100 Subject: [PATCH 10/17] fix: merge SystemConfig and ConfigResolver --- src/gui/creds/credentials.cpp | 3 +- src/gui/creds/oauth.cpp | 6 +- src/gui/creds/oauth.h | 2 +- .../creds/requestauthenticationcontroller.cpp | 5 +- .../newaccountwizard/oauthpagecontroller.cpp | 5 +- .../newaccountwizard/urlpagecontroller.cpp | 6 +- src/libsync/CMakeLists.txt | 1 - src/libsync/config/configresolver.cpp | 52 ------------ src/libsync/config/configresolver.h | 51 ------------ src/libsync/config/openidconfig.cpp | 4 + src/libsync/config/openidconfig.h | 1 + src/libsync/config/systemconfig.cpp | 83 ++++++++++++------- src/libsync/config/systemconfig.h | 31 +++---- src/libsync/configfile.cpp | 5 +- src/libsync/configfile.h | 2 + test/testoauth.cpp | 5 +- 16 files changed, 97 insertions(+), 165 deletions(-) delete mode 100644 src/libsync/config/configresolver.cpp delete mode 100644 src/libsync/config/configresolver.h diff --git a/src/gui/creds/credentials.cpp b/src/gui/creds/credentials.cpp index 89997b34b06..88ef20be0a9 100644 --- a/src/gui/creds/credentials.cpp +++ b/src/gui/creds/credentials.cpp @@ -288,7 +288,8 @@ void Credentials::refreshAccessTokenInternal() return; // parent with nam to ensure we reset when the nam is reset // todo: #22 - the parenting here is highly questionable, as is the use of the shared account ptr - _oAuthJob = new AccountBasedOAuth(_account, this); + SystemConfig systemConfig; + _oAuthJob = new AccountBasedOAuth(_account, systemConfig.openIdConfig(), this); connect(_oAuthJob, &AccountBasedOAuth::refreshError, this, &Credentials::handleRefreshError); connect(_oAuthJob, &AccountBasedOAuth::refreshFinished, this, &Credentials::handleRefreshSuccess); diff --git a/src/gui/creds/oauth.cpp b/src/gui/creds/oauth.cpp index 04c77b3f851..12ea3282b7b 100644 --- a/src/gui/creds/oauth.cpp +++ b/src/gui/creds/oauth.cpp @@ -15,7 +15,7 @@ #include "oauth.h" #include "accessmanager.h" -#include "config/configresolver.h" +#include "config/systemconfig.h" #include "creds/credentialssupport.h" #include "gui/networkadapters/userinfoadapter.h" #include "libsync/creds/credentialmanager.h" @@ -478,8 +478,8 @@ void OAuth::openBrowser() // todo: I was contemplating how we can make sure the passed account isn't null before we use it // to seed the OAuth ctr, and really, I'm not sure this should be a subclass of oauth in the first place. Instead it could simply use an // oauth instance to complete the tasks it can't do itself -> this could possibly be a "has a" not an "is a" impl -AccountBasedOAuth::AccountBasedOAuth(Account *account, QObject *parent) - : OAuth(account->url(), account->davUser(), ConfigResolver::openIdConfig(), account->accessManager(), parent) +AccountBasedOAuth::AccountBasedOAuth(Account *account, const OpenIdConfig& openIdConfig, QObject *parent) + : OAuth(account->url(), account->davUser(), openIdConfig, account->accessManager(), parent) , _account(account) { } diff --git a/src/gui/creds/oauth.h b/src/gui/creds/oauth.h index cd6cdab4e81..45661aa2314 100644 --- a/src/gui/creds/oauth.h +++ b/src/gui/creds/oauth.h @@ -146,7 +146,7 @@ class AccountBasedOAuth : public OAuth Q_OBJECT public: - explicit AccountBasedOAuth(Account *account, QObject *parent); + explicit AccountBasedOAuth(Account *account, const OpenIdConfig& openIdConfig, QObject *parent); void startAuthentication() override; diff --git a/src/gui/creds/requestauthenticationcontroller.cpp b/src/gui/creds/requestauthenticationcontroller.cpp index 9ec7f1c7eda..315a21aff9f 100644 --- a/src/gui/creds/requestauthenticationcontroller.cpp +++ b/src/gui/creds/requestauthenticationcontroller.cpp @@ -21,6 +21,8 @@ #include "accountmodalwidget.h" #include "settingsdialog.h" +#include + namespace OCC { /** @@ -58,8 +60,9 @@ void RequestAuthenticationController::startAuthentication(Account *account) delete _oauth; _oauth = nullptr; } + SystemConfig systemConfig; _account = account; - _oauth = new AccountBasedOAuth(_account, this); + _oauth = new AccountBasedOAuth(_account, systemConfig.openIdConfig(), this); connect(_oauth, &OAuth::authorisationLinkChanged, this, &RequestAuthenticationController::authUrlReady); connect(_oauth, &OAuth::result, this, &RequestAuthenticationController::handleOAuthResult); if (_widget && _modalWidget == nullptr) { // first show of the gui diff --git a/src/gui/newaccountwizard/oauthpagecontroller.cpp b/src/gui/newaccountwizard/oauthpagecontroller.cpp index 2692522ac35..80541e1e45d 100644 --- a/src/gui/newaccountwizard/oauthpagecontroller.cpp +++ b/src/gui/newaccountwizard/oauthpagecontroller.cpp @@ -15,7 +15,7 @@ #include "accessmanager.h" #include "accountmanager.h" -#include "config/configresolver.h" +#include "config/systemconfig.h" #include "networkadapters/fetchcapabilitiesadapter.h" #include "networkadapters/userinfoadapter.h" #include "networkadapters/webfingerlookupadapter.h" @@ -209,7 +209,8 @@ bool OAuthPageController::validate() _authEndpoint.clear(); _urlField->clear(); - _oauth = new OAuth(_authUrl, {}, ConfigResolver::openIdConfig(), _accessManager.get(), this); + SystemConfig systemConfig; + _oauth = new OAuth(_authUrl, {}, systemConfig.openIdConfig(), _accessManager.get(), this); // if we ever need to split out the auth link calculation, it's coming from fetchWellKnown which is a subset of // the "full" authentication routine in the oauth impl connect(_oauth, &OAuth::authorisationLinkChanged, this, &OAuthPageController::authUrlReady); diff --git a/src/gui/newaccountwizard/urlpagecontroller.cpp b/src/gui/newaccountwizard/urlpagecontroller.cpp index 63405c8fe66..8fcc71f4d83 100644 --- a/src/gui/newaccountwizard/urlpagecontroller.cpp +++ b/src/gui/newaccountwizard/urlpagecontroller.cpp @@ -13,7 +13,6 @@ */ #include "urlpagecontroller.h" -#include "../../libsync/config/configresolver.h" #include "../../libsync/config/systemconfig.h" #include "accessmanager.h" #include "configfile.h" @@ -41,7 +40,8 @@ UrlPageController::UrlPageController(QWizardPage *page, AccessManager *accessMan return; } - QString serverUrl = ConfigResolver::serverUrl(); + SystemConfig systemConfig; + QString serverUrl = systemConfig.serverUrl(); // no server url was given by any means, so the user has to provide one if (serverUrl.isEmpty()) { return; @@ -49,7 +49,7 @@ UrlPageController::UrlPageController(QWizardPage *page, AccessManager *accessMan setUrl(serverUrl); // The system admin provides the url, don't let the user change it! - bool allowServerUrlChange = ConfigResolver::allowServerUrlChange(); + bool allowServerUrlChange = systemConfig.allowServerUrlChange(); if (!allowServerUrlChange) { _urlField->setEnabled(false); _instructionLabel->setText(tr("Your web browser will be opened to complete sign in.")); diff --git a/src/libsync/CMakeLists.txt b/src/libsync/CMakeLists.txt index 737391a2db0..1fb21eae1a1 100644 --- a/src/libsync/CMakeLists.txt +++ b/src/libsync/CMakeLists.txt @@ -53,7 +53,6 @@ set(libsync_SRCS appprovider.cpp config/systemconfig.cpp - config/configresolver.cpp config/openidconfig.cpp ) diff --git a/src/libsync/config/configresolver.cpp b/src/libsync/config/configresolver.cpp deleted file mode 100644 index 546e86a3b98..00000000000 --- a/src/libsync/config/configresolver.cpp +++ /dev/null @@ -1,52 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -// SPDX-FileCopyrightText: 2026 Thomas Müller - -#include "configresolver.h" -#include "../theme.h" -#include "systemconfig.h" - -namespace OCC { - -bool ConfigResolver::allowServerUrlChange() -{ - return SystemConfig::allowServerUrlChange(); -} - -QString ConfigResolver::serverUrl() -{ - // a theme can provide a hardcoded url which is not subject of change by definition - auto serverUrl = Theme::instance()->overrideServerUrlV2(); - if (!serverUrl.isEmpty()) { - return serverUrl; - } - - return SystemConfig::serverUrl();; -} - -bool ConfigResolver::skipUpdateCheck() -{ - // only in SystemConfig - this is not a Theme option - return SystemConfig::skipUpdateCheck(); -} - -OpenIdConfig ConfigResolver::openIdConfig() -{ - // TODO: shall we fill values in from the Theme in case not set within SystemConfig? - // not reasonable for clientId/secret, but maybe for ports/scopes/prompt? - // system config has precedence here - auto cfg = SystemConfig::openIdConfig(); - if (!cfg.clientId().isEmpty()) { - return cfg; - } - - // load config from theme - QString clientId = Theme::instance()->oauthClientId(); - QString clientSecret = Theme::instance()->oauthClientSecret(); - - const auto ports = Theme::instance()->oauthPorts(); - QString scopes = Theme::instance()->openIdConnectScopes(); - QString prompt = Theme::instance()->openIdConnectPrompt(); - - return OpenIdConfig(clientId, clientSecret, ports, scopes, prompt); -} -} diff --git a/src/libsync/config/configresolver.h b/src/libsync/config/configresolver.h deleted file mode 100644 index ead17942e25..00000000000 --- a/src/libsync/config/configresolver.h +++ /dev/null @@ -1,51 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -// SPDX-FileCopyrightText: 2026 Thomas Müller - -#pragma once - -#include "openidconfig.h" -#include "owncloudlib.h" - -#include - - -namespace OCC { - -/** - * @brief The ConfigResolver class - * @ingroup libsync - * @note This class provides methods to resolve configuration settings - * from various sources such as system configuration and themes. - */ -class OWNCLOUDSYNC_EXPORT ConfigResolver -{ -public: - /** - * Determine if changing the server URL is allowed. - * This checks the system configuration for the relevant setting. - * @return True if changing the server URL is allowed, false otherwise. - */ - static bool allowServerUrlChange(); - - /** - * Retrieve the server URL. - * This checks the system configuration and theme overrides for the server URL. - * @return The server URL as a QString. If not set, returns an empty string. - */ - static QString serverUrl(); - - /** - * Determine if update checks should be skipped. - * This checks the system configuration for the relevant setting. - * @return True if update checks should be skipped, false otherwise. - */ - static bool skipUpdateCheck(); - - /** - * Retrieve the OpenID Connect configuration. - * This checks the system configuration and theme settings for OpenID Connect parameters. - * @return An OpenIdConfig object containing the OpenID Connect settings. - */ - static OpenIdConfig openIdConfig(); -}; -} diff --git a/src/libsync/config/openidconfig.cpp b/src/libsync/config/openidconfig.cpp index 9765da105b4..6a90a65b39c 100644 --- a/src/libsync/config/openidconfig.cpp +++ b/src/libsync/config/openidconfig.cpp @@ -5,6 +5,10 @@ namespace OCC { +OpenIdConfig::OpenIdConfig() +{ +} + OpenIdConfig::OpenIdConfig(const QString &clientId, const QString &clientSecret, const QList &ports, const QString &scopes, const QString &prompt) : _clientId(clientId) , _clientSecret(clientSecret) diff --git a/src/libsync/config/openidconfig.h b/src/libsync/config/openidconfig.h index 41dae8a6372..17df61d1ade 100644 --- a/src/libsync/config/openidconfig.h +++ b/src/libsync/config/openidconfig.h @@ -18,6 +18,7 @@ namespace OCC { class OWNCLOUDSYNC_EXPORT OpenIdConfig { public: + OpenIdConfig(); explicit OpenIdConfig(const QString &clientId, const QString &clientSecret, const QList &ports, const QString &scopes, const QString &prompt); QString clientId() const; diff --git a/src/libsync/config/systemconfig.cpp b/src/libsync/config/systemconfig.cpp index ce15dcfc36a..2b41c47767b 100644 --- a/src/libsync/config/systemconfig.cpp +++ b/src/libsync/config/systemconfig.cpp @@ -30,13 +30,37 @@ namespace { static constexpr auto KEY_OIDC_PROMPT = "OpenIDConnect/Prompt"; } // anonymous namespace -QVariant SystemConfig::value(QAnyStringView key, const QVariant &defaultValue) +SystemConfig::SystemConfig() { auto format = Utility::isWindows() ? QSettings::NativeFormat : QSettings::IniFormat; QSettings system(configPath(QOperatingSystemVersion::currentType(), *Theme::instance()), format); - return system.value(key, defaultValue); + _allowServerURLChange = system.value(KEY_SETUP_ALLOW_SERVER_URL_CHANGE, true).toBool(); + _serverUrl = system.value(KEY_SETUP_SERVER_URL, QString()).toString(); + _skipUpdateCheck = system.value(KEY_UPDATER_SKIP_UPDATE_CHECK, false).toBool(); + _moveToTrash = system.value(KEY_SETUP_MOVE_TO_TRASH, false).toBool(); + + // read OpenID Connect configuration + auto clientId = system.value(KEY_OIDC_CLIENT_ID, QString()).toString(); + auto clientSecret = system.value(KEY_OIDC_CLIENT_SECRET, QString()).toString(); + + QVariant portsVar = system.value(KEY_OIDC_PORTS, "0").toString(); + QList ports; + const auto parts = portsVar.toString().split(QLatin1Char(','), Qt::SkipEmptyParts); + for (const QString &p : parts) { + bool ok = false; + const quint16 val = static_cast(p.trimmed().toUInt(&ok)); + if (ok) { + ports.append(val); + } + } + + QString scopes = system.value(KEY_OIDC_SCOPES, QString()).toString(); + QString prompt = system.value(KEY_OIDC_PROMPT, QString()).toString(); + + _openIdConfig = OpenIdConfig(clientId, clientSecret, ports, scopes, prompt); } + QString SystemConfig::configPath(const QOperatingSystemVersion::OSType& os, const Theme& theme) { if (os == QOperatingSystemVersion::Windows) { @@ -52,51 +76,48 @@ QString SystemConfig::configPath(const QOperatingSystemVersion::OSType& os, cons return QString("/etc/%1/%1.ini").arg(theme.appName()); } -bool SystemConfig::allowServerUrlChange() +bool SystemConfig::allowServerUrlChange() const { - return value(KEY_SETUP_ALLOW_SERVER_URL_CHANGE, true).toBool(); + return _allowServerURLChange; } -QString SystemConfig::serverUrl() +QString SystemConfig::serverUrl() const { - return value(KEY_SETUP_SERVER_URL, QString()).toString(); + // a theme can provide a hardcoded url which is not subject of change by definition + auto serverUrl = Theme::instance()->overrideServerUrlV2(); + if (!serverUrl.isEmpty()) { + return serverUrl; + } + + return _serverUrl; } -bool SystemConfig::skipUpdateCheck() +bool SystemConfig::skipUpdateCheck() const { - return value(KEY_UPDATER_SKIP_UPDATE_CHECK, false).toBool(); + return _skipUpdateCheck; } -bool SystemConfig::moveToTrash() +bool SystemConfig::moveToTrash() const { - // check settings first; if not present, fall back to the Theme default - QVariant v = value(KEY_SETUP_MOVE_TO_TRASH, QVariant()); - if (v.isValid()) { - return v.toBool(); - } - - return false; + return _moveToTrash; } -OpenIdConfig SystemConfig::openIdConfig() +OpenIdConfig SystemConfig::openIdConfig() const { - auto clientId = value(KEY_OIDC_CLIENT_ID, QString()).toString(); - auto clientSecret = value(KEY_OIDC_CLIENT_SECRET, QString()).toString(); - - QVariant portsVar = value(KEY_OIDC_PORTS, "0").toString(); - QList ports; - const auto parts = portsVar.toString().split(QLatin1Char(','), Qt::SkipEmptyParts); - for (const QString &p : parts) { - bool ok = false; - const quint16 val = static_cast(p.trimmed().toUInt(&ok)); - if (ok) { - ports.append(val); - } + // system config has precedence here + if (!_openIdConfig.clientId().isEmpty()) { + return _openIdConfig; } - QString scopes = value(KEY_OIDC_SCOPES, QString()).toString(); - QString prompt = value(KEY_OIDC_PROMPT, QString()).toString(); + // load config from theme + QString clientId = Theme::instance()->oauthClientId(); + QString clientSecret = Theme::instance()->oauthClientSecret(); + + const auto ports = Theme::instance()->oauthPorts(); + QString scopes = Theme::instance()->openIdConnectScopes(); + QString prompt = Theme::instance()->openIdConnectPrompt(); return OpenIdConfig(clientId, clientSecret, ports, scopes, prompt); + } } diff --git a/src/libsync/config/systemconfig.h b/src/libsync/config/systemconfig.h index 8c5ba29bcec..152d0d3177d 100644 --- a/src/libsync/config/systemconfig.h +++ b/src/libsync/config/systemconfig.h @@ -66,53 +66,54 @@ namespace OCC { class OWNCLOUDSYNC_EXPORT SystemConfig { public: + explicit SystemConfig(); /** * Determine if changing the server URL is allowed based on system configuration. * This value is only relevant if SystemConfig::serverUrl() returns a non-empty string. * @return True if changing the server URL is allowed, false otherwise. */ + bool allowServerUrlChange() const; - static bool allowServerUrlChange(); /** - * Retrieve the server URL from the system configuration. + * Retrieve the server URL from the system configuration or theme. * @return The server URL as a QString. If not set, returns an empty string. */ - static QString serverUrl(); + QString serverUrl() const; /** - * Retrieve the OpenID Connect configuration from the system configuration. + * Retrieve the OpenID Connect configuration from the system configuration or the theme. * The configuration includes client ID, client secret, ports, scopes, and prompt settings. * The returned OpenIdConfig object may have empty values if not set in the system configuration. * @return An OpenIdConfig object containing the OpenID Connect settings. */ - static OpenIdConfig openIdConfig(); + OpenIdConfig openIdConfig() const; /** * Determine if update checks should be skipped based on system configuration. * @return True if update checks should be skipped, false otherwise. */ - static bool skipUpdateCheck(); + bool skipUpdateCheck() const; /** * Determine if files should be moved to trash based on system configuration. * @return True if files should be moved to trash, false otherwise. */ - static bool moveToTrash(); + bool moveToTrash() const; - // General purpose function - /** - * Retrieve a configuration value from the system configuration. - * @param key The key of the configuration value to retrieve. - * @param defaultValue The default value to return if the key is not found. - * @return The configuration value associated with the key, or the default value if the key is not found. - */ - static QVariant value(QAnyStringView key, const QVariant &defaultValue); /** * Get the path to the system configuration file or registry path based on the operating system and theme. * @param os operating system type * @param theme the theme instance * @return the path to the system configuration file or registry path + * @internal kept public for testing purposes */ static QString configPath(const QOperatingSystemVersion::OSType& os, const Theme& theme); + +private: + bool _allowServerURLChange; + QString _serverUrl; + bool _skipUpdateCheck; + OpenIdConfig _openIdConfig; + bool _moveToTrash; }; } diff --git a/src/libsync/configfile.cpp b/src/libsync/configfile.cpp index 2cd8bf7480e..cc734f4a04f 100644 --- a/src/libsync/configfile.cpp +++ b/src/libsync/configfile.cpp @@ -16,7 +16,6 @@ #include "common/utility.h" #include "common/version.h" -#include "config/configresolver.h" #include "config/systemconfig.h" #ifdef Q_OS_WIN #include "common/utility_win.h" @@ -448,7 +447,7 @@ chrono::milliseconds ConfigFile::updateCheckInterval(const QString &connection) bool ConfigFile::skipUpdateCheck() const { - if (ConfigResolver::skipUpdateCheck()) { + if (_systemConfig.skipUpdateCheck()) { return true; } auto con = defaultConnection(); @@ -578,7 +577,7 @@ void ConfigFile::setPauseSyncWhenMetered(bool isChecked) bool ConfigFile::moveToTrash() const { // system config has precedence - if (SystemConfig::moveToTrash()) { + if (_systemConfig.moveToTrash()) { return true; } diff --git a/src/libsync/configfile.h b/src/libsync/configfile.h index e9f108e9a10..f3c854cd2e3 100644 --- a/src/libsync/configfile.h +++ b/src/libsync/configfile.h @@ -15,6 +15,7 @@ #pragma once #include "owncloudlib.h" +#include "config/systemconfig.h" #include #include @@ -178,6 +179,7 @@ class OWNCLOUDSYNC_EXPORT ConfigFile void setValue(const QString &key, const QVariant &value); private: + SystemConfig _systemConfig; typedef QSharedPointer SharedCreds; static QString _oCVersion; diff --git a/test/testoauth.cpp b/test/testoauth.cpp index 77d9edf0697..30a8336eae4 100644 --- a/test/testoauth.cpp +++ b/test/testoauth.cpp @@ -13,6 +13,8 @@ #include "testutils/syncenginetestutils.h" #include "theme.h" +#include + using namespace std::chrono_literals; using namespace OCC; @@ -155,7 +157,8 @@ class OAuthTestCase : public QObject QObject::connect(&desktopServiceHook, &DesktopServiceHook::hooked, this, &OAuthTestCase::openBrowserHook); - auto out = std::make_unique(account, nullptr); + SystemConfig config; + auto out = std::make_unique(account, config.openIdConfig(), nullptr); QObject::connect(out.get(), &OAuth::result, this, &OAuthTestCase::oauthResult); return out; } From 20b6f5ac61e827146a1410cd37ad1430ebc091d1 Mon Sep 17 00:00:00 2001 From: Erik Verbruggen Date: Mon, 2 Feb 2026 16:55:30 +0100 Subject: [PATCH 11/17] Incorporate feedback --- src/gui/creds/oauth.cpp | 2 +- src/gui/newaccountwizard/urlpagecontroller.cpp | 3 +-- src/libsync/config/systemconfig.cpp | 16 +++++++++++++--- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/gui/creds/oauth.cpp b/src/gui/creds/oauth.cpp index 12ea3282b7b..743544e149f 100644 --- a/src/gui/creds/oauth.cpp +++ b/src/gui/creds/oauth.cpp @@ -43,7 +43,7 @@ namespace { const QString wellKnownPathC = QStringLiteral("/.well-known/openid-configuration"); -auto defaultOauthPromptValue(const OpenIdConfig& config) +OAuth::PromptValuesSupportedFlags defaultOauthPromptValue(const OpenIdConfig& config) { static const auto promptValue = [config] { auto prompt = config.prompt(); diff --git a/src/gui/newaccountwizard/urlpagecontroller.cpp b/src/gui/newaccountwizard/urlpagecontroller.cpp index 8fcc71f4d83..4a99edc7f23 100644 --- a/src/gui/newaccountwizard/urlpagecontroller.cpp +++ b/src/gui/newaccountwizard/urlpagecontroller.cpp @@ -13,9 +13,8 @@ */ #include "urlpagecontroller.h" -#include "../../libsync/config/systemconfig.h" #include "accessmanager.h" -#include "configfile.h" +#include "config/systemconfig.h" #include "networkadapters/determineauthtypeadapter.h" #include "networkadapters/discoverwebfingerserviceadapter.h" #include "networkadapters/resolveurladapter.h" diff --git a/src/libsync/config/systemconfig.cpp b/src/libsync/config/systemconfig.cpp index 2b41c47767b..00547d90a04 100644 --- a/src/libsync/config/systemconfig.cpp +++ b/src/libsync/config/systemconfig.cpp @@ -63,21 +63,31 @@ SystemConfig::SystemConfig() QString SystemConfig::configPath(const QOperatingSystemVersion::OSType& os, const Theme& theme) { + // Important: these paths conform to how names typically work on the systems on which they are used. This includes usage of upper-/lowercase. + if (os == QOperatingSystemVersion::Windows) { - // we use HKEY_LOCAL_MACHINE\Software\Policies since this is the location whe GPO operates + // We use HKEY_LOCAL_MACHINE\Software\Policies since this is the location where GPO operates. + // Note: use of uppercase/camelcase is common. return QString("HKEY_LOCAL_MACHINE\\Software\\Policies\\%1\\%2").arg(theme.vendor(), theme.appNameGUI()); } + if (os == QOperatingSystemVersion::MacOS) { - // we use a subfolder to have one common location where in the future more files can be stored (like icons, images and such) - // ini is used on macOS in contrary to plist because they are easier to maintain + // We use a subfolder to have one common location where in the future more files can be stored (like icons, images and such) + // ini is used on macOS in contrary to plist because they are easier to maintain. + // Note: rev-domain notation and lowercase is typically used. return QString("/Library/Preferences/%1/%2.ini").arg(theme.orgDomainName(), theme.appName()); } + // On Unix style systems, the application name in lowercase is typically used. return QString("/etc/%1/%1.ini").arg(theme.appName()); } bool SystemConfig::allowServerUrlChange() const { + // If a theme provides a hardcoded URL, do not allow for URL change. + QString overrideServerUrl = Theme::instance()->overrideServerUrlV2(); + if (!overrideServerUrl.isEmpty()) + return false; return _allowServerURLChange; } From 931b6c7030d35ce94ec160387679e792482c8fd3 Mon Sep 17 00:00:00 2001 From: Erik Verbruggen Date: Wed, 4 Feb 2026 14:58:06 +0100 Subject: [PATCH 12/17] More feedback fixes --- src/gui/creds/credentials.cpp | 5 ++-- src/gui/creds/credentials.h | 1 + src/libsync/config/systemconfig.cpp | 37 +++++++---------------------- src/libsync/config/systemconfig.h | 20 ++++++++++------ src/libsync/configfile.cpp | 7 +----- 5 files changed, 26 insertions(+), 44 deletions(-) diff --git a/src/gui/creds/credentials.cpp b/src/gui/creds/credentials.cpp index 88ef20be0a9..7933420b524 100644 --- a/src/gui/creds/credentials.cpp +++ b/src/gui/creds/credentials.cpp @@ -85,6 +85,7 @@ Credentials::Credentials(const QString &token, const QString &refreshToken, Acco , _accessToken(token) , _refreshToken(refreshToken) , _ready(false) + , _openIdConfig(SystemConfig().openIdConfig()) { if (!token.isEmpty() && !refreshToken.isEmpty()) _ready = true; @@ -286,10 +287,10 @@ void Credentials::refreshAccessTokenInternal() { if (!_account) return; + // parent with nam to ensure we reset when the nam is reset // todo: #22 - the parenting here is highly questionable, as is the use of the shared account ptr - SystemConfig systemConfig; - _oAuthJob = new AccountBasedOAuth(_account, systemConfig.openIdConfig(), this); + _oAuthJob = new AccountBasedOAuth(_account, _openIdConfig, this); connect(_oAuthJob, &AccountBasedOAuth::refreshError, this, &Credentials::handleRefreshError); connect(_oAuthJob, &AccountBasedOAuth::refreshFinished, this, &Credentials::handleRefreshSuccess); diff --git a/src/gui/creds/credentials.h b/src/gui/creds/credentials.h index 0aa28955e1e..a6db2d96a09 100644 --- a/src/gui/creds/credentials.h +++ b/src/gui/creds/credentials.h @@ -94,6 +94,7 @@ class OWNCLOUDGUI_EXPORT Credentials : public AbstractCredentials QString _fetchErrorString; bool _ready = false; + const OpenIdConfig _openIdConfig; int _tokenRefreshRetriesCount = 0; QPointer _oAuthJob; diff --git a/src/libsync/config/systemconfig.cpp b/src/libsync/config/systemconfig.cpp index 00547d90a04..dfb57aae9b0 100644 --- a/src/libsync/config/systemconfig.cpp +++ b/src/libsync/config/systemconfig.cpp @@ -15,36 +15,20 @@ namespace OCC { namespace chrono = std::chrono; -namespace { - // Setup related keys - static constexpr auto KEY_SETUP_ALLOW_SERVER_URL_CHANGE = "Setup/AllowServerUrlChange"; - static constexpr auto KEY_SETUP_SERVER_URL = "Setup/ServerUrl"; - static constexpr auto KEY_SETUP_MOVE_TO_TRASH = "Setup/MoveToTrash"; - // Updater related keys - static constexpr auto KEY_UPDATER_SKIP_UPDATE_CHECK = "Updater/SkipUpdateCheck"; - // OpenID Connect related keys - static constexpr auto KEY_OIDC_CLIENT_ID = "OpenIDConnect/ClientId"; - static constexpr auto KEY_OIDC_CLIENT_SECRET = "OpenIDConnect/ClientSecret"; - static constexpr auto KEY_OIDC_PORTS = "OpenIDConnect/Ports"; - static constexpr auto KEY_OIDC_SCOPES = "OpenIDConnect/Scopes"; - static constexpr auto KEY_OIDC_PROMPT = "OpenIDConnect/Prompt"; -} // anonymous namespace - SystemConfig::SystemConfig() { auto format = Utility::isWindows() ? QSettings::NativeFormat : QSettings::IniFormat; QSettings system(configPath(QOperatingSystemVersion::currentType(), *Theme::instance()), format); - _allowServerURLChange = system.value(KEY_SETUP_ALLOW_SERVER_URL_CHANGE, true).toBool(); - _serverUrl = system.value(KEY_SETUP_SERVER_URL, QString()).toString(); - _skipUpdateCheck = system.value(KEY_UPDATER_SKIP_UPDATE_CHECK, false).toBool(); - _moveToTrash = system.value(KEY_SETUP_MOVE_TO_TRASH, false).toBool(); + _allowServerURLChange = system.value(SetupAllowServerUrlChangeKey, true).toBool(); + _serverUrl = system.value(SetupServerUrlKey, QString()).toString(); + _skipUpdateCheck = system.value(UpdaterSkipUpdateCheckKey, false).toBool(); // read OpenID Connect configuration - auto clientId = system.value(KEY_OIDC_CLIENT_ID, QString()).toString(); - auto clientSecret = system.value(KEY_OIDC_CLIENT_SECRET, QString()).toString(); + auto clientId = system.value(OidcClientIdKey, QString()).toString(); + auto clientSecret = system.value(OidcClientSecretKey, QString()).toString(); - QVariant portsVar = system.value(KEY_OIDC_PORTS, "0").toString(); + QVariant portsVar = system.value(OidcPortsKey, "0").toString(); QList ports; const auto parts = portsVar.toString().split(QLatin1Char(','), Qt::SkipEmptyParts); for (const QString &p : parts) { @@ -55,8 +39,8 @@ SystemConfig::SystemConfig() } } - QString scopes = system.value(KEY_OIDC_SCOPES, QString()).toString(); - QString prompt = system.value(KEY_OIDC_PROMPT, QString()).toString(); + QString scopes = system.value(OidcScopesKey, QString()).toString(); + QString prompt = system.value(OidcPortsKey, QString()).toString(); _openIdConfig = OpenIdConfig(clientId, clientSecret, ports, scopes, prompt); } @@ -107,11 +91,6 @@ bool SystemConfig::skipUpdateCheck() const return _skipUpdateCheck; } -bool SystemConfig::moveToTrash() const -{ - return _moveToTrash; -} - OpenIdConfig SystemConfig::openIdConfig() const { // system config has precedence here diff --git a/src/libsync/config/systemconfig.h b/src/libsync/config/systemconfig.h index 152d0d3177d..f1b5ea20106 100644 --- a/src/libsync/config/systemconfig.h +++ b/src/libsync/config/systemconfig.h @@ -94,12 +94,6 @@ class OWNCLOUDSYNC_EXPORT SystemConfig */ bool skipUpdateCheck() const; - /** - * Determine if files should be moved to trash based on system configuration. - * @return True if files should be moved to trash, false otherwise. - */ - bool moveToTrash() const; - /** * Get the path to the system configuration file or registry path based on the operating system and theme. * @param os operating system type @@ -109,11 +103,23 @@ class OWNCLOUDSYNC_EXPORT SystemConfig */ static QString configPath(const QOperatingSystemVersion::OSType& os, const Theme& theme); +private: // System settings keys + // Setup related keys + inline static const QString SetupAllowServerUrlChangeKey = QStringLiteral("Setup/AllowServerUrlChange"); + inline static const QString SetupServerUrlKey = QStringLiteral("Setup/ServerUrl"); + // Updater related keys + inline static const QString UpdaterSkipUpdateCheckKey = QStringLiteral("Updater/SkipUpdateCheck"); + // OpenID Connect related keys + inline static const QString OidcClientIdKey = QStringLiteral("OpenIDConnect/ClientId"); + inline static const QString OidcClientSecretKey = QStringLiteral("OpenIDConnect/ClientSecret"); + inline static const QString OidcPortsKey = QStringLiteral("OpenIDConnect/Ports"); + inline static const QString OidcScopesKey = QStringLiteral("OpenIDConnect/Scopes"); + inline static const QString OidcPromptKey = QStringLiteral("OpenIDConnect/Prompt"); + private: bool _allowServerURLChange; QString _serverUrl; bool _skipUpdateCheck; OpenIdConfig _openIdConfig; - bool _moveToTrash; }; } diff --git a/src/libsync/configfile.cpp b/src/libsync/configfile.cpp index 0c592598cbf..cbaa7209afe 100644 --- a/src/libsync/configfile.cpp +++ b/src/libsync/configfile.cpp @@ -553,12 +553,7 @@ void ConfigFile::setPauseSyncWhenMetered(bool isChecked) bool ConfigFile::moveToTrash() const { - // system config has precedence - if (_systemConfig.moveToTrash()) { - return true; - } - - auto defaultValue = Theme::instance()->moveToTrashDefaultValue(); + bool defaultValue = Theme::instance()->moveToTrashDefaultValue(); return getValue(moveToTrashC(), QString(), defaultValue).toBool(); } From cd43ad3d22969d3f3838aa689ea7bff3f9d02726 Mon Sep 17 00:00:00 2001 From: Erik Verbruggen Date: Tue, 24 Feb 2026 16:28:28 +0100 Subject: [PATCH 13/17] Change system config handling to the new spec --- src/gui/creds/credentials.cpp | 1 + src/libsync/config/openidconfig.cpp | 5 ++ src/libsync/config/openidconfig.h | 4 +- src/libsync/config/systemconfig.cpp | 113 +++++++++++++++++----------- src/libsync/config/systemconfig.h | 6 +- src/libsync/configfile.cpp | 26 ------- src/libsync/configfile.h | 5 -- src/libsync/owncloudtheme.cpp | 5 ++ src/libsync/owncloudtheme.h | 1 + src/libsync/theme.cpp | 5 ++ src/libsync/theme.h | 8 +- 11 files changed, 99 insertions(+), 80 deletions(-) diff --git a/src/gui/creds/credentials.cpp b/src/gui/creds/credentials.cpp index 7933420b524..f97242deddc 100644 --- a/src/gui/creds/credentials.cpp +++ b/src/gui/creds/credentials.cpp @@ -15,6 +15,7 @@ #include "accessmanager.h" #include "account.h" +#include "config/systemconfig.h" #include "configfile.h" #include "creds/credentialmanager.h" #include "oauth.h" diff --git a/src/libsync/config/openidconfig.cpp b/src/libsync/config/openidconfig.cpp index 6a90a65b39c..2ef1154af16 100644 --- a/src/libsync/config/openidconfig.cpp +++ b/src/libsync/config/openidconfig.cpp @@ -38,4 +38,9 @@ QString OpenIdConfig::prompt() const { return _prompt; } +bool OpenIdConfig::isValid() const +{ + return !_clientId.isEmpty() && !_ports.isEmpty(); +} + } diff --git a/src/libsync/config/openidconfig.h b/src/libsync/config/openidconfig.h index 17df61d1ade..515723d8e78 100644 --- a/src/libsync/config/openidconfig.h +++ b/src/libsync/config/openidconfig.h @@ -27,6 +27,8 @@ class OWNCLOUDSYNC_EXPORT OpenIdConfig QString scopes() const; QString prompt() const; + bool isValid() const; + private: QString _clientId; QString _clientSecret; @@ -34,4 +36,4 @@ class OWNCLOUDSYNC_EXPORT OpenIdConfig QString _scopes; QString _prompt; }; -} \ No newline at end of file +} diff --git a/src/libsync/config/systemconfig.cpp b/src/libsync/config/systemconfig.cpp index dfb57aae9b0..2b78affbbbf 100644 --- a/src/libsync/config/systemconfig.cpp +++ b/src/libsync/config/systemconfig.cpp @@ -13,35 +13,79 @@ namespace OCC { +Q_LOGGING_CATEGORY(lcSystemConfig, "sync.systemconfig", QtInfoMsg) + namespace chrono = std::chrono; SystemConfig::SystemConfig() { + const bool allowSystemConfigOverrides = Theme::instance()->allowSystemConfigOverrides(); auto format = Utility::isWindows() ? QSettings::NativeFormat : QSettings::IniFormat; QSettings system(configPath(QOperatingSystemVersion::currentType(), *Theme::instance()), format); - _allowServerURLChange = system.value(SetupAllowServerUrlChangeKey, true).toBool(); - _serverUrl = system.value(SetupServerUrlKey, QString()).toString(); + if (allowSystemConfigOverrides && system.contains(SetupServerUrlKey)) { + _serverUrl = system.value(SetupServerUrlKey).toString(); + } else { + _serverUrl = Theme::instance()->overrideServerUrlV2(); + } + if (allowSystemConfigOverrides && system.contains(SetupAllowServerUrlChangeKey)) { + _allowServerURLChange = system.value(SetupAllowServerUrlChangeKey).toBool(); + } else { + // If a theme provides a hardcoded URL, do not allow for URL change. + _allowServerURLChange = Theme::instance()->overrideServerUrlV2().isEmpty(); + } _skipUpdateCheck = system.value(UpdaterSkipUpdateCheckKey, false).toBool(); - // read OpenID Connect configuration - auto clientId = system.value(OidcClientIdKey, QString()).toString(); - auto clientSecret = system.value(OidcClientSecretKey, QString()).toString(); - - QVariant portsVar = system.value(OidcPortsKey, "0").toString(); - QList ports; - const auto parts = portsVar.toString().split(QLatin1Char(','), Qt::SkipEmptyParts); - for (const QString &p : parts) { - bool ok = false; - const quint16 val = static_cast(p.trimmed().toUInt(&ok)); - if (ok) { - ports.append(val); + loadOpenIdConfig(system); +} + +void SystemConfig::loadOpenIdConfig(const QSettings &system) +{ + QString clientId; + QString clientSecret; + QVector ports; + QString scopes; + QString prompt; + auto theme = Theme::instance(); + + if (theme->allowSystemConfigOverrides()) { + if (system.contains(OidcClientIdKey) || system.contains(OidcClientSecretKey) || system.contains(OidcPortsKey) || system.contains(OidcScopesKey) + || system.contains(OidcPromptKey)) { + // Load *all* settings from the system config. + // When done, check if the config is valid. If it is not valid, fall back to the theme. + clientId = system.value(OidcClientIdKey).toString(); + clientSecret = system.value(OidcClientSecretKey, QString()).toString(); + + if (system.contains(OidcPortsKey)) { + QVariant portsVar = system.value(OidcPortsKey).toString(); + const auto parts = portsVar.toString().split(QLatin1Char(','), Qt::SkipEmptyParts); + for (const QString &p : parts) { + bool ok = false; + const quint16 val = static_cast(p.trimmed().toUInt(&ok)); + if (ok) { + ports.append(val); + } + } + } else { + ports.append(0); // 0 means any port + } + + scopes = system.value(OidcScopesKey, QString()).toString(); + prompt = system.value(OidcPortsKey, QString()).toString(); + _openIdConfig = OpenIdConfig(clientId, clientSecret, ports, scopes, prompt); + if (_openIdConfig.isValid()) + return; + else + qCWarning(lcSystemConfig) << "Invalid OpenIDConnect configuration in system config, falling back to defaults"; } } - QString scopes = system.value(OidcScopesKey, QString()).toString(); - QString prompt = system.value(OidcPortsKey, QString()).toString(); - + // Load *all* settings from the theme. + clientId = theme->oauthClientId(); + clientSecret = theme->oauthClientSecret(); + ports = theme->oauthPorts(); + scopes = theme->openIdConnectScopes(); + prompt = theme->openIdConnectPrompt(); _openIdConfig = OpenIdConfig(clientId, clientSecret, ports, scopes, prompt); } @@ -52,37 +96,27 @@ QString SystemConfig::configPath(const QOperatingSystemVersion::OSType& os, cons if (os == QOperatingSystemVersion::Windows) { // We use HKEY_LOCAL_MACHINE\Software\Policies since this is the location where GPO operates. // Note: use of uppercase/camelcase is common. - return QString("HKEY_LOCAL_MACHINE\\Software\\Policies\\%1\\%2").arg(theme.vendor(), theme.appNameGUI()); + return QStringLiteral("HKEY_LOCAL_MACHINE\\Software\\Policies\\%1\\%2").arg(theme.vendor(), theme.appNameGUI()); } if (os == QOperatingSystemVersion::MacOS) { // We use a subfolder to have one common location where in the future more files can be stored (like icons, images and such) // ini is used on macOS in contrary to plist because they are easier to maintain. // Note: rev-domain notation and lowercase is typically used. - return QString("/Library/Preferences/%1/%2.ini").arg(theme.orgDomainName(), theme.appName()); + return QStringLiteral("/Library/Preferences/%1/%2.ini").arg(theme.orgDomainName(), theme.appName()); } // On Unix style systems, the application name in lowercase is typically used. - return QString("/etc/%1/%1.ini").arg(theme.appName()); + return QStringLiteral("/etc/%1/%1.ini").arg(theme.appName()); } bool SystemConfig::allowServerUrlChange() const { - // If a theme provides a hardcoded URL, do not allow for URL change. - QString overrideServerUrl = Theme::instance()->overrideServerUrlV2(); - if (!overrideServerUrl.isEmpty()) - return false; return _allowServerURLChange; } QString SystemConfig::serverUrl() const { - // a theme can provide a hardcoded url which is not subject of change by definition - auto serverUrl = Theme::instance()->overrideServerUrlV2(); - if (!serverUrl.isEmpty()) { - return serverUrl; - } - return _serverUrl; } @@ -93,20 +127,7 @@ bool SystemConfig::skipUpdateCheck() const OpenIdConfig SystemConfig::openIdConfig() const { - // system config has precedence here - if (!_openIdConfig.clientId().isEmpty()) { - return _openIdConfig; - } - - // load config from theme - QString clientId = Theme::instance()->oauthClientId(); - QString clientSecret = Theme::instance()->oauthClientSecret(); - - const auto ports = Theme::instance()->oauthPorts(); - QString scopes = Theme::instance()->openIdConnectScopes(); - QString prompt = Theme::instance()->openIdConnectPrompt(); - - return OpenIdConfig(clientId, clientSecret, ports, scopes, prompt); - -} + return _openIdConfig; } + +} // OCC namespace diff --git a/src/libsync/config/systemconfig.h b/src/libsync/config/systemconfig.h index f1b5ea20106..6750befeae8 100644 --- a/src/libsync/config/systemconfig.h +++ b/src/libsync/config/systemconfig.h @@ -103,6 +103,9 @@ class OWNCLOUDSYNC_EXPORT SystemConfig */ static QString configPath(const QOperatingSystemVersion::OSType& os, const Theme& theme); +private: + void loadOpenIdConfig(const QSettings &system); + private: // System settings keys // Setup related keys inline static const QString SetupAllowServerUrlChangeKey = QStringLiteral("Setup/AllowServerUrlChange"); @@ -122,4 +125,5 @@ class OWNCLOUDSYNC_EXPORT SystemConfig bool _skipUpdateCheck; OpenIdConfig _openIdConfig; }; -} + +} // OCC namespace diff --git a/src/libsync/configfile.cpp b/src/libsync/configfile.cpp index cbaa7209afe..dcab035d71c 100644 --- a/src/libsync/configfile.cpp +++ b/src/libsync/configfile.cpp @@ -59,7 +59,6 @@ const QString optionalDesktopNotificationsC() { return QStringLiteral("optionalDesktopNotifications"); } -const QString skipUpdateCheckC() { return QStringLiteral("skipUpdateCheck"); } const QString updateCheckIntervalC() { return QStringLiteral("updateCheckInterval"); } const QString uiLanguageC() { return QStringLiteral("uiLanguage"); } const QString geometryC() { return QStringLiteral("geometry"); } @@ -444,31 +443,6 @@ chrono::milliseconds ConfigFile::updateCheckInterval(const QString &connection) return interval; } -bool ConfigFile::skipUpdateCheck() const -{ - if (_systemConfig.skipUpdateCheck()) { - return true; - } - auto con = defaultConnection(); - - QVariant fallback = getValue(skipUpdateCheckC(), con, false); - QVariant value = getValue(skipUpdateCheckC(), QString(), fallback); - return value.toBool(); -} - -void ConfigFile::setSkipUpdateCheck(bool skip, const QString &connection) -{ - QString con(connection); - if (connection.isEmpty()) - con = defaultConnection(); - - auto settings = makeQSettings(); - settings.beginGroup(con); - - settings.setValue(skipUpdateCheckC(), QVariant(skip)); - settings.sync(); -} - QString ConfigFile::uiLanguage() const { auto settings = makeQSettings(); diff --git a/src/libsync/configfile.h b/src/libsync/configfile.h index e4bd9a93ee3..1ac372530f5 100644 --- a/src/libsync/configfile.h +++ b/src/libsync/configfile.h @@ -15,7 +15,6 @@ #pragma once #include "owncloudlib.h" -#include "config/systemconfig.h" #include #include @@ -145,9 +144,6 @@ class OWNCLOUDSYNC_EXPORT ConfigFile // how often the check about new versions runs std::chrono::milliseconds updateCheckInterval(const QString &connection = QString()) const; - bool skipUpdateCheck() const; - void setSkipUpdateCheck(bool, const QString &); - QString uiLanguage() const; void setUiLanguage(const QString &uiLanguage); @@ -176,7 +172,6 @@ class OWNCLOUDSYNC_EXPORT ConfigFile void setValue(const QString &key, const QVariant &value); private: - SystemConfig _systemConfig; typedef QSharedPointer SharedCreds; static QString _oCVersion; diff --git a/src/libsync/owncloudtheme.cpp b/src/libsync/owncloudtheme.cpp index 55e4ff906c9..b71f44a43fe 100644 --- a/src/libsync/owncloudtheme.cpp +++ b/src/libsync/owncloudtheme.cpp @@ -51,4 +51,9 @@ bool ownCloudTheme::moveToTrashDefaultValue() const // for the vanilla ownCloud client move-to-trash option is enabled by default return true; } + +bool ownCloudTheme::allowSystemConfigOverrides() const +{ + return true; +} } diff --git a/src/libsync/owncloudtheme.h b/src/libsync/owncloudtheme.h index 82f6e3a6591..801e5c9afbe 100644 --- a/src/libsync/owncloudtheme.h +++ b/src/libsync/owncloudtheme.h @@ -32,5 +32,6 @@ class ownCloudTheme : public Theme QIcon wizardHeaderLogo() const override; QIcon aboutIcon() const override; bool moveToTrashDefaultValue() const override; + bool allowSystemConfigOverrides() const override; }; } diff --git a/src/libsync/theme.cpp b/src/libsync/theme.cpp index 8b81c79c090..396a11216f5 100644 --- a/src/libsync/theme.cpp +++ b/src/libsync/theme.cpp @@ -562,6 +562,11 @@ bool Theme::moveToTrashDefaultValue() const return false; } +bool Theme::allowSystemConfigOverrides() const +{ + return false; +} + bool Theme::syncNewlyDiscoveredSpaces() const { return false; diff --git a/src/libsync/theme.h b/src/libsync/theme.h index 12fb7ff628d..429704da74d 100644 --- a/src/libsync/theme.h +++ b/src/libsync/theme.h @@ -481,6 +481,12 @@ class OWNCLOUDSYNC_EXPORT Theme : public QObject */ virtual bool moveToTrashDefaultValue() const; + /** + * @brief Allow the system configuration to override theme values. + * @default false + */ + virtual bool allowSystemConfigOverrides() const; + /** * @brief Automatically add sync connections for newly discovered Spaces. * @@ -529,4 +535,4 @@ class OWNCLOUDSYNC_EXPORT Theme : public QObject bool _mono = false; }; -} \ No newline at end of file +} From 651c724ac1655893e75e0e08907f46ccdb8a54e5 Mon Sep 17 00:00:00 2001 From: Erik Verbruggen Date: Thu, 26 Feb 2026 17:48:49 +0100 Subject: [PATCH 14/17] Fix system settings loading --- src/libsync/config/systemconfig.cpp | 108 ++++++++++++++-------------- src/libsync/config/systemconfig.h | 3 +- 2 files changed, 55 insertions(+), 56 deletions(-) diff --git a/src/libsync/config/systemconfig.cpp b/src/libsync/config/systemconfig.cpp index 2b78affbbbf..8dca9e4411d 100644 --- a/src/libsync/config/systemconfig.cpp +++ b/src/libsync/config/systemconfig.cpp @@ -19,74 +19,72 @@ namespace chrono = std::chrono; SystemConfig::SystemConfig() { - const bool allowSystemConfigOverrides = Theme::instance()->allowSystemConfigOverrides(); + _serverUrl = Theme::instance()->overrideServerUrlV2(); + // If a theme provides a hardcoded URL, do not allow for URL change. + _allowServerURLChange = Theme::instance()->overrideServerUrlV2().isEmpty(); + _skipUpdateCheck = false; + _openIdConfig = loadOpenIdConfigFromTheme(); + + if (!Theme::instance()->allowSystemConfigOverrides()) + return; + + // Load all overrides + auto format = Utility::isWindows() ? QSettings::NativeFormat : QSettings::IniFormat; - QSettings system(configPath(QOperatingSystemVersion::currentType(), *Theme::instance()), format); + const QSettings system(configPath(QOperatingSystemVersion::currentType(), *Theme::instance()), format); - if (allowSystemConfigOverrides && system.contains(SetupServerUrlKey)) { - _serverUrl = system.value(SetupServerUrlKey).toString(); - } else { - _serverUrl = Theme::instance()->overrideServerUrlV2(); - } - if (allowSystemConfigOverrides && system.contains(SetupAllowServerUrlChangeKey)) { + _serverUrl = system.value(SetupServerUrlKey, QString()).toString(); + if (system.contains(SetupAllowServerUrlChangeKey)) { _allowServerURLChange = system.value(SetupAllowServerUrlChangeKey).toBool(); - } else { - // If a theme provides a hardcoded URL, do not allow for URL change. - _allowServerURLChange = Theme::instance()->overrideServerUrlV2().isEmpty(); } + _skipUpdateCheck = system.value(UpdaterSkipUpdateCheckKey, false).toBool(); - loadOpenIdConfig(system); + OpenIdConfig systemConfig = loadOpenIdConfigFromSystemConfig(system); + if (systemConfig.isValid()) { + qCInfo(lcSystemConfig()) << "Using OpenID config from system config"; + _openIdConfig = systemConfig; + } +} + +OpenIdConfig SystemConfig::loadOpenIdConfigFromTheme() +{ + Theme *theme = Theme::instance(); + + QString clientId = theme->oauthClientId(); + QString clientSecret = theme->oauthClientSecret(); + QVector ports = theme->oauthPorts(); + QString scopes = theme->openIdConnectScopes(); + QString prompt = theme->openIdConnectPrompt(); + + OpenIdConfig cfg(clientId, clientSecret, ports, scopes, prompt); + OC_ASSERT(cfg.isValid()); + + return cfg; } -void SystemConfig::loadOpenIdConfig(const QSettings &system) +OpenIdConfig SystemConfig::loadOpenIdConfigFromSystemConfig(const QSettings &system) { - QString clientId; - QString clientSecret; + QString clientId = system.value(OidcClientIdKey, QString()).toString(); + QString clientSecret = system.value(OidcClientSecretKey, QString()).toString(); + QString scopes = system.value(OidcScopesKey, QString()).toString(); + QString prompt = system.value(OidcPortsKey, QString()).toString(); + QVector ports; - QString scopes; - QString prompt; - auto theme = Theme::instance(); - - if (theme->allowSystemConfigOverrides()) { - if (system.contains(OidcClientIdKey) || system.contains(OidcClientSecretKey) || system.contains(OidcPortsKey) || system.contains(OidcScopesKey) - || system.contains(OidcPromptKey)) { - // Load *all* settings from the system config. - // When done, check if the config is valid. If it is not valid, fall back to the theme. - clientId = system.value(OidcClientIdKey).toString(); - clientSecret = system.value(OidcClientSecretKey, QString()).toString(); - - if (system.contains(OidcPortsKey)) { - QVariant portsVar = system.value(OidcPortsKey).toString(); - const auto parts = portsVar.toString().split(QLatin1Char(','), Qt::SkipEmptyParts); - for (const QString &p : parts) { - bool ok = false; - const quint16 val = static_cast(p.trimmed().toUInt(&ok)); - if (ok) { - ports.append(val); - } - } - } else { - ports.append(0); // 0 means any port - } - - scopes = system.value(OidcScopesKey, QString()).toString(); - prompt = system.value(OidcPortsKey, QString()).toString(); - _openIdConfig = OpenIdConfig(clientId, clientSecret, ports, scopes, prompt); - if (_openIdConfig.isValid()) - return; - else - qCWarning(lcSystemConfig) << "Invalid OpenIDConnect configuration in system config, falling back to defaults"; + QVariant portsVar = system.value(OidcPortsKey, QString()).toString(); + const auto parts = portsVar.toString().split(QLatin1Char(','), Qt::SkipEmptyParts); + for (const QString &p : parts) { + bool ok = false; + const quint16 val = static_cast(p.trimmed().toUInt(&ok)); + if (ok) { + ports.append(val); } } - // Load *all* settings from the theme. - clientId = theme->oauthClientId(); - clientSecret = theme->oauthClientSecret(); - ports = theme->oauthPorts(); - scopes = theme->openIdConnectScopes(); - prompt = theme->openIdConnectPrompt(); - _openIdConfig = OpenIdConfig(clientId, clientSecret, ports, scopes, prompt); + if (ports.isEmpty()) + ports.append(0); // 0 means any port + + return OpenIdConfig(clientId, clientSecret, ports, scopes, prompt); } QString SystemConfig::configPath(const QOperatingSystemVersion::OSType& os, const Theme& theme) diff --git a/src/libsync/config/systemconfig.h b/src/libsync/config/systemconfig.h index 6750befeae8..0a0c6a82a01 100644 --- a/src/libsync/config/systemconfig.h +++ b/src/libsync/config/systemconfig.h @@ -104,7 +104,8 @@ class OWNCLOUDSYNC_EXPORT SystemConfig static QString configPath(const QOperatingSystemVersion::OSType& os, const Theme& theme); private: - void loadOpenIdConfig(const QSettings &system); + static OpenIdConfig loadOpenIdConfigFromTheme(); + static OpenIdConfig loadOpenIdConfigFromSystemConfig(const QSettings &system); private: // System settings keys // Setup related keys From 48c44230420970924482007beea7c4f3aac21650 Mon Sep 17 00:00:00 2001 From: Erik Verbruggen Date: Thu, 26 Feb 2026 17:49:30 +0100 Subject: [PATCH 15/17] Remove old `SkipUpdateCheck` from user config --- src/gui/aboutdialog.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/gui/aboutdialog.cpp b/src/gui/aboutdialog.cpp index 3416b7e1463..21f0c7b1545 100644 --- a/src/gui/aboutdialog.cpp +++ b/src/gui/aboutdialog.cpp @@ -20,6 +20,7 @@ #include #ifdef WITH_AUTO_UPDATER +#include "config/systemconfig.h" #include "libsync/configfile.h" #include "updater/ocupdater.h" #ifdef Q_OS_MAC @@ -80,7 +81,9 @@ void AboutDialog::setupUpdaterWidget() } } - if (!ConfigFile().skipUpdateCheck() && Updater::instance()) { + ConfigFile().makeQSettings().remove("skipUpdateCheck"); // remove old config key + + if (!SystemConfig().skipUpdateCheck() && Updater::instance()) { // Note: the sparkle-updater is not an OCUpdater if (auto *ocupdater = qobject_cast(Updater::instance())) { auto updateInfo = [ocupdater, this] { From 3f94249d65fb7cb654931e198d954a88f1b302cb Mon Sep 17 00:00:00 2001 From: Erik Verbruggen Date: Thu, 26 Feb 2026 18:34:21 +0100 Subject: [PATCH 16/17] Other fixes --- src/gui/creds/credentials.cpp | 1 - src/libsync/config/systemconfig.cpp | 6 +++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/gui/creds/credentials.cpp b/src/gui/creds/credentials.cpp index f97242deddc..7d107bb0ab4 100644 --- a/src/gui/creds/credentials.cpp +++ b/src/gui/creds/credentials.cpp @@ -16,7 +16,6 @@ #include "accessmanager.h" #include "account.h" #include "config/systemconfig.h" -#include "configfile.h" #include "creds/credentialmanager.h" #include "oauth.h" #include "requestauthenticationcontroller.h" diff --git a/src/libsync/config/systemconfig.cpp b/src/libsync/config/systemconfig.cpp index 8dca9e4411d..f71273299dc 100644 --- a/src/libsync/config/systemconfig.cpp +++ b/src/libsync/config/systemconfig.cpp @@ -94,18 +94,18 @@ QString SystemConfig::configPath(const QOperatingSystemVersion::OSType& os, cons if (os == QOperatingSystemVersion::Windows) { // We use HKEY_LOCAL_MACHINE\Software\Policies since this is the location where GPO operates. // Note: use of uppercase/camelcase is common. - return QStringLiteral("HKEY_LOCAL_MACHINE\\Software\\Policies\\%1\\%2").arg(theme.vendor(), theme.appNameGUI()); + return QString("HKEY_LOCAL_MACHINE\\Software\\Policies\\%1\\%2").arg(theme.vendor(), theme.appNameGUI()); } if (os == QOperatingSystemVersion::MacOS) { // We use a subfolder to have one common location where in the future more files can be stored (like icons, images and such) // ini is used on macOS in contrary to plist because they are easier to maintain. // Note: rev-domain notation and lowercase is typically used. - return QStringLiteral("/Library/Preferences/%1/%2.ini").arg(theme.orgDomainName(), theme.appName()); + return QString("/Library/Preferences/%1/%2.ini").arg(theme.orgDomainName(), theme.appName()); } // On Unix style systems, the application name in lowercase is typically used. - return QStringLiteral("/etc/%1/%1.ini").arg(theme.appName()); + return QString("/etc/%1/%1.ini").arg(theme.appName()); } bool SystemConfig::allowServerUrlChange() const From 1f472d157ada61cd37fadfc411ffa4dd4cc23181 Mon Sep 17 00:00:00 2001 From: Erik Verbruggen Date: Fri, 27 Feb 2026 16:32:43 +0100 Subject: [PATCH 17/17] Fix Linux build --- src/gui/updater/ocupdater.cpp | 6 ++++-- src/gui/updater/ocupdater.h | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/gui/updater/ocupdater.cpp b/src/gui/updater/ocupdater.cpp index a4a9d9ffb8b..5b3c2dedf49 100644 --- a/src/gui/updater/ocupdater.cpp +++ b/src/gui/updater/ocupdater.cpp @@ -16,6 +16,7 @@ #include "application.h" #include "common/utility.h" #include "common/version.h" +#include "config/systemconfig.h" #include "configfile.h" #include "theme.h" @@ -39,6 +40,7 @@ namespace OCC { UpdaterScheduler::UpdaterScheduler(Application *app, QObject *parent) : QObject(parent) + , _skipUpdateCheck(SystemConfig().skipUpdateCheck()) { connect(&_updateCheckTimer, &QTimer::timeout, this, &UpdaterScheduler::slotTimerFired); @@ -85,8 +87,8 @@ void UpdaterScheduler::slotTimerFired() } // consider the skipUpdateCheck flag in the config. - if (cfg.skipUpdateCheck()) { - qCInfo(lcUpdater) << "Skipping update check because of config file"; + if (_skipUpdateCheck) { + qCInfo(lcUpdater) << "Skipping update check because of system config"; return; } diff --git a/src/gui/updater/ocupdater.h b/src/gui/updater/ocupdater.h index 8e8a945ee56..95d9d11945f 100644 --- a/src/gui/updater/ocupdater.h +++ b/src/gui/updater/ocupdater.h @@ -89,6 +89,7 @@ private Q_SLOTS: void slotTimerFired(); private: + const bool skipUpdateCheck; QTimer _updateCheckTimer; /** Timer for the regular update check. */ // make sure we are going to show only one of them at once