Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,18 @@ namespace Azure { namespace Storage { namespace Sas {
*/
std::string DelegatedUserObjectId;

/**
* @brief Optional. Custom Request Headers to include in the SAS. Any usage of the SAS must
* include these headers and values in the request.
*/
std::map<std::string, std::string> RequestHeaders;

/**
* @brief Optional. Custom Request Query Parameters to include in the SAS. Any usage of the SAS
* must include these query parameters and values in the request.
*/
std::map<std::string, std::string> RequestQueryParameters;

/**
* @brief Override the value returned for Cache-Control response header..
*/
Expand Down
71 changes: 66 additions & 5 deletions sdk/storage/azure-storage-blobs/src/blob_sas_builder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
#include <azure/core/http/http.hpp>
#include <azure/storage/common/crypt.hpp>

/* cSpell:ignore rscc, rscd, rsce, rscl, rsct, skoid, sktid, skdutid, sduoid */
#include <iostream>

/* cSpell:ignore rscc, rscd, rsce, rscl, rsct, skoid, sktid, skdutid, sduoid, srh, srq */

namespace Azure { namespace Storage { namespace Sas {

Expand Down Expand Up @@ -36,6 +38,53 @@ namespace Azure { namespace Storage { namespace Sas {
throw std::invalid_argument("Unknown BlobSasResource value.");
}
}

std::string ParseRequestQueryParameters(
const std::map<std::string, std::string>& queryParameters)
{
if (queryParameters.empty())
{
return "";
}
std::string result;
for (const auto& pair : queryParameters)
{
result += "\n" + pair.first + ":" + pair.second;
}
return result;
}

std::string ParseRequestHeaders(const std::map<std::string, std::string>& headers)
{
if (headers.empty())
{
return "";
}
std::string result;
for (const auto& pair : headers)
{
result += pair.first + ":" + pair.second + "\n";
}
return result;
}

std::string ParseRequestKeys(const std::map<std::string, std::string>& map)
{
if (map.empty())
{
return "";
}
std::string result;
for (auto it = map.begin(); it != map.end(); ++it)
{
result += it->first;
if (std::next(it) != map.end())
{
result += ",";
}
}
return result;
}
} // namespace

void BlobSasBuilder::SetPermissions(BlobContainerSasPermissions permissions)
Expand Down Expand Up @@ -267,8 +316,9 @@ namespace Azure { namespace Storage { namespace Sas {
: "")
+ "\n" + DelegatedUserObjectId + "\n" + (IPRange.HasValue() ? IPRange.Value() : "") + "\n"
+ protocol + "\n" + SasVersion + "\n" + resource + "\n" + snapshotVersion + "\n"
+ EncryptionScope + "\n\n\n" + CacheControl + "\n" + ContentDisposition + "\n"
+ ContentEncoding + "\n" + ContentLanguage + "\n" + ContentType;
+ EncryptionScope + "\n" + ParseRequestHeaders(RequestHeaders) + "\n"
+ ParseRequestQueryParameters(RequestQueryParameters) + "\n" + CacheControl + "\n"
+ ContentDisposition + "\n" + ContentEncoding + "\n" + ContentLanguage + "\n" + ContentType;

std::string signature = Azure::Core::Convert::Base64Encode(_internal::HmacSha256(
std::vector<uint8_t>(stringToSign.begin(), stringToSign.end()),
Expand Down Expand Up @@ -309,6 +359,16 @@ namespace Azure { namespace Storage { namespace Sas {
builder.AppendQueryParameter(
"sduoid", _internal::UrlEncodeQueryParameter(DelegatedUserObjectId));
}
if (!RequestHeaders.empty())
{
builder.AppendQueryParameter(
"srh", _internal::UrlEncodeQueryParameter(ParseRequestKeys(RequestHeaders)));
}
if (!RequestQueryParameters.empty())
{
builder.AppendQueryParameter(
"srq", _internal::UrlEncodeQueryParameter(ParseRequestKeys(RequestQueryParameters)));
}
if (!CacheControl.empty())
{
builder.AppendQueryParameter("rscc", _internal::UrlEncodeQueryParameter(CacheControl));
Expand Down Expand Up @@ -418,8 +478,9 @@ namespace Azure { namespace Storage { namespace Sas {
: "")
+ "\n" + DelegatedUserObjectId + "\n" + (IPRange.HasValue() ? IPRange.Value() : "") + "\n"
+ protocol + "\n" + SasVersion + "\n" + resource + "\n" + snapshotVersion + "\n"
+ EncryptionScope + "\n\n\n" + CacheControl + "\n" + ContentDisposition + "\n"
+ ContentEncoding + "\n" + ContentLanguage + "\n" + ContentType;
+ EncryptionScope + "\n" + ParseRequestHeaders(RequestHeaders) + "\n"
+ ParseRequestQueryParameters(RequestQueryParameters) + "\n" + CacheControl + "\n"
+ ContentDisposition + "\n" + ContentEncoding + "\n" + ContentLanguage + "\n" + ContentType;
}

}}} // namespace Azure::Storage::Sas
79 changes: 77 additions & 2 deletions sdk/storage/azure-storage-blobs/test/ut/blob_sas_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -920,15 +920,15 @@ namespace Azure { namespace Storage { namespace Test {
AppendQueryParameters(Azure::Core::Url(blobClient.GetUrl()), sasToken),
GetTestCredential(),
InitStorageClientOptions<Blobs::BlobClientOptions>());
EXPECT_NO_THROW(blobClient1.GetProperties());
EXPECT_NO_THROW(blobClient1.Download());

blobSasBuilder.DelegatedUserObjectId = "invalidObjectId";
sasToken = blobSasBuilder.GenerateSasToken(userDelegationKey, accountName);
Blobs::BlockBlobClient blobClient2(
AppendQueryParameters(Azure::Core::Url(blobClient.GetUrl()), sasToken),
GetTestCredential(),
InitStorageClientOptions<Blobs::BlobClientOptions>());
EXPECT_THROW(blobClient2.GetProperties(), StorageException);
EXPECT_THROW(blobClient2.Download(), StorageException);
}

TEST_F(BlobSasTest, DISABLED_PrincipalBoundDelegationSas_CrossTenant)
Expand Down Expand Up @@ -993,4 +993,79 @@ namespace Azure { namespace Storage { namespace Test {
InitStorageClientOptions<Blobs::BlobClientOptions>());
EXPECT_THROW(blobClient2.Download(), StorageException);
}

TEST_F(BlobSasTest, DISABLED_DynamicSas)
{
auto sasStartsOn = std::chrono::system_clock::now() - std::chrono::minutes(5);
auto sasExpiresOn = std::chrono::system_clock::now() + std::chrono::minutes(60);

auto keyCredential
= _internal::ParseConnectionString(StandardStorageConnectionString()).KeyCredential;
auto accountName = keyCredential->AccountName;

Blobs::Models::UserDelegationKey userDelegationKey;
{
auto blobServiceClient = Blobs::BlobServiceClient(
m_blobServiceClient->GetUrl(),
GetTestCredential(),
InitStorageClientOptions<Blobs::BlobClientOptions>());
userDelegationKey = blobServiceClient.GetUserDelegationKey(sasExpiresOn).Value;
}

auto blobContainerClient = *m_blobContainerClient;
auto blobClient = *m_blockBlobClient;
const std::string blobName = m_blobName;

Sas::BlobSasBuilder blobSasBuilder;
blobSasBuilder.Protocol = Sas::SasProtocol::HttpsAndHttp;
blobSasBuilder.StartsOn = sasStartsOn;
blobSasBuilder.ExpiresOn = sasExpiresOn;
blobSasBuilder.BlobContainerName = m_containerName;
blobSasBuilder.BlobName = blobName;
blobSasBuilder.Resource = Sas::BlobSasResource::Blob;

blobSasBuilder.SetPermissions(Sas::BlobSasPermissions::All);

std::map<std::string, std::string> requestHeaders;
requestHeaders["x-ms-range"] = "bytes=0-1023";
requestHeaders["x-ms-range-get-content-md5"] = "true";

std::map<std::string, std::string> requestQueryParameters;
requestQueryParameters["spr"] = "https,http";
requestQueryParameters["sks"] = "b";

blobSasBuilder.RequestHeaders = requestHeaders;
blobSasBuilder.RequestQueryParameters = requestQueryParameters;
auto sasToken = blobSasBuilder.GenerateSasToken(userDelegationKey, accountName);

Blobs::DownloadBlobOptions downloadOptions;
Core::Http::HttpRange range;
range.Offset = 0;
range.Length = 1024;
downloadOptions.Range = range;
downloadOptions.RangeHashAlgorithm = HashAlgorithm::Md5;

Blobs::BlockBlobClient blobClient1(
AppendQueryParameters(Azure::Core::Url(blobClient.GetUrl()), sasToken),
InitStorageClientOptions<Blobs::BlobClientOptions>());
EXPECT_NO_THROW(blobClient1.Download(downloadOptions));

requestHeaders["foo$"] = "bar!";
requestHeaders["company"] = "msft";
requestHeaders["city"] = "redmond,atlanta,reston";

requestQueryParameters["hello$"] = "world!";
requestQueryParameters["abra"] = "cadabra";
requestQueryParameters["firstName"] = "john,Tim";

blobSasBuilder.RequestHeaders = requestHeaders;
blobSasBuilder.RequestQueryParameters = requestQueryParameters;

sasToken = blobSasBuilder.GenerateSasToken(userDelegationKey, accountName);
Blobs::BlockBlobClient blobClient2(
AppendQueryParameters(Azure::Core::Url(blobClient.GetUrl()), sasToken),
InitStorageClientOptions<Blobs::BlobClientOptions>());
EXPECT_THROW(blobClient2.Download(downloadOptions), StorageException);
}

}}} // namespace Azure::Storage::Test
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,18 @@ namespace Azure { namespace Storage { namespace Sas {
*/
std::string DelegatedUserObjectId;

/**
* @brief Optional. Custom Request Headers to include in the SAS. Any usage of the SAS must
* include these headers and values in the request.
*/
std::map<std::string, std::string> RequestHeaders;

/**
* @brief Optional. Custom Request Query Parameters to include in the SAS. Any usage of the SAS
* must include these query parameters and values in the request.
*/
std::map<std::string, std::string> RequestQueryParameters;

/**
* @brief Override the value returned for Cache-Control response header.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
#include <azure/core/http/http.hpp>
#include <azure/storage/common/crypt.hpp>

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

namespace Azure { namespace Storage { namespace Sas {
namespace {
Expand All @@ -31,6 +32,53 @@ namespace Azure { namespace Storage { namespace Sas {
throw std::invalid_argument("Unknown DataLakeSasResource value.");
}
}

std::string ParseRequestQueryParameters(
const std::map<std::string, std::string>& queryParameters)
{
if (queryParameters.empty())
{
return "";
}
std::string result;
for (const auto& pair : queryParameters)
{
result += "\n" + pair.first + ":" + pair.second;
}
return result;
}

std::string ParseRequestHeaders(const std::map<std::string, std::string>& headers)
{
if (headers.empty())
{
return "";
}
std::string result;
for (const auto& pair : headers)
{
result += pair.first + ":" + pair.second + "\n";
}
return result;
}

std::string ParseRequestKeys(const std::map<std::string, std::string>& map)
{
if (map.empty())
{
return "";
}
std::string result;
for (auto it = map.begin(); it != map.end(); ++it)
{
result += it->first;
if (std::next(it) != map.end())
{
result += ",";
}
}
return result;
}
} // namespace

void DataLakeSasBuilder::SetPermissions(DataLakeFileSystemSasPermissions permissions)
Expand Down Expand Up @@ -231,9 +279,10 @@ namespace Azure { namespace Storage { namespace Sas {
? userDelegationKey.SignedDelegatedUserTid.Value()
: "")
+ "\n" + DelegatedUserObjectId + "\n" + (IPRange.HasValue() ? IPRange.Value() : "") + "\n"
+ protocol + "\n" + SasVersion + "\n" + resource + "\n" + "\n" + EncryptionScope + "\n\n\n"
+ CacheControl + "\n" + ContentDisposition + "\n" + ContentEncoding + "\n" + ContentLanguage
+ "\n" + ContentType;
+ protocol + "\n" + SasVersion + "\n" + resource + "\n" + "\n" + EncryptionScope + "\n"
+ ParseRequestHeaders(RequestHeaders) + "\n"
+ ParseRequestQueryParameters(RequestQueryParameters) + "\n" + CacheControl + "\n"
+ ContentDisposition + "\n" + ContentEncoding + "\n" + ContentLanguage + "\n" + ContentType;

std::string signature = Azure::Core::Convert::Base64Encode(_internal::HmacSha256(
std::vector<uint8_t>(stringToSign.begin(), stringToSign.end()),
Expand Down Expand Up @@ -292,6 +341,16 @@ namespace Azure { namespace Storage { namespace Sas {
builder.AppendQueryParameter(
"sdd", _internal::UrlEncodeQueryParameter(std::to_string(DirectoryDepth.Value())));
}
if (!RequestHeaders.empty())
{
builder.AppendQueryParameter(
"srh", _internal::UrlEncodeQueryParameter(ParseRequestKeys(RequestHeaders)));
}
if (!RequestQueryParameters.empty())
{
builder.AppendQueryParameter(
"srq", _internal::UrlEncodeQueryParameter(ParseRequestKeys(RequestQueryParameters)));
}
if (!CacheControl.empty())
{
builder.AppendQueryParameter("rscc", _internal::UrlEncodeQueryParameter(CacheControl));
Expand Down Expand Up @@ -379,9 +438,10 @@ namespace Azure { namespace Storage { namespace Sas {
? userDelegationKey.SignedDelegatedUserTid.Value()
: "")
+ "\n" + DelegatedUserObjectId + "\n" + (IPRange.HasValue() ? IPRange.Value() : "") + "\n"
+ protocol + "\n" + SasVersion + "\n" + resource + "\n" + "\n" + EncryptionScope + "\n\n\n"
+ CacheControl + "\n" + ContentDisposition + "\n" + ContentEncoding + "\n" + ContentLanguage
+ "\n" + ContentType;
+ protocol + "\n" + SasVersion + "\n" + resource + "\n" + "\n" + EncryptionScope + "\n"
+ ParseRequestHeaders(RequestHeaders) + "\n"
+ ParseRequestQueryParameters(RequestQueryParameters) + "\n" + CacheControl + "\n"
+ ContentDisposition + "\n" + ContentEncoding + "\n" + ContentLanguage + "\n" + ContentType;
}

}}} // namespace Azure::Storage::Sas
Loading
Loading