Skip to content

Commit

Permalink
Disallow changes to managed preferences
Browse files Browse the repository at this point in the history
- Define enumerations for preferences that can be managed in an enterprise environment using MDM
- Add methods in AppState to check for managed preferences
- Update Advanced, Download, Experiments and Update preference panes to disable controls
  to modify any of the managed preferences
- Update Xcode category list button to be disabled if preference is managed
  • Loading branch information
abiligiri committed Jun 21, 2024
1 parent 4a4b469 commit 6e64db2
Show file tree
Hide file tree
Showing 11 changed files with 70 additions and 21 deletions.
4 changes: 2 additions & 2 deletions Xcodes/Backend/AppState+Install.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ extension AppState {

// check to see if we should auto install for the user
public func autoInstallIfNeeded() {
guard let storageValue = UserDefaults.standard.object(forKey: "autoInstallation") as? Int, let autoInstallType = AutoInstallationType(rawValue: storageValue) else { return }
guard let storageValue = Current.defaults.get(forKey: "autoInstallation") as? Int, let autoInstallType = AutoInstallationType(rawValue: storageValue) else { return }

if autoInstallType == .none { return }

// get newest xcode version
Expand Down
2 changes: 1 addition & 1 deletion Xcodes/Backend/AppState+Runtimes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ extension AppState {
// sets a proper cookie for runtimes
try await validateADCSession(path: runtime.downloadPath)

let downloader = Downloader(rawValue: UserDefaults.standard.string(forKey: "downloader") ?? "aria2") ?? .aria2
let downloader = Downloader(rawValue: Current.defaults.string(forKey: "downloader") ?? "aria2") ?? .aria2

let url = URL(string: runtime.source)!
let expectedRuntimePath = Path.xcodesApplicationSupport/"\(url.lastPathComponent)"
Expand Down
37 changes: 31 additions & 6 deletions Xcodes/Backend/AppState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,23 @@ import os.log
import DockProgress
import XcodesKit

enum PreferenceKey: String {
case installPath
case localPath
case unxipExperiment
case createSymLinkOnSelect
case onSelectActionType
case showOpenInRosettaOption
case autoInstallation
case SUEnableAutomaticChecks
case includePrereleaseVersions
case downloader
case dataSource
case xcodeListCategory

func isManaged() -> Bool { UserDefaults.standard.objectIsForced(forKey: self.rawValue) }
}

class AppState: ObservableObject {
private let client = AppleAPI.Client()
internal let runtimeService = RuntimeService()
Expand Down Expand Up @@ -66,26 +83,32 @@ class AppState: ObservableObject {
}
}

var disableLocalPathChange: Bool { PreferenceKey.localPath.isManaged() }

@Published var installPath = "" {
didSet {
Current.defaults.set(installPath, forKey: "installPath")
}
}


var disableInstallPathChange: Bool { PreferenceKey.installPath.isManaged() }

@Published var unxipExperiment = false {
didSet {
Current.defaults.set(unxipExperiment, forKey: "unxipExperiment")
}
}

var disableUnxipExperiment: Bool { PreferenceKey.unxipExperiment.isManaged() }

@Published var createSymLinkOnSelect = false {
didSet {
Current.defaults.set(createSymLinkOnSelect, forKey: "createSymLinkOnSelect")
}
}

var createSymLinkOnSelectDisabled: Bool {
return onSelectActionType == .rename
return onSelectActionType == .rename || PreferenceKey.createSymLinkOnSelect.isManaged()
}

@Published var onSelectActionType = SelectedActionType.none {
Expand All @@ -98,6 +121,8 @@ class AppState: ObservableObject {
}
}

var onSelectActionTypeDisabled: Bool { PreferenceKey.onSelectActionType.isManaged() }

@Published var showOpenInRosettaOption = false {
didSet {
Current.defaults.set(showOpenInRosettaOption, forKey: "showOpenInRosettaOption")
Expand Down Expand Up @@ -178,8 +203,8 @@ class AppState: ObservableObject {
// MARK: Timer
/// Runs a timer every 6 hours when app is open to check if it needs to auto install any xcodes
func setupAutoInstallTimer() {
guard let storageValue = UserDefaults.standard.object(forKey: "autoInstallation") as? Int, let autoInstallType = AutoInstallationType(rawValue: storageValue) else { return }
guard let storageValue = Current.defaults.get(forKey: "autoInstallation") as? Int, let autoInstallType = AutoInstallationType(rawValue: storageValue) else { return }

if autoInstallType == .none { return }

autoInstallTimer = Timer.scheduledTimer(withTimeInterval: 60*60*6, repeats: true) { [weak self] _ in
Expand Down Expand Up @@ -479,7 +504,7 @@ class AppState: ObservableObject {
.mapError { $0 as Error }
}
.flatMap { [unowned self] in
self.install(.version(availableXcode), downloader: Downloader(rawValue: UserDefaults.standard.string(forKey: "downloader") ?? "aria2") ?? .aria2)
self.install(.version(availableXcode), downloader: Downloader(rawValue: Current.defaults.string(forKey: "downloader") ?? "aria2") ?? .aria2)
}
.receive(on: DispatchQueue.main)
.sink(
Expand All @@ -505,7 +530,7 @@ class AppState: ObservableObject {
func installWithoutLogin(id: Xcode.ID) {
guard let availableXcode = availableXcodes.first(where: { $0.version == id }) else { return }

installationPublishers[id] = self.install(.version(availableXcode), downloader: Downloader(rawValue: UserDefaults.standard.string(forKey: "downloader") ?? "aria2") ?? .aria2)
installationPublishers[id] = self.install(.version(availableXcode), downloader: Downloader(rawValue: Current.defaults.string(forKey: "downloader") ?? "aria2") ?? .aria2)
.receive(on: DispatchQueue.main)
.sink(
receiveCompletion: { [unowned self] completion in
Expand Down
2 changes: 2 additions & 0 deletions Xcodes/Backend/DataSource.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,6 @@ public enum DataSource: String, CaseIterable, Identifiable, CustomStringConverti
case .xcodeReleases: return "Xcode Releases"
}
}

var isManaged: Bool { PreferenceKey.dataSource.isManaged() }
}
2 changes: 2 additions & 0 deletions Xcodes/Backend/Downloader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,6 @@ public enum Downloader: String, CaseIterable, Identifiable, CustomStringConverti
case .aria2: return "aria2"
}
}

var isManaged: Bool { PreferenceKey.downloader.isManaged() }
}
5 changes: 4 additions & 1 deletion Xcodes/Frontend/Preferences/AdvancedPreferencePane.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ struct AdvancedPreferencePane: View {
self.appState.installPath = path.string
}
}
.disabled(appState.disableInstallPathChange)
Text("InstallPathDescription")
.font(.footnote)
.foregroundStyle(.secondary)
Expand Down Expand Up @@ -72,6 +73,7 @@ struct AdvancedPreferencePane: View {
self.appState.localPath = path.string
}
}
.disabled(appState.disableLocalPathChange)
Text("LocalCachePathDescription")
.font(.footnote)
.foregroundStyle(.secondary)
Expand All @@ -93,7 +95,8 @@ struct AdvancedPreferencePane: View {
}
.labelsHidden()
.pickerStyle(.inline)

.disabled(appState.onSelectActionTypeDisabled)

Text(appState.onSelectActionType.detailedDescription)
.font(.footnote)
.foregroundStyle(.secondary)
Expand Down
8 changes: 5 additions & 3 deletions Xcodes/Frontend/Preferences/DownloadPreferencePane.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,16 @@ struct DownloadPreferencePane: View {
}
.labelsHidden()
.fixedSize()

Text("DataSourceDescription")
.font(.footnote)
.foregroundStyle(.secondary)
.fixedSize(horizontal: false, vertical: true)
}
}
.groupBoxStyle(PreferencesGroupBoxStyle())

.disabled(dataSource.isManaged)

GroupBox(label: Text("Downloader")) {
VStack(alignment: .leading) {
Picker("Downloader", selection: $downloader) {
Expand All @@ -38,14 +39,15 @@ struct DownloadPreferencePane: View {
}
.labelsHidden()
.fixedSize()

Text("DownloaderDescription")
.font(.footnote)
.foregroundStyle(.secondary)
.fixedSize(horizontal: false, vertical: true)
}
}
.groupBoxStyle(PreferencesGroupBoxStyle())
.disabled(downloader.isManaged)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ struct ExperimentsPreferencePane: View {
"UseUnxipExperiment",
isOn: $appState.unxipExperiment
)
.disabled(appState.disableUnxipExperiment)
Text("FasterUnxipDescription")
.font(.footnote)
.foregroundStyle(.secondary)
Expand Down
27 changes: 19 additions & 8 deletions Xcodes/Frontend/Preferences/UpdatesPreferencePane.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@ struct UpdatesPreferencePane: View {
"AutomaticInstallNewVersion",
isOn: $autoInstallationType.isAutoInstalling
)

.disabled(updater.disableAutoInstallNewVersions)

Toggle(
"IncludePreRelease",
isOn: $autoInstallationType.isAutoInstallingBeta
)
.disabled(updater.disableIncludePrereleaseVersions)
}
.fixedSize(horizontal: false, vertical: true)
}
Expand All @@ -34,17 +36,20 @@ struct UpdatesPreferencePane: View {
isOn: $updater.automaticallyChecksForUpdates
)
.fixedSize(horizontal: true, vertical: false)

.disabled(updater.disableAutoUpdateXcodesApp)

Toggle(
"IncludePreRelease",
isOn: $updater.includePrereleaseVersions
)

.disabled(updater.disableAutoUpdateXcodesAppPrereleaseVersions)

Button("CheckNow") {
updater.checkForUpdates()
}
.padding(.top)

.disabled(updater.disableAutoUpdateXcodesApp)

Text(String(format: localizeString("LastChecked"), lastUpdatedString))
.font(.footnote)
.foregroundStyle(.secondary)
Expand Down Expand Up @@ -83,12 +88,18 @@ class ObservableUpdater: ObservableObject {
private var lastUpdateCheckDateObservation: NSKeyValueObservation?
@Published var includePrereleaseVersions = false {
didSet {
UserDefaults.standard.setValue(includePrereleaseVersions, forKey: "includePrereleaseVersions")
Current.defaults.set(includePrereleaseVersions, forKey: "includePrereleaseVersions")

updaterDelegate.includePrereleaseVersions = includePrereleaseVersions
}
}


var disableAutoInstallNewVersions: Bool { PreferenceKey.autoInstallation.isManaged() }
var disableIncludePrereleaseVersions: Bool { PreferenceKey.autoInstallation.isManaged() }

var disableAutoUpdateXcodesApp: Bool { PreferenceKey.SUEnableAutomaticChecks.isManaged() }
var disableAutoUpdateXcodesAppPrereleaseVersions: Bool { PreferenceKey.includePrereleaseVersions.isManaged() }

init() {
updater = SPUStandardUpdaterController(startingUpdater: true, updaterDelegate: updaterDelegate, userDriverDelegate: nil).updater

Expand All @@ -111,7 +122,7 @@ class ObservableUpdater: ObservableObject {
self.lastUpdateCheckDate = updater.lastUpdateCheckDate
}
)
includePrereleaseVersions = UserDefaults.standard.bool(forKey: "includePrereleaseVersions")
includePrereleaseVersions = Current.defaults.bool(forKey: "includePrereleaseVersions") ?? false
}

func checkForUpdates() {
Expand Down
1 change: 1 addition & 0 deletions Xcodes/Frontend/XcodeList/MainToolbar.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ struct MainToolbarModifier: ViewModifier {
}
}
.help("FilterAvailableDescription")
.disabled(category.isManaged)

Button(action: {
isInstalledOnly.toggle()
Expand Down
2 changes: 2 additions & 0 deletions Xcodes/Frontend/XcodeList/XcodeListCategory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,6 @@ enum XcodeListCategory: String, CaseIterable, Identifiable, CustomStringConverti
case .beta: return localizeString("Beta")
}
}

var isManaged: Bool { PreferenceKey.xcodeListCategory.isManaged() }
}

0 comments on commit 6e64db2

Please sign in to comment.