diff --git a/sdk/storage/azblob/internal/shared/shared.go b/sdk/storage/azblob/internal/shared/shared.go index 2244ae5efd86..02936ad0362b 100644 --- a/sdk/storage/azblob/internal/shared/shared.go +++ b/sdk/storage/azblob/internal/shared/shared.go @@ -87,22 +87,6 @@ func ParseConnectionString(connectionString string) (ParsedConnectionString, err connStrMap[parts[0]] = parts[1] } - accountName, ok := connStrMap["AccountName"] - if !ok { - return ParsedConnectionString{}, errors.New("connection string missing AccountName") - } - - accountKey, ok := connStrMap["AccountKey"] - if !ok { - sharedAccessSignature, ok := connStrMap["SharedAccessSignature"] - if !ok { - return ParsedConnectionString{}, errors.New("connection string missing AccountKey and SharedAccessSignature") - } - return ParsedConnectionString{ - ServiceURL: fmt.Sprintf("%v://%v.blob.%v/?%v", defaultScheme, accountName, defaultSuffix, sharedAccessSignature), - }, nil - } - protocol, ok := connStrMap["DefaultEndpointsProtocol"] if !ok { protocol = defaultScheme @@ -113,19 +97,40 @@ func ParseConnectionString(connectionString string) (ParsedConnectionString, err suffix = defaultSuffix } - if blobEndpoint, ok := connStrMap["BlobEndpoint"]; ok { + blobEndpoint, has_blobEndpoint := connStrMap["BlobEndpoint"] + accountName, has_accountName := connStrMap["AccountName"] + + var serviceURL string + if has_blobEndpoint { + serviceURL = blobEndpoint + } else if has_accountName { + serviceURL = fmt.Sprintf("%v://%v.blob.%v", protocol, accountName, suffix) + } else { + return ParsedConnectionString{}, errors.New("connection string needs either AccountName or BlobEndpoint") + } + + if !strings.HasSuffix(serviceURL, "/") { + // add a trailing slash to be consistent with the portal + serviceURL += "/" + } + + accountKey, has_accountKey := connStrMap["AccountKey"] + sharedAccessSignature, has_sharedAccessSignature := connStrMap["SharedAccessSignature"] + + if has_accountName && has_accountKey { return ParsedConnectionString{ - ServiceURL: blobEndpoint, + ServiceURL: serviceURL, AccountName: accountName, AccountKey: accountKey, }, nil + } else if has_sharedAccessSignature { + return ParsedConnectionString{ + ServiceURL: fmt.Sprintf("%v?%v", serviceURL, sharedAccessSignature), + }, nil + } else { + return ParsedConnectionString{}, errors.New("connection string needs either AccountKey or SharedAccessSignature") } - return ParsedConnectionString{ - ServiceURL: fmt.Sprintf("%v://%v.blob.%v", protocol, accountName, suffix), - AccountName: accountName, - AccountKey: accountKey, - }, nil } // SerializeBlobTags converts tags to generated.BlobTags diff --git a/sdk/storage/azblob/internal/shared/shared_test.go b/sdk/storage/azblob/internal/shared/shared_test.go index ea7f39cf2a6e..fba1919f95b8 100644 --- a/sdk/storage/azblob/internal/shared/shared_test.go +++ b/sdk/storage/azblob/internal/shared/shared_test.go @@ -37,7 +37,7 @@ func TestParseConnectionString(t *testing.T) { connStr := "DefaultEndpointsProtocol=https;AccountName=dummyaccount;AccountKey=secretkeykey;EndpointSuffix=core.windows.net" parsed, err := ParseConnectionString(connStr) require.NoError(t, err) - require.Equal(t, "https://dummyaccount.blob.core.windows.net", parsed.ServiceURL) + require.Equal(t, "https://dummyaccount.blob.core.windows.net/", parsed.ServiceURL) require.Equal(t, "dummyaccount", parsed.AccountName) require.Equal(t, "secretkeykey", parsed.AccountKey) } @@ -46,7 +46,16 @@ func TestParseConnectionStringHTTP(t *testing.T) { connStr := "DefaultEndpointsProtocol=http;AccountName=dummyaccount;AccountKey=secretkeykey;EndpointSuffix=core.windows.net" parsed, err := ParseConnectionString(connStr) require.NoError(t, err) - require.Equal(t, "http://dummyaccount.blob.core.windows.net", parsed.ServiceURL) + require.Equal(t, "http://dummyaccount.blob.core.windows.net/", parsed.ServiceURL) + require.Equal(t, "dummyaccount", parsed.AccountName) + require.Equal(t, "secretkeykey", parsed.AccountKey) +} + +func TestParseConnectionStringSuffixTrailingSlash(t *testing.T) { + connStr := "DefaultEndpointsProtocol=https;AccountName=dummyaccount;AccountKey=secretkeykey;EndpointSuffix=core.windows.net/" + parsed, err := ParseConnectionString(connStr) + require.NoError(t, err) + require.Equal(t, "https://dummyaccount.blob.core.windows.net/", parsed.ServiceURL) require.Equal(t, "dummyaccount", parsed.AccountName) require.Equal(t, "secretkeykey", parsed.AccountKey) } @@ -55,7 +64,7 @@ func TestParseConnectionStringBasic(t *testing.T) { connStr := "AccountName=dummyaccount;AccountKey=secretkeykey" parsed, err := ParseConnectionString(connStr) require.NoError(t, err) - require.Equal(t, "https://dummyaccount.blob.core.windows.net", parsed.ServiceURL) + require.Equal(t, "https://dummyaccount.blob.core.windows.net/", parsed.ServiceURL) require.Equal(t, "dummyaccount", parsed.AccountName) require.Equal(t, "secretkeykey", parsed.AccountKey) } @@ -64,7 +73,7 @@ func TestParseConnectionStringCustomDomain(t *testing.T) { connStr := "AccountName=dummyaccount;AccountKey=secretkeykey;BlobEndpoint=www.mydomain.com;" parsed, err := ParseConnectionString(connStr) require.NoError(t, err) - require.Equal(t, "www.mydomain.com", parsed.ServiceURL) + require.Equal(t, "www.mydomain.com/", parsed.ServiceURL) require.Equal(t, "dummyaccount", parsed.AccountName) require.Equal(t, "secretkeykey", parsed.AccountKey) } @@ -78,20 +87,56 @@ func TestParseConnectionStringSAS(t *testing.T) { require.Empty(t, parsed.AccountKey) } +func TestParseConnectionStringSASAndEndpointAndAccountName(t *testing.T) { + connStr := "AccountName=devstoreaccount1;SharedAccessSignature=fakesharedaccesssignature;BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1;" + parsed, err := ParseConnectionString(connStr) + require.NoError(t, err) + require.Equal(t, "http://127.0.0.1:10000/devstoreaccount1/?fakesharedaccesssignature", parsed.ServiceURL) + require.Empty(t, parsed.AccountName) + require.Empty(t, parsed.AccountKey) +} + +func TestParseConnectionStringSASSuffixTrailingSlash(t *testing.T) { + connStr := "AccountName=dummyaccount;SharedAccessSignature=fakesharedaccesssignature;EndpointSuffix=core.windows.net/" + parsed, err := ParseConnectionString(connStr) + require.NoError(t, err) + require.Equal(t, "https://dummyaccount.blob.core.windows.net/?fakesharedaccesssignature", parsed.ServiceURL) + require.Empty(t, parsed.AccountName) + require.Empty(t, parsed.AccountKey) +} + +func TestParseConnectionStringSASAndEndpoint(t *testing.T) { + connStr := "SharedAccessSignature=fakesharedaccesssignature;BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1;" + parsed, err := ParseConnectionString(connStr) + require.NoError(t, err) + require.Equal(t, "http://127.0.0.1:10000/devstoreaccount1/?fakesharedaccesssignature", parsed.ServiceURL) + require.Empty(t, parsed.AccountName) + require.Empty(t, parsed.AccountKey) +} + +func TestParseConnectionStringSASAndEndpointTrailingSlash(t *testing.T) { + connStr := "SharedAccessSignature=fakesharedaccesssignature;BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1/;" + parsed, err := ParseConnectionString(connStr) + require.NoError(t, err) + require.Equal(t, "http://127.0.0.1:10000/devstoreaccount1/?fakesharedaccesssignature", parsed.ServiceURL) + require.Empty(t, parsed.AccountName) + require.Empty(t, parsed.AccountKey) +} + func TestParseConnectionStringChinaCloud(t *testing.T) { connStr := "AccountName=dummyaccountname;AccountKey=secretkeykey;DefaultEndpointsProtocol=http;EndpointSuffix=core.chinacloudapi.cn;" parsed, err := ParseConnectionString(connStr) require.NoError(t, err) - require.Equal(t, "http://dummyaccountname.blob.core.chinacloudapi.cn", parsed.ServiceURL) + require.Equal(t, "http://dummyaccountname.blob.core.chinacloudapi.cn/", parsed.ServiceURL) require.Equal(t, "dummyaccountname", parsed.AccountName) require.Equal(t, "secretkeykey", parsed.AccountKey) } -func TestCParseConnectionStringAzurite(t *testing.T) { +func TestParseConnectionStringAzurite(t *testing.T) { connStr := "DefaultEndpointsProtocol=http;AccountName=dummyaccountname;AccountKey=secretkeykey;BlobEndpoint=http://local-machine:11002/custom/account/path/faketokensignature;" parsed, err := ParseConnectionString(connStr) require.NoError(t, err) - require.Equal(t, "http://local-machine:11002/custom/account/path/faketokensignature", parsed.ServiceURL) + require.Equal(t, "http://local-machine:11002/custom/account/path/faketokensignature/", parsed.ServiceURL) require.Equal(t, "dummyaccountname", parsed.AccountName) require.Equal(t, "secretkeykey", parsed.AccountKey) } diff --git a/sdk/storage/azqueue/internal/shared/shared.go b/sdk/storage/azqueue/internal/shared/shared.go index e634fec6fbc6..27ca12cd6ea8 100644 --- a/sdk/storage/azqueue/internal/shared/shared.go +++ b/sdk/storage/azqueue/internal/shared/shared.go @@ -78,22 +78,6 @@ func ParseConnectionString(connectionString string) (ParsedConnectionString, err connStrMap[parts[0]] = parts[1] } - accountName, ok := connStrMap["AccountName"] - if !ok { - return ParsedConnectionString{}, errors.New("connection string missing AccountName") - } - - accountKey, ok := connStrMap["AccountKey"] - if !ok { - sharedAccessSignature, ok := connStrMap["SharedAccessSignature"] - if !ok { - return ParsedConnectionString{}, errors.New("connection string missing AccountKey and SharedAccessSignature") - } - return ParsedConnectionString{ - ServiceURL: fmt.Sprintf("%v://%v.queue.%v/?%v", defaultScheme, accountName, defaultSuffix, sharedAccessSignature), - }, nil - } - protocol, ok := connStrMap["DefaultEndpointsProtocol"] if !ok { protocol = defaultScheme @@ -104,19 +88,39 @@ func ParseConnectionString(connectionString string) (ParsedConnectionString, err suffix = defaultSuffix } - if queueEndpoint, ok := connStrMap["QueueEndpoint"]; ok { + queueEndpoint, has_queueEndpoint := connStrMap["QueueEndpoint"] + accountName, has_accountName := connStrMap["AccountName"] + + var serviceURL string + if has_queueEndpoint { + serviceURL = queueEndpoint + } else if has_accountName { + serviceURL = fmt.Sprintf("%v://%v.queue.%v", protocol, accountName, suffix) + } else { + return ParsedConnectionString{}, errors.New("connection string needs either AccountName or QueueEndpoint") + } + + if !strings.HasSuffix(serviceURL, "/") { + // add a trailing slash to be consistent with the portal + serviceURL += "/" + } + + accountKey, has_accountKey := connStrMap["AccountKey"] + sharedAccessSignature, has_sharedAccessSignature := connStrMap["SharedAccessSignature"] + + if has_accountName && has_accountKey { return ParsedConnectionString{ - ServiceURL: queueEndpoint, + ServiceURL: serviceURL, AccountName: accountName, AccountKey: accountKey, }, nil + } else if has_sharedAccessSignature { + return ParsedConnectionString{ + ServiceURL: fmt.Sprintf("%v?%v", serviceURL, sharedAccessSignature), + }, nil + } else { + return ParsedConnectionString{}, errors.New("connection string needs either AccountKey or SharedAccessSignature") } - - return ParsedConnectionString{ - ServiceURL: fmt.Sprintf("%v://%v.queue.%v", protocol, accountName, suffix), - AccountName: accountName, - AccountKey: accountKey, - }, nil } func GetClientOptions[T any](o *T) *T { diff --git a/sdk/storage/azqueue/internal/shared/shared_test.go b/sdk/storage/azqueue/internal/shared/shared_test.go new file mode 100644 index 000000000000..0d88a5a9f287 --- /dev/null +++ b/sdk/storage/azqueue/internal/shared/shared_test.go @@ -0,0 +1,141 @@ +//go:build go1.18 +// +build go1.18 + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +package shared + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestParseConnectionStringInvalid(t *testing.T) { + badConnectionStrings := []string{ + "", + "foobar", + "foo;bar;baz", + "foo=;bar=;", + "=", + ";", + "=;==", + "foobar=baz=foo", + } + + for _, badConnStr := range badConnectionStrings { + parsed, err := ParseConnectionString(badConnStr) + require.Error(t, err) + require.Zero(t, parsed) + //require.Contains(t, err.Error(), errConnectionString.Error()) + } +} + +func TestParseConnectionString(t *testing.T) { + connStr := "DefaultEndpointsProtocol=https;AccountName=dummyaccount;AccountKey=secretkeykey;EndpointSuffix=core.windows.net" + parsed, err := ParseConnectionString(connStr) + require.NoError(t, err) + require.Equal(t, "https://dummyaccount.queue.core.windows.net/", parsed.ServiceURL) + require.Equal(t, "dummyaccount", parsed.AccountName) + require.Equal(t, "secretkeykey", parsed.AccountKey) +} + +func TestParseConnectionStringHTTP(t *testing.T) { + connStr := "DefaultEndpointsProtocol=http;AccountName=dummyaccount;AccountKey=secretkeykey;EndpointSuffix=core.windows.net" + parsed, err := ParseConnectionString(connStr) + require.NoError(t, err) + require.Equal(t, "http://dummyaccount.queue.core.windows.net/", parsed.ServiceURL) + require.Equal(t, "dummyaccount", parsed.AccountName) + require.Equal(t, "secretkeykey", parsed.AccountKey) +} + +func TestParseConnectionStringSuffixTrailingSlash(t *testing.T) { + connStr := "DefaultEndpointsProtocol=https;AccountName=dummyaccount;AccountKey=secretkeykey;EndpointSuffix=core.windows.net/" + parsed, err := ParseConnectionString(connStr) + require.NoError(t, err) + require.Equal(t, "https://dummyaccount.queue.core.windows.net/", parsed.ServiceURL) + require.Equal(t, "dummyaccount", parsed.AccountName) + require.Equal(t, "secretkeykey", parsed.AccountKey) +} + +func TestParseConnectionStringBasic(t *testing.T) { + connStr := "AccountName=dummyaccount;AccountKey=secretkeykey" + parsed, err := ParseConnectionString(connStr) + require.NoError(t, err) + require.Equal(t, "https://dummyaccount.queue.core.windows.net/", parsed.ServiceURL) + require.Equal(t, "dummyaccount", parsed.AccountName) + require.Equal(t, "secretkeykey", parsed.AccountKey) +} + +func TestParseConnectionStringCustomDomain(t *testing.T) { + connStr := "AccountName=dummyaccount;AccountKey=secretkeykey;QueueEndpoint=www.mydomain.com;" + parsed, err := ParseConnectionString(connStr) + require.NoError(t, err) + require.Equal(t, "www.mydomain.com/", parsed.ServiceURL) + require.Equal(t, "dummyaccount", parsed.AccountName) + require.Equal(t, "secretkeykey", parsed.AccountKey) +} + +func TestParseConnectionStringSAS(t *testing.T) { + connStr := "AccountName=dummyaccount;SharedAccessSignature=fakesharedaccesssignature;" + parsed, err := ParseConnectionString(connStr) + require.NoError(t, err) + require.Equal(t, "https://dummyaccount.queue.core.windows.net/?fakesharedaccesssignature", parsed.ServiceURL) + require.Empty(t, parsed.AccountName) + require.Empty(t, parsed.AccountKey) +} + +func TestParseConnectionStringSASAndEndpointAndCustomDomain(t *testing.T) { + connStr := "AccountName=devstoreaccount1;SharedAccessSignature=fakesharedaccesssignature;QueueEndpoint=http://127.0.0.1:10000/devstoreaccount1;" + parsed, err := ParseConnectionString(connStr) + require.NoError(t, err) + require.Equal(t, "http://127.0.0.1:10000/devstoreaccount1/?fakesharedaccesssignature", parsed.ServiceURL) + require.Empty(t, parsed.AccountName) + require.Empty(t, parsed.AccountKey) +} + +func TestParseConnectionStringSASSuffixTrailingSlash(t *testing.T) { + connStr := "AccountName=dummyaccount;SharedAccessSignature=fakesharedaccesssignature;EndpointSuffix=core.windows.net/" + parsed, err := ParseConnectionString(connStr) + require.NoError(t, err) + require.Equal(t, "https://dummyaccount.queue.core.windows.net/?fakesharedaccesssignature", parsed.ServiceURL) + require.Empty(t, parsed.AccountName) + require.Empty(t, parsed.AccountKey) +} + +func TestParseConnectionStringSASAndEndpoint(t *testing.T) { + connStr := "SharedAccessSignature=fakesharedaccesssignature;QueueEndpoint=http://127.0.0.1:10000/devstoreaccount1;" + parsed, err := ParseConnectionString(connStr) + require.NoError(t, err) + require.Equal(t, "http://127.0.0.1:10000/devstoreaccount1/?fakesharedaccesssignature", parsed.ServiceURL) + require.Empty(t, parsed.AccountName) + require.Empty(t, parsed.AccountKey) +} + +func TestParseConnectionStringSASAndEndpointTrailingSlash(t *testing.T) { + connStr := "SharedAccessSignature=fakesharedaccesssignature;QueueEndpoint=http://127.0.0.1:10000/devstoreaccount1/;" + parsed, err := ParseConnectionString(connStr) + require.NoError(t, err) + require.Equal(t, "http://127.0.0.1:10000/devstoreaccount1/?fakesharedaccesssignature", parsed.ServiceURL) + require.Empty(t, parsed.AccountName) + require.Empty(t, parsed.AccountKey) +} + +func TestParseConnectionStringChinaCloud(t *testing.T) { + connStr := "AccountName=dummyaccountname;AccountKey=secretkeykey;DefaultEndpointsProtocol=http;EndpointSuffix=core.chinacloudapi.cn;" + parsed, err := ParseConnectionString(connStr) + require.NoError(t, err) + require.Equal(t, "http://dummyaccountname.queue.core.chinacloudapi.cn/", parsed.ServiceURL) + require.Equal(t, "dummyaccountname", parsed.AccountName) + require.Equal(t, "secretkeykey", parsed.AccountKey) +} + +func TestParseConnectionStringAzurite(t *testing.T) { + connStr := "DefaultEndpointsProtocol=http;AccountName=dummyaccountname;AccountKey=secretkeykey;QueueEndpoint=http://local-machine:11002/custom/account/path/faketokensignature;" + parsed, err := ParseConnectionString(connStr) + require.NoError(t, err) + require.Equal(t, "http://local-machine:11002/custom/account/path/faketokensignature/", parsed.ServiceURL) + require.Equal(t, "dummyaccountname", parsed.AccountName) + require.Equal(t, "secretkeykey", parsed.AccountKey) +}