...

Source file src/github.com/Azure/azure-sdk-for-go/storage/authorization.go

Documentation: github.com/Azure/azure-sdk-for-go/storage

     1  // Package storage provides clients for Microsoft Azure Storage Services.
     2  package storage
     3  
     4  // Copyright (c) Microsoft Corporation. All rights reserved.
     5  // Licensed under the MIT License. See License.txt in the project root for license information.
     6  
     7  import (
     8  	"bytes"
     9  	"fmt"
    10  	"net/url"
    11  	"sort"
    12  	"strings"
    13  )
    14  
    15  // See: https://docs.microsoft.com/rest/api/storageservices/fileservices/authentication-for-the-azure-storage-services
    16  
    17  type authentication string
    18  
    19  const (
    20  	sharedKey             authentication = "sharedKey"
    21  	sharedKeyForTable     authentication = "sharedKeyTable"
    22  	sharedKeyLite         authentication = "sharedKeyLite"
    23  	sharedKeyLiteForTable authentication = "sharedKeyLiteTable"
    24  
    25  	// headers
    26  	headerAcceptCharset           = "Accept-Charset"
    27  	headerAuthorization           = "Authorization"
    28  	headerContentLength           = "Content-Length"
    29  	headerDate                    = "Date"
    30  	headerXmsDate                 = "x-ms-date"
    31  	headerXmsVersion              = "x-ms-version"
    32  	headerContentEncoding         = "Content-Encoding"
    33  	headerContentLanguage         = "Content-Language"
    34  	headerContentType             = "Content-Type"
    35  	headerContentMD5              = "Content-MD5"
    36  	headerIfModifiedSince         = "If-Modified-Since"
    37  	headerIfMatch                 = "If-Match"
    38  	headerIfNoneMatch             = "If-None-Match"
    39  	headerIfUnmodifiedSince       = "If-Unmodified-Since"
    40  	headerRange                   = "Range"
    41  	headerDataServiceVersion      = "DataServiceVersion"
    42  	headerMaxDataServiceVersion   = "MaxDataServiceVersion"
    43  	headerContentTransferEncoding = "Content-Transfer-Encoding"
    44  )
    45  
    46  func (c *Client) addAuthorizationHeader(verb, url string, headers map[string]string, auth authentication) (map[string]string, error) {
    47  	if !c.sasClient {
    48  		authHeader, err := c.getSharedKey(verb, url, headers, auth)
    49  		if err != nil {
    50  			return nil, err
    51  		}
    52  		headers[headerAuthorization] = authHeader
    53  	}
    54  	return headers, nil
    55  }
    56  
    57  func (c *Client) getSharedKey(verb, url string, headers map[string]string, auth authentication) (string, error) {
    58  	canRes, err := c.buildCanonicalizedResource(url, auth, false)
    59  	if err != nil {
    60  		return "", err
    61  	}
    62  
    63  	canString, err := buildCanonicalizedString(verb, headers, canRes, auth)
    64  	if err != nil {
    65  		return "", err
    66  	}
    67  	return c.createAuthorizationHeader(canString, auth), nil
    68  }
    69  
    70  func (c *Client) buildCanonicalizedResource(uri string, auth authentication, sas bool) (string, error) {
    71  	errMsg := "buildCanonicalizedResource error: %s"
    72  	u, err := url.Parse(uri)
    73  	if err != nil {
    74  		return "", fmt.Errorf(errMsg, err.Error())
    75  	}
    76  
    77  	cr := bytes.NewBufferString("")
    78  	if c.accountName != StorageEmulatorAccountName || !sas {
    79  		cr.WriteString("/")
    80  		cr.WriteString(c.getCanonicalizedAccountName())
    81  	}
    82  
    83  	if len(u.Path) > 0 {
    84  		// Any portion of the CanonicalizedResource string that is derived from
    85  		// the resource's URI should be encoded exactly as it is in the URI.
    86  		// -- https://msdn.microsoft.com/en-gb/library/azure/dd179428.aspx
    87  		cr.WriteString(u.EscapedPath())
    88  	}
    89  
    90  	params, err := url.ParseQuery(u.RawQuery)
    91  	if err != nil {
    92  		return "", fmt.Errorf(errMsg, err.Error())
    93  	}
    94  
    95  	// See https://github.com/Azure/azure-storage-net/blob/master/Lib/Common/Core/Util/AuthenticationUtility.cs#L277
    96  	if auth == sharedKey {
    97  		if len(params) > 0 {
    98  			cr.WriteString("\n")
    99  
   100  			keys := []string{}
   101  			for key := range params {
   102  				keys = append(keys, key)
   103  			}
   104  			sort.Strings(keys)
   105  
   106  			completeParams := []string{}
   107  			for _, key := range keys {
   108  				if len(params[key]) > 1 {
   109  					sort.Strings(params[key])
   110  				}
   111  
   112  				completeParams = append(completeParams, fmt.Sprintf("%s:%s", key, strings.Join(params[key], ",")))
   113  			}
   114  			cr.WriteString(strings.Join(completeParams, "\n"))
   115  		}
   116  	} else {
   117  		// search for "comp" parameter, if exists then add it to canonicalizedresource
   118  		if v, ok := params["comp"]; ok {
   119  			cr.WriteString("?comp=" + v[0])
   120  		}
   121  	}
   122  
   123  	return string(cr.Bytes()), nil
   124  }
   125  
   126  func (c *Client) getCanonicalizedAccountName() string {
   127  	// since we may be trying to access a secondary storage account, we need to
   128  	// remove the -secondary part of the storage name
   129  	return strings.TrimSuffix(c.accountName, "-secondary")
   130  }
   131  
   132  func buildCanonicalizedString(verb string, headers map[string]string, canonicalizedResource string, auth authentication) (string, error) {
   133  	contentLength := headers[headerContentLength]
   134  	if contentLength == "0" {
   135  		contentLength = ""
   136  	}
   137  	date := headers[headerDate]
   138  	if v, ok := headers[headerXmsDate]; ok {
   139  		if auth == sharedKey || auth == sharedKeyLite {
   140  			date = ""
   141  		} else {
   142  			date = v
   143  		}
   144  	}
   145  	var canString string
   146  	switch auth {
   147  	case sharedKey:
   148  		canString = strings.Join([]string{
   149  			verb,
   150  			headers[headerContentEncoding],
   151  			headers[headerContentLanguage],
   152  			contentLength,
   153  			headers[headerContentMD5],
   154  			headers[headerContentType],
   155  			date,
   156  			headers[headerIfModifiedSince],
   157  			headers[headerIfMatch],
   158  			headers[headerIfNoneMatch],
   159  			headers[headerIfUnmodifiedSince],
   160  			headers[headerRange],
   161  			buildCanonicalizedHeader(headers),
   162  			canonicalizedResource,
   163  		}, "\n")
   164  	case sharedKeyForTable:
   165  		canString = strings.Join([]string{
   166  			verb,
   167  			headers[headerContentMD5],
   168  			headers[headerContentType],
   169  			date,
   170  			canonicalizedResource,
   171  		}, "\n")
   172  	case sharedKeyLite:
   173  		canString = strings.Join([]string{
   174  			verb,
   175  			headers[headerContentMD5],
   176  			headers[headerContentType],
   177  			date,
   178  			buildCanonicalizedHeader(headers),
   179  			canonicalizedResource,
   180  		}, "\n")
   181  	case sharedKeyLiteForTable:
   182  		canString = strings.Join([]string{
   183  			date,
   184  			canonicalizedResource,
   185  		}, "\n")
   186  	default:
   187  		return "", fmt.Errorf("%s authentication is not supported yet", auth)
   188  	}
   189  	return canString, nil
   190  }
   191  
   192  func buildCanonicalizedHeader(headers map[string]string) string {
   193  	cm := make(map[string]string)
   194  
   195  	for k, v := range headers {
   196  		headerName := strings.TrimSpace(strings.ToLower(k))
   197  		if strings.HasPrefix(headerName, "x-ms-") {
   198  			cm[headerName] = v
   199  		}
   200  	}
   201  
   202  	if len(cm) == 0 {
   203  		return ""
   204  	}
   205  
   206  	keys := []string{}
   207  	for key := range cm {
   208  		keys = append(keys, key)
   209  	}
   210  
   211  	sort.Strings(keys)
   212  
   213  	ch := bytes.NewBufferString("")
   214  
   215  	for _, key := range keys {
   216  		ch.WriteString(key)
   217  		ch.WriteRune(':')
   218  		ch.WriteString(cm[key])
   219  		ch.WriteRune('\n')
   220  	}
   221  
   222  	return strings.TrimSuffix(string(ch.Bytes()), "\n")
   223  }
   224  
   225  func (c *Client) createAuthorizationHeader(canonicalizedString string, auth authentication) string {
   226  	signature := c.computeHmac256(canonicalizedString)
   227  	var key string
   228  	switch auth {
   229  	case sharedKey, sharedKeyForTable:
   230  		key = "SharedKey"
   231  	case sharedKeyLite, sharedKeyLiteForTable:
   232  		key = "SharedKeyLite"
   233  	}
   234  	return fmt.Sprintf("%s %s:%s", key, c.getCanonicalizedAccountName(), signature)
   235  }
   236  

View as plain text