183 lines
6.6 KiB
Go
183 lines
6.6 KiB
Go
package fileauth
|
||
|
||
import (
|
||
"crypto/hmac"
|
||
"crypto/sha1"
|
||
"encoding/hex"
|
||
"encoding/json"
|
||
"errors"
|
||
"fmt"
|
||
"strconv"
|
||
"strings"
|
||
"time"
|
||
)
|
||
|
||
// ReturnBodyTemplate 监管 Ver2.0 §2.3.3 文档示例中的 returnBody 固定模板。
|
||
const ReturnBodyTemplate = `{"name":$(fname),"size":$(fsize),"w":$(imageInfo.width),"h":$(imageInfo.height),"hash":$(etag)}`
|
||
|
||
// DocExamplePolicyJSON 文档教学样例 putPolicy(固定向量单测用)。
|
||
const DocExamplePolicyJSON = `{"scope":"my-bucket:sunflower.jpg","deadline":1451491200,"returnBody":"{\"name\":$(fname),\"size\":$(fsize),\"w\":$(imageInfo.width),\"h\":$(imageInfo.height),\"hash\":$(etag)}"}`
|
||
|
||
// DocExampleEncodedPutPolicy 文档 §2.3.3 步骤 2 给出的 encodedPutPolicy。
|
||
const DocExampleEncodedPutPolicy = "eyJzY29wZSI6Im15LWJ1Y2tldDpzdW5mbG93ZXIuanBnIiwiZGVhZGxpbmUiOjE0NTE0OTEyMDAsInJldHVybkJvZHkiOiJ7XCJuYW1lXCI6JChmbmFtZSksXCJzaXplXCI6JChmc2l6ZSksXCJ3XCI6JChpbWFnZUluZm8ud2lkdGgpLFwiaFwiOiQoaW1hZ2VJbmZvLmhlaWdodCksXCJoYXNoXCI6JChldGFnKX0ifQ=="
|
||
|
||
// DocExampleSignHex 文档样例 HMAC 原始字节十六进制(SecretKey=MY_SECRET_KEY)。
|
||
const DocExampleSignHex = "c10e287f2b1e7f547b20a9ebce2aada26ab20ef2"
|
||
|
||
// DocExampleEncodedSign 文档样例 encodedSign。
|
||
const DocExampleEncodedSign = "wQ4ofysef1R7IKnrziqtomqyDvI="
|
||
|
||
// DocExampleUploadToken 文档样例完整 uploadToken(AccessKey=MY_ACCESS_KEY)。
|
||
const DocExampleUploadToken = "MY_ACCESS_KEY:wQ4ofysef1R7IKnrziqtomqyDvI=:" + DocExampleEncodedPutPolicy
|
||
|
||
// MarshalPutPolicyDoc 文档 profile:scope 可含文件名 + returnBody。
|
||
func MarshalPutPolicyDoc(scope string, deadlineUnix int64) ([]byte, error) {
|
||
scope = strings.TrimSpace(scope)
|
||
if scope == "" {
|
||
return nil, errors.New("empty scope")
|
||
}
|
||
if deadlineUnix <= 0 {
|
||
return nil, errors.New("invalid deadline")
|
||
}
|
||
scopeJSON, err := json.Marshal(scope)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
rbJSON, err := json.Marshal(ReturnBodyTemplate)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
return []byte(`{"scope":` + string(scopeJSON) + `,"deadline":` + strconv.FormatInt(deadlineUnix, 10) + `,"returnBody":` + string(rbJSON) + `}`), nil
|
||
}
|
||
|
||
// MarshalPutPolicyUpload 生产 profile:scope=仅 bucket,无 returnBody。
|
||
func MarshalPutPolicyUpload(bucket string, deadlineUnix int64) ([]byte, error) {
|
||
bucket = strings.TrimSpace(bucket)
|
||
if bucket == "" {
|
||
return nil, errors.New("empty bucket")
|
||
}
|
||
if deadlineUnix <= 0 {
|
||
return nil, errors.New("invalid deadline")
|
||
}
|
||
scopeJSON, err := json.Marshal(bucket)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
return []byte(`{"scope":` + string(scopeJSON) + `,"deadline":` + strconv.FormatInt(deadlineUnix, 10) + `}`), nil
|
||
}
|
||
|
||
// MarshalPutPolicyVer233 兼容旧名;等同 doc profile。
|
||
func MarshalPutPolicyVer233(scope string, deadlineUnix int64) ([]byte, error) {
|
||
return MarshalPutPolicyDoc(scope, deadlineUnix)
|
||
}
|
||
|
||
// signPolicyVer233 §2.3.3:HMAC-SHA1(secret, UTF-8(encodedPutPolicy)) → urlSafeB64。
|
||
func signPolicyVer233(secret, encodedPutPolicy string) (string, error) {
|
||
mac := hmac.New(sha1.New, []byte(secret))
|
||
if _, err := mac.Write([]byte(encodedPutPolicy)); err != nil {
|
||
return "", fmt.Errorf("hmac: %w", err)
|
||
}
|
||
return encodeURLSafe(mac.Sum(nil)), nil
|
||
}
|
||
|
||
// urlSafeBase64Ver233 供单测;使用当前 B64 模式(doc 单测应 SetB64Mode(padded))。
|
||
func urlSafeBase64Ver233(data []byte) string {
|
||
return encodeURLSafe(data)
|
||
}
|
||
|
||
// BuildUploadTokenFromPolicy 按 §2.3.3 五步由 putPolicy JSON 生成 token。
|
||
func BuildUploadTokenFromPolicy(accessKey, secret string, putPolicyJSON []byte, algorithm string) (UploadTokenParts, error) {
|
||
accessKey = strings.TrimSpace(accessKey)
|
||
secret = strings.TrimSpace(secret)
|
||
if accessKey == "" || secret == "" {
|
||
return UploadTokenParts{}, errors.New("empty key or secret")
|
||
}
|
||
if len(putPolicyJSON) == 0 {
|
||
return UploadTokenParts{}, errors.New("empty putPolicy")
|
||
}
|
||
if algorithm == "" {
|
||
algorithm = "ver233"
|
||
}
|
||
|
||
encodedPutPolicy := encodeURLSafe(putPolicyJSON)
|
||
sign, err := signPolicyVer233(secret, encodedPutPolicy)
|
||
if err != nil {
|
||
return UploadTokenParts{}, err
|
||
}
|
||
token := accessKey + ":" + sign + ":" + encodedPutPolicy
|
||
|
||
var policy struct {
|
||
Deadline int64 `json:"deadline"`
|
||
}
|
||
_ = json.Unmarshal(putPolicyJSON, &policy)
|
||
|
||
return UploadTokenParts{
|
||
AccessKey: accessKey,
|
||
PolicyJSON: string(putPolicyJSON),
|
||
EncodedPutPolicy: encodedPutPolicy,
|
||
SignInput: encodedPutPolicy,
|
||
EncodedSign: sign,
|
||
UploadToken: token,
|
||
DeadlineUnix: policy.Deadline,
|
||
Algorithm: algorithm,
|
||
}, nil
|
||
}
|
||
|
||
// BuildUploadTokenPartsForUpload 生产路径:scope=仅 bucket,§2.3.3 验签。
|
||
func BuildUploadTokenPartsForUpload(accessKey, secret, bucket string, deadlineUnix int64) (UploadTokenParts, error) {
|
||
accessKey = strings.TrimSpace(accessKey)
|
||
secret = strings.TrimSpace(secret)
|
||
bucket = strings.TrimSpace(bucket)
|
||
if accessKey == "" || secret == "" {
|
||
return UploadTokenParts{}, errors.New("empty key or secret")
|
||
}
|
||
if bucket == "" {
|
||
return UploadTokenParts{}, errors.New("empty bucket")
|
||
}
|
||
if deadlineUnix <= 0 {
|
||
return UploadTokenParts{}, errors.New("invalid deadline")
|
||
}
|
||
jsonPolicy, err := MarshalPutPolicyUpload(bucket, deadlineUnix)
|
||
if err != nil {
|
||
return UploadTokenParts{}, err
|
||
}
|
||
parts, err := BuildUploadTokenFromPolicy(accessKey, secret, jsonPolicy, "ver233-upload")
|
||
if err != nil {
|
||
return UploadTokenParts{}, err
|
||
}
|
||
now := time.Now().Unix()
|
||
expires := deadlineUnix - now
|
||
if expires < 0 {
|
||
expires = 0
|
||
}
|
||
parts.ExpiresSec = expires
|
||
return parts, nil
|
||
}
|
||
|
||
// BuildUploadTokenPartsAtFile filename 不参与 scope(HTTP 为 PDF 二进制体,非 multipart)。
|
||
func BuildUploadTokenPartsAtFile(accessKey, secret, bucket, filename string, deadlineUnix int64) (UploadTokenParts, error) {
|
||
_ = filename
|
||
return BuildUploadTokenPartsForUpload(accessKey, secret, bucket, deadlineUnix)
|
||
}
|
||
|
||
// CreateUploadTokenAtFile 生产上传凭证。
|
||
func CreateUploadTokenAtFile(accessKey, secret, bucket, filename string, deadlineUnix int64) (string, error) {
|
||
parts, err := BuildUploadTokenPartsAtFile(accessKey, secret, bucket, filename, deadlineUnix)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
return parts.UploadToken, nil
|
||
}
|
||
|
||
// BuildUploadTokenPartsDoc 文档黄金样例(固定 scope/deadline)。
|
||
func BuildUploadTokenPartsDoc(accessKey, secret string) (UploadTokenParts, error) {
|
||
return BuildUploadTokenFromPolicy(accessKey, secret, []byte(DocExamplePolicyJSON), "ver233-doc")
|
||
}
|
||
|
||
// HMACSignHex 返回 HMAC-SHA1 原始字节的十六进制(调试用)。
|
||
func HMACSignHex(secret, encodedPutPolicy string) string {
|
||
mac := hmac.New(sha1.New, []byte(secret))
|
||
_, _ = mac.Write([]byte(encodedPutPolicy))
|
||
return hex.EncodeToString(mac.Sum(nil))
|
||
}
|