vendor: add qingstor-sdk-go for QingStor
This commit is contained in:
+259
@@ -0,0 +1,259 @@
|
||||
// +-------------------------------------------------------------------------
|
||||
// | Copyright (C) 2016 Yunify, Inc.
|
||||
// +-------------------------------------------------------------------------
|
||||
// | Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// | you may not use this work except in compliance with the License.
|
||||
// | You may obtain a copy of the License in the LICENSE file, or at:
|
||||
// |
|
||||
// | http://www.apache.org/licenses/LICENSE-2.0
|
||||
// |
|
||||
// | Unless required by applicable law or agreed to in writing, software
|
||||
// | distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// | See the License for the specific language governing permissions and
|
||||
// | limitations under the License.
|
||||
// +-------------------------------------------------------------------------
|
||||
|
||||
package signer
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/pengsrc/go-shared/convert"
|
||||
|
||||
"github.com/yunify/qingstor-sdk-go/logger"
|
||||
"github.com/yunify/qingstor-sdk-go/utils"
|
||||
)
|
||||
|
||||
// QingStorSigner is the http request signer for QingStor service.
|
||||
type QingStorSigner struct {
|
||||
AccessKeyID string
|
||||
SecretAccessKey string
|
||||
}
|
||||
|
||||
// WriteSignature calculates signature and write it to http request header.
|
||||
func (qss *QingStorSigner) WriteSignature(request *http.Request) error {
|
||||
authorization, err := qss.BuildSignature(request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
request.Header.Set("Authorization", authorization)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteQuerySignature calculates signature and write it to http request url.
|
||||
func (qss *QingStorSigner) WriteQuerySignature(request *http.Request, expires int) error {
|
||||
query, err := qss.BuildQuerySignature(request, expires)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if request.URL.RawQuery != "" {
|
||||
query = "?" + request.URL.RawQuery + "&" + query
|
||||
} else {
|
||||
query = "?" + query
|
||||
}
|
||||
|
||||
newRequest, err := http.NewRequest(request.Method,
|
||||
request.URL.Scheme+"://"+request.URL.Host+utils.URLQueryEscape(request.URL.Path)+query, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
request.URL = newRequest.URL
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// BuildSignature calculates the signature string.
|
||||
func (qss *QingStorSigner) BuildSignature(request *http.Request) (string, error) {
|
||||
stringToSign, err := qss.BuildStringToSign(request)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
h := hmac.New(sha256.New, []byte(qss.SecretAccessKey))
|
||||
h.Write([]byte(stringToSign))
|
||||
|
||||
signature := strings.TrimSpace(base64.StdEncoding.EncodeToString(h.Sum(nil)))
|
||||
authorization := "QS " + qss.AccessKeyID + ":" + signature
|
||||
|
||||
logger.Debug(fmt.Sprintf(
|
||||
"QingStor authorization: [%d] %s",
|
||||
convert.StringToUnixTimestamp(request.Header.Get("Date"), convert.RFC822),
|
||||
authorization),
|
||||
)
|
||||
|
||||
return authorization, nil
|
||||
}
|
||||
|
||||
// BuildQuerySignature calculates the signature string for query.
|
||||
func (qss *QingStorSigner) BuildQuerySignature(request *http.Request, expires int) (string, error) {
|
||||
stringToSign, err := qss.BuildQueryStringToSign(request, expires)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
h := hmac.New(sha256.New, []byte(qss.SecretAccessKey))
|
||||
h.Write([]byte(stringToSign))
|
||||
|
||||
signature := strings.TrimSpace(base64.StdEncoding.EncodeToString(h.Sum(nil)))
|
||||
signature = utils.URLQueryEscape(signature)
|
||||
query := fmt.Sprintf(
|
||||
"access_key_id=%s&expires=%d&signature=%s",
|
||||
qss.AccessKeyID, expires, signature,
|
||||
)
|
||||
|
||||
logger.Debug(fmt.Sprintf(
|
||||
"QingStor query signature: [%d] %s",
|
||||
convert.StringToUnixTimestamp(request.Header.Get("Date"), convert.RFC822),
|
||||
query,
|
||||
))
|
||||
|
||||
return query, nil
|
||||
}
|
||||
|
||||
// BuildStringToSign build the string to sign.
|
||||
func (qss *QingStorSigner) BuildStringToSign(request *http.Request) (string, error) {
|
||||
stringToSign := fmt.Sprintf(
|
||||
"%s\n%s\n%s\n%s\n",
|
||||
request.Method,
|
||||
request.Header.Get("Content-MD5"),
|
||||
request.Header.Get("Content-Type"),
|
||||
request.Header.Get("Date"),
|
||||
)
|
||||
|
||||
stringToSign += qss.buildCanonicalizedHeaders(request)
|
||||
canonicalizedResource, err := qss.buildCanonicalizedResource(request)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
stringToSign += canonicalizedResource
|
||||
|
||||
logger.Debug(fmt.Sprintf(
|
||||
"QingStor string to sign: [%d] %s",
|
||||
convert.StringToUnixTimestamp(request.Header.Get("Date"), convert.RFC822),
|
||||
stringToSign,
|
||||
))
|
||||
|
||||
return stringToSign, nil
|
||||
}
|
||||
|
||||
// BuildQueryStringToSign build the string to sign for query.
|
||||
func (qss *QingStorSigner) BuildQueryStringToSign(request *http.Request, expires int) (string, error) {
|
||||
stringToSign := fmt.Sprintf(
|
||||
"%s\n%s\n%s\n%d\n",
|
||||
request.Method,
|
||||
request.Header.Get("Content-MD5"),
|
||||
request.Header.Get("Content-Type"),
|
||||
expires,
|
||||
)
|
||||
|
||||
stringToSign += qss.buildCanonicalizedHeaders(request)
|
||||
canonicalizedResource, err := qss.buildCanonicalizedResource(request)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
stringToSign += canonicalizedResource
|
||||
|
||||
logger.Debug(fmt.Sprintf(
|
||||
"QingStor query string to sign: [%d] %s",
|
||||
convert.StringToUnixTimestamp(request.Header.Get("Date"), convert.RFC822),
|
||||
stringToSign,
|
||||
))
|
||||
|
||||
return stringToSign, nil
|
||||
}
|
||||
|
||||
func (qss *QingStorSigner) buildCanonicalizedHeaders(request *http.Request) string {
|
||||
keys := []string{}
|
||||
for key := range request.Header {
|
||||
if strings.HasPrefix(strings.ToLower(key), "x-qs-") {
|
||||
keys = append(keys, strings.TrimSpace(strings.ToLower(key)))
|
||||
}
|
||||
}
|
||||
|
||||
sort.Strings(keys)
|
||||
|
||||
canonicalizedHeaders := ""
|
||||
for _, key := range keys {
|
||||
canonicalizedHeaders += key + ":" + strings.TrimSpace(request.Header.Get(key)) + "\n"
|
||||
}
|
||||
|
||||
return canonicalizedHeaders
|
||||
}
|
||||
|
||||
func (qss *QingStorSigner) buildCanonicalizedResource(request *http.Request) (string, error) {
|
||||
path := utils.URLQueryEscape(request.URL.Path)
|
||||
query := request.URL.Query()
|
||||
|
||||
keys := []string{}
|
||||
for key := range query {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
|
||||
sort.Strings(keys)
|
||||
|
||||
parts := []string{}
|
||||
for _, key := range keys {
|
||||
values := query[key]
|
||||
if qss.paramsToSign(key) {
|
||||
if len(values) > 0 {
|
||||
if values[0] != "" {
|
||||
value := strings.TrimSpace(strings.Join(values, ""))
|
||||
value, err := utils.URLQueryUnescape(value)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
parts = append(parts, key+"="+value)
|
||||
} else {
|
||||
parts = append(parts, key)
|
||||
}
|
||||
} else {
|
||||
parts = append(parts, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
joinedParts := strings.Join(parts, "&")
|
||||
if joinedParts != "" {
|
||||
path = path + "?" + joinedParts
|
||||
}
|
||||
|
||||
logger.Debug(fmt.Sprintf(
|
||||
"QingStor canonicalized resource: [%d] %s",
|
||||
convert.StringToUnixTimestamp(request.Header.Get("Date"), convert.RFC822),
|
||||
path,
|
||||
))
|
||||
|
||||
return path, nil
|
||||
}
|
||||
|
||||
func (qss *QingStorSigner) paramsToSign(key string) bool {
|
||||
keysMap := map[string]bool{
|
||||
"acl": true,
|
||||
"cors": true,
|
||||
"delete": true,
|
||||
"mirror": true,
|
||||
"part_number": true,
|
||||
"policy": true,
|
||||
"stats": true,
|
||||
"upload_id": true,
|
||||
"uploads": true,
|
||||
"response-expires": true,
|
||||
"response-cache-control": true,
|
||||
"response-content-type": true,
|
||||
"response-content-language": true,
|
||||
"response-content-encoding": true,
|
||||
"response-content-disposition": true,
|
||||
}
|
||||
|
||||
return keysMap[key]
|
||||
}
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
// +-------------------------------------------------------------------------
|
||||
// | Copyright (C) 2016 Yunify, Inc.
|
||||
// +-------------------------------------------------------------------------
|
||||
// | Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// | you may not use this work except in compliance with the License.
|
||||
// | You may obtain a copy of the License in the LICENSE file, or at:
|
||||
// |
|
||||
// | http://www.apache.org/licenses/LICENSE-2.0
|
||||
// |
|
||||
// | Unless required by applicable law or agreed to in writing, software
|
||||
// | distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// | See the License for the specific language governing permissions and
|
||||
// | limitations under the License.
|
||||
// +-------------------------------------------------------------------------
|
||||
|
||||
package signer
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/pengsrc/go-shared/convert"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestQingStorSignerWriteSignature(t *testing.T) {
|
||||
url := "https://qingstor.com/?acl&upload_id=fde133b5f6d932cd9c79bac3c7318da1&part_number=0&other=abc"
|
||||
httpRequest, err := http.NewRequest("GET", url, nil)
|
||||
httpRequest.Header.Set("Date", convert.TimeToString(time.Time{}, convert.RFC822))
|
||||
httpRequest.Header.Set("X-QS-Test-2", "Test 2")
|
||||
httpRequest.Header.Set("X-QS-Test-1", "Test 1")
|
||||
assert.Nil(t, err)
|
||||
|
||||
s := QingStorSigner{
|
||||
AccessKeyID: "ENV_ACCESS_KEY_ID",
|
||||
SecretAccessKey: "ENV_SECRET_ACCESS_KEY",
|
||||
}
|
||||
|
||||
err = s.WriteSignature(httpRequest)
|
||||
assert.Nil(t, err)
|
||||
|
||||
signature := "QS ENV_ACCESS_KEY_ID:bvglZF9iMOv1RaCTxPYWxexmt1UN2m5WKngYnhDEp2c="
|
||||
assert.Equal(t, signature, httpRequest.Header.Get("Authorization"))
|
||||
}
|
||||
|
||||
func TestQingStorSignerWriteSignatureChinese(t *testing.T) {
|
||||
url := "https://zone.qingstor.com/bucket-name/中文"
|
||||
httpRequest, err := http.NewRequest("GET", url, nil)
|
||||
httpRequest.Header.Set("Date", convert.TimeToString(time.Time{}, convert.RFC822))
|
||||
assert.Nil(t, err)
|
||||
|
||||
s := QingStorSigner{
|
||||
AccessKeyID: "ENV_ACCESS_KEY_ID",
|
||||
SecretAccessKey: "ENV_SECRET_ACCESS_KEY",
|
||||
}
|
||||
|
||||
err = s.WriteSignature(httpRequest)
|
||||
assert.Nil(t, err)
|
||||
|
||||
signature := "QS ENV_ACCESS_KEY_ID:XsTXX50kzqBf92zLG1aIUIJmZ0hqIHoaHgkumwnV3fs="
|
||||
assert.Equal(t, signature, httpRequest.Header.Get("Authorization"))
|
||||
}
|
||||
|
||||
func TestQingStorSignerWriteQuerySignature(t *testing.T) {
|
||||
url := "https://qingstor.com/?acl&upload_id=fde133b5f6d932cd9c79bac3c7318da1&part_number=0"
|
||||
httpRequest, err := http.NewRequest("GET", url, nil)
|
||||
httpRequest.Header.Set("Date", convert.TimeToString(time.Time{}, convert.RFC822))
|
||||
httpRequest.Header.Set("X-QS-Test-2", "Test 2")
|
||||
httpRequest.Header.Set("X-QS-Test-1", "Test 1")
|
||||
assert.Nil(t, err)
|
||||
|
||||
s := QingStorSigner{
|
||||
AccessKeyID: "ENV_ACCESS_KEY_ID",
|
||||
SecretAccessKey: "ENV_SECRET_ACCESS_KEY",
|
||||
}
|
||||
|
||||
err = s.WriteQuerySignature(httpRequest, 3600)
|
||||
assert.Nil(t, err)
|
||||
|
||||
targetURL := "https://qingstor.com/?acl&upload_id=fde133b5f6d932cd9c79bac3c7318da1&part_number=0&access_key_id=ENV_ACCESS_KEY_ID&expires=3600&signature=GRL3p3NOgHR9CQygASvyo344vdnO1hFke6ZvQ5mDVHM="
|
||||
assert.Equal(t, httpRequest.URL.String(), targetURL)
|
||||
}
|
||||
Reference in New Issue
Block a user