gnome-lirc-properties: Using PolicyKit to get sudo access

Can’t get PolicyKit’s ObtainAuthorization() to work.

gnome-lirc-properties, which I just mentioned needs to edit /etc/lirc/lircd.conf, which requires sudo/root access. Various APIs exist to get temporary sudo/root access but everyone now seems to agree that the new PolicyKit system is the way to go. I’d like to link to a website for it. All the system administration control panels in Ubuntu Hardy Heron seem to use PolicyKit already.

But I can’t get the thing to work, maybe because I’m using Python, and I haven’t found any help so far. I have defined the PolicyKit mechanism, but I can’t get even a simple test of the ObtainAuthorization() method to do anything interesting. This python code does not return anything other than None from ObtainAuthorization() and none of the callbacks are called:

#!/usr/bin/python

import pygtk
pygtk.require('2.0')
import gtk

import dbus
import os

class TestWindow:
    def on_button_clicked(self, widget, data=None):

	#Call the D-Bus method to request PolicyKit authorization:

        session_bus = dbus.SessionBus()
        policykit = session_bus.get_object('org.freedesktop.PolicyKit.AuthenticationAgent', '/', "org.gnome.PolicyKit.AuthorizationManager.SingleInstance")
        if(policykit == None):
           print("Error: Could not get PolicyKit D-Bus Interface\n")

        gdkwindow = self.window.window
        xid = gdkwindow.xid

        print "Calling ObtainAuthorization..."

        #This complains that no ObtainAuthorization(ssi) exists:
        #granted = policykit.ObtainAuthorization("test_action_id", xid, os.getpid())

        #TODO: Neither of the async callbacks are called, and how could the return value be useful if it is async?
        # Note: Other code (such as gnome-panel) seems to use ShowDialog instead of ObtainAuthorization, though
        # ShowDialog is apparently deprecated (I don't know when), but that also has no effect.
        granted = policykit.ObtainAuthorization("test_action_id", xid, os.getpid(), reply_handler=self.__handleAuthReply, error_handler=self.__handleAuthError)

        print "...Finished."

        print "granted=", granted

    def __handleAuthReply(self, granted):
        print "handleAuthReply: granted\n"

    def __handleAuthError(self, exception):
        print "handleAuthError: not granted: %s\n" % exception

    def on_delete_event(self, widget, event, data=None):
        # Close the window:
        return False

    def on_destroy(self, widget, data=None):
        gtk.main_quit()

    def show(self):
       self.window.show()

    def __init__(self):

        self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
        self.window.connect("delete_event", self.on_delete_event)
        self.window.connect("destroy", self.on_destroy)

        self.button = gtk.Button("Obtain Authorization")
        self.button.connect("clicked", self.on_button_clicked, None)
        self.window.add(self.button)
        self.button.show()

window = TestWindow()
window.show()
gtk.main()

gnome-lirc-properties has a real mechanism and tries to use ObtainAuthorization() for real, if someone wants to look at it properly. Here is the gnome-lirc-properties svn, and a tarball (it doesn’t do much yet).

How to use PolicyKit from an application

In return for asking the lazy web, I’ll try to describe how I think it should work, based mostly on David Zeuthen’s description. There are many opportunities to get something wrong:

  • You install a D-Bus service (a PolicyKit “mechanism”) that does the thing that needs sudo access. PolicyKit will control access to this mechanism. For instance, the Clock applet’s mechanism has a SetTimeZone method. gnome-lirc-properties uses the Python D-Bus bindings to implement its mechanism. I know that our WriteConfigFile() method is far too generic, but we will improve it when we get this working.
  • Update:: And this mechanism’s implementation should ask PolicyKit if it has authorization, and return an error if it doesn’t. Thanks mclasen. I’ll try to find out how to do this. Update: You can do this via the PolicyKit (on the session bus) object’s IsProcessAuthorized() method, now used in the gnome-lirc-properties PolicyKit mechanism.
  • As with other D-Bus services, you install a .service file, so that D-Bus activation can start it on demand. PolicyKit mechanisms use the System bus rather than the Session bus, so you install it in $(datadir)/dbus-1/system-services (/usr/share/dbus-1/system-services on Ubuntu). Here is the .service file for gnome-lirc-properties.
  • As with other D-Bus services, you install a .conf file, saying who can use the service. This is installed in $(sysconfdir)/dbus-1/system.d (/etc/dbus-1/system.d/ on Ubuntu). This is the .conf file for gnome-lirc-properties
  • A .policy file tells PolicyKit who should be allowed to use the mechanism. You install this in $(datadir)/PolicyKit/policy (/usr/share/PolicyKit on Ubuntu). Here is the .policy file for gnome-lirc-properties. I believe this allows system administrators to configure access to these features via these .policy files, and I believe that is the whole point of this D-Bus “mechanism” abstraction.
  • Applications that want to use this mechanism call the ObtainAuthorization() method on the “org.freedesktop.PolicyKit.AuthenticationAgent” D-Bus service, passing the action ID that is mentioned in the .policy file. If necessary, PolicyKit asks for a password (or whatever) for authentication, and presumably then gives the application the appropriate rights. The dialog uses (translatable) text from the .policy file, so it’s very specific to your application. Many applications still seem to be using the ShowDialog() method instead, but that has been deprecated.

