Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use Privileged Helper for file operations #662

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
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
2 changes: 2 additions & 0 deletions App.xcconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
DEVELOPMENT_TEAM=ZU6GR6B2FY
CODE_SIGNING_SUBJECT_ORGANIZATIONAL_UNIT=ZU6GR6B2FY
2 changes: 2 additions & 0 deletions Helper.xcconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
DEVELOPMENT_TEAM=ZU6GR6B2FY
CODE_SIGNING_SUBJECT_ORGANIZATIONAL_UNIT=ZU6GR6B2FY
61 changes: 61 additions & 0 deletions HelperXPCShared/FileOperations.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import Foundation
import os.log

enum FileOperations {
private static var subsystem = Bundle.main.bundleIdentifier!
static let fileOperations = Logger(subsystem: subsystem, category: "fileOperations")

static func moveApp(at source: String, to destination: String, completion: @escaping ((any Error)?) -> Void) {
do {
guard URL(fileURLWithPath: source).hasDirectoryPath else { throw XPCDelegateError(.invalidSourcePath)}

guard URL(fileURLWithPath: destination).deletingLastPathComponent().hasDirectoryPath else { throw
XPCDelegateError(.invalidDestinationPath)}

try FileManager.default.moveItem(at: URL(fileURLWithPath: source), to: URL(fileURLWithPath: destination))
completion(nil)
} catch {
completion(error)
}
}

// does an Xcode.app file exist?
static func createSymbolicLink(source: String, destination: String, completion: @escaping ((any Error)?) -> Void) {
do {
if FileManager.default.fileExists(atPath: destination) {
let attributes: [FileAttributeKey : Any]? = try? FileManager.default.attributesOfItem(atPath: destination)

if attributes?[.type] as? FileAttributeType == FileAttributeType.typeSymbolicLink {
try FileManager.default.removeItem(atPath: destination)
Self.fileOperations.info("Successfully deleted old symlink")
} else {
throw XPCDelegateError(.destinationIsNotASymbolicLink)
}
}

try FileManager.default.createSymbolicLink(atPath: destination, withDestinationPath: source)
Self.fileOperations.info("Successfully created symbolic link with \(destination)")
completion(nil)
} catch {
completion(error)
}
}

static func rename(source: String, destination: String, completion: @escaping ((any Error)?) -> Void) {
do {
try FileManager.default.moveItem(at: URL(fileURLWithPath: source), to: URL(fileURLWithPath: destination))
completion(nil)
} catch {
completion(error)
}
}

static func remove(path: String, completion: @escaping ((any Error)?) -> Void) {
do {
try FileManager.default.removeItem(atPath: path)
completion(nil)
} catch {
completion(error)
}
}
}
50 changes: 50 additions & 0 deletions HelperXPCShared/HelperXPCShared.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,54 @@ protocol HelperXPCProtocol {
func addStaffToDevelopersGroup(completion: @escaping (Error?) -> Void)
func acceptXcodeLicense(absoluteXcodePath: String, completion: @escaping (Error?) -> Void)
func runFirstLaunch(absoluteXcodePath: String, completion: @escaping (Error?) -> Void)
func moveApp(at source: String, to destination: String, completion: @escaping (Error?) -> Void)
func createSymbolicLink(source: String, destination: String, completion: @escaping (Error?) -> Void)
func rename(source: String, destination: String, completion: @escaping (Error?) -> Void)
func remove(path: String, completion: @escaping (Error?) -> Void)
}

