update
This commit is contained in:
parent
4038924415
commit
f9239a4c82
1 changed files with 61 additions and 83 deletions
|
|
@ -310,22 +310,61 @@ async def webhook(agent_id: str):
|
||||||
if not auth_header.startswith("Bearer "):
|
if not auth_header.startswith("Bearer "):
|
||||||
raise Exception("Missing Bearer token")
|
raise Exception("Missing Bearer token")
|
||||||
|
|
||||||
token = auth_header.replace("Bearer ", "")
|
token = auth_header[len("Bearer "):].strip()
|
||||||
|
if not token:
|
||||||
|
raise Exception("Empty Bearer token")
|
||||||
|
|
||||||
|
alg = (jwt_cfg.get("algorithm") or "HS256").upper()
|
||||||
|
|
||||||
|
decode_kwargs = {
|
||||||
|
"key": secret,
|
||||||
|
"algorithms": [alg],
|
||||||
|
}
|
||||||
|
options = {}
|
||||||
|
if jwt_cfg.get("audience"):
|
||||||
|
decode_kwargs["audience"] = jwt_cfg["audience"]
|
||||||
|
options["verify_aud"] = True
|
||||||
|
else:
|
||||||
|
options["verify_aud"] = False
|
||||||
|
|
||||||
|
if jwt_cfg.get("issuer"):
|
||||||
|
decode_kwargs["issuer"] = jwt_cfg["issuer"]
|
||||||
|
options["verify_iss"] = True
|
||||||
|
else:
|
||||||
|
options["verify_iss"] = False
|
||||||
try:
|
try:
|
||||||
decoded = jwt.decode(
|
decoded = jwt.decode(
|
||||||
token,
|
token,
|
||||||
secret,
|
options=options,
|
||||||
algorithms=[jwt_cfg.get("algorithm", "HS256")],
|
**decode_kwargs,
|
||||||
audience=jwt_cfg.get("audience"),
|
|
||||||
issuer=jwt_cfg.get("issuer"),
|
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise Exception(f"Invalid JWT: {str(e)}")
|
raise Exception(f"Invalid JWT: {str(e)}")
|
||||||
|
|
||||||
for claim in required_claims:
|
raw_required_claims = jwt_cfg.get("required_claims", [])
|
||||||
if claim not in decoded:
|
if isinstance(raw_required_claims, str):
|
||||||
raise Exception(f"Missing JWT claim: {claim}")
|
required_claims = [raw_required_claims]
|
||||||
|
elif isinstance(raw_required_claims, (list, tuple, set)):
|
||||||
|
required_claims = list(raw_required_claims)
|
||||||
|
else:
|
||||||
|
required_claims = []
|
||||||
|
|
||||||
|
required_claims = [
|
||||||
|
c for c in required_claims
|
||||||
|
if isinstance(c, str) and c.strip()
|
||||||
|
]
|
||||||
|
|
||||||
|
# RESERVED_CLAIMS = {"exp", "sub", "aud", "iss", "nbf", "iat"}
|
||||||
|
# for claim in required_claims:
|
||||||
|
# if claim in RESERVED_CLAIMS:
|
||||||
|
# raise Exception(f"Reserved JWT claim cannot be required: {claim}")
|
||||||
|
|
||||||
|
# for claim in required_claims:
|
||||||
|
# if claim not in decoded:
|
||||||
|
# raise Exception(f"Missing JWT claim: {claim}")
|
||||||
|
|
||||||
|
return decoded
|
||||||
|
|
||||||
|
|
||||||
async def _validate_hmac_auth(security_cfg):
|
async def _validate_hmac_auth(security_cfg):
|
||||||
"""Validate HMAC signature from header."""
|
"""Validate HMAC signature from header."""
|
||||||
|
|
@ -645,87 +684,26 @@ async def webhook(agent_id: str):
|
||||||
|
|
||||||
if execution_mode == "Immediately":
|
if execution_mode == "Immediately":
|
||||||
status = response_cfg.get("status", 200)
|
status = response_cfg.get("status", 200)
|
||||||
headers_tpl = response_cfg.get("headers_template", {})
|
body_tpl = response_cfg.get("body_template", "")
|
||||||
body_tpl = response_cfg.get("body_template", {})
|
|
||||||
|
def parse_body(body: str):
|
||||||
|
if not body:
|
||||||
|
return None, "application/json"
|
||||||
|
|
||||||
|
try:
|
||||||
|
parsed = json.loads(body)
|
||||||
|
return parsed, "application/json"
|
||||||
|
except (json.JSONDecodeError, TypeError):
|
||||||
|
return body, "text/plain"
|
||||||
|
|
||||||
|
|
||||||
placeholder_pattern = re.compile(r"\{* *\{([a-zA-Z:0-9]+@[A-Za-z0-9_.-]+|sys\.[A-Za-z0-9_.]+|env\.[A-Za-z0-9_.]+)\} *\}*")
|
body, content_type = parse_body(body_tpl)
|
||||||
def extract_placeholder_value(placeholder: str, clean_request: dict):
|
|
||||||
"""
|
|
||||||
Extract values from clean_request using placeholders like:
|
|
||||||
{webhook@body.payload}
|
|
||||||
{webhook@query.event}
|
|
||||||
{webhook@headers.X-Trace-ID}
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Example placeholder: webhook@body.payload
|
|
||||||
if "@" in placeholder:
|
|
||||||
prefix, path = placeholder.split("@", 1)
|
|
||||||
|
|
||||||
return get_from_request_path(clean_request, path)
|
|
||||||
|
|
||||||
# sys.xxx / env.xxx handled by canvas, do not resolve here
|
|
||||||
return None
|
|
||||||
|
|
||||||
def get_from_request_path(clean_request: dict, path: str):
|
|
||||||
"""
|
|
||||||
Resolve path like:
|
|
||||||
body.payload
|
|
||||||
query.event
|
|
||||||
headers.X-Token
|
|
||||||
"""
|
|
||||||
|
|
||||||
parts = path.split(".")
|
|
||||||
if len(parts) == 0:
|
|
||||||
return None
|
|
||||||
|
|
||||||
root = parts[0] # body / query / headers
|
|
||||||
|
|
||||||
if root not in clean_request:
|
|
||||||
return None
|
|
||||||
|
|
||||||
value = clean_request[root]
|
|
||||||
|
|
||||||
for p in parts[1:]:
|
|
||||||
if isinstance(value, dict) and p in value:
|
|
||||||
value = value[p]
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
return value
|
|
||||||
|
|
||||||
def render_template(tpl, clean_request: dict):
|
|
||||||
if isinstance(tpl, dict):
|
|
||||||
return {k: render_template(v, clean_request) for k, v in tpl.items()}
|
|
||||||
|
|
||||||
if isinstance(tpl, list):
|
|
||||||
return [render_template(item, clean_request) for item in tpl]
|
|
||||||
|
|
||||||
if not isinstance(tpl, str):
|
|
||||||
return tpl
|
|
||||||
|
|
||||||
matches = placeholder_pattern.findall(tpl)
|
|
||||||
rendered = tpl
|
|
||||||
|
|
||||||
for m in matches:
|
|
||||||
val = extract_placeholder_value(m, clean_request)
|
|
||||||
rendered = rendered.replace(f"{{{m}}}", str(val))
|
|
||||||
|
|
||||||
return rendered
|
|
||||||
|
|
||||||
# Render "{xxx@query.xxx}" syntax
|
|
||||||
headers = render_template(headers_tpl, clean_request)
|
|
||||||
body = render_template(body_tpl, clean_request)
|
|
||||||
|
|
||||||
resp = Response(
|
resp = Response(
|
||||||
json.dumps(body, ensure_ascii=False),
|
json.dumps(body, ensure_ascii=False) if content_type == "application/json" else body,
|
||||||
status=status,
|
status=status,
|
||||||
content_type="application/json"
|
content_type=content_type,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Add custom headers
|
|
||||||
for k, v in headers.items():
|
|
||||||
resp.headers[k] = v
|
|
||||||
async def background_run():
|
async def background_run():
|
||||||
try:
|
try:
|
||||||
async for _ in canvas.run(
|
async for _ in canvas.run(
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue