/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 Hogeborn
  • Date: 2021-01-31 21:40:15 UTC
  • Revision ID: teddy@recompile.se-20210131214015-yz7ogk2mqfdfflo6
Work around Debian bug #981302

* plugin-runner.c (main): If the /dev/fd symlink is missing, create
  it.
* plugins.d/mandos-client.c (main): - '' -

Reported-By: Eero Häkkinen <+debian-bts-2021@eero.xn--hkkinen-5wa.fi>
Suggested-by: Eero Häkkinen <+debian-bts-2021@eero.xn--hkkinen-5wa.fi>
Thanks: Eero Häkkinen for bug report and analysis

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
#!/usr/bin/python
2
 
# -*- mode: python; coding: utf-8; after-save-hook: (lambda () (let ((command (if (and (boundp 'tramp-file-name-structure) (string-match (car tramp-file-name-structure) (buffer-file-name))) (tramp-file-name-localname (tramp-dissect-file-name (buffer-file-name))) (buffer-file-name)))) (if (= (shell-command (format "%s --check" (shell-quote-argument command)) "*Test*") 0) (let ((w (get-buffer-window "*Test*"))) (if w (delete-window w)) (kill-buffer "*Test*")) (display-buffer "*Test*")))); -*-
3
 
#
4
 
# Mandos Monitor - Control and monitor the Mandos server
5
 
#
6
 
# Copyright © 2008-2019 Teddy Hogeborn
7
 
# Copyright © 2008-2019 Björn Påhlsson
 
1
#!/usr/bin/python3 -bbI
 
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
#
 
4
# Mandos Control - Control or query the Mandos server
 
5
#
 
6
# Copyright © 2008-2020 Teddy Hogeborn
 
7
# Copyright © 2008-2020 Björn Påhlsson
8
8
#
9
9
# This file is part of Mandos.
10
10
#
46
46
import tempfile
47
47
import contextlib
48
48
 
49
 
import dbus as dbus_python
 
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
 
 
64
try:
 
65
    import dbussy
 
66
    import ravel
 
67
except ImportError:
 
68
    try:
 
69
        import pydbus
 
70
        import gi
 
71
    except ImportError:
 
72
        import dbus as dbus_python
 
73
 
50
74
 
51
75
# Show warnings by default
52
76
if not sys.warnoptions:
60
84
logging.captureWarnings(True)   # Show warnings via the logging system
61
85
 
62
86
if sys.version_info.major == 2:
63
 
    str = unicode
64
87
    import StringIO
65
88
    io.StringIO = StringIO.StringIO
66
89
 
67
90
locale.setlocale(locale.LC_ALL, "")
68
91
 
69
 
version = "1.8.3"
 
92
version = "1.8.13"
70
93
 
71
94
 
72
95
def main():
81
104
    if options.debug:
82
105
        log.setLevel(logging.DEBUG)
83
106
 
84
 
    bus = dbus_python_adapter.CachingBus(dbus_python)
 
107
    if dbussy is not None and ravel is not None:
 
108
        bus = dbussy_adapter.CachingBus(dbussy, ravel)
 
109
    elif pydbus is not None:
 
110
        bus = pydbus_adapter.CachingBus(pydbus)
 
111
    else:
 
112
        bus = dbus_python_adapter.CachingBus(dbus_python)
85
113
 
86
114
    try:
87
115
        all_clients = bus.get_clients_and_properties()
235
263
def rfc3339_duration_to_delta(duration):
236
264
    """Parse an RFC 3339 "duration" and return a datetime.timedelta
237
265
 
238
 
    >>> rfc3339_duration_to_delta("P7D")
239
 
    datetime.timedelta(7)
240
 
    >>> rfc3339_duration_to_delta("PT60S")
241
 
    datetime.timedelta(0, 60)
242
 
    >>> rfc3339_duration_to_delta("PT60M")
243
 
    datetime.timedelta(0, 3600)
244
 
    >>> rfc3339_duration_to_delta("P60M")
245
 
    datetime.timedelta(1680)
246
 
    >>> rfc3339_duration_to_delta("PT24H")
247
 
    datetime.timedelta(1)
248
 
    >>> rfc3339_duration_to_delta("P1W")
249
 
    datetime.timedelta(7)
250
 
    >>> rfc3339_duration_to_delta("PT5M30S")
251
 
    datetime.timedelta(0, 330)
252
 
    >>> rfc3339_duration_to_delta("P1DT3M20S")
253
 
    datetime.timedelta(1, 200)
 
266
    >>> rfc3339_duration_to_delta("P7D") == datetime.timedelta(7)
 
267
    True
 
268
    >>> rfc3339_duration_to_delta("PT60S") == datetime.timedelta(0, 60)
 
269
    True
 
270
    >>> rfc3339_duration_to_delta("PT60M") == datetime.timedelta(hours=1)
 
271
    True
 
272
    >>> # 60 months
 
273
    >>> rfc3339_duration_to_delta("P60M") == datetime.timedelta(1680)
 
274
    True
 
275
    >>> rfc3339_duration_to_delta("PT24H") == datetime.timedelta(1)
 
276
    True
 
277
    >>> rfc3339_duration_to_delta("P1W") == datetime.timedelta(7)
 
278
    True
 
279
    >>> rfc3339_duration_to_delta("PT5M30S") == datetime.timedelta(0, 330)
 
280
    True
 
281
    >>> rfc3339_duration_to_delta("P1DT3M20S") == datetime.timedelta(1, 200)
 
282
    True
254
283
    >>> # Can not be empty:
255
284
    >>> rfc3339_duration_to_delta("")
256
285
    Traceback (most recent call last):
366
395
    """Parse an interval string as documented by Mandos before 1.6.1,
367
396
    and return a datetime.timedelta
368
397
 
369
 
    >>> parse_pre_1_6_1_interval('7d')
370
 
    datetime.timedelta(7)
371
 
    >>> parse_pre_1_6_1_interval('60s')
372
 
    datetime.timedelta(0, 60)
373
 
    >>> parse_pre_1_6_1_interval('60m')
374
 
    datetime.timedelta(0, 3600)
375
 
    >>> parse_pre_1_6_1_interval('24h')
376
 
    datetime.timedelta(1)
377
 
    >>> parse_pre_1_6_1_interval('1w')
378
 
    datetime.timedelta(7)
379
 
    >>> parse_pre_1_6_1_interval('5m 30s')
380
 
    datetime.timedelta(0, 330)
381
 
    >>> parse_pre_1_6_1_interval('')
382
 
    datetime.timedelta(0)
 
398
    >>> parse_pre_1_6_1_interval('7d') == datetime.timedelta(days=7)
 
399
    True
 
400
    >>> parse_pre_1_6_1_interval('60s') == datetime.timedelta(0, 60)
 
401
    True
 
402
    >>> parse_pre_1_6_1_interval('60m') == datetime.timedelta(hours=1)
 
403
    True
 
404
    >>> parse_pre_1_6_1_interval('24h') == datetime.timedelta(days=1)
 
405
    True
 
406
    >>> parse_pre_1_6_1_interval('1w') == datetime.timedelta(days=7)
 
407
    True
 
408
    >>> parse_pre_1_6_1_interval('5m 30s') == datetime.timedelta(0, 330)
 
409
    True
 
410
    >>> parse_pre_1_6_1_interval('') == datetime.timedelta(0)
 
411
    True
383
412
    >>> # Ignore unknown characters, allow any order and repetitions
384
 
    >>> parse_pre_1_6_1_interval('2dxy7zz11y3m5m')
385
 
    datetime.timedelta(2, 480, 18000)
 
413
    >>> parse_pre_1_6_1_interval('2dxy7zz11y3m5m') == datetime.timedelta(2, 480, 18000)
 
414
    True
386
415
 
