Dear all, since one concern pronounced in this bug report was that there is no automatic migration. Therefore, I developed a helper script to convert most kinds of pkla files to the JS-based format (see attachment). I created a merge request in the upstream repo as well if they want to include the converter as well [1].
Maybe it is now possible to upgrade polkit to a newer version including the new-style rules? Best, Jan [1] https://gitlab.freedesktop.org/polkit/polkit/-/merge_requests/66 On Fri, 6 Dec 2019 00:25:41 +0000 Simon McVittie <s...@debian.org> wrote: > On Thu, 05 Dec 2019 at 23:07:00 +0100, Geert Stappers wrote: > > Upstream is at version 0.116 ( > > https://gitlab.freedesktop.org/polkit/polkit/-/tags ) > > Debian version is 0.105-something ( > > https://salsa.debian.org/utopia-team/polkit/blob/master/debian/changelog ) > > > > Debian changelog talks about changes from upstream version 0.114 and 0.116. > > The latest upstream version is available in experimental. > > The version in unstable is exactly what its version number indicates: > a very old upstream version, with the majority of the upstream changes > from later versions backported into it via debian/patches. > > The reason for this is that upstream version > 0.106 removed the "localauthority" backend (which > configures polkit via .ini-style files like for example > /var/lib/polkit-1/localauthority/10-vendor.d/org.freedesktop.Flatpak.pkla, > which can be overridden by sysadmin configuration in > /etc/polkit-1/localauthority or /var/lib/polkit-1/localauthority) > and replaced it with a new JavaScript-based backend (which > configures polkit via short JavaScript fragments like for example > /usr/share/polkit-1/rules.d/org.freedesktop.Flatpak.rules, which can be > overridden by sysadmin configuration in /etc/polkit-1/rules.d). > > The migration path between the two is not obvious, and some of the Debian > maintainers of polkit are strongly opposed to it being configured with > JavaScript files, so at the moment we are stuck with polkit 0.105. > > One day, I want to stop patching changes from 0.11x into 0.105, > and instead start patching the "localauthority" backend into 0.11x, > so that we can stick to the upstream version in all respects except > for the configuration backend. However, all the maintainers of polkit > (both in Debian and upstream) mostly work on other things, so it's rare > that someone has enough uninterrupted time to do something like that. > > There has been some upstream unhappiness with the current configuration > arrangements, mostly because the mozjs library that is used for the > JavaScript interpreter does not have a stable API; so it is possible that > a newer upstream version will either change the configuration language > (again), or change the JavaScript implementation to something more friendly > (most likely a smaller interpreter like duktape). > > smcv > >
#!/usr/bin/env python3 from __future__ import annotations import argparse import configparser from dataclasses import dataclass import enum import sys import traceback from textwrap import dedent, indent from typing import Dict, List, Optional, Union, cast def ensure_indent(text: str, numspaces: int = 4) -> str: return indent(dedent(text), " " * numspaces) class PolkitConfConverterException(Exception): def __init__(self, msg: str): super().__init__() self.msg = msg class PolkitIdType(enum.Enum): USER = enum.auto() GROUP = enum.auto() NETGROUP = enum.auto() @classmethod def parse_identity_type(cls, input: str) -> PolkitIdType: result_map = { "unix-user": cls.USER, "unix-group": cls.GROUP, "unix-netgroup": cls.NETGROUP, } try: return result_map[input] except KeyError as e: raise PolkitConfConverterException("Unknown polkit identity type") from e class PolkitResult(enum.Enum): YES = enum.auto() NO = enum.auto() AUTH_SELF = enum.auto() AUTH_SELF_KEEP = enum.auto() AUTH_ADMIN = enum.auto() AUTH_ADMIN_KEEP = enum.auto() @classmethod def parse_polkit_result( cls, polkit_result: Optional[str] ) -> Optional[PolkitResult]: if polkit_result is None: return None result_map = { "yes": cls.YES, "no": cls.NO, "auth_self": cls.AUTH_SELF, "auth_self_keep": cls.AUTH_SELF_KEEP, "auth_admin": cls.AUTH_ADMIN, "auth_admin_keep": cls.AUTH_ADMIN_KEEP, } try: return result_map[polkit_result] except KeyError as e: raise PolkitConfConverterException("Unknown polkit result value") from e @classmethod def polkit_result_str(cls, element: PolkitResult) -> Optional[str]: result_map = { cls.YES: "polkit.Result.YES", cls.NO: "polkit.Result.NO", cls.AUTH_SELF: "polkit.Result.AUTH_SELF", cls.AUTH_SELF_KEEP: "polkit.Result.AUTH_SELF_KEEP", cls.AUTH_ADMIN: "polkit.Result.AUTH_ADMIN", cls.AUTH_ADMIN_KEEP: "polkit.Result.AUTH_ADMIN_KEEP", } try: return result_map[element] except KeyError: return None @dataclass class PolkitIdentity: identity_type: PolkitIdType identity: str def __str__(self) -> str: if self.identity_type is PolkitIdType.USER: return f"unix-user:{self.identity}" elif self.identity_type is PolkitIdType.GROUP: return f"unix-group:{self.identity}" elif self.identity_type is PolkitIdType.NETGROUP: return f"unix-netgroup:{self.identity}" else: raise PolkitConfConverterException("Illegal PolkitIdType") @classmethod def parse_identity_str(cls, input: str) -> PolkitIdentity: id_type, id_name = input.split(":", maxsplit=1) return cls( identity_type=PolkitIdType.parse_identity_type(id_type), identity=id_name ) @dataclass class PolkitAdminConf: admin_identity: List[PolkitIdentity] def to_javascript(self) -> str: result_list = [str(i) for i in self.admin_identity] return dedent( f"""\ polkit.addAdminRule(function(action, subject) {{ return {result_list}; }}); """ ) @dataclass class PolkitPKLA: identity: List[PolkitIdentity] action: List[str] result_active: Optional[PolkitResult] result_inactive: Optional[PolkitResult] result_any: Optional[PolkitResult] return_value: Optional[Dict[str, str]] def format_action_expression(self, action: str) -> str: if "*" in action: if action.endswith("*"): return f'action.startsWith("{action[:-1]}")' raise PolkitConfConverterException( "Automatic conversion of globbing only at end of action string supported." ) return f'action.id == "{action}"' def format_id_expression(self, id: PolkitIdentity) -> str: if id.identity_type is PolkitIdType.USER: return f'subject.user == "{id.identity}"' elif id.identity_type is PolkitIdType.GROUP: return f'subject.isInGroup("{id.identity}")' elif id.identity_type is PolkitIdType.NETGROUP: return f'subject.isInNetGroup("{id.identity}")' else: raise PolkitConfConverterException("Illegal PolkitIdType") def or_conditionals(self, conds: List[str], numspaces=8) -> str: if len(conds) > 1: numspaces += 1 c = ensure_indent(" ||\n".join(conds), numspaces) if len(conds) > 1: c = " " * (numspaces - 1) + "(" + c.lstrip() + ")" return c def to_javascript(self) -> str: if self.return_value: raise PolkitConfConverterException( "Automatic conversion of PKLA return values is not supported." ) action_conds = self.or_conditionals([self.format_action_expression(a) for a in self.action]) identity_conds = self.or_conditionals([self.format_id_expression(i) for i in self.identity]) if action_conds and identity_conds: merged_conds = dedent( """\ {action_conds} && {identity_conds} """ ).format( action_conds=action_conds.lstrip(), identity_conds=identity_conds.rstrip() ).rstrip() elif action_conds: merged_conds = action_conds elif identity_conds: merged_conds = identity_conds else: merged_conds = "true" if self.result_any and ( (not self.result_active and not self.result_inactive) or (not self.result_active and self.result_inactive is self.result_any) or (not self.result_inactive and self.result_active is self.result_any) or ( self.result_active is self.result_any and self.result_inactive is self.result_any ) ): results = ensure_indent( f"return {PolkitResult.polkit_result_str(self.result_any)};", numspaces=8, ) else: if self.result_active: result_active = ensure_indent( f"""\ if (subject.active && subject.local) {{ return {PolkitResult.polkit_result_str(self.result_active)}; }} """, numspaces=8, ) else: result_active = "" if self.result_inactive: result_inactive = ensure_indent( f"""\ if (subject.inactive && subject.local) {{ return {PolkitResult.polkit_result_str(self.result_inactive)}; }} """, numspaces=8, ) else: result_inactive = "" if self.result_any: result_any = ensure_indent( f"return {PolkitResult.polkit_result_str(self.result_any)};", numspaces=8, ) else: result_any = "" results = "".join((result_active, result_inactive, result_any)) body = dedent( """\ polkit.addRule(function(action, subject) {{ if ({merged_conds}) {{ {results} }} }}); """ ) return body.format(merged_conds=merged_conds, results=results) def _parse_ini_section( section: configparser.SectionProxy, ) -> Union[PolkitAdminConf, PolkitPKLA]: if section.get("AdminIdentities"): admin_ids = [ PolkitIdentity.parse_identity_str(i) for i in section["AdminIdentities"].split(";") if i ] return PolkitAdminConf(admin_identity=admin_ids) identity = section.get("Identity") action = section.get("Action") result_active = PolkitResult.parse_polkit_result(section.get("ResultActive")) result_inactive = PolkitResult.parse_polkit_result(section.get("ResultInactive")) result_any = PolkitResult.parse_polkit_result(section.get("ResultAny")) return_value = section.get("ReturnValue") if ( not identity or not action or not (result_active or result_inactive or result_any) ): raise PolkitConfConverterException("Invalid input configuration") identity = [PolkitIdentity.parse_identity_str(i) for i in identity.split(";") if i] action = [i for i in action.split(";") if i] if return_value: return_value = dict([r.split("=", maxsplit=1) for r in return_value.split(";")]) # type: ignore return PolkitPKLA( identity=identity, action=action, result_active=result_active, result_inactive=result_inactive, result_any=result_any, return_value=cast(Optional[Dict[str, str]], return_value), ) # Main CLI function def main() -> bool: parser = argparse.ArgumentParser( description="Convert old polkit config to new one." ) parser.add_argument( "input", nargs="?", metavar="INPUT", type=argparse.FileType("r"), default=sys.stdin, help='Input file, can be "-" for stdin.', ) parser.add_argument( "output", nargs="?", metavar="OUTPUT", type=argparse.FileType("w"), default=sys.stdout, help='Output file, can be "-" for stdout.', ) parser.add_argument( "-d", "--debug", action="store_true", help="Enable debugging output." ) args = parser.parse_args() input_config = configparser.ConfigParser() input_config.read_file(args.input) try: for section in input_config.sections(): conf_obj = _parse_ini_section(input_config[section]) args.output.write(conf_obj.to_javascript()) except PolkitConfConverterException as e: if args.debug: traceback.print_exc() else: print(e.msg, file=sys.stderr) return False return True if __name__ == "__main__": sys.exit(main())