/mandos/trunk

To get this branch, use:
bzr branch http://bzr.recompile.se/loggerhead/mandos/trunk

« back to all changes in this revision

Viewing changes to mandos-ctl

  • Committer: teddy at recompile
  • Date: 2020-01-12 01:42:09 UTC
  • Revision ID: teddy@recompile.se-20200112014209-ktr3acloxzbmhbnt
mandos-ctl: Add DBussy support

Add support in mandos-ctl for the "DBussy" Python D-Bus module.  Use
it by default, if it is available.

* mandos-ctl: Try to import the "dbussy" and its high-level module
  "ravel".
  (main): Use DBussy if import succeeded.
  (dbussy_adapter): New.
  (Test_dbussy_adapter_SystemBus): New test class.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
#!/usr/bin/python
 
1
#!/usr/bin/python3 -bbI
2
2
# -*- after-save-hook: (lambda () (let ((command (if (fboundp 'file-local-name) (file-local-name (buffer-file-name)) (or (file-remote-p (buffer-file-name) 'localname) (buffer-file-name))))) (if (= (progn (if (get-buffer "*Test*") (kill-buffer "*Test*")) (process-file-shell-command (format "%s --check" (shell-quote-argument command)) nil "*Test*")) 0) (let ((w (get-buffer-window "*Test*"))) (if w (delete-window w))) (progn (with-current-buffer "*Test*" (compilation-mode)) (display-buffer "*Test*" '(display-buffer-in-side-window)))))); coding: utf-8 -*-
3
3
#
4
4
# Mandos Monitor - Control and monitor the Mandos server
46
46
import tempfile
47
47
import contextlib
48
48
 
 
49
if sys.version_info.major == 2:
 
50
    __metaclass__ = type
 
51
    str = unicode
 
52
 
 
53
class gi:
 
54
    """Dummy gi module, for the tests"""
 
55
    class repository:
 
56
        class GLib:
 
57
            class Error(Exception):
 
58
                pass
 
59
dbussy = None
 
60
ravel = None
 
61
dbus_python = None
 
62
pydbus = None
 
63
 
49
64
try:
50
 
    import pydbus
51
 
    import gi
52
 
    dbus_python = None
 
65
    import dbussy
 
66
    import ravel
53
67
except ImportError:
54
 
    import dbus as dbus_python
55
 
    pydbus = None
56
 
    class gi(object):
57
 
        """Dummy gi module, for the tests"""
58
 
        class repository(object):
59
 
            class GLib(object):
60
 
                class Error(Exception):
61
 
                    pass
 
68
    try:
 
69
        import pydbus
 
70
        import gi
 
71
    except ImportError:
 
72
        import dbus as dbus_python
 
73
 
62
74
 
63
75
# Show warnings by default
64
76
if not sys.warnoptions:
72
84
logging.captureWarnings(True)   # Show warnings via the logging system
73
85
 
74
86
if sys.version_info.major == 2:
75
 
    str = unicode
76
87
    import StringIO
77
88
    io.StringIO = StringIO.StringIO
78
89
 
79
90
locale.setlocale(locale.LC_ALL, "")
80
91
 
81
 
version = "1.8.5"
 
92
version = "1.8.9"
82
93
 
83
94
 
84
95
def main():
93
104
    if options.debug:
94
105
        log.setLevel(logging.DEBUG)
95
106
 
96
 
    if pydbus is not None:
 
107
    if dbussy is not None and ravel is not None:
 
108
        bus = dbussy_adapter.CachingBus(dbussy, ravel)
 
109
    elif pydbus is not None:
97
110
        bus = pydbus_adapter.CachingBus(pydbus)
98
111
    else:
99
112
        bus = dbus_python_adapter.CachingBus(dbus_python)
467
480
        parser.error("--remove can only be combined with --deny")
468
481
 
469
482
 
470
 
class dbus(object):
 
483
class dbus:
471
484
 
472
 
    class SystemBus(object):
 
485
    class SystemBus:
473
486
 
474
487
        object_manager_iface = "org.freedesktop.DBus.ObjectManager"
