169 lines
5.4 KiB
Go
169 lines
5.4 KiB
Go
|
|
package fileauth
|
|||
|
|
|
|||
|
|
import (
|
|||
|
|
"encoding/base64"
|
|||
|
|
"encoding/json"
|
|||
|
|
"errors"
|
|||
|
|
"strings"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
// UploadTokenParts 上传凭证分步结果(监管 2.3:AccessKey:encodedSign:encodedPutPolicy)。
|
|||
|
|
type UploadTokenParts struct {
|
|||
|
|
AccessKey string
|
|||
|
|
PolicyJSON string
|
|||
|
|
EncodedPutPolicy string
|
|||
|
|
SignInput string // §2.3.3:encodedPutPolicy 字符串
|
|||
|
|
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 profile(scope 仅 bucket,无 returnBody)",
|
|||
|
|
UploadTokenSample: DocExampleRegulatory,
|
|||
|
|
AccessKeyPart: ak,
|
|||
|
|
EncodedSignPart: sign,
|
|||
|
|
EncodedPolicyPart: pol,
|
|||
|
|
PolicyJSONDecoded: policyDecoded,
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// BuildUploadTokenParts 分步生成上传凭证(§2.3.3 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 + ':' + encodedPutPolicy(按前两处冒号拆分)",
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
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 段 encodedPutPolicy(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 解码 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
|
|||
|
|
}
|