全局事件,Mac App Store和沙箱 [英] Global events, the Mac App Store, and the sandbox

查看:789
本文介绍了全局事件,Mac App Store和沙箱的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用一个应用程序,其中使用全局下键事件将是其操作的要求。此外,我计划通过App Store严格分发。 (它是一个Mac应用程序,而不是iOS)。我有一个例子,通过addGlobalMonitorForEventsMatchingMask监听全局事件,但是有警告。



注意:选择使用现代API并不依赖于早期的Carbon热键方法。如果他们最终被弃用,我不想在以后解决这个问题。



主要的问题是应用程序必须信任用于检测全局事件的顺序。否则,必须为所有应用启用辅助功能。启用辅助功能时,会成功检测事件。此要求在此处记录, http://developer.apple.com /library/mac/#DOCUMENTATION/Cocoa/Conceptual/EventOverview/MonitoringEvents/MonitoringEvents.html



我希望对于我的用户,他们不会必须启用辅助功能。从我进行的其他研究,你可以通过调用AXMakeProcessTrusted获得一个应用程序的信任,然后重新启动应用程序。



在我使用的代码,不获得身份验证提示。该应用程序将重新启动,但仍然不受信任(可能是因为我没有获得身份验证提示)。这是我的代码这部分:

   - (void)applicationDidFinishLaunching:(NSNotification *)aNotification 
{

if(!AXAPIEnabled()&&!AXIsProcessTrusted()){

NSString * appPath = [[NSBundle mainBundle] bundlePath];
AXError error = AXMakeProcessTrusted((CFStringRef)CFBridgingRetain(appPath));

[self restartApp];
}
}

- (void)restartApp {

NSTask * task = [[NSTask alloc] init];
NSMutableArray * args = [NSMutableArray array];
[args addObject:@ - c];
[args addObject:[NSString stringWithFormat:@sleep%d; open \%@ \,3,[[NSBundle mainBundle] bundlePath]]];
[task setLaunchPath:@/ bin / sh];
[task setArguments:args];
[任务启动];
[NSApp terminate:nil];

}

此外,我查看了授权服务此处的任务 https://developer.apple.com/library/mac/#documentation/security/conceptual/authorization_concepts/03authtasks/authtasks.html#//apple_ref/doc/uid/TP30000995-CH206-BCIGAIAG 。 p>

弹出的第一件事是这个信息框重要在应用程序沙箱中不支持授权服务API,因为它允许特权升级。



如果在重新启动应用程序之前需要此API来获取身份验证提示,则似乎我可能无法获得没有辅助功能的全局事件



总之,我的具体问题是:


  1. 在我的示例代码中有一个错误,如何获取
    身份验证提示出现?


  2. 为了让身份验证提示出现,我需要
    才能使用授权服务API吗?


  3. 有可能或不可能有沙箱应用程序有
    访问全局事件?



解决方案

方式你可以自动允许应用程序使用可访问API,这将工作在沙盒环境中,从而在应用商店。推荐的方法是简单地指导用户,以便他们可以轻松地启用它自己。新的API调用 AXIsProcessTrustedWithOptions 正是为了这样:

  NSDictionary * options = @ {(id)kAXTrustedCheckOptionPrompt:@YES}; 
AXIsProcessTrustedWithOptions((CFDictionaryRef)options);

现在,对于你的第一个和第二个问题(仅仅是为了完整性 - 工作在沙盒中):
AXMakeProcessTrusted 背后的想法是,你实际上创建了一个新的辅助应用程序。然后,该实用程序调用 AXMakeProcessTrusted 传入主应用程序的可执行文件。最后,你必须重新启动主应用程序。 API调用已在OSX 10.9中弃用。



要以root身份生成一个新进程,必须使用 launchd 使用 SMJobSubmit 。这将提示用户输入身份验证提示,说明应用程序正在尝试安装助手工具,以及是否应该允许。具体地:

  +(BOOL)makeTrustedWithError:(NSError **)error {
NSString * label = FMTStr %@。%@,kShiftItAppBundleId,@mktrusted);
NSString * command = [[NSBundle mainBundle] pathForAuxiliaryExecutable:@mktrusted];
AuthorizationItem authItem = {kSMRightModifySystemDaemons,0,NULL,0};
AuthorizationRights authRights = {1,& authItem};
AuthorizationFlags flags = kAuthorizationFlagInteractionAllowed | kAuthorizationFlagPreAuthorize | kAuthorizationFlagExtendRights;
AuthorizationRef auth;