struct XPCDelegateError: CustomNSError {
enum Code: Int {
case invalidXcodePath
case invalidSourcePath
case invalidDestinationPath
case destinationIsNotASymbolicLink
}

let code: Code

init(_ code: Code) {
self.code = code
}

// MARK: - CustomNSError

static var errorDomain: String { "XPCDelegateError" }

var errorCode: Int { code.rawValue }

var errorUserInfo: [String : Any] {
switch code {
case .invalidXcodePath:
return [
NSLocalizedDescriptionKey: "Invalid Xcode path.",
NSLocalizedFailureReasonErrorKey: "Xcode path must be absolute."
]
case .invalidSourcePath:
return [
NSLocalizedDescriptionKey: "Invalid source path.",
NSLocalizedFailureReasonErrorKey: "Source path must be absolute and must be a directory."
]
case .invalidDestinationPath:
return [
NSLocalizedDescriptionKey: "Invalid destination path.",
NSLocalizedFailureReasonErrorKey: "Destination path must be absolute and must be a directory."
]
case .destinationIsNotASymbolicLink:
return [
NSLocalizedDescriptionKey: "Invalid destination path.",
NSLocalizedFailureReasonErrorKey: "Destination path must be a symbolic link."
]
}
}
}
2 changes: 2 additions & 0 deletions Tests.xcconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
DEVELOPMENT_TEAM=ZU6GR6B2FY
CODE_SIGNING_SUBJECT_ORGANIZATIONAL_UNIT=ZU6GR6B2FY
36 changes: 21 additions & 15 deletions Xcodes.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
objects = {

/* Begin PBXBuildFile section */
150235A12CED5E2200F6ECBF /* FileOperations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 150235A02CED5E2200F6ECBF /* FileOperations.swift */; };
150235A22CED5E2200F6ECBF /* FileOperations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 150235A02CED5E2200F6ECBF /* FileOperations.swift */; };
15F5B8902CCF09B900705E2F /* CryptoKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 15F5B88F2CCF09B900705E2F /* CryptoKit.framework */; };
33027E342CA8C18800CB387C /* LibFido2Swift in Frameworks */ = {isa = PBXBuildFile; productRef = 334A932B2CA885A400A5E079 /* LibFido2Swift */; };
3328073F2CA5E2C80036F691 /* SignInSecurityKeyPinView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3328073E2CA5E2C80036F691 /* SignInSecurityKeyPinView.swift */; };
Expand Down Expand Up @@ -197,6 +199,10 @@
/* End PBXCopyFilesBuildPhase section */

