1 package autorest
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 import (
18 "bytes"
19 "crypto/hmac"
20 "crypto/sha256"
21 "encoding/base64"
22 "fmt"
23 "net/http"
24 "net/url"
25 "sort"
26 "strings"
27 "time"
28 )
29
30
31
32 type SharedKeyType string
33
34 const (
35
36 SharedKey SharedKeyType = "sharedKey"
37
38
39 SharedKeyForTable SharedKeyType = "sharedKeyTable"
40
41
42
43 SharedKeyLite SharedKeyType = "sharedKeyLite"
44
45
46
47 SharedKeyLiteForTable SharedKeyType = "sharedKeyLiteTable"
48 )
49
50 const (
51 headerAccept = "Accept"
52 headerAcceptCharset = "Accept-Charset"
53 headerContentEncoding = "Content-Encoding"
54 headerContentLength = "Content-Length"
55 headerContentMD5 = "Content-MD5"
56 headerContentLanguage = "Content-Language"
57 headerIfModifiedSince = "If-Modified-Since"
58 headerIfMatch = "If-Match"
59 headerIfNoneMatch = "If-None-Match"
60 headerIfUnmodifiedSince = "If-Unmodified-Since"
61 headerDate = "Date"
62 headerXMSDate = "X-Ms-Date"
63 headerXMSVersion = "x-ms-version"
64 headerRange = "Range"
65 )
66
67 const storageEmulatorAccountName = "devstoreaccount1"
68
69
70
71 type SharedKeyAuthorizer struct {
72 accountName string
73 accountKey []byte
74 keyType SharedKeyType
75 }
76
77
78 func NewSharedKeyAuthorizer(accountName, accountKey string, keyType SharedKeyType) (*SharedKeyAuthorizer, error) {
79 key, err := base64.StdEncoding.DecodeString(accountKey)
80 if err != nil {
81 return nil, fmt.Errorf("malformed storage account key: %v", err)
82 }
83 return &SharedKeyAuthorizer{
84 accountName: accountName,
85 accountKey: key,
86 keyType: keyType,
87 }, nil
88 }
89
90
91
92
93
94
95
96
97
98 func (sk *SharedKeyAuthorizer) WithAuthorization() PrepareDecorator {
99 return func(p Preparer) Preparer {
100 return PreparerFunc(func(r *http.Request) (*http.Request, error) {
101 r, err := p.Prepare(r)
102 if err != nil {
103 return r, err
104 }
105
106 sk, err := buildSharedKey(sk.accountName, sk.accountKey, r, sk.keyType)
107 if err != nil {
108 return r, err
109 }
110 return Prepare(r, WithHeader(headerAuthorization, sk))
111 })
112 }
113 }
114
115 func buildSharedKey(accName string, accKey []byte, req *http.Request, keyType SharedKeyType) (string, error) {
116 canRes, err := buildCanonicalizedResource(accName, req.URL.String(), keyType)
117 if err != nil {
118 return "", err
119 }
120
121 if req.Header == nil {
122 req.Header = http.Header{}
123 }
124
125
126 if req.Header.Get(headerDate) == "" && req.Header.Get(headerXMSDate) == "" {
127 date := time.Now().UTC().Format(http.TimeFormat)
128 req.Header.Set(headerXMSDate, date)
129 }
130 canString, err := buildCanonicalizedString(req.Method, req.Header, canRes, keyType)
131 if err != nil {
132 return "", err
133 }
134 return createAuthorizationHeader(accName, accKey, canString, keyType), nil
135 }
136
137 func buildCanonicalizedResource(accountName, uri string, keyType SharedKeyType) (string, error) {
138 errMsg := "buildCanonicalizedResource error: %s"
139 u, err := url.Parse(uri)
140 if err != nil {
141 return "", fmt.Errorf(errMsg, err.Error())
142 }
143
144 cr := bytes.NewBufferString("")
145 if accountName != storageEmulatorAccountName {
146 cr.WriteString("/")
147 cr.WriteString(getCanonicalizedAccountName(accountName))
148 }
149
150 if len(u.Path) > 0 {
151
152
153
154 cr.WriteString(u.EscapedPath())
155 } else {
156
157 cr.WriteString("/")
158 }
159
160 params, err := url.ParseQuery(u.RawQuery)
161 if err != nil {
162 return "", fmt.Errorf(errMsg, err.Error())
163 }
164
165
166 if keyType == SharedKey {
167 if len(params) > 0 {
168 cr.WriteString("\n")
169
170 keys := []string{}
171 for key := range params {
172 keys = append(keys, key)
173 }
174 sort.Strings(keys)
175
176 completeParams := []string{}
177 for _, key := range keys {
178 if len(params[key]) > 1 {
179 sort.Strings(params[key])
180 }
181
182 completeParams = append(completeParams, fmt.Sprintf("%s:%s", key, strings.Join(params[key], ",")))
183 }
184 cr.WriteString(strings.Join(completeParams, "\n"))
185 }
186 } else {
187
188 if v, ok := params["comp"]; ok {
189 cr.WriteString("?comp=" + v[0])
190 }
191 }
192
193 return string(cr.Bytes()), nil
194 }
195
196 func getCanonicalizedAccountName(accountName string) string {
197
198
199 return strings.TrimSuffix(accountName, "-secondary")
200 }
201
202 func buildCanonicalizedString(verb string, headers http.Header, canonicalizedResource string, keyType SharedKeyType) (string, error) {
203 contentLength := headers.Get(headerContentLength)
204 if contentLength == "0" {
205 contentLength = ""
206 }
207 date := headers.Get(headerDate)
208 if v := headers.Get(headerXMSDate); v != "" {
209 if keyType == SharedKey || keyType == SharedKeyLite {
210 date = ""
211 } else {
212 date = v
213 }
214 }
215 var canString string
216 switch keyType {
217 case SharedKey:
218 canString = strings.Join([]string{
219 verb,
220 headers.Get(headerContentEncoding),
221 headers.Get(headerContentLanguage),
222 contentLength,
223 headers.Get(headerContentMD5),
224 headers.Get(headerContentType),
225 date,
226 headers.Get(headerIfModifiedSince),
227 headers.Get(headerIfMatch),
228 headers.Get(headerIfNoneMatch),
229 headers.Get(headerIfUnmodifiedSince),
230 headers.Get(headerRange),
231 buildCanonicalizedHeader(headers),
232 canonicalizedResource,
233 }, "\n")
234 case SharedKeyForTable:
235 canString = strings.Join([]string{
236 verb,
237 headers.Get(headerContentMD5),
238 headers.Get(headerContentType),
239 date,
240 canonicalizedResource,
241 }, "\n")
242 case SharedKeyLite:
243 canString = strings.Join([]string{
244 verb,
245 headers.Get(headerContentMD5),
246 headers.Get(headerContentType),
247 date,
248 buildCanonicalizedHeader(headers),
249 canonicalizedResource,
250 }, "\n")
251 case SharedKeyLiteForTable:
252 canString = strings.Join([]string{
253 date,
254 canonicalizedResource,
255 }, "\n")
256 default:
257 return "", fmt.Errorf("key type '%s' is not supported", keyType)
258 }
259 return canString, nil
260 }
261
262 func buildCanonicalizedHeader(headers http.Header) string {
263 cm := make(map[string]string)
264
265 for k := range headers {
266 headerName := strings.TrimSpace(strings.ToLower(k))
267 if strings.HasPrefix(headerName, "x-ms-") {
268 cm[headerName] = headers.Get(k)
269 }
270 }
271
272 if len(cm) == 0 {
273 return ""
274 }
275
276 keys := []string{}
277 for key := range cm {
278 keys = append(keys, key)
279 }
280
281 sort.Strings(keys)
282
283 ch := bytes.NewBufferString("")
284
285 for _, key := range keys {
286 ch.WriteString(key)
287 ch.WriteRune(':')
288 ch.WriteString(cm[key])
289 ch.WriteRune('\n')
290 }
291
292 return strings.TrimSuffix(string(ch.Bytes()), "\n")
293 }
294
295 func createAuthorizationHeader(accountName string, accountKey []byte, canonicalizedString string, keyType SharedKeyType) string {
296 h := hmac.New(sha256.New, accountKey)
297 h.Write([]byte(canonicalizedString))
298 signature := base64.StdEncoding.EncodeToString(h.Sum(nil))
299 var key string
300 switch keyType {
301 case SharedKey, SharedKeyForTable:
302 key = "SharedKey"
303 case SharedKeyLite, SharedKeyLiteForTable:
304 key = "SharedKeyLite"
305 }
306 return fmt.Sprintf("%s %s:%s", key, getCanonicalizedAccountName(accountName), signature)
307 }
308
View as plain text