Add Dynamic Sas support (#6868)
This commit is contained in:
parent
464b57b2a0
commit
579af3e43b
@ -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..
|
||||
*/
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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)
|
||||
@ -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()),
|
||||
@ -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));
|
||||
@ -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
|
||||
|
||||
@ -920,7 +920,7 @@ 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);
|
||||
@ -928,7 +928,7 @@ namespace Azure { namespace Storage { namespace Test {
|
||||
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)
|
||||
@ -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
|
||||
|
||||
@ -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.
|
||||
*/
|
||||
|
||||
@ -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 {
|
||||
@ -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)
|
||||
@ -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()),
|
||||
@ -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));
|
||||
@ -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
|
||||
|
||||
@ -989,4 +989,78 @@ namespace Azure { namespace Storage { namespace Test {
|
||||
InitStorageClientOptions<Files::DataLake::DataLakeClientOptions>());
|
||||
EXPECT_THROW(fileClient2.GetProperties(), StorageException);
|
||||
}
|
||||
|
||||
TEST_F(DataLakeSasTest, 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(AdlsGen2ConnectionString()).KeyCredential;
|
||||
auto accountName = keyCredential->AccountName;
|
||||
|
||||
Files::DataLake::Models::UserDelegationKey userDelegationKey
|
||||
= GetDataLakeServiceClientOAuth().GetUserDelegationKey(sasExpiresOn).Value;
|
||||
|
||||
std::string fileName = RandomString();
|
||||
|
||||
auto dataLakeFileSystemClient = *m_fileSystemClient;
|
||||
auto dataLakeFileClient = dataLakeFileSystemClient.GetFileClient(fileName);
|
||||
dataLakeFileClient.Create();
|
||||
auto buffer = RandomBuffer(1024);
|
||||
auto stream = Azure::Core::IO::MemoryBodyStream(buffer);
|
||||
Files::DataLake::AppendFileOptions appendOptions;
|
||||
appendOptions.Flush = true;
|
||||
dataLakeFileClient.Append(stream, 0, appendOptions);
|
||||
|
||||
Sas::DataLakeSasBuilder fileSasBuilder;
|
||||
fileSasBuilder.Protocol = Sas::SasProtocol::HttpsAndHttp;
|
||||
fileSasBuilder.StartsOn = sasStartsOn;
|
||||
fileSasBuilder.ExpiresOn = sasExpiresOn;
|
||||
fileSasBuilder.FileSystemName = m_fileSystemName;
|
||||
fileSasBuilder.Path = fileName;
|
||||
fileSasBuilder.Resource = Sas::DataLakeSasResource::File;
|
||||
|
||||
fileSasBuilder.SetPermissions(Sas::DataLakeSasPermissions::All);
|
||||
|
||||
std::map<std::string, std::string> requestHeaders;
|
||||
requestHeaders["x-ms-range"] = "bytes=0-1023";
|
||||
requestHeaders["x-ms-upn"] = "true";
|
||||
|
||||
std::map<std::string, std::string> requestQueryParameters;
|
||||
requestQueryParameters["spr"] = "https,http";
|
||||
requestQueryParameters["sks"] = "b";
|
||||
|
||||
fileSasBuilder.RequestHeaders = requestHeaders;
|
||||
fileSasBuilder.RequestQueryParameters = requestQueryParameters;
|
||||
auto sasToken = fileSasBuilder.GenerateSasToken(userDelegationKey, accountName);
|
||||
|
||||
Files::DataLake::DownloadFileOptions downloadOptions;
|
||||
Core::Http::HttpRange range;
|
||||
range.Offset = 0;
|
||||
range.Length = 1024;
|
||||
downloadOptions.Range = range;
|
||||
downloadOptions.IncludeUserPrincipalName = true;
|
||||
|
||||
Files::DataLake::DataLakeFileClient fileClient1(
|
||||
AppendQueryParameters(Azure::Core::Url(dataLakeFileClient.GetUrl()), sasToken),
|
||||
InitStorageClientOptions<Files::DataLake::DataLakeClientOptions>());
|
||||
EXPECT_NO_THROW(fileClient1.Download(downloadOptions));
|
||||
|
||||
requestHeaders["foo$"] = "bar!";
|
||||
requestHeaders["company"] = "msft";
|
||||
requestHeaders["city"] = "redmond,atlanta,reston";
|
||||
|
||||
requestQueryParameters["hello$"] = "world!";
|
||||
requestQueryParameters["abra"] = "cadabra";
|
||||
requestQueryParameters["firstName"] = "john,Tim";
|
||||
|
||||
fileSasBuilder.RequestHeaders = requestHeaders;
|
||||
fileSasBuilder.RequestQueryParameters = requestQueryParameters;
|
||||
|
||||
sasToken = fileSasBuilder.GenerateSasToken(userDelegationKey, accountName);
|
||||
Files::DataLake::DataLakeFileClient fileClient2(
|
||||
AppendQueryParameters(Azure::Core::Url(dataLakeFileClient.GetUrl()), sasToken),
|
||||
InitStorageClientOptions<Files::DataLake::DataLakeClientOptions>());
|
||||
EXPECT_THROW(fileClient2.Download(downloadOptions), StorageException);
|
||||
}
|
||||
}}} // namespace Azure::Storage::Test
|
||||
|
||||
Loading…
Reference in New Issue
Block a user