387
416
    """
388
417
 
451
480
        parser.error("--remove can only be combined with --deny")
452
481
 
453
482
 
454
 
class dbus(object):
 
483
class dbus:
455
484
 
456
 
    class SystemBus(object):
 
485
    class SystemBus:
457
486
 
458
487
        object_manager_iface = "org.freedesktop.DBus.ObjectManager"
459
488
        def get_managed_objects(self, busname, objectpath):
468
497
                             self.properties_iface, interface, key,
469
498
                             value)
470
499
 
 
500
        def call_method(self, methodname, busname, objectpath,
 
501
                        interface, *args):
 
502
            raise NotImplementedError()
 
503
 
471
504
 
472
505
    class MandosBus(SystemBus):
473
506
        busname_domain = "se.recompile"
505
538
        pass
506
539
 
507
540
 
508
 
class dbus_python_adapter(object):
 
541
class dbus_python_adapter:
509
542
 
510
543
    class SystemBus(dbus.MandosBus):
511
544
        """Use dbus-python"""
556
589
                        for key, subval in value.items()}
557
590
            return value
558
591
 
 
592
        def set_client_property(self, objectpath, key, value):
 
593
            if key == "Secret":
 
594
                if not isinstance(value, bytes):
 
595
                    value = value.encode("utf-8")
 
596
                value = self.dbus_python.ByteArray(value)
 
597
            return self.set_property(self.busname, objectpath,
 
598
                                     self.client_interface, key,
 
599
                                     value)
559
600
 
560
 
    class SilenceLogger(object):
 
601
    class SilenceLogger:
561
602
        "Simple context manager to silence a particular logger"
562
603
        def __init__(self, loggername):
563
604
            self.logger = logging.getLogger(loggername)
592
633
                return new_object
593
634
 
594
635
 
 
636
class pydbus_adapter:
 
637
    class SystemBus(dbus.MandosBus):
 
638
        def __init__(self, module=pydbus):
 
639
            self.pydbus = module
 
640
            self.bus = self.pydbus.SystemBus()
 
641
 
 
642
        @contextlib.contextmanager
 
643
        def convert_exception(self, exception_class=dbus.Error):
 
644
            try:
 
645
                yield
 
646
            except gi.repository.GLib.Error as e:
 
647
                # This does what "raise from" would do
 
648
                exc = exception_class(*e.args)
 
649
                exc.__cause__ = e
 
650
                raise exc
 
651
 
 
652
        def call_method(self, methodname, busname, objectpath,
 
653
                        interface, *args):
 
654
            proxy_object = self.get(busname, objectpath)
 
655
            log.debug("D-Bus: %s:%s:%s.%s(%s)", busname, objectpath,
 
656
                      interface, methodname,
 
657
                      ", ".join(repr(a) for a in args))
 
658
            method = getattr(proxy_object[interface], methodname)
 
659
            with self.convert_exception():
 
660
                return method(*args)
 
661
 
 
662
        def get(self, busname, objectpath):
 
663
            log.debug("D-Bus: Connect to: (busname=%r, path=%r)",
 
664
                      busname, objectpath)
 
665
            with self.convert_exception(dbus.ConnectFailed):
 
666
                if sys.version_info.major <= 2:
 
667
                    with warnings.catch_warnings():
 
668
                        warnings.filterwarnings(
 
669
                            "ignore", "", DeprecationWarning,
 
670
                            r"^xml\.etree\.ElementTree$")
 
671
                        return self.bus.get(busname, objectpath)
 
672
                else:
 
673
                    return self.bus.get(busname, objectpath)
 
674
 
 
675
        def set_property(self, busname, objectpath, interface, key,
 
676
                         value):
 
677
            proxy_object = self.get(busname, objectpath)
 
678
            log.debug("D-Bus: %s:%s:%s.Set(%r, %r, %r)", busname,
 
679
                      objectpath, self.properties_iface, interface,
 
680
                      key, value)
 
681
            setattr(proxy_object[interface], key, value)
 
682
 
 
683
    class CachingBus(SystemBus):
 
684
        """A caching layer for pydbus_adapter.SystemBus"""
 
685
        def __init__(self, *args, **kwargs):
 
686
            self.object_cache = {}
 
687
            super(pydbus_adapter.CachingBus,
 
688
                  self).__init__(*args, **kwargs)
 
689
        def get(self, busname, objectpath):
 
690
            try:
 
691
                return self.object_cache[(busname, objectpath)]
 
692
            except KeyError:
 
693
                new_object = (super(pydbus_adapter.CachingBus, self)
 
694
                              .get(busname, objectpath))
 
695
                self.object_cache[(busname, objectpath)]  = new_object
 
696
                return new_object
 
697
 
 
698
 
 
699
class dbussy_adapter:
 
700
    class SystemBus(dbus.SystemBus):
 
701
        """Use DBussy"""
 
702
 
 
703
        def __init__(self, dbussy, ravel):
 
704
            self.dbussy = dbussy
 
705
            self.ravel = ravel
 
706
            self.bus = ravel.system_bus()
 
707
 
 
708
        @contextlib.contextmanager
 
709
        def convert_exception(self, exception_class=dbus.Error):
 
710
            try:
 
711
                yield
 
712
            except self.dbussy.DBusError as e:
 
713
                # This does what "raise from" would do
 
714
                exc = exception_class(*e.args)
 
715
                exc.__cause__ = e
 
716
                raise exc
 
717
 
 
718
        def call_method(self, methodname, busname, objectpath,
 
719
                        interface, *args):
 
720
            proxy_object = self.get_object(busname, objectpath)
 
721
            log.debug("D-Bus: %s:%s:%s.%s(%s)", busname, objectpath,
 
722
                      interface, methodname,
 
723
                      ", ".join(repr(a) for a in args))
 
724
            iface = proxy_object.get_interface(interface)
 
725
            method = getattr(iface, methodname)
 
726
            with self.convert_exception(dbus.Error):
 
727
                value =  method(*args)
 
728
            # DBussy returns values either as an empty list or as a
 
729
            # list of one element with the return value
 
730
            if value:
 
731
                return self.type_filter(value[0])
 
732
 
 
733
        def get_object(self, busname, objectpath):
 
734
            log.debug("D-Bus: Connect to: (busname=%r, path=%r)",
 
735
                      busname, objectpath)
 
736
            with self.convert_exception(dbus.ConnectFailed):
 
737
                return self.bus[busname][objectpath]
 
738
 
 
739
        def type_filter(self, value):
 
740
            """Convert the most bothersome types to Python types"""
 
741
            # A D-Bus Variant value is represented as the Python type
 
742
            # Tuple[dbussy.DBUS.Signature, Any]
 
743
            if isinstance(value, tuple):
 
744
                if (len(value) == 2
 
745
                    and isinstance(value[0],
 
746
                                   self.dbussy.DBUS.Signature)):
 
747
                    return self.type_filter(value[1])
 
748
            elif isinstance(value, self.dbussy.DBUS.ObjectPath):
 
749
                return str(value)
 
750
            # Also recurse into dictionaries
 
751
            elif isinstance(value, dict):
 
752
                return {self.type_filter(key):
 
753
                        self.type_filter(subval)
 
754
                        for key, subval in value.items()}
 
755
            return value
 
756
 
 
757
        def set_property(self, busname, objectpath, interface, key,
 
758
                         value):
 
759
            proxy_object = self.get_object(busname, objectpath)
 
760
            log.debug("D-Bus: %s:%s:%s.Set(%r, %r, %r)", busname,
 
761
                      objectpath, self.properties_iface, interface,
 
762
                      key, value)
 
763
            if key == "Secret":
 
764
                # DBussy wants a Byte Array to be a sequence of
 
765
                # values, not a byte string
 
766
                value = tuple(value)
 
767
            setattr(proxy_object.get_interface(interface), key, value)
 
768
 
 
769
    class MandosBus(SystemBus, dbus.MandosBus):
 
770
        pass
 
771
 
 
772
    class CachingBus(MandosBus):
 
773
        """A caching layer for dbussy_adapter.MandosBus"""
 
774
        def __init__(self, *args, **kwargs):
 
775
            self.object_cache = {}
 
776
            super(dbussy_adapter.CachingBus, self).__init__(*args,
 
777
                                                            **kwargs)
 
778
        def get_object(self, busname, objectpath):
 
779
            try:
 
780
                return self.object_cache[(busname, objectpath)]
 
781
            except KeyError:
 
782
                new_object = super(
 
783
                    dbussy_adapter.CachingBus,
 
784
                    self).get_object(busname, objectpath)
 
785
                self.object_cache[(busname, objectpath)]  = new_object
 
786
                return new_object
 
787
 
 
788
 
595
789
def commands_from_options(options):
596
790
 
597
791
    commands = list(options.commands)
625
819
    return commands
626
820
 
627
821
 
628
 
class command(object):
 
822
class command:
629
823
    """A namespace for command classes"""
630
824
 
631
 
    class Base(object):
 
825
    class Base:
632
826
        """Abstract base class for commands"""
633
827
        def run(self, clients, bus=None):
634
828
            """Normal commands should implement run_on_one_client(),
