fix: correct V4 signing - URI encode query params, fix canonical headers format
This commit is contained in:
parent
03e1639ffc
commit
073bd711c8
@ -33,20 +33,19 @@ def _get_signature_key(secret_key, date_stamp, region, service):
|
||||
return k_signing
|
||||
|
||||
|
||||
def _uri_encode(s, encode_slash=True):
|
||||
"""URI encode string per V4 spec."""
|
||||
import urllib.parse
|
||||
if encode_slash:
|
||||
return urllib.parse.quote(s, safe='')
|
||||
else:
|
||||
return urllib.parse.quote(s, safe='/')
|
||||
|
||||
|
||||
async def call_volcengine_api(vendor, operation, params, api_mapping):
|
||||
"""
|
||||
Call Volcengine Ark API with V4 signing.
|
||||
|
||||
Args:
|
||||
vendor: vendor identifier (e.g., "volcengine")
|
||||
operation: internal operation name (e.g., "create_session")
|
||||
params: API parameters dict
|
||||
api_mapping: dict mapping operation to API action name
|
||||
|
||||
Returns:
|
||||
dict: API response or error
|
||||
"""
|
||||
# Get action name from mapping
|
||||
action = api_mapping.get(operation)
|
||||
if not action:
|
||||
return {"error": f"未配置操作: {operation}"}
|
||||
@ -61,43 +60,51 @@ async def call_volcengine_api(vendor, operation, params, api_mapping):
|
||||
return {"error": f"供应商配置不存在: {vendor}"}
|
||||
rec = recs[0]
|
||||
ak = getattr(rec, "ak", "")
|
||||
sk_encrypted = getattr(rec, "sk", "")
|
||||
sk = getattr(rec, "sk", "")
|
||||
|
||||
if not ak or not sk_encrypted:
|
||||
if not ak or not sk:
|
||||
return {"error": "AK/SK未配置"}
|
||||
|
||||
# AK/SK are stored in plaintext
|
||||
sk = sk_encrypted
|
||||
|
||||
# Build signed request
|
||||
# Timestamps
|
||||
now = datetime.datetime.utcnow()
|
||||
date_stamp = now.strftime("%Y%m%d")
|
||||
amz_date = now.strftime("%Y%m%dT%H%M%SZ")
|
||||
|
||||
# Query string with Action and Version
|
||||
query_params = f"Action={action}&Version={VERSION}"
|
||||
# Query string (sorted and URI-encoded)
|
||||
query_params = {
|
||||
"Action": action,
|
||||
"Version": VERSION,
|
||||
}
|
||||
canonical_querystring = "&".join(
|
||||
f"{_uri_encode(k)}={_uri_encode(v)}"
|
||||
for k, v in sorted(query_params.items())
|
||||
)
|
||||
|
||||
# Headers
|
||||
content_type = "application/json"
|
||||
payload = json.dumps(params, ensure_ascii=False)
|
||||
# Payload
|
||||
payload = json.dumps(params, ensure_ascii=False, separators=(',', ':'))
|
||||
payload_hash = hashlib.sha256(payload.encode("utf-8")).hexdigest()
|
||||
|
||||
# Headers to sign (lowercase keys, trimmed values)
|
||||
headers_to_sign = {
|
||||
"content-type": "application/json",
|
||||
"host": HOST,
|
||||
"x-date": amz_date,
|
||||
"x-content-sha256": payload_hash,
|
||||
"content-type": content_type,
|
||||
"x-date": amz_date,
|
||||
}
|
||||
|
||||
# Canonical headers (sorted, each ends with \n)
|
||||
canonical_headers = ""
|
||||
for key in sorted(headers_to_sign.keys()):
|
||||
canonical_headers += f"{key}:{headers_to_sign[key]}\n"
|
||||
|
||||
# Signed headers list
|
||||
signed_headers = ";".join(sorted(headers_to_sign.keys()))
|
||||
canonical_headers = "".join(
|
||||
f"{k}:{v}\n" for k, v in sorted(headers_to_sign.items())
|
||||
)
|
||||
|
||||
# Canonical request
|
||||
canonical_request = "\n".join([
|
||||
"POST",
|
||||
"/",
|
||||
query_params,
|
||||
canonical_querystring,
|
||||
canonical_headers,
|
||||
signed_headers,
|
||||
payload_hash,
|
||||
@ -127,12 +134,12 @@ async def call_volcengine_api(vendor, operation, params, api_mapping):
|
||||
)
|
||||
|
||||
# Build request
|
||||
url = f"{BASE_URL}/?{query_params}"
|
||||
url = f"{BASE_URL}/?{canonical_querystring}"
|
||||
req_headers = {
|
||||
"Host": HOST,
|
||||
"X-Date": amz_date,
|
||||
"X-Content-Sha256": payload_hash,
|
||||
"Content-Type": content_type,
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": authorization,
|
||||
}
|
||||
|
||||
@ -141,7 +148,6 @@ async def call_volcengine_api(vendor, operation, params, api_mapping):
|
||||
hc = StreamHttpClient()
|
||||
raw = await hc.request("POST", url, headers=req_headers, data=payload.encode("utf-8"))
|
||||
|
||||
# Parse response
|
||||
if isinstance(raw, bytes):
|
||||
raw = raw.decode("utf-8")
|
||||
result = json.loads(raw) if raw else {}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user