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"""
301
306
def run_on_one_client(self, client, properties):
302
307
"""Set the Client's D-Bus property"""
303
client.Set(client_interface, self.property, self.value_to_set,
308
log.debug("D-Bus: %s:%s:%s.Set(%r, %r, %r)", busname,
309
client.__dbus_object_path__,
310
dbus.PROPERTIES_IFACE, client_interface,
311
self.propname, self.value_to_set
312
if not isinstance(self.value_to_set, dbus.Boolean)
313
else bool(self.value_to_set))
314
client.Set(client_interface, self.propname, self.value_to_set,
304
315
dbus_interface=dbus.PROPERTIES_IFACE)
318
raise NotImplementedError()
306
320
class ValueArgumentMixIn(object):
307
321
"""Mixin class for commands taking a value as argument"""
424
438
class IsEnabledCmd(Command):
425
def run_on_one_client(self, client, properties):
439
def run(self, clients, bus=None, mandos=None):
440
client, properties = next(iter(clients.items()))
426
441
if self.is_enabled(client, properties):
429
444
def is_enabled(self, client, properties):
430
return bool(properties["Enabled"])
445
return properties["Enabled"]
432
447
class RemoveCmd(Command):
433
448
def run_on_one_client(self, client, properties):
449
log.debug("D-Bus: %s:%s:%s.RemoveClient(%r)", busname,
450
server_path, server_interface,
451
str(client.__dbus_object_path__))
434
452
self.mandos.RemoveClient(client.__dbus_object_path__)
436
454
class ApproveCmd(Command):
437
455
def run_on_one_client(self, client, properties):
456
log.debug("D-Bus: %s:%s:%s.Approve(True)", busname,
457
client.__dbus_object_path__, client_interface)
438
458
client.Approve(dbus.Boolean(True),
439
459
dbus_interface=client_interface)
441
461
class DenyCmd(Command):
442
462
def run_on_one_client(self, client, properties):
463
log.debug("D-Bus: %s:%s:%s.Approve(False)", busname,
464
client.__dbus_object_path__, client_interface)
443
465
client.Approve(dbus.Boolean(False),
444
466
dbus_interface=client_interface)
446
468
class EnableCmd(PropertyCmd):
448
470
value_to_set = dbus.Boolean(True)
450
472
class DisableCmd(PropertyCmd):
452
474
value_to_set = dbus.Boolean(False)
454
476
class BumpTimeoutCmd(PropertyCmd):
455
property = "LastCheckedOK"
477
propname = "LastCheckedOK"
456
478
value_to_set = ""
458
480
class StartCheckerCmd(PropertyCmd):
459
property = "CheckerRunning"
481
propname = "CheckerRunning"
460
482
value_to_set = dbus.Boolean(True)
462
484
class StopCheckerCmd(PropertyCmd):
463
property = "CheckerRunning"
485
propname = "CheckerRunning"
464
486
value_to_set = dbus.Boolean(False)
466
488
class ApproveByDefaultCmd(PropertyCmd):
467
property = "ApprovedByDefault"
489
propname = "ApprovedByDefault"
468
490
value_to_set = dbus.Boolean(True)
470
492
class DenyByDefaultCmd(PropertyCmd):
471
property = "ApprovedByDefault"
493
propname = "ApprovedByDefault"
472
494
value_to_set = dbus.Boolean(False)
474
496
class SetCheckerCmd(PropertyCmd, ValueArgumentMixIn):
477
499
class SetHostCmd(PropertyCmd, ValueArgumentMixIn):
480
502
class SetSecretCmd(PropertyCmd, ValueArgumentMixIn):
482
505
def value_to_set(self):
486
509
"""When setting, read data from supplied file object"""
487
510
self._vts = value.read()
491
513
class SetTimeoutCmd(PropertyCmd, MillisecondsValueArgumentMixIn):
494
516
class SetExtendedTimeoutCmd(PropertyCmd,
495
517
MillisecondsValueArgumentMixIn):
496
property = "ExtendedTimeout"
518
propname = "ExtendedTimeout"
498
520
class SetIntervalCmd(PropertyCmd, MillisecondsValueArgumentMixIn):
499
property = "Interval"
521
propname = "Interval"
501
523
class SetApprovalDelayCmd(PropertyCmd,
502
524
MillisecondsValueArgumentMixIn):
503
property = "ApprovalDelay"
525
propname = "ApprovalDelay"
505
527
class SetApprovalDurationCmd(PropertyCmd,
506
528
MillisecondsValueArgumentMixIn):
507
property = "ApprovalDuration"
529
propname = "ApprovalDuration"
509
531
def add_command_line_options(parser):
510
532
parser.add_argument("--version", action="version",
566
588
help="Approve any current client request")
567
589
approve_deny.add_argument("-D", "--deny", action="store_true",
568
590
help="Deny any current client request")
591
parser.add_argument("--debug", action="store_true",
592
help="Debug mode (show D-Bus commands)")
569
593
parser.add_argument("--check", action="store_true",
570
594
help="Run self-test")
571
595
parser.add_argument("client", nargs="*", help="Client name")
732
768
if not clientnames:
733
clients = {bus.get_object(busname, path): properties
734
for path, properties in mandos_clients.items()}
769
clients = {objpath: properties
770
for objpath, properties in mandos_clients.items()}
736
772
for name in clientnames:
737
for path, client in mandos_clients.items():
738
if client["Name"] == name:
739
client_objc = bus.get_object(busname, path)
740
clients[client_objc] = client
773
for objpath, properties in mandos_clients.items():
774
if properties["Name"] == name:
775
clients[objpath] = properties
743
778
log.critical("Client not found on server: %r", name)
802
837
class MockClient(object):
803
838
def __init__(self, name, **attributes):
804
self.__dbus_object_path__ = "objpath_{}".format(name)
839
self.__dbus_object_path__ = "/clients/{}".format(name)
805
840
self.attributes = attributes
806
841
self.attributes["Name"] = name
808
def Set(self, interface, property, value, dbus_interface):
809
testcase.assertEqual(interface, client_interface)
810
testcase.assertEqual(dbus_interface,
811
dbus.PROPERTIES_IFACE)
812
self.attributes[property] = value
813
def Get(self, interface, property, dbus_interface):
814
testcase.assertEqual(interface, client_interface)
815
testcase.assertEqual(dbus_interface,
816
dbus.PROPERTIES_IFACE)
817
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]
818
853
def Approve(self, approve, dbus_interface):
819
854
testcase.assertEqual(dbus_interface, client_interface)
820
855
self.calls.append(("Approve", (approve,
869
904
LastCheckerStatus=-2)
870
905
self.clients = collections.OrderedDict(
872
(self.client, self.client.attributes),
873
(self.other_client, self.other_client.attributes),
907
("/clients/foo", self.client.attributes),
908
("/clients/barbar", self.other_client.attributes),
875
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,
877
923
class TestPrintTableCmd(TestCmd):
878
924
def test_normal(self):
879
output = PrintTableCmd().output(self.clients)
925
output = PrintTableCmd().output(self.clients.values())
880
926
expected_output = """
881
927
Name Enabled Timeout Last Successful Check
882
928
foo Yes 00:05:00 2019-02-03T00:00:00
990
1037
self.calls.append(("RemoveClient", (dbus_path,)))
991
1038
mandos = MockMandos()
992
1039
super(TestRemoveCmd, self).setUp()
993
RemoveCmd().run(mandos, self.clients)
1040
RemoveCmd().run(self.clients, self.bus, mandos)
994
1041
self.assertEqual(len(mandos.calls), 2)
995
for client in self.clients:
996
self.assertIn(("RemoveClient",
997
(client.__dbus_object_path__,)),
1042
for clientpath in self.clients:
1043
self.assertIn(("RemoveClient", (clientpath,)),
1000
1046
class TestApproveCmd(TestCmd):
1001
1047
def test_approve(self):
1002
ApproveCmd().run(None, self.clients)
1003
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)
1004
1051
self.assertIn(("Approve", (True, client_interface)),
1007
1054
class TestDenyCmd(TestCmd):
1008
1055
def test_deny(self):
1009
DenyCmd().run(None, self.clients)
1010
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)
1011
1059
self.assertIn(("Approve", (False, client_interface)),
1014
1062
class TestEnableCmd(TestCmd):
1015
1063
def test_enable(self):
1016
for client in self.clients:
1064
for clientpath in self.clients:
1065
client = self.bus.get_object(busname, clientpath)
1017
1066
client.attributes["Enabled"] = False
1019
EnableCmd().run(None, self.clients)
1068
EnableCmd().run(self.clients, self.bus)
1021
for client in self.clients:
1070
for clientpath in self.clients:
1071
client = self.bus.get_object(busname, clientpath)
1022
1072
self.assertTrue(client.attributes["Enabled"])
1024
1074
class TestDisableCmd(TestCmd):
1025
1075
def test_disable(self):
1026
DisableCmd().run(None, self.clients)
1028
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)
1029
1079
self.assertFalse(client.attributes["Enabled"])
1031
1081
class Unique(object):
1041
1091
self.values_to_set)
1042
1092
for value_to_set, value_to_get in zip(self.values_to_set,
1043
1093
values_to_get):
1044
for client in self.clients:
1045
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]
1046
1097
self.assertNotIsInstance(old_value, Unique)
1047
client.attributes[self.property] = Unique()
1098
client.attributes[self.propname] = Unique()
1048
1099
self.run_command(value_to_set, self.clients)
1049
for client in self.clients:
1050
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]
1051
1103
self.assertNotIsInstance(value, Unique)
1052
1104
self.assertEqual(value, value_to_get)
1053
1105
def run_command(self, value, clients):
1054
self.command().run(None, clients)
1106
self.command().run(clients, self.bus)
1056
1108
class TestBumpTimeoutCmd(TestPropertyCmd):
1057
1109
command = BumpTimeoutCmd
1058
property = "LastCheckedOK"
1110
propname = "LastCheckedOK"
1059
1111
values_to_set = [""]
1061
1113
class TestStartCheckerCmd(TestPropertyCmd):
1062
1114
command = StartCheckerCmd
1063
property = "CheckerRunning"
1115
propname = "CheckerRunning"
1064
1116
values_to_set = [dbus.Boolean(True)]
1066
1118
class TestStopCheckerCmd(TestPropertyCmd):
1067
1119
command = StopCheckerCmd
1068
property = "CheckerRunning"
1120
propname = "CheckerRunning"
1069
1121
values_to_set = [dbus.Boolean(False)]
1071
1123
class TestApproveByDefaultCmd(TestPropertyCmd):
1072
1124
command = ApproveByDefaultCmd
1073
property = "ApprovedByDefault"
1125
propname = "ApprovedByDefault"
1074
1126
values_to_set = [dbus.Boolean(True)]
1076
1128
class TestDenyByDefaultCmd(TestPropertyCmd):
1077
1129
command = DenyByDefaultCmd
1078
property = "ApprovedByDefault"
1130
propname = "ApprovedByDefault"
1079
1131
values_to_set = [dbus.Boolean(False)]
1081
1133
class TestValueArgumentPropertyCmd(TestPropertyCmd):
1087
1139
return super(TestValueArgumentPropertyCmd, self).runTest()
1088
1140
def run_command(self, value, clients):
1089
self.command(value).run(None, clients)
1141
self.command(value).run(clients, self.bus)
1091
1143
class TestSetCheckerCmd(TestValueArgumentPropertyCmd):
1092
1144
command = SetCheckerCmd
1093
property = "Checker"
1145
propname = "Checker"
1094
1146
values_to_set = ["", ":", "fping -q -- %s"]
1096
1148
class TestSetHostCmd(TestValueArgumentPropertyCmd):
1097
1149
command = SetHostCmd
1099
1151
values_to_set = ["192.0.2.3", "foo.example.org"]
1101
1153
class TestSetSecretCmd(TestValueArgumentPropertyCmd):
1102
1154
command = SetSecretCmd
1104
1156
values_to_set = [io.BytesIO(b""),
1105
1157
io.BytesIO(b"secret\0xyzzy\nbar")]
1106
1158
values_to_get = [b"", b"secret\0xyzzy\nbar"]
1108
1160
class TestSetTimeoutCmd(TestValueArgumentPropertyCmd):
1109
1161
command = SetTimeoutCmd
1110
property = "Timeout"
1162
propname = "Timeout"
1111
1163
values_to_set = [datetime.timedelta(),
1112
1164
datetime.timedelta(minutes=5),
1113
1165
datetime.timedelta(seconds=1),
1333
1385
def test_is_enabled_short(self):
1334
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)
1337
1405
class Test_check_option_syntax(unittest.TestCase):
1338
1406
# This mostly corresponds to the definition from has_actions() in
1453
1521
with self.assertParseError():
1454
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)
1458
1537
def should_only_run_tests():