From 70d9e0333574eca07c4d3976bb3c767cb5f00c02 Mon Sep 17 00:00:00 2001 From: David Larsen Date: Fri, 27 Feb 2026 14:36:19 -0500 Subject: [PATCH] Fix Slack and MS Teams notifiers not reading URL from dashboard config Same config key mismatch as #34 (webhook). Both notifiers read self.config.get('webhook_url') but notifications.yaml defines param names as 'slack_webhook_url' and 'msteams_webhook_url'. Dashboard config values were silently dropped. Also adds missing MS Teams auto-enablement block in NotificationManager.load_from_config(), which prevented the notifier from loading via dashboard config or env var at all. Env var fallback paths (INPUT_SLACK_WEBHOOK_URL, INPUT_MSTEAMS_WEBHOOK_URL) were unaffected. --- socket_basics/core/notification/manager.py | 14 ++++ .../core/notification/ms_teams_notifier.py | 2 +- .../core/notification/slack_notifier.py | 2 +- tests/test_msteams_notifier_params.py | 83 +++++++++++++++++++ tests/test_slack_notifier_params.py | 83 +++++++++++++++++++ 5 files changed, 182 insertions(+), 2 deletions(-) create mode 100644 tests/test_msteams_notifier_params.py create mode 100644 tests/test_slack_notifier_params.py diff --git a/socket_basics/core/notification/manager.py b/socket_basics/core/notification/manager.py index e98464b..d6e8dcc 100644 --- a/socket_basics/core/notification/manager.py +++ b/socket_basics/core/notification/manager.py @@ -8,6 +8,7 @@ load_connectors_config, get_slack_webhook_url, get_webhook_url, + get_msteams_webhook_url, get_ms_sentinel_workspace_id, get_jira_url, get_sumologic_endpoint, @@ -74,6 +75,19 @@ def load_from_config(self) -> None: else: enable_cause = 'app_config:slack_webhook_url' + # MS Teams: webhook + if name.lower() == 'msteams': + msteams_url = get_msteams_webhook_url() + if ( + msteams_url + or (self.app_config and self.app_config.get('msteams_webhook_url')) + ): + enabled = True + if msteams_url: + enable_cause = 'env:MSTEAMS_WEBHOOK_URL or INPUT_MSTEAMS_WEBHOOK_URL' + else: + enable_cause = 'app_config:msteams_webhook_url' + # Webhook generic if name.lower() == 'webhook': webhook = get_webhook_url() diff --git a/socket_basics/core/notification/ms_teams_notifier.py b/socket_basics/core/notification/ms_teams_notifier.py index 24e443d..3ae1bda 100644 --- a/socket_basics/core/notification/ms_teams_notifier.py +++ b/socket_basics/core/notification/ms_teams_notifier.py @@ -19,7 +19,7 @@ def __init__(self, params: Dict[str, Any] | None = None): super().__init__(params or {}) # Teams webhook URL from params, env variable, or app config self.webhook_url = ( - self.config.get('webhook_url') or + self.config.get('msteams_webhook_url') or get_msteams_webhook_url() ) self.title = self.config.get('title', 'Socket Security') diff --git a/socket_basics/core/notification/slack_notifier.py b/socket_basics/core/notification/slack_notifier.py index 7469680..30ca5a7 100644 --- a/socket_basics/core/notification/slack_notifier.py +++ b/socket_basics/core/notification/slack_notifier.py @@ -19,7 +19,7 @@ def __init__(self, params: Dict[str, Any] | None = None): super().__init__(params or {}) # Slack webhook URL from params, env variable, or app config self.webhook_url = ( - self.config.get('webhook_url') or + self.config.get('slack_webhook_url') or get_slack_webhook_url() ) self.username = "Socket Security" diff --git a/tests/test_msteams_notifier_params.py b/tests/test_msteams_notifier_params.py new file mode 100644 index 0000000..b0a3295 --- /dev/null +++ b/tests/test_msteams_notifier_params.py @@ -0,0 +1,83 @@ +import os + +from socket_basics.core.notification.ms_teams_notifier import MSTeamsNotifier +from socket_basics.core.notification.manager import NotificationManager + + +def _base_cfg(): + return { + "notifiers": { + "msteams": { + "module_path": "socket_basics.core.notification.ms_teams_notifier", + "class": "MSTeamsNotifier", + "parameters": [ + {"name": "msteams_webhook_url", "env_variable": "INPUT_MSTEAMS_WEBHOOK_URL", "type": "str"}, + ], + } + } + } + + +def test_msteams_notifier_reads_url_from_params(): + """msteams_webhook_url param from dashboard config should populate self.webhook_url""" + n = MSTeamsNotifier({"msteams_webhook_url": "https://outlook.office.com/webhook/xxx"}) + assert n.webhook_url == "https://outlook.office.com/webhook/xxx" + + +def test_msteams_notifier_url_is_none_without_config(monkeypatch): + """Without any config or env var, webhook_url should be falsy""" + monkeypatch.delenv("MSTEAMS_WEBHOOK_URL", raising=False) + monkeypatch.delenv("INPUT_MSTEAMS_WEBHOOK_URL", raising=False) + n = MSTeamsNotifier({}) + assert not n.webhook_url + + +def test_msteams_notifier_falls_back_to_env_var(monkeypatch): + """INPUT_MSTEAMS_WEBHOOK_URL env var should work as fallback when params empty""" + monkeypatch.setenv("INPUT_MSTEAMS_WEBHOOK_URL", "https://outlook.office.com/webhook/env") + n = MSTeamsNotifier({}) + assert n.webhook_url == "https://outlook.office.com/webhook/env" + + +def test_msteams_notifier_params_take_precedence_over_env(monkeypatch): + """Dashboard config (params) should take precedence over env var""" + monkeypatch.setenv("INPUT_MSTEAMS_WEBHOOK_URL", "https://outlook.office.com/webhook/env") + n = MSTeamsNotifier({"msteams_webhook_url": "https://outlook.office.com/webhook/dashboard"}) + assert n.webhook_url == "https://outlook.office.com/webhook/dashboard" + + +def test_msteams_enabled_via_app_config(monkeypatch): + """MS Teams notifier should load and receive URL when msteams_webhook_url is in app_config""" + monkeypatch.delenv("MSTEAMS_WEBHOOK_URL", raising=False) + monkeypatch.delenv("INPUT_MSTEAMS_WEBHOOK_URL", raising=False) + + cfg = _base_cfg() + nm = NotificationManager(cfg, app_config={"msteams_webhook_url": "https://outlook.office.com/webhook/dashboard"}) + nm.load_from_config() + + msteams = next(n for n in nm.notifiers if getattr(n, "name", "") == "msteams") + assert msteams.webhook_url == "https://outlook.office.com/webhook/dashboard" + + +def test_msteams_enabled_via_env_var(monkeypatch): + """MS Teams notifier should load when INPUT_MSTEAMS_WEBHOOK_URL env var is set""" + monkeypatch.setenv("INPUT_MSTEAMS_WEBHOOK_URL", "https://outlook.office.com/webhook/env") + + cfg = _base_cfg() + nm = NotificationManager(cfg, app_config={}) + nm.load_from_config() + + msteams = next(n for n in nm.notifiers if getattr(n, "name", "") == "msteams") + assert msteams.webhook_url == "https://outlook.office.com/webhook/env" + + +def test_msteams_app_config_precedence_over_env(monkeypatch): + """app_config msteams_webhook_url should take precedence over env var in manager flow""" + monkeypatch.setenv("INPUT_MSTEAMS_WEBHOOK_URL", "https://outlook.office.com/webhook/env") + + cfg = _base_cfg() + nm = NotificationManager(cfg, app_config={"msteams_webhook_url": "https://outlook.office.com/webhook/dashboard"}) + nm.load_from_config() + + msteams = next(n for n in nm.notifiers if getattr(n, "name", "") == "msteams") + assert msteams.webhook_url == "https://outlook.office.com/webhook/dashboard" diff --git a/tests/test_slack_notifier_params.py b/tests/test_slack_notifier_params.py new file mode 100644 index 0000000..b8cf1ee --- /dev/null +++ b/tests/test_slack_notifier_params.py @@ -0,0 +1,83 @@ +import os + +from socket_basics.core.notification.slack_notifier import SlackNotifier +from socket_basics.core.notification.manager import NotificationManager + + +def _base_cfg(): + return { + "notifiers": { + "slack": { + "module_path": "socket_basics.core.notification.slack_notifier", + "class": "SlackNotifier", + "parameters": [ + {"name": "slack_webhook_url", "env_variable": "INPUT_SLACK_WEBHOOK_URL", "type": "str"}, + ], + } + } + } + + +def test_slack_notifier_reads_url_from_params(): + """slack_webhook_url param from dashboard config should populate self.webhook_url""" + n = SlackNotifier({"slack_webhook_url": "https://hooks.slack.com/services/T00/B00/xxx"}) + assert n.webhook_url == "https://hooks.slack.com/services/T00/B00/xxx" + + +def test_slack_notifier_url_is_none_without_config(monkeypatch): + """Without any config or env var, webhook_url should be falsy""" + monkeypatch.delenv("SLACK_WEBHOOK_URL", raising=False) + monkeypatch.delenv("INPUT_SLACK_WEBHOOK_URL", raising=False) + n = SlackNotifier({}) + assert not n.webhook_url + + +def test_slack_notifier_falls_back_to_env_var(monkeypatch): + """INPUT_SLACK_WEBHOOK_URL env var should work as fallback when params empty""" + monkeypatch.setenv("INPUT_SLACK_WEBHOOK_URL", "https://hooks.slack.com/env") + n = SlackNotifier({}) + assert n.webhook_url == "https://hooks.slack.com/env" + + +def test_slack_notifier_params_take_precedence_over_env(monkeypatch): + """Dashboard config (params) should take precedence over env var""" + monkeypatch.setenv("INPUT_SLACK_WEBHOOK_URL", "https://hooks.slack.com/env") + n = SlackNotifier({"slack_webhook_url": "https://hooks.slack.com/dashboard"}) + assert n.webhook_url == "https://hooks.slack.com/dashboard" + + +def test_slack_enabled_via_app_config(monkeypatch): + """Slack notifier should load and receive URL when slack_webhook_url is in app_config""" + monkeypatch.delenv("SLACK_WEBHOOK_URL", raising=False) + monkeypatch.delenv("INPUT_SLACK_WEBHOOK_URL", raising=False) + + cfg = _base_cfg() + nm = NotificationManager(cfg, app_config={"slack_webhook_url": "https://hooks.slack.com/dashboard"}) + nm.load_from_config() + + slack = next(n for n in nm.notifiers if getattr(n, "name", "") == "slack") + assert slack.webhook_url == "https://hooks.slack.com/dashboard" + + +def test_slack_enabled_via_env_var(monkeypatch): + """Slack notifier should load when INPUT_SLACK_WEBHOOK_URL env var is set""" + monkeypatch.setenv("INPUT_SLACK_WEBHOOK_URL", "https://hooks.slack.com/env") + + cfg = _base_cfg() + nm = NotificationManager(cfg, app_config={}) + nm.load_from_config() + + slack = next(n for n in nm.notifiers if getattr(n, "name", "") == "slack") + assert slack.webhook_url == "https://hooks.slack.com/env" + + +def test_slack_app_config_precedence_over_env(monkeypatch): + """app_config slack_webhook_url should take precedence over env var in manager flow""" + monkeypatch.setenv("INPUT_SLACK_WEBHOOK_URL", "https://hooks.slack.com/env") + + cfg = _base_cfg() + nm = NotificationManager(cfg, app_config={"slack_webhook_url": "https://hooks.slack.com/dashboard"}) + nm.load_from_config() + + slack = next(n for n in nm.notifiers if getattr(n, "name", "") == "slack") + assert slack.webhook_url == "https://hooks.slack.com/dashboard"