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