如何通过PolicyKit获取权限实现PyQt5程序的文件写入操作
PyQt5 + DBus + PolicyKit 无库提权读写文件实现
一、先理清PolicyKit配置文件的作用
你之前放置的三个文件各司其职:
.policy:定义权限动作规则,告诉PolicyKit你的程序需要什么权限、触发验证的条件,放在/usr/share/polkit-1/actions/.conf:自定义授权规则(比如免密码),优先级高于系统默认规则,放在/etc/polkit-1/localauthority/50-local.d/.service:DBus系统服务定义,指定以root身份运行的特权程序路径和服务名,放在/usr/share/dbus-1/system-services/
配置文件示例
1. 权限动作定义(com.example.FileWriter.policy)
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE policyconfig PUBLIC "-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN" "http://www.freedesktop.org/standards/PolicyKit/1/policyconfig.dtd"> <policyconfig> <action id="com.example.FileWriter.write"> <description>写入受保护文件</description> <message>需要管理员权限才能写入该文件</message> <defaults> <allow_any>auth_admin</allow_any> <allow_inactive>auth_admin</allow_inactive> <allow_active>auth_admin_keep</allow_active> </defaults> </action> </policyconfig>
auth_admin:需要管理员密码验证;auth_admin_keep:验证一次后保持权限一段时间- 将文件放到
/usr/share/polkit-1/actions/,权限设为644
2. DBus系统服务定义(com.example.FileWriter.service)
[D-BUS Service] Name=com.example.FileWriter Exec=/usr/bin/example-file-writer User=root SystemdService=example-file-writer.service
Name:DBus服务的唯一标识;Exec:特权操作程序的绝对路径;User=root:以root身份运行
3. 可选免密码授权规则(50-example-file-writer.conf)
如果需要特定用户免密码执行,创建这个文件:
[com.example.FileWriter.write] Identity=unix-user:your_username Action=com.example.FileWriter.write ResultAny=yes ResultInactive=yes ResultActive=yes
替换your_username为实际用户名,放到/etc/polkit-1/localauthority/50-local.d/
二、编写特权操作程序
不能让PyQt5主程序直接提权,必须把需要root权限的操作(写文件)封装成独立的DBus服务程序,以root身份运行。
创建/usr/bin/example-file-writer:
#!/usr/bin/env python3 import dbus import dbus.service from dbus.mainloop.glib import DBusGMainLoop import os from gi.repository import GLib class FileWriterService(dbus.service.Object): def __init__(self): bus_name = dbus.service.BusName('com.example.FileWriter', dbus.SystemBus()) super().__init__(bus_name, '/com/example/FileWriter') @dbus.service.method( 'com.example.FileWriter', in_signature='ss', out_signature='b', sender_keyword='sender' ) def WriteToFile(self, file_path, content, sender=None): try: with open(file_path, 'w', encoding='utf-8') as f: f.write(content) return True except Exception as e: print(f"写入失败: {str(e)}", file=os.stderr) return False if __name__ == '__main__': DBusGMainLoop(set_as_default=True) service = FileWriterService() loop = GLib.MainLoop() loop.run()
给文件加执行权限:sudo chmod +x /usr/bin/example-file-writer
用systemd管理特权服务(可选)
创建/etc/systemd/system/example-file-writer.service:
[Unit] Description=文件写入特权DBus服务 After=dbus.service [Service] Type=dbus BusName=com.example.FileWriter ExecStart=/usr/bin/example-file-writer User=root Restart=on-failure [Install] WantedBy=multi-user.target
启用并启动服务:
sudo systemctl daemon-reload sudo systemctl enable --now example-file-writer.service
三、PyQt5主程序整合逻辑
主程序需要完成:检查本地权限 → 无权限则请求PolicyKit授权 → 授权成功调用特权DBus服务。
核心代码片段
from PyQt5.QtWidgets import QApplication, QMainWindow, QPushButton, QMessageBox import os import dbus def has_write_permission(file_path): """检查当前用户是否有文件写入权限""" return os.path.exists(file_path) and os.access(file_path, os.W_OK) def request_policykit_auth(action_id): """通过DBus调用PolicyKit请求权限""" try: bus = dbus.SystemBus() polkit_auth = bus.get_object( 'org.freedesktop.PolicyKit1', '/org/freedesktop/PolicyKit1/Authority' ) auth_interface = dbus.Interface( polkit_auth, 'org.freedesktop.PolicyKit1.Authority' ) # 构造进程主题,标识当前程序 subject = ('unix-process', { 'pid': dbus.UInt32(os.getpid()), 'start-time': dbus.UInt64(0) }) # 请求授权,允许弹窗交互 result = auth_interface.CheckAuthorization( subject, action_id, {}, dbus.UInt32(1), '' ) return result[0] except dbus.DBusException as e: print(f"PolicyKit请求失败: {str(e)}") return False def call_privileged_write(file_path, content): """调用DBus特权服务写入文件""" try: bus = dbus.SystemBus() service = bus.get_object('com.example.FileWriter', '/com/example/FileWriter') write_interface = dbus.Interface(service, 'com.example.FileWriter') return write_interface.WriteToFile(file_path, content) except dbus.DBusException as e: print(f"特权服务调用失败: {str(e)}") return False class MainWindow(QMainWindow): def __init__(self): super().__init__() self.init_ui() def init_ui(self): self.setWindowTitle("特权文件写入工具") self.write_btn = QPushButton("写入受保护文件", self) self.write_btn.clicked.connect(self.handle_write) self.setCentralWidget(self.write_btn) self.resize(300, 100) def handle_write(self): target_file = '/etc/test_privileged.txt' content = "PyQt5通过PolicyKit提权写入的内容\n" if has_write_permission(target_file): # 直接写入 try: with open(target_file, 'a', encoding='utf-8') as f: f.write(content) QMessageBox.information(self, "成功", "文件写入完成") except Exception as e: QMessageBox.critical(self, "失败", f"写入错误: {str(e)}") else: # 请求提权 if request_policykit_auth('com.example.FileWriter.write'): if call_privileged_write(target_file, content): QMessageBox.information(self, "成功", "已通过提权写入文件") else: QMessageBox.critical(self, "失败", "特权写入操作失败") else: QMessageBox.critical(self, "失败", "权限验证未通过") if __name__ == '__main__': app = QApplication([]) window = MainWindow() window.show() app.exec_()
四、部署验证
- 把所有配置文件放到对应目录,重启DBus:
sudo systemctl restart dbus - 启动特权服务(如果用systemd)
- 运行PyQt5程序,点击写入按钮,无权限时会弹出PolicyKit验证窗口,输入密码后即可完成写入
内容的提问来源于stack exchange,提问作者omen
相关产品推荐
相关产品推荐

