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)
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()
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)
318
raise NotImplementedError()
312
320
class ValueArgumentMixIn(object):
313
321
"""Mixin class for commands taking a value as argument"""
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):
435
444
def is_enabled(self, client, properties):
436
return bool(properties["Enabled"])
445
return properties["Enabled"]
438
447
class RemoveCmd(Command):
439
448
def run_on_one_client(self, client, properties):
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)
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)
459
468
class EnableCmd(PropertyCmd):
461
470
value_to_set = dbus.Boolean(True)
463
472
class DisableCmd(PropertyCmd):
465
474
value_to_set = dbus.Boolean(False)
467
476
class BumpTimeoutCmd(PropertyCmd):
468
property = "LastCheckedOK"
477
propname = "LastCheckedOK"
469
478
value_to_set = ""
471
480
class StartCheckerCmd(PropertyCmd):
472
property = "CheckerRunning"
481
propname = "CheckerRunning"
473
482
value_to_set = dbus.Boolean(True)
475
484
class StopCheckerCmd(PropertyCmd):
476
property = "CheckerRunning"
485
propname = "CheckerRunning"
477
486
value_to_set = dbus.Boolean(False)
479
488
class ApproveByDefaultCmd(PropertyCmd):
480
property = "ApprovedByDefault"
489
propname = "ApprovedByDefault"
481
490
value_to_set = dbus.Boolean(True)
483
492
class DenyByDefaultCmd(PropertyCmd):
484
property = "ApprovedByDefault"
493
propname = "ApprovedByDefault"
485
494
value_to_set = dbus.Boolean(False)
487
496
class SetCheckerCmd(PropertyCmd, ValueArgumentMixIn):
490
499
class SetHostCmd(PropertyCmd, ValueArgumentMixIn):
493
502
class SetSecretCmd(PropertyCmd, ValueArgumentMixIn):
495
505
def value_to_set(self):
499
509
"""When setting, read data from supplied file object"""
500
510
self._vts = value.read()
504
513
class SetTimeoutCmd(PropertyCmd, MillisecondsValueArgumentMixIn):
507
516
class SetExtendedTimeoutCmd(PropertyCmd,
508
517
MillisecondsValueArgumentMixIn):
509
property = "ExtendedTimeout"
518
propname = "ExtendedTimeout"
511
520
class SetIntervalCmd(PropertyCmd, MillisecondsValueArgumentMixIn):
512
property = "Interval"
521
propname = "Interval"
514
523
class SetApprovalDelayCmd(PropertyCmd,
515
524
MillisecondsValueArgumentMixIn):
516
property = "ApprovalDelay"
525
propname = "ApprovalDelay"
518
527
class SetApprovalDurationCmd(PropertyCmd,
519
528
MillisecondsValueArgumentMixIn):
520
property = "ApprovalDuration"
529
propname = "ApprovalDuration"
522
531
def add_command_line_options(parser):
523
532
parser.add_argument("--version", action="version",
754
768
if not clientnames:
755
clients = {bus.get_object(busname, path): properties
756
for path, properties in mandos_clients.items()}
769
clients = {objpath: properties
770
for objpath, properties in mandos_clients.items()}
758
772
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
773
for objpath, properties in mandos_clients.items():
774
if properties["Name"] == name:
775
clients[objpath] = properties
765
778
log.critical("Client not found on server: %r", name)
824
837
class MockClient(object):
825
838
def __init__(self, name, **attributes):
826
self.__dbus_object_path__ = "objpath_{}".format(name)
839
self.__dbus_object_path__ = "/clients/{}".format(name)
827
840
self.attributes = attributes
828
841
self.attributes["Name"] = name
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]
843
def Set(self, interface, propname, value, dbus_interface):
844
testcase.assertEqual(interface, client_interface)
845
testcase.assertEqual(dbus_interface,
846
dbus.PROPERTIES_IFACE)
847
self.attributes[propname] = value
848
def Get(self, interface, propname, dbus_interface):
849
testcase.assertEqual(interface, client_interface)
850
testcase.assertEqual(dbus_interface,
851
dbus.PROPERTIES_IFACE)
852
return self.attributes[propname]
840
853
def Approve(self, approve, dbus_interface):
841
854
testcase.assertEqual(dbus_interface, client_interface)
842
855
self.calls.append(("Approve", (approve,
891
904
LastCheckerStatus=-2)
892
905
self.clients = collections.OrderedDict(
894
(self.client, self.client.attributes),
895
(self.other_client, self.other_client.attributes),
907
("/clients/foo", self.client.attributes),
908
("/clients/barbar", self.other_client.attributes),
897
self.one_client = {self.client: self.client.attributes}
910
self.one_client = {"/clients/foo": self.client.attributes}
915
def get_object(client_bus_name, path):
916
self.assertEqual(client_bus_name, busname)
918
"/clients/foo": self.client,
919
"/clients/barbar": self.other_client,
899
923
class TestPrintTableCmd(TestCmd):
900
924
def test_normal(self):
901
output = PrintTableCmd().output(self.clients)
925
output = PrintTableCmd().output(self.clients.values())
902
926
expected_output = """
903
927
Name Enabled Timeout Last Successful Check
904
928
foo Yes 00:05:00 2019-02-03T00:00:00
989
1014
for client, properties in self.clients.items()))
990
1015
def test_is_enabled_run_exits_successfully(self):
991
1016
with self.assertRaises(SystemExit) as e:
992
IsEnabledCmd().run(None, self.one_client)
1017
IsEnabledCmd().run(self.one_client)
993
1018
if e.exception.code is not None:
994
1019
self.assertEqual(e.exception.code, 0)
997
1022
def test_is_enabled_run_exits_with_failure(self):
998
1023
self.client.attributes["Enabled"] = dbus.Boolean(False)
999
1024
with self.assertRaises(SystemExit) as e:
1000
IsEnabledCmd().run(None, self.one_client)
1025
IsEnabledCmd().run(self.one_client)
1001
1026
if isinstance(e.exception.code, int):
1002
1027
self.assertNotEqual(e.exception.code, 0)
1012
1037
self.calls.append(("RemoveClient", (dbus_path,)))
1013
1038
mandos = MockMandos()
1014
1039
super(TestRemoveCmd, self).setUp()
1015
RemoveCmd().run(mandos, self.clients)
1040
RemoveCmd().run(self.clients, self.bus, mandos)
1016
1041
self.assertEqual(len(mandos.calls), 2)
1017
for client in self.clients:
1018
self.assertIn(("RemoveClient",
1019
(client.__dbus_object_path__,)),
1042
for clientpath in self.clients:
1043
self.assertIn(("RemoveClient", (clientpath,)),
1022
1046
class TestApproveCmd(TestCmd):
1023
1047
def test_approve(self):
1024
ApproveCmd().run(None, self.clients)
1025
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)
1026
1051
self.assertIn(("Approve", (True, client_interface)),
1029
1054
class TestDenyCmd(TestCmd):
1030
1055
def test_deny(self):
1031
DenyCmd().run(None, self.clients)
1032
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)
1033
1059
self.assertIn(("Approve", (False, client_interface)),
1036
1062
class TestEnableCmd(TestCmd):
1037
1063
def test_enable(self):
1038
for client in self.clients:
1064
for clientpath in self.clients:
1065
client = self.bus.get_object(busname, clientpath)
1039
1066
client.attributes["Enabled"] = False
1041
EnableCmd().run(None, self.clients)
1068
EnableCmd().run(self.clients, self.bus)
1043
for client in self.clients:
1070
for clientpath in self.clients:
1071
client = self.bus.get_object(busname, clientpath)
1044
1072
self.assertTrue(client.attributes["Enabled"])
1046
1074
class TestDisableCmd(TestCmd):
1047
1075
def test_disable(self):
1048
DisableCmd().run(None, self.clients)
1050
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)
1051
1079
self.assertFalse(client.attributes["Enabled"])
1053
1081
class Unique(object):
1063
1091
self.values_to_set)
1064
1092
for value_to_set, value_to_get in zip(self.values_to_set,
1065
1093
values_to_get):
1066
for client in self.clients:
1067
old_value = client.attributes[self.property]
1094
for clientpath in self.clients:
1095
client = self.bus.get_object(busname, clientpath)
1096
old_value = client.attributes[self.propname]
1068
1097
self.assertNotIsInstance(old_value, Unique)
1069
client.attributes[self.property] = Unique()
1098
client.attributes[self.propname] = Unique()
1070
1099
self.run_command(value_to_set, self.clients)
1071
for client in self.clients:
1072
value = client.attributes[self.property]
1100
for clientpath in self.clients:
1101
client = self.bus.get_object(busname, clientpath)
1102
value = client.attributes[self.propname]
1073
1103
self.assertNotIsInstance(value, Unique)
1074
1104
self.assertEqual(value, value_to_get)
1075
1105
def run_command(self, value, clients):
1076
self.command().run(None, clients)
1106
self.command().run(clients, self.bus)
1078
1108
class TestBumpTimeoutCmd(TestPropertyCmd):
1079
1109
command = BumpTimeoutCmd
1080
property = "LastCheckedOK"
1110
propname = "LastCheckedOK"
1081
1111
values_to_set = [""]
1083
1113
class TestStartCheckerCmd(TestPropertyCmd):
1084
1114
command = StartCheckerCmd
1085
property = "CheckerRunning"
1115
propname = "CheckerRunning"
1086
1116
values_to_set = [dbus.Boolean(True)]
1088
1118
class TestStopCheckerCmd(TestPropertyCmd):
1089
1119
command = StopCheckerCmd
1090
property = "CheckerRunning"
1120
propname = "CheckerRunning"
1091
1121
values_to_set = [dbus.Boolean(False)]
1093
1123
class TestApproveByDefaultCmd(TestPropertyCmd):
1094
1124
command = ApproveByDefaultCmd
1095
property = "ApprovedByDefault"
1125
propname = "ApprovedByDefault"
1096
1126
values_to_set = [dbus.Boolean(True)]
1098
1128
class TestDenyByDefaultCmd(TestPropertyCmd):
1099
1129
command = DenyByDefaultCmd
1100
property = "ApprovedByDefault"
1130
propname = "ApprovedByDefault"
1101
1131
values_to_set = [dbus.Boolean(False)]
1103
1133
class TestValueArgumentPropertyCmd(TestPropertyCmd):
1109
1139
return super(TestValueArgumentPropertyCmd, self).runTest()
1110
1140
def run_command(self, value, clients):
1111
self.command(value).run(None, clients)
1141
self.command(value).run(clients, self.bus)
1113
1143
class TestSetCheckerCmd(TestValueArgumentPropertyCmd):
1114
1144
command = SetCheckerCmd
1115
property = "Checker"
1145
propname = "Checker"
1116
1146
values_to_set = ["", ":", "fping -q -- %s"]
1118
1148
class TestSetHostCmd(TestValueArgumentPropertyCmd):
1119
1149
command = SetHostCmd
1121
1151
values_to_set = ["192.0.2.3", "foo.example.org"]
1123
1153
class TestSetSecretCmd(TestValueArgumentPropertyCmd):
1124
1154
command = SetSecretCmd
1126
1156
values_to_set = [io.BytesIO(b""),
1127
1157
io.BytesIO(b"secret\0xyzzy\nbar")]
1128
1158
values_to_get = [b"", b"secret\0xyzzy\nbar"]
1130
1160
class TestSetTimeoutCmd(TestValueArgumentPropertyCmd):
1131
1161
command = SetTimeoutCmd
1132
property = "Timeout"
1162
propname = "Timeout"
1133
1163
values_to_set = [datetime.timedelta(),
1134
1164
datetime.timedelta(minutes=5),
1135
1165
datetime.timedelta(seconds=1),
1140
1170
class TestSetExtendedTimeoutCmd(TestValueArgumentPropertyCmd):
1141
1171
command = SetExtendedTimeoutCmd
1142
property = "ExtendedTimeout"
1172
propname = "ExtendedTimeout"
1143
1173
values_to_set = [datetime.timedelta(),
1144
1174
datetime.timedelta(minutes=5),
1145
1175
datetime.timedelta(seconds=1),
1160
1190
class TestSetApprovalDelayCmd(TestValueArgumentPropertyCmd):
1161
1191
command = SetApprovalDelayCmd
1162
property = "ApprovalDelay"
1192
propname = "ApprovalDelay"
1163
1193
values_to_set = [datetime.timedelta(),
1164
1194
datetime.timedelta(minutes=5),
1165
1195
datetime.timedelta(seconds=1),
1170
1200
class TestSetApprovalDurationCmd(TestValueArgumentPropertyCmd):
1171
1201
command = SetApprovalDurationCmd
1172
property = "ApprovalDuration"
1202
propname = "ApprovalDuration"
1173
1203
values_to_set = [datetime.timedelta(),
1174
1204
datetime.timedelta(minutes=5),
1175
1205
datetime.timedelta(seconds=1),
1355
1385
def test_is_enabled_short(self):
1356
1386
self.assert_command_from_args(["-V", "foo"], IsEnabledCmd)
1388
def test_deny_before_remove(self):
1389
options = self.parser.parse_args(["--deny", "--remove", "foo"])
1390
check_option_syntax(self.parser, options)
1391
commands = commands_from_options(options)
1392
self.assertEqual(len(commands), 2)
1393
self.assertIsInstance(commands[0], DenyCmd)
1394
self.assertIsInstance(commands[1], RemoveCmd)
1396
def test_deny_before_remove_reversed(self):
1397
options = self.parser.parse_args(["--remove", "--deny", "--all"])
1398
check_option_syntax(self.parser, options)
1399
commands = commands_from_options(options)
1400
self.assertEqual(len(commands), 2)
1401
self.assertIsInstance(commands[0], DenyCmd)
1402
self.assertIsInstance(commands[1], RemoveCmd)
1359
1405
class Test_check_option_syntax(unittest.TestCase):
1360
1406
# This mostly corresponds to the definition from has_actions() in
1475
1521
with self.assertParseError():
1476
1522
self.check_option_syntax(options)
1524
def test_remove_can_only_be_combined_with_action_deny(self):
1525
for action, value in self.actions.items():
1526
if action in {"remove", "deny"}:
1528
options = self.parser.parse_args()
1529
setattr(options, action, value)
1531
options.remove = True
1532
with self.assertParseError():
1533
self.check_option_syntax(options)
1480
1537
def should_only_run_tests():