1package storage
2
3// Copyright 2017 Microsoft Corporation
4//
5//  Licensed under the Apache License, Version 2.0 (the "License");
6//  you may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at
8//
9//      http://www.apache.org/licenses/LICENSE-2.0
10//
11//  Unless required by applicable law or agreed to in writing, software
12//  distributed under the License is distributed on an "AS IS" BASIS,
13//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14//  See the License for the specific language governing permissions and
15//  limitations under the License.
16
17import (
18	"errors"
19	"fmt"
20	"net/url"
21	"strings"
22	"time"
23)
24
25// OverrideHeaders defines overridable response heaedrs in
26// a request using a SAS URI.
27// See https://docs.microsoft.com/en-us/rest/api/storageservices/constructing-a-service-sas
28type OverrideHeaders struct {
29	CacheControl       string
30	ContentDisposition string
31	ContentEncoding    string
32	ContentLanguage    string
33	ContentType        string
34}
35
36// BlobSASOptions are options to construct a blob SAS
37// URI.
38// See https://docs.microsoft.com/en-us/rest/api/storageservices/constructing-a-service-sas
39type BlobSASOptions struct {
40	BlobServiceSASPermissions
41	OverrideHeaders
42	SASOptions
43}
44
45// BlobServiceSASPermissions includes the available permissions for
46// blob service SAS URI.
47type BlobServiceSASPermissions struct {
48	Read   bool
49	Add    bool
50	Create bool
51	Write  bool
52	Delete bool
53}
54
55func (p BlobServiceSASPermissions) buildString() string {
56	permissions := ""
57	if p.Read {
58		permissions += "r"
59	}
60	if p.Add {
61		permissions += "a"
62	}
63	if p.Create {
64		permissions += "c"
65	}
66	if p.Write {
67		permissions += "w"
68	}
69	if p.Delete {
70		permissions += "d"
71	}
72	return permissions
73}
74
75// GetSASURI creates an URL to the blob which contains the Shared
76// Access Signature with the specified options.
77//
78// See https://docs.microsoft.com/en-us/rest/api/storageservices/constructing-a-service-sas
79func (b *Blob) GetSASURI(options BlobSASOptions) (string, error) {
80	uri := b.GetURL()
81	signedResource := "b"
82	canonicalizedResource, err := b.Container.bsc.client.buildCanonicalizedResource(uri, b.Container.bsc.auth, true)
83	if err != nil {
84		return "", err
85	}
86
87	permissions := options.BlobServiceSASPermissions.buildString()
88	return b.Container.bsc.client.blobAndFileSASURI(options.SASOptions, uri, permissions, canonicalizedResource, signedResource, options.OverrideHeaders)
89}
90
91func (c *Client) blobAndFileSASURI(options SASOptions, uri, permissions, canonicalizedResource, signedResource string, headers OverrideHeaders) (string, error) {
92	start := ""
93	if options.Start != (time.Time{}) {
94		start = options.Start.UTC().Format(time.RFC3339)
95	}
96
97	expiry := options.Expiry.UTC().Format(time.RFC3339)
98
99	// We need to replace + with %2b first to avoid being treated as a space (which is correct for query strings, but not the path component).
100	canonicalizedResource = strings.Replace(canonicalizedResource, "+", "%2b", -1)
101	canonicalizedResource, err := url.QueryUnescape(canonicalizedResource)
102	if err != nil {
103		return "", err
104	}
105
106	protocols := ""
107	if options.UseHTTPS {
108		protocols = "https"
109	}
110	stringToSign, err := blobSASStringToSign(permissions, start, expiry, canonicalizedResource, options.Identifier, options.IP, protocols, c.apiVersion, headers)
111	if err != nil {
112		return "", err
113	}
114
115	sig := c.computeHmac256(stringToSign)
116	sasParams := url.Values{
117		"sv":  {c.apiVersion},
118		"se":  {expiry},
119		"sr":  {signedResource},
120		"sp":  {permissions},
121		"sig": {sig},
122	}
123
124	if start != "" {
125		sasParams.Add("st", start)
126	}
127
128	if c.apiVersion >= "2015-04-05" {
129		if protocols != "" {
130			sasParams.Add("spr", protocols)
131		}
132		if options.IP != "" {
133			sasParams.Add("sip", options.IP)
134		}
135	}
136
137	// Add override response hedaers
138	addQueryParameter(sasParams, "rscc", headers.CacheControl)
139	addQueryParameter(sasParams, "rscd", headers.ContentDisposition)
140	addQueryParameter(sasParams, "rsce", headers.ContentEncoding)
141	addQueryParameter(sasParams, "rscl", headers.ContentLanguage)
142	addQueryParameter(sasParams, "rsct", headers.ContentType)
143
144	sasURL, err := url.Parse(uri)
145	if err != nil {
146		return "", err
147	}
148	sasURL.RawQuery = sasParams.Encode()
149	return sasURL.String(), nil
150}
151
152func blobSASStringToSign(signedPermissions, signedStart, signedExpiry, canonicalizedResource, signedIdentifier, signedIP, protocols, signedVersion string, headers OverrideHeaders) (string, error) {
153	rscc := headers.CacheControl
154	rscd := headers.ContentDisposition
155	rsce := headers.ContentEncoding
156	rscl := headers.ContentLanguage
157	rsct := headers.ContentType
158
159	if signedVersion >= "2015-02-21" {
160		canonicalizedResource = "/blob" + canonicalizedResource
161	}
162
163	// https://msdn.microsoft.com/en-us/library/azure/dn140255.aspx#Anchor_12
164	if signedVersion >= "2015-04-05" {
165		return fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s", signedPermissions, signedStart, signedExpiry, canonicalizedResource, signedIdentifier, signedIP, protocols, signedVersion, rscc, rscd, rsce, rscl, rsct), nil
166	}
167
168	// reference: http://msdn.microsoft.com/en-us/library/azure/dn140255.aspx
169	if signedVersion >= "2013-08-15" {
170		return fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s", signedPermissions, signedStart, signedExpiry, canonicalizedResource, signedIdentifier, signedVersion, rscc, rscd, rsce, rscl, rsct), nil
171	}
172
173	return "", errors.New("storage: not implemented SAS for versions earlier than 2013-08-15")
174}
175