macOS - SMJobBless 獲取管理員權限

*如果應用程序偶爾需要管理員權限執(zhí)行命令旱眯,可使用AppleScript接口
*但是如果應用程序需要頻繁使用管理員權限執(zhí)行命令趴拧,這時候就需要用到SMJobBless獲取長時間可用的權限了
*由于此方法不能使用App Sandbox奶卓,可能會影響上架App Store

1.添加Target,作為后臺程序(Helper)

File -> New -> Target... -> macOS -> Command Line Tool

SMJobBless - New Target.png

命名格式一般為 com.yourcompany.mainapp.helper

SMJobBless - New Target Name.png

設置Bundle Identifier和Targer名一致,并且建議重命名一下Helper目錄(例子里把"com.ljq.SMJobBlessApp.CommandHelper"目錄命名為"CommandHelper"了)

SMJobBless - Helper Bundle ID.png

2.創(chuàng)建plist文件

選中Helper的目錄玻孟,F(xiàn)ile -> New -> File...(或Commmand + N),創(chuàng)建Property List文件鳍征,命名為"CommandHelper-Info"

SMJobBless - New Property List File.png

選中CommandHelper-Info.plist右鍵 -> Open As -> Soure Code黍翎,復制粘貼以下內(nèi)容并修改:

CFBundleIdentifier:Helper程序的Bundle Identifier

CFBundleName:Helper程序的名稱

SMAuthorizedClients里的"com.ljq.SMJobBlessApp"為主Target的Bundle Identifier

SMAuthorizedClients里的"GN79NMG846"為開發(fā)者賬號的Team Identifier

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>CFBundleIdentifier</key>
    <string>com.ljq.SMJobBlessApp.CommandHelper</string>
    <key>CFBundleInfoDictionaryVersion</key>
    <string>6.0</string>
    <key>CFBundleName</key>
    <string>CommandHelper</string>
    <key>CFBundleVersion</key>
    <string>1.0</string>
    <key>SMAuthorizedClients</key>
    <array>
        <string>anchor apple generic and identifier &quot;com.ljq.SMJobBlessApp&quot; and (certificate leaf[field.1.2.840.113635.100.6.1.9] /* exists */ or certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ and certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */ and certificate leaf[subject.OU] = &quot;GN79NMG846&quot;)</string>
    </array>
</dict>
</plist>

如圖所示:

SMJobBless - Helper Info Plist.png

再次選中Helper的目錄,創(chuàng)建Property List文件艳丛,此次命名為"CommandHelper-Launchd"匣掸,并且和上面一樣復制粘貼以下內(nèi)容并修改:

Label:Helper程序的Bundle Identifier

MachServices:用于XPC通信服務

(其他為可選參數(shù))

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>com.ljq.SMJobBlessApp.CommandHelper</string>
    <key>RunAtLoad</key>
    <true/>
    <key>MachServices</key>
    <dict>
        <key>com.ljq.SMJobBlessApp.CommandHelper</key>
        <true/>
    </dict>
    <key>KeepAlive</key>
    <dict>
        <key>SuccessfulExit</key>
        <false/>
    </dict>
</dict>
</plist>

如圖所示:

SMJobBless - Helper Launchd Plist.png

3.配置主Target

主Target -> General -> Frameworks... 趟紊,添加SystemConfiguration.framework與Security.framework

SMJobBless - General Frameworks.png

主Target -> Signing & Capabilities ,移除App Sandbox(假如有)

SMJobBless - App Sandbox.png

主Target -> Build Phases -> Dependencies碰酝,添加Helper

SMJobBless - Dependencies.png

主Target -> Build Phases -> Copy Bundle Resources添加"CommandHelper-Info.plist"和"CommandHelper-Launchd.plist"

SMJobBless - Copy Bundle Resources.png

主Target -> Build Phases 左上角+號 New Copy Files Phase

SMJobBless - New Copy Files Phase.png

Destination 改為 Wrapper霎匈,Subpath 改為 "Contents/Library/LaunchServices" ,并在下面添加Helper程序

SMJobBless - Copy Files Wrapper.png

選中主Target的Info.plist文件送爸,修改"com.ljq.SMJobBlessApp.CommandHelper"和"GN79NMG846"并添加以下內(nèi)容:

<key>SMPrivilegedExecutables</key>
<dict>
    <key>com.ljq.SMJobBlessApp.CommandHelper</key>
    <string>anchor apple generic and identifier &quot;com.ljq.SMJobBlessApp.CommandHelper&quot; and (certificate leaf[field.1.2.840.113635.100.6.1.9] /* exists */ or certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ and certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */ and certificate leaf[subject.OU] = &quot;GN79NMG846&quot;)</string>
</dict>

如圖所示:

SMJobBless - Main Info Plist.png

注:如果出問題铛嘱,可以嘗試移除.entitlements文件試試,非必須的

SMJobBless - Remove Entitlements File.png

假如要移除entitlements文件袭厂,還需要在主Target -> Build Settings -> Signing -> Code Signing Entitlements 刪除其內(nèi)容

SMJobBless - Remove Entitlements Setting.png

4.配置Helper Target

Helper Target -> Build Settings -> Linking -> Other Linker Flags墨吓,修改以下內(nèi)容復制進去(點擊+直接粘貼長文本,回車后會自動換行)纹磺,下面兩個"xxx.plist"路徑要修改為項目實際的路徑帖烘,其他不用改

-sectcreate __TEXT __info_plist "$(SRCROOT)/CommandHelper/CommandHelper-Info.plist" -sectcreate __TEXT __launchd_plist "$(SRCROOT)/CommandHelper/CommandHelper-Launchd.plist"

如圖所示:

SMJobBless - Other Linker Flags.png

Helper Target -> Build Settings -> Packaging -> Info.plist File,修改以下路徑復制進去

$(SRCROOT)/CommandHelper/CommandHelper-Info.plist

如圖所示:

SMJobBless - Packaging.png

5.1.編寫安裝代碼(以下內(nèi)容建議直接查看demo源碼)

修改ViewController.m橄杨,運行時如果彈窗內(nèi)容為"The Helper Tool is available!"秘症,表示Helper程序已成功安裝,如果為其他則表示安裝失敗式矫,建議檢查以上流程

#import "ViewController.h"
#import <ServiceManagement/ServiceManagement.h>
#import <Security/Authorization.h>

@interface ViewController (){
    AuthorizationRef _authRef;
}
@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [self initHelper];
    });
}

- (void)initHelper
{
    BOOL isAvailable        = YES;
    NSError *error          = nil;
    NSString *installedPath = [NSString stringWithFormat:@"/Library/PrivilegedHelperTools/%@", @"com.ljq.SMJobBlessApp.CommandHelper"];
    if ([[NSFileManager defaultManager] fileExistsAtPath:installedPath] == NO) {
        OSStatus status     = AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment, kAuthorizationFlagDefaults, &self->_authRef);
        if (status == errAuthorizationSuccess) {
            isAvailable     = [self blessHelperWithLabel:@"com.ljq.SMJobBlessApp.CommandHelper" error:&error];
        } else {
            self->_authRef  = NULL;
            isAvailable     = NO;
            assert(NO);/* AuthorizationCreate really shouldn't fail. */
        }
    }
    NSAlert *alert          = [NSAlert new];
    alert.informativeText   =
    isAvailable ?
    @"The Helper Tool is available!" :
    [NSString stringWithFormat:@"Something went wrong! code:%ld %@",error.code,error.localizedDescription];
    [alert beginSheetModalForWindow:self.view.window completionHandler:nil];
}

- (BOOL)blessHelperWithLabel:(NSString *)label error:(NSError **)errorPtr
{
    BOOL result     = NO;
    NSError * error = nil;
    
    AuthorizationItem authItem      = { kSMRightBlessPrivilegedHelper, 0, NULL, 0 };
    AuthorizationRights authRights  = { 1, &authItem };
    AuthorizationFlags flags        =   kAuthorizationFlagDefaults              |
                                        kAuthorizationFlagInteractionAllowed    |
                                        kAuthorizationFlagPreAuthorize          |
                                        kAuthorizationFlagExtendRights;
                                               
    /* Obtain the right to install our privileged helper tool (kSMRightBlessPrivilegedHelper). */
    OSStatus status = AuthorizationCopyRights(self->_authRef, &authRights, kAuthorizationEmptyEnvironment, flags, NULL);
    if (status != errAuthorizationSuccess) {
        NSString *errMsg = (__bridge_transfer NSString *)SecCopyErrorMessageString(status, NULL);
        error = [NSError errorWithDomain:NSOSStatusErrorDomain code:status userInfo:@{NSLocalizedDescriptionKey:errMsg}];
    } else {
        CFErrorRef  cfError;
        /* This does all the work of verifying the helper tool against the application
         * and vice-versa. Once verification has passed, the embedded launchd.plist
         * is extracted and placed in /Library/LaunchDaemons and then loaded. The
         * executable is placed in /Library/PrivilegedHelperTools.
         */
        result = (BOOL)SMJobBless(kSMDomainSystemLaunchd, (__bridge CFStringRef)label, self->_authRef, &cfError);
        if (!result) {
            error = CFBridgingRelease(cfError);
        }
    }
    if (!result && (errorPtr != NULL) ) {
        assert(error != nil);
        *errorPtr = error;
    }
    return result;
}

@end

5.2.編寫XPC通信代碼

光是安裝Helper程序并不算得上是一個完整的代碼乡摹,此時需要使用到XPC接口來調(diào)用Helper程序

選中Helper的目錄添加頭文件(File -> New -> File... -> macOS -> Header File),命名為CommandHelperProtocol.h衷佃,并添加以下內(nèi)容

#import <Foundation/Foundation.h>

@protocol CommandHelperProtocol

- (void)executeCommand:(NSString *)command reply:(void(^)(int result))reply;

@end

再在此目錄添加一個類(File -> New -> File... -> macOS -> Cocoa Class)趟卸,命名為CommandHelper,并添加以下內(nèi)容

CommandHelper.h

#import <Foundation/Foundation.h>
#import "CommandHelperProtocol.h"

@interface CommandHelper : NSObject

- (void)run;

@end

CommandHelper.m

#import "CommandHelper.h"

@interface CommandHelper () <NSXPCListenerDelegate>

@property (nonatomic, strong) NSXPCListener *listener;

@end

@implementation CommandHelper

- (instancetype)init
{
    self = [super init];
    if (self) {
        self.listener           = [[NSXPCListener alloc] initWithMachServiceName:@"com.ljq.SMJobBlessApp.CommandHelper"];
        self.listener.delegate  = self;
    }
    return self;
}

- (BOOL)listener:(NSXPCListener *)listener shouldAcceptNewConnection:(NSXPCConnection *)newConnection {
    newConnection.exportedInterface = [NSXPCInterface interfaceWithProtocol:@protocol(CommandHelperProtocol)];
    newConnection.exportedObject    = self;
    [newConnection resume];
    return YES;
}

- (void)executeCommand:(NSString *)command reply:(nonnull void (^)(int))reply
{
    reply(system(command.UTF8String));
}

- (void)run
{
    [self.listener resume];
    [[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
    [[NSRunLoop currentRunLoop] run];
}

@end

最后在Helper目錄下的main.m中添加以下內(nèi)容

#import <Foundation/Foundation.h>
#import "CommandHelper.h"

int main(int argc, const char * argv[]) {
    [[CommandHelper new] run];
    return 0;
}

測試是否可用(更新了Helper代碼后氏义,需要在/Library/PrivilegedHelperTools/目錄下刪除此項目的Helper程序)

往ViewController.m添加以下代碼锄列,測試設置Wi-Fi的DNS命令

...
#import "CommandHelperProtocol.h"

...

@implementation ViewController

....

- (void)initHelper
{
    ...
    
    if (isAvailable) {
        // Reset DNS: "networksetup -setdnsservers Wi-Fi Empty"
        [self executeCommand:@"networksetup -setdnsservers Wi-Fi 8.8.8.8" reply:^(int result) {
            dispatch_async(dispatch_get_main_queue(), ^{
                NSAlert *alert          = [NSAlert new];
                alert.informativeText   =
                result == errSecSuccess ?
                @"Execute succeeded!" :
                [NSString stringWithFormat:@"Execute failed: %d",result];
                [alert beginSheetModalForWindow:self.view.window completionHandler:nil];
            });
        }];
    } else {
        NSAlert *alert          = [NSAlert new];
        alert.informativeText   = [NSString stringWithFormat:@"Something went wrong! code:%ld %@",error.code,error.localizedDescription];
        [alert beginSheetModalForWindow:self.view.window completionHandler:nil];
    }
}

- (void)executeCommand:(NSString *)command reply:(void(^)(int result))reply
{
    NSXPCConnection *xpcConnection      = [[NSXPCConnection alloc] initWithMachServiceName:@"com.ljq.SMJobBlessApp.CommandHelper"
                                                                                   options:NSXPCConnectionPrivileged];
    xpcConnection.remoteObjectInterface = [NSXPCInterface interfaceWithProtocol:@protocol(CommandHelperProtocol)];
    xpcConnection.exportedInterface     = [NSXPCInterface interfaceWithProtocol:@protocol(CommandHelperProtocol)];
    xpcConnection.exportedObject        = self;
    [[xpcConnection remoteObjectProxyWithErrorHandler:^(NSError * _Nonnull error) {
        // 無法連接XPC服務、Helper進程已退出或已崩潰
        NSLog(@"Get remote object proxy error: %@",error);
        reply((int)error.code);
    }] executeCommand:command reply:reply];
    [xpcConnection resume];
}

...

@end

假如程序順利執(zhí)行惯悠,就會把Wi-Fi下的DNS設置為8.8.8.8

SMJobBless - Test.png

6.卸載

新建一個'Uninstall.sh'腳本到主Target的目錄下邻邮,添加以下內(nèi)容并修改"com.ljq.SMJobBlessApp.CommandHelper"

#!/bin/bash

sudo launchctl unload /Library/LaunchDaemons/com.ljq.SMJobBlessApp.CommandHelper.plist
sudo rm /Library/LaunchDaemons/com.ljq.SMJobBlessApp.CommandHelper.plist
sudo rm /Library/PrivilegedHelperTools/com.ljq.SMJobBlessApp.CommandHelper

執(zhí)行'Uninstall.sh'腳本

NSString *scriptPath    = [[NSBundle mainBundle] pathForResource:@"Uninstall" ofType:@"sh"];
NSString *shellScript   = [NSString stringWithFormat:@"out=`sh \\\"%@\\\"`",scriptPath];
NSString *script        = [NSString stringWithFormat:@"do shell script \"%@\" with administrator privileges", shellScript];
NSDictionary *errorInfo         = nil;
NSAppleScript *appleScript      = [[NSAppleScript new] initWithSource:script];
NSAppleEventDescriptor *result  = [appleScript executeAndReturnError:&errorInfo];
if (errorInfo == nil || errorInfo.count == 0) {
    NSLog(@"Uninstall succeeded!!!");
} else {
    NSLog(@"Uninstall failed! script result: %@ , error: %@", [result stringValue], errorInfo.description);
}

示例代碼

https://github.com/LeungKinKeung/SMJobBlessDemo

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市克婶,隨后出現(xiàn)的幾起案子筒严,更是在濱河造成了極大的恐慌,老刑警劉巖情萤,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件鸭蛙,死亡現(xiàn)場離奇詭異,居然都是意外死亡筋岛,警方通過查閱死者的電腦和手機娶视,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人肪获,你說我怎么就攤上這事寝凌。” “怎么了孝赫?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵较木,是天一觀的道長。 經(jīng)常有香客問我青柄,道長伐债,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任刹前,我火速辦了婚禮泳赋,結果婚禮上雌桑,老公的妹妹穿的比我還像新娘喇喉。我一直安慰自己,他們只是感情好校坑,可當我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布拣技。 她就那樣靜靜地躺著,像睡著了一般耍目。 火紅的嫁衣襯著肌膚如雪膏斤。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天邪驮,我揣著相機與錄音莫辨,去河邊找鬼。 笑死毅访,一個胖子當著我的面吹牛沮榜,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播喻粹,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼蟆融,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了守呜?” 一聲冷哼從身側響起型酥,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎查乒,沒想到半個月后弥喉,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡玛迄,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年由境,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片憔晒。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡藻肄,死狀恐怖蔑舞,靈堂內(nèi)的尸體忽然破棺而出惠爽,到底是詐尸還是另有隱情陨瘩,我是刑警寧澤驹沿,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布胚嘲,位于F島的核電站非区,受9級特大地震影響朽色,放射性物質發(fā)生泄漏谤牡。R本人自食惡果不足惜实柠,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一婆翔、第九天 我趴在偏房一處隱蔽的房頂上張望拯杠。 院中可真熱鬧,春花似錦啃奴、人聲如沸潭陪。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽依溯。三九已至,卻和暖如春瘟则,著一層夾襖步出監(jiān)牢的瞬間黎炉,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工醋拧, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留慷嗜,地道東北人。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓丹壕,卻偏偏與公主長得像庆械,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子雀费,可洞房花燭夜當晚...
    茶點故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內(nèi)容