dbus 和 policykit 實例篇(python)
使用policykit 的程序一般都有一個dbus daemon程序來完成相關操作,這個dbus daemon 會在系統注冊一個system bus 服務名,用于響應要求root privileged的操作,當dbus請求到達時會先驗證請求程序是否有相應的權限來調用這個操作(方法),而這是在.conf文件中定義的(后面說明)。
首先定義個System Dbus daemon,寫一個.service文件來啟動我們的daemon
?org.example.foo.service
文件放置目錄:/usr/share/dbus-1/system-services
2?Name=org.example.foo
3?Exec=/usr/local/libexec/policykit_dbus_foo_daemon.py
4?User=root
?
其中Name是注冊的SystemBus 服務名
Exec 是daemon 程序所在路徑
我們以root權限啟動
當有程序請求org.example.foo服務時,系統會自動以root啟動我們的daemon。
相關信息看這里D-Bus system bus activation
?注: SessionBus 的 'org.freedesktop.PolicyKit.AuthenticationAgent' 的服務,只有在請求認證的時候才自動啟動,打開過一段時間會自動關閉。
再看我們的daemon程序
policykit_dbus_foo_daemon.py
文件放置目錄:/usr/local/libexec
?2?#?-*-?coding:?UTF-8?-*-
?3?"""
?4?Author:?joe.zhou
?5?"""
?6?import?os
?7?import?sys
?8?import?gobject
?9?import?dbus
10?import?dbus.service
11?import?dbus.mainloop.glib
12?
13?class?NotPrivilegedException?(dbus.DBusException):
14?????_dbus_error_name?=?"org.example.foo.dbus.service.PolKit.NotPrivilegedException"
15?????def?__init__?(self,?action_id,?*p,?**k):
16?????????self._dbus_error_name?=?self.__class__._dbus_error_name?+?"."?+?action_id
17?????????super?(NotPrivilegedException,?self).__init__?(*p,?**k)
18?????????
19?def?require_auth?(action_id):
20?????def?require_auth_decorator(func):????????
21?????????def?_func(*args,**kwds):
22?????????????revoke_if_one_shot?=?True
23?????????????system_bus?=?dbus.SystemBus()
24?????????????auth_obj?=?system_bus.get_object('org.freedesktop.PolicyKit','/')
25?????????????auth_interface?=?dbus.Interface(auth_obj,'org.freedesktop.PolicyKit')
26?????????????try:
27?????????????????dbus_name?=?kwds['sender_keyword']????????????????
28?????????????except:
29?????????????????raise?NotPrivilegedException?(action_id)
30?????????????granted?=?auth_interface.IsSystemBusNameAuthorized(action_id,dbus_name,revoke_if_one_shot)
31?????????????if?granted?!=?'yes':
32?????????????????raise?NotPrivilegedException?(action_id)
33?
34?????????????return?func(*args,**kwds)
35?????????????
36?????????_func.func_name?=?func.func_name
37?????????_func.__name__?=?func.__name__
38?????????_func.__doc__?=?func.__doc__????????
39?????????return?_func????
40?????return?require_auth_decorator
41?
42?'''
43?A?D-Bus?service?that?PolicyKit?controls?access?to.
44?'''
45?class?PolicyKitFooMechanism(dbus.service.Object):??????
46?????SERVICE_NAME?=?'org.example.foo'
47?????SERVICE_PATH?=?'/org/example/foo'
48?????INTERFACE_NAME?=?'org.example.foo'
49?
50?????def?__init__(self,?conn,?object_path=SERVICE_PATH):
51?????????dbus.service.Object.__init__(self,?conn,?object_path)
52?
53?????@dbus.service.method(dbus_interface=INTERFACE_NAME,?in_signature='ss',out_signature='s',sender_keyword='sender')
54?????def?WriteFile(self,?filepath,?contents,sender=None):
55?????????'''
56?????????Write?the?contents?to?a?file?that?requires?sudo/root?access?to?do?so.
57?????????PolicyKit?will?not?allow?this?function?to?be?called?without?sudo/root?
58?????????access,?and?will?ask?the?user?to?authenticate?if?necessary,?when?
59?????????the?application?calls?PolicyKit's?ObtainAuthentication().
60?????????'''
61?????????@require_auth('org.example.foo.modify')
62?????????def?_write_file(filepath,contents,sender_keyword?=?None):
63?????????????f?=?open(filepath,?'w')
64?????????????f.write(contents)
65?????????????f.close()
66?????????????return?'done'
67?????????return?_write_file(filepath,contents,sender_keyword?=?sender)?????
68?????
69?????@dbus.service.method(dbus_interface=INTERFACE_NAME,?in_signature='s',out_signature='as',sender_keyword='sender')
70?????def?RunCmd(self,?cmdStr,?sender=None):
71?????????@require_auth('org.example.foo.sys')
72?????????def?_run_cmd(cmdStr,sender_keyword?=?None):
73?????????????f?=?os.popen(cmdStr)
74?????????????output?=?f.readlines()
75?????????????f.close()
76?????????????return?output????????
77?????????return?_run_cmd(cmdStr,sender_keyword?=?sender)
78?
79?????@dbus.service.method(dbus_interface=INTERFACE_NAME,in_signature='',?out_signature='',sender_keyword='sender')
80?????def?Exit(self,?sender=None):
81?????????@require_auth('org.example.foo.sys')
82?????????def?_exit(sender_keyword?=?None):
83?????????????loop.quit()
84?????????return?_exit(sender_keyword?=?sender)
85?????????
86?????@dbus.service.method(dbus_interface=INTERFACE_NAME,in_signature='',?out_signature='s')????
87?????def?hello(self):
88?????????return?'hello'
89?
90?if?__name__?==?'__main__':
91?????dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
92?????bus?=?dbus.SystemBus()
93?????name?=?dbus.service.BusName(PolicyKitFooMechanism.SERVICE_NAME,?bus)
94?????the_object?=?PolicyKitFooMechanism(bus)
95?????loop?=?gobject.MainLoop()
96?????loop.run()
97?
98?
?
在daemon程序中定義了三個需要權限的操作,一個不需要權限的操作,也定義了操作(方法)要求驗證的action id,程序請求寫文件操作時需先向org.freedesktop.PolicyKit.AuthenticationAgent 請求對應的 action id權限才能再進行調用,
否則會提示沒有權限,hello操作不需要操作權限,兩者區別在于來請求時是否先向 org.freedesktop.Policykit 檢測這個 dbus 請求是否有 previleged。
具體是調用? IsSystemBusNameAuthorized 方法來驗證,通過則返回'yes',否則 返回其它字符串
使用命令以下命令查看方法的 IsSystemBusNameAuthorized 詳細 introspec
?
在這里值得注意的是如果你定義了一系列的系統級調用操作(以root方式啟動前面的程序,但去除了前面的@require_auth 部分),你必須保證每個操作要進行權限驗證,即加上這個東西@require_auth('org.example.foo.sys')
如果你定義了寫文件的dbus操作,但是沒有進行權限驗證的話,一個普通用戶的dbus 調用請求也會調用通過,即普通用戶可以隨意改寫任何文件,這是很危險的
你也可以嘗試把前面的@require_auth部分去掉,再啟動服務,用 d-feet? 就可以調用WriteFile方法隨意地在根目錄上寫入文件
?
?
--題外話——
本想將程序寫成這種形式的
2?@dbus.service.method(dbus_interface=INTERFACE_NAME,in_signature='',?out_signature='',sender_keyword='sender')
3?def?Exit(self,?sender=None):
4?????loop.quit()
?這樣寫,用d-feet 看了下,服務起不來,調了很久也找不出原因,無奈寫成這種冗余的方式 --!,看能否有高手指點下,不盡感激!!
?
接著定義誰可以調用這些操作(方法),在.conf 文件定義
org.example.foo.conf
文件放置目錄:/etc/dbus-1/system.d
?2?
?3?<!DOCTYPE?busconfig?PUBLIC
?4??"-//freedesktop//DTD?D-BUS?Bus?Configuration?1.0//EN"
?5??"http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
?6?<busconfig>
?7?
?8???<!--?Only?root?can?own?the?service?-->
?9???<policy?user="root">
10?????<allow?own="org.example.foo"/>
11?????<allow?send_interface="org.example.foo"/>
12???</policy>
13?
14???<!--?allow?Introspectable?--><!-- 任何人都可以調用,在后面使用.policy進行約束-->
15???<policy?context="default">
16?????<allow?send_interface="org.example.foo"/>
17?????<allow?send_interface="org.freedesktop.DBus.Introspectable"/>
18???</policy>
19?
20?</busconfig>
21?
?
?
再跟著是定義相關的 action id了,在.policy 文件定義
其中定義授權認證的方式和時效可以有以下幾種
auth_self$$
auth_admin$$
yes
其中加$$的可以附加后綴?_one_shot,_keep_session,_keep_always
其意義字面已經很清楚了
?另外也可以看看 man policykit.conf
不會寫?參照/usr/share/PolicyKit/policy 目錄下一堆 .policy文件總會了吧
寫好后可以用工具 polkit-policy-file-validate 驗證下是否有效
org.example.foo.policy
文件放置目錄:/usr/share/PolicyKit/policy
?2?<!DOCTYPE?policyconfig?PUBLIC
?3??"-//freedesktop//DTD?PolicyKit?Policy?Configuration?1.0//EN"
?4??"http://www.freedesktop.org/standards/PolicyKit/1.0/policyconfig.dtd">
?5?<policyconfig>
?6?
?7???<vendor>Example?Application</vendor>
?8???<vendor_url>http://fedoraproject.org/example</vendor_url>
?9?
10????<action?id="org.example.foo.modify">
11?????<description>Example?Write?Access</description>
12?????<message>System?policy?prevents?write?access?to?the?Example?service</message>
13?????<defaults>
14???????<allow_inactive>no</allow_inactive>
15???????<allow_active>auth_admin</allow_active>
16?????</defaults>
17???</action>
18?
19???<action?id="org.example.foo.sys">
20?????<description>Example?system?action</description>
21?????<message>System?policy?prevents?do?system?action?to?the?Example?service</message>
22?????<defaults>
23???????<allow_inactive>no</allow_inactive>
24???????<allow_active>auth_admin</allow_active>
25?????</defaults>
26???</action>
27?
28??
29?</policyconfig>
?
做好以上工作,我們可以寫我們的調用端程序了
?
?2?#?-*-?coding:?UTF-8?-*-
?3?import?os
?4?import?sys
?5?import?gobject
?6?import?dbus
?7?import?dbus.service
?8?import?dbus.mainloop.glib
?9?import?traceback
10?
11?def?auth_proxy?(func):
12?????DBUSNAME?=?'org.freedesktop.PolicyKit.AuthenticationAgent'
13?????DBUSPATH?=?'/'?
14?????DBUSINTERFACE?=?'org.freedesktop.PolicyKit.AuthenticationAgent'
15?????EXC_NAME?=??"org.example.foo.dbus.service.PolKit.NotPrivilegedException"??
16?????def?auth_proxy_wrapper?(*args,?**kwds):
17?????????try:
18?????????????return?func?(*args,?**kwds)
19?????????except?dbus.DBusException,?e:
20?????????????exc_name?=?e.get_dbus_name?()
21?????????????if?exc_name.startswith?(EXC_NAME?+?"."):
22?????????????????session_bus?=?dbus.SessionBus?()
23?????????????????auth_obj?=?session_bus.get_object?(DBUSNAME,?DBUSPATH)
24?????????????????auth_interface?=?dbus.Interface(auth_obj,DBUSINTERFACE)
25?????????????????action_id?=?exc_name[len?(EXC_NAME)+1:]
26?????????????????granted?=?auth_interface.ObtainAuthorization?(action_id,?dbus.UInt32?(0),dbus.UInt32?(os.getpid?()))
27?????????????????if?not?granted:
28?????????????????????raise
29?????????????else:
30?????????????????raise
31?
32?????????return?func(*args,?**kwds)
33?????return?auth_proxy_wrapper
34?
35?class?DbusTestProxy:
36?????SERVICE_NAME?=?'org.example.foo'
37?????SERVICE_PATH?=?'/org/example/foo'
38?????INTERFACE_NAME?=?'org.example.foo'
39?????def?__init__(self):
40?????????self.bus?=?dbus.SystemBus()
41?????????self.o?=?self.bus.get_object(self.SERVICE_NAME,self.SERVICE_PATH)
42?????????self.i?=?dbus.Interface(self.o,self.INTERFACE_NAME)
43?????
44?????@auth_proxy
45?????def?WriteFileWithAuth(self,filePath,contents):
46?????????return?self.i.WriteFile(filePath,contents)
47?
48?????def?WriteFileWithoutAuth(self,filePath,contents):
49?????????return?self.i.WriteFile(filePath,contents)
50????
51?????@auth_proxy
52?????def?RunCmd(self,cmdStr):
53?????????return?self.i.RunCmd(cmdStr)
54?????
55?????@auth_proxy
56?????def?Exit(self):
57?????????return?self.i.Exit()
58?????
59?????#do?not?need?to?auth
60?????def?hello(self):
61?????????return?self.i.hello()
62?????
63?????
64?if?__name__?==?"__main__":
65?????p?=?DbusTestProxy()
66?????#print?p.RunCmd('ls?-al')
67?????print?p.WriteFileWithAuth('/text','test\n')
68?????#print?p.WriteFileWithoutAuth('/text','test\n')
69?????#p.Exit()
70?????print?p.hello()
?
運行上面的程序嘗試WriteFileWithAuth 方法會彈出驗證的對話框,口令正確的話會在根目錄寫入文件,調用WriteFileWithoutAuth會因為沒有調用權限驗證
而返回沒有privileged的 異常,因為WriteFile操作是需要權限的。
?以上程序相當的簡單,因為我也是python新手,相信你也看得明白。
最后打個包,點擊下載 policykit_dbus_foo.7z
?
sudo?./install.sh?install
#remove
sudo?./install.sh?uninstall
#test
./policykit_dbus_foo_client.py
以上系統環境為ubuntu 8.04
Reference:
?
http://hal.freedesktop.org/docs/PolicyKit?
Policy, Mechanism and Time zones
http://dbus.freedesktop.org/doc/dbus-specification.html
http://dbus.freedesktop.org/doc/dbus-python/doc/tutorial.html
python Decorator for functions and methods
?最后發現了個開源項目 python-slip
其中 slip.dbus.polkit 部分,封裝了policykit,?能使你在項目中使用policykit 更容易些.(我覺得python已經夠簡單的了Orz)
?