Apple은 연결된 프로세스가 노출된 XPC 메서드를 호출할 권한이 있는지 인증하는 또 다른 방법을 제안합니다.
응용 프로그램이 특권 사용자로서 작업을 실행해야 하는 경우, 특권 사용자로서 응용 프로그램을 실행하는 대신, 일반적으로 응용 프로그램에서 해당 작업을 수행하기 위해 호출할 수 있는 XPC 서비스로 HelperTool을 루트로 설치합니다. 그러나 서비스를 호출하는 응용 프로그램은 충분한 권한을 가져야 합니다.
ShouldAcceptNewConnection 항상 YES
EvenBetterAuthorizationSample에서 예제를 찾을 수 있습니다. App/AppDelegate.m에서 HelperTool에 연결을 시도합니다. 그리고 HelperTool/HelperTool.m에서 shouldAcceptNewConnection 함수는 이전에 언급된 요구 사항을 확인하지 않습니다. 항상 YES를 반환합니다:
- (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에서 메소드를 호출할 때 권한이 부여됩니다.
App/AppDelegate.m의 applicationDidFinishLaunching 함수는 앱이 시작된 후에 빈 권한 참조를 만듭니다. 이것은 항상 작동해야 합니다.
그런 다음 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**를 찾을 수 있다는 점에 유의하십시오. 후자는 이 권한을 얻을 수 있는 사용자를 나타냅니다.
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에 저장된다고 언급되었습니다. 다음 명령을 사용하여 저장된 모든 규칙을 나열할 수 있습니다:
**여기**에서 모든 권한 구성을 찾을 수 있지만 사용자 상호 작용이 필요하지 않은 조합은 다음과 같습니다:
'authenticate-user': 'false'
이것은 가장 직접적인 키입니다. false로 설정되면 사용자가 이 권한을 얻기 위해 인증을 제공할 필요가 없음을 지정합니다.
이는 아래 2개 중 하나와 조합되거나 사용자가 속해야 하는 그룹을 나타내는 데 사용됩니다.
'allow-root': 'true'
사용자가 루트 사용자로 작동하는 경우(승격된 권한을 가진), 이 키가 true로 설정되어 있으면 루트 사용자는 추가 인증 없이 이 권한을 얻을 수 있습니다. 그러나 일반적으로 루트 사용자 상태에 도달하는 데는 이미 인증이 필요하므로 대부분의 사용자에게는 "인증 없음" 시나리오가 아닙니다.
'session-owner': 'true'
true로 설정되면 세션 소유자(현재 로그인한 사용자)가 이 권한을 자동으로 받게 됩니다. 사용자가 이미 로그인한 경우 추가 인증을 우회할 수 있습니다.
'shared': 'true'
이 키는 인증 없이 권한을 부여하지 않습니다. 대신 true로 설정되면 권한이 인증된 후 여러 프로세스 사이에서 공유될 수 있음을 의미합니다. 그러나 권한의 초기 부여는 'authenticate-user': 'false'와 같은 다른 키와 조합되지 않는 한 여전히 인증이 필요합니다.
만약 [HelperTool checkAuthorization:command:] 함수를 찾는다면, 해당 프로세스가 권한에 대해 이전에 언급한 스키마를 사용하고 있을 가능성이 높습니다:
이 함수가 AuthorizationCreateFromExternalForm, authorizationRightForCommand, AuthorizationCopyRights, AuhtorizationFree와 같은 함수를 호출한다면, EvenBetterAuthorizationSample를 사용하고 있습니다.
**/var/db/auth.db**를 확인하여 사용자 상호작용 없이 특정 권한 작업을 호출할 수 있는지 확인하세요.
프로토콜 통신
그런 다음, XPC 서비스와 통신을 수립할 수 있도록 프로토콜 스키마를 찾아야 합니다.
shouldAcceptNewConnection 함수는 내보내지는 프로토콜을 나타냅니다:
이 경우, EvenBetterAuthorizationSample과 동일한 것을 가지고 있습니다, 이 라인을 확인하세요.