/mandos/release

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

« back to all changes in this revision

Viewing changes to mandos-ctl

  • Committer: Teddy Hogeborn
  • Date: 2019-03-10 02:35:22 UTC
  • mto: This revision was merged to the branch mainline in revision 382.
  • Revision ID: teddy@recompile.se-20190310023522-cvos2tw2si6t7if3
mandos-ctl: Minimize number of D-Bus calls

* mandos-ctl (Command.run): Now takes a {objpath: properties} dict,
                            followed by optional "bus" and "mandos"
                            arguments.  Use "bus" to connect to
                            clients when dispatching to method
                            run_on_one_client().  All callers changed.
  (IsEnabledCmd.run): New.
  (IsEnabledCmd.run_on_one_client): Remove.
  (ApproveCmd.run_on_one_client): Add busname to debug output.
  (DenyCmd.run_on_one_client): - '' -
  (main): In D-Bus debug output, change "name" to "busname".  Also,
          don't connect to clients, just use the object path as the
          key of the "clients" dict passed to Command.run().
  (TestCmd.clients): Changed to an {objpath: properties} dict.
  (TestCmd.one_client): - '' -
  (TestCmd.bus): New mock bus object having a get_object() method.

Show diffs side-by-side

added added

removed removed

Lines of Context:
276
276
# Abstract classes first
277
277
class Command(object):
278
278
    """Abstract class for commands"""
279
 
    def run(self, mandos, clients):
 
279
    def run(self, clients, bus=None, mandos=None):
280
280
        """Normal commands should implement run_on_one_client(), but
281
281
        commands which want to operate on all clients at the same time
282
282
        can override this run() method instead."""
283
283
        self.mandos = mandos
284
 
        for client, properties in clients.items():
 
284
        for clientpath, properties in clients.items():
 
285
            log.debug("D-Bus: Connect to: (busname=%r, path=%r)",
 
286
                      busname, str(clientpath))
 
287
            client = bus.get_object(busname, clientpath)
285
288
            self.run_on_one_client(client, properties)
286
289
 
287
290
class PrintCmd(Command):
293
296
                    "LastApprovalRequest", "ApprovalDelay",
294
297
                    "ApprovalDuration", "Checker", "ExtendedTimeout",
295
298
                    "Expires", "LastCheckerStatus")
296
 
    def run(self, mandos, clients):
 
299
    def run(self, clients, bus=None, mandos=None):
297
300
        print(self.output(clients.values()))
298
301
    def output(self, clients):
299
302
        raise NotImplementedError()
433
436
        return value
434
437
 
435
438
class IsEnabledCmd(Command):
436
 
    def run_on_one_client(self, client, properties):
 
439
    def run(self, clients, bus=None, mandos=None):
 
440
        client, properties = next(iter(clients.items()))
437
441
        if self.is_enabled(client, properties):
438
442
            sys.exit(0)
439
443
        sys.exit(1)
449
453
 
450
454
class ApproveCmd(Command):
451
455
    def run_on_one_client(self, client, properties):
