dbus 和 policykit 實例篇(python)

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

1 [D-BUS Service]
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

複製代碼
 1 #!/usr/bin/python
 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

dbus-send --system --print-reply --dest=org.freedesktop.PolicyKit / org.freedesktop.DBus.Introspectable.Introspect

 

在這裏值得注意的是如果你定義了一系列的系統級調用操作(以root方式啓動前面的程序,但去除了前面的@require_auth 部分),你必須保證每個操作要進行權限驗證,即加上這個東西@require_auth('org.example.foo.sys')

如果你定義了寫文件的dbus操作,但是沒有進行權限驗證的話,一個普通用戶的dbus 調用請求也會調用通過,即普通用戶可以隨意改寫任何文件,這是很危險的

你也可以嘗試把前面的@require_auth部分去掉,再啓動服務,用 d-feet  就可以調用WriteFile方法隨意地在根目錄上寫入文件

 

 

--題外話——

本想將程序寫成這種形式的

1 @require_auth('org.example.foo.sys')
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

複製代碼
 1 <?xml version="1.0" encoding="UTF-8"?> <!-- -*- XML -*- -->
 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 文件定義

其中定義授權認證的方式和時效可以有以下幾種

複製代碼
no 
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

複製代碼
 1 <?xml version="1.0" encoding="UTF-8"?>
 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>
複製代碼

 

做好以上工作,我們可以寫我們的調用端程序了

 

複製代碼
 1 #!/usr/bin/python
 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

 

複製代碼
#install
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)

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章