diff --git a/PPPC UtilityTests/ModelTests/ModelTests.swift b/PPPC UtilityTests/ModelTests/ModelTests.swift index a07cb54..0e1a57e 100644 --- a/PPPC UtilityTests/ModelTests/ModelTests.swift +++ b/PPPC UtilityTests/ModelTests/ModelTests.swift @@ -218,9 +218,8 @@ class ModelTests: XCTestCase { // then verify the services XCTAssertEqual(2, content.services.count) - let appleEvents = content.services["AppleEvents"] - XCTAssertNotNil(appleEvents) - let appleEventsPolicy = appleEvents?.first + let appleEventsPolicy = content.services["AppleEvents"]?.first + XCTAssertNotNil(appleEventsPolicy) XCTAssertEqual("one", appleEventsPolicy?.identifier) XCTAssertEqual("oneReq", appleEventsPolicy?.codeRequirement) XCTAssertEqual("bundleID", appleEventsPolicy?.identifierType) @@ -230,9 +229,8 @@ class ModelTests: XCTestCase { XCTAssertTrue(appleEventsPolicy?.allowed == true) XCTAssertNil(appleEventsPolicy?.authorization) - let allFiles = content.services["SystemPolicyAllFiles"] - XCTAssertNotNil(allFiles) - let allFilesPolicy = allFiles?.first + let allFilesPolicy = content.services["SystemPolicyAllFiles"]?.first + XCTAssertNotNil(allFilesPolicy) XCTAssertEqual("two", allFilesPolicy?.identifier) XCTAssertEqual("twoReq", allFilesPolicy?.codeRequirement) XCTAssertEqual("bundleID", allFilesPolicy?.identifierType) diff --git a/Source/SwiftUI/UploadInfoView.swift b/Source/SwiftUI/UploadInfoView.swift index 18cdc98..4a49fd3 100644 --- a/Source/SwiftUI/UploadInfoView.swift +++ b/Source/SwiftUI/UploadInfoView.swift @@ -12,11 +12,13 @@ struct UploadInfoView: View { /// The signing identities available to be used. let signingIdentities: [SigningIdentity] /// Function to call when this view needs to be removed - let dismissAction: (() -> Void)? + let dismissAction: () -> Void // Communicate this info to the user @State private var warningInfo: String? @State private var networkOperationInfo: String? + /// Tracks whether the view has been dismissed to guard async callbacks + @State private var isDismissed = false /// Must sign the profile if Jamf Pro is less than v10.7.1 @State private var mustSign = false /// The hash of connection info that has been verified with a succesful connection @@ -147,6 +149,9 @@ struct UploadInfoView: View { payloadId = tccProfile.identifier } } + .onDisappear { + isDismissed = true + } } /// Creates a hash of the currently entered connection info @@ -275,43 +280,61 @@ struct UploadInfoView: View { guard connectionInfoPassesValidation(setWarningInfo: true) else { return } - + let requestHash = hashOfConnectionInfo networkOperationInfo = "Checking Jamf Pro server" let uploadMgr = UploadManager(serverURL: serverURL) uploadMgr.verifyConnection(authManager: makeAuthManager()) { result in - if case .success(let success) = result { - mustSign = success.mustSign - organization = success.organization - verifiedConnectionHash = hashOfConnectionInfo - if saveToKeychain { - do { - try SecurityWrapper.saveCredentials(username: username, - password: password, - server: serverURL) - } catch { - logger.error("Failed to save credentials with error: \(error.localizedDescription)") + Task { @MainActor in + guard !isDismissed, requestHash == hashOfConnectionInfo else { return } + + switch result { + case .success(let success): + mustSign = success.mustSign + organization = success.organization + verifiedConnectionHash = requestHash + if saveToKeychain { + let user = username + let pass = password + let server = serverURL + Task.detached { + do { + try SecurityWrapper.saveCredentials(username: user, + password: pass, + server: server) + } catch { + logger.error("Failed to save credentials with error: \(error.localizedDescription)") + } + } + } + case .failure(let failure): + if case .anyError(let errorString) = failure { + warningInfo = errorString } + verifiedConnectionHash = 0 } - // Future on macOS 12+: focus on Payload Name field - } else if case .failure(let failure) = result, - case .anyError(let errorString) = failure { - warningInfo = errorString - verifiedConnectionHash = 0 - } - networkOperationInfo = nil + networkOperationInfo = nil + } } } private func dismissView() { - if !saveToKeychain { - try? SecurityWrapper.removeCredentials(server: serverURL, username: username) - } + // Capture values needed for potential background credential removal + let shouldRemoveCredentials = !saveToKeychain + let server = serverURL + let user = username - if let dismiss = dismissAction { - dismiss() - } + isDismissed = true + + // Dismiss the view immediately to keep the UI responsive + dismissAction() + + if shouldRemoveCredentials { + Task.detached { + try? SecurityWrapper.removeCredentials(server: server, username: user) + } + } } func performUpload() { @@ -342,18 +365,19 @@ struct UploadInfoView: View { authMgr: makeAuthManager(), siteInfo: siteIdAndName, signingIdentity: mustSign ? signingId : nil) { possibleError in - if let error = possibleError { - warningInfo = error.localizedDescription - } else { - Alert().display(header: "Success", message: "Profile uploaded succesfully") - dismissView() - } - networkOperationInfo = nil + guard !isDismissed else { return } + + if let error = possibleError { + warningInfo = error.localizedDescription + } else { + Alert().display(header: "Success", message: "Profile uploaded successfully") + dismissView() + } + networkOperationInfo = nil } } } #Preview { - UploadInfoView(signingIdentities: [], - dismissAction: nil) + UploadInfoView(signingIdentities: []) {} } diff --git a/Source/View Controllers/TCCProfileViewController.swift b/Source/View Controllers/TCCProfileViewController.swift index 835439a..5c69e40 100644 --- a/Source/View Controllers/TCCProfileViewController.swift +++ b/Source/View Controllers/TCCProfileViewController.swift @@ -188,13 +188,14 @@ class TCCProfileViewController: NSViewController { logger.error("Error loading identities: \(error.localizedDescription)") } - let uploadView = UploadInfoView(signingIdentities: identities) { - // Dismiss the sheet when the UploadInfoView decides it is done - if let controller = self.presentedViewControllers?.first { - self.dismiss(controller) - } + let hostingController = NSHostingController( + rootView: UploadInfoView(signingIdentities: identities) {} + ) + hostingController.rootView = UploadInfoView(signingIdentities: identities) { [weak self, weak hostingController] in + guard let self, let controller = hostingController else { return } + self.dismiss(controller) } - self.presentAsSheet(NSHostingController(rootView: uploadView)) + self.presentAsSheet(hostingController) } fileprivate func showAlert(_ error: LocalizedError, for window: NSWindow) {