7

I have a properly sandboxed application in macOS, Objective-C that talks to third party applications by Apple Events (e.g. Adobe InDesign).

In macOS Mojave everything breaks cause Apple's new SIP (https://developer.apple.com/library/archive/documentation/Security/Conceptual/System_Integrity_Protection_Guide/Introduction/Introduction.html) doesn't allow the communication.

I didn't find any solution yet. Any help appreciated.

This is the error message:

skipped scripting addition "/Library/ScriptingAdditions/Adobe Unit Types.osax" because it is not SIP-protected.

This is a pretty good summary of the problem: https://www.felix-schwarz.org/blog/2018/06/apple-event-sandboxing-in-macos-mojave

4

3 回答 3

12

Apple still has to work on this, it's not perfect, it's not user friendly, it's not well documented. But here is a working solution.

Beginning from OSX 10.14 (Mojave) you have to ask OSX's System Integrity Protection (SIP) if the user allowed your application to communicate with others.

For making it to work you need to add an entry to your app's .plist file:

key: NSAppleEventsUsageDescription
value: [Some description why you need to use AppleEvents]

enter image description here

Note: You can not use more entries for more applications. Its one entry. So choose your description wisely. This description will be shown in Apple's dialog asking the user for acceptance.

If you have an XPC service like i do, place this in your MAIN app, not in the service.

Now in your application - before using Apple events - check for the current state (if AppleEvents allowed or not). I wrote this method:

- (BOOL)checkSIPforAppIdentifier:(NSString*)identifier {

    // First available from 10.14 Mojave
    if (@available(macOS 10.14, *)) {

        OSStatus status;
        NSAppleEventDescriptor *targetAppEventDescriptor;

        targetAppEventDescriptor = [NSAppleEventDescriptor descriptorWithBundleIdentifier:identifier];

        status = AEDeterminePermissionToAutomateTarget(targetAppEventDescriptor.aeDesc, typeWildCard, typeWildCard, true);

        switch (status) {
            case -600: //procNotFound
                NSLog(@"Not running app with id '%@'",identifier);
                break;

            case 0: // noErr
                NSLog(@"SIP check successfull for app with id '%@'",identifier);
                break;

            case -1744: // errAEEventWouldRequireUserConsent
                // This only appears if you send false for askUserIfNeeded
                NSLog(@"User consent required for app with id '%@'",identifier);
                break;

            case -1743: //errAEEventNotPermitted
                NSLog(@"User didn't allow usage for app with id '%@'",identifier);

                // Here you should present a dialog with a tutorial on how to activate it manually
                // This can be something like
                // Go to system preferences > security > privacy
                // Choose automation and active [APPNAME] for [APPNAME]

                return NO;

            default:
                break;
        }
    }
    return YES;
}

call it like this:

[self checkSIPforAppIdentifier:@"com.apple.mail"];

You may find detailed information in AppleEvents.h - here's a copy for the used method :

AEDeterminePermissionToAutomateTarget()

Discussion: Determines whether the current application is able to send an AppleEvent with the given eventClass and eventID to the application described as targetAddressDesc.

Mac OS 10.14 and later impose additional requirements on applications when they send AppleEvents to other applications in order to insure that users are aware of and consent to allowing such control or information exchange. Generally this involves the user being prompted in a secure fashion the first time an application attempts to send an AppleEvent to another application.

If the user consents then this application can send events to the target. If the user does not consent then any future attempts to send AppleEvents will result in a failure with errAEEventNotPermitted being returned. Certain AppleEvents are allowed to be sent without prompting the user. Pass typeWildCard for the eventClass and eventID to determine if every event is allowed to be sent from this application to the target.

Applications can determine, without sending an AppleEvent to a target application, whether they are allowed to send AppleEvents to the target with this function. If askUserIfNeeded is true, and this application does not yet have permission to send AppleEvents to the target, then the user will be asked if permission can be granted; if askUserIfNeeded is false and permission has not been granted, then errAEEventWouldRequireUserConsent will be returned.

The target AEAddressDesc must refer to an already running application.

Results