697
891
                keywords = self.all_keywords
698
892
            print(self.TableOfClients(clients.values(), keywords))
699
893
 
700
 
        class TableOfClients(object):
 
894
        class TableOfClients:
701
895
            tableheaders = {
702
896
                "Name": "Name",
703
897
                "Enabled": "Enabled",
934
1128
                                                     "output"))
935
1129
 
936
1130
 
937
 
class Unique(object):
 
1131
class Unique:
938
1132
    """Class for objects which exist only to be unique objects, since
939
1133
unittest.mock.sentinel only exists in Python 3.3"""
940
1134
 
1224
1418
class Test_dbus_python_adapter_SystemBus(TestCaseWithAssertLogs):
1225
1419
 
1226
1420
    def MockDBusPython_func(self, func):
1227
 
        class mock_dbus_python(object):
 
1421
        class mock_dbus_python:
1228
1422
            """mock dbus-python module"""
1229
 
            class exceptions(object):
 
1423
            class exceptions:
1230
1424
                """Pseudo-namespace"""
1231
1425
                class DBusException(Exception):
1232
1426
                    pass
1233
 
            class SystemBus(object):
 
1427
            class SystemBus:
1234
1428
                @staticmethod
1235
1429
                def get_object(busname, objectpath):
1236
1430
                    DBusObject = collections.namedtuple(
1237
 
                        "DBusObject", ("methodname",))
 
1431
                        "DBusObject", ("methodname", "Set"))
1238
1432
                    def method(*args, **kwargs):
1239
1433
                        self.assertEqual({"dbus_interface":
1240
1434
                                          "interface"},
1241
1435
                                         kwargs)
1242
1436
                        return func(*args)
1243
 
                    return DBusObject(methodname=method)
1244
 
            class Boolean(object):
 
1437
                    def set_property(interface, key, value,
 
1438
                                     dbus_interface=None):
 
1439
                        self.assertEqual(
 
1440
                            "org.freedesktop.DBus.Properties",
 
1441
                            dbus_interface)
 
1442
                        self.assertEqual("Secret", key)
 
1443
                        return func(interface, key, value,
 
1444
                                    dbus_interface=dbus_interface)
 
1445
                    return DBusObject(methodname=method,
 
1446
                                      Set=set_property)
 
1447
            class Boolean:
1245
1448
                def __init__(self, value):
1246
1449
                    self.value = bool(value)
1247
1450
                def __bool__(self):
1252
1455
                pass
1253
1456
            class Dictionary(dict):
1254
1457
                pass
 
1458
            class ByteArray(bytes):
 
1459
                pass
1255
1460
        return mock_dbus_python
1256
1461
 
