397
def check_option_syntax(parser, options):
398
"""Apply additional restrictions on options, not expressible in
401
def has_actions(options):
402
return any((options.enable,
404
options.bump_timeout,
405
options.start_checker,
406
options.stop_checker,
409
options.checker is not None,
410
options.timeout is not None,
411
options.extended_timeout is not None,
412
options.interval is not None,
413
options.approved_by_default is not None,
414
options.approval_delay is not None,
415
options.approval_duration is not None,
416
options.host is not None,
417
options.secret is not None,
271
class TableOfClients(object):
274
"Enabled": "Enabled",
275
"Timeout": "Timeout",
276
"LastCheckedOK": "Last Successful Check",
277
"LastApprovalRequest": "Last Approval Request",
278
"Created": "Created",
279
"Interval": "Interval",
281
"Fingerprint": "Fingerprint",
283
"CheckerRunning": "Check Is Running",
284
"LastEnabled": "Last Enabled",
285
"ApprovalPending": "Approval Is Pending",
286
"ApprovedByDefault": "Approved By Default",
287
"ApprovalDelay": "Approval Delay",
288
"ApprovalDuration": "Approval Duration",
289
"Checker": "Checker",
290
"ExtendedTimeout": "Extended Timeout",
291
"Expires": "Expires",
292
"LastCheckerStatus": "Last Checker Status",
295
def __init__(self, clients, keywords, tablewords=None):
296
self.clients = clients
297
self.keywords = keywords
298
if tablewords is not None:
299
self.tablewords = tablewords
302
return "\n".join(self.rows())
304
if sys.version_info.major == 2:
305
__unicode__ = __str__
307
return str(self).encode(locale.getpreferredencoding())
310
format_string = self.row_formatting_string()
311
rows = [self.header_line(format_string)]
312
rows.extend(self.client_line(client, format_string)
313
for client in self.clients)
316
def row_formatting_string(self):
317
"Format string used to format table rows"
318
return " ".join("{{{key}:{width}}}".format(
319
width=max(len(self.tablewords[key]),
320
*(len(self.string_from_client(client, key))
321
for client in self.clients)),
323
for key in self.keywords)
325
def string_from_client(self, client, key):
326
return self.valuetostring(client[key], key)
329
def valuetostring(value, keyword):
330
if isinstance(value, dbus.Boolean):
331
return "Yes" if value else "No"
332
if keyword in ("Timeout", "Interval", "ApprovalDelay",
333
"ApprovalDuration", "ExtendedTimeout"):
334
return milliseconds_to_string(value)
337
def header_line(self, format_string):
338
return format_string.format(**self.tablewords)
340
def client_line(self, client, format_string):
341
return format_string.format(
342
**{key: self.string_from_client(client, key)
343
for key in self.keywords})
346
## Classes for commands.
348
# Abstract classes first
349
class Command(object):
350
"""Abstract class for commands"""
351
def run(self, clients):
352
"""Normal commands should implement run_on_one_client(), but
353
commands which want to operate on all clients at the same time
354
can override this run() method instead."""
355
for client in clients:
356
self.run_on_one_client(client)
358
class PrintCmd(Command):
359
"""Abstract class for commands printing client details"""
360
all_keywords = ("Name", "Enabled", "Timeout", "LastCheckedOK",
361
"Created", "Interval", "Host", "KeyID",
362
"Fingerprint", "CheckerRunning", "LastEnabled",
363
"ApprovalPending", "ApprovedByDefault",
364
"LastApprovalRequest", "ApprovalDelay",
365
"ApprovalDuration", "Checker", "ExtendedTimeout",
366
"Expires", "LastCheckerStatus")
367
def run(self, clients):
368
print(self.output(clients))
370
class PropertyCmd(Command):
371
"""Abstract class for Actions for setting one client property"""
372
def run_on_one_client(self, client):
373
"""Set the Client's D-Bus property"""
374
client.Set(client_interface, self.property, self.value_to_set,
375
dbus_interface=dbus.PROPERTIES_IFACE)
377
class ValueArgumentMixIn(object):
378
"""Mixin class for commands taking a value as argument"""
379
def __init__(self, value):
380
self.value_to_set = value
382
class MillisecondsValueArgumentMixIn(ValueArgumentMixIn):
383
"""Mixin class for commands taking a value argument as
386
def value_to_set(self):
389
def value_to_set(self, value):
390
"""When setting, convert value to a datetime.timedelta"""
391
self._vts = string_to_delta(value).total_seconds() * 1000
393
# Actual (non-abstract) command classes
395
class PrintTableCmd(PrintCmd):
396
def __init__(self, verbose=False):
397
self.verbose = verbose
398
def output(self, clients):
400
keywords = self.all_keywords
402
keywords = ("Name", "Enabled", "Timeout", "LastCheckedOK")
403
return str(TableOfClients(clients.values(), keywords))
405
class DumpJSONCmd(PrintCmd):
406
def output(self, clients):
407
data = {client["Name"]:
408
{key: self.dbus_boolean_to_bool(client[key])
409
for key in self.all_keywords}
410
for client in clients.values()}
411
return json.dumps(data, indent=4, separators=(',', ': '))
413
def dbus_boolean_to_bool(value):
414
if isinstance(value, dbus.Boolean):
418
class IsEnabledCmd(Command):
419
def run_on_one_client(self, client):
420
if self.is_enabled(client):
423
def is_enabled(self, client):
424
return client.Get(client_interface, "Enabled",
425
dbus_interface=dbus.PROPERTIES_IFACE)
427
class RemoveCmd(Command):
428
def __init__(self, mandos):
430
def run_on_one_client(self, client):
431
self.mandos.RemoveClient(client.__dbus_object_path__)
433
class ApproveCmd(Command):
434
def run_on_one_client(self, client):
435
client.Approve(dbus.Boolean(True),
436
dbus_interface=client_interface)
438
class DenyCmd(Command):
439
def run_on_one_client(self, client):
440
client.Approve(dbus.Boolean(False),
441
dbus_interface=client_interface)
443
class EnableCmd(PropertyCmd):
445
value_to_set = dbus.Boolean(True)
447
class DisableCmd(PropertyCmd):
449
value_to_set = dbus.Boolean(False)
451
class BumpTimeoutCmd(PropertyCmd):
452
property = "LastCheckedOK"
455
class StartCheckerCmd(PropertyCmd):
456
property = "CheckerRunning"
457
value_to_set = dbus.Boolean(True)
459
class StopCheckerCmd(PropertyCmd):
460
property = "CheckerRunning"
461
value_to_set = dbus.Boolean(False)
463
class ApproveByDefaultCmd(PropertyCmd):
464
property = "ApprovedByDefault"
465
value_to_set = dbus.Boolean(True)
467
class DenyByDefaultCmd(PropertyCmd):
468
property = "ApprovedByDefault"
469
value_to_set = dbus.Boolean(False)
471
class SetCheckerCmd(PropertyCmd, ValueArgumentMixIn):
474
class SetHostCmd(PropertyCmd, ValueArgumentMixIn):
477
class SetSecretCmd(PropertyCmd, ValueArgumentMixIn):
480
class SetTimeoutCmd(PropertyCmd, MillisecondsValueArgumentMixIn):
483
class SetExtendedTimeoutCmd(PropertyCmd,
484
MillisecondsValueArgumentMixIn):
485
property = "ExtendedTimeout"
487
class SetIntervalCmd(PropertyCmd, MillisecondsValueArgumentMixIn):
488
property = "Interval"
490
class SetApprovalDelayCmd(PropertyCmd,
491
MillisecondsValueArgumentMixIn):
492
property = "ApprovalDelay"
494
class SetApprovalDurationCmd(PropertyCmd,
495
MillisecondsValueArgumentMixIn):
496
property = "ApprovalDuration"
498
def has_actions(options):
499
return any((options.enable,
501
options.bump_timeout,
502
options.start_checker,
503
options.stop_checker,
506
options.checker is not None,
507
options.timeout is not None,
508
options.extended_timeout is not None,
509
options.interval is not None,
510
options.approved_by_default is not None,
511
options.approval_delay is not None,
512
options.approval_duration is not None,
513
options.host is not None,
514
options.secret is not None,
520
parser = argparse.ArgumentParser()
521
parser.add_argument("--version", action="version",
522
version="%(prog)s {}".format(version),
523
help="show version number and exit")
524
parser.add_argument("-a", "--all", action="store_true",
525
help="Select all clients")
526
parser.add_argument("-v", "--verbose", action="store_true",
527
help="Print all fields")
528
parser.add_argument("-j", "--dump-json", action="store_true",
529
help="Dump client data in JSON format")
530
enable_disable = parser.add_mutually_exclusive_group()
531
enable_disable.add_argument("-e", "--enable", action="store_true",
532
help="Enable client")
533
enable_disable.add_argument("-d", "--disable",
535
help="disable client")
536
parser.add_argument("-b", "--bump-timeout", action="store_true",
537
help="Bump timeout for client")
538
start_stop_checker = parser.add_mutually_exclusive_group()
539
start_stop_checker.add_argument("--start-checker",
541
help="Start checker for client")
542
start_stop_checker.add_argument("--stop-checker",
544
help="Stop checker for client")
545
parser.add_argument("-V", "--is-enabled", action="store_true",
546
help="Check if client is enabled")
547
parser.add_argument("-r", "--remove", action="store_true",
548
help="Remove client")
549
parser.add_argument("-c", "--checker",
550
help="Set checker command for client")
551
parser.add_argument("-t", "--timeout",
552
help="Set timeout for client")
553
parser.add_argument("--extended-timeout",
554
help="Set extended timeout for client")
555
parser.add_argument("-i", "--interval",
556
help="Set checker interval for client")
557
approve_deny_default = parser.add_mutually_exclusive_group()
558
approve_deny_default.add_argument(
559
"--approve-by-default", action="store_true",
560
default=None, dest="approved_by_default",
561
help="Set client to be approved by default")
562
approve_deny_default.add_argument(
563
"--deny-by-default", action="store_false",
564
dest="approved_by_default",
565
help="Set client to be denied by default")
566
parser.add_argument("--approval-delay",
567
help="Set delay before client approve/deny")
568
parser.add_argument("--approval-duration",
569
help="Set duration of one client approval")
570
parser.add_argument("-H", "--host", help="Set host for client")
571
parser.add_argument("-s", "--secret",
572
type=argparse.FileType(mode="rb"),
573
help="Set password blob (file) for client")
574
approve_deny = parser.add_mutually_exclusive_group()
575
approve_deny.add_argument(
576
"-A", "--approve", action="store_true",
577
help="Approve any current client request")
578
approve_deny.add_argument("-D", "--deny", action="store_true",
579
help="Deny any current client request")
580
parser.add_argument("--check", action="store_true",
581
help="Run self-test")
582
parser.add_argument("client", nargs="*", help="Client name")
583
options = parser.parse_args()
421
585
if has_actions(options) and not (options.client or options.all):
422
586
parser.error("Options require clients names or --all.")
512
643
SetExtendedTimeoutCmd(options.extended_timeout))
514
645
if options.interval is not None:
515
commands.append(SetIntervalCmd(options.interval))
646
command.append(SetIntervalCmd(options.interval))
648
if options.approved_by_default is not None:
649
if options.approved_by_default:
650
command.append(ApproveByDefaultCmd())
652
command.append(DenyByDefaultCmd())
517
654
if options.approval_delay is not None:
518
commands.append(SetApprovalDelayCmd(options.approval_delay))
655
command.append(SetApprovalDelayCmd(options.approval_delay))
520
657
if options.approval_duration is not None:
522
659
SetApprovalDurationCmd(options.approval_duration))
661
if options.host is not None:
662
command.append(SetHostCmd(options.host))
664
if options.secret is not None:
665
command.append(SetSecretCmd(options.secret))
668
commands.append(ApproveCmd())
671
commands.append(DenyCmd())
524
673
# If no command option has been given, show table of clients,
525
674
# optionally verbosely
527
676
commands.append(PrintTableCmd(verbose=options.verbose))
532
class Command(object):
533
"""Abstract class for commands"""
534
def run(self, clients, bus=None, mandos=None):
535
"""Normal commands should implement run_on_one_client(), but
536
commands which want to operate on all clients at the same time
537
can override this run() method instead."""
539
for clientpath, properties in clients.items():
540
log.debug("D-Bus: Connect to: (busname=%r, path=%r)",
541
dbus_busname, str(clientpath))
542
client = bus.get_object(dbus_busname, clientpath)
543
self.run_on_one_client(client, properties)
546
class IsEnabledCmd(Command):
547
def run(self, clients, bus=None, mandos=None):
548
client, properties = next(iter(clients.items()))
549
if self.is_enabled(client, properties):
678
# block stderr since dbus library prints to stderr
679
null = os.open(os.path.devnull, os.O_RDWR)
680
stderrcopy = os.dup(sys.stderr.fileno())
681
os.dup2(null, sys.stderr.fileno())
685
mandos_clients = {path: ifs_and_props[client_interface]
686
for path, ifs_and_props in
687
mandos_serv_object_manager
688
.GetManagedObjects().items()
689
if client_interface in ifs_and_props}
692
os.dup2(stderrcopy, sys.stderr.fileno())
694
except dbus.exceptions.DBusException as e:
695
log.critical("Failed to access Mandos server through D-Bus:"
552
def is_enabled(self, client, properties):
553
return properties["Enabled"]
556
class ApproveCmd(Command):
557
def run_on_one_client(self, client, properties):
558
log.debug("D-Bus: %s:%s:%s.Approve(True)", dbus_busname,
559
client.__dbus_object_path__, client_dbus_interface)
560
client.Approve(dbus.Boolean(True),
561
dbus_interface=client_dbus_interface)
564
class DenyCmd(Command):
565
def run_on_one_client(self, client, properties):
566
log.debug("D-Bus: %s:%s:%s.Approve(False)", dbus_busname,
567
client.__dbus_object_path__, client_dbus_interface)
568
client.Approve(dbus.Boolean(False),
569
dbus_interface=client_dbus_interface)
572
class RemoveCmd(Command):
573
def run_on_one_client(self, client, properties):
574
log.debug("D-Bus: %s:%s:%s.RemoveClient(%r)", dbus_busname,
575
server_dbus_path, server_dbus_interface,
576
str(client.__dbus_object_path__))
577
self.mandos.RemoveClient(client.__dbus_object_path__)
580
class OutputCmd(Command):
581
"""Abstract class for commands outputting client details"""
582
all_keywords = ("Name", "Enabled", "Timeout", "LastCheckedOK",
583
"Created", "Interval", "Host", "KeyID",
584
"Fingerprint", "CheckerRunning", "LastEnabled",
585
"ApprovalPending", "ApprovedByDefault",
586
"LastApprovalRequest", "ApprovalDelay",
587
"ApprovalDuration", "Checker", "ExtendedTimeout",
588
"Expires", "LastCheckerStatus")
590
def run(self, clients, bus=None, mandos=None):
591
print(self.output(clients.values()))
593
def output(self, clients):
594
raise NotImplementedError()
597
class DumpJSONCmd(OutputCmd):
598
def output(self, clients):
599
data = {client["Name"]:
600
{key: self.dbus_boolean_to_bool(client[key])
601
for key in self.all_keywords}
602
for client in clients}
603
return json.dumps(data, indent=4, separators=(',', ': '))
606
def dbus_boolean_to_bool(value):
607
if isinstance(value, dbus.Boolean):
612
class PrintTableCmd(OutputCmd):
613
def __init__(self, verbose=False):
614
self.verbose = verbose
616
def output(self, clients):
617
default_keywords = ("Name", "Enabled", "Timeout",
619
keywords = default_keywords
621
keywords = self.all_keywords
622
return str(self.TableOfClients(clients, keywords))
624
class TableOfClients(object):
627
"Enabled": "Enabled",
628
"Timeout": "Timeout",
629
"LastCheckedOK": "Last Successful Check",
630
"LastApprovalRequest": "Last Approval Request",
631
"Created": "Created",
632
"Interval": "Interval",
634
"Fingerprint": "Fingerprint",
636
"CheckerRunning": "Check Is Running",
637
"LastEnabled": "Last Enabled",
638
"ApprovalPending": "Approval Is Pending",
639
"ApprovedByDefault": "Approved By Default",
640
"ApprovalDelay": "Approval Delay",
641
"ApprovalDuration": "Approval Duration",
642
"Checker": "Checker",
643
"ExtendedTimeout": "Extended Timeout",
644
"Expires": "Expires",
645
"LastCheckerStatus": "Last Checker Status",
648
def __init__(self, clients, keywords):
649
self.clients = clients
650
self.keywords = keywords
653
return "\n".join(self.rows())
655
if sys.version_info.major == 2:
656
__unicode__ = __str__
658
return str(self).encode(locale.getpreferredencoding())
661
format_string = self.row_formatting_string()
662
rows = [self.header_line(format_string)]
663
rows.extend(self.client_line(client, format_string)
664
for client in self.clients)
667
def row_formatting_string(self):
668
"Format string used to format table rows"
669
return " ".join("{{{key}:{width}}}".format(
670
width=max(len(self.tableheaders[key]),
671
*(len(self.string_from_client(client, key))
672
for client in self.clients)),
674
for key in self.keywords)
676
def string_from_client(self, client, key):
677
return self.valuetostring(client[key], key)
680
def valuetostring(cls, value, keyword):
681
if isinstance(value, dbus.Boolean):
682
return "Yes" if value else "No"
683
if keyword in ("Timeout", "Interval", "ApprovalDelay",
684
"ApprovalDuration", "ExtendedTimeout"):
685
return cls.milliseconds_to_string(value)
688
def header_line(self, format_string):
689
return format_string.format(**self.tableheaders)
691
def client_line(self, client, format_string):
692
return format_string.format(
693
**{key: self.string_from_client(client, key)
694
for key in self.keywords})
697
def milliseconds_to_string(ms):
698
td = datetime.timedelta(0, 0, 0, ms)
699
return ("{days}{hours:02}:{minutes:02}:{seconds:02}"
700
.format(days="{}T".format(td.days)
702
hours=td.seconds // 3600,
703
minutes=(td.seconds % 3600) // 60,
704
seconds=td.seconds % 60))
707
class PropertyCmd(Command):
708
"""Abstract class for Actions for setting one client property"""
710
def run_on_one_client(self, client, properties):
711
"""Set the Client's D-Bus property"""
712
log.debug("D-Bus: %s:%s:%s.Set(%r, %r, %r)", dbus_busname,
713
client.__dbus_object_path__,
714
dbus.PROPERTIES_IFACE, client_dbus_interface,
715
self.propname, self.value_to_set
716
if not isinstance(self.value_to_set, dbus.Boolean)
717
else bool(self.value_to_set))
718
client.Set(client_dbus_interface, self.propname,
720
dbus_interface=dbus.PROPERTIES_IFACE)
724
raise NotImplementedError()
727
class EnableCmd(PropertyCmd):
729
value_to_set = dbus.Boolean(True)
732
class DisableCmd(PropertyCmd):
734
value_to_set = dbus.Boolean(False)
737
class BumpTimeoutCmd(PropertyCmd):
738
propname = "LastCheckedOK"
742
class StartCheckerCmd(PropertyCmd):
743
propname = "CheckerRunning"
744
value_to_set = dbus.Boolean(True)
747
class StopCheckerCmd(PropertyCmd):
748
propname = "CheckerRunning"
749
value_to_set = dbus.Boolean(False)
752
class ApproveByDefaultCmd(PropertyCmd):
753
propname = "ApprovedByDefault"
754
value_to_set = dbus.Boolean(True)
757
class DenyByDefaultCmd(PropertyCmd):
758
propname = "ApprovedByDefault"
759
value_to_set = dbus.Boolean(False)
762
class PropertyValueCmd(PropertyCmd):
763
"""Abstract class for PropertyCmd recieving a value as argument"""
764
def __init__(self, value):
765
self.value_to_set = value
768
class SetCheckerCmd(PropertyValueCmd):
772
class SetHostCmd(PropertyValueCmd):
776
class SetSecretCmd(PropertyValueCmd):
780
def value_to_set(self):
784
def value_to_set(self, value):
785
"""When setting, read data from supplied file object"""
786
self._vts = value.read()
790
class MillisecondsPropertyValueArgumentCmd(PropertyValueCmd):
791
"""Abstract class for PropertyValueCmd taking a value argument as
792
a datetime.timedelta() but should store it as milliseconds."""
795
def value_to_set(self):
799
def value_to_set(self, value):
800
"""When setting, convert value from a datetime.timedelta"""
801
self._vts = int(round(value.total_seconds() * 1000))
804
class SetTimeoutCmd(MillisecondsPropertyValueArgumentCmd):
808
class SetExtendedTimeoutCmd(MillisecondsPropertyValueArgumentCmd):
809
propname = "ExtendedTimeout"
812
class SetIntervalCmd(MillisecondsPropertyValueArgumentCmd):
813
propname = "Interval"
816
class SetApprovalDelayCmd(MillisecondsPropertyValueArgumentCmd):
817
propname = "ApprovalDelay"
820
class SetApprovalDurationCmd(MillisecondsPropertyValueArgumentCmd):
821
propname = "ApprovalDuration"
699
# Compile dict of (clients: properties) to process
702
if options.all or not options.client:
703
clients = {bus.get_object(busname, path): properties
704
for path, properties in mandos_clients.items()}
706
for name in options.client:
707
for path, client in mandos_clients.items():
708
if client["Name"] == name:
709
client_objc = bus.get_object(busname, path)
710
clients[client_objc] = client
713
log.critical("Client not found on server: %r", name)
716
# Run all commands on clients
717
for command in commands:
721
class Test_milliseconds_to_string(unittest.TestCase):
723
self.assertEqual(milliseconds_to_string(93785000),
725
def test_no_days(self):
726
self.assertEqual(milliseconds_to_string(7385000), "02:03:05")
727
def test_all_zero(self):
728
self.assertEqual(milliseconds_to_string(0), "00:00:00")
729
def test_no_fractional_seconds(self):
730
self.assertEqual(milliseconds_to_string(400), "00:00:00")
731
self.assertEqual(milliseconds_to_string(900), "00:00:00")
732
self.assertEqual(milliseconds_to_string(1900), "00:00:01")
825
734
class Test_string_to_delta(unittest.TestCase):
826
735
def test_handles_basic_rfc3339(self):
827
self.assertEqual(string_to_delta("PT0S"),
828
datetime.timedelta())
829
self.assertEqual(string_to_delta("P0D"),
830
datetime.timedelta())
831
self.assertEqual(string_to_delta("PT1S"),
832
datetime.timedelta(0, 1))
833
736
self.assertEqual(string_to_delta("PT2H"),
834
737
datetime.timedelta(0, 7200))
836
738
def test_falls_back_to_pre_1_6_1_with_warning(self):
837
739
# assertLogs only exists in Python 3.4
838
740
if hasattr(self, "assertLogs"):
839
741
with self.assertLogs(log, logging.WARNING):
840
742
value = string_to_delta("2h")
842
class WarningFilter(logging.Filter):
843
"""Don't show, but record the presence of, warnings"""
844
def filter(self, record):
845
is_warning = record.levelno >= logging.WARNING
846
self.found = is_warning or getattr(self, "found",
848
return not is_warning
849
warning_filter = WarningFilter()
850
log.addFilter(warning_filter)
852
value = string_to_delta("2h")
854
log.removeFilter(warning_filter)
855
self.assertTrue(getattr(warning_filter, "found", False))
744
value = string_to_delta("2h")
856
745
self.assertEqual(value, datetime.timedelta(0, 7200))
859
class Test_check_option_syntax(unittest.TestCase):
861
self.parser = argparse.ArgumentParser()
862
add_command_line_options(self.parser)
864
def test_actions_requires_client_or_all(self):
865
for action, value in self.actions.items():
866
options = self.parser.parse_args()
867
setattr(options, action, value)
868
with self.assertParseError():
869
self.check_option_syntax(options)
871
# This mostly corresponds to the definition from has_actions() in
872
# check_option_syntax()
874
# The actual values set here are not that important, but we do
875
# at least stick to the correct types, even though they are
879
"bump_timeout": True,
880
"start_checker": True,
881
"stop_checker": True,
885
"timeout": datetime.timedelta(),
886
"extended_timeout": datetime.timedelta(),
887
"interval": datetime.timedelta(),
888
"approved_by_default": True,
889
"approval_delay": datetime.timedelta(),
890
"approval_duration": datetime.timedelta(),
892
"secret": io.BytesIO(b"x"),
897
@contextlib.contextmanager
898
def assertParseError(self):
899
with self.assertRaises(SystemExit) as e:
900
with self.temporarily_suppress_stderr():
902
# Exit code from argparse is guaranteed to be "2". Reference:
903
# https://docs.python.org/3/library
904
# /argparse.html#exiting-methods
905
self.assertEqual(e.exception.code, 2)
908
@contextlib.contextmanager
909
def temporarily_suppress_stderr():
910
null = os.open(os.path.devnull, os.O_RDWR)
911
stderrcopy = os.dup(sys.stderr.fileno())
912
os.dup2(null, sys.stderr.fileno())
918
os.dup2(stderrcopy, sys.stderr.fileno())
921
def check_option_syntax(self, options):
922
check_option_syntax(self.parser, options)
924
def test_actions_conflicts_with_verbose(self):
925
for action, value in self.actions.items():
926
options = self.parser.parse_args()
927
setattr(options, action, value)
928
options.verbose = True
929
with self.assertParseError():
930
self.check_option_syntax(options)
932
def test_dump_json_conflicts_with_verbose(self):
933
options = self.parser.parse_args()
934
options.dump_json = True
935
options.verbose = True
936
with self.assertParseError():
937
self.check_option_syntax(options)
939
def test_dump_json_conflicts_with_action(self):
940
for action, value in self.actions.items():
941
options = self.parser.parse_args()
942
setattr(options, action, value)
943
options.dump_json = True
944
with self.assertParseError():
945
self.check_option_syntax(options)
947
def test_all_can_not_be_alone(self):
948
options = self.parser.parse_args()
950
with self.assertParseError():
951
self.check_option_syntax(options)
953
def test_all_is_ok_with_any_action(self):
954
for action, value in self.actions.items():
955
options = self.parser.parse_args()
956
setattr(options, action, value)
958
self.check_option_syntax(options)
960
def test_is_enabled_fails_without_client(self):
961
options = self.parser.parse_args()
962
options.is_enabled = True
963
with self.assertParseError():
964
self.check_option_syntax(options)
966
def test_is_enabled_works_with_one_client(self):
967
options = self.parser.parse_args()
968
options.is_enabled = True
969
options.client = ["foo"]
970
self.check_option_syntax(options)
972
def test_is_enabled_fails_with_two_clients(self):
973
options = self.parser.parse_args()
974
options.is_enabled = True
975
options.client = ["foo", "barbar"]
976
with self.assertParseError():
977
self.check_option_syntax(options)
979
def test_remove_can_only_be_combined_with_action_deny(self):
980
for action, value in self.actions.items():
981
if action in {"remove", "deny"}:
983
options = self.parser.parse_args()
984
setattr(options, action, value)
986
options.remove = True
987
with self.assertParseError():
988
self.check_option_syntax(options)
991
class Test_SilenceLogger(unittest.TestCase):
992
loggername = "mandos-ctl.Test_SilenceLogger"
993
log = logging.getLogger(loggername)
994
log.propagate = False
995
log.addHandler(logging.NullHandler())
998
self.counting_filter = self.CountingFilter()
1000
class CountingFilter(logging.Filter):
1001
"Count number of records"
1003
def filter(self, record):
1007
def test_should_filter_records_only_when_active(self):
1009
with SilenceLogger(self.loggername):
1010
self.log.addFilter(self.counting_filter)
1011
self.log.info("Filtered log message 1")
1012
self.log.info("Non-filtered message 2")
1013
self.log.info("Non-filtered message 3")
1015
self.log.removeFilter(self.counting_filter)
1016
self.assertEqual(self.counting_filter.count, 2)
1019
class Test_commands_from_options(unittest.TestCase):
1021
self.parser = argparse.ArgumentParser()
1022
add_command_line_options(self.parser)
1024
def test_is_enabled(self):
1025
self.assert_command_from_args(["--is-enabled", "foo"],
1028
def assert_command_from_args(self, args, command_cls,
1030
"""Assert that parsing ARGS should result in an instance of
1031
COMMAND_CLS with (optionally) all supplied attributes (CMD_ATTRS)."""
1032
options = self.parser.parse_args(args)
1033
check_option_syntax(self.parser, options)
1034
commands = commands_from_options(options)
1035
self.assertEqual(len(commands), 1)
1036
command = commands[0]
1037
self.assertIsInstance(command, command_cls)
1038
for key, value in cmd_attrs.items():
1039
self.assertEqual(getattr(command, key), value)
1041
def test_is_enabled_short(self):
1042
self.assert_command_from_args(["-V", "foo"], IsEnabledCmd)
1044
def test_approve(self):
1045
self.assert_command_from_args(["--approve", "foo"],
1048
def test_approve_short(self):
1049
self.assert_command_from_args(["-A", "foo"], ApproveCmd)
1051
def test_deny(self):
1052
self.assert_command_from_args(["--deny", "foo"], DenyCmd)
1054
def test_deny_short(self):
1055
self.assert_command_from_args(["-D", "foo"], DenyCmd)
1057
def test_remove(self):
1058
self.assert_command_from_args(["--remove", "foo"],
1061
def test_deny_before_remove(self):
1062
options = self.parser.parse_args(["--deny", "--remove",
1064
check_option_syntax(self.parser, options)
1065
commands = commands_from_options(options)
1066
self.assertEqual(len(commands), 2)
1067
self.assertIsInstance(commands[0], DenyCmd)
1068
self.assertIsInstance(commands[1], RemoveCmd)
1070
def test_deny_before_remove_reversed(self):
1071
options = self.parser.parse_args(["--remove", "--deny",
1073
check_option_syntax(self.parser, options)
1074
commands = commands_from_options(options)
1075
self.assertEqual(len(commands), 2)
1076
self.assertIsInstance(commands[0], DenyCmd)
1077
self.assertIsInstance(commands[1], RemoveCmd)
1079
def test_remove_short(self):
1080
self.assert_command_from_args(["-r", "foo"], RemoveCmd)
1082
def test_dump_json(self):
1083
self.assert_command_from_args(["--dump-json"], DumpJSONCmd)
1085
def test_enable(self):
1086
self.assert_command_from_args(["--enable", "foo"], EnableCmd)
1088
def test_enable_short(self):
1089
self.assert_command_from_args(["-e", "foo"], EnableCmd)
1091
def test_disable(self):
1092
self.assert_command_from_args(["--disable", "foo"],
1095
def test_disable_short(self):
1096
self.assert_command_from_args(["-d", "foo"], DisableCmd)
1098
def test_bump_timeout(self):
1099
self.assert_command_from_args(["--bump-timeout", "foo"],
1102
def test_bump_timeout_short(self):
1103
self.assert_command_from_args(["-b", "foo"], BumpTimeoutCmd)
1105
def test_start_checker(self):
1106
self.assert_command_from_args(["--start-checker", "foo"],
1109
def test_stop_checker(self):
1110
self.assert_command_from_args(["--stop-checker", "foo"],
1113
def test_approve_by_default(self):
1114
self.assert_command_from_args(["--approve-by-default", "foo"],
1115
ApproveByDefaultCmd)
1117
def test_deny_by_default(self):
1118
self.assert_command_from_args(["--deny-by-default", "foo"],
1121
def test_checker(self):
1122
self.assert_command_from_args(["--checker", ":", "foo"],
1123
SetCheckerCmd, value_to_set=":")
1125
def test_checker_empty(self):
1126
self.assert_command_from_args(["--checker", "", "foo"],
1127
SetCheckerCmd, value_to_set="")
1129
def test_checker_short(self):
1130
self.assert_command_from_args(["-c", ":", "foo"],
1131
SetCheckerCmd, value_to_set=":")
1133
def test_host(self):
1134
self.assert_command_from_args(["--host", "foo.example.org",
1136
value_to_set="foo.example.org")
1138
def test_host_short(self):
1139
self.assert_command_from_args(["-H", "foo.example.org",
1141
value_to_set="foo.example.org")
1143
def test_secret_devnull(self):
1144
self.assert_command_from_args(["--secret", os.path.devnull,
1145
"foo"], SetSecretCmd,
1148
def test_secret_tempfile(self):
1149
with tempfile.NamedTemporaryFile(mode="r+b") as f:
1150
value = b"secret\0xyzzy\nbar"
1153
self.assert_command_from_args(["--secret", f.name,
1154
"foo"], SetSecretCmd,
1157
def test_secret_devnull_short(self):
1158
self.assert_command_from_args(["-s", os.path.devnull, "foo"],
1159
SetSecretCmd, value_to_set=b"")
1161
def test_secret_tempfile_short(self):
1162
with tempfile.NamedTemporaryFile(mode="r+b") as f:
1163
value = b"secret\0xyzzy\nbar"
1166
self.assert_command_from_args(["-s", f.name, "foo"],
1170
def test_timeout(self):
1171
self.assert_command_from_args(["--timeout", "PT5M", "foo"],
1173
value_to_set=300000)
1175
def test_timeout_short(self):
1176
self.assert_command_from_args(["-t", "PT5M", "foo"],
1178
value_to_set=300000)
1180
def test_extended_timeout(self):
1181
self.assert_command_from_args(["--extended-timeout", "PT15M",
1183
SetExtendedTimeoutCmd,
1184
value_to_set=900000)
1186
def test_interval(self):
1187
self.assert_command_from_args(["--interval", "PT2M", "foo"],
1189
value_to_set=120000)
1191
def test_interval_short(self):
1192
self.assert_command_from_args(["-i", "PT2M", "foo"],
1194
value_to_set=120000)
1196
def test_approval_delay(self):
1197
self.assert_command_from_args(["--approval-delay", "PT30S",
1198
"foo"], SetApprovalDelayCmd,
1201
def test_approval_duration(self):
1202
self.assert_command_from_args(["--approval-duration", "PT1S",
1203
"foo"], SetApprovalDurationCmd,
1206
def test_print_table(self):
1207
self.assert_command_from_args([], PrintTableCmd,
1210
def test_print_table_verbose(self):
1211
self.assert_command_from_args(["--verbose"], PrintTableCmd,
1214
def test_print_table_verbose_short(self):
1215
self.assert_command_from_args(["-v"], PrintTableCmd,
1219
class TestCmd(unittest.TestCase):
1220
"""Abstract class for tests of command classes"""
1224
class MockClient(object):
1225
def __init__(self, name, **attributes):
1226
self.__dbus_object_path__ = "/clients/{}".format(name)
1227
self.attributes = attributes
1228
self.attributes["Name"] = name
1230
def Set(self, interface, propname, value, dbus_interface):
1231
testcase.assertEqual(interface, client_dbus_interface)
1232
testcase.assertEqual(dbus_interface,
1233
dbus.PROPERTIES_IFACE)
1234
self.attributes[propname] = value
1235
def Get(self, interface, propname, dbus_interface):
1236
testcase.assertEqual(interface, client_dbus_interface)
1237
testcase.assertEqual(dbus_interface,
1238
dbus.PROPERTIES_IFACE)
1239
return self.attributes[propname]
1240
def Approve(self, approve, dbus_interface):
1241
testcase.assertEqual(dbus_interface,
1242
client_dbus_interface)
1243
self.calls.append(("Approve", (approve,
1245
self.client = MockClient(
1247
KeyID=("92ed150794387c03ce684574b1139a65"
1248
"94a34f895daaaf09fd8ea90a27cddb12"),
1250
Host="foo.example.org",
1251
Enabled=dbus.Boolean(True),
1253
LastCheckedOK="2019-02-03T00:00:00",
1254
Created="2019-01-02T00:00:00",
1256
Fingerprint=("778827225BA7DE539C5A"
1257
"7CFA59CFF7CDBD9A5920"),
1258
CheckerRunning=dbus.Boolean(False),
1259
LastEnabled="2019-01-03T00:00:00",
1260
ApprovalPending=dbus.Boolean(False),
1261
ApprovedByDefault=dbus.Boolean(True),
1262
LastApprovalRequest="",
1264
ApprovalDuration=1000,
1265
Checker="fping -q -- %(host)s",
1266
ExtendedTimeout=900000,
1267
Expires="2019-02-04T00:00:00",
1268
LastCheckerStatus=0)
1269
self.other_client = MockClient(
1271
KeyID=("0558568eedd67d622f5c83b35a115f79"
1272
"6ab612cff5ad227247e46c2b020f441c"),
1273
Secret=b"secretbar",
1275
Enabled=dbus.Boolean(True),
1277
LastCheckedOK="2019-02-04T00:00:00",
1278
Created="2019-01-03T00:00:00",
1280
Fingerprint=("3E393AEAEFB84C7E89E2"
1281
"F547B3A107558FCA3A27"),
1282
CheckerRunning=dbus.Boolean(True),
1283
LastEnabled="2019-01-04T00:00:00",
1284
ApprovalPending=dbus.Boolean(False),
1285
ApprovedByDefault=dbus.Boolean(False),
1286
LastApprovalRequest="2019-01-03T00:00:00",
1287
ApprovalDelay=30000,
1288
ApprovalDuration=93785000,
1290
ExtendedTimeout=900000,
1291
Expires="2019-02-05T00:00:00",
1292
LastCheckerStatus=-2)
1293
self.clients = collections.OrderedDict(
1295
("/clients/foo", self.client.attributes),
1296
("/clients/barbar", self.other_client.attributes),
1298
self.one_client = {"/clients/foo": self.client.attributes}
1304
def get_object(client_bus_name, path):
1305
self.assertEqual(client_bus_name, dbus_busname)
1307
# Note: "self" here is the TestCmd instance, not
1308
# the Bus instance, since this is a static method!
1309
"/clients/foo": self.client,
1310
"/clients/barbar": self.other_client,
1315
class TestIsEnabledCmd(TestCmd):
1316
def test_is_enabled(self):
1317
self.assertTrue(all(IsEnabledCmd().is_enabled(client,
1319
for client, properties
1320
in self.clients.items()))
1322
def test_is_enabled_run_exits_successfully(self):
1323
with self.assertRaises(SystemExit) as e:
1324
IsEnabledCmd().run(self.one_client)
1325
if e.exception.code is not None:
1326
self.assertEqual(e.exception.code, 0)
1328
self.assertIsNone(e.exception.code)
1330
def test_is_enabled_run_exits_with_failure(self):
1331
self.client.attributes["Enabled"] = dbus.Boolean(False)
1332
with self.assertRaises(SystemExit) as e:
1333
IsEnabledCmd().run(self.one_client)
1334
if isinstance(e.exception.code, int):
1335
self.assertNotEqual(e.exception.code, 0)
1337
self.assertIsNotNone(e.exception.code)
1340
class TestApproveCmd(TestCmd):
1341
def test_approve(self):
1342
ApproveCmd().run(self.clients, self.bus)
1343
for clientpath in self.clients:
1344
client = self.bus.get_object(dbus_busname, clientpath)
1345
self.assertIn(("Approve", (True, client_dbus_interface)),
1349
class TestDenyCmd(TestCmd):
1350
def test_deny(self):
1351
DenyCmd().run(self.clients, self.bus)
1352
for clientpath in self.clients:
1353
client = self.bus.get_object(dbus_busname, clientpath)
1354
self.assertIn(("Approve", (False, client_dbus_interface)),
1358
class TestRemoveCmd(TestCmd):
1359
def test_remove(self):
1360
class MockMandos(object):
1363
def RemoveClient(self, dbus_path):
1364
self.calls.append(("RemoveClient", (dbus_path,)))
1365
mandos = MockMandos()
1366
super(TestRemoveCmd, self).setUp()
1367
RemoveCmd().run(self.clients, self.bus, mandos)
1368
self.assertEqual(len(mandos.calls), 2)
1369
for clientpath in self.clients:
1370
self.assertIn(("RemoveClient", (clientpath,)),
1374
class TestDumpJSONCmd(TestCmd):
1376
self.expected_json = {
1379
"KeyID": ("92ed150794387c03ce684574b1139a65"
1380
"94a34f895daaaf09fd8ea90a27cddb12"),
1381
"Host": "foo.example.org",
1384
"LastCheckedOK": "2019-02-03T00:00:00",
1385
"Created": "2019-01-02T00:00:00",
1387
"Fingerprint": ("778827225BA7DE539C5A"
1388
"7CFA59CFF7CDBD9A5920"),
1389
"CheckerRunning": False,
1390
"LastEnabled": "2019-01-03T00:00:00",
1391
"ApprovalPending": False,
1392
"ApprovedByDefault": True,
1393
"LastApprovalRequest": "",
1395
"ApprovalDuration": 1000,
1396
"Checker": "fping -q -- %(host)s",
1397
"ExtendedTimeout": 900000,
1398
"Expires": "2019-02-04T00:00:00",
1399
"LastCheckerStatus": 0,
1403
"KeyID": ("0558568eedd67d622f5c83b35a115f79"
1404
"6ab612cff5ad227247e46c2b020f441c"),
1405
"Host": "192.0.2.3",
1408
"LastCheckedOK": "2019-02-04T00:00:00",
1409
"Created": "2019-01-03T00:00:00",
1411
"Fingerprint": ("3E393AEAEFB84C7E89E2"
1412
"F547B3A107558FCA3A27"),
1413
"CheckerRunning": True,
1414
"LastEnabled": "2019-01-04T00:00:00",
1415
"ApprovalPending": False,
1416
"ApprovedByDefault": False,
1417
"LastApprovalRequest": "2019-01-03T00:00:00",
1418
"ApprovalDelay": 30000,
1419
"ApprovalDuration": 93785000,
1421
"ExtendedTimeout": 900000,
1422
"Expires": "2019-02-05T00:00:00",
1423
"LastCheckerStatus": -2,
747
class Test_TableOfClients(unittest.TestCase):
753
"Bool": "A D-BUS Boolean",
754
"NonDbusBoolean": "A Non-D-BUS Boolean",
755
"Integer": "An Integer",
756
"Timeout": "Timedelta 1",
757
"Interval": "Timedelta 2",
758
"ApprovalDelay": "Timedelta 3",
759
"ApprovalDuration": "Timedelta 4",
760
"ExtendedTimeout": "Timedelta 5",
761
"String": "A String",
1426
return super(TestDumpJSONCmd, self).setUp()
1428
def test_normal(self):
1429
output = DumpJSONCmd().output(self.clients.values())
1430
json_data = json.loads(output)
1431
self.assertDictEqual(json_data, self.expected_json)
1433
def test_one_client(self):
1434
output = DumpJSONCmd().output(self.one_client.values())
1435
json_data = json.loads(output)
1436
expected_json = {"foo": self.expected_json["foo"]}
1437
self.assertDictEqual(json_data, expected_json)
1440
class TestPrintTableCmd(TestCmd):
1441
def test_normal(self):
1442
output = PrintTableCmd().output(self.clients.values())
1443
expected_output = "\n".join((
1444
"Name Enabled Timeout Last Successful Check",
1445
"foo Yes 00:05:00 2019-02-03T00:00:00 ",
1446
"barbar Yes 00:05:00 2019-02-04T00:00:00 ",
1448
self.assertEqual(output, expected_output)
1450
def test_verbose(self):
1451
output = PrintTableCmd(verbose=True).output(
1452
self.clients.values())
1467
"Last Successful Check ",
1468
"2019-02-03T00:00:00 ",
1469
"2019-02-04T00:00:00 ",
1472
"2019-01-02T00:00:00 ",
1473
"2019-01-03T00:00:00 ",
1485
("92ed150794387c03ce684574b1139a6594a34f895daaaf09fd8"
1487
("0558568eedd67d622f5c83b35a115f796ab612cff5ad227247e"
1491
"778827225BA7DE539C5A7CFA59CFF7CDBD9A5920 ",
1492
"3E393AEAEFB84C7E89E2F547B3A107558FCA3A27 ",
1494
"Check Is Running ",
1499
"2019-01-03T00:00:00 ",
1500
"2019-01-04T00:00:00 ",
1502
"Approval Is Pending ",
1506
"Approved By Default ",
1510
"Last Approval Request ",
1512
"2019-01-03T00:00:00 ",
1518
"Approval Duration ",
1523
"fping -q -- %(host)s ",
1526
"Extended Timeout ",
1531
"2019-02-04T00:00:00 ",
1532
"2019-02-05T00:00:00 ",
1534
"Last Checker Status",
1539
num_lines = max(len(rows) for rows in columns)
1540
expected_output = "\n".join("".join(rows[line]
1541
for rows in columns)
1542
for line in range(num_lines))
1543
self.assertEqual(output, expected_output)
1545
def test_one_client(self):
1546
output = PrintTableCmd().output(self.one_client.values())
1547
expected_output = "\n".join((
1548
"Name Enabled Timeout Last Successful Check",
1549
"foo Yes 00:05:00 2019-02-03T00:00:00 ",
1551
self.assertEqual(output, expected_output)
1554
class TestPropertyCmd(TestCmd):
1555
"""Abstract class for tests of PropertyCmd classes"""
1557
if not hasattr(self, "command"):
1559
values_to_get = getattr(self, "values_to_get",
1561
for value_to_set, value_to_get in zip(self.values_to_set,
1563
for clientpath in self.clients:
1564
client = self.bus.get_object(dbus_busname, clientpath)
1565
old_value = client.attributes[self.propname]
1566
self.assertNotIsInstance(old_value, self.Unique)
1567
client.attributes[self.propname] = self.Unique()
1568
self.run_command(value_to_set, self.clients)
1569
for clientpath in self.clients:
1570
client = self.bus.get_object(dbus_busname, clientpath)
1571
value = client.attributes[self.propname]
1572
self.assertNotIsInstance(value, self.Unique)
1573
self.assertEqual(value, value_to_get)
1575
class Unique(object):
1576
"""Class for objects which exist only to be unique objects,
1577
since unittest.mock.sentinel only exists in Python 3.3"""
1579
def run_command(self, value, clients):
1580
self.command().run(clients, self.bus)
1583
class TestEnableCmd(TestPropertyCmd):
1585
propname = "Enabled"
1586
values_to_set = [dbus.Boolean(True)]
1589
class TestDisableCmd(TestPropertyCmd):
1590
command = DisableCmd
1591
propname = "Enabled"
1592
values_to_set = [dbus.Boolean(False)]
1595
class TestBumpTimeoutCmd(TestPropertyCmd):
1596
command = BumpTimeoutCmd
1597
propname = "LastCheckedOK"
1598
values_to_set = [""]
1601
class TestStartCheckerCmd(TestPropertyCmd):
1602
command = StartCheckerCmd
1603
propname = "CheckerRunning"
1604
values_to_set = [dbus.Boolean(True)]
1607
class TestStopCheckerCmd(TestPropertyCmd):
1608
command = StopCheckerCmd
1609
propname = "CheckerRunning"
1610
values_to_set = [dbus.Boolean(False)]
1613
class TestApproveByDefaultCmd(TestPropertyCmd):
1614
command = ApproveByDefaultCmd
1615
propname = "ApprovedByDefault"
1616
values_to_set = [dbus.Boolean(True)]
1619
class TestDenyByDefaultCmd(TestPropertyCmd):
1620
command = DenyByDefaultCmd
1621
propname = "ApprovedByDefault"
1622
values_to_set = [dbus.Boolean(False)]
1625
class TestPropertyValueCmd(TestPropertyCmd):
1626
"""Abstract class for tests of PropertyValueCmd classes"""
1629
if type(self) is TestPropertyValueCmd:
1631
return super(TestPropertyValueCmd, self).runTest()
1633
def run_command(self, value, clients):
1634
self.command(value).run(clients, self.bus)
1637
class TestSetCheckerCmd(TestPropertyValueCmd):
1638
command = SetCheckerCmd
1639
propname = "Checker"
1640
values_to_set = ["", ":", "fping -q -- %s"]
1643
class TestSetHostCmd(TestPropertyValueCmd):
1644
command = SetHostCmd
1646
values_to_set = ["192.0.2.3", "foo.example.org"]
1649
class TestSetSecretCmd(TestPropertyValueCmd):
1650
command = SetSecretCmd
1652
values_to_set = [io.BytesIO(b""),
1653
io.BytesIO(b"secret\0xyzzy\nbar")]
1654
values_to_get = [b"", b"secret\0xyzzy\nbar"]
1657
class TestSetTimeoutCmd(TestPropertyValueCmd):
1658
command = SetTimeoutCmd
1659
propname = "Timeout"
1660
values_to_set = [datetime.timedelta(),
1661
datetime.timedelta(minutes=5),
1662
datetime.timedelta(seconds=1),
1663
datetime.timedelta(weeks=1),
1664
datetime.timedelta(weeks=52)]
1665
values_to_get = [0, 300000, 1000, 604800000, 31449600000]
1668
class TestSetExtendedTimeoutCmd(TestPropertyValueCmd):
1669
command = SetExtendedTimeoutCmd
1670
propname = "ExtendedTimeout"
1671
values_to_set = [datetime.timedelta(),
1672
datetime.timedelta(minutes=5),
1673
datetime.timedelta(seconds=1),
1674
datetime.timedelta(weeks=1),
1675
datetime.timedelta(weeks=52)]
1676
values_to_get = [0, 300000, 1000, 604800000, 31449600000]
1679
class TestSetIntervalCmd(TestPropertyValueCmd):
1680
command = SetIntervalCmd
1681
propname = "Interval"
1682
values_to_set = [datetime.timedelta(),
1683
datetime.timedelta(minutes=5),
1684
datetime.timedelta(seconds=1),
1685
datetime.timedelta(weeks=1),
1686
datetime.timedelta(weeks=52)]
1687
values_to_get = [0, 300000, 1000, 604800000, 31449600000]
1690
class TestSetApprovalDelayCmd(TestPropertyValueCmd):
1691
command = SetApprovalDelayCmd
1692
propname = "ApprovalDelay"
1693
values_to_set = [datetime.timedelta(),
1694
datetime.timedelta(minutes=5),
1695
datetime.timedelta(seconds=1),
1696
datetime.timedelta(weeks=1),
1697
datetime.timedelta(weeks=52)]
1698
values_to_get = [0, 300000, 1000, 604800000, 31449600000]
1701
class TestSetApprovalDurationCmd(TestPropertyValueCmd):
1702
command = SetApprovalDurationCmd
1703
propname = "ApprovalDuration"
1704
values_to_set = [datetime.timedelta(),
1705
datetime.timedelta(minutes=5),
1706
datetime.timedelta(seconds=1),
1707
datetime.timedelta(weeks=1),
1708
datetime.timedelta(weeks=52)]
1709
values_to_get = [0, 300000, 1000, 604800000, 31449600000]
763
self.keywords = ["Attr1", "AttrTwo"]
769
"Bool": dbus.Boolean(False),
770
"NonDbusBoolean": False,
774
"ApprovalDelay": 2000,
775
"ApprovalDuration": 3000,
776
"ExtendedTimeout": 4000,
783
"Bool": dbus.Boolean(True),
784
"NonDbusBoolean": True,
787
"Interval": 93786000,
788
"ApprovalDelay": 93787000,
789
"ApprovalDuration": 93788000,
790
"ExtendedTimeout": 93789000,
791
"String": "A huge string which will not fit," * 10,
794
def test_short_header(self):
795
text = str(TableOfClients(self.clients, self.keywords,
802
self.assertEqual(text, expected_text)
803
def test_booleans(self):
804
keywords = ["Bool", "NonDbusBoolean"]
805
text = str(TableOfClients(self.clients, keywords,
808
A D-BUS Boolean A Non-D-BUS Boolean
812
self.assertEqual(text, expected_text)
813
def test_milliseconds_detection(self):
814
keywords = ["Integer", "Timeout", "Interval", "ApprovalDelay",
815
"ApprovalDuration", "ExtendedTimeout"]
816
text = str(TableOfClients(self.clients, keywords,
819
An Integer Timedelta 1 Timedelta 2 Timedelta 3 Timedelta 4 Timedelta 5
820
0 00:00:00 00:00:01 00:00:02 00:00:03 00:00:04
821
1 1T02:03:05 1T02:03:06 1T02:03:07 1T02:03:08 1T02:03:09
823
self.assertEqual(text, expected_text)
824
def test_empty_and_long_string_values(self):
825
keywords = ["String"]
826
text = str(TableOfClients(self.clients, keywords,
831
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,
833
self.assertEqual(text, expected_text)