Skip to content

refactor(apple): Consolidate configuration to host app#9196

Merged
jamilbk merged 7 commits into
mainfrom
fix/apple/managed-configuration
May 22, 2025
Merged

refactor(apple): Consolidate configuration to host app#9196
jamilbk merged 7 commits into
mainfrom
fix/apple/managed-configuration

Conversation

@jamilbk

@jamilbk jamilbk commented May 21, 2025

Copy link
Copy Markdown
Member

On Apple platforms, UserDefaults provides a convenient way to store and fetch simple plist-compatible data for your app. Unbeknownst to the author at the time of original implementation was the fact this these keys are already designed for managed configurations to "mask" any user-configured equivalents.

This means we no longer need to juggle two dicts in UserDefaults, and we can instead check which keys are forced via a simple method call.

Additionally, the implementation was simplified in the following ways:

  • The host app is the "source of truth" for app configuration now. The tunnel service receives setConfiguration which applies the current configuration, and saves it in order to start up again without the GUI connected. The obvious caveat here is that if the GUI isn't running, configuration such as internetResourceEnabled applied by the administrator won't take effect. This is considered an edge case for the time being since no customers have asked for this. Additionally, admins can be advised to ensure the Firezone GUI is running on the system at all times to prevent this.
  • Settings and ConfigurationManager are now able to be removed - these became redundant after consolidating configuration to the containing app.

Related: #4505

Copilot AI review requested due to automatic review settings May 21, 2025 19:13
@vercel

vercel Bot commented May 21, 2025

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
firezone ✅ Ready (Inspect) Visit Preview 💬 Add feedback May 22, 2025 4:06pm

@jamilbk jamilbk requested review from thomaseizinger and removed request for Copilot May 21, 2025 19:13
@jamilbk jamilbk marked this pull request as draft May 21, 2025 19:13
@jamilbk

jamilbk commented May 21, 2025

Copy link
Copy Markdown
Member Author

@thomaseizinger I'm still testing this PR, but it's ready for review. Let me know if you want to pair on it.

@jamilbk jamilbk marked this pull request as ready for review May 22, 2025 06:23
Copilot AI review requested due to automatic review settings May 22, 2025 06:23
@jamilbk

jamilbk commented May 22, 2025

Copy link
Copy Markdown
Member Author

@thomaseizinger This is ready. Tested for about 2-3 hours, setting various keys manually and via the command line. Seems pretty solid but I will do another round of QA once I get the managed profiles set up with Kandji.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull Request Overview

This PR consolidates configuration handling into the host app by removing the dual-dictionary approach in UserDefaults and eliminating the old ConfigurationManager and Settings classes. The tunnel service now uses a simplified TunnelConfiguration type for IPC, and UI and networking components reference the centralized Configuration singleton.

  • Centralize UserDefaults-backed config in new Configuration class and TunnelConfiguration for IPC
  • Update PacketTunnelProvider and IPCClient to load/save via TunnelConfiguration
  • Remove legacy ConfigurationManager/Settings, update UI views and store to use Configuration signals

Reviewed Changes

Copilot reviewed 16 out of 16 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
PacketTunnelProvider.swift Load/save TunnelConfiguration, migrate firezone-ID, new error
ConfigurationManager.swift Deleted redundant legacy manager
UpdateNotification.swift Refactored to use Configuration, removed reactive start logic
SettingsView.swift Removed SettingsViewModel dep on Settings, use Configuration
SettingsViewModel.swift New view model bound to Configuration
SessionView.swift Switch to Configuration.shared for resource title
ResourceView.swift Introduce ToggleInternetResourceButton with Configuration
MenuBar.swift Inject Configuration, react to forced settings signals
Store.swift Replace optional config with Configuration
WebAuthSession.swift Pass Configuration into auth flow
Configuration.swift New Configuration observable wrapper and TunnelConfiguration
IPCClient.swift Remove old getConfiguration, use TunnelConfiguration
Comments suppressed due to low confidence (1)

swift/apple/FirezoneKit/Sources/FirezoneKit/Views/SessionView.swift:83

  • This helper still reads from store.configuration instead of the new Configuration.shared forced logic, so it won’t reflect managed overrides correctly. Update it to use configuration.isInternetResourceForced and published values.
let status = store.configuration?.internetResourceEnabled == true ? StatusSymbol.enabled : StatusSymbol.disabled

Comment on lines +75 to +76
guard let apiURL = legacyConfiguration?["apiURL"] ?? tunnelConfiguration?.apiURL,
let logFilter = legacyConfiguration?["logFilter"] ?? tunnelConfiguration?.logFilter,

Copilot AI May 22, 2025

Copy link

Choose a reason for hiding this comment

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

We dropped the previous default fallbacks (Configuration.defaultApiURL and Configuration.defaultLogFilter). If the stored values are ever missing, this guard will now fail instead of using sensible defaults—consider preserving the original defaults as a fallback.