475
488
        def get_managed_objects(self, busname, objectpath):
484
497
                             self.properties_iface, interface, key,
485
498
                             value)
486
499
 
487
 
 
488
500
    class MandosBus(SystemBus):
489
501
        busname_domain = "se.recompile"
490
502
        busname = busname_domain + ".Mandos"
521
533
        pass
522
534
 
523
535
 
524
 
class dbus_python_adapter(object):
 
536
class dbus_python_adapter:
525
537
 
526
538
    class SystemBus(dbus.MandosBus):
527
539
        """Use dbus-python"""
581
593
                                     self.client_interface, key,
582
594
                                     value)
583
595
 
584
 
    class SilenceLogger(object):
 
596
    class SilenceLogger:
585
597
        "Simple context manager to silence a particular logger"
586
598
        def __init__(self, loggername):
587
599
            self.logger = logging.getLogger(loggername)
616
628
                return new_object
617
629
 
618
630
 
619
 
class pydbus_adapter(object):
 
631
class pydbus_adapter:
620
632
    class SystemBus(dbus.MandosBus):
621
633
        def __init__(self, module=pydbus):
622
634
            self.pydbus = module
679
691
                return new_object
680
692
 
681
693
 
 
694
class dbussy_adapter:
 
695
    class SystemBus(dbus.SystemBus):
 
696
        """Use DBussy"""
 
697
 
 
698
        def __init__(self, dbussy, ravel):
 
699
            self.dbussy = dbussy
 
700
            self.ravel = ravel
 
701
            self.bus = ravel.system_bus()
 
702
 
 
703
        @contextlib.contextmanager
 
704
        def convert_exception(self, exception_class=dbus.Error):
 
705
            try:
 
706
                yield
 
707
            except self.dbussy.DBusError as e:
 
708
                # This does what "raise from" would do
 
709
                exc = exception_class(*e.args)
 
710
                exc.__cause__ = e
 
711
                raise exc
 
712
 
 
713
        def call_method(self, methodname, busname, objectpath,
 
714
                        interface, *args):
 
715
            proxy_object = self.get_object(busname, objectpath)
 
716
            log.debug("D-Bus: %s:%s:%s.%s(%s)", busname, objectpath,
 
717
                      interface, methodname,
 
718
                      ", ".join(repr(a) for a in args))
 
719
            iface = proxy_object.get_interface(interface)
 
720
            method = getattr(iface, methodname)
 
721
            with self.convert_exception(dbus.Error):
 
722
                value =  method(*args)
 
723
            # DBussy returns values either as an empty list or as a
 
724
            # tuple: (signature, value)
 
725
            if value:
 
726
                return self.type_filter(value[0])
 
727
 
 
728
        def get_object(self, busname, objectpath):
 
729
            log.debug("D-Bus: Connect to: (busname=%r, path=%r)",
 
730
                      busname, objectpath)
 
731
            with self.convert_exception(dbus.ConnectFailed):
 
732
                return self.bus[busname][objectpath]
 
733
 
 
734
        def type_filter(self, value):
 
735
            """Convert the most bothersome types to Python types"""
 
736
            if isinstance(value, tuple):
 
737
                if (len(value) == 2
 
738
                    and isinstance(value[0],
 
739
                                   self.dbussy.DBUS.Signature)):
 
740
                    return self.type_filter(value[1])
 
741
            elif isinstance(value, self.dbussy.DBUS.ObjectPath):
 
742
                return str(value)
 
743
            # Also recurse into dictionaries
 
744
            elif isinstance(value, dict):
 
745
                return {self.type_filter(key):
 
746
                        self.type_filter(subval)
 
747
                        for key, subval in value.items()}
 
748
            return value
 
749
 
 
750
        def set_property(self, busname, objectpath, interface, key,
 
751
                         value):
 
752
            proxy_object = self.get_object(busname, objectpath)
 
753
            log.debug("D-Bus: %s:%s:%s.Set(%r, %r, %r)", busname,
 
754
                      objectpath, self.properties_iface, interface,
 
755
                      key, value)
 
