|
Packit |
63bb0d |
package autorest
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// Copyright 2017 Microsoft Corporation
|
|
Packit |
63bb0d |
//
|
|
Packit |
63bb0d |
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
Packit |
63bb0d |
// you may not use this file except in compliance with the License.
|
|
Packit |
63bb0d |
// You may obtain a copy of the License at
|
|
Packit |
63bb0d |
//
|
|
Packit |
63bb0d |
// http://www.apache.org/licenses/LICENSE-2.0
|
|
Packit |
63bb0d |
//
|
|
Packit |
63bb0d |
// Unless required by applicable law or agreed to in writing, software
|
|
Packit |
63bb0d |
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
Packit |
63bb0d |
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
Packit |
63bb0d |
// See the License for the specific language governing permissions and
|
|
Packit |
63bb0d |
// limitations under the License.
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
import (
|
|
Packit |
63bb0d |
"bytes"
|
|
Packit |
63bb0d |
"crypto/hmac"
|
|
Packit |
63bb0d |
"crypto/sha256"
|
|
Packit |
63bb0d |
"encoding/base64"
|
|
Packit |
63bb0d |
"fmt"
|
|
Packit |
63bb0d |
"net/http"
|
|
Packit |
63bb0d |
"net/url"
|
|
Packit |
63bb0d |
"sort"
|
|
Packit |
63bb0d |
"strings"
|
|
Packit |
63bb0d |
"time"
|
|
Packit |
63bb0d |
)
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// SharedKeyType defines the enumeration for the various shared key types.
|
|
Packit |
63bb0d |
// See https://docs.microsoft.com/en-us/rest/api/storageservices/authorize-with-shared-key for details on the shared key types.
|
|
Packit |
63bb0d |
type SharedKeyType string
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
const (
|
|
Packit |
63bb0d |
// SharedKey is used to authorize against blobs, files and queues services.
|
|
Packit |
63bb0d |
SharedKey SharedKeyType = "sharedKey"
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// SharedKeyForTable is used to authorize against the table service.
|
|
Packit |
63bb0d |
SharedKeyForTable SharedKeyType = "sharedKeyTable"
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// SharedKeyLite is used to authorize against blobs, files and queues services. It's provided for
|
|
Packit |
63bb0d |
// backwards compatibility with API versions before 2009-09-19. Prefer SharedKey instead.
|
|
Packit |
63bb0d |
SharedKeyLite SharedKeyType = "sharedKeyLite"
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// SharedKeyLiteForTable is used to authorize against the table service. It's provided for
|
|
Packit |
63bb0d |
// backwards compatibility with older table API versions. Prefer SharedKeyForTable instead.
|
|
Packit |
63bb0d |
SharedKeyLiteForTable SharedKeyType = "sharedKeyLiteTable"
|
|
Packit |
63bb0d |
)
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
const (
|
|
Packit |
63bb0d |
headerAccept = "Accept"
|
|
Packit |
63bb0d |
headerAcceptCharset = "Accept-Charset"
|
|
Packit |
63bb0d |
headerContentEncoding = "Content-Encoding"
|
|
Packit |
63bb0d |
headerContentLength = "Content-Length"
|
|
Packit |
63bb0d |
headerContentMD5 = "Content-MD5"
|
|
Packit |
63bb0d |
headerContentLanguage = "Content-Language"
|
|
Packit |
63bb0d |
headerIfModifiedSince = "If-Modified-Since"
|
|
Packit |
63bb0d |
headerIfMatch = "If-Match"
|
|
Packit |
63bb0d |
headerIfNoneMatch = "If-None-Match"
|
|
Packit |
63bb0d |
headerIfUnmodifiedSince = "If-Unmodified-Since"
|
|
Packit |
63bb0d |
headerDate = "Date"
|
|
Packit |
63bb0d |
headerXMSDate = "X-Ms-Date"
|
|
Packit |
63bb0d |
headerXMSVersion = "x-ms-version"
|
|
Packit |
63bb0d |
headerRange = "Range"
|
|
Packit |
63bb0d |
)
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
const storageEmulatorAccountName = "devstoreaccount1"
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// SharedKeyAuthorizer implements an authorization for Shared Key
|
|
Packit |
63bb0d |
// this can be used for interaction with Blob, File and Queue Storage Endpoints
|
|
Packit |
63bb0d |
type SharedKeyAuthorizer struct {
|
|
Packit |
63bb0d |
accountName string
|
|
Packit |
63bb0d |
accountKey []byte
|
|
Packit |
63bb0d |
keyType SharedKeyType
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// NewSharedKeyAuthorizer creates a SharedKeyAuthorizer using the provided credentials and shared key type.
|
|
Packit |
63bb0d |
func NewSharedKeyAuthorizer(accountName, accountKey string, keyType SharedKeyType) (*SharedKeyAuthorizer, error) {
|
|
Packit |
63bb0d |
key, err := base64.StdEncoding.DecodeString(accountKey)
|
|
Packit |
63bb0d |
if err != nil {
|
|
Packit |
63bb0d |
return nil, fmt.Errorf("malformed storage account key: %v", err)
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
return &SharedKeyAuthorizer{
|
|
Packit |
63bb0d |
accountName: accountName,
|
|
Packit |
63bb0d |
accountKey: key,
|
|
Packit |
63bb0d |
keyType: keyType,
|
|
Packit |
63bb0d |
}, nil
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// WithAuthorization returns a PrepareDecorator that adds an HTTP Authorization header whose
|
|
Packit |
63bb0d |
// value is "<SharedKeyType> " followed by the computed key.
|
|
Packit |
63bb0d |
// This can be used for the Blob, Queue, and File Services
|
|
Packit |
63bb0d |
//
|
|
Packit |
63bb0d |
// from: https://docs.microsoft.com/en-us/rest/api/storageservices/authorize-with-shared-key
|
|
Packit |
63bb0d |
// You may use Shared Key authorization to authorize a request made against the
|
|
Packit |
63bb0d |
// 2009-09-19 version and later of the Blob and Queue services,
|
|
Packit |
63bb0d |
// and version 2014-02-14 and later of the File services.
|
|
Packit |
63bb0d |
func (sk *SharedKeyAuthorizer) WithAuthorization() PrepareDecorator {
|
|
Packit |
63bb0d |
return func(p Preparer) Preparer {
|
|
Packit |
63bb0d |
return PreparerFunc(func(r *http.Request) (*http.Request, error) {
|
|
Packit |
63bb0d |
r, err := p.Prepare(r)
|
|
Packit |
63bb0d |
if err != nil {
|
|
Packit |
63bb0d |
return r, err
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
sk, err := buildSharedKey(sk.accountName, sk.accountKey, r, sk.keyType)
|
|
Packit |
63bb0d |
if err != nil {
|
|
Packit |
63bb0d |
return r, err
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
return Prepare(r, WithHeader(headerAuthorization, sk))
|
|
Packit |
63bb0d |
})
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
func buildSharedKey(accName string, accKey []byte, req *http.Request, keyType SharedKeyType) (string, error) {
|
|
Packit |
63bb0d |
canRes, err := buildCanonicalizedResource(accName, req.URL.String(), keyType)
|
|
Packit |
63bb0d |
if err != nil {
|
|
Packit |
63bb0d |
return "", err
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
if req.Header == nil {
|
|
Packit |
63bb0d |
req.Header = http.Header{}
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// ensure date is set
|
|
Packit |
63bb0d |
if req.Header.Get(headerDate) == "" && req.Header.Get(headerXMSDate) == "" {
|
|
Packit |
63bb0d |
date := time.Now().UTC().Format(http.TimeFormat)
|
|
Packit |
63bb0d |
req.Header.Set(headerXMSDate, date)
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
canString, err := buildCanonicalizedString(req.Method, req.Header, canRes, keyType)
|
|
Packit |
63bb0d |
if err != nil {
|
|
Packit |
63bb0d |
return "", err
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
return createAuthorizationHeader(accName, accKey, canString, keyType), nil
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
func buildCanonicalizedResource(accountName, uri string, keyType SharedKeyType) (string, error) {
|
|
Packit |
63bb0d |
errMsg := "buildCanonicalizedResource error: %s"
|
|
Packit |
63bb0d |
u, err := url.Parse(uri)
|
|
Packit |
63bb0d |
if err != nil {
|
|
Packit |
63bb0d |
return "", fmt.Errorf(errMsg, err.Error())
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
cr := bytes.NewBufferString("")
|
|
Packit |
63bb0d |
if accountName != storageEmulatorAccountName {
|
|
Packit |
63bb0d |
cr.WriteString("/")
|
|
Packit |
63bb0d |
cr.WriteString(getCanonicalizedAccountName(accountName))
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
if len(u.Path) > 0 {
|
|
Packit |
63bb0d |
// Any portion of the CanonicalizedResource string that is derived from
|
|
Packit |
63bb0d |
// the resource's URI should be encoded exactly as it is in the URI.
|
|
Packit |
63bb0d |
// -- https://msdn.microsoft.com/en-gb/library/azure/dd179428.aspx
|
|
Packit |
63bb0d |
cr.WriteString(u.EscapedPath())
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
params, err := url.ParseQuery(u.RawQuery)
|
|
Packit |
63bb0d |
if err != nil {
|
|
Packit |
63bb0d |
return "", fmt.Errorf(errMsg, err.Error())
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// See https://github.com/Azure/azure-storage-net/blob/master/Lib/Common/Core/Util/AuthenticationUtility.cs#L277
|
|
Packit |
63bb0d |
if keyType == SharedKey {
|
|
Packit |
63bb0d |
if len(params) > 0 {
|
|
Packit |
63bb0d |
cr.WriteString("\n")
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
keys := []string{}
|
|
Packit |
63bb0d |
for key := range params {
|
|
Packit |
63bb0d |
keys = append(keys, key)
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
sort.Strings(keys)
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
completeParams := []string{}
|
|
Packit |
63bb0d |
for _, key := range keys {
|
|
Packit |
63bb0d |
if len(params[key]) > 1 {
|
|
Packit |
63bb0d |
sort.Strings(params[key])
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
completeParams = append(completeParams, fmt.Sprintf("%s:%s", key, strings.Join(params[key], ",")))
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
cr.WriteString(strings.Join(completeParams, "\n"))
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
} else {
|
|
Packit |
63bb0d |
// search for "comp" parameter, if exists then add it to canonicalizedresource
|
|
Packit |
63bb0d |
if v, ok := params["comp"]; ok {
|
|
Packit |
63bb0d |
cr.WriteString("?comp=" + v[0])
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
return string(cr.Bytes()), nil
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
func getCanonicalizedAccountName(accountName string) string {
|
|
Packit |
63bb0d |
// since we may be trying to access a secondary storage account, we need to
|
|
Packit |
63bb0d |
// remove the -secondary part of the storage name
|
|
Packit |
63bb0d |
return strings.TrimSuffix(accountName, "-secondary")
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
func buildCanonicalizedString(verb string, headers http.Header, canonicalizedResource string, keyType SharedKeyType) (string, error) {
|
|
Packit |
63bb0d |
contentLength := headers.Get(headerContentLength)
|
|
Packit |
63bb0d |
if contentLength == "0" {
|
|
Packit |
63bb0d |
contentLength = ""
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
date := headers.Get(headerDate)
|
|
Packit |
63bb0d |
if v := headers.Get(headerXMSDate); v != "" {
|
|
Packit |
63bb0d |
if keyType == SharedKey || keyType == SharedKeyLite {
|
|
Packit |
63bb0d |
date = ""
|
|
Packit |
63bb0d |
} else {
|
|
Packit |
63bb0d |
date = v
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
var canString string
|
|
Packit |
63bb0d |
switch keyType {
|
|
Packit |
63bb0d |
case SharedKey:
|
|
Packit |
63bb0d |
canString = strings.Join([]string{
|
|
Packit |
63bb0d |
verb,
|
|
Packit |
63bb0d |
headers.Get(headerContentEncoding),
|
|
Packit |
63bb0d |
headers.Get(headerContentLanguage),
|
|
Packit |
63bb0d |
contentLength,
|
|
Packit |
63bb0d |
headers.Get(headerContentMD5),
|
|
Packit |
63bb0d |
headers.Get(headerContentType),
|
|
Packit |
63bb0d |
date,
|
|
Packit |
63bb0d |
headers.Get(headerIfModifiedSince),
|
|
Packit |
63bb0d |
headers.Get(headerIfMatch),
|
|
Packit |
63bb0d |
headers.Get(headerIfNoneMatch),
|
|
Packit |
63bb0d |
headers.Get(headerIfUnmodifiedSince),
|
|
Packit |
63bb0d |
headers.Get(headerRange),
|
|
Packit |
63bb0d |
buildCanonicalizedHeader(headers),
|
|
Packit |
63bb0d |
canonicalizedResource,
|
|
Packit |
63bb0d |
}, "\n")
|
|
Packit |
63bb0d |
case SharedKeyForTable:
|
|
Packit |
63bb0d |
canString = strings.Join([]string{
|
|
Packit |
63bb0d |
verb,
|
|
Packit |
63bb0d |
headers.Get(headerContentMD5),
|
|
Packit |
63bb0d |
headers.Get(headerContentType),
|
|
Packit |
63bb0d |
date,
|
|
Packit |
63bb0d |
canonicalizedResource,
|
|
Packit |
63bb0d |
}, "\n")
|
|
Packit |
63bb0d |
case SharedKeyLite:
|
|
Packit |
63bb0d |
canString = strings.Join([]string{
|
|
Packit |
63bb0d |
verb,
|
|
Packit |
63bb0d |
headers.Get(headerContentMD5),
|
|
Packit |
63bb0d |
headers.Get(headerContentType),
|
|
Packit |
63bb0d |
date,
|
|
Packit |
63bb0d |
buildCanonicalizedHeader(headers),
|
|
Packit |
63bb0d |
canonicalizedResource,
|
|
Packit |
63bb0d |
}, "\n")
|
|
Packit |
63bb0d |
case SharedKeyLiteForTable:
|
|
Packit |
63bb0d |
canString = strings.Join([]string{
|
|
Packit |
63bb0d |
date,
|
|
Packit |
63bb0d |
canonicalizedResource,
|
|
Packit |
63bb0d |
}, "\n")
|
|
Packit |
63bb0d |
default:
|
|
Packit |
63bb0d |
return "", fmt.Errorf("key type '%s' is not supported", keyType)
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
return canString, nil
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
func buildCanonicalizedHeader(headers http.Header) string {
|
|
Packit |
63bb0d |
cm := make(map[string]string)
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
for k := range headers {
|
|
Packit |
63bb0d |
headerName := strings.TrimSpace(strings.ToLower(k))
|
|
Packit |
63bb0d |
if strings.HasPrefix(headerName, "x-ms-") {
|
|
Packit |
63bb0d |
cm[headerName] = headers.Get(k)
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
if len(cm) == 0 {
|
|
Packit |
63bb0d |
return ""
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
keys := []string{}
|
|
Packit |
63bb0d |
for key := range cm {
|
|
Packit |
63bb0d |
keys = append(keys, key)
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
sort.Strings(keys)
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
ch := bytes.NewBufferString("")
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
for _, key := range keys {
|
|
Packit |
63bb0d |
ch.WriteString(key)
|
|
Packit |
63bb0d |
ch.WriteRune(':')
|
|
Packit |
63bb0d |
ch.WriteString(cm[key])
|
|
Packit |
63bb0d |
ch.WriteRune('\n')
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
return strings.TrimSuffix(string(ch.Bytes()), "\n")
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
func createAuthorizationHeader(accountName string, accountKey []byte, canonicalizedString string, keyType SharedKeyType) string {
|
|
Packit |
63bb0d |
h := hmac.New(sha256.New, accountKey)
|
|
Packit |
63bb0d |
h.Write([]byte(canonicalizedString))
|
|
Packit |
63bb0d |
signature := base64.StdEncoding.EncodeToString(h.Sum(nil))
|
|
Packit |
63bb0d |
var key string
|
|
Packit |
63bb0d |
switch keyType {
|
|
Packit |
63bb0d |
case SharedKey, SharedKeyForTable:
|
|
Packit |
63bb0d |
key = "SharedKey"
|
|
Packit |
63bb0d |
case SharedKeyLite, SharedKeyLiteForTable:
|
|
Packit |
63bb0d |
key = "SharedKeyLite"
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
return fmt.Sprintf("%s %s:%s", key, getCanonicalizedAccountName(accountName), signature)
|
|
Packit |
63bb0d |
}
|