h4ck1nH4ck1n  2022-04-05 04:10 字节时代 隐藏边栏 |   抢沙发  1,046 
文章评分 1 次,平均分 5.0

应用中有些shell命令可能需要sudo,也就是需要root权限来完成的,比如:

$ sudo spctl --master-disable # 开启软件安装的未知来源

碰到这类情况,直接使用是肯定不行的,那么我们该如何处理?

方案一

AuthorizationExecuteWithPrivileges()

这个函数是Security.framework中的一员,使用非常方便。而且还有一个封装得非常好的库 STPrivilegedTask,用法和NSTask几乎一样。

但是,虽然这个方法简单易用,但根据官方文档所示,AuthorizationExecuteWithPrivileges 函数的适用限制在macOS 10.1–10.7,也就是说早在OS X Lion 的时候就开始被Deprecated了,现在居然还能用也挺令人迷惑的。

经测试,在新版的系统使用该函数在某些情况下会提权失败,为了兼容以后的系统,不推荐使用这种方法,在本文也不做过多介绍。

优点:

  • 上手简单,使用方便

缺点:

  • Deprecated,不稳定、兼容性差

方案二

注册 LaunchdDaemon

注册LaunchdDaemon的常用方法是通过launchd工具加载一个与Daemon程序相关的标准的plist文件,由于launchd需要高权限运行,所以启动的子程序自然也是高权限运行。这个过程一般放在PKG 的安装脚本中完成,但当前越来越多的软件摒弃了PKG的打包方式,而是直接选择了打包成App来提升用户体验,此时安装辅助工具的工作也就要放到 App 运行过程中了。ServiceManagement的 API 可以完成这样的操作。

通过ServiceManagement注册LaunchdDaemon是苹果推荐的一种提权方式,官方也提供了一个SMJobBlessDemo,需要用苹果开发者账号编译。

具体思路是使用Security.frameworkServiceManagement.framework两个库,把需要root权限的操作封装成一个 Target,作为项目的子程序,把该子程序注册LaunchdDaemon

成为LaunchdDaemon后:

  • 子进程会被放在 /Library/PrivilegedHelperTools
  • 相应的plist配置文件会被放在 /Library/LaunchDaemonsLaunchd加载该子进程会需要读取该配置文件

过程较为复杂,可以先学习一下 Demo,然后尝试给自己的应用(这里以项目 MyApp 举例)添加:

  1. 关闭 App Sandbox

  2. 拷贝 Demo 中的 SMJobBlessUtil.py 到项目根目录

  3. 创建一个新 Target,选择 Command Line Tool,命名为 MyAppHelper

  4. 创建 MyAppHelper-Info.plist 文件并配置必要参数

  5. 创建 MyAppHelper-Launchd.plist 文件并配置必要参数

  6. 在 MyAppHelper Target 中配置 Other Linker Flags:
    -sectcreate TEXT info_plist MyAppHelper/MyAppHelper-Info.plist
    -sectcreate TEXT launchd_plist MyAppHelper/MyAppHelper-Launchd.plist

  7. 在 MyAppHelper Target 中配置 Product Module Name 和 Product Name 为 com.yourcompany.MyApp.Helper

  8. 选择 MyApp Target 配置 Copy Files,路径 Contents/Library/LaunchServices

  9. 生成签名参数(参考 Demo 中的 ReadMe.txt)

  10. 写入授权代码(参考 Demo 中的 SMJobBlessAppController.m)

  11. 运行测试,如果授权成功即可开始修改具体需要授权的部分代码行为

优点:

  • 目前主流的提权方案
  • 将高权限任务封装到独立的子程序按需调用,不会让整个程序处于高权限的状态,相对安全
  • 子程序可实现开机启动、长驻后台、高权限的需求

缺点:

  • 使用起来较为繁琐
  • 弹出认证对话框的提示内容是”需要安装帮助程序”,这样的提示不够友好
  • LaunchdDaemon及其配置文件是需要安装到 /Library 下的,可能存在卸载残留的问题

方案三

AppleScript

do shell script "..." with administrator privileges

省略号部分填入shell脚本即可。

AppleScript脚本在代码中有两种执行方式:

直接用传统的Process执行 /usr/bin/osascript -e "do shell ..."

let task = Process()
task.launchPath = "/usr/bin/osascript"
task.arguments = ["-e", "do shell script \"...\" with administrator privileges"]
task.launch()

通过NSAppleScript执行

let script = "do shell script \"...\" with administrator privileges"
var error: NSDictionary?
if let scriptObject = NSAppleScript(source: script) {
  let output: NSAppleEventDescriptor = scriptObject.executeAndReturnError(&error)
  print(output.stringValue)
}

使用传统的Process方法要注意:

  • 在所有shell执行完成后才会把stdout返回,因此当启动的是Daemon进程,就算使用ProcessNSFileHandleDataAvailable Notification,也无法把stdout分次读取出来,不能做到与用户进行良好的交互。
  • 这种方法的认证窗口提示信息是osascript wants to make changes.,对于小白用户来说会不会有一种这样的感觉,我明明安装的是 XXX.app,怎么来了一个osascript让我输入密码?
    • 解决方法:添加with prompt "xxx"参数自定义提示信息

NSAppleScript方法使用时认证窗口的提示信息是“APP_NAME wants to make changes.”,这样的提示较为友好,但也要注意:

  • NSAppleScript执行Daemon进程的话会直到Daemon退出才退出,即会一直占用线程
    • 解决方法:在子线程使用NSAppleScript,以免阻塞UI线程

优点:

  • 比上述“注册 LaunchdDaemon”的方法实现起来简单很多
  • 不用担心卸载残留的问题,因为全部内容都存放于 xxx.app
  

本文为原创文章,版权归所有,欢迎分享本文,转载请保留出处!

h4ck1n
H4ck1n 关注:0    粉丝:0 最后编辑于:2022-09-26
这个人很懒,什么都没写

发表评论

表情 格式 链接 私密 签到

扫一扫二维码分享