Blame SOURCES/0010-fix-dbus-fix-service-API-break.patch

8a3219
From 905f7eb62dd31a58b86fbfa191b2ce2482361b0b Mon Sep 17 00:00:00 2001
8a3219
From: Eric Garver <eric@garver.life>
8a3219
Date: Mon, 24 Jun 2019 10:36:40 -0400
8a3219
Subject: [PATCH 10/20] fix: dbus: fix service API break
8a3219
8a3219
This fixes a dbus API break that occurred when introducing service
8a3219
includes. The includes were added to the method's tuple, but doing so
8a3219
changed the dbus signature and thus broke the API. This restores the old
8a3219
signature.
8a3219
8a3219
Move to using key,value based import/export and sanity checking.
8a3219
Previously we were using a tuple with semi-undocumented positions.
8a3219
8a3219
Fixes: 1fc208bf9317 ("feat: service includes")
8a3219
Fixes: rhbz 1721414
8a3219
(cherry picked from commit 335a68c1bba5b1b1fbd430505a485a9eb035360c)
8a3219
---
8a3219
 doc/xml/firewalld.dbus.xml       |  5 ++-
8a3219
 src/firewall/core/fw_config.py   | 59 +++++++++++++++++++++++++++++++-
8a3219
 src/firewall/core/io/service.py  | 35 ++++++++++++++++---
8a3219
 src/firewall/server/firewalld.py | 13 ++++++-
8a3219
 4 files changed, 103 insertions(+), 9 deletions(-)
8a3219
8a3219
diff --git a/doc/xml/firewalld.dbus.xml b/doc/xml/firewalld.dbus.xml
8a3219
index 64d4d2b9c73b..cb4e1eac0fb9 100644
8a3219
--- a/doc/xml/firewalld.dbus.xml
8a3219
+++ b/doc/xml/firewalld.dbus.xml
8a3219
@@ -242,12 +242,12 @@
8a3219
             </listitem>
8a3219
           </varlistentry>
8a3219
 	  <varlistentry id="FirewallD1.Methods.getServiceSettings">
8a3219
-            <term><methodname>getServiceSettings</methodname>(s: <parameter>service</parameter>) → (sssa(ss)asa{ss}asa(ss)as)</term>
8a3219
+            <term><methodname>getServiceSettings</methodname>(s: <parameter>service</parameter>) → (sssa(ss)asa{ss}asa(ss))</term>
8a3219
             <listitem>
8a3219
               <para>
8a3219
                 Return runtime settings of given <replaceable>service</replaceable>.
8a3219
                 For getting permanent settings see <link linkend="FirewallD1.config.service.Methods.getSettings">org.fedoraproject.FirewallD1.config.service.Methods.getSettings</link>.
8a3219
-                Settings are in format: <parameter>version</parameter>, <parameter>name</parameter>, <parameter>description</parameter>, array of <parameter>ports</parameter> (port, protocol), array of <parameter>module names</parameter>, dictionary of <parameter>destinations</parameter>, array of <parameter>protocols</parameter>, array of <parameter>source-ports</parameter> (port, protocol) and array of service <parameter>includes</parameter>.
8a3219
+                Settings are in format: <parameter>version</parameter>, <parameter>name</parameter>, <parameter>description</parameter>, array of <parameter>ports</parameter> (port, protocol), array of <parameter>module names</parameter>, dictionary of <parameter>destinations</parameter>, array of <parameter>protocols</parameter>, array of <parameter>source-ports</parameter> (port, protocol).
8a3219
               </para>
8a3219
               <para>
8a3219
                 <variablelist>
8a3219
@@ -259,7 +259,6 @@
8a3219
                   <varlistentry><term><parameter>destinations (a{ss})</parameter>: dictionary of {IP family : IP address} where 'IP family' key can be either 'ipv4' or 'ipv6'. See <literal>destination</literal> tag in <citerefentry><refentrytitle>firewalld.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</term></varlistentry>
8a3219
                   <varlistentry><term><parameter>protocols (as)</parameter>: array of protocols, see <literal>protocol</literal> tag in <citerefentry><refentrytitle>firewalld.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</term></varlistentry>
8a3219
 		  <varlistentry><term><parameter>source-ports (a(ss))</parameter>: array of port and protocol pairs. See <literal>source-port</literal> tag in <citerefentry><refentrytitle>firewalld.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</term></varlistentry>
8a3219
-                  <varlistentry><term><parameter>includes (as)</parameter>: array of service includes, see <literal>include</literal> tag in <citerefentry><refentrytitle>firewalld.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</term></varlistentry>
8a3219
                 </variablelist>
8a3219
               </para>
8a3219
 	      <para>