1257
1462
    def call_method(self, bus, methodname, busname, objectpath,
1429
1634
        finally:
1430
1635
            dbus_logger.removeFilter(counting_handler)
1431
1636
 
1432
 
        self.assertNotIsInstance(e, dbus.ConnectFailed)
 
1637
        self.assertNotIsInstance(e.exception, dbus.ConnectFailed)
1433
1638
 
1434
1639
        # Make sure the dbus logger was suppressed
1435
1640
        self.assertEqual(0, counting_handler.count)
1436
1641
 
 
1642
    def test_Set_Secret_sends_bytearray(self):
 
1643
        ret = [None]
 
1644
        def func(*args, **kwargs):
 
1645
            ret[0] = (args, kwargs)
 
1646
        mock_dbus_python = self.MockDBusPython_func(func)
 
1647
        bus = dbus_python_adapter.SystemBus(mock_dbus_python)
 
1648
        bus.set_client_property("objectpath", "Secret", "value")
 
1649
        expected_call = (("se.recompile.Mandos.Client", "Secret",
 
1650
                          mock_dbus_python.ByteArray(b"value")),
 
1651
                         {"dbus_interface":
 
1652
                          "org.freedesktop.DBus.Properties"})
 
1653
        self.assertEqual(expected_call, ret[0])
 
1654
        if sys.version_info.major == 2:
 
1655
            self.assertIsInstance(ret[0][0][-1],
 
1656
                                  mock_dbus_python.ByteArray)
 
1657
 
1437
1658
    def test_get_object_converts_to_correct_exception(self):
1438
1659
        bus = dbus_python_adapter.SystemBus(
1439
1660
            self.fake_dbus_python_raises_exception_on_connect)
1441
1662
            self.call_method(bus, "methodname", "busname",
1442
1663
                             "objectpath", "interface")
1443
1664
 
1444
 
    class fake_dbus_python_raises_exception_on_connect(object):
 
1665
    class fake_dbus_python_raises_exception_on_connect:
1445
1666
        """fake dbus-python module"""
1446
 
        class exceptions(object):
 
1667
        class exceptions:
1447
1668
            """Pseudo-namespace"""
1448
1669
            class DBusException(Exception):
1449
1670
                pass
1457
1678
 
1458
1679
 
1459
1680
class Test_dbus_python_adapter_CachingBus(unittest.TestCase):
1460
 
    class mock_dbus_python(object):
 
1681
    class mock_dbus_python:
1461
1682
        """mock dbus-python modules"""
1462
 
        class SystemBus(object):
 
1683
        class SystemBus:
1463
1684
            @staticmethod
1464
1685
            def get_object(busname, objectpath):
1465
1686
                return Unique()
1508
1729
        self.assertIs(obj1, obj1b)
1509
1730
 
1510
1731
 
 
1732
class Test_pydbus_adapter_SystemBus(TestCaseWithAssertLogs):
 
1733
 
 
1734
    def Stub_pydbus_func(self, func):
 
1735
        class stub_pydbus:
 
1736
            """stub pydbus module"""
 
1737
            class SystemBus:
 
1738
                @staticmethod
 
1739
                def get(busname, objectpath):
 
1740
                    DBusObject = collections.namedtuple(
 
1741
                        "DBusObject", ("methodname",))
 
1742
                    return {"interface":
 
1743
                            DBusObject(methodname=func)}
 
1744
        return stub_pydbus
 
1745
 
 
1746
    def call_method(self, bus, methodname, busname, objectpath,
 
1747
                    interface, *args):
 
1748
        with self.assertLogs(log, logging.DEBUG):
 
1749
            return bus.call_method(methodname, busname, objectpath,
 
1750
                                   interface, *args)
 
1751
 
 
1752
    def test_call_method_returns(self):
 
1753
        expected_method_return = Unique()
 
1754
        method_args = (Unique(), Unique())
 
1755
        def func(*args):
 
1756
            self.assertEqual(len(method_args), len(args))
 
1757
            for marg, arg in zip(method_args, args):
 
1758
                self.assertIs(marg, arg)
 
1759
            return expected_method_return
 
1760
        stub_pydbus = self.Stub_pydbus_func(func)
 
1761
        bus = pydbus_adapter.SystemBus(stub_pydbus)
 
1762
        ret = self.call_method(bus, "methodname", "busname",
 
1763
                               "objectpath", "interface",
 
1764
                               *method_args)
 
1765
        self.assertIs(ret, expected_method_return)
 
1766
 
 
1767
    def test_call_method_handles_exception(self):
 
1768
        dbus_logger = logging.getLogger("dbus.proxies")
 
1769
 
 
1770
        def func():
 
1771
            raise gi.repository.GLib.Error()
 
1772
 
 
1773
        stub_pydbus = self.Stub_pydbus_func(func)
 
1774
        bus = pydbus_adapter.SystemBus(stub_pydbus)
 
1775
 
 
1776
        with self.assertRaises(dbus.Error) as e:
 
1777
            self.call_method(bus, "methodname", "busname",
 
1778
                             "objectpath", "interface")
 
1779
 
 
1780
        self.assertNotIsInstance(e.exception, dbus.ConnectFailed)
 
1781
 
 
1782
    def test_get_converts_to_correct_exception(self):
 
1783
        bus = pydbus_adapter.SystemBus(
 
1784
            self.fake_pydbus_raises_exception_on_connect)
 
1785
        with self.assertRaises(dbus.ConnectFailed):
 
1786
            self.call_method(bus, "methodname", "busname",
 
1787
                             "objectpath", "interface")
 
1788
 
 
1789
    class fake_pydbus_raises_exception_on_connect:
 
1790
        """fake dbus-python module"""
 
1791
        @classmethod
 
1792
        def SystemBus(cls):
 
1793
            def get(busname, objectpath):
 
1794
                raise gi.repository.GLib.Error()
 
1795
            Bus = collections.namedtuple("Bus", ["get"])
 
1796
            return Bus(get=get)
 
1797
 
 
1798
    def test_set_property_uses_setattr(self):
 
1799
        class Object:
 
1800
            pass
 
1801
        obj = Object()
 
1802
        class pydbus_spy:
 
1803
            class SystemBus:
 
1804
                @staticmethod
 
1805
                def get(busname, objectpath):
 
1806
                    return {"interface": obj}
 
1807
        bus = pydbus_adapter.SystemBus(pydbus_spy)
 
1808
        value = Unique()
 
1809
        bus.set_property("busname", "objectpath", "interface", "key",
 
1810
                         value)
 
1811
        self.assertIs(value, obj.key)
 
1812
 
 
1813
    def test_get_suppresses_xml_deprecation_warning(self):
 
1814
        if sys.version_info.major >= 3:
 
1815
            return
 
1816
        class stub_pydbus_get:
 
1817
            class SystemBus:
 
1818
                @staticmethod
 
1819
                def get(busname, objectpath):
 
1820
                    warnings.warn_explicit(
 
1821
                        "deprecated", DeprecationWarning,
 
1822
                        "xml.etree.ElementTree", 0)
 
1823
        bus = pydbus_adapter.SystemBus(stub_pydbus_get)
 
1824
        with warnings.catch_warnings(record=True) as w:
 
1825
            warnings.simplefilter("always")
 
1826
            bus.get("busname", "objectpath")
 
1827
            self.assertEqual(0, len(w))
 
1828
 
 
1829
 
 
1830
class Test_pydbus_adapter_CachingBus(unittest.TestCase):
 
1831
    class stub_pydbus:
 
1832
        """stub pydbus module"""
 
1833
        class SystemBus:
 
1834
            @staticmethod
 
1835
            def get(busname, objectpath):
 
1836
                return Unique()
 
1837
 
 
1838
    def setUp(self):
 
1839
        self.bus = pydbus_adapter.CachingBus(self.stub_pydbus)
 
1840
 
 
1841
    def test_returns_distinct_objectpaths(self):
 
1842
        obj1 = self.bus.get("busname", "objectpath1")
 
1843
        self.assertIsInstance(obj1, Unique)
 
1844
        obj2 = self.bus.get("busname", "objectpath2")
 
1845
        self.assertIsInstance(obj2, Unique)
 
1846
        self.assertIsNot(obj1, obj2)
 
1847
 
 
1848
    def test_returns_distinct_busnames(self):
 
1849
        obj1 = self.bus.get("busname1", "objectpath")
 
1850
        self.assertIsInstance(obj1, Unique)
 
1851
        obj2 = self.bus.get("busname2", "objectpath")
 
1852
        self.assertIsInstance(obj2, Unique)
 
1853
        self.assertIsNot(obj1, obj2)
 
1854
 
 
1855
    def test_returns_distinct_both(self):
 
1856
        obj1 = self.bus.get("busname1", "objectpath")
 
1857
        self.assertIsInstance(obj1, Unique)
 
1858
        obj2 = self.bus.get("busname2", "objectpath")
 
1859
        self.assertIsInstance(obj2, Unique)
 
1860
        self.assertIsNot(obj1, obj2)
 
1861
 
 
1862
    def test_returns_same(self):
 
1863
        obj1 = self.bus.get("busname", "objectpath")
 
1864
        self.assertIsInstance(obj1, Unique)
 
1865
        obj2 = self.bus.get("busname", "objectpath")
 
1866
        self.assertIsInstance(obj2, Unique)
 
1867
        self.assertIs(obj1, obj2)
 
1868
 
 
1869
    def test_returns_same_old(self):
 
1870
        obj1 = self.bus.get("busname1", "objectpath1")
 
1871
        self.assertIsInstance(obj1, Unique)
 
1872
        obj2 = self.bus.get("busname2", "objectpath2")
 
1873
        self.assertIsInstance(obj2, Unique)
 
1874
        obj1b = self.bus.get("busname1", "objectpath1")
 
1875
        self.assertIsInstance(obj1b, Unique)
 
1876
        self.assertIsNot(obj1, obj2)
 
1877
        self.assertIsNot(obj2, obj1b)
 
1878
        self.assertIs(obj1, obj1b)
 
1879
 
 
1880
 
 
1881
class Test_dbussy_adapter_SystemBus(TestCaseWithAssertLogs):
 
1882
 
 
1883
    class dummy_dbussy:
 
1884
        class DBUS:
 
1885
            class ObjectPath(str):
 
1886
                pass
 
1887
        class DBusError(Exception):
 
1888
            pass
 
1889
 
 
1890
    def fake_ravel_func(self, func):
 
1891
        class fake_ravel:
 
1892
            @staticmethod
 
1893
            def system_bus():
 
1894
                class DBusInterfaceProxy:
 
1895
                    @staticmethod
 
1896
                    def methodname(*args):
 
1897
                        return [func(*args)]
 
1898
                class DBusObject:
 
1899
                    @staticmethod
 
1900
                    def get_interface(interface):
 
1901
                        if interface == "interface":
 
1902
                            return DBusInterfaceProxy()
 
1903
                return {"busname": {"objectpath": DBusObject()}}
 
1904
        return fake_ravel
 
1905
 
 
1906
    def call_method(self, bus, methodname, busname, objectpath,
 
1907
                    interface, *args):
 
1908
        with self.assertLogs(log, logging.DEBUG):
 
1909
            return bus.call_method(methodname, busname, objectpath,
 
1910
                                   interface, *args)
 
1911
 
 
1912
    def test_call_method_returns(self):
 
1913
        expected_method_return = Unique()
 
1914
        method_args = (Unique(), Unique())
 
1915
        def func(*args):
 
1916
            self.assertEqual(len(method_args), len(args))
 
1917
            for marg, arg in zip(method_args, args):
 
1918
                self.assertIs(marg, arg)
 
1919
            return expected_method_return
 
1920
        fake_ravel = self.fake_ravel_func(func)
 
1921
        bus = dbussy_adapter.SystemBus(self.dummy_dbussy, fake_ravel)
 
1922
        ret = self.call_method(bus, "methodname", "busname",
 
1923
                               "objectpath", "interface",
 
1924
                               *method_args)
 
1925
        self.assertIs(ret, expected_method_return)
 
1926
 
 
1927
    def test_call_method_filters_objectpath(self):
 
1928
        def func():
 
1929
            return method_return
 
1930
        fake_ravel = self.fake_ravel_func(func)
 
1931
        bus = dbussy_adapter.SystemBus(self.dummy_dbussy, fake_ravel)
 
1932
        method_return = (self.dummy_dbussy.DBUS
 
1933
                         .ObjectPath("objectpath"))
 
1934
        ret = self.call_method(bus, "methodname", "busname",
 
1935
                               "objectpath", "interface")
 
1936
        self.assertEqual("objectpath", ret)
 
1937
        self.assertNotIsInstance(ret,
 
1938
                                 self.dummy_dbussy.DBUS.ObjectPath)
 
1939
 
 
1940
    def test_call_method_filters_objectpaths_in_dict(self):
 
1941
        ObjectPath = self.dummy_dbussy.DBUS.ObjectPath
 
1942
        def func():
 
1943
            return method_return
 
1944
        fake_ravel = self.fake_ravel_func(func)
 
1945
        bus = dbussy_adapter.SystemBus(self.dummy_dbussy, fake_ravel)
 
1946
        method_return = {
 
1947
            ObjectPath("objectpath_key_1"):
 
1948
            ObjectPath("objectpath_value_1"),
 
1949
            ObjectPath("objectpath_key_2"):
 
1950
            ObjectPath("objectpath_value_2"),
 
1951
        }
 
1952
        ret = self.call_method(bus, "methodname", "busname",
 
1953
                               "objectpath", "interface")
 
1954
        expected_method_return = {str(key): str(value)
 
1955
                                  for key, value in
 
1956
                                  method_return.items()}
 
1957
        for key, value in ret.items():
 
1958
            self.assertNotIsInstance(key, ObjectPath)
 
1959
            self.assertNotIsInstance(value, ObjectPath)
 
1960
        self.assertEqual(expected_method_return, ret)
 
1961
        self.assertIsInstance(ret, dict)
 
1962
 
 
1963
    def test_call_method_filters_objectpaths_in_dict_in_dict(self):
 
1964
        ObjectPath = self.dummy_dbussy.DBUS.ObjectPath
 
1965
        def func():
 
1966
            return method_return
 
1967
        fake_ravel = self.fake_ravel_func(func)
 
1968
        bus = dbussy_adapter.SystemBus(self.dummy_dbussy, fake_ravel)
 
1969
        method_return = {
 
1970
            ObjectPath("key1"): {
 
1971
                ObjectPath("key11"): ObjectPath("value11"),
 
1972
                ObjectPath("key12"): ObjectPath("value12"),
 
1973
            },
 
1974
            ObjectPath("key2"): {
 
1975
                ObjectPath("key21"): ObjectPath("value21"),
 
1976
                ObjectPath("key22"): ObjectPath("value22"),
 
1977
            },
 
1978
        }
 
1979
        ret = self.call_method(bus, "methodname", "busname",
 
1980
                               "objectpath", "interface")
 
1981
        expected_method_return = {
 
1982
            "key1": {"key11": "value11",
 
1983
                     "key12": "value12"},
 
1984
            "key2": {"key21": "value21",
 
1985
                     "key22": "value22"},
 
1986
        }
 
1987
        self.assertEqual(expected_method_return, ret)
 
1988
        for key, value in ret.items():
 
1989
            self.assertIsInstance(value, dict)
 
1990
            self.assertEqual(expected_method_return[key], value)
 
1991
            self.assertNotIsInstance(key, ObjectPath)
 
1992
            for inner_key, inner_value in value.items():
 
1993
                self.assertIsInstance(value, dict)
 
1994
                self.assertEqual(
 
1995
                    expected_method_return[key][inner_key],
 
1996
                    inner_value)
 
1997
                self.assertNotIsInstance(key, ObjectPath)
 
1998
 
 
1999
    def test_call_method_filters_objectpaths_in_dict_three_deep(self):
 
2000
        ObjectPath = self.dummy_dbussy.DBUS.ObjectPath
 
2001
        def func():
 
2002
            return method_return
 
2003
        fake_ravel = self.fake_ravel_func(func)
 
2004
        bus = dbussy_adapter.SystemBus(self.dummy_dbussy, fake_ravel)
 
2005
        method_return = {
 
2006
            ObjectPath("key1"): {
 
2007
                ObjectPath("key2"): {
 
2008
                    ObjectPath("key3"): ObjectPath("value"),
 
2009
                },
 
2010
            },
 
2011
        }
 
2012
        ret = self.call_method(bus, "methodname", "busname",
 
2013
                               "objectpath", "interface")
 
2014
        expected_method_return = {"key1": {"key2": {"key3": "value"}}}
 
2015
        self.assertEqual(expected_method_return, ret)
 
2016
        self.assertIsInstance(ret, dict)
 
2017
        self.assertNotIsInstance(next(iter(ret.keys())), ObjectPath)
 
2018
        self.assertIsInstance(ret["key1"], dict)
 
2019
        self.assertNotIsInstance(next(iter(ret["key1"].keys())),
 
2020
                                 ObjectPath)
 
2021
        self.assertIsInstance(ret["key1"]["key2"], dict)
 
2022
        self.assertNotIsInstance(
 
2023
            next(iter(ret["key1"]["key2"].keys())),
 
2024
            ObjectPath)
 
2025
        self.assertEqual("value", ret["key1"]["key2"]["key3"])
 
2026
        self.assertNotIsInstance(ret["key1"]["key2"]["key3"],
 
2027
                                 self.dummy_dbussy.DBUS.ObjectPath)
 
2028
 
 
2029
    def test_call_method_handles_exception(self):
 
2030
        def func():
 
2031
            raise self.dummy_dbussy.DBusError()
 
2032
 
 
2033
        fake_ravel = self.fake_ravel_func(func)
 
2034
        bus = dbussy_adapter.SystemBus(self.dummy_dbussy, fake_ravel)
 
2035
 
 
2036
        with self.assertRaises(dbus.Error) as e:
 
2037
            self.call_method(bus, "methodname", "busname",
 
2038
                             "objectpath", "interface")
 
2039
 
 
2040
        self.assertNotIsInstance(e.exception, dbus.ConnectFailed)
 
2041
 
 
2042
    def test_get_object_converts_to_correct_exception(self):
 
2043
        class fake_ravel_raises_exception_on_connect:
 
2044
            @staticmethod
 
2045
            def system_bus():
 
2046
                class Bus:
 
2047
                    @staticmethod
 
2048
                    def __getitem__(key):
 
2049
                        if key == "objectpath":
 
2050
                            raise self.dummy_dbussy.DBusError()
 
2051
                        raise Exception(key)
 
2052
                return {"busname": Bus()}
 
2053
        def func():
 
2054
            raise self.dummy_dbussy.DBusError()
 
2055
        bus = dbussy_adapter.SystemBus(
 
2056
            self.dummy_dbussy,
 
2057
            fake_ravel_raises_exception_on_connect)
 
2058
        with self.assertRaises(dbus.ConnectFailed):
 
2059
            self.call_method(bus, "methodname", "busname",
 
2060
                             "objectpath", "interface")
 
2061
 
 
2062
 
1511
2063
class Test_commands_from_options(unittest.TestCase):
1512
2064
 
1513
2065
    def setUp(self):
1518
2070
        self.assert_command_from_args(["--is-enabled", "client"],
1519
2071
                                      command.IsEnabled)
1520
2072
 
1521
 
    def assert_command_from_args(self, args, command_cls,
1522
 
                                 **cmd_attrs):
 
2073
    def assert_command_from_args(self, args, command_cls, length=1,
 
2074
                                 clients=None, **cmd_attrs):
1523
2075
        """Assert that parsing ARGS should result in an instance of
1524
2076
COMMAND_CLS with (optionally) all supplied attributes (CMD_ATTRS)."""
1525
2077
        options = self.parser.parse_args(args)
1526
2078
        check_option_syntax(self.parser, options)
1527
2079
        commands = commands_from_options(options)
1528
 
        self.assertEqual(1, len(commands))
1529
 
        command = commands[0]
1530
 
        self.assertIsInstance(command, command_cls)
 
2080
        self.assertEqual(length, len(commands))
 
2081
        for command in commands:
 
2082
            if isinstance(command, command_cls):
 
2083
                break
 
2084
        else:
 
2085
            self.assertIsInstance(command, command_cls)
 
2086
        if clients is not None:
 
2087
            self.assertEqual(clients, options.client)
1531
2088
        for key, value in cmd_attrs.items():
1532
2089
            self.assertEqual(value, getattr(command, key))
1533
2090
 
 
2091
    def assert_commands_from_args(self, args, commands, clients=None):
 
2092
        for cmd in commands:
 
2093
            self.assert_command_from_args(args, cmd,
 
2094
                                          length=len(commands),
 
2095
                                          clients=clients)
 
2096
 
1534
2097
    def test_is_enabled_short(self):
1535
2098
        self.assert_command_from_args(["-V", "client"],
1536
2099
                                      command.IsEnabled)
1727
2290
                                      verbose=True)
