Skip to content

Commit

Permalink
tuned-ppd: Unify polkit policy with power-profiles-daemon
Browse files Browse the repository at this point in the history
Rename HoldProfile/ReleaseProfile polkit actions to
hold-profile/release-profile.

Add the switch-profile action and check against it when
switching the profile using the ActiveProfile DBus property.

The changes require some refactoring of the DBusExporter class,
mainly because we need to be able to supply a custom polkit action
name instead of deriving it from the DBus method name.
  • Loading branch information
zacikpa committed Oct 17, 2024
1 parent 35eed3c commit 138ae79
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 44 deletions.
48 changes: 26 additions & 22 deletions tuned/exports/dbus_exporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,36 +122,40 @@ def _prepare_for_dbus(self, method, wrapper):
func = FunctionType(code.co_consts[0], locals(), method.__name__)
return func

def export(self, method, in_signature, out_signature):
def _polkit_auth(self, action_name, *args):
action_id = self._namespace + "." + action_name
caller = args[-1]
log.debug("checking authorization for action '%s' requested by caller '%s'" % (action_id, caller))
ret = self._polkit.check_authorization(caller, action_id)
args_copy = args
if ret == 1:
log.debug("action '%s' requested by caller '%s' was successfully authorized by polkit" % (action_id, caller))
elif ret == 2:
log.warning("polkit error, but action '%s' requested by caller '%s' was successfully authorized by fallback method" % (action_id, caller))
elif ret == 0:
log.info("action '%s' requested by caller '%s' wasn't authorized, ignoring the request" % (action_id, caller))
args_copy = list(args[:-1]) + [""]
elif ret == -1:
log.warning("polkit error and action '%s' requested by caller '%s' wasn't authorized by fallback method, ignoring the request" % (action_id, caller))
args_copy = list(args[:-1]) + [""]
else:
log.error("polkit error and unable to use fallback method to authorize action '%s' requested by caller '%s', ignoring the request" % (action_id, caller))
args_copy = list(args[:-1]) + [""]
return args_copy

def export(self, method, in_signature, out_signature, action_name=None):
if not ismethod(method):
raise Exception("Only bound methods can be exported.")

method_name = method.__name__
if method_name in self._dbus_methods:
raise Exception("Method with this name is already exported.")

def wrapper(owner, *args, **kwargs):
action_id = self._namespace + "." + method.__name__
caller = args[-1]
log.debug("checking authorization for action '%s' requested by caller '%s'" % (action_id, caller))
ret = self._polkit.check_authorization(caller, action_id)
args_copy = args
if ret == 1:
log.debug("action '%s' requested by caller '%s' was successfully authorized by polkit" % (action_id, caller))
elif ret == 2:
log.warning("polkit error, but action '%s' requested by caller '%s' was successfully authorized by fallback method" % (action_id, caller))
elif ret == 0:
log.info("action '%s' requested by caller '%s' wasn't authorized, ignoring the request" % (action_id, caller))
args_copy = list(args[:-1]) + [""]
elif ret == -1:
log.warning("polkit error and action '%s' requested by caller '%s' wasn't authorized by fallback method, ignoring the request" % (action_id, caller))
args_copy = list(args[:-1]) + [""]
else:
log.error("polkit error and unable to use fallback method to authorize action '%s' requested by caller '%s', ignoring the request" % (action_id, caller))
args_copy = list(args[:-1]) + [""]
return method(*args_copy, **kwargs)
action_name = action_name or method_name
def auth_wrapper(owner, *args, **kwargs):
return method(*self._polkit_auth(action_name, *args), **kwargs)

wrapper = self._prepare_for_dbus(method, wrapper)
wrapper = self._prepare_for_dbus(method, auth_wrapper)
wrapper = dbus.service.method(self._interface_name, in_signature, out_signature, sender_keyword = "caller")(wrapper)

self._dbus_methods[method_name] = wrapper
Expand Down
34 changes: 23 additions & 11 deletions tuned/exports/dbus_exporter_with_properties.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,50 +11,62 @@ def __init__(self, bus_name, interface_name, object_name, namespace):
self._property_setters = {}
self._property_getters = {}

def Get(_, interface_name, property_name):
def Get(_, interface_name, property_name, caller):
if interface_name != self._interface_name:
raise DBusException("Unknown interface: %s" % interface_name)
if property_name not in self._property_getters:
raise DBusException("No such property: %s" % property_name)
getter = self._property_getters[property_name]
return getter()
return getter(caller)

def Set(_, interface_name, property_name, value):
def Set(_, interface_name, property_name, value, caller):
if interface_name != self._interface_name:
raise DBusException("Unknown interface: %s" % interface_name)
if property_name not in self._property_setters:
raise DBusException("No such property: %s" % property_name)
setter = self._property_setters[property_name]
setter(value)
setter(value, caller)

def GetAll(_, interface_name):
def GetAll(_, interface_name, caller):
if interface_name != self._interface_name:
raise DBusException("Unknown interface: %s" % interface_name)
return {name: getter() for name, getter in self._property_getters.items()}
return {name: getter(caller) for name, getter in self._property_getters.items()}

def PropertiesChanged(_, interface_name, changed_properties, invalidated_properties):
if interface_name != self._interface_name:
raise DBusException("Unknown interface: %s" % interface_name)

