129 lines
3.5 KiB
Go
129 lines
3.5 KiB
Go
package fileauth
|
||
|
||
import (
|
||
"encoding/base64"
|
||
"encoding/json"
|
||
"errors"
|
||
"strings"
|
||
)
|
||
|
||
// UploadTokenParts 上传凭证分步结果(FileAuth.java:AccessKey:encodedSign:policyB64)。
|
||
type UploadTokenParts struct {
|
||
AccessKey string
|
||
PolicyJSON string
|
||
EncodedPutPolicy string // 第三段:policy JSON 的 URL-safe Base64
|
||
SignInput string // HMAC 输入:policy JSON 明文
|
||
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"`
|
||
}
|
||
|
||
// BuildUploadTokenParts 分步生成上传凭证(生产 upload profile,scope=仅 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:policyB64;expectedAccessKey 非空时校验首段。
|
||
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 + ':' + policyB64(按前两处冒号拆分)",
|
||
},
|
||
{
|
||
ID: "access_key_non_empty",
|
||
OK: ok && ak != "",
|
||
Detail: "第 1 段 AccessKey 非空",
|
||
},
|
||
{
|
||
ID: "encoded_sign_non_empty",
|
||
OK: ok && sign != "",
|
||
Detail: "第 2 段 encodedSign(URL-safe Base64 HMAC)非空",
|
||
},
|
||
{
|
||
ID: "encoded_policy_non_empty",
|
||
OK: ok && pol != "",
|
||
Detail: "第 3 段 policyB64(URL-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 解码 policyB64 为 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 {
|
||
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
|
||
}
|