if(AuthorizationCreate(& authRights,kAuthorizationEmptyEnvironment,flags,& auth)== errAuthorizationSuccess){
//这实际上很重要 - 如果从任何原因,它不会重新启动
//来检查正在运行的作业使用:sudo launchctl list
// sudo很重要,因为此作业在root下运行
SMJobRemove(kSMDomainSystemLaunchd,(CFStringRef)label ,auth,false,NULL);
//这实际上是一个新进程的launchd plist
// https://developer.apple.com/library/mac/documentation/Darwin/Reference/Manpages/man5/launchd.plist。 5.html#// apple_ref / doc / man / 5 / launchd.plist
NSDictionary * plist = @ {
@Label:label,
@RunAtLoad:@YES,
@ProgramArguments:@ [command],
@Debug:@YES
};
BOOL ret;
if(SMJobSubmit(kSMDomainSystemLaunchd,(CFDictionaryRef)plist,auth,(CFErrorRef *)error)){
FMTLogDebug(@Executed%@,command);
ret = YES;
} else {
FMTLogError(@无法执行%@作为已授权的进程:%@,命令,*错误);
ret = NO;
}
//从任何原因,这不工作很好
//似乎它在执行前删除了作业
// SMJobRemove(kSMDomainSystemLaunchd,(CFStringRef)label ,auth,false,NULL);
AuthorizationFree(auth,0);
return ret;
} else {
FMTLogError(@无法创建授权对象);
return NO;
}
}

对于重新启动,这通常也使用外部实用程序等待主应用程序完成并再次启动(通过使用PID)。如果您使用 sparkle框架,则可以重复使用现有的:

  +(void)relaunch {
NSString * relaunch = [[NSBundle bundleForClass:[SUUpdater class]] pathForResource:@relaunchofType:@];
NSString * path = [[NSBundle mainBundle] bundlePath];
NSString * pid = FMTStr(@%d,[[NSProcessInfo processInfo] processIdentifier]);
[NSTask launchedTaskWithLaunchPath:relaunch arguments:@ [path,pid]];
[NSApp terminate:self];
}

另一个选择是破解 / Library / Application Support / com.apple.TCC / TCC.db sqlite数据库使用辅助助手手动添加权限:

  NSString * sqlite = @/ usr / bin / sqlite3; 
NSString * sql = FMTStr(@INSERT or REPLACE INTO access values('kTCCServiceAccessibility','%@',1,1,1,NULL);,MY_BUNDLE_ID);
NSArray * args = @ [@/ Library / Application Support / com.apple.TCC / TCC.db,sql];
NSTask * task = [NSTask launchedTaskWithLaunchPath:sqlite arguments:args];
[task waitUntilExit];

但是,这会取消应用程式存取的资格。更多的它真的只是一个黑客和db /模式可以随时更改。某些应用程序(例如Divvy.app用于这样做)在应用程序安装程序后安装脚本中使用了这种攻击。这样可以防止应用程序请求安装辅助工具的对话框。


I'm working on an app where using global key-down events will be a requirement for its operation. Additionally, I plan on distributing this strictly via the App Store. (Its a Mac app, not iOS.) I've gotten an example of listening for the global events working via addGlobalMonitorForEventsMatchingMask, but with caveats.

Note: I am making the choice to use the modern API's and not rely on the earlier Carbon hotkey methods. In the event that they are deprecated eventually, I don't want to have to figure this problem out later.

The principle issue is that the app has to be trusted in order for global events to be detected. Otherwise, accessibility has to be enabled for all apps. When I enable accessibility, events are detected successfully. This requirement is documented here, http://developer.apple.com/library/mac/#DOCUMENTATION/Cocoa/Conceptual/EventOverview/MonitoringEvents/MonitoringEvents.html.

I would prefer that for my users, they will not have to enable accessibility. From other research I've done, you can get an application to be trusted by calling AXMakeProcessTrusted, then restarting the application.

In the code that I'm using, I do not get an authentication prompt. The app will restart, but is still not trusted (likely because I don't get an authentication prompt). Here's my code for this part:

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{

    if (!AXAPIEnabled() && !AXIsProcessTrusted()) {

        NSString *appPath = [[NSBundle mainBundle] bundlePath];
        AXError error = AXMakeProcessTrusted( (CFStringRef)CFBridgingRetain(appPath) );

        [self restartApp];
    }
}

- (void)restartApp{

    NSTask *task = [[NSTask alloc] init];
    NSMutableArray *args = [NSMutableArray array];
    [args addObject:@"-c"];
    [args addObject:[NSString stringWithFormat:@"sleep %d; open \"%@\"", 3, [[NSBundle mainBundle] bundlePath]]];
    [task setLaunchPath:@"/bin/sh"];
    [task setArguments:args];
    [task launch];
    [NSApp terminate:nil];

}

Further, I've looked at the documentation for Authorization Service Tasks here https://developer.apple.com/library/mac/#documentation/security/conceptual/authorization_concepts/03authtasks/authtasks.html#//apple_ref/doc/uid/TP30000995-CH206-BCIGAIAG.

The first thing that worries me that pops out is this info box, "Important The authorization services API is not supported within an app sandbox because it allows privilege escalation."

If this API is required to get the authentication prompt before restarting the app, it seems that I may not be able to get global events without the accessibility feature enabled.

