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
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
317
class MillisecondsValueArgumentMixIn(ValueArgumentMixIn):
318
"""Mixin class for commands taking a value argument as
325
class MillisecondsPropertyValueArgumentCmd(PropertyValueCmd):
326
"""Abstract class for PropertyValueCmd taking a value argument as
327
a datetime.timedelta() but should store it as milliseconds."""
321
329
def value_to_set(self):
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))
328
336
# Actual (non-abstract) command classes
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
class SetCheckerCmd(PropertyCmd, ValueArgumentMixIn):
490
class SetHostCmd(PropertyCmd, ValueArgumentMixIn):
493
class SetSecretCmd(PropertyCmd, ValueArgumentMixIn):
496
class SetCheckerCmd(PropertyValueCmd):
499
class SetHostCmd(PropertyValueCmd):
502
class SetSecretCmd(PropertyValueCmd):
495
505
def value_to_set(self):
499
509
"""When setting, read data from supplied file object"""
500
510
self._vts = value.read()
504
class SetTimeoutCmd(PropertyCmd, MillisecondsValueArgumentMixIn):
507
class SetExtendedTimeoutCmd(PropertyCmd,
508
MillisecondsValueArgumentMixIn):
509
property = "ExtendedTimeout"
511
class SetIntervalCmd(PropertyCmd, MillisecondsValueArgumentMixIn):
512
property = "Interval"
514
class SetApprovalDelayCmd(PropertyCmd,
515
MillisecondsValueArgumentMixIn):
516
property = "ApprovalDelay"
518
class SetApprovalDurationCmd(PropertyCmd,
519
MillisecondsValueArgumentMixIn):
520
property = "ApprovalDuration"
513
class SetTimeoutCmd(MillisecondsPropertyValueArgumentCmd):
516
class SetExtendedTimeoutCmd(MillisecondsPropertyValueArgumentCmd):
517
propname = "ExtendedTimeout"
519
class SetIntervalCmd(MillisecondsPropertyValueArgumentCmd):
520
propname = "Interval"
522
class SetApprovalDelayCmd(MillisecondsPropertyValueArgumentCmd):
523
propname = "ApprovalDelay"
525
class SetApprovalDurationCmd(MillisecondsPropertyValueArgumentCmd):
526
propname = "ApprovalDuration"
522
528
def add_command_line_options(parser):
523
529
parser.add_argument("--version", action="version",
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()}
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
765
775
log.critical("Client not found on server: %r", name)
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
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(
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),
897
self.one_client = {self.client: self.client.attributes}
907
self.one_client = {"/clients/foo": self.client.attributes}
912
def get_object(client_bus_name, path):
913
self.assertEqual(client_bus_name, busname)
915
"/clients/foo": self.client,
916
"/clients/barbar": self.other_client,
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
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,)),
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)),
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)),
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
1041
EnableCmd().run(None, self.clients)
1065
EnableCmd().run(self.clients, self.bus)
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"])
1046
1071
class TestDisableCmd(TestCmd):
1047
1072
def test_disable(self):
1048
DisableCmd().run(None, self.clients)
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"])
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)
1078
1105
class TestBumpTimeoutCmd(TestPropertyCmd):
1079
1106
command = BumpTimeoutCmd
1080
property = "LastCheckedOK"
1107
propname = "LastCheckedOK"
1081
1108
values_to_set = [""]
1083
1110
class TestStartCheckerCmd(TestPropertyCmd):
1084
1111
command = StartCheckerCmd
1085
property = "CheckerRunning"
1112
propname = "CheckerRunning"
1086
1113
values_to_set = [dbus.Boolean(True)]
1088
1115
class TestStopCheckerCmd(TestPropertyCmd):
1089
1116
command = StopCheckerCmd
1090
property = "CheckerRunning"
1117
propname = "CheckerRunning"
1091
1118
values_to_set = [dbus.Boolean(False)]
1093
1120
class TestApproveByDefaultCmd(TestPropertyCmd):
1094
1121
command = ApproveByDefaultCmd
1095
property = "ApprovedByDefault"
1122
propname = "ApprovedByDefault"
1096
1123
values_to_set = [dbus.Boolean(True)]
1098
1125
class TestDenyByDefaultCmd(TestPropertyCmd):
1099
1126
command = DenyByDefaultCmd
1100
property = "ApprovedByDefault"
1127
propname = "ApprovedByDefault"
1101
1128
values_to_set = [dbus.Boolean(False)]
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:
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)
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"]
1118
class TestSetHostCmd(TestValueArgumentPropertyCmd):
1144
class TestSetHostCmd(TestPropertyValueCmd):
1119
1145
command = SetHostCmd
1121
1147
values_to_set = ["192.0.2.3", "foo.example.org"]
1123
class TestSetSecretCmd(TestValueArgumentPropertyCmd):
1149
class TestSetSecretCmd(TestPropertyValueCmd):
1124
1150
command = SetSecretCmd
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"]
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]
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),
1157
1183
datetime.timedelta(weeks=52)]
1158
1184
values_to_get = [0, 300000, 1000, 604800000, 31449600000]
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]
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)
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)
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)
1359
1401
class Test_check_option_syntax(unittest.TestCase):
1360
1402
# This mostly corresponds to the definition from has_actions() in