271
## Classes for commands.
273
# Abstract classes first
274
class Command(object):
275
"""Abstract class for commands"""
276
def run(self, mandos, clients):
277
"""Normal commands should implement run_on_one_client(), but
278
commands which want to operate on all clients at the same time
279
can override this run() method instead."""
281
for client in clients:
282
self.run_on_one_client(client)
284
class PrintCmd(Command):
285
"""Abstract class for commands printing client details"""
286
all_keywords = ("Name", "Enabled", "Timeout", "LastCheckedOK",
287
"Created", "Interval", "Host", "KeyID",
288
"Fingerprint", "CheckerRunning", "LastEnabled",
289
"ApprovalPending", "ApprovedByDefault",
290
"LastApprovalRequest", "ApprovalDelay",
291
"ApprovalDuration", "Checker", "ExtendedTimeout",
292
"Expires", "LastCheckerStatus")
293
def run(self, mandos, clients):
294
print(self.output(clients))
296
class PropertyCmd(Command):
297
"""Abstract class for Actions for setting one client property"""
298
def run_on_one_client(self, client):
299
"""Set the Client's D-Bus property"""
300
client.Set(client_interface, self.property, self.value_to_set,
301
dbus_interface=dbus.PROPERTIES_IFACE)
303
class ValueArgumentMixIn(object):
304
"""Mixin class for commands taking a value as argument"""
305
def __init__(self, value):
306
self.value_to_set = value
308
class MillisecondsValueArgumentMixIn(ValueArgumentMixIn):
309
"""Mixin class for commands taking a value argument as
312
def value_to_set(self):
315
def value_to_set(self, value):
316
"""When setting, convert value to a datetime.timedelta"""
317
self._vts = string_to_delta(value).total_seconds() * 1000
319
# Actual (non-abstract) command classes
321
class PrintTableCmd(PrintCmd):
322
def __init__(self, verbose=False):
323
self.verbose = verbose
325
def output(self, clients):
327
keywords = self.all_keywords
329
keywords = ("Name", "Enabled", "Timeout", "LastCheckedOK")
330
return str(self.TableOfClients(clients.values(), keywords))
332
class TableOfClients(object):
335
"Enabled": "Enabled",
336
"Timeout": "Timeout",
337
"LastCheckedOK": "Last Successful Check",
338
"LastApprovalRequest": "Last Approval Request",
339
"Created": "Created",
340
"Interval": "Interval",
342
"Fingerprint": "Fingerprint",
344
"CheckerRunning": "Check Is Running",
345
"LastEnabled": "Last Enabled",
346
"ApprovalPending": "Approval Is Pending",
347
"ApprovedByDefault": "Approved By Default",
348
"ApprovalDelay": "Approval Delay",
349
"ApprovalDuration": "Approval Duration",
350
"Checker": "Checker",
351
"ExtendedTimeout": "Extended Timeout",
352
"Expires": "Expires",
353
"LastCheckerStatus": "Last Checker Status",
356
def __init__(self, clients, keywords, tableheaders=None):
357
self.clients = clients
358
self.keywords = keywords
359
if tableheaders is not None:
360
self.tableheaders = tableheaders
363
return "\n".join(self.rows())
365
if sys.version_info.major == 2:
366
__unicode__ = __str__
368
return str(self).encode(locale.getpreferredencoding())
371
format_string = self.row_formatting_string()
372
rows = [self.header_line(format_string)]
373
rows.extend(self.client_line(client, format_string)
374
for client in self.clients)
377
def row_formatting_string(self):
378
"Format string used to format table rows"
379
return " ".join("{{{key}:{width}}}".format(
380
width=max(len(self.tableheaders[key]),
381
*(len(self.string_from_client(client, key))
382
for client in self.clients)),
384
for key in self.keywords)
386
def string_from_client(self, client, key):
387
return self.valuetostring(client[key], key)
390
def valuetostring(value, keyword):
391
if isinstance(value, dbus.Boolean):
392
return "Yes" if value else "No"
393
if keyword in ("Timeout", "Interval", "ApprovalDelay",
394
"ApprovalDuration", "ExtendedTimeout"):
395
return milliseconds_to_string(value)
398
def header_line(self, format_string):
399
return format_string.format(**self.tableheaders)
401
def client_line(self, client, format_string):
402
return format_string.format(
403
**{key: self.string_from_client(client, key)
404
for key in self.keywords})
408
class DumpJSONCmd(PrintCmd):
409
def output(self, clients):
410
data = {client["Name"]:
411
{key: self.dbus_boolean_to_bool(client[key])
412
for key in self.all_keywords}
413
for client in clients.values()}
414
return json.dumps(data, indent=4, separators=(',', ': '))
272
def print_clients(clients, keywords):
273
print('\n'.join(TableOfClients(clients, keywords).rows()))
275
class TableOfClients(object):
278
"Enabled": "Enabled",
279
"Timeout": "Timeout",
280
"LastCheckedOK": "Last Successful Check",
281
"LastApprovalRequest": "Last Approval Request",
282
"Created": "Created",
283
"Interval": "Interval",
285
"Fingerprint": "Fingerprint",
287
"CheckerRunning": "Check Is Running",
288
"LastEnabled": "Last Enabled",
289
"ApprovalPending": "Approval Is Pending",
290
"ApprovedByDefault": "Approved By Default",
291
"ApprovalDelay": "Approval Delay",
292
"ApprovalDuration": "Approval Duration",
293
"Checker": "Checker",
294
"ExtendedTimeout": "Extended Timeout",
295
"Expires": "Expires",
296
"LastCheckerStatus": "Last Checker Status",
299
def __init__(self, clients, keywords, tablewords=None):
300
self.clients = clients
301
self.keywords = keywords
302
if tablewords is not None:
303
self.tablewords = tablewords
306
format_string = self.row_formatting_string()
307
rows = [self.header_line(format_string)]
308
rows.extend(self.client_line(client, format_string)
309
for client in self.clients)
312
def row_formatting_string(self):
313
"Format string used to format table rows"
314
return " ".join("{{{key}:{width}}}".format(
315
width=max(len(self.tablewords[key]),
316
max(len(self.string_from_client(client, key))
317
for client in self.clients)),
319
for key in self.keywords)
321
def string_from_client(self, client, key):
322
return self.valuetostring(client[key], key)
416
def dbus_boolean_to_bool(value):
325
def valuetostring(value, keyword):
417
326
if isinstance(value, dbus.Boolean):
421
class IsEnabledCmd(Command):
422
def run_on_one_client(self, client):
423
if self.is_enabled(client):
426
def is_enabled(self, client):
427
return client.Get(client_interface, "Enabled",
428
dbus_interface=dbus.PROPERTIES_IFACE)
430
class RemoveCmd(Command):
431
def run_on_one_client(self, client):
432
self.mandos.RemoveClient(client.__dbus_object_path__)
434
class ApproveCmd(Command):
435
def run_on_one_client(self, client):
436
client.Approve(dbus.Boolean(True),
437
dbus_interface=client_interface)
439
class DenyCmd(Command):
440
def run_on_one_client(self, client):
441
client.Approve(dbus.Boolean(False),
442
dbus_interface=client_interface)
444
class EnableCmd(PropertyCmd):
446
value_to_set = dbus.Boolean(True)
448
class DisableCmd(PropertyCmd):
450
value_to_set = dbus.Boolean(False)
452
class BumpTimeoutCmd(PropertyCmd):
453
property = "LastCheckedOK"
456
class StartCheckerCmd(PropertyCmd):
457
property = "CheckerRunning"
458
value_to_set = dbus.Boolean(True)
460
class StopCheckerCmd(PropertyCmd):
461
property = "CheckerRunning"
462
value_to_set = dbus.Boolean(False)
464
class ApproveByDefaultCmd(PropertyCmd):
465
property = "ApprovedByDefault"
466
value_to_set = dbus.Boolean(True)
468
class DenyByDefaultCmd(PropertyCmd):
469
property = "ApprovedByDefault"
470
value_to_set = dbus.Boolean(False)
472
class SetCheckerCmd(PropertyCmd, ValueArgumentMixIn):
475
class SetHostCmd(PropertyCmd, ValueArgumentMixIn):
478
class SetSecretCmd(PropertyCmd, ValueArgumentMixIn):
481
class SetTimeoutCmd(PropertyCmd, MillisecondsValueArgumentMixIn):
484
class SetExtendedTimeoutCmd(PropertyCmd,
485
MillisecondsValueArgumentMixIn):
486
property = "ExtendedTimeout"
488
class SetIntervalCmd(PropertyCmd, MillisecondsValueArgumentMixIn):
489
property = "Interval"
491
class SetApprovalDelayCmd(PropertyCmd,
492
MillisecondsValueArgumentMixIn):
493
property = "ApprovalDelay"
495
class SetApprovalDurationCmd(PropertyCmd,
496
MillisecondsValueArgumentMixIn):
497
property = "ApprovalDuration"
327
return "Yes" if value else "No"
328
if keyword in ("Timeout", "Interval", "ApprovalDelay",
329
"ApprovalDuration", "ExtendedTimeout"):
330
return milliseconds_to_string(value)
333
def header_line(self, format_string):
334
return format_string.format(**self.tablewords)
336
def client_line(self, client, format_string):
337
return format_string.format(
338
**{key: self.string_from_client(client, key)
339
for key in self.keywords})
499
342
def has_actions(options):
500
343
return any((options.enable,
723
476
log.critical("Client not found on server: %r", name)
726
# Run all commands on clients
727
for command in commands:
728
command.run(mandos_serv, clients)
479
if not has_actions(options) and clients:
480
if options.verbose or options.dump_json:
481
keywords = ("Name", "Enabled", "Timeout", "LastCheckedOK",
482
"Created", "Interval", "Host", "KeyID",
483
"Fingerprint", "CheckerRunning",
484
"LastEnabled", "ApprovalPending",
485
"ApprovedByDefault", "LastApprovalRequest",
486
"ApprovalDelay", "ApprovalDuration",
487
"Checker", "ExtendedTimeout", "Expires",
490
keywords = defaultkeywords
492
if options.dump_json:
493
json.dump({client["Name"]: {key:
495
if isinstance(client[key],
499
for client in clients.values()},
500
fp=sys.stdout, indent=4,
501
separators=(',', ': '))
504
print_clients(clients.values(), keywords)
506
# Process each client in the list by all selected options
507
for client in clients:
509
def set_client_prop(prop, value):
510
"""Set a Client D-Bus property"""
511
client.Set(client_interface, prop, value,
512
dbus_interface=dbus.PROPERTIES_IFACE)
514
def set_client_prop_ms(prop, value):
515
"""Set a Client D-Bus property, converted
516
from a string to milliseconds."""
517
set_client_prop(prop,
518
string_to_delta(value).total_seconds()
522
mandos_serv.RemoveClient(client.__dbus_object_path__)
524
set_client_prop("Enabled", dbus.Boolean(True))
526
set_client_prop("Enabled", dbus.Boolean(False))
527
if options.bump_timeout:
528
set_client_prop("LastCheckedOK", "")
529
if options.start_checker:
530
set_client_prop("CheckerRunning", dbus.Boolean(True))
531
if options.stop_checker:
532
set_client_prop("CheckerRunning", dbus.Boolean(False))
533
if options.is_enabled:
534
if client.Get(client_interface, "Enabled",
535
dbus_interface=dbus.PROPERTIES_IFACE):
539
if options.checker is not None:
540
set_client_prop("Checker", options.checker)
541
if options.host is not None:
542
set_client_prop("Host", options.host)
543
if options.interval is not None:
544
set_client_prop_ms("Interval", options.interval)
545
if options.approval_delay is not None:
546
set_client_prop_ms("ApprovalDelay",
547
options.approval_delay)
548
if options.approval_duration is not None:
549
set_client_prop_ms("ApprovalDuration",
550
options.approval_duration)
551
if options.timeout is not None:
552
set_client_prop_ms("Timeout", options.timeout)
553
if options.extended_timeout is not None:
554
set_client_prop_ms("ExtendedTimeout",
555
options.extended_timeout)
556
if options.secret is not None:
557
set_client_prop("Secret",
558
dbus.ByteArray(options.secret.read()))
559
if options.approved_by_default is not None:
560
set_client_prop("ApprovedByDefault",
562
.approved_by_default))
564
client.Approve(dbus.Boolean(True),
565
dbus_interface=client_interface)
567
client.Approve(dbus.Boolean(False),
568
dbus_interface=client_interface)
731
571
class Test_milliseconds_to_string(unittest.TestCase):
751
591
with self.assertLogs(log, logging.WARNING):
752
592
value = string_to_delta("2h")
754
class WarningFilter(logging.Filter):
755
"""Don't show, but record the presence of, warnings"""
756
def filter(self, record):
757
is_warning = record.levelno >= logging.WARNING
758
self.found = is_warning or getattr(self, "found",
760
return not is_warning
761
warning_filter = WarningFilter()
762
log.addFilter(warning_filter)
764
value = string_to_delta("2h")
766
log.removeFilter(warning_filter)
767
self.assertTrue(getattr(warning_filter, "found", False))
594
value = string_to_delta("2h")
768
595
self.assertEqual(value, datetime.timedelta(0, 7200))
771
class TestCmd(unittest.TestCase):
772
"""Abstract class for tests of command classes"""
775
class MockClient(object):
776
def __init__(self, name, **attributes):
777
self.__dbus_object_path__ = "objpath_{}".format(name)
778
self.attributes = attributes
779
self.attributes["Name"] = name
780
def Set(interface, property, value,
781
properties_interface):
782
testcase.assertEqual(interface, client_interface)
783
testcase.assertEqual(properties_interface,
784
dbus.PROPERTIES_IFACE)
785
self.attributes[property] = value
786
def Get(interface, property, properties_interface):
787
testcase.assertEqual(interface, client_interface)
788
testcase.assertEqual(properties_interface,
789
dbus.PROPERTIES_IFACE)
790
return self.attributes[property]
791
def __getitem__(self, key):
792
return self.attributes[key]
793
self.clients = collections.OrderedDict([
797
KeyID=("92ed150794387c03ce684574b1139a65"
798
"94a34f895daaaf09fd8ea90a27cddb12"),
800
Host="foo.example.org",
801
Enabled=dbus.Boolean(True),
803
LastCheckedOK="2019-02-03T00:00:00",
804
Created="2019-01-02T00:00:00",
806
Fingerprint=("778827225BA7DE539C5A"
807
"7CFA59CFF7CDBD9A5920"),
808
CheckerRunning=dbus.Boolean(False),
809
LastEnabled="2019-01-03T00:00:00",
810
ApprovalPending=dbus.Boolean(False),
811
ApprovedByDefault=dbus.Boolean(True),
812
LastApprovalRequest="",
814
ApprovalDuration=1000,
815
Checker="fping -q -- %(host)s",
816
ExtendedTimeout=900000,
817
Expires="2019-02-04T00:00:00",
818
LastCheckerStatus=0)),
822
KeyID=("0558568eedd67d622f5c83b35a115f79"
823
"6ab612cff5ad227247e46c2b020f441c"),
826
Enabled=dbus.Boolean(True),
828
LastCheckedOK="2019-02-04T00:00:00",
829
Created="2019-01-03T00:00:00",
831
Fingerprint=("3E393AEAEFB84C7E89E2"
832
"F547B3A107558FCA3A27"),
833
CheckerRunning=dbus.Boolean(True),
834
LastEnabled="2019-01-04T00:00:00",
835
ApprovalPending=dbus.Boolean(False),
836
ApprovedByDefault=dbus.Boolean(False),
837
LastApprovalRequest="2019-01-03T00:00:00",
839
ApprovalDuration=1000,
841
ExtendedTimeout=900000,
842
Expires="2019-02-05T00:00:00",
843
LastCheckerStatus=-2)),
846
class TestPrintTableCmd(TestCmd):
847
def test_normal(self):
848
output = PrintTableCmd().output(self.clients)
849
expected_output = """
850
Name Enabled Timeout Last Successful Check
851
foo Yes 00:05:00 2019-02-03T00:00:00
852
barbar Yes 00:05:00 2019-02-04T00:00:00
854
self.assertEqual(output, expected_output)
855
def test_verbose(self):
856
output = PrintTableCmd(verbose=True).output(self.clients)
857
expected_output = """
858
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
859
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
860
barbar Yes 00:05:00 2019-02-04T00:00:00 2019-01-03T00:00:00 00:02:00 192.0.2.3 0558568eedd67d622f5c83b35a115f796ab612cff5ad227247e46c2b020f441c 3E393AEAEFB84C7E89E2F547B3A107558FCA3A27 Yes 2019-01-04T00:00:00 No No 2019-01-03T00:00:00 00:00:30 00:00:01 : 00:15:00 2019-02-05T00:00:00 -2
862
self.assertEqual(output, expected_output)
863
def test_one_client(self):
864
output = PrintTableCmd().output({"foo": self.clients["foo"]})
865
expected_output = """
866
Name Enabled Timeout Last Successful Check
867
foo Yes 00:05:00 2019-02-03T00:00:00
869
self.assertEqual(output, expected_output)
871
class TestDumpJSONCmd(TestCmd):
873
self.expected_json = {
876
"KeyID": ("92ed150794387c03ce684574b1139a65"
877
"94a34f895daaaf09fd8ea90a27cddb12"),
878
"Host": "foo.example.org",
881
"LastCheckedOK": "2019-02-03T00:00:00",
882
"Created": "2019-01-02T00:00:00",
884
"Fingerprint": ("778827225BA7DE539C5A"
885
"7CFA59CFF7CDBD9A5920"),
886
"CheckerRunning": False,
887
"LastEnabled": "2019-01-03T00:00:00",
888
"ApprovalPending": False,
889
"ApprovedByDefault": True,
890
"LastApprovalRequest": "",
892
"ApprovalDuration": 1000,
893
"Checker": "fping -q -- %(host)s",
894
"ExtendedTimeout": 900000,
895
"Expires": "2019-02-04T00:00:00",
896
"LastCheckerStatus": 0,
900
"KeyID": ("0558568eedd67d622f5c83b35a115f79"
901
"6ab612cff5ad227247e46c2b020f441c"),
905
"LastCheckedOK": "2019-02-04T00:00:00",
906
"Created": "2019-01-03T00:00:00",
908
"Fingerprint": ("3E393AEAEFB84C7E89E2"
909
"F547B3A107558FCA3A27"),
910
"CheckerRunning": True,
911
"LastEnabled": "2019-01-04T00:00:00",
912
"ApprovalPending": False,
913
"ApprovedByDefault": False,
914
"LastApprovalRequest": "2019-01-03T00:00:00",
915
"ApprovalDelay": 30000,
916
"ApprovalDuration": 1000,
918
"ExtendedTimeout": 900000,
919
"Expires": "2019-02-05T00:00:00",
920
"LastCheckerStatus": -2,
597
class Test_TableOfClients(unittest.TestCase):
603
"Bool": "A D-BUS Boolean",
604
"NonDbusBoolean": "A Non-D-BUS Boolean",
605
"Integer": "An Integer",
606
"Timeout": "Timedelta 1",
607
"Interval": "Timedelta 2",
608
"ApprovalDelay": "Timedelta 3",
609
"ApprovalDuration": "Timedelta 4",
610
"ExtendedTimeout": "Timedelta 5",
611
"String": "A String",
923
return super(TestDumpJSONCmd, self).setUp()
924
def test_normal(self):
925
json_data = json.loads(DumpJSONCmd().output(self.clients))
926
self.assertDictEqual(json_data, self.expected_json)
927
def test_one_client(self):
928
clients = {"foo": self.clients["foo"]}
929
json_data = json.loads(DumpJSONCmd().output(clients))
930
expected_json = {"foo": self.expected_json["foo"]}
931
self.assertDictEqual(json_data, expected_json)
613
self.keywords = ["Attr1", "AttrTwo"]
619
"Bool": dbus.Boolean(False),
620
"NonDbusBoolean": False,
624
"ApprovalDelay": 2000,
625
"ApprovalDuration": 3000,
626
"ExtendedTimeout": 4000,
633
"Bool": dbus.Boolean(True),
634
"NonDbusBoolean": True,
637
"Interval": 93786000,
638
"ApprovalDelay": 93787000,
639
"ApprovalDuration": 93788000,
640
"ExtendedTimeout": 93789000,
641
"String": "A huge string which will not fit," * 10,
644
def test_short_header(self):
645
rows = TableOfClients(self.clients, self.keywords,
646
self.tablewords).rows()
651
self.assertEqual(rows, expected_rows)
652
def test_booleans(self):
653
keywords = ["Bool", "NonDbusBoolean"]
654
rows = TableOfClients(self.clients, keywords,
655
self.tablewords).rows()
657
"A D-BUS Boolean A Non-D-BUS Boolean",
661
self.assertEqual(rows, expected_rows)
662
def test_milliseconds_detection(self):
663
keywords = ["Integer", "Timeout", "Interval", "ApprovalDelay",
664
"ApprovalDuration", "ExtendedTimeout"]
665
rows = TableOfClients(self.clients, keywords,
666
self.tablewords).rows()
668
An Integer Timedelta 1 Timedelta 2 Timedelta 3 Timedelta 4 Timedelta 5
669
0 00:00:00 00:00:01 00:00:02 00:00:03 00:00:04
670
1 1T02:03:05 1T02:03:06 1T02:03:07 1T02:03:08 1T02:03:09
673
self.assertEqual(rows, expected_rows)
674
def test_empty_and_long_string_values(self):
675
keywords = ["String"]
676
rows = TableOfClients(self.clients, keywords,
677
self.tablewords).rows()
681
A huge string which will not fit,A huge string which will not fit,A huge string which will not fit,A huge string which will not fit,A huge string which will not fit,A huge string which will not fit,A huge string which will not fit,A huge string which will not fit,A huge string which will not fit,A huge string which will not fit,
684
self.assertEqual(rows, expected_rows)
934
688
def should_only_run_tests():