1
This commit is contained in:
@@ -20,12 +20,13 @@ const OSS_V4_REQUEST: &str = "aliyun_v4_request";
|
||||
const OSS_V4_SERVICE: &str = "oss";
|
||||
const OSS_UNSIGNED_PAYLOAD: &str = "UNSIGNED-PAYLOAD";
|
||||
|
||||
pub const LEGACY_PUBLIC_PREFIXES: [&str; 8] = [
|
||||
pub const LEGACY_PUBLIC_PREFIXES: [&str; 9] = [
|
||||
"generated-character-drafts",
|
||||
"generated-characters",
|
||||
"generated-animations",
|
||||
"generated-big-fish-assets",
|
||||
"generated-square-hole-assets",
|
||||
"generated-puzzle-assets",
|
||||
"generated-custom-world-scenes",
|
||||
"generated-custom-world-covers",
|
||||
"generated-qwen-sprites",
|
||||
@@ -419,8 +420,11 @@ impl OssClient {
|
||||
let policy = serde_json::to_string(&policy_json)
|
||||
.map_err(|error| OssError::SerializePolicy(format!("序列化 policy 失败:{error}")))?;
|
||||
let encoded_policy = BASE64_STANDARD.encode(policy.as_bytes());
|
||||
let signature =
|
||||
sign_v4_content(&self.config.access_key_secret, &signature_scope, &encoded_policy)?;
|
||||
let signature = sign_v4_content(
|
||||
&self.config.access_key_secret,
|
||||
&signature_scope,
|
||||
&encoded_policy,
|
||||
)?;
|
||||
|
||||
Ok(OssPostObjectResponse {
|
||||
signature_version: "v4",
|
||||
@@ -492,11 +496,8 @@ impl OssClient {
|
||||
let canonical_uri = build_v4_canonical_uri(&self.config.bucket, Some(&object_key));
|
||||
let object_url_path = format!("/{}", encode_url_path(&object_key));
|
||||
let additional_headers = "host";
|
||||
let canonical_headers = format!(
|
||||
"host:{}.{}\n",
|
||||
self.config.bucket(),
|
||||
self.config.endpoint()
|
||||
);
|
||||
let canonical_headers =
|
||||
format!("host:{}.{}\n", self.config.bucket(), self.config.endpoint());
|
||||
let canonical_query = build_canonical_query_string(&query);
|
||||
let canonical_request = build_v4_canonical_request(
|
||||
Method::GET.as_str(),
|
||||
@@ -506,10 +507,16 @@ impl OssClient {
|
||||
additional_headers,
|
||||
OSS_UNSIGNED_PAYLOAD,
|
||||
);
|
||||
let string_to_sign =
|
||||
build_v4_string_to_sign(query["x-oss-date"].as_str(), &signature_scope, &canonical_request);
|
||||
let signature =
|
||||
sign_v4_content(&self.config.access_key_secret, &signature_scope, &string_to_sign)?;
|
||||
let string_to_sign = build_v4_string_to_sign(
|
||||
query["x-oss-date"].as_str(),
|
||||
&signature_scope,
|
||||
&canonical_request,
|
||||
);
|
||||
let signature = sign_v4_content(
|
||||
&self.config.access_key_secret,
|
||||
&signature_scope,
|
||||
&string_to_sign,
|
||||
)?;
|
||||
query.insert("x-oss-signature".to_string(), signature);
|
||||
let signed_url = format!(
|
||||
"{}{}?{}",
|
||||
@@ -1036,8 +1043,13 @@ fn signed_request_builder(
|
||||
additional_headers,
|
||||
&body_sha256,
|
||||
);
|
||||
let string_to_sign = build_v4_string_to_sign(&signed_at_text, &signature_scope, &canonical_request);
|
||||
let signature = sign_v4_content(config.access_key_secret(), &signature_scope, &string_to_sign)?;
|
||||
let string_to_sign =
|
||||
build_v4_string_to_sign(&signed_at_text, &signature_scope, &canonical_request);
|
||||
let signature = sign_v4_content(
|
||||
config.access_key_secret(),
|
||||
&signature_scope,
|
||||
&string_to_sign,
|
||||
)?;
|
||||
let mut builder = client
|
||||
.request(method, target_url)
|
||||
.header("x-oss-content-sha256", body_sha256)
|
||||
@@ -1065,33 +1077,29 @@ fn signed_request_builder(
|
||||
}
|
||||
|
||||
fn build_v4_signature_scope(endpoint: &str, signed_at: OffsetDateTime) -> Result<String, OssError> {
|
||||
let date = signed_at
|
||||
.date()
|
||||
.to_string()
|
||||
.replace('-', "");
|
||||
let date = format_v4_signature_scope_date(signed_at);
|
||||
let region = extract_oss_region(endpoint)?;
|
||||
|
||||
Ok(format!("{date}/{region}/{OSS_V4_SERVICE}/{OSS_V4_REQUEST}"))
|
||||
}
|
||||
|
||||
fn build_v4_signature_date(signed_at: OffsetDateTime) -> Result<String, OssError> {
|
||||
let date = signed_at
|
||||
.date()
|
||||
.to_string()
|
||||
.replace('-', "");
|
||||
let time = signed_at
|
||||
.time()
|
||||
.to_string()
|
||||
.split('.')
|
||||
.next()
|
||||
.unwrap_or("00:00:00")
|
||||
.replace(':', "");
|
||||
Ok(format!(
|
||||
"{}T{:02}{:02}{:02}Z",
|
||||
format_v4_signature_scope_date(signed_at),
|
||||
signed_at.hour(),
|
||||
signed_at.minute(),
|
||||
signed_at.second()
|
||||
))
|
||||
}
|
||||
|
||||
if time.len() != 6 {
|
||||
return Err(OssError::Sign("OSS V4 签名时间格式化失败".to_string()));
|
||||
}
|
||||
|
||||
Ok(format!("{date}T{time}Z"))
|
||||
fn format_v4_signature_scope_date(signed_at: OffsetDateTime) -> String {
|
||||
format!(
|
||||
"{:04}{:02}{:02}",
|
||||
signed_at.year(),
|
||||
signed_at.month() as u8,
|
||||
signed_at.day()
|
||||
)
|
||||
}
|
||||
|
||||
fn build_v4_canonical_uri(bucket: &str, object_key: Option<&str>) -> String {
|
||||
@@ -1116,9 +1124,7 @@ fn extract_oss_region(endpoint: &str) -> Result<String, OssError> {
|
||||
.map(str::to_string)
|
||||
.filter(|region| !region.is_empty())
|
||||
.ok_or_else(|| {
|
||||
OssError::InvalidConfig(format!(
|
||||
"OSS endpoint 无法解析 region,当前值:{endpoint}"
|
||||
))
|
||||
OssError::InvalidConfig(format!("OSS endpoint 无法解析 region,当前值:{endpoint}"))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1131,7 +1137,10 @@ fn sign_v4_content(
|
||||
Ok(hex_sha256_hmac(&signing_key, content.as_bytes()))
|
||||
}
|
||||
|
||||
fn build_v4_signing_key(access_key_secret: &str, signature_scope: &str) -> Result<Vec<u8>, OssError> {
|
||||
fn build_v4_signing_key(
|
||||
access_key_secret: &str,
|
||||
signature_scope: &str,
|
||||
) -> Result<Vec<u8>, OssError> {
|
||||
let mut parts = signature_scope.split('/');
|
||||
let date = parts
|
||||
.next()
|
||||
@@ -1160,8 +1169,7 @@ fn hmac_sha256_raw(key: &[u8], content: &str) -> Result<Vec<u8>, OssError> {
|
||||
}
|
||||
|
||||
fn hex_sha256_hmac(key: &[u8], content: &[u8]) -> String {
|
||||
let mut signer = HmacSha256::new_from_slice(key)
|
||||
.expect("HMAC-SHA256 accepts keys of any size");
|
||||
let mut signer = HmacSha256::new_from_slice(key).expect("HMAC-SHA256 accepts keys of any size");
|
||||
signer.update(content);
|
||||
hex_lower(&signer.finalize().into_bytes())
|
||||
}
|
||||
@@ -1213,7 +1221,13 @@ fn build_v4_canonical_headers(headers: &BTreeMap<String, String>) -> String {
|
||||
fn build_canonical_query_string(params: &BTreeMap<String, String>) -> String {
|
||||
params
|
||||
.iter()
|
||||
.map(|(key, value)| format!("{}={}", encode_url_query_value(key), encode_url_query_value(value)))
|
||||
.map(|(key, value)| {
|
||||
format!(
|
||||
"{}={}",
|
||||
encode_url_query_value(key),
|
||||
encode_url_query_value(value)
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join("&")
|
||||
}
|
||||
@@ -1286,9 +1300,30 @@ mod tests {
|
||||
LegacyAssetPrefix::parse("/generated-characters/*"),
|
||||
Some(LegacyAssetPrefix::Characters)
|
||||
);
|
||||
assert_eq!(
|
||||
LegacyAssetPrefix::parse("/generated-puzzle-assets/*"),
|
||||
Some(LegacyAssetPrefix::PuzzleAssets)
|
||||
);
|
||||
assert!(LEGACY_PUBLIC_PREFIXES.contains(&"generated-puzzle-assets"));
|
||||
assert_eq!(LegacyAssetPrefix::parse("unknown"), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_v4_signature_date_zero_pads_single_digit_time_parts() {
|
||||
let signed_at =
|
||||
OffsetDateTime::from_unix_timestamp(1_771_477_389).expect("timestamp should be valid");
|
||||
|
||||
assert_eq!(
|
||||
build_v4_signature_date(signed_at).expect("date should format"),
|
||||
"20260219T050309Z"
|
||||
);
|
||||
assert_eq!(
|
||||
build_v4_signature_scope("oss-cn-shanghai.aliyuncs.com", signed_at)
|
||||
.expect("scope should format"),
|
||||
"20260219/cn-shanghai/oss/aliyun_v4_request"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sign_post_object_returns_bucket_and_object_key_for_private_storage_truth() {
|
||||
let client = build_client();
|
||||
@@ -1327,18 +1362,19 @@ mod tests {
|
||||
response.form_fields.signature_version,
|
||||
OSS_V4_ALGORITHM.to_string()
|
||||
);
|
||||
assert!(response
|
||||
.form_fields
|
||||
.credential
|
||||
.starts_with("test-access-key-id/"));
|
||||
assert!(response
|
||||
.form_fields
|
||||
.credential
|
||||
.ends_with("/cn-shanghai/oss/aliyun_v4_request"));
|
||||
assert_eq!(
|
||||
response.form_fields.date.len(),
|
||||
"20260507T120000Z".len()
|
||||
assert!(
|
||||
response
|
||||
.form_fields
|
||||
.credential
|
||||
.starts_with("test-access-key-id/")
|
||||
);
|
||||
assert!(
|
||||
response
|
||||
.form_fields
|
||||
.credential
|
||||
.ends_with("/cn-shanghai/oss/aliyun_v4_request")
|
||||
);
|
||||
assert_eq!(response.form_fields.date.len(), "20260507T120000Z".len());
|
||||
assert_eq!(
|
||||
response.form_fields.metadata.get("x-oss-meta-asset-kind"),
|
||||
Some(&"character-visual".to_string())
|
||||
@@ -1441,9 +1477,11 @@ mod tests {
|
||||
.signed_url
|
||||
.contains("x-oss-signature-version=OSS4-HMAC-SHA256")
|
||||
);
|
||||
assert!(response
|
||||
.signed_url
|
||||
.contains("x-oss-credential=test-access-key-id%2F"));
|
||||
assert!(
|
||||
response
|
||||
.signed_url
|
||||
.contains("x-oss-credential=test-access-key-id%2F")
|
||||
);
|
||||
assert!(response.signed_url.contains("&x-oss-expires=300"));
|
||||
assert!(response.signed_url.contains("&x-oss-signature="));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user