275
## Classes for commands.
277
# Abstract classes first
278
class Command(object):
279
"""Abstract class for commands"""
280
def run(self, clients, bus=None, mandos=None):
281
"""Normal commands should implement run_on_one_client(), but
282
commands which want to operate on all clients at the same time
283
can override this run() method instead."""
285
for clientpath, properties in clients.items():
286
log.debug("D-Bus: Connect to: (busname=%r, path=%r)",
287
dbus_busname, str(clientpath))
288
client = bus.get_object(dbus_busname, clientpath)
289
self.run_on_one_client(client, properties)
291
class PrintCmd(Command):
292
"""Abstract class for commands printing client details"""
293
all_keywords = ("Name", "Enabled", "Timeout", "LastCheckedOK",
294
"Created", "Interval", "Host", "KeyID",
295
"Fingerprint", "CheckerRunning", "LastEnabled",
296
"ApprovalPending", "ApprovedByDefault",
297
"LastApprovalRequest", "ApprovalDelay",
298
"ApprovalDuration", "Checker", "ExtendedTimeout",
299
"Expires", "LastCheckerStatus")
300
def run(self, clients, bus=None, mandos=None):
301
print(self.output(clients.values()))
302
def output(self, clients):
303
raise NotImplementedError()
305
class PropertyCmd(Command):
306
"""Abstract class for Actions for setting one client property"""
307
def run_on_one_client(self, client, properties):
308
"""Set the Client's D-Bus property"""
309
log.debug("D-Bus: %s:%s:%s.Set(%r, %r, %r)", dbus_busname,
310
client.__dbus_object_path__,
311
dbus.PROPERTIES_IFACE, client_dbus_interface,
312
self.propname, self.value_to_set
313
if not isinstance(self.value_to_set, dbus.Boolean)
314
else bool(self.value_to_set))
315
client.Set(client_dbus_interface, self.propname,
317
dbus_interface=dbus.PROPERTIES_IFACE)
320
raise NotImplementedError()
322
class PropertyValueCmd(PropertyCmd):
323
"""Abstract class for PropertyCmd recieving a value as argument"""
324
def __init__(self, value):
325
self.value_to_set = value
327
class MillisecondsPropertyValueArgumentCmd(PropertyValueCmd):
328
"""Abstract class for PropertyValueCmd taking a value argument as
329
a datetime.timedelta() but should store it as milliseconds."""
331
def value_to_set(self):
334
def value_to_set(self, value):
335
"""When setting, convert value from a datetime.timedelta"""
336
self._vts = int(round(value.total_seconds() * 1000))
338
# Actual (non-abstract) command classes
340
class PrintTableCmd(PrintCmd):
341
def __init__(self, verbose=False):
342
self.verbose = verbose
344
def output(self, clients):
345
default_keywords = ("Name", "Enabled", "Timeout", "LastCheckedOK")
346
keywords = default_keywords
348
keywords = self.all_keywords
349
return str(self.TableOfClients(clients, keywords))
351
class TableOfClients(object):
354
"Enabled": "Enabled",
355
"Timeout": "Timeout",
356
"LastCheckedOK": "Last Successful Check",
357
"LastApprovalRequest": "Last Approval Request",
358
"Created": "Created",
359
"Interval": "Interval",
361
"Fingerprint": "Fingerprint",
363
"CheckerRunning": "Check Is Running",
364
"LastEnabled": "Last Enabled",
365
"ApprovalPending": "Approval Is Pending",
366
"ApprovedByDefault": "Approved By Default",
367
"ApprovalDelay": "Approval Delay",
368
"ApprovalDuration": "Approval Duration",
369
"Checker": "Checker",
370
"ExtendedTimeout": "Extended Timeout",
371
"Expires": "Expires",
372
"LastCheckerStatus": "Last Checker Status",
375
def __init__(self, clients, keywords, tableheaders=None):
376
self.clients = clients
377
self.keywords = keywords
378
if tableheaders is not None:
379
self.tableheaders = tableheaders
382
return "\n".join(self.rows())
384
if sys.version_info.major == 2:
385
__unicode__ = __str__
387
return str(self).encode(locale.getpreferredencoding())
390
format_string = self.row_formatting_string()
391
rows = [self.header_line(format_string)]
392
rows.extend(self.client_line(client, format_string)
393
for client in self.clients)
396
def row_formatting_string(self):
397
"Format string used to format table rows"
398
return " ".join("{{{key}:{width}}}".format(
399
width=max(len(self.tableheaders[key]),
400
*(len(self.string_from_client(client, key))
401
for client in self.clients)),
403
for key in self.keywords)
405
def string_from_client(self, client, key):
406
return self.valuetostring(client[key], key)
409
def valuetostring(value, keyword):
410
if isinstance(value, dbus.Boolean):
411
return "Yes" if value else "No"
412
if keyword in ("Timeout", "Interval", "ApprovalDelay",
413
"ApprovalDuration", "ExtendedTimeout"):
414
return milliseconds_to_string(value)
417
def header_line(self, format_string):
418
return format_string.format(**self.tableheaders)
420
def client_line(self, client, format_string):
421
return format_string.format(
422
**{key: self.string_from_client(client, key)
423
for key in self.keywords})
427
class DumpJSONCmd(PrintCmd):
428
def output(self, clients):
429
data = {client["Name"]:
430
{key: self.dbus_boolean_to_bool(client[key])
431
for key in self.all_keywords}
432
for client in clients.values()}
433
return json.dumps(data, indent=4, separators=(',', ': '))
435
def dbus_boolean_to_bool(value):
436
if isinstance(value, dbus.Boolean):
440
class IsEnabledCmd(Command):
441
def run(self, clients, bus=None, mandos=None):
442
client, properties = next(iter(clients.items()))
443
if self.is_enabled(client, properties):
446
def is_enabled(self, client, properties):
447
return properties["Enabled"]
449
class RemoveCmd(Command):
450
def run_on_one_client(self, client, properties):
451
log.debug("D-Bus: %s:%s:%s.RemoveClient(%r)", dbus_busname,
452
server_dbus_path, server_dbus_interface,
453
str(client.__dbus_object_path__))
454
self.mandos.RemoveClient(client.__dbus_object_path__)
456
class ApproveCmd(Command):
457
def run_on_one_client(self, client, properties):
458
log.debug("D-Bus: %s:%s:%s.Approve(True)", dbus_busname,
459
client.__dbus_object_path__, client_dbus_interface)
460
client.Approve(dbus.Boolean(True),
461
dbus_interface=client_dbus_interface)
463
class DenyCmd(Command):
464
def run_on_one_client(self, client, properties):
465
log.debug("D-Bus: %s:%s:%s.Approve(False)", dbus_busname,
466
client.__dbus_object_path__, client_dbus_interface)
467
client.Approve(dbus.Boolean(False),
468
dbus_interface=client_dbus_interface)
470
class EnableCmd(PropertyCmd):
472
value_to_set = dbus.Boolean(True)
474
class DisableCmd(PropertyCmd):
476
value_to_set = dbus.Boolean(False)
478
class BumpTimeoutCmd(PropertyCmd):
479
propname = "LastCheckedOK"
482
class StartCheckerCmd(PropertyCmd):
483
propname = "CheckerRunning"
484
value_to_set = dbus.Boolean(True)
486
class StopCheckerCmd(PropertyCmd):
487
propname = "CheckerRunning"
488
value_to_set = dbus.Boolean(False)
490
class ApproveByDefaultCmd(PropertyCmd):
491
propname = "ApprovedByDefault"
492
value_to_set = dbus.Boolean(True)
494
class DenyByDefaultCmd(PropertyCmd):
495
propname = "ApprovedByDefault"
496
value_to_set = dbus.Boolean(False)
498
class SetCheckerCmd(PropertyValueCmd):
501
class SetHostCmd(PropertyValueCmd):
504
class SetSecretCmd(PropertyValueCmd):
507
def value_to_set(self):
510
def value_to_set(self, value):
511
"""When setting, read data from supplied file object"""
512
self._vts = value.read()
515
class SetTimeoutCmd(MillisecondsPropertyValueArgumentCmd):
518
class SetExtendedTimeoutCmd(MillisecondsPropertyValueArgumentCmd):
519
propname = "ExtendedTimeout"
521
class SetIntervalCmd(MillisecondsPropertyValueArgumentCmd):
522
propname = "Interval"
524
class SetApprovalDelayCmd(MillisecondsPropertyValueArgumentCmd):
525
propname = "ApprovalDelay"
527
class SetApprovalDurationCmd(MillisecondsPropertyValueArgumentCmd):
528
propname = "ApprovalDuration"
530
def add_command_line_options(parser):
531
parser.add_argument("--version", action="version",
532
version="%(prog)s {}".format(version),
533
help="show version number and exit")
534
parser.add_argument("-a", "--all", action="store_true",
535
help="Select all clients")
536
parser.add_argument("-v", "--verbose", action="store_true",
537
help="Print all fields")
538
parser.add_argument("-j", "--dump-json", action="store_true",
539
help="Dump client data in JSON format")
540
enable_disable = parser.add_mutually_exclusive_group()
541
enable_disable.add_argument("-e", "--enable", action="store_true",
542
help="Enable client")
543
enable_disable.add_argument("-d", "--disable",
545
help="disable client")
546
parser.add_argument("-b", "--bump-timeout", action="store_true",
547
help="Bump timeout for client")
548
start_stop_checker = parser.add_mutually_exclusive_group()
549
start_stop_checker.add_argument("--start-checker",
551
help="Start checker for client")
552
start_stop_checker.add_argument("--stop-checker",
554
help="Stop checker for client")
555
parser.add_argument("-V", "--is-enabled", action="store_true",
556
help="Check if client is enabled")
557
parser.add_argument("-r", "--remove", action="store_true",
558
help="Remove client")
559
parser.add_argument("-c", "--checker",
560
help="Set checker command for client")
561
parser.add_argument("-t", "--timeout", type=string_to_delta,
562
help="Set timeout for client")
563
parser.add_argument("--extended-timeout", type=string_to_delta,
564
help="Set extended timeout for client")
565
parser.add_argument("-i", "--interval", type=string_to_delta,
566
help="Set checker interval for client")
567
approve_deny_default = parser.add_mutually_exclusive_group()
568
approve_deny_default.add_argument(
569
"--approve-by-default", action="store_true",
570
default=None, dest="approved_by_default",
571
help="Set client to be approved by default")
572
approve_deny_default.add_argument(
573
"--deny-by-default", action="store_false",
574
dest="approved_by_default",
575
help="Set client to be denied by default")
576
parser.add_argument("--approval-delay", type=string_to_delta,
577
help="Set delay before client approve/deny")
578
parser.add_argument("--approval-duration", type=string_to_delta,
579
help="Set duration of one client approval")
580
parser.add_argument("-H", "--host", help="Set host for client")
581
parser.add_argument("-s", "--secret",
582
type=argparse.FileType(mode="rb"),
583
help="Set password blob (file) for client")
584
approve_deny = parser.add_mutually_exclusive_group()
585
approve_deny.add_argument(
586
"-A", "--approve", action="store_true",
587
help="Approve any current client request")
588
approve_deny.add_argument("-D", "--deny", action="store_true",
589
help="Deny any current client request")
590
parser.add_argument("--debug", action="store_true",
591
help="Debug mode (show D-Bus commands)")
592
parser.add_argument("--check", action="store_true",
593
help="Run self-test")
594
parser.add_argument("client", nargs="*", help="Client name")
597
def commands_from_options(options):
601
if options.dump_json:
602
commands.append(DumpJSONCmd())
605
commands.append(EnableCmd())
608
commands.append(DisableCmd())
610
if options.bump_timeout:
611
commands.append(BumpTimeoutCmd())
613
if options.start_checker:
614
commands.append(StartCheckerCmd())
616
if options.stop_checker:
617
commands.append(StopCheckerCmd())
619
if options.is_enabled:
620
commands.append(IsEnabledCmd())
622
if options.checker is not None:
623
commands.append(SetCheckerCmd(options.checker))
625
if options.timeout is not None:
626
commands.append(SetTimeoutCmd(options.timeout))
628
if options.extended_timeout:
630
SetExtendedTimeoutCmd(options.extended_timeout))
632
if options.interval is not None:
633
commands.append(SetIntervalCmd(options.interval))
635
if options.approved_by_default is not None:
636
if options.approved_by_default:
637
commands.append(ApproveByDefaultCmd())
639
commands.append(DenyByDefaultCmd())
641
if options.approval_delay is not None:
642
commands.append(SetApprovalDelayCmd(options.approval_delay))
644
if options.approval_duration is not None:
646
SetApprovalDurationCmd(options.approval_duration))
648
if options.host is not None:
649
commands.append(SetHostCmd(options.host))
651
if options.secret is not None:
652
commands.append(SetSecretCmd(options.secret))
655
commands.append(ApproveCmd())
658
commands.append(DenyCmd())
661
commands.append(RemoveCmd())
663
# If no command option has been given, show table of clients,
664
# optionally verbosely
666
commands.append(PrintTableCmd(verbose=options.verbose))
671
441
def check_option_syntax(parser, options):
672
442
"""Apply additional restrictions on options, not expressible in
675
def has_actions(options):
676
return any((options.enable,
678
options.bump_timeout,
679
options.start_checker,
680
options.stop_checker,
683
options.checker is not None,
684
options.timeout is not None,
685
options.extended_timeout is not None,
686
options.interval is not None,
687
options.approved_by_default is not None,
688
options.approval_delay is not None,
689
options.approval_duration is not None,
690
options.host is not None,
691
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)
695
if has_actions(options) and not (options.client or options.all):
469
if has_commands(options) and not (options.client or options.all):
696
470
parser.error("Options require clients names or --all.")
697
if options.verbose and has_actions(options):
471
if options.verbose and has_commands(options):
698
472
parser.error("--verbose can only be used alone.")
699
if options.dump_json and (options.verbose
700
or has_actions(options)):
473
if (has_commands(options, (command.DumpJSON,))
474
and (options.verbose or len(options.commands) > 1)):
701
475
parser.error("--dump-json can only be used alone.")
702
if options.all and not has_actions(options):
476
if options.all and not has_commands(options):
703
477
parser.error("--all requires an action.")
704
if options.is_enabled and len(options.client) > 1:
478
if (has_commands(options, (command.IsEnabled,))
479
and len(options.client) > 1):
705
480
parser.error("--is-enabled requires exactly one client")
707
options.remove = False
708
if has_actions(options) and not options.deny:
709
parser.error("--remove can only be combined with --deny")
710
options.remove = True
714
parser = argparse.ArgumentParser()
716
add_command_line_options(parser)
718
options = parser.parse_args()
720
check_option_syntax(parser, options)
722
clientnames = options.client
725
log.setLevel(logging.DEBUG)
728
bus = dbus.SystemBus()
729
log.debug("D-Bus: Connect to: (busname=%r, path=%r)",
730
dbus_busname, server_dbus_path)
731
mandos_dbus_objc = bus.get_object(dbus_busname,
733
except dbus.exceptions.DBusException:
734
log.critical("Could not connect to Mandos server")
737
mandos_serv = dbus.Interface(mandos_dbus_objc,
738
dbus_interface=server_dbus_interface)
739
mandos_serv_object_manager = dbus.Interface(
740
mandos_dbus_objc, dbus_interface=dbus.OBJECT_MANAGER_IFACE)
742
# Filter out log message from dbus module
743
dbus_logger = logging.getLogger("dbus.proxies")
744
class NullFilter(logging.Filter):
745
def filter(self, record):
747
dbus_filter = NullFilter()
749
dbus_logger.addFilter(dbus_filter)
750
log.debug("D-Bus: %s:%s:%s.GetManagedObjects()", dbus_busname,
751
server_dbus_path, dbus.OBJECT_MANAGER_IFACE)
752
mandos_clients = {path: ifs_and_props[client_dbus_interface]
753
for path, ifs_and_props in
754
mandos_serv_object_manager
755
.GetManagedObjects().items()
756
if client_dbus_interface in ifs_and_props}
757
except dbus.exceptions.DBusException as e:
758
log.critical("Failed to access Mandos server through D-Bus:"
762
# restore dbus logger
763
dbus_logger.removeFilter(dbus_filter)
765
# Compile dict of (clients: properties) to process
769
clients = {objpath: properties
770
for objpath, properties in mandos_clients.items()}
772
for name in clientnames:
773
for objpath, properties in mandos_clients.items():
774
if properties["Name"] == name:
775
clients[objpath] = properties
778
log.critical("Client not found on server: %r", name)
781
# Run all commands on clients
782
commands = commands_from_options(options)
783
for command in commands:
784
command.run(clients, bus, mandos_serv)
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"
787
class Test_milliseconds_to_string(unittest.TestCase):
789
self.assertEqual(milliseconds_to_string(93785000),
791
def test_no_days(self):
792
self.assertEqual(milliseconds_to_string(7385000), "02:03:05")
793
def test_all_zero(self):
794
self.assertEqual(milliseconds_to_string(0), "00:00:00")
795
def test_no_fractional_seconds(self):
796
self.assertEqual(milliseconds_to_string(400), "00:00:00")
797
self.assertEqual(milliseconds_to_string(900), "00:00:00")
798
self.assertEqual(milliseconds_to_string(1900), "00:00:01")
800
class Test_string_to_delta(unittest.TestCase):
801
def test_handles_basic_rfc3339(self):
802
self.assertEqual(string_to_delta("PT0S"),
803
datetime.timedelta())
804
self.assertEqual(string_to_delta("P0D"),
805
datetime.timedelta())
806
self.assertEqual(string_to_delta("PT1S"),
807
datetime.timedelta(0, 1))
808
self.assertEqual(string_to_delta("PT2H"),
809
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"))
810
1146
def test_falls_back_to_pre_1_6_1_with_warning(self):
811
# assertLogs only exists in Python 3.4
812
if hasattr(self, "assertLogs"):
813
with self.assertLogs(log, logging.WARNING):
814
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)
816
class WarningFilter(logging.Filter):
817
"""Don't show, but record the presence of, warnings"""
818
def filter(self, record):
819
is_warning = record.levelno >= logging.WARNING
820
self.found = is_warning or getattr(self, "found",
822
return not is_warning
823
warning_filter = WarningFilter()
824
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:
826
value = string_to_delta("2h")
828
log.removeFilter(warning_filter)
829
self.assertTrue(getattr(warning_filter, "found", False))
830
self.assertEqual(value, datetime.timedelta(0, 7200))
833
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):
834
2313
"""Abstract class for tests of command classes"""
837
class MockClient(object):
838
def __init__(self, name, **attributes):
839
self.__dbus_object_path__ = "/clients/{}".format(name)
840
self.attributes = attributes
841
self.attributes["Name"] = name
843
def Set(self, interface, propname, value, dbus_interface):
844
testcase.assertEqual(interface, client_dbus_interface)
845
testcase.assertEqual(dbus_interface,
846
dbus.PROPERTIES_IFACE)
847
self.attributes[propname] = value
848
def Get(self, interface, propname, dbus_interface):
849
testcase.assertEqual(interface, client_dbus_interface)
850
testcase.assertEqual(dbus_interface,
851
dbus.PROPERTIES_IFACE)
852
return self.attributes[propname]
853
def Approve(self, approve, dbus_interface):
854
testcase.assertEqual(dbus_interface,
855
client_dbus_interface)
856
self.calls.append(("Approve", (approve,
858
self.client = MockClient(
860
KeyID=("92ed150794387c03ce684574b1139a65"
861
"94a34f895daaaf09fd8ea90a27cddb12"),
863
Host="foo.example.org",
864
Enabled=dbus.Boolean(True),
866
LastCheckedOK="2019-02-03T00:00:00",
867
Created="2019-01-02T00:00:00",
869
Fingerprint=("778827225BA7DE539C5A"
870
"7CFA59CFF7CDBD9A5920"),
871
CheckerRunning=dbus.Boolean(False),
872
LastEnabled="2019-01-03T00:00:00",
873
ApprovalPending=dbus.Boolean(False),
874
ApprovedByDefault=dbus.Boolean(True),
875
LastApprovalRequest="",
877
ApprovalDuration=1000,
878
Checker="fping -q -- %(host)s",
879
ExtendedTimeout=900000,
880
Expires="2019-02-04T00:00:00",
882
self.other_client = MockClient(
884
KeyID=("0558568eedd67d622f5c83b35a115f79"
885
"6ab612cff5ad227247e46c2b020f441c"),
888
Enabled=dbus.Boolean(True),
890
LastCheckedOK="2019-02-04T00:00:00",
891
Created="2019-01-03T00:00:00",
893
Fingerprint=("3E393AEAEFB84C7E89E2"
894
"F547B3A107558FCA3A27"),
895
CheckerRunning=dbus.Boolean(True),
896
LastEnabled="2019-01-04T00:00:00",
897
ApprovalPending=dbus.Boolean(False),
898
ApprovedByDefault=dbus.Boolean(False),
899
LastApprovalRequest="2019-01-03T00:00:00",
901
ApprovalDuration=1000,
903
ExtendedTimeout=900000,
904
Expires="2019-02-05T00:00:00",
905
LastCheckerStatus=-2)
906
self.clients = collections.OrderedDict(
908
("/clients/foo", self.client.attributes),
909
("/clients/barbar", self.other_client.attributes),
911
self.one_client = {"/clients/foo": self.client.attributes}
916
def get_object(client_bus_name, path):
917
self.assertEqual(client_bus_name, dbus_busname)
919
"/clients/foo": self.client,
920
"/clients/barbar": self.other_client,
924
class TestPrintTableCmd(TestCmd):
925
def test_normal(self):
926
output = PrintTableCmd().output(self.clients.values())
927
expected_output = """
928
Name Enabled Timeout Last Successful Check
929
foo Yes 00:05:00 2019-02-03T00:00:00
930
barbar Yes 00:05:00 2019-02-04T00:00:00
932
self.assertEqual(output, expected_output)
933
def test_verbose(self):
934
output = PrintTableCmd(verbose=True).output(
935
self.clients.values())
936
expected_output = """
937
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
938
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
939
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
941
self.assertEqual(output, expected_output)
942
def test_one_client(self):
943
output = PrintTableCmd().output(self.one_client.values())
944
expected_output = """
945
Name Enabled Timeout Last Successful Check
946
foo Yes 00:05:00 2019-02-03T00:00:00
948
self.assertEqual(output, expected_output)
950
class TestDumpJSONCmd(TestCmd):
952
self.expected_json = {
2315
class FakeMandosBus(dbus.MandosBus):
2316
def __init__(self, testcase):
2317
self.client_properties = {
955
2319
"KeyID": ("92ed150794387c03ce684574b1139a65"
956
2320
"94a34f895daaaf09fd8ea90a27cddb12"),
2321
"Secret": b"secret",
957
2322
"Host": "foo.example.org",
958
2323
"Enabled": True,
959
2324
"Timeout": 300000,
992
2358
"ApprovedByDefault": False,
993
2359
"LastApprovalRequest": "2019-01-03T00:00:00",
994
2360
"ApprovalDelay": 30000,
995
"ApprovalDuration": 1000,
2361
"ApprovalDuration": 93785000,
997
2363
"ExtendedTimeout": 900000,
998
2364
"Expires": "2019-02-05T00:00:00",
999
2365
"LastCheckerStatus": -2,
1002
return super(TestDumpJSONCmd, self).setUp()
1003
def test_normal(self):
1004
json_data = json.loads(DumpJSONCmd().output(self.clients))
1005
self.assertDictEqual(json_data, self.expected_json)
1006
def test_one_client(self):
1007
clients = self.one_client
1008
json_data = json.loads(DumpJSONCmd().output(clients))
1009
expected_json = {"foo": self.expected_json["foo"]}
1010
self.assertDictEqual(json_data, expected_json)
1012
class TestIsEnabledCmd(TestCmd):
1013
def test_is_enabled(self):
1014
self.assertTrue(all(IsEnabledCmd().is_enabled(client, properties)
1015
for client, properties in self.clients.items()))
1016
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):
1017
2410
with self.assertRaises(SystemExit) as e:
1018
IsEnabledCmd().run(self.one_client)
2411
command.IsEnabled().run(self.bus.one_client)
1019
2412
if e.exception.code is not None:
1020
self.assertEqual(e.exception.code, 0)
2413
self.assertEqual(0, e.exception.code)
1022
2415
self.assertIsNone(e.exception.code)
1023
def test_is_enabled_run_exits_with_failure(self):
1024
self.client.attributes["Enabled"] = dbus.Boolean(False)
2417
def test_IsEnabled_exits_with_failure(self):
2418
self.bus.client_properties["Enabled"] = False
1025
2419
with self.assertRaises(SystemExit) as e:
1026
IsEnabledCmd().run(self.one_client)
2420
command.IsEnabled().run(self.bus.one_client)
1027
2421
if isinstance(e.exception.code, int):
1028
self.assertNotEqual(e.exception.code, 0)
2422
self.assertNotEqual(0, e.exception.code)
1030
2424
self.assertIsNotNone(e.exception.code)
1032
class TestRemoveCmd(TestCmd):
1033
def test_remove(self):
1034
class MockMandos(object):
1037
def RemoveClient(self, dbus_path):
1038
self.calls.append(("RemoveClient", (dbus_path,)))
1039
mandos = MockMandos()
1040
super(TestRemoveCmd, self).setUp()
1041
RemoveCmd().run(self.clients, self.bus, mandos)
1042
self.assertEqual(len(mandos.calls), 2)
1043
for clientpath in self.clients:
1044
self.assertIn(("RemoveClient", (clientpath,)),
1047
class TestApproveCmd(TestCmd):
1048
def test_approve(self):
1049
ApproveCmd().run(self.clients, self.bus)
1050
for clientpath in self.clients:
1051
client = self.bus.get_object(dbus_busname, clientpath)
1052
self.assertIn(("Approve", (True, client_dbus_interface)),
1055
class TestDenyCmd(TestCmd):
1056
def test_deny(self):
1057
DenyCmd().run(self.clients, self.bus)
1058
for clientpath in self.clients:
1059
client = self.bus.get_object(dbus_busname, clientpath)
1060
self.assertIn(("Approve", (False, client_dbus_interface)),
1063
class TestEnableCmd(TestCmd):
1064
def test_enable(self):
1065
for clientpath in self.clients:
1066
client = self.bus.get_object(dbus_busname, clientpath)
1067
client.attributes["Enabled"] = False
1069
EnableCmd().run(self.clients, self.bus)
1071
for clientpath in self.clients:
1072
client = self.bus.get_object(dbus_busname, clientpath)
1073
self.assertTrue(client.attributes["Enabled"])
1075
class TestDisableCmd(TestCmd):
1076
def test_disable(self):
1077
DisableCmd().run(self.clients, self.bus)
1078
for clientpath in self.clients:
1079
client = self.bus.get_object(dbus_busname, clientpath)
1080
self.assertFalse(client.attributes["Enabled"])
1082
class Unique(object):
1083
"""Class for objects which exist only to be unique objects, since
1084
unittest.mock.sentinel only exists in Python 3.3"""
1086
class TestPropertyCmd(TestCmd):
1087
"""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"""
1088
2651
def runTest(self):
1089
2652
if not hasattr(self, "command"):
1091
values_to_get = getattr(self, "values_to_get",
1093
for value_to_set, value_to_get in zip(self.values_to_set,
1095
for clientpath in self.clients:
1096
client = self.bus.get_object(dbus_busname, clientpath)
1097
old_value = client.attributes[self.propname]
1098
self.assertNotIsInstance(old_value, Unique)
1099
client.attributes[self.propname] = Unique()
1100
self.run_command(value_to_set, self.clients)
1101
for clientpath in self.clients:
1102
client = self.bus.get_object(dbus_busname, clientpath)
1103
value = client.attributes[self.propname]
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]
1104
2672
self.assertNotIsInstance(value, Unique)
1105
self.assertEqual(value, value_to_get)
1106
def run_command(self, value, clients):
1107
self.command().run(clients, self.bus)
1109
class TestBumpTimeoutCmd(TestPropertyCmd):
1110
command = BumpTimeoutCmd
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
1111
2690
propname = "LastCheckedOK"
1112
values_to_set = [""]
1114
class TestStartCheckerCmd(TestPropertyCmd):
1115
command = StartCheckerCmd
1116
propname = "CheckerRunning"
1117
values_to_set = [dbus.Boolean(True)]
1119
class TestStopCheckerCmd(TestPropertyCmd):
1120
command = StopCheckerCmd
1121
propname = "CheckerRunning"
1122
values_to_set = [dbus.Boolean(False)]
1124
class TestApproveByDefaultCmd(TestPropertyCmd):
1125
command = ApproveByDefaultCmd
1126
propname = "ApprovedByDefault"
1127
values_to_set = [dbus.Boolean(True)]
1129
class TestDenyByDefaultCmd(TestPropertyCmd):
1130
command = DenyByDefaultCmd
1131
propname = "ApprovedByDefault"
1132
values_to_set = [dbus.Boolean(False)]
1134
class TestPropertyValueCmd(TestPropertyCmd):
1135
"""Abstract class for tests of PropertyValueCmd classes"""
1137
if type(self) is TestPropertyValueCmd:
1139
return super(TestPropertyValueCmd, self).runTest()
1140
def run_command(self, value, clients):
1141
self.command(value).run(clients, self.bus)
1143
class TestSetCheckerCmd(TestPropertyValueCmd):
1144
command = SetCheckerCmd
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
1145
2720
propname = "Checker"
1146
2721
values_to_set = ["", ":", "fping -q -- %s"]
1148
class TestSetHostCmd(TestPropertyValueCmd):
1149
command = SetHostCmd
2724
class TestSetHostCmd(TestPropertySetterCmd):
2725
command = command.SetHost
1150
2726
propname = "Host"
1151
values_to_set = ["192.0.2.3", "foo.example.org"]
1153
class TestSetSecretCmd(TestPropertyValueCmd):
1154
command = SetSecretCmd
2727
values_to_set = ["192.0.2.3", "client.example.org"]
2730
class TestSetSecretCmd(TestPropertySetterCmd):
2731
command = command.SetSecret
1155
2732
propname = "Secret"
1156
values_to_set = [io.BytesIO(b""),
1157
io.BytesIO(b"secret\0xyzzy\nbar")]
1158
values_to_get = [b"", b"secret\0xyzzy\nbar"]
1160
class TestSetTimeoutCmd(TestPropertyValueCmd):
1161
command = SetTimeoutCmd
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
1162
2743
propname = "Timeout"
1163
2744
values_to_set = [datetime.timedelta(),
1164
2745
datetime.timedelta(minutes=5),
1165
2746
datetime.timedelta(seconds=1),
1166
2747
datetime.timedelta(weeks=1),
1167
2748
datetime.timedelta(weeks=52)]
1168
values_to_get = [0, 300000, 1000, 604800000, 31449600000]
1170
class TestSetExtendedTimeoutCmd(TestPropertyValueCmd):
1171
command = SetExtendedTimeoutCmd
2749
values_to_get = [dt.total_seconds()*1000 for dt in values_to_set]
2752
class TestSetExtendedTimeoutCmd(TestPropertySetterCmd):
2753
command = command.SetExtendedTimeout
1172
2754
propname = "ExtendedTimeout"
1173
2755
values_to_set = [datetime.timedelta(),
1174
2756
datetime.timedelta(minutes=5),
1175
2757
datetime.timedelta(seconds=1),
1176
2758
datetime.timedelta(weeks=1),
1177
2759
datetime.timedelta(weeks=52)]
1178
values_to_get = [0, 300000, 1000, 604800000, 31449600000]
1180
class TestSetIntervalCmd(TestPropertyValueCmd):
1181
command = SetIntervalCmd
2760
values_to_get = [dt.total_seconds()*1000 for dt in values_to_set]
2763
class TestSetIntervalCmd(TestPropertySetterCmd):
2764
command = command.SetInterval
1182
2765
propname = "Interval"
1183
2766
values_to_set = [datetime.timedelta(),
1184
2767
datetime.timedelta(minutes=5),
1185
2768
datetime.timedelta(seconds=1),
1186
2769
datetime.timedelta(weeks=1),
1187
2770
datetime.timedelta(weeks=52)]
1188
values_to_get = [0, 300000, 1000, 604800000, 31449600000]
1190
class TestSetApprovalDelayCmd(TestPropertyValueCmd):
1191
command = SetApprovalDelayCmd
2771
values_to_get = [dt.total_seconds()*1000 for dt in values_to_set]
2774
class TestSetApprovalDelayCmd(TestPropertySetterCmd):
2775
command = command.SetApprovalDelay
1192
2776
propname = "ApprovalDelay"
1193
2777
values_to_set = [datetime.timedelta(),
1194
2778
datetime.timedelta(minutes=5),
1195
2779
datetime.timedelta(seconds=1),
1196
2780
datetime.timedelta(weeks=1),
1197
2781
datetime.timedelta(weeks=52)]
1198
values_to_get = [0, 300000, 1000, 604800000, 31449600000]
1200
class TestSetApprovalDurationCmd(TestPropertyValueCmd):
1201
command = SetApprovalDurationCmd
2782
values_to_get = [dt.total_seconds()*1000 for dt in values_to_set]
2785
class TestSetApprovalDurationCmd(TestPropertySetterCmd):
2786
command = command.SetApprovalDuration
1202
2787
propname = "ApprovalDuration"
1203
2788
values_to_set = [datetime.timedelta(),
1204
2789
datetime.timedelta(minutes=5),
1205
2790
datetime.timedelta(seconds=1),
1206
2791
datetime.timedelta(weeks=1),
1207
2792
datetime.timedelta(weeks=52)]
1208
values_to_get = [0, 300000, 1000, 604800000, 31449600000]
1210
class Test_command_from_options(unittest.TestCase):
1212
self.parser = argparse.ArgumentParser()
1213
add_command_line_options(self.parser)
1214
def assert_command_from_args(self, args, command_cls, **cmd_attrs):
1215
"""Assert that parsing ARGS should result in an instance of
1216
COMMAND_CLS with (optionally) all supplied attributes (CMD_ATTRS)."""
1217
options = self.parser.parse_args(args)
1218
check_option_syntax(self.parser, options)
1219
commands = commands_from_options(options)
1220
self.assertEqual(len(commands), 1)
1221
command = commands[0]
1222
self.assertIsInstance(command, command_cls)
1223
for key, value in cmd_attrs.items():
1224
self.assertEqual(getattr(command, key), value)
1225
def test_print_table(self):
1226
self.assert_command_from_args([], PrintTableCmd,
1229
def test_print_table_verbose(self):
1230
self.assert_command_from_args(["--verbose"], PrintTableCmd,
1233
def test_print_table_verbose_short(self):
1234
self.assert_command_from_args(["-v"], PrintTableCmd,
1237
def test_enable(self):
1238
self.assert_command_from_args(["--enable", "foo"], EnableCmd)
1240
def test_enable_short(self):
1241
self.assert_command_from_args(["-e", "foo"], EnableCmd)
1243
def test_disable(self):
1244
self.assert_command_from_args(["--disable", "foo"],
1247
def test_disable_short(self):
1248
self.assert_command_from_args(["-d", "foo"], DisableCmd)
1250
def test_bump_timeout(self):
1251
self.assert_command_from_args(["--bump-timeout", "foo"],
1254
def test_bump_timeout_short(self):
1255
self.assert_command_from_args(["-b", "foo"], BumpTimeoutCmd)
1257
def test_start_checker(self):
1258
self.assert_command_from_args(["--start-checker", "foo"],
1261
def test_stop_checker(self):
1262
self.assert_command_from_args(["--stop-checker", "foo"],
1265
def test_remove(self):
1266
self.assert_command_from_args(["--remove", "foo"],
1269
def test_remove_short(self):
1270
self.assert_command_from_args(["-r", "foo"], RemoveCmd)
1272
def test_checker(self):
1273
self.assert_command_from_args(["--checker", ":", "foo"],
1274
SetCheckerCmd, value_to_set=":")
1276
def test_checker_empty(self):
1277
self.assert_command_from_args(["--checker", "", "foo"],
1278
SetCheckerCmd, value_to_set="")
1280
def test_checker_short(self):
1281
self.assert_command_from_args(["-c", ":", "foo"],
1282
SetCheckerCmd, value_to_set=":")
1284
def test_timeout(self):
1285
self.assert_command_from_args(["--timeout", "PT5M", "foo"],
1287
value_to_set=300000)
1289
def test_timeout_short(self):
1290
self.assert_command_from_args(["-t", "PT5M", "foo"],
1292
value_to_set=300000)
1294
def test_extended_timeout(self):
1295
self.assert_command_from_args(["--extended-timeout", "PT15M",
1297
SetExtendedTimeoutCmd,
1298
value_to_set=900000)
1300
def test_interval(self):
1301
self.assert_command_from_args(["--interval", "PT2M", "foo"],
1303
value_to_set=120000)
1305
def test_interval_short(self):
1306
self.assert_command_from_args(["-i", "PT2M", "foo"],
1308
value_to_set=120000)
1310
def test_approve_by_default(self):
1311
self.assert_command_from_args(["--approve-by-default", "foo"],
1312
ApproveByDefaultCmd)
1314
def test_deny_by_default(self):
1315
self.assert_command_from_args(["--deny-by-default", "foo"],
1318
def test_approval_delay(self):
1319
self.assert_command_from_args(["--approval-delay", "PT30S",
1320
"foo"], SetApprovalDelayCmd,
1323
def test_approval_duration(self):
1324
self.assert_command_from_args(["--approval-duration", "PT1S",
1325
"foo"], SetApprovalDurationCmd,
1328
def test_host(self):
1329
self.assert_command_from_args(["--host", "foo.example.org",
1331
value_to_set="foo.example.org")
1333
def test_host_short(self):
1334
self.assert_command_from_args(["-H", "foo.example.org",
1336
value_to_set="foo.example.org")
1338
def test_secret_devnull(self):
1339
self.assert_command_from_args(["--secret", os.path.devnull,
1340
"foo"], SetSecretCmd,
1343
def test_secret_tempfile(self):
1344
with tempfile.NamedTemporaryFile(mode="r+b") as f:
1345
value = b"secret\0xyzzy\nbar"
1348
self.assert_command_from_args(["--secret", f.name,
1349
"foo"], SetSecretCmd,
1352
def test_secret_devnull_short(self):
1353
self.assert_command_from_args(["-s", os.path.devnull, "foo"],
1354
SetSecretCmd, value_to_set=b"")
1356
def test_secret_tempfile_short(self):
1357
with tempfile.NamedTemporaryFile(mode="r+b") as f:
1358
value = b"secret\0xyzzy\nbar"
1361
self.assert_command_from_args(["-s", f.name, "foo"],
1365
def test_approve(self):
1366
self.assert_command_from_args(["--approve", "foo"],
1369
def test_approve_short(self):
1370
self.assert_command_from_args(["-A", "foo"], ApproveCmd)
1372
def test_deny(self):
1373
self.assert_command_from_args(["--deny", "foo"], DenyCmd)
1375
def test_deny_short(self):
1376
self.assert_command_from_args(["-D", "foo"], DenyCmd)
1378
def test_dump_json(self):
1379
self.assert_command_from_args(["--dump-json"], DumpJSONCmd)
1381
def test_is_enabled(self):
1382
self.assert_command_from_args(["--is-enabled", "foo"],
1385
def test_is_enabled_short(self):
1386
self.assert_command_from_args(["-V", "foo"], IsEnabledCmd)
1388
def test_deny_before_remove(self):
1389
options = self.parser.parse_args(["--deny", "--remove", "foo"])
1390
check_option_syntax(self.parser, options)
1391
commands = commands_from_options(options)
1392
self.assertEqual(len(commands), 2)
1393
self.assertIsInstance(commands[0], DenyCmd)
1394
self.assertIsInstance(commands[1], RemoveCmd)
1396
def test_deny_before_remove_reversed(self):
1397
options = self.parser.parse_args(["--remove", "--deny", "--all"])
1398
check_option_syntax(self.parser, options)
1399
commands = commands_from_options(options)
1400
self.assertEqual(len(commands), 2)
1401
self.assertIsInstance(commands[0], DenyCmd)
1402
self.assertIsInstance(commands[1], RemoveCmd)
1405
class Test_check_option_syntax(unittest.TestCase):
1406
# This mostly corresponds to the definition from has_actions() in
1407
# check_option_syntax()
1409
# The actual values set here are not that important, but we do
1410
# at least stick to the correct types, even though they are
1414
"bump_timeout": True,
1415
"start_checker": True,
1416
"stop_checker": True,
1420
"timeout": datetime.timedelta(),
1421
"extended_timeout": datetime.timedelta(),
1422
"interval": datetime.timedelta(),
1423
"approved_by_default": True,
1424
"approval_delay": datetime.timedelta(),
1425
"approval_duration": datetime.timedelta(),
1427
"secret": io.BytesIO(b"x"),
1433
self.parser = argparse.ArgumentParser()
1434
add_command_line_options(self.parser)
1436
@contextlib.contextmanager
1437
def assertParseError(self):
1438
with self.assertRaises(SystemExit) as e:
1439
with self.temporarily_suppress_stderr():
1441
# Exit code from argparse is guaranteed to be "2". Reference:
1442
# https://docs.python.org/3/library/argparse.html#exiting-methods
1443
self.assertEqual(e.exception.code, 2)
1446
@contextlib.contextmanager
1447
def temporarily_suppress_stderr():
1448
null = os.open(os.path.devnull, os.O_RDWR)
1449
stderrcopy = os.dup(sys.stderr.fileno())
1450
os.dup2(null, sys.stderr.fileno())
1456
os.dup2(stderrcopy, sys.stderr.fileno())
1457
os.close(stderrcopy)
1459
def check_option_syntax(self, options):
1460
check_option_syntax(self.parser, options)
1462
def test_actions_requires_client_or_all(self):
1463
for action, value in self.actions.items():
1464
options = self.parser.parse_args()
1465
setattr(options, action, value)
1466
with self.assertParseError():
1467
self.check_option_syntax(options)
1469
def test_actions_conflicts_with_verbose(self):
1470
for action, value in self.actions.items():
1471
options = self.parser.parse_args()
1472
setattr(options, action, value)
1473
options.verbose = True
1474
with self.assertParseError():
1475
self.check_option_syntax(options)
1477
def test_dump_json_conflicts_with_verbose(self):
1478
options = self.parser.parse_args()
1479
options.dump_json = True
1480
options.verbose = True
1481
with self.assertParseError():
1482
self.check_option_syntax(options)
1484
def test_dump_json_conflicts_with_action(self):
1485
for action, value in self.actions.items():
1486
options = self.parser.parse_args()
1487
setattr(options, action, value)
1488
options.dump_json = True
1489
with self.assertParseError():
1490
self.check_option_syntax(options)
1492
def test_all_can_not_be_alone(self):
1493
options = self.parser.parse_args()
1495
with self.assertParseError():
1496
self.check_option_syntax(options)
1498
def test_all_is_ok_with_any_action(self):
1499
for action, value in self.actions.items():
1500
options = self.parser.parse_args()
1501
setattr(options, action, value)
1503
self.check_option_syntax(options)
1505
def test_is_enabled_fails_without_client(self):
1506
options = self.parser.parse_args()
1507
options.is_enabled = True
1508
with self.assertParseError():
1509
self.check_option_syntax(options)
1511
def test_is_enabled_works_with_one_client(self):
1512
options = self.parser.parse_args()
1513
options.is_enabled = True
1514
options.client = ["foo"]
1515
self.check_option_syntax(options)
1517
def test_is_enabled_fails_with_two_clients(self):
1518
options = self.parser.parse_args()
1519
options.is_enabled = True
1520
options.client = ["foo", "barbar"]
1521
with self.assertParseError():
1522
self.check_option_syntax(options)
1524
def test_remove_can_only_be_combined_with_action_deny(self):
1525
for action, value in self.actions.items():
1526
if action in {"remove", "deny"}:
1528
options = self.parser.parse_args()
1529
setattr(options, action, value)
1531
options.remove = True
1532
with self.assertParseError():
1533
self.check_option_syntax(options)
2793
values_to_get = [dt.total_seconds()*1000 for dt in values_to_set]
1537
def should_only_run_tests():
2797
def parse_test_args():
2798
# type: () -> argparse.Namespace
1538
2799
parser = argparse.ArgumentParser(add_help=False)
1539
parser.add_argument("--check", action='store_true')
2800
parser.add_argument("--check", action="store_true")
2801
parser.add_argument("--prefix", )
1540
2802
args, unknown_args = parser.parse_known_args()
1541
run_tests = args.check
1543
# Remove --check argument from sys.argv
2804
# Remove test options from sys.argv
1544
2805
sys.argv[1:] = unknown_args
1547
2808
# Add all tests from doctest strings
1548
2809
def load_tests(loader, tests, none):