From 8f22710c878370211e5ca6d7f16f36bff17f85b5 Mon Sep 17 00:00:00 2001 From: Tony Eichelberger Date: Thu, 29 Jan 2026 16:32:23 -0600 Subject: [PATCH 01/53] AI generated removal of the big sur compatibility logic. Needs vetted and tested --- PPPC UtilityTests/ModelTests/ModelTests.swift | 197 +----------------- Resources/Base.lproj/Main.storyboard | 21 +- Source/Model/Model.swift | 52 +---- .../TCCProfileViewController.swift | 59 ------ 4 files changed, 12 insertions(+), 317 deletions(-) diff --git a/PPPC UtilityTests/ModelTests/ModelTests.swift b/PPPC UtilityTests/ModelTests/ModelTests.swift index a07cb54..85ea06e 100644 --- a/PPPC UtilityTests/ModelTests/ModelTests.swift +++ b/PPPC UtilityTests/ModelTests/ModelTests.swift @@ -130,7 +130,6 @@ class ModelTests: XCTestCase { func testExportProfileWithAppleEventsAndAuthorization() { // given - model.usingLegacyAllowKey = false let exe1 = Executable(identifier: "one", codeRequirement: "oneReq") let exe2 = Executable(identifier: "two", codeRequirement: "twoReq") @@ -185,65 +184,6 @@ class ModelTests: XCTestCase { } } - func testExportProfileWithAppleEventsAndLegacyAllowed() { - // given - let exe1 = Executable(identifier: "one", codeRequirement: "oneReq") - let exe2 = Executable(identifier: "two", codeRequirement: "twoReq") - - exe1.appleEvents = [AppleEventRule(source: exe1, destination: exe2, value: true)] - exe2.policy.SystemPolicyAllFiles = "Allow" - - model.selectedExecutables = [exe1, exe2] - model.usingLegacyAllowKey = true - - // when - let profile = model.exportProfile(organization: "Org", identifier: "ID", displayName: "Name", payloadDescription: "Desc") - - // then check top level settings - XCTAssertEqual("Org", profile.organization) - XCTAssertEqual("ID", profile.identifier) - XCTAssertEqual("Name", profile.displayName) - XCTAssertEqual("Desc", profile.payloadDescription) - XCTAssertEqual("System", profile.scope) - XCTAssertEqual("Configuration", profile.type) - XCTAssertNotNil(profile.uuid) - XCTAssertEqual(1, profile.version) - - // then check policy settings - // then verify the payload content top level - XCTAssertEqual(1, profile.content.count) - profile.content.forEach { content in - XCTAssertNotNil(content.uuid) - XCTAssertEqual(1, content.version) - - // then verify the services - XCTAssertEqual(2, content.services.count) - let appleEvents = content.services["AppleEvents"] - XCTAssertNotNil(appleEvents) - let appleEventsPolicy = appleEvents?.first - XCTAssertEqual("one", appleEventsPolicy?.identifier) - XCTAssertEqual("oneReq", appleEventsPolicy?.codeRequirement) - XCTAssertEqual("bundleID", appleEventsPolicy?.identifierType) - XCTAssertEqual("two", appleEventsPolicy?.receiverIdentifier) - XCTAssertEqual("twoReq", appleEventsPolicy?.receiverCodeRequirement) - XCTAssertEqual("bundleID", appleEventsPolicy?.receiverIdentifierType) - XCTAssertTrue(appleEventsPolicy?.allowed == true) - XCTAssertNil(appleEventsPolicy?.authorization) - - let allFiles = content.services["SystemPolicyAllFiles"] - XCTAssertNotNil(allFiles) - let allFilesPolicy = allFiles?.first - XCTAssertEqual("two", allFilesPolicy?.identifier) - XCTAssertEqual("twoReq", allFilesPolicy?.codeRequirement) - XCTAssertEqual("bundleID", allFilesPolicy?.identifierType) - XCTAssertNil(allFilesPolicy?.receiverIdentifier) - XCTAssertNil(allFilesPolicy?.receiverCodeRequirement) - XCTAssertNil(allFilesPolicy?.receiverIdentifierType) - XCTAssertTrue(allFilesPolicy?.allowed == true) - XCTAssertNil(allFilesPolicy?.authorization) - } - } - // MARK: - tests for importProfile func testImportProfileUsingAuthorizationKeyAllow() { @@ -330,11 +270,10 @@ class ModelTests: XCTestCase { XCTAssertEqual("Deny", model.selectedExecutables.first?.policy.SystemPolicyAllFiles) } - // MARK: - tests for profileToString + // MARK: - tests for policyFromString - func testPolicyWhenUsingAllowAndAuthorizationKey() { + func testPolicyWhenUsingAllow() { // given - model.usingLegacyAllowKey = false let app = Executable(identifier: "id", codeRequirement: "req") // when @@ -347,7 +286,6 @@ class ModelTests: XCTestCase { func testPolicyWhenUsingDeny() { // given - model.usingLegacyAllowKey = false let app = Executable(identifier: "id", codeRequirement: "req") // when @@ -360,7 +298,6 @@ class ModelTests: XCTestCase { func testPolicyWhenUsingAllowForStandardUsers() { // given - model.usingLegacyAllowKey = false let app = Executable(identifier: "id", codeRequirement: "req") // when @@ -382,134 +319,4 @@ class ModelTests: XCTestCase { XCTAssertNil(policy, "should have not created the policy with an unknown value") } - func testPolicyWhenUsingLegacyDeny() { - // given - let app = Executable(identifier: "id", codeRequirement: "req") - model.usingLegacyAllowKey = true - - // when - let policy = model.policyFromString(executable: app, value: "Deny") - - // then - XCTAssertNil(policy?.authorization, "should not set authorization when in legacy mode") - XCTAssertEqual(policy?.allowed, false) - } - - func testPolicyWhenUsingLegacyAllow() { - // given - let app = Executable(identifier: "id", codeRequirement: "req") - model.usingLegacyAllowKey = true - - // when - let policy = model.policyFromString(executable: app, value: "Allow") - - // then - XCTAssertNil(policy?.authorization, "should not set authorization when in legacy mode") - XCTAssertEqual(policy?.allowed, true) - } - - // test for the unrecognized strings for both legacy and normal - func testPolicyWhenUsingLegacyAllowButNonLegacyValueUsed() { - // given - let app = Executable(identifier: "id", codeRequirement: "req") - model.usingLegacyAllowKey = true - - // when - let policy = model.policyFromString(executable: app, value: "Let Standard Users Approve") - - // then - XCTAssertNil(policy, "should have errored out because of an invalid value") - } - - // MARK: - tests for requiresAuthorizationKey - - func testWhenServiceIsUsingAllowStandarUsersToApprove() { - // given - let profile = TCCProfileBuilder().buildProfile(authorization: .allowStandardUserToSetSystemService) - - // when - model.importProfile(tccProfile: profile) - - // then - XCTAssertTrue(model.requiresAuthorizationKey()) - } - - func testWhenServiceIsUsingOnlyAllowKey() { - // given - let profile = TCCProfileBuilder().buildProfile(authorization: .allow) - - // when - model.importProfile(tccProfile: profile) - - // then - XCTAssertFalse(model.requiresAuthorizationKey()) - } - - func testWhenServiceIsUsingOnlyDenyKey() { - // given - let profile = TCCProfileBuilder().buildProfile(authorization: .deny) - - // when - model.importProfile(tccProfile: profile) - - // then - XCTAssertFalse(model.requiresAuthorizationKey()) - } - - // MARK: - tests for changeToUseLegacyAllowKey - - func testChangingFromAuthorizationKeyToLegacyAllowKey() { - // given - let allowStandard = TCCProfileDisplayValue.allowStandardUsersToApprove.rawValue - let exeSettings = ["AddressBook": "Allow", "ListenEvent": allowStandard, "ScreenCapture": allowStandard] - let model = ModelBuilder().addExecutable(settings: exeSettings).build() - model.usingLegacyAllowKey = false - - // when - model.changeToUseLegacyAllowKey() - - // then - XCTAssertEqual(1, model.selectedExecutables.count, "should have only one exe") - let policy = model.selectedExecutables.first?.policy - XCTAssertEqual("Allow", policy?.AddressBook) - XCTAssertEqual("-", policy?.Camera) - XCTAssertEqual("-", policy?.ListenEvent) - XCTAssertEqual("-", policy?.ScreenCapture) - XCTAssertTrue(model.usingLegacyAllowKey) - } - - func testChangingFromAuthorizationKeyToLegacyAllowKeyWithMoreComplexVaues() { - // given - let allowStandard = TCCProfileDisplayValue.allowStandardUsersToApprove.rawValue - let p1Settings = ["SystemPolicyAllFiles": "Allow", - "ListenEvent": allowStandard, - "ScreenCapture": "Deny", - "Camera": "Deny"] - - let p2Settings = ["SystemPolicyAllFiles": "Deny", - "ScreenCapture": allowStandard, - "Calendar": "Allow"] - let builder = ModelBuilder().addExecutable(settings: p1Settings) - model = builder.addExecutable(settings: p2Settings).build() - model.usingLegacyAllowKey = false - - // when - model.changeToUseLegacyAllowKey() - - // then - XCTAssertEqual(2, model.selectedExecutables.count, "should have only one exe") - let policy1 = model.selectedExecutables[0].policy - XCTAssertEqual("Allow", policy1.SystemPolicyAllFiles) - XCTAssertEqual("-", policy1.ListenEvent) - XCTAssertEqual("Deny", policy1.ScreenCapture) - XCTAssertEqual("Deny", policy1.Camera) - - let policy2 = model.selectedExecutables[1].policy - XCTAssertEqual("Deny", policy2.SystemPolicyAllFiles) - XCTAssertEqual("-", policy2.ListenEvent) - XCTAssertEqual("-", policy2.ScreenCapture) - XCTAssertEqual("Allow", policy2.Calendar) - XCTAssertTrue(model.usingLegacyAllowKey) - } - } diff --git a/Resources/Base.lproj/Main.storyboard b/Resources/Base.lproj/Main.storyboard index 76e1e55..bd1a5b6 100644 --- a/Resources/Base.lproj/Main.storyboard +++ b/Resources/Base.lproj/Main.storyboard @@ -2397,20 +2397,6 @@ - - - - - - - - - - - - - - - + @@ -2275,7 +2275,7 @@ - + @@ -2327,7 +2327,7 @@ - - + + @@ -2398,7 +2398,7 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -2014,6 +2188,15 @@ + + + + + + + + + @@ -2036,6 +2219,9 @@ + + + @@ -2058,6 +2244,9 @@ + + + @@ -2541,6 +2730,18 @@ + + + + + + + + + + + + @@ -2591,6 +2792,9 @@ + + + diff --git a/Resources/PPPCServices.json b/Resources/PPPCServices.json index 72d6410..dd1f938 100644 --- a/Resources/PPPCServices.json +++ b/Resources/PPPCServices.json @@ -4,6 +4,11 @@ "englishName": "Accessibility", "englishDescription": "Allows specified apps to control the Mac via Accessibility APIs." }, + { + "mdmKey": "BluetoothAlways", + "englishName": "Bluetooth Always", + "englishDescription": "Specifies the policies for the app to access Bluetooth devices." + }, { "mdmKey": "AppleEvents", "englishName": "AppleEvents", @@ -181,6 +186,16 @@ "com.apple.security.files.user-selected.read-write" ] }, + { + "mdmKey": "SystemPolicyAppBundles", + "englishName": "App Bundles", + "englishDescription": "Allows the app to update or delete other apps." + }, + { + "mdmKey": "SystemPolicyAppData", + "englishName": "App Data", + "englishDescription": "Specifies the policies for the app to access the data of other apps." + }, { "mdmKey": "SystemPolicySysAdminFiles", "englishName": "Administrator Files", diff --git a/Resources/TestTCCUnsignedProfile.mobileconfig b/Resources/TestTCCUnsignedProfile.mobileconfig index 9092b0c..b1983e2 100644 --- a/Resources/TestTCCUnsignedProfile.mobileconfig +++ b/Resources/TestTCCUnsignedProfile.mobileconfig @@ -48,7 +48,34 @@ bundleID - AddressBook + BluetoothAlways + + +Allowed + +CodeRequirement +identifier "io.brackets.appshell" and anchor apple generic and certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ and certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */ and certificate leaf[subject.OU] = JQ525L2MZD +Comment + +Identifier +io.brackets.appshell +IdentifierType +bundleID + + +Allowed + +CodeRequirement +identifier "com.jamfsoftware.JamfAdmin" and anchor apple generic and certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ and certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */ and certificate leaf[subject.OU] = "483DWKW443" +Comment + +Identifier +com.jamfsoftware.JamfAdmin +IdentifierType +bundleID + + + AddressBook Allowed @@ -450,7 +477,61 @@ bundleID - SystemPolicyDesktopFolder + SystemPolicyAppBundles + + +Allowed + +CodeRequirement +identifier "io.brackets.appshell" and anchor apple generic and certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ and certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */ and certificate leaf[subject.OU] = JQ525L2MZD +Comment + +Identifier +io.brackets.appshell +IdentifierType +bundleID + + +Allowed + +CodeRequirement +identifier "com.jamfsoftware.JamfAdmin" and anchor apple generic and certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ and certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */ and certificate leaf[subject.OU] = "483DWKW443" +Comment + +Identifier +com.jamfsoftware.JamfAdmin +IdentifierType +bundleID + + +SystemPolicyAppData + + +Allowed + +CodeRequirement +identifier "io.brackets.appshell" and anchor apple generic and certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ and certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */ and certificate leaf[subject.OU] = JQ525L2MZD +Comment + +Identifier +io.brackets.appshell +IdentifierType +bundleID + + +Allowed + +CodeRequirement +identifier "com.jamfsoftware.JamfAdmin" and anchor apple generic and certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ and certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */ and certificate leaf[subject.OU] = "483DWKW443" +Comment + +Identifier +com.jamfsoftware.JamfAdmin +IdentifierType +bundleID + + + SystemPolicyDesktopFolder Allowed diff --git a/Source/Model/Executable.swift b/Source/Model/Executable.swift index 9e4cc25..aaaab3e 100644 --- a/Source/Model/Executable.swift +++ b/Source/Model/Executable.swift @@ -76,6 +76,7 @@ class Executable: NSObject { class Policy: NSObject { @objc dynamic var AddressBook: String = "-" + @objc dynamic var BluetoothAlways: String = "-" @objc dynamic var Calendar: String = "-" @objc dynamic var Reminders: String = "-" @objc dynamic var Photos: String = "-" @@ -84,6 +85,8 @@ class Policy: NSObject { @objc dynamic var Accessibility: String = "-" @objc dynamic var PostEvent: String = "-" @objc dynamic var SystemPolicyAllFiles: String = "-" + @objc dynamic var SystemPolicyAppBundles: String = "-" + @objc dynamic var SystemPolicyAppData: String = "-" @objc dynamic var SystemPolicySysAdminFiles: String = "-" @objc dynamic var FileProviderPresence: String = "-" @objc dynamic var ListenEvent: String = "-" diff --git a/Source/Model/TCCProfile.swift b/Source/Model/TCCProfile.swift index 2c01a38..b161f40 100644 --- a/Source/Model/TCCProfile.swift +++ b/Source/Model/TCCProfile.swift @@ -194,25 +194,28 @@ public struct TCCProfile: Codable { } enum ServicesKeys: String { + case accessibility = "Accessibility" case addressBook = "AddressBook" + case adminFiles = "SystemPolicySysAdminFiles" + case allFiles = "SystemPolicyAllFiles" + case appBundles = "SystemPolicyAppBundles" + case appData = "SystemPolicyAppData" + case appleEvents = "AppleEvents" + case bluetoothAlways = "BluetoothAlways" case calendar = "Calendar" - case reminders = "Reminders" - case photos = "Photos" case camera = "Camera" - case microphone = "Microphone" - case accessibility = "Accessibility" - case postEvent = "PostEvent" - case allFiles = "SystemPolicyAllFiles" - case adminFiles = "SystemPolicySysAdminFiles" - case fileProviderPresence = "FileProviderPresence" - case listenEvent = "ListenEvent" - case mediaLibrary = "MediaLibrary" - case screenCapture = "ScreenCapture" - case speechRecognition = "SpeechRecognition" case desktopFolder = "SystemPolicyDesktopFolder" case documentsFolder = "SystemPolicyDocumentsFolder" case downloadsFolder = "SystemPolicyDownloadsFolder" + case fileProviderPresence = "FileProviderPresence" + case listenEvent = "ListenEvent" + case mediaLibrary = "MediaLibrary" + case microphone = "Microphone" case networkVolumes = "SystemPolicyNetworkVolumes" + case photos = "Photos" + case postEvent = "PostEvent" + case reminders = "Reminders" case removableVolumes = "SystemPolicyRemovableVolumes" - case appleEvents = "AppleEvents" + case screenCapture = "ScreenCapture" + case speechRecognition = "SpeechRecognition" } diff --git a/Source/View Controllers/TCCProfileViewController.swift b/Source/View Controllers/TCCProfileViewController.swift index 9b670e0..a85daf5 100644 --- a/Source/View Controllers/TCCProfileViewController.swift +++ b/Source/View Controllers/TCCProfileViewController.swift @@ -52,80 +52,92 @@ class TCCProfileViewController: NSViewController { @IBOutlet weak var identifierLabel: NSTextField! @IBOutlet weak var codeRequirementLabel: NSTextField! - @IBOutlet weak var addressBookPopUp: NSPopUpButton! - @IBOutlet weak var photosPopUp: NSPopUpButton! - @IBOutlet weak var remindersPopUp: NSPopUpButton! - @IBOutlet weak var calendarPopUp: NSPopUpButton! @IBOutlet weak var accessibilityPopUp: NSPopUpButton! - @IBOutlet weak var postEventsPopUp: NSPopUpButton! + @IBOutlet weak var addressBookPopUp: NSPopUpButton! @IBOutlet weak var adminFilesPopUp: NSPopUpButton! @IBOutlet weak var allFilesPopUp: NSPopUpButton! + @IBOutlet weak var appBundlesPopUp: NSPopUpButton! + @IBOutlet weak var appDataPopUp: NSPopUpButton! + @IBOutlet weak var bluetoothAlwaysPopUp: NSPopUpButton! + @IBOutlet weak var calendarPopUp: NSPopUpButton! @IBOutlet weak var cameraPopUp: NSPopUpButton! - @IBOutlet weak var microphonePopUp: NSPopUpButton! - @IBOutlet weak var fileProviderPresencePopUp: NSPopUpButton! - @IBOutlet weak var listenEventPopUp: NSPopUpButton! - @IBOutlet weak var mediaLibraryPopUp: NSPopUpButton! - @IBOutlet weak var screenCapturePopUp: NSPopUpButton! - @IBOutlet weak var speechRecognitionPopUp: NSPopUpButton! @IBOutlet weak var dekstopFolderPopUp: NSPopUpButton! @IBOutlet weak var documentsFolderPopUp: NSPopUpButton! @IBOutlet weak var downloadsFolderPopUp: NSPopUpButton! + @IBOutlet weak var fileProviderPresencePopUp: NSPopUpButton! + @IBOutlet weak var listenEventPopUp: NSPopUpButton! + @IBOutlet weak var mediaLibraryPopUp: NSPopUpButton! + @IBOutlet weak var microphonePopUp: NSPopUpButton! @IBOutlet weak var networkVolumesPopUp: NSPopUpButton! + @IBOutlet weak var photosPopUp: NSPopUpButton! + @IBOutlet weak var postEventsPopUp: NSPopUpButton! + @IBOutlet weak var remindersPopUp: NSPopUpButton! @IBOutlet weak var removableVolumesPopUp: NSPopUpButton! + @IBOutlet weak var screenCapturePopUp: NSPopUpButton! + @IBOutlet weak var speechRecognitionPopUp: NSPopUpButton! // Labels with descriptions - @IBOutlet weak var addressBookHelpButton: InfoButton! - @IBOutlet weak var photosHelpButton: InfoButton! - @IBOutlet weak var remindersHelpButton: InfoButton! - @IBOutlet weak var calendarHelpButton: InfoButton! @IBOutlet weak var accessibilityHelpButton: InfoButton! - @IBOutlet weak var postEventsHelpButton: InfoButton! + @IBOutlet weak var addressBookHelpButton: InfoButton! @IBOutlet weak var adminFilesHelpButton: InfoButton! @IBOutlet weak var allFilesHelpButton: InfoButton! + @IBOutlet weak var appBundlesHelpButton: InfoButton! + @IBOutlet weak var appDataHelpButton: InfoButton! + @IBOutlet weak var bluetoothAlwaysHelpButton: InfoButton! + @IBOutlet weak var calendarHelpButton: InfoButton! @IBOutlet weak var cameraHelpButton: InfoButton! - @IBOutlet weak var microphoneHelpButton: InfoButton! - @IBOutlet weak var fileProviderHelpButton: InfoButton! - @IBOutlet weak var listenEventHelpButton: InfoButton! - @IBOutlet weak var mediaLibraryHelpButton: InfoButton! - @IBOutlet weak var screenCaptureHelpButton: InfoButton! - @IBOutlet weak var speechRecognitionHelpButton: InfoButton! @IBOutlet weak var desktopFolderHelpButton: InfoButton! @IBOutlet weak var documentsFolderHelpButton: InfoButton! @IBOutlet weak var downloadsFolderHelpButton: InfoButton! + @IBOutlet weak var fileProviderHelpButton: InfoButton! + @IBOutlet weak var listenEventHelpButton: InfoButton! + @IBOutlet weak var mediaLibraryHelpButton: InfoButton! + @IBOutlet weak var microphoneHelpButton: InfoButton! @IBOutlet weak var networkVolumesHelpButton: InfoButton! + @IBOutlet weak var photosHelpButton: InfoButton! + @IBOutlet weak var postEventsHelpButton: InfoButton! + @IBOutlet weak var remindersHelpButton: InfoButton! @IBOutlet weak var removableVolumesHelpButton: InfoButton! + @IBOutlet weak var screenCaptureHelpButton: InfoButton! + @IBOutlet weak var speechRecognitionHelpButton: InfoButton! @IBOutlet weak var adminFilesStackView: NSStackView! + @IBOutlet weak var allFilesStackView: NSStackView! + @IBOutlet weak var appBundlesStackView: NSStackView! + @IBOutlet weak var appDataStackView: NSStackView! + @IBOutlet weak var bluetoothAlwaysStackView: NSStackView! @IBOutlet weak var cameraStackView: NSStackView! @IBOutlet weak var desktopFolderStackView: NSStackView! @IBOutlet weak var downloadsFolderStackView: NSStackView! - @IBOutlet weak var allFilesStackView: NSStackView! @IBOutlet weak var mediaLibraryStackView: NSStackView! @IBOutlet weak var networkVolumesStackView: NSStackView! @IBOutlet weak var postEventsStackView: NSStackView! @IBOutlet weak var removableVolumesStackView: NSStackView! @IBOutlet weak var speechRecognitionStackView: NSStackView! - @IBOutlet weak var addressBookPopUpAC: NSArrayController! - @IBOutlet weak var photosPopUpAC: NSArrayController! - @IBOutlet weak var remindersPopUpAC: NSArrayController! - @IBOutlet weak var calendarPopUpAC: NSArrayController! @IBOutlet weak var accessibilityPopUpAC: NSArrayController! - @IBOutlet weak var postEventsPopUpAC: NSArrayController! + @IBOutlet weak var addressBookPopUpAC: NSArrayController! @IBOutlet weak var adminFilesPopUpAC: NSArrayController! @IBOutlet weak var allFilesPopUpAC: NSArrayController! + @IBOutlet weak var appBundlesPopUpAC: NSArrayController! + @IBOutlet weak var appDataPopUpAC: NSArrayController! + @IBOutlet weak var bluetoothAlwaysPopUpAC: NSArrayController! + @IBOutlet weak var calendarPopUpAC: NSArrayController! @IBOutlet weak var cameraPopUpAC: NSArrayController! - @IBOutlet weak var microphonePopUpAC: NSArrayController! - @IBOutlet weak var fileProviderPresencePopUpAC: NSArrayController! - @IBOutlet weak var listenEventPopUpAC: NSArrayController! - @IBOutlet weak var mediaLibraryPopUpAC: NSArrayController! - @IBOutlet weak var screenCapturePopUpAC: NSArrayController! - @IBOutlet weak var speechRecognitionPopUpAC: NSArrayController! @IBOutlet weak var dekstopFolderPopUpAC: NSArrayController! @IBOutlet weak var documentsFolderPopUpAC: NSArrayController! @IBOutlet weak var downloadsFolderPopUpAC: NSArrayController! + @IBOutlet weak var fileProviderPresencePopUpAC: NSArrayController! + @IBOutlet weak var listenEventPopUpAC: NSArrayController! + @IBOutlet weak var mediaLibraryPopUpAC: NSArrayController! + @IBOutlet weak var microphonePopUpAC: NSArrayController! @IBOutlet weak var networkVolumesPopUpAC: NSArrayController! + @IBOutlet weak var photosPopUpAC: NSArrayController! + @IBOutlet weak var remindersPopUpAC: NSArrayController! @IBOutlet weak var removableVolumesPopUpAC: NSArrayController! + @IBOutlet weak var screenCapturePopUpAC: NSArrayController! + @IBOutlet weak var speechRecognitionPopUpAC: NSArrayController! + @IBOutlet weak var postEventsPopUpAC: NSArrayController! @IBOutlet weak var saveButton: NSButton! @IBOutlet weak var uploadButton: NSButton! @@ -238,22 +250,25 @@ class TCCProfileViewController: NSViewController { // Setup policy pop up setupAllowDeny(policies: [ - addressBookPopUpAC, - photosPopUpAC, - remindersPopUpAC, - calendarPopUpAC, accessibilityPopUpAC, - postEventsPopUpAC, + addressBookPopUpAC, adminFilesPopUpAC, allFilesPopUpAC, - fileProviderPresencePopUpAC, - mediaLibraryPopUpAC, - speechRecognitionPopUpAC, + appBundlesPopUpAC, + appDataPopUpAC, + bluetoothAlwaysPopUpAC, + calendarPopUpAC, dekstopFolderPopUpAC, documentsFolderPopUpAC, downloadsFolderPopUpAC, + fileProviderPresencePopUpAC, + mediaLibraryPopUpAC, networkVolumesPopUpAC, - removableVolumesPopUpAC + photosPopUpAC, + postEventsPopUpAC, + remindersPopUpAC, + removableVolumesPopUpAC, + speechRecognitionPopUpAC ]) setupStandardUserAllowAndDeny(policies: [ @@ -270,10 +285,13 @@ class TCCProfileViewController: NSViewController { setupStackViewsWithBackground(stackViews: [ adminFilesStackView, + allFilesStackView, + appBundlesStackView, + appDataStackView, + bluetoothAlwaysStackView, cameraStackView, desktopFolderStackView, downloadsFolderStackView, - allFilesStackView, mediaLibraryStackView, networkVolumesStackView, postEventsStackView, @@ -340,26 +358,29 @@ class TCCProfileViewController: NSViewController { private func setupDescriptions() { let services = PPPCServicesManager.shared - addressBookHelpButton.setHelpMessage(services.allServices["AddressBook"]?.userHelp) - photosHelpButton.setHelpMessage(services.allServices["Photos"]?.userHelp) - remindersHelpButton.setHelpMessage(services.allServices["Reminders"]?.userHelp) - calendarHelpButton.setHelpMessage(services.allServices["Calendar"]?.userHelp) accessibilityHelpButton.setHelpMessage(services.allServices["Accessibility"]?.userHelp) - postEventsHelpButton.setHelpMessage(services.allServices["PostEvent"]?.userHelp) + addressBookHelpButton.setHelpMessage(services.allServices["AddressBook"]?.userHelp) adminFilesHelpButton.setHelpMessage(services.allServices["SystemPolicySysAdminFiles"]?.userHelp) allFilesHelpButton.setHelpMessage(services.allServices["SystemPolicyAllFiles"]?.userHelp) + appBundlesHelpButton.setHelpMessage(services.allServices["SystemPolicyAppBundles"]?.userHelp) + appDataHelpButton.setHelpMessage(services.allServices["SystemPolicyAppData"]?.userHelp) + bluetoothAlwaysHelpButton.setHelpMessage(services.allServices["BluetoothAlways"]?.userHelp) + calendarHelpButton.setHelpMessage(services.allServices["Calendar"]?.userHelp) cameraHelpButton.setHelpMessage(services.allServices["Camera"]?.userHelp) - microphoneHelpButton.setHelpMessage(services.allServices["Microphone"]?.userHelp) - fileProviderHelpButton.setHelpMessage(services.allServices["FileProviderPresence"]?.userHelp) - listenEventHelpButton.setHelpMessage(services.allServices["ListenEvent"]?.userHelp) - mediaLibraryHelpButton.setHelpMessage(services.allServices["MediaLibrary"]?.userHelp) - screenCaptureHelpButton.setHelpMessage(services.allServices["ScreenCapture"]?.userHelp) - speechRecognitionHelpButton.setHelpMessage(services.allServices["SpeechRecognition"]?.userHelp) desktopFolderHelpButton.setHelpMessage(services.allServices["SystemPolicyDesktopFolder"]?.userHelp) documentsFolderHelpButton.setHelpMessage(services.allServices["SystemPolicyDocumentsFolder"]?.userHelp) downloadsFolderHelpButton.setHelpMessage(services.allServices["SystemPolicyDownloadsFolder"]?.userHelp) + fileProviderHelpButton.setHelpMessage(services.allServices["FileProviderPresence"]?.userHelp) + listenEventHelpButton.setHelpMessage(services.allServices["ListenEvent"]?.userHelp) + mediaLibraryHelpButton.setHelpMessage(services.allServices["MediaLibrary"]?.userHelp) + microphoneHelpButton.setHelpMessage(services.allServices["Microphone"]?.userHelp) networkVolumesHelpButton.setHelpMessage(services.allServices["SystemPolicyNetworkVolumes"]?.userHelp) + photosHelpButton.setHelpMessage(services.allServices["Photos"]?.userHelp) + postEventsHelpButton.setHelpMessage(services.allServices["PostEvent"]?.userHelp) + remindersHelpButton.setHelpMessage(services.allServices["Reminders"]?.userHelp) removableVolumesHelpButton.setHelpMessage(services.allServices["SystemPolicyRemovableVolumes"]?.userHelp) + screenCaptureHelpButton.setHelpMessage(services.allServices["ScreenCapture"]?.userHelp) + speechRecognitionHelpButton.setHelpMessage(services.allServices["SpeechRecognition"]?.userHelp) } override func prepare(for segue: NSStoryboardSegue, sender: Any?) { @@ -414,6 +435,29 @@ class TCCProfileViewController: NSViewController { addAppleEventButton.setAccessibilityIdentifier("AddAppleEventButton") removeAppleEventButton.setAccessibilityIdentifier("RemoveAppleEventButton") removeExecutableButton.setAccessibilityIdentifier("RemoveExecutableButton") + accessibilityPopUp.setAccessibilityIdentifier("AccessibilityPopUp") + addressBookPopUp.setAccessibilityIdentifier("AddressBookPopUp") + adminFilesPopUp.setAccessibilityIdentifier("AdminFilesPopUp") + allFilesPopUp.setAccessibilityIdentifier("AllFilesPopUp") + appBundlesPopUp.setAccessibilityIdentifier("AppBundlesPopUp") + appDataPopUp.setAccessibilityIdentifier("AppDataPopUp") + bluetoothAlwaysPopUp.setAccessibilityIdentifier("BluetoothAlwaysPopUp") + calendarPopUp.setAccessibilityIdentifier("CalendarPopUp") + cameraPopUp.setAccessibilityIdentifier("CameraPopUp") + dekstopFolderPopUp.setAccessibilityIdentifier("DesktopFolderPopUp") + documentsFolderPopUp.setAccessibilityIdentifier("DocumentsFolderPopUp") + downloadsFolderPopUp.setAccessibilityIdentifier("DownloadsFolderPopUp") + fileProviderPresencePopUp.setAccessibilityIdentifier("FileProviderPresencePopUp") + listenEventPopUp.setAccessibilityIdentifier("ListenEventPopUp") + mediaLibraryPopUp.setAccessibilityIdentifier("MediaLibraryPopUp") + microphonePopUp.setAccessibilityIdentifier("MicrophonePopUp") + networkVolumesPopUp.setAccessibilityIdentifier("NetworkVolumesPopUp") + photosPopUp.setAccessibilityIdentifier("PhotosPopUp") + postEventsPopUp.setAccessibilityIdentifier("PostEventsPopUp") + remindersPopUp.setAccessibilityIdentifier("RemindersPopUp") + removableVolumesPopUp.setAccessibilityIdentifier("RemovableVolumesPopUp") + screenCapturePopUp.setAccessibilityIdentifier("ScreenCapturePopUp") + speechRecognitionPopUp.setAccessibilityIdentifier("SpeechRecognitionPopUp") } private func loadUITestProfileIfNeeded() { diff --git a/specs/002-add-pppc-keys/quickstart.md b/specs/002-add-pppc-keys/quickstart.md index c3bd7c7..63e9d07 100644 --- a/specs/002-add-pppc-keys/quickstart.md +++ b/specs/002-add-pppc-keys/quickstart.md @@ -52,7 +52,7 @@ xcodebuild clean build -project "PPPC Utility.xcodeproj" -scheme "PPPC Utility" xcodebuild test -project "PPPC Utility.xcodeproj" -scheme "PPPC Utility" -destination "platform=macOS" -testPlan "PPPC Utility" # UI tests -xcodebuild test -project "PPPC Utility.xcodeproj" -scheme "PPPC Utility" -destination "platform=macOS" -testPlan "PPPC Utility UI Tests" +xcodebuild test -project "PPPC Utility.xcodeproj" -scheme "PPPC Utility UI Tests" -destination "platform=macOS" # Compiler warnings check xcodebuild clean build-for-testing -project "PPPC Utility.xcodeproj" -scheme "PPPC Utility" -destination "platform=macOS" 2>&1 | grep -i "warning:" | grep -v "xcodebuild: WARNING" diff --git a/specs/002-add-pppc-keys/tasks.md b/specs/002-add-pppc-keys/tasks.md index 33970c2..43b13ff 100644 --- a/specs/002-add-pppc-keys/tasks.md +++ b/specs/002-add-pppc-keys/tasks.md @@ -23,9 +23,9 @@ **Purpose**: Capture baseline before any changes -- [ ] T001 Capture baseline compiler warnings by running: `xcodebuild clean build-for-testing -project "PPPC Utility.xcodeproj" -scheme "PPPC Utility" -destination "platform=macOS" 2>&1 | grep -i "warning:" | grep -v "xcodebuild: WARNING"` -- [ ] T002 Run existing unit tests to confirm green baseline: `xcodebuild test -project "PPPC Utility.xcodeproj" -scheme "PPPC Utility" -destination "platform=macOS" -testPlan "PPPC Utility"` -- [ ] T003 Run existing UI tests to confirm green baseline: `xcodebuild test -project "PPPC Utility.xcodeproj" -scheme "PPPC Utility" -destination "platform=macOS" -testPlan "PPPC Utility UI Tests"` +- [X] T001 Capture baseline compiler warnings by running: `xcodebuild clean build-for-testing -project "PPPC Utility.xcodeproj" -scheme "PPPC Utility" -destination "platform=macOS" 2>&1 | grep -i "warning:" | grep -v "xcodebuild: WARNING"` +- [X] T002 Run existing unit tests to confirm green baseline: `xcodebuild test -project "PPPC Utility.xcodeproj" -scheme "PPPC Utility" -destination "platform=macOS" -testPlan "PPPC Utility"` +- [X] T003 Run existing UI tests to confirm green baseline: `xcodebuild test -project "PPPC Utility.xcodeproj" -scheme "PPPC Utility" -destination "platform=macOS" -testPlan "PPPC Utility UI Tests"` --- @@ -35,9 +35,9 @@ **⚠️ CRITICAL**: No UI or test work can begin until this phase is complete. -- [ ] T004 [P] [US1] [US2] [US3] Add 3 new service entries to `Resources/PPPCServices.json` — insert `BluetoothAlways` (after Accessibility, alphabetically), `SystemPolicyAppBundles` (after SystemPolicyAllFiles), and `SystemPolicyAppData` (after SystemPolicyAppBundles). Each entry has `mdmKey`, `englishName`, and `englishDescription` per data-model.md. No `entitlements`, `denyOnly`, or `allowStandardUsers` fields. -- [ ] T005 [P] [US1] [US2] [US3] Add 3 new enum cases to `ServicesKeys` in `Source/Model/TCCProfile.swift` — add `case bluetoothAlways = "BluetoothAlways"`, `case appBundles = "SystemPolicyAppBundles"`, `case appData = "SystemPolicyAppData"`. -- [ ] T006 [P] [US1] [US2] [US3] Add 3 new `@objc dynamic` properties to the `Policy` class in `Source/Model/Executable.swift` — add `@objc dynamic var BluetoothAlways: String = "-"`, `@objc dynamic var SystemPolicyAppBundles: String = "-"`, `@objc dynamic var SystemPolicyAppData: String = "-"`. Property names MUST exactly match MDM key strings (KVC requirement). +- [X] T004 [P] [US1] [US2] [US3] Add 3 new service entries to `Resources/PPPCServices.json` — insert `BluetoothAlways` (after Accessibility, alphabetically), `SystemPolicyAppBundles` (after SystemPolicyAllFiles), and `SystemPolicyAppData` (after SystemPolicyAppBundles). Each entry has `mdmKey`, `englishName`, and `englishDescription` per data-model.md. No `entitlements`, `denyOnly`, or `allowStandardUsers` fields. +- [X] T005 [P] [US1] [US2] [US3] Add 3 new enum cases to `ServicesKeys` in `Source/Model/TCCProfile.swift` — add `case bluetoothAlways = "BluetoothAlways"`, `case appBundles = "SystemPolicyAppBundles"`, `case appData = "SystemPolicyAppData"`. +- [X] T006 [P] [US1] [US2] [US3] Add 3 new `@objc dynamic` properties to the `Policy` class in `Source/Model/Executable.swift` — add `@objc dynamic var BluetoothAlways: String = "-"`, `@objc dynamic var SystemPolicyAppBundles: String = "-"`, `@objc dynamic var SystemPolicyAppData: String = "-"`. Property names MUST exactly match MDM key strings (KVC requirement). **Checkpoint**: Data model complete. Build should succeed with no new warnings. Existing tests may now fail (service count assertions) — expected. @@ -51,9 +51,9 @@ ### Implementation -- [ ] T007 [US1] [US2] [US3] Add IBOutlet declarations for all 3 new services in `Source/View Controllers/TCCProfileViewController.swift` — for each service, add: `NSPopUpButton` outlet, `NSArrayController` outlet, `InfoButton` outlet, and `NSStackView` outlet. Follow the naming pattern of existing outlets (e.g., `bluetoothAlwaysPopUp`, `bluetoothAlwaysPopUpAC`, `bluetoothAlwaysHelpButton`, `bluetoothAlwaysStackView`). -- [ ] T008 [US1] [US2] [US3] Wire new services into setup methods in `Source/View Controllers/TCCProfileViewController.swift` — add the 3 new `NSArrayController` outlets to the `setupAllowDeny()` call in `viewDidLoad()`. Add the 3 new `InfoButton` outlets to `setupDescriptions()` with their MDM keys. Add the 3 new `NSStackView` outlets to `setupStackViewsWithBackground()` (for alternating row backgrounds). Add accessibility identifiers for the 3 new popups in `setupAccessibilityIdentifiers()`. -- [ ] T009 [US1] [US2] [US3] Add UI elements for all 3 new services in `Resources/Base.lproj/Main.storyboard` — for each service, duplicate an existing service row (e.g., the Reminders row pattern) and update: label text, popup button binding to the corresponding `Policy` property via Cocoa Bindings, NSArrayController connection, InfoButton connection, and NSStackView connection. Wire all IBOutlets to the view controller. +- [X] T007 [US1] [US2] [US3] Add IBOutlet declarations for all 3 new services in `Source/View Controllers/TCCProfileViewController.swift` — for each service, add: `NSPopUpButton` outlet, `NSArrayController` outlet, `InfoButton` outlet, and `NSStackView` outlet. Follow the naming pattern of existing outlets (e.g., `bluetoothAlwaysPopUp`, `bluetoothAlwaysPopUpAC`, `bluetoothAlwaysHelpButton`, `bluetoothAlwaysStackView`). +- [X] T008 [US1] [US2] [US3] Wire new services into setup methods in `Source/View Controllers/TCCProfileViewController.swift` — add the 3 new `NSArrayController` outlets to the `setupAllowDeny()` call in `viewDidLoad()`. Add the 3 new `InfoButton` outlets to `setupDescriptions()` with their MDM keys. Add the 3 new `NSStackView` outlets to `setupStackViewsWithBackground()` (for alternating row backgrounds). Add accessibility identifiers for the 3 new popups in `setupAccessibilityIdentifiers()`. +- [X] T009 [US1] [US2] [US3] Add UI elements for all 3 new services in `Resources/Base.lproj/Main.storyboard` — for each service, duplicate an existing service row (e.g., the Reminders row pattern) and update: label text, popup button binding to the corresponding `Policy` property via Cocoa Bindings, NSArrayController connection, InfoButton connection, and NSStackView connection. Wire all IBOutlets to the view controller. **Checkpoint**: All 3 services visible in UI. Build and launch app to verify popups appear. Cocoa Bindings functional (selecting Allow/Deny updates the Policy object). @@ -65,10 +65,10 @@ ### Tests -- [ ] T010 [P] [US1] [US2] [US3] Update service count assertion in `PPPC UtilityTests/ModelTests/PPPCServicesManagerTests.swift` — change expected `allServices.count` from 21 to 24. Add assertions that `allServices["BluetoothAlways"]`, `allServices["SystemPolicyAppBundles"]`, and `allServices["SystemPolicyAppData"]` are non-nil with correct `englishName` values. (Satisfies TR-001) -- [ ] T011 [P] [US1] [US2] [US3] Update policy defaults test in `PPPC UtilityTests/ModelTests/ExecutableTests.swift` — verify that a new `Executable`'s `policy.BluetoothAlways`, `policy.SystemPolicyAppBundles`, and `policy.SystemPolicyAppData` all default to `"-"`. Update any existing count-based assertions for total policy properties. (Satisfies TR-002) -- [ ] T012 [P] [US1] [US2] [US3] Add new keys to `buildTCCPolicies()` in `PPPC UtilityTests/Helpers/TCCProfileBuilder.swift` — add `"BluetoothAlways"`, `"SystemPolicyAppBundles"`, and `"SystemPolicyAppData"` entries to the returned dictionary so round-trip tests exercise the new keys. -- [ ] T013 [US1] [US2] [US3] Add export/import round-trip tests in `PPPC UtilityTests/ModelTests/ModelTests.swift` — add tests verifying that for each of the 3 new keys, setting a policy to Allow or Deny, exporting, and re-importing preserves the value. Use `ModelBuilder` to create executables with specific policy settings. (Satisfies TR-003) +- [X] T010 [P] [US1] [US2] [US3] Update service count assertion in `PPPC UtilityTests/ModelTests/PPPCServicesManagerTests.swift` — change expected `allServices.count` from 21 to 24. Add assertions that `allServices["BluetoothAlways"]`, `allServices["SystemPolicyAppBundles"]`, and `allServices["SystemPolicyAppData"]` are non-nil with correct `englishName` values. (Satisfies TR-001) +- [X] T011 [P] [US1] [US2] [US3] Update policy defaults test in `PPPC UtilityTests/ModelTests/ExecutableTests.swift` — verify that a new `Executable`'s `policy.BluetoothAlways`, `policy.SystemPolicyAppBundles`, and `policy.SystemPolicyAppData` all default to `"-"`. Update any existing count-based assertions for total policy properties. (Satisfies TR-002) +- [X] T012 [P] [US1] [US2] [US3] Add new keys to `buildTCCPolicies()` in `PPPC UtilityTests/Helpers/TCCProfileBuilder.swift` — add `"BluetoothAlways"`, `"SystemPolicyAppBundles"`, and `"SystemPolicyAppData"` entries to the returned dictionary so round-trip tests exercise the new keys. +- [X] T013 [US1] [US2] [US3] Add export/import round-trip tests in `PPPC UtilityTests/ModelTests/ModelTests.swift` — add tests verifying that for each of the 3 new keys, setting a policy to Allow or Deny, exporting, and re-importing preserves the value. Use `ModelBuilder` to create executables with specific policy settings. (Satisfies TR-003) **Checkpoint**: Run unit test plan — all tests pass including updated service count, defaults, and round-trip assertions. @@ -82,15 +82,15 @@ ### Test Fixtures -- [ ] T014 [P] [US4] Rename existing fixture `PPPC UtilityTests/TCCProfileImporterTests/TestTCCUnsignedProfile.mobileconfig` to `TestTCCUnsignedProfile-Legacy.mobileconfig` — this preserves the original file (without new keys) as the legacy import fixture. Update the Xcode project file if the resource is referenced by name. -- [ ] T015 [P] [US4] Create new `PPPC UtilityTests/TCCProfileImporterTests/TestTCCUnsignedProfile.mobileconfig` — copy from the legacy file and add `BluetoothAlways`, `SystemPolicyAppBundles`, and `SystemPolicyAppData` service entries with test policy dictionaries (Allowed: true, Identifier, CodeRequirement, IdentifierType, Comment). Follow the exact XML plist structure of existing service entries. -- [ ] T016 [P] [US4] Update `PPPC UtilityTests/TCCProfileImporterTests/TestTCCUnsignedProfile-allLower.mobileconfig` — add `bluetoothalways`, `systempolicyappbundles`, and `systempolicyappdata` entries (lowercase keys) following the existing lowercase pattern. -- [ ] T017 [P] [US4] Update `Resources/TestTCCUnsignedProfile.mobileconfig` — add the 3 new service entries (same as T015) so the app-bundled UI test profile includes all 24 services. +- [X] T014 [P] [US4] Rename existing fixture `PPPC UtilityTests/TCCProfileImporterTests/TestTCCUnsignedProfile.mobileconfig` to `TestTCCUnsignedProfile-Legacy.mobileconfig` — this preserves the original file (without new keys) as the legacy import fixture. Update the Xcode project file if the resource is referenced by name. +- [X] T015 [P] [US4] Create new `PPPC UtilityTests/TCCProfileImporterTests/TestTCCUnsignedProfile.mobileconfig` — copy from the legacy file and add `BluetoothAlways`, `SystemPolicyAppBundles`, and `SystemPolicyAppData` service entries with test policy dictionaries (Allowed: true, Identifier, CodeRequirement, IdentifierType, Comment). Follow the exact XML plist structure of existing service entries. +- [X] T016 [P] [US4] Update `PPPC UtilityTests/TCCProfileImporterTests/TestTCCUnsignedProfile-allLower.mobileconfig` — add `bluetoothalways`, `systempolicyappbundles`, and `systempolicyappdata` entries (lowercase keys) following the existing lowercase pattern. +- [X] T017 [P] [US4] Update `Resources/TestTCCUnsignedProfile.mobileconfig` — add the 3 new service entries (same as T015) so the app-bundled UI test profile includes all 24 services. ### Tests -- [ ] T018 [US4] Add legacy import test in `PPPC UtilityTests/TCCProfileImporterTests/TCCProfileImporterTests.swift` — add a test that imports `TestTCCUnsignedProfile-Legacy.mobileconfig`, feeds it through `Model.importProfile()`, and verifies the 3 new policy columns default to `"-"` on all imported executables. (Satisfies TR-005) -- [ ] T019 [US4] Verify existing `correctUnsignedProfileContentData` test passes with the new fixture in `PPPC UtilityTests/TCCProfileImporterTests/TCCProfileImporterTests.swift` — the test should now import a profile with all 24 services and succeed. (Satisfies TR-004) +- [X] T018 [US4] Add legacy import test in `PPPC UtilityTests/TCCProfileImporterTests/TCCProfileImporterTests.swift` — add a test that imports `TestTCCUnsignedProfile-Legacy.mobileconfig`, feeds it through `Model.importProfile()`, and verifies the 3 new policy columns default to `"-"` on all imported executables. (Satisfies TR-005) +- [X] T019 [US4] Verify existing `correctUnsignedProfileContentData` test passes with the new fixture in `PPPC UtilityTests/TCCProfileImporterTests/TCCProfileImporterTests.swift` — the test should now import a profile with all 24 services and succeed. (Satisfies TR-004) **Checkpoint**: All importer tests pass. Legacy profile imports cleanly. Modern profile imports with all 24 services. @@ -100,11 +100,11 @@ **Purpose**: UI test, final validation, quality gate checks -- [ ] T020 [US1] [US2] [US3] Add or update a UI test in `PPPC UtilityUITests/AppLaunchTests.swift` to verify the policy table column count reflects the 3 new services. Use the `-UITestMode` launch argument (which loads the test profile) and assert expected popup/column count. (Satisfies TR-006) -- [ ] T021 Compare compiler warnings against T001 baseline — verify no new warnings introduced by running the same command and diffing output. -- [ ] T022 Run full unit test plan and verify all tests pass: `xcodebuild test -project "PPPC Utility.xcodeproj" -scheme "PPPC Utility" -destination "platform=macOS" -testPlan "PPPC Utility"` -- [ ] T023 Run full UI test plan and verify all tests pass: `xcodebuild test -project "PPPC Utility.xcodeproj" -scheme "PPPC Utility" -destination "platform=macOS" -testPlan "PPPC Utility UI Tests"` -- [ ] T024 Validate quickstart.md build and test commands still work per `specs/002-add-pppc-keys/quickstart.md` +- [X] T020 [US1] [US2] [US3] Add or update a UI test in `PPPC UtilityUITests/AppLaunchTests.swift` to verify the policy table column count reflects the 3 new services. Use the `-UITestMode` launch argument (which loads the test profile) and assert expected popup/column count. (Satisfies TR-006) +- [X] T021 Compare compiler warnings against T001 baseline — verify no new warnings introduced by running the same command and diffing output. +- [X] T022 Run full unit test plan and verify all tests pass: `xcodebuild test -project "PPPC Utility.xcodeproj" -scheme "PPPC Utility" -destination "platform=macOS" -testPlan "PPPC Utility"` +- [X] T023 Run full UI test plan and verify all tests pass: `xcodebuild test -project "PPPC Utility.xcodeproj" -scheme "PPPC Utility" -destination "platform=macOS" -testPlan "PPPC Utility UI Tests"` +- [X] T024 Validate quickstart.md build and test commands still work per `specs/002-add-pppc-keys/quickstart.md` --- From 10bdab1349240fba13a696a82dab4ba4e0a4328c Mon Sep 17 00:00:00 2001 From: Mike Anderson Date: Mon, 13 Apr 2026 12:24:18 -0500 Subject: [PATCH 53/53] addressed copilot comments (except the one about splitting the pr) --- .../TCCProfileImporterTests.swift | 9 +- .../TCCProfileTests.swift | 29 ++++++ ...stTCCUnsignedProfile-allLower.mobileconfig | 94 +++++++++---------- PPPC UtilityUITests/AppLaunchTests.swift | 32 +------ 4 files changed, 87 insertions(+), 77 deletions(-) diff --git a/PPPC UtilityTests/TCCProfileImporterTests/TCCProfileImporterTests.swift b/PPPC UtilityTests/TCCProfileImporterTests/TCCProfileImporterTests.swift index e930452..f8a653c 100644 --- a/PPPC UtilityTests/TCCProfileImporterTests/TCCProfileImporterTests.swift +++ b/PPPC UtilityTests/TCCProfileImporterTests/TCCProfileImporterTests.swift @@ -72,8 +72,12 @@ struct TCCProfileImporterTests { let tccProfile = try tccProfileImporter.decodeTCCProfile(fileUrl: resourceURL) #expect(!tccProfile.content.isEmpty) - #expect(!tccProfile.content[0].services.isEmpty) - #expect(tccProfile.content[0].services.count >= 24, "Profile should contain at least 24 services including 3 new keys") + let services = tccProfile.content[0].services + #expect(!services.isEmpty) + #expect(services.count == 24, "Profile should contain exactly 24 services") + #expect(services["BluetoothAlways"] != nil, "BluetoothAlways service should exist") + #expect(services["SystemPolicyAppBundles"] != nil, "SystemPolicyAppBundles service should exist") + #expect(services["SystemPolicyAppData"] != nil, "SystemPolicyAppData service should exist") } @Test("Legacy profile without new keys imports with dash defaults") @@ -87,6 +91,7 @@ struct TCCProfileImporterTests { await model.importProfile(tccProfile: tccProfile) // then + #expect(!model.selectedExecutables.isEmpty, "Legacy profile should import at least one executable") for executable in model.selectedExecutables { #expect(executable.policy.BluetoothAlways == "-", "BluetoothAlways should default to dash for legacy profiles") #expect(executable.policy.SystemPolicyAppBundles == "-", "SystemPolicyAppBundles should default to dash for legacy profiles") diff --git a/PPPC UtilityTests/TCCProfileImporterTests/TCCProfileTests.swift b/PPPC UtilityTests/TCCProfileImporterTests/TCCProfileTests.swift index 4a907cb..4845730 100644 --- a/PPPC UtilityTests/TCCProfileImporterTests/TCCProfileTests.swift +++ b/PPPC UtilityTests/TCCProfileImporterTests/TCCProfileTests.swift @@ -64,6 +64,22 @@ struct TCCProfileTests { // then verify the services key #expect(content.services.count == 5) + let allFiles = content.services["SystemPolicyAllFiles"] + #expect(allFiles?.count == 1) + allFiles?.forEach { policy in + #expect(policy.identifier == "policy id") + #expect(policy.identifierType == "policy id type") + #expect(policy.codeRequirement == "policy code req") + #expect(policy.allowed == nil) + #expect(policy.authorization == .allowStandardUserToSetSystemService) + #expect(policy.comment == "policy comment") + #expect(policy.receiverIdentifier == "policy receiver id") + #expect(policy.receiverIdentifierType == "policy receiver id type") + #expect(policy.receiverCodeRequirement == "policy receiver code req") + } + #expect(content.services["BluetoothAlways"] != nil, "BluetoothAlways should be included") + #expect(content.services["SystemPolicyAppBundles"] != nil, "SystemPolicyAppBundles should be included") + #expect(content.services["SystemPolicyAppData"] != nil, "SystemPolicyAppData should be included") } } @@ -96,6 +112,19 @@ struct TCCProfileTests { // then verify the services key #expect(content.services.count == 5) + let allFiles = content.services["SystemPolicyAllFiles"] + #expect(allFiles?.count == 1) + allFiles?.forEach { policy in + #expect(policy.identifier == "policy id") + #expect(policy.identifierType == "policy id type") + #expect(policy.codeRequirement == "policy code req") + #expect(policy.allowed == true) + #expect(policy.authorization == nil) + #expect(policy.comment == "policy comment") + #expect(policy.receiverIdentifier == "policy receiver id") + #expect(policy.receiverIdentifierType == "policy receiver id type") + #expect(policy.receiverCodeRequirement == "policy receiver code req") + } } } diff --git a/PPPC UtilityTests/TCCProfileImporterTests/TestTCCUnsignedProfile-allLower.mobileconfig b/PPPC UtilityTests/TCCProfileImporterTests/TestTCCUnsignedProfile-allLower.mobileconfig index 8f0f267..21ccbbb 100644 --- a/PPPC UtilityTests/TCCProfileImporterTests/TestTCCUnsignedProfile-allLower.mobileconfig +++ b/PPPC UtilityTests/TCCProfileImporterTests/TestTCCUnsignedProfile-allLower.mobileconfig @@ -48,22 +48,22 @@ bundleid - bluetoothalways - - -allowed - -coderequirement -identifier "io.brackets.appshell" and anchor apple generic and certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ and certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */ and certificate leaf[subject.OU] = JQ525L2MZD -comment - -identifier -io.brackets.appshell -identifiertype -bundleID - - - addressbook + bluetoothalways + + + allowed + + coderequirement + identifier "io.brackets.appshell" and anchor apple generic and certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ and certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */ and certificate leaf[subject.ou] = jq525l2mzd + comment + + identifier + io.brackets.appshell + identifiertype + bundleid + + + addressbook allowed @@ -465,37 +465,37 @@ bundleid - systempolicyappbundles - - -allowed - -coderequirement -identifier "io.brackets.appshell" and anchor apple generic and certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ and certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */ and certificate leaf[subject.OU] = JQ525L2MZD -comment - -identifier -io.brackets.appshell -identifiertype -bundleID - - -systempolicyappdata - - -allowed - -coderequirement -identifier "io.brackets.appshell" and anchor apple generic and certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ and certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */ and certificate leaf[subject.OU] = JQ525L2MZD -comment - -identifier -io.brackets.appshell -identifiertype -bundleID - - - systempolicydesktopfolder + systempolicyappbundles + + + allowed + + coderequirement + identifier "io.brackets.appshell" and anchor apple generic and certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ and certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */ and certificate leaf[subject.ou] = jq525l2mzd + comment + + identifier + io.brackets.appshell + identifiertype + bundleid + + + systempolicyappdata + + + allowed + + coderequirement + identifier "io.brackets.appshell" and anchor apple generic and certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ and certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */ and certificate leaf[subject.ou] = jq525l2mzd + comment + + identifier + io.brackets.appshell + identifiertype + bundleid + + + systempolicydesktopfolder allowed diff --git a/PPPC UtilityUITests/AppLaunchTests.swift b/PPPC UtilityUITests/AppLaunchTests.swift index 19233c5..7334829 100644 --- a/PPPC UtilityUITests/AppLaunchTests.swift +++ b/PPPC UtilityUITests/AppLaunchTests.swift @@ -80,44 +80,20 @@ final class AppLaunchTests: XCTestCase { XCTAssertFalse(removeAppleEventButton.isEnabled, "Remove Apple Event button should be disabled with no selection") } - func testServicePopupsExistInExpectedOrder() { + func testServicePopupsExist() { app.terminate() app.launchArguments = ["-UITestMode"] app.launch() - let expectedPopUpOrder = [ - "AccessibilityPopUp", - "AdminFilesPopUp", + let newServicePopUps = [ "AppBundlesPopUp", "AppDataPopUp", - "BluetoothAlwaysPopUp", - "CalendarPopUp", - "CameraPopUp", - "AddressBookPopUp", - "DesktopFolderPopUp", - "DocumentsFolderPopUp", - "DownloadsFolderPopUp", - "FileProviderPresencePopUp", - "AllFilesPopUp", - "ListenEventPopUp", - "MediaLibraryPopUp", - "MicrophonePopUp", - "NetworkVolumesPopUp", - "PhotosPopUp", - "PostEventsPopUp", - "RemindersPopUp", - "RemovableVolumesPopUp", - "ScreenCapturePopUp", - "SpeechRecognitionPopUp" + "BluetoothAlwaysPopUp" ] - var previousY = -CGFloat.greatestFiniteMagnitude - for identifier in expectedPopUpOrder { + for identifier in newServicePopUps { let popUp = app.popUpButtons[identifier] XCTAssertTrue(popUp.waitForExistence(timeout: 5), "\(identifier) should exist") - let currentY = popUp.frame.origin.y - XCTAssertGreaterThan(currentY, previousY, "\(identifier) should appear below the previous popup") - previousY = currentY } } }