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)
105
managed_objects = get_managed_objects(mandos_serv_object_manager)
108
for path, ifs_and_props in managed_objects.items():
110
all_clients[path] = ifs_and_props[client_dbus_interface]
114
# Compile dict of (clientpath: properties) to process
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)
119
log.debug("D-Bus: %s:%s:%s.GetManagedObjects()", dbus_busname,
120
server_dbus_path, dbus.OBJECT_MANAGER_IFACE)
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}
126
except dbus.exceptions.DBusException as e:
127
log.critical("Failed to access Mandos server through D-Bus:"
131
# restore dbus logger
132
dbus_logger.removeFilter(dbus_filter)
134
# Compile dict of (clients: properties) to process
115
137
if not clientnames:
116
clients = all_clients
138
clients = {objpath: properties
139
for objpath, properties in mandos_clients.items()}
119
141
for name in clientnames:
120
for objpath, properties in all_clients.items():
142
for objpath, properties in mandos_clients.items():
121
143
if properties["Name"] == name:
122
144
clients[objpath] = properties
424
446
options.remove = True
427
def get_mandos_dbus_object(bus):
428
log.debug("D-Bus: Connect to: (busname=%r, path=%r)",
429
dbus_busname, server_dbus_path)
430
with if_dbus_exception_log_with_exception_and_exit(
431
"Could not connect to Mandos server: %s"):
432
mandos_dbus_object = bus.get_object(dbus_busname,
434
return mandos_dbus_object
437
@contextlib.contextmanager
438
def if_dbus_exception_log_with_exception_and_exit(*args, **kwargs):
441
except dbus.exceptions.DBusException as e:
442
log.critical(*(args + (e,)), **kwargs)
446
def get_managed_objects(object_manager):
447
log.debug("D-Bus: %s:%s:%s.GetManagedObjects()", dbus_busname,
448
server_dbus_path, dbus.OBJECT_MANAGER_IFACE)
449
with if_dbus_exception_log_with_exception_and_exit(
450
"Failed to access Mandos server through D-Bus:\n%s"):
451
with SilenceLogger("dbus.proxies"):
452
managed_objects = object_manager.GetManagedObjects()
453
return managed_objects
456
class SilenceLogger(object):
457
"Simple context manager to silence a particular logger"
458
def __init__(self, loggername):
459
self.logger = logging.getLogger(loggername)
462
self.logger.addFilter(self.nullfilter)
465
class NullFilter(logging.Filter):
466
def filter(self, record):
469
nullfilter = NullFilter()
471
def __exit__(self, exc_type, exc_val, exc_tb):
472
self.logger.removeFilter(self.nullfilter)
475
449
def commands_from_options(options):
1005
971
self.check_option_syntax(options)
1008
class Test_get_mandos_dbus_object(unittest.TestCase):
1009
def test_calls_and_returns_get_object_on_bus(self):
1010
class MockBus(object):
1012
def get_object(mockbus_self, busname, dbus_path):
1013
# Note that "self" is still the testcase instance,
1014
# this MockBus instance is in "mockbus_self".
1015
self.assertEqual(busname, dbus_busname)
1016
self.assertEqual(dbus_path, server_dbus_path)
1017
mockbus_self.called = True
1020
mockbus = get_mandos_dbus_object(bus=MockBus())
1021
self.assertIsInstance(mockbus, MockBus)
1022
self.assertTrue(mockbus.called)
1024
def test_logs_and_exits_on_dbus_error(self):
1025
class MockBusFailing(object):
1026
def get_object(self, busname, dbus_path):
1027
raise dbus.exceptions.DBusException("Test")
1029
# assertLogs only exists in Python 3.4
1030
if hasattr(self, "assertLogs"):
1031
with self.assertLogs(log, logging.CRITICAL):
1032
with self.assertRaises(SystemExit) as e:
1033
bus = get_mandos_dbus_object(bus=MockBus())
1035
critical_filter = self.CriticalFilter()
1036
log.addFilter(critical_filter)
1038
with self.assertRaises(SystemExit) as e:
1039
get_mandos_dbus_object(bus=MockBusFailing())
1041
log.removeFilter(critical_filter)
1042
self.assertTrue(critical_filter.found)
1043
if isinstance(e.exception.code, int):
1044
self.assertNotEqual(e.exception.code, 0)
1046
self.assertIsNotNone(e.exception.code)
1048
class CriticalFilter(logging.Filter):
1049
"""Don't show, but register, critical messages"""
1051
def filter(self, record):
1052
is_critical = record.levelno >= logging.CRITICAL
1053
self.found = is_critical or self.found
1054
return not is_critical
1057
class Test_get_managed_objects(unittest.TestCase):
1058
def test_calls_and_returns_GetManagedObjects(self):
1059
managed_objects = {"/clients/foo": { "Name": "foo"}}
1060
class MockObjectManager(object):
1062
def GetManagedObjects():
1063
return managed_objects
1064
retval = get_managed_objects(MockObjectManager())
1065
self.assertDictEqual(managed_objects, retval)
1067
def test_logs_and_exits_on_dbus_error(self):
1068
class MockObjectManagerFailing(object):
1070
def GetManagedObjects():
1071
raise dbus.exceptions.DBusException("Test")
1073
if hasattr(self, "assertLogs"):
1074
with self.assertLogs(log, logging.CRITICAL):
1075
with self.assertRaises(SystemExit):
1076
get_managed_objects(MockObjectManagerFailing())
1078
critical_filter = self.CriticalFilter()
1079
log.addFilter(critical_filter)
1081
with self.assertRaises(SystemExit) as e:
1082
get_managed_objects(MockObjectManagerFailing())
1084
log.removeFilter(critical_filter)
1085
self.assertTrue(critical_filter.found)
1086
if isinstance(e.exception.code, int):
1087
self.assertNotEqual(e.exception.code, 0)
1089
self.assertIsNotNone(e.exception.code)
1091
class CriticalFilter(logging.Filter):
1092
"""Don't show, but register, critical messages"""
1094
def filter(self, record):
1095
is_critical = record.levelno >= logging.CRITICAL
1096
self.found = is_critical or self.found
1097
return not is_critical
1100
class Test_SilenceLogger(unittest.TestCase):
1101
loggername = "mandos-ctl.Test_SilenceLogger"
1102
log = logging.getLogger(loggername)
1103
log.propagate = False
1104
log.addHandler(logging.NullHandler())
1107
self.counting_filter = self.CountingFilter()
1109
class CountingFilter(logging.Filter):
1110
"Count number of records"
1112
def filter(self, record):
1116
def test_should_filter_records_only_when_active(self):
1118
with SilenceLogger(self.loggername):
1119
self.log.addFilter(self.counting_filter)
1120
self.log.info("Filtered log message 1")
1121
self.log.info("Non-filtered message 2")
1122
self.log.info("Non-filtered message 3")
1124
self.log.removeFilter(self.counting_filter)
1125
self.assertEqual(self.counting_filter.count, 2)
1128
class Test_commands_from_options(unittest.TestCase):
974
class Test_command_from_options(unittest.TestCase):
1129
975
def setUp(self):
1130
976
self.parser = argparse.ArgumentParser()
1131
977
add_command_line_options(self.parser)
1133
def test_is_enabled(self):
1134
self.assert_command_from_args(["--is-enabled", "foo"],
1137
978
def assert_command_from_args(self, args, command_cls,
1139
980
"""Assert that parsing ARGS should result in an instance of
1146
987
self.assertIsInstance(command, command_cls)
1147
988
for key, value in cmd_attrs.items():
1148
989
self.assertEqual(getattr(command, key), value)
1150
def test_is_enabled_short(self):
1151
self.assert_command_from_args(["-V", "foo"], IsEnabledCmd)
1153
def test_approve(self):
1154
self.assert_command_from_args(["--approve", "foo"],
1157
def test_approve_short(self):
1158
self.assert_command_from_args(["-A", "foo"], ApproveCmd)
1160
def test_deny(self):
1161
self.assert_command_from_args(["--deny", "foo"], DenyCmd)
1163
def test_deny_short(self):
1164
self.assert_command_from_args(["-D", "foo"], DenyCmd)
1166
def test_remove(self):
1167
self.assert_command_from_args(["--remove", "foo"],
1170
def test_deny_before_remove(self):
1171
options = self.parser.parse_args(["--deny", "--remove",
1173
check_option_syntax(self.parser, options)
1174
commands = commands_from_options(options)
1175
self.assertEqual(len(commands), 2)
1176
self.assertIsInstance(commands[0], DenyCmd)
1177
self.assertIsInstance(commands[1], RemoveCmd)
1179
def test_deny_before_remove_reversed(self):
1180
options = self.parser.parse_args(["--remove", "--deny",
1182
check_option_syntax(self.parser, options)
1183
commands = commands_from_options(options)
1184
self.assertEqual(len(commands), 2)
1185
self.assertIsInstance(commands[0], DenyCmd)
1186
self.assertIsInstance(commands[1], RemoveCmd)
1188
def test_remove_short(self):
1189
self.assert_command_from_args(["-r", "foo"], RemoveCmd)
1191
def test_dump_json(self):
1192
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,
1194
1002
def test_enable(self):
1195
1003
self.assert_command_from_args(["--enable", "foo"], EnableCmd)
1239
1046
self.assert_command_from_args(["-c", ":", "foo"],
1240
1047
SetCheckerCmd, value_to_set=":")
1242
def test_host(self):
1243
self.assert_command_from_args(["--host", "foo.example.org",
1245
value_to_set="foo.example.org")
1247
def test_host_short(self):
1248
self.assert_command_from_args(["-H", "foo.example.org",
1250
value_to_set="foo.example.org")
1252
def test_secret_devnull(self):
1253
self.assert_command_from_args(["--secret", os.path.devnull,
1254
"foo"], SetSecretCmd,
1257
def test_secret_tempfile(self):
1258
with tempfile.NamedTemporaryFile(mode="r+b") as f:
1259
value = b"secret\0xyzzy\nbar"
1262
self.assert_command_from_args(["--secret", f.name,
1263
"foo"], SetSecretCmd,
1266
def test_secret_devnull_short(self):
1267
self.assert_command_from_args(["-s", os.path.devnull, "foo"],
1268
SetSecretCmd, value_to_set=b"")
1270
def test_secret_tempfile_short(self):
1271
with tempfile.NamedTemporaryFile(mode="r+b") as f:
1272
value = b"secret\0xyzzy\nbar"
1275
self.assert_command_from_args(["-s", f.name, "foo"],
1279
1049
def test_timeout(self):
1280
1050
self.assert_command_from_args(["--timeout", "PT5M", "foo"],
1312
1090
"foo"], SetApprovalDurationCmd,
1313
1091
value_to_set=1000)
1315
def test_print_table(self):
1316
self.assert_command_from_args([], PrintTableCmd,
1319
def test_print_table_verbose(self):
1320
self.assert_command_from_args(["--verbose"], PrintTableCmd,
1323
def test_print_table_verbose_short(self):
1324
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)
1328
1172
class TestCmd(unittest.TestCase):
1329
1173
"""Abstract class for tests of command classes"""
1331
1174
def setUp(self):
1332
1175
testcase = self
1333
1176
class MockClient(object):
1672
1509
for clientpath in self.clients:
1673
1510
client = self.bus.get_object(dbus_busname, clientpath)
1674
1511
old_value = client.attributes[self.propname]
1675
self.assertNotIsInstance(old_value, self.Unique)
1676
client.attributes[self.propname] = self.Unique()
1512
self.assertNotIsInstance(old_value, Unique)
1513
client.attributes[self.propname] = Unique()
1677
1514
self.run_command(value_to_set, self.clients)
1678
1515
for clientpath in self.clients:
1679
1516
client = self.bus.get_object(dbus_busname, clientpath)
1680
1517
value = client.attributes[self.propname]
1681
self.assertNotIsInstance(value, self.Unique)
1518
self.assertNotIsInstance(value, Unique)
1682
1519
self.assertEqual(value, value_to_get)
1684
class Unique(object):
1685
"""Class for objects which exist only to be unique objects,
1686
since unittest.mock.sentinel only exists in Python 3.3"""
1688
1520
def run_command(self, value, clients):
1689
1521
self.command().run(clients, self.bus)