In summary, my specific questions are:

  1. Is there an error in my sample code about how to get the authentication prompt to appear?

  2. In order to get the authentication prompt to appear, am I required to use the Authorization Services API?

  3. Is it possible, or not possible, to have a sandboxed app that has access to global events?

解决方案

First of all, there is no way you can automatically allow an app to use accessibility API which would work in a sandbox environment and thus in app store. The recommended way is to simply guide users so they can easily enable it themselves. The new API call AXIsProcessTrustedWithOptions is exactly for that:

        NSDictionary *options = @{(id) kAXTrustedCheckOptionPrompt : @YES};
        AXIsProcessTrustedWithOptions((CFDictionaryRef) options);

Now, to your first and second question (just for the sake of completeness - again it won't work in sandbox): The idea behind AXMakeProcessTrusted was that you actually create a new auxiliary application that you run as root from the main application. This utility then calls AXMakeProcessTrusted passing in the executable of the main application. Finally you have to restart the main app. The API call has been deprecated in OSX 10.9.

To spawn a new process as a root you have to use launchd using SMJobSubmit. This will prompt a user with an authentication prompt saying that an application is trying to install a helper tool and whether it should be allowed. Concretely:

    + (BOOL)makeTrustedWithError:(NSError **)error {
        NSString *label = FMTStr(@"%@.%@", kShiftItAppBundleId, @"mktrusted");
        NSString *command = [[NSBundle mainBundle] pathForAuxiliaryExecutable:@"mktrusted"];
        AuthorizationItem authItem = {kSMRightModifySystemDaemons, 0, NULL, 0};
        AuthorizationRights authRights = {1, &authItem};
        AuthorizationFlags flags = kAuthorizationFlagInteractionAllowed | kAuthorizationFlagPreAuthorize | kAuthorizationFlagExtendRights;
        AuthorizationRef auth;

        if (AuthorizationCreate(&authRights, kAuthorizationEmptyEnvironment, flags, &auth) == errAuthorizationSuccess) {
           // this is actually important - if from any reason the job was not removed, it won't relaunch
           // to check for the running jobs use: sudo launchctl list
           // the sudo is important since this job runs under root
           SMJobRemove(kSMDomainSystemLaunchd, (CFStringRef) label, auth, false, NULL);
           // this is actually the launchd plist for a new process
           // https://developer.apple.com/library/mac/documentation/Darwin/Reference/Manpages/man5/launchd.plist.5.html#//apple_ref/doc/man/5/launchd.plist
           NSDictionary *plist = @{
                   @"Label" : label,
                   @"RunAtLoad" : @YES,
                   @"ProgramArguments" : @[command],
                   @"Debug" : @YES
           };
           BOOL ret;
           if (SMJobSubmit(kSMDomainSystemLaunchd, (CFDictionaryRef) plist, auth, (CFErrorRef *) error)) {
               FMTLogDebug(@"Executed %@", command);
               ret = YES;
           } else {
               FMTLogError(@"Failed to execute %@ as priviledged process: %@", command, *error);
               ret = NO;
           }
           // From whatever reason this did not work very well
           // seems like it removed the job before it was executed
           // SMJobRemove(kSMDomainSystemLaunchd, (CFStringRef) label, auth, false, NULL);
           AuthorizationFree(auth, 0);
           return ret;
        } else {
           FMTLogError(@"Unable to create authorization object");
           return NO;
        }
    }

As for the restarting, this is usually done also using an external utility to which waits for a main application to finish and starts it again (by using PID). If you use sparkle framework you can reuse the existing one:

     + (void) relaunch {
         NSString *relaunch = [[NSBundle bundleForClass:[SUUpdater class]] pathForResource:@"relaunch" ofType:@""];
         NSString *path = [[NSBundle mainBundle] bundlePath];
         NSString *pid = FMTStr(@"%d", [[NSProcessInfo processInfo] processIdentifier]);
         [NSTask launchedTaskWithLaunchPath:relaunch arguments:@[path, pid]];
         [NSApp terminate:self];
    }

Another option is to hack the /Library/Application Support/com.apple.TCC/TCC.db sqlite database add the permissions manually using an auxiliary helper:

    NSString *sqlite = @"/usr/bin/sqlite3";
    NSString *sql = FMTStr(@"INSERT or REPLACE INTO access values ('kTCCServiceAccessibility', '%@', 1, 1, 1, NULL);", MY_BUNDLE_ID); 
    NSArray *args = @[@"/Library/Application Support/com.apple.TCC/TCC.db", sql];
    NSTask *task = [NSTask launchedTaskWithLaunchPath:sqlite arguments:args];
    [task waitUntilExit];

This however will disqualify the app from being app store. More over it is really just a hack and the db / schema can change any time. Some applications (e.g. Divvy.app used to do this) used this hack within the application installer post install script. This way prevents the dialog telling that an app is requesting to install an auxiliary tool.

这篇关于全局事件,Mac App Store和沙箱的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