274
## Classes for commands.
276
# Abstract classes first
277
class Command(object):
278
"""Abstract class for commands"""
279
def run(self, mandos, clients):
280
"""Normal commands should implement run_on_one_client(), but
281
commands which want to operate on all clients at the same time
282
can override this run() method instead."""
284
for client, properties in clients.items():
285
self.run_on_one_client(client, properties)
287
class PrintCmd(Command):
288
"""Abstract class for commands printing client details"""
289
all_keywords = ("Name", "Enabled", "Timeout", "LastCheckedOK",
290
"Created", "Interval", "Host", "KeyID",
291
"Fingerprint", "CheckerRunning", "LastEnabled",
292
"ApprovalPending", "ApprovedByDefault",
293
"LastApprovalRequest", "ApprovalDelay",
294
"ApprovalDuration", "Checker", "ExtendedTimeout",
295
"Expires", "LastCheckerStatus")
296
def run(self, mandos, clients):
297
print(self.output(clients.values()))
298
def output(self, clients):
299
raise NotImplementedError()
301
class PropertyCmd(Command):
302
"""Abstract class for Actions for setting one client property"""
303
def run_on_one_client(self, client, properties):
304
"""Set the Client's D-Bus property"""
305
log.debug("D-Bus: %s:%s:%s.Set(%r, %r, %r)", busname,
306
client.__dbus_object_path__,
307
dbus.PROPERTIES_IFACE, client_interface,
308
self.propname, self.value_to_set
309
if not isinstance(self.value_to_set, dbus.Boolean)
310
else bool(self.value_to_set))
311
client.Set(client_interface, self.propname, self.value_to_set,
312
dbus_interface=dbus.PROPERTIES_IFACE)
315
raise NotImplementedError()
317
class ValueArgumentMixIn(object):
318
"""Mixin class for commands taking a value as argument"""
319
def __init__(self, value):
320
self.value_to_set = value
322
class MillisecondsValueArgumentMixIn(ValueArgumentMixIn):
323
"""Mixin class for commands taking a value argument as
326
def value_to_set(self):
329
def value_to_set(self, value):
330
"""When setting, convert value to a datetime.timedelta"""
331
self._vts = int(round(value.total_seconds() * 1000))
333
# Actual (non-abstract) command classes
335
class PrintTableCmd(PrintCmd):
336
def __init__(self, verbose=False):
337
self.verbose = verbose
339
def output(self, clients):
340
default_keywords = ("Name", "Enabled", "Timeout", "LastCheckedOK")
341
keywords = default_keywords
343
keywords = self.all_keywords
344
return str(self.TableOfClients(clients, keywords))
346
class TableOfClients(object):
349
"Enabled": "Enabled",
350
"Timeout": "Timeout",
351
"LastCheckedOK": "Last Successful Check",
352
"LastApprovalRequest": "Last Approval Request",
353
"Created": "Created",
354
"Interval": "Interval",
356
"Fingerprint": "Fingerprint",
358
"CheckerRunning": "Check Is Running",
359
"LastEnabled": "Last Enabled",
360
"ApprovalPending": "Approval Is Pending",
361
"ApprovedByDefault": "Approved By Default",
362
"ApprovalDelay": "Approval Delay",
363
"ApprovalDuration": "Approval Duration",
364
"Checker": "Checker",
365
"ExtendedTimeout": "Extended Timeout",
366
"Expires": "Expires",
367
"LastCheckerStatus": "Last Checker Status",
370
def __init__(self, clients, keywords, tableheaders=None):
371
self.clients = clients
372
self.keywords = keywords
373
if tableheaders is not None:
374
self.tableheaders = tableheaders
377
return "\n".join(self.rows())
379
if sys.version_info.major == 2:
380
__unicode__ = __str__
382
return str(self).encode(locale.getpreferredencoding())
385
format_string = self.row_formatting_string()
386
rows = [self.header_line(format_string)]
387
rows.extend(self.client_line(client, format_string)
388
for client in self.clients)
391
def row_formatting_string(self):
392
"Format string used to format table rows"
393
return " ".join("{{{key}:{width}}}".format(
394
width=max(len(self.tableheaders[key]),
395
*(len(self.string_from_client(client, key))
396
for client in self.clients)),
398
for key in self.keywords)
400
def string_from_client(self, client, key):
401
return self.valuetostring(client[key], key)
404
def valuetostring(value, keyword):
405
if isinstance(value, dbus.Boolean):
406
return "Yes" if value else "No"
407
if keyword in ("Timeout", "Interval", "ApprovalDelay",
408
"ApprovalDuration", "ExtendedTimeout"):
409
return milliseconds_to_string(value)
412
def header_line(self, format_string):
413
return format_string.format(**self.tableheaders)
415
def client_line(self, client, format_string):
416
return format_string.format(
417
**{key: self.string_from_client(client, key)
418
for key in self.keywords})
422
class DumpJSONCmd(PrintCmd):
423
def output(self, clients):
424
data = {client["Name"]:
425
{key: self.dbus_boolean_to_bool(client[key])
426
for key in self.all_keywords}
427
for client in clients.values()}
428
return json.dumps(data, indent=4, separators=(',', ': '))
430
def dbus_boolean_to_bool(value):
431
if isinstance(value, dbus.Boolean):
435
class IsEnabledCmd(Command):
436
def run_on_one_client(self, client, properties):
437
if self.is_enabled(client, properties):
440
def is_enabled(self, client, properties):
441
log.debug("D-Bus: %s:%s:%s.Get(%r, %r)", busname,
442
client.__dbus_object_path__,
443
dbus.PROPERTIES_IFACE, client_interface,
445
return bool(client.Get(client_interface, "Enabled",
446
dbus_interface=dbus.PROPERTIES_IFACE))
448
class RemoveCmd(Command):
449
def run_on_one_client(self, client, properties):
450
log.debug("D-Bus: %s:%s:%s.RemoveClient(%r)", busname,
451
server_path, server_interface,
452
str(client.__dbus_object_path__))
453
self.mandos.RemoveClient(client.__dbus_object_path__)
455
class ApproveCmd(Command):
456
def run_on_one_client(self, client, properties):
457
log.debug("D-Bus: %s:%s.Approve(True)",
458
client.__dbus_object_path__, client_interface)
459
client.Approve(dbus.Boolean(True),
460
dbus_interface=client_interface)
462
class DenyCmd(Command):
463
def run_on_one_client(self, client, properties):
464
log.debug("D-Bus: %s:%s.Approve(False)",
465
client.__dbus_object_path__, client_interface)
466
client.Approve(dbus.Boolean(False),
467
dbus_interface=client_interface)
469
class EnableCmd(PropertyCmd):
471
value_to_set = dbus.Boolean(True)
473
class DisableCmd(PropertyCmd):
475
value_to_set = dbus.Boolean(False)
477
class BumpTimeoutCmd(PropertyCmd):
478
propname = "LastCheckedOK"
481
class StartCheckerCmd(PropertyCmd):
482
propname = "CheckerRunning"
483
value_to_set = dbus.Boolean(True)
485
class StopCheckerCmd(PropertyCmd):
486
propname = "CheckerRunning"
487
value_to_set = dbus.Boolean(False)
489
class ApproveByDefaultCmd(PropertyCmd):
490
propname = "ApprovedByDefault"
491
value_to_set = dbus.Boolean(True)
493
class DenyByDefaultCmd(PropertyCmd):
494
propname = "ApprovedByDefault"
495
value_to_set = dbus.Boolean(False)
497
class SetCheckerCmd(PropertyCmd, ValueArgumentMixIn):
500
class SetHostCmd(PropertyCmd, ValueArgumentMixIn):
503
class SetSecretCmd(PropertyCmd, ValueArgumentMixIn):
506
def value_to_set(self):
509
def value_to_set(self, value):
510
"""When setting, read data from supplied file object"""
511
self._vts = value.read()
514
class SetTimeoutCmd(PropertyCmd, MillisecondsValueArgumentMixIn):
517
class SetExtendedTimeoutCmd(PropertyCmd,
518
MillisecondsValueArgumentMixIn):
519
propname = "ExtendedTimeout"
521
class SetIntervalCmd(PropertyCmd, MillisecondsValueArgumentMixIn):
522
propname = "Interval"
524
class SetApprovalDelayCmd(PropertyCmd,
525
MillisecondsValueArgumentMixIn):
526
propname = "ApprovalDelay"
528
class SetApprovalDurationCmd(PropertyCmd,
529
MillisecondsValueArgumentMixIn):
530
propname = "ApprovalDuration"
532
def add_command_line_options(parser):
533
parser.add_argument("--version", action="version",
534
version="%(prog)s {}".format(version),
535
help="show version number and exit")
536
parser.add_argument("-a", "--all", action="store_true",
537
help="Select all clients")
538
parser.add_argument("-v", "--verbose", action="store_true",
539
help="Print all fields")
540
parser.add_argument("-j", "--dump-json", action="store_true",
541
help="Dump client data in JSON format")
542
enable_disable = parser.add_mutually_exclusive_group()
543
enable_disable.add_argument("-e", "--enable", action="store_true",
544
help="Enable client")
545
enable_disable.add_argument("-d", "--disable",
547
help="disable client")
548
parser.add_argument("-b", "--bump-timeout", action="store_true",
549
help="Bump timeout for client")
550
start_stop_checker = parser.add_mutually_exclusive_group()
551
start_stop_checker.add_argument("--start-checker",
553
help="Start checker for client")
554
start_stop_checker.add_argument("--stop-checker",
556
help="Stop checker for client")
557
parser.add_argument("-V", "--is-enabled", action="store_true",
558
help="Check if client is enabled")
559
parser.add_argument("-r", "--remove", action="store_true",
560
help="Remove client")
561
parser.add_argument("-c", "--checker",
562
help="Set checker command for client")
563
parser.add_argument("-t", "--timeout", type=string_to_delta,
564
help="Set timeout for client")
565
parser.add_argument("--extended-timeout", type=string_to_delta,
566
help="Set extended timeout for client")
567
parser.add_argument("-i", "--interval", type=string_to_delta,
568
help="Set checker interval for client")
569
approve_deny_default = parser.add_mutually_exclusive_group()
570
approve_deny_default.add_argument(
571
"--approve-by-default", action="store_true",
572
default=None, dest="approved_by_default",
573
help="Set client to be approved by default")
574
approve_deny_default.add_argument(
575
"--deny-by-default", action="store_false",
576
dest="approved_by_default",
577
help="Set client to be denied by default")
578
parser.add_argument("--approval-delay", type=string_to_delta,
579
help="Set delay before client approve/deny")
580
parser.add_argument("--approval-duration", type=string_to_delta,
581
help="Set duration of one client approval")
582
parser.add_argument("-H", "--host", help="Set host for client")
583
parser.add_argument("-s", "--secret",
584
type=argparse.FileType(mode="rb"),
585
help="Set password blob (file) for client")
586
approve_deny = parser.add_mutually_exclusive_group()
587
approve_deny.add_argument(
588
"-A", "--approve", action="store_true",
589
help="Approve any current client request")
590
approve_deny.add_argument("-D", "--deny", action="store_true",
591
help="Deny any current client request")
592
parser.add_argument("--debug", action="store_true",
593
help="Debug mode (show D-Bus commands)")
594
parser.add_argument("--check", action="store_true",
595
help="Run self-test")
596
parser.add_argument("client", nargs="*", help="Client name")
599
def commands_from_options(options):
603
if options.dump_json:
604
commands.append(DumpJSONCmd())
607
commands.append(EnableCmd())
610
commands.append(DisableCmd())
612
if options.bump_timeout:
613
commands.append(BumpTimeoutCmd())
615
if options.start_checker:
616
commands.append(StartCheckerCmd())
618
if options.stop_checker:
619
commands.append(StopCheckerCmd())
621
if options.is_enabled:
622
commands.append(IsEnabledCmd())
624
if options.checker is not None:
625
commands.append(SetCheckerCmd(options.checker))
627
if options.timeout is not None:
628
commands.append(SetTimeoutCmd(options.timeout))
630
if options.extended_timeout:
632
SetExtendedTimeoutCmd(options.extended_timeout))
634
if options.interval is not None:
635
commands.append(SetIntervalCmd(options.interval))
637
if options.approved_by_default is not None:
638
if options.approved_by_default:
639
commands.append(ApproveByDefaultCmd())
641
commands.append(DenyByDefaultCmd())
643
if options.approval_delay is not None:
644
commands.append(SetApprovalDelayCmd(options.approval_delay))
646
if options.approval_duration is not None:
648
SetApprovalDurationCmd(options.approval_duration))
650
if options.host is not None:
651
commands.append(SetHostCmd(options.host))
653
if options.secret is not None:
654
commands.append(SetSecretCmd(options.secret))
657
commands.append(ApproveCmd())
660
commands.append(DenyCmd())
663
commands.append(RemoveCmd())
665
# If no command option has been given, show table of clients,
666
# optionally verbosely
668
commands.append(PrintTableCmd(verbose=options.verbose))
424
673
def check_option_syntax(parser, options):
425
674
"""Apply additional restrictions on options, not expressible in
428
def has_commands(options, commands=None):
430
commands = (command.Enable,
433
command.StartChecker,
439
command.SetExtendedTimeout,
441
command.ApproveByDefault,
442
command.DenyByDefault,
443
command.SetApprovalDelay,
444
command.SetApprovalDuration,
449
return any(isinstance(cmd, commands)
450
for cmd in options.commands)
677
def has_actions(options):
678
return any((options.enable,
680
options.bump_timeout,
681
options.start_checker,
682
options.stop_checker,
685
options.checker is not None,
686
options.timeout is not None,
687
options.extended_timeout is not None,
688
options.interval is not None,
689
options.approved_by_default is not None,
690
options.approval_delay is not None,
691
options.approval_duration is not None,
692
options.host is not None,
693
options.secret is not None,
452
if has_commands(options) and not (options.client or options.all):
697
if has_actions(options) and not (options.client or options.all):
453
698
parser.error("Options require clients names or --all.")
454
if options.verbose and has_commands(options):
699
if options.verbose and has_actions(options):
455
700
parser.error("--verbose can only be used alone.")
456
if (has_commands(options, (command.DumpJSON,))
457
and (options.verbose or len(options.commands) > 1)):
701
if options.dump_json and (options.verbose
702
or has_actions(options)):
458
703
parser.error("--dump-json can only be used alone.")
459
if options.all and not has_commands(options):
704
if options.all and not has_actions(options):
460
705
parser.error("--all requires an action.")
461
if (has_commands(options, (command.IsEnabled,))
462
and len(options.client) > 1):
706
if options.is_enabled and len(options.client) > 1:
463
707
parser.error("--is-enabled requires exactly one client")
464
if (len(options.commands) > 1
465
and has_commands(options, (command.Remove,))
466
and not has_commands(options, (command.Deny,))):
467
parser.error("--remove can only be combined with --deny")
472
class SystemBus(object):
474
object_manager_iface = "org.freedesktop.DBus.ObjectManager"
475
def get_managed_objects(self, busname, objectpath):
476
return self.call_method("GetManagedObjects", busname,
478
self.object_manager_iface)
480
properties_iface = "org.freedesktop.DBus.Properties"
481
def set_property(self, busname, objectpath, interface, key,
483
self.call_method("Set", busname, objectpath,
484
self.properties_iface, interface, key,
488
class MandosBus(SystemBus):
489
busname_domain = "se.recompile"
490
busname = busname_domain + ".Mandos"
492
server_interface = busname_domain + ".Mandos"
493
client_interface = busname_domain + ".Mandos.Client"
496
def get_clients_and_properties(self):
497
managed_objects = self.get_managed_objects(
498
self.busname, self.server_path)
499
return {objpath: properties[self.client_interface]
500
for objpath, properties in managed_objects.items()
501
if self.client_interface in properties}
503
def set_client_property(self, objectpath, key, value):
504
return self.set_property(self.busname, objectpath,
505
self.client_interface, key,
508
def call_client_method(self, objectpath, method, *args):
509
return self.call_method(method, self.busname, objectpath,
510
self.client_interface, *args)
512
def call_server_method(self, method, *args):
513
return self.call_method(method, self.busname,
515
self.server_interface, *args)
517
class Error(Exception):
520
class ConnectFailed(Error):
524
class dbus_python_adapter(object):
526
class SystemBus(dbus.MandosBus):
527
"""Use dbus-python"""
529
def __init__(self, module=dbus_python):
530
self.dbus_python = module
531
self.bus = self.dbus_python.SystemBus()
533
@contextlib.contextmanager
534
def convert_exception(self, exception_class=dbus.Error):
537
except self.dbus_python.exceptions.DBusException as e:
538
# This does what "raise from" would do
539
exc = exception_class(*e.args)
543
def call_method(self, methodname, busname, objectpath,
545
proxy_object = self.get_object(busname, objectpath)
546
log.debug("D-Bus: %s:%s:%s.%s(%s)", busname, objectpath,
547
interface, methodname,
548
", ".join(repr(a) for a in args))
549
method = getattr(proxy_object, methodname)
550
with self.convert_exception():
551
with dbus_python_adapter.SilenceLogger(
553
value = method(*args, dbus_interface=interface)
554
return self.type_filter(value)
556
def get_object(self, busname, objectpath):
557
log.debug("D-Bus: Connect to: (busname=%r, path=%r)",
559
with self.convert_exception(dbus.ConnectFailed):
560
return self.bus.get_object(busname, objectpath)
562
def type_filter(self, value):
563
"""Convert the most bothersome types to Python types"""
564
if isinstance(value, self.dbus_python.Boolean):
566
if isinstance(value, self.dbus_python.ObjectPath):
568
# Also recurse into dictionaries
569
if isinstance(value, self.dbus_python.Dictionary):
570
return {self.type_filter(key):
571
self.type_filter(subval)
572
for key, subval in value.items()}
575
def set_client_property(self, objectpath, key, value):
577
if not isinstance(value, bytes):
578
value = value.encode("utf-8")
579
value = self.dbus_python.ByteArray(value)
580
return self.set_property(self.busname, objectpath,
581
self.client_interface, key,
584
class SilenceLogger(object):
585
"Simple context manager to silence a particular logger"
586
def __init__(self, loggername):
587
self.logger = logging.getLogger(loggername)
590
self.logger.addFilter(self.nullfilter)
592
class NullFilter(logging.Filter):
593
def filter(self, record):
596
nullfilter = NullFilter()
598
def __exit__(self, exc_type, exc_val, exc_tb):
599
self.logger.removeFilter(self.nullfilter)
602
class CachingBus(SystemBus):
603
"""A caching layer for dbus_python_adapter.SystemBus"""
604
def __init__(self, *args, **kwargs):
605
self.object_cache = {}
606
super(dbus_python_adapter.CachingBus,
607
self).__init__(*args, **kwargs)
608
def get_object(self, busname, objectpath):
610
return self.object_cache[(busname, objectpath)]
613
dbus_python_adapter.CachingBus,
614
self).get_object(busname, objectpath)
615
self.object_cache[(busname, objectpath)] = new_object
619
class pydbus_adapter(object):
620
class SystemBus(dbus.MandosBus):
621
def __init__(self, module=pydbus):
623
self.bus = self.pydbus.SystemBus()
625
@contextlib.contextmanager
626
def convert_exception(self, exception_class=dbus.Error):
629
except gi.repository.GLib.Error as e:
630
# This does what "raise from" would do
631
exc = exception_class(*e.args)
635
def call_method(self, methodname, busname, objectpath,
637
proxy_object = self.get(busname, objectpath)
638
log.debug("D-Bus: %s:%s:%s.%s(%s)", busname, objectpath,
639
interface, methodname,
640
", ".join(repr(a) for a in args))
641
method = getattr(proxy_object[interface], methodname)
642
with self.convert_exception():
645
def get(self, busname, objectpath):
646
log.debug("D-Bus: Connect to: (busname=%r, path=%r)",
648
with self.convert_exception(dbus.ConnectFailed):
649
if sys.version_info.major <= 2:
650
with warnings.catch_warnings():
651
warnings.filterwarnings(
652
"ignore", "", DeprecationWarning,
653
r"^xml\.etree\.ElementTree$")
654
return self.bus.get(busname, objectpath)
656
return self.bus.get(busname, objectpath)
658
def set_property(self, busname, objectpath, interface, key,
660
proxy_object = self.get(busname, objectpath)
661
log.debug("D-Bus: %s:%s:%s.Set(%r, %r, %r)", busname,
662
objectpath, self.properties_iface, interface,
664
setattr(proxy_object[interface], key, value)
666
class CachingBus(SystemBus):
667
"""A caching layer for pydbus_adapter.SystemBus"""
668
def __init__(self, *args, **kwargs):
669
self.object_cache = {}
670
super(pydbus_adapter.CachingBus,
671
self).__init__(*args, **kwargs)
672
def get(self, busname, objectpath):
674
return self.object_cache[(busname, objectpath)]
676
new_object = (super(pydbus_adapter.CachingBus, self)
677
.get(busname, objectpath))
678
self.object_cache[(busname, objectpath)] = new_object
682
def commands_from_options(options):
684
commands = list(options.commands)
686
def find_cmd(cmd, commands):
688
for i, c in enumerate(commands):
689
if isinstance(c, cmd):
693
# If command.Remove is present, move any instances of command.Deny
694
# to occur ahead of command.Remove.
695
index_of_remove = find_cmd(command.Remove, commands)
696
before_remove = commands[:index_of_remove]
697
after_remove = commands[index_of_remove:]
699
for cmd in after_remove:
700
if isinstance(cmd, command.Deny):
701
before_remove.append(cmd)
703
cleaned_after.append(cmd)
704
if cleaned_after != after_remove:
705
commands = before_remove + cleaned_after
707
# If no command option has been given, show table of clients,
708
# optionally verbosely
710
commands.append(command.PrintTable(verbose=options.verbose))
715
class command(object):
716
"""A namespace for command classes"""
719
"""Abstract base class for commands"""
720
def run(self, clients, bus=None):
721
"""Normal commands should implement run_on_one_client(),
722
but commands which want to operate on all clients at the same time can
723
override this run() method instead.
726
for client, properties in clients.items():
727
self.run_on_one_client(client, properties)
730
class IsEnabled(Base):
731
def run(self, clients, bus=None):
732
properties = next(iter(clients.values()))
733
if properties["Enabled"]:
739
def run_on_one_client(self, client, properties):
740
self.bus.call_client_method(client, "Approve", True)
744
def run_on_one_client(self, client, properties):
745
self.bus.call_client_method(client, "Approve", False)
749
def run(self, clients, bus):
750
for clientpath in frozenset(clients.keys()):
751
bus.call_server_method("RemoveClient", clientpath)
755
"""Abstract class for commands outputting client details"""
756
all_keywords = ("Name", "Enabled", "Timeout", "LastCheckedOK",
757
"Created", "Interval", "Host", "KeyID",
758
"Fingerprint", "CheckerRunning",
759
"LastEnabled", "ApprovalPending",
760
"ApprovedByDefault", "LastApprovalRequest",
761
"ApprovalDelay", "ApprovalDuration",
762
"Checker", "ExtendedTimeout", "Expires",
766
class DumpJSON(Output):
767
def run(self, clients, bus=None):
768
data = {properties["Name"]:
769
{key: properties[key]
770
for key in self.all_keywords}
771
for properties in clients.values()}
772
print(json.dumps(data, indent=4, separators=(',', ': ')))
775
class PrintTable(Output):
776
def __init__(self, verbose=False):
777
self.verbose = verbose
779
def run(self, clients, bus=None):
780
default_keywords = ("Name", "Enabled", "Timeout",
782
keywords = default_keywords
784
keywords = self.all_keywords
785
print(self.TableOfClients(clients.values(), keywords))
787
class TableOfClients(object):
790
"Enabled": "Enabled",
791
"Timeout": "Timeout",
792
"LastCheckedOK": "Last Successful Check",
793
"LastApprovalRequest": "Last Approval Request",
794
"Created": "Created",
795
"Interval": "Interval",
797
"Fingerprint": "Fingerprint",
799
"CheckerRunning": "Check Is Running",
800
"LastEnabled": "Last Enabled",
801
"ApprovalPending": "Approval Is Pending",
802
"ApprovedByDefault": "Approved By Default",
803
"ApprovalDelay": "Approval Delay",
804
"ApprovalDuration": "Approval Duration",
805
"Checker": "Checker",
806
"ExtendedTimeout": "Extended Timeout",
807
"Expires": "Expires",
808
"LastCheckerStatus": "Last Checker Status",
811
def __init__(self, clients, keywords):
812
self.clients = clients
813
self.keywords = keywords
816
return "\n".join(self.rows())
818
if sys.version_info.major == 2:
819
__unicode__ = __str__
821
return str(self).encode(
822
locale.getpreferredencoding())
825
format_string = self.row_formatting_string()
826
rows = [self.header_line(format_string)]
827
rows.extend(self.client_line(client, format_string)
828
for client in self.clients)
831
def row_formatting_string(self):
832
"Format string used to format table rows"
833
return " ".join("{{{key}:{width}}}".format(
834
width=max(len(self.tableheaders[key]),
835
*(len(self.string_from_client(client,
837
for client in self.clients)),
839
for key in self.keywords)
841
def string_from_client(self, client, key):
842
return self.valuetostring(client[key], key)
845
def valuetostring(cls, value, keyword):
846
if isinstance(value, bool):
847
return "Yes" if value else "No"
848
if keyword in ("Timeout", "Interval", "ApprovalDelay",
849
"ApprovalDuration", "ExtendedTimeout"):
850
return cls.milliseconds_to_string(value)
853
def header_line(self, format_string):
854
return format_string.format(**self.tableheaders)
856
def client_line(self, client, format_string):
857
return format_string.format(
858
**{key: self.string_from_client(client, key)
859
for key in self.keywords})
862
def milliseconds_to_string(ms):
863
td = datetime.timedelta(0, 0, 0, ms)
864
return ("{days}{hours:02}:{minutes:02}:{seconds:02}"
865
.format(days="{}T".format(td.days)
867
hours=td.seconds // 3600,
868
minutes=(td.seconds % 3600) // 60,
869
seconds=td.seconds % 60))
872
class PropertySetter(Base):
873
"Abstract class for Actions for setting one client property"
875
def run_on_one_client(self, client, properties=None):
876
"""Set the Client's D-Bus property"""
877
self.bus.set_client_property(client, self.propname,
882
raise NotImplementedError()
885
class Enable(PropertySetter):
890
class Disable(PropertySetter):
895
class BumpTimeout(PropertySetter):
896
propname = "LastCheckedOK"
900
class StartChecker(PropertySetter):
901
propname = "CheckerRunning"
905
class StopChecker(PropertySetter):
906
propname = "CheckerRunning"
910
class ApproveByDefault(PropertySetter):
911
propname = "ApprovedByDefault"
915
class DenyByDefault(PropertySetter):
916
propname = "ApprovedByDefault"
920
class PropertySetterValue(PropertySetter):
921
"""Abstract class for PropertySetter recieving a value as
922
constructor argument instead of a class attribute."""
923
def __init__(self, value):
924
self.value_to_set = value
927
def argparse(cls, argtype):
929
return cls(argtype(arg))
932
class SetChecker(PropertySetterValue):
936
class SetHost(PropertySetterValue):
940
class SetSecret(PropertySetterValue):
944
def value_to_set(self):
948
def value_to_set(self, value):
949
"""When setting, read data from supplied file object"""
950
self._vts = value.read()
954
class PropertySetterValueMilliseconds(PropertySetterValue):
955
"""Abstract class for PropertySetterValue taking a value
956
argument as a datetime.timedelta() but should store it as
960
def value_to_set(self):
964
def value_to_set(self, value):
965
"When setting, convert value from a datetime.timedelta"
966
self._vts = int(round(value.total_seconds() * 1000))
969
class SetTimeout(PropertySetterValueMilliseconds):
973
class SetExtendedTimeout(PropertySetterValueMilliseconds):
974
propname = "ExtendedTimeout"
977
class SetInterval(PropertySetterValueMilliseconds):
978
propname = "Interval"
981
class SetApprovalDelay(PropertySetterValueMilliseconds):
982
propname = "ApprovalDelay"
985
class SetApprovalDuration(PropertySetterValueMilliseconds):
986
propname = "ApprovalDuration"
709
options.remove = False
710
if has_actions(options) and not options.deny:
711
parser.error("--remove can only be combined with --deny")
712
options.remove = True
716
parser = argparse.ArgumentParser()
718
add_command_line_options(parser)
720
options = parser.parse_args()
722
check_option_syntax(parser, options)
724
clientnames = options.client
727
log.setLevel(logging.DEBUG)
730
bus = dbus.SystemBus()
731
log.debug("D-Bus: Connect to: (name=%r, path=%r)", busname,
733
mandos_dbus_objc = bus.get_object(busname, server_path)
734
except dbus.exceptions.DBusException:
735
log.critical("Could not connect to Mandos server")
738
mandos_serv = dbus.Interface(mandos_dbus_objc,
739
dbus_interface=server_interface)
740
mandos_serv_object_manager = dbus.Interface(
741
mandos_dbus_objc, dbus_interface=dbus.OBJECT_MANAGER_IFACE)
743
# Filter out log message from dbus module
744
dbus_logger = logging.getLogger("dbus.proxies")
745
class NullFilter(logging.Filter):
746
def filter(self, record):
748
dbus_filter = NullFilter()
750
dbus_logger.addFilter(dbus_filter)
751
log.debug("D-Bus: %s:%s:%s.GetManagedObjects()", busname,
752
server_path, dbus.OBJECT_MANAGER_IFACE)
753
mandos_clients = {path: ifs_and_props[client_interface]
754
for path, ifs_and_props in
755
mandos_serv_object_manager
756
.GetManagedObjects().items()
757
if client_interface in ifs_and_props}
758
except dbus.exceptions.DBusException as e:
759
log.critical("Failed to access Mandos server through D-Bus:"
763
# restore dbus logger
764
dbus_logger.removeFilter(dbus_filter)
766
# Compile dict of (clients: properties) to process
770
clients = {(log.debug("D-Bus: Connect to: (name=%r, path=%r)",
771
busname, str(path)) and False) or
772
bus.get_object(busname, path): properties
773
for path, properties in mandos_clients.items()}
775
for name in clientnames:
776
for path, client in mandos_clients.items():
777
if client["Name"] == name:
778
log.debug("D-Bus: Connect to: (name=%r, path=%r)",
780
client_objc = bus.get_object(busname, path)
781
clients[client_objc] = client
784
log.critical("Client not found on server: %r", name)
787
# Run all commands on clients
788
commands = commands_from_options(options)
789
for command in commands:
790
command.run(mandos_serv, clients)
990
class TestCaseWithAssertLogs(unittest.TestCase):
991
"""unittest.TestCase.assertLogs only exists in Python 3.4"""
993
if not hasattr(unittest.TestCase, "assertLogs"):
994
@contextlib.contextmanager
995
def assertLogs(self, logger, level=logging.INFO):
996
capturing_handler = self.CapturingLevelHandler(level)
997
old_level = logger.level
998
old_propagate = logger.propagate
999
logger.addHandler(capturing_handler)
1000
logger.setLevel(level)
1001
logger.propagate = False
1003
yield capturing_handler.watcher
1005
logger.propagate = old_propagate
1006
logger.removeHandler(capturing_handler)
1007
logger.setLevel(old_level)
1008
self.assertGreater(len(capturing_handler.watcher.records),
1011
class CapturingLevelHandler(logging.Handler):
1012
def __init__(self, level, *args, **kwargs):
1013
logging.Handler.__init__(self, *args, **kwargs)
1014
self.watcher = self.LoggingWatcher([], [])
1015
def emit(self, record):
1016
self.watcher.records.append(record)
1017
self.watcher.output.append(self.format(record))
1019
LoggingWatcher = collections.namedtuple("LoggingWatcher",
1024
class Unique(object):
1025
"""Class for objects which exist only to be unique objects, since
1026
unittest.mock.sentinel only exists in Python 3.3"""
1029
class Test_string_to_delta(TestCaseWithAssertLogs):
1030
# Just test basic RFC 3339 functionality here, the doc string for
1031
# rfc3339_duration_to_delta() already has more comprehensive
1032
# tests, which are run by doctest.
1034
def test_rfc3339_zero_seconds(self):
1035
self.assertEqual(datetime.timedelta(),
1036
string_to_delta("PT0S"))
1038
def test_rfc3339_zero_days(self):
1039
self.assertEqual(datetime.timedelta(), string_to_delta("P0D"))
1041
def test_rfc3339_one_second(self):
1042
self.assertEqual(datetime.timedelta(0, 1),
1043
string_to_delta("PT1S"))
1045
def test_rfc3339_two_hours(self):
1046
self.assertEqual(datetime.timedelta(0, 7200),
1047
string_to_delta("PT2H"))
793
class Test_milliseconds_to_string(unittest.TestCase):
795
self.assertEqual(milliseconds_to_string(93785000),
797
def test_no_days(self):
798
self.assertEqual(milliseconds_to_string(7385000), "02:03:05")
799
def test_all_zero(self):
800
self.assertEqual(milliseconds_to_string(0), "00:00:00")
801
def test_no_fractional_seconds(self):
802
self.assertEqual(milliseconds_to_string(400), "00:00:00")
803
self.assertEqual(milliseconds_to_string(900), "00:00:00")
804
self.assertEqual(milliseconds_to_string(1900), "00:00:01")
806
class Test_string_to_delta(unittest.TestCase):
807
def test_handles_basic_rfc3339(self):
808
self.assertEqual(string_to_delta("PT0S"),
809
datetime.timedelta())
810
self.assertEqual(string_to_delta("P0D"),
811
datetime.timedelta())
812
self.assertEqual(string_to_delta("PT1S"),
813
datetime.timedelta(0, 1))
814
self.assertEqual(string_to_delta("PT2H"),
815
datetime.timedelta(0, 7200))
1049
816
def test_falls_back_to_pre_1_6_1_with_warning(self):
1050
with self.assertLogs(log, logging.WARNING):
1051
value = string_to_delta("2h")
1052
self.assertEqual(datetime.timedelta(0, 7200), value)
1055
class Test_check_option_syntax(unittest.TestCase):
1057
self.parser = argparse.ArgumentParser()
1058
add_command_line_options(self.parser)
1060
def test_actions_requires_client_or_all(self):
1061
for action, value in self.actions.items():
1062
args = self.actionargs(action, value)
1063
with self.assertParseError():
1064
self.parse_args(args)
1066
# This mostly corresponds to the definition from has_commands() in
1067
# check_option_syntax()
1071
"--bump-timeout": None,
1072
"--start-checker": None,
1073
"--stop-checker": None,
1074
"--is-enabled": None,
1077
"--timeout": "PT0S",
1078
"--extended-timeout": "PT0S",
1079
"--interval": "PT0S",
1080
"--approve-by-default": None,
1081
"--deny-by-default": None,
1082
"--approval-delay": "PT0S",
1083
"--approval-duration": "PT0S",
1084
"--host": "hostname",
1085
"--secret": "/dev/null",
1091
def actionargs(action, value, *args):
1092
if value is not None:
1093
return [action, value] + list(args)
817
# assertLogs only exists in Python 3.4
818
if hasattr(self, "assertLogs"):
819
with self.assertLogs(log, logging.WARNING):
820
value = string_to_delta("2h")
1095
return [action] + list(args)
1097
@contextlib.contextmanager
1098
def assertParseError(self):
1099
with self.assertRaises(SystemExit) as e:
1100
with self.redirect_stderr_to_devnull():
1102
# Exit code from argparse is guaranteed to be "2". Reference:
1103
# https://docs.python.org/3/library
1104
# /argparse.html#exiting-methods
1105
self.assertEqual(2, e.exception.code)
1107
def parse_args(self, args):
1108
options = self.parser.parse_args(args)
1109
check_option_syntax(self.parser, options)
1112
@contextlib.contextmanager
1113
def redirect_stderr_to_devnull():
1114
old_stderr = sys.stderr
1115
with contextlib.closing(open(os.devnull, "w")) as null:
822
class WarningFilter(logging.Filter):
823
"""Don't show, but record the presence of, warnings"""
824
def filter(self, record):
825
is_warning = record.levelno >= logging.WARNING
826
self.found = is_warning or getattr(self, "found",
828
return not is_warning
829
warning_filter = WarningFilter()
830
log.addFilter(warning_filter)
832
value = string_to_delta("2h")
1120
sys.stderr = old_stderr
1122
def check_option_syntax(self, options):
1123
check_option_syntax(self.parser, options)
1125
def test_actions_all_conflicts_with_verbose(self):
1126
for action, value in self.actions.items():
1127
args = self.actionargs(action, value, "--all",
1129
with self.assertParseError():
1130
self.parse_args(args)
1132
def test_actions_with_client_conflicts_with_verbose(self):
1133
for action, value in self.actions.items():
1134
args = self.actionargs(action, value, "--verbose",
1136
with self.assertParseError():
1137
self.parse_args(args)
1139
def test_dump_json_conflicts_with_verbose(self):
1140
args = ["--dump-json", "--verbose"]
1141
with self.assertParseError():
1142
self.parse_args(args)
1144
def test_dump_json_conflicts_with_action(self):
1145
for action, value in self.actions.items():
1146
args = self.actionargs(action, value, "--dump-json")
1147
with self.assertParseError():
1148
self.parse_args(args)
1150
def test_all_can_not_be_alone(self):
1152
with self.assertParseError():
1153
self.parse_args(args)
1155
def test_all_is_ok_with_any_action(self):
1156
for action, value in self.actions.items():
1157
args = self.actionargs(action, value, "--all")
1158
self.parse_args(args)
1160
def test_any_action_is_ok_with_one_client(self):
1161
for action, value in self.actions.items():
1162
args = self.actionargs(action, value, "client")
1163
self.parse_args(args)
1165
def test_one_client_with_all_actions_except_is_enabled(self):
1166
for action, value in self.actions.items():
1167
if action == "--is-enabled":
1169
args = self.actionargs(action, value, "client")
1170
self.parse_args(args)
1172
def test_two_clients_with_all_actions_except_is_enabled(self):
1173
for action, value in self.actions.items():
1174
if action == "--is-enabled":
1176
args = self.actionargs(action, value, "client1",
1178
self.parse_args(args)
1180
def test_two_clients_are_ok_with_actions_except_is_enabled(self):
1181
for action, value in self.actions.items():
1182
if action == "--is-enabled":
1184
args = self.actionargs(action, value, "client1",
1186
self.parse_args(args)
1188
def test_is_enabled_fails_without_client(self):
1189
args = ["--is-enabled"]
1190
with self.assertParseError():
1191
self.parse_args(args)
1193
def test_is_enabled_fails_with_two_clients(self):
1194
args = ["--is-enabled", "client1", "client2"]
1195
with self.assertParseError():
1196
self.parse_args(args)
1198
def test_remove_can_only_be_combined_with_action_deny(self):
1199
for action, value in self.actions.items():
1200
if action in {"--remove", "--deny"}:
1202
args = self.actionargs(action, value, "--all",
1204
with self.assertParseError():
1205
self.parse_args(args)
1208
class Test_dbus_exceptions(unittest.TestCase):
1210
def test_dbus_ConnectFailed_is_Error(self):
1211
with self.assertRaises(dbus.Error):
1212
raise dbus.ConnectFailed()
1215
class Test_dbus_MandosBus(unittest.TestCase):
1217
class MockMandosBus(dbus.MandosBus):
1219
self._name = "se.recompile.Mandos"
1220
self._server_path = "/"
1221
self._server_interface = "se.recompile.Mandos"
1222
self._client_interface = "se.recompile.Mandos.Client"
1224
self.call_method_return = Unique()
1226
def call_method(self, methodname, busname, objectpath,
1228
self.calls.append((methodname, busname, objectpath,
1230
return self.call_method_return
1233
self.bus = self.MockMandosBus()
1235
def test_set_client_property(self):
1236
self.bus.set_client_property("objectpath", "key", "value")
1237
expected_call = ("Set", self.bus._name, "objectpath",
1238
"org.freedesktop.DBus.Properties",
1239
(self.bus._client_interface, "key", "value"))
1240
self.assertIn(expected_call, self.bus.calls)
1242
def test_call_client_method(self):
1243
ret = self.bus.call_client_method("objectpath", "methodname")
1244
self.assertIs(self.bus.call_method_return, ret)
1245
expected_call = ("methodname", self.bus._name, "objectpath",
1246
self.bus._client_interface, ())
1247
self.assertIn(expected_call, self.bus.calls)
1249
def test_call_client_method_with_args(self):
1250
args = (Unique(), Unique())
1251
ret = self.bus.call_client_method("objectpath", "methodname",
1253
self.assertIs(self.bus.call_method_return, ret)
1254
expected_call = ("methodname", self.bus._name, "objectpath",
1255
self.bus._client_interface,
1257
self.assertIn(expected_call, self.bus.calls)
1259
def test_get_clients_and_properties(self):
1262
self.bus._client_interface: {
1266
"irrelevant_interface": {
1267
"key": "othervalue",
1271
"other_objectpath": {
1272
"other_irrelevant_interface": {
1278
expected_clients_and_properties = {
1284
self.bus.call_method_return = managed_objects
1285
ret = self.bus.get_clients_and_properties()
1286
self.assertDictEqual(expected_clients_and_properties, ret)
1287
expected_call = ("GetManagedObjects", self.bus._name,
1288
self.bus._server_path,
1289
"org.freedesktop.DBus.ObjectManager", ())
1290
self.assertIn(expected_call, self.bus.calls)
1292
def test_call_server_method(self):
1293
ret = self.bus.call_server_method("methodname")
1294
self.assertIs(self.bus.call_method_return, ret)
1295
expected_call = ("methodname", self.bus._name,
1296
self.bus._server_path,
1297
self.bus._server_interface, ())
1298
self.assertIn(expected_call, self.bus.calls)
1300
def test_call_server_method_with_args(self):
1301
args = (Unique(), Unique())
1302
ret = self.bus.call_server_method("methodname", *args)
1303
self.assertIs(self.bus.call_method_return, ret)
1304
expected_call = ("methodname", self.bus._name,
1305
self.bus._server_path,
1306
self.bus._server_interface,
1308
self.assertIn(expected_call, self.bus.calls)
1311
class Test_dbus_python_adapter_SystemBus(TestCaseWithAssertLogs):
1313
def MockDBusPython_func(self, func):
1314
class mock_dbus_python(object):
1315
"""mock dbus-python module"""
1316
class exceptions(object):
1317
"""Pseudo-namespace"""
1318
class DBusException(Exception):
1320
class SystemBus(object):
1322
def get_object(busname, objectpath):
1323
DBusObject = collections.namedtuple(
1324
"DBusObject", ("methodname", "Set"))
1325
def method(*args, **kwargs):
1326
self.assertEqual({"dbus_interface":
1330
def set_property(interface, key, value,
1331
dbus_interface=None):
1333
"org.freedesktop.DBus.Properties",
1335
self.assertEqual("Secret", key)
1336
return func(interface, key, value,
1337
dbus_interface=dbus_interface)
1338
return DBusObject(methodname=method,
1340
class Boolean(object):
1341
def __init__(self, value):
1342
self.value = bool(value)
1345
if sys.version_info.major == 2:
1346
__nonzero__ = __bool__
1347
class ObjectPath(str):
1349
class Dictionary(dict):
1351
class ByteArray(bytes):
1353
return mock_dbus_python
1355
def call_method(self, bus, methodname, busname, objectpath,
1357
with self.assertLogs(log, logging.DEBUG):
1358
return bus.call_method(methodname, busname, objectpath,
1361
def test_call_method_returns(self):
1362
expected_method_return = Unique()
1363
method_args = (Unique(), Unique())
1365
self.assertEqual(len(method_args), len(args))
1366
for marg, arg in zip(method_args, args):
1367
self.assertIs(marg, arg)
1368
return expected_method_return
1369
mock_dbus_python = self.MockDBusPython_func(func)
1370
bus = dbus_python_adapter.SystemBus(mock_dbus_python)
1371
ret = self.call_method(bus, "methodname", "busname",
1372
"objectpath", "interface",
1374
self.assertIs(ret, expected_method_return)
1376
def test_call_method_filters_bool_true(self):
1378
return method_return
1379
mock_dbus_python = self.MockDBusPython_func(func)
1380
bus = dbus_python_adapter.SystemBus(mock_dbus_python)
1381
method_return = mock_dbus_python.Boolean(True)
1382
ret = self.call_method(bus, "methodname", "busname",
1383
"objectpath", "interface")
1384
self.assertTrue(ret)
1385
self.assertNotIsInstance(ret, mock_dbus_python.Boolean)
1387
def test_call_method_filters_bool_false(self):
1389
return method_return
1390
mock_dbus_python = self.MockDBusPython_func(func)
1391
bus = dbus_python_adapter.SystemBus(mock_dbus_python)
1392
method_return = mock_dbus_python.Boolean(False)
1393
ret = self.call_method(bus, "methodname", "busname",
1394
"objectpath", "interface")
1395
self.assertFalse(ret)
1396
self.assertNotIsInstance(ret, mock_dbus_python.Boolean)
1398
def test_call_method_filters_objectpath(self):
1400
return method_return
1401
mock_dbus_python = self.MockDBusPython_func(func)
1402
bus = dbus_python_adapter.SystemBus(mock_dbus_python)
1403
method_return = mock_dbus_python.ObjectPath("objectpath")
1404
ret = self.call_method(bus, "methodname", "busname",
1405
"objectpath", "interface")
1406
self.assertEqual("objectpath", ret)
1407
self.assertIsNot("objectpath", ret)
1408
self.assertNotIsInstance(ret, mock_dbus_python.ObjectPath)
1410
def test_call_method_filters_booleans_in_dict(self):
1412
return method_return
1413
mock_dbus_python = self.MockDBusPython_func(func)
1414
bus = dbus_python_adapter.SystemBus(mock_dbus_python)
1415
method_return = mock_dbus_python.Dictionary(
1416
{mock_dbus_python.Boolean(True):
1417
mock_dbus_python.Boolean(False),
1418
mock_dbus_python.Boolean(False):
1419
mock_dbus_python.Boolean(True)})
1420
ret = self.call_method(bus, "methodname", "busname",
1421
"objectpath", "interface")
1422
expected_method_return = {True: False,
1424
self.assertEqual(expected_method_return, ret)
1425
self.assertNotIsInstance(ret, mock_dbus_python.Dictionary)
1427
def test_call_method_filters_objectpaths_in_dict(self):
1429
return method_return
1430
mock_dbus_python = self.MockDBusPython_func(func)
1431
bus = dbus_python_adapter.SystemBus(mock_dbus_python)
1432
method_return = mock_dbus_python.Dictionary(
1433
{mock_dbus_python.ObjectPath("objectpath_key_1"):
1434
mock_dbus_python.ObjectPath("objectpath_value_1"),
1435
mock_dbus_python.ObjectPath("objectpath_key_2"):
1436
mock_dbus_python.ObjectPath("objectpath_value_2")})
1437
ret = self.call_method(bus, "methodname", "busname",
1438
"objectpath", "interface")
1439
expected_method_return = {str(key): str(value)
1441
method_return.items()}
1442
self.assertEqual(expected_method_return, ret)
1443
self.assertIsInstance(ret, dict)
1444
self.assertNotIsInstance(ret, mock_dbus_python.Dictionary)
1446
def test_call_method_filters_dict_in_dict(self):
1448
return method_return
1449
mock_dbus_python = self.MockDBusPython_func(func)
1450
bus = dbus_python_adapter.SystemBus(mock_dbus_python)
1451
method_return = mock_dbus_python.Dictionary(
1452
{"key1": mock_dbus_python.Dictionary({"key11": "value11",
1453
"key12": "value12"}),
1454
"key2": mock_dbus_python.Dictionary({"key21": "value21",
1455
"key22": "value22"})})
1456
ret = self.call_method(bus, "methodname", "busname",
1457
"objectpath", "interface")
1458
expected_method_return = {
1459
"key1": {"key11": "value11",
1460
"key12": "value12"},
1461
"key2": {"key21": "value21",
1462
"key22": "value22"},
1464
self.assertEqual(expected_method_return, ret)
1465
self.assertIsInstance(ret, dict)
1466
self.assertNotIsInstance(ret, mock_dbus_python.Dictionary)
1467
for key, value in ret.items():
1468
self.assertIsInstance(value, dict)
1469
self.assertEqual(expected_method_return[key], value)
1470
self.assertNotIsInstance(value,
1471
mock_dbus_python.Dictionary)
1473
def test_call_method_filters_dict_three_deep(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.Dictionary(
1480
mock_dbus_python.Dictionary(
1482
mock_dbus_python.Dictionary(
1484
mock_dbus_python.Boolean(True),
1488
ret = self.call_method(bus, "methodname", "busname",
1489
"objectpath", "interface")
1490
expected_method_return = {"key1": {"key2": {"key3": True}}}
1491
self.assertEqual(expected_method_return, ret)
1492
self.assertIsInstance(ret, dict)
1493
self.assertNotIsInstance(ret, mock_dbus_python.Dictionary)
1494
self.assertIsInstance(ret["key1"], dict)
1495
self.assertNotIsInstance(ret["key1"],
1496
mock_dbus_python.Dictionary)
1497
self.assertIsInstance(ret["key1"]["key2"], dict)
1498
self.assertNotIsInstance(ret["key1"]["key2"],
1499
mock_dbus_python.Dictionary)
1500
self.assertTrue(ret["key1"]["key2"]["key3"])
1501
self.assertNotIsInstance(ret["key1"]["key2"]["key3"],
1502
mock_dbus_python.Boolean)
1504
def test_call_method_handles_exception(self):
1505
dbus_logger = logging.getLogger("dbus.proxies")
1508
dbus_logger.error("Test")
1509
raise mock_dbus_python.exceptions.DBusException()
1511
mock_dbus_python = self.MockDBusPython_func(func)
1512
bus = dbus_python_adapter.SystemBus(mock_dbus_python)
1514
class CountingHandler(logging.Handler):
1516
def emit(self, record):
1519
counting_handler = CountingHandler()
1521
dbus_logger.addHandler(counting_handler)
1524
with self.assertRaises(dbus.Error) as e:
1525
self.call_method(bus, "methodname", "busname",
1526
"objectpath", "interface")
1528
dbus_logger.removeFilter(counting_handler)
1530
self.assertNotIsInstance(e, dbus.ConnectFailed)
1532
# Make sure the dbus logger was suppressed
1533
self.assertEqual(0, counting_handler.count)
1535
def test_Set_Secret_sends_bytearray(self):
1537
def func(*args, **kwargs):
1538
ret[0] = (args, kwargs)
1539
mock_dbus_python = self.MockDBusPython_func(func)
1540
bus = dbus_python_adapter.SystemBus(mock_dbus_python)
1541
bus.set_client_property("objectpath", "Secret", "value")
1542
expected_call = (("se.recompile.Mandos.Client", "Secret",
1543
mock_dbus_python.ByteArray(b"value")),
1545
"org.freedesktop.DBus.Properties"})
1546
self.assertEqual(expected_call, ret[0])
1547
if sys.version_info.major == 2:
1548
self.assertIsInstance(ret[0][0][-1],
1549
mock_dbus_python.ByteArray)
1551
def test_get_object_converts_to_correct_exception(self):
1552
bus = dbus_python_adapter.SystemBus(
1553
self.fake_dbus_python_raises_exception_on_connect)
1554
with self.assertRaises(dbus.ConnectFailed):
1555
self.call_method(bus, "methodname", "busname",
1556
"objectpath", "interface")
1558
class fake_dbus_python_raises_exception_on_connect(object):
1559
"""fake dbus-python module"""
1560
class exceptions(object):
1561
"""Pseudo-namespace"""
1562
class DBusException(Exception):
1567
def get_object(busname, objectpath):
1568
raise cls.exceptions.DBusException()
1569
Bus = collections.namedtuple("Bus", ["get_object"])
1570
return Bus(get_object=get_object)
1573
class Test_dbus_python_adapter_CachingBus(unittest.TestCase):
1574
class mock_dbus_python(object):
1575
"""mock dbus-python modules"""
1576
class SystemBus(object):
1578
def get_object(busname, objectpath):
1582
self.bus = dbus_python_adapter.CachingBus(
1583
self.mock_dbus_python)
1585
def test_returns_distinct_objectpaths(self):
1586
obj1 = self.bus.get_object("busname", "objectpath1")
1587
self.assertIsInstance(obj1, Unique)
1588
obj2 = self.bus.get_object("busname", "objectpath2")
1589
self.assertIsInstance(obj2, Unique)
1590
self.assertIsNot(obj1, obj2)
1592
def test_returns_distinct_busnames(self):
1593
obj1 = self.bus.get_object("busname1", "objectpath")
1594
self.assertIsInstance(obj1, Unique)
1595
obj2 = self.bus.get_object("busname2", "objectpath")
1596
self.assertIsInstance(obj2, Unique)
1597
self.assertIsNot(obj1, obj2)
1599
def test_returns_distinct_both(self):
1600
obj1 = self.bus.get_object("busname1", "objectpath")
1601
self.assertIsInstance(obj1, Unique)
1602
obj2 = self.bus.get_object("busname2", "objectpath")
1603
self.assertIsInstance(obj2, Unique)
1604
self.assertIsNot(obj1, obj2)
1606
def test_returns_same(self):
1607
obj1 = self.bus.get_object("busname", "objectpath")
1608
self.assertIsInstance(obj1, Unique)
1609
obj2 = self.bus.get_object("busname", "objectpath")
1610
self.assertIsInstance(obj2, Unique)
1611
self.assertIs(obj1, obj2)
1613
def test_returns_same_old(self):
1614
obj1 = self.bus.get_object("busname1", "objectpath1")
1615
self.assertIsInstance(obj1, Unique)
1616
obj2 = self.bus.get_object("busname2", "objectpath2")
1617
self.assertIsInstance(obj2, Unique)
1618
obj1b = self.bus.get_object("busname1", "objectpath1")
1619
self.assertIsInstance(obj1b, Unique)
1620
self.assertIsNot(obj1, obj2)
1621
self.assertIsNot(obj2, obj1b)
1622
self.assertIs(obj1, obj1b)
1625
class Test_pydbus_adapter_SystemBus(TestCaseWithAssertLogs):
1627
def Stub_pydbus_func(self, func):
1628
class stub_pydbus(object):
1629
"""stub pydbus module"""
1630
class SystemBus(object):
1632
def get(busname, objectpath):
1633
DBusObject = collections.namedtuple(
1634
"DBusObject", ("methodname",))
1635
return {"interface":
1636
DBusObject(methodname=func)}
1639
def call_method(self, bus, methodname, busname, objectpath,
1641
with self.assertLogs(log, logging.DEBUG):
1642
return bus.call_method(methodname, busname, objectpath,
1645
def test_call_method_returns(self):
1646
expected_method_return = Unique()
1647
method_args = (Unique(), Unique())
1649
self.assertEqual(len(method_args), len(args))
1650
for marg, arg in zip(method_args, args):
1651
self.assertIs(marg, arg)
1652
return expected_method_return
1653
stub_pydbus = self.Stub_pydbus_func(func)
1654
bus = pydbus_adapter.SystemBus(stub_pydbus)
1655
ret = self.call_method(bus, "methodname", "busname",
1656
"objectpath", "interface",
1658
self.assertIs(ret, expected_method_return)
1660
def test_call_method_handles_exception(self):
1661
dbus_logger = logging.getLogger("dbus.proxies")
1664
raise gi.repository.GLib.Error()
1666
stub_pydbus = self.Stub_pydbus_func(func)
1667
bus = pydbus_adapter.SystemBus(stub_pydbus)
1669
with self.assertRaises(dbus.Error) as e:
1670
self.call_method(bus, "methodname", "busname",
1671
"objectpath", "interface")
1673
self.assertNotIsInstance(e, dbus.ConnectFailed)
1675
def test_get_converts_to_correct_exception(self):
1676
bus = pydbus_adapter.SystemBus(
1677
self.fake_pydbus_raises_exception_on_connect)
1678
with self.assertRaises(dbus.ConnectFailed):
1679
self.call_method(bus, "methodname", "busname",
1680
"objectpath", "interface")
1682
class fake_pydbus_raises_exception_on_connect(object):
1683
"""fake dbus-python module"""
1686
def get(busname, objectpath):
1687
raise gi.repository.GLib.Error()
1688
Bus = collections.namedtuple("Bus", ["get"])
1691
def test_set_property_uses_setattr(self):
1692
class Object(object):
1695
class pydbus_spy(object):
1696
class SystemBus(object):
1698
def get(busname, objectpath):
1699
return {"interface": obj}
1700
bus = pydbus_adapter.SystemBus(pydbus_spy)
1702
bus.set_property("busname", "objectpath", "interface", "key",
1704
self.assertIs(value, obj.key)
1706
def test_get_suppresses_xml_deprecation_warning(self):
1707
if sys.version_info.major >= 3:
1709
class stub_pydbus_get(object):
1710
class SystemBus(object):
1712
def get(busname, objectpath):
1713
warnings.warn_explicit(
1714
"deprecated", DeprecationWarning,
1715
"xml.etree.ElementTree", 0)
1716
bus = pydbus_adapter.SystemBus(stub_pydbus_get)
1717
with warnings.catch_warnings(record=True) as w:
1718
warnings.simplefilter("always")
1719
bus.get("busname", "objectpath")
1720
self.assertEqual(0, len(w))
1723
class Test_pydbus_adapter_CachingBus(unittest.TestCase):
1724
class stub_pydbus(object):
1725
"""stub pydbus module"""
1726
class SystemBus(object):
1728
def get(busname, objectpath):
1732
self.bus = pydbus_adapter.CachingBus(self.stub_pydbus)
1734
def test_returns_distinct_objectpaths(self):
1735
obj1 = self.bus.get("busname", "objectpath1")
1736
self.assertIsInstance(obj1, Unique)
1737
obj2 = self.bus.get("busname", "objectpath2")
1738
self.assertIsInstance(obj2, Unique)
1739
self.assertIsNot(obj1, obj2)
1741
def test_returns_distinct_busnames(self):
1742
obj1 = self.bus.get("busname1", "objectpath")
1743
self.assertIsInstance(obj1, Unique)
1744
obj2 = self.bus.get("busname2", "objectpath")
1745
self.assertIsInstance(obj2, Unique)
1746
self.assertIsNot(obj1, obj2)
1748
def test_returns_distinct_both(self):
1749
obj1 = self.bus.get("busname1", "objectpath")
1750
self.assertIsInstance(obj1, Unique)
1751
obj2 = self.bus.get("busname2", "objectpath")
1752
self.assertIsInstance(obj2, Unique)
1753
self.assertIsNot(obj1, obj2)
1755
def test_returns_same(self):
1756
obj1 = self.bus.get("busname", "objectpath")
1757
self.assertIsInstance(obj1, Unique)
1758
obj2 = self.bus.get("busname", "objectpath")
1759
self.assertIsInstance(obj2, Unique)
1760
self.assertIs(obj1, obj2)
1762
def test_returns_same_old(self):
1763
obj1 = self.bus.get("busname1", "objectpath1")
1764
self.assertIsInstance(obj1, Unique)
1765
obj2 = self.bus.get("busname2", "objectpath2")
1766
self.assertIsInstance(obj2, Unique)
1767
obj1b = self.bus.get("busname1", "objectpath1")
1768
self.assertIsInstance(obj1b, Unique)
1769
self.assertIsNot(obj1, obj2)
1770
self.assertIsNot(obj2, obj1b)
1771
self.assertIs(obj1, obj1b)
1774
class Test_commands_from_options(unittest.TestCase):
1777
self.parser = argparse.ArgumentParser()
1778
add_command_line_options(self.parser)
1780
def test_is_enabled(self):
1781
self.assert_command_from_args(["--is-enabled", "client"],
1784
def assert_command_from_args(self, args, command_cls, length=1,
1785
clients=None, **cmd_attrs):
1786
"""Assert that parsing ARGS should result in an instance of
1787
COMMAND_CLS with (optionally) all supplied attributes (CMD_ATTRS)."""
1788
options = self.parser.parse_args(args)
1789
check_option_syntax(self.parser, options)
1790
commands = commands_from_options(options)
1791
self.assertEqual(length, len(commands))
1792
for command in commands:
1793
if isinstance(command, command_cls):
1796
self.assertIsInstance(command, command_cls)
1797
if clients is not None:
1798
self.assertEqual(clients, options.client)
1799
for key, value in cmd_attrs.items():
1800
self.assertEqual(value, getattr(command, key))
1802
def assert_commands_from_args(self, args, commands, clients=None):
1803
for cmd in commands:
1804
self.assert_command_from_args(args, cmd,
1805
length=len(commands),
1808
def test_is_enabled_short(self):
1809
self.assert_command_from_args(["-V", "client"],
1812
def test_approve(self):
1813
self.assert_command_from_args(["--approve", "client"],
1816
def test_approve_short(self):
1817
self.assert_command_from_args(["-A", "client"],
1820
def test_deny(self):
1821
self.assert_command_from_args(["--deny", "client"],
1824
def test_deny_short(self):
1825
self.assert_command_from_args(["-D", "client"], command.Deny)
1827
def test_remove(self):
1828
self.assert_command_from_args(["--remove", "client"],
1831
def test_deny_before_remove(self):
1832
options = self.parser.parse_args(["--deny", "--remove",
1834
check_option_syntax(self.parser, options)
1835
commands = commands_from_options(options)
1836
self.assertEqual(2, len(commands))
1837
self.assertIsInstance(commands[0], command.Deny)
1838
self.assertIsInstance(commands[1], command.Remove)
1840
def test_deny_before_remove_reversed(self):
1841
options = self.parser.parse_args(["--remove", "--deny",
1843
check_option_syntax(self.parser, options)
1844
commands = commands_from_options(options)
1845
self.assertEqual(2, len(commands))
1846
self.assertIsInstance(commands[0], command.Deny)
1847
self.assertIsInstance(commands[1], command.Remove)
1849
def test_remove_short(self):
1850
self.assert_command_from_args(["-r", "client"],
1853
def test_dump_json(self):
1854
self.assert_command_from_args(["--dump-json"],
1857
def test_enable(self):
1858
self.assert_command_from_args(["--enable", "client"],
1861
def test_enable_short(self):
1862
self.assert_command_from_args(["-e", "client"],
1865
def test_disable(self):
1866
self.assert_command_from_args(["--disable", "client"],
1869
def test_disable_short(self):
1870
self.assert_command_from_args(["-d", "client"],
1873
def test_bump_timeout(self):
1874
self.assert_command_from_args(["--bump-timeout", "client"],
1875
command.BumpTimeout)
1877
def test_bump_timeout_short(self):
1878
self.assert_command_from_args(["-b", "client"],
1879
command.BumpTimeout)
1881
def test_start_checker(self):
1882
self.assert_command_from_args(["--start-checker", "client"],
1883
command.StartChecker)
1885
def test_stop_checker(self):
1886
self.assert_command_from_args(["--stop-checker", "client"],
1887
command.StopChecker)
1889
def test_approve_by_default(self):
1890
self.assert_command_from_args(["--approve-by-default",
1892
command.ApproveByDefault)
1894
def test_deny_by_default(self):
1895
self.assert_command_from_args(["--deny-by-default", "client"],
1896
command.DenyByDefault)
1898
def test_checker(self):
1899
self.assert_command_from_args(["--checker", ":", "client"],
1903
def test_checker_empty(self):
1904
self.assert_command_from_args(["--checker", "", "client"],
1908
def test_checker_short(self):
1909
self.assert_command_from_args(["-c", ":", "client"],
1913
def test_host(self):
1914
self.assert_command_from_args(
1915
["--host", "client.example.org", "client"],
1916
command.SetHost, value_to_set="client.example.org")
1918
def test_host_short(self):
1919
self.assert_command_from_args(
1920
["-H", "client.example.org", "client"], command.SetHost,
1921
value_to_set="client.example.org")
1923
def test_secret_devnull(self):
1924
self.assert_command_from_args(["--secret", os.path.devnull,
1925
"client"], command.SetSecret,
1928
def test_secret_tempfile(self):
1929
with tempfile.NamedTemporaryFile(mode="r+b") as f:
1930
value = b"secret\0xyzzy\nbar"
1933
self.assert_command_from_args(["--secret", f.name,
1938
def test_secret_devnull_short(self):
1939
self.assert_command_from_args(["-s", os.path.devnull,
1940
"client"], command.SetSecret,
1943
def test_secret_tempfile_short(self):
1944
with tempfile.NamedTemporaryFile(mode="r+b") as f:
1945
value = b"secret\0xyzzy\nbar"
1948
self.assert_command_from_args(["-s", f.name, "client"],
1952
def test_timeout(self):
1953
self.assert_command_from_args(["--timeout", "PT5M", "client"],
1955
value_to_set=300000)
1957
def test_timeout_short(self):
1958
self.assert_command_from_args(["-t", "PT5M", "client"],
1960
value_to_set=300000)
1962
def test_extended_timeout(self):
1963
self.assert_command_from_args(["--extended-timeout", "PT15M",
1965
command.SetExtendedTimeout,
1966
value_to_set=900000)
1968
def test_interval(self):
1969
self.assert_command_from_args(["--interval", "PT2M",
1970
"client"], command.SetInterval,
1971
value_to_set=120000)
1973
def test_interval_short(self):
1974
self.assert_command_from_args(["-i", "PT2M", "client"],
1975
command.SetInterval,
1976
value_to_set=120000)
1978
def test_approval_delay(self):
1979
self.assert_command_from_args(["--approval-delay", "PT30S",
1981
command.SetApprovalDelay,
1984
def test_approval_duration(self):
1985
self.assert_command_from_args(["--approval-duration", "PT1S",
1987
command.SetApprovalDuration,
1990
def test_print_table(self):
1991
self.assert_command_from_args([], command.PrintTable,
1994
def test_print_table_verbose(self):
1995
self.assert_command_from_args(["--verbose"],
1999
def test_print_table_verbose_short(self):
2000
self.assert_command_from_args(["-v"], command.PrintTable,
2004
def test_manual_page_example_1(self):
2005
self.assert_command_from_args("",
2010
def test_manual_page_example_2(self):
2011
self.assert_command_from_args(
2012
"--verbose foo1.example.org foo2.example.org".split(),
2013
command.PrintTable, clients=["foo1.example.org",
2014
"foo2.example.org"],
2017
def test_manual_page_example_3(self):
2018
self.assert_command_from_args("--enable --all".split(),
2022
def test_manual_page_example_4(self):
2023
self.assert_commands_from_args(
2024
("--timeout=PT5M --interval=PT1M foo1.example.org"
2025
" foo2.example.org").split(),
2026
[command.SetTimeout, command.SetInterval],
2027
clients=["foo1.example.org", "foo2.example.org"])
2029
def test_manual_page_example_5(self):
2030
self.assert_command_from_args("--approve --all".split(),
2035
class TestCommand(unittest.TestCase):
834
log.removeFilter(warning_filter)
835
self.assertTrue(getattr(warning_filter, "found", False))
836
self.assertEqual(value, datetime.timedelta(0, 7200))
839
class TestCmd(unittest.TestCase):
2036
840
"""Abstract class for tests of command classes"""
2038
class FakeMandosBus(dbus.MandosBus):
2039
def __init__(self, testcase):
2040
self.client_properties = {
843
class MockClient(object):
844
def __init__(self, name, **attributes):
845
self.__dbus_object_path__ = "objpath_{}".format(name)
846
self.attributes = attributes
847
self.attributes["Name"] = name
849
def Set(self, interface, propname, value, dbus_interface):
850
testcase.assertEqual(interface, client_interface)
851
testcase.assertEqual(dbus_interface,
852
dbus.PROPERTIES_IFACE)
853
self.attributes[propname] = value
854
def Get(self, interface, propname, dbus_interface):
855
testcase.assertEqual(interface, client_interface)
856
testcase.assertEqual(dbus_interface,
857
dbus.PROPERTIES_IFACE)
858
return self.attributes[propname]
859
def Approve(self, approve, dbus_interface):
860
testcase.assertEqual(dbus_interface, client_interface)
861
self.calls.append(("Approve", (approve,
863
self.client = MockClient(
865
KeyID=("92ed150794387c03ce684574b1139a65"
866
"94a34f895daaaf09fd8ea90a27cddb12"),
868
Host="foo.example.org",
869
Enabled=dbus.Boolean(True),
871
LastCheckedOK="2019-02-03T00:00:00",
872
Created="2019-01-02T00:00:00",
874
Fingerprint=("778827225BA7DE539C5A"
875
"7CFA59CFF7CDBD9A5920"),
876
CheckerRunning=dbus.Boolean(False),
877
LastEnabled="2019-01-03T00:00:00",
878
ApprovalPending=dbus.Boolean(False),
879
ApprovedByDefault=dbus.Boolean(True),
880
LastApprovalRequest="",
882
ApprovalDuration=1000,
883
Checker="fping -q -- %(host)s",
884
ExtendedTimeout=900000,
885
Expires="2019-02-04T00:00:00",
887
self.other_client = MockClient(
889
KeyID=("0558568eedd67d622f5c83b35a115f79"
890
"6ab612cff5ad227247e46c2b020f441c"),
893
Enabled=dbus.Boolean(True),
895
LastCheckedOK="2019-02-04T00:00:00",
896
Created="2019-01-03T00:00:00",
898
Fingerprint=("3E393AEAEFB84C7E89E2"
899
"F547B3A107558FCA3A27"),
900
CheckerRunning=dbus.Boolean(True),
901
LastEnabled="2019-01-04T00:00:00",
902
ApprovalPending=dbus.Boolean(False),
903
ApprovedByDefault=dbus.Boolean(False),
904
LastApprovalRequest="2019-01-03T00:00:00",
906
ApprovalDuration=1000,
908
ExtendedTimeout=900000,
909
Expires="2019-02-05T00:00:00",
910
LastCheckerStatus=-2)
911
self.clients = collections.OrderedDict(
913
(self.client, self.client.attributes),
914
(self.other_client, self.other_client.attributes),
916
self.one_client = {self.client: self.client.attributes}
918
class TestPrintTableCmd(TestCmd):
919
def test_normal(self):
920
output = PrintTableCmd().output(self.clients.values())
921
expected_output = """
922
Name Enabled Timeout Last Successful Check
923
foo Yes 00:05:00 2019-02-03T00:00:00
924
barbar Yes 00:05:00 2019-02-04T00:00:00
926
self.assertEqual(output, expected_output)
927
def test_verbose(self):
928
output = PrintTableCmd(verbose=True).output(
929
self.clients.values())
930
expected_output = """
931
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
932
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
933
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
935
self.assertEqual(output, expected_output)
936
def test_one_client(self):
937
output = PrintTableCmd().output(self.one_client.values())
938
expected_output = """
939
Name Enabled Timeout Last Successful Check
940
foo Yes 00:05:00 2019-02-03T00:00:00
942
self.assertEqual(output, expected_output)
944
class TestDumpJSONCmd(TestCmd):
946
self.expected_json = {
2042
949
"KeyID": ("92ed150794387c03ce684574b1139a65"
2043
950
"94a34f895daaaf09fd8ea90a27cddb12"),
2044
"Secret": b"secret",
2045
951
"Host": "foo.example.org",
2046
952
"Enabled": True,
2047
953
"Timeout": 300000,
2081
986
"ApprovedByDefault": False,
2082
987
"LastApprovalRequest": "2019-01-03T00:00:00",
2083
988
"ApprovalDelay": 30000,
2084
"ApprovalDuration": 93785000,
989
"ApprovalDuration": 1000,
2086
991
"ExtendedTimeout": 900000,
2087
992
"Expires": "2019-02-05T00:00:00",
2088
993
"LastCheckerStatus": -2,
2090
self.clients = collections.OrderedDict(
2092
("client_objectpath", self.client_properties),
2093
("other_client_objectpath",
2094
self.other_client_properties),
2096
self.one_client = {"client_objectpath":
2097
self.client_properties}
2098
self.testcase = testcase
2101
def call_method(self, methodname, busname, objectpath,
2103
self.testcase.assertEqual("se.recompile.Mandos", busname)
2104
self.calls.append((methodname, busname, objectpath,
2106
if interface == "org.freedesktop.DBus.Properties":
2107
if methodname == "Set":
2108
self.testcase.assertEqual(3, len(args))
2109
interface, key, value = args
2110
self.testcase.assertEqual(
2111
"se.recompile.Mandos.Client", interface)
2112
self.clients[objectpath][key] = value
2114
elif interface == "se.recompile.Mandos":
2115
self.testcase.assertEqual("RemoveClient", methodname)
2116
self.testcase.assertEqual(1, len(args))
2117
clientpath = args[0]
2118
del self.clients[clientpath]
2120
elif interface == "se.recompile.Mandos.Client":
2121
if methodname == "Approve":
2122
self.testcase.assertEqual(1, len(args))
2127
self.bus = self.FakeMandosBus(self)
2130
class TestBaseCommands(TestCommand):
2132
def test_IsEnabled_exits_successfully(self):
996
return super(TestDumpJSONCmd, self).setUp()
997
def test_normal(self):
998
json_data = json.loads(DumpJSONCmd().output(self.clients))
999
self.assertDictEqual(json_data, self.expected_json)
1000
def test_one_client(self):
1001
clients = self.one_client
1002
json_data = json.loads(DumpJSONCmd().output(clients))
1003
expected_json = {"foo": self.expected_json["foo"]}
1004
self.assertDictEqual(json_data, expected_json)
1006
class TestIsEnabledCmd(TestCmd):
1007
def test_is_enabled(self):
1008
self.assertTrue(all(IsEnabledCmd().is_enabled(client, properties)
1009
for client, properties in self.clients.items()))
1010
def test_is_enabled_run_exits_successfully(self):
2133
1011
with self.assertRaises(SystemExit) as e:
2134
command.IsEnabled().run(self.bus.one_client)
1012
IsEnabledCmd().run(None, self.one_client)
2135
1013
if e.exception.code is not None:
2136
self.assertEqual(0, e.exception.code)
1014
self.assertEqual(e.exception.code, 0)
2138
1016
self.assertIsNone(e.exception.code)
2140
def test_IsEnabled_exits_with_failure(self):
2141
self.bus.client_properties["Enabled"] = False
1017
def test_is_enabled_run_exits_with_failure(self):
1018
self.client.attributes["Enabled"] = dbus.Boolean(False)
2142
1019
with self.assertRaises(SystemExit) as e:
2143
command.IsEnabled().run(self.bus.one_client)
1020
IsEnabledCmd().run(None, self.one_client)
2144
1021
if isinstance(e.exception.code, int):
2145
self.assertNotEqual(0, e.exception.code)
1022
self.assertNotEqual(e.exception.code, 0)
2147
1024
self.assertIsNotNone(e.exception.code)
2149
def test_Approve(self):
2150
busname = "se.recompile.Mandos"
2151
client_interface = "se.recompile.Mandos.Client"
2152
command.Approve().run(self.bus.clients, self.bus)
2153
for clientpath in self.bus.clients:
2154
self.assertIn(("Approve", busname, clientpath,
2155
client_interface, (True,)), self.bus.calls)
2157
def test_Deny(self):
2158
busname = "se.recompile.Mandos"
2159
client_interface = "se.recompile.Mandos.Client"
2160
command.Deny().run(self.bus.clients, self.bus)
2161
for clientpath in self.bus.clients:
2162
self.assertIn(("Approve", busname, clientpath,
2163
client_interface, (False,)),
2166
def test_Remove(self):
2167
command.Remove().run(self.bus.clients, self.bus)
2168
for clientpath in self.bus.clients:
2169
self.assertIn(("RemoveClient", dbus_busname,
2170
dbus_server_path, dbus_server_interface,
2171
(clientpath,)), self.bus.calls)
2176
"KeyID": ("92ed150794387c03ce684574b1139a65"
2177
"94a34f895daaaf09fd8ea90a27cddb12"),
2178
"Host": "foo.example.org",
2181
"LastCheckedOK": "2019-02-03T00:00:00",
2182
"Created": "2019-01-02T00:00:00",
2184
"Fingerprint": ("778827225BA7DE539C5A"
2185
"7CFA59CFF7CDBD9A5920"),
2186
"CheckerRunning": False,
2187
"LastEnabled": "2019-01-03T00:00:00",
2188
"ApprovalPending": False,
2189
"ApprovedByDefault": True,
2190
"LastApprovalRequest": "",
2192
"ApprovalDuration": 1000,
2193
"Checker": "fping -q -- %(host)s",
2194
"ExtendedTimeout": 900000,
2195
"Expires": "2019-02-04T00:00:00",
2196
"LastCheckerStatus": 0,
2200
"KeyID": ("0558568eedd67d622f5c83b35a115f79"
2201
"6ab612cff5ad227247e46c2b020f441c"),
2202
"Host": "192.0.2.3",
2205
"LastCheckedOK": "2019-02-04T00:00:00",
2206
"Created": "2019-01-03T00:00:00",
2208
"Fingerprint": ("3E393AEAEFB84C7E89E2"
2209
"F547B3A107558FCA3A27"),
2210
"CheckerRunning": True,
2211
"LastEnabled": "2019-01-04T00:00:00",
2212
"ApprovalPending": False,
2213
"ApprovedByDefault": False,
2214
"LastApprovalRequest": "2019-01-03T00:00:00",
2215
"ApprovalDelay": 30000,
2216
"ApprovalDuration": 93785000,
2218
"ExtendedTimeout": 900000,
2219
"Expires": "2019-02-05T00:00:00",
2220
"LastCheckerStatus": -2,
2224
def test_DumpJSON_normal(self):
2225
with self.capture_stdout_to_buffer() as buffer:
2226
command.DumpJSON().run(self.bus.clients)
2227
json_data = json.loads(buffer.getvalue())
2228
self.assertDictEqual(self.expected_json, json_data)
2231
@contextlib.contextmanager
2232
def capture_stdout_to_buffer():
2233
capture_buffer = io.StringIO()
2234
old_stdout = sys.stdout
2235
sys.stdout = capture_buffer
2237
yield capture_buffer
2239
sys.stdout = old_stdout
2241
def test_DumpJSON_one_client(self):
2242
with self.capture_stdout_to_buffer() as buffer:
2243
command.DumpJSON().run(self.bus.one_client)
2244
json_data = json.loads(buffer.getvalue())
2245
expected_json = {"foo": self.expected_json["foo"]}
2246
self.assertDictEqual(expected_json, json_data)
2248
def test_PrintTable_normal(self):
2249
with self.capture_stdout_to_buffer() as buffer:
2250
command.PrintTable().run(self.bus.clients)
2251
expected_output = "\n".join((
2252
"Name Enabled Timeout Last Successful Check",
2253
"foo Yes 00:05:00 2019-02-03T00:00:00 ",
2254
"barbar Yes 00:05:00 2019-02-04T00:00:00 ",
2256
self.assertEqual(expected_output, buffer.getvalue())
2258
def test_PrintTable_verbose(self):
2259
with self.capture_stdout_to_buffer() as buffer:
2260
command.PrintTable(verbose=True).run(self.bus.clients)
2275
"Last Successful Check ",
2276
"2019-02-03T00:00:00 ",
2277
"2019-02-04T00:00:00 ",
2280
"2019-01-02T00:00:00 ",
2281
"2019-01-03T00:00:00 ",
2293
("92ed150794387c03ce684574b1139a6594a34f895daaaf09fd8"
2295
("0558568eedd67d622f5c83b35a115f796ab612cff5ad227247e"
2299
"778827225BA7DE539C5A7CFA59CFF7CDBD9A5920 ",
2300
"3E393AEAEFB84C7E89E2F547B3A107558FCA3A27 ",
2302
"Check Is Running ",
2307
"2019-01-03T00:00:00 ",
2308
"2019-01-04T00:00:00 ",
2310
"Approval Is Pending ",
2314
"Approved By Default ",
2318
"Last Approval Request ",
2320
"2019-01-03T00:00:00 ",
2326
"Approval Duration ",
2331
"fping -q -- %(host)s ",
2334
"Extended Timeout ",
2339
"2019-02-04T00:00:00 ",
2340
"2019-02-05T00:00:00 ",
2342
"Last Checker Status",
2347
num_lines = max(len(rows) for rows in columns)
2348
expected_output = ("\n".join("".join(rows[line]
2349
for rows in columns)
2350
for line in range(num_lines))
2352
self.assertEqual(expected_output, buffer.getvalue())
2354
def test_PrintTable_one_client(self):
2355
with self.capture_stdout_to_buffer() as buffer:
2356
command.PrintTable().run(self.bus.one_client)
2357
expected_output = "\n".join((
2358
"Name Enabled Timeout Last Successful Check",
2359
"foo Yes 00:05:00 2019-02-03T00:00:00 ",
2361
self.assertEqual(expected_output, buffer.getvalue())
2364
class TestPropertySetterCmd(TestCommand):
2365
"""Abstract class for tests of command.PropertySetter classes"""
1026
class TestRemoveCmd(TestCmd):
1027
def test_remove(self):
1028
class MockMandos(object):
1031
def RemoveClient(self, dbus_path):
1032
self.calls.append(("RemoveClient", (dbus_path,)))
1033
mandos = MockMandos()
1034
super(TestRemoveCmd, self).setUp()
1035
RemoveCmd().run(mandos, self.clients)
1036
self.assertEqual(len(mandos.calls), 2)
1037
for client in self.clients:
1038
self.assertIn(("RemoveClient",
1039
(client.__dbus_object_path__,)),
1042
class TestApproveCmd(TestCmd):
1043
def test_approve(self):
1044
ApproveCmd().run(None, self.clients)
1045
for client in self.clients:
1046
self.assertIn(("Approve", (True, client_interface)),
1049
class TestDenyCmd(TestCmd):
1050
def test_deny(self):
1051
DenyCmd().run(None, self.clients)
1052
for client in self.clients:
1053
self.assertIn(("Approve", (False, client_interface)),
1056
class TestEnableCmd(TestCmd):
1057
def test_enable(self):
1058
for client in self.clients:
1059
client.attributes["Enabled"] = False
1061
EnableCmd().run(None, self.clients)
1063
for client in self.clients:
1064
self.assertTrue(client.attributes["Enabled"])
1066
class TestDisableCmd(TestCmd):
1067
def test_disable(self):
1068
DisableCmd().run(None, self.clients)
1070
for client in self.clients:
1071
self.assertFalse(client.attributes["Enabled"])
1073
class Unique(object):
1074
"""Class for objects which exist only to be unique objects, since
1075
unittest.mock.sentinel only exists in Python 3.3"""
1077
class TestPropertyCmd(TestCmd):
1078
"""Abstract class for tests of PropertyCmd classes"""
2367
1079
def runTest(self):
2368
1080
if not hasattr(self, "command"):
2369
return # Abstract TestCase class
2371
if hasattr(self, "values_to_set"):
2372
cmd_args = [(value,) for value in self.values_to_set]
2373
values_to_get = getattr(self, "values_to_get",
2376
cmd_args = [() for x in range(len(self.values_to_get))]
2377
values_to_get = self.values_to_get
2378
for value_to_get, cmd_arg in zip(values_to_get, cmd_args):
2379
for clientpath in self.bus.clients:
2380
self.bus.clients[clientpath][self.propname] = (
2382
self.command(*cmd_arg).run(self.bus.clients, self.bus)
2383
for clientpath in self.bus.clients:
2384
value = (self.bus.clients[clientpath]
1082
values_to_get = getattr(self, "values_to_get",
1084
for value_to_set, value_to_get in zip(self.values_to_set,
1086
for client in self.clients:
1087
old_value = client.attributes[self.propname]
1088
self.assertNotIsInstance(old_value, Unique)
1089
client.attributes[self.propname] = Unique()
1090
self.run_command(value_to_set, self.clients)
1091
for client in self.clients:
1092
value = client.attributes[self.propname]
2386
1093
self.assertNotIsInstance(value, Unique)
2387
self.assertEqual(value_to_get, value)
2390
class TestEnableCmd(TestPropertySetterCmd):
2391
command = command.Enable
2392
propname = "Enabled"
2393
values_to_get = [True]
2396
class TestDisableCmd(TestPropertySetterCmd):
2397
command = command.Disable
2398
propname = "Enabled"
2399
values_to_get = [False]
2402
class TestBumpTimeoutCmd(TestPropertySetterCmd):
2403
command = command.BumpTimeout
1094
self.assertEqual(value, value_to_get)
1095
def run_command(self, value, clients):
1096
self.command().run(None, clients)
1098
class TestBumpTimeoutCmd(TestPropertyCmd):
1099
command = BumpTimeoutCmd
2404
1100
propname = "LastCheckedOK"
2405
values_to_get = [""]
2408
class TestStartCheckerCmd(TestPropertySetterCmd):
2409
command = command.StartChecker
2410
propname = "CheckerRunning"
2411
values_to_get = [True]
2414
class TestStopCheckerCmd(TestPropertySetterCmd):
2415
command = command.StopChecker
2416
propname = "CheckerRunning"
2417
values_to_get = [False]
2420
class TestApproveByDefaultCmd(TestPropertySetterCmd):
2421
command = command.ApproveByDefault
2422
propname = "ApprovedByDefault"
2423
values_to_get = [True]
2426
class TestDenyByDefaultCmd(TestPropertySetterCmd):
2427
command = command.DenyByDefault
2428
propname = "ApprovedByDefault"
2429
values_to_get = [False]
2432
class TestSetCheckerCmd(TestPropertySetterCmd):
2433
command = command.SetChecker
1101
values_to_set = [""]
1103
class TestStartCheckerCmd(TestPropertyCmd):
1104
command = StartCheckerCmd
1105
propname = "CheckerRunning"
1106
values_to_set = [dbus.Boolean(True)]
1108
class TestStopCheckerCmd(TestPropertyCmd):
1109
command = StopCheckerCmd
1110
propname = "CheckerRunning"
1111
values_to_set = [dbus.Boolean(False)]
1113
class TestApproveByDefaultCmd(TestPropertyCmd):
1114
command = ApproveByDefaultCmd
1115
propname = "ApprovedByDefault"
1116
values_to_set = [dbus.Boolean(True)]
1118
class TestDenyByDefaultCmd(TestPropertyCmd):
1119
command = DenyByDefaultCmd
1120
propname = "ApprovedByDefault"
1121
values_to_set = [dbus.Boolean(False)]
1123
class TestValueArgumentPropertyCmd(TestPropertyCmd):
1124
"""Abstract class for tests of PropertyCmd classes using the
1125
ValueArgumentMixIn"""
1127
if type(self) is TestValueArgumentPropertyCmd:
1129
return super(TestValueArgumentPropertyCmd, self).runTest()
1130
def run_command(self, value, clients):
1131
self.command(value).run(None, clients)
1133
class TestSetCheckerCmd(TestValueArgumentPropertyCmd):
1134
command = SetCheckerCmd
2434
1135
propname = "Checker"
2435
1136
values_to_set = ["", ":", "fping -q -- %s"]
2438
class TestSetHostCmd(TestPropertySetterCmd):
2439
command = command.SetHost
1138
class TestSetHostCmd(TestValueArgumentPropertyCmd):
1139
command = SetHostCmd
2440
1140
propname = "Host"
2441
values_to_set = ["192.0.2.3", "client.example.org"]
2444
class TestSetSecretCmd(TestPropertySetterCmd):
2445
command = command.SetSecret
1141
values_to_set = ["192.0.2.3", "foo.example.org"]
1143
class TestSetSecretCmd(TestValueArgumentPropertyCmd):
1144
command = SetSecretCmd
2446
1145
propname = "Secret"
2447
1146
values_to_set = [io.BytesIO(b""),
2448
1147
io.BytesIO(b"secret\0xyzzy\nbar")]
2449
values_to_get = [f.getvalue() for f in values_to_set]
2452
class TestSetTimeoutCmd(TestPropertySetterCmd):
2453
command = command.SetTimeout
1148
values_to_get = [b"", b"secret\0xyzzy\nbar"]
1150
class TestSetTimeoutCmd(TestValueArgumentPropertyCmd):
1151
command = SetTimeoutCmd
2454
1152
propname = "Timeout"
2455
1153
values_to_set = [datetime.timedelta(),
2456
1154
datetime.timedelta(minutes=5),
2457
1155
datetime.timedelta(seconds=1),
2458
1156
datetime.timedelta(weeks=1),
2459
1157
datetime.timedelta(weeks=52)]
2460
values_to_get = [dt.total_seconds()*1000 for dt in values_to_set]
2463
class TestSetExtendedTimeoutCmd(TestPropertySetterCmd):
2464
command = command.SetExtendedTimeout
1158
values_to_get = [0, 300000, 1000, 604800000, 31449600000]
1160
class TestSetExtendedTimeoutCmd(TestValueArgumentPropertyCmd):
1161
command = SetExtendedTimeoutCmd
2465
1162
propname = "ExtendedTimeout"
2466
1163
values_to_set = [datetime.timedelta(),
2467
1164
datetime.timedelta(minutes=5),
2468
1165
datetime.timedelta(seconds=1),
2469
1166
datetime.timedelta(weeks=1),
2470
1167
datetime.timedelta(weeks=52)]
2471
values_to_get = [dt.total_seconds()*1000 for dt in values_to_set]
2474
class TestSetIntervalCmd(TestPropertySetterCmd):
2475
command = command.SetInterval
1168
values_to_get = [0, 300000, 1000, 604800000, 31449600000]
1170
class TestSetIntervalCmd(TestValueArgumentPropertyCmd):
1171
command = SetIntervalCmd
2476
1172
propname = "Interval"
2477
1173
values_to_set = [datetime.timedelta(),
2478
1174
datetime.timedelta(minutes=5),
2479
1175
datetime.timedelta(seconds=1),
2480
1176
datetime.timedelta(weeks=1),
2481
1177
datetime.timedelta(weeks=52)]
2482
values_to_get = [dt.total_seconds()*1000 for dt in values_to_set]
2485
class TestSetApprovalDelayCmd(TestPropertySetterCmd):
2486
command = command.SetApprovalDelay
1178
values_to_get = [0, 300000, 1000, 604800000, 31449600000]
1180
class TestSetApprovalDelayCmd(TestValueArgumentPropertyCmd):
1181
command = SetApprovalDelayCmd
2487
1182
propname = "ApprovalDelay"
2488
1183
values_to_set = [datetime.timedelta(),
2489
1184
datetime.timedelta(minutes=5),
2490
1185
datetime.timedelta(seconds=1),
2491
1186
datetime.timedelta(weeks=1),
2492
1187
datetime.timedelta(weeks=52)]
2493
values_to_get = [dt.total_seconds()*1000 for dt in values_to_set]
2496
class TestSetApprovalDurationCmd(TestPropertySetterCmd):
2497
command = command.SetApprovalDuration
1188
values_to_get = [0, 300000, 1000, 604800000, 31449600000]
1190
class TestSetApprovalDurationCmd(TestValueArgumentPropertyCmd):
1191
command = SetApprovalDurationCmd
2498
1192
propname = "ApprovalDuration"
2499
1193
values_to_set = [datetime.timedelta(),
2500
1194
datetime.timedelta(minutes=5),
2501
1195
datetime.timedelta(seconds=1),
2502
1196
datetime.timedelta(weeks=1),
2503
1197
datetime.timedelta(weeks=52)]
2504
values_to_get = [dt.total_seconds()*1000 for dt in values_to_set]
1198
values_to_get = [0, 300000, 1000, 604800000, 31449600000]
1200
class Test_command_from_options(unittest.TestCase):
1202
self.parser = argparse.ArgumentParser()
1203
add_command_line_options(self.parser)
1204
def assert_command_from_args(self, args, command_cls, **cmd_attrs):
1205
"""Assert that parsing ARGS should result in an instance of
1206
COMMAND_CLS with (optionally) all supplied attributes (CMD_ATTRS)."""
1207
options = self.parser.parse_args(args)
1208
check_option_syntax(self.parser, options)
1209
commands = commands_from_options(options)
1210
self.assertEqual(len(commands), 1)
1211
command = commands[0]
1212
self.assertIsInstance(command, command_cls)
1213
for key, value in cmd_attrs.items():
1214
self.assertEqual(getattr(command, key), value)
1215
def test_print_table(self):
1216
self.assert_command_from_args([], PrintTableCmd,
1219
def test_print_table_verbose(self):
1220
self.assert_command_from_args(["--verbose"], PrintTableCmd,
1223
def test_print_table_verbose_short(self):
1224
self.assert_command_from_args(["-v"], PrintTableCmd,
1227
def test_enable(self):
1228
self.assert_command_from_args(["--enable", "foo"], EnableCmd)
1230
def test_enable_short(self):
1231
self.assert_command_from_args(["-e", "foo"], EnableCmd)
1233
def test_disable(self):
1234
self.assert_command_from_args(["--disable", "foo"],
1237
def test_disable_short(self):
1238
self.assert_command_from_args(["-d", "foo"], DisableCmd)
1240
def test_bump_timeout(self):
1241
self.assert_command_from_args(["--bump-timeout", "foo"],
1244
def test_bump_timeout_short(self):
1245
self.assert_command_from_args(["-b", "foo"], BumpTimeoutCmd)
1247
def test_start_checker(self):
1248
self.assert_command_from_args(["--start-checker", "foo"],
1251
def test_stop_checker(self):
1252
self.assert_command_from_args(["--stop-checker", "foo"],
1255
def test_remove(self):
1256
self.assert_command_from_args(["--remove", "foo"],
1259
def test_remove_short(self):
1260
self.assert_command_from_args(["-r", "foo"], RemoveCmd)
1262
def test_checker(self):
1263
self.assert_command_from_args(["--checker", ":", "foo"],
1264
SetCheckerCmd, value_to_set=":")
1266
def test_checker_empty(self):
1267
self.assert_command_from_args(["--checker", "", "foo"],
1268
SetCheckerCmd, value_to_set="")
1270
def test_checker_short(self):
1271
self.assert_command_from_args(["-c", ":", "foo"],
1272
SetCheckerCmd, value_to_set=":")
1274
def test_timeout(self):
1275
self.assert_command_from_args(["--timeout", "PT5M", "foo"],
1277
value_to_set=300000)
1279
def test_timeout_short(self):
1280
self.assert_command_from_args(["-t", "PT5M", "foo"],
1282
value_to_set=300000)
1284
def test_extended_timeout(self):
1285
self.assert_command_from_args(["--extended-timeout", "PT15M",
1287
SetExtendedTimeoutCmd,
1288
value_to_set=900000)
1290
def test_interval(self):
1291
self.assert_command_from_args(["--interval", "PT2M", "foo"],
1293
value_to_set=120000)
1295
def test_interval_short(self):
1296
self.assert_command_from_args(["-i", "PT2M", "foo"],
1298
value_to_set=120000)
1300
def test_approve_by_default(self):
1301
self.assert_command_from_args(["--approve-by-default", "foo"],
1302
ApproveByDefaultCmd)
1304
def test_deny_by_default(self):
1305
self.assert_command_from_args(["--deny-by-default", "foo"],
1308
def test_approval_delay(self):
1309
self.assert_command_from_args(["--approval-delay", "PT30S",
1310
"foo"], SetApprovalDelayCmd,
1313
def test_approval_duration(self):
1314
self.assert_command_from_args(["--approval-duration", "PT1S",
1315
"foo"], SetApprovalDurationCmd,
1318
def test_host(self):
1319
self.assert_command_from_args(["--host", "foo.example.org",
1321
value_to_set="foo.example.org")
1323
def test_host_short(self):
1324
self.assert_command_from_args(["-H", "foo.example.org",
1326
value_to_set="foo.example.org")
1328
def test_secret_devnull(self):
1329
self.assert_command_from_args(["--secret", os.path.devnull,
1330
"foo"], SetSecretCmd,
1333
def test_secret_tempfile(self):
1334
with tempfile.NamedTemporaryFile(mode="r+b") as f:
1335
value = b"secret\0xyzzy\nbar"
1338
self.assert_command_from_args(["--secret", f.name,
1339
"foo"], SetSecretCmd,
1342
def test_secret_devnull_short(self):
1343
self.assert_command_from_args(["-s", os.path.devnull, "foo"],
1344
SetSecretCmd, value_to_set=b"")
1346
def test_secret_tempfile_short(self):
1347
with tempfile.NamedTemporaryFile(mode="r+b") as f:
1348
value = b"secret\0xyzzy\nbar"
1351
self.assert_command_from_args(["-s", f.name, "foo"],
1355
def test_approve(self):
1356
self.assert_command_from_args(["--approve", "foo"],
1359
def test_approve_short(self):
1360
self.assert_command_from_args(["-A", "foo"], ApproveCmd)
1362
def test_deny(self):
1363
self.assert_command_from_args(["--deny", "foo"], DenyCmd)
1365
def test_deny_short(self):
1366
self.assert_command_from_args(["-D", "foo"], DenyCmd)
1368
def test_dump_json(self):
1369
self.assert_command_from_args(["--dump-json"], DumpJSONCmd)
1371
def test_is_enabled(self):
1372
self.assert_command_from_args(["--is-enabled", "foo"],
1375
def test_is_enabled_short(self):
1376
self.assert_command_from_args(["-V", "foo"], IsEnabledCmd)
1378
def test_deny_before_remove(self):
1379
options = self.parser.parse_args(["--deny", "--remove", "foo"])
1380
check_option_syntax(self.parser, options)
1381
commands = commands_from_options(options)
1382
self.assertEqual(len(commands), 2)
1383
self.assertIsInstance(commands[0], DenyCmd)
1384
self.assertIsInstance(commands[1], RemoveCmd)
1386
def test_deny_before_remove_reversed(self):
1387
options = self.parser.parse_args(["--remove", "--deny", "--all"])
1388
check_option_syntax(self.parser, options)
1389
commands = commands_from_options(options)
1390
self.assertEqual(len(commands), 2)
1391
self.assertIsInstance(commands[0], DenyCmd)
1392
self.assertIsInstance(commands[1], RemoveCmd)
1395
class Test_check_option_syntax(unittest.TestCase):
1396
# This mostly corresponds to the definition from has_actions() in
1397
# check_option_syntax()
1399
# The actual values set here are not that important, but we do
1400
# at least stick to the correct types, even though they are
1404
"bump_timeout": True,
1405
"start_checker": True,
1406
"stop_checker": True,
1410
"timeout": datetime.timedelta(),
1411
"extended_timeout": datetime.timedelta(),
1412
"interval": datetime.timedelta(),
1413
"approved_by_default": True,
1414
"approval_delay": datetime.timedelta(),
1415
"approval_duration": datetime.timedelta(),
1417
"secret": io.BytesIO(b"x"),
1423
self.parser = argparse.ArgumentParser()
1424
add_command_line_options(self.parser)
1426
@contextlib.contextmanager
1427
def assertParseError(self):
1428
with self.assertRaises(SystemExit) as e:
1429
with self.temporarily_suppress_stderr():
1431
# Exit code from argparse is guaranteed to be "2". Reference:
1432
# https://docs.python.org/3/library/argparse.html#exiting-methods
1433
self.assertEqual(e.exception.code, 2)
1436
@contextlib.contextmanager
1437
def temporarily_suppress_stderr():
1438
null = os.open(os.path.devnull, os.O_RDWR)
1439
stderrcopy = os.dup(sys.stderr.fileno())
1440
os.dup2(null, sys.stderr.fileno())
1446
os.dup2(stderrcopy, sys.stderr.fileno())
1447
os.close(stderrcopy)
1449
def check_option_syntax(self, options):
1450
check_option_syntax(self.parser, options)
1452
def test_actions_requires_client_or_all(self):
1453
for action, value in self.actions.items():
1454
options = self.parser.parse_args()
1455
setattr(options, action, value)
1456
with self.assertParseError():
1457
self.check_option_syntax(options)
1459
def test_actions_conflicts_with_verbose(self):
1460
for action, value in self.actions.items():
1461
options = self.parser.parse_args()
1462
setattr(options, action, value)
1463
options.verbose = True
1464
with self.assertParseError():
1465
self.check_option_syntax(options)
1467
def test_dump_json_conflicts_with_verbose(self):
1468
options = self.parser.parse_args()
1469
options.dump_json = True
1470
options.verbose = True
1471
with self.assertParseError():
1472
self.check_option_syntax(options)
1474
def test_dump_json_conflicts_with_action(self):
1475
for action, value in self.actions.items():
1476
options = self.parser.parse_args()
1477
setattr(options, action, value)
1478
options.dump_json = True
1479
with self.assertParseError():
1480
self.check_option_syntax(options)
1482
def test_all_can_not_be_alone(self):
1483
options = self.parser.parse_args()
1485
with self.assertParseError():
1486
self.check_option_syntax(options)
1488
def test_all_is_ok_with_any_action(self):
1489
for action, value in self.actions.items():
1490
options = self.parser.parse_args()
1491
setattr(options, action, value)
1493
self.check_option_syntax(options)
1495
def test_is_enabled_fails_without_client(self):
1496
options = self.parser.parse_args()
1497
options.is_enabled = True
1498
with self.assertParseError():
1499
self.check_option_syntax(options)
1501
def test_is_enabled_works_with_one_client(self):
1502
options = self.parser.parse_args()
1503
options.is_enabled = True
1504
options.client = ["foo"]
1505
self.check_option_syntax(options)
1507
def test_is_enabled_fails_with_two_clients(self):
1508
options = self.parser.parse_args()
1509
options.is_enabled = True
1510
options.client = ["foo", "barbar"]
1511
with self.assertParseError():
1512
self.check_option_syntax(options)
1514
def test_remove_can_only_be_combined_with_action_deny(self):
1515
for action, value in self.actions.items():
1516
if action in {"remove", "deny"}:
1518
options = self.parser.parse_args()
1519
setattr(options, action, value)
1521
options.remove = True
1522
with self.assertParseError():
1523
self.check_option_syntax(options)