If the current application is permitted to send the given AppleEvent to the target, then noErr will be returned. If the current application is not permitted to send the event, errAEEventNotPermitted will be returned. If the target application is not running, then procNotFound will be returned. If askUserIfNeeded is false, and this application is not yet permitted to send AppleEvents to the target, then errAEEventWouldRequireUserConsent will be returned.

Mac OS X threading:

Thread safe since version 10.14. Do not call this function on your main thread because it may take arbitrarily long to return if the user needs to be prompted for consent.

Parameters:

target:

A pointer to an address descriptor. Before calling AEDeterminePermissionToAutomateTarget, you set the descriptor to identify the target application for the Apple event. The target address descriptor must refer to a running application. If the target application is on another machine, then Remote AppleEvents must be enabled on that machine for the user.

theAEEventClass: The event class of the Apple event to determine permission for.

theAEEventID: The event ID of the Apple event to determine permission for.

askUserIfNeeded: a Boolean; if true, and if this application does not yet have permission to send events to the target application, then prompt the user to obtain permission. If false, do not prompt the user.

Conclusion:

As mentioned before, it's not perfect.

  • The targetted App has to run - otherwise it will return -600
  • Once denied, the user can only manually activate it - this does not feel good and smooth.
  • Its thread safe, so you should not call it in the main thread (except the dialog for manual activation)
于 2018-09-26T14:55:36.620 回答
2

I took @Pat_Morita 's excellent answer and made it Swifty:

func checkSIPforAppIdentifier(_ sipIdentifier:String) -> Dictionary<String, Any> {

        var dictSIPResponse = [String:Any]()
        var targetAppEventDescriptor = NSAppleEventDescriptor(bundleIdentifier: sipIdentifier)
        var status = AEDeterminePermissionToAutomateTarget(targetAppEventDescriptor.aeDesc, typeWildCard, typeWildCard, true);

        switch (status) {
                case -600: //procNotFound
                    dictSIPResponse["isSipEnabled"] = false
                    dictSIPResponse["sipMessage"] = "Not running app with id \(sipIdentifier)"
                    break;

                case 0: // noErr
                    dictSIPResponse["isSipEnabled"] = true
                    dictSIPResponse["sipMessage"] = "SIP check successfull for app with id \(sipIdentifier)"
                    break;

                case -1744: // errAEEventWouldRequireUserConsent
                    // This only appears if you send false for askUserIfNeeded
                    dictSIPResponse["isSipEnabled"] = false
                    dictSIPResponse["sipMessage"] = "User consent required for app with id \(sipIdentifier)"
                    break;

                case -1743: //errAEEventNotPermitted

                    dictSIPResponse["isSipEnabled"] = false
                    dictSIPResponse["sipMessage"] = "User didn't allow usage for app with id \(sipIdentifier)"

                    // Here you should present a dialog with a tutorial on how to activate it manually
                    // This can be something like
                    // Go to system preferences > security > privacy
                    // Choose automation and active [APPNAME] for [APPNAME]


                default:
                    break;
            }

        return dictSIPResponse

    }
于 2020-02-11T18:26:40.690 回答
1

Let’s be clear about the distinction between SIP (System Integrity Protection) and TCC (Transparency, Consent, Control: basically, all the "this app wants to access this service" dialogs, managed in Security & Privacy). Mojave includes changes to both, and the effects they have on Apple events and AppleScript are covered in the Mojave release notes.

First, SIP: the log message about Adobe Unit Types not loading is indeed because of changes to SIP — the Hardened Runtime means that globally installed third-party scripting additions are no longer allowed — but unless you’re using Adobe’s unit coercions, that will not affect your app.

Next, TCC: if sending Apple events from your app is failing when it was working in 10.13, then your problem is most likely with TCC. The error to watch for is errAEEventNotPermitted, -1743: this means that TCC blocked an event. (-600 and -10004 indicate problems with sandbox entitlements.) If you are not seeing a TCC dialog first, you are most likely missing the NSAppleEventsUsageDescription entry in your Info.plist: this is required when building against the 10.14 SDK.

In general, you can simply send the event normally and handle any errors. However, if you wish to provide additional error UI — for example, an advance warning that the user denied Apple events — then use AEDeterminePermissionToAutomateTarget.

于 2018-10-24T03:49:24.087 回答