odps/account/aliyun_account.go (141 lines of code) (raw):
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License 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 account
import (
"bytes"
"net/http"
"net/url"
"os"
"sort"
"strings"
"github.com/aliyun/aliyun-odps-go-sdk/odps/common"
)
type AliyunAccount struct {
accessId string
accessKey string
regionId string
}
func NewAliyunAccount(accessId string, accessKey string, regionId ...string) *AliyunAccount {
if len(regionId) > 0 {
return &AliyunAccount{
accessId: accessId,
accessKey: accessKey,
regionId: regionId[0],
}
} else {
return &AliyunAccount{
accessId: accessId,
accessKey: accessKey,
}
}
}
func AliyunAccountFromEnv() *AliyunAccount {
account := AliyunAccount{}
if accessId, found := os.LookupEnv("ALIBABA_CLOUD_ACCESS_KEY_ID"); found {
account.accessId = accessId
}
if accessKey, found := os.LookupEnv("ALIBABA_CLOUD_ACCESS_KEY_SECRET"); found {
account.accessKey = accessKey
}
return &account
}
func AccountFromEnv() Account {
accessId, found := os.LookupEnv("ALIBABA_CLOUD_ACCESS_KEY_ID")
accessKey, found := os.LookupEnv("ALIBABA_CLOUD_ACCESS_KEY_SECRET")
if !found {
return nil
}
securityToken, found := os.LookupEnv("ALIBABA_CLOUD_SECURITY_TOKEN")
if found {
return NewStsAccount(accessId, accessKey, securityToken)
} else {
return NewAliyunAccount(accessId, accessKey)
}
}
func (account *AliyunAccount) AccessId() string {
return account.accessId
}
func (account *AliyunAccount) AccessKey() string {
return account.accessKey
}
func (account *AliyunAccount) RegionId() string {
return account.regionId
}
func (account *AliyunAccount) GetType() Provider {
return Aliyun
}
func (account *AliyunAccount) SignRequest(req *http.Request, endpoint string) error {
canonicalString := account.buildCanonicalString(req, endpoint)
// Generate signature
signature := account.generateSignature(canonicalString.Bytes())
// Set authorization header
req.Header.Set(common.HttpHeaderAuthorization, signature)
return nil
}
// buildCanonicalString constructs canonical string for ODPS signature
func (account *AliyunAccount) buildCanonicalString(req *http.Request, endpoint string) bytes.Buffer {
var msg bytes.Buffer
// Write HTTP method
msg.WriteString(req.Method)
msg.WriteByte('\n')
// Write standard headers
msg.WriteString(req.Header.Get(common.HttpHeaderContentMD5))
msg.WriteByte('\n')
msg.WriteString(req.Header.Get(common.HttpHeaderContentType))
msg.WriteByte('\n')
msg.WriteString(req.Header.Get(common.HttpHeaderDate))
msg.WriteByte('\n')
// Write canonical headers
msg.WriteString(account.buildCanonicalHeaders(req.Header))
// Write canonical resource
msg.WriteString(account.buildCanonicalResource(req, endpoint))
return msg
}
// buildCanonicalHeaders constructs canonical headers for ODPS signature
func (account *AliyunAccount) buildCanonicalHeaders(headers http.Header) string {
var headerBuf bytes.Buffer
var canonicalHeaderKeys []string
// Collect ODPS-specific headers
for key := range headers {
lowerKey := strings.ToLower(key)
if strings.HasPrefix(lowerKey, common.HttpHeaderOdpsPrefix) {
canonicalHeaderKeys = append(canonicalHeaderKeys, lowerKey)
}
}
// Sort and write headers
sort.Strings(canonicalHeaderKeys)
for _, key := range canonicalHeaderKeys {
headerBuf.WriteString(key)
headerBuf.WriteByte(':')
headerBuf.WriteString(strings.Join(headers.Values(key), ","))
headerBuf.WriteByte('\n')
}
return headerBuf.String()
}
// buildCanonicalResource constructs canonical resource path for ODPS signature
func (account *AliyunAccount) buildCanonicalResource(req *http.Request, endpoint string) string {
var resBuf bytes.Buffer
parsedEndpoint, _ := url.Parse(endpoint)
basePath := parsedEndpoint.Path
// Handle path normalization
if strings.HasPrefix(req.URL.Path, basePath) {
resBuf.WriteString(req.URL.Path[len(basePath):])
} else {
resBuf.WriteString(req.URL.Path)
}
// Handle query parameters
queryParams := req.URL.Query()
if len(queryParams) > 0 {
resBuf.WriteByte('?')
paramKeys := make([]string, 0, len(queryParams))
for k := range queryParams {
paramKeys = append(paramKeys, k)
}
sort.Strings(paramKeys)
for i, key := range paramKeys {
if i > 0 {
resBuf.WriteByte('&')
}
resBuf.WriteString(key)
if value := queryParams.Get(key); value != "" {
resBuf.WriteByte('=')
resBuf.WriteString(value)
}
}
}
return resBuf.String()
}
// generateSignature creates the final authorization signature
func (account *AliyunAccount) generateSignature(data []byte) string {
signature := base64HmacSha1([]byte(account.accessKey), data)
var authBuf bytes.Buffer
authBuf.WriteString("ODPS ")
authBuf.WriteString(account.accessId)
authBuf.WriteByte(':')
authBuf.WriteString(signature)
return authBuf.String()
}