/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: 2019-03-10 03:33:46 UTC
  • Revision ID: teddy@recompile.se-20190310033346-be20o4gcr3sjkr91
mandos-ctl: Refactor

* mandos-ctl (ValueArgumentMixIn): Remove.
  (PropertyValueCmd): New proper subclass of PropertyCmd; replaces
                      ValueArgumentMixIn.
  (MillisecondsValueArgumentMixIn): Remove.
  (MillisecondsPropertyValueArgumentCmd): New subclass of
            PropertyValueCmd; replaces MillisecondsValueArgumentMixIn.
  (TestValueArgumentPropertyCmd): Rename to "TestPropertyValueCmd".  All users changed.

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):
297
 
        print(self.output(clients))
 
299
    def run(self, clients, bus=None, mandos=None):
 
300
        print(self.output(clients.values()))
 
301
    def output(self, clients):
 
302
        raise NotImplementedError()
298
303
 
299
304
class PropertyCmd(Command):
300
305
    """Abstract class for Actions for setting one client property"""
303
308
        log.debug("D-Bus: %s:%s:%s.Set(%r, %r, %r)", busname,
304
309
                  client.__dbus_object_path__,
305
310
                  dbus.PROPERTIES_IFACE, client_interface,
306
 
                  self.property, self.value_to_set
 
311
                  self.propname, self.value_to_set
307
312
                  if not isinstance(self.value_to_set, dbus.Boolean)
308
313
                  else bool(self.value_to_set))
