Η Apple προτείνει επίσης έναν άλλο τρόπο για να πιστοποιήσει αν η διαδικασία σύνδεσης έχει δικαιώματα να καλέσει μια εκτεθειμένη μέθοδο XPC.
Όταν μια εφαρμογή χρειάζεται να εκτελέσει ενέργειες ως προνομιούχος χρήστης, αντί να εκτελεί την εφαρμογή ως προνομιούχος χρήστης, συνήθως εγκαθιστά ως root ένα HelperTool ως υπηρεσία XPC που μπορεί να καλείται από την εφαρμογή για να εκτελέσει αυτές τις ενέργειες. Ωστόσο, η εφαρμογή που καλεί την υπηρεσία θα πρέπει να έχει αρκετή εξουσιοδότηση.
ShouldAcceptNewConnection πάντα ΝΑΙ
Ένα παράδειγμα μπορεί να βρεθεί στο EvenBetterAuthorizationSample. Στο App/AppDelegate.m προσπαθεί να συνδεθεί με το HelperTool. Και στο HelperTool/HelperTool.m η συνάρτηση shouldAcceptNewConnectionδεν θα ελέγξει καμία από τις απαιτήσεις που αναφέρθηκαν προηγουμένως. Θα επιστρέφει πάντα ΝΑΙ:
- (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;
}
Για περισσότερες πληροφορίες σχετικά με το πώς να ρυθμίσετε σωστά αυτήν την επαλήθευση:
Ωστόσο, υπάρχει κάποια εξουσιοδότηση που συμβαίνει όταν καλείται μια μέθοδος από το HelperTool.
Η συνάρτηση applicationDidFinishLaunching από το App/AppDelegate.m θα δημιουργήσει μια κενή αναφορά εξουσιοδότησης μετά την εκκίνηση της εφαρμογής. Αυτό θα πρέπει πάντα να λειτουργεί.
Στη συνέχεια, θα προσπαθήσει να προσθέσει κάποια δικαιώματα σε αυτήν την αναφορά εξουσιοδότησης καλώντας το 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];
}
Η συνάρτηση setupAuthorizationRights από το Common/Common.m θα αποθηκεύσει στη βάση δεδομένων auth /var/db/auth.db τα δικαιώματα της εφαρμογής. Σημειώστε πώς θα προσθέσει μόνο τα δικαιώματα που δεν είναι ακόμη στη βάση δεδομένων:
+ (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.
}
}];
}
Η συνάρτηση enumerateRightsUsingBlock είναι αυτή που χρησιμοποιείται για να αποκτήσει τις άδειες των εφαρμογών, οι οποίες ορίζονται στο 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);
}];
}
Αυτό σημαίνει ότι στο τέλος αυτής της διαδικασίας, οι άδειες που δηλώνονται μέσα στο commandInfo θα αποθηκευτούν στο /var/db/auth.db. Σημειώστε πώς μπορείτε να βρείτε για κάθε μέθοδο που θα απαιτεί αυθεντικοποίηση, το όνομα άδειας και το kCommandKeyAuthRightDefault. Το τελευταίο υποδεικνύει ποιος μπορεί να αποκτήσει αυτό το δικαίωμα.
Υπάρχουν διαφορετικοί τομείς για να υποδείξουν ποιος μπορεί να έχει πρόσβαση σε ένα δικαίωμα. Ορισμένοι από αυτούς ορίζονται στο AuthorizationDB.h (μπορείτε να βρείτε όλους εδώ), αλλά ως σύνοψη:
Όνομα
Τιμή
Περιγραφή
kAuthorizationRuleClassAllow
allow
Οποιοσδήποτε
kAuthorizationRuleClassDeny
deny
Κανείς
kAuthorizationRuleIsAdmin
is-admin
Ο τρέχων χρήστης πρέπει να είναι διαχειριστής (μέσα στην ομάδα διαχειριστών)
kAuthorizationRuleAuthenticateAsSessionUser
authenticate-session-owner
Ρωτήστε τον χρήστη να αυθεντικοποιηθεί.
kAuthorizationRuleAuthenticateAsAdmin
authenticate-admin
Ρωτήστε τον χρήστη να αυθεντικοποιηθεί. Πρέπει να είναι διαχειριστής (μέσα στην ομάδα διαχειριστών)
kAuthorizationRightRule
rule
Καθορίστε κανόνες
kAuthorizationComment
comment
Καθορίστε μερικά επιπλέον σχόλια σχετικά με το δικαίωμα
Επαλήθευση Δικαιωμάτων
Στο HelperTool/HelperTool.m η συνάρτηση readLicenseKeyAuthorization ελέγχει αν ο καλών είναι εξουσιοδοτημένος να εκτελέσει αυτή τη μέθοδο καλώντας τη συνάρτηση checkAuthorization. Αυτή η συνάρτηση θα ελέγξει αν τα authData που αποστέλλονται από τη διαδικασία κλήσης έχουν σωστή μορφή και στη συνέχεια θα ελέγξει τι απαιτείται για να αποκτήσει το δικαίωμα να καλέσει τη συγκεκριμένη μέθοδο. Αν όλα πάνε καλά, το επιστρεφόμενο error θα είναι 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;
}
Σημειώστε ότι για να ελέγξετε τις απαιτήσεις για να αποκτήσετε το δικαίωμα να καλέσετε αυτή τη μέθοδο, η συνάρτηση authorizationRightForCommand θα ελέγξει απλώς το προηγουμένως σχολιασμένο αντικείμενο commandInfo. Στη συνέχεια, θα καλέσει AuthorizationCopyRights για να ελέγξει αν έχει τα δικαιώματα να καλέσει τη συνάρτηση (σημειώστε ότι οι σημαίες επιτρέπουν την αλληλεπίδραση με τον χρήστη).
Σε αυτή την περίπτωση, για να καλέσετε τη συνάρτηση readLicenseKeyAuthorization, το kCommandKeyAuthRightDefault ορίζεται σε @kAuthorizationRuleClassAllow. Έτσι, ο καθένας μπορεί να το καλέσει.
Πληροφορίες DB
Αναφέρθηκε ότι αυτές οι πληροφορίες αποθηκεύονται στο /var/db/auth.db. Μπορείτε να καταγράψετε όλους τους αποθηκευμένους κανόνες με:
You can find όλες τις ρυθμίσεις αδειώνεδώ, αλλά οι συνδυασμοί που δεν θα απαιτούν αλληλεπίδραση από τον χρήστη είναι:
'authenticate-user': 'false'
Αυτό είναι το πιο άμεσο κλειδί. Αν οριστεί σε false, καθορίζει ότι ένας χρήστης δεν χρειάζεται να παρέχει πιστοποίηση για να αποκτήσει αυτό το δικαίωμα.
Χρησιμοποιείται σε συνδυασμό με ένα από τα 2 παρακάτω ή υποδεικνύοντας μια ομάδα στην οποία πρέπει να ανήκει ο χρήστης.
'allow-root': 'true'
Αν ένας χρήστης λειτουργεί ως ο χρήστης root (ο οποίος έχει ανυψωμένα δικαιώματα), και αυτό το κλειδί είναι ορισμένο σε true, ο χρήστης root θα μπορούσε ενδεχομένως να αποκτήσει αυτό το δικαίωμα χωρίς περαιτέρω πιστοποίηση. Ωστόσο, συνήθως, η απόκτηση καθεστώτος χρήστη root απαιτεί ήδη πιστοποίηση, οπότε αυτό δεν είναι ένα σενάριο "χωρίς πιστοποίηση" για τους περισσότερους χρήστες.
'session-owner': 'true'
Αν οριστεί σε true, ο κάτοχος της συνεδρίας (ο τρέχων συνδεδεμένος χρήστης) θα αποκτήσει αυτό το δικαίωμα αυτόματα. Αυτό μπορεί να παρακάμψει πρόσθετη πιστοποίηση αν ο χρήστης είναι ήδη συνδεδεμένος.
'shared': 'true'
Αυτό το κλειδί δεν παρέχει δικαιώματα χωρίς πιστοποίηση. Αντίθετα, αν οριστεί σε true, σημαίνει ότι μόλις το δικαίωμα έχει πιστοποιηθεί, μπορεί να μοιραστεί μεταξύ πολλών διαδικασιών χωρίς να χρειάζεται η κάθε μία να επαναπιστοποιηθεί. Αλλά η αρχική χορήγηση του δικαιώματος θα απαιτεί ακόμα πιστοποίηση εκτός αν συνδυαστεί με άλλα κλειδιά όπως το 'authenticate-user': 'false'.
Έλεγχος αν χρησιμοποιείται το EvenBetterAuthorization
Αν βρείτε τη συνάρτηση: [HelperTool checkAuthorization:command:] είναι πιθανό ότι η διαδικασία χρησιμοποιεί το προηγουμένως αναφερόμενο σχήμα για εξουσιοδότηση:
Αν αυτή η συνάρτηση καλεί συναρτήσεις όπως AuthorizationCreateFromExternalForm, authorizationRightForCommand, AuthorizationCopyRights, AuhtorizationFree, χρησιμοποιεί το EvenBetterAuthorizationSample.
Ελέγξτε το /var/db/auth.db για να δείτε αν είναι δυνατό να αποκτήσετε άδειες για να καλέσετε κάποια προνομιακή ενέργεια χωρίς αλληλεπίδραση χρήστη.
Πρωτόκολλο Επικοινωνίας
Στη συνέχεια, πρέπει να βρείτε το σχήμα πρωτοκόλλου προκειμένου να μπορέσετε να καθιερώσετε επικοινωνία με την υπηρεσία XPC.
Η συνάρτηση shouldAcceptNewConnection υποδεικνύει το πρωτόκολλο που εξάγεται:
Σε αυτή την περίπτωση, έχουμε το ίδιο όπως στο EvenBetterAuthorizationSample, ελέγξτε αυτή τη γραμμή.
Γνωρίζοντας το όνομα του χρησιμοποιούμενου πρωτοκόλλου, είναι δυνατό να εκφορτώσετε τον ορισμό της κεφαλίδας του με:
Τέλος, πρέπει απλώς να γνωρίζουμε το όνομα της εκτεθειμένης Υπηρεσίας Mach προκειμένου να καθορίσουμε μια επικοινωνία μαζί της. Υπάρχουν αρκετοί τρόποι για να το βρούμε αυτό:
Στο [HelperTool init] όπου μπορείτε να δείτε την Υπηρεσία Mach που χρησιμοποιείται: