Skip to content

feature: managed settings. - #10589

Draft
camilasan wants to merge 17 commits into
masterfrom
feature/5497/mdm
Draft

feature: managed settings.#10589
camilasan wants to merge 17 commits into
masterfrom
feature/5497/mdm

Conversation

@camilasan

@camilasan camilasan commented Aug 12, 2026

Copy link
Copy Markdown
Member

Resolves

#5497

This PR is branched off #9191.

Managed settings for the desktop client

Adds a managed settings subsystem so an administrator can set or enforce client settings from three inputs, resolved through one precedence hierarchy: device policy (Windows registry, macOS managed preferences), server delivered values (via the support app capability), and the user config.

Design and resolution flow are documented in src/libsync/settings/README.md.

What it does

  • A ManagedSettings resolver that ranks sources by enforcement state and priority, converts the resolved value to the type declared in the schema, and reports the winning source plus whether it is enforced.
  • Device sources: Windows Software\Policies and Software hives, macOS forced preferences via CFPreferencesAppValueIsForced, Linux conf.
  • Server delivery: parses support.desktopClient from the capabilities, sanitizes it against a client allow list, caches the parsed values in a thread safe ManagedConfig service, and persists them for offline use.
  • One read gateway, ConfigFile::getConfig plus the getConfig<T> template, so callers resolve through the hierarchy instead of reading the config directly. setConfig refuses to overwrite an enforced value.
  • UI: enforced controls are disabled and labelled, never hidden. The update check control shows "Managed by your system administrator" or "Managed by your organization" depending on the source.

Security

  • The server may only default the update and proxy keys, never enforce them. Only device policy can enforce those, so a server cannot disable updates or reroute traffic. This holds on both sides as defense in depth: the support app drops those keys from the enforced map, and the client drops them again from what it accepts.
  • Delivered values are filtered against the client allow list before they reach the resolver.

Scope

Update settings (skipUpdateCheck, autoUpdateCheck) are wired end to end. The remaining allow listed keys (virtual files mode, proxy, folder limits) are accepted and cached but not yet consumed by their accessors; that is a tracked follow up.

Tests

Unit tests cover resolution precedence, type conversion, the getConfig gateway, setConfig refusal when enforced, server caching and persistence, and allow list sanitization including the dropped server enforced update and proxy keys.

Server side

Delivery depends on the support app change (separate repo and PR) exposing support.desktopClient in the capabilities, gated on an enterprise subscription.

Checklist

AI (if applicable)

@camilasan camilasan added this to the 35.0.0 milestone Aug 12, 2026
@camilasan camilasan changed the title Feature/5497/mdm feature: managed settings. Aug 12, 2026
Move phase, version and legacy discovery out of ConfigFile, Application,
AccountManager, AccountState and FolderMan into src/libsync/settings.

Assisted-by: Claude Code:claude-opus-4-8
Signed-off-by: Camila Ayres <hello@camilasan.com>
Cover the phase lifecycle, version comparison and legacy discovery.

Assisted-by: Claude Code:claude-opus-4-8
Signed-off-by: Camila Ayres <hello@camilasan.com>
Add MIGRATION.md next to the sources.

Assisted-by: Claude Code:claude-opus-4-8
Signed-off-by: Camila Ayres <hello@camilasan.com>
@camilasan
camilasan force-pushed the feature/5497/mdm branch 3 times, most recently from 6e21f30 to 99cd928 Compare September 7, 2026 13:35
Resolves the effective value of a setting from a set of injectable sources
and returns metadata (winning source, default or locked). Precedence: locked
policy, then user config, then the highest default, then the builtin settings.

Assisted-by: Claude Code:claude-opus-4-8
Signed-off-by: Camila Ayres <hello@camilasan.com>
Native registry, plist and conf adapters plus buildDeviceSources, replicating
the app name selection of ConfigFile::getValue and getPolicySetting. The OS
adapters are not unit tested on the Linux build; only the factory is smoke tested.

Assisted-by: Claude Code:claude-opus-4-8
Signed-off-by: Camila Ayres <hello@camilasan.com>
…ngs resolver