309
 
        client.Set(client_interface, self.property, self.value_to_set,
 
314
        client.Set(client_interface, self.propname, self.value_to_set,
310
315
                   dbus_interface=dbus.PROPERTIES_IFACE)
 
316
    @property
 
317
    def propname(self):
 
318
        raise NotImplementedError()
311
319
 
312
 
class ValueArgumentMixIn(object):
313
 
    """Mixin class for commands taking a value as argument"""
 
320
class PropertyValueCmd(PropertyCmd):
 
321
    """Abstract class for PropertyCmd recieving a value as argument"""
314
322
    def __init__(self, value):
315
323
        self.value_to_set = value
316
324
 
317
 
class MillisecondsValueArgumentMixIn(ValueArgumentMixIn):
318
 
    """Mixin class for commands taking a value argument as
319
 
    milliseconds."""
 
325
class MillisecondsPropertyValueArgumentCmd(PropertyValueCmd):
 
326
    """Abstract class for PropertyValueCmd taking a value argument as
 
327
a datetime.timedelta() but should store it as milliseconds."""
320
328
    @property
321
329
    def value_to_set(self):
322
330
        return self._vts
323
331
    @value_to_set.setter
324
332
    def value_to_set(self, value):
325
 
        """When setting, convert value to a datetime.timedelta"""
 
333
        """When setting, convert value from a datetime.timedelta"""
326
334
        self._vts = int(round(value.total_seconds() * 1000))
327
335
 
328
336
# Actual (non-abstract) command classes
336
344
        keywords = default_keywords
337
345
        if self.verbose:
338
346
            keywords = self.all_keywords
339
 
        return str(self.TableOfClients(clients.values(), keywords))
 
347
        return str(self.TableOfClients(clients, keywords))
340
348
 
341
349
    class TableOfClients(object):
342
350
        tableheaders = {
428
436
        return value
429
437
 
430
438
class IsEnabledCmd(Command):
431
 
    def run_on_one_client(self, client, properties):
 
439
    def run(self, clients, bus=None, mandos=None):
 
440
        client, properties = next(iter(clients.items()))
432
441
        if self.is_enabled(client, properties):
433
442
            sys.exit(0)
434
443
        sys.exit(1)
435
444
    def is_enabled(self, client, properties):
436
 
        return bool(properties["Enabled"])
 
445
        return properties["Enabled"]
437
446
 
438
447
class RemoveCmd(Command):
439
448
    def run_on_one_client(self, client, properties):
444
453
 
445
454
class ApproveCmd(Command):
446
455
    def run_on_one_client(self, client, properties):
447
 
        log.debug("D-Bus: %s:%s.Approve(True)",
 
456
        log.debug("D-Bus: %s:%s:%s.Approve(True)", busname,
448
457
                  client.__dbus_object_path__, client_interface)
449
458
        client.Approve(dbus.Boolean(True),
450
459
                       dbus_interface=client_interface)
451
460
 
452
461
class DenyCmd(Command):
453
462
    def run_on_one_client(self, client, properties):
454
 
        log.debug("D-Bus: %s:%s.Approve(False)",
 
463
        log.debug("D-Bus: %s:%s:%s.Approve(False)", busname,
455
464
                  client.__dbus_object_path__, client_interface)
456
465
        client.Approve(dbus.Boolean(False),
457
466
                       dbus_interface=client_interface)
458
467
 
459
468
class EnableCmd(PropertyCmd):
460
 
    property = "Enabled"
 
469
    propname = "Enabled"
461
470
    value_to_set = dbus.Boolean(True)
462
471
 
463
472
class DisableCmd(PropertyCmd):
464
 
    property = "Enabled"
 
473
    propname = "Enabled"
465
474
    value_to_set = dbus.Boolean(False)
466
475
 
467
476
class BumpTimeoutCmd(PropertyCmd):
468
 
    property = "LastCheckedOK"
 
477
    propname = "LastCheckedOK"
469
478
    value_to_set = ""
470
479
 
471
480
class StartCheckerCmd(PropertyCmd):
472
 
    property = "CheckerRunning"
 
481
    propname = "CheckerRunning"
473
482
    value_to_set = dbus.Boolean(True)
474
483
 
475
484
class StopCheckerCmd(PropertyCmd):
476
 
    property = "CheckerRunning"
 
485
    propname = "CheckerRunning"
477
486
    value_to_set = dbus.Boolean(False)
478
487
 
479
488
class ApproveByDefaultCmd(PropertyCmd):
480
 
    property = "ApprovedByDefault"
 
489
    propname = "ApprovedByDefault"
481
490
    value_to_set = dbus.Boolean(True)
482
491
 
483
492
class DenyByDefaultCmd(PropertyCmd):
484
 
    property = "ApprovedByDefault"
 
493
    propname = "ApprovedByDefault"
485
494
    value_to_set = dbus.Boolean(False)
486
495
 
487
 
class SetCheckerCmd(PropertyCmd, ValueArgumentMixIn):
488
 
    property = "Checker"
489
 
 
490
 
class SetHostCmd(PropertyCmd, ValueArgumentMixIn):
491
 
    property = "Host"
492
 
 
493
 
class SetSecretCmd(PropertyCmd, ValueArgumentMixIn):
 
496
class SetCheckerCmd(PropertyValueCmd):
 
497
    propname = "Checker"
 
498
 
 
499
class SetHostCmd(PropertyValueCmd):
 
500
    propname = "Host"
 
501
 
 
502
class SetSecretCmd(PropertyValueCmd):
 
503
    propname = "Secret"
494
504
    @property
495
505
    def value_to_set(self):
496
506
        return self._vts
499
509
        """When setting, read data from supplied file object"""
500
510
        self._vts = value.read()
501
511
        value.close()
502
 
    property = "Secret"
503
 
 
504
 
class SetTimeoutCmd(PropertyCmd, MillisecondsValueArgumentMixIn):
505
 
    property = "Timeout"
506
 
 
507
 
class SetExtendedTimeoutCmd(PropertyCmd,
508
 
                            MillisecondsValueArgumentMixIn):
509
 
    property = "ExtendedTimeout"
510
 
 
511
 
class SetIntervalCmd(PropertyCmd, MillisecondsValueArgumentMixIn):
512
 
    property = "Interval"
513
 
 
514
 
class SetApprovalDelayCmd(PropertyCmd,
515
 
                          MillisecondsValueArgumentMixIn):
516
 
    property = "ApprovalDelay"
517
 
 
518
 
class SetApprovalDurationCmd(PropertyCmd,
519
 
                             MillisecondsValueArgumentMixIn):
520
 
    property = "ApprovalDuration"
 
512
 
 
513
class SetTimeoutCmd(MillisecondsPropertyValueArgumentCmd):
 
514
    propname = "Timeout"
 
515
 
 
516
class SetExtendedTimeoutCmd(MillisecondsPropertyValueArgumentCmd):
 
517
    propname = "ExtendedTimeout"
 
518
 
 
519
class SetIntervalCmd(MillisecondsPropertyValueArgumentCmd):
 
520
    propname = "Interval"
 
521
 
 
522
class SetApprovalDelayCmd(MillisecondsPropertyValueArgumentCmd):
 
523
    propname = "ApprovalDelay"
 
524
 
 
525
class SetApprovalDurationCmd(MillisecondsPropertyValueArgumentCmd):
 
526
    propname = "ApprovalDuration"
521
527
 
522
528
def add_command_line_options(parser):
523
529
    parser.add_argument("--version", action="version",
611
617
    if options.is_enabled:
612
618
        commands.append(IsEnabledCmd())
613
619
 
614
 
    if options.remove:
615
 
        commands.append(RemoveCmd())
616
 
 
617
620
    if options.checker is not None:
618
621
        commands.append(SetCheckerCmd(options.checker))
619
622
 
652
655
    if options.deny:
653
656
        commands.append(DenyCmd())
654
657
 
 
658
    if options.remove:
 
659
        commands.append(RemoveCmd())
 
660
 
655
661
    # If no command option has been given, show table of clients,
656
662
    # optionally verbosely
657
663
    if not commands:
695
701
        parser.error("--all requires an action.")
696
702
    if options.is_enabled and len(options.client) > 1:
697
703
        parser.error("--is-enabled requires exactly one client")
 
704
    if options.remove:
 
705
        options.remove = False
 
706
        if has_actions(options) and not options.deny:
 
707
            parser.error("--remove can only be combined with --deny")
 
708
        options.remove = True
698
709
 
699
710
 
700
711
def main():
713
724
 
714
725
    try:
715
726
        bus = dbus.SystemBus()
716
 
        log.debug("D-Bus: Connect to: (name=%r, path=%r)", busname,
 
727
        log.debug("D-Bus: Connect to: (busname=%r, path=%r)", busname,
717
728
                  server_path)
718
729
        mandos_dbus_objc = bus.get_object(busname, server_path)
719
730
    except dbus.exceptions.DBusException:
752
763
    clients = {}
753
764
 
754
765
    if not clientnames:
755
 
        clients = {bus.get_object(busname, path): properties
756
 
                   for path, properties in mandos_clients.items()}
 
766
        clients = {objpath: properties
 
767
                   for objpath, properties in mandos_clients.items()}
757
768
    else:
758
769
        for name in clientnames:
759
 
            for path, client in mandos_clients.items():
760
 
                if client["Name"] == name:
761
 
                    client_objc = bus.get_object(busname, path)
762
 
                    clients[client_objc] = client
 
770
            for objpath, properties in mandos_clients.items():
 
771
                if properties["Name"] == name:
 
772
                    clients[objpath] = properties
763
773
                    break
764
774
            else:
765
775
                log.critical("Client not found on server: %r", name)
768
778
    # Run all commands on clients
769
779
    commands = commands_from_options(options)
770
780
    for command in commands:
771
 
        command.run(mandos_serv, clients)
 
781
        command.run(clients, bus, mandos_serv)
772
782
 
773
783
 
774
784
class Test_milliseconds_to_string(unittest.TestCase):
823
833
        testcase = self
824
834
        class MockClient(object):
825
835
            def __init__(self, name, **attributes):
826
 
                self.__dbus_object_path__ = "objpath_{}".format(name)
 
836
                self.__dbus_object_path__ = "/clients/{}".format(name)
827
837
                self.attributes = attributes
828
838
                self.attributes["Name"] = name
829
839
                self.calls = []
830
 
            def Set(self, interface, property, value, dbus_interface):
831
 
                testcase.assertEqual(interface, client_interface)
832
 
                testcase.assertEqual(dbus_interface,
833
 
                                     dbus.PROPERTIES_IFACE)
834
 
                self.attributes[property] = value
835
 
            def Get(self, interface, property, dbus_interface):
836
 
                testcase.assertEqual(interface, client_interface)
837
 
                testcase.assertEqual(dbus_interface,
838
 
                                     dbus.PROPERTIES_IFACE)
839
 
                return self.attributes[property]
 
840
            def Set(self, interface, propname, value, dbus_interface):
 
841
                testcase.assertEqual(interface, client_interface)
 
842
                testcase.assertEqual(dbus_interface,
 
843
                                     dbus.PROPERTIES_IFACE)
 
844
                self.attributes[propname] = value
 
845
            def Get(self, interface, propname, dbus_interface):
 
846
                testcase.assertEqual(interface, client_interface)
 
847
                testcase.assertEqual(dbus_interface,
 
848
                                     dbus.PROPERTIES_IFACE)
 
849
                return self.attributes[propname]
840
850
            def Approve(self, approve, dbus_interface):
841
851
                testcase.assertEqual(dbus_interface, client_interface)
842
852
                self.calls.append(("Approve", (approve,
891
901
            LastCheckerStatus=-2)
892
902
        self.clients =  collections.OrderedDict(
893
903
            [
894
 
                (self.client, self.client.attributes),
895
 
                (self.other_client, self.other_client.attributes),
 
904
                ("/clients/foo", self.client.attributes),
 
905
                ("/clients/barbar", self.other_client.attributes),
896
906
            ])
897
 
        self.one_client = {self.client: self.client.attributes}
 
907
        self.one_client = {"/clients/foo": self.client.attributes}
 
908
    @property
 
909
    def bus(self):
 
910
        class Bus(object):
 
911
            @staticmethod
 
912
            def get_object(client_bus_name, path):
 
913
                self.assertEqual(client_bus_name, busname)
 
914
                return {
 
915
                    "/clients/foo": self.client,
 
916
                    "/clients/barbar": self.other_client,
 
917
                }[path]
 
918
        return Bus()
898
919
 
899
920
class TestPrintTableCmd(TestCmd):
900
921
    def test_normal(self):
901
 
        output = PrintTableCmd().output(self.clients)
 
922
        output = PrintTableCmd().output(self.clients.values())
902
923
        expected_output = """
903
924
Name   Enabled Timeout  Last Successful Check
904
925
foo    Yes     00:05:00 2019-02-03T00:00:00  
906
927
"""[1:-1]
907
928
        self.assertEqual(output, expected_output)
908
929
    def test_verbose(self):
909
 
        output = PrintTableCmd(verbose=True).output(self.clients)
 
930
        output = PrintTableCmd(verbose=True).output(
 
931
            self.clients.values())
910
932
        expected_output = """
911
933
Name   Enabled Timeout  Last Successful Check Created             Interval Host            Key ID                                                           Fingerprint                              Check Is Running Last Enabled        Approval Is Pending Approved By Default Last Approval Request Approval Delay Approval Duration Checker              Extended Timeout Expires             Last Checker Status
912
934
foo    Yes     00:05:00 2019-02-03T00:00:00   2019-01-02T00:00:00 00:02:00 foo.example.org 92ed150794387c03ce684574b1139a6594a34f895daaaf09fd8ea90a27cddb12 778827225BA7DE539C5A7CFA59CFF7CDBD9A5920 No               2019-01-03T00:00:00 No                  Yes                                       00:00:00       00:00:01          fping -q -- %(host)s 00:15:00         2019-02-04T00:00:00 0                  
914
936
"""[1:-1]
915
937
        self.assertEqual(output, expected_output)
916
938
    def test_one_client(self):
917
 
        output = PrintTableCmd().output(self.one_client)
 
939
        output = PrintTableCmd().output(self.one_client.values())
918
940
        expected_output = """
919
941
Name Enabled Timeout  Last Successful Check
920
942
foo  Yes     00:05:00 2019-02-03T00:00:00  
989
1011
                            for client, properties in self.clients.items()))
990
1012
    def test_is_enabled_run_exits_successfully(self):
991
1013
        with self.assertRaises(SystemExit) as e:
992
 
            IsEnabledCmd().run(None, self.one_client)
 
1014
            IsEnabledCmd().run(self.one_client)
993
1015
        if e.exception.code is not None:
994
1016
            self.assertEqual(e.exception.code, 0)
995
1017
        else:
997
1019
    def test_is_enabled_run_exits_with_failure(self):
998
1020
        self.client.attributes["Enabled"] = dbus.Boolean(False)
999
1021
        with self.assertRaises(SystemExit) as e:
1000
 
            IsEnabledCmd().run(None, self.one_client)
 
1022
            IsEnabledCmd().run(self.one_client)
1001
1023
        if isinstance(e.exception.code, int):
1002
1024
            self.assertNotEqual(e.exception.code, 0)
1003
1025
        else:
1012
1034
                self.calls.append(("RemoveClient", (dbus_path,)))
1013
1035
        mandos = MockMandos()
1014
1036
        super(TestRemoveCmd, self).setUp()
1015
 
        RemoveCmd().run(mandos, self.clients)
 
1037
        RemoveCmd().run(self.clients, self.bus, mandos)
1016
1038
        self.assertEqual(len(mandos.calls), 2)
1017
 
        for client in self.clients:
1018
 
            self.assertIn(("RemoveClient",
1019
 
                           (client.__dbus_object_path__,)),
 
1039
        for clientpath in self.clients:
 
1040
            self.assertIn(("RemoveClient", (clientpath,)),
1020
1041
                          mandos.calls)
1021
1042
 
1022
1043
class TestApproveCmd(TestCmd):
1023
1044
    def test_approve(self):
1024
 
        ApproveCmd().run(None, self.clients)
1025
 
        for client in self.clients:
 
1045
        ApproveCmd().run(self.clients, self.bus)
 
1046
        for clientpath in self.clients:
 
1047
            client = self.bus.get_object(busname, clientpath)
1026
1048
            self.assertIn(("Approve", (True, client_interface)),
1027
1049
                          client.calls)
1028
1050
 
1029
1051
class TestDenyCmd(TestCmd):
1030
1052
    def test_deny(self):
1031
 
        DenyCmd().run(None, self.clients)
1032
 
        for client in self.clients:
 
1053
        DenyCmd().run(self.clients, self.bus)
 
1054
        for clientpath in self.clients:
 
1055
            client = self.bus.get_object(busname, clientpath)
1033
1056
            self.assertIn(("Approve", (False, client_interface)),
1034
1057
                          client.calls)
1035
1058
 
1036
1059
class TestEnableCmd(TestCmd):
1037
1060
    def test_enable(self):
1038
 
        for client in self.clients:
 
1061
        for clientpath in self.clients:
 
1062
            client = self.bus.get_object(busname, clientpath)
1039
1063
            client.attributes["Enabled"] = False
1040
1064
 
1041
 
        EnableCmd().run(None, self.clients)
 
1065
        EnableCmd().run(self.clients, self.bus)
1042
1066
 
1043
 
        for client in self.clients:
 
1067
        for clientpath in self.clients:
 
1068
            client = self.bus.get_object(busname, clientpath)
1044
1069
            self.assertTrue(client.attributes["Enabled"])
1045
1070
 
1046
1071
class TestDisableCmd(TestCmd):
1047
1072
    def test_disable(self):
1048
 
        DisableCmd().run(None, self.clients)
1049
 
 
1050
 
        for client in self.clients:
 
1073
        DisableCmd().run(self.clients, self.bus)
 
1074
        for clientpath in self.clients:
 
1075
            client = self.bus.get_object(busname, clientpath)
1051
1076
            self.assertFalse(client.attributes["Enabled"])
1052
1077
 
1053
1078
class Unique(object):
1063
1088
                                self.values_to_set)
1064
1089
        for value_to_set, value_to_get in zip(self.values_to_set,
1065
1090
                                              values_to_get):
1066
 
            for client in self.clients:
1067
 
                old_value = client.attributes[self.property]
 
1091
            for clientpath in self.clients:
 
1092
                client = self.bus.get_object(busname, clientpath)
 
1093
                old_value = client.attributes[self.propname]
1068
1094
                self.assertNotIsInstance(old_value, Unique)
1069
 
                client.attributes[self.property] = Unique()
 
1095
                client.attributes[self.propname] = Unique()
1070
1096
            self.run_command(value_to_set, self.clients)
1071
 
            for client in self.clients:
1072
 
                value = client.attributes[self.property]
 
1097
            for clientpath in self.clients:
 
1098
                client = self.bus.get_object(busname, clientpath)
 
1099
                value = client.attributes[self.propname]
1073
1100
                self.assertNotIsInstance(value, Unique)
1074
1101
                self.assertEqual(value, value_to_get)
1075
1102
    def run_command(self, value, clients):
1076
 
        self.command().run(None, clients)
 
1103
        self.command().run(clients, self.bus)
1077
1104
 
1078
1105
class TestBumpTimeoutCmd(TestPropertyCmd):
1079
1106
    command = BumpTimeoutCmd
1080
 
    property = "LastCheckedOK"
 
1107
    propname = "LastCheckedOK"
1081
1108
    values_to_set = [""]
1082
1109
 
1083
1110
class TestStartCheckerCmd(TestPropertyCmd):
1084
1111
    command = StartCheckerCmd
1085
 
    property = "CheckerRunning"
 
1112
    propname = "CheckerRunning"
1086
1113
    values_to_set = [dbus.Boolean(True)]
1087
1114
 
1088
1115
class TestStopCheckerCmd(TestPropertyCmd):
1089
1116
    command = StopCheckerCmd
1090
 
    property = "CheckerRunning"
 
1117
    propname = "CheckerRunning"
1091
1118
    values_to_set = [dbus.Boolean(False)]
1092
1119
 
1093
1120
class TestApproveByDefaultCmd(TestPropertyCmd):
1094
1121
    command = ApproveByDefaultCmd
1095
 
    property = "ApprovedByDefault"
 
1122
    propname = "ApprovedByDefault"
1096
1123
    values_to_set = [dbus.Boolean(True)]
1097
1124
 
1098
1125
class TestDenyByDefaultCmd(TestPropertyCmd):
1099
1126
    command = DenyByDefaultCmd
1100
 
    property = "ApprovedByDefault"
 
1127
    propname = "ApprovedByDefault"
1101
1128
    values_to_set = [dbus.Boolean(False)]
1102
1129
 
1103
 
class TestValueArgumentPropertyCmd(TestPropertyCmd):
1104
 
    """Abstract class for tests of PropertyCmd classes using the
1105
 
ValueArgumentMixIn"""
 
1130
class TestPropertyValueCmd(TestPropertyCmd):
 
1131
    """Abstract class for tests of PropertyValueCmd classes"""
1106
1132
    def runTest(self):
1107
 
        if type(self) is TestValueArgumentPropertyCmd:
 
1133
        if type(self) is TestPropertyValueCmd:
1108
1134
            return
1109
 
        return super(TestValueArgumentPropertyCmd, self).runTest()
 
1135
        return super(TestPropertyValueCmd, self).runTest()
1110
1136
    def run_command(self, value, clients):
1111
 
        self.command(value).run(None, clients)
 
1137
        self.command(value).run(clients, self.bus)
1112
1138
 
1113
 
class TestSetCheckerCmd(TestValueArgumentPropertyCmd):
 
1139
class TestSetCheckerCmd(TestPropertyValueCmd):
1114
1140
    command = SetCheckerCmd
1115
 
    property = "Checker"
 
1141
    propname = "Checker"
1116
1142
    values_to_set = ["", ":", "fping -q -- %s"]
1117
1143
 
1118
 
class TestSetHostCmd(TestValueArgumentPropertyCmd):
 
1144
class TestSetHostCmd(TestPropertyValueCmd):
1119
1145
    command = SetHostCmd
1120
 
    property = "Host"
 
1146
    propname = "Host"
1121
1147
    values_to_set = ["192.0.2.3", "foo.example.org"]
1122
1148
 
1123
 
class TestSetSecretCmd(TestValueArgumentPropertyCmd):
 
1149
class TestSetSecretCmd(TestPropertyValueCmd):
1124
1150
    command = SetSecretCmd
1125
 
    property = "Secret"
 
1151
    propname = "Secret"
1126
1152
    values_to_set = [io.BytesIO(b""),
1127
1153
                     io.BytesIO(b"secret\0xyzzy\nbar")]
1128
1154
    values_to_get = [b"", b"secret\0xyzzy\nbar"]
1129
1155
 
1130
 
class TestSetTimeoutCmd(TestValueArgumentPropertyCmd):
 
1156
class TestSetTimeoutCmd(TestPropertyValueCmd):
1131
1157
    command = SetTimeoutCmd
1132
 
    property = "Timeout"
 
1158
    propname = "Timeout"
1133
1159
    values_to_set = [datetime.timedelta(),
1134
1160
                     datetime.timedelta(minutes=5),
1135
1161
                     datetime.timedelta(seconds=1),
1137
1163
                     datetime.timedelta(weeks=52)]
1138
1164
    values_to_get = [0, 300000, 1000, 604800000, 31449600000]
1139
1165
 
1140
 
class TestSetExtendedTimeoutCmd(TestValueArgumentPropertyCmd):
 
1166
class TestSetExtendedTimeoutCmd(TestPropertyValueCmd):
1141
1167
    command = SetExtendedTimeoutCmd
1142
 
    property = "ExtendedTimeout"
 
1168
    propname = "ExtendedTimeout"
1143
1169
    values_to_set = [datetime.timedelta(),
1144
1170
                     datetime.timedelta(minutes=5),
1145
1171
                     datetime.timedelta(seconds=1),
1147
1173
                     datetime.timedelta(weeks=52)]
1148
1174
    values_to_get = [0, 300000, 1000, 604800000, 31449600000]
1149
1175
 
1150
 
class TestSetIntervalCmd(TestValueArgumentPropertyCmd):
 
1176
class TestSetIntervalCmd(TestPropertyValueCmd):
1151
1177
    command = SetIntervalCmd
1152
 
    property = "Interval"
 
1178
    propname = "Interval"
1153
1179
    values_to_set = [datetime.timedelta(),
1154
1180
                     datetime.timedelta(minutes=5),
1155
1181
                     datetime.timedelta(seconds=1),
1157
1183
                     datetime.timedelta(weeks=52)]
1158
1184
    values_to_get = [0, 300000, 1000, 604800000, 31449600000]
1159
1185
 
1160
 
class TestSetApprovalDelayCmd(TestValueArgumentPropertyCmd):
 
1186
class TestSetApprovalDelayCmd(TestPropertyValueCmd):
1161
1187
    command = SetApprovalDelayCmd
1162
 
    property = "ApprovalDelay"
 
1188
    propname = "ApprovalDelay"
1163
1189
    values_to_set = [datetime.timedelta(),
1164
1190
                     datetime.timedelta(minutes=5),
1165
1191
                     datetime.timedelta(seconds=1),
1167
1193
                     datetime.timedelta(weeks=52)]
1168
1194
    values_to_get = [0, 300000, 1000, 604800000, 31449600000]
1169
1195
 
1170
 
class TestSetApprovalDurationCmd(TestValueArgumentPropertyCmd):
 
1196
class TestSetApprovalDurationCmd(TestPropertyValueCmd):
1171
1197
    command = SetApprovalDurationCmd
1172
 
    property = "ApprovalDuration"
 
1198
    propname = "ApprovalDuration"
1173
1199
    values_to_set = [datetime.timedelta(),
1174
1200
                     datetime.timedelta(minutes=5),
1175
1201
                     datetime.timedelta(seconds=1),
1355
1381
    def test_is_enabled_short(self):
1356
1382
        self.assert_command_from_args(["-V", "foo"], IsEnabledCmd)
1357
1383
 
 
1384
    def test_deny_before_remove(self):
 
1385
        options = self.parser.parse_args(["--deny", "--remove", "foo"])
 
1386
        check_option_syntax(self.parser, options)
 
1387
        commands = commands_from_options(options)
 
1388
        self.assertEqual(len(commands), 2)
 
1389
        self.assertIsInstance(commands[0], DenyCmd)
 
1390
        self.assertIsInstance(commands[1], RemoveCmd)
 
1391
 
 
1392
    def test_deny_before_remove_reversed(self):
 
1393
        options = self.parser.parse_args(["--remove", "--deny", "--all"])
 
1394
        check_option_syntax(self.parser, options)
 
1395
        commands = commands_from_options(options)
 
1396
        self.assertEqual(len(commands), 2)
 
1397
        self.assertIsInstance(commands[0], DenyCmd)
 
1398
        self.assertIsInstance(commands[1], RemoveCmd)
 
1399
 
1358
1400
 
1359
1401
class Test_check_option_syntax(unittest.TestCase):
1360
1402
    # This mostly corresponds to the definition from has_actions() in
1475
1517
        with self.assertParseError():
1476
1518
            self.check_option_syntax(options)
1477
1519
 
 
1520
    def test_remove_can_only_be_combined_with_action_deny(self):
 
1521
        for action, value in self.actions.items():
 
1522
            if action in {"remove", "deny"}:
 
1523
                continue
 
1524
            options = self.parser.parse_args()
 
1525
            setattr(options, action, value)
 
1526
            options.all = True
 
1527
            options.remove = True
 
1528
            with self.assertParseError():
 
1529
                self.check_option_syntax(options)
 
1530
 
1478
1531
 
1479
1532
 
1480
1533
def should_only_run_tests():