Skip to content

Commit 464b57b

Browse files
committed
Storage/STG101/Cross-tenant principal bound sas (#6863)
* Cross-tenant principal bound sas * Fix test failure
1 parent b0108b0 commit 464b57b

File tree

18 files changed

+362
-47
lines changed

18 files changed

+362
-47
lines changed

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,11 @@ namespace Azure { namespace Storage { namespace Blobs {
275275
* will be truncated to second.
276276
*/
277277
Azure::DateTime StartsOn = std::chrono::system_clock::now();
278+
279+
/**
280+
* The delegated user tenant id in Azure AD.
281+
*/
282+
Nullable<std::string> DelegatedUserTid;
278283
};
279284

280285
/**

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

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
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, sduoid */
9+
/* cSpell:ignore rscc, rscd, rsce, rscl, rsct, skoid, sktid, skdutid, sduoid */
1010

1111
namespace Azure { namespace Storage { namespace Sas {
1212

@@ -261,10 +261,14 @@ namespace Azure { namespace Storage { namespace Sas {
261261
+ canonicalName + "\n" + userDelegationKey.SignedObjectId + "\n"
262262
+ userDelegationKey.SignedTenantId + "\n" + signedStartsOnStr + "\n" + signedExpiresOnStr
263263
+ "\n" + userDelegationKey.SignedService + "\n" + userDelegationKey.SignedVersion
264-
+ "\n\n\n\n\n" + DelegatedUserObjectId + "\n" + (IPRange.HasValue() ? IPRange.Value() : "")
265-
+ "\n" + protocol + "\n" + SasVersion + "\n" + resource + "\n" + snapshotVersion + "\n"
266-
+ EncryptionScope + "\n" + CacheControl + "\n" + ContentDisposition + "\n" + ContentEncoding
267-
+ "\n" + ContentLanguage + "\n" + ContentType;
264+
+ "\n\n\n\n"
265+
+ (userDelegationKey.SignedDelegatedUserTid.HasValue()
266+
? userDelegationKey.SignedDelegatedUserTid.Value()
267+
: "")
268+
+ "\n" + DelegatedUserObjectId + "\n" + (IPRange.HasValue() ? IPRange.Value() : "") + "\n"
269+
+ protocol + "\n" + SasVersion + "\n" + resource + "\n" + snapshotVersion + "\n"
270+
+ EncryptionScope + "\n\n\n" + CacheControl + "\n" + ContentDisposition + "\n"
271+
+ ContentEncoding + "\n" + ContentLanguage + "\n" + ContentType;
268272

269273
std::string signature = Azure::Core::Convert::Base64Encode(_internal::HmacSha256(
270274
std::vector<uint8_t>(stringToSign.begin(), stringToSign.end()),
@@ -294,6 +298,12 @@ namespace Azure { namespace Storage { namespace Sas {
294298
"sks", _internal::UrlEncodeQueryParameter(userDelegationKey.SignedService));
295299
builder.AppendQueryParameter(
296300
"skv", _internal::UrlEncodeQueryParameter(userDelegationKey.SignedVersion));
301+
if (userDelegationKey.SignedDelegatedUserTid.HasValue())
302+
{
303+
builder.AppendQueryParameter(
304+
"skdutid",
305+
_internal::UrlEncodeQueryParameter(userDelegationKey.SignedDelegatedUserTid.Value()));
306+
}
297307
if (!DelegatedUserObjectId.empty())
298308
{
299309
builder.AppendQueryParameter(
@@ -402,10 +412,14 @@ namespace Azure { namespace Storage { namespace Sas {
402412
return Permissions + "\n" + startsOnStr + "\n" + expiresOnStr + "\n" + canonicalName + "\n"
403413
+ userDelegationKey.SignedObjectId + "\n" + userDelegationKey.SignedTenantId + "\n"
404414
+ signedStartsOnStr + "\n" + signedExpiresOnStr + "\n" + userDelegationKey.SignedService
405-
+ "\n" + userDelegationKey.SignedVersion + "\n\n\n\n\n" + DelegatedUserObjectId + "\n"
406-
+ (IPRange.HasValue() ? IPRange.Value() : "") + "\n" + protocol + "\n" + SasVersion + "\n"
407-
+ resource + "\n" + snapshotVersion + "\n" + EncryptionScope + "\n" + CacheControl + "\n"
408-
+ ContentDisposition + "\n" + ContentEncoding + "\n" + ContentLanguage + "\n" + ContentType;
415+
+ "\n" + userDelegationKey.SignedVersion + "\n\n\n\n"
416+
+ (userDelegationKey.SignedDelegatedUserTid.HasValue()
417+
? userDelegationKey.SignedDelegatedUserTid.Value()
418+
: "")
419+
+ "\n" + DelegatedUserObjectId + "\n" + (IPRange.HasValue() ? IPRange.Value() : "") + "\n"
420+
+ protocol + "\n" + SasVersion + "\n" + resource + "\n" + snapshotVersion + "\n"
421+
+ EncryptionScope + "\n\n\n" + CacheControl + "\n" + ContentDisposition + "\n"
422+
+ ContentEncoding + "\n" + ContentLanguage + "\n" + ContentType;
409423
}
410424

411425
}}} // namespace Azure::Storage::Sas

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,7 @@ namespace Azure { namespace Storage { namespace Blobs {
184184
Azure::DateTime::DateFormat::Rfc3339, Azure::DateTime::TimeFractionFormat::Truncate);
185185
protocolLayerOptions.KeyInfo.Expiry = expiresOn.ToString(
186186
Azure::DateTime::DateFormat::Rfc3339, Azure::DateTime::TimeFractionFormat::Truncate);
187+
protocolLayerOptions.KeyInfo.DelegatedUserTid = options.DelegatedUserTid;
187188
return _detail::ServiceClient::GetUserDelegationKey(
188189
*m_pipeline, m_serviceUrl, protocolLayerOptions, _internal::WithReplicaStatus(context));
189190
}

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

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -880,7 +880,7 @@ namespace Azure { namespace Storage { namespace Test {
880880
return {};
881881
}
882882

883-
TEST_F(BlobSasTest, DISABLED_PrincipalBoundDelegationSas)
883+
TEST_F(BlobSasTest, PrincipalBoundDelegationSas_LIVEONLY_)
884884
{
885885
auto sasStartsOn = std::chrono::system_clock::now() - std::chrono::minutes(5);
886886
auto sasExpiresOn = std::chrono::system_clock::now() + std::chrono::minutes(60);
@@ -930,4 +930,67 @@ namespace Azure { namespace Storage { namespace Test {
930930
InitStorageClientOptions<Blobs::BlobClientOptions>());
931931
EXPECT_THROW(blobClient2.GetProperties(), StorageException);
932932
}
933+
934+
TEST_F(BlobSasTest, DISABLED_PrincipalBoundDelegationSas_CrossTenant)
935+
{
936+
auto sasStartsOn = std::chrono::system_clock::now() - std::chrono::minutes(5);
937+
auto sasExpiresOn = std::chrono::system_clock::now() + std::chrono::minutes(60);
938+
939+
auto keyCredential
940+
= _internal::ParseConnectionString(StandardStorageConnectionString()).KeyCredential;
941+
auto accountName = keyCredential->AccountName;
942+
Azure::Identity::ClientSecretCredentialOptions credentialOptions;
943+
credentialOptions.AdditionallyAllowedTenants = {"*"};
944+
auto endUserCredential = std::make_shared<Azure::Identity::ClientSecretCredential>(
945+
GetEnv("AZURE_TENANT_ID_CROSS_TENANT"),
946+
GetEnv("AZURE_CLIENT_ID_CROSS_TENANT"),
947+
GetEnv("AZURE_CLIENT_SECRET_CROSS_TENANT"));
948+
auto delegatedUserObjectId = getObjectIdFromTokenCredential(endUserCredential);
949+
950+
auto blobServiceClient = Blobs::BlobServiceClient(
951+
m_blobServiceClient->GetUrl(),
952+
GetTestCredential(),
953+
InitStorageClientOptions<Blobs::BlobClientOptions>());
954+
Blobs::Models::UserDelegationKey userDelegationKey;
955+
{
956+
Blobs::GetUserDelegationKeyOptions options;
957+
options.DelegatedUserTid = "4ab3a968-f1ae-47a6-b82c-f654612122a9";
958+
userDelegationKey = blobServiceClient.GetUserDelegationKey(sasExpiresOn, options).Value;
959+
}
960+
961+
auto blobContainerClient = *m_blobContainerClient;
962+
auto blobClient = *m_blockBlobClient;
963+
const std::string blobName = m_blobName;
964+
965+
Sas::BlobSasBuilder blobSasBuilder;
966+
blobSasBuilder.Protocol = Sas::SasProtocol::HttpsAndHttp;
967+
blobSasBuilder.StartsOn = sasStartsOn;
968+
blobSasBuilder.ExpiresOn = sasExpiresOn;
969+
blobSasBuilder.BlobContainerName = m_containerName;
970+
blobSasBuilder.BlobName = blobName;
971+
blobSasBuilder.Resource = Sas::BlobSasResource::Blob;
972+
blobSasBuilder.DelegatedUserObjectId = delegatedUserObjectId;
973+
974+
blobSasBuilder.SetPermissions(Sas::BlobSasPermissions::All);
975+
auto sasToken = blobSasBuilder.GenerateSasToken(userDelegationKey, accountName);
976+
977+
Blobs::BlockBlobClient blobClient1(
978+
AppendQueryParameters(Azure::Core::Url(blobClient.GetUrl()), sasToken),
979+
endUserCredential,
980+
InitStorageClientOptions<Blobs::BlobClientOptions>());
981+
EXPECT_NO_THROW(blobClient1.Download());
982+
983+
{
984+
Blobs::GetUserDelegationKeyOptions options;
985+
// Invalid Tenant Id
986+
options.DelegatedUserTid = "00000000-0000-0000-0000-000000000000";
987+
userDelegationKey = blobServiceClient.GetUserDelegationKey(sasExpiresOn, options).Value;
988+
}
989+
sasToken = blobSasBuilder.GenerateSasToken(userDelegationKey, accountName);
990+
Blobs::BlockBlobClient blobClient2(
991+
AppendQueryParameters(Azure::Core::Url(blobClient.GetUrl()), sasToken),
992+
GetTestCredential(),
993+
InitStorageClientOptions<Blobs::BlobClientOptions>());
994+
EXPECT_THROW(blobClient2.Download(), StorageException);
995+
}
933996
}}} // namespace Azure::Storage::Test

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,7 @@ namespace Azure { namespace Storage { namespace Test {
321321
EXPECT_EQ(
322322
downloadedProperties.DefaultServiceVersion.HasValue(),
323323
properties.DefaultServiceVersion.HasValue());
324-
if (downloadedProperties.DefaultServiceVersion.HasValue())
324+
if (downloadedProperties.DefaultServiceVersion.HasValue() && !m_testContext.IsPlaybackMode())
325325
{
326326
EXPECT_EQ(
327327
downloadedProperties.DefaultServiceVersion.Value(),

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ namespace Azure { namespace Storage { namespace Files { namespace DataLake {
2727
/**
2828
* The version used for the operations to Azure storage services.
2929
*/
30-
constexpr static const char* ApiVersion = "2026-02-06";
30+
constexpr static const char* ApiVersion = "2026-04-06";
3131
} // namespace _detail
3232
namespace Models {
3333
namespace _detail {

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

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
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, sduoid */
9+
/* cSpell:ignore rscc, rscd, rsce, rscl, rsct, skoid, sktid, saoid, suoid, scid, skdutid, sduoid */
1010

1111
namespace Azure { namespace Storage { namespace Sas {
1212
namespace {
@@ -226,9 +226,12 @@ namespace Azure { namespace Storage { namespace Sas {
226226
+ canonicalName + "\n" + userDelegationKey.SignedObjectId + "\n"
227227
+ userDelegationKey.SignedTenantId + "\n" + signedStartsOnStr + "\n" + signedExpiresOnStr
228228
+ "\n" + userDelegationKey.SignedService + "\n" + userDelegationKey.SignedVersion + "\n"
229-
+ PreauthorizedAgentObjectId + "\n" + AgentObjectId + "\n" + CorrelationId + "\n" + "\n"
230-
+ DelegatedUserObjectId + "\n" + (IPRange.HasValue() ? IPRange.Value() : "") + "\n"
231-
+ protocol + "\n" + SasVersion + "\n" + resource + "\n" + "\n" + EncryptionScope + "\n"
229+
+ PreauthorizedAgentObjectId + "\n" + AgentObjectId + "\n" + CorrelationId + "\n"
230+
+ (userDelegationKey.SignedDelegatedUserTid.HasValue()
231+
? userDelegationKey.SignedDelegatedUserTid.Value()
232+
: "")
233+
+ "\n" + DelegatedUserObjectId + "\n" + (IPRange.HasValue() ? IPRange.Value() : "") + "\n"
234+
+ protocol + "\n" + SasVersion + "\n" + resource + "\n" + "\n" + EncryptionScope + "\n\n\n"
232235
+ CacheControl + "\n" + ContentDisposition + "\n" + ContentEncoding + "\n" + ContentLanguage
233236
+ "\n" + ContentType;
234237

@@ -273,6 +276,12 @@ namespace Azure { namespace Storage { namespace Sas {
273276
{
274277
builder.AppendQueryParameter("scid", _internal::UrlEncodeQueryParameter(CorrelationId));
275278
}
279+
if (userDelegationKey.SignedDelegatedUserTid.HasValue())
280+
{
281+
builder.AppendQueryParameter(
282+
"skdutid",
283+
_internal::UrlEncodeQueryParameter(userDelegationKey.SignedDelegatedUserTid.Value()));
284+
}
276285
if (!DelegatedUserObjectId.empty())
277286
{
278287
builder.AppendQueryParameter(
@@ -365,10 +374,14 @@ namespace Azure { namespace Storage { namespace Sas {
365374
+ userDelegationKey.SignedObjectId + "\n" + userDelegationKey.SignedTenantId + "\n"
366375
+ signedStartsOnStr + "\n" + signedExpiresOnStr + "\n" + userDelegationKey.SignedService
367376
+ "\n" + userDelegationKey.SignedVersion + "\n" + PreauthorizedAgentObjectId + "\n"
368-
+ AgentObjectId + "\n" + CorrelationId + "\n\n" + DelegatedUserObjectId + "\n"
369-
+ (IPRange.HasValue() ? IPRange.Value() : "") + "\n" + protocol + "\n" + SasVersion + "\n"
370-
+ resource + "\n" + "\n" + EncryptionScope + "\n" + CacheControl + "\n" + ContentDisposition
371-
+ "\n" + ContentEncoding + "\n" + ContentLanguage + "\n" + ContentType;
377+
+ AgentObjectId + "\n" + CorrelationId + "\n"
378+
+ (userDelegationKey.SignedDelegatedUserTid.HasValue()
379+
? userDelegationKey.SignedDelegatedUserTid.Value()
380+
: "")
381+
+ "\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;
372385
}
373386

374387
}}} // namespace Azure::Storage::Sas

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

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ namespace Azure { namespace Storage { namespace Files { namespace DataLake {
6161
{
6262
request.GetUrl().AppendQueryParameter("timeout", std::to_string(options.Timeout.Value()));
6363
}
64-
request.SetHeader("x-ms-version", "2026-02-06");
64+
request.SetHeader("x-ms-version", "2026-04-06");
6565
if (options.ContinuationToken.HasValue() && !options.ContinuationToken.Value().empty())
6666
{
6767
request.GetUrl().AppendQueryParameter(
@@ -162,7 +162,7 @@ namespace Azure { namespace Storage { namespace Files { namespace DataLake {
162162
{
163163
request.GetUrl().AppendQueryParameter("timeout", std::to_string(options.Timeout.Value()));
164164
}
165-
request.SetHeader("x-ms-version", "2026-02-06");
165+
request.SetHeader("x-ms-version", "2026-04-06");
166166
if (options.Resource.HasValue() && !options.Resource.Value().ToString().empty())
167167
{
168168
request.GetUrl().AppendQueryParameter(
@@ -350,7 +350,7 @@ namespace Azure { namespace Storage { namespace Files { namespace DataLake {
350350
{
351351
request.GetUrl().AppendQueryParameter("timeout", std::to_string(options.Timeout.Value()));
352352
}
353-
request.SetHeader("x-ms-version", "2026-02-06");
353+
request.SetHeader("x-ms-version", "2026-04-06");
354354
if (options.Recursive.HasValue())
355355
{
356356
request.GetUrl().AppendQueryParameter(
@@ -448,7 +448,7 @@ namespace Azure { namespace Storage { namespace Files { namespace DataLake {
448448
"If-Unmodified-Since",
449449
options.IfUnmodifiedSince.Value().ToString(Azure::DateTime::DateFormat::Rfc1123));
450450
}
451-
request.SetHeader("x-ms-version", "2026-02-06");
451+
request.SetHeader("x-ms-version", "2026-04-06");
452452
auto pRawResponse = pipeline.Send(request, context);
453453
auto httpStatusCode = pRawResponse->GetStatusCode();
454454
if (httpStatusCode != Core::Http::HttpStatusCode::Ok)
@@ -495,7 +495,7 @@ namespace Azure { namespace Storage { namespace Files { namespace DataLake {
495495
{
496496
request.SetHeader("x-ms-acl", options.Acl.Value());
497497
}
498-
request.SetHeader("x-ms-version", "2026-02-06");
498+
request.SetHeader("x-ms-version", "2026-04-06");
499499
auto pRawResponse = pipeline.Send(request, context);
500500
auto httpStatusCode = pRawResponse->GetStatusCode();
501501
if (httpStatusCode != Core::Http::HttpStatusCode::Ok)
@@ -548,7 +548,7 @@ namespace Azure { namespace Storage { namespace Files { namespace DataLake {
548548
{
549549
request.SetHeader("x-ms-undelete-source", options.UndeleteSource.Value());
550550
}
551-
request.SetHeader("x-ms-version", "2026-02-06");
551+
request.SetHeader("x-ms-version", "2026-04-06");
552552
auto pRawResponse = pipeline.Send(request, context);
553553
auto httpStatusCode = pRawResponse->GetStatusCode();
554554
if (httpStatusCode != Core::Http::HttpStatusCode::Ok)
@@ -599,7 +599,7 @@ namespace Azure { namespace Storage { namespace Files { namespace DataLake {
599599
"If-Unmodified-Since",
600600
options.IfUnmodifiedSince.Value().ToString(Azure::DateTime::DateFormat::Rfc1123));
601601
}
602-
request.SetHeader("x-ms-version", "2026-02-06");
602+
request.SetHeader("x-ms-version", "2026-04-06");
603603
auto pRawResponse = pipeline.Send(request, context);
604604
auto httpStatusCode = pRawResponse->GetStatusCode();
605605
if (httpStatusCode != Core::Http::HttpStatusCode::Ok)
@@ -698,7 +698,7 @@ namespace Azure { namespace Storage { namespace Files { namespace DataLake {
698698
"If-Unmodified-Since",
699699
options.IfUnmodifiedSince.Value().ToString(Azure::DateTime::DateFormat::Rfc1123));
700700
}
701-
request.SetHeader("x-ms-version", "2026-02-06");
701+
request.SetHeader("x-ms-version", "2026-04-06");
702702
if (options.EncryptionKey.HasValue() && !options.EncryptionKey.Value().empty())
703703
{
704704
request.SetHeader("x-ms-encryption-key", options.EncryptionKey.Value());
@@ -782,7 +782,7 @@ namespace Azure { namespace Storage { namespace Files { namespace DataLake {
782782
{
783783
request.SetHeader("x-ms-proposed-lease-id", options.ProposedLeaseId.Value());
784784
}
785-
request.SetHeader("x-ms-version", "2026-02-06");
785+
request.SetHeader("x-ms-version", "2026-04-06");
786786
if (options.EncryptionKey.HasValue() && !options.EncryptionKey.Value().empty())
787787
{
788788
request.SetHeader("x-ms-encryption-key", options.EncryptionKey.Value());

sdk/storage/azure-storage-files-datalake/swagger/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,12 +88,12 @@ directive:
8888
"name": "ApiVersion",
8989
"modelAsString": false
9090
},
91-
"enum": ["2026-02-06"]
91+
"enum": ["2026-04-06"]
9292
};
9393
- from: swagger-document
9494
where: $.parameters
9595
transform: >
96-
$.ApiVersionParameter.enum[0] = "2026-02-06";
96+
$.ApiVersionParameter.enum[0] = "2026-04-06";
9797
```
9898
9999
### Rename Operations

0 commit comments

Comments
 (0)