Apple takođe predlaže još jedan način za autentifikaciju ako povezani proces ima dozvole da pozove izloženu XPC metodu.
Kada aplikacija treba da izvrši radnje kao privilegovani korisnik, umesto da pokreće aplikaciju kao privilegovanog korisnika, obično instalira kao root HelperTool kao XPC servis koji se može pozvati iz aplikacije da izvrši te radnje. Međutim, aplikacija koja poziva servis treba da ima dovoljno autorizacije.
ShouldAcceptNewConnection uvek DA
Primer se može naći u EvenBetterAuthorizationSample. U App/AppDelegate.m pokušava da poveže sa HelperTool. A u HelperTool/HelperTool.m funkcija shouldAcceptNewConnectionneće proveriti nijedan od prethodno navedenih zahteva. Uvek će vraćati DA:
- (BOOL)listener:(NSXPCListener *)listener shouldAcceptNewConnection:(NSXPCConnection *)newConnection
// Called by our XPC listener when a new connection comes in. We configure the connection
// with our protocol and ourselves as the main object.
{
assert(listener == self.listener);
#pragma unused(listener)
assert(newConnection != nil);
newConnection.exportedInterface = [NSXPCInterface interfaceWithProtocol:@protocol(HelperToolProtocol)];
newConnection.exportedObject = self;
[newConnection resume];
return YES;
}
Za više informacija o tome kako pravilno konfigurisati ovu proveru:
Međutim, postoji neka autorizacija koja se dešava kada se pozove metoda iz HelperTool-a.
Funkcija applicationDidFinishLaunching iz App/AppDelegate.m će kreirati praznu autorizacionu referencu nakon što aplikacija počne. Ovo bi uvek trebalo da funkcioniše.
Zatim, pokušaće da doda neka prava toj autorizacionoj referenci pozivajući setupAuthorizationRights:
- (void)applicationDidFinishLaunching:(NSNotification *)note
{
[...]
err = AuthorizationCreate(NULL, NULL, 0, &self->_authRef);
if (err == errAuthorizationSuccess) {
err = AuthorizationMakeExternalForm(self->_authRef, &extForm);
}
if (err == errAuthorizationSuccess) {
self.authorization = [[NSData alloc] initWithBytes:&extForm length:sizeof(extForm)];
}
assert(err == errAuthorizationSuccess);
// If we successfully connected to Authorization Services, add definitions for our default
// rights (unless they're already in the database).
if (self->_authRef) {
[Common setupAuthorizationRights:self->_authRef];
}
[self.window makeKeyAndOrderFront:self];
}
Funkcija setupAuthorizationRights iz Common/Common.m će sačuvati prava aplikacije u bazi podataka auth /var/db/auth.db. Obratite pažnju da će dodati samo prava koja još nisu u bazi podataka:
+ (void)setupAuthorizationRights:(AuthorizationRef)authRef
// See comment in header.
{
assert(authRef != NULL);
[Common enumerateRightsUsingBlock:^(NSString * authRightName, id authRightDefault, NSString * authRightDesc) {
OSStatus blockErr;
// First get the right. If we get back errAuthorizationDenied that means there's
// no current definition, so we add our default one.
blockErr = AuthorizationRightGet([authRightName UTF8String], NULL);
if (blockErr == errAuthorizationDenied) {
blockErr = AuthorizationRightSet(
authRef, // authRef
[authRightName UTF8String], // rightName
(__bridge CFTypeRef) authRightDefault, // rightDefinition
(__bridge CFStringRef) authRightDesc, // descriptionKey
NULL, // bundle (NULL implies main bundle)
CFSTR("Common") // localeTableName
);
assert(blockErr == errAuthorizationSuccess);
} else {
// A right already exists (err == noErr) or any other error occurs, we
// assume that it has been set up in advance by the system administrator or
// this is the second time we've run. Either way, there's nothing more for
// us to do.
}
}];
}
Funkcija enumerateRightsUsingBlock se koristi za dobijanje dozvola aplikacija, koje su definisane u commandInfo:
static NSString * kCommandKeyAuthRightName = @"authRightName";
static NSString * kCommandKeyAuthRightDefault = @"authRightDefault";
static NSString * kCommandKeyAuthRightDesc = @"authRightDescription";
+ (NSDictionary *)commandInfo
{
static dispatch_once_t sOnceToken;
static NSDictionary * sCommandInfo;
dispatch_once(&sOnceToken, ^{
sCommandInfo = @{
NSStringFromSelector(@selector(readLicenseKeyAuthorization:withReply:)) : @{
kCommandKeyAuthRightName : @"com.example.apple-samplecode.EBAS.readLicenseKey",
kCommandKeyAuthRightDefault : @kAuthorizationRuleClassAllow,
kCommandKeyAuthRightDesc : NSLocalizedString(
@"EBAS is trying to read its license key.",
@"prompt shown when user is required to authorize to read the license key"
)
},
NSStringFromSelector(@selector(writeLicenseKey:authorization:withReply:)) : @{
kCommandKeyAuthRightName : @"com.example.apple-samplecode.EBAS.writeLicenseKey",
kCommandKeyAuthRightDefault : @kAuthorizationRuleAuthenticateAsAdmin,
kCommandKeyAuthRightDesc : NSLocalizedString(
@"EBAS is trying to write its license key.",
@"prompt shown when user is required to authorize to write the license key"
)
},
NSStringFromSelector(@selector(bindToLowNumberPortAuthorization:withReply:)) : @{
kCommandKeyAuthRightName : @"com.example.apple-samplecode.EBAS.startWebService",
kCommandKeyAuthRightDefault : @kAuthorizationRuleClassAllow,
kCommandKeyAuthRightDesc : NSLocalizedString(
@"EBAS is trying to start its web service.",
@"prompt shown when user is required to authorize to start the web service"
)
}
};
});
return sCommandInfo;
}
+ (NSString *)authorizationRightForCommand:(SEL)command
// See comment in header.
{
return [self commandInfo][NSStringFromSelector(command)][kCommandKeyAuthRightName];
}
+ (void)enumerateRightsUsingBlock:(void (^)(NSString * authRightName, id authRightDefault, NSString * authRightDesc))block
// Calls the supplied block with information about each known authorization right..
{
[self.commandInfo enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
#pragma unused(key)
#pragma unused(stop)
NSDictionary * commandDict;
NSString * authRightName;
id authRightDefault;
NSString * authRightDesc;
// If any of the following asserts fire it's likely that you've got a bug
// in sCommandInfo.
commandDict = (NSDictionary *) obj;
assert([commandDict isKindOfClass:[NSDictionary class]]);
authRightName = [commandDict objectForKey:kCommandKeyAuthRightName];
assert([authRightName isKindOfClass:[NSString class]]);
authRightDefault = [commandDict objectForKey:kCommandKeyAuthRightDefault];
assert(authRightDefault != nil);
authRightDesc = [commandDict objectForKey:kCommandKeyAuthRightDesc];
assert([authRightDesc isKindOfClass:[NSString class]]);
block(authRightName, authRightDefault, authRightDesc);
}];
}
Ovo znači da će na kraju ovog procesa, dozvole deklarisane unutar commandInfo biti sačuvane u /var/db/auth.db. Obratite pažnju da možete pronaći za svaku metodu koja će requirati autentifikaciju, ime dozvole i kCommandKeyAuthRightDefault. Potonji ukazuje ko može dobiti ovo pravo.
Postoje različiti opsezi koji ukazuju ko može pristupiti pravu. Neki od njih su definisani u AuthorizationDB.h (možete pronaći sve njih ovde), ali kao sažetak:
Ime
Vrednost
Opis
kAuthorizationRuleClassAllow
allow
Bilo ko
kAuthorizationRuleClassDeny
deny
Niko
kAuthorizationRuleIsAdmin
is-admin
Trenutni korisnik treba da bude admin (unutar admin grupe)
kAuthorizationRuleAuthenticateAsSessionUser
authenticate-session-owner
Traži od korisnika da se autentifikuje.
kAuthorizationRuleAuthenticateAsAdmin
authenticate-admin
Traži od korisnika da se autentifikuje. Mora biti admin (unutar admin grupe)
kAuthorizationRightRule
rule
Specifikujte pravila
kAuthorizationComment
comment
Specifikujte neke dodatne komentare o pravu
Provera prava
U HelperTool/HelperTool.m funkcija readLicenseKeyAuthorization proverava da li je pozivalac autorizovan da izvrši takvu metodu pozivajući funkciju checkAuthorization. Ova funkcija će proveriti da li authData poslata od strane pozivnog procesa ima ispravan format i zatim će proveriti šta je potrebno da se dobije pravo da se pozove specifična metoda. Ako sve prođe dobro, vraćena error će biti nil:
- (NSError *)checkAuthorization:(NSData *)authData command:(SEL)command
{
[...]
// First check that authData looks reasonable.
error = nil;
if ( (authData == nil) || ([authData length] != sizeof(AuthorizationExternalForm)) ) {
error = [NSError errorWithDomain:NSOSStatusErrorDomain code:paramErr userInfo:nil];
}
// Create an authorization ref from that the external form data contained within.
if (error == nil) {
err = AuthorizationCreateFromExternalForm([authData bytes], &authRef);
// Authorize the right associated with the command.
if (err == errAuthorizationSuccess) {
AuthorizationItem oneRight = { NULL, 0, NULL, 0 };
AuthorizationRights rights = { 1, &oneRight };
oneRight.name = [[Common authorizationRightForCommand:command] UTF8String];
assert(oneRight.name != NULL);
err = AuthorizationCopyRights(
authRef,
&rights,
NULL,
kAuthorizationFlagExtendRights | kAuthorizationFlagInteractionAllowed,
NULL
);
}
if (err != errAuthorizationSuccess) {
error = [NSError errorWithDomain:NSOSStatusErrorDomain code:err userInfo:nil];
}
}
if (authRef != NULL) {
junk = AuthorizationFree(authRef, 0);
assert(junk == errAuthorizationSuccess);
}
return error;
}
Napomena da da bi se proverili zahtevi za dobijanje prava da se pozove ta metoda, funkcija authorizationRightForCommand će samo proveriti prethodno komentarisani objekat commandInfo. Zatim će pozvati AuthorizationCopyRights da proveri da li ima prava da pozove funkciju (napomena da zastavice omogućavaju interakciju sa korisnikom).
U ovom slučaju, da bi se pozvala funkcija readLicenseKeyAuthorization, kCommandKeyAuthRightDefault je definisan kao @kAuthorizationRuleClassAllow. Tako da svako može da je pozove.
DB Informacije
Pomenuto je da se ove informacije čuvaju u /var/db/auth.db. Možete nabrojati sve sačuvane pravila sa:
Možete pronaći sve konfiguracije dozvolaovde, ali kombinacije koje neće zahtevati interakciju korisnika bi bile:
'authenticate-user': 'false'
Ovo je najdirektnija ključ. Ako je postavljeno na false, to označava da korisnik ne mora da pruži autentifikaciju da bi dobio ovo pravo.
Ovo se koristi u kombinaciji sa jednim od 2 ispod ili označavanjem grupe kojoj korisnik mora pripadati.
'allow-root': 'true'
Ako korisnik radi kao root korisnik (koji ima povišene privilegije), i ovaj ključ je postavljen na true, root korisnik bi potencijalno mogao dobiti ovo pravo bez dalјe autentifikacije. Međutim, obično, dobijanje statusa root korisnika već zahteva autentifikaciju, tako da ovo nije scenario "bez autentifikacije" za većinu korisnika.
'session-owner': 'true'
Ako je postavljeno na true, vlasnik sesije (trenutno prijavljeni korisnik) bi automatski dobio ovo pravo. Ovo bi moglo zaobići dodatnu autentifikaciju ako je korisnik već prijavljen.
'shared': 'true'
Ovaj ključ ne dodeljuje prava bez autentifikacije. Umesto toga, ako je postavljen na true, to znači da, jednom kada je pravo autentifikovano, može se deliti među više procesa bez potrebe da se svaki ponovo autentifikuje. Ali inicijalno dodeljivanje prava bi i dalje zahtevalo autentifikaciju osim ako nije kombinovano sa drugim ključevima kao što su 'authenticate-user': 'false'.
Proveravanje da li se koristi EvenBetterAuthorization
Ako pronađete funkciju: [HelperTool checkAuthorization:command:] verovatno je da proces koristi prethodno pomenutu šemu za autorizaciju:
Ako ova funkcija poziva funkcije kao što su AuthorizationCreateFromExternalForm, authorizationRightForCommand, AuthorizationCopyRights, AuhtorizationFree, koristi EvenBetterAuthorizationSample.
Proverite /var/db/auth.db da vidite da li je moguće dobiti dozvole za pozivanje neke privilegovane akcije bez interakcije korisnika.
Protokol komunikacije
Zatim, potrebno je pronaći šemu protokola kako biste mogli uspostaviti komunikaciju sa XPC servisom.
Funkcija shouldAcceptNewConnection ukazuje na protokol koji se izlaže:
U ovom slučaju, imamo isto kao u EvenBetterAuthorizationSample, proverite ovu liniju.
Znajući ime korišćenog protokola, moguće je izvršiti dump njegove definicije zaglavlja sa: