Skip to content

Commit 579af3e

Browse files
committed
Add Dynamic Sas support (#6868)
1 parent 464b57b commit 579af3e

File tree

6 files changed

+308
-14
lines changed

6 files changed

+308
-14
lines changed

sdk/storage/azure-storage-blobs/inc/azure/storage/blobs/blob_sas_builder.hpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,18 @@ namespace Azure { namespace Storage { namespace Sas {
262262
*/
263263
std::string DelegatedUserObjectId;
264264

265+
/**
266+
* @brief Optional. Custom Request Headers to include in the SAS. Any usage of the SAS must
267+
* include these headers and values in the request.
268+
*/
269+
std::map<std::string, std::string> RequestHeaders;
270+
271+
/**
272+
* @brief Optional. Custom Request Query Parameters to include in the SAS. Any usage of the SAS
273+
* must include these query parameters and values in the request.
274+
*/
275+
std::map<std::string, std::string> RequestQueryParameters;
276+
265277
/**
266278
* @brief Override the value returned for Cache-Control response header..
267279
*/

sdk/storage/azure-storage-blobs/src/blob_sas_builder.cpp

Lines changed: 66 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66
#include <azure/core/http/http.hpp>
77
#include <azure/storage/common/crypt.hpp>
88

9-
/* cSpell:ignore rscc, rscd, rsce, rscl, rsct, skoid, sktid, skdutid, sduoid */
9+
#include <iostream>
10+
11+
/* cSpell:ignore rscc, rscd, rsce, rscl, rsct, skoid, sktid, skdutid, sduoid, srh, srq */
1012

1113
namespace Azure { namespace Storage { namespace Sas {
1214

@@ -36,6 +38,53 @@ namespace Azure { namespace Storage { namespace Sas {
3638
throw std::invalid_argument("Unknown BlobSasResource value.");
3739
}
3840
}
41+
42+
std::string ParseRequestQueryParameters(
43+
const std::map<std::string, std::string>& queryParameters)
44+
{
45+
if (queryParameters.empty())
46+
{
47+
return "";
48+
}
49+
std::string result;
50+
for (const auto& pair : queryParameters)
51+
{
52+
result += "\n" + pair.first + ":" + pair.second;
53+
}
54+
return result;
55+
}
56+
57+
std::string ParseRequestHeaders(const std::map<std::string, std::string>& headers)
58+
{
59+
if (headers.empty())
60+
{
61+
return "";
62+
}
63+
std::string result;
64+
for (const auto& pair : headers)
65+
{
66+
result += pair.first + ":" + pair.second + "\n";
67+
}
68+
return result;
69+
}
70+
71+
std::string ParseRequestKeys(const std::map<std::string, std::string>& map)
72+
{
73+
if (map.empty())
74+
{
75+
return "";
76+
}
77+
std::string result;
78+
for (auto it = map.begin(); it != map.end(); ++it)
79+
{
80+
result += it->first;
81+
if (std::next(it) != map.end())
82+
{
83+
result += ",";
84+
}
85+
}
86+
return result;
87+
}
3988
} // namespace
4089

4190
void BlobSasBuilder::SetPermissions(BlobContainerSasPermissions permissions)
@@ -267,8 +316,9 @@ namespace Azure { namespace Storage { namespace Sas {
267316
: "")
268317
+ "\n" + DelegatedUserObjectId + "\n" + (IPRange.HasValue() ? IPRange.Value() : "") + "\n"
269318
+ protocol + "\n" + SasVersion + "\n" + resource + "\n" + snapshotVersion + "\n"
270-
+ EncryptionScope + "\n\n\n" + CacheControl + "\n" + ContentDisposition + "\n"
271-
+ ContentEncoding + "\n" + ContentLanguage + "\n" + ContentType;
319+
+ EncryptionScope + "\n" + ParseRequestHeaders(RequestHeaders) + "\n"
320+
+ ParseRequestQueryParameters(RequestQueryParameters) + "\n" + CacheControl + "\n"
321+
+ ContentDisposition + "\n" + ContentEncoding + "\n" + ContentLanguage + "\n" + ContentType;
272322

273323
std::string signature = Azure::Core::Convert::Base64Encode(_internal::HmacSha256(
274324
std::vector<uint8_t>(stringToSign.begin(), stringToSign.end()),
@@ -309,6 +359,16 @@ namespace Azure { namespace Storage { namespace Sas {
309359
builder.AppendQueryParameter(
310360
"sduoid", _internal::UrlEncodeQueryParameter(DelegatedUserObjectId));
311361
}
362+
if (!RequestHeaders.empty())
363+
{
364+
builder.AppendQueryParameter(
365+
"srh", _internal::UrlEncodeQueryParameter(ParseRequestKeys(RequestHeaders)));
366+
}
367+
if (!RequestQueryParameters.empty())
368+
{
369+
builder.AppendQueryParameter(
370+
"srq", _internal::UrlEncodeQueryParameter(ParseRequestKeys(RequestQueryParameters)));
371+
}
312372
if (!CacheControl.empty())
313373
{
314374
builder.AppendQueryParameter("rscc", _internal::UrlEncodeQueryParameter(CacheControl));
@@ -418,8 +478,9 @@ namespace Azure { namespace Storage { namespace Sas {
418478
: "")
419479
+ "\n" + DelegatedUserObjectId + "\n" + (IPRange.HasValue() ? IPRange.Value() : "") + "\n"
420480
+ protocol + "\n" + SasVersion + "\n" + resource + "\n" + snapshotVersion + "\n"
421-
+ EncryptionScope + "\n\n\n" + CacheControl + "\n" + ContentDisposition + "\n"
422-
+ ContentEncoding + "\n" + ContentLanguage + "\n" + ContentType;
481+
+ EncryptionScope + "\n" + ParseRequestHeaders(RequestHeaders) + "\n"
482+
+ ParseRequestQueryParameters(RequestQueryParameters) + "\n" + CacheControl + "\n"
483+
+ ContentDisposition + "\n" + ContentEncoding + "\n" + ContentLanguage + "\n" + ContentType;
423484
}
424485

425486
}}} // namespace Azure::Storage::Sas

sdk/storage/azure-storage-blobs/test/ut/blob_sas_test.cpp

Lines changed: 77 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -920,15 +920,15 @@ namespace Azure { namespace Storage { namespace Test {
920920
AppendQueryParameters(Azure::Core::Url(blobClient.GetUrl()), sasToken),
921921
GetTestCredential(),
922922
InitStorageClientOptions<Blobs::BlobClientOptions>());
923-
EXPECT_NO_THROW(blobClient1.GetProperties());
923+
EXPECT_NO_THROW(blobClient1.Download());
924924

925925
blobSasBuilder.DelegatedUserObjectId = "invalidObjectId";
926926
sasToken = blobSasBuilder.GenerateSasToken(userDelegationKey, accountName);
927927
Blobs::BlockBlobClient blobClient2(
928928
AppendQueryParameters(Azure::Core::Url(blobClient.GetUrl()), sasToken),
929929
GetTestCredential(),
930930
InitStorageClientOptions<Blobs::BlobClientOptions>());
931-
EXPECT_THROW(blobClient2.GetProperties(), StorageException);
931+
EXPECT_THROW(blobClient2.Download(), StorageException);
932932
}
933933

934934
TEST_F(BlobSasTest, DISABLED_PrincipalBoundDelegationSas_CrossTenant)
@@ -993,4 +993,79 @@ namespace Azure { namespace Storage { namespace Test {
993993
InitStorageClientOptions<Blobs::BlobClientOptions>());
994994
EXPECT_THROW(blobClient2.Download(), StorageException);
995995
}
996+
997+
TEST_F(BlobSasTest, DISABLED_DynamicSas)
998+
{
999+
auto sasStartsOn = std::chrono::system_clock::now() - std::chrono::minutes(5);
1000+
auto sasExpiresOn = std::chrono::system_clock::now() + std::chrono::minutes(60);
1001+
1002+
auto keyCredential
1003+
= _internal::ParseConnectionString(StandardStorageConnectionString()).KeyCredential;
1004+
auto accountName = keyCredential->AccountName;
1005+
1006+
Blobs::Models::UserDelegationKey userDelegationKey;
1007+
{
1008+
auto blobServiceClient = Blobs::BlobServiceClient(
1009+
m_blobServiceClient->GetUrl(),
1010+
GetTestCredential(),
1011+
InitStorageClientOptions<Blobs::BlobClientOptions>());
1012+
userDelegationKey = blobServiceClient.GetUserDelegationKey(sasExpiresOn).Value;
1013+
}
1014+
1015+
auto blobContainerClient = *m_blobContainerClient;
1016+
auto blobClient = *m_blockBlobClient;
1017+
const std::string blobName = m_blobName;
1018+
1019+
Sas::BlobSasBuilder blobSasBuilder;
1020+
blobSasBuilder.Protocol = Sas::SasProtocol::HttpsAndHttp;
1021+
blobSasBuilder.StartsOn = sasStartsOn;
1022+
blobSasBuilder.ExpiresOn = sasExpiresOn;
1023+
blobSasBuilder.BlobContainerName = m_containerName;
1024+
blobSasBuilder.BlobName = blobName;
1025+
blobSasBuilder.Resource = Sas::BlobSasResource::Blob;
1026+
1027+
blobSasBuilder.SetPermissions(Sas::BlobSasPermissions::All);
1028+
1029+
std::map<std::string, std::string> requestHeaders;
1030+
requestHeaders["x-ms-range"] = "bytes=0-1023";
1031+
requestHeaders["x-ms-range-get-content-md5"] = "true";
1032+
1033+
std::map<std::string, std::string> requestQueryParameters;
1034+
requestQueryParameters["spr"] = "https,http";
1035+
requestQueryParameters["sks"] = "b";
1036+
1037+
blobSasBuilder.RequestHeaders = requestHeaders;
1038+
blobSasBuilder.RequestQueryParameters = requestQueryParameters;
1039+
auto sasToken = blobSasBuilder.GenerateSasToken(userDelegationKey, accountName);
1040+
1041+
Blobs::DownloadBlobOptions downloadOptions;
1042+
Core::Http::HttpRange range;
1043+
range.Offset = 0;
1044+
range.Length = 1024;
1045+
downloadOptions.Range = range;
1046+
downloadOptions.RangeHashAlgorithm = HashAlgorithm::Md5;
1047+
1048+
Blobs::BlockBlobClient blobClient1(
1049+
AppendQueryParameters(Azure::Core::Url(blobClient.GetUrl()), sasToken),
1050+
InitStorageClientOptions<Blobs::BlobClientOptions>());
1051+
EXPECT_NO_THROW(blobClient1.Download(downloadOptions));
1052+
1053+
requestHeaders["foo$"] = "bar!";
1054+
requestHeaders["company"] = "msft";
1055+
requestHeaders["city"] = "redmond,atlanta,reston";
1056+
1057+
requestQueryParameters["hello$"] = "world!";
1058+
requestQueryParameters["abra"] = "cadabra";
1059+
requestQueryParameters["firstName"] = "john,Tim";
1060+
1061+
blobSasBuilder.RequestHeaders = requestHeaders;
1062+
blobSasBuilder.RequestQueryParameters = requestQueryParameters;
1063+
1064+
sasToken = blobSasBuilder.GenerateSasToken(userDelegationKey, accountName);
1065+
Blobs::BlockBlobClient blobClient2(
1066+
AppendQueryParameters(Azure::Core::Url(blobClient.GetUrl()), sasToken),
1067+
InitStorageClientOptions<Blobs::BlobClientOptions>());
1068+
EXPECT_THROW(blobClient2.Download(downloadOptions), StorageException);
1069+
}
1070+
9961071
}}} // namespace Azure::Storage::Test

sdk/storage/azure-storage-files-datalake/inc/azure/storage/files/datalake/datalake_sas_builder.hpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,18 @@ namespace Azure { namespace Storage { namespace Sas {
249249
*/
250250
std::string DelegatedUserObjectId;
251251

252+
/**
253+
* @brief Optional. Custom Request Headers to include in the SAS. Any usage of the SAS must
254+
* include these headers and values in the request.
255+
*/
256+
std::map<std::string, std::string> RequestHeaders;
257+
258+
/**
259+
* @brief Optional. Custom Request Query Parameters to include in the SAS. Any usage of the SAS
260+
* must include these query parameters and values in the request.
261+
*/
262+
std::map<std::string, std::string> RequestQueryParameters;
263+
252264
/**
253265
* @brief Override the value returned for Cache-Control response header.
254266
*/

sdk/storage/azure-storage-files-datalake/src/datalake_sas_builder.cpp

Lines changed: 67 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
#include <azure/core/http/http.hpp>
77
#include <azure/storage/common/crypt.hpp>
88

9-
/* cSpell:ignore rscc, rscd, rsce, rscl, rsct, skoid, sktid, saoid, suoid, scid, skdutid, sduoid */
9+
/* cSpell:ignore rscc, rscd, rsce, rscl, rsct, skoid, sktid, saoid, suoid, scid, skdutid, sduoid,
10+
* srh, srq */
1011

1112
namespace Azure { namespace Storage { namespace Sas {
1213
namespace {
@@ -31,6 +32,53 @@ namespace Azure { namespace Storage { namespace Sas {
3132
throw std::invalid_argument("Unknown DataLakeSasResource value.");
3233
}
3334
}
35+
36+
std::string ParseRequestQueryParameters(
37+
const std::map<std::string, std::string>& queryParameters)
38+
{
39+
if (queryParameters.empty())
40+
{
41+
return "";
42+
}
43+
std::string result;
44+
for (const auto& pair : queryParameters)
45+
{
46+
result += "\n" + pair.first + ":" + pair.second;
47+
}
48+
return result;
49+
}
50+
51+
std::string ParseRequestHeaders(const std::map<std::string, std::string>& headers)
52+
{
53+
if (headers.empty())
54+
{
55+
return "";
56+
}
57+
std::string result;
58+
for (const auto& pair : headers)
59+
{
60+
result += pair.first + ":" + pair.second + "\n";
61+
}
62+
return result;
63+
}
64+
65+
std::string ParseRequestKeys(const std::map<std::string, std::string>& map)
66+
{
67+
if (map.empty())
68+
{
69+
return "";
70+
}
71+
std::string result;
72+
for (auto it = map.begin(); it != map.end(); ++it)
73+
{
74+
result += it->first;
75+
if (std::next(it) != map.end())
76+
{
77+
result += ",";
78+
}
79+
}
80+
return result;
81+
}
3482
} // namespace
3583

3684
void DataLakeSasBuilder::SetPermissions(DataLakeFileSystemSasPermissions permissions)
@@ -231,9 +279,10 @@ namespace Azure { namespace Storage { namespace Sas {
231279
? userDelegationKey.SignedDelegatedUserTid.Value()
232280
: "")
233281
+ "\n" + DelegatedUserObjectId + "\n" + (IPRange.HasValue() ? IPRange.Value() : "") + "\n"
234-
+ protocol + "\n" + SasVersion + "\n" + resource + "\n" + "\n" + EncryptionScope + "\n\n\n"
235-
+ CacheControl + "\n" + ContentDisposition + "\n" + ContentEncoding + "\n" + ContentLanguage
236-
+ "\n" + ContentType;
282+
+ protocol + "\n" + SasVersion + "\n" + resource + "\n" + "\n" + EncryptionScope + "\n"
283+
+ ParseRequestHeaders(RequestHeaders) + "\n"
284+
+ ParseRequestQueryParameters(RequestQueryParameters) + "\n" + CacheControl + "\n"
285+
+ ContentDisposition + "\n" + ContentEncoding + "\n" + ContentLanguage + "\n" + ContentType;
237286

238287
std::string signature = Azure::Core::Convert::Base64Encode(_internal::HmacSha256(
239288
std::vector<uint8_t>(stringToSign.begin(), stringToSign.end()),
@@ -292,6 +341,16 @@ namespace Azure { namespace Storage { namespace Sas {
292341
builder.AppendQueryParameter(
293342
"sdd", _internal::UrlEncodeQueryParameter(std::to_string(DirectoryDepth.Value())));
294343
}
344+
if (!RequestHeaders.empty())
345+
{
346+
builder.AppendQueryParameter(
347+
"srh", _internal::UrlEncodeQueryParameter(ParseRequestKeys(RequestHeaders)));
348+
}
349+
if (!RequestQueryParameters.empty())
350+
{
351+
builder.AppendQueryParameter(
352+
"srq", _internal::UrlEncodeQueryParameter(ParseRequestKeys(RequestQueryParameters)));
353+
}
295354
if (!CacheControl.empty())
296355
{
297356
builder.AppendQueryParameter("rscc", _internal::UrlEncodeQueryParameter(CacheControl));
@@ -379,9 +438,10 @@ namespace Azure { namespace Storage { namespace Sas {
379438
? userDelegationKey.SignedDelegatedUserTid.Value()
380439
: "")
381440
+ "\n" + DelegatedUserObjectId + "\n" + (IPRange.HasValue() ? IPRange.Value() : "") + "\n"
382-
+ protocol + "\n" + SasVersion + "\n" + resource + "\n" + "\n" + EncryptionScope + "\n\n\n"
383-
+ CacheControl + "\n" + ContentDisposition + "\n" + ContentEncoding + "\n" + ContentLanguage
384-
+ "\n" + ContentType;
441+
+ protocol + "\n" + SasVersion + "\n" + resource + "\n" + "\n" + EncryptionScope + "\n"
442+
+ ParseRequestHeaders(RequestHeaders) + "\n"
443+
+ ParseRequestQueryParameters(RequestQueryParameters) + "\n" + CacheControl + "\n"
444+
+ ContentDisposition + "\n" + ContentEncoding + "\n" + ContentLanguage + "\n" + ContentType;
385445
}
386446

387447
}}} // namespace Azure::Storage::Sas

0 commit comments

Comments
 (0)