From 66ba5b0e5e133c54d5f3f43eeed98b7fa620ca43 Mon Sep 17 00:00:00 2001 From: Kili Date: Wed, 11 Mar 2026 00:33:37 +0100 Subject: [PATCH] Add MS Graph auth matrix regression tests (#696) * Rebase MS Graph auth matrix tests onto current master * Expand ClientSecret auth matrix coverage --- tests.py | 309 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 309 insertions(+) diff --git a/tests.py b/tests.py index d9595d9..9f4b062 100755 --- a/tests.py +++ b/tests.py @@ -1509,5 +1509,314 @@ class TestMSGraphFolderFallback(unittest.TestCase): connection._get_well_known_folder_id.assert_not_called() +class TestMSGraphCliValidation(unittest.TestCase): + @patch("parsedmarc.cli.get_dmarc_reports_from_mailbox") + @patch("parsedmarc.cli.MSGraphConnection") + def testCliPassesMsGraphClientSecretAuthSettings( + self, mock_graph_connection, mock_get_mailbox_reports + ): + mock_graph_connection.return_value = object() + mock_get_mailbox_reports.return_value = { + "aggregate_reports": [], + "forensic_reports": [], + "smtp_tls_reports": [], + } + + config_text = """[general] +silent = true + +[msgraph] +auth_method = ClientSecret +client_id = client-id +client_secret = client-secret +tenant_id = tenant-id +mailbox = shared@example.com +""" + + with tempfile.NamedTemporaryFile("w", suffix=".ini", delete=False) as cfg: + cfg.write(config_text) + cfg_path = cfg.name + self.addCleanup(lambda: os.path.exists(cfg_path) and os.remove(cfg_path)) + + with patch.object(sys, "argv", ["parsedmarc", "-c", cfg_path]): + parsedmarc.cli._main() + + self.assertEqual( + mock_graph_connection.call_args.kwargs.get("auth_method"), "ClientSecret" + ) + self.assertEqual( + mock_graph_connection.call_args.kwargs.get("client_secret"), + "client-secret", + ) + self.assertEqual( + mock_graph_connection.call_args.kwargs.get("tenant_id"), "tenant-id" + ) + self.assertEqual( + mock_graph_connection.call_args.kwargs.get("mailbox"), + "shared@example.com", + ) + + @patch("parsedmarc.cli.get_dmarc_reports_from_mailbox") + @patch("parsedmarc.cli.MSGraphConnection") + @patch("parsedmarc.cli.logger") + def testCliRequiresMsGraphClientSecretForClientSecretAuth( + self, mock_logger, mock_graph_connection, mock_get_mailbox_reports + ): + config_text = """[general] +silent = true + +[msgraph] +auth_method = ClientSecret +client_id = client-id +tenant_id = tenant-id +mailbox = shared@example.com +""" + + with tempfile.NamedTemporaryFile("w", suffix=".ini", delete=False) as cfg: + cfg.write(config_text) + cfg_path = cfg.name + self.addCleanup(lambda: os.path.exists(cfg_path) and os.remove(cfg_path)) + + with patch.object(sys, "argv", ["parsedmarc", "-c", cfg_path]): + with self.assertRaises(SystemExit) as system_exit: + parsedmarc.cli._main() + + self.assertEqual(system_exit.exception.code, -1) + mock_logger.critical.assert_called_once_with( + "client_secret setting missing from the msgraph config section" + ) + mock_graph_connection.assert_not_called() + mock_get_mailbox_reports.assert_not_called() + + @patch("parsedmarc.cli.get_dmarc_reports_from_mailbox") + @patch("parsedmarc.cli.MSGraphConnection") + @patch("parsedmarc.cli.logger") + def testCliRequiresMsGraphTenantIdForClientSecretAuth( + self, mock_logger, mock_graph_connection, mock_get_mailbox_reports + ): + config_text = """[general] +silent = true + +[msgraph] +auth_method = ClientSecret +client_id = client-id +client_secret = client-secret +mailbox = shared@example.com +""" + + with tempfile.NamedTemporaryFile("w", suffix=".ini", delete=False) as cfg: + cfg.write(config_text) + cfg_path = cfg.name + self.addCleanup(lambda: os.path.exists(cfg_path) and os.remove(cfg_path)) + + with patch.object(sys, "argv", ["parsedmarc", "-c", cfg_path]): + with self.assertRaises(SystemExit) as system_exit: + parsedmarc.cli._main() + + self.assertEqual(system_exit.exception.code, -1) + mock_logger.critical.assert_called_once_with( + "tenant_id setting missing from the msgraph config section" + ) + mock_graph_connection.assert_not_called() + mock_get_mailbox_reports.assert_not_called() + + @patch("parsedmarc.cli.get_dmarc_reports_from_mailbox") + @patch("parsedmarc.cli.MSGraphConnection") + @patch("parsedmarc.cli.logger") + def testCliRequiresMsGraphMailboxForClientSecretAuth( + self, mock_logger, mock_graph_connection, mock_get_mailbox_reports + ): + config_text = """[general] +silent = true + +[msgraph] +auth_method = ClientSecret +client_id = client-id +client_secret = client-secret +tenant_id = tenant-id +""" + + with tempfile.NamedTemporaryFile("w", suffix=".ini", delete=False) as cfg: + cfg.write(config_text) + cfg_path = cfg.name + self.addCleanup(lambda: os.path.exists(cfg_path) and os.remove(cfg_path)) + + with patch.object(sys, "argv", ["parsedmarc", "-c", cfg_path]): + with self.assertRaises(SystemExit) as system_exit: + parsedmarc.cli._main() + + self.assertEqual(system_exit.exception.code, -1) + mock_logger.critical.assert_called_once_with( + "mailbox setting missing from the msgraph config section" + ) + mock_graph_connection.assert_not_called() + mock_get_mailbox_reports.assert_not_called() + + @patch("parsedmarc.cli.get_dmarc_reports_from_mailbox") + @patch("parsedmarc.cli.MSGraphConnection") + def testCliAllowsMsGraphDeviceCodeWithoutUser( + self, mock_graph_connection, mock_get_mailbox_reports + ): + mock_graph_connection.return_value = object() + mock_get_mailbox_reports.return_value = { + "aggregate_reports": [], + "forensic_reports": [], + "smtp_tls_reports": [], + } + + config_text = """[general] +silent = true + +[msgraph] +auth_method = DeviceCode +client_id = client-id +tenant_id = tenant-id +mailbox = shared@example.com +""" + + with tempfile.NamedTemporaryFile("w", suffix=".ini", delete=False) as cfg: + cfg.write(config_text) + cfg_path = cfg.name + self.addCleanup(lambda: os.path.exists(cfg_path) and os.remove(cfg_path)) + + with patch.object(sys, "argv", ["parsedmarc", "-c", cfg_path]): + parsedmarc.cli._main() + + self.assertEqual( + mock_graph_connection.call_args.kwargs.get("auth_method"), "DeviceCode" + ) + self.assertEqual( + mock_graph_connection.call_args.kwargs.get("mailbox"), + "shared@example.com", + ) + self.assertIsNone(mock_graph_connection.call_args.kwargs.get("username")) + + @patch("parsedmarc.cli.get_dmarc_reports_from_mailbox") + @patch("parsedmarc.cli.MSGraphConnection") + @patch("parsedmarc.cli.logger") + def testCliRequiresMsGraphTenantIdForDeviceCodeAuth( + self, mock_logger, mock_graph_connection, mock_get_mailbox_reports + ): + config_text = """[general] +silent = true + +[msgraph] +auth_method = DeviceCode +client_id = client-id +mailbox = shared@example.com +""" + + with tempfile.NamedTemporaryFile("w", suffix=".ini", delete=False) as cfg: + cfg.write(config_text) + cfg_path = cfg.name + self.addCleanup(lambda: os.path.exists(cfg_path) and os.remove(cfg_path)) + + with patch.object(sys, "argv", ["parsedmarc", "-c", cfg_path]): + with self.assertRaises(SystemExit) as system_exit: + parsedmarc.cli._main() + + self.assertEqual(system_exit.exception.code, -1) + mock_logger.critical.assert_called_once_with( + "tenant_id setting missing from the msgraph config section" + ) + mock_graph_connection.assert_not_called() + mock_get_mailbox_reports.assert_not_called() + + @patch("parsedmarc.cli.get_dmarc_reports_from_mailbox") + @patch("parsedmarc.cli.MSGraphConnection") + @patch("parsedmarc.cli.logger") + def testCliRequiresMsGraphMailboxForDeviceCodeAuth( + self, mock_logger, mock_graph_connection, mock_get_mailbox_reports + ): + config_text = """[general] +silent = true + +[msgraph] +auth_method = DeviceCode +client_id = client-id +tenant_id = tenant-id +""" + + with tempfile.NamedTemporaryFile("w", suffix=".ini", delete=False) as cfg: + cfg.write(config_text) + cfg_path = cfg.name + self.addCleanup(lambda: os.path.exists(cfg_path) and os.remove(cfg_path)) + + with patch.object(sys, "argv", ["parsedmarc", "-c", cfg_path]): + with self.assertRaises(SystemExit) as system_exit: + parsedmarc.cli._main() + + self.assertEqual(system_exit.exception.code, -1) + mock_logger.critical.assert_called_once_with( + "mailbox setting missing from the msgraph config section" + ) + mock_graph_connection.assert_not_called() + mock_get_mailbox_reports.assert_not_called() + + @patch("parsedmarc.cli.get_dmarc_reports_from_mailbox") + @patch("parsedmarc.cli.MSGraphConnection") + @patch("parsedmarc.cli.logger") + def testCliRequiresMsGraphTenantIdForCertificateAuth( + self, mock_logger, mock_graph_connection, mock_get_mailbox_reports + ): + config_text = """[general] +silent = true + +[msgraph] +auth_method = Certificate +client_id = client-id +mailbox = shared@example.com +certificate_path = /tmp/msgraph-cert.pem +""" + + with tempfile.NamedTemporaryFile("w", suffix=".ini", delete=False) as cfg: + cfg.write(config_text) + cfg_path = cfg.name + self.addCleanup(lambda: os.path.exists(cfg_path) and os.remove(cfg_path)) + + with patch.object(sys, "argv", ["parsedmarc", "-c", cfg_path]): + with self.assertRaises(SystemExit) as system_exit: + parsedmarc.cli._main() + + self.assertEqual(system_exit.exception.code, -1) + mock_logger.critical.assert_called_once_with( + "tenant_id setting missing from the msgraph config section" + ) + mock_graph_connection.assert_not_called() + mock_get_mailbox_reports.assert_not_called() + + @patch("parsedmarc.cli.get_dmarc_reports_from_mailbox") + @patch("parsedmarc.cli.MSGraphConnection") + @patch("parsedmarc.cli.logger") + def testCliRequiresMsGraphMailboxForCertificateAuth( + self, mock_logger, mock_graph_connection, mock_get_mailbox_reports + ): + config_text = """[general] +silent = true + +[msgraph] +auth_method = Certificate +client_id = client-id +tenant_id = tenant-id +certificate_path = /tmp/msgraph-cert.pem +""" + + with tempfile.NamedTemporaryFile("w", suffix=".ini", delete=False) as cfg: + cfg.write(config_text) + cfg_path = cfg.name + self.addCleanup(lambda: os.path.exists(cfg_path) and os.remove(cfg_path)) + + with patch.object(sys, "argv", ["parsedmarc", "-c", cfg_path]): + with self.assertRaises(SystemExit) as system_exit: + parsedmarc.cli._main() + + self.assertEqual(system_exit.exception.code, -1) + mock_logger.critical.assert_called_once_with( + "mailbox setting missing from the msgraph config section" + ) + mock_graph_connection.assert_not_called() + mock_get_mailbox_reports.assert_not_called() + + if __name__ == "__main__": unittest.main(verbosity=2)