From 46e20407dc367b3595758fb8f5e5a554eaa47ed1 Mon Sep 17 00:00:00 2001 From: Joel Hendrix Date: Tue, 6 Sep 2022 17:56:24 -0700 Subject: [PATCH] Move SAS-related functionality to the sas package (#19010) * Move SAS-related functionality to the sas package SAS functionality has been moved out of internal/exported to the sas package. All type aliases to the former types have been removed. Removed "SAS" prefix from funcs and types in the sas package. URL parsing has been moved out of internal/exported to the blob package. * fix doc comment * reduced and cleaned up more public surface area * fix doc comment * comment out dead code for linter --- sdk/storage/azblob/appendblob/client.go | 4 +- sdk/storage/azblob/blob/client.go | 28 ++- sdk/storage/azblob/blob/client_test.go | 35 ++- sdk/storage/azblob/blob/constants.go | 7 +- sdk/storage/azblob/blob/examples_test.go | 16 ++ sdk/storage/azblob/blob/models.go | 16 -- sdk/storage/azblob/blob/utils.go | 14 +- sdk/storage/azblob/blockblob/client.go | 4 +- sdk/storage/azblob/blockblob/client_test.go | 14 +- sdk/storage/azblob/common.go | 25 +- sdk/storage/azblob/container/client.go | 11 +- sdk/storage/azblob/container/examples_test.go | 3 +- sdk/storage/azblob/container/models.go | 15 -- sdk/storage/azblob/examples_test.go | 16 -- .../internal/exported/access_conditions.go | 2 + .../exported/shared_key_credential.go | 5 + sdk/storage/azblob/internal/shared/shared.go | 20 ++ sdk/storage/azblob/pageblob/client.go | 4 +- .../account_sas.go => sas/account.go} | 95 ++++---- .../query_params.go} | 218 +++++++++--------- .../query_params_test.go} | 16 +- .../service_sas.go => sas/service.go} | 103 ++++----- .../exported/blob_url.go => sas/url_parts.go} | 49 ++-- .../url_parts_test.go} | 12 +- sdk/storage/azblob/service/client.go | 9 +- sdk/storage/azblob/service/client_test.go | 11 +- sdk/storage/azblob/service/constants.go | 7 - sdk/storage/azblob/service/examples_test.go | 23 +- sdk/storage/azblob/service/models.go | 22 -- 29 files changed, 367 insertions(+), 437 deletions(-) rename sdk/storage/azblob/{internal/exported/account_sas.go => sas/account.go} (58%) rename sdk/storage/azblob/{internal/exported/sas_query_params.go => sas/query_params.go} (52%) rename sdk/storage/azblob/{internal/exported/sas_query_params_test.go => sas/query_params_test.go} (86%) rename sdk/storage/azblob/{internal/exported/service_sas.go => sas/service.go} (72%) rename sdk/storage/azblob/{internal/exported/blob_url.go => sas/url_parts.go} (75%) rename sdk/storage/azblob/{internal/exported/blob_url_test.go => sas/url_parts_test.go} (79%) diff --git a/sdk/storage/azblob/appendblob/client.go b/sdk/storage/azblob/appendblob/client.go index 7ded35697732..524e10d7c3df 100644 --- a/sdk/storage/azblob/appendblob/client.go +++ b/sdk/storage/azblob/appendblob/client.go @@ -98,7 +98,7 @@ func (ab *Client) URL() string { // WithSnapshot creates a new AppendBlobURL object identical to the source but with the specified snapshot timestamp. // Pass "" to remove the snapshot returning a URL to the base blob. func (ab *Client) WithSnapshot(snapshot string) (*Client, error) { - p, err := exported.ParseURL(ab.URL()) + p, err := blob.ParseURL(ab.URL()) if err != nil { return nil, err } @@ -110,7 +110,7 @@ func (ab *Client) WithSnapshot(snapshot string) (*Client, error) { // WithVersionID creates a new AppendBlobURL object identical to the source but with the specified version id. // Pass "" to remove the versionID returning a URL to the base blob. func (ab *Client) WithVersionID(versionID string) (*Client, error) { - p, err := exported.ParseURL(ab.URL()) + p, err := blob.ParseURL(ab.URL()) if err != nil { return nil, err } diff --git a/sdk/storage/azblob/blob/client.go b/sdk/storage/azblob/blob/client.go index c6c5581478c7..b3559d5cd12f 100644 --- a/sdk/storage/azblob/blob/client.go +++ b/sdk/storage/azblob/blob/client.go @@ -22,6 +22,7 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/internal/exported" "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/internal/generated" "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/internal/shared" + "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/sas" ) // ClientOptions contains the optional parameters when creating a Client. @@ -95,7 +96,7 @@ func (b *Client) URL() string { // WithSnapshot creates a new Client object identical to the source but with the specified snapshot timestamp. // Pass "" to remove the snapshot returning a URL to the base blob. func (b *Client) WithSnapshot(snapshot string) (*Client, error) { - p, err := exported.ParseURL(b.URL()) + p, err := ParseURL(b.URL()) if err != nil { return nil, err } @@ -107,7 +108,7 @@ func (b *Client) WithSnapshot(snapshot string) (*Client, error) { // WithVersionID creates a new AppendBlobURL object identical to the source but with the specified version id. // Pass "" to remove the versionID returning a URL to the base blob. func (b *Client) WithVersionID(versionID string) (*Client, error) { - p, err := exported.ParseURL(b.URL()) + p, err := ParseURL(b.URL()) if err != nil { return nil, err } @@ -226,26 +227,29 @@ func (b *Client) CopyFromURL(ctx context.Context, copySource string, options *Co return resp, err } -// GetSASToken is a convenience method for generating a SAS token for the currently pointed at blob. +// GetSASURL is a convenience method for generating a SAS token for the currently pointed at blob. // It can only be used if the credential supplied during creation was a SharedKeyCredential. -func (b *Client) GetSASToken(permissions SASPermissions, start time.Time, expiry time.Time) (string, error) { - urlParts, _ := exported.ParseURL(b.URL()) - - t, err := time.Parse(exported.SnapshotTimeFormat, urlParts.Snapshot) +func (b *Client) GetSASURL(permissions sas.BlobPermissions, start time.Time, expiry time.Time) (string, error) { + if b.sharedKey() == nil { + return "", errors.New("credential is not a SharedKeyCredential. SAS can only be signed with a SharedKeyCredential") + } + urlParts, err := ParseURL(b.URL()) if err != nil { - t = time.Time{} + return "", err } - if b.sharedKey() == nil { - return "", errors.New("credential is not a SharedKeyCredential. SAS can only be signed with a SharedKeyCredential") + t, err := time.Parse(SnapshotTimeFormat, urlParts.Snapshot) + + if err != nil { + t = time.Time{} } - qps, err := exported.BlobSASSignatureValues{ + qps, err := sas.BlobSignatureValues{ ContainerName: urlParts.ContainerName, BlobName: urlParts.BlobName, SnapshotTime: t, - Version: exported.SASVersion, + Version: sas.Version, Permissions: permissions.String(), diff --git a/sdk/storage/azblob/blob/client_test.go b/sdk/storage/azblob/blob/client_test.go index ca6656702ee8..b27174edb59e 100644 --- a/sdk/storage/azblob/blob/client_test.go +++ b/sdk/storage/azblob/blob/client_test.go @@ -20,13 +20,12 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/azcore/streaming" "github.com/Azure/azure-sdk-for-go/sdk/azcore/to" - "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob" "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blob" "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/bloberror" "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blockblob" "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/container" - "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/internal/exported" "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/internal/testcommon" + "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/sas" "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/service" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" @@ -78,7 +77,7 @@ func (s *BlobUnrecordedTestsSuite) TestCreateBlobClient() { blobName := testcommon.GenerateBlobName(testName) bbClient := testcommon.GetBlockBlobClient(blobName, containerClient) - blobURLParts, err := azblob.ParseURL(bbClient.URL()) + blobURLParts, err := blob.ParseURL(bbClient.URL()) _require.Nil(err) _require.Equal(blobURLParts.BlobName, blobName) _require.Equal(blobURLParts.ContainerName, containerName) @@ -108,19 +107,19 @@ func (s *BlobUnrecordedTestsSuite) TestCreateBlobClientWithSnapshotAndSAS() { credential, err := testcommon.GetGenericCredential(testcommon.TestAccountDefault) _require.Nil(err) - sasQueryParams, err := service.SASSignatureValues{ - Protocol: service.SASProtocolHTTPS, + sasQueryParams, err := sas.AccountSignatureValues{ + Protocol: sas.ProtocolHTTPS, ExpiryTime: currentTime, - Permissions: to.Ptr(service.SASPermissions{Read: true, List: true}).String(), - Services: to.Ptr(service.SASServices{Blob: true}).String(), - ResourceTypes: to.Ptr(service.SASResourceTypes{Container: true, Object: true}).String(), + Permissions: to.Ptr(sas.AccountPermissions{Read: true, List: true}).String(), + Services: to.Ptr(sas.AccountServices{Blob: true}).String(), + ResourceTypes: to.Ptr(sas.AccountResourceTypes{Container: true, Object: true}).String(), }.Sign(credential) _require.Nil(err) - parts, err := exported.ParseURL(bbClient.URL()) + parts, err := blob.ParseURL(bbClient.URL()) _require.Nil(err) parts.SAS = sasQueryParams - parts.Snapshot = currentTime.Format(service.SnapshotTimeFormat) + parts.Snapshot = currentTime.Format(blob.SnapshotTimeFormat) blobURLParts := parts.String() // The snapshot format string is taken from the snapshotTimeFormat value in parsing_urls.go. The field is not public, so @@ -150,19 +149,19 @@ func (s *BlobUnrecordedTestsSuite) TestCreateBlobClientWithSnapshotAndSASUsingCo credential, err := testcommon.GetGenericCredential(testcommon.TestAccountDefault) _require.Nil(err) - sasQueryParams, err := service.SASSignatureValues{ - Protocol: service.SASProtocolHTTPS, + sasQueryParams, err := sas.AccountSignatureValues{ + Protocol: sas.ProtocolHTTPS, ExpiryTime: currentTime, - Permissions: to.Ptr(service.SASPermissions{Read: true, List: true}).String(), - Services: to.Ptr(service.SASServices{Blob: true}).String(), - ResourceTypes: to.Ptr(service.SASResourceTypes{Container: true, Object: true}).String(), + Permissions: to.Ptr(sas.AccountPermissions{Read: true, List: true}).String(), + Services: to.Ptr(sas.AccountServices{Blob: true}).String(), + ResourceTypes: to.Ptr(sas.AccountResourceTypes{Container: true, Object: true}).String(), }.Sign(credential) _require.Nil(err) - parts, err := exported.ParseURL(bbClient.URL()) + parts, err := blob.ParseURL(bbClient.URL()) _require.Nil(err) parts.SAS = sasQueryParams - parts.Snapshot = currentTime.Format(service.SnapshotTimeFormat) + parts.Snapshot = currentTime.Format(blob.SnapshotTimeFormat) blobURLParts := parts.String() // The snapshot format string is taken from the snapshotTimeFormat value in parsing_urls.go. The field is not public, so @@ -3146,7 +3145,7 @@ func (s *BlobRecordedTestsSuite) TestBlobClientPartsSASQueryTimes() { "st=" + url.QueryEscape(StartTimesInputs[i]) + "&" + "sv=2019-10-10" - parts, _ := azblob.ParseURL(urlString) + parts, _ := blob.ParseURL(urlString) _require.Equal(parts.Scheme, "https") _require.Equal(parts.Host, "myaccount.blob.core.windows.net") _require.Equal(parts.ContainerName, "mycontainer") diff --git a/sdk/storage/azblob/blob/constants.go b/sdk/storage/azblob/blob/constants.go index 73aa99209989..d0e5d7d825cc 100644 --- a/sdk/storage/azblob/blob/constants.go +++ b/sdk/storage/azblob/blob/constants.go @@ -6,12 +6,15 @@ package blob -import "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/internal/generated" +import ( + "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/internal/exported" + "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/internal/generated" +) const ( CountToEnd = 0 - SnapshotTimeFormat = "2006-01-02T15:04:05.0000000Z07:00" + SnapshotTimeFormat = exported.SnapshotTimeFormat // DefaultDownloadBlockSize is default block size DefaultDownloadBlockSize = int64(4 * 1024 * 1024) // 4MB diff --git a/sdk/storage/azblob/blob/examples_test.go b/sdk/storage/azblob/blob/examples_test.go index 955f448552b2..8e3148420f2d 100644 --- a/sdk/storage/azblob/blob/examples_test.go +++ b/sdk/storage/azblob/blob/examples_test.go @@ -232,3 +232,19 @@ func Example_blob_Client_StartCopyFromURL() { } fmt.Printf("Copy from %s to %s: ID=%s, Status=%s\n", src, blobClient.URL(), copyID, copyStatus) } + +// This example demonstrates splitting a URL into its parts so you can examine and modify the URL in an Azure Storage fluent way. +func ExampleParseURL() { + // Here is an example of a blob snapshot. + u := "https://myaccount.blob.core.windows.net/mycontainter/ReadMe.txt?" + + "snapshot=2011-03-09T01:42:34Z&" + + "sv=2015-02-21&sr=b&st=2111-01-09T01:42:34.936Z&se=2222-03-09T01:42:34.936Z&sp=rw&sip=168.1.5.60-168.1.5.70&" + + "spr=https,http&si=myIdentifier&ss=bf&srt=s&sig=92836758923659283652983562==" + + // Breaking the URL down into it's parts by conversion to URLParts + parts, _ := blob.ParseURL(u) + + // The URLParts allows access to individual portions of a Blob URL + fmt.Printf("Host: %s\nContainerName: %s\nBlobName: %s\nSnapshot: %s\n", parts.Host, parts.ContainerName, parts.BlobName, parts.Snapshot) + fmt.Printf("Version: %s\nResource: %s\nStartTime: %s\nExpiryTime: %s\nPermissions: %s\n", parts.SAS.Version(), parts.SAS.Resource(), parts.SAS.StartTime(), parts.SAS.ExpiryTime(), parts.SAS.Permissions()) +} diff --git a/sdk/storage/azblob/blob/models.go b/sdk/storage/azblob/blob/models.go index 20c275387366..3170f99d9b7f 100644 --- a/sdk/storage/azblob/blob/models.go +++ b/sdk/storage/azblob/blob/models.go @@ -43,25 +43,9 @@ type CpkScopeInfo = generated.CpkScopeInfo // HTTPHeaders contains a group of parameters for the BlobClient.SetHTTPHeaders method. type HTTPHeaders = generated.BlobHTTPHeaders -// SASProtocol indicates the http/https. -type SASProtocol = exported.SASProtocol - -// IPRange represents a SAS IP range's start IP and (optionally) end IP. -type IPRange = exported.IPRange - -// SASQueryParameters object represents the components that make up an Azure Storage SAS' query parameters. -// You parse a map of query parameters into its fields by calling Sign(). You add the components -// to a query parameter map by calling AddToValues(). -// NOTE: Changing any field requires computing a new SAS signature using a XxxSASSignatureValues type. -type SASQueryParameters = exported.SASQueryParameters - // SourceModifiedAccessConditions contains a group of parameters for the BlobClient.StartCopyFromURL method. type SourceModifiedAccessConditions = generated.SourceModifiedAccessConditions -// SASPermissions type simplifies creating the permissions string for an Azure Storage blob SAS. -// Initialize an instance of this type and then call its String method to set BlobSASSignatureValues's Permissions field. -type SASPermissions = exported.BlobSASPermissions - // Tags represent map of blob index tags type Tags = generated.BlobTag diff --git a/sdk/storage/azblob/blob/utils.go b/sdk/storage/azblob/blob/utils.go index fb0bdbf7f07a..7b4c8e248991 100644 --- a/sdk/storage/azblob/blob/utils.go +++ b/sdk/storage/azblob/blob/utils.go @@ -8,9 +8,8 @@ package blob import ( "strings" - "time" - "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/internal/exported" + "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/sas" ) // ObjectReplicationRules struct @@ -69,7 +68,12 @@ func ParseHTTPHeaders(resp GetPropertiesResponse) HTTPHeaders { } } -// ParseSASTimeString try to parse sas time string. -func ParseSASTimeString(val string) (t time.Time, timeFormat string, err error) { - return exported.ParseSASTimeString(val) +// URLParts object represents the components that make up an Azure Storage Container/Blob URL. +// NOTE: Changing any SAS-related field requires computing a new SAS signature. +type URLParts = sas.URLParts + +// ParseURL parses a URL initializing URLParts' fields including any SAS-related & snapshot query parameters. Any other +// query parameters remain in the UnparsedParams field. This method overwrites all fields in the URLParts object. +func ParseURL(u string) (URLParts, error) { + return sas.ParseURL(u) } diff --git a/sdk/storage/azblob/blockblob/client.go b/sdk/storage/azblob/blockblob/client.go index c6d1884c2aff..a4d0a0501bb8 100644 --- a/sdk/storage/azblob/blockblob/client.go +++ b/sdk/storage/azblob/blockblob/client.go @@ -105,7 +105,7 @@ func (bb *Client) BlobClient() *blob.Client { // WithSnapshot creates a new Client object identical to the source but with the specified snapshot timestamp. // Pass "" to remove the snapshot returning a URL to the base blob. func (bb *Client) WithSnapshot(snapshot string) (*Client, error) { - p, err := exported.ParseURL(bb.URL()) + p, err := blob.ParseURL(bb.URL()) if err != nil { return nil, err } @@ -117,7 +117,7 @@ func (bb *Client) WithSnapshot(snapshot string) (*Client, error) { // WithVersionID creates a new AppendBlobURL object identical to the source but with the specified version id. // Pass "" to remove the versionID returning a URL to the base blob. func (bb *Client) WithVersionID(versionID string) (*Client, error) { - p, err := exported.ParseURL(bb.URL()) + p, err := blob.ParseURL(bb.URL()) if err != nil { return nil, err } diff --git a/sdk/storage/azblob/blockblob/client_test.go b/sdk/storage/azblob/blockblob/client_test.go index 7f4c1ea5ae47..1e5458fb4f8d 100644 --- a/sdk/storage/azblob/blockblob/client_test.go +++ b/sdk/storage/azblob/blockblob/client_test.go @@ -20,12 +20,12 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/azcore/streaming" "github.com/Azure/azure-sdk-for-go/sdk/azcore/to" - "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob" "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blob" "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/bloberror" "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blockblob" "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/container" "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/internal/testcommon" + "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/sas" "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/service" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" @@ -1159,16 +1159,16 @@ func (s *BlockBlobUnrecordedTestsSuite) TestSetTierOnCopyBlockBlobFromURL() { if err != nil { s.T().Fatal("Couldn't fetch credential because " + err.Error()) } - sasQueryParams, err := service.SASSignatureValues{ - Protocol: service.SASProtocolHTTPS, + sasQueryParams, err := sas.AccountSignatureValues{ + Protocol: sas.ProtocolHTTPS, ExpiryTime: expiryTime, - Permissions: to.Ptr(service.SASPermissions{Read: true, List: true}).String(), - Services: to.Ptr(service.SASServices{Blob: true}).String(), - ResourceTypes: to.Ptr(service.SASResourceTypes{Container: true, Object: true}).String(), + Permissions: to.Ptr(sas.AccountPermissions{Read: true, List: true}).String(), + Services: to.Ptr(sas.AccountServices{Blob: true}).String(), + ResourceTypes: to.Ptr(sas.AccountResourceTypes{Container: true, Object: true}).String(), }.Sign(credential) _require.Nil(err) - srcBlobParts, _ := azblob.ParseURL(srcBlob.URL()) + srcBlobParts, _ := blob.ParseURL(srcBlob.URL()) srcBlobParts.SAS = sasQueryParams srcBlobURLWithSAS := srcBlobParts.String() diff --git a/sdk/storage/azblob/common.go b/sdk/storage/azblob/common.go index c16e96cdc97a..c037fee806e9 100644 --- a/sdk/storage/azblob/common.go +++ b/sdk/storage/azblob/common.go @@ -8,6 +8,7 @@ package azblob import ( "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/internal/exported" + "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/sas" ) // SharedKeyCredential contains an account's name and its primary or secondary key. @@ -19,30 +20,12 @@ func NewSharedKeyCredential(accountName, accountKey string) (*SharedKeyCredentia return exported.NewSharedKeyCredential(accountName, accountKey) } -// IPEndpointStyleInfo is used for IP endpoint style URL when working with Azure storage emulator. -// Ex: "https://10.132.141.33/accountname/containername" -type IPEndpointStyleInfo = exported.IPEndpointStyleInfo - -// URLParts object represents the components that make up an Azure Storage Container/Blob URL. You parse an -// existing URL into its parts by calling NewBlobURLParts(). You construct a URL from parts by calling URL(). +// URLParts object represents the components that make up an Azure Storage Container/Blob URL. // NOTE: Changing any SAS-related field requires computing a new SAS signature. -type URLParts = exported.URLParts +type URLParts = sas.URLParts // ParseURL parses a URL initializing URLParts' fields including any SAS-related & snapshot query parameters. Any other // query parameters remain in the UnparsedParams field. This method overwrites all fields in the URLParts object. func ParseURL(u string) (URLParts, error) { - return exported.ParseURL(u) + return sas.ParseURL(u) } - -// SASQueryParameters object represents the components that make up an Azure Storage SAS' query parameters. -// You parse a map of query parameters into its fields by calling NewSASQueryParameters(). You add the components -// to a query parameter map by calling AddToValues(). -// NOTE: Changing any field requires computing a new SAS signature using a XxxSASSignatureValues type. -// https://docs.microsoft.com/en-us/rest/api/storageservices/constructing-a-service-sas -type SASQueryParameters = exported.SASQueryParameters - -// SASProtocol indicates the protocol in use for the SAS URL (http/https). -type SASProtocol = exported.SASProtocol - -// IPRange represents a SAS IP range's start IP and (optionally) end IP. -type IPRange = exported.IPRange diff --git a/sdk/storage/azblob/container/client.go b/sdk/storage/azblob/container/client.go index 558779ef3544..c8546f4c7dfe 100644 --- a/sdk/storage/azblob/container/client.go +++ b/sdk/storage/azblob/container/client.go @@ -24,6 +24,7 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/internal/generated" "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/internal/shared" "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/pageblob" + "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/sas" ) // ClientOptions contains the optional parameters when creating a Client. @@ -276,20 +277,20 @@ func (c *Client) NewListBlobsHierarchyPager(delimiter string, o *ListBlobsHierar // GetSASURL is a convenience method for generating a SAS token for the currently pointed at container. // It can only be used if the credential supplied during creation was a SharedKeyCredential. -func (c *Client) GetSASURL(permissions SASPermissions, start time.Time, expiry time.Time) (string, error) { +func (c *Client) GetSASURL(permissions sas.ContainerPermissions, start time.Time, expiry time.Time) (string, error) { if c.sharedKey() == nil { return "", errors.New("SAS can only be signed with a SharedKeyCredential") } - urlParts, err := exported.ParseURL(c.URL()) + urlParts, err := blob.ParseURL(c.URL()) if err != nil { return "", err } // Containers do not have snapshots, nor versions. - qps, err := SASSignatureValues{ - Version: exported.SASVersion, - Protocol: exported.SASProtocolHTTPS, + qps, err := sas.BlobSignatureValues{ + Version: sas.Version, + Protocol: sas.ProtocolHTTPS, ContainerName: urlParts.ContainerName, Permissions: permissions.String(), StartTime: start.UTC(), diff --git a/sdk/storage/azblob/container/examples_test.go b/sdk/storage/azblob/container/examples_test.go index d70659a0aa11..81e783d03010 100644 --- a/sdk/storage/azblob/container/examples_test.go +++ b/sdk/storage/azblob/container/examples_test.go @@ -22,6 +22,7 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/azidentity" "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob" "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/container" + "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/sas" ) func handleError(err error) { @@ -287,7 +288,7 @@ func Example_container_ClientGetSASURL() { containerClient, err := container.NewClient(containerURL, cred, nil) handleError(err) - permission := container.SASPermissions{Read: true} + permission := sas.ContainerPermissions{Read: true} start := time.Now() expiry := start.AddDate(1, 0, 0) sasURL, err := containerClient.GetSASURL(permission, start, expiry) diff --git a/sdk/storage/azblob/container/models.go b/sdk/storage/azblob/container/models.go index 99d4480bf3b5..34ae5652edaf 100644 --- a/sdk/storage/azblob/container/models.go +++ b/sdk/storage/azblob/container/models.go @@ -50,21 +50,6 @@ type AccessPolicyPermission = exported.AccessPolicyPermission // SignedIdentifier - signed identifier type SignedIdentifier = generated.SignedIdentifier -// SASProtocol indicates the http/https. -type SASProtocol = exported.SASProtocol - -// IPRange represents a SAS IP range's start IP and (optionally) end IP. -type IPRange = exported.IPRange - -// SASSignatureValues is used to generate a Shared Access Signature (SAS) for an Azure Storage container or blob. -// For more information, see https://docs.microsoft.com/rest/api/storageservices/constructing-a-service-sas -type SASSignatureValues = exported.BlobSASSignatureValues - -// SASPermissions type simplifies creating the permissions string for an Azure Storage container SAS. -// Initialize an instance of this type and then call its String method to set BlobSASSignatureValues's Permissions field. -// All permissions descriptions can be found here: https://docs.microsoft.com/en-us/rest/api/storageservices/create-service-sas#permissions-for-a-directory-container-or-blob -type SASPermissions = exported.ContainerSASPermissions - // Request Model Declaration ------------------------------------------------------------------------------------------- // CreateOptions contains the optional parameters for the Client.Create method. diff --git a/sdk/storage/azblob/examples_test.go b/sdk/storage/azblob/examples_test.go index 7e09b9c6b5a3..eca36c6bdf17 100644 --- a/sdk/storage/azblob/examples_test.go +++ b/sdk/storage/azblob/examples_test.go @@ -400,22 +400,6 @@ func ExampleResponseError() { handleError(err) } -// This example demonstrates splitting a URL into its parts so you can examine and modify the URL in an Azure Storage fluent way. -func ExampleParseURL() { - // Here is an example of a blob snapshot. - u := "https://myaccount.blob.core.windows.net/mycontainter/ReadMe.txt?" + - "snapshot=2011-03-09T01:42:34Z&" + - "sv=2015-02-21&sr=b&st=2111-01-09T01:42:34.936Z&se=2222-03-09T01:42:34.936Z&sp=rw&sip=168.1.5.60-168.1.5.70&" + - "spr=https,http&si=myIdentifier&ss=bf&srt=s&sig=92836758923659283652983562==" - - // Breaking the URL down into it's parts by conversion to URLParts - parts, _ := azblob.ParseURL(u) - - // The URLParts allows access to individual portions of a Blob URL - fmt.Printf("Host: %s\nContainerName: %s\nBlobName: %s\nSnapshot: %s\n", parts.Host, parts.ContainerName, parts.BlobName, parts.Snapshot) - fmt.Printf("Version: %s\nResource: %s\nStartTime: %s\nExpiryTime: %s\nPermissions: %s\n", parts.SAS.Version(), parts.SAS.Resource(), parts.SAS.StartTime(), parts.SAS.ExpiryTime(), parts.SAS.Permissions()) -} - // This example shows how to perform operations on blob conditionally. func Example_blob_AccessConditions() { accountName, accountKey := os.Getenv("AZURE_STORAGE_ACCOUNT_NAME"), os.Getenv("AZURE_STORAGE_ACCOUNT_KEY") diff --git a/sdk/storage/azblob/internal/exported/access_conditions.go b/sdk/storage/azblob/internal/exported/access_conditions.go index 666f63eb0836..96d188fa5678 100644 --- a/sdk/storage/azblob/internal/exported/access_conditions.go +++ b/sdk/storage/azblob/internal/exported/access_conditions.go @@ -8,6 +8,8 @@ package exported import "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/internal/generated" +const SnapshotTimeFormat = "2006-01-02T15:04:05.0000000Z07:00" + // ContainerAccessConditions identifies container-specific access conditions which you optionally set. type ContainerAccessConditions struct { ModifiedAccessConditions *ModifiedAccessConditions diff --git a/sdk/storage/azblob/internal/exported/shared_key_credential.go b/sdk/storage/azblob/internal/exported/shared_key_credential.go index 12f271ef3f8f..d1563105423f 100644 --- a/sdk/storage/azblob/internal/exported/shared_key_credential.go +++ b/sdk/storage/azblob/internal/exported/shared_key_credential.go @@ -178,6 +178,11 @@ func (c *SharedKeyCredential) buildCanonicalizedResource(u *url.URL) (string, er return cr.String(), nil } +// ComputeHMACSHA256 is a helper for computing the signed string outside of this package. +func ComputeHMACSHA256(cred *SharedKeyCredential, message string) (string, error) { + return cred.computeHMACSHA256(message) +} + // the following content isn't actually exported but must live // next to SharedKeyCredential as it uses its unexported methods diff --git a/sdk/storage/azblob/internal/shared/shared.go b/sdk/storage/azblob/internal/shared/shared.go index 29ac3b262f88..6c06483f1e30 100644 --- a/sdk/storage/azblob/internal/shared/shared.go +++ b/sdk/storage/azblob/internal/shared/shared.go @@ -10,6 +10,7 @@ import ( "errors" "fmt" "io" + "net" "net/url" "strconv" "strings" @@ -256,3 +257,22 @@ func GetClientOptions[T any](o *T) *T { } return o } + +// IsIPEndpointStyle checkes if URL's host is IP, in this case the storage account endpoint will be composed as: +// http(s)://IP(:port)/storageaccount/container/... +// As url's Host property, host could be both host or host:port +func IsIPEndpointStyle(host string) bool { + if host == "" { + return false + } + if h, _, err := net.SplitHostPort(host); err == nil { + host = h + } + // For IPv6, there could be case where SplitHostPort fails for cannot finding port. + // In this case, eliminate the '[' and ']' in the URL. + // For details about IPv6 URL, please refer to https://tools.ietf.org/html/rfc2732 + if host[0] == '[' && host[len(host)-1] == ']' { + host = host[1 : len(host)-1] + } + return net.ParseIP(host) != nil +} diff --git a/sdk/storage/azblob/pageblob/client.go b/sdk/storage/azblob/pageblob/client.go index 8afcb5810dfd..c7da8870aec8 100644 --- a/sdk/storage/azblob/pageblob/client.go +++ b/sdk/storage/azblob/pageblob/client.go @@ -104,7 +104,7 @@ func (pb *Client) sharedKey() *blob.SharedKeyCredential { // WithSnapshot creates a new PageBlobURL object identical to the source but with the specified snapshot timestamp. // Pass "" to remove the snapshot returning a URL to the base blob. func (pb *Client) WithSnapshot(snapshot string) (*Client, error) { - p, err := exported.ParseURL(pb.URL()) + p, err := blob.ParseURL(pb.URL()) if err != nil { return nil, err } @@ -116,7 +116,7 @@ func (pb *Client) WithSnapshot(snapshot string) (*Client, error) { // WithVersionID creates a new PageBlobURL object identical to the source but with the specified snapshot timestamp. // Pass "" to remove the version returning a URL to the base blob. func (pb *Client) WithVersionID(versionID string) (*Client, error) { - p, err := exported.ParseURL(pb.URL()) + p, err := blob.ParseURL(pb.URL()) if err != nil { return nil, err } diff --git a/sdk/storage/azblob/internal/exported/account_sas.go b/sdk/storage/azblob/sas/account.go similarity index 58% rename from sdk/storage/azblob/internal/exported/account_sas.go rename to sdk/storage/azblob/sas/account.go index 2c8cb4873492..dd2dd0d915b7 100644 --- a/sdk/storage/azblob/internal/exported/account_sas.go +++ b/sdk/storage/azblob/sas/account.go @@ -4,7 +4,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. -package exported +package sas import ( "bytes" @@ -12,38 +12,43 @@ import ( "fmt" "strings" "time" + + "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/internal/exported" ) -// AccountSASSignatureValues is used to generate a Shared Access Signature (SAS) for an Azure Storage account. +// SharedKeyCredential contains an account's name and its primary or secondary key. +type SharedKeyCredential = exported.SharedKeyCredential + +// AccountSignatureValues is used to generate a Shared Access Signature (SAS) for an Azure Storage account. // For more information, see https://docs.microsoft.com/rest/api/storageservices/constructing-an-account-sas -type AccountSASSignatureValues struct { - Version string `param:"sv"` // If not specified, this format to SASVersion - Protocol SASProtocol `param:"spr"` // See the SASProtocol* constants - StartTime time.Time `param:"st"` // Not specified if IsZero - ExpiryTime time.Time `param:"se"` // Not specified if IsZero - Permissions string `param:"sp"` // Create by initializing a AccountSASPermissions and then call String() - IPRange IPRange `param:"sip"` - Services string `param:"ss"` // Create by initializing AccountSASServices and then call String() - ResourceTypes string `param:"srt"` // Create by initializing AccountSASResourceTypes and then call String() +type AccountSignatureValues struct { + Version string `param:"sv"` // If not specified, this format to SASVersion + Protocol Protocol `param:"spr"` // See the SASProtocol* constants + StartTime time.Time `param:"st"` // Not specified if IsZero + ExpiryTime time.Time `param:"se"` // Not specified if IsZero + Permissions string `param:"sp"` // Create by initializing a AccountSASPermissions and then call String() + IPRange IPRange `param:"sip"` + Services string `param:"ss"` // Create by initializing AccountSASServices and then call String() + ResourceTypes string `param:"srt"` // Create by initializing AccountSASResourceTypes and then call String() } // Sign uses an account's shared key credential to sign this signature values to produce // the proper SAS query parameters. -func (v AccountSASSignatureValues) Sign(sharedKeyCredential *SharedKeyCredential) (SASQueryParameters, error) { +func (v AccountSignatureValues) Sign(sharedKeyCredential *SharedKeyCredential) (QueryParameters, error) { // https://docs.microsoft.com/en-us/rest/api/storageservices/Constructing-an-Account-SAS if v.ExpiryTime.IsZero() || v.Permissions == "" || v.ResourceTypes == "" || v.Services == "" { - return SASQueryParameters{}, errors.New("account SAS is missing at least one of these: ExpiryTime, Permissions, Service, or ResourceType") + return QueryParameters{}, errors.New("account SAS is missing at least one of these: ExpiryTime, Permissions, Service, or ResourceType") } if v.Version == "" { - v.Version = SASVersion + v.Version = Version } - perms := &AccountSASPermissions{} - if err := perms.Parse(v.Permissions); err != nil { - return SASQueryParameters{}, err + perms, err := parseAccountPermissions(v.Permissions) + if err != nil { + return QueryParameters{}, err } v.Permissions = perms.String() - startTime, expiryTime, _ := FormatTimesForSASSigning(v.StartTime, v.ExpiryTime, time.Time{}) + startTime, expiryTime, _ := formatTimesForSigning(v.StartTime, v.ExpiryTime, time.Time{}) stringToSign := strings.Join([]string{ sharedKeyCredential.AccountName(), @@ -58,11 +63,11 @@ func (v AccountSASSignatureValues) Sign(sharedKeyCredential *SharedKeyCredential ""}, // That is right, the account SAS requires a terminating extra newline "\n") - signature, err := sharedKeyCredential.computeHMACSHA256(stringToSign) + signature, err := exported.ComputeHMACSHA256(sharedKeyCredential, stringToSign) if err != nil { - return SASQueryParameters{}, err + return QueryParameters{}, err } - p := SASQueryParameters{ + p := QueryParameters{ // Common SAS parameters version: v.Version, protocol: v.Protocol, @@ -82,15 +87,15 @@ func (v AccountSASSignatureValues) Sign(sharedKeyCredential *SharedKeyCredential return p, nil } -// AccountSASPermissions type simplifies creating the permissions string for an Azure Storage Account SAS. +// AccountPermissions type simplifies creating the permissions string for an Azure Storage Account SAS. // Initialize an instance of this type and then call its String method to set AccountSASSignatureValues's Permissions field. -type AccountSASPermissions struct { +type AccountPermissions struct { Read, Write, Delete, DeletePreviousVersion, List, Add, Create, Update, Process, Tag, FilterByTags bool } // String produces the SAS permissions string for an Azure Storage account. // Call this method to set AccountSASSignatureValues's Permissions field. -func (p *AccountSASPermissions) String() string { +func (p *AccountPermissions) String() string { var buffer bytes.Buffer if p.Read { buffer.WriteRune('r') @@ -129,8 +134,8 @@ func (p *AccountSASPermissions) String() string { } // Parse initializes the AccountSASPermissions' fields from a string. -func (p *AccountSASPermissions) Parse(s string) error { - *p = AccountSASPermissions{} // Clear out the flags +func parseAccountPermissions(s string) (AccountPermissions, error) { + p := AccountPermissions{} // Clear out the flags for _, r := range s { switch r { case 'r': @@ -156,21 +161,21 @@ func (p *AccountSASPermissions) Parse(s string) error { case 'f': p.FilterByTags = true default: - return fmt.Errorf("invalid permission character: '%v'", r) + return AccountPermissions{}, fmt.Errorf("invalid permission character: '%v'", r) } } - return nil + return p, nil } -// AccountSASServices type simplifies creating the services string for an Azure Storage Account SAS. +// AccountServices type simplifies creating the services string for an Azure Storage Account SAS. // Initialize an instance of this type and then call its String method to set AccountSASSignatureValues's Services field. -type AccountSASServices struct { +type AccountServices struct { Blob, Queue, File bool } // String produces the SAS services string for an Azure Storage account. // Call this method to set AccountSASSignatureValues's Services field. -func (s *AccountSASServices) String() string { +func (s *AccountServices) String() string { var buffer bytes.Buffer if s.Blob { buffer.WriteRune('b') @@ -185,8 +190,8 @@ func (s *AccountSASServices) String() string { } // Parse initializes the AccountSASServices' fields from a string. -func (s *AccountSASServices) Parse(str string) error { - *s = AccountSASServices{} // Clear out the flags +/*func parseAccountServices(str string) (AccountServices, error) { + s := AccountServices{} // Clear out the flags for _, r := range str { switch r { case 'b': @@ -196,21 +201,21 @@ func (s *AccountSASServices) Parse(str string) error { case 'f': s.File = true default: - return fmt.Errorf("invalid service character: '%v'", r) + return AccountServices{}, fmt.Errorf("invalid service character: '%v'", r) } } - return nil -} + return s, nil +}*/ -// AccountSASResourceTypes type simplifies creating the resource types string for an Azure Storage Account SAS. +// AccountResourceTypes type simplifies creating the resource types string for an Azure Storage Account SAS. // Initialize an instance of this type and then call its String method to set AccountSASSignatureValues's ResourceTypes field. -type AccountSASResourceTypes struct { +type AccountResourceTypes struct { Service, Container, Object bool } // String produces the SAS resource types string for an Azure Storage account. // Call this method to set AccountSASSignatureValues's ResourceTypes field. -func (rt *AccountSASResourceTypes) String() string { +func (rt *AccountResourceTypes) String() string { var buffer bytes.Buffer if rt.Service { buffer.WriteRune('s') @@ -224,9 +229,9 @@ func (rt *AccountSASResourceTypes) String() string { return buffer.String() } -// Parse initializes the AccountSASResourceType's fields from a string. -func (rt *AccountSASResourceTypes) Parse(s string) error { - *rt = AccountSASResourceTypes{} // Clear out the flags +// Parse initializes the AccountResourceTypes's fields from a string. +/*func parseAccountResourceTypes(s string) (AccountResourceTypes, error) { + rt := AccountResourceTypes{} // Clear out the flags for _, r := range s { switch r { case 's': @@ -236,8 +241,8 @@ func (rt *AccountSASResourceTypes) Parse(s string) error { case 'o': rt.Object = true default: - return fmt.Errorf("invalid resource type: '%v'", r) + return AccountResourceTypes{}, fmt.Errorf("invalid resource type: '%v'", r) } } - return nil -} + return rt, nil +}*/ diff --git a/sdk/storage/azblob/internal/exported/sas_query_params.go b/sdk/storage/azblob/sas/query_params.go similarity index 52% rename from sdk/storage/azblob/internal/exported/sas_query_params.go rename to sdk/storage/azblob/sas/query_params.go index 433052d793b8..f439b86c30e9 100644 --- a/sdk/storage/azblob/internal/exported/sas_query_params.go +++ b/sdk/storage/azblob/sas/query_params.go @@ -4,7 +4,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. -package exported +package sas import ( "errors" @@ -12,67 +12,69 @@ import ( "net/url" "strings" "time" -) - -var ( - SASVersion = "2019-12-12" - // SASTimeFormats ISO 8601 format. - // Please refer to https://docs.microsoft.com/en-us/rest/api/storageservices/constructing-a-service-sas for more details. - SASTimeFormats = []string{"2006-01-02T15:04:05.0000000Z", SASTimeFormat, "2006-01-02T15:04Z", "2006-01-02"} + "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/internal/exported" ) -// SASTimeFormat represents the format of a SAS start or expiry time. Use it when formatting/parsing a time.Time. -// TODO: do these need to be exported? +// TimeFormat represents the format of a SAS start or expiry time. Use it when formatting/parsing a time.Time. const ( - SASTimeFormat = "2006-01-02T15:04:05Z" // "2017-07-27T00:00:00Z" // ISO 8601 + TimeFormat = "2006-01-02T15:04:05Z" // "2017-07-27T00:00:00Z" // ISO 8601 ) +var ( + // Version is the default version encoded in the SAS token. + Version = "2019-12-12" +) + +// TimeFormats ISO 8601 format. +// Please refer to https://docs.microsoft.com/en-us/rest/api/storageservices/constructing-a-service-sas for more details. +var timeFormats = []string{"2006-01-02T15:04:05.0000000Z", TimeFormat, "2006-01-02T15:04Z", "2006-01-02"} + +// Protocol indicates the http/https. +type Protocol string + const ( - // SASProtocolHTTPS can be specified for a SAS protocol - SASProtocolHTTPS SASProtocol = "https" + // ProtocolHTTPS can be specified for a SAS protocol + ProtocolHTTPS Protocol = "https" - // SASProtocolHTTPSandHTTP can be specified for a SAS protocol - SASProtocolHTTPSandHTTP SASProtocol = "https,http" + // ProtocolHTTPSandHTTP can be specified for a SAS protocol + ProtocolHTTPSandHTTP Protocol = "https,http" ) -// SASProtocol indicates the http/https. -type SASProtocol string - -// FormatTimesForSASSigning converts a time.Time to a snapshotTimeFormat string suitable for a -// SASField's StartTime or ExpiryTime fields. Returns "" if value.IsZero(). -func FormatTimesForSASSigning(startTime, expiryTime, snapshotTime time.Time) (string, string, string) { +// FormatTimesForSigning converts a time.Time to a snapshotTimeFormat string suitable for a +// Field's StartTime or ExpiryTime fields. Returns "" if value.IsZero(). +func formatTimesForSigning(startTime, expiryTime, snapshotTime time.Time) (string, string, string) { ss := "" if !startTime.IsZero() { - ss = formatSASTimeWithDefaultFormat(&startTime) + ss = formatTimeWithDefaultFormat(&startTime) } se := "" if !expiryTime.IsZero() { - se = formatSASTimeWithDefaultFormat(&expiryTime) + se = formatTimeWithDefaultFormat(&expiryTime) } sh := "" if !snapshotTime.IsZero() { - sh = snapshotTime.Format(SnapshotTimeFormat) + sh = snapshotTime.Format(exported.SnapshotTimeFormat) } return ss, se, sh } -// formatSASTimeWithDefaultFormat format time with ISO 8601 in "yyyy-MM-ddTHH:mm:ssZ". -func formatSASTimeWithDefaultFormat(t *time.Time) string { - return formatSASTime(t, SASTimeFormat) // By default, "yyyy-MM-ddTHH:mm:ssZ" is used +// formatTimeWithDefaultFormat format time with ISO 8601 in "yyyy-MM-ddTHH:mm:ssZ". +func formatTimeWithDefaultFormat(t *time.Time) string { + return formatTime(t, TimeFormat) // By default, "yyyy-MM-ddTHH:mm:ssZ" is used } -// formatSASTime format time with given format, use ISO 8601 in "yyyy-MM-ddTHH:mm:ssZ" by default. -func formatSASTime(t *time.Time, format string) string { +// formatTime format time with given format, use ISO 8601 in "yyyy-MM-ddTHH:mm:ssZ" by default. +func formatTime(t *time.Time, format string) string { if format != "" { return t.Format(format) } - return t.Format(SASTimeFormat) // By default, "yyyy-MM-ddTHH:mm:ssZ" is used + return t.Format(TimeFormat) // By default, "yyyy-MM-ddTHH:mm:ssZ" is used } -// ParseSASTimeString try to parse sas time string. -func ParseSASTimeString(val string) (t time.Time, timeFormat string, err error) { - for _, sasTimeFormat := range SASTimeFormats { +// ParseTime try to parse a SAS time string. +func parseTime(val string) (t time.Time, timeFormat string, err error) { + for _, sasTimeFormat := range timeFormats { t, err = time.Parse(sasTimeFormat, val) if err == nil { timeFormat = sasTimeFormat @@ -107,189 +109,184 @@ func (ipr *IPRange) String() string { // https://docs.microsoft.com/en-us/rest/api/storageservices/constructing-a-service-sas -// SASQueryParameters object represents the components that make up an Azure Storage SAS' query parameters. -// You parse a map of query parameters into its fields by calling NewSASQueryParameters(). You add the components +// QueryParameters object represents the components that make up an Azure Storage SAS' query parameters. +// You parse a map of query parameters into its fields by calling NewQueryParameters(). You add the components // to a query parameter map by calling AddToValues(). // NOTE: Changing any field requires computing a new SAS signature using a XxxSASSignatureValues type. // This type defines the components used by all Azure Storage resources (Containers, Blobs, Files, & Queues). -type SASQueryParameters struct { +type QueryParameters struct { // All members are immutable or values so copies of this struct are goroutine-safe. - version string `param:"sv"` - services string `param:"ss"` - resourceTypes string `param:"srt"` - protocol SASProtocol `param:"spr"` - startTime time.Time `param:"st"` - expiryTime time.Time `param:"se"` - snapshotTime time.Time `param:"snapshot"` - ipRange IPRange `param:"sip"` - identifier string `param:"si"` - resource string `param:"sr"` - permissions string `param:"sp"` - signature string `param:"sig"` - cacheControl string `param:"rscc"` - contentDisposition string `param:"rscd"` - contentEncoding string `param:"rsce"` - contentLanguage string `param:"rscl"` - contentType string `param:"rsct"` - signedOID string `param:"skoid"` - signedTID string `param:"sktid"` - signedStart time.Time `param:"skt"` - signedService string `param:"sks"` - signedExpiry time.Time `param:"ske"` - signedVersion string `param:"skv"` - signedDirectoryDepth string `param:"sdd"` - preauthorizedAgentObjectID string `param:"saoid"` - agentObjectID string `param:"suoid"` - correlationID string `param:"scid"` + version string `param:"sv"` + services string `param:"ss"` + resourceTypes string `param:"srt"` + protocol Protocol `param:"spr"` + startTime time.Time `param:"st"` + expiryTime time.Time `param:"se"` + snapshotTime time.Time `param:"snapshot"` + ipRange IPRange `param:"sip"` + identifier string `param:"si"` + resource string `param:"sr"` + permissions string `param:"sp"` + signature string `param:"sig"` + cacheControl string `param:"rscc"` + contentDisposition string `param:"rscd"` + contentEncoding string `param:"rsce"` + contentLanguage string `param:"rscl"` + contentType string `param:"rsct"` + signedOID string `param:"skoid"` + signedTID string `param:"sktid"` + signedStart time.Time `param:"skt"` + signedService string `param:"sks"` + signedExpiry time.Time `param:"ske"` + signedVersion string `param:"skv"` + signedDirectoryDepth string `param:"sdd"` + preauthorizedAgentObjectID string `param:"saoid"` + agentObjectID string `param:"suoid"` + correlationID string `param:"scid"` // private member used for startTime and expiryTime formatting. stTimeFormat string seTimeFormat string } // PreauthorizedAgentObjectID returns preauthorizedAgentObjectID -func (p *SASQueryParameters) PreauthorizedAgentObjectID() string { +func (p *QueryParameters) PreauthorizedAgentObjectID() string { return p.preauthorizedAgentObjectID } // AgentObjectID returns agentObjectID -func (p *SASQueryParameters) AgentObjectID() string { +func (p *QueryParameters) AgentObjectID() string { return p.agentObjectID } // SignedCorrelationID returns signedCorrelationID -func (p *SASQueryParameters) SignedCorrelationID() string { +func (p *QueryParameters) SignedCorrelationID() string { return p.correlationID } // SignedOID returns signedOID -func (p *SASQueryParameters) SignedOID() string { +func (p *QueryParameters) SignedOID() string { return p.signedOID } // SignedTID returns signedTID -func (p *SASQueryParameters) SignedTID() string { +func (p *QueryParameters) SignedTID() string { return p.signedTID } // SignedStart returns signedStart -func (p *SASQueryParameters) SignedStart() time.Time { +func (p *QueryParameters) SignedStart() time.Time { return p.signedStart } // SignedExpiry returns signedExpiry -func (p *SASQueryParameters) SignedExpiry() time.Time { +func (p *QueryParameters) SignedExpiry() time.Time { return p.signedExpiry } // SignedService returns signedService -func (p *SASQueryParameters) SignedService() string { +func (p *QueryParameters) SignedService() string { return p.signedService } // SignedVersion returns signedVersion -func (p *SASQueryParameters) SignedVersion() string { +func (p *QueryParameters) SignedVersion() string { return p.signedVersion } // SnapshotTime returns snapshotTime -func (p *SASQueryParameters) SnapshotTime() time.Time { +func (p *QueryParameters) SnapshotTime() time.Time { return p.snapshotTime } // Version returns version -func (p *SASQueryParameters) Version() string { +func (p *QueryParameters) Version() string { return p.version } // Services returns services -func (p *SASQueryParameters) Services() string { +func (p *QueryParameters) Services() string { return p.services } // ResourceTypes returns resourceTypes -func (p *SASQueryParameters) ResourceTypes() string { +func (p *QueryParameters) ResourceTypes() string { return p.resourceTypes } // Protocol returns protocol -func (p *SASQueryParameters) Protocol() SASProtocol { +func (p *QueryParameters) Protocol() Protocol { return p.protocol } // StartTime returns startTime -func (p *SASQueryParameters) StartTime() time.Time { +func (p *QueryParameters) StartTime() time.Time { return p.startTime } // ExpiryTime returns expiryTime -func (p *SASQueryParameters) ExpiryTime() time.Time { +func (p *QueryParameters) ExpiryTime() time.Time { return p.expiryTime } // IPRange returns ipRange -func (p *SASQueryParameters) IPRange() IPRange { +func (p *QueryParameters) IPRange() IPRange { return p.ipRange } // Identifier returns identifier -func (p *SASQueryParameters) Identifier() string { +func (p *QueryParameters) Identifier() string { return p.identifier } // Resource returns resource -func (p *SASQueryParameters) Resource() string { +func (p *QueryParameters) Resource() string { return p.resource } // Permissions returns permissions -func (p *SASQueryParameters) Permissions() string { +func (p *QueryParameters) Permissions() string { return p.permissions } // Signature returns signature -func (p *SASQueryParameters) Signature() string { +func (p *QueryParameters) Signature() string { return p.signature } // CacheControl returns cacheControl -func (p *SASQueryParameters) CacheControl() string { +func (p *QueryParameters) CacheControl() string { return p.cacheControl } // ContentDisposition returns contentDisposition -func (p *SASQueryParameters) ContentDisposition() string { +func (p *QueryParameters) ContentDisposition() string { return p.contentDisposition } // ContentEncoding returns contentEncoding -func (p *SASQueryParameters) ContentEncoding() string { +func (p *QueryParameters) ContentEncoding() string { return p.contentEncoding } // ContentLanguage returns contentLanguage -func (p *SASQueryParameters) ContentLanguage() string { +func (p *QueryParameters) ContentLanguage() string { return p.contentLanguage } // ContentType returns sontentType -func (p *SASQueryParameters) ContentType() string { +func (p *QueryParameters) ContentType() string { return p.contentType } // SignedDirectoryDepth returns signedDirectoryDepth -func (p *SASQueryParameters) SignedDirectoryDepth() string { +func (p *QueryParameters) SignedDirectoryDepth() string { return p.signedDirectoryDepth } // Encode encodes the SAS query parameters into URL encoded form sorted by key. -func (p *SASQueryParameters) Encode() string { +func (p *QueryParameters) Encode() string { v := url.Values{} - p.addToValues(v) - return v.Encode() -} -// AddToValues adds the SAS components to the specified query parameters map. -func (p *SASQueryParameters) addToValues(v url.Values) url.Values { if p.version != "" { v.Add("sv", p.version) } @@ -303,10 +300,10 @@ func (p *SASQueryParameters) addToValues(v url.Values) url.Values { v.Add("spr", string(p.protocol)) } if !p.startTime.IsZero() { - v.Add("st", formatSASTime(&(p.startTime), p.stTimeFormat)) + v.Add("st", formatTime(&(p.startTime), p.stTimeFormat)) } if !p.expiryTime.IsZero() { - v.Add("se", formatSASTime(&(p.expiryTime), p.seTimeFormat)) + v.Add("se", formatTime(&(p.expiryTime), p.seTimeFormat)) } if len(p.ipRange.Start) > 0 { v.Add("sip", p.ipRange.String()) @@ -323,8 +320,8 @@ func (p *SASQueryParameters) addToValues(v url.Values) url.Values { if p.signedOID != "" { v.Add("skoid", p.signedOID) v.Add("sktid", p.signedTID) - v.Add("skt", p.signedStart.Format(SASTimeFormat)) - v.Add("ske", p.signedExpiry.Format(SASTimeFormat)) + v.Add("skt", p.signedStart.Format(TimeFormat)) + v.Add("ske", p.signedExpiry.Format(TimeFormat)) v.Add("sks", p.signedService) v.Add("skv", p.signedVersion) } @@ -358,15 +355,16 @@ func (p *SASQueryParameters) addToValues(v url.Values) url.Values { if p.correlationID != "" { v.Add("scid", p.correlationID) } - return v + + return v.Encode() } -// NewSASQueryParameters creates and initializes a SASQueryParameters object based on the +// NewQueryParameters creates and initializes a QueryParameters object based on the // query parameter map's passed-in values. If deleteSASParametersFromValues is true, // all SAS-related query parameters are removed from the passed-in map. If // deleteSASParametersFromValues is false, the map passed-in map is unaltered. -func NewSASQueryParameters(values url.Values, deleteSASParametersFromValues bool) SASQueryParameters { - p := SASQueryParameters{} +func NewQueryParameters(values url.Values, deleteSASParametersFromValues bool) QueryParameters { + p := QueryParameters{} for k, v := range values { val := v[0] isSASKey := true @@ -378,13 +376,13 @@ func NewSASQueryParameters(values url.Values, deleteSASParametersFromValues bool case "srt": p.resourceTypes = val case "spr": - p.protocol = SASProtocol(val) + p.protocol = Protocol(val) case "snapshot": - p.snapshotTime, _ = time.Parse(SnapshotTimeFormat, val) + p.snapshotTime, _ = time.Parse(exported.SnapshotTimeFormat, val) case "st": - p.startTime, p.stTimeFormat, _ = ParseSASTimeString(val) + p.startTime, p.stTimeFormat, _ = parseTime(val) case "se": - p.expiryTime, p.seTimeFormat, _ = ParseSASTimeString(val) + p.expiryTime, p.seTimeFormat, _ = parseTime(val) case "sip": dashIndex := strings.Index(val, "-") if dashIndex == -1 { @@ -416,9 +414,9 @@ func NewSASQueryParameters(values url.Values, deleteSASParametersFromValues bool case "sktid": p.signedTID = val case "skt": - p.signedStart, _ = time.Parse(SASTimeFormat, val) + p.signedStart, _ = time.Parse(TimeFormat, val) case "ske": - p.signedExpiry, _ = time.Parse(SASTimeFormat, val) + p.signedExpiry, _ = time.Parse(TimeFormat, val) case "sks": p.signedService = val case "skv": diff --git a/sdk/storage/azblob/internal/exported/sas_query_params_test.go b/sdk/storage/azblob/sas/query_params_test.go similarity index 86% rename from sdk/storage/azblob/internal/exported/sas_query_params_test.go rename to sdk/storage/azblob/sas/query_params_test.go index 9418a0b22a55..e5bca3cefc8d 100644 --- a/sdk/storage/azblob/internal/exported/sas_query_params_test.go +++ b/sdk/storage/azblob/sas/query_params_test.go @@ -4,7 +4,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. -package exported +package sas import ( "fmt" @@ -20,11 +20,11 @@ func TestSAS(t *testing.T) { _url := fmt.Sprintf("https://teststorageaccount.blob.core.windows.net/testcontainer/testpath?%s", sas) _uri, err := url.Parse(_url) require.NoError(t, err) - sasQueryParams := NewSASQueryParameters(_uri.Query(), true) + sasQueryParams := NewQueryParameters(_uri.Query(), true) validateSAS(t, sas, sasQueryParams) } -func validateSAS(t *testing.T, sas string, parameters SASQueryParameters) { +func validateSAS(t *testing.T, sas string, parameters QueryParameters) { sasCompMap := make(map[string]string) for _, sasComp := range strings.Split(sas, "&") { comp := strings.Split(sasComp, "=") @@ -36,18 +36,18 @@ func validateSAS(t *testing.T, sas string, parameters SASQueryParameters) { require.Equal(t, parameters.ResourceTypes(), sasCompMap["srt"]) require.Equal(t, string(parameters.Protocol()), sasCompMap["spr"]) if _, ok := sasCompMap["st"]; ok { - startTime, _, err := ParseSASTimeString(sasCompMap["st"]) + startTime, _, err := parseTime(sasCompMap["st"]) require.NoError(t, err) require.Equal(t, parameters.StartTime(), startTime) } if _, ok := sasCompMap["se"]; ok { - endTime, _, err := ParseSASTimeString(sasCompMap["se"]) + endTime, _, err := parseTime(sasCompMap["se"]) require.NoError(t, err) require.Equal(t, parameters.ExpiryTime(), endTime) } if _, ok := sasCompMap["snapshot"]; ok { - snapshotTime, _, err := ParseSASTimeString(sasCompMap["snapshot"]) + snapshotTime, _, err := parseTime(sasCompMap["snapshot"]) require.NoError(t, err) require.Equal(t, parameters.SnapshotTime(), snapshotTime) } @@ -70,14 +70,14 @@ func validateSAS(t *testing.T, sas string, parameters SASQueryParameters) { require.Equal(t, parameters.SignedTID(), sasCompMap["sktid"]) if _, ok := sasCompMap["skt"]; ok { - signedStart, _, err := ParseSASTimeString(sasCompMap["skt"]) + signedStart, _, err := parseTime(sasCompMap["skt"]) require.NoError(t, err) require.Equal(t, parameters.SignedStart(), signedStart) } require.Equal(t, parameters.SignedService(), sasCompMap["sks"]) if _, ok := sasCompMap["ske"]; ok { - signedExpiry, _, err := ParseSASTimeString(sasCompMap["ske"]) + signedExpiry, _, err := parseTime(sasCompMap["ske"]) require.NoError(t, err) require.Equal(t, parameters.SignedExpiry(), signedExpiry) } diff --git a/sdk/storage/azblob/internal/exported/service_sas.go b/sdk/storage/azblob/sas/service.go similarity index 72% rename from sdk/storage/azblob/internal/exported/service_sas.go rename to sdk/storage/azblob/sas/service.go index c436e22d9cc0..b6dd2090ff44 100644 --- a/sdk/storage/azblob/internal/exported/service_sas.go +++ b/sdk/storage/azblob/sas/service.go @@ -4,22 +4,24 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -package exported +package sas import ( "bytes" "fmt" "strings" "time" + + "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/internal/exported" ) -// BlobSASSignatureValues is used to generate a Shared Access Signature (SAS) for an Azure Storage container or blob. +// BlobSignatureValues is used to generate a Shared Access Signature (SAS) for an Azure Storage container or blob. // For more information, see https://docs.microsoft.com/rest/api/storageservices/constructing-a-service-sas -type BlobSASSignatureValues struct { - Version string `param:"sv"` // If not specified, this format to SASVersion - Protocol SASProtocol `param:"spr"` // See the SASProtocol* constants - StartTime time.Time `param:"st"` // Not specified if IsZero - ExpiryTime time.Time `param:"se"` // Not specified if IsZero +type BlobSignatureValues struct { + Version string `param:"sv"` // If not specified, this defaults to Version + Protocol Protocol `param:"spr"` // See the Protocol* constants + StartTime time.Time `param:"st"` // Not specified if IsZero + ExpiryTime time.Time `param:"se"` // Not specified if IsZero SnapshotTime time.Time Permissions string `param:"sp"` // Create by initializing a ContainerSASPermissions or BlobSASPermissions and then call String() IPRange IPRange `param:"sip"` @@ -47,56 +49,36 @@ func getDirectoryDepth(path string) string { // Sign uses an account's StorageAccountCredential to sign this signature values to produce the proper SAS query parameters. // See: StorageAccountCredential. Compatible with both UserDelegationCredential and SharedKeyCredential -func (v BlobSASSignatureValues) Sign(sharedKeyCredential *SharedKeyCredential) (SASQueryParameters, error) { - resource := "c" +func (v BlobSignatureValues) Sign(sharedKeyCredential *SharedKeyCredential) (QueryParameters, error) { if sharedKeyCredential == nil { - return SASQueryParameters{}, fmt.Errorf("cannot sign SAS query without Shared Key Credential") + return QueryParameters{}, fmt.Errorf("cannot sign SAS query without Shared Key Credential") + } + + //Make sure the permission characters are in the correct order + perms, err := parseBlobPermissions(v.Permissions) + if err != nil { + return QueryParameters{}, err } + v.Permissions = perms.String() + resource := "c" if !v.SnapshotTime.IsZero() { resource = "bs" - //Make sure the permission characters are in the correct order - perms := &BlobSASPermissions{} - if err := perms.Parse(v.Permissions); err != nil { - return SASQueryParameters{}, err - } - v.Permissions = perms.String() } else if v.BlobVersion != "" { resource = "bv" - //Make sure the permission characters are in the correct order - perms := &BlobSASPermissions{} - if err := perms.Parse(v.Permissions); err != nil { - return SASQueryParameters{}, err - } - v.Permissions = perms.String() } else if v.Directory != "" { resource = "d" v.BlobName = "" - perms := &BlobSASPermissions{} - if err := perms.Parse(v.Permissions); err != nil { - return SASQueryParameters{}, err - } - v.Permissions = perms.String() } else if v.BlobName == "" { - // Make sure the permission characters are in the correct order - perms := &ContainerSASPermissions{} - if err := perms.Parse(v.Permissions); err != nil { - return SASQueryParameters{}, err - } - v.Permissions = perms.String() + // do nothing } else { resource = "b" - // Make sure the permission characters are in the correct order - perms := &BlobSASPermissions{} - if err := perms.Parse(v.Permissions); err != nil { - return SASQueryParameters{}, err - } - v.Permissions = perms.String() } + if v.Version == "" { - v.Version = SASVersion + v.Version = Version } - startTime, expiryTime, snapshotTime := FormatTimesForSASSigning(v.StartTime, v.ExpiryTime, v.SnapshotTime) + startTime, expiryTime, snapshotTime := formatTimesForSigning(v.StartTime, v.ExpiryTime, v.SnapshotTime) signedIdentifier := v.Identifier @@ -138,13 +120,12 @@ func (v BlobSASSignatureValues) Sign(sharedKeyCredential *SharedKeyCredential) ( v.ContentType}, // rsct "\n") - signature := "" - signature, err := sharedKeyCredential.computeHMACSHA256(stringToSign) + signature, err := exported.ComputeHMACSHA256(sharedKeyCredential, stringToSign) if err != nil { - return SASQueryParameters{}, err + return QueryParameters{}, err } - p := SASQueryParameters{ + p := QueryParameters{ // Common SAS parameters version: v.Version, protocol: v.Protocol, @@ -196,17 +177,17 @@ func getCanonicalName(account string, containerName string, blobName string, dir return strings.Join(elements, "") } -// ContainerSASPermissions type simplifies creating the permissions string for an Azure Storage container SAS. +// ContainerPermissions type simplifies creating the permissions string for an Azure Storage container SAS. // Initialize an instance of this type and then call its String method to set BlobSASSignatureValues's Permissions field. // All permissions descriptions can be found here: https://docs.microsoft.com/en-us/rest/api/storageservices/create-service-sas#permissions-for-a-directory-container-or-blob -type ContainerSASPermissions struct { +type ContainerPermissions struct { Read, Add, Create, Write, Delete, DeletePreviousVersion, List, Tag bool Execute, ModifyOwnership, ModifyPermissions bool // Hierarchical Namespace only } // String produces the SAS permissions string for an Azure Storage container. // Call this method to set BlobSASSignatureValues's Permissions field. -func (p *ContainerSASPermissions) String() string { +func (p *ContainerPermissions) String() string { var b bytes.Buffer if p.Read { b.WriteRune('r') @@ -245,8 +226,8 @@ func (p *ContainerSASPermissions) String() string { } // Parse initializes the ContainerSASPermissions' fields from a string. -func (p *ContainerSASPermissions) Parse(s string) error { - *p = ContainerSASPermissions{} // Clear the flags +/*func parseContainerPermissions(s string) (ContainerPermissions, error) { + p := ContainerPermissions{} // Clear the flags for _, r := range s { switch r { case 'r': @@ -272,21 +253,21 @@ func (p *ContainerSASPermissions) Parse(s string) error { case 'p': p.ModifyPermissions = true default: - return fmt.Errorf("invalid permission: '%v'", r) + return ContainerPermissions{}, fmt.Errorf("invalid permission: '%v'", r) } } - return nil -} + return p, nil +}*/ -// BlobSASPermissions type simplifies creating the permissions string for an Azure Storage blob SAS. +// BlobPermissions type simplifies creating the permissions string for an Azure Storage blob SAS. // Initialize an instance of this type and then call its String method to set BlobSASSignatureValues's Permissions field. -type BlobSASPermissions struct { +type BlobPermissions struct { Read, Add, Create, Write, Delete, DeletePreviousVersion, Tag, List, Move, Execute, Ownership, Permissions bool } // String produces the SAS permissions string for an Azure Storage blob. -// Call this method to set BlobSASSignatureValues's Permissions field. -func (p *BlobSASPermissions) String() string { +// Call this method to set BlobSignatureValues's Permissions field. +func (p *BlobPermissions) String() string { var b bytes.Buffer if p.Read { b.WriteRune('r') @@ -328,8 +309,8 @@ func (p *BlobSASPermissions) String() string { } // Parse initializes the BlobSASPermissions's fields from a string. -func (p *BlobSASPermissions) Parse(s string) error { - *p = BlobSASPermissions{} // Clear the flags +func parseBlobPermissions(s string) (BlobPermissions, error) { + p := BlobPermissions{} // Clear the flags for _, r := range s { switch r { case 'r': @@ -357,8 +338,8 @@ func (p *BlobSASPermissions) Parse(s string) error { case 'p': p.Permissions = true default: - return fmt.Errorf("invalid permission: '%v'", r) + return BlobPermissions{}, fmt.Errorf("invalid permission: '%v'", r) } } - return nil + return p, nil } diff --git a/sdk/storage/azblob/internal/exported/blob_url.go b/sdk/storage/azblob/sas/url_parts.go similarity index 75% rename from sdk/storage/azblob/internal/exported/blob_url.go rename to sdk/storage/azblob/sas/url_parts.go index da122de16576..57fe053f07ae 100644 --- a/sdk/storage/azblob/internal/exported/blob_url.go +++ b/sdk/storage/azblob/sas/url_parts.go @@ -4,18 +4,19 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. -package exported +package sas import ( - "net" "net/url" "strings" + + "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/internal/exported" + "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/internal/shared" ) const ( - snapshot = "snapshot" - versionId = "versionid" - SnapshotTimeFormat = "2006-01-02T15:04:05.0000000Z07:00" + snapshot = "snapshot" + versionId = "versionid" ) // IPEndpointStyleInfo is used for IP endpoint style URL when working with Azure storage emulator. @@ -24,8 +25,7 @@ type IPEndpointStyleInfo struct { AccountName string // "" if not using IP endpoint style } -// URLParts object represents the components that make up an Azure Storage Container/Blob URL. You parse an -// existing URL into its parts by calling NewBlobURLParts(). You construct a URL from parts by calling URL(). +// URLParts object represents the components that make up an Azure Storage Container/Blob URL. // NOTE: Changing any SAS-related field requires computing a new SAS signature. type URLParts struct { Scheme string // Ex: "https://" @@ -34,13 +34,13 @@ type URLParts struct { ContainerName string // "" if no container BlobName string // "" if no blob Snapshot string // "" if not a snapshot - SAS SASQueryParameters + SAS QueryParameters UnparsedParams string VersionID string // "" if not versioning enabled } -// ParseURL parses a URL initializing URLParts' fields including any SAS-related & snapshot query parameters. Any other -// query parameters remain in the UnparsedParams field. This method overwrites all fields in the URLParts object. +// ParseURL parses a URL initializing URLParts' fields including any SAS-related & snapshot query parameters. +// Any other query parameters remain in the UnparsedParams field. func ParseURL(u string) (URLParts, error) { uri, err := url.Parse(u) if err != nil { @@ -58,7 +58,7 @@ func ParseURL(u string) (URLParts, error) { if path[0] == '/' { path = path[1:] // If path starts with a slash, remove it } - if IsIPEndpointStyle(up.Host) { + if shared.IsIPEndpointStyle(up.Host) { if accountEndIndex := strings.Index(path, "/"); accountEndIndex == -1 { // Slash not found; path has account name & no container name or blob up.IPEndpointStyleInfo.AccountName = path path = "" // No ContainerName present in the URL so path should be empty @@ -95,7 +95,7 @@ func ParseURL(u string) (URLParts, error) { delete(paramsMap, "versionId") // delete "versionId" from paramsMap } - up.SAS = NewSASQueryParameters(paramsMap, true) + up.SAS = NewQueryParameters(paramsMap, true) up.UnparsedParams = paramsMap.Encode() return up, nil } @@ -104,7 +104,7 @@ func ParseURL(u string) (URLParts, error) { // field contains the SAS, snapshot, and unparsed query parameters. func (up URLParts) String() string { path := "" - if IsIPEndpointStyle(up.Host) && up.IPEndpointStyleInfo.AccountName != "" { + if shared.IsIPEndpointStyle(up.Host) && up.IPEndpointStyleInfo.AccountName != "" { path += "/" + up.IPEndpointStyleInfo.AccountName } // Concatenate container & blob names (if they exist) @@ -118,8 +118,8 @@ func (up URLParts) String() string { rawQuery := up.UnparsedParams //If no snapshot is initially provided, fill it in from the SAS query properties to help the user - if up.Snapshot == "" && !up.SAS.snapshotTime.IsZero() { - up.Snapshot = up.SAS.snapshotTime.Format(SnapshotTimeFormat) + if up.Snapshot == "" && !up.SAS.SnapshotTime().IsZero() { + up.Snapshot = up.SAS.SnapshotTime().Format(exported.SnapshotTimeFormat) } // Concatenate blob version id query parameter (if it exists) @@ -153,25 +153,6 @@ func (up URLParts) String() string { return u.String() } -// IsIPEndpointStyle checkes if URL's host is IP, in this case the storage account endpoint will be composed as: -// http(s)://IP(:port)/storageaccount/container/... -// As url's Host property, host could be both host or host:port -func IsIPEndpointStyle(host string) bool { - if host == "" { - return false - } - if h, _, err := net.SplitHostPort(host); err == nil { - host = h - } - // For IPv6, there could be case where SplitHostPort fails for cannot finding port. - // In this case, eliminate the '[' and ']' in the URL. - // For details about IPv6 URL, please refer to https://tools.ietf.org/html/rfc2732 - if host[0] == '[' && host[len(host)-1] == ']' { - host = host[1 : len(host)-1] - } - return net.ParseIP(host) != nil -} - type caseInsensitiveValues url.Values // map[string][]string func (values caseInsensitiveValues) Get(key string) ([]string, bool) { diff --git a/sdk/storage/azblob/internal/exported/blob_url_test.go b/sdk/storage/azblob/sas/url_parts_test.go similarity index 79% rename from sdk/storage/azblob/internal/exported/blob_url_test.go rename to sdk/storage/azblob/sas/url_parts_test.go index 213572d62556..31ada7e40bd6 100644 --- a/sdk/storage/azblob/internal/exported/blob_url_test.go +++ b/sdk/storage/azblob/sas/url_parts_test.go @@ -4,7 +4,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. -package exported +package sas import ( "fmt" @@ -19,11 +19,11 @@ func TestParseURL(t *testing.T) { testContainer := "fakecontainer" fileNames := []string{"/._.TESTT.txt", "/.gitignore/dummyfile1"} - sas := "sv=2019-12-12&sr=b&st=2111-01-09T01:42:34.936Z&se=2222-03-09T01:42:34.936Z&sp=rw&sip=168.1.5.60-168.1.5.70&spr=https,http&si=myIdentifier&ss=bf&srt=s&sig=clNxbtnkKSHw7f3KMEVVc4agaszoRFdbZr%2FWBmPNsrw%3D" + const sasStr = "sv=2019-12-12&sr=b&st=2111-01-09T01:42:34.936Z&se=2222-03-09T01:42:34.936Z&sp=rw&sip=168.1.5.60-168.1.5.70&spr=https,http&si=myIdentifier&ss=bf&srt=s&sig=clNxbtnkKSHw7f3KMEVVc4agaszoRFdbZr%2FWBmPNsrw%3D" for _, fileName := range fileNames { snapshotID, versionID := "", "2021-10-25T05:41:32.5526810Z" - sasWithVersionID := "?versionId=" + versionID + "&" + sas + sasWithVersionID := "?versionId=" + versionID + "&" + sasStr urlWithVersion := fmt.Sprintf("https://%s.blob.core.windows.net/%s%s%s", testStorageAccount, testContainer, fileName, sasWithVersionID) blobURLParts, err := ParseURL(urlWithVersion) require.NoError(t, err) @@ -34,12 +34,12 @@ func TestParseURL(t *testing.T) { require.Equal(t, blobURLParts.VersionID, versionID) require.Equal(t, blobURLParts.Snapshot, snapshotID) - validateSAS(t, sas, blobURLParts.SAS) + validateSAS(t, sasStr, blobURLParts.SAS) } for _, fileName := range fileNames { snapshotID, versionID := "2011-03-09T01:42:34Z", "" - sasWithSnapshotID := "?snapshot=" + snapshotID + "&" + sas + sasWithSnapshotID := "?snapshot=" + snapshotID + "&" + sasStr urlWithVersion := fmt.Sprintf("https://%s.blob.core.windows.net/%s%s%s", testStorageAccount, testContainer, fileName, sasWithSnapshotID) blobURLParts, err := ParseURL(urlWithVersion) require.NoError(t, err) @@ -50,7 +50,7 @@ func TestParseURL(t *testing.T) { require.Equal(t, blobURLParts.VersionID, versionID) require.Equal(t, blobURLParts.Snapshot, snapshotID) - validateSAS(t, sas, blobURLParts.SAS) + validateSAS(t, sasStr, blobURLParts.SAS) } //urlWithIP := "https://127.0.0.1:5000/" diff --git a/sdk/storage/azblob/service/client.go b/sdk/storage/azblob/service/client.go index ef4d7a1af458..892140193783 100644 --- a/sdk/storage/azblob/service/client.go +++ b/sdk/storage/azblob/service/client.go @@ -21,6 +21,7 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/internal/exported" "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/internal/generated" "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/internal/shared" + "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/sas" ) // ClientOptions contains the optional parameters when creating a Client. @@ -213,14 +214,14 @@ func (s *Client) GetStatistics(ctx context.Context, o *GetStatisticsOptions) (Ge // GetSASURL is a convenience method for generating a SAS token for the currently pointed at account. // It can only be used if the credential supplied during creation was a SharedKeyCredential. // This validity can be checked with CanGetAccountSASToken(). -func (s *Client) GetSASURL(resources SASResourceTypes, permissions SASPermissions, services SASServices, start time.Time, expiry time.Time) (string, error) { +func (s *Client) GetSASURL(resources sas.AccountResourceTypes, permissions sas.AccountPermissions, services sas.AccountServices, start time.Time, expiry time.Time) (string, error) { if s.sharedKey() == nil { return "", errors.New("SAS can only be signed with a SharedKeyCredential") } - qps, err := SASSignatureValues{ - Version: exported.SASVersion, - Protocol: exported.SASProtocolHTTPS, + qps, err := sas.AccountSignatureValues{ + Version: sas.Version, + Protocol: sas.ProtocolHTTPS, Permissions: permissions.String(), Services: services.String(), ResourceTypes: resources.String(), diff --git a/sdk/storage/azblob/service/client_test.go b/sdk/storage/azblob/service/client_test.go index 9cd1907cccc7..670a256d6d69 100644 --- a/sdk/storage/azblob/service/client_test.go +++ b/sdk/storage/azblob/service/client_test.go @@ -15,6 +15,7 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/internal/shared" "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/internal/testcommon" + "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/sas" "github.com/Azure/azure-sdk-for-go/sdk/azcore/to" "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob" @@ -497,12 +498,12 @@ func (s *ServiceUnrecordedTestsSuite) TestSASServiceClient() { containerName := testcommon.GenerateContainerName(testName) - resources := service.SASResourceTypes{ + resources := sas.AccountResourceTypes{ Object: true, Service: true, Container: true, } - permissions := service.SASPermissions{ + permissions := sas.AccountPermissions{ Read: true, Add: true, Write: true, @@ -510,7 +511,7 @@ func (s *ServiceUnrecordedTestsSuite) TestSASServiceClient() { Update: true, Delete: true, } - services := service.SASServices{ + services := sas.AccountServices{ Blob: true, } start := time.Now().Add(-time.Hour) @@ -543,7 +544,7 @@ func (s *ServiceUnrecordedTestsSuite) TestSASContainerClient() { containerName := testcommon.GenerateContainerName(testName) containerClient := serviceClient.NewContainerClient(containerName) - permissions := container.SASPermissions{ + permissions := sas.ContainerPermissions{ Read: true, Add: true, } @@ -575,7 +576,7 @@ func (s *ServiceUnrecordedTestsSuite) TestSASContainerClient2() { containerName := testcommon.GenerateContainerName(testName) containerClient := serviceClient.NewContainerClient(containerName) - sasUrlReadAdd, err := containerClient.GetSASURL(container.SASPermissions{Read: true, Add: true}, + sasUrlReadAdd, err := containerClient.GetSASURL(sas.ContainerPermissions{Read: true, Add: true}, time.Now().Add(-5*time.Minute).UTC(), time.Now().Add(time.Hour)) _require.Nil(err) _, err = containerClient.Create(context.Background(), &container.CreateOptions{Metadata: testcommon.BasicMetadata}) diff --git a/sdk/storage/azblob/service/constants.go b/sdk/storage/azblob/service/constants.go index 40b887761d03..20665fc2b79c 100644 --- a/sdk/storage/azblob/service/constants.go +++ b/sdk/storage/azblob/service/constants.go @@ -7,16 +7,9 @@ package service import ( - "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/internal/exported" "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/internal/generated" ) -const ( - SASProtocolHTTPS = exported.SASProtocolHTTPS - - SnapshotTimeFormat = "2006-01-02T15:04:05.0000000Z07:00" -) - const ( // ContainerNameRoot is the special Azure Storage name used to identify a storage account's root container. ContainerNameRoot = "$root" diff --git a/sdk/storage/azblob/service/examples_test.go b/sdk/storage/azblob/service/examples_test.go index 9246d68b2dab..8f8fb4dea7dd 100644 --- a/sdk/storage/azblob/service/examples_test.go +++ b/sdk/storage/azblob/service/examples_test.go @@ -16,7 +16,9 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/azcore/to" "github.com/Azure/azure-sdk-for-go/sdk/azidentity" "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob" + "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blob" "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/container" + "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/sas" "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/service" ) @@ -164,11 +166,11 @@ func Example_service_Client_GetSASURL() { serviceClient, err := service.NewClientWithSharedKeyCredential("https://.blob.core.windows.net", cred, nil) handleError(err) - resources := service.SASResourceTypes{Service: true} - permission := service.SASPermissions{Read: true} + resources := sas.AccountResourceTypes{Service: true} + permission := sas.AccountPermissions{Read: true} start := time.Now() expiry := start.AddDate(1, 0, 0) - sasURL, err := serviceClient.GetSASURL(resources, permission, service.SASServices{Blob: true}, start, expiry) + sasURL, err := serviceClient.GetSASURL(resources, permission, sas.AccountServices{Blob: true}, start, expiry) handleError(err) serviceURL := fmt.Sprintf("https://.blob.core.windows.net/?%s", sasURL) @@ -225,23 +227,22 @@ func Example_service_SASSignatureValues_Sign() { credential, err := azblob.NewSharedKeyCredential(accountName, accountKey) handleError(err) - sasQueryParams, err := service.SASSignatureValues{ - Protocol: service.SASProtocolHTTPS, + sasQueryParams, err := sas.AccountSignatureValues{ + Protocol: sas.ProtocolHTTPS, ExpiryTime: time.Now().UTC().Add(48 * time.Hour), - Permissions: to.Ptr(service.SASPermissions{Read: true, List: true}).String(), - Services: to.Ptr(service.SASServices{Blob: true}).String(), - ResourceTypes: to.Ptr(service.SASResourceTypes{Container: true, Object: true}).String(), + Permissions: to.Ptr(sas.AccountPermissions{Read: true, List: true}).String(), + Services: to.Ptr(sas.AccountServices{Blob: true}).String(), + ResourceTypes: to.Ptr(sas.AccountResourceTypes{Container: true, Object: true}).String(), }.Sign(credential) handleError(err) - queryParams := sasQueryParams.Encode() - sasURL := fmt.Sprintf("https://%s.blob.core.windows.net/?%s", accountName, queryParams) + sasURL := fmt.Sprintf("https://%s.blob.core.windows.net/?%s", accountName, sasQueryParams.Encode()) // This URL can be used to authenticate requests now serviceClient, err := service.NewClientWithNoCredential(sasURL, nil) handleError(err) // You can also break a blob URL up into it's constituent parts - blobURLParts, _ := azblob.ParseURL(serviceClient.URL()) + blobURLParts, _ := blob.ParseURL(serviceClient.URL()) fmt.Printf("SAS expiry time = %s\n", blobURLParts.SAS.ExpiryTime()) } diff --git a/sdk/storage/azblob/service/models.go b/sdk/storage/azblob/service/models.go index f4b1c4799ffc..48982c832228 100644 --- a/sdk/storage/azblob/service/models.go +++ b/sdk/storage/azblob/service/models.go @@ -60,28 +60,6 @@ type StorageServiceProperties = generated.StorageServiceProperties // StorageServiceStats - Stats for the storage service. type StorageServiceStats = generated.StorageServiceStats -// SASResourceTypes type simplifies creating the resource types string for an Azure Storage Account SAS. -// Initialize an instance of this type and then call its String method to set AccountSASSignatureValues's ResourceTypes field. -type SASResourceTypes = exported.AccountSASResourceTypes - -// SASServices type simplifies creating the services string for an Azure Storage Account SAS. -// Initialize an instance of this type and then call its String method to set SASServices' Services field. -type SASServices = exported.AccountSASServices - -// SASPermissions type simplifies creating the permissions string for an Azure Storage Account SAS. -// Initialize an instance of this type and then call its String method to set AccountSASSignatureValues' Permissions field. -type SASPermissions = exported.AccountSASPermissions - -// SASProtocol indicates the http/https. -type SASProtocol = exported.SASProtocol - -// IPRange represents a SAS IP range's start IP and (optionally) end IP. -type IPRange = exported.IPRange - -// SASSignatureValues is used to generate a Shared Access Signature (SAS) for an Azure Storage account. -// For more information, see https://docs.microsoft.com/rest/api/storageservices/constructing-an-account-sas -type SASSignatureValues = exported.AccountSASSignatureValues - // --------------------------------------------------------------------------------------------------------------------- // GetAccountInfoOptions provides set of options for Client.GetAccountInfo