274
## Classes for commands.
276
# Abstract classes first
277
class Command(object):
278
"""Abstract class for commands"""
279
def run(self, mandos, clients):
280
"""Normal commands should implement run_on_one_client(), but
281
commands which want to operate on all clients at the same time
282
can override this run() method instead."""
284
for client, properties in clients.items():
285
self.run_on_one_client(client, properties)
287
class PrintCmd(Command):
288
"""Abstract class for commands printing client details"""
289
all_keywords = ("Name", "Enabled", "Timeout", "LastCheckedOK",
290
"Created", "Interval", "Host", "KeyID",
291
"Fingerprint", "CheckerRunning", "LastEnabled",
292
"ApprovalPending", "ApprovedByDefault",
293
"LastApprovalRequest", "ApprovalDelay",
294
"ApprovalDuration", "Checker", "ExtendedTimeout",
295
"Expires", "LastCheckerStatus")
296
def run(self, mandos, clients):
297
print(self.output(clients))
299
class PropertyCmd(Command):
300
"""Abstract class for Actions for setting one client property"""
301
def run_on_one_client(self, client, properties):
302
"""Set the Client's D-Bus property"""
303
log.debug("D-Bus: %s:%s:%s.Set(%r, %r, %r)", busname,
304
client.__dbus_object_path__,
305
dbus.PROPERTIES_IFACE, client_interface,
306
self.property, self.value_to_set
307
if not isinstance(self.value_to_set, dbus.Boolean)
308
else bool(self.value_to_set))
309
client.Set(client_interface, self.property, self.value_to_set,
310
dbus_interface=dbus.PROPERTIES_IFACE)
312
class ValueArgumentMixIn(object):
313
"""Mixin class for commands taking a value as argument"""
314
def __init__(self, value):
315
self.value_to_set = value
317
class MillisecondsValueArgumentMixIn(ValueArgumentMixIn):
318
"""Mixin class for commands taking a value argument as
321
def value_to_set(self):
324
def value_to_set(self, value):
325
"""When setting, convert value to a datetime.timedelta"""
326
self._vts = int(round(value.total_seconds() * 1000))
328
# Actual (non-abstract) command classes
330
class PrintTableCmd(PrintCmd):
331
def __init__(self, verbose=False):
332
self.verbose = verbose
334
def output(self, clients):
335
default_keywords = ("Name", "Enabled", "Timeout", "LastCheckedOK")
336
keywords = default_keywords
338
keywords = self.all_keywords
339
return str(self.TableOfClients(clients.values(), keywords))
341
class TableOfClients(object):
344
"Enabled": "Enabled",
345
"Timeout": "Timeout",
346
"LastCheckedOK": "Last Successful Check",
347
"LastApprovalRequest": "Last Approval Request",
348
"Created": "Created",
349
"Interval": "Interval",
351
"Fingerprint": "Fingerprint",
353
"CheckerRunning": "Check Is Running",
354
"LastEnabled": "Last Enabled",
355
"ApprovalPending": "Approval Is Pending",
356
"ApprovedByDefault": "Approved By Default",
357
"ApprovalDelay": "Approval Delay",
358
"ApprovalDuration": "Approval Duration",
359
"Checker": "Checker",
360
"ExtendedTimeout": "Extended Timeout",
361
"Expires": "Expires",
362
"LastCheckerStatus": "Last Checker Status",
365
def __init__(self, clients, keywords, tableheaders=None):
366
self.clients = clients
367
self.keywords = keywords
368
if tableheaders is not None:
369
self.tableheaders = tableheaders
372
return "\n".join(self.rows())
374
if sys.version_info.major == 2:
375
__unicode__ = __str__
377
return str(self).encode(locale.getpreferredencoding())
380
format_string = self.row_formatting_string()
381
rows = [self.header_line(format_string)]
382
rows.extend(self.client_line(client, format_string)
383
for client in self.clients)
386
def row_formatting_string(self):
387
"Format string used to format table rows"
388
return " ".join("{{{key}:{width}}}".format(
389
width=max(len(self.tableheaders[key]),
390
*(len(self.string_from_client(client, key))
391
for client in self.clients)),
393
for key in self.keywords)
395
def string_from_client(self, client, key):
396
return self.valuetostring(client[key], key)
399
def valuetostring(value, keyword):
400
if isinstance(value, dbus.Boolean):
401
return "Yes" if value else "No"
402
if keyword in ("Timeout", "Interval", "ApprovalDelay",
403
"ApprovalDuration", "ExtendedTimeout"):
404
return milliseconds_to_string(value)
407
def header_line(self, format_string):
408
return format_string.format(**self.tableheaders)
410
def client_line(self, client, format_string):
411
return format_string.format(
412
**{key: self.string_from_client(client, key)
413
for key in self.keywords})
417
class DumpJSONCmd(PrintCmd):
418
def output(self, clients):
419
data = {client["Name"]:
420
{key: self.dbus_boolean_to_bool(client[key])
421
for key in self.all_keywords}
422
for client in clients.values()}
423
return json.dumps(data, indent=4, separators=(',', ': '))
425
def dbus_boolean_to_bool(value):
426
if isinstance(value, dbus.Boolean):
430
class IsEnabledCmd(Command):
431
def run_on_one_client(self, client, properties):
432
if self.is_enabled(client, properties):
435
def is_enabled(self, client, properties):
436
return bool(properties["Enabled"])
438
class RemoveCmd(Command):
439
def run_on_one_client(self, client, properties):
440
log.debug("D-Bus: %s:%s:%s.RemoveClient(%r)", busname,
441
server_path, server_interface,
442
str(client.__dbus_object_path__))
443
self.mandos.RemoveClient(client.__dbus_object_path__)
445
class ApproveCmd(Command):
446
def run_on_one_client(self, client, properties):
447
log.debug("D-Bus: %s:%s.Approve(True)",
448
client.__dbus_object_path__, client_interface)
449
client.Approve(dbus.Boolean(True),
450
dbus_interface=client_interface)
452
class DenyCmd(Command):
453
def run_on_one_client(self, client, properties):
454
log.debug("D-Bus: %s:%s.Approve(False)",
455
client.__dbus_object_path__, client_interface)
456
client.Approve(dbus.Boolean(False),
457
dbus_interface=client_interface)
459
class EnableCmd(PropertyCmd):
461
value_to_set = dbus.Boolean(True)
463
class DisableCmd(PropertyCmd):
465
value_to_set = dbus.Boolean(False)
467
class BumpTimeoutCmd(PropertyCmd):
468
property = "LastCheckedOK"
471
class StartCheckerCmd(PropertyCmd):
472
property = "CheckerRunning"
473
value_to_set = dbus.Boolean(True)
475
class StopCheckerCmd(PropertyCmd):
476
property = "CheckerRunning"
477
value_to_set = dbus.Boolean(False)
479
class ApproveByDefaultCmd(PropertyCmd):
480
property = "ApprovedByDefault"
481
value_to_set = dbus.Boolean(True)
483
class DenyByDefaultCmd(PropertyCmd):
484
property = "ApprovedByDefault"
485
value_to_set = dbus.Boolean(False)
487
class SetCheckerCmd(PropertyCmd, ValueArgumentMixIn):
490
class SetHostCmd(PropertyCmd, ValueArgumentMixIn):
493
class SetSecretCmd(PropertyCmd, ValueArgumentMixIn):
495
def value_to_set(self):
498
def value_to_set(self, value):
499
"""When setting, read data from supplied file object"""
500
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"
522
def add_command_line_options(parser):
523
parser.add_argument("--version", action="version",
524
version="%(prog)s {}".format(version),
525
help="show version number and exit")
526
parser.add_argument("-a", "--all", action="store_true",
527
help="Select all clients")
528
parser.add_argument("-v", "--verbose", action="store_true",
529
help="Print all fields")
530
parser.add_argument("-j", "--dump-json", action="store_true",
531
help="Dump client data in JSON format")
532
enable_disable = parser.add_mutually_exclusive_group()
533
enable_disable.add_argument("-e", "--enable", action="store_true",
534
help="Enable client")
535
enable_disable.add_argument("-d", "--disable",
537
help="disable client")
538
parser.add_argument("-b", "--bump-timeout", action="store_true",
539
help="Bump timeout for client")
540
start_stop_checker = parser.add_mutually_exclusive_group()
541
start_stop_checker.add_argument("--start-checker",
543
help="Start checker for client")
544
start_stop_checker.add_argument("--stop-checker",
546
help="Stop checker for client")
547
parser.add_argument("-V", "--is-enabled", action="store_true",
548
help="Check if client is enabled")
549
parser.add_argument("-r", "--remove", action="store_true",
550
help="Remove client")
551
parser.add_argument("-c", "--checker",
552
help="Set checker command for client")
553
parser.add_argument("-t", "--timeout", type=string_to_delta,
554
help="Set timeout for client")
555
parser.add_argument("--extended-timeout", type=string_to_delta,
556
help="Set extended timeout for client")
557
parser.add_argument("-i", "--interval", type=string_to_delta,
558
help="Set checker interval for client")
559
approve_deny_default = parser.add_mutually_exclusive_group()
560
approve_deny_default.add_argument(
561
"--approve-by-default", action="store_true",
562
default=None, dest="approved_by_default",
563
help="Set client to be approved by default")
564
approve_deny_default.add_argument(
565
"--deny-by-default", action="store_false",
566
dest="approved_by_default",
567
help="Set client to be denied by default")
568
parser.add_argument("--approval-delay", type=string_to_delta,
569
help="Set delay before client approve/deny")
570
parser.add_argument("--approval-duration", type=string_to_delta,
571
help="Set duration of one client approval")
572
parser.add_argument("-H", "--host", help="Set host for client")
573
parser.add_argument("-s", "--secret",
574
type=argparse.FileType(mode="rb"),
575
help="Set password blob (file) for client")
576
approve_deny = parser.add_mutually_exclusive_group()
577
approve_deny.add_argument(
578
"-A", "--approve", action="store_true",
579
help="Approve any current client request")
580
approve_deny.add_argument("-D", "--deny", action="store_true",
581
help="Deny any current client request")
582
parser.add_argument("--debug", action="store_true",
583
help="Debug mode (show D-Bus commands)")
584
parser.add_argument("--check", action="store_true",
585
help="Run self-test")
586
parser.add_argument("client", nargs="*", help="Client name")
589
def commands_from_options(options):
593
if options.dump_json:
594
commands.append(DumpJSONCmd())
597
commands.append(EnableCmd())
600
commands.append(DisableCmd())
602
if options.bump_timeout:
603
commands.append(BumpTimeoutCmd())
605
if options.start_checker:
606
commands.append(StartCheckerCmd())
608
if options.stop_checker:
609
commands.append(StopCheckerCmd())
611
if options.is_enabled:
612
commands.append(IsEnabledCmd())
614
if options.checker is not None:
615
commands.append(SetCheckerCmd(options.checker))
617
if options.timeout is not None:
618
commands.append(SetTimeoutCmd(options.timeout))
620
if options.extended_timeout:
622
SetExtendedTimeoutCmd(options.extended_timeout))
624
if options.interval is not None:
625
commands.append(SetIntervalCmd(options.interval))
627
if options.approved_by_default is not None:
628
if options.approved_by_default:
629
commands.append(ApproveByDefaultCmd())
631
commands.append(DenyByDefaultCmd())
633
if options.approval_delay is not None:
634
commands.append(SetApprovalDelayCmd(options.approval_delay))
636
if options.approval_duration is not None:
638
SetApprovalDurationCmd(options.approval_duration))
640
if options.host is not None:
641
commands.append(SetHostCmd(options.host))
643
if options.secret is not None:
644
commands.append(SetSecretCmd(options.secret))
647
commands.append(ApproveCmd())
650
commands.append(DenyCmd())
653
commands.append(RemoveCmd())
655
# If no command option has been given, show table of clients,
656
# optionally verbosely
658
commands.append(PrintTableCmd(verbose=options.verbose))
663
441
def check_option_syntax(parser, options):
664
442
"""Apply additional restrictions on options, not expressible in
667
def has_actions(options):
668
return any((options.enable,
670
options.bump_timeout,
671
options.start_checker,
672
options.stop_checker,
675
options.checker is not None,
676
options.timeout is not None,
677
options.extended_timeout is not None,
678
options.interval is not None,
679
options.approved_by_default is not None,
680
options.approval_delay is not None,
681
options.approval_duration is not None,
682
options.host is not None,
683
options.secret is not None,
445
def has_commands(options, commands=None):
447
commands = (command.Enable,
450
command.StartChecker,
456
command.SetExtendedTimeout,
458
command.ApproveByDefault,
459
command.DenyByDefault,
460
command.SetApprovalDelay,
461
command.SetApprovalDuration,
466
return any(isinstance(cmd, commands)
467
for cmd in options.commands)
687
if has_actions(options) and not (options.client or options.all):
469
if has_commands(options) and not (options.client or options.all):
688
470
parser.error("Options require clients names or --all.")
689
if options.verbose and has_actions(options):
471
if options.verbose and has_commands(options):
690
472
parser.error("--verbose can only be used alone.")
691
if options.dump_json and (options.verbose
692
or has_actions(options)):
473
if (has_commands(options, (command.DumpJSON,))
474
and (options.verbose or len(options.commands) > 1)):
693
475
parser.error("--dump-json can only be used alone.")
694
if options.all and not has_actions(options):
476
if options.all and not has_commands(options):
695
477
parser.error("--all requires an action.")
696
if options.is_enabled and len(options.client) > 1:
478
if (has_commands(options, (command.IsEnabled,))
479
and len(options.client) > 1):
697
480
parser.error("--is-enabled requires exactly one client")
701
parser = argparse.ArgumentParser()
703
add_command_line_options(parser)
705
options = parser.parse_args()
707
check_option_syntax(parser, options)
709
clientnames = options.client
712
log.setLevel(logging.DEBUG)
715
bus = dbus.SystemBus()
716
log.debug("D-Bus: Connect to: (name=%r, path=%r)", busname,
718
mandos_dbus_objc = bus.get_object(busname, server_path)
719
except dbus.exceptions.DBusException:
720
log.critical("Could not connect to Mandos server")
723
mandos_serv = dbus.Interface(mandos_dbus_objc,
724
dbus_interface=server_interface)
725
mandos_serv_object_manager = dbus.Interface(
726
mandos_dbus_objc, dbus_interface=dbus.OBJECT_MANAGER_IFACE)
728
# Filter out log message from dbus module
729
dbus_logger = logging.getLogger("dbus.proxies")
730
class NullFilter(logging.Filter):
731
def filter(self, record):
733
dbus_filter = NullFilter()
735
dbus_logger.addFilter(dbus_filter)
736
log.debug("D-Bus: %s:%s:%s.GetManagedObjects()", busname,
737
server_path, dbus.OBJECT_MANAGER_IFACE)
738
mandos_clients = {path: ifs_and_props[client_interface]
739
for path, ifs_and_props in
740
mandos_serv_object_manager
741
.GetManagedObjects().items()
742
if client_interface in ifs_and_props}
743
except dbus.exceptions.DBusException as e:
744
log.critical("Failed to access Mandos server through D-Bus:"
748
# restore dbus logger
749
dbus_logger.removeFilter(dbus_filter)
751
# Compile dict of (clients: properties) to process
755
clients = {bus.get_object(busname, path): properties
756
for path, properties in mandos_clients.items()}
758
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
765
log.critical("Client not found on server: %r", name)
768
# Run all commands on clients
769
commands = commands_from_options(options)
770
for command in commands:
771
command.run(mandos_serv, clients)
481
if (len(options.commands) > 1
482
and has_commands(options, (command.Remove,))
483
and not has_commands(options, (command.Deny,))):
484
parser.error("--remove can only be combined with --deny")
491
object_manager_iface = "org.freedesktop.DBus.ObjectManager"
493
def get_managed_objects(self, busname, objectpath):
494
return self.call_method("GetManagedObjects", busname,
496
self.object_manager_iface)
498
properties_iface = "org.freedesktop.DBus.Properties"
500
def set_property(self, busname, objectpath, interface, key,
502
self.call_method("Set", busname, objectpath,
503
self.properties_iface, interface, key,
506
def call_method(self, methodname, busname, objectpath,
508
raise NotImplementedError()
510
class MandosBus(SystemBus):
511
busname_domain = "se.recompile"
512
busname = busname_domain + ".Mandos"
514
server_interface = busname_domain + ".Mandos"
515
client_interface = busname_domain + ".Mandos.Client"
518
def get_clients_and_properties(self):
519
managed_objects = self.get_managed_objects(
520
self.busname, self.server_path)
521
return {objpath: properties[self.client_interface]
522
for objpath, properties in managed_objects.items()
523
if self.client_interface in properties}
525
def set_client_property(self, objectpath, key, value):
526
return self.set_property(self.busname, objectpath,
527
self.client_interface, key,
530
def call_client_method(self, objectpath, method, *args):
531
return self.call_method(method, self.busname, objectpath,
532
self.client_interface, *args)
534
def call_server_method(self, method, *args):
535
return self.call_method(method, self.busname,
537
self.server_interface, *args)
539
class Error(Exception):
542
class ConnectFailed(Error):
546
class dbus_python_adapter:
548
class SystemBus(dbus.MandosBus):
549
"""Use dbus-python"""
551
def __init__(self, module=dbus_python):
552
self.dbus_python = module
553
self.bus = self.dbus_python.SystemBus()
555
@contextlib.contextmanager
556
def convert_exception(self, exception_class=dbus.Error):
559
except self.dbus_python.exceptions.DBusException as e:
560
# This does what "raise from" would do
561
exc = exception_class(*e.args)
565
def call_method(self, methodname, busname, objectpath,
567
proxy_object = self.get_object(busname, objectpath)
568
log.debug("D-Bus: %s:%s:%s.%s(%s)", busname, objectpath,
569
interface, methodname,
570
", ".join(repr(a) for a in args))
571
method = getattr(proxy_object, methodname)
572
with self.convert_exception():
573
with dbus_python_adapter.SilenceLogger(
575
value = method(*args, dbus_interface=interface)
576
return self.type_filter(value)
578
def get_object(self, busname, objectpath):
579
log.debug("D-Bus: Connect to: (busname=%r, path=%r)",
581
with self.convert_exception(dbus.ConnectFailed):
582
return self.bus.get_object(busname, objectpath)
584
def type_filter(self, value):
585
"""Convert the most bothersome types to Python types"""
586
if isinstance(value, self.dbus_python.Boolean):
588
if isinstance(value, self.dbus_python.ObjectPath):
590
# Also recurse into dictionaries
591
if isinstance(value, self.dbus_python.Dictionary):
592
return {self.type_filter(key):
593
self.type_filter(subval)
594
for key, subval in value.items()}
597
def set_client_property(self, objectpath, key, value):
599
if not isinstance(value, bytes):
600
value = value.encode("utf-8")
601
value = self.dbus_python.ByteArray(value)
602
return self.set_property(self.busname, objectpath,
603
self.client_interface, key,
607
"Simple context manager to silence a particular logger"
609
def __init__(self, loggername):
610
self.logger = logging.getLogger(loggername)
613
self.logger.addFilter(self.nullfilter)
615
class NullFilter(logging.Filter):
616
def filter(self, record):
619
nullfilter = NullFilter()
621
def __exit__(self, exc_type, exc_val, exc_tb):
622
self.logger.removeFilter(self.nullfilter)
624
class CachingBus(SystemBus):
625
"""A caching layer for dbus_python_adapter.SystemBus"""
627
def __init__(self, *args, **kwargs):
628
self.object_cache = {}
629
super(dbus_python_adapter.CachingBus,
630
self).__init__(*args, **kwargs)
632
def get_object(self, busname, objectpath):
634
return self.object_cache[(busname, objectpath)]
637
dbus_python_adapter.CachingBus,
638
self).get_object(busname, objectpath)
639
self.object_cache[(busname, objectpath)] = new_object
643
class pydbus_adapter:
644
class SystemBus(dbus.MandosBus):
645
def __init__(self, module=pydbus):
647
self.bus = self.pydbus.SystemBus()
649
@contextlib.contextmanager
650
def convert_exception(self, exception_class=dbus.Error):
653
except gi.repository.GLib.Error as e:
654
# This does what "raise from" would do
655
exc = exception_class(*e.args)
659
def call_method(self, methodname, busname, objectpath,
661
proxy_object = self.get(busname, objectpath)
662
log.debug("D-Bus: %s:%s:%s.%s(%s)", busname, objectpath,
663
interface, methodname,
664
", ".join(repr(a) for a in args))
665
method = getattr(proxy_object[interface], methodname)
666
with self.convert_exception():
669
def get(self, busname, objectpath):
670
log.debug("D-Bus: Connect to: (busname=%r, path=%r)",
672
with self.convert_exception(dbus.ConnectFailed):
673
if sys.version_info.major <= 2:
674
with warnings.catch_warnings():
675
warnings.filterwarnings(
676
"ignore", "", DeprecationWarning,
677
r"^xml\.etree\.ElementTree$")
678
return self.bus.get(busname, objectpath)
680
return self.bus.get(busname, objectpath)
682
def set_property(self, busname, objectpath, interface, key,
684
proxy_object = self.get(busname, objectpath)
685
log.debug("D-Bus: %s:%s:%s.Set(%r, %r, %r)", busname,
686
objectpath, self.properties_iface, interface,
688
setattr(proxy_object[interface], key, value)
690
class CachingBus(SystemBus):
691
"""A caching layer for pydbus_adapter.SystemBus"""
693
def __init__(self, *args, **kwargs):
694
self.object_cache = {}
695
super(pydbus_adapter.CachingBus,
696
self).__init__(*args, **kwargs)
698
def get(self, busname, objectpath):
700
return self.object_cache[(busname, objectpath)]
702
new_object = (super(pydbus_adapter.CachingBus, self)
703
.get(busname, objectpath))
704
self.object_cache[(busname, objectpath)] = new_object
708
class dbussy_adapter:
709
class SystemBus(dbus.SystemBus):
712
def __init__(self, dbussy, ravel):
715
self.bus = ravel.system_bus()
717
@contextlib.contextmanager
718
def convert_exception(self, exception_class=dbus.Error):
721
except self.dbussy.DBusError as e:
722
# This does what "raise from" would do
723
exc = exception_class(*e.args)
727
def call_method(self, methodname, busname, objectpath,
729
proxy_object = self.get_object(busname, objectpath)
730
log.debug("D-Bus: %s:%s:%s.%s(%s)", busname, objectpath,
731
interface, methodname,
732
", ".join(repr(a) for a in args))
733
iface = proxy_object.get_interface(interface)
734
method = getattr(iface, methodname)
735
with self.convert_exception(dbus.Error):
736
value = method(*args)
737
# DBussy returns values either as an empty list or as a
738
# list of one element with the return value
740
return self.type_filter(value[0])
742
def get_object(self, busname, objectpath):
743
log.debug("D-Bus: Connect to: (busname=%r, path=%r)",
745
with self.convert_exception(dbus.ConnectFailed):
746
return self.bus[busname][objectpath]
748
def type_filter(self, value):
749
"""Convert the most bothersome types to Python types"""
750
# A D-Bus Variant value is represented as the Python type
751
# Tuple[dbussy.DBUS.Signature, Any]
752
if isinstance(value, tuple):
754
and isinstance(value[0],
755
self.dbussy.DBUS.Signature)):
756
return self.type_filter(value[1])
757
elif isinstance(value, self.dbussy.DBUS.ObjectPath):
759
# Also recurse into dictionaries
760
elif isinstance(value, dict):
761
return {self.type_filter(key):
762
self.type_filter(subval)
763
for key, subval in value.items()}
766
def set_property(self, busname, objectpath, interface, key,
768
proxy_object = self.get_object(busname, objectpath)
769
log.debug("D-Bus: %s:%s:%s.Set(%r, %r, %r)", busname,
770
objectpath, self.properties_iface, interface,
773
# DBussy wants a Byte Array to be a sequence of
774
# values, not a byte string
776
setattr(proxy_object.get_interface(interface), key, value)
778
class MandosBus(SystemBus, dbus.MandosBus):
781
class CachingBus(MandosBus):
782
"""A caching layer for dbussy_adapter.MandosBus"""
784
def __init__(self, *args, **kwargs):
785
self.object_cache = {}
786
super(dbussy_adapter.CachingBus, self).__init__(*args,
789
def get_object(self, busname, objectpath):
791
return self.object_cache[(busname, objectpath)]
794
dbussy_adapter.CachingBus,
795
self).get_object(busname, objectpath)
796
self.object_cache[(busname, objectpath)] = new_object
800
def commands_from_options(options):
802
commands = list(options.commands)
804
def find_cmd(cmd, commands):
806
for i, c in enumerate(commands):
807
if isinstance(c, cmd):
811
# If command.Remove is present, move any instances of command.Deny
812
# to occur ahead of command.Remove.
813
index_of_remove = find_cmd(command.Remove, commands)
814
before_remove = commands[:index_of_remove]
815
after_remove = commands[index_of_remove:]
817
for cmd in after_remove:
818
if isinstance(cmd, command.Deny):
819
before_remove.append(cmd)
821
cleaned_after.append(cmd)
822
if cleaned_after != after_remove:
823
commands = before_remove + cleaned_after
825
# If no command option has been given, show table of clients,
826
# optionally verbosely
828
commands.append(command.PrintTable(verbose=options.verbose))
834
"""A namespace for command classes"""
837
"""Abstract base class for commands"""
839
def run(self, clients, bus=None):
840
"""Normal commands should implement run_on_one_client(),
841
but commands which want to operate on all clients at the same time can
842
override this run() method instead.
845
for client, properties in clients.items():
846
self.run_on_one_client(client, properties)
848
class IsEnabled(Base):
849
def run(self, clients, bus=None):
850
properties = next(iter(clients.values()))
851
if properties["Enabled"]:
856
def run_on_one_client(self, client, properties):
857
self.bus.call_client_method(client, "Approve", True)
860
def run_on_one_client(self, client, properties):
861
self.bus.call_client_method(client, "Approve", False)
864
def run(self, clients, bus):
865
for clientpath in frozenset(clients.keys()):
866
bus.call_server_method("RemoveClient", clientpath)
869
"""Abstract class for commands outputting client details"""
870
all_keywords = ("Name", "Enabled", "Timeout", "LastCheckedOK",
871
"Created", "Interval", "Host", "KeyID",
872
"Fingerprint", "CheckerRunning",
873
"LastEnabled", "ApprovalPending",
874
"ApprovedByDefault", "LastApprovalRequest",
875
"ApprovalDelay", "ApprovalDuration",
876
"Checker", "ExtendedTimeout", "Expires",
879
class DumpJSON(Output):
880
def run(self, clients, bus=None):
881
data = {properties["Name"]:
882
{key: properties[key]
883
for key in self.all_keywords}
884
for properties in clients.values()}
885
print(json.dumps(data, indent=4, separators=(",", ": ")))
887
class PrintTable(Output):
888
def __init__(self, verbose=False):
889
self.verbose = verbose
891
def run(self, clients, bus=None):
892
default_keywords = ("Name", "Enabled", "Timeout",
894
keywords = default_keywords
896
keywords = self.all_keywords
897
print(self.TableOfClients(clients.values(), keywords))
899
class TableOfClients:
902
"Enabled": "Enabled",
903
"Timeout": "Timeout",
904
"LastCheckedOK": "Last Successful Check",
905
"LastApprovalRequest": "Last Approval Request",
906
"Created": "Created",
907
"Interval": "Interval",
909
"Fingerprint": "Fingerprint",
911
"CheckerRunning": "Check Is Running",
912
"LastEnabled": "Last Enabled",
913
"ApprovalPending": "Approval Is Pending",
914
"ApprovedByDefault": "Approved By Default",
915
"ApprovalDelay": "Approval Delay",
916
"ApprovalDuration": "Approval Duration",
917
"Checker": "Checker",
918
"ExtendedTimeout": "Extended Timeout",
919
"Expires": "Expires",
920
"LastCheckerStatus": "Last Checker Status",
923
def __init__(self, clients, keywords):
924
self.clients = clients
925
self.keywords = keywords
928
return "\n".join(self.rows())
930
if sys.version_info.major == 2:
931
__unicode__ = __str__
934
return str(self).encode(
935
locale.getpreferredencoding())
938
format_string = self.row_formatting_string()
939
rows = [self.header_line(format_string)]
940
rows.extend(self.client_line(client, format_string)
941
for client in self.clients)
944
def row_formatting_string(self):
945
"Format string used to format table rows"
946
return " ".join("{{{key}:{width}}}".format(
947
width=max(len(self.tableheaders[key]),
948
*(len(self.string_from_client(client,
950
for client in self.clients)),
952
for key in self.keywords)
954
def string_from_client(self, client, key):
955
return self.valuetostring(client[key], key)
958
def valuetostring(cls, value, keyword):
959
if isinstance(value, bool):
960
return "Yes" if value else "No"
961
if keyword in ("Timeout", "Interval", "ApprovalDelay",
962
"ApprovalDuration", "ExtendedTimeout"):
963
return cls.milliseconds_to_string(value)
966
def header_line(self, format_string):
967
return format_string.format(**self.tableheaders)
969
def client_line(self, client, format_string):
970
return format_string.format(
971
**{key: self.string_from_client(client, key)
972
for key in self.keywords})
975
def milliseconds_to_string(ms):
976
td = datetime.timedelta(0, 0, 0, ms)
977
return ("{days}{hours:02}:{minutes:02}:{seconds:02}"
978
.format(days="{}T".format(td.days)
980
hours=td.seconds // 3600,
981
minutes=(td.seconds % 3600) // 60,
982
seconds=td.seconds % 60))
984
class PropertySetter(Base):
985
"Abstract class for Actions for setting one client property"
987
def run_on_one_client(self, client, properties=None):
988
"""Set the Client's D-Bus property"""
989
self.bus.set_client_property(client, self.propname,
994
raise NotImplementedError()
996
class Enable(PropertySetter):
1000
class Disable(PropertySetter):
1001
propname = "Enabled"
1002
value_to_set = False
1004
class BumpTimeout(PropertySetter):
1005
propname = "LastCheckedOK"
1008
class StartChecker(PropertySetter):
1009
propname = "CheckerRunning"
1012
class StopChecker(PropertySetter):
1013
propname = "CheckerRunning"
1014
value_to_set = False
1016
class ApproveByDefault(PropertySetter):
1017
propname = "ApprovedByDefault"
1020
class DenyByDefault(PropertySetter):
1021
propname = "ApprovedByDefault"
1022
value_to_set = False
1024
class PropertySetterValue(PropertySetter):
1025
"""Abstract class for PropertySetter recieving a value as
1026
constructor argument instead of a class attribute."""
1028
def __init__(self, value):
1029
self.value_to_set = value
1032
def argparse(cls, argtype):
1034
return cls(argtype(arg))
1037
class SetChecker(PropertySetterValue):
1038
propname = "Checker"
1040
class SetHost(PropertySetterValue):
1043
class SetSecret(PropertySetterValue):
1047
def value_to_set(self):
1050
@value_to_set.setter
1051
def value_to_set(self, value):
1052
"""When setting, read data from supplied file object"""
1053
self._vts = value.read()
1056
class PropertySetterValueMilliseconds(PropertySetterValue):
1057
"""Abstract class for PropertySetterValue taking a value
1058
argument as a datetime.timedelta() but should store it as
1062
def value_to_set(self):
1065
@value_to_set.setter
1066
def value_to_set(self, value):
1067
"When setting, convert value from a datetime.timedelta"
1068
self._vts = int(round(value.total_seconds() * 1000))
1070
class SetTimeout(PropertySetterValueMilliseconds):
1071
propname = "Timeout"
1073
class SetExtendedTimeout(PropertySetterValueMilliseconds):
1074
propname = "ExtendedTimeout"
1076
class SetInterval(PropertySetterValueMilliseconds):
1077
propname = "Interval"
1079
class SetApprovalDelay(PropertySetterValueMilliseconds):
1080
propname = "ApprovalDelay"
1082
class SetApprovalDuration(PropertySetterValueMilliseconds):
1083
propname = "ApprovalDuration"
774
class Test_milliseconds_to_string(unittest.TestCase):
776
self.assertEqual(milliseconds_to_string(93785000),
778
def test_no_days(self):
779
self.assertEqual(milliseconds_to_string(7385000), "02:03:05")
780
def test_all_zero(self):
781
self.assertEqual(milliseconds_to_string(0), "00:00:00")
782
def test_no_fractional_seconds(self):
783
self.assertEqual(milliseconds_to_string(400), "00:00:00")
784
self.assertEqual(milliseconds_to_string(900), "00:00:00")
785
self.assertEqual(milliseconds_to_string(1900), "00:00:01")
787
class Test_string_to_delta(unittest.TestCase):
788
def test_handles_basic_rfc3339(self):
789
self.assertEqual(string_to_delta("PT0S"),
790
datetime.timedelta())
791
self.assertEqual(string_to_delta("P0D"),
792
datetime.timedelta())
793
self.assertEqual(string_to_delta("PT1S"),
794
datetime.timedelta(0, 1))
795
self.assertEqual(string_to_delta("PT2H"),
796
datetime.timedelta(0, 7200))
1087
class TestCaseWithAssertLogs(unittest.TestCase):
1088
"""unittest.TestCase.assertLogs only exists in Python 3.4"""
1090
if not hasattr(unittest.TestCase, "assertLogs"):
1091
@contextlib.contextmanager
1092
def assertLogs(self, logger, level=logging.INFO):
1093
capturing_handler = self.CapturingLevelHandler(level)
1094
old_level = logger.level
1095
old_propagate = logger.propagate
1096
logger.addHandler(capturing_handler)
1097
logger.setLevel(level)
1098
logger.propagate = False
1100
yield capturing_handler.watcher
1102
logger.propagate = old_propagate
1103
logger.removeHandler(capturing_handler)
1104
logger.setLevel(old_level)
1105
self.assertGreater(len(capturing_handler.watcher.records),
1108
class CapturingLevelHandler(logging.Handler):
1109
def __init__(self, level, *args, **kwargs):
1110
logging.Handler.__init__(self, *args, **kwargs)
1111
self.watcher = self.LoggingWatcher([], [])
1112
def emit(self, record):
1113
self.watcher.records.append(record)
1114
self.watcher.output.append(self.format(record))
1116
LoggingWatcher = collections.namedtuple("LoggingWatcher",
1122
"""Class for objects which exist only to be unique objects, since
1123
unittest.mock.sentinel only exists in Python 3.3"""
1126
class Test_string_to_delta(TestCaseWithAssertLogs):
1127
# Just test basic RFC 3339 functionality here, the doc string for
1128
# rfc3339_duration_to_delta() already has more comprehensive
1129
# tests, which are run by doctest.
1131
def test_rfc3339_zero_seconds(self):
1132
self.assertEqual(datetime.timedelta(),
1133
string_to_delta("PT0S"))
1135
def test_rfc3339_zero_days(self):
1136
self.assertEqual(datetime.timedelta(), string_to_delta("P0D"))
1138
def test_rfc3339_one_second(self):
1139
self.assertEqual(datetime.timedelta(0, 1),
1140
string_to_delta("PT1S"))
1142
def test_rfc3339_two_hours(self):
1143
self.assertEqual(datetime.timedelta(0, 7200),
1144
string_to_delta("PT2H"))
797
1146
def test_falls_back_to_pre_1_6_1_with_warning(self):
798
# assertLogs only exists in Python 3.4
799
if hasattr(self, "assertLogs"):
800
with self.assertLogs(log, logging.WARNING):
801
value = string_to_delta("2h")
1147
with self.assertLogs(log, logging.WARNING):
1148
value = string_to_delta("2h")
1149
self.assertEqual(datetime.timedelta(0, 7200), value)
1152
class Test_check_option_syntax(unittest.TestCase):
1154
self.parser = argparse.ArgumentParser()
1155
add_command_line_options(self.parser)
1157
def test_actions_requires_client_or_all(self):
1158
for action, value in self.actions.items():
1159
args = self.actionargs(action, value)
1160
with self.assertParseError():
1161
self.parse_args(args)
1163
# This mostly corresponds to the definition from has_commands() in
1164
# check_option_syntax()
1168
"--bump-timeout": None,
1169
"--start-checker": None,
1170
"--stop-checker": None,
1171
"--is-enabled": None,
1174
"--timeout": "PT0S",
1175
"--extended-timeout": "PT0S",
1176
"--interval": "PT0S",
1177
"--approve-by-default": None,
1178
"--deny-by-default": None,
1179
"--approval-delay": "PT0S",
1180
"--approval-duration": "PT0S",
1181
"--host": "hostname",
1182
"--secret": "/dev/null",
1188
def actionargs(action, value, *args):
1189
if value is not None:
1190
return [action, value] + list(args)
803
class WarningFilter(logging.Filter):
804
"""Don't show, but record the presence of, warnings"""
805
def filter(self, record):
806
is_warning = record.levelno >= logging.WARNING
807
self.found = is_warning or getattr(self, "found",
809
return not is_warning
810
warning_filter = WarningFilter()
811
log.addFilter(warning_filter)
1192
return [action] + list(args)
1194
@contextlib.contextmanager
1195
def assertParseError(self):
1196
with self.assertRaises(SystemExit) as e:
1197
with self.redirect_stderr_to_devnull():
1199
# Exit code from argparse is guaranteed to be "2". Reference:
1200
# https://docs.python.org/3/library
1201
# /argparse.html#exiting-methods
1202
self.assertEqual(2, e.exception.code)
1204
def parse_args(self, args):
1205
options = self.parser.parse_args(args)
1206
check_option_syntax(self.parser, options)
1209
@contextlib.contextmanager
1210
def redirect_stderr_to_devnull():
1211
old_stderr = sys.stderr
1212
with contextlib.closing(open(os.devnull, "w")) as null:
813
value = string_to_delta("2h")
815
log.removeFilter(warning_filter)
816
self.assertTrue(getattr(warning_filter, "found", False))
817
self.assertEqual(value, datetime.timedelta(0, 7200))
820
class TestCmd(unittest.TestCase):
1217
sys.stderr = old_stderr
1219
def check_option_syntax(self, options):
1220
check_option_syntax(self.parser, options)
1222
def test_actions_all_conflicts_with_verbose(self):
1223
for action, value in self.actions.items():
1224
args = self.actionargs(action, value, "--all",
1226
with self.assertParseError():
1227
self.parse_args(args)
1229
def test_actions_with_client_conflicts_with_verbose(self):
1230
for action, value in self.actions.items():
1231
args = self.actionargs(action, value, "--verbose",
1233
with self.assertParseError():
1234
self.parse_args(args)
1236
def test_dump_json_conflicts_with_verbose(self):
1237
args = ["--dump-json", "--verbose"]
1238
with self.assertParseError():
1239
self.parse_args(args)
1241
def test_dump_json_conflicts_with_action(self):
1242
for action, value in self.actions.items():
1243
args = self.actionargs(action, value, "--dump-json")
1244
with self.assertParseError():
1245
self.parse_args(args)
1247
def test_all_can_not_be_alone(self):
1249
with self.assertParseError():
1250
self.parse_args(args)
1252
def test_all_is_ok_with_any_action(self):
1253
for action, value in self.actions.items():
1254
args = self.actionargs(action, value, "--all")
1255
self.parse_args(args)
1257
def test_any_action_is_ok_with_one_client(self):
1258
for action, value in self.actions.items():
1259
args = self.actionargs(action, value, "client")
1260
self.parse_args(args)
1262
def test_one_client_with_all_actions_except_is_enabled(self):
1263
for action, value in self.actions.items():
1264
if action == "--is-enabled":
1266
args = self.actionargs(action, value, "client")
1267
self.parse_args(args)
1269
def test_two_clients_with_all_actions_except_is_enabled(self):
1270
for action, value in self.actions.items():
1271
if action == "--is-enabled":
1273
args = self.actionargs(action, value, "client1",
1275
self.parse_args(args)
1277
def test_two_clients_are_ok_with_actions_except_is_enabled(self):
1278
for action, value in self.actions.items():
1279
if action == "--is-enabled":
1281
args = self.actionargs(action, value, "client1",
1283
self.parse_args(args)
1285
def test_is_enabled_fails_without_client(self):
1286
args = ["--is-enabled"]
1287
with self.assertParseError():
1288
self.parse_args(args)
1290
def test_is_enabled_fails_with_two_clients(self):
1291
args = ["--is-enabled", "client1", "client2"]
1292
with self.assertParseError():
1293
self.parse_args(args)
1295
def test_remove_can_only_be_combined_with_action_deny(self):
1296
for action, value in self.actions.items():
1297
if action in {"--remove", "--deny"}:
1299
args = self.actionargs(action, value, "--all",
1301
with self.assertParseError():
1302
self.parse_args(args)
1305
class Test_dbus_exceptions(unittest.TestCase):
1307
def test_dbus_ConnectFailed_is_Error(self):
1308
with self.assertRaises(dbus.Error):
1309
raise dbus.ConnectFailed()
1312
class Test_dbus_MandosBus(unittest.TestCase):
1314
class MockMandosBus(dbus.MandosBus):
1316
self._name = "se.recompile.Mandos"
1317
self._server_path = "/"
1318
self._server_interface = "se.recompile.Mandos"
1319
self._client_interface = "se.recompile.Mandos.Client"
1321
self.call_method_return = Unique()
1323
def call_method(self, methodname, busname, objectpath,
1325
self.calls.append((methodname, busname, objectpath,
1327
return self.call_method_return
1330
self.bus = self.MockMandosBus()
1332
def test_set_client_property(self):
1333
self.bus.set_client_property("objectpath", "key", "value")
1334
expected_call = ("Set", self.bus._name, "objectpath",
1335
"org.freedesktop.DBus.Properties",
1336
(self.bus._client_interface, "key", "value"))
1337
self.assertIn(expected_call, self.bus.calls)
1339
def test_call_client_method(self):
1340
ret = self.bus.call_client_method("objectpath", "methodname")
1341
self.assertIs(self.bus.call_method_return, ret)
1342
expected_call = ("methodname", self.bus._name, "objectpath",
1343
self.bus._client_interface, ())
1344
self.assertIn(expected_call, self.bus.calls)
1346
def test_call_client_method_with_args(self):
1347
args = (Unique(), Unique())
1348
ret = self.bus.call_client_method("objectpath", "methodname",
1350
self.assertIs(self.bus.call_method_return, ret)
1351
expected_call = ("methodname", self.bus._name, "objectpath",
1352
self.bus._client_interface,
1354
self.assertIn(expected_call, self.bus.calls)
1356
def test_get_clients_and_properties(self):
1359
self.bus._client_interface: {
1363
"irrelevant_interface": {
1364
"key": "othervalue",
1368
"other_objectpath": {
1369
"other_irrelevant_interface": {
1375
expected_clients_and_properties = {
1381
self.bus.call_method_return = managed_objects
1382
ret = self.bus.get_clients_and_properties()
1383
self.assertDictEqual(expected_clients_and_properties, ret)
1384
expected_call = ("GetManagedObjects", self.bus._name,
1385
self.bus._server_path,
1386
"org.freedesktop.DBus.ObjectManager", ())
1387
self.assertIn(expected_call, self.bus.calls)
1389
def test_call_server_method(self):
1390
ret = self.bus.call_server_method("methodname")
1391
self.assertIs(self.bus.call_method_return, ret)
1392
expected_call = ("methodname", self.bus._name,
1393
self.bus._server_path,
1394
self.bus._server_interface, ())
1395
self.assertIn(expected_call, self.bus.calls)
1397
def test_call_server_method_with_args(self):
1398
args = (Unique(), Unique())
1399
ret = self.bus.call_server_method("methodname", *args)
1400
self.assertIs(self.bus.call_method_return, ret)
1401
expected_call = ("methodname", self.bus._name,
1402
self.bus._server_path,
1403
self.bus._server_interface,
1405
self.assertIn(expected_call, self.bus.calls)
1408
class Test_dbus_python_adapter_SystemBus(TestCaseWithAssertLogs):
1410
def MockDBusPython_func(self, func):
1411
class mock_dbus_python:
1412
"""mock dbus-python module"""
1414
"""Pseudo-namespace"""
1415
class DBusException(Exception):
1419
def get_object(busname, objectpath):
1420
DBusObject = collections.namedtuple(
1421
"DBusObject", ("methodname", "Set"))
1422
def method(*args, **kwargs):
1423
self.assertEqual({"dbus_interface":
1427
def set_property(interface, key, value,
1428
dbus_interface=None):
1430
"org.freedesktop.DBus.Properties",
1432
self.assertEqual("Secret", key)
1433
return func(interface, key, value,
1434
dbus_interface=dbus_interface)
1435
return DBusObject(methodname=method,
1438
def __init__(self, value):
1439
self.value = bool(value)
1442
if sys.version_info.major == 2:
1443
__nonzero__ = __bool__
1444
class ObjectPath(str):
1446
class Dictionary(dict):
1448
class ByteArray(bytes):
1450
return mock_dbus_python
1452
def call_method(self, bus, methodname, busname, objectpath,
1454
with self.assertLogs(log, logging.DEBUG):
1455
return bus.call_method(methodname, busname, objectpath,
1458
def test_call_method_returns(self):
1459
expected_method_return = Unique()
1460
method_args = (Unique(), Unique())
1462
self.assertEqual(len(method_args), len(args))
1463
for marg, arg in zip(method_args, args):
1464
self.assertIs(marg, arg)
1465
return expected_method_return
1466
mock_dbus_python = self.MockDBusPython_func(func)
1467
bus = dbus_python_adapter.SystemBus(mock_dbus_python)
1468
ret = self.call_method(bus, "methodname", "busname",
1469
"objectpath", "interface",
1471
self.assertIs(ret, expected_method_return)
1473
def test_call_method_filters_bool_true(self):
1475
return method_return
1476
mock_dbus_python = self.MockDBusPython_func(func)
1477
bus = dbus_python_adapter.SystemBus(mock_dbus_python)
1478
method_return = mock_dbus_python.Boolean(True)
1479
ret = self.call_method(bus, "methodname", "busname",
1480
"objectpath", "interface")
1481
self.assertTrue(ret)
1482
self.assertNotIsInstance(ret, mock_dbus_python.Boolean)
1484
def test_call_method_filters_bool_false(self):
1486
return method_return
1487
mock_dbus_python = self.MockDBusPython_func(func)
1488
bus = dbus_python_adapter.SystemBus(mock_dbus_python)
1489
method_return = mock_dbus_python.Boolean(False)
1490
ret = self.call_method(bus, "methodname", "busname",
1491
"objectpath", "interface")
1492
self.assertFalse(ret)
1493
self.assertNotIsInstance(ret, mock_dbus_python.Boolean)
1495
def test_call_method_filters_objectpath(self):
1497
return method_return
1498
mock_dbus_python = self.MockDBusPython_func(func)
1499
bus = dbus_python_adapter.SystemBus(mock_dbus_python)
1500
method_return = mock_dbus_python.ObjectPath("objectpath")
1501
ret = self.call_method(bus, "methodname", "busname",
1502
"objectpath", "interface")
1503
self.assertEqual("objectpath", ret)
1504
self.assertIsNot("objectpath", ret)
1505
self.assertNotIsInstance(ret, mock_dbus_python.ObjectPath)
1507
def test_call_method_filters_booleans_in_dict(self):
1509
return method_return
1510
mock_dbus_python = self.MockDBusPython_func(func)
1511
bus = dbus_python_adapter.SystemBus(mock_dbus_python)
1512
method_return = mock_dbus_python.Dictionary(
1513
{mock_dbus_python.Boolean(True):
1514
mock_dbus_python.Boolean(False),
1515
mock_dbus_python.Boolean(False):
1516
mock_dbus_python.Boolean(True)})
1517
ret = self.call_method(bus, "methodname", "busname",
1518
"objectpath", "interface")
1519
expected_method_return = {True: False,
1521
self.assertEqual(expected_method_return, ret)
1522
self.assertNotIsInstance(ret, mock_dbus_python.Dictionary)
1524
def test_call_method_filters_objectpaths_in_dict(self):
1526
return method_return
1527
mock_dbus_python = self.MockDBusPython_func(func)
1528
bus = dbus_python_adapter.SystemBus(mock_dbus_python)
1529
method_return = mock_dbus_python.Dictionary(
1530
{mock_dbus_python.ObjectPath("objectpath_key_1"):
1531
mock_dbus_python.ObjectPath("objectpath_value_1"),
1532
mock_dbus_python.ObjectPath("objectpath_key_2"):
1533
mock_dbus_python.ObjectPath("objectpath_value_2")})
1534
ret = self.call_method(bus, "methodname", "busname",
1535
"objectpath", "interface")
1536
expected_method_return = {str(key): str(value)
1538
method_return.items()}
1539
self.assertEqual(expected_method_return, ret)
1540
self.assertIsInstance(ret, dict)
1541
self.assertNotIsInstance(ret, mock_dbus_python.Dictionary)
1543
def test_call_method_filters_dict_in_dict(self):
1545
return method_return
1546
mock_dbus_python = self.MockDBusPython_func(func)
1547
bus = dbus_python_adapter.SystemBus(mock_dbus_python)
1548
method_return = mock_dbus_python.Dictionary(
1549
{"key1": mock_dbus_python.Dictionary({"key11": "value11",
1550
"key12": "value12"}),
1551
"key2": mock_dbus_python.Dictionary({"key21": "value21",
1552
"key22": "value22"})})
1553
ret = self.call_method(bus, "methodname", "busname",
1554
"objectpath", "interface")
1555
expected_method_return = {
1556
"key1": {"key11": "value11",
1557
"key12": "value12"},
1558
"key2": {"key21": "value21",
1559
"key22": "value22"},
1561
self.assertEqual(expected_method_return, ret)
1562
self.assertIsInstance(ret, dict)
1563
self.assertNotIsInstance(ret, mock_dbus_python.Dictionary)
1564
for key, value in ret.items():
1565
self.assertIsInstance(value, dict)
1566
self.assertEqual(expected_method_return[key], value)
1567
self.assertNotIsInstance(value,
1568
mock_dbus_python.Dictionary)
1570
def test_call_method_filters_dict_three_deep(self):
1572
return method_return
1573
mock_dbus_python = self.MockDBusPython_func(func)
1574
bus = dbus_python_adapter.SystemBus(mock_dbus_python)
1575
method_return = mock_dbus_python.Dictionary(
1577
mock_dbus_python.Dictionary(
1579
mock_dbus_python.Dictionary(
1581
mock_dbus_python.Boolean(True),
1585
ret = self.call_method(bus, "methodname", "busname",
1586
"objectpath", "interface")
1587
expected_method_return = {"key1": {"key2": {"key3": True}}}
1588
self.assertEqual(expected_method_return, ret)
1589
self.assertIsInstance(ret, dict)
1590
self.assertNotIsInstance(ret, mock_dbus_python.Dictionary)
1591
self.assertIsInstance(ret["key1"], dict)
1592
self.assertNotIsInstance(ret["key1"],
1593
mock_dbus_python.Dictionary)
1594
self.assertIsInstance(ret["key1"]["key2"], dict)
1595
self.assertNotIsInstance(ret["key1"]["key2"],
1596
mock_dbus_python.Dictionary)
1597
self.assertTrue(ret["key1"]["key2"]["key3"])
1598
self.assertNotIsInstance(ret["key1"]["key2"]["key3"],
1599
mock_dbus_python.Boolean)
1601
def test_call_method_handles_exception(self):
1602
dbus_logger = logging.getLogger("dbus.proxies")
1605
dbus_logger.error("Test")
1606
raise mock_dbus_python.exceptions.DBusException()
1608
mock_dbus_python = self.MockDBusPython_func(func)
1609
bus = dbus_python_adapter.SystemBus(mock_dbus_python)
1611
class CountingHandler(logging.Handler):
1613
def emit(self, record):
1616
counting_handler = CountingHandler()
1618
dbus_logger.addHandler(counting_handler)
1621
with self.assertRaises(dbus.Error) as e:
1622
self.call_method(bus, "methodname", "busname",
1623
"objectpath", "interface")
1625
dbus_logger.removeFilter(counting_handler)
1627
self.assertNotIsInstance(e.exception, dbus.ConnectFailed)
1629
# Make sure the dbus logger was suppressed
1630
self.assertEqual(0, counting_handler.count)
1632
def test_Set_Secret_sends_bytearray(self):
1634
def func(*args, **kwargs):
1635
ret[0] = (args, kwargs)
1636
mock_dbus_python = self.MockDBusPython_func(func)
1637
bus = dbus_python_adapter.SystemBus(mock_dbus_python)
1638
bus.set_client_property("objectpath", "Secret", "value")
1639
expected_call = (("se.recompile.Mandos.Client", "Secret",
1640
mock_dbus_python.ByteArray(b"value")),
1642
"org.freedesktop.DBus.Properties"})
1643
self.assertEqual(expected_call, ret[0])
1644
if sys.version_info.major == 2:
1645
self.assertIsInstance(ret[0][0][-1],
1646
mock_dbus_python.ByteArray)
1648
def test_get_object_converts_to_correct_exception(self):
1649
bus = dbus_python_adapter.SystemBus(
1650
self.fake_dbus_python_raises_exception_on_connect)
1651
with self.assertRaises(dbus.ConnectFailed):
1652
self.call_method(bus, "methodname", "busname",
1653
"objectpath", "interface")
1655
class fake_dbus_python_raises_exception_on_connect:
1656
"""fake dbus-python module"""
1658
"""Pseudo-namespace"""
1659
class DBusException(Exception):
1664
def get_object(busname, objectpath):
1665
raise cls.exceptions.DBusException()
1666
Bus = collections.namedtuple("Bus", ["get_object"])
1667
return Bus(get_object=get_object)
1670
class Test_dbus_python_adapter_CachingBus(unittest.TestCase):
1671
class mock_dbus_python:
1672
"""mock dbus-python modules"""
1675
def get_object(busname, objectpath):
1679
self.bus = dbus_python_adapter.CachingBus(
1680
self.mock_dbus_python)
1682
def test_returns_distinct_objectpaths(self):
1683
obj1 = self.bus.get_object("busname", "objectpath1")
1684
self.assertIsInstance(obj1, Unique)
1685
obj2 = self.bus.get_object("busname", "objectpath2")
1686
self.assertIsInstance(obj2, Unique)
1687
self.assertIsNot(obj1, obj2)
1689
def test_returns_distinct_busnames(self):
1690
obj1 = self.bus.get_object("busname1", "objectpath")
1691
self.assertIsInstance(obj1, Unique)
1692
obj2 = self.bus.get_object("busname2", "objectpath")
1693
self.assertIsInstance(obj2, Unique)
1694
self.assertIsNot(obj1, obj2)
1696
def test_returns_distinct_both(self):
1697
obj1 = self.bus.get_object("busname1", "objectpath")
1698
self.assertIsInstance(obj1, Unique)
1699
obj2 = self.bus.get_object("busname2", "objectpath")
1700
self.assertIsInstance(obj2, Unique)
1701
self.assertIsNot(obj1, obj2)
1703
def test_returns_same(self):
1704
obj1 = self.bus.get_object("busname", "objectpath")
1705
self.assertIsInstance(obj1, Unique)
1706
obj2 = self.bus.get_object("busname", "objectpath")
1707
self.assertIsInstance(obj2, Unique)
1708
self.assertIs(obj1, obj2)
1710
def test_returns_same_old(self):
1711
obj1 = self.bus.get_object("busname1", "objectpath1")
1712
self.assertIsInstance(obj1, Unique)
1713
obj2 = self.bus.get_object("busname2", "objectpath2")
1714
self.assertIsInstance(obj2, Unique)
1715
obj1b = self.bus.get_object("busname1", "objectpath1")
1716
self.assertIsInstance(obj1b, Unique)
1717
self.assertIsNot(obj1, obj2)
1718
self.assertIsNot(obj2, obj1b)
1719
self.assertIs(obj1, obj1b)
1722
class Test_pydbus_adapter_SystemBus(TestCaseWithAssertLogs):
1724
def Stub_pydbus_func(self, func):
1726
"""stub pydbus module"""
1729
def get(busname, objectpath):
1730
DBusObject = collections.namedtuple(
1731
"DBusObject", ("methodname",))
1732
return {"interface":
1733
DBusObject(methodname=func)}
1736
def call_method(self, bus, methodname, busname, objectpath,
1738
with self.assertLogs(log, logging.DEBUG):
1739
return bus.call_method(methodname, busname, objectpath,
1742
def test_call_method_returns(self):
1743
expected_method_return = Unique()
1744
method_args = (Unique(), Unique())
1746
self.assertEqual(len(method_args), len(args))
1747
for marg, arg in zip(method_args, args):
1748
self.assertIs(marg, arg)
1749
return expected_method_return
1750
stub_pydbus = self.Stub_pydbus_func(func)
1751
bus = pydbus_adapter.SystemBus(stub_pydbus)
1752
ret = self.call_method(bus, "methodname", "busname",
1753
"objectpath", "interface",
1755
self.assertIs(ret, expected_method_return)
1757
def test_call_method_handles_exception(self):
1759
raise gi.repository.GLib.Error()
1761
stub_pydbus = self.Stub_pydbus_func(func)
1762
bus = pydbus_adapter.SystemBus(stub_pydbus)
1764
with self.assertRaises(dbus.Error) as e:
1765
self.call_method(bus, "methodname", "busname",
1766
"objectpath", "interface")
1768
self.assertNotIsInstance(e.exception, dbus.ConnectFailed)
1770
def test_get_converts_to_correct_exception(self):
1771
bus = pydbus_adapter.SystemBus(
1772
self.fake_pydbus_raises_exception_on_connect)
1773
with self.assertRaises(dbus.ConnectFailed):
1774
self.call_method(bus, "methodname", "busname",
1775
"objectpath", "interface")
1777
class fake_pydbus_raises_exception_on_connect:
1778
"""fake dbus-python module"""
1781
def get(busname, objectpath):
1782
raise gi.repository.GLib.Error()
1783
Bus = collections.namedtuple("Bus", ["get"])
1786
def test_set_property_uses_setattr(self):
1793
def get(busname, objectpath):
1794
return {"interface": obj}
1795
bus = pydbus_adapter.SystemBus(pydbus_spy)
1797
bus.set_property("busname", "objectpath", "interface", "key",
1799
self.assertIs(value, obj.key)
1801
def test_get_suppresses_xml_deprecation_warning(self):
1802
if sys.version_info.major >= 3:
1804
class stub_pydbus_get:
1807
def get(busname, objectpath):
1808
warnings.warn_explicit(
1809
"deprecated", DeprecationWarning,
1810
"xml.etree.ElementTree", 0)
1811
bus = pydbus_adapter.SystemBus(stub_pydbus_get)
1812
with warnings.catch_warnings(record=True) as w:
1813
warnings.simplefilter("always")
1814
bus.get("busname", "objectpath")
1815
self.assertEqual(0, len(w))
1818
class Test_pydbus_adapter_CachingBus(unittest.TestCase):
1820
"""stub pydbus module"""
1823
def get(busname, objectpath):
1827
self.bus = pydbus_adapter.CachingBus(self.stub_pydbus)
1829
def test_returns_distinct_objectpaths(self):
1830
obj1 = self.bus.get("busname", "objectpath1")
1831
self.assertIsInstance(obj1, Unique)
1832
obj2 = self.bus.get("busname", "objectpath2")
1833
self.assertIsInstance(obj2, Unique)
1834
self.assertIsNot(obj1, obj2)
1836
def test_returns_distinct_busnames(self):
1837
obj1 = self.bus.get("busname1", "objectpath")
1838
self.assertIsInstance(obj1, Unique)
1839
obj2 = self.bus.get("busname2", "objectpath")
1840
self.assertIsInstance(obj2, Unique)
1841
self.assertIsNot(obj1, obj2)
1843
def test_returns_distinct_both(self):
1844
obj1 = self.bus.get("busname1", "objectpath")
1845
self.assertIsInstance(obj1, Unique)
1846
obj2 = self.bus.get("busname2", "objectpath")
1847
self.assertIsInstance(obj2, Unique)
1848
self.assertIsNot(obj1, obj2)
1850
def test_returns_same(self):
1851
obj1 = self.bus.get("busname", "objectpath")
1852
self.assertIsInstance(obj1, Unique)
1853
obj2 = self.bus.get("busname", "objectpath")
1854
self.assertIsInstance(obj2, Unique)
1855
self.assertIs(obj1, obj2)
1857
def test_returns_same_old(self):
1858
obj1 = self.bus.get("busname1", "objectpath1")
1859
self.assertIsInstance(obj1, Unique)
1860
obj2 = self.bus.get("busname2", "objectpath2")
1861
self.assertIsInstance(obj2, Unique)
1862
obj1b = self.bus.get("busname1", "objectpath1")
1863
self.assertIsInstance(obj1b, Unique)
1864
self.assertIsNot(obj1, obj2)
1865
self.assertIsNot(obj2, obj1b)
1866
self.assertIs(obj1, obj1b)
1869
class Test_dbussy_adapter_SystemBus(TestCaseWithAssertLogs):
1873
class ObjectPath(str):
1875
class DBusError(Exception):
1878
def fake_ravel_func(self, func):
1882
class DBusInterfaceProxy:
1884
def methodname(*args):
1885
return [func(*args)]
1888
def get_interface(interface):
1889
if interface == "interface":
1890
return DBusInterfaceProxy()
1891
return {"busname": {"objectpath": DBusObject()}}
1894
def call_method(self, bus, methodname, busname, objectpath,
1896
with self.assertLogs(log, logging.DEBUG):
1897
return bus.call_method(methodname, busname, objectpath,
1900
def test_call_method_returns(self):
1901
expected_method_return = Unique()
1902
method_args = (Unique(), Unique())
1904
self.assertEqual(len(method_args), len(args))
1905
for marg, arg in zip(method_args, args):
1906
self.assertIs(marg, arg)
1907
return expected_method_return
1908
fake_ravel = self.fake_ravel_func(func)
1909
bus = dbussy_adapter.SystemBus(self.dummy_dbussy, fake_ravel)
1910
ret = self.call_method(bus, "methodname", "busname",
1911
"objectpath", "interface",
1913
self.assertIs(ret, expected_method_return)
1915
def test_call_method_filters_objectpath(self):
1917
return method_return
1918
fake_ravel = self.fake_ravel_func(func)
1919
bus = dbussy_adapter.SystemBus(self.dummy_dbussy, fake_ravel)
1920
method_return = (self.dummy_dbussy.DBUS
1921
.ObjectPath("objectpath"))
1922
ret = self.call_method(bus, "methodname", "busname",
1923
"objectpath", "interface")
1924
self.assertEqual("objectpath", ret)
1925
self.assertNotIsInstance(ret,
1926
self.dummy_dbussy.DBUS.ObjectPath)
1928
def test_call_method_filters_objectpaths_in_dict(self):
1929
ObjectPath = self.dummy_dbussy.DBUS.ObjectPath
1931
return method_return
1932
fake_ravel = self.fake_ravel_func(func)
1933
bus = dbussy_adapter.SystemBus(self.dummy_dbussy, fake_ravel)
1935
ObjectPath("objectpath_key_1"):
1936
ObjectPath("objectpath_value_1"),
1937
ObjectPath("objectpath_key_2"):
1938
ObjectPath("objectpath_value_2"),
1940
ret = self.call_method(bus, "methodname", "busname",
1941
"objectpath", "interface")
1942
expected_method_return = {str(key): str(value)
1944
method_return.items()}
1945
for key, value in ret.items():
1946
self.assertNotIsInstance(key, ObjectPath)
1947
self.assertNotIsInstance(value, ObjectPath)
1948
self.assertEqual(expected_method_return, ret)
1949
self.assertIsInstance(ret, dict)
1951
def test_call_method_filters_objectpaths_in_dict_in_dict(self):
1952
ObjectPath = self.dummy_dbussy.DBUS.ObjectPath
1954
return method_return
1955
fake_ravel = self.fake_ravel_func(func)
1956
bus = dbussy_adapter.SystemBus(self.dummy_dbussy, fake_ravel)
1958
ObjectPath("key1"): {
1959
ObjectPath("key11"): ObjectPath("value11"),
1960
ObjectPath("key12"): ObjectPath("value12"),
1962
ObjectPath("key2"): {
1963
ObjectPath("key21"): ObjectPath("value21"),
1964
ObjectPath("key22"): ObjectPath("value22"),
1967
ret = self.call_method(bus, "methodname", "busname",
1968
"objectpath", "interface")
1969
expected_method_return = {
1970
"key1": {"key11": "value11",
1971
"key12": "value12"},
1972
"key2": {"key21": "value21",
1973
"key22": "value22"},
1975
self.assertEqual(expected_method_return, ret)
1976
for key, value in ret.items():
1977
self.assertIsInstance(value, dict)
1978
self.assertEqual(expected_method_return[key], value)
1979
self.assertNotIsInstance(key, ObjectPath)
1980
for inner_key, inner_value in value.items():
1981
self.assertIsInstance(value, dict)
1983
expected_method_return[key][inner_key],
1985
self.assertNotIsInstance(key, ObjectPath)
1987
def test_call_method_filters_objectpaths_in_dict_three_deep(self):
1988
ObjectPath = self.dummy_dbussy.DBUS.ObjectPath
1990
return method_return
1991
fake_ravel = self.fake_ravel_func(func)
1992
bus = dbussy_adapter.SystemBus(self.dummy_dbussy, fake_ravel)
1994
ObjectPath("key1"): {
1995
ObjectPath("key2"): {
1996
ObjectPath("key3"): ObjectPath("value"),
2000
ret = self.call_method(bus, "methodname", "busname",
2001
"objectpath", "interface")
2002
expected_method_return = {"key1": {"key2": {"key3": "value"}}}
2003
self.assertEqual(expected_method_return, ret)
2004
self.assertIsInstance(ret, dict)
2005
self.assertNotIsInstance(next(iter(ret.keys())), ObjectPath)
2006
self.assertIsInstance(ret["key1"], dict)
2007
self.assertNotIsInstance(next(iter(ret["key1"].keys())),
2009
self.assertIsInstance(ret["key1"]["key2"], dict)
2010
self.assertNotIsInstance(
2011
next(iter(ret["key1"]["key2"].keys())),
2013
self.assertEqual("value", ret["key1"]["key2"]["key3"])
2014
self.assertNotIsInstance(ret["key1"]["key2"]["key3"],
2015
self.dummy_dbussy.DBUS.ObjectPath)
2017
def test_call_method_handles_exception(self):
2019
raise self.dummy_dbussy.DBusError()
2021
fake_ravel = self.fake_ravel_func(func)
2022
bus = dbussy_adapter.SystemBus(self.dummy_dbussy, fake_ravel)
2024
with self.assertRaises(dbus.Error) as e:
2025
self.call_method(bus, "methodname", "busname",
2026
"objectpath", "interface")
2028
self.assertNotIsInstance(e.exception, dbus.ConnectFailed)
2030
def test_get_object_converts_to_correct_exception(self):
2031
class fake_ravel_raises_exception_on_connect:
2036
def __getitem__(key):
2037
if key == "objectpath":
2038
raise self.dummy_dbussy.DBusError()
2039
raise Exception(key)
2040
return {"busname": Bus()}
2042
raise self.dummy_dbussy.DBusError()
2043
bus = dbussy_adapter.SystemBus(
2045
fake_ravel_raises_exception_on_connect)
2046
with self.assertRaises(dbus.ConnectFailed):
2047
self.call_method(bus, "methodname", "busname",
2048
"objectpath", "interface")
2051
class Test_commands_from_options(unittest.TestCase):
2054
self.parser = argparse.ArgumentParser()
2055
add_command_line_options(self.parser)
2057
def test_is_enabled(self):
2058
self.assert_command_from_args(["--is-enabled", "client"],
2061
def assert_command_from_args(self, args, command_cls, length=1,
2062
clients=None, **cmd_attrs):
2063
"""Assert that parsing ARGS should result in an instance of
2064
COMMAND_CLS with (optionally) all supplied attributes (CMD_ATTRS)."""
2065
options = self.parser.parse_args(args)
2066
check_option_syntax(self.parser, options)
2067
commands = commands_from_options(options)
2068
self.assertEqual(length, len(commands))
2069
for command in commands:
2070
if isinstance(command, command_cls):
2073
self.assertIsInstance(command, command_cls)
2074
if clients is not None:
2075
self.assertEqual(clients, options.client)
2076
for key, value in cmd_attrs.items():
2077
self.assertEqual(value, getattr(command, key))
2079
def assert_commands_from_args(self, args, commands, clients=None):
2080
for cmd in commands:
2081
self.assert_command_from_args(args, cmd,
2082
length=len(commands),
2085
def test_is_enabled_short(self):
2086
self.assert_command_from_args(["-V", "client"],
2089
def test_approve(self):
2090
self.assert_command_from_args(["--approve", "client"],
2093
def test_approve_short(self):
2094
self.assert_command_from_args(["-A", "client"],
2097
def test_deny(self):
2098
self.assert_command_from_args(["--deny", "client"],
2101
def test_deny_short(self):
2102
self.assert_command_from_args(["-D", "client"], command.Deny)
2104
def test_remove(self):
2105
self.assert_command_from_args(["--remove", "client"],
2108
def test_deny_before_remove(self):
2109
options = self.parser.parse_args(["--deny", "--remove",
2111
check_option_syntax(self.parser, options)
2112
commands = commands_from_options(options)
2113
self.assertEqual(2, len(commands))
2114
self.assertIsInstance(commands[0], command.Deny)
2115
self.assertIsInstance(commands[1], command.Remove)
2117
def test_deny_before_remove_reversed(self):
2118
options = self.parser.parse_args(["--remove", "--deny",
2120
check_option_syntax(self.parser, options)
2121
commands = commands_from_options(options)
2122
self.assertEqual(2, len(commands))
2123
self.assertIsInstance(commands[0], command.Deny)
2124
self.assertIsInstance(commands[1], command.Remove)
2126
def test_remove_short(self):
2127
self.assert_command_from_args(["-r", "client"],
2130
def test_dump_json(self):
2131
self.assert_command_from_args(["--dump-json"],
2134
def test_enable(self):
2135
self.assert_command_from_args(["--enable", "client"],
2138
def test_enable_short(self):
2139
self.assert_command_from_args(["-e", "client"],
2142
def test_disable(self):
2143
self.assert_command_from_args(["--disable", "client"],
2146
def test_disable_short(self):
2147
self.assert_command_from_args(["-d", "client"],
2150
def test_bump_timeout(self):
2151
self.assert_command_from_args(["--bump-timeout", "client"],
2152
command.BumpTimeout)
2154
def test_bump_timeout_short(self):
2155
self.assert_command_from_args(["-b", "client"],
2156
command.BumpTimeout)
2158
def test_start_checker(self):
2159
self.assert_command_from_args(["--start-checker", "client"],
2160
command.StartChecker)
2162
def test_stop_checker(self):
2163
self.assert_command_from_args(["--stop-checker", "client"],
2164
command.StopChecker)
2166
def test_approve_by_default(self):
2167
self.assert_command_from_args(["--approve-by-default",
2169
command.ApproveByDefault)
2171
def test_deny_by_default(self):
2172
self.assert_command_from_args(["--deny-by-default", "client"],
2173
command.DenyByDefault)
2175
def test_checker(self):
2176
self.assert_command_from_args(["--checker", ":", "client"],
2180
def test_checker_empty(self):
2181
self.assert_command_from_args(["--checker", "", "client"],
2185
def test_checker_short(self):
2186
self.assert_command_from_args(["-c", ":", "client"],
2190
def test_host(self):
2191
self.assert_command_from_args(
2192
["--host", "client.example.org", "client"],
2193
command.SetHost, value_to_set="client.example.org")
2195
def test_host_short(self):
2196
self.assert_command_from_args(
2197
["-H", "client.example.org", "client"], command.SetHost,
2198
value_to_set="client.example.org")
2200
def test_secret_devnull(self):
2201
self.assert_command_from_args(["--secret", os.path.devnull,
2202
"client"], command.SetSecret,
2205
def test_secret_tempfile(self):
2206
with tempfile.NamedTemporaryFile(mode="r+b") as f:
2207
value = b"secret\0xyzzy\nbar"
2210
self.assert_command_from_args(["--secret", f.name,
2215
def test_secret_devnull_short(self):
2216
self.assert_command_from_args(["-s", os.path.devnull,
2217
"client"], command.SetSecret,
2220
def test_secret_tempfile_short(self):
2221
with tempfile.NamedTemporaryFile(mode="r+b") as f:
2222
value = b"secret\0xyzzy\nbar"
2225
self.assert_command_from_args(["-s", f.name, "client"],
2229
def test_timeout(self):
2230
self.assert_command_from_args(["--timeout", "PT5M", "client"],
2232
value_to_set=300000)
2234
def test_timeout_short(self):
2235
self.assert_command_from_args(["-t", "PT5M", "client"],
2237
value_to_set=300000)
2239
def test_extended_timeout(self):
2240
self.assert_command_from_args(["--extended-timeout", "PT15M",
2242
command.SetExtendedTimeout,
2243
value_to_set=900000)
2245
def test_interval(self):
2246
self.assert_command_from_args(["--interval", "PT2M",
2247
"client"], command.SetInterval,
2248
value_to_set=120000)
2250
def test_interval_short(self):
2251
self.assert_command_from_args(["-i", "PT2M", "client"],
2252
command.SetInterval,
2253
value_to_set=120000)
2255
def test_approval_delay(self):
2256
self.assert_command_from_args(["--approval-delay", "PT30S",
2258
command.SetApprovalDelay,
2261
def test_approval_duration(self):
2262
self.assert_command_from_args(["--approval-duration", "PT1S",
2264
command.SetApprovalDuration,
2267
def test_print_table(self):
2268
self.assert_command_from_args([], command.PrintTable,
2271
def test_print_table_verbose(self):
2272
self.assert_command_from_args(["--verbose"],
2276
def test_print_table_verbose_short(self):
2277
self.assert_command_from_args(["-v"], command.PrintTable,
2281
def test_manual_page_example_1(self):
2282
self.assert_command_from_args("",
2287
def test_manual_page_example_2(self):
2288
self.assert_command_from_args(
2289
"--verbose foo1.example.org foo2.example.org".split(),
2290
command.PrintTable, clients=["foo1.example.org",
2291
"foo2.example.org"],
2294
def test_manual_page_example_3(self):
2295
self.assert_command_from_args("--enable --all".split(),
2299
def test_manual_page_example_4(self):
2300
self.assert_commands_from_args(
2301
("--timeout=PT5M --interval=PT1M foo1.example.org"
2302
" foo2.example.org").split(),
2303
[command.SetTimeout, command.SetInterval],
2304
clients=["foo1.example.org", "foo2.example.org"])
2306
def test_manual_page_example_5(self):
2307
self.assert_command_from_args("--approve --all".split(),
2312
class TestCommand(unittest.TestCase):
821
2313
"""Abstract class for tests of command classes"""
824
class MockClient(object):
825
def __init__(self, name, **attributes):
826
self.__dbus_object_path__ = "objpath_{}".format(name)
827
self.attributes = attributes
828
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 Approve(self, approve, dbus_interface):
841
testcase.assertEqual(dbus_interface, client_interface)
842
self.calls.append(("Approve", (approve,
844
self.client = MockClient(
846
KeyID=("92ed150794387c03ce684574b1139a65"
847
"94a34f895daaaf09fd8ea90a27cddb12"),
849
Host="foo.example.org",
850
Enabled=dbus.Boolean(True),
852
LastCheckedOK="2019-02-03T00:00:00",
853
Created="2019-01-02T00:00:00",
855
Fingerprint=("778827225BA7DE539C5A"
856
"7CFA59CFF7CDBD9A5920"),
857
CheckerRunning=dbus.Boolean(False),
858
LastEnabled="2019-01-03T00:00:00",
859
ApprovalPending=dbus.Boolean(False),
860
ApprovedByDefault=dbus.Boolean(True),
861
LastApprovalRequest="",
863
ApprovalDuration=1000,
864
Checker="fping -q -- %(host)s",
865
ExtendedTimeout=900000,
866
Expires="2019-02-04T00:00:00",
868
self.other_client = MockClient(
870
KeyID=("0558568eedd67d622f5c83b35a115f79"
871
"6ab612cff5ad227247e46c2b020f441c"),
874
Enabled=dbus.Boolean(True),
876
LastCheckedOK="2019-02-04T00:00:00",
877
Created="2019-01-03T00:00:00",
879
Fingerprint=("3E393AEAEFB84C7E89E2"
880
"F547B3A107558FCA3A27"),
881
CheckerRunning=dbus.Boolean(True),
882
LastEnabled="2019-01-04T00:00:00",
883
ApprovalPending=dbus.Boolean(False),
884
ApprovedByDefault=dbus.Boolean(False),
885
LastApprovalRequest="2019-01-03T00:00:00",
887
ApprovalDuration=1000,
889
ExtendedTimeout=900000,
890
Expires="2019-02-05T00:00:00",
891
LastCheckerStatus=-2)
892
self.clients = collections.OrderedDict(
894
(self.client, self.client.attributes),
895
(self.other_client, self.other_client.attributes),
897
self.one_client = {self.client: self.client.attributes}
899
class TestPrintTableCmd(TestCmd):
900
def test_normal(self):
901
output = PrintTableCmd().output(self.clients)
902
expected_output = """
903
Name Enabled Timeout Last Successful Check
904
foo Yes 00:05:00 2019-02-03T00:00:00
905
barbar Yes 00:05:00 2019-02-04T00:00:00
907
self.assertEqual(output, expected_output)
908
def test_verbose(self):
909
output = PrintTableCmd(verbose=True).output(self.clients)
910
expected_output = """
911
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
912
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
913
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
915
self.assertEqual(output, expected_output)
916
def test_one_client(self):
917
output = PrintTableCmd().output(self.one_client)
918
expected_output = """
919
Name Enabled Timeout Last Successful Check
920
foo Yes 00:05:00 2019-02-03T00:00:00
922
self.assertEqual(output, expected_output)
924
class TestDumpJSONCmd(TestCmd):
926
self.expected_json = {
2315
class FakeMandosBus(dbus.MandosBus):
2316
def __init__(self, testcase):
2317
self.client_properties = {
929
2319
"KeyID": ("92ed150794387c03ce684574b1139a65"
930
2320
"94a34f895daaaf09fd8ea90a27cddb12"),
2321
"Secret": b"secret",
931
2322
"Host": "foo.example.org",
932
2323
"Enabled": True,
933
2324
"Timeout": 300000,
966
2358
"ApprovedByDefault": False,
967
2359
"LastApprovalRequest": "2019-01-03T00:00:00",
968
2360
"ApprovalDelay": 30000,
969
"ApprovalDuration": 1000,
2361
"ApprovalDuration": 93785000,
971
2363
"ExtendedTimeout": 900000,
972
2364
"Expires": "2019-02-05T00:00:00",
973
2365
"LastCheckerStatus": -2,
976
return super(TestDumpJSONCmd, self).setUp()
977
def test_normal(self):
978
json_data = json.loads(DumpJSONCmd().output(self.clients))
979
self.assertDictEqual(json_data, self.expected_json)
980
def test_one_client(self):
981
clients = self.one_client
982
json_data = json.loads(DumpJSONCmd().output(clients))
983
expected_json = {"foo": self.expected_json["foo"]}
984
self.assertDictEqual(json_data, expected_json)
986
class TestIsEnabledCmd(TestCmd):
987
def test_is_enabled(self):
988
self.assertTrue(all(IsEnabledCmd().is_enabled(client, properties)
989
for client, properties in self.clients.items()))
990
def test_is_enabled_run_exits_successfully(self):
2367
self.clients = collections.OrderedDict(
2369
("client_objectpath", self.client_properties),
2370
("other_client_objectpath",
2371
self.other_client_properties),
2373
self.one_client = {"client_objectpath":
2374
self.client_properties}
2375
self.testcase = testcase
2378
def call_method(self, methodname, busname, objectpath,
2380
self.testcase.assertEqual("se.recompile.Mandos", busname)
2381
self.calls.append((methodname, busname, objectpath,
2383
if interface == "org.freedesktop.DBus.Properties":
2384
if methodname == "Set":
2385
self.testcase.assertEqual(3, len(args))
2386
interface, key, value = args
2387
self.testcase.assertEqual(
2388
"se.recompile.Mandos.Client", interface)
2389
self.clients[objectpath][key] = value
2391
elif interface == "se.recompile.Mandos":
2392
self.testcase.assertEqual("RemoveClient", methodname)
2393
self.testcase.assertEqual(1, len(args))
2394
clientpath = args[0]
2395
del self.clients[clientpath]
2397
elif interface == "se.recompile.Mandos.Client":
2398
if methodname == "Approve":
2399
self.testcase.assertEqual(1, len(args))
2404
self.bus = self.FakeMandosBus(self)
2407
class TestBaseCommands(TestCommand):
2409
def test_IsEnabled_exits_successfully(self):
991
2410
with self.assertRaises(SystemExit) as e:
992
IsEnabledCmd().run(None, self.one_client)
2411
command.IsEnabled().run(self.bus.one_client)
993
2412
if e.exception.code is not None:
994
self.assertEqual(e.exception.code, 0)
2413
self.assertEqual(0, e.exception.code)
996
2415
self.assertIsNone(e.exception.code)
997
def test_is_enabled_run_exits_with_failure(self):
998
self.client.attributes["Enabled"] = dbus.Boolean(False)
2417
def test_IsEnabled_exits_with_failure(self):
2418
self.bus.client_properties["Enabled"] = False
999
2419
with self.assertRaises(SystemExit) as e:
1000
IsEnabledCmd().run(None, self.one_client)
2420
command.IsEnabled().run(self.bus.one_client)
1001
2421
if isinstance(e.exception.code, int):
1002
self.assertNotEqual(e.exception.code, 0)
2422
self.assertNotEqual(0, e.exception.code)
1004
2424
self.assertIsNotNone(e.exception.code)
1006
class TestRemoveCmd(TestCmd):
1007
def test_remove(self):
1008
class MockMandos(object):
1011
def RemoveClient(self, dbus_path):
1012
self.calls.append(("RemoveClient", (dbus_path,)))
1013
mandos = MockMandos()
1014
super(TestRemoveCmd, self).setUp()
1015
RemoveCmd().run(mandos, self.clients)
1016
self.assertEqual(len(mandos.calls), 2)
1017
for client in self.clients:
1018
self.assertIn(("RemoveClient",
1019
(client.__dbus_object_path__,)),
1022
class TestApproveCmd(TestCmd):
1023
def test_approve(self):
1024
ApproveCmd().run(None, self.clients)
1025
for client in self.clients:
1026
self.assertIn(("Approve", (True, client_interface)),
1029
class TestDenyCmd(TestCmd):
1030
def test_deny(self):
1031
DenyCmd().run(None, self.clients)
1032
for client in self.clients:
1033
self.assertIn(("Approve", (False, client_interface)),
1036
class TestEnableCmd(TestCmd):
1037
def test_enable(self):
1038
for client in self.clients:
1039
client.attributes["Enabled"] = False
1041
EnableCmd().run(None, self.clients)
1043
for client in self.clients:
1044
self.assertTrue(client.attributes["Enabled"])
1046
class TestDisableCmd(TestCmd):
1047
def test_disable(self):
1048
DisableCmd().run(None, self.clients)
1050
for client in self.clients:
1051
self.assertFalse(client.attributes["Enabled"])
1053
class Unique(object):
1054
"""Class for objects which exist only to be unique objects, since
1055
unittest.mock.sentinel only exists in Python 3.3"""
1057
class TestPropertyCmd(TestCmd):
1058
"""Abstract class for tests of PropertyCmd classes"""
2426
def test_Approve(self):
2427
busname = "se.recompile.Mandos"
2428
client_interface = "se.recompile.Mandos.Client"
2429
command.Approve().run(self.bus.clients, self.bus)
2430
self.assertTrue(self.bus.clients)
2431
for clientpath in self.bus.clients:
2432
self.assertIn(("Approve", busname, clientpath,
2433
client_interface, (True,)), self.bus.calls)
2435
def test_Deny(self):
2436
busname = "se.recompile.Mandos"
2437
client_interface = "se.recompile.Mandos.Client"
2438
command.Deny().run(self.bus.clients, self.bus)
2439
self.assertTrue(self.bus.clients)
2440
for clientpath in self.bus.clients:
2441
self.assertIn(("Approve", busname, clientpath,
2442
client_interface, (False,)),
2445
def test_Remove(self):
2446
busname = "se.recompile.Mandos"
2448
server_interface = "se.recompile.Mandos"
2449
orig_clients = self.bus.clients.copy()
2450
command.Remove().run(self.bus.clients, self.bus)
2451
self.assertFalse(self.bus.clients)
2452
for clientpath in orig_clients:
2453
self.assertIn(("RemoveClient", busname,
2454
server_path, server_interface,
2455
(clientpath,)), self.bus.calls)
2460
"KeyID": ("92ed150794387c03ce684574b1139a65"
2461
"94a34f895daaaf09fd8ea90a27cddb12"),
2462
"Host": "foo.example.org",
2465
"LastCheckedOK": "2019-02-03T00:00:00",
2466
"Created": "2019-01-02T00:00:00",
2468
"Fingerprint": ("778827225BA7DE539C5A"
2469
"7CFA59CFF7CDBD9A5920"),
2470
"CheckerRunning": False,
2471
"LastEnabled": "2019-01-03T00:00:00",
2472
"ApprovalPending": False,
2473
"ApprovedByDefault": True,
2474
"LastApprovalRequest": "",
2476
"ApprovalDuration": 1000,
2477
"Checker": "fping -q -- %(host)s",
2478
"ExtendedTimeout": 900000,
2479
"Expires": "2019-02-04T00:00:00",
2480
"LastCheckerStatus": 0,
2484
"KeyID": ("0558568eedd67d622f5c83b35a115f79"
2485
"6ab612cff5ad227247e46c2b020f441c"),
2486
"Host": "192.0.2.3",
2489
"LastCheckedOK": "2019-02-04T00:00:00",
2490
"Created": "2019-01-03T00:00:00",
2492
"Fingerprint": ("3E393AEAEFB84C7E89E2"
2493
"F547B3A107558FCA3A27"),
2494
"CheckerRunning": True,
2495
"LastEnabled": "2019-01-04T00:00:00",
2496
"ApprovalPending": False,
2497
"ApprovedByDefault": False,
2498
"LastApprovalRequest": "2019-01-03T00:00:00",
2499
"ApprovalDelay": 30000,
2500
"ApprovalDuration": 93785000,
2502
"ExtendedTimeout": 900000,
2503
"Expires": "2019-02-05T00:00:00",
2504
"LastCheckerStatus": -2,
2508
def test_DumpJSON_normal(self):
2509
with self.capture_stdout_to_buffer() as buffer:
2510
command.DumpJSON().run(self.bus.clients)
2511
json_data = json.loads(buffer.getvalue())
2512
self.assertDictEqual(self.expected_json, json_data)
2515
@contextlib.contextmanager
2516
def capture_stdout_to_buffer():
2517
capture_buffer = io.StringIO()
2518
old_stdout = sys.stdout
2519
sys.stdout = capture_buffer
2521
yield capture_buffer
2523
sys.stdout = old_stdout
2525
def test_DumpJSON_one_client(self):
2526
with self.capture_stdout_to_buffer() as buffer:
2527
command.DumpJSON().run(self.bus.one_client)
2528
json_data = json.loads(buffer.getvalue())
2529
expected_json = {"foo": self.expected_json["foo"]}
2530
self.assertDictEqual(expected_json, json_data)
2532
def test_PrintTable_normal(self):
2533
with self.capture_stdout_to_buffer() as buffer:
2534
command.PrintTable().run(self.bus.clients)
2535
expected_output = "\n".join((
2536
"Name Enabled Timeout Last Successful Check",
2537
"foo Yes 00:05:00 2019-02-03T00:00:00 ",
2538
"barbar Yes 00:05:00 2019-02-04T00:00:00 ",
2540
self.assertEqual(expected_output, buffer.getvalue())
2542
def test_PrintTable_verbose(self):
2543
with self.capture_stdout_to_buffer() as buffer:
2544
command.PrintTable(verbose=True).run(self.bus.clients)
2559
"Last Successful Check ",
2560
"2019-02-03T00:00:00 ",
2561
"2019-02-04T00:00:00 ",
2564
"2019-01-02T00:00:00 ",
2565
"2019-01-03T00:00:00 ",
2577
("92ed150794387c03ce684574b1139a6594a34f895daaaf09fd8"
2579
("0558568eedd67d622f5c83b35a115f796ab612cff5ad227247e"
2583
"778827225BA7DE539C5A7CFA59CFF7CDBD9A5920 ",
2584
"3E393AEAEFB84C7E89E2F547B3A107558FCA3A27 ",
2586
"Check Is Running ",
2591
"2019-01-03T00:00:00 ",
2592
"2019-01-04T00:00:00 ",
2594
"Approval Is Pending ",
2598
"Approved By Default ",
2602
"Last Approval Request ",
2604
"2019-01-03T00:00:00 ",
2610
"Approval Duration ",
2615
"fping -q -- %(host)s ",
2618
"Extended Timeout ",
2623
"2019-02-04T00:00:00 ",
2624
"2019-02-05T00:00:00 ",
2626
"Last Checker Status",
2631
num_lines = max(len(rows) for rows in columns)
2632
expected_output = ("\n".join("".join(rows[line]
2633
for rows in columns)
2634
for line in range(num_lines))
2636
self.assertEqual(expected_output, buffer.getvalue())
2638
def test_PrintTable_one_client(self):
2639
with self.capture_stdout_to_buffer() as buffer:
2640
command.PrintTable().run(self.bus.one_client)
2641
expected_output = "\n".join((
2642
"Name Enabled Timeout Last Successful Check",
2643
"foo Yes 00:05:00 2019-02-03T00:00:00 ",
2645
self.assertEqual(expected_output, buffer.getvalue())
2648
class TestPropertySetterCmd(TestCommand):
2649
"""Abstract class for tests of command.PropertySetter classes"""
1059
2651
def runTest(self):
1060
2652
if not hasattr(self, "command"):
1062
values_to_get = getattr(self, "values_to_get",
1064
for value_to_set, value_to_get in zip(self.values_to_set,
1066
for client in self.clients:
1067
old_value = client.attributes[self.property]
1068
self.assertNotIsInstance(old_value, Unique)
1069
client.attributes[self.property] = Unique()
1070
self.run_command(value_to_set, self.clients)
1071
for client in self.clients:
1072
value = client.attributes[self.property]
2653
return # Abstract TestCase class
2655
if hasattr(self, "values_to_set"):
2656
cmd_args = [(value,) for value in self.values_to_set]
2657
values_to_get = getattr(self, "values_to_get",
2660
cmd_args = [() for x in range(len(self.values_to_get))]
2661
values_to_get = self.values_to_get
2662
self.assertTrue(values_to_get)
2663
for value_to_get, cmd_arg in zip(values_to_get, cmd_args):
2664
for clientpath in self.bus.clients:
2665
self.bus.clients[clientpath][self.propname] = (
2667
self.command(*cmd_arg).run(self.bus.clients, self.bus)
2668
self.assertTrue(self.bus.clients)
2669
for clientpath in self.bus.clients:
2670
value = (self.bus.clients[clientpath]
1073
2672
self.assertNotIsInstance(value, Unique)
1074
self.assertEqual(value, value_to_get)
1075
def run_command(self, value, clients):
1076
self.command().run(None, clients)
1078
class TestBumpTimeoutCmd(TestPropertyCmd):
1079
command = BumpTimeoutCmd
1080
property = "LastCheckedOK"
1081
values_to_set = [""]
1083
class TestStartCheckerCmd(TestPropertyCmd):
1084
command = StartCheckerCmd
1085
property = "CheckerRunning"
1086
values_to_set = [dbus.Boolean(True)]
1088
class TestStopCheckerCmd(TestPropertyCmd):
1089
command = StopCheckerCmd
1090
property = "CheckerRunning"
1091
values_to_set = [dbus.Boolean(False)]
1093
class TestApproveByDefaultCmd(TestPropertyCmd):
1094
command = ApproveByDefaultCmd
1095
property = "ApprovedByDefault"
1096
values_to_set = [dbus.Boolean(True)]
1098
class TestDenyByDefaultCmd(TestPropertyCmd):
1099
command = DenyByDefaultCmd
1100
property = "ApprovedByDefault"
1101
values_to_set = [dbus.Boolean(False)]
1103
class TestValueArgumentPropertyCmd(TestPropertyCmd):
1104
"""Abstract class for tests of PropertyCmd classes using the
1105
ValueArgumentMixIn"""
1107
if type(self) is TestValueArgumentPropertyCmd:
1109
return super(TestValueArgumentPropertyCmd, self).runTest()
1110
def run_command(self, value, clients):
1111
self.command(value).run(None, clients)
1113
class TestSetCheckerCmd(TestValueArgumentPropertyCmd):
1114
command = SetCheckerCmd
1115
property = "Checker"
2673
self.assertEqual(value_to_get, value)
2676
class TestEnableCmd(TestPropertySetterCmd):
2677
command = command.Enable
2678
propname = "Enabled"
2679
values_to_get = [True]
2682
class TestDisableCmd(TestPropertySetterCmd):
2683
command = command.Disable
2684
propname = "Enabled"
2685
values_to_get = [False]
2688
class TestBumpTimeoutCmd(TestPropertySetterCmd):
2689
command = command.BumpTimeout
2690
propname = "LastCheckedOK"
2691
values_to_get = [""]
2694
class TestStartCheckerCmd(TestPropertySetterCmd):
2695
command = command.StartChecker
2696
propname = "CheckerRunning"
2697
values_to_get = [True]
2700
class TestStopCheckerCmd(TestPropertySetterCmd):
2701
command = command.StopChecker
2702
propname = "CheckerRunning"
2703
values_to_get = [False]
2706
class TestApproveByDefaultCmd(TestPropertySetterCmd):
2707
command = command.ApproveByDefault
2708
propname = "ApprovedByDefault"
2709
values_to_get = [True]
2712
class TestDenyByDefaultCmd(TestPropertySetterCmd):
2713
command = command.DenyByDefault
2714
propname = "ApprovedByDefault"
2715
values_to_get = [False]
2718
class TestSetCheckerCmd(TestPropertySetterCmd):
2719
command = command.SetChecker
2720
propname = "Checker"
1116
2721
values_to_set = ["", ":", "fping -q -- %s"]
1118
class TestSetHostCmd(TestValueArgumentPropertyCmd):
1119
command = SetHostCmd
1121
values_to_set = ["192.0.2.3", "foo.example.org"]
1123
class TestSetSecretCmd(TestValueArgumentPropertyCmd):
1124
command = SetSecretCmd
1126
values_to_set = [io.BytesIO(b""),
1127
io.BytesIO(b"secret\0xyzzy\nbar")]
1128
values_to_get = [b"", b"secret\0xyzzy\nbar"]
1130
class TestSetTimeoutCmd(TestValueArgumentPropertyCmd):
1131
command = SetTimeoutCmd
1132
property = "Timeout"
1133
values_to_set = [datetime.timedelta(),
1134
datetime.timedelta(minutes=5),
1135
datetime.timedelta(seconds=1),
1136
datetime.timedelta(weeks=1),
1137
datetime.timedelta(weeks=52)]
1138
values_to_get = [0, 300000, 1000, 604800000, 31449600000]
1140
class TestSetExtendedTimeoutCmd(TestValueArgumentPropertyCmd):
1141
command = SetExtendedTimeoutCmd
1142
property = "ExtendedTimeout"
1143
values_to_set = [datetime.timedelta(),
1144
datetime.timedelta(minutes=5),
1145
datetime.timedelta(seconds=1),
1146
datetime.timedelta(weeks=1),
1147
datetime.timedelta(weeks=52)]
1148
values_to_get = [0, 300000, 1000, 604800000, 31449600000]
1150
class TestSetIntervalCmd(TestValueArgumentPropertyCmd):
1151
command = SetIntervalCmd
1152
property = "Interval"
1153
values_to_set = [datetime.timedelta(),
1154
datetime.timedelta(minutes=5),
1155
datetime.timedelta(seconds=1),
1156
datetime.timedelta(weeks=1),
1157
datetime.timedelta(weeks=52)]
1158
values_to_get = [0, 300000, 1000, 604800000, 31449600000]
1160
class TestSetApprovalDelayCmd(TestValueArgumentPropertyCmd):
1161
command = SetApprovalDelayCmd
1162
property = "ApprovalDelay"
1163
values_to_set = [datetime.timedelta(),
1164
datetime.timedelta(minutes=5),
1165
datetime.timedelta(seconds=1),
1166
datetime.timedelta(weeks=1),
1167
datetime.timedelta(weeks=52)]
1168
values_to_get = [0, 300000, 1000, 604800000, 31449600000]
1170
class TestSetApprovalDurationCmd(TestValueArgumentPropertyCmd):
1171
command = SetApprovalDurationCmd
1172
property = "ApprovalDuration"
1173
values_to_set = [datetime.timedelta(),
1174
datetime.timedelta(minutes=5),
1175
datetime.timedelta(seconds=1),
1176
datetime.timedelta(weeks=1),
1177
datetime.timedelta(weeks=52)]
1178
values_to_get = [0, 300000, 1000, 604800000, 31449600000]
1180
class Test_command_from_options(unittest.TestCase):
1182
self.parser = argparse.ArgumentParser()
1183
add_command_line_options(self.parser)
1184
def assert_command_from_args(self, args, command_cls, **cmd_attrs):
1185
"""Assert that parsing ARGS should result in an instance of
1186
COMMAND_CLS with (optionally) all supplied attributes (CMD_ATTRS)."""
1187
options = self.parser.parse_args(args)
1188
check_option_syntax(self.parser, options)
1189
commands = commands_from_options(options)
1190
self.assertEqual(len(commands), 1)
1191
command = commands[0]
1192
self.assertIsInstance(command, command_cls)
1193
for key, value in cmd_attrs.items():
1194
self.assertEqual(getattr(command, key), value)
1195
def test_print_table(self):
1196
self.assert_command_from_args([], PrintTableCmd,
1199
def test_print_table_verbose(self):
1200
self.assert_command_from_args(["--verbose"], PrintTableCmd,
1203
def test_print_table_verbose_short(self):
1204
self.assert_command_from_args(["-v"], PrintTableCmd,
1207
def test_enable(self):
1208
self.assert_command_from_args(["--enable", "foo"], EnableCmd)
1210
def test_enable_short(self):
1211
self.assert_command_from_args(["-e", "foo"], EnableCmd)
1213
def test_disable(self):
1214
self.assert_command_from_args(["--disable", "foo"],
1217
def test_disable_short(self):
1218
self.assert_command_from_args(["-d", "foo"], DisableCmd)
1220
def test_bump_timeout(self):
1221
self.assert_command_from_args(["--bump-timeout", "foo"],
1224
def test_bump_timeout_short(self):
1225
self.assert_command_from_args(["-b", "foo"], BumpTimeoutCmd)
1227
def test_start_checker(self):
1228
self.assert_command_from_args(["--start-checker", "foo"],
1231
def test_stop_checker(self):
1232
self.assert_command_from_args(["--stop-checker", "foo"],
1235
def test_remove(self):
1236
self.assert_command_from_args(["--remove", "foo"],
1239
def test_remove_short(self):
1240
self.assert_command_from_args(["-r", "foo"], RemoveCmd)
1242
def test_checker(self):
1243
self.assert_command_from_args(["--checker", ":", "foo"],
1244
SetCheckerCmd, value_to_set=":")
1246
def test_checker_empty(self):
1247
self.assert_command_from_args(["--checker", "", "foo"],
1248
SetCheckerCmd, value_to_set="")
1250
def test_checker_short(self):
1251
self.assert_command_from_args(["-c", ":", "foo"],
1252
SetCheckerCmd, value_to_set=":")
1254
def test_timeout(self):
1255
self.assert_command_from_args(["--timeout", "PT5M", "foo"],
1257
value_to_set=300000)
1259
def test_timeout_short(self):
1260
self.assert_command_from_args(["-t", "PT5M", "foo"],
1262
value_to_set=300000)
1264
def test_extended_timeout(self):
1265
self.assert_command_from_args(["--extended-timeout", "PT15M",
1267
SetExtendedTimeoutCmd,
1268
value_to_set=900000)
1270
def test_interval(self):
1271
self.assert_command_from_args(["--interval", "PT2M", "foo"],
1273
value_to_set=120000)
1275
def test_interval_short(self):
1276
self.assert_command_from_args(["-i", "PT2M", "foo"],
1278
value_to_set=120000)
1280
def test_approve_by_default(self):
1281
self.assert_command_from_args(["--approve-by-default", "foo"],
1282
ApproveByDefaultCmd)
1284
def test_deny_by_default(self):
1285
self.assert_command_from_args(["--deny-by-default", "foo"],
1288
def test_approval_delay(self):
1289
self.assert_command_from_args(["--approval-delay", "PT30S",
1290
"foo"], SetApprovalDelayCmd,
1293
def test_approval_duration(self):
1294
self.assert_command_from_args(["--approval-duration", "PT1S",
1295
"foo"], SetApprovalDurationCmd,
1298
def test_host(self):
1299
self.assert_command_from_args(["--host", "foo.example.org",
1301
value_to_set="foo.example.org")
1303
def test_host_short(self):
1304
self.assert_command_from_args(["-H", "foo.example.org",
1306
value_to_set="foo.example.org")
1308
def test_secret_devnull(self):
1309
self.assert_command_from_args(["--secret", os.path.devnull,
1310
"foo"], SetSecretCmd,
1313
def test_secret_tempfile(self):
1314
with tempfile.NamedTemporaryFile(mode="r+b") as f:
1315
value = b"secret\0xyzzy\nbar"
1318
self.assert_command_from_args(["--secret", f.name,
1319
"foo"], SetSecretCmd,
1322
def test_secret_devnull_short(self):
1323
self.assert_command_from_args(["-s", os.path.devnull, "foo"],
1324
SetSecretCmd, value_to_set=b"")
1326
def test_secret_tempfile_short(self):
1327
with tempfile.NamedTemporaryFile(mode="r+b") as f:
1328
value = b"secret\0xyzzy\nbar"
1331
self.assert_command_from_args(["-s", f.name, "foo"],
1335
def test_approve(self):
1336
self.assert_command_from_args(["--approve", "foo"],
1339
def test_approve_short(self):
1340
self.assert_command_from_args(["-A", "foo"], ApproveCmd)
1342
def test_deny(self):
1343
self.assert_command_from_args(["--deny", "foo"], DenyCmd)
1345
def test_deny_short(self):
1346
self.assert_command_from_args(["-D", "foo"], DenyCmd)
1348
def test_dump_json(self):
1349
self.assert_command_from_args(["--dump-json"], DumpJSONCmd)
1351
def test_is_enabled(self):
1352
self.assert_command_from_args(["--is-enabled", "foo"],
1355
def test_is_enabled_short(self):
1356
self.assert_command_from_args(["-V", "foo"], IsEnabledCmd)
1358
def test_deny_before_remove(self):
1359
options = self.parser.parse_args(["--deny", "--remove", "foo"])
1360
check_option_syntax(self.parser, options)
1361
commands = commands_from_options(options)
1362
self.assertEqual(len(commands), 2)
1363
self.assertIsInstance(commands[0], DenyCmd)
1364
self.assertIsInstance(commands[1], RemoveCmd)
1366
def test_deny_before_remove_reversed(self):
1367
options = self.parser.parse_args(["--remove", "--deny", "--all"])
1368
check_option_syntax(self.parser, options)
1369
commands = commands_from_options(options)
1370
self.assertEqual(len(commands), 2)
1371
self.assertIsInstance(commands[0], DenyCmd)
1372
self.assertIsInstance(commands[1], RemoveCmd)
1375
class Test_check_option_syntax(unittest.TestCase):
1376
# This mostly corresponds to the definition from has_actions() in
1377
# check_option_syntax()
1379
# The actual values set here are not that important, but we do
1380
# at least stick to the correct types, even though they are
1384
"bump_timeout": True,
1385
"start_checker": True,
1386
"stop_checker": True,
1390
"timeout": datetime.timedelta(),
1391
"extended_timeout": datetime.timedelta(),
1392
"interval": datetime.timedelta(),
1393
"approved_by_default": True,
1394
"approval_delay": datetime.timedelta(),
1395
"approval_duration": datetime.timedelta(),
1397
"secret": io.BytesIO(b"x"),
1403
self.parser = argparse.ArgumentParser()
1404
add_command_line_options(self.parser)
1406
@contextlib.contextmanager
1407
def assertParseError(self):
1408
with self.assertRaises(SystemExit) as e:
1409
with self.temporarily_suppress_stderr():
1411
# Exit code from argparse is guaranteed to be "2". Reference:
1412
# https://docs.python.org/3/library/argparse.html#exiting-methods
1413
self.assertEqual(e.exception.code, 2)
1416
@contextlib.contextmanager
1417
def temporarily_suppress_stderr():
1418
null = os.open(os.path.devnull, os.O_RDWR)
1419
stderrcopy = os.dup(sys.stderr.fileno())
1420
os.dup2(null, sys.stderr.fileno())
1426
os.dup2(stderrcopy, sys.stderr.fileno())
1427
os.close(stderrcopy)
1429
def check_option_syntax(self, options):
1430
check_option_syntax(self.parser, options)
1432
def test_actions_requires_client_or_all(self):
1433
for action, value in self.actions.items():
1434
options = self.parser.parse_args()
1435
setattr(options, action, value)
1436
with self.assertParseError():
1437
self.check_option_syntax(options)
1439
def test_actions_conflicts_with_verbose(self):
1440
for action, value in self.actions.items():
1441
options = self.parser.parse_args()
1442
setattr(options, action, value)
1443
options.verbose = True
1444
with self.assertParseError():
1445
self.check_option_syntax(options)
1447
def test_dump_json_conflicts_with_verbose(self):
1448
options = self.parser.parse_args()
1449
options.dump_json = True
1450
options.verbose = True
1451
with self.assertParseError():
1452
self.check_option_syntax(options)
1454
def test_dump_json_conflicts_with_action(self):
1455
for action, value in self.actions.items():
1456
options = self.parser.parse_args()
1457
setattr(options, action, value)
1458
options.dump_json = True
1459
with self.assertParseError():
1460
self.check_option_syntax(options)
1462
def test_all_can_not_be_alone(self):
1463
options = self.parser.parse_args()
1465
with self.assertParseError():
1466
self.check_option_syntax(options)
1468
def test_all_is_ok_with_any_action(self):
1469
for action, value in self.actions.items():
1470
options = self.parser.parse_args()
1471
setattr(options, action, value)
1473
self.check_option_syntax(options)
1475
def test_is_enabled_fails_without_client(self):
1476
options = self.parser.parse_args()
1477
options.is_enabled = True
1478
with self.assertParseError():
1479
self.check_option_syntax(options)
1481
def test_is_enabled_works_with_one_client(self):
1482
options = self.parser.parse_args()
1483
options.is_enabled = True
1484
options.client = ["foo"]
1485
self.check_option_syntax(options)
1487
def test_is_enabled_fails_with_two_clients(self):
1488
options = self.parser.parse_args()
1489
options.is_enabled = True
1490
options.client = ["foo", "barbar"]
1491
with self.assertParseError():
1492
self.check_option_syntax(options)
2724
class TestSetHostCmd(TestPropertySetterCmd):
2725
command = command.SetHost
2727
values_to_set = ["192.0.2.3", "client.example.org"]
2730
class TestSetSecretCmd(TestPropertySetterCmd):
2731
command = command.SetSecret
2733
def __init__(self, *args, **kwargs):
2734
self.values_to_set = [io.BytesIO(b""),
2735
io.BytesIO(b"secret\0xyzzy\nbar")]
2736
self.values_to_get = [f.getvalue() for f in
2738
super(TestSetSecretCmd, self).__init__(*args, **kwargs)
2741
class TestSetTimeoutCmd(TestPropertySetterCmd):
2742
command = command.SetTimeout
2743
propname = "Timeout"
2744
values_to_set = [datetime.timedelta(),
2745
datetime.timedelta(minutes=5),
2746
datetime.timedelta(seconds=1),
2747
datetime.timedelta(weeks=1),
2748
datetime.timedelta(weeks=52)]
2749
values_to_get = [dt.total_seconds()*1000 for dt in values_to_set]
2752
class TestSetExtendedTimeoutCmd(TestPropertySetterCmd):
2753
command = command.SetExtendedTimeout
2754
propname = "ExtendedTimeout"
2755
values_to_set = [datetime.timedelta(),
2756
datetime.timedelta(minutes=5),
2757
datetime.timedelta(seconds=1),
2758
datetime.timedelta(weeks=1),
2759
datetime.timedelta(weeks=52)]
2760
values_to_get = [dt.total_seconds()*1000 for dt in values_to_set]
2763
class TestSetIntervalCmd(TestPropertySetterCmd):
2764
command = command.SetInterval
2765
propname = "Interval"
2766
values_to_set = [datetime.timedelta(),
2767
datetime.timedelta(minutes=5),
2768
datetime.timedelta(seconds=1),
2769
datetime.timedelta(weeks=1),
2770
datetime.timedelta(weeks=52)]
2771
values_to_get = [dt.total_seconds()*1000 for dt in values_to_set]
2774
class TestSetApprovalDelayCmd(TestPropertySetterCmd):
2775
command = command.SetApprovalDelay
2776
propname = "ApprovalDelay"
2777
values_to_set = [datetime.timedelta(),
2778
datetime.timedelta(minutes=5),
2779
datetime.timedelta(seconds=1),
2780
datetime.timedelta(weeks=1),
2781
datetime.timedelta(weeks=52)]
2782
values_to_get = [dt.total_seconds()*1000 for dt in values_to_set]
2785
class TestSetApprovalDurationCmd(TestPropertySetterCmd):
2786
command = command.SetApprovalDuration
2787
propname = "ApprovalDuration"
2788
values_to_set = [datetime.timedelta(),
2789
datetime.timedelta(minutes=5),
2790
datetime.timedelta(seconds=1),
2791
datetime.timedelta(weeks=1),
2792
datetime.timedelta(weeks=52)]
2793
values_to_get = [dt.total_seconds()*1000 for dt in values_to_set]
1496
def should_only_run_tests():
2797
def parse_test_args():
2798
# type: () -> argparse.Namespace
1497
2799
parser = argparse.ArgumentParser(add_help=False)
1498
parser.add_argument("--check", action='store_true')
2800
parser.add_argument("--check", action="store_true")
2801
parser.add_argument("--prefix", )
1499
2802
args, unknown_args = parser.parse_known_args()
1500
run_tests = args.check
1502
# Remove --check argument from sys.argv
2804
# Remove test options from sys.argv
1503
2805
sys.argv[1:] = unknown_args
1506
2808
# Add all tests from doctest strings
1507
2809
def load_tests(loader, tests, none):