94
94
log.setLevel(logging.DEBUG)
96
bus = dbus.SystemBus()
98
mandos_dbus_object = get_mandos_dbus_object(bus)
100
mandos_serv = dbus.Interface(
101
mandos_dbus_object, dbus_interface=server_dbus_interface)
97
bus = dbus.SystemBus()
98
log.debug("D-Bus: Connect to: (busname=%r, path=%r)",
99
dbus_busname, server_dbus_path)
100
mandos_dbus_objc = bus.get_object(dbus_busname,
102
except dbus.exceptions.DBusException:
103
log.critical("Could not connect to Mandos server")
106
mandos_serv = dbus.Interface(mandos_dbus_objc,
107
dbus_interface=server_dbus_interface)
102
108
mandos_serv_object_manager = dbus.Interface(
103
mandos_dbus_object, dbus_interface=dbus.OBJECT_MANAGER_IFACE)
109
mandos_dbus_objc, dbus_interface=dbus.OBJECT_MANAGER_IFACE)
111
# Filter out log message from dbus module
112
dbus_logger = logging.getLogger("dbus.proxies")
113
class NullFilter(logging.Filter):
114
def filter(self, record):
116
dbus_filter = NullFilter()
118
dbus_logger.addFilter(dbus_filter)
106
119
log.debug("D-Bus: %s:%s:%s.GetManagedObjects()", dbus_busname,
107
120
server_dbus_path, dbus.OBJECT_MANAGER_IFACE)
108
with SilenceLogger("dbus.proxies"):
109
all_clients = {path: ifs_and_props[client_dbus_interface]
110
for path, ifs_and_props in
111
mandos_serv_object_manager
112
.GetManagedObjects().items()
113
if client_dbus_interface in ifs_and_props}
121
mandos_clients = {path: ifs_and_props[client_dbus_interface]
122
for path, ifs_and_props in
123
mandos_serv_object_manager
124
.GetManagedObjects().items()
125
if client_dbus_interface in ifs_and_props}
114
126
except dbus.exceptions.DBusException as e:
115
127
log.critical("Failed to access Mandos server through D-Bus:"
131
# restore dbus logger
132
dbus_logger.removeFilter(dbus_filter)
119
134
# Compile dict of (clients: properties) to process
122
137
if not clientnames:
123
clients = all_clients
138
clients = {objpath: properties
139
for objpath, properties in mandos_clients.items()}
125
141
for name in clientnames:
126
for objpath, properties in all_clients.items():
142
for objpath, properties in mandos_clients.items():
127
143
if properties["Name"] == name:
128
144
clients[objpath] = properties
430
446
options.remove = True
433
def get_mandos_dbus_object(bus):
435
log.debug("D-Bus: Connect to: (busname=%r, path=%r)",
436
dbus_busname, server_dbus_path)
437
mandos_dbus_object = bus.get_object(dbus_busname,
439
except dbus.exceptions.DBusException:
440
log.critical("Could not connect to Mandos server")
443
return mandos_dbus_object
446
class SilenceLogger(object):
447
"Simple context manager to silence a particular logger"
448
def __init__(self, loggername):
449
self.logger = logging.getLogger(loggername)
452
self.logger.addFilter(self.nullfilter)
455
class NullFilter(logging.Filter):
456
def filter(self, record):
459
nullfilter = NullFilter()
461
def __exit__(self, exc_type, exc_val, exc_tb):
462
self.logger.removeFilter(self.nullfilter)
465
449
def commands_from_options(options):
584
568
self.mandos.RemoveClient(client.__dbus_object_path__)
587
class OutputCmd(Command):
588
"""Abstract class for commands outputting client details"""
571
class PrintCmd(Command):
572
"""Abstract class for commands printing client details"""
589
573
all_keywords = ("Name", "Enabled", "Timeout", "LastCheckedOK",
590
574
"Created", "Interval", "Host", "KeyID",
591
575
"Fingerprint", "CheckerRunning", "LastEnabled",
593
577
"LastApprovalRequest", "ApprovalDelay",
594
578
"ApprovalDuration", "Checker", "ExtendedTimeout",
595
579
"Expires", "LastCheckerStatus")
597
580
def run(self, clients, bus=None, mandos=None):
598
581
print(self.output(clients.values()))
600
582
def output(self, clients):
601
583
raise NotImplementedError()
604
class DumpJSONCmd(OutputCmd):
586
class DumpJSONCmd(PrintCmd):
605
587
def output(self, clients):
606
588
data = {client["Name"]:
607
589
{key: self.dbus_boolean_to_bool(client[key])
608
590
for key in self.all_keywords}
609
for client in clients}
591
for client in clients.values()}
610
592
return json.dumps(data, indent=4, separators=(',', ': '))
613
594
def dbus_boolean_to_bool(value):
614
595
if isinstance(value, dbus.Boolean):
928
897
def check_option_syntax(self, options):
929
898
check_option_syntax(self.parser, options)
900
def test_actions_requires_client_or_all(self):
901
for action, value in self.actions.items():
902
options = self.parser.parse_args()
903
setattr(options, action, value)
904
with self.assertParseError():
905
self.check_option_syntax(options)
931
907
def test_actions_conflicts_with_verbose(self):
932
908
for action, value in self.actions.items():
933
909
options = self.parser.parse_args()
995
971
self.check_option_syntax(options)
998
class Test_get_mandos_dbus_object(unittest.TestCase):
999
def test_calls_and_returns_get_object_on_bus(self):
1000
class MockBus(object):
1002
def get_object(mockbus_self, busname, dbus_path):
1003
# Note that "self" is still the testcase instance,
1004
# this MockBus instance is in "mockbus_self".
1005
self.assertEqual(busname, dbus_busname)
1006
self.assertEqual(dbus_path, server_dbus_path)
1007
mockbus_self.called = True
1010
mockbus = get_mandos_dbus_object(bus=MockBus())
1011
self.assertIsInstance(mockbus, MockBus)
1012
self.assertTrue(mockbus.called)
1014
def test_logs_and_exits_on_dbus_error(self):
1015
class MockBusFailing(object):
1016
def get_object(self, busname, dbus_path):
1017
raise dbus.exceptions.DBusException("Test")
1019
# assertLogs only exists in Python 3.4
1020
if hasattr(self, "assertLogs"):
1021
with self.assertLogs(log, logging.CRITICAL):
1022
with self.assertRaises(SystemExit) as e:
1023
bus = get_mandos_dbus_object(bus=MockBus())
1025
critical_filter = self.CriticalFilter()
1026
log.addFilter(critical_filter)
1028
with self.assertRaises(SystemExit) as e:
1029
get_mandos_dbus_object(bus=MockBusFailing())
1031
log.removeFilter(critical_filter)
1032
self.assertTrue(critical_filter.found)
1033
if isinstance(e.exception.code, int):
1034
self.assertNotEqual(e.exception.code, 0)
1036
self.assertIsNotNone(e.exception.code)
1038
class CriticalFilter(logging.Filter):
1039
"""Don't show, but register, critical messages"""
1041
def filter(self, record):
1042
is_critical = record.levelno >= logging.CRITICAL
1043
self.found = is_critical or self.found
1044
return not is_critical
1047
class Test_SilenceLogger(unittest.TestCase):
1048
loggername = "mandos-ctl.Test_SilenceLogger"
1049
log = logging.getLogger(loggername)
1050
log.propagate = False
1051
log.addHandler(logging.NullHandler())
1054
self.counting_filter = self.CountingFilter()
1056
class CountingFilter(logging.Filter):
1057
"Count number of records"
1059
def filter(self, record):
1063
def test_should_filter_records_only_when_active(self):
1065
with SilenceLogger(self.loggername):
1066
self.log.addFilter(self.counting_filter)
1067
self.log.info("Filtered log message 1")
1068
self.log.info("Non-filtered message 2")
1069
self.log.info("Non-filtered message 3")
1071
self.log.removeFilter(self.counting_filter)
1072
self.assertEqual(self.counting_filter.count, 2)
1075
class Test_commands_from_options(unittest.TestCase):
974
class Test_command_from_options(unittest.TestCase):
1076
975
def setUp(self):
1077
976
self.parser = argparse.ArgumentParser()
1078
977
add_command_line_options(self.parser)
1080
def test_is_enabled(self):
1081
self.assert_command_from_args(["--is-enabled", "foo"],
1084
978
def assert_command_from_args(self, args, command_cls,
1086
980
"""Assert that parsing ARGS should result in an instance of
1093
987
self.assertIsInstance(command, command_cls)
1094
988
for key, value in cmd_attrs.items():
1095
989
self.assertEqual(getattr(command, key), value)
1097
def test_is_enabled_short(self):
1098
self.assert_command_from_args(["-V", "foo"], IsEnabledCmd)
1100
def test_approve(self):
1101
self.assert_command_from_args(["--approve", "foo"],
1104
def test_approve_short(self):
1105
self.assert_command_from_args(["-A", "foo"], ApproveCmd)
1107
def test_deny(self):
1108
self.assert_command_from_args(["--deny", "foo"], DenyCmd)
1110
def test_deny_short(self):
1111
self.assert_command_from_args(["-D", "foo"], DenyCmd)
1113
def test_remove(self):
1114
self.assert_command_from_args(["--remove", "foo"],
1117
def test_deny_before_remove(self):
1118
options = self.parser.parse_args(["--deny", "--remove",
1120
check_option_syntax(self.parser, options)
1121
commands = commands_from_options(options)
1122
self.assertEqual(len(commands), 2)
1123
self.assertIsInstance(commands[0], DenyCmd)
1124
self.assertIsInstance(commands[1], RemoveCmd)
1126
def test_deny_before_remove_reversed(self):
1127
options = self.parser.parse_args(["--remove", "--deny",
1129
check_option_syntax(self.parser, options)
1130
commands = commands_from_options(options)
1131
self.assertEqual(len(commands), 2)
1132
self.assertIsInstance(commands[0], DenyCmd)
1133
self.assertIsInstance(commands[1], RemoveCmd)
1135
def test_remove_short(self):
1136
self.assert_command_from_args(["-r", "foo"], RemoveCmd)
1138
def test_dump_json(self):
1139
self.assert_command_from_args(["--dump-json"], DumpJSONCmd)
990
def test_print_table(self):
991
self.assert_command_from_args([], PrintTableCmd,
994
def test_print_table_verbose(self):
995
self.assert_command_from_args(["--verbose"], PrintTableCmd,
998
def test_print_table_verbose_short(self):
999
self.assert_command_from_args(["-v"], PrintTableCmd,
1141
1002
def test_enable(self):
1142
1003
self.assert_command_from_args(["--enable", "foo"], EnableCmd)
1166
1027
self.assert_command_from_args(["--stop-checker", "foo"],
1167
1028
StopCheckerCmd)
1169
def test_approve_by_default(self):
1170
self.assert_command_from_args(["--approve-by-default", "foo"],
1171
ApproveByDefaultCmd)
1030
def test_remove(self):
1031
self.assert_command_from_args(["--remove", "foo"],
1173
def test_deny_by_default(self):
1174
self.assert_command_from_args(["--deny-by-default", "foo"],
1034
def test_remove_short(self):
1035
self.assert_command_from_args(["-r", "foo"], RemoveCmd)
1177
1037
def test_checker(self):
1178
1038
self.assert_command_from_args(["--checker", ":", "foo"],
1186
1046
self.assert_command_from_args(["-c", ":", "foo"],
1187
1047
SetCheckerCmd, value_to_set=":")
1189
def test_host(self):
1190
self.assert_command_from_args(["--host", "foo.example.org",
1192
value_to_set="foo.example.org")
1194
def test_host_short(self):
1195
self.assert_command_from_args(["-H", "foo.example.org",
1197
value_to_set="foo.example.org")
1199
def test_secret_devnull(self):
1200
self.assert_command_from_args(["--secret", os.path.devnull,
1201
"foo"], SetSecretCmd,
1204
def test_secret_tempfile(self):
1205
with tempfile.NamedTemporaryFile(mode="r+b") as f:
1206
value = b"secret\0xyzzy\nbar"
1209
self.assert_command_from_args(["--secret", f.name,
1210
"foo"], SetSecretCmd,
1213
def test_secret_devnull_short(self):
1214
self.assert_command_from_args(["-s", os.path.devnull, "foo"],
1215
SetSecretCmd, value_to_set=b"")
1217
def test_secret_tempfile_short(self):
1218
with tempfile.NamedTemporaryFile(mode="r+b") as f:
1219
value = b"secret\0xyzzy\nbar"
1222
self.assert_command_from_args(["-s", f.name, "foo"],
1226
1049
def test_timeout(self):
1227
1050
self.assert_command_from_args(["--timeout", "PT5M", "foo"],
1259
1090
"foo"], SetApprovalDurationCmd,
1260
1091
value_to_set=1000)
1262
def test_print_table(self):
1263
self.assert_command_from_args([], PrintTableCmd,
1266
def test_print_table_verbose(self):
1267
self.assert_command_from_args(["--verbose"], PrintTableCmd,
1270
def test_print_table_verbose_short(self):
1271
self.assert_command_from_args(["-v"], PrintTableCmd,
1093
def test_host(self):
1094
self.assert_command_from_args(["--host", "foo.example.org",
1096
value_to_set="foo.example.org")
1098
def test_host_short(self):
1099
self.assert_command_from_args(["-H", "foo.example.org",
1101
value_to_set="foo.example.org")
1103
def test_secret_devnull(self):
1104
self.assert_command_from_args(["--secret", os.path.devnull,
1105
"foo"], SetSecretCmd,
1108
def test_secret_tempfile(self):
1109
with tempfile.NamedTemporaryFile(mode="r+b") as f:
1110
value = b"secret\0xyzzy\nbar"
1113
self.assert_command_from_args(["--secret", f.name,
1114
"foo"], SetSecretCmd,
1117
def test_secret_devnull_short(self):
1118
self.assert_command_from_args(["-s", os.path.devnull, "foo"],
1119
SetSecretCmd, value_to_set=b"")
1121
def test_secret_tempfile_short(self):
1122
with tempfile.NamedTemporaryFile(mode="r+b") as f:
1123
value = b"secret\0xyzzy\nbar"
1126
self.assert_command_from_args(["-s", f.name, "foo"],
1130
def test_approve(self):
1131
self.assert_command_from_args(["--approve", "foo"],
1134
def test_approve_short(self):
1135
self.assert_command_from_args(["-A", "foo"], ApproveCmd)
1137
def test_deny(self):
1138
self.assert_command_from_args(["--deny", "foo"], DenyCmd)
1140
def test_deny_short(self):
1141
self.assert_command_from_args(["-D", "foo"], DenyCmd)
1143
def test_dump_json(self):
1144
self.assert_command_from_args(["--dump-json"], DumpJSONCmd)
1146
def test_is_enabled(self):
1147
self.assert_command_from_args(["--is-enabled", "foo"],
1150
def test_is_enabled_short(self):
1151
self.assert_command_from_args(["-V", "foo"], IsEnabledCmd)
1153
def test_deny_before_remove(self):
1154
options = self.parser.parse_args(["--deny", "--remove",
1156
check_option_syntax(self.parser, options)
1157
commands = commands_from_options(options)
1158
self.assertEqual(len(commands), 2)
1159
self.assertIsInstance(commands[0], DenyCmd)
1160
self.assertIsInstance(commands[1], RemoveCmd)
1162
def test_deny_before_remove_reversed(self):
1163
options = self.parser.parse_args(["--remove", "--deny",
1165
check_option_syntax(self.parser, options)
1166
commands = commands_from_options(options)
1167
self.assertEqual(len(commands), 2)
1168
self.assertIsInstance(commands[0], DenyCmd)
1169
self.assertIsInstance(commands[1], RemoveCmd)
1275
1172
class TestCmd(unittest.TestCase):
1276
1173
"""Abstract class for tests of command classes"""
1278
1174
def setUp(self):
1279
1175
testcase = self
1280
1176
class MockClient(object):
1482
1372
return super(TestDumpJSONCmd, self).setUp()
1484
1373
def test_normal(self):
1485
output = DumpJSONCmd().output(self.clients.values())
1486
json_data = json.loads(output)
1374
json_data = json.loads(DumpJSONCmd().output(self.clients))
1487
1375
self.assertDictEqual(json_data, self.expected_json)
1489
1376
def test_one_client(self):
1490
output = DumpJSONCmd().output(self.one_client.values())
1491
json_data = json.loads(output)
1377
clients = self.one_client
1378
json_data = json.loads(DumpJSONCmd().output(clients))
1492
1379
expected_json = {"foo": self.expected_json["foo"]}
1493
1380
self.assertDictEqual(json_data, expected_json)
1619
1509
for clientpath in self.clients:
1620
1510
client = self.bus.get_object(dbus_busname, clientpath)
1621
1511
old_value = client.attributes[self.propname]
1622
self.assertNotIsInstance(old_value, self.Unique)
1623
client.attributes[self.propname] = self.Unique()
1512
self.assertNotIsInstance(old_value, Unique)
1513
client.attributes[self.propname] = Unique()
1624
1514
self.run_command(value_to_set, self.clients)
1625
1515
for clientpath in self.clients:
1626
1516
client = self.bus.get_object(dbus_busname, clientpath)
1627
1517
value = client.attributes[self.propname]
1628
self.assertNotIsInstance(value, self.Unique)
1518
self.assertNotIsInstance(value, Unique)
1629
1519
self.assertEqual(value, value_to_get)
1631
class Unique(object):
1632
"""Class for objects which exist only to be unique objects,
1633
since unittest.mock.sentinel only exists in Python 3.3"""
1635
1520
def run_command(self, value, clients):
1636
1521
self.command().run(clients, self.bus)
1639
class TestEnableCmd(TestPropertyCmd):
1641
propname = "Enabled"
1642
values_to_set = [dbus.Boolean(True)]
1645
class TestDisableCmd(TestPropertyCmd):
1646
command = DisableCmd
1647
propname = "Enabled"
1648
values_to_set = [dbus.Boolean(False)]
1524
class TestEnableCmd(TestCmd):
1525
def test_enable(self):
1526
for clientpath in self.clients:
1527
client = self.bus.get_object(dbus_busname, clientpath)
1528
client.attributes["Enabled"] = False
1530
EnableCmd().run(self.clients, self.bus)
1532
for clientpath in self.clients:
1533
client = self.bus.get_object(dbus_busname, clientpath)
1534
self.assertTrue(client.attributes["Enabled"])
1537
class TestDisableCmd(TestCmd):
1538
def test_disable(self):
1539
DisableCmd().run(self.clients, self.bus)
1540
for clientpath in self.clients:
1541
client = self.bus.get_object(dbus_busname, clientpath)
1542
self.assertFalse(client.attributes["Enabled"])
1651
1545
class TestBumpTimeoutCmd(TestPropertyCmd):