You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
go-library/vendor/github.com/qiniu/go-sdk/v7/auth/credentials.go

235 lines
5.5 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

package auth
import (
"bytes"
"crypto/hmac"
"crypto/sha1"
"encoding/base64"
"fmt"
"io/ioutil"
"net/http"
"net/textproto"
"sort"
"strings"
api "github.com/qiniu/go-sdk/v7"
"github.com/qiniu/go-sdk/v7/conf"
)
const (
IAMKeyLen = 33
IAMKeyPrefix = "IAM-"
AuthorizationPrefixQiniu = "Qiniu "
AuthorizationPrefixQBox = "QBox "
)
// 七牛鉴权类用于生成Qbox, Qiniu, Upload签名
//
// AK/SK可以从 https://portal.qiniu.com/user/key 获取
type Credentials struct {
AccessKey string
SecretKey []byte
}
// 构建一个Credentials对象
func New(accessKey, secretKey string) *Credentials {
return &Credentials{accessKey, []byte(secretKey)}
}
// Sign 对数据进行签名,一般用于私有空间下载用途
func (ath *Credentials) Sign(data []byte) (token string) {
h := hmac.New(sha1.New, ath.SecretKey)
h.Write(data)
sign := base64.URLEncoding.EncodeToString(h.Sum(nil))
return fmt.Sprintf("%s:%s", ath.AccessKey, sign)
}
// SignToken 根据t的类型对请求进行签名并把token加入req中
func (ath *Credentials) AddToken(t TokenType, req *http.Request) error {
switch t {
case TokenQiniu:
token, sErr := ath.SignRequestV2(req)
if sErr != nil {
return sErr
}
req.Header.Add("Authorization", AuthorizationPrefixQiniu+token)
default:
token, err := ath.SignRequest(req)
if err != nil {
return err
}
req.Header.Add("Authorization", AuthorizationPrefixQBox+token)
}
return nil
}
// SignWithData 对数据进行签名,一般用于上传凭证的生成用途
func (ath *Credentials) SignWithData(b []byte) (token string) {
encodedData := base64.URLEncoding.EncodeToString(b)
sign := ath.Sign([]byte(encodedData))
return fmt.Sprintf("%s:%s", sign, encodedData)
}
// IsIAMKey 判断AccessKey是否为IAM的Key
func (ath *Credentials) IsIAMKey() bool {
return len(ath.AccessKey) == IAMKeyLen*4/3 &&
strings.HasPrefix(ath.AccessKey, IAMKeyPrefix)
}
func collectData(req *http.Request) (data []byte, err error) {
u := req.URL
s := u.Path
if u.RawQuery != "" {
s += "?"
s += u.RawQuery
}
s += "\n"
data = []byte(s)
if incBody(req) {
s2, rErr := api.BytesFromRequest(req)
if rErr != nil {
err = rErr
return
}
req.Body = ioutil.NopCloser(bytes.NewReader(s2))
data = append(data, s2...)
}
return
}
type (
xQiniuHeaderItem struct {
HeaderName string
HeaderValue string
}
xQiniuHeaders []xQiniuHeaderItem
)
func (headers xQiniuHeaders) Len() int {
return len(headers)
}
func (headers xQiniuHeaders) Less(i, j int) bool {
if headers[i].HeaderName < headers[j].HeaderName {
return true
} else if headers[i].HeaderName > headers[j].HeaderName {
return false
} else {
return headers[i].HeaderValue < headers[j].HeaderValue
}
}
func (headers xQiniuHeaders) Swap(i, j int) {
headers[i], headers[j] = headers[j], headers[i]
}
func collectDataV2(req *http.Request) (data []byte, err error) {
u := req.URL
//write method path?query
s := fmt.Sprintf("%s %s", req.Method, u.Path)
if u.RawQuery != "" {
s += "?"
s += u.RawQuery
}
//write host and post
s += "\nHost: " + req.Host + "\n"
//write content type
contentType := req.Header.Get("Content-Type")
if contentType == "" {
contentType = "application/x-www-form-urlencoded"
req.Header.Set("Content-Type", contentType)
}
s += fmt.Sprintf("Content-Type: %s\n", contentType)
xQiniuHeaders := make(xQiniuHeaders, 0, len(req.Header))
for headerName := range req.Header {
if len(headerName) > len("X-Qiniu-") && strings.HasPrefix(headerName, "X-Qiniu-") {
xQiniuHeaders = append(xQiniuHeaders, xQiniuHeaderItem{
HeaderName: textproto.CanonicalMIMEHeaderKey(headerName),
HeaderValue: req.Header.Get(headerName),
})
}
}
if len(xQiniuHeaders) > 0 {
sort.Sort(xQiniuHeaders)
for _, xQiniuHeader := range xQiniuHeaders {
s += fmt.Sprintf("%s: %s\n", xQiniuHeader.HeaderName, xQiniuHeader.HeaderValue)
}
}
s += "\n"
data = []byte(s)
//write body
if incBodyV2(req) {
s2, rErr := api.BytesFromRequest(req)
if rErr != nil {
err = rErr
return
}
req.Body = ioutil.NopCloser(bytes.NewReader(s2))
data = append(data, s2...)
}
return
}
// SignRequest 对数据进行签名,一般用于管理凭证的生成
func (ath *Credentials) SignRequest(req *http.Request) (token string, err error) {
data, err := collectData(req)
if err != nil {
return
}
token = ath.Sign(data)
return
}
// SignRequestV2 对数据进行签名,一般用于高级管理凭证的生成
func (ath *Credentials) SignRequestV2(req *http.Request) (token string, err error) {
data, err := collectDataV2(req)
if err != nil {
return
}
token = ath.Sign(data)
return
}
// 管理凭证生成时是否同时对request body进行签名
func incBody(req *http.Request) bool {
return req.Body != nil && req.Header.Get("Content-Type") == conf.CONTENT_TYPE_FORM
}
func incBodyV2(req *http.Request) bool {
contentType := req.Header.Get("Content-Type")
return req.Body != nil && (contentType == conf.CONTENT_TYPE_FORM || contentType == conf.CONTENT_TYPE_JSON)
}
// VerifyCallback 验证上传回调请求是否来自七牛
func (ath *Credentials) VerifyCallback(req *http.Request) (bool, error) {
auth := req.Header.Get("Authorization")
if auth == "" {
return false, nil
}
if strings.HasPrefix(auth, AuthorizationPrefixQiniu) {
token, err := ath.SignRequestV2(req)
if err != nil {
return false, err
}
return auth == AuthorizationPrefixQiniu+token, nil
} else {
token, err := ath.SignRequest(req)
if err != nil {
return false, err
}
return auth == AuthorizationPrefixQBox+token, nil
}
}