skipUpdateCheck and autoUpdateCheck now go through ManagedSettings with the
device sources and the user config, preserving the precedence policy over user
over OS default over builtin. The user source carries the connection group so a
locked policy still overrides a group scoped user value.

Assisted-by: Claude Code:claude-opus-4-8
Signed-off-by: Camila Ayres <hello@camilasan.com>
Assisted-by: Claude Code:claude-opus-4-8
Signed-off-by: Camila Ayres <hello@camilasan.com>
Add ForcedPreferenceSource, a locked platform policy source that contributes a
value only when an administrator forces the key. MacForcedPreferenceSource
implements it with CFPreferencesAppValueIsForced and CFPreferencesCopyAppValue,
so host and per user managed preferences are honored. buildDeviceSources now
uses it on macOS instead of reading the managed preferences plist as a file.

Assisted-by: Claude Code:claude-opus-4-8
Signed-off-by: Camila Ayres <hello@camilasan.com>
Add ServerManagedSettings, parsing and an allow list sanitizer for the values
the server delivers through the support.desktopClient capability. Only allow
listed keys pass, and only server lockable keys stay locked.
ServerSettingsSource and buildServerSources expose them as ServerDefault
(priority 30) and ServerLocked (priority 100), so device policy still wins.

Assisted-by: Claude Code:claude-opus-4-8
Signed-off-by: Camila Ayres <hello@camilasan.com>
Add the end to end resolution flow above ManagedSettings and the server
delivery flow above ServerManagedSettings, in the same in header style as
Migration.

Assisted-by: Claude Code:claude-opus-4-8
Signed-off-by: Camila Ayres <hello@camilasan.com>
An admin enforces settings, so LockState, enforceable, the ServerLocked source
kind, and the wire and config keys become EnforcementState, enforceable,
ServerEnforced, and enforced. ForcedPreferenceSource keeps Apple's forced term.

Also shorten comments.

Assisted-by: Claude Code:claude-opus-4-8
Signed-off-by: Camila Ayres <hello@camilasan.com>
getConfig resolves a setting name across the enforcement hierarchy and returns
the value with its source and enforcement. getConfigBool/Int/String, setConfig
(refused when the value is enforced), isEnforced and sourceOf build on it.
skipUpdateCheck and autoUpdateCheck now use it; resolveManagedBool is removed.

Also document managed config getConfig gateway design.

Assisted-by: Claude Code:claude-opus-4-8
Signed-off-by: Camila Ayres <hello@camilasan.com>
getConfig parsed the server settings JSON from the config file on every call.
ManagedConfig caches the parsed settings, keyed by config path and guarded by a
read write lock, refreshed on write. ConfigFile serverManagedSettings and
setServerManagedSettings delegate to it. Reads stay live for the user and device
layers.

Assisted-by: Claude Code:claude-opus-4-8
Signed-off-by: Camila Ayres <hello@camilasan.com>
When autoUpdateCheck is enforced by device policy or the server, the checkbox is
disabled and its tooltip names who manages it. Populating it does not write the
value back, and the toggle saves through setConfig, which refuses an enforced
value. skipUpdateCheck still hides the whole updater section.

Assisted-by: Claude Code:claude-opus-4-8
Signed-off-by: Camila Ayres <hello@camilasan.com>
A trusted server can suggest but not enforce skipUpdateCheck, autoUpdateCheck
and the proxy keys; only device policy enforces them, so a server cannot disable
updates or reroute traffic. sanitizeServerManagedSettings drops them from a
server enforced payload. Virtual files and folder limits stay server enforceable.

Assisted-by: Claude Code:claude-opus-4-8
Signed-off-by: Camila Ayres <hello@camilasan.com>
Assisted-by: Claude Code:claude-opus-4-8
Signed-off-by: Camila Ayres <hello@camilasan.com>
Test targets compile with QT_NO_KEYWORDS, so the slots keyword does not
resolve. Use Q_SLOTS.

Assisted-by: Claude Code:claude-opus-4-8
Signed-off-by: Camila Ayres <hello@camilasan.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant