mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-06-29 15:31:05 +08:00
fix: require explicit anonymous webhook access (#14890)
### What problem does this PR solve? Fixes #14882 Agent webhook execution currently fails open when the saved webhook `security` block is missing/empty, or when `auth_type` is set to `none`. This allows unauthenticated webhook invocation without an explicit operator opt-in. This PR makes anonymous webhook access explicit: - Rejects missing or empty webhook security config. - Requires `allow_anonymous: true` when `auth_type` is `none`. - Preserves explicit anonymous webhooks by having the frontend serialize `allow_anonymous: true` when the user selects `None` auth. - Updates webhook unit tests to cover both denied implicit-anonymous configs and allowed explicit-anonymous configs. ### Type of change - [x] Bug Fix - [x] Security hardening - [x] Test ### Tests - [x] `ZHIPU_AI_API_KEY=dummy uv run python -m pytest --confcutdir=test/testcases/test_web_api/test_agent_app test/testcases/test_web_api/test_agent_app/test_agents_webhook_unit.py` - [x] `uv run ruff check api/apps/restful_apis/agent_api.py test/testcases/test_web_api/test_agent_app/test_agents_webhook_unit.py` - [x] `npm exec eslint src/pages/agent/utils.ts src/pages/agent/form/begin-form/schema.ts` --------- Co-authored-by: Zhichang Yu <yuzhichang@gmail.com>
This commit is contained in:
@@ -100,6 +100,22 @@ def _require_canvas_owner_sync(func):
|
||||
return wrapper
|
||||
|
||||
|
||||
def _is_truthy(value):
|
||||
if isinstance(value, bool):
|
||||
return value
|
||||
if isinstance(value, int):
|
||||
return value != 0
|
||||
if isinstance(value, str):
|
||||
return value.strip().lower() in {"1", "true", "yes", "on"}
|
||||
return False
|
||||
|
||||
|
||||
def _allow_anonymous_webhook(security_cfg: dict) -> bool:
|
||||
if not isinstance(security_cfg, dict):
|
||||
return False
|
||||
return _is_truthy(security_cfg.get("allow_anonymous"))
|
||||
|
||||
|
||||
def _get_user_nickname(user_id: str) -> str:
|
||||
exists, user = UserService.get_by_id(user_id)
|
||||
if not exists:
|
||||
@@ -1567,9 +1583,26 @@ async def agent_chat_completion(tenant_id, agent_id=None):
|
||||
|
||||
|
||||
@manager.route("/agents/<agent_id>/webhook", methods=["POST", "GET", "PUT", "PATCH", "DELETE", "HEAD"]) # noqa: F821
|
||||
@manager.route("/agents/<agent_id>/webhook/test",methods=["POST", "GET", "PUT", "PATCH", "DELETE", "HEAD"],) # noqa: F821
|
||||
async def webhook(agent_id: str):
|
||||
is_test = request.path.startswith(f"/api/v1/agents/{agent_id}/webhook/test")
|
||||
return await _webhook_impl(agent_id, is_test=False)
|
||||
|
||||
|
||||
@manager.route("/agents/<agent_id>/webhook/test", methods=["POST", "GET", "PUT", "PATCH", "DELETE", "HEAD"]) # noqa: F821
|
||||
@login_required
|
||||
@add_tenant_id_to_kwargs
|
||||
async def webhook_test(agent_id: str, tenant_id: str):
|
||||
if not UserCanvasService.query(user_id=tenant_id, id=agent_id):
|
||||
logging.warning(
|
||||
"Webhook test denied: owner check failed agent_id=%s tenant_id=%s method=%s",
|
||||
agent_id,
|
||||
tenant_id,
|
||||
request.method,
|
||||
)
|
||||
return get_json_result(data=False, message="Only the owner of the agent is authorized for this operation.", code=RetCode.OPERATING_ERROR)
|
||||
return await _webhook_impl(agent_id, is_test=True)
|
||||
|
||||
|
||||
async def _webhook_impl(agent_id: str, is_test: bool):
|
||||
start_ts = time.time()
|
||||
|
||||
# 1. Fetch canvas by agent_id
|
||||
@@ -1605,12 +1638,16 @@ async def webhook(agent_id: str):
|
||||
code=RetCode.BAD_REQUEST,message=f"HTTP method '{request_method}' not allowed for this webhook."
|
||||
),RetCode.BAD_REQUEST
|
||||
|
||||
# 6. Validate webhook security
|
||||
async def validate_webhook_security(security_cfg: dict):
|
||||
"""Validate webhook security rules based on security configuration."""
|
||||
|
||||
if not security_cfg:
|
||||
return # No security config → allowed by default
|
||||
if not isinstance(security_cfg, dict) or not security_cfg:
|
||||
logging.warning(
|
||||
"Webhook denied: missing security config agent_id=%s method=%s",
|
||||
agent_id,
|
||||
request.method,
|
||||
)
|
||||
raise Exception("Webhook security is required. Set allow_anonymous to true to permit unauthenticated webhooks.")
|
||||
|
||||
# 1. Validate max body size
|
||||
await _validate_max_body_size(security_cfg)
|
||||
@@ -1625,6 +1662,13 @@ async def webhook(agent_id: str):
|
||||
auth_type = security_cfg.get("auth_type", "none")
|
||||
|
||||
if auth_type == "none":
|
||||
if not _allow_anonymous_webhook(security_cfg):
|
||||
logging.warning(
|
||||
"Webhook denied: anonymous access missing explicit opt-in agent_id=%s method=%s",
|
||||
agent_id,
|
||||
request.method,
|
||||
)
|
||||
raise Exception("Anonymous webhook access requires allow_anonymous to be true")
|
||||
return
|
||||
|
||||
if auth_type == "token":
|
||||
@@ -1643,7 +1687,7 @@ async def webhook(agent_id: str):
|
||||
"""Check request size does not exceed max_body_size."""
|
||||
max_size = security_cfg.get("max_body_size")
|
||||
if not max_size:
|
||||
return
|
||||
max_size = "10MB"
|
||||
|
||||
# Convert "10MB" → bytes
|
||||
units = {"kb": 1024, "mb": 1024**2}
|
||||
@@ -1688,7 +1732,7 @@ async def webhook(agent_id: str):
|
||||
"""Simple in-memory rate limiting."""
|
||||
rl = security_cfg.get("rate_limit")
|
||||
if not rl:
|
||||
return
|
||||
rl = {"limit": 60, "per": "minute"}
|
||||
|
||||
limit = int(rl.get("limit", 60))
|
||||
if limit <= 0:
|
||||
|
||||
@@ -167,6 +167,12 @@ def _default_webhook_params(
|
||||
}
|
||||
|
||||
|
||||
def _anonymous_security(**overrides):
|
||||
security = {"auth_type": "none", "allow_anonymous": True}
|
||||
security.update(overrides)
|
||||
return security
|
||||
|
||||
|
||||
def _make_webhook_cvs(module, *, params=None, dsl=None, canvas_category=None):
|
||||
if dsl is None:
|
||||
if params is None:
|
||||
@@ -654,7 +660,15 @@ def test_webhook_security_dispatch(monkeypatch):
|
||||
_DummyRequest(headers={"Content-Type": "application/json"}, json_body={}, args={"a": "b"}),
|
||||
)
|
||||
|
||||
for security in ({}, {"auth_type": "none"}):
|
||||
for security, message in (
|
||||
({}, "Webhook security is required"),
|
||||
({"auth_type": "none"}, "Anonymous webhook access requires allow_anonymous"),
|
||||
):
|
||||
cvs = _make_webhook_cvs(module, params=_default_webhook_params(security=security))
|
||||
monkeypatch.setattr(module.UserCanvasService, "get_by_id", lambda _id, _cvs=cvs: (True, _cvs))
|
||||
_assert_bad_request(_run(module.webhook("agent-1")), message)
|
||||
|
||||
for security in (_anonymous_security(), _anonymous_security(allow_anonymous="true")):
|
||||
cvs = _make_webhook_cvs(module, params=_default_webhook_params(security=security))
|
||||
monkeypatch.setattr(module.UserCanvasService, "get_by_id", lambda _id, _cvs=cvs: (True, _cvs))
|
||||
res = _run(module.webhook("agent-1"))
|
||||
@@ -666,6 +680,30 @@ def test_webhook_security_dispatch(monkeypatch):
|
||||
_assert_bad_request(_run(module.webhook("agent-1")), "Unsupported auth_type")
|
||||
|
||||
|
||||
@pytest.mark.p2
|
||||
def test_webhook_test_requires_owner(monkeypatch):
|
||||
module = _load_agents_app(monkeypatch)
|
||||
_patch_background_task(monkeypatch, module)
|
||||
|
||||
monkeypatch.setattr(
|
||||
module,
|
||||
"request",
|
||||
_DummyRequest(path="/api/v1/agents/agent-1/webhook/test", headers={"Content-Type": "application/json"}, json_body={}),
|
||||
)
|
||||
|
||||
monkeypatch.setattr(module.UserCanvasService, "query", lambda **_kwargs: [])
|
||||
denied = _run(module.webhook_test(agent_id="agent-1"))
|
||||
assert denied["code"] == module.RetCode.OPERATING_ERROR
|
||||
assert "Only the owner" in denied["message"]
|
||||
|
||||
cvs = _make_webhook_cvs(module, params=_default_webhook_params(security=_anonymous_security()))
|
||||
monkeypatch.setattr(module.UserCanvasService, "query", lambda **_kwargs: [cvs])
|
||||
monkeypatch.setattr(module.UserCanvasService, "get_by_id", lambda _id: (True, cvs))
|
||||
allowed = _run(module.webhook_test(agent_id="agent-1"))
|
||||
assert hasattr(allowed, "status_code")
|
||||
assert allowed.status_code == 200
|
||||
|
||||
|
||||
@pytest.mark.p2
|
||||
def test_webhook_max_body_size(monkeypatch):
|
||||
module = _load_agents_app(monkeypatch)
|
||||
@@ -674,18 +712,18 @@ def test_webhook_max_body_size(monkeypatch):
|
||||
base_request = _DummyRequest(headers={"Content-Type": "application/json"}, json_body={})
|
||||
monkeypatch.setattr(module, "request", base_request)
|
||||
|
||||
cvs = _make_webhook_cvs(module, params=_default_webhook_params(security={"auth_type": "none"}))
|
||||
cvs = _make_webhook_cvs(module, params=_default_webhook_params(security=_anonymous_security()))
|
||||
monkeypatch.setattr(module.UserCanvasService, "get_by_id", lambda _id: (True, cvs))
|
||||
res = _run(module.webhook("agent-1"))
|
||||
assert hasattr(res, "status_code")
|
||||
assert res.status_code == 200
|
||||
|
||||
security = {"auth_type": "none", "max_body_size": "123"}
|
||||
security = _anonymous_security(max_body_size="123")
|
||||
cvs = _make_webhook_cvs(module, params=_default_webhook_params(security=security))
|
||||
monkeypatch.setattr(module.UserCanvasService, "get_by_id", lambda _id: (True, cvs))
|
||||
_assert_bad_request(_run(module.webhook("agent-1")), "Invalid max_body_size format")
|
||||
|
||||
security = {"auth_type": "none", "max_body_size": "11mb"}
|
||||
security = _anonymous_security(max_body_size="11mb")
|
||||
cvs = _make_webhook_cvs(module, params=_default_webhook_params(security=security))
|
||||
monkeypatch.setattr(module.UserCanvasService, "get_by_id", lambda _id: (True, cvs))
|
||||
_assert_bad_request(_run(module.webhook("agent-1")), "exceeds maximum allowed size")
|
||||
@@ -695,11 +733,35 @@ def test_webhook_max_body_size(monkeypatch):
|
||||
"request",
|
||||
_DummyRequest(headers={"Content-Type": "application/json"}, json_body={}, content_length=2048),
|
||||
)
|
||||
security = {"auth_type": "none", "max_body_size": "1kb"}
|
||||
security = _anonymous_security(max_body_size="1kb")
|
||||
cvs = _make_webhook_cvs(module, params=_default_webhook_params(security=security))
|
||||
monkeypatch.setattr(module.UserCanvasService, "get_by_id", lambda _id: (True, cvs))
|
||||
_assert_bad_request(_run(module.webhook("agent-1")), "Request body too large")
|
||||
|
||||
monkeypatch.setattr(
|
||||
module,
|
||||
"request",
|
||||
_DummyRequest(headers={"Content-Type": "application/json"}, json_body={}, content_length=10 * 1024 * 1024 + 1),
|
||||
)
|
||||
security = _anonymous_security()
|
||||
cvs = _make_webhook_cvs(module, params=_default_webhook_params(security=security))
|
||||
monkeypatch.setattr(module.UserCanvasService, "get_by_id", lambda _id: (True, cvs))
|
||||
_assert_bad_request(_run(module.webhook("agent-1")), "Request body too large")
|
||||
|
||||
token_security = {"auth_type": "token", "token": {"token_header": "X-TOKEN", "token_value": "ok"}}
|
||||
cvs = _make_webhook_cvs(module, params=_default_webhook_params(security=token_security))
|
||||
monkeypatch.setattr(module.UserCanvasService, "get_by_id", lambda _id: (True, cvs))
|
||||
monkeypatch.setattr(
|
||||
module,
|
||||
"request",
|
||||
_DummyRequest(
|
||||
headers={"Content-Type": "application/json", "X-TOKEN": "ok"},
|
||||
json_body={},
|
||||
content_length=10 * 1024 * 1024 + 1,
|
||||
),
|
||||
)
|
||||
_assert_bad_request(_run(module.webhook("agent-1")), "Request body too large")
|
||||
|
||||
|
||||
@pytest.mark.p2
|
||||
def test_webhook_ip_whitelist(monkeypatch):
|
||||
@@ -713,14 +775,14 @@ def test_webhook_ip_whitelist(monkeypatch):
|
||||
)
|
||||
|
||||
for whitelist in ([], ["127.0.0.0/24"], ["127.0.0.1"]):
|
||||
security = {"auth_type": "none", "ip_whitelist": whitelist}
|
||||
security = _anonymous_security(ip_whitelist=whitelist)
|
||||
cvs = _make_webhook_cvs(module, params=_default_webhook_params(security=security))
|
||||
monkeypatch.setattr(module.UserCanvasService, "get_by_id", lambda _id, _cvs=cvs: (True, _cvs))
|
||||
res = _run(module.webhook("agent-1"))
|
||||
assert hasattr(res, "status_code"), res
|
||||
assert res.status_code == 200
|
||||
|
||||
security = {"auth_type": "none", "ip_whitelist": ["10.0.0.1"]}
|
||||
security = _anonymous_security(ip_whitelist=["10.0.0.1"])
|
||||
cvs = _make_webhook_cvs(module, params=_default_webhook_params(security=security))
|
||||
monkeypatch.setattr(module.UserCanvasService, "get_by_id", lambda _id: (True, cvs))
|
||||
_assert_bad_request(_run(module.webhook("agent-1")), "is not allowed")
|
||||
@@ -733,25 +795,47 @@ def test_webhook_rate_limit(monkeypatch):
|
||||
|
||||
monkeypatch.setattr(module, "request", _DummyRequest(headers={"Content-Type": "application/json"}, json_body={}))
|
||||
|
||||
cvs = _make_webhook_cvs(module, params=_default_webhook_params(security={"auth_type": "none"}))
|
||||
cvs = _make_webhook_cvs(module, params=_default_webhook_params(security=_anonymous_security()))
|
||||
monkeypatch.setattr(module.UserCanvasService, "get_by_id", lambda _id: (True, cvs))
|
||||
res = _run(module.webhook("agent-1"))
|
||||
assert hasattr(res, "status_code")
|
||||
assert res.status_code == 200
|
||||
|
||||
bad_limit = {"auth_type": "none", "rate_limit": {"limit": 0, "per": "minute"}}
|
||||
module.REDIS_CONN.bucket_result = [0]
|
||||
cvs = _make_webhook_cvs(module, params=_default_webhook_params(security=_anonymous_security()))
|
||||
monkeypatch.setattr(module.UserCanvasService, "get_by_id", lambda _id: (True, cvs))
|
||||
_assert_bad_request(_run(module.webhook("agent-1")), "Too many requests")
|
||||
|
||||
module.REDIS_CONN.bucket_result = [1]
|
||||
token_security = {"auth_type": "token", "token": {"token_header": "X-TOKEN", "token_value": "ok"}}
|
||||
cvs = _make_webhook_cvs(module, params=_default_webhook_params(security=token_security))
|
||||
monkeypatch.setattr(module.UserCanvasService, "get_by_id", lambda _id: (True, cvs))
|
||||
monkeypatch.setattr(
|
||||
module,
|
||||
"request",
|
||||
_DummyRequest(headers={"Content-Type": "application/json", "X-TOKEN": "ok"}, json_body={}),
|
||||
)
|
||||
res = _run(module.webhook("agent-1"))
|
||||
assert hasattr(res, "status_code")
|
||||
assert res.status_code == 200
|
||||
|
||||
module.REDIS_CONN.bucket_result = [0]
|
||||
_assert_bad_request(_run(module.webhook("agent-1")), "Too many requests")
|
||||
|
||||
module.REDIS_CONN.bucket_result = [1]
|
||||
bad_limit = _anonymous_security(rate_limit={"limit": 0, "per": "minute"})
|
||||
cvs = _make_webhook_cvs(module, params=_default_webhook_params(security=bad_limit))
|
||||
monkeypatch.setattr(module.UserCanvasService, "get_by_id", lambda _id: (True, cvs))
|
||||
_assert_bad_request(_run(module.webhook("agent-1")), "rate_limit.limit must be > 0")
|
||||
|
||||
bad_per = {"auth_type": "none", "rate_limit": {"limit": 1, "per": "week"}}
|
||||
bad_per = _anonymous_security(rate_limit={"limit": 1, "per": "week"})
|
||||
cvs = _make_webhook_cvs(module, params=_default_webhook_params(security=bad_per))
|
||||
monkeypatch.setattr(module.UserCanvasService, "get_by_id", lambda _id: (True, cvs))
|
||||
_assert_bad_request(_run(module.webhook("agent-1")), "Invalid rate_limit.per")
|
||||
|
||||
module.REDIS_CONN.bucket_result = [0]
|
||||
module.REDIS_CONN.bucket_exc = None
|
||||
denied = {"auth_type": "none", "rate_limit": {"limit": 1, "per": "minute"}}
|
||||
denied = _anonymous_security(rate_limit={"limit": 1, "per": "minute"})
|
||||
cvs = _make_webhook_cvs(module, params=_default_webhook_params(security=denied))
|
||||
monkeypatch.setattr(module.UserCanvasService, "get_by_id", lambda _id: (True, cvs))
|
||||
_assert_bad_request(_run(module.webhook("agent-1")), "Too many requests")
|
||||
@@ -869,7 +953,7 @@ def test_webhook_parse_request_branches(monkeypatch):
|
||||
module = _load_agents_app(monkeypatch)
|
||||
_patch_background_task(monkeypatch, module)
|
||||
|
||||
security = {"auth_type": "none"}
|
||||
security = _anonymous_security()
|
||||
params = _default_webhook_params(security=security, content_types="application/json")
|
||||
cvs = _make_webhook_cvs(module, params=params)
|
||||
monkeypatch.setattr(module.UserCanvasService, "get_by_id", lambda _id: (True, cvs))
|
||||
@@ -935,7 +1019,7 @@ def test_webhook_parse_request_branches(monkeypatch):
|
||||
def test_webhook_canvas_constructor_exception(monkeypatch):
|
||||
module = _load_agents_app(monkeypatch)
|
||||
|
||||
params = _default_webhook_params(security={"auth_type": "none"})
|
||||
params = _default_webhook_params(security=_anonymous_security())
|
||||
cvs = _make_webhook_cvs(module, params=params)
|
||||
monkeypatch.setattr(module.UserCanvasService, "get_by_id", lambda _id: (True, cvs))
|
||||
monkeypatch.setattr(
|
||||
@@ -1048,7 +1132,7 @@ def test_webhook_parse_request_form_and_raw_body_paths(monkeypatch):
|
||||
module = _load_agents_app(monkeypatch)
|
||||
_patch_background_task(monkeypatch, module)
|
||||
|
||||
security = {"auth_type": "none"}
|
||||
security = _anonymous_security()
|
||||
|
||||
def _run_with(params, req):
|
||||
cvs = _make_webhook_cvs(module, params=params)
|
||||
@@ -1137,7 +1221,7 @@ def test_webhook_schema_extract_cast_defaults_and_validation_errors(monkeypatch)
|
||||
}
|
||||
|
||||
params = _default_webhook_params(
|
||||
security={"auth_type": "none"},
|
||||
security=_anonymous_security(),
|
||||
content_types="application/json",
|
||||
schema=base_schema,
|
||||
)
|
||||
@@ -1216,7 +1300,7 @@ def test_webhook_schema_extract_cast_defaults_and_validation_errors(monkeypatch)
|
||||
|
||||
for schema, body_payload, expected_substring in failure_cases:
|
||||
params = _default_webhook_params(
|
||||
security={"auth_type": "none"},
|
||||
security=_anonymous_security(),
|
||||
content_types="application/json",
|
||||
schema=schema,
|
||||
)
|
||||
@@ -1238,7 +1322,7 @@ def test_webhook_immediate_response_status_and_template_validation(monkeypatch):
|
||||
|
||||
def _run_case(response_cfg):
|
||||
params = _default_webhook_params(
|
||||
security={"auth_type": "none"},
|
||||
security=_anonymous_security(),
|
||||
content_types="application/json",
|
||||
response=response_cfg,
|
||||
)
|
||||
@@ -1301,8 +1385,9 @@ def test_webhook_background_run_success_and_error_trace_paths(monkeypatch):
|
||||
|
||||
monkeypatch.setattr(module, "Canvas", _CanvasSuccess)
|
||||
|
||||
params = _default_webhook_params(security={"auth_type": "none"}, content_types="application/json")
|
||||
params = _default_webhook_params(security=_anonymous_security(), content_types="application/json")
|
||||
cvs = _make_webhook_cvs(module, params=params)
|
||||
monkeypatch.setattr(module.UserCanvasService, "query", lambda **_kwargs: [cvs])
|
||||
monkeypatch.setattr(module.UserCanvasService, "get_by_id", lambda _id: (True, cvs))
|
||||
monkeypatch.setattr(
|
||||
module,
|
||||
@@ -1310,7 +1395,7 @@ def test_webhook_background_run_success_and_error_trace_paths(monkeypatch):
|
||||
_DummyRequest(path="/api/v1/agents/agent-1/webhook/test", headers={"Content-Type": "application/json"}, json_body={}),
|
||||
)
|
||||
|
||||
res = _run(module.webhook("agent-1"))
|
||||
res = _run(module.webhook_test(agent_id="agent-1"))
|
||||
assert res.status_code == 200
|
||||
assert len(tasks) == 1
|
||||
_run(tasks.pop(0))
|
||||
@@ -1332,8 +1417,9 @@ def test_webhook_background_run_success_and_error_trace_paths(monkeypatch):
|
||||
tasks.clear()
|
||||
redis_store.clear()
|
||||
cvs = _make_webhook_cvs(module, params=params)
|
||||
monkeypatch.setattr(module.UserCanvasService, "query", lambda **_kwargs: [cvs])
|
||||
monkeypatch.setattr(module.UserCanvasService, "get_by_id", lambda _id, _cvs=cvs: (True, _cvs))
|
||||
res = _run(module.webhook("agent-1"))
|
||||
res = _run(module.webhook_test(agent_id="agent-1"))
|
||||
assert res.status_code == 200
|
||||
_run(tasks.pop(0))
|
||||
trace_obj = json.loads(redis_store[key])
|
||||
@@ -1348,8 +1434,9 @@ def test_webhook_background_run_success_and_error_trace_paths(monkeypatch):
|
||||
monkeypatch.setattr(module.REDIS_CONN, "set_obj", lambda *_args, **_kwargs: None)
|
||||
tasks.clear()
|
||||
cvs = _make_webhook_cvs(module, params=params)
|
||||
monkeypatch.setattr(module.UserCanvasService, "query", lambda **_kwargs: [cvs])
|
||||
monkeypatch.setattr(module.UserCanvasService, "get_by_id", lambda _id, _cvs=cvs: (True, _cvs))
|
||||
_run(module.webhook("agent-1"))
|
||||
_run(module.webhook_test(agent_id="agent-1"))
|
||||
_run(tasks.pop(0))
|
||||
assert any("Failed to append webhook trace" in msg for msg in log_messages)
|
||||
|
||||
@@ -1363,11 +1450,12 @@ def test_webhook_sse_success_and_exception_paths(monkeypatch):
|
||||
monkeypatch.setattr(module.REDIS_CONN, "set_obj", lambda key, obj, _ttl: redis_store.__setitem__(key, json.dumps(obj)))
|
||||
|
||||
params = _default_webhook_params(
|
||||
security={"auth_type": "none"},
|
||||
security=_anonymous_security(),
|
||||
content_types="application/json",
|
||||
execution_mode="Deferred",
|
||||
)
|
||||
cvs = _make_webhook_cvs(module, params=params)
|
||||
monkeypatch.setattr(module.UserCanvasService, "query", lambda **_kwargs: [cvs])
|
||||
monkeypatch.setattr(module.UserCanvasService, "get_by_id", lambda _id: (True, cvs))
|
||||
|
||||
class _CanvasSSESuccess(_StubCanvas):
|
||||
@@ -1383,7 +1471,7 @@ def test_webhook_sse_success_and_exception_paths(monkeypatch):
|
||||
"request",
|
||||
_DummyRequest(path="/api/v1/agents/agent-1/webhook/test", headers={"Content-Type": "application/json"}, json_body={}),
|
||||
)
|
||||
res = _run(module.webhook("agent-1"))
|
||||
res = _run(module.webhook_test(agent_id="agent-1"))
|
||||
assert res.status_code == 201
|
||||
payload = json.loads(_run(res.get_data(as_text=True)))
|
||||
assert payload == {"message": "<think></think>Hello", "success": True, "code": 201}
|
||||
@@ -1399,7 +1487,7 @@ def test_webhook_sse_success_and_exception_paths(monkeypatch):
|
||||
"request",
|
||||
_DummyRequest(path="/api/v1/agents/agent-1/webhook/test", headers={"Content-Type": "application/json"}, json_body={}),
|
||||
)
|
||||
res = _run(module.webhook("agent-1"))
|
||||
res = _run(module.webhook_test(agent_id="agent-1"))
|
||||
assert res.status_code == 400
|
||||
payload = json.loads(_run(res.get_data(as_text=True)))
|
||||
assert payload["code"] == 400
|
||||
|
||||
@@ -2881,6 +2881,9 @@ Important structured information may include: names, dates, locations, events, k
|
||||
'Accepted Response: The system returns an acknowledgment immediately after the request is validated, while the workflow continues to execute asynchronously in the background. /Final Response: The system returns a response only after the workflow execution is completed.',
|
||||
authMethods: 'Authentication methods',
|
||||
authType: 'Authentication type',
|
||||
allowAnonymous: 'Allow anonymous access',
|
||||
allowAnonymousTip:
|
||||
'Anyone with this webhook URL can trigger the agent when this is enabled.',
|
||||
limit: 'Request frequency limit',
|
||||
per: 'Time period',
|
||||
maxBodySize: 'Maximum body size',
|
||||
|
||||
@@ -29,6 +29,7 @@ export const BeginFormSchema = z.object({
|
||||
per: z.string().optional(),
|
||||
}),
|
||||
max_body_size: z.string(),
|
||||
allow_anonymous: z.boolean().optional(),
|
||||
jwt: z
|
||||
.object({
|
||||
algorithm: z.string().default(WebhookJWTAlgorithmList[0]).optional(),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { SelectWithSearch } from '@/components/originui/select-with-search';
|
||||
import { RAGFlowFormItem } from '@/components/ragflow-form';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
import { WebhookJWTAlgorithmList } from '@/constants/agent';
|
||||
import { WebhookSecurityAuthType } from '@/pages/agent/constant';
|
||||
import { buildOptions } from '@/utils/form';
|
||||
@@ -100,7 +101,21 @@ export function Auth() {
|
||||
[WebhookSecurityAuthType.Token]: renderTokenAuth,
|
||||
[WebhookSecurityAuthType.Basic]: renderBasicAuth,
|
||||
[WebhookSecurityAuthType.Jwt]: renderJwtAuth,
|
||||
[WebhookSecurityAuthType.None]: () => null,
|
||||
[WebhookSecurityAuthType.None]: () => (
|
||||
<RAGFlowFormItem
|
||||
name="security.allow_anonymous"
|
||||
label={t('flow.webhook.allowAnonymous')}
|
||||
tooltip={t('flow.webhook.allowAnonymousTip')}
|
||||
horizontal
|
||||
>
|
||||
{(field) => (
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
></Switch>
|
||||
)}
|
||||
</RAGFlowFormItem>
|
||||
),
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -456,6 +456,14 @@ function transformBeginParams(params: BeginFormSchemaType) {
|
||||
required_claims: security?.jwt?.required_claims.map((x) => x.value),
|
||||
};
|
||||
}
|
||||
if (
|
||||
params.security?.auth_type === WebhookSecurityAuthType.None &&
|
||||
params.security?.allow_anonymous
|
||||
) {
|
||||
nextSecurity.allow_anonymous = true;
|
||||
} else {
|
||||
delete nextSecurity.allow_anonymous;
|
||||
}
|
||||
return {
|
||||
...params,
|
||||
schema: transformRequestSchemaToJsonschema(params.schema),
|
||||
|
||||
Reference in New Issue
Block a user