452
 
        log.debug("D-Bus: %s:%s.Approve(True)",
 
456
        log.debug("D-Bus: %s:%s:%s.Approve(True)", busname,
453
457
                  client.__dbus_object_path__, client_interface)
454
458
        client.Approve(dbus.Boolean(True),
455
459
                       dbus_interface=client_interface)
456
460
 
457
461
class DenyCmd(Command):
458
462
    def run_on_one_client(self, client, properties):
459
 
        log.debug("D-Bus: %s:%s.Approve(False)",
 
463
        log.debug("D-Bus: %s:%s:%s.Approve(False)", busname,
460
464
                  client.__dbus_object_path__, client_interface)
461
465
        client.Approve(dbus.Boolean(False),
462
466
                       dbus_interface=client_interface)
723
727
 
724
728
    try:
725
729
        bus = dbus.SystemBus()
726
 
        log.debug("D-Bus: Connect to: (name=%r, path=%r)", busname,
 
730
        log.debug("D-Bus: Connect to: (busname=%r, path=%r)", busname,
727
731
                  server_path)
728
732
        mandos_dbus_objc = bus.get_object(busname, server_path)
729
733
    except dbus.exceptions.DBusException:
762
766
    clients = {}
763
767
 
764
768
    if not clientnames:
765
 
        clients = {(log.debug("D-Bus: Connect to: (name=%r, path=%r)",
766
 
                              busname, str(path)) and False) or
767
 
                   bus.get_object(busname, path): properties
768
 
                   for path, properties in mandos_clients.items()}
 
769
        clients = {objpath: properties
 
770
                   for objpath, properties in mandos_clients.items()}
769
771
    else:
770
772
        for name in clientnames:
771
 
            for path, client in mandos_clients.items():
772
 
                if client["Name"] == name:
773
 
                    log.debug("D-Bus: Connect to: (name=%r, path=%r)",
774
 
                              busname, str(path))
775
 
                    client_objc = bus.get_object(busname, path)
776
 
                    clients[client_objc] = client
 
773
            for objpath, properties in mandos_clients.items():
 
774
                if properties["Name"] == name:
 
775
                    clients[objpath] = properties
777
776
                    break
778
777
            else:
779
778
                log.critical("Client not found on server: %r", name)
782
781
    # Run all commands on clients
783
782
    commands = commands_from_options(options)
784
783
    for command in commands:
785
 
        command.run(mandos_serv, clients)
 
784
        command.run(clients, bus, mandos_serv)
786
785
 
787
786
 
788
787
class Test_milliseconds_to_string(unittest.TestCase):
837
836
        testcase = self
838
837
        class MockClient(object):
839
838
            def __init__(self, name, **attributes):
840
 
                self.__dbus_object_path__ = "objpath_{}".format(name)
 
839
                self.__dbus_object_path__ = "/clients/{}".format(name)
841
840
                self.attributes = attributes
842
841
                self.attributes["Name"] = name
843
842
                self.calls = []
905
904
            LastCheckerStatus=-2)
906
905
        self.clients =  collections.OrderedDict(
907
906
            [
908
 
                (self.client, self.client.attributes),
909
 
                (self.other_client, self.other_client.attributes),
 
907
                ("/clients/foo", self.client.attributes),
 
908
                ("/clients/barbar", self.other_client.attributes),
910
909
            ])
911
 
        self.one_client = {self.client: self.client.attributes}
 
910
        self.one_client = {"/clients/foo": self.client.attributes}
 
911
    @property
 
912
    def bus(self):
 
913
        class Bus(object):
 
914
            @staticmethod
 
915
            def get_object(client_bus_name, path):
 
916
                self.assertEqual(client_bus_name, busname)
 
917
                return {
 
918
                    "/clients/foo": self.client,
 
919
                    "/clients/barbar": self.other_client,
 
920
                }[path]
 
921
        return Bus()
912
922
 
913
923
class TestPrintTableCmd(TestCmd):
914
924
    def test_normal(self):
1004
1014
                            for client, properties in self.clients.items()))
1005
1015
    def test_is_enabled_run_exits_successfully(self):
1006
1016
        with self.assertRaises(SystemExit) as e:
1007
 
            IsEnabledCmd().run(None, self.one_client)
 
1017
            IsEnabledCmd().run(self.one_client)
1008
1018
        if e.exception.code is not None:
1009
1019
            self.assertEqual(e.exception.code, 0)
1010
1020
        else:
1012
1022
    def test_is_enabled_run_exits_with_failure(self):
1013
1023
        self.client.attributes["Enabled"] = dbus.Boolean(False)
1014
1024
        with self.assertRaises(SystemExit) as e:
1015
 
            IsEnabledCmd().run(None, self.one_client)
 
1025
            IsEnabledCmd().run(self.one_client)
1016
1026
        if isinstance(e.exception.code, int):
1017
1027
            self.assertNotEqual(e.exception.code, 0)
1018
1028
        else:
1027
1037
                self.calls.append(("RemoveClient", (dbus_path,)))
1028
1038
        mandos = MockMandos()
1029
1039
        super(TestRemoveCmd, self).setUp()
1030
 
        RemoveCmd().run(mandos, self.clients)
 
1040
        RemoveCmd().run(self.clients, self.bus, mandos)
1031
1041
        self.assertEqual(len(mandos.calls), 2)
1032
 
        for client in self.clients:
1033
 
            self.assertIn(("RemoveClient",
1034
 
                           (client.__dbus_object_path__,)),
 
1042
        for clientpath in self.clients:
 
1043
            self.assertIn(("RemoveClient", (clientpath,)),
1035
1044
                          mandos.calls)
1036
1045
 
1037
1046
class TestApproveCmd(TestCmd):
1038
1047
    def test_approve(self):
1039
 
        ApproveCmd().run(None, self.clients)
1040
 
        for client in self.clients:
 
1048
        ApproveCmd().run(self.clients, self.bus)
 
1049
        for clientpath in self.clients:
 
1050
            client = self.bus.get_object(busname, clientpath)
1041
1051
            self.assertIn(("Approve", (True, client_interface)),
1042
1052
                          client.calls)
1043
1053
 
1044
1054
class TestDenyCmd(TestCmd):
1045
1055
    def test_deny(self):
1046
 
        DenyCmd().run(None, self.clients)
1047
 
        for client in self.clients:
 
1056
        DenyCmd().run(self.clients, self.bus)
 
1057
        for clientpath in self.clients:
 
1058
            client = self.bus.get_object(busname, clientpath)
1048
1059
            self.assertIn(("Approve", (False, client_interface)),
1049
1060
                          client.calls)
1050
1061
 
1051
1062
class TestEnableCmd(TestCmd):
1052
1063
    def test_enable(self):
1053
 
        for client in self.clients:
 
1064
        for clientpath in self.clients:
 
1065
            client = self.bus.get_object(busname, clientpath)
1054
1066
            client.attributes["Enabled"] = False
1055
1067
 
1056
 
        EnableCmd().run(None, self.clients)
 
1068
        EnableCmd().run(self.clients, self.bus)
1057
1069
 
1058
 
        for client in self.clients:
 
1070
        for clientpath in self.clients:
 
1071
            client = self.bus.get_object(busname, clientpath)
1059
1072
            self.assertTrue(client.attributes["Enabled"])
1060
1073
 
1061
1074
class TestDisableCmd(TestCmd):
1062
1075
    def test_disable(self):
1063
 
        DisableCmd().run(None, self.clients)
1064
 
 
1065
 
        for client in self.clients:
 
1076
        DisableCmd().run(self.clients, self.bus)
 
1077
        for clientpath in self.clients:
 
1078
            client = self.bus.get_object(busname, clientpath)
1066
1079
            self.assertFalse(client.attributes["Enabled"])
1067
1080
 
1068
1081
class Unique(object):
1078
1091
                                self.values_to_set)
1079
1092
        for value_to_set, value_to_get in zip(self.values_to_set,
1080
1093
                                              values_to_get):
1081
 
            for client in self.clients:
 
1094
            for clientpath in self.clients:
 
1095
                client = self.bus.get_object(busname, clientpath)
1082
1096
                old_value = client.attributes[self.propname]
1083
1097
                self.assertNotIsInstance(old_value, Unique)
1084
1098
                client.attributes[self.propname] = Unique()
1085
1099
            self.run_command(value_to_set, self.clients)
1086
 
            for client in self.clients:
 
1100
            for clientpath in self.clients:
 
1101
                client = self.bus.get_object(busname, clientpath)
1087
1102
                value = client.attributes[self.propname]
1088
1103
                self.assertNotIsInstance(value, Unique)
1089
1104
                self.assertEqual(value, value_to_get)
1090
1105
    def run_command(self, value, clients):
1091
 
        self.command().run(None, clients)
 
1106
        self.command().run(clients, self.bus)
1092
1107
 
1093
1108
class TestBumpTimeoutCmd(TestPropertyCmd):
1094
1109
    command = BumpTimeoutCmd
1123
1138
            return
1124
1139
        return super(TestValueArgumentPropertyCmd, self).runTest()
1125
1140
    def run_command(self, value, clients):
1126
 
        self.command(value).run(None, clients)
 
1141
        self.command(value).run(clients, self.bus)
1127
1142
 
1128
1143
class TestSetCheckerCmd(TestValueArgumentPropertyCmd):
1129
1144
    command = SetCheckerCmd