├── .gitignore ├── .swift-format ├── CSAuthSample Example App ├── App │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── Contents.json │ ├── Base.lproj │ │ └── Main.storyboard │ ├── CSAuthSample_Example_App.entitlements │ ├── ContentView.swift │ ├── Info.plist │ └── MessageSender.swift ├── CSAuthSample Example App.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ └── xcschemes │ │ └── Example App.xcscheme ├── Common │ ├── Build Scripts │ │ └── GlobalSetupScript.swift │ ├── Config │ │ └── CSAuthSample-Example.xcconfig │ ├── ExampleCommandSet.swift │ ├── HelperToolProtocol.swift │ ├── Identifiers.swift │ └── en.lproj │ │ └── Prompts.strings ├── Helper Tool │ ├── Build Scripts │ │ └── SetupHelperPlists.swift │ ├── HelperConnection.swift │ ├── Info.plist │ ├── Launchd.plist │ └── main.swift └── XPC Service │ ├── Info.plist │ ├── ServiceDelegate.swift │ ├── XPCService.swift │ ├── XPCServiceProtocol.swift │ └── main.swift ├── CSAuthSample.xcodeproj ├── project.pbxproj └── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ ├── CSAuthSample.xccheckout │ └── IDEWorkspaceChecks.plist ├── LICENSE.md ├── Package.swift ├── README.md └── Sources ├── CSAuthSampleApp ├── HelperClient.swift └── SandboxWorkaround.swift ├── CSAuthSampleCommon ├── CSASCommon.m └── include │ ├── CSASCommon.h │ └── module.modulemap └── CSAuthSampleHelper ├── CSASHelperConnection.m ├── CSASHelperConnectionInternal.h ├── CSASHelperConnectionWrapper.h ├── CSASHelperConnectionWrapper.m ├── CSASHelperTool.m ├── CSASHelperToolInternal.h └── include ├── CSASHelperConnection.h ├── CSASHelperTool.h └── module.modulemap /.gitignore: -------------------------------------------------------------------------------- 1 | xcuserdata 2 | .DS_Store 3 | .build 4 | swiftpm 5 | 6 | -------------------------------------------------------------------------------- /.swift-format: -------------------------------------------------------------------------------- 1 | { 2 | "fileScopedDeclarationPrivacy" : { 3 | "accessLevel" : "private" 4 | }, 5 | "indentation" : { 6 | "spaces" : 4 7 | }, 8 | "indentConditionalCompilationBlocks" : true, 9 | "indentSwitchCaseLabels" : false, 10 | "lineBreakAroundMultilineExpressionChainComponents" : false, 11 | "lineBreakBeforeControlFlowKeywords" : false, 12 | "lineBreakBeforeEachArgument" : false, 13 | "lineBreakBeforeEachGenericRequirement" : false, 14 | "lineLength" : 125, 15 | "maximumBlankLines" : 1, 16 | "prioritizeKeepingFunctionOutputTogether" : false, 17 | "respectsExistingLineBreaks" : true, 18 | "rules" : { 19 | "AllPublicDeclarationsHaveDocumentation" : true, 20 | "AlwaysUseLowerCamelCase" : true, 21 | "AmbiguousTrailingClosureOverload" : true, 22 | "BeginDocumentationCommentWithOneLineSummary" : true, 23 | "DoNotUseSemicolons" : true, 24 | "DontRepeatTypeInStaticProperties" : true, 25 | "FileScopedDeclarationPrivacy" : true, 26 | "FullyIndirectEnum" : true, 27 | "GroupNumericLiterals" : true, 28 | "IdentifiersMustBeASCII" : true, 29 | "NeverForceUnwrap" : false, 30 | "NeverUseForceTry" : false, 31 | "NeverUseImplicitlyUnwrappedOptionals" : false, 32 | "NoAccessLevelOnExtensionDeclaration" : true, 33 | "NoBlockComments" : true, 34 | "NoCasesWithOnlyFallthrough" : true, 35 | "NoEmptyTrailingClosureParentheses" : true, 36 | "NoLabelsInCasePatterns" : true, 37 | "NoLeadingUnderscores" : false, 38 | "NoParensAroundConditions" : true, 39 | "NoVoidReturnOnFunctionSignature" : true, 40 | "OneCasePerLine" : true, 41 | "OneVariableDeclarationPerLine" : true, 42 | "OnlyOneTrailingClosureArgument" : true, 43 | "OrderedImports" : true, 44 | "ReturnVoidInsteadOfEmptyTuple" : false, 45 | "UseLetInEveryBoundCaseVariable" : true, 46 | "UseShorthandTypeNames" : true, 47 | "UseSingleLinePropertyGetter" : true, 48 | "UseSynthesizedInitializer" : true, 49 | "UseTripleSlashForDocumentationComments" : true, 50 | "ValidateDocumentationComments" : true 51 | }, 52 | "tabWidth" : 8, 53 | "version" : 1 54 | } 55 | -------------------------------------------------------------------------------- /CSAuthSample Example App/App/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // CSAuthSample Example App 4 | // 5 | // Created by Charles Srstka on 4/5/20. 6 | // 7 | 8 | import Cocoa 9 | import SwiftUI 10 | 11 | @NSApplicationMain 12 | class AppDelegate: NSObject, NSApplicationDelegate { 13 | var window: NSWindow! 14 | 15 | func applicationDidFinishLaunching(_ aNotification: Notification) { 16 | let contentView = ContentView() 17 | 18 | window = NSWindow( 19 | contentRect: NSRect(x: 0, y: 0, width: 480, height: 300), 20 | styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView], 21 | backing: .buffered, defer: false) 22 | window.center() 23 | window.title = "CSAuthSample Example App" 24 | window.setFrameAutosaveName("Main Window") 25 | window.contentView = NSHostingView(rootView: contentView) 26 | window.makeKeyAndOrderFront(nil) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /CSAuthSample Example App/App/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "scale" : "1x", 6 | "size" : "16x16" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "scale" : "2x", 11 | "size" : "16x16" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "scale" : "1x", 16 | "size" : "32x32" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "scale" : "2x", 21 | "size" : "32x32" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "scale" : "1x", 26 | "size" : "128x128" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "scale" : "2x", 31 | "size" : "128x128" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "scale" : "1x", 36 | "size" : "256x256" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "scale" : "2x", 41 | "size" : "256x256" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "scale" : "1x", 46 | "size" : "512x512" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "scale" : "2x", 51 | "size" : "512x512" 52 | } 53 | ], 54 | "info" : { 55 | "author" : "xcode", 56 | "version" : 1 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /CSAuthSample Example App/App/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /CSAuthSample Example App/App/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 | 525 | 526 | 527 | 528 | Default 529 | 530 | 531 | 532 | 533 | 534 | 535 | Left to Right 536 | 537 | 538 | 539 | 540 | 541 | 542 | Right to Left 543 | 544 | 545 | 546 | 547 | 548 | 549 | 550 | 551 | 552 | 553 | Default 554 | 555 | 556 | 557 | 558 | 559 | 560 | Left to Right 561 | 562 | 563 | 564 | 565 | 566 | 567 | Right to Left 568 | 569 | 570 | 571 | 572 | 573 | 574 | 575 | 576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 | 609 | 610 | 611 | 612 | 613 | 614 | 615 | 616 | 617 | 618 | 619 | 620 | 621 | 622 | 623 | 624 | 625 | 626 | 627 | 628 | 629 | 630 | 631 | 632 | 633 | 634 | 635 | 636 | 637 | 638 | 639 | 640 | 641 | 642 | 643 | 644 | 645 | 646 | 647 | 648 | 649 | 650 | 651 | 652 | 653 | 654 | 655 | 656 | 657 | 658 | 659 | 660 | 661 | 662 | 663 | 664 | 665 | 666 | 667 | 668 | 669 | 670 | 671 | 672 | 673 | 674 | 675 | 676 | 677 | 678 | 679 | 680 | 681 | 682 | 683 | 684 | -------------------------------------------------------------------------------- /CSAuthSample Example App/App/CSAuthSample_Example_App.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.user-selected.read-only 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /CSAuthSample Example App/App/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // CSAuthSample Example App 4 | // 5 | // Created by Charles Srstka on 4/5/20. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct ContentView: View { 11 | @State private var response = "" 12 | @State private var messageSendInProgress = false 13 | 14 | var body: some View { 15 | VStack { 16 | Button { 17 | self.messageSendInProgress = true 18 | 19 | MessageSender.shared.sayHello { 20 | self.messageSendInProgress = false 21 | 22 | switch $0 { 23 | case .success(let reply): 24 | self.response = "Received reply from helper:\n\n\(reply)" 25 | case .failure(let error): 26 | self.response = "Received error from helper:\n\n\(error.localizedDescription)" 27 | } 28 | } 29 | } label: { 30 | Text("Say Hello") 31 | }.padding().disabled(self.messageSendInProgress) 32 | Text("Response:") 33 | Text($response.wrappedValue) 34 | .frame(maxWidth: .infinity, maxHeight: .infinity) 35 | }.padding().frame(minWidth: 300) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /CSAuthSample Example App/App/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | LSApplicationCategoryType 24 | public.app-category.developer-tools 25 | LSMinimumSystemVersion 26 | $(MACOSX_DEPLOYMENT_TARGET) 27 | NSHumanReadableCopyright 28 | Free for public use 29 | NSMainStoryboardFile 30 | Main 31 | NSPrincipalClass 32 | NSApplication 33 | NSSupportsAutomaticTermination 34 | 35 | NSSupportsSuddenTermination 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /CSAuthSample Example App/App/MessageSender.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MessageSender.swift 3 | // Example App 4 | // 5 | // Created by Charles Srstka on 4/5/20. 6 | // 7 | 8 | import CSAuthSampleApp 9 | import Foundation 10 | 11 | class MessageSender { 12 | static let shared = MessageSender() 13 | 14 | private var _xpcConnection: NSXPCConnection? 15 | private var sema = DispatchSemaphore(value: 1) 16 | 17 | private var xpcConnection: NSXPCConnection { 18 | self.sema.wait() 19 | defer { self.sema.signal() } 20 | 21 | if let connection = self._xpcConnection { 22 | return connection 23 | } 24 | 25 | let connection = NSXPCConnection(serviceName: Identifiers.xpcServiceID) 26 | 27 | connection.remoteObjectInterface = NSXPCInterface(with: XPCServiceProtocol.self) 28 | 29 | connection.invalidationHandler = { [weak connection] in 30 | self.sema.wait() 31 | defer { self.sema.signal() } 32 | 33 | connection?.invalidationHandler = nil 34 | self._xpcConnection = nil 35 | } 36 | 37 | connection.resume() 38 | 39 | self._xpcConnection = connection 40 | return connection 41 | } 42 | 43 | func sayHello(reply: @escaping (Result) -> Void) { 44 | let proxy = self.getProxy { reply(.failure($0)) } 45 | let sandboxWorkaround = SandboxWorkaround() 46 | 47 | proxy.sayHello(message: "Hello there, helper tool!") { 48 | sandboxWorkaround.stop() 49 | 50 | if let replyMessage = $0 { 51 | reply(.success(replyMessage)) 52 | } else { 53 | reply(.failure($1 ?? CocoaError(.fileReadUnknown))) 54 | } 55 | } 56 | } 57 | 58 | func getProxy(errorHandler: @escaping (Error) -> Void) -> XPCServiceProtocol { 59 | return self.xpcConnection.remoteObjectProxyWithErrorHandler(errorHandler) as! XPCServiceProtocol 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /CSAuthSample Example App/CSAuthSample Example App.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 2805EA37243A55F4008BD2DC /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2805EA36243A55F4008BD2DC /* AppDelegate.swift */; }; 11 | 2805EA39243A55F4008BD2DC /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2805EA38243A55F4008BD2DC /* ContentView.swift */; }; 12 | 2805EA41243A55F5008BD2DC /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2805EA3F243A55F5008BD2DC /* Main.storyboard */; }; 13 | 2805EA50243A563B008BD2DC /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2805EA4F243A563B008BD2DC /* main.swift */; }; 14 | 2805EA95243A73FB008BD2DC /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2805EA94243A73FB008BD2DC /* main.swift */; }; 15 | 2805EA99243A73FB008BD2DC /* Example XPC Service.xpc in Embed XPC Services */ = {isa = PBXBuildFile; fileRef = 2805EA8E243A73FB008BD2DC /* Example XPC Service.xpc */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 16 | 2805EAC1243A7577008BD2DC /* XPCServiceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2805EA90243A73FB008BD2DC /* XPCServiceProtocol.swift */; }; 17 | 2805EAC2243A757B008BD2DC /* XPCServiceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2805EA90243A73FB008BD2DC /* XPCServiceProtocol.swift */; }; 18 | 2805EAD5243A7B9A008BD2DC /* HelperToolProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2805EAD4243A7B9A008BD2DC /* HelperToolProtocol.swift */; }; 19 | 2805EAD6243A7B9A008BD2DC /* HelperToolProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2805EAD4243A7B9A008BD2DC /* HelperToolProtocol.swift */; }; 20 | 2805EADF243A81BF008BD2DC /* Prompts.strings in Resources */ = {isa = PBXBuildFile; fileRef = 2805EAE1243A81BF008BD2DC /* Prompts.strings */; }; 21 | 2805EAE2243A862A008BD2DC /* XPCService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2805EAD9243A7EAD008BD2DC /* XPCService.swift */; }; 22 | 2805EAE3243A8635008BD2DC /* ExampleCommandSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2805EADB243A7FC2008BD2DC /* ExampleCommandSet.swift */; }; 23 | 2805EAE4243A8636008BD2DC /* ExampleCommandSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2805EADB243A7FC2008BD2DC /* ExampleCommandSet.swift */; }; 24 | 2805EAF6243ADC5B008BD2DC /* ServiceDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2805EAF5243ADC5B008BD2DC /* ServiceDelegate.swift */; }; 25 | 2805EAF8243AEB00008BD2DC /* MessageSender.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2805EAF7243AEB00008BD2DC /* MessageSender.swift */; }; 26 | 2805EAFA243AEBD4008BD2DC /* Identifiers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2805EAF9243AEBD4008BD2DC /* Identifiers.swift */; }; 27 | 2805EAFB243AEBD4008BD2DC /* Identifiers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2805EAF9243AEBD4008BD2DC /* Identifiers.swift */; }; 28 | 2805EAFC243AEBD4008BD2DC /* Identifiers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2805EAF9243AEBD4008BD2DC /* Identifiers.swift */; }; 29 | 2821463F2443EC4D008BD2DC /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2821463E2443EC4D008BD2DC /* Assets.xcassets */; }; 30 | 28F83F082447FEB0008BD2DC /* Example Helper Tool in CopyFiles */ = {isa = PBXBuildFile; fileRef = 2805EA4D243A563B008BD2DC /* Example Helper Tool */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; 31 | 28F83F2A244808B1008BD2DC /* HelperConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28F83F29244808B1008BD2DC /* HelperConnection.swift */; }; 32 | /* End PBXBuildFile section */ 33 | 34 | /* Begin PBXContainerItemProxy section */ 35 | 28D533FC25FD2C750029362F /* PBXContainerItemProxy */ = { 36 | isa = PBXContainerItemProxy; 37 | containerPortal = 2805EA2B243A55F4008BD2DC /* Project object */; 38 | proxyType = 1; 39 | remoteGlobalIDString = 2805EA4C243A563B008BD2DC; 40 | remoteInfo = "Example Helper Tool"; 41 | }; 42 | 28D533FE25FD2C7B0029362F /* PBXContainerItemProxy */ = { 43 | isa = PBXContainerItemProxy; 44 | containerPortal = 2805EA2B243A55F4008BD2DC /* Project object */; 45 | proxyType = 1; 46 | remoteGlobalIDString = 2805EA4C243A563B008BD2DC; 47 | remoteInfo = "Example Helper Tool"; 48 | }; 49 | 28D5340625FD2CA90029362F /* PBXContainerItemProxy */ = { 50 | isa = PBXContainerItemProxy; 51 | containerPortal = 2805EA2B243A55F4008BD2DC /* Project object */; 52 | proxyType = 1; 53 | remoteGlobalIDString = 2805EA8D243A73FB008BD2DC; 54 | remoteInfo = "Example XPC Service"; 55 | }; 56 | /* End PBXContainerItemProxy section */ 57 | 58 | /* Begin PBXCopyFilesBuildPhase section */ 59 | 2805EA4B243A563B008BD2DC /* CopyFiles */ = { 60 | isa = PBXCopyFilesBuildPhase; 61 | buildActionMask = 2147483647; 62 | dstPath = /usr/share/man/man1/; 63 | dstSubfolderSpec = 0; 64 | files = ( 65 | ); 66 | runOnlyForDeploymentPostprocessing = 1; 67 | }; 68 | 2805EA9D243A73FB008BD2DC /* Embed XPC Services */ = { 69 | isa = PBXCopyFilesBuildPhase; 70 | buildActionMask = 2147483647; 71 | dstPath = "$(CONTENTS_FOLDER_PATH)/XPCServices"; 72 | dstSubfolderSpec = 16; 73 | files = ( 74 | 2805EA99243A73FB008BD2DC /* Example XPC Service.xpc in Embed XPC Services */, 75 | ); 76 | name = "Embed XPC Services"; 77 | runOnlyForDeploymentPostprocessing = 0; 78 | }; 79 | 28F83F072447FEA5008BD2DC /* CopyFiles */ = { 80 | isa = PBXCopyFilesBuildPhase; 81 | buildActionMask = 2147483647; 82 | dstPath = Contents/Library/LaunchServices; 83 | dstSubfolderSpec = 1; 84 | files = ( 85 | 28F83F082447FEB0008BD2DC /* Example Helper Tool in CopyFiles */, 86 | ); 87 | runOnlyForDeploymentPostprocessing = 0; 88 | }; 89 | /* End PBXCopyFilesBuildPhase section */ 90 | 91 | /* Begin PBXFileReference section */ 92 | 2805EA33243A55F4008BD2DC /* Example App.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Example App.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 93 | 2805EA36243A55F4008BD2DC /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 94 | 2805EA38243A55F4008BD2DC /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 95 | 2805EA40243A55F5008BD2DC /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 96 | 2805EA42243A55F5008BD2DC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 97 | 2805EA43243A55F5008BD2DC /* CSAuthSample_Example_App.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = CSAuthSample_Example_App.entitlements; sourceTree = ""; }; 98 | 2805EA4D243A563B008BD2DC /* Example Helper Tool */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = "Example Helper Tool"; sourceTree = BUILT_PRODUCTS_DIR; }; 99 | 2805EA4F243A563B008BD2DC /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; }; 100 | 2805EA8E243A73FB008BD2DC /* Example XPC Service.xpc */ = {isa = PBXFileReference; explicitFileType = "wrapper.xpc-service"; includeInIndex = 0; path = "Example XPC Service.xpc"; sourceTree = BUILT_PRODUCTS_DIR; }; 101 | 2805EA90243A73FB008BD2DC /* XPCServiceProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XPCServiceProtocol.swift; sourceTree = ""; }; 102 | 2805EA94243A73FB008BD2DC /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; }; 103 | 2805EA96243A73FB008BD2DC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 104 | 2805EAD4243A7B9A008BD2DC /* HelperToolProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HelperToolProtocol.swift; sourceTree = ""; }; 105 | 2805EAD9243A7EAD008BD2DC /* XPCService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XPCService.swift; sourceTree = ""; }; 106 | 2805EADB243A7FC2008BD2DC /* ExampleCommandSet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExampleCommandSet.swift; sourceTree = ""; }; 107 | 2805EAE0243A81BF008BD2DC /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Prompts.strings; sourceTree = ""; }; 108 | 2805EAF5243ADC5B008BD2DC /* ServiceDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServiceDelegate.swift; sourceTree = ""; }; 109 | 2805EAF7243AEB00008BD2DC /* MessageSender.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageSender.swift; sourceTree = ""; }; 110 | 2805EAF9243AEBD4008BD2DC /* Identifiers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Identifiers.swift; sourceTree = ""; }; 111 | 2821463D2443E4CB008BD2DC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 112 | 2821463E2443EC4D008BD2DC /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 113 | 282146402443ED57008BD2DC /* CSAuthSample-Example.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "CSAuthSample-Example.xcconfig"; sourceTree = ""; }; 114 | 282146452444060B008BD2DC /* Launchd.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Launchd.plist; sourceTree = ""; }; 115 | 28F83F29244808B1008BD2DC /* HelperConnection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HelperConnection.swift; sourceTree = ""; }; 116 | 28F83F3824496868008BD2DC /* GlobalSetupScript.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlobalSetupScript.swift; sourceTree = ""; }; 117 | 28F83F3924496D1A008BD2DC /* SetupHelperPlists.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetupHelperPlists.swift; sourceTree = ""; }; 118 | /* End PBXFileReference section */ 119 | 120 | /* Begin PBXFrameworksBuildPhase section */ 121 | 2805EA30243A55F4008BD2DC /* Frameworks */ = { 122 | isa = PBXFrameworksBuildPhase; 123 | buildActionMask = 2147483647; 124 | files = ( 125 | ); 126 | runOnlyForDeploymentPostprocessing = 0; 127 | }; 128 | 2805EA4A243A563B008BD2DC /* Frameworks */ = { 129 | isa = PBXFrameworksBuildPhase; 130 | buildActionMask = 2147483647; 131 | files = ( 132 | ); 133 | runOnlyForDeploymentPostprocessing = 0; 134 | }; 135 | 2805EA8B243A73FB008BD2DC /* Frameworks */ = { 136 | isa = PBXFrameworksBuildPhase; 137 | buildActionMask = 2147483647; 138 | files = ( 139 | ); 140 | runOnlyForDeploymentPostprocessing = 0; 141 | }; 142 | /* End PBXFrameworksBuildPhase section */ 143 | 144 | /* Begin PBXGroup section */ 145 | 2805EA2A243A55F4008BD2DC = { 146 | isa = PBXGroup; 147 | children = ( 148 | 2805EAD3243A7B79008BD2DC /* Common */, 149 | 2805EA35243A55F4008BD2DC /* App */, 150 | 2805EA8F243A73FB008BD2DC /* XPC Service */, 151 | 2805EA4E243A563B008BD2DC /* Helper Tool */, 152 | 2805EA34243A55F4008BD2DC /* Products */, 153 | 2805EA9E243A740E008BD2DC /* Frameworks */, 154 | ); 155 | sourceTree = ""; 156 | }; 157 | 2805EA34243A55F4008BD2DC /* Products */ = { 158 | isa = PBXGroup; 159 | children = ( 160 | 2805EA33243A55F4008BD2DC /* Example App.app */, 161 | 2805EA4D243A563B008BD2DC /* Example Helper Tool */, 162 | 2805EA8E243A73FB008BD2DC /* Example XPC Service.xpc */, 163 | ); 164 | name = Products; 165 | sourceTree = ""; 166 | }; 167 | 2805EA35243A55F4008BD2DC /* App */ = { 168 | isa = PBXGroup; 169 | children = ( 170 | 2805EA36243A55F4008BD2DC /* AppDelegate.swift */, 171 | 2805EA38243A55F4008BD2DC /* ContentView.swift */, 172 | 2805EAF7243AEB00008BD2DC /* MessageSender.swift */, 173 | 2805EA3F243A55F5008BD2DC /* Main.storyboard */, 174 | 2805EA42243A55F5008BD2DC /* Info.plist */, 175 | 2821463E2443EC4D008BD2DC /* Assets.xcassets */, 176 | 2805EA43243A55F5008BD2DC /* CSAuthSample_Example_App.entitlements */, 177 | ); 178 | path = App; 179 | sourceTree = ""; 180 | }; 181 | 2805EA4E243A563B008BD2DC /* Helper Tool */ = { 182 | isa = PBXGroup; 183 | children = ( 184 | 28F83F3B24496D5D008BD2DC /* Build Scripts */, 185 | 2805EA4F243A563B008BD2DC /* main.swift */, 186 | 28F83F29244808B1008BD2DC /* HelperConnection.swift */, 187 | 2821463D2443E4CB008BD2DC /* Info.plist */, 188 | 282146452444060B008BD2DC /* Launchd.plist */, 189 | ); 190 | path = "Helper Tool"; 191 | sourceTree = ""; 192 | }; 193 | 2805EA8F243A73FB008BD2DC /* XPC Service */ = { 194 | isa = PBXGroup; 195 | children = ( 196 | 2805EA90243A73FB008BD2DC /* XPCServiceProtocol.swift */, 197 | 2805EAD9243A7EAD008BD2DC /* XPCService.swift */, 198 | 2805EAF5243ADC5B008BD2DC /* ServiceDelegate.swift */, 199 | 2805EA94243A73FB008BD2DC /* main.swift */, 200 | 2805EA96243A73FB008BD2DC /* Info.plist */, 201 | ); 202 | path = "XPC Service"; 203 | sourceTree = ""; 204 | }; 205 | 2805EA9E243A740E008BD2DC /* Frameworks */ = { 206 | isa = PBXGroup; 207 | children = ( 208 | ); 209 | name = Frameworks; 210 | sourceTree = ""; 211 | }; 212 | 2805EAD3243A7B79008BD2DC /* Common */ = { 213 | isa = PBXGroup; 214 | children = ( 215 | 2805EAF9243AEBD4008BD2DC /* Identifiers.swift */, 216 | 2805EAD4243A7B9A008BD2DC /* HelperToolProtocol.swift */, 217 | 2805EADB243A7FC2008BD2DC /* ExampleCommandSet.swift */, 218 | 2805EAE1243A81BF008BD2DC /* Prompts.strings */, 219 | 28F83F37244967AC008BD2DC /* Build Scripts */, 220 | 282146442443FA83008BD2DC /* Config */, 221 | ); 222 | path = Common; 223 | sourceTree = ""; 224 | }; 225 | 282146442443FA83008BD2DC /* Config */ = { 226 | isa = PBXGroup; 227 | children = ( 228 | 282146402443ED57008BD2DC /* CSAuthSample-Example.xcconfig */, 229 | ); 230 | path = Config; 231 | sourceTree = ""; 232 | }; 233 | 28F83F37244967AC008BD2DC /* Build Scripts */ = { 234 | isa = PBXGroup; 235 | children = ( 236 | 28F83F3824496868008BD2DC /* GlobalSetupScript.swift */, 237 | ); 238 | path = "Build Scripts"; 239 | sourceTree = ""; 240 | }; 241 | 28F83F3B24496D5D008BD2DC /* Build Scripts */ = { 242 | isa = PBXGroup; 243 | children = ( 244 | 28F83F3924496D1A008BD2DC /* SetupHelperPlists.swift */, 245 | ); 246 | path = "Build Scripts"; 247 | sourceTree = ""; 248 | }; 249 | /* End PBXGroup section */ 250 | 251 | /* Begin PBXNativeTarget section */ 252 | 2805EA32243A55F4008BD2DC /* Example App */ = { 253 | isa = PBXNativeTarget; 254 | buildConfigurationList = 2805EA46243A55F5008BD2DC /* Build configuration list for PBXNativeTarget "Example App" */; 255 | buildPhases = ( 256 | 2805EA2F243A55F4008BD2DC /* Sources */, 257 | 2805EA30243A55F4008BD2DC /* Frameworks */, 258 | 2805EA31243A55F4008BD2DC /* Resources */, 259 | 2805EA9D243A73FB008BD2DC /* Embed XPC Services */, 260 | ); 261 | buildRules = ( 262 | ); 263 | dependencies = ( 264 | 28D533FF25FD2C7B0029362F /* PBXTargetDependency */, 265 | 28D5340725FD2CA90029362F /* PBXTargetDependency */, 266 | ); 267 | name = "Example App"; 268 | packageProductDependencies = ( 269 | ); 270 | productName = "CSAuthSample Example App"; 271 | productReference = 2805EA33243A55F4008BD2DC /* Example App.app */; 272 | productType = "com.apple.product-type.application"; 273 | }; 274 | 2805EA4C243A563B008BD2DC /* Example Helper Tool */ = { 275 | isa = PBXNativeTarget; 276 | buildConfigurationList = 2805EA51243A563B008BD2DC /* Build configuration list for PBXNativeTarget "Example Helper Tool" */; 277 | buildPhases = ( 278 | 282146772447DB93008BD2DC /* ShellScript */, 279 | 2805EA49243A563B008BD2DC /* Sources */, 280 | 2805EA4A243A563B008BD2DC /* Frameworks */, 281 | 2805EA4B243A563B008BD2DC /* CopyFiles */, 282 | ); 283 | buildRules = ( 284 | ); 285 | dependencies = ( 286 | ); 287 | name = "Example Helper Tool"; 288 | packageProductDependencies = ( 289 | ); 290 | productName = "CSAuthSample Example Helper Tool"; 291 | productReference = 2805EA4D243A563B008BD2DC /* Example Helper Tool */; 292 | productType = "com.apple.product-type.tool"; 293 | }; 294 | 2805EA8D243A73FB008BD2DC /* Example XPC Service */ = { 295 | isa = PBXNativeTarget; 296 | buildConfigurationList = 2805EA9A243A73FB008BD2DC /* Build configuration list for PBXNativeTarget "Example XPC Service" */; 297 | buildPhases = ( 298 | 2805EA8A243A73FB008BD2DC /* Sources */, 299 | 2805EA8B243A73FB008BD2DC /* Frameworks */, 300 | 2805EA8C243A73FB008BD2DC /* Resources */, 301 | 28F83F072447FEA5008BD2DC /* CopyFiles */, 302 | ); 303 | buildRules = ( 304 | ); 305 | dependencies = ( 306 | 28D533FD25FD2C750029362F /* PBXTargetDependency */, 307 | ); 308 | name = "Example XPC Service"; 309 | packageProductDependencies = ( 310 | ); 311 | productName = "Example App XPC Service"; 312 | productReference = 2805EA8E243A73FB008BD2DC /* Example XPC Service.xpc */; 313 | productType = "com.apple.product-type.xpc-service"; 314 | }; 315 | /* End PBXNativeTarget section */ 316 | 317 | /* Begin PBXProject section */ 318 | 2805EA2B243A55F4008BD2DC /* Project object */ = { 319 | isa = PBXProject; 320 | attributes = { 321 | LastSwiftUpdateCheck = 1140; 322 | LastUpgradeCheck = 1320; 323 | ORGANIZATIONNAME = "Charles Srstka"; 324 | TargetAttributes = { 325 | 2805EA32243A55F4008BD2DC = { 326 | CreatedOnToolsVersion = 11.4; 327 | }; 328 | 2805EA4C243A563B008BD2DC = { 329 | CreatedOnToolsVersion = 11.4; 330 | }; 331 | 2805EA8D243A73FB008BD2DC = { 332 | CreatedOnToolsVersion = 11.4; 333 | }; 334 | }; 335 | }; 336 | buildConfigurationList = 2805EA2E243A55F4008BD2DC /* Build configuration list for PBXProject "CSAuthSample Example App" */; 337 | compatibilityVersion = "Xcode 9.3"; 338 | developmentRegion = en; 339 | hasScannedForEncodings = 0; 340 | knownRegions = ( 341 | en, 342 | Base, 343 | ); 344 | mainGroup = 2805EA2A243A55F4008BD2DC; 345 | packageReferences = ( 346 | ); 347 | productRefGroup = 2805EA34243A55F4008BD2DC /* Products */; 348 | projectDirPath = ""; 349 | projectRoot = ""; 350 | targets = ( 351 | 2805EA32243A55F4008BD2DC /* Example App */, 352 | 2805EA8D243A73FB008BD2DC /* Example XPC Service */, 353 | 2805EA4C243A563B008BD2DC /* Example Helper Tool */, 354 | ); 355 | }; 356 | /* End PBXProject section */ 357 | 358 | /* Begin PBXResourcesBuildPhase section */ 359 | 2805EA31243A55F4008BD2DC /* Resources */ = { 360 | isa = PBXResourcesBuildPhase; 361 | buildActionMask = 2147483647; 362 | files = ( 363 | 2821463F2443EC4D008BD2DC /* Assets.xcassets in Resources */, 364 | 2805EA41243A55F5008BD2DC /* Main.storyboard in Resources */, 365 | ); 366 | runOnlyForDeploymentPostprocessing = 0; 367 | }; 368 | 2805EA8C243A73FB008BD2DC /* Resources */ = { 369 | isa = PBXResourcesBuildPhase; 370 | buildActionMask = 2147483647; 371 | files = ( 372 | 2805EADF243A81BF008BD2DC /* Prompts.strings in Resources */, 373 | ); 374 | runOnlyForDeploymentPostprocessing = 0; 375 | }; 376 | /* End PBXResourcesBuildPhase section */ 377 | 378 | /* Begin PBXShellScriptBuildPhase section */ 379 | 282146772447DB93008BD2DC /* ShellScript */ = { 380 | isa = PBXShellScriptBuildPhase; 381 | buildActionMask = 2147483647; 382 | files = ( 383 | ); 384 | inputFileListPaths = ( 385 | ); 386 | inputPaths = ( 387 | ); 388 | outputFileListPaths = ( 389 | ); 390 | outputPaths = ( 391 | "$(DERIVED_FILE_DIR)/Info.plist", 392 | "$(DERIVED_FILE_DIR)/Launchd.plist", 393 | ); 394 | runOnlyForDeploymentPostprocessing = 0; 395 | shellPath = /bin/sh; 396 | shellScript = "swift \"$SRCROOT/Helper Tool/Build Scripts/SetupHelperPlists.swift\"\n"; 397 | }; 398 | /* End PBXShellScriptBuildPhase section */ 399 | 400 | /* Begin PBXSourcesBuildPhase section */ 401 | 2805EA2F243A55F4008BD2DC /* Sources */ = { 402 | isa = PBXSourcesBuildPhase; 403 | buildActionMask = 2147483647; 404 | files = ( 405 | 2805EAF8243AEB00008BD2DC /* MessageSender.swift in Sources */, 406 | 2805EA39243A55F4008BD2DC /* ContentView.swift in Sources */, 407 | 2805EAC1243A7577008BD2DC /* XPCServiceProtocol.swift in Sources */, 408 | 2805EAFA243AEBD4008BD2DC /* Identifiers.swift in Sources */, 409 | 2805EA37243A55F4008BD2DC /* AppDelegate.swift in Sources */, 410 | ); 411 | runOnlyForDeploymentPostprocessing = 0; 412 | }; 413 | 2805EA49243A563B008BD2DC /* Sources */ = { 414 | isa = PBXSourcesBuildPhase; 415 | buildActionMask = 2147483647; 416 | files = ( 417 | 28F83F2A244808B1008BD2DC /* HelperConnection.swift in Sources */, 418 | 2805EAE4243A8636008BD2DC /* ExampleCommandSet.swift in Sources */, 419 | 2805EA50243A563B008BD2DC /* main.swift in Sources */, 420 | 2805EAFC243AEBD4008BD2DC /* Identifiers.swift in Sources */, 421 | 2805EAD6243A7B9A008BD2DC /* HelperToolProtocol.swift in Sources */, 422 | ); 423 | runOnlyForDeploymentPostprocessing = 0; 424 | }; 425 | 2805EA8A243A73FB008BD2DC /* Sources */ = { 426 | isa = PBXSourcesBuildPhase; 427 | buildActionMask = 2147483647; 428 | files = ( 429 | 2805EAE3243A8635008BD2DC /* ExampleCommandSet.swift in Sources */, 430 | 2805EA95243A73FB008BD2DC /* main.swift in Sources */, 431 | 2805EAC2243A757B008BD2DC /* XPCServiceProtocol.swift in Sources */, 432 | 2805EAD5243A7B9A008BD2DC /* HelperToolProtocol.swift in Sources */, 433 | 2805EAFB243AEBD4008BD2DC /* Identifiers.swift in Sources */, 434 | 2805EAF6243ADC5B008BD2DC /* ServiceDelegate.swift in Sources */, 435 | 2805EAE2243A862A008BD2DC /* XPCService.swift in Sources */, 436 | ); 437 | runOnlyForDeploymentPostprocessing = 0; 438 | }; 439 | /* End PBXSourcesBuildPhase section */ 440 | 441 | /* Begin PBXTargetDependency section */ 442 | 28D533FD25FD2C750029362F /* PBXTargetDependency */ = { 443 | isa = PBXTargetDependency; 444 | target = 2805EA4C243A563B008BD2DC /* Example Helper Tool */; 445 | targetProxy = 28D533FC25FD2C750029362F /* PBXContainerItemProxy */; 446 | }; 447 | 28D533FF25FD2C7B0029362F /* PBXTargetDependency */ = { 448 | isa = PBXTargetDependency; 449 | target = 2805EA4C243A563B008BD2DC /* Example Helper Tool */; 450 | targetProxy = 28D533FE25FD2C7B0029362F /* PBXContainerItemProxy */; 451 | }; 452 | 28D5340725FD2CA90029362F /* PBXTargetDependency */ = { 453 | isa = PBXTargetDependency; 454 | target = 2805EA8D243A73FB008BD2DC /* Example XPC Service */; 455 | targetProxy = 28D5340625FD2CA90029362F /* PBXContainerItemProxy */; 456 | }; 457 | /* End PBXTargetDependency section */ 458 | 459 | /* Begin PBXVariantGroup section */ 460 | 2805EA3F243A55F5008BD2DC /* Main.storyboard */ = { 461 | isa = PBXVariantGroup; 462 | children = ( 463 | 2805EA40243A55F5008BD2DC /* Base */, 464 | ); 465 | name = Main.storyboard; 466 | sourceTree = ""; 467 | }; 468 | 2805EAE1243A81BF008BD2DC /* Prompts.strings */ = { 469 | isa = PBXVariantGroup; 470 | children = ( 471 | 2805EAE0243A81BF008BD2DC /* en */, 472 | ); 473 | name = Prompts.strings; 474 | sourceTree = ""; 475 | }; 476 | /* End PBXVariantGroup section */ 477 | 478 | /* Begin XCBuildConfiguration section */ 479 | 2805EA44243A55F5008BD2DC /* Debug */ = { 480 | isa = XCBuildConfiguration; 481 | baseConfigurationReference = 282146402443ED57008BD2DC /* CSAuthSample-Example.xcconfig */; 482 | buildSettings = { 483 | ALWAYS_SEARCH_USER_PATHS = NO; 484 | CLANG_ANALYZER_NONNULL = YES; 485 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 486 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 487 | CLANG_CXX_LIBRARY = "libc++"; 488 | CLANG_ENABLE_MODULES = YES; 489 | CLANG_ENABLE_OBJC_ARC = YES; 490 | CLANG_ENABLE_OBJC_WEAK = YES; 491 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 492 | CLANG_WARN_BOOL_CONVERSION = YES; 493 | CLANG_WARN_COMMA = YES; 494 | CLANG_WARN_CONSTANT_CONVERSION = YES; 495 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 496 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 497 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 498 | CLANG_WARN_EMPTY_BODY = YES; 499 | CLANG_WARN_ENUM_CONVERSION = YES; 500 | CLANG_WARN_INFINITE_RECURSION = YES; 501 | CLANG_WARN_INT_CONVERSION = YES; 502 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 503 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 504 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 505 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 506 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 507 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 508 | CLANG_WARN_STRICT_PROTOTYPES = YES; 509 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 510 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 511 | CLANG_WARN_UNREACHABLE_CODE = YES; 512 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 513 | CODE_SIGN_IDENTITY = "Apple Development"; 514 | CODE_SIGN_STYLE = Manual; 515 | COPY_PHASE_STRIP = NO; 516 | DEBUG_INFORMATION_FORMAT = dwarf; 517 | DEVELOPMENT_TEAM = HRLUCP7QP4; 518 | ENABLE_STRICT_OBJC_MSGSEND = YES; 519 | ENABLE_TESTABILITY = YES; 520 | GCC_C_LANGUAGE_STANDARD = gnu11; 521 | GCC_DYNAMIC_NO_PIC = NO; 522 | GCC_NO_COMMON_BLOCKS = YES; 523 | GCC_OPTIMIZATION_LEVEL = 0; 524 | GCC_PREPROCESSOR_DEFINITIONS = ( 525 | "DEBUG=1", 526 | "$(inherited)", 527 | ); 528 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 529 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 530 | GCC_WARN_UNDECLARED_SELECTOR = YES; 531 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 532 | GCC_WARN_UNUSED_FUNCTION = YES; 533 | GCC_WARN_UNUSED_VARIABLE = YES; 534 | MACOSX_DEPLOYMENT_TARGET = 10.15; 535 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 536 | MTL_FAST_MATH = YES; 537 | ONLY_ACTIVE_ARCH = YES; 538 | SDKROOT = macosx; 539 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 540 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 541 | SWIFT_VERSION = 5.0; 542 | }; 543 | name = Debug; 544 | }; 545 | 2805EA45243A55F5008BD2DC /* Release */ = { 546 | isa = XCBuildConfiguration; 547 | baseConfigurationReference = 282146402443ED57008BD2DC /* CSAuthSample-Example.xcconfig */; 548 | buildSettings = { 549 | ALWAYS_SEARCH_USER_PATHS = NO; 550 | CLANG_ANALYZER_NONNULL = YES; 551 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 552 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 553 | CLANG_CXX_LIBRARY = "libc++"; 554 | CLANG_ENABLE_MODULES = YES; 555 | CLANG_ENABLE_OBJC_ARC = YES; 556 | CLANG_ENABLE_OBJC_WEAK = YES; 557 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 558 | CLANG_WARN_BOOL_CONVERSION = YES; 559 | CLANG_WARN_COMMA = YES; 560 | CLANG_WARN_CONSTANT_CONVERSION = YES; 561 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 562 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 563 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 564 | CLANG_WARN_EMPTY_BODY = YES; 565 | CLANG_WARN_ENUM_CONVERSION = YES; 566 | CLANG_WARN_INFINITE_RECURSION = YES; 567 | CLANG_WARN_INT_CONVERSION = YES; 568 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 569 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 570 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 571 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 572 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 573 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 574 | CLANG_WARN_STRICT_PROTOTYPES = YES; 575 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 576 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 577 | CLANG_WARN_UNREACHABLE_CODE = YES; 578 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 579 | CODE_SIGN_IDENTITY = "Apple Development"; 580 | CODE_SIGN_STYLE = Manual; 581 | COPY_PHASE_STRIP = NO; 582 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 583 | DEVELOPMENT_TEAM = HRLUCP7QP4; 584 | ENABLE_NS_ASSERTIONS = NO; 585 | ENABLE_STRICT_OBJC_MSGSEND = YES; 586 | GCC_C_LANGUAGE_STANDARD = gnu11; 587 | GCC_NO_COMMON_BLOCKS = YES; 588 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 589 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 590 | GCC_WARN_UNDECLARED_SELECTOR = YES; 591 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 592 | GCC_WARN_UNUSED_FUNCTION = YES; 593 | GCC_WARN_UNUSED_VARIABLE = YES; 594 | MACOSX_DEPLOYMENT_TARGET = 10.15; 595 | MTL_ENABLE_DEBUG_INFO = NO; 596 | MTL_FAST_MATH = YES; 597 | SDKROOT = macosx; 598 | SWIFT_COMPILATION_MODE = wholemodule; 599 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 600 | SWIFT_VERSION = 5.0; 601 | }; 602 | name = Release; 603 | }; 604 | 2805EA47243A55F5008BD2DC /* Debug */ = { 605 | isa = XCBuildConfiguration; 606 | buildSettings = { 607 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 608 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 609 | CODE_SIGN_ENTITLEMENTS = App/CSAuthSample_Example_App.entitlements; 610 | CODE_SIGN_IDENTITY = "-"; 611 | COMBINE_HIDPI_IMAGES = YES; 612 | ENABLE_PREVIEWS = YES; 613 | INFOPLIST_FILE = App/Info.plist; 614 | LD_RUNPATH_SEARCH_PATHS = ( 615 | "$(inherited)", 616 | "@executable_path/../Frameworks", 617 | ); 618 | MACOSX_DEPLOYMENT_TARGET = 10.15; 619 | PRODUCT_BUNDLE_IDENTIFIER = "$(APP_BUNDLE_ID)"; 620 | PRODUCT_NAME = "$(TARGET_NAME)"; 621 | }; 622 | name = Debug; 623 | }; 624 | 2805EA48243A55F5008BD2DC /* Release */ = { 625 | isa = XCBuildConfiguration; 626 | buildSettings = { 627 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 628 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 629 | CODE_SIGN_ENTITLEMENTS = App/CSAuthSample_Example_App.entitlements; 630 | CODE_SIGN_IDENTITY = "-"; 631 | COMBINE_HIDPI_IMAGES = YES; 632 | ENABLE_PREVIEWS = YES; 633 | INFOPLIST_FILE = App/Info.plist; 634 | LD_RUNPATH_SEARCH_PATHS = ( 635 | "$(inherited)", 636 | "@executable_path/../Frameworks", 637 | ); 638 | MACOSX_DEPLOYMENT_TARGET = 10.15; 639 | PRODUCT_BUNDLE_IDENTIFIER = "$(APP_BUNDLE_ID)"; 640 | PRODUCT_NAME = "$(TARGET_NAME)"; 641 | }; 642 | name = Release; 643 | }; 644 | 2805EA52243A563B008BD2DC /* Debug */ = { 645 | isa = XCBuildConfiguration; 646 | buildSettings = { 647 | CODE_SIGN_IDENTITY = "-"; 648 | OTHER_LDFLAGS = ( 649 | "-sectcreate", 650 | __TEXT, 651 | __info_plist, 652 | "\"$(DERIVED_FILE_DIR)/Info.plist\"", 653 | "-sectcreate", 654 | __TEXT, 655 | __launchd_plist, 656 | "\"$(DERIVED_FILE_DIR)/Launchd.plist\"", 657 | ); 658 | PRODUCT_NAME = "$(HELPER_ID)"; 659 | }; 660 | name = Debug; 661 | }; 662 | 2805EA53243A563B008BD2DC /* Release */ = { 663 | isa = XCBuildConfiguration; 664 | buildSettings = { 665 | CODE_SIGN_IDENTITY = "-"; 666 | OTHER_LDFLAGS = ( 667 | "-sectcreate", 668 | __TEXT, 669 | __info_plist, 670 | "\"$(DERIVED_FILE_DIR)/Info.plist\"", 671 | "-sectcreate", 672 | __TEXT, 673 | __launchd_plist, 674 | "\"$(DERIVED_FILE_DIR)/Launchd.plist\"", 675 | ); 676 | PRODUCT_NAME = "$(HELPER_ID)"; 677 | }; 678 | name = Release; 679 | }; 680 | 2805EA9B243A73FB008BD2DC /* Debug */ = { 681 | isa = XCBuildConfiguration; 682 | buildSettings = { 683 | CODE_SIGN_IDENTITY = "-"; 684 | COMBINE_HIDPI_IMAGES = YES; 685 | INFOPLIST_FILE = "XPC Service/Info.plist"; 686 | PRODUCT_BUNDLE_IDENTIFIER = "$(APP_BUNDLE_ID).XPCService"; 687 | PRODUCT_NAME = "$(TARGET_NAME)"; 688 | SKIP_INSTALL = YES; 689 | }; 690 | name = Debug; 691 | }; 692 | 2805EA9C243A73FB008BD2DC /* Release */ = { 693 | isa = XCBuildConfiguration; 694 | buildSettings = { 695 | CODE_SIGN_IDENTITY = "-"; 696 | COMBINE_HIDPI_IMAGES = YES; 697 | INFOPLIST_FILE = "XPC Service/Info.plist"; 698 | PRODUCT_BUNDLE_IDENTIFIER = "$(APP_BUNDLE_ID).XPCService"; 699 | PRODUCT_NAME = "$(TARGET_NAME)"; 700 | SKIP_INSTALL = YES; 701 | }; 702 | name = Release; 703 | }; 704 | /* End XCBuildConfiguration section */ 705 | 706 | /* Begin XCConfigurationList section */ 707 | 2805EA2E243A55F4008BD2DC /* Build configuration list for PBXProject "CSAuthSample Example App" */ = { 708 | isa = XCConfigurationList; 709 | buildConfigurations = ( 710 | 2805EA44243A55F5008BD2DC /* Debug */, 711 | 2805EA45243A55F5008BD2DC /* Release */, 712 | ); 713 | defaultConfigurationIsVisible = 0; 714 | defaultConfigurationName = Release; 715 | }; 716 | 2805EA46243A55F5008BD2DC /* Build configuration list for PBXNativeTarget "Example App" */ = { 717 | isa = XCConfigurationList; 718 | buildConfigurations = ( 719 | 2805EA47243A55F5008BD2DC /* Debug */, 720 | 2805EA48243A55F5008BD2DC /* Release */, 721 | ); 722 | defaultConfigurationIsVisible = 0; 723 | defaultConfigurationName = Release; 724 | }; 725 | 2805EA51243A563B008BD2DC /* Build configuration list for PBXNativeTarget "Example Helper Tool" */ = { 726 | isa = XCConfigurationList; 727 | buildConfigurations = ( 728 | 2805EA52243A563B008BD2DC /* Debug */, 729 | 2805EA53243A563B008BD2DC /* Release */, 730 | ); 731 | defaultConfigurationIsVisible = 0; 732 | defaultConfigurationName = Release; 733 | }; 734 | 2805EA9A243A73FB008BD2DC /* Build configuration list for PBXNativeTarget "Example XPC Service" */ = { 735 | isa = XCConfigurationList; 736 | buildConfigurations = ( 737 | 2805EA9B243A73FB008BD2DC /* Debug */, 738 | 2805EA9C243A73FB008BD2DC /* Release */, 739 | ); 740 | defaultConfigurationIsVisible = 0; 741 | defaultConfigurationName = Release; 742 | }; 743 | /* End XCConfigurationList section */ 744 | }; 745 | rootObject = 2805EA2B243A55F4008BD2DC /* Project object */; 746 | } 747 | -------------------------------------------------------------------------------- /CSAuthSample Example App/CSAuthSample Example App.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /CSAuthSample Example App/CSAuthSample Example App.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /CSAuthSample Example App/CSAuthSample Example App.xcodeproj/xcshareddata/xcschemes/Example App.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 11 | 14 | 15 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 33 | 39 | 40 | 41 | 42 | 43 | 48 | 49 | 50 | 51 | 61 | 63 | 69 | 70 | 71 | 72 | 78 | 80 | 86 | 87 | 88 | 89 | 91 | 92 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /CSAuthSample Example App/Common/Build Scripts/GlobalSetupScript.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GlobalSetupScript.swift 3 | // CSAuthSample Example App 4 | // 5 | // Created by Charles Srstka on 4/12/20. 6 | // 7 | 8 | import Foundation 9 | 10 | let env = ProcessInfo.processInfo.environment 11 | let versionVarName = "CURRENT_PROJECT_VERSION" 12 | 13 | let srcURL = URL(fileURLWithPath: env["SRCROOT"]!) 14 | let derivedFileDir = URL(fileURLWithPath: env["DERIVED_FILE_DIR"]!) 15 | let configURL = srcURL.appendingPathComponent("Common/Config/CSAuthSample-Example.xcconfig") 16 | 17 | let version = Int(env[versionVarName]!)! 18 | 19 | let newConfig = """ 20 | \(versionVarName) = \(version + 1) 21 | APP_BUNDLE_ID = \(Identifiers.appID) 22 | XPC_SERVICE_ID = \(Identifiers.xpcServiceID) 23 | HELPER_ID = \(Identifiers.helperID) 24 | CS_REQUIREMENT=\(getRequirement()) 25 | """ 26 | 27 | try! newConfig.write(to: configURL, atomically: true, encoding: .utf8) 28 | 29 | func getRequirement() -> String { 30 | // Is there any way to generate the designated requirement string without invoking the codesign tool? 31 | // This is a bit of a kludge, but it's the only way I've been find to do this so far other than hard-coding the 32 | // format that `codesign` uses 33 | 34 | let tempFileName = UUID().uuidString 35 | let tempFileURL = `derivedFileDir`.appendingPathComponent(tempFileName) 36 | 37 | try! Data().write(to: tempFileURL) 38 | defer { try! FileManager.default.removeItem(at: tempFileURL) } 39 | 40 | let codesign = Process() 41 | codesign.executableURL = URL(fileURLWithPath: "/usr/bin/codesign") 42 | codesign.arguments = ["-s", env["CODE_SIGN_IDENTITY"]!, "-i", "", tempFileURL.path] 43 | try! codesign.run() 44 | codesign.waitUntilExit() 45 | 46 | var code: SecStaticCode? 47 | assert(SecStaticCodeCreateWithPath(tempFileURL as CFURL, [], &code) == errSecSuccess) 48 | 49 | var requirement: SecRequirement? 50 | assert(SecCodeCopyDesignatedRequirement(code!, [], &requirement) == errSecSuccess) 51 | 52 | var cfRequirementString: CFString? 53 | assert(SecRequirementCopyString(requirement!, [], &cfRequirementString) == errSecSuccess) 54 | 55 | var requirementString = cfRequirementString! as String 56 | 57 | let identifierRange = requirementString.range(of: "identifier \"\(tempFileName)\" and ")! 58 | requirementString.replaceSubrange(identifierRange, with: "") 59 | 60 | return requirementString 61 | } 62 | -------------------------------------------------------------------------------- /CSAuthSample Example App/Common/Config/CSAuthSample-Example.xcconfig: -------------------------------------------------------------------------------- 1 | CURRENT_PROJECT_VERSION = 1 2 | -------------------------------------------------------------------------------- /CSAuthSample Example App/Common/ExampleCommandSet.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ExampleCommandSet.swift 3 | // Example App 4 | // 5 | // Created by Charles Srstka on 4/5/20. 6 | // Copyright © 2020-2021 Charles Srstka. All rights reserved. 7 | // 8 | 9 | import CSAuthSampleCommon 10 | import Foundation 11 | 12 | let exampleCommandSet: CommandSet = { 13 | let bundle = Bundle.main 14 | 15 | let sayHelloRightName = "com.charlessoft.CSAuthSample-Example.Say-Hello" 16 | let sayHelloPrompt = bundle.localizedString(forKey: "SayHello", value: nil, table: "Prompts") 17 | let sayHelloSelector = #selector(HelperToolProtocol.sayHello(authorizationData:message:reply:)) 18 | 19 | let rights = [ 20 | AuthorizationRight( 21 | selector: sayHelloSelector, 22 | name: sayHelloRightName, 23 | rule: kAuthorizationRuleAuthenticateAsAdmin, 24 | prompt: sayHelloPrompt 25 | ) 26 | ] 27 | 28 | return CommandSet(authorizationRights: rights) 29 | }() 30 | -------------------------------------------------------------------------------- /CSAuthSample Example App/Common/HelperToolProtocol.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HelperToolProtocol.swift 3 | // CSAuthSample Example App 4 | // 5 | // Created by Charles Srstka on 4/5/20. 6 | // 7 | 8 | import CSAuthSampleCommon 9 | import Foundation 10 | 11 | @objc protocol HelperToolProtocol: BuiltInCommands { 12 | func sayHello(authorizationData: Data, message: String, reply: @escaping (String?, Error?) -> Void) 13 | } 14 | -------------------------------------------------------------------------------- /CSAuthSample Example App/Common/Identifiers.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Identifiers.swift 3 | // CSAuthSample Example App 4 | // 5 | // Created by Charles Srstka on 4/5/20. 6 | // Copyright © 2020-2021 Charles Srstka. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct Identifiers { 12 | static let appID = "com.charlessoft.CSAuthSample-Example" 13 | static let helperID = "\(Identifiers.appID).helper" 14 | static let xpcServiceID = "\(Identifiers.appID).XPCService" 15 | } 16 | -------------------------------------------------------------------------------- /CSAuthSample Example App/Common/en.lproj/Prompts.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Prompts.strings 3 | CSAuthSample Example App 4 | 5 | Created by Charles Srstka on 4/5/20. 6 | Copyright © 2020-2021 Charles Srstka. All rights reserved. 7 | */ 8 | 9 | "SayHello" = "CSAuthSample Example App would like to say hello."; 10 | -------------------------------------------------------------------------------- /CSAuthSample Example App/Helper Tool/Build Scripts/SetupHelperPlists.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SetupHelperPlists.swift 3 | // CSAuthSample Example Helper Tool 4 | // 5 | // Created by Charles Srstka on 4/16/20. 6 | // 7 | 8 | import Foundation 9 | 10 | let env = ProcessInfo.processInfo.environment 11 | 12 | let helperID = env["HELPER_ID"]! 13 | let xpcServiceID = env["XPC_SERVICE_ID"]! 14 | 15 | let srcRoot = URL(fileURLWithPath: env["SRCROOT"]!) 16 | let derivedFileDir = URL(fileURLWithPath: env["DERIVED_FILE_DIR"]!) 17 | 18 | let infoSrcURL = srcRoot.appendingPathComponent("Helper Tool/Info.plist") 19 | let launchdSrcURL = srcRoot.appendingPathComponent("Helper Tool/Launchd.plist") 20 | 21 | let infoURL = derivedFileDir.appendingPathComponent("Info.plist") 22 | let launchdURL = derivedFileDir.appendingPathComponent("Launchd.plist") 23 | 24 | var info = NSDictionary(contentsOf: infoSrcURL) as! [String: Any] 25 | var launchd = NSDictionary(contentsOf: launchdSrcURL) as! [String: Any] 26 | 27 | info[kCFBundleVersionKey as String] = env["CURRENT_PROJECT_VERSION"]! 28 | info[kCFBundleIdentifierKey as String] = "\(helperID)" 29 | info["SMAuthorizedClients"] = ["identifier \"\(xpcServiceID)\" and \(env["CS_REQUIREMENT"]!)"] 30 | 31 | launchd["Label"] = helperID 32 | launchd["MachServices"] = [helperID: true] 33 | 34 | (info as NSDictionary).write(to: infoURL, atomically: true) 35 | (launchd as NSDictionary).write(to: launchdURL, atomically: true) 36 | -------------------------------------------------------------------------------- /CSAuthSample Example App/Helper Tool/HelperConnection.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HelperConnection.swift 3 | // Example Helper Tool 4 | // 5 | // Created by Charles Srstka on 4/15/20. 6 | // Copyright © 2020-2021 Charles Srstka. All rights reserved. 7 | // 8 | 9 | import CSAuthSampleHelper 10 | import Foundation 11 | 12 | class HelperConnection: CSAuthSampleHelper.HelperConnection, HelperToolProtocol { 13 | func sayHello(authorizationData: Data, message: String, reply: @escaping (String?, Error?) -> ()) { 14 | if let error = self.checkAuthorization(authorizationData) { 15 | reply(nil, error) 16 | return 17 | } 18 | 19 | let replyMessage = """ 20 | Received message from app: “\(message)” 21 | Sending reply to app: “Hello app! My UID is \(getuid()) and my GID is \(getgid())! 22 | """ 23 | 24 | reply(replyMessage, nil) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /CSAuthSample Example App/Helper Tool/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleName 6 | CSAuthSample Example App Helper 7 | CFBundleShortVersionString 8 | 1.0 9 | CFBundleVersion 10 | (set by build script) 11 | CFBundleIdentifier 12 | (set by build script) 13 | SMAuthorizedClients 14 | 15 | (set by build script) 16 | 17 | CFBundleInfoDictionaryVersion 18 | 6.0 19 | 20 | 21 | -------------------------------------------------------------------------------- /CSAuthSample Example App/Helper Tool/Launchd.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Label 6 | (set by build script) 7 | MachServices 8 | (set by build script) 9 | 10 | 11 | -------------------------------------------------------------------------------- /CSAuthSample Example App/Helper Tool/main.swift: -------------------------------------------------------------------------------- 1 | // 2 | // main.swift 3 | // CSAuthSample Example Helper Tool 4 | // 5 | // Created by Charles Srstka on 4/5/20. 6 | // 7 | 8 | import CSAuthSampleHelper 9 | import Foundation 10 | 11 | let helperTool = HelperTool( 12 | helperID: Identifiers.helperID, 13 | commandSet: exampleCommandSet, 14 | senderRequirements: nil, 15 | connectionClass: HelperConnection.self, 16 | interface: NSXPCInterface(with: HelperToolProtocol.self) 17 | ) 18 | 19 | helperTool.run() 20 | -------------------------------------------------------------------------------- /CSAuthSample Example App/XPC Service/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | Example App XPC Service 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSHumanReadableCopyright 24 | Free for public use 25 | SMPrivilegedExecutables 26 | 27 | com.charlessoft.CSAuthSample-Example.helper 28 | identifier "$(HELPER_ID)" and $(CS_REQUIREMENT) 29 | 30 | XPCService 31 | 32 | JoinExistingSession 33 | 34 | ServiceType 35 | Application 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /CSAuthSample Example App/XPC Service/ServiceDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ServiceDelegate.swift 3 | // Example XPC Service 4 | // 5 | // Created by Charles Srstka on 4/5/20. 6 | // Copyright © 2020-2021 Charles Srstka. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class ServiceDelegate: NSObject, NSXPCListenerDelegate { 12 | func listener(_ listener: NSXPCListener, shouldAcceptNewConnection connection: NSXPCConnection) -> Bool { 13 | // This method is where the NSXPCListener configures, accepts, and resumes a new incoming NSXPCConnection. 14 | 15 | // Configure the connection. 16 | // First, set the interface that the exported object implements. 17 | connection.exportedInterface = NSXPCInterface(with: XPCServiceProtocol.self) 18 | 19 | // Next, set the object that the connection exports. All messages sent on the connection to 20 | // this service will be sent to the exported object to handle. The connection retains the 21 | // exported object. 22 | let exportedObject = XPCService() 23 | connection.exportedObject = exportedObject 24 | 25 | // Resuming the connection allows the system to deliver more incoming messages. 26 | connection.resume() 27 | 28 | // Returning true from this method tells the system that you have accepted this connection. 29 | // If you want to reject the connection for some reason, call invalidate() on the connection 30 | // and return false. 31 | return true 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /CSAuthSample Example App/XPC Service/XPCService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // XPCService.swift 3 | // Example App XPC Service 4 | // 5 | // Created by Charles Srstka on 4/5/20. 6 | // 7 | 8 | import CSAuthSampleApp 9 | import Foundation 10 | 11 | // This object implements the protocol which we have defined. It provides the actual behavior for the service. 12 | // It is 'exported' by the service to make it available to the process hosting the service over an NSXPCConnection. 13 | class XPCService: NSObject, XPCServiceProtocol { 14 | private static let bundle = Bundle(for: XPCService.self) 15 | private static let bundleVersion = XPCService.bundle.infoDictionary![kCFBundleVersionKey as String] as! String 16 | 17 | func sayHello(message: String, reply: @escaping (String?, Error?) -> Void) { 18 | self.connectToHelperTool { 19 | switch $0 { 20 | case .success(let (proxy, authData)): 21 | proxy.sayHello(authorizationData: authData, message: message, reply: reply) 22 | case .failure(let error): 23 | reply(nil, error) 24 | } 25 | } 26 | } 27 | 28 | private func connectToHelperTool(closure: @escaping (Result<(HelperToolProtocol, Data), Error>) -> ()) { 29 | let client: HelperClient 30 | let authData: Data 31 | 32 | do { 33 | client = try self.getClient() 34 | authData = try client.authorizationData() 35 | } catch { 36 | closure(.failure(error)) 37 | return 38 | } 39 | 40 | client.connectToHelperTool( 41 | helperID: Identifiers.helperID, 42 | protocol: HelperToolProtocol.self, 43 | expectedVersion: XPCService.bundleVersion, 44 | errorHandler: { closure(.failure($0)) }, 45 | connectionHandler: { closure(.success(($0, authData))) } 46 | ) 47 | } 48 | 49 | private func getClient() throws -> HelperClient { 50 | try HelperClient(commandSet: exampleCommandSet, bundle: .main, tableName: "Prompts") 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /CSAuthSample Example App/XPC Service/XPCServiceProtocol.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Example_App_XPC_ServiceProtocol.swift 3 | // Example App XPC Service 4 | // 5 | // Created by Charles Srstka on 4/5/20. 6 | // 7 | 8 | import Foundation 9 | 10 | @objc protocol XPCServiceProtocol { 11 | func sayHello(message: String, reply: @escaping (String?, Error?) -> Void) 12 | } 13 | -------------------------------------------------------------------------------- /CSAuthSample Example App/XPC Service/main.swift: -------------------------------------------------------------------------------- 1 | // 2 | // main.swift 3 | // Example App XPC Service 4 | // 5 | // Created by Charles Srstka on 4/5/20. 6 | // 7 | 8 | import Foundation 9 | 10 | // Create the delegate for the service. 11 | let delegate = ServiceDelegate() 12 | 13 | // Set up the one NSXPCListener for this service. It will handle all incoming connections. 14 | let listener = NSXPCListener.service() 15 | listener.delegate = delegate 16 | 17 | // Resuming the serviceListener starts this service. This method does not return. 18 | listener.resume() 19 | -------------------------------------------------------------------------------- /CSAuthSample.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 281ACABD20E962740006B59B /* CSASHelperConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = 281ACABA20E960A50006B59B /* CSASHelperConnection.m */; }; 11 | 281ACABE20E9672B0006B59B /* CSASHelperConnection.h in Headers */ = {isa = PBXBuildFile; fileRef = 281ACAB920E960A50006B59B /* CSASHelperConnection.h */; }; 12 | 281ACACF20E967D70006B59B /* CSASHelperConnection.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 281ACAB920E960A50006B59B /* CSASHelperConnection.h */; }; 13 | 2835DB8F20E13DDA0006B59B /* HelperClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2835DB5220E13CFB0006B59B /* HelperClient.swift */; }; 14 | 2835DB9120E13DF60006B59B /* CSASHelperTool.m in Sources */ = {isa = PBXBuildFile; fileRef = 2835DB9020E13DF60006B59B /* CSASHelperTool.m */; }; 15 | 2835DB9520E13E350006B59B /* CSASCommon.h in Headers */ = {isa = PBXBuildFile; fileRef = 2835DB9320E13E350006B59B /* CSASCommon.h */; }; 16 | 2835DB9620E13E350006B59B /* CSASCommon.m in Sources */ = {isa = PBXBuildFile; fileRef = 2835DB9420E13E350006B59B /* CSASCommon.m */; }; 17 | 2835DB9720E13E380006B59B /* CSASCommon.m in Sources */ = {isa = PBXBuildFile; fileRef = 2835DB9420E13E350006B59B /* CSASCommon.m */; }; 18 | 2835DBC920E169C00006B59B /* CSASHelperTool.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 2835DB9220E13E250006B59B /* CSASHelperTool.h */; }; 19 | 2835DBCA20E169C00006B59B /* CSASCommon.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 2835DB9320E13E350006B59B /* CSASCommon.h */; }; 20 | 287DBE80214AF5370006B59B /* SandboxWorkaround.swift in Sources */ = {isa = PBXBuildFile; fileRef = 287DBE7E214AF0CE0006B59B /* SandboxWorkaround.swift */; }; 21 | 28E085F6244CC842008BD2DC /* CSASHelperConnectionWrapper.m in Sources */ = {isa = PBXBuildFile; fileRef = 28E085F4244CC842008BD2DC /* CSASHelperConnectionWrapper.m */; }; 22 | 28E085F7244CC842008BD2DC /* CSASHelperConnectionWrapper.h in Headers */ = {isa = PBXBuildFile; fileRef = 28E085F5244CC842008BD2DC /* CSASHelperConnectionWrapper.h */; }; 23 | /* End PBXBuildFile section */ 24 | 25 | /* Begin PBXContainerItemProxy section */ 26 | 28C77306214DAEFB0006B59B /* PBXContainerItemProxy */ = { 27 | isa = PBXContainerItemProxy; 28 | containerPortal = 29B97313FDCFA39411CA2CEA /* Project object */; 29 | proxyType = 1; 30 | remoteGlobalIDString = 2835DB8520E13DD30006B59B; 31 | remoteInfo = "Helper Library"; 32 | }; 33 | /* End PBXContainerItemProxy section */ 34 | 35 | /* Begin PBXCopyFilesBuildPhase section */ 36 | 2835DBC820E169AC0006B59B /* CopyFiles */ = { 37 | isa = PBXCopyFilesBuildPhase; 38 | buildActionMask = 2147483647; 39 | dstPath = "include/$(PRODUCT_NAME)"; 40 | dstSubfolderSpec = 16; 41 | files = ( 42 | 281ACACF20E967D70006B59B /* CSASHelperConnection.h in CopyFiles */, 43 | 2835DBC920E169C00006B59B /* CSASHelperTool.h in CopyFiles */, 44 | 2835DBCA20E169C00006B59B /* CSASCommon.h in CopyFiles */, 45 | ); 46 | runOnlyForDeploymentPostprocessing = 0; 47 | }; 48 | /* End PBXCopyFilesBuildPhase section */ 49 | 50 | /* Begin PBXFileReference section */ 51 | 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = /System/Library/Frameworks/Cocoa.framework; sourceTree = ""; }; 52 | 13E42FB307B3F0F600E4EEF1 /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = /System/Library/Frameworks/CoreData.framework; sourceTree = ""; }; 53 | 2805E9EF243A48D8008BD2DC /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = ""; }; 54 | 2805E9F1243A4F11008BD2DC /* Package.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Package.swift; sourceTree = ""; }; 55 | 281ACAB920E960A50006B59B /* CSASHelperConnection.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CSASHelperConnection.h; sourceTree = ""; }; 56 | 281ACABA20E960A50006B59B /* CSASHelperConnection.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CSASHelperConnection.m; sourceTree = ""; }; 57 | 2835DB5220E13CFB0006B59B /* HelperClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HelperClient.swift; sourceTree = ""; }; 58 | 2835DB5820E13D720006B59B /* libCSAuthSampleApp.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libCSAuthSampleApp.a; sourceTree = BUILT_PRODUCTS_DIR; }; 59 | 2835DB8620E13DD30006B59B /* libCSAuthSampleHelper.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libCSAuthSampleHelper.a; sourceTree = BUILT_PRODUCTS_DIR; }; 60 | 2835DB9020E13DF60006B59B /* CSASHelperTool.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CSASHelperTool.m; sourceTree = ""; }; 61 | 2835DB9220E13E250006B59B /* CSASHelperTool.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CSASHelperTool.h; sourceTree = ""; }; 62 | 2835DB9320E13E350006B59B /* CSASCommon.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CSASCommon.h; sourceTree = ""; }; 63 | 2835DB9420E13E350006B59B /* CSASCommon.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CSASCommon.m; sourceTree = ""; }; 64 | 2835DBC420E165A70006B59B /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = ""; }; 65 | 287DBE7E214AF0CE0006B59B /* SandboxWorkaround.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SandboxWorkaround.swift; sourceTree = ""; }; 66 | 289E508420F50B6F0006B59B /* CSASHelperToolInternal.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CSASHelperToolInternal.h; sourceTree = ""; }; 67 | 28A6F66B16918ECD006F0A93 /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = ../../../../System/Library/Frameworks/CoreFoundation.framework; sourceTree = ""; }; 68 | 28C7733F214DB4CE0006B59B /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 69 | 28E085F4244CC842008BD2DC /* CSASHelperConnectionWrapper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CSASHelperConnectionWrapper.m; sourceTree = ""; }; 70 | 28E085F5244CC842008BD2DC /* CSASHelperConnectionWrapper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSASHelperConnectionWrapper.h; sourceTree = ""; }; 71 | 28E085F8244CD5EB008BD2DC /* CSASHelperConnectionInternal.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CSASHelperConnectionInternal.h; sourceTree = ""; }; 72 | 28F16E4C25FEEB1C00370D24 /* .swift-format */ = {isa = PBXFileReference; lastKnownFileType = text; path = ".swift-format"; sourceTree = ""; }; 73 | 29B97324FDCFA39411CA2CEA /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = /System/Library/Frameworks/AppKit.framework; sourceTree = ""; }; 74 | 29B97325FDCFA39411CA2CEA /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = ""; }; 75 | 4BE4906310445F2F006BE471 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; 76 | 4BE4906710445F36006BE471 /* ServiceManagement.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ServiceManagement.framework; path = System/Library/Frameworks/ServiceManagement.framework; sourceTree = SDKROOT; }; 77 | /* End PBXFileReference section */ 78 | 79 | /* Begin PBXFrameworksBuildPhase section */ 80 | 2835DB5620E13D720006B59B /* Frameworks */ = { 81 | isa = PBXFrameworksBuildPhase; 82 | buildActionMask = 2147483647; 83 | files = ( 84 | ); 85 | runOnlyForDeploymentPostprocessing = 0; 86 | }; 87 | 2835DB8420E13DD30006B59B /* Frameworks */ = { 88 | isa = PBXFrameworksBuildPhase; 89 | buildActionMask = 2147483647; 90 | files = ( 91 | ); 92 | runOnlyForDeploymentPostprocessing = 0; 93 | }; 94 | /* End PBXFrameworksBuildPhase section */ 95 | 96 | /* Begin PBXGroup section */ 97 | 1058C7A0FEA54F0111CA2CBB /* Linked Frameworks */ = { 98 | isa = PBXGroup; 99 | children = ( 100 | 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */, 101 | 28A6F66B16918ECD006F0A93 /* CoreFoundation.framework */, 102 | 4BE4906310445F2F006BE471 /* Security.framework */, 103 | 4BE4906710445F36006BE471 /* ServiceManagement.framework */, 104 | ); 105 | name = "Linked Frameworks"; 106 | sourceTree = ""; 107 | }; 108 | 1058C7A2FEA54F0111CA2CBB /* Other Frameworks */ = { 109 | isa = PBXGroup; 110 | children = ( 111 | 29B97324FDCFA39411CA2CEA /* AppKit.framework */, 112 | 13E42FB307B3F0F600E4EEF1 /* CoreData.framework */, 113 | 29B97325FDCFA39411CA2CEA /* Foundation.framework */, 114 | ); 115 | name = "Other Frameworks"; 116 | sourceTree = ""; 117 | }; 118 | 19C28FACFE9D520D11CA2CBB /* Products */ = { 119 | isa = PBXGroup; 120 | children = ( 121 | 2835DB5820E13D720006B59B /* libCSAuthSampleApp.a */, 122 | 2835DB8620E13DD30006B59B /* libCSAuthSampleHelper.a */, 123 | ); 124 | name = Products; 125 | sourceTree = ""; 126 | }; 127 | 2805E9EB243A4829008BD2DC /* Sources */ = { 128 | isa = PBXGroup; 129 | children = ( 130 | 2805E9EC243A4831008BD2DC /* CSAuthSampleApp */, 131 | 2805E9ED243A483D008BD2DC /* CSAuthSampleHelper */, 132 | 2805E9EE243A4852008BD2DC /* CSAuthSampleCommon */, 133 | ); 134 | path = Sources; 135 | sourceTree = ""; 136 | }; 137 | 2805E9EC243A4831008BD2DC /* CSAuthSampleApp */ = { 138 | isa = PBXGroup; 139 | children = ( 140 | 2835DB5220E13CFB0006B59B /* HelperClient.swift */, 141 | 287DBE7E214AF0CE0006B59B /* SandboxWorkaround.swift */, 142 | ); 143 | path = CSAuthSampleApp; 144 | sourceTree = ""; 145 | }; 146 | 2805E9ED243A483D008BD2DC /* CSAuthSampleHelper */ = { 147 | isa = PBXGroup; 148 | children = ( 149 | 2805E9F2243A5061008BD2DC /* include */, 150 | 289E508420F50B6F0006B59B /* CSASHelperToolInternal.h */, 151 | 2835DB9020E13DF60006B59B /* CSASHelperTool.m */, 152 | 28E085F8244CD5EB008BD2DC /* CSASHelperConnectionInternal.h */, 153 | 281ACABA20E960A50006B59B /* CSASHelperConnection.m */, 154 | 28E085F5244CC842008BD2DC /* CSASHelperConnectionWrapper.h */, 155 | 28E085F4244CC842008BD2DC /* CSASHelperConnectionWrapper.m */, 156 | ); 157 | path = CSAuthSampleHelper; 158 | sourceTree = ""; 159 | }; 160 | 2805E9EE243A4852008BD2DC /* CSAuthSampleCommon */ = { 161 | isa = PBXGroup; 162 | children = ( 163 | 2805E9F3243A5085008BD2DC /* include */, 164 | 2835DB9420E13E350006B59B /* CSASCommon.m */, 165 | ); 166 | path = CSAuthSampleCommon; 167 | sourceTree = ""; 168 | }; 169 | 2805E9F2243A5061008BD2DC /* include */ = { 170 | isa = PBXGroup; 171 | children = ( 172 | 2835DBC420E165A70006B59B /* module.modulemap */, 173 | 2835DB9220E13E250006B59B /* CSASHelperTool.h */, 174 | 281ACAB920E960A50006B59B /* CSASHelperConnection.h */, 175 | ); 176 | path = include; 177 | sourceTree = ""; 178 | }; 179 | 2805E9F3243A5085008BD2DC /* include */ = { 180 | isa = PBXGroup; 181 | children = ( 182 | 2805E9EF243A48D8008BD2DC /* module.modulemap */, 183 | 2835DB9320E13E350006B59B /* CSASCommon.h */, 184 | ); 185 | path = include; 186 | sourceTree = ""; 187 | }; 188 | 29B97314FDCFA39411CA2CEA /* SMJobBless */ = { 189 | isa = PBXGroup; 190 | children = ( 191 | 28C7733F214DB4CE0006B59B /* README.md */, 192 | 2805E9F1243A4F11008BD2DC /* Package.swift */, 193 | 28F16E4C25FEEB1C00370D24 /* .swift-format */, 194 | 2805E9EB243A4829008BD2DC /* Sources */, 195 | 29B97323FDCFA39411CA2CEA /* Frameworks */, 196 | 19C28FACFE9D520D11CA2CBB /* Products */, 197 | ); 198 | name = SMJobBless; 199 | sourceTree = ""; 200 | }; 201 | 29B97323FDCFA39411CA2CEA /* Frameworks */ = { 202 | isa = PBXGroup; 203 | children = ( 204 | 1058C7A0FEA54F0111CA2CBB /* Linked Frameworks */, 205 | 1058C7A2FEA54F0111CA2CBB /* Other Frameworks */, 206 | ); 207 | name = Frameworks; 208 | sourceTree = ""; 209 | }; 210 | /* End PBXGroup section */ 211 | 212 | /* Begin PBXHeadersBuildPhase section */ 213 | 2835DB5420E13D720006B59B /* Headers */ = { 214 | isa = PBXHeadersBuildPhase; 215 | buildActionMask = 2147483647; 216 | files = ( 217 | ); 218 | runOnlyForDeploymentPostprocessing = 0; 219 | }; 220 | 2835DB8220E13DD30006B59B /* Headers */ = { 221 | isa = PBXHeadersBuildPhase; 222 | buildActionMask = 2147483647; 223 | files = ( 224 | 281ACABE20E9672B0006B59B /* CSASHelperConnection.h in Headers */, 225 | 28E085F7244CC842008BD2DC /* CSASHelperConnectionWrapper.h in Headers */, 226 | 2835DB9520E13E350006B59B /* CSASCommon.h in Headers */, 227 | ); 228 | runOnlyForDeploymentPostprocessing = 0; 229 | }; 230 | /* End PBXHeadersBuildPhase section */ 231 | 232 | /* Begin PBXNativeTarget section */ 233 | 2835DB5720E13D720006B59B /* App Library */ = { 234 | isa = PBXNativeTarget; 235 | buildConfigurationList = 2835DB5E20E13D720006B59B /* Build configuration list for PBXNativeTarget "App Library" */; 236 | buildPhases = ( 237 | 2835DB5420E13D720006B59B /* Headers */, 238 | 2835DB5520E13D720006B59B /* Sources */, 239 | 2835DB5620E13D720006B59B /* Frameworks */, 240 | ); 241 | buildRules = ( 242 | ); 243 | dependencies = ( 244 | 28C77307214DAEFB0006B59B /* PBXTargetDependency */, 245 | ); 246 | name = "App Library"; 247 | productName = "App Library (ObjC Helper)"; 248 | productReference = 2835DB5820E13D720006B59B /* libCSAuthSampleApp.a */; 249 | productType = "com.apple.product-type.library.static"; 250 | }; 251 | 2835DB8520E13DD30006B59B /* Helper Library */ = { 252 | isa = PBXNativeTarget; 253 | buildConfigurationList = 2835DB8C20E13DD30006B59B /* Build configuration list for PBXNativeTarget "Helper Library" */; 254 | buildPhases = ( 255 | 2835DB8220E13DD30006B59B /* Headers */, 256 | 2835DBC820E169AC0006B59B /* CopyFiles */, 257 | 2835DB8320E13DD30006B59B /* Sources */, 258 | 2835DB8420E13DD30006B59B /* Frameworks */, 259 | ); 260 | buildRules = ( 261 | ); 262 | dependencies = ( 263 | ); 264 | name = "Helper Library"; 265 | productName = "ObjC Helper Library"; 266 | productReference = 2835DB8620E13DD30006B59B /* libCSAuthSampleHelper.a */; 267 | productType = "com.apple.product-type.library.static"; 268 | }; 269 | /* End PBXNativeTarget section */ 270 | 271 | /* Begin PBXProject section */ 272 | 29B97313FDCFA39411CA2CEA /* Project object */ = { 273 | isa = PBXProject; 274 | attributes = { 275 | LastUpgradeCheck = 1400; 276 | TargetAttributes = { 277 | 2835DB5720E13D720006B59B = { 278 | CreatedOnToolsVersion = 10.0; 279 | LastSwiftMigration = 1020; 280 | }; 281 | 2835DB8520E13DD30006B59B = { 282 | CreatedOnToolsVersion = 10.0; 283 | }; 284 | }; 285 | }; 286 | buildConfigurationList = C01FCF4E08A954540054247B /* Build configuration list for PBXProject "CSAuthSample" */; 287 | compatibilityVersion = "Xcode 9.3"; 288 | developmentRegion = en; 289 | hasScannedForEncodings = 1; 290 | knownRegions = ( 291 | en, 292 | Base, 293 | ); 294 | mainGroup = 29B97314FDCFA39411CA2CEA /* SMJobBless */; 295 | packageReferences = ( 296 | ); 297 | projectDirPath = ""; 298 | projectRoot = ""; 299 | targets = ( 300 | 2835DB5720E13D720006B59B /* App Library */, 301 | 2835DB8520E13DD30006B59B /* Helper Library */, 302 | ); 303 | }; 304 | /* End PBXProject section */ 305 | 306 | /* Begin PBXSourcesBuildPhase section */ 307 | 2835DB5520E13D720006B59B /* Sources */ = { 308 | isa = PBXSourcesBuildPhase; 309 | buildActionMask = 2147483647; 310 | files = ( 311 | 2835DB9720E13E380006B59B /* CSASCommon.m in Sources */, 312 | 287DBE80214AF5370006B59B /* SandboxWorkaround.swift in Sources */, 313 | 2835DB8F20E13DDA0006B59B /* HelperClient.swift in Sources */, 314 | ); 315 | runOnlyForDeploymentPostprocessing = 0; 316 | }; 317 | 2835DB8320E13DD30006B59B /* Sources */ = { 318 | isa = PBXSourcesBuildPhase; 319 | buildActionMask = 2147483647; 320 | files = ( 321 | 281ACABD20E962740006B59B /* CSASHelperConnection.m in Sources */, 322 | 2835DB9620E13E350006B59B /* CSASCommon.m in Sources */, 323 | 2835DB9120E13DF60006B59B /* CSASHelperTool.m in Sources */, 324 | 28E085F6244CC842008BD2DC /* CSASHelperConnectionWrapper.m in Sources */, 325 | ); 326 | runOnlyForDeploymentPostprocessing = 0; 327 | }; 328 | /* End PBXSourcesBuildPhase section */ 329 | 330 | /* Begin PBXTargetDependency section */ 331 | 28C77307214DAEFB0006B59B /* PBXTargetDependency */ = { 332 | isa = PBXTargetDependency; 333 | target = 2835DB8520E13DD30006B59B /* Helper Library */; 334 | targetProxy = 28C77306214DAEFB0006B59B /* PBXContainerItemProxy */; 335 | }; 336 | /* End PBXTargetDependency section */ 337 | 338 | /* Begin XCBuildConfiguration section */ 339 | 2835DB5F20E13D720006B59B /* Debug */ = { 340 | isa = XCBuildConfiguration; 341 | buildSettings = { 342 | ALWAYS_SEARCH_USER_PATHS = NO; 343 | CLANG_ANALYZER_NONNULL = YES; 344 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 345 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 346 | CLANG_CXX_LIBRARY = "libc++"; 347 | CLANG_ENABLE_MODULES = YES; 348 | CLANG_ENABLE_OBJC_ARC = YES; 349 | CLANG_ENABLE_OBJC_WEAK = YES; 350 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 351 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 352 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 353 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 354 | CODE_SIGN_IDENTITY = "Apple Development"; 355 | CODE_SIGN_STYLE = Automatic; 356 | COPY_PHASE_STRIP = NO; 357 | DEAD_CODE_STRIPPING = YES; 358 | DEBUG_INFORMATION_FORMAT = dwarf; 359 | DEFINES_MODULE = YES; 360 | EXECUTABLE_PREFIX = lib; 361 | GCC_C_LANGUAGE_STANDARD = gnu11; 362 | GCC_DYNAMIC_NO_PIC = NO; 363 | GCC_PREPROCESSOR_DEFINITIONS = ( 364 | "DEBUG=1", 365 | "$(inherited)", 366 | ); 367 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 368 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 369 | HEADER_SEARCH_PATHS = $SRCROOT/Sources/CSAuthSampleCommon/include; 370 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 371 | PRODUCT_MODULE_NAME = CSAuthSampleApp; 372 | PRODUCT_NAME = CSAuthSampleApp; 373 | SDKROOT = macosx; 374 | SKIP_INSTALL = YES; 375 | SWIFT_INCLUDE_PATHS = $SRCROOT/Sources/Common; 376 | SWIFT_VERSION = 5.0; 377 | USER_HEADER_SEARCH_PATHS = ( 378 | $TARGET_BUILD_DIR/include/CSAuthSampleHelper, 379 | $SRCROOT/ObjCHelper/Internal, 380 | ); 381 | }; 382 | name = Debug; 383 | }; 384 | 2835DB6020E13D720006B59B /* Release */ = { 385 | isa = XCBuildConfiguration; 386 | buildSettings = { 387 | ALWAYS_SEARCH_USER_PATHS = NO; 388 | CLANG_ANALYZER_NONNULL = YES; 389 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 390 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 391 | CLANG_CXX_LIBRARY = "libc++"; 392 | CLANG_ENABLE_MODULES = YES; 393 | CLANG_ENABLE_OBJC_ARC = YES; 394 | CLANG_ENABLE_OBJC_WEAK = YES; 395 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 396 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 397 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 398 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 399 | CODE_SIGN_IDENTITY = "Apple Development"; 400 | CODE_SIGN_STYLE = Automatic; 401 | COPY_PHASE_STRIP = NO; 402 | DEAD_CODE_STRIPPING = YES; 403 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 404 | DEFINES_MODULE = YES; 405 | ENABLE_NS_ASSERTIONS = NO; 406 | EXECUTABLE_PREFIX = lib; 407 | GCC_C_LANGUAGE_STANDARD = gnu11; 408 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 409 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 410 | HEADER_SEARCH_PATHS = $SRCROOT/Sources/CSAuthSampleCommon/include; 411 | MTL_ENABLE_DEBUG_INFO = NO; 412 | PRODUCT_MODULE_NAME = CSAuthSampleApp; 413 | PRODUCT_NAME = CSAuthSampleApp; 414 | SDKROOT = macosx; 415 | SKIP_INSTALL = YES; 416 | SWIFT_INCLUDE_PATHS = $SRCROOT/Sources/Common; 417 | SWIFT_VERSION = 5.0; 418 | USER_HEADER_SEARCH_PATHS = ( 419 | $TARGET_BUILD_DIR/include/CSAuthSampleHelper, 420 | $SRCROOT/ObjCHelper/Internal, 421 | ); 422 | }; 423 | name = Release; 424 | }; 425 | 2835DB8D20E13DD30006B59B /* Debug */ = { 426 | isa = XCBuildConfiguration; 427 | buildSettings = { 428 | ALWAYS_SEARCH_USER_PATHS = NO; 429 | CLANG_ANALYZER_NONNULL = YES; 430 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 431 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 432 | CLANG_CXX_LIBRARY = "libc++"; 433 | CLANG_ENABLE_MODULES = YES; 434 | CLANG_ENABLE_OBJC_ARC = YES; 435 | CLANG_ENABLE_OBJC_WEAK = YES; 436 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 437 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 438 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 439 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 440 | CODE_SIGN_IDENTITY = "-"; 441 | CODE_SIGN_STYLE = Automatic; 442 | COPY_PHASE_STRIP = NO; 443 | DEAD_CODE_STRIPPING = YES; 444 | DEBUG_INFORMATION_FORMAT = dwarf; 445 | DEFINES_MODULE = YES; 446 | EXECUTABLE_PREFIX = lib; 447 | GCC_C_LANGUAGE_STANDARD = gnu11; 448 | GCC_DYNAMIC_NO_PIC = NO; 449 | GCC_PREPROCESSOR_DEFINITIONS = ( 450 | "DEBUG=1", 451 | "$(inherited)", 452 | ); 453 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 454 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 455 | HEADER_SEARCH_PATHS = ( 456 | $SRCROOT/Sources/CSAuthSampleCommon/include, 457 | $SRCROOT/Sources/CSAuthSampleHelper/include, 458 | ); 459 | MODULEMAP_FILE = $SRCROOT/Sources/CSAuthSampleHelper/include/module.modulemap; 460 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 461 | PRODUCT_MODULE_NAME = CSAuthSampleHelper; 462 | PRODUCT_NAME = CSAuthSampleHelper; 463 | SDKROOT = macosx; 464 | SKIP_INSTALL = YES; 465 | }; 466 | name = Debug; 467 | }; 468 | 2835DB8E20E13DD30006B59B /* Release */ = { 469 | isa = XCBuildConfiguration; 470 | buildSettings = { 471 | ALWAYS_SEARCH_USER_PATHS = NO; 472 | CLANG_ANALYZER_NONNULL = YES; 473 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 474 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 475 | CLANG_CXX_LIBRARY = "libc++"; 476 | CLANG_ENABLE_MODULES = YES; 477 | CLANG_ENABLE_OBJC_ARC = YES; 478 | CLANG_ENABLE_OBJC_WEAK = YES; 479 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 480 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 481 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 482 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 483 | CODE_SIGN_IDENTITY = "-"; 484 | CODE_SIGN_STYLE = Automatic; 485 | COPY_PHASE_STRIP = NO; 486 | DEAD_CODE_STRIPPING = YES; 487 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 488 | DEFINES_MODULE = YES; 489 | ENABLE_NS_ASSERTIONS = NO; 490 | EXECUTABLE_PREFIX = lib; 491 | GCC_C_LANGUAGE_STANDARD = gnu11; 492 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 493 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 494 | HEADER_SEARCH_PATHS = ( 495 | $SRCROOT/Sources/CSAuthSampleCommon/include, 496 | $SRCROOT/Sources/CSAuthSampleHelper/include, 497 | ); 498 | MODULEMAP_FILE = $SRCROOT/Sources/CSAuthSampleHelper/include/module.modulemap; 499 | MTL_ENABLE_DEBUG_INFO = NO; 500 | PRODUCT_MODULE_NAME = CSAuthSampleHelper; 501 | PRODUCT_NAME = CSAuthSampleHelper; 502 | SDKROOT = macosx; 503 | SKIP_INSTALL = YES; 504 | }; 505 | name = Release; 506 | }; 507 | C01FCF4F08A954540054247B /* Debug */ = { 508 | isa = XCBuildConfiguration; 509 | buildSettings = { 510 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 511 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 512 | CLANG_WARN_BOOL_CONVERSION = YES; 513 | CLANG_WARN_COMMA = YES; 514 | CLANG_WARN_CONSTANT_CONVERSION = YES; 515 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 516 | CLANG_WARN_EMPTY_BODY = YES; 517 | CLANG_WARN_ENUM_CONVERSION = YES; 518 | CLANG_WARN_INFINITE_RECURSION = YES; 519 | CLANG_WARN_INT_CONVERSION = YES; 520 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 521 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 522 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 523 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 524 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 525 | CLANG_WARN_STRICT_PROTOTYPES = YES; 526 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 527 | CLANG_WARN_UNREACHABLE_CODE = YES; 528 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 529 | CODE_SIGN_IDENTITY = "Mac Developer"; 530 | DEAD_CODE_STRIPPING = YES; 531 | ENABLE_STRICT_OBJC_MSGSEND = YES; 532 | ENABLE_TESTABILITY = YES; 533 | GCC_C_LANGUAGE_STANDARD = gnu99; 534 | GCC_NO_COMMON_BLOCKS = YES; 535 | GCC_OPTIMIZATION_LEVEL = 0; 536 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 537 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 538 | GCC_WARN_UNDECLARED_SELECTOR = YES; 539 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 540 | GCC_WARN_UNUSED_FUNCTION = YES; 541 | GCC_WARN_UNUSED_VARIABLE = YES; 542 | MACOSX_DEPLOYMENT_TARGET = 10.13; 543 | ONLY_ACTIVE_ARCH = YES; 544 | PROVISIONING_PROFILE = ""; 545 | SKIP_INSTALL = YES; 546 | SWIFT_COMPILATION_MODE = singlefile; 547 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 548 | SWIFT_VERSION = 4.2; 549 | WARNING_CFLAGS = ( 550 | "-Weverything", 551 | "-Werror", 552 | "-Wno-objc-missing-property-synthesis", 553 | "-Wno-pedantic", 554 | "-Wno-documentation", 555 | "-Wno-direct-ivar-access", 556 | "-Wno-assign-enum", 557 | "-Wno-cast-qual", 558 | ); 559 | }; 560 | name = Debug; 561 | }; 562 | C01FCF5008A954540054247B /* Release */ = { 563 | isa = XCBuildConfiguration; 564 | buildSettings = { 565 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 566 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 567 | CLANG_WARN_BOOL_CONVERSION = YES; 568 | CLANG_WARN_COMMA = YES; 569 | CLANG_WARN_CONSTANT_CONVERSION = YES; 570 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 571 | CLANG_WARN_EMPTY_BODY = YES; 572 | CLANG_WARN_ENUM_CONVERSION = YES; 573 | CLANG_WARN_INFINITE_RECURSION = YES; 574 | CLANG_WARN_INT_CONVERSION = YES; 575 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 576 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 577 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 578 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 579 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 580 | CLANG_WARN_STRICT_PROTOTYPES = YES; 581 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 582 | CLANG_WARN_UNREACHABLE_CODE = YES; 583 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 584 | CODE_SIGN_IDENTITY = "Mac Developer"; 585 | DEAD_CODE_STRIPPING = YES; 586 | ENABLE_STRICT_OBJC_MSGSEND = YES; 587 | GCC_C_LANGUAGE_STANDARD = gnu99; 588 | GCC_NO_COMMON_BLOCKS = YES; 589 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 590 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 591 | GCC_WARN_UNDECLARED_SELECTOR = YES; 592 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 593 | GCC_WARN_UNUSED_FUNCTION = YES; 594 | GCC_WARN_UNUSED_VARIABLE = YES; 595 | MACOSX_DEPLOYMENT_TARGET = 10.13; 596 | PROVISIONING_PROFILE = ""; 597 | SKIP_INSTALL = YES; 598 | SWIFT_COMPILATION_MODE = wholemodule; 599 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 600 | SWIFT_VERSION = 4.2; 601 | WARNING_CFLAGS = ( 602 | "-Weverything", 603 | "-Werror", 604 | "-Wno-objc-missing-property-synthesis", 605 | "-Wno-pedantic", 606 | "-Wno-documentation", 607 | "-Wno-direct-ivar-access", 608 | "-Wno-assign-enum", 609 | "-Wno-cast-qual", 610 | ); 611 | }; 612 | name = Release; 613 | }; 614 | /* End XCBuildConfiguration section */ 615 | 616 | /* Begin XCConfigurationList section */ 617 | 2835DB5E20E13D720006B59B /* Build configuration list for PBXNativeTarget "App Library" */ = { 618 | isa = XCConfigurationList; 619 | buildConfigurations = ( 620 | 2835DB5F20E13D720006B59B /* Debug */, 621 | 2835DB6020E13D720006B59B /* Release */, 622 | ); 623 | defaultConfigurationIsVisible = 0; 624 | defaultConfigurationName = Release; 625 | }; 626 | 2835DB8C20E13DD30006B59B /* Build configuration list for PBXNativeTarget "Helper Library" */ = { 627 | isa = XCConfigurationList; 628 | buildConfigurations = ( 629 | 2835DB8D20E13DD30006B59B /* Debug */, 630 | 2835DB8E20E13DD30006B59B /* Release */, 631 | ); 632 | defaultConfigurationIsVisible = 0; 633 | defaultConfigurationName = Release; 634 | }; 635 | C01FCF4E08A954540054247B /* Build configuration list for PBXProject "CSAuthSample" */ = { 636 | isa = XCConfigurationList; 637 | buildConfigurations = ( 638 | C01FCF4F08A954540054247B /* Debug */, 639 | C01FCF5008A954540054247B /* Release */, 640 | ); 641 | defaultConfigurationIsVisible = 0; 642 | defaultConfigurationName = Release; 643 | }; 644 | /* End XCConfigurationList section */ 645 | }; 646 | rootObject = 29B97313FDCFA39411CA2CEA /* Project object */; 647 | } 648 | -------------------------------------------------------------------------------- /CSAuthSample.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /CSAuthSample.xcodeproj/project.xcworkspace/xcshareddata/CSAuthSample.xccheckout: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDESourceControlProjectFavoriteDictionaryKey 6 | 7 | IDESourceControlProjectIdentifier 8 | 234C1A16-0DB1-439A-9BA3-B1C9DCBC43ED 9 | IDESourceControlProjectName 10 | CSAuthSample 11 | IDESourceControlProjectOriginsDictionary 12 | 13 | E933AC01F0A8FC2C42BCF1CAB754F165E11CE206 14 | https://github.com/atnan/SMJobBlessXPC.git 15 | 16 | IDESourceControlProjectPath 17 | CSAuthSample.xcodeproj 18 | IDESourceControlProjectRelativeInstallPathDictionary 19 | 20 | E933AC01F0A8FC2C42BCF1CAB754F165E11CE206 21 | ../.. 22 | 23 | IDESourceControlProjectURL 24 | https://github.com/atnan/SMJobBlessXPC.git 25 | IDESourceControlProjectVersion 26 | 111 27 | IDESourceControlProjectWCCIdentifier 28 | E933AC01F0A8FC2C42BCF1CAB754F165E11CE206 29 | IDESourceControlProjectWCConfigurations 30 | 31 | 32 | IDESourceControlRepositoryExtensionIdentifierKey 33 | public.vcs.git 34 | IDESourceControlWCCIdentifierKey 35 | E933AC01F0A8FC2C42BCF1CAB754F165E11CE206 36 | IDESourceControlWCCName 37 | CSAuthSample 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /CSAuthSample.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # Copyright © 2013-2021 Charles J. Srstka 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.2 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "CSAuthSample", 8 | platforms: [ 9 | .macOS(.v10_13) 10 | ], 11 | products: [ 12 | .library( 13 | name: "CSAuthSampleApp", 14 | targets: ["CSAuthSampleApp"] 15 | ), 16 | .library( 17 | name: "CSAuthSampleHelper", 18 | targets: ["CSAuthSampleHelper"] 19 | ), 20 | .library( 21 | name: "CSAuthSampleCommon", 22 | targets: ["CSAuthSampleCommon"] 23 | ), 24 | ], 25 | dependencies: [], 26 | targets: [ 27 | .target( 28 | name: "CSAuthSampleApp", 29 | dependencies: ["CSAuthSampleCommon"] 30 | ), 31 | .target( 32 | name: "CSAuthSampleHelper", 33 | dependencies: ["CSAuthSampleCommon"] 34 | ), 35 | .target( 36 | name: "CSAuthSampleCommon", 37 | dependencies: [] 38 | ), 39 | ] 40 | ) 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CSAuthSample 2 | 3 | This is a set of libraries that will assist in writing privileged helper tools for macOS applications. 4 | It is intended to be a little more up to date and easier to use than Apple's aging [EvenBetterAuthorizationSample](https://developer.apple.com/library/archive/samplecode/EvenBetterAuthorizationSample/Introduction/Intro.html). 5 | [Pacifist](https://www.charlessoft.com) has been using it for some time. 6 | 7 | CSAuthSample began as a port of Nathan de Vries’ [SMJobBlessXPC](https://github.com/atnan/SMJobBlessXPC), although it has been rewritten so many times that I doubt any of the original code remains. 8 | At some point it was rewritten around Apple’s [BetterAuthorizationSample](https://developer.apple.com/library/archive/samplecode/BetterAuthorizationSample/Introduction/Intro.html). 9 | More recently it has been rewritten around Apple's [EvenBetterAuthorizationSample](https://developer.apple.com/library/archive/samplecode/EvenBetterAuthorizationSample/Introduction/Intro.html). 10 | This latest rewrite uses NSXPCConnection, and thus requires the helper to be written in either Objective-C or Swift. 11 | For users that require the helper to be in straight C, the older code using libxpc is available in the 'c-helper' branch. 12 | 13 | The current code assumes the front-end application will be written in Swift. 14 | Either Objective-C or Swift can be used to write the helper tool. 15 | If you wish to use Swift for the full stack, a Swift package is available. 16 | For your convenience, there is an example app project included that will show you how to write a Swift-based helper app and corresponding client app. 17 | 18 | For those who like to be on the cutting edge, a Swift-only rewrite which uses Swift Concurrency is available under the `swift-concurrency` branch. 19 | 20 | CSAuthSample is free to use under the terms of the MIT license. 21 | 22 | Enjoy! 23 | -------------------------------------------------------------------------------- /Sources/CSAuthSampleApp/HelperClient.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HelperClient.swift 3 | // App Library 4 | // 5 | // Created by Charles Srstka on 6/25/18. 6 | // 7 | 8 | import CSAuthSampleCommon 9 | import Foundation 10 | import ServiceManagement 11 | 12 | /// The primary class used by your application to communicate with your helper tool. 13 | /// 14 | /// To use, create an instance and use `connectToHelperTool` to send messages to the helper tool. 15 | public struct HelperClient { 16 | private let authRef: AuthorizationRef 17 | 18 | /// Create a `HelperClient` object. 19 | /// 20 | /// - Parameters: 21 | /// - authData: Authorization data, in the format of an `AuthorizationExternalForm`. If not provided, a new `AuthorizationRef` will be created. 22 | /// - commandSet: A `CommandSet` object describing the messages the helper accepts, and their required authorization levels. 23 | /// - bundle: A bundle containing a strings table containing localized messages to present to the user. Optional. 24 | /// - tableName: The name of a strings table containing localized messages to present to the user. Optional. 25 | /// - Throws: Any errors that occur in the process of creating the `HelperClient`'s internal `AuthorizationRef`. 26 | public init( 27 | authData: Data? = nil, commandSet: CommandSet, bundle: Bundle? = nil, tableName: String? = nil 28 | ) throws { 29 | if let data = authData { 30 | self.authRef = try data.withUnsafeBytes { 31 | guard let extForm = $0.bindMemory(to: AuthorizationExternalForm.self).baseAddress else { 32 | throw CocoaError(.fileReadUnknown) 33 | } 34 | 35 | var authRef: AuthorizationRef? 36 | 37 | let err = AuthorizationCreateFromExternalForm(extForm, &authRef) 38 | if err != errSecSuccess { throw ConvertOSStatus(err) } 39 | 40 | return try authRef ?? { throw CocoaError(.fileReadUnknown) }() 41 | } 42 | } else { 43 | var authRef: AuthorizationRef? 44 | let err = AuthorizationCreate(nil, nil, [], &authRef) 45 | if err != errSecSuccess { throw ConvertOSStatus(err) } 46 | self.authRef = try authRef ?? { throw CocoaError(.fileReadUnknown) }() 47 | } 48 | 49 | commandSet.setupAuthorizationRights(self.authRef, bundle: bundle, tableName: tableName) 50 | } 51 | 52 | /// Generate authorization data. 53 | /// 54 | /// It is generally recommended to send this to the helper tool, in order to establish the identity of the sender and prevent message spoofing. 55 | /// 56 | /// - Throws: Any error that occurs while generating the authorization data. 57 | /// - Returns: A `Data` object containing the authorization data, in the format used by `AuthorizationExternalForm`. 58 | public func authorizationData() throws -> Data { 59 | var authData = Data(count: MemoryLayout.size) 60 | 61 | try authData.withUnsafeMutableBytes { 62 | guard let ptr = $0.bindMemory(to: AuthorizationExternalForm.self).baseAddress else { 63 | throw CocoaError(.fileReadUnknown) 64 | } 65 | 66 | let err = AuthorizationMakeExternalForm(self.authRef, ptr) 67 | 68 | if err != errAuthorizationSuccess { 69 | throw ConvertOSStatus(err) 70 | } 71 | } 72 | 73 | return authData 74 | } 75 | 76 | /// Install the helper tool. 77 | /// 78 | /// - Parameters: 79 | /// - helperID: The bundle identifier of your helper tool, which should generally be distinct from your main application's bundle identifier. 80 | /// - completionHandler: Reports on the success or failure of the installation. 81 | public func installHelperTool(helperID: String, completionHandler: @escaping (Error?) -> Void) { 82 | do { 83 | try self.requestPrivileges([kSMRightBlessPrivilegedHelper], allowUserInteraction: true) 84 | } catch { 85 | completionHandler(error) 86 | return 87 | } 88 | 89 | self.uninstallHelperTool(helperID: helperID) { _ in 90 | self.blessHelperTool(helperID: helperID, completionHandler: completionHandler) 91 | } 92 | } 93 | 94 | /// Uninstall the helper tool. 95 | /// 96 | /// - Parameters: 97 | /// - helperID: The bundle identifier of your helper tool, which should generally be distinct from your main application's bundle identifier. 98 | /// - completionHandler: Reports on the success or failure of the uninstallation. 99 | public func uninstallHelperTool(helperID: String, completionHandler: @escaping (Error?) -> Void) { 100 | do { 101 | let authData = try self.authorizationData() 102 | 103 | let errorHandler: (Error) -> Void = { completionHandler($0) } 104 | 105 | let connectionHandler: (BuiltInCommands) -> Void = { proxy in 106 | proxy.uninstallHelperTool(authorizationData: authData) { uninstallResult in 107 | self.unblessHelperTool(helperID: helperID) { 108 | completionHandler(uninstallResult ?? $0) 109 | } 110 | } 111 | } 112 | 113 | self.connectToHelperTool( 114 | helperID: helperID, 115 | protocol: BuiltInCommands.self, 116 | installIfNecessary: false, 117 | errorHandler: errorHandler, 118 | connectionHandler: connectionHandler 119 | ) 120 | } catch { 121 | completionHandler(error) 122 | } 123 | } 124 | 125 | /// Get the version of the helper tool. 126 | /// 127 | /// This is helpful for making sure that the application and helper tool are in sync with each other. 128 | /// If the helper's version does not match the app's version, it is generally a sign that the helper needs to be upgraded. 129 | /// 130 | /// - Parameters: 131 | /// - helperID: The bundle identifier of your helper tool, which should generally be distinct from your main application's bundle identifier. 132 | /// - completionHandler: If successful, returns the version of the helper tool. 133 | public func requestHelperVersion( 134 | helperID: String, completionHandler: @escaping (Result) -> Void 135 | ) { 136 | let conn: NSXPCConnection 137 | 138 | do { 139 | conn = try self._openConnection( 140 | helperID: helperID, interface: nil, protocol: BuiltInCommands.self) 141 | } catch { 142 | completionHandler(.failure(error)) 143 | return 144 | } 145 | 146 | self.checkHelperVersion(connection: conn, completionHandler: completionHandler) 147 | } 148 | 149 | /// Send a message to the helper tool, and receive a notification on getting its reply. 150 | /// 151 | /// - Parameters: 152 | /// - helperID: The bundle identifier of your helper tool, which should generally be distinct from your main application's bundle identifier. 153 | /// - proto: A protocol describing the messages the helper tool accepts. Must conform to `BuiltInCommands`. 154 | /// - interface: An optional `NSXPCInterface` describing the helper's interface. If not provided, this will be generated from the `protocol`. 155 | /// - expectedVersion: The expected version of the helper. Optional. 156 | /// - installIfNecessary: Ignored unless `expectedVersion` is provided. If true, the helper tool will be installed if it is not present, or if its version does not match the expected version. 157 | /// - errorHandler: A closure which will be invoked in the event of an error occurring while communicating wth the helper tool. 158 | /// - connectionHandler: A closure which will be invoked upon establishing a successful connection to the helper tool. 159 | public func connectToHelperTool( 160 | helperID: String, 161 | protocol proto: P.Type, 162 | interface: NSXPCInterface? = nil, 163 | expectedVersion: String? = nil, 164 | installIfNecessary: Bool = true, 165 | errorHandler: @escaping (Error) -> Void, 166 | connectionHandler: @escaping (P) -> Void 167 | ) { 168 | let conn: NSXPCConnection 169 | 170 | do { 171 | conn = try self._openConnection(helperID: helperID, interface: interface, protocol: proto) 172 | } catch { 173 | errorHandler(error) 174 | return 175 | } 176 | 177 | if let expectedVersion = expectedVersion { 178 | self.checkHelperVersion(connection: conn) { 179 | switch $0 { 180 | case .success(let version) where version == expectedVersion: 181 | self._connectToHelperTool( 182 | connection: conn, 183 | protocol: proto, 184 | interface: interface, 185 | errorHandler: errorHandler, 186 | connectionHandler: connectionHandler) 187 | case .failure where installIfNecessary, .success where installIfNecessary: 188 | self._installAndConnect( 189 | helperID: helperID, 190 | protocol: proto, 191 | interface: interface, 192 | errorHandler: errorHandler, 193 | connectionHandler: connectionHandler) 194 | case .failure(let error) where !installIfNecessary: 195 | errorHandler(error) 196 | default: 197 | errorHandler(CocoaError(.fileReadUnknown)) 198 | } 199 | } 200 | } else { 201 | self._connectToHelperTool( 202 | connection: conn, 203 | protocol: proto, 204 | interface: interface, 205 | errorHandler: errorHandler, 206 | connectionHandler: connectionHandler 207 | ) 208 | } 209 | } 210 | 211 | /// Establish a connection via an `NSXPCListenerEndpoint` passed by the helper tool. 212 | /// 213 | /// This can sometimes be useful if more advanced communication between the app and the helper tool is needed. 214 | /// - Parameters: 215 | /// - endpoint: The `NSXPCListenerEndpoint` from which to establish a connection. 216 | /// - proto: A protocol describing the messages the helper tool accepts. Must conform to `BuiltInCommands`. 217 | /// - interface: An optional `NSXPCInterface` describing the helper's interface. If not provided, this will be generated from the `protocol`. 218 | /// - errorHandler: A closure which will be invoked in the event of an error occurring while communicating wth the helper tool. 219 | /// - Throws: Any errors that occur in the process of establishing communication with the helper tool. 220 | /// - Returns: An proxy object, conforming to `proto`, which can be used to send messages to the helper tool. 221 | public func connectViaEndpoint( 222 | _ endpoint: NSXPCListenerEndpoint, 223 | protocol proto: P.Type, 224 | interface: NSXPCInterface? = nil, 225 | errorHandler: @escaping (Error) -> Void 226 | ) throws -> P { 227 | let conn = NSXPCConnection(listenerEndpoint: endpoint) 228 | 229 | return try self.getProxy(conn, protocol: proto, interface: interface, errorHandler: errorHandler) 230 | } 231 | 232 | private func getProxy( 233 | _ conn: NSXPCConnection, 234 | protocol proto: P.Type, 235 | interface: NSXPCInterface?, 236 | errorHandler: @escaping (Error) -> Void 237 | ) throws -> P { 238 | let proxy = conn.remoteObjectProxyWithErrorHandler(errorHandler) 239 | 240 | return try proxy as? P ?? { throw CocoaError(.fileReadUnknown) }() 241 | } 242 | 243 | private func requestPrivileges(_ privs: [String], allowUserInteraction: Bool = true) throws { 244 | if privs.isEmpty { 245 | return 246 | } 247 | 248 | let items = UnsafeMutablePointer.allocate(capacity: privs.count) 249 | 250 | for (index, eachPriv) in privs.enumerated() { 251 | let name: UnsafePointer = eachPriv.withCString { 252 | let len = strlen($0) + 1 253 | let copy = UnsafeMutablePointer.allocate(capacity: len) 254 | copy.initialize(from: $0, count: len) 255 | 256 | return UnsafePointer(copy) 257 | } 258 | 259 | items[index] = AuthorizationItem(name: name, valueLength: 0, value: nil, flags: 0) 260 | } 261 | 262 | defer { 263 | for index in 0.. Void) { 286 | var smError: Unmanaged? 287 | if !SMJobBless(kSMDomainSystemLaunchd, helperID as CFString, self.authRef, &smError) { 288 | completionHandler(smError.map { ConvertCFError($0.takeRetainedValue()) } ?? CocoaError(.fileWriteUnknown)) 289 | } else { 290 | completionHandler(nil) 291 | } 292 | } 293 | 294 | private func unblessHelperTool(helperID: String, completionHandler: @escaping (Error?) -> Void) { 295 | var smError: Unmanaged? = nil 296 | // deprecated, but there is still not a decent replacement, so 🤷 297 | // use some rather unfortunate hackery with dlsym to get around deprecation warning 298 | 299 | guard let smJobRemoveSym = dlsym(UnsafeMutableRawPointer(bitPattern: -2), "SMJobRemove") else { 300 | completionHandler(CocoaError(.fileWriteUnknown)) 301 | return 302 | } 303 | 304 | let smJobRemove = unsafeBitCast(smJobRemoveSym, to: (@convention(c) (CFString?, CFString, AuthorizationRef?, Bool, UnsafeMutablePointer?>?) -> Bool).self) 305 | 306 | guard smJobRemove(kSMDomainSystemLaunchd, helperID as CFString, self.authRef, true, &smError) else { 307 | completionHandler(smError.map { ConvertCFError($0.takeRetainedValue()) } ?? CocoaError(.fileWriteUnknown)) 308 | return 309 | } 310 | 311 | completionHandler(nil) 312 | } 313 | 314 | private func _installAndConnect( 315 | helperID: String, 316 | protocol proto: P.Type, 317 | interface: NSXPCInterface?, 318 | errorHandler: @escaping (Error) -> Void, 319 | connectionHandler: @escaping (P) -> Void 320 | ) { 321 | self.installHelperTool(helperID: helperID) { 322 | if let error = $0 { 323 | errorHandler(error) 324 | return 325 | } 326 | 327 | self.connectToHelperTool( 328 | helperID: helperID, 329 | protocol: proto, 330 | interface: interface, 331 | expectedVersion: nil, 332 | installIfNecessary: false, 333 | errorHandler: errorHandler, 334 | connectionHandler: connectionHandler 335 | ) 336 | } 337 | } 338 | 339 | private func _openConnection( 340 | helperID: String, 341 | interface: NSXPCInterface?, 342 | protocol proto: P.Type 343 | ) throws -> NSXPCConnection { 344 | guard let objcProto = proto as Any as AnyObject as? Protocol else { 345 | throw CocoaError(.fileReadUnknown) 346 | } 347 | 348 | let conn = NSXPCConnection(machServiceName: helperID, options: .privileged) 349 | 350 | conn.remoteObjectInterface = interface ?? NSXPCInterface(with: objcProto) 351 | conn.resume() 352 | 353 | return conn 354 | } 355 | 356 | private func _connectToHelperTool( 357 | connection conn: NSXPCConnection, 358 | protocol proto: P.Type, 359 | interface: NSXPCInterface?, 360 | errorHandler: @escaping (Error) -> Void, 361 | connectionHandler: @escaping (P) -> Void 362 | ) { 363 | do { 364 | let proxy = try self.getProxy(conn, protocol: proto, interface: interface, errorHandler: errorHandler) 365 | connectionHandler(proxy) 366 | } catch { 367 | errorHandler(error) 368 | } 369 | } 370 | 371 | private func checkHelperVersion( 372 | connection: NSXPCConnection, 373 | completionHandler: @escaping (Result) -> Void 374 | ) { 375 | let errorHandler: (Error) -> Void = { completionHandler(.failure($0)) } 376 | 377 | guard let proxy = connection.remoteObjectProxyWithErrorHandler(errorHandler) as? BuiltInCommands else { 378 | completionHandler(.failure(CocoaError(.fileReadUnknown))) 379 | return 380 | } 381 | 382 | proxy.getVersion { 383 | if let error = $1 { 384 | completionHandler(.failure(error)) 385 | } else if let version = $0 { 386 | completionHandler(.success(version)) 387 | } else { 388 | completionHandler(.failure(CocoaError(.fileReadUnknown))) 389 | } 390 | } 391 | } 392 | } 393 | -------------------------------------------------------------------------------- /Sources/CSAuthSampleApp/SandboxWorkaround.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SandboxWorkaround.swift 3 | // App Library 4 | // 5 | // Created by Charles Srstka on 9/13/18. 6 | // 7 | 8 | import Cocoa 9 | 10 | /// Workaround for a macOS UI bug. 11 | /// 12 | /// As of macOS 10.13, there is a UI issue that causes authentication boxes 13 | /// not to automatically gain focus when the app is sandboxed, which significantly 14 | /// impairs the user experience. This class provides an ugly workaround for this 15 | /// issue. Simply create an instance before beginning an operation that may possibly 16 | /// result in an authentication prompt, and stop it in the reply block. 17 | /// 18 | /// Usage example: 19 | /// ``` 20 | /// let workaround = SandboxWorkaround() 21 | /// proxy.doSomePrivilegedOperation { reply in 22 | /// workaround.stop() 23 | /// 24 | /// ... 25 | /// } 26 | /// ``` 27 | public class SandboxWorkaround { 28 | /// Create an instance of `SandboxWorkaround`. 29 | /// 30 | /// Be sure to call `stop()` once you receive a reply from the helper tool. 31 | public init() { 32 | self.queue.async(execute: self.activateIfReady) 33 | } 34 | 35 | /// Call this within the reply block after sending a message to your helper tool. 36 | public func stop() { 37 | self.doneSemaphore.wait() 38 | defer { self.doneSemaphore.signal() } 39 | 40 | self.done = true 41 | } 42 | 43 | private let queue = DispatchQueue(label: "com.charlessoft.CSAuthSample.SandboxWorkaround.queue") 44 | private let doneSemaphore = DispatchSemaphore(value: 1) 45 | private var done: Bool = false 46 | 47 | private func activateIfReady() { 48 | let agentID = "com.apple.SecurityAgent" 49 | 50 | self.doneSemaphore.wait() 51 | defer { self.doneSemaphore.signal() } 52 | 53 | if self.done { 54 | return 55 | } 56 | 57 | if let securityAgent = NSRunningApplication.runningApplications( 58 | withBundleIdentifier: agentID 59 | ) 60 | .last { 61 | securityAgent.activate(options: []) 62 | } else { 63 | self.queue.asyncAfter( 64 | deadline: .now() + .microseconds(10), 65 | execute: self.activateIfReady) 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Sources/CSAuthSampleCommon/CSASCommon.m: -------------------------------------------------------------------------------- 1 | // 2 | // CSASCommon.m 3 | // Helper Library 4 | // 5 | // Based on Common.m from EvenBetterAuthorizationSample, 6 | // Copyright © 2013 Apple Computer. 7 | // 8 | // Created by Charles Srstka on 6/25/18. 9 | // 10 | 11 | @import Foundation; 12 | @import Darwin.POSIX.syslog; 13 | #import "CSASCommon.h" 14 | 15 | NS_ASSUME_NONNULL_BEGIN 16 | 17 | @interface CSASCommandSet () 18 | 19 | @property (nonatomic, copy) NSDictionary *rights; 20 | 21 | @end 22 | 23 | static NSString * const rightsKey = @"com.charlessoft.CSAuthSample.CSASCommandSet.Rights"; 24 | static NSString * const countKey = @"com.charlessoft.CSAuthSample.CSASCommandSet.Count"; 25 | static NSString * const rightNameKey = @"com.charlessoft.CSAuthSample.CSASCommandSet.RightName"; 26 | static NSString * const rightValueKey = @"com.charlessoft.CSAuthSample.CSASCommandSet.RightValue"; 27 | 28 | @implementation CSASCommandSet 29 | 30 | - (instancetype)initWithAuthorizationRights:(NSArray *)rights { 31 | self = [super init]; 32 | if (self == nil) { 33 | return nil; 34 | } 35 | 36 | NSMutableDictionary *rightsDict = [NSMutableDictionary new]; 37 | 38 | for (CSASAuthorizationRight *eachRight in rights) { 39 | rightsDict[NSStringFromSelector(eachRight.selector)] = eachRight; 40 | } 41 | 42 | [self addBuiltInAuthorizationRights:rightsDict]; 43 | 44 | self->_rights = [rightsDict copy]; 45 | 46 | return self; 47 | } 48 | 49 | - (NSArray *)authorizationRights { 50 | return self.rights.allValues; 51 | } 52 | 53 | - (void)addBuiltInAuthorizationRights:(NSMutableDictionary *)rightsDict { 54 | // Add built-in commands 55 | 56 | SEL connectSel = @selector(getEndpointWithAuthorizationData:endpoint:); 57 | NSString *connectName = NSStringFromSelector(connectSel); 58 | if (rightsDict[connectName] == nil) { 59 | NSString *rightName = @"com.charlessoft.CSAuthSample.ConnectWithEndpoint"; 60 | const char *rule = kAuthorizationRuleClassAllow; 61 | 62 | rightsDict[connectName] = [[CSASAuthorizationRight alloc] initWithSelector:connectSel 63 | name:rightName 64 | rule:rule 65 | prompt:nil]; 66 | } 67 | 68 | SEL versionSel = @selector(getVersionWithReply:); 69 | NSString *versionName = NSStringFromSelector(versionSel); 70 | if (rightsDict[versionName] == nil) { 71 | NSString *rightName = @"com.charlessoft.CSAuthSample.GetVersion"; 72 | const char *rule = kAuthorizationRuleClassAllow; 73 | 74 | rightsDict[versionName] = [[CSASAuthorizationRight alloc] initWithSelector:versionSel 75 | name:rightName 76 | rule:rule 77 | prompt:nil]; 78 | } 79 | 80 | SEL uninstallSel = @selector(uninstallHelperToolWithAuthorizationData:reply:); 81 | NSString *uninstallName = NSStringFromSelector(uninstallSel); 82 | if (rightsDict[uninstallName] == nil) { 83 | NSString *rightName = @"com.charlessoft.CSAuthSample.UninstallHelper"; 84 | const char *rule = kAuthorizationRuleClassAllow; 85 | 86 | rightsDict[uninstallName] = [[CSASAuthorizationRight alloc] initWithSelector:uninstallSel 87 | name:rightName 88 | rule:rule 89 | prompt:nil]; 90 | } 91 | } 92 | 93 | - (nullable CSASAuthorizationRight *)authorizationRightForCommand:(SEL)command { 94 | return self.rights[NSStringFromSelector(command)]; 95 | } 96 | 97 | - (void)setupAuthorizationRights:(AuthorizationRef)authRef bundle:(nullable NSBundle *)bundle tableName:(nullable NSString *)tableName { 98 | assert(authRef != NULL); 99 | 100 | for (CSASAuthorizationRight *eachRight in self.rights.allValues) { 101 | // First get the right. If we get back errAuthorizationDenied that means there's 102 | // no current definition, so we add our default one. 103 | 104 | const char *rightName = (const char * _Nonnull)eachRight.name.UTF8String; 105 | assert(rightName != NULL); 106 | 107 | CFBundleRef cfBundle = NULL; 108 | if (bundle != nil) { 109 | if (bundle == [NSBundle mainBundle]) { 110 | cfBundle = CFBundleGetMainBundle(); 111 | CFRetain(cfBundle); 112 | } else { 113 | cfBundle = CFBundleCreate(kCFAllocatorDefault, (__bridge CFURLRef)bundle.bundleURL); 114 | } 115 | } 116 | 117 | OSStatus err = AuthorizationRightGet(rightName, NULL); 118 | if (err == errAuthorizationDenied) { 119 | err = AuthorizationRightSet( 120 | authRef, 121 | rightName, 122 | (__bridge CFTypeRef)eachRight.rule, 123 | (__bridge CFStringRef)eachRight.prompt, 124 | cfBundle, 125 | (__bridge CFStringRef)tableName 126 | ); 127 | assert(err == errAuthorizationSuccess); 128 | } else { 129 | // A right already exists (err == noErr) or any other error occurs, we 130 | // assume that it has been set up in advance by the system administrator or 131 | // this is the second time we've run. Either way, there's nothing more for 132 | // us to do. 133 | } 134 | 135 | if (cfBundle != NULL) { 136 | CFRelease(cfBundle); 137 | } 138 | } 139 | } 140 | 141 | + (BOOL)supportsSecureCoding { return YES; } 142 | 143 | - (nullable instancetype)initWithCoder:(NSCoder *)coder { 144 | self = [super init]; 145 | if (self == nil) { 146 | return nil; 147 | } 148 | 149 | NSInteger count = [coder decodeIntegerForKey:countKey]; 150 | NSData *rightsData = [coder decodeObjectOfClass:[NSData class] forKey:rightsKey]; 151 | 152 | NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingFromData:rightsData error:NULL]; 153 | 154 | if (unarchiver == nil) { 155 | return nil; 156 | } 157 | 158 | NSMutableDictionary *rightsDict = [NSMutableDictionary new]; 159 | 160 | for (NSInteger i = 0; i < count; i++) { 161 | NSString *nameKey = [rightNameKey stringByAppendingFormat:@"%ld", (long)i]; 162 | NSString *valueKey = [rightValueKey stringByAppendingFormat:@"%ld", (long)i]; 163 | 164 | NSString *name = [unarchiver decodeObjectOfClass:[NSString class] forKey:nameKey]; 165 | CSASAuthorizationRight *value = [unarchiver decodeObjectOfClass:[CSASAuthorizationRight class] forKey:valueKey]; 166 | 167 | if (name == nil || value == nil) { 168 | return nil; 169 | } 170 | 171 | rightsDict[name] = value; 172 | } 173 | 174 | self->_rights = [rightsDict copy]; 175 | 176 | return self; 177 | } 178 | 179 | - (void)encodeWithCoder:(NSCoder *)coder { 180 | NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initRequiringSecureCoding:YES]; 181 | 182 | __block NSInteger count = 0; 183 | 184 | [self.rights enumerateKeysAndObjectsUsingBlock:^(NSString *name, CSASAuthorizationRight *right, __unused BOOL *stop) { 185 | [archiver encodeObject:name forKey:[rightNameKey stringByAppendingFormat:@"%ld", (long)count]]; 186 | [archiver encodeObject:right forKey:[rightValueKey stringByAppendingFormat:@"%ld", (long)count]]; 187 | 188 | count++; 189 | }]; 190 | 191 | NSData *data = archiver.encodedData; 192 | 193 | [coder encodeInteger:count forKey:countKey]; 194 | [coder encodeObject:data forKey:rightsKey]; 195 | } 196 | 197 | @end 198 | 199 | static NSString * const selectorKey = @"com.charlessoft.CSAuthSample.CSASAuthorizationRight.Selector"; 200 | static NSString * const nameKey = @"com.charlessoft.CSAuthSample.CSASAuthorizationRight.Name"; 201 | static NSString * const ruleKey = @"com.charlessoft.CSAuthSample.CSASAuthorizationRight.Rule"; 202 | static NSString * const promptKey = @"com.charlessoft.CSAuthSample.CSASAuthorizationRight.Prompt"; 203 | 204 | @implementation CSASAuthorizationRight 205 | 206 | + (BOOL)supportsSecureCoding { return YES; } 207 | 208 | - (instancetype)initWithSelector:(SEL)selector name:(NSString *)name rule:(const char *)rule prompt:(nullable NSString *)prompt { 209 | self = [super init]; 210 | if (self == nil) { 211 | return nil; 212 | } 213 | 214 | self->_selector = selector; 215 | self->_name = [name copy]; 216 | 217 | self->_rule = (NSString * _Nonnull)[[NSString alloc] initWithUTF8String:rule]; 218 | assert(self->_rule != nil); 219 | 220 | self->_prompt = [prompt copy]; 221 | 222 | return self; 223 | } 224 | 225 | - (nullable instancetype)initWithCoder:(NSCoder *)coder { 226 | self = [super init]; 227 | if (self == nil || !coder.allowsKeyedCoding) { 228 | return nil; 229 | } 230 | 231 | NSString *selName = [coder decodeObjectOfClass:[NSString class] forKey:selectorKey]; 232 | 233 | if (selName == nil) { 234 | return nil; 235 | } 236 | 237 | self->_selector = NSSelectorFromString(selName); 238 | self->_name = (NSString * _Nonnull)[coder decodeObjectOfClass:[NSString class] forKey:nameKey]; 239 | self->_rule = (NSString * _Nonnull)[coder decodeObjectOfClass:[NSString class] forKey:ruleKey]; 240 | self->_prompt = (NSString * _Nonnull)[coder decodeObjectOfClass:[NSString class] forKey:promptKey]; 241 | 242 | if (self->_selector == NULL || self->_name == nil || self->_rule == nil) { 243 | return nil; 244 | } 245 | 246 | return self; 247 | } 248 | 249 | - (void)encodeWithCoder:(NSCoder *)coder { 250 | [coder encodeObject:NSStringFromSelector(self->_selector) forKey:selectorKey]; 251 | [coder encodeObject:self->_name forKey:nameKey]; 252 | [coder encodeObject:self->_rule forKey:ruleKey]; 253 | 254 | if (self->_prompt != nil) { 255 | [coder encodeObject:self->_prompt forKey:promptKey]; 256 | } 257 | } 258 | 259 | @end 260 | 261 | static NSInteger CSASConvertPOSIXErrorCode(NSInteger code) { 262 | switch (code) { 263 | case ECANCELED: 264 | return NSUserCancelledError; 265 | case ENOENT: 266 | return NSFileNoSuchFileError; 267 | case EFBIG: 268 | return NSFileReadTooLargeError; 269 | case EEXIST: 270 | return NSFileWriteFileExistsError; 271 | case ENOSPC: 272 | return NSFileWriteOutOfSpaceError; 273 | case EROFS: 274 | return NSFileWriteVolumeReadOnlyError; 275 | default: 276 | return 0; 277 | } 278 | } 279 | 280 | static NSInteger CSASConvertOSStatusErrorCode(NSInteger code) { 281 | if (code >= errSecErrnoBase && code <= errSecErrnoLimit) { 282 | NSInteger newCode = CSASConvertPOSIXErrorCode(code - errSecErrnoBase); 283 | 284 | if (newCode != 0) { 285 | return newCode; 286 | } 287 | } 288 | 289 | switch (code) { 290 | case userCanceledErr: 291 | case errAuthorizationCanceled: 292 | case errSecCSCancelled: 293 | case errAEWaitCanceled: 294 | case kernelCanceledErr: 295 | case kOTCanceledErr: 296 | case kECANCELErr: 297 | case errIACanceled: 298 | case kRAConnectionCanceled: 299 | case kTXNUserCanceledOperationErr: 300 | case kFBCindexingCanceled: 301 | case kFBCaccessCanceled: 302 | case kFBCsummarizationCanceled: 303 | return NSUserCancelledError; 304 | case fnfErr: 305 | return NSFileNoSuchFileError; 306 | case fileBoundsErr: 307 | case fsDataTooBigErr: 308 | return NSFileReadTooLargeError; 309 | case dupFNErr: 310 | return NSFileWriteFileExistsError; 311 | case dskFulErr: 312 | case errFSNotEnoughSpaceForOperation: 313 | return NSFileWriteOutOfSpaceError; 314 | case vLckdErr: 315 | return NSFileWriteVolumeReadOnlyError; 316 | default: 317 | return 0; 318 | } 319 | } 320 | 321 | FOUNDATION_EXPORT NSError *CSASConvertNSError(NSError *error) { 322 | // If we can find a NSCocoaError that corresponds to the same error condition as this error, use it. 323 | // NSCocoaError tends to present nicer error messages to the user. 324 | NSInteger newCode = 0; 325 | 326 | if ([error.domain isEqualToString:NSPOSIXErrorDomain]) { 327 | newCode = CSASConvertPOSIXErrorCode(error.code); 328 | } else if ([error.domain isEqualToString:NSOSStatusErrorDomain]) { 329 | newCode = CSASConvertOSStatusErrorCode(error.code); 330 | } else { 331 | newCode = 0; 332 | } 333 | 334 | if (newCode != 0) { 335 | NSMutableDictionary *userInfo = error.userInfo.mutableCopy; 336 | 337 | userInfo[NSUnderlyingErrorKey] = error; 338 | 339 | // Use the built-in error messages instead 340 | userInfo[NSLocalizedFailureReasonErrorKey] = nil; 341 | 342 | return [[NSError alloc] initWithDomain:NSCocoaErrorDomain code:newCode userInfo:userInfo]; 343 | } else if ([error.domain isEqualToString:NSOSStatusErrorDomain]) { 344 | // At least try to find a nicer error string to display to the user. 345 | 346 | CFStringRef errString = SecCopyErrorMessageString((OSStatus)error.code, NULL); 347 | 348 | if (errString != NULL) { 349 | NSMutableDictionary *userInfo = error.userInfo.mutableCopy; 350 | 351 | userInfo[NSLocalizedFailureReasonErrorKey] = CFBridgingRelease(errString); 352 | 353 | return [[NSError alloc] initWithDomain:NSOSStatusErrorDomain code:error.code userInfo:userInfo]; 354 | } 355 | } 356 | 357 | // We weren't able to improve this error message; just return it as is 358 | return error; 359 | } 360 | 361 | FOUNDATION_EXPORT NSError *CSASConvertCFError(CFErrorRef error) { 362 | return CSASConvertNSError((__bridge NSError *)error); 363 | } 364 | 365 | FOUNDATION_EXPORT NSError *CSASConvertPOSIXError(int err) { 366 | return CSASConvertNSError([[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:err userInfo:nil]); 367 | } 368 | 369 | FOUNDATION_EXPORT NSError *CSASConvertOSStatus(OSStatus err) { 370 | return CSASConvertNSError([[NSError alloc] initWithDomain:NSOSStatusErrorDomain code:err userInfo:nil]); 371 | } 372 | 373 | NS_ASSUME_NONNULL_END 374 | -------------------------------------------------------------------------------- /Sources/CSAuthSampleCommon/include/CSASCommon.h: -------------------------------------------------------------------------------- 1 | // 2 | // CSASCommon.h 3 | // Helper Library 4 | // 5 | // Created by Charles Srstka on 6/25/18. 6 | // 7 | 8 | @import Foundation; 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | // Some built-in commands handled by the library which are provided for free. 13 | // The protocol you pass to CSASHelperTool should conform to this protocol. 14 | // 15 | // IMPORTANT: NSXPCConnection can call these methods on any thread. It turns out that our 16 | // implementation of these methods is thread safe but if that's not the case for your code 17 | // you have to implement your own protection (for example, having your own serial queue and 18 | // dispatching over to it). 19 | 20 | NS_SWIFT_NAME(BuiltInCommands) @protocol CSASBuiltInCommands 21 | 22 | // Part of CSASBuiltInCommands. Not used by the standard app (it's part of the sandboxed 23 | // XPC service support). Called by the XPC service to get an endpoint for our listener. It then 24 | // passes this endpoint to the app so that the sandboxed app can talk us directly. 25 | - (void)getEndpointWithAuthorizationData:(NSData *)authData 26 | endpoint:(void (^)(NSXPCListenerEndpoint * _Nullable, NSError * _Nullable))reply 27 | NS_SWIFT_NAME(connect(authorizationData:endpoint:)); 28 | 29 | // Part of CSASBuiltInCommands. Returns the version number of the tool. 30 | - (void)getVersionWithReply:(void(^)(NSString * _Nullable version, NSError * _Nullable))reply 31 | NS_SWIFT_NAME(getVersion(reply:)); 32 | 33 | // Part of CSASBuiltInCommands. Uninstalls the helper tool. 34 | - (void)uninstallHelperToolWithAuthorizationData:(NSData *)authData 35 | reply:(void (^)(NSError * _Nullable))reply 36 | NS_SWIFT_NAME(uninstallHelperTool(authorizationData:reply:)); 37 | 38 | @end 39 | 40 | NS_SWIFT_NAME(AuthorizationRight) @interface CSASAuthorizationRight: NSObject 41 | 42 | - (instancetype)initWithSelector:(SEL)selector 43 | name:(NSString *)name 44 | rule:(const char *)rule 45 | prompt:(nullable NSString *)prompt; 46 | 47 | @property (nonatomic) SEL selector; 48 | @property (nonatomic, copy) NSString *name; 49 | @property (nonatomic, copy) NSString *rule; 50 | @property (nonatomic, copy, nullable) NSString *prompt; 51 | 52 | @end 53 | 54 | NS_SWIFT_NAME(CommandSet) @interface CSASCommandSet: NSObject 55 | 56 | - (instancetype)initWithAuthorizationRights:(NSArray *)rights; 57 | 58 | @property (nonatomic, readonly) NSArray *authorizationRights; 59 | 60 | // For a given command selector, return the associated authorization right name. 61 | - (nullable CSASAuthorizationRight *)authorizationRightForCommand:(SEL)command; 62 | 63 | // Set up the default authorization rights in the authorization database. 64 | - (void)setupAuthorizationRights:(AuthorizationRef)authRef 65 | bundle:(nullable NSBundle *)bundle 66 | tableName:(nullable NSString *)tableName; 67 | 68 | @end 69 | 70 | // Cocoa tends to do a nicer job presenting Cocoa errors than POSIX or OSStatus ones, 71 | // particularly with NSUserCancelledError, in which case -presentError: will skip 72 | // showing the error altogether. For certain other error types, using the Cocoa domain 73 | // will provide a little more information, including, sometimes, the filename for which 74 | // the operation failed. Therefore, convert errors to NSCocoaErrorDomain when possible. 75 | 76 | FOUNDATION_EXPORT NSError *CSASConvertNSError(NSError *error) NS_SWIFT_NAME(ConvertError(_:)); 77 | FOUNDATION_EXPORT NSError *CSASConvertCFError(CFErrorRef error) NS_SWIFT_NAME(ConvertCFError(_:)); 78 | FOUNDATION_EXPORT NSError *CSASConvertPOSIXError(int err) NS_SWIFT_NAME(ConvertPOSIXError(_:)); 79 | FOUNDATION_EXPORT NSError *CSASConvertOSStatus(OSStatus status) NS_SWIFT_NAME(ConvertOSStatus(_:)); 80 | 81 | NS_ASSUME_NONNULL_END 82 | -------------------------------------------------------------------------------- /Sources/CSAuthSampleCommon/include/module.modulemap: -------------------------------------------------------------------------------- 1 | module CSAuthSampleCommon { 2 | header "CSASCommon.h" 3 | export * 4 | } 5 | -------------------------------------------------------------------------------- /Sources/CSAuthSampleHelper/CSASHelperConnection.m: -------------------------------------------------------------------------------- 1 | // 2 | // CSASHelperConnection.m 3 | // Helper Tool 4 | // 5 | // Created by Charles Srstka on 7/1/18. 6 | // 7 | 8 | @import Foundation; 9 | #import "CSASHelperConnection.h" 10 | #import "CSASHelperConnectionInternal.h" 11 | #import "CSASHelperToolInternal.h" 12 | 13 | @interface CSASHelperConnection () 14 | 15 | @property (nonatomic, readonly, weak) CSASHelperTool *helperTool; 16 | 17 | @end 18 | 19 | static NSString * const currentCommandKey = @"com.charlessoft.CSAuthSample.currentCommand"; 20 | 21 | @implementation CSASHelperConnection 22 | 23 | - (instancetype)initWithConnection:(NSXPCConnection *)connection 24 | helperTool:(CSASHelperTool *)helperTool 25 | commandSet:(CSASCommandSet *)commandSet { 26 | self = [super init]; 27 | if (self == nil) { 28 | return nil; 29 | } 30 | 31 | self->_connection = connection; 32 | self->_helperTool = helperTool; 33 | self->_commandSet = commandSet; 34 | 35 | return self; 36 | } 37 | 38 | - (SEL)currentCommand { 39 | NSString *cmdName = [NSThread currentThread].threadDictionary[currentCommandKey]; 40 | 41 | return (cmdName == nil) ? nil : NSSelectorFromString(cmdName); 42 | } 43 | 44 | - (void)setCurrentCommand:(SEL)currentCommand { 45 | [NSThread currentThread].threadDictionary[currentCommandKey] = NSStringFromSelector(currentCommand); 46 | } 47 | 48 | - (nullable NSError *)checkAuthorization:(NSData *)authData { 49 | // First check that authData looks reasonable. 50 | if ((authData == nil) || (authData.length != sizeof(AuthorizationExternalForm))) { 51 | return [[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:EINVAL userInfo:nil]; 52 | } 53 | 54 | // Create an authorization ref from that the external form data contained within. 55 | AuthorizationRef authRef = NULL; 56 | OSStatus err = AuthorizationCreateFromExternalForm(authData.bytes, &authRef); 57 | 58 | if (err != errAuthorizationSuccess) { 59 | return CSASConvertOSStatus(err); 60 | } 61 | 62 | @try { 63 | // Call our authorization method. 64 | return [self _checkAuthorization:authRef forCommand:self.currentCommand]; 65 | } 66 | @finally { 67 | OSStatus junk = AuthorizationFree(authRef, 0); 68 | assert(junk == errAuthorizationSuccess); 69 | } 70 | } 71 | 72 | - (nullable NSError *)_checkAuthorization:(AuthorizationRef)auth forCommand:(SEL)command { 73 | CSASAuthorizationRight *authRight = [self.commandSet authorizationRightForCommand:command]; 74 | 75 | if (authRight == nil) { 76 | return [[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:EINVAL userInfo:nil]; 77 | } 78 | 79 | // Authorize the right associated with the command. 80 | 81 | AuthorizationItem oneRight = { NULL, 0, NULL, 0 }; 82 | AuthorizationRights rights = { 1, &oneRight }; 83 | 84 | oneRight.name = (const char * _Nonnull)authRight.name.UTF8String; 85 | assert(oneRight.name != NULL); 86 | 87 | AuthorizationFlags flags = kAuthorizationFlagExtendRights | kAuthorizationFlagInteractionAllowed; 88 | OSStatus err = AuthorizationCopyRights(auth, &rights, NULL, flags, NULL); 89 | 90 | if (err != errAuthorizationSuccess) { 91 | return CSASConvertOSStatus(err); 92 | } 93 | 94 | return nil; 95 | } 96 | 97 | #pragma mark * CSASBuiltInCommands 98 | 99 | - (void)getEndpointWithAuthorizationData:(NSData *)authData endpoint:(void (^)(NSXPCListenerEndpoint * _Nullable, NSError * _Nullable))reply { 100 | NSError *error = [self checkAuthorization:authData]; 101 | if (error != nil) { 102 | reply(nil, error); 103 | return; 104 | } 105 | 106 | reply(self.helperTool.listener.endpoint, nil); 107 | } 108 | 109 | - (void)getVersionWithReply:(void (^)(NSString * _Nullable, NSError * _Nullable))reply { 110 | NSString *vers = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"]; 111 | 112 | if (vers != nil) { 113 | reply([[NSString alloc] initWithFormat:@"%@", vers], nil); 114 | } else { 115 | reply(nil, [[NSError alloc] initWithDomain:NSCocoaErrorDomain code:NSFileReadUnknownError userInfo:nil]); 116 | } 117 | } 118 | 119 | - (void)uninstallHelperToolWithAuthorizationData:(NSData *)authData reply:(void (^)(NSError * _Nullable))reply { 120 | NSFileManager *fm = [NSFileManager defaultManager]; 121 | 122 | NSURL *helperURL = [NSURL fileURLWithPath:[NSProcessInfo processInfo].arguments[0]]; 123 | 124 | if (helperURL == nil) { 125 | reply([[NSError alloc] initWithDomain:NSCocoaErrorDomain code:NSFileWriteUnknownError userInfo:nil]); 126 | return; 127 | } 128 | 129 | NSURL *libraryURL = [fm URLForDirectory:NSLibraryDirectory inDomain:NSLocalDomainMask appropriateForURL:nil create:NO error:NULL]; 130 | 131 | NSURL *daemonsURL = [libraryURL URLByAppendingPathComponent:@"LaunchDaemons"]; 132 | 133 | NSURL *serviceURL = [[daemonsURL URLByAppendingPathComponent:self.helperTool.helperID] URLByAppendingPathExtension:@"plist"]; 134 | 135 | NSError *error = [self checkAuthorization:authData]; 136 | 137 | if (error != nil) { 138 | reply(error); 139 | return; 140 | } 141 | 142 | if ([helperURL checkResourceIsReachableAndReturnError:NULL] && ![fm removeItemAtURL:helperURL error:&error]) { 143 | reply(error); 144 | return; 145 | } 146 | 147 | if ([serviceURL checkResourceIsReachableAndReturnError:NULL] && ![fm removeItemAtURL:serviceURL error:&error]) { 148 | reply(error); 149 | return; 150 | } 151 | 152 | reply(nil); 153 | } 154 | 155 | @end 156 | -------------------------------------------------------------------------------- /Sources/CSAuthSampleHelper/CSASHelperConnectionInternal.h: -------------------------------------------------------------------------------- 1 | // 2 | // CSASHelperConnectionInternal.h 3 | // CSAuthSample 4 | // 5 | // Created by Charles Srstka on 4/19/20. 6 | // 7 | 8 | @import Foundation; 9 | #import "CSASHelperConnection.h" 10 | 11 | @interface CSASHelperConnection () 12 | 13 | @property (nonatomic) SEL currentCommand; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /Sources/CSAuthSampleHelper/CSASHelperConnectionWrapper.h: -------------------------------------------------------------------------------- 1 | // 2 | // CSASHelperConnectionWrapper.h 3 | // Helper Library 4 | // 5 | // Created by Charles Srstka on 4/19/20. 6 | // 7 | 8 | @import Foundation; 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | @class CSASHelperTool; 13 | @class CSASCommandSet; 14 | 15 | @interface CSASHelperConnectionWrapper : NSProxy 16 | 17 | - (instancetype)initWithConnectionClass:(Class)connectionClass 18 | xpcConnection:(NSXPCConnection *)xpcConnection 19 | helperTool:(CSASHelperTool *)helperTool 20 | commandSet:(CSASCommandSet *)commandSet; 21 | 22 | @end 23 | 24 | NS_ASSUME_NONNULL_END 25 | -------------------------------------------------------------------------------- /Sources/CSAuthSampleHelper/CSASHelperConnectionWrapper.m: -------------------------------------------------------------------------------- 1 | // 2 | // CSASHelperConnectionWrapper.m 3 | // Helper Library 4 | // 5 | // Created by Charles Srstka on 4/19/20. 6 | // 7 | 8 | @import ObjectiveC.runtime; 9 | #import "CSASHelperConnectionWrapper.h" 10 | #import "CSASHelperConnection.h" 11 | #import "CSASHelperTool.h" 12 | #import "CSASHelperToolInternal.h" 13 | #import "CSASHelperConnectionInternal.h" 14 | 15 | @interface CSASHelperConnectionWrapper () 16 | 17 | @property (nonatomic, readonly) CSASHelperConnection *connection; 18 | 19 | @end 20 | 21 | @implementation CSASHelperConnectionWrapper 22 | 23 | - (instancetype)initWithConnectionClass:(Class)connectionClass 24 | xpcConnection:(NSXPCConnection *)xpcConnection 25 | helperTool:(CSASHelperTool *)helperTool 26 | commandSet:(CSASCommandSet *)commandSet { 27 | self->_connection = [(CSASHelperConnection *)[connectionClass alloc] initWithConnection:xpcConnection 28 | helperTool:helperTool 29 | commandSet:commandSet]; 30 | 31 | return self; 32 | } 33 | 34 | - (BOOL)respondsToSelector:(SEL)selector { return [self.connection respondsToSelector:selector]; } 35 | - (NSMethodSignature *)methodSignatureForSelector:(SEL)sel { return [self.connection methodSignatureForSelector:sel]; } 36 | - (BOOL)conformsToProtocol:(Protocol *)protocol { return [self.connection conformsToProtocol:protocol]; } 37 | 38 | - (void)forwardInvocation:(NSInvocation *)invocation { 39 | self.connection.currentCommand = invocation.selector; 40 | [invocation invokeWithTarget:self.connection]; 41 | } 42 | 43 | @end 44 | -------------------------------------------------------------------------------- /Sources/CSAuthSampleHelper/CSASHelperTool.m: -------------------------------------------------------------------------------- 1 | // 2 | // CSASHelperTool.m 3 | // Helper Library 4 | // 5 | // Based on HelperTool.m from EvenBetterAuthorizationSample, 6 | // Copyright © 2013 Apple Computer. 7 | // 8 | // Created by Charles Srstka on 6/25/18. 9 | // 10 | 11 | @import Foundation; 12 | @import Darwin.POSIX.syslog; 13 | @import ObjectiveC.runtime; 14 | #import "CSASHelperTool.h" 15 | #import "CSASHelperToolInternal.h" 16 | #import "CSASHelperConnection.h" 17 | #import "CSASHelperConnectionWrapper.h" 18 | 19 | NS_ASSUME_NONNULL_BEGIN 20 | 21 | @interface CSASHelperTool() 22 | 23 | @property (nonatomic, readonly) CSASCommandSet *commandSet; 24 | @property (nonatomic, readonly) NSArray *requirements; 25 | @property (nonatomic, readonly) Class connectionClass; 26 | @property (nonatomic, readonly) NSXPCInterface *interface; 27 | @property (nonatomic, strong) CSASHelperConnectionWrapper *connectionWrapper; 28 | 29 | @property (nonatomic) NSUInteger connectionCount; 30 | 31 | @end 32 | 33 | @implementation CSASHelperTool 34 | 35 | - (instancetype)initWithHelperID:(NSString *)helperID 36 | commandSet:(CSASCommandSet *)commandSet 37 | senderRequirements:(nullable NSArray *)senderRequirements 38 | connectionClass:(Class)connectionClass 39 | protocol:(Protocol *)protocol { 40 | NSXPCInterface *interface = [NSXPCInterface interfaceWithProtocol:protocol]; 41 | 42 | return [self initWithHelperID:helperID 43 | commandSet:commandSet 44 | senderRequirements:senderRequirements 45 | connectionClass:connectionClass 46 | interface:interface]; 47 | } 48 | 49 | - (instancetype)initWithHelperID:(NSString *)helperID 50 | commandSet:(CSASCommandSet *)commandSet 51 | senderRequirements:(nullable NSArray *)_senderRequirements 52 | connectionClass:(Class)connectionClass // must be CSASHelperConnection subclass 53 | interface:(NSXPCInterface *)interface { 54 | self = [super init]; 55 | if (self == nil) { 56 | return nil; 57 | } 58 | 59 | NSArray *senderRequirements; 60 | if (_senderRequirements != nil) { 61 | senderRequirements = _senderRequirements; 62 | } else { 63 | senderRequirements = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"SMAuthorizedClients"]; 64 | } 65 | 66 | [CSASHelperTool configureDefaultsForInterface:interface commandSet:commandSet]; 67 | 68 | // Set up our XPC listener to handle requests on our Mach service. 69 | self->_listener = [[NSXPCListener alloc] initWithMachServiceName:helperID]; 70 | self->_listener.delegate = self; 71 | self->_commandSet = commandSet; 72 | self->_requirements = [senderRequirements copy]; 73 | self->_connectionClass = connectionClass; 74 | self->_interface = interface; 75 | self->_connectionCount = 0; 76 | self->_helperID = [helperID copy]; 77 | 78 | return self; 79 | } 80 | 81 | + (void)configureDefaultsForInterface:(NSXPCInterface *)interface commandSet:(CSASCommandSet *)commandSet { 82 | NSSet *data = [NSSet setWithObject:[NSData class]]; 83 | NSSet *string = [NSSet setWithObject:[NSString class]]; 84 | NSSet *endpoint = [NSSet setWithObject:[NSXPCListenerEndpoint class]]; 85 | NSSet *error = [NSSet setWithObject:[NSError class]]; 86 | 87 | SEL getEndpoint = @selector(getEndpointWithAuthorizationData:endpoint:); 88 | 89 | [interface setClasses:data forSelector:getEndpoint argumentIndex:0 ofReply:NO]; 90 | [interface setClasses:endpoint forSelector:getEndpoint argumentIndex:0 ofReply:YES]; 91 | [interface setClasses:error forSelector:getEndpoint argumentIndex:1 ofReply:YES]; 92 | 93 | SEL getVersion = @selector(getVersionWithReply:); 94 | 95 | [interface setClasses:string forSelector:getVersion argumentIndex:0 ofReply:YES]; 96 | [interface setClasses:error forSelector:getVersion argumentIndex:1 ofReply:YES]; 97 | 98 | SEL uninstall = @selector(uninstallHelperToolWithAuthorizationData:reply:); 99 | 100 | [interface setClasses:data forSelector:uninstall argumentIndex:0 ofReply:NO]; 101 | [interface setClasses:error forSelector:uninstall argumentIndex:0 ofReply:YES]; 102 | 103 | for (CSASAuthorizationRight *eachRight in commandSet.authorizationRights) { 104 | // getVersion is allowed not to have an auth parameter 105 | if (sel_isEqual(eachRight.selector, getVersion)) { 106 | continue; 107 | } 108 | 109 | if (![[interface classesForSelector:eachRight.selector argumentIndex:0 ofReply:NO] isEqualToSet:data]) { 110 | NSString *name = @"CSAuthSampleMissingAuthorizationData"; 111 | NSString *reason = @"All privileged operations must include an authorizationData parameter."; 112 | 113 | NSException *exception = [[NSException alloc] initWithName:name reason:reason userInfo:nil]; 114 | 115 | [exception raise]; 116 | } 117 | } 118 | } 119 | 120 | - (void)run { 121 | [self.listener resume]; 122 | 123 | [[NSRunLoop currentRunLoop] run]; 124 | 125 | // Should never get here. Crash if we do. 126 | exit(EXIT_FAILURE); 127 | } 128 | 129 | - (BOOL)listener:(NSXPCListener *)listener shouldAcceptNewConnection:(NSXPCConnection *)conn { 130 | assert(listener == self.listener); 131 | assert(conn != nil); 132 | 133 | if (![self shouldApproveConnection:conn]) { 134 | return NO; 135 | } 136 | 137 | self->_connectionWrapper = [[CSASHelperConnectionWrapper alloc] initWithConnectionClass:self.connectionClass 138 | xpcConnection:conn 139 | helperTool:self 140 | commandSet:self.commandSet]; 141 | 142 | conn.exportedInterface = self.interface; 143 | conn.exportedObject = self->_connectionWrapper; 144 | 145 | // Keep track of how many connections we have open. If the number reaches zero, exit the process. 146 | // This will prevent the helper tool from sticking around long after we're done with it. 147 | self.connectionCount++; 148 | conn.invalidationHandler = ^{ 149 | self.connectionCount--; 150 | 151 | if (self.connectionCount == 0) { 152 | exit(0); 153 | } 154 | }; 155 | 156 | [conn resume]; 157 | 158 | return YES; 159 | } 160 | 161 | - (BOOL)shouldApproveConnection:(NSXPCConnection *)connection { 162 | for (NSString *eachRequirement in self.requirements) { 163 | if ([self checkCodeSigningForConnection:connection requirement:eachRequirement error:NULL]) { 164 | return YES; 165 | } 166 | } 167 | 168 | return NO; 169 | } 170 | 171 | - (BOOL)checkCodeSigningForConnection:(NSXPCConnection *)connection 172 | requirement:(NSString *)req 173 | error:(__autoreleasing NSError * _Nullable * _Nullable)error { 174 | // Check the code signing requirement for the command. 175 | 176 | SecCodeRef secCode = NULL; 177 | SecRequirementRef secRequirement = NULL; 178 | 179 | @try { 180 | pid_t pid = connection.processIdentifier; 181 | NSDictionary *codeAttrs = @{ (__bridge NSString *)kSecGuestAttributePid: @(pid) }; 182 | 183 | OSStatus err = SecCodeCopyGuestWithAttributes(NULL, (__bridge CFDictionaryRef)codeAttrs, kSecCSDefaultFlags, &secCode); 184 | 185 | if (err != errSecSuccess) { 186 | if (error) *error = CSASConvertOSStatus(err); 187 | return NO; 188 | } 189 | 190 | err = SecRequirementCreateWithString((__bridge CFStringRef)req, kSecCSDefaultFlags, &secRequirement); 191 | 192 | if (err != errSecSuccess) { 193 | if (error) *error = CSASConvertOSStatus(err); 194 | return NO; 195 | } 196 | 197 | err = SecCodeCheckValidity(secCode, kSecCSDefaultFlags, secRequirement); 198 | 199 | if (err == errSecSuccess) { 200 | return YES; 201 | } else { 202 | if (error) *error = CSASConvertOSStatus(err); 203 | return NO; 204 | } 205 | } 206 | @finally { 207 | if (secCode != NULL) { 208 | CFRelease(secCode); 209 | } 210 | 211 | if (secRequirement != NULL) { 212 | CFRelease(secRequirement); 213 | } 214 | } 215 | } 216 | 217 | - (void)log:(NSString *)format, ... { 218 | va_list list; 219 | va_start(list, format); 220 | 221 | [self logWithPriority:LOG_NOTICE format:format arguments:list]; 222 | 223 | va_end(list); 224 | } 225 | 226 | - (void)logWithPriority:(int)priority format:(NSString *)format, ... { 227 | va_list list; 228 | va_start(list, format); 229 | 230 | [self logWithPriority:priority format:format arguments:list]; 231 | 232 | va_end(list); 233 | } 234 | 235 | - (void)logWithPriority:(int)priority format:(NSString *)format arguments:(va_list)args { 236 | NSString *string = [[NSString alloc] initWithFormat:format arguments:args]; 237 | 238 | syslog(LOG_NOTICE, "%s", string.UTF8String); 239 | } 240 | 241 | @end 242 | 243 | NS_ASSUME_NONNULL_END 244 | -------------------------------------------------------------------------------- /Sources/CSAuthSampleHelper/CSASHelperToolInternal.h: -------------------------------------------------------------------------------- 1 | // 2 | // CSASHelperToolInternal.h 3 | // CSAuthSample 4 | // 5 | // Created by Charles Srstka on 7/10/18. 6 | // 7 | 8 | #import "CSASHelperTool.h" 9 | 10 | @interface CSASHelperTool () 11 | 12 | @property (nonatomic, readonly) NSXPCListener *listener; 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /Sources/CSAuthSampleHelper/include/CSASHelperConnection.h: -------------------------------------------------------------------------------- 1 | // 2 | // CSASHelperConnection.h 3 | // Helper Tool 4 | // 5 | // Created by Charles Srstka on 7/1/18. 6 | // 7 | 8 | @import Foundation; 9 | @import CSAuthSampleCommon; 10 | #import "CSASHelperTool.h" 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | NS_SWIFT_NAME(HelperConnection) @interface CSASHelperConnection : NSObject 15 | 16 | @property (nonatomic, readonly) NSXPCConnection *connection; 17 | @property (nonatomic, readonly) CSASCommandSet *commandSet; 18 | 19 | - (instancetype)initWithConnection:(NSXPCConnection *)connection 20 | helperTool:(CSASHelperTool *)helperTool 21 | commandSet:(CSASCommandSet *)commandSet; 22 | 23 | // This method must be called at the beginning of every command, before executing any other code. 24 | // Returns nil on successful authorization, and an `NSError` otherwise. 25 | 26 | - (nullable NSError *)checkAuthorization:(NSData *)authData; 27 | 28 | @end 29 | 30 | NS_ASSUME_NONNULL_END 31 | -------------------------------------------------------------------------------- /Sources/CSAuthSampleHelper/include/CSASHelperTool.h: -------------------------------------------------------------------------------- 1 | // 2 | // CSASHelperTool.h 3 | // CSAuthSample 4 | // 5 | // Created by Charles Srstka on 6/25/18. 6 | // 7 | 8 | @import Foundation; 9 | #import "CSASCommon.h" 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | NS_SWIFT_NAME(HelperTool) @interface CSASHelperTool: NSObject 14 | 15 | @property (nonatomic, readonly) NSString *helperID; 16 | 17 | // If nil is passed for senderRequirements, the value for the 18 | // "SMAuthorizedClients" key will be read from the helper tool's 19 | // built-in Info.plist. 20 | 21 | - (instancetype)initWithHelperID:(NSString *)helperID 22 | commandSet:(CSASCommandSet *)commandSet 23 | senderRequirements:(nullable NSArray *)senderRequirements 24 | connectionClass:(Class)connectionClass // must be CSASHelperConnection subclass 25 | protocol:(Protocol *)protocol; 26 | 27 | - (instancetype)initWithHelperID:(NSString *)helperID 28 | commandSet:(CSASCommandSet *)commandSet 29 | senderRequirements:(nullable NSArray *)senderRequirements 30 | connectionClass:(Class)connectionClass // must be CSASHelperConnection subclass 31 | interface:(NSXPCInterface *)interface; 32 | 33 | - (void)run __attribute__((noreturn)); 34 | 35 | // Do any security checks prior to allowing a connection. 36 | // The default implementation checks the calling process's code signature 37 | // to make sure it matches one of the requirements passed in the 38 | // senderRequirements parameter when initializing the object. 39 | - (BOOL)shouldApproveConnection:(NSXPCConnection *)connection; 40 | 41 | // Check the code signature of an arbitrary NSXPCConnection and requirement. 42 | // This can be useful if you are establishing additional connections from the helper. 43 | - (BOOL)checkCodeSigningForConnection:(NSXPCConnection *)connection 44 | requirement:(NSString *)req 45 | error:(__autoreleasing NSError * _Nullable * _Nullable)error; 46 | 47 | // Logging methods that wrap the syslog(3) command. 48 | // Priority constants are defined in . Default is LOG_NOTICE. 49 | - (void)log:(NSString *)format, ... NS_FORMAT_FUNCTION(1, 2); 50 | - (void)logWithPriority:(int)priority format:(NSString *)format, ... NS_FORMAT_FUNCTION(2, 3); 51 | - (void)logWithPriority:(int)priority format:(NSString *)format arguments:(va_list)args NS_FORMAT_FUNCTION(2,0); 52 | 53 | @end 54 | 55 | NS_ASSUME_NONNULL_END 56 | -------------------------------------------------------------------------------- /Sources/CSAuthSampleHelper/include/module.modulemap: -------------------------------------------------------------------------------- 1 | module CSAuthSampleHelper { 2 | header "CSASHelperTool.h" 3 | header "CSASHelperConnection.h" 4 | export * 5 | } 6 | --------------------------------------------------------------------------------