Storage/STG101/Cross-tenant principal bound sas (#6863)

* Cross-tenant principal bound sas

* Fix test failure
This commit is contained in:
microzchang 2025-12-09 11:24:26 +08:00 committed by microzchang
parent b0108b0d13
commit 464b57b2a0
18 changed files with 362 additions and 47 deletions

View File

@ -275,6 +275,11 @@ namespace Azure { namespace Storage { namespace Blobs {
* will be truncated to second.
*/
Azure::DateTime StartsOn = std::chrono::system_clock::now();
/**
* The delegated user tenant id in Azure AD.
*/
Nullable<std::string> DelegatedUserTid;
};
/**

View File

@ -6,7 +6,7 @@
#include <azure/core/http/http.hpp>
#include <azure/storage/common/crypt.hpp>
/* cSpell:ignore rscc, rscd, rsce, rscl, rsct, skoid, sktid, sduoid */
/* cSpell:ignore rscc, rscd, rsce, rscl, rsct, skoid, sktid, skdutid, sduoid */
namespace Azure { namespace Storage { namespace Sas {
@ -261,10 +261,14 @@ namespace Azure { namespace Storage { namespace Sas {
+ canonicalName + "\n" + userDelegationKey.SignedObjectId + "\n"
+ userDelegationKey.SignedTenantId + "\n" + signedStartsOnStr + "\n" + signedExpiresOnStr
+ "\n" + userDelegationKey.SignedService + "\n" + userDelegationKey.SignedVersion
+ "\n\n\n\n\n" + DelegatedUserObjectId + "\n" + (IPRange.HasValue() ? IPRange.Value() : "")
+ "\n" + protocol + "\n" + SasVersion + "\n" + resource + "\n" + snapshotVersion + "\n"
+ EncryptionScope + "\n" + CacheControl + "\n" + ContentDisposition + "\n" + ContentEncoding
+ "\n" + ContentLanguage + "\n" + ContentType;
+ "\n\n\n\n"
+ (userDelegationKey.SignedDelegatedUserTid.HasValue()
? userDelegationKey.SignedDelegatedUserTid.Value()
: "")
+ "\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;
std::string signature = Azure::Core::Convert::Base64Encode(_internal::HmacSha256(
std::vector<uint8_t>(stringToSign.begin(), stringToSign.end()),
@ -294,6 +298,12 @@ namespace Azure { namespace Storage { namespace Sas {
"sks", _internal::UrlEncodeQueryParameter(userDelegationKey.SignedService));
builder.AppendQueryParameter(
"skv", _internal::UrlEncodeQueryParameter(userDelegationKey.SignedVersion));
if (userDelegationKey.SignedDelegatedUserTid.HasValue())
{
builder.AppendQueryParameter(
"skdutid",
_internal::UrlEncodeQueryParameter(userDelegationKey.SignedDelegatedUserTid.Value()));
}
if (!DelegatedUserObjectId.empty())
{
builder.AppendQueryParameter(
@ -402,10 +412,14 @@ namespace Azure { namespace Storage { namespace Sas {
return Permissions + "\n" + startsOnStr + "\n" + expiresOnStr + "\n" + canonicalName + "\n"
+ userDelegationKey.SignedObjectId + "\n" + userDelegationKey.SignedTenantId + "\n"
+ signedStartsOnStr + "\n" + signedExpiresOnStr + "\n" + userDelegationKey.SignedService
+ "\n" + userDelegationKey.SignedVersion + "\n\n\n\n\n" + DelegatedUserObjectId + "\n"
+ (IPRange.HasValue() ? IPRange.Value() : "") + "\n" + protocol + "\n" + SasVersion + "\n"
+ resource + "\n" + snapshotVersion + "\n" + EncryptionScope + "\n" + CacheControl + "\n"
+ ContentDisposition + "\n" + ContentEncoding + "\n" + ContentLanguage + "\n" + ContentType;
+ "\n" + userDelegationKey.SignedVersion + "\n\n\n\n"
+ (userDelegationKey.SignedDelegatedUserTid.HasValue()
? userDelegationKey.SignedDelegatedUserTid.Value()
: "")
+ "\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;
}
}}} // namespace Azure::Storage::Sas

View File

@ -184,6 +184,7 @@ namespace Azure { namespace Storage { namespace Blobs {
Azure::DateTime::DateFormat::Rfc3339, Azure::DateTime::TimeFractionFormat::Truncate);
protocolLayerOptions.KeyInfo.Expiry = expiresOn.ToString(
Azure::DateTime::DateFormat::Rfc3339, Azure::DateTime::TimeFractionFormat::Truncate);
protocolLayerOptions.KeyInfo.DelegatedUserTid = options.DelegatedUserTid;
return _detail::ServiceClient::GetUserDelegationKey(
*m_pipeline, m_serviceUrl, protocolLayerOptions, _internal::WithReplicaStatus(context));
}

View File

@ -880,7 +880,7 @@ namespace Azure { namespace Storage { namespace Test {
return {};
}
TEST_F(BlobSasTest, DISABLED_PrincipalBoundDelegationSas)
TEST_F(BlobSasTest, PrincipalBoundDelegationSas_LIVEONLY_)
{
auto sasStartsOn = std::chrono::system_clock::now() - std::chrono::minutes(5);
auto sasExpiresOn = std::chrono::system_clock::now() + std::chrono::minutes(60);
@ -930,4 +930,67 @@ namespace Azure { namespace Storage { namespace Test {
InitStorageClientOptions<Blobs::BlobClientOptions>());
EXPECT_THROW(blobClient2.GetProperties(), StorageException);
}
TEST_F(BlobSasTest, DISABLED_PrincipalBoundDelegationSas_CrossTenant)
{
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;
Azure::Identity::ClientSecretCredentialOptions credentialOptions;
credentialOptions.AdditionallyAllowedTenants = {"*"};
auto endUserCredential = std::make_shared<Azure::Identity::ClientSecretCredential>(
GetEnv("AZURE_TENANT_ID_CROSS_TENANT"),
GetEnv("AZURE_CLIENT_ID_CROSS_TENANT"),
GetEnv("AZURE_CLIENT_SECRET_CROSS_TENANT"));
auto delegatedUserObjectId = getObjectIdFromTokenCredential(endUserCredential);
auto blobServiceClient = Blobs::BlobServiceClient(
m_blobServiceClient->GetUrl(),
GetTestCredential(),
InitStorageClientOptions<Blobs::BlobClientOptions>());
Blobs::Models::UserDelegationKey userDelegationKey;
{
Blobs::GetUserDelegationKeyOptions options;
options.DelegatedUserTid = "4ab3a968-f1ae-47a6-b82c-f654612122a9";
userDelegationKey = blobServiceClient.GetUserDelegationKey(sasExpiresOn, options).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.DelegatedUserObjectId = delegatedUserObjectId;
blobSasBuilder.SetPermissions(Sas::BlobSasPermissions::All);
auto sasToken = blobSasBuilder.GenerateSasToken(userDelegationKey, accountName);
Blobs::BlockBlobClient blobClient1(
AppendQueryParameters(Azure::Core::Url(blobClient.GetUrl()), sasToken),
endUserCredential,
InitStorageClientOptions<Blobs::BlobClientOptions>());
EXPECT_NO_THROW(blobClient1.Download());
{
Blobs::GetUserDelegationKeyOptions options;
// Invalid Tenant Id
options.DelegatedUserTid = "00000000-0000-0000-0000-000000000000";
userDelegationKey = blobServiceClient.GetUserDelegationKey(sasExpiresOn, options).Value;
}
sasToken = blobSasBuilder.GenerateSasToken(userDelegationKey, accountName);
Blobs::BlockBlobClient blobClient2(
AppendQueryParameters(Azure::Core::Url(blobClient.GetUrl()), sasToken),
GetTestCredential(),
InitStorageClientOptions<Blobs::BlobClientOptions>());
EXPECT_THROW(blobClient2.Download(), StorageException);
}
}}} // namespace Azure::Storage::Test

View File

@ -321,7 +321,7 @@ namespace Azure { namespace Storage { namespace Test {
EXPECT_EQ(
downloadedProperties.DefaultServiceVersion.HasValue(),
properties.DefaultServiceVersion.HasValue());
if (downloadedProperties.DefaultServiceVersion.HasValue())
if (downloadedProperties.DefaultServiceVersion.HasValue() && !m_testContext.IsPlaybackMode())
{
EXPECT_EQ(
downloadedProperties.DefaultServiceVersion.Value(),

View File

@ -27,7 +27,7 @@ namespace Azure { namespace Storage { namespace Files { namespace DataLake {
/**
* The version used for the operations to Azure storage services.
*/
constexpr static const char* ApiVersion = "2026-02-06";
constexpr static const char* ApiVersion = "2026-04-06";
} // namespace _detail
namespace Models {
namespace _detail {

View File

@ -6,7 +6,7 @@
#include <azure/core/http/http.hpp>
#include <azure/storage/common/crypt.hpp>
/* cSpell:ignore rscc, rscd, rsce, rscl, rsct, skoid, sktid, saoid, suoid, scid, sduoid */
/* cSpell:ignore rscc, rscd, rsce, rscl, rsct, skoid, sktid, saoid, suoid, scid, skdutid, sduoid */
namespace Azure { namespace Storage { namespace Sas {
namespace {
@ -226,9 +226,12 @@ namespace Azure { namespace Storage { namespace Sas {
+ canonicalName + "\n" + userDelegationKey.SignedObjectId + "\n"
+ userDelegationKey.SignedTenantId + "\n" + signedStartsOnStr + "\n" + signedExpiresOnStr
+ "\n" + userDelegationKey.SignedService + "\n" + userDelegationKey.SignedVersion + "\n"
+ PreauthorizedAgentObjectId + "\n" + AgentObjectId + "\n" + CorrelationId + "\n" + "\n"
+ DelegatedUserObjectId + "\n" + (IPRange.HasValue() ? IPRange.Value() : "") + "\n"
+ protocol + "\n" + SasVersion + "\n" + resource + "\n" + "\n" + EncryptionScope + "\n"
+ PreauthorizedAgentObjectId + "\n" + AgentObjectId + "\n" + CorrelationId + "\n"
+ (userDelegationKey.SignedDelegatedUserTid.HasValue()
? 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;
@ -273,6 +276,12 @@ namespace Azure { namespace Storage { namespace Sas {
{
builder.AppendQueryParameter("scid", _internal::UrlEncodeQueryParameter(CorrelationId));
}
if (userDelegationKey.SignedDelegatedUserTid.HasValue())
{
builder.AppendQueryParameter(
"skdutid",
_internal::UrlEncodeQueryParameter(userDelegationKey.SignedDelegatedUserTid.Value()));
}
if (!DelegatedUserObjectId.empty())
{
builder.AppendQueryParameter(
@ -365,10 +374,14 @@ namespace Azure { namespace Storage { namespace Sas {
+ userDelegationKey.SignedObjectId + "\n" + userDelegationKey.SignedTenantId + "\n"
+ signedStartsOnStr + "\n" + signedExpiresOnStr + "\n" + userDelegationKey.SignedService
+ "\n" + userDelegationKey.SignedVersion + "\n" + PreauthorizedAgentObjectId + "\n"
+ AgentObjectId + "\n" + CorrelationId + "\n\n" + DelegatedUserObjectId + "\n"
+ (IPRange.HasValue() ? IPRange.Value() : "") + "\n" + protocol + "\n" + SasVersion + "\n"
+ resource + "\n" + "\n" + EncryptionScope + "\n" + CacheControl + "\n" + ContentDisposition
+ "\n" + ContentEncoding + "\n" + ContentLanguage + "\n" + ContentType;
+ AgentObjectId + "\n" + CorrelationId + "\n"
+ (userDelegationKey.SignedDelegatedUserTid.HasValue()
? 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;
}
}}} // namespace Azure::Storage::Sas

View File

@ -61,7 +61,7 @@ namespace Azure { namespace Storage { namespace Files { namespace DataLake {
{
request.GetUrl().AppendQueryParameter("timeout", std::to_string(options.Timeout.Value()));
}
request.SetHeader("x-ms-version", "2026-02-06");
request.SetHeader("x-ms-version", "2026-04-06");
if (options.ContinuationToken.HasValue() && !options.ContinuationToken.Value().empty())
{
request.GetUrl().AppendQueryParameter(
@ -162,7 +162,7 @@ namespace Azure { namespace Storage { namespace Files { namespace DataLake {
{
request.GetUrl().AppendQueryParameter("timeout", std::to_string(options.Timeout.Value()));
}
request.SetHeader("x-ms-version", "2026-02-06");
request.SetHeader("x-ms-version", "2026-04-06");
if (options.Resource.HasValue() && !options.Resource.Value().ToString().empty())
{
request.GetUrl().AppendQueryParameter(
@ -350,7 +350,7 @@ namespace Azure { namespace Storage { namespace Files { namespace DataLake {
{
request.GetUrl().AppendQueryParameter("timeout", std::to_string(options.Timeout.Value()));
}
request.SetHeader("x-ms-version", "2026-02-06");
request.SetHeader("x-ms-version", "2026-04-06");
if (options.Recursive.HasValue())
{
request.GetUrl().AppendQueryParameter(
@ -448,7 +448,7 @@ namespace Azure { namespace Storage { namespace Files { namespace DataLake {
"If-Unmodified-Since",
options.IfUnmodifiedSince.Value().ToString(Azure::DateTime::DateFormat::Rfc1123));
}
request.SetHeader("x-ms-version", "2026-02-06");
request.SetHeader("x-ms-version", "2026-04-06");
auto pRawResponse = pipeline.Send(request, context);
auto httpStatusCode = pRawResponse->GetStatusCode();
if (httpStatusCode != Core::Http::HttpStatusCode::Ok)
@ -495,7 +495,7 @@ namespace Azure { namespace Storage { namespace Files { namespace DataLake {
{
request.SetHeader("x-ms-acl", options.Acl.Value());
}
request.SetHeader("x-ms-version", "2026-02-06");
request.SetHeader("x-ms-version", "2026-04-06");
auto pRawResponse = pipeline.Send(request, context);
auto httpStatusCode = pRawResponse->GetStatusCode();
if (httpStatusCode != Core::Http::HttpStatusCode::Ok)
@ -548,7 +548,7 @@ namespace Azure { namespace Storage { namespace Files { namespace DataLake {
{
request.SetHeader("x-ms-undelete-source", options.UndeleteSource.Value());
}
request.SetHeader("x-ms-version", "2026-02-06");
request.SetHeader("x-ms-version", "2026-04-06");
auto pRawResponse = pipeline.Send(request, context);
auto httpStatusCode = pRawResponse->GetStatusCode();
if (httpStatusCode != Core::Http::HttpStatusCode::Ok)
@ -599,7 +599,7 @@ namespace Azure { namespace Storage { namespace Files { namespace DataLake {
"If-Unmodified-Since",
options.IfUnmodifiedSince.Value().ToString(Azure::DateTime::DateFormat::Rfc1123));
}
request.SetHeader("x-ms-version", "2026-02-06");
request.SetHeader("x-ms-version", "2026-04-06");
auto pRawResponse = pipeline.Send(request, context);
auto httpStatusCode = pRawResponse->GetStatusCode();
if (httpStatusCode != Core::Http::HttpStatusCode::Ok)
@ -698,7 +698,7 @@ namespace Azure { namespace Storage { namespace Files { namespace DataLake {
"If-Unmodified-Since",
options.IfUnmodifiedSince.Value().ToString(Azure::DateTime::DateFormat::Rfc1123));
}
request.SetHeader("x-ms-version", "2026-02-06");
request.SetHeader("x-ms-version", "2026-04-06");
if (options.EncryptionKey.HasValue() && !options.EncryptionKey.Value().empty())
{
request.SetHeader("x-ms-encryption-key", options.EncryptionKey.Value());
@ -782,7 +782,7 @@ namespace Azure { namespace Storage { namespace Files { namespace DataLake {
{
request.SetHeader("x-ms-proposed-lease-id", options.ProposedLeaseId.Value());
}
request.SetHeader("x-ms-version", "2026-02-06");
request.SetHeader("x-ms-version", "2026-04-06");
if (options.EncryptionKey.HasValue() && !options.EncryptionKey.Value().empty())
{
request.SetHeader("x-ms-encryption-key", options.EncryptionKey.Value());

View File

@ -88,12 +88,12 @@ directive:
"name": "ApiVersion",
"modelAsString": false
},
"enum": ["2026-02-06"]
"enum": ["2026-04-06"]
};
- from: swagger-document
where: $.parameters
transform: >
$.ApiVersionParameter.enum[0] = "2026-02-06";
$.ApiVersionParameter.enum[0] = "2026-04-06";
```
### Rename Operations

View File

@ -886,7 +886,7 @@ namespace Azure { namespace Storage { namespace Test {
return {};
}
TEST_F(DataLakeSasTest, DISABLED_PrincipalBoundDelegationSas)
TEST_F(DataLakeSasTest, PrincipalBoundDelegationSas_LIVEONLY_)
{
auto sasStartsOn = std::chrono::system_clock::now() - std::chrono::minutes(5);
auto sasExpiresOn = std::chrono::system_clock::now() + std::chrono::minutes(60);
@ -932,4 +932,61 @@ namespace Azure { namespace Storage { namespace Test {
InitStorageClientOptions<Files::DataLake::DataLakeClientOptions>());
EXPECT_THROW(fileClient2.GetProperties(), StorageException);
}
TEST_F(DataLakeSasTest, DISABLED_PrincipalBoundDelegationSas_CrossTenant)
{
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;
Azure::Identity::ClientSecretCredentialOptions credentialOptions;
credentialOptions.AdditionallyAllowedTenants = {"*"};
auto endUserCredential = std::make_shared<Azure::Identity::ClientSecretCredential>(
GetEnv("AZURE_TENANT_ID_CROSS_TENANT"),
GetEnv("AZURE_CLIENT_ID_CROSS_TENANT"),
GetEnv("AZURE_CLIENT_SECRET_CROSS_TENANT"));
auto delegatedUserObjectId = getObjectIdFromTokenCredential(endUserCredential);
Files::DataLake::GetUserDelegationKeyOptions options;
options.DelegatedUserTid = "4ab3a968-f1ae-47a6-b82c-f654612122a9";
Files::DataLake::Models::UserDelegationKey userDelegationKey
= GetDataLakeServiceClientOAuth().GetUserDelegationKey(sasExpiresOn, options).Value;
std::string fileName = RandomString();
auto dataLakeFileSystemClient = *m_fileSystemClient;
auto dataLakeFileClient = dataLakeFileSystemClient.GetFileClient(fileName);
dataLakeFileClient.Create();
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.DelegatedUserObjectId = delegatedUserObjectId;
fileSasBuilder.SetPermissions(Sas::DataLakeSasPermissions::All);
auto sasToken = fileSasBuilder.GenerateSasToken(userDelegationKey, accountName);
Files::DataLake::DataLakeFileClient fileClient1(
AppendQueryParameters(Azure::Core::Url(dataLakeFileClient.GetUrl()), sasToken),
endUserCredential,
InitStorageClientOptions<Files::DataLake::DataLakeClientOptions>());
EXPECT_NO_THROW(fileClient1.GetProperties());
options.DelegatedUserTid = "00000000-0000-0000-0000-000000000000";
userDelegationKey
= GetDataLakeServiceClientOAuth().GetUserDelegationKey(sasExpiresOn, options).Value;
sasToken = fileSasBuilder.GenerateSasToken(userDelegationKey, accountName);
Files::DataLake::DataLakeFileClient fileClient2(
AppendQueryParameters(Azure::Core::Url(dataLakeFileClient.GetUrl()), sasToken),
endUserCredential,
InitStorageClientOptions<Files::DataLake::DataLakeClientOptions>());
EXPECT_THROW(fileClient2.GetProperties(), StorageException);
}
}}} // namespace Azure::Storage::Test

View File

@ -377,6 +377,11 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares {
* will be truncated to second.
*/
Azure::DateTime StartsOn = std::chrono::system_clock::now();
/**
* The delegated user tenant id in Azure AD.
*/
Nullable<std::string> DelegatedUserTid;
};
/**

View File

@ -8,7 +8,7 @@
#include <azure/core/http/http.hpp>
#include <azure/storage/common/crypt.hpp>
/* cSpell:ignore rscc, rscd, rsce, rscl, rsct, skoid, sktid, sduoid */
/* cSpell:ignore rscc, rscd, rsce, rscl, rsct, skoid, sktid, skdutid, sduoid */
namespace Azure { namespace Storage { namespace Sas {
@ -185,8 +185,11 @@ namespace Azure { namespace Storage { namespace Sas {
std::string stringToSign = Permissions + "\n" + startsOnStr + "\n" + expiresOnStr + "\n"
+ canonicalName + "\n" + userDelegationKey.SignedObjectId + "\n"
+ userDelegationKey.SignedTenantId + "\n" + signedStartsOnStr + "\n" + signedExpiresOnStr
+ "\n" + userDelegationKey.SignedService + "\n" + userDelegationKey.SignedVersion + "\n\n"
+ DelegatedUserObjectId + "\n" + (IPRange.HasValue() ? IPRange.Value() : "") + "\n"
+ "\n" + userDelegationKey.SignedService + "\n" + userDelegationKey.SignedVersion + "\n"
+ (userDelegationKey.SignedDelegatedUserTid.HasValue()
? userDelegationKey.SignedDelegatedUserTid.Value()
: "")
+ "\n" + DelegatedUserObjectId + "\n" + (IPRange.HasValue() ? IPRange.Value() : "") + "\n"
+ protocol + "\n" + SasVersion + "\n" + CacheControl + "\n" + ContentDisposition + "\n"
+ ContentEncoding + "\n" + ContentLanguage + "\n" + ContentType;
@ -224,6 +227,12 @@ namespace Azure { namespace Storage { namespace Sas {
"sks", _internal::UrlEncodeQueryParameter(userDelegationKey.SignedService));
builder.AppendQueryParameter(
"skv", _internal::UrlEncodeQueryParameter(userDelegationKey.SignedVersion));
if (userDelegationKey.SignedDelegatedUserTid.HasValue())
{
builder.AppendQueryParameter(
"skdutid",
_internal::UrlEncodeQueryParameter(userDelegationKey.SignedDelegatedUserTid.Value()));
}
if (!DelegatedUserObjectId.empty())
{
builder.AppendQueryParameter(
@ -307,10 +316,13 @@ namespace Azure { namespace Storage { namespace Sas {
return Permissions + "\n" + startsOnStr + "\n" + expiresOnStr + "\n" + canonicalName + "\n"
+ userDelegationKey.SignedObjectId + "\n" + userDelegationKey.SignedTenantId + "\n"
+ signedStartsOnStr + "\n" + signedExpiresOnStr + "\n" + userDelegationKey.SignedService
+ "\n" + userDelegationKey.SignedVersion + "\n\n" + DelegatedUserObjectId + "\n"
+ (IPRange.HasValue() ? IPRange.Value() : "") + "\n" + protocol + "\n" + SasVersion + "\n"
+ CacheControl + "\n" + ContentDisposition + "\n" + ContentEncoding + "\n" + ContentLanguage
+ "\n" + ContentType;
+ "\n" + userDelegationKey.SignedVersion + "\n"
+ (userDelegationKey.SignedDelegatedUserTid.HasValue()
? userDelegationKey.SignedDelegatedUserTid.Value()
: "")
+ "\n" + DelegatedUserObjectId + "\n" + (IPRange.HasValue() ? IPRange.Value() : "") + "\n"
+ protocol + "\n" + SasVersion + "\n" + CacheControl + "\n" + ContentDisposition + "\n"
+ ContentEncoding + "\n" + ContentLanguage + "\n" + ContentType;
}
}}} // namespace Azure::Storage::Sas

View File

@ -194,6 +194,7 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares {
Azure::DateTime::DateFormat::Rfc3339, Azure::DateTime::TimeFractionFormat::Truncate);
protocolLayerOptions.KeyInfo.Expiry = expiresOn.ToString(
Azure::DateTime::DateFormat::Rfc3339, Azure::DateTime::TimeFractionFormat::Truncate);
protocolLayerOptions.KeyInfo.DelegatedUserTid = options.DelegatedUserTid;
return _detail::ServiceClient::GetUserDelegationKey(
*m_pipeline, m_serviceUrl, protocolLayerOptions, context);
}

View File

@ -753,7 +753,7 @@ namespace Azure { namespace Storage { namespace Test {
return {};
}
TEST_F(ShareSasTest, DISABLED_PrincipalBoundDelegationSas)
TEST_F(ShareSasTest, PrincipalBoundDelegationSas_LIVEONLY_)
{
auto sasStartsOn = std::chrono::system_clock::now() - std::chrono::minutes(5);
auto sasExpiresOn = std::chrono::system_clock::now() + std::chrono::minutes(60);
@ -791,10 +791,12 @@ namespace Azure { namespace Storage { namespace Test {
fileSasBuilder.SetPermissions(Sas::ShareFileSasPermissions::All);
auto sasToken = fileSasBuilder.GenerateSasToken(userDelegationKey, accountName);
auto fileOptions = InitStorageClientOptions<Files::Shares::ShareClientOptions>();
fileOptions.ShareTokenIntent = Files::Shares::Models::ShareTokenIntent::Backup;
Files::Shares::ShareFileClient fileClient1(
AppendQueryParameters(Azure::Core::Url(fileClient.GetUrl()), sasToken),
GetTestCredential(),
InitStorageClientOptions<Files::Shares::ShareClientOptions>());
fileOptions);
EXPECT_NO_THROW(fileClient1.GetProperties());
fileSasBuilder.DelegatedUserObjectId = "invalidObjectId";
@ -802,7 +804,73 @@ namespace Azure { namespace Storage { namespace Test {
Files::Shares::ShareFileClient fileClient2(
AppendQueryParameters(Azure::Core::Url(fileClient.GetUrl()), sasToken),
GetTestCredential(),
fileOptions);
EXPECT_THROW(fileClient2.GetProperties(), StorageException);
}
TEST_F(ShareSasTest, DISABLED_PrincipalBoundDelegationSas_CrossTenant)
{
auto sasStartsOn = std::chrono::system_clock::now() - std::chrono::minutes(5);
auto sasExpiresOn = std::chrono::system_clock::now() + std::chrono::minutes(60);
std::string fileName = RandomString();
auto keyCredential
= _internal::ParseConnectionString(StandardStorageConnectionString()).KeyCredential;
auto accountName = keyCredential->AccountName;
Azure::Identity::ClientSecretCredentialOptions credentialOptions;
credentialOptions.AdditionallyAllowedTenants = {"*"};
auto endUserCredential = std::make_shared<Azure::Identity::ClientSecretCredential>(
GetEnv("AZURE_TENANT_ID_CROSS_TENANT"),
GetEnv("AZURE_CLIENT_ID_CROSS_TENANT"),
GetEnv("AZURE_CLIENT_SECRET_CROSS_TENANT"));
auto delegatedUserObjectId = getObjectIdFromTokenCredential(endUserCredential);
auto shareServiceClient = Files::Shares::ShareServiceClient(
m_shareServiceClient->GetUrl(),
GetTestCredential(),
InitStorageClientOptions<Files::Shares::ShareClientOptions>());
Files::Shares::Models::UserDelegationKey userDelegationKey;
{
Files::Shares::GetUserDelegationKeyOptions options;
options.DelegatedUserTid = "4ab3a968-f1ae-47a6-b82c-f654612122a9";
userDelegationKey = shareServiceClient.GetUserDelegationKey(sasExpiresOn, options).Value;
}
Sas::ShareSasBuilder fileSasBuilder;
fileSasBuilder.Protocol = Sas::SasProtocol::HttpsAndHttp;
fileSasBuilder.StartsOn = sasStartsOn;
fileSasBuilder.ExpiresOn = sasExpiresOn;
fileSasBuilder.ShareName = m_shareName;
fileSasBuilder.FilePath = fileName;
fileSasBuilder.Resource = Sas::ShareSasResource::File;
fileSasBuilder.DelegatedUserObjectId = delegatedUserObjectId;
auto shareClient = *m_shareClient;
auto fileClient = shareClient.GetRootDirectoryClient().GetFileClient(fileName);
fileClient.Create(1);
auto fileOptions = InitStorageClientOptions<Files::Shares::ShareClientOptions>();
fileOptions.ShareTokenIntent = Files::Shares::Models::ShareTokenIntent::Backup;
fileSasBuilder.SetPermissions(Sas::ShareFileSasPermissions::All);
auto sasToken = fileSasBuilder.GenerateSasToken(userDelegationKey, accountName);
Files::Shares::ShareFileClient fileClient1(
AppendQueryParameters(Azure::Core::Url(fileClient.GetUrl()), sasToken),
endUserCredential,
fileOptions);
EXPECT_NO_THROW(fileClient1.GetProperties());
{
Files::Shares::GetUserDelegationKeyOptions options;
options.DelegatedUserTid = "00000000-0000-0000-0000-000000000000";
userDelegationKey = shareServiceClient.GetUserDelegationKey(sasExpiresOn, options).Value;
}
sasToken = fileSasBuilder.GenerateSasToken(userDelegationKey, accountName);
Files::Shares::ShareFileClient fileClient2(
AppendQueryParameters(Azure::Core::Url(fileClient.GetUrl()), sasToken),
endUserCredential,
fileOptions);
EXPECT_THROW(fileClient2.GetProperties(), StorageException);
}
}}} // namespace Azure::Storage::Test

View File

@ -208,6 +208,11 @@ namespace Azure { namespace Storage { namespace Queues {
* will be truncated to second.
*/
Azure::DateTime StartsOn = std::chrono::system_clock::now();
/**
* The delegated user tenant id in Azure AD.
*/
Nullable<std::string> DelegatedUserTid;
};
/**

View File

@ -8,7 +8,7 @@
#include <azure/core/http/http.hpp>
#include <azure/storage/common/crypt.hpp>
/* cSpell:ignore skoid, sktid, sduoid */
/* cSpell:ignore skoid, sktid, skdutid, sduoid */
namespace Azure { namespace Storage { namespace Sas {
@ -113,8 +113,11 @@ namespace Azure { namespace Storage { namespace Sas {
std::string stringToSign = Permissions + "\n" + startsOnStr + "\n" + expiresOnStr + "\n"
+ canonicalName + "\n" + userDelegationKey.SignedObjectId + "\n"
+ userDelegationKey.SignedTenantId + "\n" + signedStartsOnStr + "\n" + signedExpiresOnStr
+ "\n" + userDelegationKey.SignedService + "\n" + userDelegationKey.SignedVersion + "\n\n"
+ DelegatedUserObjectId + "\n" + (IPRange.HasValue() ? IPRange.Value() : "") + "\n"
+ "\n" + userDelegationKey.SignedService + "\n" + userDelegationKey.SignedVersion + "\n"
+ (userDelegationKey.SignedDelegatedUserTid.HasValue()
? userDelegationKey.SignedDelegatedUserTid.Value()
: "")
+ "\n" + DelegatedUserObjectId + "\n" + (IPRange.HasValue() ? IPRange.Value() : "") + "\n"
+ protocol + "\n" + SasVersion;
std::string signature = Azure::Core::Convert::Base64Encode(_internal::HmacSha256(
@ -150,6 +153,12 @@ namespace Azure { namespace Storage { namespace Sas {
"sks", _internal::UrlEncodeQueryParameter(userDelegationKey.SignedService));
builder.AppendQueryParameter(
"skv", _internal::UrlEncodeQueryParameter(userDelegationKey.SignedVersion));
if (userDelegationKey.SignedDelegatedUserTid.HasValue())
{
builder.AppendQueryParameter(
"skdutid",
_internal::UrlEncodeQueryParameter(userDelegationKey.SignedDelegatedUserTid.Value()));
}
if (!DelegatedUserObjectId.empty())
{
builder.AppendQueryParameter(
@ -204,7 +213,11 @@ namespace Azure { namespace Storage { namespace Sas {
return Permissions + "\n" + startsOnStr + "\n" + expiresOnStr + "\n" + canonicalName + "\n"
+ userDelegationKey.SignedObjectId + "\n" + userDelegationKey.SignedTenantId + "\n"
+ signedStartsOnStr + "\n" + signedExpiresOnStr + "\n" + userDelegationKey.SignedService
+ "\n" + userDelegationKey.SignedVersion + "\n\n" + DelegatedUserObjectId + "\n"
+ (IPRange.HasValue() ? IPRange.Value() : "") + "\n" + protocol + "\n" + SasVersion;
+ "\n" + userDelegationKey.SignedVersion + "\n"
+ (userDelegationKey.SignedDelegatedUserTid.HasValue()
? userDelegationKey.SignedDelegatedUserTid.Value()
: "")
+ "\n" + DelegatedUserObjectId + "\n" + (IPRange.HasValue() ? IPRange.Value() : "") + "\n"
+ protocol + "\n" + SasVersion;
}
}}} // namespace Azure::Storage::Sas

View File

@ -203,6 +203,7 @@ namespace Azure { namespace Storage { namespace Queues {
Azure::DateTime::DateFormat::Rfc3339, Azure::DateTime::TimeFractionFormat::Truncate);
protocolLayerOptions.KeyInfo.Expiry = expiresOn.ToString(
Azure::DateTime::DateFormat::Rfc3339, Azure::DateTime::TimeFractionFormat::Truncate);
protocolLayerOptions.KeyInfo.DelegatedUserTid = options.DelegatedUserTid;
return _detail::ServiceClient::GetUserDelegationKey(
*m_pipeline, m_serviceUrl, protocolLayerOptions, context);
}

View File

@ -499,7 +499,7 @@ namespace Azure { namespace Storage { namespace Test {
return {};
}
TEST_F(QueueSasTest, DISABLED_PrincipalBoundDelegationSas)
TEST_F(QueueSasTest, PrincipalBoundDelegationSas_LIVEONLY_)
{
auto sasStartsOn = std::chrono::system_clock::now() - std::chrono::minutes(5);
auto sasExpiresOn = std::chrono::system_clock::now() + std::chrono::minutes(60);
@ -546,4 +546,61 @@ namespace Azure { namespace Storage { namespace Test {
EXPECT_THROW(queueClient2.GetProperties(), StorageException);
}
TEST_F(QueueSasTest, DISABLED_PrincipalBoundDelegationSas_CrossTenant)
{
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;
Azure::Identity::ClientSecretCredentialOptions credentialOptions;
credentialOptions.AdditionallyAllowedTenants = {"*"};
auto endUserCredential = std::make_shared<Azure::Identity::ClientSecretCredential>(
GetEnv("AZURE_TENANT_ID_CROSS_TENANT"),
GetEnv("AZURE_CLIENT_ID_CROSS_TENANT"),
GetEnv("AZURE_CLIENT_SECRET_CROSS_TENANT"));
auto delegatedUserObjectId = getObjectIdFromTokenCredential(endUserCredential);
auto queueServiceClient = Queues::QueueServiceClient(
m_queueServiceClient->GetUrl(),
GetTestCredential(),
InitStorageClientOptions<Queues::QueueClientOptions>());
Queues::Models::UserDelegationKey userDelegationKey;
{
Queues::GetUserDelegationKeyOptions options;
options.DelegatedUserTid = "4ab3a968-f1ae-47a6-b82c-f654612122a9";
userDelegationKey = queueServiceClient.GetUserDelegationKey(sasExpiresOn, options).Value;
}
Sas::QueueSasBuilder queueSasBuilder;
queueSasBuilder.Protocol = Sas::SasProtocol::HttpsAndHttp;
queueSasBuilder.StartsOn = sasStartsOn;
queueSasBuilder.ExpiresOn = sasExpiresOn;
queueSasBuilder.QueueName = m_queueName;
queueSasBuilder.DelegatedUserObjectId = delegatedUserObjectId;
auto queueClient = *m_queueClient;
auto accountName = keyCredential->AccountName;
queueSasBuilder.SetPermissions(Sas::QueueSasPermissions::All);
auto sasToken = queueSasBuilder.GenerateSasToken(userDelegationKey, accountName);
Queues::QueueClient queueClient1(
AppendQueryParameters(Azure::Core::Url(queueClient.GetUrl()), sasToken),
endUserCredential,
InitStorageClientOptions<Queues::QueueClientOptions>());
EXPECT_NO_THROW(queueClient1.GetProperties());
{
Queues::GetUserDelegationKeyOptions options;
options.DelegatedUserTid = "00000000-0000-0000-0000-000000000000";
userDelegationKey = queueServiceClient.GetUserDelegationKey(sasExpiresOn, options).Value;
}
sasToken = queueSasBuilder.GenerateSasToken(userDelegationKey, accountName);
Queues::QueueClient queueClient2(
AppendQueryParameters(Azure::Core::Url(queueClient.GetUrl()), sasToken),
endUserCredential,
InitStorageClientOptions<Queues::QueueClientOptions>());
EXPECT_THROW(queueClient2.GetProperties(), StorageException);
}
}}} // namespace Azure::Storage::Test