756
            if key == "Secret":
 
757
                # DBussy wants a Byte Array to be a sequence of
 
758
                # values, not a byte string
 
759
                value = tuple(value)
 
760
            setattr(proxy_object.get_interface(interface), key, value)
 
761
 
 
762
    class MandosBus(SystemBus, dbus.MandosBus):
 
763
        pass
 
764
 
 
765
    class CachingBus(MandosBus):
 
766
        """A caching layer for dbussy_adapter.MandosBus"""
 
767
        def __init__(self, *args, **kwargs):
 
768
            self.object_cache = {}
 
769
            super(dbussy_adapter.CachingBus, self).__init__(*args,
 
770
                                                            **kwargs)
 
771
        def get_object(self, busname, objectpath):
 
772
            try:
 
773
                return self.object_cache[(busname, objectpath)]
 
774
            except KeyError:
 
775
                new_object = super(
 
776
                    dbussy_adapter.CachingBus,
 
777
                    self).get_object(busname, objectpath)
 
778
                self.object_cache[(busname, objectpath)]  = new_object
 
779
                return new_object
 
780
 
 
781
 
682
782
def commands_from_options(options):
683
783
 
684
784
    commands = list(options.commands)
712
812
    return commands
713
813
 
714
814
 
715
 
class command(object):
 
815
class command:
716
816
    """A namespace for command classes"""
717
817
 
718
 
    class Base(object):
 
818
    class Base:
719
819
        """Abstract base class for commands"""
720
820
        def run(self, clients, bus=None):
721
821
            """Normal commands should implement run_on_one_client(),
784
884
                keywords = self.all_keywords
785
885
            print(self.TableOfClients(clients.values(), keywords))
786
886
 
787
 
        class TableOfClients(object):
 
887
        class TableOfClients:
788
888
            tableheaders = {
789
889
                "Name": "Name",
790
890
                "Enabled": "Enabled",
1021
1121
                                                     "output"))
1022
1122
 
1023
1123
 
1024
 
class Unique(object):
 
1124
class Unique:
1025
1125
    """Class for objects which exist only to be unique objects, since
