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