Files
xk-hy-transit-go/internal/fileauth/parts.go

169 lines
5.4 KiB
Go
Raw Normal View History

2026-05-28 16:39:51 +08:00
package fileauth
import (
"encoding/base64"
"encoding/json"
"errors"
"strings"
)
// UploadTokenParts 上传凭证分步结果(监管 2.3AccessKey:encodedSign:encodedPutPolicy
type UploadTokenParts struct {
AccessKey string
PolicyJSON string
EncodedPutPolicy string
SignInput string // §2.3.3encodedPutPolicy 字符串
EncodedSign string
UploadToken string
DeadlineUnix int64
ExpiresSec int64
Algorithm string
}
// FormatCheck 单条 uploadToken 格式校验结果。
type FormatCheck struct {
ID string `json:"id"`
OK bool `json:"ok"`
Detail string `json:"detail"`
}
// DocExample 监管 2.3 教学示例(含 returnBody仅格式对照非 transit 生产 policy
type DocExample struct {
Note string `json:"note"`
UploadTokenSample string `json:"uploadTokenSample"`
AccessKeyPart string `json:"accessKeyPart"`
EncodedSignPart string `json:"encodedSignPart"`
EncodedPolicyPart string `json:"encodedPolicyPart"`
PolicyJSONDecoded string `json:"policyJsonDecoded"`
}
// DocExampleRegulatory 内置监管文档第 5 步示例 token含 returnBody 的完整 putPolicy
const DocExampleRegulatory = "MY_ACCESS_KEY:wQ4ofysef1R7IKnrziqtomqyDvI=:eyJzY29wZSI6Im15LWJ1Y2tldDpzdW5mbG93ZXIuanBnIiwiZGVhZGxpbmUiOjE0NTE0OTEyMDAsInJldHVybkJvZHkiOiJ7XCJuYW1lXCI6JChmbmFtZSksXCJzaXplXCI6JChmc2l6ZSksXCJ3XCI6JChpbWFnZUluZm8ud2lkdGgpLFwiaFwiOiQoaW1hZ2VJbmZvLmhlaWdodCksXCJoYXNoXCI6JChldGFnKX0ifQ=="
const docExamplePolicyJSON = `{"scope":"my-bucket:sunflower.jpg","deadline":1451491200,"returnBody":"{\"name\":$(fname),\"size\":$(fsize),\"w\":$(imageInfo.width),\"h\":$(imageInfo.height),\"hash\":$(etag)}"}`
// RegulatoryDocExample 返回文档教学示例的拆分与解码(只读对照)。
func RegulatoryDocExample() DocExample {
ak, sign, pol, ok := SplitUploadToken(DocExampleRegulatory)
policyDecoded := ""
if ok {
if s, err := DecodePutPolicyB64(pol); err == nil {
policyDecoded = s
}
}
return DocExample{
Note: "监管 §2.3.3 教学示例doc profile生产为 upload profilescope 仅 bucket无 returnBody",
UploadTokenSample: DocExampleRegulatory,
AccessKeyPart: ak,
EncodedSignPart: sign,
EncodedPolicyPart: pol,
PolicyJSONDecoded: policyDecoded,
}
}
// BuildUploadTokenParts 分步生成上传凭证§2.3.3 upload profilescope=仅 bucket
func BuildUploadTokenParts(accessKey, secret, bucket string, deadlineUnix int64) (UploadTokenParts, error) {
return BuildUploadTokenPartsForUpload(accessKey, secret, bucket, deadlineUnix)
}
// SplitUploadToken 按前两处英文冒号拆分为 accessKey、encodedSign、encodedPutPolicy。
func SplitUploadToken(token string) (accessKey, encodedSign, encodedPutPolicy string, ok bool) {
token = strings.TrimSpace(token)
i := strings.Index(token, ":")
if i <= 0 {
return "", "", "", false
}
rest := token[i+1:]
j := strings.Index(rest, ":")
if j <= 0 {
return "", "", "", false
}
ak := token[:i]
sign := rest[:j]
pol := rest[j+1:]
if ak == "" || sign == "" || pol == "" {
return "", "", "", false
}
return ak, sign, pol, true
}
// ValidateUploadTokenFormat 校验 token 是否符合 AccessKey:sign:policyB64expectedAccessKey 非空时校验首段。
func ValidateUploadTokenFormat(token, expectedAccessKey string) (allOK bool, checks []FormatCheck) {
ak, sign, pol, ok := SplitUploadToken(token)
checks = []FormatCheck{
{
ID: "three_parts",
OK: ok,
Detail: "uploadToken = AccessKey + ':' + encodedSign + ':' + encodedPutPolicy按前两处冒号拆分",
},
{
ID: "access_key_non_empty",
OK: ok && ak != "",
Detail: "第 1 段 AccessKey 非空",
},
{
ID: "encoded_sign_non_empty",
OK: ok && sign != "",
Detail: "第 2 段 encodedSignURL-safe Base64 HMAC非空",
},
{
ID: "encoded_policy_non_empty",
OK: ok && pol != "",
Detail: "第 3 段 encodedPutPolicyURL-safe Base64 policy JSON非空",
},
}
if expectedAccessKey != "" {
exp := strings.TrimSpace(expectedAccessKey)
match := ok && ak == exp
checks = append(checks, FormatCheck{
ID: "access_key_match",
OK: match,
Detail: "第 1 段与当前 AccessKey 一致",
})
}
_, decErr := DecodePutPolicyB64(pol)
checks = append(checks, FormatCheck{
ID: "policy_b64_decodable",
OK: ok && decErr == nil,
Detail: "第 3 段可 URL-safe Base64 解码为 JSON",
})
allOK = true
for _, c := range checks {
if !c.OK {
allOK = false
break
}
}
return allOK, checks
}
// DecodePutPolicyB64 解码 encodedPutPolicy 为 JSON 字符串(美化缩进)。
func DecodePutPolicyB64(encoded string) (string, error) {
encoded = strings.TrimSpace(encoded)
if encoded == "" {
return "", errors.New("empty encoded policy")
}
raw, err := base64.RawURLEncoding.DecodeString(encoded)
if err != nil {
// 兼容带 padding 的标准 url-safe
raw, err = base64.URLEncoding.DecodeString(encoded)
if err != nil {
return "", err
}
}
var pretty interface{}
if err := json.Unmarshal(raw, &pretty); err != nil {
return string(raw), nil
}
out, err := json.MarshalIndent(pretty, "", " ")
if err != nil {
return string(raw), nil
}
return string(out), nil
}
// DecodeDocExamplePolicy 返回内置文档示例 policy 明文(便于测试)。
func DecodeDocExamplePolicy() string {
return docExamplePolicyJSON
}