1026
1126
unittest.mock.sentinel only exists in Python 3.3"""
1027
1127
 
1311
1411
class Test_dbus_python_adapter_SystemBus(TestCaseWithAssertLogs):
1312
1412
 
1313
1413
    def MockDBusPython_func(self, func):
1314
 
        class mock_dbus_python(object):
 
1414
        class mock_dbus_python:
1315
1415
            """mock dbus-python module"""
1316
 
            class exceptions(object):
 
1416
            class exceptions:
1317
1417
                """Pseudo-namespace"""
1318
1418
                class DBusException(Exception):
1319
1419
                    pass
1320
 
            class SystemBus(object):
 
1420
            class SystemBus:
1321
1421
                @staticmethod
1322
1422
                def get_object(busname, objectpath):
1323
1423
                    DBusObject = collections.namedtuple(
1337
1437
                                    dbus_interface=dbus_interface)
1338
1438
                    return DBusObject(methodname=method,
1339
1439
                                      Set=set_property)
1340
 
            class Boolean(object):
 
1440
            class Boolean:
1341
1441
                def __init__(self, value):
1342
1442
                    self.value = bool(value)
1343
1443
                def __bool__(self):
1555
1655
            self.call_method(bus, "methodname", "busname",
1556
1656
                             "objectpath", "interface")
1557
1657
 
1558
 
    class fake_dbus_python_raises_exception_on_connect(object):
 
1658
    class fake_dbus_python_raises_exception_on_connect:
1559
1659
        """fake dbus-python module"""
1560
 
        class exceptions(object):
 
1660
        class exceptions:
1561
1661
            """Pseudo-namespace"""
1562
1662
            class DBusException(Exception):
1563
1663
                pass
1571
1671
 
1572
1672
 
1573
1673
class Test_dbus_python_adapter_CachingBus(unittest.TestCase):
1574
 
    class mock_dbus_python(object):
 
1674
    class mock_dbus_python:
1575
1675
        """mock dbus-python modules"""
1576
 
        class SystemBus(object):
 
1676
        class SystemBus:
1577
1677
            @staticmethod
1578
1678
            def get_object(busname, objectpath):
1579
1679
                return Unique()
1625
1725
class Test_pydbus_adapter_SystemBus(TestCaseWithAssertLogs):
1626
1726
 
1627
1727
    def Stub_pydbus_func(self, func):
1628
 
        class stub_pydbus(object):
 
1728
        class stub_pydbus:
1629
1729
            """stub pydbus module"""
1630
 
            class SystemBus(object):
 
1730
            class SystemBus:
1631
1731
                @staticmethod
1632
1732
                def get(busname, objectpath):
1633
1733
                    DBusObject = collections.namedtuple(
1679
1779
            self.call_method(bus, "methodname", "busname",
1680
1780
                             "objectpath", "interface")
1681
1781
 
1682
 
    class fake_pydbus_raises_exception_on_connect(object):
 
1782
    class fake_pydbus_raises_exception_on_connect:
1683
1783
        """fake dbus-python module"""
1684
1784
        @classmethod
1685
1785
        def SystemBus(cls):
1689
1789
            return Bus(get=get)
1690
1790
 
1691
1791
    def test_set_property_uses_setattr(self):
1692
 
        class Object(object):
 
1792
        class Object:
1693
1793
            pass
1694
1794
        obj = Object()
1695
 
        class pydbus_spy(object):
1696
 
            class SystemBus(object):
 
1795
        class pydbus_spy:
 
1796
            class SystemBus:
1697
1797
                @staticmethod
1698
1798
                def get(busname, objectpath):
1699
1799
                    return {"interface": obj}
1706
1806
    def test_get_suppresses_xml_deprecation_warning(self):
1707
1807
        if sys.version_info.major >= 3:
1708
1808
            return
1709
 
        class stub_pydbus_get(object):
1710
 
            class SystemBus(object):
 
1809
        class stub_pydbus_get:
 
1810
            class SystemBus:
1711
1811
                @staticmethod
1712
1812
                def get(busname, objectpath):
1713
1813
                    warnings.warn_explicit(
1721
1821
 
1722
1822
 
1723
1823
class Test_pydbus_adapter_CachingBus(unittest.TestCase):
1724
 
    class stub_pydbus(object):
 
1824
    class stub_pydbus:
1725
1825
        """stub pydbus module"""
1726
 
        class SystemBus(object):
 
1826
        class SystemBus:
1727
1827
            @staticmethod
1728
1828
            def get(busname, objectpath):
1729
1829
                return Unique()
1771
1871
        self.assertIs(obj1, obj1b)
1772
1872
 
1773
1873
 
 
1874
class Test_dbussy_adapter_SystemBus(TestCaseWithAssertLogs):
 
1875
 
 
1876
    class dummy_dbussy:
 
1877
        class DBUS:
 
1878
            class ObjectPath(str):
 
1879
                pass
 
1880
        class DBusError(Exception):
 
1881
            pass
 
1882
 
 
1883
    def fake_ravel_func(self, func):
 
1884
        class fake_ravel:
 
1885
            @staticmethod
 
1886
            def system_bus():
 
1887
                class DBusInterfaceProxy:
 
1888
                    @staticmethod
 
1889
                    def methodname(*args):
 
1890
                        return [func(*args)]
 
1891
                class DBusObject:
 
1892
                    @staticmethod
 
1893
                    def get_interface(interface):
 
1894
                        if interface == "interface":
 
1895
                            return DBusInterfaceProxy()
 
1896
                return {"busname": {"objectpath": DBusObject()}}
 
1897
        return fake_ravel
 
1898
 
 
1899
    def call_method(self, bus, methodname, busname, objectpath,
 
1900
                    interface, *args):
 
1901
        with self.assertLogs(log, logging.DEBUG):
 
1902
            return bus.call_method(methodname, busname, objectpath,
 
1903
                                   interface, *args)
 
1904
 
 
1905
    def test_call_method_returns(self):
 
1906
        expected_method_return = Unique()
 
1907
        method_args = (Unique(), Unique())
 
1908
        def func(*args):
 
1909
            self.assertEqual(len(method_args), len(args))
 
1910
            for marg, arg in zip(method_args, args):
 
1911
                self.assertIs(marg, arg)
 
1912
            return expected_method_return
 
1913
        fake_ravel = self.fake_ravel_func(func)
 
1914
        bus = dbussy_adapter.SystemBus(self.dummy_dbussy, fake_ravel)
 
1915
        ret = self.call_method(bus, "methodname", "busname",
 
1916
                               "objectpath", "interface",
 
1917
                               *method_args)
 
1918
        self.assertIs(ret, expected_method_return)
 
1919
 
 
1920
    def test_call_method_filters_objectpath(self):
 
1921
        def func():
 
1922
            return method_return
 
1923
        fake_ravel = self.fake_ravel_func(func)
 
1924
        bus = dbussy_adapter.SystemBus(self.dummy_dbussy, fake_ravel)
 
1925
        method_return = (self.dummy_dbussy.DBUS
 
1926
                         .ObjectPath("objectpath"))
 
1927
        ret = self.call_method(bus, "methodname", "busname",
 
1928
                               "objectpath", "interface")
 
1929
        self.assertEqual("objectpath", ret)
 
1930
        self.assertNotIsInstance(ret,
 
1931
                                 self.dummy_dbussy.DBUS.ObjectPath)
 
1932
 
 
1933
    def test_call_method_filters_objectpaths_in_dict(self):
 
1934
        ObjectPath = self.dummy_dbussy.DBUS.ObjectPath
 
1935
        def func():
 
1936
            return method_return
 
1937
        fake_ravel = self.fake_ravel_func(func)
 
1938
        bus = dbussy_adapter.SystemBus(self.dummy_dbussy, fake_ravel)
 
1939
        method_return = {
 
1940
            ObjectPath("objectpath_key_1"):
 
1941
            ObjectPath("objectpath_value_1"),
 
1942
            ObjectPath("objectpath_key_2"):
 
1943
            ObjectPath("objectpath_value_2"),
 
1944
        }
 
1945
        ret = self.call_method(bus, "methodname", "busname",
 
1946
                               "objectpath", "interface")
 
1947
        expected_method_return = {str(key): str(value)
 
1948
                                  for key, value in
 
1949
                                  method_return.items()}
 
1950
        for key, value in ret.items():
 
1951
            self.assertNotIsInstance(key, ObjectPath)
 
1952
            self.assertNotIsInstance(value, ObjectPath)
 
1953
        self.assertEqual(expected_method_return, ret)
 
1954
        self.assertIsInstance(ret, dict)
 
1955
 
 
1956
    def test_call_method_filters_objectpaths_in_dict_in_dict(self):
 
1957
        ObjectPath = self.dummy_dbussy.DBUS.ObjectPath
 
1958
        def func():
 
1959
            return method_return
 
1960
        fake_ravel = self.fake_ravel_func(func)
 
1961
        bus = dbussy_adapter.SystemBus(self.dummy_dbussy, fake_ravel)
 
1962
        method_return = {
 
1963
            ObjectPath("key1"): {
 
1964
                ObjectPath("key11"): ObjectPath("value11"),
 
1965
                ObjectPath("key12"): ObjectPath("value12"),
 
1966
            },
 
1967
            ObjectPath("key2"): {
 
1968
                ObjectPath("key21"): ObjectPath("value21"),
 
1969
                ObjectPath("key22"): ObjectPath("value22"),
 
1970
            },
 
1971
        }
 
1972
        ret = self.call_method(bus, "methodname", "busname",
 
1973
                               "objectpath", "interface")
 
1974
        expected_method_return = {
 
1975
            "key1": {"key11": "value11",
 
1976
                     "key12": "value12"},
 
1977
            "key2": {"key21": "value21",
 
1978
                     "key22": "value22"},
 
1979
        }
 
1980
        self.assertEqual(expected_method_return, ret)
 
1981
        for key, value in ret.items():
 
1982
            self.assertIsInstance(value, dict)
 
1983
            self.assertEqual(expected_method_return[key], value)
 
1984
            self.assertNotIsInstance(key, ObjectPath)
 
1985
            for inner_key, inner_value in value.items():
 
1986
                self.assertIsInstance(value, dict)
 
1987
                self.assertEqual(
 
1988
                    expected_method_return[key][inner_key],
 
1989
                    inner_value)
 
1990
                self.assertNotIsInstance(key, ObjectPath)
 
1991
 
 
1992
    def test_call_method_filters_objectpaths_in_dict_three_deep(self):
 
1993
        ObjectPath = self.dummy_dbussy.DBUS.ObjectPath
 
1994
        def func():
 
1995
            return method_return
 
1996
        fake_ravel = self.fake_ravel_func(func)
 
1997
        bus = dbussy_adapter.SystemBus(self.dummy_dbussy, fake_ravel)
 
1998
        method_return = {
 
1999
            ObjectPath("key1"): {
 
2000
                ObjectPath("key2"): {
 
2001
                    ObjectPath("key3"): ObjectPath("value"),
 
2002
                },
 
2003
            },
 
2004
        }
 
2005
        ret = self.call_method(bus, "methodname", "busname",
 
2006
                               "objectpath", "interface")
 
2007
        expected_method_return = {"key1": {"key2": {"key3": "value"}}}
 
2008
        self.assertEqual(expected_method_return, ret)
 
2009
        self.assertIsInstance(ret, dict)
 
2010
        self.assertNotIsInstance(next(iter(ret.keys())), ObjectPath)
 
2011
        self.assertIsInstance(ret["key1"], dict)
 
2012
        self.assertNotIsInstance(next(iter(ret["key1"].keys())),
 
2013
                                 ObjectPath)
 
2014
        self.assertIsInstance(ret["key1"]["key2"], dict)
 
2015
        self.assertNotIsInstance(
 
2016
            next(iter(ret["key1"]["key2"].keys())),
 
2017
            ObjectPath)
 
2018
        self.assertEqual("value", ret["key1"]["key2"]["key3"])
 
2019
        self.assertNotIsInstance(ret["key1"]["key2"]["key3"],
 
2020
                                 self.dummy_dbussy.DBUS.ObjectPath)
 
2021
 
 
2022
    def test_call_method_handles_exception(self):
 
2023
        def func():
 
2024
            raise self.dummy_dbussy.DBusError()
 
2025
 
 
2026
        fake_ravel = self.fake_ravel_func(func)
 
2027
        bus = dbussy_adapter.SystemBus(self.dummy_dbussy, fake_ravel)
 
2028
 
 
2029
        with self.assertRaises(dbus.Error) as e:
 
2030
            self.call_method(bus, "methodname", "busname",
 
2031
                             "objectpath", "interface")
 
2032
 
 
2033
        self.assertNotIsInstance(e.exception, dbus.ConnectFailed)
 
2034
 
 
2035
    def test_get_object_converts_to_correct_exception(self):
 
2036
        class fake_ravel_raises_exception_on_connect:
 
2037
            @staticmethod
 
2038
            def system_bus():
 
2039
                class Bus:
 
2040
                    @staticmethod
 
2041
                    def __getitem__(key):
 
2042
                        if key == "objectpath":
 
2043
                            raise self.dummy_dbussy.DBusError()
 
2044
                        raise Exception(key)
 
2045
                return {"busname": Bus()}
 
2046
        def func():
 
2047
            raise self.dummy_dbussy.DBusError()
 
2048
        bus = dbussy_adapter.SystemBus(
 
2049
            self.dummy_dbussy,
 
2050
            fake_ravel_raises_exception_on_connect)
 
2051
        with self.assertRaises(dbus.ConnectFailed):
 
2052
            self.call_method(bus, "methodname", "busname",
 
2053
                             "objectpath", "interface")
 
2054
 
 
2055
 
1774
2056
class Test_commands_from_options(unittest.TestCase):
1775
2057
 
1776
2058
    def setUp(self):