837
786
testcase.assertEqual(dbus_interface,
838
787
dbus.PROPERTIES_IFACE)
839
788
self.attributes[property] = value
789
self.calls.append(("Set", (interface, property, value,
840
791
def Get(self, interface, property, dbus_interface):
841
792
testcase.assertEqual(interface, client_interface)
842
793
testcase.assertEqual(dbus_interface,
843
794
dbus.PROPERTIES_IFACE)
795
self.calls.append(("Get", (interface, property,
844
797
return self.attributes[property]
845
def Approve(self, approve, dbus_interface):
846
testcase.assertEqual(dbus_interface, client_interface)
847
self.calls.append(("Approve", (approve,
849
self.client = MockClient(
851
KeyID=("92ed150794387c03ce684574b1139a65"
852
"94a34f895daaaf09fd8ea90a27cddb12"),
854
Host="foo.example.org",
855
Enabled=dbus.Boolean(True),
857
LastCheckedOK="2019-02-03T00:00:00",
858
Created="2019-01-02T00:00:00",
860
Fingerprint=("778827225BA7DE539C5A"
861
"7CFA59CFF7CDBD9A5920"),
862
CheckerRunning=dbus.Boolean(False),
863
LastEnabled="2019-01-03T00:00:00",
864
ApprovalPending=dbus.Boolean(False),
865
ApprovedByDefault=dbus.Boolean(True),
866
LastApprovalRequest="",
868
ApprovalDuration=1000,
869
Checker="fping -q -- %(host)s",
870
ExtendedTimeout=900000,
871
Expires="2019-02-04T00:00:00",
873
self.other_client = MockClient(
875
KeyID=("0558568eedd67d622f5c83b35a115f79"
876
"6ab612cff5ad227247e46c2b020f441c"),
879
Enabled=dbus.Boolean(True),
881
LastCheckedOK="2019-02-04T00:00:00",
882
Created="2019-01-03T00:00:00",
884
Fingerprint=("3E393AEAEFB84C7E89E2"
885
"F547B3A107558FCA3A27"),
886
CheckerRunning=dbus.Boolean(True),
887
LastEnabled="2019-01-04T00:00:00",
888
ApprovalPending=dbus.Boolean(False),
889
ApprovedByDefault=dbus.Boolean(False),
890
LastApprovalRequest="2019-01-03T00:00:00",
892
ApprovalDuration=1000,
894
ExtendedTimeout=900000,
895
Expires="2019-02-05T00:00:00",
896
LastCheckerStatus=-2)
897
self.clients = collections.OrderedDict(
899
(self.client, self.client.attributes),
900
(self.other_client, self.other_client.attributes),
798
def __getitem__(self, key):
799
return self.attributes[key]
800
def __setitem__(self, key, value):
801
self.attributes[key] = value
802
self.clients = collections.OrderedDict([
806
KeyID=("92ed150794387c03ce684574b1139a65"
807
"94a34f895daaaf09fd8ea90a27cddb12"),
809
Host="foo.example.org",
810
Enabled=dbus.Boolean(True),
812
LastCheckedOK="2019-02-03T00:00:00",
813
Created="2019-01-02T00:00:00",
815
Fingerprint=("778827225BA7DE539C5A"
816
"7CFA59CFF7CDBD9A5920"),
817
CheckerRunning=dbus.Boolean(False),
818
LastEnabled="2019-01-03T00:00:00",
819
ApprovalPending=dbus.Boolean(False),
820
ApprovedByDefault=dbus.Boolean(True),
821
LastApprovalRequest="",
823
ApprovalDuration=1000,
824
Checker="fping -q -- %(host)s",
825
ExtendedTimeout=900000,
826
Expires="2019-02-04T00:00:00",
827
LastCheckerStatus=0)),
831
KeyID=("0558568eedd67d622f5c83b35a115f79"
832
"6ab612cff5ad227247e46c2b020f441c"),
835
Enabled=dbus.Boolean(True),
837
LastCheckedOK="2019-02-04T00:00:00",
838
Created="2019-01-03T00:00:00",
840
Fingerprint=("3E393AEAEFB84C7E89E2"
841
"F547B3A107558FCA3A27"),
842
CheckerRunning=dbus.Boolean(True),
843
LastEnabled="2019-01-04T00:00:00",
844
ApprovalPending=dbus.Boolean(False),
845
ApprovedByDefault=dbus.Boolean(False),
846
LastApprovalRequest="2019-01-03T00:00:00",
848
ApprovalDuration=1000,
850
ExtendedTimeout=900000,
851
Expires="2019-02-05T00:00:00",
852
LastCheckerStatus=-2)),
902
self.one_client = {self.client: self.client.attributes}
904
855
class TestPrintTableCmd(TestCmd):
905
856
def test_normal(self):
983
934
json_data = json.loads(DumpJSONCmd().output(self.clients))
984
935
self.assertDictEqual(json_data, self.expected_json)
985
936
def test_one_client(self):
986
clients = self.one_client
937
clients = {"foo": self.clients["foo"]}
987
938
json_data = json.loads(DumpJSONCmd().output(clients))
988
939
expected_json = {"foo": self.expected_json["foo"]}
989
940
self.assertDictEqual(json_data, expected_json)
991
942
class TestIsEnabledCmd(TestCmd):
992
943
def test_is_enabled(self):
993
self.assertTrue(all(IsEnabledCmd().is_enabled(client, properties)
994
for client, properties in self.clients.items()))
944
self.assertTrue(all(IsEnabledCmd().is_enabled(client)
945
for client in self.clients.values()))
946
def test_is_enabled_does_get_attribute(self):
947
client = self.clients["foo"]
948
self.assertTrue(IsEnabledCmd().is_enabled(client))
949
self.assertListEqual(client.calls,
951
("se.recompile.Mandos.Client",
953
"org.freedesktop.DBus.Properties"))])
995
954
def test_is_enabled_run_exits_successfully(self):
955
client = self.clients["foo"]
996
956
with self.assertRaises(SystemExit) as e:
997
IsEnabledCmd().run(None, self.one_client)
957
IsEnabledCmd().run(None, [client])
998
958
if e.exception.code is not None:
999
959
self.assertEqual(e.exception.code, 0)
1001
961
self.assertIsNone(e.exception.code)
1002
962
def test_is_enabled_run_exits_with_failure(self):
1003
self.client.attributes["Enabled"] = dbus.Boolean(False)
963
client = self.clients["foo"]
964
client["Enabled"] = dbus.Boolean(False)
1004
965
with self.assertRaises(SystemExit) as e:
1005
IsEnabledCmd().run(None, self.one_client)
966
IsEnabledCmd().run(None, [client])
1006
967
if isinstance(e.exception.code, int):
1007
968
self.assertNotEqual(e.exception.code, 0)
1009
970
self.assertIsNotNone(e.exception.code)
1011
class TestRemoveCmd(TestCmd):
1012
def test_remove(self):
1013
class MockMandos(object):
1016
def RemoveClient(self, dbus_path):
1017
self.calls.append(("RemoveClient", (dbus_path,)))
1018
mandos = MockMandos()
1019
super(TestRemoveCmd, self).setUp()
1020
RemoveCmd().run(mandos, self.clients)
1021
self.assertEqual(len(mandos.calls), 2)
1022
for client in self.clients:
1023
self.assertIn(("RemoveClient",
1024
(client.__dbus_object_path__,)),
1027
class TestApproveCmd(TestCmd):
1028
def test_approve(self):
1029
ApproveCmd().run(None, self.clients)
1030
for client in self.clients:
1031
self.assertIn(("Approve", (True, client_interface)),
1034
class TestDenyCmd(TestCmd):
1035
def test_deny(self):
1036
DenyCmd().run(None, self.clients)
1037
for client in self.clients:
1038
self.assertIn(("Approve", (False, client_interface)),
1041
class TestEnableCmd(TestCmd):
1042
def test_enable(self):
1043
for client in self.clients:
1044
client.attributes["Enabled"] = False
1046
EnableCmd().run(None, self.clients)
1048
for client in self.clients:
1049
self.assertTrue(client.attributes["Enabled"])
1051
class TestDisableCmd(TestCmd):
1052
def test_disable(self):
1053
DisableCmd().run(None, self.clients)
1055
for client in self.clients:
1056
self.assertFalse(client.attributes["Enabled"])
1058
class Unique(object):
1059
"""Class for objects which exist only to be unique objects, since
1060
unittest.mock.sentinel only exists in Python 3.3"""
1062
class TestPropertyCmd(TestCmd):
1063
"""Abstract class for tests of PropertyCmd classes"""
1065
if not hasattr(self, "command"):
1067
values_to_get = getattr(self, "values_to_get",
1069
for value_to_set, value_to_get in zip(self.values_to_set,
1071
for client in self.clients:
1072
old_value = client.attributes[self.property]
1073
self.assertNotIsInstance(old_value, Unique)
1074
client.attributes[self.property] = Unique()
1075
self.run_command(value_to_set, self.clients)
1076
for client in self.clients:
1077
value = client.attributes[self.property]
1078
self.assertNotIsInstance(value, Unique)
1079
self.assertEqual(value, value_to_get)
1080
def run_command(self, value, clients):
1081
self.command().run(None, clients)
1083
class TestBumpTimeoutCmd(TestPropertyCmd):
1084
command = BumpTimeoutCmd
1085
property = "LastCheckedOK"
1086
values_to_set = [""]
1088
class TestStartCheckerCmd(TestPropertyCmd):
1089
command = StartCheckerCmd
1090
property = "CheckerRunning"
1091
values_to_set = [dbus.Boolean(True)]
1093
class TestStopCheckerCmd(TestPropertyCmd):
1094
command = StopCheckerCmd
1095
property = "CheckerRunning"
1096
values_to_set = [dbus.Boolean(False)]
1098
class TestApproveByDefaultCmd(TestPropertyCmd):
1099
command = ApproveByDefaultCmd
1100
property = "ApprovedByDefault"
1101
values_to_set = [dbus.Boolean(True)]
1103
class TestDenyByDefaultCmd(TestPropertyCmd):
1104
command = DenyByDefaultCmd
1105
property = "ApprovedByDefault"
1106
values_to_set = [dbus.Boolean(False)]
1108
class TestValueArgumentPropertyCmd(TestPropertyCmd):
1109
"""Abstract class for tests of PropertyCmd classes using the
1110
ValueArgumentMixIn"""
1112
if type(self) is TestValueArgumentPropertyCmd:
1114
return super(TestValueArgumentPropertyCmd, self).runTest()
1115
def run_command(self, value, clients):
1116
self.command(value).run(None, clients)
1118
class TestSetCheckerCmd(TestValueArgumentPropertyCmd):
1119
command = SetCheckerCmd
1120
property = "Checker"
1121
values_to_set = ["", ":", "fping -q -- %s"]
1123
class TestSetHostCmd(TestValueArgumentPropertyCmd):
1124
command = SetHostCmd
1126
values_to_set = ["192.0.2.3", "foo.example.org"]
1128
class TestSetSecretCmd(TestValueArgumentPropertyCmd):
1129
command = SetSecretCmd
1131
values_to_set = [io.BytesIO(b""),
1132
io.BytesIO(b"secret\0xyzzy\nbar")]
1133
values_to_get = [b"", b"secret\0xyzzy\nbar"]
1135
class TestSetTimeoutCmd(TestValueArgumentPropertyCmd):
1136
command = SetTimeoutCmd
1137
property = "Timeout"
1138
values_to_set = [datetime.timedelta(),
1139
datetime.timedelta(minutes=5),
1140
datetime.timedelta(seconds=1),
1141
datetime.timedelta(weeks=1),
1142
datetime.timedelta(weeks=52)]
1143
values_to_get = [0, 300000, 1000, 604800000, 31449600000]
1145
class TestSetExtendedTimeoutCmd(TestValueArgumentPropertyCmd):
1146
command = SetExtendedTimeoutCmd
1147
property = "ExtendedTimeout"
1148
values_to_set = [datetime.timedelta(),
1149
datetime.timedelta(minutes=5),
1150
datetime.timedelta(seconds=1),
1151
datetime.timedelta(weeks=1),
1152
datetime.timedelta(weeks=52)]
1153
values_to_get = [0, 300000, 1000, 604800000, 31449600000]
1155
class TestSetIntervalCmd(TestValueArgumentPropertyCmd):
1156
command = SetIntervalCmd
1157
property = "Interval"
1158
values_to_set = [datetime.timedelta(),
1159
datetime.timedelta(minutes=5),
1160
datetime.timedelta(seconds=1),
1161
datetime.timedelta(weeks=1),
1162
datetime.timedelta(weeks=52)]
1163
values_to_get = [0, 300000, 1000, 604800000, 31449600000]
1165
class TestSetApprovalDelayCmd(TestValueArgumentPropertyCmd):
1166
command = SetApprovalDelayCmd
1167
property = "ApprovalDelay"
1168
values_to_set = [datetime.timedelta(),
1169
datetime.timedelta(minutes=5),
1170
datetime.timedelta(seconds=1),
1171
datetime.timedelta(weeks=1),
1172
datetime.timedelta(weeks=52)]
1173
values_to_get = [0, 300000, 1000, 604800000, 31449600000]
1175
class TestSetApprovalDurationCmd(TestValueArgumentPropertyCmd):
1176
command = SetApprovalDurationCmd
1177
property = "ApprovalDuration"
1178
values_to_set = [datetime.timedelta(),
1179
datetime.timedelta(minutes=5),
1180
datetime.timedelta(seconds=1),
1181
datetime.timedelta(weeks=1),
1182
datetime.timedelta(weeks=52)]
1183
values_to_get = [0, 300000, 1000, 604800000, 31449600000]
1185
class Test_command_from_options(unittest.TestCase):
1187
self.parser = argparse.ArgumentParser()
1188
add_command_line_options(self.parser)
1189
def assert_command_from_args(self, args, command_cls, **cmd_attrs):
1190
"""Assert that parsing ARGS should result in an instance of
1191
COMMAND_CLS with (optionally) all supplied attributes (CMD_ATTRS)."""
1192
options = self.parser.parse_args(args)
1193
check_option_syntax(self.parser, options)
1194
commands = commands_from_options(options)
1195
self.assertEqual(len(commands), 1)
1196
command = commands[0]
1197
self.assertIsInstance(command, command_cls)
1198
for key, value in cmd_attrs.items():
1199
self.assertEqual(getattr(command, key), value)
1200
def test_print_table(self):
1201
self.assert_command_from_args([], PrintTableCmd,
1204
def test_print_table_verbose(self):
1205
self.assert_command_from_args(["--verbose"], PrintTableCmd,
1208
def test_print_table_verbose_short(self):
1209
self.assert_command_from_args(["-v"], PrintTableCmd,
1212
def test_enable(self):
1213
self.assert_command_from_args(["--enable", "foo"], EnableCmd)
1215
def test_enable_short(self):
1216
self.assert_command_from_args(["-e", "foo"], EnableCmd)
1218
def test_disable(self):
1219
self.assert_command_from_args(["--disable", "foo"],
1222
def test_disable_short(self):
1223
self.assert_command_from_args(["-d", "foo"], DisableCmd)
1225
def test_bump_timeout(self):
1226
self.assert_command_from_args(["--bump-timeout", "foo"],
1229
def test_bump_timeout_short(self):
1230
self.assert_command_from_args(["-b", "foo"], BumpTimeoutCmd)
1232
def test_start_checker(self):
1233
self.assert_command_from_args(["--start-checker", "foo"],
1236
def test_stop_checker(self):
1237
self.assert_command_from_args(["--stop-checker", "foo"],
1240
def test_remove(self):
1241
self.assert_command_from_args(["--remove", "foo"],
1244
def test_remove_short(self):
1245
self.assert_command_from_args(["-r", "foo"], RemoveCmd)
1247
def test_checker(self):
1248
self.assert_command_from_args(["--checker", ":", "foo"],
1249
SetCheckerCmd, value_to_set=":")
1251
def test_checker_empty(self):
1252
self.assert_command_from_args(["--checker", "", "foo"],
1253
SetCheckerCmd, value_to_set="")
1255
def test_checker_short(self):
1256
self.assert_command_from_args(["-c", ":", "foo"],
1257
SetCheckerCmd, value_to_set=":")
1259
def test_timeout(self):
1260
self.assert_command_from_args(["--timeout", "PT5M", "foo"],
1262
value_to_set=300000)
1264
def test_timeout_short(self):
1265
self.assert_command_from_args(["-t", "PT5M", "foo"],
1267
value_to_set=300000)
1269
def test_extended_timeout(self):
1270
self.assert_command_from_args(["--extended-timeout", "PT15M",
1272
SetExtendedTimeoutCmd,
1273
value_to_set=900000)
1275
def test_interval(self):
1276
self.assert_command_from_args(["--interval", "PT2M", "foo"],
1278
value_to_set=120000)
1280
def test_interval_short(self):
1281
self.assert_command_from_args(["-i", "PT2M", "foo"],
1283
value_to_set=120000)
1285
def test_approve_by_default(self):
1286
self.assert_command_from_args(["--approve-by-default", "foo"],
1287
ApproveByDefaultCmd)
1289
def test_deny_by_default(self):
1290
self.assert_command_from_args(["--deny-by-default", "foo"],
1293
def test_approval_delay(self):
1294
self.assert_command_from_args(["--approval-delay", "PT30S",
1295
"foo"], SetApprovalDelayCmd,
1298
def test_approval_duration(self):
1299
self.assert_command_from_args(["--approval-duration", "PT1S",
1300
"foo"], SetApprovalDurationCmd,
1303
def test_host(self):
1304
self.assert_command_from_args(["--host", "foo.example.org",
1306
value_to_set="foo.example.org")
1308
def test_host_short(self):
1309
self.assert_command_from_args(["-H", "foo.example.org",
1311
value_to_set="foo.example.org")
1313
def test_secret_devnull(self):
1314
self.assert_command_from_args(["--secret", os.path.devnull,
1315
"foo"], SetSecretCmd,
1318
def test_secret_tempfile(self):
1319
with tempfile.NamedTemporaryFile(mode="r+b") as f:
1320
value = b"secret\0xyzzy\nbar"
1323
self.assert_command_from_args(["--secret", f.name,
1324
"foo"], SetSecretCmd,
1327
def test_secret_devnull_short(self):
1328
self.assert_command_from_args(["-s", os.path.devnull, "foo"],
1329
SetSecretCmd, value_to_set=b"")
1331
def test_secret_tempfile_short(self):
1332
with tempfile.NamedTemporaryFile(mode="r+b") as f:
1333
value = b"secret\0xyzzy\nbar"
1336
self.assert_command_from_args(["-s", f.name, "foo"],
1340
def test_approve(self):
1341
self.assert_command_from_args(["--approve", "foo"],
1344
def test_approve_short(self):
1345
self.assert_command_from_args(["-A", "foo"], ApproveCmd)
1347
def test_deny(self):
1348
self.assert_command_from_args(["--deny", "foo"], DenyCmd)
1350
def test_deny_short(self):
1351
self.assert_command_from_args(["-D", "foo"], DenyCmd)
1353
def test_dump_json(self):
1354
self.assert_command_from_args(["--dump-json"], DumpJSONCmd)
1356
def test_is_enabled(self):
1357
self.assert_command_from_args(["--is-enabled", "foo"],
1360
def test_is_enabled_short(self):
1361
self.assert_command_from_args(["-V", "foo"], IsEnabledCmd)
1363
def test_deny_before_remove(self):
1364
options = self.parser.parse_args(["--deny", "--remove", "foo"])
1365
check_option_syntax(self.parser, options)
1366
commands = commands_from_options(options)
1367
self.assertEqual(len(commands), 2)
1368
self.assertIsInstance(commands[0], DenyCmd)
1369
self.assertIsInstance(commands[1], RemoveCmd)
1371
def test_deny_before_remove_reversed(self):
1372
options = self.parser.parse_args(["--remove", "--deny", "--all"])
1373
check_option_syntax(self.parser, options)
1374
commands = commands_from_options(options)
1375
self.assertEqual(len(commands), 2)
1376
self.assertIsInstance(commands[0], DenyCmd)
1377
self.assertIsInstance(commands[1], RemoveCmd)
1380
class Test_check_option_syntax(unittest.TestCase):
1381
# This mostly corresponds to the definition from has_actions() in
1382
# check_option_syntax()
1384
# The actual values set here are not that important, but we do
1385
# at least stick to the correct types, even though they are
1389
"bump_timeout": True,
1390
"start_checker": True,
1391
"stop_checker": True,
1395
"timeout": datetime.timedelta(),
1396
"extended_timeout": datetime.timedelta(),
1397
"interval": datetime.timedelta(),
1398
"approved_by_default": True,
1399
"approval_delay": datetime.timedelta(),
1400
"approval_duration": datetime.timedelta(),
1402
"secret": io.BytesIO(b"x"),
1408
self.parser = argparse.ArgumentParser()
1409
add_command_line_options(self.parser)
1411
@contextlib.contextmanager
1412
def assertParseError(self):
1413
with self.assertRaises(SystemExit) as e:
1414
with self.temporarily_suppress_stderr():
1416
# Exit code from argparse is guaranteed to be "2". Reference:
1417
# https://docs.python.org/3/library/argparse.html#exiting-methods
1418
self.assertEqual(e.exception.code, 2)
1421
@contextlib.contextmanager
1422
def temporarily_suppress_stderr():
1423
null = os.open(os.path.devnull, os.O_RDWR)
1424
stderrcopy = os.dup(sys.stderr.fileno())
1425
os.dup2(null, sys.stderr.fileno())
1431
os.dup2(stderrcopy, sys.stderr.fileno())
1432
os.close(stderrcopy)
1434
def check_option_syntax(self, options):
1435
check_option_syntax(self.parser, options)
1437
def test_actions_requires_client_or_all(self):
1438
for action, value in self.actions.items():
1439
options = self.parser.parse_args()
1440
setattr(options, action, value)
1441
with self.assertParseError():
1442
self.check_option_syntax(options)
1444
def test_actions_conflicts_with_verbose(self):
1445
for action, value in self.actions.items():
1446
options = self.parser.parse_args()
1447
setattr(options, action, value)
1448
options.verbose = True
1449
with self.assertParseError():
1450
self.check_option_syntax(options)
1452
def test_dump_json_conflicts_with_verbose(self):
1453
options = self.parser.parse_args()
1454
options.dump_json = True
1455
options.verbose = True
1456
with self.assertParseError():
1457
self.check_option_syntax(options)
1459
def test_dump_json_conflicts_with_action(self):
1460
for action, value in self.actions.items():
1461
options = self.parser.parse_args()
1462
setattr(options, action, value)
1463
options.dump_json = True
1464
with self.assertParseError():
1465
self.check_option_syntax(options)
1467
def test_all_can_not_be_alone(self):
1468
options = self.parser.parse_args()
1470
with self.assertParseError():
1471
self.check_option_syntax(options)
1473
def test_all_is_ok_with_any_action(self):
1474
for action, value in self.actions.items():
1475
options = self.parser.parse_args()
1476
setattr(options, action, value)
1478
self.check_option_syntax(options)
1480
def test_is_enabled_fails_without_client(self):
1481
options = self.parser.parse_args()
1482
options.is_enabled = True
1483
with self.assertParseError():
1484
self.check_option_syntax(options)
1486
def test_is_enabled_works_with_one_client(self):
1487
options = self.parser.parse_args()
1488
options.is_enabled = True
1489
options.client = ["foo"]
1490
self.check_option_syntax(options)
1492
def test_is_enabled_fails_with_two_clients(self):
1493
options = self.parser.parse_args()
1494
options.is_enabled = True
1495
options.client = ["foo", "barbar"]
1496
with self.assertParseError():
1497
self.check_option_syntax(options)
1499
def test_remove_can_only_be_combined_with_action_deny(self):
1500
for action, value in self.actions.items():
1501
if action in {"remove", "deny"}:
1503
options = self.parser.parse_args()
1504
setattr(options, action, value)
1506
options.remove = True
1507
with self.assertParseError():
1508
self.check_option_syntax(options)
1512
974
def should_only_run_tests():