12 thoughts on “gnome-lirc-properties: Using PolicyKit to get sudo access

  1. Btw, try

    org.freedesktop.PolicyKit.AuthenticationAgent

    instead of

    org.gnome.PolicyKit.AuthorizationManager.SingleInstance

    And note that the type signature for ObtainAuthorization() on the fd.o interface is in fact (string, uint32, uint32) which I think is “suu”, not “ssi”. So you may need to do some type casting as weel.

    Btw,

    dbus-send –session –print-reply –dest=org.freedesktop.PolicyKit.AuthenticationAgent / org.freedesktop.DBus.Introspectable.Introspect

    will tell you the whole signature for the service.

    Hope this helps.

  2. davidz wrote:
    > try
    > org.freedesktop.PolicyKit.AuthenticationAgent
    > instead of
    > org.gnome.PolicyKit.AuthorizationManager.SingleInstance

    Thanks for the suggestion. So this is what I had:
    policykit = session_bus.get_object(‘org.freedesktop.PolicyKit.AuthenticationAgent’, ‘/’, “org.gnome.PolicyKit.AuthorizationManager.SingleInstance”)
    I copy/pasted that from somewhere or other and I actually have no idea what that (optional) third argument is for. I can’t find any DBus-Python reference documentation to tell me.

    I tried this:
    policykit = session_bus.get_object(‘org.freedesktop.PolicyKit.AuthenticationAgent’, ‘/’, “org.gnome.PolicyKit.AuthorizationManager.SingleInstance”)
    and this:
    policykit = session_bus.get_object(‘org.freedesktop.PolicyKit.AuthenticationAgent’, ‘/’)
    with no change.

    > And note that the type signature for ObtainAuthorization() on the fd.o interface is in fact (string, uint32, uint32) which I think is “suu”, not “ssi”.

    Yes, I did change this
    granted = policykit.ObtainAuthorization(“test_action_id”, xid, os.getpid(), reply_handler=self.__handleAuthReply, error_handler=self.__handleAuthError)
    (also copy pasted from somewhere)
    to this
    granted = policykit.ObtainAuthorization(“test_action_id”, (dbus.UInt32)(xid), (dbus.UInt32)(os.getpid()))
    and now I get 0 instead of None, which suggests it is now working, though I’d expect some kind of exception saying that the action doesn’t exist.

    I wonder how it is even possible to call ObtainAuthorization() with those 5 wrong arguments. If I call it with just the 3 arguments without casting then Python complains. I’m also surprised that a cast is necessary in python.

  3. Cool, glad that it works.

    Not a Python expert myself, bu am pretty sure the third argument is the D-Bus interface name for the Python proxy object; remember an object may implement more than one interface (and it’s not even uncommon; several objects from the org.fd.Hal service implement multiple interfaces).

  4. Bizarrely, ObtainAuthorization() also returns 0 if I have already authenticated (confirmed by removing my previous authentication via the Authentications control panel). I don’t see any other way to see if I already have authentication.

  5. Thanks for the post, I really found it helpful as I was trying to do the same thing.

    You can use the org.freedesktop.PolicyKit.IsProcessAuthorized() method to check if the process is authorized and then call ObtainAuthorization() if needed

  6. I used this post and comments to guide my own usage of policykit and authentication agent. My question is what action-id should I use? I am writing a graphical installer for a game so I will need write access to /usr or /opt. Is there a policykit action-id that is highly likely to be available under common linux distros (ubuntu, fedora, suse) ?

  7. extremely useful article for my project. thanks for that. but i couldn’t figure out “test_action_id” parameter in policykit.ObtainAuthorization method. is that sth like that
    com.redhat.NewPrinterNotification.GetReady() —>from d-feet i found. ???

    com.redhat.NewPrinterNotification is interface
    GetReady() is a method.

    thanks in advance.

Leave a Reply

Your email address will not be published. Required fields are marked *