2012-09-14 10 views
6

Sto lavorando a un'app in cui l'utilizzo di eventi key-down globali sarà un requisito per il suo funzionamento. Inoltre, ho intenzione di distribuirlo rigorosamente tramite l'App Store. (È un'app per Mac, non per iOS.) Ho avuto un esempio di ascolto degli eventi globali che funzionano tramite addGlobalMonitorForEventsMatchingMask, ma con avvertenze.Eventi globali, il Mac App Store e la sandbox

Nota: sto facendo la scelta di utilizzare le API moderne e non fare affidamento sui precedenti metodi hotkey Carbon. Nel caso in cui alla fine siano deprecati, non voglio dover risolvere questo problema in seguito.

Il problema principale è che l'app deve essere attendibile affinché gli eventi globali possano essere rilevati. Altrimenti, l'accessibilità deve essere abilitata per tutte le app. Quando abilito l'accessibilità, gli eventi vengono rilevati correttamente. Questo requisito è documentato qui, http://developer.apple.com/library/mac/#DOCUMENTATION/Cocoa/Conceptual/EventOverview/MonitoringEvents/MonitoringEvents.html.

Preferisco che per i miei utenti, non dovranno abilitare l'accessibilità. Da altre ricerche che ho svolto, è possibile ottenere un'applicazione attendibile chiamando AXMakeProcessTrusted e riavviando l'applicazione.

Nel codice che sto utilizzando, non ottengo una richiesta di autenticazione. L'app verrà riavviata, ma non è ancora attendibile (probabilmente perché non ricevo un prompt di autenticazione). Ecco il mio codice per questa parte:

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

} 

Inoltre, ho guardato la documentazione per compiti di servizio Autorizzazione qui https://developer.apple.com/library/mac/#documentation/security/conceptual/authorization_concepts/03authtasks/authtasks.html#//apple_ref/doc/uid/TP30000995-CH206-BCIGAIAG.

La prima cosa che mi preoccupa che spunta è questa casella informativa, "Importante L'API dei servizi di autorizzazione non è supportata all'interno di una sandbox dell'app perché consente l'escalation dei privilegi."

Se questa API è richiesta per ottenere il prompt di autenticazione prima di riavviare l'app, sembra che io possa non essere in grado di ottenere eventi globali senza la funzione di accessibilità attivata.

In sintesi, le mie domande specifiche sono:

  1. C'è un errore nel mio codice di esempio su come arrivare a comparire il prompt di autenticazione ?

  2. Per visualizzare la richiesta di autenticazione, sono necessario per utilizzare l'API dei servizi di autorizzazione?

  3. E 'possibile, o non è possibile, avere un'app sandbox che ha l'accesso agli eventi globali?

+0

Sei riuscito ad aggirare questo? Ho trovato lo stesso problema :( –

+0

Non direttamente. Allo scopo di tasti di scelta rapida globali, ho dovuto seguire il percorso di utilizzo dei tasti di scelta rapida Cocoa. – Geuis

+0

Sto anche cercando un modo per richiedere all'utente. si eseguirebbe come root con le autorizzazioni necessarie quando l'app viene installata e chiama AXMakeProcessTrusted Se l'installer è il Mac App Store, è necessario un hook per richiedere questa autorizzazione. – Brennan

risposta

0

Ho trovato una soluzione potenziale su GitHub.

https://github.com/K8TIY/CW-Station

Esso ha un'applicazione ausiliaria che verrebbe eseguito in root per richiedere l'accesso per l'applicazione principale. È un po 'obsoleto e sta usando alcune funzioni che sono state deprecate, quindi sto lavorando per modernizzarlo. Sembra un buon punto di partenza.

1

Fondamentalmente, le restrizioni MAS richiedono il percorso di avere tge user attivando AX per tutti.

4

Prima di tutto, non è possibile consentire automaticamente a un'app di utilizzare l'API di accessibilità che funzionerebbe in un ambiente sandbox e quindi nell'app store. Il modo consigliato è di guidare semplicemente gli utenti in modo che possano facilmente attivarli autonomamente. La nuova chiamata API AXIsProcessTrustedWithOptions è esattamente per questo:

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

Ora, per la prima e la seconda domanda (solo per il gusto di completezza - ancora una volta non funzionerà in sandbox): L'idea alla base era che AXMakeProcessTrusted in realtà crei un nuovo auxiliary application eseguito come root dall'applicazione principale. Questa utility chiama quindi AXMakeProcessTrusted passando nell'eseguibile dell'applicazione principale. Infine, devi riavviare l'app principale. La chiamata API è stata deprecata in OSX 10.9.

Per generare un nuovo processo come root, è necessario utilizzare launchd utilizzando SMJobSubmit. Ciò richiederà all'utente un prompt di autenticazione che dichiari che un'applicazione sta tentando di installare uno strumento di supporto e se dovrebbe essere consentito. In concreto:

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

Per quanto riguarda il riavvio, questo di solito è fatto anche utilizzando un programma di utilità esterno al quale attende un'applicazione principale per finire e si avvia di nuovo (utilizzando PID). Se si utilizza si può riutilizzare quello esistente:

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

Un'altra opzione è quella di incidere il database SQLite /Library/Application Support/com.apple.TCC/TCC.db aggiungere i permessi manualmente utilizzando un aiutante ausiliario:

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]; 

Questo però porterà alla squalifica l'applicazione da essendo app store. In più è davvero solo un hack e il db/schema può cambiare in qualsiasi momento. Alcune applicazioni (ad esempio Divvy.app utilizzato per fare ciò) hanno utilizzato questo hack all'interno dello script di post installazione del programma di installazione dell'applicazione. In questo modo impedisce che la finestra di dialogo indichi che un'app richiede di installare uno strumento ausiliario.

+0

L'opzione numero 2 (che modifica direttamente il database di fiducia della privacy dell'utente) quasi certamente squalificherà la sua app dal Mac App Store in quanto costituirebbe un'utilizzo di API private, modificando un file di sistema e rompere la sandbox dell'applicazione. Apple ha anche dichiarato che eliminerà la validità di questo metodo se le persone inizieranno a spedire app che fanno affidamento su di esso – Draxillion

+0

@Draxillion - grazie! L'ho reso esplicito – fikovnik

+0

@Draxillion - ti dispiacerebbe dare un link alla dichiarazione di Apple? – fikovnik