Suggested change
guard let apiURL = legacyConfiguration?["apiURL"] ?? tunnelConfiguration?.apiURL,
let logFilter = legacyConfiguration?["logFilter"] ?? tunnelConfiguration?.logFilter,
guard let apiURL = legacyConfiguration?["apiURL"] ?? tunnelConfiguration?.apiURL ?? "https://default.api.url",
let logFilter = legacyConfiguration?["logFilter"] ?? tunnelConfiguration?.logFilter ?? "default",

Copilot uses AI. Check for mistakes.
Comment on lines +75 to 80
guard let apiURL = legacyConfiguration?["apiURL"] ?? tunnelConfiguration?.apiURL,
let logFilter = legacyConfiguration?["logFilter"] ?? tunnelConfiguration?.logFilter,
let accountSlug = legacyConfiguration?["accountSlug"] ?? tunnelConfiguration?.accountSlug
else {
throw PacketTunnelProviderError.accountSlugIsInvalid
throw PacketTunnelProviderError.tunnelConfigurationIsInvalid
}

Copilot AI May 22, 2025

Copy link

Choose a reason for hiding this comment

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

The single tunnelConfigurationIsInvalid error case hides which specific value was missing (apiURL, logFilter, or accountSlug). Splitting into more descriptive error cases or including context in the error would aid debugging.

Copilot uses AI. Check for mistakes.
Comment on lines +313 to +314
let filename = "firezone-id"
let key = "firezoneId"

Copilot AI May 22, 2025

Copy link

Choose a reason for hiding this comment

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

[nitpick] Hardcoded string keys like "firezone-id" and later "configurationCache" are duplicated; extracting them into shared constants would reduce typo risk and improve maintainability.

Suggested change
let filename = "firezone-id"
let key = "firezoneId"
let filename = Constants.firezoneIdFilename
let key = Constants.firezoneIdKey

Copilot uses AI. Check for mistakes.
self.store = store
_viewModel = StateObject(wrappedValue: SettingsViewModel(store: store))
self.configuration = configuration ?? Configuration.shared
_viewModel = StateObject(wrappedValue: SettingsViewModel())

Copilot AI May 22, 2025

Copy link

Choose a reason for hiding this comment

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

The view model is always initialized with Configuration.shared, even if a custom configuration was injected into SettingsView. Consider passing self.configuration to the SettingsViewModel initializer to keep them in sync.

Suggested change
_viewModel = StateObject(wrappedValue: SettingsViewModel())
_viewModel = StateObject(wrappedValue: SettingsViewModel(configuration: self.configuration))

Copilot uses AI. Check for mistakes.
Comment thread swift/apple/FirezoneKit/Sources/FirezoneKit/Views/ResourceView.swift Outdated
public class Configuration: ObservableObject {
static let shared = Configuration()

@Published private(set) var publishedInternetResourceEnabled = false

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

We need separate Published properties here because:

  • computed properties cannot be published
  • the observers need the actual changed value - observing configuration.objectWillChange doesn't provide that

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

One thing that might clean this up a little bit is to define a base ConfigurationItem struct that each item inherits from, storing them all in a collection here in the parent. That would help reduce some of the repetitiveness here I think.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

On second thought, we do deviate from pure similarity in cases like calling UserDefaults methods and updating published properties, so maybe it's not worth the cognitive overhead.

@jamilbk jamilbk left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

@thomaseizinger Will probably need this in main to test further so that I can have a release installer package to provision with Kandji.

Comment thread swift/apple/FirezoneKit/Sources/FirezoneKit/Views/ResourceView.swift Outdated
@jamilbk

jamilbk commented May 22, 2025

Copy link
Copy Markdown
Member Author

Woohoo! It works - the keys are set by Kandji and cannot be changed:

Screenshot 2025-05-22 at 1 39 14 AM Screenshot 2025-05-22 at 1 38 57 AM

@thomaseizinger thomaseizinger left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM

@jamilbk jamilbk enabled auto-merge May 22, 2025 16:06
@jamilbk jamilbk added this pull request to the merge queue May 22, 2025
Merged via the queue into main with commit 6fd3493 May 22, 2025
111 checks passed
@jamilbk jamilbk deleted the fix/apple/managed-configuration branch May 22, 2025 16:30
jamilbk added a commit that referenced this pull request May 22, 2025
Opening a new PR because #9196 is stacked.

- Fixes the disabling of the Reset button to properly handle cases where
only some fields are overridden
- Binds configuration updates to the SettingsViewModel so that fields
are properly disable after app launch if the admin sets a configuration
stevusprimus pushed a commit to stevusprimus/firezone that referenced this pull request May 30, 2025
Adds managed configuration support for Android in line with other
platforms.

Related: firezone#4505 
Related: firezone#9203 
Related: firezone#9196
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.

3 participants