1728
2291
 
1729
2292
 
 
2293
    def test_manual_page_example_1(self):
 
2294
        self.assert_command_from_args("",
 
2295
                                      command.PrintTable,
 
2296
                                      clients=[],
 
2297
                                      verbose=False)
 
2298
 
 
2299
    def test_manual_page_example_2(self):
 
2300
        self.assert_command_from_args(
 
2301
            "--verbose foo1.example.org foo2.example.org".split(),
 
2302
            command.PrintTable, clients=["foo1.example.org",
 
2303
                                         "foo2.example.org"],
 
2304
            verbose=True)
 
2305
 
 
2306
    def test_manual_page_example_3(self):
 
2307
        self.assert_command_from_args("--enable --all".split(),
 
2308
                                      command.Enable,
 
2309
                                      clients=[])
 
2310
 
 
2311
    def test_manual_page_example_4(self):
 
2312
        self.assert_commands_from_args(
 
2313
            ("--timeout=PT5M --interval=PT1M foo1.example.org"
 
2314
             " foo2.example.org").split(),
 
2315
            [command.SetTimeout, command.SetInterval],
 
2316
            clients=["foo1.example.org", "foo2.example.org"])
 
2317
 
 
2318
    def test_manual_page_example_5(self):
 
2319
        self.assert_command_from_args("--approve --all".split(),
 
2320
                                      command.Approve,
 
2321
                                      clients=[])
 
2322
 
 
2323
 
1730
2324
class TestCommand(unittest.TestCase):
1731
2325
    """Abstract class for tests of command classes"""
1732
2326