1
2 package storage
3
4
5
6
7 import (
8 "bytes"
9 "fmt"
10 "net/url"
11 "sort"
12 "strings"
13 )
14
15
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
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
85
86
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
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
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
128
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