mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-06-01 07:01:41 +00:00
Harden msgraph webhook auth requirements (#30169)
This commit is contained in:
parent
3e78e353d7
commit
4ca77f1059
4 changed files with 38 additions and 4 deletions
|
|
@ -424,7 +424,9 @@ _PLATFORM_CONNECTED_CHECKERS: dict[Platform, Callable[[PlatformConfig], bool]] =
|
||||||
Platform.SMS: lambda cfg: bool(os.getenv("TWILIO_ACCOUNT_SID")),
|
Platform.SMS: lambda cfg: bool(os.getenv("TWILIO_ACCOUNT_SID")),
|
||||||
Platform.API_SERVER: lambda cfg: True,
|
Platform.API_SERVER: lambda cfg: True,
|
||||||
Platform.WEBHOOK: 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.FEISHU: lambda cfg: bool(cfg.extra.get("app_id")),
|
||||||
Platform.WECOM: lambda cfg: bool(cfg.extra.get("bot_id")),
|
Platform.WECOM: lambda cfg: bool(cfg.extra.get("bot_id")),
|
||||||
Platform.WECOM_CALLBACK: lambda cfg: bool(
|
Platform.WECOM_CALLBACK: lambda cfg: bool(
|
||||||
|
|
|
||||||
|
|
@ -133,6 +133,12 @@ class MSGraphWebhookAdapter(BasePlatformAdapter):
|
||||||
self._notification_scheduler = scheduler
|
self._notification_scheduler = scheduler
|
||||||
|
|
||||||
async def connect(self) -> bool:
|
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 = web.Application()
|
||||||
app.router.add_get(self._health_path, self._handle_health)
|
app.router.add_get(self._health_path, self._handle_health)
|
||||||
app.router.add_get(self._webhook_path, self._handle_validation)
|
app.router.add_get(self._webhook_path, self._handle_validation)
|
||||||
|
|
@ -310,7 +316,7 @@ class MSGraphWebhookAdapter(BasePlatformAdapter):
|
||||||
"""
|
"""
|
||||||
expected = self._client_state
|
expected = self._client_state
|
||||||
if expected is None:
|
if expected is None:
|
||||||
return True
|
return False
|
||||||
provided = self._string_or_none(notification.get("clientState"))
|
provided = self._string_or_none(notification.get("clientState"))
|
||||||
if provided is None:
|
if provided is None:
|
||||||
return False
|
return False
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import json
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from gateway.config import GatewayConfig, Platform, PlatformConfig, _apply_env_overrides
|
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:
|
def _make_adapter(**extra_overrides) -> MSGraphWebhookAdapter:
|
||||||
|
|
@ -70,6 +70,15 @@ class TestMSGraphWebhookConfig:
|
||||||
|
|
||||||
|
|
||||||
class TestMSGraphValidationHandshake:
|
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
|
@pytest.mark.anyio
|
||||||
async def test_validation_token_echo_on_get(self):
|
async def test_validation_token_echo_on_get(self):
|
||||||
adapter = _make_adapter()
|
adapter = _make_adapter()
|
||||||
|
|
@ -99,6 +108,22 @@ class TestMSGraphValidationHandshake:
|
||||||
|
|
||||||
|
|
||||||
class TestMSGraphNotifications:
|
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
|
@pytest.mark.anyio
|
||||||
async def test_valid_notification_accepted_and_scheduled(self):
|
async def test_valid_notification_accepted_and_scheduled(self):
|
||||||
adapter = _make_adapter()
|
adapter = _make_adapter()
|
||||||
|
|
|
||||||
|
|
@ -79,10 +79,11 @@ def test_checker_returns_true_when_configured(platform, checker, monkeypatch):
|
||||||
elif platform in {
|
elif platform in {
|
||||||
Platform.API_SERVER,
|
Platform.API_SERVER,
|
||||||
Platform.WEBHOOK,
|
Platform.WEBHOOK,
|
||||||
Platform.MSGRAPH_WEBHOOK,
|
|
||||||
Platform.WHATSAPP,
|
Platform.WHATSAPP,
|
||||||
}:
|
}:
|
||||||
mock_config.extra = {}
|
mock_config.extra = {}
|
||||||
|
elif platform == Platform.MSGRAPH_WEBHOOK:
|
||||||
|
mock_config.extra = {"client_state": "expected-client-state"}
|
||||||
elif platform == Platform.FEISHU:
|
elif platform == Platform.FEISHU:
|
||||||
mock_config.extra = {"app_id": "app"}
|
mock_config.extra = {"app_id": "app"}
|
||||||
elif platform == Platform.WECOM:
|
elif platform == Platform.WECOM:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue