From 138ae79b44dda8ac07964767e652292a9d5d8501 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavol=20=C5=BD=C3=A1=C4=8Dik?= Date: Wed, 16 Oct 2024 16:26:08 +0200 Subject: [PATCH] tuned-ppd: Unify polkit policy with power-profiles-daemon 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. --- tuned/exports/dbus_exporter.py | 48 ++++++++++--------- .../exports/dbus_exporter_with_properties.py | 34 ++++++++----- tuned/ppd/controller.py | 18 +++---- tuned/ppd/tuned-ppd.policy | 14 +++++- 4 files changed, 70 insertions(+), 44 deletions(-) diff --git a/tuned/exports/dbus_exporter.py b/tuned/exports/dbus_exporter.py index 0c9e8be31..75a49a4dc 100644 --- a/tuned/exports/dbus_exporter.py +++ b/tuned/exports/dbus_exporter.py @@ -122,7 +122,28 @@ 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.") @@ -130,28 +151,11 @@ def export(self, method, in_signature, out_signature): 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 diff --git a/tuned/exports/dbus_exporter_with_properties.py b/tuned/exports/dbus_exporter_with_properties.py index 64219e24f..8b327b98f 100644 --- a/tuned/exports/dbus_exporter_with_properties.py +++ b/tuned/exports/dbus_exporter_with_properties.py @@ -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 diff --git a/tuned/ppd/controller.py b/tuned/ppd/controller.py index 4376c1e95..b7fb00ddd 100644 --- a/tuned/ppd/controller.py +++ b/tuned/ppd/controller.py @@ -172,7 +172,7 @@ 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( @@ -180,7 +180,7 @@ def HoldProfile(self, profile, reason, app_id, caller): ) 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) @@ -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) @@ -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() diff --git a/tuned/ppd/tuned-ppd.policy b/tuned/ppd/tuned-ppd.policy index 8106808d4..f4dea8c34 100644 --- a/tuned/ppd/tuned-ppd.policy +++ b/tuned/ppd/tuned-ppd.policy @@ -6,7 +6,17 @@ TuneD https://tuned-project.org/ - + + Switch power profile + Authentication is required to switch power profiles. + + no + no + yes + + + + Hold power profile Authentication is required to hold power profiles. @@ -16,7 +26,7 @@ - + Release power profile Authentication is required to release power profiles.