self._dbus_methods["Get"] = method(PROPERTIES_IFACE, in_signature="ss", out_signature="v")(Get)
self._dbus_methods["Set"] = method(PROPERTIES_IFACE, in_signature="ssv")(Set)
self._dbus_methods["GetAll"] = method(PROPERTIES_IFACE, in_signature="s", out_signature="a{sv}")(GetAll)
self._dbus_methods["Get"] = method(PROPERTIES_IFACE, in_signature="ss", out_signature="v", sender_keyword="caller")(Get)
self._dbus_methods["Set"] = method(PROPERTIES_IFACE, in_signature="ssv", sender_keyword="caller")(Set)
self._dbus_methods["GetAll"] = method(PROPERTIES_IFACE, in_signature="s", out_signature="a{sv}", sender_keyword="caller")(GetAll)
self._dbus_methods["PropertiesChanged"] = signal(PROPERTIES_IFACE, signature="sa{sv}as")(PropertiesChanged)
self._signals.add("PropertiesChanged")

def _auth_wrapper(self, method, action_name):
def wrapper(*args, **kwargs):
new_args = self._polkit_auth(action_name, *args)
if new_args[-1] == "":
raise DBusException("Unauthorized")
return method(*new_args, **kwargs)
return wrapper

def property_changed(self, property_name, value):
self.send_signal("PropertiesChanged", self._interface_name, {property_name: value}, {})

def property_getter(self, method, property_name):
def property_getter(self, method, property_name, action_name=None):
if not ismethod(method):
raise Exception("Only bound methods can be exported.")
if property_name in self._property_getters:
raise Exception("A getter for this property is already registered.")
if action_name is not None:
method = self._auth_wrapper(method, action_name)
self._property_getters[property_name] = method

def property_setter(self, method, property_name):
def property_setter(self, method, property_name, action_name=None):
if not ismethod(method):
raise Exception("Only bound methods can be exported.")
if property_name in self._property_setters:
raise Exception("A setter for this property is already registered.")
if action_name is not None:
method = self._auth_wrapper(method, action_name)
self._property_setters[property_name] = method
18 changes: 9 additions & 9 deletions tuned/ppd/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,15 +172,15 @@ def active_profile(self):
tuned_profile = self._tuned_interface.active_profile()
return self._config.tuned_to_ppd.get(tuned_profile, UNKNOWN_PROFILE)

@exports.export("sss", "u")
@exports.export("sss", "u", "hold-profile")
def HoldProfile(self, profile, reason, app_id, caller):
if profile != PPD_POWER_SAVER and profile != PPD_PERFORMANCE:
raise dbus.exceptions.DBusException(
"Only '%s' and '%s' profiles may be held" % (PPD_POWER_SAVER, PPD_PERFORMANCE)
)
return self._profile_holds.add(profile, reason, app_id, caller)

@exports.export("u", "")
@exports.export("u", "", "release-profile")
def ReleaseProfile(self, cookie, caller):
if not self._profile_holds.has(cookie):
raise dbus.exceptions.DBusException("No active hold for cookie '%s'" % cookie)
Expand All @@ -190,8 +190,8 @@ def ReleaseProfile(self, cookie, caller):
def ProfileReleased(self, cookie):
pass

@exports.property_setter("ActiveProfile")
def set_active_profile(self, profile):
@exports.property_setter("ActiveProfile", "switch-profile")
def set_active_profile(self, profile, caller):
if profile not in self._config.ppd_to_tuned:
raise dbus.exceptions.DBusException("Invalid profile '%s'" % profile)
log.debug("Setting base profile to %s" % profile)
Expand All @@ -200,24 +200,24 @@ def set_active_profile(self, profile):
self.switch_profile(profile)

@exports.property_getter("ActiveProfile")
def get_active_profile(self):
def get_active_profile(self, caller):
return self.active_profile()

@exports.property_getter("Profiles")
def get_profiles(self):
def get_profiles(self, caller):
return dbus.Array(
[{"Profile": profile, "Driver": DRIVER} for profile in self._config.ppd_to_tuned.keys()],
signature="a{sv}",
)

@exports.property_getter("Actions")
def get_actions(self):
def get_actions(self, caller):
return dbus.Array([], signature="s")

@exports.property_getter("PerformanceDegraded")
def get_performance_degraded(self):
def get_performance_degraded(self, caller):
return self._performance_degraded

@exports.property_getter("ActiveProfileHolds")
def get_active_profile_holds(self):
def get_active_profile_holds(self, caller):
return self._profile_holds.as_dbus_array()
14 changes: 12 additions & 2 deletions tuned/ppd/tuned-ppd.policy
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,17 @@
<vendor>TuneD</vendor>
<vendor_url>https://tuned-project.org/</vendor_url>

<action id="net.hadess.PowerProfiles.HoldProfile">
<action id="net.hadess.PowerProfiles.switch-profile">
<description>Switch power profile</description>
<message>Authentication is required to switch power profiles.</message>
<defaults>
<allow_any>no</allow_any>
<allow_inactive>no</allow_inactive>
<allow_active>yes</allow_active>
</defaults>
</action>

<action id="net.hadess.PowerProfiles.hold-profile">
<description>Hold power profile</description>
<message>Authentication is required to hold power profiles.</message>
<defaults>
Expand All @@ -16,7 +26,7 @@
</defaults>
</action>

<action id="net.hadess.PowerProfiles.ReleaseProfile">
<action id="net.hadess.PowerProfiles.release-profile">
<description>Release power profile</description>
<message>Authentication is required to release power profiles.</message>
<defaults>
Expand Down

0 comments on commit 138ae79

Please sign in to comment.