8a3219
diff --git a/src/firewall/core/fw_config.py b/src/firewall/core/fw_config.py
8a3219
index a759cfdf83b3..8f29f0c416d2 100644
8a3219
--- a/src/firewall/core/fw_config.py
8a3219
+++ b/src/firewall/core/fw_config.py
8a3219
@@ -545,9 +545,43 @@ class FirewallConfig(object):
8a3219
         return self._builtin_services[obj.name]
8a3219
 
8a3219
     def get_service_config(self, obj):
8a3219
+        conf_dict = obj.export_config()
8a3219
+        conf_list = []
8a3219
+        for i in range(8): # tuple based dbus API has 8 elements
8a3219
+            if obj.IMPORT_EXPORT_STRUCTURE[i][0] not in conf_dict:
8a3219
+                # old API needs the empty elements as well. Grab it from the
8a3219
+                # object otherwise we don't know the type.
8a3219
+                conf_list.append(copy.deepcopy(getattr(obj, obj.IMPORT_EXPORT_STRUCTURE[i][0])))
8a3219
+            else:
8a3219
+                conf_list.append(conf_dict[obj.IMPORT_EXPORT_STRUCTURE[i][0]])
8a3219
+        return tuple(conf_list)
8a3219
+
8a3219
+    def get_service_config_dict(self, obj):
8a3219
         return obj.export_config()
8a3219
 
8a3219
     def set_service_config(self, obj, conf):
8a3219
+        conf_dict = {}
8a3219
+        for i,value in enumerate(conf):
8a3219
+            conf_dict[obj.IMPORT_EXPORT_STRUCTURE[i][0]] = value
8a3219
+
8a3219
+        if obj.builtin:
8a3219
+            x = copy.copy(obj)
8a3219
+            x.cleanup()
8a3219
+            x.import_config(conf_dict)
8a3219
+            x.path = config.ETC_FIREWALLD_SERVICES
8a3219
+            x.builtin = False
8a3219
+            if obj.path != x.path:
8a3219
+                x.default = False
8a3219
+            self.add_service(x)
8a3219
+            service_writer(x)
8a3219
+            return x
8a3219
+        else:
8a3219
+            obj.cleanup()
8a3219
+            obj.import_config(conf_dict)
8a3219
+            service_writer(obj)
8a3219
+            return obj
8a3219
+
8a3219
+    def set_service_config_dict(self, obj, conf):
8a3219
         if obj.builtin:
8a3219
             x = copy.copy(obj)
8a3219
             x.import_config(conf)
8a3219
@@ -568,6 +602,29 @@ class FirewallConfig(object):
8a3219
             raise FirewallError(errors.NAME_CONFLICT,
8a3219
                                 "new_service(): '%s'" % name)
8a3219
 
8a3219
+        conf_dict = {}
8a3219
+        for i,value in enumerate(conf):
8a3219
+            conf_dict[Service.IMPORT_EXPORT_STRUCTURE[i][0]] = value
8a3219
+
8a3219
+        x = Service()
8a3219
+        x.check_name(name)
8a3219
+        x.import_config(conf_dict)
8a3219
+        x.name = name
8a3219
+        x.filename = "%s.xml" % name
8a3219
+        x.path = config.ETC_FIREWALLD_SERVICES
8a3219
+        # It is not possible to add a new one with a name of a buitin
8a3219
+        x.builtin = False
8a3219
+        x.default = True
8a3219
+
8a3219
+        service_writer(x)
8a3219
+        self.add_service(x)
8a3219
+        return x
8a3219
+
8a3219
+    def new_service_dict(self, name, conf):
8a3219
+        if name in self._services or name in self._builtin_services:
8a3219
+            raise FirewallError(errors.NAME_CONFLICT,
8a3219
+                                "new_service(): '%s'" % name)
8a3219
+
8a3219
         x = Service()
8a3219
         x.check_name(name)
8a3219
         x.import_config(conf)
8a3219
@@ -684,7 +741,7 @@ class FirewallConfig(object):
8a3219
         return new_service
8a3219
 
8a3219
     def _copy_service(self, obj, name):
8a3219
-        return self.new_service(name, obj.export_config())
8a3219
+        return self.new_service_dict(name, obj.export_config())
8a3219
 
8a3219
     # zones
8a3219
 
8a3219
diff --git a/src/firewall/core/io/service.py b/src/firewall/core/io/service.py
8a3219
index 3479dab7f175..44dc0ff8a9b0 100644
8a3219
--- a/src/firewall/core/io/service.py
8a3219
+++ b/src/firewall/core/io/service.py
8a3219
@@ -25,6 +25,8 @@ import xml.sax as sax
8a3219
 import os
8a3219
 import io
8a3219
 import shutil
8a3219
+import copy
8a3219
+from collections import OrderedDict
8a3219
 
8a3219
 from firewall import config
8a3219
 from firewall.functions import u2b_if_py2
8a3219
@@ -47,7 +49,7 @@ class Service(IO_Object):
8a3219
         ( "source_ports", [ ( "", "" ), ], ), # a(ss)
8a3219
         ( "includes", [ "" ], ),              # as
8a3219
         )
8a3219
-    DBUS_SIGNATURE = '(sssa(ss)asa{ss}asa(ss)as)'
8a3219
+    DBUS_SIGNATURE = '(sssa(ss)asa{ss}asa(ss))'
8a3219
     ADDITIONAL_ALNUM_CHARS = [ "_", "-" ]
8a3219
     PARSER_REQUIRED_ELEMENT_ATTRS = {
8a3219
         "short": None,
8a3219
@@ -76,6 +78,34 @@ class Service(IO_Object):
8a3219
         self.source_ports = [ ]
8a3219
         self.includes = [ ]
8a3219
 
8a3219
+    def import_config(self, conf):
8a3219
+        self.check_config(conf)
8a3219
+
8a3219
+        for key in conf:
8a3219
+            if not hasattr(self, key):
8a3219
+                raise FirewallError(errors.UNKNOWN_ERROR, "Internal error. '{}' is not a valid attribute".format(key))
8a3219
+            if isinstance(conf[key], list):
8a3219
+                # maintain list order while removing duplicates
8a3219
+                setattr(self, key, list(OrderedDict.fromkeys(copy.deepcopy(conf[key]))))
8a3219
+            else:
8a3219
+                setattr(self, key, copy.deepcopy(conf[key]))
8a3219
+
8a3219
+    def export_config(self):
8a3219
+        conf = {}
8a3219
+        type_formats = dict([(x[0], x[1]) for x in self.IMPORT_EXPORT_STRUCTURE])
8a3219
+        for key in type_formats:
8a3219
+            if getattr(self, key):
8a3219
+                conf[key] = copy.deepcopy(getattr(self, key))
8a3219
+        return conf
8a3219
+
8a3219
+    def check_config(self, conf):
8a3219
+        type_formats = dict([(x[0], x[1]) for x in self.IMPORT_EXPORT_STRUCTURE])
8a3219
+        for key in conf:
8a3219
+            if key not in [x for (x,y) in self.IMPORT_EXPORT_STRUCTURE]:
8a3219
+                raise FirewallError(errors.INVALID_OPTION, "service option '{}' is not valid".format(key))
8a3219
+            self._check_config_structure(conf[key], type_formats[key])
8a3219
+            self._check_config(conf[key], key)
8a3219
+
8a3219
     def cleanup(self):
8a3219
         self.version = ""
8a3219
         self.short = ""
8a3219
@@ -138,9 +168,6 @@ class Service(IO_Object):
8a3219
                 if len(module) < 2:
8a3219
                     raise FirewallError(errors.INVALID_MODULE, module)
8a3219
 
8a3219
-        elif item == "includes":
8a3219
-            pass
8a3219
-
8a3219
 # PARSER
8a3219
 
8a3219
 class service_ContentHandler(IO_Object_ContentHandler):
8a3219
diff --git a/src/firewall/server/firewalld.py b/src/firewall/server/firewalld.py
8a3219
index bc04f2d0f4c3..233160b64b18 100644
8a3219
--- a/src/firewall/server/firewalld.py
8a3219
+++ b/src/firewall/server/firewalld.py
8a3219
@@ -26,6 +26,7 @@ from gi.repository import GLib, GObject
8a3219
 import sys
8a3219
 sys.modules['gobject'] = GObject
8a3219
 
8a3219
+import copy
8a3219
 import dbus
8a3219
 import dbus.service
8a3219
 import slip.dbus
8a3219
@@ -921,7 +922,17 @@ class FirewallD(slip.dbus.service.Object):
8a3219
         # returns service settings for service
8a3219
         service = dbus_to_python(service, str)
8a3219
         log.debug1("getServiceSettings(%s)", service)
8a3219
-        return self.fw.service.get_service(service).export_config()
8a3219
+        obj = self.fw.service.get_service(service)
8a3219
+        conf_dict = obj.export_config()
8a3219
+        conf_list = []
8a3219
+        for i in range(8): # tuple based dbus API has 8 elements
8a3219
+            if obj.IMPORT_EXPORT_STRUCTURE[i][0] not in conf_dict:
8a3219
+                # old API needs the empty elements as well. Grab it from the
8a3219
+                # object otherwise we don't know the type.
8a3219
+                conf_list.append(copy.deepcopy(getattr(obj, obj.IMPORT_EXPORT_STRUCTURE[i][0])))
8a3219
+            else:
8a3219
+                conf_list.append(conf_dict[obj.IMPORT_EXPORT_STRUCTURE[i][0]])
8a3219
+        return tuple(conf_list)
8a3219
 
8a3219
     @slip.dbus.polkit.require_auth(config.dbus.PK_ACTION_INFO)
8a3219
     @dbus_service_method(config.dbus.DBUS_INTERFACE, in_signature='',
8a3219
-- 
8a3219
2.20.1
8a3219