Harden msgraph webhook auth requirements (#30169)

This commit is contained in:
Teknium 2026-05-24 04:25:20 -07:00 committed by GitHub
parent 3e78e353d7
commit 4ca77f1059
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 38 additions and 4 deletions

View file

@ -424,7 +424,9 @@ _PLATFORM_CONNECTED_CHECKERS: dict[Platform, Callable[[PlatformConfig], bool]] =
Platform.SMS: lambda cfg: bool(os.getenv("TWILIO_ACCOUNT_SID")),
Platform.API_SERVER: lambda cfg: True,
Platform.WEBHOOK: lambda cfg: True,
Platform.MSGRAPH_WEBHOOK: lambda cfg: True,
Platform.MSGRAPH_WEBHOOK: lambda cfg: bool(
str(cfg.extra.get("client_state") or "").strip()
),
Platform.FEISHU: lambda cfg: bool(cfg.extra.get("app_id")),
Platform.WECOM: lambda cfg: bool(cfg.extra.get("bot_id")),
Platform.WECOM_CALLBACK: lambda cfg: bool(

View file

@ -133,6 +133,12 @@ class MSGraphWebhookAdapter(BasePlatformAdapter):
self._notification_scheduler = scheduler
async def connect(self) -> bool:
if self._client_state is None:
logger.error(
"[msgraph_webhook] Refusing to start without extra.client_state configured"
)
return False
app = web.Application()
app.router.add_get(self._health_path, self._handle_health)
app.router.add_get(self._webhook_path, self._handle_validation)
@ -310,7 +316,7 @@ class MSGraphWebhookAdapter(BasePlatformAdapter):
"""
expected = self._client_state
if expected is None:
return True
return False
provided = self._string_or_none(notification.get("clientState"))
if provided is None:
return False

View file

@ -6,7 +6,7 @@ import json
import pytest
from gateway.config import GatewayConfig, Platform, PlatformConfig, _apply_env_overrides
from gateway.platforms.msgraph_webhook import MSGraphWebhookAdapter
from gateway.platforms.msgraph_webhook import AIOHTTP_AVAILABLE, MSGraphWebhookAdapter
def _make_adapter(**extra_overrides) -> MSGraphWebhookAdapter:
@ -70,6 +70,15 @@ class TestMSGraphWebhookConfig:
class TestMSGraphValidationHandshake:
@pytest.mark.anyio
async def test_connect_requires_client_state(self):
if not AIOHTTP_AVAILABLE:
pytest.skip("aiohttp not installed")
adapter = MSGraphWebhookAdapter(PlatformConfig(enabled=True, extra={}))
connected = await adapter.connect()
assert connected is False
assert adapter.is_connected() is False
@pytest.mark.anyio
async def test_validation_token_echo_on_get(self):
adapter = _make_adapter()
@ -99,6 +108,22 @@ class TestMSGraphValidationHandshake:
class TestMSGraphNotifications:
@pytest.mark.anyio
async def test_missing_client_state_is_auth_rejected(self):
adapter = _make_adapter(client_state=None)
payload = {
"value": [
{
"id": "notif-no-client-state",
"subscriptionId": "sub-1",
"changeType": "updated",
"resource": "communications/onlineMeetings/meeting-1",
}
]
}
resp = await adapter._handle_notification(_FakeRequest(json_payload=payload))
assert resp.status == 403
@pytest.mark.anyio
async def test_valid_notification_accepted_and_scheduled(self):
adapter = _make_adapter()

View file

@ -79,10 +79,11 @@ def test_checker_returns_true_when_configured(platform, checker, monkeypatch):
elif platform in {
Platform.API_SERVER,
Platform.WEBHOOK,
Platform.MSGRAPH_WEBHOOK,
Platform.WHATSAPP,
}:
mock_config.extra = {}
elif platform == Platform.MSGRAPH_WEBHOOK:
mock_config.extra = {"client_state": "expected-client-state"}
elif platform == Platform.FEISHU:
mock_config.extra = {"app_id": "app"}
elif platform == Platform.WECOM: