"потому что он не защищен SIP" - ошибка события Apple в OSX Mojave

У меня есть популярное изолированное приложение в OSX, цель-c, который общается со сторонними приложениями посредством событий Apple (например, Adobe InDesign).

В OSX Mojave все ломается, потому что новый SIP от Apple ( https://developer.apple.com/library/archive/documentation/Security/Conceptual/System_Integrity_Protection_Guide/Introduction/Introduction.html) не разрешает связь.

Я не нашел никакого решения еще. Любая помощь приветствуется.

Это сообщение об ошибке

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

Это довольно хорошее резюме проблемы: https://www.felix-schwarz.org/blog/2018/06/apple-event-sandboxing-in-macos-mojave

2 ответа

Решение

Apple все еще должна работать над этим, она не идеальна, она не удобна для пользователя, она плохо документирована. Но вот рабочее решение.

Начиная с OSX 10.14 (Mojave), вы должны спросить OSX System Integrity Protection (SIP), разрешил ли пользователь вашему приложению общаться с другими.

Чтобы заставить его работать, вам нужно добавить запись в файл.plist вашего приложения:

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

Примечание: Вы не можете использовать больше записей для большего количества приложений. Это одна запись. Так что выбирайте свое описание с умом. Это описание будет отображаться в диалоговом окне Apple с просьбой принять его.

Если у вас есть служба XPC, как у меня, поместите ее в свое ГЛАВНОЕ приложение, а не в службу.

Теперь в вашем приложении - перед использованием событий Apple - проверьте текущее состояние (разрешены AppleEvents или нет). Я написал этот метод:

- (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;
}

Назовите это так:

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

Вы можете найти подробную информацию в AppleEvents.h - вот копия для используемого метода:

AEDeterminePermissionToAutomateTarget ()

Обсуждение: Определяет, может ли текущее приложение отправить AppleEvent с заданным eventClass и eventID приложению, описанному как targetAddressDesc.

Mac OS 10.14 и более поздние версии предъявляют дополнительные требования к приложениям, когда они отправляют AppleEvents другим приложениям, чтобы гарантировать, что пользователи знают и дают согласие на такой контроль или обмен информацией. Как правило, это связано с тем, что пользователю предлагается безопасный способ запроса при первой попытке приложения отправить AppleEvent в другое приложение.

Если пользователь соглашается, то это приложение может отправлять события в цель. Если пользователь не дает согласия, то любые будущие попытки отправить AppleEvents приведут к ошибке с возвратом errAEEventNotPermitted. Определенные AppleEvents разрешено отправлять без запроса пользователя. Передайте typeWildCard для eventClass и eventID, чтобы определить, разрешено ли отправлять каждое событие из этого приложения в цель.

Приложения могут определить, не отправляя AppleEvent целевому приложению, разрешено ли им отправлять AppleEvent к целевому объекту с помощью этой функции. Если askUserIfNeeded имеет значение true, и у этого приложения еще нет разрешения на отправку AppleEvents к цели, то пользователю будет предложено предоставить разрешение; если значение askUserIfNeeded - false и разрешение не было предоставлено, то будет возвращено значение errAEEventWouldRequireUserConsent.

Целевой объект AEAddressDesc должен ссылаться на уже работающее приложение.

Результаты

Если текущему приложению разрешено отправлять AppleEvent указанному объекту, то noErr будет возвращено. Если текущему приложению не разрешено отправлять событие, будет возвращено errAEEventNotPermitted. Если целевое приложение не запущено, будет возвращен procNotFound. Если askUserIfNeeded имеет значение false, и это приложение еще не разрешено отправлять AppleEvents к цели, тогда будет возвращено errAEEventWouldRequireUserConsent.

Mac OS X потоков:

Поток безопасно с версии 10.14. Не вызывайте эту функцию в главном потоке, потому что может потребоваться произвольное время для возврата, если пользователю необходимо запросить согласие.

Параметры:

цель:

Указатель на дескриптор адреса. Перед вызовом AEDeterminePermissionToAutomateTarget вы устанавливаете дескриптор для определения целевого приложения для события Apple. Дескриптор целевого адреса должен ссылаться на работающее приложение. Если целевое приложение находится на другом компьютере, то для этого пользователя на этом компьютере должны быть включены Remote AppleEvents.

theAEEventClass: класс события Apple, для которого необходимо определить разрешение.

theAEEventID: идентификатор события Apple, для которого необходимо определить разрешение.

askUserIfNeeded: логическое значение; если true, и если у этого приложения еще нет разрешения на отправку событий целевому приложению, то предложите пользователю получить разрешение. Если ложь, не предлагать пользователю.

Заключение:

Как упоминалось ранее, это не идеально.

  • Целевое приложение должно работать - в противном случае оно вернет -600
  • После отказа пользователь может активировать его только вручную - это не очень хорошо и гладко.
  • Его потокобезопасен, поэтому вы не должны вызывать его в основном потоке (кроме диалога для ручной активации)

Я взял отличный ответ @Pat_Morita и сделал его 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

    }

Давайте проясним разницу между SIP (защита целостности системы) и TCC (прозрачность, согласие, контроль: в основном все диалоговые окна "это приложение хочет получить доступ к этой услуге", управляемые в разделе "Безопасность и конфиденциальность"). Mojave включает в себя изменения обоих, и их влияние на события Apple и AppleScript описаны в заметках о выпуске Mojave.

Во-первых, SIP: сообщение в журнале о том, что Adobe Unit Types не загружается, действительно из-за изменений в SIP - Hardened Runtime означает, что глобально установленные сторонние добавления сценариев больше не разрешены - но если вы не используете принудительное использование модулей Adobe, это будет не влияет на ваше приложение.

Далее, TCC: если отправка событий Apple из вашего приложения не удалась, когда оно работало в 10.13, то ваша проблема, скорее всего, связана с TCC. Ошибка, чтобы следить за errAEEventNotPermitted, -1743: это означает, что TCC заблокировал событие. (-600 и -10004 указывают на проблемы с правами на доступ к песочнице.) Если вы сначала не видите диалог TCC, скорее всего, вам не хватает NSAppleEventsUsageDescription запись в вашем Info.plist: это необходимо при сборке с 10.14 SDK.

В общем, вы можете просто отправить событие в обычном режиме и обработать любые ошибки. Однако, если вы хотите предоставить дополнительный интерфейс ошибки - например, заблаговременное предупреждение о том, что пользователь запретил события Apple, - используйте AEDeterminePermissionToAutomateTarget,

Другие вопросы по тегам