Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 25 additions & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,35 @@ jobs:
steps:
- uses: actions/checkout@v4

- name: Run unit tests
- name: Run unit tests with code coverage
run: |
xcodebuild test \
-project "PPPC Utility.xcodeproj" \
-scheme "PPPC Utility" \
-destination "platform=macOS" \
-enableCodeCoverage YES \
-resultBundlePath TestResults.xcresult \
CODE_SIGN_IDENTITY="-" \
CODE_SIGNING_REQUIRED=NO

- name: Display code coverage summary
if: always()
run: |
if [ ! -d TestResults.xcresult ]; then
echo "Coverage bundle 'TestResults.xcresult' not found; skipping coverage summary."
exit 0
fi
# Generate coverage report; capture output while still printing it
echo "Generating coverage report from TestResults.xcresult..."
if ! xcrun xccov view --report TestResults.xcresult | tee coverage-report.txt; then
echo "Failed to generate coverage report with xccov; printing any captured output and continuing."
cat coverage-report.txt 2>/dev/null || true
exit 0
fi
echo
echo "Filtered coverage summary (matching app targets or PPPC-Utility/Source):"
if ! grep -E '^[A-Z].*\.app|PPPC-Utility/Source' coverage-report.txt; then
echo
echo "No matching coverage lines found; displaying full coverage report instead."
cat coverage-report.txt
fi
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
### Changed
- Update print and os_log calls to the modern OSLog class calls for updated logging. ([Issue #112](https://github.com/jamf/PPPC-Utility/issues/112)) [@SkylerGodfrey](https://github.com/SkylerGodfrey)
- Now using [Haversack](https://github.com/jamf/Haversack) for simplified access to the keychain ([Issue #124](https://github.com/jamf/PPPC-Utility/issues/124)) [@macblazer](https://github.com/macblazer).
- PPPC Utility now requires macOS 11+ to run. It can still produce profiles usable on older versions of macOS.
- PPPC Utility now requires macOS 13+ to run. It can still produce profiles usable on older versions of macOS.
- Removed Big Sur compatibility toggle and legacy `Allowed` key support. The `Authorization` key is now always used.

Choose a reason for hiding this comment

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

If we are jumping from 11 to 13, we'd be removing Monterey too, right?

Copy link
Contributor

Choose a reason for hiding this comment

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

The Big Sur compatibility feature was a backward compatibility thing specific to the how we build the config profile.

The first bullet is about what version of macOS the app can run on. The second bullet refers specifically to that feature that was removed.


## [1.5.0] - 2022-10-04

Expand Down
8 changes: 2 additions & 6 deletions PPPC Utility.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@
C0EE9A7D28639BF800738B6B /* TestTCCProfileForJamfProAPI.txt in Resources */ = {isa = PBXBuildFile; fileRef = C0EE9A7C28639BF800738B6B /* TestTCCProfileForJamfProAPI.txt */; };
C0EE9A7F2863BDE300738B6B /* JamfProAPITypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0EE9A7E2863BDE300738B6B /* JamfProAPITypes.swift */; };
C0EE9A812863BE2B00738B6B /* NetworkAuthManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0EE9A802863BE2B00738B6B /* NetworkAuthManager.swift */; };
C0EE9A832863BEEB00738B6B /* URLSessionAsyncCompatibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0EE9A822863BEEB00738B6B /* URLSessionAsyncCompatibility.swift */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand Down Expand Up @@ -131,7 +130,6 @@
C0EE9A7C28639BF800738B6B /* TestTCCProfileForJamfProAPI.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = TestTCCProfileForJamfProAPI.txt; sourceTree = "<group>"; };
C0EE9A7E2863BDE300738B6B /* JamfProAPITypes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JamfProAPITypes.swift; sourceTree = "<group>"; };
C0EE9A802863BE2B00738B6B /* NetworkAuthManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkAuthManager.swift; sourceTree = "<group>"; };
C0EE9A822863BEEB00738B6B /* URLSessionAsyncCompatibility.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URLSessionAsyncCompatibility.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -324,7 +322,6 @@
C03270BF28636397008B38E0 /* JamfProAPIClient.swift */,
C0EE9A7E2863BDE300738B6B /* JamfProAPITypes.swift */,
C05844B72AD4512D00141353 /* Token.swift */,
C0EE9A822863BEEB00738B6B /* URLSessionAsyncCompatibility.swift */,
);
path = Networking;
sourceTree = "<group>";
Expand Down Expand Up @@ -535,7 +532,6 @@
6E6216F9215321CE0043DF18 /* OpenViewController.swift in Sources */,
C05844B82AD4512D00141353 /* Token.swift in Sources */,
6EC40A10214DE3B200BE4F17 /* Executable.swift in Sources */,
C0EE9A832863BEEB00738B6B /* URLSessionAsyncCompatibility.swift in Sources */,
C0EE9A812863BE2B00738B6B /* NetworkAuthManager.swift in Sources */,
6EC40A16214ECF1E00BE4F17 /* SaveViewController.swift in Sources */,
C05844BE2AD45F7900141353 /* UploadInfoView.swift in Sources */,
Expand Down Expand Up @@ -667,7 +663,7 @@
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IBSC_NOTICES = NO;
MACOSX_DEPLOYMENT_TARGET = 11.0;
MACOSX_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
Expand Down Expand Up @@ -723,7 +719,7 @@
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IBSC_NOTICES = NO;
MACOSX_DEPLOYMENT_TARGET = 11.0;
MACOSX_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = macosx;
Expand Down
197 changes: 2 additions & 195 deletions PPPC UtilityTests/ModelTests/ModelTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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")

Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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
Expand All @@ -347,7 +286,6 @@ class ModelTests: XCTestCase {

func testPolicyWhenUsingDeny() {
// given
model.usingLegacyAllowKey = false
let app = Executable(identifier: "id", codeRequirement: "req")

// when
Expand All @@ -360,7 +298,6 @@ class ModelTests: XCTestCase {

func testPolicyWhenUsingAllowForStandardUsers() {
// given
model.usingLegacyAllowKey = false
let app = Executable(identifier: "id", codeRequirement: "req")

// when
Expand All @@ -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)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ class PPPCServicesManagerTests: XCTestCase {
let service = try XCTUnwrap(services.allServices["ScreenCapture"])

// when
let actual = try XCTUnwrap(service.allowStandardUsersMacOS11Plus)
let actual = try XCTUnwrap(service.allowStandardUsers)

// then
XCTAssertTrue(actual)
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

[logo]: /Resources/Assets.xcassets/AppIcon.appiconset/PPPC_Logo_32%402x.png "PPPC Utility"

PPPC Utility is a macOS (10.15 and newer) application for creating configuration profiles containing the Privacy Preferences Policy Control payload for macOS. The profiles can be saved locally, signed or unsigned. Profiles can also be uploaded directly to a Jamf Pro server.
PPPC Utility is a macOS (13.0 and newer) application for creating configuration profiles containing the Privacy Preferences Policy Control payload for macOS. The profiles can be saved locally, signed or unsigned. Profiles can also be uploaded directly to a Jamf Pro server.

Choose a reason for hiding this comment

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

Are we skipping 12.0 as well?

Copy link
Contributor

Choose a reason for hiding this comment

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

yeah, and maybe we make even a little bigger jump since apple only really supports back to macOS 14.


All changes to the application are tracked in [the changelog](https://github.com/jamf/PPPC-Utility/blob/master/CHANGELOG.md).

Expand Down
Loading
Loading