/* Begin PBXFileReference section */
150235A02CED5E2200F6ECBF /* FileOperations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileOperations.swift; sourceTree = "<group>"; };
1542A3022CEF05AE00DB71B0 /* App.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = App.xcconfig; sourceTree = "<group>"; };
1542A3032CEF05B800DB71B0 /* Tests.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Tests.xcconfig; sourceTree = "<group>"; };
1542A3042CEF05C900DB71B0 /* Helper.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Helper.xcconfig; sourceTree = "<group>"; };
15F5B88F2CCF09B900705E2F /* CryptoKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CryptoKit.framework; path = System/Library/Frameworks/CryptoKit.framework; sourceTree = SDKROOT; };
3328073E2CA5E2C80036F691 /* SignInSecurityKeyPinView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignInSecurityKeyPinView.swift; sourceTree = "<group>"; };
332807402CA5EA820036F691 /* SignInSecurityKeyTouchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignInSecurityKeyTouchView.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -456,6 +462,7 @@
isa = PBXGroup;
children = (
CA9FF8CE25959A9700E47BAF /* HelperXPCShared.swift */,
150235A02CED5E2200F6ECBF /* FileOperations.swift */,
);
path = HelperXPCShared;
sourceTree = "<group>";
Expand Down Expand Up @@ -593,6 +600,9 @@
CAD2E79F2449574E00113D76 /* Products */,
CA538A12255A4F7C00E64DD7 /* Frameworks */,
CA452BE025A2354D0072DFA4 /* Recovered References */,
1542A3022CEF05AE00DB71B0 /* App.xcconfig */,
1542A3032CEF05B800DB71B0 /* Tests.xcconfig */,
1542A3042CEF05C900DB71B0 /* Helper.xcconfig */,
);
sourceTree = "<group>";
};
Expand Down Expand Up @@ -884,6 +894,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
150235A22CED5E2200F6ECBF /* FileOperations.swift in Sources */,
CA9FF8D025959A9700E47BAF /* HelperXPCShared.swift in Sources */,
CA42DD7325AEB04300BC0B0C /* Logger.swift in Sources */,
CA9FF8DB25959B4000E47BAF /* XPCDelegate.swift in Sources */,
Expand All @@ -910,6 +921,7 @@
B0C6AD0D2AD91D7900E64698 /* IconView.swift in Sources */,
CA9FF9362595B44700E47BAF /* HelperClient.swift in Sources */,
B0C6AD042AD6E65700E64698 /* ReleaseDateView.swift in Sources */,
150235A12CED5E2200F6ECBF /* FileOperations.swift in Sources */,
CAA8587C25A2B37900ACF8C0 /* IsTesting.swift in Sources */,
CABFA9CA2592EEEA00380FEE /* AppState+Update.swift in Sources */,
CA44901F2463AD34003D8213 /* Tag.swift in Sources */,
Expand Down Expand Up @@ -1058,7 +1070,6 @@
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGNING_SUBJECT_ORGANIZATIONAL_UNIT = ZU6GR6B2FY;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
Expand Down Expand Up @@ -1091,9 +1102,9 @@
};
CA8FB636256E154800469DA5 /* Test */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 1542A3022CEF05AE00DB71B0 /* App.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGNING_SUBJECT_ORGANIZATIONAL_UNIT = ZU6GR6B2FY;
CODE_SIGN_ENTITLEMENTS = Xcodes/Resources/XcodesTest.entitlements;
CODE_SIGN_IDENTITY = "-";
CODE_SIGN_STYLE = Manual;
Expand All @@ -1120,6 +1131,7 @@
};
CA8FB637256E154800469DA5 /* Test */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 1542A3032CEF05B800DB71B0 /* Tests.xcconfig */;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
BUNDLE_LOADER = "$(TEST_HOST)";
Expand All @@ -1144,12 +1156,11 @@
};
CA9FF8B22595967A00E47BAF /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 1542A3042CEF05C900DB71B0 /* Helper.xcconfig */;
buildSettings = {
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CODE_SIGNING_SUBJECT_ORGANIZATIONAL_UNIT = ZU6GR6B2FY;
CODE_SIGN_STYLE = Automatic;
CREATE_INFOPLIST_SECTION_IN_BINARY = YES;
DEVELOPMENT_TEAM = ZU6GR6B2FY;
ENABLE_HARDENED_RUNTIME = YES;
INFOPLIST_FILE = "$(SRCROOT)/$(TARGET_NAME)/Info.plist";
MARKETING_VERSION = 2.0.0;
Expand All @@ -1169,9 +1180,9 @@
};
CA9FF8B32595967A00E47BAF /* Test */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 1542A3042CEF05C900DB71B0 /* Helper.xcconfig */;
buildSettings = {
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CODE_SIGNING_SUBJECT_ORGANIZATIONAL_UNIT = ZU6GR6B2FY;
CODE_SIGN_ENTITLEMENTS = com.xcodesorg.xcodesapp.Helper/com.xcodesorg.xcodesapp.HelperTest.entitlements;
CODE_SIGN_STYLE = Manual;
CREATE_INFOPLIST_SECTION_IN_BINARY = YES;
Expand All @@ -1196,13 +1207,12 @@
};
CA9FF8B42595967A00E47BAF /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 1542A3042CEF05C900DB71B0 /* Helper.xcconfig */;
buildSettings = {
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CODE_SIGNING_SUBJECT_ORGANIZATIONAL_UNIT = ZU6GR6B2FY;
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CREATE_INFOPLIST_SECTION_IN_BINARY = YES;
DEVELOPMENT_TEAM = ZU6GR6B2FY;
ENABLE_HARDENED_RUNTIME = YES;
INFOPLIST_FILE = "$(SRCROOT)/$(TARGET_NAME)/Info.plist";
MARKETING_VERSION = 2.0.0;
Expand Down Expand Up @@ -1253,7 +1263,6 @@
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGNING_SUBJECT_ORGANIZATIONAL_UNIT = ZU6GR6B2FY;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
Expand Down Expand Up @@ -1317,7 +1326,6 @@
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGNING_SUBJECT_ORGANIZATIONAL_UNIT = ZU6GR6B2FY;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
Expand All @@ -1343,16 +1351,15 @@
};
CAD2E7BD2449575100113D76 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 1542A3022CEF05AE00DB71B0 /* App.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGNING_SUBJECT_ORGANIZATIONAL_UNIT = ZU6GR6B2FY;
CODE_SIGN_ENTITLEMENTS = Xcodes/Resources/Xcodes.entitlements;
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 30;
DEVELOPMENT_ASSET_PATHS = "\"Xcodes/Preview Content\"";
DEVELOPMENT_TEAM = ZU6GR6B2FY;
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_PREVIEWS = YES;
INFOPLIST_FILE = Xcodes/Resources/Info.plist;
Expand All @@ -1371,16 +1378,15 @@
};
CAD2E7BE2449575100113D76 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 1542A3022CEF05AE00DB71B0 /* App.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGNING_SUBJECT_ORGANIZATIONAL_UNIT = ZU6GR6B2FY;
CODE_SIGN_ENTITLEMENTS = Xcodes/Resources/Xcodes.entitlements;
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 30;
DEVELOPMENT_ASSET_PATHS = "\"Xcodes/Preview Content\"";
DEVELOPMENT_TEAM = ZU6GR6B2FY;
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_PREVIEWS = YES;
INFOPLIST_FILE = Xcodes/Resources/Info.plist;
Expand All @@ -1399,12 +1405,12 @@
};
CAD2E7C02449575100113D76 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 1542A3032CEF05B800DB71B0 /* Tests.xcconfig */;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_TEAM = ZU6GR6B2FY;
INFOPLIST_FILE = XcodesTests/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
Expand All @@ -1421,13 +1427,13 @@
};
CAD2E7C12449575100113D76 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 1542A3032CEF05B800DB71B0 /* Tests.xcconfig */;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
BUNDLE_LOADER = "$(TEST_HOST)";
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "-";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_TEAM = ZU6GR6B2FY;
INFOPLIST_FILE = XcodesTests/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
Expand Down
15 changes: 12 additions & 3 deletions Xcodes/Backend/AppState+Install.swift
Original file line number Diff line number Diff line change
Expand Up @@ -266,18 +266,27 @@ extension AppState {
return Fail(error: error)
.eraseToAnyPublisher()
}
.tryMap { output -> URL in
.flatMap { output -> AnyPublisher<URL, Swift.Error> in
self.setInstallationStep(of: availableXcode.version, to: .moving(destination: destination.path))

let xcodeURL = source.deletingLastPathComponent().appendingPathComponent("Xcode.app")
let xcodeBetaURL = source.deletingLastPathComponent().appendingPathComponent("Xcode-beta.app")
if Current.files.fileExists(atPath: xcodeURL.path) {
try Current.files.moveItem(at: xcodeURL, to: destination)
return Current.helper.moveApp(xcodeURL.path, destination.path)
.map { _ in destination }
.eraseToAnyPublisher()
}

else if Current.files.fileExists(atPath: xcodeBetaURL.path) {
try Current.files.moveItem(at: xcodeBetaURL, to: destination)
return Current.helper.moveApp(xcodeBetaURL.path, destination.path)
.map { _ in destination }
.eraseToAnyPublisher()
}

return Fail(error: InstallationError.failedToMoveXcodeToApplications)
.eraseToAnyPublisher()
}
.tryMap { output -> URL in
return destination
}
.handleEvents(receiveCancel: {
Expand Down
Loading