diff --git a/sdk/storage/inc/blobs/blob_sas_builder.hpp b/sdk/storage/inc/blobs/blob_sas_builder.hpp index 37f91f7f7..0f3bef386 100644 --- a/sdk/storage/inc/blobs/blob_sas_builder.hpp +++ b/sdk/storage/inc/blobs/blob_sas_builder.hpp @@ -42,7 +42,7 @@ namespace Azure { namespace Storage { namespace Blobs { /** * @brief The list of permissions that can be set for a blob container's access policy. - */ + */ enum class BlobContainerSasPermissions { /** @@ -191,7 +191,7 @@ namespace Azure { namespace Storage { namespace Blobs { * @brief Optionally specify the time at which the shared access signature becomes * valid. */ - std::string StartsOn; + Azure::Core::Nullable StartsOn; /** * @brief The time at which the shared access signature becomes invalid. This field must @@ -220,7 +220,7 @@ namespace Azure { namespace Storage { namespace Blobs { /** * @brief The name of the blob being made accessible, or empty for a container SAS.. - */ + */ std::string BlobName; /** @@ -237,7 +237,7 @@ namespace Azure { namespace Storage { namespace Blobs { /** * @brief Specifies which resources are accessible via the shared access signature. - */ + */ BlobSasResource Resource; /** @@ -267,7 +267,7 @@ namespace Azure { namespace Storage { namespace Blobs { /** * @brief Sets the permissions for the blob container SAS. - * + * * @param * permissions The allowed permissions. */ @@ -275,7 +275,7 @@ namespace Azure { namespace Storage { namespace Blobs { /** * @brief Sets the permissions for the blob SAS. - * + * * @param permissions The * allowed permissions. */ @@ -284,7 +284,7 @@ namespace Azure { namespace Storage { namespace Blobs { /** * @brief Uses the SharedKeyCredential to sign this shared access signature, to produce * the proper SAS query parameters for authentication requests. - * + * * @param credential * The storage account's shared key credential. * @return The SAS query parameters used for @@ -295,7 +295,7 @@ namespace Azure { namespace Storage { namespace Blobs { /** * @brief Uses an account's user delegation key to sign this shared access signature, to * produce the proper SAS query parameters for authentication requests. - * + * * @param * credential UserDelegationKey retruned from BlobServiceClient.GetUserDelegationKey. * @param accountName The name of the storage account. diff --git a/sdk/storage/inc/common/account_sas_builder.hpp b/sdk/storage/inc/common/account_sas_builder.hpp index 1c37e506a..7dea6e5fb 100644 --- a/sdk/storage/inc/common/account_sas_builder.hpp +++ b/sdk/storage/inc/common/account_sas_builder.hpp @@ -28,6 +28,11 @@ namespace Azure { namespace Storage { HttpsOnly, }; + inline std::string SasProtocolToString(SasProtocol protocol) + { + return protocol == SasProtocol::HttpsAndHtttp ? "https,http" : "https"; + } + /** * @brief Specifies the resource types accessible from an account level shared access * signature. diff --git a/sdk/storage/inc/common/storage_uri_builder.hpp b/sdk/storage/inc/common/storage_uri_builder.hpp index 78d5d261e..c14bc2b88 100644 --- a/sdk/storage/inc/common/storage_uri_builder.hpp +++ b/sdk/storage/inc/common/storage_uri_builder.hpp @@ -42,7 +42,7 @@ namespace Azure { namespace Storage { } // query must be encoded - void SetQuery(const std::string& query); + void AppendQueries(const std::string& query); void AppendQuery(const std::string& key, const std::string& value, bool do_encoding = false) { diff --git a/sdk/storage/src/blobs/blob_sas_builder.cpp b/sdk/storage/src/blobs/blob_sas_builder.cpp index cbdf26a88..8e8d93b94 100644 --- a/sdk/storage/src/blobs/blob_sas_builder.cpp +++ b/sdk/storage/src/blobs/blob_sas_builder.cpp @@ -7,6 +7,32 @@ namespace Azure { namespace Storage { namespace Blobs { + namespace { + std::string BlobSasResourceToString(BlobSasResource resource) + { + if (resource == BlobSasResource::Container) + { + return "c"; + } + else if (resource == BlobSasResource::Blob) + { + return "b"; + } + else if (resource == BlobSasResource::BlobSnapshot) + { + return "bs"; + } + else if (resource == BlobSasResource::BlobVersion) + { + return "bv"; + } + else + { + throw std::runtime_error("unknown BlobSasResource value"); + } + } + } // namespace + void BlobSasBuilder::SetPermissions(BlobContainerSasPermissions permissions) { Permissions.clear(); @@ -87,37 +113,14 @@ namespace Azure { namespace Storage { namespace Blobs { { canonicalName += "/" + BlobName; } - std::string protocol; - if (Protocol == SasProtocol::HttpsAndHtttp) - { - protocol = "https,http"; - } - else - { - protocol = "https"; - } - std::string resource; - if (Resource == BlobSasResource::Container) - { - resource = "c"; - } - else if (Resource == BlobSasResource::Blob) - { - resource = "b"; - } - else if (Resource == BlobSasResource::BlobSnapshot) - { - resource = "bs"; - } - else if (Resource == BlobSasResource::BlobVersion) - { - resource = "bv"; - } - std::string stringToSign = Permissions + "\n" + StartsOn + "\n" + ExpiresOn + "\n" - + canonicalName + "\n" + Identifier + "\n" + (IPRange.HasValue() ? IPRange.GetValue() : "") - + "\n" + protocol + "\n" + Version + "\n" + resource + "\n" + Snapshot + "\n" + CacheControl - + "\n" + ContentDisposition + "\n" + ContentEncoding + "\n" + ContentLanguage + "\n" - + ContentType; + std::string protocol = SasProtocolToString(Protocol); + std::string resource = BlobSasResourceToString(Resource); + + std::string stringToSign = Permissions + "\n" + (StartsOn.HasValue() ? StartsOn.GetValue() : "") + + "\n" + ExpiresOn + "\n" + canonicalName + "\n" + Identifier + "\n" + + (IPRange.HasValue() ? IPRange.GetValue() : "") + "\n" + protocol + "\n" + Version + "\n" + + resource + "\n" + Snapshot + "\n" + CacheControl + "\n" + ContentDisposition + "\n" + + ContentEncoding + "\n" + ContentLanguage + "\n" + ContentType; std::string signature = Base64Encode(HMAC_SHA256(stringToSign, Base64Decode(credential.GetAccountKey()))); @@ -125,7 +128,10 @@ namespace Azure { namespace Storage { namespace Blobs { UriBuilder builder; builder.AppendQuery("sv", Version); builder.AppendQuery("spr", protocol); - builder.AppendQuery("st", StartsOn); + if (StartsOn.HasValue()) + { + builder.AppendQuery("st", StartsOn.GetValue()); + } builder.AppendQuery("se", ExpiresOn); if (IPRange.HasValue()) { @@ -166,8 +172,69 @@ namespace Azure { namespace Storage { namespace Blobs { const UserDelegationKey& userDelegationKey, const std::string& accountName) { - unused(userDelegationKey, accountName); - return std::string(); + std::string canonicalName = "/blob/" + accountName + "/" + ContainerName; + if (Resource == BlobSasResource::Blob || Resource == BlobSasResource::BlobSnapshot) + { + canonicalName += "/" + BlobName; + } + std::string protocol = SasProtocolToString(Protocol); + std::string resource = BlobSasResourceToString(Resource); + + std::string stringToSign = Permissions + "\n" + (StartsOn.HasValue() ? StartsOn.GetValue() : "") + + "\n" + ExpiresOn + "\n" + canonicalName + "\n" + userDelegationKey.SignedObjectId + "\n" + + userDelegationKey.SignedTenantId + "\n" + userDelegationKey.SignedStartsOn + "\n" + + userDelegationKey.SignedExpiresOn + "\n" + userDelegationKey.SignedService + "\n" + + userDelegationKey.SignedVersion + "\n" + (IPRange.HasValue() ? IPRange.GetValue() : "") + + "\n" + protocol + "\n" + Version + "\n" + resource + "\n" + Snapshot + "\n" + CacheControl + + "\n" + ContentDisposition + "\n" + ContentEncoding + "\n" + ContentLanguage + "\n" + + ContentType; + + std::string signature + = Base64Encode(HMAC_SHA256(stringToSign, Base64Decode(userDelegationKey.Value))); + + UriBuilder builder; + builder.AppendQuery("sv", Version); + builder.AppendQuery("sr", resource); + if (StartsOn.HasValue()) + { + builder.AppendQuery("st", StartsOn.GetValue()); + } + builder.AppendQuery("se", ExpiresOn); + builder.AppendQuery("sp", Permissions); + if (IPRange.HasValue()) + { + builder.AppendQuery("sip", IPRange.GetValue()); + } + builder.AppendQuery("spr", protocol); + builder.AppendQuery("skoid", userDelegationKey.SignedObjectId); + builder.AppendQuery("sktid", userDelegationKey.SignedTenantId); + builder.AppendQuery("skt", userDelegationKey.SignedStartsOn); + builder.AppendQuery("ske", userDelegationKey.SignedExpiresOn); + builder.AppendQuery("sks", userDelegationKey.SignedService); + builder.AppendQuery("skv", userDelegationKey.SignedVersion); + if (!CacheControl.empty()) + { + builder.AppendQuery("rscc", CacheControl); + } + if (!ContentDisposition.empty()) + { + builder.AppendQuery("rscd", ContentDisposition); + } + if (!ContentEncoding.empty()) + { + builder.AppendQuery("rsce", ContentEncoding); + } + if (!ContentLanguage.empty()) + { + builder.AppendQuery("rscl", ContentLanguage); + } + if (!ContentType.empty()) + { + builder.AppendQuery("rsct", ContentType); + } + builder.AppendQuery("sig", signature, true); + + return builder.ToString(); } }}} // namespace Azure::Storage::Blobs diff --git a/sdk/storage/src/common/storage_credential.cpp b/sdk/storage/src/common/storage_credential.cpp index 3b86a7401..2043d3ba2 100644 --- a/sdk/storage/src/common/storage_credential.cpp +++ b/sdk/storage/src/common/storage_credential.cpp @@ -96,10 +96,10 @@ namespace Azure { namespace Storage { namespace Details { std::string sas = getWithDefault(connectionStringMap, "SharedAccessSignature"); if (!sas.empty()) { - connectionStringParts.BlobServiceUri.SetQuery(sas); - connectionStringParts.DataLakeServiceUri.SetQuery(sas); - connectionStringParts.FileServiceUri.SetQuery(sas); - connectionStringParts.QueueServiceUri.SetQuery(sas); + connectionStringParts.BlobServiceUri.AppendQueries(sas); + connectionStringParts.DataLakeServiceUri.AppendQueries(sas); + connectionStringParts.FileServiceUri.AppendQueries(sas); + connectionStringParts.QueueServiceUri.AppendQueries(sas); } return connectionStringParts; diff --git a/sdk/storage/src/common/storage_uri_builder.cpp b/sdk/storage/src/common/storage_uri_builder.cpp index bcd3fd86f..1a7a38ff7 100644 --- a/sdk/storage/src/common/storage_uri_builder.cpp +++ b/sdk/storage/src/common/storage_uri_builder.cpp @@ -47,7 +47,7 @@ namespace Azure { namespace Storage { if (pos != url.end() && *pos == '?') { auto queryIter = std::find(pos + 1, url.end(), '#'); - SetQuery(std::string(pos + 1, queryIter)); + AppendQueries(std::string(pos + 1, queryIter)); pos = queryIter; } @@ -152,10 +152,8 @@ namespace Azure { namespace Storage { return encoded; } - void UriBuilder::SetQuery(const std::string& query) + void UriBuilder::AppendQueries(const std::string& query) { - m_query.clear(); - std::string::const_iterator cur = query.begin(); if (cur != query.end() && *cur == '?') { diff --git a/sdk/storage/test/blobs/blob_sas_test.cpp b/sdk/storage/test/blobs/blob_sas_test.cpp index 1981599c0..cd61d1242 100644 --- a/sdk/storage/test/blobs/blob_sas_test.cpp +++ b/sdk/storage/test/blobs/blob_sas_test.cpp @@ -33,6 +33,7 @@ namespace Azure { namespace Storage { namespace Test { auto keyCredential = Details::ParseConnectionString(StandardStorageConnectionString()).KeyCredential; + auto accountName = keyCredential->AccountName; auto blobServiceClient0 = Blobs::BlobServiceClient::CreateFromConnectionString(StandardStorageConnectionString()); auto blobContainerClient0 = blobServiceClient0.GetBlobContainerClient(m_containerName); @@ -42,6 +43,14 @@ namespace Azure { namespace Storage { namespace Test { auto containerUri = blobContainerClient0.GetUri(); auto blobUri = blobClient0.GetUri(); + auto blobServiceClient1 = Blobs::BlobServiceClient( + serviceUri, + std::make_shared( + AadTenantId(), AadClientId(), AadClientSecret())); + auto userDelegationKey = *blobServiceClient1.GetUserDelegationKey( + ToISO8601(std::chrono::system_clock::now() - std::chrono::minutes(5)), + ToISO8601(std::chrono::system_clock::now() + std::chrono::minutes(60))); + auto verify_blob_read = [&](const std::string& sas) { EXPECT_NO_THROW(blobClient0.Create()); auto blobClient = Blobs::AppendBlobClient(blobUri + sas); @@ -172,42 +181,50 @@ namespace Azure { namespace Storage { namespace Test { { blobSasBuilder.SetPermissions(permissions); auto sasToken = blobSasBuilder.ToSasQueryParameters(*keyCredential); + auto sasToken2 = blobSasBuilder.ToSasQueryParameters(userDelegationKey, accountName); if ((permissions & Blobs::BlobSasPermissions::Read) == Blobs::BlobSasPermissions::Read) { verify_blob_read(sasToken); + verify_blob_read(sasToken2); } if ((permissions & Blobs::BlobSasPermissions::Write) == Blobs::BlobSasPermissions::Write) { verify_blob_write(sasToken); + verify_blob_write(sasToken2); } if ((permissions & Blobs::BlobSasPermissions::Delete) == Blobs::BlobSasPermissions::Delete) { verify_blob_delete(sasToken); + verify_blob_delete(sasToken2); } if ((permissions & Blobs::BlobSasPermissions::Add) == Blobs::BlobSasPermissions::Add) { verify_blob_add(sasToken); + verify_blob_add(sasToken2); } if ((permissions & Blobs::BlobSasPermissions::Create) == Blobs::BlobSasPermissions::Create) { verify_blob_create(sasToken); + verify_blob_create(sasToken2); } if ((permissions & Blobs::BlobSasPermissions::Tags) == Blobs::BlobSasPermissions::Tags) { verify_blob_tags(sasToken); + verify_blob_tags(sasToken2); } if ((permissions & Blobs::BlobSasPermissions::DeleteVersion) == Blobs::BlobSasPermissions::DeleteVersion) { verify_blob_delete_version(sasToken); + verify_blob_delete_version(sasToken2); } } + accountSasBuilder.SetPermissions(AccountSasPermissions::All); // Expires { AccountSasBuilder builder2 = accountSasBuilder; - builder2.SetPermissions(AccountSasPermissions::All); builder2.StartsOn = ToISO8601(std::chrono::system_clock::now() - std::chrono::minutes(5)); builder2.ExpiresOn = ToISO8601(std::chrono::system_clock::now() - std::chrono::minutes(1)); auto sasToken = builder2.ToSasQueryParameters(*keyCredential); @@ -217,7 +234,6 @@ namespace Azure { namespace Storage { namespace Test { // Without start time { AccountSasBuilder builder2 = accountSasBuilder; - builder2.SetPermissions(AccountSasPermissions::All); builder2.StartsOn.Reset(); auto sasToken = builder2.ToSasQueryParameters(*keyCredential); EXPECT_NO_THROW(verify_blob_create(sasToken)); @@ -226,39 +242,32 @@ namespace Azure { namespace Storage { namespace Test { // IP { AccountSasBuilder builder2 = accountSasBuilder; - builder2.SetPermissions(AccountSasPermissions::All); builder2.IPRange = "1.1.1.1"; auto sasToken = builder2.ToSasQueryParameters(*keyCredential); - auto blobClient = Blobs::AppendBlobClient(blobUri + sasToken); EXPECT_THROW(verify_blob_create(sasToken), StorageError); + builder2.IPRange = "0.0.0.0-255.255.255.255"; sasToken = builder2.ToSasQueryParameters(*keyCredential); - blobClient = Blobs::AppendBlobClient(blobUri + sasToken); EXPECT_NO_THROW(verify_blob_create(sasToken)); } // Account SAS Service { AccountSasBuilder builder2 = accountSasBuilder; - builder2.SetPermissions(AccountSasPermissions::All); builder2.Services = AccountSasServices::Files; auto sasToken = builder2.ToSasQueryParameters(*keyCredential); - auto blobClient = Blobs::AppendBlobClient(blobUri + sasToken); EXPECT_THROW(verify_blob_create(sasToken), StorageError); builder2.Services = AccountSasServices::All; sasToken = builder2.ToSasQueryParameters(*keyCredential); - blobClient = Blobs::AppendBlobClient(blobUri + sasToken); EXPECT_NO_THROW(verify_blob_create(sasToken)); } // Account SAS Resource Types { AccountSasBuilder builder2 = accountSasBuilder; - builder2.SetPermissions(AccountSasPermissions::All); builder2.ResourceTypes = AccountSasResource::Service; auto sasToken = builder2.ToSasQueryParameters(*keyCredential); - auto blobClient = Blobs::AppendBlobClient(blobUri + sasToken); EXPECT_THROW(verify_blob_create(sasToken), StorageError); auto serviceClient = Blobs::BlobServiceClient(serviceUri + sasToken); @@ -277,40 +286,49 @@ namespace Azure { namespace Storage { namespace Test { { containerSasBuilder.SetPermissions(permissions); auto sasToken = containerSasBuilder.ToSasQueryParameters(*keyCredential); + auto sasToken2 = containerSasBuilder.ToSasQueryParameters(userDelegationKey, accountName); + if ((permissions & Blobs::BlobContainerSasPermissions::Read) == Blobs::BlobContainerSasPermissions::Read) { verify_blob_read(sasToken); + verify_blob_read(sasToken2); } if ((permissions & Blobs::BlobContainerSasPermissions::Write) == Blobs::BlobContainerSasPermissions::Write) { verify_blob_write(sasToken); + verify_blob_write(sasToken2); } if ((permissions & Blobs::BlobContainerSasPermissions::Delete) == Blobs::BlobContainerSasPermissions::Delete) { verify_blob_delete(sasToken); + verify_blob_delete(sasToken2); } if ((permissions & Blobs::BlobContainerSasPermissions::List) == Blobs::BlobContainerSasPermissions::List) { verify_blob_list(sasToken); + verify_blob_list(sasToken2); } if ((permissions & Blobs::BlobContainerSasPermissions::Add) == Blobs::BlobContainerSasPermissions::Add) { verify_blob_add(sasToken); + verify_blob_add(sasToken2); } if ((permissions & Blobs::BlobContainerSasPermissions::Create) == Blobs::BlobContainerSasPermissions::Create) { verify_blob_create(sasToken); + verify_blob_create(sasToken2); } if ((permissions & Blobs::BlobContainerSasPermissions::Tags) == Blobs::BlobContainerSasPermissions::Tags) { verify_blob_tags(sasToken); + verify_blob_tags(sasToken2); } } @@ -322,6 +340,19 @@ namespace Azure { namespace Storage { namespace Test { builder2.ExpiresOn = ToISO8601(std::chrono::system_clock::now() - std::chrono::minutes(1)); auto sasToken = builder2.ToSasQueryParameters(*keyCredential); EXPECT_THROW(verify_blob_create(sasToken), StorageError); + + auto sasToken2 = builder2.ToSasQueryParameters(userDelegationKey, accountName); + EXPECT_THROW(verify_blob_create(sasToken2), StorageError); + } + + // Without start time + { + Blobs::BlobSasBuilder builder2 = blobSasBuilder; + builder2.StartsOn.Reset(); + auto sasToken = builder2.ToSasQueryParameters(*keyCredential); + EXPECT_NO_THROW(verify_blob_create(sasToken)); + auto sasToken2 = builder2.ToSasQueryParameters(userDelegationKey, accountName); + EXPECT_NO_THROW(verify_blob_create(sasToken2)); } // IP @@ -329,12 +360,15 @@ namespace Azure { namespace Storage { namespace Test { Blobs::BlobSasBuilder builder2 = blobSasBuilder; builder2.IPRange = "0.0.0.0-0.0.0.1"; auto sasToken = builder2.ToSasQueryParameters(*keyCredential); - auto blobClient = Blobs::AppendBlobClient(blobUri + sasToken); EXPECT_THROW(verify_blob_create(sasToken), StorageError); + auto sasToken2 = builder2.ToSasQueryParameters(userDelegationKey, accountName); + EXPECT_THROW(verify_blob_create(sasToken2), StorageError); + builder2.IPRange = "0.0.0.0-255.255.255.255"; sasToken = builder2.ToSasQueryParameters(*keyCredential); - blobClient = Blobs::AppendBlobClient(blobUri + sasToken); EXPECT_NO_THROW(verify_blob_create(sasToken)); + sasToken2 = builder2.ToSasQueryParameters(userDelegationKey, accountName); + EXPECT_NO_THROW(verify_blob_create(sasToken2)); } // response headers override @@ -361,6 +395,15 @@ namespace Azure { namespace Storage { namespace Test { EXPECT_EQ(p->HttpHeaders.ContentDisposition, headers.ContentDisposition); EXPECT_EQ(p->HttpHeaders.CacheControl, headers.CacheControl); EXPECT_EQ(p->HttpHeaders.ContentEncoding, headers.ContentEncoding); + + auto sasToken2 = builder2.ToSasQueryParameters(userDelegationKey, accountName); + blobClient = Blobs::AppendBlobClient(blobUri + sasToken2); + p = blobClient.GetProperties(); + EXPECT_EQ(p->HttpHeaders.ContentType, headers.ContentType); + EXPECT_EQ(p->HttpHeaders.ContentLanguage, headers.ContentLanguage); + EXPECT_EQ(p->HttpHeaders.ContentDisposition, headers.ContentDisposition); + EXPECT_EQ(p->HttpHeaders.CacheControl, headers.CacheControl); + EXPECT_EQ(p->HttpHeaders.ContentEncoding, headers.ContentEncoding); } blobClient0.Create(); @@ -369,14 +412,24 @@ namespace Azure { namespace Storage { namespace Test { std::string blobSnapshotUri; + auto create_snapshot = [&]() { + std::string snapshot = blobClient0.CreateSnapshot()->Snapshot; + BlobSnapshotSasBuilder.Snapshot = snapshot; + blobSnapshotUri = blobClient0.WithSnapshot(snapshot).GetUri(); + }; + auto verify_blob_snapshot_read = [&](const std::string sas) { - auto blobSnapshotClient = Blobs::AppendBlobClient(blobSnapshotUri + "&" + sas.substr(1)); + UriBuilder blobSnapshotUriWithSas(blobSnapshotUri); + blobSnapshotUriWithSas.AppendQueries(sas); + auto blobSnapshotClient = Blobs::AppendBlobClient(blobSnapshotUriWithSas.ToString()); auto downloadedContent = blobSnapshotClient.Download(); EXPECT_TRUE(ReadBodyStream(downloadedContent->BodyStream).empty()); }; auto verify_blob_snapshot_delete = [&](const std::string sas) { - auto blobSnapshotClient = Blobs::AppendBlobClient(blobSnapshotUri + "&" + sas.substr(1)); + UriBuilder blobSnapshotUriWithSas(blobSnapshotUri); + blobSnapshotUriWithSas.AppendQueries(sas); + auto blobSnapshotClient = Blobs::AppendBlobClient(blobSnapshotUriWithSas.ToString()); EXPECT_NO_THROW(blobSnapshotClient.Delete()); }; @@ -386,19 +439,24 @@ namespace Azure { namespace Storage { namespace Test { Blobs::BlobSasPermissions::Delete, }) { - std::string snapshot = blobClient0.CreateSnapshot()->Snapshot; - BlobSnapshotSasBuilder.Snapshot = snapshot; - blobSnapshotUri = blobClient0.WithSnapshot(snapshot).GetUri(); + create_snapshot(); BlobSnapshotSasBuilder.SetPermissions(permissions); auto sasToken = BlobSnapshotSasBuilder.ToSasQueryParameters(*keyCredential); + auto sasToken2 = BlobSnapshotSasBuilder.ToSasQueryParameters(userDelegationKey, accountName); if ((permissions & Blobs::BlobSasPermissions::Read) == Blobs::BlobSasPermissions::Read) { verify_blob_snapshot_read(sasToken); + verify_blob_snapshot_read(sasToken2); } if ((permissions & Blobs::BlobSasPermissions::Delete) == Blobs::BlobSasPermissions::Delete) { + create_snapshot(); + sasToken = BlobSnapshotSasBuilder.ToSasQueryParameters(*keyCredential); verify_blob_snapshot_delete(sasToken); + create_snapshot(); + sasToken2 = BlobSnapshotSasBuilder.ToSasQueryParameters(userDelegationKey, accountName); + verify_blob_snapshot_delete(sasToken2); } } }